rodio/source/
blt.rs

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