direct_neural_biasing/processing/filters/
bandpass.rs

1use super::FilterInstance;
2use crate::processing::signal_processor::SignalProcessorConfig;
3
4use std::collections::HashMap;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Serialize, Deserialize, Clone)]
8pub struct BandPassFilterConfig {
9    pub id: String,
10    pub f_low: f64,
11    pub f_high: f64,
12}
13
14pub struct BandPassFilter {
15    config: BandPassFilterConfig,
16    high_pass: SecondOrderFilter,
17    low_pass: SecondOrderFilter,
18    keys: Keys,
19}
20
21struct SecondOrderFilter {
22    a: [f64; 3],
23    b: [f64; 3],
24    x: [f64; 2],
25    y: [f64; 2],
26}
27
28impl SecondOrderFilter {
29    pub fn new(f0: f64, fs: f64, filter_type: &str) -> Self {
30        let q = (2.0f64).sqrt() / 2.0; // Example for a Butterworth filter
31        let omega = 2.0 * std::f64::consts::PI * f0 / fs;
32        let alpha = f64::sin(omega) / (2.0 * q);
33
34        let (b0, b1, b2, a0, a1, a2) = match filter_type {
35            "high" => (
36                (1.0 + f64::cos(omega)) / 2.0,
37                -(1.0 + f64::cos(omega)),
38                (1.0 + f64::cos(omega)) / 2.0,
39                1.0 + alpha,
40                -2.0 * f64::cos(omega),
41                1.0 - alpha,
42            ),
43            "low" => (
44                (1.0 - f64::cos(omega)) / 2.0,
45                1.0 - f64::cos(omega),
46                (1.0 - f64::cos(omega)) / 2.0,
47                1.0 + alpha,
48                -2.0 * f64::cos(omega),
49                1.0 - alpha,
50            ),
51            _ => panic!("Unsupported filter type"),
52        };
53
54        SecondOrderFilter {
55            a: [a0, a1, a2],
56            b: [b0, b1, b2],
57            x: [0.0, 0.0],
58            y: [0.0, 0.0],
59        }
60    }
61
62    fn calculate_output(&mut self, input: f64) -> f64 {
63        let output = (self.b[0] / self.a[0]) * input
64            + (self.b[1] / self.a[0]) * self.x[0]
65            + (self.b[2] / self.a[0]) * self.x[1]
66            - (self.a[1] / self.a[0]) * self.y[0]
67            - (self.a[2] / self.a[0]) * self.y[1];
68
69        // Update internal sample history
70        self.x[1] = self.x[0];
71        self.x[0] = input;
72        self.y[1] = self.y[0];
73        self.y[0] = output;
74
75        output
76    }
77}
78
79pub struct Keys {
80    filtered_sample: &'static str,
81}
82
83impl BandPassFilter {
84    // Constructor with initialization for high-pass and low-pass filters
85    pub fn new(config: BandPassFilterConfig, fs: f64) -> Self {
86        let high_pass = SecondOrderFilter::new(config.f_low, fs, "high");
87        let low_pass = SecondOrderFilter::new(config.f_high, fs, "low");
88        let keys = Keys {
89            filtered_sample: Box::leak(
90                format!("filters:{}:filtered_sample", config.id).into_boxed_str(),
91            ),
92        };
93
94        BandPassFilter {
95            config,
96            high_pass,
97            low_pass,
98            keys,
99        }
100    }
101}
102
103impl FilterInstance for BandPassFilter {
104    fn id(&self) -> &str {
105        &self.config.id
106    }
107
108    fn process_sample(
109        &mut self,
110        _global_config: &SignalProcessorConfig,
111        results: &mut HashMap<&'static str, f64>,
112    ) {
113        if let Some(&raw_sample) = results.get("global:raw_sample") {
114            // Apply high-pass filter first
115            let high_pass_output = self.high_pass.calculate_output(raw_sample);
116            // Apply low-pass filter to the output of the high-pass filter
117            let filtered_sample = self.low_pass.calculate_output(high_pass_output);
118
119            results.insert(self.keys.filtered_sample, filtered_sample);
120        }
121    }
122}