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}