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