Hi Till,

Sorry to introduce confusion here.   The "p = 1-w1(freq)/pl.SR" formula is
only reasonable at low frequencies.  At 48kHz, instability is expected.  I
was only doing a low-frequency test.

Also, both filter1 and filter2 in my code example are normalized ladders.
I did initially have a comparison to direct form, as the erroneous first
comment indicates (which threw me off when I sent the email in haste due to
a timeout), but looking at it now, the purpose of the filter1 and filter2
comparison is to check that fi.iir_nl (which can design normalized ladders
of any order, including order 1) matches fi.tf2snp (which can only do order
2).  The comparison was inconvenienced by the fact that tf2snp maps
frequencies through the bilinear transform while iir_nl does not, hence the
direct pole calculation for filter2.

I agree that the right path is to use tf2snp specialized to first-order
(filter1).  (I have now added tf1snp (and tf1s) to filters.lib doing this,
and they are finished if the C++ optimization does the right thing.)

Sorry for emailing in a rush!  The alternative was most likely no email at
all until two weeks from now :-)

- Julius

On Sat, May 29, 2021 at 6:38 AM Till Bovermann <lf...@lfsaw.de> wrote:
>
> Dear Julius,
>
>
> thank you for your input!
>
> My filter theory background is nearly non-existent (for now!) so I try to
catch up as best I can, this is very helpful indeed!
>
> My aim here is more to build something that is musically similar and
approximates the analog design relatively close while maintaining numerical
stability when quickly changing parameters and input.
> Based on your comments, I would have thought that the normalized-ladder
form of the filter would be a better choice for this purpose, however I
found that it blows up with a corner frequency >= 15278 (at a constant SR
of 48k).
>
> Next step will be to adapt my multimode filter design to incorporate your
`lp1` filters and test their behaviour...
>
> Below my partial rework of your sketch (mainly renaming of variables to
make them more explicit for me to understand), possibly helpful for others
on this list, too :)
>
> all the best
>         Till
>
> ```
> //////////////////////////////////////////
> import("stdfaust.lib");
>
> // Frequency-scaling parameter for real-time bilinear form = corner
frequency:
>
> // direct form 1st order implemented with filling
> // appropriate params into 2nd order direct form and setting a0 = 0.
> // JOS: does not optimise properly on faust-level (possibly C compiler
does?)
> // JOS: needs oversampling for numerical stability
> lp1_direct(freq) = fi.tf2snp( b2, b1, b0, a1, a0, w1(freq) ) with {
>     // freq argument: corner frequency (-3dB drop-off)
>     w1(fc) = 2 * ma.PI * max( fc, 20 );
>
>     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
>
> };
>
> // normalized-ladder
> // JOS: digital counterpart for musical use at normal audio sampling rates
> // blows (@ SR = 48000) for freq >= 15278
> lp1_ladder(freq) = 0.5*(1-p) * fi.iir_nl(bvd,avd) with {
>     // freq argument: corner frequency (-3dB drop-off)
>     w1(fc) = 2 * ma.PI * max( fc, 20 );
>
>     bvd = (1,1);
>     avd = -p;
>     p = 1-w1(freq)/pl.SR;
> };
>
> process = os.square(freq) <: lp1_direct(ffreq), lp1_ladder(ffreq) with {
>     ffreq = hslider("ffreq", 20, 20, 20000, 0.001);
>     freq = hslider("freq", 20, 20, 10000, 0.001);
> };
> ```
>
>
>
> --
> Till Bovermann
>
> https://tai-studio.org | http://lfsaw.de |
https://www.instagram.com/_lfsaw/
>
>
>
>
>
>
>
>
>
>
>
> > On 29. May 2021, at 01:43, Julius Smith <julius.sm...@gmail.com> wrote:
> >
> > 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
>


--
"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