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}