audiobox/mixer/
limiter.rs1use crate::math::exp2;
3use crate::{Error, Result};
4
5#[derive(Clone, Copy, Debug, PartialEq)]
16pub struct MasterLimiter {
17 pub ceiling_db: f32,
19 pub lookahead_ms: f32,
21 pub release_ms: f32,
23}
24impl Default for MasterLimiter {
25 fn default() -> Self {
26 Self {
27 ceiling_db: -1.0,
28 lookahead_ms: 0.0,
29 release_ms: 50.0,
30 }
31 }
32}
33
34pub(super) struct Limiter {
35 ceiling: f64,
36 release: f64,
38 channels: usize,
39 window: usize,
41 delay: Vec<f32>,
43 at: usize,
44 gain: f64,
45 queue: Vec<(u64, f64)>,
48 queue_head: usize,
49 queue_len: usize,
50 frames: u64,
51}
52
53impl Limiter {
54 pub fn new(settings: MasterLimiter, rate: u32, channels: usize) -> Result<Self> {
55 let MasterLimiter {
56 ceiling_db,
57 lookahead_ms,
58 release_ms,
59 } = settings;
60 if !(-24.0..=0.0).contains(&ceiling_db)
61 || !(0.0..=10.0).contains(&lookahead_ms)
62 || !(1.0..=2000.0).contains(&release_ms)
63 {
64 return Err(Error::invalid(
65 "The limiter needs a ceiling of -24..=0 dBFS, a look-ahead of 0..=10 ms and a release of 1..=2000 ms",
66 ));
67 }
68 let lookahead = (lookahead_ms as f64 * rate as f64 / 1000.0).round() as usize;
69 let release_frames = release_ms as f64 * rate as f64 / 1000.0;
70 Ok(Self {
71 ceiling: exp2(ceiling_db as f64 * std::f64::consts::LOG2_10 / 20.0),
72 release: exp2(-std::f64::consts::LOG2_E / release_frames),
74 channels,
75 window: lookahead + 1,
76 delay: vec![0.0; (lookahead + 1) * channels],
77 at: 0,
78 gain: 1.0,
79 queue: vec![(0, 0.0); lookahead + 1],
80 queue_head: 0,
81 queue_len: 0,
82 frames: 0,
83 })
84 }
85 pub fn latency(&self) -> usize {
87 self.window - 1
88 }
89 fn push_peak(&mut self, peak: f64) -> f64 {
92 let w = self.window;
93 while self.queue_len > 0 && self.queue[self.queue_head].0 + w as u64 <= self.frames {
96 self.queue_head = (self.queue_head + 1) % w;
97 self.queue_len -= 1;
98 }
99 while self.queue_len > 0 && self.queue[(self.queue_head + self.queue_len - 1) % w].1 <= peak
100 {
101 self.queue_len -= 1;
102 }
103 self.queue[(self.queue_head + self.queue_len) % w] = (self.frames, peak);
104 self.queue_len += 1;
105 self.frames += 1;
106 self.queue[self.queue_head].1
107 }
108 pub fn process(&mut self, out: &mut [f32]) -> f64 {
110 let ch = self.channels;
111 let mut lowest = 1.0f64;
112 let ceiling = self.ceiling as f32;
113 for frame in out.chunks_exact_mut(ch) {
114 for s in frame.iter_mut() {
117 if s.is_nan() {
118 *s = 0.0;
119 } else if s.is_infinite() {
120 *s = f32::MAX.copysign(*s);
121 }
122 }
123 let peak = frame.iter().map(|s| s.abs() as f64).fold(0.0, f64::max);
124 let slot = self.at;
125 let loudest = self.push_peak(peak);
126 let next = (slot + 1) % self.window;
129 let mut delayed = [0.0f32; 8];
130 if self.window == 1 {
131 delayed[..ch].copy_from_slice(frame);
132 } else {
133 delayed[..ch].copy_from_slice(&self.delay[next * ch..next * ch + ch]);
134 self.delay[slot * ch..slot * ch + ch].copy_from_slice(frame);
135 }
136 let target = if loudest > self.ceiling {
137 self.ceiling / loudest
138 } else {
139 1.0
140 };
141 self.gain = if target < self.gain {
142 target
143 } else {
144 target + (self.gain - target) * self.release
145 };
146 lowest = lowest.min(self.gain);
147 for (o, &d) in frame.iter_mut().zip(&delayed[..ch]) {
148 *o = ((d as f64 * self.gain) as f32).clamp(-ceiling, ceiling);
150 }
151 self.at = next;
152 }
153 lowest
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn the_queue_tracks_the_window_maximum() {
163 let mut limiter = Limiter::new(
164 MasterLimiter {
165 lookahead_ms: 1.0,
166 ..MasterLimiter::default()
167 },
168 48_000,
169 1,
170 )
171 .unwrap();
172 let window = limiter.window;
173 let mut seed = 0x9e37_79b9u32;
174 let mut peaks: Vec<f64> = (0..5000)
175 .map(|_| {
176 seed = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
177 (seed >> 8) as f64 / (1u32 << 24) as f64 * 2.0
178 })
179 .collect();
180 peaks.extend((0..3 * window).map(|i| 2.0 - i as f64 / (3 * window) as f64));
182 peaks.extend(std::iter::repeat_n(0.5, 2 * window));
183 peaks.extend((0..window).map(|i| 0.5 + i as f64 * 0.01));
184 peaks.extend((0..4 * window).rev().map(|i| i as f64));
185 for (n, &peak) in peaks.iter().enumerate() {
186 let expected = peaks[n.saturating_sub(window - 1)..=n]
187 .iter()
188 .copied()
189 .fold(0.0, f64::max);
190 assert_eq!(limiter.push_peak(peak), expected, "frame {n}");
191 assert!(limiter.queue_len <= window);
192 }
193 }
194
195 #[test]
196 fn decreasing_peaks_keep_the_window_maximum() {
197 let settings = MasterLimiter {
199 lookahead_ms: 1.0,
200 ..MasterLimiter::default()
201 };
202 let mut limiter = Limiter::new(settings, 1000, 1).unwrap();
203 assert_eq!(limiter.window, 2);
204 let got: Vec<f64> = [3.0, 2.0, 1.0, 0.5, 4.0, 0.0]
205 .iter()
206 .map(|&peak| limiter.push_peak(peak))
207 .collect();
208 assert_eq!(got, [3.0, 3.0, 2.0, 1.0, 4.0, 4.0]);
209 }
210}