idsp/
cic.rs

1use core::ops::AddAssign;
2
3use num_traits::{AsPrimitive, Num, Pow, WrappingAdd, WrappingSub};
4
5use dsp_process::Process;
6
7/// Cascaded integrator comb structure
8///
9/// Order `N` where `N = 3` is cubic.
10/// Comb delay `M` where `M = 1` is typical.
11/// Use `rate=0` and some larger `M` to implemnent a unit-rate lowpass.
12#[derive(Clone, Debug)]
13pub struct Cic<T, const N: usize, const M: usize = 1> {
14    /// Rate change (fast/slow - 1)
15    /// Interpolator: output/input - 1
16    /// Decimator: input/output - 1
17    rate: u32,
18    /// Up/downsampler state (count down)
19    index: u32,
20    /// Zero order hold behind comb sections.
21    /// Interpolator: Combined with the upsampler
22    /// Decimator: To support `get_decimate()`
23    zoh: T,
24    /// Comb/differentiator state
25    combs: [[T; M]; N],
26    /// Integrator state
27    integrators: [T; N],
28}
29
30impl<T, const N: usize, const M: usize> Cic<T, N, M>
31where
32    T: Num + AddAssign + WrappingAdd + WrappingSub + Pow<usize, Output = T> + Copy + 'static,
33    u32: AsPrimitive<T>,
34    usize: AsPrimitive<T>,
35{
36    const _M: () = assert!(M > 0, "Comb delay must be non-zero");
37
38    /// Create a new zero-initialized filter with the given rate change.
39    pub fn new(rate: u32) -> Self {
40        Self {
41            rate,
42            index: 0,
43            zoh: T::zero(),
44            combs: [[T::zero(); M]; N],
45            integrators: [T::zero(); N],
46        }
47    }
48
49    /// Filter order
50    ///
51    /// * 0: zero order hold
52    /// * 1: linear
53    /// * 2: quadratic
54    /// * 3: cubic interpolation/decimation
55    ///
56    /// etc.
57    pub const fn order(&self) -> usize {
58        N
59    }
60
61    /// Comb delay
62    pub const fn comb_delay(&self) -> usize {
63        M
64    }
65
66    /// Rate change
67    ///
68    /// `fast/slow - 1`
69    pub const fn rate(&self) -> u32 {
70        self.rate
71    }
72
73    /// Set the rate change
74    ///
75    /// `fast/slow - 1`
76    pub fn set_rate(&mut self, rate: u32) {
77        self.rate = rate;
78    }
79
80    /// Zero-initialize the filter state
81    pub fn clear(&mut self) {
82        *self = Self::new(self.rate);
83    }
84
85    /// Accepts/provides new slow-rate sample
86    ///
87    /// Interpolator: accepts new input sample
88    /// Decimator: returns new output sample
89    pub const fn tick(&self) -> bool {
90        self.index == 0
91    }
92
93    /// Current interpolator output
94    pub fn get_interpolate(&self) -> T {
95        self.integrators.last().copied().unwrap_or(self.zoh)
96    }
97
98    /// Current decimator output
99    pub fn get_decimate(&self) -> T {
100        self.zoh
101    }
102
103    /// Filter gain
104    pub fn gain(&self) -> T {
105        (M.as_() * (self.rate.as_() + T::one())).pow(N)
106    }
107
108    /// Right shift amount
109    ///
110    /// `log2(gain())` if gain is a power of two,
111    /// otherwise an upper bound.
112    pub const fn gain_log2(&self) -> u32 {
113        (u32::BITS - (M as u32 * self.rate + (M - 1) as u32).leading_zeros()) * N as u32
114    }
115
116    /// Impulse response length
117    pub const fn response_length(&self) -> usize {
118        self.rate as usize * N
119    }
120
121    /// Establish a settled filter state
122    pub fn settle_interpolate(&mut self, x: T) {
123        self.clear();
124        if let Some(c) = self.combs.first_mut() {
125            *c = [x; M];
126        } else {
127            self.zoh = x;
128        }
129        let g = self.gain();
130        if let Some(i) = self.integrators.last_mut() {
131            *i = x * g;
132        }
133    }
134
135    /// Establish a settled filter state
136    ///
137    /// Unimplemented!
138    pub fn settle_decimate(&mut self, x: T) {
139        self.clear();
140        self.zoh = x * self.gain();
141        unimplemented!();
142    }
143}
144
145/// Optionally ingest a new low-rate sample and
146/// retrieve the next output.
147///
148/// A new sample must be supplied at the correct time (when [`Cic::tick()`] is true)
149impl<T, const N: usize, const M: usize> Process<Option<T>, T> for Cic<T, N, M>
150where
151    T: Num + AddAssign + Copy,
152{
153    fn process(&mut self, x: Option<T>) -> T {
154        if let Some(x) = x {
155            debug_assert_eq!(self.index, 0);
156            self.index = self.rate;
157            self.zoh = self.combs.iter_mut().fold(x, |x, c| {
158                let y = x - c[0];
159                c.copy_within(1.., 0);
160                c[M - 1] = x;
161                y
162            });
163        } else {
164            self.index -= 1;
165        }
166        self.integrators.iter_mut().fold(self.zoh, |x, i| {
167            // Overflow is not OK
168            *i += x;
169            *i
170        })
171    }
172}
173
174/// Ingest a new high-rate sample and optionally retrieve next output.
175impl<T, const N: usize, const M: usize> Process<T, Option<T>> for Cic<T, N, M>
176where
177    T: WrappingAdd + WrappingSub + Copy,
178{
179    fn process(&mut self, x: T) -> Option<T> {
180        let x = self.integrators.iter_mut().fold(x, |x, i| {
181            // Overflow is OK if bitwidth is sufficient (input * gain)
182            *i = i.wrapping_add(&x);
183            *i
184        });
185        if let Some(index) = self.index.checked_sub(1) {
186            self.index = index;
187            None
188        } else {
189            self.index = self.rate;
190            self.zoh = self.combs.iter_mut().fold(x, |x, c| {
191                // Overflows expected
192                let y = x.wrapping_sub(&c[0]);
193                c.copy_within(1.., 0);
194                c[M - 1] = x;
195                y
196            });
197            Some(self.zoh)
198        }
199    }
200}
201
202#[cfg(test)]
203mod test {
204    use core::cmp::Ordering;
205
206    use super::*;
207    use quickcheck_macros::quickcheck;
208
209    #[quickcheck]
210    fn new(rate: u32) {
211        let _ = Cic::<i64, 3>::new(rate);
212    }
213
214    #[quickcheck]
215    fn identity_dec(x: Vec<i64>) {
216        let mut dec = Cic::<_, 3>::new(0);
217        for x in x {
218            assert_eq!(Some(x), dec.process(x));
219            assert_eq!(x, dec.get_decimate());
220        }
221    }
222
223    #[quickcheck]
224    fn identity_int(x: Vec<i64>) {
225        const N: usize = 3;
226        let mut int = Cic::<_, N>::new(0);
227        for x in x {
228            assert_eq!(x >> N, int.process(Some(x >> N)));
229            assert_eq!(x >> N, int.get_interpolate());
230        }
231    }
232
233    #[quickcheck]
234    fn response_length_gain_settle(x: Vec<i32>, rate: u32) {
235        let mut int = Cic::<_, 3>::new(rate);
236        let shift = int.gain_log2();
237        if shift >= 32 {
238            return;
239        }
240        assert!(int.gain() <= 1 << shift);
241        for x in x {
242            while !int.tick() {
243                int.process(None);
244            }
245            let y_last = int.get_interpolate();
246            let y_want = x as i64 * int.gain();
247            for i in 0..2 * int.response_length() {
248                let y = int.process(if int.tick() { Some(x as i64) } else { None });
249                assert_eq!(y, int.get_interpolate());
250                if i < int.response_length() {
251                    match y_want.cmp(&y_last) {
252                        Ordering::Greater => assert!((y_last..y_want).contains(&y)),
253                        Ordering::Less => assert!((y_want..y_last).contains(&(y - 1))),
254                        Ordering::Equal => assert_eq!(y_want, y),
255                    }
256                } else {
257                    assert_eq!(y, y_want);
258                }
259            }
260        }
261    }
262
263    #[quickcheck]
264    fn settle(rate: u32, x: i32) {
265        let mut int = Cic::<i64, 3>::new(rate);
266        if int.gain_log2() >= 32 {
267            return;
268        }
269        int.settle_interpolate(x as _);
270        // let mut dec = Cic::<i64, 3>::new(rate);
271        // dec.settle_decimate(x as _);
272        for _ in 0..100 {
273            let y = int.process(if int.tick() { Some(x as _) } else { None });
274            assert_eq!(y, x as i64 * int.gain());
275            assert_eq!(y, int.get_interpolate());
276            // assert_eq!(dec.get_decimate(), x as i64 * dec.gain());
277            // if let Some(y) = dec.decimate(x as _) {
278            //     assert_eq!(y, x as i64 * dec.gain());
279            // }
280        }
281    }
282
283    #[quickcheck]
284    fn unit_rate(x: (i32, i32, i32, i32, i32)) {
285        let x: [i32; 5] = x.into();
286        let mut cic = Cic::<i64, 3, 3>::new(0);
287        assert!(cic.gain_log2() == 6);
288        assert!(cic.gain() == (cic.comb_delay() as i64).pow(cic.order() as _));
289        for x in x {
290            assert!(cic.tick());
291            let y: Option<_> = cic.process(x as _);
292            println!("{x:11} {:11}", y.unwrap());
293        }
294        for _ in 0..100 {
295            let y: Option<_> = cic.process(0 as _);
296            assert_eq!(y, Some(cic.get_decimate()));
297            println!("{:11}", y.unwrap());
298            println!("");
299        }
300    }
301}