rill_core_dsp/
smoothing.rs1use rill_core::math::Transcendental;
6use rill_core::traits::ProcessResult;
7use rill_core::traits::{Algorithm, AlgorithmCategory, AlgorithmMetadata};
8
9#[derive(Debug, Clone)]
31pub struct ParamSmoother<T: Transcendental> {
32 current: T,
34 target: T,
36 coeff: T,
38}
39
40impl<T: Transcendental> ParamSmoother<T> {
41 pub fn new(coeff: T) -> Self {
45 Self {
46 current: T::ZERO,
47 target: T::ZERO,
48 coeff,
49 }
50 }
51
52 pub fn set_coeff(&mut self, coeff: T) {
54 self.coeff = coeff;
55 }
56
57 pub fn current(&self) -> T {
59 self.current
60 }
61
62 pub fn target(&self) -> T {
64 self.target
65 }
66
67 pub fn snap_to(&mut self, value: T) {
69 self.current = value;
70 self.target = value;
71 }
72
73 #[allow(clippy::should_implement_trait)]
75 pub fn next(&mut self) -> T {
76 let diff = self.target.sub(self.current);
77 let step = diff.mul(self.coeff);
78 self.current = self.current.add(step);
79 self.current
80 }
81}
82
83impl<T: Transcendental> Algorithm<T> for ParamSmoother<T> {
84 fn process(&mut self, _input: Option<&[T]>, output: &mut [T]) -> ProcessResult<()> {
85 for sample in output.iter_mut() {
86 *sample = self.next();
87 }
88 Ok(())
89 }
90
91 fn apply_command(&mut self, value: T) {
92 self.target = value;
93 }
94
95 fn init(&mut self, _sample_rate: f32) {}
96
97 fn reset(&mut self) {
98 self.current = T::ZERO;
99 self.target = T::ZERO;
100 }
101
102 fn metadata(&self) -> AlgorithmMetadata {
103 AlgorithmMetadata {
104 name: "ParamSmoother",
105 category: AlgorithmCategory::Utility,
106 description: "One-pole smoother for zipper-free parameter transitions",
107 author: "Rill",
108 version: "0.1.0",
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn test_smoother_basic() {
119 let mut s = ParamSmoother::new(0.5f32);
120
121 s.apply_command(1.0);
122 let mut buf = [0.0f32; 4];
123 s.process(None, &mut buf).unwrap();
124 assert!((buf[0] - 0.5).abs() < 1e-6);
126 assert!((buf[1] - 0.75).abs() < 1e-6);
128 }
129
130 #[test]
131 fn test_smoother_snap() {
132 let mut s = ParamSmoother::new(0.1f32);
133 s.snap_to(42.0);
134 assert!((s.current() - 42.0).abs() < 1e-6);
135 assert!((s.target() - 42.0).abs() < 1e-6);
136 }
137
138 #[test]
139 fn test_smoother_empty_block() {
140 let mut s = ParamSmoother::new(0.1f32);
141 let buf: &mut [f32] = &mut [];
142 assert!(s.process(None, buf).is_ok());
143 }
144}