Skip to main content

sim_lib_audio_dsp/
fixture.rs

1use sim_lib_audio_graph_core::{
2    BlockArena, NullEventSink, PrepareConfig, ProcessBlock, Processor, Transport,
3};
4
5/// A deterministic input/expected-output pair for regression-testing a
6/// [`Processor`] offline.
7#[derive(Clone, Debug, PartialEq)]
8pub struct GoldenFixture {
9    /// Fixture name.
10    pub name: &'static str,
11    /// Sample rate used when preparing the processor.
12    pub sample_rate_hz: u32,
13    /// Input audio lanes, one per channel.
14    pub input: Vec<Vec<f32>>,
15    /// Expected output audio lanes, one per channel.
16    pub expected: Vec<Vec<f32>>,
17}
18
19impl GoldenFixture {
20    /// Returns the fixture frame count (length of the first input lane).
21    pub fn frames(&self) -> u32 {
22        self.input.first().map_or(0, Vec::len) as u32
23    }
24}
25
26/// Returns the R30 gain golden fixture (a 0.25x gain reference).
27pub fn r30_gain_golden_fixture() -> GoldenFixture {
28    GoldenFixture {
29        name: "r30-gain",
30        sample_rate_hz: 48_000,
31        input: vec![vec![1.0, -0.5, 0.25, 0.0, -0.25, 0.5, -1.0, 0.75]],
32        expected: vec![vec![
33            0.25, -0.125, 0.0625, 0.0, -0.0625, 0.125, -0.25, 0.1875,
34        ]],
35    }
36}
37
38/// Returns the R30 delay golden fixture (a two-sample impulse delay).
39pub fn r30_delay_golden_fixture() -> GoldenFixture {
40    GoldenFixture {
41        name: "r30-delay",
42        sample_rate_hz: 1_000,
43        input: vec![vec![1.0, 0.0, 0.0, 0.0, 0.0, 0.0]],
44        expected: vec![vec![0.0, 0.0, 1.0, 0.0, 0.0, 0.0]],
45    }
46}
47
48/// Prepares and runs `processor` over a fixture's input, returning the rendered
49/// output lanes for comparison against [`GoldenFixture::expected`].
50pub fn run_offline<P: Processor>(
51    processor: &mut P,
52    fixture: &GoldenFixture,
53    out_channels: usize,
54) -> Vec<Vec<f32>> {
55    let frames = fixture.frames() as usize;
56    processor.prepare(PrepareConfig::new(
57        fixture.sample_rate_hz,
58        fixture.frames(),
59        fixture.input.len() as u16,
60        out_channels as u16,
61    ));
62    let mut output = vec![vec![0.0; frames]; out_channels];
63    let input_refs: Vec<&[f32]> = fixture.input.iter().map(Vec::as_slice).collect();
64    let mut output_refs: Vec<&mut [f32]> = output.iter_mut().map(Vec::as_mut_slice).collect();
65    let mut sink = NullEventSink;
66    let mut scratch = BlockArena::with_f32_capacity(frames * out_channels.max(1));
67    let mut block = ProcessBlock {
68        frames: fixture.frames(),
69        in_audio: &input_refs,
70        out_audio: &mut output_refs,
71        in_events: &[],
72        out_events: &mut sink,
73        transport: Transport::default(),
74        scratch: &mut scratch,
75    };
76    processor.process(&mut block);
77    output
78}