Skip to main content

rodio/source/
agc.rs

1//
2//      Automatic Gain Control (AGC) Algorithm
3//      Designed by @UnknownSuperficialNight
4//
5//   Features:
6//   • Adaptive peak detection
7//   • RMS-based level estimation
8//   • Asymmetric attack/release
9//   • RMS-based general adjustments with peak limiting
10//
11//   Optimized for smooth and responsive gain control
12//
13//   Crafted with love. Enjoy! :)
14//
15
16use super::SeekError;
17use crate::math::duration_to_coefficient;
18use crate::{Float, Sample, Source};
19#[cfg(all(feature = "experimental", not(feature = "64bit")))]
20use atomic_float::AtomicF32;
21#[cfg(all(feature = "experimental", feature = "64bit"))]
22use atomic_float::AtomicF64;
23#[cfg(feature = "experimental")]
24use std::sync::atomic::{AtomicBool, Ordering};
25#[cfg(feature = "experimental")]
26use std::sync::Arc;
27use std::time::Duration;
28
29#[cfg(all(feature = "experimental", not(feature = "64bit")))]
30type AtomicFloat = AtomicF32;
31#[cfg(all(feature = "experimental", feature = "64bit"))]
32type AtomicFloat = AtomicF64;
33
34use crate::common::{ChannelCount, SampleRate};
35#[cfg(feature = "tracing")]
36use tracing;
37
38/// Ensures `RMS_WINDOW_SIZE` is a power of two
39const fn power_of_two(n: usize) -> usize {
40    assert!(
41        n.is_power_of_two(),
42        "RMS_WINDOW_SIZE must be a power of two"
43    );
44    n
45}
46
47/// Size of the circular buffer used for RMS calculation.
48/// A larger size provides more stable RMS values but increases latency.
49const RMS_WINDOW_SIZE: usize = power_of_two(8192);
50
51/// Settings for the Automatic Gain Control (AGC).
52///
53/// This struct contains parameters that define how the AGC will function,
54/// allowing users to customise its behaviour.
55#[derive(Debug, Clone)]
56pub struct AutomaticGainControlSettings {
57    /// The desired output level that the AGC tries to maintain.
58    /// A value of 1.0 means no change to the original level.
59    pub target_level: Float,
60    /// Time constant for gain increases (how quickly the AGC responds to level increases).
61    /// Longer durations result in slower, more gradual gain increases.
62    pub attack_time: Duration,
63    /// Time constant for gain decreases (how quickly the AGC responds to level decreases).
64    /// Shorter durations allow for faster response to sudden loud signals.
65    pub release_time: Duration,
66    /// Maximum allowable gain multiplication to prevent excessive amplification.
67    /// This acts as a safety limit to avoid distortion from over-amplification.
68    pub absolute_max_gain: Float,
69}
70
71impl Default for AutomaticGainControlSettings {
72    fn default() -> Self {
73        AutomaticGainControlSettings {
74            target_level: 1.0,                    // Default to original level
75            attack_time: Duration::from_secs(4),  // Recommended attack time
76            release_time: Duration::from_secs(0), // Recommended release time
77            absolute_max_gain: 7.0,               // Recommended max gain
78        }
79    }
80}
81
82#[cfg(feature = "experimental")]
83/// Automatic Gain Control filter for maintaining consistent output levels.
84///
85/// This struct implements an AGC algorithm that dynamically adjusts audio levels
86/// based on both **peak** and **RMS** (Root Mean Square) measurements.
87#[derive(Clone, Debug)]
88pub struct AutomaticGainControl<I> {
89    input: I,
90    target_level: Arc<AtomicFloat>,
91    floor: Float,
92    absolute_max_gain: Arc<AtomicFloat>,
93    current_gain: Float,
94    attack_coeff: Arc<AtomicFloat>,
95    release_coeff: Arc<AtomicFloat>,
96    peak_level: Float,
97    rms_window: CircularBuffer,
98    is_enabled: Arc<AtomicBool>,
99}
100
101#[cfg(not(feature = "experimental"))]
102/// Automatic Gain Control filter for maintaining consistent output levels.
103///
104/// This struct implements an AGC algorithm that dynamically adjusts audio levels
105/// based on both **peak** and **RMS** (Root Mean Square) measurements.
106#[derive(Clone, Debug)]
107pub struct AutomaticGainControl<I> {
108    input: I,
109    target_level: Float,
110    floor: Float,
111    absolute_max_gain: Float,
112    current_gain: Float,
113    attack_coeff: Float,
114    release_coeff: Float,
115    peak_level: Float,
116    rms_window: CircularBuffer,
117    is_enabled: bool,
118}
119
120/// A circular buffer for efficient RMS calculation over a sliding window.
121///
122/// This structure allows for constant-time updates and mean calculations,
123/// which is crucial for real-time audio processing.
124#[derive(Clone, Debug)]
125struct CircularBuffer {
126    buffer: Box<[Float; RMS_WINDOW_SIZE]>,
127    sum: Float,
128    index: usize,
129}
130
131impl CircularBuffer {
132    /// Creates a new `CircularBuffer` with a fixed size determined at compile time.
133    #[inline]
134    fn new() -> Self {
135        CircularBuffer {
136            buffer: Box::new([0.0; RMS_WINDOW_SIZE]),
137            sum: 0.0,
138            index: 0,
139        }
140    }
141
142    /// Pushes a new value into the buffer and returns the old value.
143    ///
144    /// This method maintains a running sum for efficient mean calculation.
145    #[inline]
146    fn push(&mut self, value: Float) -> Float {
147        let old_value = self.buffer[self.index];
148        // Update the sum by first subtracting the old value and then adding the new value; this is more accurate.
149        self.sum = self.sum - old_value + value;
150        self.buffer[self.index] = value;
151        // Use bitwise AND for efficient index wrapping since RMS_WINDOW_SIZE is a power of two.
152        self.index = (self.index + 1) & (RMS_WINDOW_SIZE - 1);
153        old_value
154    }
155
156    /// Calculates the mean of all values in the buffer.
157    ///
158    /// This operation is `O(1)` due to the maintained running sum.
159    #[inline]
160    fn mean(&self) -> Float {
161        self.sum / RMS_WINDOW_SIZE as Float
162    }
163}
164
165/// Constructs an `AutomaticGainControl` object with specified parameters.
166///
167/// # Arguments
168///
169/// * `input` - The input audio source
170/// * `target_level` - The desired output level
171/// * `attack_time` - Time constant for gain increase
172/// * `release_time` - Time constant for gain decrease
173/// * `absolute_max_gain` - Maximum allowable gain
174#[inline]
175pub(crate) fn automatic_gain_control<I>(
176    input: I,
177    target_level: Float,
178    attack_time: Duration,
179    release_time: Duration,
180    absolute_max_gain: Float,
181) -> AutomaticGainControl<I>
182where
183    I: Source,
184{
185    let sample_rate = input.sample_rate();
186    let attack_coeff = duration_to_coefficient(attack_time, sample_rate);
187    let release_coeff = duration_to_coefficient(release_time, sample_rate);
188
189    #[cfg(feature = "experimental")]
190    {
191        AutomaticGainControl {
192            input,
193            target_level: Arc::new(AtomicFloat::new(target_level)),
194            floor: 0.0,
195            absolute_max_gain: Arc::new(AtomicFloat::new(absolute_max_gain)),
196            current_gain: 1.0,
197            attack_coeff: Arc::new(AtomicFloat::new(attack_coeff)),
198            release_coeff: Arc::new(AtomicFloat::new(release_coeff)),
199            peak_level: 0.0,
200            rms_window: CircularBuffer::new(),
201            is_enabled: Arc::new(AtomicBool::new(true)),
202        }
203    }
204
205    #[cfg(not(feature = "experimental"))]
206    {
207        AutomaticGainControl {
208            input,
209            target_level,
210            floor: 0.0,
211            absolute_max_gain,
212            current_gain: 1.0,
213            attack_coeff,
214            release_coeff,
215            peak_level: 0.0,
216            rms_window: CircularBuffer::new(),
217            is_enabled: true,
218        }
219    }
220}
221
222impl<I> AutomaticGainControl<I>
223where
224    I: Source,
225{
226    #[inline]
227    fn target_level(&self) -> Float {
228        #[cfg(feature = "experimental")]
229        {
230            self.target_level.load(Ordering::Relaxed)
231        }
232        #[cfg(not(feature = "experimental"))]
233        {
234            self.target_level
235        }
236    }
237
238    #[inline]
239    fn absolute_max_gain(&self) -> Float {
240        #[cfg(feature = "experimental")]
241        {
242            self.absolute_max_gain.load(Ordering::Relaxed)
243        }
244        #[cfg(not(feature = "experimental"))]
245        {
246            self.absolute_max_gain
247        }
248    }
249
250    #[inline]
251    fn attack_coeff(&self) -> Float {
252        #[cfg(feature = "experimental")]
253        {
254            self.attack_coeff.load(Ordering::Relaxed)
255        }
256        #[cfg(not(feature = "experimental"))]
257        {
258            self.attack_coeff
259        }
260    }
261
262    #[inline]
263    fn release_coeff(&self) -> Float {
264        #[cfg(feature = "experimental")]
265        {
266            self.release_coeff.load(Ordering::Relaxed)
267        }
268        #[cfg(not(feature = "experimental"))]
269        {
270            self.release_coeff
271        }
272    }
273
274    #[inline]
275    fn is_enabled(&self) -> bool {
276        #[cfg(feature = "experimental")]
277        {
278            self.is_enabled.load(Ordering::Relaxed)
279        }
280        #[cfg(not(feature = "experimental"))]
281        {
282            self.is_enabled
283        }
284    }
285
286    #[cfg(feature = "experimental")]
287    /// Access the target output level for real-time adjustment.
288    ///
289    /// Use this to dynamically modify the AGC's target level while audio is processing.
290    /// Adjust this value to control the overall output amplitude of the processed signal.
291    #[inline]
292    pub fn get_target_level(&self) -> Arc<AtomicFloat> {
293        Arc::clone(&self.target_level)
294    }
295
296    #[cfg(feature = "experimental")]
297    /// Access the maximum gain limit for real-time adjustment.
298    ///
299    /// Use this to dynamically modify the AGC's maximum allowable gain during runtime.
300    /// Adjusting this value helps prevent excessive amplification in low-level signals.
301    #[inline]
302    pub fn get_absolute_max_gain(&self) -> Arc<AtomicFloat> {
303        Arc::clone(&self.absolute_max_gain)
304    }
305
306    #[cfg(feature = "experimental")]
307    /// Access the attack coefficient for real-time adjustment.
308    ///
309    /// Use this to dynamically modify how quickly the AGC responds to level increases.
310    /// Smaller values result in faster response, larger values in slower response.
311    /// Adjust during runtime to fine-tune AGC behavior for different audio content.
312    #[inline]
313    pub fn get_attack_coeff(&self) -> Arc<AtomicFloat> {
314        Arc::clone(&self.attack_coeff)
315    }
316
317    #[cfg(feature = "experimental")]
318    /// Access the release coefficient for real-time adjustment.
319    ///
320    /// Use this to dynamically modify how quickly the AGC responds to level decreases.
321    /// Smaller values result in faster response, larger values in slower response.
322    /// Adjust during runtime to optimize AGC behavior for varying audio dynamics.
323    #[inline]
324    pub fn get_release_coeff(&self) -> Arc<AtomicFloat> {
325        Arc::clone(&self.release_coeff)
326    }
327
328    #[cfg(feature = "experimental")]
329    /// Access the AGC on/off control.
330    /// Use this to dynamically enable or disable AGC processing during runtime.
331    ///
332    /// AGC is on by default. `false` is disabled state, `true` is enabled.
333    /// In disabled state the sound is passed through AGC unchanged.
334    ///
335    /// In particular, this control is useful for comparing processed and unprocessed audio.
336    #[inline]
337    pub fn get_agc_control(&self) -> Arc<AtomicBool> {
338        Arc::clone(&self.is_enabled)
339    }
340
341    /// Enable or disable AGC processing.
342    ///
343    /// Use this to enable or disable AGC processing.
344    /// Useful for comparing processed and unprocessed audio or for disabling/enabling AGC.
345    #[inline]
346    pub fn set_enabled(&mut self, enabled: bool) {
347        #[cfg(feature = "experimental")]
348        {
349            self.is_enabled.store(enabled, Ordering::Relaxed);
350        }
351        #[cfg(not(feature = "experimental"))]
352        {
353            self.is_enabled = enabled;
354        }
355    }
356
357    /// Set the floor value for the AGC
358    ///
359    /// This method sets the floor value for the AGC. The floor value is the minimum
360    /// gain that the AGC will allow. The gain will not drop below this value.
361    ///
362    /// Passing `None` will disable the floor value (setting it to 0.0), allowing the
363    /// AGC gain to drop to very low levels.
364    #[inline]
365    pub fn set_floor(&mut self, floor: Option<Float>) {
366        self.floor = floor.unwrap_or(0.0);
367    }
368
369    /// Updates the peak level using instant attack and slow release behaviour
370    ///
371    /// This method uses instant response (0.0 coefficient) when the signal is increasing
372    /// and the release coefficient when the signal is decreasing, providing
373    /// appropriate tracking behaviour for peak detection.
374    #[inline]
375    fn update_peak_level(&mut self, sample_value: Sample, release_coeff: Float) {
376        let coeff = if sample_value > self.peak_level {
377            // Fast attack for rising peaks
378            0.0
379        } else {
380            // Slow release for falling peaks
381            release_coeff
382        };
383
384        self.peak_level = self.peak_level * coeff + sample_value * (1.0 - coeff);
385    }
386
387    /// Updates the RMS (Root Mean Square) level using a circular buffer approach.
388    /// This method calculates a moving average of the squared input samples,
389    /// providing a measure of the signal's average power over time.
390    #[inline]
391    fn update_rms(&mut self, sample_value: Sample) -> Float {
392        let squared_sample = sample_value * sample_value;
393        self.rms_window.push(squared_sample);
394        self.rms_window.mean().sqrt()
395    }
396
397    /// Calculate gain adjustments based on peak levels
398    /// This method determines the appropriate gain level to apply to the audio
399    /// signal, considering the peak level.
400    /// The peak level helps prevent sudden spikes in the output signal.
401    #[inline]
402    fn calculate_peak_gain(&self, target_level: Float, absolute_max_gain: Float) -> Float {
403        if self.peak_level > 0.0 {
404            (target_level / self.peak_level).min(absolute_max_gain)
405        } else {
406            absolute_max_gain
407        }
408    }
409
410    #[inline]
411    fn process_sample(&mut self, sample: I::Item) -> I::Item {
412        // Cache atomic loads at the start - avoids repeated atomic operations
413        let target_level = self.target_level();
414        let absolute_max_gain = self.absolute_max_gain();
415        let attack_coeff = self.attack_coeff();
416        let release_coeff = self.release_coeff();
417
418        // Convert the sample to its absolute float value for level calculations
419        let sample_value = sample.abs();
420
421        // Dynamically adjust peak level using cached release coefficient
422        self.update_peak_level(sample_value, release_coeff);
423
424        // Calculate the current RMS (Root Mean Square) level using a sliding window approach
425        let rms = self.update_rms(sample_value);
426
427        // Compute the gain adjustment required to reach the target level based on RMS
428        let rms_gain = if rms > 0.0 {
429            target_level / rms
430        } else {
431            absolute_max_gain // Default to max gain if RMS is zero
432        };
433
434        // Calculate the peak limiting gain
435        let peak_gain = self.calculate_peak_gain(target_level, absolute_max_gain);
436
437        // Use RMS for general adjustments, but limit by peak gain to prevent clipping and apply a minimum floor value
438        let desired_gain = rms_gain.min(peak_gain).max(self.floor);
439
440        // Adaptive attack/release speed for AGC (Automatic Gain Control)
441        //
442        // This mechanism implements an asymmetric approach to gain adjustment:
443        // 1. **Slow increase**: Prevents abrupt amplification of noise during quiet periods.
444        // 2. **Fast decrease**: Rapidly attenuates sudden loud signals to avoid distortion.
445        //
446        // The asymmetry is crucial because:
447        // - Gradual gain increases sound more natural and less noticeable to listeners.
448        // - Quick gain reductions are necessary to prevent clipping and maintain audio quality.
449        //
450        // This approach addresses several challenges associated with high attack times:
451        // 1. **Slow response**: With a high attack time, the AGC responds very slowly to changes in input level.
452        //    This means it takes longer for the gain to adjust to new signal levels.
453        // 2. **Initial gain calculation**: When the audio starts or after a period of silence, the initial gain
454        //    calculation might result in a very high gain value, especially if the input signal starts quietly.
455        // 3. **Overshooting**: As the gain slowly increases (due to the high attack time), it might overshoot
456        //    the desired level, causing the signal to become too loud.
457        // 4. **Overcorrection**: The AGC then tries to correct this by reducing the gain, but due to the slow response,
458        //    it might reduce the gain too much, causing the sound to drop to near-zero levels.
459        // 5. **Slow recovery**: Again, due to the high attack time, it takes a while for the gain to increase
460        //    back to the appropriate level.
461        //
462        // By using a faster release time for decreasing gain, we can mitigate these issues and provide
463        // more responsive control over sudden level increases while maintaining smooth gain increases.
464        let attack_speed = if desired_gain > self.current_gain {
465            attack_coeff
466        } else {
467            release_coeff
468        };
469
470        // Gradually adjust the current gain towards the desired gain for smooth transitions
471        self.current_gain = self.current_gain * attack_speed + desired_gain * (1.0 - attack_speed);
472
473        // Ensure the calculated gain stays within the defined operational range
474        self.current_gain = self.current_gain.clamp(0.1, absolute_max_gain);
475
476        // Output current gain value for developers to fine tune their inputs to automatic_gain_control
477        #[cfg(feature = "tracing")]
478        tracing::debug!("AGC gain: {}", self.current_gain,);
479
480        // Apply the computed gain to the input sample and return the result
481        sample * self.current_gain
482    }
483
484    /// Returns an immutable reference to the inner source.
485    pub fn inner(&self) -> &I {
486        &self.input
487    }
488
489    /// Returns a mutable reference to the inner source.
490    pub fn inner_mut(&mut self) -> &mut I {
491        &mut self.input
492    }
493}
494
495impl<I> Iterator for AutomaticGainControl<I>
496where
497    I: Source,
498{
499    type Item = I::Item;
500
501    #[inline]
502    fn next(&mut self) -> Option<Self::Item> {
503        self.input.next().map(|sample| {
504            if self.is_enabled() {
505                self.process_sample(sample)
506            } else {
507                sample
508            }
509        })
510    }
511
512    #[inline]
513    fn size_hint(&self) -> (usize, Option<usize>) {
514        self.input.size_hint()
515    }
516}
517
518impl<I> ExactSizeIterator for AutomaticGainControl<I> where I: Source + ExactSizeIterator {}
519
520impl<I> Source for AutomaticGainControl<I>
521where
522    I: Source,
523{
524    #[inline]
525    fn current_span_len(&self) -> Option<usize> {
526        self.input.current_span_len()
527    }
528
529    #[inline]
530    fn channels(&self) -> ChannelCount {
531        self.input.channels()
532    }
533
534    #[inline]
535    fn sample_rate(&self) -> SampleRate {
536        self.input.sample_rate()
537    }
538
539    #[inline]
540    fn total_duration(&self) -> Option<Duration> {
541        self.input.total_duration()
542    }
543
544    #[inline]
545    fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
546        self.input.try_seek(pos)
547    }
548}