math_audio_dsp/
dc_blocker.rs1#[derive(Debug, Clone)]
20pub struct DcBlocker {
21 x_prev: Vec<f32>,
22 y_prev: Vec<f32>,
23 coeff: f32,
24 channels: usize,
25}
26
27impl DcBlocker {
28 pub fn new(channels: usize, sample_rate: u32, cutoff_hz: f32) -> Self {
32 Self {
33 x_prev: vec![0.0; channels],
34 y_prev: vec![0.0; channels],
35 coeff: Self::calculate_coeff(cutoff_hz, sample_rate),
36 channels,
37 }
38 }
39
40 pub fn new_default(channels: usize, sample_rate: u32) -> Self {
42 Self::new(channels, sample_rate, 5.0)
43 }
44
45 fn calculate_coeff(cutoff_hz: f32, sample_rate: u32) -> f32 {
46 if sample_rate == 0 {
47 return 0.99999; }
49 let r = 1.0 - (2.0 * std::f32::consts::PI * cutoff_hz / sample_rate as f32);
52 r.clamp(0.9, 0.99999)
53 }
54
55 #[inline]
59 pub fn process_sample(&mut self, sample: f32, channel: usize) -> f32 {
60 let x_prev = self.x_prev[channel];
61 let y_prev = self.y_prev[channel];
62 let output = sample - x_prev + self.coeff * y_prev;
63 self.x_prev[channel] = sample;
64 self.y_prev[channel] = output;
65 output
66 }
67
68 pub fn process_block_interleaved(
74 &mut self,
75 buffer: &mut [f32],
76 channels: usize,
77 num_frames: usize,
78 ) {
79 debug_assert_eq!(channels, self.channels);
80 for frame in 0..num_frames {
81 for ch in 0..channels {
82 let idx = frame * channels + ch;
83 buffer[idx] = self.process_sample(buffer[idx], ch);
84 }
85 }
86 }
87
88 pub fn reset(&mut self) {
90 self.x_prev.fill(0.0);
91 self.y_prev.fill(0.0);
92 }
93
94 pub fn set_sample_rate(&mut self, sample_rate: u32, cutoff_hz: f32) {
96 self.coeff = Self::calculate_coeff(cutoff_hz, sample_rate);
97 }
98
99 pub fn set_channels(&mut self, channels: usize) {
101 self.channels = channels;
102 self.x_prev.resize(channels, 0.0);
103 self.y_prev.resize(channels, 0.0);
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn test_dc_removal() {
113 let mut blocker = DcBlocker::new(1, 48000, 5.0);
114 let num_samples = 96000;
116 let mut last_output = 0.0f32;
117 for _ in 0..num_samples {
118 last_output = blocker.process_sample(1.0, 0);
119 }
120 assert!(
122 last_output.abs() < 0.01,
123 "DC not removed: output = {last_output}"
124 );
125 }
126
127 #[test]
128 fn test_passband_preservation() {
129 let sr = 48000;
130 let mut blocker = DcBlocker::new(1, sr, 5.0);
131 let freq = 1000.0;
133 let num_samples = 4800; let mut max_input = 0.0f32;
135 let mut max_output = 0.0f32;
136 for i in 0..num_samples {
137 let t = i as f32 / sr as f32;
138 let sample = (2.0 * std::f32::consts::PI * freq * t).sin();
139 let out = blocker.process_sample(sample, 0);
140 if i > 2400 {
141 max_input = max_input.max(sample.abs());
143 max_output = max_output.max(out.abs());
144 }
145 }
146 let ratio = max_output / max_input;
147 assert!(ratio > 0.99, "1kHz attenuated too much: ratio = {ratio}");
148 }
149
150 #[test]
151 fn test_block_processing() {
152 let mut blocker = DcBlocker::new(2, 48000, 5.0);
153 let mut buffer = vec![1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0];
155 blocker.process_block_interleaved(&mut buffer, 2, 4);
156 assert!(buffer[1].abs() < 1e-10); assert!(buffer[0] > 0.0); }
160
161 #[test]
162 fn test_reset() {
163 let mut blocker = DcBlocker::new(1, 48000, 5.0);
164 blocker.process_sample(1.0, 0);
165 blocker.reset();
166 let out = blocker.process_sample(0.0, 0);
168 assert!(out.abs() < 1e-10);
169 }
170
171 #[test]
172 fn test_sample_rate_zero_no_panic() {
173 let mut blocker = DcBlocker::new(1, 0, 5.0);
175 let out = blocker.process_sample(1.0, 0);
176 assert!(!out.is_nan(), "NaN from sample_rate=0");
177 assert!(!out.is_infinite(), "inf from sample_rate=0");
178 }
179}