Monday, December 22, 2014

Tone music: advanced playing software




Abstract
In this post we are going to present a more sophisticated program for playing melodies on your Arduino. It allows you to encode repetitions and alternate endings, compacting your longer melodies.
Keywords: tone music; pauses; augmentations; ties; repetitions.

The new playing routine
The new play function implements more sophisticated management of melodies allowing pauses, ties, augmentations (like the past routine) and it also introduces the possibility to use repetitions and alternate endings. This post explains how to encode all this.

The sketch with the new routine is given below. Remark that it makes use of a support class that implements a simple stack. Source files can found here: Stack.h, Stack.cpp. In order to be able to compile the sketch you should open this two files in other tabs of your Arduino IDE. You also need instructions.h which contains the encoding for the new instructions used to encode the melodies.

Compatibility
All the songs encoded for the previous version of the playing routine still work for this new version without any modification. Indeed, the new melody encoding is an extension of the older one.

See also
Tone music surprises!
Tone music: how to encode songs
Tone music: changing the Tempo of your melodies

Enjoy!

/*
 *  PlayMelodyAdv
 *
 *  A more advanced program for playing melodies on Arduino. 
 *
 *  Define the label DEBUG to have some debug support
 *
 *  Written by Enrico Formenti, 22 dec 2014
 *
 *  BSD license, check the License page on the blog for more information
 *  All this text must be included in any redistribution.
 *
 *  See macduino.blogspot.com for more details.
 */

#include "pitches.h"
#include "durations.h"
#include "instructions.h"
#include "Stack.h"
#include "Stack.cpp"
#include "SilentNight.h"
 
#define TONEPIN 8

typedef struct {
  struct {
  uint16_t N;  // position in melody array
  uint16_t D;  // position in durations array
  } I;         // individual addressing
  uint32_t A;  // addressing all together
} POS;

void playMusic( uint16_t *instr, uint16_t *durs, uint16_t len) {

  POS coda;      // address of coda symbol
  POS segno;     // address of segno symbol
  POS invRepeat; // position of inv repeat sign
  POS nskip;     // posistion of the first note to skip
  POS fine;      // end of music symbol position
  POS askip;     // position of the first note to skip in alternate ending
  POS curPos; 

  // reset all internal variables
  coda.A = 0;
  segno.A = 0;
  invRepeat.A = 0;
  nskip.A = 0;
  fine.A = 0;
  askip.A = 0;
  curPos.A = 0;
  
  // start looping
  while(curPos.I.N < len) {
    Stack<POS> reps; // make the stack local so it is emptied when the loop 
                     // is over

    switch(instr[curPos.I.N]) {
      case NOTE_PAUSE:
        // noTone(TONEPIN);
        delay(durs[curPos.I.D]);
        curPos.A += 0x11;
        break;
      case NOTE_CODA:
        if(nskip.A) {
          curPos.A = nskip.A;
          nskip.A = 0;
          coda.A = 0;
          break;
        }
        coda.A = curPos.A+0x10; // start from next position
        (curPos.I.N)++;
        break;
      case NOTE_DACAPO:
        if(reps.isEmpty() || (reps.topElement().A!=curPos.A)) { // if this event has not already occurred
          reps.push(curPos); // add this event
          curPos.A=0;  // repeat from the beginning
          break;
        }
        // otherwise we simply skip and update the instruction pointer
        reps.pop();
        (curPos.I.N)++;
        break;
      case NOTE_SEGNO:
        segno.A=curPos.A+0x10; // start from next position in note array
        (curPos.I.N)++;        
        break;
      case NOTE_REPEAT:
        if(reps.isEmpty() || (reps.topElement().A != curPos.A)) {
          if(invRepeat.A) { // repeat from last inv repeat sign if any
            curPos.A=invRepeat.A+0x11; // add +1 to both N and D pointers
            invRepeat.A=0;
            reps.push(curPos);// push the event on the stack
            break;
          }
          // otherwise repeat from the beginning
          reps.push(curPos);// push the event on the stack
          curPos.A=0;
          break;
        }
        // if this is the sign that issued the event
        reps.pop();
        (curPos.I.N)++;
        break;
      case NOTE_INVREPEAT:
        invRepeat.A=curPos.A+0x10; // start from the next position in notes array
        (curPos.I.N)++;
        break;
      case NOTE_DALSEGNO:
        if(reps.isEmpty() || (reps.topElement().A!=curPos.A)) { // if this has not generated the event
          reps.push(curPos);
          curPos.A=segno.A;
          segno.A=0;
          break;
        }
        // otherwise skip and update N only
        reps.pop();
        segno.A=0;
        (curPos.I.N)++;
        break;
      case NOTE_DALSEGNOCODA:
        if(reps.isEmpty() || (reps.topElement().A!=curPos.A)) {
          reps.push(curPos);
          curPos.A = segno.A;
          segno.A = 0;
          nskip.A = coda.A+0x10;
          break;
        }
        // otherwise skip and update N only
        reps.pop();
        (curPos.I.N)++;
        break;
        case NOTE_DACAPOCODA:
        if(reps.isEmpty() || (reps.topElement().A!=curPos.A)) {
          reps.push(curPos);
          curPos.A = 0;
          segno.A = 0;
          nskip.A = coda.A+0x10;
          break;
        }
        // otherwise skip and update N only
        reps.pop();
        (curPos.I.N)++;
        break;
      case NOTE_FINE:
        if(fine.A) { // we already passed through the music end symbol
          curPos.I.N = len; // then stop playing
          break;
        }
        fine.A = curPos.A;
        (curPos.I.N)++;
        break;
      case NOTE_DACAPOFINE:
        curPos.A = 0;
        break;
      case NOTE_DALSEGNOFINE:
        curPos.A = segno.A;
        break;
      case NOTE_ALTBEGIN:
        if(askip.A) { // we are repeating the sequence, hence skip
          curPos.A = askip.A;
          askip.A = 0;
          break;
        }
        // not repeating so just keep playing
        (curPos.I.N)++;
        break;
      case NOTE_ALTEND:
        askip.A = curPos.A+0x10; // set the skip to the next instruction
                                 // so that it will be skipped during the
                                 // repetition
        break;   
      default: // it is a note instruction
        tone(TONEPIN, instr[curPos.I.N], durs[curPos.I.D]);
        // to distinguish the notes, set a minimum time between them.
        // the note's duration + 30% seems to work well:
        delay(durs[curPos.I.D] * 1.3);
        // stop the tone playing:
        noTone(TONEPIN);
        curPos.A += 0x11; // update both N and D pointers
        break;
    }
  }
}

void setup() {
  
#ifdef DEBUG
  Serial.begin();
#endif
  
  playMusic((uint16_t *)melody,(uint16_t *)noteDurations,(uint16_t)melody_len);  
}

void loop() {
  // no need to repeat the melody.
}

No comments :

Post a Comment