synfx_dsp/
filters.rs

1// Copyright (c) 2021-2022 Weird Constructor <weirdconstructor@gmail.com>
2// This file is a part of synfx-dsp. Released under GPL-3.0-or-later.
3// See README.md and COPYING for details.
4
5//! A collection of filters, ranging from simple one poles to more interesting ones.
6
7use crate::{f, Flt};
8use std::simd::f32x4;
9
10// one pole lp from valley rack free:
11// https://github.com/ValleyAudio/ValleyRackFree/blob/v1.0/src/Common/DSP/OnePoleFilters.cpp
12#[inline]
13/// Process a very simple one pole 6dB low pass filter.
14/// Useful in various applications, from usage in a synthesizer to
15/// damping stuff in a reverb/delay.
16///
17/// * `input`  - Input sample
18/// * `freq`   - Frequency between 1.0 and 22000.0Hz
19/// * `israte` - 1.0 / samplerate
20/// * `z`      - The internal one sample buffer of the filter.
21///
22///```
23///    use synfx_dsp::*;
24///
25///    let samples  = vec![0.0; 44100];
26///    let mut z    = 0.0;
27///    let mut freq = 1000.0;
28///
29///    for s in samples.iter() {
30///        let s_out =
31///            process_1pole_lowpass(*s, freq, 1.0 / 44100.0, &mut z);
32///        // ... do something with the result here.
33///    }
34///```
35pub fn process_1pole_lowpass(input: f32, freq: f32, israte: f32, z: &mut f32) -> f32 {
36    let b = (-std::f32::consts::TAU * freq * israte).exp();
37    let a = 1.0 - b;
38    *z = a * input + *z * b;
39    *z
40}
41
42#[derive(Debug, Clone, Copy, Default)]
43pub struct OnePoleLPF<F: Flt> {
44    israte: F,
45    a: F,
46    b: F,
47    freq: F,
48    z: F,
49}
50
51impl<F: Flt> OnePoleLPF<F> {
52    pub fn new() -> Self {
53        Self {
54            israte: f::<F>(1.0) / f(44100.0),
55            a: f::<F>(0.0),
56            b: f::<F>(0.0),
57            freq: f::<F>(1000.0),
58            z: f::<F>(0.0),
59        }
60    }
61
62    pub fn reset(&mut self) {
63        self.z = f(0.0);
64    }
65
66    #[inline]
67    fn recalc(&mut self) {
68        self.b = (f::<F>(-1.0) * F::TAU() * self.freq * self.israte).exp();
69        self.a = f::<F>(1.0) - self.b;
70    }
71
72    #[inline]
73    pub fn set_sample_rate(&mut self, srate: F) {
74        self.israte = f::<F>(1.0) / srate;
75        self.recalc();
76    }
77
78    #[inline]
79    pub fn set_freq(&mut self, freq: F) {
80        if freq != self.freq {
81            self.freq = freq;
82            self.recalc();
83        }
84    }
85
86    #[inline]
87    pub fn process(&mut self, input: F) -> F {
88        self.z = self.a * input + self.z * self.b;
89        self.z
90    }
91}
92
93// Fixed one pole with setable pole and gain.
94// Implementation taken from tubonitaub / alec-deason
95// from https://github.com/alec-deason/virtual_modular/blob/4025f1ef343c2eb9cd74eac07b5350c1e7ec9c09/src/simd_graph.rs#L4292
96// under MIT License
97#[derive(Debug, Copy, Clone, Default)]
98pub struct FixedOnePole {
99    b0: f32,
100    a1: f32,
101    y1: f32,
102    gain: f32,
103}
104
105impl FixedOnePole {
106    pub fn new(pole: f32, gain: f32) -> Self {
107        let b0 = if pole > 0.0 { 1.0 - pole } else { 1.0 + pole };
108
109        Self { b0, a1: -pole, y1: 0.0, gain }
110    }
111
112    pub fn reset(&mut self) {
113        self.y1 = 0.0;
114    }
115
116    pub fn set_gain(&mut self, gain: f32) {
117        self.gain = gain;
118    }
119
120    pub fn process(&mut self, input: f32) -> f32 {
121        let output = self.b0 * self.gain * input - self.a1 * self.y1;
122        self.y1 = output;
123        output
124    }
125}
126
127// one pole hp from valley rack free:
128// https://github.com/ValleyAudio/ValleyRackFree/blob/v1.0/src/Common/DSP/OnePoleFilters.cpp
129#[inline]
130/// Process a very simple one pole 6dB high pass filter.
131/// Useful in various applications.
132///
133/// * `input`  - Input sample
134/// * `freq`   - Frequency between 1.0 and 22000.0Hz
135/// * `israte` - 1.0 / samplerate
136/// * `z`      - The first internal buffer of the filter.
137/// * `y`      - The second internal buffer of the filter.
138///
139///```
140///    use synfx_dsp::*;
141///
142///    let samples  = vec![0.0; 44100];
143///    let mut z    = 0.0;
144///    let mut y    = 0.0;
145///    let mut freq = 1000.0;
146///
147///    for s in samples.iter() {
148///        let s_out =
149///            process_1pole_highpass(*s, freq, 1.0 / 44100.0, &mut z, &mut y);
150///        // ... do something with the result here.
151///    }
152///```
153pub fn process_1pole_highpass(input: f32, freq: f32, israte: f32, z: &mut f32, y: &mut f32) -> f32 {
154    let b = (-std::f32::consts::TAU * freq * israte).exp();
155    let a = (1.0 + b) / 2.0;
156
157    let v = a * input - a * *z + b * *y;
158    *y = v;
159    *z = input;
160    v
161}
162
163#[derive(Debug, Clone, Copy, Default)]
164pub struct OnePoleHPF<F: Flt> {
165    israte: F,
166    a: F,
167    b: F,
168    freq: F,
169    z: F,
170    y: F,
171}
172
173impl<F: Flt> OnePoleHPF<F> {
174    pub fn new() -> Self {
175        Self {
176            israte: f(1.0 / 44100.0),
177            a: f(0.0),
178            b: f(0.0),
179            freq: f(1000.0),
180            z: f(0.0),
181            y: f(0.0),
182        }
183    }
184
185    pub fn reset(&mut self) {
186        self.z = f(0.0);
187        self.y = f(0.0);
188    }
189
190    #[inline]
191    fn recalc(&mut self) {
192        self.b = (f::<F>(-1.0) * F::TAU() * self.freq * self.israte).exp();
193        self.a = (f::<F>(1.0) + self.b) / f(2.0);
194    }
195
196    pub fn set_sample_rate(&mut self, srate: F) {
197        self.israte = f::<F>(1.0) / srate;
198        self.recalc();
199    }
200
201    #[inline]
202    pub fn set_freq(&mut self, freq: F) {
203        if freq != self.freq {
204            self.freq = freq;
205            self.recalc();
206        }
207    }
208
209    #[inline]
210    pub fn process(&mut self, input: F) -> F {
211        let v = self.a * input - self.a * self.z + self.b * self.y;
212
213        self.y = v;
214        self.z = input;
215
216        v
217    }
218}
219
220// one pole from:
221// http://www.willpirkle.com/Downloads/AN-4VirtualAnalogFilters.pdf
222// (page 5)
223#[inline]
224/// Process a very simple one pole 6dB low pass filter in TPT form.
225/// Useful in various applications, from usage in a synthesizer to
226/// damping stuff in a reverb/delay.
227///
228/// * `input`  - Input sample
229/// * `freq`   - Frequency between 1.0 and 22000.0Hz
230/// * `israte` - 1.0 / samplerate
231/// * `z`      - The internal one sample buffer of the filter.
232///
233///```
234///    use synfx_dsp::*;
235///
236///    let samples  = vec![0.0; 44100];
237///    let mut z    = 0.0;
238///    let mut freq = 1000.0;
239///
240///    for s in samples.iter() {
241///        let s_out =
242///            process_1pole_tpt_highpass(*s, freq, 1.0 / 44100.0, &mut z);
243///        // ... do something with the result here.
244///    }
245///```
246pub fn process_1pole_tpt_lowpass(input: f32, freq: f32, israte: f32, z: &mut f32) -> f32 {
247    let g = (std::f32::consts::PI * freq * israte).tan();
248    let a = g / (1.0 + g);
249
250    let v1 = a * (input - *z);
251    let v2 = v1 + *z;
252    *z = v2 + v1;
253
254    // let (m0, m1) = (0.0, 1.0);
255    // (m0 * input + m1 * v2) as f32);
256    v2
257}
258
259// one pole from:
260// http://www.willpirkle.com/Downloads/AN-4VirtualAnalogFilters.pdf
261// (page 5)
262#[inline]
263/// Process a very simple one pole 6dB high pass filter in TPT form.
264/// Useful in various applications.
265///
266/// * `input`  - Input sample
267/// * `freq`   - Frequency between 1.0 and 22000.0Hz
268/// * `israte` - 1.0 / samplerate
269/// * `z`      - The internal one sample buffer of the filter.
270///
271///```
272///    use synfx_dsp::*;
273///
274///    let samples  = vec![0.0; 44100];
275///    let mut z    = 0.0;
276///    let mut freq = 1000.0;
277///
278///    for s in samples.iter() {
279///        let s_out =
280///            process_1pole_tpt_lowpass(*s, freq, 1.0 / 44100.0, &mut z);
281///        // ... do something with the result here.
282///    }
283///```
284pub fn process_1pole_tpt_highpass(input: f32, freq: f32, israte: f32, z: &mut f32) -> f32 {
285    let g = (std::f32::consts::PI * freq * israte).tan();
286    let a1 = g / (1.0 + g);
287
288    let v1 = a1 * (input - *z);
289    let v2 = v1 + *z;
290    *z = v2 + v1;
291
292    input - v2
293}
294
295/// The internal oversampling factor of [process_hal_chamberlin_svf].
296const FILTER_OVERSAMPLE_HAL_CHAMBERLIN: usize = 2;
297// Hal Chamberlin's State Variable (12dB/oct) filter
298// https://www.earlevel.com/main/2003/03/02/the-digital-state-variable-filter/
299// Inspired by SynthV1 by Rui Nuno Capela, under the terms of
300// GPLv2 or any later:
301/// Process a HAL Chamberlin filter with two delays/state variables that is 12dB.
302/// The filter does internal oversampling with very simple decimation to
303/// rise the stability for cutoff frequency up to 16kHz.
304///
305/// * `input` - Input sample.
306/// * `freq` - Frequency in Hz. Please keep it inside 0.0 to 16000.0 Hz!
307/// otherwise the filter becomes unstable.
308/// * `res`  - Resonance from 0.0 to 0.99. Resonance of 1.0 is not recommended,
309/// as the filter will then oscillate itself out of control.
310/// * `israte` - 1.0 divided by the sampling rate (eg. 1.0 / 44100.0).
311/// * `band` - First state variable, containing the band pass result
312/// after processing.
313/// * `low` - Second state variable, containing the low pass result
314/// after processing.
315///
316/// Returned are the results of the high and notch filter.
317///
318///```
319///    use synfx_dsp::*;
320///
321///    let samples  = vec![0.0; 44100];
322///    let mut band = 0.0;
323///    let mut low  = 0.0;
324///    let mut freq = 1000.0;
325///
326///    for s in samples.iter() {
327///        let (high, notch) =
328///            process_hal_chamberlin_svf(
329///                *s, freq, 0.5, 1.0 / 44100.0, &mut band, &mut low);
330///        // ... do something with the result here.
331///    }
332///```
333#[inline]
334pub fn process_hal_chamberlin_svf(
335    input: f32,
336    freq: f32,
337    res: f32,
338    israte: f32,
339    band: &mut f32,
340    low: &mut f32,
341) -> (f32, f32) {
342    let q = 1.0 - res;
343    let cutoff = 2.0 * (std::f32::consts::PI * freq * 0.5 * israte).sin();
344
345    let mut high = 0.0;
346    let mut notch = 0.0;
347
348    for _ in 0..FILTER_OVERSAMPLE_HAL_CHAMBERLIN {
349        *low += cutoff * *band;
350        high = input - *low - q * *band;
351        *band += cutoff * high;
352        notch = high + *low;
353    }
354
355    //d// println!("q={:4.2} cut={:8.3} freq={:8.1} LP={:8.3} HP={:8.3} BP={:8.3} N={:8.3}",
356    //d//     q, cutoff, freq, *low, high, *band, notch);
357
358    (high, notch)
359}
360
361/// This function processes a Simper SVF with 12dB. It's a much newer algorithm
362/// for filtering and provides easy to calculate multiple outputs.
363///
364/// * `input` - Input sample.
365/// * `freq` - Frequency in Hz.
366/// otherwise the filter becomes unstable.
367/// * `res`  - Resonance from 0.0 to 0.99. Resonance of 1.0 is not recommended,
368/// as the filter will then oscillate itself out of control.
369/// * `israte` - 1.0 divided by the sampling rate (eg. 1.0 / 44100.0).
370/// * `band` - First state variable, containing the band pass result
371/// after processing.
372/// * `low` - Second state variable, containing the low pass result
373/// after processing.
374///
375/// This function returns the low pass, band pass and high pass signal.
376/// For a notch or peak filter signal, please consult the following example:
377///
378///```
379///    use synfx_dsp::*;
380///
381///    let samples   = vec![0.0; 44100];
382///    let mut ic1eq = 0.0;
383///    let mut ic2eq = 0.0;
384///    let mut freq  = 1000.0;
385///
386///    for s in samples.iter() {
387///        let (low, band, high) =
388///            process_simper_svf(
389///                *s, freq, 0.5, 1.0 / 44100.0, &mut ic1eq, &mut ic2eq);
390///
391///        // You can easily calculate the notch and peak results too:
392///        let notch = low + high;
393///        let peak  = low - high;
394///        // ... do something with the result here.
395///    }
396///```
397// Simper SVF implemented from
398// https://cytomic.com/files/dsp/SvfLinearTrapezoidalSin.pdf
399// Big thanks go to Andrew Simper @ Cytomic for developing and publishing
400// the paper.
401#[inline]
402pub fn process_simper_svf(
403    input: f32,
404    freq: f32,
405    res: f32,
406    israte: f32,
407    ic1eq: &mut f32,
408    ic2eq: &mut f32,
409) -> (f32, f32, f32) {
410    // XXX: the 1.989 were tuned by hand, so the resonance is more audible.
411    let k = 2f32 - (1.989f32 * res);
412    let w = std::f32::consts::PI * freq * israte;
413
414    let s1 = w.sin();
415    let s2 = (2.0 * w).sin();
416    let nrm = 1.0 / (2.0 + k * s2);
417
418    let g0 = s2 * nrm;
419    let g1 = (-2.0 * s1 * s1 - k * s2) * nrm;
420    let g2 = (2.0 * s1 * s1) * nrm;
421
422    let t0 = input - *ic2eq;
423    let t1 = g0 * t0 + g1 * *ic1eq;
424    let t2 = g2 * t0 + g0 * *ic1eq;
425
426    let v1 = t1 + *ic1eq;
427    let v2 = t2 + *ic2eq;
428
429    *ic1eq += 2.0 * t1;
430    *ic2eq += 2.0 * t2;
431
432    // low   = v2
433    // band  = v1
434    // high  = input - k * v1 - v2
435    // notch = low + high            = input - k * v1
436    // peak  = low - high            = 2 * v2 - input + k * v1
437    // all   = low + high - k * band = input - 2 * k * v1
438
439    (v2, v1, input - k * v1 - v2)
440}
441
442/// This function implements a simple Stilson/Moog low pass filter with 24dB.
443/// It provides only a low pass output.
444///
445/// * `input` - Input sample.
446/// * `freq` - Frequency in Hz.
447/// otherwise the filter becomes unstable.
448/// * `res`  - Resonance from 0.0 to 0.99. Resonance of 1.0 is not recommended,
449/// as the filter will then oscillate itself out of control.
450/// * `israte` - 1.0 divided by the sampling rate (`1.0 / 44100.0`).
451/// * `b0` to `b3` - Internal values used for filtering.
452/// * `delay` - A buffer holding other delayed samples.
453///
454///```
455///    use synfx_dsp::*;
456///
457///    let samples  = vec![0.0; 44100];
458///    let mut b0   = 0.0;
459///    let mut b1   = 0.0;
460///    let mut b2   = 0.0;
461///    let mut b3   = 0.0;
462///    let mut delay = [0.0; 4];
463///    let mut freq = 1000.0;
464///
465///    for s in samples.iter() {
466///        let low =
467///            process_stilson_moog(
468///                *s, freq, 0.5, 1.0 / 44100.0,
469///                &mut b0, &mut b1, &mut b2, &mut b3,
470///                &mut delay);
471///
472///        // ... do something with the result here.
473///    }
474///```
475// Stilson/Moog implementation partly translated from here:
476// https://github.com/ddiakopoulos/MoogLadders/blob/master/src/MusicDSPModel.h
477// without any copyright as found on musicdsp.org
478// (http://www.musicdsp.org/showone.php?id=24).
479//
480// It's also found on MusicDSP and has probably no proper license anyways.
481// See also: https://github.com/ddiakopoulos/MoogLadders
482// and https://github.com/rncbc/synthv1/blob/master/src/synthv1_filter.h#L103
483// and https://github.com/ddiakopoulos/MoogLadders/blob/master/src/MusicDSPModel.h
484#[inline]
485pub fn process_stilson_moog(
486    input: f32,
487    freq: f32,
488    res: f32,
489    israte: f32,
490    b0: &mut f32,
491    b1: &mut f32,
492    b2: &mut f32,
493    b3: &mut f32,
494    delay: &mut [f32; 4],
495) -> f32 {
496    let cutoff = 2.0 * freq * israte;
497
498    let p = cutoff * (1.8 - 0.8 * cutoff);
499    let k = 2.0 * (cutoff * std::f32::consts::PI * 0.5).sin() - 1.0;
500
501    let t1 = (1.0 - p) * 1.386249;
502    let t2 = 12.0 + t1 * t1;
503
504    let res = res * (t2 + 6.0 * t1) / (t2 - 6.0 * t1);
505
506    let x = input - res * *b3;
507
508    // Four cascaded one-pole filters (bilinear transform)
509    *b0 = x * p + delay[0] * p - k * *b0;
510    *b1 = *b0 * p + delay[1] * p - k * *b1;
511    *b2 = *b1 * p + delay[2] * p - k * *b2;
512    *b3 = *b2 * p + delay[3] * p - k * *b3;
513
514    // Clipping band-limited sigmoid
515    *b3 -= (*b3 * *b3 * *b3) * 0.166667;
516
517    delay[0] = x;
518    delay[1] = *b0;
519    delay[2] = *b1;
520    delay[3] = *b2;
521
522    *b3
523}
524
525// translated from Odin 2 Synthesizer Plugin
526// Copyright (C) 2020 TheWaveWarden
527// under GPLv3 or any later
528#[derive(Debug, Clone, Copy)]
529pub struct DCBlockFilter<F: Flt> {
530    xm1: F,
531    ym1: F,
532    r: F,
533}
534
535impl<F: Flt> DCBlockFilter<F> {
536    pub fn new() -> Self {
537        Self { xm1: f(0.0), ym1: f(0.0), r: f(0.995) }
538    }
539
540    pub fn reset(&mut self) {
541        self.xm1 = f(0.0);
542        self.ym1 = f(0.0);
543    }
544
545    pub fn set_sample_rate(&mut self, srate: F) {
546        self.r = f(0.995);
547        if srate > f(90000.0) {
548            self.r = f(0.9965);
549        } else if srate > f(120000.0) {
550            self.r = f(0.997);
551        }
552    }
553
554    pub fn next(&mut self, input: F) -> F {
555        let y = input - self.xm1 + self.r * self.ym1;
556        self.xm1 = input;
557        self.ym1 = y;
558        y as F
559    }
560}
561
562// Taken from va-filter by Fredemus aka Frederik Halkjær aka RocketPhysician
563// https://github.com/Fredemus/va-filter
564// Under License GPL-3.0-or-later
565/// Basic 4 channel SIMD DC-filter from Understanding Digital Signal Processing by Richard Lyons.
566#[derive(Debug, Clone)]
567pub struct DCFilterX4 {
568    y0: f32x4,
569    x0: f32x4,
570    /// higher alpha moves the cutoff lower, but also makes it settle slower.
571    /// See if it's reasonable to make it higher.
572    alpha: f32x4,
573}
574
575impl Default for DCFilterX4 {
576    fn default() -> Self {
577        Self {
578            y0: f32x4::splat(0.),
579            x0: f32x4::splat(0.),
580            // alpha: f32x4::splat(0.975),
581            alpha: f32x4::splat(0.9999),
582        }
583    }
584}
585
586impl DCFilterX4 {
587    pub fn process(&mut self, input: f32x4) -> f32x4 {
588        let y_new = input - self.x0 + self.alpha * self.y0;
589        self.x0 = input;
590        self.y0 = y_new;
591
592        y_new
593    }
594
595    pub fn reset(&mut self) {
596        self.y0 = f32x4::splat(0.);
597        self.x0 = f32x4::splat(0.);
598        self.alpha = f32x4::splat(0.9999);
599    }
600}