librespot_playback/
dither.rs1use rand::SeedableRng;
2use rand::rngs::SmallRng;
3use rand_distr::{Distribution, Normal, Triangular, Uniform};
4use std::fmt;
5
6use crate::NUM_CHANNELS;
7
8pub trait Ditherer {
32 fn new() -> Self
33 where
34 Self: Sized;
35 fn name(&self) -> &'static str;
36 fn noise(&mut self) -> f64;
37}
38
39impl fmt::Display for dyn Ditherer {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 write!(f, "{}", self.name())
42 }
43}
44
45fn create_rng() -> SmallRng {
46 SmallRng::from_os_rng()
47}
48
49pub struct TriangularDitherer {
50 cached_rng: SmallRng,
51 distribution: Triangular<f64>,
52}
53
54impl Ditherer for TriangularDitherer {
55 fn new() -> Self {
56 Self {
57 cached_rng: create_rng(),
58 distribution: Triangular::new(-1.0, 1.0, 0.0).unwrap(),
60 }
61 }
62
63 fn name(&self) -> &'static str {
64 Self::NAME
65 }
66
67 #[inline]
68 fn noise(&mut self) -> f64 {
69 self.distribution.sample(&mut self.cached_rng)
70 }
71}
72
73impl TriangularDitherer {
74 pub const NAME: &'static str = "tpdf";
75}
76
77pub struct GaussianDitherer {
78 cached_rng: SmallRng,
79 distribution: Normal<f64>,
80}
81
82impl Ditherer for GaussianDitherer {
83 fn new() -> Self {
84 Self {
85 cached_rng: create_rng(),
86 distribution: Normal::new(0.0, 0.6).unwrap(),
95 }
96 }
97
98 fn name(&self) -> &'static str {
99 Self::NAME
100 }
101
102 #[inline]
103 fn noise(&mut self) -> f64 {
104 self.distribution.sample(&mut self.cached_rng)
105 }
106}
107
108impl GaussianDitherer {
109 pub const NAME: &'static str = "gpdf";
110}
111
112pub struct HighPassDitherer {
113 active_channel: usize,
114 previous_noises: [f64; NUM_CHANNELS as usize],
115 cached_rng: SmallRng,
116 distribution: Uniform<f64>,
117}
118
119impl Ditherer for HighPassDitherer {
120 fn new() -> Self {
121 Self {
122 active_channel: 0,
123 previous_noises: [0.0; NUM_CHANNELS as usize],
124 cached_rng: create_rng(),
125 distribution: Uniform::new_inclusive(-0.5, 0.5)
127 .expect("Failed to create uniform distribution"),
128 }
129 }
130
131 fn name(&self) -> &'static str {
132 Self::NAME
133 }
134
135 #[inline]
136 fn noise(&mut self) -> f64 {
137 let new_noise = self.distribution.sample(&mut self.cached_rng);
138 let high_passed_noise = new_noise - self.previous_noises[self.active_channel];
139 self.previous_noises[self.active_channel] = new_noise;
140 self.active_channel ^= 1;
141 high_passed_noise
142 }
143}
144
145impl HighPassDitherer {
146 pub const NAME: &'static str = "tpdf_hp";
147}
148
149pub fn mk_ditherer<D: Ditherer + 'static>() -> Box<dyn Ditherer> {
150 Box::new(D::new())
151}
152
153pub type DithererBuilder = fn() -> Box<dyn Ditherer>;
154
155pub fn find_ditherer(name: Option<String>) -> Option<DithererBuilder> {
156 match name.as_deref() {
157 Some(TriangularDitherer::NAME) => Some(mk_ditherer::<TriangularDitherer>),
158 Some(GaussianDitherer::NAME) => Some(mk_ditherer::<GaussianDitherer>),
159 Some(HighPassDitherer::NAME) => Some(mk_ditherer::<HighPassDitherer>),
160 _ => None,
161 }
162}