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