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            // Released to silence, the envelope would sink into the slow subnormal range (k * c
52            // rounds back to k). No usable threshold is this small.
53            if self.envelope < 1.0e-30 {
54                self.envelope = 0.0;
55            }
56            let gain = if self.envelope > self.threshold {
57                self.threshold / self.envelope
58            } else {
59                1.0
60            };
61            for channel in 0..channels {
62                outputs[channel][frame] = finite_or_zero(inputs[channel][frame]) * gain;
63            }
64        }
65    }
66
67    fn reset(&mut self) {
68        self.envelope = 0.0;
69    }
70
71    fn history_samples(&self) -> u32 {
72        crate::processor::settling_samples(f64::from(self.release_coefficient))
73    }
74
75    fn save_state(&self) -> StateBlob {
76        StateBlob::new(self.envelope.to_le_bytes().to_vec())
77    }
78
79    fn load_state(&mut self, state: &StateBlob) -> Result<(), StateError> {
80        let bytes = <[u8; 4]>::try_from(state.as_bytes()).map_err(|_| StateError::Mismatch)?;
81        let envelope = f32::from_le_bytes(bytes);
82        if !envelope.is_finite() || envelope < 0.0 {
83            return Err(StateError::Invalid);
84        }
85        self.envelope = envelope;
86        Ok(())
87    }
88}
89
90fn finite_or_zero(value: f32) -> f32 {
91    if value.is_finite() { value } else { 0.0 }
92}
93
94fn valid_positive(value: f32, fallback: f32) -> f32 {
95    if value.is_finite() && value > 0.0 {
96        value
97    } else {
98        fallback
99    }
100}