2022-02-06 13:38:59 +11:00
|
|
|
use atomic_float::AtomicF32;
|
2022-03-02 03:29:09 +11:00
|
|
|
use parking_lot::{MappedMutexGuard, Mutex, MutexGuard};
|
2022-02-14 04:32:59 +11:00
|
|
|
use std::sync::atomic::{AtomicI32, Ordering};
|
2022-02-06 11:33:19 +11:00
|
|
|
|
2022-03-02 03:33:22 +11:00
|
|
|
use crate::buffer::Block;
|
|
|
|
|
2022-02-03 07:08:23 +11:00
|
|
|
/// Controls if and how parameters gets smoothed.
|
|
|
|
pub enum SmoothingStyle {
|
|
|
|
/// No smoothing is applied. The parameter's `value` field contains the latest sample value
|
|
|
|
/// available for the parameters.
|
|
|
|
None,
|
2022-02-03 09:00:17 +11:00
|
|
|
/// Smooth parameter changes so the current value approaches the target value at a constant
|
|
|
|
/// rate.
|
2022-02-03 08:00:00 +11:00
|
|
|
Linear(f32),
|
2022-02-03 09:00:17 +11:00
|
|
|
/// Smooth parameter changes such that the rate matches the curve of a logarithmic function.
|
|
|
|
/// This is useful for smoothing things like frequencies and decibel gain value. **The caveat is
|
|
|
|
/// that the value may never reach 0**, or you will end up multiplying and dividing things by
|
|
|
|
/// zero. Make sure your value ranges don't include 0.
|
|
|
|
Logarithmic(f32),
|
2022-02-03 07:08:23 +11:00
|
|
|
// TODO: Sample-accurate modes
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A smoother, providing a smoothed value for each sample.
|
2022-02-06 11:33:19 +11:00
|
|
|
//
|
|
|
|
// TODO: We need to use atomics here so we can share the params object with the GUI. Is there a
|
|
|
|
// better alternative to allow the process function to mutate these smoothers?
|
2022-02-03 07:08:23 +11:00
|
|
|
pub struct Smoother<T> {
|
|
|
|
/// The kind of snoothing that needs to be applied, if any.
|
|
|
|
style: SmoothingStyle,
|
|
|
|
/// The number of steps of smoothing left to take.
|
2022-02-14 04:32:59 +11:00
|
|
|
///
|
|
|
|
// This is a signed integer because we can skip multiple steps, which would otherwise make it
|
|
|
|
// possible to get an underflow here.
|
|
|
|
steps_left: AtomicI32,
|
2022-02-03 07:08:23 +11:00
|
|
|
/// The amount we should adjust the current value each sample to be able to reach the target in
|
|
|
|
/// the specified tiem frame. This is also a floating point number to keep the smoothing
|
|
|
|
/// uniform.
|
|
|
|
step_size: f32,
|
|
|
|
/// The value for the current sample. Always stored as floating point for obvious reasons.
|
2022-02-06 13:38:59 +11:00
|
|
|
current: AtomicF32,
|
2022-02-03 07:08:23 +11:00
|
|
|
/// The value we're smoothing towards
|
|
|
|
target: T,
|
2022-03-02 03:07:03 +11:00
|
|
|
|
|
|
|
/// A dense buffer containing smoothed values for an entire block of audio. Useful when using
|
|
|
|
/// [crate::Buffer::iter_blocks()] to process small blocks of audio multiple times.
|
|
|
|
block_values: Mutex<Vec<T>>,
|
2022-02-03 07:08:23 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: Default> Default for Smoother<T> {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
style: SmoothingStyle::None,
|
2022-02-14 04:32:59 +11:00
|
|
|
steps_left: AtomicI32::new(0),
|
2022-02-03 07:08:23 +11:00
|
|
|
step_size: Default::default(),
|
2022-02-06 13:38:59 +11:00
|
|
|
current: AtomicF32::new(0.0),
|
2022-02-03 07:08:23 +11:00
|
|
|
target: Default::default(),
|
2022-03-02 03:07:03 +11:00
|
|
|
|
|
|
|
block_values: Mutex::new(Vec::new()),
|
2022-02-03 07:08:23 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T: Default> Smoother<T> {
|
|
|
|
/// Use the specified style for the smoothing.
|
|
|
|
pub fn new(style: SmoothingStyle) -> Self {
|
|
|
|
Self {
|
|
|
|
style,
|
|
|
|
..Default::default()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Convenience function for not applying any smoothing at all. Same as `Smoother::default`.
|
|
|
|
pub fn none() -> Self {
|
|
|
|
Default::default()
|
|
|
|
}
|
2022-02-04 02:51:35 +11:00
|
|
|
|
2022-02-05 22:56:03 +11:00
|
|
|
/// Whether calling [Self::next()] will yield a new value or an old value. Useful if you need to
|
2022-02-04 02:51:35 +11:00
|
|
|
/// recompute something wheenver this parameter changes.
|
|
|
|
pub fn is_smoothing(&self) -> bool {
|
2022-02-06 11:33:19 +11:00
|
|
|
self.steps_left.load(Ordering::Relaxed) > 0
|
2022-02-04 02:51:35 +11:00
|
|
|
}
|
2022-03-02 03:07:03 +11:00
|
|
|
|
|
|
|
/// Allocate memory to store smoothed values for an entire block of audio. Call this in
|
|
|
|
/// [crate::Plugin::initialize()] with the same max block size you are going to pass to
|
|
|
|
/// [crate::Buffer::iter_blocks()].
|
|
|
|
pub fn initialize_block_smoother(&mut self, max_block_size: usize) {
|
|
|
|
self.block_values
|
|
|
|
.lock()
|
|
|
|
.resize_with(max_block_size, || T::default());
|
|
|
|
}
|
2022-02-03 07:08:23 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
// These are not iterators for the sole reason that this will always yield a value, and needing to
|
|
|
|
// unwrap all of those options is not going to be very fun.
|
|
|
|
impl Smoother<f32> {
|
2022-02-05 01:14:47 +11:00
|
|
|
/// Reset the smoother the specified value.
|
|
|
|
pub fn reset(&mut self, value: f32) {
|
|
|
|
self.target = value;
|
2022-02-06 13:38:59 +11:00
|
|
|
self.current.store(value, Ordering::Relaxed);
|
2022-02-06 11:33:19 +11:00
|
|
|
self.steps_left.store(0, Ordering::Relaxed);
|
2022-02-05 01:14:47 +11:00
|
|
|
}
|
|
|
|
|
2022-02-03 07:08:23 +11:00
|
|
|
/// Set the target value.
|
2022-02-05 01:14:47 +11:00
|
|
|
pub fn set_target(&mut self, sample_rate: f32, target: f32) {
|
2022-02-03 07:08:23 +11:00
|
|
|
self.target = target;
|
2022-02-06 11:33:19 +11:00
|
|
|
|
|
|
|
let steps_left = match self.style {
|
2022-02-05 01:14:47 +11:00
|
|
|
SmoothingStyle::None => 1,
|
|
|
|
SmoothingStyle::Linear(time) | SmoothingStyle::Logarithmic(time) => {
|
2022-02-14 04:32:59 +11:00
|
|
|
(sample_rate * time / 1000.0).round() as i32
|
2022-02-05 01:14:47 +11:00
|
|
|
}
|
|
|
|
};
|
2022-02-06 11:33:19 +11:00
|
|
|
self.steps_left.store(steps_left, Ordering::Relaxed);
|
|
|
|
|
2022-02-06 13:38:59 +11:00
|
|
|
let current = self.current.load(Ordering::Relaxed);
|
2022-02-05 01:14:47 +11:00
|
|
|
self.step_size = match self.style {
|
|
|
|
SmoothingStyle::None => 0.0,
|
2022-02-06 11:33:19 +11:00
|
|
|
SmoothingStyle::Linear(_) => (self.target - current) / steps_left as f32,
|
2022-02-05 01:14:47 +11:00
|
|
|
SmoothingStyle::Logarithmic(_) => {
|
|
|
|
// We need to solve `current * (step_size ^ steps_left) = target` for
|
|
|
|
// `step_size`
|
2022-02-06 11:33:19 +11:00
|
|
|
nih_debug_assert_ne!(current, 0.0);
|
|
|
|
(self.target / current).powf((steps_left as f32).recip())
|
2022-02-05 01:14:47 +11:00
|
|
|
}
|
|
|
|
};
|
2022-02-03 07:08:23 +11:00
|
|
|
}
|
|
|
|
|
2022-02-14 04:32:59 +11:00
|
|
|
/// Get the next value from this smoother. The value will be equal to the previous value once
|
2022-03-02 03:29:09 +11:00
|
|
|
/// the smoothing period is over. This should be called exactly once per sample.
|
2022-02-03 07:08:23 +11:00
|
|
|
// Yes, Clippy, like I said, this was intentional
|
|
|
|
#[allow(clippy::should_implement_trait)]
|
2022-03-02 02:55:30 +11:00
|
|
|
#[inline]
|
2022-02-06 11:33:19 +11:00
|
|
|
pub fn next(&self) -> f32 {
|
2022-02-14 04:32:59 +11:00
|
|
|
self.next_step(1)
|
|
|
|
}
|
|
|
|
|
2022-03-02 03:29:09 +11:00
|
|
|
/// Produce smoothed values for an entire block of audio. Used in conjunction with
|
|
|
|
/// [crate::Buffer::iter_blocks()]. Make sure to call
|
|
|
|
/// [crate::Plugin::initialize_block_smoothers()] with the same maximum buffer block size as the
|
|
|
|
/// one passed to `iter_blocks()` in your [crate::Plugin::initialize()] function first to
|
|
|
|
/// allocate memory for the block smoothing.
|
|
|
|
///
|
|
|
|
/// Returns a `None` value if the block length exceed's the allocated capacity.
|
|
|
|
#[inline]
|
2022-03-02 03:33:22 +11:00
|
|
|
pub fn next_block(&self, block: &Block) -> Option<MappedMutexGuard<[f32]>> {
|
2022-03-02 03:29:09 +11:00
|
|
|
let mut block_values = self.block_values.lock();
|
2022-03-02 03:33:22 +11:00
|
|
|
if block_values.len() < block.len() {
|
2022-03-02 03:29:09 +11:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: As a small optimization we could split this up into two loops for the smoothed and
|
|
|
|
// unsmoothed parts. Another worthwhile optimization would be to remember if the
|
|
|
|
// buffer is already filled with the target value and [Self::is_smoothing()] is false.
|
|
|
|
// In that case we wouldn't need to do anything ehre.
|
2022-03-02 03:33:22 +11:00
|
|
|
(&mut block_values[..block.len()]).fill_with(|| self.next());
|
2022-03-02 03:29:09 +11:00
|
|
|
|
|
|
|
Some(MutexGuard::map(block_values, |values| {
|
2022-03-02 03:33:22 +11:00
|
|
|
&mut values[..block.len()]
|
2022-03-02 03:29:09 +11:00
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
2022-02-14 04:32:59 +11:00
|
|
|
/// [Self::next()], but with the ability to skip forward in the smoother. [Self::next()] is
|
|
|
|
/// equivalent to calling this function with a `steps` value of 1. Calling this function with a
|
|
|
|
/// `steps` value of `n` means will cause you to skip the next `n - 1` values and return the
|
|
|
|
/// `n`th value.
|
2022-03-02 02:55:30 +11:00
|
|
|
#[inline]
|
2022-02-14 04:32:59 +11:00
|
|
|
pub fn next_step(&self, steps: u32) -> f32 {
|
|
|
|
nih_debug_assert_ne!(steps, 0);
|
|
|
|
|
2022-02-13 06:54:03 +11:00
|
|
|
if self.steps_left.load(Ordering::Relaxed) > 0 {
|
2022-02-06 13:38:59 +11:00
|
|
|
let current = self.current.load(Ordering::Relaxed);
|
2022-02-06 11:33:19 +11:00
|
|
|
|
2022-02-14 04:32:59 +11:00
|
|
|
// The number of steps usually won't fit exactly, so make sure we don't end up with
|
|
|
|
// quantization errors on overshoots or undershoots. We also need to account for the
|
|
|
|
// possibility that we only have `n < steps` steps left.
|
|
|
|
let old_steps_left = self.steps_left.fetch_sub(steps as i32, Ordering::Relaxed);
|
|
|
|
let new = if old_steps_left <= steps as i32 {
|
|
|
|
self.steps_left.store(0, Ordering::Relaxed);
|
2022-02-06 11:33:19 +11:00
|
|
|
self.target
|
2022-02-03 07:08:23 +11:00
|
|
|
} else {
|
|
|
|
match &self.style {
|
2022-02-06 11:33:19 +11:00
|
|
|
SmoothingStyle::None => self.target,
|
2022-02-14 04:32:59 +11:00
|
|
|
SmoothingStyle::Linear(_) => current + (self.step_size * steps as f32),
|
|
|
|
SmoothingStyle::Logarithmic(_) => current * (self.step_size.powi(steps as i32)),
|
2022-02-06 11:33:19 +11:00
|
|
|
}
|
|
|
|
};
|
2022-02-06 13:38:59 +11:00
|
|
|
self.current.store(new, Ordering::Relaxed);
|
2022-02-06 11:33:19 +11:00
|
|
|
|
|
|
|
new
|
2022-02-03 07:08:23 +11:00
|
|
|
} else {
|
|
|
|
self.target
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-02-03 07:26:20 +11:00
|
|
|
|
|
|
|
impl Smoother<i32> {
|
2022-02-05 01:14:47 +11:00
|
|
|
/// Reset the smoother the specified value.
|
|
|
|
pub fn reset(&mut self, value: i32) {
|
|
|
|
self.target = value;
|
2022-02-06 13:38:59 +11:00
|
|
|
self.current.store(value as f32, Ordering::Relaxed);
|
2022-02-06 11:33:19 +11:00
|
|
|
self.steps_left.store(0, Ordering::Relaxed);
|
2022-02-05 01:14:47 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_target(&mut self, sample_rate: f32, target: i32) {
|
2022-02-03 07:26:20 +11:00
|
|
|
self.target = target;
|
2022-02-06 11:33:19 +11:00
|
|
|
|
|
|
|
let steps_left = match self.style {
|
2022-02-05 01:14:47 +11:00
|
|
|
SmoothingStyle::None => 1,
|
|
|
|
SmoothingStyle::Linear(time) | SmoothingStyle::Logarithmic(time) => {
|
2022-02-14 04:32:59 +11:00
|
|
|
(sample_rate * time / 1000.0).round() as i32
|
2022-02-05 01:14:47 +11:00
|
|
|
}
|
|
|
|
};
|
2022-02-06 11:33:19 +11:00
|
|
|
self.steps_left.store(steps_left, Ordering::Relaxed);
|
|
|
|
|
2022-02-06 13:38:59 +11:00
|
|
|
let current = self.current.load(Ordering::Relaxed);
|
2022-02-05 01:14:47 +11:00
|
|
|
self.step_size = match self.style {
|
|
|
|
SmoothingStyle::None => 0.0,
|
2022-02-06 11:33:19 +11:00
|
|
|
SmoothingStyle::Linear(_) => (self.target as f32 - current) / steps_left as f32,
|
2022-02-05 01:14:47 +11:00
|
|
|
SmoothingStyle::Logarithmic(_) => {
|
2022-02-06 11:33:19 +11:00
|
|
|
nih_debug_assert_ne!(current, 0.0);
|
|
|
|
(self.target as f32 / current).powf((steps_left as f32).recip())
|
2022-02-05 01:14:47 +11:00
|
|
|
}
|
|
|
|
};
|
2022-02-03 07:26:20 +11:00
|
|
|
}
|
|
|
|
|
2022-02-14 04:32:59 +11:00
|
|
|
/// Get the next value from this smoother. The value will be equal to the previous value once
|
|
|
|
// the smoothing period is over. This should be called exactly once per sample.
|
|
|
|
// Yes, Clippy, like I said, this was intentional
|
2022-02-03 07:26:20 +11:00
|
|
|
#[allow(clippy::should_implement_trait)]
|
2022-02-14 04:32:59 +11:00
|
|
|
pub fn next(&self) -> i32 {
|
|
|
|
self.next_step(1)
|
|
|
|
}
|
|
|
|
|
2022-03-02 03:29:09 +11:00
|
|
|
/// Produce smoothed values for an entire block of audio. Used in conjunction with
|
|
|
|
/// [crate::Buffer::iter_blocks()]. Make sure to call
|
|
|
|
/// [crate::Plugin::initialize_block_smoothers()] with the same maximum buffer block size as the
|
|
|
|
/// one passed to `iter_blocks()` in your [crate::Plugin::initialize()] function first to
|
|
|
|
/// allocate memory for the block smoothing.
|
|
|
|
///
|
|
|
|
/// Returns a `None` value if the block length exceed's the allocated capacity.
|
|
|
|
#[inline]
|
|
|
|
pub fn next_block(&self, block_len: usize) -> Option<MappedMutexGuard<[i32]>> {
|
|
|
|
let mut block_values = self.block_values.lock();
|
|
|
|
if block_values.len() < block_len {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
(&mut block_values[..block_len]).fill_with(|| self.next());
|
|
|
|
|
|
|
|
Some(MutexGuard::map(block_values, |values| {
|
|
|
|
&mut values[..block_len]
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
2022-02-14 04:32:59 +11:00
|
|
|
/// [Self::next()], but with the ability to skip forward in the smoother. [Self::next()] is
|
|
|
|
/// equivalent to calling this function with a `steps` value of 1. Calling this function with a
|
|
|
|
/// `steps` value of `n` means will cause you to skip the next `n - 1` values and return the
|
|
|
|
/// `n`th value.
|
|
|
|
pub fn next_step(&self, steps: u32) -> i32 {
|
|
|
|
nih_debug_assert_ne!(steps, 0);
|
|
|
|
|
2022-02-13 06:54:03 +11:00
|
|
|
if self.steps_left.load(Ordering::Relaxed) > 0 {
|
2022-02-06 13:38:59 +11:00
|
|
|
let current = self.current.load(Ordering::Relaxed);
|
2022-02-06 11:33:19 +11:00
|
|
|
|
2022-02-14 04:32:59 +11:00
|
|
|
// The number of steps usually won't fit exactly, so make sure we don't end up with
|
|
|
|
// quantization errors on overshoots or undershoots. We also need to account for the
|
|
|
|
// possibility that we only have `n < steps` steps left.
|
|
|
|
let old_steps_left = self.steps_left.fetch_sub(steps as i32, Ordering::Relaxed);
|
|
|
|
let new = if old_steps_left <= steps as i32 {
|
|
|
|
self.steps_left.store(0, Ordering::Relaxed);
|
2022-02-06 11:33:19 +11:00
|
|
|
self.target as f32
|
2022-02-03 07:26:20 +11:00
|
|
|
} else {
|
|
|
|
match &self.style {
|
2022-02-06 11:33:19 +11:00
|
|
|
SmoothingStyle::None => self.target as f32,
|
2022-02-14 04:32:59 +11:00
|
|
|
SmoothingStyle::Linear(_) => current + (self.step_size * steps as f32),
|
|
|
|
SmoothingStyle::Logarithmic(_) => current * self.step_size.powi(steps as i32),
|
2022-02-06 11:33:19 +11:00
|
|
|
}
|
|
|
|
};
|
2022-02-06 13:38:59 +11:00
|
|
|
self.current.store(new, Ordering::Relaxed);
|
2022-02-06 11:33:19 +11:00
|
|
|
|
|
|
|
new.round() as i32
|
2022-02-03 07:26:20 +11:00
|
|
|
} else {
|
|
|
|
self.target
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-02-03 08:34:29 +11:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn linear_f32_smoothing() {
|
|
|
|
let mut smoother: Smoother<f32> = Smoother::new(SmoothingStyle::Linear(100.0));
|
2022-02-05 01:14:47 +11:00
|
|
|
smoother.reset(10.0);
|
2022-02-03 08:34:29 +11:00
|
|
|
assert_eq!(smoother.next(), 10.0);
|
|
|
|
|
|
|
|
// Instead of testing the actual values, we'll make sure that we reach the target values at
|
|
|
|
// the expected time.
|
2022-02-05 01:14:47 +11:00
|
|
|
smoother.set_target(100.0, 20.0);
|
2022-02-03 08:34:29 +11:00
|
|
|
for _ in 0..(10 - 2) {
|
2022-02-11 09:40:18 +11:00
|
|
|
smoother.next();
|
2022-02-03 08:34:29 +11:00
|
|
|
}
|
|
|
|
assert_ne!(smoother.next(), 20.0);
|
|
|
|
assert_eq!(smoother.next(), 20.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn linear_i32_smoothing() {
|
|
|
|
let mut smoother: Smoother<i32> = Smoother::new(SmoothingStyle::Linear(100.0));
|
2022-02-05 01:14:47 +11:00
|
|
|
smoother.reset(10);
|
2022-02-03 08:34:29 +11:00
|
|
|
assert_eq!(smoother.next(), 10);
|
|
|
|
|
|
|
|
// Integers are rounded, but with these values we can still test this
|
2022-02-05 01:14:47 +11:00
|
|
|
smoother.set_target(100.0, 20);
|
2022-02-03 08:34:29 +11:00
|
|
|
for _ in 0..(10 - 2) {
|
2022-02-11 09:40:18 +11:00
|
|
|
smoother.next();
|
2022-02-03 08:34:29 +11:00
|
|
|
}
|
|
|
|
assert_ne!(smoother.next(), 20);
|
|
|
|
assert_eq!(smoother.next(), 20);
|
|
|
|
}
|
2022-02-03 09:00:17 +11:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn logarithmic_f32_smoothing() {
|
|
|
|
let mut smoother: Smoother<f32> = Smoother::new(SmoothingStyle::Logarithmic(100.0));
|
2022-02-05 01:14:47 +11:00
|
|
|
smoother.reset(10.0);
|
2022-02-03 09:00:17 +11:00
|
|
|
assert_eq!(smoother.next(), 10.0);
|
|
|
|
|
|
|
|
// Instead of testing the actual values, we'll make sure that we reach the target values at
|
|
|
|
// the expected time.
|
2022-02-05 01:14:47 +11:00
|
|
|
smoother.set_target(100.0, 20.0);
|
2022-02-03 09:00:17 +11:00
|
|
|
for _ in 0..(10 - 2) {
|
2022-02-11 09:40:18 +11:00
|
|
|
smoother.next();
|
2022-02-03 09:00:17 +11:00
|
|
|
}
|
|
|
|
assert_ne!(smoother.next(), 20.0);
|
|
|
|
assert_eq!(smoother.next(), 20.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn logarithmic_i32_smoothing() {
|
|
|
|
let mut smoother: Smoother<i32> = Smoother::new(SmoothingStyle::Logarithmic(100.0));
|
2022-02-05 01:14:47 +11:00
|
|
|
smoother.reset(10);
|
2022-02-03 09:00:17 +11:00
|
|
|
assert_eq!(smoother.next(), 10);
|
|
|
|
|
|
|
|
// Integers are rounded, but with these values we can still test this
|
2022-02-05 01:14:47 +11:00
|
|
|
smoother.set_target(100.0, 20);
|
2022-02-03 09:00:17 +11:00
|
|
|
for _ in 0..(10 - 2) {
|
2022-02-11 09:40:18 +11:00
|
|
|
smoother.next();
|
2022-02-03 09:00:17 +11:00
|
|
|
}
|
|
|
|
assert_ne!(smoother.next(), 20);
|
|
|
|
assert_eq!(smoother.next(), 20);
|
|
|
|
}
|
2022-02-14 04:32:59 +11:00
|
|
|
|
|
|
|
/// Same as [linear_f32_smoothing], but skipping steps instead.
|
|
|
|
#[test]
|
|
|
|
fn skipping_linear_f32_smoothing() {
|
|
|
|
let mut smoother: Smoother<f32> = Smoother::new(SmoothingStyle::Linear(100.0));
|
|
|
|
smoother.reset(10.0);
|
|
|
|
assert_eq!(smoother.next(), 10.0);
|
|
|
|
|
|
|
|
smoother.set_target(100.0, 20.0);
|
|
|
|
smoother.next_step(8);
|
|
|
|
assert_ne!(smoother.next(), 20.0);
|
|
|
|
assert_eq!(smoother.next(), 20.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Same as [linear_i32_smoothing], but skipping steps instead.
|
|
|
|
#[test]
|
|
|
|
fn skipping_linear_i32_smoothing() {
|
|
|
|
let mut smoother: Smoother<i32> = Smoother::new(SmoothingStyle::Linear(100.0));
|
|
|
|
smoother.reset(10);
|
|
|
|
assert_eq!(smoother.next(), 10);
|
|
|
|
|
|
|
|
smoother.set_target(100.0, 20);
|
|
|
|
smoother.next_step(8);
|
|
|
|
assert_ne!(smoother.next(), 20);
|
|
|
|
assert_eq!(smoother.next(), 20);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Same as [logarithmic_f32_smoothing], but skipping steps instead.
|
|
|
|
#[test]
|
|
|
|
fn skipping_logarithmic_f32_smoothing() {
|
|
|
|
let mut smoother: Smoother<f32> = Smoother::new(SmoothingStyle::Logarithmic(100.0));
|
|
|
|
smoother.reset(10.0);
|
|
|
|
assert_eq!(smoother.next(), 10.0);
|
|
|
|
|
|
|
|
smoother.set_target(100.0, 20.0);
|
|
|
|
smoother.next_step(8);
|
|
|
|
assert_ne!(smoother.next(), 20.0);
|
|
|
|
assert_eq!(smoother.next(), 20.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Same as [logarithmic_i32_smoothing], but skipping steps instead.
|
|
|
|
#[test]
|
|
|
|
fn skipping_logarithmic_i32_smoothing() {
|
|
|
|
let mut smoother: Smoother<i32> = Smoother::new(SmoothingStyle::Logarithmic(100.0));
|
|
|
|
smoother.reset(10);
|
|
|
|
assert_eq!(smoother.next(), 10);
|
|
|
|
|
|
|
|
smoother.set_target(100.0, 20);
|
|
|
|
smoother.next_step(8);
|
|
|
|
assert_ne!(smoother.next(), 20);
|
|
|
|
assert_eq!(smoother.next(), 20);
|
|
|
|
}
|
2022-02-03 08:34:29 +11:00
|
|
|
}
|