use ebur128::{EbuR128, Mode as LoudnessMode};
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct LoudnessValues {
pub momentary: f32,
pub short_term: f32,
pub integrated: f32,
}
impl LoudnessValues {
pub const SILENCE: f32 = -90.0;
pub fn silence() -> Self {
Self {
momentary: Self::SILENCE,
short_term: Self::SILENCE,
integrated: Self::SILENCE,
}
}
pub fn sanitized(self) -> Self {
Self {
momentary: sanitize_lufs(self.momentary),
short_term: sanitize_lufs(self.short_term),
integrated: sanitize_lufs(self.integrated),
}
}
}
fn sanitize_lufs(value: f32) -> f32 {
if value.is_finite() {
value
} else {
LoudnessValues::SILENCE
}
}
pub struct LoudnessMeter {
meter: EbuR128,
channels: usize,
sample_rate: u32,
}
impl LoudnessMeter {
pub fn new(channels: usize, sample_rate: u32) -> Result<Self, ebur128::Error> {
let meter = EbuR128::new(
channels as u32,
sample_rate,
LoudnessMode::M | LoudnessMode::S | LoudnessMode::I,
)?;
Ok(Self {
meter,
channels,
sample_rate,
})
}
pub fn channels(&self) -> usize {
self.channels
}
pub fn sample_rate(&self) -> u32 {
self.sample_rate
}
pub fn feed_interleaved(&mut self, samples: &[f32]) {
if samples.is_empty() {
return;
}
if let Err(e) = self.meter.add_frames_f32(samples) {
tracing::warn!("EBU R128 loudness analysis failed: {}", e);
}
}
pub fn values(&self) -> LoudnessValues {
let momentary = self.meter.loudness_momentary().unwrap_or(f64::NEG_INFINITY) as f32;
let short_term = self.meter.loudness_shortterm().unwrap_or(f64::NEG_INFINITY) as f32;
let integrated = self.meter.loudness_global().unwrap_or(f64::NEG_INFINITY) as f32;
LoudnessValues {
momentary,
short_term,
integrated,
}
.sanitized()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn silence_returns_silence_values() {
let mut meter = LoudnessMeter::new(2, 48_000).unwrap();
meter.feed_interleaved(&[0.0_f32; 48_000 * 2]);
assert_eq!(meter.values(), LoudnessValues::silence());
}
#[test]
fn sine_wave_produces_finite_lufs() {
let sample_rate = 48_000;
let duration = 3 * sample_rate;
let frequency = 1000.0;
let samples: Vec<f32> = (0..duration)
.flat_map(|i| {
let phase = 2.0 * std::f32::consts::PI * frequency * i as f32 / sample_rate as f32;
let sample = phase.sin() * 0.1;
[sample, sample]
})
.collect();
let mut meter = LoudnessMeter::new(2, sample_rate).unwrap();
meter.feed_interleaved(&samples);
let values = meter.values();
assert!(values.momentary.is_finite());
assert!(values.short_term.is_finite());
assert!(values.integrated.is_finite());
assert!(values.integrated > -90.0);
}
#[test]
fn integrated_changes_after_multiple_blocks() {
let sample_rate = 48_000;
let block: Vec<f32> = (0..sample_rate)
.flat_map(|i| {
let phase = 2.0 * std::f32::consts::PI * 1000.0 * i as f32 / sample_rate as f32;
let sample = phase.sin() * 0.05;
[sample, sample]
})
.collect();
let mut meter = LoudnessMeter::new(2, sample_rate).unwrap();
meter.feed_interleaved(&block);
let first = meter.values();
meter.feed_interleaved(&block);
let second = meter.values();
assert_ne!(first.integrated, second.integrated);
}
}