Japaninoを触ってみた:MMLの再生

8ビットマイコン (大人の科学マガジンシリーズ)
Arduino互換機Japaninoというのが面白そうなので触ってみた。
電子回路の知識がなくてもとりあえずいろんなデバイスが動かせるというのは、ソフトウェアでいうとVisual Basicみたいな感じで楽しい。

プログラムのことをスケッチと呼ぶらしいが、とりあえずのHello World代わりということで、文字列として配置したMMLを読み込んで再生するスケッチをかいてみた。
オクターブ変更、半音、休符、音長をサポートしたが、スラー・タイは無視する。

J.S. Bach Prélude (from BWV1007)

/*
   MML for Arduino
   Bach
 */

#include <avr/pgmspace.h>

const int ledPin =  13;
const int spPin = 14;


// pos == -1 : initial
const int POS_INIT = -1;
// 0 <= pos <= 11 : note
const char freqs[] = {65, 69, 73, 78, 82, 87, 92, 98, 104, 110, 117, 123};
// pos == 20 : tempo
const int POS_TEMPO = 20;
// pos == 21 : rest
const int POS_REST = 21;
// pos == 22 : set base duration
const int POS_SET_BASE_DURATION = 22;

const int defaultDuration = 8;
const int initialTempo = 240;


void noteWithFreq(int freq, int attack, int total)
{
   tone(spPin, freq);
   delay(attack);
   noTone(spPin);
   delay(total-attack);
}

int scoreSpeed ;
int baseDuration;

void setup()   {                
  pinMode(ledPin, OUTPUT);   
  pinMode(spPin, OUTPUT);  
  setTempo(initialTempo);
  baseDuration = defaultDuration;
//  Serial.begin(9600);
}


void setTempo(int tempo) {
  scoreSpeed = 60000 * 4 / tempo;
}


void note(int pos, int octave, int duration)
{
  if (pos == POS_INIT) 
    return;
    
  if (pos == POS_TEMPO) {
    setTempo(duration);
    return;
  }
  
  if (pos == POS_SET_BASE_DURATION) {
    baseDuration = duration;
    return;
  }
  
  if (duration == 0) {
    duration = baseDuration;
  }
  
  int total = scoreSpeed / duration;
  
  if (pos == POS_REST) {
    delay(total);
    return;
  }
  
  noteWithFreq(freqs[pos] * (1<<octave), total*9/10, total);
}



PROGMEM prog_char s00[]="t80l16g&<d&babdbd>g&<d&babdbd>g&<e&<c>b<c>e<c>e>g&<e&<c>b<c>e<c>e>g&<f#";
PROGMEM prog_char s01[]="&<c>b<c>f#<c>f#>g&<f#&<c>b<c>f#<c>f#>g&<g&babgbg>g&<g&babgbt74f#t70>g&t";
PROGMEM prog_char s02[]="80<e&babgf+gegf+g>b<dt74c+>b<c+&t80g&agagagc+&g&agagagt70f+&t76a&t80<dc";
PROGMEM prog_char s03[]="+d>agaf+agadf+ed>e&b&<gf+g>b<g>be&b&<gf+g>b<gt75>bt64e&t80<c+&dedc+>ba<";
PROGMEM prog_char s04[]="gf+e<dc+>bagt80f+&e&d<d>a<d>f+ad&e&f+agf+edt70g+t80df&e&fdg+dbdf&e&fdg+";
PROGMEM prog_char s05[]="dc&e&ab<c>aedc&e&ab<c>af+t75et75d+&t80f+&d+f+af+af+d+&f+&d+f+af+af+g&f+";
PROGMEM prog_char s06[]="&egf+gaf+gf+edc>bt70agt60f+&t80<c&dcdcdc>f+&<c&dcdcdc>g&b&<fef>b<f>bg&b";
PROGMEM prog_char s07[]="&<fef>b<f>bg&<c&edecec>g&<c&edecec>g&<f+&<c>b<c>f+<c>f+>g&<f+&<c>b<c>f+";
PROGMEM prog_char s08[]="<ct60>f+>g&t80<d&babgf+edc>bat75gf+edc+&t80a&<ef+gef+g>c+&a&<ef+gef+g>c";
PROGMEM prog_char s09[]="&a&<def+def+>c&a&<def+def+>c&a&t70<df+t60a<c+d8&dt78>>ab<ct80def+gaf+de";
PROGMEM prog_char s10[]="f+gab<c>af+gab<cde-&d&c+&dd&c&>b&<cc>af+ed>ab<c>d&a&<df+ab<c>abgdc>bgat";
PROGMEM prog_char s11[]="72bd&t80g&b<dgabg<c+&>b&a&b-b-&a&g+&aa&g&f+&ggec+>ba&<c+&ega<c+dc+t72dt";
PROGMEM prog_char s12[]="80>af+&e&f+adf+t72>at80<dc+>bagf+ed8<<c&>b&a&g&f+&et70dt80<c&>b&a&g&f+&";
PROGMEM prog_char s13[]="e&dt72ct80b&a&g&f+&e&d&ct72>bt80<a&g&f+&e&d&c&t72>bt60at75<g&f+&eF#t80a";
PROGMEM prog_char s14[]="DaEaF#aGaEaF#aDat82GaEaF#aDaGaEaF#aDaEaF#at83GaAaBaDaAaBa<C>aDaBa<C>a<D";
PROGMEM prog_char s15[]=">aBa<C>aBa<C>aAaBaAaBaGat80AaGaAaF#aGaF#aGaEaF#at83d&e&F&dF#dGdG#dAdB-d";
PROGMEM prog_char s16[]="Bd<C>dt85<C#>d<D>d<E->d<E>d<F>d<F#>dt80<G&t83>b&db<g>b<g>bt80<g&t83>b&d";
PROGMEM prog_char s17[]="b<g>b<g>b<g&>a&da<g>a<g>at80<g&t83>a&da<g>a<g>at80<f#&t83c&>d<cf#cf#cf#";
PROGMEM prog_char s18[]="&c&>d<cf#cf#ct60>>g32&<b32&t75<g1";
PROGMEM char *score[] = {
/* // BWV1013
  "EAG#A<C>AE>A<EA&G#&A<C>AE>A<CEF>G#<FED CEG#A>E<DC>BA<CEF>G#<FED CEG#A>E<DC>BA<C>G#AF<D>G#A E<C>G#AD<C>BAG#EG#B<EG#B<D >F<D>BG#EDC>B <C>A<CEA<CE>F ",
    "GB-GEC>B-AGAFA<CDFAC >BGB<DEGBD C>A<CEFA<C>E D>B<DF#GB<D>FEG<C>B<CEC>G CG<C>B<CEC>G CGB-AB-<E>B-G CGB-AB-<E>B-GC#GB-AB-<E>B-G C#GEC#>AGFE D<GFEFAFD ",
    ">BG#B<DEDC>B<CEAG#A<C>AF# D#>B<D#F#BAGF# GF#EG>A<EG<C >F#EDF#>G<DF#BEDCE>F#<EF#A D#>B<C>AD#<C>BA G<EFD>G#<FED>A<F#GE>B-<GF#ED#BG#DC#AF#C >B<GE>B-A<FD#>A G<EC#>AF#<C>AF#D#F#A<C>B<AGF#G F#16E16 B<C8> F#4&F#8 E8 E",
*/
  s00,s01,s02,s03,s04,s05,s06,s07,s08,s09,s10,s11,s12,s13,s14,s15,s16,s17,s18
};

char cbuf[72];

void loop()                     
{
  
  int octave = 2;
  int pos  = POS_INIT;
  int duration = 0;
  
  for (int j = 0; j < 19; j++) {
    strcpy_P(cbuf, (char *)pgm_read_word(&(score[j])));
    for (int i = 0; cbuf[i] != '\0' ; i++) {
        char c = cbuf[i];
        
        // toupper
        if ('a' <= c && c <= 'z') {
          c -= 0x20;
        }
        // beginning of note
        if ('<'==c || '>'== c || ('A' <= c && c <= 'Z')) {
           // send last note
           note(pos, octave, duration);
           // reset variables
           pos = -1;
           duration = 0; /* default */
        }
        if ('0' <= c && c <= '9') {
          duration = duration * 10 + c - '0';
        }
        switch(c) {
            case '<': octave++; break;
            case '>': octave--; break;
            case '#': case '+': pos++; if(pos==12) { pos = 0; } break;
            case '-': pos--; if(pos==-1) { pos = 11; }  break;
            case 'C': pos=0; break;
            case 'D': pos=2; break;
            case 'E': pos=4; break;
            case 'F': pos=5; break;
            case 'G': pos=7; break;
            case 'A': pos=9; break;
            case 'B': pos=11; break;
            case 'T': pos=POS_TEMPO; break;
            case 'R': pos=POS_REST; break;
            case 'L': pos=POS_SET_BASE_DURATION; break;
            default: /* ignore */ break;
        }
    }
  }
  note(pos, octave, duration);
  noTone(spPin);
  delay(1000);
}