I found an appropriate implementation http://www.musicdsp.org/showArchiveComm ... hiveID=266 (http://www.musicdsp.org/showArchiveComment.php?ArchiveID=266) which I ported to Java for testing.This means that summing the low-pass and high-pass outputs, the gain at the crossover frequency will be 0 dB, so the crossover behaves like an all-pass filter, having a flat amplitude response with a smoothly changing phase response.
Setup:
Now I wrote some code to create a 10sec chirp from 20Hz - 18kHz. I separated the signal at 2000Hz by the first Linkwitz-Riley to get two bands [low, high]. Both bands will then be split by two other Linkwitz-Riley to divide them again at 500Hz and 8kHz. So in theory I should have created four bands from 0-500Hz, 500Hz-2kHz, 2kHz-8kHz & lastly 8kHz-Nyquist. To test if everything works I put all bands into one wav-file to be opened in a sample-editor including the sum of the first split and the sum of the 4 bands. Please check the attached screenshot.
Now my problem:
It looks like the sum of the first split does follow the properties as described on wikipedia (flat response, no energy lost). However after the second split, energy loss is introduced at the first split-frequency. While it looks like magic to me, there must be a reasonable explanation for this behaviour. Either the loss was already there but barely visible and got increased after the second split or I did not quite understand the concept of cross-filters, group delay and phase shifting. Any help is appreciated.
Following the setup code and I attached the screenshot of the wav-file.
Code: Select all (#)
public final class BandSplitMain {
private static final int SAMPLE_RATE = 48000;
private static final String TMP_FOLDER = "tmp/";
public static void main( @Nonnull final String[] args ) throws IOException {
final int length = SAMPLE_RATE * 10; // 10 seconds
final double[] source = new double[length];
final double[] c0 = new double[length];
final double[] c1 = new double[length];
final double[] b0 = new double[length];
final double[] b1 = new double[length];
final double[] b2 = new double[length];
final double[] b3 = new double[length];
final double[] b_mix = new double[length];
final double[] c_mix = new double[length];
final double incMin = 20.0 / SAMPLE_RATE;
final double incMax = 18000.0 / SAMPLE_RATE;
final double incMult = Math.pow( incMax / incMin, 1.0 / ( double ) length );
double phase = 0.0;
double phaseIncr = incMin;
for( int i = 0; i < length; ++i ) {
source[i] = Math.sin( phase * 2.0 * Math.PI );
phaseIncr *= incMult;
phase += phaseIncr;
}
final LinkwitzRiley crossM = new LinkwitzRiley(); // split source into 2 bands
crossM.setFreq( 2000.0, SAMPLE_RATE );
crossM.process( source, c0, c1, length );
final LinkwitzRiley crossL = new LinkwitzRiley(); // split lower band into 2 sub-bands
crossL.setFreq( 500.0, SAMPLE_RATE );
crossL.process( c0, b0, b1, length );
final LinkwitzRiley crossH = new LinkwitzRiley(); // split higher band into 2 sub-bands
crossH.setFreq( 8000.0, SAMPLE_RATE );
crossH.process( c1, b2, b3, length );
for( int i = 0; i < length; ++i ) {
c_mix[i] = c0[i] + c1[i]; // sum first split to check the outcome
}
for( int i = 0; i < length; ++i ) {
b_mix[i] = b0[i] + b1[i] + b2[i] + b3[i]; // sum second splits to check the outcome
}
final byte[] wavFile = WavEncoder.encode32( new double[][]{ source, c0, c1, c_mix, b0, b1, b2, b3, b_mix }, SAMPLE_RATE );
final FileOutputStream fos = new FileOutputStream( TMP_FOLDER + "noise.wav" );
fos.write( wavFile );
fos.close();
}
}
1. Chirp 20Hz-18kHz
2. First split low (<8kHz)
3. First split high (>8kHz)
4. Sum of 2, 3 looks perfectly flat
5. Second split low, low (<500Hz)
6. Second split low, high (500Hz-2kHz)
7. Second split high, low (2kHz-8kHz)
8. Second split high, high (>8kHz)
9. Sum of 5, 6, 7, 8 does not look flat at all around 8kHz (frequency of first split)