Skip to main content

nice_plug_core/params/
smoothing.rs

1//! Utilities to handle smoothing parameter changes over time.
2
3use std::sync::Arc;
4use std::sync::atomic::{AtomicI32, Ordering};
5
6// Re-exported here because it's sued in `SmoothingStyle`.
7// TODO: This re-export has been moved in the `util` module. Remove the
8// `pub` in the next breaking version release.
9pub use crate::util::AtomicF32;
10
11use crate::{nice_debug_assert, nice_debug_assert_ne};
12
13/// Controls if and how parameters gets smoothed.
14#[derive(Debug, Clone)]
15pub enum SmoothingStyle {
16    /// Wraps another smoothing style to create a multi-rate oversampling-aware smoother for a
17    /// parameter that's used in an oversampled part of the plugin. The `Arc<AtomicF32>` indicates
18    /// the oversampling amount, where `1.0` means no oversampling. This value can change at
19    /// runtime, and it effectively scales the sample rate when computing new smoothing coefficients
20    /// when the parameter's value changes.
21    OversamplingAware(Arc<AtomicF32>, &'static SmoothingStyle),
22
23    /// No smoothing is applied. The parameter's `value` field contains the latest sample value
24    /// available for the parameters.
25    None,
26    /// Smooth parameter changes so the current value approaches the target value at a constant
27    /// rate. The target value will be reached in exactly this many milliseconds.
28    Linear(f32),
29    /// Smooth parameter changes such that the rate matches the curve of a logarithmic function,
30    /// starting out slow and then constantly increasing the slope until the value is reached. The
31    /// target value will be reached in exactly this many milliseconds. This is useful for smoothing
32    /// things like frequencies and decibel gain value. **The caveat is that the value may never
33    /// reach 0**, or you will end up multiplying and dividing things by zero. Make sure your value
34    /// ranges don't include 0.
35    Logarithmic(f32),
36    /// Smooth parameter changes such that the rate matches the curve of an exponential function,
37    /// starting out fast and then tapering off until the end. This is a single-pole IIR filter
38    /// under the hood, while the other smoothing options are FIR filters. This means that the exact
39    /// value would never be reached. Instead, this reaches 99.99% of the value target value in the
40    /// specified number of milliseconds, and it then snaps to the target value in the last step.
41    /// This results in a smoother transition, with the caveat being that there will be a tiny jump
42    /// at the end. Unlike the `Logarithmic` option, this does support crossing the zero value.
43    Exponential(f32),
44}
45
46/// A smoother, providing a smoothed value for each sample.
47//
48// TODO: We need to use atomics here so we can share the params object with the GUI. Is there a
49//       better alternative to allow the process function to mutate these smoothers?
50#[derive(Debug)]
51pub struct Smoother<T: Smoothable> {
52    /// The kind of snoothing that needs to be applied, if any.
53    pub style: SmoothingStyle,
54    /// The number of steps of smoothing left to take.
55    ///
56    // This is a signed integer because we can skip multiple steps, which would otherwise make it
57    // possible to get an underflow here.
58    steps_left: AtomicI32,
59    /// The amount we should adjust the current value each sample to be able to reach the target in
60    /// the specified tiem frame. This is also a floating point number to keep the smoothing
61    /// uniform.
62    ///
63    /// In the case of the `Exponential` smoothing style this is the coefficient `x` that the
64    /// previous sample is multiplied by.
65    step_size: AtomicF32,
66    /// The value for the current sample. Always stored as floating point for obvious reasons.
67    current: AtomicF32,
68    /// The value we're smoothing towards
69    target: T::Atomic,
70}
71
72/// An iterator that continuously produces smoothed values. Can be used as an alternative to the
73/// block-based smoothing API. Since the iterator itself is infinite, you can use
74/// [`Smoother::is_smoothing()`] and [`Smoother::steps_left()`] to get information on the current
75/// smoothing status.
76pub struct SmootherIter<'a, T: Smoothable> {
77    smoother: &'a Smoother<T>,
78}
79
80impl SmoothingStyle {
81    /// Compute the number of steps to reach the target value based on the sample rate and this
82    /// smoothing style's duration.
83    #[inline]
84    pub fn num_steps(&self, sample_rate: f32) -> u32 {
85        nice_debug_assert!(sample_rate > 0.0);
86
87        match self {
88            Self::OversamplingAware(oversampling_times, style) => {
89                style.num_steps(sample_rate * oversampling_times.load(Ordering::Relaxed))
90            }
91
92            Self::None => 1,
93            Self::Linear(time) | Self::Logarithmic(time) | Self::Exponential(time) => {
94                nice_debug_assert!(*time >= 0.0);
95                (sample_rate * time / 1000.0).round() as u32
96            }
97        }
98    }
99
100    /// Compute the step size for this smoother. `num_steps` can be obtained using
101    /// [`SmoothingStyle::num_steps()`]. Check the source code of the [`SmoothingStyle::next()`] and
102    /// [`SmoothingStyle::next_step()`] functions for details on how these values should be used.
103    #[inline]
104    pub fn step_size(&self, start: f32, target: f32, num_steps: u32) -> f32 {
105        nice_debug_assert!(num_steps >= 1);
106
107        match self {
108            Self::OversamplingAware(_, style) => style.step_size(start, target, num_steps),
109
110            Self::None => 0.0,
111            Self::Linear(_) => (target - start) / (num_steps as f32),
112            Self::Logarithmic(_) => {
113                // We need to solve `start * (step_size ^ num_steps) = target` for `step_size`
114                nice_debug_assert_ne!(start, 0.0);
115                ((target / start) as f64).powf((num_steps as f64).recip()) as f32
116            }
117            // In this case the step size value is the coefficient the current value will be
118            // multiplied by, while the target value is multiplied by one minus the coefficient. This
119            // reaches 99.99% of the target value after `num_steps`. The smoother will snap to the
120            // target value after that point.
121            Self::Exponential(_) => 0.0001f64.powf((num_steps as f64).recip()) as f32,
122        }
123    }
124
125    /// Compute the next value from `current` leading up to `target` using the `step_size` computed
126    /// using [`SmoothingStyle::step_size()`]. Depending on the smoothing style this function may
127    /// never completely reach `target`, so you will need to snap to `target` yourself after
128    /// computing the target number of steps.
129    ///
130    /// See the docstring on the [`SmoothingStyle::next_step()`] function for the formulas used.
131    #[inline]
132    pub fn next(&self, current: f32, target: f32, step_size: f32) -> f32 {
133        match self {
134            Self::OversamplingAware(_, style) => style.next(current, target, step_size),
135
136            Self::None => target,
137            Self::Linear(_) => current + step_size,
138            Self::Logarithmic(_) => current * step_size,
139            Self::Exponential(_) => (current * step_size) + (target * (1.0 - step_size)),
140        }
141    }
142
143    /// The same as [`next()`][Self::next()], but with the option to take more than one step at a
144    /// time. Calling `next_step()` with step count `n` gives the same result as applying `next()`
145    /// `n` times to a value, but is more efficient to compute. `next_step()` with 1 step is
146    /// equivalent to `step()`.
147    ///
148    /// See the docstring on the [`SmoothingStyle::next_step()`] function for the formulas used.
149    #[inline]
150    pub fn next_step(&self, current: f32, target: f32, step_size: f32, steps: u32) -> f32 {
151        nice_debug_assert!(steps >= 1);
152
153        match self {
154            Self::OversamplingAware(_, style) => style.next_step(current, target, step_size, steps),
155
156            Self::None => target,
157            Self::Linear(_) => current + (step_size * steps as f32),
158            Self::Logarithmic(_) => current * (step_size.powi(steps as i32)),
159            Self::Exponential(_) => {
160                // This is the same as calculating `current = (current * step_size) +
161                // (target * (1 - step_size))` in a loop since the target value won't change
162                let coefficient = step_size.powi(steps as i32);
163                (current * coefficient) + (target * (1.0 - coefficient))
164            }
165        }
166    }
167}
168
169/// A type that can be smoothed. This exists just to avoid duplicate explicit implementations for
170/// the smoothers.
171pub trait Smoothable: Default + Clone + Copy {
172    /// The atomic representation of `Self`.
173    type Atomic: Default;
174
175    fn to_f32(self) -> f32;
176    fn from_f32(value: f32) -> Self;
177
178    fn atomic_new(value: Self) -> Self::Atomic;
179    /// A relaxed atomic load.
180    fn atomic_load(this: &Self::Atomic) -> Self;
181    /// A relaxed atomic store.
182    fn atomic_store(this: &Self::Atomic, value: Self);
183}
184
185impl<T: Smoothable> Default for Smoother<T> {
186    fn default() -> Self {
187        Self {
188            style: SmoothingStyle::None,
189            steps_left: AtomicI32::new(0),
190            step_size: Default::default(),
191            current: AtomicF32::new(0.0),
192            target: Default::default(),
193        }
194    }
195}
196
197impl<T: Smoothable> Iterator for SmootherIter<'_, T> {
198    type Item = T;
199
200    #[inline]
201    fn next(&mut self) -> Option<Self::Item> {
202        Some(self.smoother.next())
203    }
204}
205
206impl<T: Smoothable> Clone for Smoother<T> {
207    fn clone(&self) -> Self {
208        // We can't derive clone because of the atomics, but these atomics are only here to allow
209        // Send+Sync interior mutability
210        Self {
211            style: self.style.clone(),
212            steps_left: AtomicI32::new(self.steps_left.load(Ordering::Relaxed)),
213            step_size: AtomicF32::new(self.step_size.load(Ordering::Relaxed)),
214            current: AtomicF32::new(self.current.load(Ordering::Relaxed)),
215            target: T::atomic_new(T::atomic_load(&self.target)),
216        }
217    }
218}
219
220impl<T: Smoothable> Smoother<T> {
221    /// Use the specified style for the smoothing.
222    pub fn new(style: SmoothingStyle) -> Self {
223        Self {
224            style,
225            ..Default::default()
226        }
227    }
228
229    /// Convenience function for not applying any smoothing at all. Same as `Smoother::default`.
230    pub fn none() -> Self {
231        Default::default()
232    }
233
234    /// The number of steps left until calling [`next()`][Self::next()] will stop yielding new
235    /// values.
236    #[inline]
237    pub fn steps_left(&self) -> i32 {
238        self.steps_left.load(Ordering::Relaxed)
239    }
240
241    /// Whether calling [`next()`][Self::next()] will yield a new value or an old value. Useful if
242    /// you need to recompute something whenever this parameter changes.
243    #[inline]
244    pub fn is_smoothing(&self) -> bool {
245        self.steps_left() > 0
246    }
247
248    /// Produce an iterator that yields smoothed values. These are not iterators already for the
249    /// sole reason that this will always yield a value, and needing to unwrap all of those options
250    /// is not going to be very fun.
251    #[inline]
252    pub fn iter(&self) -> SmootherIter<'_, T> {
253        SmootherIter { smoother: self }
254    }
255
256    /// Reset the smoother the specified value.
257    pub fn reset(&self, value: T) {
258        T::atomic_store(&self.target, value);
259        self.current.store(value.to_f32(), Ordering::Relaxed);
260        self.steps_left.store(0, Ordering::Relaxed);
261    }
262
263    /// Set the target value.
264    pub fn set_target(&self, sample_rate: f32, target: T) {
265        T::atomic_store(&self.target, target);
266
267        let steps_left = self.style.num_steps(sample_rate) as i32;
268        self.steps_left.store(steps_left, Ordering::Relaxed);
269
270        let current = self.current.load(Ordering::Relaxed);
271        let target_f32 = target.to_f32();
272        self.step_size.store(
273            if steps_left > 0 {
274                self.style.step_size(current, target_f32, steps_left as u32)
275            } else {
276                0.0
277            },
278            Ordering::Relaxed,
279        );
280    }
281
282    /// Get the next value from this smoother. The value will be equal to the previous value once
283    /// the smoothing period is over. This should be called exactly once per sample.
284    // Yes, Clippy, like I said, this was intentional
285    #[allow(clippy::should_implement_trait)]
286    #[inline]
287    pub fn next(&self) -> T {
288        let target = T::atomic_load(&self.target);
289
290        // NOTE: This used to be implemented in terms of `next_step()`, but this is more efficient
291        //       for the common use case of single steps
292        if self.steps_left.load(Ordering::Relaxed) > 0 {
293            let current = self.current.load(Ordering::Relaxed);
294            let target_f32 = target.to_f32();
295            let step_size = self.step_size.load(Ordering::Relaxed);
296
297            // The number of steps usually won't fit exactly, so make sure we don't end up with
298            // quantization errors on overshoots or undershoots. We also need to account for the
299            // possibility that we only have `n < steps` steps left. This is especially important
300            // for the `Exponential` smoothing style, since that won't reach the target value
301            // exactly.
302            let old_steps_left = self.steps_left.fetch_sub(1, Ordering::Relaxed);
303            let new = if old_steps_left == 1 {
304                self.steps_left.store(0, Ordering::Relaxed);
305                target_f32
306            } else {
307                self.style.next(current, target_f32, step_size)
308            };
309            self.current.store(new, Ordering::Relaxed);
310
311            T::from_f32(new)
312        } else {
313            target
314        }
315    }
316
317    /// [`next()`][Self::next()], but with the ability to skip forward in the smoother.
318    /// [`next()`][Self::next()] is equivalent to calling this function with a `steps` value of 1.
319    /// Calling this function with a `steps` value of `n` means will cause you to skip the next `n -
320    /// 1` values and return the `n`th value.
321    #[inline]
322    pub fn next_step(&self, steps: u32) -> T {
323        nice_debug_assert_ne!(steps, 0);
324
325        let target = T::atomic_load(&self.target);
326
327        if self.steps_left.load(Ordering::Relaxed) > 0 {
328            let current = self.current.load(Ordering::Relaxed);
329            let target_f32 = target.to_f32();
330            let step_size = self.step_size.load(Ordering::Relaxed);
331
332            // The number of steps usually won't fit exactly, so make sure we don't end up with
333            // quantization errors on overshoots or undershoots. We also need to account for the
334            // possibility that we only have `n < steps` steps left. This is especially important
335            // for the `Exponential` smoothing style, since that won't reach the target value
336            // exactly.
337            let old_steps_left = self.steps_left.fetch_sub(steps as i32, Ordering::Relaxed);
338            let new = if old_steps_left <= steps as i32 {
339                self.steps_left.store(0, Ordering::Relaxed);
340                target_f32
341            } else {
342                self.style.next_step(current, target_f32, step_size, steps)
343            };
344            self.current.store(new, Ordering::Relaxed);
345
346            T::from_f32(new)
347        } else {
348            target
349        }
350    }
351
352    /// Get previous value returned by this smoother. This may be useful to save some boilerplate
353    /// when [`is_smoothing()`][Self::is_smoothing()] is used to determine whether an expensive
354    /// calculation should take place, and [`next()`][Self::next()] gets called as part of that
355    /// calculation.
356    pub fn previous_value(&self) -> T {
357        T::from_f32(self.current.load(Ordering::Relaxed))
358    }
359
360    /// Produce smoothed values for an entire block of audio. This is useful when iterating the same
361    /// block of audio multiple times. For instance when summing voices for a synthesizer.
362    /// `block_values[..block_len]` will be filled with the smoothed values. This is simply a
363    /// convenient function for [`next_block_exact()`][Self::next_block_exact()] when iterating over
364    /// variable length blocks with a known maximum size.
365    ///
366    /// # Panics
367    ///
368    /// Panics if `block_len > block_values.len()`.
369    pub fn next_block(&self, block_values: &mut [T], block_len: usize) {
370        self.next_block_exact(&mut block_values[..block_len])
371    }
372
373    /// The same as [`next_block()`][Self::next_block()], but filling the entire slice.
374    pub fn next_block_exact(&self, block_values: &mut [T]) {
375        let target = T::atomic_load(&self.target);
376
377        // `self.next()` will yield the current value if the parameter is no longer smoothing, but
378        // it's a bit of a waste to continuously call that if only the first couple or none of the
379        // values in `block_values` would require smoothing and the rest don't. Instead, we'll just
380        // smooth the values as necessary, and then reuse the target value for the rest of the
381        // block.
382        let steps_left = self.steps_left.load(Ordering::Relaxed) as usize;
383        let num_smoothed_values = block_values.len().min(steps_left);
384        if num_smoothed_values > 0 {
385            let mut current = self.current.load(Ordering::Relaxed);
386            let target_f32 = target.to_f32();
387            let step_size = self.step_size.load(Ordering::Relaxed);
388
389            if num_smoothed_values == steps_left {
390                // This is the same as calling `next()` `num_smoothed_values` times, but with some
391                // conditionals optimized out
392                block_values[..num_smoothed_values - 1].fill_with(|| {
393                    current = self.style.next(current, target_f32, step_size);
394                    T::from_f32(current)
395                });
396
397                // In `next()` the last step snaps the value to the target value, so we'll do the
398                // same thing here
399                current = target_f32.to_f32();
400                block_values[num_smoothed_values - 1] = target;
401            } else {
402                block_values[..num_smoothed_values].fill_with(|| {
403                    current = self.style.next(current, target_f32, step_size);
404                    T::from_f32(current)
405                });
406            }
407
408            block_values[num_smoothed_values..].fill(target);
409
410            self.current.store(current, Ordering::Relaxed);
411            self.steps_left
412                .fetch_sub(num_smoothed_values as i32, Ordering::Relaxed);
413        } else {
414            block_values.fill(target);
415        }
416    }
417
418    /// The same as [`next_block()`][Self::next_block()], but with a function applied to each
419    /// produced value. The mapping function takes an index in the block and a floating point
420    /// representation of the smoother's current value. This allows the modulation to be consistent
421    /// during smoothing. Additionally, the mapping function is always called even if the smoothing
422    /// is finished.
423    pub fn next_block_mapped(
424        &self,
425        block_values: &mut [T],
426        block_len: usize,
427        f: impl FnMut(usize, f32) -> T,
428    ) {
429        self.next_block_exact_mapped(&mut block_values[..block_len], f)
430    }
431
432    /// The same as [`next_block_exact()`][Self::next_block()], but with a function applied to each
433    /// produced value. Useful when applying modulation to a smoothed parameter.
434    pub fn next_block_exact_mapped(
435        &self,
436        block_values: &mut [T],
437        mut f: impl FnMut(usize, f32) -> T,
438    ) {
439        // This works exactly the same as `next_block_exact()`, except for the addition of the
440        // mapping function
441        let target_f32 = T::atomic_load(&self.target).to_f32();
442
443        let steps_left = self.steps_left.load(Ordering::Relaxed) as usize;
444        let num_smoothed_values = block_values.len().min(steps_left);
445        if num_smoothed_values > 0 {
446            let mut current = self.current.load(Ordering::Relaxed);
447            let step_size = self.step_size.load(Ordering::Relaxed);
448
449            // See `next_block_exact()` for more details
450            if num_smoothed_values == steps_left {
451                for (idx, value) in block_values
452                    .iter_mut()
453                    .enumerate()
454                    .take(num_smoothed_values - 1)
455                {
456                    current = self.style.next(current, target_f32, step_size);
457                    *value = f(idx, current);
458                }
459
460                current = target_f32.to_f32();
461                block_values[num_smoothed_values - 1] = f(num_smoothed_values - 1, target_f32);
462            } else {
463                for (idx, value) in block_values
464                    .iter_mut()
465                    .enumerate()
466                    .take(num_smoothed_values)
467                {
468                    current = self.style.next(current, target_f32, step_size);
469                    *value = f(idx, current);
470                }
471            }
472
473            for (idx, value) in block_values
474                .iter_mut()
475                .enumerate()
476                .skip(num_smoothed_values)
477            {
478                *value = f(idx, target_f32);
479            }
480
481            self.current.store(current, Ordering::Relaxed);
482            self.steps_left
483                .fetch_sub(num_smoothed_values as i32, Ordering::Relaxed);
484        } else {
485            for (idx, value) in block_values.iter_mut().enumerate() {
486                *value = f(idx, target_f32);
487            }
488        }
489    }
490}
491
492impl Smoothable for f32 {
493    type Atomic = AtomicF32;
494
495    #[inline]
496    fn to_f32(self) -> f32 {
497        self
498    }
499
500    #[inline]
501    fn from_f32(value: f32) -> Self {
502        value
503    }
504
505    #[inline]
506    fn atomic_new(value: Self) -> Self::Atomic {
507        AtomicF32::new(value)
508    }
509
510    #[inline]
511    fn atomic_load(this: &Self::Atomic) -> Self {
512        this.load(Ordering::Relaxed)
513    }
514
515    #[inline]
516    fn atomic_store(this: &Self::Atomic, value: Self) {
517        this.store(value, Ordering::Relaxed)
518    }
519}
520
521impl Smoothable for i32 {
522    type Atomic = AtomicI32;
523
524    #[inline]
525    fn to_f32(self) -> f32 {
526        self as f32
527    }
528
529    #[inline]
530    fn from_f32(value: f32) -> Self {
531        value.round() as i32
532    }
533
534    #[inline]
535    fn atomic_new(value: Self) -> Self::Atomic {
536        AtomicI32::new(value)
537    }
538
539    #[inline]
540    fn atomic_load(this: &Self::Atomic) -> Self {
541        this.load(Ordering::Relaxed)
542    }
543
544    #[inline]
545    fn atomic_store(this: &Self::Atomic, value: Self) {
546        this.store(value, Ordering::Relaxed)
547    }
548}
549
550#[cfg(test)]
551mod tests {
552    use super::*;
553
554    /// Applying `next()` `n` times should be the same as `next_step()` for `n` steps.
555    #[test]
556    fn linear_f32_next_equivalence() {
557        let style = SmoothingStyle::Linear(100.0);
558
559        let mut current = 0.4;
560        let target = 0.8;
561        let steps = 15;
562        let step_size = style.step_size(current, target, steps);
563
564        let expected_result = style.next_step(current, target, step_size, steps);
565        for _ in 0..steps {
566            current = style.next(current, target, step_size);
567        }
568
569        approx::assert_relative_eq!(current, expected_result, epsilon = 1e-5);
570    }
571
572    #[test]
573    fn logarithmic_f32_next_equivalence() {
574        let style = SmoothingStyle::Logarithmic(100.0);
575
576        let mut current = 0.4;
577        let target = 0.8;
578        let steps = 15;
579        let step_size = style.step_size(current, target, steps);
580
581        let expected_result = style.next_step(current, target, step_size, steps);
582        for _ in 0..steps {
583            current = style.next(current, target, step_size);
584        }
585
586        approx::assert_relative_eq!(current, expected_result, epsilon = 1e-5);
587    }
588
589    #[test]
590    fn exponential_f32_next_equivalence() {
591        let style = SmoothingStyle::Exponential(100.0);
592
593        let mut current = 0.4;
594        let target = 0.8;
595        let steps = 15;
596        let step_size = style.step_size(current, target, steps);
597
598        let expected_result = style.next_step(current, target, step_size, steps);
599        for _ in 0..steps {
600            current = style.next(current, target, step_size);
601        }
602
603        approx::assert_relative_eq!(current, expected_result, epsilon = 1e-5);
604    }
605
606    #[test]
607    fn linear_f32_smoothing() {
608        let smoother: Smoother<f32> = Smoother::new(SmoothingStyle::Linear(100.0));
609        smoother.reset(10.0);
610        assert_eq!(smoother.next(), 10.0);
611
612        // Instead of testing the actual values, we'll make sure that we reach the target values at
613        // the expected time.
614        smoother.set_target(100.0, 20.0);
615        for _ in 0..(10 - 2) {
616            smoother.next();
617        }
618        assert_ne!(smoother.next(), 20.0);
619        assert_eq!(smoother.next(), 20.0);
620    }
621
622    #[test]
623    fn linear_i32_smoothing() {
624        let smoother: Smoother<i32> = Smoother::new(SmoothingStyle::Linear(100.0));
625        smoother.reset(10);
626        assert_eq!(smoother.next(), 10);
627
628        // Integers are rounded, but with these values we can still test this
629        smoother.set_target(100.0, 20);
630        for _ in 0..(10 - 2) {
631            smoother.next();
632        }
633        assert_ne!(smoother.next(), 20);
634        assert_eq!(smoother.next(), 20);
635    }
636
637    #[test]
638    fn logarithmic_f32_smoothing() {
639        let smoother: Smoother<f32> = Smoother::new(SmoothingStyle::Logarithmic(100.0));
640        smoother.reset(10.0);
641        assert_eq!(smoother.next(), 10.0);
642
643        // Instead of testing the actual values, we'll make sure that we reach the target values at
644        // the expected time.
645        smoother.set_target(100.0, 20.0);
646        for _ in 0..(10 - 2) {
647            smoother.next();
648        }
649        assert_ne!(smoother.next(), 20.0);
650        assert_eq!(smoother.next(), 20.0);
651    }
652
653    #[test]
654    fn logarithmic_i32_smoothing() {
655        let smoother: Smoother<i32> = Smoother::new(SmoothingStyle::Logarithmic(100.0));
656        smoother.reset(10);
657        assert_eq!(smoother.next(), 10);
658
659        // Integers are rounded, but with these values we can still test this
660        smoother.set_target(100.0, 20);
661        for _ in 0..(10 - 2) {
662            smoother.next();
663        }
664        assert_ne!(smoother.next(), 20);
665        assert_eq!(smoother.next(), 20);
666    }
667
668    /// Same as [`linear_f32_smoothing`], but skipping steps instead.
669    #[test]
670    fn skipping_linear_f32_smoothing() {
671        let smoother: Smoother<f32> = Smoother::new(SmoothingStyle::Linear(100.0));
672        smoother.reset(10.0);
673        assert_eq!(smoother.next(), 10.0);
674
675        smoother.set_target(100.0, 20.0);
676        smoother.next_step(8);
677        assert_ne!(smoother.next(), 20.0);
678        assert_eq!(smoother.next(), 20.0);
679    }
680
681    /// Same as [`linear_i32_smoothing`], but skipping steps instead.
682    #[test]
683    fn skipping_linear_i32_smoothing() {
684        let smoother: Smoother<i32> = Smoother::new(SmoothingStyle::Linear(100.0));
685        smoother.reset(10);
686        assert_eq!(smoother.next(), 10);
687
688        smoother.set_target(100.0, 20);
689        smoother.next_step(8);
690        assert_ne!(smoother.next(), 20);
691        assert_eq!(smoother.next(), 20);
692    }
693
694    /// Same as [`logarithmic_f32_smoothing`], but skipping steps instead.
695    #[test]
696    fn skipping_logarithmic_f32_smoothing() {
697        let smoother: Smoother<f32> = Smoother::new(SmoothingStyle::Logarithmic(100.0));
698        smoother.reset(10.0);
699        assert_eq!(smoother.next(), 10.0);
700
701        smoother.set_target(100.0, 20.0);
702        smoother.next_step(8);
703        assert_ne!(smoother.next(), 20.0);
704        assert_eq!(smoother.next(), 20.0);
705    }
706
707    /// Same as [`logarithmic_i32_smoothing`], but skipping steps instead.
708    #[test]
709    fn skipping_logarithmic_i32_smoothing() {
710        let smoother: Smoother<i32> = Smoother::new(SmoothingStyle::Logarithmic(100.0));
711        smoother.reset(10);
712        assert_eq!(smoother.next(), 10);
713
714        smoother.set_target(100.0, 20);
715        smoother.next_step(8);
716        assert_ne!(smoother.next(), 20);
717        assert_eq!(smoother.next(), 20);
718    }
719
720    // TODO: Tests for the exponential smoothing
721}