From 596d3c50151f1b428d5682fe9b7d3bb36b60d5fd Mon Sep 17 00:00:00 2001 From: David Braun <2096055+DBraun@users.noreply.github.com> Date: Fri, 13 Oct 2023 16:05:06 -0400 Subject: [PATCH 1/4] add fi.svf.morph --- filters.lib | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/filters.lib b/filters.lib index 2a64b02a..81412fe8 100644 --- a/filters.lib +++ b/filters.lib @@ -2633,8 +2633,10 @@ with { //======================================================================================== //-----------------`(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. +// An environment with `lp`, `bp`, `hp`, `notch`, `peak`, `ap`, `bell`, `ls`, `hs`, `morph` SVF based filters. +// All filters have `freq` and `Q` parameters. The `bell`, `ls`, `hs` ones also have a `gain` third parameter. +// The `morph` filter has a `blend` third parameter [0..2] continuous, where 0 is `lp`, 1 is `bp`, and +// 2 is `hp`. This allows seamless blending between those three filter types. // // #### Usage // @@ -2657,7 +2659,7 @@ declare svf license "MIT-style STK-4.3 license"; svf = environment { // Internal implementation - svf(T,F,Q,G) = tick ~ (_,_) : !,!,si.dot(3, mix) + svf(T,F,Q,G,B) = tick ~ (_,_) : !,!,si.dot(3, mix(T)) with { tick(ic1eq, ic2eq, v0) = 2*v1 - ic1eq, @@ -2691,19 +2693,22 @@ svf = environment { (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); + // blend among the weights of LP, BP, HP: + (9) => ef.mixLinearClamp(3, 3, B, (mix(0), mix(1), mix(2))); + }; }; // 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); + lp(f,q) = svf(0, f, q, 0, 0); + bp(f,q) = svf(1, f, q, 0, 0); + hp(f,q) = svf(2, f, q, 0, 0); + notch(f,q) = svf(3, f, q, 0, 0); + peak(f,q) = svf(4, f, q, 0, 0); + ap(f,q) = svf(5, f, q, 0, 0); + bell(f,q,g) = svf(6, f, q, g, 0); + ls(f,q,g) = svf(7, f, q, g, 0); + hs(f,q,g) = svf(8, f, q, 0, 0); + morph(f,q,b) = svf(9, f, q, 0, b); }; From fcdcd993bad5ac6230c762d8cbbdc4c9cdede5e4 Mon Sep 17 00:00:00 2001 From: David Braun <2096055+DBraun@users.noreply.github.com> Date: Fri, 13 Oct 2023 16:05:06 -0400 Subject: [PATCH 2/4] add fi.svf.morph --- filters.lib | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/filters.lib b/filters.lib index 2a64b02a..1f86f0ba 100644 --- a/filters.lib +++ b/filters.lib @@ -2633,8 +2633,10 @@ with { //======================================================================================== //-----------------`(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. +// An environment with `lp`, `bp`, `hp`, `notch`, `peak`, `ap`, `bell`, `ls`, `hs`, `morph` SVF based filters. +// All filters have `freq` and `Q` parameters. The `bell`, `ls`, `hs` ones also have a `gain` third parameter. +// The `morph` filter has a `blend` third parameter [0..2] continuous, where 0 is `lp`, 1 is `bp`, and +// 2 is `hp`. This allows seamless blending between those three filter types. // // #### Usage // @@ -2704,6 +2706,32 @@ svf = environment { 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); + morph(f,q,b) = lpw * lpc , bpw * bpc , hpw * hpc :> _ + with { + onePoleTPT(cf, x) = loop ~ _ : ! , si.bus(3) + with { + g = tan(cf * ma.PI * ma.T); + G = g / (1.0 + g); + loop(s) = v + lp , lp , hp , ap + with { + v = (x - s) * G; + lp = v + s; + hp = x - lp; + ap = lp - hp; + }; + }; + AP1(cf, x) = onePoleTPT(cf, x) : ! , ! , _; + // calculate weights + lpw = max(0, 1 - b); + bpw = min(1, b) - hpw; + hpw = max(0, b - 1); + + lpc = svf.lp(1000, q); + // shift the phase of the BP by 90deg at `cf` with a one-pole allpass to make it match the other filters' phases + bpc = svf.bp(1000, q) : AP1(1000); + // invert the sign of the HP output to make its phase match the LP's phase perfectly + hpc = -1 * svf.hp(1000, q); + }; }; From c3313b3b7f4e9ed4d1623281325df81209fa9a04 Mon Sep 17 00:00:00 2001 From: David Braun <2096055+DBraun@users.noreply.github.com> Date: Sat, 14 Oct 2023 15:28:04 -0400 Subject: [PATCH 3/4] Update filters.lib --- filters.lib | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/filters.lib b/filters.lib index 02572854..1f86f0ba 100644 --- a/filters.lib +++ b/filters.lib @@ -2659,7 +2659,7 @@ declare svf license "MIT-style STK-4.3 license"; svf = environment { // Internal implementation - svf(T,F,Q,G,B) = tick ~ (_,_) : !,!,si.dot(3, mix(T)) + svf(T,F,Q,G) = tick ~ (_,_) : !,!,si.dot(3, mix) with { tick(ic1eq, ic2eq, v0) = 2*v1 - ic1eq, @@ -2693,9 +2693,7 @@ svf = environment { (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; - // blend among the weights of LP, BP, HP: - (9) => ef.mixLinearClamp(3, 3, B, (mix(0), mix(1), mix(2))); - }; + } (T); }; // External API From f6411f2d7b78c5ae3ac4948e3f367e88cdf3f757 Mon Sep 17 00:00:00 2001 From: David Braun <2096055+DBraun@users.noreply.github.com> Date: Sat, 14 Oct 2023 19:48:50 -0400 Subject: [PATCH 4/4] move code outside of svf environment --- filters.lib | 108 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 80 insertions(+), 28 deletions(-) diff --git a/filters.lib b/filters.lib index 1f86f0ba..bb4194c0 100644 --- a/filters.lib +++ b/filters.lib @@ -22,6 +22,7 @@ // * [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) +// * [Topology-Preserving Transform Filters](#topology-preserving-transform-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) @@ -2633,10 +2634,8 @@ with { //======================================================================================== //-----------------`(fi.)svf`---------------------- -// An environment with `lp`, `bp`, `hp`, `notch`, `peak`, `ap`, `bell`, `ls`, `hs`, `morph` SVF based filters. -// All filters have `freq` and `Q` parameters. The `bell`, `ls`, `hs` ones also have a `gain` third parameter. -// The `morph` filter has a `blend` third parameter [0..2] continuous, where 0 is `lp`, 1 is `bp`, and -// 2 is `hp`. This allows seamless blending between those three filter types. +// 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 // @@ -2706,34 +2705,87 @@ svf = environment { 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); - morph(f,q,b) = lpw * lpc , bpw * bpc , hpw * hpc :> _ +}; + + +//-----------------`(fi.)svf_morph`---------------------- +// A SVF-based filter than can smoothly morph between being lowpass, bandpass, and highpass. +// +// #### Usage +// +// ``` +// _ : svf_morph(freq, Q, blend) : _ +// ``` +// +// Where: +// +// * `freq`: cut frequency +// * `Q`: quality factor +// * `blend`: [0..2] continuous, where 0 is `lowpass`, 1 is `bandpass`, and 2 is `highpass` +// +//--------------------------------------------------- +declare svf_morph author "Dario Sanfilippo"; +declare svf_morph copyright "Copyright (C) 2023 Dario Sanfilippo "; +declare svf_morph license "MIT-style STK-4.3 license"; + +svf_morph(f,q,b) = lpw * lpc , bpw * bpc , hpw * hpc :> _ +with { + // calculate weights + lpw = max(0, 1 - b); + bpw = min(1, b) - hpw; + hpw = max(0, b - 1); + + lpc = svf.lp(f, q); + // shift the phase of the BP by 90deg at `cf` with a one-pole allpass to make it match the other filters' phases + bpc = svf.bp(f, q) : AP1(f); + // invert the sign of the HP output to make its phase match the LP's phase perfectly + hpc = -1 * svf.hp(f, q); +}; + + +//==================Topology-preserving Transform Filters====================== +// +// Topology-preserving transform (TPT) filters. These are useful when the +// frequency is modulating quickly. +// +// #### Reference +// Zavalishin, Vadim. "The art of VA filter design." Native Instruments, Berlin, Germany (2012). +//============================================================================= + +//-----------------`(fi.)onePoleTPT`------------------------- +// One-pole topology-preserving transform (TPT). The three +// outputs are the lowpass, highpass, and allpass signals. +// +// #### Usage +// +// ``` +// _ : onePoleTPT(freq) : si.bus(3) +// ``` +// +// Where: +// +// * `freq`: cut frequency +// +//----------------------------------------------------------- +declare onePoleTPT author "Dario Sanfilippo"; +declare onePoleTPT copyright "Copyright (C) 2023 Dario Sanfilippo "; +declare onePoleTPT license "MIT-style STK-4.3 license"; + +onePoleTPT(cf, x) = loop ~ _ : ! , si.bus(3) +with { + g = tan(cf * ma.PI * ma.T); + G = g / (1.0 + g); + loop(s) = v + lp , lp , hp , ap with { - onePoleTPT(cf, x) = loop ~ _ : ! , si.bus(3) - with { - g = tan(cf * ma.PI * ma.T); - G = g / (1.0 + g); - loop(s) = v + lp , lp , hp , ap - with { - v = (x - s) * G; - lp = v + s; - hp = x - lp; - ap = lp - hp; - }; - }; - AP1(cf, x) = onePoleTPT(cf, x) : ! , ! , _; - // calculate weights - lpw = max(0, 1 - b); - bpw = min(1, b) - hpw; - hpw = max(0, b - 1); - - lpc = svf.lp(1000, q); - // shift the phase of the BP by 90deg at `cf` with a one-pole allpass to make it match the other filters' phases - bpc = svf.bp(1000, q) : AP1(1000); - // invert the sign of the HP output to make its phase match the LP's phase perfectly - hpc = -1 * svf.hp(1000, q); + v = (x - s) * G; + lp = v + s; + hp = x - lp; + ap = lp - hp; }; }; +AP1(cf, x) = onePoleTPT(cf, x) : ! , ! , _; + //===========Linkwitz-Riley 4th-order 2-way, 3-way, and 4-way crossovers===== //