XQuery/MusicXML to Arduino

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Motivation[edit | edit source]

You want to play music available in MusicXML format on an Arduino.

Approach[edit | edit source]

Fetch the Music XML file (either plain XML or compressed) and transform one monophic part to code to be included in an Arduino sketch.

Script[edit | edit source]

(: ~
 : convert a monotonic part in a MusicXML score to an Arduino code fragment suitable to include in a sketch 
 :
 :@param uri - the uri of the MusicXML file
 :@param part - the id of the part to be converted to midi notes
 :@return text containing Arduino statements to :
 :     set the tempo, 
 :     define the array of midi notes a
 :     define a parallel array of note durations in beats 
 :@author Chris Wallace
 :)
 
(: offsets of the letters ABCDEFG from C :)
declare namespace fw = "http://www.cems.uwe.ac.uk/xmlwiki/fw";

declare variable $fw:step2offset := (9,11,0,2,4,5,7);  

declare function fw:filter($path as xs:string, $type as xs:string, $param as item()*) as xs:boolean {
 (: pass all :)
 true()
};

declare function fw:process($path as xs:string,$type as xs:string, $data as item()? , $param as item()*) {
 (: return the XML :)
 $data
};

declare function fw:unzip($uri) {
let $zip := httpclient:get(xs:anyURI($uri), true(), ())/httpclient:body/text()
let $filter := util:function(QName("http://www.cems.uwe.ac.uk/xmlwiki/fw","fw:filter"),3)
let $process := util:function(QName("http://www.cems.uwe.ac.uk/xmlwiki/fw","fw:process"),4)
let $xml := compression:unzip($zip,$filter,(),$process,())
return $xml
};


declare function fw:MidiNote($thispitch as element() ) as xs:integer {
  let $step := $thispitch/step
  let $alter :=
    if (empty($thispitch/alter)) then 0
    else xs:integer($thispitch/alter)
  let $octave := xs:integer($thispitch/octave)
  let $pitchstep := $fw:step2offset [ string-to-codepoints($step) - 64]
  return 12 * ($octave + 1) + $pitchstep + $alter
} ;

declare function fw:mxl-to-midi ($part) {
  for $note in $part//note
  return 
    element note {
       attribute midi { if ($note/rest) then 0 else fw:MidiNote($note/pitch)},
       attribute duration { ($note/duration, 1) [1] }
    }
};

declare function fw:notes-to-arduino ($notes as element(note)*) as element(code) {
(: create the two int arrays for inclusion in an Arduino sketch :)
<code>
int note_midi[] = {{&#10; { 
   string-join(
      for $midi at $i in $notes/@midi
      return 
        concat(if ($i  mod 10 eq 0) then "&#10;" else (),$midi)
      ,", ")
    }
}};

int note_duration[] = {{&#10; {
    string-join(
      for $duration at $i in $notes/@duration
      return 
        concat(if ($i  mod 10 eq 0) then "&#10;" else (),$duration)
      ,", ")
    } 
}}; 
</code>
};
 
declare option exist:serialize "method=text media-type=text/text";

let $uri := request:get-parameter("uri",())
let $part := request:get-parameter("part","P1")
let $format := request:get-parameter("format","xml")
let $doc := 
    if ($format = "xml")
    then doc ($uri)
    else if ($format = "zip")
    then fw:unzip($uri)
    else ()
(: get the requested part :)
let $part := $doc//part[@id = $part]
(: use the data in the first measure to set the temp :)
let $measure := $part/measure[1]
let $tempo := (xs:integer($measure/sound/@tempo), 100)[1]
(: convert the notes into an internal XML format :)
let $notes := fw:mxl-to-midi($part)
return

(: generate the sketch fragmemt:)
  <sketch>
int tempo = {$tempo};
{fw:notes-to-arduino($notes) }
  </sketch>

Examples[edit | edit source]

  1. Good King Wensceslas
  2. HTML Form interface