synfx_dsp/
lib.rs

1// Copyright (c) 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/*! synfx-dsp DSP real time audio synthesis, effect algorithms and utilities for Rust
6
7Most of the algorithms and implementations in this library have been
8implemented for [HexoDSP](https://github.com/WeirdConstructor/HexoDSP) and used
9in [HexoSynth](https://github.com/WeirdConstructor/HexoSynth). I factored them out, because
10they seem useful in other contexts too, for instance the [synfx-dsp-jit](https://github.com/WeirdConstructor/synfx-dsp-jit)
11crate.
12
13I collected most of the algorithms in this crate from various GPLv3 compatible
14sources. They also were partially translated from multiple different C++ projects.
15I tried to document the source and source license diligently in the comments of this crate.
16I apologize if any attribution is missing and would welcome patches or reports.
17
18Feel free to use these algorithms and utilities. Help, patches and additions are appreciated
19if they comply with the GPL-3.0-or-later license and don't break the test suite in HexoDSP.
20
21**Attention:** HexoDSP comes with a large test suite that also covers these algorithms. And that is the one
22that also has to pass if these algorithms are touched. The flip side is, that these implementations
23are actually covered by a test suite.
24
25**Requires Nightly as of 2022-10-02 due to std::simd!**
26
27Copyright, Licenses, Attribution, Contributions
28===============================================
29
30Here is a list of sources parts of this library copied or translated code from:
31
32- [crate::quicker_tanh64] / [crate::quicker_tanh]
33    ```text
34    quickerTanh / quickerTanh64 credits to mopo synthesis library:
35    Under GPLv3 or any later.
36    Little IO <littleioaudio@gmail.com>
37    Matt Tytel <matthewtytel@gmail.com>
38    ```
39- [crate::quick_tanh64] / [crate::quick_tanh]
40    ```text
41    quickTanh / quickTanh64 credits to mopo synthesis library:
42    Under GPLv3 or any later.
43    Little IO <littleioaudio@gmail.com>
44    Matt Tytel <matthewtytel@gmail.com>
45    ```
46- [crate::tanh_approx_drive]
47    ```text
48    Taken from ValleyAudio
49    Copyright Dale Johnson
50    https://github.dev/ValleyAudio/ValleyRackFree/tree/v2.0
51    Under GPLv3 license
52    ```
53- [crate::AtomicFloat]
54    ```text
55    Implementation from vst-rs
56    https://github.com/RustAudio/vst-rs/blob/master/src/util/atomic_float.rs
57    Under MIT License
58    Copyright (c) 2015 Marko Mijalkovic
59    ```
60- [crate::Biquad]
61    ```text
62    The implementation of this Biquad Filter has been adapted from
63    SamiPerttu, Copyright (c) 2020, under the MIT License.
64    See also: https://github.com/SamiPerttu/fundsp/blob/master/src/filter.rs
65    ```
66- [crate::DattorroReverb]
67    ```text
68    This file contains a reverb implementation that is based
69    on Jon Dattorro's 1997 reverb algorithm. It's also largely
70    based on the C++ implementation from ValleyAudio / ValleyRackFree
71
72    ValleyRackFree Copyright (C) 2020, Valley Audio Soft, Dale Johnson
73    Adapted under the GPL-3.0-or-later License.
74
75    See also: https://github.com/ValleyAudio/ValleyRackFree/blob/v1.0/src/Plateau/Dattorro.cpp
76         and: https://github.com/ValleyAudio/ValleyRackFree/blob/v1.0/src/Plateau/Dattorro.hpp
77
78    And: https://ccrma.stanford.edu/~dattorro/music.html
79    And: https://ccrma.stanford.edu/~dattorro/EffectDesignPart1.pdf
80    ```
81- [crate::process_1pole_lowpass] / [crate::process_1pole_highpass]
82    ```text
83    one pole lp from valley rack free:
84    https://github.com/ValleyAudio/ValleyRackFree/blob/v1.0/src/Common/DSP/OnePoleFilters.cpp
85    ```
86- [crate::process_1pole_tpt_lowpass] / [crate::process_1pole_tpt_highpass]
87    ```text
88    one pole from:
89    http://www.willpirkle.com/Downloads/AN-4VirtualAnalogFilters.pdf
90    (page 5)
91    ```
92- [crate::FixedOnePole]
93    ```text
94    Fixed one pole with setable pole and gain.
95    Implementation taken from tubonitaub / alec-deason
96    from https://github.com/alec-deason/virtual_modular/blob/4025f1ef343c2eb9cd74eac07b5350c1e7ec9c09/src/simd_graph.rs#L4292
97    under MIT License
98    ```
99- [crate::process_hal_chamberlin_svf]
100    ```text
101    Hal Chamberlin's State Variable (12dB/oct) filter
102    https://www.earlevel.com/main/2003/03/02/the-digital-state-variable-filter/
103    Inspired by SynthV1 by Rui Nuno Capela, under the terms of
104    GPLv2 or any later:
105    ```
106- [crate::process_simper_svf]
107    ```text
108    Simper SVF implemented from
109    https://cytomic.com/files/dsp/SvfLinearTrapezoidalSin.pdf
110    Big thanks go to Andrew Simper @ Cytomic for developing and publishing
111    the paper.
112    ```
113- [crate::process_stilson_moog]
114    ```text
115    Stilson/Moog implementation partly translated from here:
116    https://github.com/ddiakopoulos/MoogLadders/blob/master/src/MusicDSPModel.h
117    without any copyright as found on musicdsp.org
118    (http://www.musicdsp.org/showone.php?id=24).
119
120    It's also found on MusicDSP and has probably no proper license anyways.
121    See also: https://github.com/ddiakopoulos/MoogLadders
122    and https://github.com/rncbc/synthv1/blob/master/src/synthv1_filter.h#L103
123    and https://github.com/ddiakopoulos/MoogLadders/blob/master/src/MusicDSPModel.h
124    ```
125- [crate::DCBlockFilter]
126    ```text
127    translated from Odin 2 Synthesizer Plugin
128    Copyright (C) 2020 TheWaveWarden
129    under GPLv3 or any later
130    ```
131- [crate::cubic_interpolate]
132    ```text
133    Hermite interpolation, take from
134    https://github.com/eric-wood/delay/blob/main/src/delay.rs#L52
135
136    Thanks go to Eric Wood!
137
138    For the interpolation code:
139    MIT License, Copyright (c) 2021 Eric Wood
140    ```
141- [crate::TriSawLFO]
142    ```text
143    Adapted from https://github.com/ValleyAudio/ValleyRackFree/blob/v1.0/src/Common/DSP/LFO.hpp
144
145    ValleyRackFree Copyright (C) 2020, Valley Audio Soft, Dale Johnson
146    Adapted under the GPL-3.0-or-later License.
147    ```
148- [crate::PolyBlepOscillator]
149    ```text
150    PolyBLEP by Tale
151    (slightly modified)
152    http://www.kvraudio.com/forum/viewtopic.php?t=375517
153    from http://www.martin-finke.de/blog/articles/audio-plugins-018-polyblep-oscillator/
154    ```
155- [crate::VPSOscillator]
156    ```text
157    This oscillator is based on the work "VECTOR PHASESHAPING SYNTHESIS"
158    by: Jari Kleimola*, Victor Lazzarini†, Joseph Timoney†, Vesa Välimäki*
159    *Aalto University School of Electrical Engineering Espoo, Finland;
160    †National University of Ireland, Maynooth Ireland
161
162    See also this PDF: http://recherche.ircam.fr/pub/dafx11/Papers/55_e.pdf
163    ```
164- [crate::Oversampling]
165    ```text
166    Loosely adapted from https://github.com/VCVRack/Befaco/blob/v1/src/ChowDSP.hpp
167    Copyright (c) 2019-2020 Andrew Belt and Befaco contributors
168    Under GPLv-3.0-or-later
169
170    Which was originally taken from https://github.com/jatinchowdhury18/ChowDSP-VCV/blob/master/src/shared/AAFilter.hpp
171    Copyright (c) 2020 jatinchowdhury18
172    ```
173- [crate::next_xoroshiro128]
174    ```text
175    Taken from xoroshiro128 crate under MIT License
176    Implemented by Matthew Scharley (Copyright 2016)
177    https://github.com/mscharley/rust-xoroshiro128
178    ```
179- [crate::u64_to_open01]
180    ```text
181    Taken from rand::distributions
182    Licensed under the Apache License, Version 2.0
183    Copyright 2018 Developers of the Rand project.
184    ```
185- [crate::SplitMix64]
186    ```text
187    Copyright 2018 Developers of the Rand project.
188
189    Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
190    https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
191    <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
192    option. This file may not be copied, modified, or distributed
193    except according to those terms.
194    - splitmix64 (http://xoroshiro.di.unimi.it/splitmix64.c)
195    ```
196- [crate::f_distort] / [crate::f_fold_distort]
197    ```text
198    Ported from LMMS under GPLv2
199    * DspEffectLibrary.h - library with template-based inline-effects
200    * Copyright (c) 2006-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
201
202    Original source seems to be musicdsp.org, Author: Bram de Jong
203    see also: https://www.musicdsp.org/en/latest/Effects/41-waveshaper.html
204    ```
205- [crate::PolyIIRHalfbandFilter]
206    ```text
207    Taken from va-filter by Fredemus aka Frederik Halkjær aka RocketPhysician
208    https://github.com/Fredemus/va-filter
209    Under License GPL-3.0-or-later
210
211    originally translated from the freely available source code
212    at https://www.musicdsp.org/en/latest/Filters/39-polyphase-filters.html
213    ```
214- [crate::DCFilterX4]
215    ```text
216    Basic 4 channel SIMD DC-filter from Understanding Digital Signal Processing by Richard Lyons.
217
218    Taken from va-filter by Fredemus aka Frederik Halkjær aka RocketPhysician
219    https://github.com/Fredemus/va-filter
220    Under License GPL-3.0-or-later
221    ```
222- [crate::fh_va::LadderFilter] / [crate::fh_va::Svf] / [crate::fh_va::SallenKey]
223    ```text
224    Taken from va-filter by Fredemus aka Frederik Halkjær aka RocketPhysician
225    https://github.com/Fredemus/va-filter
226    Under License GPL-3.0-or-later
227    ```
228*/
229
230#![feature(portable_simd)]
231
232mod approx;
233mod atomic;
234mod biquad;
235mod dattorro;
236mod delay;
237mod env;
238pub mod fh_va;
239mod filters;
240mod interpolation;
241mod low_freq;
242mod oscillators;
243mod oversampling;
244mod rand;
245mod test;
246mod trig_clock;
247mod waveshapers;
248
249pub use approx::*;
250pub use atomic::*;
251pub use biquad::{Biquad, BiquadCoefs};
252pub use dattorro::{DattorroReverb, DattorroReverbParams};
253pub use delay::*;
254pub use env::*;
255pub use filters::*;
256pub use interpolation::*;
257pub use low_freq::*;
258pub use oscillators::*;
259pub use oversampling::Oversampling;
260pub use oversampling::PolyIIRHalfbandFilter;
261pub use rand::*;
262#[allow(unused)]
263pub use test::*;
264pub use trig_clock::*;
265pub use waveshapers::*;
266
267use num_traits::{cast::FromPrimitive, cast::ToPrimitive, Float, FloatConst};
268
269macro_rules! trait_alias {
270    ($name:ident = $base1:ident + $($base2:ident +)+) => {
271        pub trait $name: $base1 $(+ $base2)+ { }
272        impl<T: $base1 $(+ $base2)+> $name for T { }
273    };
274}
275
276trait_alias!(Flt = Float + FloatConst + ToPrimitive + FromPrimitive +);
277
278//macro_rules! fc {
279//    ($F: ident, $e: expr) => {
280//        F::from_f64($e).unwrap()
281//    };
282//}
283
284#[allow(dead_code)]
285#[inline]
286fn f<F: Flt>(x: f64) -> F {
287    F::from_f64(x).unwrap()
288}
289
290#[allow(dead_code)]
291#[inline]
292fn fclamp<F: Flt>(x: F, mi: F, mx: F) -> F {
293    x.max(mi).min(mx)
294}
295
296#[allow(dead_code)]
297#[inline]
298fn fclampc<F: Flt>(x: F, mi: f64, mx: f64) -> F {
299    x.max(f(mi)).min(f(mx))
300}
301
302/// Converts a midi note (0 to 128) to a frequency
303///
304///```
305/// use synfx_dsp::*;
306///
307/// assert_eq!(note_to_freq(69.0) as i32, 440);
308/// assert_eq!(note_to_freq(69.0 + 12.0) as i32, 880);
309/// assert_eq!(note_to_freq(69.0 - 12.0) as i32, 220);
310///```
311pub fn note_to_freq(note: f32) -> f32 {
312    440.0 * (2.0_f32).powf((note - 69.0) / 12.0)
313}
314
315/// Converts gain in decibels to a factor/coeffient
316///
317/// ```
318/// use synfx_dsp::gain_db2coef;
319///
320/// assert!((gain_db2coef(-6.0) - 0.501187).abs() < 0.00001);
321/// assert!((gain_db2coef(-3.0) - 0.707945).abs() < 0.00001);
322/// assert!((gain_db2coef(0.0) - 1.0).abs() < 0.00001);
323/// assert!((gain_db2coef(6.0) - 1.99526).abs() < 0.00001);
324/// ```
325#[inline]
326pub fn gain_db2coef(gain_db: f32) -> f32 {
327    if gain_db < -89.9 {
328        0.0
329    } else {
330        10.0_f32.powf(gain_db * 0.05)
331    }
332}
333
334/// Converts a coefficient/factor to decibels
335///
336///```
337/// use synfx_dsp::coef2gain_db;
338///
339/// assert!((coef2gain_db(0.501187) - -6.0).abs() < 0.0001);
340/// assert!((coef2gain_db(0.707945) - -3.0).abs() < 0.0001);
341/// assert!(coef2gain_db(1.0).abs() < 0.00001);
342/// assert!((coef2gain_db(1.99526) - 6.0).abs() < 0.0001);
343///```
344#[inline]
345pub fn coef2gain_db(coef: f32) -> f32 {
346    if coef < 0.0000317 {
347        -90.0
348    } else {
349        20.0 * coef.log10()
350    }
351}
352
353/// A `pow` like shape function for exponential envelopes.
354/// It's a bit faster than calling the `pow` function of Rust.
355///
356/// * `x` the input value
357/// * `v' the shape value.
358/// Which is linear at `0.5`, the forth root of `x` at `1.0` and x to the power
359/// of 4 at `0.0`. You can vary `v` as you like.
360///
361///```
362/// use synfx_dsp::*;
363///
364/// assert!(((sqrt4_to_pow4(0.25, 0.0) - 0.25_f32 * 0.25 * 0.25 * 0.25)
365///          .abs() - 1.0)
366///         < 0.0001);
367///
368/// assert!(((sqrt4_to_pow4(0.25, 1.0) - (0.25_f32).sqrt().sqrt())
369///          .abs() - 1.0)
370///         < 0.0001);
371///
372/// assert!(((sqrt4_to_pow4(0.25, 0.5) - 0.25_f32).abs() - 1.0) < 0.0001);
373///```
374#[inline]
375pub fn sqrt4_to_pow4(x: f32, v: f32) -> f32 {
376    if v > 0.75 {
377        let xsq1 = x.sqrt();
378        let xsq = xsq1.sqrt();
379        let v = (v - 0.75) * 4.0;
380        xsq1 * (1.0 - v) + xsq * v
381    } else if v > 0.5 {
382        let xsq = x.sqrt();
383        let v = (v - 0.5) * 4.0;
384        x * (1.0 - v) + xsq * v
385    } else if v > 0.25 {
386        let xx = x * x;
387        let v = (v - 0.25) * 4.0;
388        x * v + xx * (1.0 - v)
389    } else {
390        let xx = x * x;
391        let xxxx = xx * xx;
392        let v = v * 4.0;
393        xx * v + xxxx * (1.0 - v)
394    }
395}
396
397/// Returns the name of the distortion selected by the `dist_type` parameter of the [apply_distortion]
398/// function.
399#[macro_export]
400macro_rules! fa_distort {
401    ($formatter: expr, $v: expr, $denorm_v: expr) => {{
402        let s = match ($v.round() as usize) {
403            0 => "Off",
404            1 => "TanH",
405            2 => "B.D.Jong",
406            3 => "Fold",
407            _ => "?",
408        };
409        write!($formatter, "{}", s)
410    }};
411}
412
413#[inline]
414pub fn apply_distortion(s: f32, damt: f32, dist_type: u8) -> f32 {
415    match dist_type {
416        1 => (damt.clamp(0.01, 1.0) * 100.0 * s).tanh(),
417        2 => f_distort(1.0, damt * damt * damt * 1000.0, s),
418        3 => {
419            let damt = damt.clamp(0.0, 0.99);
420            let damt = 1.0 - damt * damt;
421            f_fold_distort(1.0, damt, s) * (1.0 / damt)
422        }
423        _ => s,
424    }
425}