use num_complex::Complex;
use super::DspBlock;
use super::buffer::StreamBuffer;
use super::window::{WindowType, design_fir_filter};
pub struct Decimator {
factor: usize,
fir: Vec<f32>,
buffer: StreamBuffer<Complex<f32>>,
phase: usize,
}
impl Decimator {
pub fn new(factor: usize) -> Self {
assert!(factor > 0, "Decimation factor must be greater than 0");
let taps = 31;
let cutoff = 0.5 / factor as f32; let fir = design_fir_filter(cutoff, taps, WindowType::Hamming);
let mid = taps / 2;
Self {
factor,
fir,
buffer: StreamBuffer::new(),
phase: mid, }
}
pub fn with_params(factor: usize, taps: usize, cutoff: f32) -> Self {
assert!(factor > 0, "Decimation factor must be greater than 0");
assert!(
cutoff > 0.0 && cutoff <= 0.5,
"Cutoff must be in range (0.0, 0.5]"
);
let fir = design_fir_filter(cutoff, taps, WindowType::Hamming);
let mid = taps / 2;
Self {
factor,
fir,
buffer: StreamBuffer::new(),
phase: mid,
}
}
pub fn factor(&self) -> usize {
self.factor
}
pub fn taps(&self) -> usize {
self.fir.len()
}
pub fn reset(&mut self) {
self.buffer.clear();
self.phase = self.fir.len() / 2;
}
}
impl DspBlock for Decimator {
fn process(&mut self, data: &[Complex<f32>]) -> Vec<Complex<f32>> {
self.buffer.push_slice(data);
let taps = self.fir.len();
let mid = taps / 2;
let mut output = Vec::new();
let mut i = self.phase;
while i + mid < self.buffer.len() {
let mut acc = Complex::new(0.0, 0.0);
let base = i - mid;
for j in 0..taps {
acc += self.buffer[base + j] * self.fir[j];
}
output.push(acc);
i += self.factor;
}
let consume = i.saturating_sub(mid);
if consume > 0 {
self.buffer.consume(consume);
}
self.phase = i - consume;
output
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_decimator_new() {
let dec = Decimator::new(4);
assert_eq!(dec.factor(), 4);
assert_eq!(dec.taps(), 31);
}
#[test]
fn test_decimator_with_params() {
let dec = Decimator::with_params(8, 63, 0.06);
assert_eq!(dec.factor(), 8);
assert_eq!(dec.taps(), 63);
}
#[test]
#[should_panic(expected = "Decimation factor must be greater than 0")]
fn test_decimator_zero_factor() {
let _ = Decimator::new(0);
}
#[test]
#[should_panic(expected = "Cutoff must be in range")]
fn test_decimator_invalid_cutoff() {
let _ = Decimator::with_params(4, 31, 0.6);
}
#[test]
fn test_decimator_length() {
let mut dec = Decimator::new(4);
let input: Vec<Complex<f32>> = (0..1024).map(|i| Complex::new(i as f32, 0.0)).collect();
let output = dec.process(&input);
assert!(output.len() >= 240 && output.len() <= 260);
}
#[test]
fn test_decimator_dc_signal() {
let mut dec = Decimator::new(4);
let input = vec![Complex::new(1.0, 0.0); 4096];
let output = dec.process(&input);
let settled_samples: Vec<_> = output.iter().skip(20).take(output.len() - 30).collect();
assert!(!settled_samples.is_empty(), "Should have settled samples");
for sample in settled_samples {
assert_relative_eq!(sample.re, 1.0, epsilon = 0.15);
assert_relative_eq!(sample.im, 0.0, epsilon = 0.15);
}
}
#[test]
fn test_decimator_stateful() {
let mut dec = Decimator::new(8);
let chunk1: Vec<Complex<f32>> = (0..512).map(|i| Complex::new(i as f32, 0.0)).collect();
let chunk2: Vec<Complex<f32>> = (512..1024).map(|i| Complex::new(i as f32, 0.0)).collect();
let out1 = dec.process(&chunk1);
let out2 = dec.process(&chunk2);
assert!(!out1.is_empty());
assert!(!out2.is_empty());
}
#[test]
fn test_decimator_reset() {
let mut dec = Decimator::new(4);
let input: Vec<Complex<f32>> = (0..512).map(|i| Complex::new(i as f32, 0.0)).collect();
let _ = dec.process(&input);
dec.reset();
assert_eq!(dec.buffer.len(), 0);
}
#[test]
fn test_decimator_filter_normalization() {
let dec = Decimator::new(4);
let sum: f32 = dec.fir.iter().sum();
assert_relative_eq!(sum, 1.0, epsilon = 1e-6);
}
}