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}