Log(music) with Undertone

Leave a comment

[This blog was moved here]

christmas-musical

Whether you’re a physicist, biologist, economist, data scientist, whatever, the chances are you’ll meet the Markov chains sooner or later. And today we’ll try them on… music!

If you’re not up to the math part, feel free to skip it.

Math

The system states changes are represented with a help of transition matrix, describing the probabilities of possible transitions. This matrix is a key to determine the state of the model after n steps.

The math behind that is very straightforward – we need just a matrix power. Oh, wait – what if it is fractional, for example, when representing time? There’re many ways to compute it, let’s take a look at some of them.

1. Eigen vectors

This method is based on properties of eigen values decomposition:

V - eigen vectors
A * v_j = \lambda_j * v_j
A * V = V * D, where D = diag(\lambda_1, ..., \lambda_n)
A^n = V * (D ^ n) * V^{-1}

That’s actually how Matlab mpower function looks like:

 1: [V,D] = eig(M)
 2: An = V * (D .^ n) * inv(V) 

We can take advantage of Math.NET matrix decompositions to implement this method:

1: open namespaces, define constants
2: 
3: /// Matrix power: eigen values decomposition method
4: /// M^p = V * (D .^ p) / V
5: let mpowEig (m: SparseMatrix) p =
6:     let evd = m.Evd()
7:     let v, d = evd.EigenVectors(), evd.D()
8:     mapInPlace (fun x -> x ** p) d
9:     v * d * v.Inverse()

What are the pros/cons?

+ the method is rather efficient
inverse matrix V might not exist 1

A = \begin{bmatrix} 0.78&0.2&0.12\\ 0&0.78&0.32\\0&0&0.78 \end{bmatrix}
V = \begin{bmatrix} 1.0000&-1.0000\\ 0.0000&0.0000 \end{bmatrix}

some matrices cannot be diagonalised:

A = \begin{bmatrix} 3&2\\0&3 \end{bmatrix}

2. Generator matrices

If we can find a matrix G, such as A = exp(G), then (remember series?)

A^x = exp(x*G) = I + (x*G) + (x*G)^2/2! + (x*G)^3/3! + ...

We are interested in finding a generator G with zero row sums and nonnegative off-diagonal entries. So we’ll use Mercator’s series:

log(1+x) = x - x^2/2 + x^3/3 - x^4/4 + ...

In matrix case it’s

G = log(I + (A-I)) = (A-I) - (A-I)^2/2 + (A-I)^3/3 - ...

The common concern is that the series methods are not very efficient and may be not accurate enough. It’s easy to compare the methods by computing the squared error between the actual and expected values (say, compute (A^{1/3})^3 )

Another problem is that a generator matrix (and power too) might have negative elements – not really what you want when dealing with probabilities. We can make them non-negative and keep the rows sums equal to zero… but the power properties won’t be preserved. For example, you can find out that A^7 <> A^{3.5} * A^{3.5}

 1: /// generator matrix
 2: let genm (a: SparseMatrix) =
 3:     let size = a.RowCount
 4:     let mid = constDiag size 1.0
 5:     let a_i = a - mid
 6: 
 7:     let rec logm (i, qpow: SparseMatrix) res =
 8:         if i = MAX_ITER then res
 9:         else
10:            let qdiv = qpow.Divide (float i)
11:            if sumSq qdiv < PREC then res + qdiv
12:            else 
13:                logm (i+1, -qpow * a_i) (res + qdiv)
14:     // find log(A - I)
15:     // update negative off-diagonal entries, the row-sum = 0
16:     logm (1, a_i) (zeroCreate size size) |> updateneg
17: 
18: /// exp series approximation
19: let expSeries (m: SparseMatrix)  =
20:     let size = m.RowCount
21:     
22:     let rec sum (i, k, mpow: SparseMatrix) res =
23:         if i = MAX_ITER then res
24:         else
25:             let mpowi, ki = mpow * m, k * float i
26:             let mdiv = mpowi.Divide ki
27:             if sumSq mdiv < PREC then res + mdiv
28:             else
29:                 sum (i+1, ki, mpowi) (res + mdiv) 
30: 
31:     let mid = constDiag size 1.0
32:     sum (1, 1.0, mid) mid
33: 
34: /// Matrix power: series method
35: let mpowSeries m (p: float) = SparseMatrix.OfMatrix (genm m * p) |> expSeries

+ Works for non-diagonizable matrices
Not very efficient
May be not accurate enough or even fail to converge

These are just the most simple algorithms – would be interesting to check the others, but let’s move on.

References
1. 19 Dubious Ways to Compute the Exponential of a Matrix
2. Finding Generators for Markov Chains via empirical transition matrices, with applications to credit ratings

Music

It’s not a secret, that Markov models are often used for generative music, here’s one of the examples: Programming Instrumental Music From Scratch (there’re also some interesting references in the comments). We’ll do a different thing, though: given notes, we can calculate the transition probabilities and… generate new melodies.

But everything in its time.

We need to somehow define the sequences of notes and play them. And thanks to Undertone that’s pretty easy! First of all, let’s download the piano samples – University of Iowa has a collection of recordings here. You can get them manually or using this script. It’s also ok to generate the notes programmatically – but somewhat a bit more realistic is more fun ^_^

 1: open namespaces
 2: 
 3: // Size: 2/4
 4: 
 5: /// 1/16 (semiquaver)
 6: [<Literal>] 
 7: let Sq = 0.0625
 8: /// 1/8 (quaver)
 9: [<Literal>] 
10: let Q1 = 0.125
11: /// 3/8
12: [<Literal>] 
13: let Q3 = 0.375
14: 
15: ...
16: 
17: /// Mapping between note enum and file name convention 
18: let noteToPianoName note =
19:     match note with
20:     | Note.C         -> "C" 
21:     | Note.Csharp    -> "Db"
22:     | Note.D         -> "D"
23:     | Note.Dsharp    -> "Eb"
24:     | Note.E         -> "E"
25:     | Note.F         -> "F"
26:     | Note.Fsharp    -> "Gb"
27:     | Note.G         -> "G"
28:     | Note.Gsharp    -> "Ab"
29:     | Note.A         -> "A"
30:     | Note.Asharp    -> "Bb"
31:     | Note.B         -> "B"
32:     | _ -> failwith "invalidnote"
33: 
34: /// Read piano note from .aiff file
35: let readPianoNote() =
36:     let cache = Dictionary<_,_> HashIdentity.Structural
37:     let inline path (noteName, octave) = 
38:         Path.Combine(dir, "Piano.ff." + noteName + string octave + ".aiff")
39: 
40:     fun (note, octave) ->
41:         let noteKey = noteToPianoName note, octave
42:         match cache.TryGetValue noteKey with
43:         | true, wave -> wave
44:         | _ -> 
45:             let wave = IO.read (path noteKey) |> Seq.toArray
46:             cache.Add(noteKey, wave)
47:             wave
48: 
49: let makeNote = readPianoNote() 
50: let getMelody = Seq.collect (fun (noct, time) -> makeNote noct |> Seq.take (noteValue time))

The note is defined with its symbol (C = Do, F# = Fis = Fa-diesis = F sharp), octave and time (♪ – 1/8, ♬ – 2 notes * 1/16). The sequence of notes gives a melody, which can be played and even saved later. Can you guess the following one?

Original

Sounds recognizable, but awkwardly stumbling – makes me feel much better about my piano skills. I’d recommend to watch this performace, it’s amazing! (and that pianist-feeling suddenly goes down again)). I believe there’s a way to make the generated sound smoother and so on, but that’s not the goal now.

 1: Define commonly used notes
 2: 
 3: /// Sample melody
 4: let ent = [
 5:     D3,Sq; Ds3,Sq
 6:     E3,Sq; C4,Q1; E3,Sq; C4,Q1; E3,Sq; C4,Q3
 7:     C4,Sq; D4,Sq; Ds4,Sq
 8:     E4,Sq; C4,Sq; D4,Sq; E4,Q1; C4,Sq; D4,Q1
 9:     C4,Sq; D3,Sq; Ds3,Sq ...
10:     C4,Q3
11: ]
12: 
13: Player.Play (getMelody ent)

(Note, the example above has 3 times faster tempo that the one you’ll get with downloaded notes)

The next thing to do is to calculate the transition probabilities. Consider the following sequence: E3 – 1/16, C4 – 1/8, E3 – 1/16, C4 – 3/8, C4 – 1/16

The first option is to simply count transitions between notes and then normalize the rows, so the sum are equal to 1:

\begin{matrix} & E3 & C4 \\ E3 & 0 & 0 \\ C4 & 0 & 0 \end{matrix} \rightarrow  \begin{matrix} & E3 & C4 \\ E3 & 0 & 1 \\ C4 & 0 & 0 \end{matrix} \rightarrow  \begin{matrix} & E3 & C4 \\ E3 & 0 & 1 \\ C4 & 1 & 0 \end{matrix} \rightarrow  \begin{matrix} & E3 & C4 \\ E3 & 0 & 2 \\ C4 & 1 & 0 \end{matrix} \rightarrow  \begin{matrix} & E3 & C4 \\ E3 & 0 & 2 \\ C4 & 1 & 0 \end{matrix} \rightarrow  \begin{matrix} & E3 & C4 \\ E3 & 0 & 2 \\ C4 & 1 & 1 \end{matrix} \rightarrow  \begin{matrix} & E3 & C4 \\ E3 & 0 & 1 \\ C4 & 0.5 & 0.5 \end{matrix}

But wait, what about time? There’re different notes, we can take that into account too: if the ‘smallest’ length is 1/16, then we can count, how many such intervals each note takes, multiplying by 162:

E3 – 1/16, C4 – 1/8, E3 – 1/16, C4 – 3/8, C4 – 1/16

\begin{matrix} & E3 & C4 \\ E3 & 0 & 0 \\ C4 & 0 & 0 \end{matrix} \rightarrow  \begin{matrix} & E3 & C4 \\ E3 & 0 & 1 \\ C4 & 0 & 0 \end{matrix} \rightarrow  \begin{matrix} & E3 & C4 \\ E3 & 0 & 1 \\ C4 & 1 & 1 \end{matrix} \rightarrow  \begin{matrix} & E3 & C4 \\ E3 & 0 & 2 \\ C4 & 1 & 1 \end{matrix} \rightarrow  \begin{matrix} & E3 & C4 \\ E3 & 0 & 2 \\ C4 & 1 & 7 \end{matrix} \rightarrow  \begin{matrix} & E3 & C4 \\ E3 & 0 & 1 \\ C4 & 0.125 & 0.875 \end{matrix}

So, we know the transitions and times when the next note should be played. A uniform random number generator will choose this next note: e.g. if transitions are 0.4 0.2 0.1 0.3 and generated number is 0.65, that’s the 3rd one (this operation is called choose below).

The note at time t, given transition matrix P is

n_t = choose(P^t_{n_{t-1}})
 1: /// Compute the times and probabilities of transitions between notes
 2: let transitions xs =
 3:     let noteToId = Seq.distinctBy fst xs |> Seq.mapi (fun i (note, _) -> note, i) |> dict
 4: 
 5:     // # of unique notes and # of notes in melody
 6:     let n, m = noteToId.Count, List.length xs
 7: 
 8:     let matrix = Array.init n (fun _ -> Array.zeroCreate n)
 9:     let times = Array.create m 0.
10: 
11:     // fill the matrix
12:     Seq.pairwise xs
13:     |> Seq.iteri (fun i ((note1, time1), (note2, _)) ->
14:         let n1, n2 = noteToId.[note1], noteToId.[note2]
15:         // option #1 - ignore times
16:         //   matrix.[n1].[n2] <- matrix.[n1].[n2] + 1.
17:         // option #2 - add extra probability for transition to the same note
18:         //    matrix.[n1].[n1] <- matrix.[n1].[n1] + time1 * 16.0
19:         //    matrix.[n1].[n2] <- matrix.[n1].[n2] + 1.0
20:         // option #3 - take time into account (scale by 16)
21:         let p = time1*16.-1. in if p > 0. then matrix.[n1].[n1] <- matrix.[n1].[n1] + p
22:         matrix.[n1].[n2] <- matrix.[n1].[n2] + 1.0
23:         
24:         times.[i+1] <- times.[i] + time1)
25:     
26:     normalizeRows matrix, times, Seq.toArray noteToId.Keys
27: 
28: let matrix, times, idToNote = transitions ent
29: 
30: /// Find index of the next note given probability
31: let transTo (ps: _[]) = ...
32: 
33: /// Generate music from transition matrices in a file
34: let genMusic matricesFileName (fstNote, idToNote: _[]) =
35:     // transition matrices and times
36:     let ps, ts = readMatrices matricesFileName
37:     let n = ps.Count
38:     let rand = System.Random()
39:     
40:     let rec gen i prevInd res =
41:         if i >= n-1 then List.rev res
42:         else
43:             let j = transTo ps.[i].[prevInd] (rand.NextDouble())
44:             let next = idToNote.[j], ts.[i]
45:             gen (i+1) j (next::res)
46: 
47:     gen 0 0 [fstNote]

I tried to compute the transition matrix – and both method failed! Turns out this is the real-life ‘unlucky’ matrix. To be sure that doesn’t work, checked R’s expm package:

 1: logm(x, method = "Eigen")
 2: Error in logm(x, method = "Eigen") : non diagonalisable matrix
 3: logm(x, method = "Higham08")
 4: Error in solve.default(X[ii, ii] + X[ij, ij], S[ii, ij] - sumU) :
 5: system is computationally singular: reciprocal condition number = 0
 6: In addition: Warning messages:
 7: 1: In sqrt(S[ij, ij]) : NaNs produced
 8: 2: In sqrt(S[ij, ij]) : NaNs produced

Fortunately, we don’t need fraction power here: if we count time in 1/16th, it becomes integer! 1/16 is 1, 3/8 is 6 and so on. Good old multiplication saves the situation.

Here’s several examples of generated melodies – there’re only 10 notes are in the game, but the results are already pretty different from the original:

Generated-1

Generated-2

Generated-3

What if we add an ‘extra-probability’ for note to stay in the same state?

E3 – 1/16, C4 – 1/8, E3 – 1/16, C4 – 3/8, C4 – 1/16

\begin{matrix} & E3 & C4 \\ E3 & 0 & 0 \\ C4 & 0 & 0 \end{matrix} \rightarrow  \begin{matrix} & E3 & C4 \\ E3 & 1 & 1 \\ C4 & 0 & 0 \end{matrix} \rightarrow  \begin{matrix} & E3 & C4 \\ E3 & 1 & 1 \\ C4 & 1 & 2 \end{matrix} \rightarrow  \begin{matrix} & E3 & C4 \\ E3 & 2 & 2 \\ C4 & 1 & 2 \end{matrix} \rightarrow  \begin{matrix} & E3 & C4 \\ E3 & 2 & 2 \\ C4 & 1 & 9 \end{matrix} \rightarrow  \begin{matrix} & E3 & C4 \\ E3 & 0.5 & 0.5 \\ C4 & 0.1 & 0.9 \end{matrix}

For this matrix, the generator does exist and we can programmatically compose something like that:

Generated-series-1

Generated-series-2

Why stop here? We could define the similar transitions for note length; or add chords – say, generated according to the current tonality; or switch tonalities/modes… Well, there’s a whole ‘musical math’, solfeggio, full of different functions – plenty of space to experiment!

Happy Holidays!

 1: let jingleBells = [
 2:     E4,Sq; E4,Sq; E4,Q1
 3:     E4,Sq; E4,Sq; E4,Q1
 4:     E4,Sq; G4,Sq; C4,Sq; D4,Sq
 5:     E4,Q1*2.0
 6:     F4,Sq; F4,Sq; F4,Sq; F4,Sq
 7:     F4,Sq; E4,Sq; E4,Sq; E4,Sq
 8:     E4,Sq; D4,Sq; D4,Sq; E4,Sq
 9:     D4,Q1; G4,Q1
10: ]
11: 
12: Player.Play (getMelody jingleBells)
open MathNet.Numerics.LinearAlgebra.Double
open MathNet.Numerics.LinearAlgebra.Generic
open Matrix
open SparseMatrix[<Literal>]
let MAX_ITER = 10000
[<Literal>]
let PREC = 1e-60

val mpowEig : m:SparseMatrix -> p:float -> Matrix<float>Full name: LogMusic.mpowEig

Matrix power: eigen values decomposition method
M^p = V * (D .^ p) / V

val m : SparseMatrix
Multiple items
type SparseMatrix =
inherit Matrix
new : storage:SparseCompressedRowMatrixStorage<float> -> SparseMatrix + 6 overloads
member CreateMatrix : numberOfRows:int * numberOfColumns:int * ?fullyMutable:bool -> Matrix<float>
member CreateVector : size:int * ?fullyMutable:bool -> Vector<float>
member FrobeniusNorm : unit -> float
member IndexedEnumerator : unit -> IEnumerable<Tuple<int, int, float>>
member InfinityNorm : unit -> float
member IsSymmetric : bool
member KroneckerProduct : other:Matrix<float> * result:Matrix<float> -> unit
member LowerTriangle : unit -> Matrix<float> + 1 overload
member NonZerosCount : int
…Full name: MathNet.Numerics.LinearAlgebra.Double.SparseMatrix

——————–
SparseMatrix(storage: MathNet.Numerics.LinearAlgebra.Storage.SparseCompressedRowMatrixStorage<float>) : unit
SparseMatrix(order: int) : unit
SparseMatrix(rows: int, columns: int) : unit

val p : float
val evd : Factorization.Evd
Matrix.Evd() : Factorization.Evd
val v : Matrix<float>
val d : Matrix<float>
Factorization.Evd.EigenVectors() : Matrix<float>
Factorization.Evd.D() : Matrix<float>
val mapInPlace : f:(float -> float) -> A:#Matrix<float> -> unitFull name: MathNet.Numerics.LinearAlgebra.Double.Matrix.mapInPlace

val x : float
Matrix.Inverse() : Matrix<float>
val genm : a:SparseMatrix -> Matrix<float>Full name: LogMusic.genm

generator matrix

val a : SparseMatrix
val size : int
property Matrix.RowCount: int
val mid : SparseMatrix
val constDiag : n:int -> f:float -> SparseMatrixFull name: MathNet.Numerics.LinearAlgebra.Double.SparseMatrix.constDiag

val a_i : SparseMatrix
val logm : (int * SparseMatrix -> Matrix<float> -> Matrix<float>)
val i : int
val qpow : SparseMatrix
val res : Matrix<float>
val MAX_ITER : intFull name: LogMusic.MAX_ITER

val qdiv : Matrix<float>
Matrix.Divide(scalar: float) : Matrix<float>
Matrix.Divide(scalar: float, result: Matrix<float>) : unit
Multiple items
val float : value:’T -> float (requires member op_Explicit)Full name: Microsoft.FSharp.Core.Operators.float

——————–
type float = System.Double

Full name: Microsoft.FSharp.Core.float

——————–
type float<‘Measure> = float

Full name: Microsoft.FSharp.Core.float<_>

val sumSq : m:Matrix<float> -> floatFull name: LogMusic.sumSq

sum of squares

val PREC : floatFull name: LogMusic.PREC

val zeroCreate : rows:int -> cols:int -> SparseMatrixFull name: MathNet.Numerics.LinearAlgebra.Double.SparseMatrix.zeroCreate

val updateneg : m:Matrix<float> -> Matrix<float>Full name: LogMusic.updateneg

update negative

val expSeries : m:SparseMatrix -> Matrix<float>Full name: LogMusic.expSeries

exp series approximation

val sum : (int * float * SparseMatrix -> Matrix<float> -> Matrix<float>)
val k : float
val mpow : SparseMatrix
val mpowi : SparseMatrix
val ki : float
val mdiv : Matrix<float>
val mpowSeries : m:SparseMatrix -> p:float -> Matrix<float>Full name: LogMusic.mpowSeries

Matrix power: series method

SparseMatrix.OfMatrix(matrix: Matrix<float>) : SparseMatrix
open System.IO
open System.Collections.Generic
open Undertone
open Undertone.Waves
Multiple items
type LiteralAttribute =
inherit Attribute
new : unit -> LiteralAttributeFull name: Microsoft.FSharp.Core.LiteralAttribute

——————–
new : unit -> LiteralAttribute

val Sq : floatFull name: LogMusic.Sq

1/16 (semiquaver)

val Q1 : floatFull name: LogMusic.Q1

1/8 (quaver)

val Q3 : floatFull name: LogMusic.Q3

3/8

let inline noteValue time = Time.noteValue 15. time/// Note samples directory
[<Literal>]
let dir = @”C:\samples”

val noteToPianoName : note:Note -> stringFull name: LogMusic.noteToPianoName

Mapping between note enum and file name convention

val note : Note
type Note =
| Cflat = -1
| C = 0
| Csharp = 1
| Dflat = 1
| D = 2
| Dsharp = 3
| Eflat = 3
| E = 4
| Fflat = 4
| Esharp = 5
| F = 5
| Fsharp = 6
| Gflat = 6
| G = 7
| Gsharp = 8
| Aflat = 8
| A = 9
| Asharp = 10
| Bflat = 10
| B = 11
| Bsharp = 12Full name: Undertone.Note

Note.C: Note = 0
Note.Csharp: Note = 1
Note.D: Note = 2
Note.Dsharp: Note = 3
Note.E: Note = 4
Note.F: Note = 5
Note.Fsharp: Note = 6
Note.G: Note = 7
Note.Gsharp: Note = 8
Note.A: Note = 9
Note.Asharp: Note = 10
Note.B: Note = 11
val failwith : message:string -> ‘TFull name: Microsoft.FSharp.Core.Operators.failwith

val readPianoNote : unit -> (Note * int -> float [])Full name: LogMusic.readPianoNote

Read piano note from .aiff file

val cache : Dictionary<(string * int),float []>
Multiple items
type Dictionary<‘TKey,’TValue> =
new : unit -> Dictionary<‘TKey, ‘TValue> + 5 overloads
member Add : key:’TKey * value:’TValue -> unit
member Clear : unit -> unit
member Comparer : IEqualityComparer<‘TKey>
member ContainsKey : key:’TKey -> bool
member ContainsValue : value:’TValue -> bool
member Count : int
member GetEnumerator : unit -> Enumerator<‘TKey, ‘TValue>
member GetObjectData : info:SerializationInfo * context:StreamingContext -> unit
member Item : ‘TKey -> ‘TValue with get, set

nested type Enumerator
nested type KeyCollection
nested type ValueCollectionFull name: System.Collections.Generic.Dictionary<_,_>

——————–
Dictionary() : unit
Dictionary(capacity: int) : unit
Dictionary(comparer: IEqualityComparer<‘TKey>) : unit
Dictionary(dictionary: IDictionary<‘TKey,’TValue>) : unit
Dictionary(capacity: int, comparer: IEqualityComparer<‘TKey>) : unit
Dictionary(dictionary: IDictionary<‘TKey,’TValue>, comparer: IEqualityComparer<‘TKey>) : unit

module HashIdentityfrom Microsoft.FSharp.Collections

val Structural<‘T (requires equality)> : IEqualityComparer<‘T> (requires equality)Full name: Microsoft.FSharp.Collections.HashIdentity.Structural

val path : (string * ‘a -> string)
val noteName : string
val octave : ‘a
type Path =
static val DirectorySeparatorChar : char
static val AltDirectorySeparatorChar : char
static val VolumeSeparatorChar : char
static val InvalidPathChars : char[]
static val PathSeparator : char
static member ChangeExtension : path:string * extension:string -> string
static member Combine : params paths:string[] -> string + 3 overloads
static member GetDirectoryName : path:string -> string
static member GetExtension : path:string -> string
static member GetFileName : path:string -> string
…Full name: System.IO.Path

Path.Combine(params paths: string []) : string
Path.Combine(path1: string, path2: string) : string
Path.Combine(path1: string, path2: string, path3: string) : string
Path.Combine(path1: string, path2: string, path3: string, path4: string) : string
val dir : stringFull name: LogMusic.dir

Note samples directory

Multiple items
val string : value:’T -> stringFull name: Microsoft.FSharp.Core.Operators.string

——————–
type string = System.String

Full name: Microsoft.FSharp.Core.string

val octave : int
val noteKey : string * int
Dictionary.TryGetValue(key: string * int, value: byref<float []>) : bool
val wave : float []
module IOfrom Undertone

val read : file:string -> seq<float>Full name: Undertone.IO.read

module Seqfrom Microsoft.FSharp.Collections

val toArray : source:seq<‘T> -> ‘T []Full name: Microsoft.FSharp.Collections.Seq.toArray

Dictionary.Add(key: string * int, value: float []) : unit
val makeNote : (Note * int -> float [])Full name: LogMusic.makeNote

val getMelody : (((Note * int) * float) list -> seq<float>)Full name: LogMusic.getMelody

val collect : mapping:(‘T -> #seq<‘U>) -> source:seq<‘T> -> seq<‘U>Full name: Microsoft.FSharp.Collections.Seq.collect

val noct : Note * int
val time : float
val take : count:int -> source:seq<‘T> -> seq<‘T>Full name: Microsoft.FSharp.Collections.Seq.take

val noteValue : time:float -> intFull name: LogMusic.noteValue

let D3 = Note.D, 3
let Ds3 = Note.Dsharp, 3
let E3 = Note.E, 3
let Fs3 = Note.Fsharp, 3
let G3 = Note.G, 3
let A3 = Note.A, 3
let C4 = Note.C, 4
let D4 = Note.D, 4
let Ds4 = Note.Dsharp, 4
let E4 = Note.E, 4let G4 = Note.G, 4
let F4 = Note.F, 4

val ent : ((Note * int) * float) listFull name: LogMusic.ent

Sample melody

val D3 : Note * intFull name: LogMusic.D3

val Ds3 : Note * intFull name: LogMusic.Ds3

val E3 : Note * intFull name: LogMusic.E3

val C4 : Note * intFull name: LogMusic.C4

val D4 : Note * intFull name: LogMusic.D4

val Ds4 : Note * intFull name: LogMusic.Ds4

val E4 : Note * intFull name: LogMusic.E4

E3,Sq; C4,Q1; E3,Sq; C4,Q1; E3,Sq; C4,Q3
A3,Sq; G3,Sq
Fs3,Sq; A3,Sq; C4,Sq; E4,Q1; D4,Sq; C4,Sq; A3,Sq
D4,Q3; D3,Sq; Ds3,Sq
E3,Sq; C4,Q1; E3,Sq; C4,Q1; E3,Sq; C4,Q3
C4,Sq; D4,Sq; Ds4,Sq
E4,Sq; C4,Sq; D4,Sq; E4,Q1; C4,Sq; D4,Q1
C4,Q3; D4,Sq; Ds4,Sq
E4,Sq; C4,Sq; D4,Sq; E4,Q1; C4,Sq; D4,Sq; C4,Sq
E4,Sq; C4,Sq; D4,Sq; E4,Q1; C4,Sq; D4,Sq; C4,Sq
E4,Sq; C4,Sq; D4,Sq; E4,Q1; C4,Sq; D4,Q1
type Player =
private new : unit -> Player
static member Play : sampleSource:seq<float> -> IPlayerFull name: Undertone.Player

static member Player.Play : sampleSource:seq<float> -> IPlayer
val transitions : xs:(‘a * float) list -> float [] [] * float [] * ‘a [] (requires equality)Full name: LogMusic.transitions

Compute the times and probabilities of transitions between notes

val xs : (‘a * float) list (requires equality)
val noteToId : IDictionary<‘a,int> (requires equality)
val distinctBy : projection:(‘T -> ‘Key) -> source:seq<‘T> -> seq<‘T> (requires equality)Full name: Microsoft.FSharp.Collections.Seq.distinctBy

val fst : tuple:(‘T1 * ‘T2) -> ‘T1Full name: Microsoft.FSharp.Core.Operators.fst

val mapi : mapping:(int -> ‘T -> ‘U) -> source:seq<‘T> -> seq<‘U>Full name: Microsoft.FSharp.Collections.Seq.mapi

val note : ‘a (requires equality)
val dict : keyValuePairs:seq<‘Key * ‘Value> -> IDictionary<‘Key,’Value> (requires equality)Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.dict

val n : int
val m : int
property ICollection.Count: int
Multiple items
type List<‘T> =
new : unit -> List<‘T> + 2 overloads
member Add : item:’T -> unit
member AddRange : collection:IEnumerable<‘T> -> unit
member AsReadOnly : unit -> ReadOnlyCollection<‘T>
member BinarySearch : item:’T -> int + 2 overloads
member Capacity : int with get, set
member Clear : unit -> unit
member Contains : item:’T -> bool
member ConvertAll<‘TOutput> : converter:Converter<‘T, ‘TOutput> -> List<‘TOutput>
member CopyTo : array:’T[] -> unit + 2 overloads

nested type EnumeratorFull name: System.Collections.Generic.List<_>

——————–
List() : unit
List(capacity: int) : unit
List(collection: IEnumerable<‘T>) : unit

val length : list:’T list -> intFull name: Microsoft.FSharp.Collections.List.length

val matrix : float [] []
module Arrayfrom Microsoft.FSharp.Collections

val init : count:int -> initializer:(int -> ‘T) -> ‘T []Full name: Microsoft.FSharp.Collections.Array.init

val zeroCreate : count:int -> ‘T []Full name: Microsoft.FSharp.Collections.Array.zeroCreate

val times : float []
val create : count:int -> value:’T -> ‘T []Full name: Microsoft.FSharp.Collections.Array.create

val pairwise : source:seq<‘T> -> seq<‘T * ‘T>Full name: Microsoft.FSharp.Collections.Seq.pairwise

val iteri : action:(int -> ‘T -> unit) -> source:seq<‘T> -> unitFull name: Microsoft.FSharp.Collections.Seq.iteri

val note1 : ‘a (requires equality)
val time1 : float
val note2 : ‘a (requires equality)
val n1 : int
val n2 : int
val normalizeRows : m:float [] [] -> float [] []Full name: LogMusic.normalizeRows

Normalize rows, so the row-sums are equal to 1

property IDictionary.Keys: ICollection<‘a>
val matrix : float [] []Full name: LogMusic.matrix

val times : float []Full name: LogMusic.times

val idToNote : (Note * int) []Full name: LogMusic.idToNote

val transTo : ps:float [] -> (float -> int)Full name: LogMusic.transTo

Find index of the next note given probability

val ps : float []
let last = ps.Length-1
let rec findj j v =
if ps.[j] >= v || j = last then j
else findj (j+1) (v – ps.[j])
findj 0
val genMusic : matricesFileName:string -> fstNote:(‘a * float) * idToNote:’a [] -> (‘a * float) listFull name: LogMusic.genMusic

Generate music from transition matrices in a file

val matricesFileName : string
val fstNote : ‘a * float
val idToNote : ‘a []
val ps : List<ResizeArray<float []>>
val ts : float []
val readMatrices : path:string -> List<ResizeArray<float []>> * float []Full name: LogMusic.readMatrices

Read transition matrices and times

property List.Count: int
val rand : System.Random
namespace System
Multiple items
type Random =
new : unit -> Random + 1 overload
member Next : unit -> int + 2 overloads
member NextBytes : buffer:byte[] -> unit
member NextDouble : unit -> floatFull name: System.Random

——————–
System.Random() : unit
System.Random(Seed: int) : unit

val gen : (int -> int -> (‘a * float) list -> (‘a * float) list)
val prevInd : int
val res : (‘a * float) list
val rev : list:’T list -> ‘T listFull name: Microsoft.FSharp.Collections.List.rev

val j : int
System.Random.NextDouble() : float
val next : ‘a * float
val jingleBells : ((Note * int) * float) listFull name: LogMusic.jingleBells

val G4 : Note * intFull name: LogMusic.G4

val F4 : Note * intFull name: LogMusic.F4

  1. the example matrix isn’t actually a transition matrix – the row sums should be equal to 1 – it’s here just to demonstrate a possible problem
  2. the sequence C4 – 1/8, E3 – 1/16 is almost the same as C4 – 1/16, C4 – 1/16, E3 – 1/16. That gives us 50/50 transitions: after each period of time C4 can either stay as C4 or transform to E3