DD4WH
Well-known member
I am trying to use decimation/interpolation for the Teensy SDR Receiver. The software on the Teensy 3.1 processes the I & Q and uses several FIR and biquad filters. I would like to reduce processor load and improve filter steepness (to possibly include noise reduction) by decimation and subsequent interpolation. My test setup is something like this:
sin oscillator -> FIR_decimation filter -> biquad filter (4 stages using calculated coefficients with Iowa Hills IIR Filter designer for the new 4x lower sampling frequency) --> FIR_interpolation filter (also with coefficients for the lower sampling f) --> biquad filter (standard 4-stage Linkwitz-Riley)
I am a newby to the Audio library and the Teensy, so I may be on the totally wrong path:
Using the implementation of the FIR filter in the audio library, I built a script for decimation using arm_fir_decimate_fast_q15:
filter_fir_decimate.cpp
filter_fir_decimate.h
As I understood it, the number of input samples should be AUDIO_BLOCK_SAMPLES * decimation factor, because I need AUDIO_BLOCK_SAMPLES as the no. of output samples. But then I have a timing problem, because I receive one block 2.9ms, but after that have only 1/4 output samples . . . Probably I am missing something here.
I did the same for interpolation, but divided the no. of input samples by the interpolation factor.
filter_fir_interpolate.cpp
filter_fir_interpolate.h
The test main code is:
it needs the following coefficients for the filters:
filters.cpp
filters.h
fir_dec_44.h
fir_int_44.h
iir_44_fs11029.h
Would anybody be able to point me in the right direction with my approach?
Edit: the sketch compiles, but while running gets stuck along the way. I also tried the sketch without multiplying/dividing AUDIO_BLOCK_SAMPLES in the filter_fir_decimate and filter_fir_interpolate files: compiles, but gets stuck or produces distorted sound.
On the other hand, is that approach even a possible option ? More generally spoken: Is decimation / interpolation possible with the Teensy without changing the hardware clock during processing? (my aim is saving processor power and having steeper filter skirts; using a generally lower sampling frequency is not an option, because I need the 44.1kHz of spectrum display created by the complex FFT of the input I and Q signals) ;-)
Frank DD4WH
sin oscillator -> FIR_decimation filter -> biquad filter (4 stages using calculated coefficients with Iowa Hills IIR Filter designer for the new 4x lower sampling frequency) --> FIR_interpolation filter (also with coefficients for the lower sampling f) --> biquad filter (standard 4-stage Linkwitz-Riley)
I am a newby to the Audio library and the Teensy, so I may be on the totally wrong path:
Using the implementation of the FIR filter in the audio library, I built a script for decimation using arm_fir_decimate_fast_q15:
filter_fir_decimate.cpp
Code:
/* Audio Library for Teensy 3.X
* Copyright (c) 2014, Pete (El Supremo)
*
* 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.
*
* changed by DD4WH for using the decimation algorithm
* 2015_11_21
*
*/
#include "filter_fir_decimate.h"
void AudioFilterFIRDec::update(void)
{
audio_block_t *block, *b_new;
block = receiveReadOnly();
if (!block) return;
// If there's no coefficient table, give up.
if (coeff_p == NULL) {
release(block);
return;
}
// do passthru
if (coeff_p == FIR_PASSTHRU) {
// Just passthrough
transmit(block);
release(block);
return;
}
// get a block for the FIR output
b_new = allocate();
if (b_new) {
arm_fir_decimate_fast_q15(&fir_inst, (q15_t *)block->data,
// arm_fir_decimate_q15(&fir_inst, (q15_t *)block->data,
// (q15_t *)b_new->data, AUDIO_BLOCK_SAMPLES);
(q15_t *)b_new->data, AUDIO_BLOCK_SAMPLES * 4); // I use decimation by 4, so I should have an input of 4x as many samples!?
transmit(b_new); // send the FIR output
release(b_new);
}
release(block);
}
filter_fir_decimate.h
Code:
/* Audio Library for Teensy 3.X
* Copyright (c) 2014, Pete (El Supremo)
*
* 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.
*
* changed by DD4WH for using the decimation algorithm
* 2015_11_21
*
*/
#ifndef filter_fir_decimate_h_
#define filter_fir_decimate_h_
#include "AudioStream.h"
#include "arm_math.h"
// Indicates that the code should just pass through the audio
// without any filtering (as opposed to doing nothing at all)
#define FIR_PASSTHRU ((const short *) 1)
#define FIR_MAX_COEFFS 200
class AudioFilterFIRDec : public AudioStream
{
public:
AudioFilterFIRDec(void): AudioStream(1,inputQueueArray), coeff_p(NULL) {
}
void begin(const short *cp, int n_coeffs, int Mdec) {
coeff_p = cp;
// Initialize FIR instance (ARM DSP Math Library)
if (coeff_p && (coeff_p != FIR_PASSTHRU) && n_coeffs <= FIR_MAX_COEFFS) {
arm_fir_decimate_init_q15(&fir_inst, n_coeffs, Mdec,(q15_t *)coeff_p,
&StateQ15[0], AUDIO_BLOCK_SAMPLES*Mdec); // input no. of blocks should be Mdec times larger!?
}
}
void end(void) {
coeff_p = NULL;
}
virtual void update(void);
private:
audio_block_t *inputQueueArray[1];
// pointer to current coefficients or NULL or FIR_PASSTHRU
const short *coeff_p;
// ARM DSP Math library filter instance
arm_fir_decimate_instance_q15 fir_inst;
q15_t StateQ15[AUDIO_BLOCK_SAMPLES + FIR_MAX_COEFFS];
};
#endif
As I understood it, the number of input samples should be AUDIO_BLOCK_SAMPLES * decimation factor, because I need AUDIO_BLOCK_SAMPLES as the no. of output samples. But then I have a timing problem, because I receive one block 2.9ms, but after that have only 1/4 output samples . . . Probably I am missing something here.
I did the same for interpolation, but divided the no. of input samples by the interpolation factor.
filter_fir_interpolate.cpp
Code:
/* Audio Library for Teensy 3.X
* Copyright (c) 2014, Pete (El Supremo)
*
* 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.
*
* changed by DD4WH for using the decimation algorithm
* 2015_11_21
*
*/
#include "filter_fir_interpolate.h"
void AudioFilterFIRInt::update(void)
{
audio_block_t *block, *b_new;
block = receiveReadOnly();
if (!block) return;
// If there's no coefficient table, give up.
if (coeff_p == NULL) {
release(block);
return;
}
// do passthru
if (coeff_p == FIR_PASSTHRU) {
// Just passthrough
transmit(block);
release(block);
return;
}
// get a block for the FIR output
b_new = allocate();
if (b_new) {
// arm_fir_interpolate_fast_q15(&fir_inst, (q15_t *)block->data,
arm_fir_interpolate_q15(&fir_inst, (q15_t *)block->data,
(q15_t *)b_new->data, AUDIO_BLOCK_SAMPLES);
transmit(b_new); // send the FIR output
release(b_new);
}
release(block);
}
filter_fir_interpolate.h
Code:
/* Audio Library for Teensy 3.X
* Copyright (c) 2014, Pete (El Supremo)
*
* 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.
*
* changed by DD4WH for using the decimation algorithm
* 2015_11_21
*
*/
#ifndef filter_fir_interpolate_h_
#define filter_fir_interpolate_h_
#include "AudioStream.h"
#include "arm_math.h"
// Indicates that the code should just pass through the audio
// without any filtering (as opposed to doing nothing at all)
#define FIR_PASSTHRU ((const short *) 1)
#define FIR_MAX_COEFFS 200
class AudioFilterFIRInt : public AudioStream
{
public:
AudioFilterFIRInt(void): AudioStream(1,inputQueueArray), coeff_p(NULL) {
}
void begin(const short *cp, int n_coeffs, int LDec) {
coeff_p = cp;
// Initialize FIR instance (ARM DSP Math Library)
if (coeff_p && (coeff_p != FIR_PASSTHRU) && n_coeffs <= FIR_MAX_COEFFS) {
arm_fir_interpolate_init_q15(&fir_inst, LDec, n_coeffs, (q15_t *)coeff_p, // strange, why order is different to arm_fir_decimate_instance --> Mdec AFTER n_coeffs !?
&StateQ15[0], AUDIO_BLOCK_SAMPLES/4);
}
}
void end(void) {
coeff_p = NULL;
}
virtual void update(void);
private:
audio_block_t *inputQueueArray[1];
// pointer to current coefficients or NULL or FIR_PASSTHRU
const short *coeff_p;
// ARM DSP Math library filter instance
arm_fir_interpolate_instance_q15 fir_inst;
q15_t StateQ15[AUDIO_BLOCK_SAMPLES + FIR_MAX_COEFFS];
};
#endif
The test main code is:
Code:
/* Teensy SDR
* Decimate_Interpolate Test
* DD4WH
* 2015_11_24
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Audio.h>
#include <SPI.h>
#include "SD.h"
#include "Wire.h"
#include "filters.h"
#include "iir_44_fs11029.h"
#include "filter_fir_decimate.h"
#include "filter_fir_interpolate.h"
// audio definitions
AudioSynthWaveform sin4000; // Local Oscillator
AudioSynthWaveform sin400; // Local Oscillator
// FIR filters
AudioFilterFIRDec FIR_DEC;
AudioFilterFIRInt FIR_INT;
AudioFilterBiquad biquad1;
AudioFilterBiquad biquad2;
AudioMixer4 mixer; // Summer (add inputs)
AudioOutputI2S audioOutput; // Audio Shield: headphones & line-out
AudioControlSGTL5000 audioShield; // Create an object to control the audio shield.
//---------------------------------------------------------------------------------------------------------
// Create Audio connections to build a Decimator / Interpolator
//
AudioConnection c1(sin400, 0, mixer, 1);
AudioConnection c2(sin4000, 0, mixer, 0);
AudioConnection c3 (mixer, 0, FIR_DEC,0);
AudioConnection c4 (FIR_DEC, 0, biquad1,0);
AudioConnection c5 (biquad1, 0 ,FIR_INT, 0);
AudioConnection c6 (FIR_INT, 0, biquad2, 0);
AudioConnection c7(biquad2,0, audioOutput, 0);
AudioConnection c8(biquad2,0, audioOutput, 1);
//---------------------------------------------------------------------------------------------------------
void setup()
{
Serial.begin(9600); // debug console
#ifdef DEBUG
while (!Serial) ; // wait for connection
Serial.println("initializing");
#endif
AudioMemory(180);
// Enable the audio shield and set the output volume.
audioShield.enable();
audioShield.volume(0.3);
Processor("Setup finished ! ");
} // end void SETUP
void Processor (String string) {
delay(1000);
Serial.print(string);
Serial.print("Proc = ");
Serial.print(AudioProcessorUsage());
Serial.print(" (");
Serial.print(AudioProcessorUsageMax());
Serial.print("), Mem = ");
Serial.print(AudioMemoryUsage());
Serial.print(" (");
Serial.print(AudioMemoryUsageMax());
Serial.println(")");
delay(1000);
}
void loop()
{
AudioNoInterrupts();
mixer.gain (0, 0.8);
mixer.gain (1, 0.8);
// set & start oscillators
sin400.begin(1, 400, TONE_TYPE_SINE);
sin4000.begin(1, 4000, TONE_TYPE_SINE);
AudioInterrupts();
Processor("Started oscillators ");
AudioNoInterrupts();
FIR_DEC.begin(firdec44, 16, 4); // 16 taps, decimation by 4
AudioInterrupts();
Processor ("Started FIR Decimator");
AudioNoInterrupts();
biquad1.setCoefficients(0, IIR_44_dec_Coeffs_0);
biquad1.setCoefficients(1, IIR_44_dec_Coeffs_1);
biquad1.setCoefficients(2, IIR_44_dec_Coeffs_2);
biquad1.setCoefficients(3, IIR_44_dec_Coeffs_3);
AudioInterrupts();
Processor ("Started biquad1 ");
AudioNoInterrupts();
FIR_INT.begin(firint44, 16, 4); // 16 taps, interpolation by 4
AudioInterrupts();
Processor ("Started FIR Interpolator ");
AudioNoInterrupts();
// Linkwitz-Riley filter
biquad2.setLowpass(0, 3600, 0.54);
biquad2.setLowpass(1, 3600, 1.3);
biquad2.setLowpass(2, 3600, 0.54);
biquad2.setLowpass(3, 3600, 1.3);
AudioInterrupts();
Processor ("Started biquad2 ");
/* AudioNoInterrupts();
sin400.begin(1, 300, TONE_TYPE_SINE);
AudioInterrupts();
Processor("sin 300");
delay(1000);
AudioNoInterrupts();
sin400.begin(0, 400, TONE_TYPE_SINE);
AudioInterrupts();
Processor("sin 300 silent");
*/
}
it needs the following coefficients for the filters:
filters.cpp
Code:
#include "filters.h"
const short firdec44[16] = {
#include "fir_dec_44.h"
};
const short firint44[16] = {
#include "fir_int_44.h"
};
filters.h
Code:
// Number of coefficients
extern const short firdec44[];
extern const short firint44[];
fir_dec_44.h
Code:
// fir_dec_44
// Filter by DD4WH DC to 3.6 kHz
// (sampling rate 44.118 kHz)
// 23.11.2015
// Parameters generated using Iowa Hills FIR Filter Designer
// 16 taps
// Window off
// Parks McLellan
//
-168.1076582,
-107.1159149,
118.342852,
865.899433,
2284.093234,
4204.524053,
6098.087297,
7285.354147,
7285.354147,
6098.087297,
4204.524053,
2284.093234,
865.899433,
118.342852,
-107.1159149,
-168.1076582
fir_int_44.h
Code:
// fir_int_44
// Filter by DD4WH DC to 3.6 kHz
// (sampling rate 11029 Hz)
// 23.11.2015
// Parameters generated using Iowa Hills FIR Filter Designer
// 16 taps
// Window Kaiser
// Parks McClellan
//
406.6672163,
1242.01822,
-479.5970509,
-762.072896,
2255.405164,
-2522.992971,
-1066.734916,
18491.08144,
18491.08144,
-1066.734916,
-2522.992971,
2255.405164,
-762.072896,
-479.5970509,
1242.01822,
406.6672163
iir_44_fs11029.h
Code:
// DD4WH LPF DC - 4.36 kHz
// (sampling rate 11.029 kHz)
// 21.11.2015
// Parameters generated using Iowa Hills IIR Filter Designer
// 8th order elliptic Lowpass (4-stage cascaded biquad)
// 60dB stopband, 0.001dB ripple
//
//#ifndef __IIR_44
//#define __IIR_44
// coefficients must be in order: B0, B1, B2, A1, A2
// unlike output by Iowa Hils IIR Filter Designer !
const double IIR_44_dec_Coeffs_0[] =
{ 0.532523942999473809,
0.921030328613157034,
0.532523942999473809,
0.796466502794781150,
0.189611711817323586
};
const double IIR_44_dec_Coeffs_1[] =
{ 0.675500388383717176,
1.205670503377937130,
0.675500388383717176 ,
1.082874305237743640,
0.473796974907627788
};
const double IIR_44_dec_Coeffs_2[] =
{ 0.795665615887912514,
1.501904179248414020,
0.795665615887912514,
1.350176609463351380,
0.743058801560887550
};
const double IIR_44_dec_Coeffs_3[] =
{ 0.864183664781403293,
1.714828205360521940,
0.864183664781403293,
1.519180538086421620,
0.924014996836906910
};
Would anybody be able to point me in the right direction with my approach?
Edit: the sketch compiles, but while running gets stuck along the way. I also tried the sketch without multiplying/dividing AUDIO_BLOCK_SAMPLES in the filter_fir_decimate and filter_fir_interpolate files: compiles, but gets stuck or produces distorted sound.
On the other hand, is that approach even a possible option ? More generally spoken: Is decimation / interpolation possible with the Teensy without changing the hardware clock during processing? (my aim is saving processor power and having steeper filter skirts; using a generally lower sampling frequency is not an option, because I need the 44.1kHz of spectrum display created by the complex FFT of the input I and Q signals) ;-)
Frank DD4WH
Last edited: