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/// ```rust,no_run
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/// ```rust,no_run
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/// ```rust,no_run
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/// ```rust,no_run
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///
71/// Invariants / Notes:
72/// - Unit expression is evaluated once per macro invocation; cloned only for dual-snapshot arms.
73/// - Name expression (in widened arms) is evaluated exactly once and converted with Into<String>.
74/// - Input expression in the (name, unit, input) arm is evaluated twice (once per SVG, once per WAV).
75/// - Two-arg (name, unit) arm keeps `$name:literal` to avoid ambiguity with the (unit, config) arm.
76///   To use a dynamic (non-literal) name, supply an input or a config (3 or 4 argument forms).
77#[macro_export]
78macro_rules! assert_audio_unit_snapshot {
79    // With just the unit (SVG + WAV16)
80    ($unit:expr) => {{
81        // Capture unit once; clone for second snapshot to avoid move error.
82        let mut __unit = $unit;
83        let __unit_clone = __unit.clone();
84
85        // SVG
86        let config = $crate::config::SnapshotConfigBuilder::default()
87            .build()
88            .unwrap();
89        let name = config.file_name(None);
90        let data_svg = $crate::snapshot::snapshot_audio_unit_with_options(__unit, config);
91
92        ::insta::with_settings!({ omit_expression => true}, {
93            ::insta::assert_binary_snapshot!(&name, data_svg.as_slice().to_vec());
94        });
95
96        // WAV16
97        let config = $crate::config::SnapshotConfigBuilder::default()
98            .output_mode($crate::config::WavOutput::Wav16)
99            .build()
100            .unwrap();
101        let name = config.file_name(None);
102        let data_wav = $crate::snapshot::snapshot_audio_unit_with_options(__unit_clone, config);
103
104        ::insta::with_settings!({ omit_expression => true, snapshot_suffix => "audio" }, {
105            ::insta::assert_binary_snapshot!(&name, data_wav.as_slice().to_vec());
106        });
107    }};
108
109    // With name and unit (name.svg + name.wav) - kept literal for disambiguation.
110    ($name:literal, $unit:expr) => {{
111        let mut __unit = $unit;
112        let __unit_clone = __unit.clone();
113
114        // SVG
115        let config = $crate::config::SnapshotConfigBuilder::default()
116            .chart_title($name)
117            .build()
118            .unwrap();
119        let name = config.file_name(Some($name));
120        let data_svg = $crate::snapshot::snapshot_audio_unit_with_options(__unit, config);
121
122        ::insta::with_settings!({ omit_expression => true}, {
123            ::insta::assert_binary_snapshot!(&name, data_svg.as_slice().to_vec());
124        });
125
126        // WAV16
127        let config = $crate::config::SnapshotConfigBuilder::default()
128            .output_mode($crate::config::WavOutput::Wav16)
129            .build()
130            .unwrap();
131        let name = config.file_name(Some($name));
132        let data_wav = $crate::snapshot::snapshot_audio_unit_with_options(__unit_clone, config);
133
134        ::insta::with_settings!({ omit_expression => true, snapshot_suffix => "audio" }, {
135            ::insta::assert_binary_snapshot!(&name, data_wav.as_slice().to_vec());
136        });
137    }};
138
139    // With input source (name.svg + name.wav) - widened name
140    ($name:expr, $unit:expr, $input:expr) => {{
141        let __name: String = ::std::convert::Into::into($name);
142        let mut __unit = $unit;
143        let __unit_clone = __unit.clone();
144        // Input expression is evaluated twice; prefer passing a cheap expression (e.g. InputSource::impulse()).
145        // If you have a bound variable you need two independent values.
146
147        // SVG
148        let config = $crate::config::SnapshotConfigBuilder::default()
149            .chart_title(__name.as_str())
150            .build()
151            .unwrap();
152        let name = config.file_name(Some(__name.as_str()));
153        let data_svg =
154            $crate::snapshot::snapshot_audio_unit_with_input_and_options(__unit, $input, config);
155
156        ::insta::with_settings!({ omit_expression => true}, {
157            ::insta::assert_binary_snapshot!(&name, data_svg.as_slice().to_vec());
158        });
159
160        // WAV16
161        let config = $crate::config::SnapshotConfigBuilder::default()
162            .output_mode($crate::config::WavOutput::Wav16)
163            .build()
164            .unwrap();
165        let name = config.file_name(Some(__name.as_str()));
166        let data_wav =
167            $crate::snapshot::snapshot_audio_unit_with_input_and_options(__unit_clone, $input, config);
168
169        ::insta::with_settings!({ omit_expression => true, snapshot_suffix => "audio" }, {
170            ::insta::assert_binary_snapshot!(&name, data_wav.as_slice().to_vec());
171        });
172    }};
173
174    // With input source and config (single snapshot; uses config.output_mode) - widened name
175    ($name:expr, $unit:expr, $input:expr, $config:expr) => {{
176        let __name: String = ::std::convert::Into::into($name);
177        let mut config = $config;
178        config.maybe_title(__name.as_str());
179
180        let is_audio = matches!(
181            config.output_mode,
182            $crate::config::SnapshotOutputMode::Wav(_)
183        );
184
185        let name = config.file_name(Some(__name.as_str()));
186        let data = $crate::snapshot::snapshot_audio_unit_with_input_and_options($unit, $input, config);
187
188        if is_audio {
189            ::insta::with_settings!({ omit_expression => true, snapshot_suffix => "audio" }, {
190                ::insta::assert_binary_snapshot!(&name, data.as_slice().to_vec());
191            });
192        }
193        else {
194            ::insta::with_settings!({ omit_expression => true}, {
195                ::insta::assert_binary_snapshot!(&name, data.as_slice().to_vec());
196            });
197        }
198    }};
199
200    // With unit and config (single snapshot; uses config.output_mode)
201    // NOTE: The (name, unit) two-arg arm keeps name as a literal to avoid ambiguity with this (unit, config) arm.
202    // If you need a dynamic (non-literal) name, use a 3-arg or 4-arg form (name, unit, input[, config]) which are widened to $name:expr.
203        ($unit:expr, $config:expr) => {{
204
205        let is_audio = matches!(
206            $config.output_mode,
207            $crate::config::SnapshotOutputMode::Wav(_)
208        );
209        // Capture name before moving config into processing.
210        let name = $config.file_name(None);
211        let data = $crate::snapshot::snapshot_audio_unit_with_options($unit, $config);
212
213        if is_audio {
214            ::insta::with_settings!({ omit_expression => true, snapshot_suffix => "audio" }, {
215                ::insta::assert_binary_snapshot!(&name, data.as_slice().to_vec());
216            });
217        }
218        else {
219            ::insta::with_settings!({ omit_expression => true }, {
220                ::insta::assert_binary_snapshot!(&name, data.as_slice().to_vec());
221            });
222        }
223    }};
224}
225
226/// Macro to snapshot a fundsp `Net` as a Graphviz DOT binary using `snapshot_dsp_net`.
227///
228/// ### Usage:
229///
230/// `assert_dsp_net_snapshot!(name_expr, net_expr);`
231///
232/// - name_expr: used to name the snapshot file (e.g., "my_net")
233/// - net_expr: expression that evaluates to a `fundsp::net::Net`
234///
235/// Produces a single binary snapshot (.dot content) under the given name.
236#[macro_export]
237macro_rules! assert_dsp_net_snapshot {
238    ($name:expr, $net:expr) => {{
239        let __name: String = ::std::convert::Into::into($name);
240        // Build DOT bytes from the Net
241        let __snap_name = format!("{}.dot", __name);
242        let __bytes: ::std::vec::Vec<u8> = $crate::graph::snapshot_dsp_net_wiring($net);
243        // Assert as binary snapshot
244        ::insta::with_settings!({ omit_expression => true }, {
245            ::insta::assert_binary_snapshot!(&__snap_name, __bytes);
246        });
247    }};
248}