use serde::{Deserialize, Serialize};
const DEFAULT_SMOOTH_TIME: f32 = 0.005;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct SmoothedParam {
current: f32,
target: f32,
alpha: f32,
}
impl SmoothedParam {
pub fn new(initial: f32, sample_rate: f32) -> Self {
Self {
current: initial,
target: initial,
alpha: Self::alpha_from_time(DEFAULT_SMOOTH_TIME, sample_rate),
}
}
#[allow(dead_code)]
pub fn with_time(initial: f32, smooth_time_secs: f32, sample_rate: f32) -> Self {
Self {
current: initial,
target: initial,
alpha: Self::alpha_from_time(smooth_time_secs, sample_rate),
}
}
#[inline]
pub fn set_target(&mut self, target: f32) {
self.target = target;
}
#[inline]
pub fn next(&mut self) -> f32 {
if (self.current - self.target).abs() < 1e-8 {
self.current = self.target;
} else {
self.current = self.alpha * self.current + (1.0 - self.alpha) * self.target;
}
self.current
}
#[inline]
#[allow(dead_code)]
pub fn value(&self) -> f32 {
self.current
}
#[inline]
#[allow(dead_code)]
pub fn set_immediate(&mut self, value: f32) {
self.current = value;
self.target = value;
}
fn alpha_from_time(time_secs: f32, sample_rate: f32) -> f32 {
if time_secs <= 0.0 || sample_rate <= 0.0 {
return 0.0; }
let samples = time_secs * sample_rate;
crate::math::f32::exp(-1.0 / samples)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_immediate_convergence() {
let mut p = SmoothedParam::new(0.0, 44100.0);
p.set_immediate(1.0);
assert!((p.next() - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_smooth_approach() {
let mut p = SmoothedParam::new(0.0, 44100.0);
p.set_target(1.0);
for _ in 0..2000 {
p.next();
}
assert!(
(p.next() - 1.0).abs() < 0.001,
"should converge: got {}",
p.value()
);
}
#[test]
fn test_zero_time_is_instant() {
let mut p = SmoothedParam::with_time(0.0, 0.0, 44100.0);
p.set_target(1.0);
assert!((p.next() - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_serde_roundtrip() {
let p = SmoothedParam::new(0.5, 44100.0);
let json = serde_json::to_string(&p).unwrap();
let p2: SmoothedParam = serde_json::from_str(&json).unwrap();
assert!((p2.value() - 0.5).abs() < f32::EPSILON);
}
}