calf-studio-gear / calf

Developers repository of Calf Studio Gear. Expect some issues when using it for production.
http://calf-studio-gear.org
GNU Lesser General Public License v2.1
682 stars 96 forks source link

crossover filter bank don't have a flat frequency response #217

Open Bk8 opened 5 years ago

Bk8 commented 5 years ago

I was measuring the frequency response of your crossover filters and is not flat. I looked to this issue: https://github.com/calf-studio-gear/calf/issues/98

I don't really understand the code, but since you are using Linkwitz-Riley (as is mentioned in the issue) you need to compensate the phase introduced by each extra band split with an all-pass filter (look this link: https://www.modernmetalproduction.com/linkwitz-riley-crossovers-digital-multiband-processing/). I was not really able to solve this issue, this are the frequencies I used for this test: 5349.8 hz, 11602.1 hz and 15247.8 hz .

image

lpirl commented 5 years ago

As a workaround, I tend to put a multi-band compressor (which does nothing) in a session's master bus right after creating the session, so the filters don't mess with the frequency response of the mix later in the process when I do want to add multi-band compression… It would be great to see this fixed. <3

Bk8 commented 5 years ago

The problem is that even if the multi-band compressor is doing nothing you are removing part of the signal. This is happening because the frequency response of the crossover filter bank is not flat. I think the only open source multi band studio quality plugins are these http://lsp-plug.in/ (they have the only mastering open source fir linear phase eq that exists at the moment), but I have to test them more in depth to see if they are actually doing what are they supposed to do.

lpirl commented 5 years ago

Very true. The idea behind installing the compressor as early in the process as possible is that I compensate for the filters' frequency response when mixing/EQing.

v-fox commented 5 years ago

Is it possible to work around this with equalizer ?

For example, if I have multiband compressor and multiband limiter in series and with same crossover points, both showing -6db pits in their GUIs at those points, will adding 12db at those points in equalizer with matching curves help or mess it up more ?

jrigg commented 5 years ago

frequencies I used for this test: 5349.8 hz, 11602.1 hz and 15247.8 hz .

Those are quite close together, which could be making the notch worse due to phase issues. Having said that, measuring the response of the multiband compressor at the default crossover frequencies I get a -2dB dip at the low/mid crossover (1kHz) with LR8, and -1dB with LR4. The other two crossover points are flat.

jrigg commented 5 years ago

both showing -6db pits in their GUIs at those points

That isn't how it works. Linkwitz-Riley filter sections are -6dB at the designated frequency, but summing the LPF and HPF at that point doubles the level back to 0dB (both are in phase at the crossover frequency with LR4 or LR8). There shouldn't be a notch in the summed response.

jrigg commented 5 years ago

Further info. I've tested LSP Multiband Compressor, ZaMultiComp, and zita-lrx crossover, all set to similar frequencies to the Calf Multiband Compressor defaults (all LR4). All have similar ripples in the response, ie. around 1dB difference between min and max. Perhaps a completely flat response is expecting too much from this type of filter.

A suggestion: how about making the default filter mode LR4 instead of LR8?

sadko4u commented 5 years ago

Classic crossovers can not be designed as ideal crossovers because recursive filters do not allow to implement linear phase. Consider there are two filters with transfer functions:

H1[p]=1/(1 + 0.01*p)^2;
H2[p]=(0.01*p)^2/(1 + 0.01*p)^2;

And a final result, as a sum of frequency responses:

H3[p] = H1[p] + H2[p];

We really will get a notch filter here (H1 is red, H2 is blue, H3 is pink): изображение

And here's the phase response for each filter: изображение

Then... How should the second filter look like to get a flat frequency response? Simple:

H1[p]=1/(1 + 0.01*p)^2;
H4[p] = 1 - H1[p];

It actually has different characteristics (H1 is red, H4 is green): изображение So we can now get a flat response:

H5[p] = H1[p] + H4[p];

But what if we change gain of one bands, for example, by 4 times?

H5[p] = H1[p] + 4*H4[p];

изображение Oh, now we've got a hump in the response.

Bk8 commented 5 years ago

Classic crossovers can not be designed as ideal crossovers because recursive filters do not allow to implement linear phase. Consider there are two filters with transfer functions:

H1[p]=1/(1 + 0.01*p)^2;
H2[p]=(0.01*p)^2/(1 + 0.01*p)^2;

And a final result, as a sum of frequency responses:

H3[p] = H1[p] + H2[p];

We really will get a notch filter here (H1 is red, H2 is blue, H3 is pink): изображение

And here's the phase response for each filter: изображение

Then... How should the second filter look like to get a flat frequency response? Simple:

H1[p]=1/(1 + 0.01*p)^2;
H4[p] = 1 - H1[p];

It actually has different characteristics (H1 is red, H4 is green): изображение So we can now get a flat response:

H5[p] = H1[p] + H4[p];

But what if we change gain of one bands, for example, by 4 times?

H5[p] = H1[p] + 4*H4[p];

изображение Oh, now we've got a hump in the response.

Yes but if you change the crossover center frequency you get a huge 'hole' in the frequency spectrum depending on the center frequency. Also this is not an analog emulation and it say it uses Linkwitz-Riley filters (look the name of the class), so it must have a flat frequency response. And finally Linkwitz-Riley filters have an actual analog counterpart and some gear actually uses it, so I don't get the point of your reply. This is obviously an error in the implementation, or take a look to any digital commercial implementation and you are going to know what I'm talking about.

v-fox commented 5 years ago

@jrigg

That isn't how it works. Linkwitz-Riley filter sections are -6dB at the designated frequency, but summing the LPF and HPF at that point doubles the level back to 0dB (both are in phase at the crossover frequency with LR4 or LR8). There shouldn't be a notch in the summed response.

But that completely side-steps my entire question which is "Is it possible to work around this with equalizer ?", "THIS" being "the problem" of this issue and "work around" being what @lpirl does to "compensate for the filters' frequency response when mixing/EQing". I assumed that "the problem" is that there is somehow no summing or that there is some error that skews the curves which leads to a "hole" in response. But now @sadko4u,the creator of LSP himself, says that perfect compensation by summing is impossible, so I have to ask: is there a problem to begin with ? And if so, what is it then ? Are those "holes" the size that they are expected to be and in places that are the result of chosen crossover points ?

Does it actually behaves like it supposed to ? Is there supposed to be a workaround in implementation (some additional compensation step in calculations ?) or in configuration (claimed EQ compensation and such) ? And if it an intrinsic problem to this algorithm, are there better alternatives ? Are those alternatives implemented in some similar F/OSS-licensed JACK plugin toolkit ? And what makes one assume that "commercial implementations" are somehow differ ?

sadko4u commented 5 years ago

To avoid holes for the final EQ curve, you need to utilize dynamic shelving filters to adjust band's gain instead of LR filters with future summing of results. This will work for multiband plugins as it is done in LSP Multiband Compressor plugin: https://lsp-plug.in/?page=manuals&section=mb_compressor_mono

Modern operating mode - special operating mode that allows to avoid disadvantages of 'classic' crossover-based compressors. Crossiver-based compressors use crossover filters for splitting the original signal into independent frequency bands, then process each band independently by it's individual compressor. Finally, all bands are summarized and so the output signal is formed. This principle has a huge disadvantage because recursive crossover filters have non-linear phase and that's why even after just splitting signal into separate bands and summarizing them back, we get the amplitude distortion of the signal. It's not so noticeable when each band is present by frequency range of more than decade Hz but the distortion is very noticeable when there are too many narrow bands. In Modern mode, each band is processed by pair of dynamic shelving filters. This allows to avoid amplitude distortion and preserve the 0 dB transfer function at the whole frequency range when compressor is not working.

But this won't work for crossover where you decompose the signal into bands and then summarize them somewhere back.

Bk8 commented 5 years ago

@v-fox If you compensate that way are going to change phase of the input signal and you are not going to end with a flat response. I'm not assuming "commercial implementations" are different, I know they are different. You can measure the frequency response using an impulse, and you are going to see that the response is perfectly flat. And that is because they use Linkwitz-Riley filters that are correctly implemented, the ones that are being used in Calf just have the name of a Linkwitz-Riley filter but they are something completely different. It is completely possible to create perfectly flat crossovers, I made one and I may submit a patch if I have time (because my coding standards are really different).

boomshop commented 5 years ago

I may submit a patch if I have time

That would be highly appreciated.

sadko4u commented 5 years ago

Let's solve the reverse task: we need to have two filters with transfer functions H1[p] and H2[p] which give H1[p] + H2[p] = 1. Let's start with the second-order filter:

k = 0.01;
H1[p]=1/((k*p)^2 + 2*k*p + 1);

изображение Now we need to design filter H2[p] = 1 - H1[p], which is:

H2[p] = 1 - 1/((k*p)^2 + 2*k*p + 1) = ((k*p)^2 + 2*k*p)/((k*p)^2 + 2*k*p + 1);

изображение As we see, we get a boost here which is not desirable.

But now we can rewrite our equation in different form, which is true:

H1[p] + H2[p] = 1;

Now we substitute H1[p] and H2[p]:

1/((k*p)^2 + 2*k*p + 1) + ((k*p)^2 + 2*k*p)/((k*p)^2 + 2*k*p + 1) = 1

Now do some magic to make the top polynoms to be symmetric (move k*p to the top of the first):

(1+k*p)/((k*p)^2 + 2*k*p + 1) + ((k*p)^2 + k*p)/((k*p)^2 + 2*k*p + 1) = 1

Now let's consider:

H4[p]=(1+k*p)/((k*p)^2 + 2*k*p + 1);
H5[p]=((k*p)^2+k*p)/((k*p)^2 + 2*k*p + 1);
H6[p]=H4[p]+H5[p];

изображение We finally got the result that we've expected. Now, lets's raise gain of one of bands:

H7[p]=H4[p]+4*H5[p];
H8[p]=4*H4[p]+H5[p];

изображение Seems to be working well but... These filters are NOT LR filters because they don't give 6 dB attenuation at the split point. To compute a more sharp filter, we need to write the equation for 4-order filter and do the same things we've done previously but now with 4-order polynoms which is not trivial operation.

jrigg commented 5 years ago

to write the equation for 4-order filter and do the same things we've done previously but now with 4-order polynoms which is not trivial operation.

Can a 4th-order LR filter not be implemented by cascading two 2nd-order Butterworth filters? IIRC that's how the original analogue LR crossovers were made.

sadko4u commented 5 years ago

to write the equation for 4-order filter and do the same things we've done previously but now with 4-order polynoms which is not trivial operation.

Can a 4th-order LR filter not be implemented by cascading two 2nd-order Butterworth filters? IIRC that's how the original analogue LR crossovers were made.

No, it can't. If we take:

H7[p]=H4[p]*H4[p];
H8[p]=H5[p]*H5[p];

The final equation:

H7[p] + H8[p] = 1

will become false due to complex nature of the p parameter. The same is true relative to Butterworth filters, which are chains of 1-order of 2-order biquad filters.

Bk8 commented 5 years ago

@sadko4u You are a bit confused, a 2 band Linkwitz–Riley consists of a parallel combination of a low-pass and a high-pass. Each filter is created cascading two Butterworth filters, each of which has −3 dB gain at the cut-off frequency. The resulting Linkwitz–Riley filter has a −6 dB gain at the cutoff frequency. This means that summing the low-pass and high-pass outputs, the gain at the crossover frequency will be 0 dB. But this is the most trivial case, if you need an n band frecuency splitter you also need to add an all-pass filters. These all-pass filters are made of Linkwitz–Riley 2 band cross-overs to add the same amount of phase to every band.

sadko4u commented 5 years ago

@Bk8, alright. Let's start with second-order Butterworth filter. The equation of 2-order Butterworth hi-pas and low-pass filters are:

k = 0.01;
H1[p] = 1/(k*k*p^2 + 1.414*k*p + 1); // Low-pass
H2[p] = (k*k*p^2)/(k*k*p^2 + 1.414*k*p + 1); // Hi-pass
H3[p] = H1[p] + H2[p]; // The sum
H4[p] = H1[p] - H2[p]; // The difference

As we see, we get a notch or a hump, depending on the phase (positive, negative) of one of results: image

Now let's build LR filters:

H5[p] = H1[p] * H1[p];
H6[p] = H2[p] * H2[p];
H7[p] = H5[p] + H6[p];

image

What about if we change gain?

H8[p] = H5[p] + 4 * H6[p];
H9[p] = 4 * H5[p] + H6[p];

Seems to be working well: image

What about higher-order filter? Let's take 3-order Butterworth filter as a base:

H10[p] = 1/((k*p + 1)*(k*k*p^2 + k*p + 1));
H11[p] = (k*k*k*p^3)/((k*p + 1)*(k*k*p^2 + k*p + 1));
H12[p] = H10[p] * H10[p];
H13[p] = H11[p] * H11[p];
H14[p] = H11[p] + H12[p];

Whoops, it's not working: image

sadko4u commented 5 years ago

Let's try to update top of the H1 and H2 filters:

H1[p] = 1/(k*k*p^2 + 1.414*k*p + 1);
H2[p] = (k*k*p^2)/(k*k*p^2 + 1.414*k*p + 1); 

H3[p] = (0.707*k*p + 1)/(k*k*p^2 + 1.414*k*p + 1);
H4[p] = (k*k*p^2 + 0.707*k*p)/(k*k*p^2 + 1.414*k*p + 1);
H5[p] = H3[p] + H4[p];

Not very good filters (6db/octave) but the response is flat: image

What about 3-order butterworth filter?

H10[p] = 1/((k*p + 1)*(k*k*p^2 + k*p + 1));
H11[p] = 1 - 1/((k*p + 1)*(k*k*p^2 + k*p + 1));

These equations can be surprisingly good optimized to:

H10[p] = 1/(k*p+1);
H11[p] = (k*p)/(k*p+1);

We get the same 6 db/octave attenuated filters but more smooth, flat response: image

I'll try to reproduce the 4-th order filter now...

sadko4u commented 5 years ago

Here's the computation with 4-order Butterworth filter:

a = 0.765;
b = 1.848;

H1[p] = 1/((p^2 + a*p + 1)*(p^2 + b*p + 1));
H2[p] = (p^4)/((p^2 + a*p + 1)*(p^2 + b*p + 1));

x = 0.5*(a + b);
y = 0.5*(a*b + 2);

H3[p] = (x*p^3 + y*p^2 + x*p + 1)/((p^2 + a*p + 1)*(p^2 + b*p + 1));
H4[p] = (p^4 + x*p^3 + y*p^2 + x*p)/((p^2 + a*p + 1)*(p^2 + b*p + 1));

H5[p] = H3[p] + H4[p];

The result is pretty disappointing: image

The LR filters, by the way, are working:

H10[p] = H1[p] * H1[p];
H11[p] = H2[p] * H2[p];
H12[p] = H10[p] + H11[p];

image

sadko4u commented 5 years ago

@Bk8 , I've looked again at what you've said and seems that you're right about LR filters which are based no 2N-order of butterworth filter. I've looked at this article, it shows that additional all-pass filters should be added to the chain, as you've explained: https://www.modernmetalproduction.com/linkwitz-riley-crossovers-digital-multiband-processing/

sadko4u commented 4 years ago

@Bk8 Thank you for information from your side. Phase compensation using specially designed all-pass filters helped. Today I've finished and committed changes into the devel branch of the LSP Plugins suite which are fixing non-flat frequency response for the Multiband Compressor plugin in the Classic mode. The changes will be available in the 1.1.11 release of the LSP suite.

Bk8 commented 4 years ago

@sadko4u You helped me in the past, thanks to you! For fixing it, finally there is a great multiband compressor for linux!

unfa commented 4 years ago

Oh wow! I've always heard there's some phase issues in Calf Mutiband Comprssor, as it created a very distinct character in my drum sounds, though recently @x42 has shown me Ardour's phase and frequency response analysis - and it looked terrible!

Are these issues fixed now though?

alpetreov commented 3 years ago

@Bk8 Thank you for information from your side. Phase compensation using specially designed all-pass filters helped. Today I've finished and committed changes into the devel branch of the LSP Plugins suite which are fixing non-flat frequency response for the Multiband Compressor plugin in the Classic mode. The changes will be available in the 1.1.11 release of the LSP suite.

Are you open to explain how you designed your all pass filters for the phase compensation? I am currently struggling with this issue and your help will be deeply appreciated.

sadko4u commented 3 years ago

Are you open to explain how you designed your all pass filters for the phase compensation? I am currently struggling with this issue and your help will be deeply appreciated.

I'll talk in terms of analog filters.

If we have a low-pass filter with HL[p] complex transfer characteristic and a high-pass filter with HH[p] complex transfer characteristic, then after we sum the characteristics we get HA[p] = HL[p] + HH[p] transfer characteristic which, actually, is an all-pass filter with phase shift.

Example: HL[p] = 1/(1+ap + bp^2) HH[p] = (p^2)/(1+ap + bp^2)

The desired all-pass filter: HA[p] = HL[p] + HH[p] = 1/(1+ap + bp^2) + (p^2)/(1+ap + bp^2) = (1 + p^2) / (1+ap + bp^2)

sadko4u commented 3 years ago

@alpetreov

The overall optimized chain with all-pass filters for multiband compressor:

https://github.com/sadko4u/lsp-plugins/blob/ad2720345ce5dffb45f871146de1ae6d16f4c73d/src/plugins/mb_compressor.cpp#L1107-L1124

alpetreov commented 3 years ago

Are you open to explain how you designed your all pass filters for the phase compensation? I am currently struggling with this issue and your help will be deeply appreciated.

I'll talk in terms of analog filters.

If we have a low-pass filter with HL[p] complex transfer characteristic and a high-pass filter with HH[p] complex transfer characteristic, then after we sum the characteristics we get HA[p] = HL[p] + HH[p] transfer characteristic which, actually, is an all-pass filter with phase shift.

Example: HL[p] = 1/(1+ap + bp^2) HH[p] = (p^2)/(1+ap + bp^2)

The desired all-pass filter: HA[p] = HL[p] + HH[p] = 1/(1+ap + bp^2) + (p^2)/(1+ap + bp^2) = (1 + p^2) / (1+ap + bp^2)

Thank you so much for your fast reply! Much appreciated! It was exactly the concept which I was missing.

Cheers!

bsjdsp commented 1 year ago

So is the phase issue resolved, is a fix coming?

sadko4u commented 1 year ago

@bsjdsp for LSP, not for Calf.