Generating music in F# part 3: Note values

April 5, 2015

Previous posts in series:
Part 1
Part 2

After last post the program can generate melodies using Markov chains generated from seed data. Next important aspect of a catchy melody is note value. Note value means a relative duration of a note.

Just like notes note values are modelled as a type:

type value =
| Full
| Half
| Quarter
| Eighth

The actual duration of a note value in seconds is relative to tempo. A map of durations for values can be created using this function:

let time beatsPerMinute =
let quarterLength = 60. / beatsPerMinute
Map.ofList [
(Eighth, 0.5 * quarterLength);
(Quarter, quarterLength);
(Half, 2. * quarterLength);
(Full, 4. * quarterLength)]

Beats per minute(bpm) means the amount of quarter notes in a minute. Dividing minute(60 seconds) with bpm yields length of quarter note in seconds. Other time values can be counted from that quarter note value.

I wanted some more complex seed data:

let yesterday =
[(G, Eighth); (F, Eighth); (F, Half);
(A, Eighth); (B, Eighth);(Cis, Eighth); (D, Eighth); (E, Eighth);(F, Eighth);
(E, Eighth); (D, Eighth); (D, Half);
(D, Eighth); (D, Eighth); (C, Eighth); (Ais, Eighth); (A, Eighth); (G, Eighth);
(Ais, Quarter); (A, Eighth); (A, Quarter); (G, Quarter);
(F, Quarter); (A, Eighth); (G, Eighth); (G, Quarter); (D, Eighth);
(F, Quarter); (A, Eighth); (A, Eighth); (A, Half);]

Melody is now represented as list of tuples of note and value. Both types will have their own Markov chain structure

let createMarkovChains data =
data
|> Seq.windowed 2
|> Seq.groupBy (fun x -> x.)
|> Seq.map (fun x ->
(fst x,
x
|> snd
|> Seq.map (Seq.nth 1)))
|> Map.ofSeq

let noteData =
yesterday
|> List.map(fst)
|> createMarkovChains

let timingsData =
yesterday
|> List.map(snd)
|> createMarkovChains

Creation of random sequence is not dependent of the type of item in this case. Functions can be made more generic by removing type declarations referring to note-type.

let getNextElement (random : System.Random) (data : Map<_,_>) currentElement =
let nextSet = data.[currentElement]
let nextElementIndex = random.Next(0, Seq.length nextSet)
nextSet
|> Seq.skip nextElementIndex

let r = System.Random()
let nextElementFromData x y = getNextElement r x y

let rec randomSequence wantedLength seedData currentElement (acc : 'a list) =
if acc.Length = wantedLength then acc
else
let nextElement = nextElementFromData seedData currentElement
randomSequence wantedLength seedData nextElement (acc @ [ nextElement ])

Here is the main now:

[<EntryPoint>]
let main argv =
let availableNotes =
noteData |> Map.toSeq |> Seq.map fst |> List.ofSeq
let firstNote = availableNotes.[r.Next(0, availableNotes.Length)]
let melody = randomSequence 24 noteData firstNote [ firstNote ]
|> List.map(fun x -> frequency.[x])

let timingsForTempo = (time 120.)
let availableTimes =
timingsData |> Map.toSeq |> Seq.map fst |> List.ofSeq
let firstTime = availableTimes.[r.Next(0, availableTimes.Length)]
let timings = randomSequence 24 timingsData firstTime [ firstTime ]
|> List.map(fun x -> timingsForTempo.[x])

List.zip melody timings |> Synth.writeMelody
0

Random sequences are created for both notes and time values. Then they are zipped into list of tuples and written to wav-file.

Full source on GitHub. Here is an example output: