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