Imports System.Speech
Imports System.Speech.Recognition
Imports System.Speech.Synthesis
Public Class Form2
'Nuovo Engine di riconoscimento vocale
Private Engine As New SpeechRecognitionEngine
'GrammarBuilder per costruire la grammatica
Private GrammarBuilder As New GrammarBuilder
'Oggetto Grammar che rappresenta la grammatica
Private Grammar As Grammar
'Questo dizionario associa ad ogni parola il
'corrispondente valore numerico (one=1)
Private TextNumber
As Dictionary(Of
String, Int32
)
'Questo array già inizializzato contiene l'elenco di
'tutte le parole che l'engine può rilevare
Private Numbers As String() = _
New String() {"one", "two", "three", "four", _
"five", "six", "seven", "eight", "nine", "ten", _
"eleven", "twelve", "thirteen", "fourteen", _
"fiftheen", "sixteen", "seventeen", "eighteen", _
"nineteen", "twenty", "thirty", "fourty", "fifty", _
"sixty", "seventy", "eighty", "ninty", "hundred", _
"thousand", "reset"}
'L'ultima parola, reset, serve per porre a 0 il
'conteggio, nel caso si volesse ripetere
'Delegato che servirà dopo
Private Delegate Sub SetLabel(ByVal Res As RecognitionResult)
'Prev ricorda l'ultimo numero immesso
Private Prev As Int32
'Result contiene il numero finale
Private Result As Int32
Private Sub Form2_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'All'avvio del form, si imposta l'input dell'engine sul
'normale microfono (che deve essere collegato al computer).
'Anche in questo caso si usa un thread, per lo stesso
'motivo citato nel capitolo precedente
Dim T As New Threading.Thread(AddressOf Engine.SetInputToDefaultAudioDevice)
T.Start()
T.Join()
'Poi si genera il dizionario che associa le parole ai
'valori numerici veri e propri. Dato che l'array
'Numbers contiene i numeri in ordine, sfruttermo
'qualche for per riempire il dizionario in poche
'righe di codice
With TextNumber
'I primi 20 numeri sono in ordine crescente,
'da 1 a 20. Perciò basta aggiungere 1
'all'indice I per ottenere il numero che la
'parola indica
For I As Int16 = 0 To 19
.Add(Numbers(I), I + 1)
Next
'I successivi sette numeri sono tutti i multipli
'di 10, da 30 a 90. Con la formula:
'(I-19)*10 + 20
'` come se I andasse da 1 a 7 e quindi
'otteniamo tutte le decine da 20+10 a 20+70
For I As Int16 = 20 To 26
.Add(Numbers(I), (I - 19) * 10 + 20)
Next
'Infine si aggiungono centinaia e migliaia a parte
.Add("hundred", 100)
.Add("thousand", 1000)
End With
'Aggiunge tutte le parole-numero al GrammarBuilder
GrammarBuilder.Append(New Choices(Numbers))
'Imposta la lingua a inglese
GrammarBuilder.Culture = Globalization.CultureInfo.GetCultureInfo("en-US")
'Costruisce la nuova "grammatica" con il GrammarBuilder
Grammar = New Grammar(GrammarBuilder)
'Questo metodo serve per eliminare tutte le grammatiche
'già presenti. Anche se quasi sicuramente non ci
'sarà nessun grammatica precaricata, è sempre
'meglio farlo prima di aggiungerne di nuove
Engine.UnloadAllGrammars()
'Quindi carica la grammatica Grammar. Ora Engine è in
'grado di riconoscere le parole dell'array Numbers
Engine.LoadGrammar(Grammar)
'Parte importantissima: aggiunge l'handler di evento per
'l'evento SpeechRecognized, che viene lanciato quando
'l'engine ha ascoltato la voce, l'ha analizzata e ha
'trovato una corrispondenza valida nella sua grammatica
AddHandler Engine.SpeechRecognized, AddressOf Speech_Recognized
End Sub
Private Sub btnStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStart.Click
'Fa partire il riconoscimento vocale. Il metodo è asincrono,
'quindi viene eseguito su un altro thread e non blocca il form
'chiamante. L'argomento Multiple indica che si effetteranno più
'riconoscimenti e non uno solo
Engine.RecognizeAsync(RecognizeMode.Multiple)
'Disabilita Start e abilita Stop
btnStart.Enabled = False
btnStop.Enabled = True
End Sub
Private Sub btnStop_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStop.Click
'Termina il riconoscimento asincrono
Engine.RecognizeAsyncCancel()
'Abilita Start e disabilita Stop
btnStart.Enabled = True
btnStop.Enabled = False
End Sub
Private Sub Speech_Recognized(ByVal sender As Object, ByVal e As SpeechRecognizedEventArgs)
'Può capitare che dopo l'esecuzione di questo evento,
'sia generata un'eccezione TargetInvocationException, causata
'dall'engine, il quale lancia un evento uguale prima che
'questo sia terminato. Usando un thread risolviamo tutto
Dim T As New Threading.Thread(AddressOf InvokeSetLabel)
T.Start(e.Result)
End Sub
Private Sub InvokeSetLabel(ByVal Res As RecognitionResult)
'Ovviamente questi stupido tipo di errori ci fa usare
'una via alternativa sprecando molto codice in più.
'Dato che, come sapete, non si può accedere ai
'controlli di un form da un thread differente da quello
'in cui sono stati creati, dobbiamo usare Invoke
'per far eseguire lo stesso compito al thread principale
'partendo da questo thread secondario.
'Per chi non si ricorda i delegate, Invoke permette di
'far correre un metodo nel thread dell'oggetto da cui è
'richiamato (Me, ossia il form). In questo caso usiamo
'il delegato di tipo SetLabel che punta ad AnalyuzeText
'e gli passiamo dierettamente Res come parametro
Me.Invoke(New SetLabel(AddressOf AnalyzeText), New Object() {Res})
End Sub
Private Sub AnalyzeText(ByVal Res As RecognitionResult)
Dim N As Int32
'Ottiene il testo, ossia la parola pronunciata
Dim Text As String = Res.Text
'Se il testo è "reset", annulla tutto
If Text = "reset" Then
Result = 0
End If
'Se il testo è contenuto nel dizionario, allora
'è un numero valido
If TextNumber.ContainsKey(Text) Then
'Ottiene il numero
N = TextNumber(Text)
'Se è 100, significa che si è pronunciato
'"hundred". Hundred indica le centinaia e perciò
'sicuramente non si può dire "twenty hundred", né
'"one thousand hundred": l'unico caso in cui si può
'usare hundred è dopo una singola cifra, ad esempio
'"one hundred" o "nine hundred". Quindi controlla che il
'numero precedente sia compreso tra 1 e 9
If (N = 100) And (Prev > 0 And Prev < 10) Then
'Togli l'unità
Result -= Prev
'E la trasforma in centinaia
Result += Prev * 100
End If
'Parimenti, si può usare "thousand" solo dopo un
'numero minore di mille. Anche se lecito, nessuno direbbe
'"a thousand thousand", ma piuttosto "a million"
If (N = 1000) And (Result < 1000) Then
Result *= 1000
End If
'Se il numero è minore di 100, semplicemente lo
'aggiunge. Se quindi si pronunciano "twenty" e "thirty"
'di seguito, si otterà 50. Non chiedetemi perchè
'l'ho fatto così...
If (N < 100) Then
Result += N
End If
Else
N = 0
End If
Prev = N
'Imposta il testo della label
lblNumber.Text = String.Format("{0:N0}", Result)
End Sub
End Class