Skip to content

Commit d1a9b79

Browse files
authored
Merge pull request #669 from roderickvd/feature/float-decoder
Decoder overhaul (16 to f32 samples + other enhancements)
2 parents 11fc2e4 + 9861056 commit d1a9b79

25 files changed

+388
-314
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818
`GeneratorFunction`.
1919
- Minimal builds without `cpal` audio output are now supported.
2020
See `README.md` for instructions. (#349)
21+
- Added `Sample::is_zero()` method for checking zero samples.
2122

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

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

3847
# Version 0.20.1 (2024-11-08)
3948

Cargo.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,28 @@ default = ["playback", "flac", "vorbis", "wav", "mp3"]
3030
tracing = ["dep:tracing"]
3131
experimental = ["dep:atomic_float"]
3232
playback = ["dep:cpal"]
33+
integer-decoder = []
3334

3435
flac = ["claxon"]
3536
vorbis = ["lewton"]
3637
wav = ["hound"]
3738
mp3 = ["symphonia-mp3"]
3839
minimp3 = ["dep:minimp3_fixed"]
40+
3941
noise = ["rand"]
42+
4043
wasm-bindgen = ["cpal/wasm-bindgen"]
4144
cpal-shared-stdcxx = ["cpal/oboe-shared-stdcxx"]
45+
4246
symphonia-aac = ["symphonia/aac"]
43-
symphonia-all = ["symphonia-aac", "symphonia-flac", "symphonia-isomp4", "symphonia-mp3", "symphonia-vorbis", "symphonia-wav"]
47+
symphonia-all = [
48+
"symphonia-aac",
49+
"symphonia-flac",
50+
"symphonia-isomp4",
51+
"symphonia-mp3",
52+
"symphonia-vorbis",
53+
"symphonia-wav",
54+
]
4455
symphonia-flac = ["symphonia/flac"]
4556
symphonia-isomp4 = ["symphonia/isomp4"]
4657
symphonia-mp3 = ["symphonia/mp3"]

benches/conversions.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
use dasp_sample::FromSample;
22
use divan::Bencher;
3-
use rodio::Source;
3+
use rodio::{decoder::DecoderSample, Source};
44

55
mod shared;
6-
use shared::TestSource;
76

87
fn main() {
98
divan::main();
109
}
1110

1211
#[divan::bench(types = [i16, u16, f32])]
13-
fn from_i16_to<T: rodio::Sample + FromSample<i16>>(bencher: Bencher) {
12+
fn from_sample_to<T: rodio::Sample + FromSample<DecoderSample>>(bencher: Bencher) {
1413
bencher
15-
.with_inputs(|| TestSource::music_wav())
14+
.with_inputs(|| shared::music_wav())
1615
.bench_values(|source| {
1716
source
1817
.convert_samples::<T>()

benches/effects.rs

Lines changed: 38 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,84 +4,76 @@ use divan::Bencher;
44
use rodio::Source;
55

66
mod shared;
7-
use shared::TestSource;
7+
use shared::music_wav;
88

99
fn main() {
1010
divan::main();
1111
}
1212

1313
#[divan::bench]
1414
fn reverb(bencher: Bencher) {
15-
bencher
16-
.with_inputs(|| TestSource::music_wav())
17-
.bench_values(|source| {
18-
source
19-
.buffered()
20-
.reverb(Duration::from_secs_f32(0.05), 0.3)
21-
.for_each(divan::black_box_drop)
22-
})
15+
bencher.with_inputs(|| music_wav()).bench_values(|source| {
16+
source
17+
.buffered()
18+
.reverb(Duration::from_secs_f32(0.05), 0.3)
19+
.for_each(divan::black_box_drop)
20+
})
2321
}
2422

2523
#[divan::bench]
2624
fn high_pass(bencher: Bencher) {
2725
bencher
28-
.with_inputs(|| TestSource::music_wav().to_f32s())
26+
.with_inputs(|| music_wav().to_f32s())
2927
.bench_values(|source| source.high_pass(200).for_each(divan::black_box_drop))
3028
}
3129

3230
#[divan::bench]
3331
fn fade_out(bencher: Bencher) {
34-
bencher
35-
.with_inputs(|| TestSource::music_wav())
36-
.bench_values(|source| {
37-
source
38-
.fade_out(Duration::from_secs(5))
39-
.for_each(divan::black_box_drop)
40-
})
32+
bencher.with_inputs(|| music_wav()).bench_values(|source| {
33+
source
34+
.fade_out(Duration::from_secs(5))
35+
.for_each(divan::black_box_drop)
36+
})
4137
}
4238

4339
#[divan::bench]
4440
fn amplify(bencher: Bencher) {
4541
bencher
46-
.with_inputs(|| TestSource::music_wav().to_f32s())
42+
.with_inputs(|| music_wav())
4743
.bench_values(|source| source.amplify(0.8).for_each(divan::black_box_drop))
4844
}
4945

5046
#[divan::bench]
5147
fn agc_enabled(bencher: Bencher) {
52-
bencher
53-
.with_inputs(|| TestSource::music_wav().to_f32s())
54-
.bench_values(|source| {
55-
source
56-
.automatic_gain_control(
57-
1.0, // target_level
58-
4.0, // attack_time (in seconds)
59-
0.005, // release_time (in seconds)
60-
5.0, // absolute_max_gain
61-
)
62-
.for_each(divan::black_box_drop)
63-
})
48+
bencher.with_inputs(|| music_wav()).bench_values(|source| {
49+
source
50+
.automatic_gain_control(
51+
1.0, // target_level
52+
4.0, // attack_time (in seconds)
53+
0.005, // release_time (in seconds)
54+
5.0, // absolute_max_gain
55+
)
56+
.for_each(divan::black_box_drop)
57+
})
6458
}
6559

6660
#[cfg(feature = "experimental")]
6761
#[divan::bench]
6862
fn agc_disabled(bencher: Bencher) {
69-
bencher
70-
.with_inputs(|| TestSource::music_wav().to_f32s())
71-
.bench_values(|source| {
72-
// Create the AGC source
73-
let amplified_source = source.automatic_gain_control(
74-
1.0, // target_level
75-
4.0, // attack_time (in seconds)
76-
0.005, // release_time (in seconds)
77-
5.0, // absolute_max_gain
78-
);
63+
bencher.with_inputs(|| music_wav()).bench_values(|source| {
64+
// Create the AGC source
65+
let amplified_source = source.automatic_gain_control(
66+
1.0, // target_level
67+
4.0, // attack_time (in seconds)
68+
0.005, // release_time (in seconds)
69+
5.0, // absolute_max_gain
70+
);
7971

80-
// Get the control handle and disable AGC
81-
let agc_control = amplified_source.get_agc_control();
82-
agc_control.store(false, std::sync::atomic::Ordering::Relaxed);
72+
// Get the control handle and disable AGC
73+
let agc_control = amplified_source.get_agc_control();
74+
agc_control.store(false, std::sync::atomic::Ordering::Relaxed);
8375

84-
// Process the audio stream with AGC disabled
85-
amplified_source.for_each(divan::black_box_drop)
86-
})
76+
// Process the audio stream with AGC disabled
77+
amplified_source.for_each(divan::black_box_drop)
78+
})
8779
}

benches/resampler.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use divan::Bencher;
2+
use rodio::decoder::DecoderSample;
23
use rodio::source::UniformSourceIterator;
34

45
mod shared;
6+
use shared::music_wav;
7+
58
use rodio::Source;
6-
use shared::TestSource;
79

810
fn main() {
911
divan::main();
@@ -13,11 +15,11 @@ fn main() {
1315
fn no_resampling(bencher: Bencher) {
1416
bencher
1517
.with_inputs(|| {
16-
let source = TestSource::<i16>::music_wav();
18+
let source = music_wav();
1719
(source.channels(), source.sample_rate(), source)
1820
})
1921
.bench_values(|(channels, sample_rate, source)| {
20-
UniformSourceIterator::<_, i16>::new(source, channels, sample_rate)
22+
UniformSourceIterator::<_, DecoderSample>::new(source, channels, sample_rate)
2123
.for_each(divan::black_box_drop)
2224
})
2325
}
@@ -32,11 +34,11 @@ const COMMON_SAMPLE_RATES: [u32; 12] = [
3234
fn resample_to(bencher: Bencher, target_sample_rate: u32) {
3335
bencher
3436
.with_inputs(|| {
35-
let source = TestSource::<i16>::music_wav();
37+
let source = music_wav();
3638
(source.channels(), source)
3739
})
3840
.bench_values(|(channels, source)| {
39-
UniformSourceIterator::<_, i16>::new(source, channels, target_sample_rate)
41+
UniformSourceIterator::<_, DecoderSample>::new(source, channels, target_sample_rate)
4042
.for_each(divan::black_box_drop)
4143
})
4244
}

benches/shared.rs

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::io::Cursor;
22
use std::time::Duration;
33
use std::vec;
44

5-
use rodio::{ChannelCount, SampleRate, Source};
5+
use rodio::{decoder::DecoderSample, ChannelCount, Sample, SampleRate, Source};
66

77
pub struct TestSource<T> {
88
samples: vec::IntoIter<T>,
@@ -14,53 +14,42 @@ pub struct TestSource<T> {
1414
impl<T> Iterator for TestSource<T> {
1515
type Item = T;
1616

17+
#[inline]
1718
fn next(&mut self) -> Option<Self::Item> {
1819
self.samples.next()
1920
}
2021
}
2122

2223
impl<T> ExactSizeIterator for TestSource<T> {
24+
#[inline]
2325
fn len(&self) -> usize {
2426
self.samples.len()
2527
}
2628
}
2729

28-
impl<T: rodio::Sample> Source for TestSource<T> {
30+
impl<T: Sample> Source for TestSource<T> {
31+
#[inline]
2932
fn current_span_len(&self) -> Option<usize> {
3033
None // forever
3134
}
3235

36+
#[inline]
3337
fn channels(&self) -> ChannelCount {
3438
self.channels
3539
}
3640

41+
#[inline]
3742
fn sample_rate(&self) -> SampleRate {
3843
self.sample_rate
3944
}
4045

46+
#[inline]
4147
fn total_duration(&self) -> Option<Duration> {
4248
Some(self.total_duration)
4349
}
4450
}
4551

4652
impl TestSource<i16> {
47-
pub fn music_wav() -> Self {
48-
let data = include_bytes!("../assets/music.wav");
49-
let cursor = Cursor::new(data);
50-
51-
let duration = Duration::from_secs(10);
52-
let sound = rodio::Decoder::new(cursor)
53-
.expect("music.wav is correctly encoded & wav is supported")
54-
.take_duration(duration);
55-
56-
TestSource {
57-
channels: sound.channels(),
58-
sample_rate: sound.sample_rate(),
59-
total_duration: duration,
60-
samples: sound.into_iter().collect::<Vec<_>>().into_iter(),
61-
}
62-
}
63-
6453
#[allow(unused, reason = "not everything from shared is used in all libs")]
6554
pub fn to_f32s(self) -> TestSource<f32> {
6655
let TestSource {
@@ -69,10 +58,7 @@ impl TestSource<i16> {
6958
sample_rate,
7059
total_duration,
7160
} = self;
72-
let samples = samples
73-
.map(|s| dasp_sample::Sample::from_sample(s))
74-
.collect::<Vec<_>>()
75-
.into_iter();
61+
let samples = samples.map(|s| s.to_f32()).collect::<Vec<_>>().into_iter();
7662
TestSource {
7763
samples,
7864
channels,
@@ -81,3 +67,27 @@ impl TestSource<i16> {
8167
}
8268
}
8369
}
70+
71+
impl TestSource<f32> {
72+
#[allow(unused, reason = "not everything from shared is used in all libs")]
73+
pub fn to_f32s(self) -> TestSource<f32> {
74+
self
75+
}
76+
}
77+
78+
pub fn music_wav() -> TestSource<DecoderSample> {
79+
let data = include_bytes!("../assets/music.wav");
80+
let cursor = Cursor::new(data);
81+
82+
let duration = Duration::from_secs(10);
83+
let sound = rodio::Decoder::new(cursor)
84+
.expect("music.wav is correctly encoded & wav is supported")
85+
.take_duration(duration);
86+
87+
TestSource {
88+
channels: sound.channels(),
89+
sample_rate: sound.sample_rate(),
90+
total_duration: duration,
91+
samples: sound.into_iter().collect::<Vec<_>>().into_iter(),
92+
}
93+
}

src/conversions/mod.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,5 @@ pub use self::sample::Sample;
1111
pub use self::sample_rate::SampleRateConverter;
1212

1313
mod channels;
14-
// TODO: < shouldn't be public ; there's a bug in Rust 1.4 and below that makes This
15-
// `pub` mandatory
16-
pub mod sample;
14+
mod sample;
1715
mod sample_rate;

0 commit comments

Comments
 (0)