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}