The bilinear transform is the most popular method of converting analog filter prototypes in the s domain to the z domain so we can implement them as digital filters. The reason we are interested in these s domain filters is that analog filter theory has been around longer than digital filter theory, so we have available to us a number of popular and useful filters in the s domain. The standard biquads used heavily in audio processing are direct implementations from the s domain using the bilinear transform.

The theory of the bilinear transform is well documented in DSP texts and on the web, so rather than spend time going into the theory, we’ll cut to the chase and show how to do the transform, using a second order lowpass filter as an example.

The s domain transfer function of a second order lowpass filter is

To move to the z domain, we need to substitute for s in terms of z. At the same time, we “pre-warp” the response using the tan function in order to wrap the infinite s plane onto the circular z plane. The combined substitution is

Here’s our z domain transfer function after substituting for s:

In order to solve for our filter coefficients, we need to get the equation into the form of a second order polynomial in the numerator (the zeros) and another second order polynomial in the denominator (the poles). That is, we need it to look something like this:

It helps to use a math package to cut down on some of the tedious work. If we take the right side of the equation and ask MathCad to “simplify”, it gives

The first thing we see is that there are too many Q’s, so divide the top and bottom by Q, resulting in

Now combine terms in the numerator and rearrange in descending powers of z; do the same for the denominator:

The z operator means delay, and its power tell us how many samples. Positive powers of z are in the future, and negative powers are in the past. Our filter can’t see into the future (we call such filters “non-causal”), so we need to reduce the powers of z to zero and below (to make it causal). To do this we multiply the numerator and denominator by , which subtracts two from each power of z, and gives us our final form:

Our coefficients are now evident:

Because the b0 coefficient is associated with y[n], the filter output, we divide everything by b0 to normalize the filter. Here we also note some convenient relationships between the “a” coefficients:

As a further efficiency, you can compute the common numerator term once and use it where needed. When calculating the coefficients, Q is the filter Q value (for a Butterworth lowpass, where the passband is flat with no corner peaking, use 0.7071, which is the reciprocal of the square root of two). Recall that we defined K as

where Fc is the filter corner frequency you want, and Fs is the sample rate.

Flawless explanation Nigel, excellent tutorials!

The clearest explanations of the process I have ever read! I now get that it’s kinda mechanical in Mathematica or Maple, yet requires human assistance to get right.

Any advice on approaching polynomials where y[0] isn’t present? Do you timeshift the whole expression one sample and go from there or is there a different “correct thing” to do?

Thanks in advance.

Well, y[0] is the output, but yes, in general each additional increment in the index indicates another unit delay—so that if you skip from y[1] to y[3], there would be two unit delays.

Thanks for these very good explanations !

But I’m wondering about gain ? where is it in these equations ? implicit ?

I can’t figure out where it is…!

Hope it’s not to late ! thank you

There is gain, but we normalize to unity gain by dividing all the factors by the output coefficient. So, the lowpass filter has unity gain in the passband, and only corner frequency and Q adjustment. Something like a peaking and shelving filters are a little different, with positive and negative gain for the peaking and shelf portions, and unity gain elsewhere.

Okay, thanks. I understand for unity gain now. But, for instance, in the case of a peaking filter, how do you adjust the gain of the peak ?

Trying to understand that, I played with your calculator and noticed that only a0 and a2 change (I also noticed that a0 and a2 aren’t the same in the case of a peaking filter and that’s where I understood that your equation above is only for lowpass filter, isn’t it ?)

How can I reproduce your reasoning for the peaking filter ?

To clarify, my goal is to convert the coefficient used in the direct form 1 equation to those of the direct form 2 equation (as the DF1 is used in Max/MSP and the DF2 in Pure Data, and I need to “import” curve from Max/MSP to Pure data).

Tank you again !

OK, first, you don’t need to convert coefficients for the different biquad direct forms. The different forms are simply transpositions. My C++ source uses transposed direct form II, which works well for floating point, but I’ve used the same in direct form I, which suits the 56k DSP family. So, if you want filters that behave the same as in my Biquad calculator v2, then you can use the equations given in the source code.

Also, you noted that only a0 and a2 change for the peaking filter gain, but that’s only true for boost—cut requires a different arrangement to keep the response symmetrical (the poles are fixed and the zeros as moved for boost, zeros are fixed and poles are moves for cut).

Basically, a peaking filter is a bandpass response added to the input. But if you simply mix a bandpass with the direct signal, you’ll have phase issues where they come together (because the IIR filter is not phase linear). So, you actually have to work it into the equation then do the bilinear transform on the whole thing.

For instance, since we have the lowpass equation above, 1 / (s^2 + s/Q +1), I’ll use the example of a low shelf filter. It would be the lowpass, times a gain value (H, which is zero-based), plus the direct signal (1); Q is a complicated issue with shelving filters, so I’ll revert to Butterworth response, where 1/Q is sqrt(2): H / (s^2 + s*sqrt(2) +1) + 1. We put that into a common denominator: (H + (s^2 + s*sqrt(2) +1)) / (s^2 + s*sqrt(2) +1). We want unity gain to be a gain of 1, so we substitute V for H + 1, and end up with: (V + s^2 + s*sqrt(2)) / (s^2 + s*sqrt(2) +1). Now, you run that through the bilinear z transform, and that’s where you get the coefficient formulas. For the boost case anyway…

Wow, okay thanks a lot. I’m a bit lost in math (and english also!).

So, you say that I don’t need to convert coefficients between DFI and DFII but, when I try to apply those of DFI (from max msp’s biquad~) to a biquad~ using DFII (the one of puredata), I can’t ear a sound. It should mean that the two biquads don’t use the coefficients the same way (and It’s normal since they are not based on the same equation). But there should be a way to convert (or transpose) the coefficients between one and the other…

Your coefficients don’t work with puredata so It’s a pitty for me, I can’t use your sources…

I’ll have to be creative to do what I want to do ! 🙂

Thank you for your help anyway !

One key gotcha is that while “a” and “b” coefficient naming is common, there is no convention on which one is the feedback (pole coefficients) and which are the feed-forward (zero coefficients). Try using the a2 coefficient for b2, etc. I use the convention that “a” is on top of the transfer function, the left side of a direct form I diagram, as do many other classic text books.

Yes, I tried changing a for b, etc, but it still doesn’t work.

I think I’m going to calculate the coefficients, starting with frequency, gain and q. Now I’ve to find how…!

I tried your calculator again and some type of filter seem to work (I can ear sound through a biquad objet) but not as expected (for instance a “selective” bandpass filter – with Q of 10 – doesn’t filter the audio a lot as it should). Very strange behavior…

I looked up the Max biquad~ object, if that’s what you are using:

y[n] = a0 * x[n] + a1 * x[n-1] + a2 * x[n-2] – b1 * y[n-1] – b2 * y[n-2]

This is precisely the convention that I use, so there should be no change needed.

For a filter that’s not working right for you, give me one exact set of parameters for the calculator (v2), and the list of coefficients it produces. Describe the incorrect filter response that you are seeing as clearly as you can.

Yes that’s what I thought. But my goal is to create a filter in max msp and to import it into pure data. My issue is in pure data because it uses these equations :

y[n] = b0*w[n] + b1*w[n-1] + b2*w[n-2]

with :

w[n] = x[n] – a1*w[n-1] – a2*w[n-2]

And I’m pretty sure that it’s not the same set of coefficients between those two (I tried to put them into pure data, to change a and b etc…)

With those equations, a and b coefficients need to be swapped. However, if that still doesn’t work, it may be that the equation are misstated—another very common difference in implementations is whether the minus signs are rolled into the coefficients or not (some people like to use all additions). So after you swap a and b, and confirm that it still doesn’t work, flip the signs of the feedback coefficients. (If that doesn’t work, you can try other combinations, but the feedback coefficients are the usual issue.)

This is it !

So first, I had to invert a and b, then, to flip the signs of b1 and b2. In fact, Puredata equation is :

y[n] = b0*w[n] + b1*w[n-1] + b2*w[n-2]

with :

w[n] = x[n] + a1*w[n-1] + a2*w[n-2] (all additions, as you said. I made a mistake in my previous reply)

Thank you very much !

PS: May I post a link to this website to inform the Max/MSP community ? A post talks about this with no solutions, It could help.

Yes, of course you can post a link! Glad you got the problem solved!

Very good explanation! can it be used for a notch filter as well?

Yes—I derived all the filters, including notch, in the biquad calculator and biquad source code, exactly this way.

Hi Nigel,

I have a question: When doing the pre-warping and substitute s to Z at the same time , the K=tan(wT/2) , how does this tan(wT/2) came from ?

(My understanding is to pre-warp by Wc=2/T*tan(wc/2) first than substitute s to Z by s=2/T*(z-1)/(z+1) )

thanks

This shows the derivation of the tan warping: https://en.wikipedia.org/wiki/Bilinear_transform#Frequency_warping