Generating music in F# part 3: Note values

April 5, 2015

Read time 2 min

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:

Never miss a post