Skip to content

Commit b4cf40d

Browse files
authored
feat: add optional 64-bit sample precision (#821)
Replace hardcoded f32 types with Sample/Float type aliases throughout the codebase for consistency. Add compile-time configurability for sample precision via a 64bit feature flag. Tise 64bit mode addresses precision drift in long-running signal generators and accumulated operations, following an approach similar to CamillaDSP's compile-time precision selection.
1 parent a1a1219 commit b4cf40d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+581
-513
lines changed

.github/workflows/ci.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,10 @@ jobs:
5555
- run: cargo check --tests --lib --no-default-features
5656
# Check alternative decoders.
5757
- run: cargo check --tests --lib --no-default-features --features claxon,hound,minimp3,lewton
58+
# Test 64-bit sample mode
59+
- run: cargo test --all-targets --features 64bit
60+
- run: cargo test --doc --features 64bit
61+
- run: cargo test --all-targets --all-features --features 64bit
62+
# Check examples compile in both modes
63+
- run: cargo check --examples
64+
- run: cargo check --examples --features 64bit

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2828
- Four dithering algorithms: `TPDF`, `RPDF`, `GPDF`, and `HighPass`
2929
- `DitherAlgorithm` enum for algorithm selection
3030
- `Source::dither()` function for applying dithering
31+
- Added `64bit` feature to opt-in to 64-bit sample precision (`f64`).
3132

3233
### Fixed
3334
- docs.rs will now document all features, including those that are optional.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ wav_output = ["dep:hound"]
3434
tracing = ["dep:tracing"]
3535
# Experimental features using atomic floating-point operations
3636
experimental = ["dep:atomic_float"]
37+
# Perform all calculations with 64-bit floats (instead of 32)
38+
64bit = []
3739

3840
# Audio generation features
3941
#

benches/conversions.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use dasp_sample::{Duplex, Sample};
1+
use dasp_sample::{Duplex, Sample as DaspSample};
22
use divan::Bencher;
3-
use rodio::conversions::SampleTypeConverter;
3+
use rodio::{conversions::SampleTypeConverter, Sample};
44

55
mod shared;
66

@@ -9,7 +9,7 @@ fn main() {
99
}
1010

1111
#[divan::bench(types = [i16, u16, f32])]
12-
fn from_sample<S: Duplex<f32>>(bencher: Bencher) {
12+
fn from_sample<S: Duplex<Sample>>(bencher: Bencher) {
1313
bencher
1414
.with_inputs(|| {
1515
shared::music_wav()
@@ -18,6 +18,6 @@ fn from_sample<S: Duplex<f32>>(bencher: Bencher) {
1818
.into_iter()
1919
})
2020
.bench_values(|source| {
21-
SampleTypeConverter::<_, rodio::Sample>::new(source).for_each(divan::black_box_drop)
21+
SampleTypeConverter::<_, Sample>::new(source).for_each(divan::black_box_drop)
2222
})
2323
}

benches/effects.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::time::Duration;
22

33
use divan::Bencher;
4-
use rodio::source::AutomaticGainControlSettings;
54
use rodio::Source;
65

76
mod shared;
@@ -48,7 +47,7 @@ fn amplify(bencher: Bencher) {
4847
fn agc_enabled(bencher: Bencher) {
4948
bencher.with_inputs(music_wav).bench_values(|source| {
5049
source
51-
.automatic_gain_control(AutomaticGainControlSettings::default())
50+
.automatic_gain_control(Default::default())
5251
.for_each(divan::black_box_drop)
5352
})
5453
}
@@ -58,8 +57,7 @@ fn agc_enabled(bencher: Bencher) {
5857
fn agc_disabled(bencher: Bencher) {
5958
bencher.with_inputs(music_wav).bench_values(|source| {
6059
// Create the AGC source
61-
let amplified_source =
62-
source.automatic_gain_control(AutomaticGainControlSettings::default());
60+
let amplified_source = source.automatic_gain_control(Default::default());
6361

6462
// Get the control handle and disable AGC
6563
let agc_control = amplified_source.get_agc_control();

benches/pipeline.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use std::num::NonZero;
22
use std::time::Duration;
33

44
use divan::Bencher;
5-
use rodio::source::AutomaticGainControlSettings;
65
use rodio::ChannelCount;
76
use rodio::{source::UniformSourceIterator, Source};
87

@@ -20,7 +19,7 @@ fn long(bencher: Bencher) {
2019
.high_pass(300)
2120
.amplify(1.2)
2221
.speed(0.9)
23-
.automatic_gain_control(AutomaticGainControlSettings::default())
22+
.automatic_gain_control(Default::default())
2423
.delay(Duration::from_secs_f32(0.5))
2524
.fade_in(Duration::from_secs_f32(2.0))
2625
.take_duration(Duration::from_secs(10));

examples/automatic_gain_control.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rodio::source::{AutomaticGainControlSettings, Source};
1+
use rodio::source::Source;
22
use rodio::Decoder;
33
use std::error::Error;
44
use std::fs::File;
@@ -16,7 +16,7 @@ fn main() -> Result<(), Box<dyn Error>> {
1616
let source = Decoder::try_from(file)?;
1717

1818
// Apply automatic gain control to the source
19-
let agc_source = source.automatic_gain_control(AutomaticGainControlSettings::default());
19+
let agc_source = source.automatic_gain_control(Default::default());
2020

2121
// Make it so that the source checks if automatic gain control should be
2222
// enabled or disabled every 5 milliseconds. We must clone `agc_enabled`,

examples/limit_settings.rs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//! to configure audio limiting parameters.
55
66
use rodio::source::{LimitSettings, SineWave, Source};
7+
use rodio::Sample;
78
use std::time::Duration;
89

910
fn main() {
@@ -39,11 +40,11 @@ fn main() {
3940
let limited_wave = sine_wave.limit(LimitSettings::default());
4041

4142
// Collect some samples to demonstrate
42-
let samples: Vec<f32> = limited_wave.take(100).collect();
43+
let samples: Vec<Sample> = limited_wave.take(100).collect();
4344
println!(" Generated {} limited samples", samples.len());
4445

4546
// Show peak reduction
46-
let max_sample = samples.iter().fold(0.0f32, |acc, &x| acc.max(x.abs()));
47+
let max_sample: Sample = samples.iter().fold(0.0, |acc, &x| acc.max(x.abs()));
4748
println!(" Peak amplitude after limiting: {max_sample:.3}");
4849
println!();
4950

@@ -56,7 +57,7 @@ fn main() {
5657

5758
// Apply the custom settings from Example 2
5859
let custom_limited = sine_wave2.limit(custom_limiting);
59-
let custom_samples: Vec<f32> = custom_limited.take(50).collect();
60+
let custom_samples: Vec<Sample> = custom_limited.take(50).collect();
6061
println!(
6162
" Generated {} samples with custom settings",
6263
custom_samples.len()
@@ -101,7 +102,7 @@ fn main() {
101102
println!("Example 6: Limiting with -6dB threshold");
102103

103104
// Create a sine wave that will definitely trigger limiting
104-
const AMPLITUDE: f32 = 2.5; // High amplitude to ensure limiting occurs
105+
const AMPLITUDE: Sample = 2.5; // High amplitude to ensure limiting occurs
105106
let test_sine = SineWave::new(440.0)
106107
.amplify(AMPLITUDE)
107108
.take_duration(Duration::from_millis(100)); // 100ms = ~4410 samples
@@ -114,24 +115,24 @@ fn main() {
114115
.with_release(Duration::from_millis(12)); // Moderate release
115116

116117
let limited_sine = test_sine.limit(strict_limiting.clone());
117-
let test_samples: Vec<f32> = limited_sine.take(4410).collect();
118+
let test_samples: Vec<Sample> = limited_sine.take(4410).collect();
118119

119120
// Analyze peaks at different time periods
120-
let early_peak = test_samples[0..500]
121+
let early_peak: Sample = test_samples[0..500]
121122
.iter()
122-
.fold(0.0f32, |acc, &x| acc.max(x.abs()));
123-
let mid_peak = test_samples[1000..1500]
123+
.fold(0.0, |acc, &x| acc.max(x.abs()));
124+
let mid_peak: Sample = test_samples[1000..1500]
124125
.iter()
125-
.fold(0.0f32, |acc, &x| acc.max(x.abs()));
126-
let settled_peak = test_samples[2000..]
126+
.fold(0.0, |acc, &x| acc.max(x.abs()));
127+
let settled_peak: Sample = test_samples[2000..]
127128
.iter()
128-
.fold(0.0f32, |acc, &x| acc.max(x.abs()));
129+
.fold(0.0, |acc, &x| acc.max(x.abs()));
129130

130131
// With -6dB threshold, ALL samples are well below 1.0!
131-
let target_linear = 10.0_f32.powf(strict_limiting.threshold / 20.0);
132-
let max_settled = test_samples[2000..]
132+
let target_linear = (10.0 as Sample).powf(strict_limiting.threshold / 20.0);
133+
let max_settled: Sample = test_samples[2000..]
133134
.iter()
134-
.fold(0.0f32, |acc, &x| acc.max(x.abs()));
135+
.fold(0.0, |acc, &x| acc.max(x.abs()));
135136

136137
println!(
137138
" {}dB threshold limiting results:",

examples/mix_multiple_sources.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
use rodio::mixer;
22
use rodio::source::{SineWave, Source};
3+
use rodio::Float;
34
use std::error::Error;
45
use std::num::NonZero;
56
use std::time::Duration;
67

8+
const NOTE_DURATION: Duration = Duration::from_secs(1);
9+
const NOTE_AMPLITUDE: Float = 0.20;
10+
711
fn main() -> Result<(), Box<dyn Error>> {
812
// Construct a dynamic controller and mixer, stream_handle, and sink.
913
let (controller, mixer) = mixer::mixer(NonZero::new(2).unwrap(), NonZero::new(44_100).unwrap());
@@ -14,17 +18,17 @@ fn main() -> Result<(), Box<dyn Error>> {
1418
// notes in the key of C and in octave 4: C4, or middle C on a piano,
1519
// E4, G4, and A4 respectively.
1620
let source_c = SineWave::new(261.63)
17-
.take_duration(Duration::from_secs_f32(1.))
18-
.amplify(0.20);
21+
.take_duration(NOTE_DURATION)
22+
.amplify(NOTE_AMPLITUDE);
1923
let source_e = SineWave::new(329.63)
20-
.take_duration(Duration::from_secs_f32(1.))
21-
.amplify(0.20);
24+
.take_duration(NOTE_DURATION)
25+
.amplify(NOTE_AMPLITUDE);
2226
let source_g = SineWave::new(392.0)
23-
.take_duration(Duration::from_secs_f32(1.))
24-
.amplify(0.20);
27+
.take_duration(NOTE_DURATION)
28+
.amplify(NOTE_AMPLITUDE);
2529
let source_a = SineWave::new(440.0)
26-
.take_duration(Duration::from_secs_f32(1.))
27-
.amplify(0.20);
30+
.take_duration(NOTE_DURATION)
31+
.amplify(NOTE_AMPLITUDE);
2832

2933
// Add sources C, E, G, and A to the mixer controller.
3034
controller.add(source_c);

examples/noise_generator.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
44
use std::{error::Error, thread::sleep, time::Duration};
55

6-
use rodio::source::{
7-
noise::{Blue, Brownian, Pink, Velvet, Violet, WhiteGaussian, WhiteTriangular, WhiteUniform},
8-
Source,
6+
use rodio::{
7+
source::noise::{
8+
Blue, Brownian, Pink, Velvet, Violet, WhiteGaussian, WhiteTriangular, WhiteUniform,
9+
},
10+
Sample, Source,
911
};
1012

1113
fn main() -> Result<(), Box<dyn Error>> {
@@ -74,7 +76,7 @@ fn main() -> Result<(), Box<dyn Error>> {
7476
/// Helper function to play a noise type with description
7577
fn play_noise<S>(stream_handle: &rodio::OutputStream, source: S, name: &str, description: &str)
7678
where
77-
S: Source<Item = f32> + Send + 'static,
79+
S: Source<Item = Sample> + Send + 'static,
7880
{
7981
println!("{} Noise", name);
8082
println!(" Application: {}", description);

0 commit comments

Comments
 (0)