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.
188 lines
9.5 KiB
188 lines
9.5 KiB
/*


* AudioFilterFIRGeneral_F32


*


* Created: Bob Larkin W7PUA 20 May 2020


*


* Credit and thanks to PJRC, Paul Stoffregen and Teensy products for the audio


* system and library that this is built upon as well as the float32


* work of Chip Audette embodied in the OpenAudio_ArduinoLibrary. Many thanks


* for the library structures and wonderful Teensy products.


*


* There are enough different FIR filter Audio blocks to need a summary. Here goes:


*


* AudioFilterFIR (Teensy Audio Library by PJRC) handles 16bit integer data,


* and a maximum of 200 FIR coefficients, even only. (taps). For Teensy Audio.


* AudioFilterFIR_F32 (OpenAudio_ArduinoLibrary by Chip Audette) handles 32bit floating point


* data and a maximum of 200 taps. Can be used with a mix of Teensy Audio


* and 32bit OpenAudio_Arduino blocks.


* AudioFilterFIRGeneral_F32 (This block) handles 32bit floating point


* data and very large number of taps, even or odd. Can be used with a mix of Teensy Audio


* and 32bit OpenAudio_Arduino blocks. This includes the design of an


* arbitrary frequency response using Kaiser windows.


* AudioFilterFIRGeneral_I16 Same as this block, but data is 16bit integer and


* the number of taps must be even.


* AudioFilterEqualizer_F32 handles 32bit floating point data and 250 maximum taps


* even or odd. Can be used with a mix of Teensy Audio


* and 32bit OpenAudio_Arduino blocks. This includes the design of an


* arbitrary frequency response using multiple flat response steps. A Kaiser windows


* is used.


* AudioFilterEqualizer_I16 handles 16bit integer data and 250 maximum taps,


* even only.


*


* FIR filters suffer from needing considerable computation of the multiplyandadd


* sort. This limits the number of taps that can be used, but less so as time goes by.


* In particular, the Teensy 4.x, if it *did nothing but* FIR calculations, could


* use about 6000 taps inmonaural, which is a huge number. But, this also


* suggests that if the filtering task is an important function of a project,


* using, say 2000 taps is practical.


*


* FIR filters can be (and are here) implemented to have symmetrical coefficients. This


* results in constant delay at all frequencies (linear phase). For some applications this can


* be an important feature. Sometimes it is suggested that the FIR should not be


* used because of the latency it creates. Note that if constant delay is needed, the FIR


* implementation does this with minimum latency.


*


* For this block, AudioFilterFIRGeneral_F32, memory storage for the FIR


* coefficiients as well as working storage for the ARM FIR routine is provided


* by the calling .INO. This allows large FIR sizes without always tying up a


* big memory block.


*


* This block normally calculates the FIR coefficients using a Fourier transform


* of the desired amplitude response and a Kaiser window. This flexability requires


* the calling .INO to provide an array of response levels, in relative dB,


* that is half the length of the number of FIR taps. An example of this entry is a


* 300 point lowpass filter with a cutoff at 10% of the 44.1 kHz sample frequency:


* for(int i=0; i<150; i++) {


* if (i<30) dbA[i] = 0.0f;


* else dbA[i] = 140.0f;


* }


* firg1.FIRGeneralNew(&dbA[0], 300, &equalizeCoeffs[0], 50.0f, &workSpace[0]);


*


* As an alternate to inputting the response function, the FIR coefficients can be


* entered directly using LoadCoeffs(nFIR, cf32f, *pStateArray). This is a very quick


* operation as only pointers to coefficients are involved. Several filters can be


* stored in arrays and switched quickly this way. If this is done, pStateArray[]


* as initially setup should be large enough for all filters. There will be "clicks"


* associated with filter changes and these may need to be muted.


*


* How well the desired response is achieved depends on the number of FIR coefficients


* being used. As noted above, for some applications it may be desired to use


* large numbers of taps. The achieved response can be evaluated


* by the function getResponse(nPoints, pResponse) which fills an .INOsupplied array


* pResponse[nPoints] with the frequency response of the equalizer in dB. The nPoints


* are spread evenly between 0.0 and half of the sample frequency.


*


* Initialization is a 2step process. This makes it practical to change equalizer


* levels onthefly. The constructor starts up with a 4tap FIR setup for direct


* pass through. Then the setup() in the .INO can specify the equalizer.


* The newEqualizer() function has several parameters, the number of equalizer bands,


* the frequencies of the bands, and the sidelobe level. All of these can be changed


* dynamically. This function can be changed dynamically, but it may be desireable to


* mute the audio during the change to prevent clicks.


*


* Measured timing of update() for L or R 128 sample block, Teensy 3.6:


* Fixed time 13 microseconds


* Per FIR Coefficient time 2.5 microseconds (about 51E6 multiply and accumulates/sec)


* Total for 199 FIR Coefficients = 505 microseconds (17.4% of 44117 Hz available time)


* Total for stereo is twice those numbers.


* Measured timing of update() for L or R 128 sample block, Teensy 4.0:


* Fixed time 1.4 microseconds


* Per FIR Coefficient time 0.44 microseconds (about 290E6 multiply and accumulate/sec)


* Total for 199 FIR Coefficients = 90 microseconds (3.1% of 44117 Hz available time)


* Total for stereo is twice those numbers


* Measured for FIRGeneralNew(), T4.0, to design a 4000 tap FIR is 14 sec. This goes


* with the square of the number of taps.


* Measured for getResponse() for nFIR=4000 and nFreq=5000, T4.0, is about a minute.


*


* Functions for the AudioFilterFIRGeneral_F32 object are


* FIRGeneralNew(*adb, nFIR, cf32f, kdb, *pStateArray); // to design and use an adb[]


* frequency response.


* LoadCoeffs(nFIR, cf32f, *pStateArray); // To directly load FIR coefficients cf32f[]


* getResponse(nFreq, *rdb); // To obtain the amplitude response in dB, rdb[]


*


* Status: Tested T3.6, T4.0 No known bugs


*


* Examples: TestFIRGeneralLarge4.ino TestFIRGeneralLarge5.ino


*


* Copyright (c) 2020 Bob Larkin


* Any snippets of code from PJRC or Chip Audette used here brings with it


* the associated license.


*


* In addition, work here is covered by MIT LIcense:


*


* 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.


*/




#ifndef _filter_FIR_general_f32_h


#define _filter_FIR_general_f32_h




#include "Arduino.h"


#include "AudioStream_F32.h"


#include "arm_math.h"


#include "mathDSP_F32.h"




#ifndef MF_PI


#define MF_PI 3.1415926f


#endif




// Temporary timing test


#define TEST_TIME_FIRG 0




#define ERR_FIRGEN_BANDS 1


#define ERR_FIRGEN_SIDELOBES 2


#define ERR_FIRGEN_NFIR 3




class AudioFilterFIRGeneral_F32 : public AudioStream_F32


{


//GUI: inputs:1, outputs:1 //this line used for automatic generation of GUI node


//GUI: shortName:filter_Equalizer


public:


AudioFilterFIRGeneral_F32(void): AudioStream_F32(1,inputQueueArray) {


// Initialize FIR instance (ARM DSP Math Library) with default simple passthrough FIR


arm_fir_init_f32(&fir_inst, nFIR, (float32_t *)cf32f, &StateF32[0], (uint32_t)block_size);


}


AudioFilterFIRGeneral_F32(const AudioSettings_F32 &settings): AudioStream_F32(1,inputQueueArray) {


block_size = settings.audio_block_samples;


sample_rate_Hz = settings.sample_rate_Hz;


arm_fir_init_f32(&fir_inst, nFIR, (float32_t *)cf32f, &StateF32[0], (uint32_t)block_size);


}




uint16_t FIRGeneralNew(float32_t *adb, uint16_t _nFIR, float32_t *_cf32f, float32_t kdb, float32_t *pStateArray);


uint16_t LoadCoeffs(uint16_t _nFIR, float32_t *_cf32f, float32_t *pStateArray);


void getResponse(uint16_t nFreq, float32_t *rdb);


void update(void);




private:


audio_block_f32_t *inputQueueArray[1];


uint16_t block_size = AUDIO_BLOCK_SAMPLES;


float32_t firStart[4] = {0.0, 1.0, 0.0, 0.0}; // Initialize to passthrough


float32_t* cf32f = firStart; // pointer to current coefficients


uint16_t nFIR = 4; // Number of coefficients


float32_t sample_rate_Hz = AUDIO_SAMPLE_RATE;




// *Temporary*  TEST_TIME allows measuring time in microseconds for each part of the update()


#if TEST_TIME_FIRG


elapsedMicros tElapse;


int32_t iitt = 999000; // count up to a million during startup


#endif


// ARM DSP Math library filter instance


arm_fir_instance_f32 fir_inst;


float32_t StateF32[AUDIO_BLOCK_SAMPLES + 4]; // FIR_GENERAL_MAX_COEFFS]; // max, max


};


#endif


