Inner working of filter_biquad

Status
Not open for further replies.

ossi

Well-known member
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!
 
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.
 
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.
 
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.
 
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.

http://www.earlevel.com/main/2003/02/28/biquads/

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/Audio/commit/8d0cb7b413de75390b87e07e1b67d16d10566308
 
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.
 
@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.
 
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.
 
Status
Not open for further replies.
Back
Top