Skip to main content

rodio/source/
limit.rs

1//! Audio peak limiting for dynamic range control.
2//!
3//! This module implements a feedforward limiter that prevents audio peaks from exceeding
4//! a specified threshold while maintaining audio quality. The limiter is based on:
5//! Giannoulis, D., Massberg, M., & Reiss, J.D. (2012). Digital Dynamic Range Compressor Design,
6//! A Tutorial and Analysis. Journal of The Audio Engineering Society, 60, 399-408.
7//!
8//! # What is Limiting?
9//!
10//! A limiter reduces the amplitude of audio signals that exceed a threshold level.
11//! For example, with a -6dB threshold, peaks above that level are reduced to stay near the
12//! threshold, preventing clipping and maintaining consistent output levels.
13//!
14//! # Features
15//!
16//! * **Soft-knee limiting** - Gradual transition into limiting for natural sound
17//! * **Per-channel detection** - Decoupled peak detection per channel
18//! * **Coupled gain reduction** - Uniform gain reduction across channels preserves stereo imaging
19//! * **Configurable timing** - Adjustable attack/release times for different use cases
20//! * **Efficient processing** - Optimized implementations for mono, stereo, and multi-channel audio
21//!
22//! # Usage
23//!
24//! Use [`LimitSettings`] to configure the limiter, then apply it to any audio source:
25//!
26//! ```rust
27//! use rodio::source::{SineWave, Source, LimitSettings};
28//! use std::time::Duration;
29//!
30//! // Create a loud sine wave
31//! let source = SineWave::new(440.0).amplify(2.0);
32//!
33//! // Apply limiting with -6dB threshold
34//! let settings = LimitSettings::default().with_threshold(-6.0);
35//! let limited = source.limit(settings);
36//! ```
37//!
38//! # Presets
39//!
40//! [`LimitSettings`] provides optimized presets for common use cases:
41//!
42//! * [`LimitSettings::default()`] - General-purpose limiting (-1 dBFS, balanced)
43//! * [`LimitSettings::dynamic_content()`] - Music and sound effects (-3 dBFS, transparent)
44//! * [`LimitSettings::broadcast()`] - Streaming and voice chat (fast response, consistent)
45//! * [`LimitSettings::mastering()`] - Final production stage (-0.5 dBFS, tight peak control)
46//! * [`LimitSettings::gaming()`] - Interactive audio (-3 dBFS, responsive dynamics)
47//! * [`LimitSettings::live_performance()`] - Real-time applications (ultra-fast protection)
48//!
49//! ```rust
50//! use rodio::source::{SineWave, Source, LimitSettings};
51//!
52//! // Use preset optimized for music
53//! let music = SineWave::new(440.0).amplify(1.5);
54//! let limited_music = music.limit(LimitSettings::dynamic_content());
55//!
56//! // Use preset optimized for streaming
57//! let stream = SineWave::new(440.0).amplify(2.0);
58//! let limited_stream = stream.limit(LimitSettings::broadcast());
59//! ```
60
61use std::time::Duration;
62
63use super::SeekError;
64use crate::{
65    common::{ChannelCount, Sample, SampleRate},
66    math::{self, duration_to_coefficient},
67    Float, Source,
68};
69
70/// Configuration settings for audio limiting.
71///
72/// This struct defines how the limiter behaves, including when to start limiting
73/// (threshold), how gradually to apply it (knee width), and how quickly to respond
74/// to level changes (attack/release times).
75///
76/// # Parameters
77///
78/// * **Threshold** - Level in dB where limiting begins (must be negative, typically -1 to -6 dB)
79/// * **Knee Width** - Range in dB over which limiting gradually increases (wider = smoother)
80/// * **Attack** - Time to respond to level increases (shorter = faster but may distort)
81/// * **Release** - Time to recover after level decreases (longer = smoother)
82///
83/// # Examples
84///
85/// ## Basic Usage
86///
87/// ```rust
88/// use rodio::source::{SineWave, Source, LimitSettings};
89/// use std::time::Duration;
90///
91/// // Use default settings (-1 dB threshold, 4 dB knee, 5ms attack, 100ms release)
92/// let source = SineWave::new(440.0).amplify(2.0);
93/// let limited = source.limit(LimitSettings::default());
94/// ```
95///
96/// ## Custom Settings with Builder Pattern
97///
98/// ```rust
99/// use rodio::source::{SineWave, Source, LimitSettings};
100/// use std::time::Duration;
101///
102/// let source = SineWave::new(440.0).amplify(3.0);
103/// let settings = LimitSettings::new()
104///     .with_threshold(-6.0)                    // Limit peaks above -6dB
105///     .with_knee_width(2.0)                    // 2dB soft knee for smooth limiting
106///     .with_attack(Duration::from_millis(3))   // Fast 3ms attack
107///     .with_release(Duration::from_millis(50)); // 50ms release
108///
109/// let limited = source.limit(settings);
110/// ```
111///
112/// ## Common Adjustments
113///
114/// ```rust
115/// use rodio::source::LimitSettings;
116/// use std::time::Duration;
117///
118/// // More headroom for dynamic content
119/// let conservative = LimitSettings::default()
120///     .with_threshold(-3.0)                    // More headroom
121///     .with_knee_width(6.0);                   // Wide knee for transparency
122///
123/// // Tighter control for broadcast/streaming
124/// let broadcast = LimitSettings::default()
125///     .with_knee_width(2.0)                     // Narrower knee for firmer limiting
126///     .with_attack(Duration::from_millis(3))    // Faster attack
127///     .with_release(Duration::from_millis(50)); // Faster release
128/// ```
129#[derive(Debug, Clone)]
130/// Configuration settings for audio limiting.
131///
132/// # dB vs. dBFS Reference
133///
134/// This limiter uses **dBFS (decibels relative to Full Scale)** for all level measurements:
135/// - **0 dBFS** = maximum possible digital level (1.0 in linear scale)
136/// - **Negative dBFS** = levels below maximum (e.g., -6 dBFS = 0.5 in linear scale)
137/// - **Positive dBFS** = levels above maximum (causes digital clipping)
138///
139/// Unlike absolute dB measurements (dB SPL), dBFS is relative to the digital system's
140/// maximum representable value, making it the standard for digital audio processing.
141///
142/// ## Common dBFS Reference Points
143/// - **0 dBFS**: Digital maximum (clipping threshold)
144/// - **-1 dBFS**: Just below clipping (tight limiting)
145/// - **-3 dBFS**: Moderate headroom (balanced limiting)
146/// - **-6 dBFS**: Generous headroom (gentle limiting)
147/// - **-12 dBFS**: Conservative level (preserves significant dynamics)
148/// - **-20 dBFS**: Very quiet level (background/ambient sounds)
149pub struct LimitSettings {
150    /// Level where limiting begins (dBFS, must be negative).
151    ///
152    /// Specifies the threshold in dBFS where the limiter starts to reduce gain:
153    /// - `-1.0` = limit at -1 dBFS (tight limiting, prevents clipping)
154    /// - `-3.0` = limit at -3 dBFS (balanced approach with headroom)
155    /// - `-6.0` = limit at -6 dBFS (gentle limiting, preserves dynamics)
156    ///
157    /// Values must be negative - positive values would attempt limiting above
158    /// 0 dBFS, which cannot prevent clipping.
159    pub threshold: Float,
160    /// Range over which limiting gradually increases (dB).
161    ///
162    /// Defines the transition zone width in dB where limiting gradually increases
163    /// from no effect to full limiting:
164    /// - `0.0` = hard limiting (abrupt transition)
165    /// - `2.0` = moderate knee (some gradual transition)
166    /// - `4.0` = soft knee (smooth, transparent transition)
167    /// - `8.0` = very soft knee (very gradual, musical transition)
168    pub knee_width: Float,
169    /// Time to respond to level increases
170    pub attack: Duration,
171    /// Time to recover after level decreases
172    pub release: Duration,
173}
174
175impl Default for LimitSettings {
176    fn default() -> Self {
177        Self {
178            threshold: -1.0,                     // -1 dB
179            knee_width: 4.0,                     // 4 dB
180            attack: Duration::from_millis(5),    // 5 ms
181            release: Duration::from_millis(100), // 100 ms
182        }
183    }
184}
185
186impl LimitSettings {
187    /// Creates new limit settings with default values.
188    ///
189    /// Equivalent to [`LimitSettings::default()`].
190    #[inline]
191    pub fn new() -> Self {
192        Self::default()
193    }
194
195    /// Creates settings optimized for dynamic content like music and sound effects.
196    ///
197    /// Designed for content with varying dynamics where you want to preserve
198    /// the natural feel while preventing occasional peaks from clipping.
199    ///
200    /// # Configuration
201    ///
202    /// - **Threshold**: -3.0 dBFS (more headroom than default)
203    /// - **Knee width**: 6.0 dB (wide, transparent transition)
204    /// - **Attack**: 5 ms (default, balanced response)
205    /// - **Release**: 100 ms (default, smooth recovery)
206    ///
207    /// # Use Cases
208    ///
209    /// - Music playback with occasional loud peaks
210    /// - Sound effects that need natural dynamics
211    /// - Content where transparency is more important than tight control
212    /// - Game audio with varying intensity levels
213    ///
214    /// # Examples
215    ///
216    /// ```
217    /// use rodio::source::{SineWave, Source, LimitSettings};
218    ///
219    /// let music = SineWave::new(440.0).amplify(1.5);
220    /// let limited = music.limit(LimitSettings::dynamic_content());
221    /// ```
222    #[inline]
223    pub fn dynamic_content() -> Self {
224        Self::default()
225            .with_threshold(-3.0) // More headroom for dynamics
226            .with_knee_width(6.0) // Wide knee for transparency
227    }
228
229    /// Creates settings optimized for broadcast and streaming applications.
230    ///
231    /// Designed for consistent loudness and reliable peak control in scenarios
232    /// where clipping absolutely cannot occur and consistent levels are critical.
233    ///
234    /// # Configuration
235    ///
236    /// - **Threshold**: -1.0 dBFS (default, tight control)
237    /// - **Knee width**: 2.0 dB (narrower, more decisive limiting)
238    /// - **Attack**: 3 ms (faster response to catch transients)
239    /// - **Release**: 50 ms (faster recovery for consistent levels)
240    ///
241    /// # Use Cases
242    ///
243    /// - Live streaming where clipping would be catastrophic
244    /// - Broadcast audio that must meet loudness standards
245    /// - Voice chat applications requiring consistent levels
246    /// - Podcast production for consistent listening experience
247    /// - Game voice communication systems
248    ///
249    /// # Examples
250    ///
251    /// ```
252    /// use rodio::source::{SineWave, Source, LimitSettings};
253    ///
254    /// let voice_chat = SineWave::new(440.0).amplify(2.0);
255    /// let limited = voice_chat.limit(LimitSettings::broadcast());
256    /// ```
257    #[inline]
258    pub fn broadcast() -> Self {
259        Self::default()
260            .with_knee_width(2.0) // Narrower knee for decisive limiting
261            .with_attack(Duration::from_millis(3)) // Faster attack for transients
262            .with_release(Duration::from_millis(50)) // Faster recovery for consistency
263    }
264
265    /// Creates settings optimized for mastering and final audio production.
266    ///
267    /// Designed for the final stage of audio production where tight peak control
268    /// is needed while maintaining audio quality and preventing any clipping.
269    ///
270    /// # Configuration
271    ///
272    /// - **Threshold**: -0.5 dBFS (very tight, maximum loudness)
273    /// - **Knee width**: 1.0 dB (narrow, precise control)
274    /// - **Attack**: 1 ms (very fast, catches all transients)
275    /// - **Release**: 200 ms (slower, maintains natural envelope)
276    ///
277    /// # Use Cases
278    ///
279    /// - Final mastering stage for tight peak control
280    /// - Preparing audio for streaming platforms (after loudness processing)
281    /// - Album mastering where consistent peak levels are critical
282    /// - Audio post-production for film/video
283    ///
284    /// # Examples
285    ///
286    /// ```
287    /// use rodio::source::{SineWave, Source, LimitSettings};
288    ///
289    /// let master_track = SineWave::new(440.0).amplify(3.0);
290    /// let mastered = master_track.limit(LimitSettings::mastering());
291    /// ```
292    #[inline]
293    pub fn mastering() -> Self {
294        Self {
295            threshold: -0.5,                     // Very tight for peak control
296            knee_width: 1.0,                     // Narrow knee for precise control
297            attack: Duration::from_millis(1),    // Very fast attack
298            release: Duration::from_millis(200), // Slower release for natural envelope
299        }
300    }
301
302    /// Creates settings optimized for live performance and real-time applications.
303    ///
304    /// Designed for scenarios where low latency is critical and the limiter
305    /// must respond quickly to protect equipment and audiences.
306    ///
307    /// # Configuration
308    ///
309    /// - **Threshold**: -2.0 dBFS (some headroom for safety)
310    /// - **Knee width**: 3.0 dB (moderate, good compromise)
311    /// - **Attack**: 0.5 ms (extremely fast for protection)
312    /// - **Release**: 30 ms (fast recovery for live feel)
313    ///
314    /// # Use Cases
315    ///
316    /// - Live concert sound reinforcement
317    /// - DJ mixing and live electronic music
318    /// - Real-time audio processing where latency matters
319    /// - Equipment protection in live settings
320    /// - Interactive audio applications and games
321    ///
322    /// # Examples
323    ///
324    /// ```
325    /// use rodio::source::{SineWave, Source, LimitSettings};
326    ///
327    /// let live_input = SineWave::new(440.0).amplify(2.5);
328    /// let protected = live_input.limit(LimitSettings::live_performance());
329    /// ```
330    #[inline]
331    pub fn live_performance() -> Self {
332        Self {
333            threshold: -2.0,                    // Some headroom for safety
334            knee_width: 3.0,                    // Moderate knee
335            attack: Duration::from_micros(500), // Extremely fast for protection
336            release: Duration::from_millis(30), // Fast recovery for live feel
337        }
338    }
339
340    /// Creates settings optimized for gaming and interactive audio.
341    ///
342    /// Designed for games where audio levels can vary dramatically between
343    /// quiet ambient sounds and loud action sequences, requiring responsive
344    /// limiting that maintains immersion.
345    ///
346    /// # Configuration
347    ///
348    /// - **Threshold**: -3.0 dBFS (balanced headroom for dynamic range)
349    /// - **Knee width**: 3.0 dB (moderate transition for natural feel)
350    /// - **Attack**: 2 ms (fast enough for sound effects, not harsh)
351    /// - **Release**: 75 ms (quick recovery for interactive responsiveness)
352    ///
353    /// # Use Cases
354    ///
355    /// - Game audio mixing for consistent player experience
356    /// - Interactive audio applications requiring dynamic response
357    /// - VR/AR audio where sudden loud sounds could be jarring
358    /// - Mobile games needing battery-efficient processing
359    /// - Streaming gameplay audio for viewers
360    ///
361    /// # Examples
362    ///
363    /// ```
364    /// use rodio::source::{SineWave, Source, LimitSettings};
365    ///
366    /// let game_audio = SineWave::new(440.0).amplify(2.0);
367    /// let limited = game_audio.limit(LimitSettings::gaming());
368    /// ```
369    #[inline]
370    pub fn gaming() -> Self {
371        Self {
372            threshold: -3.0,                    // Balanced headroom for dynamics
373            knee_width: 3.0,                    // Moderate for natural feel
374            attack: Duration::from_millis(2),   // Fast but not harsh
375            release: Duration::from_millis(75), // Quick for interactivity
376        }
377    }
378
379    /// Sets the threshold level where limiting begins.
380    ///
381    /// # Arguments
382    ///
383    /// * `threshold` - Level in dBFS where limiting starts (must be negative)
384    ///   - `-1.0` = limiting starts at -1 dBFS (tight limiting, prevents clipping)
385    ///   - `-3.0` = limiting starts at -3 dBFS (balanced approach with headroom)
386    ///   - `-6.0` = limiting starts at -6 dBFS (gentle limiting, preserves dynamics)
387    ///   - `-12.0` = limiting starts at -12 dBFS (very aggressive, significantly reduces dynamics)
388    ///
389    /// # dBFS Context
390    ///
391    /// Remember that 0 dBFS is the digital maximum. Negative dBFS values represent
392    /// levels below this maximum:
393    /// - `-1 dBFS` ≈ 89% of maximum amplitude (very loud, limiting triggers late)
394    /// - `-3 dBFS` ≈ 71% of maximum amplitude (loud, moderate limiting)
395    /// - `-6 dBFS` ≈ 50% of maximum amplitude (moderate, gentle limiting)
396    /// - `-12 dBFS` ≈ 25% of maximum amplitude (quiet, aggressive limiting)
397    ///
398    /// Lower thresholds (more negative) trigger limiting earlier and reduce dynamics more.
399    /// Only negative values are meaningful - positive values would attempt limiting
400    /// above 0 dBFS, which cannot prevent clipping.
401    #[inline]
402    pub fn with_threshold(mut self, threshold: Float) -> Self {
403        self.threshold = threshold;
404        self
405    }
406
407    /// Sets the knee width - range over which limiting gradually increases.
408    ///
409    /// # Arguments
410    ///
411    /// * `knee_width` - Range in dB over which limiting transitions from off to full effect
412    ///   - `0.0` dB = hard knee (abrupt limiting, may sound harsh)
413    ///   - `1.0-2.0` dB = moderate knee (noticeable but controlled limiting)
414    ///   - `4.0` dB = soft knee (smooth, transparent limiting) \[default\]
415    ///   - `6.0-8.0` dB = very soft knee (very gradual, musical limiting)
416    ///
417    /// # How Knee Width Works
418    ///
419    /// The knee creates a transition zone around the threshold. For example, with
420    /// `threshold = -3.0` dBFS and `knee_width = 4.0` dB:
421    /// - No limiting below -5 dBFS (threshold - knee_width/2)
422    /// - Gradual limiting from -5 dBFS to -1 dBFS
423    /// - Full limiting above -1 dBFS (threshold + knee_width/2)
424    #[inline]
425    pub fn with_knee_width(mut self, knee_width: Float) -> Self {
426        self.knee_width = knee_width;
427        self
428    }
429
430    /// Sets the attack time - how quickly the limiter responds to level increases.
431    ///
432    /// # Arguments
433    ///
434    /// * `attack` - Time duration for the limiter to react to peaks
435    ///   - Shorter (1-5 ms) = faster response, may cause distortion
436    ///   - Longer (10-20 ms) = smoother sound, may allow brief overshoots
437    #[inline]
438    pub fn with_attack(mut self, attack: Duration) -> Self {
439        self.attack = attack;
440        self
441    }
442
443    /// Sets the release time - how quickly the limiter recovers after level decreases.
444    ///
445    /// # Arguments
446    ///
447    /// * `release` - Time duration for the limiter to stop limiting
448    ///   - Shorter (10-50 ms) = quick recovery, may sound pumping
449    ///   - Longer (100-500 ms) = smooth recovery, more natural sound
450    #[inline]
451    pub fn with_release(mut self, release: Duration) -> Self {
452        self.release = release;
453        self
454    }
455}
456
457/// Creates a limiter that processes the input audio source.
458///
459/// This function applies the specified limiting settings to control audio peaks.
460/// The limiter uses feedforward processing with configurable attack/release times
461/// and soft-knee characteristics for natural-sounding dynamic range control.
462///
463/// # Arguments
464///
465/// * `input` - Audio source to process
466/// * `settings` - Limiter configuration (threshold, knee, timing)
467///
468/// # Returns
469///
470/// A [`Limit`] source that applies the limiting to the input audio.
471///
472/// # Example
473///
474/// ```rust
475/// use rodio::source::{SineWave, Source, LimitSettings};
476///
477/// let source = SineWave::new(440.0).amplify(2.0);
478/// let settings = LimitSettings::default().with_threshold(-6.0);
479/// let limited = source.limit(settings);
480/// ```
481pub(crate) fn limit<I: Source>(input: I, settings: LimitSettings) -> Limit<I> {
482    let sample_rate = input.sample_rate();
483    let attack = duration_to_coefficient(settings.attack, sample_rate);
484    let release = duration_to_coefficient(settings.release, sample_rate);
485    let channels = input.channels().get() as usize;
486
487    let base = LimitBase::new(settings.threshold, settings.knee_width, attack, release);
488
489    let inner = match channels {
490        1 => LimitInner::Mono(LimitMono {
491            input,
492            base,
493            limiter_integrator: 0.0,
494            limiter_peak: 0.0,
495        }),
496        2 => LimitInner::Stereo(LimitStereo {
497            input,
498            base,
499            limiter_integrators: [0.0; 2],
500            limiter_peaks: [0.0; 2],
501            position: 0,
502        }),
503        n => LimitInner::MultiChannel(LimitMulti {
504            input,
505            base,
506            limiter_integrators: vec![0.0; n],
507            limiter_peaks: vec![0.0; n],
508            position: 0,
509        }),
510    };
511
512    Limit(inner)
513}
514
515/// A source filter that applies audio limiting to prevent peaks from exceeding a threshold.
516///
517/// This filter reduces the amplitude of audio signals that exceed the configured threshold
518/// level, helping to prevent clipping and maintain consistent output levels. The limiter
519/// automatically adapts to mono, stereo, or multi-channel audio sources by using the
520/// appropriate internal implementation.
521///
522/// # How It Works
523///
524/// The limiter detects peaks in each audio channel independently but applies gain reduction
525/// uniformly across all channels. This preserves stereo imaging while ensuring that loud
526/// peaks in any channel are controlled. The limiting uses:
527///
528/// - **Soft-knee compression**: Gradual gain reduction around the threshold
529/// - **Attack/release timing**: Configurable response speed to level changes
530/// - **Peak detection**: Tracks maximum levels across all channels
531/// - **Gain smoothing**: Prevents audible artifacts from rapid gain changes
532///
533/// # Created By
534///
535/// Use [`Source::limit()`] with [`LimitSettings`] to create a `Limit` source:
536///
537/// ```
538/// use rodio::source::{SineWave, Source};
539/// use rodio::source::LimitSettings;
540/// use std::time::Duration;
541///
542/// let source = SineWave::new(440.0).amplify(2.0);
543/// let settings = LimitSettings::default()
544///     .with_threshold(-6.0)  // -6 dBFS threshold
545///     .with_attack(Duration::from_millis(5))
546///     .with_release(Duration::from_millis(100));
547/// let limited = source.limit(settings);
548/// ```
549///
550/// # Performance
551///
552/// The limiter automatically selects the most efficient implementation based on channel count:
553/// - **Mono**: Single-channel optimized processing
554/// - **Stereo**: Two-channel optimized with interleaved processing
555/// - **Multi-channel**: Generic implementation for 3+ channels
556///
557/// # Channel Count Stability
558///
559/// **Important**: The limiter is optimized for sources with fixed channel counts.
560/// Most audio files (music, podcasts, etc.) maintain constant channel counts,
561/// making this optimization safe and beneficial.
562///
563/// If the underlying source changes channel count mid-stream (rare), the limiter
564/// will continue to function but performance may be degraded. For such cases,
565/// recreate the limiter when the channel count changes.
566///
567/// # Type Parameters
568///
569/// * `I` - The input audio source type that implements [`Source`]
570#[derive(Clone, Debug)]
571pub struct Limit<I>(LimitInner<I>)
572where
573    I: Source;
574
575impl<I> Source for Limit<I>
576where
577    I: Source,
578{
579    #[inline]
580    fn current_span_len(&self) -> Option<usize> {
581        self.0.current_span_len()
582    }
583
584    #[inline]
585    fn sample_rate(&self) -> SampleRate {
586        self.0.sample_rate()
587    }
588
589    #[inline]
590    fn channels(&self) -> ChannelCount {
591        self.0.channels()
592    }
593
594    #[inline]
595    fn total_duration(&self) -> Option<Duration> {
596        self.0.total_duration()
597    }
598
599    #[inline]
600    fn try_seek(&mut self, position: Duration) -> Result<(), SeekError> {
601        self.0.try_seek(position)
602    }
603}
604
605impl<I> Limit<I>
606where
607    I: Source,
608{
609    /// Returns a reference to the inner audio source.
610    ///
611    /// This allows access to the original source's properties and methods without
612    /// consuming the limiter. Useful for inspecting source characteristics like
613    /// sample rate, channels, or duration.
614    ///
615    /// Useful for inspecting source properties without consuming the filter.
616    #[inline]
617    pub fn inner(&self) -> &I {
618        self.0.inner()
619    }
620
621    /// Returns a mutable reference to the inner audio source.
622    ///
623    /// This allows modification of the original source while keeping the limiter
624    /// wrapper. Essential for operations like seeking that need to modify the
625    /// underlying source.
626    #[inline]
627    pub fn inner_mut(&mut self) -> &mut I {
628        self.0.inner_mut()
629    }
630
631    /// Consumes the limiter and returns the inner audio source.
632    ///
633    /// This dismantles the limiter wrapper to extract the original source,
634    /// allowing the audio pipeline to continue without limiting overhead.
635    /// Useful when limiting is no longer needed but the source should continue.
636    #[inline]
637    pub fn into_inner(self) -> I {
638        self.0.into_inner()
639    }
640}
641
642impl<I> Iterator for Limit<I>
643where
644    I: Source,
645{
646    type Item = I::Item;
647
648    /// Provides the next limited sample.
649    #[inline]
650    fn next(&mut self) -> Option<Self::Item> {
651        self.0.next()
652    }
653
654    /// Provides size hints from the inner limiter.
655    #[inline]
656    fn size_hint(&self) -> (usize, Option<usize>) {
657        self.0.size_hint()
658    }
659}
660
661/// Internal limiter implementation that adapts to different channel configurations.
662///
663/// This enum is private and automatically selects the most efficient implementation
664/// based on the number of audio channels:
665/// - **Mono**: Single-channel optimized processing with minimal state
666/// - **Stereo**: Two-channel optimized with fixed-size arrays for performance
667/// - **Multi-channel**: Generic implementation using vectors for arbitrary channel counts
668///
669/// The enum is wrapped by the public [`Limit`] struct to provide a clean API while
670/// maintaining internal optimization flexibility.
671///
672/// # Channel-Specific Optimizations
673///
674/// - **Mono**: Direct processing without channel indexing overhead
675/// - **Stereo**: Fixed-size arrays avoid heap allocation and provide cache efficiency
676/// - **Multi-channel**: Dynamic vectors handle surround sound and custom configurations
677///
678/// # Type Parameters
679///
680/// * `I` - The input audio source type that implements [`Source`]
681#[derive(Clone, Debug)]
682enum LimitInner<I: Source>
683where
684    I: Source,
685{
686    /// Mono channel limiter
687    Mono(LimitMono<I>),
688    /// Stereo channel limiter
689    Stereo(LimitStereo<I>),
690    /// Multi-channel limiter for arbitrary channel counts
691    MultiChannel(LimitMulti<I>),
692}
693
694/// Common parameters and processing logic shared across all limiter variants.
695///
696/// Handles:
697/// * Parameter storage (threshold, knee width, attack/release coefficients)
698/// * Per-channel state updates for peak detection
699/// * Gain computation through soft-knee limiting
700#[derive(Clone, Debug)]
701struct LimitBase {
702    /// Level where limiting begins (dB)
703    threshold: Float,
704    /// Width of the soft-knee region (dB)
705    knee_width: Float,
706    /// Inverse of 8 times the knee width (precomputed for efficiency)
707    inv_knee_8: Float,
708    /// Attack time constant (ms)
709    attack: Float,
710    /// Release time constant (ms)
711    release: Float,
712}
713
714/// Mono channel limiter optimized for single-channel processing.
715///
716/// This variant is automatically selected by [`Limit`] for mono audio sources.
717/// It uses minimal state (single integrator and peak detector) for optimal
718/// performance with single-channel audio.
719///
720/// # Internal Use
721///
722/// This struct is used internally by [`LimitInner::Mono`] and is not intended
723/// for direct construction. Use [`Source::limit()`] instead.
724#[derive(Clone, Debug)]
725pub struct LimitMono<I> {
726    /// Input audio source
727    input: I,
728    /// Common limiter parameters
729    base: LimitBase,
730    /// Peak detection integrator state
731    limiter_integrator: Float,
732    /// Peak detection state
733    limiter_peak: Float,
734}
735
736/// Stereo channel limiter with optimized two-channel processing.
737///
738/// This variant is automatically selected by [`Limit`] for stereo audio sources.
739/// It uses fixed-size arrays instead of vectors for better cache performance
740/// and avoids heap allocation overhead common in stereo audio processing.
741///
742/// # Performance
743///
744/// The fixed arrays and channel position tracking provide optimal performance
745/// for interleaved stereo sample processing, avoiding the dynamic allocation
746/// overhead of the multi-channel variant.
747///
748/// # Internal Use
749///
750/// This struct is used internally by [`LimitInner::Stereo`] and is not intended
751/// for direct construction. Use [`Source::limit()`] instead.
752#[derive(Clone, Debug)]
753pub struct LimitStereo<I> {
754    /// Input audio source
755    input: I,
756    /// Common limiter parameters
757    base: LimitBase,
758    /// Peak detection integrator states for left and right channels
759    limiter_integrators: [Float; 2],
760    /// Peak detection states for left and right channels
761    limiter_peaks: [Float; 2],
762    /// Current channel position (0 = left, 1 = right)
763    position: u8,
764}
765
766/// Generic multi-channel limiter for surround sound or other configurations.
767///
768/// This variant is automatically selected by [`Limit`] for audio sources with
769/// 3 or more channels. It uses dynamic vectors to handle arbitrary channel
770/// counts, making it suitable for surround sound (5.1, 7.1) and other
771/// multi-channel audio configurations.
772///
773/// # Flexibility vs Performance
774///
775/// While this variant has slightly more overhead than the mono/stereo variants
776/// due to vector allocation and dynamic indexing, it provides the flexibility
777/// needed for complex audio setups while maintaining good performance.
778///
779/// # Internal Use
780///
781/// This struct is used internally by [`LimitInner::MultiChannel`] and is not
782/// intended for direct construction. Use [`Source::limit()`] instead.
783#[derive(Clone, Debug)]
784pub struct LimitMulti<I> {
785    /// Input audio source
786    input: I,
787    /// Common limiter parameters
788    base: LimitBase,
789    /// Peak detector integrator states (one per channel)
790    limiter_integrators: Vec<Float>,
791    /// Peak detector states (one per channel)
792    limiter_peaks: Vec<Float>,
793    /// Current channel position (0 to channels-1)
794    position: usize,
795}
796
797/// Computes the gain reduction amount in dB based on input level.
798///
799/// Implements soft-knee compression with three regions:
800/// 1. Below threshold - knee_width: No compression (returns 0.0)
801/// 2. Within knee region: Gradual compression with quadratic curve
802/// 3. Above threshold + knee_width: Linear compression
803///
804/// Optimized for the most common case where samples are below threshold and no limiting is needed
805/// (returns `0.0` early).
806///
807/// # Arguments
808///
809/// * `sample` - Input sample value (with initial gain applied)
810/// * `threshold` - Level where limiting begins (dB)
811/// * `knee_width` - Width of soft knee region (dB)
812/// * `inv_knee_8` - Precomputed value: 1.0 / (8.0 * knee_width) for efficiency
813///
814/// # Returns
815///
816/// Amount of gain reduction to apply in dB
817#[inline]
818fn process_sample(
819    sample: Sample,
820    threshold: Float,
821    knee_width: Float,
822    inv_knee_8: Float,
823) -> Sample {
824    // Add slight DC offset. Some samples are silence, which is -inf dB and gets the limiter stuck.
825    // Adding a small positive offset prevents this.
826    let bias_db = math::linear_to_db(sample.abs() + Sample::MIN_POSITIVE) - threshold;
827    let knee_boundary_db = bias_db * 2.0;
828    if knee_boundary_db < -knee_width {
829        0.0
830    } else if knee_boundary_db.abs() <= knee_width {
831        // Faster than powi(2)
832        let x = knee_boundary_db + knee_width;
833        x * x * inv_knee_8
834    } else {
835        bias_db
836    }
837}
838
839impl LimitBase {
840    fn new(threshold: Float, knee_width: Float, attack: Float, release: Float) -> Self {
841        let inv_knee_8 = 1.0 / (8.0 * knee_width);
842        Self {
843            threshold,
844            knee_width,
845            inv_knee_8,
846            attack,
847            release,
848        }
849    }
850
851    /// Updates the channel's envelope detection state.
852    ///
853    /// For each channel, processes:
854    /// 1. Initial gain and dB conversion
855    /// 2. Soft-knee limiting calculation
856    /// 3. Envelope detection with attack/release filtering
857    /// 4. Peak level tracking
858    ///
859    /// The envelope detection uses a dual-stage approach:
860    /// - First stage: Max of current signal and smoothed release
861    /// - Second stage: Attack smoothing of the peak detector output
862    ///
863    /// Note: Only updates state, gain application is handled by the variant implementations to
864    /// allow for coupled gain reduction across channels.
865    #[must_use]
866    #[inline]
867    fn process_channel(&self, sample: Sample, integrator: &mut Float, peak: &mut Float) -> Sample {
868        // step 1-4: half-wave rectification and conversion into dB, and gain computer with soft
869        // knee and subtractor
870        let limiter_db = process_sample(sample, self.threshold, self.knee_width, self.inv_knee_8);
871
872        // step 5: smooth, decoupled peak detector
873        *integrator = Float::max(
874            limiter_db,
875            self.release * *integrator + (1.0 - self.release) * limiter_db,
876        );
877        *peak = self.attack * *peak + (1.0 - self.attack) * *integrator;
878
879        sample
880    }
881}
882
883impl<I> LimitMono<I>
884where
885    I: Source,
886{
887    /// Processes the next mono sample through the limiter.
888    ///
889    /// Single channel implementation with direct state updates.
890    #[inline]
891    fn process_next(&mut self, sample: I::Item) -> I::Item {
892        let processed =
893            self.base
894                .process_channel(sample, &mut self.limiter_integrator, &mut self.limiter_peak);
895
896        // steps 6-8: conversion into level and multiplication into gain stage
897        processed * math::db_to_linear(-self.limiter_peak)
898    }
899}
900
901impl<I> LimitStereo<I>
902where
903    I: Source,
904{
905    /// Processes the next stereo sample through the limiter.
906    ///
907    /// Uses efficient channel position tracking with XOR toggle and direct array access for state
908    /// updates.
909    #[inline]
910    fn process_next(&mut self, sample: I::Item) -> I::Item {
911        let channel = self.position as usize;
912        self.position ^= 1;
913
914        let processed = self.base.process_channel(
915            sample,
916            &mut self.limiter_integrators[channel],
917            &mut self.limiter_peaks[channel],
918        );
919
920        // steps 6-8: conversion into level and multiplication into gain stage. Find maximum peak
921        // across both channels to couple the gain and maintain stereo imaging.
922        let max_peak = Float::max(self.limiter_peaks[0], self.limiter_peaks[1]);
923        processed * math::db_to_linear(-max_peak)
924    }
925}
926
927impl<I> LimitMulti<I>
928where
929    I: Source,
930{
931    /// Processes the next multi-channel sample through the limiter.
932    ///
933    /// Generic implementation supporting arbitrary channel counts with `Vec`-based state storage.
934    #[inline]
935    fn process_next(&mut self, sample: I::Item) -> I::Item {
936        let channel = self.position;
937        self.position = (self.position + 1) % self.limiter_integrators.len();
938
939        let processed = self.base.process_channel(
940            sample,
941            &mut self.limiter_integrators[channel],
942            &mut self.limiter_peaks[channel],
943        );
944
945        // steps 6-8: conversion into level and multiplication into gain stage. Find maximum peak
946        // across all channels to couple the gain and maintain multi-channel imaging.
947        let max_peak = self
948            .limiter_peaks
949            .iter()
950            .fold(0.0, |max, &peak| Float::max(max, peak));
951        processed * math::db_to_linear(-max_peak)
952    }
953}
954
955impl<I> LimitInner<I>
956where
957    I: Source,
958{
959    /// Returns a reference to the inner audio source.
960    ///
961    /// This allows access to the original source's properties and methods without
962    /// consuming the limiter. Useful for inspecting source characteristics like
963    /// sample rate, channels, or duration.
964    #[inline]
965    pub fn inner(&self) -> &I {
966        match self {
967            LimitInner::Mono(mono) => &mono.input,
968            LimitInner::Stereo(stereo) => &stereo.input,
969            LimitInner::MultiChannel(multi) => &multi.input,
970        }
971    }
972
973    /// Returns a mutable reference to the inner audio source.
974    ///
975    /// This allows modification of the original source while keeping the limiter
976    /// wrapper. Essential for operations like seeking that need to modify the
977    /// underlying source.
978    #[inline]
979    pub fn inner_mut(&mut self) -> &mut I {
980        match self {
981            LimitInner::Mono(mono) => &mut mono.input,
982            LimitInner::Stereo(stereo) => &mut stereo.input,
983            LimitInner::MultiChannel(multi) => &mut multi.input,
984        }
985    }
986
987    /// Consumes the filter and returns the inner audio source.
988    ///
989    /// This dismantles the limiter wrapper to extract the original source,
990    /// allowing the audio pipeline to continue without limiting overhead.
991    /// Useful when limiting is no longer needed but the source should continue.
992    #[inline]
993    pub fn into_inner(self) -> I {
994        match self {
995            LimitInner::Mono(mono) => mono.input,
996            LimitInner::Stereo(stereo) => stereo.input,
997            LimitInner::MultiChannel(multi) => multi.input,
998        }
999    }
1000}
1001
1002impl<I> Iterator for LimitInner<I>
1003where
1004    I: Source,
1005{
1006    type Item = I::Item;
1007
1008    /// Provides the next processed sample.
1009    ///
1010    /// Routes processing to the appropriate channel-specific implementation:
1011    /// * `Mono`: Direct single-channel processing
1012    /// * `Stereo`: Optimized two-channel processing
1013    /// * `MultiChannel`: Generic multi-channel processing
1014    ///
1015    /// # Channel Count Changes
1016    ///
1017    /// **Important**: This limiter assumes a fixed channel count determined at creation time.
1018    /// Most audio sources (files, streams) maintain constant channel counts, making this
1019    /// assumption safe for typical usage.
1020    ///
1021    /// If the underlying source changes its channel count mid-stream (rare), the limiter
1022    /// will continue to function but may experience timing and imaging issues. For optimal
1023    /// performance, recreate the limiter when the channel count changes.
1024    #[inline]
1025    fn next(&mut self) -> Option<Self::Item> {
1026        match self {
1027            LimitInner::Mono(mono) => {
1028                let sample = mono.input.next()?;
1029                Some(mono.process_next(sample))
1030            }
1031            LimitInner::Stereo(stereo) => {
1032                let sample = stereo.input.next()?;
1033                Some(stereo.process_next(sample))
1034            }
1035            LimitInner::MultiChannel(multi) => {
1036                let sample = multi.input.next()?;
1037                Some(multi.process_next(sample))
1038            }
1039        }
1040    }
1041
1042    /// Provides size hints from the inner source.
1043    ///
1044    /// Delegates directly to the source to maintain accurate collection sizing.
1045    /// Used by collection operations for optimization.
1046    #[inline]
1047    fn size_hint(&self) -> (usize, Option<usize>) {
1048        self.inner().size_hint()
1049    }
1050}
1051
1052impl<I> Source for LimitInner<I>
1053where
1054    I: Source,
1055{
1056    /// Returns the number of samples in the current audio frame.
1057    ///
1058    /// Delegates to inner source to maintain frame alignment.
1059    #[inline]
1060    fn current_span_len(&self) -> Option<usize> {
1061        self.inner().current_span_len()
1062    }
1063
1064    /// Returns the number of channels in the audio stream.
1065    ///
1066    /// Channel count determines which limiter variant is used:
1067    /// * 1: Mono
1068    /// * 2: Stereo
1069    /// * >2: MultiChannel
1070    #[inline]
1071    fn channels(&self) -> ChannelCount {
1072        self.inner().channels()
1073    }
1074
1075    /// Returns the audio sample rate in Hz.
1076    #[inline]
1077    fn sample_rate(&self) -> SampleRate {
1078        self.inner().sample_rate()
1079    }
1080
1081    /// Returns the total duration of the audio.
1082    ///
1083    /// Returns None for streams without known duration.
1084    #[inline]
1085    fn total_duration(&self) -> Option<Duration> {
1086        self.inner().total_duration()
1087    }
1088
1089    /// Attempts to seek to the specified position.
1090    ///
1091    /// Resets limiter state to prevent artifacts after seeking:
1092    /// * Mono: Direct reset of integrator and peak values
1093    /// * Stereo: Efficient array fill for both channels
1094    /// * `MultiChannel`: Resets all channel states via fill
1095    ///
1096    /// # Arguments
1097    ///
1098    /// * `target` - Position to seek to
1099    ///
1100    /// # Errors
1101    ///
1102    /// Returns error if the underlying source fails to seek
1103    fn try_seek(&mut self, target: Duration) -> Result<(), SeekError> {
1104        self.inner_mut().try_seek(target)?;
1105
1106        match self {
1107            LimitInner::Mono(mono) => {
1108                mono.limiter_integrator = 0.0;
1109                mono.limiter_peak = 0.0;
1110            }
1111            LimitInner::Stereo(stereo) => {
1112                stereo.limiter_integrators.fill(0.0);
1113                stereo.limiter_peaks.fill(0.0);
1114            }
1115            LimitInner::MultiChannel(multi) => {
1116                multi.limiter_integrators.fill(0.0);
1117                multi.limiter_peaks.fill(0.0);
1118            }
1119        }
1120
1121        Ok(())
1122    }
1123}
1124
1125#[cfg(test)]
1126mod tests {
1127    use super::*;
1128    use crate::buffer::SamplesBuffer;
1129    use crate::math::nz;
1130    use crate::source::{SineWave, Source};
1131    use std::time::Duration;
1132
1133    fn create_test_buffer(
1134        samples: Vec<Sample>,
1135        channels: ChannelCount,
1136        sample_rate: SampleRate,
1137    ) -> SamplesBuffer {
1138        SamplesBuffer::new(channels, sample_rate, samples)
1139    }
1140
1141    #[test]
1142    fn test_limiter_creation() {
1143        // Test mono
1144        let buffer = create_test_buffer(vec![0.5, 0.8, 1.0, 0.3], nz!(1), nz!(44100));
1145        let limiter = limit(buffer, LimitSettings::default());
1146        assert_eq!(limiter.channels(), nz!(1));
1147        assert_eq!(limiter.sample_rate(), nz!(44100));
1148        matches!(limiter.0, LimitInner::Mono(_));
1149
1150        // Test stereo
1151        let buffer = create_test_buffer(
1152            vec![0.5, 0.8, 1.0, 0.3, 0.2, 0.6, 0.9, 0.4],
1153            nz!(2),
1154            nz!(44100),
1155        );
1156        let limiter = limit(buffer, LimitSettings::default());
1157        assert_eq!(limiter.channels(), nz!(2));
1158        matches!(limiter.0, LimitInner::Stereo(_));
1159
1160        // Test multichannel
1161        let buffer = create_test_buffer(vec![0.5; 12], nz!(3), nz!(44100));
1162        let limiter = limit(buffer, LimitSettings::default());
1163        assert_eq!(limiter.channels(), nz!(3));
1164        matches!(limiter.0, LimitInner::MultiChannel(_));
1165    }
1166}