Perfect Crossover Filters


With a little help from this forum and this post here:

I’ve been attempting to build a perfect Multiband crossover network, however I keep seeing issues in the signal when it’s reconstructed. Although audibly it sounds okay, I see distortions in the signal, or perhaps the “perfect crossover” isn’t perfect in this manner?

My method is the one described by @danielrudrich in this post:

I’m using the makeLowPass, makeHighPass, and makeAllPass functions of the IIR filter classes to construct the filters

The block looks like:

                                | ---> HP ---> |
        | ---> HP ------------->|              + ---> |
        |                       | ---> LP ---> |
     -->|                                      + ---> |
        | ---> LP ---> AP -------------------- + ---> | 

HP & LP refer to two of those lowpass & highpass filters in a chain. From my understanding of these forum posts, chaining the filters together in this manner creates a Linkwitz Riley response.

The AP filter has the cutoff set to that of the second crossover. From my understanding this should realign the phase between the bands for a perfect response. I’ve tried using two Allpasses in chain, and one, but neither generated a perfect response. So, that is the first thing I’m unclear about. When using these chained butterworths, do I need to also chain two AP filters for the correct response?

I’m getting a sort of notch sounding phase cancellation in the signal I can’t track down. This is actually improved by removing the Allpass filter, but visually I can see the waveform is getting distorted although audibly the notch is lessened.

Would any DSP wizards be willing to chime in and tell me if the is the correct approach here? The article from modern metal says the mid band should have its phase inverted, but this only worsens the notching sound in the signal.

I was able to get a perfect reconstruction by doing the approach of generating the low & high band, and subtracting them to generate the mid band. although this method works well, I need to refilter the bands after processing, which that doesn’t support. I also found the bands to sound less tight than desired.

Appreciate any input or feedback!



1 Like

I don’t think that makeAllPass will give you the needed allpass-filter to correct the phase shift by the LR-crossover.

Take a look at these lines:

That’s the code how we calculate the coefficients for LP, HP and AP of the LR-crossover. The allpass coefficients are set in the last line.

I think the logic here is good, but the order of the filters might be off. If you have LP and HP filters at the 2Nth order, you need an all-pass filter at the Nth order to compensate their phase response.

If you want to create your third bands separator using only JUCE classes, each LP and HP block has to be the combination of TWO juce::IIRFilter, to get a 4th order LP/HP filter, all with the same cutoff frequency and the resonance at 1/sqrt(2). Then you can use on juce::IIRFilter for the all-pass filter with the right cutoff frequency, and you get a separation with 4th order Linkwitz Riley filters. The maths is quite different for 2nd order, you can’t just use a regular LP or HP filter at the 2nd order, and you need 1st order all-pass filter so it would be more complicated to do.

Hope that helps


Hey guys, thanks for your response : )

Thanks for the code example Daniel. I feel kind of uncomfortable copying your coefficient code. Do you have any references I can read into on this perhaps?

I actually did check out the compressor code process block where you posted it in the other channel, but it was quite complex! I’ve been coding things in a quite obtuse way as I work through this.

Thanks for this Ivan, so that clarifies I only need a single All Pass

So, from what I can tell, in order to pull this off in the way described, here are all the filters I’d need:

    enum Filters {
    //          \
    //           \-------------
    Stage_1_Band_1_Lowpass = 0,
    //           /------------
    //          /
    //         /--------\
    //        /          \
    //-------/            \---------
    //                    /--------
    //                   /

And the coefficients as such:

//          \
//           \-------------

IIRCoeff low_mid_lowpass_coeff = dsp::IIR::Coefficients<float>::makeLowPass(mSampleRate, inCrossover1Freq);
IIRCoeff low_allpass_coeff = dsp::IIR::Coefficients<float>::makeAllPass(mSampleRate, inCrossover2Freq);

mBSF.LeftFilters[BandSplittingFilters::Stage_1_Band_1_Lowpass].coefficients = low_mid_lowpass_coeff;

mBSF.LeftFilters[BandSplittingFilters::Stage_2_Band_1_Lowpass].coefficients = low_mid_lowpass_coeff;

mBSF.LeftFilters[BandSplittingFilters::Stage_1_Band_1_Allpass].coefficients = low_allpass_coeff;

//           /------------
//          /

IIRCoeff low_mid_highpass_coeff = dsp::IIR::Coefficients<float>::makeHighPass(mSampleRate, inCrossover1Freq);

mBSF.LeftFilters[BandSplittingFilters::Stage_1_Band_2_Highpass].coefficients = low_mid_highpass_coeff;

mBSF.LeftFilters[BandSplittingFilters::Stage_2_Band_2_Highpass].coefficients = low_mid_highpass_coeff;

//         /--------\
//        /          \
//-------/            \-------------

IIRCoeff mid_high_lowpass_coeff = dsp::IIR::Coefficients<float>::makeLowPass(mSampleRate, inCrossover2Freq);

mBSF.LeftFilters[BandSplittingFilters::Stage_1_Band_2_Lowpass].coefficients = mid_high_lowpass_coeff;

mBSF.LeftFilters[BandSplittingFilters::Stage_2_Band_2_Lowpass].coefficients = mid_high_lowpass_coeff;

//                     /--------
//                    /

IIRCoeff mid_high_highpass_coeff = dsp::IIR::Coefficients<float>::makeHighPass(mSampleRate, inCrossover2Freq);

mBSF.LeftFilters[BandSplittingFilters::Stage_1_Band_3_Highpass].coefficients = mid_high_highpass_coeff;

mBSF.LeftFilters[BandSplittingFilters::Stage_2_Band_3_Highpass].coefficients = mid_high_highpass_coeff;

So, it’s two LPF on the dry input to create the low band a cross over 1, then an Allpass filter.

two HPF on dry input at crossover 1 to create the high band, now we have two bands, then pass the high band to a LPF at crossover 2 to generate the mid band, and a HPF at crossover2 to create the high band.

So, if this is all correct, then perhaps it really is the APF isn’t working for this like you say @danielrudrich? Are there any good papers on where these coeffs came from? I don’t wanna just copy for coefficient code : / although I’ll give it a test and see if that fixes it. Thank you both!

1 Like
                             | ---> HP1
   | ---> HP0 ---> AP2 ----->|             
   |                         | ---> LP1 
   |                         | ---> HP2 
   | ---> LP0 ---> AP1 ----->| 
                             | ---> LP2 

Did you do something like this? AP1 has the frequency response of HP1+LP1 and AP2 that of HP2+LP2 in order to compensate both paths.

With that layout you’ll need an additional AP2 in the lower lane.
AP1 only compensates for HP1+HP2, however the lanes after HP1 and LP1 get an additional Allpass-characteristic (AP2 which is HP2+LP2).

@danielrudrich Thanks very much! I seem to have a flatter response now. There still seems to be a drop off of a few dB across the whole frequency range but I can live with that.

Hey Jake, wondering if you figured this out or found some good references on how/why to place the All-Pass in the right place? Going through this process trying to create a multi-band processor right now

Hey, the filter configuration i showed above is actually correct. If you set it up that way, you should be all good. The issue i was having of phasyness was actually due to extra latency being introduced to a band before summing them back together, something to watch out for!