use std::collections::VecDeque;
const ABS_GATE_LUFS: f32 = -70.0;
const REL_GATE_LU: f32 = -10.0;
const OFFSET: f32 = -0.691;
#[derive(Clone, Copy)]
struct Biquad {
b0: f64,
b1: f64,
b2: f64,
a1: f64,
a2: f64,
z1: f64,
z2: f64,
}
impl Biquad {
#[inline]
fn process(&mut self, x: f64) -> f64 {
let y = self.b0 * x + self.z1;
self.z1 = self.b1 * x - self.a1 * y + self.z2;
self.z2 = self.b2 * x - self.a2 * y;
y
}
fn pre_filter(fs: f64) -> Self {
let f0 = 1681.974450956;
let g = 3.999843853973;
let q = 0.707175236955;
let k = (std::f64::consts::PI * f0 / fs).tan();
let vh = 10f64.powf(g / 20.0);
let vb = vh.powf(0.499666774155);
let a0 = 1.0 + k / q + k * k;
Self {
b0: (vh + vb * k / q + k * k) / a0,
b1: 2.0 * (k * k - vh) / a0,
b2: (vh - vb * k / q + k * k) / a0,
a1: 2.0 * (k * k - 1.0) / a0,
a2: (1.0 - k / q + k * k) / a0,
z1: 0.0,
z2: 0.0,
}
}
fn rlb_high_pass(fs: f64) -> Self {
let f0 = 38.135470876024;
let q = 0.500327037324;
let k = (std::f64::consts::PI * f0 / fs).tan();
let a0 = 1.0 + k / q + k * k;
Self {
b0: 1.0,
b1: -2.0,
b2: 1.0,
a1: 2.0 * (k * k - 1.0) / a0,
a2: (1.0 - k / q + k * k) / a0,
z1: 0.0,
z2: 0.0,
}
}
}
fn k_weighting(fs: f64) -> (Biquad, Biquad) {
(Biquad::pre_filter(fs), Biquad::rlb_high_pass(fs))
}
fn k_weight_channel(samples: &[f32], fs: f64) -> Vec<f64> {
let (mut shelf, mut hp) = k_weighting(fs);
samples
.iter()
.map(|&x| hp.process(shelf.process(x as f64)))
.collect()
}
fn block_mean_squares(interleaved_stereo: &[f32], fs: f32) -> Vec<f64> {
let n_frames = interleaved_stereo.len() / 2;
if n_frames == 0 {
return Vec::new();
}
let fs = fs as f64;
let block = (0.400 * fs).round() as usize; let hop = (0.100 * fs).round() as usize; if block == 0 || hop == 0 || n_frames < block {
return Vec::new();
}
let mut left = Vec::with_capacity(n_frames);
let mut right = Vec::with_capacity(n_frames);
for f in 0..n_frames {
left.push(interleaved_stereo[2 * f]);
right.push(interleaved_stereo[2 * f + 1]);
}
let l = k_weight_channel(&left, fs);
let r = k_weight_channel(&right, fs);
let mut z: Vec<f64> = Vec::new();
let mut start = 0;
while start + block <= n_frames {
let end = start + block;
let mut sl = 0.0f64;
let mut sr = 0.0f64;
for i in start..end {
sl += l[i] * l[i];
sr += r[i] * r[i];
}
z.push((sl + sr) / block as f64); start += hop;
}
z
}
#[inline]
fn block_loudness(zj: f64) -> f64 {
if zj <= 0.0 {
f64::NEG_INFINITY
} else {
OFFSET as f64 + 10.0 * zj.log10()
}
}
fn integrated_from_blocks(z: &[f64]) -> f32 {
if z.is_empty() {
return f32::NEG_INFINITY;
}
let offset = OFFSET as f64;
let loud = block_loudness;
let abs_kept: Vec<f64> = z
.iter()
.copied()
.filter(|&zj| loud(zj) >= ABS_GATE_LUFS as f64)
.collect();
if abs_kept.is_empty() {
return f32::NEG_INFINITY;
}
let mean_abs: f64 = abs_kept.iter().sum::<f64>() / abs_kept.len() as f64;
let rel_thresh = offset + 10.0 * mean_abs.log10() + REL_GATE_LU as f64;
let rel_kept: Vec<f64> = abs_kept
.into_iter()
.filter(|&zj| loud(zj) >= rel_thresh)
.collect();
if rel_kept.is_empty() {
return f32::NEG_INFINITY;
}
let mean_rel: f64 = rel_kept.iter().sum::<f64>() / rel_kept.len() as f64;
(offset + 10.0 * mean_rel.log10()) as f32
}
pub(crate) struct StreamLoudness {
shelf_l: Biquad,
hp_l: Biquad,
shelf_r: Biquad,
hp_r: Biquad,
left: VecDeque<f64>,
right: VecDeque<f64>,
block: usize,
hop: usize,
frames: usize,
blocks: Vec<f64>,
}
impl StreamLoudness {
pub(crate) fn new(fs: u32) -> Self {
let fs = fs as f32 as f64;
let (shelf_l, hp_l) = k_weighting(fs);
let (shelf_r, hp_r) = k_weighting(fs);
let block = (0.400 * fs).round() as usize;
let hop = (0.100 * fs).round() as usize;
Self {
shelf_l,
hp_l,
shelf_r,
hp_r,
left: VecDeque::with_capacity(block),
right: VecDeque::with_capacity(block),
block,
hop,
frames: 0,
blocks: Vec::new(),
}
}
pub(crate) fn push(&mut self, interleaved_stereo: &[f32]) {
assert!(
interleaved_stereo.len().is_multiple_of(2),
"stereo block must contain complete frames"
);
for frame in interleaved_stereo.chunks_exact(2) {
let l = self.hp_l.process(self.shelf_l.process(frame[0] as f64));
let r = self.hp_r.process(self.shelf_r.process(frame[1] as f64));
self.left.push_back(l);
self.right.push_back(r);
if self.left.len() > self.block {
self.left.pop_front();
self.right.pop_front();
}
self.frames += 1;
if self.block == 0
|| self.hop == 0
|| self.frames < self.block
|| !(self.frames - self.block).is_multiple_of(self.hop)
{
continue;
}
let mut sl = 0.0f64;
let mut sr = 0.0f64;
for (&l, &r) in self.left.iter().zip(self.right.iter()) {
sl += l * l;
sr += r * r;
}
self.blocks.push((sl + sr) / self.block as f64);
}
}
pub(crate) fn finish(self) -> f32 {
integrated_from_blocks(&self.blocks)
}
#[cfg(test)]
fn block_count(&self) -> usize {
self.blocks.len()
}
}
pub fn momentary_lufs(interleaved_stereo: &[f32], fs: u32) -> Vec<f32> {
let fs = fs as f32;
block_mean_squares(interleaved_stereo, fs)
.into_iter()
.map(|zj| block_loudness(zj) as f32)
.collect()
}
pub fn integrated_lufs(interleaved_stereo: &[f32], fs: u32) -> f32 {
let fs = fs as f32;
let z = block_mean_squares(interleaved_stereo, fs);
integrated_from_blocks(&z)
}
const TP_OVERSAMPLE: usize = 4;
const TP_TAPS_PER_PHASE: usize = 12;
fn tp_polyphase() -> [[f32; TP_TAPS_PER_PHASE]; TP_OVERSAMPLE] {
let n = TP_OVERSAMPLE * TP_TAPS_PER_PHASE; let center = (n as f64 - 1.0) / 2.0;
let mut proto = [0.0f64; TP_OVERSAMPLE * TP_TAPS_PER_PHASE];
for (i, p) in proto.iter_mut().enumerate() {
let x = i as f64 - center;
let s = if x.abs() < 1e-9 {
1.0
} else {
let a = std::f64::consts::PI * x / TP_OVERSAMPLE as f64;
a.sin() / a
};
let w = 0.42 - 0.5 * (2.0 * std::f64::consts::PI * i as f64 / (n as f64 - 1.0)).cos()
+ 0.08 * (4.0 * std::f64::consts::PI * i as f64 / (n as f64 - 1.0)).cos();
*p = s * w;
}
let mut phases = [[0.0f32; TP_TAPS_PER_PHASE]; TP_OVERSAMPLE];
for (ph, phase) in phases.iter_mut().enumerate() {
let mut sum = 0.0f64;
for k in 0..TP_TAPS_PER_PHASE {
sum += proto[ph + TP_OVERSAMPLE * k];
}
let g = if sum.abs() > 1e-12 { 1.0 / sum } else { 1.0 };
for k in 0..TP_TAPS_PER_PHASE {
phase[k] = (proto[ph + TP_OVERSAMPLE * k] * g) as f32;
}
}
phases
}
fn channel_true_peak_env(
c: &[f32],
phases: &[[f32; TP_TAPS_PER_PHASE]; TP_OVERSAMPLE],
) -> Vec<f32> {
let mut env = vec![0.0f32; c.len()];
for (i, e) in env.iter_mut().enumerate() {
let mut peak = c[i].abs();
for phase in phases.iter() {
let mut acc = 0.0f32;
for (k, &coef) in phase.iter().enumerate() {
if i >= k {
acc += c[i - k] * coef;
}
}
peak = peak.max(acc.abs());
}
*e = peak;
}
env
}
#[derive(Default)]
struct StreamTruePeakChannel {
history: [f32; TP_TAPS_PER_PHASE],
next: usize,
len: usize,
#[cfg(test)]
peak: f32,
}
impl StreamTruePeakChannel {
fn push(&mut self, sample: f32, phases: &[[f32; TP_TAPS_PER_PHASE]; TP_OVERSAMPLE]) -> f32 {
self.history[self.next] = sample;
self.len = (self.len + 1).min(TP_TAPS_PER_PHASE);
let mut peak = sample.abs();
for phase in phases {
let mut acc = 0.0f32;
for (k, &coef) in phase.iter().enumerate().take(self.len) {
let i = (self.next + TP_TAPS_PER_PHASE - k) % TP_TAPS_PER_PHASE;
acc += self.history[i] * coef;
}
peak = peak.max(acc.abs());
}
#[cfg(test)]
{
self.peak = self.peak.max(peak);
}
self.next = (self.next + 1) % TP_TAPS_PER_PHASE;
peak
}
}
pub(crate) struct StreamTruePeak {
phases: [[f32; TP_TAPS_PER_PHASE]; TP_OVERSAMPLE],
left: StreamTruePeakChannel,
right: StreamTruePeakChannel,
}
impl StreamTruePeak {
pub(crate) fn new() -> Self {
Self {
phases: tp_polyphase(),
left: StreamTruePeakChannel::default(),
right: StreamTruePeakChannel::default(),
}
}
pub(crate) fn push_frame(&mut self, left: f32, right: f32) -> f32 {
self.left
.push(left, &self.phases)
.max(self.right.push(right, &self.phases))
}
#[cfg(test)]
pub(crate) fn push(&mut self, interleaved_stereo: &[f32]) {
assert!(
interleaved_stereo.len().is_multiple_of(2),
"stereo block must contain complete frames"
);
for frame in interleaved_stereo.chunks_exact(2) {
self.push_frame(frame[0], frame[1]);
}
}
#[cfg(test)]
pub(crate) fn finish(self) -> f32 {
let peak = self.left.peak.max(self.right.peak);
if peak <= 0.0 {
f32::NEG_INFINITY
} else {
20.0 * peak.log10()
}
}
}
fn channel_true_peak(c: &[f32], phases: &[[f32; TP_TAPS_PER_PHASE]; TP_OVERSAMPLE]) -> f32 {
channel_true_peak_env(c, phases)
.into_iter()
.fold(0.0f32, f32::max)
}
pub fn true_peak_dbtp(interleaved_stereo: &[f32], _fs: u32) -> f32 {
let phases = tp_polyphase();
let n_frames = interleaved_stereo.len() / 2;
let mut left = Vec::with_capacity(n_frames);
let mut right = Vec::with_capacity(n_frames);
for f in 0..n_frames {
left.push(interleaved_stereo[2 * f]);
right.push(interleaved_stereo[2 * f + 1]);
}
let peak = channel_true_peak(&left, &phases).max(channel_true_peak(&right, &phases));
if peak <= 0.0 {
f32::NEG_INFINITY
} else {
20.0 * peak.log10()
}
}
const TP_ATTACK_MS: f32 = 5.0;
const TP_RELEASE_MS: f32 = 100.0;
const TP_SAFETY_DB: f32 = 1.0;
pub(crate) const TP_MAX_PASSES: usize = 12;
pub(crate) fn limiter_config(fs: u32, ceiling_dbtp: f32) -> (f32, f32, f32) {
let fs = fs as f32;
let ceiling = 10f32.powf((ceiling_dbtp - TP_SAFETY_DB) / 20.0);
let attack = 10f32.powf((20.0 / (TP_ATTACK_MS / 1000.0 * fs)) / 20.0);
let release = 10f32.powf((20.0 / (TP_RELEASE_MS / 1000.0 * fs)) / 20.0);
(ceiling, attack, release)
}
fn limit_pass(
left: &mut [f32],
right: &mut [f32],
fs: f32,
ceil_lin: f32,
phases: &[[f32; TP_TAPS_PER_PHASE]; TP_OVERSAMPLE],
) -> bool {
let n = left.len();
if n == 0 {
return false;
}
let env_l = channel_true_peak_env(left, phases);
let env_r = channel_true_peak_env(right, phases);
let mut g: Vec<f32> = (0..n)
.map(|i| {
let env = env_l[i].max(env_r[i]);
if env > ceil_lin {
ceil_lin / env
} else {
1.0
}
})
.collect();
if g.iter().all(|&x| x >= 1.0) {
return false; }
let atk_step = 10f32.powf((20.0 / (TP_ATTACK_MS / 1000.0 * fs)) / 20.0);
for i in (0..n - 1).rev() {
let ramp = g[i + 1] * atk_step;
if ramp < g[i] {
g[i] = ramp;
}
}
let rel_step = 10f32.powf((20.0 / (TP_RELEASE_MS / 1000.0 * fs)) / 20.0);
for i in 1..n {
let ramp = g[i - 1] * rel_step;
if ramp < g[i] {
g[i] = ramp;
}
}
for i in 0..n {
left[i] *= g[i];
right[i] *= g[i];
}
true
}
pub fn limit_true_peak(interleaved_stereo: &mut [f32], fs: u32, ceiling_dbtp: f32) {
let (ceil_lin, _, _) = limiter_config(fs, ceiling_dbtp);
let fs = fs as f32;
let n_frames = interleaved_stereo.len() / 2;
if n_frames == 0 {
return;
}
let phases = tp_polyphase();
let mut left: Vec<f32> = (0..n_frames).map(|f| interleaved_stereo[2 * f]).collect();
let mut right: Vec<f32> = (0..n_frames)
.map(|f| interleaved_stereo[2 * f + 1])
.collect();
for _ in 0..TP_MAX_PASSES {
if !limit_pass(&mut left, &mut right, fs, ceil_lin, &phases) {
break;
}
}
for f in 0..n_frames {
interleaved_stereo[2 * f] = left[f];
interleaved_stereo[2 * f + 1] = right[f];
}
}
#[cfg(test)]
mod tests {
use super::*;
fn stereo_sine(freq: f32, peak: f32, secs: f32, fs: f32) -> Vec<f32> {
stereo_sine_phase(freq, peak, secs, fs, 0.0)
}
fn stereo_sine_phase(freq: f32, peak: f32, secs: f32, fs: f32, phase: f32) -> Vec<f32> {
let n = (secs * fs) as usize;
let mut v = Vec::with_capacity(n * 2);
for i in 0..n {
let s = peak * (2.0 * std::f32::consts::PI * freq * i as f32 / fs + phase).sin();
v.push(s);
v.push(s);
}
v
}
fn push_irregular_chunks(
loudness: &mut StreamLoudness,
true_peak: &mut StreamTruePeak,
signal: &[f32],
) {
let chunk_frames = [1usize, 7, 64, 3, 257, 2, 19, 1024];
let mut frame = 0;
let total = signal.len() / 2;
let mut chunk = 0;
while frame < total {
let end = (frame + chunk_frames[chunk % chunk_frames.len()]).min(total);
let samples = &signal[frame * 2..end * 2];
loudness.push(samples);
true_peak.push(samples);
frame = end;
chunk += 1;
}
}
#[test]
fn streaming_meters_are_bit_exact_across_irregular_chunks() {
let fs = 44100u32;
let mut signal = stereo_sine(997.0, 0.15, 1.3, fs as f32);
signal.extend(std::iter::repeat_n(0.0, fs as usize / 3 * 2));
signal.extend(stereo_sine_phase(
fs as f32 / 4.0,
0.81,
0.73,
fs as f32,
std::f32::consts::FRAC_PI_4,
));
let expected_loudness = integrated_lufs(&signal, fs);
let expected_true_peak = true_peak_dbtp(&signal, fs);
let mut loudness = StreamLoudness::new(fs);
let mut true_peak = StreamTruePeak::new();
push_irregular_chunks(&mut loudness, &mut true_peak, &signal);
assert_eq!(
loudness.finish().to_bits(),
expected_loudness.to_bits(),
"streaming BS.1770 changed a floating-point operation"
);
assert_eq!(
true_peak.finish().to_bits(),
expected_true_peak.to_bits(),
"streaming true peak changed a floating-point operation"
);
}
#[test]
fn streaming_loudness_emits_only_complete_windows() {
let fs = 10u32; let mut meter = StreamLoudness::new(fs);
meter.push(&[0.1, -0.1, 0.2, -0.2, 0.3, -0.3]);
assert_eq!(meter.block_count(), 0);
meter.push(&[0.4, -0.4]);
assert_eq!(meter.block_count(), 1);
meter.push(&[0.5, -0.5, 0.6, -0.6]);
assert_eq!(meter.block_count(), 3);
}
#[test]
fn ebu_3341_minus23_calibration_48k() {
let peak = 10f32.powf(-23.0 / 20.0); let sig = stereo_sine(1000.0, peak, 5.0, 48000.0);
let lufs = integrated_lufs(&sig, 48000);
assert!(
(lufs - (-23.0)).abs() < 0.1,
"48k 1kHz −23dBFS should read −23.0 LUFS, got {lufs}"
);
}
#[test]
fn ebu_3341_minus23_calibration_44k1() {
let peak = 10f32.powf(-23.0 / 20.0);
let sig = stereo_sine(1000.0, peak, 5.0, 44100.0);
let lufs = integrated_lufs(&sig, 44100);
assert!(
(lufs - (-23.0)).abs() < 0.1,
"44.1k 1kHz −23dBFS should read −23.0 LUFS, got {lufs}"
);
}
#[test]
fn momentary_matches_ebu_calibration_on_steady_tone() {
let peak = 10f32.powf(-23.0 / 20.0);
for &fs in &[44100.0f32, 48000.0] {
let sig = stereo_sine(1000.0, peak, 5.0, fs);
let m = momentary_lufs(&sig, fs as u32);
assert_eq!(m.len(), 47, "fs={fs}: unexpected block count");
for (i, &b) in m.iter().enumerate() {
assert!(
(b - (-23.0)).abs() < 0.1,
"fs={fs} block {i}: steady −23 dBFS tone should read −23.0 LUFS, got {b}"
);
}
}
}
#[test]
fn sub_five_hz_rates_return_without_looping() {
let one_frame = [0.0, 0.0];
for fs in 0..=4 {
assert!(
momentary_lufs(&one_frame, fs).is_empty(),
"fs={fs} should have no representable momentary block"
);
assert_eq!(
integrated_lufs(&one_frame, fs),
f32::NEG_INFINITY,
"fs={fs} should have no integrated loudness"
);
}
}
#[test]
fn momentary_tracks_a_step_that_integrated_averages() {
let fs = 44100.0f32;
let mut sig = stereo_sine(1000.0, 10f32.powf(-43.0 / 20.0), 3.0, fs);
sig.extend(stereo_sine(1000.0, 10f32.powf(-23.0 / 20.0), 3.0, fs));
let m = momentary_lufs(&sig, fs as u32);
let quiet = m[10];
let loud = m[m.len() - 10];
assert!(
(quiet - (-43.0)).abs() < 0.1,
"first plateau should read −43.0, got {quiet}"
);
assert!(
(loud - (-23.0)).abs() < 0.1,
"second plateau should read −23.0, got {loud}"
);
let integrated = integrated_lufs(&sig, fs as u32);
assert!(
(integrated - (-23.0)).abs() < 0.5,
"integrated should report ~−23.0 (quiet half relative-gated out), got {integrated}"
);
}
#[test]
fn scaling_is_minus_6db() {
let a = stereo_sine(1000.0, 0.5, 5.0, 44100.0);
let b = stereo_sine(1000.0, 0.25, 5.0, 44100.0);
let la = integrated_lufs(&a, 44100);
let lb = integrated_lufs(&b, 44100);
assert!(
((la - lb) - 6.0206).abs() < 0.02,
"halving amplitude should be −6.02 LU, got {}",
la - lb
);
}
#[test]
fn silence_is_gated_out() {
let fs = 44100.0;
let mut sig = stereo_sine(1000.0, 0.5, 3.0, fs); sig.extend(std::iter::repeat_n(0.0f32, (10.0 * fs) as usize * 2)); let gated = integrated_lufs(&sig, fs as u32);
let loud_only = integrated_lufs(&stereo_sine(1000.0, 0.5, 5.0, fs), fs as u32);
assert!(
(gated - loud_only).abs() < 0.3,
"silence should be gated out (gated {gated} vs loud-only {loud_only})"
);
}
#[test]
fn silence_is_neg_inf() {
let sig = vec![0.0f32; 44100 * 2];
assert_eq!(integrated_lufs(&sig, 44100), f32::NEG_INFINITY);
}
fn sample_peak_dbfs(interleaved: &[f32]) -> f32 {
let p = interleaved.iter().fold(0.0f32, |m, &x| m.max(x.abs()));
20.0 * p.log10()
}
#[test]
fn true_peak_recovers_intersample() {
let fs = 44100.0;
let sig = stereo_sine_phase(fs / 4.0, 1.0, 1.0, fs, std::f32::consts::FRAC_PI_4);
let tp = true_peak_dbtp(&sig, fs as u32);
let sp = sample_peak_dbfs(&sig); assert!(
tp > sp + 2.0,
"oversampler should recover most of the inter-sample crest (tp {tp} vs sp {sp})"
);
assert!(
(tp - 0.0).abs() < 0.6,
"true peak of a full-scale sine should be ~0 dBTP, got {tp}"
);
}
#[test]
fn true_peak_low_tone_matches_sample() {
let fs = 44100.0;
let sig = stereo_sine(100.0, 0.5, 1.0, fs);
let tp = true_peak_dbtp(&sig, fs as u32);
assert!(
(tp - (-6.0206)).abs() < 0.2,
"0.5-amp low tone should read ~-6.02 dBTP, got {tp}"
);
}
#[test]
fn true_peak_silence_neg_inf() {
let sig = vec![0.0f32; 4410 * 2];
assert_eq!(true_peak_dbtp(&sig, 44100), f32::NEG_INFINITY);
}
#[test]
fn limiter_meets_ceiling() {
let fs = 44100.0;
let mut sig = stereo_sine(1000.0, 2.0, 1.0, fs); assert!(true_peak_dbtp(&sig, fs as u32) > 5.0);
limit_true_peak(&mut sig, fs as u32, -1.0);
let tp = true_peak_dbtp(&sig, fs as u32);
assert!(
tp <= -0.9,
"limited true peak must sit at/under the -1 dBTP ceiling, got {tp}"
);
}
#[test]
fn limiter_unity_under_ceiling() {
let fs = 44100.0;
let orig = stereo_sine(440.0, 0.3, 1.0, fs); let mut sig = orig.clone();
limit_true_peak(&mut sig, fs as u32, -1.0);
let max_diff = orig
.iter()
.zip(&sig)
.fold(0.0f32, |m, (a, b)| m.max((a - b).abs()));
assert!(
max_diff < 1e-6,
"under-ceiling content must be unchanged, max sample diff {max_diff}"
);
}
#[test]
fn limiter_releases_to_unity() {
let fs = 44100.0;
let mut burst = stereo_sine(1000.0, 2.5, 0.05, fs); let tail = stereo_sine(300.0, 0.2, 0.6, fs); burst.extend_from_slice(&tail);
let orig = burst.clone();
limit_true_peak(&mut burst, fs as u32, -1.0);
let start = orig.len() - (0.2 * fs) as usize * 2;
let max_diff = orig[start..]
.iter()
.zip(&burst[start..])
.fold(0.0f32, |m, (a, b)| m.max((a - b).abs()));
assert!(
max_diff < 1e-5,
"gain must release to unity for the quiet tail, max diff {max_diff}"
);
}
}