Search

Learning Arduino - D3 - playing tunes

A bit of time today while waiting to drive off into snowy Gloucestershire so time for a bit of a play.  I got a baby 8 ohm speaker yesterday, found a 100 ohm resistor and set about the melody example.

This uses the tone function to generate a square wave with a given frequency. Sounds very square indeed..

The example plays a hard-coded tune once only. The tune is encoded in two arrays, one of pitches, the other of note lengths.  Pitches are defined in a included file of #define statements.

First change is to structure it a bit more:by defining the pin number explicitly setting its mode to OUTPUT (seems that's the default though),  moving the play tune code into a function and calling it in the loop.

To change the tune, even to just add another note, means not only adding values to both arrays but changing the length of the loop which is hardcoded.  I change this to sizeof(melody) - 1 and hear lots of additional clicks. Ah..  sizeof is in bytes and ints are two bytes.  The reference manual on the sizeof function shows how to get the length of the array in objects - but it turns out to be wrong surely?


  for (i = 0; i < (sizeof(myInts)/sizeof(int)) - 1; i++) {

  // do something with myInts[i]
}

The minus one is needed for strings which are null-terminated but not for arrays of ints etc, so this should be :

 for (i = 0; i < (sizeof(myInts)/sizeof(int)); i++) {
  // do something with myInts[i]
}

I think that's right - should I mention it in the forum - dont know.

The modified sketch looks like

 #include "pitches.h"

// notes in the melody:
int melody[] = {
  NOTE_C4, NOTE_G3,NOTE_G3, NOTE_A3, NOTE_G3,0, NOTE_B3, NOTE_C4, NOTE_D4};

// note durations: 4 = quarter note, 8 = eighth note, etc.:
int noteDurations[] = {
  4, 8, 8, 4,4,4,4,4,4 };
int speaker = 8;

void setup() {
   pinMode(speaker, OUTPUT);     
}

void loop() {
    play();
    delay(3000);
}

void play () {
  // iterate over the notes of the melody:
  for (int thisNote = 0; thisNote < (sizeof(melody)/sizeof(int)); thisNote++) {

    // to calculate the note duration, take one second 
    // divided by the note type.
    //e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
    int noteDuration = 1000/noteDurations[thisNote];
    tone(speaker, melody[thisNote],noteDuration);

    // to distinguish the notes, set a minimum time between them.
    // the note's duration + 30% seems to work well:
    int pauseBetweenNotes = noteDuration * 1.30;
    delay(pauseBetweenNotes);


    // added this later when this code made horrible noises


    noNote(speaker);
  }
}


Sounds pretty horrible.  Two changes I'd like to make

    *  I don't much like the #defines - why not a table of frequencies keyed by midi note number?
    * a single note representation would be better than parallel arrays.

Midi notes
I dont want to type up the array so I use a bit of XQuery (well I have to dont I !)  to scrape a table on a page I found:

http://www.glassarmonica.com/science/frequency_midi.php

declare namespace h = "http://www.w3.org/1999/xhtml";
declare option exist:serialize "method=text media-type=text/text";

let $doc := doc("http://www.glassarmonica.com/science/frequency_midi.php")
let $table :=  $doc//h:table[@class="single"]
return
<code>
  {
  concat(
"// midi note frequencies - 0 is midi 21
int midi[] = {",
    string-join(
    for $row at $i in $table/h:tbody/h:tr[position() > 1]
    return
      concat(xs:string(round($row/h:td[3])), if ($i mod 10 = 0) then "&#10;" else ())
     ,", "
     )
     ,"} ;"
    )
   }
</code>

which generates
 

 
// midi note frequencies - 0 is midi 21
int midifrequencies[] = {28, 29, 31, 33, 35, 37, 39, 41, 44, 46
, 49, 52, 55, 58, 62, 65, 69, 73, 78, 82
, 87, 93, 98, 104, 110, 117, 124, 131, 139, 147
, 156, 165, 175, 185, 196, 208, 220, 233, 247, 262
, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466
, 494, 523, 554, 587, 622, 659, 699, 740, 784, 831
, 880, 932, 988, 1047, 1109, 1175, 1245, 1319, 1397, 1480
, 1568, 1661, 1760, 1865, 1976, 2093, 2218, 2349, 2489, 2637
, 2794, 2960, 3136, 3322, 3520, 3729, 3951, 4186} ;

First note is midi 21, so frequency of midi note x is  midi[x - 21]

Now I can define Cmajor scale as

  int melody[] = {60,62,64,65,67,69,71,72};

and get the frquency with

int midifreq(int midinote) {
   return midifrequencies[midinote - 21];
}

Well that sounds rubbish - not a recognisable scale at all.

Coming back to this later and looking at more examples, I see that the note has to be turned off which seems redundant when the duration is set.  Doesnt matter if the tune is played only once but its needed if the tune is looped.  I think that example is a bit misleading.

Passing a string to a function

Trying to write code which palys  a string of note names, I ran into problems passing a string to a function  - as in



char GKW[] = "GGGAGGDEDEFGGGGAGGGDEDEFGGDCBABAGEDEFGGDDEFGGADCBAGCG";


...


play(GKW)


...


void play(char* melody) {


for (int thisNote = 0; thisNote < sizeof(melody) - 1; thisNote++) {
  



}


sizeof() returns 2 - not what I want.  After much ferreting around I realize I should have been using the C function strlen()

 for (int thisNote = 0; thisNote < strlen(melody); thisNote++) {

There is very little in the reference manual on functions, parameter passing and the use of C functions in Arduino. I must dust off my K&R.

Playing an encoded tune

Some time ago I was playing with an XML encoding for music called, naturally enough, MusicXML.  Here's an example of a Mozart Piano Concerto  and the article on XQuery processing of MusicXML files in the XQuery Wikibook

Have no time to write an XML parser for the Arduino, I need a character encoding of a note which in XML would look like



<note>
                <pitch>
                    <step>C</step>
                    <alter>1</alter>
                    <octave>6</octave>
                </pitch>
                <duration>1</duration>
                <voice>1</voice>
                <type>16th</type>
                <stem>down</stem>
                <staff>1</staff>
                <beam number="1">continue</beam>
                <beam number="2">continue</beam>
                <notations>
                    <slur type="stop" number="1"/>
                </notations>
            </note>


Just the pitch and duration will do  - something like  "C#6;1" and use some string functions to parse unless there's a regexp function I could use.

Note to frequency

However they are coded, I'll need to convert  (letter, alter,octave) >  midi >  frequency .   For example:

 ( 'C','#', 4) to  midi 61 and hence to freq 277 hz.



int dohray2step[]  = {9,11,0,2,4,5,7};  // offsets of the letters ABCDEFG from C 
int speaker = 8;
int rate = 100;
void setup() {
   pinMode(speaker, OUTPUT);   
}

void loop() {
     Serial.println("**********");
     playnote('C','#',4,4);
     playnote('C',' ',4,4);
     playnote('C','b',4,4);
     delay(2000); 
}

int playnote(char dohray, char alter, int octave, int length) { 
    int inc = 0;
    // decode sharp + and flat - 
    if (alter == '#') inc = 1; else if (alter =='b') inc=-1; else ;
    // compute the midi note value
    int midinote = 12 * (octave + 1) + dohray2step[int(dohray) - 65] + inc;
     //look it up in the frequency table 
    int freq = midifrequencies[midinote - 21];
     //play the note 
    int millisec = length * rate;
    //play the note 
    tone(speaker, freq, millisec);
    //delay while the note plays and a bit longer
    delay (millisec * 1.3);
    // turn the note off
    noTone(speaker);
}

That works fine. 

Wrapup

More trip-ups;  can't use the same name for a variable as a function; beware reserved words (step is one) ; single quote around single char, double quote around strings and remember to turn the note off!

Next step is to parse some string format for the note and I should be able to play Good King Wenceslas as  the Christmas lights flash the lyrics in morse - but how I wonder to do both tasks at once?