1use crate::math::{cos_turns, db_to_gain, exp, sin_turns};
4use crate::{Audio, Error, Result};
5use std::collections::VecDeque;
6use std::f64::consts::PI;
7
8const PHASES: usize = 8;
9const TAPS: usize = 64;
10const HALF: usize = TAPS / 2;
11
12#[derive(Clone, Copy, Debug)]
15pub struct TruePeakLimiterOptions {
16 pub ceiling_db: f64,
19 pub lookahead_ms: f64,
22 pub release_ms: f64,
24 pub safety_margin_db: f64,
27 pub max_working_bytes: usize,
30}
31
32impl Default for TruePeakLimiterOptions {
33 fn default() -> Self {
34 Self {
35 ceiling_db: -1.0,
36 lookahead_ms: 5.0,
37 release_ms: 50.0,
38 safety_margin_db: 0.3,
39 max_working_bytes: 1 << 30,
40 }
41 }
42}
43
44struct Detector {
45 kernel: [[f64; PHASES - 1]; TAPS],
47}
48
49impl Detector {
50 fn new() -> Self {
51 let mut kernel = [[0.0; PHASES - 1]; TAPS];
52 for phase in 1..PHASES {
53 let mut sum = 0.0;
54 for (tap, row) in kernel.iter_mut().enumerate() {
55 let t = tap as f64 - (HALF - 1) as f64 - phase as f64 / PHASES as f64;
56 let sinc = sin_turns(t / 2.0) / (PI * t);
57 let window =
58 0.42 + 0.5 * cos_turns(t / TAPS as f64) + 0.08 * cos_turns(t / HALF as f64);
59 row[phase - 1] = sinc * window;
60 sum += row[phase - 1];
61 }
62 for row in &mut kernel {
63 row[phase - 1] /= sum;
64 }
65 }
66 Self { kernel }
67 }
68
69 fn peak_at(&self, audio: &Audio, frame: i64) -> f64 {
72 let mut peak = 0.0f64;
73 for channel in &audio.data {
74 if frame >= 0 && (frame as usize) < channel.len() {
75 peak = peak.max(channel[frame as usize].abs() as f64);
76 }
77 let mut sums = [0.0; PHASES - 1];
78 let start = frame - (HALF - 1) as i64;
79 if start >= 0 && start as usize + TAPS <= channel.len() {
80 for (&x, row) in channel[start as usize..start as usize + TAPS]
81 .iter()
82 .zip(&self.kernel)
83 {
84 for (sum, &k) in sums.iter_mut().zip(row) {
85 *sum += x as f64 * k;
86 }
87 }
88 } else {
89 for (tap, row) in self.kernel.iter().enumerate() {
90 let at = start + tap as i64;
91 if at >= 0 && (at as usize) < channel.len() {
92 let x = channel[at as usize] as f64;
93 for (sum, &k) in sums.iter_mut().zip(row) {
94 *sum += x * k;
95 }
96 }
97 }
98 }
99 for sum in sums {
100 peak = peak.max(sum.abs());
101 }
102 }
103 peak
104 }
105
106 fn edges(&self, audio: &Audio) -> (f64, f64) {
107 let head = (-(HALF as i64)..0)
108 .map(|frame| self.peak_at(audio, frame))
109 .fold(0.0, f64::max);
110 let end = audio.frames() as i64;
111 let tail = (end..end + HALF as i64)
112 .map(|frame| self.peak_at(audio, frame))
113 .fold(0.0, f64::max);
114 (head, tail)
115 }
116
117 fn peak(&self, audio: &Audio) -> f64 {
118 let (head, tail) = self.edges(audio);
119 (0..audio.frames())
120 .map(|i| self.peak_at(audio, i as i64))
121 .fold(head.max(tail), f64::max)
122 }
123}
124
125impl Audio {
126 pub fn limit_true_peak(&self, options: TruePeakLimiterOptions) -> Result<Self> {
143 if !options.ceiling_db.is_finite()
144 || !options.lookahead_ms.is_finite()
145 || options.lookahead_ms < 0.0
146 || !options.release_ms.is_finite()
147 || options.release_ms <= 0.0
148 || !options.safety_margin_db.is_finite()
149 || !(0.0..=12.0).contains(&options.safety_margin_db)
150 {
151 return Err(Error::invalid("Invalid true-peak limiter settings"));
152 }
153 let ceiling = db_to_gain(options.ceiling_db - options.safety_margin_db);
154 if !ceiling.is_finite() || ceiling <= 0.0 {
155 return Err(Error::invalid("Invalid true-peak limiter ceiling"));
156 }
157 let frames = self.frames();
158 if frames as u128 + TAPS as u128 > i64::MAX as u128 {
159 return Err(Error::limit("True-peak limiter frame count overflow"));
160 }
161 let look = (options.lookahead_ms * self.rate as f64 / 1000.0)
162 .round()
163 .min(frames as f64) as usize;
164 let capacity = frames.min(look.saturating_add(1));
165 let bytes = frames
166 .checked_mul(self.channels())
167 .and_then(|v| v.checked_mul(size_of::<f32>()))
168 .and_then(|v| v.checked_add(self.channels().checked_mul(size_of::<Vec<f32>>())?))
169 .and_then(|v| v.checked_add(capacity.checked_mul(size_of::<(usize, f64)>())?))
170 .and_then(|v| v.checked_add(size_of::<Detector>()))
171 .ok_or_else(|| Error::limit("True-peak limiter workspace size overflow"))?;
172 if bytes > options.max_working_bytes {
173 return Err(Error::limit("True-peak limiter exceeds workspace budget"));
174 }
175 let allocation = |_| Error::limit("Cannot allocate true-peak limiter workspace");
176 let mut data = Vec::new();
177 data.try_reserve_exact(self.channels())
178 .map_err(allocation)?;
179 for channel in &self.data {
180 let mut copy = Vec::new();
181 copy.try_reserve_exact(frames).map_err(allocation)?;
182 copy.extend_from_slice(channel);
183 data.push(copy);
184 }
185 let mut peaks = VecDeque::<(usize, f64)>::new();
186 peaks.try_reserve_exact(capacity).map_err(allocation)?;
187 if frames == 0 {
188 return Ok(self.with_data(data));
189 }
190 let detector = Detector::new();
191 let (head, tail) = detector.edges(self);
192 let protected = ceiling * (1.0 - 8.0 * f32::EPSILON as f64);
194 let release = exp(-1.0 / (options.release_ms * self.rate as f64 / 1000.0));
195 let mut gain = 1.0;
196 let mut next = 0;
197 for i in 0..frames {
198 while peaks.front().is_some_and(|(at, _)| *at < i) {
201 peaks.pop_front();
202 }
203 while next < frames && next <= i.saturating_add(look) {
204 let mut peak = detector.peak_at(self, next as i64);
205 if next == 0 {
206 peak = peak.max(head);
207 }
208 if next + 1 == frames {
209 peak = peak.max(tail);
210 }
211 while peaks.back().is_some_and(|(_, p)| *p <= peak) {
212 peaks.pop_back();
213 }
214 peaks.push_back((next, peak));
215 next += 1;
216 }
217 let peak = peaks.front().map_or(0.0, |(_, p)| *p);
218 let target = if peak > protected {
219 protected / peak
220 } else {
221 1.0
222 };
223 gain = if target < gain {
224 target
225 } else {
226 target + (gain - target) * release
227 };
228 for channel in &mut data {
229 channel[i] = (channel[i] as f64 * gain) as f32;
230 }
231 }
232 let mut output = self.with_data(data);
233 let peak = detector.peak(&output).max(output.render_true_peak());
234 if peak > protected {
235 let correction = protected / peak;
236 for sample in output.data.iter_mut().flatten() {
237 *sample = (*sample as f64 * correction) as f32;
238 }
239 }
240 Ok(output)
241 }
242}