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/)

August 2022
Status: Limited

Imports

http://mathling.com/geometric/edge
import 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()*)

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

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

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

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

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

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

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

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

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

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

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()*)

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()*)

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()*)

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()*)*

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()*)

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

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()*)*

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()*)

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

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

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

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

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

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()*)*

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()*)

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()*)


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()*


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()*)*


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)
        )
      }
    )
  )
};