//##################################### filters.lib ######################################## // Filters library. Its official prefix is `fi`. // // This library provides a comprehensive collection of linear and nonlinear // filters used in audio and signal processing. It includes low-pass, high-pass, // band-pass, allpass, shelving, equalizer, and crossover filters, as well as advanced // analog and digital filter design sections for both educational and production use. // // The Filters library is organized into 25 sections: // // * [Basic Filters](#basic-filters) // * [Comb Filters](#comb-filters) // * [Direct-Form Digital Filter Sections](#direct-form-digital-filter-sections) // * [Direct-Form Second-Order Biquad Sections](#direct-form-second-order-biquad-sections) // * [Ladder/Lattice Digital Filters](#ladderlattice-digital-filters) // * [Useful Special Cases](#useful-special-cases) // * [Ladder/Lattice Allpass Filters](#ladderlattice-allpass-filters) // * [Clipping-Prevention Allpass Filters](#clipping-prevention-allpass-filters) // * [Digital Filter Sections Specified as Analog Filter Sections](#digital-filter-sections-specified-as-analog-filter-sections) // * [Simple Resonator Filters](#simple-resonator-filters) // * [Butterworth Lowpass/Highpass Filters](#butterworth-lowpasshighpass-filters) // * [Special Filter-Bank Delay-Equalizing Allpass Filters](#special-filter-bank-delay-equalizing-allpass-filters) // * [Elliptic (Cauer) Lowpass Filters](#elliptic-cauer-lowpass-filters) // * [Elliptic Highpass Filters](#elliptic-highpass-filters) // * [Butterworth Bandpass/Bandstop Filters](#butterworth-bandpassbandstop-filters) // * [Elliptic Bandpass Filters](#elliptic-bandpass-filters) // * [Parametric Equalizers (Shelf, Peaking)](#parametric-equalizers-shelf-peaking) // * [Mth-Octave Filter-Banks](#mth-octave-filter-banks) // * [Arbitrary-Crossover Filter-Banks and Spectrum Analyzers](#arbitrary-crossover-filter-banks-and-spectrum-analyzers) // * [State Variable Filters (SVF)](#state-variable-filters) // * [Linkwitz-Riley 4th-order 2-way, 3-way, and 4-way crossovers](#linkwitz-riley-4th-order-2-way-3-way-and-4-way-crossovers) // * [Standardized Filters](#standardized-filters) // * [Averaging Functions](#averaging-functions) // * [Kalman Filters](#kalman-filters) // * [Adaptive Filters](#adaptive-filters) // // #### References // // * //######################################################################################## // NOTE ABOUT LICENSES: // Each function in this library has its own license. Licenses are declared // before each function. Corresponding license terms can be found at the // bottom of this file or in the Faust libraries documentation. ma = library("maths.lib"); ba = library("basics.lib"); ro = library("routes.lib"); de = library("delays.lib"); an = library("analyzers.lib"); ef = library("misceffects.lib"); si = library("signals.lib"); fi = library("filters.lib"); // for compatible copy/paste out of this file la = library("linearalgebra.lib"); declare name "Faust Filters Library"; declare version "1.13.1"; //===============================Basic Filters============================================ //======================================================================================== //----------------------`(fi.)zero`-------------------------- // One zero filter. Difference equation: \(y(n) = x(n) - zx(n-1)\). // // #### Usage // // ``` // _ : zero(z) : _ // ``` // // Where: // // * `z`: location of zero along real axis in z-plane // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // zero_test = src : fi.zero(0.5); // zero_slider_test = no.noise : fi.zero(hslider("z", 0.5, -1, 1, 0.01)); // zero_modulated_test = no.noise : fi.zero(-0.9 + 1.8*tri) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // zero_jump_test = no.noise : fi.zero(-0.9 + 1.8*sq) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### References // //---------------------------------------------------------- declare zero author "Julius O. Smith III"; declare zero copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare zero license "LicenseRef-STK-4.3"; zero(z) = _ <: _,mem : _,*(z) : -; //------------------------`(fi.)pole`--------------------------- // One pole filter. Could also be called a "leaky integrator". // Difference equation: \(y(n) = x(n) + py(n-1)\). // // #### Usage // // ``` // _ : pole(p) : _ // ``` // // Where: // // * `p`: pole location = feedback coefficient // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // pole_test = src : fi.pole(0.9); // pole_slider_test = no.noise : fi.pole(hslider("p", 0.9, 0, 0.999, 0.001)); // pole_modulated_test = no.noise : fi.pole(0.5 + 0.499*tri) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // pole_jump_test = no.noise : fi.pole(0.5 + 0.499*sq) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### References // //------------------------------------------------------------ declare pole author "Julius O. Smith III"; declare pole copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare pole license "LicenseRef-STK-4.3"; pole(p) = + ~ *(p); //----------------------`(fi.)integrator`-------------------------- // Same as `pole(1)` [implemented separately for block-diagram clarity]. // // #### Usage // // ``` // _ : integrator : _ // ``` // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // src = os.tosc(440); // integrator_test = src : fi.integrator; // ``` //------------------------------------------------------------ declare integrator author "Julius O. Smith III"; declare integrator copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare integrator license "LicenseRef-STK-4.3"; integrator = + ~ _; //-------------------`(fi.)dcblockerat`----------------------- // DC blocker with configurable "break frequency". // The amplitude response is substantially flat above `fb`, // and sloped at about +6 dB/octave below `fb`. // Derived from the analog transfer function: // $$H(s) = \frac{s}{(s + 2 \pi f_b)}$$ // (which can be seen as a 1st-order Butterworth highpass filter) // by the low-frequency-matching bilinear transform method // (i.e., using the typical frequency-scaling constant `2*SR`). // // #### Usage // // ``` // _ : dcblockerat(fb) : _ // ``` // // Where: // // * `fb`: "break frequency" in Hz, i.e., -3 dB gain frequency (see 2nd reference below) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // dcblockerat_test = src : fi.dcblockerat(30); // dcblockerat_slider_test = no.noise : fi.dcblockerat(hslider("fb", 30, 1, 500, 1)); // dcblockerat_modulated_test = no.noise : fi.dcblockerat(5*pow(40, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // dcblockerat_jump_test = no.noise : fi.dcblockerat(5*pow(40, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### References // // * // * // //------------------------------------------------------------ declare dcblockerat author "Julius O. Smith III"; declare dcblockerat copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare dcblockerat license "LicenseRef-STK-4.3"; dcblockerat(fb) = *(b0) : zero(1) : pole(p) with { wn = ma.PI*fb/ma.SR; b0 = 1.0 / (1 + wn); p = (1 - wn) * b0; }; //----------------------`(fi.)dcblocker`-------------------------- // DC blocker. Default dc blocker has -3dB point near 35 Hz (at 44.1 kHz) // and high-frequency gain near 1.0025 (due to no scaling). // `dcblocker` is a standard Faust function. // // #### Usage // // ``` // _ : dcblocker : _ // ``` // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // src = os.tosc(440); // dcblocker_test = src : fi.dcblocker; // ``` //------------------------------------------------------------ declare dcblocker author "Julius O. Smith III"; declare dcblocker copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare dcblocker license "LicenseRef-STK-4.3"; dcblocker = zero(1) : pole(0.995); //----------------------------`(fi.)lptN`-------------------------------------- // One-pole lowpass filter with arbitrary dis/charging factors set in dB and // times set in seconds. // // #### Usage // // ``` // _ : lptN(N, tN) : _ // ``` // // Where: // // * `N`: is the attenuation factor in dB // * `tN`: is the filter period in seconds, that is, the time for the // impulse response to decay by `N` dB // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // lptN_test = src : fi.lptN(60, 0.1); // lptN_slider_test = no.noise : fi.lptN(60, hslider("tN", 0.1, 0.001, 1, 0.001)); // lptN_modulated_test = no.noise : fi.lptN(60, 0.001*pow(1000, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // lptN_jump_test = no.noise : fi.lptN(60, 0.001*pow(1000, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### References // // //---------------------------------------------------------- declare lptN author "Dario Sanfilippo"; declare lptN copyright "Copyright (C) 2020 Dario Sanfilippo "; declare lptN license "MIT"; lptN(N, tN, x) = x : si.smooth(ba.tau2pole(tN / log(10.0^(float(N)/20.0)))); // Special cases of lptN //------------------------`(fi.)lptau`-------------------------- // One-pole lowpass with a tau time constant (1/e attenuation after `tN` seconds). // // #### Usage // // ``` // _ : lptau(tN) : _ // ``` // // Where: // // * `tN`: tau time constant in seconds (1/e attenuation after `tN` seconds) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // lptau_test = src : fi.lptau(0.1); // lptau_slider_test = no.noise : fi.lptau(hslider("tN", 0.1, 0.001, 1, 0.001)); // lptau_modulated_test = no.noise : fi.lptau(0.001*pow(1000, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // lptau_jump_test = no.noise : fi.lptau(0.001*pow(1000, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //------------------------------------------------------------ declare lptau author "Dario Sanfilippo"; declare lptau copyright "Copyright (C) 2020 Dario Sanfilippo "; declare lptau license "MIT"; lptau(tN, x) = lptN(8.6858896381, tN, x); // Tau time constant, i.e., 1/e atten. after tN secs //------------------------`(fi.)lpt60`-------------------------- // One-pole lowpass with a T60 time constant (60 dB attenuation after `tN` seconds). // // #### Usage // // ``` // _ : lpt60(tN) : _ // ``` // // Where: // // * `tN`: T60 time constant in seconds (60 dB attenuation after `tN` seconds) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // lpt60_test = src : fi.lpt60(0.3); // lpt60_slider_test = no.noise : fi.lpt60(hslider("tN", 0.3, 0.001, 1, 0.001)); // lpt60_modulated_test = no.noise : fi.lpt60(0.001*pow(1000, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // lpt60_jump_test = no.noise : fi.lpt60(0.001*pow(1000, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //------------------------------------------------------------ declare lpt60 author "Dario Sanfilippo"; declare lpt60 copyright "Copyright (C) 2020 Dario Sanfilippo "; declare lpt60 license "MIT"; lpt60(tN, x) = lptN(60, tN, x); // T60 constant, i.e., 1/1000 atten. after tN secs //------------------------`(fi.)lpt19`-------------------------- // One-pole lowpass with a T19 time constant (approx. 19 dB attenuation after `tN` seconds). // // #### Usage // // ``` // _ : lpt19(tN) : _ // ``` // // Where: // // * `tN`: T19 time constant in seconds (approximately 19 dB attenuation after `tN` seconds) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // lpt19_test = src : fi.lpt19(0.2); // lpt19_slider_test = no.noise : fi.lpt19(hslider("tN", 0.2, 0.001, 1, 0.001)); // lpt19_modulated_test = no.noise : fi.lpt19(0.001*pow(1000, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // lpt19_jump_test = no.noise : fi.lpt19(0.001*pow(1000, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //------------------------------------------------------------ declare lpt19 author "Dario Sanfilippo"; declare lpt19 copyright "Copyright (C) 2020 Dario Sanfilippo "; declare lpt19 license "MIT"; lpt19(tN, x) = lptN(19, tN, x); // T19 constant, i.e., 1/e^2.2 atten. after tN secs //=======================================Comb Filters===================================== //======================================================================================== //------`(fi.)ff_comb`-------- // Feed-Forward Comb Filter. Note that `ff_comb` requires integer delays // (uses `delay` internally). // `ff_comb` is a standard Faust function. It computes // // ``` // y[n] = b0 x[n] + bM x[n - intdel] // ``` // // #### Usage // // ``` // _ : ff_comb(maxdel,intdel,b0,bM) : _ // ``` // // Where: // // * `maxdel`: maximum delay (a power of 2) // * `intdel`: current (integer) comb-filter delay between 0 and maxdel // (a non-integer value is truncated) // * `b0`: gain applied to the direct (undelayed) input // * `bM`: gain applied to delay-line output and then summed with the direct path // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // ff_comb_test = src : fi.ff_comb(2048, 64, 1, 0.7); // ff_comb_slider_test = no.noise : fi.ff_comb(2048, hslider("delay", 64, 1, 2047, 1), hslider("b0", 1, -1, 1, 0.01), hslider("bM", 0.7, -1, 1, 0.01)); // ff_comb_modulated_test = no.noise : fi.ff_comb(2048, d, 1, 0.7) with { P = int(ma.SR/10); m = 112*(P - abs(2*ba.period(P) - P)); d = 16 + int((m - m % P)/P + 0.5); }; // ``` // // #### References // // //------------------------------------------------------------ declare ff_comb author "Julius O. Smith III"; declare ff_comb copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare ff_comb license "LicenseRef-STK-4.3"; ff_comb(maxdel,M,b0,bM) = _ <: *(b0), bM * de.delay(maxdel,M) : +; //------`(fi.)ff_fcomb`-------- // Feed-Forward Comb Filter. Note that `ff_fcomb` takes floating-point delays // (uses `fdelay` internally). // `ff_fcomb` is a standard Faust function. It computes // `y[n] = b0 x[n] + bM x[n - del]`, where `x[n - del]` is linearly interpolated. // // #### Usage // // ``` // _ : ff_fcomb(maxdel,del,b0,bM) : _ // ``` // // Where: // // * `maxdel`: maximum delay (a power of 2) // * `del`: current (float) comb-filter delay between 0 and maxdel // * `b0`: gain applied to the direct (undelayed) input // * `bM`: gain applied to delay-line output and then summed with the direct path // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // ff_fcomb_test = src : fi.ff_fcomb(2048, 64.5, 1, 0.7); // ff_fcomb_slider_test = no.noise : fi.ff_fcomb(2048, hslider("delay", 64.5, 1, 2047, 0.01), hslider("b0", 1, -1, 1, 0.01), hslider("bM", 0.7, -1, 1, 0.01)); // ff_fcomb_modulated_test = no.noise : fi.ff_fcomb(2048, 16 + 112*tri, 1, 0.7) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // ff_fcomb_jump_test = no.noise : fi.ff_fcomb(2048, 16 + 112*sq, 1, 0.7) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### References // // //------------------------------------------------------------ declare ff_fcomb author "Julius O. Smith III"; declare ff_fcomb copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare ff_fcomb license "LicenseRef-STK-4.3"; ff_fcomb(maxdel,del,b0,bM) = _ <: *(b0), bM * de.fdelay(maxdel,del) : +; //-----------`(fi.)ffcombfilter`------------------- // Typical special case of `ff_comb()` where: `b0 = 1`. // // #### Usage // // ``` // _ : ffcombfilter(maxdel,del,g) : _ // ``` // // Where: // // * `maxdel`: maximum delay (a power of 2) // * `del`: current integer comb-filter delay between 0 and maxdel // (a non-integer value is truncated, as in `ff_comb`) // * `g`: gain applied to the delayed tap // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // ffcombfilter_test = src : fi.ffcombfilter(2048, 64, 0.7); // ffcombfilter_slider_test = no.noise : fi.ffcombfilter(2048, hslider("delay", 64, 1, 2047, 1), hslider("g", 0.7, -1, 1, 0.01)); // ffcombfilter_modulated_test = no.noise : fi.ffcombfilter(2048, d, 0.7) with { P = int(ma.SR/10); m = 112*(P - abs(2*ba.period(P) - P)); d = 16 + int((m - m % P)/P + 0.5); }; // ``` //------------------------------------------------------------ declare ffcombfilter author "Julius O. Smith III"; declare ffcombfilter copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare ffcombfilter license "LicenseRef-STK-4.3"; ffcombfilter(maxdel,del,g) = ff_comb(maxdel,del,1,g); //---------------------`(fi.)fb_comb_common`--------------------- // A generic feedback comb filter. // // #### Usage // // ``` // _ : fb_comb_common(dop,N,b0,aN) : _ // ``` // // Where // // * `dop`: delay operator, e.g. `@` or `de.fdelay4a(2048)` // * `N`: current delay // * `b0`: gain applied to input // * `aN`: gain applied to delay-line output // // #### Example test program // // ``` // process = fb_comb_common(@,N,b0,aN); // ``` // implements the following difference equation: // ``` // y[n] = b0 x[n] + aN y[n - N] // ``` // // See more examples in `filters.lib` below. // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // fb_comb_common_test = src : fi.fb_comb_common(@, 64, 0.8, 0.6); // fb_comb_common_slider_test = no.noise : fi.fb_comb_common(@, hslider("delay", 64, 1, 2047, 1), hslider("b0", 0.8, -1, 1, 0.01), hslider("aN", 0.6, -1, 1, 0.01)); // fb_comb_common_modulated_test = no.noise : fi.fb_comb_common(@, d, 0.8, 0.6) with { P = int(ma.SR/10); m = 112*(P - abs(2*ba.period(P) - P)); d = 16 + int((m - m % P)/P + 0.5) : max(16) : min(128); }; // ``` // -------------------------------------------------------- declare fb_comb_common author "Oleg Nesterov"; fb_comb_common(dop,N,b0,aN) = + ~ aN * dop(N-1) : *(b0); //-----------------------`(fi.)fb_comb`----------------------- // Feed-Back Comb Filter (integer delay). It computes // // ``` // y[n] = b0 x[n-1] - aN y[n-del] // ``` // // The output is delayed by one sample (the `mem` at the end of the definition): // the transfer function is b0 z^(-1)/(1 + aN z^(-del)), not b0/(1 + aN z^(-del)). // // #### Usage // // ``` // _ : fb_comb(maxdel,del,b0,aN) : _ // ``` // // Where: // // * `maxdel`: maximum delay (a power of 2) // * `del`: current integer comb-filter delay (the feedback loop length), between 1 and maxdel // (a non-integer value is truncated) // * `b0`: input gain // * `aN`: minus the gain applied to delay-line output before summing with the input // and feeding to the delay line // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // fb_comb_test = src : fi.fb_comb(2048, 64, 0.7, 0.6); // fb_comb_slider_test = no.noise : fi.fb_comb(2048, hslider("delay", 64, 1, 2047, 1), hslider("b0", 0.7, -1, 1, 0.01), hslider("aN", 0.6, -1, 1, 0.01)); // fb_comb_modulated_test = no.noise : fi.fb_comb(2048, d, 0.7, 0.6) with { P = int(ma.SR/10); m = 112*(P - abs(2*ba.period(P) - P)); d = 16 + int((m - m % P)/P + 0.5); }; // ``` // // #### References // // //------------------------------------------------------------ declare fb_comb author "Julius O. Smith III"; declare fb_comb copyright "Copyright (C) 2003-2019 by Julius O. Smith III , revised by Oleg Nesterov"; declare fb_comb license "LicenseRef-STK-4.3"; fb_comb(maxdel,del,b0,aN) = fb_comb_common(de.delay(maxdel),del,b0,-aN) : mem; //-----------------------`(fi.)fb_fcomb`----------------------- // Feed-Back Comb Filter (floating point delay). // `fb_fcomb` is a standard Faust function. As `fb_comb`, it computes // `y[n] = b0 x[n-1] - aN y[n-del]` (the output is delayed by one sample), // where `y[n-del]` is linearly interpolated. // // #### Usage // // ``` // _ : fb_fcomb(maxdel,del,b0,aN) : _ // ``` // // Where: // // * `maxdel`: maximum delay (a power of 2) // * `del`: current (float) comb-filter delay (the feedback loop length), between 1 and maxdel // * `b0`: input gain // * `aN`: minus the gain applied to delay-line output before summing with the input // and feeding to the delay line // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // fb_fcomb_test = src : fi.fb_fcomb(2048, 64.5, 0.7, 0.6); // fb_fcomb_slider_test = no.noise : fi.fb_fcomb(2048, hslider("delay", 64.5, 1, 2047, 0.01), hslider("b0", 0.7, -1, 1, 0.01), hslider("aN", 0.6, -1, 1, 0.01)); // fb_fcomb_modulated_test = no.noise : fi.fb_fcomb(2048, 16 + 112*tri, 0.7, 0.6) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // fb_fcomb_jump_test = no.noise : fi.fb_fcomb(2048, 16 + 112*sq, 0.7, 0.6) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### References // // //------------------------------------------------------------ declare fb_fcomb author "Julius O. Smith III"; declare fb_fcomb copyright "Copyright (C) 2003-2019 by Julius O. Smith III , revised by Oleg Nesterov"; declare fb_fcomb license "LicenseRef-STK-4.3"; fb_fcomb(maxdel,del,b0,aN) = fb_comb_common(de.fdelay(maxdel),del,b0,-aN) : mem; //-----------------------`(fi.)rev1`----------------------- // Special case of `fb_comb` (`rev1(maxdel,N,g)` is `fb_comb(maxdel,N,1,-g)`): // // ``` // y[n] = x[n-1] + g y[n-N] // ``` // // The "rev1 section" dates back to the 1960s in computer-music reverberation. // See `jcrev` and `satrev` in `reverbs.lib` for usage examples. // // #### Usage // // ``` // _ : rev1(maxdel,N,g) : _ // ``` // // Where: // // * `maxdel`: maximum delay (a power of 2) // * `N`: current feedback comb-filter delay in samples (the loop length, an integer) // * `g`: feedback gain // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // rev1_test = src : fi.rev1(2048, 64, 0.6); // rev1_slider_test = no.noise : fi.rev1(2048, hslider("delay", 64, 1, 2047, 1), hslider("g", 0.6, -1, 1, 0.01)); // rev1_modulated_test = no.noise : fi.rev1(2048, d, 0.6) with { P = int(ma.SR/10); m = 112*(P - abs(2*ba.period(P) - P)); d = 16 + int((m - m % P)/P + 0.5); }; // ``` //------------------------------------------------------------ declare rev1 author "Julius O. Smith III"; declare rev1 copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare rev1 license "LicenseRef-STK-4.3"; rev1(maxdel,N,g) = fb_comb(maxdel,N,1,-g); //-----`(fi.)fbcombfilter`, `(fi.)ffbcombfilter`------------ // Other special cases of Feed-Back Comb Filter. Their output is the // delay-line output, and the feedback loop is one sample longer than the delay: // // ``` // y[n] = x[n-intdel] + g y[n-intdel-1] // ``` // // (with `del` in place of `intdel`, linearly interpolated, for `ffbcombfilter`). // // #### Usage // // ``` // _ : fbcombfilter(maxdel,del,g) : _ // _ : ffbcombfilter(maxdel,del,g) : _ // ``` // // Where: // // * `maxdel`: maximum delay (a power of 2) // * `del`: current comb-filter delay between 0 and maxdel: an integer for // `fbcombfilter` (uses `de.delay`; a non-integer value is truncated), a // float for `ffbcombfilter` (uses `de.fdelay`); the loop length is `del+1` // * `g`: feedback gain // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // fbcombfilter_test = src : fi.fbcombfilter(2048, 64, 0.6); // ffbcombfilter_test = src : fi.ffbcombfilter(2048, 64.5, 0.6); // fbcombfilter_slider_test = no.noise : fi.fbcombfilter(2048, hslider("delay", 64, 1, 2047, 1), hslider("g", 0.6, -1, 1, 0.01)); // fbcombfilter_modulated_test = no.noise : fi.fbcombfilter(2048, d, 0.6) with { P = int(ma.SR/10); m = 112*(P - abs(2*ba.period(P) - P)); d = 16 + int((m - m % P)/P + 0.5); }; // ffbcombfilter_slider_test = no.noise : fi.ffbcombfilter(2048, hslider("delay", 64.5, 1, 2047, 0.01), hslider("g", 0.6, -1, 1, 0.01)); // ffbcombfilter_modulated_test = no.noise : fi.ffbcombfilter(2048, 16 + 112*tri, 0.6) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // ffbcombfilter_jump_test = no.noise : fi.ffbcombfilter(2048, 16 + 112*sq, 0.6) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### References // // //------------------------------------------------------------ declare fbcombfilter author "Julius O. Smith III"; declare fbcombfilter copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare fbcombfilter license "LicenseRef-STK-4.3"; fbcombfilter(maxdel,intdel,g) = (+ : de.delay(maxdel,intdel)) ~ *(g); declare ffbcombfilter author "Julius O. Smith III"; declare ffbcombfilter copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare ffbcombfilter license "LicenseRef-STK-4.3"; ffbcombfilter(maxdel,del,g) = (+ : de.fdelay(maxdel,del)) ~ *(g); //-------------------`(fi.)allpass_comb`----------------- // Schroeder Allpass Comb Filter. Note that: // // ``` // allpass_comb(maxlen,len,aN) : mem = ff_comb(maxlen,len,aN,1) : fb_comb(maxlen,len,1,aN); // ``` // // (`fb_comb` delays its output by one sample, hence the `mem`), // which is a direct-form-1 implementation, requiring two delay lines. // The implementation here is direct-form-2 requiring only one delay line. // // #### Usage // // ``` // _ : allpass_comb(maxdel,intdel,aN) : _ // ``` // // Where: // // * `maxdel`: maximum delay (a power of 2) // * `intdel`: current (integer) comb-filter delay between 1 and maxdel // (a non-integer value is truncated) // * `aN`: minus the feedback gain // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // allpass_comb_test = src : fi.allpass_comb(2048, 64, 0.6); // allpass_comb_slider_test = no.noise : fi.allpass_comb(2048, hslider("delay", 64, 1, 2047, 1), hslider("aN", 0.6, -1, 1, 0.01)); // allpass_comb_modulated_test = no.noise : fi.allpass_comb(2048, d, 0.6) with { P = int(ma.SR/10); m = 112*(P - abs(2*ba.period(P) - P)); d = 16 + int((m - m % P)/P + 0.5); }; // ``` // // #### References // // * // * // * //------------------------------------------------------------ declare allpass_comb author "Julius O. Smith III"; declare allpass_comb copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare allpass_comb license "LicenseRef-STK-4.3"; allpass_comb(maxdel,N,aN) = (+ <: de.delay(maxdel,N-1),*(aN)) ~ *(-aN) : mem,_ : +; //-------------------`(fi.)allpass_fcomb`----------------- // Schroeder Allpass Comb Filter. // `allpass_fcomb` is a standard Faust function. // Note that: // // ``` // allpass_fcomb(maxlen,len,aN) : mem = ff_fcomb(maxlen,len,aN,1) : fb_fcomb(maxlen,len,1,aN); // ``` // // (`fb_fcomb` delays its output by one sample, hence the `mem`), // which is a direct-form-1 implementation, requiring two delay lines. // The implementation here is direct-form-2 requiring only one delay line. // // The delay line is linearly interpolated (`de.fdelay`), and linear // interpolation is a lowpass filter at fractional delays, so the response is // allpass only for an integer `del`. With `del` = 1000.5 and `aN` = 0.6, the // impulse response keeps 64% of its energy, and the gain drops by // 1.6 dB (8-12 kHz) to 4.3 dB (20-24 kHz) at 48 kHz on average. // `allpass_fcomb1a` stays allpass at every delay. // // #### Usage // // ``` // _ : allpass_fcomb(maxdel,del,aN) : _ // ``` // // Where: // // * `maxdel`: maximum delay (a power of 2) // * `del`: current (float) comb-filter delay between 1 and maxdel // * `aN`: minus the feedback gain // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // allpass_fcomb_test = src : fi.allpass_fcomb(2048, 64.5, 0.6); // allpass_fcomb_slider_test = no.noise : fi.allpass_fcomb(2048, hslider("delay", 64.5, 1, 2047, 0.01), hslider("aN", 0.6, -1, 1, 0.01)); // allpass_fcomb_modulated_test = no.noise : fi.allpass_fcomb(2048, 16 + 112*tri, 0.6) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // allpass_fcomb_jump_test = no.noise : fi.allpass_fcomb(2048, 16 + 112*sq, 0.6) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### References // // * // * // * //------------------------------------------------------------ declare allpass_fcomb author "Julius O. Smith III"; declare allpass_fcomb copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare allpass_fcomb license "LicenseRef-STK-4.3"; allpass_fcomb(maxdel,N,aN) = (+ <: de.fdelay(maxdel,N-1),*(aN)) ~ *(-aN) : mem,_ : +; //-----------------------`(fi.)rev2`----------------------- // Special case of `allpass_comb` (`rev2(maxlen,len,g)`). // The "rev2 section" dates back to the 1960s in computer-music reverberation. // See `jcrev` and `satrev` in `reverbs.lib` for usage examples. // // #### Usage // // ``` // _ : rev2(maxlen,len,g) : _ // ``` // // Where: // // * `maxlen`: maximum delay (a power of 2) // * `len`: current allpass comb-filter delay in samples // * `g`: allpass coefficient (its sign is negated internally) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // rev2_test = src : fi.rev2(2048, 64, 0.6); // rev2_slider_test = no.noise : fi.rev2(2048, hslider("delay", 64, 1, 2047, 1), hslider("g", 0.6, -1, 1, 0.01)); // rev2_modulated_test = no.noise : fi.rev2(2048, d, 0.6) with { P = int(ma.SR/10); m = 112*(P - abs(2*ba.period(P) - P)); d = 16 + int((m - m % P)/P + 0.5); }; // ``` //------------------------------------------------------------ declare rev2 author "Julius O. Smith III"; declare rev2 copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare rev2 license "LicenseRef-STK-4.3"; rev2(maxlen,len,g) = allpass_comb(maxlen,len,-g); //-------------------`(fi.)allpass_fcomb5`, `(fi.)allpass_fcomb1a`----------------- // Same as `allpass_fcomb` but use `fdelay5` and `fdelay1a` internally // (Interpolation helps - look at an fft of faust2octave on: // `1-1' <: allpass_fcomb(1024,10.5,0.95), allpass_fcomb5(1024,10.5,0.95);`) // // `allpass_fcomb1a` (first-order Thiran allpass interpolation) is allpass at // every delay `N` from 1.5 to maxdel. `allpass_fcomb5` (fifth-order Lagrange // interpolation) is allpass only for an integer `N`: at fractional delays the // interpolator is a lowpass filter, worst at a fractional part of 0.5. With // `N` = 1000.5 and `aN` = 0.6, the impulse response keeps 79% of its energy: // the gain is within 0.2 dB of unity up to SR/4 on average, and drops by // 2.5 dB (16-20 kHz) to 4 dB (20-24 kHz) at 48 kHz. // `allpass_fcomb5` needs `N` from 3 to maxdel-1: above maxdel-1 the top // interpolator tap is clamped by the delay line, and the gain exceeds unity // (by up to 0.28 dB at `N` = 2047.5 with maxdel = 2048). // // #### Usage // // ``` // _ : allpass_fcomb5(maxdel,N,aN) : _ // _ : allpass_fcomb1a(maxdel,N,aN) : _ // ``` // // Where: // // * `maxdel`: maximum delay (a power of 2) // * `N`: current (float) allpass comb-filter delay between 0 and maxdel // * `aN`: allpass coefficient: the transfer function is (aN + z^(-N))/(1 + aN z^(-N)) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // allpass_fcomb5_test = src : fi.allpass_fcomb5(2048, 64.5, 0.6); // allpass_fcomb1a_test = src : fi.allpass_fcomb1a(2048, 64.5, 0.6); // allpass_fcomb5_slider_test = no.noise : fi.allpass_fcomb5(2048, hslider("delay", 64.5, 1, 2047, 0.01), hslider("aN", 0.6, -1, 1, 0.01)); // allpass_fcomb5_modulated_test = no.noise : fi.allpass_fcomb5(2048, 16 + 112*tri, 0.6) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // allpass_fcomb5_jump_test = no.noise : fi.allpass_fcomb5(2048, 16 + 112*sq, 0.6) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // allpass_fcomb1a_slider_test = no.noise : fi.allpass_fcomb1a(2048, hslider("delay", 64.5, 1, 2047, 0.01), hslider("aN", 0.6, -1, 1, 0.01)); // allpass_fcomb1a_modulated_test = no.noise : fi.allpass_fcomb1a(2048, 16 + 112*tri, 0.6) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // allpass_fcomb1a_jump_test = no.noise : fi.allpass_fcomb1a(2048, 16 + 112*sq, 0.6) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //------------------------------------------------------------ declare allpass_fcomb5 author "Julius O. Smith III"; declare allpass_fcomb5 copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare allpass_fcomb5 license "LicenseRef-STK-4.3"; allpass_fcomb5(maxdel,N,aN) = (+ <: de.fdelay5(maxdel,N-1),*(aN)) ~ *(-aN) : mem,_ : +; declare allpass_fcomb1a author "Julius O. Smith III"; declare allpass_fcomb1a copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare allpass_fcomb1a license "LicenseRef-STK-4.3"; allpass_fcomb1a(maxdel,N,aN) = (+ <: de.fdelay1a(maxdel,N-1),*(aN)) ~ *(-aN) : mem,_ : +; //========================Direct-Form Digital Filter Sections============================= // Filters specified by transfer-function polynomials `B(z)/A(z)`, computed // directly from their coefficients. A direct form loses precision when its // poles come close to z = 1, that is, for frequencies low relative to the // sample rate, and it behaves badly when its coefficients change while it // runs. For an analog prototype, prefer the functions of the section // [Digital Filter Sections Specified as Analog Filter Sections](#digital-filter-sections-specified-as-analog-filter-sections), // whose references explain why. For a second-order z-domain design whose // coefficients move, `tf2_tpt` realizes the same `B(z)/A(z)` as a // state-variable filter. //======================================================================================== // Specified by transfer-function polynomials B(z)/A(z) as in matlab //----------------------------`(fi.)iir`------------------------------- // Nth-order Infinite-Impulse-Response (IIR) digital filter, // implemented in terms of the Transfer-Function (TF) coefficients. // Such filter structures are termed "direct form". // // `iir` is a standard Faust function. // // #### Usage // // ``` // _ : iir(bcoeffs,acoeffs) : _ // ``` // // Where: // // * `bcoeffs`: (b0,b1,...,b_order) = TF numerator coefficients // * `acoeffs`: (a1,...,a_order) = TF denominator coeffs (a0=1) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // src = os.tosc(440); // iir_test = src : fi.iir((0.5, 0.5), (0.3)); // ``` // // #### References // // //------------------------------------------------------------ declare iir author "Julius O. Smith III"; declare iir copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare iir license "LicenseRef-STK-4.3"; iir(bv,av) = ma.sub ~ fir(av) : fir(bv); //-----------------------------`(fi.)fir`--------------------------------- // FIR filter (convolution of FIR filter coefficients with a signal). // `fir` is a standard Faust function. // // #### Usage // // ``` // _ : fir(bv) : _ // ``` // // Where: // // * `bv`: `b0,b1,...,bn`, a parallel bank of coefficient signals // // #### Note // // `bv` is processed using pattern-matching at compile time, // so it must have this normal form (parallel signals). // // #### Example test program // // Smoothing white noise with a five-point moving average: // // ``` // bv = .2,.2,.2,.2,.2; // process = noise : fir(bv); // ``` // // Equivalent (note double parens): // // ``` // process = noise : fir((.2,.2,.2,.2,.2)); // ``` // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // src = os.tosc(440); // fir_test = src : fi.fir((0.2, 0.2, 0.2, 0.2, 0.2)); // ``` //------------------------------------------------------------ declare fir author "Julius O. Smith III"; declare fir copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare fir license "LicenseRef-STK-4.3"; fir((b0,bv)) = _ <: *(b0), R(1,bv) :> _ with { R(n,(bn,bv)) = (@(n):*(bn)), R(n+1,bv); R(n, bn) = (@(n):*(bn)); }; fir(b0) = *(b0); //---------------`(fi.)conv`, `(fi.)convN`------------------------------- // Convolution of input signal with given coefficients. `conv` filters one // signal with all the coefficients. `convN` takes N signals and sums the i-th, // delayed by i samples, times the i-th coefficient: split one signal to N // copies first to convolve it with the first N coefficients. // // #### Usage // // ``` // _ : conv(kv) : _ // si.bus(N) : convN(N,kv) : _ // Useful when N < ba.count(kv) // _ <: si.bus(N) : convN(N,kv) : _ // one signal, N coefficients // ``` // // Where: // // * `kv`: the list of coefficients `(k1,k2,k3,...)`, one signal bank // * `N`: number of input signals of `convN`, at most the number of // coefficients (a constant numerical expression) // // #### Example // // ``` // _ : conv((0.25,0.25,0.25,0.25)) : _ // _ <: si.bus(3) : convN(3,(0.3,0.2,0.1,0.05)) : _ // ``` // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // si = library("signals.lib"); // src = os.tosc(440); // convN_test = (src <: si.bus(3)) : fi.convN(3, (0.3, 0.2, 0.1, 0.05)); // conv_test = src : fi.conv((0.25, 0.25, 0.25, 0.25)); // ``` //------------------------------------------------------------ //convN(N,kv,x) = sum(i,N,ba.take(i+1,kv) * x@i); // take() defined in basics.lib declare convN author "Julius O. Smith III"; declare convN copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare convN license "LicenseRef-STK-4.3"; convN(N,kv) = sum(i,N, @(i)*ba.take(i+1,kv)); // take() defined in basics.lib //conv(kv,x) = sum(i,count(kv),take(i+1,kv) * x@i); // count() from math.lib declare conv author "Julius O. Smith III"; declare conv copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare conv license "LicenseRef-STK-4.3"; conv(kv) = fir(kv); //----------------`(fi.)tf1`, `(fi.)tf2`, `(fi.)tf3`---------------------- // tfN = N'th-order direct-form digital filter. // `tf2` is a standard Faust function. // // #### Usage // // ``` // _ : tf1(b0,b1,a1) : _ // _ : tf2(b0,b1,b2,a1,a2) : _ // _ : tf3(b0,b1,b2,b3,a1,a2,a3) : _ // ``` // // Where: // // * `b0`, `b1`, `b2`, `b3`: transfer-function numerator coefficients // (`b0..bN` for `tfN`) // * `a1`, `a2`, `a3`: transfer-function denominator coefficients, the // denominator being monic (`a0 = 1`; `a1..aN` for `tfN`) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // src = os.tosc(440); // tf1_test = src : fi.tf1(0.5, 0.25, -0.4); // tf2_test = src : fi.tf2(0.1, 0.2, 0.1, -0.5, 0.06); // tf3_test = src : fi.tf3(0.1, 0.3, 0.3, 0.1, -0.9, 0.26, -0.024); // ``` // // #### References // // //------------------------------------------------------------ declare tf1 author "Julius O. Smith III"; declare tf1 copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare tf1 license "LicenseRef-STK-4.3"; tf1(b0,b1,a1) = _ <: *(b0), (mem : *(b1)) :> + ~ *(0-a1); declare tf2 author "Julius O. Smith III"; declare tf2 copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare tf2 license "LicenseRef-STK-4.3"; tf2(b0,b1,b2,a1,a2) = iir((b0,b1,b2),(a1,a2)); // tf2 is a variant of tf22 below with duplicated mems declare tf3 author "Julius O. Smith III"; declare tf3 copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare tf3 license "LicenseRef-STK-4.3"; tf3(b0,b1,b2,b3,a1,a2,a3) = iir((b0,b1,b2,b3),(a1,a2,a3)); //-----------------------------`(fi.)TF2`-------------------------------- // Tunable second-order direct-form digital filter, the "original" // biquad of music.lib: y[n] = b0*x[n] + b1*x[n-1] + b2*x[n-2] - // a1*y[n-1] - a2*y[n-2]. Kept for comparison and for the compatibility // libraries (maxmsp.lib, instruments.lib); new code should use `tf2` // instead. // // #### Usage // // ``` // _ : TF2(b0,b1,b2,a1,a2) : _ // ``` // // Where: // // * `b0`, `b1`, `b2`: the feedforward coefficients // * `a1`, `a2`: the feedback coefficients // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // TF2_legacy_test = os.tosc(440) : fi.TF2(0.2, 0.4, 0.2, -0.5, 0.3); // ``` //------------------------------------------------------------ declare TF2 author "GRAME"; declare TF2 copyright "GRAME"; declare TF2 license "LicenseRef-LGPL-2.1-or-later-with-Faust-exception"; TF2(b0,b1,b2,a1,a2) = sub ~ conv2(a1,a2) : conv3(b0,b1,b2) with { conv3(k0,k1,k2,x) = k0*x + k1*x' + k2*x''; conv2(k0,k1,x) = k0*x + k1*x'; sub(x,y) = y-x; }; //-----------------------------`(fi.)tf2_tpt`-------------------------------- // Second-order digital filter `N(z)/A(z)`, with `A(z) = 1 + a1 z^-1 + a2 z^-2` // and `N(z) = n0 + n1 z^-1 + n2 z^-2`, realized as a trapezoidal // (topology-preserving) state-variable filter with exactly the same poles and // zeros. It is for z-domain designs whose parameters move (a pole radius and // angle, a notch or allpass frequency): the same filter as `tf2`, but it stays // accurate in single precision when the poles approach z = 1 or z = -1, and // its output stays continuous when the coefficients jump or move at audio // rate, where a direct form rings or diverges. For an analog prototype, use // `tf2s`. // // The coefficients are passed in a form the caller computes without // cancellation from its design parameters, never from rounded `a1` and `a2` // (see the method below). // // #### Usage // // ``` // _ : tf2_tpt(P,S,fq,Np,Nm,nd) : _ // ``` // // Where: // // * `P`: `A(1) = 1 + a1 + a2`, positive, or 0 together with `Np` (a pole and a // zero at z = 1 that cancel, as in `notchw` at 0 Hz) // * `S`: `A(-1) = 1 - a1 + a2`, positive, or 0 together with `Nm` (a pole and // a zero at z = -1 that cancel, as in `notchw` at the Nyquist limit) // * `fq`: `1 - a2`, positive (`P + S + 2*fq = 4`) // * `Np`: `N(1) = n0 + n1 + n2`, the gain at DC is `Np/P` // * `Nm`: `N(-1) = n0 - n1 + n2`, the gain at Nyquist is `Nm/S` // * `nd`: `n0 - n2` // // #### Method // // For complex poles `r*exp(+-j*th)`, with `om = 1 - r` computed directly // (for example `0 - ma.expm1(-PI*B/SR)` for a bandwidth `B`): // `P = om^2 + 4*r*sin(th/2)^2`, `S = om^2 + 4*r*cos(th/2)^2` and // `fq = om*(2 - om)`. Near the Nyquist limit, compute `cos(th/2)` as // `sin(PI*(SR/2 - f)/SR)` for a frequency `f`, so that `S` is as accurate as // `P`. For example, a resonator of unity peak gain, // `(1 - r^2)/2*(1 - z^-2)/A(z)`, is `tf2_tpt(P,S,fq,0,0,fq)`, and the notch // `(1 + A_ap(z))/2` of an allpass `A_ap` with these poles is // `tf2_tpt(P,S,fq,P,S,0)` (see `notchw`). // // The state-variable filter has `g = sqrt(P/S)`, `k*g = 2*fq/S` and // `1 + k*g + g^2 = 4/S`, and its highpass, bandpass and lowpass outputs map // onto the numerator with the taps `cH = Nm/S`, `cB = 2*nd/(g*S)` and // `cL = Np/P`. Since `hp + k*bp + lp = x`, the output is computed as // `cH*x + (cB - k*cH)*bp + (cL - cH)*lp`, and the state-variable filter runs // on `x` scaled by the larger of the last two taps, so that its states hold the // dominant part of the output at the output level. Each coefficient update // costs four divisions and one square root; none per sample when the // coefficients are constant. // // When `P > S` (poles with Re z < 0: a resonance above `SR/4`), the section // is mirrored: it realizes `H(z) = H'(-z)`, where `H'` has `P` and `S`, and // `Np` and `Nm`, exchanged, by replacing each state delay `z^-1` with `-z^-1` // (under the bilinear transform, z -> -z is s -> 1/s). The poles of `H'` have // Re z > 0 and `g <= 1`, so a pole near z = -1 is as accurate as one near // z = 1. When the coefficients cross `P = S`, the states are mapped to those // of the other form that give the same output. // // #### Test // ``` // fi = library("filters.lib"); // ba = library("basics.lib"); // ma = library("maths.lib"); // no = library("noises.lib"); // os = library("oscillators.lib"); // tf2_tpt_test = no.noise : fi.tf2_tpt(P, S, fq, 0, 0, fq) with { fc = 1000; om = 0 - ma.expm1(0 - ma.PI*fc/10/ma.SR); r = 1 - om; th = 2*ma.PI*fc/ma.SR; P = om*om + 4*r*sin(0.5*th)^2; S = om*om + 4*r*cos(0.5*th)^2; fq = om*(2 - om); }; // tf2_tpt_slider_test = no.noise : fi.tf2_tpt(P, S, fq, 0, 0, fq) with { fc = hslider("fc", 1000, 20, 20000, 1); om = 0 - ma.expm1(0 - ma.PI*fc/10/ma.SR); r = 1 - om; th = 2*ma.PI*fc/ma.SR; P = om*om + 4*r*sin(0.5*th)^2; S = om*om + 4*r*cos(0.5*th)^2; fq = om*(2 - om); }; // tf2_tpt_modulated_test = no.noise : fi.tf2_tpt(P, S, fq, 0, 0, fq) with { Pd = int(ma.SR/10); tri = 1 - abs(2*ba.period(Pd)/Pd - 1); fc = 20*pow(250, tri); om = 0 - ma.expm1(0 - ma.PI*fc/10/ma.SR); r = 1 - om; th = 2*ma.PI*fc/ma.SR; P = om*om + 4*r*sin(0.5*th)^2; S = om*om + 4*r*cos(0.5*th)^2; fq = om*(2 - om); }; // tf2_tpt_jump_test = no.noise : fi.tf2_tpt(P, S, fq, 0, 0, fq) with { Pd = int(ma.SR/10); sq = ba.period(2*Pd) < Pd; fc = 20*pow(250.0, sq); om = 0 - ma.expm1(0 - ma.PI*fc/10/ma.SR); r = 1 - om; th = 2*ma.PI*fc/ma.SR; P = om*om + 4*r*sin(0.5*th)^2; S = om*om + 4*r*cos(0.5*th)^2; fq = om*(2 - om); }; // tf2_tpt_audio_modulated_test = 0.1*no.noise : fi.tf2_tpt(P, S, fq, 0, 0, fq) with { fc = max(20, 1000*(1 + 0.9*os.tosc(500))); om = 0 - ma.expm1(0 - ma.PI*fc/10/ma.SR); r = 1 - om; th = 2*ma.PI*fc/ma.SR; P = om*om + 4*r*sin(0.5*th)^2; S = om*om + 4*r*cos(0.5*th)^2; fq = om*(2 - om); }; // tf2_tpt_high_test = no.noise : fi.tf2_tpt(P, S, fq, 0, 0, fq) with { fc = ma.SR/2 - 200; om = 0 - ma.expm1(0 - ma.PI*100/ma.SR); r = 1 - om; P = om*om + 4*r*sin(ma.PI*fc/ma.SR)^2; S = om*om + 4*r*sin(ma.PI*(0.5*ma.SR - fc)/ma.SR)^2; fq = om*(2 - om); }; // tf2_tpt_mirror_modulated_test = no.noise : fi.tf2_tpt(P, S, fq, 0, 0, fq) with { Pd = int(ma.SR/10); tri = 1 - abs(2*ba.period(Pd)/Pd - 1); fc = ma.SR*(0.1 + 0.35*tri); om = 0 - ma.expm1(0 - ma.PI*fc/10/ma.SR); r = 1 - om; P = om*om + 4*r*sin(ma.PI*fc/ma.SR)^2; S = om*om + 4*r*sin(ma.PI*(0.5*ma.SR - fc)/ma.SR)^2; fq = om*(2 - om); }; // tf2_tpt_mirror_jump_test = no.noise : fi.tf2_tpt(P, S, fq, 0, 0, fq) with { Pd = int(ma.SR/10); sq = ba.period(2*Pd) < Pd; fc = ma.SR*select2(sq, 0.1, 0.45); om = 0 - ma.expm1(0 - ma.PI*fc/10/ma.SR); r = 1 - om; P = om*om + 4*r*sin(ma.PI*fc/ma.SR)^2; S = om*om + 4*r*sin(ma.PI*(0.5*ma.SR - fc)/ma.SR)^2; fq = om*(2 - om); }; // ``` // // #### References // // * Julius O. Smith III, "Digital State-Variable Filters": // // * Vadim Zavalishin, "The Art of VA Filter Design", revision 2.1.2, 2020: // //------------------------------------------------------------ declare tf2_tpt author "Julius O. Smith III"; declare tf2_tpt license "MIT"; tf2_tpt(P,S,fq,Np,Nm,nd,x) = cH*x + (x*sig : svf) with { // Poles with Re z < 0 (P > S): the mirrored section H'(-z), where H' has // P and S, and Np and Nm, exchanged (its poles have Re z > 0) and its // state delays z^-1 become -z^-1. Then g is at most 1, and a // pole near z = -1 is as accurate as one near z = 1. mir = P > S; ms = 1 - 2*mir; // -1: the state delays are -z^-1 Pm = select2(mir, P, S); Sm = select2(mir, S, P); Npm = select2(mir, Np, Nm); Nmm = select2(mir, Nm, Np); // Pm and Sm at least EPSILON^2: at P = 0 (a pole on z = 1, as in notchw at // 0 Hz) the section computes the limit of the filter, and likewise at S = 0 Pc = max(Pm, ma.EPSILON*ma.EPSILON); Sc = max(Sm, ma.EPSILON*ma.EPSILON); // four reciprocals (min(ma.MAX) keeps Faust from turning the products // back into divisions) and one square root per coefficient update iS = 1/Sc : min(ma.MAX); iP = 1/Pc : min(ma.MAX); g = sqrt(Pc*iS); ig = 1/g : min(ma.MAX); k = 2*fq*ig*iS; cH = Nmm*iS; cB = 2*nd*ig*iS - k*cH; // bp tap of the output written as cH*x + cB*bp + cL*lp cL = Npm*iP - cH; sig = max(abs(cB), abs(cL)) : max(ma.MIN); // the SVF runs on sig*x isg = 1/sig : min(ma.MAX); kpg = (2*fq + Pc)*ig*iS; // k + g d = 0.25*Sc; // 1/(1 + k*g + g^2) // When the section changes form, the states map to those of the other form // that give the same output: s <- -g*(sig/sig')*swap(s), with g and sig // of the form now in use and sig' of the previous one chg = mir != mir'; cf = 0 - g*sig*isg'; svf = tick ~ (_,_) : !,!,_ with { tick(s1i, s2i, u) = ms*(bp + g*hp), ms*(lp + g*bp), (cB*bp + cL*lp)*isg with { s1 = select2(chg, s1i, cf*s2i); s2 = select2(chg, s2i, cf*s1i); hp = (u - kpg*s1 - s2)*d; bp = g*hp + s1; lp = g*bp + s2; }; }; }; //------------`(fi.)notchw`-------------- // Simple notch filter based on a biquad, realized with `tf2_tpt`. // `notchw` is a standard Faust function. // // #### Usage: // // ``` // _ : notchw(width,freq) : _ // ``` // // Where: // // * `width`: "notch width" in Hz (approximate) // * `freq`: "notch frequency" in Hz, from 0 (a first-order DC notch) to // `ma.SR/2` (a first-order notch at the Nyquist limit). Above `ma.SR/4` the // section runs mirrored (see `tf2_tpt`), so that a notch near the Nyquist // limit is as accurate as one near 0 Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // notchw_test = src : fi.notchw(200, 1000); // notchw_slider_test = no.noise : fi.notchw(hslider("width", 200, 10, 2000, 1), hslider("freq", 1000, 20, 20000, 1)); // notchw_modulated_test = no.noise : fi.notchw(100, 200*pow(25, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // notchw_jump_test = no.noise : fi.notchw(100, 200*pow(25, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // notchw_audio_modulated_test = 0.1*no.noise : fi.notchw(fc/10, fc) with { fc = max(20, 1000*(1 + 0.9*os.tosc(500))); }; // notchw_dc_test = no.noise : fi.notchw(100, 0); // notchw_nyquist_test = no.noise : fi.notchw(100, ma.SR/2); // notchw_high_test = no.noise : fi.notchw(100, ma.SR/2 - 100); // notchw_mirror_modulated_test = no.noise : fi.notchw(1000, ma.SR*(0.1 + 0.35*tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // notchw_mirror_jump_test = no.noise : fi.notchw(1000, ma.SR*select2(sq, 0.1, 0.45)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### References // // * // * Julius O. Smith III, "Digital State-Variable Filters", for the trapezoidal // (TPT) state-variable filter that `tf2_tpt` runs on: // //------------------------------------------------------------ declare notchw author "Julius O. Smith III"; declare notchw copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare notchw license "LicenseRef-STK-4.3"; notchw(width,freq) = tf2_tpt(P, S, fq, P, S, 0) with { fb = 0.5*width; // First design a dcblockerat(width/2) wn = ma.PI*fb/ma.SR; om = 2*wn/(1 + wn); // 1 - p, with p = (1 - wn)/(1 + wn) our pole radius q = 1 + (1 - om)^2; // 1 + p^2 // Now place unit-circle zeros at desired angles: (1 + A(z))/2, A the // allpass a1 = -(1 + p^2)*cos(tn), a2 = p^2, tn = 2*PI*fc/SR fc = max(0, min(freq, 0.5*ma.SR)); P = 2*q*sin(ma.PI*fc/ma.SR)^2; // 1 + a1 + a2 = 2q sin(tn/2)^2 S = 2*q*sin(ma.PI*(0.5*ma.SR - fc)/ma.SR)^2; // 1 - a1 + a2 = 2q cos(tn/2)^2, accurate near SR/2 fq = om*(2 - om); // 1 - a2 }; //======================Direct-Form Second-Order Biquad Sections========================== // Direct-Form Second-Order Biquad Sections // // #### References // // //======================================================================================== //----------------`(fi.)tf21`, `(fi.)tf22`, `(fi.)tf22t`, `(fi.)tf21t`---------------------- // tfN = N'th-order direct-form digital filter where: // // * `tf21` is tf2, direct-form 1 // * `tf22` is tf2, direct-form 2 // * `tf22t` is tf2, direct-form 2 transposed // * `tf21t` is tf2, direct-form 1 transposed // // #### Usage // // ``` // _ : tf21(b0,b1,b2,a1,a2) : _ // _ : tf22(b0,b1,b2,a1,a2) : _ // _ : tf22t(b0,b1,b2,a1,a2) : _ // _ : tf21t(b0,b1,b2,a1,a2) : _ // ``` // // Where: // // * `b0`, `b1`, `b2`: transfer-function numerator coefficients // * `a1`, `a2`: transfer-function denominator coefficients (monic // denominator, `a0 = 1`) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // src = os.tosc(440); // tf21_test = src : fi.tf21(0.1, 0.2, 0.1, -0.5, 0.06); // tf22_test = src : fi.tf22(0.1, 0.2, 0.1, -0.5, 0.06); // tf22t_test = src : fi.tf22t(0.1, 0.2, 0.1, -0.5, 0.06); // tf21t_test = src : fi.tf21t(0.1, 0.2, 0.1, -0.5, 0.06); // ``` // // #### References // // //------------------------------------------------------------ declare tf21 author "Julius O. Smith III"; declare tf21 copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare tf21 license "LicenseRef-STK-4.3"; tf21(b0,b1,b2,a1,a2) = // tf2, direct-form 1: _ <:(mem<:((mem:*(b2)),*(b1))),*(b0) :>_ : ((_,_,_:>_) ~(_<:*(-a1),(mem:*(-a2)))); declare tf22 author "Julius O. Smith III"; declare tf22 copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare tf22 license "LicenseRef-STK-4.3"; tf22(b0,b1,b2,a1,a2) = // tf2, direct-form 2: _ : (((_,_,_:>_)~*(-a1)<:mem,*(b0))~*(-a2)) : (_<:mem,*(b1)),_ : *(b2),_,_ :> _; declare tf22t author "Julius O. Smith III"; declare tf22t copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare tf22t license "LicenseRef-STK-4.3"; tf22t(b0,b1,b2,a1,a2) = // tf2, direct-form 2 transposed: _ : (_,_,(_ <: *(b2)',*(b1)',*(b0)) : _,+',_,_ :> _)~*(-a1)~*(-a2) : _; declare tf21t author "Julius O. Smith III"; declare tf21t copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare tf21t license "LicenseRef-STK-4.3"; tf21t(b0,b1,b2,a1,a2) = // tf2, direct-form 1 transposed: tf22t(1,0,0,a1,a2) : tf22t(b0,b1,b2,0,0); // or write it out if you want //=========================== Ladder/Lattice Digital Filters ============================= // Ladder and lattice digital filters generally have superior numerical // properties relative to direct-form digital filters. They can be derived // from digital waveguide filters, which gives them a physical interpretation. // #### References // // * F. Itakura and S. Saito: "Digital Filtering Techniques for Speech Analysis and Synthesis", // 7th Int. Cong. Acoustics, Budapest, 25 C 1, 1971. // * J. D. Markel and A. H. Gray: Linear Prediction of Speech, New York: Springer Verlag, 1976. // * //======================================================================================== //-------------------------------`(fi.)av2sv`----------------------------------- // Compute reflection coefficients sv from transfer-function denominator av. // The output `sv` is the parallel signal bank `s1,...,sN`, where `si` is // the i'th reflection coefficient. // // #### Usage // // ``` // av2sv(av) : si.bus(ba.count(av)) // ``` // // Where: // // * `av`: parallel signal bank `a1,...,aN`, where `ai` is the coefficient // of `z^(-i)` in the filter transfer-function denominator `A(z)` // // #### Test // ``` // fi = library("filters.lib"); // si = library("signals.lib"); // av2sv_test = fi.av2sv((-0.4, 0.1)) : si.bus(2); // ``` // // #### References // // // (where reflection coefficients are denoted by k rather than s). //------------------------------------------------------------ declare av2sv author "Julius O. Smith III"; declare av2sv copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare av2sv license "LicenseRef-STK-4.3"; av2sv(av) = par(i,M,s(i+1)) with { M = ba.count(av); s(m) = sr(M-m+1); // m=1..M sr(m) = Ari(m,M-m+1); // s_{M-1-m} Ari(m,i) = ba.take(i+1,Ar(m-1)); //step-down recursion for lattice/ladder digital filters: Ar(0) = (1,av); // Ar(m) is order M-m (i.e. "reverse-indexed") Ar(m) = 1,par(i,M-m, (Ari(m,i+1) - sr(m)*Ari(m,M-m-i))/(1-sr(m)*sr(m))); }; //----------------------------`(fi.)bvav2nuv`-------------------------------- // Compute lattice tap coefficients from transfer-function coefficients. // The output `nuv` is the parallel signal bank `nu0,nu1,...,nuN` (N+1 // signals), where `nui` is the i'th tap coefficient. // // #### Usage // // ``` // bvav2nuv(bv,av) : si.bus(ba.count(av)+1) // ``` // // Where: // // * `bv`: parallel signal bank `b0,b1,...,bN`, where `bi` is the // coefficient of `z^(-i)` in the filter numerator // * `av`: parallel signal bank `a1,...,aN`, where `ai` is the coefficient // of `z^(-i)` in the filter denominator // // #### Test // ``` // fi = library("filters.lib"); // si = library("signals.lib"); // bvav2nuv_test = fi.bvav2nuv((0.1, 0.2, 0.3), (-0.4, 0.1)) : si.bus(3); // ``` //------------------------------------------------------------ declare bvav2nuv author "Julius O. Smith III"; declare bvav2nuv copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare bvav2nuv license "LicenseRef-STK-4.3"; bvav2nuv(bv,av) = par(m,M+1,nu(m)) with { M = ba.count(av); nu(m) = ba.take(m+1,Pr(M-m)); // m=0..M // lattice/ladder tap parameters: Pr(0) = bv; // Pr(m) is order M-m, 'r' means "reversed" Pr(m) = par(i,M-m+1, (Pri(m,i) - nu(M-m+1)*Ari(m,M-m-i+1))); Pri(m,i) = ba.take(i+1,Pr(m-1)); Ari(m,i) = ba.take(i+1,Ar(m-1)); //step-down recursion for lattice/ladder digital filters: Ar(0) = (1,av); // Ar(m) is order M-m (recursion index must start at constant) Ar(m) = 1,par(i,M-m, (Ari(m,i+1) - sr(m)*Ari(m,M-m-i))/(1-sr(m)*sr(m))); sr(m) = Ari(m,M-m+1); // s_{M-1-m} }; //--------------------`(fi.)iir_lat2`----------------------- // Two-multiply lattice IIR filter of arbitrary order. // // #### Usage // // ``` // _ : iir_lat2(bv,av) : _ // ``` // // Where: // // * `bv`: transfer-function numerator // * `av`: transfer-function denominator (monic) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // src = os.tosc(440); // iir_lat2_test = src : fi.iir_lat2((0.1, 0.2, 0.3), (-0.4, 0.1)); // ``` //------------------------------------------------------------ declare iir_lat2 author "Julius O. Smith III"; declare iir_lat2 copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare iir_lat2 license "LicenseRef-STK-4.3"; iir_lat2(bv,av) = allpassnt(M,sv) : sum(i,M+1,*(ba.take(M-i+1,tg))) with { M = ba.count(av); sv = av2sv(av); // sv = vector of sin(theta) reflection coefficients tg = bvav2nuv(bv,av); // tg = vector of tap gains }; //-----------------------`(fi.)allpassnt`-------------------------- // Two-multiply lattice allpass (nested order-1 direct-form-ii allpasses), with taps. // // #### Usage // // ``` // _ : allpassnt(n,sv) : si.bus(n+1) // ``` // // Where: // // * `n`: the order of the filter // * `sv`: the reflection coefficients (-1 1) // // The first output is the n-th order allpass output, // while the remaining outputs are taps taken from the // input of each delay element from the input to the output. // See (fi.)allpassn for the single-output case. // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // si = library("signals.lib"); // src = os.tosc(440); // allpassnt_test = src : fi.allpassnt(2, (0.3, -0.2)) : si.bus(3); // ``` //------------------------------------------------------------ declare allpassnt author "Julius O. Smith III"; declare allpassnt copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare allpassnt license "LicenseRef-STK-4.3"; allpassnt(0,sv) = _; allpassnt(n,sv) = _ : ((+ <: (allpassnt(n-1,sv),*(s)))~*(-s)) : fsec(n) with { fsec(1) = ro.crossnn(1) : _, (_<:mem,_) : +,_; fsec(n) = ro.crossn1(n) : _, (_<:mem,_),par(i,n-1,_) : +, par(i,n,_); innertaps(n) = par(i,n,_); s = ba.take(n,sv); // reflection coefficient s = sin(theta) }; //--------------------`(fi.)iir_kl`----------------------- // Kelly-Lochbaum ladder IIR filter of arbitrary order. // // #### Usage // // ``` // _ : iir_kl(bv,av) : _ // ``` // // Where: // // * `bv`: transfer-function numerator // * `av`: transfer-function denominator (monic) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // src = os.tosc(440); // iir_kl_test = src : fi.iir_kl((0.1, 0.2, 0.3), (-0.4, 0.1)); // ``` //------------------------------------------------------------ declare iir_kl author "Julius O. Smith III"; declare iir_kl copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare iir_kl license "LicenseRef-STK-4.3"; iir_kl(bv,av) = allpassnklt(M,sv) : sum(i,M+1,*(tghr(i))) with { M = ba.count(av); sv = av2sv(av); // sv = vector of sin(theta) reflection coefficients tg = bvav2nuv(bv,av); // tg = vector of tap gains for 2mul case tgr(i) = ba.take(M+1-i,tg); tghr(n) = tgr(n)/pi(n); pi(0) = 1; pi(n) = pi(n-1)*(1+ba.take(M-n+1,sv)); // all sign parameters '+' }; //-----------------------`(fi.)allpassnklt`-------------------------- // Kelly-Lochbaum ladder allpass. Its n+1 outputs are the allpass output // followed by the n tap signals of the ladder (used by `iir_kl` to form // a general transfer function). // // #### Usage: // // ``` // _ : allpassnklt(n,sv) : si.bus(n+1) // ``` // // Where: // // * `n`: the order of the filter // * `sv`: the reflection coefficients (-1 1) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // si = library("signals.lib"); // src = os.tosc(440); // allpassnklt_test = src : fi.allpassnklt(2, (0.3, -0.2)) : si.bus(3); // ``` //------------------------------------------------------------ declare allpassnklt author "Julius O. Smith III"; declare allpassnklt copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare allpassnklt license "LicenseRef-STK-4.3"; allpassnklt(0,sv) = _; allpassnklt(n,sv) = _ <: *(s),(*(1+s) : (+ : allpassnklt(n-1,sv))~(*(-s))) : fsec(n) with { fsec(1) = _, (_<:mem*(1-s),_) : sumandtaps(n); fsec(n) = _, (_<:mem*(1-s),_), par(i,n-1,_) : sumandtaps(n); s = ba.take(n,sv); sumandtaps(n) = +,par(i,n,_); }; //--------------------`(fi.)iir_lat1`----------------------- // One-multiply lattice IIR filter of arbitrary order. // // #### Usage // // ``` // _ : iir_lat1(bv,av) : _ // ``` // // Where: // // * `bv`: transfer-function numerator as a bank of parallel signals // * `av`: transfer-function denominator as a bank of parallel signals // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // src = os.tosc(440); // iir_lat1_test = src : fi.iir_lat1((0.1, 0.2, 0.3), (-0.4, 0.1)); // ``` //------------------------------------------------------------ declare iir_lat1 author "Julius O. Smith III"; declare iir_lat1 copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare iir_lat1 license "LicenseRef-STK-4.3"; iir_lat1(bv,av) = allpassn1mt(M,sv) : sum(i,M+1,*(tghr(i+1))) with { M = ba.count(av); sv = av2sv(av); // sv = vector of sin(theta) reflection coefficients tg = bvav2nuv(bv,av); // tg = vector of tap gains tgr(i) = ba.take(M+2-i,tg); // i=1..M+1 (for "takability") tghr(n) = tgr(n)/pi(n); pi(1) = 1; pi(n) = pi(n-1)*(1+ba.take(M-n+2,sv)); // all sign parameters '+' }; //-----------------------`(fi.)allpassn1mt`-------------------------- // One-multiply lattice allpass with tap lines. Its N+1 outputs are the // allpass output followed by the N tap signals of the lattice (used by // `iir_lat1` to form a general transfer function). // // #### Usage // // ``` // _ : allpassn1mt(N,sv) : si.bus(N+1) // ``` // // Where: // // * `N`: the order of the filter (fixed at compile time) // * `sv`: the reflection coefficients (-1 1) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // si = library("signals.lib"); // src = os.tosc(440); // allpassn1mt_test = src : fi.allpassn1mt(2, (0.3, -0.2)) : si.bus(3); // ``` //------------------------------------------------------------ declare allpassn1mt author "Julius O. Smith III"; declare allpassn1mt copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare allpassn1mt license "LicenseRef-STK-4.3"; allpassn1mt(0,sv) = _; allpassn1mt(n,sv) = _ <: _,_ : ((+:*(s) <: _,_),_ : _,+ : ro.crossnn(1) : allpassn1mt(n-1,sv),_)~(*(-1)) : fsec(n) with { fsec(1) = ro.crossnn(1) : _, (_<:mem,_) : +,_; fsec(n) = ro.crossn1(n) : _, (_<:mem,_),par(i,n-1,_) : +, par(i,n,_); innertaps(n) = par(i,n,_); s = ba.take(n,sv); // reflection coefficient s = sin(theta) }; //-------------------------------`(fi.)iir_nl`------------------------- // Normalized ladder filter of arbitrary order. // // #### Usage // // ``` // _ : iir_nl(bv,av) : _ // ``` // // Where: // // * `bv`: transfer-function numerator // * `av`: transfer-function denominator (monic) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // src = os.tosc(440); // iir_nl_test = src : fi.iir_nl((0.1, 0.2, 0.3), (-0.4, 0.1)); // ``` // // #### References // // * J. D. Markel and A. H. Gray, Linear Prediction of Speech, New York: Springer Verlag, 1976. // * //------------------------------------------------------------ declare iir_nl author "Julius O. Smith III"; declare iir_nl copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare iir_nl license "LicenseRef-STK-4.3"; iir_nl(bv,av) = allpassnnlt(M,sv) : sum(i,M+1,*(tghr(i))) with { M = ba.count(av); sv = av2sv(av); // sv = vector of sin(theta) reflection coefficients tg = bvav2nuv(bv,av); // tg = vector of tap gains for 2mul case tgr(i) = ba.take(M+1-i,tg); tghr(n) = tgr(n)/pi(n); pi(0) = 1; s(n) = ba.take(M-n+1,sv); // reflection coefficient = sin(theta) c(n) = sqrt(max(0,1-s(n)*s(n))); // compiler crashes on sqrt(-) pi(n) = pi(n-1)*c(n); }; //-------------------------------`(fi.)allpassnnlt`------------------------- // Normalized ladder allpass filter of arbitrary order. Its N+1 outputs are // the allpass output followed by the N tap signals of the ladder (used by // `iir_nl` to form a general transfer function). // // #### Usage: // // ``` // _ : allpassnnlt(N,sv) : si.bus(N+1) // ``` // // Where: // // * `N`: the order of the filter (fixed at compile time) // * `sv`: the reflection coefficients (-1 1) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // si = library("signals.lib"); // no = library("noises.lib"); // src = os.tosc(440); // allpassnnlt_test = src : fi.allpassnnlt(2, (0.3, -0.2)) : si.bus(3); // allpassnnlt_product_test = no.noise : fi.allpassnnlt(1, 0.5*hslider("s", 0.6, -1, 1, 0.01)) : si.bus(2); // ``` // // #### References // // * J. D. Markel and A. H. Gray, Linear Prediction of Speech, New York: Springer Verlag, 1976. // * //------------------------------------------------------------ declare allpassnnlt author "Julius O. Smith III"; declare allpassnnlt copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare allpassnnlt license "LicenseRef-STK-4.3"; allpassnnlt(0,sv) = _; allpassnnlt(n,sv) = _ <: *(s),(*(c) : (+ : allpassnnlt(n-1,sv))~(*(-s))) : fsec(n) with { fsec(1) = _, (_<:mem*(c),_) : sumandtaps(n); fsec(n) = _, (_<:mem*(c),_), par(i,n-1,_) : sumandtaps(n); s = ba.take(n,sv); c = sqrt(max(0,1-s*s)); sumandtaps(n) = +,par(i,n,_); }; //=============================Useful Special Cases======================================= //======================================================================================== //--------------------------------`(fi.)tf2np`------------------------------------ // Biquad based on a stable second-order Normalized Ladder Filter // (more robust to modulation than `tf2` and protected against instability). // // #### Usage // // ``` // _ : tf2np(b0,b1,b2,a1,a2) : _ // ``` // // Where: // // * `b0`, `b1`, `b2`: transfer-function numerator coefficients // * `a1`, `a2`: transfer-function denominator coefficients (monic // denominator, `a0 = 1`) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // src = os.tosc(440); // tf2np_test = src : fi.tf2np(0.6, 0.3, 0.2, -0.5, 0.2); // ``` //------------------------------------------------------------ declare tf2np author "Julius O. Smith III"; declare tf2np copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare tf2np license "LicenseRef-STK-4.3"; tf2np(b0,b1,b2,a1,a2) = allpassnnlt(M,sv) : sum(i,M+1,*(tghr(i))) with { smax = 1.0-ma.EPSILON; // maximum reflection-coefficient magnitude allowed s2 = max(-smax, min(smax,a2)); // Project both reflection-coefficients s1 = max(-smax, min(smax,a1/(1+a2))); // into the defined stability-region. sv = (s1,s2); // vector of sin(theta) reflection coefficients M = 2; nu(2) = b2; nu(1) = b1 - b2*a1; nu(0) = (b0-b2*a2) - nu(1)*s1; tg = (nu(0),nu(1),nu(2)); tgr(i) = ba.take(M+1-i,tg); // vector of tap gains for 2mul case tghr(n) = tgr(n)/pi(n); // apply pi parameters for NLF case pi(0) = 1; s(n) = ba.take(M-n+1,sv); c(n) = sqrt(1-s(n)*s(n)); pi(n) = pi(n-1)*c(n); }; //-----------------------------`(fi.)wgr`--------------------------------- // Second-order transformer-normalized digital waveguide resonator. // Its two outputs are in phase quadrature: the first is the cosine-phase // output, the second the sine-phase output (see `os.oscwc`, `os.oscws`). // // #### Usage // // ``` // _ : wgr(f,r) : _,_ // ``` // // Where: // // * `f`: resonance frequency (Hz), between 0 and SR/2 (excluded: the frequency is // kept a hair inside that range so that the amplitude compensation stays finite) // * `r`: loss factor for exponential decay (set to 1 to make a numerically stable oscillator) // // The two outputs are the resonator states, in phase quadrature // (`os.oscwc` uses the first, `os.oscws` the second). // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // wgr_test = fi.wgr(440, 0.995, src); // wgr_slider_test = fi.wgr(hslider("freq", 440, 20, 5000, 1), hslider("r", 0.995, 0.9, 1, 0.001), no.noise); // wgr_modulated_test = fi.wgr(100*pow(20, tri), 0.995, no.noise) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // wgr_zero_freq_test = fi.wgr(1000*(ba.time >= 100), 0.995, no.noise); // ``` // // #### References // // * // * //------------------------------------------------------------ declare wgr author "Julius O. Smith III"; declare wgr copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare wgr license "LicenseRef-STK-4.3"; wgr(f,r,x) = (*(G),_<:_,((+:*(C))<:_,_),_:+,_,_:+(x),-) ~ cross : _,*(0-gi) with { // |C| < 1 keeps gi finite and nonzero at f = 0 and f = SR/2, where G = r*gi'/gi // would otherwise be NaN for good (and where cos rounds to +/-1 in float): C = max(ma.EPSILON-1, min(1-ma.EPSILON, cos(2*ma.PI*f/ma.SR))); gi = sqrt(max(0,(1+C)/(1-C))); // compensate amplitude (only needed when G = r*(1-1' + gi')/gi; // frequency changes substantially) cross = _,_ <: !,_,_,!; }; //-----------------------------`(fi.)nlf2`-------------------------------- // Second order normalized digital waveguide resonator. // Its two outputs are in phase quadrature: the first is the sine-phase // output, the second the cosine-phase output (see `os.oscrs`, `os.oscrc`). // // #### Usage // // ``` // _ : nlf2(f,r) : _,_ // ``` // // Where: // // * `f`: resonance frequency (Hz) // * `r`: loss factor for exponential decay (set to 1 to make a sinusoidal oscillator) // // The two outputs are the resonator states, in phase quadrature // (`os.oscrs` uses the first, `os.oscrc` the second). // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // nlf2_test = fi.nlf2(440, 0.995, src); // nlf2_slider_test = fi.nlf2(hslider("freq", 440, 20, 5000, 1), hslider("r", 0.995, 0.9, 1, 0.001), no.noise); // nlf2_modulated_test = fi.nlf2(100*pow(20, tri), 0.995, no.noise) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // nlf2_jump_test = fi.nlf2(100*pow(20, sq), 0.995, no.noise) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### References // // //------------------------------------------------------------ declare nlf2 author "Julius O. Smith III"; declare nlf2 copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare nlf2 license "LicenseRef-STK-4.3"; nlf2(f,r,x) = ((_<:_,_),(_<:_,_) : (*(s),*(c),*(c),*(0-s)) :> (*(r),+(x))) ~ cross with { th = 2*ma.PI*f/ma.SR; c = cos(th); s = sin(th); cross = _,_ <: !,_,_,!; }; //------------`(fi.)apnl`--------------- // Passive Nonlinear Allpass based on Pierce switching springs idea. // Switch between allpass coefficient `a1` and `a2` at signal zero crossings. // The first-order allpass is realized in normalized (rotation) form, which is // lossless at every sample, so that switching the coefficient can never add // energy; with a constant coefficient its output is that of the usual // first-order allpass (a + z^(-1))/(1 + a z^(-1)). // // #### Usage // // ``` // _ : apnl(a1,a2) : _ // ``` // // Where: // // * `a1`: allpass coefficient used while the filter state is positive (-1 < a1 < 1) // * `a2`: allpass coefficient used otherwise (-1 < a2 < 1) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // no = library("noises.lib"); // src = os.tosc(440); // apnl_test = fi.apnl(0.5, -0.5, src); // apnl_noise_test = fi.apnl(0.9, 0.1, no.noise); // ``` // // #### References // // * "A Passive Nonlinear Digital Filter Design ..." by John R. Pierce and Scott // A. Van Duyne, JASA, vol. 101, no. 2, pp. 1120-1126, 1997 //------------------------------------------------------------ declare apnl author "Julius O. Smith III"; declare apnl copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare apnl license "LicenseRef-STK-4.3"; apnl(a1,a2,x) = loop ~ _ : !,_ with { c1 = sqrt(1 - a1*a1); c2 = sqrt(1 - a2*a2); // state v and output y from the input x and the previous state vp, as a // plane rotation by the angle whose sine is the current coefficient: loop(vp) = c*x - s*vp, s*x + c*vp with { s = select2(vp > 0, a2, a1); c = select2(vp > 0, c2, c1); }; }; //============================Ladder/Lattice Allpass Filters============================== // An allpass filter has gain 1 at every frequency, but variable phase. // Ladder/lattice allpass filters are specified by reflection coefficients. // They are defined here as nested allpass filters, hence the names `allpassn*`. // // #### References // // * // * // * Linear Prediction of Speech, Markel and Gray, Springer Verlag, 1976 //======================================================================================== //-----------------------`(fi.)scatN`-------------------------- // N-port scattering junction. // // #### Usage // // ``` // si.bus(N) : scatN(N,av,filter) : si.bus(N) // ``` // // Where: // // * `N`: number of incoming/outgoing waves // * `av`: vector (list) of `N` alpha parameters (each between 0 and 2, and normally summing to 2): // * `filter` : optional junction filter to apply (`_` for none, see below) // // With no filter: // // - The junction is _lossless_ when the alpha parameters sum to 2 ("allpass"). // - The junction is _passive_ but lossy when the alpha parameters sum to less than 2 ("resistive loss"). // - Dynamic and reactive junctions are obtained using the `filter` argument. // For guaranteed stability, the filter should be _positive real_. (See 2nd ref. below). // // For \(N=2\) (two-port scattering), the reflection coefficient \(\rho\) corresponds // to alpha parameters \(1\pm\rho\). // // #### Example: Whacky echo chamber made of 16 lossless "acoustic tubes": // // ``` // process = _ : *(1.0/sqrt(16)) <: daisyRev(16,2,0.9999) :> _,_ with { // daisyRev(N,Dp2,G) = si.bus(N) : (si.bus(2*N) :> si.bus(N) // : fi.scatN(N, par(i,N,2*G/float(N)), fi.lowpass(1,5000.0)) // : par(i,N,de.delay(DS(i),DS(i)-1))) ~ si.bus(N) with { DS(i) = 2^(Dp2+i); }; // }; // ``` // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // dual_src = os.tosc(440), os.tosc(660); // scatN_test = dual_src : fi.scatN(2, (1, 1), _); // ``` // // #### References // // * // * // * //------------------------------------------------------------ declare scatN author "Julius O. Smith III"; declare scatN copyright "Copyright (C) 2024 by Julius O. Smith III "; declare scatN license "LicenseRef-STK-4.3"; scatN(0,av,filter) = !; scatN(N,av,filter) = incomingWaves <: (junctionSum : filter <: si.bus(N)), par(i,N,*(-1)) :> si.bus(N) with { incomingWaves = si.bus(N); alpha(i) = ba.take(i+1,av); junctionSum = par(i,N,*(alpha(i))) :> _; // Junction velocity/pressure for series/parallel junction }; //---------------`(fi.)scat`----------------- // Scatter off of reflectance r with reflection coefficient s. // // #### Usage: // // ``` // _ : scat(s,r) : _ // ``` // Where: // // * `s`: reflection coefficient between -1 and 1 for stability // * `r`: single-input, single-output block diagram, // having gain less than 1 at all frequencies for stability. // // #### Example: the following program should produce all zeros: // // ``` // process = _ <: fi.allpassn(3,(.3,.2,.1)), fi.scat(.1, fi.scat(.2, fi.scat(.3, _))) // : - : ^(2) : +~_; // ``` // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // src = os.tosc(440); // scat_test = src : fi.scat(0.5, _); // ``` // // #### References // // * //---------------------------------------------- declare scat author "Julius O. Smith III"; declare scat copyright "Copyright (C) 2024 by Julius O. Smith III "; declare scat license "LicenseRef-STK-4.3"; scat(s,r) = _ <: ((+ <: (r,*(s)))~(*(-s))) : _',_ :+; //---------------`(fi.)allpassn`----------------- // Two-multiply lattice filter. // // #### Usage: // // ``` // _ : allpassn(n,sv) : _ // ``` // Where: // // * `n`: the order of the filter // * `sv`: the reflection coefficients `(s1,s2,...,sN)`, each between -1 and 1 // // Equivalent to `fi.allpassnt(n,sv) : _, par(i,n,!);` // // Equivalent to `fi.scat( s(n), fi.scat( s(n-1), ..., fi.scat( s(1), _ ))) // with { s(k) = ba.take(k,sv); } ;` // // Identical to `allpassn` in `old/filter.lib`. // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // src = os.tosc(440); // allpassn_test = src : fi.allpassn(3, (0.3, 0.2, 0.1)); // ``` // // #### References // // * J. D. Markel and A. H. Gray: Linear Prediction of Speech, New York: Springer Verlag, 1976. // * //---------------------------------------------- declare allpassn author "Julius O. Smith III"; declare allpassn copyright "Copyright (c) 2003-2024 by Julius O. Smith III "; declare allpassn license "LicenseRef-STK-4.3"; allpassn(0,sv) = _; allpassn(n,sv) = _ <: ((+ <: (allpassn(n-1,sv)),*(s))~(*(-s))) : _',_ :+ with { s = ba.take(n,sv); }; //---------------`(fi.)allpassnn`----------------- // Normalized form - four multiplies and two adds per section, // but coefficients can be time varying and nonlinear without // "parametric amplification" (modulation of signal energy). // // #### Usage: // // ``` // _ : allpassnn(n,tv) : _ // ``` // // Where: // // * `n`: the order of the filter // * `tv`: the `n` section angles in radians (not reflection coefficients): // section `i` has reflection coefficient sin(t_i) and uses cos(t_i) as its // tap gain, so that `allpassnn(n,tv)` has the transfer function of // `allpassn(n,sv)` with sv_i = sin(t_i). Any real angle gives a stable // filter; angles in (-PI/2, PI/2) cover every reflection coefficient in (-1, 1). // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // src = os.tosc(440); // allpassnn_test = src : fi.allpassnn(3, (0.3, 0.2, 0.1)); // ``` //---------------------------------------------- // power-normalized (reflection coefficients s = sin(t)): declare allpassnn author "Julius O. Smith III"; declare allpassnn copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare allpassnn license "LicenseRef-STK-4.3"; allpassnn(0,tv) = _; allpassnn(n,tv) = _ <: *(s), (*(c) : (+ : allpassnn(n-1,tv))~(*(-s))) : _, mem*c : + with { c = cos(ba.take(n,tv)); s = sin(ba.take(n,tv)); }; //---------------`(fi.)allpassnkl`----------------- // Kelly-Lochbaum form - four multiplies and two adds per // section, but all signals have an immediate physical // interpretation as traveling pressure waves, etc. // // #### Usage: // // ``` // _ : allpassnkl(n,sv) : _ // ``` // // Where: // // * `n`: the order of the filter // * `sv`: the reflection coefficients (-1 1) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // src = os.tosc(440); // allpassnkl_test = src : fi.allpassnkl(3, (0.3, 0.2, 0.1)); // ``` //---------------------------------------------- // Kelly-Lochbaum: declare allpassnkl author "Julius O. Smith III"; declare allpassnkl copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare allpassnkl license "LicenseRef-STK-4.3"; allpassnkl(0,sv) = _; allpassnkl(n,sv) = _ <: *(s),(*(1+s) : (+ : allpassnkl(n-1,sv))~(*(-s))) : _, mem*(1-s) : + with { s = ba.take(n,sv); }; //---------------`(fi.)allpassn1m`----------------- // One-multiply form - one multiply and three adds per section. // Normally the most efficient in special-purpose hardware. // // #### Usage: // // ``` // _ : allpassn1m(n,sv) : _ // ``` // // Where: // // * `n`: the order of the filter // * `sv`: the reflection coefficients (-1 1) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // src = os.tosc(440); // allpassn1m_test = src : fi.allpassn1m(3, (0.3, 0.2, 0.1)); // ``` //---------------------------------------------- // one-multiply: declare allpassn1m author "Julius O. Smith III"; declare allpassn1m copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare allpassn1m license "LicenseRef-STK-4.3"; allpassn1m(0,sv) = _; allpassn1m(n,sv) = _ <: _,_ : ((+:*(s) <: _,_),_ : _,+ : cross : allpassn1m(n-1,sv),_)~(*(-1)) : _',_ : + with { s = ba.take(n,sv); cross = _,_ <: !,_,_,!; }; //==========================Clipping-Prevention Allpass Filters=========================== // First- and second-order allpass filters whose coefficients may change at // any time without the output exceeding a threshold. When a coefficient change // would make the output exceed the threshold, the change is limited for as // long as needed, and the coefficient then catches up with its target. When // the plain direct-form allpass stays within the threshold, the output is // the same as its output. // // This is a different protection from the energy-preserving time-varying // allpasses (`fi.allpassnt` and the other normalized-ladder forms): those // keep the signal energy under control, but their output can still exceed a // given level after a coefficient change. // // #### References // // * F. Fontana, S. Pasin, A. Bernardini and S. D'Angelo, "A Clipping Prevention Method for All-Pass Digital Filters with Time-Varying Coefficients", Proc. DAFx26, Cambridge, MA, USA, 2026, pp. 364-371: //======================================================================================== //-------------------`(fi.)allpass1_noclip`, `(fi.)allpass2_noclip`----------------------- // First- and second-order direct-form allpass filters with clipping prevention // (Fontana et al., DAFx26, Algorithms 1 and 2). At each sample the filter // computes the output it would give without the coefficient change, and // limits the change so that the output stays within [-M, M]. The limiting // acts whenever the plain allpass output would exceed M: mostly just after a // coefficient change, but also with constant coefficients when the input is // near M. Once the output allows it, the coefficients return to their targets. // // `allpass1_noclip` computes y[n] = c (x[n] - y[n-1]) + x[n-1], i.e. // `fi.tf1(c, 1, c)`. `allpass2_noclip` computes // y[n] = c0 (x[n] - y[n-2]) + c1 (x[n-1] - y[n-1]) + x[n-2], i.e. // `fi.tf2(c0, c1, 1, c1, c0)`, and gives each coefficient half of the margin. // // When the input stays within [-M, M], so does the output, up to rounding (with // M = 1, the measured peaks stay below 1 + 1e-15 in double and 1 + 5e-7 in // float). An input that exceeds M is not limited: the output stays within // max(M, |x[n-1]|) for the first order and max(M, |x[n-2]|) for the second. The // modified coefficients are stable whenever the targets are (|c| < 1; |c0| < 1 // and |c1| < 1 + c0), whatever the input. In the first order the coefficient in // use lies between 0 and its target. In the second order, when limiting each // coefficient separately (as in the reference) would give an unstable pair, the // targets are scaled toward 0 instead. The filter starts with the target // coefficients. Only the allpass output is bounded: a structure that mixes it // with its input (a phaser, a Regalia-Mitra equalizer) can still exceed M. // // Precision: each limiting decision is a comparison, which can go one way in // single precision and the other in double. The two outputs then differ until // the difference decays at the rate of the poles, so with poles near the unit // circle and long stretches of limiting they can differ by up to 2M sample by // sample, both staying within [-M, M]. With noise in and (c0, c1) alternating // every 50 ms between (0.98, -1.96) and (-0.98, 0.01) (pole radius 0.99), the // RMS levels of the single- and double-precision outputs differ by up to 2.1e-3 // at full-scale input and 9e-3 at 0.3, from 44.1 to 192 kHz. With pairs of pole // radius 0.9 (`allpass2_noclip_jump_test`), the outputs differ by less than 2e-5. // // #### Usage // // ``` // _ : allpass1_noclip(M, c) : _ // _ : allpass2_noclip(M, c0, c1) : _ // ``` // // Where: // // * `M`: output threshold (e.g. 1) // * `c`: coefficient of the first-order allpass (target, may change at any time) // * `c0`, `c1`: coefficients of the second-order allpass (targets, may change // at any time) // // #### Test // ``` // ba = library("basics.lib"); // fi = library("filters.lib"); // ma = library("maths.lib"); // no = library("noises.lib"); // // constant coefficients, input low enough that the output never reaches M: // // same output as fi.tf1(0.5, 1, 0.5) and fi.tf2(0.81, -1.27, 1, -1.27, 0.81) // allpass1_noclip_test = no.noise * 0.5 : fi.allpass1_noclip(1, 0.5); // allpass2_noclip_test = no.noise * 0.3 : fi.allpass2_noclip(1, 0.81, -1.27); // // full-scale noise, coefficients from sliders // allpass1_noclip_slider_test = no.noise : fi.allpass1_noclip(M, c) // with { // M = hslider("M", 1, 0.1, 1, 0.01); // c = hslider("c", 0.5, -0.99, 0.99, 0.01); // }; // allpass2_noclip_slider_test = no.noise : fi.allpass2_noclip(M, c0, c1) // with { // M = hslider("M", 1, 0.1, 1, 0.01); // c0 = hslider("c0", 0.81, -0.99, 0.99, 0.01); // c1 = hslider("c1", -1.27, -1.98, 1.98, 0.01); // }; // // full-scale noise, coefficients jumping every 0.5 ms (24 samples at 48 kHz) // allpass1_noclip_modulated_test = no.noise : fi.allpass1_noclip(1, c) // with { P = int(ma.SR/2000); c = select2(ba.pulsen(P, 2*P), 0.99, -0.99); }; // allpass2_noclip_modulated_test = no.noise : fi.allpass2_noclip(1, c0, c1) // with { // P = int(ma.SR/2000); // jump = ba.pulsen(P, 2*P); // c0 = select2(jump, 0.98, -0.98); // c1 = select2(jump, -1.96, 0.01); // }; // // coefficients jumping at 10 Hz: c between the ends of its range, and (c0, c1) // // between two stable pairs of pole radius 0.9 // allpass1_noclip_jump_test = no.noise : fi.allpass1_noclip(1, c) // with { P = int(ma.SR/10); c = select2(ba.period(2*P) < P, -0.99, 0.99); }; // allpass2_noclip_jump_test = no.noise : fi.allpass2_noclip(1, c0, c1) // with { // P = int(ma.SR/10); // jump = ba.period(2*P) < P; // c0 = select2(jump, 0.81, -0.81); // c1 = select2(jump, -1.27, 0.01); // }; // ``` // // #### References // // * F. Fontana, S. Pasin, A. Bernardini and S. D'Angelo, "A Clipping Prevention Method for All-Pass Digital Filters with Time-Varying Coefficients", Proc. DAFx26, Cambridge, MA, USA, 2026, pp. 364-371: //---------------------------------------------------------------------------------------- declare allpass1_noclip author "David Braun"; declare allpass1_noclip license "MIT"; allpass1_noclip(M, c, x) = (step ~ (_, _)) : !, _ with { // state: the coefficient in use and y[n-1] step(cp, y1) = cn, cn * d + x' with { ca = select2(1 - 1', cp, c); // at the first sample, start from the target d = x - y1; w = ca * d + x'; // output with the previous coefficient // bound max(M, |x[n-1]|): c = 0 (y = x[n-1]) is always feasible cn = ca + _apnoclip_limit(1, max(M, abs(x')), w, d, c - ca); }; }; declare allpass2_noclip author "David Braun"; declare allpass2_noclip license "MIT"; allpass2_noclip(M, c0, c1, x) = (step ~ (_, _, _)) : !, !, _ with { // state: the two coefficients in use and y[n-1] step(cp0, cp1, y1) = cn0, cn1, cn0 * d0 + cn1 * d1 + x'' with { ca0 = select2(1 - 1', cp0, c0); // at the first sample, start from the targets ca1 = select2(1 - 1', cp1, c1); d0 = x - y1'; d1 = x' - y1; w = ca0 * d0 + ca1 * d1 + x''; // output with the previous coefficients // Algorithm 2 of the reference p0 = ca0 + _apnoclip_limit(0.5, M, w, d0, c0 - ca0); p1 = ca1 + _apnoclip_limit(0.5, M, w, d1, c1 - ca1); // Each coefficient is limited on its own, so the pair can leave the // stability triangle and drift (under full-scale input with fast // changes). Then use the targets scaled toward (0, 0) by the largest // factor l in [0, 1] with |l*(c0*d0 + c1*d1) + x[n-2]| <= M: the scaled // pair is stable, and l = 0 gives y = x[n-2]. stable = (abs(p0) < 1) & (abs(p1) < 1 + p0); tc = c0 * d0 + c1 * d1; Mx = max(M, abs(x'')); tcs = select2(tc == 0, tc, 1); rtc = 1 / tcs : min(ma.MAX); // min keeps one division l = select2(tc == 0, min(1, max((0 - Mx - x'') * rtc, (Mx - x'') * rtc)), 1); cn0 = select2(stable, l * c0, p0); cn1 = select2(stable, l * c1, p1); }; }; // Coefficient change dc limited so that |share*w + dc*d| <= share*M (eqs. 4-7 and // 11-15 of the reference): dc clamped between -share(M+w)/d and share(M-w)/d. // When d = 0 the change does not affect the output and passes. _apnoclip_limit(share, M, w, d, dc) = select2(d == 0, min(hi, max(lo, dc)), dc) with { ds = select2(d == 0, d, 1); // select2 evaluates both branches // one division; min stops the normalizer from turning x*(1/ds) back into x/ds r = share / ds : min(ma.MAX); b0 = (0 - M - w) * r; b1 = (M - w) * r; lo = min(b0, b1); hi = max(b0, b1); }; //===========Digital Filter Sections Specified as Analog Filter Sections================== // The functions of this section take an analog transfer function `B(s)/A(s)` // and digitize it with the bilinear transform. How the digital filter is then // computed matters as much as its transfer function: two realizations of the // same filter can differ by orders of magnitude in single precision, and when // their coefficients change. `tf2s` and `tf3slf` use trapezoidal integrators // in the analog block diagram (a topology-preserving realization), `tf2sb` // and `tf1sb` trapezoidal state-variable sections, and `tf2snp` and `tf1snp` // protected normalized ladders. // // For an online tutorial deriving a state-variable filter with trapezoidal // integrators, see the first reference below. // // Their frequency parameters accept any value. A frequency of 0 (or below) // makes every integrator gain 0, so the states hold: a lowpass then holds // its last output, a highpass passes its input minus the DC its states hold, // and a band filter whose lower edge is 0 becomes the lowpass at its upper // edge (a bandstop the highpass). A frequency at or above 0.499*SR acts as // 0.499*SR: at Nyquist the bilinear-transform constant `tan(w*T/2)` is // infinite, above it negative (an unstable filter), and short of it the // integrator gains grow as `1/(SR/2 - f)`, which costs precision in float. // At 0.499*SR the single-precision outputs are within 7e-4 (RMS) of double // precision (1e-3 for `peak_eq`), from 44.1 to 192 kHz. A frequency can be // swept down to 0 or jump to and from 0 without a click or a spike. // // #### References // // * Julius O. Smith III, "Digital State-Variable Filters" (including the // trapezoidal state-variable filter used here): // // * Vadim Zavalishin, "The Art of VA Filter Design", revision 2.1.2, 2020: // // * // * // * //======================================================================================== //-------------------------`(fi.)tf2s`, `(fi.)tf2snp`-------------------------------- // Second-order digital filter, // specified by ANALOG transfer-function polynomials B(s)/A(s), // and a frequency-scaling parameter. Digitization via the // bilinear transform is built in. `tf2snp` computes the same filter as a // protected normalized ladder. The analog transfer function is: // // ``` // b2 s^2 + b1 s + b0 // H(s) = -------------------- // s^2 + a1 s + a0 // ``` // // #### Usage // // ``` // _ : tf2s(b2,b1,b0,a1,a0,w1) : _ // _ : tf2snp(b2,b1,b0,a1,a0,w1) : _ // ``` // // Where: // // * `b2`, `b1`, `b0`: numerator coefficients of the analog transfer function `H(s)` // * `a1`, `a0`: denominator coefficients of `H(s)` (monic denominator, the // `s^2` coefficient being 1) // * `w1`: the desired digital frequency (in radians/second) // corresponding to analog frequency 1 rad/sec (i.e., `s = j`). // `w1` <= 0 holds the filter's states, and `w1` above `0.998*PI*SR` // (0.499*SR in Hz) acts as that value (see the section introduction) // // #### Example test program // // A second-order ANALOG Butterworth lowpass filter, // normalized to have cutoff frequency at 1 rad/sec, // has transfer function: // // ``` // 1 // H(s) = ----------------- // s^2 + a1 s + 1 // ``` // // where `a1 = sqrt(2)`. Therefore, a DIGITAL Butterworth lowpass // cutting off at `SR/4` is specified as `tf2s(0,0,1,sqrt(2),1,PI*SR/2);` // // #### Method // // Bilinear transform scaled for exact mapping of w1. `tf2s` realizes it // with two trapezoidal integrators (the bilinear transform is trapezoidal // integration) of gain `tan(w1*T/2)`, in controllable canonical form: the // integrators compute `V = X/A(s)` and its derivative, the delay-free loop // is solved in closed form for `u = s^2*V`, and the output taps // `y = b2*u + b1*s*V + b0*V` follow, as in the direct form (poles first, // numerator last: a gain common to the `b`s, such as `resonbp`'s, multiplies // the output exactly, even when it varies). The transfer function is the // same as that of the direct-form biquad, but the poles stay accurate in // single precision when `w1` is small relative to the sample rate, where // the direct form's cluster near z = 1 and lose their digits (a 20 Hz // Butterworth lowpass had a 16 % RMS error at 192 kHz, and a 5 Hz one // diverged: issue #263). Near Nyquist its errors stay close to the direct // form's: at 44.1 kHz with `w1` at 21 kHz, an RMS error of 1.9e-6 instead of // 9.5e-7, and a magnitude-response error of 3e-4 dB instead of 5e-3 dB. // The integrators act on `s/w`, where `w = max(|a1|, sqrt(|a0|))` is // a frequency scale of the denominator, so that every state stays at the // output level when the coefficients change: `a1` and `a0` must not both // be zero. // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ma = library("maths.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // src = os.tosc(440); // tf2s_test = src : fi.tf2s(0, 0, 1, sqrt(2), 1, ma.PI*ma.SR/2); // tf2s_lp20_test = no.noise : fi.tf2s(0, 0, 1, sqrt(2), 1, 2*ma.PI*20); // tf2s_lp5_test = no.noise : fi.tf2s(0, 0, 1, sqrt(2), 1, 2*ma.PI*5); // tf2s_notch50_test = no.noise : fi.tf2s(1, 0, 1, 0.1, 1, 2*ma.PI*50); // tf2s_slider_test = no.noise : fi.tf2s(0, 0, 1, sqrt(2), 1, 2*ma.PI*hslider("fc", 1000, 20, 20000, 1)); // tf2s_modulated_test = no.noise : fi.tf2s(0, 0, 1, sqrt(2), 1, 2*ma.PI*20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // tf2s_jump_test = no.noise : fi.tf2s(0, 0, 1, sqrt(2), 1, 2*ma.PI*20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // tf2snp_test = src : fi.tf2snp(0, 0, 1, sqrt(2), 1, ma.PI*ma.SR/2); // tf2snp_lowfc_test = no.noise : fi.tf2snp(0, 0, 1, sqrt(2), 1, 2*ma.PI*20); // tf2snp_hp_lowfc_test = no.noise : fi.tf2snp(1, 0, 0, sqrt(2), 1, 2*ma.PI*10.1); // tf2s_nyquist_test = no.noise : fi.tf2s(0, 0, 1, sqrt(2), 1, 2*ma.PI*0.6*ma.SR); // tf2s_zero_freq_test = no.noise : fi.tf2s(1, 0, 0, sqrt(2), 1, 2*ma.PI*(1000*max(0, 1 - ba.time/12000) + 1000*(ba.time >= 24000))); // tf2s_audio_modulated_test = 0.1*no.noise : fi.tf2s(1, 1, 1, 0.1, 1, 2*ma.PI*max(20, 1000*(1 + 0.9*os.tosc(500)))); // ``` // // #### References // // * //---------------------------------------------- declare tf2s author "Julius O. Smith III"; declare tf2s copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare tf2s license "LicenseRef-STK-4.3"; tf2s(b2,b1,b0,a1,a0,w1) = tick ~ (_,_) : !,!,_ with { w = max(abs(a1), sqrt(abs(a0))); // frequency scale of the denominator w2 = w*w; g = w*_tanhalf(w1); // trapezoidal integrators of s/w, prewarped at w1 // states: integrators of u = (s/w)^2*V and (s/w)*V, with V = X/A(s) tick(s1, s0, x) = 2*v1 - s1, 2*v0 - s0, y with { // times the reciprocal, kept off the states' critical path (see svf) u = (x - a1/w*s1 - a0/w2*(g*s1 + s0))*(1/(1 + g*(a1/w + g*a0/w2)) : min(ma.MAX)); v1 = g*u + s1; v0 = g*v1 + s0; y = b2*u + b1/w*v1 + b0/w2*v0; }; }; // tf2snp = tf2s but using a protected normalized ladder filter for tf2. // It computes the tf2np ladder (same reflection coefficients s, same tap gains) // from the analog coefficients directly: each s, its complement c = sqrt(1-s^2) // and the tap gains are written so that nothing cancels, which keeps the filter // accurate in single precision when w1 is small relative to ma.SR (both s // approach +-1 there, and forming c = sqrt(1-s*s) from a rounded s would lose // most of its digits). With t = tan(w1/(2*ma.SR)), the bilinear transform gives // the digital coefficients over D = a0*t^2 + a1*t + 1. The ladder itself still // needs 1+s1 = 2*a0*t^2/(1+a0*t^2) to be representable next to 1: in single // precision that holds while a0*t^2 is well above ma.EPSILON (for a0 = 1, down to // about 20 Hz at 192 kHz). For lower w1/ma.SR, tf2s keeps its accuracy. declare tf2snp author "Julius O. Smith III"; declare tf2snp copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare tf2snp license "LicenseRef-STK-4.3"; tf2snp(b2,b1,b0,a1,a0,w1) = _allpassnnltc(2,(s1,s2),(c1,c2)) : sum(i,3,*(tghr(i))) with { smax = 1.0-ma.EPSILON; // maximum reflection-coefficient magnitude allowed, as in tf2np cmin = sqrt(1.0-smax*smax); // complement of smax t = _tanhalf(w1); A0 = a0*t*t; A1 = a1*t; B0 = b0*t*t; B1 = b1*t; B2 = b2; D = A0 + A1 + 1; // s2 = a2d and s1 = a1d/(1+a2d), projected into the stability region as in // tf2np; c2, c1 from (1-s)(1+s) in closed form, never below cmin, so that a // projected s keeps a consistent c: s2 = max(-smax, min(smax, (A0 - A1 + 1)/D)); s1 = max(-smax, min(smax, (A0 - 1)/(A0 + 1))); c2 = max(cmin, 2*sqrt(max(0, A1*(A0 + 1)))/D); c1 = max(cmin, 2*sqrt(max(0, A0))/(A0 + 1)); // tap gains nu2 = b2d, nu1 = b1d - b2d*a1d, nu0 = b0d - b2d*a2d - nu1*s1: nu2 = (B0 - B1 + B2)/D; nu1 = 2*(B0*A1 + 2*B0 - 2*B2*A0 - B2*A1 + B1*A0 - B1)/(D*D); nu0 = 4*(B0*(A1 + 1 - A0) + B2*A0*(A1 + A0 - 1) + 2*B1*A0)/(D*D*(A0 + 1)); // scaled by the ladder's pi parameters (1, c2, c2*c1), as in tf2np: tghr(0) = nu2; tghr(1) = nu1/c2; tghr(2) = nu0/(c2*c1); }; // allpassnnlt with each section's complement c = sqrt(1-s^2) supplied in cv // instead of computed from s (for tf2snp and tf1snp): _allpassnnltc(0,sv,cv) = _; _allpassnnltc(n,sv,cv) = _ <: *(s),(*(c) : (+ : _allpassnnltc(n-1,sv,cv))~(*(0-s))) : fsec(n) with { fsec(1) = _, (_<:mem*(c),_) : sumandtaps(n); fsec(n) = _, (_<:mem*(c),_), par(i,n-1,_) : sumandtaps(n); s = ba.take(n,sv); c = ba.take(n,cv); sumandtaps(n) = +,par(i,n,_); }; //-----------------------------`(fi.)tf1snp`------------------------------- // First-order special case of tf2snp above: a first-order normalized ladder, // with its reflection coefficient, complement and tap gains computed from the // analog coefficients without cancellation, like tf2snp. // // #### Usage // // ``` // _ : tf1snp(b1,b0,a0,w1) : _ // ``` // // Where: // // * `b1`: analog numerator coefficient for `s` // * `b0`: analog numerator constant coefficient // * `a0`: analog denominator constant coefficient // * `w1`: desired digital frequency (radians/second) corresponding to analog frequency `1 rad/sec` // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // no = library("noises.lib"); // ba = library("basics.lib"); // tf1snp_test = src : fi.tf1snp(0, 1, 1, ma.PI*ma.SR/2); // tf1snp_lowfc_test = no.noise : fi.tf1snp(0, 1, 1, 2*ma.PI*10.1); // tf1snp_slider_test = no.noise : fi.tf1snp(0, 1, 1, 2*ma.PI*hslider("fc", 1000, 20, 20000, 1)); // tf1snp_modulated_test = no.noise : fi.tf1snp(0, 1, 1, 2*ma.PI*20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // tf1snp_jump_test = no.noise : fi.tf1snp(0, 1, 1, 2*ma.PI*20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //---------------------------------------------- declare tf1snp author "Julius O. Smith III"; declare tf1snp copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare tf1snp license "LicenseRef-STK-4.3"; tf1snp(b1,b0,a0,w1) = _allpassnnltc(1,s1,c1) : *(nu1), *(nu0/c1) :> _ with { smax = 1.0-ma.EPSILON; cmin = sqrt(1.0-smax*smax); t = _tanhalf(w1); A0 = a0*t; D = A0 + 1; // s1 = a1d, c1 from (1-s1)(1+s1) in closed form; taps nu1 = b1d, nu0 = b0d - b1d*a1d: s1 = max(-smax, min(smax, (A0 - 1)/D)); c1 = max(cmin, 2*sqrt(max(0, A0))/D); nu1 = (b0*t - b1)/D; nu0 = 2*t*(b0 + a0*b1)/(D*D); }; //-----------------------------`(fi.)tf3slf`------------------------------- // Analogous to `tf2s` above, but third order, and using the typical // low-frequency-matching bilinear-transform constant 2/T ("lf" series) // instead of the specific-frequency-matching value used in `tf2s` and `tf1s`. // Note the lack of a "w1" argument. // // #### Usage // // ``` // _ : tf3slf(b3,b2,b1,b0,a3,a2,a1,a0) : _ // ``` // // Where: // // * `b3`: analog numerator coefficient of `s^3` // * `b2`: analog numerator coefficient of `s^2` // * `b1`: analog numerator coefficient of `s` // * `b0`: analog numerator constant term // * `a3`: analog denominator coefficient of `s^3` (nonzero: for a // second-order section, use `tf2s`) // * `a2`: analog denominator coefficient of `s^2` // * `a1`: analog denominator coefficient of `s` // * `a0`: analog denominator constant term // // #### Method // // The bilinear transform is trapezoidal integration, so the analog section // is realized with three trapezoidal integrators (`g = T/2`), in controllable // canonical form: the integrators compute `V = X/A(s)` and its derivatives, // the delay-free loop is solved in closed form for the highest one, // `u = s^3*V`, and the output taps `y = b3*u + b2*s^2*V + b1*s*V + b0*V` follow, // as in the direct form (poles first, numerator last: a gain common to the // `b`s multiplies the output exactly, even when it varies). The transfer // function is the same as that of a third-order direct form with `c = 2*SR`, // but the coefficients enter as they are rather than as sums of `a_k*c^k`, // so the poles stay accurate in single precision when they are close to // z = 1 (cutoffs low relative to the sample rate), where the direct form's // become non-finite (a 20 Hz Butterworth section at 44.1 kHz, for example: // issue #263). The integrators act on `s/w`, where // `w = max(|a2/a3|, sqrt(|a1/a3|), cbrt(|a0/a3|))` is a frequency scale of // the denominator (its largest pole magnitude lies between `w/3` and `2*w`), // so that every state stays at the output level: when the coefficients jump // (a cutoff switching between 50 Hz and 5 kHz), the output stays at the // level of `lowpass(3, fc)`'s, where an unnormalized realization peaks // thousands of times higher. // // #### Test // ``` // ba = library("basics.lib"); // fi = library("filters.lib"); // ma = library("maths.lib"); // no = library("noises.lib"); // os = library("oscillators.lib"); // src = os.tosc(440); // tf3slf_test = src : fi.tf3slf(0, 0, 0, 1, 1, 2, 2, 1); // tf3slf_lp20_test = no.noise : fi.tf3slf(0, 0, 0, w^3, 1, 2*w, 2*w^2, w^3) with { w = 2*ma.PI*20; }; // tf3slf_hp20_test = no.noise : fi.tf3slf(1, 0, 0, 0, 1, 2*w, 2*w^2, w^3) with { w = 2*ma.PI*20; }; // tf3slf_lp1k_test = no.noise : fi.tf3slf(0, 0, 0, w^3, 1, 2*w, 2*w^2, w^3) with { w = 2*ma.PI*1000; }; // tf3slf_slider_test = no.noise : fi.tf3slf(0, 0, 0, w^3, 1, 2*w, 2*w^2, w^3) with { w = 2*ma.PI*hslider("fc", 1000, 20, 20000, 1); }; // tf3slf_modulated_test = no.noise : fi.tf3slf(0, 0, 0, w^3, 1, 2*w, 2*w^2, w^3) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); w = 2*ma.PI*20*pow(250, tri); }; // tf3slf_jump_test = no.noise : fi.tf3slf(0, 0, 0, w^3, 1, 2*w, 2*w^2, w^3) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; w = 2*ma.PI*20*pow(250, sq); }; // ``` //---------------------------------------------- declare tf3slf author "Julius O. Smith III"; declare tf3slf copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare tf3slf license "LicenseRef-STK-4.3"; tf3slf(b3,b2,b1,b0,a3,a2,a1,a0) = tick ~ (_,_,_) : !,!,!,_ with { // frequency scale of the denominator: its largest pole magnitude is between w/3 and 2*w w = max(abs(a2/a3), max(sqrt(abs(a1/a3)), pow(abs(a0/a3), 1.0/3))); g = w*0.5/ma.SR; // trapezoidal integrators of s/w = bilinear transform with c = 2*SR w2 = w*w; w3 = w2*w; // states: integrators of u = (s/w)^3*V, (s/w)^2*V, (s/w)*V, with V = X/A(s) tick(s2, s1, s0, x) = 2*v2 - s2, 2*v1 - s1, 2*v0 - s0, y with { u = (x - a2/w*s2 - a1/w2*(g*s2 + s1) - a0/w3*(g*(g*s2 + s1) + s0)) / (a3 + g*(a2/w + g*(a1/w2 + g*a0/w3))); v2 = g*u + s2; v1 = g*v2 + s1; v0 = g*v1 + s0; y = b3*u + b2/w*v2 + b1/w2*v1 + b0/w3*v0; }; }; //-----------------------------`(fi.)tf1s`-------------------------------- // First-order direct-form digital filter, // specified by ANALOG transfer-function polynomials B(s)/A(s), // and a frequency-scaling parameter. // // #### Usage // // ``` // _ : tf1s(b1,b0,a0,w1) : _ // ``` // // Where: // // * `b1`: analog numerator coefficient of `s` // * `b0`: analog numerator constant term // * `a0`: analog denominator constant term in `s + a0` // * `w1`: desired digital frequency in radians per second corresponding to analog frequency `1 rad/s`; // `w1` <= 0 holds the filter's state, and `w1` above `0.998*PI*SR` (0.499*SR in Hz) // acts as that value (see the section introduction) // // Where: // // b1 s + b0 // H(s) = ---------- // s + a0 // // and `w1` is the desired digital frequency (in radians/second) // corresponding to analog frequency 1 rad/sec (i.e., `s = j`). // // #### Example test program // // A first-order ANALOG Butterworth lowpass filter, // normalized to have cutoff frequency at 1 rad/sec, // has transfer function: // // 1 // H(s) = ------- // s + 1 // // so `b0 = a0 = 1` and `b1 = 0`. Therefore, a DIGITAL first-order // Butterworth lowpass with gain -3dB at `SR/4` is specified as // // ``` // tf1s(0,1,1,PI*SR/2); // digital half-band order 1 Butterworth // ``` // // #### Method // // Bilinear transform scaled for exact mapping of w1, computed with one // trapezoidal integrator of gain `tan(w1*T/2)`, as in `tf2s`: the integrator // computes `V = X/(s + a0)` (normalized by `w = |a0|`, or 1 if `a0` is 0), // and the output is `b1*s*V + b0*V`. The direct form it replaces divided by // `tan(w1*T/2)`, which is 0 at `w1` = 0 (inf/inf, NaN for good), and its // pole near z = 1 lost digits in float at a low `w1`: `lowpass(1, fc)` was up // to 2.2e-3 (RMS) off its double-precision output for `fc` from 1e-6 to // 10 Hz (44.1 to 192 kHz), and is now within 7.7e-6. With fixed // coefficients the outputs agree within 1.6e-14 in double precision; with a // modulated `w1` they differ as two time-varying realizations do. // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ma = library("maths.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // src = os.tosc(440); // tf1s_test = src : fi.tf1s(0, 1, 1, ma.PI*ma.SR/2); // tf1s_zero_freq_test = no.noise : fi.tf1s(1, 0, 1, 2*ma.PI*1000*(ba.time >= 100)); // tf1s_nyquist_test = no.noise : fi.tf1s(0, 1, 1, 2*ma.PI*0.6*ma.SR); // tf1s_slider_test = no.noise : fi.tf1s(0, 1, 1, 2*ma.PI*hslider("fc", 1000, 20, 20000, 1)); // tf1s_modulated_test = no.noise : fi.tf1s(0, 1, 1, 2*ma.PI*20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // tf1s_jump_test = no.noise : fi.tf1s(0, 1, 1, 2*ma.PI*20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### References // // * //---------------------------------------------- declare tf1s author "Julius O. Smith III"; declare tf1s copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare tf1s license "LicenseRef-STK-4.3"; tf1s(b1,b0,a0,w1) = tick ~ _ : !,_ with { w = abs(a0) + (a0 == 0); // frequency scale of the denominator (1 if a0 = 0) g = w*_tanhalf(w1); // trapezoidal integrator of s/w, prewarped at w1 // min(ma.MAX), a no-op, keeps the normalizer from turning u*k back into a // division, which would sit on the state's critical path (30 % slower // with a modulated w1) k = 1/(1 + g*a0/w) : min(ma.MAX); // state: integrator of u = (s/w)*V, with V = X/A(s), updated by adding // a small increment (2*v0 - s0 = v0 + g*u) tick(s0, x) = v0 + g*u, y with { u = (x - a0/w*s0)*k; v0 = g*u + s0; y = b1*u + b0/w*v0; }; }; // tan(w*T/2), the bilinear-transform constant that maps the analog frequency // 1 rad/s to w rad/s, for the analog sections above. The angle w*T/2 is // clamped to [0, 0.499*PI], i.e., w/(2*PI) to [0, 0.499*SR]: w <= 0 gives 0 // (every integrator holds its state), and w at or above Nyquist, where tan // would be infinite or negative (an unstable filter), acts as 0.499*SR, // where tan = 318 (see the section introduction). _tanhalf(w) = tan(max(0, min(w*0.5/ma.SR, 0.499*ma.PI))); //-------------------------`(fi.)tf2s_df`, `(fi.)tf1s_df`-------------------------------- // Direct-form versions of `tf2s` and `tf1s`: the same bilinear transform of // B(s)/A(s), prewarped at `w1`, realized as the direct-form biquad `tf2` and // first-order section `tf1`. They are cheaper than `tf2s` and `tf1s` (about // 1.6x for a fixed `tf2s_df`), and parallel banks of them vectorize better // with `faust -vec`, but they are only for FIXED cutoffs that stay well above // the low end of the band: use `tf2s` and `tf1s` everywhere else. // // #### Usage // // ``` // _ : tf2s_df(b2,b1,b0,a1,a0,w1) : _ // _ : tf1s_df(b1,b0,a0,w1) : _ // ``` // // Where: // // * `b2`, `b1`, `b0`, `a1`, `a0`: analog coefficients, as for `tf2s` and `tf1s` // * `w1`: the digital frequency (in radians/second) corresponding to analog // frequency 1 rad/sec, which must be greater than 0 (`w1` = 0 divides by // zero); above `0.998*PI*SR` (0.499*SR in Hz) it acts as that value // // #### Method // // The direct form stores output-level states whose poles cluster near // z = 1 when `w1` is small relative to the sample rate, and loses their // digits in single precision. Relative RMS error of a float render against // double, 2 s impulse response, second-order Butterworth lowpass // (`tf2s_df(0,0,1,sqrt(2),1,2*PI*fc)`), against `tf2s`: // // | SR | `fc` | 5 Hz | 20 Hz | 50 Hz | 200 Hz | 1 kHz | 10 kHz | // | :-- | :-- | --: | --: | --: | --: | --: | --: | // | 48 kHz | `tf2s_df` | -25 dB | -36 dB | -50 dB | -91 dB | -104 dB | -138 dB | // | 48 kHz | `tf2s` | -109 dB | -125 dB | -128 dB | -136 dB | -134 dB | -135 dB | // | 192 kHz | `tf2s_df` | diverges | -25 dB | -31 dB | -50 dB | -80 dB | -117 dB | // | 192 kHz | `tf2s` | -108 dB | -109 dB | -119 dB | -128 dB | -130 dB | -143 dB | // // A resonant section is worse: with Q = 10 at 192 kHz, `tf2s_df` is -30 dB // (3 %) off at 200 Hz and still -70 dB at 1 kHz. In float, keep a // second-order `w1` above about 1 % of the highest sample rate in use (for // example `fc` above 2 kHz at 192 kHz). The first-order `tf1s_df` is much // less sensitive: -66 dB at 2 Hz and below -80 dB from 10 Hz up at // 192 kHz. // // The direct form also behaves worse than `tf2s` when `w1` varies: a // downward jump of the cutoff multiplies its ringing by up to the jump // ratio, and modulation at audio rates can make it diverge even in double // precision. Its cost advantage also shrinks there (about 1.1x for a // modulated `w1`), since recomputing the coefficients dominates. // Under the audio-rate modulation of the `_audio_modulated_test`s (`w1` // swept by +-90 % around 1 kHz at 500 Hz, see `resonlp`), a `tf2s_df` // section with Q = 10 diverges at every rate from 44.1 to 192 kHz, in // double precision too (its output passes 1e38 within 0.42 s), and a // Butterworth section peaks 3.5 to 4.2 times higher than `tf2s`, which // stays within 0.9 to 1.05 times its static peak. `tf2s_df` therefore has // no `_audio_modulated_test`. Those of `tf2s`, `resonlp`, `resonhp` and // `resonbp` (Q = 10) fail `make check-precision` (non-finite) if their // section is moved to it, and in direct form the third-order Butterworth // `lowpass` peaks 4.4 to 5.0 times higher than in TPT (`highpass` 1.2 to // 1.4 times). // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ma = library("maths.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // src = os.tosc(440); // tf2s_df_test = src : fi.tf2s_df(0, 0, 1, sqrt(2), 1, ma.PI*ma.SR/2); // tf2s_df_lp1k_test = no.noise : fi.tf2s_df(0, 0, 1, sqrt(2), 1, 2*ma.PI*1000); // tf2s_df_slider_test = no.noise : fi.tf2s_df(0, 0, 1, sqrt(2), 1, 2*ma.PI*hslider("fc", 2000, 2000, 20000, 1)); // tf2s_df_modulated_test = no.noise : fi.tf2s_df(0, 0, 1, sqrt(2), 1, 2*ma.PI*2000*pow(8, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // tf2s_df_jump_test = no.noise : fi.tf2s_df(0, 0, 1, sqrt(2), 1, 2*ma.PI*2000*pow(8, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // tf1s_df_test = src : fi.tf1s_df(0, 1, 1, ma.PI*ma.SR/2); // tf1s_df_hp_test = no.noise : fi.tf1s_df(1, 0, 1, 2*ma.PI*100); // tf1s_df_slider_test = no.noise : fi.tf1s_df(0, 1, 1, 2*ma.PI*hslider("fc", 1000, 20, 20000, 1)); // tf1s_df_modulated_test = no.noise : fi.tf1s_df(0, 1, 1, 2*ma.PI*20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // tf1s_df_jump_test = no.noise : fi.tf1s_df(0, 1, 1, 2*ma.PI*20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### References // // * // * //---------------------------------------------- declare tf2s_df author "Julius O. Smith III"; declare tf2s_df copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare tf2s_df license "LicenseRef-STK-4.3"; tf2s_df(b2,b1,b0,a1,a0,w1) = tf2(b0d,b1d,b2d,a1d,a2d) with { c = 1/_tanhalf(w1); // bilinear-transform scale factor csq = c*c; d = a0 + a1*c + csq; b0d = (b0 + b1*c + b2*csq)/d; b1d = 2*(b0 - b2*csq)/d; b2d = (b0 - b1*c + b2*csq)/d; a1d = 2*(a0 - csq)/d; a2d = (a0 - a1*c + csq)/d; }; declare tf1s_df author "Julius O. Smith III"; declare tf1s_df copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare tf1s_df license "LicenseRef-STK-4.3"; tf1s_df(b1,b0,a0,w1) = tf1(b0d,b1d,a1d) with { c = 1/_tanhalf(w1); // bilinear-transform scale factor d = a0 + c; b1d = (b0 - b1*c)/d; b0d = (b0 + b1*c)/d; a1d = (a0 - c)/d; }; //-------------------------`(fi.)tpt_df_fmin`-------------------------------- // The lowest cutoff, in Hz, at which the `*_tpt_df` functions below switch // from trapezoidal (TPT) sections to the cheaper direct-form ones // (`tf2s_df`, `tf1s_df`) in the precision being compiled: 2304 Hz in single // precision, 0 (direct form everywhere) in double, quad and fixed-point // precision. // The filter banks and analyzers (`filterbank`, `mth_octave_filterbank`, // `an.mth_octave_analyzer`, ...) use it as their default limit: // `filterbank(O,freqs)` is `filterbank_tpt_df(tpt_df_fmin,O,freqs)`. // // #### Usage // // ``` // tpt_df_fmin : _ // ``` // // #### Method // // The float error of a direct-form section grows as the inverse square of // `fc/SR` (12 dB per octave down), so the lowest safe `fc/SR` scales as the // square root of the machine epsilon. In single precision, Butterworth // sections up to order 8 and the 6th-order elliptic `lowpass6e` and // `highpass6e` stay within -80 dB (relative RMS error, float against double) // above `fc/SR` = 0.012, which is 2304 Hz at 192 kHz, the highest rate // `ma.SR` reports. Scaled by the square root of the epsilon ratio, the same // accuracy would need 0.1 Hz in double precision and 0.002 Hz in quad, below // any useful cutoff: the limit is 0 there. Each precision gets its value // from a precision-qualified definition (`singleprecision tpt_df_fmin = // 2304.0;`, and 0 for `doubleprecision`, `quadprecision` and // `fixedpointprecision`), the mechanism `ma.EPSILON` uses, so the value is a // constant of the program and the switch costs nothing: each section is // compiled in one form only, when its cutoff is a constant. Fixed-point // compilation, like double, uses the direct form. // // Single- and double-precision builds therefore realize the sections // below 2304 Hz in different forms. Their outputs agree for constant or // slowly varying cutoffs, but not under fast modulation or abrupt jumps of // a cutoff, where the direct form rings more (see `tf2s_df`). // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // tpt_df_fmin_test = os.tosc(440) * (fi.tpt_df_fmin <= 2304); // ``` //---------------------------------------------- declare tpt_df_fmin author "Julius O. Smith III"; declare tpt_df_fmin license "MIT"; singleprecision tpt_df_fmin = 2304.0; // 0.012*192000: TPT below, direct form above doubleprecision tpt_df_fmin = 0.0; // direct form everywhere quadprecision tpt_df_fmin = 0.0; fixedpointprecision tpt_df_fmin = 0.0; // The section tpt below the cutoff limit L, and df(fc) at and above it: // L <= 0 selects df and L >= ma.MAX selects tpt for every fc, and a // constant L and fc compile one of the two only. Otherwise both run, and // the direct form runs at max(fc, L), never below the limit, where it // would lose its accuracy (or diverge) in single precision before fc // comes back up: the output then switches between two filters that track // nearly the same cutoff when fc crosses L. _tpt_df(L, fc, tpt, df) = _ <: dfL, (_ <: tpt, (_ <: tpt, dfL : select2(fc >= L)) : select2(L < ma.MAX)) : select2(L > 0) with { dfL = df(max(fc, L)); }; //-----------------------------`(fi.)tf2sb`-------------------------------- // Bandpass mapping of `tf2s`: In addition to a frequency-scaling parameter // `w1` (set to the desired passband width in rad/sec: the prototype's // cutoff at 1 rad/sec maps to two band edges `w1` apart), // there is a desired center-frequency parameter wc (also in rad/s). // Thus, `tf2sb` implements a fourth-order digital bandpass filter section // specified by the coefficients of a second-order analog lowpass prototype // section. Such sections can be combined in series for higher orders. // The order of mappings is (1) frequency scaling (to set lowpass cutoff w1), // (2) bandpass mapping to wc, then (3) the bilinear transform, with the // usual scale parameter `2*SR`. // // #### Usage // // ``` // _ : tf2sb(b2,b1,b0,a1,a0,w1,wc) : _ // ``` // // Where: // // * `b2`, `b1`, `b0`: analog lowpass numerator coefficients // * `a1`, `a0`: analog lowpass denominator coefficients // * `w1`: desired passband width in radians/second (distance between the band edges), >= 0; // floored at `5e-4*wc` (see Method) // * `wc`: desired center frequency in radians/second, >= 0 (0 gives the // lowpass prototype at `w1`) // // #### Method // // The fourth-order section is computed as two trapezoidal state-variable // sections in cascade, not as one fourth-order direct form. For complex // prototype poles, the four poles form two conjugate pairs, at `W1` and // `W2 = wc^2/W1` with the same damping, computed from the prototype poles. // The second section is fed by the first one's lowpass output, and with // `p = (s^2 + wc^2)/(w1*s)` and `D(p) = p^2 + a1*p + a0`, the output // `b2*x + (b1 - b2*a1)*p/D(p)*x + (b0 - b2*a0)/D(p)*x` is read on the // outputs of the two sections with gains that stay bounded for all // `w1 >= 0` and `wc >= 0`. For real prototype poles, each pole gives one // section, with integrator gains `max(wc, P)` and `wc^2/max(wc, P)`, where // `P` is the pole's magnitude times `w1`. The transfer function is the same // bilinear transform (unwarped, scale `2*SR`), but it stays accurate in // single precision when the band is low or narrow relative to the sample // rate, where the direct form's poles cluster near z = 1 (issue #261). // // Each section's input is scaled by `w1/W1`, so that its states stay at the // level of the output for any band, narrow or low: `wc` (from the lower band // edge `fl` of `bandpass`) can be swept down to 0 or jump to and from it, and // a band can close and reopen, without a spike. At `wc` = 0 the second // section is a DC blocker at 0 Hz and the first one the lowpass prototype at // `w1`. `w1` is floored at `5e-4*wc` (a band Q of at most 2000): a narrower // band, down to `w1` = 0 (`fl` = `fu`), acts as one of width `5e-4*wc`, so // that the sections are never lossless; after the band closes, what they // hold decays with a time constant of about `0.75*1000/fc` seconds for a // center frequency `fc` in Hz (`bandpass(2)`). Measured with // `bandpass(2, fl, 2000)`: a sweep of `fl` from 1 kHz to 0 and back in 2 s // peaks at 1.0 times the input (the previous version, which fed the second // section with the bandpass output and scaled its outputs by `(w1/wc)^2`, // at 9e12); a jump of `fl` from 1 kHz to 1 Hz peaks at 1.23 times the static // output (previously 735; the direct form before it, 693); an octave band // at 1 kHz closed for 0.1 s and reopened peaks at 0.89 (previously 210; the // direct form, 1060). After a jump of `wc` to 0, the DC held in the second // section stays in the output, as in any DC blocker at 0 Hz. // // When `w1` or `wc` jump abruptly, the state-variable sections release a // little more of their internal state than the direct form would: the // transient is slightly larger for an input well outside the band (about // -75 dB instead of -98 dB in Julius Smith's test, a 5 kHz sine through // `bandpass(2, fc, 1.2*fc)` with `fc` switching between 100 and 120 Hz). // With fixed or smoothly varying edges, both forms reject the same. // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ma = library("maths.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // src = os.tosc(440); // tf2sb_test = src : fi.tf2sb(0, 0, 1, sqrt(2), 1, 2*ma.PI*200, 2*ma.PI*1000); // tf2sb_zero_freq_test = no.noise : fi.tf2sb(1, 0, 0, sqrt(2), 1, 2*ma.PI*2000, 2*ma.PI*(1000*max(0, 1 - ba.time/12000) + 1000*(ba.time >= 24000))); // tf2sb_real_test = no.noise : fi.tf2sb(0.3, 0.5, 1, 3, 2, 2*ma.PI*500, 2*ma.PI*1000); // tf2sb_slider_test = no.noise : fi.tf2sb(0, 0, 1, sqrt(2), 1, 2*ma.PI*hslider("bw", 800, 10, 10000, 1), 2*ma.PI*hslider("fc", 1000, 20, 20000, 1)); // tf2sb_modulated_test = no.noise : fi.tf2sb(0, 0, 1, sqrt(2), 1, 2*ma.PI*fc/5, 2*ma.PI*fc) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); fc = 20*pow(250, tri); }; // tf2sb_jump_test = no.noise : fi.tf2sb(0, 0, 1, sqrt(2), 1, 2*ma.PI*fc/5, 2*ma.PI*fc) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; fc = 20*pow(250, sq); }; // ``` //---------------------------------------------- declare tf2sb author "Julius O. Smith III"; declare tf2sb copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare tf2sb license "LicenseRef-STK-4.3"; tf2sb(b2,b1,b0,a1,a0,w1in,wc) = (w1in, wc, _) : _tf2sbr(b2,b1,b0,a1,a0); // tf2sb with w1 and wc on inputs, so that the expressions given for them // appear once in the evaluated program (faust -e) instead of once per use: // otherwise faust -e of bandpass12e printed 7 GB, and timed out in // scripts/check_usage.py. The generated code is the same. _tf2sbr(b2,b1,b0,a1,a0,w1in,wc,x) = b2*x + (x <: (cplx, rl) : select2(real)) with { // bandwidth floor: w1 >= 5e-4*wc, so that the sections are never lossless w1 = max(w1in, 5e-4*wc); real = a1*a1 >= 4*a0; c1 = b1 - b2*a1; // H(p) = b2 + (c1*p + c0)/(p^2 + a1*p + a0) c0 = b0 - b2*a0; // unwarped bilinear transform, as in the direct form: g = w/(2*SR) sec(G1,G2,k) = _svfg(G1*0.5/ma.SR, G2*0.5/ma.SR, k); // Complex prototype poles: sections D1 at W1 = |U| and D2 at W2 = wc^2/W1, // same damping R, with w1^2*s^2*D(p) = D1*D2, where U and wc^2/U are the // roots of U^2 - q*w1*U + wc^2 for a prototype pole q. With rho1 = w1/W1 // and r = W2/W1: // 1/D(p) = w1^2*s^2/(D1*D2) = rho1^2*hp2[lp1] // p/D(p) = w1*s*(s^2 + W1*W2)/(D1*D2) = rho1*(hp2[bp1] + bp2[lp1]) // and hp2[bp1] = (s/W1)*hp2[lp1] = bp1 - 2*R*r*hp2 - r*bp2 (from the section // equations hp + k*bp + lp = in, s*lp = W*bp, s*bp = W*hp). Each section's // input is scaled by rho1, so that its states stay at the level of the // output even for a narrow band (whose sections have Q ~ 1/rho1), and the // remaining factors are bounded closed forms: from U + wc^2/U = q*w1, // R = (a1/2)*rho1/(1 + r) and (1 - r)/rho1 = Im(q)*|U|/Im(U). W1 = _tf2sbpoles(a1,a0,w1,wc) : _,! : max(ma.MIN); // 0 only if w1 = wc = 0 tq = _tf2sbpoles(a1,a0,w1,wc) : !,_; // |U|/Im(U) rho1 = w1/W1 : min(ma.MAX); // barrier: rho1*rho1 must not become w1^2/W1^2 r = (wc/W1 : min(1))^2; // W1 >= wc W2 = W1*r; R = a1*0.5*rho1/(1 + r); qi0 = sqrt(max(0, a0 - a1*a1*0.25)); // Im(q) cplx = *(rho1) : sec(W1, W1, 2*R) : (*(rho1) : sec(W2, W2, 2*R)), _, ! : ctaps; ctaps(lp2, bp2, hp2, bp1) = c1*bp1 + (c0 - c1*a1*r/(1 + r))*hp2 + c1*qi0*tq*bp2; // Real prototype poles q1, q2 < 0: sections D_i = s^2 + p_i*s + wc^2, // p_i = -q_i*w1, in cascade (section B fed by section A's band output // G1A*s/DA), with integrator gains G1 = max(wc, p_i) and G2 = wc^2/G1, so // that p_i -> infinity relative to wc (wc -> 0) keeps every gain bounded. q1 = 0 - (a1 + sqrt(max(0, a1*a1 - 4*a0)))*0.5 : min(0 - ma.MIN); q2 = a0/q1; pA = 0 - q1*w1; pB = 0 - q2*w1; G1A = max(wc, pA) : max(ma.MIN); G1B = max(wc, pB) : max(ma.MIN); rl = sec(G1A, wc*(wc/G1A), pA/G1A) : (!, _, !) : sec(G1B, wc*(wc/G1B), pB/G1B) : rtaps; rtaps(lpB, bpB, hpB) = (w1/G1A)*(c1*(hpB + lpB) + c0*(w1/G1B)*bpB); }; // Sections of tf2sb for complex prototype poles: the bandpass mapping sends // each prototype pole q to the roots U of U^2 - q*w1*U + wc^2. U, the root of // larger modulus, and conj(U) form one section at W1 = |U| with damping // R = -Re(U)/|U|; wc^2/U and its conjugate a section at wc^2/W1, same R. // Computed after normalizing w1 and wc by their maximum S (no overflow), // with a complex square root that does not cancel. // Returns (W1, |U|/Im(U)). _tf2sbpoles(a1,a0,w1,wc) = S*m, m/max(ui, ma.MIN) with { S = max(w1, wc) : max(ma.MIN); // min(1) (a no-op) keeps the Faust normalizer from factoring S out of the // expressions below, which would form w1^4 and overflow in float r1 = w1/S : min(1); rc = wc/S : min(1); qr = 0 - a1*0.5*r1; // Re(q)*w1/S qi = sqrt(max(0, a0 - a1*a1*0.25))*r1; // Im(q)*w1/S // sqrt(x + j*y) of the discriminant (q*w1/S)^2 - 4*(wc/S)^2: x = qr*qr - qi*qi - 4*rc*rc; y = 2*qr*qi; h = sqrt((sqrt(x*x + y*y) + abs(x))*0.5) : max(ma.MIN); dr = select2(x < 0, h, abs(y)/(2*h)); di = select2(x < 0, y/(2*h), select2(y < 0, h, 0 - h)); sg = select2(qr*dr + qi*di < 0, 1, -1); ur = (qr + sg*dr)*0.5; ui = (qi + sg*di)*0.5; m = sqrt(ur*ur + ui*ui) : max(ma.MIN); }; // Trapezoidal state-variable section with integrator gains g1 and g2 and // damping k: outputs lp, bp, hp = G1*G2/D, G1*s/D, s^2/D with // D = s^2 + k*G1*s + G1*G2, bilinear transform with g = G*T/2 (for tf2sb, // tf1sb). g1 = g2 is the usual state-variable filter at w = G1 = G2. _svfg(g1,g2,k) = tick ~ (_,_) : !,!,_,_,_ with { // solved for the highpass first, each state updated by a small increment // (see svf below) tick(ic1eq, ic2eq, v0) = v1 + g1*hp, v2 + g2*v1, v2, v1, hp with { hp = (v0 - (k + g2)*ic1eq - ic2eq)/(1 + g1*(k + g2)); v1 = g1*hp + ic1eq; v2 = g2*v1 + ic2eq; }; }; //-----------------------------`(fi.)tf1sb`-------------------------------- // First-to-second-order lowpass-to-bandpass section mapping, // analogous to tf2sb above. // // #### Usage // // ``` // _ : tf1sb(b1,b0,a0,w1,wc) : _ // ``` // // Where: // // * `b1`, `b0`: analog numerator coefficients // * `a0`: analog denominator constant coefficient // * `w1`: desired passband width in radians/second (distance between the band edges), >= 0; // floored at `5e-4*wc` (see Method) // * `wc`: desired center frequency in radians/second, >= 0 (0 gives the // lowpass prototype at `w1`) // // #### Method // // `(b1*p + b0)/(p + a0)` with `p = (s^2 + wc^2)/(w1*s)` is // `b1 + (b0 - b1*a0)*w1*s/(s^2 + a0*w1*s + wc^2)`: one trapezoidal // state-variable section, whose bandpass output gives the second term, with // the unwarped bilinear transform of the direct form. Its two integrator // gains are `G = max(wc, a0*w1)` and `wc^2/G`: for a narrow band // (`a0*w1 <= wc`) the usual section at `wc`, and gains that stay bounded // when `wc` goes to 0, where the section becomes the lowpass // `w1/(s + a0*w1)` (see `tf2sb`). As in `tf2sb`, the input is scaled so that // the states stay at the level of the output, and `w1` is floored at // `5e-4*wc`. // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ma = library("maths.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // src = os.tosc(440); // tf1sb_test = src : fi.tf1sb(0, 1, 1, 2*ma.PI*200, 2*ma.PI*1000); // tf1sb_zero_freq_test = no.noise : fi.tf1sb(0, 1, 1, 2*ma.PI*2000, 2*ma.PI*(1000*max(0, 1 - ba.time/12000) + 1000*(ba.time >= 24000))); // tf1sb_slider_test = no.noise : fi.tf1sb(0, 1, 1, 2*ma.PI*hslider("bw", 800, 10, 10000, 1), 2*ma.PI*hslider("fc", 1000, 20, 20000, 1)); // tf1sb_modulated_test = no.noise : fi.tf1sb(0, 1, 1, 2*ma.PI*fc/5, 2*ma.PI*fc) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); fc = 20*pow(250, tri); }; // tf1sb_jump_test = no.noise : fi.tf1sb(0, 1, 1, 2*ma.PI*fc/5, 2*ma.PI*fc) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; fc = 20*pow(250, sq); }; // ``` //---------------------------------------------- declare tf1sb author "Julius O. Smith III"; declare tf1sb copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare tf1sb license "LicenseRef-STK-4.3"; tf1sb(b1,b0,a0,w1in,wc) = (w1in, wc, _) : _tf1sbr(b1,b0,a0); // tf1sb with its frequencies on inputs (see _tf2sbr). _tf1sbr(b1,b0,a0,w1in,wc,x) = b1*x + (x*(w1/G1) : _svfg(G1*0.5/ma.SR, G2*0.5/ma.SR, p/G1) : !,*(b0 - b1*a0),!) with { // D = s^2 + p*s + wc^2 with integrator gains G1 = max(wc, p), G2 = wc^2/G1: // the usual state-variable section at wc for a narrow band (p <= wc), and // gains that stay bounded when wc goes to 0 (fl = 0 in bandpass). The input // is scaled by w1/G1 so that the states stay at the level of the output, // and w1 >= 5e-4*wc (as in tf2sb) keeps the section from being lossless. w1 = max(w1in, 5e-4*wc); p = a0*w1; G1 = max(wc, p) : max(ma.MIN); G2 = wc*(wc/G1); }; //==============================Simple Resonator Filters================================== //======================================================================================== //------------------`(fi.)resonlp`----------------- // Simple resonant lowpass filter based on `tf2s` (virtual analog). // `resonlp` is a standard Faust function. // // #### Usage // // ``` // _ : resonlp(fc,Q,gain) : _ // _ : resonhp(fc,Q,gain) : _ // _ : resonbp(fc,Q,gain) : _ // // ``` // // Where: // // * `fc`: center frequency (Hz) // * `Q`: q // * `gain`: gain (0-1) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // resonlp_test = src : fi.resonlp(1000, 2, 0.8); // resonlp_slider_test = no.noise : fi.resonlp(hslider("fc", 1000, 20, 20000, 1), hslider("Q", 2, 0.5, 20, 0.01), hslider("gain", 0.8, 0, 1, 0.01)); // resonlp_modulated_test = no.noise : fi.resonlp(20*pow(250, tri), 2, 0.8) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // resonlp_jump_test = no.noise : fi.resonlp(20*pow(250, sq), 2, 0.8) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // resonlp_audio_modulated_test = 0.1*no.noise : fi.resonlp(max(20, 1000*(1 + 0.9*os.tosc(500))), 10, 1); // ``` //--------------------------------------------------------------------- // resonlp = 2nd-order lowpass with corner resonance: declare resonlp author "Julius O. Smith III"; declare resonlp copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare resonlp license "LicenseRef-STK-4.3"; resonlp(fc,Q,gain) = tf2s(b2,b1,b0,a1,a0,wc) with { wc = 2*ma.PI*fc; a1 = 1/Q; a0 = 1; b2 = 0; b1 = 0; b0 = gain; }; //------------------`(fi.)resonhp`----------------- // Simple resonant highpass filters based on `tf2s` (virtual analog). // `resonhp` is a standard Faust function. It is the second-order highpass // gain*s^2/(s^2 + s/Q + 1), with s normalized by 2*PI*fc: it rolls off at // 12 dB per octave below fc, and its gain is `gain` well above fc. // // #### Usage // // ``` // _ : resonlp(fc,Q,gain) : _ // _ : resonhp(fc,Q,gain) : _ // _ : resonbp(fc,Q,gain) : _ // // ``` // // Where: // // * `fc`: center frequency (Hz) // * `Q`: q // * `gain`: gain (0-1) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // resonhp_test = fi.resonhp(1000, 2, 0.8, src); // resonhp_slider_test = no.noise : fi.resonhp(hslider("fc", 1000, 20, 20000, 1), hslider("Q", 2, 0.5, 20, 0.01), hslider("gain", 0.8, 0, 1, 0.01)); // resonhp_modulated_test = no.noise : fi.resonhp(20*pow(250, tri), 2, 0.8) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // resonhp_jump_test = no.noise : fi.resonhp(20*pow(250, sq), 2, 0.8) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // resonhp_audio_modulated_test = 0.1*no.noise : fi.resonhp(max(20, 1000*(1 + 0.9*os.tosc(500))), 10, 1); // ``` //--------------------------------------------------------------------- // resonhp = 2nd-order highpass with corner resonance: declare resonhp author "Julius O. Smith III"; declare resonhp copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare resonhp license "LicenseRef-STK-4.3"; resonhp(fc,Q,gain) = tf2s(b2,b1,b0,a1,a0,wc) with { wc = 2*ma.PI*fc; a1 = 1/Q; a0 = 1; b2 = gain; b1 = 0; b0 = 0; }; //------------------`(fi.)resonbp`----------------- // Simple resonant bandpass filters based on `tf2s` (virtual analog). // `resonbp` is a standard Faust function. It is // gain*s/(s^2 + s/Q + 1), with s normalized by 2*PI*fc, so its peak gain, // at fc, is gain*Q. // // #### Usage // // ``` // _ : resonlp(fc,Q,gain) : _ // _ : resonhp(fc,Q,gain) : _ // _ : resonbp(fc,Q,gain) : _ // // ``` // // Where: // // * `fc`: center frequency (Hz) // * `Q`: q // * `gain`: gain (0-1) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // resonbp_test = src : fi.resonbp(1000, 2, 0.8); // resonbp_slider_test = no.noise : fi.resonbp(hslider("fc", 1000, 20, 20000, 1), hslider("Q", 2, 0.5, 20, 0.01), hslider("gain", 0.8, 0, 1, 0.01)); // resonbp_modulated_test = no.noise : fi.resonbp(20*pow(250, tri), 2, 0.8) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // resonbp_jump_test = no.noise : fi.resonbp(20*pow(250, sq), 2, 0.8) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // resonbp_audio_modulated_test = 0.1*no.noise : fi.resonbp(max(20, 1000*(1 + 0.9*os.tosc(500))), 10, 1); // ``` //--------------------------------------------------------------------- // resonbp = 2nd-order bandpass declare resonbp author "Julius O. Smith III"; declare resonbp copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare resonbp license "LicenseRef-STK-4.3"; resonbp(fc,Q,gain) = tf2s(b2,b1,b0,a1,a0,wc) with { wc = 2*ma.PI*fc; a1 = 1/Q; a0 = 1; b2 = 0; b1 = gain; b0 = 0; }; //======================Butterworth Lowpass/Highpass Filters============================== //======================================================================================== //----------------`(fi.)lowpass`-------------------- // Nth-order Butterworth lowpass filter. // `lowpass` is a standard Faust function. // // #### Usage // // ``` // _ : lowpass(N,fc) : _ // ``` // // Where: // // * `N`: filter order (number of poles), nonnegative constant numerical expression // * `fc`: desired cut-off frequency (-3dB frequency) in Hz, clamped to // [0, 0.499*SR]: 0 holds the filter's states (see the section // [Digital Filter Sections Specified as Analog Filter Sections](#digital-filter-sections-specified-as-analog-filter-sections)) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // no = library("noises.lib"); // ba = library("basics.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // lowpass_test = src : fi.lowpass(4, 2000); // lowpass_lowfc_test = no.noise : fi.lowpass(3, 10.1); // lowpass_zero_freq_test = no.noise : fi.lowpass(3, 1000*max(0, 1 - ba.time/12000) + 1000*(ba.time >= 24000)); // lowpass_slider_test = no.noise : fi.lowpass(4, hslider("fc", 2000, 20, 20000, 1)); // lowpass_modulated_test = no.noise : fi.lowpass(4, 20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // lowpass_jump_test = no.noise : fi.lowpass(4, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // lowpass_audio_modulated_test = 0.1*no.noise : fi.lowpass(3, max(20, 1000*(1 + 0.9*os.tosc(500)))); // ``` // // #### References // // * // * `butter` function in Octave `("[z,p,g] = butter(N,1,'s');")` //------------------------------ declare lowpass author "Julius O. Smith III"; declare lowpass copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare lowpass license "LicenseRef-STK-4.3"; lowpass(N,fc) = lowpass0_highpass1(0,N,fc); //----------------`(fi.)highpass`-------------------- // Nth-order Butterworth highpass filter. // `highpass` is a standard Faust function. // // #### Usage // // ``` // _ : highpass(N,fc) : _ // ``` // // Where: // // * `N`: filter order (number of poles), nonnegative constant numerical expression // * `fc`: desired cut-off frequency (-3dB frequency) in Hz, clamped to // [0, 0.499*SR]: 0 holds the filter's states, so that the input passes // minus the DC they hold (see the section // [Digital Filter Sections Specified as Analog Filter Sections](#digital-filter-sections-specified-as-analog-filter-sections)) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // no = library("noises.lib"); // ba = library("basics.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // highpass_test = src : fi.highpass(4, 500); // highpass_lowfc_test = no.noise : fi.highpass(3, 10.1); // highpass_zero_freq_test = no.noise : fi.highpass(3, 1000*max(0, 1 - ba.time/12000) + 1000*(ba.time >= 24000)); // highpass_slider_test = no.noise : fi.highpass(4, hslider("fc", 500, 20, 20000, 1)); // highpass_modulated_test = no.noise : fi.highpass(4, 20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // highpass_jump_test = no.noise : fi.highpass(4, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // highpass_audio_modulated_test = 0.1*no.noise : fi.highpass(3, max(20, 1000*(1 + 0.9*os.tosc(500)))); // ``` // // #### References // // * // * `butter` function in Octave `("[z,p,g] = butter(N,1,'s');")` //------------------------------ declare highpass author "Julius O. Smith III"; declare highpass copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare highpass license "LicenseRef-STK-4.3"; highpass(N,fc) = lowpass0_highpass1(1,N,fc); //-------------`(fi.)lowpass0_highpass1`-------------- // Generic Butterworth lowpass/highpass filter: the shared implementation // behind `fi.lowpass` and `fi.highpass`, with a selector choosing between // the two responses. // // #### Usage // // ``` // _ : lowpass0_highpass1(s,N,fc) : _ // ``` // // Where: // // * `s`: response selector: 0 for lowpass, 1 for highpass (a constant numerical expression) // * `N`: filter order (a constant numerical expression) // * `fc`: -3dB cutoff frequency in Hz // // #### Method // // A first-order section `tf1s` for odd `N`, then second-order sections with // the Butterworth pole angles. Each second-order section has the transfer // function of `tf2s` (bilinear transform prewarped at `fc`), computed in // trapezoidal state-variable form (`svf.lp`/`svf.hp`), which stays accurate in // single precision when `fc` is small relative to the sample rate. // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // lowpass0_highpass1_test = src : fi.lowpass0_highpass1(0, 2, 1000); // lowpass0_highpass1_slider_test = no.noise : fi.lowpass0_highpass1(0, 2, hslider("fc", 1000, 20, 20000, 1)); // lowpass0_highpass1_modulated_test = no.noise : fi.lowpass0_highpass1(0, 2, 20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // lowpass0_highpass1_jump_test = no.noise : fi.lowpass0_highpass1(0, 2, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //------------------------------ declare lowpass0_highpass1 author "Julius O. Smith III"; declare lowpass0_highpass1 copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare lowpass0_highpass1 license "LicenseRef-STK-4.3"; lowpass0_highpass1(s,N,fc) = lphpr(s,N,N,fc) with { lphpr(s,0,N,fc) = _; lphpr(s,1,N,fc) = tf1s(s,1-s,1,2*ma.PI*fc); lphpr(s,O,N,fc) = lphpr(s,(O-2),N,fc) : section(s) with { parity = N % 2; S = (O-parity)/2; // current section number a1s = -2*cos((ma.PI)*-1 + (1-parity)*ma.PI/(2*N) + (S-1+parity)*ma.PI/N); // tf2s(s,0,1-s,a1s,1,2*ma.PI*fc) in trapezoidal state-variable form: the // same bilinear transform prewarped at fc, but the direct-form biquad's // poles near z=1 are replaced by integrator states, which stay accurate // in single precision when fc is small relative to ma.SR (issue #262): section(0) = svf.lp(fc, 1/a1s); section(1) = svf.hp(fc, 1/a1s); }; }; // lowpass0_highpass1 in direct form (tf2s_df, tf1s_df), for the *_tpt_df // functions above their cutoff limit _lowpass0_highpass1_df(s,N,fc) = lphpr(s,N,N,fc) with { lphpr(s,0,N,fc) = _; lphpr(s,1,N,fc) = tf1s_df(s,1-s,1,2*ma.PI*fc); lphpr(s,O,N,fc) = lphpr(s,(O-2),N,fc) : tf2s_df(s,0,1-s,a1s,1,2*ma.PI*fc) with { parity = N % 2; S = (O-parity)/2; // current section number a1s = -2*cos((ma.PI)*-1 + (1-parity)*ma.PI/(2*N) + (S-1+parity)*ma.PI/N); }; }; //------------`(fi.)lowpass_tpt_df`, `(fi.)highpass_tpt_df`-------------- // `lowpass` and `highpass` (order-`N` Butterworth) in trapezoidal sections // below the cutoff limit `L` and in direct-form sections (`tf2s_df`, // `tf1s_df`) at and above it. With a constant `fc`, only one of the two is // compiled. With a varying `fc`, both run (more CPU than either), the // direct form at `max(fc, L)` so that it stays accurate, and the output // switches between them where `fc` crosses `L`: pass `L = ma.MAX` // (trapezoidal) or `L = 0` (direct form) to compile one form only. // // #### Usage // // ``` // _ : lowpass_tpt_df(L,N,fc) : _ // _ : highpass_tpt_df(L,N,fc) : _ // ``` // // Where: // // * `L`: cutoff limit in Hz, usually `tpt_df_fmin`; `L <= 0` selects the // direct form and `L >= ma.MAX` the trapezoidal form for every `fc` // * `N`: filter order (number of poles), a constant numerical expression // * `fc`: cutoff frequency in Hz // // #### Test // ``` // fi = library("filters.lib"); // no = library("noises.lib"); // ba = library("basics.lib"); // ma = library("maths.lib"); // lowpass_tpt_df_test = no.noise : fi.lowpass_tpt_df(fi.tpt_df_fmin, 5, 4000); // lowpass_tpt_df_low_test = no.noise : fi.lowpass_tpt_df(fi.tpt_df_fmin, 5, 50); // lowpass_tpt_df_slider_test = no.noise : fi.lowpass_tpt_df(fi.tpt_df_fmin, 3, hslider("fc", 1000, 20, 20000, 1)); // lowpass_tpt_df_modulated_test = no.noise : fi.lowpass_tpt_df(0, 3, 2500*pow(8, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // lowpass_tpt_df_jump_test = no.noise : fi.lowpass_tpt_df(ma.MAX, 3, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // highpass_tpt_df_test = no.noise : fi.highpass_tpt_df(fi.tpt_df_fmin, 5, 4000); // highpass_tpt_df_low_test = no.noise : fi.highpass_tpt_df(fi.tpt_df_fmin, 5, 50); // highpass_tpt_df_slider_test = no.noise : fi.highpass_tpt_df(fi.tpt_df_fmin, 3, hslider("fc", 1000, 20, 20000, 1)); // highpass_tpt_df_modulated_test = no.noise : fi.highpass_tpt_df(0, 3, 2500*pow(8, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // highpass_tpt_df_jump_test = no.noise : fi.highpass_tpt_df(ma.MAX, 3, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //------------------------------------------------------------ declare lowpass_tpt_df author "Julius O. Smith III"; declare lowpass_tpt_df license "MIT"; lowpass_tpt_df(L,N,fc) = _tpt_df(L, fc, lowpass(N,fc), \(f).(_lowpass0_highpass1_df(0,N,f))); declare highpass_tpt_df author "Julius O. Smith III"; declare highpass_tpt_df license "MIT"; highpass_tpt_df(L,N,fc) = _tpt_df(L, fc, highpass(N,fc), \(f).(_lowpass0_highpass1_df(1,N,f))); //================Special Filter-Bank Delay-Equalizing Allpass Filters==================== // These special allpass filters are needed by filterbank et al. below. // They are equivalent to `highpass(N,fc)` +|- `lowpass(N,fc)`, but with // canceling pole-zero pairs removed (which occurs for odd N). Both the sum // and the difference are allpass for odd N only, the order the filter banks // require. //======================================================================================== //--------------------`(fi.)highpass_plus_lowpass`---------------- // Sum of the order-`N` Butterworth highpass and lowpass responses at the same // cutoff: for odd `N`, an allpass used as delay equalizer by `fi.filterbank` // and the filter banks below. For odd orders 1, 3 and 5, canceling pole-zero // pairs are removed. For even `N` the sum is not allpass: it has a notch at // `fc` for N=2, and +3 dB at `fc` for N=4. // // #### Usage // // ``` // _ : highpass_plus_lowpass(N,fc) : _ // ``` // // Where: // // * `N`: filter order (a constant numerical expression) // * `fc`: crossover frequency in Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // highpass_plus_lowpass_test = os.tosc(440) : fi.highpass_plus_lowpass(3, 1000); // highpass_plus_lowpass_slider_test = no.noise : fi.highpass_plus_lowpass(3, hslider("fc", 1000, 20, 20000, 1)); // highpass_plus_lowpass_modulated_test = no.noise : fi.highpass_plus_lowpass(3, 20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // highpass_plus_lowpass_jump_test = no.noise : fi.highpass_plus_lowpass(3, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //------------------------------ declare highpass_plus_lowpass author "Julius O. Smith III"; declare highpass_plus_lowpass copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare highpass_plus_lowpass license "LicenseRef-STK-4.3"; highpass_plus_lowpass(N,fc) = _highpass_plus_lowpass(tf2s, lowpass0_highpass1, N, fc); // highpass_plus_lowpass built from the second-order section t2 and the // Butterworth lowpass0_highpass1-style function lphp, so that the // trapezoidal and direct-form versions share their coefficients: _highpass_plus_lowpass(t2, lphp, 1, fc) = _; _highpass_plus_lowpass(t2, lphp, 3, fc) = t2(1,-1,1,1,1,w1) with { w1 = 2*ma.PI*fc; }; _highpass_plus_lowpass(t2, lphp, 5, fc) = t2(1,-a11,1,a11,1,w1) with { a11 = 1.618033988749895; w1 = 2*ma.PI*fc; }; // Catch-all definition for generality (canceling pole-zero pairs not removed): _highpass_plus_lowpass(t2, lphp, N, fc) = _ <: lphp(1,N,fc), lphp(0,N,fc) :> _; //--------------------`(fi.)highpass_minus_lowpass`---------------- // Difference between the order-`N` Butterworth highpass and lowpass responses // at the same cutoff: an allpass used as delay equalizer by the // dc-inverted filter banks (`fi.mth_octave_filterbank_alt`). For odd orders // 3 and 5, canceling pole-zero pairs are removed. // // #### Usage // // ``` // _ : highpass_minus_lowpass(N,fc) : _ // ``` // // Where: // // * `N`: filter order (a constant numerical expression) // * `fc`: crossover frequency in Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // highpass_minus_lowpass_test = os.tosc(440) : fi.highpass_minus_lowpass(3, 1000); // highpass_minus_lowpass_slider_test = no.noise : fi.highpass_minus_lowpass(3, hslider("fc", 1000, 20, 20000, 1)); // highpass_minus_lowpass_modulated_test = no.noise : fi.highpass_minus_lowpass(3, 20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // highpass_minus_lowpass_jump_test = no.noise : fi.highpass_minus_lowpass(3, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //------------------------------ declare highpass_minus_lowpass author "Julius O. Smith III"; declare highpass_minus_lowpass copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare highpass_minus_lowpass license "LicenseRef-STK-4.3"; highpass_minus_lowpass(N,fc) = _highpass_minus_lowpass(tf2s, tf1s, lowpass0_highpass1, N, fc); // highpass_minus_lowpass from the sections t2, t1 and lphp (see // _highpass_plus_lowpass): _highpass_minus_lowpass(t2, t1, lphp, 3, fc) = t1(1,-1,1,w1) with { w1 = 2*ma.PI*fc; }; _highpass_minus_lowpass(t2, t1, lphp, 5, fc) = t1(1,-1,1,w1) : t2(1,-a12,1,a12,1,w1) with { a12 = 0.618033988749895; w1 = 2*ma.PI*fc; }; // Catch-all definition for generality (canceling pole-zero pairs not removed): _highpass_minus_lowpass(t2, t1, lphp, N, fc) = _ <: lphp(1,N,fc), lphp(0,N,fc) : -; //---`(fi.)highpass_plus_lowpass_tpt_df`, `(fi.)highpass_minus_lowpass_tpt_df`--- // `highpass_plus_lowpass` and `highpass_minus_lowpass` in trapezoidal // sections below the cutoff limit `L` and in direct-form sections // (`tf2s_df`, `tf1s_df`) at and above it, as in `lowpass_tpt_df`: the delay // equalizers of the `*_tpt_df` filter banks. // // #### Usage // // ``` // _ : highpass_plus_lowpass_tpt_df(L,N,fc) : _ // _ : highpass_minus_lowpass_tpt_df(L,N,fc) : _ // ``` // // Where: // // * `L`: cutoff limit in Hz, usually `tpt_df_fmin`; `L <= 0` selects the // direct form and `L >= ma.MAX` the trapezoidal form for every `fc` // * `N`: filter order (a constant numerical expression) // * `fc`: crossover frequency in Hz // // #### Test // ``` // fi = library("filters.lib"); // no = library("noises.lib"); // ba = library("basics.lib"); // ma = library("maths.lib"); // highpass_plus_lowpass_tpt_df_test = no.noise : fi.highpass_plus_lowpass_tpt_df(fi.tpt_df_fmin, 5, 4000); // highpass_plus_lowpass_tpt_df_low_test = no.noise : fi.highpass_plus_lowpass_tpt_df(fi.tpt_df_fmin, 3, 50); // highpass_plus_lowpass_tpt_df_slider_test = no.noise : fi.highpass_plus_lowpass_tpt_df(fi.tpt_df_fmin, 5, hslider("fc", 1000, 20, 20000, 1)); // highpass_plus_lowpass_tpt_df_modulated_test = no.noise : fi.highpass_plus_lowpass_tpt_df(0, 5, 2500*pow(8, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // highpass_plus_lowpass_tpt_df_jump_test = no.noise : fi.highpass_plus_lowpass_tpt_df(ma.MAX, 5, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // highpass_minus_lowpass_tpt_df_test = no.noise : fi.highpass_minus_lowpass_tpt_df(fi.tpt_df_fmin, 5, 4000); // highpass_minus_lowpass_tpt_df_low_test = no.noise : fi.highpass_minus_lowpass_tpt_df(fi.tpt_df_fmin, 3, 50); // highpass_minus_lowpass_tpt_df_slider_test = no.noise : fi.highpass_minus_lowpass_tpt_df(fi.tpt_df_fmin, 5, hslider("fc", 1000, 20, 20000, 1)); // highpass_minus_lowpass_tpt_df_modulated_test = no.noise : fi.highpass_minus_lowpass_tpt_df(0, 5, 2500*pow(8, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // highpass_minus_lowpass_tpt_df_jump_test = no.noise : fi.highpass_minus_lowpass_tpt_df(ma.MAX, 5, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //------------------------------------------------------------ declare highpass_plus_lowpass_tpt_df author "Julius O. Smith III"; declare highpass_plus_lowpass_tpt_df license "MIT"; highpass_plus_lowpass_tpt_df(L,N,fc) = _tpt_df(L, fc, highpass_plus_lowpass(N,fc), \(f).(_highpass_plus_lowpass(tf2s_df, _lowpass0_highpass1_df, N, f))); declare highpass_minus_lowpass_tpt_df author "Julius O. Smith III"; declare highpass_minus_lowpass_tpt_df license "MIT"; highpass_minus_lowpass_tpt_df(L,N,fc) = _tpt_df(L, fc, highpass_minus_lowpass(N,fc), \(f).(_highpass_minus_lowpass(tf2s_df, tf1s_df, _lowpass0_highpass1_df, N, f))); //--------------------`(fi.)highpass_plus_lowpass_even`---------------- // Sum of the even-order Butterworth highpass and lowpass responses, // `highpass(N,fc) + lowpass(N,fc)`. Takes two copies of the input signal // (both normally fed the same signal); `fi.highpass_plus_lowpass` selects // this case automatically for even `N`. // // #### Usage // // ``` // _,_ : highpass_plus_lowpass_even(N,fc) : _ // ``` // // Where: // // * `N`: even filter order (a constant numerical expression) // * `fc`: crossover frequency in Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // highpass_plus_lowpass_even_test = os.tosc(440), os.tosc(440) : fi.highpass_plus_lowpass_even(4, 1000); // highpass_plus_lowpass_even_slider_test = (no.noise <: _, _) : fi.highpass_plus_lowpass_even(4, hslider("fc", 1000, 20, 20000, 1)); // highpass_plus_lowpass_even_modulated_test = (no.noise <: _, _) : fi.highpass_plus_lowpass_even(4, 20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // highpass_plus_lowpass_even_jump_test = (no.noise <: _, _) : fi.highpass_plus_lowpass_even(4, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //------------------------------ declare highpass_plus_lowpass_even author "Julius O. Smith III"; declare highpass_plus_lowpass_even copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare highpass_plus_lowpass_even license "LicenseRef-STK-4.3"; highpass_plus_lowpass_even(N,fc) = highpass(N,fc) + lowpass(N,fc); //--------------------`(fi.)highpass_minus_lowpass_even`---------------- // Difference between the even-order Butterworth highpass and lowpass // responses, `highpass(N,fc) - lowpass(N,fc)`. Takes two copies of the input // signal (both normally fed the same signal); `fi.highpass_minus_lowpass` // selects this case automatically for even `N`. // // #### Usage // // ``` // _,_ : highpass_minus_lowpass_even(N,fc) : _ // ``` // // Where: // // * `N`: even filter order (a constant numerical expression) // * `fc`: crossover frequency in Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // highpass_minus_lowpass_even_test = os.tosc(440), os.tosc(440) : fi.highpass_minus_lowpass_even(4, 1000); // highpass_minus_lowpass_even_slider_test = (no.noise <: _, _) : fi.highpass_minus_lowpass_even(4, hslider("fc", 1000, 20, 20000, 1)); // highpass_minus_lowpass_even_modulated_test = (no.noise <: _, _) : fi.highpass_minus_lowpass_even(4, 20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // highpass_minus_lowpass_even_jump_test = (no.noise <: _, _) : fi.highpass_minus_lowpass_even(4, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //------------------------------ declare highpass_minus_lowpass_even author "Julius O. Smith III"; declare highpass_minus_lowpass_even copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare highpass_minus_lowpass_even license "LicenseRef-STK-4.3"; highpass_minus_lowpass_even(N,fc) = highpass(N,fc) - lowpass(N,fc); //--------------------`(fi.)highpass_plus_lowpass_odd`----------------- // Sum of the odd-order highpass and lowpass responses. // // #### Usage // // ``` // _,_ : highpass_plus_lowpass_odd(N,fc) : _ // ``` // // Where: // // * `N`: odd Butterworth order // * `fc`: cutoff frequency in Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // highpass_plus_lowpass_odd_test = os.tosc(440), os.tosc(440) : fi.highpass_plus_lowpass_odd(3, 1000); // highpass_plus_lowpass_odd_slider_test = (no.noise <: _, _) : fi.highpass_plus_lowpass_odd(3, hslider("fc", 1000, 20, 20000, 1)); // highpass_plus_lowpass_odd_modulated_test = (no.noise <: _, _) : fi.highpass_plus_lowpass_odd(3, 20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // highpass_plus_lowpass_odd_jump_test = (no.noise <: _, _) : fi.highpass_plus_lowpass_odd(3, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // FIXME: Rewrite the following, as for orders 3 and 5 above, // to eliminate pole-zero cancellations: //------------------------------ declare highpass_plus_lowpass_odd author "Julius O. Smith III"; declare highpass_plus_lowpass_odd copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare highpass_plus_lowpass_odd license "LicenseRef-STK-4.3"; highpass_plus_lowpass_odd(N,fc) = highpass(N,fc) + lowpass(N,fc); //--------------------`(fi.)highpass_minus_lowpass_odd`---------------- // Difference between the odd-order highpass and lowpass responses. // // #### Usage // // ``` // _,_ : highpass_minus_lowpass_odd(N,fc) : _ // ``` // // Where: // // * `N`: odd Butterworth order // * `fc`: cutoff frequency in Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // highpass_minus_lowpass_odd_test = os.tosc(440), os.tosc(440) : fi.highpass_minus_lowpass_odd(3, 1000); // highpass_minus_lowpass_odd_slider_test = (no.noise <: _, _) : fi.highpass_minus_lowpass_odd(3, hslider("fc", 1000, 20, 20000, 1)); // highpass_minus_lowpass_odd_modulated_test = (no.noise <: _, _) : fi.highpass_minus_lowpass_odd(3, 20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // highpass_minus_lowpass_odd_jump_test = (no.noise <: _, _) : fi.highpass_minus_lowpass_odd(3, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // FIXME: Rewrite the following, as for orders 3 and 5 above, // to eliminate pole-zero cancellations/ //------------------------------ highpass_minus_lowpass_odd(N,fc) = highpass(N,fc) - lowpass(N,fc); declare highpass_minus_lowpass_odd author "Julius O. Smith III"; declare highpass_minus_lowpass_odd copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare highpass_minus_lowpass_odd license "LicenseRef-STK-4.3"; //==========================Elliptic (Cauer) Lowpass Filters============================== // Elliptic (Cauer) Lowpass Filters // // #### References // // * // * functions `ncauer` and `ellip` in Octave. //======================================================================================== //-----------------------------`(fi.)lowpass3e`----------------------------- // Third-order Elliptic (Cauer) lowpass filter. // // #### Usage // // ``` // _ : lowpass3e(fc) : _ // ``` // // Where: // // * `fc`: passband edge frequency in Hz, where the gain is -0.2 dB (the // -3 dB point is near 1.28*fc) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // src = os.tosc(440); // lowpass3e_test = src : fi.lowpass3e(1000); // lowpass3e_slider_test = no.noise : fi.lowpass3e(hslider("fc", 1000, 20, 20000, 1)); // ``` // // #### Design // // For spectral band-slice level display (see `octave_analyzer3e`): // // ``` // [z,p,g] = ncauer(Rp,Rs,3); % analog zeros, poles, and gain, where // Rp = 60 % dB ripple in stopband // Rs = 0.2 % dB ripple in passband // ``` //--------------------------------------------------------------------- declare lowpass3e author "Julius O. Smith III"; declare lowpass3e copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare lowpass3e license "LicenseRef-STK-4.3"; lowpass3e(fc) = tf2s(b21,b11,b01,a11,a01,w1) : tf1s(0,1,a02,w1) with { a11 = 0.802636764161030; // format long; poly(p(1:2)) % in octave a01 = 1.412270893774204; a02 = 0.822445908998816; // poly(p(3)) % in octave b21 = 0.019809144837789; // poly(z) b11 = 0; b01 = 1.161516418982696; w1 = 2*ma.PI*fc; }; //-----------------------------`(fi.)lowpass6e`----------------------------- // Sixth-order Elliptic/Cauer lowpass filter. // // #### Usage // // ``` // _ : lowpass6e(fc) : _ // ``` // // Where: // // * `fc`: passband edge frequency in Hz, where the gain is -0.2 dB (the // -3 dB point is near 1.06*fc) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // no = library("noises.lib"); // src = os.tosc(440); // lowpass6e_test = src : fi.lowpass6e(1000); // lowpass6e_slider_test = no.noise : fi.lowpass6e(hslider("fc", 1000, 20, 20000, 1)); // ``` // // #### Design // // For spectral band-slice level display (see octave_analyzer6e): // // ``` // [z,p,g] = ncauer(Rp,Rs,6); % analog zeros, poles, and gain, where // Rp = 80 % dB ripple in stopband // Rs = 0.2 % dB ripple in passband // ``` //---------------------------------------------------------------------- declare lowpass6e author "Julius O. Smith III"; declare lowpass6e copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare lowpass6e license "LicenseRef-STK-4.3"; lowpass6e(fc) = _lowpass6e(tf2s, fc); // lowpass6e from the second-order section t2 (tf2s or tf2s_df) _lowpass6e(t2, fc) = t2(b21,b11,b01,a11,a01,w1) : t2(b22,b12,b02,a12,a02,w1) : t2(b23,b13,b03,a13,a03,w1) with { b21 = 0.000099999997055; a21 = 1; b11 = 0; a11 = 0.782413046821645; b01 = 0.000433227200555; a01 = 0.245291508706160; b22 = 1; a22 = 1; b12 = 0; a12 = 0.512478641889141; b02 = 7.621731298870603; a02 = 0.689621364484675; b23 = 1; a23 = 1; b13 = 0; a13 = 0.168404871113589; b03 = 53.536152954556727; a03 = 1.069358407707312; w1 = 2*ma.PI*fc; }; //=========================Elliptic Highpass Filters====================================== //======================================================================================== //-----------------------------`(fi.)highpass3e`----------------------------- // Third-order Elliptic (Cauer) highpass filter. Inversion of `lowpass3e` wrt unit // circle in s plane (s <- 1/s). // // #### Usage // // ``` // _ : highpass3e(fc) : _ // ``` // // Where: // // * `fc`: passband edge frequency in Hz, where the gain is -0.2 dB (the // -3 dB point is near fc/1.28) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // highpass3e_test = src : fi.highpass3e(1000); // highpass3e_slider_test = no.noise : fi.highpass3e(hslider("fc", 1000, 20, 20000, 1)); // highpass3e_modulated_test = no.noise : fi.highpass3e(20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // highpass3e_jump_test = no.noise : fi.highpass3e(20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //------------------------------------------------------------------------- declare highpass3e author "Julius O. Smith III"; declare highpass3e copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare highpass3e license "LicenseRef-STK-4.3"; highpass3e(fc) = tf2s(b01/a01,b11/a01,b21/a01,a11/a01,1/a01,w1) : tf1s(1/a02,0,1/a02,w1) with { a11 = 0.802636764161030; a01 = 1.412270893774204; a02 = 0.822445908998816; b21 = 0.019809144837789; b11 = 0; b01 = 1.161516418982696; w1 = 2*ma.PI*fc; }; //-----------------------------`(fi.)highpass6e`----------------------------- // Sixth-order Elliptic/Cauer highpass filter. Inversion of `lowpass6e` wrt unit // circle in s plane (s <- 1/s). // // #### Usage // // ``` // _ : highpass6e(fc) : _ // ``` // // Where: // // * `fc`: passband edge frequency in Hz, where the gain is -0.2 dB (the // -3 dB point is near fc/1.06) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // highpass6e_test = src : fi.highpass6e(1000); // highpass6e_slider_test = no.noise : fi.highpass6e(hslider("fc", 1000, 20, 20000, 1)); // highpass6e_modulated_test = no.noise : fi.highpass6e(20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // highpass6e_jump_test = no.noise : fi.highpass6e(20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //------------------------------------------------------------------------- declare highpass6e author "Julius O. Smith III"; declare highpass6e copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare highpass6e license "LicenseRef-STK-4.3"; highpass6e(fc) = _highpass6e(tf2s, fc); // highpass6e from the second-order section t2 (tf2s or tf2s_df) _highpass6e(t2, fc) = t2(b01/a01,b11/a01,b21/a01,a11/a01,1/a01,w1) : t2(b02/a02,b12/a02,b22/a02,a12/a02,1/a02,w1) : t2(b03/a03,b13/a03,b23/a03,a13/a03,1/a03,w1) with { b21 = 0.000099999997055; a21 = 1; b11 = 0; a11 = 0.782413046821645; b01 = 0.000433227200555; a01 = 0.245291508706160; b22 = 1; a22 = 1; b12 = 0; a12 = 0.512478641889141; b02 = 7.621731298870603; a02 = 0.689621364484675; b23 = 1; a23 = 1; b13 = 0; a13 = 0.168404871113589; b03 = 53.536152954556727; a03 = 1.069358407707312; w1 = 2*ma.PI*fc; }; //------------`(fi.)lowpass6e_tpt_df`, `(fi.)highpass6e_tpt_df`-------------- // `lowpass6e` and `highpass6e` in trapezoidal sections below the cutoff // limit `L` and in direct-form sections (`tf2s_df`) at and above it, as in // `lowpass_tpt_df`: the band splits of `an.mth_octave_analyzer6e_tpt_df`. // // #### Usage // // ``` // _ : lowpass6e_tpt_df(L,fc) : _ // _ : highpass6e_tpt_df(L,fc) : _ // ``` // // Where: // // * `L`: cutoff limit in Hz, usually `tpt_df_fmin`; `L <= 0` selects the // direct form and `L >= ma.MAX` the trapezoidal form for every `fc` // * `fc`: -3dB frequency in Hz // // #### Test // ``` // fi = library("filters.lib"); // no = library("noises.lib"); // ba = library("basics.lib"); // ma = library("maths.lib"); // lowpass6e_tpt_df_test = no.noise : fi.lowpass6e_tpt_df(fi.tpt_df_fmin, 4000); // lowpass6e_tpt_df_low_test = no.noise : fi.lowpass6e_tpt_df(fi.tpt_df_fmin, 50); // lowpass6e_tpt_df_slider_test = no.noise : fi.lowpass6e_tpt_df(fi.tpt_df_fmin, hslider("fc", 1000, 20, 20000, 1)); // lowpass6e_tpt_df_modulated_test = no.noise : fi.lowpass6e_tpt_df(0, 2500*pow(8, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // lowpass6e_tpt_df_jump_test = no.noise : fi.lowpass6e_tpt_df(ma.MAX, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // highpass6e_tpt_df_test = no.noise : fi.highpass6e_tpt_df(fi.tpt_df_fmin, 4000); // highpass6e_tpt_df_low_test = no.noise : fi.highpass6e_tpt_df(fi.tpt_df_fmin, 50); // highpass6e_tpt_df_slider_test = no.noise : fi.highpass6e_tpt_df(fi.tpt_df_fmin, hslider("fc", 1000, 20, 20000, 1)); // highpass6e_tpt_df_modulated_test = no.noise : fi.highpass6e_tpt_df(0, 2500*pow(8, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // highpass6e_tpt_df_jump_test = no.noise : fi.highpass6e_tpt_df(ma.MAX, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //------------------------------------------------------------ declare lowpass6e_tpt_df author "Julius O. Smith III"; declare lowpass6e_tpt_df license "MIT"; lowpass6e_tpt_df(L,fc) = _tpt_df(L, fc, lowpass6e(fc), \(f).(_lowpass6e(tf2s_df, f))); declare highpass6e_tpt_df author "Julius O. Smith III"; declare highpass6e_tpt_df license "MIT"; highpass6e_tpt_df(L,fc) = _tpt_df(L, fc, highpass6e(fc), \(f).(_highpass6e(tf2s_df, f))); //========================Butterworth Bandpass/Bandstop Filters=========================== //======================================================================================== //--------------------`(fi.)bandpass`---------------- // Order 2*Nh Butterworth bandpass filter made using the transformation // `s <- s + wc^2/s` on `lowpass(Nh)`, where `wc` is the desired bandpass center // frequency. The `lowpass(Nh)` cutoff `w1` is the desired bandpass width. // `bandpass` is a standard Faust function. // // #### Usage // // ``` // _ : bandpass(Nh,fl,fu) : _ // ``` // // Where: // // * `Nh`: HALF the desired bandpass order (which is therefore even) // * `fl`: lower -3dB frequency in Hz // * `fu`: upper -3dB frequency in Hz // Thus, the passband width is `fu-fl`, and its center frequency, where // the response peaks, is the geometric mean of the band edges prewarped by // the bilinear transform: `sqrt(fl*fu)` well below Nyquist, somewhat higher // near it. `fu` is clamped to [0, 0.499*SR] and `fl` to [0, `fu`]: `fl` = 0 // gives `lowpass(Nh, fu)`, and `fl` can be swept to 0 or jump to and from it // without a click or a spike (see `tf2sb`). The passband width is floored // at 5e-4 times the center frequency (a Q of at most 2000), so `fl` = `fu` // gives that narrowest band, and a band can close and reopen without a spike. // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // no = library("noises.lib"); // ba = library("basics.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // bandpass_test = src : fi.bandpass(2, 500, 1500); // bandpass_lowband_test = no.noise : fi.bandpass(2, 100, 200); // bandpass_narrowlow_test = no.noise : fi.bandpass(2, 20, 22); // bandpass_zero_freq_test = no.noise : fi.bandpass(2, 1000*max(0, 1 - ba.time/12000) + 1000*(ba.time >= 24000), 2000); // bandpass3_zero_freq_test = no.noise : fi.bandpass(3, 1000*max(0, 1 - ba.time/12000) + 1000*(ba.time >= 24000), 2000); // bandpass_nyquist_test = no.noise : fi.bandpass(2, 1000, 0.6*ma.SR); // bandpass_zero_width_test = no.noise : fi.bandpass(2, select2(z, 707.1, 1000), select2(z, 1414.2, 1000)) with { z = (ba.time >= 4800) & (ba.time < 9600); }; // bandpass3_zero_width_test = no.noise : fi.bandpass(3, select2(z, 707.1, 1000), select2(z, 1414.2, 1000)) with { z = (ba.time >= 4800) & (ba.time < 9600); }; // bandpass_slider_test = no.noise : fi.bandpass(2, hslider("fl", 500, 20, 20000, 1), hslider("fu", 1500, 20, 20000, 1)); // bandpass_modulated_test = no.noise : fi.bandpass(2, fl, 3*fl) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); fl = 20*pow(250, tri); }; // bandpass_jump_test = no.noise : fi.bandpass(2, fl, 3*fl) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; fl = 20*pow(250, sq); }; // ``` //------------------------------------------------------------------------- declare bandpass author "Julius O. Smith III"; declare bandpass copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare bandpass license "LicenseRef-STK-4.3"; bandpass(Nh,fl,fu) = bandpass0_bandstop1(0,Nh,fl,fu); //--------------------`(fi.)bandstop`---------------- // Order 2*Nh Butterworth bandstop filter made using the transformation // `s <- s + wc^2/s` on `highpass(Nh)`, where `wc` is the desired bandpass center // frequency. The `highpass(Nh)` cutoff `w1` is the desired bandstop width. // `bandstop` is a standard Faust function. // // #### Usage // // ``` // _ : bandstop(Nh,fl,fu) : _ // ``` // Where: // // * `Nh`: HALF the desired bandstop order (which is therefore even) // * `fl`: lower -3dB frequency in Hz // * `fu`: upper -3dB frequency in Hz // Thus, the stopband width is `fu-fl`, and its center frequency, where // the response has its zero, is the geometric mean of the band edges // prewarped by the bilinear transform: `sqrt(fl*fu)` well below Nyquist, // somewhat higher near it. `fu` is clamped to [0, 0.499*SR] and `fl` to // [0, `fu`]: `fl` = 0 gives `highpass(Nh, fu)`, and `fl` can be swept to 0 // or jump to and from it without a click or a spike (see `tf2sb`); `fl` = // `fu` gives the narrowest stopband: the width is floored at 5e-4 times the // center frequency, as in `bandpass`. // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // no = library("noises.lib"); // ba = library("basics.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // bandstop_test = src : fi.bandstop(2, 500, 1500); // bandstop_wide_test = no.noise : fi.bandstop(2, 5000, 8000); // bandstop_zero_freq_test = no.noise : fi.bandstop(2, 1000*max(0, 1 - ba.time/12000) + 1000*(ba.time >= 24000), 2000); // bandstop_zero_width_test = no.noise : fi.bandstop(2, select2(z, 707.1, 1000), select2(z, 1414.2, 1000)) with { z = (ba.time >= 4800) & (ba.time < 9600); }; // bandstop_slider_test = no.noise : fi.bandstop(2, hslider("fl", 500, 20, 20000, 1), hslider("fu", 1500, 20, 20000, 1)); // bandstop_modulated_test = no.noise : fi.bandstop(2, fl, 3*fl) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); fl = 20*pow(250, tri); }; // bandstop_jump_test = no.noise : fi.bandstop(2, fl, 3*fl) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; fl = 20*pow(250, sq); }; // ``` //------------------------------------------------------------------------- declare bandstop author "Julius O. Smith III"; declare bandstop copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare bandstop license "LicenseRef-STK-4.3"; bandstop(Nh,fl,fu) = bandpass0_bandstop1(1,Nh,fl,fu); //--------------------`(fi.)bandpass0_bandstop1`---------------- // Generic Butterworth bandpass/bandstop filter: the shared implementation // behind `fi.bandpass` and `fi.bandstop`, with a selector choosing between // the two responses. // // #### Usage // // ``` // _ : bandpass0_bandstop1(s,Nh,fl,fu) : _ // ``` // // Where: // // * `s`: response selector: 0 for bandpass, 1 for bandstop (a constant numerical expression) // * `Nh`: HALF the filter order, i.e. the number of second-order sections (a constant numerical expression) // * `fl`: lower -3dB band edge frequency in Hz // * `fu`: upper -3dB band edge frequency in Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // bandpass0_bandstop1_test = src : fi.bandpass0_bandstop1(0, 2, 500, 1500); // bandpass0_bandstop1_slider_test = no.noise : fi.bandpass0_bandstop1(0, 2, hslider("fl", 500, 20, 20000, 1), hslider("fu", 1500, 20, 20000, 1)); // bandpass0_bandstop1_modulated_test = no.noise : fi.bandpass0_bandstop1(0, 2, fl, 3*fl) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); fl = 20*pow(250, tri); }; // bandpass0_bandstop1_jump_test = no.noise : fi.bandpass0_bandstop1(0, 2, fl, 3*fl) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; fl = 20*pow(250, sq); }; // ``` //------------------------------------------------- declare bandpass0_bandstop1 author "Julius O. Smith III"; declare bandpass0_bandstop1 copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare bandpass0_bandstop1 license "LicenseRef-STK-4.3"; bandpass0_bandstop1(s,Nh,fl,fu) = bpbsr(s,Nh,Nh,fl,fu) with { // s-plane lowpass prototype cutoff (bandwidth) and center frequency: w1 = _bandedges(fl,fu) : _,!; wc = _bandedges(fl,fu) : !,_; bpbsr(s,0,Nh,fl,fu) = _; bpbsr(s,1,Nh,fl,fu) = tf1sb(s,1-s,1,w1,wc); bpbsr(s,O,Nh,fl,fu) = bpbsr(s,O-2,Nh,fl,fu) : tf2sb(s,0,(1-s),a1s,1,w1,wc) with { parity = Nh % 2; S = (O-parity)/2; // current section number a1s = -2*cos(-1*ma.PI + (1-parity)*ma.PI/(2*Nh) + (S-1+parity)*ma.PI/Nh); }; }; // Analog band edges of bandpass, bandstop, bandpass6e and bandpass12e: fl and // fu prewarped for the bilinear transform with the scale 2*SR of tf2sb and // tf1sb, fu clamped to [0, SR/2) (as in _tanhalf) and fl to [0, fu]. Returns // (w1, wc): the s-plane lowpass prototype cutoff, which is the bandwidth, and // the center frequency. fl = 0 gives wc = 0 (tf2sb and tf1sb then reduce to // the lowpass at fu), fl = fu gives w1 = 0. _bandedges(fl,fu) = w1, wc with { c = 2.0*ma.SR; wua = c*tan(max(0, min(2*ma.PI*fu/c, 0.499*ma.PI))); // analog upper cutoff wla = min(wua, c*tan(max(0, min(2*ma.PI*fl/c, 0.499*ma.PI)))); // analog lower cutoff wc = sqrt(wla*wua); w1 = max(0, wua - wc^2/max(wua, ma.MIN)); }; //===========================Elliptic Bandpass Filters==================================== //======================================================================================== //---------------------`(fi.)bandpass6e`----------------------------- // Order 6 elliptic bandpass filter analogous to `bandpass(3)`: a third-order // elliptic lowpass prototype mapped to a bandpass, with a 0.2 dB equiripple // passband between `fl` and `fu`. // // #### Usage // // ``` // _ : bandpass6e(fl,fu) : _ // ``` // // Where: // // * `fl`: lower passband edge frequency in Hz (-0.2 dB), clamped to [0, `fu`] // (0 gives the elliptic lowpass at `fu`; the width is floored at 5e-4 times // the center frequency; see `bandpass`) // * `fu`: upper passband edge frequency in Hz (-0.2 dB), clamped to [0, 0.499*SR] // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // bandpass6e_test = src : fi.bandpass6e(500, 1500); // bandpass6e_zero_freq_test = no.noise : fi.bandpass6e(1000*max(0, 1 - ba.time/12000) + 1000*(ba.time >= 24000), 2000); // bandpass6e_slider_test = no.noise : fi.bandpass6e(hslider("fl", 500, 20, 20000, 1), hslider("fu", 1500, 20, 20000, 1)); // bandpass6e_modulated_test = no.noise : fi.bandpass6e(fl, 3*fl) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); fl = 20*pow(250, tri); }; // bandpass6e_jump_test = no.noise : fi.bandpass6e(fl, 3*fl) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; fl = 20*pow(250, sq); }; // ``` //-------------------------------------------------------------- declare bandpass6e author "Julius O. Smith III"; declare bandpass6e copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare bandpass6e license "LicenseRef-STK-4.3"; bandpass6e(fl,fu) = tf2sb(b21,b11,b01,a11,a01,w1,wc) : tf1sb(0,1,a02,w1,wc) with { a11 = 0.802636764161030; // In octave: format long; poly(p(1:2)) a01 = 1.412270893774204; a02 = 0.822445908998816; // poly(p(3)) b21 = 0.019809144837789; // poly(z) b11 = 0; b01 = 1.161516418982696; // s-plane lowpass prototype cutoff (bandwidth) and center frequency: w1 = _bandedges(fl,fu) : _,!; wc = _bandedges(fl,fu) : !,_; }; //----------------------`(fi.)bandpass12e`--------------------------- // Order 12 elliptic bandpass filter analogous to `bandpass(6)`: a sixth-order // elliptic lowpass prototype mapped to a bandpass, with a 0.2 dB equiripple // passband between `fl` and `fu`. // // #### Usage // // ``` // _ : bandpass12e(fl,fu) : _ // ``` // // Where: // // * `fl`: lower passband edge frequency in Hz (-0.2 dB), clamped to [0, `fu`] // (0 gives the elliptic lowpass at `fu`; the width is floored at 5e-4 times // the center frequency; see `bandpass`) // * `fu`: upper passband edge frequency in Hz (-0.2 dB), clamped to [0, 0.499*SR] // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // bandpass12e_test = src : fi.bandpass12e(500, 1500); // bandpass12e_zero_freq_test = no.noise : fi.bandpass12e(1000*max(0, 1 - ba.time/12000) + 1000*(ba.time >= 24000), 2000); // bandpass12e_slider_test = no.noise : fi.bandpass12e(hslider("fl", 500, 20, 20000, 1), hslider("fu", 1500, 20, 20000, 1)); // bandpass12e_modulated_test = no.noise : fi.bandpass12e(fl, 3*fl) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); fl = 20*pow(250, tri); }; // bandpass12e_jump_test = no.noise : fi.bandpass12e(fl, 3*fl) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; fl = 20*pow(250, sq); }; // ``` //-------------------------------------------------------------- declare bandpass12e author "Julius O. Smith III"; declare bandpass12e copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare bandpass12e license "LicenseRef-STK-4.3"; bandpass12e(fl,fu) = tf2sb(b21,b11,b01,a11,a01,w1,wc) : tf2sb(b22,b12,b02,a12,a02,w1,wc) : tf2sb(b23,b13,b03,a13,a03,w1,wc) with { // octave script output: b21 = 0.000099999997055; a21 = 1; b11 = 0; a11 = 0.782413046821645; b01 = 0.000433227200555; a01 = 0.245291508706160; b22 = 1; a22 = 1; b12 = 0; a12 = 0.512478641889141; b02 = 7.621731298870603; a02 = 0.689621364484675; b23 = 1; a23 = 1; b13 = 0; a13 = 0.168404871113589; b03 = 53.536152954556727; a03 = 1.069358407707312; // s-plane lowpass prototype cutoff (bandwidth) and center frequency: w1 = _bandedges(fl,fu) : _,!; wc = _bandedges(fl,fu) : !,_; }; //------------------------`(fi.)pospass`--------------------------- // Positive-Pass Filter (single-side-band filter). // // #### Usage // // ``` // _ : pospass(N,fc) : _,_ // ``` // // where // // * `N`: filter order (Butterworth bandpass for positive frequencies). // * `fc`: lower bandpass cutoff frequency in Hz. // - Highpass cutoff frequency at ma.SR/2 - fc Hz. // // #### Example test program // // * See `dm.pospass_demo` // * Look at frequency response // // #### Method // // A filter passing only positive frequencies can be made from a // half-band lowpass by modulating it up to the positive-frequency range. // Equivalently, down-modulate the input signal using a complex sinusoid at -SR/4 Hz, // lowpass it with a half-band filter, and modulate back up by SR/4 Hz. // In Faust/math notation: // $$pospass(N) = \ast(e^{-j\frac{\pi}{2}n}) : \mbox{lowpass(N,SR/4)} : \ast(e^{j\frac{\pi}{2}n})$$ // // An approximation to the Hilbert transform is given by the // imaginary output signal: // // ``` // hilbert(N) = pospass(N) : !,*(2); // ``` // // #### Test // ``` // fi = library("filters.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // os = library("oscillators.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // pospass_test = src : fi.pospass(3, 1000); // pospass_slider_test = no.noise : fi.pospass(3, hslider("fc", 1000, 20, 20000, 1)); // pospass_modulated_test = no.noise : fi.pospass(3, 20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // pospass_jump_test = no.noise : fi.pospass(3, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### References // // * // * // * //------------------------------------------------------------ declare pospass author "Julius O. Smith III"; declare pospass copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare pospass license "LicenseRef-STK-4.3"; pospass(N,fc) = fi._pospass0(lpf) with { fcs = ma.SR/4 - fc; // Upper lowpass cutoff = (SR/2 - fc) - SR/4 lpf = fi.lowpass(N,fcs); // Butterworth lowpass }; //-----------------------------`(fi.)pospass6e`-------------------------------- // Positive-pass filter like `pospass`, but built on the order-6 elliptic // lowpass `lowpass6e` instead of a Butterworth lowpass: a steeper // transition band for the same order. // // #### Usage // // ``` // _ : pospass6e(fc) : _,_ // ``` // // Where: // // * `fc`: lower cutoff frequency in Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // pospass6e_test = os.tosc(440) : fi.pospass6e(100); // pospass6e_slider_test = no.noise : fi.pospass6e(hslider("fc", 100, 20, 20000, 1)); // pospass6e_modulated_test = no.noise : fi.pospass6e(20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // pospass6e_jump_test = no.noise : fi.pospass6e(20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //------------------------------------------------------------ declare pospass6e author "Julius O. Smith III"; declare pospass6e copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare pospass6e license "LicenseRef-STK-4.3"; pospass6e(fc) = fi._pospass0(lpf) with { lpf = fi.lowpass6e(ma.SR/4 - fc); // Elliptic lowpass, order 6 }; _pospass0(lpf) = unmodulate : lpf, lpf : modulate with { c = 1-1' : +~(*(-1):mem); // complex sinusoid rotating at SR/4 s = c'; // ||: 0, 1, 0, -1 :|| unmodulate = _ <: *(c),*(-s); // subtract SR/4 from all input frequencies modulate(x,y) = c*x-s*y, c*y + s*x; // add SR/4 to all frequencies }; //------------------------`(fi.)hilbert`--------------------------- // Approximate Hilbert transform: twice the imaginary part of the analytic // signal computed by `fi.pospass`, i.e. a signal in quadrature (-90 degrees) // with the in-phase (real) output of the same filter. This is the one-line // construction given in the `fi.pospass` documentation. // // The output carries the group delay of the underlying filter, so pair it // with the real part of `fi.pospass` (not with the raw input) when phase // alignment matters. Accuracy improves with the order `N` and with the input // frequency: rejecting the negative-frequency image of a component at `f` Hz // requires the internal half-band lowpass to cut inside a transition band of // width `2*f`, so low frequencies demand high orders (measured at 48 kHz: // with `N = 8`, a 5 kHz sine yields an analytic pair with 0.5% envelope // ripple and quadrature to 1e-4, while a 1 kHz sine needs `N` around 32 for // comparable quality). // // #### Usage // // ``` // _ : hilbert(N,fc) : _ // ``` // // Where: // // * `N`: order of the underlying Butterworth positive-pass filter (a constant numerical expression) // * `fc`: lower cutoff frequency in Hz of the positive-pass filter // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // hilbert_test = os.tosc(440) : fi.hilbert(4, 20); // hilbert_slider_test = no.noise : fi.hilbert(4, hslider("fc", 20, 5, 500, 1)); // hilbert_modulated_test = no.noise : fi.hilbert(4, 10*pow(10, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // hilbert_jump_test = no.noise : fi.hilbert(4, 10*pow(10, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### References // // * // * //------------------------------------------------------------ declare hilbert author "Julius O. Smith III"; declare hilbert copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare hilbert license "LicenseRef-STK-4.3"; hilbert(N,fc) = fi.pospass(N,fc) : !,*(2.0); //=================Parametric Equalizers (Shelf, Peaking)================================= // Parametric Equalizers (Shelf, Peaking). // // #### References // // * // * // * Digital Audio Signal Processing, Udo Zolzer, Wiley, 1999, p. 124 // * // * // * maxmsp.lib in the Faust distribution // * bandfilter.dsp in the faust2pd distribution //======================================================================================== //----------------------`(fi.)lowshelf`---------------------- // Order-`N` "low shelf" filter (gain boost|cut between dc and some frequency) // `low_shelf` is a standard Faust function. // // #### Usage // // ``` // _ : lowshelf(N,L0,fx) : _ // _ : low_shelf(L0,fx) : _ // default case (order 3) // _ : lowshelf_other_freq(N,L0,fx) : _ // ``` // // Where: // // * `N`: filter order 1, 3, 5, ... (odd only, default should be 3, a constant numerical expression) // * `L0`: desired level (dB) between dc and fx (boost `L0>0` or cut `L0<0`) // * `fx`: -3dB frequency of lowpass band (`L0>0`) or upper band (`L0<0`) // (see "SHELF SHAPE" below). // // The gain at SR/2 is constrained to be 1. // The generalization to arbitrary odd orders is based on the well known // fact that odd-order Butterworth band-splits are allpass-complementary // (see filterbank documentation below for references). // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // lowshelf_test = src : fi.lowshelf(3, 6, 500); // lowshelf_slider_test = no.noise : fi.lowshelf(3, hslider("L0", 6, -24, 24, 0.1), hslider("fc", 500, 20, 20000, 1)); // lowshelf_modulated_test = no.noise : fi.lowshelf(3, 6, 20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // lowshelf_jump_test = no.noise : fi.lowshelf(3, 6, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### Shelf Shape // The magnitude frequency response is approximately piecewise-linear // on a log-log plot ("BODE PLOT"). The Bode "stick diagram" approximation // L(lf) is easy to state in dB versus dB-frequency lf = dB(f): // // * L0 > 0: // * L(lf) = L0, f between 0 and fx = 1st corner frequency; // * L(lf) = L0 - N * (lf - lfx), f between fx and f2 = 2nd corner frequency; // * L(lf) = 0, lf > lf2. // * lf2 = lfx + L0/N = dB-frequency at which level gets back to 0 dB. // * L0 < 0: // * L(lf) = L0, f between 0 and f1 = 1st corner frequency; // * L(lf) = - N * (lfx - lf), f between f1 and lfx = 2nd corner frequency; // * L(lf) = 0, lf > lfx. // * lf1 = lfx + L0/N = dB-frequency at which level goes up from L0. // // See `lowshelf_other_freq`. // // #### References // // * See "Parametric Equalizers" above for references regarding `low_shelf`, `high_shelf`, and `peak_eq`. //-------------------------------------------------------------- declare lowshelf author "Julius O. Smith III"; declare lowshelf copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare lowshelf license "LicenseRef-STK-4.3"; // trapezoidal sections in every precision (L = ma.MAX): a shelf cutoff is // usually a control that moves, where the direct form rings more lowshelf(N,L0,fx) = filterbank_tpt_df(ma.MAX,N,(fx)) : _, *(ba.db2linear(L0)) :> _; // Special cases and optimization: //----------------------`(fi.)low_shelf`---------------------- // Default low shelf filter: 3rd-order Butterworth case of `fi.lowshelf` // (`low_shelf = lowshelf(3)`), boosting or cutting by `L0` dB between dc and // `fx`. `low_shelf` is a standard Faust function. See `fi.lowshelf` for the // full documentation, including the shelf shape. // // #### Usage // // ``` // _ : low_shelf(L0,fx) : _ // ``` // // Where: // // * `L0`: desired level (dB) between dc and `fx` (boost `L0>0` or cut `L0<0`) // * `fx`: transition frequency in Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // low_shelf_test = src : fi.low_shelf(6, 500); // low_shelf_slider_test = no.noise : fi.low_shelf(hslider("L0", 6, -24, 24, 0.1), hslider("fc", 500, 20, 20000, 1)); // low_shelf_modulated_test = no.noise : fi.low_shelf(6, 20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // low_shelf_jump_test = no.noise : fi.low_shelf(6, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //----------------------------------------------- declare low_shelf author "Julius O. Smith III"; declare low_shelf copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare low_shelf license "LicenseRef-STK-4.3"; low_shelf = lowshelf(3); // default = 3rd order Butterworth //-----------------------`(fi.)low_shelf1`----------------------- // First-order low shelf filter, an optimized special case of `fi.lowshelf(1)`: // boosts or cuts by `L0` dB between dc and `fx`. // // #### Usage // // ``` // _ : low_shelf1(L0,fx) : _ // ``` // // Where: // // * `L0`: desired level (dB) between dc and `fx` (boost `L0>0` or cut `L0<0`) // * `fx`: transition frequency in Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // low_shelf1_test = fi.low_shelf1(2, 500, src); // low_shelf1_slider_test = no.noise : fi.low_shelf1(hslider("L0", 2, -24, 24, 0.1), hslider("fc", 500, 20, 20000, 1)); // low_shelf1_modulated_test = no.noise : fi.low_shelf1(2, 20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // low_shelf1_jump_test = no.noise : fi.low_shelf1(2, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //----------------------------------------------- declare low_shelf1 author "Julius O. Smith III"; declare low_shelf1 copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare low_shelf1 license "LicenseRef-STK-4.3"; low_shelf1(L0,fx,x) = x + (ba.db2linear(L0)-1)*lowpass(1,fx,x); // optimized //----------------------`(fi.)low_shelf1_l`---------------------- // First-order low shelf filter like `fi.low_shelf1`, but taking a LINEAR gain // `G0` instead of a level in dB (avoids computing `ba.db2linear` at run time // when the gain is already linear). // // #### Usage // // ``` // _ : low_shelf1_l(G0,fx) : _ // ``` // // Where: // // * `G0`: desired linear gain between dc and `fx` // * `fx`: transition frequency in Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // low_shelf1_l_test = fi.low_shelf1_l(2, 500, src); // low_shelf1_l_slider_test = no.noise : fi.low_shelf1_l(hslider("G0", 2, 0, 16, 0.01), hslider("fc", 500, 20, 20000, 1)); // low_shelf1_l_modulated_test = no.noise : fi.low_shelf1_l(2, 20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // low_shelf1_l_jump_test = no.noise : fi.low_shelf1_l(2, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //----------------------------------------------- declare low_shelf1_l author "Julius O. Smith III"; declare low_shelf1_l copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare low_shelf1_l license "LicenseRef-STK-4.3"; low_shelf1_l(G0,fx,x) = x + (G0-1)*lowpass(1,fx,x); // optimized //----------------------`(fi.)lowshelf_other_freq`---------------------- // Second corner frequency of a low shelf: given the shelf order `N`, level // `L0` and transition frequency `fx` of `fi.lowshelf(N,L0,fx)`, returns the // frequency at which the response returns to 0 dB (see the "Shelf Shape" // section of `fi.lowshelf`). This is a frequency computation, not a filter. // // #### Usage // // ``` // lowshelf_other_freq(N,L0,fx) : _ // ``` // // Where: // // * `N`: shelf order (odd) // * `L0`: shelf level in dB // * `fx`: transition frequency in Hz // // #### Test // ``` // fi = library("filters.lib"); // ba = library("basics.lib"); // ma = library("maths.lib"); // lowshelf_other_freq_test = fi.lowshelf_other_freq(3, 6, 500); // lowshelf_other_freq_slider_test = fi.lowshelf_other_freq(3, hslider("L0", 6, -24, 24, 0.1), hslider("fc", 500, 20, 20000, 1)); // lowshelf_other_freq_modulated_test = fi.lowshelf_other_freq(3, 6, 20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // lowshelf_other_freq_jump_test = fi.lowshelf_other_freq(3, 6, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //----------------------------------------------- declare lowshelf_other_freq author "Julius O. Smith III"; declare lowshelf_other_freq copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare lowshelf_other_freq license "LicenseRef-STK-4.3"; lowshelf_other_freq(N, L0, fx) = ba.db2linear(ba.linear2db(fx) + L0/N); // convenience //--------------`(fi.)highshelf`-------------- // Order-`N` "high shelf" filter (gain boost|cut above some frequency). // `high_shelf` is a standard Faust function. // // #### Usage // // ``` // _ : highshelf(N,Lpi,fx) : _ // _ : high_shelf(L0,fx) : _ // default case (order 3) // _ : highshelf_other_freq(N,Lpi,fx) : _ // ``` // // Where: // // * `N`: filter order 1, 3, 5, ... (odd only, a constant numerical expression). // * `Lpi`: desired level (dB) between fx and SR/2 (boost Lpi>0 or cut Lpi<0) // * `fx`: -3dB frequency of highpass band (L0>0) or lower band (L0<0) // (Use highshelf_other_freq() below to find the other one.) // // The gain at dc is constrained to be 1. // See `lowshelf` documentation above for more details on shelf shape. // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // highshelf_test = src : fi.highshelf(3, 6, 2000); // highshelf_slider_test = no.noise : fi.highshelf(3, hslider("Lpi", 6, -24, 24, 0.1), hslider("fc", 2000, 20, 20000, 1)); // highshelf_modulated_test = no.noise : fi.highshelf(3, 6, 20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // highshelf_jump_test = no.noise : fi.highshelf(3, 6, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### References // // * See "Parametric Equalizers" above for references regarding `low_shelf`, `high_shelf`, and `peak_eq`. //-------------------------------------------------------------- declare highshelf author "Julius O. Smith III"; declare highshelf copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare highshelf license "LicenseRef-STK-4.3"; // trapezoidal sections in every precision, as for lowshelf highshelf(N,Lpi,fx) = filterbank_tpt_df(ma.MAX,N,(fx)) : *(ba.db2linear(Lpi)), _ :> _; // Special cases and optimization: //----------------------`(fi.)high_shelf`---------------------- // Default high shelf filter: 3rd-order Butterworth case of `fi.highshelf` // (`high_shelf = highshelf(3)`), boosting or cutting by `Lpi` dB between `fx` // and SR/2. `high_shelf` is a standard Faust function. See `fi.highshelf` // for the full documentation. // // #### Usage // // ``` // _ : high_shelf(Lpi,fx) : _ // ``` // // Where: // // * `Lpi`: desired level (dB) between `fx` and SR/2 (boost `Lpi>0` or cut `Lpi<0`) // * `fx`: transition frequency in Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // high_shelf_test = src : fi.high_shelf(6, 2000); // high_shelf_slider_test = no.noise : fi.high_shelf(hslider("Lpi", 6, -24, 24, 0.1), hslider("fc", 2000, 20, 20000, 1)); // high_shelf_modulated_test = no.noise : fi.high_shelf(6, 20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // high_shelf_jump_test = no.noise : fi.high_shelf(6, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //--------------------------------------------------- declare high_shelf author "Julius O. Smith III"; declare high_shelf copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare high_shelf license "LicenseRef-STK-4.3"; high_shelf = highshelf(3); // default = 3rd order Butterworth //----------------------`(fi.)high_shelf1`---------------------- // First-order high shelf filter, an optimized special case of // `fi.highshelf(1)`: boosts or cuts by `Lpi` dB between `fx` and SR/2. // // #### Usage // // ``` // _ : high_shelf1(Lpi,fx) : _ // ``` // // Where: // // * `Lpi`: desired level (dB) between `fx` and SR/2 (boost `Lpi>0` or cut `Lpi<0`) // * `fx`: transition frequency in Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // high_shelf1_test = fi.high_shelf1(6, 2000, src); // high_shelf1_slider_test = no.noise : fi.high_shelf1(hslider("Lpi", 6, -24, 24, 0.1), hslider("fc", 2000, 20, 20000, 1)); // high_shelf1_modulated_test = no.noise : fi.high_shelf1(6, 20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // high_shelf1_jump_test = no.noise : fi.high_shelf1(6, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //--------------------------------------------------- declare high_shelf1 author "Julius O. Smith III"; declare high_shelf1 copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare high_shelf1 license "LicenseRef-STK-4.3"; high_shelf1(Lpi,fx,x) = x + (ba.db2linear(Lpi)-1)*highpass(1,fx,x); // optimized //----------------------`(fi.)high_shelf1_l`---------------------- // First-order high shelf filter like `fi.high_shelf1`, but taking a LINEAR // gain `Gpi` instead of a level in dB. // // #### Usage // // ``` // _ : high_shelf1_l(Gpi,fx) : _ // ``` // // Where: // // * `Gpi`: desired linear gain between `fx` and SR/2 // * `fx`: transition frequency in Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // high_shelf1_l_test = fi.high_shelf1_l(2, 2000, src); // high_shelf1_l_slider_test = no.noise : fi.high_shelf1_l(hslider("Gpi", 2, 0, 16, 0.01), hslider("fc", 2000, 20, 20000, 1)); // high_shelf1_l_modulated_test = no.noise : fi.high_shelf1_l(2, 20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // high_shelf1_l_jump_test = no.noise : fi.high_shelf1_l(2, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //--------------------------------------------------- declare high_shelf1_l author "Julius O. Smith III"; declare high_shelf1_l copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare high_shelf1_l license "LicenseRef-STK-4.3"; high_shelf1_l(Gpi,fx,x) = x + (Gpi-1)*highpass(1,fx,x); //optimized // shelf transitions between frequency fx and this one: //----------------------`(fi.)highshelf_other_freq`---------------------- // Second corner frequency of a high shelf: given the shelf order `N`, level // `Lpi` and transition frequency `fx` of `fi.highshelf(N,Lpi,fx)`, returns // the frequency at which the response leaves 0 dB. This is a frequency // computation, not a filter. // // #### Usage // // ``` // highshelf_other_freq(N,Lpi,fx) : _ // ``` // // Where: // // * `N`: shelf order (odd) // * `Lpi`: shelf level in dB // * `fx`: transition frequency in Hz // // #### Test // ``` // fi = library("filters.lib"); // ba = library("basics.lib"); // ma = library("maths.lib"); // highshelf_other_freq_test = fi.highshelf_other_freq(3, 6, 2000); // highshelf_other_freq_slider_test = fi.highshelf_other_freq(3, hslider("Lpi", 6, -24, 24, 0.1), hslider("fc", 2000, 20, 20000, 1)); // highshelf_other_freq_modulated_test = fi.highshelf_other_freq(3, 6, 20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // highshelf_other_freq_jump_test = fi.highshelf_other_freq(3, 6, 20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //--------------------------------------------------- declare highshelf_other_freq author "Julius O. Smith III"; declare highshelf_other_freq copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare highshelf_other_freq license "LicenseRef-STK-4.3"; highshelf_other_freq(N, Lpi, fx) = ba.db2linear(ba.linear2db(fx) - Lpi/N); //-------------------`(fi.)peak_eq`------------------------------ // Second order "peaking equalizer" section (gain boost or cut near some frequency) // Also called a "parametric equalizer" section. // `peak_eq` is a standard Faust function. // // #### Usage // // ``` // _ : peak_eq(Lfx,fx,B) : _ // ``` // // Where: // // * `Lfx`: level (dB) at fx (boost Lfx>0 or cut Lfx<0) // * `fx`: peak frequency (Hz), clamped to [0.001, 0.499*SR]: the bandwidth // prewarping divides by `sin(2*PI*fx/SR)`, which is 0 at 0 and SR/2. As // `fx` goes to 0, the peak becomes a shelf of level `Lfx` below about `B` Hz. // * `B`: bandwidth of the peak in Hz (B = fx/Q): the bandwidth of the // resonance (boost) or anti-resonance (cut), which is the -3 dB bandwidth // about fx when |Lfx| is large // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // peak_eq_test = src : fi.peak_eq(6, 1000, 200); // peak_eq_zero_freq_test = no.noise : fi.peak_eq(6, 1000*max(0, 1 - ba.time/12000) + 1000*(ba.time >= 24000), 100); // peak_eq_slider_test = no.noise : fi.peak_eq(hslider("Lfx", 6, -24, 24, 0.1), hslider("fc", 1000, 20, 20000, 1), hslider("B", 200, 1, 5000, 1)); // peak_eq_modulated_test = no.noise : fi.peak_eq(6, fx, fx/5) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); fx = 20*pow(250, tri); }; // peak_eq_jump_test = no.noise : fi.peak_eq(6, fx, fx/5) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; fx = 20*pow(250, sq); }; // ``` // // #### References // // * See "Parametric Equalizers" above for references regarding `low_shelf`, `high_shelf`, and `peak_eq`. //-------------------------------------------------------------- declare peak_eq author "Julius O. Smith III"; declare peak_eq copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare peak_eq license "LicenseRef-STK-4.3"; peak_eq(Lfx,fx,B) = tf2s(1,b1s,1,a1s,1,wx) with { T = float(1.0/ma.SR); Bw = B*T/sin(wx*T); // prewarp s-bandwidth for more accuracy in z-plane a1 = 2*ma.PI*Bw; // = 1/Q, the s-plane bandwidth normalized by wx b1 = g*a1; g = ba.db2linear(abs(Lfx)); b1s = select2(Lfx>0,a1,b1); // When Lfx>0, pole dominates bandwidth a1s = select2(Lfx>0,b1,a1); // When Lfx<0, zero dominates // fx clamped to [0.001, 0.499*SR]: sin(wx*T) = 0 at fx = 0 or SR/2 // would make Bw infinite (and negative above SR/2) wx = 2*ma.PI*max(0.001, min(fx, 0.499*ma.SR)); }; //--------------------`(fi.)peak_eq_cq`---------------------------- // Constant-Q second order peaking equalizer section. // // #### Usage // // ``` // _ : peak_eq_cq(Lfx,fx,Q) : _ // ``` // // Where: // // * `Lfx`: level (dB) at fx // * `fx`: boost or cut frequency (Hz) // * `Q`: "Quality factor" = fx/B where B = bandwidth of peak in Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // peak_eq_cq_test = src : fi.peak_eq_cq(6, 1000, 4); // peak_eq_cq_slider_test = no.noise : fi.peak_eq_cq(hslider("Lfx", 6, -24, 24, 0.1), hslider("fc", 1000, 20, 20000, 1), hslider("Q", 4, 0.5, 20, 0.01)); // peak_eq_cq_modulated_test = no.noise : fi.peak_eq_cq(6, 20*pow(250, tri), 4) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // peak_eq_cq_jump_test = no.noise : fi.peak_eq_cq(6, 20*pow(250, sq), 4) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### References // // * See "Parametric Equalizers" above for references regarding `low_shelf`, `high_shelf`, and `peak_eq`. //------------------------------------------------------------ declare peak_eq_cq author "Julius O. Smith III"; declare peak_eq_cq copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare peak_eq_cq license "LicenseRef-STK-4.3"; peak_eq_cq(Lfx,fx,Q) = peak_eq(Lfx,fx,fx/Q); //-------------------`(fi.)peak_eq_rm`-------------------------- // Regalia-Mitra second order peaking equalizer section: // `H(z) = (1 + A(z))/2 + K (1 - A(z))/2`, with `A(z)` the second order // allpass centered at `fx` and `K = 10^(Lfx/20)`. At `Lfx = 0` (K = 1) the // section is the identity. It is realized as a trapezoidal state-variable // filter, `x + (K - 1)*k*bp`, which stays accurate in single precision at low // `fx` and near the Nyquist limit, and continuous when `fx` jumps or moves at // audio rate: its states hold `k*bp`, at the output level. // // #### Usage // // ``` // _ : peak_eq_rm(Lfx,fx,tanPiBT) : _ // ``` // // Where: // // * `Lfx`: level (dB) at fx // * `fx`: boost or cut frequency (Hz), from 0 (where the section becomes a // first-order shelf) to `ma.SR/2` (the mirror image of that shelf, at the // Nyquist limit). Above `ma.SR/4` the section runs mirrored, as in // `tf2_tpt`, so that a peak near the Nyquist limit is as accurate as one // near 0 Hz // * `tanPiBT`: `tan(PI*B/SR)`, where B = -3dB bandwidth (Hz) when 10^(Lfx/20) = 0 // ~ PI*B/SR for narrow bandwidths B // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ma = library("maths.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // src = os.tosc(440); // peak_eq_rm_test = src : fi.peak_eq_rm(6, 1000, tan(ma.PI*200/ma.SR)); // peak_eq_rm_slider_test = no.noise : fi.peak_eq_rm(hslider("Lfx", 6, -24, 24, 0.1), hslider("fc", 1000, 20, 20000, 1), tan(ma.PI*hslider("B", 200, 1, 5000, 1)/ma.SR)); // peak_eq_rm_modulated_test = no.noise : fi.peak_eq_rm(6, fx, tan(ma.PI*fx/5/ma.SR)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); fx = 20*pow(250, tri); }; // peak_eq_rm_jump_test = no.noise : fi.peak_eq_rm(6, fx, tan(ma.PI*fx/5/ma.SR)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; fx = 20*pow(250.0, sq); }; // peak_eq_rm_unity_test = no.noise : fi.peak_eq_rm(0, 1000, tan(ma.PI*200/ma.SR)); // peak_eq_rm_audio_modulated_test = 0.1*no.noise : fi.peak_eq_rm(6, fc, tan(ma.PI*fc/10/ma.SR)) with { fc = max(20, 1000*(1 + 0.9*os.tosc(500))); }; // peak_eq_rm_dc_test = no.noise : fi.peak_eq_rm(6, 0, tan(ma.PI*200/ma.SR)); // peak_eq_rm_nyquist_test = no.noise : fi.peak_eq_rm(6, ma.SR/2, tan(ma.PI*200/ma.SR)); // peak_eq_rm_high_test = no.noise : fi.peak_eq_rm(6, ma.SR/2 - 100, tan(ma.PI*200/ma.SR)); // peak_eq_rm_mirror_modulated_test = no.noise : fi.peak_eq_rm(6, fx, tan(ma.PI*1000/ma.SR)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); fx = ma.SR*(0.1 + 0.35*tri); }; // peak_eq_rm_mirror_jump_test = no.noise : fi.peak_eq_rm(6, fx, tan(ma.PI*1000/ma.SR)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; fx = ma.SR*select2(sq, 0.1, 0.45); }; // peak_eq_rm_low_jump_test = no.noise : fi.peak_eq_rm(12, fx, tan(ma.PI*1000/ma.SR)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; fx = select2(sq, 2400, 24); }; // ``` // // #### References // // P.A. Regalia, S.K. Mitra, and P.P. Vaidyanathan, // "The Digital All-Pass Filter: A Versatile Signal Processing Building Block" // Proceedings of the IEEE, 76(1):19-37, Jan. 1988. (See pp. 29-30.) // See also "Parametric Equalizers" above for references on shelf // and peaking equalizers in general. // // Julius O. Smith III, "Digital State-Variable Filters", for the trapezoidal // (TPT) state-variable filter this section runs on, and its bell output: // //------------------------------------------------------------ declare peak_eq_rm author "Julius O. Smith III"; declare peak_eq_rm copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare peak_eq_rm license "LicenseRef-STK-4.3"; peak_eq_rm(Lfx,fx,tanPiBT,x) = x + (K - 1.0)*(x*k : tick ~ (_,_) : !,!,_) with { // The allpass A(z) (a1 = -cos(2*th)*(1+k2), a2 = k2 = (1-tanPiBT)/(1+tanPiBT), // th = PI*fx/SR) is the trapezoidal state-variable allpass x - 2*k*bp with // g = tan(th) and k = 2*tanPiBT/sin(2*th), so that // (1 + A)/2 + K*(1 - A)/2 = x + (K - 1)*k*bp. The SVF runs on k*x, so that // its states hold k*bp at the output level when k jumps. // Above SR/4 the section is mirrored, as in tf2_tpt: th becomes PI/2 - th // (sin and cos exchanged, g becomes 1/g, k is unchanged) and the state // delays z^-1 become -z^-1, so the pole near z = -1 is as accurate as the // one near z = 1 below SR/4. fc = max(0, min(fx, 0.5*ma.SR)); mir = fc > 0.25*ma.SR; ms = 1 - 2*mir; // -1: the state delays are -z^-1 s0 = sin(ma.PI*fc/ma.SR); // sin(th) c0 = sin(ma.PI*(0.5*ma.SR - fc)/ma.SR); // cos(th), accurate near SR/2 // sn at least EPSILON: at fx = 0 or SR/2 the section computes the limit of // the filter, a first-order shelf, instead of dividing by 0 sn = max(select2(mir, s0, c0), ma.EPSILON); cs = select2(mir, c0, s0); isc = 1.0/(sn*cs) : min(ma.MAX); g = sn*sn*isc; // tan(th) k = tanPiBT*isc; kpg = (tanPiBT + sn*sn)*isc; // k + g d = cs*cs/(1.0 + tanPiBT); // 1/(1 + k*g + g^2) K = ba.db2linear(Lfx); // When the section changes form, s <- -g*swap(s), g of the form now in use chg = mir != mir'; tick(s1i, s2i, u) = ms*(bp + g*hp), ms*(lp + g*bp), bp with { s1 = select2(chg, s1i, (0 - g)*s2i); s2 = select2(chg, s2i, (0 - g)*s1i); hp = (u - kpg*s1 - s2)*d; bp = g*hp + s1; lp = g*bp + s2; }; }; //---------------------`(fi.)spectral_tilt`------------------------- // Spectral tilt filter, providing an arbitrary spectral rolloff factor // alpha in (-1,1), where // -1 corresponds to one pole (-6 dB per octave), and // +1 corresponds to one zero (+6 dB per octave). // In other words, alpha is the slope of the ln magnitude versus ln frequency. // For a "pinking filter" (e.g., to generate 1/f noise from white noise), // set alpha to -1/2. // // #### Usage // // ``` // _ : spectral_tilt(N,f0,bw,alpha) : _ // ``` // Where: // // * `N`: desired integer filter order, at least 2 (fixed at compile time) // * `f0`: lower frequency limit for desired roll-off band > 0 // * `bw`: bandwidth of desired roll-off band // * `alpha`: slope of roll-off desired in nepers per neper, // between -1 and 1 (ln mag / ln radian freq) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // spectral_tilt_test = src : fi.spectral_tilt(4, 200, 2000, -0.5); // spectral_tilt_slider_test = no.noise : fi.spectral_tilt(4, hslider("f0", 200, 20, 2000, 1), hslider("bw", 2000, 100, 10000, 1), hslider("alpha", -0.5, -1, 1, 0.01)); // spectral_tilt_modulated_test = no.noise : fi.spectral_tilt(4, 200, 2000, -1 + 2*tri) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // spectral_tilt_jump_test = no.noise : fi.spectral_tilt(4, 200, 2000, -1 + 2*sq) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### Example test program // // See `dm.spectral_tilt_demo` and the documentation for `no.pink_noise`. // // #### References // // J.O. Smith and H.F. Smith, // "Closed Form Fractional Integration and Differentiation via Real Exponentially Spaced Pole-Zero Pairs", // arXiv.org publication arXiv:1606.06154 [cs.CE], June 7, 2016, //------------------------------------------------------------ declare spectral_tilt author "Julius O. Smith III"; declare spectral_tilt copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare spectral_tilt license "LicenseRef-STK-4.3"; spectral_tilt(N,f0,bw,alpha) = seq(i,N,sec(i)) with { sec(i) = g * tf1s(b1,b0,a0,1) with { g = a0/b0; // unity dc-gain scaling b1 = 1.0; b0 = mzh(i); a0 = mph(i); mzh(i) = prewarp(mz(i),ma.SR,w0); // prewarping for bilinear transform mph(i) = prewarp(mp(i),ma.SR,w0); prewarp(w,SR,wp) = wp * tan(w*T/2) / tan(wp*T/2) with { T = 1/ma.SR; }; mz(i) = w0 * r ^ (-alpha+i); // minus zero i in s plane mp(i) = w0 * r ^ i; // minus pole i in s plane f0p = max(f0,ma.EPSILON); // cannot go to zero w0 = 2 * ma.PI * f0p; // radian frequency of first pole f1 = f0p + bw; // upper band limit r = (f1/f0p)^(1.0/float(N-1)); // pole ratio (2 => octave spacing) }; }; //----------------------`(fi.)levelfilter`---------------------- // Dynamic level lowpass filter. // `levelfilter` is a standard Faust function. // // #### Usage // // ``` // _ : levelfilter(L,freq) : _ // ``` // // Where: // // * `L`: level, linear between 0 and 1 (not in dB: a negative value gives NaN): // 1 passes the input unchanged, smaller values lowpass it more. The gain is // L^(4/3) at the Nyquist limit (SR/2), and 1 - L + L^(4/3) (within 1 dB of unity) at dc // * `freq`: corner frequency (-3dB point when L = 0) usually set to fundamental freq // // See `levelfilterN` for N such filters in series. // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // levelfilter_test = fi.levelfilter(0.1, 200, src); // levelfilter_slider_test = fi.levelfilter(hslider("L", 0.1, 0, 1, 0.01), hslider("fc", 200, 20, 20000, 1), no.noise); // levelfilter_modulated_test = fi.levelfilter(0.1, 20*pow(250, tri), no.noise) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // levelfilter_jump_test = fi.levelfilter(0.1, 20*pow(250, sq), no.noise) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // levelfilter_dc_test = fi.levelfilter(0.1, 200, 1); // ``` // // #### References // // * //------------------------------------------------------------ declare levelfilter author "Julius O. Smith III"; declare levelfilter copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare levelfilter license "LicenseRef-STK-4.3"; levelfilter(L,freq,x) = (L * L0 * x) + ((1.0-L) * lp2out(x)) with { L0 = pow(L,1/3); Lw = ma.PI*freq/ma.SR; // = w1 T / 2 Lgain = Lw / (1.0 + Lw); Lpole2 = (1.0 - Lw) / (1.0 + Lw); // bilinear-transform lowpass Lgain*(1+z^-1)/(1-Lpole2*z^-1): unity gain at dc lp2out = _ <: _ + _' : *(Lgain) : + ~ *(Lpole2); }; //----------------------`(fi.)levelfilterN`---------------------- // Dynamic level lowpass filter. // // #### Usage // // ``` // _ : levelfilterN(N,freq,L) : _ // ``` // // Where: // // * `N`: number of `levelfilter` in series, each with the level `L^(1/N)`, a constant numerical expression // * `freq`: corner frequency of each `levelfilter`, usually set to fundamental freq // * `L`: level, linear between 0 and 1 (see `levelfilter`); the cascade has the // same gain at the Nyquist limit as one `levelfilter` // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // levelfilterN_test = src : fi.levelfilterN(3, 200, 0.1); // levelfilterN_slider_test = no.noise : fi.levelfilterN(3, hslider("fc", 200, 20, 20000, 1), hslider("L", 0.1, 0, 1, 0.01)); // levelfilterN_modulated_test = no.noise : fi.levelfilterN(3, 20*pow(250, tri), 0.1) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // levelfilterN_jump_test = no.noise : fi.levelfilterN(3, 20*pow(250, sq), 0.1) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // levelfilterN_nyquist_test = 1 - 2*ba.period(2) : fi.levelfilterN(3, 200, 0.1); // ``` // // #### References // // * //------------------------------------------------------------ declare levelfilterN author "Julius O. Smith III"; declare levelfilterN copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare levelfilterN license "LicenseRef-STK-4.3"; levelfilterN(N,freq,L) = seq(i,N,levelfilter(pow(L,1.0/N),freq)); //=================================Mth-Octave Filter-Banks================================ // Mth-octave filter-banks split the input signal into a bank of parallel signals, one // for each spectral band. They are related to the Mth-Octave Spectrum-Analyzers in // `analysis.lib`. // The documentation of this library contains more details about the implementation. // The parameters are: // // * `M`: number of band-slices per octave (>=1), a constant numerical expression // * `N`: total number of bands (>2), a constant numerical expression // * `ftop`: upper bandlimit of the Mth-octave bands ( //======================================================================================== //------------------------`(fi.)mth_octave_filterbank[n]`, `(fi.)mth_octave_filterbank`------------------------- // Allpass-complementary filter banks based on Butterworth band-splitting. // For Butterworth band-splits, the needed delay equalizer is easily found. // Sections whose crossover is at or above `tpt_df_fmin` (2304 Hz in single // precision, 0 in double) are direct form, the others trapezoidal: see // `mth_octave_filterbank_tpt_df`. // // #### Usage // // ``` // _ : mth_octave_filterbank(O,M,ftop,N) : par(i,N,_) // Oth-order // _ : mth_octave_filterbank_alt(O,M,ftop,N) : par(i,N,_) // dc-inverted version // ``` // // Also for convenience: // // ``` // _ : mth_octave_filterbank3(M,ftop,N) : par(i,N,_) // 3rd-order Butterworth // _ : mth_octave_filterbank5(M,ftop,N) : par(i,N,_) // 5th-order Butterworth // mth_octave_filterbank_default = mth_octave_filterbank5; // ``` // // Where: // // * `O`: order of filter used to split each frequency band into two, a constant numerical expression // * `M`: number of band-slices per octave, a constant numerical expression // * `ftop`: highest band-split crossover frequency (e.g., 20 kHz) // * `N`: total number of bands (including dc and Nyquist), a constant numerical expression // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // sig = os.tosc(440); // mth_octave_filterbank_test = sig : fi.mth_octave_filterbank(3, 2, 8000, 2); // ``` //------------------------------------------------------------ declare mth_octave_filterbank author "Julius O. Smith III"; declare mth_octave_filterbank copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare mth_octave_filterbank license "LicenseRef-STK-4.3"; mth_octave_filterbank(O,M,ftop,N) = mth_octave_filterbank_tpt_df(tpt_df_fmin,O,M,ftop,N); //------------------------`(fi.)mth_octave_filterbank_tpt_df`, `(fi.)mth_octave_filterbank_alt_tpt_df`------------------------- // `mth_octave_filterbank` and `mth_octave_filterbank_alt` with an explicit // cutoff limit `L`, as `filterbank_tpt_df` is to `filterbank`: // `mth_octave_filterbank(O,M,ftop,N)` is // `mth_octave_filterbank_tpt_df(tpt_df_fmin,O,M,ftop,N)`, and likewise for // the `_alt` version (and so for `mth_octave_filterbank3`, `5` and // `_default`). // // #### Usage // // ``` // _ : mth_octave_filterbank_tpt_df(L,O,M,ftop,N) : par(i,N,_) // _ : mth_octave_filterbank_alt_tpt_df(L,O,M,ftop,N) : par(i,N,_) // ``` // // Where: // // * `L`: cutoff limit in Hz, usually `tpt_df_fmin`; `L <= 0` selects the // direct form and `L >= ma.MAX` the trapezoidal form for every crossover // * `O`, `M`, `ftop`, `N`: as for `mth_octave_filterbank` // // #### Test // ``` // fi = library("filters.lib"); // no = library("noises.lib"); // mth_octave_filterbank_tpt_df_test = no.noise : fi.mth_octave_filterbank_tpt_df(fi.tpt_df_fmin, 5, 1, 10000, 10); // mth_octave_filterbank_tpt_df_sum_test = no.noise : fi.mth_octave_filterbank_tpt_df(fi.tpt_df_fmin, 5, 1, 10000, 10) :> _; // mth_octave_filterbank_alt_tpt_df_test = no.noise : fi.mth_octave_filterbank_alt_tpt_df(fi.tpt_df_fmin, 3, 2, 8000, 10); // mth_octave_filterbank_alt_tpt_df_sum_test = no.noise : fi.mth_octave_filterbank_alt_tpt_df(fi.tpt_df_fmin, 5, 2, 8000, 10) :> _; // ``` //------------------------------------------------------------ declare mth_octave_filterbank_tpt_df author "Julius O. Smith III"; declare mth_octave_filterbank_tpt_df license "MIT"; mth_octave_filterbank_tpt_df(L,O,M,ftop,N) = an.mth_octave_analyzer_tpt_df(L,O,M,ftop,N) : delayeq(N) with { fc(n) = ftop * 2^(float(n-N+1)/float(M)); // -3dB crossover frequencies ap(n) = highpass_plus_lowpass_tpt_df(L,O,fc(n)); // delay-equalizing allpass delayeq(N) = par(i,N-2,apchain(i+1)), _, _; apchain(i) = seq(j,N-1-i,ap(j+1)); }; declare mth_octave_filterbank_alt_tpt_df author "Julius O. Smith III"; declare mth_octave_filterbank_alt_tpt_df license "MIT"; mth_octave_filterbank_alt_tpt_df(L,O,M,ftop,N) = an.mth_octave_analyzer_tpt_df(L,O,M,ftop,N) : delayeqi(N) with { fc(n) = ftop * 2^(float(n-N+1)/float(M)); // -3dB crossover frequencies // lowpass minus highpass: half the order of the 'plus' case ap(n) = highpass_minus_lowpass_tpt_df(L,O,fc(n)) : *(-1.0); delayeqi(N) = par(i,N-2,apchain(i+1)), _, *(-1.0); apchain(i) = seq(j,N-1-i,ap(j+1)); }; // dc-inverted version. This reduces the delay-equalizer order for odd O. // Negating the input signal makes the dc band noninverting // and all higher bands sign-inverted (if preferred). declare mth_octave_filterbank_alt author "Julius O. Smith III"; declare mth_octave_filterbank_alt copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare mth_octave_filterbank_alt license "LicenseRef-STK-4.3"; //------------------------`(fi.)mth_octave_filterbank_alt`------------------------- // // #### Usage // // ``` // _ : mth_octave_filterbank_alt(O,M,ftop,N) : par(i,N,_) // ``` // // Where: // // * `O`: filter order used to split each band // * `M`: number of band-slices per octave // * `ftop`: highest band-split crossover frequency // * `N`: total number of bands // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // no = library("noises.lib"); // sig = os.tosc(440); // mth_octave_filterbank_alt_test = sig : fi.mth_octave_filterbank_alt(3, 2, 8000, 2); // mth_octave_filterbank_alt_bands_test = sig : fi.mth_octave_filterbank_alt(3, 2, 8000, 5); // mth_octave_filterbank_alt_o5_sum_test = no.noise : fi.mth_octave_filterbank_alt(5, 2, 8000, 5) :> _; // ``` //---------------------------------------------------------- mth_octave_filterbank_alt(O,M,ftop,N) = mth_octave_filterbank_alt_tpt_df(tpt_df_fmin,O,M,ftop,N); // Note that even-order cases require complex coefficients. // See Vaidyanathan 1993 and papers cited there for more info. declare mth_octave_filterbank3 author "Julius O. Smith III"; declare mth_octave_filterbank3 copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare mth_octave_filterbank3 license "LicenseRef-STK-4.3"; //------------------------`(fi.)mth_octave_filterbank3`------------------------- // // #### Usage // // ``` // _ : mth_octave_filterbank3(M,ftop,N) : par(i,N,_) // ``` // // Where: // // * `M`: number of band-slices per octave // * `ftop`: highest band-split crossover frequency // * `N`: total number of bands // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // sig = os.tosc(440); // mth_octave_filterbank3_test = sig : fi.mth_octave_filterbank3(2, 8000, 2); // mth_octave_filterbank3_bands_test = sig : fi.mth_octave_filterbank3(2, 8000, 5); // ``` //---------------------------------------------------------- mth_octave_filterbank3(M,ftop,N) = mth_octave_filterbank_alt(3,M,ftop,N); declare mth_octave_filterbank5 author "Julius O. Smith III"; declare mth_octave_filterbank5 copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare mth_octave_filterbank5 license "LicenseRef-STK-4.3"; //------------------------`(fi.)mth_octave_filterbank5`------------------------- // // #### Usage // // ``` // _ : mth_octave_filterbank5(M,ftop,N) : par(i,N,_) // ``` // // Where: // // * `M`: number of band-slices per octave // * `ftop`: highest band-split crossover frequency // * `N`: total number of bands // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // sig = os.tosc(440); // mth_octave_filterbank5_test = sig : fi.mth_octave_filterbank5(2, 8000, 2); // ``` //---------------------------------------------------------- mth_octave_filterbank5(M,ftop,N) = mth_octave_filterbank(5,M,ftop,N); declare mth_octave_filterbank_default author "Julius O. Smith III"; declare mth_octave_filterbank_default copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare mth_octave_filterbank_default license "LicenseRef-STK-4.3"; //------------------------`(fi.)mth_octave_filterbank_default`------------------------- // Default Mth-octave filter bank: the 5th-order Butterworth case of // `fi.mth_octave_filterbank` (`mth_octave_filterbank_default = // mth_octave_filterbank5`). See `fi.mth_octave_filterbank[n]` for the full // documentation. // // #### Usage // // ``` // _ : mth_octave_filterbank_default(M,ftop,N) : par(i,N,_) // ``` // // Where: // // * `M`: number of band-slices per octave (a constant numerical expression) // * `ftop`: highest band-split crossover frequency in Hz // * `N`: total number of bands, including dc and Nyquist (a constant numerical expression) // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // sig = os.tosc(440); // mth_octave_filterbank_default_test = sig : fi.mth_octave_filterbank_default(2, 8000, 2); // ``` //---------------------------------------------------------- mth_octave_filterbank_default = mth_octave_filterbank5; //===============Arbitrary-Crossover Filter-Banks and Spectrum Analyzers================== // These are similar to the Mth-octave analyzers above, except that the // band-split frequencies are passed explicitly as arguments. //======================================================================================== // ACKNOWLEDGMENT // Technique for processing a variable number of signal arguments due // to Yann Orlarey (as is the entire Faust framework!) //---------------`(fi.)filterbank`-------------------------- // Filter bank. // `filterbank` is a standard Faust function. // Sections whose crossover is at or above `tpt_df_fmin` (2304 Hz in single // precision, 0 in double) are direct form, the others trapezoidal: see // `filterbank_tpt_df`. // // #### Usage // // ``` // _ : filterbank(O,freqs) : par(i,ba.count(freqs)+1,_) // Butterworth band-splits // ``` // // Where: // // * `O`: band-split filter order (odd integer required for filterbank[i], a constant numerical expression) // * `freqs`: (fc1,fc2,...,fcNs) [in numerically ascending order], where // Ns=N-1 is the number of octave band-splits // (total number of bands N=Ns+1, the number of outputs). // // If frequencies are listed explicitly as arguments, enclose them in parens: // // ``` // _ : filterbank(3,(fc1,fc2)) : _,_,_ // ``` // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // src = os.tosc(440); // filterbank_test = src : fi.filterbank(3, (500, 2000)); // ``` //--------------------------------------------------- declare filterbank author "Julius O. Smith III"; declare filterbank copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare filterbank license "LicenseRef-STK-4.3"; filterbank(O,lfreqs) = filterbank_tpt_df(tpt_df_fmin,O,lfreqs); //---------------`(fi.)filterbank_tpt_df`-------------------------- // `filterbank` with an explicit cutoff limit `L`: the band splits and delay // equalizers whose crossover is below `L` use trapezoidal sections, the // others the cheaper direct-form sections (`lowpass_tpt_df`, // `highpass_plus_lowpass_tpt_df`). `filterbank(O,freqs)` is // `filterbank_tpt_df(tpt_df_fmin,O,freqs)`: trapezoidal below 2304 Hz in // single precision, direct form everywhere in double precision. Each // section is compiled in one form only when its crossover is a constant; // for crossovers that vary (sliders), pass `L = ma.MAX` (trapezoidal) or // `L = 0` (direct form), or both forms run. // // #### Usage // // ``` // _ : filterbank_tpt_df(L,O,freqs) : par(i,ba.count(freqs)+1,_) // ``` // // Where: // // * `L`: cutoff limit in Hz, usually `tpt_df_fmin`; `L <= 0` selects the // direct form and `L >= ma.MAX` the trapezoidal form for every crossover // * `O`, `freqs`: as for `filterbank` // // #### Test // ``` // fi = library("filters.lib"); // no = library("noises.lib"); // ba = library("basics.lib"); // ma = library("maths.lib"); // filterbank_tpt_df_test = no.noise : fi.filterbank_tpt_df(fi.tpt_df_fmin, 3, (50, 500, 4000)); // filterbank_tpt_df_o5_sum_test = no.noise : fi.filterbank_tpt_df(fi.tpt_df_fmin, 5, (50, 500, 4000)) :> _; // filterbank_tpt_df_slider_test = no.noise : fi.filterbank_tpt_df(fi.tpt_df_fmin, 3, (hslider("f1", 500, 20, 10000, 1), hslider("f2", 4000, 20, 20000, 1))); // filterbank_tpt_df_modulated_test = no.noise : fi.filterbank_tpt_df(0, 3, (f1, 4*f1)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); f1 = 2500*pow(2, tri); }; // filterbank_tpt_df_jump_test = no.noise : fi.filterbank_tpt_df(ma.MAX, 3, (f1, 4*f1)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; f1 = 100*pow(10, sq); }; // ``` //--------------------------------------------------- declare filterbank_tpt_df author "Julius O. Smith III"; declare filterbank_tpt_df license "MIT"; filterbank_tpt_df(L,O,lfreqs) = an.analyzer_tpt_df(L,O,lfreqs) : delayeq(nb) with { nb = ba.count(lfreqs); fc(n) = ba.take(n, lfreqs); ap(n) = highpass_plus_lowpass_tpt_df(L,O,fc(n)); delayeq(1) = _,_; // par(i,0,...) does not fly delayeq(nb) = par(i,nb-1,apchain(nb-1-i)),_,_; apchain(0) = _; apchain(i) = ap(i) : apchain(i-1); }; //-----------------`(fi.)filterbanki`---------------------- // Inverted-dc filter bank. // Sections whose crossover is at or above `tpt_df_fmin` (2304 Hz in single // precision, 0 in double) are direct form, the others trapezoidal: see // `filterbanki_tpt_df`. // // #### Usage // // ``` // _ : filterbanki(O,freqs) : par(i,ba.count(freqs)+1,_) // Inverted-dc version // ``` // // Where: // // * `O`: band-split filter order (odd integer required for `filterbank[i]`, a constant numerical expression) // * `freqs`: (fc1,fc2,...,fcNs) [in numerically ascending order], where // Ns=N-1 is the number of octave band-splits // (total number of bands N=Ns+1, the number of outputs). // // If frequencies are listed explicitly as arguments, enclose them in parens: // // ``` // _ : filterbanki(3,(fc1,fc2)) : _,_,_ // ``` // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // no = library("noises.lib"); // src = os.tosc(440); // filterbanki_test = src : fi.filterbanki(3, (500, 2000)); // filterbanki_o5_sum_test = no.noise : fi.filterbanki(5, (500, 1000, 2000)) :> _; // ``` //--------------------------------------------------- declare filterbanki author "Julius O. Smith III"; declare filterbanki copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare filterbanki license "LicenseRef-STK-4.3"; filterbanki(O,lfreqs) = filterbanki_tpt_df(tpt_df_fmin,O,lfreqs); //-----------------`(fi.)filterbanki_tpt_df`---------------------- // `filterbanki` with an explicit cutoff limit `L`, as `filterbank_tpt_df` // is to `filterbank`: `filterbanki(O,freqs)` is // `filterbanki_tpt_df(tpt_df_fmin,O,freqs)`. // // #### Usage // // ``` // _ : filterbanki_tpt_df(L,O,freqs) : par(i,ba.count(freqs)+1,_) // ``` // // Where: // // * `L`: cutoff limit in Hz, usually `tpt_df_fmin`; `L <= 0` selects the // direct form and `L >= ma.MAX` the trapezoidal form for every crossover // * `O`, `freqs`: as for `filterbanki` // // #### Test // ``` // fi = library("filters.lib"); // no = library("noises.lib"); // ba = library("basics.lib"); // ma = library("maths.lib"); // filterbanki_tpt_df_test = no.noise : fi.filterbanki_tpt_df(fi.tpt_df_fmin, 3, (50, 500, 4000)); // filterbanki_tpt_df_o5_sum_test = no.noise : fi.filterbanki_tpt_df(fi.tpt_df_fmin, 5, (50, 500, 4000)) :> _; // filterbanki_tpt_df_slider_test = no.noise : fi.filterbanki_tpt_df(fi.tpt_df_fmin, 3, (hslider("f1", 500, 20, 10000, 1), hslider("f2", 4000, 20, 20000, 1))); // filterbanki_tpt_df_modulated_test = no.noise : fi.filterbanki_tpt_df(0, 3, (f1, 4*f1)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); f1 = 2500*pow(2, tri); }; // filterbanki_tpt_df_jump_test = no.noise : fi.filterbanki_tpt_df(ma.MAX, 3, (f1, 4*f1)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; f1 = 100*pow(10, sq); }; // ``` //--------------------------------------------------- declare filterbanki_tpt_df author "Julius O. Smith III"; declare filterbanki_tpt_df license "MIT"; filterbanki_tpt_df(L,O,lfreqs) = _ <: bsplit(nb) with { nb = ba.count(lfreqs); fc(n) = ba.take(n, lfreqs); lp(n) = lowpass_tpt_df(L,O,fc(n)); hp(n) = highpass_tpt_df(L,O,fc(n)); // lowpass minus highpass, so that each band sum hp*ap + lp*(sum below) // factors as (lp - hp)*(sum below), an allpass: ap(n) = highpass_minus_lowpass_tpt_df(L,O,fc(n)) : *(-1.0); bsplit(0) = *(-1.0); bsplit(i) = (hp(i) : delayeq(i-1)), (lp(i) <: bsplit(i-1)); delayeq(0) = _; // moving the *(-1) here inverts all outputs BUT dc delayeq(i) = ap(i) : delayeq(i-1); }; //===============State Variable Filters========================================================= // #### References // // * Solving the continuous SVF equations using trapezoidal integration // * //======================================================================================== //-----------------`(fi.)svf`---------------------- // An environment with `lp`, `bp`, `hp`, `notch`, `peak`, `ap`, `bell`, `ls`, `hs` SVF based filters. // All filters have `freq` and `Q` parameters, the `bell`, `ls`, `hs` ones also have a `gain` third parameter. // // #### Usage // // ``` // _ : svf.lp(freq, Q) : _ // _ : svf.bp(freq, Q) : _ // _ : svf.hp(freq, Q) : _ // _ : svf.notch(freq, Q) : _ // _ : svf.peak(freq, Q) : _ // _ : svf.ap(freq, Q) : _ // _ : svf.bell(freq, Q, gain) : _ // _ : svf.ls(freq, Q, gain) : _ // _ : svf.hs(freq, Q, gain) : _ // ``` // // Where: // // * `freq`: cut frequency in Hz, clamped to [0, 0.499*SR]: 0 holds the // filter's states (see the section // [Digital Filter Sections Specified as Analog Filter Sections](#digital-filter-sections-specified-as-analog-filter-sections)) // * `Q`: quality factor // * `gain`: gain in dB (`bell`, `ls` and `hs` only) // // #### Method // // Andrew Simper's trapezoidal state-variable filter: the bilinear transform // of the analog state-variable filter, prewarped at `freq`, whose outputs // are mixed into the nine responses. The delay-free loop is solved for the // highpass first, so that each state is updated by adding a small // increment. Solving it for the bandpass first, as in Simper's listings, // multiplies a state by `1/(1 + g*(g+k))`, close to 1 at a low cutoff, and // loses more than two digits of the damping in single precision: a 20 Hz // lowpass with `Q` = 10 at 192 kHz was 8.8e-4 off its double-precision // output, and is now 3.7e-6 off. See the section // [Digital Filter Sections Specified as Analog Filter Sections](#digital-filter-sections-specified-as-analog-filter-sections) // and . // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // sig = os.tosc(440); // svf_lp_test = fi.svf.lp(1000, 0.707, sig); // svf_lp_lowfc_test = no.noise : fi.svf.lp(5, 10); // svf_bp_lowfc_test = no.noise : fi.svf.bp(20, 30); // svf_nyquist_test = no.noise : fi.svf.lp(0.6*ma.SR, 1); // svf_slider_test = no.noise : fi.svf.bell(hslider("fc", 1000, 20, 20000, 1), hslider("Q", 0.707, 0.5, 20, 0.01), hslider("gain", 6, -24, 24, 0.1)); // svf_modulated_test = no.noise : fi.svf.lp(20*pow(250, tri), 0.707) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // svf_jump_test = no.noise : fi.svf.lp(20*pow(250, sq), 0.707) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // svf_bp_test = fi.svf.bp(1000, 0.707, sig); // svf_hp_test = fi.svf.hp(1000, 0.707, sig); // svf_notch_test = fi.svf.notch(1000, 0.707, sig); // svf_peak_test = fi.svf.peak(1000, 0.707, sig); // svf_ap_test = fi.svf.ap(1000, 0.707, sig); // svf_bell_test = fi.svf.bell(1000, 0.707, 6, sig); // svf_ls_test = fi.svf.ls(500, 0.707, 6, sig); // svf_hs_test = fi.svf.hs(3000, 0.707, 6, sig); // svf_audio_modulated_test = 0.1*no.noise <: fi.svf.lp(fc, 10), fi.svf.bp(fc, 10), fi.svf.hp(fc, 10) with { fc = max(20, 1000*(1 + 0.9*os.tosc(500))); }; // ``` //--------------------------------------------------- declare svf author "Oleg Nesterov"; declare svf copyright "Copyright (C) 2020 Oleg Nesterov "; declare svf license "LicenseRef-STK-4.3"; svf = environment { // Internal implementation svf(T,F,Q,G) = tick ~ (_,_) : !,!,si.dot(3, mix) with { // The delay-free loop is solved for the highpass v0 - k*v1 - v2 first, // so that each state is updated by adding a small increment: solving // it for v1 first multiplies ic1eq by 1/(1 + g*(g+k)), close to 1 at // a low cutoff, and loses the damping in single precision (see // "Digital Filter Sections Specified as Analog Filter Sections"). tick(ic1eq, ic2eq, v0) = v1 + g*hp, v2 + g*v1, v0, v1, v2 with { // multiplied by the reciprocal, which min(ma.MAX) (a no-op) keeps // the normalizer from turning back into a division: computed from // the coefficients only, it stays off the states' critical path // (a division there costs about 30 % with a modulated cutoff) hp = (v0 - (g+k)*ic1eq - ic2eq)*(1/(1 + g*(g+k)) : min(ma.MAX)); v1 = g*hp + ic1eq; v2 = g*v1 + ic2eq; }; A = pow(10.0, G/40.0); // F is clamped to [0, SR/2) as in _tanhalf: F = 0 holds the states, // F at or above SR/2 acts as just below it instead of making g // infinite or negative g = tan(max(0, min(F * ma.PI/ma.SR, 0.499*ma.PI))) : case { (7) => /(sqrt(A)); (8) => *(sqrt(A)); (t) => _; } (T); k = case { (6) => 1/(Q*A); (t) => 1/Q; } (T); mix = case { (0) => 0, 0, 1; (1) => 0, 1, 0; (2) => 1, -k, -1; (3) => 1, -k, 0; (4) => 1, -k, -2; (5) => 1, -2*k, 0; (6) => 1, k*(A*A-1), 0; (7) => 1, k*(A-1), A*A-1; (8) => A*A, k*(1-A)*A, 1-A*A; } (T); }; // External API lp(f,q) = svf(0, f, q, 0); bp(f,q) = svf(1, f, q, 0); hp(f,q) = svf(2, f, q, 0); notch(f,q) = svf(3, f, q, 0); peak(f,q) = svf(4, f, q, 0); ap(f,q) = svf(5, f, q, 0); bell(f,q,g) = svf(6, f, q, g); ls(f,q,g) = svf(7, f, q, g); hs(f,q,g) = svf(8, f, q, g); }; //-----------------`(fi.)svf_morph`-------------------- // An SVF-based filter that can smoothly morph between // being lowpass, bandpass, and highpass. // // #### Usage // // ``` // _ : svf_morph(freq, Q, blend) : _ // ``` // // Where: // // * `freq`: cutoff frequency // * `Q`: quality factor // * `blend`: [0..2] continuous, where 0 is `lowpass`, 1 is `bandpass`, and 2 is `highpass`. For performance, the value is not clamped to [0..2]. // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // sig = os.tosc(440); // svf_morph_test = fi.svf_morph(1000, 0.707, 1, sig); // svf_morph_slider_test = fi.svf_morph(hslider("fc", 1000, 20, 20000, 1), hslider("Q", 0.707, 0.5, 20, 0.01), hslider("blend", 1, 0, 2, 0.01), no.noise); // svf_morph_modulated_test = fi.svf_morph(20*pow(250, tri), 0.707, 2*tri, no.noise) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // svf_morph_jump_test = fi.svf_morph(20*pow(250, sq), 0.707, 2*sq, no.noise) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### Example test program // // ``` // process = no.noise : svf_morph(freq, q, blend) // with { // blend = hslider("Blend", 0, 0, 2, .01) : si.smoo; // q = hslider("Q", 1, 0.1, 10, .01) : si.smoo; // freq = hslider("freq", 5000, 100, 18000, 1) : si.smoo; // }; // ``` // // #### References // // //----------------------------------------------------- declare svf_morph author "David Braun and Clarence W. Rowley"; declare svf_morph copyright "Copyright (C) 2024 David Braun "; declare svf_morph license "LicenseRef-STK-4.3"; svf_morph(f, q, _b, x) = svf.lp(f,q,x)*w_LP + svf.bp(f,q,x)*w_BP + svf.hp(f,q,x)*w_HP with { b = _b - 1; // b is now -1 to 1. w_LP = max(-b, 0); w_BP = sqrt(1 - b^2); w_HP = max(b, 0); }; //-----------------`(fi.)svf_notch_morph`-------------------- // An SVF-based notch-filter that can smoothly morph between // being lowpass, notch, and highpass. // // #### Usage // // ``` // _ : svf_notch_morph(freq, Q, blend) : _ // ``` // // Where: // // * `freq`: cutoff frequency // * `Q`: quality factor // * `blend`: [0..2] continuous, where 0 is `lowpass`, 1 is `notch`, and 2 is `highpass`. For performance, the value is not clamped to [0..2]. // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // sig = os.tosc(440); // svf_notch_morph_test = fi.svf_notch_morph(1000, 0.707, 1, sig); // svf_notch_morph_slider_test = fi.svf_notch_morph(hslider("fc", 1000, 20, 20000, 1), hslider("Q", 0.707, 0.5, 20, 0.01), hslider("blend", 1, 0, 2, 0.01), no.noise); // svf_notch_morph_modulated_test = fi.svf_notch_morph(20*pow(250, tri), 0.707, 2*tri, no.noise) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // svf_notch_morph_jump_test = fi.svf_notch_morph(20*pow(250, sq), 0.707, 2*sq, no.noise) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### Example test program // // ``` // process = no.noise : svf_notch_morph(freq, q, blend) // with { // blend = hslider("Blend", 0, 0, 2, .01) : si.smoo; // q = hslider("Q", 1, 0.1, 10, .01) : si.smoo; // freq = hslider("freq", 5000, 100, 18000, 1) : si.smoo; // }; // ``` // // #### References // // //----------------------------------------------------------- declare svf_notch_morph author "David Braun and Clarence W. Rowley"; declare svf_notch_morph copyright "Copyright (C) 2024 David Braun "; declare svf_notch_morph license "LicenseRef-STK-4.3"; svf_notch_morph(f, q, _b, x) = svf.lp(f,q,x)*w_LP + svf.hp(f,q,x)*w_HP with { b = _b - 1; // b is now -1 to 1. w_LP = min(1-b, 1); w_HP = min(1+b, 1); }; //----------`(fi.)SVFTPT`--------------------------------------------------------- // // Topology-preserving transform implementation following Zavalishin's method. // // Outputs: lowpass, highpass, bandpass, normalised bandpass, notch, allpass, // peaking. // // Each individual output can be recalled with its name in the environment as in: // `SVFTPT.LP2(1000.0, .707)`. // // The 7 outputs can be recalled by using `SVF` name as in: // `SVFTPT.SVF(1000.0, .707)`. // // Even though the implementation is different, the characteristics of this // filter are comparable to those of the `svf` environment in this library. // // #### Usage: // // ``` // _ : SVFTPT.SVF(CF, Q) : si.bus(7) // _ : SVFTPT.LP2(CF, Q) : _ // _ : SVFTPT.HP2(CF, Q) : _ // _ : SVFTPT.BP2(CF, Q) : _ // _ : SVFTPT.BP2Norm(CF, Q) : _ // _ : SVFTPT.Notch2(CF, Q) : _ // _ : SVFTPT.AP2(CF, Q) : _ // _ : SVFTPT.Peaking2(CF, Q) : _ // ``` // // Where: // // * `CF`: cutoff in Hz // * `Q`: resonance // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // sig = os.tosc(440); // SVFTPT_SVF_test = fi.SVFTPT.SVF(1000, 0.707, sig); // SVFTPT_slider_test = fi.SVFTPT.SVF(hslider("fc", 1000, 20, 20000, 1), hslider("Q", 0.707, 0.5, 20, 0.01), no.noise); // SVFTPT_modulated_test = fi.SVFTPT.SVF(20*pow(250, tri), 0.707, no.noise) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // SVFTPT_jump_test = fi.SVFTPT.SVF(20*pow(250, sq), 0.707, no.noise) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // SVFTPT_LP2_test = fi.SVFTPT.LP2(1000, 0.707, sig); // SVFTPT_HP2_test = fi.SVFTPT.HP2(1000, 0.707, sig); // SVFTPT_BP2_test = fi.SVFTPT.BP2(1000, 0.707, sig); // SVFTPT_BP2Norm_test = fi.SVFTPT.BP2Norm(1000, 0.707, sig); // SVFTPT_Notch2_test = fi.SVFTPT.Notch2(1000, 0.707, sig); // SVFTPT_AP2_test = fi.SVFTPT.AP2(1000, 0.707, sig); // SVFTPT_Peaking2_test = fi.SVFTPT.Peaking2(1000, 0.707, sig); // ``` //------------------------------------------------------------------------------ declare SVFTPT author "Dario Sanfilippo"; declare SVFTPT copyright "Copyright (C) 2024 Dario Sanfilippo "; declare SVFTPT license "MIT"; SVFTPT = environment { SVF(CF, Q, x) = f ~ si.bus(2) : (! , ! , _ , _ , _ , _ , _ , _ , _) with { g = tan(CF * ma.PI * ma.T); R2 = 1.0 / Q; gPlusR2 = g + R2; f(s0, s1) = u0 , u1 , LP , HP , BP , BPNorm , Notch , AP , Peaking with { HP = (x - s0 * gPlusR2 - s1) / (1.0 + g * gPlusR2); v0 = HP * g; BP = s0 + v0; v1 = BP * g; LP = s1 + v1; BPNorm = BP * R2; Notch = x - BPNorm; AP = x - BP * (R2 + R2); Peaking = LP - HP; u0 = v0 + BP; u1 = v1 + LP; }; }; LP2(CF, Q, x) = SVF(CF, Q, x) : ba.selectn(7, 0); HP2(CF, Q, x) = SVF(CF, Q, x) : ba.selectn(7, 1); BP2(CF, Q, x) = SVF(CF, Q, x) : ba.selectn(7, 2); BP2Norm(CF, Q, x) = SVF(CF, Q, x) : ba.selectn(7, 3); Notch2(CF, Q, x) = SVF(CF, Q, x) : ba.selectn(7, 4); AP2(CF, Q, x) = SVF(CF, Q, x) : ba.selectn(7, 5); Peaking2(CF, Q, x) = SVF(CF, Q, x) : ba.selectn(7, 6); }; //----------`(fi.)dynamicSmoothing`------------------------------------------------ // // Adaptive smoother based on Andy Simper's paper. // // This filter uses both the lowpass and bandpass outputs of a // state-variable filter. The lowpass is used to smooth out the input signal, // the bandpass, which is a smoothed out version of the highpass, provides // information on the rate of change of the input. Hence, the bandpass signal // can be used to adjust the cutoff of the filter to quickly follow the input's // fast and large variations while effectively filtering out local // perturbations. // // This implementation does not use an approximation for the CF computation, // and it deploys guards to prevent overshooting with extreme sensitivity // values. // // #### Usage: // // ``` // _ : dynamicSmoothing(sensitivity, baseCF) : _ // ``` // // Where: // // * `sensitivity`: sensitivity to changes in the input signal. // The range is, theoretically, from 0 to INF, though anything between // 0.0 and 1.0 should be reasonable // * `baseCF`: cutoff frequency, in Hz, when there is no variation in the // input signal // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // sig = os.tosc(440); // dynamicSmoothing_test = fi.dynamicSmoothing(0.5, 500, sig); // dynamicSmoothing_slider_test = fi.dynamicSmoothing(hslider("sensitivity", 0.5, 0, 1, 0.01), hslider("fc", 500, 20, 20000, 1), no.noise); // dynamicSmoothing_modulated_test = fi.dynamicSmoothing(0.5, 20*pow(250, tri), no.noise) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // dynamicSmoothing_jump_test = fi.dynamicSmoothing(0.5, 20*pow(250, sq), no.noise) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### References // // * //------------------------------------------------------------------------------ declare dynamicSmoothing author "Dario Sanfilippo"; declare dynamicSmoothing copyright "Copyright (C) 2024 Dario Sanfilippo "; declare dynamicSmoothing license "MIT"; dynamicSmoothing(sensitivity, baseCF, x) = f ~ _ : ! , _ with { f(s) = SVFTPT.BP2(CF, .5 , x) , SVFTPT.LP2(CF, .5, x) with { CF = min(ma.SR * .125, baseCF + sensitivity * abs(s) * ma.SR * .5); }; }; //-----------------`(fi.)oneEuro`---------------------------------- // The One Euro Filter (1€ Filter) is an adaptive lowpass filter. // This kind of filter is commonly used in object-tracking, // not necessarily audio processing. // // #### Usage // // ``` // _ : oneEuro(derivativeCutoff, beta, minCutoff) : _ // ``` // // Where: // // * `derivativeCutoff`: Used to filter the first derivative of the input. 1 Hz is a good default. // * `beta`: "Speed" parameter where higher values reduce latency. // * `minCutoff`: Minimum cutoff frequency in Hz. Lower values remove more jitter. // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // sig = os.tosc(440); // oneEuro_test = sig : fi.oneEuro(1, 0.5, 5); // oneEuro_slider_test = no.noise : fi.oneEuro(hslider("derivativeCutoff", 1, 0.1, 10, 0.1), hslider("beta", 0.5, 0, 1, 0.01), hslider("minCutoff", 5, 0.1, 50, 0.1)); // oneEuro_modulated_test = no.noise : fi.oneEuro(1, 0.5, pow(50, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // oneEuro_jump_test = no.noise : fi.oneEuro(1, 0.5, pow(50, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### References // // * //------------------------------------------------------------------------ declare oneEuro author "David Braun"; declare oneEuro copyright "Copyright (C) 2024 by David Braun "; declare oneEuro license "MIT"; oneEuro(derivativeCutoff, beta, minCutoff) = _oneEuro ~ _ with { // exponential moving average that calculates its own alpha from a cutoff in Hz // _ : ema(cutoff) : _ ema(cutoff) = tick ~ _ with { alpha = 1.0 / (1.0 + ma.SR / (2 * ma.PI * cutoff)); tick(prev, x) = prev*(1-alpha) + x*alpha; }; _oneEuro(prev, x) = x : ema(adaptiveCutoff) with { derivative = (x - prev)*ma.SR; derivativeFiltered = derivative : ema(derivativeCutoff); adaptiveCutoff = minCutoff + beta * abs(derivativeFiltered); }; }; //===========Linkwitz-Riley 4th-order 2-way, 3-way, and 4-way crossovers===== // // The Linkwitz-Riley (LR) crossovers are designed to produce a fully-flat // magnitude response when their outputs are combined. The 4th-order // LR filters (LR4) have a 24dB/octave slope and they are rather popular audio // crossovers used in multi-band processing. // // The LR4 can be constructed by cascading two second-order Butterworth // filters. For the second-order Butterworth filters, we will use the SVF // filter implemented above by setting the Q-factor to 1.0 / sqrt(2.0). // These will be cascaded in pairs to build the LR4 highpass and lowpass. // For the phase correction, we will use the 2nd-order Butterworth allpass. // // #### References // // Zavalishin, Vadim. "The art of VA filter design." Native Instruments, Berlin, Germany (2012). //============================================================================= //----------`(fi.)lowpassLR4`--------------------------------------------------- // 4th-order Linkwitz-Riley lowpass. // // #### Usage // // ``` // _ : lowpassLR4(cf) : _ // ``` // // Where: // // * `cf`: lowpass cutoff in Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // lowpassLR4_test = src : fi.lowpassLR4(1000); // lowpassLR4_slider_test = no.noise : fi.lowpassLR4(hslider("fc", 1000, 20, 20000, 1)); // lowpassLR4_modulated_test = no.noise : fi.lowpassLR4(20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // lowpassLR4_jump_test = no.noise : fi.lowpassLR4(20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //------------------------------------------------------------------------------ declare lowpassLR4 author "Dario Sanfilippo"; declare lowpassLR4 copyright "Copyright (C) 2022 Dario Sanfilippo "; declare lowpassLR4 license "LicenseRef-STK-4.3"; lowpassLR4(cf, x) = x : seq(i, 2, svf.lp(cf, 1.0 / sqrt(2.0))); //----------`(fi.)highpassLR4`-------------------------------------------------- // 4th-order Linkwitz-Riley highpass. // // #### Usage // // ``` // _ : highpassLR4(cf) : _ // ``` // // Where: // // * `cf`: highpass cutoff in Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // highpassLR4_test = src : fi.highpassLR4(1000); // highpassLR4_slider_test = no.noise : fi.highpassLR4(hslider("fc", 1000, 20, 20000, 1)); // highpassLR4_modulated_test = no.noise : fi.highpassLR4(20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // highpassLR4_jump_test = no.noise : fi.highpassLR4(20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //------------------------------------------------------------------------------ declare highpassLR4 author "Dario Sanfilippo"; declare highpassLR4 copyright "Copyright (C) 2022 Dario Sanfilippo "; declare highpassLR4 license "LicenseRef-STK-4.3"; highpassLR4(cf, x) = x : seq(i, 2, svf.hp(cf, 1.0 / sqrt(2.0))); //----------`(fi.)crossover2LR4`------------------------------------------------ // Two-way 4th-order Linkwitz-Riley crossover. // // #### Usage // // ``` // _ : crossover2LR4(cf) : si.bus(2) // ``` // // Where: // // * `cf`: crossover split cutoff in Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // crossover2LR4_test = src : fi.crossover2LR4(1000); // crossover2LR4_slider_test = no.noise : fi.crossover2LR4(hslider("fc", 1000, 20, 20000, 1)); // crossover2LR4_modulated_test = no.noise : fi.crossover2LR4(20*pow(250, tri)) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // crossover2LR4_jump_test = no.noise : fi.crossover2LR4(20*pow(250, sq)) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` //------------------------------------------------------------------------------ declare crossover2LR4 author "Dario Sanfilippo"; declare crossover2LR4 copyright "Copyright (C) 2022 Dario Sanfilippo "; declare crossover2LR4 license "LicenseRef-STK-4.3"; crossover2LR4(cf, x) = lowpassLR4(cf, x) , highpassLR4(cf, x); //----------`(fi.)crossover3LR4`------------------------------------------------ // Three-way 4th-order Linkwitz-Riley crossover. // // #### Usage // // ``` // _ : crossover3LR4(cf1, cf2) : si.bus(3) // ``` // // Where: // // * `cf1`: crossover lower split cutoff in Hz // * `cf2`: crossover upper split cutoff in Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // crossover3LR4_test = src : fi.crossover3LR4(500, 2000); // crossover3LR4_slider_test = no.noise : fi.crossover3LR4(hslider("cf1", 500, 20, 20000, 1), hslider("cf2", 2000, 20, 20000, 1)); // crossover3LR4_modulated_test = no.noise : fi.crossover3LR4(cf, 4*cf) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); cf = 20*pow(60, tri); }; // crossover3LR4_jump_test = no.noise : fi.crossover3LR4(cf, 4*cf) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; cf = 20*pow(60, sq); }; // ``` //------------------------------------------------------------------------------ declare crossover3LR4 author "Dario Sanfilippo"; declare crossover3LR4 copyright "Copyright (C) 2022 Dario Sanfilippo "; declare crossover3LR4 license "LicenseRef-STK-4.3"; crossover3LR4(cf1, cf2, x) = crossover2LR4(cf1, x) : svf.ap(cf2, 1.0 / sqrt(2.0)) , crossover2LR4(cf2); //----------`(fi.)crossover4LR4`------------------------------------------------ // Four-way 4th-order Linkwitz-Riley crossover. // // #### Usage // // ``` // _ : crossover4LR4(cf1, cf2, cf3) : si.bus(4) // ``` // // Where: // // * `cf1`: crossover lower split cutoff in Hz // * `cf2`: crossover mid split cutoff in Hz // * `cf3`: crossover upper split cutoff in Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // crossover4LR4_test = src : fi.crossover4LR4(300, 1000, 3000); // crossover4LR4_slider_test = no.noise : fi.crossover4LR4(hslider("cf1", 300, 20, 20000, 1), hslider("cf2", 1000, 20, 20000, 1), hslider("cf3", 3000, 20, 20000, 1)); // crossover4LR4_modulated_test = no.noise : fi.crossover4LR4(cf, 3*cf, 9*cf) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); cf = 20*pow(25, tri); }; // crossover4LR4_jump_test = no.noise : fi.crossover4LR4(cf, 3*cf, 9*cf) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; cf = 20*pow(25, sq); }; // ``` //------------------------------------------------------------------------------ declare crossover4LR4 author "Dario Sanfilippo"; declare crossover4LR4 copyright "Copyright (C) 2022 Dario Sanfilippo "; declare crossover4LR4 license "LicenseRef-STK-4.3"; crossover4LR4(cf1, cf2, cf3, x) = crossover2LR4(cf2, x) : svf.ap(cf3, 1.0 / sqrt(2.0)) , svf.ap(cf1, 1.0 / sqrt(2.0)) : crossover2LR4(cf1) , crossover2LR4(cf3); //----------`(fi.)crossover8LR4`------------------------------------------------ // Eight-way 4th-order Linkwitz-Riley crossover. // // #### Usage // // ``` // _ : crossover8LR4(cf1, cf2, cf3, cf4, cf5, cf6, cf7) : si.bus(8) // ``` // // Where: // // * `cf1`: lowest crossover cutoff frequency in Hz // * `cf2`: second crossover cutoff frequency in Hz // * `cf3`: third crossover cutoff frequency in Hz // * `cf4`: fourth crossover cutoff frequency in Hz // * `cf5`: fifth crossover cutoff frequency in Hz // * `cf6`: sixth crossover cutoff frequency in Hz // * `cf7`: highest crossover cutoff frequency in Hz // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // src = os.tosc(440); // crossover8LR4_test = src : fi.crossover8LR4(100, 200, 400, 800, 1600, 3200, 6400); // crossover8LR4_slider_test = no.noise : fi.crossover8LR4(hslider("cf1", 100, 20, 20000, 1), hslider("cf2", 200, 20, 20000, 1), hslider("cf3", 400, 20, 20000, 1), hslider("cf4", 800, 20, 20000, 1), hslider("cf5", 1600, 20, 20000, 1), hslider("cf6", 3200, 20, 20000, 1), hslider("cf7", 6400, 20, 20000, 1)); // crossover8LR4_modulated_test = no.noise : fi.crossover8LR4(cf, 2*cf, 4*cf, 8*cf, 16*cf, 32*cf, 64*cf) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); cf = 20*pow(4, tri); }; // crossover8LR4_jump_test = no.noise : fi.crossover8LR4(cf, 2*cf, 4*cf, 8*cf, 16*cf, 32*cf, 64*cf) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; cf = 20*pow(4, sq); }; // ``` //------------------------------------------------------------------------------ declare crossover8LR4 author "Dario Sanfilippo"; declare crossover8LR4 copyright "Copyright (C) 2022 Dario Sanfilippo "; declare crossover8LR4 license "LicenseRef-STK-4.3"; crossover8LR4(cf1, cf2, cf3, cf4, cf5, cf6, cf7, x) = crossover2LR4(cf4, x) : (ap(cf6) : ap(cf5) : ap(cf7)) , (ap(cf2) : ap(cf1) : ap(cf3)) : crossover2LR4(cf2) , crossover2LR4(cf6) : ap(cf3) , ap(cf1) , ap(cf7) , ap(cf5) : crossover2LR4(cf1) , crossover2LR4(cf3) , crossover2LR4(cf5) , crossover2LR4(cf7) with { ap(cf) = svf.ap(cf, 1.0 / sqrt(2.0)); }; //=========================== Standardized Filters ============================ //============================================================================= // // This section provides filters that are defined by national or // international standards, e.g. for measurement applications. //----------------------`(fi.)itu_r_bs_1770_4_kfilter`------------------------- // The prefilter from Recommendation ITU-R BS.1770-4 for loudness // measurement. Also known as "K-filter". The recommendation defines // biquad filter coefficients for a fixed sample rate of 48kHz (page // 4-5). Here, we construct biquads for arbitrary samplerates. The // resulting filter is normalized, such that the magnitude at 997Hz is // unity gain 1.0. // // Please note, the ITU-recommendation handles the normalization in // equation (2) by subtracting 0.691dB, which is not needed with // `itu_r_bs_1770_4_kfilter`. // // One option for future improvement might be, to round those filter // coefficients, that are almost equal to one. Second, the maximum // magnitude difference at 48kHz between the ITU-defined filter and // `itu_r_bs_1770_4_kfilter` is 0.001dB, which obviously could be // less. // // #### Usage // // ``` // _ : itu_r_bs_1770_4_kfilter : _ // ``` // // #### Test // ``` // fi = library("filters.lib"); // os = library("oscillators.lib"); // src = os.tosc(440); // itu_r_bs_1770_4_kfilter_test = src : fi.itu_r_bs_1770_4_kfilter; // ``` // // #### References // // * // * //----------------------------------------------------------------------------- declare itu_r_bs_1770_4_kfilter author "Jakob Dübel"; declare itu_r_bs_1770_4_kfilter copyright "Copyright (C) 2022 Jakob Dübel"; declare itu_r_bs_1770_4_kfilter license "ISC"; itu_r_bs_1770_4_kfilter = stage1 : stage2 : normalize997Hz with { // Both stages are bilinear transforms, prewarped at f_c, of analog // prototypes, realized with tf2s (trapezoidal integrators). // H(s) = (V_0 s^2 + sqrt(2 V_0) s + 1)/(s^2 + sqrt(2) s + 1) stage1 = tf2s(V_0, sqrt(2.0*V_0), 1, sqrt(2.0), 1, 2*ma.PI*f_c) with { f_c = 1681.7632251028442; // Hertz gain = 3.9997778685513232; // Decibel V_0 = pow(10, (gain/20.0)); }; // H(s) = s^2/(s^2 + s/Q + 1) stage2 = tf2s(1, 0, 0, 1/Q, 1, 2*ma.PI*f_c) with { f_c = 38.135470876002174; // Hertz Q = 0.5003270373223665; }; normalize997Hz = *(0.9273671710547968); }; //============================Averaging Functions============================== //============================================================================= // // These are a set of samplerate independent averaging functions based on // moving-average and one-pole filters with specific response characteristics. //----------------------------`(fi.)avg_rect`---------------------------------- // Moving average. // // #### Usage // // ``` // _ : avg_rect(period) : _ // ``` // // Where: // // * `period`: averaging frame duration in seconds // // #### Test // ``` // fi = library("filters.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // avg_rect_test = no.noise : fi.avg_rect(0.01); // avg_rect_slider_test = no.noise : fi.avg_rect(hslider("period", 0.01, 0.001, 0.1, 0.001)); // avg_rect_modulated_test = no.noise : fi.avg_rect(0.001 + 0.019*tri) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1) : max(0) : min(1); }; // avg_rect_jump_test = no.noise : fi.avg_rect(0.001 + 0.019*sq) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // //----------------------------------------------------------------------------- declare avg_rect author "Dario Sanfilippo and Julius O. Smith III"; declare avg_rect copyright "Copyright (C) 2020 Dario Sanfilippo and 2003-2020 by Julius O. Smith III "; declare avg_rect license "LicenseRef-STK-4.3"; avg_rect(period, x) = x : ba.slidingMean(rint(period * ma.SR)); //----------------------------`(fi.)avg_tau`------------------------------------- // Averaging function based on a one-pole filter and the tau response time. // Tau represents the effective length of the one-pole impulse response, // that is, tau is the integral of the filter's impulse response. This // response is slower to reach the final value but has less ripples in // non-steady signals. // // #### Usage // // ``` // _ : avg_tau(period) : _ // ``` // // Where: // // * `period`: time, in seconds, for the system to decay by 1/e, // or to reach 1-1/e of its final value. // // #### Test // ``` // fi = library("filters.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // avg_tau_test = no.noise : fi.avg_tau(0.01); // avg_tau_slider_test = no.noise : fi.avg_tau(hslider("period", 0.01, 0.001, 0.1, 0.001)); // avg_tau_modulated_test = no.noise : fi.avg_tau(0.001 + 0.019*tri) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // avg_tau_jump_test = no.noise : fi.avg_tau(0.001 + 0.019*sq) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### References // // * //----------------------------------------------------------------------------- declare avg_tau author "Dario Sanfilippo and Julius O. Smith III"; declare avg_tau copyright "Copyright (C) 2020 Dario Sanfilippo and 2003-2020 by Julius O. Smith III "; declare avg_tau license "LicenseRef-STK-4.3"; avg_tau(period, x) = fi.lptau(period, x); //----------------------------`(fi.)avg_t60`------------------------------------- // Averaging function based on a one-pole filter and the t60 response time. // This response is particularly useful when the system is required to // reach the final value after about `period` seconds. // // #### Usage // // ``` // _ : avg_t60(period) : _ // ``` // // Where: // // * `period`: time, in seconds, for the system to decay by 1/1000, // or to reach 1-1/1000 of its final value. // // #### Test // ``` // fi = library("filters.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // avg_t60_test = no.noise : fi.avg_t60(0.01); // avg_t60_slider_test = no.noise : fi.avg_t60(hslider("period", 0.01, 0.001, 0.1, 0.001)); // avg_t60_modulated_test = no.noise : fi.avg_t60(0.001 + 0.019*tri) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // avg_t60_jump_test = no.noise : fi.avg_t60(0.001 + 0.019*sq) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### References // // * //----------------------------------------------------------------------------- declare avg_t60 author "Dario Sanfilippo and Julius O. Smith III"; declare avg_t60 copyright "Copyright (C) 2020 Dario Sanfilippo and 2003-2020 by Julius O. Smith III "; declare avg_t60 license "LicenseRef-STK-4.3"; avg_t60(period, x) = fi.lpt60(period, x); //----------------------------`(fi.)avg_t19`------------------------------------- // Averaging function based on a one-pole filter and the t19 response time. // This response is close to the moving-average algorithm as it roughly reaches // the final value after `period` seconds and shows about the same // oscillations for non-steady signals. // // #### Usage // // ``` // _ : avg_t19(period) : _ // ``` // // Where: // // * `period`: time, in seconds, for the system to decay by 1/e^2.2, // or to reach 1-1/e^2.2 of its final value. // // #### Test // ``` // fi = library("filters.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // avg_t19_test = no.noise : fi.avg_t19(0.01); // avg_t19_slider_test = no.noise : fi.avg_t19(hslider("period", 0.01, 0.001, 0.1, 0.001)); // avg_t19_modulated_test = no.noise : fi.avg_t19(0.001 + 0.019*tri) with { P = int(ma.SR/10); tri = 1 - abs(2*ba.period(P)/P - 1); }; // avg_t19_jump_test = no.noise : fi.avg_t19(0.001 + 0.019*sq) with { P = int(ma.SR/10); sq = ba.period(2*P) < P; }; // ``` // // #### References // // Zölzer, U. (2008). Digital audio signal processing (Vol. 9). New York: Wiley. //----------------------------------------------------------------------------- declare avg_t19 author "Dario Sanfilippo and Julius O. Smith III"; declare avg_t19 copyright "Copyright (C) 2020 Dario Sanfilippo and 2003-2020 by Julius O. Smith III "; declare avg_t19 license "LicenseRef-STK-4.3"; avg_t19(period, x) = fi.lpt19(period, x); //============================Kalman Filters=================================== //============================================================================= // // Functions related to the Kalman filter. kalmanEnv = environment { matMul = la.matMul; // Predicts the next state. // * `N`: State size // * `M`: Measurement size // * `F`: State transition matrix (NxN) // * `B`: Control input matrix (NxM) // Implicit arguments: // * `x`: Current state (Nx1) // * `u`: Control input (Mx1) predictState(N, M, F, B) = si.vecOp((Fx, Bu), +) with { // Fx = F @ x, where F is NxN and x is Nx1 Fx = matMul(N, N, N, 1, F, si.bus(N)); // Bu = B @ u, where B is NxM and u is Mx1 Bu = matMul(N, M, M, 1, B, si.bus(M)); }; // Predicts the covariance matrix for the next state. // * `N`: State size // * `F`: State transition matrix (NxN) // * `Q`: Process noise covariance matrix (NxN) // * `P`: Current state covariance matrix (NxN) // No implict arguments. predictCovariance(N, F, Q, P) = si.vecOp((FPF_T, Q), +) with { // FP = F @ P FP = matMul(N, N, N, N, F, P); // FPF_T = FP @ F^T, where FP is NxN and F^T is NxN FPF_T = matMul(N, N, N, N, FP, la.transpose2(N, N, F)); }; // Calculates the Kalman gain (NxM). // * `N`: State size // * `M`: Measurement size // * `H`: Observation matrix (MxN) // * `R`: Measurement noise covariance matrix (MxM) // Implict arguments: // * `P_pred`: Predicted covariance matrix (NxN) kalmanGain(N, M, H, R) = P_pred <: matMul(N, M, M, M, matMul(N, N, N, M, P_pred, H_T), S_inv) with { P_pred = si.bus(N*N); // H_T = Transpose of H H_T = la.transpose2(M, N, H); // HP = H * P_pred, where H is MxN and P_pred is NxN HP = matMul(M, N, N, N, H, P_pred); // HPH_T = HP @ H^T, where HP is MxN and H^T is NxM, resulting in MxM HPH_T = matMul(M, N, N, M, HP, H_T); // S_inv = inverse(HPH_T + R) S_inv = si.vecOp((HPH_T, R), +) : la.inverse(M); }; // Updates the state estimate based on the Kalman gain and measurement. // * `N`: State size // * `M`: Measurement size // * `H`: Observation matrix (MxN) // Implicit arguments: // * `K`: Kalman gain matrix (NxM) // * `x_pred`: (Nx1) // * `z`: (Mx1) updateState(N, M, H) = si.bus(N*M), si.bus(N), si.bus(M) <: si.vecOp((get_x_pred, K_mul_residual(N, M, H)), +) with { get_x_pred = par(i, N*M, !), par(i, N, _), par(i, M, !); }; // todo: find a way to not use mySwap. // Its purpose is to route K, x, z into K, z, x mySwap(N, M) = si.bus(N*M), si.bus(N), si.bus(M) <: select_K, select_z, select_x_pred with { select_K = par(i, N*M, _), par(i, N, !), par(i, M, !); select_x_pred = par(i, N*M, !), par(i, N, _), par(i, M, !); select_z = par(i, N*M, !), par(i, N, !), par(i, M, _); }; K_mul_residual(N, M, H) = mySwap(N, M) : matMul(N, M, M, 1, si.bus(N*M), residual) with { // residual = z - H @ x_pred, where z is Mx1 and H @ x_pred is Mx1 residual = si.vecOp((si.bus(M), matMul(M, N, N, 1, H, si.bus(N))), -); }; // Updates the covariance matrix based on the Kalman gain and observation matrix. // * `N`: State size // * `M`: Measurement size // * `H`: Observation matrix (MxN) // * `K`: Kalman gain matrix (NxM) // * `P_pred`: Predicted covariance matrix (NxN) // No implict arguments. updateCovariance(N, M, H, K, P_pred) = matMul(N, N, N, N, I_minus_KH, P_pred) with { // KH = K @ H, where K is NxM and H is MxN KH = matMul(N, M, M, N, K, H); // I_minus_KH = I - K @ H, where both are NxN I_minus_KH = si.vecOp((la.identity(N), KH), -); }; // implict arguments: // * `u`: Control input (Mx1) // * `z`: Measurement signal (Mx1) kalmanFilter(N, M, B, R, H, Q, F, reset) = (p, x, u, z <: pNew, xNew) ~ (pBus, xBus) : pCut, xBus with { doReset = reset > 0; pBus = si.bus(N*N); pCut = par(i, N*N, !); pInit = la.diag(N, par(i, N, 100)); // large value for initial uncertainty in P. p = pBus, pInit : ba.selectbus(N*N, 2, doReset); xBus = si.bus(N); xCut = par(i, N, !); xInit = par(i, N, 0); // initialize with zeros. x = xBus, xInit : ba.selectbus(N, 2, doReset); u = si.bus(M); z = si.bus(M); uCut = par(i, M, !); zCut = par(i, M, !); predCov = predictCovariance(N, F, Q, pBus); gain = kalmanGain(N, M, H, R); xNew = pBus, xBus, u, z : updateState(N, M, H, gain(predCov), predictState(N, M, F, B, xBus, u), z); pNew = pBus, xCut, uCut, zCut : predCov <: updateCovariance(N, M, H, gain(pBus), pBus); }; }; //-----------`(fi.)kalman`---------------------------------------------------- // The Kalman filter. It returns the state (a bus of size `N`). // Note that the only compile-time constant arguments are `N` and `M`. // Other arguments are capitalized because they're matrices, and it makes // reading them much easier. // // #### Usage // ``` // kalman(N, M, B, R, H, Q, F, reset, u, z) : si.bus(N) // ``` // // Where: // // * `N`: State size (constant int) // * `M`: Measurement size (constant int) // * `B`: Control input matrix (NxM) // * `R`: Measurement noise covariance matrix (MxM) // * `H`: Observation matrix (MxN) // * `Q`: Process noise covariance matrix (NxN) // * `F`: State transition matrix (NxN) // * `reset`: Reset trigger. Whenever `reset>0`, the internal state `x` and covariance matrix `P` are reset. // * `u`: Control input (Mx1) // * `z`: Measurement signal (Mx1) // // #### Example test programs // Demo 1 `(N=1, M=1)` (don't listen, just use oscilloscope): // // ``` // process = fi.kalman(N, M, B, R, H, Q, F, reset, u, z) : it.interpolate_linear(filteredAmt, z) // with { // B = 1.; // R = 0.1; // H = 1; // Q = .01; // F = la.identity(N); // reset = button("reset"); // // // Dimensions // N = 1; // State size // M = 1; // Measurement size // // freq = hslider("Freq", 1, 0.01, 10, .01); // u = 0.; // constant input // trueState = os.osc(freq)*.5 + u; // noiseGain = hslider("Noise Gain", .1, 0, 1, .01); // // filteredAmt = hslider("Filter Amount", 1, 0, 1, .01) : si.smoo; // // measurementNoise = no.noise*noiseGain; // z = trueState + measurementNoise; // Observed state // }; // ``` // // Demo 2 `(N=2, M=1)` (don't listen, just use oscilloscope) // // ``` // process = fi.kalman(N, M, B, R, H, Q, F, reset, u, z) // with { // B = par(i, N, 0); // R = (0.1); // H = (1, 0); // Q = la.diag(2, par(i, N, .1)); // F = la.identity(N); // reset = 0; // u = si.bus(M); // z = si.bus(M); // // // Dimensions // N = 2; // State size // M = 1; // Measurement size // }; // ``` // // #### Test // ``` // fi = library("filters.lib"); // ba = library("basics.lib"); // no = library("noises.lib"); // ma = library("maths.lib"); // kalman_test = fi.kalman(1, 1, 1, 0.1, 1, 0.01, 1, 0, 0, z) with { P = int(ma.SR/10); z = 0.5*(1 - abs(2*ba.period(P)/P - 1)) + 0.1*no.noise; }; // ``` // // #### References // // * // * //---------------------------------------------------------------------------- declare kalman author "David Braun"; declare kalman license "MIT"; kalman = kalmanEnv.kalmanFilter; //============================Adaptive Filters================================= //============================================================================= // // Least-mean-squares adaptive FIR filters: N weights updated by parallel // gradient recursions, no automatic differentiation involved. The classic // applications are system identification, echo/noise cancellation and // adaptive notching. //-----------`(fi.)lms`, `(fi.)nlms`------------------------------------------- // Adaptive N-tap FIR filter trained by the (delayed-update) LMS rule: // the filter output `y` is the dot product of the adaptive weights with the // last `N` input samples, the error `e = d - y` is fed back, and each weight // moves along its instantaneous gradient: `w_k <- w_k + mu * e * x@k`. // `nlms` is the normalized variant: the step is divided by the energy of // the `N` input samples currently in the filter, making the convergence // rate independent of the input level (use it unless the input power is // known and constant). // // The weight update uses the one-sample-delayed error (the standard // "delayed LMS" that a causal sample loop imposes), which does not change // the converged solution. // // #### Usage // // ``` // x, d : lms(N, mu) : _, _ // outputs: y (filter output), e (error) // x, d : nlms(N, mu) : _, _ // ``` // // Where: // // * `N`: number of adaptive weights (a constant numerical expression) // * `mu`: adaptation step; for `lms`, stability requires // `mu < 2/(N*power(x))`, while for `nlms` values in (0, 2) are stable and // 0.1-1.0 is typical // * `x`: input signal (the reference fed to the adaptive FIR) // * `d`: desired signal // // A converged identification of an unknown system H feeding // `d = H(x)` leaves the weights equal to the first `N` samples of the // impulse response of H, `y` equal to `d`, and `e` near zero. // // #### Test // ``` // fi = library("filters.lib"); // no = library("noises.lib"); // lms_test = no.noise, (no.noise : @(3)*0.5 + no.noise@(1)*0.25) : fi.lms(8, 0.01); // nlms_test = no.noise, (no.noise : @(3)*0.5 + no.noise@(1)*0.25) : fi.nlms(8, 0.5); // ``` // // #### References // // * S. Haykin, "Adaptive Filter Theory", Prentice Hall. // * B. Widrow and S.D. Stearns, "Adaptive Signal Processing", Prentice Hall. //----------------------------------------------------------------------------- declare lms license "MIT"; declare nlms license "MIT"; lms(N, mu, x, d) = adaptFIR(N, mu, x, d); nlms(N, mu, x, d) = adaptFIR(N, mu / (ma.EPSILON + sum(k, N, x@k * x@k)), x, d); //-----------`(fi.)adaptFIR`--------------------------------------------------- // Generic adaptive N-tap FIR engine that `lms` and `nlms` are built on. // The filter output `y` is the dot product of the adaptive weights with the // last `N` input samples, the error `e = d - y` is the single feedback // signal, and each weight integrates its (one-sample-delayed) gradient // step: `w_k <- w_k + step * e * x@k`. // // Unlike `mu` in `lms`, the `step` parameter is a **signal**, which makes // this the extension point for the whole family of adaptation rules: // `nlms` is `adaptFIR` with the step divided by the instantaneous input // energy, a leaky or gated LMS is `adaptFIR` with a step that decays or is // forced to 0 while adaptation should be frozen, a scheduled step // implements search-then-converge, and so on. Use `lms`/`nlms` directly // unless you need a custom rule. // // #### Usage // // ``` // x, d : adaptFIR(N, step) : _, _ // outputs: y (filter output), e (error) // ``` // // Where: // // * `N`: number of adaptive weights (a constant numerical expression) // * `step`: adaptation step, as a signal evaluated at every sample; the // `lms` stability bound `step < 2/(N*power(x))` applies to its // instantaneous value // * `x`: input signal (the reference fed to the adaptive FIR) // * `d`: desired signal // // #### Example // // ``` // // NLMS is one normalization away: // nlms(N, mu, x, d) = adaptFIR(N, mu / (ma.EPSILON + sum(k, N, x@k * x@k)), x, d); // // gated adaptation: freeze the weights below an input-level threshold // gatedLMS(N, mu, th, x, d) = adaptFIR(N, mu * (abs(x) > th), x, d); // ``` // // #### Test // ``` // fi = library("filters.lib"); // no = library("noises.lib"); // adaptFIR_test = no.noise, (no.noise : @(3)*0.5 + no.noise@(1)*0.25) // : fi.adaptFIR(8, 0.01); // ``` // // #### References // // * S. Haykin, "Adaptive Filter Theory", Prentice Hall. //----------------------------------------------------------------------------- declare adaptFIR license "MIT"; adaptFIR(N, step, x, d) = (loop ~ _) : (!, _, _) with { loop(ep) = e, y, e with { // w_k[n] = sum_{m. This is, however, not a binding provision of this license. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- ## LGPL License This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. -------------------------------------------------------------------------------- ## ISC License Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *******************************************************************************/ // Deprecated backward-compatibility alias, kept for one version and // scheduled for removal: the canonical name now carries the internal // underscore prefix (see CONTRIBUTING.md, "Variables and identifiers scoping"). declare pospass0 author "Julius O. Smith III"; declare pospass0 copyright "Copyright (C) 2003-2019 by Julius O. Smith III "; declare pospass0 license "LicenseRef-STK-4.3"; declare pospass0 deprecated "internal core of pospass and pospass6e, renamed _pospass0"; pospass0 = _pospass0;