XQuery/MusicXML to Arduino

Motivation

edit

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

Approach

edit

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
(: ~
 : 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
  1. Good King Wensceslas
  2. HTML Form interface