1use fundsp::prelude::*;
2
3use crate::abnormal::AbnormalSample;
4use crate::chart::generate_svg;
5use crate::config::{Processing, SnapshotConfig, SnapshotOutputMode, SvgChartConfig};
6use crate::input::InputSource;
7use crate::meta::SnapshotMetadata;
8use crate::meta_dashboard::generate_meta_dashboard_svg;
9use crate::wav::generate_wav;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum SnapshotAbnormalSample {
19 Nan,
21 NegInf,
23 PosInf,
25}
26
27impl std::fmt::Display for SnapshotAbnormalSample {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 match self {
30 SnapshotAbnormalSample::Nan => write!(f, "NaN"),
31 SnapshotAbnormalSample::NegInf => write!(f, "-∞"),
32 SnapshotAbnormalSample::PosInf => write!(f, "∞"),
33 }
34 }
35}
36
37impl From<AbnormalSample> for SnapshotAbnormalSample {
38 fn from(value: AbnormalSample) -> Self {
39 match value {
40 AbnormalSample::Nan => Self::Nan,
41 AbnormalSample::NegInf => Self::NegInf,
42 AbnormalSample::PosInf => Self::PosInf,
43 }
44 }
45}
46
47impl From<SnapshotAbnormalSample> for AbnormalSample {
48 fn from(value: SnapshotAbnormalSample) -> Self {
49 match value {
50 SnapshotAbnormalSample::Nan => Self::Nan,
51 SnapshotAbnormalSample::NegInf => Self::NegInf,
52 SnapshotAbnormalSample::PosInf => Self::PosInf,
53 }
54 }
55}
56
57#[derive(Debug, Clone)]
64pub struct AudioUnitSnapshotData {
65 pub input_data: Vec<Vec<f32>>,
67 pub output_data: Vec<Vec<f32>>,
69 pub abnormalities: Vec<Vec<(usize, SnapshotAbnormalSample)>>,
75 pub sample_rate: f64,
77 pub num_samples: usize,
79 pub start_sample: usize,
82}
83
84pub fn snapshot_audio_unit<N>(unit: N) -> Vec<u8>
96where
97 N: AudioUnit,
98{
99 snapshot_audio_unit_with_input_and_options(unit, InputSource::None, SnapshotConfig::default())
100}
101
102pub fn snapshot_audio_unit_data<N>(unit: N) -> AudioUnitSnapshotData
118where
119 N: AudioUnit,
120{
121 snapshot_audio_unit_data_with_input_and_options(unit, InputSource::None, SnapshotConfig::default())
122}
123
124pub fn snapshot_audio_unit_with_options<N>(unit: N, options: SnapshotConfig) -> Vec<u8>
137where
138 N: AudioUnit,
139{
140 snapshot_audio_unit_with_input_and_options(unit, InputSource::None, options)
141}
142
143pub fn snapshot_audio_unit_data_with_options<N>(unit: N, options: SnapshotConfig) -> AudioUnitSnapshotData
159where
160 N: AudioUnit,
161{
162 snapshot_audio_unit_data_with_input_and_options(unit, InputSource::None, options)
163}
164
165pub fn snapshot_audio_unit_with_input<N>(unit: N, input_source: InputSource) -> Vec<u8>
178where
179 N: AudioUnit,
180{
181 snapshot_audio_unit_with_input_and_options(
182 unit,
183 input_source,
184 SnapshotConfig {
185 ..SnapshotConfig::default()
186 },
187 )
188}
189
190pub fn snapshot_audio_unit_data_with_input<N>(unit: N, input_source: InputSource) -> AudioUnitSnapshotData
206where
207 N: AudioUnit,
208{
209 snapshot_audio_unit_data_with_input_and_options(
210 unit,
211 input_source,
212 SnapshotConfig {
213 ..SnapshotConfig::default()
214 },
215 )
216}
217
218pub fn snapshot_audio_unit_with_input_and_options<N>(
232 unit: N,
233 input_source: InputSource,
234 config: SnapshotConfig,
235) -> Vec<u8>
236where
237 N: AudioUnit,
238{
239 let snapshot_data = capture_audio_unit_data(unit, input_source, &config);
240 render_snapshot_output(&snapshot_data, &config.output_mode)
241}
242
243pub fn snapshot_audio_unit_data_with_input_and_options<N>(
264 unit: N,
265 input_source: InputSource,
266 config: SnapshotConfig,
267) -> AudioUnitSnapshotData
268where
269 N: AudioUnit,
270{
271 capture_audio_unit_data(unit, input_source, &config)
272}
273
274pub fn snapshot_metadata_dashboard(metadata: &SnapshotMetadata) -> Vec<u8> {
279 snapshot_metadata_dashboard_with_chart_options(metadata, SvgChartConfig::default())
280}
281
282pub fn snapshot_metadata_dashboard_with_chart_options(
284 metadata: &SnapshotMetadata,
285 chart_config: SvgChartConfig,
286) -> Vec<u8> {
287 if let Err(err) = metadata.validate() {
288 panic!("invalid metadata dashboard payload: {err}");
289 }
290 generate_meta_dashboard_svg(metadata, &chart_config)
291 .as_bytes()
292 .to_vec()
293}
294
295pub fn snapshot_metadata_dashboard_with_snapshot_config(
300 metadata: &SnapshotMetadata,
301 config: &SnapshotConfig,
302) -> Vec<u8> {
303 let chart_config = match &config.output_mode {
304 SnapshotOutputMode::SvgChart(chart) => chart.clone(),
305 SnapshotOutputMode::Wav(_) => SvgChartConfig::default(),
306 };
307 snapshot_metadata_dashboard_with_chart_options(metadata, chart_config)
308}
309
310fn capture_audio_unit_data<N>(
311 mut unit: N,
312 mut input_source: InputSource,
313 config: &SnapshotConfig,
314) -> AudioUnitSnapshotData
315where
316 N: AudioUnit,
317{
318 let num_inputs = N::inputs(&unit);
319 let num_outputs = N::outputs(&unit);
320
321 unit.set_sample_rate(config.sample_rate);
322 unit.reset();
323 unit.allocate();
324
325 let input_data = input_source.make_data(num_inputs, config.num_samples);
326
327 let mut output_data: Vec<Vec<f32>> = vec![vec![]; num_outputs];
328
329 let warmup_samples = config
330 .warm_up
331 .warm_up_samples(config.sample_rate, num_inputs);
332
333 let num_warmup_samples = warmup_samples
334 .iter()
335 .map(|ch| ch.len())
336 .next()
337 .unwrap_or_default();
338
339 let mut abnormalities: Vec<Vec<(usize, SnapshotAbnormalSample)>> = vec![vec![]; num_outputs];
340
341 let mut checked_sample = |mut sample: f32, ch: usize, i: usize| {
342 if sample.is_nan() || sample.is_infinite() {
343 let abnormality = SnapshotAbnormalSample::from(AbnormalSample::from(sample));
344
345 if config.allow_abnormal_samples {
346 abnormalities[ch].push((i, abnormality));
347 sample = 0.0;
348 } else {
349 panic!("Output channel #[{ch}] at sample [{i}] produced [{abnormality}] sample");
350 }
351 }
352 sample
353 };
354
355 (0..num_warmup_samples).for_each(|i| {
356 let mut input_frame = vec![0.0; num_inputs];
357 for ch in 0..num_inputs {
358 input_frame[ch] = warmup_samples[ch][i];
359 }
360 let mut output_frame = vec![0.0; num_outputs];
361 unit.tick(&input_frame, &mut output_frame);
362 });
364
365 match config.processing_mode {
366 Processing::Tick => {
367 (0..config.num_samples).for_each(|i| {
368 let mut input_frame = vec![0.0; num_inputs];
369 for ch in 0..num_inputs {
370 input_frame[ch] = input_data[ch][i];
371 }
372 let mut output_frame = vec![0.0; num_outputs];
373 unit.tick(&input_frame, &mut output_frame);
374 for ch in 0..num_outputs {
375 let sample = checked_sample(output_frame[ch], ch, i);
376 output_data[ch].push(sample);
377 }
378 });
379 }
380 Processing::Batch(batch_size) => {
381 assert!(
382 batch_size <= MAX_BUFFER_SIZE as u8,
383 "Batch size must be less than or equal to [{MAX_BUFFER_SIZE}]"
384 );
385
386 let samples_index = (0..config.num_samples).collect::<Vec<_>>();
387 for chunk in samples_index.chunks(batch_size as usize) {
388 let mut input_buff = BufferVec::new(num_inputs);
389 for (frame_index, input_index) in chunk.iter().enumerate() {
390 for (ch, input) in input_data.iter().enumerate() {
391 let value: f32 = input[*input_index];
392 input_buff.set_f32(ch, frame_index, value);
393 }
394 }
395 let input_ref = input_buff.buffer_ref();
396 let mut output_buf = BufferVec::new(num_outputs);
397 let mut output_ref = output_buf.buffer_mut();
398
399 unit.process(chunk.len(), &input_ref, &mut output_ref);
400
401 for (ch, data) in output_data.iter_mut().enumerate() {
402 data.extend(
403 output_buf
404 .channel_f32(ch)
405 .iter()
406 .enumerate()
407 .map(|(i, &value)| checked_sample(value, ch, i + chunk[0])),
408 );
409 }
410 }
411 }
412 }
413
414 AudioUnitSnapshotData {
415 input_data,
416 output_data,
417 abnormalities,
418 sample_rate: config.sample_rate,
419 num_samples: config.num_samples,
420 start_sample: config.warm_up.num_samples(config.sample_rate),
421 }
422}
423
424fn render_snapshot_output(data: &AudioUnitSnapshotData, output_mode: &SnapshotOutputMode) -> Vec<u8> {
425 match output_mode {
426 SnapshotOutputMode::SvgChart(svg_chart_config) => {
427 let abnormalities = data
428 .abnormalities
429 .iter()
430 .map(|channel| {
431 channel
432 .iter()
433 .map(|(i, abnormality)| (*i, AbnormalSample::from(*abnormality)))
434 .collect::<Vec<_>>()
435 })
436 .collect::<Vec<_>>();
437
438 generate_svg(
439 &data.input_data,
440 &data.output_data,
441 &abnormalities,
442 svg_chart_config,
443 data.sample_rate,
444 data.num_samples,
445 data.start_sample,
446 )
447 .as_bytes()
448 .to_vec()
449 }
450 SnapshotOutputMode::Wav(wav_output) => {
451 generate_wav(&data.output_data, wav_output, data.sample_rate, data.num_samples)
452 }
453 }
454}