1#[derive(Copy, Clone, Debug, Default, PartialEq)]
19pub struct WaveformBucket {
20 pub min: f32,
22 pub max: f32,
24 pub rms: f32,
26}
27
28#[derive(Clone, Debug, PartialEq)]
30pub struct WaveformLevel {
31 pub bucket_samples: usize,
33 pub channels: Vec<Vec<WaveformBucket>>,
35}
36
37#[derive(Clone, Debug, PartialEq)]
39pub struct Waveform {
40 pub frames: u64,
42 pub levels: Vec<WaveformLevel>,
44}
45
46#[derive(Copy, Clone, Debug, Eq, PartialEq)]
48#[non_exhaustive]
49pub enum WaveformError {
50 InvalidShape,
52 MismatchedBlock,
54}
55
56impl std::fmt::Display for WaveformError {
57 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 formatter.write_str(match self {
59 Self::InvalidShape => "a waveform needs channels, a positive bucket, and a level",
60 Self::MismatchedBlock => "the block's channels do not match the waveform's",
61 })
62 }
63}
64
65impl std::error::Error for WaveformError {}
66
67const FOLD: usize = 4;
69pub const MAX_WAVEFORM_LEVELS: usize = 16;
72
73#[derive(Copy, Clone, Debug)]
75struct Accumulator {
76 min: f32,
77 max: f32,
78 sum_squares: f64,
79 samples: u64,
80 parts: usize,
81}
82
83impl Accumulator {
84 const EMPTY: Self = Self {
85 min: f32::INFINITY,
86 max: f32::NEG_INFINITY,
87 sum_squares: 0.0,
88 samples: 0,
89 parts: 0,
90 };
91
92 fn is_empty(&self) -> bool {
93 self.samples == 0
94 }
95
96 fn add_sample(&mut self, sample: f32) {
99 let sample = if sample.is_finite() { sample } else { 0.0 };
100 self.min = lower(self.min, sample);
101 self.max = higher(self.max, sample);
102 let square = f64::from(sample);
103 self.sum_squares += square * square;
104 self.samples += 1;
105 }
106
107 fn add_part(&mut self, part: &Self) {
108 self.min = lower(self.min, part.min);
109 self.max = higher(self.max, part.max);
110 self.sum_squares += part.sum_squares;
111 self.samples += part.samples;
112 self.parts += 1;
113 }
114
115 #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
116 fn bucket(&self) -> WaveformBucket {
117 WaveformBucket {
118 min: self.min,
119 max: self.max,
120 rms: (self.sum_squares / self.samples as f64).sqrt() as f32,
121 }
122 }
123}
124
125fn lower(current: f32, sample: f32) -> f32 {
127 if sample.total_cmp(¤t).is_lt() {
128 sample
129 } else {
130 current
131 }
132}
133
134fn higher(current: f32, sample: f32) -> f32 {
136 if sample.total_cmp(¤t).is_gt() {
137 sample
138 } else {
139 current
140 }
141}
142
143#[derive(Clone, Debug)]
145pub struct WaveformSummary {
146 base_bucket: usize,
147 levels: Vec<WaveformLevel>,
148 open: Vec<Vec<Accumulator>>,
150 frames: u64,
151}
152
153impl WaveformSummary {
154 pub fn new(channels: usize, base_bucket: usize, levels: usize) -> Result<Self, WaveformError> {
161 if channels == 0 || base_bucket == 0 || !(1..=MAX_WAVEFORM_LEVELS).contains(&levels) {
162 return Err(WaveformError::InvalidShape);
163 }
164 let mut bucket_samples = base_bucket;
165 let mut summary_levels = Vec::with_capacity(levels);
166 for _ in 0..levels {
167 summary_levels.push(WaveformLevel {
168 bucket_samples,
169 channels: vec![Vec::new(); channels],
170 });
171 bucket_samples = bucket_samples.saturating_mul(FOLD);
172 }
173 Ok(Self {
174 base_bucket,
175 levels: summary_levels,
176 open: vec![vec![Accumulator::EMPTY; channels]; levels],
177 frames: 0,
178 })
179 }
180
181 pub fn push(&mut self, planes: &[&[f32]]) -> Result<(), WaveformError> {
188 let channels = self.levels[0].channels.len();
189 let Some(frames) = planes.first().map(|plane| plane.len()) else {
190 return Err(WaveformError::MismatchedBlock);
191 };
192 if planes.len() != channels || planes.iter().any(|plane| plane.len() != frames) {
193 return Err(WaveformError::MismatchedBlock);
194 }
195 for index in 0..frames {
196 for (channel, plane) in planes.iter().enumerate() {
197 self.open[0][channel].add_sample(plane[index]);
198 }
199 if self.open[0][0].samples == self.base_bucket as u64 {
200 self.close(0);
201 }
202 }
203 self.frames += frames as u64;
204 Ok(())
205 }
206
207 fn close(&mut self, level: usize) {
210 let channels = self.open[level].len();
211 for channel in 0..channels {
212 let finished = self.open[level][channel];
213 self.levels[level].channels[channel].push(finished.bucket());
214 if let Some(next) = self.open.get_mut(level + 1) {
215 next[channel].add_part(&finished);
216 }
217 self.open[level][channel] = Accumulator::EMPTY;
218 }
219 if level + 1 < self.open.len() && self.open[level + 1][0].parts == FOLD {
220 self.close(level + 1);
221 }
222 }
223
224 #[must_use]
226 pub fn finish(mut self) -> Waveform {
227 for level in 0..self.open.len() {
228 if !self.open[level][0].is_empty() {
229 self.close(level);
230 }
231 }
232 Waveform {
233 frames: self.frames,
234 levels: self.levels,
235 }
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::{WaveformBucket, WaveformError, WaveformSummary};
242
243 fn extreme_bits(samples: &[f32], bucket: usize) -> Vec<(u32, u32)> {
245 let mut summary = WaveformSummary::new(1, bucket, 1).expect("shape");
246 summary.push(&[samples]).expect("block");
247 summary.finish().levels[0].channels[0]
248 .iter()
249 .map(|bucket| (bucket.min.to_bits(), bucket.max.to_bits()))
250 .collect()
251 }
252
253 #[test]
254 fn a_signed_zero_extreme_is_the_same_on_every_target() {
255 let (positive, negative) = (0.0_f32.to_bits(), (-0.0_f32).to_bits());
259 let samples = [
260 0.0, -0.0, 0.0, -0.0, -0.0, 0.0, -0.0, 0.0, -0.25, 0.0, -0.0, -0.0, 0.25, -0.0, 0.0, 0.0, -0.0, -0.0, -0.0, -0.0, ];
266 assert_eq!(
267 extreme_bits(&samples, 4),
268 [
269 (negative, positive),
270 (negative, positive),
271 ((-0.25_f32).to_bits(), positive),
272 (negative, 0.25_f32.to_bits()),
273 (negative, negative),
274 ]
275 );
276 let mut summary = WaveformSummary::new(1, 2, 2).expect("shape");
277 summary.push(&[&samples[..8]]).expect("block");
278 let coarse = summary.finish().levels[1].channels[0][0];
279 assert_eq!(
280 (coarse.min.to_bits(), coarse.max.to_bits()),
281 (negative, positive)
282 );
283 }
284
285 fn sine(frames: usize, period: usize, amplitude: f32) -> Vec<f32> {
286 (0..frames)
287 .map(|index| {
288 #[allow(clippy::cast_precision_loss)]
289 let phase = index as f32 / period as f32 * core::f32::consts::TAU;
290 amplitude * libm::sinf(phase)
291 })
292 .collect()
293 }
294
295 #[test]
296 fn a_sine_has_its_peak_and_its_rms() {
297 let signal = sine(4_096, 64, 0.5);
299 let mut summary = WaveformSummary::new(1, 256, 1).expect("shape");
300 summary.push(&[&signal]).expect("block");
301 let waveform = summary.finish();
302 assert_eq!(waveform.frames, 4_096);
303 let buckets = &waveform.levels[0].channels[0];
304 assert_eq!(buckets.len(), 16);
305 for bucket in buckets {
306 assert!((bucket.max - 0.5).abs() < 1e-6, "{bucket:?}");
307 assert!((bucket.min + 0.5).abs() < 1e-6, "{bucket:?}");
308 assert!(
309 (bucket.rms - 0.5 / 2.0_f32.sqrt()).abs() < 1e-5,
310 "{bucket:?}"
311 );
312 }
313 }
314
315 #[test]
316 fn coarser_levels_are_the_fold_of_finer_ones_and_match_direct_summaries() {
317 let left = sine(10_000, 37, 0.8);
318 let right = sine(10_000, 91, 0.3)
319 .into_iter()
320 .enumerate()
321 .map(|(index, sample)| if index % 7 == 0 { -sample } else { sample })
322 .collect::<Vec<_>>();
323 let mut folded = WaveformSummary::new(2, 16, 3).expect("shape");
324 folded.push(&[&left, &right]).expect("block");
325 let folded = folded.finish();
326 for (level, bucket_samples) in [(1, 64), (2, 256)] {
327 let mut direct = WaveformSummary::new(2, bucket_samples, 1).expect("shape");
328 direct.push(&[&left, &right]).expect("block");
329 let direct = direct.finish();
330 assert_eq!(folded.levels[level].bucket_samples, bucket_samples);
331 for channel in 0..2 {
332 let ours = &folded.levels[level].channels[channel];
333 let theirs = &direct.levels[0].channels[channel];
334 assert_eq!(ours.len(), theirs.len(), "level {level}");
335 for (a, b) in ours.iter().zip(theirs) {
336 assert_eq!(a.min.to_bits(), b.min.to_bits());
337 assert_eq!(a.max.to_bits(), b.max.to_bits());
338 assert!((a.rms - b.rms).abs() <= f32::EPSILON * b.rms, "{a:?} {b:?}");
341 }
342 }
343 }
344 assert_eq!(folded.levels[0].channels[0].len(), 625);
346 assert_eq!(folded.levels[1].channels[0].len(), 157);
347 assert_eq!(folded.levels[2].channels[0].len(), 40);
348 }
349
350 #[test]
351 fn block_boundaries_do_not_change_the_result() {
352 let signal = sine(5_000, 23, 0.9);
353 let whole = {
354 let mut summary = WaveformSummary::new(1, 30, 4).expect("shape");
355 summary.push(&[&signal]).expect("block");
356 summary.finish()
357 };
358 for block in [1, 7, 29, 128, 4_999] {
359 let mut summary = WaveformSummary::new(1, 30, 4).expect("shape");
360 for chunk in signal.chunks(block) {
361 summary.push(&[chunk]).expect("block");
362 }
363 assert_eq!(summary.finish(), whole, "block {block}");
364 }
365 }
366
367 #[test]
368 fn a_partial_last_bucket_covers_only_its_samples() {
369 let mut summary = WaveformSummary::new(1, 4, 2).expect("shape");
370 summary
371 .push(&[&[0.5, -0.5, 0.5, -0.5, 1.0, 1.0]])
372 .expect("block");
373 let waveform = summary.finish();
374 assert_eq!(
375 waveform.levels[0].channels[0],
376 vec![
377 WaveformBucket {
378 min: -0.5,
379 max: 0.5,
380 rms: 0.5
381 },
382 WaveformBucket {
383 min: 1.0,
384 max: 1.0,
385 rms: 1.0
386 },
387 ]
388 );
389 let top = waveform.levels[1].channels[0][0];
390 assert_eq!((top.min, top.max), (-0.5, 1.0));
391 assert!((top.rms - 0.5_f32.sqrt()).abs() < 1e-7);
393 }
394
395 #[test]
396 fn a_non_finite_sample_counts_as_silence_at_every_level() {
397 let samples = [
400 0.1,
401 f32::NAN,
402 0.2,
403 0.3,
404 f32::NAN,
405 f32::NAN,
406 f32::NAN,
407 f32::NAN,
408 0.1,
409 f32::INFINITY,
410 f32::NEG_INFINITY,
411 0.1,
412 ];
413 let mut summary = WaveformSummary::new(1, 4, 2).expect("shape");
414 summary.push(&[&samples]).expect("block");
415 let waveform = summary.finish();
416 let silenced = samples.map(|sample| if sample.is_finite() { sample } else { 0.0 });
417 let mut reference = WaveformSummary::new(1, 4, 2).expect("shape");
418 reference.push(&[&silenced]).expect("block");
419 assert_eq!(waveform, reference.finish());
420 for level in &waveform.levels {
421 for bucket in &level.channels[0] {
422 assert!(
423 bucket.min.is_finite() && bucket.max.is_finite() && bucket.rms.is_finite(),
424 "{bucket:?}"
425 );
426 }
427 }
428 assert_eq!(
430 waveform.levels[0].channels[0][1],
431 WaveformBucket {
432 min: 0.0,
433 max: 0.0,
434 rms: 0.0
435 }
436 );
437 }
438
439 #[test]
440 fn shapes_and_blocks_are_checked() {
441 assert_eq!(
442 WaveformSummary::new(0, 1, 1).map(|_| ()),
443 Err(WaveformError::InvalidShape)
444 );
445 assert_eq!(
446 WaveformSummary::new(1, 0, 1).map(|_| ()),
447 Err(WaveformError::InvalidShape)
448 );
449 assert_eq!(
450 WaveformSummary::new(1, 1, 17).map(|_| ()),
451 Err(WaveformError::InvalidShape)
452 );
453 let mut summary = WaveformSummary::new(2, 8, 1).expect("shape");
454 assert_eq!(
455 summary.push(&[&[0.0; 3], &[0.0; 2]]),
456 Err(WaveformError::MismatchedBlock)
457 );
458 assert_eq!(
459 summary.push(&[&[0.0; 3]]),
460 Err(WaveformError::MismatchedBlock)
461 );
462 }
463}