I've been working in the past few days on a simple implementation of a Moog Ladder filter following Vadim Zavalishin's techniques, as illustrated in his book.
I coded right from the book and filter itself was very poor, little stability overall and even with very low amount of resonance the filter would explode for high cutoff frequencies. In addition, the gain attenuation at DC was much more than the theoretical maximum of about -14 db.
Then I decided to look at some existing implementation and tried faust's one, which is in turn based on Will Pirkle's implementation, and found out the same issues I had with mine. It seemed strange because Pirkle's one seems to be a widely accepted implementation.
Anyway, I got back to paper and redid all the computations and realized that, in Pirkle's, and subsequently faust's, implementation there are a couple of errors.
First of all, a little reminder of Zavalishin's technique to fix notation: we see a filter as a linear operator composed of a part that depends on the current time and a part that depends on the past, i.e.
y = H_LP[x] = Gx + S.
This allows for an easy analysis and implementation of zdf filter with more complex structures.
In particuar, Zavalishin's implementation is
Code: Select all
v = (x - s)*G
y = v + s
s = y + v
In the case of the ladder filter we have four low pass H_1, H_2, H_3, H_4, wich are fed with the input and a feedback line. Hence we got
y = H_4[H_3[H_2[H_1[x - ky]]]]
= S_4 + G(S_3 + G(S_2 + G(S_1 + G(x - ky))))
that is
y = (G^4 x + kS)/(1 + G^4),
where S = S_4 + G*S_3 + G^2 * S_2 + G^3 * S_1.
And here is the first difference: G should be the the same as in the one pole filters. Instead, Pirkle's implementation uses g (the warped cutoff frequency) for the computation fo the feedbacks. This is not just a notation issue, since in the one pole filters he correctly uses G. This sounded very strange.
Then I realized what the issue was. In Pirkle's implementations the memory states S_1,...,S_4 for the computation of the ladder feedback where taken directly as the inner memory states of the one pole filters . However, I believe this is wrong. If we write Zavalishin's implementation of the one pole eliminating the intermediate value v we get
Code: Select all
y = s + (x - s)*G = Gx + (1-G)s
s = y + (x-s)*G
Correcting these issues lead to a correct and stable implementation of the moog ladder.
Here are two images of the frequency response of the two implementations. Both have been compared with the theoretically corrected one, i.e. the one obtained as the composition of moog filter's response (H(s) = 1/((1+ s)^4 + k)) and the bilinear transform (s(z) = (1 + z^-1)/(1 - z^-1)/tan(π wc) ).
For the implementations the frequency response are measured by feeding an impulse as input and then taking the absolute value of the fft of the output.
The first one depicts the "right" response (the dotted one) and the result of faust/pirkle's filter. Actually, this code came directly from faust code, I just translated it to MatLab, which I am more familiar with.
Same color means same cutoff frequency, resonance is k = 3. As you can see the result of the filter is very far from the "right" response. And in addition to that, from the resonance peaks we can see that the cutoff frequency is altered as well.
On the other hand, the second image is the result of the corrected filter implementation. Here the difference between filter's response and "right" response is not even noticeable.


So, I don't know if anybody already did encounter and fixed this issue but, if not, I hope this might help
P.S.
It was almost ten years I didn't write in a forum, damn social networks!
