#[tokio::main(flavor = "current_thread")]
async fn main() {
println!("Audio level monitor (5 seconds)");
println!("Legend: each '|' ≈ 3 dB above -60 dBFS\n");
let config = oxisound::StreamConfig::stereo_48k();
let stream = match oxisound::capture_stream(config) {
Ok(s) => s,
Err(e) => {
println!("Failed to open capture stream: {e}");
return;
}
};
let start = std::time::Instant::now();
let duration = std::time::Duration::from_secs(5);
tokio::pin!(stream);
let _ = tokio::time::timeout(duration, async {
use std::future::poll_fn;
use std::task::Poll;
use futures_core::stream::Stream;
loop {
let chunk: Option<Vec<f32>> = poll_fn(|cx| {
match stream.as_mut().poll_next(cx) {
Poll::Ready(v) => Poll::Ready(v),
Poll::Pending => {
cx.waker().wake_by_ref();
Poll::Pending
}
}
})
.await;
match chunk {
Some(samples) => {
let rms = compute_rms(&samples);
print_level(rms, start.elapsed());
}
None => break,
}
}
})
.await;
println!("\nDone.");
}
fn compute_rms(samples: &[f32]) -> f32 {
if samples.is_empty() {
return 0.0;
}
let mean_sq = samples.iter().map(|&s| s * s).sum::<f32>() / samples.len() as f32;
mean_sq.sqrt()
}
fn print_level(rms: f32, elapsed: std::time::Duration) {
let db = if rms > 1e-10 {
20.0 * rms.log10()
} else {
-60.0_f32
};
let bars = ((db + 60.0) / 3.0).max(0.0) as usize;
let meter: String = "|".repeat(bars.min(20));
println!(
"{:.1}s [{:<20}] {:.1} dBFS",
elapsed.as_secs_f32(),
meter,
db
);
}