How do some filters allow the cutoff/center freq to be above SR/2?

Hi I’ve noticed that in some plugins like Fabfilter Pro-Q3 and Maag EQ4 you can set the cutoff/center frequency above the nyquist freq. I assumed that perhaps these plugins are doing some kind of oversampling which seems easy enough with the Oversampling class. But then I thought it seemed like overkill to process 2 samples for every 1 just so you can attain a slightly different curve by putting the cutoff/center above nyquist (which may be something I need to do with the plugin I’m working on). Unless those plugins oversample by default to reduce aliasing and such (something I don’t know much about). Also, I noticed that the Oversampling class says that it may introduce latency, although it shouldn’t if using the IIR filters right? Fabfilter Pro-Q3 and maag eq4 certainly don’t report any latency.

Does anyone know if oversampling is happening with plugins that allow you to put the cutoff/center frequency above nyquist? If not, how else might those plugins be achieving the ability to set such a high cutoff/center freq? It seems impossible without oversampling given where you can and cannot put poles and zeros in the z-domain.

Dan Worrall mentioned something related before. Oversampling is an easy but inefficient way (by the way, if an oversampling does not introduce latency, it is wrong). To fix the cramping issue of bell filter near the Nyquist freq, Pro-Q3 adds another high-shelf filter (Massberg method). I would guess that you can do something similar so that you can ‘‘match’’ the Analog response even if the cutoff is above the Nyquist freq.

Ah I see, do you know of any resources about how to do the Massberg method? I cannot seem to find anything when I google it.

Oversampling doesn’t just give you a higher frequency, it also alleviates cramping towards Nyquist. Albeit at a x2 cost. You also want to use FIR halfband filtering because IIR will screw up the phase. That comes with a little latency.

The Fabfilter filters (and others) are proprietary. I might be wrong, but I don’t think they’re using a high shelf fix because all of their filter types are decramped.

Closest I’ve found are Martin Vicanek’s matched filters. The paper is available online if you search for it.

Still, I get the feeling there’s a method of filtering that doesn’t require a fudge factor for decramping and the same technique will allow a cutoff above Nyquist. Alas, I can’t shed any light on what this method is.

1 Like

Still, I get the feeling there’s a method of filtering that doesn’t require a fudge factor for decramping and the same technique will allow a cutoff above Nyquist. Alas, I can’t shed any light on what this method is.

@Nitsuj70 this is essentially what Massberg’s approach does, by using a different analog prototype filter prior to applying the bilinear transform to get the discrete time equivalent.

FWIW, by definition, for it to appear to be a “lowpass” with a cutoff above Nyquist, it’s a highpass by definition (no zeros at Nyquist). Another way of thinking about it is that if you take the inverse bilinear transform you’ll get back an analog filter whose gain at infinity is non-zero.

The original paper was in 2011:
Massberg, M. (2011, October). Digital low-pass filter design with analog-matched magnitude response. In Audio Engineering Society Convention 131 . Audio Engineering Society. 2011.


And the paper that Nitsuj70 mentions is (a good & brief review to other methods is included!):
Vicanek, M. (2016). Matched Second Order Digital Filters.


Some other methods (I implemented it and it became non-sense with some large Q):
Al-Alaoui, M. A. (2007). Novel approach to analog-to-digital transforms. IEEE Transactions on Circuits and Systems I: Regular Papers , 54 (2), 338-350.

1 Like

I used Martin Vicanek’s method as a starting point to come up with a way to create a pair of matching digital biquads to any analog biquad - thus enabling this for any analog prototype regardless of pole/zero count. It’s mentionned in the paper. The various matching criteria he came up with can be mixed and matched and one ends up with linear systems of degrees up to five that can be solved to get digital biquad coefficients. It for instants enables you to fit a digital biquad at 5 frequency/gain points. Lots of considerations have to be taken in order not to end up with unrealizable filters, but it is doable.

If a filter has poles/zeros above nyquist the fitting actually becomes much easier because the curves become simpler if the hard/curvy parts are outside the digital fitting range.

3 Likes

So I implemented Vicanek’s high pass filter and high shelf filter. They are great and I may use these. Ideally I would have more like a butterworth style high pass filter where the cutoff is at -3dB on the curve, there is no Q parameter and a bonus if the slope is adjustable. Does anyone know of how to calculate the biquad coefficients for a matched butterworth style filter? I cannot seem to work it out from Vicanek’s paper. Basically I’m trying to make a parallel EQ for various reasons and the resonant high pass filter in Vicanek’s paper causes some phase cancellation if the Q is above a certain value, but if the Q is any lower the curve warps and the actual roll off loses its intuitive releationship with the cutoff parameter.

The Vicanek biquad low- and highpass filters are modelled after 2-pole butterworth low- and highpass filters. Just use sqrt(0.5) as Q and you will get a matched butterworth response. There is no phase cancellation as long as the filter is not mixed with a parallel signal path. I haven’t experienced what you describe for low-Q… so maybe something isn’t calculated quite right?
If you want higher-order without too much fuss, you could look into stacking multiple biquads with the appropriate Q’s for a butterworth response. The factors can be calculated from a higher-order butterworth analog prototype, here’s an online calculator by EarLevel Engineering:

https://www.earlevel.com/main/2016/09/29/cascading-filters/

So you’d just use the Q values from there and stack Vicanek matched biquads sharing the same cutoff frequency to get a matched higher-order filter.

1 Like

I think I have worked out what the problem is. I actually need a first order butterworth filter because I’m doing parallel EQ. But of course Vicanek’s paper details only second order filters.

I verified this by using plugindoctor to compare the frequency response of two plugins I built. The first plugin is Vicanek’s high pass filter (with Q set to sqrt(0.5)) + unfiltered input and the second plugin is Faust’s butterworth high pass filter + unfiltered input. When I set the order of Faust’s high pass to 2, the frequency response of plugin 1 exactly matches the response of plugin 2 and there is phase cancellation in both cases. But if I set the order of Faust’s high pass to 1, there is no (or negligible) phase cancellation for plugin 2 and the slope is more gentle, which is what I would like.

Vicanek has another paper with coefficients for matched first order shelving filters, but not other first order filter types. Do you know how I would change the coefficients to get a matched first order high pass filter?

Edit: I have attached a screenshot of plugindoctor’s frequency analyzer, the pink curve is plugin 2 when the order of faust’s high pass filter is 1, the red curve is plugin 1 which is identical to plugin 2 when the order of faust’s high pass filter is 2.

First order low and high pass can be done in a similar way to the ones in the 2nd order paper. There’s no Q for one-poles, and the fitting can be done with the following conditions:

  • gain 0 at freq 0
  • match analog prototype gain at nyquist

For normalised frequency nf (1 = samplerate), pole coeffs a and zero coeffs b :
The single pole is using matched-z transform:

a_1 = -exp(-nf * 2 * M_PI)

The gain at nyquist from the analog one-pole is:

gain_nyq = sqrt(0.25 / (0.25 + nf * nf));

The fitting leads to these b:

b_0 = 0.5 * gain_nyq * (1 - a_1)
b_1 = -b_0

I hope I typed everything correctly. This only works for cutoff frequencies below nyquist.
If you want to process using a biquad, a_2 and b_2 are 0.

1 Like

This works perfectly thanks so much for your help with this. I tried to understand how to derive these filters myself but found it very difficult. Hopefully I’ll learn how to do it at some point. I’m going to explain how I implemented all of these filters for anyone else who finds themselves in a similar situation. Basically I’m implementing all of these filters in Faust and then exporting the Faust code to a JUCE project. All of the filters I implemented allow you to set a cutoff frequency above Nyquist (SampleRate/2), including the first order highpass I implemented.

Note that for each filter detailed below, the code calculates the coefficients which are then arguments to the tf21 function. This function is a direct-form second-order biquad filter. The a1,a2 coefficients describe where the poles are in the z-domain, and the b0,b1,b2 coefficients describe where the zeros are in the z-domain.

Here is the matched second order high pass filter. The coefficients are taken from this Vicanek paper:

biquad_highpass_matched(fc,Q) = tf21(b0,b1,b2,a1,a2) with {
    w0 = (2*ma.PI*fc)/ma.SR; //converts freq in HZ to radians per sample
    q = 1/(2*Q); //Explained just after Equation 6
    a1coef = -2*ma.E^(-q*w0);
    a11 = a1coef*cos(sqrt(1-q^2)*w0); //Equation 12 of paper
    a12 = a1coef*cosh(sqrt(q^2-1)*w0); //Equation 12 of paper
    a1 = ba.if(q <= 1, a11, a12); //Equation 12 of paper
    a2 = ma.E^(-2*q*w0); //Equation 12 of paper
    f0 = fc/(ma.SR/2); //converts freq in HZ to half-cycles per sample
    r1= (1-a1+a2)/sqrt((1-f0^2)^2 + f0^2/Q^2); //Equation 48 of paper
    b0 = r1/4; //Equation 49 of paper
    b2 = b0; //Equation 49 of paper
    b1 = -2*b0; //Equation 49 of paper
};

Here is the matched high shelf filter. The coefficients are taken from this Vicanek paper:

biquad_matched_shelf(fc,G) = tf21(b0,b1,b2,a1,a2) with {
    f_ny = fc/(ma.SR/2); //converts freq in HZ to half-cycles per sample
    f_m = 0.9; // Equation 12 of paper
    phi = 1- cos(ma.PI*f_m); // Equation 4 of paper with f_m as argument
    alpha = 2/(ma.PI^2) * (1/(f_m^2)+ 1/(G*f_ny^2)) - 1/phi; // Equation 12 of paper
    beta = 2/(ma.PI^2) * (1/(f_m^2)+ G/(f_ny^2)) - 1/phi; // Equation 12 of paper
    a1 = -alpha/(1+alpha+sqrt(1+2*alpha)); // Equation 10 of paper
    b = -beta/(1+beta+sqrt(1+2*beta)); // Equation 10 of paper
    a2 = 0; //inferred
    b0 = (1+a1)/(1+b); // Equation 11 of paper
    b1 = b*b0; // Equation 11 of paper
    b2 = 0; //inferred
};

Finally, here is the matched first order high pass that @pflugshaupt explained:

biquad_highpass_1st_order(fc) = tf21(b0,b1,b2,a1,a2) with {
    nf = fc/ma.SR;
    a1 = -1 * ma.E^(-nf * 2 * ma.PI);
    gain_nyq = sqrt(0.25 / (0.25 + nf * nf));
    b0 = 0.5 * gain_nyq * (1 - a1);
    b1 = -b0;
    a2 = 0;
    b2 = 0;
};
1 Like

This is missing the 1p lowpass. The pole is the same as in the highpass case:

a_1 = -exp(-nf * 2 * M_PI)

the other stuff comes from

  • gain1 at freq 0
  • match gain at nyquist
gain_nyq = sqrt(nf * nf / (0.25 + nf * nf))
b_0 = 0.5 * (gain_nyq * (1 - a_1) + 1 + a_1)
b_1 = 1 + a_1 - b_0
1 Like

What I’m still missing are similar nyq-matched 2p highshelf and lowshelf filters, but I also haven’t tried very hard. If anyone came up with a solution and wants to share… this would be the perfect spot ;).

1 Like

I follow the method from Vicanek’s paper and match the magnitude at three freq:

ws = [0, w0 * (1 - np.power(2, -q)), w0]

Here is the matched 2nd order lowshelf (f=20000, sr=48000, q=10.0, g=10dB):


and the highshelf:

The code is available at Google Colab (bonus: notch is included). I haven’t derived a closed-form formula but it shouldn’t be hard (I only use np.linalg.solve for two/three variables).

1 Like

The Man Himself has answered our prayers:

3 Likes

I almost forgot this thread :laughing: Here is an implementation of the Vicanek’s algorithm (with some modifications to make them work for a wide range of Qs).

Here’s my contribution to this topic. Matched allpass filters (with interactive plot):

https://apulsoft.ch/blog/matched-allpass

2 Likes

Nicely done!
I think the polarity is inverted on the 1st order one.

thanks! The one-pole has phase 0 at DC, which I consider to be the normal polarity. The minus sign on the -cos(ω)/(sin(ω)+1) coefficient might look weird, but it is exactly the same as the commonly used (tan(ω/2)-1)/(tan(ω/2)+1).