Skip to content

Commit 8062bd2

Browse files
authored
Improve sample rounding and clean up noise shaping leftovers (#771)
1 parent a2fde0a commit 8062bd2

4 files changed

Lines changed: 89 additions & 24 deletions

File tree

Cargo.lock

Lines changed: 74 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

playback/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ version = "0.2.0"
2020
[dependencies]
2121
futures-executor = "0.3"
2222
futures-util = { version = "0.3", default_features = false, features = ["alloc"] }
23+
libmath = "0.2"
2324
log = "0.4"
2425
byteorder = "1.4"
2526
shell-words = "1.0.0"

playback/src/convert.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,19 @@ impl Converter {
3232

3333
// Denormalize and dither
3434
pub fn scale(&mut self, sample: f32, factor: i64) -> f32 {
35+
let dither = match self.ditherer {
36+
Some(ref mut d) => d.noise(),
37+
None => 0.0,
38+
};
39+
3540
// From the many float to int conversion methods available, match what
3641
// the reference Vorbis implementation uses: sample * 32768 (for 16 bit)
37-
let int_value = sample * factor as f32;
42+
let int_value = sample * factor as f32 + dither;
3843

39-
// https://doc.rust-lang.org/nomicon/casts.html: casting float to integer
40-
// rounds towards zero, then saturates. Ideally halves should round to even to
41-
// prevent any bias, but since it is extremely unlikely that a float has
42-
// *exactly* .5 as fraction, this should be more than precise enough.
43-
match self.ditherer {
44-
Some(ref mut d) => int_value + d.noise(int_value),
45-
None => int_value,
46-
}
44+
// Casting float to integer rounds towards zero by default, i.e. it
45+
// truncates, and that generates larger error than rounding to nearest.
46+
// Absolute lowest error is gained from rounding ties to even.
47+
math::round::half_to_even(int_value.into(), 0) as f32
4748
}
4849

4950
// Special case for samples packed in a word of greater bit depth (e.g.

playback/src/dither.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ pub trait Ditherer {
3232
where
3333
Self: Sized;
3434
fn name(&self) -> &'static str;
35-
fn noise(&mut self, sample: f32) -> f32;
35+
fn noise(&mut self) -> f32;
3636
}
3737

3838
impl fmt::Display for dyn Ditherer {
@@ -64,7 +64,7 @@ impl Ditherer for TriangularDitherer {
6464
"Triangular"
6565
}
6666

67-
fn noise(&mut self, _sample: f32) -> f32 {
67+
fn noise(&mut self) -> f32 {
6868
self.distribution.sample(&mut self.cached_rng)
6969
}
7070
}
@@ -87,7 +87,7 @@ impl Ditherer for GaussianDitherer {
8787
"Gaussian"
8888
}
8989

90-
fn noise(&mut self, _sample: f32) -> f32 {
90+
fn noise(&mut self) -> f32 {
9191
self.distribution.sample(&mut self.cached_rng)
9292
}
9393
}
@@ -113,7 +113,7 @@ impl Ditherer for HighPassDitherer {
113113
"Triangular, High Passed"
114114
}
115115

116-
fn noise(&mut self, _sample: f32) -> f32 {
116+
fn noise(&mut self) -> f32 {
117117
let new_noise = self.distribution.sample(&mut self.cached_rng);
118118
let high_passed_noise = new_noise - self.previous_noises[self.active_channel];
119119
self.previous_noises[self.active_channel] = new_noise;

0 commit comments

Comments
 (0)