bbx_dsp/blocks/effectors/
dc_blocker.rs1use std::marker::PhantomData;
4
5use bbx_core::flush_denormal_f64;
6
7use crate::{
8 block::{Block, DEFAULT_EFFECTOR_INPUT_COUNT, DEFAULT_EFFECTOR_OUTPUT_COUNT},
9 context::DspContext,
10 graph::MAX_BLOCK_OUTPUTS,
11 parameter::ModulationOutput,
12 sample::Sample,
13};
14
15pub struct DcBlockerBlock<S: Sample> {
19 pub enabled: bool,
21
22 x_prev: [f64; MAX_BLOCK_OUTPUTS],
23 y_prev: [f64; MAX_BLOCK_OUTPUTS],
24
25 coeff: f64,
27
28 _phantom: PhantomData<S>,
29}
30
31impl<S: Sample> DcBlockerBlock<S> {
32 pub fn new(enabled: bool) -> Self {
34 Self {
35 enabled,
36 x_prev: [0.0; MAX_BLOCK_OUTPUTS],
37 y_prev: [0.0; MAX_BLOCK_OUTPUTS],
38 coeff: 0.995, _phantom: PhantomData,
40 }
41 }
42
43 pub fn set_sample_rate(&mut self, sample_rate: f64) {
46 let cutoff_hz = 5.0;
49 self.coeff = 1.0 - (2.0 * S::PI.to_f64() * cutoff_hz / sample_rate);
50 self.coeff = self.coeff.clamp(0.9, 0.9999);
51 }
52
53 pub fn reset(&mut self) {
55 self.x_prev = [0.0; MAX_BLOCK_OUTPUTS];
56 self.y_prev = [0.0; MAX_BLOCK_OUTPUTS];
57 }
58}
59
60impl<S: Sample> Block<S> for DcBlockerBlock<S> {
61 fn process(&mut self, inputs: &[&[S]], outputs: &mut [&mut [S]], _modulation_values: &[S], _context: &DspContext) {
62 if !self.enabled {
63 for (ch, input) in inputs.iter().enumerate() {
65 if ch < outputs.len() {
66 outputs[ch].copy_from_slice(input);
67 }
68 }
69 return;
70 }
71
72 for (ch, input) in inputs.iter().enumerate() {
74 if ch >= outputs.len() || ch >= MAX_BLOCK_OUTPUTS {
75 break;
76 }
77
78 for (i, &sample) in input.iter().enumerate() {
79 let x = sample.to_f64();
80
81 let y = x - self.x_prev[ch] + self.coeff * self.y_prev[ch];
83
84 self.x_prev[ch] = x;
85 self.y_prev[ch] = flush_denormal_f64(y);
87
88 outputs[ch][i] = S::from_f64(y);
89 }
90 }
91 }
92
93 #[inline]
94 fn input_count(&self) -> usize {
95 DEFAULT_EFFECTOR_INPUT_COUNT
96 }
97
98 #[inline]
99 fn output_count(&self) -> usize {
100 DEFAULT_EFFECTOR_OUTPUT_COUNT
101 }
102
103 #[inline]
104 fn modulation_outputs(&self) -> &[ModulationOutput] {
105 &[]
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use crate::channel::ChannelLayout;
113
114 fn test_context(buffer_size: usize) -> DspContext {
115 DspContext {
116 sample_rate: 44100.0,
117 num_channels: 6,
118 buffer_size,
119 current_sample: 0,
120 channel_layout: ChannelLayout::Surround51,
121 }
122 }
123
124 #[test]
125 fn test_dc_blocker_6_channels() {
126 let mut blocker = DcBlockerBlock::<f32>::new(true);
127 blocker.set_sample_rate(44100.0);
128 let context = test_context(4);
129
130 let input: [[f32; 4]; 6] = [[0.5; 4]; 6];
131 let mut outputs: [[f32; 4]; 6] = [[0.0; 4]; 6];
132
133 let input_refs: Vec<&[f32]> = input.iter().map(|ch| ch.as_slice()).collect();
134 let mut output_refs: Vec<&mut [f32]> = outputs.iter_mut().map(|ch| ch.as_mut_slice()).collect();
135
136 blocker.process(&input_refs, &mut output_refs, &[], &context);
137
138 for ch in 0..6 {
139 assert!(outputs[ch][3].abs() > 0.0, "Channel {ch} should have output");
140 }
141 }
142
143 #[test]
144 fn test_dc_blocker_removes_dc_offset() {
145 let mut blocker = DcBlockerBlock::<f32>::new(true);
146 blocker.set_sample_rate(44100.0);
147 let context = test_context(1024);
148
149 let input: [f32; 1024] = [0.5; 1024];
150 let mut output: [f32; 1024] = [0.0; 1024];
151
152 let inputs: [&[f32]; 1] = [&input];
153 let mut outputs: [&mut [f32]; 1] = [&mut output];
154
155 for _ in 0..100 {
156 blocker.process(&inputs, &mut outputs, &[], &context);
157 }
158
159 let final_val = output[1023].abs();
160 assert!(final_val < 0.1, "DC should be mostly removed, got {final_val}");
161 }
162
163 #[test]
164 fn test_dc_blocker_input_output_counts_f32() {
165 let blocker = DcBlockerBlock::<f32>::new(true);
166 assert_eq!(blocker.input_count(), DEFAULT_EFFECTOR_INPUT_COUNT);
167 assert_eq!(blocker.output_count(), DEFAULT_EFFECTOR_OUTPUT_COUNT);
168 }
169
170 #[test]
171 fn test_dc_blocker_input_output_counts_f64() {
172 let blocker = DcBlockerBlock::<f64>::new(true);
173 assert_eq!(blocker.input_count(), DEFAULT_EFFECTOR_INPUT_COUNT);
174 assert_eq!(blocker.output_count(), DEFAULT_EFFECTOR_OUTPUT_COUNT);
175 }
176
177 #[test]
178 fn test_dc_blocker_basic_f64() {
179 let mut blocker = DcBlockerBlock::<f64>::new(true);
180 blocker.set_sample_rate(44100.0);
181 let context = test_context(64);
182
183 let input: [f64; 64] = [0.5; 64];
184 let mut output: [f64; 64] = [0.0; 64];
185
186 let inputs: [&[f64]; 1] = [&input];
187 let mut outputs: [&mut [f64]; 1] = [&mut output];
188
189 blocker.process(&inputs, &mut outputs, &[], &context);
190
191 assert!(output[63].abs() > 0.0, "DC blocker should produce output");
192 }
193
194 #[test]
195 fn test_dc_blocker_modulation_outputs_empty() {
196 let blocker = DcBlockerBlock::<f32>::new(true);
197 assert!(blocker.modulation_outputs().is_empty());
198 }
199
200 #[test]
201 fn test_dc_blocker_disabled_passthrough() {
202 let mut blocker = DcBlockerBlock::<f32>::new(false);
203 let context = test_context(4);
204
205 let input: [f32; 4] = [0.5, 0.6, 0.7, 0.8];
206 let mut output: [f32; 4] = [0.0; 4];
207
208 let inputs: [&[f32]; 1] = [&input];
209 let mut outputs: [&mut [f32]; 1] = [&mut output];
210
211 blocker.process(&inputs, &mut outputs, &[], &context);
212
213 assert_eq!(output, input, "Disabled DC blocker should pass through unchanged");
214 }
215
216 #[test]
217 fn test_dc_blocker_reset() {
218 let mut blocker = DcBlockerBlock::<f32>::new(true);
219 blocker.set_sample_rate(44100.0);
220 let context = test_context(64);
221
222 let input: [f32; 64] = [0.5; 64];
223 let mut output: [f32; 64] = [0.0; 64];
224
225 let inputs: [&[f32]; 1] = [&input];
226 let mut outputs: [&mut [f32]; 1] = [&mut output];
227
228 blocker.process(&inputs, &mut outputs, &[], &context);
229 blocker.reset();
230
231 let mut output2: [f32; 64] = [0.0; 64];
232 let mut outputs2: [&mut [f32]; 1] = [&mut output2];
233 blocker.process(&inputs, &mut outputs2, &[], &context);
234
235 assert!((output[0] - output2[0]).abs() < 1e-6, "Reset should clear state");
236 }
237}