@@ -5,30 +5,27 @@ use std::fmt;
5
5
const NUM_CHANNELS : usize = 2 ;
6
6
7
7
// Dithering lowers digital-to-analog conversion ("requantization") error,
8
- // lowering distortion and replacing it with a constant, fixed noise level,
9
- // which is more pleasant to the ear than the distortion. Doing so can with
10
- // a noise-shaped dither can increase the dynamic range of 96 dB CD-quality
11
- // audio to a perceived 120 dB.
8
+ // linearizing output, lowering distortion and replacing it with a constant,
9
+ // fixed noise level, which is more pleasant to the ear than the distortion.
12
10
//
13
- // Guidance: experts can configure many different configurations of ditherers
14
- // and noise shapers. For the rest of us:
11
+ // Guidance:
15
12
//
16
- // * Don't dither or shape noise on S32 or F32. On F32 it's not supported
17
- // anyway (there are no rounding errors due to integer conversions) and on
18
- // S32 the noise level is so far down that it is simply inaudible.
19
- //
20
- // * Generally use high pass dithering (hp) without noise shaping. Depending
21
- // on personal preference you may use Gaussian dithering (gauss) instead;
13
+ // * On S24, S24_3 and S24, the default is to use triangular dithering.
14
+ // Depending on personal preference you may use Gaussian dithering instead;
22
15
// it's not as good objectively, but it may be preferred subjectively if
23
- // you are looking for a more "analog" sound.
16
+ // you are looking for a more "analog" sound akin to tape hiss .
24
17
//
25
- // * On power-constrained hardware, use the fraction saving noise shaper
26
- // instead of dithering. Performance-wise, this is not necessary even on a
27
- // Raspberry Pi Zero, but if you're on batteries...
18
+ // * Advanced users who know that they have a DAC without noise shaping have
19
+ // a third option: high-passed dithering, which is like triangular dithering
20
+ // except that it moves dithering noise up in frequency where it is less
21
+ // audible. Note: 99% of DACs are of delta-sigma design with noise shaping,
22
+ // so unless you have a multibit / R2R DAC, or otherwise know what you are
23
+ // doing, this is not for you.
28
24
//
29
- // Implementation note: we save the handle to ThreadRng so it doesn't require
30
- // a lookup on each call (which is on each sample!). This is ~2.5x as fast.
31
- // Downside is that it is not Send so we cannot move it around player threads.
25
+ // * Don't dither or shape noise on S32 or F32. On F32 it's not supported
26
+ // anyway (there are no integer conversions and so no rounding errors) and
27
+ // on S32 the noise level is so far down that it is simply inaudible even
28
+ // after volume normalisation and control.
32
29
//
33
30
pub trait Ditherer {
34
31
fn new ( ) -> Self
@@ -40,7 +37,7 @@ pub trait Ditherer {
40
37
41
38
impl dyn Ditherer {
42
39
pub fn default ( ) -> fn ( ) -> Box < Self > {
43
- mk_ditherer :: < HighPassDitherer >
40
+ mk_ditherer :: < TriangularDitherer >
44
41
}
45
42
}
46
43
@@ -50,83 +47,11 @@ impl fmt::Display for dyn Ditherer {
50
47
}
51
48
}
52
49
53
- pub struct NoDithering { }
54
- impl Ditherer for NoDithering {
55
- fn new ( ) -> Self {
56
- Self { }
57
- }
58
-
59
- fn name ( & self ) -> & ' static str {
60
- "None"
61
- }
62
-
63
- fn noise ( & mut self , _sample : f32 ) -> f32 {
64
- 0.0
65
- }
66
- }
67
-
68
- // "True" white noise (refer to Gaussian for analog source hiss). Advantages:
69
- // least CPU-intensive dither, lowest signal-to-noise ratio. Disadvantage:
70
- // highest perceived loudness, suffers from intermodulation distortion unless
71
- // you are using this for subtractive dithering, which you most likely are not,
72
- // and is not supported by any of the librespot backends. Guidance: use some
73
- // other ditherer unless you know what you're doing.
74
- pub struct RectangularDitherer {
75
- cached_rng : ThreadRng ,
76
- distribution : Uniform < f32 > ,
77
- }
78
-
79
- impl Ditherer for RectangularDitherer {
80
- fn new ( ) -> Self {
81
- Self {
82
- cached_rng : rand:: thread_rng ( ) ,
83
- // 1 LSB peak-to-peak needed to linearize the response:
84
- distribution : Uniform :: new_inclusive ( -0.5 , 0.5 ) ,
85
- }
86
- }
87
-
88
- fn name ( & self ) -> & ' static str {
89
- "Rectangular"
90
- }
91
-
92
- fn noise ( & mut self , _sample : f32 ) -> f32 {
93
- self . distribution . sample ( & mut self . cached_rng )
94
- }
95
- }
96
-
97
- // Like Rectangular, but with lower error and OK to use for the default case
98
- // of non-subtractive dithering such as to the librespot backends.
99
- pub struct StochasticDitherer {
100
- cached_rng : ThreadRng ,
101
- distribution : Uniform < f32 > ,
102
- }
103
-
104
- impl Ditherer for StochasticDitherer {
105
- fn new ( ) -> Self {
106
- Self {
107
- cached_rng : rand:: thread_rng ( ) ,
108
- distribution : Uniform :: new ( 0.0 , 1.0 ) ,
109
- }
110
- }
111
-
112
- fn name ( & self ) -> & ' static str {
113
- "Stochastic"
114
- }
115
-
116
- fn noise ( & mut self , sample : f32 ) -> f32 {
117
- let fract = sample. fract ( ) ;
118
- if self . distribution . sample ( & mut self . cached_rng ) <= fract {
119
- 1.0 - fract
120
- } else {
121
- fract * -1.0
122
- }
123
- }
124
- }
50
+ // Implementation note: we save the handle to ThreadRng so it doesn't require
51
+ // a lookup on each call (which is on each sample!). This is ~2.5x as fast.
52
+ // Downside is that it is not Send so we cannot move it around player threads.
53
+ //
125
54
126
- // Higher level than Rectangular. Advantages: superior to Rectangular as it
127
- // does not suffer from modulation noise effects. Disadvantage: more CPU-
128
- // expensive. Guidance: all-round recommendation to reduce quantization noise,
129
- // even on 24-bit output.
130
55
pub struct TriangularDitherer {
131
56
cached_rng : ThreadRng ,
132
57
distribution : Triangular < f32 > ,
@@ -150,9 +75,6 @@ impl Ditherer for TriangularDitherer {
150
75
}
151
76
}
152
77
153
- // Like Triangular, but with higher noise power and more like phono hiss.
154
- // Guidance: theoretically less optimal, but an alternative to Triangular
155
- // if a more analog sound is sought after.
156
78
pub struct GaussianDitherer {
157
79
cached_rng : ThreadRng ,
158
80
distribution : Normal < f32 > ,
@@ -176,10 +98,6 @@ impl Ditherer for GaussianDitherer {
176
98
}
177
99
}
178
100
179
- // Like Triangular, but with a high-pass filter. Advantages: comparably less
180
- // perceptible noise, less CPU-intensive. Disadvantage: this acts like a FIR
181
- // filter with weights [1.0, -1.0], and is superseded by noise shapers.
182
- // Guidance: better than Triangular if not doing other noise shaping.
183
101
pub struct HighPassDitherer {
184
102
active_channel : usize ,
185
103
previous_noises : [ f32 ; NUM_CHANNELS ] ,
@@ -198,7 +116,7 @@ impl Ditherer for HighPassDitherer {
198
116
}
199
117
200
118
fn name ( & self ) -> & ' static str {
201
- "High Pass "
119
+ "Triangular, High Passed "
202
120
}
203
121
204
122
fn noise ( & mut self , _sample : f32 ) -> f32 {
@@ -216,21 +134,11 @@ pub fn mk_ditherer<D: Ditherer + 'static>() -> Box<dyn Ditherer> {
216
134
217
135
pub type DithererBuilder = fn ( ) -> Box < dyn Ditherer > ;
218
136
219
- pub const DITHERERS : & [ ( & str , DithererBuilder ) ] = & [
220
- ( "none" , mk_ditherer :: < NoDithering > ) ,
221
- ( "rect" , mk_ditherer :: < RectangularDitherer > ) ,
222
- ( "sto" , mk_ditherer :: < StochasticDitherer > ) ,
223
- ( "tri" , mk_ditherer :: < TriangularDitherer > ) ,
224
- ( "gauss" , mk_ditherer :: < GaussianDitherer > ) ,
225
- ( "hp" , mk_ditherer :: < HighPassDitherer > ) ,
226
- ] ;
227
-
228
- pub fn find_ditherer ( name : Option < String > ) -> Option < fn ( ) -> Box < dyn Ditherer > > {
229
- match name {
230
- Some ( name) => DITHERERS
231
- . iter ( )
232
- . find ( |ditherer| name == ditherer. 0 )
233
- . map ( |ditherer| ditherer. 1 ) ,
234
- _ => Some ( mk_ditherer :: < NoDithering > ) ,
137
+ pub fn find_ditherer ( name : Option < String > ) -> Option < DithererBuilder > {
138
+ match name. as_deref ( ) {
139
+ Some ( "tpdf" ) => Some ( mk_ditherer :: < TriangularDitherer > ) ,
140
+ Some ( "gpdf" ) => Some ( mk_ditherer :: < GaussianDitherer > ) ,
141
+ Some ( "tpdf_hp" ) => Some ( mk_ditherer :: < HighPassDitherer > ) ,
142
+ _ => None ,
235
143
}
236
144
}
0 commit comments