Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Decoder overhaul (16 to f32 samples + other enhancements) #669

Merged
merged 18 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`GeneratorFunction`.
- Minimal builds without `cpal` audio output are now supported.
See `README.md` for instructions. (#349)
- Added `Sample::is_zero()` method for checking zero samples.

### Changed
- Breaking: `OutputStreamBuilder` should now be used to initialize an audio output stream.
Expand All @@ -26,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Breaking: `Sink::try_new` renamed to `connect_new` and does not return error anymore.
`Sink::new_idle` was renamed to `new`.
- Breaking: In the `Source` trait, the method `current_frame_len()` was renamed to `current_span_len()`.
- Breaking: `Decoder` now outputs `f32` samples by default instead of `i16`.
Enable the `integer-decoder` to revert to `i16` samples.
- The term 'frame' was renamed to 'span' in the crate and documentation.

### Fixed
Expand All @@ -34,6 +37,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Symphonia decoder `total_duration` incorrect value caused by conversion from `Time` to `Duration`.
- An issue with `SignalGenerator` that caused it to create increasingly distorted waveforms
over long run times has been corrected. (#201)
- WAV and FLAC decoder duration calculation now calculated once and handles very large files
correctly
- Removed unwrap() calls in MP3, WAV, FLAC and Vorbis format detection for better error handling

### Deprecated
- Deprecated `Sample::zero_value()` function in favor of `Sample::ZERO_VALUE` constant

# Version 0.20.1 (2024-11-08)

Expand Down
13 changes: 12 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,28 @@ default = ["playback", "flac", "vorbis", "wav", "mp3"]
tracing = ["dep:tracing"]
experimental = ["dep:atomic_float"]
playback = ["dep:cpal"]
integer-decoder = []

flac = ["claxon"]
vorbis = ["lewton"]
wav = ["hound"]
mp3 = ["symphonia-mp3"]
minimp3 = ["dep:minimp3_fixed"]

noise = ["rand"]

wasm-bindgen = ["cpal/wasm-bindgen"]
cpal-shared-stdcxx = ["cpal/oboe-shared-stdcxx"]

symphonia-aac = ["symphonia/aac"]
symphonia-all = ["symphonia-aac", "symphonia-flac", "symphonia-isomp4", "symphonia-mp3", "symphonia-vorbis", "symphonia-wav"]
symphonia-all = [
"symphonia-aac",
"symphonia-flac",
"symphonia-isomp4",
"symphonia-mp3",
"symphonia-vorbis",
"symphonia-wav",
]
symphonia-flac = ["symphonia/flac"]
symphonia-isomp4 = ["symphonia/isomp4"]
symphonia-mp3 = ["symphonia/mp3"]
Expand Down
7 changes: 3 additions & 4 deletions benches/conversions.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
use dasp_sample::FromSample;
use divan::Bencher;
use rodio::Source;
use rodio::{decoder::DecoderSample, Source};

mod shared;
use shared::TestSource;

fn main() {
divan::main();
}

#[divan::bench(types = [i16, u16, f32])]
fn from_i16_to<T: rodio::Sample + FromSample<i16>>(bencher: Bencher) {
fn from_sample_to<T: rodio::Sample + FromSample<DecoderSample>>(bencher: Bencher) {
bencher
.with_inputs(|| TestSource::music_wav())
.with_inputs(|| shared::music_wav())
.bench_values(|source| {
source
.convert_samples::<T>()
Expand Down
84 changes: 38 additions & 46 deletions benches/effects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,84 +4,76 @@ use divan::Bencher;
use rodio::Source;

mod shared;
use shared::TestSource;
use shared::music_wav;

fn main() {
divan::main();
}

#[divan::bench]
fn reverb(bencher: Bencher) {
bencher
.with_inputs(|| TestSource::music_wav())
.bench_values(|source| {
source
.buffered()
.reverb(Duration::from_secs_f32(0.05), 0.3)
.for_each(divan::black_box_drop)
})
bencher.with_inputs(|| music_wav()).bench_values(|source| {
source
.buffered()
.reverb(Duration::from_secs_f32(0.05), 0.3)
.for_each(divan::black_box_drop)
})
}

#[divan::bench]
fn high_pass(bencher: Bencher) {
bencher
.with_inputs(|| TestSource::music_wav().to_f32s())
.with_inputs(|| music_wav().to_f32s())
.bench_values(|source| source.high_pass(200).for_each(divan::black_box_drop))
}

#[divan::bench]
fn fade_out(bencher: Bencher) {
bencher
.with_inputs(|| TestSource::music_wav())
.bench_values(|source| {
source
.fade_out(Duration::from_secs(5))
.for_each(divan::black_box_drop)
})
bencher.with_inputs(|| music_wav()).bench_values(|source| {
source
.fade_out(Duration::from_secs(5))
.for_each(divan::black_box_drop)
})
}

#[divan::bench]
fn amplify(bencher: Bencher) {
bencher
.with_inputs(|| TestSource::music_wav().to_f32s())
.with_inputs(|| music_wav())
.bench_values(|source| source.amplify(0.8).for_each(divan::black_box_drop))
}

#[divan::bench]
fn agc_enabled(bencher: Bencher) {
bencher
.with_inputs(|| TestSource::music_wav().to_f32s())
.bench_values(|source| {
source
.automatic_gain_control(
1.0, // target_level
4.0, // attack_time (in seconds)
0.005, // release_time (in seconds)
5.0, // absolute_max_gain
)
.for_each(divan::black_box_drop)
})
bencher.with_inputs(|| music_wav()).bench_values(|source| {
source
.automatic_gain_control(
1.0, // target_level
4.0, // attack_time (in seconds)
0.005, // release_time (in seconds)
5.0, // absolute_max_gain
)
.for_each(divan::black_box_drop)
})
}

#[cfg(feature = "experimental")]
#[divan::bench]
fn agc_disabled(bencher: Bencher) {
bencher
.with_inputs(|| TestSource::music_wav().to_f32s())
.bench_values(|source| {
// Create the AGC source
let amplified_source = source.automatic_gain_control(
1.0, // target_level
4.0, // attack_time (in seconds)
0.005, // release_time (in seconds)
5.0, // absolute_max_gain
);
bencher.with_inputs(|| music_wav()).bench_values(|source| {
// Create the AGC source
let amplified_source = source.automatic_gain_control(
1.0, // target_level
4.0, // attack_time (in seconds)
0.005, // release_time (in seconds)
5.0, // absolute_max_gain
);

// Get the control handle and disable AGC
let agc_control = amplified_source.get_agc_control();
agc_control.store(false, std::sync::atomic::Ordering::Relaxed);
// Get the control handle and disable AGC
let agc_control = amplified_source.get_agc_control();
agc_control.store(false, std::sync::atomic::Ordering::Relaxed);

// Process the audio stream with AGC disabled
amplified_source.for_each(divan::black_box_drop)
})
// Process the audio stream with AGC disabled
amplified_source.for_each(divan::black_box_drop)
})
}
12 changes: 7 additions & 5 deletions benches/resampler.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use divan::Bencher;
use rodio::decoder::DecoderSample;
use rodio::source::UniformSourceIterator;

mod shared;
use shared::music_wav;

use rodio::Source;
use shared::TestSource;

fn main() {
divan::main();
Expand All @@ -13,11 +15,11 @@ fn main() {
fn no_resampling(bencher: Bencher) {
bencher
.with_inputs(|| {
let source = TestSource::<i16>::music_wav();
let source = music_wav();
(source.channels(), source.sample_rate(), source)
})
.bench_values(|(channels, sample_rate, source)| {
UniformSourceIterator::<_, i16>::new(source, channels, sample_rate)
UniformSourceIterator::<_, DecoderSample>::new(source, channels, sample_rate)
.for_each(divan::black_box_drop)
})
}
Expand All @@ -32,11 +34,11 @@ const COMMON_SAMPLE_RATES: [u32; 12] = [
fn resample_to(bencher: Bencher, target_sample_rate: u32) {
bencher
.with_inputs(|| {
let source = TestSource::<i16>::music_wav();
let source = music_wav();
(source.channels(), source)
})
.bench_values(|(channels, source)| {
UniformSourceIterator::<_, i16>::new(source, channels, target_sample_rate)
UniformSourceIterator::<_, DecoderSample>::new(source, channels, target_sample_rate)
.for_each(divan::black_box_drop)
})
}
56 changes: 33 additions & 23 deletions benches/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::io::Cursor;
use std::time::Duration;
use std::vec;

use rodio::{ChannelCount, SampleRate, Source};
use rodio::{decoder::DecoderSample, ChannelCount, Sample, SampleRate, Source};

pub struct TestSource<T> {
samples: vec::IntoIter<T>,
Expand All @@ -14,53 +14,42 @@ pub struct TestSource<T> {
impl<T> Iterator for TestSource<T> {
type Item = T;

#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.samples.next()
}
}

impl<T> ExactSizeIterator for TestSource<T> {
#[inline]
fn len(&self) -> usize {
self.samples.len()
}
}

impl<T: rodio::Sample> Source for TestSource<T> {
impl<T: Sample> Source for TestSource<T> {
#[inline]
fn current_span_len(&self) -> Option<usize> {
None // forever
}

#[inline]
fn channels(&self) -> ChannelCount {
self.channels
}

#[inline]
fn sample_rate(&self) -> SampleRate {
self.sample_rate
}

#[inline]
fn total_duration(&self) -> Option<Duration> {
Some(self.total_duration)
}
}

impl TestSource<i16> {
pub fn music_wav() -> Self {
let data = include_bytes!("../assets/music.wav");
let cursor = Cursor::new(data);

let duration = Duration::from_secs(10);
let sound = rodio::Decoder::new(cursor)
.expect("music.wav is correctly encoded & wav is supported")
.take_duration(duration);

TestSource {
channels: sound.channels(),
sample_rate: sound.sample_rate(),
total_duration: duration,
samples: sound.into_iter().collect::<Vec<_>>().into_iter(),
}
}

#[allow(unused, reason = "not everything from shared is used in all libs")]
pub fn to_f32s(self) -> TestSource<f32> {
let TestSource {
Expand All @@ -69,10 +58,7 @@ impl TestSource<i16> {
sample_rate,
total_duration,
} = self;
let samples = samples
.map(|s| dasp_sample::Sample::from_sample(s))
.collect::<Vec<_>>()
.into_iter();
let samples = samples.map(|s| s.to_f32()).collect::<Vec<_>>().into_iter();
TestSource {
samples,
channels,
Expand All @@ -81,3 +67,27 @@ impl TestSource<i16> {
}
}
}

impl TestSource<f32> {
#[allow(unused, reason = "not everything from shared is used in all libs")]
pub fn to_f32s(self) -> TestSource<f32> {
self
}
}

pub fn music_wav() -> TestSource<DecoderSample> {
let data = include_bytes!("../assets/music.wav");
let cursor = Cursor::new(data);

let duration = Duration::from_secs(10);
let sound = rodio::Decoder::new(cursor)
.expect("music.wav is correctly encoded & wav is supported")
.take_duration(duration);

TestSource {
channels: sound.channels(),
sample_rate: sound.sample_rate(),
total_duration: duration,
samples: sound.into_iter().collect::<Vec<_>>().into_iter(),
}
}
4 changes: 1 addition & 3 deletions src/conversions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,5 @@ pub use self::sample::Sample;
pub use self::sample_rate::SampleRateConverter;

mod channels;
// TODO: < shouldn't be public ; there's a bug in Rust 1.4 and below that makes This
// `pub` mandatory
pub mod sample;
mod sample;
mod sample_rate;
Loading
Loading