1mod measurements;
3use crate::{Audio, Error, Result};
4pub use measurements::{
5 BoundaryMeasurement, BoundarySamples, ChannelStep, MAX_MEASUREMENT_BOUNDARIES,
6 MeasurementBoundary, StereoMeasurements, ThresholdSilence, dc_mean, stereo_measurements,
7};
8use std::f64::consts::PI;
9
10pub fn to_db(linear: f64) -> f64 {
11 if linear > 0.0 {
12 20.0 * linear.log10()
13 } else {
14 f64::NEG_INFINITY
15 }
16}
17pub fn from_db(db: f64) -> f64 {
18 crate::math::db_to_gain(db)
19}
20pub(crate) fn render_db(linear: f64) -> f64 {
23 if linear > 0.0 {
24 20.0 * crate::math::log2(linear) / std::f64::consts::LOG2_10
25 } else {
26 f64::NEG_INFINITY
27 }
28}
29#[derive(Clone, Debug)]
30pub struct LoudnessResult {
31 pub integrated: f64,
33 pub range: f64,
35 pub peak_momentary: f64,
37}
38#[derive(Clone, Debug)]
43pub struct DeterministicMeasurements {
44 pub loudness: LoudnessResult,
46 pub peak_db: f64,
48 pub true_peak_db: f64,
50}
51#[derive(Clone, Debug)]
52pub struct Waveform {
53 pub min: Vec<f32>,
54 pub max: Vec<f32>,
55}
56#[derive(Clone, Debug)]
57pub struct Spectrum {
61 pub frequencies: Vec<f64>,
63 pub magnitudes: Vec<f64>,
66}
67#[derive(Clone, Debug)]
68pub struct Spectrogram {
69 pub frequencies: Vec<f64>,
70 pub times: Vec<f64>,
71 pub magnitudes: Vec<Vec<f64>>,
72}
73
74fn fft(real: &mut [f64], imag: &mut [f64]) {
75 let n = real.len();
76 let mut j = 0;
77 for i in 1..n {
78 let mut bit = n >> 1;
79 while j & bit != 0 {
80 j ^= bit;
81 bit >>= 1;
82 }
83 j ^= bit;
84 if i < j {
85 real.swap(i, j);
86 imag.swap(i, j);
87 }
88 }
89 let mut len = 2;
90 while len <= n {
91 let theta = -2.0 * PI / len as f64;
92 let (wi, wr) = theta.sin_cos();
93 for start in (0..n).step_by(len) {
94 let (mut ur, mut ui) = (1.0, 0.0);
95 for k in 0..len / 2 {
96 let a = start + k;
97 let b = a + len / 2;
98 let tr = ur * real[b] - ui * imag[b];
99 let ti = ur * imag[b] + ui * real[b];
100 real[b] = real[a] - tr;
101 imag[b] = imag[a] - ti;
102 real[a] += tr;
103 imag[a] += ti;
104 let next = ur * wr - ui * wi;
105 ui = ur * wi + ui * wr;
106 ur = next;
107 }
108 }
109 len *= 2;
110 }
111}
112fn check_fft(sample_rate: u32, fft_size: usize) -> Result<()> {
113 if sample_rate == 0 || fft_size < 2 || !fft_size.is_power_of_two() || fft_size > 1 << 20 {
114 return Err(Error::invalid(
115 "FFT size must be a power of two from 2 to 1048576",
116 ));
117 }
118 Ok(())
119}
120fn check_finite(samples: &[f32]) -> Result<()> {
121 if samples.iter().any(|s| !s.is_finite()) {
122 return Err(Error::invalid("FFT samples must be finite"));
123 }
124 Ok(())
125}
126fn hann(fft_size: usize) -> (Vec<f64>, f64) {
128 let mut sum = 0.0;
129 let window = (0..fft_size)
130 .map(|i| {
131 let w = 0.5 - 0.5 * (2.0 * PI * i as f64 / (fft_size - 1) as f64).cos();
132 sum += w;
133 w
134 })
135 .collect();
136 (window, sum)
137}
138fn transform(
141 samples: &[f32],
142 window: &[f64],
143 sum: f64,
144 real: &mut [f64],
145 imag: &mut [f64],
146) -> Vec<f64> {
147 let fft_size = real.len();
148 for (i, (v, w)) in real.iter_mut().zip(window).enumerate() {
149 *v = samples.get(i).copied().unwrap_or(0.0) as f64 * w;
150 }
151 imag.fill(0.0);
152 fft(real, imag);
153 (0..=fft_size / 2)
154 .map(|i| {
155 real[i].hypot(imag[i]) / sum
156 * if i == 0 || i == fft_size / 2 {
157 1.0
158 } else {
159 2.0
160 }
161 })
162 .collect()
163}
164fn frequencies(sample_rate: u32, fft_size: usize) -> Vec<f64> {
165 (0..=fft_size / 2)
166 .map(|i| i as f64 * sample_rate as f64 / fft_size as f64)
167 .collect()
168}
169pub fn spectrum(samples: &[f32], sample_rate: u32, fft_size: usize) -> Result<Spectrum> {
190 check_fft(sample_rate, fft_size)?;
191 check_finite(samples)?;
192 let (window, sum) = hann(fft_size);
193 let mut real = vec![0.0; fft_size];
194 let mut imag = vec![0.0; fft_size];
195 Ok(Spectrum {
196 frequencies: frequencies(sample_rate, fft_size),
197 magnitudes: transform(samples, &window, sum, &mut real, &mut imag),
198 })
199}
200pub fn spectrogram(samples: &[f32], rate: u32, fft_size: usize, hop: usize) -> Result<Spectrogram> {
203 if hop == 0 {
204 return Err(Error::invalid("Spectrogram hop must be positive"));
205 }
206 check_fft(rate, fft_size)?;
207 let columns = samples.len().div_ceil(hop);
208 if columns
209 .checked_mul(fft_size / 2 + 1)
210 .is_none_or(|n| n > 64 * 1024 * 1024)
211 {
212 return Err(Error::limit("Spectrogram exceeds allocation limit"));
213 }
214 check_finite(samples)?;
217 let (window, sum) = hann(fft_size);
219 let mut real = vec![0.0; fft_size];
220 let mut imag = vec![0.0; fft_size];
221 let mut times = Vec::with_capacity(columns);
222 let mut magnitudes = Vec::with_capacity(columns);
223 for i in (0..samples.len()).step_by(hop) {
224 times.push(i as f64 / rate as f64);
225 magnitudes.push(transform(&samples[i..], &window, sum, &mut real, &mut imag));
226 }
227 Ok(Spectrogram {
228 frequencies: frequencies(rate, fft_size),
229 times,
230 magnitudes,
231 })
232}
233impl Audio {
234 pub fn peak(&self) -> f64 {
235 self.data
236 .iter()
237 .flatten()
238 .map(|s| s.abs() as f64)
239 .fold(0.0, f64::max)
240 }
241 pub fn peak_db(&self) -> f64 {
242 to_db(self.peak())
243 }
244 pub fn mean_square(&self) -> f64 {
252 let n = self.frames() * self.channels();
253 if n == 0 {
254 0.0
255 } else {
256 self.data
257 .iter()
258 .flatten()
259 .map(|&s| (s as f64).powi(2))
260 .sum::<f64>()
261 / n as f64
262 }
263 }
264 pub fn rms(&self) -> f64 {
268 self.mean_square().sqrt()
269 }
270 pub fn rms_db(&self) -> f64 {
272 to_db(self.rms())
273 }
274 pub fn measure_deterministic(&self) -> Result<DeterministicMeasurements> {
297 if self.rate < 4000 {
298 return Err(Error::invalid(
299 "Deterministic loudness requires at least 4000 Hz",
300 ));
301 }
302 crate::Limits::default().check(self.frames(), self.channels(), self.rate)?;
303 if self.data.iter().flatten().any(|s| !s.is_finite()) {
304 return Err(Error::invalid("Measurement samples must be finite"));
305 }
306 let loudness = self.render_loudness();
307 if !loudness.range.is_finite()
308 || [loudness.integrated, loudness.peak_momentary]
309 .iter()
310 .any(|v| !v.is_finite() && *v != f64::NEG_INFINITY)
311 {
312 return Err(Error::invalid("Deterministic measurement overflow"));
313 }
314 Ok(DeterministicMeasurements {
315 loudness,
316 peak_db: render_db(self.peak()),
317 true_peak_db: self.render_true_peak_db(),
318 })
319 }
320 pub fn true_peak_db(&self) -> f64 {
321 to_db(self.true_peak::<false>())
322 }
323 pub(crate) fn render_true_peak_db(&self) -> f64 {
324 render_db(self.render_true_peak())
325 }
326 pub(crate) fn render_true_peak(&self) -> f64 {
327 self.true_peak::<true>()
328 }
329 fn true_peak<const RENDER: bool>(&self) -> f64 {
330 let mut peak = self.peak();
331 let mut kernel = [0.0; 128];
332 for (i, k) in kernel.iter_mut().enumerate() {
333 let t = i as f64 / 4.0 - 16.0;
334 let sinc = if t == 0.0 {
335 1.0
336 } else {
337 (if RENDER {
338 crate::math::sin_turns(t / 2.0)
339 } else {
340 (PI * t).sin()
341 }) / (PI * t)
342 };
343 let w = i as f64 / 127.0;
344 let cos = |turns| {
345 if RENDER {
346 crate::math::cos_turns(turns)
347 } else {
348 (2.0 * PI * turns).cos()
349 }
350 };
351 *k = sinc * (0.42 - 0.5 * cos(w) + 0.08 * cos(2.0 * w));
352 }
353 for c in &self.data {
354 for i in 0..c.len() {
355 if i >= 16 && i + 16 <= c.len() {
358 let mut sums = [0.0f64; 4];
359 for (k, &x) in c[i - 16..i + 16].iter().enumerate() {
360 for (phase, sum) in sums.iter_mut().enumerate() {
361 *sum += x as f64 * kernel[k * 4 + phase];
362 }
363 }
364 for sum in sums {
365 peak = peak.max(sum.abs());
366 }
367 continue;
368 }
369 for phase in 0..4 {
370 let mut sum = 0.0;
371 for k in 0..32 {
372 let at = i as i64 + k as i64 - 16;
373 if at >= 0 && (at as usize) < c.len() {
374 sum += c[at as usize] as f64 * kernel[k * 4 + phase];
375 }
376 }
377 peak = peak.max(sum.abs());
378 }
379 }
380 }
381 peak
382 }
383 pub fn waveform(&self, buckets: usize) -> Result<Waveform> {
384 if buckets == 0 || buckets > 16 * 1024 * 1024 {
385 return Err(Error::invalid("Invalid waveform bucket count"));
386 }
387 let mut min = vec![0.0f32; buckets];
388 let mut max = vec![0.0f32; buckets];
389 for b in 0..buckets {
390 let start = (b as u128 * self.frames() as u128 / buckets as u128) as usize;
391 let end = (((b + 1) as u128 * self.frames() as u128 / buckets as u128) as usize)
392 .max(start.saturating_add(1))
393 .min(self.frames());
394 for c in &self.data {
395 for &s in &c[start..end] {
396 min[b] = min[b].min(s);
397 max[b] = max[b].max(s);
398 }
399 }
400 }
401 Ok(Waveform { min, max })
402 }
403 pub fn loudness(&self) -> LoudnessResult {
405 self.loudness_impl::<false>()
406 }
407 pub(crate) fn render_loudness(&self) -> LoudnessResult {
408 self.loudness_impl::<true>()
409 }
410 fn loudness_impl<const RENDER: bool>(&self) -> LoudnessResult {
411 let rate = self.rate as f64;
412 let tangent = |frequency: f64| {
413 if RENDER {
414 let turns = frequency / rate / 2.0;
415 crate::math::sin_turns(turns) / crate::math::cos_turns(turns)
416 } else {
417 (PI * frequency / rate).tan()
418 }
419 };
420 let k = tangent(1681.974450955533);
421 let vh = if RENDER {
422 crate::math::db_to_gain(3.999843853973347)
423 } else {
424 10f64.powf(3.999843853973347 / 20.0)
425 };
426 let vb = if RENDER {
427 crate::math::db_to_gain(3.999843853973347 * 0.4996667741545416)
428 } else {
429 vh.powf(0.4996667741545416)
430 };
431 let q = 0.7071752369554196;
432 let a0 = 1.0 + k / q + k * k;
433 let shelf = [
434 (vh + vb * k / q + k * k) / a0,
435 2.0 * (k * k - vh) / a0,
436 (vh - vb * k / q + k * k) / a0,
437 2.0 * (k * k - 1.0) / a0,
438 (1.0 - k / q + k * k) / a0,
439 ];
440 let k = tangent(38.13547087602444);
441 let q = 0.5003270373238773;
442 let a0 = 1.0 + k / q + k * k;
443 let hp = [
444 1.0,
445 -2.0,
446 1.0,
447 2.0 * (k * k - 1.0) / a0,
448 (1.0 - k / q + k * k) / a0,
449 ];
450 let filtered: Vec<_> = self
451 .data
452 .iter()
453 .map(|c| crate::dsp::biquad(&crate::dsp::biquad(c, shelf), hp))
454 .collect();
455 if RENDER && filtered.iter().flatten().any(|s| !s.is_finite()) {
456 return LoudnessResult {
457 integrated: f64::NAN,
458 range: f64::NAN,
459 peak_momentary: f64::NAN,
460 };
461 }
462 let weights: Vec<f64> = match self.channels() {
463 4 => vec![1.0, 1.0, 1.41, 1.41],
464 5 => vec![1.0, 1.0, 1.0, 1.41, 1.41],
465 6 => vec![1.0, 1.0, 1.0, 0.0, 1.41, 1.41],
466 _ => vec![1.0; self.channels()],
467 };
468 let block = (0.4 * rate).round().max(1.0) as usize;
469 let step = (0.1 * rate).round().max(1.0) as usize;
470 let power = |start: usize, end: usize| {
471 if start == end {
472 return 0.0;
473 }
474 filtered
475 .iter()
476 .zip(&weights)
477 .map(|(c, w)| {
478 w * c[start..end]
479 .iter()
480 .map(|&s| (s as f64).powi(2))
481 .sum::<f64>()
482 / (end - start) as f64
483 })
484 .sum::<f64>()
485 };
486 let lufs = |p: f64| {
487 if p > 0.0 {
488 -0.691
489 + if RENDER {
490 10.0 * crate::math::log2(p) / std::f64::consts::LOG2_10
491 } else {
492 10.0 * p.log10()
493 }
494 } else {
495 f64::NEG_INFINITY
496 }
497 };
498 if self.frames() < block {
499 let value = lufs(power(0, self.frames()));
500 return LoudnessResult {
501 integrated: value,
502 range: 0.0,
503 peak_momentary: value,
504 };
505 }
506 let blocks: Vec<_> = (0..=self.frames() - block)
507 .step_by(step)
508 .map(|i| power(i, i + block))
509 .collect();
510 gated_loudness(&blocks, lufs)
511 }
512}
513
514fn gated_loudness(blocks: &[f64], lufs: impl Fn(f64) -> f64) -> LoudnessResult {
515 let absolute: Vec<_> = blocks
516 .iter()
517 .copied()
518 .filter(|&p| lufs(p) >= -70.0)
519 .collect();
520 if absolute.is_empty() {
521 return LoudnessResult {
522 integrated: f64::NEG_INFINITY,
523 range: 0.0,
524 peak_momentary: f64::NEG_INFINITY,
525 };
526 }
527 let relative = lufs(absolute.iter().sum::<f64>() / absolute.len() as f64) - 10.0;
528 let gated: Vec<_> = absolute
529 .iter()
530 .copied()
531 .filter(|&p| lufs(p) >= relative)
532 .collect();
533 let integrated = lufs(gated.iter().sum::<f64>() / gated.len() as f64);
534 let mut sorted: Vec<_> = gated.iter().map(|&p| lufs(p)).collect();
535 sorted.sort_by(f64::total_cmp);
536 let range = sorted[(sorted.len() * 95 / 100).min(sorted.len() - 1)] - sorted[sorted.len() / 10];
537 LoudnessResult {
538 integrated,
539 range,
540 peak_momentary: blocks
541 .iter()
542 .map(|&p| lufs(p))
543 .fold(f64::NEG_INFINITY, f64::max),
544 }
545}
546
547#[cfg(test)]
548mod normalization_tests {
549 use super::*;
550
551 #[test]
552 fn gates_include_boundary_levels_and_separate_adjacent_powers() {
553 let lufs = |p: f64| -0.691 + render_db(p) / 2.0;
554 let mut bits = crate::math::db_to_gain((-70.0 + 0.691) * 2.0).to_bits();
557 while lufs(f64::from_bits(bits)) < -70.0 {
558 bits += 1;
559 }
560 while lufs(f64::from_bits(bits - 1)) >= -70.0 {
561 bits -= 1;
562 }
563 let below = f64::from_bits(bits - 1);
564 let above = f64::from_bits(bits);
565 assert_eq!(gated_loudness(&[below], lufs).integrated, f64::NEG_INFINITY);
566 assert_eq!(gated_loudness(&[above], lufs).integrated, lufs(above));
567 assert_eq!(gated_loudness(&[1.0], |_| -70.0).integrated, -70.0);
569 let below = (1.0 / 19.0) * (1.0 - 1e-10);
571 let above = (1.0 / 19.0) * (1.0 + 1e-10);
572 assert_eq!(gated_loudness(&[1.0, below], lufs).integrated, lufs(1.0));
573 assert_eq!(
574 gated_loudness(&[1.0, above], lufs).integrated,
575 lufs((1.0 + above) / 2.0)
576 );
577 let equality = |p: f64| {
578 if p == 1.0 {
579 -20.0
580 } else if p == 0.1 {
581 -30.0
582 } else {
583 -20.0
584 }
585 };
586 assert_eq!(gated_loudness(&[1.0, 0.1], equality).range, 10.0);
587 }
588}