maolan_engine/
loudness.rs1use ebur128::{EbuR128, Mode as LoudnessMode};
2
3#[derive(Clone, Copy, Debug, Default, PartialEq)]
5pub struct LoudnessValues {
6 pub momentary: f32,
8 pub short_term: f32,
10 pub integrated: f32,
12}
13
14impl LoudnessValues {
15 pub const SILENCE: f32 = -90.0;
17
18 pub fn silence() -> Self {
20 Self {
21 momentary: Self::SILENCE,
22 short_term: Self::SILENCE,
23 integrated: Self::SILENCE,
24 }
25 }
26
27 pub fn sanitized(self) -> Self {
29 Self {
30 momentary: sanitize_lufs(self.momentary),
31 short_term: sanitize_lufs(self.short_term),
32 integrated: sanitize_lufs(self.integrated),
33 }
34 }
35}
36
37fn sanitize_lufs(value: f32) -> f32 {
38 if value.is_finite() {
39 value
40 } else {
41 LoudnessValues::SILENCE
42 }
43}
44
45pub struct LoudnessMeter {
50 meter: EbuR128,
51 channels: usize,
52 sample_rate: u32,
53}
54
55impl LoudnessMeter {
56 pub fn new(channels: usize, sample_rate: u32) -> Result<Self, ebur128::Error> {
58 let meter = EbuR128::new(
59 channels as u32,
60 sample_rate,
61 LoudnessMode::M | LoudnessMode::S | LoudnessMode::I,
62 )?;
63 Ok(Self {
64 meter,
65 channels,
66 sample_rate,
67 })
68 }
69
70 pub fn channels(&self) -> usize {
71 self.channels
72 }
73
74 pub fn sample_rate(&self) -> u32 {
75 self.sample_rate
76 }
77
78 pub fn feed_interleaved(&mut self, samples: &[f32]) {
83 if samples.is_empty() {
84 return;
85 }
86 if let Err(e) = self.meter.add_frames_f32(samples) {
87 tracing::warn!("EBU R128 loudness analysis failed: {}", e);
88 }
89 }
90
91 pub fn values(&self) -> LoudnessValues {
93 let momentary = self.meter.loudness_momentary().unwrap_or(f64::NEG_INFINITY) as f32;
94 let short_term = self.meter.loudness_shortterm().unwrap_or(f64::NEG_INFINITY) as f32;
95 let integrated = self.meter.loudness_global().unwrap_or(f64::NEG_INFINITY) as f32;
96
97 LoudnessValues {
98 momentary,
99 short_term,
100 integrated,
101 }
102 .sanitized()
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn silence_returns_silence_values() {
112 let mut meter = LoudnessMeter::new(2, 48_000).unwrap();
113 meter.feed_interleaved(&[0.0_f32; 48_000 * 2]);
114 assert_eq!(meter.values(), LoudnessValues::silence());
115 }
116
117 #[test]
118 fn sine_wave_produces_finite_lufs() {
119 let sample_rate = 48_000;
120 let duration = 3 * sample_rate;
121 let frequency = 1000.0;
122 let samples: Vec<f32> = (0..duration)
123 .flat_map(|i| {
124 let phase = 2.0 * std::f32::consts::PI * frequency * i as f32 / sample_rate as f32;
125 let sample = phase.sin() * 0.1;
126 [sample, sample]
127 })
128 .collect();
129
130 let mut meter = LoudnessMeter::new(2, sample_rate).unwrap();
131 meter.feed_interleaved(&samples);
132 let values = meter.values();
133
134 assert!(values.momentary.is_finite());
135 assert!(values.short_term.is_finite());
136 assert!(values.integrated.is_finite());
137 assert!(values.integrated > -90.0);
138 }
139
140 #[test]
141 fn integrated_changes_after_multiple_blocks() {
142 let sample_rate = 48_000;
143 let block: Vec<f32> = (0..sample_rate)
144 .flat_map(|i| {
145 let phase = 2.0 * std::f32::consts::PI * 1000.0 * i as f32 / sample_rate as f32;
146 let sample = phase.sin() * 0.05;
147 [sample, sample]
148 })
149 .collect();
150
151 let mut meter = LoudnessMeter::new(2, sample_rate).unwrap();
152 meter.feed_interleaved(&block);
153 let first = meter.values();
154 meter.feed_interleaved(&block);
155 let second = meter.values();
156
157 assert_ne!(first.integrated, second.integrated);
158 }
159}