robsoncouto / arduino-songs

1.1k stars 445 forks source link

Non-Blocking Code #33

Open tahunus opened 1 year ago

tahunus commented 1 year ago

Amazing work! Kudos!

I made some changes to your code to make it non-blocking: instead of delay statements for each note and program logic waiting for the song to end, my changes allow for program execution while the tunes are being played.

Also I added a flag that can stop tune playback before it ends (for example if program logic requires it).

I also added the option to have up to 10 tunes selectable either by user input or by program logic.

Thanks!

Note: I don't know how to upload programs into a reported issue other than presenting the full code using markup language, so I renamed the NonBlockingTunes.INO and the tunes.h files to .TXT so that they could be copied here.

NonBlockingTunes.txt tunes.txt

tahunus commented 1 year ago

Perhaps adding the code files here can assist in a visual tour.

const byte buzzPin = 12;
const byte buzzChan = 0; 
#include "tunes.h"

int tempo, notes, wholenote, divider, thisNote, noteDuration;
byte activeTune;
bool tuneIsOn, noteIsOn;
ulong elapsedNote;

void defineTunes() { 
  switch (activeTune) {
    case 0: tempo = 190; notes = sizeof(superMarioShort) / sizeof(superMarioShort[0]) / 2;    break;
    case 1: tempo = 170; notes = sizeof(darthVaderShort) / sizeof(darthVaderShort[0]) / 2;    break;
    case 2: tempo = 170; notes = sizeof(pinkPantherShort) / sizeof(pinkPantherShort[0]) / 2;  break;
    case 3: tempo = 190; notes = sizeof(superMarioFull) / sizeof(superMarioFull[0]) / 2;      break;
    case 4: tempo = 170; notes = sizeof(darthVaderFull) / sizeof(darthVaderFull[0]) / 2;      break;
    case 5: tempo = 170; notes = sizeof(pinkPantherFull) / sizeof(pinkPantherFull[0]) / 2;    break;
    case 6: tempo = 140; notes = sizeof(happybirthday) / sizeof(happybirthday[0]) / 2;        break;
    case 7: tempo = 114; notes = sizeof(odetojoy) / sizeof(odetojoy[0]) / 2;                  break;
    case 8: tempo = 122; notes = sizeof(lionsleeps) / sizeof(lionsleeps[0]) / 2;              break;
    case 9: tempo =  76; notes = sizeof(brahmslullaby) / sizeof(brahmslullaby[0]) / 2;        break;
  }
  wholenote = (60000 * 4) / tempo; //calculate the duration of a whole note in ms
  thisNote = 0;
  tuneIsOn = true;
  noteIsOn = false;
}

void chkOnTunes() {
  if (thisNote < notes * 2) { //  for (int thisNote = 0; thisNote < notes * 2; thisNote = thisNote + 2) {
    if (!noteIsOn) { // calculates the duration of each note
      switch (activeTune) {   //array is twice the number of notes (notes + durations)
        case 0: divider = superMarioShort[thisNote + 1];  break;
        case 1: divider = darthVaderShort[thisNote + 1];  break;
        case 2: divider = pinkPantherShort[thisNote + 1]; break;
        case 3: divider = superMarioFull[thisNote + 1];   break;
        case 4: divider = darthVaderFull[thisNote + 1];   break;
        case 5: divider = pinkPantherFull[thisNote + 1];  break;
        case 6: divider = happybirthday[thisNote + 1];    break;
        case 7: divider = odetojoy[thisNote + 1];         break;
        case 8: divider = lionsleeps[thisNote + 1];       break;
        case 9: divider = brahmslullaby[thisNote +1];     break;
      }
      if (divider > 0) noteDuration = (wholenote) / divider; // regular note, just proceed
      else if (divider < 0) { // dotted notes are represented with negative durations
          noteDuration = (wholenote) / abs(divider);
          noteDuration *= 1.5; // increases the duration in half for dotted notes
        }
      noteIsOn = true;
      elapsedNote = 0;
    } //end of calculation of note duration
    if (elapsedNote == 0) { //start to play note
      switch (activeTune) { 
        case 0: ledcWriteTone(buzzChan,superMarioShort[thisNote]);  break;
        case 1: ledcWriteTone(buzzChan,darthVaderShort[thisNote]);  break;
        case 2: ledcWriteTone(buzzChan,pinkPantherShort[thisNote]); break;
        case 3: ledcWriteTone(buzzChan,superMarioFull[thisNote]);   break;
        case 4: ledcWriteTone(buzzChan,darthVaderFull[thisNote]);   break;
        case 5: ledcWriteTone(buzzChan,pinkPantherFull[thisNote]);  break;
        case 6: ledcWriteTone(buzzChan,happybirthday[thisNote]);    break;
        case 7: ledcWriteTone(buzzChan,odetojoy[thisNote]);         break;
        case 8: ledcWriteTone(buzzChan,lionsleeps[thisNote]);       break;
        case 9: ledcWriteTone(buzzChan,brahmslullaby[thisNote]);    break;
      }
      elapsedNote = millis();
    }else{ // Wait for the specified duration before playing the next note
      if (millis() - elapsedNote >= noteDuration) { 
        ledcWriteTone(buzzChan,0); // stop the waveform generation before the next note
        noteIsOn = false;
        thisNote += 2;
      }
    }
  }else // tune has ended because thisNote > notes * 2
    tuneIsOn = false; 
}

void setup() //------------------------------------------------------------------SETUP
{
  pinMode(buzzPin, OUTPUT); 
  ledcSetup(buzzChan, 10000, 12); 
  ledcAttachPin(buzzPin, buzzChan);
  tuneIsOn = true; //flag to start or stop playback. To be used in program logic in loop()
  activeTune = 4; //number of Tune to play. Can be defined by user input or as program logic
  defineTunes();
}

void loop() //--------------------------------------------------------------------LOOP
{
  if (tuneIsOn) chkOnTunes();
}