Decimation & Interpolation with arm_fir_decimate / interpolate

Status
Not open for further replies.

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
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:
Frank,

I'm probably not the best person to determine all of what is going on in your code, but I too have recently been interested in greatly reducing sampling rate as I am only interested in low frequencies with better resolution.

The Teensy Audio board does seem to be able to drop down to an 8k sample just by changing the configuration parameters as other folks on the forum have done.

Also, shouldn't the FIR filter coefficients you are using be 16 bit integer format. This is what the filter blocks in the audio library require...

Let the group know if you got this to work, I'm very interested.
 
Hi Treed,

thanks for your interest. What I need is not using another sample rate (I still need the 44.1kHz sample rate) but decimate and interpolate in software. Not sure about the filter coeffs, but my filtering works with const short for FIR filters and double for the biquad filters (IIR).

I have not yet managed to implement decimation and interpolation, but I recently got a message by Esteban using similar hardware for a Teensy SDR who implemented decimation and interpolation on the Teensy!

https://github.com/gmtii/Teensy_SDR/tree/test_version

Will try to test that out in the next days.

73 de Frank DD4WH
 
Status
Not open for further replies.
Back
Top