Xpander style filter modes for your 4-pole "ladder"

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

Andy mentioned in the circuit-modeling thread that he might be going to explain this stuff at some point, but since I thought it might be insightful for any newbie thinking of how to get different modes from a filter, and I have this in fresh memory having worked with it recently, I thought I'd explain the thing.

The basic filter configuration we are dealing with is exactly the same as the Moog ladder, though I believe the non-linearities in the Xpander filter are different, but since we will be dealing with linear stuff only, that makes no difference whatsoever. As long as you keep your stage gains right, you can mess with whatever non-linearities you want.

We are going to proceed with the concept of adding together 5 outputs from the filter: one after the resonance feedback, and 4 after each 1-pole LP stage. I'm going to name these outputs as A,B,C,D and E. (We'll ignore any Xpander specific magic here, since I'm explaining the concept and not how to model an Xpander filter.)

The concept is simple (and hinted at in the service manual of the Oberheim thing, though it's somewhat obscured by analog implementation issue there): we work out the transfer function of each output, then solve for constants we need to multiply them to get a desired transfer function. We'll ignore the resonance from the feedback loop, as it happens that it makes no useful difference in the discussion, and there is nothing in the original Oberheim about it anyway (IIRC and AFAIK and whatever qualifiers).

Now a one-pole analog lowpass can be taken to have the transfer function 1/(1+s) which is about as much as we need to understand about Laplace transforms to be able to get this done. The other thing we need to understand is that if we put two lowpasses in a row, it happens to become multiplication in the transfer domain (this follows from the relationship of convolution and multiplication, and the fact that both of them are associative operations).

So, the outputs therefore get transfer functions as follows:

A = 1
B = 1/(1+s)
C = (1/(1+s))^2
D = (1/(1+s))^3
E = (1/(1+s))^4

Now to be able to combine the outputs in a useful way, we are going to assign coefficients [a,b,c,d,e] for them, giving us a totally generic (as far as the filter can provide) transfer function:

a*A + b*B + c*C + d*D + e*E

From that you obviously get a 4-pole LP by letting e=1 and rest of them 0. For two pole LP, you take just the C output, and so on.

Anyway, just different LPs aren't all that interesting, so using your favourite symbolic mathematics software (I usually use Maxima) or pencil and paper if you really feel like it, you'll want to expand and then simplify that to get something like:

Code: Select all

as^4 + (4a+b)s^3 + (6a+3b+c)s^2 + (4a+3b+2c+d)s + a+b+c+d+e
-----------------------------------------------------------
                       (1+s)^4
sorry it's the board software breaking the long divisor line

If your stages are inverting (like I think they were in the Xpander IIRC) then you need to negate every other output, which will give you all positive (or negative if you get it wrong way) coeffs later, but for trivial digital 1-pole lowpasses that's not necessary. Notice that this is sensitive to individual stage gains though (if your stages have equal gain and it starts self-oscillating at feedback of 4 you're at least close; if not, you can either modify the stages or the coeffs you solved).

Anyway, now that we have the general transfer function, we can solve the coefficients to get a particular transfer function. As an example, we'll take 4-pole hipass.

One can get 1-pole high-pass from a 1-pole lowpass, by taking the difference between the input and the output. This obviously (I hope it's obvious anyway) is no good for multi-pole high-pass though, so we'll multiply (or "put in a row" in the time domain sense) a couple of single-pole high-passes instead, giving us:

(1 - 1/(1+s))^4 or (s/(1+s))^4 in case you simplify first

Using your favourite symbolic mathematics software again, you might manage to simplify it to something like:

s^4 / (1+s)^4

ok, in this case it would be trivial to do it on paper as well, but in more complicated modes I'd just dump it to Maxima and be done with it

So now we have the ugly polynomial above, and then this simple polynomial, and we wanna make them the same:

Code: Select all

  s^4     as^4+(4a+b)s^3+(6a+3b+c)s^2+(4a+3b+2c+d)s+a+b+c+d+e
------- = ---------------------------------------------------
(1+s)^4                         (1+s)^4
The denominators are obviously useless at this point, and if you just solve for each power of s in the nominator, you'll get a trivial set of linear equations:

Code: Select all

 /  a           = 1        a = 1
 | 4a+ b        = 0        b = -4
 < 6a+3b+c      = 0   =>   c = 6
 | 4a+3b+2c+d   = 0        d = -4
 \  a+ b+ c+d+e = 0        e = 1
Here we go, we've got a 4-pole high-pass response when we multiply the outputs by the constants we just solved, then sum them together.

Same principle can be used for other modes, say 2+2 pole BP could be solved from:

(s/(1+s))^2 * (1/(1+s))^2 = two-pole highpass going into two-pole lowpass

For notch filter you need to do something more clever, and add hipass and lowpass responses together (at least 2-pole for each such that they happen to cancel each other near the cutoff).

You can also make more fancy modes, like notch going into lowpass, or whatever, and even if we're dealing with the analog transfer functions here, it translates "as-is" into the digital domain as long as you've got the indivial lowpass-stages translated properly.

Which ofcourse you won't have, because somehow you'll have to compensate for the fact that you have an extra delay in the feedback loop. This fortunately causes only minor problems (there seems to be some anomality with notch and resonance; the notch doesn't exactly cancel the resonance because the frequencies don't match exactly). I personally haven't bothered fixing it yet, though potential remedies would be structuring the one-poles such that correct phases are available inside them (no idea if this is workable), or by using all-passes (fractional delay filters should do the job?) for phase correction (more things I'll have to do before release... blah).

In any case, hope that was useful for someone, and please report any typos/grammar problems that my proof reading was unable to find.
Last edited by mystran on Thu Feb 21, 2008 1:59 pm, edited 1 time in total.

Post

Oops, seems the board software made a mess of the wide equations when I posted it (wasn't a problem in preview)... trying to fix..

[edit]

Fixed on the expense of removing some space that made 'em easier to read.

Post

So everybody thinks that was useless (or contained such huge errors that it's not worth the trouble to bother to try to correct)?

Come on, please drop some comments, for good or bad, I wanna know if anybody thinks I should waste more time explaining stuff I come across.

Post

I thought it was utterly useful and interesting, but haven't had a chance to toy around with stuff yet.

Would've been nice to have some simple wiki for these kind of things (as in explaining stuff one comes across). Musicdsp has been awesome, but it's starting to feel a bit... dated maybe :)
Stefan H Singer
Musician, coder and co-founder of We made you look Web agency

Post

Cool, thanks mystran :)

I'm off to download Maxima and have a play :D

Post

Caco wrote:Cool, thanks mystran :)

I'm off to download Maxima and have a play :D
Well you don't exactly need Maxima, it just happens to be a free symbolics mathematics software and happened to be the one that I happened to like enough to learn to use, and if you just wanna get things done, having some math software available saves you the trouble of accidentally getting it wrong on paper three time a row.

For Maxima specifically you'll be able to get the nominators almost as I wrote them (though I've reordered the big one differently from what Maxima would do) when you multiply by the known denominator (because in some cases the rational could be simplified but it's easiest to solve the coeffs if you just match the denominators and then throw 'em away.

What I mean, is, say you want 2 pole highpass, you could write something like:

Code: Select all

 ((s/(1+s))^2) * (1+s)^4
(and tell it to simplify) to get only the nominator of your desired filter (by multiplying out the "known" or "necessary" denominator). If you still get a rational that way after simplification, then you probably won't be able to get the response out of the filter, but then again I've never had that problem happen for any of the responses I've wanted to solve for.

Anyway, now we're getting into details of how to use a particular symbolic mathematics software which I think is off-topic. :)

Post

I'm off to download Maxima and have a play :D
which is what i did yesterday already. so thanks mystran for sharing the results of your work and pointing me to maxima - which seems to be really powerful given the fact that it is completely free (and even open source). i found your post very interesting and useful right from the start - i'm just a bit hesitant to comment on topics where i not really feel fit and competent enough.
My website: rs-met.com, My presences on: YouTube, GitHub, Facebook

Post

thanks for that post. its really interesting stuff to me, though I am just starting to understand the most basic kinds of digital filters (butterworth etc). Unfortunately I don't understand how to get from an s-domain function to a transfer function (or better yet a difference eqn, or um CODE). I would be thrilled if you would carry this example further into actual implementation of a tunable filter. I don't see any connection between your example and the basic IIR stuff I know, is there any?

Post

asomers wrote:I don't understand how to get from an s-domain function to a transfer function (or better yet a difference eqn, or um CODE).


if you have your transfer-function in the s-domain (in form of poles and zeros in the s-plane) - then you are actually almost done. you just need to carry it over to the z-domain via the bilinear transform. that is, if S is a pole (or zero) in the s-plane, you obtain the corresponding pole (or zero) in the z-plane, denoted as Z, via:

Z = (1 / (2*sampleRate)) * (1+S)/(1-S)

the difference equation is then obtained by multiplying out the numerator and denominator of z-plane transfer function and using the resulting polynomial coefficients as feedforward or feedback coefficients (numerator->feedforward, denominator->feedback). you can also pair complex conjugate poles/zeros in the z-plane to quadratic factors and then implement your filter as cascade of biquads (which is more advisable for higher order filters).

the hard part of filter design is generally to obtain the poles and zeros in the s-plane. but once you have them, the above procedure should be straightforward. finding the poles and zeros in the s-plane is also straightforward for butterworth-filters, but more involved for chebyshev filters and a serious mathematical challenge for elliptic filters. ...but as always: the devil is in the detail, even in the butterworth case: this filter has s-plane zeros at infinity - you need to catch such special conditions and place z-plane zeros at z=-1 for them.
I don't see any connection between your example and the basic IIR stuff I know, is there any?
this approach here is somewhat different from classical filter design. if you want to get your head around classical filter design, don't let this here distract/confuse you
My website: rs-met.com, My presences on: YouTube, GitHub, Facebook

Post

Indeed, the stuff here is mostly interesting when you need a synth-like filter with properties such as stable response in time-varying case, easy tuning, independent resonance control, interesting sorts of non-linearities... and you then want to use the same model to also allow multiple filter modes.

Classical structures like direct form biquads are rather easy to design and CPU efficient in the time-invariant case, but they lack most of the other features one would want from a VCF-wannabe.

If you want some example code of what might be a filter suitable for what has been discussed here, you might want to look into the music-dsp source-code archive, which contains a couple of "Moog" filters many of which would probably work for what has been discussed.

Post

Thanks for the post. I had read something similar in an old Electronotes, but it is good to see the math explained.

One issue that I have found is that the s-plane equations do not work with the Stilson-style Moog filter. Tim ended up using 1-pole 1-zero sections in his filter, with the zeros placed in such a location that the filter was roughly constant-Q in a useable range without oversampling or lookup tables. However, the zeros also screw up the summing gains for the different filter modes.

Antti's Moog-style filter may be a better candidate for this kind of multimode filter, as I think he used one-pole sections exclusively.

Sean Costello

Post

valhallasound wrote:Thanks for the post. I had read something similar in an old Electronotes, but it is good to see the math explained.

One issue that I have found is that the s-plane equations do not work with the Stilson-style Moog filter. Tim ended up using 1-pole 1-zero sections in his filter, with the zeros placed in such a location that the filter was roughly constant-Q in a useable range without oversampling or lookup tables. However, the zeros also screw up the summing gains for the different filter modes.

Antti's Moog-style filter may be a better candidate for this kind of multimode filter, as I think he used one-pole sections exclusively.

Sean Costello
Worry not, said Stilson model can still be made to work. You just have to notice that the feedback gain for self-oscillation is now (approximately) 1, instead of 4; there's now extra total gain of 4 in the poles. Since moving some gains around in a linear filter has no effect, we can return the self-oscillation gain back to 4 by dividing by sqrt(2) after each pole as sqrt(2)^4 = 4. Since the poles are all equal, it is reasonable assumption that we should now get the right gains for the output combinations to work properly. The alternative (if you don't need the gains right for other purposes) is to just divide the pole output coefficients by whatever extra gains have been accumulated so far.

Post

Thanks for the tip! I would probably work with a nonlinear filter, but the gain values should still hold true for small signal values, where the gain is roughly linear. The only change would be to scale the pole output coefficients, or the between-filter scales, to reflect any changes the nonlinearities add. Or, if the nonlinearity is primarily in the feedback loop (like the SH-101), I would keep the gain values as you describe above for the Stilson model.

Sean Costello

Post

hey, how about making it morphable via interpolating between the highpass weights, the bandpass weights and the (trivial) lowpass-weights. such that with some parameter p we have:
p=0: lowpass
p=0.5: bandpass
p=1: highpass
does that seem to be doable? i mean doable in the sense that the transition would be meaningful? i guess i have to try. i just ported Christian Budde's implementation of Antti's Moog-filter to C++ and i'm currently playing with it anyway.
My website: rs-met.com, My presences on: YouTube, GitHub, Facebook

Post

Robin from www.rs-met.com wrote:hey, how about making it morphable via interpolating between the highpass weights, the bandpass weights and the (trivial) lowpass-weights.
Välimäki&Huovilainen (Oscillator and Filter Algorithms for Virtual Analog Synthesis, Computer Music Journal 2006) claim it's doable (they also give coeffs for some common responses without going into details about how to derive them, other than a reference to the Oberheim service manuals), but I haven't tried, so can't comment on that one.

Post Reply

Return to “DSP and Plugin Development”