Skip to main content

embedded_dsp/
resampling.rs

1//! Multi-rate digital signal processing routines: Cascaded Integrator-Comb (CIC) decimation/interpolation and linear fractional resampling.
2
3/// Cascaded Integrator-Comb (CIC) Decimator for downsampling signals in integer arithmetic.
4pub struct CicDecimator<const STAGES: usize> {
5    r: usize, // Decimation factor
6    integrator_state: [i32; STAGES],
7    comb_state: [i32; STAGES],
8    sample_counter: usize,
9}
10
11impl<const STAGES: usize> CicDecimator<STAGES> {
12    /// Initialise a new CIC decimator with decimation factor `r`.
13    pub fn new(r: usize) -> Self {
14        Self {
15            r,
16            integrator_state: [0; STAGES],
17            comb_state: [0; STAGES],
18            sample_counter: 0,
19        }
20    }
21
22    /// Process an input sample. Returns `Some(decimated_sample)` every `R` samples.
23    pub fn process_sample(&mut self, input: i32) -> Option<i32> {
24        // Integrator stages running at high sample rate
25        let mut val = input;
26        for i in 0..STAGES {
27            self.integrator_state[i] = self.integrator_state[i].wrapping_add(val);
28            val = self.integrator_state[i];
29        }
30
31        self.sample_counter += 1;
32        if self.sample_counter >= self.r {
33            self.sample_counter = 0;
34
35            // Comb stages running at low sample rate
36            for i in 0..STAGES {
37                let diff = val.wrapping_sub(self.comb_state[i]);
38                self.comb_state[i] = val;
39                val = diff;
40            }
41            Some(val)
42        } else {
43            None
44        }
45    }
46}
47
48/// Cascaded Integrator-Comb (CIC) Interpolator for upsampling signals in integer arithmetic.
49pub struct CicInterpolator<const STAGES: usize> {
50    r: usize, // Interpolation factor
51    comb_state: [i32; STAGES],
52    integrator_state: [i32; STAGES],
53}
54
55impl<const STAGES: usize> CicInterpolator<STAGES> {
56    /// Initialise a new CIC interpolator with interpolation factor `r`.
57    pub fn new(r: usize) -> Self {
58        Self {
59            r,
60            comb_state: [0; STAGES],
61            integrator_state: [0; STAGES],
62        }
63    }
64
65    /// Process a single input sample and populate `out_buf` with `R` interpolated output samples.
66    pub fn process_sample(&mut self, input: i32, out_buf: &mut [i32]) {
67        assert!(
68            out_buf.len() >= self.r,
69            "out_buf must hold at least R samples"
70        );
71
72        // Comb stages at low rate
73        let mut val = input;
74        for i in 0..STAGES {
75            let diff = val.wrapping_sub(self.comb_state[i]);
76            self.comb_state[i] = val;
77            val = diff;
78        }
79
80        // Zero stuffing and integrator stages at high rate
81        for step in 0..self.r {
82            let in_step = if step == 0 { val } else { 0 };
83            let mut stage_val = in_step;
84
85            for i in 0..STAGES {
86                self.integrator_state[i] = self.integrator_state[i].wrapping_add(stage_val);
87                stage_val = self.integrator_state[i];
88            }
89
90            out_buf[step] = stage_val;
91        }
92    }
93}
94
95/// Linear fractional resampler.
96/// Resamples `src` into `dst` according to `ratio` (`src_sample_rate / dst_sample_rate`).
97pub fn resample_linear_f32(src: &[f32], dst: &mut [f32], ratio: f32) {
98    if src.is_empty() || dst.is_empty() || ratio <= 0.0 {
99        return;
100    }
101
102    for i in 0..dst.len() {
103        let src_idx_float = i as f32 * ratio;
104        let idx0 = src_idx_float as usize;
105        let idx1 = (idx0 + 1).min(src.len() - 1);
106
107        if idx0 >= src.len() {
108            dst[i] = src[src.len() - 1];
109            continue;
110        }
111
112        let frac = src_idx_float - idx0 as f32;
113        dst[i] = src[idx0] * (1.0 - frac) + src[idx1] * frac;
114    }
115}
116
117#[cfg(feature = "transform")]
118use crate::transform::cfft_f32;
119#[cfg(feature = "transform")]
120use crate::types::Status;
121
122/// Spectral (Sinc) 2:1 Interpolator using frequency-domain zero-padding via FFT/IFFT.
123///
124/// `src` length must be a power of 2 (e.g. 16, 32, 64, 128, 256).
125/// `dst` must have length at least `2 * src.len()`.
126///
127/// Requires the `transform` feature (enabled by `full`).
128#[cfg(feature = "transform")]
129pub fn spectral_interpolate_2x_f32(src: &[f32], dst: &mut [f32]) -> Status {
130    let n = src.len();
131    if n < 4 || (n & (n - 1)) != 0 {
132        return Status::ArgumentError;
133    }
134    if dst.len() < 2 * n {
135        return Status::LengthError;
136    }
137    if 4 * n > 1024 {
138        return Status::LengthError; // Max scratch size limit (256-pt input -> 512-pt complex)
139    }
140
141    let mut c_buf = [0.0f32; 1024];
142
143    // Copy src into complex array (size 2 * 2n)
144    for i in 0..n {
145        c_buf[2 * i] = src[i];
146        c_buf[2 * i + 1] = 0.0;
147    }
148
149    // FFT of size n
150    cfft_f32(&mut c_buf[..2 * n], n, 0, 1);
151
152    // Half Nyquist component
153    let nyq_re = 0.5 * c_buf[n];
154    let nyq_im = 0.5 * c_buf[n + 1];
155    c_buf[n] = nyq_re;
156    c_buf[n + 1] = nyq_im;
157
158    // Shift negative frequencies to upper half and zero middle
159    let mut expanded = [0.0f32; 1024];
160    // Copy 0..=N/2
161    for i in 0..=(n / 2) {
162        expanded[2 * i] = c_buf[2 * i];
163        expanded[2 * i + 1] = c_buf[2 * i + 1];
164    }
165    // Nyquist conjugate mirror at 3N/2
166    expanded[2 * (3 * n / 2)] = nyq_re;
167    expanded[2 * (3 * n / 2) + 1] = nyq_im;
168
169    // Negative frequencies
170    for i in (n / 2 + 1)..n {
171        expanded[2 * (i + n)] = c_buf[2 * i];
172        expanded[2 * (i + n) + 1] = c_buf[2 * i + 1];
173    }
174
175    // IFFT of size 2n
176    cfft_f32(&mut expanded[..4 * n], 2 * n, 1, 1);
177
178    // Copy back scaled real part (factor of 2)
179    for i in 0..(2 * n) {
180        dst[i] = 2.0 * expanded[2 * i];
181    }
182
183    Status::Success
184}