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 et40\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 !
Full name: XMas fun.synt
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
Full name: XMas fun.say
SpeechSynthesizer.Speak(prompt: Prompt) : unit
SpeechSynthesizer.Speak(textToSpeak: string) : unit
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = System.String
Full name: Microsoft.FSharp.Core.string
Full name: XMas fun.english
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(culture: int) : CultureInfo
CultureInfo.GetCultureInfo(name: string, altName: string) : CultureInfo
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
| NotSet = 0
| Male = 1
| Female = 2
| Neutral = 3
Full name: System.Speech.Synthesis.VoiceGender
| NotSet = 0
| Child = 10
| Teen = 15
| Adult = 30
| Senior = 65
Full name: System.Speech.Synthesis.VoiceAge
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<_>
Full name: Microsoft.FSharp.Collections.List.map
Full name: Microsoft.FSharp.Collections.List.iter
| 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
Full name: Microsoft.FSharp.Collections.list<_>
Full name: Microsoft.FSharp.Core.Operators.min
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<_>
Full name: Microsoft.FSharp.Core.Operators.max
Full name: XMas fun.build
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
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
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
Full name: Microsoft.FSharp.Collections.List.toArray
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
Full name: XMas fun.recog
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
Full name: XMas fun.recognize
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
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>
Full name: Microsoft.FSharp.Control.Event.add
Full name: XMas fun.fb
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>
Full name: XMas fun.santaActorsFilms
<summary>An indexing of specific named individuals of type 'Film character' in the web data store</summary>
from Microsoft.FSharp.Collections
Full name: Microsoft.FSharp.Collections.Seq.map
<summary></summary>
<summary></summary>
Full name: Microsoft.FSharp.Collections.Seq.toList
Full name: XMas fun.santaActorsGrammar
Full name: XMas fun.recognized
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
Full name: XMas fun.rejected