use std::time::Duration;
use truce_core::cast::sample_count_usize;
use truce_core::export::PluginExport;
use truce_driver::{DriverResult, MeterReadings};
const AUDIBLE_THRESHOLD: f32 = 0.001;
fn duration_to_frames<P: PluginExport>(result: &DriverResult<P>, d: Duration) -> usize {
let frames = sample_count_usize(d.as_secs_f64() * result.sample_rate);
frames.min(result.total_frames)
}
fn peak_in_range<P: PluginExport>(result: &DriverResult<P>, start: usize, end: usize) -> f32 {
if start >= end {
return 0.0;
}
result
.output
.iter()
.flat_map(|ch| {
let s = start.min(ch.len());
let e = end.min(ch.len()).max(s);
ch[s..e].iter()
})
.map(|s| s.abs())
.fold(0.0f32, f32::max)
}
#[allow(clippy::cast_precision_loss)]
pub fn assert_nonzero<P: PluginExport>(result: &DriverResult<P>) {
let peak = peak_in_range(result, 0, result.total_frames);
assert!(
peak > AUDIBLE_THRESHOLD,
"Expected non-zero output over the full {:.3} s run, but peak sample was {peak}",
result.total_frames as f64 / result.sample_rate
);
}
pub fn assert_silence<P: PluginExport>(result: &DriverResult<P>) {
let peak = peak_in_range(result, 0, result.total_frames);
assert!(
peak < AUDIBLE_THRESHOLD,
"Expected silence over the full run, but peak sample was {peak}"
);
}
#[allow(clippy::cast_precision_loss)]
pub fn assert_no_nans<P: PluginExport>(result: &DriverResult<P>) {
let bad = result
.output
.iter()
.enumerate()
.flat_map(|(ch, data)| data.iter().enumerate().map(move |(i, &s)| (ch, i, s)))
.find(|&(_, _, s)| !s.is_finite());
if let Some((ch, i, s)) = bad {
panic!(
"NaN/Inf at channel {ch} sample {i} (t = {:.3} ms): {s}",
(i as f64 / result.sample_rate) * 1000.0
);
}
}
pub fn assert_peak_below<P: PluginExport>(result: &DriverResult<P>, threshold: f32) {
let peak = peak_in_range(result, 0, result.total_frames);
assert!(
peak <= threshold,
"Peak sample {peak} exceeded threshold {threshold}"
);
}
pub fn assert_silence_after<P: PluginExport>(result: &DriverResult<P>, t: Duration) {
let start = duration_to_frames(result, t);
let peak = peak_in_range(result, start, result.total_frames);
assert!(
peak < AUDIBLE_THRESHOLD,
"Expected silence after {:.3} ms but peak was {peak} \
(tail starts at sample {start}, run ends at sample {})",
t.as_secs_f64() * 1000.0,
result.total_frames
);
}
pub fn assert_nonzero_after<P: PluginExport>(result: &DriverResult<P>, t: Duration) {
let start = duration_to_frames(result, t);
let peak = peak_in_range(result, start, result.total_frames);
assert!(
peak > AUDIBLE_THRESHOLD,
"Expected non-zero audio after {:.3} ms but peak was {peak}",
t.as_secs_f64() * 1000.0
);
}
pub fn assert_silence_between<P: PluginExport>(
result: &DriverResult<P>,
start: Duration,
end: Duration,
) {
let s = duration_to_frames(result, start);
let e = duration_to_frames(result, end);
assert!(s < e, "assert_silence_between: start >= end");
let peak = peak_in_range(result, s, e);
assert!(
peak < AUDIBLE_THRESHOLD,
"Expected silence in [{:.3} ms, {:.3} ms) but peak was {peak}",
start.as_secs_f64() * 1000.0,
end.as_secs_f64() * 1000.0
);
}
pub fn assert_nonzero_between<P: PluginExport>(
result: &DriverResult<P>,
start: Duration,
end: Duration,
) {
let s = duration_to_frames(result, start);
let e = duration_to_frames(result, end);
assert!(s < e, "assert_nonzero_between: start >= end");
let peak = peak_in_range(result, s, e);
assert!(
peak > AUDIBLE_THRESHOLD,
"Expected non-zero audio in [{:.3} ms, {:.3} ms) but peak was {peak}",
start.as_secs_f64() * 1000.0,
end.as_secs_f64() * 1000.0
);
}
fn final_meters<P: PluginExport>(result: &DriverResult<P>) -> &[(u32, f32)] {
match &result.meters {
MeterReadings::Final(v) => v.as_slice(),
MeterReadings::PerBlock(blocks) => blocks.last().map_or(&[], std::vec::Vec::as_slice),
MeterReadings::None => panic!(
"meter assertion called but CaptureSpec::meters was MeterCapture::None - \
call .capture_meters(MeterCapture::Final) on the driver"
),
}
}
pub fn assert_meter_above<P: PluginExport>(result: &DriverResult<P>, id: u32, threshold: f32) {
let meters = final_meters(result);
let Some((_, value)) = meters.iter().find(|(mid, _)| *mid == id) else {
panic!(
"Meter id {id} not found in DriverResult. Available ids: {:?}",
meters.iter().map(|(i, _)| i).collect::<Vec<_>>()
);
};
assert!(
*value > threshold,
"Meter {id} read {value} at end-of-run, expected > {threshold}"
);
}
pub fn assert_meter_below<P: PluginExport>(result: &DriverResult<P>, id: u32, threshold: f32) {
let meters = final_meters(result);
let Some((_, value)) = meters.iter().find(|(mid, _)| *mid == id) else {
panic!(
"Meter id {id} not found in DriverResult. Available ids: {:?}",
meters.iter().map(|(i, _)| i).collect::<Vec<_>>()
);
};
assert!(
*value < threshold,
"Meter {id} read {value} at end-of-run, expected < {threshold}"
);
}
pub fn assert_output_event_count<P: PluginExport>(result: &DriverResult<P>, n: usize) {
assert_eq!(
result.output_events.len(),
n,
"Expected {n} output events, got {} ({:?})",
result.output_events.len(),
result
.output_events
.iter()
.map(|e| (e.sample_offset, std::mem::discriminant(&e.body)))
.collect::<Vec<_>>()
);
}