Hi Till,

Thanks for the cool app note - very interesting!

I am not familiar with fi.svf.lp, but I've done similar things.

If you just want to emulate the analog for testing purposes, I would use
direct form first-order filters and oversample like crazy.

If you want to deploy a digital counterpart for musical use at normal audio
sampling rates, I would use the normalized ladder form in place of direct
form (see code below).

You could also make a wave digital filter version (now that we have
wdmodels.lib thanks to Dirk).

These "multimode filters" look like normal state variable filters to me
(following early analog filters used for solving differential equations in
a state-space format, according to my experience - I first encountered one
in 1972 or so at Rice University).

I have a small write-up on digitizing state-variable filters here:
https://ccrma.stanford.edu/~jos/svf/
but I didn't get into normalized structures (there are several).

I just compared direct-form to normalized-ladder in the test example
below.  The Faust compiler does not catch the optimization opportunity
(1st-order filter written in 2nd-order form), so it's up to the C++
compiler to find that.  I don't have time to look at the assembly code
right now, so please let us know if you check on that and learn the
result.  It should not be necessary to write out tf1snp explicitly.

// Test tf1snp defined as tf2snp with two 0 coefficients and compare to
direct-form

import("stdfaust.lib");

fc = 1000; // -3 dB frequency
g = 1.0;   // dc gain

// Analog zero at infinity:
b2 = 0;  b1 = g;  b0 = 0;

// Analog poles in normalized form (a2=1 implicitly):
a1 = 1.0;
a0 = 0; // Optimization should remove anything associated with this

// Frequency-scaling parameter for real-time bilinear xform = corner
frequency:
w1 = 2*ma.PI*max(fc,20);

filter1 = fi.tf2snp(b2,b1,b0,a1,a0,w1);
filter2 = 0.5*(1-p) * fi.iir_nl(bvd,avd) with { bvd = (1,1); avd = -p; p =
1-w1/pl.SR; };
process = 1-1' <: filter1, filter2;

// Check using faust2octave

On Fri, May 28, 2021 at 7:21 AM Till Bovermann <lf...@lfsaw.de> wrote:

> Hello list,
>
> long time again that I sneaked in here...
> but still working with faust and it is generally a nice experience, thanks
> everybody!
>
> I am currently looking into multimode filters; we are considering building
> something (analog) with a multimode filter based on the design found in
>
> http://www.soundsemiconductor.com/downloads/AN701.pdf
>
> p.19
>
> however with a feedback path to control filter resonance (as outlined on
> p.16)
>
>
> I build a preliminary emulation in faust like so [1].
>
> However, I am not happy with the dynamics of the filter when changing its
> parameters.
>
> I was thinking to use the fi.svf.lp filter as a building block instead but
> it is (AFAICS, it is not mentioned in the doc string...) not a 1st order
> design (which is crucial here, partly for consistency with the to be
> emulated analog circuitry, partly because the phase shift should be
> consistent to be able to deal with the resonance feedback).
>
>
> I would be very much interested in other approaches, especially one's that
> are closely related to the analog filter topology and its numerical
> stability when applying dynamics to the filter freq and q.
>
>
> any help much appreciated!
>
>
>
>         Till
>
>
> (I am also searching for a gentle introduction to filter theory, i.e.
> explanations of Laplace / z-transforms, transfer functions, (virtual)
> analog filter design, ... I am OK in general maths but filter theory
> escapes me and most of the literature I find online assumes to much
> preliminary knowledge or dives too fast into mathematical deeps for me to
> follow...)
>
>
> [1]
>
> a parametric multimode filter inspired by Semiconductor's AN701
>
> #### Parameters
> + a_amp: amplitude of original signal added in the mixing stage
>        * `< 0` — use table
>        * `> 0` — use this amplitude
> + freq; filter frequency
> + q; Q-factor
> + idx; indicates one of the 45 modes of the filter based on Sound
> Semiconductor's AN701
> [ 4LP, 3LP1HP, 3LP1AP, 3LP, 2LP2HP, 2LP1HP1AP, 2LP1HP, 2LP2AP, 2LP1AP,
> 2LP, 2LP1NT, 1LP3HP, 1LP2HP1AP, 1LP2HP, 1LP1HP2AP, 1LP1HP1AP, 1LP1HP,
> 1LP1HP1NT, 1LP3AP, 1LP2AP, 1LP1AP, 1LP1AP1NT, 1LP, 1LP1NT, 4HP, 3HP1AP,
> 3HP, 2HP2AP, 2NP1AP, 2HP, 2HP1NT, 1HP3AP, 1HP2AP, 1HP1AP, 1HP1AP1NT, 1HP,
> 1HP1NT, 4AP1, 3AP, 2AP, 2AP1NT, 1AP, 1AP1NT, 1NT, 2NT ]
>
> #### Usage
>
> process = _ : multimode(freq, q, idx, a_amp) * amp <: _,_ with {
>     freq = hslider("[0]freq[scale:exp]",100,10,20000,0.001);
>     q = hslider("[1]Q",0,0,15,0.001);
>     idx = hslider("[2]mode",10, 0,44,1);
>     amp = hslider("outamp",1, 0,4,0.001);
>     a_amp = hslider("a_amp",-0.001, -0.001,1.0001,0.001);
> };
>
> ```
> multimode(freq, q, idx, a_amp) = _ : mix ~ _ : !, _ with {
>
>     a(fbck, x) = x - (q * fbck);
>
>     node = _ : lp(freq) * ba.db2linear(-3) * (-1) with {
>             lp(freq) = fi.lowpass(1, freq);
>     };
>
>     b(fbck, x) = a(fbck, x) : node;
>     c(fbck, x) = b(fbck, x) : node;
>     d(fbck, x) = c(fbck, x) : node;
>     e(fbck, x) = d(fbck, x) : node;
>
>     // factors for filter modes according to Sound Semiconductor's AN701,
> 45 states
>     a_table = waveform{ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
> 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
> 0.16666666666667, 0.11111111111111, 0.33333333333333, 1.0, 0.2, 0.5,
> 0.14285714285714, 0.35, 0.125, 0.33333333333333, 1.0, 1.0, 0.25, 0.75,
> 0.083333333333333, 0.25, 1.0, 0.5, 0.16666666666667, 0.5, 0.125 };
>     b_table = waveform{ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
> 0.0, 0.33333333333333, 0.2, 0.125, 0.125, 0.33333333333333, 1.0, 0.25,
> 0.083333333333333, 0.25, 0.5, 0.16666666666667, 1.0, 0.5, 0.66666666666667,
> 0.55555555555556, 1.0, 0.0625, 0.8, 1.0, 0.57142857142857, 0.05, 0.625,
> 1.0, 0.066666666666667, 1.0, 0.75, 1.0, 0.5, 1.0, 0.0625, 1.0,
> 0.66666666666667, 1.0, 0.5 };
>     c_table = waveform{ 0.0, 0.0, 0.0, 0.0, 0.5, 0.33333333333333, 1.0,
> 0.25, 0.5, 1.0, 0.5, 1.0, 0.8, 0.625, 0.625, 1.0, 1.0, 0.75, 0.5, 1.0, 1.0,
> 0.66666666666667, 0.0, 1.0, 1.0, 1.0, 1.0, 0.1875, 1.0, 0.5, 1.0, 0.4, 1.0,
> 0.66666666666667, 0.0, 0.0, 1.0, 0.125, 1.0, 1.0, 0.25, 0.0, 1.0, 1.0, 1.0
> };
>     d_table = waveform{ 0.0, 1.0, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0,
> 1.0, 1.0, 1.0, 1.0, 1.0, 0.66666666666667, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0,
> 0.0, 1.0, 0.66666666666667, 0.77777777777778, 0.33333333333333, 0.75, 0.4,
> 0.0, 0.85714285714286, 1.0, 0.5, 0.0, 0.66666666666667, 0.0, 0.5,
> 0.083333333333333, 0.66666666666667, 0.0, 1.0, 0.0, 0.66666666666667, 0.0,
> 1.0 };
>     e_table = waveform{ 1.0, 1.0, 1.0, 0.0, 0.5, 0.66666666666667, 0.0,
> 1.0, 0.0, 0.0, 1.0, 0.33333333333333, 0.4, 0.5, 0.5, 0.0, 0.0, 0.5,
> 0.66666666666667, 0.0, 0.0, 0.66666666666667, 0.0, 0.0, 0.16666666666667,
> 0.22222222222222, 0.0, 0.25, 0.0, 0.0, 0.28571428571429, 0.4, 0.0, 0.0,
> 0.26666666666667, 0.0, 0.0, 0.66666666666667, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0,
> 0.5};
>
>     fac(table, i) = table, int(i): rdtable : si.smoo;
>     fac_custom_amp(table, i, a_amp) = select2(a_amp >= 0, fac(table, i),
> a_amp);
>
>     mix(fbck, x) = e(fbck, x), (
>         a(fbck, x) * fac_custom_amp(a_table, idx, a_amp) +
>         b(fbck, x) * fac(b_table, idx) +
>         c(fbck, x) * fac(c_table, idx) +
>         d(fbck, x) * fac(d_table, idx) +
>         e(fbck, x) * fac(e_table, idx)
>     );
> };
> ```
>
> --
> Till Bovermann
>
> https://tai-studio.org | http://lfsaw.de |
> https://www.instagram.com/_lfsaw/
>
>
>
>
>
>
>
>
>
>
>
>
>
> _______________________________________________
> Faudiostream-users mailing list
> Faudiostream-users@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/faudiostream-users
>


-- 
"Anybody who knows all about nothing knows everything" -- Leonard Susskind
_______________________________________________
Faudiostream-users mailing list
Faudiostream-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/faudiostream-users

Reply via email to