use std::env;
use std::sync::mpsc;
use std::time::{Duration, Instant};
use atome::device::AtomeDevice;
use atome::{input, output, AudioEngine, Plugin};
use cpal::traits::StreamTrait;
mod common;
type Sample = f32;
const PRE_ROLL: Duration = Duration::from_millis(120);
const DEFAULT_SECONDS: u64 = 15;
fn gain(db: f32) -> Plugin {
let scale = 10.0_f32.powf(db / 20.0);
Plugin::internal(format!("gain {db:+} dB"), move |buffer: &mut [f32], _| {
for sample in buffer {
*sample *= scale;
}
})
}
fn soft_clip(drive: f32) -> Plugin {
Plugin::internal(format!("soft clip x{drive}"), move |buffer: &mut [f32], _| {
for sample in buffer {
*sample = (*sample * drive).tanh();
}
})
}
fn main() -> Result<(), cpal::Error> {
let seconds = env::args()
.nth(1)
.and_then(|arg| arg.parse::<u64>().ok())
.unwrap_or(DEFAULT_SECONDS);
let inputs = input::list_devices(None)?;
let outputs = output::list_devices(None)?;
let (Some(source), Some(sink)) = (inputs.first(), outputs.first()) else {
eprintln!(
"needs one input and one output; this machine has {} and {}",
inputs.len(),
outputs.len()
);
return Ok(());
};
let rate = common::shared_rate(source, sink);
let mut engine = AudioEngine::<Sample>::new(
vec![AtomeDevice::input(source.clone(), common::host())
.with_plugin(soft_clip(1.5))],
vec![AtomeDevice::output(sink.clone(), common::host()).with_plugin(gain(-24.0))],
rate,
vec![2],
Some(512),
vec![gain(-6.0)],
)?;
common::describe(&engine);
let in_channels = engine.inputs()[0].input().channels();
let out_channels = engine.outputs()[0].output().channels().max(1);
let (blocks, captured) = mpsc::channel::<Vec<Sample>>();
{
let input = engine.inputs_mut()[0].input_mut();
input.set_callback(move |block: &[Sample]| {
let _ = blocks.send(block.to_vec());
});
match input.build_stream() {
Ok(stream) => stream.play()?,
Err(error) => {
eprintln!("cannot capture from {}: {error}", common::name_of(source));
return Ok(());
}
}
}
let started = {
let output = engine.outputs_mut()[0].output_mut();
let stream = output.build_stream()?;
stream.play()?;
Instant::now()
};
println!(
"\nmonitoring {} -> {} for {seconds} s",
common::name_of(source),
common::name_of(sink)
);
println!("wear headphones — this is a microphone feeding a speaker\n");
let deadline = started + Duration::from_secs(seconds);
let mut index: Option<usize> = None;
let mut forwarded = 0usize;
let mut blocks_sent = 0usize;
let mut refused = 0usize;
while Instant::now() < deadline {
let Ok(mut block) = captured.recv_timeout(Duration::from_millis(250)) else {
continue;
};
engine.apply_input_plugins(0, &mut block)?;
let mut block = engine.outputs()[0]
.output()
.align_samples(&block, rate, in_channels, true)?;
engine.apply_engine_plugins(&mut block, out_channels)?;
engine.apply_output_plugins(0, &mut block)?;
let at = *index.get_or_insert_with(|| {
let ahead = started.elapsed() + PRE_ROLL;
let frames = (ahead.as_secs_f64() * rate.hz() as f64) as usize;
frames * out_channels as usize
});
match engine.outputs_mut()[0].output_mut().add_samples(&block, at) {
Ok(next) => {
index = Some(next);
forwarded += block.len();
blocks_sent += 1;
}
Err(_) => refused += 1,
}
}
engine.outputs_mut()[0].output_mut().stop();
let played = forwarded as f64 / rate.hz() as f64 / out_channels as f64;
println!("forwarded {played:.2} s of audio in {blocks_sent} blocks");
if refused > 0 {
println!("dropped {refused} blocks: the mixer queue was full");
}
println!("done");
Ok(())
}