// thinkbeforecoding

Ukulele Fun for XMas !

2015-12-17T10:44-43 / jeremie chassaing

This post is part of the F# Advent Calendar in English 2015 project. Check out all the other great posts there! And special thanks to Sergey Tihon for organizing this.

Hi something fun and not too technical for end the year !

As everyone knows, the favorite instrument of Santa Claus is Ukulele ! So let's play some music, and especialy some Ukulele !

First thing first, let's create functions for notes. We start with C at octave 0, and have a progression by half tones.

So C is 0, D is 2, E is 4.

Since there is only a half tone between E and F, F is 5.

F is 7, A is 9, B is 11, and we reach next octave at 12, which is C 1 :

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
open System

let C n = 12 * n
let D n = C n + 2
let E n = C n + 4
let F n = C n + 5
let G n = C n + 7
let A n = C n + 9
let B n = C n + 11 

For sharps and flat, lets define two functions that had and remove a half tone

1: 
2: 
let sharp n = n + 1
let flat n = n - 1

We can now create names for each note :

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
let Cd = C >> sharp
let Db = D >> flat
let Dd = D >> sharp
let Eb = E >> flat
let Fd = F >> sharp
let Gb = G >> flat
let Gd = G >> sharp
let Ab = A >> flat
let Ad = A >> sharp
let Bb = B >> flat

There is no E sharp or F flat because it is F and E respectively, same thing for B and C...

Will create a structure with a custome comparison/equality that doesn't take the octave into account by using a 12 modulus, this will prove usefull to work with chords:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
[<Struct>]
[<CustomComparison>]
[<CustomEquality>]
[<StructuredFormatDisplay("{Display}")>]
type Note(note : int) =
    member __.Note = note
    
    override __.GetHashCode() = note % 12 

    override __.Equals other =
        match other with
        | :? Note as other ->
            note % 12 = other.Note % 12
        | _ -> false

    static member names = 
        [| "C"
           "C#"
           "D"
           "D#"
           "E"
           "F"
           "F#"
           "G"
           "G#"
           "A"
           "A#"
           "B" |]
    member __.Display = 
        let name = Note.names.[note % 12]
        let octave = note / 12
        sprintf "%s %d" name octave

    override this.ToString() = this.Display
        
    interface IEquatable<Note> with
        member __.Equals other =
            note % 12 = other.Note % 12
    interface IComparable<Note> with
        member __.CompareTo other =
            compare (note % 12) (other.Note % 12) 
    interface IComparable with
        member __.CompareTo other =
            match other with
            | :? Note as other -> 
                compare (note % 12) (other.Note % 12)
            | _ -> 1 

    static member (+) (string: Note, fret: int) =
        Note (string.Note + fret)

let notes = List.map Note

Ukulele Strings

A Ukulele has 4 strings.

The funy thing is that the 1st one is higher than the second one, where on most string instruments strings are in progressive order.

This is simply due to the limited size of the Ukulele, a low first string would not sound good, so it is adjusted to the next octave.

This gives use the following:

1: 
let strings = notes [G 4;C 4;E 4; A 4]

Chords

Instead of hard-encoding ukulele chords, we will compute them !

So a bit of theory about chords.

Chords are defined by their root note and the chord quality (major, minor).

The chords start on the root note, and the chord quality indicates the distance to other notes to include in the chord.

On string instrument, the order and the height of the actual notes are not really important for the chord to be ok. So we can use a note at any octave.

Now, let's define the chord qualities.

First, Major, uses the root note, 3rd and 5th, for instance for C, it will be C, E, G, which gives intervals of 0, 4 and 7 half tones from root.

1: 
2: 
3: 
let quality = notes >> Set.ofList

let M n = quality [n ; n + 4; n+7] 

Then, Minor, uses the root note, the lower 3rd and 5th. For C it will be C, E flat, G, so intervals of 0, 3 and 7 half tones for root.

1: 
let m n = quality [n; n + 3; n+7] 

The 7th adds a 4th note on the Major:

1: 
let M7 n = quality [n; n + 4; n+7; n+11 ]

Frets

As on a gitare, a ukulele has frets, places where you press the string with your finger to change the tone of a string.

0 usually represent when you don't press a string at all, and pinching the string will play the string note.

When pressing fret 1, the note is one half tone higher, fret 2, two half tone (or one tone) higher.

So pressing the second fret on the C 4 string give a D 4.

Our first function will try pressing on frets to find frets for notes that belong to the chord

1: 
2: 
3: 
4: 
5: 
let findFrets chord (string: Note) =
    [0..10]
    |> List.filter (fun fret -> 
        Set.contains (string + fret) chord)
    |> List.map (fun fret -> fret, string + fret)

The result is list of pair, (fret, note) that can be used on the strnig

The second function will explore the combinaison of frets/note and keep only those that contains all notes of the chords.

Ex: for a C Major chord, we need at least a C, a E and a G.

using frets 0 on string G, 0 on string C, 3 on string E, and 3 on string A, we get G, C, G, C.

All notes are part of the chord, but there is no E... not enough. 0,0,0,3 is a better solution.

The function explore all possible solution by checking notes on string that belong to the chord, and each time remove a note from the chord. At the end, there should be no missing note.

At each level sub solutions are sorted by a cost. Standard Ukulele chords try to place fingers as close to the top as possible. So lewer frets are better.

The cost function for a chords is to sum square of frets. If there is any solution, we keep the one with the lowest cost.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
let rec filterChord chord missingNotes solution stringFrets  =
    match stringFrets with
    | [] -> 
        if Set.isEmpty missingNotes then 
            Some (List.rev solution)
        else
            None
    | string :: tail -> 
        string
        |> List.filter (fun (_,note) -> 
            chord |> Set.contains note)
        |> List.choose (fun (fret,note) -> 
            filterChord chord (Set.remove note missingNotes) ((fret,note) :: solution) tail)
        |> List.sortBy(fun s -> 
            List.sumBy (fun (fret,_) -> fret*fret) s)
        |> List.tryHead
       

making a cord is now simple.

Compute the note in the chord using quality and root.

For each string, map possible frets the belong to the chord, then filter it.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
let chord root quality =    
    let chord = quality (root 4)
    strings
    |> List.map (findFrets chord)
    |> filterChord chord chord []
    |> Option.get
    

We can now try with classic chords:

1: 
let CM = chord C M

and the result is:

include-value: CM

Now C minor:

1: 
let Cm = chord C m

which is exactly what you can find on a tab sheet:

include-value: Cm

1: 
2: 
3: 
4: 
5: 
6: 
7: 
chord D m
    
chord A M
chord A m

chord G m
chord E M

Printing chords

To print chords, we will simply use pretty unicode chars, and place a small 'o' on the fret where we should place fingers:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
let print chord  =
    let fret n frt = 
        if n = frt then 
            "o" 
        else 
            "│"
    let line chord n  =
            chord 
            |> List.map (fst >> fret n)
            |> String.concat ""       
    printfn "┬┬┬┬"
    [1..4] 
    |> List.map (line chord)
    |> String.concat "\n┼┼┼┼\n" 
    |> printfn "%s"

Let's try it

define-output: chordCM

1: 
chord C M |> print

It prints

include-output: chordCM

Another one

define-output: chordGM

1: 
chord G M |> print

and we get

include-output: chordGM

Playing chords

We can also play chords using NAudio.

You can find NAudio on nuget.org

For simplicity I will use the midi synthetizer:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
#r @"packages\NAudio\lib\net35\NAudio.dll"

open NAudio.Midi
let device = new MidiOut(0)
MidiOut.DeviceInfo 0
let midi (m:MidiMessage) =  device.Send m.RawData

let startNote note volume = 
    MidiMessage.StartNote(note, volume, 2) |> midi

let stopNote note volume = 
    MidiMessage.StopNote(note, volume, 2) |> midi

let sleep n = System.Threading.Thread.Sleep(n: int)

Now we can define a function that will play a chord.

The tempo is used as a multiplicator for a the chord length.

Longer tempo means slower.

For better result we introduce an arpegio, a small delay between each note. Don't forget to remove this time from the waiting length...

The direction indicate if the cords are strumed Up, or Down. In the Up case we reverse the chord.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
type Direction = Dn of int | Up of int

let play tempo arpegio (chord, strum)  =
    let strings, length = 
        match strum with 
        | Dn length -> chord, length
        | Up length -> List.rev chord, length 

    strings
    |> List.iter (fun (_,(n: Note)) -> 
        startNote n.Note 100 ; sleep arpegio )

    let arpegioLength = 
        List.length chord * arpegio

    sleep (length * tempo - arpegioLength)

    strings
    |> List.iter (fun (_,(n: Note)) -> 
        stopNote n.Note 100 )

To strum a chord, we give a list of length, and a chord, and it will apply the cord to each length:

1: 
2: 
3: 
4: 
5: 
6: 
let strum strm chord =
    let repeatedChord = 
        strm 
        |> List.map (fun _ -> chord)
    
    List.zip repeatedChord strm

Now here is Santa Clause favorite song, Get Lucky by Daft Punk.

First the chords :

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
let luckyChords = 
    [ //Like the legend of the Phoenix,
      chord B m
      // All ends with beginnings.
      chord D M
      // What keeps the planets spinning,
      chord (Fd) m
      // The force from the beginning.
      chord E M ]

Then strum, this is the rythm used to play the same chord, it goes like, Dam, Dam, Dam Dala Dam Dam:

1: 
2: 
let luckyStrum = 
    [ Dn 4; Dn 3; Dn 2; Dn 1; Up 2; Dn 2; Up 2]

and the full song :

1: 
2: 
3: 
let getLucky =
    luckyChords
    |> List.collect (strum luckyStrum)

And now, let's play it :

1: 
2: 
3: 
4: 
getLucky
|> List.replicate 2
|> List.concat
|> List.iter (play 130 25)

And the tab notations for the song !

define-output: song

1: 
2: 
luckyChords
|> List.iter print

include-output: song

Conclusion

I hope this small thing was entertaining and that it'll get you into ukulele !

For excercise you can:

  • implements more chords
  • Better printing
  • add more liveliness and groove by adding some jitter to the strum...
  • add the lyrics for Karaoke !
  • try with other songs !
  • try the same for a 6 string gitar !

Now it's your turn to rock !

namespace System
val C : n:int -> int

Full name: Ukulele.C
val n : int
val D : n:int -> int

Full name: Ukulele.D
val E : n:int -> int

Full name: Ukulele.E
val F : n:int -> int

Full name: Ukulele.F
val G : n:int -> int

Full name: Ukulele.G
val A : n:int -> int

Full name: Ukulele.A
val B : n:int -> int

Full name: Ukulele.B
val sharp : n:int -> int

Full name: Ukulele.sharp
val flat : n:int -> int

Full name: Ukulele.flat
val Cd : (int -> int)

Full name: Ukulele.Cd
val Db : (int -> int)

Full name: Ukulele.Db
val Dd : (int -> int)

Full name: Ukulele.Dd
val Eb : (int -> int)

Full name: Ukulele.Eb
val Fd : (int -> int)

Full name: Ukulele.Fd
val Gb : (int -> int)

Full name: Ukulele.Gb
val Gd : (int -> int)

Full name: Ukulele.Gd
val Ab : (int -> int)

Full name: Ukulele.Ab
val Ad : (int -> int)

Full name: Ukulele.Ad
val Bb : (int -> int)

Full name: Ukulele.Bb
Multiple items
type StructAttribute =
  inherit Attribute
  new : unit -> StructAttribute

Full name: Microsoft.FSharp.Core.StructAttribute

--------------------
new : unit -> StructAttribute
Multiple items
type CustomComparisonAttribute =
  inherit Attribute
  new : unit -> CustomComparisonAttribute

Full name: Microsoft.FSharp.Core.CustomComparisonAttribute

--------------------
new : unit -> CustomComparisonAttribute
Multiple items
type CustomEqualityAttribute =
  inherit Attribute
  new : unit -> CustomEqualityAttribute

Full name: Microsoft.FSharp.Core.CustomEqualityAttribute

--------------------
new : unit -> CustomEqualityAttribute
Multiple items
type StructuredFormatDisplayAttribute =
  inherit Attribute
  new : value:string -> StructuredFormatDisplayAttribute
  member Value : string

Full name: Microsoft.FSharp.Core.StructuredFormatDisplayAttribute

--------------------
new : value:string -> StructuredFormatDisplayAttribute
Multiple items
type Note =
  struct
    interface IComparable
    interface IComparable<Note>
    interface IEquatable<Note>
    new : note:int -> Note
    override Equals : other:obj -> bool
    override GetHashCode : unit -> int
    override ToString : unit -> string
    member Display : string
    member Note : int
    static member names : string []
    ...
  end

Full name: Ukulele.Note

--------------------
Note()
new : note:int -> Note
val note : int
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
Multiple items
member Note.Note : int

Full name: Ukulele.Note.Note

--------------------
type Note =
  struct
    interface IComparable
    interface IComparable<Note>
    interface IEquatable<Note>
    new : note:int -> Note
    override Equals : other:obj -> bool
    override GetHashCode : unit -> int
    override ToString : unit -> string
    member Display : string
    member Note : int
    static member names : string []
    ...
  end

Full name: Ukulele.Note

--------------------
Note()
new : note:int -> Note
val __ : byref<Note>
override Note.GetHashCode : unit -> int

Full name: Ukulele.Note.GetHashCode
override Note.Equals : other:obj -> bool

Full name: Ukulele.Note.Equals
val other : obj
val other : Note
property Note.Note: int
static member Note.names : string []

Full name: Ukulele.Note.names
member Note.Display : string

Full name: Ukulele.Note.Display
val name : string
property Note.names: string []
val octave : int
val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
val this : byref<Note>
override Note.ToString : unit -> string

Full name: Ukulele.Note.ToString
type IEquatable<'T> =
  member Equals : other:'T -> bool

Full name: System.IEquatable<_>
override Note.Equals : other:Note -> bool

Full name: Ukulele.Note.Equals
Multiple items
type IComparable =
  member CompareTo : obj:obj -> int

Full name: System.IComparable

--------------------
type IComparable<'T> =
  member CompareTo : other:'T -> int

Full name: System.IComparable<_>
override Note.CompareTo : other:Note -> int

Full name: Ukulele.Note.CompareTo
val compare : e1:'T -> e2:'T -> int (requires comparison)

Full name: Microsoft.FSharp.Core.Operators.compare
override Note.CompareTo : other:obj -> int

Full name: Ukulele.Note.CompareTo
Multiple items
val string : Note

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
val fret : int
val notes : (int list -> IComparable list)

Full name: Ukulele.notes
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
val strings : IComparable list

Full name: Ukulele.strings
val quality : (int list -> Set<IComparable>)

Full name: Ukulele.quality
Multiple items
module Set

from Microsoft.FSharp.Collections

--------------------
type Set<'T (requires comparison)> =
  interface IComparable
  interface IEnumerable
  interface IEnumerable<'T>
  interface ICollection<'T>
  new : elements:seq<'T> -> Set<'T>
  member Add : value:'T -> Set<'T>
  member Contains : value:'T -> bool
  override Equals : obj -> bool
  member IsProperSubsetOf : otherSet:Set<'T> -> bool
  member IsProperSupersetOf : otherSet:Set<'T> -> bool
  ...

Full name: Microsoft.FSharp.Collections.Set<_>

--------------------
new : elements:seq<'T> -> Set<'T>
val ofList : elements:'T list -> Set<'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Set.ofList
val M : n:int -> Set<IComparable>

Full name: Ukulele.M
val m : n:int -> Set<IComparable>

Full name: Ukulele.m
val M7 : n:int -> Set<IComparable>

Full name: Ukulele.M7
val findFrets : chord:'a -> string:Note -> 'b

Full name: Ukulele.findFrets
val chord : 'a
val contains : element:'T -> set:Set<'T> -> bool (requires comparison)

Full name: Microsoft.FSharp.Collections.Set.contains
val filterChord : chord:'a -> missingNotes:Set<'b> -> solution:'c -> stringFrets:'d list -> 'e option (requires comparison)

Full name: Ukulele.filterChord
val missingNotes : Set<'b> (requires comparison)
val solution : 'c
val stringFrets : 'd list
val isEmpty : set:Set<'T> -> bool (requires comparison)

Full name: Microsoft.FSharp.Collections.Set.isEmpty
union case Option.Some: Value: 'T -> Option<'T>
union case Option.None: Option<'T>
Multiple items
val string : 'd

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
val tail : 'd list
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
val remove : value:'T -> set:Set<'T> -> Set<'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Set.remove
val chord : root:(int -> 'a) -> quality:('a -> Set<'b>) -> 'c (requires comparison)

Full name: Ukulele.chord
val root : (int -> 'a)
val quality : ('a -> Set<'b>) (requires comparison)
val chord : Set<'b> (requires comparison)
module Option

from Microsoft.FSharp.Core
val get : option:'T option -> 'T

Full name: Microsoft.FSharp.Core.Option.get
val CM : obj

Full name: Ukulele.CM
val Cm : obj

Full name: Ukulele.Cm
val print : chord:'a -> unit

Full name: Ukulele.print
val fret : ('b -> 'b -> string) (requires equality)
val n : 'b (requires equality)
val frt : 'b (requires equality)
val line : ('b -> 'c -> string)
val chord : 'b
val n : 'c
val fst : tuple:('T1 * 'T2) -> 'T1

Full name: Microsoft.FSharp.Core.Operators.fst
Multiple items
type String =
  new : value:char -> string + 7 overloads
  member Chars : int -> char
  member Clone : unit -> obj
  member CompareTo : value:obj -> int + 1 overload
  member Contains : value:string -> bool
  member CopyTo : sourceIndex:int * destination:char[] * destinationIndex:int * count:int -> unit
  member EndsWith : value:string -> bool + 2 overloads
  member Equals : obj:obj -> bool + 2 overloads
  member GetEnumerator : unit -> CharEnumerator
  member GetHashCode : unit -> int
  ...

Full name: System.String

--------------------
String(value: nativeptr<char>) : unit
String(value: nativeptr<sbyte>) : unit
String(value: char []) : unit
String(c: char, count: int) : unit
String(value: nativeptr<char>, startIndex: int, length: int) : unit
String(value: nativeptr<sbyte>, startIndex: int, length: int) : unit
String(value: char [], startIndex: int, length: int) : unit
String(value: nativeptr<sbyte>, startIndex: int, length: int, enc: Text.Encoding) : unit
val concat : sep:string -> strings:seq<string> -> string

Full name: Microsoft.FSharp.Core.String.concat
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
val device : obj

Full name: Ukulele.device
val midi : m:'a -> 'b

Full name: Ukulele.midi
val m : 'a
val startNote : note:'a -> volume:'b -> 'c

Full name: Ukulele.startNote
val note : 'a
val volume : 'b
val stopNote : note:'a -> volume:'b -> 'c

Full name: Ukulele.stopNote
val sleep : n:int -> unit

Full name: Ukulele.sleep
namespace System.Threading
Multiple items
type Thread =
  inherit CriticalFinalizerObject
  new : start:ThreadStart -> Thread + 3 overloads
  member Abort : unit -> unit + 1 overload
  member ApartmentState : ApartmentState with get, set
  member CurrentCulture : CultureInfo with get, set
  member CurrentUICulture : CultureInfo with get, set
  member DisableComObjectEagerCleanup : unit -> unit
  member ExecutionContext : ExecutionContext
  member GetApartmentState : unit -> ApartmentState
  member GetCompressedStack : unit -> CompressedStack
  member GetHashCode : unit -> int
  ...

Full name: System.Threading.Thread

--------------------
Threading.Thread(start: Threading.ThreadStart) : unit
Threading.Thread(start: Threading.ParameterizedThreadStart) : unit
Threading.Thread(start: Threading.ThreadStart, maxStackSize: int) : unit
Threading.Thread(start: Threading.ParameterizedThreadStart, maxStackSize: int) : unit
Threading.Thread.Sleep(timeout: TimeSpan) : unit
Threading.Thread.Sleep(millisecondsTimeout: int) : unit
type Direction =
  | Dn of int
  | Up of int

Full name: Ukulele.Direction
union case Direction.Dn: int -> Direction
union case Direction.Up: int -> Direction
val play : tempo:int -> arpegio:int -> chord:'a * strum:Direction -> 'b

Full name: Ukulele.play
val tempo : int
val arpegio : int
val strum : Direction
val strings : 'a
val length : int
val arpegioLength : int
val strum : strm:'a -> chord:'b -> 'c

Full name: Ukulele.strum
val strm : 'a
val repeatedChord : obj
val luckyChords : obj list

Full name: Ukulele.luckyChords
val luckyStrum : Direction list

Full name: Ukulele.luckyStrum
val getLucky : obj

Full name: Ukulele.getLucky