math_audio_dsp/
smoothing.rs1#[derive(Debug, Clone, Copy)]
7pub struct Smoother {
8 target: f32,
9 current: f32,
10 coeff: f32,
11}
12
13#[allow(dead_code)]
14impl Smoother {
15 pub fn new(value: f32, time_ms: f32, sample_rate: u32) -> Self {
18 let coeff = Self::calculate_coeff(time_ms, sample_rate);
19 Self {
20 target: value,
21 current: value,
22 coeff,
23 }
24 }
25
26 fn calculate_coeff(time_ms: f32, sample_rate: u32) -> f32 {
27 if time_ms <= 0.0 || sample_rate == 0 {
28 0.0
29 } else {
30 (-1.0 / (time_ms * 0.001 * sample_rate as f32)).exp()
33 }
34 }
35
36 pub fn set_time(&mut self, time_ms: f32, sample_rate: u32) {
37 self.coeff = Self::calculate_coeff(time_ms, sample_rate);
38 }
39
40 pub fn set_target(&mut self, value: f32) {
42 self.target = value;
43 if self.coeff == 0.0 {
45 self.current = value;
46 }
47 }
48
49 #[inline]
51 pub fn next_n(&mut self, n: usize) -> f32 {
52 if self.coeff == 0.0 || (self.current - self.target).abs() < 1e-5 || n == 0 {
53 self.current = self.target;
54 } else {
55 let block_coeff = self.coeff.powi(n as i32);
57 self.current = self.target + block_coeff * (self.current - self.target);
58 }
59 self.current
60 }
61
62 #[inline]
64 pub fn advance(&mut self) -> f32 {
65 self.next_n(1)
66 }
67
68 #[inline]
70 pub fn current(&self) -> f32 {
71 self.current
72 }
73
74 #[inline]
76 pub fn target(&self) -> f32 {
77 self.target
78 }
79
80 #[inline]
83 pub fn process_sample(&mut self, sample: f32) -> f32 {
84 if (self.current - self.target).abs() < 1e-5 {
87 self.current = self.target;
88 } else {
89 self.current = self.target + self.coeff * (self.current - self.target);
90 }
91 sample * self.current
92 }
93
94 pub fn reset(&mut self, value: f32) {
96 self.target = value;
97 self.current = value;
98 }
99}
100
101#[derive(Debug, Clone, Copy)]
104pub struct LinearSmoother {
105 target: f32,
106 current: f32,
107 step: f32,
108 sample_rate: u32,
109 time_ms: f32,
110}
111
112impl LinearSmoother {
113 pub fn new(value: f32, time_ms: f32, sample_rate: u32) -> Self {
114 Self {
115 target: value,
116 current: value,
117 step: 0.0,
118 sample_rate,
119 time_ms,
120 }
121 }
122
123 pub fn set_target(&mut self, value: f32) {
124 self.target = value;
125 if self.time_ms <= 0.0 {
126 self.current = value;
127 self.step = 0.0;
128 } else {
129 let samples = (self.time_ms * 0.001 * self.sample_rate as f32).max(1.0);
130 self.step = (self.target - self.current) / samples;
131 }
132 }
133
134 #[inline]
135 pub fn advance(&mut self) -> f32 {
136 self.next_n(1)
137 }
138
139 #[inline]
140 pub fn next_n(&mut self, n: usize) -> f32 {
141 if n == 0 {
142 return self.current;
143 }
144 let total_step = self.step * n as f32;
145 if (self.current - self.target).abs() <= total_step.abs() {
146 self.current = self.target;
147 self.step = 0.0;
148 } else {
149 self.current += total_step;
150 }
151 self.current
152 }
153
154 #[allow(dead_code)]
155 pub fn reset(&mut self, value: f32) {
156 self.target = value;
157 self.current = value;
158 self.step = 0.0;
159 }
160
161 #[allow(dead_code)]
162 pub fn current(&self) -> f32 {
163 self.current
164 }
165
166 pub fn target(&self) -> f32 {
167 self.target
168 }
169}
170
171#[derive(Debug, Clone, Copy)]
174pub struct LogSmoother {
175 target: f32,
176 current: f32,
177 ratio: f32,
178 sample_rate: u32,
179 time_ms: f32,
180}
181
182impl LogSmoother {
183 pub fn new(value: f32, time_ms: f32, sample_rate: u32) -> Self {
184 Self {
185 target: value.max(1e-7),
186 current: value.max(1e-7),
187 ratio: 1.0,
188 sample_rate,
189 time_ms,
190 }
191 }
192
193 pub fn set_target(&mut self, value: f32) {
194 self.target = value.max(1e-7);
195 if self.time_ms <= 0.0 {
196 self.current = self.target;
197 self.ratio = 1.0;
198 } else {
199 let samples = (self.time_ms * 0.001 * self.sample_rate as f32).max(1.0);
200 self.ratio = (self.target / self.current).powf(1.0 / samples);
202 }
203 }
204
205 #[inline]
206 pub fn advance(&mut self) -> f32 {
207 self.next_n(1)
208 }
209
210 #[inline]
211 pub fn next_n(&mut self, n: usize) -> f32 {
212 if self.ratio == 1.0 || n == 0 {
213 return self.current;
214 }
215
216 let new_log = self.current.ln() + self.ratio.ln() * n as f32;
217 self.current = new_log.exp().clamp(1e-7, 1e6);
218
219 if (self.ratio > 1.0 && self.current >= self.target)
221 || (self.ratio < 1.0 && self.current <= self.target)
222 {
223 self.current = self.target;
224 self.ratio = 1.0;
225 }
226
227 self.current
228 }
229
230 #[allow(dead_code)]
231 pub fn reset(&mut self, value: f32) {
232 self.target = value.max(1e-7);
233 self.current = self.target;
234 self.ratio = 1.0;
235 }
236
237 #[allow(dead_code)]
238 pub fn current(&self) -> f32 {
239 self.current
240 }
241
242 pub fn target(&self) -> f32 {
243 self.target
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn test_exponential_smoother() {
253 let mut s = Smoother::new(0.0, 10.0, 1000); s.set_target(1.0);
255
256 let first = s.advance();
257 assert!(first > 0.0 && first < 1.0);
258
259 for _ in 0..100 {
260 s.advance();
261 }
262 assert!((s.current() - 1.0).abs() < 1e-4);
263 }
264
265 #[test]
266 fn test_linear_smoother() {
267 let mut s = LinearSmoother::new(0.0, 10.0, 1000); s.set_target(1.0);
269
270 assert!((s.advance() - 0.1).abs() < 1e-6);
271 assert!((s.advance() - 0.2).abs() < 1e-6);
272
273 for _ in 0..7 {
274 s.advance();
275 }
276 assert!((s.advance() - 1.0).abs() < 1e-6);
277 assert!((s.advance() - 1.0).abs() < 1e-6);
278 }
279
280 #[test]
281 fn test_log_smoother() {
282 let mut s = LogSmoother::new(100.0, 10.0, 1000); s.set_target(1000.0);
284
285 let first = s.advance();
286 assert!((first - 125.89).abs() < 0.1);
288
289 for _ in 0..8 {
290 s.advance();
291 }
292 assert!((s.advance() - 1000.0).abs() < 1e-3);
293 assert!((s.advance() - 1000.0).abs() < 1e-6);
294 }
295
296 #[test]
297 fn test_log_smoother_large_block_stays_finite() {
298 let mut s = LogSmoother::new(1e-7, 20.0, 48000);
299 s.set_target(1e6);
300
301 let value = s.next_n(4096);
302 assert!(
303 value.is_finite(),
304 "large-block log smoothing must not overflow to inf"
305 );
306 assert!(
307 value <= 1e6,
308 "large-block log smoothing should clamp at target range, got {value}"
309 );
310 }
311
312 #[test]
313 fn test_smoother_sample_rate_zero_no_panic() {
314 let mut s = Smoother::new(1.0, 50.0, 0);
316 assert_eq!(s.current(), 1.0);
317 s.set_target(2.0);
318 assert_eq!(s.current(), 2.0);
320 let val = s.advance();
322 assert_eq!(val, 2.0);
323 assert!(!val.is_nan());
324 assert!(!val.is_infinite());
325 }
326
327 #[test]
328 fn test_smoother_set_time_sample_rate_zero() {
329 let mut s = Smoother::new(1.0, 10.0, 48000);
330 s.set_time(10.0, 0);
331 s.set_target(5.0);
332 assert_eq!(s.current(), 5.0);
334 }
335}