1use std::f64::consts::PI;
7
8pub fn rc_lowpass_impulse_response(
18 r_ohms: f64,
19 c_farads: f64,
20 sample_rate: f64,
21 n_samples: usize,
22) -> Vec<f64> {
23 let tau = r_ohms * c_farads;
24 let dt = 1.0 / sample_rate;
25
26 let k = 2.0 / dt;
38 let denom = 1.0 + k * tau;
39 let b0 = 1.0 / denom;
40 let b1 = b0;
41 let a1 = (1.0 - k * tau) / denom;
42
43 let mut output = Vec::with_capacity(n_samples);
45 let mut x_prev = 0.0;
46 let mut y_prev = 0.0;
47
48 for i in 0..n_samples {
49 let x = if i == 0 { 1.0 } else { 0.0 };
50 let y = b0 * x + b1 * x_prev - a1 * y_prev;
51 output.push(y);
52 x_prev = x;
53 y_prev = y;
54 }
55
56 output
57}
58
59pub fn rc_lowpass_magnitude(r_ohms: f64, c_farads: f64, freq_hz: f64) -> f64 {
64 let fc = 1.0 / (2.0 * PI * r_ohms * c_farads);
65 1.0 / (1.0 + (freq_hz / fc).powi(2)).sqrt()
66}
67
68pub fn rc_lowpass_phase(r_ohms: f64, c_farads: f64, freq_hz: f64) -> f64 {
72 let fc = 1.0 / (2.0 * PI * r_ohms * c_farads);
73 -(freq_hz / fc).atan()
74}
75
76pub fn rc_highpass_impulse_response(
90 r_ohms: f64,
91 c_farads: f64,
92 sample_rate: f64,
93 n_samples: usize,
94) -> Vec<f64> {
95 let tau = r_ohms * c_farads;
96 let dt = 1.0 / sample_rate;
97
98 let k = 2.0 / dt;
99 let kt = k * tau;
100 let denom = 1.0 + kt;
101 let b0 = kt / denom;
102 let b1 = -b0;
103 let a1 = (1.0 - kt) / denom;
104
105 let mut output = Vec::with_capacity(n_samples);
106 let mut x_prev = 0.0;
107 let mut y_prev = 0.0;
108
109 for i in 0..n_samples {
110 let x = if i == 0 { 1.0 } else { 0.0 };
111 let y = b0 * x + b1 * x_prev - a1 * y_prev;
112 output.push(y);
113 x_prev = x;
114 y_prev = y;
115 }
116
117 output
118}
119
120pub fn rc_highpass_filter(
125 signal: &[f64],
126 r_ohms: f64,
127 c_farads: f64,
128 sample_rate: f64,
129) -> Vec<f64> {
130 let tau = r_ohms * c_farads;
131 let dt = 1.0 / sample_rate;
132
133 let k = 2.0 / dt;
134 let kt = k * tau;
135 let denom = 1.0 + kt;
136 let b0 = kt / denom;
137 let b1 = -b0;
138 let a1 = (1.0 - kt) / denom;
139
140 let mut output = Vec::with_capacity(signal.len());
141 let mut x_prev = 0.0;
142 let mut y_prev = 0.0;
143
144 for &x in signal {
145 let y = b0 * x + b1 * x_prev - a1 * y_prev;
146 output.push(y);
147 x_prev = x;
148 y_prev = y;
149 }
150
151 output
152}
153
154pub fn rlc_bandpass_filter(
169 signal: &[f64],
170 r_ohms: f64,
171 l_henrys: f64,
172 c_farads: f64,
173 r_load: f64,
174 sample_rate: f64,
175) -> Vec<f64> {
176 let dt = 1.0 / sample_rate;
177
178 let b_s = r_load * c_farads;
181 let a2 = l_henrys * c_farads;
182 let a1 = (r_ohms + r_load) * c_farads;
183 let a0 = 1.0;
184
185 let k = 2.0 / dt;
187 let k2 = k * k;
188
189 let d0 = a2 * k2 + a1 * k + a0;
199 let d1 = -2.0 * a2 * k2 + 2.0 * a0;
200 let d2 = a2 * k2 - a1 * k + a0;
201
202 let n0 = b_s * k;
203 let n2 = -b_s * k;
205
206 let b0 = n0 / d0;
208 let b1_coeff = 0.0; let b2 = n2 / d0;
210 let a1_coeff = d1 / d0;
211 let a2_coeff = d2 / d0;
212
213 let mut output = Vec::with_capacity(signal.len());
215 let mut x1 = 0.0;
216 let mut x2 = 0.0;
217 let mut y1 = 0.0;
218 let mut y2 = 0.0;
219
220 for &x in signal {
221 let y = b0 * x + b1_coeff * x1 + b2 * x2 - a1_coeff * y1 - a2_coeff * y2;
222 output.push(y);
223 x2 = x1;
224 x1 = x;
225 y2 = y1;
226 y1 = y;
227 }
228
229 output
230}
231
232pub fn twin_t_notch_filter(
243 signal: &[f64],
244 r_ohms: f64,
245 c_farads: f64,
246 sample_rate: f64,
247) -> Vec<f64> {
248 let dt = 1.0 / sample_rate;
249 let rc = r_ohms * c_farads;
250 let rc2 = rc * rc;
251
252 let k = 2.0 / dt;
257 let k2 = k * k;
258
259 let n0 = rc2 * k2 + 1.0;
272 let n1 = -2.0 * rc2 * k2 + 2.0;
273 let n2 = rc2 * k2 + 1.0;
274
275 let d0 = rc2 * k2 + 4.0 * rc * k + 1.0;
276 let d1 = -2.0 * rc2 * k2 + 2.0;
277 let d2 = rc2 * k2 - 4.0 * rc * k + 1.0;
278
279 let b0 = n0 / d0;
281 let b1_coeff = n1 / d0;
282 let b2 = n2 / d0;
283 let a1_coeff = d1 / d0;
284 let a2_coeff = d2 / d0;
285
286 let mut output = Vec::with_capacity(signal.len());
287 let mut x1 = 0.0;
288 let mut x2 = 0.0;
289 let mut y1 = 0.0;
290 let mut y2 = 0.0;
291
292 for &x in signal {
293 let y = b0 * x + b1_coeff * x1 + b2 * x2 - a1_coeff * y1 - a2_coeff * y2;
294 output.push(y);
295 x2 = x1;
296 x1 = x;
297 y2 = y1;
298 y1 = y;
299 }
300
301 output
302}
303
304pub fn rc_lowpass_filter(signal: &[f64], r_ohms: f64, c_farads: f64, sample_rate: f64) -> Vec<f64> {
308 let tau = r_ohms * c_farads;
309 let dt = 1.0 / sample_rate;
310
311 let k = 2.0 / dt;
312 let denom = 1.0 + k * tau;
313 let b0 = 1.0 / denom;
314 let b1 = b0;
315 let a1 = (1.0 - k * tau) / denom;
316
317 let mut output = Vec::with_capacity(signal.len());
318 let mut x_prev = 0.0;
319 let mut y_prev = 0.0;
320
321 for &x in signal {
322 let y = b0 * x + b1 * x_prev - a1 * y_prev;
323 output.push(y);
324 x_prev = x;
325 y_prev = y;
326 }
327
328 output
329}
330
331pub fn rl_lowpass_magnitude(l_henrys: f64, r_ohms: f64, freq_hz: f64) -> f64 {
337 let fc = r_ohms / (2.0 * PI * l_henrys);
338 1.0 / (1.0 + (freq_hz / fc).powi(2)).sqrt()
339}
340
341pub fn rl_lowpass_filter(signal: &[f64], l_henrys: f64, r_ohms: f64, sample_rate: f64) -> Vec<f64> {
349 let tau = l_henrys / r_ohms;
351 let dt = 1.0 / sample_rate;
352
353 let k = 2.0 / dt;
356 let denom = 1.0 + k * tau;
357 let b0 = 1.0 / denom;
358 let b1 = b0;
359 let a1 = (1.0 - k * tau) / denom;
360
361 let mut output = Vec::with_capacity(signal.len());
362 let mut x_prev = 0.0;
363 let mut y_prev = 0.0;
364
365 for &x in signal {
366 let y = b0 * x + b1 * x_prev - a1 * y_prev;
367 output.push(y);
368 x_prev = x;
369 y_prev = y;
370 }
371
372 output
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378
379 #[test]
380 fn rc_lowpass_impulse_decays() {
381 let r = 10_000.0; let c = 10e-9; let sr = 96000.0;
384 let ir = rc_lowpass_impulse_response(r, c, sr, 1000);
385
386 assert!(ir[0] > 0.0);
388
389 let peak_idx = ir
391 .iter()
392 .enumerate()
393 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
394 .map(|(i, _)| i)
395 .unwrap();
396
397 assert!(
399 peak_idx < 10,
400 "Peak should be near the start, got idx {}",
401 peak_idx
402 );
403
404 for i in (peak_idx + 1)..ir.len() {
406 assert!(
407 ir[i] <= ir[i - 1] + 1e-10, "Impulse response should decay after peak at sample {}",
409 i
410 );
411 }
412
413 let peak_val = ir[peak_idx];
415 assert!(ir[999] < peak_val * 0.01, "Should decay to <1% of peak");
416 }
417
418 #[test]
419 fn rc_lowpass_cutoff_is_3db() {
420 let r = 10_000.0;
421 let c = 10e-9;
422 let fc = 1.0 / (2.0 * PI * r * c); let mag_at_fc = rc_lowpass_magnitude(r, c, fc);
425 let db_at_fc = 20.0 * mag_at_fc.log10();
426
427 assert!(
429 (db_at_fc + 3.0).abs() < 0.1,
430 "Magnitude at fc should be -3dB, got {db_at_fc}"
431 );
432 }
433
434 #[test]
435 fn rc_lowpass_passband_is_unity() {
436 let r = 10_000.0;
437 let c = 10e-9;
438
439 let mag_dc = rc_lowpass_magnitude(r, c, 0.0);
440 assert!((mag_dc - 1.0).abs() < 1e-10, "DC gain should be unity");
441 }
442
443 #[test]
444 fn rc_highpass_blocks_dc() {
445 let r = 33_000.0;
446 let c = 22e-9;
447 let sr = 96000.0;
448
449 let dc_input = vec![1.0; 10000];
451 let output = rc_highpass_filter(&dc_input, r, c, sr);
452
453 assert!(
455 output.last().unwrap().abs() < 1e-3,
456 "Highpass should block DC, got {}",
457 output.last().unwrap()
458 );
459 }
460
461 #[test]
462 fn rc_highpass_passes_high_freq() {
463 let r = 33_000.0;
464 let c = 22e-9;
465 let sr = 96000.0;
466 let fc = 1.0 / (2.0 * PI * r * c); let f_test = fc * 10.0;
470 let duration = 0.1;
471 let n = (sr * duration) as usize;
472 let input: Vec<f64> = (0..n)
473 .map(|i| (2.0 * PI * f_test * i as f64 / sr).sin())
474 .collect();
475
476 let output = rc_highpass_filter(&input, r, c, sr);
477
478 let in_rms: f64 = (input.iter().map(|x| x * x).sum::<f64>() / n as f64).sqrt();
480 let out_rms: f64 = (output.iter().map(|x| x * x).sum::<f64>() / n as f64).sqrt();
481 let ratio = out_rms / in_rms;
482
483 assert!(
484 ratio > 0.95,
485 "Highpass should pass high frequencies, ratio = {}",
486 ratio
487 );
488 }
489
490 #[test]
491 fn rlc_bandpass_peaks_at_resonance() {
492 let r = 100.0_f64;
494 let l = 10e-3_f64;
495 let c = 100e-9_f64;
496 let r_load = 100.0_f64; let sr = 96000.0 * 4.0; let f0 = 1.0 / (2.0 * PI * (l * c).sqrt()); let duration = 0.1;
500 let n = (sr * duration) as usize;
501
502 let input_res: Vec<f64> = (0..n)
504 .map(|i| (2.0 * PI * f0 * i as f64 / sr).sin())
505 .collect();
506 let output_res = rlc_bandpass_filter(&input_res, r, l, c, r_load, sr);
507
508 let f_low = f0 / 20.0;
510 let input_low: Vec<f64> = (0..n)
511 .map(|i| (2.0 * PI * f_low * i as f64 / sr).sin())
512 .collect();
513 let output_low = rlc_bandpass_filter(&input_low, r, l, c, r_load, sr);
514
515 let half = n / 2;
517 let rms_res: f64 =
518 (output_res[half..].iter().map(|x| x * x).sum::<f64>() / (n - half) as f64).sqrt();
519 let rms_low: f64 =
520 (output_low[half..].iter().map(|x| x * x).sum::<f64>() / (n - half) as f64).sqrt();
521
522 assert!(
523 rms_res > rms_low * 1.5,
524 "Response at resonance ({:.3}) should be greater than at {:.0}Hz ({:.3})",
525 rms_res,
526 f_low,
527 rms_low
528 );
529 }
530
531 #[test]
532 fn twin_t_notch_at_design_frequency() {
533 let r = 10_000.0;
534 let c = 10e-9;
535 let sr = 96000.0 * 4.0;
536 let f_notch = 1.0 / (2.0 * PI * r * c); let duration = 0.1;
538 let n = (sr * duration) as usize;
539
540 let input_notch: Vec<f64> = (0..n)
542 .map(|i| (2.0 * PI * f_notch * i as f64 / sr).sin())
543 .collect();
544 let output_notch = twin_t_notch_filter(&input_notch, r, c, sr);
545
546 let f_pass = f_notch * 5.0;
548 let input_pass: Vec<f64> = (0..n)
549 .map(|i| (2.0 * PI * f_pass * i as f64 / sr).sin())
550 .collect();
551 let output_pass = twin_t_notch_filter(&input_pass, r, c, sr);
552
553 let half = n / 2;
555 let rms_notch: f64 =
556 (output_notch[half..].iter().map(|x| x * x).sum::<f64>() / (n - half) as f64).sqrt();
557 let rms_pass: f64 =
558 (output_pass[half..].iter().map(|x| x * x).sum::<f64>() / (n - half) as f64).sqrt();
559
560 assert!(
562 rms_notch < rms_pass * 0.1,
563 "Notch response ({:.6}) should be much less than passband ({:.6})",
564 rms_notch,
565 rms_pass
566 );
567 }
568}