//! Audio capture and playback via cpal.
mod capture;
mod playback;
pub mod levels;
use anyhow::Result;
use capture::CaptureStream;
use playback::PlaybackStream;
use crossbeam_channel::{bounded, Receiver, Sender};
use std::sync::Arc;
use parking_lot::Mutex;
/// Audio sample type.
pub type Sample = f32;
/// Ring buffer for waveform visualization
const WAVEFORM_BUFFER_SIZE: usize = 2048;
/// Audio manager handling capture and playback.
pub struct AudioManager {
/// Captured audio buffer (for transcription)
capture_buffer: Arc<Mutex<Vec<Sample>>>,
/// Ring buffer for waveform visualization
waveform_buffer: Arc<Mutex<Vec<Sample>>>,
/// Is currently recording (for transcription)
is_recording: Arc<Mutex<bool>>,
/// Audio level sender
level_tx: Sender<f32>,
/// Audio level receiver
level_rx: Receiver<f32>,
/// Audio capture stream (always running for visualization)
_capture_stream: Option<CaptureStream>,
/// Audio playback stream
playback_stream: Option<PlaybackStream>,
/// Sample rate
sample_rate: u32,
/// Playback sample rate
playback_sample_rate: u32,
}
impl AudioManager {
/// Create a new audio manager with continuous audio capture.
pub fn new() -> Result<Self> {
let (level_tx, level_rx) = bounded(64);
let capture_buffer = Arc::new(Mutex::new(Vec::with_capacity(16000 * 30))); // 30s buffer
let waveform_buffer = Arc::new(Mutex::new(vec![0.0; WAVEFORM_BUFFER_SIZE]));
let is_recording = Arc::new(Mutex::new(false));
// Create capture stream that always runs (for visualization)
let capture_stream = match CaptureStream::new(
capture_buffer.clone(),
waveform_buffer.clone(),
is_recording.clone(),
level_tx.clone(),
) {
Ok(stream) => {
stream.start()?;
Some(stream)
}
Err(e) => {
tracing::error!("Failed to create capture stream: {}", e);
None
}
};
let sample_rate = capture_stream
.as_ref()
.map(|s| s.sample_rate())
.unwrap_or(16000);
// Create playback stream
let playback_stream = match PlaybackStream::new() {
Ok(stream) => {
stream.start()?;
Some(stream)
}
Err(e) => {
tracing::error!("Failed to create playback stream: {}", e);
None
}
};
let playback_sample_rate = playback_stream
.as_ref()
.map(|s| s.sample_rate())
.unwrap_or(48000);
Ok(Self {
capture_buffer,
waveform_buffer,
is_recording,
level_tx,
level_rx,
_capture_stream: capture_stream,
playback_stream,
sample_rate,
playback_sample_rate,
})
}
/// Start recording audio (for transcription).
pub fn start_recording(&mut self) {
let mut is_recording = self.is_recording.lock();
if *is_recording {
return;
}
*is_recording = true;
// Clear transcription buffer
self.capture_buffer.lock().clear();
tracing::info!("Started recording for transcription");
}
/// Stop recording and return captured audio.
pub fn stop_recording(&mut self) -> Vec<Sample> {
let mut is_recording = self.is_recording.lock();
*is_recording = false;
let buffer = self.capture_buffer.lock().clone();
tracing::info!("Stopped recording, captured {} samples", buffer.len());
buffer
}
/// Get a copy of currently captured audio without stopping recording.
/// Used for periodic VAD checks.
pub fn get_captured_audio(&self) -> Vec<Sample> {
self.capture_buffer.lock().clone()
}
/// Get waveform data for visualization (always available).
pub fn get_waveform_data(&self) -> Vec<f32> {
self.waveform_buffer.lock().clone()
}
/// Get the latest audio level (0.0 - 1.0).
pub fn get_level(&self) -> Option<f32> {
self.level_rx.try_recv().ok()
}
/// Drain all pending audio levels and return the max.
pub fn drain_levels(&self) -> f32 {
let mut max_level = 0.0f32;
while let Ok(level) = self.level_rx.try_recv() {
max_level = max_level.max(level);
}
max_level
}
/// Get sample rate.
pub fn sample_rate(&self) -> u32 {
self.sample_rate
}
/// Play audio data.
/// Input is expected at 24kHz (TTS output), will be resampled if needed.
pub fn play_audio(&self, samples: Vec<f32>, input_sample_rate: u32) -> Result<()> {
let playback = self.playback_stream.as_ref()
.ok_or_else(|| anyhow::anyhow!("No playback stream"))?;
// Resample if needed
let output_samples = if input_sample_rate != self.playback_sample_rate {
resample(&samples, input_sample_rate, self.playback_sample_rate)
} else {
samples
};
playback.queue(output_samples)?;
Ok(())
}
}
/// Simple linear resampling
fn resample(samples: &[f32], from_rate: u32, to_rate: u32) -> Vec<f32> {
if from_rate == to_rate {
return samples.to_vec();
}
let ratio = to_rate as f64 / from_rate as f64;
let output_len = (samples.len() as f64 * ratio) as usize;
let mut output = Vec::with_capacity(output_len);
for i in 0..output_len {
let src_idx = i as f64 / ratio;
let idx = src_idx as usize;
let frac = src_idx - idx as f64;
let sample = if idx + 1 < samples.len() {
// Linear interpolation
samples[idx] * (1.0 - frac as f32) + samples[idx + 1] * frac as f32
} else if idx < samples.len() {
samples[idx]
} else {
0.0
};
output.push(sample);
}
output
}