4 Band EQ

Hello,

Does anyone have any experience setting up a 4 band EQ using filters? I’ve written code that uses 1 filter at a time, but I’d like to get 4 filters, each processing a specific freq. range, at the same time.

For each band I’d like to select the target freq. within the range and adjust the dB (-15 to 15), for that freq.

My questions:

  1. I’m assuming I would need to identify the freq. of the incoming audio signal, then apply the proper filter?
  2. With each filter having its own gain, how would I combine the 4 results as the output signal?
  3. Is this possible with one seed/one channel, or would I need 2 i.e. each channel 1 of the 4 filters and combining all 4 outputs?

Any help would be appreciated.
Thanks!

I saw this when I was digging through some of the ARM DSP stuff - maybe it will be of use to you, at least conceptually?

https://www.keil.com/pack/doc/CMSIS/DSP/html/group__GEQ5Band.html

I initially thought I’d have to do the processing in parallel, but seeing a serial option is interesting, thank you for sharing!

I suggest studying the DaisyDuino FilterBank example for Daisy Patch.

  1. No
  2. You don’t combine. Each filter gets processed in the audio callback
  3. No, you don’t need more than one Daisy.

I have a seed and pod and did not think to look at examples for the other hardware, will do, thanks!

1 Like

For anyone interested, first pass of my code for the Pod hardware…

/*  
4 Band EQ for the Daisy Pod, similar to an EQ found in audio outboard gear
params to adjust:
    freq
    gain
    Q TODO (will need a 3rd knob)
*/

#include "daisysp.h"        // include the daisy SP lib
#include "daisy_pod.h"      // include the POD hardware lib

using namespace daisysp;    // namespace for SP lib
using namespace daisy;      // namespace for daisy

static DaisyPod pod;        // var for the pod hardware

float minFreq[4];           // array of min freq for each band
float maxFreq[4];           // array of max freq for each band
float initFreq[4];          // array of init freq for each band
int band;                   // the current freq band

// filter structure
struct Filter                                   
{
    Svf   filt;                                 // use the Svf filter lib
    float amp;                                  // var for amp (gain) value

    void Init(float samplerate, float freq)     // init the Svf filter lib
    {
        filt.Init(samplerate);                  // sample rate
        filt.SetRes(0.85);                      // set resonance - tweak this to reduce/remove high-pitch wine
        filt.SetDrive(.002);                    // set drive
        filt.SetFreq(freq);                     // set freq
        amp = .5f;                              // set amp (gain) value
    }

    float Process(float in)                     // func to process the audio signal
    {
        filt.Process(in);                       // process the audio signal
        return filt.Peak() * amp;               // return the peak filter signal output * amp (gain)
    }

    void SetFreq(float targetFreq)              // func to set the target frequency
    {
        filt.SetFreq(targetFreq);               // set freq
    }
};
Filter filters[4];                              // create an array to store 4 filters, 1 for each of the 4 bands

// controls func
void UpdateControls()                           // function to update the controls
{
    // get inputs 
    float k1, k2;                               // vars for the 2 knobs on the Pod

    pod.ProcessAnalogControls();                // process analog controls (pots, switches, etc.)
    pod.ProcessDigitalControls();               // process digital controls (encoder)

    // encoder
    band += pod.encoder.Increment();            // cycle thru the bands, using the encoder
    band = (band % 4 + 4) % 4;                  // capped at the total number of EQ bands

    // use k1 to adjust freq., use K2 to adjust gain, TODO use K3 (custom hardware) to support Q or Bell
    k1 = pod.knob1.Process();                   // get the k1 value - freq
    k2 = pod.knob2.Process();                   // get the k2 value - gain

    // adjust the current band
    filters[band].SetFreq(((maxFreq[band] - minFreq[band])* k1) + minFreq[band]);   // select the target freq, within this band's range
                                                                                    // (maxFreq - minFreg) * K1 (0 to 1) + min
                                                                                    // eg. 450-30=420, 420 * (0 to 1) = 0 to 420, + 30 = 30 to 450
    filters[band].amp = k2;                                                         // update the current band filter's amp value

    // based on the current band, update led1
    switch(band)                                
    {
        case 0:
            pod.led1.Set(0,0,0.25); break;      // LOW light blue to...
        case 1:
            pod.led1.Set(0,0,0.50); break;      // LOW MID 
        case 2:
            pod.led1.Set(0,0,0.75); break;      // HIGH MID 
        case 3:
            pod.led1.Set(0,0,1); break;         // HIGH ...bright blue
    }
    pod.led2.Set(0,k1,0);                       // update led2 based on the K1 knob
    pod.UpdateLeds();                           // update the leds         
}

// audio call back
static void AudioCallback(AudioHandle::InputBuffer  in, AudioHandle::OutputBuffer out, size_t size)
{
    UpdateControls();                                           // check the controls and adjust parameters 
                                                                // NOTE - placing updateControls() here provides better input response for the encoder/knobs, but impacts performance
                                                                // to improve perf, comment this line out and uncomment the same line in the main while loop

    for(size_t i = 0; i < size; i++)                            // repeat for the sample
    {
        float sig = 0.f;                                        // set sig to 0.0
        for(int j = 0; j < 4; j++)                              // repeat for each of the 4 filters
        {
            sig += filters[j].Process(in[0][i]);                // process the signal with this filter
        }
        sig *= .06;                                             // mult the signal 
        out[0][i] = out[1][i] = out[2][i] = out[3][i] = sig;    // output the results of all 4 filters
    }
}

// init the freq
void InitFreqs()
{
    // set the min, initial and max freq for each band
    // Low 30hz to 450hz
    minFreq[0] = 30;
    initFreq[0] = 200;
    maxFreq[0] = 450;
    // Low Mid 200hz to 2.5khz
    minFreq[1] = 200;
    initFreq[1] = 1000;
    maxFreq[1] = 2500;
    // High Mid 600hz to 7.5khz
    minFreq[2] = 600;
    initFreq[2] = 3500;
    maxFreq[2] = 7500;
    // High 1.5khz to 16khz
    minFreq[3] = 1500;
    initFreq[3] = 7500;
    maxFreq[3] = 16000; // MAX sample rate / 3 = 48k/3 = 16khz
}

// init the filters
void InitFilters(float samplerate)
{
    for(int i = 0; i < 4 ; i++)                     // repeat for the 4 filter bands
    {
        filters[i].Init(samplerate, initFreq[i]);   // init each filter, passing the sample rate and init freq for this filter
    }
}

// main 
int main(void)  // main function, init everything here
{
    float samplerate;                       // var for sample rate
    pod.Init();                             // init the hardware
    samplerate = pod.AudioSampleRate();     // set the sample rate (TODO confirm 48khz)     

    InitFreqs();                            // init the freq array
    InitFilters(samplerate);                // init the filter array
    band = 0;                               // set band to 0

    pod.StartAdc();                         // start the ADC
    pod.StartAudio(AudioCallback);          // start the audio callback
    while(1) {                              // repeat forever
        //UpdateControls();                 // check the controls and adjust parameters
                                            // NOTE if need to improve performance, uncomment this line and comment the same line in the audio callback function                                    
    }                             
}

2 Likes

Does this work? The Pod is 2 channel in and out, no?

Hi,

Assuming you are referring to this…

for(size_t i = 0; i < size; i++)                            // repeat for the sample
{
    float sig = 0.f;                                        // set sig to 0.0
    for(int j = 0; j < 4; j++)                              // repeat for each of the 4 filters
    {
        sig += filters[j].Process(in[0][i]);                // process the signal with this filter
    }
    sig *= .06;                                             // mult the signal 
    out[0][i] = out[1][i] = out[2][i] = out[3][i] = sig;    // output the results of all 4 filters
}

I do hear audio, BUT yeah, I just noticed the multiple in/out channels and the pod is just 2 channel, for example…

for(size_t i = 0; i < size; i += 2)
{
    inl = in[i];                                        // get audio left
    inr = in[i + 1];                                    // get audio right

    out[i] = outl;                                      // left out
    out[i + 1] = outr;                                  // right out
}

}

Thanks for catching this, I’m in the process of tweaking it :slight_smile:

I modified the callback function…

void AudioCallback(AudioHandle::InterleavingInputBuffer in, AudioHandle::InterleavingOutputBuffer out, size_t size) // used by multi-effect (pod)
{
    UpdateControls();                                           // check the controls and adjust parameters 
                                                                // NOTE - placing updateControls() here provides better input response for the encoder/knobs, but impacts performance
                                                                // to improve perf, comment this line out and uncomment the same line in the main while loop

    for(size_t i = 0; i < size; i += 2)                         // repeat for each sample in the buffer - OLD for(size_t i = 0; i < size; i++) 
    {
        float sig = 0.f;                                        // reset the signal to 0
        for(int j = 0; j < 4; j++)                              // repeat for each of the 4 filters
        {
            sig += filters[j].Process(in[i]);                   // process the left channel with all 4 filters - OLD sig += filters[j].Process(in[0][i]);
        }
        sig *= .06;                                             // mult the signal
        out[i] = out[i+1] = sig;                                // output the results - OLD out[0][i] = out[1][i] = out[2][i] = out[3][i] = sig;
    }
}

To closer match what I’m seeing in other pod hardware examples. Gonna dig into the differences between the patch and pod, for example the different audio callback commands used by each.

Seems to me that this should work, but I haven’t tried it. My Daisy is occupied elsewhere.

    for(size_t i = 0; i < size; i += 2)                         
    {
        float sig = in[i];
        for(int j = 0; j < 4; j++)                              
        {
            sig = filters[j].Process(sig); 
        }
        
        out[i] = out[i+1] = sig;
    }

No worries, would this line…

 sig = filters[j].Process(sig); 

Need to change to this…

 sig += filters[j].Process(sig); 

As I’m assuming in the first line we’d get the output of just the 4th filter, where the second gets the sum of all 4 filters?

Thanks again

I’m pretty sure what I wrote is correct. Try it.

Back to your code, the .06 looked arbitrary, until I re-examined the Patch example. Since that code adds up the results of all 16 filters, multiplying by .06 approximates division by 16, which should get the amplitude right.

So, for your 4 filter code, you would use 0.25.

I really ought to hack up some code to run on my Daisy, which is installed in a Veno-Echo.

EDIT: ignore everything I wrote. I completely misunderstood how the Svf works, and missed that Process is using the Svf Peak output.

I like that you publish your code and your progress! :slight_smile:

How I use the SVF filter:

Define the object:
Svf fxFilterL, fxFilterR;

Process the signal:
fxFilterL.Process(sigL);

Get the result:
sigL = fxFilterL.Band(); // or .Low() or .High()

Isn’t a 4 band EQ 4 band-pass filters?

Then you should divide each band (filter) by 4 (or multiply with .25) and add them up to get the out signal.

EDIT: Now I read @tele_player’s last comment, maybe that is more correct.

If you are looking to do the typical parametric EQ type thing (like the channel EQ in Logic for example), then you’ll want to use a combination of peaking and shelving bi-quad sections like described here, Peaking Equalizers

To do the multiple bands you cascade the individual sections in series.

There are lots of ways to compute the coefficients of the bi-quad sections for a given Q or slope, gain, and frequency, but the Robert Bristow-Johnson method is well documented. Here’s a link to info on the RBJ EQ Cookbook at Musicdsp, RBJ Audio-EQ-Cookbook — Musicdsp.org documentation

If you have a Mac or an iOS device, this is basically what I do with my LRC5 and LRC7 EQ plugins and you could play around with them and see if they behave the way you want.

2 Likes