rg3d_sound/dsp/
filters.rs

1//! Filters module.
2//!
3//! # Overview
4//!
5//! This module contains some of common filters used in digital signal processing.
6//! Since this is very specific theme with lots of background, every filter has link to source with good
7//! description of each filter. There is no need to describe them all here.
8
9use crate::dsp::DelayLine;
10use rg3d_core::visitor::{Visit, VisitResult, Visitor};
11
12/// One-pole Filter.
13/// For details see - <https://www.earlevel.com/main/2012/12/15/a-one-pole-filter/>
14#[derive(Debug, Clone)]
15pub struct OnePole {
16    a0: f32,
17    b1: f32,
18    last: f32,
19}
20
21impl Default for OnePole {
22    fn default() -> Self {
23        Self {
24            a0: 1.0,
25            b1: 0.0,
26            last: 0.0,
27        }
28    }
29}
30
31fn get_b1(fc: f32) -> f32 {
32    (-2.0 * std::f32::consts::PI * fc.min(1.0).max(0.0)).exp()
33}
34
35impl OnePole {
36    /// Creates new instance of one pole filter with given normalized frequency.
37    pub fn new(fc: f32) -> Self {
38        let b1 = get_b1(fc);
39        Self {
40            b1,
41            a0: 1.0 - b1,
42            last: 0.0,
43        }
44    }
45
46    /// Sets normalized frequency of the filter.
47    pub fn set_fc(&mut self, fc: f32) {
48        self.b1 = get_b1(fc);
49        self.a0 = 1.0 - self.b1;
50    }
51
52    /// Sets pole of filter directly.
53    pub fn set_pole(&mut self, pole: f32) {
54        self.b1 = pole.min(1.0).max(0.0);
55        self.a0 = 1.0 - self.b1;
56    }
57
58    /// Processes single sample.
59    pub fn feed(&mut self, sample: f32) -> f32 {
60        let result = sample * self.a0 + self.last * self.b1;
61        self.last = result;
62        result
63    }
64}
65
66impl Visit for OnePole {
67    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
68        visitor.enter_region(name)?;
69
70        self.a0.visit("A0", visitor)?;
71        self.b1.visit("B1", visitor)?;
72        self.last.visit("Last", visitor)?;
73
74        visitor.leave_region()
75    }
76}
77
78/// Lowpass-Feedback Comb Filter
79/// For details see - <https://ccrma.stanford.edu/~jos/pasp/Lowpass_Feedback_Comb_Filter.html>
80#[derive(Debug, Clone)]
81pub struct LpfComb {
82    low_pass: OnePole,
83    delay_line: DelayLine,
84    feedback: f32,
85}
86
87impl Default for LpfComb {
88    fn default() -> Self {
89        Self {
90            low_pass: Default::default(),
91            delay_line: Default::default(),
92            feedback: 0.0,
93        }
94    }
95}
96
97impl LpfComb {
98    /// Creates new instance of lowpass-feedback comb filter with given parameters.
99    pub fn new(len: usize, fc: f32, feedback: f32) -> Self {
100        Self {
101            low_pass: OnePole::new(fc),
102            delay_line: DelayLine::new(len),
103            feedback,
104        }
105    }
106
107    /// Sets feedback factor. For numeric stability factor should be in 0..1 range.
108    pub fn set_feedback(&mut self, feedback: f32) {
109        self.feedback = feedback;
110    }
111
112    /// Returns current feedback factor.
113    pub fn feedback(&self) -> f32 {
114        self.feedback
115    }
116
117    /// Sets normalized frequency of internal lowpass filter.
118    pub fn set_fc(&mut self, fc: f32) {
119        self.low_pass.set_fc(fc)
120    }
121
122    /// Returns total length of internal delay line (in samples)
123    pub fn len(&self) -> usize {
124        self.delay_line.len()
125    }
126
127    /// Processes single sample.
128    pub fn feed(&mut self, sample: f32) -> f32 {
129        let result = sample + self.feedback * self.low_pass.feed(self.delay_line.last());
130        self.delay_line.feed(result);
131        result
132    }
133}
134
135impl Visit for LpfComb {
136    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
137        visitor.enter_region(name)?;
138
139        self.delay_line.visit("DelayLine", visitor)?;
140        self.feedback.visit("Feedback", visitor)?;
141        self.low_pass.visit("LowPass", visitor)?;
142
143        visitor.leave_region()
144    }
145}
146
147/// Allpass Filter - <https://ccrma.stanford.edu/~jos/pasp/Allpass_Filters.html>
148/// For details see - <https://ccrma.stanford.edu/~jos/pasp/Allpass_Two_Combs.html>
149#[derive(Debug, Clone)]
150pub struct AllPass {
151    delay_line: DelayLine,
152    gain: f32,
153}
154
155impl Default for AllPass {
156    fn default() -> Self {
157        Self {
158            delay_line: Default::default(),
159            gain: 1.0,
160        }
161    }
162}
163
164impl AllPass {
165    /// Creates new instance of allpass filter.
166    pub fn new(len: usize, gain: f32) -> Self {
167        Self {
168            delay_line: DelayLine::new(len),
169            gain,
170        }
171    }
172
173    /// Sets overall gain of feedback parts of filter. Should be in 0..1 range.
174    pub fn set_gain(&mut self, gain: f32) {
175        self.gain = gain;
176    }
177
178    /// Returns length of internal delay line.
179    pub fn len(&self) -> usize {
180        self.delay_line.len()
181    }
182
183    /// Processes single sample.
184    pub fn feed(&mut self, sample: f32) -> f32 {
185        let delay_line_output = self.delay_line.last();
186        let am_arm = -self.gain * delay_line_output;
187        let sum_left = sample + am_arm;
188        let b0_arm = sum_left * self.gain;
189        self.delay_line.feed(sum_left);
190        delay_line_output + b0_arm
191    }
192}
193
194impl Visit for AllPass {
195    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
196        visitor.enter_region(name)?;
197
198        self.delay_line.visit("DelayLine", visitor)?;
199        self.gain.visit("Gain", visitor)?;
200
201        visitor.leave_region()
202    }
203}
204
205/// Exact kind of biquad filter - it defines coefficients of the filter.
206/// More info here: <https://shepazu.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html>
207pub enum BiquadKind {
208    /// Reduces amplitude of frequencies higher F_center.
209    LowPass,
210
211    /// Reduces amplitude of frequencies lower F_center.
212    HighPass,
213
214    /// Reduces amplitude of all frequencies except in some band around F_center giving _/̅ \_ shape
215    BandPass,
216
217    /// Passes all frequencies but gives 90* phase shift to a signal at F_center.
218    AllPass,
219
220    /// Reduces amplitude of frequencies in a shape like this ̅ \_ where location of center of \
221    /// defined by F_center.
222    LowShelf,
223
224    /// Reduces amplitude of frequencies in a shape like this _/̅  where location of center of /
225    /// defined by F_center.
226    HighShelf,
227}
228
229/// Generic second order digital filter.
230/// More info here: <https://ccrma.stanford.edu/~jos/filters/BiQuad_Section.html>
231#[derive(Clone, Debug)]
232pub struct Biquad {
233    b0: f32,
234    b1: f32,
235    b2: f32,
236    a1: f32,
237    a2: f32,
238    prev1: f32,
239    prev2: f32,
240}
241
242impl Biquad {
243    /// Creates new filter of given kind with specified parameters, where:
244    /// `fc` - normalized frequency
245    /// `gain` - desired gain at `fc`
246    /// `quality` - defines band width at which amplitude decays by half (or by 3 db in log scale), the lower it will
247    /// be, the wider band will be and vice versa. See more info [here](https://ccrma.stanford.edu/~jos/filters/Quality_Factor_Q.html)
248    pub fn new(kind: BiquadKind, fc: f32, gain: f32, quality: f32) -> Self {
249        let mut filter = Self::default();
250
251        filter.tune(kind, fc, gain, quality);
252
253        filter
254    }
255
256    /// Creates new instance of filter with given coefficients.
257    pub fn from_coefficients(b0: f32, b1: f32, b2: f32, a1: f32, a2: f32) -> Self {
258        Self {
259            b0,
260            b1,
261            b2,
262            a1,
263            a2,
264            prev1: 0.0,
265            prev2: 0.0,
266        }
267    }
268
269    /// Tunes filter using specified parameters.
270    /// `kind` - new kind of filter.
271    /// `fc` - normalized frequency
272    /// `gain` - desired gain at `fc`
273    /// `quality` - defines band width at which amplitude decays by half (or by 3 db in log scale), the lower it will
274    /// be, the wider band will be and vice versa. See more info [here](https://ccrma.stanford.edu/~jos/filters/Quality_Factor_Q.html)
275    pub fn tune(&mut self, kind: BiquadKind, fc: f32, gain: f32, quality: f32) {
276        let w0 = 2.0 * std::f32::consts::PI * fc;
277        let w0_cos = w0.cos();
278        let w0_sin = w0.sin();
279        let alpha = w0_sin / (2.0 * quality);
280
281        let (b0, b1, b2, a0, a1, a2) = match kind {
282            BiquadKind::LowPass => {
283                let b0 = (1.0 - w0_cos) / 2.0;
284                let b1 = 1.0 - w0_cos;
285                let b2 = b0;
286                let a0 = 1.0 + alpha;
287                let a1 = -2.0 * w0_cos;
288                let a2 = 1.0 - alpha;
289                (b0, b1, b2, a0, a1, a2)
290            }
291            BiquadKind::HighPass => {
292                let b0 = (1.0 + w0_cos) / 2.0;
293                let b1 = -(1.0 + w0_cos);
294                let b2 = b0;
295                let a0 = 1.0 + alpha;
296                let a1 = -2.0 * w0_cos;
297                let a2 = 1.0 - alpha;
298                (b0, b1, b2, a0, a1, a2)
299            }
300            BiquadKind::BandPass => {
301                let b0 = w0_sin / 2.0;
302                let b1 = 0.0;
303                let b2 = -b0;
304                let a0 = 1.0 + alpha;
305                let a1 = -2.0 * w0_cos;
306                let a2 = 1.0 - alpha;
307                (b0, b1, b2, a0, a1, a2)
308            }
309            BiquadKind::AllPass => {
310                let b0 = 1.0 - alpha;
311                let b1 = -2.0 * w0_cos;
312                let b2 = 1.0 + alpha;
313                let a0 = b2;
314                let a1 = -2.0 * w0_cos;
315                let a2 = 1.0 - alpha;
316                (b0, b1, b2, a0, a1, a2)
317            }
318            BiquadKind::LowShelf => {
319                let sq = 2.0 * gain.sqrt() * alpha;
320                let b0 = gain * ((gain + 1.0) - (gain - 1.0) * w0_cos + sq);
321                let b1 = 2.0 * gain * ((gain - 1.0) - (gain + 1.0) * w0_cos);
322                let b2 = gain * ((gain + 1.0) - (gain - 1.0) * w0_cos - sq);
323                let a0 = (gain + 1.0) + (gain - 1.0) * w0_cos + sq;
324                let a1 = -2.0 * ((gain - 1.0) + (gain + 1.0) * w0_cos);
325                let a2 = (gain + 1.0) + (gain - 1.0) * w0_cos - sq;
326                (b0, b1, b2, a0, a1, a2)
327            }
328            BiquadKind::HighShelf => {
329                let sq = 2.0 * gain.sqrt() * alpha;
330                let b0 = gain * ((gain + 1.0) + (gain - 1.0) * w0_cos + sq);
331                let b1 = -2.0 * gain * ((gain - 1.0) + (gain + 1.0) * w0_cos);
332                let b2 = gain * ((gain + 1.0) + (gain - 1.0) * w0_cos - sq);
333                let a0 = (gain + 1.0) - (gain - 1.0) * w0_cos + sq;
334                let a1 = 2.0 * ((gain - 1.0) - (gain + 1.0) * w0_cos);
335                let a2 = (gain + 1.0) - (gain - 1.0) * w0_cos - sq;
336                (b0, b1, b2, a0, a1, a2)
337            }
338        };
339
340        self.b0 = b0 / a0;
341        self.b1 = b1 / a0;
342        self.b2 = b2 / a0;
343        self.a1 = a1 / a0;
344        self.a2 = a2 / a0;
345    }
346
347    /// Processes single sample.
348    pub fn feed(&mut self, sample: f32) -> f32 {
349        let result = sample * self.b0 + self.prev1;
350        self.prev1 = sample * self.b1 - result * self.a1 + self.prev2;
351        self.prev2 = sample * self.b2 - result * self.a2;
352        result
353    }
354}
355
356impl Default for Biquad {
357    fn default() -> Self {
358        Self {
359            b0: 1.0,
360            b1: 0.0,
361            b2: 0.0,
362            a1: 0.0,
363            a2: 0.0,
364            prev1: 0.0,
365            prev2: 0.0,
366        }
367    }
368}
369
370impl Visit for Biquad {
371    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
372        visitor.enter_region(name)?;
373
374        self.b0.visit("b0", visitor)?;
375        self.b1.visit("b1", visitor)?;
376        self.b2.visit("b2", visitor)?;
377        self.a1.visit("a1", visitor)?;
378        self.a2.visit("a2", visitor)?;
379        self.prev1.visit("prev1", visitor)?;
380        self.prev2.visit("prev2", visitor)?;
381
382        visitor.leave_region()
383    }
384}