//! CLI driver — capture or playback or both. use std::path::PathBuf; use std::process::ExitCode; use std::time::Duration; use audio_capture_playback_spike::{synth_sine_wav, AudioCapture, AudioPlayback}; use tracing::{info, warn}; fn main() -> ExitCode { tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), ) .init(); let mode = std::env::args().nth(1).unwrap_or_else(|| "roundtrip".into()); let tmp = std::env::temp_dir(); let cap_path = tmp.join("chanora_audio_spike_capture.wav"); let play_path = tmp.join("chanora_audio_spike_synth.wav"); let result: anyhow::Result<()> = match mode.as_str() { "capture" => do_capture(&cap_path), "synth" => do_synth(&play_path), "playback" => do_playback(&play_path), "roundtrip" => do_roundtrip(&cap_path, &play_path), other => { eprintln!("unknown mode: {other}. Use one of: capture, synth, playback, roundtrip."); return ExitCode::from(2); } }; if let Err(e) = result { eprintln!("error: {e:#}"); return ExitCode::from(1); } ExitCode::SUCCESS } fn do_capture(path: &PathBuf) -> anyhow::Result<()> { info!(target: "spike", "capture → {}", path.display()); let frames = AudioCapture::record_to_wav(path, Duration::from_secs(1))?; println!("captured {} frames to {}", frames, path.display()); Ok(()) } fn do_synth(path: &PathBuf) -> anyhow::Result<()> { info!(target: "spike", "synth → {}", path.display()); let (frames, sr) = synth_sine_wav(path, 440.0, Duration::from_millis(500), 48_000)?; println!("synthesised {} frames @ {} Hz to {}", frames, sr, path.display()); Ok(()) } fn do_playback(path: &PathBuf) -> anyhow::Result<()> { info!(target: "spike", "playback ← {}", path.display()); AudioPlayback::play_wav(path)?; println!("playback finished"); Ok(()) } fn do_roundtrip(cap_path: &PathBuf, play_path: &PathBuf) -> anyhow::Result<()> { // Try real capture first; if no input device or the capture failed, // fall back to synthesised audio so the playback path is still // exercised. This mirrors what production code would do during // a "device test" UI on headless or permission-denied hosts. let to_play = match AudioCapture::record_to_wav(cap_path, Duration::from_millis(750)) { Ok(n) if n > 0 => { info!(target: "spike", "captured {} frames; playing back", n); cap_path.clone() } Ok(_) => { warn!(target: "spike", "capture wrote 0 frames; falling back to synth"); let (n, sr) = synth_sine_wav(play_path, 440.0, Duration::from_millis(500), 48_000)?; info!(target: "spike", frames = n, sample_rate = sr, "synthesised fallback"); play_path.clone() } Err(e) => { warn!(target: "spike", error = %e, "capture unavailable; falling back to synth"); let (n, sr) = synth_sine_wav(play_path, 440.0, Duration::from_millis(500), 48_000)?; info!(target: "spike", frames = n, sample_rate = sr, "synthesised fallback"); play_path.clone() } }; AudioPlayback::play_wav(&to_play)?; println!("round-trip complete (played from {})", to_play.display()); Ok(()) }