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}