1use truce_rack_core::buffer::{AudioBuffer, BusRange};
26use truce_rack_core::bus::BusLayout;
27use truce_rack_core::error::{Error, Result};
28use truce_rack_core::events::EventList;
29use truce_rack_core::plugin::{Plugin, PluginCore, ProcessContext};
30
31#[derive(Debug, Clone)]
34pub struct Rendered {
35 pub output: Vec<Vec<f32>>,
37}
38
39impl Rendered {
40 #[must_use]
42 pub fn peak(&self) -> f32 {
43 self.output
44 .iter()
45 .flat_map(|c| c.iter())
46 .map(|s| s.abs())
47 .fold(0.0f32, f32::max)
48 }
49
50 #[must_use]
52 pub fn any_nan(&self) -> bool {
53 self.output
54 .iter()
55 .flat_map(|c| c.iter())
56 .any(|s| s.is_nan())
57 }
58}
59
60pub fn render_silence<P>(plugin: &mut P, sample_rate: f64, num_frames: usize) -> Result<Rendered>
67where
68 P: PluginCore + Plugin<f32>,
69{
70 render(plugin, sample_rate, num_frames, |_ch, _frame| 0.0)
71}
72
73pub fn render<P, F>(
80 plugin: &mut P,
81 sample_rate: f64,
82 num_frames: usize,
83 mut generator: F,
84) -> Result<Rendered>
85where
86 P: PluginCore + Plugin<f32>,
87 F: FnMut(usize, usize) -> f32,
88{
89 let channels = 2usize;
90 if !plugin.is_active() {
91 plugin.activate(BusLayout::stereo(), sample_rate, num_frames)?;
92 }
93 let mut input_buf = vec![vec![0.0f32; num_frames]; channels];
94 for (ch_idx, ch) in input_buf.iter_mut().enumerate() {
95 for (frame, sample) in ch.iter_mut().enumerate() {
96 *sample = generator(ch_idx, frame);
97 }
98 }
99 let mut output_buf = vec![vec![0.0f32; num_frames]; channels];
100 let bus_in = [BusRange::new(0, channels)];
101 let bus_out = [BusRange::new(0, channels)];
102
103 {
104 let inputs: Vec<&[f32]> = input_buf.iter().map(Vec::as_slice).collect();
105 let mut outputs: Vec<&mut [f32]> = output_buf.iter_mut().map(Vec::as_mut_slice).collect();
106 let mut buffer = AudioBuffer::new(&inputs, &mut outputs, num_frames, &bus_in, &bus_out);
107 let events = EventList::default();
108 let mut out_events = EventList::default();
109 let mut ctx = ProcessContext {
110 sample_rate,
111 max_block_size: num_frames,
112 transport: None,
113 output_events: &mut out_events,
114 };
115 plugin.process(&mut buffer, &events, &mut ctx)?;
116 }
117
118 Ok(Rendered { output: output_buf })
119}
120
121pub fn assert_no_nans(rendered: &Rendered) {
126 assert!(!rendered.any_nan(), "rendered audio contained NaN samples");
127}
128
129pub fn assert_peak_below(rendered: &Rendered, bound: f32) {
134 let peak = rendered.peak();
135 assert!(peak <= bound, "rendered peak {peak} exceeds bound {bound}");
136}
137
138pub fn assert_state_round_trip<P>(plugin: &mut P) -> Result<()>
146where
147 P: PluginCore,
148{
149 let first = plugin.save_state()?;
150 plugin.load_state(&first)?;
151 let second = plugin.save_state()?;
152 if first != second {
153 return Err(Error::Other(format!(
154 "state round-trip mismatch: first {} bytes, second {} bytes",
155 first.len(),
156 second.len(),
157 )));
158 }
159 Ok(())
160}