Skip to main content

alpha2_audio/
limiter.rs

1use crate::{ProcessCtx, Processor, StateBlob, StateError};
2
3/// Linked-channel peak limiter with instantaneous attack and exponential release.
4#[derive(Copy, Clone, Debug, PartialEq)]
5pub struct PeakLimiter {
6    threshold: f32,
7    release_ms: f32,
8    envelope: f32,
9    release_coefficient: f32,
10}
11
12impl PeakLimiter {
13    /// Creates a limiter with a positive linear threshold and release time.
14    ///
15    /// A non-finite or non-positive threshold becomes 1.0 and such a release becomes 50 ms.
16    #[must_use]
17    pub fn new(threshold: f32, release_ms: f32) -> Self {
18        Self {
19            threshold: valid_positive(threshold, 1.0),
20            release_ms: valid_positive(release_ms, 50.0),
21            envelope: 0.0,
22            release_coefficient: 0.0,
23        }
24    }
25}
26
27impl Processor for PeakLimiter {
28    fn prepare(&mut self, sample_rate: u32, _max_block: usize) {
29        #[allow(clippy::cast_precision_loss)]
30        let release_samples = self.release_ms * 0.001 * sample_rate.max(1) as f32;
31        self.release_coefficient = libm::expf(-1.0 / release_samples);
32    }
33
34    fn process(&mut self, _ctx: &ProcessCtx, inputs: &[&[f32]], outputs: &mut [&mut [f32]]) {
35        let frames = inputs
36            .iter()
37            .map(|channel| channel.len())
38            .chain(outputs.iter().map(|channel| channel.len()))
39            .min()
40            .unwrap_or(0);
41        let channels = inputs.len().min(outputs.len());
42        for frame in 0..frames {
43            let peak = inputs[..channels]
44                .iter()
45                .map(|channel| finite_or_zero(channel[frame]).abs())
46                .fold(0.0_f32, f32::max);
47            if !self.envelope.is_finite() {
48                self.envelope = 0.0;
49            }
50            self.envelope = peak.max(self.envelope * self.release_coefficient);
51            // Keep a released envelope out of the subnormal range, where k * c rounds back to k.
52            if self.envelope < 1.0e-30 {
53                self.envelope = 0.0;
54            }
55            let gain = if self.envelope > self.threshold {
56                self.threshold / self.envelope
57            } else {
58                1.0
59            };
60            for channel in 0..channels {
61                outputs[channel][frame] = finite_or_zero(inputs[channel][frame]) * gain;
62            }
63        }
64    }
65
66    fn reset(&mut self) {
67        self.envelope = 0.0;
68    }
69
70    fn history_samples(&self) -> u32 {
71        crate::processor::settling_samples(f64::from(self.release_coefficient))
72    }
73
74    fn save_state(&self) -> StateBlob {
75        StateBlob::new(self.envelope.to_le_bytes().to_vec())
76    }
77
78    fn load_state(&mut self, state: &StateBlob) -> Result<(), StateError> {
79        let bytes = <[u8; 4]>::try_from(state.as_bytes()).map_err(|_| StateError::Mismatch)?;
80        let envelope = f32::from_le_bytes(bytes);
81        if !envelope.is_finite() || envelope < 0.0 {
82            return Err(StateError::Invalid);
83        }
84        self.envelope = envelope;
85        Ok(())
86    }
87}
88
89fn finite_or_zero(value: f32) -> f32 {
90    if value.is_finite() { value } else { 0.0 }
91}
92
93fn valid_positive(value: f32, fallback: f32) -> f32 {
94    if value.is_finite() && value > 0.0 {
95        value
96    } else {
97        fallback
98    }
99}