You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OpenAudio_ArduinoLibrary/AudioFilterFIRGeneral_F32.cpp

260 lines
11 KiB

/* AudioFilterFIRGeneralr_F32.cpp
*
* Bob Larkin, W7PUA 20 May 2020 (c)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/* A math note - The design of the FIR filter from the frequency response requires
* taking a discrete Fourier transform. The calculation of the achieved
* frequency response requires another Fourier transform. Good news is
* that this is arithmetically simple, because of
* working with real (not complex) inputs and requiring the FIR be linear
* phase. One of a number of references is C.S. Burris and T. W. Parks,
* "Digital Filter Design." That book also has excellent sections on
* Chebychev error (Parks-McClellan-Rabiner) FIR filters and IIR Filters.
*/
#include "AudioFilterFIRGeneral_F32.h"
void AudioFilterFIRGeneral_F32::update(void) {
audio_block_f32_t *blockIn, *blockOut;
#if TEST_TIME_FIRG
if (iitt++ >1000000) iitt = -10;
uint32_t t1, t2;
t1 = tElapse;
#endif
blockIn = AudioStream_F32::receiveReadOnly_f32();
if (!blockIn) return;
// If there's no coefficient table, give up.
if (cf32f == NULL) {
AudioStream_F32::release(blockIn);
return;
}
blockOut = AudioStream_F32::allocate_f32(); // get a block for the FIR output
if (blockOut) {
// The FIR update
arm_fir_f32(&fir_inst, blockIn->data, blockOut->data, blockIn->length);
AudioStream_F32::transmit(blockOut); // send the FIR output
AudioStream_F32::release(blockOut);
}
AudioStream_F32::release(blockIn);
#if TEST_TIME_FIRG
t2 = tElapse;
if(iitt++ < 0) {Serial.print("At FIRGeneral end, microseconds = "); Serial.println (t2 - t1); // }
Serial.print("numtaps = "); Serial.println (fir_inst.numTaps);
}
t1 = tElapse;
#endif
}
/* FIRGeneralNew() calculates the generalFIR filter coefficients. Works from:
* adb Pointer to nFIR/2 array adb[] of levels, in dB
* nFIR The number of FIR coefficients (taps) used
* cf32f Pointer to an array of float to hold nFIR FIR coefficients
* kdb A parameter that trades off sidelobe levels for sharpness of band transition.
* kdb=30 sharp cutoff, poor sidelobes
* kdb=60 slow cutoff, low sidelobes
* pStateArray Pointer to 128+nFIR array of floats, used here briefly and then
* passed to the FIR routine as working storage
*
* The arrays, adb[], cf32f[] and pStateArray[] are supplied by the calling .INO
*
* Returns: 0 if successful, or an error code if not.
* Errors: 1 = NU
* 2 = sidelobe level out of range, must be > 0
* 3 = nFIR out of range
*
* Note - This function runs at setup time, so slowness is not an issue
*/
uint16_t AudioFilterFIRGeneral_F32::FIRGeneralNew(
float32_t *adb, uint16_t _nFIR, float32_t *_cf32f, float32_t kdb,
float32_t *pStateArray) {
uint16_t i, k, n;
uint16_t nHalfFIR;
float32_t beta, kbes;
float32_t num, xn2, scaleXn2, WindowWt;
float32_t M; // Burris and Parks, (2.16)
mathDSP_F32 mathEqualizer; // For Bessel function
bool even;
cf32f = _cf32f; // Make pivate copies
nFIR = _nFIR;
// Check range of nFIR
if (nFIR<4)
return ERR_FIRGEN_NFIR;
M = 0.5f*(float32_t)(nFIR - 1);
// The number of FIR coefficients is even or odd
if (2*(nFIR/2) == nFIR) {
even = true;
nHalfFIR = nFIR/2;
}
else {
even = false;
nHalfFIR = (nFIR - 1)/2;
}
for (i=0; i<nFIR; i++) // To be sure, zero the coefficients
cf32f[i] = 0.0f;
for(i=0; i<(nFIR+AUDIO_BLOCK_SAMPLES); i++) // And the storage
pStateArray[i] = 0.0f;
// Convert dB to "Voltage" ratios, frequencies to fractions of sampling freq
// Borrow pStateArray, as it has not yet gone to ARM math
for(i=0; i<=nHalfFIR; i++)
pStateArray[i] = powf(10.0, 0.05f*adb[i]);
/* Find FIR coefficients, the Fourier transform of the frequency
* response. This general frequency description has half as many
* frequency dB points as FIR coefficients. If nFIR is odd,
* the number of frequency points is nHalfFIR = (nFIR - 1)/2.
*
* Numbering example for nFIR==21:
* Odd nFIR: Subscript 0 to 9 is 10 taps; 11 to 20 is 10 taps; 10+1+10=21 taps
*
* Numbering example for nFIR==20:
* Even nFIR: Subscript 0 to 9 is 10 taps; 10 to 19 is 10 taps; 10+10=20 taps
*/
float32_t oneOnNFIR = 1.0f / (float32_t)nFIR;
if(even) {
for(n=0; n<nHalfFIR; n++) { // Over half of even nFIR FIR coeffs
cf32f[n] = pStateArray[0]; // DC term
for(k=1; k<nHalfFIR; k++) { // Over all frequencies, half of nFIR
num = (M - (float32_t)n) * (float32_t)k;
cf32f[n] += 2.0f*pStateArray[k]*cosf(MF_TWOPI*num*oneOnNFIR);
}
cf32f[n] *= oneOnNFIR; // Divide by nFIR
}
}
else { // nFIR is odd
for(n=0; n<=nHalfFIR; n++) { // Over half of FIR coeffs, nFIR odd
cf32f[n] = pStateArray[0];
for(k=1; k<=nHalfFIR; k++) {
num = (float32_t)((nHalfFIR - n)*k);
cf32f[n] += 2.0f*pStateArray[k]*cosf(MF_TWOPI*num*oneOnNFIR);
}
cf32f[n] *= oneOnNFIR;
}
}
/* At this point, the cf32f[] coefficients are simply truncated, creating
* high sidelobe responses. To reduce the sidelobes, a windowing function is applied.
* This has the side affect of increasing the rate of cutoff for sharp frequency transition.
* The only windowing function available here is that of James Kaiser. This has a number
* of desirable features. The sidelobes drop off as the frequency away from a transition.
* Also, the tradeoff of sidelobe level versus cutoff rate is variable.
* Here we specify it in terms of kdb, the highest sidelobe, in dB, next to a sharp cutoff. For
* calculating the windowing vector, we need a parameter beta, found as follows:
*/
if (kdb<0.0f) return ERR_FIRGEN_SIDELOBES;
if (kdb < 20.0f)
beta = 0.0;
else
beta = -2.17+0.17153*kdb-0.0002841*kdb*kdb; // Within a dB or so
// Note: i0f is the fp zero'th order modified Bessel function (see mathDSP_F32.h)
kbes = 1.0f / mathEqualizer.i0f(beta); // An additional derived parameter used in loop
scaleXn2 = 4.0f / ( ((float32_t)nFIR - 1.0f)*((float32_t)nFIR - 1.0f) ); // Needed for even & odd
if (even) {
// nHalfFIR = nFIR/2; for nFIR even
for (n=0; n<nHalfFIR; n++) { // For 20 Taps, this is 0 to 9
xn2 = 0.5f+(float32_t)n;
xn2 = scaleXn2*xn2*xn2;
WindowWt=kbes*(mathEqualizer.i0f(beta*sqrt(1.0-xn2)));
if(kdb > 0.1f) // kdb==0.0 means no window
cf32f[nHalfFIR - n - 1] *= WindowWt; // Apply window, reverse subscripts
cf32f[nHalfFIR + n] = cf32f[nHalfFIR - n - 1]; // and create the upper half
}
}
else { // nFIR is odd, nHalfFIR = (nFIR - 1)/2
for (n=0; n<=nHalfFIR; n++) { // For 21 Taps, this is 0 to 10, including center
xn2 = (int16_t)(0.5f+(float32_t)n);
xn2 = scaleXn2*xn2*xn2;
WindowWt=kbes*(mathEqualizer.i0f(beta*sqrt(1.0-xn2)));
if(kdb > 0.1f)
cf32f[nHalfFIR - n] *= WindowWt;
// 21 taps, for n=0, nHalfFIR-n = 10, for n=1 nHalfFIR-n=9, for n=nHalfFIR, nHalfFIR-n=0
cf32f[nHalfFIR + n] = cf32f[nHalfFIR - n]; // and create upper half (rewrite center is OK)
}
}
// And finally, fill in the members of fir_inst given in update() to the ARM FIR routine.
AudioNoInterrupts();
arm_fir_init_f32(&fir_inst, nFIR, (float32_t *)cf32f, &pStateArray[0], (uint32_t)block_size);
AudioInterrupts();
return 0;
}
// FIRGeneralLoad() allows an array of nFIR FIR coefficients to be loaded. They come from an .INO
// supplied array. Also, pStateArray[] is .INO supplied and must be (block_size + nFIR) in size.
uint16_t AudioFilterFIRGeneral_F32::LoadCoeffs(uint16_t _nFIR, float32_t *_cf32f, float32_t *pStateArray) {
nFIR = _nFIR;
cf32f = _cf32f;
if (nFIR<4) // Check range of nFIR
return ERR_FIRGEN_NFIR;
for(int i=0; i<(nFIR+AUDIO_BLOCK_SAMPLES); i++) // Zero, to be sure
pStateArray[i] = 0.0f;
AudioNoInterrupts();
arm_fir_init_f32(&fir_inst, nFIR, &cf32f[0], &pStateArray[0], (uint32_t)block_size);
AudioInterrupts();
return 0;
}
/* Calculate frequency response in dB. Leave nFreq point result in array rdb[] supplied
* by the calling .INO See B&P p27 (Type 1 and 2). Be aware that if nFIR*nFreq is big,
* like 100,000 or more, this will take a while to calcuulate all the cosf(). Normally,
* this is not an issue as this is an infrequent calculation.
* This function assumes that the phase of the FIR is linear with frequency, i.e.,
* the coefficients are symmetrical about the middle. Otherwise, doubling of values,
* as is done here, is not valid.
*/
void AudioFilterFIRGeneral_F32::getResponse(uint16_t nFreq, float32_t *rdb) {
uint16_t i, n;
float32_t bt;
float32_t piOnNfreq;
uint16_t nHalfFIR;
float32_t M;
piOnNfreq = MF_PI / (float32_t)nFreq;
// The number of FIR coefficients, even or odd?
if (2*(nFIR/2) == nFIR) { // it is even
nHalfFIR = nFIR/2;
M = 0.5f*(float32_t)(nFIR - 1);
for (i=0; i<nFreq; i++) {
bt = 0.0;
for (n=0; n<nHalfFIR; n++) // Add in terms twice, as they are symmetric
bt += 2.0f*cf32f[n]*cosf(piOnNfreq*((M-(float32_t)n)*(float32_t)i));
rdb[i] = 20.0f*log10f(fabsf(bt)); // Convert to dB
}
}
else { // it is odd
nHalfFIR = (nFIR - 1)/2;
for (i=0; i<nFreq; i++) {
bt = cf32f[nHalfFIR]; // Center coefficient
for (n=0; n<nHalfFIR; n++) // Add in the others twice, as they are symmetric
bt += 2.0f*cf32f[n]*cosf(piOnNfreq*(float32_t)((nHalfFIR-n)*i));
rdb[i] = 20.0f*log10f(fabsf(bt));
}
}
}