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

189 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 16-bit integer data,
* and a maximum of 200 FIR coefficients, even only. (taps). For Teensy Audio.
* AudioFilterFIR_F32 (OpenAudio_ArduinoLibrary by Chip Audette) handles 32-bit floating point
* data and a maximum of 200 taps. Can be used with a mix of Teensy Audio
* and 32-bit OpenAudio_Arduino blocks.
* AudioFilterFIRGeneral_F32 (This block) handles 32-bit floating point
* data and very large number of taps, even or odd. Can be used with a mix of Teensy Audio
* and 32-bit OpenAudio_Arduino blocks. This includes the design of an
* arbitrary frequency response using Kaiser windows.
* AudioFilterFIRGeneral_I16 Same as this block, but data is 16-bit integer and
* the number of taps must be even.
* AudioFilterEqualizer_F32 handles 32-bit floating point data and 250 maximum taps
* even or odd. Can be used with a mix of Teensy Audio
* and 32-bit 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 16-bit integer data and 250 maximum taps,
* even only.
*
* FIR filters suffer from needing considerable computation of the multiply-and-add
* 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 low-pass 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 .INO-supplied 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 2-step process. This makes it practical to change equalizer
* levels on-the-fly. The constructor starts up with a 4-tap 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