#[allow(dead_code)]
#[path = "ab/mod.rs"]
mod ab;
use ab::{render_with_rate_schedule, Arm};
use timestretch::StreamProfile;
const SAMPLE_RATE: u32 = 44_100;
const CALLBACK_FRAMES: usize = 256;
fn sine(freq: f32, frames: usize, amp: f32) -> Vec<f32> {
(0..frames)
.map(|i| amp * (2.0 * std::f32::consts::PI * freq * i as f32 / SAMPLE_RATE as f32).sin())
.collect()
}
fn rms(samples: &[f32]) -> f64 {
if samples.is_empty() {
return 0.0;
}
(samples
.iter()
.map(|&s| (s as f64) * (s as f64))
.sum::<f64>()
/ samples.len() as f64)
.sqrt()
}
fn max_adjacent_diff(samples: &[f32]) -> f32 {
samples
.windows(2)
.map(|w| (w[1] - w[0]).abs())
.fold(0.0, f32::max)
}
#[test]
fn ab_adapter_drives_both_engines_over_identical_fixture() {
let amp = 0.5f32;
let input = sine(220.0, SAMPLE_RATE as usize * 6, amp);
let ride = |t: f64| 1.0 + 0.06 * (2.0 * std::f64::consts::PI * 0.25 * t).sin();
let old = render_with_rate_schedule(
Arm::OldVarispeed(StreamProfile::Live),
&input,
1,
SAMPLE_RATE,
CALLBACK_FRAMES,
&ride,
);
let new =
render_with_rate_schedule(Arm::NewTape, &input, 1, SAMPLE_RATE, CALLBACK_FRAMES, &ride);
println!(
"A/B smoke: old len={} rms={:.4} max_diff={:.4} | new len={} rms={:.4} max_diff={:.4} underruns={}",
old.output.len(),
rms(&old.output),
max_adjacent_diff(&old.output),
new.output.len(),
rms(&new.output),
max_adjacent_diff(&new.output),
new.underrun_frames,
);
for (label, out) in [("old", &old.output), ("new", &new.output)] {
let len_err = (out.len() as f64 - input.len() as f64).abs() / input.len() as f64;
assert!(
len_err < 0.05,
"{label} arm length error {len_err:.3} (len {})",
out.len()
);
}
let source_rms = amp as f64 / std::f64::consts::SQRT_2;
for (label, out) in [("old", &old.output), ("new", &new.output)] {
let r = rms(out);
assert!(
(r - source_rms).abs() / source_rms < 0.1,
"{label} arm RMS {r:.4} deviates from source {source_rms:.4}"
);
}
let slew_bound = amp * 2.0 * std::f32::consts::PI * 220.0 * 1.06 / SAMPLE_RATE as f32;
let old_scan = &old.output[8192.min(old.output.len())..];
assert!(
max_adjacent_diff(old_scan) <= slew_bound * 6.0,
"old arm clicked: {:.5}",
max_adjacent_diff(old_scan)
);
assert!(
max_adjacent_diff(&new.output) <= slew_bound * 1.5,
"new arm clicked: {:.5}",
max_adjacent_diff(&new.output)
);
assert_eq!(new.underrun_frames, 0, "A/B feeder must keep the ring fed");
}