caw_modules/
band_pass_butterworth.rs

1use crate::low_level::biquad_band_pass_filter::butterworth;
2use caw_builder_proc_macros::builder;
3use caw_core::{Buf, Filter, SigCtx, SigT};
4use itertools::izip;
5
6builder! {
7    #[constructor = "band_pass_butterworth"]
8    #[constructor_doc = "A basic band pass filter"]
9    #[generic_setter_type_name = "X"]
10    pub struct Props {
11        #[generic_with_constraint = "SigT<Item = f32>"]
12        #[generic_name = "L"]
13        lower_cutoff_hz: _,
14        #[generic_with_constraint = "SigT<Item = f32>"]
15        #[generic_name = "U"]
16        upper_cutoff_hz: _,
17        #[default = 1]
18        filter_order_half: usize,
19    }
20}
21
22impl<L, U> Filter for Props<L, U>
23where
24    L: SigT<Item = f32>,
25    U: SigT<Item = f32>,
26{
27    type ItemIn = f32;
28
29    type Out<S>
30        = BandPassButterworth<S, L, U>
31    where
32        S: SigT<Item = Self::ItemIn>;
33
34    fn into_sig<S>(self, sig: S) -> Self::Out<S>
35    where
36        S: SigT<Item = Self::ItemIn>,
37    {
38        BandPassButterworth {
39            state: butterworth::State::new(self.filter_order_half),
40            props: self,
41            sig,
42            buf: Vec::new(),
43        }
44    }
45}
46
47pub struct BandPassButterworth<S, L, U>
48where
49    L: SigT<Item = f32>,
50    U: SigT<Item = f32>,
51{
52    props: Props<L, U>,
53    sig: S,
54    state: butterworth::State,
55    buf: Vec<f32>,
56}
57
58impl<S, L, U> SigT for BandPassButterworth<S, L, U>
59where
60    S: SigT<Item = f32>,
61    L: SigT<Item = f32>,
62    U: SigT<Item = f32>,
63{
64    type Item = f32;
65
66    fn sample(&mut self, ctx: &SigCtx) -> impl Buf<Self::Item> {
67        self.buf.resize(ctx.num_samples, 0.0);
68        let sig = self.sig.sample(ctx);
69        let lower_cutoff_hz = self.props.lower_cutoff_hz.sample(ctx);
70        let upper_cutoff_hz = self.props.upper_cutoff_hz.sample(ctx);
71        for (out, sample, lower_cutoff_hz, upper_cutoff_hz) in izip! {
72            self.buf.iter_mut(),
73            sig.iter(),
74            lower_cutoff_hz.iter(),
75            upper_cutoff_hz.iter(),
76        } {
77            *out = self.state.run(
78                sample as f64,
79                ctx.sample_rate_hz as f64,
80                lower_cutoff_hz as f64,
81                upper_cutoff_hz as f64,
82            ) as f32;
83        }
84        &self.buf
85    }
86}
87
88pub struct PropsCentered<C, W, M>
89where
90    C: SigT<Item = f32>,
91    W: SigT<Item = f32>,
92{
93    mid_cutoff_hz: C,
94    width_cutoff_ratio: W,
95    min_cutoff_hz: M,
96    filter_order_half: usize,
97}
98
99impl<C, W, M> PropsCentered<C, W, M>
100where
101    C: SigT<Item = f32>,
102    W: SigT<Item = f32>,
103    M: SigT<Item = f32>,
104{
105    fn new(
106        mid_cutoff_hz: C,
107        width_cutoff_ratio: W,
108        min_cutoff_hz: M,
109        filter_order_half: usize,
110    ) -> Self {
111        Self {
112            mid_cutoff_hz,
113            width_cutoff_ratio,
114            min_cutoff_hz,
115            filter_order_half,
116        }
117    }
118}
119
120builder! {
121    #[constructor = "band_pass_butterworth_centered"]
122    #[constructor_doc = "A basic band pass filter"]
123    #[build_fn = "PropsCentered::new"]
124    #[build_ty = "PropsCentered<C, W, M>"]
125    #[generic_setter_type_name = "X"]
126    pub struct PropsCenteredBuilder {
127        #[generic_with_constraint = "SigT<Item = f32>"]
128        #[generic_name = "C"]
129        mid_cutoff_hz: _,
130        #[generic_with_constraint = "SigT<Item = f32>"]
131        #[generic_name = "W"]
132        width_cutoff_ratio: _,
133        #[generic_with_constraint = "SigT<Item = f32>"]
134        #[generic_name = "M"]
135        #[default = 20.0]
136        min_cutoff_hz: f32,
137        #[default = 1]
138        filter_order_half: usize,
139    }
140}
141
142impl<C, W, M> Filter for PropsCenteredBuilder<C, W, M>
143where
144    C: SigT<Item = f32>,
145    W: SigT<Item = f32>,
146    M: SigT<Item = f32>,
147{
148    type ItemIn = f32;
149
150    type Out<S>
151        = BandPassButterworthCentered<S, C, W, M>
152    where
153        S: SigT<Item = Self::ItemIn>;
154
155    fn into_sig<S>(self, sig: S) -> Self::Out<S>
156    where
157        S: SigT<Item = Self::ItemIn>,
158    {
159        let props = self.build();
160        BandPassButterworthCentered {
161            state: butterworth::State::new(props.filter_order_half),
162            props,
163            sig,
164            buf: Vec::new(),
165        }
166    }
167}
168
169pub struct BandPassButterworthCentered<S, C, W, M>
170where
171    C: SigT<Item = f32>,
172    W: SigT<Item = f32>,
173    M: SigT<Item = f32>,
174{
175    props: PropsCentered<C, W, M>,
176    sig: S,
177    state: butterworth::State,
178    buf: Vec<f32>,
179}
180
181const SAFETY: f32 = 2.;
182
183impl<S, C, W, M> SigT for BandPassButterworthCentered<S, C, W, M>
184where
185    S: SigT<Item = f32>,
186    C: SigT<Item = f32>,
187    W: SigT<Item = f32>,
188    M: SigT<Item = f32>,
189{
190    type Item = f32;
191
192    fn sample(&mut self, ctx: &SigCtx) -> impl Buf<Self::Item> {
193        self.buf.resize(ctx.num_samples, 0.0);
194        let sig = self.sig.sample(ctx);
195        let mid_cutoff_hz = self.props.mid_cutoff_hz.sample(ctx);
196        let width_cutoff_ratio = self.props.width_cutoff_ratio.sample(ctx);
197        let min_cutoff_hz = self.props.min_cutoff_hz.sample(ctx);
198        for (out, sample, mid_cutoff_hz, width_cutoff_ratio, min_cutoff_hz) in izip! {
199            self.buf.iter_mut(),
200            sig.iter(),
201            mid_cutoff_hz.iter(),
202            width_cutoff_ratio.iter(),
203            min_cutoff_hz.iter(),
204        } {
205            let width_cutoff_hz = width_cutoff_ratio * mid_cutoff_hz;
206            let lower_cutoff_hz =
207                (mid_cutoff_hz - (width_cutoff_hz / 2.0)).max(min_cutoff_hz);
208            let upper_cutoff_hz = lower_cutoff_hz + width_cutoff_hz;
209            *out = (self.state.run(
210                sample as f64,
211                ctx.sample_rate_hz as f64,
212                lower_cutoff_hz as f64,
213                upper_cutoff_hz as f64,
214            ) as f32)
215                .clamp(-SAFETY, SAFETY)
216        }
217        &self.buf
218    }
219}