-
Notifications
You must be signed in to change notification settings - Fork 34
/
Copy pathAudioFilterEqualizer_F32.h
178 lines (166 loc) · 8.43 KB
/
AudioFilterEqualizer_F32.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/*
* AudioFilterEqualizer_F32
*
* Created: Bob Larkin W7PUA 8 May 2020
*
* This is a direct translation of the receiver audio equalizer written
* by this author for the open-source DSP-10 radio in 1999. See
* http://www.janbob.com/electron/dsp10/dsp10.htm and
* http://www.janbob.com/electron/dsp10/uhf3_35a.zip
*
* 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.
*
* This equalizer is specified by an array of 'nBands' frequency bands
* each of of arbitrary frequency span. The first band always starts at
* 0.0 Hz, and that value is not entered. Each band is specified by the upper
* frequency limit to the band.
* The last band always ends at half of the sample frequency, which for 44117 Hz
* sample frequency would be 22058.5. Each band is specified by its upper
* frequency in an .INO supplied array feq[]. The dB level of that band is
* specified by a value, in dB, arranged in an .INO supplied array
* aeq[]. Thus a trivial bass/treble control might look like:
* nBands = 3;
* feq[] = {300.0, 1500.0, 22058.5};
* float32_t bass = -2.5; // in dB, relative to anything
* float32_t treble = 6.0;
* aeq[] = {bass, 0.0, treble};
*
* It may be obvious that this equalizer is a more general case of the common
* functions such as low-pass, band-pass, notch, etc. For instance, a pair
* of band pass filters would look like:
* nBands = 5;
* feq[] = {500.0, 700.0, 2000.0, 2200.0, 22058.5};
* aeq[] = {-100.0, 0.0, -100.0, 2.0, -100.0};
* where we added 2 dB of gain to the 2200 to 2400 Hz filter, relative to the 500
* to 700 Hz band.
*
* An octave band equalizer is made by starting at some low frequency, say 40 Hz for the
* first band. The lowest frequency band will be from 0.0 Hz up to that first frequency.
* Next multiply the first frequency by 2, creating in our example, a band from 40.0
* to 80 Hz. This is continued until the last frequency is about 22058 Hz.
* This works out to require 10 bands, as follows:
* nBands = 10;
* feq[] = { 40.0, 80.0, 160.0, 320.0, 640.0, 1280.0, 2560.0, 5120.0, 10240.0, 22058.5};
* aeq[] = { 5.0, 4.0, 2.0, -3.0, -4.0, -1.0, 3.0, 6.0, 3.0, 0.5 };
*
* For a "half octave" equalizer, multiply each upper band limit by the square root of 2 = 1.414
* to get the next band limit. For that case, feq[] would start with a sequence
* like 40, 56.56, 80.00, 113.1, 160.0, ... for a total of about 20 bands.
*
* How well all of this is achieved depends on the number of FIR coefficients
* being used. In the Teensy 3.6 / 4.0 the resourses allow a hefty number,
* say 201, of coefficients to be used without stealing all the processor time
* (see Timing, below). The coefficient and FIR memory is sized for a maximum of
* 250 coefficients, but can be recompiled for bigger with the define FIR_MAX_COEFFS.
* To simplify calculations, the number of FIR coefficients should be odd. If not
* odd, the number will be reduced by one, quietly.
*
* If you try to make the bands too narrow for the number of FIR coeffficients,
* the approximation to the desired curve becomes poor. This can all 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.
*
* This 16-bit integer version adjusts the maximum coefficient size to scale16 in the calls
* to both equalizerNew() and getResponse(). Broadband equalizers can work with full-scale
* 32767.0f sorts of levels, where narrow band filtering may need smaller values to
* prevent overload. Experiment and check carefully. Use lower values if there are doubts.
*
* For a pass-through function, something like this (which can be intermixed with fancy equalizers):
* float32_t fBand[] = {10000.0f, 22058.5f};
* float32_t dbBand[] = {0.0f, 0.0f};
* equalize1.equalizerNew(2, &fBand[0], &dbBand[0], 4, &equalizeCoeffs[0], 30.0f, 32767.0f);
*
* Measured timing of update() for a 128 sample block, Teensy 3.6:
* Fixed time 13 microseconds
* Per FIR Coefficient time 2.5 microseconds
* Total for 199 FIR Coefficients = 505 microseconds (17.4% of 44117 Hz available time)
*
* Per FIR Coefficient, Teensy 4.0, 0.44 microseconds
*
* 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_equalizer_f32_h
#define _filter_equalizer_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_EQ 0
#define EQUALIZER_MAX_COEFFS 251
#define ERR_EQ_BANDS 1
#define ERR_EQ_SIDELOBES 2
#define ERR_EQ_NFIR 3
class AudioFilterEqualizer_F32 : public AudioStream_F32
{
//GUI: inputs:1, outputs:1 //this line used for automatic generation of GUI node
//GUI: shortName:filter_Equalizer
public:
AudioFilterEqualizer_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);
}
AudioFilterEqualizer_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 equalizerNew(uint16_t _nBands, float32_t *feq, float32_t *adb,
uint16_t _nFIR, float32_t *_cf32f, float32_t kdb);
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
uint16_t nBands = 2;
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_EQ
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 + EQUALIZER_MAX_COEFFS]; // max, max
};
#endif