1mod butterworth;
8mod notch;
9mod sos;
10
11pub use butterworth::{bessel_sos, butterworth_sos, chebyshev1_sos};
12pub use notch::notch_sos;
13pub use sos::{Section, Sos, DEFAULT_SETTLING_TOLERANCE};
14
15pub use crate::error::OpsError;
16
17#[derive(Debug, Clone, Copy, PartialEq)]
19pub enum FilterBand {
20 Lowpass(f64),
21 Highpass(f64),
22 Bandpass(f64, f64),
23 Bandstop(f64, f64),
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
28pub enum FilterMode {
29 Forward,
31 #[default]
34 ForwardBackward,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
40pub enum FilterStart {
41 #[default]
44 Rest,
45 SteadyState,
48}
49
50#[derive(Debug, Clone, PartialEq)]
52pub enum FilterDesign {
53 Butterworth { order: usize, band: FilterBand },
55 Chebyshev1 { order: usize, ripple_db: f64, band: FilterBand },
57 Bessel { order: usize, band: FilterBand },
59 Notch { freq_hz: f64, q: f64 },
61 Sos(Sos),
63}
64
65#[derive(Debug, Clone, PartialEq)]
67pub struct FilterSpec {
68 pub design: FilterDesign,
69 pub mode: FilterMode,
70 pub start: FilterStart,
71}
72
73pub const DEFAULT_BUTTERWORTH_ORDER: usize = 5;
75
76impl FilterSpec {
77 pub fn butterworth(order: usize, band: FilterBand) -> Self {
78 Self { design: FilterDesign::Butterworth { order, band }, mode: FilterMode::default(), start: FilterStart::default() }
79 }
80
81 pub fn bandpass(low_hz: f64, high_hz: f64) -> Self {
82 Self::butterworth(DEFAULT_BUTTERWORTH_ORDER, FilterBand::Bandpass(low_hz, high_hz))
83 }
84
85 pub fn highpass(cutoff_hz: f64) -> Self {
86 Self::butterworth(DEFAULT_BUTTERWORTH_ORDER, FilterBand::Highpass(cutoff_hz))
87 }
88
89 pub fn lowpass(cutoff_hz: f64) -> Self {
90 Self::butterworth(DEFAULT_BUTTERWORTH_ORDER, FilterBand::Lowpass(cutoff_hz))
91 }
92
93 pub fn bandstop(low_hz: f64, high_hz: f64) -> Self {
94 Self::butterworth(DEFAULT_BUTTERWORTH_ORDER, FilterBand::Bandstop(low_hz, high_hz))
95 }
96
97 pub fn chebyshev1(order: usize, ripple_db: f64, band: FilterBand) -> Self {
98 Self { design: FilterDesign::Chebyshev1 { order, ripple_db, band }, mode: FilterMode::default(), start: FilterStart::default() }
99 }
100
101 pub fn bessel(order: usize, band: FilterBand) -> Self {
103 Self { design: FilterDesign::Bessel { order, band }, mode: FilterMode::default(), start: FilterStart::default() }
104 }
105
106 pub fn notch(freq_hz: f64, q: f64) -> Self {
107 Self { design: FilterDesign::Notch { freq_hz, q }, mode: FilterMode::default(), start: FilterStart::default() }
108 }
109
110 pub fn sos(sos: Sos) -> Self {
111 Self { design: FilterDesign::Sos(sos), mode: FilterMode::default(), start: FilterStart::default() }
112 }
113
114 pub fn with_order(mut self, new_order: usize) -> Self {
116 if let FilterDesign::Butterworth { order, .. } | FilterDesign::Chebyshev1 { order, .. } | FilterDesign::Bessel { order, .. } = &mut self.design {
117 *order = new_order;
118 }
119 self
120 }
121
122 pub fn with_mode(mut self, mode: FilterMode) -> Self {
123 self.mode = mode;
124 self
125 }
126
127 pub fn with_start(mut self, start: FilterStart) -> Self {
129 self.start = start;
130 self
131 }
132
133 pub fn design(&self, sample_rate: f64) -> Result<Sos, OpsError> {
141 match &self.design {
142 FilterDesign::Butterworth { order, band } => butterworth_sos(*order, *band, sample_rate),
143 FilterDesign::Chebyshev1 { order, ripple_db, band } => chebyshev1_sos(*order, *ripple_db, *band, sample_rate),
144 FilterDesign::Bessel { order, band } => bessel_sos(*order, *band, sample_rate),
145 FilterDesign::Notch { freq_hz, q } => notch_sos(*freq_hz, *q, sample_rate),
146 FilterDesign::Sos(sos) => {
147 sos.validate()?;
148 Ok(sos.clone())
149 }
150 }
151 }
152
153 pub fn settling(&self, sample_rate: f64) -> Result<(usize, usize), OpsError> {
160 let s = self.design(sample_rate)?.settling_samples(DEFAULT_SETTLING_TOLERANCE);
161 Ok(match self.mode {
162 FilterMode::Forward => (s, 0),
163 FilterMode::ForwardBackward => (s, s),
164 })
165 }
166}
167
168pub(crate) fn check_sample_rate(fs: f64) -> Result<(), OpsError> {
169 if fs.is_finite() && fs > 0.0 { Ok(()) } else { Err(OpsError::InvalidSampleRate(fs)) }
170}
171
172pub(crate) fn check_cutoff(cutoff_hz: f64, fs: f64) -> Result<(), OpsError> {
173 let nyquist_hz = fs / 2.0;
174 if cutoff_hz.is_finite() && cutoff_hz > 0.0 && cutoff_hz < nyquist_hz {
175 Ok(())
176 } else {
177 Err(OpsError::CutoffOutOfRange { cutoff_hz, nyquist_hz })
178 }
179}