record.rs

 1use std::{
 2    env,
 3    path::{Path, PathBuf},
 4    sync::{Arc, Mutex},
 5    time::Duration,
 6};
 7
 8use anyhow::{Context, Result};
 9use cpal::traits::{DeviceTrait, StreamTrait};
10use rodio::{buffer::SamplesBuffer, conversions::SampleTypeConverter};
11use util::ResultExt;
12
13pub struct CaptureInput {
14    pub name: String,
15    config: cpal::SupportedStreamConfig,
16    samples: Arc<Mutex<Vec<i16>>>,
17    _stream: cpal::Stream,
18}
19
20impl CaptureInput {
21    pub fn start() -> anyhow::Result<Self> {
22        let (device, config) = crate::default_device(true)?;
23        let name = device.name().unwrap_or("<unknown>".to_string());
24        log::info!("Using microphone: {}", name);
25
26        let samples = Arc::new(Mutex::new(Vec::new()));
27        let stream = start_capture(device, config.clone(), samples.clone())?;
28
29        Ok(Self {
30            name,
31            _stream: stream,
32            config,
33            samples,
34        })
35    }
36
37    pub fn finish(self) -> Result<PathBuf> {
38        let name = self.name;
39        let mut path = env::current_dir().context("Could not get current dir")?;
40        path.push(&format!("test_recording_{name}.wav"));
41        log::info!("Test recording written to: {}", path.display());
42        write_out(self.samples, self.config, &path)?;
43        Ok(path)
44    }
45}
46
47fn start_capture(
48    device: cpal::Device,
49    config: cpal::SupportedStreamConfig,
50    samples: Arc<Mutex<Vec<i16>>>,
51) -> Result<cpal::Stream> {
52    let stream = device
53        .build_input_stream_raw(
54            &config.config(),
55            config.sample_format(),
56            move |data, _: &_| {
57                let data = crate::get_sample_data(config.sample_format(), data).log_err();
58                let Some(data) = data else {
59                    return;
60                };
61                samples
62                    .try_lock()
63                    .expect("Only locked after stream ends")
64                    .extend_from_slice(&data);
65            },
66            |err| log::error!("error capturing audio track: {:?}", err),
67            Some(Duration::from_millis(100)),
68        )
69        .context("failed to build input stream")?;
70
71    stream.play()?;
72    Ok(stream)
73}
74
75fn write_out(
76    samples: Arc<Mutex<Vec<i16>>>,
77    config: cpal::SupportedStreamConfig,
78    path: &Path,
79) -> Result<()> {
80    let samples = std::mem::take(
81        &mut *samples
82            .try_lock()
83            .expect("Stream has ended, callback cant hold the lock"),
84    );
85    let samples: Vec<f32> = SampleTypeConverter::<_, f32>::new(samples.into_iter()).collect();
86    let mut samples = SamplesBuffer::new(config.channels(), config.sample_rate().0, samples);
87    match rodio::output_to_wav(&mut samples, path) {
88        Ok(_) => Ok(()),
89        Err(e) => Err(anyhow::anyhow!("Failed to write wav file: {}", e)),
90    }
91}