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