Strange behaviour of the biquad function

Technical questions regarding the XTC tools and programming with XMOS.
Post Reply
Gemüsesaft
New User
Posts: 3
Joined: Tue Aug 17, 2021 1:20 pm

Strange behaviour of the biquad function

Post by Gemüsesaft »

Hi,

I'm trying to implement a biquad filter in my I2S stream on my MC Audio evaluation board, but when I filter the samples, e.g. with a low-pass filter with a corner frequency of 1kHz, I get an output signal as if the filter was a low-pass at 2kHz.
I created the coefficients with the filter design function in the dsp library and simulated the frequency response of these coefficients with a Python script. The coefficients are valid.
The part of my code responsible for applying the filter is the following:
#include "i2s.h"
#include "dsp.h"

#define SAMPLING_FREQUENCY 48000
#define Q_FORMAT 24

void audio_filter(streaming chanend c_dsp) {
    int32_t in_sample[CHANNEL_COUNT] = {0}; // Holds the incoming sample
    int32_t out_sample[CHANNEL_COUNT] = {0}; // Holds the outgoing sample

    double filter_fq = 1000.0/SAMPLING_FREQUENCY; // Sets the corner frequency of the filter
    double filter_q = 0.707; // Sets the Q factor of the filter

    int32_t filter_state[4] = {0};

    dsp_design_biquad_lowpass(filter_fq, filter_q, coeffs, Q_FORMAT); // calculates the biquad coefficients for the filter 

    while (1) {
        /* Exchanges samples with the i2s handler */
        for (size_t i = 0; i < CHANNEL_COUNT; i++) {
            c_dsp :> in_sample;
            c_dsp <: out_sample;
        }
        
        /* Applies the filter to the sample */
        for (size_t i = 0; i < CHANNEL_COUNT; i++) {
            out_sample = dsp_filters_biquad(in_sample, coeffs, filter_state, Q_FORMAT);
        }
    }
}


Before that, there is an i2s handler function that manages the conversion of i2s to samples and sends new samples to the filter function via the channel c_dsp and vice versa.

I also included a picture of a messured filter with a corner frequency of 1kHz and a Q factor of 0.707:

Inkedfilter_lowpass_1kHz_Q0,707_1_LI.jpg
lowpass filter at 1kZ Q 0.707, measured at 2kHz
(2.6 MiB) Not downloaded yet
Inkedfilter_lowpass_1kHz_Q0,707_1_LI.jpg
lowpass filter at 1kZ Q 0.707, measured at 2kHz
(2.6 MiB) Not downloaded yet
I work with xTimeComposer 14.4.1, the xc language and the dsp library. The code runs on an MC Audio Eval Board with XCore-200 Chip.

I don't know what I did wrong.I would be very grateful for any help!
Many thanks in advance!

Best regards
Till


User avatar
CousinItt
Respected Member
Posts: 360
Joined: Wed May 31, 2017 6:55 pm

Post by CousinItt »

The main pitfall I'm aware of is that the a coefficients are negated, for efficiency reasons. It's easy to miss. Check the lib_dsp user guide.
User avatar
CousinItt
Respected Member
Posts: 360
Joined: Wed May 31, 2017 6:55 pm

Post by CousinItt »

Dumb answer - dsp_design_biquad_lowpass() handles the signs of the a coefficients. I should have realised that.

It may be do with confusion over the meaning of normalisation in the cut-off frequency parameter spec, or it could just be a bug. I'm going to check.
User avatar
CousinItt
Respected Member
Posts: 360
Joined: Wed May 31, 2017 6:55 pm

Post by CousinItt »

OK, the coefficients look right to me, comparing with the output of this GNU Octave script:

Code: Select all

# butterworth design test
pkg load signal
output_precision(8)

fs = 48000; nyquist = fs / 2; fc = 1000;

[b, a] = butter(2, fc / nyquist)

b
a
Are you exchanging the data with the i2s task in the right way? I.e. should you be interleaving the exchanges or doing all inputs then all outputs (or vice versa)?

Also you need to use independent state data for each channel.
RitchRock
XCore Addict
Posts: 186
Joined: Tue Jan 17, 2017 9:25 pm

Post by RitchRock »

Check the for loop where you are applying the biquad. You aren't looping over all the samples at the moment. Also, you should have state data for each channel. Finally, did you choose the coefficient array to enforce 64 bit alignment?

Take another look at the example from the app notes....

Code: Select all


// For 8 channels:
//Note: array size of 6 is chosen to enforce 64 bit alignment for ldd and std
int32_t [[aligned(8)]]coeff[8][6]; // Coefficients for each filter

int32_t [[aligned(8)]]state[8][4] = {{0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0}};

for (int i=0 ; i<8 ; i++)
    {
        out_samples[i] = dsp_filters_biquad(in_samples[i], coeff[i], state[i], Q_FORMAT);
    }
    
Post Reply