geng_rodio/source/
blt.rs

1use std::f32::consts::PI;
2use std::time::Duration;
3
4use crate::Source;
5
6// Implemented following http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
7
8/// Internal function that builds a `BltFilter` object.
9pub fn low_pass<I>(input: I, freq: u32) -> BltFilter<I>
10where
11    I: Source<Item = f32>,
12{
13    BltFilter {
14        input,
15        formula: BltFormula::LowPass { freq, q: 0.5 },
16        applier: None,
17        x_n1: 0.0,
18        x_n2: 0.0,
19        y_n1: 0.0,
20        y_n2: 0.0,
21    }
22}
23
24pub fn high_pass<I>(input: I, freq: u32) -> BltFilter<I>
25where
26    I: Source<Item = f32>,
27{
28    BltFilter {
29        input,
30        formula: BltFormula::HighPass { freq, q: 0.5 },
31        applier: None,
32        x_n1: 0.0,
33        x_n2: 0.0,
34        y_n1: 0.0,
35        y_n2: 0.0,
36    }
37}
38
39#[derive(Clone, Debug)]
40pub struct BltFilter<I> {
41    input: I,
42    formula: BltFormula,
43    applier: Option<BltApplier>,
44    x_n1: f32,
45    x_n2: f32,
46    y_n1: f32,
47    y_n2: f32,
48}
49
50impl<I> BltFilter<I> {
51    /// Modifies this filter so that it becomes a low-pass filter.
52    pub fn to_low_pass(&mut self, freq: u32) {
53        self.formula = BltFormula::LowPass { freq, q: 0.5 };
54        self.applier = None;
55    }
56
57    /// Modifies this filter so that it becomes a high-pass filter
58    pub fn to_high_pass(&mut self, freq: u32) {
59        self.formula = BltFormula::HighPass { freq, q: 0.5 };
60        self.applier = None;
61    }
62
63    /// Returns a reference to the inner source.
64    #[inline]
65    pub fn inner(&self) -> &I {
66        &self.input
67    }
68
69    /// Returns a mutable reference to the inner source.
70    #[inline]
71    pub fn inner_mut(&mut self) -> &mut I {
72        &mut self.input
73    }
74
75    /// Returns the inner source.
76    #[inline]
77    pub fn into_inner(self) -> I {
78        self.input
79    }
80}
81
82impl<I> Iterator for BltFilter<I>
83where
84    I: Source<Item = f32>,
85{
86    type Item = f32;
87
88    #[inline]
89    fn next(&mut self) -> Option<f32> {
90        let last_in_frame = self.input.current_frame_len() == Some(1);
91
92        if self.applier.is_none() {
93            self.applier = Some(self.formula.to_applier(self.input.sample_rate()));
94        }
95
96        let sample = match self.input.next() {
97            None => return None,
98            Some(s) => s,
99        };
100
101        let result = self
102            .applier
103            .as_ref()
104            .unwrap()
105            .apply(sample, self.x_n1, self.x_n2, self.y_n1, self.y_n2);
106
107        self.y_n2 = self.y_n1;
108        self.x_n2 = self.x_n1;
109        self.y_n1 = result;
110        self.x_n1 = sample;
111
112        if last_in_frame {
113            self.applier = None;
114        }
115
116        Some(result)
117    }
118
119    #[inline]
120    fn size_hint(&self) -> (usize, Option<usize>) {
121        self.input.size_hint()
122    }
123}
124
125impl<I> ExactSizeIterator for BltFilter<I> where I: Source<Item = f32> + ExactSizeIterator {}
126
127impl<I> Source for BltFilter<I>
128where
129    I: Source<Item = f32>,
130{
131    #[inline]
132    fn current_frame_len(&self) -> Option<usize> {
133        self.input.current_frame_len()
134    }
135
136    #[inline]
137    fn channels(&self) -> u16 {
138        self.input.channels()
139    }
140
141    #[inline]
142    fn sample_rate(&self) -> u32 {
143        self.input.sample_rate()
144    }
145
146    #[inline]
147    fn total_duration(&self) -> Option<Duration> {
148        self.input.total_duration()
149    }
150}
151
152#[derive(Clone, Debug)]
153enum BltFormula {
154    LowPass { freq: u32, q: f32 },
155    HighPass { freq: u32, q: f32 },
156}
157
158impl BltFormula {
159    fn to_applier(&self, sampling_frequency: u32) -> BltApplier {
160        match *self {
161            BltFormula::LowPass { freq, q } => {
162                let w0 = 2.0 * PI * freq as f32 / sampling_frequency as f32;
163
164                let alpha = w0.sin() / (2.0 * q);
165                let b1 = 1.0 - w0.cos();
166                let b0 = b1 / 2.0;
167                let b2 = b0;
168                let a0 = 1.0 + alpha;
169                let a1 = -2.0 * w0.cos();
170                let a2 = 1.0 - alpha;
171
172                BltApplier {
173                    b0: b0 / a0,
174                    b1: b1 / a0,
175                    b2: b2 / a0,
176                    a1: a1 / a0,
177                    a2: a2 / a0,
178                }
179            }
180            BltFormula::HighPass { freq, q } => {
181                let w0 = 2.0 * PI * freq as f32 / sampling_frequency as f32;
182                let cos_w0 = w0.cos();
183                let alpha = w0.sin() / (2.0 * q);
184
185                let b0 = (1.0 + cos_w0) / 2.0;
186                let b1 = -1.0 - cos_w0;
187                let b2 = b0;
188                let a0 = 1.0 + alpha;
189                let a1 = -2.0 * cos_w0;
190                let a2 = 1.0 - alpha;
191
192                BltApplier {
193                    b0: b0 / a0,
194                    b1: b1 / a0,
195                    b2: b2 / a0,
196                    a1: a1 / a0,
197                    a2: a2 / a0,
198                }
199            }
200        }
201    }
202}
203
204#[derive(Clone, Debug)]
205struct BltApplier {
206    b0: f32,
207    b1: f32,
208    b2: f32,
209    a1: f32,
210    a2: f32,
211}
212
213impl BltApplier {
214    #[inline]
215    fn apply(&self, x_n: f32, x_n1: f32, x_n2: f32, y_n1: f32, y_n2: f32) -> f32 {
216        self.b0 * x_n + self.b1 * x_n1 + self.b2 * x_n2 - self.a1 * y_n1 - self.a2 * y_n2
217    }
218}