// thinkbeforecoding

Ukulele Fun for XMas !

2015-12-17T09: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 :

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

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

We can now create names for each note :

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:

[<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:

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.

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.

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

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

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

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.

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.

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:

let CM = chord C M

and the result is:

[(0, G 4); (0, C 4); (0, E 4); (3, C 5)]

Now C minor:

let Cm = chord C m

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

[(0, G 4); (3, D# 4); (3, G 4); (3, C 5)]
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:

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

chord C M |> print

It prints

┬┬┬┬
││││
┼┼┼┼
││││
┼┼┼┼
│││o
┼┼┼┼
││││

Another one

chord G M |> print

and we get

┬┬┬┬
││││
┼┼┼┼
│o│o
┼┼┼┼
││o│
┼┼┼┼
││││

Playing chords

We can also play chords using NAudio.

You can find NAudio on nuget.org

For simplicity I will use the midi synthetizer:

#r @"../packages/NAudio/lib/netstandard2.0/NAudio.dll"
#r @"../packages/NAudio.Core/lib/netstandard2.0/NAudio.Core.dll"
#r @"../packages/NAudio.Midi/lib/netstandard2.0/NAudio.Midi.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.

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:

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 :

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:

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

and the full song :

let getLucky =
    luckyChords
    |> List.collect (strum luckyStrum)

And now, let's play it :

getLucky
|> List.replicate 2
|> List.concat
|> List.iter (play 130 25)

And the tab notations for the song !

luckyChords
|> List.iter print
┬┬┬┬
││││
┼┼┼┼
│ooo
┼┼┼┼
││││
┼┼┼┼
o│││
┬┬┬┬
││││
┼┼┼┼
ooo│
┼┼┼┼
││││
┼┼┼┼
││││
┬┬┬┬
│o││
┼┼┼┼
o│o│
┼┼┼┼
││││
┼┼┼┼
││││
┬┬┬┬
o│││
┼┼┼┼
│││o
┼┼┼┼
││││
┼┼┼┼
│o││

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
val n: int
val D: n: int -> int
val E: n: int -> int
val F: n: int -> int
val G: n: int -> int
val A: n: int -> int
val B: n: int -> int
val sharp: n: int -> int
val flat: n: int -> int
val Cd: (int -> int)
val Db: (int -> int)
val Dd: (int -> int)
val Eb: (int -> int)
val Fd: (int -> int)
val Gb: (int -> int)
val Gd: (int -> int)
val Ab: (int -> int)
val Ad: (int -> int)
val Bb: (int -> int)
Multiple items
type StructAttribute = inherit Attribute new: unit -> StructAttribute
<summary>Adding this attribute to a type causes it to be represented using a CLI struct.</summary>
<category>Attributes</category>


--------------------
new: unit -> StructAttribute
Multiple items
type CustomComparisonAttribute = inherit Attribute new: unit -> CustomComparisonAttribute
<summary>Adding this attribute to a type indicates it is a type with a user-defined implementation of comparison.</summary>
<category>Attributes</category>


--------------------
new: unit -> CustomComparisonAttribute
Multiple items
type CustomEqualityAttribute = inherit Attribute new: unit -> CustomEqualityAttribute
<summary>Adding this attribute to a type indicates it is a type with a user-defined implementation of equality.</summary>
<category>Attributes</category>


--------------------
new: unit -> CustomEqualityAttribute
Multiple items
type StructuredFormatDisplayAttribute = inherit Attribute new: value: string -> StructuredFormatDisplayAttribute member Value: string
<summary>This attribute is used to mark how a type is displayed by default when using '%A' printf formatting patterns and other two-dimensional text-based display layouts. In this version of F# valid values are of the form <c>PreText {PropertyName1} PostText {PropertyName2} ... {PropertyNameX} PostText</c>. The property names indicate properties to evaluate and to display instead of the object itself. </summary>
<category>Attributes</category>


--------------------
new: value: string -> StructuredFormatDisplayAttribute
Multiple items
[<Struct>] type Note = 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 static member (+) : string: Note * fret: int -> Note member Display: string member Note: int ...

--------------------
Note ()
new: note: int -> Note
val note: int
Multiple items
val int: value: 'T -> int (requires member op_Explicit)
<summary>Converts the argument to signed 32-bit integer. This is a direct conversion for all primitive numeric types. For strings, the input is converted using <c>Int32.Parse()</c> with InvariantCulture settings. Otherwise the operation requires an appropriate static conversion method on the input type.</summary>
<param name="value">The input value.</param>
<returns>The converted int</returns>


--------------------
[<Struct>] type int = int32
<summary>An abbreviation for the CLI type <see cref="T:System.Int32" />.</summary>
<category>Basic Types</category>


--------------------
type int<'Measure> = int
<summary>The type of 32-bit signed integer numbers, annotated with a unit of measure. The unit of measure is erased in compiled code and when values of this type are analyzed using reflection. The type is representationally equivalent to <see cref="T:System.Int32" />.</summary>
<category>Basic Types with Units of Measure</category>
val __: inref<Note>
val other: obj
val other: Note
property Note.Note: int with get
val name: string
property Note.names: string[] with get
val octave: int
val sprintf: format: Printf.StringFormat<'T> -> 'T
<summary>Print to a string using the given format.</summary>
<param name="format">The formatter.</param>
<returns>The formatted result.</returns>
val this: inref<Note>
type IEquatable<'T> = member Equals: other: 'T -> bool
<summary>Defines a generalized method that a value type or class implements to create a type-specific method for determining equality of instances.</summary>
<typeparam name="T">The type of objects to compare.</typeparam>
Multiple items
type IComparable = member CompareTo: obj: obj -> int
<summary>Defines a generalized type-specific comparison method that a value type or class implements to order or sort its instances.</summary>

--------------------
type IComparable<'T> = member CompareTo: other: 'T -> int
<summary>Defines a generalized comparison method that a value type or class implements to create a type-specific comparison method for ordering or sorting its instances.</summary>
<typeparam name="T">The type of object to compare.</typeparam>
val compare: e1: 'T -> e2: 'T -> int (requires comparison)
<summary>Generic comparison.</summary>
<param name="e1">The first value.</param>
<param name="e2">The second value.</param>
<returns>The result of the comparison.</returns>
Multiple items
val string: Note

--------------------
type string = String
<summary>An abbreviation for the CLI type <see cref="T:System.String" />.</summary>
<category>Basic Types</category>
val fret: int
val notes: (int list -> Note list)
Multiple items
module List from Microsoft.FSharp.Collections
<summary>Contains operations for working with values of type <see cref="T:Microsoft.FSharp.Collections.list`1" />.</summary>
<namespacedoc><summary>Operations for collections such as lists, arrays, sets, maps and sequences. See also <a href="https://docs.microsoft.com/dotnet/fsharp/language-reference/fsharp-collection-types">F# Collection Types</a> in the F# Language Guide. </summary></namespacedoc>


--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T member IsEmpty: bool member Item: index: int -> 'T with get ...
<summary>The type of immutable singly-linked lists.</summary>
<remarks>Use the constructors <c>[]</c> and <c>::</c> (infix) to create values of this type, or the notation <c>[1;2;3]</c>. Use the values in the <c>List</c> module to manipulate values of this type, or pattern match against the values directly. </remarks>
<exclude />
val map: mapping: ('T -> 'U) -> list: 'T list -> 'U list
<summary>Builds a new collection whose elements are the results of applying the given function to each of the elements of the collection.</summary>
<param name="mapping">The function to transform elements from the input list.</param>
<param name="list">The input list.</param>
<returns>The list of transformed elements.</returns>
val strings: Note list
val quality: (int list -> Set<Note>)
Multiple items
module Set from Microsoft.FSharp.Collections
<summary>Contains operations for working with values of type <see cref="T:Microsoft.FSharp.Collections.Set`1" />.</summary>

--------------------
type Set<'T (requires comparison)> = interface IReadOnlyCollection<'T> 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 ...
<summary>Immutable sets based on binary trees, where elements are ordered by F# generic comparison. By default comparison is the F# structural comparison function or uses implementations of the IComparable interface on element values.</summary>
<remarks>See the <see cref="T:Microsoft.FSharp.Collections.SetModule" /> module for further operations on sets. All members of this class are thread-safe and may be used concurrently from multiple threads.</remarks>


--------------------
new: elements: seq<'T> -> Set<'T>
val ofList: elements: 'T list -> Set<'T> (requires comparison)
<summary>Builds a set that contains the same elements as the given list.</summary>
<param name="elements">The input list.</param>
<returns>A set containing the elements form the input list.</returns>
val M: n: int -> Set<Note>
val m: n: int -> Set<Note>
val M7: n: int -> Set<Note>
val findFrets: chord: Set<Note> -> string: Note -> (int * Note) list
val chord: Set<Note>
val filter: predicate: ('T -> bool) -> list: 'T list -> 'T list
<summary>Returns a new collection containing only the elements of the collection for which the given predicate returns "true"</summary>
<param name="predicate">The function to test the input elements.</param>
<param name="list">The input list.</param>
<returns>A list containing only the elements that satisfy the predicate.</returns>
val contains: element: 'T -> set: Set<'T> -> bool (requires comparison)
<summary>Evaluates to "true" if the given element is in the given set.</summary>
<param name="element">The element to test.</param>
<param name="set">The input set.</param>
<returns>True if <c>element</c> is in <c>set</c>.</returns>
val filterChord: chord: Set<'a> -> missingNotes: Set<'a> -> solution: (int * 'a) list -> stringFrets: (int * 'a) list list -> (int * 'a) list option (requires comparison)
val chord: Set<'a> (requires comparison)
val missingNotes: Set<'a> (requires comparison)
val solution: (int * 'a) list (requires comparison)
val stringFrets: (int * 'a) list list (requires comparison)
val isEmpty: set: Set<'T> -> bool (requires comparison)
<summary>Returns "true" if the set is empty.</summary>
<param name="set">The input set.</param>
<returns>True if <c>set</c> is empty.</returns>
union case Option.Some: Value: 'T -> Option<'T>
<summary>The representation of "Value of type 'T"</summary>
<param name="Value">The input value.</param>
<returns>An option representing the value.</returns>
val rev: list: 'T list -> 'T list
<summary>Returns a new list with the elements in reverse order.</summary>
<param name="list">The input list.</param>
<returns>The reversed list.</returns>
union case Option.None: Option<'T>
<summary>The representation of "No value"</summary>
Multiple items
val string: (int * 'a) list (requires comparison)

--------------------
type string = String
<summary>An abbreviation for the CLI type <see cref="T:System.String" />.</summary>
<category>Basic Types</category>
val tail: (int * 'a) list list (requires comparison)
val note: 'a (requires comparison)
val choose: chooser: ('T -> 'U option) -> list: 'T list -> 'U list
<summary>Applies the given function to each element of the list. Returns the list comprised of the results <c>x</c> for each element where the function returns Some(x)</summary>
<param name="chooser">The function to generate options from the elements.</param>
<param name="list">The input list.</param>
<returns>The list comprising the values selected from the chooser function.</returns>
val remove: value: 'T -> set: Set<'T> -> Set<'T> (requires comparison)
<summary>Returns a new set with the given element removed. No exception is raised if the set doesn't contain the given element.</summary>
<param name="value">The element to remove.</param>
<param name="set">The input set.</param>
<returns>The input set with <c>value</c> removed.</returns>
val sortBy: projection: ('T -> 'Key) -> list: 'T list -> 'T list (requires comparison)
<summary>Sorts the given list using keys given by the given projection. Keys are compared using <see cref="M:Microsoft.FSharp.Core.Operators.compare" />.</summary>
<remarks>This is a stable sort, i.e. the original order of equal elements is preserved.</remarks>
<param name="projection">The function to transform the list elements into the type to be compared.</param>
<param name="list">The input list.</param>
<returns>The sorted list.</returns>
val s: (int * 'a) list (requires comparison)
val sumBy: projection: ('T -> 'U) -> list: 'T list -> 'U (requires member (+) and member get_Zero)
<summary>Returns the sum of the results generated by applying the function to each element of the list.</summary>
<param name="projection">The function to transform the list elements into the type to be summed.</param>
<param name="list">The input list.</param>
<returns>The resulting sum.</returns>
val tryHead: list: 'T list -> 'T option
<summary>Returns the first element of the list, or <c>None</c> if the list is empty.</summary>
<param name="list">The input list.</param>
<returns>The first element of the list or None.</returns>
val chord: root: (int -> 'a) -> quality: ('a -> Set<Note>) -> (int * Note) list
val root: (int -> 'a)
val quality: ('a -> Set<Note>)
module Option from Microsoft.FSharp.Core
<summary>Contains operations for working with options.</summary>
<category>Options</category>
val get: option: 'T option -> 'T
<summary>Gets the value associated with the option.</summary>
<param name="option">The input option.</param>
<example><code> Some 42 |&gt; Option.get // evaluates to 42 None |&gt; Option.get // throws exception! </code></example>
<returns>The value within the option.</returns>
<exception href="System.ArgumentException">Thrown when the option is None.</exception>
val CM: (int * Note) list
val Cm: (int * Note) list
val print: chord: (int * 'a) list -> unit
val chord: (int * 'a) list
val fret: ('b -> 'b -> string) (requires equality)
val n: 'b (requires equality)
val frt: 'b (requires equality)
val line: (('b * 'c) list -> 'b -> string) (requires equality)
val chord: ('b * 'c) list (requires equality)
val fst: tuple: ('T1 * 'T2) -> 'T1
<summary>Return the first element of a tuple, <c>fst (a,b) = a</c>.</summary>
<param name="tuple">The input tuple.</param>
<returns>The first value.</returns>
Multiple items
type String = interface IEnumerable<char> interface IEnumerable interface ICloneable interface IComparable interface IComparable<string> interface IConvertible interface IEquatable<string> new: value: nativeptr<char> -> unit + 8 overloads member Clone: unit -> obj member CompareTo: value: obj -> int + 1 overload ...
<summary>Represents text as a sequence of UTF-16 code units.</summary>

--------------------
String(value: nativeptr<char>) : String
String(value: char[]) : String
String(value: ReadOnlySpan<char>) : String
String(value: nativeptr<sbyte>) : String
String(c: char, count: int) : String
String(value: nativeptr<char>, startIndex: int, length: int) : String
String(value: char[], startIndex: int, length: int) : String
String(value: nativeptr<sbyte>, startIndex: int, length: int) : String
String(value: nativeptr<sbyte>, startIndex: int, length: int, enc: Text.Encoding) : String
val concat: sep: string -> strings: seq<string> -> string
<summary>Returns a new string made by concatenating the given strings with separator <c>sep</c>, that is <c>a1 + sep + ... + sep + aN</c>.</summary>
<param name="sep">The separator string to be inserted between the strings of the input sequence.</param>
<param name="strings">The sequence of strings to be concatenated.</param>
<returns>A new string consisting of the concatenated strings separated by the separation string.</returns>
<exception cref="T:System.ArgumentNullException">Thrown when <c>strings</c> is null.</exception>
val printfn: format: Printf.TextWriterFormat<'T> -> 'T
<summary>Print to <c>stdout</c> using the given format, and add a newline.</summary>
<param name="format">The formatter.</param>
<returns>The formatted result.</returns>
namespace NAudio
namespace NAudio.Midi
val device: MidiOut
Multiple items
type MidiOut = interface IDisposable new: deviceNo: int -> unit member Close: unit -> unit member Dispose: unit -> unit member Reset: unit -> unit member Send: message: int -> unit member SendBuffer: byteBuffer: byte[] -> unit member SendDriverMessage: message: int * param1: int * param2: int -> unit static member DeviceInfo: midiOutDeviceNumber: int -> MidiOutCapabilities member Volume: int ...

--------------------
MidiOut(deviceNo: int) : MidiOut
MidiOut.DeviceInfo(midiOutDeviceNumber: int) : MidiOutCapabilities
val midi: m: MidiMessage -> unit
val m: MidiMessage
Multiple items
type MidiMessage = new: status: int * data1: int * data2: int -> unit + 1 overload static member ChangeControl: controller: int * value: int * channel: int -> MidiMessage static member ChangePatch: patch: int * channel: int -> MidiMessage static member StartNote: note: int * volume: int * channel: int -> MidiMessage static member StopNote: note: int * volume: int * channel: int -> MidiMessage member RawData: int

--------------------
MidiMessage(rawData: int) : MidiMessage
MidiMessage(status: int, data1: int, data2: int) : MidiMessage
MidiOut.Send(message: int) : unit
property MidiMessage.RawData: int with get
val startNote: note: int -> volume: int -> unit
val volume: int
MidiMessage.StartNote(note: int, volume: int, channel: int) : MidiMessage
val stopNote: note: int -> volume: int -> unit
MidiMessage.StopNote(note: int, volume: int, channel: int) : MidiMessage
val sleep: n: int -> unit
namespace System.Threading
Multiple items
type Thread = inherit CriticalFinalizerObject new: start: ParameterizedThreadStart -> unit + 3 overloads member Abort: unit -> unit + 1 overload member DisableComObjectEagerCleanup: unit -> unit member GetApartmentState: unit -> ApartmentState member GetCompressedStack: unit -> CompressedStack member GetHashCode: unit -> int member Interrupt: unit -> unit member Join: unit -> unit + 2 overloads member Resume: unit -> unit ...
<summary>Creates and controls a thread, sets its priority, and gets its status.</summary>

--------------------
Threading.Thread(start: Threading.ParameterizedThreadStart) : Threading.Thread
Threading.Thread(start: Threading.ThreadStart) : Threading.Thread
Threading.Thread(start: Threading.ParameterizedThreadStart, maxStackSize: int) : Threading.Thread
Threading.Thread(start: Threading.ThreadStart, maxStackSize: int) : Threading.Thread
Threading.Thread.Sleep(timeout: TimeSpan) : unit
Threading.Thread.Sleep(millisecondsTimeout: int) : unit
type Direction = | Dn of int | Up of int
union case Direction.Dn: int -> Direction
union case Direction.Up: int -> Direction
val play: tempo: int -> arpegio: int -> chord: ('a * Note) list * strum: Direction -> unit
val tempo: int
val arpegio: int
val chord: ('a * Note) list
val strum: Direction
val strings: ('a * Note) list
val length: int
val iter: action: ('T -> unit) -> list: 'T list -> unit
<summary>Applies the given function to each element of the collection.</summary>
<param name="action">The function to apply to elements from the input list.</param>
<param name="list">The input list.</param>
val n: Note
val arpegioLength: int
val length: list: 'T list -> int
<summary>Returns the length of the list.</summary>
<param name="list">The input list.</param>
<returns>The length of the list.</returns>
val strum: strm: 'a list -> chord: 'b -> ('b * 'a) list
val strm: 'a list
val chord: 'b
val repeatedChord: 'b list
val zip: list1: 'T1 list -> list2: 'T2 list -> ('T1 * 'T2) list
<summary>Combines the two lists into a list of pairs. The two lists must have equal lengths.</summary>
<param name="list1">The first input list.</param>
<param name="list2">The second input list.</param>
<returns>A single list containing pairs of matching elements from the input lists.</returns>
val luckyChords: (int * Note) list list
val luckyStrum: Direction list
val getLucky: ((int * Note) list * Direction) list
val collect: mapping: ('T -> 'U list) -> list: 'T list -> 'U list
<summary>For each element of the list, applies the given function. Concatenates all the results and return the combined list.</summary>
<param name="mapping">The function to transform each input element into a sublist to be concatenated.</param>
<param name="list">The input list.</param>
<returns>The concatenation of the transformed sublists.</returns>
val replicate: count: int -> initial: 'T -> 'T list
<summary>Creates a list by replicating the given initial value.</summary>
<param name="count">The number of elements to replicate.</param>
<param name="initial">The value to replicate</param>
<returns>The generated list.</returns>
val concat: lists: seq<'T list> -> 'T list
<summary>Returns a new list that contains the elements of each the lists in order.</summary>
<param name="lists">The input sequence of lists.</param>
<returns>The resulting concatenated list.</returns>