Processing math: 100%

mathr / blog / #

Biquad conversions

One representation of a biquad filter's transfer function is:

H(z)=b0+b1z1+b2z2a0+a1z1+a2z2

Without loss of generality, it can be assumed that a0=1 (otherwise divide both top and bottom by the original a0 to give a new set of coefficients).

Another representation of the transfer function is:

H(z)=g(zZ)(zZ)(zP)(zP)

where denotes complex conjugation. Here Z is a zero and P is a pole, and both are complex, and g is gain factor, which is real. It will make things easier to assume g=1, if it isn't then just multiply the input x by g before-hand. Multiplying out the top and bottom, and dividing both by z2, recovers the first representation:

H(z)=12Re(Z)z1+|Z|2z212Re(P)z1+|P|2z2

Or more explicitly:

a0=1a1=2Re(P)a2=|P|2b0=1b1=2Re(Z)b2=|Z|2

The utility of the second pole-zero representation is in filter design, the former is useful in filter implementation. The direct form 2 implementation is a pair of recurrence relations, where x is the input, y is the output, and w is only used internally:

wn=a0xna1wn1a2wn2yn=b0wn+b1wn1+b2wn2

The software Pure-data implements its biquad~ object using direct form 2, except with a1 and a2 negated. So an instantiation biquad~ A B C D E has the correspondence:

A=a1B=a2C=b0D=b1E=b2

The final puzzle is the factorization from direct form 2 into pole-zero representation. Assuming a0=1=b0 (otherwise factor out the gain coefficient and normalize), we get:

Re(Z)=12b1Im(Z)=b214b21Re(P)=12a1Im(P)=a214a21

The motivation for this post was mkfilter giving code output which is inherently unstable for higher order filters, reducing it to smaller stages (like biquads) in serial is much less likely to blow up. I did some experiments converting the pole-zero output from mkfilter into Pd patches using cpole~ and czero~, but biquads are more efficient as the input and ouput are both real (a biquad needs 5 real multiplications and 4 real additions, whereas a single complex multiplication needs 4 real multiplications and 2 real additions). The next step is to write a program into which I can pipe mkfilter output, and get out a Pd patch using some chained biquads.

In the process I fixed some minor issues in the mkfilter source code (retrieved 2015-03-26) and wrote a new build system to get it to compile in the modern age.