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.[0])
|> 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
|> Seq.head
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:
Software professional with a passion for quality. Likes TDD and working in agile teams. Has worked with wide-range of technologies, backend and frontend, from C++ to Javascript. Currently very interested in functional programming.