Parallel bank of DSP filters

Schroeder reverberator

This example shows how to create a Schroeder reverberator. Here it is a cascade of 3 Schroeder allpass sections and a parallel bank of 4 comb filters as illustrated by the figure below.

First, create the Schroeder allpass sections and comb filters and select only the comb filters.

Then, click on the button in the right panel of the DSP filter dialog. This copies the current filter selection in a parallel bank and unselect copied items.

After adding the allpass sections to the selection, you have created the Schroeder reverberator. You can also delete the original comb filters if you do not need it anymore.

When right clicking on a parrallel bank, a Mixer item is available in the menu displayed.

Selecting the Mixer item opens a mixer dialog box.

Each row in the mixer dialog represents one input chanel filtered by one of the DSP filters in the parallel bank. Here, we have 2 chanels (stereo), 4 filters and therefore 8 lines.
There is one column of entries for each output chanel. The output signal for a given chanel is the sum of the entries of the corresponding column multiplying the output of the row at its left.

The left figure above shows the default for which each input chanel is multiplied by 1/(number of filters) for the correponding output and by 0 for the other one.
In the right figure, the output chanels of the filter 2 (FFCF 9719, 0.715) ar mixed between chane 0 and 1 outputs.

You can download schroeder_reverb.py displayed below and Load it from the application for reproducing the Schroeder reverberator. You can edit it in order to add more filters, use feedback instead of feedforword comb filters or add lowpass filter taps for reducing high frequency reverberation.

 schroeder_reverb.py
def shroeder_reverb(damping=0.,  audioFile=""):
    # Example from
    # https://ccrma.stanford.edu/~jos/pasp/Example_Schroeder_Reverberators.html

    # If damping is not 0 
    # => add low pass filter tap to comb an allpass filters
    # You can also set it independently for each all-pass/ comb later

    DSPFilterOpen()
    width, height=GetDSPSize()
    SetDSPSize(700, 300)
    DSPClear()
    #Remember where to start selection for filters created later
    nFilterOrg=dsp_get_num_filters()
    # set a damping parameters 
    # => add low pass filter tap to comb an allpass filters

    DSPFilterType("All Pass")
    delay=[1051,337,113] # in sample
    g=0.7
    gain=[g,g,g]
    longdelay=True
    if longdelay == True:
        #Nearest prime from original delay *1.8 ~> 44100/25000
        #since the filters have been designed for 25 kHz sample rate
        delay=[1891,607,199]
        gain=[g,g,g]

    nNew=len(delay)
    # Creat Schroeder allpass sections for each pair (delay, gain)
    for i in range(nNew):
        DSPFilterAllPass( delay[i], gain[i],damping)
        
    DSPFilterType("Comb")
    delay=[4799,4999,5399,5801]
    gain=[0.742,0.733,0.715,0.697]
    if longdelay == True:
         #Nearest prime from original delay *1.8 ~> 44100/25000
        delay=[8641,8999,9719,10433]

    nNew=len(delay)
    #for selcting the last one when done
    nFilters=dsp_get_num_filters()
    # Creat Comb filters for each pair (delay, gain)
    feedBack=0 # feedforward otherwise
    for i in range(nNew):
        DSPFilterComb(feedBack, delay[i], gain[i],damping)
    #Unselect all 
    DSPSelectAll(0)
    #Select only created comb filters
    DSPSelect(nFilters, nFilters+nNew-1, 1)
    # Pack selection in a parallel block 

    # Create a parallel bank with the current selection
    label=DSPSetParallel()
    #select all pass sections
    DSPSelect(nFilterOrg, nFilterOrg+2, 1)
    #rename the filter
    DSPFilterRename(label, "Schroeder parallel combs")
    if audioFile != "":
        # Open an audio file and listen to the 
        DSPRealTime(1)
        OpenSoundFile(audioFile)
        # Playback for 10 sec
        PlaySound(0.00000000, 10)
        # wait audio stream ends
        WaitSoundThread(10.5)

#You can add set a damping parameter (lowpas filter tap)"
damping=0.
#You can also add an audio file for listen"
audioFileName=""
shroeder_reverb( damping, audioFileName)
        

Parallel bandpass filters

In the example below where two bandpass filters have been put in a parallel bank, the default mixer settings are not appropriated since in that case, each filter contributes to a different part of frequency range.
The left band whose output has been multiplied by 0.5 and therefore has a passband attenuation of -6 dB whereas the right band whose mixer coeffictients have been set to one does not attenuate the signal in the passband.

⚠ The impulse response of a parallel filter bank is the sum of the impulse responses of individual filters. These are complex function and there sum have a linear phase only if all filters have a linear phase and the same length (order).

Parallel bank of two Kaiser window linear-phase filters with the same order. The sum is a linear-phase filter.
Parallel bank of two Kaiser window linear-phase filters with different orders. The sum is not a linear-phase filter.

Python commands

Here are the functions that can be called from Python scripts.

            DSPSetParallel()
            
# Copies the current filter selection in a parallel filter bank.
# Returns the label of the new filter bank.
###############################################################
            DSPOpenParallelMixer(string filterBankLabel)
            
# Opens the Mixer dialog.
###############################################################
            DspApplyParallelMixer(string filterBankLabel, int numChanel, in numRow, 
            M[0,0],  M[0,1], .... ,  M[numRow-1,0], M[numRow-1,1])
            
# Set the mixer for the parallel bank filterBankLabel.
# M[i,j] is the mixing coeffictient for row i to output j.
###############################################################