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