Skip to main content

pito_codecs/filter/
limiter.rs

1use super::loudness::{Loudness, LoudnessMeter, TAPS, interpolated, oversampling, polyphase};
2use super::mix::gain;
3use crate::media::{AudioBuffer, Error, Result};
4use std::collections::VecDeque;
5
6const LOOKAHEAD: f64 = 0.005;
7const RELEASE: f64 = 0.08;
8const CENTRE: usize = TAPS / 2;
9
10#[derive(Debug, Clone)]
11pub struct Limiter {
12    channels: usize,
13    ceiling: f32,
14    look: usize,
15    release: f32,
16    bank: Vec<[f32; TAPS]>,
17    buffers: Vec<Vec<f32>>,
18    peaks: Vec<f32>,
19    scratch: Vec<f32>,
20    mins: VecDeque<(u64, f32)>,
21    boxed: Vec<f32>,
22    boxed_sum: f64,
23    boxed_at: usize,
24    released: f32,
25    delay: Vec<f32>,
26    delay_at: usize,
27    step: u64,
28}
29
30impl Limiter {
31    pub fn new(rate: u32, channels: u16, ceiling_db: f64) -> Result<Self> {
32        if rate == 0 || channels == 0 {
33            return Err(Error::Invalid(format!(
34                "limiter at {rate} Hz with {channels} channels"
35            )));
36        }
37        let look = ((rate as f64 * LOOKAHEAD).round() as usize).max(1);
38        let latency = look - 1 + CENTRE;
39        let channels = channels as usize;
40        Ok(Limiter {
41            channels,
42            ceiling: 10f64.powf(ceiling_db / 20.0) as f32,
43            look,
44            release: (1.0 - (-1.0 / (RELEASE * rate as f64)).exp()) as f32,
45            bank: polyphase(oversampling(rate as f64).max(2)),
46            buffers: vec![vec![0.0; TAPS - 1]; channels],
47            peaks: Vec::new(),
48            scratch: vec![0.0; 1024],
49            mins: VecDeque::with_capacity(look + 2),
50            boxed: vec![1.0; look],
51            boxed_sum: look as f64,
52            boxed_at: 0,
53            released: 1.0,
54            delay: vec![0.0; (latency + 1) * channels],
55            delay_at: 0,
56            step: 0,
57        })
58    }
59
60    pub fn latency(&self) -> usize {
61        self.look - 1 + CENTRE
62    }
63
64    pub fn process(&mut self, input: &AudioBuffer, out: &mut AudioBuffer) -> Result<()> {
65        if input.channels as usize != self.channels {
66            return Err(Error::Invalid(format!(
67                "limiter expects {} channels, got {}",
68                self.channels, input.channels
69            )));
70        }
71        out.rate = input.rate;
72        out.channels = input.channels;
73        self.run(&input.samples, out);
74        Ok(())
75    }
76
77    pub fn flush(&mut self, out: &mut AudioBuffer) {
78        out.channels = self.channels as u16;
79        let silence = vec![0.0; self.latency() * self.channels];
80        self.run(&silence, out);
81    }
82
83    fn run(&mut self, input: &[f32], out: &mut AudioBuffer) {
84        let nch = self.channels;
85        let frames = input.len() / nch;
86        out.samples.clear();
87        out.samples.reserve(frames * nch);
88        self.peaks.clear();
89        self.peaks.resize(frames, 0.0);
90        for (c, buffer) in self.buffers.iter_mut().enumerate() {
91            buffer.extend(input.iter().skip(c).step_by(nch).take(frames));
92            peaks(
93                buffer,
94                &self.bank,
95                &mut self.scratch,
96                &mut self.peaks,
97                self.ceiling,
98            );
99            let n = buffer.len();
100            buffer.copy_within(n - (TAPS - 1).., 0);
101            buffer.truncate(TAPS - 1);
102        }
103        let slots = self.delay.len() / nch;
104        for (f, frame) in input.chunks_exact(nch).enumerate() {
105            let p = self.peaks[f];
106            let need = if p > self.ceiling {
107                self.ceiling / p
108            } else {
109                1.0
110            };
111            while self.mins.back().is_some_and(|&(_, g)| g >= need) {
112                self.mins.pop_back();
113            }
114            self.mins.push_back((self.step, need));
115            while self
116                .mins
117                .front()
118                .is_some_and(|&(i, _)| i + (self.look as u64) < self.step)
119            {
120                self.mins.pop_front();
121            }
122            let h = self.mins.front().map_or(1.0, |&(_, g)| g);
123            self.released = h.min(self.released + (1.0 - self.released) * self.release);
124            self.boxed_sum += (self.released - self.boxed[self.boxed_at]) as f64;
125            self.boxed[self.boxed_at] = self.released;
126            self.boxed_at = (self.boxed_at + 1) % self.look;
127            let g = (self.boxed_sum / self.look as f64) as f32;
128            let at = self.delay_at * nch;
129            let oldest = ((self.delay_at + 1) % slots) * nch;
130            self.delay[at..at + nch].copy_from_slice(frame);
131            for c in 0..nch {
132                out.samples.push(self.delay[oldest + c] * g);
133            }
134            self.delay_at = (self.delay_at + 1) % slots;
135            self.step += 1;
136        }
137    }
138}
139
140fn peaks(buffer: &[f32], bank: &[[f32; TAPS]], scratch: &mut [f32], peaks: &mut [f32], floor: f32) {
141    let n = peaks.len();
142    let mut start = 0;
143    while start < n {
144        let len = (n - start).min(scratch.len());
145        let out = &mut scratch[..len];
146        interpolated(&buffer[start..start + len + TAPS - 1], bank, out, floor);
147        for (p, &v) in peaks[start..start + len].iter_mut().zip(out.iter()) {
148            *p = p.max(v);
149        }
150        start += len;
151    }
152}
153
154pub fn normalize_gain_db(measured: &Loudness, target_lufs: f64) -> f64 {
155    if measured.integrated.is_finite() && target_lufs.is_finite() {
156        target_lufs - measured.integrated
157    } else {
158        0.0
159    }
160}
161
162pub fn normalize(buffer: &mut AudioBuffer, target_lufs: f64, ceiling_db: f64) -> Result<Loudness> {
163    let mut first = None;
164    for _ in 0..3 {
165        let mut meter = LoudnessMeter::new(buffer.rate, buffer.channels)?;
166        meter.process(buffer)?;
167        let now = meter.summary();
168        first.get_or_insert(now);
169        let g = normalize_gain_db(&now, target_lufs);
170        if g.abs() < 0.05 && now.true_peak <= ceiling_db + 0.05 {
171            break;
172        }
173        gain(buffer, g as f32);
174        if now.true_peak + g > ceiling_db {
175            limit(buffer, ceiling_db)?;
176        }
177    }
178    first.ok_or_else(|| Error::Invalid("nothing measured".into()))
179}
180
181fn limit(buffer: &mut AudioBuffer, ceiling_db: f64) -> Result<()> {
182    let mut limiter = Limiter::new(buffer.rate, buffer.channels, ceiling_db)?;
183    let mut out = AudioBuffer::new(buffer.rate, buffer.channels);
184    let mut tail = AudioBuffer::new(buffer.rate, buffer.channels);
185    limiter.process(buffer, &mut out)?;
186    limiter.flush(&mut tail);
187    out.samples.extend_from_slice(&tail.samples);
188    let skip = limiter.latency() * buffer.channels as usize;
189    buffer.samples.clear();
190    buffer.samples.extend_from_slice(&out.samples[skip..]);
191    Ok(())
192}