http://mathling.com/music library module
http://mathling.com/music
Music: parsing MusicXML, simplisticly
Doesn't really understand a lot but will map pitch and durations as long
as nothing too complicated is going on
Copyright© Mary Holstege 2022-2023
CC-BY (https://creativecommons.org/licenses/by/4.0/)
Status: Limited
Imports
http://mathling.com/geometric/edgeimport module namespace edge="http://mathling.com/geometric/edge" at "../geo/edge.xqy"http://mathling.com/core/utilities
import module namespace util="http://mathling.com/core/utilities" at "../core/utilities.xqy"http://mathling.com/type/wrapper
import module namespace wrapper="http://mathling.com/type/wrapper" at "../types/wrapper.xqy"http://mathling.com/geometric/rectangle
import module namespace box="http://mathling.com/geometric/rectangle" at "../geo/rectangle.xqy"http://mathling.com/type/slot
import module namespace slot="http://mathling.com/type/slot" at "../types/slot.xqy"http://mathling.com/geometric
import module namespace geom="http://mathling.com/geometric" at "../geo/euclidean.xqy"http://mathling.com/core/errors
import module namespace errors="http://mathling.com/core/errors" at "../core/errors.xqy"http://mathling.com/geometric/point
import module namespace point="http://mathling.com/geometric/point" at "../geo/point.xqy"
Functions
Function: note
declare function note($n as xs:integer,
$octave as xs:numeric,
$tone as xs:numeric,
$sharpflat as xs:numeric,
$measure as xs:integer,
$start as xs:numeric,
$duration as xs:numeric,
$grace as xs:boolean) as map(xs:string,item()*)
declare function note($n as xs:integer, $octave as xs:numeric, $tone as xs:numeric, $sharpflat as xs:numeric, $measure as xs:integer, $start as xs:numeric, $duration as xs:numeric, $grace as xs:boolean) as map(xs:string,item()*)
Params
- n as xs:integer
- octave as xs:numeric
- tone as xs:numeric
- sharpflat as xs:numeric
- measure as xs:integer
- start as xs:numeric
- duration as xs:numeric
- grace as xs:boolean
Returns
- map(xs:string,item()*)
declare function this:note( $n as xs:integer, $octave as xs:numeric, $tone as xs:numeric, $sharpflat as xs:numeric, $measure as xs:integer, $start as xs:numeric, $duration as xs:numeric, $grace as xs:boolean ) as map(xs:string,item()*) { map { "kind": "note", "n": $n, "octave": $octave, "tone": $tone, "sharpflat": $sharpflat, "measure": $measure, "start": $start, "duration": $duration, "grace": $grace } }
Function: kind
declare function kind($item as map(xs:string,item()*)) as xs:string
declare function kind($item as map(xs:string,item()*)) as xs:string
Params
- item as map(xs:string,item()*)
Returns
- xs:string
declare function this:kind($item as map(xs:string,item()*)) as xs:string { $item("kind") }
Function: note
declare function note($item as map(xs:string,item()*)) as xs:integer
declare function note($item as map(xs:string,item()*)) as xs:integer
Params
- item as map(xs:string,item()*)
Returns
- xs:integer
declare function this:note($item as map(xs:string,item()*)) as xs:integer { $item("n") }
Function: octave
declare function octave($note as map(xs:string,item()*)) as xs:numeric
declare function octave($note as map(xs:string,item()*)) as xs:numeric
Params
- note as map(xs:string,item()*)
Returns
- xs:numeric
declare function this:octave($note as map(xs:string,item()*)) as xs:numeric { $note("octave") }
Function: tone
declare function tone($note as map(xs:string,item()*)) as xs:numeric
declare function tone($note as map(xs:string,item()*)) as xs:numeric
Params
- note as map(xs:string,item()*)
Returns
- xs:numeric
declare function this:tone($note as map(xs:string,item()*)) as xs:numeric { $note("tone") }
Function: sharpflat
declare function sharpflat($note as map(xs:string,item()*)) as xs:numeric
declare function sharpflat($note as map(xs:string,item()*)) as xs:numeric
Params
- note as map(xs:string,item()*)
Returns
- xs:numeric
declare function this:sharpflat($note as map(xs:string,item()*)) as xs:numeric { $note("sharpflat") }
Function: measure
declare function measure($note as map(xs:string,item()*)) as xs:integer
declare function measure($note as map(xs:string,item()*)) as xs:integer
Params
- note as map(xs:string,item()*)
Returns
- xs:integer
declare function this:measure($note as map(xs:string,item()*)) as xs:integer { $note("measure") }
Function: start
declare function start($note as map(xs:string,item()*)) as xs:numeric
declare function start($note as map(xs:string,item()*)) as xs:numeric
Params
- note as map(xs:string,item()*)
Returns
- xs:numeric
declare function this:start($note as map(xs:string,item()*)) as xs:numeric { $note("start") }
Function: duration
declare function duration($note as map(xs:string,item()*)) as xs:numeric
declare function duration($note as map(xs:string,item()*)) as xs:numeric
Params
- note as map(xs:string,item()*)
Returns
- xs:numeric
declare function this:duration($note as map(xs:string,item()*)) as xs:numeric { $note("duration") }
Function: grace
declare function grace($note as map(xs:string,item()*)) as xs:boolean
declare function grace($note as map(xs:string,item()*)) as xs:boolean
Params
- note as map(xs:string,item()*)
Returns
- xs:boolean
declare function this:grace($note as map(xs:string,item()*)) as xs:boolean { $note("grace") }
Function: pitch
declare function pitch($note as map(xs:string,item()*)) as xs:numeric
declare function pitch($note as map(xs:string,item()*)) as xs:numeric
Params
- note as map(xs:string,item()*)
Returns
- xs:numeric
declare function this:pitch($note as map(xs:string,item()*)) as xs:numeric { this:octave($note) + this:tone($note) div 8 + this:sharpflat($note) div 16 }
Function: note
declare function note($n as xs:integer,
$octave as xs:numeric,
$tone as xs:numeric,
$sharpflat as xs:numeric,
$measure as xs:integer,
$start as xs:numeric,
$duration as xs:numeric) as map(xs:string,item()*)
declare function note($n as xs:integer, $octave as xs:numeric, $tone as xs:numeric, $sharpflat as xs:numeric, $measure as xs:integer, $start as xs:numeric, $duration as xs:numeric) as map(xs:string,item()*)
Params
- n as xs:integer
- octave as xs:numeric
- tone as xs:numeric
- sharpflat as xs:numeric
- measure as xs:integer
- start as xs:numeric
- duration as xs:numeric
Returns
- map(xs:string,item()*)
declare function this:note( $n as xs:integer, $octave as xs:numeric, $tone as xs:numeric, $sharpflat as xs:numeric, $measure as xs:integer, $start as xs:numeric, $duration as xs:numeric ) as map(xs:string,item()*) { this:note($n, $octave, $tone, $sharpflat, $measure, $start, $duration, false()) }
Function: rest
declare function rest($n as xs:integer,
$measure as xs:integer,
$start as xs:numeric,
$duration as xs:numeric) as map(xs:string,item()*)
declare function rest($n as xs:integer, $measure as xs:integer, $start as xs:numeric, $duration as xs:numeric) as map(xs:string,item()*)
Params
- n as xs:integer
- measure as xs:integer
- start as xs:numeric
- duration as xs:numeric
Returns
- map(xs:string,item()*)
declare function this:rest( $n as xs:integer, $measure as xs:integer, $start as xs:numeric, $duration as xs:numeric ) as map(xs:string,item()*) { map { "kind": "rest", "n": $n, "measure": $measure, "start": $start, "duration": $duration } }
Function: measure
declare function measure($m as xs:integer,
$notes as map(xs:string,item()*)*) as map(xs:string,item()*)
declare function measure($m as xs:integer, $notes as map(xs:string,item()*)*) as map(xs:string,item()*)
Params
- m as xs:integer
- notes as map(xs:string,item()*)*
Returns
- map(xs:string,item()*)
declare function this:measure( $m as xs:integer, $notes as map(xs:string,item()*)* ) as map(xs:string,item()*) { map { "kind": "score", "measure": $m, "notes": $notes } }
Function: notes
declare function notes($measure as map(xs:string,item()*)) as map(xs:string,item()*)*
declare function notes($measure as map(xs:string,item()*)) as map(xs:string,item()*)*
Params
- measure as map(xs:string,item()*)
Returns
- map(xs:string,item()*)*
declare function this:notes($measure as map(xs:string,item()*)) as map(xs:string,item()*)* { $measure("notes") }
Function: part
declare function part($p as xs:integer,
$measure-list as map(xs:string,item()*)*) as map(xs:string,item()*)
declare function part($p as xs:integer, $measure-list as map(xs:string,item()*)*) as map(xs:string,item()*)
Params
- p as xs:integer
- measure-list as map(xs:string,item()*)*
Returns
- map(xs:string,item()*)
declare function this:part( $p as xs:integer, $measure-list as map(xs:string,item()*)* ) as map(xs:string,item()*) { map { "kind": "part", "p": $p, "measure-list": $measure-list } }
Function: part
declare function part($part as map(xs:string,item()*)) as xs:integer
declare function part($part as map(xs:string,item()*)) as xs:integer
Params
- part as map(xs:string,item()*)
Returns
- xs:integer
declare function this:part($part as map(xs:string,item()*)) as xs:integer { $part("p") }
Function: measure-list
declare function measure-list($part as map(xs:string,item()*)) as map(xs:string,item()*)*
declare function measure-list($part as map(xs:string,item()*)) as map(xs:string,item()*)*
Params
- part as map(xs:string,item()*)
Returns
- map(xs:string,item()*)*
declare function this:measure-list($part as map(xs:string,item()*)) as map(xs:string,item()*)* { $part("measure-list") }
Function: score
declare function score($n-parts as xs:integer,
$n-measures as xs:integer,
$min-octave as xs:integer,
$max-octave as xs:integer) as map(xs:string,item()*)
declare function score($n-parts as xs:integer, $n-measures as xs:integer, $min-octave as xs:integer, $max-octave as xs:integer) as map(xs:string,item()*)
Params
- n-parts as xs:integer
- n-measures as xs:integer
- min-octave as xs:integer
- max-octave as xs:integer
Returns
- map(xs:string,item()*)
declare function this:score( $n-parts as xs:integer, $n-measures as xs:integer, $min-octave as xs:integer, $max-octave as xs:integer ) as map(xs:string,item()*) { map { "kind": "score", "parts": $n-parts, "measures": $n-measures, "min-octave": $min-octave, "max-octave": $max-octave } }
Function: measures
declare function measures($score as map(xs:string,item()*)) as xs:integer
declare function measures($score as map(xs:string,item()*)) as xs:integer
Params
- score as map(xs:string,item()*)
Returns
- xs:integer
declare function this:measures($score as map(xs:string,item()*)) as xs:integer { $score("measures") }
Function: parts
declare function parts($score as map(xs:string,item()*)) as xs:integer
declare function parts($score as map(xs:string,item()*)) as xs:integer
Params
- score as map(xs:string,item()*)
Returns
- xs:integer
declare function this:parts($score as map(xs:string,item()*)) as xs:integer { $score("parts") }
Function: min-octave
declare function min-octave($score as map(xs:string,item()*)) as xs:integer
declare function min-octave($score as map(xs:string,item()*)) as xs:integer
Params
- score as map(xs:string,item()*)
Returns
- xs:integer
declare function this:min-octave($score as map(xs:string,item()*)) as xs:integer { $score("min-octave") }
Function: max-octave
declare function max-octave($score as map(xs:string,item()*)) as xs:integer
declare function max-octave($score as map(xs:string,item()*)) as xs:integer
Params
- score as map(xs:string,item()*)
Returns
- xs:integer
declare function this:max-octave($score as map(xs:string,item()*)) as xs:integer { $score("max-octave") }
Function: octaves
declare function octaves($score as map(xs:string,item()*)) as xs:integer
declare function octaves($score as map(xs:string,item()*)) as xs:integer
Params
- score as map(xs:string,item()*)
Returns
- xs:integer
declare function this:octaves($score as map(xs:string,item()*)) as xs:integer { this:max-octave($score) - this:min-octave($score) + 1 }
Function: part-list
declare function part-list($score as map(xs:string,item()*)) as map(xs:string,item()*)*
declare function part-list($score as map(xs:string,item()*)) as map(xs:string,item()*)*
Params
- score as map(xs:string,item()*)
Returns
- map(xs:string,item()*)*
declare function this:part-list($score as map(xs:string,item()*)) as map(xs:string,item()*)* { $score("part-list") }
Function: part-list
declare function part-list($score as map(xs:string,item()*), $part-list as map(xs:string,item()*)*) as map(xs:string,item()*)
declare function part-list($score as map(xs:string,item()*), $part-list as map(xs:string,item()*)*) as map(xs:string,item()*)
Params
- score as map(xs:string,item()*)
- part-list as map(xs:string,item()*)*
Returns
- map(xs:string,item()*)
declare function this:part-list($score as map(xs:string,item()*), $part-list as map(xs:string,item()*)*) as map(xs:string,item()*) { $score=>map:put("part-list", $part-list) }
Function: parse
declare function parse($score-node as node()) as map(xs:string,item()*)
declare function parse($score-node as node()) as map(xs:string,item()*)
parse()
Parse the MusicXML score.
Params
- score-node as node(): root of MusicXML score
Returns
- map(xs:string,item()*): An expanded score object, which includes part objects
declare function this:parse( $score-node as node() ) as map(xs:string,item()*) { let $letters as xs:string* := ("A", "B", "C", "D", "E", "F", "G") let $n-measures as xs:integer := max($score-node//measure/@number!xs:integer(.)) cast as xs:integer let $n-parts as xs:integer := count($score-node//part) let $octaves as xs:integer* := $score-node//octave!xs:integer(.) let $min-octave as xs:integer := min($octaves) cast as xs:integer let $max-octave as xs:integer := max($octaves) cast as xs:integer let $score := this:score($n-parts, $n-measures, $min-octave, $max-octave) let $n-octaves as xs:integer := $max-octave - $min-octave + 1 let $parts := ( for $part at $p in $score-node//part let $measures := ( for $measure at $m in $part//measure (: divisions per quarter note :) (: divisions * 4 = length of measure :) let $divisions as xs:integer := 4 * head(($measure/attributes/divisions, $measure/preceding-sibling::measure/attributes/divisions)) cast as xs:integer (: Starts in counts; will convert to fraction of measure :) let $starts as xs:integer* := ( fold-left($measure/note, 0, function($starts as xs:integer*, $note-node as element(note)) as xs:integer* { $starts, if (exists($note-node/duration)) then ( $starts[last()] + xs:integer($note-node/duration) ) else ( $starts[last()] ) } ) ) let $notes := ( for $note-node at $n in $measure/note (: Duration as fraction of measure :) let $duration as xs:numeric := (if (exists($note-node/duration)) then (xs:integer($note-node/duration) div $divisions) else 0) let $octave as xs:numeric := (if (exists($note-node/pitch)) then xs:integer($note-node/pitch/octave) else 0) let $tone as xs:numeric := (if (exists($note-node/pitch/step)) then (for $l at $li in $letters where $l=$note-node/pitch/step return $li) else 0) let $sharpflat as xs:numeric := (if (exists($note-node/pitch/alter)) then xs:integer($note-node/pitch/alter) else 0) let $note as map(xs:string,item()*)? := ( if (exists($note-node/pitch)) then ( if (exists($note-node/duration)) then ( this:note($n, $octave, $tone, $sharpflat, $m, $starts[$n] div $divisions, $duration, exists($note-node/grace)) ) else ( this:note($n, $octave, $tone, $sharpflat, $m, $starts[$n] div $divisions, 0, true()) ) ) else if (exists($note-node/duration)) then ( this:rest($n, $m, $starts[$n], $duration) ) else () ) return ( $note ) ) return ( this:measure($m, $notes) ) ) return ( this:part($p, $measures) ) ) return ( $score=>this:part-list($parts) ) }
Function: process-score
declare function process-score($score-node as node(),
$score-fn as function(map(xs:string,item()*), item()*) as item()*,
$part-fn as function(xs:integer, map(xs:string,item()*), item()*) as item()*,
$measure-fn as function(xs:integer, map(xs:string,item()*), item()*) as item()*,
$note-fn as function(xs:integer, map(xs:string,item()*), map(xs:string,item()*), xs:integer) as item()*) as item()*
declare function process-score($score-node as node(), $score-fn as function(map(xs:string,item()*), item()*) as item()*, $part-fn as function(xs:integer, map(xs:string,item()*), item()*) as item()*, $measure-fn as function(xs:integer, map(xs:string,item()*), item()*) as item()*, $note-fn as function(xs:integer, map(xs:string,item()*), map(xs:string,item()*), xs:integer) as item()*) as item()*
process-score()
Process the MusicXML score as it is parsed.
Params
- score-node as node(): root of MusicXML score
- score-fn as function(map(xs:string,item()*),item()*)asitem()*: function to process accumulated results once whole score is parsed Takes parameters: score object, accumulated parts results
- part-fn as function(xs:integer,map(xs:string,item()*),item()*)asitem()*: function to process accumlated results once whole part is parsed Takes parameters: part number, score object, accumulated measures results
- measure-fn as function(xs:integer,map(xs:string,item()*),item()*)asitem()*: function to process accumulated results once whole measure is parsed Takes parameters: measure number, accumulated notes results
- note-fn as function(xs:integer,map(xs:string,item()*),map(xs:string,item()*),xs:integer)asitem()*: function to process parsed note Takes parameters: part number, note/rest object, score object, divisions per quarter note
Returns
- item()*
declare function this:process-score( $score-node as node(), $score-fn as function(map(xs:string,item()*), item()*) as item()*, $part-fn as function(xs:integer, map(xs:string,item()*), item()*) as item()*, $measure-fn as function(xs:integer, map(xs:string,item()*), item()*) as item()*, $note-fn as function(xs:integer, map(xs:string,item()*), map(xs:string,item()*), xs:integer) as item()* ) as item()* { let $letters as xs:string* := ("A", "B", "C", "D", "E", "F", "G") let $n-measures as xs:integer := max($score-node//measure/@number!xs:integer(.)) cast as xs:integer let $n-parts as xs:integer := count($score-node//part) let $octaves as xs:integer* := $score-node//octave!xs:integer(.) let $min-octave as xs:integer := min($octaves) cast as xs:integer let $max-octave as xs:integer := max($octaves) cast as xs:integer let $score := this:score($n-parts, $n-measures, $min-octave, $max-octave) let $n-octaves as xs:integer := $max-octave - $min-octave + 1 let $parts := ( for $part at $p in $score-node//part let $measures := ( for $measure at $m in $part/measure (: divisions per quarter note :) (: divisions * 4 = length of measure :) let $divisions as xs:integer := 4 * head(($measure/attributes/divisions, $measure/preceding-sibling::measure/attributes/divisions)) cast as xs:integer (: Starts in counts; will convert to fraction of measure :) let $starts as xs:integer* := ( fold-left($measure/note, 0, function($starts as xs:integer*, $note-node as element(note)) as xs:integer* { $starts, if (exists($note-node/duration)) then ( $starts[last()] + xs:integer($note-node/duration) ) else ( $starts[last()] ) } ) ) let $notes := ( for $note-node at $n in $measure/note (: Duration as fraction of measure :) let $duration as xs:numeric := (if (exists($note-node/duration)) then (xs:integer($note-node/duration) div $divisions) else 0) let $octave as xs:numeric := (if (exists($note-node/pitch)) then xs:integer($note-node/pitch/octave) else 0) let $tone as xs:numeric := (if (exists($note-node/pitch/step)) then (for $l at $li in $letters where $l=$note-node/pitch/step return $li) else 0) let $sharpflat as xs:numeric := (if (exists($note-node/pitch/alter)) then xs:integer($note-node/pitch/alter) else 0) let $note as map(xs:string,item()*)? := ( if (exists($note-node/pitch)) then ( if (exists($note-node/duration)) then ( this:note($n, $octave, $tone, $sharpflat, $m, $starts[$n] div $divisions, $duration, exists($note-node/grace)) ) else ( this:note($n, $octave, $tone, $sharpflat, $m, $starts[$n] div $divisions, 0, true()) ) ) else if (exists($note-node/duration)) then ( this:rest($n, $m, $starts[$n], $duration) ) else () ) where exists($note) return ( $note-fn($p, $note, $score, $divisions) ) ) return ( $measure-fn($m, $score, $notes) ) ) return ( $part-fn($p, $score, $measures) ) ) return ( $score-fn($score, $parts) ) }
Function: lines
declare function lines($score-node as node(),
$space as map(xs:string,item()*),
$property-fn as function(xs:integer, map(xs:string,item()*), xs:numeric, xs:numeric) as map(xs:string,item()*)) as map(xs:string,item()*)*
declare function lines($score-node as node(), $space as map(xs:string,item()*), $property-fn as function(xs:integer, map(xs:string,item()*), xs:numeric, xs:numeric) as map(xs:string,item()*)) as map(xs:string,item()*)*
lines()
Comvert the MusicXML score into a set of edges.
Params
- score-node as node(): root of MusicXML score
- space as map(xs:string,item()*): space to fill
- property-fn as function(xs:integer,map(xs:string,item()*),xs:numeric,xs:numeric)asmap(xs:string,item()*): function to calculate additional edge properties Takes parameters: part number, note object, mapped measure length, mapped octave height
Returns
- map(xs:string,item()*)*: a slot for each part containing edges representing each note
declare function this:lines( $score-node as node(), $space as map(xs:string,item()*), $property-fn as function(xs:integer, map(xs:string,item()*), xs:numeric, xs:numeric) as map(xs:string,item()*) ) as map(xs:string,item()*)* { let $min-x := box:min-x($space) let $max-x := box:max-x($space) let $min-y := box:min-y($space) let $max-y := box:max-y($space) return ( this:process-score($score-node, (: score-function :) function( $score as map(xs:string,item()*), $parts as map(xs:string,item()*)* ) as map(xs:string,item()*)* { $parts }, (: part function :) function( $part as xs:integer, $score as map(xs:string,item()*), $edges as map(xs:string,item()*)* ) as map(xs:string,item()*)? { util:log("part "||$part||" n="||count($edges)), if (empty($edges)) then () else slot:slot($edges, map {"id": "part"||$part}) }, (: measure function :) function( $measure as xs:integer, $score as map(xs:string,item()*), $edges as map(xs:string,item()*)* ) as map(xs:string,item()*)* { $edges }, (: note function :) function( $part as xs:integer, $note as map(xs:string,item()*), $score as map(xs:string,item()*), $divisions as xs:integer ) as map(xs:string,item()*)? { (: util:log("note="||util:quote($note)), :) if (this:kind($note)="rest" or this:grace($note)) then () else (: Mapped sizes: x-range=length(measure), y-range=height(octave) :) let $m := this:measure($note) let $measure-length := ($max-x - $min-x) idiv this:measures($score) let $octave-height := ($max-y - $min-y) idiv this:octaves($score) let $x := $min-x + $measure-length * ($m - 1) + this:start($note) * $measure-length let $y := $min-y + $octave-height * (this:pitch($note) - this:min-octave($score)) let $properties := $property-fn($part, $note, $measure-length, $octave-height) return ( edge:edge(point:point($x, $y), point:point($x + this:duration($note) * $measure-length, $y))=> geom:with-properties($properties) ) } ) ) }
Original Source Code
xquery version "3.1"; (:~ : Music: parsing MusicXML, simplisticly : Doesn't really understand a lot but will map pitch and durations as long : as nothing too complicated is going on : : Copyright© Mary Holstege 2022-2023 : CC-BY (https://creativecommons.org/licenses/by/4.0/) : @since August 2022 : @custom:Status Limited :) module namespace this="http://mathling.com/music"; import module namespace errors="http://mathling.com/core/errors" at "../core/errors.xqy"; import module namespace wrapper="http://mathling.com/type/wrapper" at "../types/wrapper.xqy"; import module namespace util="http://mathling.com/core/utilities" at "../core/utilities.xqy"; import module namespace slot="http://mathling.com/type/slot" at "../types/slot.xqy"; import module namespace geom="http://mathling.com/geometric" at "../geo/euclidean.xqy"; import module namespace point="http://mathling.com/geometric/point" at "../geo/point.xqy"; import module namespace edge="http://mathling.com/geometric/edge" at "../geo/edge.xqy"; import module namespace box="http://mathling.com/geometric/rectangle" at "../geo/rectangle.xqy"; declare namespace svg="http://www.w3.org/2000/svg"; declare namespace map="http://www.w3.org/2005/xpath-functions/map"; declare namespace array="http://www.w3.org/2005/xpath-functions/array"; declare namespace math="http://www.w3.org/2005/xpath-functions/math"; declare namespace saxon="http://saxon.sf.net/"; declare boundary-space preserve; declare function this:note( $n as xs:integer, $octave as xs:numeric, $tone as xs:numeric, $sharpflat as xs:numeric, $measure as xs:integer, $start as xs:numeric, $duration as xs:numeric, $grace as xs:boolean ) as map(xs:string,item()*) { map { "kind": "note", "n": $n, "octave": $octave, "tone": $tone, "sharpflat": $sharpflat, "measure": $measure, "start": $start, "duration": $duration, "grace": $grace } }; declare function this:kind($item as map(xs:string,item()*)) as xs:string { $item("kind") }; declare function this:note($item as map(xs:string,item()*)) as xs:integer { $item("n") }; declare function this:octave($note as map(xs:string,item()*)) as xs:numeric { $note("octave") }; declare function this:tone($note as map(xs:string,item()*)) as xs:numeric { $note("tone") }; declare function this:sharpflat($note as map(xs:string,item()*)) as xs:numeric { $note("sharpflat") }; declare function this:measure($note as map(xs:string,item()*)) as xs:integer { $note("measure") }; declare function this:start($note as map(xs:string,item()*)) as xs:numeric { $note("start") }; declare function this:duration($note as map(xs:string,item()*)) as xs:numeric { $note("duration") }; declare function this:grace($note as map(xs:string,item()*)) as xs:boolean { $note("grace") }; declare function this:pitch($note as map(xs:string,item()*)) as xs:numeric { this:octave($note) + this:tone($note) div 8 + this:sharpflat($note) div 16 }; declare function this:note( $n as xs:integer, $octave as xs:numeric, $tone as xs:numeric, $sharpflat as xs:numeric, $measure as xs:integer, $start as xs:numeric, $duration as xs:numeric ) as map(xs:string,item()*) { this:note($n, $octave, $tone, $sharpflat, $measure, $start, $duration, false()) }; declare function this:rest( $n as xs:integer, $measure as xs:integer, $start as xs:numeric, $duration as xs:numeric ) as map(xs:string,item()*) { map { "kind": "rest", "n": $n, "measure": $measure, "start": $start, "duration": $duration } }; declare function this:measure( $m as xs:integer, $notes as map(xs:string,item()*)* ) as map(xs:string,item()*) { map { "kind": "score", "measure": $m, "notes": $notes } }; declare function this:notes($measure as map(xs:string,item()*)) as map(xs:string,item()*)* { $measure("notes") }; declare function this:part( $p as xs:integer, $measure-list as map(xs:string,item()*)* ) as map(xs:string,item()*) { map { "kind": "part", "p": $p, "measure-list": $measure-list } }; declare function this:part($part as map(xs:string,item()*)) as xs:integer { $part("p") }; declare function this:measure-list($part as map(xs:string,item()*)) as map(xs:string,item()*)* { $part("measure-list") }; declare function this:score( $n-parts as xs:integer, $n-measures as xs:integer, $min-octave as xs:integer, $max-octave as xs:integer ) as map(xs:string,item()*) { map { "kind": "score", "parts": $n-parts, "measures": $n-measures, "min-octave": $min-octave, "max-octave": $max-octave } }; declare function this:measures($score as map(xs:string,item()*)) as xs:integer { $score("measures") }; declare function this:parts($score as map(xs:string,item()*)) as xs:integer { $score("parts") }; declare function this:min-octave($score as map(xs:string,item()*)) as xs:integer { $score("min-octave") }; declare function this:max-octave($score as map(xs:string,item()*)) as xs:integer { $score("max-octave") }; declare function this:octaves($score as map(xs:string,item()*)) as xs:integer { this:max-octave($score) - this:min-octave($score) + 1 }; declare function this:part-list($score as map(xs:string,item()*)) as map(xs:string,item()*)* { $score("part-list") }; declare function this:part-list($score as map(xs:string,item()*), $part-list as map(xs:string,item()*)*) as map(xs:string,item()*) { $score=>map:put("part-list", $part-list) }; (:~ : parse() : Parse the MusicXML score. : : @param $score-node: root of MusicXML score : @return An expanded score object, which includes part objects :) declare function this:parse( $score-node as node() ) as map(xs:string,item()*) { let $letters as xs:string* := ("A", "B", "C", "D", "E", "F", "G") let $n-measures as xs:integer := max($score-node//measure/@number!xs:integer(.)) cast as xs:integer let $n-parts as xs:integer := count($score-node//part) let $octaves as xs:integer* := $score-node//octave!xs:integer(.) let $min-octave as xs:integer := min($octaves) cast as xs:integer let $max-octave as xs:integer := max($octaves) cast as xs:integer let $score := this:score($n-parts, $n-measures, $min-octave, $max-octave) let $n-octaves as xs:integer := $max-octave - $min-octave + 1 let $parts := ( for $part at $p in $score-node//part let $measures := ( for $measure at $m in $part//measure (: divisions per quarter note :) (: divisions * 4 = length of measure :) let $divisions as xs:integer := 4 * head(($measure/attributes/divisions, $measure/preceding-sibling::measure/attributes/divisions)) cast as xs:integer (: Starts in counts; will convert to fraction of measure :) let $starts as xs:integer* := ( fold-left($measure/note, 0, function($starts as xs:integer*, $note-node as element(note)) as xs:integer* { $starts, if (exists($note-node/duration)) then ( $starts[last()] + xs:integer($note-node/duration) ) else ( $starts[last()] ) } ) ) let $notes := ( for $note-node at $n in $measure/note (: Duration as fraction of measure :) let $duration as xs:numeric := (if (exists($note-node/duration)) then (xs:integer($note-node/duration) div $divisions) else 0) let $octave as xs:numeric := (if (exists($note-node/pitch)) then xs:integer($note-node/pitch/octave) else 0) let $tone as xs:numeric := (if (exists($note-node/pitch/step)) then (for $l at $li in $letters where $l=$note-node/pitch/step return $li) else 0) let $sharpflat as xs:numeric := (if (exists($note-node/pitch/alter)) then xs:integer($note-node/pitch/alter) else 0) let $note as map(xs:string,item()*)? := ( if (exists($note-node/pitch)) then ( if (exists($note-node/duration)) then ( this:note($n, $octave, $tone, $sharpflat, $m, $starts[$n] div $divisions, $duration, exists($note-node/grace)) ) else ( this:note($n, $octave, $tone, $sharpflat, $m, $starts[$n] div $divisions, 0, true()) ) ) else if (exists($note-node/duration)) then ( this:rest($n, $m, $starts[$n], $duration) ) else () ) return ( $note ) ) return ( this:measure($m, $notes) ) ) return ( this:part($p, $measures) ) ) return ( $score=>this:part-list($parts) ) }; (:~ : process-score() : Process the MusicXML score as it is parsed. : : @param $score-node: root of MusicXML score : @param $score-fn: function to process accumulated results once whole score is parsed : Takes parameters: score object, accumulated parts results : @param $part-fn: function to process accumlated results once whole part is parsed : Takes parameters: part number, score object, accumulated measures results : @param $measure-fn: function to process accumulated results once whole measure is parsed : Takes parameters: measure number, accumulated notes results : @param $note-fn: function to process parsed note : Takes parameters: part number, note/rest object, score object, divisions per quarter note :) declare function this:process-score( $score-node as node(), $score-fn as function(map(xs:string,item()*), item()*) as item()*, $part-fn as function(xs:integer, map(xs:string,item()*), item()*) as item()*, $measure-fn as function(xs:integer, map(xs:string,item()*), item()*) as item()*, $note-fn as function(xs:integer, map(xs:string,item()*), map(xs:string,item()*), xs:integer) as item()* ) as item()* { let $letters as xs:string* := ("A", "B", "C", "D", "E", "F", "G") let $n-measures as xs:integer := max($score-node//measure/@number!xs:integer(.)) cast as xs:integer let $n-parts as xs:integer := count($score-node//part) let $octaves as xs:integer* := $score-node//octave!xs:integer(.) let $min-octave as xs:integer := min($octaves) cast as xs:integer let $max-octave as xs:integer := max($octaves) cast as xs:integer let $score := this:score($n-parts, $n-measures, $min-octave, $max-octave) let $n-octaves as xs:integer := $max-octave - $min-octave + 1 let $parts := ( for $part at $p in $score-node//part let $measures := ( for $measure at $m in $part/measure (: divisions per quarter note :) (: divisions * 4 = length of measure :) let $divisions as xs:integer := 4 * head(($measure/attributes/divisions, $measure/preceding-sibling::measure/attributes/divisions)) cast as xs:integer (: Starts in counts; will convert to fraction of measure :) let $starts as xs:integer* := ( fold-left($measure/note, 0, function($starts as xs:integer*, $note-node as element(note)) as xs:integer* { $starts, if (exists($note-node/duration)) then ( $starts[last()] + xs:integer($note-node/duration) ) else ( $starts[last()] ) } ) ) let $notes := ( for $note-node at $n in $measure/note (: Duration as fraction of measure :) let $duration as xs:numeric := (if (exists($note-node/duration)) then (xs:integer($note-node/duration) div $divisions) else 0) let $octave as xs:numeric := (if (exists($note-node/pitch)) then xs:integer($note-node/pitch/octave) else 0) let $tone as xs:numeric := (if (exists($note-node/pitch/step)) then (for $l at $li in $letters where $l=$note-node/pitch/step return $li) else 0) let $sharpflat as xs:numeric := (if (exists($note-node/pitch/alter)) then xs:integer($note-node/pitch/alter) else 0) let $note as map(xs:string,item()*)? := ( if (exists($note-node/pitch)) then ( if (exists($note-node/duration)) then ( this:note($n, $octave, $tone, $sharpflat, $m, $starts[$n] div $divisions, $duration, exists($note-node/grace)) ) else ( this:note($n, $octave, $tone, $sharpflat, $m, $starts[$n] div $divisions, 0, true()) ) ) else if (exists($note-node/duration)) then ( this:rest($n, $m, $starts[$n], $duration) ) else () ) where exists($note) return ( $note-fn($p, $note, $score, $divisions) ) ) return ( $measure-fn($m, $score, $notes) ) ) return ( $part-fn($p, $score, $measures) ) ) return ( $score-fn($score, $parts) ) }; (:~ : lines() : Comvert the MusicXML score into a set of edges. : : @param $score-node: root of MusicXML score : @param $space: space to fill : @param $property-fn: function to calculate additional edge properties : Takes parameters: part number, note object, mapped measure length, mapped octave height : @return a slot for each part containing edges representing each note :) declare function this:lines( $score-node as node(), $space as map(xs:string,item()*), $property-fn as function(xs:integer, map(xs:string,item()*), xs:numeric, xs:numeric) as map(xs:string,item()*) ) as map(xs:string,item()*)* { let $min-x := box:min-x($space) let $max-x := box:max-x($space) let $min-y := box:min-y($space) let $max-y := box:max-y($space) return ( this:process-score($score-node, (: score-function :) function( $score as map(xs:string,item()*), $parts as map(xs:string,item()*)* ) as map(xs:string,item()*)* { $parts }, (: part function :) function( $part as xs:integer, $score as map(xs:string,item()*), $edges as map(xs:string,item()*)* ) as map(xs:string,item()*)? { util:log("part "||$part||" n="||count($edges)), if (empty($edges)) then () else slot:slot($edges, map {"id": "part"||$part}) }, (: measure function :) function( $measure as xs:integer, $score as map(xs:string,item()*), $edges as map(xs:string,item()*)* ) as map(xs:string,item()*)* { $edges }, (: note function :) function( $part as xs:integer, $note as map(xs:string,item()*), $score as map(xs:string,item()*), $divisions as xs:integer ) as map(xs:string,item()*)? { (: util:log("note="||util:quote($note)), :) if (this:kind($note)="rest" or this:grace($note)) then () else (: Mapped sizes: x-range=length(measure), y-range=height(octave) :) let $m := this:measure($note) let $measure-length := ($max-x - $min-x) idiv this:measures($score) let $octave-height := ($max-y - $min-y) idiv this:octaves($score) let $x := $min-x + $measure-length * ($m - 1) + this:start($note) * $measure-length let $y := $min-y + $octave-height * (this:pitch($note) - this:min-octave($score)) let $properties := $property-fn($part, $note, $measure-length, $octave-height) return ( edge:edge(point:point($x, $y), point:point($x + this:duration($note) * $measure-length, $y))=> geom:with-properties($properties) ) } ) ) };