Skip to main content

max / audiofiles

1.4 KB · 46 lines History Blame Raw
1 //! Integrated loudness measurement in LUFS per ITU-R BS.1770-4.
2
3 /// Measure integrated LUFS loudness per ITU-R BS.1770-4.
4 /// Returns -70.0 for silence/near-silence where gating produces no result.
5 pub fn measure_lufs(samples: &[f32], sample_rate: u32) -> f64 {
6 if samples.is_empty() || sample_rate == 0 {
7 return -70.0;
8 }
9 let mut meter = bs1770::ChannelLoudnessMeter::new(sample_rate);
10 meter.push(samples.iter().copied());
11 let windows = meter.into_100ms_windows();
12 let power = bs1770::gated_mean(windows.as_ref());
13 let lkfs = power.loudness_lkfs() as f64;
14 if lkfs.is_nan() || lkfs.is_infinite() {
15 -70.0
16 } else {
17 lkfs
18 }
19 }
20
21 #[cfg(test)]
22 mod tests {
23 use super::*;
24
25 #[test]
26 fn silence_is_very_quiet() {
27 let silence = vec![0.0f32; 48000];
28 let lufs = measure_lufs(&silence, 48000);
29 // Silence should be extremely quiet (negative infinity or very low)
30 assert!(lufs < -60.0, "silence LUFS should be < -60, got {lufs}");
31 }
32
33 #[test]
34 fn loud_sine_is_near_zero() {
35 let samples: Vec<f32> = (0..48000)
36 .map(|i| (2.0 * std::f32::consts::PI * 1000.0 * i as f32 / 48000.0).sin())
37 .collect();
38 let lufs = measure_lufs(&samples, 48000);
39 // A 1kHz full-scale sine should be around -3 LUFS
40 assert!(
41 lufs > -10.0 && lufs < 0.0,
42 "full-scale sine LUFS should be near -3, got {lufs}"
43 );
44 }
45 }
46