Realtime audio processing with floating point math & new CMSIS lib in the Teensy 3.5

Status
Not open for further replies.

DD4WH

Well-known member
Realtime audio processing with floating point math & new CMSIS lib in the Teensy 3.5

I would like to use the floating point capabilities of the new Teensy 3.5 for faster and more accurate audio processing in real time. This will be the basis for audio-processing in a SoftwareDefinedRadio (like the Teensy SDR or mcHF) which involves a whole lot of FIR and IIR filters with decimation and interpolation and also some complex FFTs. [what I mean with "more accurate": with fixed point math for example, real time decimation and interpolation of audio with the Teensy 3.2 was not possible --> audio was distorted, same with FFT & IFFT]

However, the audio lib uses 16-bit fixed point for all the objects. This means, one cannot use the new FPU power of the Teensy 3.5 for audio processing. So, I looked for a way to use floating point math for all audio processing.

I tried the following script: it takes one audio block with 128 int16_t from the LINE INPUT by using the recordqueue-object, converts it from int16_t to float32_t, does all the desired audio processing in float32_t with the NEW ARM CMSIS lib (see Duffs description of how to use the new CMSIS lib for Teensyduino --> HERE and additionally add one file --> HERE), converts the audio to int16_t again and gives it back to the headphone output. It took me some hours to figure it out, but it works, even with quite a large FIR lowpass filter with 200 taps at 44118sps! I can not hear any decrease in audio quality, which is very promising, but requires rigorous testing! Ray has similar experience with nice audio using floating point FFT/IFFT pass thru --> THREAD.

Code:
// Use queue for input AND output
// to allow for floating point audio processing
// 
// Frank DD4WH 2016_10_24
//
// this is a setup to test whether we can do real time audio processing 
// with the Teensy 3.5 in floating point math
// it takes the LINE INPUT audio, converts it from int16_t to float32_t, 
// does the audio processing in float32_t with the NEW ARM CMSIS lib (see https://forum.pjrc.com/threads/38325-Excellent-results-with-Floating-Point-FFT-IFFT-Processing-and-Teensy-3-6?p=119797&viewfull=1#post119797),
// and see here: https://forum.pjrc.com/threads/38325-Excellent-results-with-Floating-Point-FFT-IFFT-Processing-and-Teensy-3-6?p=120177&viewfull=1#post120177 
// . . . converts the audio back to int16_t and gives it back to the headphone output
//
// QUESTIONS TO TEST
// - what is the processor load ?
// - how is the audio quality ?
// - can we do decimation / interpolation in 32-bit floating point in realtime without noticable audio loss ?
//
// MIT licence

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <Metro.h>
#include "font_Arial.h"
#include <ILI9341_t3.h>

#define BACKLIGHT_PIN 0

#define TFT_DC      20
#define TFT_CS      21
#define TFT_RST     32  // 255 = unused. connect to 3.3V
#define TFT_MOSI     7
#define TFT_SCLK    14
#define TFT_MISO    12

ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);

Metro five_sec=Metro(500); // Set up a 0.5 second Metro

// this audio comes from the codec by I2S2

AudioInputI2S            i2s_in; 
//AudioInputI2S            i2s_in_R;           
AudioRecordQueue         Q_in;    

AudioPlayQueue           Q_out; 
AudioOutputI2S           i2s_out;           
AudioConnection          patchCord1(i2s_in, 0, Q_in, 0);

AudioConnection          patchCord3(Q_out, 0, i2s_out, 0);
AudioConnection          patchCord4(Q_out, 0, i2s_out, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=265.212

#define BUFFER_SIZE 256
const int myInput = AUDIO_INPUT_LINEIN;
int idx = 0;
float32_t float_buffer [256];
float32_t float_buffer2 [256];
int16_t int_buffer [256];
// 200 tap FIR filter
arm_fir_instance_f32 FIR_lowpass1;
float32_t FIR_lowpass1_state[200 + BUFFER_SIZE/2];
// 2-pole biquad IIR
arm_biquad_casd_df1_inst_f32 biquad_lowpass1;
float32_t biquad_lowpass1_state[4];
// pass-thru coefficients
float32_t biquad_lowpass1_coeffs[5] = {1,0,0,0,0};
// 10-pole biquad IIR
arm_biquad_casd_df1_inst_f32 biquad_lowpass2;
float32_t biquad_lowpass2_state[20];
// lowpass elliptic 2kHz, IIR biquad 5 stages = 10 pole
// fs 44118Hz
// IIR Filter designer Iowa Hills
// a1 and a2 negated
// order of coefficients: b0, b1, b2, a1, a2

float32_t biquad_lowpass2_coeffs[25] = {
   0.290842061698378285,
   -0.554693042178232898,
   0.290842061698378285,
   1.703434597368454600,
   -0.730425678586978488,

   0.449552103368312861,
   -0.853559775858211078,
   0.449552103368312861,
   1.774090745134004670,
   -0.819635176012419420,

   0.490545749389036945,
   -0.917438292195382643,
   0.490545749389036945,
   1.843867832401652420,
   -0.907521038984343553,

   0.307079065606046031,
   -0.540112802032841843,
   0.307079065606046031,
   1.886261593263963920,
   -0.960306922443214361,

   0.062519618312348771,
   -0.046493567918447658,
   0.062519618312348771,
   1.910667387543194320,
   -0.989213056249444222
};
// FIR 200 taps, Raised Cosine 0.940
// Fc = 3.000kHz, 75dB stopband
// fs 44118Hz
// just to test if this works, 
// a 200 tap FIR is ridicously big and lots of calculation work for the processor
// --> it works !
float32_t FIR_lowpass1_coeffs[200] =
{ 94.97870007611338390E-9,
-1.009924021425136600E-6,
-3.955509751038522200E-6,
-8.167421170036924140E-6,
-12.23385614931934380E-6,
-14.14794389888045070E-6,
-11.83601267362507410E-6,
-3.874707993346245160E-6,
 9.784409190338859470E-6,
 27.31190087730911390E-6,
 44.93736028076724410E-6,
 57.46570395053866780E-6,
 59.37446254570318160E-6,
 46.32396853632215540E-6,
 16.75260847587800940E-6,
-26.87322881082111080E-6,
-77.58971029118251290E-6,
-124.5675198379420860E-6,
-154.9396363165045050E-6,
-156.6193065484440300E-6,
-121.5946328111847800E-6,
-48.95032523151913040E-6,
 53.22076923312532420E-6,
 167.7426457861406560E-6,
 270.5249246949471740E-6,
 334.8987951214899680E-6,
 337.4870793426350130E-6,
 264.4733746227138910E-6,
 116.8225367007839280E-6,
-87.00810636093507360E-6,
-311.9857878058807610E-6,
-511.4939298316222110E-6,
-635.9492759051854590E-6,
-643.6567497319431370E-6,
-511.7505262783362240E-6,
-244.6910091391752930E-6,
 122.0563006080398620E-6,
 524.7970382117916870E-6,
 881.2433438597264510E-6,
 0.001105897264525711,
 0.001128584366403902,
 912.4295155936849820E-6,
 467.1426447303733770E-6,
-146.0617101058849410E-6,
-820.2329826975067140E-6,
-0.001419395225860928,
-0.001804044124385529,
-0.001861021146414526,
-0.001531785067380846,
-832.6535950832496840E-6,
 138.4931805272558170E-6,
 0.001212301918764672,
 0.002174825940792150,
 0.002807530450404730,
 0.002933442142689424,
 0.002460262925219348,
 0.001410796432743954,
-67.40198731776092700E-6,
-0.001717749520116288,
-0.003215444385842726,
-0.004227679123662836,
-0.004482787665470263,
-0.003834751661105775,
-0.002308921085773049,
-117.1613956177577340E-6,
 0.002364261142304478,
 0.004653686825192925,
 0.006251853354833492,
 0.006743484329240796,
 0.005893195184744424,
 0.003715295847640134,
 500.2318515875483630E-6,
-0.003212572302564382,
-0.006715879276971413,
-0.009259324885670151,
-0.010197256146698041,
-0.009132578886926748,
-0.006027440073467311,
-0.001255112944564374,
 0.004423632349767859,
 0.009963382868447519,
 0.014204955965723772,
 0.016088984764790849,
 0.014876395277668464,
 0.010335310034903742,
 0.002856460155015252,
-0.006531028294970668,
-0.016255904142096857,
-0.024406432741385542,
-0.029011490508947704,
-0.028359336686767097,
-0.021302866903510787,
-0.007499202509221305,
 0.012460426961983004,
 0.037058421514990315,
 0.064002438051746699,
 0.090503604392753637,
 0.113642557166307487,
 0.130768970678281582,
 0.139873389683470517,
 0.139873389683470517,
 0.130768970678281582,
 0.113642557166307487,
 0.090503604392753637,
 0.064002438051746699,
 0.037058421514990315,
 0.012460426961983004,
-0.007499202509221305,
-0.021302866903510787,
-0.028359336686767097,
-0.029011490508947704,
-0.024406432741385542,
-0.016255904142096857,
-0.006531028294970668,
 0.002856460155015252,
 0.010335310034903742,
 0.014876395277668464,
 0.016088984764790849,
 0.014204955965723772,
 0.009963382868447519,
 0.004423632349767859,
-0.001255112944564374,
-0.006027440073467311,
-0.009132578886926748,
-0.010197256146698041,
-0.009259324885670151,
-0.006715879276971413,
-0.003212572302564382,
 500.2318515875483630E-6,
 0.003715295847640134,
 0.005893195184744424,
 0.006743484329240796,
 0.006251853354833492,
 0.004653686825192925,
 0.002364261142304478,
-117.1613956177577340E-6,
-0.002308921085773049,
-0.003834751661105775,
-0.004482787665470263,
-0.004227679123662836,
-0.003215444385842726,
-0.001717749520116288,
-67.40198731776092700E-6,
 0.001410796432743954,
 0.002460262925219348,
 0.002933442142689424,
 0.002807530450404730,
 0.002174825940792150,
 0.001212301918764672,
 138.4931805272558170E-6,
-832.6535950832496840E-6,
-0.001531785067380846,
-0.001861021146414526,
-0.001804044124385529,
-0.001419395225860928,
-820.2329826975067140E-6,
-146.0617101058849410E-6,
 467.1426447303733770E-6,
 912.4295155936849820E-6,
 0.001128584366403902,
 0.001105897264525711,
 881.2433438597264510E-6,
 524.7970382117916870E-6,
 122.0563006080398620E-6,
-244.6910091391752930E-6,
-511.7505262783362240E-6,
-643.6567497319431370E-6,
-635.9492759051854590E-6,
-511.4939298316222110E-6,
-311.9857878058807610E-6,
-87.00810636093507360E-6,
 116.8225367007839280E-6,
 264.4733746227138910E-6,
 337.4870793426350130E-6,
 334.8987951214899680E-6,
 270.5249246949471740E-6,
 167.7426457861406560E-6,
 53.22076923312532420E-6,
-48.95032523151913040E-6,
-121.5946328111847800E-6,
-156.6193065484440300E-6,
-154.9396363165045050E-6,
-124.5675198379420860E-6,
-77.58971029118251290E-6,
-26.87322881082111080E-6,
 16.75260847587800940E-6,
 46.32396853632215540E-6,
 59.37446254570318160E-6,
 57.46570395053866780E-6,
 44.93736028076724410E-6,
 27.31190087730911390E-6,
 9.784409190338859470E-6,
-3.874707993346245160E-6,
-11.83601267362507410E-6,
-14.14794389888045070E-6,
-12.23385614931934380E-6,
-8.167421170036924140E-6,
-3.955509751038522200E-6,
-1.009924021425136600E-6,
 94.97870007611338390E-9
};




void setup() {

  // Audio connections require memory. and the record queue
  // uses this memory to buffer incoming audio.
  AudioMemory(200);

  // Enable the audio shield. select input. and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.6);
  sgtl5000_1.adcHighPassFilterDisable(); // kills a lot of digital noise !

  // Initialize the SD card
  SPI.setMOSI(7);
  SPI.setSCK(14);
  if (!(SD.begin(10))) {
    // stop here if no SD card. but print a message
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }

  pinMode( BACKLIGHT_PIN, OUTPUT );
  analogWrite( BACKLIGHT_PIN, 1023 );

  tft.begin();
  tft.setRotation( 3 );
  tft.fillScreen(ILI9341_BLACK);
  tft.setCursor(10, 1);
  tft.setTextSize(2);
  tft.setTextColor(ILI9341_WHITE);
  tft.setFont(Arial_14);
  tft.print("Floating point audio processing");
  
    Q_in.begin();
 
          biquad_lowpass1.numStages = 1;
      for (idx = 0; idx < 4; idx++) 
        {
          biquad_lowpass1_state[idx] = 0;
        }
        biquad_lowpass1.pState = biquad_lowpass1_state;
     biquad_lowpass1.pCoeffs = biquad_lowpass1_coeffs;

      biquad_lowpass2.numStages = 5;
      idx = 0;
      for (idx = 0; idx < 20; idx++) 
        {
          biquad_lowpass2_state[idx] = 0;
        }
        biquad_lowpass2.pState = biquad_lowpass2_state;
     biquad_lowpass2.pCoeffs = biquad_lowpass2_coeffs;
     idx=0; 
     for(idx = 0; idx < (200+BUFFER_SIZE/2); idx++)   // Initialize all filter state variables
    {
        FIR_lowpass1_state [idx]= 0;
    }
    FIR_lowpass1.pState = FIR_lowpass1_state;
    arm_fir_init_f32((arm_fir_instance_f32 *)&FIR_lowpass1, 200,  (float32_t *)&FIR_lowpass1_coeffs[0],  &FIR_lowpass1_state[0],BUFFER_SIZE/2);
}


void loop() {
 
  // begin audio collection

  // packet already collected?
  if (Q_in.available() >= 1)
    {
      byte buffer[BUFFER_SIZE]; 
//      int16_t buffer[128];
      memcpy(buffer,Q_in.readBuffer(), BUFFER_SIZE);
      Q_in.freeBuffer();
//      memcpy(buffer + 256,Q_in.readBuffer(), BUFFER_SIZE/2);
//      Q_in.freeBuffer();

      memcpy(int_buffer,buffer,BUFFER_SIZE); // copy audio block into int_buffer
      // convert to float
     arm_q15_to_float (int_buffer, float_buffer, BUFFER_SIZE/2); // convert int_buffer to float 32bit

/**************************************************************************
 * From here, all the 32 bit float audio processing can start
 * ************************************************************************
 */

      
      // test filter 1 stage
      arm_biquad_cascade_df1_f32 (&biquad_lowpass1, float_buffer,float_buffer2, BUFFER_SIZE/2);

      // test filter 5 stages
      arm_biquad_cascade_df1_f32 (&biquad_lowpass2, float_buffer2,float_buffer, BUFFER_SIZE/2);
      // test FIR filter 200 taps
      arm_fir_f32(&FIR_lowpass1,float_buffer, float_buffer2, BUFFER_SIZE/2);


/**************************************************************************
 * END of 32 bit float audio processing
 * ************************************************************************
 */

      // convert to int
      arm_float_to_q15 (float_buffer2, int_buffer, BUFFER_SIZE/2); 

      byte buffer2[BUFFER_SIZE];
      memcpy(buffer2,int_buffer, BUFFER_SIZE); // copy bytewise into output buffer
      int16_t *p = Q_out.getBuffer();
      for (int i = 0; i < BUFFER_SIZE; i++) 
        {
          *((char *)p + i) = buffer2[i];
        }
      Q_out.playBuffer(); // play it !

 }


          if (five_sec.check() == 1)
    {
      Serial.print("Proc = ");
      Serial.print(AudioProcessorUsage());
      Serial.print(" (");    
      Serial.print(AudioProcessorUsageMax());
      Serial.print("),  Mem = ");
      Serial.print(AudioMemoryUsage());
      Serial.print(" (");    
      Serial.print(AudioMemoryUsageMax());
      Serial.println(")");
      tft.fillRect(100,120,200,140,ILI9341_BLACK);
      tft.setCursor(10, 120);
      tft.setTextSize(2);
      tft.setTextColor(ILI9341_WHITE);
      tft.setFont(Arial_14);
      tft.print ("Proc = ");
      tft.setCursor(100, 120);
      tft.print (AudioProcessorUsage());
      tft.setCursor(180, 120);
      tft.print (AudioProcessorUsageMax());
      tft.setCursor(10, 150);
      tft.print ("Mem  = ");
      tft.setCursor(100, 150);
      tft.print (AudioMemoryUsage());
      tft.setCursor(180, 150);
      tft.print (AudioMemoryUsageMax());
      AudioProcessorUsageMaxReset();
      AudioMemoryUsageMaxReset();
    }
   
}


My questions are:

- is my implementation of the recordqueue and playqueue objects correct and processor-efficient (lines 388-397)? Or do I need to alter something in order to be faster and accurate?

- the code takes one sample at a time (lines 388ff), processes it and plays the sample back (lines 426ff). Is there a way to take several samples, process them and then playback several samples (put them into the playqueue object one after the other)? That would be needed for a 1024point FFT, for example.

- the memory and processor usage of the audio objects only is displayed, so it stays at 0.8% all the time. How can I measure the processor usage for all that floating point processing?

Thanks in advance for your comments!

Frank DD4WH
 
Last edited:
VERSION 2

And here is the same code, now with a decimation-by-4 and an interpolation-by-4 built in (by using the arm-functions).

This means the audio is lowpass-filtered at 4.2kHz to prevent aliases [44118Hz / 4 = 11029Hz sample rate, so 11029/2= 5500Hz is the absolute limit for the filtering, 4.2kHz seemed a reasonable safe limit, I can theoretically reach 80dB suppression at 5.5kHz with the 80 tap FIR filter I designed], downsampled, and then upsampled and filtered again with the same 80-tap-FIR-filter.

First observations:

- I can hear no aliases or other annoying distortion
- audio quality has decreased nonetheless, probably because of all this int-->float-->decimation-->interpolation--> float to int . . . does anybody want to make measurements ?
- even adding the fat 200-tap-FIR-filter is working (eliminate the "//" and try it), so there seems to be enough processor power left for a considerable audio processing [and 200 taps at 11ksps is really a nice brickwall filter, not to speak of the brickwall IIR filters that can be designed for 11ksps . . . ;-)]
- drawback is, of course, the audio bandwidth limit < 4kHz --> for a shortwave/ mediumwave/ ham radio SDR this is essentially enough, because the audio bandwidth for these applications is mostly limited to < 3kHz

Although the audio is not perfect, I am extremely satisfied, because decimation-filtering-interpolation in this quality in realtime was not at all possible in fixed point with the Teensy 3.2 !

Thanks Paul et al. for the Teensy 3.5 and its FPU and the new possibilities!

Questions:

- Any idea how to improve the decimation / interpolation process ? [Maybe higher block size to process at a time ???]
- Any idea how to improve audio quality ?
- Any other comments or suggestions are highly acknowledged!
- feel free to use the code for whatever you like

Frank


Code:
// Use queue for input AND output
// to allow for floating point audio processing
// 
// Frank DD4WH 2016_10_24
//
// this is a setup to test whether we can do real time audio processing 
// with the Teensy 3.5 in floating point math
// it takes the LINE INPUT audio, converts it from int16_t to float32_t, 
// does the audio processing in float32_t with the NEW ARM CMSIS lib (see https://forum.pjrc.com/threads/38325-Excellent-results-with-Floating-Point-FFT-IFFT-Processing-and-Teensy-3-6?p=119797&viewfull=1#post119797),
// and see here: https://forum.pjrc.com/threads/38325-Excellent-results-with-Floating-Point-FFT-IFFT-Processing-and-Teensy-3-6?p=120177&viewfull=1#post120177 
// . . . converts the audio back to int16_t and gives it back to the headphone output
//
// QUESTIONS TO TEST
// - what is the processor load ?
// - how is the audio quality ?
// - can we do decimation / interpolation in 32-bit floating point in realtime without noticable audio loss ?
// --> yes, we can do decimation / interpolation, but there is a loss in audio quality
//
// MIT licence

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <Metro.h>
#include "font_Arial.h"
#include <ILI9341_t3.h>

#define BACKLIGHT_PIN 0

#define TFT_DC      20
#define TFT_CS      21
#define TFT_RST     32  // 255 = unused. connect to 3.3V
#define TFT_MOSI     7
#define TFT_SCLK    14
#define TFT_MISO    12

ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);

Metro five_sec=Metro(500); // Set up a 0.5 second Metro

// this audio comes from the codec by I2S2

AudioInputI2S            i2s_in; 
//AudioInputI2S            i2s_in_R;           
AudioRecordQueue         Q_in;    

AudioPlayQueue           Q_out; 
AudioOutputI2S           i2s_out;           
AudioConnection          patchCord1(i2s_in, 0, Q_in, 0);

AudioConnection          patchCord3(Q_out, 0, i2s_out, 0);
AudioConnection          patchCord4(Q_out, 0, i2s_out, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=265.212

#define BUFFER_SIZE 256
const int myInput = AUDIO_INPUT_LINEIN;
int idx = 0;
float32_t float_buffer [256];
float32_t float_buffer2 [256];
int16_t int_buffer [256];

// decimation with 80 tap FIR lowpass
arm_fir_decimate_instance_f32 FIR_dec1;
float32_t FIR_decim_state[80 + BUFFER_SIZE / 2];

// interpolation with 80 tap FIR lowpass
arm_fir_interpolate_instance_f32 FIR_int1;
//float32_t FIR_interp_state[4 + BUFFER_SIZE / 2];
float32_t FIR_interp_state[80 + BUFFER_SIZE / 2];

// 200 tap FIR filter
arm_fir_instance_f32 FIR_lowpass1;
float32_t FIR_lowpass1_state[200 + BUFFER_SIZE/2];

// 2-pole biquad IIR
arm_biquad_casd_df1_inst_f32 biquad_lowpass1;
float32_t biquad_lowpass1_state[4];

// pass-thru coefficients
float32_t biquad_lowpass1_coeffs[5] = {1,0,0,0,0};

// 10-pole biquad IIR
arm_biquad_casd_df1_inst_f32 biquad_lowpass2;
float32_t biquad_lowpass2_state[20];
// lowpass elliptic 2kHz, IIR biquad 5 stages = 10 pole
// fs 44118Hz
// IIR Filter designer Iowa Hills
// a1 and a2 negated
// order of coefficients: b0, b1, b2, a1, a2
float32_t biquad_lowpass2_coeffs[25] = {
   0.290842061698378285,
   -0.554693042178232898,
   0.290842061698378285,
   1.703434597368454600,
   -0.730425678586978488,

   0.449552103368312861,
   -0.853559775858211078,
   0.449552103368312861,
   1.774090745134004670,
   -0.819635176012419420,

   0.490545749389036945,
   -0.917438292195382643,
   0.490545749389036945,
   1.843867832401652420,
   -0.907521038984343553,

   0.307079065606046031,
   -0.540112802032841843,
   0.307079065606046031,
   1.886261593263963920,
   -0.960306922443214361,

   0.062519618312348771,
   -0.046493567918447658,
   0.062519618312348771,
   1.910667387543194320,
   -0.989213056249444222
};
// FIR 200 taps, Raised Cosine 0.940
// Fc = 3.000kHz, 75dB stopband
// fs 44118Hz
// just to test if this works, 
// a 200 tap FIR is ridicously big and lots of calculation work for the processor
// --> it works !
float32_t FIR_lowpass1_coeffs[200] =
{ 94.97870007611338390E-9,
-1.009924021425136600E-6,
-3.955509751038522200E-6,
-8.167421170036924140E-6,
-12.23385614931934380E-6,
-14.14794389888045070E-6,
-11.83601267362507410E-6,
-3.874707993346245160E-6,
 9.784409190338859470E-6,
 27.31190087730911390E-6,
 44.93736028076724410E-6,
 57.46570395053866780E-6,
 59.37446254570318160E-6,
 46.32396853632215540E-6,
 16.75260847587800940E-6,
-26.87322881082111080E-6,
-77.58971029118251290E-6,
-124.5675198379420860E-6,
-154.9396363165045050E-6,
-156.6193065484440300E-6,
-121.5946328111847800E-6,
-48.95032523151913040E-6,
 53.22076923312532420E-6,
 167.7426457861406560E-6,
 270.5249246949471740E-6,
 334.8987951214899680E-6,
 337.4870793426350130E-6,
 264.4733746227138910E-6,
 116.8225367007839280E-6,
-87.00810636093507360E-6,
-311.9857878058807610E-6,
-511.4939298316222110E-6,
-635.9492759051854590E-6,
-643.6567497319431370E-6,
-511.7505262783362240E-6,
-244.6910091391752930E-6,
 122.0563006080398620E-6,
 524.7970382117916870E-6,
 881.2433438597264510E-6,
 0.001105897264525711,
 0.001128584366403902,
 912.4295155936849820E-6,
 467.1426447303733770E-6,
-146.0617101058849410E-6,
-820.2329826975067140E-6,
-0.001419395225860928,
-0.001804044124385529,
-0.001861021146414526,
-0.001531785067380846,
-832.6535950832496840E-6,
 138.4931805272558170E-6,
 0.001212301918764672,
 0.002174825940792150,
 0.002807530450404730,
 0.002933442142689424,
 0.002460262925219348,
 0.001410796432743954,
-67.40198731776092700E-6,
-0.001717749520116288,
-0.003215444385842726,
-0.004227679123662836,
-0.004482787665470263,
-0.003834751661105775,
-0.002308921085773049,
-117.1613956177577340E-6,
 0.002364261142304478,
 0.004653686825192925,
 0.006251853354833492,
 0.006743484329240796,
 0.005893195184744424,
 0.003715295847640134,
 500.2318515875483630E-6,
-0.003212572302564382,
-0.006715879276971413,
-0.009259324885670151,
-0.010197256146698041,
-0.009132578886926748,
-0.006027440073467311,
-0.001255112944564374,
 0.004423632349767859,
 0.009963382868447519,
 0.014204955965723772,
 0.016088984764790849,
 0.014876395277668464,
 0.010335310034903742,
 0.002856460155015252,
-0.006531028294970668,
-0.016255904142096857,
-0.024406432741385542,
-0.029011490508947704,
-0.028359336686767097,
-0.021302866903510787,
-0.007499202509221305,
 0.012460426961983004,
 0.037058421514990315,
 0.064002438051746699,
 0.090503604392753637,
 0.113642557166307487,
 0.130768970678281582,
 0.139873389683470517,
 0.139873389683470517,
 0.130768970678281582,
 0.113642557166307487,
 0.090503604392753637,
 0.064002438051746699,
 0.037058421514990315,
 0.012460426961983004,
-0.007499202509221305,
-0.021302866903510787,
-0.028359336686767097,
-0.029011490508947704,
-0.024406432741385542,
-0.016255904142096857,
-0.006531028294970668,
 0.002856460155015252,
 0.010335310034903742,
 0.014876395277668464,
 0.016088984764790849,
 0.014204955965723772,
 0.009963382868447519,
 0.004423632349767859,
-0.001255112944564374,
-0.006027440073467311,
-0.009132578886926748,
-0.010197256146698041,
-0.009259324885670151,
-0.006715879276971413,
-0.003212572302564382,
 500.2318515875483630E-6,
 0.003715295847640134,
 0.005893195184744424,
 0.006743484329240796,
 0.006251853354833492,
 0.004653686825192925,
 0.002364261142304478,
-117.1613956177577340E-6,
-0.002308921085773049,
-0.003834751661105775,
-0.004482787665470263,
-0.004227679123662836,
-0.003215444385842726,
-0.001717749520116288,
-67.40198731776092700E-6,
 0.001410796432743954,
 0.002460262925219348,
 0.002933442142689424,
 0.002807530450404730,
 0.002174825940792150,
 0.001212301918764672,
 138.4931805272558170E-6,
-832.6535950832496840E-6,
-0.001531785067380846,
-0.001861021146414526,
-0.001804044124385529,
-0.001419395225860928,
-820.2329826975067140E-6,
-146.0617101058849410E-6,
 467.1426447303733770E-6,
 912.4295155936849820E-6,
 0.001128584366403902,
 0.001105897264525711,
 881.2433438597264510E-6,
 524.7970382117916870E-6,
 122.0563006080398620E-6,
-244.6910091391752930E-6,
-511.7505262783362240E-6,
-643.6567497319431370E-6,
-635.9492759051854590E-6,
-511.4939298316222110E-6,
-311.9857878058807610E-6,
-87.00810636093507360E-6,
 116.8225367007839280E-6,
 264.4733746227138910E-6,
 337.4870793426350130E-6,
 334.8987951214899680E-6,
 270.5249246949471740E-6,
 167.7426457861406560E-6,
 53.22076923312532420E-6,
-48.95032523151913040E-6,
-121.5946328111847800E-6,
-156.6193065484440300E-6,
-154.9396363165045050E-6,
-124.5675198379420860E-6,
-77.58971029118251290E-6,
-26.87322881082111080E-6,
 16.75260847587800940E-6,
 46.32396853632215540E-6,
 59.37446254570318160E-6,
 57.46570395053866780E-6,
 44.93736028076724410E-6,
 27.31190087730911390E-6,
 9.784409190338859470E-6,
-3.874707993346245160E-6,
-11.83601267362507410E-6,
-14.14794389888045070E-6,
-12.23385614931934380E-6,
-8.167421170036924140E-6,
-3.955509751038522200E-6,
-1.009924021425136600E-6,
 94.97870007611338390E-9
};

float32_t FIR_dec1_coeffs[80] = {
-142.9442739751772820E-6,
-252.2211831462206530E-6,
-347.5945623945052030E-6,
-321.0839601413753140E-6,
-97.22225175627986000E-6,
 326.3117239250896090E-6,
 836.1439112274239280E-6,
 0.001209027523486800,
 0.001184222537420025,
 590.9136933192837660E-6,
-520.3527006113926060E-6,
-0.001812698722149656,
-0.002724775642735784,
-0.002673582490820696,
-0.001340728074926084,
 0.001078011666779184,
 0.003797965250984124,
 0.005638404522781618,
 0.005468470132752291,
 0.002761334048239419,
-0.001980549872799683,
-0.007167492106103557,
-0.010557955026781203,
-0.010112507043340404,
-0.004978767523812532,
 0.003808453095602158,
 0.013295208276611466,
 0.019416922705541947,
 0.018489926838076525,
 0.008904119903017756,
-0.007643552646951099,
-0.025966841359731561,
-0.038527883680910445,
-0.037684191047641612,
-0.018373491864758760,
 0.019689814633387617,
 0.071157875324433559,
 0.125978118210888251,
 0.171947745227487625,
 0.198145667117833019,
 0.198145667117833019,
 0.171947745227487625,
 0.125978118210888251,
 0.071157875324433559,
 0.019689814633387617,
-0.018373491864758760,
-0.037684191047641612,
-0.038527883680910445,
-0.025966841359731561,
-0.007643552646951099,
 0.008904119903017756,
 0.018489926838076525,
 0.019416922705541947,
 0.013295208276611466,
 0.003808453095602158,
-0.004978767523812532,
-0.010112507043340404,
-0.010557955026781203,
-0.007167492106103557,
-0.001980549872799683,
 0.002761334048239419,
 0.005468470132752291,
 0.005638404522781618,
 0.003797965250984124,
 0.001078011666779184,
-0.001340728074926084,
-0.002673582490820696,
-0.002724775642735784,
-0.001812698722149656,
-520.3527006113926060E-6,
 590.9136933192837660E-6,
 0.001184222537420025,
 0.001209027523486800,
 836.1439112274239280E-6,
 326.3117239250896090E-6,
-97.22225175627986000E-6,
-321.0839601413753140E-6,
-347.5945623945052030E-6,
-252.2211831462206530E-6,
-142.9442739751772820E-6 };

float32_t FIR_int1_coeffs[4] = {
 0.210904123894329720,
 0.301141108924676826,
 0.301141108924676826,
 0.210904123894329720 };


void setup() {

  // Audio connections require memory. and the record queue
  // uses this memory to buffer incoming audio.
  AudioMemory(200);

  // Enable the audio shield. select input. and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.6);
  sgtl5000_1.adcHighPassFilterDisable(); // kills a lot of digital noise !

  // Initialize the SD card
  SPI.setMOSI(7);
  SPI.setSCK(14);
  if (!(SD.begin(10))) {
    // stop here if no SD card. but print a message
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }

  pinMode( BACKLIGHT_PIN, OUTPUT );
  analogWrite( BACKLIGHT_PIN, 1023 );

  tft.begin();
  tft.setRotation( 3 );
  tft.fillScreen(ILI9341_BLACK);
  tft.setCursor(10, 1);
  tft.setTextSize(2);
  tft.setTextColor(ILI9341_WHITE);
  tft.setFont(Arial_14);
  tft.print("Floating point audio processing");
  
    Q_in.begin();
 
          biquad_lowpass1.numStages = 1;
      for (idx = 0; idx < 4; idx++) 
        {
          biquad_lowpass1_state[idx] = 0;
        }
        biquad_lowpass1.pState = biquad_lowpass1_state;
     biquad_lowpass1.pCoeffs = biquad_lowpass1_coeffs;

      biquad_lowpass2.numStages = 5;
      idx = 0;
      for (idx = 0; idx < 20; idx++) 
        {
          biquad_lowpass2_state[idx] = 0;
        }
        biquad_lowpass2.pState = biquad_lowpass2_state;
     biquad_lowpass2.pCoeffs = biquad_lowpass2_coeffs;
     idx=0; 
     for(idx = 0; idx < (200+BUFFER_SIZE/2); idx++)   // Initialize all filter state variables
    {
        FIR_lowpass1_state [idx]= 0;
    }
    FIR_lowpass1.pState = FIR_lowpass1_state;
    arm_fir_init_f32((arm_fir_instance_f32 *)&FIR_lowpass1, 200, (float32_t *)&FIR_lowpass1_coeffs[0], &FIR_lowpass1_state[0],BUFFER_SIZE/2);
/*
 *  init decimation and interpolation filters
 */
     idx=0; 
     for(idx = 0; idx < (80+BUFFER_SIZE/2); idx++)   // Initialize all filter state variables
    {
        FIR_decim_state [idx]= 0;
    }

     idx=0; 
     for(idx = 0; idx < (80+BUFFER_SIZE/2); idx++)   // Initialize all filter state variables
    {
        FIR_interp_state [idx]= 0;
    }
    
    FIR_dec1.M = 4; // decimation by 4
    FIR_dec1.pCoeffs = FIR_dec1_coeffs;
    FIR_dec1.numTaps = 80;
    FIR_dec1.pState = FIR_decim_state;
    
    FIR_int1.L = 4; // interpolation by 4
    FIR_int1.phaseLength = 20; // 80/4 = 20  // no. of taps / L --> 4 / 4 = 1
    FIR_int1.pCoeffs = FIR_dec1_coeffs; // for testing, same filter for interpolation !
    FIR_int1.pState = FIR_interp_state; 
}


void loop() {
 
  // begin audio collection

  // packet already collected?
  if (Q_in.available() >= 1)
    {
      byte buffer[BUFFER_SIZE]; 
//      int16_t buffer[128];
      memcpy(buffer,Q_in.readBuffer(), BUFFER_SIZE);
      Q_in.freeBuffer();
//      memcpy(buffer + 256,Q_in.readBuffer(), BUFFER_SIZE/2);
//      Q_in.freeBuffer();

      memcpy(int_buffer,buffer,BUFFER_SIZE); // copy audio block into int_buffer
      // convert to float
     arm_q15_to_float (int_buffer, float_buffer, BUFFER_SIZE/2); // convert int_buffer to float 32bit

/**************************************************************************
 * From here, all the 32 bit float audio processing can start
 * ************************************************************************
 */

  // decimation by 4 --> 44118 / 4 = 11029.5 sps, means that a 4.8kHz decimation filter is fine [should have -80dB at 5.5kHz]
      //  decimation filter FIR 80 taps, lowpass 4.2kHz, Parks McClellan, Window off, Transiton width 0.1, 80dB stopband att
      arm_fir_decimate_f32(&FIR_dec1, float_buffer, float_buffer2, BUFFER_SIZE / 2);
      
      // test filter 1 stage
//      arm_biquad_cascade_df1_f32 (&biquad_lowpass1, float_buffer2,float_buffer, BUFFER_SIZE / 2 / 4);

      // test filter 5 stages
//      arm_biquad_cascade_df1_f32 (&biquad_lowpass2, float_buffer2,float_buffer, BUFFER_SIZE / 2 / 4);
      // test FIR filter 200 taps
//      arm_fir_f32(&FIR_lowpass1,float_buffer, float_buffer2, BUFFER_SIZE / 2 / 4);

  // interpolation by 4
      arm_fir_interpolate_f32(&FIR_int1, float_buffer2, float_buffer, BUFFER_SIZE / 2 / 4);
      
/**************************************************************************
 * END of 32 bit float audio processing
 * ************************************************************************
 */

      // convert to int
      arm_float_to_q15 (float_buffer, int_buffer, BUFFER_SIZE/2); 

      byte buffer2[BUFFER_SIZE];
      memcpy(buffer2,int_buffer, BUFFER_SIZE); // copy bytewise into output buffer
      int16_t *p = Q_out.getBuffer();
      for (int i = 0; i < BUFFER_SIZE; i++) 
        {
          *((char *)p + i) = buffer2[i];
        }
      Q_out.playBuffer(); // play it !

 }


          if (five_sec.check() == 1)
    {
      Serial.print("Proc = ");
      Serial.print(AudioProcessorUsage());
      Serial.print(" (");    
      Serial.print(AudioProcessorUsageMax());
      Serial.print("),  Mem = ");
      Serial.print(AudioMemoryUsage());
      Serial.print(" (");    
      Serial.print(AudioMemoryUsageMax());
      Serial.println(")");
      tft.fillRect(100,120,200,140,ILI9341_BLACK);
      tft.setCursor(10, 120);
      tft.setTextSize(2);
      tft.setTextColor(ILI9341_WHITE);
      tft.setFont(Arial_14);
      tft.print ("Proc = ");
      tft.setCursor(100, 120);
      tft.print (AudioProcessorUsage());
      tft.setCursor(180, 120);
      tft.print (AudioProcessorUsageMax());
      tft.setCursor(10, 150);
      tft.print ("Mem  = ");
      tft.setCursor(100, 150);
      tft.print (AudioMemoryUsage());
      tft.setCursor(180, 150);
      tft.print (AudioMemoryUsageMax());
      AudioProcessorUsageMaxReset();
      AudioMemoryUsageMaxReset();
    }
   
}
 
Last edited:
Hi Frank,
I used your second sketch (with decimation/interpolation) with a new version of CMSIS (V5) that I've just installed and am playing with.
I haven't got a display wired to it yet and I'm not sure that the one I have will work with it.
Anyway, here's the processor load on a T3.6 at 180MHz with input from the microphone.
Code:
Proc = 0.63 (0.68),  Mem = 9 (10)

and this is the compiler info:
Code:
Sketch uses 57,976 bytes (5%) of program storage space. Maximum is 1,048,576 bytes.
Global variables use 67,444 bytes (25%) of dynamic memory, leaving 194,700 bytes for local variables. Maximum is 262,144 bytes.

The output audio seems to have a slight echo on it.
I will try to have a closer look at it in the next few days (but I'm a beginner when it comes to DSP/FFT etc.).

BTW. The V5 CMSIS does a complex f32 64-point FFT in 28 microseconds and 1024 points in 693 microseconds. It also seems to use a lot less memory than the previous version.

Pete
 
Anyway, here's the processor load on a T3.6 at 180MHz with input from the microphone.
Code:
Proc = 0.63 (0.68),  Mem = 9 (10)

and this is the compiler info:
Code:
Sketch uses 57,976 bytes (5%) of program storage space. Maximum is 1,048,576 bytes.
Global variables use 67,444 bytes (25%) of dynamic memory, leaving 194,700 bytes for local variables. Maximum is 262,144 bytes.

The output audio seems to have a slight echo on it.
I will try to have a closer look at it in the next few days (but I'm a beginner when it comes to DSP/FFT etc.).

BTW. The V5 CMSIS does a complex f32 64-point FFT in 28 microseconds and 1024 points in 693 microseconds. It also seems to use a lot less memory than the previous version.

Pete

Hi Pete,

excellent to hear that you got V5 CMSIS working! my results are with V4.5 and a Teensy 3.5.

The processor load is the load of the audio processing in the audio library, it does not show the load by the floating point processing, is that right?

It remains the same (about 0.8%) on my Teensy 3.5, even if I switch in the big 200 tap FIR filter, so it does not seem to show the real load . . .

Yes, the audio is not perfect, my suspicion is that it has to do with the way I implemented the queue objects and all the copying buffers, I am really not familiar with that. Would be good if you have a look at that.

If you use the second sketch, the audio is supposed to be lowpass-filtered at 4.2kHz by the decimation filter (built into the arm_decimate function) and in the arm_interpolate, even if you have no additional filter in the floating point audio path. But if there are echoes when using the sketch without an additional filter, then I certainly did something wrong in the sketch, presumably with the copying/conversion float-int and vice versa etc.

One problem I noticed is that when you run the sketch for a longer time (10 min or longer), sometimes there is an audio spike and after that the memory use of the lib goes up by 50, so it ends up at using memory 56. What could that be? Maybe there should be some safe program lines to ask whether the *.readBuffer has really ended up with reading a buffer and the buffers are all emptied by *.freeBuffer afterwards?

Frank
 
BTW. The V5 CMSIS does a complex f32 64-point FFT in 28 microseconds and 1024 points in 693 microseconds. It also seems to use a lot less memory than the previous version.
They moved the tables from include to .c files. I guess, this way the linker can remove unused tables more easily.
 
VERSION 3

And here is the Stereo version, new features:

- Stereo, uses two input queue objects and two output queue objects
- simpler pointer use with the output queue objects
- uses two audio blocks with 128 samples for each channel at a time
- measures the time used for one process

AUDIO PATH IMPLEMENTED:
LINE INPUT - queue record object - memory buffer - variable int16_t buffer - conversion to float32_t buffer -
- lowpass FIR filter 80 taps - downsampling by 4 - AUDIO FILTERING in floating point [at the moment, nothing here] -
- upsampling by 4 - lowpass FIR filter 80 taps - conversion to int16_t buffer - set pointer to output buffer - play output buffer by play-queue-object

This audio path needs about 1061 µsec for put-through of 2 (stereo!) x 2 blocks of 128 samples in real-time [including the really tight 80 tap decimation and interpolation filtering and all the down- and upsampling and copying].
If you switch in the very processor-intensive FIR filters (2 x 200 taps), the whole audio path needs 1919 µsec.
That means, for realtime audio processing, there is still plenty of processor overhead left: 256/44118Hz --> 5800µsec

ISSUES to solve:
- account for wrong buffer reads: free the buffers when they have not been read properly, otherwise the audio memory fills up after a few tens of minutes
- build-in option to choose number of audio blocks to buffer (in order to implement FFT: 8 buffers with 128 samples for a 1024-point FFT, 16 buffers for a 2048-point FFT)
- eliminate annoying high pitch sound that is audible when no sound is fed into the line input (not very loud but nonetheless present and annoying)

Any suggestions welcome!

Frank

Code:
// Use queue for input AND output
// to allow for floating point audio processing
// STEREO VERSION, NEW way of output buffer read/send, uses several buffers now
// Frank DD4WH 2016_10_27
//
// this is a setup to test whether we can do real time audio processing 
// with the Teensy 3.5 in floating point math
// it takes the LINE INPUT audio, converts it from int16_t to float32_t, 
// does the audio processing in float32_t with the NEW ARM CMSIS lib (see https://forum.pjrc.com/threads/38325-Excellent-results-with-Floating-Point-FFT-IFFT-Processing-and-Teensy-3-6?p=119797&viewfull=1#post119797),
// and see here: https://forum.pjrc.com/threads/38325-Excellent-results-with-Floating-Point-FFT-IFFT-Processing-and-Teensy-3-6?p=120177&viewfull=1#post120177 
// . . . converts the audio back to int16_t and gives it back to the headphone output
//
// audio path in this version:
//
// LINE INPUT - queue record object - memory buffer - variable int16_t buffer - conversion to float32_t buffer - 
// - lowpass FIR filter 80 taps - downsampling by 4 - AUDIO FILTERING in floating point [at the moment, nothing here] - 
// - upsampling by 4 - lowpass FIR filter 80 taps - conversion to int16_t buffer - set pointer to output buffer - play output buffer by play-queue-object
//
//
// TODO:
// - account for wrong buffer reads: free the buffers when they have not been read properly, otherwise the audio memory fills up after a few tens of minutes
// - build-in option to choose number of buffers (in order to implement FFT: 8 buffers with 128 samples for a 1024-point FFT, 16 buffers for a 2048-point FFT)
// - eliminate annoying high pitch sound that is audible when no sound is fed into the line input (not very loud but nonetheless present and annoying)
//
// MIT licence

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <Metro.h>
#include "font_Arial.h"
#include <ILI9341_t3.h>

#define BACKLIGHT_PIN 0

#define TFT_DC      20
#define TFT_CS      21
#define TFT_RST     32  // 255 = unused. connect to 3.3V
#define TFT_MOSI     7
#define TFT_SCLK    14
#define TFT_MISO    12

ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);

Metro five_sec=Metro(500); // Set up a 0.5 second Metro

// this audio comes from the codec by I2S2

AudioInputI2S            i2s_in; 
           
AudioRecordQueue         Q_in_L;    
AudioRecordQueue         Q_in_R;    

AudioPlayQueue           Q_out_L; 
AudioPlayQueue           Q_out_R; 
AudioOutputI2S           i2s_out;           
AudioConnection          patchCord1(i2s_in, 0, Q_in_L, 0);
AudioConnection          patchCord2(i2s_in, 1, Q_in_R, 0);

AudioConnection          patchCord3(Q_out_L, 0, i2s_out, 1);
AudioConnection          patchCord4(Q_out_R, 0, i2s_out, 0);
AudioControlSGTL5000     sgtl5000_1;     //xy=265.212

bool FIR_200 = false;
bool biquad_1 = false;
bool biquad_5 = false;
bool dec_int = true;

int idx_t = 0;
int64_t sum;
float32_t mean;
int n_L;
int n_R;

//#define BUFFER_SIZE 256
#define BUFFER_SIZE 512
const int myInput = AUDIO_INPUT_LINEIN;
int idx = 0;
float32_t float_buffer_L [BUFFER_SIZE / 2];
float32_t float_buffer_L_2 [BUFFER_SIZE / 2];
float32_t float_buffer_R [BUFFER_SIZE / 2];
float32_t float_buffer_R_2 [BUFFER_SIZE / 2];
int16_t int_buffer_L [BUFFER_SIZE / 2];
int16_t int_buffer_R [BUFFER_SIZE / 2];

// decimation with 80 tap FIR lowpass
arm_fir_decimate_instance_f32 FIR_dec1_L;
float32_t FIR_decim_state_L [80 + BUFFER_SIZE / 2];
arm_fir_decimate_instance_f32 FIR_dec1_R;
float32_t FIR_decim_state_R [80 + BUFFER_SIZE / 2];

// interpolation with 80 tap FIR lowpass
arm_fir_interpolate_instance_f32 FIR_int1_L;
//float32_t FIR_interp_state_L [4 + BUFFER_SIZE / 2];
float32_t FIR_interp_state_L [80 + BUFFER_SIZE / 2];
arm_fir_interpolate_instance_f32 FIR_int1_R;
//float32_t FIR_interp_state[4 + BUFFER_SIZE / 2];
float32_t FIR_interp_state_R [80 + BUFFER_SIZE / 2];

// 200 tap FIR filter
arm_fir_instance_f32 FIR_lowpass1_L;
float32_t FIR_lowpass1_state_L [200 + BUFFER_SIZE/2];
arm_fir_instance_f32 FIR_lowpass1_R;
float32_t FIR_lowpass1_state_R [200 + BUFFER_SIZE/2];

// 2-pole biquad IIR
arm_biquad_casd_df1_inst_f32 biquad_lowpass1_L;
float32_t biquad_lowpass1_state_L[4];
// 2-pole biquad IIR
arm_biquad_casd_df1_inst_f32 biquad_lowpass1_R;
float32_t biquad_lowpass1_state_R[4];

// pass-thru coefficients
float32_t biquad_lowpass1_coeffs[5] = {1,0,0,0,0};

// 10-pole biquad IIR
arm_biquad_casd_df1_inst_f32 biquad_lowpass2;
float32_t biquad_lowpass2_state[20];
// lowpass elliptic 2kHz, IIR biquad 5 stages = 10 pole
// fs 44118Hz
// IIR Filter designer Iowa Hills
// a1 and a2 negated
// order of coefficients: b0, b1, b2, a1, a2
float32_t biquad_lowpass2_coeffs[25] = {
   0.290842061698378285,
   -0.554693042178232898,
   0.290842061698378285,
   1.703434597368454600,
   -0.730425678586978488,

   0.449552103368312861,
   -0.853559775858211078,
   0.449552103368312861,
   1.774090745134004670,
   -0.819635176012419420,

   0.490545749389036945,
   -0.917438292195382643,
   0.490545749389036945,
   1.843867832401652420,
   -0.907521038984343553,

   0.307079065606046031,
   -0.540112802032841843,
   0.307079065606046031,
   1.886261593263963920,
   -0.960306922443214361,

   0.062519618312348771,
   -0.046493567918447658,
   0.062519618312348771,
   1.910667387543194320,
   -0.989213056249444222
};
// FIR 200 taps, Raised Cosine 0.940
// Fc = 3.000kHz, 75dB stopband
// fs 44118Hz
// just to test if this works, 
// a 200 tap FIR is ridicously big and lots of calculation work for the processor
// --> it works !
float32_t FIR_lowpass1_coeffs[200] =
{ 94.97870007611338390E-9,
-1.009924021425136600E-6,
-3.955509751038522200E-6,
-8.167421170036924140E-6,
-12.23385614931934380E-6,
-14.14794389888045070E-6,
-11.83601267362507410E-6,
-3.874707993346245160E-6,
 9.784409190338859470E-6,
 27.31190087730911390E-6,
 44.93736028076724410E-6,
 57.46570395053866780E-6,
 59.37446254570318160E-6,
 46.32396853632215540E-6,
 16.75260847587800940E-6,
-26.87322881082111080E-6,
-77.58971029118251290E-6,
-124.5675198379420860E-6,
-154.9396363165045050E-6,
-156.6193065484440300E-6,
-121.5946328111847800E-6,
-48.95032523151913040E-6,
 53.22076923312532420E-6,
 167.7426457861406560E-6,
 270.5249246949471740E-6,
 334.8987951214899680E-6,
 337.4870793426350130E-6,
 264.4733746227138910E-6,
 116.8225367007839280E-6,
-87.00810636093507360E-6,
-311.9857878058807610E-6,
-511.4939298316222110E-6,
-635.9492759051854590E-6,
-643.6567497319431370E-6,
-511.7505262783362240E-6,
-244.6910091391752930E-6,
 122.0563006080398620E-6,
 524.7970382117916870E-6,
 881.2433438597264510E-6,
 0.001105897264525711,
 0.001128584366403902,
 912.4295155936849820E-6,
 467.1426447303733770E-6,
-146.0617101058849410E-6,
-820.2329826975067140E-6,
-0.001419395225860928,
-0.001804044124385529,
-0.001861021146414526,
-0.001531785067380846,
-832.6535950832496840E-6,
 138.4931805272558170E-6,
 0.001212301918764672,
 0.002174825940792150,
 0.002807530450404730,
 0.002933442142689424,
 0.002460262925219348,
 0.001410796432743954,
-67.40198731776092700E-6,
-0.001717749520116288,
-0.003215444385842726,
-0.004227679123662836,
-0.004482787665470263,
-0.003834751661105775,
-0.002308921085773049,
-117.1613956177577340E-6,
 0.002364261142304478,
 0.004653686825192925,
 0.006251853354833492,
 0.006743484329240796,
 0.005893195184744424,
 0.003715295847640134,
 500.2318515875483630E-6,
-0.003212572302564382,
-0.006715879276971413,
-0.009259324885670151,
-0.010197256146698041,
-0.009132578886926748,
-0.006027440073467311,
-0.001255112944564374,
 0.004423632349767859,
 0.009963382868447519,
 0.014204955965723772,
 0.016088984764790849,
 0.014876395277668464,
 0.010335310034903742,
 0.002856460155015252,
-0.006531028294970668,
-0.016255904142096857,
-0.024406432741385542,
-0.029011490508947704,
-0.028359336686767097,
-0.021302866903510787,
-0.007499202509221305,
 0.012460426961983004,
 0.037058421514990315,
 0.064002438051746699,
 0.090503604392753637,
 0.113642557166307487,
 0.130768970678281582,
 0.139873389683470517,
 0.139873389683470517,
 0.130768970678281582,
 0.113642557166307487,
 0.090503604392753637,
 0.064002438051746699,
 0.037058421514990315,
 0.012460426961983004,
-0.007499202509221305,
-0.021302866903510787,
-0.028359336686767097,
-0.029011490508947704,
-0.024406432741385542,
-0.016255904142096857,
-0.006531028294970668,
 0.002856460155015252,
 0.010335310034903742,
 0.014876395277668464,
 0.016088984764790849,
 0.014204955965723772,
 0.009963382868447519,
 0.004423632349767859,
-0.001255112944564374,
-0.006027440073467311,
-0.009132578886926748,
-0.010197256146698041,
-0.009259324885670151,
-0.006715879276971413,
-0.003212572302564382,
 500.2318515875483630E-6,
 0.003715295847640134,
 0.005893195184744424,
 0.006743484329240796,
 0.006251853354833492,
 0.004653686825192925,
 0.002364261142304478,
-117.1613956177577340E-6,
-0.002308921085773049,
-0.003834751661105775,
-0.004482787665470263,
-0.004227679123662836,
-0.003215444385842726,
-0.001717749520116288,
-67.40198731776092700E-6,
 0.001410796432743954,
 0.002460262925219348,
 0.002933442142689424,
 0.002807530450404730,
 0.002174825940792150,
 0.001212301918764672,
 138.4931805272558170E-6,
-832.6535950832496840E-6,
-0.001531785067380846,
-0.001861021146414526,
-0.001804044124385529,
-0.001419395225860928,
-820.2329826975067140E-6,
-146.0617101058849410E-6,
 467.1426447303733770E-6,
 912.4295155936849820E-6,
 0.001128584366403902,
 0.001105897264525711,
 881.2433438597264510E-6,
 524.7970382117916870E-6,
 122.0563006080398620E-6,
-244.6910091391752930E-6,
-511.7505262783362240E-6,
-643.6567497319431370E-6,
-635.9492759051854590E-6,
-511.4939298316222110E-6,
-311.9857878058807610E-6,
-87.00810636093507360E-6,
 116.8225367007839280E-6,
 264.4733746227138910E-6,
 337.4870793426350130E-6,
 334.8987951214899680E-6,
 270.5249246949471740E-6,
 167.7426457861406560E-6,
 53.22076923312532420E-6,
-48.95032523151913040E-6,
-121.5946328111847800E-6,
-156.6193065484440300E-6,
-154.9396363165045050E-6,
-124.5675198379420860E-6,
-77.58971029118251290E-6,
-26.87322881082111080E-6,
 16.75260847587800940E-6,
 46.32396853632215540E-6,
 59.37446254570318160E-6,
 57.46570395053866780E-6,
 44.93736028076724410E-6,
 27.31190087730911390E-6,
 9.784409190338859470E-6,
-3.874707993346245160E-6,
-11.83601267362507410E-6,
-14.14794389888045070E-6,
-12.23385614931934380E-6,
-8.167421170036924140E-6,
-3.955509751038522200E-6,
-1.009924021425136600E-6,
 94.97870007611338390E-9
};

float32_t FIR_dec1_coeffs[80] = {
-142.9442739751772820E-6,
-252.2211831462206530E-6,
-347.5945623945052030E-6,
-321.0839601413753140E-6,
-97.22225175627986000E-6,
 326.3117239250896090E-6,
 836.1439112274239280E-6,
 0.001209027523486800,
 0.001184222537420025,
 590.9136933192837660E-6,
-520.3527006113926060E-6,
-0.001812698722149656,
-0.002724775642735784,
-0.002673582490820696,
-0.001340728074926084,
 0.001078011666779184,
 0.003797965250984124,
 0.005638404522781618,
 0.005468470132752291,
 0.002761334048239419,
-0.001980549872799683,
-0.007167492106103557,
-0.010557955026781203,
-0.010112507043340404,
-0.004978767523812532,
 0.003808453095602158,
 0.013295208276611466,
 0.019416922705541947,
 0.018489926838076525,
 0.008904119903017756,
-0.007643552646951099,
-0.025966841359731561,
-0.038527883680910445,
-0.037684191047641612,
-0.018373491864758760,
 0.019689814633387617,
 0.071157875324433559,
 0.125978118210888251,
 0.171947745227487625,
 0.198145667117833019,
 0.198145667117833019,
 0.171947745227487625,
 0.125978118210888251,
 0.071157875324433559,
 0.019689814633387617,
-0.018373491864758760,
-0.037684191047641612,
-0.038527883680910445,
-0.025966841359731561,
-0.007643552646951099,
 0.008904119903017756,
 0.018489926838076525,
 0.019416922705541947,
 0.013295208276611466,
 0.003808453095602158,
-0.004978767523812532,
-0.010112507043340404,
-0.010557955026781203,
-0.007167492106103557,
-0.001980549872799683,
 0.002761334048239419,
 0.005468470132752291,
 0.005638404522781618,
 0.003797965250984124,
 0.001078011666779184,
-0.001340728074926084,
-0.002673582490820696,
-0.002724775642735784,
-0.001812698722149656,
-520.3527006113926060E-6,
 590.9136933192837660E-6,
 0.001184222537420025,
 0.001209027523486800,
 836.1439112274239280E-6,
 326.3117239250896090E-6,
-97.22225175627986000E-6,
-321.0839601413753140E-6,
-347.5945623945052030E-6,
-252.2211831462206530E-6,
-142.9442739751772820E-6 };

float32_t FIR_int1_coeffs[4] = {
 0.210904123894329720,
 0.301141108924676826,
 0.301141108924676826,
 0.210904123894329720 };


void setup() {
  Serial.begin(115200);

  // Audio connections require memory. and the record queue
  // uses this memory to buffer incoming audio.
  AudioMemory(200);

  // Enable the audio shield. select input. and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.9);
  sgtl5000_1.adcHighPassFilterDisable(); // kills a lot of digital noise !

/*  // Initialize the SD card
  SPI.setMOSI(7);
  SPI.setSCK(14);
  if (!(SD.begin(10))) {
    // stop here if no SD card. but print a message
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }
*/
  pinMode( BACKLIGHT_PIN, OUTPUT );
  analogWrite( BACKLIGHT_PIN, 1023 );

  tft.begin();
  tft.setRotation( 3 );
  tft.fillScreen(ILI9341_BLACK);
  tft.setCursor(10, 1);
  tft.setTextSize(2);
  tft.setTextColor(ILI9341_WHITE);
  tft.setFont(Arial_14);
  tft.print("Floating point audio processing");

        if(FIR_200)
      {
          tft.setCursor(10, 40);
          tft.print ("FIR 200 taps");
      }  
      if(biquad_1)
      {
          tft.setCursor(180, 40);
          tft.print ("biquad 1 stage");
      }  
      if(biquad_5)
      {
          tft.setCursor(180, 70);
          tft.print ("biquad 5 stages");
      }  
      if(dec_int)
      {
          tft.setCursor(10, 70);
          tft.print ("Decim./Interp.");
      }  
  
      for (idx = 0; idx < 4; idx++) 
        {
     biquad_lowpass1_state_L[idx] = 0;
     biquad_lowpass1_state_R[idx] = 0;
        }
     biquad_lowpass1_L.pState = biquad_lowpass1_state_L;
     biquad_lowpass1_L.pCoeffs = biquad_lowpass1_coeffs;
     biquad_lowpass1_R.pState = biquad_lowpass1_state_R;
     biquad_lowpass1_R.pCoeffs = biquad_lowpass1_coeffs;
     biquad_lowpass1_L.numStages = 1;
     biquad_lowpass1_R.numStages = 1;

      biquad_lowpass2.numStages = 5;
      idx = 0;
      for (idx = 0; idx < 20; idx++) 
        {
          biquad_lowpass2_state[idx] = 0;
        }
        biquad_lowpass2.pState = biquad_lowpass2_state;
     biquad_lowpass2.pCoeffs = biquad_lowpass2_coeffs;

     idx=0; 
     for(idx = 0; idx < (200+BUFFER_SIZE/2); idx++)   // Initialize all filter state variables
    {
        FIR_lowpass1_state_L [idx]= 0;
        FIR_lowpass1_state_R [idx]= 0;
    }
    FIR_lowpass1_L.pState = FIR_lowpass1_state_L;
    arm_fir_init_f32((arm_fir_instance_f32 *)&FIR_lowpass1_L, 200, (float32_t *)&FIR_lowpass1_coeffs[0], &FIR_lowpass1_state_L[0],BUFFER_SIZE/2);
    FIR_lowpass1_R.pState = FIR_lowpass1_state_R;
    arm_fir_init_f32((arm_fir_instance_f32 *)&FIR_lowpass1_R, 200, (float32_t *)&FIR_lowpass1_coeffs[0], &FIR_lowpass1_state_R[0],BUFFER_SIZE/2);
/*
 *  init decimation and interpolation filters
 */
     idx=0; 
     for(idx = 0; idx < (80+BUFFER_SIZE/2); idx++)   // Initialize all filter state variables
    {
        FIR_decim_state_L [idx]= 0;
        FIR_decim_state_R [idx]= 0;
    }

     idx=0; 
     for(idx = 0; idx < (80+BUFFER_SIZE/2); idx++)   // Initialize all filter state variables
    {
        FIR_interp_state_L [idx]= 0;
        FIR_interp_state_R [idx]= 0;
    }
    
    FIR_dec1_L.M = 4; // decimation by 4
    FIR_dec1_L.pCoeffs = FIR_dec1_coeffs;
    FIR_dec1_L.numTaps = 80;
    FIR_dec1_L.pState = FIR_decim_state_L;

    FIR_int1_L.L = 4; // interpolation by 4
    FIR_int1_L.phaseLength = 20; // 80/4 = 20  // no. of taps / L --> 4 / 4 = 1
    FIR_int1_L.pCoeffs = FIR_dec1_coeffs; // for testing, same filter for interpolation !
    FIR_int1_L.pState = FIR_interp_state_L; 

    FIR_dec1_R.M = 4; // decimation by 4
    FIR_dec1_R.pCoeffs = FIR_dec1_coeffs;
    FIR_dec1_R.numTaps = 80;
    FIR_dec1_R.pState = FIR_decim_state_R;
    
    FIR_int1_R.L = 4; // interpolation by 4
    FIR_int1_R.phaseLength = 20; // 80/4 = 20  // no. of taps / L --> 4 / 4 = 1
    FIR_int1_R.pCoeffs = FIR_dec1_coeffs; // for testing, same filter for interpolation !
    FIR_int1_R.pState = FIR_interp_state_R; 
    delay(10);
    Q_in_L.begin();
    Q_in_R.begin();    
}


void loop() {
 
  // begin audio collection

    elapsedMicros usec = 0;
  // packets already collected?
/*      n_L = Q_in_L.available();
       if (n_L > 4)
     {
       for (idx = 0; idx < n_L - 1; idx++ ) 
       {
             Q_in_L.freeBuffer();
       }
     }
      n_R = Q_in_R.available();
       if (n_R > 4)
     {
       for (idx = 0; idx < n_R - 1; idx++ )
       {
             Q_in_R.freeBuffer();
       }
     }   */
//      Serial.print("L=");Serial.print(Q_in_L.available());Serial.print(" ");
//      Serial.print("R=");Serial.print(Q_in_R.available());Serial.print(" ");
  if (Q_in_L.available() >= 4 && Q_in_R.available() >= 4)
    {
      byte buffer_L [BUFFER_SIZE]; 
      memcpy(buffer_L,Q_in_L.readBuffer(), BUFFER_SIZE/2);
      Q_in_L.freeBuffer();
      memcpy(buffer_L + (BUFFER_SIZE / 2),Q_in_L.readBuffer(), BUFFER_SIZE / 2);
      Q_in_L.freeBuffer();

      byte buffer_R [BUFFER_SIZE]; 
      memcpy(buffer_R,Q_in_R.readBuffer(), BUFFER_SIZE/2);
      Q_in_R.freeBuffer();
      memcpy(buffer_R + (BUFFER_SIZE / 2),Q_in_R.readBuffer(), BUFFER_SIZE / 2);
      Q_in_R.freeBuffer();


      memcpy(int_buffer_L,buffer_L,BUFFER_SIZE); // copy 2 audio blocks into int_buffer
      memcpy(int_buffer_R,buffer_R,BUFFER_SIZE); // copy 2 audio blocks into int_buffer

      // convert to float
     arm_q15_to_float (int_buffer_L, float_buffer_L, BUFFER_SIZE/2); // convert int_buffer to float 32bit
     arm_q15_to_float (int_buffer_R, float_buffer_R, BUFFER_SIZE/2); // convert int_buffer to float 32bit

/**************************************************************************
 * From here, all the 32 bit float audio processing can start
 * ************************************************************************
 */

  // decimation by 4 --> 44118 / 4 = 11029.5 sps, means that a 4.2kHz decimation filter is fine [should have -80dB at 5.5kHz]
      //  decimation filter FIR 80 taps, lowpass 4.2kHz, Parks McClellan, Window off, Transiton width 0.1, 80dB stopband attenuation
      arm_fir_decimate_f32(&FIR_dec1_L, float_buffer_L, float_buffer_L_2, BUFFER_SIZE / 2);
      arm_fir_decimate_f32(&FIR_dec1_R, float_buffer_R, float_buffer_R_2, BUFFER_SIZE / 2);
      
      // test filter 1 stage
//      arm_biquad_cascade_df1_f32 (&biquad_lowpass1_L, float_buffer_L_2,float_buffer_L, BUFFER_SIZE / 2 / 4);
//      arm_biquad_cascade_df1_f32 (&biquad_lowpass1_R, float_buffer_R_2,float_buffer_R, BUFFER_SIZE / 2 / 4);

      // test filter 5 stages
//      arm_biquad_cascade_df1_f32 (&biquad_lowpass2, float_buffer,float_buffer2, BUFFER_SIZE / 2 / 4);
//      arm_biquad_cascade_df1_f32 (&biquad_lowpass2, float_buffer,float_buffer2, BUFFER_SIZE / 2 / 4);

      // test FIR filter 200 taps
//     arm_fir_f32(&FIR_lowpass1_L,float_buffer_L, float_buffer_L_2, BUFFER_SIZE / 2 / 4);
//     arm_fir_f32(&FIR_lowpass1_R,float_buffer_R, float_buffer_R_2, BUFFER_SIZE / 2 / 4);

  // interpolation by 4
      arm_fir_interpolate_f32(&FIR_int1_L, float_buffer_L_2, float_buffer_L, BUFFER_SIZE / 2 / 4);
      arm_fir_interpolate_f32(&FIR_int1_R, float_buffer_R_2, float_buffer_R, BUFFER_SIZE / 2 / 4);

      // scaling after interpolation ? 4x louder audio?
      
/**************************************************************************
 * END of 32 bit float audio processing
 * ************************************************************************
 */
      // assign pointer for output buffer
      int16_t *p = Q_out_L.getBuffer();
      // convert to int and fill the buffer with converted values
      arm_float_to_q15 (float_buffer_L, p, BUFFER_SIZE/4); 
      // assign pointer for output buffer
      int16_t *q = Q_out_R.getBuffer();
      // convert to int and fill the buffer with converted values
      arm_float_to_q15 (float_buffer_R, q, BUFFER_SIZE/4); 

      Q_out_L.playBuffer(); // play it !
      Q_out_R.playBuffer(); // play it !

      // assign pointer for output buffer
      int16_t *p2 = Q_out_L.getBuffer();
      // convert to int and fill the buffer with converted values
      arm_float_to_q15 (float_buffer_L + BUFFER_SIZE/4, p2, BUFFER_SIZE/4); 
      // assign pointer for output buffer
      int16_t *q2 = Q_out_R.getBuffer();
      // convert to int and fill the buffer with converted values
      arm_float_to_q15 (float_buffer_R + BUFFER_SIZE/4, q2, BUFFER_SIZE/4); 

      Q_out_L.playBuffer(); // play it !
      Q_out_R.playBuffer(); // play it !


/**********************************************************************************
 *  PRINT ROUTINE FOR ELAPSED MICROSECONDS
 **********************************************************************************/
      sum = sum + usec;
      idx_t++;
      if (idx_t > 1000) {
          tft.fillRect(10,200,90,20,ILI9341_BLACK);   
          tft.setCursor(10, 200);
          mean = sum / idx_t;
          tft.print (mean);
          tft.setCursor(100, 200);
          tft.print ("usec for 2 stereo blocks");
 
          idx_t = 0;
          sum = 0;
         
      }

     }
/**********************************************************************************
 *  PRINT ROUTINE FOR AUDIO LIBRARY PROCESSOR AND MEMORY USAGE
 **********************************************************************************/
 /*         if (five_sec.check() == 1)
    {
      Serial.print("Proc = ");
      Serial.print(AudioProcessorUsage());
      Serial.print(" (");    
      Serial.print(AudioProcessorUsageMax());
      Serial.print("),  Mem = ");
      Serial.print(AudioMemoryUsage());
      Serial.print(" (");    
      Serial.print(AudioMemoryUsageMax());
      Serial.println(")");
      tft.fillRect(100,120,200,80,ILI9341_BLACK);
      tft.setCursor(10, 120);
      tft.setTextSize(2);
      tft.setTextColor(ILI9341_WHITE);
      tft.setFont(Arial_14);
      tft.print ("Proc = ");
      tft.setCursor(100, 120);
      tft.print (AudioProcessorUsage());
      tft.setCursor(180, 120);
      tft.print (AudioProcessorUsageMax());
      tft.setCursor(10, 150);
      tft.print ("Mem  = ");
      tft.setCursor(100, 150);
      tft.print (AudioMemoryUsage());
      tft.setCursor(180, 150);
      tft.print (AudioMemoryUsageMax());
      
      AudioProcessorUsageMaxReset();
      AudioMemoryUsageMaxReset();
    }
*/
   
}
 
Last edited:
Frank: I have been playing with the first version of your decimator/interpolator code.
I've modified it so that it only does a decimation followed by an interpolation. I haven't got the lowpass filtering working properly yet (there's some high-pitched anti-aliasing). If I can get that to work I'll add a CW filter between the decimation and interpolation. I also changed it to use the microphone input instead of line-in (just for testing) and, as you'll see below, I've rearranged everything and split the coefficient arrays off into a separate .h file.

Pete
P.S. I didn't try this with CMSIS V5 but once I get it working I will.

dd4wh_4a.ino
Code:
// From: https://forum.pjrc.com/threads/38667

/*
TODO:
  put the coefficent arrays first and then declare const integers
  which define the sizes of the coefficient arrays.
  
_3
  - Try using my coefficients and add a CW FIR filter between the
    decimation and interpolation.
Sketch uses 41,264 bytes (3%) of program storage space. Maximum is 1,048,576 bytes.
Global variables use 36,460 bytes (13%) of dynamic memory, leaving 225,684 bytes for local variables. Maximum is 262,144 bytes.
Proc = 0.64 (0.66),  Mem = 4 (5)

_2
  - Remove the display


_1
Change to mic + compile with CMSIS V5
Has an echo
Sketch uses 57,976 bytes (5%) of program storage space. Maximum is 1,048,576 bytes.
Global variables use 67,444 bytes (25%) of dynamic memory, leaving 194,700 bytes for local variables. Maximum is 262,144 bytes.
*/

// Use queue for input AND output
// to allow for floating point audio processing
//
// Frank DD4WH 2016_10_24
// Heavily modified by Pete (El_Supremo) 2016_10_27
//        using T3.6 and microphone input

// this is a setup to test whether we can do real time audio processing
// with the Teensy 3.5 in floating point math
// it takes the LINE INPUT audio, converts it from int16_t to float32_t,
// does the audio processing in float32_t with the NEW ARM CMSIS lib (see https://forum.pjrc.com/threads/38325-Excellent-results-with-Floating-Point-FFT-IFFT-Processing-and-Teensy-3-6?p=119797&viewfull=1#post119797),
// and see here: https://forum.pjrc.com/threads/38325-Excellent-results-with-Floating-Point-FFT-IFFT-Processing-and-Teensy-3-6?p=120177&viewfull=1#post120177
// . . . converts the audio back to int16_t and gives it back to the headphone output
//
// QUESTIONS TO TEST
// - what is the processor load ?
// - how is the audio quality ?
// - can we do decimation / interpolation in 32-bit floating point in realtime without noticable audio loss ?
// --> yes, we can do decimation / interpolation, but there is a loss in audio quality
//
// MIT licence

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <Metro.h>

Metro N_sec = Metro(2000); // Set up a 2 second Metro

// Change to microphone
//const int myInput = AUDIO_INPUT_LINEIN;
const int myInput = AUDIO_INPUT_MIC;

// this audio comes from the codec by I2S2
AudioInputI2S            i2s_in;
//AudioInputI2S            i2s_in_R;
AudioRecordQueue         Q_in;

AudioPlayQueue           Q_out;
AudioOutputI2S           i2s_out;
AudioConnection          patchCord1(i2s_in, 0, Q_in, 0);

AudioConnection          patchCord3(Q_out, 0, i2s_out, 0);
AudioConnection          patchCord4(Q_out, 0, i2s_out, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=265.212

#include "FIR_coeffs.h"

// determine the number of decimation and interpolation coefficients
// from the size of their arrays
const int N_DEC_COEFFS = sizeof(FIR_dec1_coeffs)/sizeof(FIR_dec1_coeffs[0]);
const int N_INT_COEFFS = sizeof(FIR_int1_coeffs)/sizeof(FIR_int1_coeffs[0]);

// We're only processing one buffer at a time so the
// number of samples is fixed at 128
#define N_SAMPLES 128

float32_t float_buffer [N_SAMPLES];
// This holds the samples output by the decimation
// and therefore there are a factor of 4 fewer samples
float32_t float_buffer2 [N_SAMPLES/4];

arm_fir_decimate_instance_f32 FIR_dec1;
float32_t FIR_decim_state[N_SAMPLES + N_DEC_COEFFS - 1];

arm_fir_interpolate_instance_f32 FIR_int1;
// Only /4 as many samples as the decimator
float32_t FIR_interp_state[N_SAMPLES/4 + N_INT_COEFFS - 1];

void setup()
{
  Serial.begin(9600);
  while(!Serial);
  delay(1000);
  
  // Audio connections require memory. and the record queue
  // uses this memory to buffer incoming audio.
// was 200
  AudioMemory(100);

  // Enable the audio shield. select input. and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.6);
  sgtl5000_1.adcHighPassFilterDisable(); // kills a lot of digital noise !
  sgtl5000_1.micGain(0);

  Q_in.begin();
  
//>>> NOTE that the order of the first three arguments to decimate_init and interpolate_init
//    are NOT the same!
  if(arm_fir_decimate_init_f32(&FIR_dec1, N_DEC_COEFFS, 4, FIR_dec1_coeffs, FIR_decim_state,N_SAMPLES)) {
    Serial.println("Init of decimation failed");
    while(1);
  }

  if(arm_fir_interpolate_init_f32(&FIR_int1, 4, N_INT_COEFFS, FIR_int1_coeffs, FIR_interp_state, N_SAMPLES/4)) {
    Serial.println("Init of interpolation failed");
    while(1);  
  }
Serial.println("Start");
}

int16_t *sp;
void loop()
{
  // Is a packet available. If so, process it
  if (Q_in.available() >= 1) {
    // Get address of packet and convert from q15 to f32
    sp = Q_in.readBuffer();
    arm_q15_to_float(sp, float_buffer, N_SAMPLES);
    // We're done with the buffer. Give it back to the audio system.
    Q_in.freeBuffer();

    /**************************************************************************
       From here, all the 32 bit float audio processing can start
     **************************************************************************/

    // decimation by 4
    arm_fir_decimate_f32(&FIR_dec1, float_buffer, float_buffer2, N_SAMPLES);

    // interpolation by 4 - number of input samples is /4
    arm_fir_interpolate_f32(&FIR_int1, float_buffer2, float_buffer, N_SAMPLES/4);

    /**************************************************************************
       END of 32 bit float audio processing
     * ************************************************************************
    */

    sp = Q_out.getBuffer();
    // convert to int
    arm_float_to_q15 (float_buffer, sp, N_SAMPLES);
    Q_out.playBuffer(); // play it !

  }

  if (N_sec.check() == 1) {
    Serial.print("Proc = ");
    Serial.print(AudioProcessorUsage());
    Serial.print(" (");
    Serial.print(AudioProcessorUsageMax());
    Serial.print("),  Mem = ");
    Serial.print(AudioMemoryUsage());
    Serial.print(" (");
    Serial.print(AudioMemoryUsageMax());
    Serial.println(")");
//    AudioProcessorUsageMaxReset();
//    AudioMemoryUsageMaxReset();
  }
}


FIR_coeffs.h
Code:
//#define USE_DD4WH

// decimation coefficients
float32_t FIR_dec1_coeffs[] = {
#ifdef USE_DD4WH
  -142.9442739751772820E-6,
  -252.2211831462206530E-6,
  -347.5945623945052030E-6,
  -321.0839601413753140E-6,
  -97.22225175627986000E-6,
  326.3117239250896090E-6,
  836.1439112274239280E-6,
  0.001209027523486800,
  0.001184222537420025,
  590.9136933192837660E-6,
  -520.3527006113926060E-6,
  -0.001812698722149656,
  -0.002724775642735784,
  -0.002673582490820696,
  -0.001340728074926084,
  0.001078011666779184,
  0.003797965250984124,
  0.005638404522781618,
  0.005468470132752291,
  0.002761334048239419,
  -0.001980549872799683,
  -0.007167492106103557,
  -0.010557955026781203,
  -0.010112507043340404,
  -0.004978767523812532,
  0.003808453095602158,
  0.013295208276611466,
  0.019416922705541947,
  0.018489926838076525,
  0.008904119903017756,
  -0.007643552646951099,
  -0.025966841359731561,
  -0.038527883680910445,
  -0.037684191047641612,
  -0.018373491864758760,
  0.019689814633387617,
  0.071157875324433559,
  0.125978118210888251,
  0.171947745227487625,
  0.198145667117833019,
  0.198145667117833019,
  0.171947745227487625,
  0.125978118210888251,
  0.071157875324433559,
  0.019689814633387617,
  -0.018373491864758760,
  -0.037684191047641612,
  -0.038527883680910445,
  -0.025966841359731561,
  -0.007643552646951099,
  0.008904119903017756,
  0.018489926838076525,
  0.019416922705541947,
  0.013295208276611466,
  0.003808453095602158,
  -0.004978767523812532,
  -0.010112507043340404,
  -0.010557955026781203,
  -0.007167492106103557,
  -0.001980549872799683,
  0.002761334048239419,
  0.005468470132752291,
  0.005638404522781618,
  0.003797965250984124,
  0.001078011666779184,
  -0.001340728074926084,
  -0.002673582490820696,
  -0.002724775642735784,
  -0.001812698722149656,
  -520.3527006113926060E-6,
  590.9136933192837660E-6,
  0.001184222537420025,
  0.001209027523486800,
  836.1439112274239280E-6,
  326.3117239250896090E-6,
  -97.22225175627986000E-6,
  -321.0839601413753140E-6,
  -347.5945623945052030E-6,
  -252.2211831462206530E-6,
  -142.9442739751772820E-6
#else
/*
64 1 2 16 32768 44118
0 4000 5300 
 1.0000  0.0000 
 1.0000 70.0000
*/
  -1.62688363E-003,
  -2.60336756E-003,
  -3.66260311E-003,
  -3.88319786E-003,
  -2.62065979E-003,
   4.36556642E-004,
   4.99021064E-003,
   1.00372298E-002,
   1.40484488E-002,
   1.54327504E-002,
   1.31643257E-002,
   7.33634898E-003,
  -6.09267539E-004,
  -8.14725300E-003,
  -1.24386445E-002,
  -1.13961147E-002,
  -4.66397017E-003,
   5.90073200E-003,
   1.65355555E-002,
   2.27020396E-002,
   2.07468164E-002,
   9.54186551E-003,
  -8.52450076E-003,
  -2.76807400E-002,
  -4.01561820E-002,
  -3.85000641E-002,
  -1.81398293E-002,
   2.06779213E-002,
   7.22874014E-002,
   1.26667870E-001,
   1.71951365E-001,
   1.97655999E-001,

   1.97655999E-001,
   1.71951365E-001,
   1.26667870E-001,
   7.22874014E-002,
   2.06779213E-002,
  -1.81398293E-002,
  -3.85000641E-002,
  -4.01561820E-002,
  -2.76807400E-002,
  -8.52450076E-003,
   9.54186551E-003,
   2.07468164E-002,
   2.27020396E-002,
   1.65355555E-002,
   5.90073200E-003,
  -4.66397017E-003,
  -1.13961147E-002,
  -1.24386445E-002,
  -8.14725300E-003,
  -6.09267539E-004,
   7.33634898E-003,
   1.31643257E-002,
   1.54327504E-002,
   1.40484488E-002,
   1.00372298E-002,
   4.99021064E-003,
   4.36556642E-004,
  -2.62065979E-003,
  -3.88319786E-003,
  -3.66260311E-003,
  -2.60336756E-003,
  -1.62688363E-003,
#endif
};

// interpolation coefficients
// This was designed for a sampling rate of 44100
float32_t FIR_int1_coeffs[] = {
#ifdef USE_DD4WH
  -142.9442739751772820E-6,
  -252.2211831462206530E-6,
  -347.5945623945052030E-6,
  -321.0839601413753140E-6,
  -97.22225175627986000E-6,
  326.3117239250896090E-6,
  836.1439112274239280E-6,
  0.001209027523486800,
  0.001184222537420025,
  590.9136933192837660E-6,
  -520.3527006113926060E-6,
  -0.001812698722149656,
  -0.002724775642735784,
  -0.002673582490820696,
  -0.001340728074926084,
  0.001078011666779184,
  0.003797965250984124,
  0.005638404522781618,
  0.005468470132752291,
  0.002761334048239419,
  -0.001980549872799683,
  -0.007167492106103557,
  -0.010557955026781203,
  -0.010112507043340404,
  -0.004978767523812532,
  0.003808453095602158,
  0.013295208276611466,
  0.019416922705541947,
  0.018489926838076525,
  0.008904119903017756,
  -0.007643552646951099,
  -0.025966841359731561,
  -0.038527883680910445,
  -0.037684191047641612,
  -0.018373491864758760,
  0.019689814633387617,
  0.071157875324433559,
  0.125978118210888251,
  0.171947745227487625,
  0.198145667117833019,
  0.198145667117833019,
  0.171947745227487625,
  0.125978118210888251,
  0.071157875324433559,
  0.019689814633387617,
  -0.018373491864758760,
  -0.037684191047641612,
  -0.038527883680910445,
  -0.025966841359731561,
  -0.007643552646951099,
  0.008904119903017756,
  0.018489926838076525,
  0.019416922705541947,
  0.013295208276611466,
  0.003808453095602158,
  -0.004978767523812532,
  -0.010112507043340404,
  -0.010557955026781203,
  -0.007167492106103557,
  -0.001980549872799683,
  0.002761334048239419,
  0.005468470132752291,
  0.005638404522781618,
  0.003797965250984124,
  0.001078011666779184,
  -0.001340728074926084,
  -0.002673582490820696,
  -0.002724775642735784,
  -0.001812698722149656,
  -520.3527006113926060E-6,
  590.9136933192837660E-6,
  0.001184222537420025,
  0.001209027523486800,
  836.1439112274239280E-6,
  326.3117239250896090E-6,
  -97.22225175627986000E-6,
  -321.0839601413753140E-6,
  -347.5945623945052030E-6,
  -252.2211831462206530E-6,
  -142.9442739751772820E-6
#else
/*
64 1 2 16 32768 44100
0 15000 19000 
 1.0000  0.0000 
 1.0000 70.0000 
*/
  -7.04096445E-005,
   1.83765601E-005,
   8.78440742E-005,
  -2.19035028E-004,
   2.31899947E-004,
   9.35889384E-006,
  -4.75456496E-004,
   8.78030165E-004,
  -7.73302874E-004,
  -1.14640273E-004,
   1.52918203E-003,
  -2.58675355E-003,
   2.18263276E-003,
   1.97042759E-004,
  -3.74280309E-003,
   6.30834876E-003,
  -5.45196137E-003,
   1.74265265E-004,
   7.68977024E-003,
  -1.36062459E-002,
   1.24956237E-002,
  -2.11466279E-003,
  -1.42503069E-002,
   2.78393914E-002,
  -2.80878009E-002,
   8.71027673E-003,
   2.67507863E-002,
  -6.30793523E-002,
   7.64551678E-002,
  -3.93223723E-002,
  -8.86910599E-002,
   5.90983553E-001,

   5.90983553E-001,
  -8.86910599E-002,
  -3.93223723E-002,
   7.64551678E-002,
  -6.30793523E-002,
   2.67507863E-002,
   8.71027673E-003,
  -2.80878009E-002,
   2.78393914E-002,
  -1.42503069E-002,
  -2.11466279E-003,
   1.24956237E-002,
  -1.36062459E-002,
   7.68977024E-003,
   1.74265265E-004,
  -5.45196137E-003,
   6.30834876E-003,
  -3.74280309E-003,
   1.97042759E-004,
   2.18263276E-003,
  -2.58675355E-003,
   1.52918203E-003,
  -1.14640273E-004,
  -7.73302874E-004,
   8.78030165E-004,
  -4.75456496E-004,
   9.35889384E-006,
   2.31899947E-004,
  -2.19035028E-004,
   8.78440742E-005,
   1.83765601E-005,
  -7.04096445E-005,
#endif
};
 
Last edited:
Hi Pete,
excellent! You are right, it is very good to use a short and simple script and optimize that until it runs perfectly and then proceed with further gimmicks ;-). So will I.

I used your script and it works perfectly (when using DD4WH coeffs in the FIR_coeffs.h), even when I use the Line Input instead of MIC input. I also learned from your script that it is much easier to use the queue! You eliminated a lot of stupid buffer copying that I built in, thanks for that!

1. the alias high frequencies are due to unsufficient low pass filtering with your coefficients, is my first guess. You state (in FIR_coeffs.h) you used a filter with a stop frequency of 4kHz, which has a stopband attenuation of 70dB at 5.3kHz? If so, that filter would be very good and sufficient. However, the coeffs you use have only 9 digits, so probably they do not have the appropriate precision for float32 processing?! I am only guessing here. The Iowa Hills FIR Filter designer is perfect for calculating the coeffs in the desired precision. I can also calculate the desired coeffs for you with that program, just give me the specifications you would need.

2. For your CW filter, also consider an IIR filter. It is much more efficient than FIR filters, especially if you use it with decimation! I can also help along with that and there is also the Iowa Hills IIR Filter Designer. Try an 8 or even 12 pole elliptic response filter (but remember to design it for the lower decimated sample rate of 44118/4), thats a brickwall for CW ;-). Again, tell me your specifications and I will calculate the coeffs for you. It is a bit tricky to get the coeffs in the right order and with the right signs, however --> [b0, b1, b2, -a1, -a2]. It took me some weeks to figure that out ;-).

3. I cannot hear the echo effect any more. Has it vanished too in your setup?

4. I cannot hear the annoying high pitch noise that is present in my versions V2 and V3, so this problem is gone too, very nice! EDIT: it is still there . . . does seem to be a hardware problem

5. Could you explain to me, whether the processor load and the memory use is really a representation of the actual use of the floating point calculations in the processor? Or does it reflect the use of the audio processing within the audio library only? If I switch in a really large FIR filter, the processor load displayed does not change and stays below 1% ;-).
EDIT: I used elapsedMicros to measure the time for one block processing in Stereo (queue-buffering-float conversion-decimation-interpolation-int-conversion-queue playing), it went down from 1061µsec to now 535µsec! That´s probably due to the new way of filling / reading the queue buffers thanks to Pete!

I will work further on that, but thats only possible on the weekend. My plans for the weekend:

6. stereo version (because I need I&Q processing for my SDR)

7. multiple block processing at a time??? Is that wise? Or should I stick with one block audio processing and use a routine that accumulates several blocks for an FFT in a separate buffer and sends that for an FFT processing when the buffer is filled?

Thanks again and happy audio processing in float . . . this offers so many new possibilities.

Frank
 
Last edited:
Hi Frank,

1. ... However, the coeffs you use have only 9 digits,
The FPU precision is only about 7 digits so specifying 9 digits for coefficients won't help because they'll be truncated to fit the FPU precision.

2. For your CW filter, also consider an IIR filter. It is much more efficient than FIR filters, especially if you use it with decimation!
The reason that I use FIR is (a) I don't know what I'm doing :) and (b) many years ago I converted the Fortran code in the Parks-McLellan paper to C for my Amiga and then added a graphical interface. I have since ported it to my PC.
I'll look at using IIR.

I can also help along with that
I'll take you up on that if I get that far.

3. I cannot hear the echo effect any more. Has it vanished too in your setup?
I just tried your original trick of also using the decimation filter as the interpolation filter. It gets rid of the high-pitched aliasing (which suggests that my interpolation filter needs redesigning) but it adds echo - weird. Can't figure that out.


5. Could you explain to me, whether the processor load and the memory use is really a representation of the actual use of the floating point calculations in the processor?
I haven't looked into what that actually measures but it appears that it isn't affected by FPU load.


7. multiple block processing at a time??? Is that wise? Or should I stick with one block audio processing and use a routine that accumulates several blocks for an FFT in a separate buffer and sends that for an FFT processing when the buffer is filled?
The state buffer in the decimators and interpolators allows it to use single audio blocks at a time. I don't think there's any advantage to do several at a time. I think I would accumulate the buffers and do the FFT when they are full.

This weekend I hope to sort out the interpolation filter and then throw in the CW filter.

Pete
 
I've got a CW filter working - this one is about 300Hz wide and is centred at 800Hz. I've used the decimator filter as the interpolator filter as well since it removes the annoying aliasing/hiss and this time it doesn't seem to have added any echo.
If pin 32 is grounded it switches the CW filter in.

Pete


dd4wh_4bl.ino
Code:
// From: https://forum.pjrc.com/threads/38667

/*
_4bl
  - add the CW filter and use Linein
  - use the decimator filter for interpolator filter because it gets rid of the
    aliasing hiss.
  - use pin 32 to switch the CW filter in (GND) or out (default HIGH).
  
_4b try using the decimator filter for interpolator as well

_4a posted to pjrc
_3
  - Try using my coefficients and add a CW FIR filter between the
    decimation and interpolation.
Sketch uses 41,264 bytes (3%) of program storage space. Maximum is 1,048,576 bytes.
Global variables use 36,460 bytes (13%) of dynamic memory, leaving 225,684 bytes for local variables. Maximum is 262,144 bytes.
Proc = 0.64 (0.66),  Mem = 4 (5)

_2
  - Remove the display


_1
Change to mic + compile with CMSIS V5
Has an echo
Sketch uses 57,976 bytes (5%) of program storage space. Maximum is 1,048,576 bytes.
Global variables use 67,444 bytes (25%) of dynamic memory, leaving 194,700 bytes for local variables. Maximum is 262,144 bytes.
*/

// Use queue for input AND output
// to allow for floating point audio processing
//
// Frank DD4WH 2016_10_24
// Heavily modified by Pete (El_Supremo) 2016_10_27
//        using T3.6 and microphone input

// this is a setup to test whether we can do real time audio processing
// with the Teensy 3.5 in floating point math
// it takes the LINE INPUT audio, converts it from int16_t to float32_t,
// does the audio processing in float32_t with the NEW ARM CMSIS lib (see https://forum.pjrc.com/threads/38325-Excellent-results-with-Floating-Point-FFT-IFFT-Processing-and-Teensy-3-6?p=119797&viewfull=1#post119797),
// and see here: https://forum.pjrc.com/threads/38325-Excellent-results-with-Floating-Point-FFT-IFFT-Processing-and-Teensy-3-6?p=120177&viewfull=1#post120177
// . . . converts the audio back to int16_t and gives it back to the headphone output
//
// QUESTIONS TO TEST
// - what is the processor load ?
// - how is the audio quality ?
// - can we do decimation / interpolation in 32-bit floating point in realtime without noticable audio loss ?
// --> yes, we can do decimation / interpolation, but there is a loss in audio quality
//
// MIT licence

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <Metro.h>

Metro N_sec = Metro(2000); // Set up a 2 second Metro

// filter is in when this pin is grounded
#define BYPASS_PIN 32

// Change to microphone
const int myInput = AUDIO_INPUT_LINEIN;
//const int myInput = AUDIO_INPUT_MIC;

// this audio comes from the codec by I2S2
AudioInputI2S            i2s_in;
//AudioInputI2S            i2s_in_R;
AudioRecordQueue         Q_in;

AudioPlayQueue           Q_out;
AudioOutputI2S           i2s_out;
AudioConnection          patchCord1(i2s_in, 0, Q_in, 0);

AudioConnection          patchCord3(Q_out, 0, i2s_out, 0);
AudioConnection          patchCord4(Q_out, 0, i2s_out, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=265.212

#include "FIR_coeffs.h"

// determine the number of decimation, interpolation and CW filter coefficients
// from the size of their arrays
const int N_DEC_COEFFS = sizeof(FIR_dec1_coeffs)/sizeof(FIR_dec1_coeffs[0]);
const int N_INT_COEFFS = sizeof(FIR_int1_coeffs)/sizeof(FIR_int1_coeffs[0]);
const int N_FILTER_COEFFS = sizeof(myfir_cw)/sizeof(myfir_cw[0]);

// We're only processing one buffer at a time so the
// number of samples is fixed at 128
#define N_SAMPLES 128

float32_t float_buffer [N_SAMPLES];
// This holds the samples output by the decimation
// and therefore there are a factor of 4 fewer samples
float32_t float_buffer2 [N_SAMPLES/4];
// An extra buffer is needed when the CW filter is in use
float32_t FIR_out_buffer [N_SAMPLES/4];

// decimator instance and state buffer
arm_fir_decimate_instance_f32 FIR_dec1;
float32_t FIR_decim_state[N_SAMPLES + N_DEC_COEFFS - 1];

// interpolator instance and state buffer
arm_fir_interpolate_instance_f32 FIR_int1;
// Only /4 as many samples as the decimator
float32_t FIR_interp_state[N_SAMPLES/4 + N_INT_COEFFS - 1];

// CW filter instance and state buffer
// The CW filter runs at a sample rate of 11025Hz and has one quarter
// as many samples as the are input to the decimator.
float32_t filter_State[N_SAMPLES/4 + N_FILTER_COEFFS - 1];
arm_fir_instance_f32 FILTER_inst;

void setup()
{
  Serial.begin(9600);
  while(!Serial);
  delay(1000);

  pinMode(BYPASS_PIN,INPUT_PULLUP);
  
  // Audio connections require memory. and the record queue
  // uses this memory to buffer incoming audio.
  // Probably serious overkill but on a T3.5 or T3.6 we can
  // afford to waste some memory!
  AudioMemory(100);

  // Enable the audio shield, select input, and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.6);
//  sgtl5000_1.adcHighPassFilterDisable(); // kills a lot of digital noise !
  sgtl5000_1.micGain(0);

  // Start the queue input
  Q_in.begin();
  
//>>> NOTE that the order of the first three arguments to decimate_init and interpolate_init
//    are NOT the same!
  if(arm_fir_decimate_init_f32(&FIR_dec1, N_DEC_COEFFS, 4, FIR_dec1_coeffs, FIR_decim_state,N_SAMPLES)) {
    Serial.println("Init of decimation failed");
    while(1);
  }

  if(arm_fir_interpolate_init_f32(&FIR_int1, 4, N_DEC_COEFFS, FIR_dec1_coeffs, FIR_interp_state, N_SAMPLES/4)) {
    Serial.println("Init of interpolation failed");
    while(1);  
  }

  // Initialize the CW FIR filter - this doesn't return an error/success value
  // so we can only hope that everything was specified correctly
  arm_fir_init_f32(&FILTER_inst,N_FILTER_COEFFS,myfir_cw,filter_State,N_SAMPLES/4);

Serial.println("Start - CW filter");
}

int16_t *sp;
void loop()
{
  // Is a packet available. If so, process it
  if (Q_in.available() >= 1) {
    // Get address of packet and convert from q15 to f32
    sp = Q_in.readBuffer();
    arm_q15_to_float(sp, float_buffer, N_SAMPLES);
    // We're done with the buffer. Give it back to the audio system.
    Q_in.freeBuffer();

    /**************************************************************************
       From here, all the 32 bit float audio processing can start
     **************************************************************************/

    // decimation by 4
    arm_fir_decimate_f32(&FIR_dec1, float_buffer, float_buffer2, N_SAMPLES);

    if(digitalRead(BYPASS_PIN) == 0) {
      // CW FIR filter
      arm_fir_f32(&FILTER_inst,float_buffer2,FIR_out_buffer,N_SAMPLES/4);
      // interpolation by 4 - number of input samples is /4
      arm_fir_interpolate_f32(&FIR_int1, FIR_out_buffer, float_buffer, N_SAMPLES/4);
    } else {
      // bypass
      // interpolation by 4 - number of input samples is /4
      arm_fir_interpolate_f32(&FIR_int1, float_buffer2, float_buffer, N_SAMPLES/4);
    }

    /**************************************************************************
       END of 32 bit float audio processing
     **************************************************************************/

    sp = Q_out.getBuffer();
    // convert float samples back to int
    arm_float_to_q15 (float_buffer, sp, N_SAMPLES);
    Q_out.playBuffer(); // play it !
  }

  if (N_sec.check() == 1) {
    Serial.print("Proc = ");
    Serial.print(AudioProcessorUsage());
    Serial.print(" (");
    Serial.print(AudioProcessorUsageMax());
    Serial.print("),  Mem = ");
    Serial.print(AudioMemoryUsage());
    Serial.print(" (");
    Serial.print(AudioMemoryUsageMax());
    Serial.println(")");
//    AudioProcessorUsageMaxReset();
//    AudioMemoryUsageMaxReset();
  }
}

FIR_coeffs.h
Code:
// decimation coefficients
float32_t FIR_dec1_coeffs[] = {
/*
64 1 2 16 32768 44118
0 4000 5300 
 1.0000  0.0000 
 1.0000 70.0000
*/
  -1.62688363E-003,
  -2.60336756E-003,
  -3.66260311E-003,
  -3.88319786E-003,
  -2.62065979E-003,
   4.36556642E-004,
   4.99021064E-003,
   1.00372298E-002,
   1.40484488E-002,
   1.54327504E-002,
   1.31643257E-002,
   7.33634898E-003,
  -6.09267539E-004,
  -8.14725300E-003,
  -1.24386445E-002,
  -1.13961147E-002,
  -4.66397017E-003,
   5.90073200E-003,
   1.65355555E-002,
   2.27020396E-002,
   2.07468164E-002,
   9.54186551E-003,
  -8.52450076E-003,
  -2.76807400E-002,
  -4.01561820E-002,
  -3.85000641E-002,
  -1.81398293E-002,
   2.06779213E-002,
   7.22874014E-002,
   1.26667870E-001,
   1.71951365E-001,
   1.97655999E-001,

   1.97655999E-001,
   1.71951365E-001,
   1.26667870E-001,
   7.22874014E-002,
   2.06779213E-002,
  -1.81398293E-002,
  -3.85000641E-002,
  -4.01561820E-002,
  -2.76807400E-002,
  -8.52450076E-003,
   9.54186551E-003,
   2.07468164E-002,
   2.27020396E-002,
   1.65355555E-002,
   5.90073200E-003,
  -4.66397017E-003,
  -1.13961147E-002,
  -1.24386445E-002,
  -8.14725300E-003,
  -6.09267539E-004,
   7.33634898E-003,
   1.31643257E-002,
   1.54327504E-002,
   1.40484488E-002,
   1.00372298E-002,
   4.99021064E-003,
   4.36556642E-004,
  -2.62065979E-003,
  -3.88319786E-003,
  -3.66260311E-003,
  -2.60336756E-003,
  -1.62688363E-003,
};

// interpolation coefficients
// This was designed for a sampling rate of 44100
// This isn't used at the moment.
float32_t FIR_int1_coeffs[] = {
/*
64 1 2 16 32768 44100
0 8500 10000 
 1.0000  0.0000 
 1.0000 70.0000 
*/
  -8.39491258E-004,
  -5.08224392E-003,
  -1.01931419E-002,
  -1.13547251E-002,
  -4.96060176E-003,
   4.34425166E-003,
   7.01243041E-003,
   4.11980835E-005,
  -7.39835844E-003,
  -4.54951323E-003,
   5.72759197E-003,
   8.91475699E-003,
  -1.23004286E-003,
  -1.14703218E-002,
  -5.75599684E-003,
   1.02601892E-002,
   1.34974779E-002,
  -3.98019901E-003,
  -1.90852599E-002,
  -7.19853771E-003,
   1.91629912E-002,
   2.12858289E-002,
  -1.06757915E-002,
  -3.43712829E-002,
  -8.38935573E-003,
   4.09737420E-002,
   3.95269281E-002,
  -3.31193147E-002,
  -8.76521424E-002,
  -9.07447467E-003,
   1.98298702E-001,
   3.81462423E-001,

   3.81462423E-001,
   1.98298702E-001,
  -9.07447467E-003,
  -8.76521424E-002,
  -3.31193147E-002,
   3.95269281E-002,
   4.09737420E-002,
  -8.38935573E-003,
  -3.43712829E-002,
  -1.06757915E-002,
   2.12858289E-002,
   1.91629912E-002,
  -7.19853771E-003,
  -1.90852599E-002,
  -3.98019901E-003,
   1.34974779E-002,
   1.02601892E-002,
  -5.75599684E-003,
  -1.14703218E-002,
  -1.23004286E-003,
   8.91475699E-003,
   5.72759197E-003,
  -4.54951323E-003,
  -7.39835844E-003,
   4.11980835E-005,
   7.01243041E-003,
   4.34425166E-003,
  -4.96060176E-003,
  -1.13547251E-002,
  -1.01931419E-002,
  -5.08224392E-003,
  -8.39491258E-004,
};

/*
100 1 3 16 32768 11025
0 385 690 910 1175 
 0.0000  1.0000  0.0000 
60.0000  1.0000 60.0000 
*/
float myfir_cw[100] = {
   6.71257165E-004,
   6.54215790E-004,
   6.79307985E-004,
   4.13877948E-004,
  -1.94667592E-004,
  -1.08734763E-003,
  -2.07227298E-003,
  -2.85627942E-003,
  -3.11333676E-003,
  -2.59452497E-003,
  -1.23248004E-003,
   7.89569578E-004,
   3.04474545E-003,
   4.95211818E-003,
   5.93895549E-003,
   5.62762958E-003,
   3.98882064E-003,
   1.39737571E-003,
  -1.44252083E-003,
  -3.69928970E-003,
  -4.68851332E-003,
  -4.13462141E-003,
  -2.32158993E-003,
  -6.15709629E-005,
   1.53845135E-003,
   1.45979905E-003,
  -8.07474997E-004,
  -4.95346480E-003,
  -9.77688887E-003,
  -1.34227803E-002,
  -1.39019304E-002,
  -9.76527311E-003,
  -7.32257657E-004,
   1.19432269E-002,
   2.55342272E-002,
   3.63795756E-002,
   4.08062656E-002,
   3.62074734E-002,
   2.19717307E-002,
  -3.34826090E-005,
  -2.56197925E-002,
  -4.91049436E-002,
  -6.46425206E-002,
  -6.77283139E-002,
  -5.64799831E-002,
  -3.23221265E-002,
   1.48070803E-004,
   3.41239138E-002,
   6.21627788E-002,
   7.79767764E-002,

   7.79767764E-002,
   6.21627788E-002,
   3.41239138E-002,
   1.48070803E-004,
  -3.23221265E-002,
  -5.64799831E-002,
  -6.77283139E-002,
  -6.46425206E-002,
  -4.91049436E-002,
  -2.56197925E-002,
  -3.34826090E-005,
   2.19717307E-002,
   3.62074734E-002,
   4.08062656E-002,
   3.63795756E-002,
   2.55342272E-002,
   1.19432269E-002,
  -7.32257657E-004,
  -9.76527311E-003,
  -1.39019304E-002,
  -1.34227803E-002,
  -9.77688887E-003,
  -4.95346480E-003,
  -8.07474997E-004,
   1.45979905E-003,
   1.53845135E-003,
  -6.15709629E-005,
  -2.32158993E-003,
  -4.13462141E-003,
  -4.68851332E-003,
  -3.69928970E-003,
  -1.44252083E-003,
   1.39737571E-003,
   3.98882064E-003,
   5.62762958E-003,
   5.93895549E-003,
   4.95211818E-003,
   3.04474545E-003,
   7.89569578E-004,
  -1.23248004E-003,
  -2.59452497E-003,
  -3.11333676E-003,
  -2.85627942E-003,
  -2.07227298E-003,
  -1.08734763E-003,
  -1.94667592E-004,
   4.13877948E-004,
   6.79307985E-004,
   6.54215790E-004,
   6.71257165E-004,
};
 
I've got a CW filter working - this one is about 300Hz wide and is centred at 800Hz. I've used the decimator filter as the interpolator filter as well since it removes the annoying aliasing/hiss and this time it doesn't seem to have added any echo.
If pin 32 is grounded it switches the CW filter in.

Excellent! Good to hear that the echo has gone away. I also experimented a bit further last night and found the echo to have disappeared.

You used a 100 tap FIR filter? I did some time measurements to evaluate the effeciency of the filtering/processing using elapsedTime = 0 at the beginning of one block processing and calculating the mean time over 1000 runs, these are my results:

queue IN & queue OUT & decimation (with 80 tap FIR) & interpolation (with 80 tap FIR): 569µsec
biquad 1 stage & biquad 6 stages (in the decimated 11029sps): 80µsec
FIR 200 taps (in the decimated 11029sps): 408µsec
biquad 5 stages (in 44118sps !): 200µsec
FFT 1024 points in float32_t: 1242µsec

My conclusion would be:

- never use FIR filters in the decimated path, unless you have a very good reason to do so (e.g. if you need excellent phase response of your filter --> phasing receiver with Hilbert FIR etc.). You can see that an IIR with 1+6 stages uses much less processor power than a FIR and even has steeper response (I suppose).
- the interpolation filter after the upsampling (the built-in polyphase FIR filter in the arm_interpolate_f32 function) is very efficient and one should not substitute it by an IIR (that would have to be placed in the 44118sps-path, because the audio has to be filtered AFTER upsampling and is inefficient for that reason).
- a permanent running floating point 1024 point FFT is possible, even with heavy audio filtering going on in realtime, which is nice!

Next step:
- get the 1024 complex FFT results and use them for a simple spectrum analyser (is already implemented in the following sketch, but I cheated and preliminarily based it on a 256 FFT from the audio library ;-), that is also nice to graphically test the frequency response of your filters in the audio path, as it is based on the queue OUTPUT)

Pete, switch in lines 303/304 and lines 307/308 to hear an 8 pole IIR CW filter with 300Hz BW and 700Hz centre frequency ;-). Just for playing around and comparison with your FIR filter. The coeffs are in the Filter_coeffs.h

Have fun with the Teensy,

Frank


Code:
// Use queue for input AND output
// to allow for floating point audio processing
// STEREO VERSION, NEW way of output buffer read/send
// Frank DD4WH 2016_10_29
// with optimizations by Pete El Supremo 2016_10_27, thanks Pete!
//
// this is a setup to test whether we can do real time audio processing 
// with the Teensy 3.5 in floating point math
// it takes the LINE INPUT audio, converts it from int16_t to float32_t, 
//  does the audio processing in float32_t with the NEW ARM CMSIS lib (see  https://forum.pjrc.com/threads/38325-Excellent-results-with-Floating-Point-FFT-IFFT-Processing-and-Teensy-3-6?p=119797&viewfull=1#post119797),
//  and see here:  https://forum.pjrc.com/threads/38325-Excellent-results-with-Floating-Point-FFT-IFFT-Processing-and-Teensy-3-6?p=120177&viewfull=1#post120177  
// . . . converts the audio back to int16_t and gives it back to the headphone output
//
// audio path in this version:
//
// LINE INPUT - queue record object - memory buffer - variable int16_t buffer - conversion to float32_t buffer - 
// - lowpass FIR filter 80 taps - downsampling by 4 - AUDIO FILTERING in floating point [at the moment, nothing here] - 
//  - upsampling by 4 - lowpass FIR filter 80 taps - conversion to int16_t  buffer - set pointer to output buffer - play output buffer by  play-queue-object
//
//
// TODO:
// - display FFT results from the floating point FFT --> spectrum analyzer
//  - eliminate annoying high pitch sound that is audible when no sound is  fed into the line input (not very loud but nonetheless present and  annoying)
//
// MIT licence

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <Metro.h>
#include "font_Arial.h"
#include <ILI9341_t3.h>
#include <arm_math.h>
#include <arm_const_structs.h>

#include "Filter_coeffs.h"

#define BACKLIGHT_PIN 0

#define TFT_DC      20
#define TFT_CS      21
#define TFT_RST     32  // 255 = unused. connect to 3.3V
#define TFT_MOSI     7
#define TFT_SCLK    14
#define TFT_MISO    12

ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);

Metro five_sec=Metro(2000); // Set up a 0.5 second Metro

// this audio comes from the codec by I2S2

AudioInputI2S            i2s_in; 
           
AudioRecordQueue         Q_in_L;    
AudioRecordQueue         Q_in_R;    

AudioPlayQueue           Q_out_L; 
AudioPlayQueue           Q_out_R; 
AudioAnalyzeFFT256  myFFT;
AudioOutputI2S           i2s_out;           
AudioConnection          patchCord1(i2s_in, 0, Q_in_L, 0);
AudioConnection          patchCord2(i2s_in, 1, Q_in_R, 0);
AudioConnection      patchCord5(Q_out_R,0,myFFT,0); 
AudioConnection          patchCord3(Q_out_L, 0, i2s_out, 1);
AudioConnection          patchCord4(Q_out_R, 0, i2s_out, 0);
AudioControlSGTL5000     sgtl5000_1;     //xy=265.212

int idx_t = 0;
int idx = 0;
int64_t sum;
float32_t mean;
int n_L;
int n_R;

int peak[512];
int barm[512];

ulong samp_ptr = 0;
bool FFT_state = false;

const int myInput = AUDIO_INPUT_LINEIN;

// We're only processing one buffer at a time so the
// number of samples is fixed at 128
#define BUFFER_SIZE 128

// Decimation and Interpolation factor
#define DECIMATION_FACTOR 4

float32_t float_buffer_L [BUFFER_SIZE];
float32_t float_buffer_R [BUFFER_SIZE];
float32_t float_buffer_L_3 [BUFFER_SIZE];
float32_t float_buffer_R_3 [BUFFER_SIZE];

// This holds the samples output by the decimation
// and therefore there are a factor of 4 fewer samples
float32_t float_buffer_L_2 [BUFFER_SIZE / DECIMATION_FACTOR];
float32_t float_buffer_R_2 [BUFFER_SIZE / DECIMATION_FACTOR];

// determine the number of decimation and interpolation coefficients
// from the size of their arrays
const int N_DEC_COEFFS = sizeof(FIR_dec1_coeffs)/sizeof(FIR_dec1_coeffs[0]);
const int N_INT_COEFFS = sizeof(FIR_int1_coeffs)/sizeof(FIR_int1_coeffs[0]);

// decimation with FIR lowpass
arm_fir_decimate_instance_f32 FIR_dec1_L;
float32_t FIR_decim_state_L [N_DEC_COEFFS + BUFFER_SIZE - 1];
arm_fir_decimate_instance_f32 FIR_dec1_R;
float32_t FIR_decim_state_R [N_DEC_COEFFS + BUFFER_SIZE - 1];

// interpolation with FIR lowpass
arm_fir_interpolate_instance_f32 FIR_int1_L;
float32_t FIR_interp_state_L [(N_INT_COEFFS / DECIMATION_FACTOR) + BUFFER_SIZE - 1];
arm_fir_interpolate_instance_f32 FIR_int1_R;
float32_t FIR_interp_state_R [(N_INT_COEFFS / DECIMATION_FACTOR) + BUFFER_SIZE - 1];

// FIR filter
// determine the number of filter coeffs from the size of their arrays
const int N_FIR1_COEFFS = sizeof(FIR_lowpass1_coeffs)/sizeof(FIR_lowpass1_coeffs[0]);
arm_fir_instance_f32 FIR_lowpass1_L;
float32_t FIR_lowpass1_state_L [N_FIR1_COEFFS + BUFFER_SIZE - 1];
arm_fir_instance_f32 FIR_lowpass1_R;
float32_t FIR_lowpass1_state_R [N_FIR1_COEFFS + BUFFER_SIZE - 1];

/****************************************************************************************
 *  init IIR filters
 ****************************************************************************************/
// 2-pole biquad IIR - definitions and Initialisation
const int N_stages_biquad_lowpass1 = sizeof(biquad_lowpass1_coeffs)/sizeof(biquad_lowpass1_coeffs[0]) / 5;
float32_t biquad_lowpass1_state_L [N_stages_biquad_lowpass1 * 4];
float32_t biquad_lowpass1_state_R [N_stages_biquad_lowpass1 * 4];
arm_biquad_casd_df1_inst_f32 biquad_lowpass1_L = {N_stages_biquad_lowpass1, biquad_lowpass1_state_L, biquad_lowpass1_coeffs}; 
arm_biquad_casd_df1_inst_f32 biquad_lowpass1_R = {N_stages_biquad_lowpass1, biquad_lowpass1_state_R, biquad_lowpass1_coeffs}; 

// 10-pole biquad IIR - definitions and Initialisation
const int N_stages_biquad_lowpass2 = sizeof(biquad_lowpass2_coeffs)/sizeof(biquad_lowpass2_coeffs[0]) / 5;
float32_t biquad_lowpass2_state_L [N_stages_biquad_lowpass2 * 4];
float32_t biquad_lowpass2_state_R [N_stages_biquad_lowpass2 * 4];
arm_biquad_casd_df1_inst_f32 biquad_lowpass2_L = {N_stages_biquad_lowpass2, biquad_lowpass2_state_L, biquad_lowpass2_coeffs}; 
arm_biquad_casd_df1_inst_f32 biquad_lowpass2_R = {N_stages_biquad_lowpass2, biquad_lowpass2_state_R, biquad_lowpass2_coeffs}; 

// complex FFT with the new library CMSIS V4.5
const static arm_cfft_instance_f32 *S;
const int length_FFT = 256; 
float32_t FFT_buffer [length_FFT * 2] __attribute__ ((aligned (4)));

void setup() {
  Serial.begin(115200);
  delay(1000);

  // Audio connections require memory. and the record queue
  // uses this memory to buffer incoming audio.
  AudioMemory(100);

  // Enable the audio shield. select input. and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.5);
  sgtl5000_1.adcHighPassFilterDisable(); // does not help too much!

/*  // Initialize the SD card
  SPI.setMOSI(7);
  SPI.setSCK(14);
  if (!(SD.begin(10))) {
    // stop here if no SD card. but print a message
    while (1) {
      Serial.println("Unable to access the SD card");
      delay(500);
    }
  }
*/
  pinMode( BACKLIGHT_PIN, OUTPUT );
  analogWrite( BACKLIGHT_PIN, 1023 );

  tft.begin();
  tft.setRotation( 3 );
  tft.fillScreen(ILI9341_BLACK);
  tft.setCursor(10, 1);
  tft.setTextSize(2);
  tft.setTextColor(ILI9341_WHITE);
  tft.setFont(Arial_14);
  tft.print("Floating point audio processing");


/****************************************************************************************
 *  init FIR filters
 ****************************************************************************************/
    arm_fir_init_f32(&FIR_lowpass1_L, N_FIR1_COEFFS, FIR_lowpass1_coeffs, FIR_lowpass1_state_L,BUFFER_SIZE);
    arm_fir_init_f32(&FIR_lowpass1_R, N_FIR1_COEFFS, FIR_lowpass1_coeffs, FIR_lowpass1_state_R,BUFFER_SIZE);
/****************************************************************************************
 *  init decimation and interpolation filters
 ****************************************************************************************/
   if(arm_fir_decimate_init_f32(&FIR_dec1_L, N_DEC_COEFFS,  DECIMATION_FACTOR, FIR_dec1_coeffs, FIR_decim_state_L, BUFFER_SIZE)) {
    Serial.println("Init of decimation failed");
    while(1);
  }
   if(arm_fir_interpolate_init_f32(&FIR_int1_L,  DECIMATION_FACTOR,  N_INT_COEFFS, FIR_int1_coeffs, FIR_interp_state_L, BUFFER_SIZE/4)) {
    Serial.println("Init of interpolation failed");
    while(1);  
  }
   if(arm_fir_decimate_init_f32(&FIR_dec1_R, N_DEC_COEFFS,   DECIMATION_FACTOR, FIR_dec1_coeffs, FIR_decim_state_R, BUFFER_SIZE)) {
    Serial.println("Init of decimation failed");
    while(1);
  }
   if(arm_fir_interpolate_init_f32(&FIR_int1_R, DECIMATION_FACTOR,  N_INT_COEFFS, FIR_int1_coeffs, FIR_interp_state_R, BUFFER_SIZE/4)) {
    Serial.println("Init of interpolation failed");
    while(1);  
  }
 /****************************************************************************************
 *  init complex FFT
 ****************************************************************************************/
   switch (length_FFT) {
    case 16:
      S = &arm_cfft_sR_f32_len16;
      break;
    case 32:
      S = &arm_cfft_sR_f32_len32;
      break;
    case 64:
      S = &arm_cfft_sR_f32_len64;
      break;
    case 128:
      S = &arm_cfft_sR_f32_len128;
      break;
    case 256:
      S = &arm_cfft_sR_f32_len256;
      break;
    case 512:
      S = &arm_cfft_sR_f32_len512;
      break;
    case 1024:
      S = &arm_cfft_sR_f32_len1024;
      break;
    case 2048:
      S = &arm_cfft_sR_f32_len2048;
      break;
    case 4096:
      S = &arm_cfft_sR_f32_len4096;
      break;
  }
 /****************************************************************************************
 *  begin to queue the audio from the audio library
 ****************************************************************************************/
    delay(100);
    Q_in_L.begin();
    Q_in_R.begin();    
} // END SETUP


int16_t *sp_L;
int16_t *sp_R;

void loop() {
 
 elapsedMicros usec = 0;
/**********************************************************************************
 *  Get samples from queue buffers
 **********************************************************************************/
    if (Q_in_L.available() >= 1 && Q_in_R.available() >= 1)
    {   
    sp_L = Q_in_L.readBuffer();
    sp_R = Q_in_R.readBuffer();

      // convert to float
     arm_q15_to_float (sp_L, float_buffer_L, BUFFER_SIZE); // convert int_buffer to float 32bit
     arm_q15_to_float (sp_R, float_buffer_R, BUFFER_SIZE); // convert int_buffer to float 32bit
     Q_in_L.freeBuffer();
     Q_in_R.freeBuffer();

/**********************************************************************************
 *  Put 128 floating point samples into FFT buffer and set flag FFT_state when it is filled
 **********************************************************************************/
        for(int i = 0; i < BUFFER_SIZE; i++)
        {
            if(FFT_state == false) // FFT buffer not yet filled
            {
                 FFT_buffer [samp_ptr] = (float32_t)float_buffer_L[i];    // get  floating point data for FFT for spectrum scope/waterfall display
                samp_ptr++;
                FFT_buffer [samp_ptr] = (float32_t)float_buffer_R[i];
                samp_ptr++;

                 // On obtaining enough samples for spectrum scope/waterfall, update  state machine, reset pointer and wait until we process what we have
                if(samp_ptr > length_FFT) 
                {
                    samp_ptr = 0;
                    FFT_state = true; // FFT buffer filled with length_FFT samples
                }
            }
        }

/**************************************************************************
 * From here, all the 32 bit float audio processing can start
 * ************************************************************************
 */
  // decimation by 4 --> 44118 / 4 = 11029.5 sps, means that a 4.2kHz decimation filter is fine [should have -80dB at 5.5kHz]
       //  decimation filter FIR 80 taps, lowpass 4.2kHz, Parks McClellan,  Window off, Transition width 0.1, 80dB stopband attenuation
      arm_fir_decimate_f32(&FIR_dec1_L, float_buffer_L, float_buffer_L_2, BUFFER_SIZE);
      arm_fir_decimate_f32(&FIR_dec1_R, float_buffer_R, float_buffer_R_2, BUFFER_SIZE);
      
      // test filter 1 stage
//      arm_biquad_cascade_df1_f32 (&biquad_lowpass1_L, float_buffer_L_2,float_buffer_L, BUFFER_SIZE / 4);
//      arm_biquad_cascade_df1_f32 (&biquad_lowpass1_R, float_buffer_R_2,float_buffer_R, BUFFER_SIZE / 4);

      // test filter 5 to 8 stages
//      arm_biquad_cascade_df1_f32 (&biquad_lowpass2_L, float_buffer_L,float_buffer_L_2, BUFFER_SIZE / 4);
//      arm_biquad_cascade_df1_f32 (&biquad_lowpass2_R, float_buffer_R,float_buffer_R_2, BUFFER_SIZE / 4);

      // test LMS noise reduction tbd

      // test LMS notch filtering tbd

       // test FIR filter 200 taps
//     arm_fir_f32(&FIR_lowpass1_L,float_buffer_L_3, float_buffer_L_2, BUFFER_SIZE / 4);
//     arm_fir_f32(&FIR_lowpass1_R,float_buffer_R_3, float_buffer_R_2, BUFFER_SIZE / 4);

  // interpolation by 4
      arm_fir_interpolate_f32(&FIR_int1_L, float_buffer_L_2, float_buffer_L, BUFFER_SIZE / 4);
      arm_fir_interpolate_f32(&FIR_int1_R, float_buffer_R_2, float_buffer_R, BUFFER_SIZE / 4);

    // this IIR filter works in 44118sps ! Quite expensive in terms of CPU power
//      arm_biquad_cascade_df1_f32 (&biquad_lowpass2_L, float_buffer_L_3,float_buffer_L, BUFFER_SIZE);
//      arm_biquad_cascade_df1_f32 (&biquad_lowpass2_R, float_buffer_R_3,float_buffer_R, BUFFER_SIZE);

      // scaling after interpolation ? 4x louder audio?
      arm_scale_f32 (float_buffer_R,4.0,float_buffer_R, BUFFER_SIZE);
      arm_scale_f32 (float_buffer_L,4.0,float_buffer_L, BUFFER_SIZE);
      
/**************************************************************************
 * END of 32 bit float audio processing
 * ************************************************************************
 */
    sp_L = Q_out_L.getBuffer();
    sp_R = Q_out_R.getBuffer();
    arm_float_to_q15 (float_buffer_L, sp_L, BUFFER_SIZE); 
    arm_float_to_q15 (float_buffer_R, sp_R, BUFFER_SIZE); 
      Q_out_L.playBuffer(); // play it !
      Q_out_R.playBuffer(); // play it !


/**********************************************************************************
 *  FFT
 **********************************************************************************/
     if (FFT_state) {

//     arm_cfft_f32(S, FFT_buffer, 0, 1);
      }
/**********************************************************************************
 *  PRINT ROUTINE FOR ELAPSED MICROSECONDS
 **********************************************************************************/
 
      sum = sum + usec;
      idx_t++;
      if (idx_t > 1000) {
          tft.fillRect(240,50,90,20,ILI9341_BLACK);   
          tft.setCursor(240, 50);
          mean = sum / idx_t;
          tft.print (mean);
          Serial.print (mean);
          Serial.print (" microsec for 2 stereo blocks    ");
          Serial.println();
          idx_t = 0;
          sum = 0;
         
      }

     }
/**********************************************************************************
 *  PRINT ROUTINE FOR AUDIO LIBRARY PROCESSOR AND MEMORY USAGE
 **********************************************************************************/
          if (five_sec.check() == 1)
    {
      Serial.print("Proc = ");
      Serial.print(AudioProcessorUsage());
      Serial.print(" (");    
      Serial.print(AudioProcessorUsageMax());
      Serial.print("),  Mem = ");
      Serial.print(AudioMemoryUsage());
      Serial.print(" (");    
      Serial.print(AudioMemoryUsageMax());
      Serial.println(")");
/*      tft.fillRect(100,120,200,80,ILI9341_BLACK);
      tft.setCursor(10, 120);
      tft.setTextSize(2);
      tft.setTextColor(ILI9341_WHITE);
      tft.setFont(Arial_14);
      tft.print ("Proc = ");
      tft.setCursor(100, 120);
      tft.print (AudioProcessorUsage());
      tft.setCursor(180, 120);
      tft.print (AudioProcessorUsageMax());
      tft.setCursor(10, 150);
      tft.print ("Mem  = ");
      tft.setCursor(100, 150);
      tft.print (AudioMemoryUsage());
      tft.setCursor(180, 150);
      tft.print (AudioMemoryUsageMax());
     */ 
      AudioProcessorUsageMaxReset();
      AudioMemoryUsageMaxReset();
    }
   spectrum();
}


 void spectrum() { // spectrum analyser code by rheslip - modified
     if (myFFT.available()) {
    int scale;
    scale = 2;
  for (int16_t x=2; x < 100; x+=2) {

     int bar = (abs(myFFT.output[x]) * scale);
     if (bar >180) bar=180;
     // this is a very simple IIR filter to smooth the reaction of the bars
     bar = 0.2 * bar + 0.8 * barm[x]; 
     if (bar > peak[x]) peak[x]=bar;
//     tft.drawFastVLine(x, 210-bar,bar, ILI9341_PURPLE);
     tft.drawFastVLine(x*2+10, 210-bar,bar, ILI9341_PINK);

     tft.drawFastVLine(x*2+10, 20, 210-bar-20, ILI9341_BLACK);    

     tft.drawPixel(x*2+10,209-peak[x], ILI9341_YELLOW);

     if(peak[x]>0) peak[x]-=1;
     barm[x] = bar;
  }
  } //end if

   } // end void spectrum


Filter_coeffs.h

Code:
// pass-thru coefficients
float32_t biquad_lowpass1_coeffs[5] = {1,0,0,0,0};
/*
// lowpass elliptic 4.0kHz, IIR biquad 6 stages = 12 pole
// fs 11027Hz
// IIR Filter designer Iowa Hills
// a1 and a2 negated
// order of coefficients: b0, b1, b2, -a1, -a2
float32_t biquad_lowpass2_coeffs [30]= {
   0.390955637301552750,
   0.556640362257148746,
   0.390955637301552750,
   -0.283754748892188324,
   -0.054796887968065991,

   0.539269449233711784,
   0.786587988954648765,
   0.539269449233711784,
   -0.561224440037602634,
   -0.303902447384469809,

   0.691524236619742827,
   1.062809822558603120,
   0.691524236619742827,
   -0.866733199372850538,
   -0.579125096425238239,

   0.776938719898112917,
   1.296485971846480290,
   0.776938719898112917,
   -1.078306368085572900,
   -0.772057043557133338,

   0.805342431114359658,
   1.482472831743180340,
   0.805342431114359658,
   -1.202727577982673820,
   -0.890430115989226167,

   0.814418978226675527,
   1.611693182699644120,
   0.814418978226675527,
   -1.273176548353518900,
   -0.967354590799476166
};
*/
// FIR 200 taps, Raised Cosine 0.940
// Fc = 3.000kHz, 75dB stopband
// fs 44118Hz
// just to test if this works, 
// a 200 tap FIR is ridicously big and lots of calculation work for the processor
// --> it works !
float32_t FIR_lowpass1_coeffs[200] =
{ 94.97870007611338390E-9,
-1.009924021425136600E-6,
-3.955509751038522200E-6,
-8.167421170036924140E-6,
-12.23385614931934380E-6,
-14.14794389888045070E-6,
-11.83601267362507410E-6,
-3.874707993346245160E-6,
 9.784409190338859470E-6,
 27.31190087730911390E-6,
 44.93736028076724410E-6,
 57.46570395053866780E-6,
 59.37446254570318160E-6,
 46.32396853632215540E-6,
 16.75260847587800940E-6,
-26.87322881082111080E-6,
-77.58971029118251290E-6,
-124.5675198379420860E-6,
-154.9396363165045050E-6,
-156.6193065484440300E-6,
-121.5946328111847800E-6,
-48.95032523151913040E-6,
 53.22076923312532420E-6,
 167.7426457861406560E-6,
 270.5249246949471740E-6,
 334.8987951214899680E-6,
 337.4870793426350130E-6,
 264.4733746227138910E-6,
 116.8225367007839280E-6,
-87.00810636093507360E-6,
-311.9857878058807610E-6,
-511.4939298316222110E-6,
-635.9492759051854590E-6,
-643.6567497319431370E-6,
-511.7505262783362240E-6,
-244.6910091391752930E-6,
 122.0563006080398620E-6,
 524.7970382117916870E-6,
 881.2433438597264510E-6,
 0.001105897264525711,
 0.001128584366403902,
 912.4295155936849820E-6,
 467.1426447303733770E-6,
-146.0617101058849410E-6,
-820.2329826975067140E-6,
-0.001419395225860928,
-0.001804044124385529,
-0.001861021146414526,
-0.001531785067380846,
-832.6535950832496840E-6,
 138.4931805272558170E-6,
 0.001212301918764672,
 0.002174825940792150,
 0.002807530450404730,
 0.002933442142689424,
 0.002460262925219348,
 0.001410796432743954,
-67.40198731776092700E-6,
-0.001717749520116288,
-0.003215444385842726,
-0.004227679123662836,
-0.004482787665470263,
-0.003834751661105775,
-0.002308921085773049,
-117.1613956177577340E-6,
 0.002364261142304478,
 0.004653686825192925,
 0.006251853354833492,
 0.006743484329240796,
 0.005893195184744424,
 0.003715295847640134,
 500.2318515875483630E-6,
-0.003212572302564382,
-0.006715879276971413,
-0.009259324885670151,
-0.010197256146698041,
-0.009132578886926748,
-0.006027440073467311,
-0.001255112944564374,
 0.004423632349767859,
 0.009963382868447519,
 0.014204955965723772,
 0.016088984764790849,
 0.014876395277668464,
 0.010335310034903742,
 0.002856460155015252,
-0.006531028294970668,
-0.016255904142096857,
-0.024406432741385542,
-0.029011490508947704,
-0.028359336686767097,
-0.021302866903510787,
-0.007499202509221305,
 0.012460426961983004,
 0.037058421514990315,
 0.064002438051746699,
 0.090503604392753637,
 0.113642557166307487,
 0.130768970678281582,
 0.139873389683470517,
 0.139873389683470517,
 0.130768970678281582,
 0.113642557166307487,
 0.090503604392753637,
 0.064002438051746699,
 0.037058421514990315,
 0.012460426961983004,
-0.007499202509221305,
-0.021302866903510787,
-0.028359336686767097,
-0.029011490508947704,
-0.024406432741385542,
-0.016255904142096857,
-0.006531028294970668,
 0.002856460155015252,
 0.010335310034903742,
 0.014876395277668464,
 0.016088984764790849,
 0.014204955965723772,
 0.009963382868447519,
 0.004423632349767859,
-0.001255112944564374,
-0.006027440073467311,
-0.009132578886926748,
-0.010197256146698041,
-0.009259324885670151,
-0.006715879276971413,
-0.003212572302564382,
 500.2318515875483630E-6,
 0.003715295847640134,
 0.005893195184744424,
 0.006743484329240796,
 0.006251853354833492,
 0.004653686825192925,
 0.002364261142304478,
-117.1613956177577340E-6,
-0.002308921085773049,
-0.003834751661105775,
-0.004482787665470263,
-0.004227679123662836,
-0.003215444385842726,
-0.001717749520116288,
-67.40198731776092700E-6,
 0.001410796432743954,
 0.002460262925219348,
 0.002933442142689424,
 0.002807530450404730,
 0.002174825940792150,
 0.001212301918764672,
 138.4931805272558170E-6,
-832.6535950832496840E-6,
-0.001531785067380846,
-0.001861021146414526,
-0.001804044124385529,
-0.001419395225860928,
-820.2329826975067140E-6,
-146.0617101058849410E-6,
 467.1426447303733770E-6,
 912.4295155936849820E-6,
 0.001128584366403902,
 0.001105897264525711,
 881.2433438597264510E-6,
 524.7970382117916870E-6,
 122.0563006080398620E-6,
-244.6910091391752930E-6,
-511.7505262783362240E-6,
-643.6567497319431370E-6,
-635.9492759051854590E-6,
-511.4939298316222110E-6,
-311.9857878058807610E-6,
-87.00810636093507360E-6,
 116.8225367007839280E-6,
 264.4733746227138910E-6,
 337.4870793426350130E-6,
 334.8987951214899680E-6,
 270.5249246949471740E-6,
 167.7426457861406560E-6,
 53.22076923312532420E-6,
-48.95032523151913040E-6,
-121.5946328111847800E-6,
-156.6193065484440300E-6,
-154.9396363165045050E-6,
-124.5675198379420860E-6,
-77.58971029118251290E-6,
-26.87322881082111080E-6,
 16.75260847587800940E-6,
 46.32396853632215540E-6,
 59.37446254570318160E-6,
 57.46570395053866780E-6,
 44.93736028076724410E-6,
 27.31190087730911390E-6,
 9.784409190338859470E-6,
-3.874707993346245160E-6,
-11.83601267362507410E-6,
-14.14794389888045070E-6,
-12.23385614931934380E-6,
-8.167421170036924140E-6,
-3.955509751038522200E-6,
-1.009924021425136600E-6,
 94.97870007611338390E-9
};

//  decimation filter FIR 80 taps, lowpass 4.2kHz, Parks McClellan, Window off, Transition width 0.1, 80dB stopband attenuation
float32_t FIR_dec1_coeffs[80] = {
-142.9442739751772820E-6,
-252.2211831462206530E-6,
-347.5945623945052030E-6,
-321.0839601413753140E-6,
-97.22225175627986000E-6,
 326.3117239250896090E-6,
 836.1439112274239280E-6,
 0.001209027523486800,
 0.001184222537420025,
 590.9136933192837660E-6,
-520.3527006113926060E-6,
-0.001812698722149656,
-0.002724775642735784,
-0.002673582490820696,
-0.001340728074926084,
 0.001078011666779184,
 0.003797965250984124,
 0.005638404522781618,
 0.005468470132752291,
 0.002761334048239419,
-0.001980549872799683,
-0.007167492106103557,
-0.010557955026781203,
-0.010112507043340404,
-0.004978767523812532,
 0.003808453095602158,
 0.013295208276611466,
 0.019416922705541947,
 0.018489926838076525,
 0.008904119903017756,
-0.007643552646951099,
-0.025966841359731561,
-0.038527883680910445,
-0.037684191047641612,
-0.018373491864758760,
 0.019689814633387617,
 0.071157875324433559,
 0.125978118210888251,
 0.171947745227487625,
 0.198145667117833019,
 0.198145667117833019,
 0.171947745227487625,
 0.125978118210888251,
 0.071157875324433559,
 0.019689814633387617,
-0.018373491864758760,
-0.037684191047641612,
-0.038527883680910445,
-0.025966841359731561,
-0.007643552646951099,
 0.008904119903017756,
 0.018489926838076525,
 0.019416922705541947,
 0.013295208276611466,
 0.003808453095602158,
-0.004978767523812532,
-0.010112507043340404,
-0.010557955026781203,
-0.007167492106103557,
-0.001980549872799683,
 0.002761334048239419,
 0.005468470132752291,
 0.005638404522781618,
 0.003797965250984124,
 0.001078011666779184,
-0.001340728074926084,
-0.002673582490820696,
-0.002724775642735784,
-0.001812698722149656,
-520.3527006113926060E-6,
 590.9136933192837660E-6,
 0.001184222537420025,
 0.001209027523486800,
 836.1439112274239280E-6,
 326.3117239250896090E-6,
-97.22225175627986000E-6,
-321.0839601413753140E-6,
-347.5945623945052030E-6,
-252.2211831462206530E-6,
-142.9442739751772820E-6 };


float32_t FIR_int1_coeffs[80] = {
-142.9442739751772820E-6,
-252.2211831462206530E-6,
-347.5945623945052030E-6,
-321.0839601413753140E-6,
-97.22225175627986000E-6,
 326.3117239250896090E-6,
 836.1439112274239280E-6,
 0.001209027523486800,
 0.001184222537420025,
 590.9136933192837660E-6,
-520.3527006113926060E-6,
-0.001812698722149656,
-0.002724775642735784,
-0.002673582490820696,
-0.001340728074926084,
 0.001078011666779184,
 0.003797965250984124,
 0.005638404522781618,
 0.005468470132752291,
 0.002761334048239419,
-0.001980549872799683,
-0.007167492106103557,
-0.010557955026781203,
-0.010112507043340404,
-0.004978767523812532,
 0.003808453095602158,
 0.013295208276611466,
 0.019416922705541947,
 0.018489926838076525,
 0.008904119903017756,
-0.007643552646951099,
-0.025966841359731561,
-0.038527883680910445,
-0.037684191047641612,
-0.018373491864758760,
 0.019689814633387617,
 0.071157875324433559,
 0.125978118210888251,
 0.171947745227487625,
 0.198145667117833019,
 0.198145667117833019,
 0.171947745227487625,
 0.125978118210888251,
 0.071157875324433559,
 0.019689814633387617,
-0.018373491864758760,
-0.037684191047641612,
-0.038527883680910445,
-0.025966841359731561,
-0.007643552646951099,
 0.008904119903017756,
 0.018489926838076525,
 0.019416922705541947,
 0.013295208276611466,
 0.003808453095602158,
-0.004978767523812532,
-0.010112507043340404,
-0.010557955026781203,
-0.007167492106103557,
-0.001980549872799683,
 0.002761334048239419,
 0.005468470132752291,
 0.005638404522781618,
 0.003797965250984124,
 0.001078011666779184,
-0.001340728074926084,
-0.002673582490820696,
-0.002724775642735784,
-0.001812698722149656,
-520.3527006113926060E-6,
 590.9136933192837660E-6,
 0.001184222537420025,
 0.001209027523486800,
 836.1439112274239280E-6,
 326.3117239250896090E-6,
-97.22225175627986000E-6,
-321.0839601413753140E-6,
-347.5945623945052030E-6,
-252.2211831462206530E-6,
-142.9442739751772820E-6 };

/*
float32_t FIR_int1_coeffs[4] = {
 0.210904123894329720,
 0.301141108924676826,
 0.301141108924676826,
 0.210904123894329720 };
*/


// brickwall CW filter
// bandpass, elliptic, Fc=700Hz, BW=300Hz, -80dB at 522Hz, -80dB at 932Hz
// 8 stages IIR
float32_t biquad_lowpass2_coeffs [40]= {
   0.364001063989044915,
   -0.696473959523549957,
   0.364001063989044971,
   1.788480069318890250,
   -0.925376106125455067,

   0.361186709947353413,
   -0.620959940244166786,
   0.361186709947353468,
   1.751855271152122700,
   -0.918221357851320086,

   0.433058689492207660,
   -0.831580060059418424,
   0.433058689492207660,
   1.831455406178845950,
   -0.949172844759517020,

   0.426669654838185863,
   -0.723801208927776329,
   0.426669654838185863,
   1.738051696214021780,
   -0.935169435187165887,

   0.377153150378401081,
   -0.731453681768477582,
   0.377153150378401136,
   1.864734872952909410,
   -0.973104720052835992,

   0.373007288989870733,
   -0.599811328494536000,
   0.373007288989870733,
   1.743134620930931970,
   -0.962407852528818108,

   0.167662570128892602,
   -0.332516665519374976,
   0.167662570128892630,
   1.886699807792520560,
   -0.991809863590435659,

   0.167030532646816859,
   -0.144078663443187288,
   0.167030532646816859,
   1.757154994466575190,
   -0.988071038589723227
};
 
Frank: The other Frank (Frank B) has written a function which changes the sampling rate of the SGTL5000. I think for what you are doing a rate of 11025 would be better and then you wouldn't need to do the decimation/interpolation. His function is here.

Pete
 
Thanks Pete!
Yes, that is excellent, if you are doing audio processing alone. But for my SDR I am planning to display a spectrum display with the widest span available. So with 44.1ksps I can display 44.1kHz (complex FFT!) of bandwidth, but with 8ksps (where audio processing is much more efficient), I can display only 8kHz of bandwidth.

So I may even be going with the opposite approach! As Frank B has implemented HIGHER sample rates, I may choose 96ksps or even 192ksps, display that bandwidth with the spectrum display and do all the audio processing with decimation/interpolation by 8 or even 16. 192/16 = 12kHz, so an audio bandwidth of 5kHz is still achievable with sample rate of 192ksps and decimation by 16, which is plenty of audio bandwidth for an AM/SSB radio ;-).

Frank
 
The DSP stuff I have done has almost always been related to processing audio to produce things like CW filters or detecting and then decoding the 100Hz subcarrier date/time information in the WWV signal. For these types of functions, 8kHz is perfect.

For decimation/interpolation by powers of 2, I understand that a half-band FIR filter is commonly used to efficiently reduce/increase the sample rate. I have a vague memory of using this with an ADSP EZ-Kit Lite many years ago but I can't remember precisely how such filters are designed and can't figure out how to create one with the Iowa Hills FIR design program, nor with my own version of Parks-McLellan. Have you had any experience with this?

Pete
 
Pete,
I have no experience with halfband filters so far, would be great to get them running on the Teensy for decimation ;-).

I read about them in Richard Lyons´ book, but have not understood them in depth . . . Also found this:
https://www.dsprelated.com/showarticle/903.php

It seems the trick is to calculate the filters with enough precision to force every other coefficient to be zero.

Frank
 
The halfband filter is not a great filter response wise for decimation. But it cuts the computations in half since every other coeff is set to zero by design.

You can further cut the multiplies by another factor of 2 by using coeff symmetry (2 pointers). The filter precision does not force coeffs to zero, it has to do with it having a cutoff at Fs/4.

Since decimation has the most computations in early stages, typically one factors the decimation by splitting it into stages (i.e. D= 8 = 2*2*2). Then in the front one uses a halfband or CIC to save computations, then a cleanup FIR on the backend.

Plus IIR filters are not used much in decimation (in the real world), since efficient structures exist for FIR. Plus you dont get linear phase and IIRs can become unstable in fixed-pt math.

If you want a matlab/octave script, I wrote one that computes halfband coeffs. Lyons book is good, he is a friend of mine.
 
Last edited:
Thanks for the info!
Yes, I also really like the Lyons book, it´s my first reference when I have questions, which happens quite often ;-).

So, there remain a few questions for me:

1. I am not sure if it would be worth it to use halfband filters on the Teensy for a decimation by 8 / by 4 (I would need these decimation factors for an IQ-SDR). Have you got an indication, how much processing effort one could save compared to the arm_decimate / interpolate functions with their efficient FIR polyphase filter implementations which I use in the script above?

2. If I would be willing to use halfband filters I would not only have to calculate the coeffs (thanks for your offer concerning the Octave script, I (and probably Pete even more) would certainly be interested), but also implement that on the Teensy. Are there any libs or code snippets one could use to ease the effort?

3. IIR: I have been using IIRs for quite a while now and I find them very efficient and reliable. Also they are much more efficient than FIRs (see the computing times above). But maybe I am missing some clever implementation of FIR? Would be interested to know about that!
I use IIRs at the lower (decimated) sample rate for the already demodulated audio, so no linear phase responses are required (I use (Hilbert)FIRs for the phase critical demodulation parts in the SDR). You are right, in fixed point, they can be problematic (try using more than four biquad stages with 16-bit on the Teensy and you will hear the aliasing artefacts), but in floating point, they work very well. I calculated the coefficients with IIR filter designer and also MATLAB fda (however, I have no licence any more for MATLAB and the filter designer is not implemented in Octave :-() and they are stable like a rock.
 
You will very rarely see an IIR used for decimation in an SDR (particularly in HW). Unless it is a small decimation rate like you are using. Believe me, I developed one of the first SDRs back in 1994. For large decimation rates the IIRs will be unstable since the cutoff freqs are low. You would need double precision math at the very least and split the thing into 2nd order sections - just to start. Plus you are doing all the computations at the HIGH rate. So for large decimation rates, which is used quite often, the savings using FIRs can be substantial. Polyphase FIRs do the computations at the LOW rate. So if I am decimating by 8 with an IIR, I am computing the filter output for all samples and throwing away 7 out of 8 of these (wasted computations). If I use a polyphase FIR, I could do it in less cycles since the savings is (D-1)/D. Plus when you stage the decimation, it is even more efficient since, for example, D=8 you can start with lower quality halfband filters (or CIC), then the final stage can be a halfband or cleanup FIR to give you the response you like.

The halfband FIR is still the same operation, just skipping the zero coeffs. So you would have to write your own routine if such a function doesnt exist in CMSIS. I have a matlab script that will do the efficient halfband fir by using symmetry and skipping the zero coeffs.

At any rate, the IIRs may work for your application, but they are used sparingly in real world multirate DSP (esp in firmware) because of the aforementioned issues.

Here is a script to compute halfband filter coeffs. It will automatically produce a proper sized filter since there are constraints on the total number of taps.

Jim


View attachment halfbandfilt.zip

Thanks for the info!
Yes, I also really like the Lyons book, it´s my first reference when I have questions, which happens quite often ;-).

So, there remain a few questions for me:

1. I am not sure if it would be worth it to use halfband filters on the Teensy for a decimation by 8 / by 4 (I would need these decimation factors for an IQ-SDR). Have you got an indication, how much processing effort one could save compared to the arm_decimate / interpolate functions with their efficient FIR polyphase filter implementations which I use in the script above?

2. If I would be willing to use halfband filters I would not only have to calculate the coeffs (thanks for your offer concerning the Octave script, I (and probably Pete even more) would certainly be interested), but also implement that on the Teensy. Are there any libs or code snippets one could use to ease the effort?

3. IIR: I have been using IIRs for quite a while now and I find them very efficient and reliable. Also they are much more efficient than FIRs (see the computing times above). But maybe I am missing some clever implementation of FIR? Would be interested to know about that!
I use IIRs at the lower (decimated) sample rate for the already demodulated audio, so no linear phase responses are required (I use (Hilbert)FIRs for the phase critical demodulation parts in the SDR). You are right, in fixed point, they can be problematic (try using more than four biquad stages with 16-bit on the Teensy and you will hear the aliasing artefacts), but in floating point, they work very well. I calculated the coefficients with IIR filter designer and also MATLAB fda (however, I have no licence any more for MATLAB and the filter designer is not implemented in Octave :-() and they are stable like a rock.
 
Polyphase FIRs do the computations at the LOW rate.
The CMSIS arm_fir_decimate and arm_fir_interpolate filters are indeed polyphase FIR filters
Edit: maybe it can be improved by matching the number of sub filters to the decimation?
 
Last edited:
@Jim, thanks for the halfband filter design script! OK, understood now, halfband is the same as FIR, just skipping multiplications for the zero coeffs AND using the symmetry of the coeffs (and the calculations) to save another half of the multiplications. Yes, there is no dedicated ARM halfband FIR function, so maybe some time in the future I will try to achieve that ;-).

The IIR issue is a misunderstanding. I use IIR filters (floating point) in the decimated path for audio filtering, NOT for decimation. The decimation itself is being done by the arm polyphase FIR implementation of the arm_decimate function. I see that IIRs do not make much sense as decimation filters.

@Walter: From my limited DSP knowledge I have no idea how much processing time one could save by using a decimation in several stages using CIC or halfbands compared to the ARM polyphase functions (or by rebuilding the arm_decimate function), but my guess would be, it is not that significant for my application where I plan to decimate by 8 or by 4 (D depending on the bandwidth of the audio I need).

Thanks for your inputs!

There will surely be more questions arising soon, as I am currently trying to implement digital convolution filtering, but all this zero-padding, overlapping & adding is twisting my brain currently ;-).
 
Ahh ok. Yeah IIR for general filtering is great. For audio where phase response is not as important even more so!

If you are only decimating by 4 or 8 there is still some savings to staging the decimation, but it might be more work than the trouble. Actually it isnt hard to code up if you just stage the polyphase filters. But designing the filters are a bit more involved. You get away with using less coeffs overall in a staged design than one large single-stage decimator. And you can use small filter length for the initial filter - which will be running at the highest computational rate so you already gain efficiency.

There is a clever technique called IFIR that allows you to design a staged decimator in this fashion.

BTW, if you are trying to implement fast convolution (vice time-domain convolution) - consider using overlap save. It is MUCH easier to implement than overlap add.


@Jim, thanks for the halfband filter design script! OK, understood now, halfband is the same as FIR, just skipping multiplications for the zero coeffs AND using the symmetry of the coeffs (and the calculations) to save another half of the multiplications. Yes, there is no dedicated ARM halfband FIR function, so maybe some time in the future I will try to achieve that ;-).

The IIR issue is a misunderstanding. I use IIR filters (floating point) in the decimated path for audio filtering, NOT for decimation. The decimation itself is being done by the arm polyphase FIR implementation of the arm_decimate function. I see that IIRs do not make much sense as decimation filters.

@Walter: From my limited DSP knowledge I have no idea how much processing time one could save by using a decimation in several stages using CIC or halfbands compared to the ARM polyphase functions (or by rebuilding the arm_decimate function), but my guess would be, it is not that significant for my application where I plan to decimate by 8 or by 4 (D depending on the bandwidth of the audio I need).

Thanks for your inputs!

There will surely be more questions arising soon, as I am currently trying to implement digital convolution filtering, but all this zero-padding, overlapping & adding is twisting my brain currently ;-).
 
Status
Not open for further replies.
Back
Top