Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `OutputStreamConfig::channel_count()`, `OutputStreamConfig::sample_rate()`,
`OutputStreamConfig::buffer_size()` and `OutputStreamConfig::sample_format()` getters to access
an `OutputStreamConfig`'s channel count, sample rate, buffer size and sample format values.
- Added `Source::limit()` method for limiting the maximum amplitude of a source.

### Changed
- Breaking: `OutputStreamBuilder` should now be used to initialize an audio output stream.
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ required-features = ["playback"]
name = "into_file"
required-features = ["mp3", "wav"]

[[example]]
name = "limit_wav"
required-features = ["playback", "wav"]

[[example]]
name = "low_pass"
required-features = ["playback", "wav"]
Expand Down
149 changes: 149 additions & 0 deletions examples/limit_settings.rs
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
);
}
20 changes: 20 additions & 0 deletions examples/limit_wav.rs
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(())
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,6 @@ pub use cpal::{
};

mod common;
mod math;
mod sink;
mod spatial_sink;
#[cfg(feature = "playback")]
Expand All @@ -178,6 +177,7 @@ mod wav_output;
pub mod buffer;
pub mod conversions;
pub mod decoder;
pub mod math;
pub mod mixer;
pub mod queue;
pub mod source;
Expand Down
Loading