use std::sync::mpsc; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use kon_core::error::{KonError, Result}; /// A chunk of captured audio from the microphone. pub struct AudioChunk { pub samples: Vec, pub sample_rate: u32, pub channels: u16, } /// Manages microphone capture via cpal. /// Call `start()` to begin capturing, which returns a receiver for audio chunks. /// Call `stop()` to end the stream. pub struct MicrophoneCapture { stream: Option, } impl MicrophoneCapture { /// Start capturing audio from the default input device. /// Returns a receiver that yields AudioChunks as they arrive. pub fn start() -> Result<(Self, mpsc::Receiver)> { let host = cpal::default_host(); let device = host.default_input_device().ok_or_else(|| { KonError::AudioCaptureFailed("No input device found".into()) })?; let config = device.default_input_config().map_err(|e| { KonError::AudioCaptureFailed(format!("No input config: {e}")) })?; let sample_rate = config.sample_rate(); let channels = config.channels() as u16; let (tx, rx) = mpsc::channel::(); let stream = device .build_input_stream( &config.into(), move |data: &[f32], _info: &cpal::InputCallbackInfo| { let _ = tx.send(AudioChunk { samples: data.to_vec(), sample_rate, channels, }); }, |err| eprintln!("audio capture error: {err}"), None, ) .map_err(|e| { KonError::AudioCaptureFailed(format!("Build stream failed: {e}")) })?; stream.play().map_err(|e| { KonError::AudioCaptureFailed(format!("Stream play failed: {e}")) })?; Ok((Self { stream: Some(stream) }, rx)) } /// Stop capturing audio. pub fn stop(&mut self) { if let Some(stream) = self.stream.take() { let _ = stream.pause(); } } } impl Drop for MicrophoneCapture { fn drop(&mut self) { self.stop(); } }