Morse encoder/decoder w/audio input, full duplex

Part #2:

  // Encode Morse code or execute commands
  if (Serial.available() > 0 && !sendingMorse)
  {
    char encodeMorseChar = Serial.read();
    
    // if a command instead, adjust some settings
    if (encodeMorseChar == MorseCommand)
    {
      // An extremely crude and simple command parser
      // Expects (and wait for) 2 or 4 characters (>i or <Cxxx)
      int digits;
      int value = 0;
      do
      {
        digits = Serial.available();
      } while (digits < 1); 
      // Read what setting
      char morseSet = Serial.read();
      // Read 3 digits unless setting is i for info
      if (morseSet != 'i' && morseSet != 'I')
      {
        do
        {
          digits = Serial.available();
        } while (digits < 3); 
        // convert value from ASCII
        for (int i=0; i<digits; i++)
        {
          encodeMorseChar = Serial.read();
          value *= 10;
          value += (encodeMorseChar - '0');
        }
      }
      Serial.flush(); // just in case

      // Adjust and print the new setting
      Serial.println();
      switch (morseSet)
      {
        case 'a': // Audio input threshold value
        case 'A':
          AudioThreshold = value;
          if (AudioThreshold<0) AudioThreshold = 0; // not recommended
          Serial.print(" > Audio threshold:");
          Serial.print (value,DEC);
          Serial.println(" <");
          break;
        case 'd': // Debounce value
        case 'D':
          debounceDelay = (unsigned long) value;
          if (debounceDelay<0) debounceDelay = 0;
          Serial.print(" > Debounce (ms):");
          Serial.print (value,DEC);
          Serial.println(" <");
          break;
        case 'e': // Turn on / off Morse echo back to serial and Morse output pin
        case 'E':
          if (value > 0)
          {
             morseEcho = true;
             Serial.println(" > Echo: on <");
          } else {
            morseEcho = false;
            Serial.println(" > Echo: off <");
          }
          break;
        case 'w': // Morse speed setting in wpm
        case 'W':
          wpm = value;
          if (wpm <= 0) wpm = 1;
          dotTime = 1200 / wpm;
          dashTime = 3 * 1200 / wpm;
          wordSpace = 7 * 1200 / wpm;
          Serial.print(" > Morse speed (wpm):");
          Serial.print (value,DEC);
          Serial.println(" <");
          break;
        case 'i': // Display info (current settings).
        case 'I':
          Serial.print(" > Morse speed: ");
          Serial.print (wpm,DEC);
          Serial.print(" wpm (dot=");
          Serial.print (dotTime,DEC);
          Serial.print("ms, dash=");
          Serial.print (dashTime,DEC);
          Serial.print("ms) Debounce: ");
          Serial.print (debounceDelay,DEC);
          Serial.print(" ms. Audio threshold: ");
          Serial.print (AudioThreshold,DEC);
          Serial.println(" <");
          break;
        default:
          Serial.print(" > Unrecognized command <");
      }
      // Mark that we have executed a command (dont send morse)
      encodeMorseChar = MorseCommand; 
    }

    if (encodeMorseChar != MorseCommand)
    {
      // change to capital letter if not
      if (encodeMorseChar > 'Z') encodeMorseChar -= 'z'-'Z';
  
      // Scan for the character to send in the Morse table
      int i;
      for (i=0; i<morseTableLength; i++) if (morseTable[i] == encodeMorseChar) break;
      int morseTablePos = i+1;  // 1-based position
      
      // Reverse dichotomic / binary tree path tracing
      
      // Find out what level in the binary tree the character is
      int test;
      for (i=0; i<morseTreeLevels; i++)
      {
        test = (morseTablePos + (0x0001 << i)) % (0x0002 << i);
        if (test == 0) break;
      }
      int startLevel = i;
      morseSignals = morseTreeLevels - i; // = the number of dots and/or dashes
      morseSignalPos = 0;
      
      // Travel the reverse path to the top of the morse table
      if (morseSignals > 0)
      {
        // build the morse signal (backwards from last signal to first)
        for (i = startLevel; i<morseTreeLevels; i++)
        {
          int add = (0x0001 << i);
          test = (morseTablePos + add) / (0x0002 << i);
          if (test & 0x0001 == 1)
          {
            morseTablePos += add;
            // Add a dot to the temporary morse signal string
            morseSignal[morseSignals-1 - morseSignalPos++] = '.';
          } else {
            morseTablePos -= add;
            // Add a dash to the temporary morse signal string
            morseSignal[morseSignals-1 - morseSignalPos++] = '-';
          }
        }
      } else {  // unless it was on the top to begin with (A space character)
        morseSignal[0] = ' ';
        morseSignalPos = 1;
        morseSignals = 1; // cheating a little; a wordspace for a "morse signal"
      }
      morseSignal[morseSignalPos] = '\0';
  
      // Echo back the letter with morse signal as ASCII to serial output
      if (morseEcho)
      {
        Serial.print(encodeMorseChar);
        Serial.print(morseSignal);
        Serial.print(" ");
      }
      if (morseTablePos-1 != morseTreetop)
      {
        Serial.println();
        Serial.print("..Hm..error? MorseTablePos = ");
        Serial.println(morseTablePos); 
      }
      // start sending the the character
      sendingMorse = true;
      sendingMorseSignalNr = 0;
      sendMorseTimer = millis();
      if (morseSignal[0] != ' ') digitalWrite(morseOutPin, HIGH);
    }
  }



  // Send Morse signals to output
  if (sendingMorse)
  {
    switch (morseSignal[sendingMorseSignalNr])
    {
      case '.': // Send a dot (actually, stop sending a signal after a "dot time")
        if (millis() - sendMorseTimer >= dotTime)
        {
          digitalWrite(morseOutPin, LOW);
          sendMorseTimer = millis();
          morseSignal[sendingMorseSignalNr] = 'x'; // Mark the signal as sent
        }
        break;
      case '-': // Send a dash (same here, stop sending after a dash worth of time)
        if (millis() - sendMorseTimer >= dashTime)
        {
          digitalWrite(morseOutPin, LOW);
          sendMorseTimer = millis();
          morseSignal[sendingMorseSignalNr] = 'x'; // Mark the signal as sent
        }
        break;
      case 'x': // To make sure there is a pause between signals and letters
        if (sendingMorseSignalNr < morseSignals-1)
        {
          // Pause between signals in the same letter
          if (millis() - sendMorseTimer >= dotTime)
          {
            sendingMorseSignalNr++;
            digitalWrite(morseOutPin, HIGH); // Start sending the next signal
            sendMorseTimer = millis();       // reset the timer
          }
        } else {
          // Pause between letters
          if (millis() - sendMorseTimer >= dashTime)
          {
            sendingMorseSignalNr++;
            sendMorseTimer = millis();       // reset the timer
          }
        }
        break;
      case ' ': // Pause between words (minus pause between letters - already sent)
      default:  // Just in case its something else
        if (millis() - sendMorseTimer > wordSpace - dashTime) sendingMorse = false;
    }
    if (sendingMorseSignalNr >= morseSignals) sendingMorse = false; // Ready to encode more letters
  }



  // Decode morse code
  if (!morseSignalState)
  {
    if (!gotLastSig)
    {
      if (morseTableJumper > 0)
      {
        // if pause for more than half a dot, get what kind of signal pulse (dot/dash) received last
        if (millis() - spaceTime > dotTime/2)
        {
          // if signal for more than 1/4 dotTime, take it as a valid morse pulse
          if (spaceTime-markTime > dotTime/4)
          {
            // if signal for less than half a dash, take it as a dot, else if not, take it as a dash
            // (dashes can be really really long...)
            if (spaceTime-markTime < dashTime/2) morseTablePointer -= morseTableJumper;
            else morseTablePointer += morseTableJumper;
            morseTableJumper /= 2;
            gotLastSig = true;
          }
        }
      } else { // error if too many pulses in one morse character
        Serial.println("<ERROR: unrecognized signal!>");
        gotLastSig = true;
        morseTableJumper = (morseTreetop+1)/2;
        morseTablePointer = morseTreetop;
      }
    }
    // Write out the character if pause is longer than 2/3 dash time (2 dots) and a character received
    if ((millis()-spaceTime >= (dotTime*2)) && (morseTableJumper < 16))
    {
      char morseChar = morseTable[morseTablePointer];
      Serial.print(morseChar);
      morseTableJumper = (morseTreetop+1)/2;
      morseTablePointer = morseTreetop;
    }
    // Write a space if pause is longer than 2/3rd wordspace
    if (millis()-spaceTime > (wordSpace*2/3) && morseSpace == false)
    {
      Serial.print(" ");
      morseSpace = true ; // space written-flag
    }
    
  } else {
    // while there is a signal, reset some flags
    gotLastSig = false;
    morseSpace = false;
  }



  // save last state of the morse signal for debouncing
  lastKeyerState = morseKeyer;
}