Forum Rule: Always post complete source code & details to reproduce any issue!

1. ## Inner working of filter_biquad

I am trying to figure out how the IIR filters are implemented in the audioLibrary: filter_biquad.cpp. But I really struggle at that code. Perhaps someone could give me some hints. As far as I understand the CPU can only do 32x16 multiplies-adds, so for each 32x32 product there should be two MAC instructions. I would expect these two MACs close together and with the same operands, the onee as "Bottom" the other as "Top". But the code really works in another way. What value should I specify for a1 (scaling) if I want a1=-0.5 or a1=1.0. I think the scale factor is 1073741824 but i am not sure. I would like to run the code on the filter:
B0= 1.0
B1= 0
B2= 0
A1=-1.0
A2= 0.5
I have coded IIR filters using an FPGA and Java, so now it comes to ARM.

I just want to learn from the code of others!  Reply With Quote

2. For the multiply & accumulate instructions, read pages 107-108 in this document

https://static.docs.arm.com/ddi0403/...armv7m_arm.pdf  Reply With Quote

3. I think I understood the MAC Instructions. The Problem is more how the MACinstructions together yield the IIR filter.
One detailed question: You sometimes and sum with 0x3FFF. I dont understand why.

Code:
```b0 = *state++;
b1 = *state++;
b2 = *state++;
a1 = *state++;
a2 = *state++;
bprev = *state++;
aprev = *state++;
sum = *state & 0x3FFF;
data = end - AUDIO_BLOCK_SAMPLES/2;
do {
in2 = *data;
sum = signed_multiply_accumulate_32x16b(sum, b0, in2);      // MAC1
sum = signed_multiply_accumulate_32x16t(sum, b1, bprev);   // MAC2
sum = signed_multiply_accumulate_32x16b(sum, b2, bprev);   // MAC3
sum = signed_multiply_accumulate_32x16t(sum, a1, aprev);   // MAC4
sum = signed_multiply_accumulate_32x16b(sum, a2, aprev);   // MAC5
out2 = signed_saturate_rshift(sum, 16, 14);
sum &= 0x3FFF;
sum = signed_multiply_accumulate_32x16t(sum, b0, in2);     // MAC6
sum = signed_multiply_accumulate_32x16b(sum, b1, in2);     // MAC7
sum = signed_multiply_accumulate_32x16t(sum, b2, bprev);  // MAC8
sum = signed_multiply_accumulate_32x16b(sum, a1, out2);   // MAC9
sum = signed_multiply_accumulate_32x16t(sum, a2, aprev);  // MAC10
bprev = in2;
aprev = pack_16b_16b(
signed_saturate_rshift(sum, 16, 14), out2);
sum &= 0x3FFF;
bprev = in2;
*data++ = aprev;
} while (data < end);```
I think MAC1 and MAC6 compute b0*in2.
But b1 is once multiplied by bprev (MAC2) and the other time by in2 (MAC7) . I don't understand that.
Multiplication b2*bprev seems ok by MAC3 and MAC8.
But a1 is once multiplied by aprev (MAC4) and the other time by out2 (MAC9) . I don't understand that.
Multiplication a2*aprev seems ok by MAC5 and MAC10.

I would be thankful for some guiding hints.  Reply With Quote

4. I think I have figuerd it out: The variables in2,out2,aprev,brev hold 2 int16 each and the ten MACs are used to compute two steps of the IIR filter.
Quite tricky, but probably fast.  Reply With Quote

5. Yup, this is how much of the audio library works, where pairs of 16 bit numbers are packed into 32 bit words. ARM designed their DSP extension instructions to work separately on either half of 32 bit words, so the samples can be used directly from the 32 bit registers.  Reply With Quote

6. Originally Posted by ossi One detailed question: You sometimes and sum with 0x3FFF. I dont understand why.
That part was meant to implement a "first order noise shaping" recommendation I found in this article.

Looking at it again now, I'm not 100% sure the logical and really is correct. This probably creates a small DC bias, since it can't be negative. Might be totally wrong for 1/2 of the cases? Also not sure if the polarity is really right either.

I've added a comment to the source code.

https://github.com/PaulStoffregen/Au...67d16d10566308  Reply With Quote

7. Hi Paul,

The noise-shaping is actually correct, because the signed shift does truncation toward -inf. The quant error is guaranteed to be a positive fraction in [0,1]. If you round instead of truncate (to prevent the 0.5 ULP DC offset) then the error feedback becomes more complicated (and slow) to implement. Its not really worthwhile to fix.. dithering the output would make more sense.  Reply With Quote

8. @Paul: Thank you for your explanation. Have you tested (by simulation) that noise shaping with biquads is really helpful? Perhaps I will try such an simulation.  Reply With Quote

9. Originally Posted by ossi Have you tested (by simulation) that noise shaping with biquads is really helpful?
No, I did basically no testing at all on the noise shaping, other than a couple very unscientific listening tests to check it wasn't harming the output.  Reply With Quote  Reply With Quote