-
Notifications
You must be signed in to change notification settings - Fork 276
feat: add audio peak limiting with configurable settings #751
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
f28794b
feat: add audio peak limiting with configurable settings
roderickvd e3ec6a0
refactor: limiter API, add presets, and move dB conversion to math mo…
roderickvd 9460888
docs: shorten code comments for max error constants in tests
roderickvd e61dce9
style: format doc comments and improve test formatting for clarity
roderickvd 77a246b
docs: document channel count stability and dynamic changes in limiter
roderickvd e8ded7b
docs: remove detailed channel count stability docs from limiter module
roderickvd File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
//! Example demonstrating the new LimitSettings API for audio limiting. | ||
//! | ||
//! This example shows how to use the LimitSettings struct with the builder | ||
//! to configure audio limiting parameters. | ||
|
||
use rodio::source::{LimitSettings, SineWave, Source}; | ||
use std::time::Duration; | ||
|
||
fn main() { | ||
println!("Example 1: Default LimitSettings"); | ||
let default_limiting = LimitSettings::default(); | ||
println!(" Threshold: {} dB", default_limiting.threshold); | ||
println!(" Knee width: {} dB", default_limiting.knee_width); | ||
println!(" Attack: {:?}", default_limiting.attack); | ||
println!(" Release: {:?}", default_limiting.release); | ||
println!(); | ||
|
||
println!("Example 2: Custom LimitSettings with builder pattern"); | ||
let custom_limiting = LimitSettings::new() | ||
.with_threshold(-3.0) | ||
.with_knee_width(2.0) | ||
.with_attack(Duration::from_millis(10)) | ||
.with_release(Duration::from_millis(50)); | ||
|
||
println!(" Threshold: {} dB", custom_limiting.threshold); | ||
println!(" Knee width: {} dB", custom_limiting.knee_width); | ||
println!(" Attack: {:?}", custom_limiting.attack); | ||
println!(" Release: {:?}", custom_limiting.release); | ||
println!(); | ||
|
||
println!("Example 3: Applying limiter to a sine wave with default settings"); | ||
|
||
// Create a sine wave at 440 Hz | ||
let sine_wave = SineWave::new(440.0) | ||
.amplify(2.0) // Amplify to cause limiting | ||
.take_duration(Duration::from_millis(100)); | ||
|
||
// Apply limiting with default settings (simplest usage) | ||
let limited_wave = sine_wave.limit(LimitSettings::default()); | ||
|
||
// Collect some samples to demonstrate | ||
let samples: Vec<f32> = limited_wave.take(100).collect(); | ||
println!(" Generated {} limited samples", samples.len()); | ||
|
||
// Show peak reduction | ||
let max_sample = samples.iter().fold(0.0f32, |acc, &x| acc.max(x.abs())); | ||
println!(" Peak amplitude after limiting: {:.3}", max_sample); | ||
println!(); | ||
|
||
println!("Example 4: Custom settings with builder pattern"); | ||
|
||
// Create another sine wave for custom limiting | ||
let sine_wave2 = SineWave::new(880.0) | ||
.amplify(1.8) | ||
.take_duration(Duration::from_millis(50)); | ||
|
||
// Apply the custom settings from Example 2 | ||
let custom_limited = sine_wave2.limit(custom_limiting); | ||
let custom_samples: Vec<f32> = custom_limited.take(50).collect(); | ||
println!( | ||
" Generated {} samples with custom settings", | ||
custom_samples.len() | ||
); | ||
println!(); | ||
|
||
println!("Example 5: Comparing different limiting scenarios"); | ||
|
||
let gentle_limiting = LimitSettings::default() | ||
.with_threshold(-6.0) // Higher threshold (less limiting) | ||
.with_knee_width(8.0) // Wide knee (softer) | ||
.with_attack(Duration::from_millis(20)) // Slower attack | ||
.with_release(Duration::from_millis(200)); // Slower release | ||
|
||
let aggressive_limiting = LimitSettings::default() | ||
.with_threshold(-1.0) // Lower threshold (more limiting) | ||
.with_knee_width(1.0) // Narrow knee (harder) | ||
.with_attack(Duration::from_millis(2)) // Fast attack | ||
.with_release(Duration::from_millis(20)); // Fast release | ||
|
||
println!(" Gentle limiting:"); | ||
println!( | ||
" Threshold: {} dB, Knee: {} dB", | ||
gentle_limiting.threshold, gentle_limiting.knee_width | ||
); | ||
println!( | ||
" Attack: {:?}, Release: {:?}", | ||
gentle_limiting.attack, gentle_limiting.release | ||
); | ||
|
||
println!(" Aggressive limiting:"); | ||
println!( | ||
" Threshold: {} dB, Knee: {} dB", | ||
aggressive_limiting.threshold, aggressive_limiting.knee_width | ||
); | ||
println!( | ||
" Attack: {:?}, Release: {:?}", | ||
aggressive_limiting.attack, aggressive_limiting.release | ||
); | ||
println!(); | ||
|
||
println!("Example 6: Limiting with -6dB threshold"); | ||
|
||
// Create a sine wave that will definitely trigger limiting | ||
const AMPLITUDE: f32 = 2.5; // High amplitude to ensure limiting occurs | ||
let test_sine = SineWave::new(440.0) | ||
.amplify(AMPLITUDE) | ||
.take_duration(Duration::from_millis(100)); // 100ms = ~4410 samples | ||
|
||
// Apply limiting with -6dB threshold (should limit to ~0.5) | ||
let strict_limiting = LimitSettings::default() | ||
.with_threshold(-6.0) | ||
.with_knee_width(0.5) // Narrow knee for precise limiting | ||
.with_attack(Duration::from_millis(3)) // Fast attack | ||
.with_release(Duration::from_millis(12)); // Moderate release | ||
|
||
let limited_sine = test_sine.limit(strict_limiting.clone()); | ||
let test_samples: Vec<f32> = limited_sine.take(4410).collect(); | ||
|
||
// Analyze peaks at different time periods | ||
let early_peak = test_samples[0..500] | ||
.iter() | ||
.fold(0.0f32, |acc, &x| acc.max(x.abs())); | ||
let mid_peak = test_samples[1000..1500] | ||
.iter() | ||
.fold(0.0f32, |acc, &x| acc.max(x.abs())); | ||
let settled_peak = test_samples[2000..] | ||
.iter() | ||
.fold(0.0f32, |acc, &x| acc.max(x.abs())); | ||
|
||
// With -6dB threshold, ALL samples are well below 1.0! | ||
let target_linear = 10.0_f32.powf(strict_limiting.threshold / 20.0); | ||
let max_settled = test_samples[2000..] | ||
.iter() | ||
.fold(0.0f32, |acc, &x| acc.max(x.abs())); | ||
|
||
println!( | ||
" {}dB threshold limiting results:", | ||
strict_limiting.threshold | ||
); | ||
println!(" Original max amplitude: {AMPLITUDE}"); | ||
println!(" Target threshold: {:.3}", target_linear); | ||
println!(" Early peak (0-500 samples): {:.3}", early_peak); | ||
println!(" Mid peak (1000-1500 samples): {:.3}", mid_peak); | ||
println!(" Settled peak (2000+ samples): {:.3}", settled_peak); | ||
println!( | ||
" ALL samples now well below 1.0: max = {:.3}", | ||
max_settled | ||
); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
use rodio::{source::LimitSettings, Source}; | ||
use std::error::Error; | ||
|
||
fn main() -> Result<(), Box<dyn Error>> { | ||
let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; | ||
let sink = rodio::Sink::connect_new(stream_handle.mixer()); | ||
|
||
let file = std::fs::File::open("assets/music.wav")?; | ||
let source = rodio::Decoder::try_from(file)? | ||
.amplify(3.0) | ||
.limit(LimitSettings::default()); | ||
|
||
sink.append(source); | ||
|
||
println!("Playing music.wav with limiting until finished..."); | ||
sink.sleep_until_end(); | ||
println!("Done."); | ||
|
||
Ok(()) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.