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}