1 use rand::rngs::SmallRng;
2 use rand::SeedableRng;
3 use rand_distr::{Distribution, Normal, Triangular, Uniform};
4 use std::fmt;
5
6 const NUM_CHANNELS: usize = 2;
7
8 // Dithering lowers digital-to-analog conversion ("requantization") error,
9 // linearizing output, lowering distortion and replacing it with a constant,
10 // fixed noise level, which is more pleasant to the ear than the distortion.
11 //
12 // Guidance:
13 //
14 // * On S24, S24_3 and S24, the default is to use triangular dithering.
15 // Depending on personal preference you may use Gaussian dithering instead;
16 // it's not as good objectively, but it may be preferred subjectively if
17 // you are looking for a more "analog" sound akin to tape hiss.
18 //
19 // * Advanced users who know that they have a DAC without noise shaping have
20 // a third option: high-passed dithering, which is like triangular dithering
21 // except that it moves dithering noise up in frequency where it is less
22 // audible. Note: 99% of DACs are of delta-sigma design with noise shaping,
23 // so unless you have a multibit / R2R DAC, or otherwise know what you are
24 // doing, this is not for you.
25 //
26 // * Don't dither or shape noise on S32 or F32. On F32 it's not supported
27 // anyway (there are no integer conversions and so no rounding errors) and
28 // on S32 the noise level is so far down that it is simply inaudible even
29 // after volume normalisation and control.
30 //
31 pub trait Ditherer {
new() -> Self where Self: Sized32 fn new() -> Self
33 where
34 Self: Sized;
name(&self) -> &'static str35 fn name(&self) -> &'static str;
noise(&mut self) -> f6436 fn noise(&mut self) -> f64;
37 }
38
39 impl fmt::Display for dyn Ditherer {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 write!(f, "{}", self.name())
42 }
43 }
44
create_rng() -> SmallRng45 fn create_rng() -> SmallRng {
46 SmallRng::from_entropy()
47 }
48
49 pub struct TriangularDitherer {
50 cached_rng: SmallRng,
51 distribution: Triangular<f64>,
52 }
53
54 impl Ditherer for TriangularDitherer {
new() -> Self55 fn new() -> Self {
56 Self {
57 cached_rng: create_rng(),
58 // 2 LSB peak-to-peak needed to linearize the response:
59 distribution: Triangular::new(-1.0, 1.0, 0.0).unwrap(),
60 }
61 }
62
name(&self) -> &'static str63 fn name(&self) -> &'static str {
64 Self::NAME
65 }
66
noise(&mut self) -> f6467 fn noise(&mut self) -> f64 {
68 self.distribution.sample(&mut self.cached_rng)
69 }
70 }
71
72 impl TriangularDitherer {
73 pub const NAME: &'static str = "tpdf";
74 }
75
76 pub struct GaussianDitherer {
77 cached_rng: SmallRng,
78 distribution: Normal<f64>,
79 }
80
81 impl Ditherer for GaussianDitherer {
new() -> Self82 fn new() -> Self {
83 Self {
84 cached_rng: create_rng(),
85 // 1/2 LSB RMS needed to linearize the response:
86 distribution: Normal::new(0.0, 0.5).unwrap(),
87 }
88 }
89
name(&self) -> &'static str90 fn name(&self) -> &'static str {
91 Self::NAME
92 }
93
noise(&mut self) -> f6494 fn noise(&mut self) -> f64 {
95 self.distribution.sample(&mut self.cached_rng)
96 }
97 }
98
99 impl GaussianDitherer {
100 pub const NAME: &'static str = "gpdf";
101 }
102
103 pub struct HighPassDitherer {
104 active_channel: usize,
105 previous_noises: [f64; NUM_CHANNELS],
106 cached_rng: SmallRng,
107 distribution: Uniform<f64>,
108 }
109
110 impl Ditherer for HighPassDitherer {
new() -> Self111 fn new() -> Self {
112 Self {
113 active_channel: 0,
114 previous_noises: [0.0; NUM_CHANNELS],
115 cached_rng: create_rng(),
116 distribution: Uniform::new_inclusive(-0.5, 0.5), // 1 LSB +/- 1 LSB (previous) = 2 LSB
117 }
118 }
119
name(&self) -> &'static str120 fn name(&self) -> &'static str {
121 Self::NAME
122 }
123
noise(&mut self) -> f64124 fn noise(&mut self) -> f64 {
125 let new_noise = self.distribution.sample(&mut self.cached_rng);
126 let high_passed_noise = new_noise - self.previous_noises[self.active_channel];
127 self.previous_noises[self.active_channel] = new_noise;
128 self.active_channel ^= 1;
129 high_passed_noise
130 }
131 }
132
133 impl HighPassDitherer {
134 pub const NAME: &'static str = "tpdf_hp";
135 }
136
mk_ditherer<D: Ditherer + 'static>() -> Box<dyn Ditherer>137 pub fn mk_ditherer<D: Ditherer + 'static>() -> Box<dyn Ditherer> {
138 Box::new(D::new())
139 }
140
141 pub type DithererBuilder = fn() -> Box<dyn Ditherer>;
142
find_ditherer(name: Option<String>) -> Option<DithererBuilder>143 pub fn find_ditherer(name: Option<String>) -> Option<DithererBuilder> {
144 match name.as_deref() {
145 Some(TriangularDitherer::NAME) => Some(mk_ditherer::<TriangularDitherer>),
146 Some(GaussianDitherer::NAME) => Some(mk_ditherer::<GaussianDitherer>),
147 Some(HighPassDitherer::NAME) => Some(mk_ditherer::<HighPassDitherer>),
148 _ => None,
149 }
150 }
151