Continuing from the previous post.
To make melody generation more interesting I decided to try applying statistical process called Markov chain. In the context of this post by Markov chain I mean that the next state of the system is not completely random but next states have different probabilities that depend on the current state.
There is a Finnish children's song and a Swedish drinking song called "Ukko-Nooa" or "Gubben Noak" that has a simple melody that goes like this:
let nooaMelody =
[C; C; C; E; D; D; D; F; E; E; D; D; C;
E; E; E; E; G; F; D; D; D; D; F; E;
C; C; C; E; D; D; D; F; E; E; D; D; C;]
We create a map of notes to sequence of notes. Sequence consists of notes that can follow the note that is used as key.
let noteData =
|> Seq.windowed 2
|> Seq.groupBy (fun (x : note ) -> x.)
|> Seq.map (fun x ->
|> Seq.map (Seq.nth 1)))
This is how to get next random note based on current note.
let getNextNote (random : System.Random) (data : Map<note,seq<note>>) (currentNote : note) =
let nextSet : seq<note> = data.[currentNote]
let nextNoteIndex = random.Next(0, Seq.length nextSet)
|> Seq.skip nextNoteIndex
The more times a note is contained in the sequence where currentNote acts as a key, the more likely it is to become next note.
Rest of the program looks like this:
let r = System.Random()
let nextNoteFromData : Map<note,seq<note>> -> note -> note = getNextNote r
let rec randomMelody wantedLength noteData currentNote (melody : note list) =
if melody.Length = wantedLength then melody
let nextNote = nextNoteFromData noteData currentNote
randomMelody wantedLength noteData nextNote (melody @ [nextNote])
let main argv =
let availableNotes =
noteData |> Map.toSeq |> Seq.map fst |> List.ofSeq
let firstNote = availableNotes.[r.Next(0, availableNotes.Length)]
randomMelody 8 noteData firstNote [ firstNote ]
|> Seq.map (fun x -> frequency.[x])
0 // return an integer exit code
nextNoteFromData is partially applied function of getNextNote that can be used without passing an instance of System.Random along. randomMelody recursively creates list of notes until given length is achieved.
That's it! Here is an example of the output. Much better than the completely random version from previous post.
The full source code is on GitHub