use core::ops::AddAssign;
use num_traits::{AsPrimitive, Num, Pow, WrappingAdd, WrappingSub};
use dsp_process::Process;
#[derive(Clone, Debug)]
pub struct Cic<T, const N: usize, const M: usize = 1> {
rate: u32,
index: u32,
zoh: T,
combs: [[T; M]; N],
integrators: [T; N],
}
impl<T, const N: usize, const M: usize> Cic<T, N, M>
where
T: Num + AddAssign + WrappingAdd + WrappingSub + Pow<usize, Output = T> + Copy + 'static,
u32: AsPrimitive<T>,
usize: AsPrimitive<T>,
{
const _M: () = assert!(M > 0, "Comb delay must be non-zero");
pub fn new(rate: u32) -> Self {
Self {
rate,
index: 0,
zoh: T::zero(),
combs: [[T::zero(); M]; N],
integrators: [T::zero(); N],
}
}
pub const fn order(&self) -> usize {
N
}
pub const fn comb_delay(&self) -> usize {
M
}
pub const fn rate(&self) -> u32 {
self.rate
}
pub fn set_rate(&mut self, rate: u32) {
self.rate = rate;
}
pub fn clear(&mut self) {
*self = Self::new(self.rate);
}
pub const fn tick(&self) -> bool {
self.index == 0
}
pub fn get_interpolate(&self) -> T {
self.integrators.last().copied().unwrap_or(self.zoh)
}
pub fn get_decimate(&self) -> T {
self.zoh
}
pub fn gain(&self) -> T {
(M.as_() * (self.rate.as_() + T::one())).pow(N)
}
pub const fn gain_log2(&self) -> u32 {
(u32::BITS - (M as u32 * self.rate + (M - 1) as u32).leading_zeros()) * N as u32
}
pub const fn response_length(&self) -> usize {
self.rate as usize * N
}
pub fn settle_interpolate(&mut self, x: T) {
self.clear();
if let Some(c) = self.combs.first_mut() {
*c = [x; M];
} else {
self.zoh = x;
}
let g = self.gain();
if let Some(i) = self.integrators.last_mut() {
*i = x * g;
}
}
pub fn settle_decimate(&mut self, x: T) {
self.clear();
self.zoh = x * self.gain();
unimplemented!();
}
}
impl<T, const N: usize, const M: usize> Process<Option<T>, T> for Cic<T, N, M>
where
T: Num + AddAssign + Copy,
{
fn process(&mut self, x: Option<T>) -> T {
if let Some(x) = x {
debug_assert_eq!(self.index, 0);
self.index = self.rate;
self.zoh = self.combs.iter_mut().fold(x, |x, c| {
let y = x - c[0];
c.copy_within(1.., 0);
c[M - 1] = x;
y
});
} else {
self.index -= 1;
}
self.integrators.iter_mut().fold(self.zoh, |x, i| {
*i += x;
*i
})
}
}
impl<T, const N: usize, const M: usize> Process<T, Option<T>> for Cic<T, N, M>
where
T: WrappingAdd + WrappingSub + Copy,
{
fn process(&mut self, x: T) -> Option<T> {
let x = self.integrators.iter_mut().fold(x, |x, i| {
*i = i.wrapping_add(&x);
*i
});
if let Some(index) = self.index.checked_sub(1) {
self.index = index;
None
} else {
self.index = self.rate;
self.zoh = self.combs.iter_mut().fold(x, |x, c| {
let y = x.wrapping_sub(&c[0]);
c.copy_within(1.., 0);
c[M - 1] = x;
y
});
Some(self.zoh)
}
}
}
#[cfg(test)]
mod test {
use core::cmp::Ordering;
use super::*;
use quickcheck_macros::quickcheck;
#[quickcheck]
fn new(rate: u32) {
let _ = Cic::<i64, 3>::new(rate);
}
#[quickcheck]
fn identity_dec(x: Vec<i64>) {
let mut dec = Cic::<_, 3>::new(0);
for x in x {
assert_eq!(Some(x), dec.process(x));
assert_eq!(x, dec.get_decimate());
}
}
#[quickcheck]
fn identity_int(x: Vec<i64>) {
const N: usize = 3;
let mut int = Cic::<_, N>::new(0);
for x in x {
assert_eq!(x >> N, int.process(Some(x >> N)));
assert_eq!(x >> N, int.get_interpolate());
}
}
#[quickcheck]
fn response_length_gain_settle(x: Vec<i32>, rate: u32) {
let mut int = Cic::<_, 3>::new(rate);
let shift = int.gain_log2();
if shift >= 32 {
return;
}
assert!(int.gain() <= 1 << shift);
for x in x {
while !int.tick() {
int.process(None);
}
let y_last = int.get_interpolate();
let y_want = x as i64 * int.gain();
for i in 0..2 * int.response_length() {
let y = int.process(if int.tick() { Some(x as i64) } else { None });
assert_eq!(y, int.get_interpolate());
if i < int.response_length() {
match y_want.cmp(&y_last) {
Ordering::Greater => assert!((y_last..y_want).contains(&y)),
Ordering::Less => assert!((y_want..y_last).contains(&(y - 1))),
Ordering::Equal => assert_eq!(y_want, y),
}
} else {
assert_eq!(y, y_want);
}
}
}
}
#[quickcheck]
fn settle(rate: u32, x: i32) {
let mut int = Cic::<i64, 3>::new(rate);
if int.gain_log2() >= 32 {
return;
}
int.settle_interpolate(x as _);
for _ in 0..100 {
let y = int.process(if int.tick() { Some(x as _) } else { None });
assert_eq!(y, x as i64 * int.gain());
assert_eq!(y, int.get_interpolate());
}
}
#[quickcheck]
fn unit_rate(x: (i32, i32, i32, i32, i32)) {
let x: [i32; 5] = x.into();
let mut cic = Cic::<i64, 3, 3>::new(0);
assert!(cic.gain_log2() == 6);
assert!(cic.gain() == (cic.comb_delay() as i64).pow(cic.order() as _));
for x in x {
assert!(cic.tick());
let y: Option<_> = cic.process(x as _);
println!("{x:11} {:11}", y.unwrap());
}
for _ in 0..100 {
let y: Option<_> = cic.process(0 as _);
assert_eq!(y, Some(cic.get_decimate()));
println!("{:11}", y.unwrap());
println!();
}
}
}
#[cfg(test)]
mod modular_tests {
use super::*;
use dsp_process::{Comb, Downsample, Hold, Integrator, Process, Rate, Split};
fn modular_decimator<const N: usize, const R: usize, const M: usize>()
-> impl Process<[i64; R], i64> {
let ints = Split::stateful(Integrator(0)).repeat::<N>();
let rate = Split::new(Downsample(R as u32 - 1), 0);
let combs = Split::stateful(Comb([0; M])).repeat::<N>().map();
((ints * rate).minor() * combs).minor().decimate()
}
fn modular_decimator_chunked_prefix<const N: usize, const R: usize, const M: usize>()
-> impl Process<[i64; R], i64> {
let ints = Split::stateful(Integrator(0)).repeat::<N>().chunk();
let rate = Split::stateful(Rate::<0>);
let combs = Split::stateful(Comb([0; M])).repeat::<N>();
(ints * rate).minor() * combs
}
fn modular_interpolator<const N: usize, const R: usize, const M: usize>()
-> impl Process<i64, [i64; R]> {
let combs = Split::stateful(Comb([0; M])).repeat::<N>().map();
let rate = Split::stateful(Hold(0));
let ints = Split::stateful(Integrator(0)).repeat::<N>();
((combs * rate).minor() * ints).interpolate()
}
fn modular_interpolator_chunked_suffix<const N: usize, const R: usize, const M: usize>()
-> impl Process<i64, [i64; R]> {
let combs = Split::stateful(Comb([0; M])).repeat::<N>().map();
let rate = Split::stateful(Hold(0));
let ints = Split::stateful(Integrator(0)).repeat::<N>().chunk();
(combs * rate).minor().interpolate() * ints
}
fn reference_decimator<const N: usize, const R: usize, const M: usize>()
-> impl Process<[i64; R], i64> {
Split::stateful(Cic::<_, N, M>::new(R as u32 - 1)).decimate()
}
fn reference_interpolator<const N: usize, const R: usize, const M: usize>()
-> impl Process<i64, [i64; R]> {
Split::stateful(Cic::<_, N, M>::new(R as u32 - 1)).interpolate()
}
#[test]
fn modular_decimator_matches_reference() {
fn verify<const N: usize, const R: usize, const M: usize>(x: &[i64]) {
let mut m = modular_decimator::<N, R, M>();
let mut c = modular_decimator_chunked_prefix::<N, R, M>();
let mut r = reference_decimator::<N, R, M>();
for chunk in x.as_chunks::<R>().0 {
let y = r.process(*chunk);
assert_eq!(m.process(*chunk), y);
assert_eq!(c.process(*chunk), y);
}
}
let x: Vec<_> = (-31..65).map(|x| x * 3 - 7).collect();
verify::<3, 4, 1>(&x);
verify::<2, 2, 1>(&x);
verify::<3, 1, 3>(&x);
}
#[test]
fn modular_interpolator_matches_reference() {
fn verify<const N: usize, const R: usize, const M: usize>(x: &[i64]) {
let mut m = modular_interpolator::<N, R, M>();
let mut c = modular_interpolator_chunked_suffix::<N, R, M>();
let mut r = reference_interpolator::<N, R, M>();
for &x in x {
let y = r.process(x);
assert_eq!(m.process(x), y);
assert_eq!(c.process(x), y);
}
}
let x: Vec<_> = (-12..20).map(|x| x * x - 3 * x + 2).collect();
verify::<3, 4, 1>(&x);
verify::<2, 2, 1>(&x);
verify::<3, 1, 3>(&x);
}
use core::hint::black_box;
const PERF_N: usize = 3;
const PERF_R: usize = 16;
const PERF_D: usize = 1;
const PERF_ITERS: usize = 1 << 27;
#[test]
#[ignore]
fn insn_dec_ref() {
let mut proc = reference_decimator::<PERF_N, PERF_R, PERF_D>();
let x = [9; PERF_R];
for _ in 0..PERF_ITERS {
black_box(proc.process(black_box(x)));
}
}
#[test]
#[ignore]
fn insn_dec_mod() {
let mut proc = modular_decimator::<PERF_N, PERF_R, PERF_D>();
let x = [9; PERF_R];
for _ in 0..PERF_ITERS {
black_box(proc.process(black_box(x)));
}
}
#[test]
#[ignore]
fn insn_dec_mod_chunked_prefix() {
let mut proc = modular_decimator_chunked_prefix::<PERF_N, PERF_R, PERF_D>();
let x = [9; PERF_R];
for _ in 0..PERF_ITERS {
black_box(proc.process(black_box(x)));
}
}
#[test]
#[ignore]
fn insn_int_ref() {
let mut proc = reference_interpolator::<PERF_N, PERF_R, PERF_D>();
let x = 9;
for _ in 0..PERF_ITERS {
black_box(proc.process(black_box(x)));
}
}
#[test]
#[ignore]
fn insn_int_mod() {
let mut proc = modular_interpolator::<PERF_N, PERF_R, PERF_D>();
let x = 9;
for _ in 0..PERF_ITERS {
black_box(proc.process(black_box(x)));
}
}
#[test]
#[ignore]
fn insn_int_mod_chunked_suffix() {
let mut proc = modular_interpolator_chunked_suffix::<PERF_N, PERF_R, PERF_D>();
let x = 9;
for _ in 0..PERF_ITERS {
black_box(proc.process(black_box(x)));
}
}
}