Help with filter design

Status
Not open for further replies.

houtson

Well-known member
I'm looking to emulate a tone control in a part of an audio effect. Trying to achieve specific low frequency and high frequency attenuation.

The spec's I'm trying to achieve are 6db/octave roll-off with a corner frequencies of 280 Hz at the low end and 4000Hz at the high end.

I'm using the biquad with high and low shelves at the moment as an approximation, it doesn't sound quite right and I also note the comment on the design tool of trouble with corner frequencies below 400Hz.

I'm familiar with the Audio Library but not filter design. I know you can pass them specific coefficients but not sure where to start to achieve the above spec. especially with the low corner frequency.

I'm assuming some sort of high pass at 280Hz in serial with a low pass at 4000Hz.

The question is : is this best approach, which objects would I use and how would I calculate the coefficients, any help appreciated.

Cheers, Paul
 
Representing audio filters as cascaded biquads is very common. Some google searching will provide lots of examples, and even some online web tools to convert frequency specs into the coefficients for cascaded biquads. There are biquad objects in the audio library itself, but you can also do the cascaded biquads as a single function call using the ARM dsp functions in the Keil CMSIS library.

Be careful that different tools might use different polarities (+ or -) so pay attention to the equation from the coefficient generator and the audio object or DSP function in order to make any trivial adjustments.

https://www.keil.com/pack/doc/CMSIS/DSP/html/group__groupFilters.html
 
Last edited:
also, it is useful to study exactly implementation (direct I or direct II, use of b0 etc)
In other words, compare the CMSIS implementation with the routines (e.g. Matlab) used to create biquads
 
Hi, thanks for the pointers

There are biquad objects in the audio library itself[/URL]
The issue with the library one is the noted issue with low corner freq (<400Hz).

I'll have a look at the CMSIS-DSP, I'm assuming I'd need to wrap that in a new object and convert the block to floats

Cheers, Paul
 
Last edited:
There are float as well as integer versions of most CMSIS functions. There are also DSP functions for converting to/from float. If you've got lots of CPU to spare, that's a good approach. If you can't afford to do it all in float, there are integer functions that support up to 64-bit internal accumulators and 32-bit coefficients but read the instructions carefully to avoid input overflow. Usually it limits to you 25% max amplitude to avoid clipping.
 
Quick bit of feedback to say I got that working - thanks for the pointers @Blackaddr and @WMXZ.

I'm a complete noob at filter design so I'll capture a few notes here for anyone else coming behind me.

I used the filter calculator at https://www.earlevel.com/main/2013/10/13/biquad-calculator-v2/ to produce the coefficients.

The calculator uses the formula : y[n] = a0 * x[n] + a1 * x[n-1] + a2 * x[n-2] – b1 * y[n-1] – b2 * y[n-2]

The CMSIS implementation uses the formula : y[n] = b0 * x[n] + b1 * x[n-1] + b2 * x[n-2] + a1 * y[n-1] + a2 * y[n-2]

So you need to change the output of the calc as:
b0 = a0
b1 = a1
b2 = a2
a0 = -1 * b0
a1 = -1 * b1

I created a custom object and converted the block to floats dividing through by 32768.0f (I'm sure their is a better way of doing this) before then calling the filter function - seems to work well. Plan is to incorporate into another custom object along with other stuff.

A snippet of the code...
Code:
void AudioFilterBiquadFloat::update(void) {
  audio_block_t *blocka;
  float32_t blockFloat[AUDIO_BLOCK_SAMPLES];

  blocka = receiveWritable(0);
  if (!blocka) {
    return;
  }
  // convert block of samples to floats
  for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) {
    blockFloat[i] = blocka->data[i] / 32768.0f;
  }
  // process a block of samples
  arm_biquad_cascade_df1_f32(&S1, blockFloat, blockFloat, AUDIO_BLOCK_SAMPLES);

  // convert block back to ints
  for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) {
    blocka->data[i] = (float)(blockFloat[i]) * 32768.0f;
  }
  transmit(blocka);
  release(blocka);
}

Thanks again, Paul
 
> I'm looking to emulate a tone control

You should be able to measure the impulse response of the existing tone control and then use a teensy 4 to convolve audio with this impulse response to duplicate the original.
 
Dividing by a constant is likely slower than multiplying by the reciprocal -
Code:
const float recip = 1.0f/INT16_MAX ;

...
    blockFloat[i] = blocka->data[i] * recip;
I'm not sure if the compiler can do this automatically, there may be some subtlety of IEEE floating point
that has different behaviour (NaNs, infinities, or whatever) between multiply and divide prohibiting
this optimization being made implicitly.

BTW there's no need to scale the value when converting int to float, a filter using floating point
is completely scale agnostic - though sometimes you might want the value normalized anyway
(ease of debugging perhaps).
 
The tone attenuation is on the feedback leg of an old reverb effect that I don't own so difficult to isolate.

I have the a schematic of it so believe it is as spec'd above, cheers Paul.
 
Dividing by a constant is likely slower than multiplying by the reciprocal

Thanks I'll try that

Is there and arm_ function to convert a block of int16_t to float?

I should have said above I'm using a teensy 4, cheers Paul
 
Status
Not open for further replies.
Back
Top