/*
 * #include <FastIO.h>
#include <I2CIO.h>
#include <LCD.h>
#include <LiquidCrystal.h>
#include <LiquidCrystal_I2C.h>
#include <LiquidCrystal_I2C_ByVac.h>
#include <LiquidCrystal_SI2C.h>
#include <LiquidCrystal_SR.h>
#include <LiquidCrystal_SR1W.h>
#include <LiquidCrystal_SR2W.h>
#include <LiquidCrystal_SR3W.h>
#include <SI2CIO.h>
#include <SoftI2CMaster.h>
*/

///////////////////////////////////////////////////////////////////////
// CW Decoder made by Hjalmar Skovholm Hansen OZ1JHM  VER 1.01       //
// Feel free to change, copy or what ever you like but respect       //
// that license is http://www.gnu.org/copyleft/gpl.html              //
// Discuss and give great ideas on                                   //
// https://groups.yahoo.com/neo/groups/oz1jhm/conversations/messages //
//                                                                   //
// Modifications by KC2UEZ. Bumped to VER 1.2:                       //
// Changed to work with the Arduino NANO.                            //
// Added selection of "Target Frequency" and "Bandwith" at power up. //
//
// Modifications by AA6BD 
// Changed to work with ESP32 and 320x240 LCD display
// I removed the LCD panel code for a cleaner appearance
///////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////
// Read more here http://en.wikipedia.org/wiki/Goertzel_algorithm        //
// if you want to know about FFT the http://www.dspguide.com/pdfbook.htm //
///////////////////////////////////////////////////////////////////////////

#include <TFT_eSPI.h>  // aa6bd
#include "Free_Fonts.h" // Include the header file attached to this sketch

// aa6bd define statements copied from sketch_NTP_DualClock
#define TITLE              "CW DECODER"
#define SCREEN_ORIENTATION       3                 // screen portrait mode:  use 1 or 3
#define LED_PIN                  2                 // built-in LED is on GPIO 2
//#define DEBUGLEVEL            INFO                 // NONE, ERROR, INFO, or DEBUG
#define BAUDRATE            115200                 // serial output baudrate                                       
#define TIMECOLOR         TFT_CYAN                 // color of 7-segment time display
#define DATECOLOR       TFT_YELLOW                 // color of displayed month & day
#define LABEL_FGCOLOR   TFT_YELLOW                 // color of label text
#define LABEL_BGCOLOR     TFT_BLUE                 // color of label background

#define XINCR                    20                 // X pixel increment for display
#define YINCR                    28                 // y pixel increment for display

TFT_eSPI tft = TFT_eSPI();  // aa6bd

const int colums = 15; /// have to be 16 or 20
const int rows = 4;  /// have to be 2 or 4

int lcdindex = 0;
int line1[colums];
int line2[colums];

int audioInPin = 13;  // A7 Changed to work with Nano by KC2UEZ Changed to 13 aa6bd
int audioOutPin = 12;  // 8 Changed to work with Nano by KC2UEZ  Changed to 12 aa6bd
int ledPin = 2;  // was 13, aa6bd changed to internal led

void blink(int count=1) {                          // diagnostic LED blink
  pinMode(LED_PIN,OUTPUT);                         // make sure pin is an output
  for (int i=0; i<count; i++) {                    // blink counter
    digitalWrite(LED_PIN,1);                       // turn LED on 
    delay(200);                                    // for 0.2s
    digitalWrite(LED_PIN,0);                       // and then turn LED off
    delay(200);                                    // for 0.2s
  } 
//  pinMode(LED_PIN,INPUT);                          // works for both Vcc & Gnd LEDs.
}

void startupScreen() {  
  tft.fillScreen(TFT_BLACK);                       // start with empty screen
  tft.fillRoundRect(0,0,319,30,10,LABEL_BGCOLOR);  // title bar
  tft.drawRoundRect(0,0,319,120,10,TFT_WHITE);     // draw edge screen
  tft.setTextColor(LABEL_FGCOLOR,LABEL_BGCOLOR);   // set label colors
  tft.drawCentreString(TITLE,160,2,4);             // show sketch title on screen
  tft.setTextColor(LABEL_FGCOLOR, TFT_BLACK);      // set text color
}

float magnitude ;
int magnitudelimit = 100;
int magnitudelimit_low = 100;
int realstate = LOW;
int realstatebefore = LOW;
int filteredstate = LOW;
int filteredstatebefore = LOW;


///////////////////////////////////////////////////////////
// The sampling frq will be 8928 on a 16 mhz             //
// without any prescaler etc                             //
// because we need the tone in the center of the bins    //
// you can set the tone to 496, 558, 744 or 992          //
// then n the number of samples which give the bandwidth //
// can be (8928 / tone) * 1 or 2 or 3 or 4 etc           //
// init is 8928/558 = 16 *4 = 64 samples                 //
// try to take n = 96 or 128 ;o)                         //
// 48 will give you a bandwidth around 186 hz            //
// 64 will give you a bandwidth around 140 hz            //
// 96 will give you a bandwidth around 94 hz             //
// 128 will give you a bandwidth around 70 hz            //
// BUT remember that high n take a lot of time           //
// so you have to find the compromice - i use 48         //
///////////////////////////////////////////////////////////

float coeff;
float Q1 = 0;
float Q2 = 0;
float sine;
float cosine;  
float spd_factor=2;
float sampling_freq=8928*spd_factor;  //aa6bd changed from 8928 due to 10x ESP32 speed
float target_freq=0.0; /// adjust for your needs see above
int n=0;  //// if you change  her please change next line also 
int testData[1280];
float bw;

//////////////////////////////
// Noise Blanker time which //
// shall be computed so     //
// this is initial          //
//////////////////////////////
int nbtime = 6;  /// ms noise blanker         

long starttimehigh;
long highduration;
long lasthighduration;
long hightimesavg;
long lowtimesavg;
long startttimelow;
long lowduration;
long laststarttime = 0;

char code[20];
int stop = LOW;
int wpm;


////////////////
// init setup //
////////////////
void setup() {

////////////////////////////////////
// The basic goertzel calculation //
////////////////////////////////////

/////////////////////////////////////////////////////////////
// Modifications by KC2UEZ                                 //
// This code lets you select BW and Frequency at power up. //
/////////////////////////////////////////////////////////////

 pinMode(32, INPUT_PULLUP);  // was A0,A1,A2,A3, changed to 32,33,16,17 aa6bd
 pinMode(33, INPUT_PULLUP);
 pinMode(16, INPUT_PULLUP);
 pinMode(17, INPUT_PULLUP);


 if (!digitalRead(16) && !digitalRead(17))
   target_freq=496.0;
 else if (!digitalRead(16) && digitalRead(17))
   target_freq=558.0;
 else if (digitalRead(16) && !digitalRead(17))
   target_freq=744.0;
 else
   target_freq=992.0;

 if (!digitalRead(32) && !digitalRead(33))
   n=sampling_freq/target_freq*1;
 else if (!digitalRead(32) && digitalRead(33))
   n=sampling_freq/target_freq*2;
 else if (digitalRead(32) && !digitalRead(33))
   n=sampling_freq/target_freq*3;
 else
   n=sampling_freq/target_freq*4;

//////////////////////////
// End of modifications //
//////////////////////////

// aa6bd
 bw = (sampling_freq/n);
 
  int	k;
  float	omega;
  k = (int) (0.5 + ((n * target_freq) / sampling_freq));
  omega = (2.0 * PI * k) / n;
  sine = sin(omega);
  cosine = cos(omega);
  coeff = 2.0 * cosine;

  tft.init();                                      // initialize LCD screen object
  tft.setRotation(SCREEN_ORIENTATION);             // landscape screen orientation
  startupScreen();                                 // show title
  blink(3);                                        // show sketch is starting
  Serial.begin(BAUDRATE);                          // open serial port
  delay(3000);

// AA6BD
// Monitor what is set by jumpers
Serial.print("16=");
Serial.print(digitalRead(16));
Serial.print(" 17=");
Serial.print(digitalRead(17));
Serial.print(" samp_freq=");
Serial.print(sampling_freq);
Serial.print(" target_freq=");
Serial.println(target_freq);

Serial.print("32=");
Serial.print(digitalRead(32));
Serial.print(" 33=");
Serial.print(digitalRead(33));
Serial.print(" n=");
Serial.print(n);
Serial.print(" bw=");
Serial.println(bw);

// initialize line characters to 32;  aa6bd
 for (int index = 0; index < colums; index++){
    line1[index] = 32;
	line2[index] = 32;
 }           

}

///////////////
// main loop //
///////////////
 void loop() {

  ///////////////////////////////////// 
  // The basic where we get the tone //
  /////////////////////////////////////
  
  for (char index = 0; index < n; index++)
  {
    testData[index] = analogRead(audioInPin);
  }
  for (char index = 0; index < n; index++){
	  float Q0;
	  Q0 = coeff * Q1 - Q2 + (float) testData[index];
	  Q2 = Q1;
	  Q1 = Q0;	
  }
  float magnitudeSquared = (Q1*Q1)+(Q2*Q2)-Q1*Q2*coeff;  // we do only need the real part //
  magnitude = sqrt(magnitudeSquared);
  Q2 = 0;
  Q1 = 0;

  //Serial.print(magnitude); Serial.println();  //// here you can measure magnitude for setup..
  
  /////////////////////////////////////////////////////////// 
  // here we will try to set the magnitude limit automatic //
  ///////////////////////////////////////////////////////////
  
  if (magnitude > magnitudelimit_low){
    magnitudelimit = (magnitudelimit +((magnitude - magnitudelimit)/6));  /// moving average filter
  }
 
  if (magnitudelimit < magnitudelimit_low)
	magnitudelimit = magnitudelimit_low;
  
  ////////////////////////////////////
  // now we check for the magnitude //
  ////////////////////////////////////

  if(magnitude > magnitudelimit*0.6) // just to have some space up 
     realstate = HIGH; 
  else
    realstate = LOW; 
  
  ///////////////////////////////////////////////////// 
  // here we clean up the state with a noise blanker //
  /////////////////////////////////////////////////////
 
  if (realstate != realstatebefore){
	laststarttime = millis();
  }
  if ((millis()-laststarttime)> nbtime){
	if (realstate != filteredstate){
		filteredstate = realstate;
	}
  }
 
 ////////////////////////////////////////////////////////////
 // Then we do want to have some durations on high and low //
 ////////////////////////////////////////////////////////////
 
 if (filteredstate != filteredstatebefore){
	if (filteredstate == HIGH){
		starttimehigh = millis();
		lowduration = (millis() - startttimelow);
	}

	if (filteredstate == LOW){
		startttimelow = millis();
		highduration = (millis() - starttimehigh);
        if (highduration < (2*hightimesavg) || hightimesavg == 0){
			hightimesavg = (highduration+hightimesavg+hightimesavg)/3;     // now we know avg dit time ( rolling 3 avg)
		}
		if (highduration > (5*hightimesavg) ){
			hightimesavg = highduration+hightimesavg;     // if speed decrease fast ..
		}
	}
  }

 ///////////////////////////////////////////////////////////////
 // now we will check which kind of baud we have - dit or dah //
 // and what kind of pause we do have 1 - 3 or 7 pause        //
 // we think that hightimeavg = 1 bit                         //
 ///////////////////////////////////////////////////////////////
 
 if (filteredstate != filteredstatebefore){
  stop = LOW;
  if (filteredstate == LOW){  //// we did end a HIGH
   if (highduration < (hightimesavg*2) && highduration > (hightimesavg*0.6)){ /// 0.6 filter out false dits
	strcat(code,".");
	Serial.print(".");
   }
   if (highduration > (hightimesavg*2) && highduration < (hightimesavg*6)){ 
	strcat(code,"-");
	Serial.print("-");
	wpm = (wpm + (1200/((highduration)/3)))/2;  //// the most precise we can do ;o)
   }
  }
 
   if (filteredstate == HIGH){  //// we did end a LOW
   
   float lacktime = 1;
   if(wpm > 25)lacktime=1.0; ///  when high speeds we have to have a little more pause before new letter or new word 
   if(wpm > 30)lacktime=1.2;
   if(wpm > 35)lacktime=1.5;
   
   if (lowduration > (hightimesavg*(2*lacktime)) && lowduration < hightimesavg*(5*lacktime)){ // letter space
    docode();
	code[0] = '\0';
	Serial.print("/");
   }
   if (lowduration >= hightimesavg*(5*lacktime)){ // word space
    docode();
	code[0] = '\0';
	printascii(char(32));
	Serial.println();
   }
  }
 }
 
 //////////////////////////////
 // write if no more letters //
 //////////////////////////////

  if ((millis() - startttimelow) > (highduration * 6) && stop == LOW){
   docode();
   code[0] = '\0';
   stop = HIGH;
  }

 /////////////////////////////////////
 // we will turn on and off the LED //
 // and the speaker                 //
 /////////////////////////////////////
 
   if(filteredstate == HIGH){ 
     digitalWrite(ledPin, HIGH);
//	 tone(audioOutPin,target_freq);
   }
   else{
     digitalWrite(ledPin, LOW);
//	 noTone(audioOutPin);
   }
 
 //////////////////////////////////
 // the end of main loop clean up//
 /////////////////////////////////
 updateinfolinelcd();
 realstatebefore = realstate;
 lasthighduration = highduration;
 filteredstatebefore = filteredstate;
 }
// end of loop()

////////////////////////////////
// translate cw code to ascii //
////////////////////////////////
// aa6bd change from ascii code to actual character

void docode(){
    if (strcmp(code,".-") == 0) printascii(char(65));
	if (strcmp(code,"-...") == 0) printascii(char(66));
	if (strcmp(code,"-.-.") == 0) printascii(char(67));
	if (strcmp(code,"-..") == 0) printascii(char(68));
	if (strcmp(code,".") == 0) printascii(char(69));
	if (strcmp(code,"..-.") == 0) printascii(char(70));
	if (strcmp(code,"--.") == 0) printascii(char(71));
	if (strcmp(code,"....") == 0) printascii(char(72));
	if (strcmp(code,"..") == 0) printascii(char(73));
	if (strcmp(code,".---") == 0) printascii(char(74));
	if (strcmp(code,"-.-") == 0) printascii(char(75));
	if (strcmp(code,".-..") == 0) printascii(char(76));
	if (strcmp(code,"--") == 0) printascii(char(77));
	if (strcmp(code,"-.") == 0) printascii(char(78));
	if (strcmp(code,"---") == 0) printascii(char(79));
	if (strcmp(code,".--.") == 0) printascii(char(80));
	if (strcmp(code,"--.-") == 0) printascii(char(81));
	if (strcmp(code,".-.") == 0) printascii(char(82));
	if (strcmp(code,"...") == 0) printascii(char(83));
	if (strcmp(code,"-") == 0) printascii(char(84));
	if (strcmp(code,"..-") == 0) printascii(char(85));
	if (strcmp(code,"...-") == 0) printascii(char(86));
	if (strcmp(code,".--") == 0) printascii(char(87));
	if (strcmp(code,"-..-") == 0) printascii(char(88));
	if (strcmp(code,"-.--") == 0) printascii(char(89));
	if (strcmp(code,"--..") == 0) printascii(char(90));

	if (strcmp(code,".----") == 0) printascii(char(49));
	if (strcmp(code,"..---") == 0) printascii(char(50));
	if (strcmp(code,"...--") == 0) printascii(char(51));
	if (strcmp(code,"....-") == 0) printascii(char(52));
	if (strcmp(code,".....") == 0) printascii(char(53));
	if (strcmp(code,"-....") == 0) printascii(char(54));
	if (strcmp(code,"--...") == 0) printascii(char(55));
	if (strcmp(code,"---..") == 0) printascii(char(56));
	if (strcmp(code,"----.") == 0) printascii(char(57));
	if (strcmp(code,"-----") == 0) printascii(char(48));

	if (strcmp(code,"..--..") == 0) printascii(char(63));
	if (strcmp(code,".-.-.-") == 0) printascii(char(46));
	if (strcmp(code,"--..--") == 0) printascii(char(44));
	if (strcmp(code,"-.-.--") == 0) printascii(char(33));
	if (strcmp(code,".--.-.") == 0) printascii(char(64));
	if (strcmp(code,"---...") == 0) printascii(char(58));
	if (strcmp(code,"-....-") == 0) printascii(char(45));
	if (strcmp(code,"-..-.") == 0) printascii(char(47));

	if (strcmp(code,"-.--.") == 0) printascii(char(40));
	if (strcmp(code,"-.--.-") == 0) printascii(char(41));
	if (strcmp(code,".-...") == 0) printascii(char(95));
	if (strcmp(code,"...-..-") == 0) printascii(char(36));
	if (strcmp(code,"...-.-") == 0) printascii(char(62));
	if (strcmp(code,".-.-.") == 0) printascii(char(60));
	if (strcmp(code,"...-.") == 0) printascii(char(126));
	//////////////////
	// The specials //
	//////////////////
	if (strcmp(code,".-.-") == 0) printascii(char(3));
	if (strcmp(code,"---.") == 0) printascii(char(4));
	if (strcmp(code,".--.-") == 0) printascii(char(6));

}

/////////////////////////////////////
// print the ascii code to the lcd //
// one a time so we can generate   //
// special letters                 //
/////////////////////////////////////
void printascii(char asciinumber){

//int fail = 0;
//if (rows == 4 and colums == 16)fail = -4; /// to fix the library problem with 4*16 display http://forum.arduino.cc/index.php/topic,14604.0.html
 tft.setFreeFont(FMO18);
 
 if (lcdindex > colums-1){
  lcdindex = 0;
  startupScreen();
  if (rows==4){
	  for (int i = 0; i <= colums-1 ; i++){
      tft.drawChar((char)line2[i], i*XINCR, (rows-3)*YINCR+120, GFXFF);  // char, x, y, font
  		line2[i]=line1[i];
	  }
  }
  for (int i = 0; i <= colums-1 ; i++){
    tft.drawChar((char)line1[i], i*XINCR, (rows-2)*YINCR+120, GFXFF);
    tft.drawString(" ", i*XINCR, (rows-1)*YINCR+100, GFXFF);
  }
 }
 line1[lcdindex]=asciinumber;
 tft.drawChar((char)asciinumber, lcdindex*XINCR, (rows-1)*YINCR+120, GFXFF);
 lcdindex += 1;
}

void updateinfolinelcd(){
/////////////////////////////////////
// here we update the upper line   //
// with the speed.                 //
/////////////////////////////////////

// Modify to work with Frequency and BW selection. By KC2UEZ
  int place;
  if (rows == 4){
   place = 0;}
  else{
   place = 2;
  }
     
//        if (wpm < 10)
//            tft.drawChar('0', 2*XINCR, 20, 4);
//        else 
    tft.drawNumber(wpm, XINCR, 30, 4);
    tft.drawString(" WPM ", 3*XINCR, 30, 4);
    tft.drawNumber(target_freq, 7*XINCR, 30, 4);
    tft.drawString(" TF ", 9*XINCR, 30, 4);
//        if (bw < 100)
//           tft.drawChar(' ', 10*XINCR, 30, 4);
    tft.drawNumber(bw, 11*XINCR, 30, 4);
    tft.drawString(" BW", 13*XINCR, 30, 4);
}
