const HISTORY_SIZE_BITS: usize = 11;
const HISTORY_SIZE: usize = 1 << HISTORY_SIZE_BITS;
#[derive(Clone)]
pub struct DcFilter {
buffer: Box<[u16; HISTORY_SIZE]>,
position: usize,
running_sum: u32,
}
impl DcFilter {
pub fn new() -> Self {
Self {
buffer: Box::new([0; HISTORY_SIZE]),
position: 0,
running_sum: 0,
}
}
#[inline]
pub fn process(&mut self, sample: u16) -> i16 {
self.running_sum -= self.buffer[self.position] as u32;
self.running_sum += sample as u32;
self.buffer[self.position] = sample;
self.position = (self.position + 1) & (HISTORY_SIZE - 1);
let dc_offset = self.running_sum >> HISTORY_SIZE_BITS;
(sample as i32 - dc_offset as i32) as i16
}
pub fn reset(&mut self) {
self.buffer.fill(0);
self.position = 0;
self.running_sum = 0;
}
}
impl Default for DcFilter {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for DcFilter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DcFilter")
.field("position", &self.position)
.field("running_sum", &self.running_sum)
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dc_filter_removes_offset() {
let mut filter = DcFilter::new();
let dc_value = 1000u16;
for _ in 0..HISTORY_SIZE * 2 {
filter.process(dc_value);
}
let output = filter.process(dc_value);
assert!(
output.abs() < 10,
"DC filter should remove constant offset, got {output}"
);
}
#[test]
fn test_dc_filter_preserves_ac() {
let mut filter = DcFilter::new();
for _ in 0..HISTORY_SIZE * 2 {
filter.process(500);
}
let output = filter.process(1500);
assert!(
output > 100,
"DC filter should pass AC component, got {output}"
);
}
#[test]
fn test_dc_filter_reset() {
let mut filter = DcFilter::new();
for i in 0..100 {
filter.process(i as u16 * 100);
}
filter.reset();
assert_eq!(filter.position, 0);
assert_eq!(filter.running_sum, 0);
}
}