1use std::marker::PhantomData;
8
9use bbx_core::flush_denormal_f64;
10
11use crate::sample::Sample;
12
13const INV_1000: f64 = 1.0 / 1000.0;
14
15const MIN_RAMP_LENGTH_MS: f64 = 0.01;
19
20#[inline]
22fn is_approximately_equal<S: Sample>(a: S, b: S) -> bool {
23 let a_f64 = a.to_f64();
24 let b_f64 = b.to_f64();
25 let epsilon = S::EPSILON.to_f64() * a_f64.abs().max(b_f64.abs()).max(1.0);
26 (a_f64 - b_f64).abs() < epsilon
27}
28
29#[derive(Debug, Clone, Copy, Default)]
34pub struct Linear;
35
36#[derive(Debug, Clone, Copy, Default)]
41pub struct Multiplicative;
42
43pub trait SmoothingStrategy: Clone + Default {
45 fn update_increment<S: Sample>(current: S, target: S, num_samples: f64) -> f64;
48
49 fn apply_increment<S: Sample>(current: S, increment: f64) -> S;
51
52 fn apply_increment_n<S: Sample>(current: S, increment: f64, n: i32) -> S;
54
55 fn default_value<S: Sample>() -> S;
57}
58
59impl SmoothingStrategy for Linear {
60 #[inline]
61 fn update_increment<S: Sample>(current: S, target: S, num_samples: f64) -> f64 {
62 (target.to_f64() - current.to_f64()) / num_samples
63 }
64
65 #[inline]
66 fn apply_increment<S: Sample>(current: S, increment: f64) -> S {
67 S::from_f64(current.to_f64() + increment)
68 }
69
70 #[inline]
71 fn apply_increment_n<S: Sample>(current: S, increment: f64, n: i32) -> S {
72 S::from_f64(current.to_f64() + increment * n as f64)
73 }
74
75 #[inline]
76 fn default_value<S: Sample>() -> S {
77 S::ZERO
78 }
79}
80
81impl SmoothingStrategy for Multiplicative {
82 #[inline]
83 fn update_increment<S: Sample>(current: S, target: S, num_samples: f64) -> f64 {
84 let current_f64 = current.to_f64();
85 let target_f64 = target.to_f64();
86 if current_f64 > 0.0 && target_f64 > 0.0 {
87 (target_f64 / current_f64).ln() / num_samples
88 } else {
89 0.0
90 }
91 }
92
93 #[inline]
94 fn apply_increment<S: Sample>(current: S, increment: f64) -> S {
95 S::from_f64(current.to_f64() * increment.exp())
96 }
97
98 #[inline]
99 fn apply_increment_n<S: Sample>(current: S, increment: f64, n: i32) -> S {
100 S::from_f64(current.to_f64() * (increment * n as f64).exp())
101 }
102
103 #[inline]
104 fn default_value<S: Sample>() -> S {
105 S::ONE
106 }
107}
108
109#[derive(Debug, Clone)]
117pub struct SmoothedValue<S: Sample, T: SmoothingStrategy> {
118 sample_rate: f64,
119 ramp_length_millis: f64,
120 current_value: S,
121 target_value: S,
122 increment: f64, _marker: PhantomData<T>,
124}
125
126impl<S: Sample, T: SmoothingStrategy> SmoothedValue<S, T> {
127 pub fn new(initial_value: S) -> Self {
131 Self {
132 sample_rate: 44100.0,
133 ramp_length_millis: 50.0,
134 current_value: initial_value,
135 target_value: initial_value,
136 increment: 0.0,
137 _marker: PhantomData,
138 }
139 }
140
141 pub fn reset(&mut self, sample_rate: f64, ramp_length_millis: f64) {
146 if sample_rate > 0.0 && ramp_length_millis >= 0.0 {
147 self.sample_rate = sample_rate;
148 self.ramp_length_millis = ramp_length_millis.max(MIN_RAMP_LENGTH_MS);
149 self.update_increment();
150 }
151 }
152
153 #[inline]
155 pub fn set_target_value(&mut self, value: S) {
156 self.target_value = value;
157 self.update_increment();
158 }
159
160 #[inline]
162 pub fn get_next_value(&mut self) -> S {
163 if is_approximately_equal(self.current_value, self.target_value) {
164 self.current_value = self.target_value;
165 return self.current_value;
166 }
167
168 self.current_value = T::apply_increment::<S>(self.current_value, self.increment);
169
170 let current_f64 = flush_denormal_f64(self.current_value.to_f64());
172 self.current_value = S::from_f64(current_f64);
173
174 let target_f64 = self.target_value.to_f64();
176 if (self.increment > 0.0 && current_f64 > target_f64) || (self.increment < 0.0 && current_f64 < target_f64) {
177 self.current_value = self.target_value;
178 }
179
180 self.current_value
181 }
182
183 #[inline]
185 pub fn skip(&mut self, num_samples: i32) {
186 if is_approximately_equal(self.current_value, self.target_value) {
187 self.current_value = self.target_value;
188 return;
189 }
190
191 let new_value = T::apply_increment_n::<S>(self.current_value, self.increment, num_samples);
192
193 let new_f64 = flush_denormal_f64(new_value.to_f64());
195 let target_f64 = self.target_value.to_f64();
196
197 if (self.increment > 0.0 && new_f64 > target_f64) || (self.increment < 0.0 && new_f64 < target_f64) {
199 self.current_value = self.target_value;
200 } else {
201 self.current_value = S::from_f64(new_f64);
202 }
203 }
204
205 #[inline]
207 pub fn current(&self) -> S {
208 self.current_value
209 }
210
211 #[inline]
213 pub fn target(&self) -> S {
214 self.target_value
215 }
216
217 #[inline]
219 pub fn is_smoothing(&self) -> bool {
220 !is_approximately_equal(self.current_value, self.target_value)
221 }
222
223 #[inline]
225 pub fn set_immediate(&mut self, value: S) {
226 self.current_value = value;
227 self.target_value = value;
228 self.increment = 0.0;
229 }
230
231 #[inline]
233 fn update_increment(&mut self) {
234 let ramp_ms = self.ramp_length_millis.max(MIN_RAMP_LENGTH_MS);
236 let num_samples = ramp_ms * self.sample_rate * INV_1000;
237 self.increment = T::update_increment::<S>(self.current_value, self.target_value, num_samples);
238 }
239}
240
241impl<S: Sample, T: SmoothingStrategy> Default for SmoothedValue<S, T> {
242 fn default() -> Self {
243 Self::new(T::default_value::<S>())
244 }
245}
246
247pub type LinearSmoothedValue<S> = SmoothedValue<S, Linear>;
249
250pub type MultiplicativeSmoothedValue<S> = SmoothedValue<S, Multiplicative>;
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn test_linear_immediate_value() {
260 let mut sv = LinearSmoothedValue::<f32>::new(1.0);
261 assert_eq!(sv.current(), 1.0);
262 assert_eq!(sv.get_next_value(), 1.0);
263 assert!(!sv.is_smoothing());
264 }
265
266 #[test]
267 fn test_linear_smoothing_f32() {
268 let mut sv = LinearSmoothedValue::<f32>::new(0.0);
269 sv.reset(1000.0, 4.0); sv.set_target_value(1.0);
272
273 assert!(sv.is_smoothing());
274 let v1 = sv.get_next_value();
276 assert!((v1 - 0.25).abs() < 0.01, "Expected ~0.25, got {}", v1);
277
278 let v2 = sv.get_next_value();
279 assert!((v2 - 0.5).abs() < 0.01, "Expected ~0.5, got {}", v2);
280
281 let v3 = sv.get_next_value();
282 assert!((v3 - 0.75).abs() < 0.01, "Expected ~0.75, got {}", v3);
283
284 let v4 = sv.get_next_value();
285 assert!((v4 - 1.0).abs() < 0.01, "Expected ~1.0, got {}", v4);
286 }
287
288 #[test]
289 fn test_linear_smoothing_f64() {
290 let mut sv = LinearSmoothedValue::<f64>::new(0.0);
291 sv.reset(1000.0, 4.0);
292 sv.set_target_value(1.0);
293
294 assert!(sv.is_smoothing());
295 let v1 = sv.get_next_value();
296 assert!((v1 - 0.25).abs() < 0.01, "Expected ~0.25, got {}", v1);
297
298 let v2 = sv.get_next_value();
299 assert!((v2 - 0.5).abs() < 0.01, "Expected ~0.5, got {}", v2);
300
301 let v3 = sv.get_next_value();
302 assert!((v3 - 0.75).abs() < 0.01, "Expected ~0.75, got {}", v3);
303
304 let v4 = sv.get_next_value();
305 assert!((v4 - 1.0).abs() < 0.01, "Expected ~1.0, got {}", v4);
306 }
307
308 #[test]
309 fn test_zero_ramp_length() {
310 let mut sv = LinearSmoothedValue::<f32>::new(0.0);
313 sv.reset(44100.0, 0.0);
314 sv.set_target_value(1.0);
315
316 assert!(sv.is_smoothing());
318
319 for _ in 0..5 {
321 sv.get_next_value();
322 }
323 assert_eq!(sv.current(), 1.0);
324 assert!(!sv.is_smoothing());
325 }
326
327 #[test]
328 fn test_skip() {
329 let mut sv = LinearSmoothedValue::<f32>::new(0.0);
330 sv.reset(1000.0, 10.0); sv.set_target_value(1.0);
332
333 sv.skip(5);
334 assert!((sv.current() - 0.5).abs() < 0.01);
335
336 sv.skip(10); assert_eq!(sv.current(), 1.0);
338 }
339
340 #[test]
341 fn test_retarget_during_smoothing() {
342 let mut sv = LinearSmoothedValue::<f32>::new(0.0);
343 sv.reset(1000.0, 4.0);
344 sv.set_target_value(1.0);
345
346 sv.get_next_value(); sv.get_next_value(); sv.set_target_value(0.0);
351 assert!(sv.is_smoothing());
353 }
354
355 #[test]
356 fn test_multiplicative_default() {
357 let sv = MultiplicativeSmoothedValue::<f32>::default();
358 assert_eq!(sv.current(), 1.0);
359 }
360
361 #[test]
362 fn test_multiplicative_smoothing() {
363 let mut sv = MultiplicativeSmoothedValue::<f32>::new(1.0);
364 sv.reset(1000.0, 10.0);
365 sv.set_target_value(2.0);
366
367 assert!(sv.is_smoothing());
368
369 let mut prev = sv.current();
371 for _ in 0..10 {
372 let curr = sv.get_next_value();
373 assert!(curr > prev, "Value should increase");
374 prev = curr;
375 }
376 }
377
378 #[test]
379 fn test_set_immediate() {
380 let mut sv = LinearSmoothedValue::<f32>::new(0.0);
381 sv.reset(44100.0, 50.0);
382 sv.set_target_value(1.0);
383 assert!(sv.is_smoothing());
384
385 sv.set_immediate(0.5);
386 assert_eq!(sv.current(), 0.5);
387 assert_eq!(sv.target(), 0.5);
388 assert!(!sv.is_smoothing());
389 }
390}