insta_fun/
macros.rs

1/// Macro for audio unit snapshot testing
2///
3/// By default (arms without an explicit `SnapshotConfig`) this macro produces
4/// TWO snapshots for the given audio unit:
5/// 1. An SVG chart (default `SvgChartConfig`)
6/// 2. A 16-bit WAV file (`WavOutput::Wav16`)
7///
8/// Arms that accept a custom `SnapshotConfig` produce exactly ONE snapshot
9/// using the `output_mode` specified in that config.
10///
11/// The unit is internally cloned so it can be processed twice (SVG + WAV)
12/// without triggering a move error. If your audio unit type does not implement
13/// `Clone`, use the config form and select a single output mode.
14///
15/// ## Examples
16///
17/// ```
18/// use fundsp::prelude::*;
19/// use insta_fun::prelude::*;
20///
21/// // With a custom name (generates name.svg & name.wav)
22/// let unit = saw_hz(220.0);
23/// assert_audio_unit_snapshot!("doc_sawtooth", unit);
24/// ```
25///
26/// ```
27/// use fundsp::prelude::*;
28/// use insta_fun::prelude::*;
29///
30/// // With input source (generates doc_lowpass.svg & doc_lowpass.wav)
31/// assert_audio_unit_snapshot!("doc_lowpass", lowpass_hz(1000.0, 1.0), InputSource::impulse());
32/// ```
33///
34/// ```
35/// use fundsp::prelude::*;
36/// use insta_fun::prelude::*;
37///
38/// // With input source and custom config (generates only one file per config.output_mode)
39/// let chart = SvgChartConfigBuilder::default().chart_title("Highpass").build().unwrap();
40/// let config = SnapshotConfigBuilder::default()
41///     .num_samples(512)
42///     .output_mode(chart)
43///     .build()
44///     .unwrap();
45/// assert_audio_unit_snapshot!(
46///     "doc_highpass",
47///     highpass_hz(2000.0, 0.7),
48///     InputSource::sine(100.0, 44100.0),
49///     config
50/// );
51/// ```
52///
53/// ```
54/// use fundsp::prelude::*;
55/// use insta_fun::prelude::*;
56///
57/// // With unit and config (single snapshot)
58/// let config = SnapshotConfigBuilder::default()
59///     .output_mode(WavOutput::Wav32)
60///     .num_samples(512)
61///     .build()
62///     .unwrap();
63/// assert_audio_unit_snapshot!(
64///     "doc_wav32",
65///     sine_hz::<f32>(440.0),
66///     InputSource::None,
67///     config
68/// );
69/// ```
70#[macro_export]
71macro_rules! assert_audio_unit_snapshot {
72    // With just the unit (SVG + WAV16)
73    ($unit:expr) => {{
74        // Capture unit once; clone for second snapshot to avoid move error.
75        let mut __unit = $unit;
76        let __unit_clone = __unit.clone();
77
78        // SVG
79        let config = $crate::config::SnapshotConfigBuilder::default()
80            .build()
81            .unwrap();
82        let name = config.file_name(None);
83        let data_svg = $crate::snapshot::snapshot_audio_unit_with_options(__unit, config);
84
85        ::insta::with_settings!({ omit_expression => true}, {
86            ::insta::assert_binary_snapshot!(&name, data_svg.as_slice().to_vec());
87        });
88
89        // WAV16
90        let config = $crate::config::SnapshotConfigBuilder::default()
91            .output_mode($crate::config::WavOutput::Wav16)
92            .build()
93            .unwrap();
94        let name = config.file_name(None);
95        let data_wav = $crate::snapshot::snapshot_audio_unit_with_options(__unit_clone, config);
96
97        ::insta::with_settings!({ omit_expression => true, snapshot_suffix => "audio" }, {
98            ::insta::assert_binary_snapshot!(&name, data_wav.as_slice().to_vec());
99        });
100    }};
101
102    // With name and unit (name.svg + name.wav)
103    ($name:literal, $unit:expr) => {{
104        let mut __unit = $unit;
105        let __unit_clone = __unit.clone();
106
107        // SVG
108        let config = $crate::config::SnapshotConfigBuilder::default()
109            .chart_title($name)
110            .build()
111            .unwrap();
112        let name = config.file_name(Some($name));
113        let data_svg = $crate::snapshot::snapshot_audio_unit_with_options(__unit, config);
114
115        ::insta::with_settings!({ omit_expression => true}, {
116            ::insta::assert_binary_snapshot!(&name, data_svg.as_slice().to_vec());
117        });
118
119        // WAV16
120        let config = $crate::config::SnapshotConfigBuilder::default()
121            .output_mode($crate::config::WavOutput::Wav16)
122            .build()
123            .unwrap();
124        let name = config.file_name(Some($name));
125        let data_wav = $crate::snapshot::snapshot_audio_unit_with_options(__unit_clone, config);
126
127        ::insta::with_settings!({ omit_expression => true, snapshot_suffix => "audio" }, {
128            ::insta::assert_binary_snapshot!(&name, data_wav.as_slice().to_vec());
129        });
130    }};
131
132    // With input source (name.svg + name.wav)
133    ($name:literal, $unit:expr, $input:expr) => {{
134        let mut __unit = $unit;
135        let __unit_clone = __unit.clone();
136        // Input expression is evaluated twice; prefer passing an expression (e.g. InputSource::impulse()).
137        // If you have a bound variable you need two independent values.
138
139        // SVG
140        let config = $crate::config::SnapshotConfigBuilder::default()
141            .chart_title($name)
142            .build()
143            .unwrap();
144        let name = config.file_name(Some($name));
145        let data_svg =
146            $crate::snapshot::snapshot_audio_unit_with_input_and_options(__unit, $input, config);
147
148        ::insta::with_settings!({ omit_expression => true}, {
149            ::insta::assert_binary_snapshot!(&name, data_svg.as_slice().to_vec());
150        });
151
152        // WAV16
153        let config = $crate::config::SnapshotConfigBuilder::default()
154            .output_mode($crate::config::WavOutput::Wav16)
155            .build()
156            .unwrap();
157        let name = config.file_name(Some($name));
158        let data_wav =
159            $crate::snapshot::snapshot_audio_unit_with_input_and_options(__unit_clone, $input, config);
160
161        ::insta::with_settings!({ omit_expression => true, snapshot_suffix => "audio" }, {
162            ::insta::assert_binary_snapshot!(&name, data_wav.as_slice().to_vec());
163        });
164    }};
165
166    // With input source and config (single snapshot; uses config.output_mode)
167    ($name:literal, $unit:expr, $input:expr, $config:expr) => {{
168        let mut config = $config;
169        config.maybe_title($name);
170
171        let name = config.file_name(Some($name));
172        let data = $crate::snapshot::snapshot_audio_unit_with_input_and_options($unit, $input, config);
173
174        ::insta::with_settings!({ omit_expression => true}, {
175            ::insta::assert_binary_snapshot!(&name, data.as_slice().to_vec());
176        });
177    }};
178
179    // With unit and config (single snapshot; uses config.output_mode)
180    ($unit:expr, $config:expr) => {{
181        // Capture name before moving config into processing.
182        let name = $config.file_name(None);
183        let data = $crate::snapshot::snapshot_audio_unit_with_options($unit, $config);
184
185        ::insta::with_settings!({ omit_expression => true}, {
186            ::insta::assert_binary_snapshot!(&name, data.as_slice().to_vec());
187        });
188    }};
189}