Skip to content

Commit 820f6bb

Browse files
committed
Add integer-samples feature and fix zero detection tests
- Add integer-samples feature to optionally use i16 instead of f32 samples - Fix unreliable zero detection in WAV and MP4 tests using Sample::is_zero() - Improve test reliability by checking !all(is_zero) instead of any(!=0.0) The new feature allows applications to choose between floating-point and fixed-point sample representation based on their needs, while maintaining f32 the default for best sound quality.
1 parent 48cc442 commit 820f6bb

File tree

13 files changed

+64
-28
lines changed

13 files changed

+64
-28
lines changed

Diff for: Cargo.toml

+12-1
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-samples = []
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"]

Diff for: benches/shared.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::io::Cursor;
22
use std::time::Duration;
33
use std::vec;
44

5+
use dasp_sample::Sample;
56
use rodio::{ChannelCount, SampleRate, Source};
67

78
pub struct TestSource<T> {
@@ -57,7 +58,11 @@ impl TestSource<f32> {
5758
channels: sound.channels(),
5859
sample_rate: sound.sample_rate(),
5960
total_duration: duration,
60-
samples: sound.into_iter().collect::<Vec<_>>().into_iter(),
61+
samples: sound
62+
.into_iter()
63+
.map(|s| s.to_sample())
64+
.collect::<Vec<_>>()
65+
.into_iter(),
6166
}
6267
}
6368

@@ -70,7 +75,7 @@ impl TestSource<f32> {
7075
total_duration,
7176
} = self;
7277
let samples = samples
73-
.map(|s| dasp_sample::Sample::from_sample(s))
78+
.map(|s| s.to_sample())
7479
.collect::<Vec<_>>()
7580
.into_iter();
7681
TestSource {

Diff for: src/conversions/sample.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ where
7272
/// You can implement this trait on your own type as well if you wish so.
7373
///
7474
pub trait Sample: DaspSample + ToSample<f32> {
75+
/// The value corresponding to the absence of sound.
76+
const ZERO_VALUE: Self = DaspSample::EQUILIBRIUM;
77+
7578
/// Linear interpolation between two samples.
7679
///
7780
/// The result should be equivalent to
@@ -93,9 +96,9 @@ pub trait Sample: DaspSample + ToSample<f32> {
9396
/// Calls `saturating_add` on the sample.
9497
fn saturating_add(self, other: Self) -> Self;
9598

96-
/// Returns the value corresponding to the absence of sound.
97-
fn zero_value() -> Self {
98-
Self::EQUILIBRIUM
99+
/// Returns true if the sample is the zero value.
100+
fn is_zero(self) -> bool {
101+
self == Self::ZERO_VALUE
99102
}
100103
}
101104

Diff for: src/decoder/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ mod vorbis;
3131
#[cfg(all(feature = "wav", not(feature = "symphonia-wav")))]
3232
mod wav;
3333

34+
#[cfg(feature = "integer-samples")]
35+
/// Output format of the decoders.
36+
pub type DecoderSample = i16;
37+
#[cfg(not(feature = "integer-samples"))]
3438
/// Output format of the decoders.
3539
pub type DecoderSample = f32;
3640

Diff for: src/lib.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,21 @@
133133
//! The "tracing" feature replaces the print to stderr when a stream error happens with a
134134
//! recording an error event with tracing.
135135
//!
136-
//! ### Feature "Noise"
136+
//! ### Feature "noise"
137137
//!
138138
//! The "noise" feature adds support for white and pink noise sources. This feature requires the
139139
//! "rand" crate.
140140
//!
141+
//! ### Feature "playback"
142+
//!
143+
//! The "playback" feature adds support for playing audio. This feature requires the "cpal" crate.
144+
//!
145+
//! ### Feature "integer-samples"
146+
//!
147+
//! The "integer-samples" changes the output format of the decoders to use `i16` instead of `f32`.
148+
//! This is useful if you want to decode audio on an exotic, low-spec or old device that does not
149+
//! have hardware support for floating-point operations.
150+
//!
141151
//! ## How it works under the hood
142152
//!
143153
//! Rodio spawns a background thread that is dedicated to reading from the sources and sending

Diff for: src/mixer.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ where
197197
}
198198

199199
fn sum_current_sources(&mut self) -> S {
200-
let mut sum = S::zero_value();
200+
let mut sum = S::ZERO_VALUE;
201201

202202
for mut source in self.current_sources.drain(..) {
203203
if let Some(value) = source.next() {

Diff for: src/source/channel_volume.rs

+2-6
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,7 @@ where
3636
let mut sample = None;
3737
for _ in 0..input.channels() {
3838
if let Some(s) = input.next() {
39-
sample = Some(
40-
sample
41-
.get_or_insert_with(I::Item::zero_value)
42-
.saturating_add(s),
43-
);
39+
sample = Some(sample.get_or_insert(I::Item::ZERO_VALUE).saturating_add(s));
4440
}
4541
}
4642
ChannelVolume {
@@ -96,7 +92,7 @@ where
9692
if let Some(s) = self.input.next() {
9793
self.current_sample = Some(
9894
self.current_sample
99-
.get_or_insert_with(I::Item::zero_value)
95+
.get_or_insert(I::Item::ZERO_VALUE)
10096
.saturating_add(s),
10197
);
10298
}

Diff for: src/source/delay.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ where
7070
fn next(&mut self) -> Option<<I as Iterator>::Item> {
7171
if self.remaining_samples >= 1 {
7272
self.remaining_samples -= 1;
73-
Some(Sample::zero_value())
73+
Some(Sample::ZERO_VALUE)
7474
} else {
7575
self.input.next()
7676
}

Diff for: src/source/pausable.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,12 @@ where
8282
fn next(&mut self) -> Option<I::Item> {
8383
if self.remaining_paused_samples > 0 {
8484
self.remaining_paused_samples -= 1;
85-
return Some(I::Item::zero_value());
85+
return Some(I::Item::ZERO_VALUE);
8686
}
8787

8888
if let Some(paused_channels) = self.paused_channels {
8989
self.remaining_paused_samples = paused_channels - 1;
90-
return Some(I::Item::zero_value());
90+
return Some(I::Item::ZERO_VALUE);
9191
}
9292

9393
self.input.next()

Diff for: src/source/zero.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,12 @@ where
5454
if let Some(num_samples) = self.num_samples {
5555
if num_samples > 0 {
5656
self.num_samples = Some(num_samples - 1);
57-
Some(S::zero_value())
57+
Some(S::ZERO_VALUE)
5858
} else {
5959
None
6060
}
6161
} else {
62-
Some(S::zero_value())
62+
Some(S::ZERO_VALUE)
6363
}
6464
}
6565
}

Diff for: tests/flac_test.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use rodio::Source;
33
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
44
use std::time::Duration;
55

6+
use rodio::Sample;
7+
68
#[cfg(any(feature = "flac", feature = "symphonia-flac"))]
79
#[test]
810
fn test_flac_encodings() {
@@ -11,8 +13,9 @@ fn test_flac_encodings() {
1113
// 16 bit FLAC file exported from Audacity (2 channels, compression level 5)
1214
let file = std::fs::File::open("assets/audacity16bit_level5.flac").unwrap();
1315
let mut decoder = rodio::Decoder::new(BufReader::new(file)).unwrap();
16+
1417
// File is not just silence
15-
assert!(decoder.any(|x| x != 0.0));
18+
assert!(!decoder.all(|x| x.is_zero()));
1619
// Symphonia does not expose functionality to get the duration so this check must be disabled
1720
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
1821
assert_eq!(decoder.total_duration(), Some(Duration::from_secs(3))); // duration is calculated correctly
@@ -21,7 +24,7 @@ fn test_flac_encodings() {
2124
for level in &[0, 5, 8] {
2225
let file = std::fs::File::open(format!("assets/audacity24bit_level{level}.flac")).unwrap();
2326
let mut decoder = rodio::Decoder::new(BufReader::new(file)).unwrap();
24-
assert!(decoder.any(|x| x != 0.0));
27+
assert!(!decoder.all(|x| x.is_zero()));
2528
#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))]
2629
assert_eq!(decoder.total_duration(), Some(Duration::from_secs(3)));
2730
}

Diff for: tests/mp4a_test.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#![cfg(all(feature = "symphonia-aac", feature = "symphonia-isomp4"))]
22
use std::io::BufReader;
33

4+
use rodio::Sample;
5+
46
#[test]
57
fn test_mp4a_encodings() {
68
// mp4a codec downloaded from YouTube
@@ -10,5 +12,5 @@ fn test_mp4a_encodings() {
1012
// http://creativecommons.org/licenses/by/3.0/
1113
let file = std::fs::File::open("assets/monkeys.mp4a").unwrap();
1214
let mut decoder = rodio::Decoder::new(BufReader::new(file)).unwrap();
13-
assert!(decoder.any(|x| x != 0.0)); // Assert not all zeros
15+
assert!(!decoder.all(|x| x.is_zero())); // Assert not all zeros
1416
}

Diff for: tests/wav_test.rs

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use rodio::Sample;
2+
13
#[cfg(feature = "wav")]
24
#[test]
35
fn test_wav_encodings() {
@@ -6,30 +8,30 @@ fn test_wav_encodings() {
68
// 16 bit wav file exported from Audacity (1 channel)
79
let file = std::fs::File::open("assets/audacity16bit.wav").unwrap();
810
let mut decoder = rodio::Decoder::new(BufReader::new(file)).unwrap();
9-
assert!(decoder.any(|x| x != 0.0)); // Assert not all zeros
11+
assert!(!decoder.all(|x| x.is_zero())); // Assert not all zeros
1012

1113
// 16 bit wav file exported from LMMS (2 channels)
1214
let file = std::fs::File::open("assets/lmms16bit.wav").unwrap();
1315
let mut decoder = rodio::Decoder::new(BufReader::new(file)).unwrap();
14-
assert!(decoder.any(|x| x != 0.0));
16+
assert!(!decoder.all(|x| x.is_zero()));
1517

1618
// 24 bit wav file exported from LMMS (2 channels)
1719
let file = std::fs::File::open("assets/lmms24bit.wav").unwrap();
1820
let mut decoder = rodio::Decoder::new(BufReader::new(file)).unwrap();
19-
assert!(decoder.any(|x| x != 0.0));
21+
assert!(!decoder.all(|x| x.is_zero()));
2022

2123
// 32 bit wav file exported from Audacity (1 channel)
2224
let file = std::fs::File::open("assets/audacity32bit.wav").unwrap();
2325
let mut decoder = rodio::Decoder::new(BufReader::new(file)).unwrap();
24-
assert!(decoder.any(|x| x != 0.0));
26+
assert!(!decoder.all(|x| x.is_zero()));
2527

2628
// 32 bit wav file exported from LMMS (2 channels)
2729
let file = std::fs::File::open("assets/lmms32bit.wav").unwrap();
2830
let mut decoder = rodio::Decoder::new(BufReader::new(file)).unwrap();
29-
assert!(decoder.any(|x| x != 0.0));
31+
assert!(!decoder.all(|x| x.is_zero()));
3032

3133
// 32 bit signed integer wav file exported from Audacity (1 channel).
3234
let file = std::fs::File::open("assets/audacity32bit_int.wav").unwrap();
3335
let mut decoder = rodio::Decoder::new(BufReader::new(file)).unwrap();
34-
assert!(decoder.any(|x| x != 0.0));
36+
assert!(!decoder.all(|x| x.is_zero()));
3537
}

0 commit comments

Comments
 (0)