use truce_rack_core::buffer::{AudioBuffer, BusRange};
use truce_rack_core::bus::BusLayout;
use truce_rack_core::error::{Error, Result};
use truce_rack_core::events::EventList;
use truce_rack_core::plugin::{Plugin, PluginCore, ProcessContext};
#[derive(Debug, Clone)]
pub struct Rendered {
pub output: Vec<Vec<f32>>,
}
impl Rendered {
#[must_use]
pub fn peak(&self) -> f32 {
self.output
.iter()
.flat_map(|c| c.iter())
.map(|s| s.abs())
.fold(0.0f32, f32::max)
}
#[must_use]
pub fn any_nan(&self) -> bool {
self.output
.iter()
.flat_map(|c| c.iter())
.any(|s| s.is_nan())
}
}
pub fn render_silence<P>(plugin: &mut P, sample_rate: f64, num_frames: usize) -> Result<Rendered>
where
P: PluginCore + Plugin<f32>,
{
render(plugin, sample_rate, num_frames, |_ch, _frame| 0.0)
}
pub fn render<P, F>(
plugin: &mut P,
sample_rate: f64,
num_frames: usize,
mut generator: F,
) -> Result<Rendered>
where
P: PluginCore + Plugin<f32>,
F: FnMut(usize, usize) -> f32,
{
let channels = 2usize;
if !plugin.is_active() {
plugin.activate(BusLayout::stereo(), sample_rate, num_frames)?;
}
let mut input_buf = vec![vec![0.0f32; num_frames]; channels];
for (ch_idx, ch) in input_buf.iter_mut().enumerate() {
for (frame, sample) in ch.iter_mut().enumerate() {
*sample = generator(ch_idx, frame);
}
}
let mut output_buf = vec![vec![0.0f32; num_frames]; channels];
let bus_in = [BusRange::new(0, channels)];
let bus_out = [BusRange::new(0, channels)];
{
let inputs: Vec<&[f32]> = input_buf.iter().map(Vec::as_slice).collect();
let mut outputs: Vec<&mut [f32]> = output_buf.iter_mut().map(Vec::as_mut_slice).collect();
let mut buffer = AudioBuffer::new(&inputs, &mut outputs, num_frames, &bus_in, &bus_out);
let events = EventList::default();
let mut out_events = EventList::default();
let mut ctx = ProcessContext {
sample_rate,
max_block_size: num_frames,
transport: None,
output_events: &mut out_events,
};
plugin.process(&mut buffer, &events, &mut ctx)?;
}
Ok(Rendered { output: output_buf })
}
pub fn assert_no_nans(rendered: &Rendered) {
assert!(!rendered.any_nan(), "rendered audio contained NaN samples");
}
pub fn assert_peak_below(rendered: &Rendered, bound: f32) {
let peak = rendered.peak();
assert!(peak <= bound, "rendered peak {peak} exceeds bound {bound}",);
}
pub fn assert_state_round_trip<P>(plugin: &mut P) -> Result<()>
where
P: PluginCore,
{
let first = plugin.save_state()?;
plugin.load_state(&first)?;
let second = plugin.save_state()?;
if first != second {
return Err(Error::Other(format!(
"state round-trip mismatch: first {} bytes, second {} bytes",
first.len(),
second.len(),
)));
}
Ok(())
}