How to implement tunable EQ?

Started by audioartillery, October 25, 2018, 10:32:45 AM

Previous topic - Next topic

audioartillery

I've got this fuzz effect I made and I've got a lowpass filter on it to deal with antialiasing, but I want to add some general EQ that can be tuned with a knob or two.  How should I go about this?  I typically just use a filter design program to output a static IIR filter and adapt it to my code, but I want something dynamic I can tune on the fly.

Any recommendations or reading I should look into?  Thanks.


Digital Larry

If you want to calculate all coefficients of a BiQuad every time you turn a knob AND have adequate CPU resources and function libraries to do that, AND you already have a BiQuad structure, that might be easiest.  I vastly recommend the State Variable Filter structure instead though as it is really made for this.  Pay close attention to details because these structures differ in the frequency range over which they are stable and I don't pay close enough attention to details to tell you which is which.

https://www.earlevel.com/main/2003/03/02/the-digital-state-variable-filter/

I'm way off into FV-1 DSP land so I don't deal with C code, but SVFs are the only kind I use as the FV-1's really optimized for those.
Digital Larry
Want to quickly design your own effects patches for the Spin FV-1 DSP chip?
https://github.com/HolyCityAudio/SpinCAD-Designer

noisette

#3
One of the simplest and good working ways to implement this is in the Soundcraft 200B (check google), it uses Wien-Bridge filters.

Edit- it is this:

"Those who believe in telekinetics, raise my hand."
― Kurt Vonnegut

Blackaddr

#4
Assuming this is for the Teensy, considering stick to filter structures that are offered by the Keil DSP library. Without the DSP acceleration, the performance would be quite poor.

Chip Audette I think did this exact thing (runtime filter coefficients) when he created his floating point version of the Teensy Audio Library.

Take a look at his AudioFilterBiquad_F32 class.
https://github.com/chipaudette/OpenAudio_ArduinoLibrary/blob/81492cc5aca290d95cd6b681729302148cb7e109/AudioFilterBiquad_F32.h

He already wrote utility functions for runtime calculation of biquad filter coefficients which he based on the Audio EQ Cookbook. You can use his code as a starting point.

He was using float32, but if possible maybe you can modify his routines to use the high-precision Q31 biquads which provide 64-bit precision in the filter. It will be much faster than using the floating point filters. I use the High Precision Q31 DSP filters in my AudioEffectAnalogDelay class. The regular Q31 filters did not maintain sufficient internal precision to avoid audio distortion, it had to be the ones with 64-bit internal precision. And yes, there is a performance penalty for using HP Q31 but it's still way faster than the DSP float filters.

One final note on Chip's code. While he uses the DSP functions for the biquads themselves, he uses regular sin() and cos() in calculating the coefficients which is slow. Keil DSP also provides accelerated sine/cosine functions which are way faster than the regular software based calls.

Use arm_sin_*() and arm_cos_*().

I was planning on using this myself to create a Tone Stack block for my BALibrary but won't get to it for a while.
Blackaddr Audio
Digital Modelling Enthusiast
www.blackaddr.com

audioartillery

Quote from: Blackaddr on October 26, 2018, 07:02:42 PM
Assuming this is for the Teensy, considering stick to filter structures that are offered by the Keil DSP library.

I had no idea this existed, this is incredibly helpful, thanks!  Just took a quick look and the ARM filter functions provide everything I could ever want.

In the effect I'm working on right now I'm filtering an upsampled version of the audio block and ARM provides a decimating FIR filter.  How lucky is that?  Though I already have that part solved.

Quote from: Blackaddr on October 26, 2018, 07:02:42 PM
He was using float32, but if possible maybe you can modify his routines to use the high-precision Q31 biquads which provide 64-bit precision in the filter. It will be much faster than using the floating point filters.

So far I've found the float performance tolerable.  I haven't really had to re-code anything in fixed point yet, though I've got a reverb effect that is right on the edge and I'll have to tweak at some point.  Is floating point really that slow?  I assumed it would be just based on my general embedded experience but I read somewhere that the M4F can do one FPU instruction per clock as long as your compiler keeps the pipeline fed.  Obviously there is other overhead like register stacking.

It's nice to go from mathematical formula to effect code without worrying about precision too much for prototyping.

Quote from: Blackaddr on October 26, 2018, 07:02:42 PMUse arm_sin_*() and arm_cos_*().

Another thing I didn't know existed!  I just implemented a lookup table with interpolation to deal with the fact that the regular sin() is far too slow.

Quote from: Blackaddr on October 26, 2018, 07:02:42 PM
I was planning on using this myself to create a Tone Stack block for my BALibrary but won't get to it for a while.

That might be useful but I suspect it will be hard to make it generic enough.

Thanks a ton for the tips, I'm making a lot of progress based on your board and have recommended it to a few people here and there.

Blackaddr

#6
Quote from: audioartillery on October 26, 2018, 11:56:16 PM
So far I've found the float performance tolerable.  I haven't really had to re-code anything in fixed point yet, though I've got a reverb effect that is right on the edge and I'll have to tweak at some point.  Is floating point really that slow?  I assumed it would be just based on my general embedded experience but I read somewhere that the M4F can do one FPU instruction per clock as long as your compiler keeps the pipeline fed.  Obviously there is other overhead like register stacking.

You are correct that the hardware FPU in the Teensy makes a world of difference and makes floating point audio processing possible. Without it (software floating point) you wouldn't be able to do any real audio processing at all.

You're smart to focus on writing correct software first, then optimize later once it's working. When I was doing my analog audio delay effect, the 8th order filter required took a lot of resources. Here was may basic process:

1) Write it using regular float. I think the effect was consuming about 90% of the Teensy, but it worked.
2) Add in the float <-> int DSP conversion acceleration. (arm_q15_to_float(), arm_float_to_q15). I think it dropped to 75% or so.
3) Replace the filtering code with a cascade of four DSP accelerated biquads (arm_biquad_cascade_df2T_f32()). It dropped to about 40% or so.
4) Replace the DSP accelerated f32 with regular Q31 and remove the floating point conversions. It dropped to about 10%, but there was audio distortion.
5) Switch to High Precision (64-bit intermediates) Q31. Distortion gone and it settled around 14% I think.

Once I got familiar with the Keil DSP library, and it's fixed point versions, I often start with 2) above, then jump to 5) once it's working since I want to have as much of the processor available as possible. Study all the functions in the Keil library so you can take advantage of of DSP acceleration whenever possible.

Keep in mind the audio samples are effectively already in Q15 format.  :icon_cool:
Blackaddr Audio
Digital Modelling Enthusiast
www.blackaddr.com

audioartillery

Got the biquad float IIR routine working this weekend (using the float version).  I only have 3 stages in there so far so no noticeable performance change even at the 8x oversampling I'm doing for this effect.  I basically just copied Chip's math for calculating the coefficients.  Very straight forward.

Anyone know a good way to visualize the composition of the frequency responses from several biquad filters?  I like what I'm hearing but I kind of want to tune it visually.