-
Notifications
You must be signed in to change notification settings - Fork 34
/
Copy pathAudioFilterConvolution_F32.cpp
285 lines (263 loc) · 9.67 KB
/
AudioFilterConvolution_F32.cpp
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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
/**
******************************************************************************
* @file AudioFilterConvolution_F32.cpp
* @author Giuseppe Callipo - IK8YFW - [email protected]
* @version V2.0.0
* @date 06-02-2021
* @brief F32 Filter Convolution
*
******************************************************************************
******************************************************************************
This software is based on the AudioFilterConvolution routine
Written by Brian Millier on Mar 2017
https://circuitcellar.com/research-design-hub/fancy-filtering-with-the-teensy-3-6/
and modified by Giuseppe Callipo - ik8yfw.
Modifications:
1) Class refactoring, change some methods visibility;
2) Filter coefficients calculation included into class;
3) Change the class for running in both with F32
OpenAudio_ArduinoLibrary for Teensy;
4) Added initFilter method for single anf fast initialization and on
the fly reinititializzation;
5) Optimize it to use as output audio filter on SDR receiver.
6) Optimize the time execution
*******************************************************************/
// Revised for OpenAudio_Arduino Teensy F32 library, 8 Feb 2022
// Revised 18 January to work for Teensy 3.5 and T3.6. Bob L
// Revised to be sure it will compile (run T3.5, T3.6, T4.x) for any. BobL 19 Jan 2023
#if defined(__MK64FX512__) || defined(__MK66FX1M0__) || defined(__IMXRT1062__)
#include "AudioFilterConvolution_F32.h"
void AudioFilterConvolution_F32::passThrough(int stat)
{
passThru=stat;
}
// Function to pre-calculate the multiplying frequency function, the "mask."
void AudioFilterConvolution_F32::impulse(float32_t *FIR_coef)
{
uint32_t k = 0;
uint32_t i = 0;
enabled = 0; // shut off audio stream while impulse is loading
for (i = 0; i < (FFT_length / 2) + 1; i++)
{
FIR_filter_mask[k++] = FIR_coef[i];
FIR_filter_mask[k++] = 0;
}
for (i = FFT_length + 1; i < FFT_length * 2; i++)
{
FIR_filter_mask[i] = 0.0;
}
// T3.5 or T3.6
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
arm_cfft_radix4_f32(&fft_instFwd, FIR_filter_mask);
#elif defined(__IMXRT1062__)
arm_cfft_f32(&arm_cfft_sR_f32_len1024, FIR_filter_mask, 0, 1); // T4.x
#endif
// for 1st time thru, zero out the last sample buffer to 0
arm_fill_f32(0, last_sample_buffer_L, 128*4);
state = 0;
enabled = 1; //enable audio stream again
}
void AudioFilterConvolution_F32::update(void)
{
audio_block_f32_t *block;
float32_t *bp;
if (enabled != 1 ) return;
block = receiveWritable_f32(0); // MUST be Writable, as convolution results are written into block
if (block) {
switch (state) {
case 0:
if (passThru ==0) {
arm_cmplx_mult_cmplx_f32(FFT_buffer, FIR_filter_mask, iFFT_buffer, FFT_length); // complex multiplication in Freq domain = convolution in time domain
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
arm_cfft_radix4_f32(&fft_instRev, iFFT_buffer);
#elif defined(__IMXRT1062__)
arm_cfft_f32(&arm_cfft_sR_f32_len1024, iFFT_buffer, 1, 1);
#endif
k = 0;
l = 1024;
for (int i = 0; i < 512; i++) {
buffer[i] = last_sample_buffer_L[i] + iFFT_buffer[k++]; // this performs the "ADD" in overlap/Add
last_sample_buffer_L[i] = iFFT_buffer[l++]; // this saves 512 samples (overlap) for next time around
k++;
l++;
}
}
arm_copy_f32 (&buffer[0], &tbuffer[0], 128*4);
bp = block->data;
for (int i = 0; i < 128; i++) {
buffer[i] = *bp;
*bp++ = tbuffer[i];
}
AudioStream_F32::transmit(block);
AudioStream_F32::release(block);
state = 1;
break;
case 1:
bp = block->data;
for (int i = 0; i < 128; i++) {
buffer[128+i] = *bp;
*bp++ = tbuffer[i+128];
}
AudioStream_F32::transmit(block);
AudioStream_F32::release(block);
state = 2;
break;
case 2:
bp = block->data;
for (int i = 0; i < 128; i++) {
buffer[256 + i] = *bp;
*bp++ = tbuffer[i+256]; // tbuffer contains results of last FFT/multiply/iFFT processing (convolution filtering)
}
AudioStream_F32::transmit(block);
AudioStream_F32::release(block);
// zero pad last half of array- necessary to prevent aliasing in FFT
arm_fill_f32(0, FFT_buffer + 1024, FFT_length);
state = 3;
break;
case 3:
bp = block->data;
for (int i = 0; i < 128; i++) {
buffer[384 + i] = *bp;
*bp++ = tbuffer[i + 384]; // tbuffer contains results of last FFT/multiply/iFFT processing (convolution filtering)
}
AudioStream_F32::transmit(block);
AudioStream_F32::release(block);
state = 0;
// 4 blocks are in- now do the FFT1024,complex multiply and iFFT1024 on 512samples of data
// using the overlap/add method
if (passThru ==0) {
//fill FFT_buffer with current audio samples
k = 0;
for (i = 0; i < 512; i++)
{
FFT_buffer[k++] = buffer[i]; // real
FFT_buffer[k++] = buffer[i]; // imag
}
// calculations are performed in-place in FFT routines
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
arm_cfft_radix4_f32(&fft_instFwd, FFT_buffer);
#elif defined(__IMXRT1062__)
arm_cfft_f32(&arm_cfft_sR_f32_len1024, FFT_buffer, 0, 1); // perform complex FFT
#endif
} //end if passTHru
break;
}
}
}
float32_t AudioFilterConvolution_F32::Izero (float32_t x)
{
float32_t x2 = x / 2.0;
float32_t summe = 1.0;
float32_t ds = 1.0;
float32_t di = 1.0;
float32_t errorlimit = 1e-9;
float32_t tmp;
do
{
tmp = x2 / di;
tmp *= tmp;
ds *= tmp;
summe += ds;
di += 1.0;
} while (ds >= errorlimit * summe);
return (summe);
} // END Izero
float AudioFilterConvolution_F32::m_sinc(int m, float fc)
{ // fc is f_cut/(Fsamp/2)
// m is between -M and M step 2
//
float x = m*PIH_F32;
if(m == 0)
return 1.0f;
else
return sinf(x*fc)/(fc*x);
}
void AudioFilterConvolution_F32::calc_FIR_coeffs
(float *coeffs, int numCoeffs, float32_t fc, float32_t Astop,
int type, float dfc, float Fsamprate){
// pointer to coefficients variable, no. of coefficients to calculate,
// frequency where it happens, stopband attenuation in dB,
// filter type, half-filter bandwidth (only for bandpass and notch).
// Modified by WMXZ and DD4WH after
// Wheatley, M. (2011): CuteSDR Technical Manual. www.metronix.com,
// pages 118 - 120, FIR with Kaiser-Bessel Window.
// Assess required number of coefficients by
// numCoeffs = (Astop - 8.0) / (2.285 * TPI * normFtrans);
// selecting high-pass, numCoeffs is forced to an even number for
// better frequency response
int ii,jj;
float32_t Beta;
float32_t izb;
float fcf = fc;
int nc = numCoeffs;
fc = 2.0f * fc / Fsamprate; // Corrected
dfc = dfc / Fsamprate;
// calculate Kaiser-Bessel window shape factor beta from stop-band attenuation
if (Astop < 20.96)
Beta = 0.0;
else if (Astop >= 50.0)
Beta = 0.1102 * (Astop - 8.71);
else
Beta = 0.5842 * powf((Astop - 20.96), 0.4) + 0.07886 * (Astop - 20.96);
izb = Izero (Beta);
if(type == LOWPASS)
{ fcf = fc;
nc = numCoeffs;
}
else if(type == HIGHPASS)
{ fcf = -fc;
nc = 2*(numCoeffs/2);
}
else if ((type == BANDPASS) || (type==BANDREJECT))
{
fcf = dfc;
nc = 2*(numCoeffs/2); // maybe not needed
}
/*
else if (type==HILBERT)
{
nc = 2*(numCoeffs/2);
// clear coefficients
for(ii=0; ii< 2*(nc-1); ii++) coeffs[ii]=0;
// set real delay
coeffs[nc]=1; // <<<<<??????????
// set imaginary Hilbert coefficients
for(ii=1; ii< (nc+1); ii+=2)
{
if(2*ii==nc) continue;
float x =(float)(2*ii - nc)/(float)nc;
float w = Izero(Beta*sqrtf(1.0f - x*x))/izb; // Kaiser window
coeffs[ii-1] = 1.0f/(PIH_F32*(float)(ii-nc/2)) * w ;
//coeffs[2*ii+1] = 1.0f/(PIH_F32*(float)(ii-nc/2)) * w ;
}
return; // From Hilbert design
}
*/
for(ii= - nc, jj=0; ii< nc; ii+=2,jj++)
{
float x =(float)ii/(float)nc;
float w = Izero(Beta*sqrtf(1.0f - x*x))/izb; // Kaiser window
coeffs[jj] = fcf * m_sinc(ii,fcf) * w;
}
if(type==HIGHPASS)
{
coeffs[nc/2] += 1;
}
else if (type==BANDPASS)
{
for(jj=0; jj< nc+1; jj++) coeffs[jj] *= 2.0f*cosf(PIH_F32*(2*jj-nc)*fc);
}
else if (type==BANDREJECT)
{
for(jj=0; jj< nc+1; jj++) coeffs[jj] *= -2.0f*cosf(PIH_F32*(2*jj-nc)*fc);
coeffs[nc/2] += 1;
}
} // END calc_FIR_coef
void AudioFilterConvolution_F32::initFilter ( float32_t fc, float32_t Astop, int type, float dfc){
//Init Fir
calc_FIR_coeffs (FIR_Coef, MAX_NUMCOEF, fc, Astop, type, dfc, fs);
enabled = 0; // set to zero to disable audio processing until impulse has been loaded
impulse(FIR_Coef); // generates Filter Mask and enables the audio stream
}
// End Only T3.5, T3.6 or T4.x
#endif