Previous posts in series:
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 |> 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: