http://mathling.com/music/noise  library module

http://mathling.com/music/noise


Music: Using music as a noise-generating function.
All noise functions are 2D.
Input points will be modded to [0, sample] range, so scale to that range
if you want to avoid mod-based repetitions.

Copyright© Mary Holstege 2023

CC-BY (https://creativecommons.org/licenses/by/4.0/)

June 2023
Status: Bleeding edge

Imports

http://mathling.com/noise/annotations
import module namespace ann="http://mathling.com/noise/annotations"
       at "../noise/annotations.xqy"
http://mathling.com/music
import module namespace music="http://mathling.com/music"
       at "../music/music.xqy"
http://mathling.com/core/utilities
import module namespace util="http://mathling.com/core/utilities"
       at "../core/utilities.xqy"
http://mathling.com/core/vector
import module namespace v="http://mathling.com/core/vector"
       at "../core/vector.xqy"
http://mathling.com/geometric/rectangle
import module namespace box="http://mathling.com/geometric/rectangle"
       at "../geo/rectangle.xqy"
http://mathling.com/image/matrix
import module namespace matrix="http://mathling.com/image/matrix"
       at "../image/matrix.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: context
declare function context($sources as xs:string*, $sample as xs:integer, $aligned as xs:boolean) as map(xs:string,item()*)


context()
Create context for the music noise.

Params
  • sources as xs:string*: locations of source MusicXML scores
  • sample as xs:integer: how large a sample grid to use to map the score onto
  • aligned as xs:boolean: should parts be aligned or sequential? (default=true())
Returns
  • map(xs:string,item()*): context object
declare function this:context(
  $sources as xs:string*,
  $sample as xs:integer,
  $aligned as xs:boolean
) as map(xs:string,item()*)
{
  map {
    "sources": $sources,
    "sample": $sample,
    "aligned": $aligned
  }=>this:recalculate()
}

Function: context
declare function context($sources as xs:string*, $sample as xs:integer) as map(xs:string,item()*)

Params
  • sources as xs:string*
  • sample as xs:integer
Returns
  • map(xs:string,item()*)
declare function this:context(
  $sources as xs:string*,
  $sample as xs:integer
) as map(xs:string,item()*)
{
  map {
    "sources": $sources,
    "sample": $sample,
    "aligned": true()
  }=>this:recalculate()
}

Function: sources
declare function sources($context as map(xs:string,item()*), $sources as xs:string*) as map(xs:string,item()*)


sources()
Setter for sources. Will recalculate matrix.

Params
  • context as map(xs:string,item()*): context object
  • sources as xs:string*: sequence of locations of MusicXML scores
Returns
  • map(xs:string,item()*): context object with new sources and recalculated matrix
declare function this:sources(
  $context as map(xs:string,item()*),
  $sources as xs:string*
) as map(xs:string,item()*)
{
  $context=>map:put("sources", $sources)=>this:recalculate()
}

Function: sources
declare function sources($context as map(xs:string,item()*)) as xs:string*


sources()
Accessor for sources.

Params
  • context as map(xs:string,item()*): context object
Returns
  • xs:string*: sequence of locations of MusicXML scores
declare function this:sources(
  $context as map(xs:string,item()*)
) as xs:string*
{
  $context("sources")
}

Function: sample
declare function sample($context as map(xs:string,item()*), $sample as xs:double) as map(xs:string,item()*)


sample()
Setter for grid size. Will recalculate matrix.

Params
  • context as map(xs:string,item()*): context object
  • sample as xs:double: size of sample grid to map music to
Returns
  • map(xs:string,item()*): context object with sample size and recalculated matrix
declare function this:sample(
  $context as map(xs:string,item()*),
  $sample as xs:double
) as map(xs:string,item()*)
{
  $context=>map:put("sample", $sample)=>this:recalculate()
}

Function: sample
declare function sample($context as map(xs:string,item()*)) as xs:double


sample()
Accessor for grid size.

Params
  • context as map(xs:string,item()*): context object
Returns
  • xs:double: size of sample grid to map music to
declare function this:sample(
  $context as map(xs:string,item()*)
) as xs:double
{
  $context("sample")
}

Function: aligned
declare function aligned($context as map(xs:string,item()*), $aligned as xs:boolean) as map(xs:string,item()*)


aligned()
Setter for alignement flag. Will recalculate matrix.

Params
  • context as map(xs:string,item()*): context object
  • aligned as xs:boolean
Returns
  • map(xs:string,item()*): context object with new alignment flag and recalculated matrix
declare function this:aligned(
  $context as map(xs:string,item()*),
  $aligned as xs:boolean
) as map(xs:string,item()*)
{
  $context=>map:put("aligned", $aligned)=>this:recalculate()
}

Function: aligned
declare function aligned($context as map(xs:string,item()*)) as xs:boolean


aligned()
Accessor for alignment flag.

Params
  • context as map(xs:string,item()*): context object
Returns
  • xs:boolean: whether measures are aligned between parts, or mapped from parts sequentially
declare function this:aligned(
  $context as map(xs:string,item()*)
) as xs:boolean
{
  $context("aligned")
}

Function: matrix
declare function matrix($context as map(xs:string,item()*)) as map(*)


matrix()
Accessor for mapped measure matrix.

Params
  • context as map(xs:string,item()*): context object
Returns
  • map(*): mapped matrix of measures
declare function this:matrix(
  $context as map(xs:string,item()*)
) as map(*)
{
  $context("matrix")
}

Function: scores
declare function scores($context as map(xs:string,item()*)) as map(xs:string,item()*)*


scores()
Accessor for parsed MusicXML scores.

Params
  • context as map(xs:string,item()*): context object
Returns
  • map(xs:string,item()*)*: parsed scores
declare function this:scores(
  $context as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
  $context("scores")
}

Function: noise
declare function noise($context as map(xs:string,item()*)) as map(*)


noise()
Get the default noise function whose value is based on sum of pitches
for location in score mapped to input point.

Params
  • context as map(xs:string,item()*): context object
Returns
  • map(*): callable noise function (point form)
declare %art:noise function this:noise(
  $context as map(xs:string,item()*)
) as map(*)
{
  ann:f("music:noise",
    function($point as map(xs:string,item()*)) as xs:double {
      this:pitch(point:pcoordinates($point), $context)
    },
    $context=>map:remove("matrix")=>map:remove("scores")
  )
}

Function: noise-vector
declare function noise-vector($context as map(xs:string,item()*)) as map(*)


noise-vector()
Get the default noise function whose value is based on sum of pitches
for location in score mapped to input point.

Params
  • context as map(xs:string,item()*): context object
Returns
  • map(*): callable noise function (vector form)
declare %art:noise function this:noise-vector(
  $context as map(xs:string,item()*)
) as map(*)
{
  ann:fv("music:noise",
    function($point as xs:double*) as xs:double {
      this:pitch($point, $context)
    },
    $context=>map:remove("matrix")=>map:remove("scores")
  )
}

Function: noise-density
declare function noise-density($context as map(xs:string,item()*)) as map(*)


noise-density()
Get the default noise function whose value is based on the count of notes
active at location in score mapped to input point.

Params
  • context as map(xs:string,item()*): context object
Returns
  • map(*): callable noise function (point form)
declare %art:noise function this:noise-density(
  $context as map(xs:string,item()*)
) as map(*)
{
  ann:f("music:noise",
    function($point as map(xs:string,item()*)) as xs:double {
      this:density(point:pcoordinates($point), $context)
    },
    $context=>map:remove("matrix")=>map:remove("scores")
  )
}

Function: noise-density-vector
declare function noise-density-vector($context as map(xs:string,item()*)) as map(*)


noise-density-vector()
Get the default noise function whose value is based on the count of notes
active at location in score mapped to input point.

Params
  • context as map(xs:string,item()*): context object
Returns
  • map(*): callable noise function (vector form)
declare %art:noise function this:noise-density-vector(
  $context as map(xs:string,item()*)
) as map(*)
{
  ann:fv("music:noise",
    function($point as xs:double*) as xs:double {
      this:density($point, $context)
    },
    $context=>map:remove("matrix")=>map:remove("scores")
  )
}

Function: noise-extreme
declare function noise-extreme($context as map(xs:string,item()*)) as map(*)


noise-extreme()
Get the default noise function whose value is based on the most extreme pitch
of notes active at location in score mapped to input point.

Params
  • context as map(xs:string,item()*): context object
Returns
  • map(*): callable noise function (point form)
declare %art:noise function this:noise-extreme(
  $context as map(xs:string,item()*)
) as map(*)
{
  ann:f("music:noise",
    function($point as map(xs:string,item()*)) as xs:double {
      this:extreme(point:pcoordinates($point), $context)
    },
    $context=>map:remove("matrix")=>map:remove("scores")
  )
}

Function: noise-extreme-vector
declare function noise-extreme-vector($context as map(xs:string,item()*)) as map(*)


noise-extreme-vector()
Get the default noise function whose value is based on the most extreme pitch
of notes active at location in score mapped to input point.

Params
  • context as map(xs:string,item()*): context object
Returns
  • map(*): callable noise function (vector form)
declare %art:noise function this:noise-extreme-vector(
  $context as map(xs:string,item()*)
) as map(*)
{
  ann:fv("music:noise",
    function($point as xs:double*) as xs:double {
      this:extreme($point, $context)
    },
    $context=>map:remove("matrix")=>map:remove("scores")
  )
}

Original Source Code

xquery version "3.1";
(:~
 : Music: Using music as a noise-generating function.
 : All noise functions are 2D.
 : Input points will be modded to [0, sample] range, so scale to that range
 : if you want to avoid mod-based repetitions.
 :
 : Copyright© Mary Holstege 2023
 :
 : CC-BY (https://creativecommons.org/licenses/by/4.0/)
 : @since June 2023
 : @custom:Status Bleeding edge
 :)
module namespace this="http://mathling.com/music/noise";

import module namespace errors="http://mathling.com/core/errors"
       at "../core/errors.xqy";
import module namespace util="http://mathling.com/core/utilities"
       at "../core/utilities.xqy";
import module namespace ann="http://mathling.com/noise/annotations"
       at "../noise/annotations.xqy";
import module namespace v="http://mathling.com/core/vector"
       at "../core/vector.xqy";
import module namespace matrix="http://mathling.com/image/matrix"
       at "../image/matrix.xqy";
import module namespace point="http://mathling.com/geometric/point"
       at "../geo/point.xqy";
import module namespace box="http://mathling.com/geometric/rectangle"
       at "../geo/rectangle.xqy";
import module namespace music="http://mathling.com/music"
       at "../music/music.xqy";

declare namespace art="http://mathling.com/art";

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/";

(:~
 : context()
 : Create context for the music noise.
 :
 : @param $sources: locations of source MusicXML scores
 : @param $sample: how large a sample grid to use to map the score onto
 : @param $aligned: should parts be aligned or sequential? (default=true())
 : @return context object 
 :)
declare function this:context(
  $sources as xs:string*,
  $sample as xs:integer,
  $aligned as xs:boolean
) as map(xs:string,item()*)
{
  map {
    "sources": $sources,
    "sample": $sample,
    "aligned": $aligned
  }=>this:recalculate()
};

declare function this:context(
  $sources as xs:string*,
  $sample as xs:integer
) as map(xs:string,item()*)
{
  map {
    "sources": $sources,
    "sample": $sample,
    "aligned": true()
  }=>this:recalculate()
};

(:~
 : recalulate()
 : Recalculate the music matrix for the context.
 : @param $context: context object
 : @return context object with matrix and parsed scores
 :)
declare %private function this:recalculate(
  $context as map(xs:string,item()*)
) as map(xs:string,item()*)
{
  let $sample := this:sample($context)
  let $sources := this:sources($context)
  let $aligned := this:aligned($context)
  let $space := box:box(1,1,$sample,$sample)
  let $width := box:width($space)
  let $height := box:width($space)
  let $scores := for $source in $sources return music:parse( doc($source) )
  let $m := sum($scores!music:measures(.))
  let $p := sum($scores!music:parts(.))
  let $k := (floor(math:sqrt($p * $m)) cast as xs:integer)
  let $all-measures := (
    for $score at $s in $scores
    return (
      if ($aligned) then (
        let $parts := music:part-list($score)
        for $measure in 1 to music:measures($score)
        for $part in $parts
        return (music:measure-list($part)[$measure])!map:put(., "score", $s)
      ) else (
        for $part in music:part-list($score)
        return music:measure-list($part)!map:put(., "score", $s)
      )
    )
  )
  let $limit := $k * ($p * $m idiv $k)
  let $matrix := matrix:array($p * $m idiv $k, $k, ($all-measures, $all-measures)[position() <= $limit])
  return (
    $context=>map:put("matrix", $matrix)=>map:put("scores", $scores)
  )
};

(:~
 : sources()
 : Setter for sources. Will recalculate matrix.
 :
 : @param $context: context object
 : @param $sources: sequence of locations of MusicXML scores
 : @return context object with new sources and recalculated matrix
 :)
declare function this:sources(
  $context as map(xs:string,item()*),
  $sources as xs:string*
) as map(xs:string,item()*)
{
  $context=>map:put("sources", $sources)=>this:recalculate()
};

(:~
 : sources()
 : Accessor for sources.
 :
 : @param $context: context object
 : @return sequence of locations of MusicXML scores
 :)
declare function this:sources(
  $context as map(xs:string,item()*)
) as xs:string*
{
  $context("sources")
};

(:~
 : sample()
 : Setter for grid size. Will recalculate matrix.
 :
 : @param $context: context object
 : @param $sample: size of sample grid to map music to
 : @return context object with sample size and recalculated matrix
 :)
declare function this:sample(
  $context as map(xs:string,item()*),
  $sample as xs:double
) as map(xs:string,item()*)
{
  $context=>map:put("sample", $sample)=>this:recalculate()
};

(:~
 : sample()
 : Accessor for grid size.
 :
 : @param $context: context object
 : @return size of sample grid to map music to
 :)
declare function this:sample(
  $context as map(xs:string,item()*)
) as xs:double
{
  $context("sample")
};

(:~
 : aligned()
 : Setter for alignement flag. Will recalculate matrix.
 :
 : @param $context: context object
 : @param $sources: whether to align measures between parts, or map measures from parts sequentially
 : @return context object with new alignment flag and recalculated matrix
 :)
declare function this:aligned(
  $context as map(xs:string,item()*),
  $aligned as xs:boolean
) as map(xs:string,item()*)
{
  $context=>map:put("aligned", $aligned)=>this:recalculate()
};

(:~
 : aligned()
 : Accessor for alignment flag.
 :
 : @param $context: context object
 : @return whether measures are aligned between parts, or mapped from parts sequentially
 :)
declare function this:aligned(
  $context as map(xs:string,item()*)
) as xs:boolean
{
  $context("aligned")
};

(:~
 : matrix()
 : Accessor for mapped measure matrix.
 :
 : @param $context: context object
 : @return mapped matrix of measures
 :)
declare function this:matrix(
  $context as map(xs:string,item()*)
) as map(*)
{
  $context("matrix")
};

(:~
 : scores()
 : Accessor for parsed MusicXML scores.
 :
 : @param $context: context object
 : @return parsed scores
 :)
declare function this:scores(
  $context as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
  $context("scores")
};

(:~
 : notes()
 : Get the set of notes in play at the given offset in the given measure.
 : 
 : @param $measure: index of measure in full list of measures
 : @param $offset: position in the measure (a fraction between 0 and 1)
 : @return the set of non-grace notes and rests active at that point in the measure
 :)
declare %private function this:notes(
  $measure as map(xs:string,item()*),
  $offset as xs:double
) as map(xs:string,item()*)*
{
  music:notes($measure)[
    (music:kind(.)="rest" or not(music:grace(.))) and
    util:twixt(music:start(.), $offset, music:start(.)+music:duration(.))
  ]
};

(:~
 : score()
 : Return the score index for the given measure.
 :
 : @param $measure: measure object
 : @return index of the score object that contributed this measure
 :)
declare %private function this:score(
  $measure as map(xs:string,item()*)
) as xs:integer
{
  $measure("score")
};

(:~
 : noise()
 : Get the default noise function whose value is based on sum of pitches
 : for location in score mapped to input point.
 :
 : @param $context: context object
 : @return callable noise function (point form)
 :)
declare %art:noise function this:noise(
  $context as map(xs:string,item()*)
) as map(*)
{
  ann:f("music:noise",
    function($point as map(xs:string,item()*)) as xs:double {
      this:pitch(point:pcoordinates($point), $context)
    },
    $context=>map:remove("matrix")=>map:remove("scores")
  )
};

(:~
 : noise-vector()
 : Get the default noise function whose value is based on sum of pitches
 : for location in score mapped to input point.
 :
 : @param $context: context object
 : @return callable noise function (vector form)
 :)
declare %art:noise function this:noise-vector(
  $context as map(xs:string,item()*)
) as map(*)
{
  ann:fv("music:noise",
    function($point as xs:double*) as xs:double {
      this:pitch($point, $context)
    },
    $context=>map:remove("matrix")=>map:remove("scores")
  )
};

(:~
 : noise-density()
 : Get the default noise function whose value is based on the count of notes
 : active at location in score mapped to input point.
 :
 : @param $context: context object
 : @return callable noise function (point form)
 :)
declare %art:noise function this:noise-density(
  $context as map(xs:string,item()*)
) as map(*)
{
  ann:f("music:noise",
    function($point as map(xs:string,item()*)) as xs:double {
      this:density(point:pcoordinates($point), $context)
    },
    $context=>map:remove("matrix")=>map:remove("scores")
  )
};

(:~
 : noise-density-vector()
 : Get the default noise function whose value is based on the count of notes
 : active at location in score mapped to input point.
 :
 : @param $context: context object
 : @return callable noise function (vector form)
 :)
declare %art:noise function this:noise-density-vector(
  $context as map(xs:string,item()*)
) as map(*)
{
  ann:fv("music:noise",
    function($point as xs:double*) as xs:double {
      this:density($point, $context)
    },
    $context=>map:remove("matrix")=>map:remove("scores")
  )
};

(:~
 : noise-extreme()
 : Get the default noise function whose value is based on the most extreme pitch
 : of notes active at location in score mapped to input point.
 :
 : @param $context: context object
 : @return callable noise function (point form)
 :)
declare %art:noise function this:noise-extreme(
  $context as map(xs:string,item()*)
) as map(*)
{
  ann:f("music:noise",
    function($point as map(xs:string,item()*)) as xs:double {
      this:extreme(point:pcoordinates($point), $context)
    },
    $context=>map:remove("matrix")=>map:remove("scores")
  )
};

(:~
 : noise-extreme-vector()
 : Get the default noise function whose value is based on the most extreme pitch
 : of notes active at location in score mapped to input point.
 :
 : @param $context: context object
 : @return callable noise function (vector form)
 :)
declare %art:noise function this:noise-extreme-vector(
  $context as map(xs:string,item()*)
) as map(*)
{
  ann:fv("music:noise",
    function($point as xs:double*) as xs:double {
      this:extreme($point, $context)
    },
    $context=>map:remove("matrix")=>map:remove("scores")
  )
};

(:~
 : pitch()
 : Workhorse for default noise function.
 :
 : @param $point: point to map to noise value (vector)
 : @param $context: context object
 : @return noise value based on sum of pitches of notes at mapped location in score
 :)
declare %private function this:pitch(
  $point as xs:double*,
  $context as map(xs:string,item()*)
) as xs:double
{
  let $sample := this:sample($context)
  let $matrix := this:matrix($context)
  let $scores := this:scores($context)
  let $rows := matrix:rows($matrix)
  let $columns := matrix:columns($matrix)
  let $xf := util:mix(1, $columns, (v:px($point) mod $sample) div $sample)
  let $x := floor($xf) cast as xs:integer
  let $y := util:intmix(1, $rows, (v:py($point) mod $sample) div $sample)
  let $measure := $matrix=>matrix:get($y, $x)
  let $m := $measure=>music:measure()
  let $offset := $xf - $x
  let $notes := this:notes($measure, $offset)
  let $n := count($notes)
  return (
    sum(
      for $note in $notes
      let $score := $scores[$measure("score")]
      return (
        util:mix(-1.0 div $n, 1.0 div $n,
          if (music:kind($note)="rest") then 0
          else (music:pitch($note) - music:min-octave($score)) div music:octaves($score)
        )
      )
    ) 
  )
};

(:~
 : density()
 : Workhorse for density noise function.
 :
 : @param $point: point to map to noise value (vector)
 : @param $context: context object
 : @return noise value based on number notes at mapped location in score
 :)
declare %private function this:density(
  $point as xs:double*,
  $context as map(xs:string,item()*)
) as xs:double
{
  let $sample := this:sample($context)
  let $matrix := this:matrix($context)
  let $rows := matrix:rows($matrix)
  let $columns := matrix:columns($matrix)
  let $xf := util:mix(1, $columns, (v:px($point) mod $sample) div $sample)
  let $x := floor($xf) cast as xs:integer
  let $y := util:intmix(1, $rows, (v:py($point) mod $sample) div $sample)
  let $measure := $matrix=>matrix:get($y, $x)
  let $m := $measure=>music:measure()
  let $offset := $xf - $x
  let $notes := this:notes($measure, $offset)
  let $n := count($notes)
  return (
    sum(
      for $note in $notes return (
        util:mix(-1.0 div $n, 1.0 div $n,
          if (music:kind($note)="rest") then 0
          else music:duration($note)
        )
      )
    ) 
  )
};

(:~
 : extreme()
 : Workhorse for default noise function.
 :
 : @param $point: point to map to noise value (vector)
 : @param $context: context object
 : @return noise value based on most extreme pitch of notes at mapped location in score
 :)
declare %private function this:extreme(
  $point as xs:double*,
  $context as map(xs:string,item()*)
) as xs:double
{
  let $sample := this:sample($context)
  let $matrix := this:matrix($context)
  let $scores := this:scores($context)
  let $rows := matrix:rows($matrix)
  let $columns := matrix:columns($matrix)
  let $xf := util:mix(1, $columns, (v:px($point) mod $sample) div $sample)
  let $x := floor($xf) cast as xs:integer
  let $y := util:intmix(1, $rows, (v:py($point) mod $sample) div $sample)
  let $measure := $matrix=>matrix:get($y, $x)
  let $m := $measure=>music:measure()
  let $offset := $xf - $x
  let $notes := this:notes($measure, $offset)
  let $n := count($notes)
  return (
    if ($n = 0) then 0 else
    util:extreme(
      for $note in $notes
      let $score := $scores[$measure("score")]
      return (
        util:mix(-1.0, 1.0,
          if (music:kind($note)="rest") then 0
          else (music:pitch($note) - music:min-octave($score)) div music:octaves($score)
        )
      )
    )
  )
};