record.rs

 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
25            .description()
26            .map(|desc| desc.name().to_string())
27            .unwrap_or("<unknown>".to_string());
28        log::info!("Using microphone: {}", name);
29
30        let samples = Arc::new(Mutex::new(Vec::new()));
31        let stream = start_capture(device, config.clone(), samples.clone())?;
32
33        Ok(Self {
34            name,
35            _stream: stream,
36            config,
37            samples,
38        })
39    }
40
41    pub fn finish(self) -> Result<PathBuf> {
42        let name = self.name;
43        let mut path = env::current_dir().context("Could not get current dir")?;
44        path.push(&format!("test_recording_{name}.wav"));
45        log::info!("Test recording written to: {}", path.display());
46        write_out(self.samples, self.config, &path)?;
47        Ok(path)
48    }
49}
50
51fn start_capture(
52    device: cpal::Device,
53    config: cpal::SupportedStreamConfig,
54    samples: Arc<Mutex<Vec<i16>>>,
55) -> Result<cpal::Stream> {
56    let stream = device
57        .build_input_stream_raw(
58            &config.config(),
59            config.sample_format(),
60            move |data, _: &_| {
61                let data = crate::get_sample_data(config.sample_format(), data).log_err();
62                let Some(data) = data else {
63                    return;
64                };
65                samples
66                    .try_lock()
67                    .expect("Only locked after stream ends")
68                    .extend_from_slice(&data);
69            },
70            |err| log::error!("error capturing audio track: {:?}", err),
71            Some(Duration::from_millis(100)),
72        )
73        .context("failed to build input stream")?;
74
75    stream.play()?;
76    Ok(stream)
77}
78
79fn write_out(
80    samples: Arc<Mutex<Vec<i16>>>,
81    config: cpal::SupportedStreamConfig,
82    path: &Path,
83) -> Result<()> {
84    let samples = std::mem::take(
85        &mut *samples
86            .try_lock()
87            .expect("Stream has ended, callback cant hold the lock"),
88    );
89    let samples: Vec<f32> = SampleTypeConverter::<_, f32>::new(samples.into_iter()).collect();
90    let mut samples = SamplesBuffer::new(
91        NonZero::new(config.channels()).expect("config channel is never zero"),
92        NonZero::new(config.sample_rate()).expect("config sample_rate is never zero"),
93        samples,
94    );
95    match rodio::wav_to_file(&mut samples, path) {
96        Ok(_) => Ok(()),
97        Err(e) => Err(anyhow::anyhow!("Failed to write wav file: {}", e)),
98    }
99}