Reimplement vDSP_deq22 for Biquad IIR Filter manually

I am moving a filter that currently uses the vDSP vdSP function for Apple (Accelerate) vDSP_deq22 for Android (where acceleration is not available). A filter block is a set of band-pass filters, each of which returns an RMS value for its respective band. Currently, the code (ObjectiveC ++ adapted from NVDSP) is as follows:

- (float) filterContiguousData: (float *)data numFrames:(UInt32)numFrames channel:(UInt32)channel { // Init float to store RMS volume float rmsVolume = 0.0f; // Provide buffer for processing float tInputBuffer[numFrames + 2]; float tOutputBuffer[numFrames + 2]; // Copy the two frames we stored into the start of the inputBuffer, filling the rest with the current buffer data memcpy(tInputBuffer, gInputKeepBuffer[channel], 2 * sizeof(float)); memcpy(tOutputBuffer, gOutputKeepBuffer[channel], 2 * sizeof(float)); memcpy(&(tInputBuffer[2]), data, numFrames * sizeof(float)); // Do the processing vDSP_deq22(tInputBuffer, 1, coefficients, tOutputBuffer, 1, numFrames); vDSP_rmsqv(tOutputBuffer, 1, &rmsVolume, numFrames); // Copy the last two data points of each array to be put at the start of the next buffer. memcpy(gInputKeepBuffer[channel], &(tInputBuffer[numFrames]), 2 * sizeof(float)); memcpy(gOutputKeepBuffer[channel], &(tOutputBuffer[numFrames]), 2 * sizeof(float)); return rmsVolume; } 

As you can see here , deq22 implements a biquad filter on a given input vector through a recursive function. This is a description of the function from the docs: vDSP_deq22

  • A =: real input vector with one precision
  • IA =: Stride for A.
  • B =: 5 inputs with one precision (filter coefficients), in increments of 1.
  • C =: real output vector with one precision.
  • IC =: Stride for C.
  • N =: number of new output elements to create.

This is what I have so far (this is in Swift, like the rest of the code base that I already ran on Android):

 // N is fixed on init to be the same size as buffer.count, below // 'input' and 'output' are initialised with (N+2) length and filled with 0s func getFilteredRMSMagnitudeFromBuffer(var buffer: [Float]) -> Float { let inputStride = 1 // hardcoded for now let outputStride = 1 input[0] = input[N] input[1] = input[N+1] output[0] = output[N] output[1] = output[N+1] // copy the current buffer into input input[2 ... N+1] = buffer[0 ..< N] // Not sure if this is neccessary, just here to duplicate NVDSP behaviour: output[2 ... N+1] = [Float](count: N, repeatedValue: 0)[0 ..< N] // Again duplicating NVDSP behaviour, can probably just start at 0: var sumOfSquares = (input[0] * input[0]) + (input[1] * input[1]) for n in (2 ... N+1) { let sumG = (0...2).reduce(Float(0)) { total, p in return total + input[(n - p) * inputStride] * coefficients[p] } let sumH = (3...4).reduce(Float(0)) { total, p in return total + output[(n - p + 2) * outputStride] * coefficients[p] } let filteredFrame = sumG - sumH output[n] = filteredFrame sumOfSquares = filteredFrame * filteredFrame } let meanSquare = sumOfSquares / Float(N + 2) // we added 2 values by hand, before the loop let rootMeanSquare = sqrt(meanSquare) return rootMeanSquare } 

The filter gives a different output signal for deq22, although it apparently has a circular circular β€œnoise” in it (with a constant input tone, this frequency measures up and down).

I have verified that the coefficient arrays are identical between each implementation. It seems that each filter "works" in that it selects the correct frequency (and only this frequency), it is just this pumping, and that the output power of the RMS is much quieter than vDSP, often in order:

  Naive | vDSP 3.24305e-06 0.000108608 1.57104e-06 5.53645e-05 1.96445e-06 4.33506e-05 2.05422e-06 2.09781e-05 1.44778e-06 1.8729e-05 4.28997e-07 2.72648e-05 

Can anyone see a problem with my logic?

Edit: here is a video of the gif result with a constant tone of 440 Hz. The various green bars are separate filter bands. The third range (shown here) is the one that is tuned to 440 Hz.

Pumping

The NVDSP version displays a constant (non-fluctuating) value, proportional to the input volume, as expected.

+5
source share
1 answer

Well, the line sumOfSquares = filteredFrame * filteredFrame should be += , not the destination. So only the last frame was calculated, explains a lot;)

Feel free to use this if you want to do some biquad filtering in Swift. MIT license as NVDSP in front of it.

+4
source

Source: https://habr.com/ru/post/1238382/


All Articles