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}