use rill_core::math::Transcendental;
use rill_core::traits::ProcessResult;
use rill_core::traits::{ActionContext, Algorithm, AlgorithmCategory, AlgorithmMetadata};
#[derive(Debug, Clone)]
pub struct ParamSmoother<T: Transcendental> {
current: T,
target: T,
coeff: T,
}
impl<T: Transcendental> ParamSmoother<T> {
pub fn new(coeff: T) -> Self {
Self {
current: T::ZERO,
target: T::ZERO,
coeff,
}
}
pub fn set_coeff(&mut self, coeff: T) {
self.coeff = coeff;
}
pub fn current(&self) -> T {
self.current
}
pub fn target(&self) -> T {
self.target
}
pub fn snap_to(&mut self, value: T) {
self.current = value;
self.target = value;
}
pub fn next(&mut self) -> T {
let diff = self.target.sub(self.current);
let step = diff.mul(self.coeff);
self.current = self.current.add(step);
self.current
}
}
impl<T: Transcendental> Algorithm<T> for ParamSmoother<T> {
fn process(
&mut self,
_input: Option<&[T]>,
output: &mut [T],
_ctx: &ActionContext,
) -> ProcessResult<()> {
for sample in output.iter_mut() {
*sample = self.next();
}
Ok(())
}
fn apply_command(&mut self, value: T) {
self.target = value;
}
fn init(&mut self, _sample_rate: f32) {}
fn reset(&mut self) {
self.current = T::ZERO;
self.target = T::ZERO;
}
fn metadata(&self) -> AlgorithmMetadata {
AlgorithmMetadata {
name: "ParamSmoother",
category: AlgorithmCategory::Utility,
description: "One-pole smoother for zipper-free parameter transitions",
author: "Rill",
version: "0.1.0",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rill_core::time::ClockTick;
#[test]
fn test_smoother_basic() {
let mut s = ParamSmoother::new(0.5f32);
let tick = ClockTick::default();
let ctx = ActionContext::new(&tick);
s.apply_command(1.0);
let mut buf = [0.0f32; 4];
s.process(None, &mut buf, &ctx).unwrap();
assert!((buf[0] - 0.5).abs() < 1e-6);
assert!((buf[1] - 0.75).abs() < 1e-6);
}
#[test]
fn test_smoother_snap() {
let mut s = ParamSmoother::new(0.1f32);
s.snap_to(42.0);
assert!((s.current() - 42.0).abs() < 1e-6);
assert!((s.target() - 42.0).abs() < 1e-6);
}
#[test]
fn test_smoother_empty_block() {
let mut s = ParamSmoother::new(0.1f32);
let tick = ClockTick::default();
let ctx = ActionContext::new(&tick);
let buf: &mut [f32] = &mut [];
assert!(s.process(None, buf, &ctx).is_ok());
}
}