use std::marker::PhantomData;
use bbx_core::flush_denormal_f64;
use crate::sample::Sample;
const INV_1000: f64 = 1.0 / 1000.0;
const MIN_RAMP_LENGTH_MS: f64 = 0.01;
#[inline]
fn is_approximately_equal<S: Sample>(a: S, b: S) -> bool {
let a_f64 = a.to_f64();
let b_f64 = b.to_f64();
let epsilon = S::EPSILON.to_f64() * a_f64.abs().max(b_f64.abs()).max(1.0);
(a_f64 - b_f64).abs() < epsilon
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Linear;
#[derive(Debug, Clone, Copy, Default)]
pub struct Multiplicative;
pub trait SmoothingStrategy: Clone + Default {
fn update_increment<S: Sample>(current: S, target: S, num_samples: f64) -> f64;
fn apply_increment<S: Sample>(current: S, increment: f64) -> S;
fn apply_increment_n<S: Sample>(current: S, increment: f64, n: i32) -> S;
fn default_value<S: Sample>() -> S;
}
impl SmoothingStrategy for Linear {
#[inline]
fn update_increment<S: Sample>(current: S, target: S, num_samples: f64) -> f64 {
(target.to_f64() - current.to_f64()) / num_samples
}
#[inline]
fn apply_increment<S: Sample>(current: S, increment: f64) -> S {
S::from_f64(current.to_f64() + increment)
}
#[inline]
fn apply_increment_n<S: Sample>(current: S, increment: f64, n: i32) -> S {
S::from_f64(current.to_f64() + increment * n as f64)
}
#[inline]
fn default_value<S: Sample>() -> S {
S::ZERO
}
}
impl SmoothingStrategy for Multiplicative {
#[inline]
fn update_increment<S: Sample>(current: S, target: S, num_samples: f64) -> f64 {
let current_f64 = current.to_f64();
let target_f64 = target.to_f64();
if current_f64 > 0.0 && target_f64 > 0.0 {
(target_f64 / current_f64).ln() / num_samples
} else {
0.0
}
}
#[inline]
fn apply_increment<S: Sample>(current: S, increment: f64) -> S {
S::from_f64(current.to_f64() * increment.exp())
}
#[inline]
fn apply_increment_n<S: Sample>(current: S, increment: f64, n: i32) -> S {
S::from_f64(current.to_f64() * (increment * n as f64).exp())
}
#[inline]
fn default_value<S: Sample>() -> S {
S::ONE
}
}
#[derive(Debug, Clone)]
pub struct SmoothedValue<S: Sample, T: SmoothingStrategy> {
sample_rate: f64,
ramp_length_millis: f64,
current_value: S,
target_value: S,
increment: f64, _marker: PhantomData<T>,
}
impl<S: Sample, T: SmoothingStrategy> SmoothedValue<S, T> {
pub fn new(initial_value: S) -> Self {
Self {
sample_rate: 44100.0,
ramp_length_millis: 50.0,
current_value: initial_value,
target_value: initial_value,
increment: 0.0,
_marker: PhantomData,
}
}
pub fn reset(&mut self, sample_rate: f64, ramp_length_millis: f64) {
if sample_rate > 0.0 && ramp_length_millis >= 0.0 {
self.sample_rate = sample_rate;
self.ramp_length_millis = ramp_length_millis.max(MIN_RAMP_LENGTH_MS);
self.update_increment();
}
}
#[inline]
pub fn set_target_value(&mut self, value: S) {
self.target_value = value;
self.update_increment();
}
#[inline]
pub fn get_next_value(&mut self) -> S {
if is_approximately_equal(self.current_value, self.target_value) {
self.current_value = self.target_value;
return self.current_value;
}
self.current_value = T::apply_increment::<S>(self.current_value, self.increment);
let current_f64 = flush_denormal_f64(self.current_value.to_f64());
self.current_value = S::from_f64(current_f64);
let target_f64 = self.target_value.to_f64();
if (self.increment > 0.0 && current_f64 > target_f64) || (self.increment < 0.0 && current_f64 < target_f64) {
self.current_value = self.target_value;
}
self.current_value
}
#[inline]
pub fn skip(&mut self, num_samples: i32) {
if is_approximately_equal(self.current_value, self.target_value) {
self.current_value = self.target_value;
return;
}
let new_value = T::apply_increment_n::<S>(self.current_value, self.increment, num_samples);
let new_f64 = flush_denormal_f64(new_value.to_f64());
let target_f64 = self.target_value.to_f64();
if (self.increment > 0.0 && new_f64 > target_f64) || (self.increment < 0.0 && new_f64 < target_f64) {
self.current_value = self.target_value;
} else {
self.current_value = S::from_f64(new_f64);
}
}
#[inline]
pub fn current(&self) -> S {
self.current_value
}
#[inline]
pub fn target(&self) -> S {
self.target_value
}
#[inline]
pub fn is_smoothing(&self) -> bool {
!is_approximately_equal(self.current_value, self.target_value)
}
#[inline]
pub fn set_immediate(&mut self, value: S) {
self.current_value = value;
self.target_value = value;
self.increment = 0.0;
}
#[inline]
fn update_increment(&mut self) {
let ramp_ms = self.ramp_length_millis.max(MIN_RAMP_LENGTH_MS);
let num_samples = ramp_ms * self.sample_rate * INV_1000;
self.increment = T::update_increment::<S>(self.current_value, self.target_value, num_samples);
}
}
impl<S: Sample, T: SmoothingStrategy> Default for SmoothedValue<S, T> {
fn default() -> Self {
Self::new(T::default_value::<S>())
}
}
pub type LinearSmoothedValue<S> = SmoothedValue<S, Linear>;
pub type MultiplicativeSmoothedValue<S> = SmoothedValue<S, Multiplicative>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_linear_immediate_value() {
let mut sv = LinearSmoothedValue::<f32>::new(1.0);
assert_eq!(sv.current(), 1.0);
assert_eq!(sv.get_next_value(), 1.0);
assert!(!sv.is_smoothing());
}
#[test]
fn test_linear_smoothing_f32() {
let mut sv = LinearSmoothedValue::<f32>::new(0.0);
sv.reset(1000.0, 4.0); sv.set_target_value(1.0);
assert!(sv.is_smoothing());
let v1 = sv.get_next_value();
assert!((v1 - 0.25).abs() < 0.01, "Expected ~0.25, got {}", v1);
let v2 = sv.get_next_value();
assert!((v2 - 0.5).abs() < 0.01, "Expected ~0.5, got {}", v2);
let v3 = sv.get_next_value();
assert!((v3 - 0.75).abs() < 0.01, "Expected ~0.75, got {}", v3);
let v4 = sv.get_next_value();
assert!((v4 - 1.0).abs() < 0.01, "Expected ~1.0, got {}", v4);
}
#[test]
fn test_linear_smoothing_f64() {
let mut sv = LinearSmoothedValue::<f64>::new(0.0);
sv.reset(1000.0, 4.0);
sv.set_target_value(1.0);
assert!(sv.is_smoothing());
let v1 = sv.get_next_value();
assert!((v1 - 0.25).abs() < 0.01, "Expected ~0.25, got {}", v1);
let v2 = sv.get_next_value();
assert!((v2 - 0.5).abs() < 0.01, "Expected ~0.5, got {}", v2);
let v3 = sv.get_next_value();
assert!((v3 - 0.75).abs() < 0.01, "Expected ~0.75, got {}", v3);
let v4 = sv.get_next_value();
assert!((v4 - 1.0).abs() < 0.01, "Expected ~1.0, got {}", v4);
}
#[test]
fn test_zero_ramp_length() {
let mut sv = LinearSmoothedValue::<f32>::new(0.0);
sv.reset(44100.0, 0.0);
sv.set_target_value(1.0);
assert!(sv.is_smoothing());
for _ in 0..5 {
sv.get_next_value();
}
assert_eq!(sv.current(), 1.0);
assert!(!sv.is_smoothing());
}
#[test]
fn test_skip() {
let mut sv = LinearSmoothedValue::<f32>::new(0.0);
sv.reset(1000.0, 10.0); sv.set_target_value(1.0);
sv.skip(5);
assert!((sv.current() - 0.5).abs() < 0.01);
sv.skip(10); assert_eq!(sv.current(), 1.0);
}
#[test]
fn test_retarget_during_smoothing() {
let mut sv = LinearSmoothedValue::<f32>::new(0.0);
sv.reset(1000.0, 4.0);
sv.set_target_value(1.0);
sv.get_next_value(); sv.get_next_value();
sv.set_target_value(0.0);
assert!(sv.is_smoothing());
}
#[test]
fn test_multiplicative_default() {
let sv = MultiplicativeSmoothedValue::<f32>::default();
assert_eq!(sv.current(), 1.0);
}
#[test]
fn test_multiplicative_smoothing() {
let mut sv = MultiplicativeSmoothedValue::<f32>::new(1.0);
sv.reset(1000.0, 10.0);
sv.set_target_value(2.0);
assert!(sv.is_smoothing());
let mut prev = sv.current();
for _ in 0..10 {
let curr = sv.get_next_value();
assert!(curr > prev, "Value should increase");
prev = curr;
}
}
#[test]
fn test_set_immediate() {
let mut sv = LinearSmoothedValue::<f32>::new(0.0);
sv.reset(44100.0, 50.0);
sv.set_target_value(1.0);
assert!(sv.is_smoothing());
sv.set_immediate(0.5);
assert_eq!(sv.current(), 0.5);
assert_eq!(sv.target(), 0.5);
assert!(!sv.is_smoothing());
}
}