ericBcreator
Published © GPL3+

A-M-P: Arduino Music Player

Bring back the 8-bit music! Arduino / ESP library for playing melodies with visual feedback (notes or lyrics).

IntermediateShowcase (no instructions)2 hours5,280

Things used in this project

Hardware components

Wemos D1 Mini
Espressif Wemos D1 Mini
×1
Speaker: 3W, 4 ohms
Speaker: 3W, 4 ohms
×1
Rotary Encoder with Push-Button
Rotary Encoder with Push-Button
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

Wemos D1 mini AMP schematic

Code

AMP 20191229 update

Arduino
/*
***************************************************************************
  Arduino Melody Player by ericBcreator
  loosely based on the old qbasic PLAY command 
  with lyrics and ESP boards support
***************************************************************************
  version 0.95 - last update 20191229 by ericBcreator
***************************************************************************
  Features:
***************************************************************************
  Syntax:  
    playMelody(string);

    commands:
    CDEFGABn  notes, n = note length: 1, 2, 4, 8, 16, 32, 64,
              2, 4, 8, 16, 32 dotted (or 3, 6, 12, 24, 48),
              4, 8, 16, 32 triplet 34, 38, 316, 332
              defaults to Ln if n is omitted
    Rn        rest, n = note length. defaults to Ln if n is omitted

    Tn        tempo n = 32-255 (default is 120 BPM, 4/4 time base)
    On        octave n = 1-8 (default is 4)
    Ln        n = default note length (default is 4, quarter note)
    MN        music normal:      7/8 note length duration (default)
    ML        or music legato:   full length duration
    MS        or music staccato: 3/4 note length duration
    MU        mute mode

    <         octave down (range 1-8)
    >         octave up (range 1-8)
    [         transpose down a step (range -12 to 12)
    ]         transpose up a step (range -12 to 12)
    #         sharp (place after note, D#n)
    -         flat (place after note, D-n)
    .         dotted note (note length will be increased with 50%)
    
    $         reset the default settings. recommended at the start
              of a new song
              
              note: the Tn, On, Ln, M? and [ ] settings are static 
                    so will remain in memory with the next call 
                    to playtune. It is recommended to reset these 
                    at the beginning of a new melody with the $
                    command
              note: spaces are ignored, use them for readability

	  example the Big Ben tune: 
              playMelody("$T120 L4 O5 ECD<GR2 G>DEC R2");
    or:       melody = "$T120 L4 O5 ECD<GR2 G>DEC R2";
              playMelody(melody);

    lyric support: 
		          The global String 'amp_lyrics' can be used to display 
              lyrics while playing melodies. The code will display words
              and syllables synchronized to the order of the notes, so
              word 1 for note 1, word 2 for note 2, etc.
              Words can be broken into syllables by adding a - (hyphen).
              Words have to be separated with space.
              A double space will be treated as an empty word.
              Lyrics are not played with rests.

	  example:
			        lyrics = "Jin-gle bells Jin-gle bells Jin-gle all the way";
              playMelody("$T240 L4 O4 EEE2 EEE2 EGC.D8 E1");
***************************************************************************
  Parts:
    - Arduino / ESP board (I used a Wemos D1 mini)
    - LED matrices
    - small speaker
    - button or rotary encoder
***************************************************************************
  Todo list:
    - cleanup
	  - check @EB-todo
    - callback functions
    - delay and check loop
    - timing accuracy (check)
    - add double tempo?
	  - amp_noteLengthToMS: use array, only calc after tempo change?
***************************************************************************
  This code is free for personal use, not for commercial purposes.
  Please leave this header intact.

  contact: ericBcreator@gmail.com
***************************************************************************
*/

//
// includes
//

#include "amp_notes.h"                                  // include the notes
#include <Adafruit_GFX.h>                               // Needed for the matrix          https://github.com/adafruit/Adafruit-GFX-Library
#include <Max72xxPanel.h>                               // Matrix library                 https://github.com/markruys/arduino-Max72xxPanel
#include <RotaryEncoder.h>                              // Rotary encoder library         https://github.com/mathertel/RotaryEncoder
#include <SPI.h>

//
// definitions
//

#define ESP                                             // @EB-setup uncomment when using an ESP

//#define DEBUG                                         // enable debug messages
//#define DEBUG_PLAY_CMD                                // print play commands
//#define DEBUG_SILENCE

#ifdef DEBUG
  #define DEBUGPRINT(x)   Serial.print(x)
  #define DEBUGPRINTLN(x) Serial.println(x)
#else
  #define DEBUGPRINT(x)
  #define DEBUGPRINTLN(x)
#endif

#ifdef ESP                                              // ESP pins
  #define RE_PINA    D1                                 // @EB-setup rotary encoder: pinA / clock pin number    // VCC -> 5V  GND -> GND
  #define RE_PINB    D2                                 // @EB-setup rotary encoder: pinB / data pin number
  #define RE_SWITCH  D3                                 // @EB-setup rotary encoder: switch / pin number
  #define PIN_CS     D4                                 // @EB-setup CS pin LED Matrix          // VCC -> 5V  GND -> GND  DIN -> D7 (MOSI) CS  -> D4  CLK -> D5 (SCK)
  #define AMP_SPEAKER_PIN D8                            // @EB-setup the pin of the buzzer of speaker
  
#else                                                   // arduino pins  @EB-todo
  #define RE_PINA    1                                  // @EB-setup rotary encoder: pinA / clock pin number    // VCC -> 5V  GND -> GND
  #define RE_PINB    2                                  // @EB-setup rotary encoder: pinB / data pin number
  #define RE_SWITCH  3                                  // @EB-setup rotary encoder: switch / pin number
  #define PIN_CS     10                                 // @EB-setup CS pin LED Matrix          // VCC -> 5V  GND -> GND  DIN -> 11 (MOSI) CS  -> 10  CLK -> 13 (SCK)
  #define AMP_SPEAKER_PIN 8                             // @EB-setup the pin of the buzzer of speaker
#endif

//
// led matrix setup
//
    
int numberOfHorizontalDisplays = 8;                     // @EB-setup
int numberOfVerticalDisplays = 1;                       // @EB-setup
Max72xxPanel matrix = Max72xxPanel(PIN_CS, numberOfHorizontalDisplays, numberOfVerticalDisplays);

int ledBrightness = 3;                                  // @EB-setup led brightness 0-15
int ledScrollDelayTime = 30;                            // @EB-setup delay time for scrolling messages

int ledSpacer = 1;
int ledWidth = 5 + ledSpacer;                           // The font width is 5 pixels
byte ledYOffset = 0;

//
// rotary encoder setup
//

RotaryEncoder RE_encoder(RE_PINA, RE_PINB);             // initialize the rotary encoder

//
// set variables
//

byte amp_lyricsMode = 0;                                // @EB-setup 0: no lyrics, 1: notes, 2: lyrics (or notes if no lyrics are set), 3: lalala ;-)
byte amp_buttonPressed = 0;
String amp_lyrics = "";
String melody = "";

int melodyNum = 0;
bool musicOff = false;
byte loopMode = 0;                                      // @EB-setup 0: loop all songs, 1: loop one song, 2: play one song, don't loop

//
// setup
//

void setup() {
  #ifdef DEBUG
    Serial.begin(115200);
    Serial.println();
  #endif
  
  setupMatrix();
  pinMode(RE_SWITCH, INPUT_PULLUP);

  playMelody("$T120 L16 O4 CEG>C8");
  displayMessage("AMP: Arduino Music Player");

  amp_buttonPressed = 0;
  amp_lyricsMode = 2;
  melodyNum = 8;
  loopMode = 0;
}

//
// main loop
//

void loop() {
  if (!musicOff) {
    DEBUGPRINTLN("Melody " + (String) melodyNum);

    switch (melodyNum) {
      case 0: 
        displayMessage("Note scale");
        playToneScale(4);
        break;
      
      case 1: 
        displayMessage("Firestone");
        playTuneFireStone();  
        break;

      case 2:
        displayMessage("Sweet but a psycho");
        amp_lyrics =  "Oh, she's sweet but a psy-cho ";
        amp_lyrics += "A lit-tle bit psy-cho ";
        amp_lyrics += "At night she's screa-min' ";
        amp_lyrics += "I'm-ma-ma out my mi-nd ";
        amp_lyrics += "Oh, she's hot but a psy-cho ";
        amp_lyrics += "So left but she's right though ";
        amp_lyrics += "At night she's screa-min' ";
        amp_lyrics += "I'm-ma-ma out my mi-nd";
        playMelody("$T133 L8 O4 [ G4G4GF#GF#4 D4DDEF#A4 E4EF#E#F#E <B>F#F#F#4EED  G4G4GF#GF#4 D4DDEF#A4 E4EF#E#F#E <B>F#F#F#4EED");
        break;
              
      case 3:
        displayMessage("Shotgun");
        amp_lyrics =  "I'll be ri-ding ";
        amp_lyrics += "shot-gun un-der-neith the hot sun fee-ling like I'm some-one I'll be ri-ding ";
        amp_lyrics += "shot-gun un-der-neith the hot sun fee-ling like I'm some-one";
        
        playMelody("$T116 L8 O4");
        melody = "GAGD FRFRGAGD FRFRGAGD FRFRR2 R2";
        playMelody(melody);
        playMelody(melody);
        break;

      case 4: 
        displayMessage("Dragostea din tei");
        playMelody("$T130 L8 O4 ]] C<B>CA4. R4C<B>CG4. R4C<B>CG4. R4C<B>C>C R<AR4  C<B>CA4. R4C<B>CG4. R4C<B>CG4. R4C<B>C>C R<AR4");
        break;

      case 5: 
        displayMessage("Star wars");
        playTuneStarWars();   
        break;

      case 6: 
        displayMessage("Big Ben");
        playMelody("$T120 L4 O4 ML ECD<GR2 G>DECR2");
        break;

      case 7:
        displayMessage("Give it up");
        playMelody("$T128 L8 O5");
        melody = "E-16-E-E-16E-E-16E-E-16E-E-E- E-4R4CCDD E-4GGF4R A-4A-GFE-FR4";
        playMelody(melody);
        playMelody(melody);
        break;

      case 8: 
        displayMessage("Jingle bells");
        playMelody("$T240 L4 O4");
        amp_lyrics = "Jin-gle bells Jin-gle bells Jin-gle all the way Oh what fun it is to ride in a one horse o-pen sleigh- ";
        amp_lyrics +=  "Jin-gle bells Jin-gle bells Jin-gle all the way Oh what fun it is to ride in a one horse o-pen sleigh";
        melody = "EEE2 EEE2 EGC.D8 E1 FFF.F8 FEEE8E8 EDDE D2G2";
        melody += "EEE2 EEE2 EGC.D8 E1 FFF.F8 FEEE8E8 GGFD C1";
        playMelody(melody);
        break;

      case 9: 
        displayMessage("Firework");
        playMelody("$T124 L8 O4");        
        amp_lyrics = "'Cos ba-by you're a fi-re-work Come on, show them what you're worth Make them go-o Oh Oh Oh! As you shoot a-cross the sky-y-y! ";
        amp_lyrics += "Ba-by you're a fi-re-work Come on, let your co-lours burst Make them go-o Oh Oh Oh! You're gon-na leave them go-ing Oh Oh Oh Oh!";
        melody = "E>EDC#<B  B2.A>C#2R EDC#<B  B2.A>C#2R EDC#<B  B4RB.R16RB4  R4 EE>EDC#<B  B4RB.R16RB4";
        melody += "R2>EDC#<B  B2.A>C#2R EDC#<B  B2.A>C#2R EDC#<B  B4RB.R16RB4  R EEE>EDC#<B  B4RB.R16RB4.A4";
        playMelody(melody);
        break;

      case 10: 
//        displayMessage("Timing test 120 BPM");
//        playTimingTest();     
        break;

      default:
        melodyNum = 0; // 1 gets added in loop so the note scale (melody 0) get skipped. set to -1 to not skip
    }
  }

  amp_lyrics = "";
  
  switch (amp_buttonPressed) {
    case 1:
      if (loopMode == 0)
        melodyNum++;
      delay(500);
      amp_buttonPressed = 0;
      break;
      
    case 2:
      musicOff = !musicOff;
      displayMessage(musicOff == false ? "Music on" : "Music off");
      DEBUGPRINTLN(musicOff == false ? "Music on" : "Music off");
      amp_buttonPressed = 0;
      break;
      
    default:
      delayAndCheckEnc(2000);
      if (amp_buttonPressed)
        break;
      
      if (loopMode == 0)
        melodyNum++;
  }

  if (loopMode == 2 || musicOff == true) {
    DEBUGPRINTLN("Pause loop");
    do {
      displayMessage("AMP: Arduino Music Player");
      if (!amp_buttonPressed)
        delayAndCheckEnc(20000);
    } while (!amp_buttonPressed);
    
    musicOff = false;
    amp_buttonPressed = 0;
  }
}

///
/// melodies
///

void playSharpFlat() {
  playMelody("$T120 L8 O4");
  
  playMelody("cc#dd#eff#gg#aa#b>c4 r4");
  playMelody("c<bb-aa-gg-fee-dd-c4");
}

void playToneScale(byte oneOctave) {
  int startOctave = 1;
  int endOctave = 8;

  if (oneOctave) {
    startOctave = oneOctave;
    endOctave = oneOctave;
  }

  playMelody("$T120 L8 O" + (String) startOctave);
  
  for (int i = startOctave; i <= endOctave; i++) {
    playMelody("cc#dd#eff#gg#aa#b >");
    if (amp_buttonPressed)
      break;
  }

  if (oneOctave)
    playMelody("c");
}

void playTimingTest() {
  playMelody("$T120 L8 O4");
  while (1) {
    playMelody("GRCRCRCR");
    if (amp_buttonPressed)
      break;
  }
}

void playTuneStarWars() {
  String tune;
  tune = "$T105 L8 O4 D38D38D38";
  tune += "G2>D2 C38<B38A38>G2D4 C38<B38A38>G2D4 C38<B38>C38<A2D34D38";
  tune += "G2>D2 C38<B38A38>G2D4 C38<B38A38>G2D4 C38<B38>C38<A2D34D38";
  tune += "E4.E>C<BAG G38A38B38A34E38F#4D34D38 E4.E>C<BAG>D.<A16A2D34D38";
  tune += "E4.E>C<BAG G38A38B38A34E38F#4>D34D38 G.F16E-.D16C.<B-16A.G16>D2.<D38D38D38";
  tune += "G2>D2 C38<B38A38>G2D4 C38<B38A38>G2D4 C38<B38>C38<A2D34D38";
  tune += "G2>D2 C38<B38A38>G2D4 G38F38E-38B-2A4 GR<G38G38G38G4R4";
  playMelody(tune);
}

void playTuneFireStone() {
  playMelody("$T113 L8 O5  D16F#ED");
  String tune = "D.<B.B>D<B>E16F#8. ER16E.<A.A16>A16<A16>F#E  D.<B.B>D<B>E16F#8.  ER16E.DR16D16F#ED  D.<B.B>D<B>E16F#8. ER16E.<A.A16>A16<A16>F#E  D.D.DDDE16F#8. D.D.D16A16D16F#ED16E16D";
  for (int i = 0; i < 2; i++) {
    playMelody(tune);
    if (amp_buttonPressed)
      break;
  }
}

///
/// local functions
///

String fillSpace(int value, int valLength) {
  String tmpStr = String(value);
  while (tmpStr.length() < valLength)
    tmpStr = " " + tmpStr;
  return String(tmpStr);
}

void setupMatrix() {
  matrix.setIntensity(ledBrightness);
  for (int i = 0; i < numberOfHorizontalDisplays; i++)
    matrix.setRotation(i, 1); // Set correct rotation
  matrix.fillScreen(LOW);
  matrix.write();
}

void displayMessage(String message) {
  int y = ledYOffset + (matrix.height() - 8) / 2; // center the text vertically
  int x, letter, reSwitch = 0;

  for (int i = 0 ; i < ledWidth * message.length() + matrix.width() - ledSpacer; i++) {
    letter = i / ledWidth;
    x = (matrix.width() - 1) - i % ledWidth;
    
    while (x + ledWidth - ledSpacer >= 0 && letter >= 0) {
      if (letter < message.length()) {
        matrix.drawChar(x, y, message[letter], HIGH, LOW, 1);
      }
      letter--;
      x -= ledWidth;
    }
    matrix.write();
    
    delay(ledScrollDelayTime / 2);

    reSwitch = readRotEncSwitch();
    if (reSwitch > 0) {
      matrix.fillScreen(LOW);
      matrix.write();
      amp_buttonPressed = reSwitch;
      return;
    }
  }
}

bool delayAndCheckEnc(unsigned int delayTime) {  
  unsigned long startTime = millis();
  unsigned long checkTime = millis();
  int reSwitch = 0;

  while ((millis() - startTime) < delayTime) {
    if (millis() - checkTime > 50) {
      checkTime = millis();
      reSwitch = readRotEncSwitch();
      if (reSwitch > 0) {
        amp_buttonPressed = reSwitch;
        return true;
      }
    } else
      delay(1);
  }
  return false;
}

int readRotEncSwitch() {                                                        // returns 1 when pressed or 2 when pressed for 1 second
  static int reLastSwitchState = 0;
  int reSwitchState = digitalRead(RE_SWITCH);

  if (reSwitchState == LOW && reLastSwitchState == HIGH) {
    unsigned long startTime = millis();
    reLastSwitchState = reSwitchState;
    
    while (digitalRead(RE_SWITCH) == LOW) {                                     // wait until the button is released or 1 second has passed
      if (millis() - startTime > 1000)
        return 2;
    }
    
    return 1;
  }
  
  reLastSwitchState = reSwitchState;
  return false;
}

//////////////////////////////////////////////////////////////////////
/// amp functions
//////////////////////////////////////////////////////////////////////

void playMelody (String melody) {
  static byte tempo = 120;                          // default to 120 BPM
  static byte octave = 4;                           // default octave
  static unsigned int defaultNoteLength = 4;        // default to quarter note
  static unsigned int noteLengthType = 0;           // 0: normal 7/8, 1: legato 1/1, 2: staccato 3/4, 9: mute (default 0)
  static int transpose = 0;                         // default to 0

  byte notes[] = { 9, 11, 0, 2, 4, 5, 7 };

  byte curFunc = 0;
  byte curChar = 0;
  unsigned int curVal = 0;
  bool skip = false;

  unsigned int curNoteLength = 0;
  int sharpFlat = 0;
  bool dotted = false;
  int octaveOffset = 0;

  int curNote = 0;
  double curNoteLengthMS = 0;  
  String curNoteName;

  #ifdef ESP32                                      // ESP32
    ledcAttachPin(AMP_SPEAKER_PIN, 0);
  #endif

  if (amp_lyricsMode > 0) {   // @EB-todo
    amp_displayLyrics("$");                         // initialize lyrics function
  }
  
  for (unsigned int i = 0; i < melody.length(); i++) {
    if (amp_buttonPressed > 0)
      break;
    
    curNoteLength = 0;
    curVal = 0;
    skip = true;
    octaveOffset = 0;
    
    curChar = melody[i];

    if (curChar >= 97 && curChar <= 122) {          // convert lowercase to uppercase
      curChar -= 32;
    }

    switch (curChar) {
      case 32:                                      // skip spaces
        break;

      case '$':                                     // restore default settings
        tempo = 120;
        octave = 4;
        defaultNoteLength = 4;
        noteLengthType = 0;
        transpose = 0;
        
        #ifdef DEBUG_PLAY_CMD 
          DEBUGPRINTLN("Reset settings"); 
        #endif
        break;
       
      case '<':                                     // < sign, octave lower
        octave--;
        if (octave < 1) octave = 1;
        
        #ifdef DEBUG_PLAY_CMD 
          DEBUGPRINTLN("Octave set to " + (String) octave); 
        #endif
        break;
        
      case '>':                                     // > sign, octave higher
        octave++;
        if (octave > 8) octave = 8;
        
        #ifdef DEBUG_PLAY_CMD 
          DEBUGPRINTLN("Octave set to " + (String) octave); 
        #endif
        break;

      case '[':                                     // [ transpose down
        transpose--;
        if (transpose < -12) transpose = -12;
        
        #ifdef DEBUG_PLAY_CMD 
          DEBUGPRINTLN("Transpose down " + (String) transpose); 
        #endif
        break;

      case ']':                                     // ] transpose up
        transpose++;
        if (transpose > 12) transpose = 12;
        
        #ifdef DEBUG_PLAY_CMD 
          DEBUGPRINTLN("Transpose up " + (String) transpose); 
        #endif
        break;

      case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'R': case '#': case '-':
                                                    // A B C D E F G notes and R rest and # sharp and - flat
        skip = false;

        if (i + 1 < melody.length()) {
          if (amp_isNum(melody[i + 1])) {
            skip = true;
          }
          if (melody[i + 1] == '#') {               // sharp
            sharpFlat = 1;
            skip = true;
          } else if (melody[i + 1] == '-') {        // flat
            sharpFlat = -1;
            skip = true;
          } else if (melody[i + 1] == '.') {        // dotted
            dotted = true;
            i++;
          }
        }
        
        if (curChar != '#' && curChar != '-') {
          curFunc = curChar;
        }
        break;

      case 'M':                                     // music length type
        if (i + 1 < melody.length()) {
          switch  (melody[i + 1]) {
            case 'N': case 'n':
              noteLengthType = 0;                   // normal 7/8
              i++;
              break;            
            case 'L': case 'l':
              noteLengthType = 1;                   // legato 1/1
              i++;
              break;            
            case 'S': case 's':
              noteLengthType = 2;                   // staccato 3/4
              i++;
              break;
            case 'U': case 'u':    
              noteLengthType = 9;                   // mute
              i++;
              break;
          }
        }       
        
      case 'L':                                     // L default note/rest length
      case 'O':                                     // O octave
      case 'T':                                     // T tempo
        curFunc = curChar;
        break;

      default:
        if (amp_isNum(curChar)) {
          curVal = curChar - 48;

          for (int j = 0; j <= 2; j++) {             // look ahead to get the next 2 numbers or dot
            if (i + 1 < melody.length()) {
              if (amp_isNum(melody[i + 1])) {
                curVal = curVal * 10 + melody[i + 1] - 48;                  
                i++;                
              } else if (melody[i + 1] == '.') {
                dotted = true;
                break;
              } else {
                break;
              }
            }
          }
          
          curNoteLength= curVal;
          skip = false;
        } 
    }

    if (curFunc > 0 && !skip) {
      #ifdef DEBUG_PLAY_CMD
        DEBUGPRINT("Command " + (String) curFunc + " value " + fillSpace(curVal, 3));
      #endif

      if (curFunc >= 65 and curFunc <= 71 || curFunc == 82) {
        if (!curNoteLength) {
          curNoteLength = defaultNoteLength;
        }
        
        if (dotted) {
          curNoteLength = curNoteLength * 1.5;
        }
        
        curNoteLengthMS = amp_noteLengthToMS(curNoteLength, tempo);
        
        if (curFunc == 82) {
          curNote = 0;
          curNoteName = "";
          
          #ifdef DEBUG_PLAY_CMD
            DEBUGPRINT(" Pause length "+ fillSpace(curNoteLength, 3) + " " + fillSpace(curNoteLengthMS, 4) + " ms");
          #endif
          
        } else {
          curNote = notes[curFunc - 65];
          curNote = curNote + transpose + sharpFlat;

          while (curNote < 0) {
            curNote += 12;
            if (octave > 1) octaveOffset -= 1;
          }
          while (curNote > 11) {
            curNote -= 12;
            if (octave < 8) octaveOffset += 1;
          }

          curNoteName = amp_noteName(curNote, sharpFlat);
          #ifdef DEBUG_PLAY_CMD
            DEBUGPRINT(" Transpose " + (String) transpose + " Octave " + (String) (octave + octaveOffset) + " Note " + curNoteName);
          #endif

          curNote = ((octave + octaveOffset) * 12) + curNote;          
          #ifdef DEBUG_PLAY_CMD
            DEBUGPRINT(" Notenumber " + fillSpace(curNote, 3) + " Frequency " + fillSpace(pt_notes[curNote], 4) + " length "+ fillSpace(curNoteLength, 3) + " " + fillSpace(curNoteLengthMS, 4) + " ms");
          #endif

          curNoteName += (String) (octave + octaveOffset);  // @EB-todo
        }
        
        switch (noteLengthType) {
          case 0: // normal 7/8
            amp_playNote(curNote, (curNoteLengthMS / 8 * 7), curNoteName);
            amp_playMelodyLyricsOff();
            amp_playNote(0, (curNoteLengthMS / 8 * 1), "");
            break;
            
          case 1: // legato 1/1
            amp_playNote(curNote, curNoteLengthMS, curNoteName);
            amp_playMelodyLyricsOff();
            break;
            
          case 2: // staccato
            amp_playNote(curNote, (curNoteLengthMS / 4 * 3), curNoteName);
            amp_playMelodyLyricsOff();
            amp_playNote(0, (curNoteLengthMS / 4 * 1), "");
            break;
            
          case 9: // mute
            amp_playNote(0, curNoteLengthMS, curNoteName);
            amp_playMelodyLyricsOff();
            #ifdef DEBUG_PLAY_CMD
              DEBUGPRINT(" MUTE");
            #endif
            break;
        }

        dotted = false;
        curNoteLength = 0;
        sharpFlat = 0;
               
      } else {
        switch (curFunc) {          
          case 'L':
            switch (curVal) {
              case 1: case 2: case 3: case 4: case 6: case 8: case 12: case 16: case 24: case 32: case 48: case 64:
              case 34: case 38: case 316: case 332:
                defaultNoteLength = curVal;
                break;
            }
            break;
            
          case 'O': 
            octave = constrain(curVal, 1, 8); 
            break;
          
          case 'T': 
            tempo = constrain(curVal, 32, 255);
            #ifdef DEBUG_PLAY_CMD
              DEBUGPRINTLN(" Tempo " + (String) tempo);
            #endif
            break;
        }
      }
      
      curFunc = 0;      
      curNoteName = "";
    }

    if (!skip) {
      #ifdef DEBUG_PLAY_CMD
        DEBUGPRINTLN("");
      #endif
    }
  }

  amp_playMelodyEnd();
}

bool amp_isNum(byte value) {
  if (value >= 48 && value <= 57)                   // 0 to 9
    return true;
  else
    return false;
}

String amp_noteName(byte note, int sharpFlat) {
  switch (note) {
    case  0:  return "C ";  break;
    case  1:  return (sharpFlat == -1 ? "D-" : "C#");  break;
    case  2:  return "D ";  break;
    case  3:  return (sharpFlat == -1 ? "E-" : "D#");  break;
    case  4:  return "E ";  break;
    case  5:  return "F ";  break;
    case  6:  return (sharpFlat == -1 ? "G-" : "F#");  break;
    case  7:  return "G ";  break;
    case  8:  return (sharpFlat == -1 ? "A-" : "G#");  break;
    case  9:  return "A ";  break;
    case 10:  return (sharpFlat == -1 ? "B-" : "A#");  break;
    case 11:  return "B ";  break;
  }
}

double amp_noteLengthToMS(unsigned int curNoteLength, byte tempo) {
  double timeBase = 60000 / tempo;        // @EB-todo default 4/4
    
  switch (curNoteLength) {
    case  1:  return (timeBase * 4);                // whole note
    case  2:  return (timeBase * 2);                // half
    case  4:  return (timeBase);                    // quarter
    case  8:  return (timeBase / 2);                // 8th
    case 16:  return (timeBase / 4);                // 16th
    case 32:  return (timeBase / 8);                // 32nd
    case 64:  return (timeBase / 16);               // 64th

    case  3:  return (timeBase * 3);                // dotted half
    case  6:  return (timeBase * 3 / 2);            // dotted 4th
    case 12:  return (timeBase * 3 / 4);            // dotted 8th
    case 24:  return (timeBase * 3 / 8);            // dotted 16th
    case 48:  return (timeBase * 3 / 16);           // dotted 32th

    case 34:  return (timeBase / 1.5);              // triplet quarter
    case 38:  return (timeBase / 3);                // triplet 8th
    case 316: return (timeBase / 6);                // triplet 16th
    case 332: return (timeBase / 12);               // triplet 32th
  }
}

void amp_playNote(byte note, int duration, String curNoteName) {

  if (amp_lyricsMode > 0) {               // @EB-todo
    amp_displayLyrics(curNoteName);
  }
  
  #ifndef DEBUG_SILENCE 
    if (note >= 0 && note <= 107) {
      if (pt_notes[note]) {
        #ifdef ESP32
          ledcWriteTone(0, pt_notes[note]);
        #else
          tone(AMP_SPEAKER_PIN, pt_notes[note]);
        #endif
      } else {
        #ifdef ESP32
          ledcWriteTone(0, 0);
          ledcWrite(0, LOW);
        #else
          noTone(AMP_SPEAKER_PIN);
        #endif
      }
    }
  #endif
  
//  delay(duration);

  delayAndCheckEnc(duration);             // @EB-todo
}

void amp_playMelodyLyricsOff() {
  if (amp_lyricsMode > 0) { // @EB-todo
    matrix.fillScreen(LOW);
    matrix.write();
  } 
}

void amp_playMelodyEnd() {
  amp_playNote(0, 0, "");
  pinMode(AMP_SPEAKER_PIN, INPUT);                 // make sure the buzzer is silent ;-)
}

//////////////////////////////////////////////////////////////////////
/// display lyrics function
//////////////////////////////////////////////////////////////////////

void amp_displayLyrics(String noteName) {
  static unsigned int wordPos = 0;
  String lyricWord = "";
  unsigned int lyricsLength = amp_lyrics.length();

  if (noteName.length() == 0) {     // skip pauses
    return;  
  }

  if (noteName == "$") {            // init
    wordPos = 0;
    return;
  }
  
  if (amp_lyricsMode == 3) {
    lyricWord = "la";
  } else if (amp_lyricsMode == 1 || amp_lyricsMode == 2 && lyricsLength == 0) {
    lyricWord = noteName;
  } else {
    if (wordPos >= lyricsLength)
      wordPos = 0;
      
    while (wordPos < lyricsLength) {
      if (amp_lyrics[wordPos] == 32) {    // break at spaces
        wordPos++;
        break;
      }
      
      lyricWord += amp_lyrics[wordPos];
      
      if (amp_lyrics[wordPos] == '-') {   // break at syllables after adding '-'
        wordPos++;
        break;
      }
      wordPos++;
    }
  }

  DEBUGPRINT(" lyric: " + lyricWord);

  int wordLength = lyricWord.length();
//  if (lyricWord[wordLength - 1] == 32)
//    wordLength--;

  int x = (matrix.width() - (wordLength * (ledWidth + ledSpacer) - 1)) / 2;
  int y = ledYOffset + (matrix.height() - 8) / 2; // center the text vertically

  for (int i = 0; i < wordLength; i++) {
    matrix.drawChar(x, y, lyricWord[i], HIGH, LOW, 1);
    x = x + ledWidth + ledSpacer;
  }
  matrix.write();
}

//////////////////////////////////////////////////////////////////////

AMP-20190610-share.ino

Arduino
 /*
***************************************************************************
  Arduino Melody Player by ericBcreator
  loosely based on the old qbasic PLAY command
  also works on ESP boards
***************************************************************************
  version 0.9 - last update 20190610 by ericBcreator
***************************************************************************
  Syntax:  
    playMelody(string);

    commands:
    CDEFGABn  notes, n = note length: 1, 2, 4, 8, 16, 32, 64,
              2, 4, 8, 16, 32 dotted (or 3, 6, 12, 24, 48),
              4, 8, 16, 32 triplet 34, 38, 316, 332
              defaults to Ln if n is omitted
    Rn        rest, n = note length. defaults to Ln if n is omitted

    Tn        tempo n = 32-255 (default is 120 BPM, 4/4 time base)
    On        octave n = 1-8 (default is 4)
    Ln        n = default note length (default is 4, quarter note)
    MN        music normal:      7/8 note length duration (default)
    ML        or music legato:   full length duration
    MS        or music staccato: 3/4 note length duration
    MU        mute mode

    <         octave down (range 1-8)
    >         octave up (range 1-8)
    [         transpose down a step (range -12 to 12)
    ]         transpose up a step (range -12 to 12)
    #         sharp (place after note, D#n)
    -         flat (place after note, D-n)
    .         dotted note (note length will be increased with 50%)
    
    $         reset the default settings. recommended at the start
              of a song when playing 
              
              note: the Tn, On, Ln, M? and [ ] settingsare static 
                    so will remain in memory with the next call 
                    to playtune. It is recommended to reset these 
                    at the beginning of a new melody with the $
                    command

    example the Big Ben tune: 
              playMelody("$T120 L4 O5 ECD<GR2 G>DEC R2");
    or:       String tune = "$T120 L4 O5 ECD<GR2 G>DEC R2";
              playMelody(tune);
***************************************************************************
  This code is free for personal use, not for commercial purposes.
  Please leave this header intact.

  contact: ericBcreator@gmail.com
***************************************************************************
*/

//
// includes
//

#include "amp_notes.h"                                  // include the notes
#include <Adafruit_GFX.h>                               // Needed for the matrix          https://github.com/adafruit/Adafruit-GFX-Library
#include <Max72xxPanel.h>                               // Matrix library                 https://github.com/markruys/arduino-Max72xxPanel
#include <RotaryEncoder.h>                              // Rotary encoder library         https://github.com/mathertel/RotaryEncoder
#include <SPI.h>

//
// definitions
//

#define ESP                                             // @EB-setup uncomment when using an ESP

//#define DEBUG                                           // @EB-setup enable debug messages
//#define DEBUG_PLAY_CMD                                  // @EB-setup print play commands
//#define DEBUG_SILENCE

#ifdef DEBUG
  #define DEBUGPRINT(x)   Serial.print(x)
  #define DEBUGPRINTLN(x) Serial.println(x)
#else
  #define DEBUGPRINT(x)
  #define DEBUGPRINTLN(x)
#endif

#ifdef ESP                                                // ESP pins
  #define RE_PINA    D1                                   // @EB-setup rotary encoder: pinA / clock pin number    // VCC -> 5V  GND -> GND
  #define RE_PINB    D2                                   // @EB-setup rotary encoder: pinB / data pin number
  #define RE_SWITCH  D3                                   // @EB-setup rotary encoder: switch / pin number
  #define PIN_CS     D4                                   // @EB-setup CS pin LED Matrix          // VCC -> 5V  GND -> GND  DIN -> D7 (MOSI) CS  -> D4  CLK -> D5 (SCK)
  #define AMP_SPEAKER_PIN D8                              // @EB-setup the pin of the buzzer of speaker
  
#else                                                    // arduino pins  @EB-todo
  #define RE_PINA    1                                   // @EB-setup rotary encoder: pinA / clock pin number    // VCC -> 5V  GND -> GND
  #define RE_PINB    2                                   // @EB-setup rotary encoder: pinB / data pin number
  #define RE_SWITCH  3                                   // @EB-setup rotary encoder: switch / pin number
  #define PIN_CS     10                                  // @EB-setup CS pin LED Matrix          // VCC -> 5V  GND -> GND  DIN -> 11 (MOSI) CS  -> 10  CLK -> 13 (SCK)
  #define AMP_SPEAKER_PIN 8                              // @EB-setup the pin of the buzzer of speaker
#endif


//
// led matrix setup
//
    
int numberOfHorizontalDisplays = 8;                     // @EB-setup
int numberOfVerticalDisplays = 1;                       // @EB-setup
Max72xxPanel matrix = Max72xxPanel(PIN_CS, numberOfHorizontalDisplays, numberOfVerticalDisplays);

int ledBrightness = 3;                                  // @EB-setup led brightness 0-15
int ledScrollDelayTime = 30;                            // @EB-setup delay time for scrolling messages

int ledSpacer = 1;
int ledWidth = 5 + ledSpacer;                           // The font width is 5 pixels
byte ledYOffset = 0;

//
// rotary encoder setup
//

RotaryEncoder RE_encoder(RE_PINA, RE_PINB);             // initialize the rotary encoder

//
// set variables
//

byte amp_lyrics = 0;                                    // @EB-setup 0: no lyrics, 1: notes, 2: lyrics (or notes if no lyrics are set), 3: lalala
byte amp_buttonPressed = 0;

byte melodyNum = 0;
byte numOfMelodies = 7;
bool musicOff = false;
String lyrics;
String melody;

//
// setup
//

void setup() {
  #ifdef DEBUG
    Serial.begin(115200);
    Serial.println();
  #endif
  
  setupMatrix();
  pinMode(RE_SWITCH, INPUT_PULLUP);

  playMelody("$T120 L16 O4 CEG>C8");

  displayMessage("AMP: Arduino Music Player");
  amp_lyrics = 2;
  melodyNum = 0;
}

//
// main loop
//

void loop() {
  if (!musicOff) {
    DEBUGPRINTLN("Melody " + (String) melodyNum);

    switch (melodyNum) {
      case 0: 
        displayMessage("Note scale");
        playToneScale(4);
        break;
      
      case 1: 
        displayMessage("Firestone");
        playTuneFireStone();  
        break;

      case 2:
        displayMessage("Sweet but a psycho");
        lyrics = "$Oh, she's sweet but a psy-cho ";
        lyrics += "A lit-tle bit psy-cho ";
        lyrics += "At night she's screa-min' ";
        lyrics += "I'mma ma ma out my mi-nd ";
        lyrics += "Oh, she's hot but a psy-cho ";
        lyrics += "So left but she's right though ";
        lyrics += "At night she's screa-min' ";
        lyrics += "I'mma ma ma out my mi-nd";
        playMelody("$T133 L8 O4 G4G4GF#GF#4 D4DDEF#A4 E4EF#E#F#E <B>F#F#F#4EED  G4G4GF#GF#4 D4DDEF#A4 E4EF#E#F#E <B>F#F#F#4EED");
        break;
              
      case 3:
        displayMessage("Shotgun");
        lyrics = "$I'll be ri-ding ";
        lyrics += "shot-gun un-der-neith the hot sun fee-ling like I'm some-one I'll be ri-ding ";
        lyrics += "shot-gun un-der-neith the hot sun fee-ling like I'm some-one";
        
        melody = "GAGD FRFRGAGD FRFRGAGD FRFRR2 R2";
        playMelody("$T116 L8 O4");
        playMelody(melody);
        playMelody(melody);
        break;

      case 4: 
        displayMessage("Dragostea din tei");
        playMelody("$T130 L8 O4 C<B>CA4. R4C<B>CG4. R4C<B>CG4. R4C<B>C>C R<AR4  C<B>CA4. R4C<B>CG4. R4C<B>CG4. R4C<B>C>C R<AR4");
        break;

      case 5: 
        displayMessage("Star wars");
        playTuneStarWars();   
        break;

      case 6: 
        displayMessage("Big Ben");
        playMelody("$T120 L4 O4 ML ECD<GR2 G>DECR2");
        break;

      case 7: 
        displayMessage("Timing test 120 BPM");
        playTimingTest();     
        break;
    }
  }

  lyrics = "";
  
  switch (amp_buttonPressed) {
    case 1:
      melodyNum++;
      if (melodyNum> numOfMelodies)
        melodyNum = 0;      
      delay(500);
      amp_buttonPressed = 0;
      break;
      
    case 2:
      musicOff = !musicOff;
      DEBUGPRINTLN(musicOff == false ? "Music on" : "Music off");
      amp_buttonPressed = 0;
      break;
      
    default:
      for (int i = 0; i < 5; i++) {
        delayAndCheckEnc(1000);
        if (amp_buttonPressed)
          break;
          
      }
      melodyNum++;
      melodyNum = melodyNum % numOfMelodies;
  }

}

///
/// local functions
///

void playToneScale(byte oneOctave) {
  int startOctave = 1;
  int endOctave = 8;

  if (oneOctave) {
    startOctave = oneOctave;
    endOctave = oneOctave;
  }

  playMelody("T120 L8 O" + (String) startOctave);
  
  for (int i = startOctave; i <= endOctave; i++) {
    playMelody("cc#dd#eff#gg#aa#b >");
    if (amp_buttonPressed)
      break;
  }

  if (oneOctave)
    playMelody("c");
}

void playTimingTest() {
  playMelody("$T120 L8 O4");
  while (1) {
    playMelody("GRCRCRCR");
    if (amp_buttonPressed)
      break;
  }
}

void playTuneStarWars() {
  String tune;
  tune = "$T105 L8 O4 D38D38D38";
  tune += "G2>D2 C38<B38A38>G2D4 C38<B38A38>G2D4 C38<B38>C38<A2D34D38";
  tune += "G2>D2 C38<B38A38>G2D4 C38<B38A38>G2D4 C38<B38>C38<A2D34D38";
  tune += "E4.E>C<BAG G38A38B38A34E38F#4D34D38 E4.E>C<BAG>D.<A16A2D34D38";
  tune += "E4.E>C<BAG G38A38B38A34E38F#4>D34D38 G.F16E-.D16C.<B-16A.G16>D2.<D38D38D38";
  tune += "G2>D2 C38<B38A38>G2D4 C38<B38A38>G2D4 C38<B38>C38<A2D34D38";
  tune += "G2>D2 C38<B38A38>G2D4 G38F38E-38B-2A4 GR<G38G38G38G4R4";
  playMelody(tune);
}

void playTuneFireStone() {
  playMelody("$T113 L8 O5  D16F#ED");
  String tune = "T113 L8 O5  D.<B.B>D<B>E16F#8. ER16E.<A.A16>A16<A16>F#E  D.<B.B>D<B>E16F#8.  ER16E.DR16D16F#ED  D.<B.B>D<B>E16F#8. ER16E.<A.A16>A16<A16>F#E  D.D.DDDE16F#8. D.D.D16A16D16F#ED16E16D";
  for (int i = 0; i < 2; i++) {
    playMelody(tune);
    if (amp_buttonPressed)
      break;
  }
}

///////////

String fillSpace(int value, int valLength) {
  String tmpStr = String(value);
  while (tmpStr.length() < valLength)
    tmpStr = " " + tmpStr;
  return String(tmpStr);
}

void setupMatrix() {
  matrix.setIntensity(ledBrightness);
  for (int i = 0; i < numberOfHorizontalDisplays; i++)
    matrix.setRotation(i, 1); // Set correct rotation
  matrix.fillScreen(LOW);
  matrix.write();
}

void displayMessage(String message) {
  for (int i = 0 ; i < ledWidth * message.length() + matrix.width() - ledSpacer; i++) {
    int letter = i / ledWidth;
    int x = (matrix.width() - 1) - i % ledWidth;
    int y = ledYOffset + (matrix.height() - 8) / 2; // center the text vertically
    
    while (x + ledWidth - ledSpacer >= 0 && letter >= 0) {
      if (letter < message.length()) {
        matrix.drawChar(x, y, message[letter], HIGH, LOW, 1);
      }
      letter--;
      x -= ledWidth;
    }
    matrix.write();
    
    delay(ledScrollDelayTime / 2);
    
    if (readRotEncSwitch()) {
      matrix.fillScreen(LOW);
      matrix.write();
      return;
    }
  }
}

void displayLyrics(String noteName) {
  static unsigned int wordPos = 0;
  String lyricWord = "";
  unsigned int lyricsLength = lyrics.length();

  if (noteName.length() == 0) {     // skip pauses
    return;  
  }
  
  if (amp_lyrics == 3) {
    lyricWord = "la";
  } else if (amp_lyrics == 1 || amp_lyrics == 2 && lyricsLength == 0) {
    lyricWord = noteName;
  } else {
    if (wordPos >= lyricsLength)
      wordPos = 0;
      
    if (lyrics[wordPos] == '$')
      wordPos = 1;
    else
      wordPos++;
  
    while (lyrics[wordPos] != 32 && wordPos < lyricsLength) {
      lyricWord += lyrics[wordPos];
      if (lyrics[wordPos] == '-')   // break at syllables
        break;
      wordPos++;
    }
  }

  DEBUGPRINT(" lyric: " + lyricWord);

  int wordLength = lyricWord.length();
//  if (lyricWord[wordLength - 1] == 32)
//    wordLength--;

  int x = (matrix.width() - (wordLength * (ledWidth + ledSpacer) - 1)) / 2;
  int y = ledYOffset + (matrix.height() - 8) / 2; // center the text vertically

  for (int i = 0; i < wordLength; i++) {
    matrix.drawChar(x, y, lyricWord[i], HIGH, LOW, 1);
    x = x + ledWidth + ledSpacer;
  }
  matrix.write();
}

bool delayAndCheckEnc(unsigned int delayTime) {  
  unsigned long startTime = millis();
  unsigned long checkTime = millis();
  int reSwitch = 0;

  while ((millis() - startTime) < delayTime) {
    if (millis() - checkTime > 50) {
      checkTime = millis();
      reSwitch = readRotEncSwitch();
      if (reSwitch > 0) {
        amp_buttonPressed = reSwitch;
        return true;
      }
    } else
      delay(1);
  }
  return false;
}

int readRotEncSwitch() {                                                        // returns 1 when pressed or 2 when pressed for 1 second
  static int reLastSwitchState = 0;
  int reSwitchState = digitalRead(RE_SWITCH);

  if (reSwitchState == LOW && reLastSwitchState == HIGH) {
    unsigned long startTime = millis();
    reLastSwitchState = reSwitchState;
    
    while (digitalRead(RE_SWITCH) == LOW) {                                     // wait until the button is released or 1 second has passed
      if (millis() - startTime > 1000)
        return 2;
    }
    
    return 1;
  }
  
  reLastSwitchState = reSwitchState;
  return false;
}

//////////////////////////////////////////////////////////////////////


#ifdef DEBUG_NO_BUZZER
  void playBuzzer(int numOfBuzz) {  
    for (int i = 0; i < numOfBuzz; i++) {
      delayAndCheckEnc(100);
    }
  }
#else
  void playBuzzer(int numOfBuzz) {  
    #ifdef ESP32                                    // ESP32
      ledcAttachPin(AMP_SPEAKER_PIN, 0);
    #endif
    
    if (numOfBuzz < 11) {
      for (int i = 0; i < numOfBuzz; i++) {
        amp_playNote(pt_notes[83], 50, "");
        amp_playNote(0, 50, "");
      }
    } else {
      switch (numOfBuzz - 11) {                     // @EB-todo
        case 1: playMelody("T120 L4 O5 ML ECD<GR2 G>DEC R2");                                                                                // bigben
      }
    }
    pinMode(AMP_SPEAKER_PIN, INPUT);                     // make sure the buzzer is silent ;-)
  }
#endif


//////////////////////////////////////////////////////////////////////
/// melody player functions
//////////////////////////////////////////////////////////////////////

void playMelody (String melody) {
  static byte tempo = 120;                          // default to 120 BPM
  static byte octave = 4;                           // default octave
  static unsigned int defaultNoteLength = 4;        // default to quarter note
  static unsigned int noteLengthType = 0;           // 0: normal 7/8, 1: legato 1/1, 2: staccato 3/4, 9: mute (default 0)
  static int transpose = 0;                         // default to 0

  byte notes[] = { 0, 2, 4, 5, 7, 9, 11 };

  byte curFunc = 0;
  byte curChar = 0;
  unsigned int curVal = 0;
  bool skip = false;

  unsigned int curNoteLength = 0;
  byte sharpFlat = 0;
  bool dotted = false;

  byte curNote = 0;  
  double curNoteLengthMS = 0;  
  String curNoteName;

  #ifdef ESP32                                      // ESP32
    ledcAttachPin(AMP_SPEAKER_PIN, 0);
  #endif

  for (unsigned int i = 0; i < melody.length(); i++) {
    if (amp_buttonPressed > 0)
      break;
    
    curNoteLength = 0;
    curVal = 0;
    skip = true;
    
    curChar = melody[i];

    if (curChar >= 97 && curChar <= 122) {          // convert lowercase to uppercase
      curChar -= 32;
    }

    switch (curChar) {
      case 32:                                      // skip spaces
        break;

      case '$':                                     // restore default settings
        tempo = 120;
        octave = 4;
        defaultNoteLength = 4;
        noteLengthType = 0;
        transpose = 0;
        break;
       
      case '<':                                     // < sign, octave lower
        octave--;
        if (octave < 1) octave = 1;
        break;
        
      case '>':                                     // > sign, octave higher
        octave++;
        if (octave > 8) octave = 8;
        break;

      case '[':                                     // [ transpose down
        transpose--;
        if (transpose < -12) transpose = -12;
        break;

      case ']':                                     // ] transpose up
        transpose++;
        if (transpose > 12) transpose = 12;
        break;

      case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'R': case '#': case '-':
                                                    // A B C D E F G notes and R rest and # sharp and - flat
        skip = false;

        if (i + 1 < melody.length()) {
          if (amp_isNum(melody[i + 1])) {
            skip = true;
          }
          if (melody[i + 1] == '#') {               // sharp
            sharpFlat = 1;
            skip = true;
          } else if (melody[i + 1] == '-') {        // flat
            sharpFlat = -1;
            skip = true;
          } else if (melody[i + 1] == '.') {        // dotted
            dotted = true;
            i++;
          }
        }
        
        if (curChar != '#' && curChar != '-') {
          curFunc = curChar;
        }
        break;

      case 'M':                                     // music length type
        if (i + 1 < melody.length()) {
          switch  (melody[i + 1]) {
            case 'N': case 'n':
              noteLengthType = 0;                   // normal 7/8
              i++;
              break;            
            case 'L': case 'l':
              noteLengthType = 1;                   // legato 1/1
              i++;
              break;            
            case 'S': case 's':
              noteLengthType = 2;                   // staccato 3/4
              i++;
              break;
            case 'U': case 'u':    
              noteLengthType = 9;                   // mute
              i++;
              break;
          }
        }       
        
      case 'L':                                     // L default note/rest length
      case 'O':                                     // O octave
      case 'T':                                     // T tempo
        curFunc = curChar;
        break;

      default:
        if (amp_isNum(curChar)) {
          curVal = curChar - 48;

          for (int j = 0; j <= 2; j++) {             // look ahead to get the next 2 numbers or dot
            if (i + 1 < melody.length()) {
              if (amp_isNum(melody[i + 1])) {
                curVal = curVal * 10 + melody[i + 1] - 48;                  
                i++;                
              } else if (melody[i + 1] == '.') {
                dotted = true;
                break;
              } else {
                break;
              }
            }
          }
          curNoteLength= curVal;
          skip = false;
        } 
    }

    if (curFunc > 0 && !skip) {
      #ifdef DEBUG_PLAY_CMD
        DEBUGPRINT("Command " + (String) curFunc + " value " + fillSpace(curVal, 3));
      #endif

      if (curFunc >= 65 and curFunc <= 71 || curFunc == 82) {
        if (!curNoteLength) {
          curNoteLength = defaultNoteLength;
        }
        
        if (dotted) {
          curNoteLength = curNoteLength * 1.5;
        }
        
        curNoteLengthMS = amp_noteLengthToMS(curNoteLength, tempo);
        
        if (curFunc == 82) {
          curNote = 0;
          curNoteName = "";
          
          #ifdef DEBUG_PLAY_CMD
            DEBUGPRINT(" Pause length "+ fillSpace(curNoteLength, 3) + " " + fillSpace(curNoteLengthMS, 4) + " ms");
          #endif
          
        } else {
          if (curFunc <= 66) {
            curNote = notes[curFunc - 60];
          } else {
            curNote = notes[curFunc - 67];
          }  

          curNote = curNote + transpose + sharpFlat;
          curNoteName = amp_noteName(curNote);

          #ifdef DEBUG_PLAY_CMD
            DEBUGPRINT(" Octave " + (String) octave + " Note " + curNoteName);
          #endif

          curNote = (octave * 12) + curNote;
          #ifdef DEBUG_PLAY_CMD
            DEBUGPRINT(" Notenumber " + fillSpace(curNote, 3) + " Frequency " + fillSpace(pt_notes[curNote], 4) + " length "+ fillSpace(curNoteLength, 3) + " " + fillSpace(curNoteLengthMS, 4) + " ms");
          #endif
        }
        
        switch (noteLengthType) {
          case 0: // normal 7/8
            amp_playNote(curNote, (curNoteLengthMS / 8 * 7), curNoteName);
            amp_playNote(0, (curNoteLengthMS / 8 * 1), "");
            break;
            
          case 1: // legato 1/1
            amp_playNote(curNote, curNoteLengthMS, curNoteName);
            break;
            
          case 2: // staccato
            amp_playNote(curNote, (curNoteLengthMS / 4 * 3), curNoteName);
            amp_playNote(0, (curNoteLengthMS / 4 * 1), "");
            break;
            
          case 9: // mute
            amp_playNote(0, curNoteLengthMS, curNoteName);
            #ifdef DEBUG_PLAY_CMD
              DEBUGPRINT(" MUTE");
            #endif
            break;
        }

        dotted = false;
        curNoteLength = 0;
        sharpFlat = 0;
               
      } else {
        switch (curFunc) {          
          case 'L':
            switch (curVal) {
              case 1: case 2: case 3: case 4: case 6: case 8: case 12: case 16: case 24: case 32: case 48: case 64:
              case 34: case 38: case 316: case 332:
                defaultNoteLength = curVal;
                break;
            }
            break;
            
          case 'O': octave = constrain(curVal, 1, 8); break;
          case 'T': 
            tempo = constrain(curVal, 32, 255);
            #ifdef DEBUG_PLAY_CMD
              DEBUGPRINTLN(" Tempo " + (String) tempo);
            #endif
            break;
        }
      }

      if (amp_lyrics > 0) {
        matrix.fillScreen(LOW);
        matrix.write();
      }
      
      curFunc = 0;      
      curNoteName = "";
    }

    if (!skip) {
      #ifdef DEBUG_PLAY_CMD
        DEBUGPRINTLN("");
      #endif
    }
  }

  playTuneEnd();
}

bool amp_isNum(byte value) {
  if (value >= 48 && value <= 57)                   // 0 to 9
    return true;
  else
    return false;
}

String amp_noteName(byte note) {
  switch (note) {
    case  0:  return "C ";  break;
    case  1:  return "C#";  break;
    case  2:  return "D ";  break;
    case  3:  return "D#";  break;
    case  4:  return "E ";  break;
    case  5:  return "F ";  break;
    case  6:  return "F#";  break;
    case  7:  return "G ";  break;
    case  8:  return "G#";  break;
    case  9:  return "A ";  break;
    case 10:  return "A#";  break;
    case 11:  return "B ";  break;
  }
}

double amp_noteLengthToMS(unsigned int curNoteLength, byte tempo) {
  double timeBase = 60000 / tempo;           // @EB-todo default 4/4
    
  switch (curNoteLength) {
    case  1:  return (timeBase * 4);                // whole note
    case  2:  return (timeBase * 2);                // half
    case  4:  return (timeBase);                    // quarter
    case  8:  return (timeBase / 2);                // 8th
    case 16:  return (timeBase / 4);                // 16th
    case 32:  return (timeBase / 8);                // 32nd
    case 64:  return (timeBase / 16);               // 64th

    case  3:  return (timeBase * 3);                // dotted half
    case  6:  return (timeBase * 3 / 2);            // dotted 4th
    case 12:  return (timeBase * 3 / 4);            // dotted 8th
    case 24:  return (timeBase * 3 / 8);            // dotted 16th
    case 48:  return (timeBase * 3 / 16);           // dotted 32th

    case 34:  return (timeBase / 1.5);              // triplet quarter
    case 38:  return (timeBase / 3);                // triplet 8th
    case 316: return (timeBase / 6);                // triplet 16th
    case 332: return (timeBase / 12);               // triplet 32th
  }
}

void amp_playNote(byte note, int duration, String curNoteName) {



  if (amp_lyrics > 0) {   // @EB-todo
    displayLyrics(curNoteName);
  }

  
  #ifndef DEBUG_SILENCE 
    if (note >= 0 && note <= 107) {
      if (pt_notes[note]) {
        #ifdef ESP32
          ledcWriteTone(0, pt_notes[note]);
        #else
          tone(AMP_SPEAKER_PIN, pt_notes[note]);
        #endif
      } else {
        #ifdef ESP32
          ledcWriteTone(0, 0);
          ledcWrite(0, LOW);
        #else
          noTone(AMP_SPEAKER_PIN);
        #endif
      }
    }
  #endif
  
//  delay(duration);

  delayAndCheckEnc(duration);              // @EB-todo
}

void playTune_lyricsOff() {
  if (amp_lyrics > 0) { // @EB-todo
    matrix.fillScreen(LOW);
    matrix.write();
  } 
}

void playTuneEnd() {
  amp_playNote(0, 0, "");
  pinMode(AMP_SPEAKER_PIN, INPUT);                     // make sure the buzzer is silent ;-)
}

amp_notes.h

Arduino
/*************************************************
 * Note frequency array
 *************************************************/

unsigned int pt_notes[] = {
      0,    0,    0,    0,    0,    0,    0 ,   0,    0,    0,    0,    0,            //  0- 11  undefined
	   31,   35,   37,   39,   41,   44,   46,   49,   52,   55,   58,   62,            // 12- 23  C1 C#1 D1 D#1 E1 F1 F#1 G1 G#1 A1 A#1 B1
	   65,   69,   73,   78,   82,   87,   93,   98,  104,  110,  117,  123,            // 24- 35  C2 to B2
	  131,  139,  147,  156,  165,  175,  185,  196,  208,  220,  233,  247,            // 36- 47  C3 to B3
	  262,  277,  294,  311,  330,  349,  370,  392,  415,  440,  466,  494,            // 48- 59  C4 to B4 (middle C)
	  523,  554,  587,  622,  659,  698,  740,  784,  831,  880,  932,  988,            // 60- 71  C5 to B5
   1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976,            // 72- 83  C6 to B6
   2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951,            // 84- 95  C7 to B7
   4186, 4435, 4699, 4978, 5274, 5588, 5920, 6272, 6645, 7040, 7459, 7902             // 96-107  C8 to B8
};

Credits

ericBcreator

ericBcreator

9 projects • 220 followers

Comments