/*
 * Gray_MorseEncoder.ino
 *
 * Code by: Gray Mack
 * Created: 1/9/2024
 * Description: Was looking for a particular style of Morse code
 * and couldn't find what I was looking for, so created it.
 * Not the most efficient solution but more for fun
*/
const byte PIN_SPEAKER = 7;
const byte PIN_LED = LED_BUILTIN;
const byte PIN_SPEED_POT = A0;
const byte PIN_TONE_FREQ_POT = A1;

const char* MorseAlphaTable[] = {
".-",    // a
"-...",  // b
"-.-.",  // c
"-..",   // d
".",     // e
"..-.",  // f
"--.",   // g
"....",  // h
"..",    // i
".---",  // j
"-.-",   // k
".-..",  // l
"--",    // m
"-.",    // n
"---",   // o
".--.",  // p
"--.-",  // q
".-.",   // r
"...",   // s
"-",     // t
"..-",   // u
"...-",  // v
".--",   // w
"-..-",  // x
"-.--",  // y
"--..",  // z
};

const char* MorseNumberTable[] = {
"-----",  // 0
".----",  // 1
"..---",  // 2
"...--",  // 3
"....-",  // 4
".....",  // 5
"-....",  // 6
"--...",  // 7
"---..",  // 8
"----.",  // 9
};

// speed calculation. If we aim for 5 words per minute and to send 5,7,10, or 12 words per minute
// 60sec / (typical word 5 letters *(letters have about 4 digits, say -.-. is 3+1+1+1+3+1+1+1+1+2)=15 ) + word pause 6)
// 60000msec / (5*15+6) = 133 for about 5 words per minute
// 60000msec / (10*15+6) = 67 for about 10 words per minute
// converted the constants to variables set by analog pots
uint16_t DotLengthMs = 133;
#define DashLengthMs() (DotLengthMs*3)
#define PauseBetweenDDLengthMs() (DotLengthMs) // pause between parts of the same letter
#define LetterSpaceLengthMs() (DotLengthMs*2) // pause between leters/numbers is 3 but we already added one after the ./- so use 2
#define WordSpaceLengthMs() (DotLengthMs*6) // pause between words is 7 but we already added one after the digit, so use 6
uint16_t ToneFrequency = 500; // some go as low as 50Hz


void setup() {
  Serial.begin(9600);
  pinMode(PIN_SPEED_POT, INPUT);
  pinMode(PIN_TONE_FREQ_POT, INPUT);
  pinMode(PIN_LED, OUTPUT);
  digitalWrite(PIN_LED, LOW);
  SendMorseString("Hello, world. How did I get here?");
  Serial.println("Enter a string to convert to Morse:");
  Serial.setTimeout(-1);
}

void loop() {
  String str = Serial.readStringUntil('\n');
  SendMorseString(str.c_str());
  Serial.println();
}

void SendMorseString(const char *str)
{
  Serial.println(str);
  const char* codePointer;
  for(uint8_t i=0; i<strlen(str); i++)
  {
    char ch = str[i];
    if(isAlpha(ch))
    {
      codePointer = MorseAlphaTable[toLowerCase(ch)-'a'];
      SendMorseAlphaNumeric(codePointer);
    }
    else if(isDigit(ch))
    {
      codePointer = MorseNumberTable[ch-'0'];
      SendMorseAlphaNumeric(codePointer);
    }
    else if(isWhitespace(ch))
    {
      Serial.print(' ');
      delay(WordSpaceLengthMs());
    }
    else if(ch=='.' || ch=='?')
    {
      Serial.println();
    }
  }
}

void SendMorseAlphaNumeric(const char* codeStr)
{
  DotLengthMs = UpdateSpeed();
  ToneFrequency = UpdateToneFreq();

  for(uint8_t i=0; i<strlen(codeStr); i++)
  {
    char dd = codeStr[i]; // grab our current dot or dash
    Serial.print(dd);
    digitalWrite(PIN_LED, HIGH);
    tone(PIN_SPEAKER, ToneFrequency);
    delay(dd == '-' ? DashLengthMs() : DotLengthMs);
    digitalWrite(PIN_LED, LOW);
    noTone(PIN_SPEAKER);
    delay(PauseBetweenDDLengthMs());
  }
  Serial.print(' ');
  delay(LetterSpaceLengthMs());
}

uint16_t UpdateSpeed()
{
  uint16_t speed = analogRead(PIN_SPEED_POT);
  return map(speed,  0, 1023,  400, 20);
}

uint16_t UpdateToneFreq()
{
  uint16_t freq = analogRead(PIN_TONE_FREQ_POT);
  return map(freq,  0, 1023,  35, 2000);
}