Think Before Coding

To content | To menu | To search

Thursday, December 17, 2015

Ukulele Fun for XMas !

Ukulele

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:

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

Now C minor:

1: 
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)]
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

1: 
chord C M |> print

It prints

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

Another one

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

 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 1
|> List.concat
|> List.iter (play 130 30)

And the tab notations for the song !

1: 
2: 
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 !

Wednesday, December 24, 2014

Speaking computers for more fun !

I didn't try it on mono, but it should also work with some tweaking, see details here

Xmas is a good time to surprise kids, and what's more fun than a talking computer ?!

Hello world !

Nothing's easier, and this kind of Hello World will appeal them to programming in a flash :

#r "System.Speech"
open System.Speech.Synthesis

let synt = new SpeechSynthesizer()
let say s = synt.Speak(s: string)

say "Hello world !" 

Of course, if you're french like me, it'll say this with an awful french accent - something like hélo ouorld !

But you can select a different voice if available by providing hints:

open System.Globalization
let english = CultureInfo.GetCultureInfo("en-US")

synt.SelectVoiceByHints(VoiceGender.NotSet, VoiceAge.NotSet, 1, english)
say "Hello world !"

Far better !

Can you beat it ?

Now, a talking fizz buzz, up to 100 ! Can you beat it ?

[1 .. 100]
|> List.map (fun n -> 
    match n%3, n%5 with
    | 0, 0 -> "FizzBuzz"
    | 0, _ -> "Fizz"
    | _, 0 -> "Buzz"
    | _ -> string n )
|> List.iter say

Even harder !

Now with a recognizer, we can wait for voice user input.

The problem with the Grammar API is that it's totally mutable and not really DSL oriented. Let's correct that :

open System.Speech.Recognition

type Grammar =
    | Phrase of text:string * result: string
    | Lst of Grammar list
    | Alt of Grammar list
    | Repeat of min: int * max: int * Grammar


let rec build = function
    | Phrase (text, result) -> 
        // Just build the a single phrase
        GrammarBuilder(SemanticResultValue(text,result))

    | Lst grammars -> 
        // Append parts of grammars one after the other
        let builder = GrammarBuilder()
        grammars
        |> List.map build
        |> List.iter builder.Append
        builder

    | Alt alternatives -> 
        // Create alternatives
        let choices =
            alternatives
            |> List.map build 
            |> List.toArray
        GrammarBuilder(Choices())

    | Repeat(min, max, grammar) -> 
        // Repeat a part of the grammar
        GrammarBuilder(build grammar, min, max)

This is not a full DSL for speach recognition, you can look at all the GrammarBuilder methods to add more possibilities.. Even here, I'll use only Phrase and Alt.

Now, we need a recognizer and wire the grammar with functions that will be called when a part of the grammar is recognized or rejected. It is mandatory to set grammar's culture to the recognizer's culture. There's usually a single recognizer installed by default on your system and it uses installed system's culture. In my case, it'll be french.

let recog = new SpeechRecognizer()

let recognize grammar recognized rejected  = 
    let builder = build grammar
    builder.Culture <- recog.RecognizerInfo.Culture
    printfn "%A" recog.RecognizerInfo.Culture
    recog.LoadGrammar(Grammar builder)
    
    recog.SpeechRecognized |> Event.add (fun e -> recognized e.Result.Text (string e.Result.Semantics.Value))
    recog.SpeechRecognitionRejected |> Event.add (fun e -> rejected ())
    recog.Enabled

We can then use this to create a little Christmass quizz thanks to the FSharp.Data FreeBase Type Provider !

We'll use free base to find a list of Actors who plaid Santa in movies.

For this, install the FSharp.Data NuGet:

nuget install FSharp.Data -o packages -x

The dll should be in .\packages\FSharp.Data\lib\net40\FSharp.Data.dll

#r @"packages\FSharp.Data\lib\net40\FSharp.Data.dll"
open FSharp.Data

let fb =FreebaseData.GetDataContext()

Let's build the grammar

let santaActorsFilms =
    fb.``Arts and Entertainment``
      .Film
      .``Film characters``
      .IndividualsAZ.S
      .``Santa Claus``
      .``Portrayed in films``
    |> Seq.map (fun c -> c.Actor.Name, c.Film.Name)
    |> Seq.toList

let santaActorsGrammar =
    santaActorsFilms
    |> List.map (fun (actor,film) -> Phrase(actor, film))
    |> Alt

Here is the function to call when an actor is recognized.

I tried to pass a discriminated union as a value, but even if the API uses an object, the documentation states that it has to be a bool, an int or a string. I used only strings here.

let recognized text value =
    say (sprintf "True ! %s was Santa in %s" text value)

Here is the function when the speech could not be matched with the grammar.

It is also possible to get the audio of the text in this case. I decided to ignore it due to time constraints.

let rejected () = say "No, Not a Santa !"

Now, let's run it !!

recognize santaActorsGrammar recognized rejected

At this point the speech recognition configuration should appear if it's the first time you use it.

Once done you should be able to try the quizz !

If your OS culture is not english, don't hesitate to use a local accent for actor's name !

Conlusion

I hope you had fun with this API, and that you'll want to tweak it for your own demo !

The full code - using FSharp.Formatting - is on my gist

Happy Christmass !

namespace System
namespace System.Speech
namespace System.Speech.Synthesis
val synt : SpeechSynthesizer

Full name: XMas fun.synt
Multiple items
type SpeechSynthesizer =
  new : unit -> SpeechSynthesizer
  member AddLexicon : uri:Uri * mediaType:string -> unit
  member Dispose : unit -> unit
  member GetCurrentlySpokenPrompt : unit -> Prompt
  member GetInstalledVoices : unit -> ReadOnlyCollection<InstalledVoice> + 1 overload
  member Pause : unit -> unit
  member Rate : int with get, set
  member RemoveLexicon : uri:Uri -> unit
  member Resume : unit -> unit
  member SelectVoice : name:string -> unit
  ...

Full name: System.Speech.Synthesis.SpeechSynthesizer

--------------------
SpeechSynthesizer() : unit
val say : s:string -> unit

Full name: XMas fun.say
val s : string
SpeechSynthesizer.Speak(promptBuilder: PromptBuilder) : unit
SpeechSynthesizer.Speak(prompt: Prompt) : unit
SpeechSynthesizer.Speak(textToSpeak: string) : unit
Multiple items
val string : value:'T -> string

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

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
namespace System.Globalization
val english : CultureInfo

Full name: XMas fun.english
Multiple items
type CultureInfo =
  new : name:string -> CultureInfo + 3 overloads
  member Calendar : Calendar
  member ClearCachedData : unit -> unit
  member Clone : unit -> obj
  member CompareInfo : CompareInfo
  member CultureTypes : CultureTypes
  member DateTimeFormat : DateTimeFormatInfo with get, set
  member DisplayName : string
  member EnglishName : string
  member Equals : value:obj -> bool
  ...

Full name: System.Globalization.CultureInfo

--------------------
CultureInfo(name: string) : unit
CultureInfo(culture: int) : unit
CultureInfo(name: string, useUserOverride: bool) : unit
CultureInfo(culture: int, useUserOverride: bool) : unit
CultureInfo.GetCultureInfo(name: string) : CultureInfo
CultureInfo.GetCultureInfo(culture: int) : CultureInfo
CultureInfo.GetCultureInfo(name: string, altName: string) : CultureInfo
SpeechSynthesizer.SelectVoiceByHints(gender: VoiceGender) : unit
SpeechSynthesizer.SelectVoiceByHints(gender: VoiceGender, age: VoiceAge) : unit
SpeechSynthesizer.SelectVoiceByHints(gender: VoiceGender, age: VoiceAge, voiceAlternate: int) : unit
SpeechSynthesizer.SelectVoiceByHints(gender: VoiceGender, age: VoiceAge, voiceAlternate: int, culture: CultureInfo) : unit
type VoiceGender =
  | NotSet = 0
  | Male = 1
  | Female = 2
  | Neutral = 3

Full name: System.Speech.Synthesis.VoiceGender
field VoiceGender.NotSet = 0
type VoiceAge =
  | NotSet = 0
  | Child = 10
  | Teen = 15
  | Adult = 30
  | Senior = 65

Full name: System.Speech.Synthesis.VoiceAge
field VoiceAge.NotSet = 0
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val map : mapping:('T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.map
val n : int
val iter : action:('T -> unit) -> list:'T list -> unit

Full name: Microsoft.FSharp.Collections.List.iter
namespace System.Speech.Recognition
type Grammar =
  | Phrase of text: string * result: string
  | Lst of Grammar list
  | Alt of Grammar list
  | Repeat of min: int * max: int * Grammar

Full name: XMas fun.Grammar
union case Grammar.Phrase: text: string * result: string -> Grammar
union case Grammar.Lst: Grammar list -> Grammar
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
union case Grammar.Alt: Grammar list -> Grammar
union case Grammar.Repeat: min: int * max: int * Grammar -> Grammar
val min : e1:'T -> e2:'T -> 'T (requires comparison)

Full name: Microsoft.FSharp.Core.Operators.min
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<_>
val max : e1:'T -> e2:'T -> 'T (requires comparison)

Full name: Microsoft.FSharp.Core.Operators.max
val build : _arg1:Grammar -> GrammarBuilder

Full name: XMas fun.build
val text : string
val result : string
Multiple items
type GrammarBuilder =
  new : unit -> GrammarBuilder + 7 overloads
  member Append : phrase:string -> unit + 7 overloads
  member AppendDictation : unit -> unit + 1 overload
  member AppendRuleReference : path:string -> unit + 1 overload
  member AppendWildcard : unit -> unit
  member Culture : CultureInfo with get, set
  member DebugShowPhrases : string
  static member Add : phrase:string * builder:GrammarBuilder -> GrammarBuilder + 4 overloads

Full name: System.Speech.Recognition.GrammarBuilder

--------------------
GrammarBuilder() : unit
GrammarBuilder(phrase: string) : unit
GrammarBuilder(alternateChoices: Choices) : unit
GrammarBuilder(key: SemanticResultKey) : unit
GrammarBuilder(value: SemanticResultValue) : unit
GrammarBuilder(phrase: string, subsetMatchingCriteria: SubsetMatchingMode) : unit
GrammarBuilder(phrase: string, minRepeat: int, maxRepeat: int) : unit
GrammarBuilder(builder: GrammarBuilder, minRepeat: int, maxRepeat: int) : unit
Multiple items
type SemanticResultValue =
  new : value:obj -> SemanticResultValue + 2 overloads
  member ToGrammarBuilder : unit -> GrammarBuilder

Full name: System.Speech.Recognition.SemanticResultValue

--------------------
SemanticResultValue(value: obj) : unit
SemanticResultValue(phrase: string, value: obj) : unit
SemanticResultValue(builder: GrammarBuilder, value: obj) : unit
val grammars : Grammar list
val builder : GrammarBuilder
GrammarBuilder.Append(value: SemanticResultValue) : unit
GrammarBuilder.Append(key: SemanticResultKey) : unit
GrammarBuilder.Append(alternateChoices: Choices) : unit
GrammarBuilder.Append(builder: GrammarBuilder) : unit
GrammarBuilder.Append(phrase: string) : unit
GrammarBuilder.Append(phrase: string, subsetMatchingCriteria: SubsetMatchingMode) : unit
GrammarBuilder.Append(builder: GrammarBuilder, minRepeat: int, maxRepeat: int) : unit
GrammarBuilder.Append(phrase: string, minRepeat: int, maxRepeat: int) : unit
val alternatives : Grammar list
val choices : GrammarBuilder []
val toArray : list:'T list -> 'T []

Full name: Microsoft.FSharp.Collections.List.toArray
Multiple items
type Choices =
  new : unit -> Choices + 2 overloads
  member Add : params phrases:string[] -> unit + 1 overload
  member ToGrammarBuilder : unit -> GrammarBuilder

Full name: System.Speech.Recognition.Choices

--------------------
Choices() : unit
Choices(params phrases: string []) : unit
Choices(params alternateChoices: GrammarBuilder []) : unit
val min : int
val max : int
val grammar : Grammar
val recog : SpeechRecognizer

Full name: XMas fun.recog
Multiple items
type SpeechRecognizer =
  new : unit -> SpeechRecognizer
  member AudioFormat : SpeechAudioFormatInfo
  member AudioLevel : int
  member AudioPosition : TimeSpan
  member AudioState : AudioState
  member Dispose : unit -> unit
  member EmulateRecognize : inputText:string -> RecognitionResult + 2 overloads
  member EmulateRecognizeAsync : inputText:string -> unit + 2 overloads
  member Enabled : bool with get, set
  member Grammars : ReadOnlyCollection<Grammar>
  ...

Full name: System.Speech.Recognition.SpeechRecognizer

--------------------
SpeechRecognizer() : unit
val recognize : grammar:Grammar -> recognized:(string -> string -> unit) -> rejected:(unit -> unit) -> bool

Full name: XMas fun.recognize
val recognized : (string -> string -> unit)
val rejected : (unit -> unit)
property GrammarBuilder.Culture: CultureInfo
property SpeechRecognizer.RecognizerInfo: RecognizerInfo
property RecognizerInfo.Culture: CultureInfo
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
SpeechRecognizer.LoadGrammar(grammar: Grammar) : unit
event SpeechRecognizer.SpeechRecognized: IEvent<System.EventHandler<SpeechRecognizedEventArgs>,SpeechRecognizedEventArgs>
Multiple items
module Event

from Microsoft.FSharp.Control

--------------------
type Event<'T> =
  new : unit -> Event<'T>
  member Trigger : arg:'T -> unit
  member Publish : IEvent<'T>

Full name: Microsoft.FSharp.Control.Event<_>

--------------------
type Event<'Delegate,'Args (requires delegate and 'Delegate :> Delegate)> =
  new : unit -> Event<'Delegate,'Args>
  member Trigger : sender:obj * args:'Args -> unit
  member Publish : IEvent<'Delegate,'Args>

Full name: Microsoft.FSharp.Control.Event<_,_>

--------------------
new : unit -> Event<'T>

--------------------
new : unit -> Event<'Delegate,'Args>
val add : callback:('T -> unit) -> sourceEvent:IEvent<'Del,'T> -> unit (requires delegate and 'Del :> System.Delegate)

Full name: Microsoft.FSharp.Control.Event.add
val e : SpeechRecognizedEventArgs
property RecognitionEventArgs.Result: RecognitionResult
property RecognizedPhrase.Text: string
property RecognizedPhrase.Semantics: SemanticValue
property SemanticValue.Value: obj
event SpeechRecognizer.SpeechRecognitionRejected: IEvent<System.EventHandler<SpeechRecognitionRejectedEventArgs>,SpeechRecognitionRejectedEventArgs>
val e : SpeechRecognitionRejectedEventArgs
property SpeechRecognizer.Enabled: bool
namespace FSharp
namespace FSharp.Data
val fb : FreebaseData.ServiceTypes.FreebaseService

Full name: XMas fun.fb
type FreebaseData =
  static member GetDataContext : unit -> FreebaseService
  nested type ServiceTypes

Full name: FSharp.Data.FreebaseData


<summary>Typed representation of Freebase data. See http://www.freebase.com for terms and conditions.</summary>
FreebaseData.GetDataContext() : FreebaseData.ServiceTypes.FreebaseService
val santaActorsFilms : (string * string) list

Full name: XMas fun.santaActorsFilms
property FreebaseData.ServiceTypes.Film.Film.Film_characterDataIndividualsAZ.S: FreebaseData.ServiceTypes.Film.Film.Film_characterDataIndividualsAZ.Film_characterDataIndividualsIndexedS


<summary>An indexing of specific named individuals of type &apos;Film character&apos; in the web data store</summary>
module Seq

from Microsoft.FSharp.Collections
val map : mapping:('T -> 'U) -> source:seq<'T> -> seq<'U>

Full name: Microsoft.FSharp.Collections.Seq.map
val c : FreebaseData.ServiceTypes.Film.Film.PerformanceData
property FreebaseData.ServiceTypes.Film.Film.PerformanceData.Actor: FreebaseData.ServiceTypes.Film.Film.ActorData


<summary></summary>
property Runtime.Freebase.IFreebaseObject.Name: string
property FreebaseData.ServiceTypes.Film.Film.PerformanceData.Film: FreebaseData.ServiceTypes.Film.Film.FilmData


<summary></summary>
val toList : source:seq<'T> -> 'T list

Full name: Microsoft.FSharp.Collections.Seq.toList
val santaActorsGrammar : Grammar

Full name: XMas fun.santaActorsGrammar
val actor : string
val film : string
val recognized : text:string -> value:string -> unit

Full name: XMas fun.recognized
val value : string
val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
val rejected : unit -> unit

Full name: XMas fun.rejected

Thursday, April 4, 2013

C# Static interfaces - Take 3 - redirect

The actual post has been moved to a new address

C# Static interfaces - Take 3

You may believe it or not, but the post that drains most of the traffic of this blog, is the one about C# static interfaces !

 

In october 2009, I simply tried to imagine where the idea of C# static interfaces could lead us, and, since then, I have more viewed pages (> 15%) on this post than on my home page !

 

And since then, nothing moved in this area in the C# langage, and I don’t expect it to happen soon.

 

But some other thing happened…

 

F#

 

Yes F# is out and running on almost all platforms, and it can do what I described in the previous post.

 

The thing is called Statically Resolved Type Parameters and is closer to C++ templates than from C# generics.

 

The trick is that you can define an inline function with statically resolved types, denoted by a ^ prefix. The usage of defined methods on the type is not given here by an interface, but by a constraint on the resolved type :

let inline count (counter: ^T) =
    let value = (^T: (member Count : int) counter) 
    value

here , the count function takes a counter of type ^T (statically resolved).

The second line express that ^T actually should have a member Count of type int, and that it will call it on counter to get the result value !

 

Magic !

 

Now, we can call count on various types that have a Count member property like :

type FakeCounter() =
    member this.Count = 42;

or

type ImmutableCounter(count: int) =
    member this.Count = count;
    member this.Next() = ImmutableCounter(count + 1)

or

type MutableCounter(count: int) =
    let mutable count = 0
    member this.Count = count;
    member this.Next() = count <- count + 1

without needing an interface !

For instance :

let c = count (new FakeCounter())

True, this is compile time duck typing !

 

And it works with methods :

let inline quack (duck: ^T) =
    let value = (^T: (member Quack : int -> string) (duck, 3))  
    value

This will call a Quack method that takes int and returns string with the value 3 on any object passed to it that has a method corresponding to the constraint.

And magically enough, you can do it with static methods :

let inline nextThenstaticCount (counter: ^T) =
    (^T: (member Next : unit -> unit) counter)
    let value = (^T: (static member Count : int) ()) 
    value

this function calls an instance method called Next, then gets the value of a static property called Count and returns the value !

It also works with operators :

let inline mac acc x y = acc + x * y

notice the signature of this function :

acc: ^a -> x: ^c -> y: ^d ->  ^e
    when ( ^a or  ^b) : (static member ( + ) :  ^a *  ^b ->  ^e) and
         ( ^c or  ^d) : (static member ( * ) :  ^c *  ^d ->  ^b)

It accepts any types as long as they provide expected + and * operators.

 

The only thing is that a specific implementation of the function will be compiled for each type on which it’s called. That’s why it called statically resolved.

 

You can use this kind of method from F# code but not from C#.

 

Anyway…

No need for static interfaces in C#, use F# !