rill_core_dsp/
smoothing.rs1use rill_core::math::Transcendental;
6use rill_core::traits::ProcessResult;
7use rill_core::traits::{ActionContext, 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 pub fn next(&mut self) -> T {
75 let diff = self.target.sub(self.current);
76 let step = diff.mul(self.coeff);
77 self.current = self.current.add(step);
78 self.current
79 }
80}
81
82impl<T: Transcendental> Algorithm<T> for ParamSmoother<T> {
83 fn process(
84 &mut self,
85 _input: Option<&[T]>,
86 output: &mut [T],
87 _ctx: &ActionContext,
88 ) -> ProcessResult<()> {
89 for sample in output.iter_mut() {
90 *sample = self.next();
91 }
92 Ok(())
93 }
94
95 fn apply_command(&mut self, value: T) {
96 self.target = value;
97 }
98
99 fn init(&mut self, _sample_rate: f32) {}
100
101 fn reset(&mut self) {
102 self.current = T::ZERO;
103 self.target = T::ZERO;
104 }
105
106 fn metadata(&self) -> AlgorithmMetadata {
107 AlgorithmMetadata {
108 name: "ParamSmoother",
109 category: AlgorithmCategory::Utility,
110 description: "One-pole smoother for zipper-free parameter transitions",
111 author: "Rill",
112 version: "0.1.0",
113 }
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use rill_core::time::ClockTick;
121
122 #[test]
123 fn test_smoother_basic() {
124 let mut s = ParamSmoother::new(0.5f32);
125 let tick = ClockTick::default();
126 let ctx = ActionContext::new(&tick);
127
128 s.apply_command(1.0);
129 let mut buf = [0.0f32; 4];
130 s.process(None, &mut buf, &ctx).unwrap();
131 assert!((buf[0] - 0.5).abs() < 1e-6);
133 assert!((buf[1] - 0.75).abs() < 1e-6);
135 }
136
137 #[test]
138 fn test_smoother_snap() {
139 let mut s = ParamSmoother::new(0.1f32);
140 s.snap_to(42.0);
141 assert!((s.current() - 42.0).abs() < 1e-6);
142 assert!((s.target() - 42.0).abs() < 1e-6);
143 }
144
145 #[test]
146 fn test_smoother_empty_block() {
147 let mut s = ParamSmoother::new(0.1f32);
148 let tick = ClockTick::default();
149 let ctx = ActionContext::new(&tick);
150 let buf: &mut [f32] = &mut [];
151 assert!(s.process(None, buf, &ctx).is_ok());
152 }
153}