Bat detector

Ok,

then, i think, i have a quite-old pending pull-request for Paul, which will speed up things, too..
it uses optimized assembler to copy the internal data at 2x speed.. sorry, have no time now, but it should be somewhere @ pauls github-rep, audio-library.

good night :)

if you need help for more optimized code, let me know..
 
Last edited:
The waterfall code did not make use of the full FFT1024 results. It took only the first 240 bins out of 512 usable bins.

I altered that and now the code takes every other bin for the display, so that the whole span of ultrasound can be displayed (480 bins, every second is displayed):

Also, the bin magnitude is taken times 5, so the colors are prettier, but this has to be tweaked when MIC gain is at highest setting (which I do not recommend because of all the digital noise etc.).

Code:
void waterfall(void) // thanks to Frank B !
{ 
  static int count = 0;
  uint16_t lbuf[320];  
  if (fft1024_1.available()) {
//    for (int i = 0; i < 240; i++) {
    for (int i = 0; i < 480; i+=2) {
//      int val = fft1024_1.read(i) * 65536.0; //v1
      int val = fft1024_1.read(i) * 65536.0 * 5; //v1
      //int val = fft1024_1.read(i*2, i*2+1 ) * 65536.0; //v2      
      lbuf[240-i/2-1] = tft.color565(
              min(255, val), //r
              (val/8>255)? 255 : val/8, //g
              ((255-val)>>1) <0? 0: (255-val)>>1 //b
             ); 
    }
    tft.writeRect(count, 16, 1, 239-16, (uint16_t*) &lbuf);
    tft.setScroll(319 - count);
    count++;
    if (count >= 320) count = 0;
  }
}

However, I have not understood how to alter the width of the upper block, that is left free for text . . . ? Maybe tomorrow ;-)
 
Last edited:
With the new waterfall SPECTROGRAM display (thanks to Frank B), a version number 1.0 is justified, I think.

- Add another button to pin 39 to toggle between the SPECTRUM display and the waterfall SPECTROGRAM.
- Use the newest ILI9341-t3 lib from yesterday - it contains a fix by Frank B that fixes the flickering when scrolling

As the waterfall needs a 1024 point FFT (which eats up a lot of processor power in high sample rates), the Teensy 3.5 only works flawlessly up to 96k sample rate. In 192k, the Teensy 3.5 does not deliver useable audio, hopefully the Teensy 3.6. delivers audio AND the waterfall without problems !?? Please test!

Frank

Teensy 3.5 & audio board & electret mic
Code:
Proc = 9.73 (43.68),  Mem = 10 (13)


Code:
/***********************************************************************
 *  (c) 2016  Frank DD4WH 2016_11_02 - MIT license 
 *  Teensy Bat & Ultrasound detector 
 *         
 *  Version 1.0 (first version with waterfall, thanks to Frank B)       
 *  https://forum.pjrc.com/threads/38988-Bat-detector
 *         
 *        made possible by the samplerate code by Frank Boesing, thanks Frank!
 * 
 *  tested on Teensy 3.5 + Teensy audio board 
 *  + standard tiny electret MIC soldered to the MIC Input of the audio board 
 *  
 *  
 *  User adjustments - with buttons
 *  
 *  MIC-GAIN                  33 + 34 -
 *  FREQUENCY                 35 + 36 -
 *  SAMPLE RATE               37 + 38 -
 *  WATERFALL/SPECTRUM        39  
 *  
 * Audio sample rate code - function setI2SFreq  
 * Copyright (c) 2016, Frank Bösing, f.boesing@gmx.de
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

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

#include <ILI9341_t3.h>
#include "font_Arial.h"

#define VERSION     " v0.3"

#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

// would be nice to use the fast DMA lib, but it does not work on my Teensy 3.5
//ILI9341_t3DMA tft = ILI9341_t3DMA(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);
ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);

#define BUTTON_MIC_GAIN_P       33     
#define BUTTON_MIC_GAIN_M       34
#define BUTTON_FREQ_P           35     
#define BUTTON_FREQ_M           36     
#define BUTTON_SAMPLE_RATE_P    37     
#define BUTTON_SAMPLE_RATE_M    38     
#define BUTTON_TOGGLE_WATERFALL 39     

Bounce mic_gain_P = Bounce(BUTTON_MIC_GAIN_P, 50); 
Bounce mic_gain_M = Bounce(BUTTON_MIC_GAIN_M, 50); 
Bounce freq_P = Bounce(BUTTON_FREQ_P, 50); 
Bounce freq_M = Bounce(BUTTON_FREQ_M, 50); 
Bounce sample_rate_P = Bounce(BUTTON_SAMPLE_RATE_P, 50);
Bounce sample_rate_M = Bounce(BUTTON_SAMPLE_RATE_M, 50);
Bounce toggle_waterfall = Bounce(BUTTON_TOGGLE_WATERFALL, 50);

#define SAMPLE_RATE_MIN               0
#define SAMPLE_RATE_44K               0
#define SAMPLE_RATE_48K               1
#define SAMPLE_RATE_88K               2
#define SAMPLE_RATE_96K               3
#define SAMPLE_RATE_176K              4
#define SAMPLE_RATE_192K              5
#define SAMPLE_RATE_MAX               5

// this audio comes from the codec by I2S2
AudioInputI2S            i2s_in; // MIC input
           
AudioSynthWaveformSine   sine1; // local oscillator

AudioEffectMultiply     mult1; // multiply = mix

AudioAnalyzeFFT256  myFFT; // for spectrum display
AudioAnalyzeFFT1024  fft1024_1; // for waterfall display

AudioOutputI2S           i2s_out; // headphone output          

AudioConnection      patchCord5(i2s_in,0,myFFT,0);
AudioConnection      patchCord8(i2s_in,0, fft1024_1,0);
AudioConnection          patchCord6(i2s_in,0, mult1,0); 
AudioConnection          patchCord7(sine1,0, mult1,1); 
AudioConnection          patchCord3(mult1, 0, i2s_out, 1);
AudioConnection          patchCord4(mult1, 0, i2s_out, 0);
AudioControlSGTL5000     sgtl5000_1;  

// Metro 1 second
Metro second = Metro(1000);
elapsedMillis since_bat_detection1;
elapsedMillis since_bat_detection2;

int count_help = 0;
int8_t waterfall_flag = 1;
int idx_t = 0;
int idx = 0;
int64_t sum;
float32_t mean;
int16_t FFT_bin [128]; 
int16_t FFT_max1 = 0;
uint32_t FFT_max_bin1 = 0;
int16_t FFT_mean1 = 0;
int16_t FFT_max2 = 0;
uint32_t FFT_max_bin2 = 0;
int16_t FFT_mean2 = 0;
//int16_t FFT_threshold = 0;
int16_t FFT_bat [3]; // max of 3 frequencies are being displayed
int16_t index_FFT;
int l_limit;
int u_limit;
int index_l_limit;
int index_u_limit;

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

int8_t mic_gain = 40; // start detecting with this MIC_GAIN in dB
int freq_real = 22000; // start detecting at this frequency
int sample_rate = SAMPLE_RATE_96K;
int sample_rate_real = 96000;
String text="96k";
int freq_LO = 7000;

typedef struct SR_Descriptor
{
    const int SR_n;
    const char* const f1;
    const char* const f2;
    const char* const f3;
    const char* const f4;
    const float32_t x_factor;
} SR_Desc;

// Text and position for the FFT spectrum display scale
const SR_Descriptor SR [SAMPLE_RATE_MAX + 1] =
{
    //   SR_n ,  f1, f2, f3, f4, x_factor = pixels per f1 kHz in spectrum display
    {  SAMPLE_RATE_44K,  "5", "10", "15", "20", 58.05}, // which means 58.05 pixels per 5 kHz
    {  SAMPLE_RATE_48K,  "5", "10", "15", "20", 53.33},
    {  SAMPLE_RATE_88K,  "10", "20", "30", "40", 58.05},
    {  SAMPLE_RATE_96K,  "10", "20", "30", "40", 53.33},
    {  SAMPLE_RATE_176K,  "20", "40", "60", "80", 58.05},
    {  SAMPLE_RATE_192K,  "20", "40", "60", "80", 53.33} // which means 53.33 pixels per 20kHz
};    


//const int myInput = AUDIO_INPUT_LINEIN;
const int myInput = AUDIO_INPUT_MIC;

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

  //setup pins with pullups
  pinMode(BUTTON_MIC_GAIN_P,INPUT_PULLUP);
  pinMode(BUTTON_MIC_GAIN_M,INPUT_PULLUP);
  pinMode(BUTTON_FREQ_P,INPUT_PULLUP);  
  pinMode(BUTTON_FREQ_M,INPUT_PULLUP);  
  pinMode(BUTTON_SAMPLE_RATE_P,INPUT_PULLUP);  
  pinMode(BUTTON_SAMPLE_RATE_M,INPUT_PULLUP);  
  pinMode(BUTTON_TOGGLE_WATERFALL,INPUT_PULLUP);  

  // Audio connections require memory. 
  AudioMemory(100);

  // Enable the audio shield. select input. and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.8);
  sgtl5000_1.micGain (mic_gain);
  sgtl5000_1.adcHighPassFilterDisable(); // does not help too much!
  
  pinMode( BACKLIGHT_PIN, OUTPUT );
  analogWrite( BACKLIGHT_PIN, 1023 );

  tft.begin();
  tft.setRotation( 3 );
  tft.fillScreen(ILI9341_BLACK);
  tft.setCursor(14, 7);
  tft.setTextColor(ILI9341_ORANGE);
  tft.setFont(Arial_12);
  tft.print("Teensy Bat Detector  "); tft.print(VERSION);
  tft.setTextColor(ILI9341_WHITE);

  display_settings();

  // sorry, couldn´t resist ;-)
  logo_head();
  for (int v = 0; v < 6; v++) {
  logo(true);
  delay(200);
  logo(false);
  delay(200);
  }

  set_sample_rate (sample_rate);

  set_freq_LO (freq_real);

} // END SETUP


void loop() {
   controls();
   if (waterfall_flag == 1) 
   {
      waterfall();
   }
   else 
   {
      spectrum();
   }
   check_processor();
}

void controls() {

  mic_gain_P.update();
  mic_gain_M.update();
  freq_P.update();
  freq_M.update();
  sample_rate_P.update();
  sample_rate_M.update();
  toggle_waterfall.update();
  // change MIC GAIN  
  if ( mic_gain_P.fallingEdge()) { 
      mic_gain = mic_gain + 2;
      if (mic_gain > 63) {
        mic_gain = 63;
      }
      set_mic_gain(mic_gain);
  }
  if ( mic_gain_M.fallingEdge()) { 
      mic_gain = mic_gain - 2;
      if (mic_gain < 0) {
        mic_gain = 0;
      }
      set_mic_gain(mic_gain);
  }

  // change FREQUENCY of the local oscillator
  if ( freq_P.fallingEdge()) { 
      freq_real = freq_real + 1000;
      if (freq_real > 85000) {
        freq_real = 85000;
      }
      set_freq_LO(freq_real);
  }
  if ( freq_M.fallingEdge()) { 
      freq_real = freq_real - 1000;
            if (freq_real < 0) {
        freq_real = 0;
      }
      set_freq_LO(freq_real);
  }

  // change sample rate
  if ( sample_rate_P.fallingEdge()) { 
      sample_rate = sample_rate + 1;
            if (sample_rate > SAMPLE_RATE_MAX) {
        sample_rate = SAMPLE_RATE_MAX;
      }
        set_sample_rate (sample_rate);
  }
  if ( sample_rate_M.fallingEdge()) { 
      sample_rate = sample_rate - 1;
            if (sample_rate < SAMPLE_RATE_MIN) {
        sample_rate = SAMPLE_RATE_MIN;
      }
        set_sample_rate (sample_rate);
  }
  if ( toggle_waterfall.fallingEdge()) { 
        if (waterfall_flag == 1) 
        {
          waterfall_flag = 0;
          tft.setScroll(0);
          tft.setRotation( 3 );
          tft.fillScreen(ILI9341_BLACK);
          tft.setCursor(14, 7);
          tft.setTextColor(ILI9341_ORANGE);
          tft.setFont(Arial_12);
          tft.print("Teensy Bat Detector  "); tft.print(VERSION);
          tft.setTextColor(ILI9341_WHITE);
          display_settings();
          prepare_spectrum_display();
          logo_head();
          logo(true);
        }
        else 
        {
          waterfall_flag = 1;
        }
  }
} // END function "controls"

void       set_mic_gain(int8_t gain) {
    AudioNoInterrupts();
    sgtl5000_1.micGain (mic_gain);
    AudioInterrupts();
    display_settings();    
} // end function set_mic_gain

void       set_freq_LO(int freq) {
    // audio lib thinks we are still in 44118sps sample rate
    // therefore we have to scale the frequency of the local oscillator
    // in accordance with the REAL sample rate
    freq_LO = freq * (AUDIO_SAMPLE_RATE_EXACT / sample_rate_real); 
    // if we switch to LOWER samples rates, make sure the running LO 
    // frequency is allowed ( < 22k) ! If not, adjust consequently, so that
    // LO freq never goes up 22k, also adjust the variable freq_real  
    if(freq_LO > 22000) {
      freq_LO = 22000;
      freq_real = freq_LO * (sample_rate_real / AUDIO_SAMPLE_RATE_EXACT) + 9;
    }
    AudioNoInterrupts();
    sine1.frequency(freq_LO);
    AudioInterrupts();
    display_settings();
} // END of function set_freq_LO

void      display_settings() {
    tft.fillRect(14,32,200,17,ILI9341_BLACK);
    tft.setCursor(14, 32);
    tft.setFont(Arial_12); 
    tft.print("gain: "); tft.print (mic_gain);
    tft.print("     "); 
    tft.print("freq: "); tft.print (freq_real);
    tft.print("    "); 
    tft.fillRect(232,32,88,17,ILI9341_BLACK);
    tft.setCursor(232, 32);
    tft.print("       "); 
    tft.print (text);
       
 /*  // only for debugging  
    tft.fillRect(0,122,200,17,ILI9341_BLACK);
    tft.setCursor(0, 122);
    tft.print("LO: "); tft.print (freq_LO);
    tft.print("   "); 
    */
}

void      set_sample_rate (int sr) {
  switch (sr) {
    case SAMPLE_RATE_44K:
    sample_rate_real = 44100;
    text = "44.1k";
    break;
    case SAMPLE_RATE_48K:
    sample_rate_real = 48000;
    text = "48k";
    break;
    case SAMPLE_RATE_88K:
    sample_rate_real = 88200;
    text = "88.2k";
    break;
    case SAMPLE_RATE_96K:
    sample_rate_real = 96000;
    text = "96k";
    break;
    case SAMPLE_RATE_176K:
    sample_rate_real = 176400;
    text = "176k";
    break;
    case SAMPLE_RATE_192K:
    sample_rate_real = 192000;
    text = "192k";
    break;
  }
    AudioNoInterrupts();
    setI2SFreq (sample_rate_real); 
    delay(200); // this delay seems to be very essential !
    set_freq_LO (freq_real);
    AudioInterrupts();
    delay(20);
    display_settings();
    prepare_spectrum_display();
} // END function set_sample_rate

void prepare_spectrum_display() {
    int base_y = 211; 
    int b_x = 10;
    int x_f = SR[sample_rate].x_factor;
    tft.fillRect(0,base_y,320,240 - base_y,ILI9341_BLACK);
//    tft.drawFastHLine(b_x, base_y + 2, 256, ILI9341_PURPLE);  
//    tft.drawFastHLine(b_x, base_y + 3, 256, ILI9341_PURPLE);  
    tft.drawFastHLine(b_x, base_y + 2, 256, ILI9341_MAROON);  
    tft.drawFastHLine(b_x, base_y + 3, 256, ILI9341_MAROON);  
    // vertical lines
    tft.drawFastVLine(b_x - 4, base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine(b_x - 3, base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f + 1 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 2 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 2 + 1 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 3 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 3 + 1 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 4 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 4 + 1 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 0.5 + b_x,  base_y + 1, 6, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 1.5 + b_x,  base_y + 1, 6, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 2.5 + b_x,  base_y + 1, 6, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 3.5 + b_x,  base_y + 1, 6, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 4.5 + b_x,  base_y + 1, 6, ILI9341_YELLOW);  
    // text
    tft.setTextColor(ILI9341_WHITE);
    tft.setFont(Arial_9);
    int text_y_offset = 16;
    int text_x_offset = - 5;
    tft.setCursor (b_x + text_x_offset + 256, base_y + text_y_offset);
    tft.print("kHz");
    // zero
    tft.setCursor (b_x + text_x_offset, base_y + text_y_offset);
    tft.print("0");
    tft.setCursor (b_x + x_f + text_x_offset, base_y + text_y_offset);
    tft.print(SR[sample_rate].f1);
    tft.setCursor (b_x + x_f * 2 + text_x_offset, base_y + text_y_offset);
    tft.print(SR[sample_rate].f2);
    tft.setCursor (b_x + x_f *3 + text_x_offset, base_y + text_y_offset);
    tft.print(SR[sample_rate].f3);
    tft.setCursor (b_x + x_f *4 + text_x_offset, base_y + text_y_offset);
    tft.print(SR[sample_rate].f4);
    tft.setFont(Arial_14);
} // END prepare_spectrum_display

 void spectrum() { // spectrum analyser code by rheslip - modified
     if (myFFT.available()) {
    int scale;
    scale = 5;
  for (int16_t x = 2; x < 128; x++) {
     FFT_bin[x] = abs(myFFT.output[x]); 
     int bar = (FFT_bin[x] * scale);
     if (bar >175) bar=175;
     // this is a very simple first order IIR filter to smooth the reaction of the bars
     bar = 0.05 * bar + 0.95 * 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.drawPixel(x*2+10, 210-barm[x], ILI9341_BLACK);
     tft.drawPixel(x*2+10, 210-bar, ILI9341_WHITE);

//     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;
  }
     search_bats();     
  } //end if
} // end void spectrum

void  search_bats() {
    // the array FFT_bin contains the results of the 256 point FFT --> 128 magnitude values
    // we look for bins that have a high amplitude compared to the mean noise, indicating the presence of ultrasound
    // 1. only search in those parts of the array > 14kHz and not around +-10kHz of the LO freq -->
    //    thus it is best, if I search in two parts --> 14kHz to freq_real-10k AND freq_real+10k to sample_rate/2
    // 2. determine mean and max in both parts of the array
    // 3. if we find a bin that is much larger than the mean (take care of situations where mean is zero!) --> identify the no. of the bin
    // 4. determine frequency of that bin (depends on sample_rate_real)
    //    a.) by simply multiplying bin# with bin width
    //    b.) by using an interpolator (not (yet) implemented)
    // 5. display frequency in bold and RED for 1-2 sec. (TODO: also display possible bat species ;-)) and then delete
    // goto 1.    

    // search array in two parts: 
    //  1.)  14k to (freq_real - 10k)
    // upper and lower limits for maximum search
     l_limit = 14000;
     u_limit = freq_real - 10000;
     index_l_limit =  (l_limit * 256 / sample_rate_real); 
     index_u_limit =  (u_limit * 256 / sample_rate_real); 
//     Serial.print(index_l_limit); Serial.print ("  "); Serial.println(index_u_limit);

     if (index_u_limit > index_l_limit) { 
        arm_max_q15(&FFT_bin[index_l_limit], index_u_limit - index_l_limit, &FFT_max1, &FFT_max_bin1);
            // this is the efficient CMSIS function to calculate the mean
        arm_mean_q15(&FFT_bin[index_l_limit], index_u_limit - index_l_limit, &FFT_mean1);
            // shift bin_max because we have not searched through ALL the 256 FFT bins
        FFT_max_bin1 = FFT_max_bin1 + index_l_limit;
     }
//     Serial.print(FFT_max1); Serial.print ("  "); Serial.println(FFT_mean1);

    //  2.)  (freq_real + 10k) to 256 
    // upper and lower limits for maximum search
     l_limit = freq_real + 10000;
     if (l_limit < 14000) {
      l_limit = 14000;
     }
     index_l_limit = (l_limit * 256 / sample_rate_real); 
     index_u_limit = 128; 
//     Serial.print(index_l_limit); Serial.print ("  "); Serial.println(index_u_limit);
     if (index_u_limit > index_l_limit) { 
        arm_max_q15(&FFT_bin[index_l_limit], index_u_limit - index_l_limit, &FFT_max2, &FFT_max_bin2);
            // this is the efficient CMSIS function to calculate the mean
        arm_mean_q15(&FFT_bin[index_l_limit], index_u_limit - index_l_limit, &FFT_mean2);
            // shift bin_max because we have not searched through ALL the 128 FFT bins
        FFT_max_bin2 = FFT_max_bin2 + index_l_limit;
     }
//         Serial.print(FFT_max2); Serial.print ("  "); Serial.println(FFT_mean2);

      display_found_bats();
    FFT_max1 = 0;
    FFT_mean1 = 0;
    FFT_max_bin1 = 0;
    FFT_max2 = 0;
    FFT_mean2 = 0;
    FFT_max_bin2 = 0;
}  // END function search_bats()

void display_found_bats() {
    int sign_x = 148; 
    int sign_y = 57; 
    if (since_bat_detection1 > 6000 && since_bat_detection2 > 6000) {
// DELETE red BAT sign
//      Serial.println("RED SIGN DELETED");      
      tft.fillRect(sign_x, sign_y, 64, 22, ILI9341_BLACK);
      tft.fillRect(sign_x + 86, sign_y + 4, 320 - 86 - sign_x, 40, ILI9341_BLACK);
//      since_bat_detection = 0;
    }

// DELETE FREQUENCY 1
    if (since_bat_detection1 > 6000) {
      tft.fillRect(sign_x + 86, sign_y + 4, 320 - 86 - sign_x, 14, ILI9341_BLACK);
//      since_bat_detection1 = 0;
    }

// DELETE FREQUENCY 2
    if (since_bat_detection2 > 6000) {
      tft.fillRect(sign_x + 86, sign_y + 30, 320 - 86 - sign_x, 14, ILI9341_BLACK);
//      since_bat_detection2 = 0;
    }
// PRINT RED BAT SIGN    
    if(FFT_max1 > (FFT_mean1 + 5) || FFT_max2 > (FFT_mean2 + 5)) {
//      Serial.println("BAT");
      tft.fillRect(sign_x, sign_y, 64, 22, ILI9341_RED);
      tft.setTextColor(ILI9341_WHITE);
      tft.setFont(Arial_14);
      tft.setCursor(sign_x + 2, sign_y + 4);
      tft.print("B A T !");      
//      since_bat_detection = 0;
    }
// PRINT frequency 1
    if(FFT_max1 > FFT_mean1 + 5) {
      tft.fillRect(sign_x + 86, sign_y + 4, 320 - 86 - sign_x, 14, ILI9341_BLACK);
      tft.setTextColor(ILI9341_ORANGE);
      tft.setFont(Arial_12);
      tft.setCursor(sign_x + 86, sign_y + 4);
      tft.print(FFT_max_bin1 * sample_rate_real / 256);
      tft.print(" Hz");      
      since_bat_detection1 = 0;
    
//      Serial.print ("max Freq 1: ");
//      Serial.println(FFT_max_bin1 * sample_rate_real / 256);
    }
    
// PRINT frequency 2    
    if(FFT_max2 > FFT_mean2 + 5) { 
      tft.fillRect(sign_x + 86, sign_y + 30, 320 - 86 - sign_x, 14, ILI9341_BLACK);
      tft.setTextColor(ILI9341_ORANGE);
      tft.setFont(Arial_12);
      tft.setCursor(sign_x + 86, sign_y + 30);
      tft.print(FFT_max_bin2 * sample_rate_real / 256);
      tft.print(" Hz");      
//      Serial.print ("max Freq 2: ");
//      Serial.println(FFT_max_bin2 * sample_rate_real / 256);
      since_bat_detection2 = 0;
    }
      tft.setTextColor(ILI9341_WHITE);
} // END function display_found_bats


// set samplerate code by Frank Boesing 
void setI2SFreq(int freq) {
  typedef struct {
    uint8_t mult;
    uint16_t div;
  } tmclk;

  const int numfreqs = 14;
  const int samplefreqs[numfreqs] = { 8000, 11025, 16000, 22050, 32000, 44100, (int)44117.64706 , 48000, 88200, (int)44117.64706 * 2, 96000, 176400, (int)44117.64706 * 4, 192000};

#if (F_PLL==16000000)
  const tmclk clkArr[numfreqs] = {{16, 125}, {148, 839}, {32, 125}, {145, 411}, {64, 125}, {151, 214}, {12, 17}, {96, 125}, {151, 107}, {24, 17}, {192, 125}, {127, 45}, {48, 17}, {255, 83} };
#elif (F_PLL==72000000)
  const tmclk clkArr[numfreqs] = {{32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {128, 1125}, {98, 625}, {8, 51}, {64, 375}, {196, 625}, {16, 51}, {128, 375}, {249, 397}, {32, 51}, {185, 271} };
#elif (F_PLL==96000000)
  const tmclk clkArr[numfreqs] = {{8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {32, 375}, {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {32, 125}, {151, 321}, {8, 17}, {64, 125} };
#elif (F_PLL==120000000)
  const tmclk clkArr[numfreqs] = {{32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {128, 1875}, {205, 2179}, {8, 85}, {64, 625}, {89, 473}, {16, 85}, {128, 625}, {178, 473}, {32, 85}, {145, 354} };
#elif (F_PLL==144000000)
  const tmclk clkArr[numfreqs] = {{16, 1125}, {49, 2500}, {32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {4, 51}, {32, 375}, {98, 625}, {8, 51}, {64, 375}, {196, 625}, {16, 51}, {128, 375} };
#elif (F_PLL==168000000)
  const tmclk clkArr[numfreqs] = {{32, 2625}, {21, 1250}, {64, 2625}, {21, 625}, {128, 2625}, {42, 625}, {8, 119}, {64, 875}, {84, 625}, {16, 119}, {128, 875}, {168, 625}, {32, 119}, {189, 646} };
#elif (F_PLL==180000000)
  const tmclk clkArr[numfreqs] = {{46, 4043}, {49, 3125}, {73, 3208}, {98, 3125}, {183, 4021}, {196, 3125}, {16, 255}, {128, 1875}, {107, 853}, {32, 255}, {219, 1604}, {214, 853}, {64, 255}, {219, 802} };
#elif (F_PLL==192000000)
  const tmclk clkArr[numfreqs] = {{4, 375}, {37, 2517}, {8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {1, 17}, {8, 125}, {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {32, 125} };
#elif (F_PLL==216000000)
  const tmclk clkArr[numfreqs] = {{32, 3375}, {49, 3750}, {64, 3375}, {49, 1875}, {128, 3375}, {98, 1875}, {8, 153}, {64, 1125}, {196, 1875}, {16, 153}, {128, 1125}, {226, 1081}, {32, 153}, {147, 646} };
#elif (F_PLL==240000000)
  const tmclk clkArr[numfreqs] = {{16, 1875}, {29, 2466}, {32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {4, 85}, {32, 625}, {205, 2179}, {8, 85}, {64, 625}, {89, 473}, {16, 85}, {128, 625} };
#endif

  for (int f = 0; f < numfreqs; f++) {
    if ( freq == samplefreqs[f] ) {
      while (I2S0_MCR & I2S_MCR_DUF) ;
      I2S0_MDR = I2S_MDR_FRACT((clkArr[f].mult - 1)) | I2S_MDR_DIVIDE((clkArr[f].div - 1));
      return;
    }
  }
}

//  bat logo taken from Shezzy:  
//  http://sisterzpsptreasures.freeforums.org/easy-animated-bat-t77.html
//  copyright free graphics
//  "The image you create by following this tutorial belongs to you and you may do whatever you want with it."
void logo(bool wing1) {
      // Logo ;-)      
     int x = 265;
     int y = 10; 
//      grey background and white line rectangle around it 
     tft.fillRect(x + 23, y - 2,16,14,ILI9341_DARKGREY);
     tft.fillRect(x - 2, y - 2,16,14,ILI9341_DARKGREY);
//     tft.fillRect(x - 2, y - 2, 41, 14,ILI9341_DARKGREY);
    if (wing1) {
     //Wing1
     tft.drawFastHLine(x + 2, y + 0, 5, ILI9341_BLACK);
     tft.drawFastHLine(x + 31, y + 0, 5, ILI9341_BLACK);
     tft.drawPixel(x + 1, y + 1, ILI9341_BLACK);
     tft.drawPixel(x + 35, y + 1, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 1, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 1, ILI9341_BLACK);
//     tft.drawPixel(x + 6, y + 2, ILI9341_BLACK);
//     tft.drawPixel(x + 30, y + 2, ILI9341_BLACK);
     tft.drawFastVLine(x , y + 2, 7, ILI9341_BLACK);
     tft.drawFastVLine(x + 36 , y + 2, 7, ILI9341_BLACK);
     tft.drawFastHLine(x + 7, y + 2, 2, ILI9341_BLACK);
     tft.drawFastHLine(x + 28, y + 2, 2, ILI9341_BLACK);
     tft.drawPixel(x + 13, y + 2, ILI9341_BLACK);
     tft.drawPixel(x + 23, y + 2, ILI9341_BLACK);
     tft.drawFastHLine(x + 9, y + 3, 4, ILI9341_BLACK);
     tft.drawFastHLine(x + 24, y + 3, 4, ILI9341_BLACK);
     tft.drawFastHLine(x + 3, y + 5, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 31, y + 5, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 7, y + 5, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 27, y + 5, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 11, y + 5, 2, ILI9341_BLACK);
     tft.drawFastHLine(x + 24, y + 5, 2, ILI9341_BLACK);
     tft.drawPixel(x + 2, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 10, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 13, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 23, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 26, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 34, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 1, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 10, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 26, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 35, y + 7, ILI9341_BLACK);
    }
    else {
     //Wing2
     tft.drawFastHLine(x + 2, y + 2, 5, ILI9341_BLACK);
     tft.drawFastHLine(x + 31, y + 2, 5, ILI9341_BLACK);
     tft.drawPixel(x + 1, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 35, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 3, ILI9341_BLACK);
//     tft.drawPixel(x + 6, y + 4, ILI9341_BLACK);
//     tft.drawPixel(x + 30, y + 4, ILI9341_BLACK);
     tft.drawFastVLine(x , y + 4, 7, ILI9341_BLACK);
     tft.drawFastVLine(x + 36 , y + 4, 7, ILI9341_BLACK);
     tft.drawFastHLine(x + 7, y + 4, 2, ILI9341_BLACK);
     tft.drawFastHLine(x + 28, y + 4, 2, ILI9341_BLACK);
//     tft.drawPixel(x + 13, y + 4, ILI9341_BLACK);
//     tft.drawPixel(x + 23, y + 4, ILI9341_BLACK);
     tft.drawFastHLine(x + 9, y + 5, 5, ILI9341_BLACK);
     tft.drawFastHLine(x + 24, y + 5, 5, ILI9341_BLACK);
     tft.drawFastHLine(x + 3, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 31, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 7, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 27, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 11, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 24, y + 7, 3, ILI9341_BLACK);
     tft.drawPixel(x + 2, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 10, y + 8, ILI9341_BLACK);
//     tft.drawPixel(x + 13, y + 8, ILI9341_BLACK);
//     tft.drawPixel(x + 23, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 26, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 34, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 1, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 10, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 26, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 35, y + 9, ILI9341_BLACK);
    }
}

void logo_head() {
     int x = 265;
     int y = 10; 
     tft.fillRect(x + 14, y - 2,9,14,ILI9341_DARKGREY);
     // Head 
     tft.drawPixel(x + 15, y + 2, ILI9341_BLACK);
     tft.drawPixel(x + 21, y + 2, ILI9341_BLACK);
     tft.drawPixel(x + 14, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 16, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 20, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 22, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 14, y + 4, ILI9341_BLACK);
     tft.drawPixel(x + 22, y + 4, ILI9341_BLACK);
     tft.drawFastHLine(x + 17, y + 4, 3, ILI9341_BLACK);
     tft.drawPixel(x + 14, y + 5, ILI9341_BLACK);
     tft.drawPixel(x + 22, y + 5, ILI9341_BLACK);
     tft.drawPixel(x + 16, y + 5, ILI9341_RED);
     tft.drawPixel(x + 20, y + 5, ILI9341_RED);
     tft.drawPixel(x + 17, y + 6, ILI9341_RED);
     tft.drawPixel(x + 19, y + 6, ILI9341_RED);
     tft.drawPixel(x + 14, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 22, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 15, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 21, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 16, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 20, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 17, y + 8, ILI9341_WHITE);
     tft.drawPixel(x + 19, y + 8, ILI9341_WHITE);
     tft.drawFastHLine(x + 17, y + 9, 3, ILI9341_BLACK);
}

void check_processor() {
      if (second.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();
    }
} // END function check_processor


void waterfall(void) // thanks to Frank B !
{ 
  static int count = 0;
  uint16_t lbuf[320];  
  if (fft1024_1.available()) {
//    for (int i = 0; i < 240; i++) {
    for (int i = 0; i < 480; i+=2) {
//      int val = fft1024_1.read(i) * 65536.0; //v1
      int val = fft1024_1.read(i) * 65536.0 * 5; //v1
      //int val = fft1024_1.read(i*2, i*2+1 ) * 65536.0; //v2      
      lbuf[240-i/2-1] = tft.color565(
              min(255, val), //r
              (val/8>255)? 255 : val/8, //g
              ((255-val)>>1) <0? 0: (255-val)>>1 //b
             ); 
    }
//    tft.writeRect(count, 16, 1, 239-16, (uint16_t*) &lbuf);
    tft.writeRect(count, 32, 1, 239-32, (uint16_t*) &lbuf);
    tft.setScroll(319 - count);
    count++;
    if (count >= 320) count = 0;
    count_help = count;
  }
}

TODO:
- delete FFT256 and reuse the FFT1024 for the spectrum display
- use FFT1024 with four times higher accuracy for the estimation of the detected bat frequencies
- make waterfall look nicer and display local oscillator frequency during waterfall
- display detected frequencies and RED BAT alert during waterfall (may not be possible, I am not sure)
 
Last edited:
Do you do GitHub?

...that would be a lot easier way of sharing (and collaborating) than copying-pasting in this forum...

Chip
 
Hi Chip,

thanks for the suggestion! Yes, I tried git (and put a whole lot of time and blood, sweat and tears into it) and I kind of use it for another project of mine (https://github.com/DD4WH/mchf-github), but it is very hard for me as non-programmer to deal with that huge functionality . . .

Time for me is limited (as for everyone), so I would like to set my priorities into the development of the code, not managing git (which can take more time than writing code, at least for me). Yes, I have all the links and books and excellent resources on git, but that´s really something I would not want to deal with at this time ;-). And: we only have one file with 750 lines, that´s not -yet- so big that we couldn´t deal with that. Let´s talk about bat detecting :).

If you would like to contribute, I would really be very happy if you do so! Let´s try it with copy/paste first and if the no. of contributions/contributors becomes to large to handle that, I will consider using git again ;-).

Have fun!

Frank
 
Last edited:
Hmm, recording the ultrasound as a RAW onto the SD card is a feature that professional bat detectors have. That enables you to analyse the file after your recording night with special software on your PC.

Recording is implemented now as alpha version.

Button 31: Start Recording
Button 30: Stop
Button 29: Play Recording

Recording and playback works in 44.1k and 48k sample rate. With 96k the recording is stuttering and the playback is in the right speed, but the recording is much shorter!?

Problem:
- stuttering recording in 96k sample rate

Is that a problem of my code or of the "slow" speed of the Teensy 3.5 I am using?
Maybe I have to adjust the no. of buffers or the buffer size for recording in higher sample rates?

Code:
/***********************************************************************
 *  (c) 2016  Frank DD4WH 2016_11_02 - MIT license 
 *  Teensy Bat & Ultrasound detector 
 *         
 *  Version 1.1 (recording alpha)        
 *  https://forum.pjrc.com/threads/38988-Bat-detector
 *         
 *        made possible by the samplerate code by Frank Boesing, thanks Frank!
 * 
 *  tested on Teensy 3.5 + Teensy audio board 
 *  + standard tiny electret MIC soldered to the MIC Input of the audio board 
 *  
 *  
 *  User adjustments - with buttons
 *  
 *  MIC-GAIN                  33 + 34 -
 *  FREQUENCY                 35 + 36 -
 *  SAMPLE RATE               37 + 38 -
 *  WATERFALL/SPECTRUM        39  
 *  
 * Audio sample rate code - function setI2SFreq  
 * Copyright (c) 2016, Frank Bösing, f.boesing@gmx.de
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

//#include <Time.h>
//#include <TimeLib.h>
#include <SD.h>
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <Bounce.h>
#include <Metro.h>

#include <ILI9341_t3.h>
#include "font_Arial.h"

/*time_t getTeensy3Time()
{
  return Teensy3Clock.get();
}
int helpmin; // definitions for time and date adjust - Menu
int helphour;
int helpday;
int helpmonth;
int helpyear;
int helpsec;
uint8_t hour10_old;
uint8_t hour1_old;
uint8_t minute10_old;
uint8_t minute1_old;
uint8_t second10_old;
uint8_t second1_old;
bool timeflag = 0;
*/

#define VERSION     " v0.3"

#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

// would be nice to use the fast DMA lib, but it does not work on my Teensy 3.5
//ILI9341_t3DMA tft = ILI9341_t3DMA(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);
ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);

#define BUTTON_MIC_GAIN_P       33     
#define BUTTON_MIC_GAIN_M       34
#define BUTTON_FREQ_P           35     
#define BUTTON_FREQ_M           36     
#define BUTTON_SAMPLE_RATE_P    37     
#define BUTTON_SAMPLE_RATE_M    38     
#define BUTTON_TOGGLE_WATERFALL 39
#define BUTTON_RECORD           31
#define BUTTON_STOP             30
#define BUTTON_PLAY             29

Bounce mic_gain_P = Bounce(BUTTON_MIC_GAIN_P, 50); 
Bounce mic_gain_M = Bounce(BUTTON_MIC_GAIN_M, 50); 
Bounce freq_P = Bounce(BUTTON_FREQ_P, 50); 
Bounce freq_M = Bounce(BUTTON_FREQ_M, 50); 
Bounce sample_rate_P = Bounce(BUTTON_SAMPLE_RATE_P, 50);
Bounce sample_rate_M = Bounce(BUTTON_SAMPLE_RATE_M, 50);
Bounce toggle_waterfall = Bounce(BUTTON_TOGGLE_WATERFALL, 50);
Bounce button_Record = Bounce(BUTTON_RECORD, 50);
Bounce button_Stop = Bounce(BUTTON_STOP, 50);
Bounce button_Play = Bounce(BUTTON_PLAY, 50);
  
#define SAMPLE_RATE_MIN               0
#define SAMPLE_RATE_44K               0
#define SAMPLE_RATE_48K               1
#define SAMPLE_RATE_88K               2
#define SAMPLE_RATE_96K               3
#define SAMPLE_RATE_176K              4
#define SAMPLE_RATE_192K              5
#define SAMPLE_RATE_MAX               5

// this audio comes from the codec by I2S2
AudioInputI2S               i2s_in; // MIC input
AudioRecordQueue            recorder; 
AudioSynthWaveformSine      sine1; // local oscillator
AudioEffectMultiply         mult1; // multiply = mix
AudioAnalyzeFFT256          myFFT; // for spectrum display
AudioAnalyzeFFT1024         fft1024_1; // for waterfall display
AudioPlaySdRaw              player; 
AudioMixer4                 mix1;
AudioOutputI2S              i2s_out; // headphone output          

AudioConnection patch1      (i2s_in, 0,myFFT, 0);
AudioConnection patch2      (i2s_in, 0, fft1024_1, 0);
AudioConnection patch3      (i2s_in, 0, recorder, 0);
AudioConnection patch4      (i2s_in, 0, mix1, 0);
AudioConnection patch5      (player, 0, mix1, 1);
AudioConnection patch6      (mix1, 0, mult1, 0);
AudioConnection patch7      (sine1, 0, mult1, 1);
AudioConnection patch8      (mult1, 0, i2s_out, 0);
AudioConnection patch9      (mult1, 0, i2s_out, 1);

AudioControlSGTL5000        sgtl5000_1;  

// Metro 1 second
Metro second = Metro(1000);
elapsedMillis since_bat_detection1;
elapsedMillis since_bat_detection2;

const int8_t    MODE_STOP = 0;
const int8_t    MODE_REC = 1;
const int8_t    MODE_PLAY = 2;

int mode = MODE_STOP; 
File frec; // audio is recorded to this file first
int file_number = 0;

int count_help = 0;
int8_t waterfall_flag = 0;
int idx_t = 0;
int idx = 0;
int64_t sum;
float32_t mean;
int16_t FFT_bin [128]; 
int16_t FFT_max1 = 0;
uint32_t FFT_max_bin1 = 0;
int16_t FFT_mean1 = 0;
int16_t FFT_max2 = 0;
uint32_t FFT_max_bin2 = 0;
int16_t FFT_mean2 = 0;
//int16_t FFT_threshold = 0;
int16_t FFT_bat [3]; // max of 3 frequencies are being displayed
int16_t index_FFT;
int l_limit;
int u_limit;
int index_l_limit;
int index_u_limit;

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

int8_t mic_gain = 40; // start detecting with this MIC_GAIN in dB
int freq_real = 22000; // start detecting at this frequency
int sample_rate = SAMPLE_RATE_96K;
int sample_rate_real = 96000;
String text="96k";
int freq_LO = 7000;

typedef struct SR_Descriptor
{
    const int SR_n;
    const char* const f1;
    const char* const f2;
    const char* const f3;
    const char* const f4;
    const float32_t x_factor;
} SR_Desc;

// Text and position for the FFT spectrum display scale
const SR_Descriptor SR [SAMPLE_RATE_MAX + 1] =
{
    //   SR_n ,  f1, f2, f3, f4, x_factor = pixels per f1 kHz in spectrum display
    {  SAMPLE_RATE_44K,  "5", "10", "15", "20", 58.05}, // which means 58.05 pixels per 5 kHz
    {  SAMPLE_RATE_48K,  "5", "10", "15", "20", 53.33},
    {  SAMPLE_RATE_88K,  "10", "20", "30", "40", 58.05},
    {  SAMPLE_RATE_96K,  "10", "20", "30", "40", 53.33},
    {  SAMPLE_RATE_176K,  "20", "40", "60", "80", 58.05},
    {  SAMPLE_RATE_192K,  "20", "40", "60", "80", 53.33} // which means 53.33 pixels per 20kHz
};    

//const int myInput = AUDIO_INPUT_LINEIN;
const int myInput = AUDIO_INPUT_MIC;

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

//setup pins with pullups
  pinMode(BUTTON_MIC_GAIN_P,INPUT_PULLUP);
  pinMode(BUTTON_MIC_GAIN_M,INPUT_PULLUP);
  pinMode(BUTTON_FREQ_P,INPUT_PULLUP);  
  pinMode(BUTTON_FREQ_M,INPUT_PULLUP);  
  pinMode(BUTTON_SAMPLE_RATE_P,INPUT_PULLUP);  
  pinMode(BUTTON_SAMPLE_RATE_M,INPUT_PULLUP);  
  pinMode(BUTTON_TOGGLE_WATERFALL,INPUT_PULLUP);  
  pinMode(BUTTON_RECORD,INPUT_PULLUP);  
  pinMode(BUTTON_STOP,INPUT_PULLUP);  
  pinMode(BUTTON_PLAY,INPUT_PULLUP);  

  // Audio connections require memory. 
  AudioMemory(100);
/*
  setSyncProvider(getTeensy3Time);
*/
// Enable the audio shield. select input. and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.8);
  sgtl5000_1.micGain (mic_gain);
  sgtl5000_1.adcHighPassFilterDisable(); // does not help too much!

// Init SD card use
  SPI.setMOSI(7); 
  SPI.setSCK(14);
  if(!(SD.begin(10))) 
  {
      while (1) {
          Serial.println("Unable to access the SD card");
          delay(500);  
      }
  }


// Init TFT display  
  pinMode( BACKLIGHT_PIN, OUTPUT );
  analogWrite( BACKLIGHT_PIN, 1023 );

  tft.begin();
  tft.setRotation( 3 );
  tft.fillScreen(ILI9341_BLACK);
  tft.setCursor(14, 7);
  tft.setTextColor(ILI9341_ORANGE);
  tft.setFont(Arial_12);
  tft.print("Teensy Bat Detector  "); tft.print(VERSION);
  tft.setTextColor(ILI9341_WHITE);

  display_settings();

  // sorry, couldn´t resist ;-)
  logo_head();
  for (int v = 0; v < 6; v++) {
  logo(true);
  delay(200);
  logo(false);
  delay(200);
  }

  set_sample_rate (sample_rate);

  set_freq_LO (freq_real);

  mix1.gain(0,1); 
  mix1.gain(1,1); 


} // END SETUP


void loop() {
   controls();
   if (waterfall_flag == 1) 
   {
      waterfall();
   }
   else 
   {
      spectrum();
   }
   check_processor();
}

void controls() {

// first, check buttons
  mic_gain_P.update();
  mic_gain_M.update();
  freq_P.update();
  freq_M.update();
  sample_rate_P.update();
  sample_rate_M.update();
  toggle_waterfall.update();
  button_Record.update();
  button_Stop.update();
  button_Play.update();

  // Respond to button presses
  if (button_Record.fallingEdge()) {
    Serial.println("Record Button Press");
    if (mode == MODE_PLAY) stopPlaying();
    if (mode == MODE_STOP) startRecording();
  }
  if (button_Stop.fallingEdge()) {
    Serial.println("Stop Button Press");
    if (mode == MODE_REC) stopRecording();
    if (mode == MODE_PLAY) stopPlaying();
  }
  if (button_Play.fallingEdge()) {
    Serial.println("Play Button Press");
    if (mode == MODE_REC) stopRecording();
    if (mode == MODE_STOP) startPlaying();
  }

  // If we're playing or recording, carry on...
  if (mode == MODE_REC) {
    continueRecording();
  }
  if (mode == MODE_PLAY) {
    continuePlaying();
  }

  // change MIC GAIN  
  if ( mic_gain_P.fallingEdge()) { 
      mic_gain = mic_gain + 2;
      if (mic_gain > 63) {
        mic_gain = 63;
      }
      set_mic_gain(mic_gain);
  }
  if ( mic_gain_M.fallingEdge()) { 
      mic_gain = mic_gain - 2;
      if (mic_gain < 0) {
        mic_gain = 0;
      }
      set_mic_gain(mic_gain);
  }

  // change FREQUENCY of the local oscillator
  if ( freq_P.fallingEdge()) { 
      freq_real = freq_real + 1000;
      if (freq_real > 85000) {
        freq_real = 85000;
      }
      set_freq_LO(freq_real);
  }
  if ( freq_M.fallingEdge()) { 
      freq_real = freq_real - 1000;
            if (freq_real < 0) {
        freq_real = 0;
      }
      set_freq_LO(freq_real);
  }

  // change sample rate
  if ( sample_rate_P.fallingEdge()) { 
      sample_rate = sample_rate + 1;
            if (sample_rate > SAMPLE_RATE_MAX) {
        sample_rate = SAMPLE_RATE_MAX;
      }
        set_sample_rate (sample_rate);
  }
  if ( sample_rate_M.fallingEdge()) { 
      sample_rate = sample_rate - 1;
            if (sample_rate < SAMPLE_RATE_MIN) {
        sample_rate = SAMPLE_RATE_MIN;
      }
        set_sample_rate (sample_rate);
  }
  if ( toggle_waterfall.fallingEdge()) { 
        if (waterfall_flag == 1) 
        {
          waterfall_flag = 0;
          tft.setScroll(0);
          tft.setRotation( 3 );
          tft.fillScreen(ILI9341_BLACK);
          tft.setCursor(14, 7);
          tft.setTextColor(ILI9341_ORANGE);
          tft.setFont(Arial_12);
          tft.print("Teensy Bat Detector  "); tft.print(VERSION);
          tft.setTextColor(ILI9341_WHITE);
          display_settings();
          prepare_spectrum_display();
          logo_head();
          logo(true);
        }
        else 
        {
          waterfall_flag = 1;
        }
  }
} // END function "controls"

void       set_mic_gain(int8_t gain) {
    AudioNoInterrupts();
    sgtl5000_1.micGain (mic_gain);
    AudioInterrupts();
    display_settings();    
} // end function set_mic_gain

void       set_freq_LO(int freq) {
    // audio lib thinks we are still in 44118sps sample rate
    // therefore we have to scale the frequency of the local oscillator
    // in accordance with the REAL sample rate
    freq_LO = freq * (AUDIO_SAMPLE_RATE_EXACT / sample_rate_real); 
    // if we switch to LOWER samples rates, make sure the running LO 
    // frequency is allowed ( < 22k) ! If not, adjust consequently, so that
    // LO freq never goes up 22k, also adjust the variable freq_real  
    if(freq_LO > 22000) {
      freq_LO = 22000;
      freq_real = freq_LO * (sample_rate_real / AUDIO_SAMPLE_RATE_EXACT) + 9;
    }
    AudioNoInterrupts();
    sine1.frequency(freq_LO);
    AudioInterrupts();
    display_settings();
} // END of function set_freq_LO

void      display_settings() {
    tft.fillRect(14,32,200,17,ILI9341_BLACK);
    tft.setCursor(14, 32);
    tft.setFont(Arial_12); 
    tft.print("gain: "); tft.print (mic_gain);
    tft.print("     "); 
    tft.print("freq: "); tft.print (freq_real);
    tft.print("    "); 
    tft.fillRect(232,32,88,17,ILI9341_BLACK);
    tft.setCursor(232, 32);
    tft.print("       "); 
    tft.print (text);
       
 /*  // only for debugging  
    tft.fillRect(0,122,200,17,ILI9341_BLACK);
    tft.setCursor(0, 122);
    tft.print("LO: "); tft.print (freq_LO);
    tft.print("   "); 
    */
}

void      set_sample_rate (int sr) {
  switch (sr) {
    case SAMPLE_RATE_44K:
    sample_rate_real = 44100;
    text = "44.1k";
    break;
    case SAMPLE_RATE_48K:
    sample_rate_real = 48000;
    text = "48k";
    break;
    case SAMPLE_RATE_88K:
    sample_rate_real = 88200;
    text = "88.2k";
    break;
    case SAMPLE_RATE_96K:
    sample_rate_real = 96000;
    text = "96k";
    break;
    case SAMPLE_RATE_176K:
    sample_rate_real = 176400;
    text = "176k";
    break;
    case SAMPLE_RATE_192K:
    sample_rate_real = 192000;
    text = "192k";
    break;
  }
    AudioNoInterrupts();
    setI2SFreq (sample_rate_real); 
    delay(200); // this delay seems to be very essential !
    set_freq_LO (freq_real);
    AudioInterrupts();
    delay(20);
    display_settings();
    prepare_spectrum_display();
} // END function set_sample_rate

void prepare_spectrum_display() {
    int base_y = 211; 
    int b_x = 10;
    int x_f = SR[sample_rate].x_factor;
    tft.fillRect(0,base_y,320,240 - base_y,ILI9341_BLACK);
//    tft.drawFastHLine(b_x, base_y + 2, 256, ILI9341_PURPLE);  
//    tft.drawFastHLine(b_x, base_y + 3, 256, ILI9341_PURPLE);  
    tft.drawFastHLine(b_x, base_y + 2, 256, ILI9341_MAROON);  
    tft.drawFastHLine(b_x, base_y + 3, 256, ILI9341_MAROON);  
    // vertical lines
    tft.drawFastVLine(b_x - 4, base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine(b_x - 3, base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f + 1 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 2 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 2 + 1 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 3 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 3 + 1 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 4 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 4 + 1 + b_x,  base_y + 1, 10, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 0.5 + b_x,  base_y + 1, 6, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 1.5 + b_x,  base_y + 1, 6, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 2.5 + b_x,  base_y + 1, 6, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 3.5 + b_x,  base_y + 1, 6, ILI9341_YELLOW);  
    tft.drawFastVLine( x_f * 4.5 + b_x,  base_y + 1, 6, ILI9341_YELLOW);  
    // text
    tft.setTextColor(ILI9341_WHITE);
    tft.setFont(Arial_9);
    int text_y_offset = 16;
    int text_x_offset = - 5;
    tft.setCursor (b_x + text_x_offset + 256, base_y + text_y_offset);
    tft.print("kHz");
    // zero
    tft.setCursor (b_x + text_x_offset, base_y + text_y_offset);
    tft.print("0");
    tft.setCursor (b_x + x_f + text_x_offset, base_y + text_y_offset);
    tft.print(SR[sample_rate].f1);
    tft.setCursor (b_x + x_f * 2 + text_x_offset, base_y + text_y_offset);
    tft.print(SR[sample_rate].f2);
    tft.setCursor (b_x + x_f *3 + text_x_offset, base_y + text_y_offset);
    tft.print(SR[sample_rate].f3);
    tft.setCursor (b_x + x_f *4 + text_x_offset, base_y + text_y_offset);
    tft.print(SR[sample_rate].f4);
    tft.setFont(Arial_14);
} // END prepare_spectrum_display

 void spectrum() { // spectrum analyser code by rheslip - modified
     if (myFFT.available()) {
    int scale;
    scale = 5;
  for (int16_t x = 2; x < 128; x++) {
     FFT_bin[x] = abs(myFFT.output[x]); 
     int bar = (FFT_bin[x] * scale);
     if (bar >175) bar=175;
     // this is a very simple first order IIR filter to smooth the reaction of the bars
     bar = 0.05 * bar + 0.95 * 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.drawPixel(x*2+10, 210-barm[x], ILI9341_BLACK);
     tft.drawPixel(x*2+10, 210-bar, ILI9341_WHITE);

//     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;
  }
     search_bats();     
  } //end if
} // end void spectrum

void  search_bats() {
    // the array FFT_bin contains the results of the 256 point FFT --> 128 magnitude values
    // we look for bins that have a high amplitude compared to the mean noise, indicating the presence of ultrasound
    // 1. only search in those parts of the array > 14kHz and not around +-10kHz of the LO freq -->
    //    thus it is best, if I search in two parts --> 14kHz to freq_real-10k AND freq_real+10k to sample_rate/2
    // 2. determine mean and max in both parts of the array
    // 3. if we find a bin that is much larger than the mean (take care of situations where mean is zero!) --> identify the no. of the bin
    // 4. determine frequency of that bin (depends on sample_rate_real)
    //    a.) by simply multiplying bin# with bin width
    //    b.) by using an interpolator (not (yet) implemented)
    // 5. display frequency in bold and RED for 1-2 sec. (TODO: also display possible bat species ;-)) and then delete
    // goto 1.    

    // search array in two parts: 
    //  1.)  14k to (freq_real - 10k)
    // upper and lower limits for maximum search
     l_limit = 14000;
     u_limit = freq_real - 10000;
     index_l_limit =  (l_limit * 256 / sample_rate_real); 
     index_u_limit =  (u_limit * 256 / sample_rate_real); 
//     Serial.print(index_l_limit); Serial.print ("  "); Serial.println(index_u_limit);

     if (index_u_limit > index_l_limit) { 
        arm_max_q15(&FFT_bin[index_l_limit], index_u_limit - index_l_limit, &FFT_max1, &FFT_max_bin1);
            // this is the efficient CMSIS function to calculate the mean
        arm_mean_q15(&FFT_bin[index_l_limit], index_u_limit - index_l_limit, &FFT_mean1);
            // shift bin_max because we have not searched through ALL the 256 FFT bins
        FFT_max_bin1 = FFT_max_bin1 + index_l_limit;
     }
//     Serial.print(FFT_max1); Serial.print ("  "); Serial.println(FFT_mean1);

    //  2.)  (freq_real + 10k) to 256 
    // upper and lower limits for maximum search
     l_limit = freq_real + 10000;
     if (l_limit < 14000) {
      l_limit = 14000;
     }
     index_l_limit = (l_limit * 256 / sample_rate_real); 
     index_u_limit = 128; 
//     Serial.print(index_l_limit); Serial.print ("  "); Serial.println(index_u_limit);
     if (index_u_limit > index_l_limit) { 
        arm_max_q15(&FFT_bin[index_l_limit], index_u_limit - index_l_limit, &FFT_max2, &FFT_max_bin2);
            // this is the efficient CMSIS function to calculate the mean
        arm_mean_q15(&FFT_bin[index_l_limit], index_u_limit - index_l_limit, &FFT_mean2);
            // shift bin_max because we have not searched through ALL the 128 FFT bins
        FFT_max_bin2 = FFT_max_bin2 + index_l_limit;
     }
//         Serial.print(FFT_max2); Serial.print ("  "); Serial.println(FFT_mean2);

      display_found_bats();
    FFT_max1 = 0;
    FFT_mean1 = 0;
    FFT_max_bin1 = 0;
    FFT_max2 = 0;
    FFT_mean2 = 0;
    FFT_max_bin2 = 0;
}  // END function search_bats()

void display_found_bats() {
    int sign_x = 148; 
    int sign_y = 57; 
    if (since_bat_detection1 > 6000 && since_bat_detection2 > 6000) {
// DELETE red BAT sign
//      Serial.println("RED SIGN DELETED");      
      tft.fillRect(sign_x, sign_y, 64, 22, ILI9341_BLACK);
      tft.fillRect(sign_x + 86, sign_y + 4, 320 - 86 - sign_x, 40, ILI9341_BLACK);
//      since_bat_detection = 0;
    }

// DELETE FREQUENCY 1
    if (since_bat_detection1 > 6000) {
      tft.fillRect(sign_x + 86, sign_y + 4, 320 - 86 - sign_x, 14, ILI9341_BLACK);
//      since_bat_detection1 = 0;
    }

// DELETE FREQUENCY 2
    if (since_bat_detection2 > 6000) {
      tft.fillRect(sign_x + 86, sign_y + 30, 320 - 86 - sign_x, 14, ILI9341_BLACK);
//      since_bat_detection2 = 0;
    }
// PRINT RED BAT SIGN    
    if(FFT_max1 > (FFT_mean1 + 5) || FFT_max2 > (FFT_mean2 + 5)) {
//      Serial.println("BAT");
      tft.fillRect(sign_x, sign_y, 64, 22, ILI9341_RED);
      tft.setTextColor(ILI9341_WHITE);
      tft.setFont(Arial_14);
      tft.setCursor(sign_x + 2, sign_y + 4);
      tft.print("B A T !");      
//      since_bat_detection = 0;
    }
// PRINT frequency 1
    if(FFT_max1 > FFT_mean1 + 5) {
      tft.fillRect(sign_x + 86, sign_y + 4, 320 - 86 - sign_x, 14, ILI9341_BLACK);
      tft.setTextColor(ILI9341_ORANGE);
      tft.setFont(Arial_12);
      tft.setCursor(sign_x + 86, sign_y + 4);
      tft.print(FFT_max_bin1 * sample_rate_real / 256);
      tft.print(" Hz");      
      since_bat_detection1 = 0;
    
//      Serial.print ("max Freq 1: ");
//      Serial.println(FFT_max_bin1 * sample_rate_real / 256);
    }
    
// PRINT frequency 2    
    if(FFT_max2 > FFT_mean2 + 5) { 
      tft.fillRect(sign_x + 86, sign_y + 30, 320 - 86 - sign_x, 14, ILI9341_BLACK);
      tft.setTextColor(ILI9341_ORANGE);
      tft.setFont(Arial_12);
      tft.setCursor(sign_x + 86, sign_y + 30);
      tft.print(FFT_max_bin2 * sample_rate_real / 256);
      tft.print(" Hz");      
//      Serial.print ("max Freq 2: ");
//      Serial.println(FFT_max_bin2 * sample_rate_real / 256);
      since_bat_detection2 = 0;
    }
      tft.setTextColor(ILI9341_WHITE);
} // END function display_found_bats


// set samplerate code by Frank Boesing 
void setI2SFreq(int freq) {
  typedef struct {
    uint8_t mult;
    uint16_t div;
  } tmclk;

  const int numfreqs = 14;
  const int samplefreqs[numfreqs] = { 8000, 11025, 16000, 22050, 32000, 44100, (int)44117.64706 , 48000, 88200, (int)44117.64706 * 2, 96000, 176400, (int)44117.64706 * 4, 192000};

#if (F_PLL==16000000)
  const tmclk clkArr[numfreqs] = {{16, 125}, {148, 839}, {32, 125}, {145, 411}, {64, 125}, {151, 214}, {12, 17}, {96, 125}, {151, 107}, {24, 17}, {192, 125}, {127, 45}, {48, 17}, {255, 83} };
#elif (F_PLL==72000000)
  const tmclk clkArr[numfreqs] = {{32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {128, 1125}, {98, 625}, {8, 51}, {64, 375}, {196, 625}, {16, 51}, {128, 375}, {249, 397}, {32, 51}, {185, 271} };
#elif (F_PLL==96000000)
  const tmclk clkArr[numfreqs] = {{8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {32, 375}, {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {32, 125}, {151, 321}, {8, 17}, {64, 125} };
#elif (F_PLL==120000000)
  const tmclk clkArr[numfreqs] = {{32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {128, 1875}, {205, 2179}, {8, 85}, {64, 625}, {89, 473}, {16, 85}, {128, 625}, {178, 473}, {32, 85}, {145, 354} };
#elif (F_PLL==144000000)
  const tmclk clkArr[numfreqs] = {{16, 1125}, {49, 2500}, {32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {4, 51}, {32, 375}, {98, 625}, {8, 51}, {64, 375}, {196, 625}, {16, 51}, {128, 375} };
#elif (F_PLL==168000000)
  const tmclk clkArr[numfreqs] = {{32, 2625}, {21, 1250}, {64, 2625}, {21, 625}, {128, 2625}, {42, 625}, {8, 119}, {64, 875}, {84, 625}, {16, 119}, {128, 875}, {168, 625}, {32, 119}, {189, 646} };
#elif (F_PLL==180000000)
  const tmclk clkArr[numfreqs] = {{46, 4043}, {49, 3125}, {73, 3208}, {98, 3125}, {183, 4021}, {196, 3125}, {16, 255}, {128, 1875}, {107, 853}, {32, 255}, {219, 1604}, {214, 853}, {64, 255}, {219, 802} };
#elif (F_PLL==192000000)
  const tmclk clkArr[numfreqs] = {{4, 375}, {37, 2517}, {8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {1, 17}, {8, 125}, {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {32, 125} };
#elif (F_PLL==216000000)
  const tmclk clkArr[numfreqs] = {{32, 3375}, {49, 3750}, {64, 3375}, {49, 1875}, {128, 3375}, {98, 1875}, {8, 153}, {64, 1125}, {196, 1875}, {16, 153}, {128, 1125}, {226, 1081}, {32, 153}, {147, 646} };
#elif (F_PLL==240000000)
  const tmclk clkArr[numfreqs] = {{16, 1875}, {29, 2466}, {32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {4, 85}, {32, 625}, {205, 2179}, {8, 85}, {64, 625}, {89, 473}, {16, 85}, {128, 625} };
#endif

  for (int f = 0; f < numfreqs; f++) {
    if ( freq == samplefreqs[f] ) {
      while (I2S0_MCR & I2S_MCR_DUF) ;
      I2S0_MDR = I2S_MDR_FRACT((clkArr[f].mult - 1)) | I2S_MDR_DIVIDE((clkArr[f].div - 1));
      return;
    }
  }
}

//  bat logo taken from Shezzy:  
//  http://sisterzpsptreasures.freeforums.org/easy-animated-bat-t77.html
//  copyright free graphics
//  "The image you create by following this tutorial belongs to you and you may do whatever you want with it."
void logo(bool wing1) {
      // Logo ;-)      
     int x = 265;
     int y = 10; 
//      grey background and white line rectangle around it 
     tft.fillRect(x + 23, y - 2,16,14,ILI9341_DARKGREY);
     tft.fillRect(x - 2, y - 2,16,14,ILI9341_DARKGREY);
//     tft.fillRect(x - 2, y - 2, 41, 14,ILI9341_DARKGREY);
    if (wing1) {
     //Wing1
     tft.drawFastHLine(x + 2, y + 0, 5, ILI9341_BLACK);
     tft.drawFastHLine(x + 31, y + 0, 5, ILI9341_BLACK);
     tft.drawPixel(x + 1, y + 1, ILI9341_BLACK);
     tft.drawPixel(x + 35, y + 1, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 1, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 1, ILI9341_BLACK);
//     tft.drawPixel(x + 6, y + 2, ILI9341_BLACK);
//     tft.drawPixel(x + 30, y + 2, ILI9341_BLACK);
     tft.drawFastVLine(x , y + 2, 7, ILI9341_BLACK);
     tft.drawFastVLine(x + 36 , y + 2, 7, ILI9341_BLACK);
     tft.drawFastHLine(x + 7, y + 2, 2, ILI9341_BLACK);
     tft.drawFastHLine(x + 28, y + 2, 2, ILI9341_BLACK);
     tft.drawPixel(x + 13, y + 2, ILI9341_BLACK);
     tft.drawPixel(x + 23, y + 2, ILI9341_BLACK);
     tft.drawFastHLine(x + 9, y + 3, 4, ILI9341_BLACK);
     tft.drawFastHLine(x + 24, y + 3, 4, ILI9341_BLACK);
     tft.drawFastHLine(x + 3, y + 5, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 31, y + 5, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 7, y + 5, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 27, y + 5, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 11, y + 5, 2, ILI9341_BLACK);
     tft.drawFastHLine(x + 24, y + 5, 2, ILI9341_BLACK);
     tft.drawPixel(x + 2, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 10, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 13, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 23, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 26, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 34, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 1, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 10, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 26, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 35, y + 7, ILI9341_BLACK);
    }
    else {
     //Wing2
     tft.drawFastHLine(x + 2, y + 2, 5, ILI9341_BLACK);
     tft.drawFastHLine(x + 31, y + 2, 5, ILI9341_BLACK);
     tft.drawPixel(x + 1, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 35, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 3, ILI9341_BLACK);
//     tft.drawPixel(x + 6, y + 4, ILI9341_BLACK);
//     tft.drawPixel(x + 30, y + 4, ILI9341_BLACK);
     tft.drawFastVLine(x , y + 4, 7, ILI9341_BLACK);
     tft.drawFastVLine(x + 36 , y + 4, 7, ILI9341_BLACK);
     tft.drawFastHLine(x + 7, y + 4, 2, ILI9341_BLACK);
     tft.drawFastHLine(x + 28, y + 4, 2, ILI9341_BLACK);
//     tft.drawPixel(x + 13, y + 4, ILI9341_BLACK);
//     tft.drawPixel(x + 23, y + 4, ILI9341_BLACK);
     tft.drawFastHLine(x + 9, y + 5, 5, ILI9341_BLACK);
     tft.drawFastHLine(x + 24, y + 5, 5, ILI9341_BLACK);
     tft.drawFastHLine(x + 3, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 31, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 7, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 27, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 11, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 24, y + 7, 3, ILI9341_BLACK);
     tft.drawPixel(x + 2, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 10, y + 8, ILI9341_BLACK);
//     tft.drawPixel(x + 13, y + 8, ILI9341_BLACK);
//     tft.drawPixel(x + 23, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 26, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 34, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 1, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 10, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 26, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 35, y + 9, ILI9341_BLACK);
    }
}

void logo_head() {
     int x = 265;
     int y = 10; 
     tft.fillRect(x + 14, y - 2,9,14,ILI9341_DARKGREY);
     // Head 
     tft.drawPixel(x + 15, y + 2, ILI9341_BLACK);
     tft.drawPixel(x + 21, y + 2, ILI9341_BLACK);
     tft.drawPixel(x + 14, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 16, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 20, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 22, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 14, y + 4, ILI9341_BLACK);
     tft.drawPixel(x + 22, y + 4, ILI9341_BLACK);
     tft.drawFastHLine(x + 17, y + 4, 3, ILI9341_BLACK);
     tft.drawPixel(x + 14, y + 5, ILI9341_BLACK);
     tft.drawPixel(x + 22, y + 5, ILI9341_BLACK);
     tft.drawPixel(x + 16, y + 5, ILI9341_RED);
     tft.drawPixel(x + 20, y + 5, ILI9341_RED);
     tft.drawPixel(x + 17, y + 6, ILI9341_RED);
     tft.drawPixel(x + 19, y + 6, ILI9341_RED);
     tft.drawPixel(x + 14, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 22, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 15, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 21, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 16, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 20, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 17, y + 8, ILI9341_WHITE);
     tft.drawPixel(x + 19, y + 8, ILI9341_WHITE);
     tft.drawFastHLine(x + 17, y + 9, 3, ILI9341_BLACK);
}

void check_processor() {
      if (second.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();
    }
} // END function check_processor


void waterfall(void) // thanks to Frank B !
{ 
  static int count = 0;
  uint16_t lbuf[320];  
  if (fft1024_1.available()) {
//    for (int i = 0; i < 240; i++) {
    for (int i = 0; i < 480; i+=2) {
//      int val = fft1024_1.read(i) * 65536.0; //v1
      int val = fft1024_1.read(i) * 65536.0 * 5; //v1
      //int val = fft1024_1.read(i*2, i*2+1 ) * 65536.0; //v2      
      lbuf[240-i/2-1] = tft.color565(
              min(255, val), //r
              (val/8>255)? 255 : val/8, //g
              ((255-val)>>1) <0? 0: (255-val)>>1 //b
             ); 
    }
//    tft.writeRect(count, 16, 1, 239-16, (uint16_t*) &lbuf);
    tft.writeRect(count, 32, 1, 239-32, (uint16_t*) &lbuf);
    tft.setScroll(319 - count);
    count++;
    if (count >= 320) count = 0;
    count_help = count;
  }
}

const char* filename (int pointer) {
      String NAME = "REC"+String(pointer)+".RAW";
      char str[15];
      NAME.toCharArray(str, sizeof(NAME));
      return str;
}

void startRecording() {
  Serial.print("startRecording");
/*  if (SD.exists("RECORD.RAW")) {
    // The SD library writes new data to the end of the
    // file, so to start a new recording, the old file
    // must be deleted before new data is written.
    SD.remove("RECORD.RAW");
  } */
//  recfile = recfile + 1;
  String NAME = "REC"+String(file_number)+".RAW";
  char fi[15];
  NAME.toCharArray(fi, sizeof(NAME));
   if (SD.exists(fi)) {
    // The SD library writes new data to the end of the
    // file, so to start a new recording, the old file
    // must be deleted before new data is written.
    SD.remove(fi);
  }    
  frec = SD.open(fi, FILE_WRITE);
  if (frec) {
    recorder.begin();
    mode = MODE_REC;
    Serial.println("RECORDING!");
/*    tft.fillRect(0, 102, 160, 12, ST7735_RED);   //ST7735_BLACK);
    tft.setCursor(0, 105);
    tft.print (" Recording !");
    showtrack();
    */
  }
}

void continueRecording() {
  if (recorder.available() >= 8) {
    byte buffer[512];
    // Fetch 2 blocks from the audio library and copy
    // into a 512 byte buffer.  The Arduino SD library
    // is most efficient when full 512 byte sector size
    // writes are used.
    memcpy(buffer, recorder.readBuffer(), 256);
    recorder.freeBuffer();
    memcpy(buffer+256, recorder.readBuffer(), 256);
    recorder.freeBuffer();
    // write all 512 bytes to the SD card
//    elapsedMicros usec = 0;
    frec.write(buffer, 512);
    // Uncomment these lines to see how long SD writes
    // are taking.  A pair of audio blocks arrives every
    // 5802 microseconds, so hopefully most of the writes
    // take well under 5802 us.  Some will take more, as
    // the SD library also must write to the FAT tables
    // and the SD card controller manages media erase and
    // wear leveling.  The recorder object can buffer
    // approximately 301700 us of audio, to allow time
    // for occasional high SD card latency, as long as
    // the average write time is under 5802 us.
    //    if (one_sec.check() == 1) {
    //      Serial.print("SD write, us=");
    //    }
    //    Serial.println(usec);
  }
}

void stopRecording() {
  Serial.print("stopRecording");
  recorder.end();
  if (mode == MODE_REC) {
    while (recorder.available() > 0) {
      frec.write((byte*)recorder.readBuffer(), 256);
      recorder.freeBuffer();
    }
    frec.close();
//    playfile = recfile;
  }
  mode = MODE_STOP;
//  clearname();
  Serial.println (" Recording stopped!");
}

void startPlaying() {
      String NAME = "REC"+String(file_number)+".RAW";
      char fi[15];
      NAME.toCharArray(fi, sizeof(NAME));
      mix1.gain(1,1);
      mix1.gain(0,0);       
  Serial.println("startPlaying ");
//  const char* fi = filename(playfile);
//  const char* fi = "REC13.RAW";
//  Serial.print ("File: ");Serial.print(playfile);
  Serial.println ("Playfile: "); Serial.print (file_number);
  Serial.println ("Name: "); Serial.print (filename(file_number));
  delay(100);
//  Switch_to_MP3();
//  playSd.play(filename(playfile));
//  playSd.play("REC13.RAW");
    player.play(fi);

  mode = MODE_PLAY;
/*  tft.fillRect(0, 102, 160, 12, ST7735_GREEN);   //ST7735_BLACK);
  tft.setCursor(0, 105);
  tft.setTextColor(ST7735_BLACK);
  tft.print (" Playing !");
  tft.setTextColor(ST7735_WHITE);
  showtrack();
*/
}
  
void continuePlaying() {
  if (!player.isPlaying()) {
    player.stop();
    mode = MODE_STOP;
      mix1.gain(1,0);
      mix1.gain(0,1);       
    Serial.println("End of recording");
  }
}

void stopPlaying() {
      mix1.gain(1,0);
      mix1.gain(0,1);       
  Serial.print("stopPlaying");
  if (mode == MODE_PLAY) player.stop();
  mode = MODE_STOP;
  Serial.println (" Playing stopped");
}
 
Last edited:
RECORDING TEST

Bat detector v1.1:
Rec 48k --> Play 48k: audio ok
Rec 48k --> Play 96k: audio ok and two times as fast and one octave higher
Rec 96k --> Play 96k: audio stuttering and distorted
> 96k: always stuttering/distortion

Stripped down code of v1.0 and eliminated all FFTs and local oscillator and multiplyer:
Rec 48k --> Play 48k: audio ok
Rec 48k --> Play 96k: audio ok and two times as fast and one octave higher
Rec 96k --> Play 96k: audio ok
Rec 96k --> Play 48k: audio ok and two times as slow and one octave lower
Rec 96k --> Play 192k: audio ok, but with annoying ticking sound and two times as fast and one octave higher

My conclusion:
- The problems with the stuttering/distorted audio & the ticking sound are probably a cause of the processor of the Teensy 3.5 which is not fast enough to write the audio samples to the SD card in the desired sample rate while simultaneously dooing the realtime audio processing and the spectrum display.

Next step:
- test if the Teensy 3.6 is fast enough to do that

Is there a way to optimise the buffering and writing speed to the SD card?
 
New test of recording with Teensy 3.6 (see below: test script, only record/play and changing of sample rate)

I use the built-in SD card slot (NOT the one on the audio board) with the new version of the SD library in Pauls github.
(I also tested the audio board SD slot: similar results.)

Rec 96k --> Play 96k: audio ok
Rec 96k --> Play 48k: audio ok and two times as slow and one octave lower
Rec 96k --> Play 192k: audio ok, and two times as fast and one octave higher

BUT: there is prickling noise in the first second of all those recordings.

Rec192k --> does not work

Also bought a new faster SD card and tested the SD card speeds, from 1.1 to 1.6MBytes/sec, that should be enough to cover mono sound at 192000ksps
192000 * 16 bits / 8bitsperByte / 1024 = 375kByte/sec

So, my conclusion would be: playing in 192k is no problem, but recording in 192k is the problem. Has anybody got a hint, how I could proceed to get saving RAW fies on SD card in 192ksps working?

Frank


Code:
/***********************************************************************
 *  (c) 2016  Frank DD4WH 2016_11_02 - MIT license 
 *  Teensy Bat & Ultrasound detector 
 *         
 *         
 *     RECORD TEST 1    
 *         
 *         
 *  Version 1.1 (recording alpha)       
 *  https://forum.pjrc.com/threads/38988-Bat-detector
 *         
 *        made possible by the samplerate code by Frank Boesing, thanks Frank!
 * 
 *  tested on Teensy 3.5 + Teensy audio board 
 *  + standard tiny electret MIC soldered to the MIC Input of the audio board 
 *  
 *  
 *  User adjustments - with buttons
 *  
 *  MIC-GAIN                  33 + 34 -
 *  FREQUENCY                 35 + 36 -
 *  SAMPLE RATE               37 + 38 -
 *  WATERFALL/SPECTRUM        39  
 *  
 * Audio sample rate code - function setI2SFreq  
 * Copyright (c) 2016, Frank Bösing, f.boesing@gmx.de
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

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

#include <ILI9341_t3.h>
#include "font_Arial.h"

#define VERSION     " v0.3"

#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

// would be nice to use the fast DMA lib, but it does not work on my Teensy 3.5
//ILI9341_t3DMA tft = ILI9341_t3DMA(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);
ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);

#define BUTTON_MIC_GAIN_P       33     
#define BUTTON_MIC_GAIN_M       34
#define BUTTON_FREQ_P           35     
#define BUTTON_FREQ_M           36     
#define BUTTON_SAMPLE_RATE_P    37     
#define BUTTON_SAMPLE_RATE_M    38     
#define BUTTON_TOGGLE_WATERFALL 39
#define BUTTON_RECORD           31
#define BUTTON_STOP             30
#define BUTTON_PLAY             29

Bounce mic_gain_P = Bounce(BUTTON_MIC_GAIN_P, 50); 
Bounce mic_gain_M = Bounce(BUTTON_MIC_GAIN_M, 50); 
Bounce freq_P = Bounce(BUTTON_FREQ_P, 50); 
Bounce freq_M = Bounce(BUTTON_FREQ_M, 50); 
Bounce sample_rate_P = Bounce(BUTTON_SAMPLE_RATE_P, 50);
Bounce sample_rate_M = Bounce(BUTTON_SAMPLE_RATE_M, 50);
Bounce toggle_waterfall = Bounce(BUTTON_TOGGLE_WATERFALL, 50);
Bounce button_Record = Bounce(BUTTON_RECORD, 50);
Bounce button_Stop = Bounce(BUTTON_STOP, 50);
Bounce button_Play = Bounce(BUTTON_PLAY, 50);
  
#define SAMPLE_RATE_MIN               0
#define SAMPLE_RATE_44K               0
#define SAMPLE_RATE_48K               1
#define SAMPLE_RATE_88K               2
#define SAMPLE_RATE_96K               3
#define SAMPLE_RATE_176K              4
#define SAMPLE_RATE_192K              5
#define SAMPLE_RATE_MAX               5

// this audio comes from the codec by I2S2
AudioInputI2S               i2s_in; // MIC input
AudioRecordQueue            recorder; 
//AudioSynthWaveformSine      sine1; // local oscillator
//AudioEffectMultiply         mult1; // multiply = mix
//AudioAnalyzeFFT256          myFFT; // for spectrum display
//AudioAnalyzeFFT1024         fft1024_1; // for waterfall display
AudioPlaySdRaw              player; 
AudioMixer4                 mix1;
AudioOutputI2S              i2s_out; // headphone output          

AudioConnection patch3      (i2s_in, 0, recorder, 0);
AudioConnection patch4      (i2s_in, 0, mix1, 0);
AudioConnection patch5      (player, 0, mix1, 1);
AudioConnection patch8      (mix1, 0, i2s_out, 0);
AudioConnection patch9      (mix1, 0, i2s_out, 1);

AudioControlSGTL5000        sgtl5000_1;  

// Metro 1 second
Metro second = Metro(1000);

const int8_t    MODE_STOP = 0;
const int8_t    MODE_REC = 1;
const int8_t    MODE_PLAY = 2;

int mode = MODE_STOP; 
File frec; // audio is recorded to this file first
int file_number = 0;

int count_help = 0;
int8_t waterfall_flag = 0;
int idx_t = 0;
int idx = 0;
int64_t sum;
float32_t mean;

int8_t mic_gain = 40; // start detecting with this MIC_GAIN in dB
int freq_real = 22000; // start detecting at this frequency
int sample_rate = SAMPLE_RATE_96K;
int sample_rate_real = 96000;
String text="96k";
int freq_LO = 7000;

typedef struct SR_Descriptor
{
    const int SR_n;
    const char* const f1;
    const char* const f2;
    const char* const f3;
    const char* const f4;
    const float32_t x_factor;
} SR_Desc;

// Text and position for the FFT spectrum display scale
const SR_Descriptor SR [SAMPLE_RATE_MAX + 1] =
{
    //   SR_n ,  f1, f2, f3, f4, x_factor = pixels per f1 kHz in spectrum display
    {  SAMPLE_RATE_44K,  "5", "10", "15", "20", 58.05}, // which means 58.05 pixels per 5 kHz
    {  SAMPLE_RATE_48K,  "5", "10", "15", "20", 53.33},
    {  SAMPLE_RATE_88K,  "10", "20", "30", "40", 58.05},
    {  SAMPLE_RATE_96K,  "10", "20", "30", "40", 53.33},
    {  SAMPLE_RATE_176K,  "20", "40", "60", "80", 58.05},
    {  SAMPLE_RATE_192K,  "20", "40", "60", "80", 53.33} // which means 53.33 pixels per 20kHz
};    

//const int myInput = AUDIO_INPUT_LINEIN;
const int myInput = AUDIO_INPUT_MIC;

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

  if(!(SD.begin(BUILTIN_SDCARD))) 
  {
      while (1) {
          Serial.println("Unable to access the SD card");
          delay(500);  
      }
  }

//setup pins with pullups
  pinMode(BUTTON_MIC_GAIN_P,INPUT_PULLUP);
  pinMode(BUTTON_MIC_GAIN_M,INPUT_PULLUP);
  pinMode(BUTTON_FREQ_P,INPUT_PULLUP);  
  pinMode(BUTTON_FREQ_M,INPUT_PULLUP);  
  pinMode(BUTTON_SAMPLE_RATE_P,INPUT_PULLUP);  
  pinMode(BUTTON_SAMPLE_RATE_M,INPUT_PULLUP);  
  pinMode(BUTTON_TOGGLE_WATERFALL,INPUT_PULLUP);  
  pinMode(BUTTON_RECORD,INPUT_PULLUP);  
  pinMode(BUTTON_STOP,INPUT_PULLUP);  
  pinMode(BUTTON_PLAY,INPUT_PULLUP);  

  // Audio connections require memory. 
  AudioMemory(100);
/*
  setSyncProvider(getTeensy3Time);
*/
// Enable the audio shield. select input. and enable output
  sgtl5000_1.enable();
  sgtl5000_1.inputSelect(myInput);
  sgtl5000_1.volume(0.8);
  sgtl5000_1.micGain (mic_gain);
  sgtl5000_1.adcHighPassFilterDisable(); // does not help too much!

// Init SD card use
//  SPI.setMOSI(7); 
//  SPI.setSCK(14);



// Init TFT display  
  pinMode( BACKLIGHT_PIN, OUTPUT );
  analogWrite( BACKLIGHT_PIN, 1023 );

  tft.begin();
  tft.setRotation( 3 );
  tft.fillScreen(ILI9341_BLACK);
  tft.setCursor(14, 7);
  tft.setTextColor(ILI9341_ORANGE);
  tft.setFont(Arial_12);
  tft.print("Teensy Bat Detector  "); tft.print(VERSION);
  tft.setTextColor(ILI9341_WHITE);

  display_settings();

  // sorry, couldn´t resist ;-)
  logo_head();
  for (int v = 0; v < 6; v++) {
  logo(true);
  delay(200);
  logo(false);
  delay(200);
  }

  set_sample_rate (sample_rate);


  mix1.gain(0,1); 
  mix1.gain(1,1); 


} // END SETUP


void loop() {
   controls();
   check_processor();
}

void controls() {

// first, check buttons
  mic_gain_P.update();
  mic_gain_M.update();
  freq_P.update();
  freq_M.update();
  sample_rate_P.update();
  sample_rate_M.update();
  toggle_waterfall.update();
  button_Record.update();
  button_Stop.update();
  button_Play.update();

  // Respond to button presses
  if (button_Record.fallingEdge()) {
    Serial.println("Record Button Press");
    if (mode == MODE_PLAY) stopPlaying();
    if (mode == MODE_STOP) startRecording();
  }
  if (button_Stop.fallingEdge()) {
    Serial.println("Stop Button Press");
    if (mode == MODE_REC) stopRecording();
    if (mode == MODE_PLAY) stopPlaying();
  }
  if (button_Play.fallingEdge()) {
    Serial.println("Play Button Press");
    if (mode == MODE_REC) stopRecording();
    if (mode == MODE_STOP) startPlaying();
  }

  // If we're playing or recording, carry on...
  if (mode == MODE_REC) {
    continueRecording();
  }
  if (mode == MODE_PLAY) {
    continuePlaying();
  }

  // change MIC GAIN  
  if ( mic_gain_P.fallingEdge()) { 
      mic_gain = mic_gain + 2;
      if (mic_gain > 63) {
        mic_gain = 63;
      }
      set_mic_gain(mic_gain);
  }
  if ( mic_gain_M.fallingEdge()) { 
      mic_gain = mic_gain - 2;
      if (mic_gain < 0) {
        mic_gain = 0;
      }
      set_mic_gain(mic_gain);
  }

  // change sample rate
  if ( sample_rate_P.fallingEdge()) { 
      sample_rate = sample_rate + 1;
            if (sample_rate > SAMPLE_RATE_MAX) {
        sample_rate = SAMPLE_RATE_MAX;
      }
        set_sample_rate (sample_rate);
  }
  if ( sample_rate_M.fallingEdge()) { 
      sample_rate = sample_rate - 1;
            if (sample_rate < SAMPLE_RATE_MIN) {
        sample_rate = SAMPLE_RATE_MIN;
      }
        set_sample_rate (sample_rate);
  }

} // END function "controls"

void       set_mic_gain(int8_t gain) {
    AudioNoInterrupts();
    sgtl5000_1.micGain (mic_gain);
    AudioInterrupts();
    display_settings();    
} // end function set_mic_gain

void      display_settings() {
    tft.fillRect(14,32,200,17,ILI9341_BLACK);
    tft.setCursor(14, 32);
    tft.setFont(Arial_12); 
    tft.print("gain: "); tft.print (mic_gain);
    tft.print("     "); 
    tft.print("freq: "); tft.print (freq_real);
    tft.print("    "); 
    tft.fillRect(232,32,88,17,ILI9341_BLACK);
    tft.setCursor(232, 32);
    tft.print("       "); 
    tft.print (text);
    Serial.println(text);       
 /*  // only for debugging  
    tft.fillRect(0,122,200,17,ILI9341_BLACK);
    tft.setCursor(0, 122);
    tft.print("LO: "); tft.print (freq_LO);
    tft.print("   "); 
    */
}

void      set_sample_rate (int sr) {
  switch (sr) {
    case SAMPLE_RATE_44K:
    sample_rate_real = 44100;
    text = "44.1k";
    break;
    case SAMPLE_RATE_48K:
    sample_rate_real = 48000;
    text = "48k";
    break;
    case SAMPLE_RATE_88K:
    sample_rate_real = 88200;
    text = "88.2k";
    break;
    case SAMPLE_RATE_96K:
    sample_rate_real = 96000;
    text = "96k";
    break;
    case SAMPLE_RATE_176K:
    sample_rate_real = 176400;
    text = "176k";
    break;
    case SAMPLE_RATE_192K:
    sample_rate_real = 192000;
    text = "192k";
    break;
  }
    AudioNoInterrupts();
    setI2SFreq (sample_rate_real); 
    delay(200); // this delay seems to be very essential !
    AudioInterrupts();
    delay(20);
    display_settings();
//    prepare_spectrum_display();
} // END function set_sample_rate


// set samplerate code by Frank Boesing 
void setI2SFreq(int freq) {
  typedef struct {
    uint8_t mult;
    uint16_t div;
  } tmclk;

  const int numfreqs = 14;
  const int samplefreqs[numfreqs] = { 8000, 11025, 16000, 22050, 32000, 44100, (int)44117.64706 , 48000, 88200, (int)44117.64706 * 2, 96000, 176400, (int)44117.64706 * 4, 192000};

#if (F_PLL==16000000)
  const tmclk clkArr[numfreqs] = {{16, 125}, {148, 839}, {32, 125}, {145, 411}, {64, 125}, {151, 214}, {12, 17}, {96, 125}, {151, 107}, {24, 17}, {192, 125}, {127, 45}, {48, 17}, {255, 83} };
#elif (F_PLL==72000000)
  const tmclk clkArr[numfreqs] = {{32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {128, 1125}, {98, 625}, {8, 51}, {64, 375}, {196, 625}, {16, 51}, {128, 375}, {249, 397}, {32, 51}, {185, 271} };
#elif (F_PLL==96000000)
  const tmclk clkArr[numfreqs] = {{8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {32, 375}, {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {32, 125}, {151, 321}, {8, 17}, {64, 125} };
#elif (F_PLL==120000000)
  const tmclk clkArr[numfreqs] = {{32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {128, 1875}, {205, 2179}, {8, 85}, {64, 625}, {89, 473}, {16, 85}, {128, 625}, {178, 473}, {32, 85}, {145, 354} };
#elif (F_PLL==144000000)
  const tmclk clkArr[numfreqs] = {{16, 1125}, {49, 2500}, {32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {4, 51}, {32, 375}, {98, 625}, {8, 51}, {64, 375}, {196, 625}, {16, 51}, {128, 375} };
#elif (F_PLL==168000000)
  const tmclk clkArr[numfreqs] = {{32, 2625}, {21, 1250}, {64, 2625}, {21, 625}, {128, 2625}, {42, 625}, {8, 119}, {64, 875}, {84, 625}, {16, 119}, {128, 875}, {168, 625}, {32, 119}, {189, 646} };
#elif (F_PLL==180000000)
  const tmclk clkArr[numfreqs] = {{46, 4043}, {49, 3125}, {73, 3208}, {98, 3125}, {183, 4021}, {196, 3125}, {16, 255}, {128, 1875}, {107, 853}, {32, 255}, {219, 1604}, {214, 853}, {64, 255}, {219, 802} };
#elif (F_PLL==192000000)
  const tmclk clkArr[numfreqs] = {{4, 375}, {37, 2517}, {8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {1, 17}, {8, 125}, {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {32, 125} };
#elif (F_PLL==216000000)
  const tmclk clkArr[numfreqs] = {{32, 3375}, {49, 3750}, {64, 3375}, {49, 1875}, {128, 3375}, {98, 1875}, {8, 153}, {64, 1125}, {196, 1875}, {16, 153}, {128, 1125}, {226, 1081}, {32, 153}, {147, 646} };
#elif (F_PLL==240000000)
  const tmclk clkArr[numfreqs] = {{16, 1875}, {29, 2466}, {32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {4, 85}, {32, 625}, {205, 2179}, {8, 85}, {64, 625}, {89, 473}, {16, 85}, {128, 625} };
#endif

  for (int f = 0; f < numfreqs; f++) {
    if ( freq == samplefreqs[f] ) {
      while (I2S0_MCR & I2S_MCR_DUF) ;
      I2S0_MDR = I2S_MDR_FRACT((clkArr[f].mult - 1)) | I2S_MDR_DIVIDE((clkArr[f].div - 1));
      return;
    }
  }
}

//  bat logo taken from Shezzy:  
//  http://sisterzpsptreasures.freeforums.org/easy-animated-bat-t77.html
//  copyright free graphics
//  "The image you create by following this tutorial belongs to you and you may do whatever you want with it."
void logo(bool wing1) {
      // Logo ;-)      
     int x = 265;
     int y = 10; 
//      grey background and white line rectangle around it 
     tft.fillRect(x + 23, y - 2,16,14,ILI9341_DARKGREY);
     tft.fillRect(x - 2, y - 2,16,14,ILI9341_DARKGREY);
//     tft.fillRect(x - 2, y - 2, 41, 14,ILI9341_DARKGREY);
    if (wing1) {
     //Wing1
     tft.drawFastHLine(x + 2, y + 0, 5, ILI9341_BLACK);
     tft.drawFastHLine(x + 31, y + 0, 5, ILI9341_BLACK);
     tft.drawPixel(x + 1, y + 1, ILI9341_BLACK);
     tft.drawPixel(x + 35, y + 1, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 1, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 1, ILI9341_BLACK);
//     tft.drawPixel(x + 6, y + 2, ILI9341_BLACK);
//     tft.drawPixel(x + 30, y + 2, ILI9341_BLACK);
     tft.drawFastVLine(x , y + 2, 7, ILI9341_BLACK);
     tft.drawFastVLine(x + 36 , y + 2, 7, ILI9341_BLACK);
     tft.drawFastHLine(x + 7, y + 2, 2, ILI9341_BLACK);
     tft.drawFastHLine(x + 28, y + 2, 2, ILI9341_BLACK);
     tft.drawPixel(x + 13, y + 2, ILI9341_BLACK);
     tft.drawPixel(x + 23, y + 2, ILI9341_BLACK);
     tft.drawFastHLine(x + 9, y + 3, 4, ILI9341_BLACK);
     tft.drawFastHLine(x + 24, y + 3, 4, ILI9341_BLACK);
     tft.drawFastHLine(x + 3, y + 5, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 31, y + 5, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 7, y + 5, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 27, y + 5, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 11, y + 5, 2, ILI9341_BLACK);
     tft.drawFastHLine(x + 24, y + 5, 2, ILI9341_BLACK);
     tft.drawPixel(x + 2, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 10, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 13, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 23, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 26, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 34, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 1, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 10, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 26, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 35, y + 7, ILI9341_BLACK);
    }
    else {
     //Wing2
     tft.drawFastHLine(x + 2, y + 2, 5, ILI9341_BLACK);
     tft.drawFastHLine(x + 31, y + 2, 5, ILI9341_BLACK);
     tft.drawPixel(x + 1, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 35, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 3, ILI9341_BLACK);
//     tft.drawPixel(x + 6, y + 4, ILI9341_BLACK);
//     tft.drawPixel(x + 30, y + 4, ILI9341_BLACK);
     tft.drawFastVLine(x , y + 4, 7, ILI9341_BLACK);
     tft.drawFastVLine(x + 36 , y + 4, 7, ILI9341_BLACK);
     tft.drawFastHLine(x + 7, y + 4, 2, ILI9341_BLACK);
     tft.drawFastHLine(x + 28, y + 4, 2, ILI9341_BLACK);
//     tft.drawPixel(x + 13, y + 4, ILI9341_BLACK);
//     tft.drawPixel(x + 23, y + 4, ILI9341_BLACK);
     tft.drawFastHLine(x + 9, y + 5, 5, ILI9341_BLACK);
     tft.drawFastHLine(x + 24, y + 5, 5, ILI9341_BLACK);
     tft.drawFastHLine(x + 3, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 31, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 7, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 27, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 11, y + 7, 3, ILI9341_BLACK);
     tft.drawFastHLine(x + 24, y + 7, 3, ILI9341_BLACK);
     tft.drawPixel(x + 2, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 10, y + 8, ILI9341_BLACK);
//     tft.drawPixel(x + 13, y + 8, ILI9341_BLACK);
//     tft.drawPixel(x + 23, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 26, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 34, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 1, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 6, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 10, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 26, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 30, y + 9, ILI9341_BLACK);
     tft.drawPixel(x + 35, y + 9, ILI9341_BLACK);
    }
}

void logo_head() {
     int x = 265;
     int y = 10; 
     tft.fillRect(x + 14, y - 2,9,14,ILI9341_DARKGREY);
     // Head 
     tft.drawPixel(x + 15, y + 2, ILI9341_BLACK);
     tft.drawPixel(x + 21, y + 2, ILI9341_BLACK);
     tft.drawPixel(x + 14, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 16, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 20, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 22, y + 3, ILI9341_BLACK);
     tft.drawPixel(x + 14, y + 4, ILI9341_BLACK);
     tft.drawPixel(x + 22, y + 4, ILI9341_BLACK);
     tft.drawFastHLine(x + 17, y + 4, 3, ILI9341_BLACK);
     tft.drawPixel(x + 14, y + 5, ILI9341_BLACK);
     tft.drawPixel(x + 22, y + 5, ILI9341_BLACK);
     tft.drawPixel(x + 16, y + 5, ILI9341_RED);
     tft.drawPixel(x + 20, y + 5, ILI9341_RED);
     tft.drawPixel(x + 17, y + 6, ILI9341_RED);
     tft.drawPixel(x + 19, y + 6, ILI9341_RED);
     tft.drawPixel(x + 14, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 22, y + 6, ILI9341_BLACK);
     tft.drawPixel(x + 15, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 21, y + 7, ILI9341_BLACK);
     tft.drawPixel(x + 16, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 20, y + 8, ILI9341_BLACK);
     tft.drawPixel(x + 17, y + 8, ILI9341_WHITE);
     tft.drawPixel(x + 19, y + 8, ILI9341_WHITE);
     tft.drawFastHLine(x + 17, y + 9, 3, ILI9341_BLACK);
}

void check_processor() {
      if (second.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();
    }
} // END function check_processor


const char* filename (int pointer) {
      String NAME = "REC"+String(pointer)+".RAW";
      char str[15];
      NAME.toCharArray(str, sizeof(NAME));
      return str;
}

void startRecording() {
  Serial.print("startRecording");
/*  if (SD.exists("RECORD.RAW")) {
    // The SD library writes new data to the end of the
    // file, so to start a new recording, the old file
    // must be deleted before new data is written.
    SD.remove("RECORD.RAW");
  } */
//  recfile = recfile + 1;
  String NAME = "REC"+String(file_number)+".RAW";
  char fi[15];
  NAME.toCharArray(fi, sizeof(NAME));
   if (SD.exists(fi)) {
    // The SD library writes new data to the end of the
    // file, so to start a new recording, the old file
    // must be deleted before new data is written.
    SD.remove(fi);
  }    
  frec = SD.open(fi, FILE_WRITE);
  if (frec) {
    recorder.begin();
    mode = MODE_REC;
    Serial.println("RECORDING!");
  }
}

void continueRecording() {
  if (recorder.available() >= 8) {
    byte buffer[512];
    // Fetch 2 blocks from the audio library and copy
    // into a 512 byte buffer.  The Arduino SD library
    // is most efficient when full 512 byte sector size
    // writes are used.
    memcpy(buffer, recorder.readBuffer(), 256);
    recorder.freeBuffer();
    memcpy(buffer+256, recorder.readBuffer(), 256);
    recorder.freeBuffer();
    // write all 512 bytes to the SD card
//    elapsedMicros usec = 0;
    frec.write(buffer, 512);
    // Uncomment these lines to see how long SD writes
    // are taking.  A pair of audio blocks arrives every
    // 5802 microseconds, so hopefully most of the writes
    // take well under 5802 us.  Some will take more, as
    // the SD library also must write to the FAT tables
    // and the SD card controller manages media erase and
    // wear leveling.  The recorder object can buffer
    // approximately 301700 us of audio, to allow time
    // for occasional high SD card latency, as long as
    // the average write time is under 5802 us.
    //    if (one_sec.check() == 1) {
    //      Serial.print("SD write, us=");
    //    }
    //    Serial.println(usec);
  }
}

void stopRecording() {
  Serial.print("stopRecording");
  recorder.end();
  if (mode == MODE_REC) {
    while (recorder.available() > 0) {
      frec.write((byte*)recorder.readBuffer(), 256);
      recorder.freeBuffer();
    }
    frec.close();
//    playfile = recfile;
  }
  mode = MODE_STOP;
//  clearname();
  Serial.println (" Recording stopped!");
}

void startPlaying() {
      String NAME = "REC"+String(file_number)+".RAW";
      char fi[15];
      NAME.toCharArray(fi, sizeof(NAME));
      mix1.gain(1,1);
      mix1.gain(0,0);       
  Serial.println("startPlaying ");
//  const char* fi = filename(playfile);
//  const char* fi = "REC13.RAW";
//  Serial.print ("File: ");Serial.print(playfile);
  Serial.println ("Playfile: "); Serial.print (file_number);
  Serial.println ("Name: "); Serial.print (filename(file_number));
  delay(100);
//  playSd.play(filename(playfile));
//  playSd.play("REC13.RAW");
    player.play(fi);

  mode = MODE_PLAY;
/*  tft.fillRect(0, 102, 160, 12, ST7735_GREEN);   //ST7735_BLACK);
  tft.setCursor(0, 105);
  tft.setTextColor(ST7735_BLACK);
  tft.print (" Playing !");
  tft.setTextColor(ST7735_WHITE);
  showtrack();
*/
}
  
void continuePlaying() {
  if (!player.isPlaying()) {
    player.stop();
    mode = MODE_STOP;
      mix1.gain(1,0);
      mix1.gain(0,1);       
    Serial.println("End of recording");
  }
}

void stopPlaying() {
      mix1.gain(1,0);
      mix1.gain(0,1);       
  Serial.print("stopPlaying");
  if (mode == MODE_PLAY) player.stop();
  mode = MODE_STOP;
  Serial.println (" Playing stopped");
}
 
Yes, try the alternate libraries from WMXZ and Bill - they have way faster write-speeds.

For the different speeds: With using the same I2S Speed for recording an play, the playback-speed will be ok, i think..

I can try to make the waterfall run with DMA, this will speed up things a bit more.
 
Frank,

thanks for the hints on the SD libs, I had a quick look at them: doesn´t look so easy to use them, as they are not plug in and play ;-). I will dig further into that the next days. Any further hints welcome!

Yes, if you use different speeds for rec and play, it is easily explainable, but it is a first hint on how I could implement a time-expansion function, so that the very short bat calls can be played back -say at 10x- slower speed. That´s a quite easy way to frequency translate the calls --> 45kHz becomes 4.5kHz and is then audible for us humans. Maybe that could be done by just switching the sample rates on the fly?

REC: 96k, PLAY: 16k --> Playback of the file is 6 times longer and pitch is 6 times lower
REC: 192k, PLAY: 22k --> Playback of the file is 8.7 times longer and pitch is 8.7 times lower

While on playback, the scale of the FFT spectrum could also be adapted to the playback, so the "REAL" frequency of the recorded audio would be displayed (although the FFT works on the lower pitch audio).

An alternative could be: downsampling the audio samples while playing the file from the RAW data on the SD card . . . But that´s the next step, first we have got to get recording in 192k running.

Yes, that would be very nice, if you could have a look on how to get the DMA TFT lib running for the waterfall. I tried it on the Teensy 3.5 and it did not work (also the RAM was not sufficient with all the buffers I use), I did not yet try it with my new Teensy 3.6 and more RAM.

Have fun,

Frank
 
I made some further tests, because of the not-working REC at 192k issue:

- Record_test_script without FFT and spectrum display:
REC 48k: ok
REC 96k: ok
REC 192k: not working

- Record_test_script with FFT1024 and spectrum display
REC 48k: not working
REC 96k: not working
REC 192k: not working

- Record_test_script with FFT256 and spectrum display
REC 48k: ok
REC 96k: works, but with drizzle noise
REC 192k: not working

So, my conclusion would be:

- the recording takes a whole lot of processor power (but where ?) and that is the problem with recording in 192k.
- it does not seem to be an issue of slow SD card reading/writing, because a mono recording in 192k should need only 192000 * 16 bits / 8bitsperByte / 1024 = 375kByte/sec write speed. I measured 1.1 to 1.6Mbytes/sec with my SD cards.

But now I do not know how to proceed. Is there a way to speed up or change the way to acquire the samples and push them into the queue object or to lower the amount of processing needed to write to the SD card. Hmmm, would be nice if anybody has got a hint on that.
@WMXZ, @Bill, do your libraries only speed up the writing process or is it also about speeding up/ lowering processor load during file processing? (unfortunately, I know nothing about that SD handling/writing stuff . . .)

Frank


P.S.: I tried Time expansion playback of recordings, which is very easy and works perfectly by just changing to a lower sample rate. This is a feature that normally only semiprofessional and professional bat detectors have. So I am very glad that it works like this an the Teensy! Samples rates from 8k to 32k will be implemented in the next version. But we have to solve the REC issue first ;-).
 
Last edited:

Well, after a week of development fun, the new version of the Teensy bat detector is now on github (;-) thanks for pointing me there, Chip !):

Teensy Bat Detector Version v1.2
- recording of ultrasound on Teensy 3.6 builtin SD card (now using uSDFS lib by WMXZ) (works up to 192ksps, but has to be tweaked for elimination of clicking noise)
- time expansion playback of recordings with lower sample rates works
- sample rates 8k, 11k, 16k, 22k, 32k implemented
- git hub site set up

- Wiki set up and filled with first information

https://github.com/DD4WH/Teensy-Bat-Detector

Try to record a shaken key ring in 96k and playback in 16k or 11k, that really sounds like many tons of steel crashing into each other ;-).
That´s how those professional and expensive time expansion bat detectors sound. (use the oldest SD card you have, for this application that seems to be the fastest way to record in realtime at this fast sample rate and produces much less noise in the audio than modern HC cards)

Have fun,

Frank
 
Last edited:
Hi DD4WH, thanks for sharing your work.

Is there enough data collected in the recordings to do visual display of specific calls - something like this:

http://www.sonobat.com/MythStdView_detail.html

Or does this level of detail require a dedicated 'time-expansion bat detector'? (If so, is it possible to get anything interesting to display?)

Thanks.
 
Hi Mike,

yes, of course! That´s the whole point of recording the calls at such a high sample rate. And in that respect, the Teensy Bat Detector is already a "dedicated time expansion detector". And it´s even more, because you cannot only playback the files for time expansion within the Teensy, but you can also analyse the recorded calls in other software packages.

Try recording the sound of a shaken key ring with the Teensy bat detector at 96ksps and play at 16ksps. You will hear your recorded sound at a much lower speed and lower pitch (sounds like big steel machines banging together ;-)). You can exactly calculate the ratio by calculating

time expansion factor = Sampling rate REC / Sampling rate PLAY

That is already possible with the version of the Teensy bat detector code that is on my github.

However, I will try to make it a little more comfortable in the future and hopefully also improve the quality and flexibility of the recording.

Also note the associated WIKI where you find much information about these things:

https://github.com/DD4WH/Teensy-Bat-Detector/wiki

There you will also find a recommendation for a very nice and dedicated professional program to analyse the recordings: BatExplorer: it´s free and it´s good enough for professional bat researchers.

I used Bat-Explorer already to open recordings made by the Teensy bat detector (unfortunately, no bat out there at this time of the year, so I took a bunch of keys to produce ultrasound). It works, use it like this (copied from my WIKI):

"This is one of the best bat analysis software packages I know, and its free!!! http://www.batlogger.com/en/support/download.html Use the Bat Explorer to analyse your calls that you recorded with your Teensy Bat Detector. Use File --> Open WAV to open the RAW-files from your Teensy Bat Detector SD card. Enter the sample rate you used for the recording, bits and mono channel (example: 192000, 16, 1) and start analysing and hearing your recordings and automatically identify the bat etc. etc."

Have fun with the Teensy bat detector and don´t hesitate to ask if you have any more questions!

Frank
 
Hi

I am waiting for my first Teensy (3.6) to arrive as I am planning to build this project. Ive downloaded your V1.2 sourcecode from GitHub and have been (after adding forward-declarations) able to compile the source using PlatformIO under VisualStudioCode.

One thing I was wondering based on closer inspection of your code is why you are using the SDcard on the Teensy.

Thanks for sharing this,

regards
Cor
 
Hi

I am waiting for my first Teensy (3.6) to arrive as I am planning to build this project. Ive downloaded your V1.2 sourcecode from GitHub and have been (after adding forward-declarations) able to compile the source using PlatformIO under VisualStudioCode.

One thing I was wondering based on closer inspection of your code is why you are using the SDcard on the Teensy.

Thanks for sharing this,

regards
Cor
From previous comments, the micro-SD card is used to record the sounds for later analysis on a computer.
 
Hi Michael,

I understood that, but I was wondering why the SDcard on the AUDIOboard is not used for that purpose.

cheers
Cor
 
Most people use the SD card for playing sounds or short clips. That's the main reason why the audio shield has a SD socket.
 
Hello Paul,

Thanks for chiming in. The reason I have asked this is that in this particular case the SDcard on the audio shield is not used at all. Frank (DD4WH) has - in his software - specifically choosen to use the Teensy SDCard to record raw (up to 192khz samplerate) data. I was just wondering why as the SDcard on the audio shield is directly available too.

Here are the specific lines in the code

"*
* uses the SD card slot of the Teensy, NOT the SD card slot of the audio board
* use an old SD card, preferably 2GB, no HC or class 10
*
¨



Cor
 
The Teensy 3.5/3.6 micro-SD card is faster than external SD cards like is found on the audio shield.

Paul can correct if/when I'm wrong, but I believe the 3.5/3.6 SD card uses a separate SPI bus for the card and it is 4 bit SDIO interface on the chip itself, while standard SD cards transfer data one bit at a time.
 
Back
Top