1use azul_css::F32Vec;
15
16#[repr(C)]
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub struct AudioConfig {
20 pub sample_rate: u32,
22 pub channels: u16,
24}
25
26impl Default for AudioConfig {
27 fn default() -> Self {
28 Self {
29 sample_rate: 48_000,
30 channels: 1,
31 }
32 }
33}
34
35impl AudioConfig {
36 #[must_use] pub const fn new(sample_rate: u32, channels: u16) -> Self {
38 Self {
39 sample_rate,
40 channels,
41 }
42 }
43}
44
45#[repr(C)]
51#[derive(Debug, Clone, PartialEq)]
52pub struct AudioFrame {
53 pub sample_rate: u32,
55 pub channels: u16,
57 pub samples: F32Vec,
59}
60
61impl AudioFrame {
62 #[must_use] pub fn frame_count(&self) -> usize {
64 if self.channels == 0 {
65 0
66 } else {
67 self.samples.as_ref().len() / self.channels as usize
68 }
69 }
70}
71
72impl_option!(AudioFrame, OptionAudioFrame, copy = false, [Clone, Debug]);
75
76#[cfg(test)]
77mod autotest_generated {
78 use alloc::{vec, vec::Vec};
79
80 use super::*;
81
82 fn frame(sample_rate: u32, channels: u16, samples: Vec<f32>) -> AudioFrame {
87 AudioFrame {
88 sample_rate,
89 channels,
90 samples: F32Vec::from_vec(samples),
91 }
92 }
93
94 #[test]
98 fn config_new_fields_match_args() {
99 let c = AudioConfig::new(48_000, 2);
100 assert_eq!(c.sample_rate, 48_000);
101 assert_eq!(c.channels, 2);
102 }
103
104 #[test]
106 fn config_new_extreme_args_no_panic() {
107 let zero = AudioConfig::new(0, 0);
108 assert_eq!(zero.sample_rate, 0);
109 assert_eq!(zero.channels, 0);
110
111 let max = AudioConfig::new(u32::MAX, u16::MAX);
112 assert_eq!(max.sample_rate, u32::MAX);
113 assert_eq!(max.channels, u16::MAX);
114
115 let odd = AudioConfig::new(1, u16::MAX);
117 assert_eq!(odd.sample_rate, 1);
118 assert_eq!(odd.channels, u16::MAX);
119 }
120
121 #[test]
123 fn config_new_is_const_evaluable() {
124 const C: AudioConfig = AudioConfig::new(44_100, 1);
125 assert_eq!(C.sample_rate, 44_100);
126 assert_eq!(C.channels, 1);
127 }
128
129 #[test]
131 fn config_default_matches_new() {
132 assert_eq!(AudioConfig::default(), AudioConfig::new(48_000, 1));
133 }
134
135 #[test]
137 fn config_eq_hash_consistent() {
138 use core::hash::{Hash, Hasher};
139 use std::collections::hash_map::DefaultHasher;
140
141 let a = AudioConfig::new(96_000, 2);
142 let b = a; assert_eq!(a, b);
144
145 let mut ha = DefaultHasher::new();
146 let mut hb = DefaultHasher::new();
147 a.hash(&mut ha);
148 b.hash(&mut hb);
149 assert_eq!(ha.finish(), hb.finish());
150
151 assert_ne!(AudioConfig::new(96_000, 2), AudioConfig::new(96_000, 1));
153 assert_ne!(AudioConfig::new(96_000, 2), AudioConfig::new(48_000, 2));
154 }
155
156 #[test]
160 fn frame_count_mono_known_value() {
161 let f = frame(48_000, 1, vec![0.0, 0.5, -0.5, 1.0]);
162 assert_eq!(f.frame_count(), 4);
163 }
164
165 #[test]
167 fn frame_count_stereo_known_value() {
168 let f = frame(48_000, 2, vec![0.0, 0.5, -0.5, 1.0]);
169 assert_eq!(f.frame_count(), 2);
170 }
171
172 #[test]
174 fn frame_count_zero_channels_no_div_by_zero() {
175 let f = frame(48_000, 0, vec![0.0, 0.1, 0.2, 0.3, 0.4]);
176 assert_eq!(f.frame_count(), 0);
177 }
178
179 #[test]
181 fn frame_count_zero_channels_empty_samples() {
182 let f = frame(0, 0, Vec::new());
183 assert_eq!(f.frame_count(), 0);
184 }
185
186 #[test]
188 fn frame_count_empty_samples() {
189 assert_eq!(frame(48_000, 1, Vec::new()).frame_count(), 0);
190 assert_eq!(frame(48_000, 2, Vec::new()).frame_count(), 0);
191 assert_eq!(
192 frame(48_000, u16::MAX, Vec::new()).frame_count(),
193 0
194 );
195 }
196
197 #[test]
199 fn frame_count_truncates_partial_frame() {
200 let f = frame(48_000, 2, vec![0.0, 0.1, 0.2, 0.3, 0.4]);
201 assert_eq!(f.frame_count(), 2);
202 }
203
204 #[test]
206 fn frame_count_channels_exceed_samples() {
207 let f = frame(48_000, u16::MAX, vec![0.0, 1.0, -1.0]);
208 assert_eq!(f.frame_count(), 0);
209 }
210
211 #[test]
213 fn frame_count_one_full_max_channel_frame() {
214 let samples = vec![0.25_f32; u16::MAX as usize];
215 let f = frame(48_000, u16::MAX, samples);
216 assert_eq!(f.frame_count(), 1);
217 }
218
219 #[test]
221 fn frame_count_ignores_non_finite_samples() {
222 let f = frame(
223 48_000,
224 2,
225 vec![f32::NAN, f32::INFINITY, f32::NEG_INFINITY, 0.0],
226 );
227 assert_eq!(f.frame_count(), 2);
228 }
229
230 #[test]
232 fn frame_count_large_buffer() {
233 let n = 100_000usize;
234 let f = frame(48_000, 2, vec![0.0_f32; n]);
235 assert_eq!(f.frame_count(), n / 2);
236 }
237
238 #[test]
242 fn frame_clone_round_trip_eq() {
243 let original = frame(44_100, 2, vec![0.0, 0.5, -0.5, 1.0, -1.0, 0.25]);
244 let cloned = original.clone();
245 assert_eq!(original, cloned);
246 assert_eq!(original.frame_count(), cloned.frame_count());
247 assert_eq!(original.samples.as_ref(), cloned.samples.as_ref());
248 }
249
250 #[test]
252 fn option_audio_frame_round_trip() {
253 let f = frame(48_000, 1, vec![0.1, 0.2, 0.3]);
254
255 let wrapped: OptionAudioFrame = Some(f.clone()).into();
256 let unwrapped: Option<AudioFrame> = wrapped.into();
257 assert_eq!(unwrapped, Some(f));
258
259 let none_wrapped: OptionAudioFrame = None.into();
260 let none_unwrapped: Option<AudioFrame> = none_wrapped.into();
261 assert_eq!(none_unwrapped, None);
262 }
263
264 #[test]
266 fn frame_with_nan_is_not_self_equal() {
267 let f = frame(48_000, 1, vec![f32::NAN]);
268 assert_ne!(f, f.clone());
270 assert_eq!(f.frame_count(), 1);
272 }
273
274 #[test]
280 fn config_repr_c_layout_is_stable() {
281 assert_eq!(core::mem::size_of::<AudioConfig>(), 8);
282 assert_eq!(core::mem::align_of::<AudioConfig>(), 4);
283 }
284
285 #[test]
288 fn config_new_no_field_truncation_sweep() {
289 const RATES: [u32; 8] = [
290 0,
291 1,
292 8_000,
293 44_100,
294 48_000,
295 192_000,
296 u16::MAX as u32, u32::MAX,
298 ];
299 const CHANNELS: [u16; 6] = [0, 1, 2, 8, 255, u16::MAX];
300
301 for &rate in &RATES {
302 for &ch in &CHANNELS {
303 let c = AudioConfig::new(rate, ch);
304 assert_eq!(c.sample_rate, rate, "rate {rate} ch {ch}");
305 assert_eq!(c.channels, ch, "rate {rate} ch {ch}");
306 assert_eq!(c, AudioConfig::new(c.sample_rate, c.channels));
308 }
309 }
310 }
311
312 #[test]
315 fn config_new_does_not_normalize() {
316 let c = AudioConfig::new(0, 0);
317 assert_ne!(c, AudioConfig::default());
318 assert_eq!(c.sample_rate, 0);
319 assert_eq!(c.channels, 0);
320 }
321
322 #[test]
325 fn config_copy_is_independent() {
326 let original = AudioConfig::new(48_000, 2);
327 let mut copy = original;
328 copy.sample_rate = 8_000;
329 copy.channels = 1;
330 assert_eq!(original.sample_rate, 48_000);
331 assert_eq!(original.channels, 2);
332 assert_eq!(copy, AudioConfig::new(8_000, 1));
333 }
334
335 #[test]
338 fn config_hash_set_dedup() {
339 use std::collections::HashSet;
340
341 let mut set = HashSet::new();
342 assert!(set.insert(AudioConfig::new(48_000, 2)));
343 assert!(!set.insert(AudioConfig::new(48_000, 2))); assert!(set.insert(AudioConfig::new(48_000, 1))); assert!(set.insert(AudioConfig::new(44_100, 2))); assert!(set.insert(AudioConfig::new(u32::MAX, u16::MAX)));
347 assert_eq!(set.len(), 4);
348 assert!(set.contains(&AudioConfig::new(48_000, 2)));
349 }
350
351 #[test]
357 fn frame_count_division_invariant_sweep() {
358 const CHANNELS: [u16; 9] = [0, 1, 2, 3, 5, 7, 8, 255, u16::MAX];
359
360 for &ch in &CHANNELS {
361 for len in 0..24usize {
362 let f = frame(48_000, ch, vec![0.5_f32; len]);
363 let fc = f.frame_count();
364
365 if ch == 0 {
366 assert_eq!(fc, 0, "ch=0 len={len}");
368 continue;
369 }
370
371 let ch_us = ch as usize;
372 assert_eq!(fc, len / ch_us, "ch={ch} len={len}");
373 let consumed = fc.checked_mul(ch_us).expect("frame_count * channels overflowed");
375 assert!(consumed <= len, "ch={ch} len={len} consumed={consumed}");
376 assert!(len - consumed < ch_us, "ch={ch} len={len}");
378 }
379 }
380 }
381
382 #[test]
385 fn frame_count_max_channel_boundaries() {
386 let n = u16::MAX as usize;
387 assert_eq!(frame(48_000, u16::MAX, vec![0.0; n - 1]).frame_count(), 0);
388 assert_eq!(frame(48_000, u16::MAX, vec![0.0; n]).frame_count(), 1);
389 assert_eq!(frame(48_000, u16::MAX, vec![0.0; n + 1]).frame_count(), 1);
390 assert_eq!(frame(48_000, u16::MAX, vec![0.0; 2 * n]).frame_count(), 2);
391 assert_eq!(
392 frame(48_000, u16::MAX, vec![0.0; 2 * n - 1]).frame_count(),
393 1
394 );
395 }
396
397 #[test]
400 fn frame_count_ignores_sample_rate() {
401 let samples = vec![0.0, 0.1, 0.2, 0.3];
402 for &rate in &[0u32, 1, 48_000, u32::MAX] {
403 assert_eq!(frame(rate, 2, samples.clone()).frame_count(), 2, "rate={rate}");
404 }
405 }
406
407 #[test]
410 fn frame_count_is_pure() {
411 let f = frame(48_000, 2, vec![0.0, 0.1, 0.2, 0.3, 0.4, 0.5]);
412 let before: Vec<f32> = f.samples.as_ref().to_vec();
413 let first = f.frame_count();
414 let second = f.frame_count();
415 let third = f.frame_count();
416 assert_eq!(first, 3);
417 assert_eq!(first, second);
418 assert_eq!(second, third);
419 assert_eq!(f.samples.as_ref(), before.as_slice());
420 assert_eq!(f.samples.len(), 6);
421 }
422
423 #[test]
426 fn frame_count_static_backed_samples() {
427 static SAMPLES: [f32; 6] = [0.0, 0.5, -0.5, 1.0, -1.0, 0.25];
428
429 let f = AudioFrame {
430 sample_rate: 48_000,
431 channels: 2,
432 samples: F32Vec::from_const_slice(&SAMPLES),
433 };
434 assert_eq!(f.frame_count(), 3);
435 assert_eq!(f.samples.as_ref(), &SAMPLES[..]);
436
437 let cloned = f.clone();
439 assert_eq!(cloned, f);
440 drop(f);
441 assert_eq!(cloned.frame_count(), 3);
442 }
443
444 #[test]
447 fn frame_count_default_f32vec_is_empty() {
448 let f = AudioFrame {
449 sample_rate: 48_000,
450 channels: 2,
451 samples: F32Vec::new(),
452 };
453 assert_eq!(f.frame_count(), 0);
454 assert!(f.samples.is_empty());
455 assert_eq!(f.samples.len(), 0);
456 assert_eq!(f.samples.as_ref(), &[] as &[f32]);
457 assert_eq!(f.clone(), f);
458 }
459
460 #[test]
463 fn frame_count_from_iterator_matches_from_vec() {
464 let via_iter = AudioFrame {
465 sample_rate: 48_000,
466 channels: 2,
467 samples: (0..10).map(|i| i as f32).collect::<F32Vec>(),
468 };
469 let via_vec = frame(48_000, 2, (0..10).map(|i| i as f32).collect::<Vec<f32>>());
470 assert_eq!(via_iter, via_vec);
471 assert_eq!(via_iter.frame_count(), 5);
472 assert_eq!(via_iter.frame_count(), via_vec.frame_count());
473 }
474
475 #[test]
478 fn frame_sample_access_is_bounds_checked() {
479 let f = frame(48_000, 2, vec![0.0, 0.1, 0.2, 0.3]);
480 let last = f.frame_count() * f.channels as usize - 1;
481 assert_eq!(f.samples.get(last), Some(&0.3));
482 assert_eq!(f.samples.get(f.samples.len()), None);
483 assert_eq!(f.samples.get(usize::MAX), None);
484 }
485
486 #[test]
489 fn frame_extreme_sample_values_preserved_bitwise() {
490 let raw = vec![
491 f32::MIN,
492 f32::MAX,
493 f32::MIN_POSITIVE,
494 -f32::MIN_POSITIVE,
495 0.0,
496 -0.0,
497 f32::EPSILON,
498 1e-45, ];
500 let f = frame(48_000, 2, raw.clone());
501 assert_eq!(f.frame_count(), 4);
502
503 let cloned = f.clone();
504 let got: Vec<u32> = cloned.samples.as_ref().iter().map(|s| s.to_bits()).collect();
505 let want: Vec<u32> = raw.iter().map(|s| s.to_bits()).collect();
506 assert_eq!(got, want, "clone must be bit-exact (incl. -0.0 and subnormals)");
507
508 assert_eq!(cloned, f);
511 assert_ne!(0.0_f32.to_bits(), (-0.0_f32).to_bits());
512 }
513
514 #[test]
517 fn frame_eq_discriminates_header_fields() {
518 let samples = vec![0.0, 0.5, -0.5, 1.0];
519 let base = frame(48_000, 2, samples.clone());
520 assert_eq!(base, frame(48_000, 2, samples.clone()));
521 assert_ne!(base, frame(44_100, 2, samples.clone()));
522 assert_ne!(base, frame(48_000, 1, samples.clone()));
523 assert_ne!(base, frame(48_000, 2, vec![0.0, 0.5, -0.5]));
524 assert_eq!(frame(48_000, 1, vec![0.0, 0.5]).frame_count(), 2);
526 assert_ne!(base, frame(48_000, 1, vec![0.0, 0.5]));
527 }
528
529 #[test]
532 fn frame_repeated_clone_drop_is_stable() {
533 let original = frame(48_000, 2, (0..64).map(|i| i as f32).collect::<Vec<f32>>());
534 for _ in 0..64 {
535 let c = original.clone();
536 assert_eq!(c.frame_count(), 32);
537 assert_eq!(c.samples.as_ref(), original.samples.as_ref());
538 drop(c);
539 }
540 assert_eq!(original.frame_count(), 32);
542 assert_eq!(original.samples.len(), 64);
543 }
544
545 #[test]
549 fn option_audio_frame_predicates() {
550 let none = OptionAudioFrame::default();
551 assert!(none.is_none());
552 assert!(!none.is_some());
553 assert!(none.as_ref().is_none());
554 assert!(none.as_option().is_none());
555
556 let some: OptionAudioFrame = Some(frame(48_000, 2, vec![0.0, 1.0])).into();
557 assert!(some.is_some());
558 assert!(!some.is_none());
559 assert_eq!(some.as_ref().map(AudioFrame::frame_count), Some(1));
560 }
561
562 #[test]
565 fn option_audio_frame_into_option_twice() {
566 let wrapped: OptionAudioFrame = Some(frame(48_000, 2, vec![0.0, 0.5, -0.5, 1.0])).into();
567 let a = wrapped.into_option().expect("some");
568 let b = wrapped.into_option().expect("some");
569 assert_eq!(a, b);
570 assert_eq!(a.frame_count(), 2);
571 drop(a);
572 assert_eq!(b.frame_count(), 2);
574 assert!(wrapped.is_some());
575 }
576
577 #[test]
580 fn option_audio_frame_replace_returns_previous() {
581 let mut slot = OptionAudioFrame::None;
582 let prev = slot.replace(frame(48_000, 1, vec![0.0, 0.1]));
583 assert!(prev.is_none());
584 assert_eq!(slot.as_ref().map(AudioFrame::frame_count), Some(2));
585
586 let prev = slot.replace(frame(48_000, 2, vec![0.0, 0.1, 0.2, 0.3]));
587 assert_eq!(prev.as_ref().map(AudioFrame::frame_count), Some(2));
588 assert_eq!(slot.as_ref().map(AudioFrame::frame_count), Some(2));
589 assert_eq!(slot.as_ref().map(|f| f.channels), Some(2));
590 }
591
592 #[test]
595 fn option_audio_frame_as_mut_mutation_visible() {
596 let mut slot: OptionAudioFrame = Some(frame(48_000, 2, vec![0.0, 0.1, 0.2, 0.3])).into();
597 assert_eq!(slot.as_ref().map(AudioFrame::frame_count), Some(2));
598
599 if let Some(f) = slot.as_mut() {
600 f.channels = 0; }
602 assert_eq!(slot.as_ref().map(AudioFrame::frame_count), Some(0));
603
604 if let Some(f) = slot.as_mut() {
605 f.channels = 1;
606 }
607 assert_eq!(slot.as_ref().map(AudioFrame::frame_count), Some(4));
608 }
609
610 #[test]
613 fn option_audio_frame_round_trip_empty_samples() {
614 let f = frame(48_000, 2, Vec::new());
615 let wrapped: OptionAudioFrame = Some(f.clone()).into();
616 let back: Option<AudioFrame> = wrapped.into();
617 let back = back.expect("some");
618 assert_eq!(back, f);
619 assert_eq!(back.frame_count(), 0);
620 assert!(back.samples.is_empty());
621 }
622}