Thanks so much for all the efforts you all put in to this seemingly simple task (that I am incapable to tackle myself)...
I'll try out stuff and will report back :) all the best Till -- Till Bovermann https://tai-studio.org | http://lfsaw.de | https://www.instagram.com/_lfsaw/ > On 29. May 2021, at 18:52, Julius Smith <julius.sm...@gmail.com> wrote: > > 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