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:
I’m assuming I would need to identify the freq. of the incoming audio signal, then apply the proper filter?
With each filter having its own gain, how would I combine the 4 results as the output signal?
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?
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
}
}
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
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.
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.
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.