idsp/
cic.rs

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