From 8062bd25185c0205a890cadce37661167a187b5a Mon Sep 17 00:00:00 2001 From: Roderick van Domburg Date: Sat, 29 May 2021 22:53:19 +0200 Subject: [PATCH] Improve sample rounding and clean up noise shaping leftovers (#771) --- Cargo.lock | 85 +++++++++++++++++++++++++++++++++++------ playback/Cargo.toml | 1 + playback/src/convert.rs | 19 ++++----- playback/src/dither.rs | 8 ++-- 4 files changed, 89 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7bd92dc..a4e3ef9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -435,6 +435,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "futures" version = "0.3.15" @@ -1073,6 +1079,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" +[[package]] +name = "libmath" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfd3416934a853ae80d5c3b006f632dfcbaf320300c5167e88a469e9ac214502" +dependencies = [ + "rand 0.3.23", +] + [[package]] name = "libmdns" version = "0.6.1" @@ -1085,7 +1100,7 @@ dependencies = [ "if-addrs", "log", "multimap", - "rand", + "rand 0.8.3", "socket2", "thiserror", "tokio", @@ -1190,7 +1205,7 @@ dependencies = [ "librespot-protocol", "log", "protobuf", - "rand", + "rand 0.8.3", "serde", "serde_json", "tokio", @@ -1223,7 +1238,7 @@ dependencies = [ "pbkdf2", "priority-queue", "protobuf", - "rand", + "rand 0.8.3", "serde", "serde_json", "sha-1", @@ -1254,7 +1269,7 @@ dependencies = [ "libmdns", "librespot-core", "log", - "rand", + "rand 0.8.3", "serde_json", "sha-1", "simple_logger", @@ -1288,6 +1303,7 @@ dependencies = [ "gstreamer-app", "jack 0.7.1", "lewton", + "libmath", "libpulse-binding", "libpulse-simple-binding", "librespot-audio", @@ -1296,7 +1312,7 @@ dependencies = [ "log", "ogg", "portaudio-rs", - "rand", + "rand 0.8.3", "rand_distr", "rodio", "sdl2", @@ -1488,7 +1504,7 @@ dependencies = [ "autocfg", "num-integer", "num-traits", - "rand", + "rand 0.8.3", ] [[package]] @@ -1840,6 +1856,29 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" +dependencies = [ + "libc", + "rand 0.4.6", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + [[package]] name = "rand" version = "0.8.3" @@ -1848,7 +1887,7 @@ checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" dependencies = [ "libc", "rand_chacha", - "rand_core", + "rand_core 0.6.2", "rand_hc", ] @@ -1859,9 +1898,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.2", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.6.2" @@ -1878,7 +1932,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da9e8f32ad24fb80d07d2323a9a2ce8b30d68a62b8cb4df88119ff49a698f038" dependencies = [ "num-traits", - "rand", + "rand 0.8.3", ] [[package]] @@ -1887,7 +1941,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" dependencies = [ - "rand_core", + "rand_core 0.6.2", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", ] [[package]] @@ -2212,7 +2275,7 @@ checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ "cfg-if 1.0.0", "libc", - "rand", + "rand 0.8.3", "redox_syscall", "remove_dir_all", "winapi", diff --git a/playback/Cargo.toml b/playback/Cargo.toml index 2af1436b..bf55b5a5 100644 --- a/playback/Cargo.toml +++ b/playback/Cargo.toml @@ -20,6 +20,7 @@ version = "0.2.0" [dependencies] futures-executor = "0.3" futures-util = { version = "0.3", default_features = false, features = ["alloc"] } +libmath = "0.2" log = "0.4" byteorder = "1.4" shell-words = "1.0.0" diff --git a/playback/src/convert.rs b/playback/src/convert.rs index a5d5a0bb..91fa0e96 100644 --- a/playback/src/convert.rs +++ b/playback/src/convert.rs @@ -32,18 +32,19 @@ impl Converter { // Denormalize and dither pub fn scale(&mut self, sample: f32, factor: i64) -> f32 { + let dither = match self.ditherer { + Some(ref mut d) => d.noise(), + None => 0.0, + }; + // From the many float to int conversion methods available, match what // the reference Vorbis implementation uses: sample * 32768 (for 16 bit) - let int_value = sample * factor as f32; + let int_value = sample * factor as f32 + dither; - // https://doc.rust-lang.org/nomicon/casts.html: casting float to integer - // rounds towards zero, then saturates. Ideally halves should round to even to - // prevent any bias, but since it is extremely unlikely that a float has - // *exactly* .5 as fraction, this should be more than precise enough. - match self.ditherer { - Some(ref mut d) => int_value + d.noise(int_value), - None => int_value, - } + // Casting float to integer rounds towards zero by default, i.e. it + // truncates, and that generates larger error than rounding to nearest. + // Absolute lowest error is gained from rounding ties to even. + math::round::half_to_even(int_value.into(), 0) as f32 } // Special case for samples packed in a word of greater bit depth (e.g. diff --git a/playback/src/dither.rs b/playback/src/dither.rs index 86aca6e2..972cca2d 100644 --- a/playback/src/dither.rs +++ b/playback/src/dither.rs @@ -32,7 +32,7 @@ pub trait Ditherer { where Self: Sized; fn name(&self) -> &'static str; - fn noise(&mut self, sample: f32) -> f32; + fn noise(&mut self) -> f32; } impl fmt::Display for dyn Ditherer { @@ -64,7 +64,7 @@ impl Ditherer for TriangularDitherer { "Triangular" } - fn noise(&mut self, _sample: f32) -> f32 { + fn noise(&mut self) -> f32 { self.distribution.sample(&mut self.cached_rng) } } @@ -87,7 +87,7 @@ impl Ditherer for GaussianDitherer { "Gaussian" } - fn noise(&mut self, _sample: f32) -> f32 { + fn noise(&mut self) -> f32 { self.distribution.sample(&mut self.cached_rng) } } @@ -113,7 +113,7 @@ impl Ditherer for HighPassDitherer { "Triangular, High Passed" } - fn noise(&mut self, _sample: f32) -> f32 { + fn noise(&mut self) -> f32 { let new_noise = self.distribution.sample(&mut self.cached_rng); let high_passed_noise = new_noise - self.previous_noises[self.active_channel]; self.previous_noises[self.active_channel] = new_noise;