# encoding: ascii # api: powershell # title: Speech Recognition # description: This is an update to my “Speech.psm1” script module for doing voice/speech recognition. With this version, speech macros will be executed asynchronously, so it doesn’t tie up the shell for the duration :) Of course, if the shell is actually BUSY, it will delay execution of macros. See usage examples at the bottom of the script. # version: 0.1 # type: module # author: Joel Bennett # license: CC0 # function: Update-SpeechCommands # x-poshcode-id: 2671 # x-archived: 2017-03-18T12:51:32 # x-published: 2012-05-10T23:49:00 # # $null = [Reflection.Assembly]::LoadWithPartialName("System.Speech") ## Create the two main objects we need for speech recognition and synthesis if(!$Global:SpeechModuleListener){ ## For XP's sake, don't create them twice... $Global:SpeechModuleSpeaker = new-object System.Speech.Synthesis.SpeechSynthesizer $Global:SpeechModuleListener = new-object System.Speech.Recognition.SpeechRecognizer } $Script:SpeechModuleMacros = @{} ## Add a way to turn it off $Script:SpeechModuleMacros.Add("Stop Listening", { $script:listen = $false; Suspend-Listening }) $Script:SpeechModuleComputerName = ${Env:ComputerName} function Update-SpeechCommands { #.Synopsis # Recreate the speech recognition grammar #.Description # This parses out the speech module macros, # and recreates the speech recognition grammar and semantic results, # and then updates the SpeechRecognizer with the new grammar, # and makes sure that the ObjectEvent is registered. $choices = new-object System.Speech.Recognition.Choices foreach($choice in $Script:SpeechModuleMacros.GetEnumerator()) { $g = New-Object System.Speech.Recognition.GrammarBuilder $phrases = @($choice.Key -split "\s*\*\s*") for($i=0;$i -lt $phrases.Count;$i++) { if($phrases[$i].Length -gt 0) { $g.Append( $phrases[$i] ) if($i+1 -lt $phrases.Count) { $g.AppendDictation() } } elseif($i -eq 0) { $g.AppendDictation() } } $choices.Add( (New-Object System.Speech.Recognition.SemanticResultValue $g, $choice.Value.ToString()).ToGrammarBuilder() ) } if($VerbosePreference -ne "SilentlyContinue") { $Script:SpeechModuleMacros.Keys | ForEach-Object { Write-Host "$($Script:SpeechModuleComputerName), $_" -Fore Cyan } } $builder = New-Object System.Speech.Recognition.GrammarBuilder "$($Script:SpeechModuleComputerName), " $builder.Append((New-Object System.Speech.Recognition.SemanticResultKey "Commands", $choices.ToGrammarBuilder())) $grammar = new-object System.Speech.Recognition.Grammar $builder $grammar.Name = "Power VoiceMacros" ## Take note of the events, but only once (make sure to remove the old one) Unregister-Event "SpeechModuleCommandRecognized" -ErrorAction SilentlyContinue $null = Register-ObjectEvent $grammar SpeechRecognized ` -SourceIdentifier "SpeechModuleCommandRecognized" ` -Action { iex $event.SourceEventArgs.Result.Semantics.Item("Commands").Value } $Global:SpeechModuleListener.UnloadAllGrammars() $Global:SpeechModuleListener.LoadGrammarAsync( $grammar ) } function Add-SpeechCommands { #.Synopsis # Add one or more commands to the speech-recognition macros, and update the recognition #.Parameter CommandText # The string key for the command to remove [CmdletBinding()] Param([hashtable]$VoiceMacros,[string]$Computer=$Script:SpeechModuleComputerName) ## Add the new macros $Script:SpeechModuleMacros += $VoiceMacros ## Update the default if they change it, so they only have to do that once. $Script:SpeechModuleComputerName = $Computer Update-SpeechCommands } function Remove-SpeechCommands { #.Synopsis # Remove one or more command from the speech-recognition macros, and update the recognition #.Parameter CommandText # The string key for the command to remove Param([string[]]$CommandText) foreach($command in $CommandText) { $Script:SpeechModuleMacros.Remove($Command) } Update-SpeechCommands } function Clear-SpeechCommands { #.Synopsis # Removes all commands from the speech-recognition macros, and update the recognition #.Parameter CommandText # The string key for the command to remove $Script:SpeechModuleMacros = @{} ## Default value: A way to turn it off $Script:SpeechModuleMacros.Add("Stop Listening", { Suspend-Listening }) Update-SpeechCommands } function Start-Listening { #.Synopsis # Sets the SpeechRecognizer to Enabled $Global:SpeechModuleListener.Enabled = $true Say "Speech Macros are $($Global:SpeechModuleListener.State)" Write-Host "Speech Macros are $($Global:SpeechModuleListener.State)" } function Suspend-Listening { #.Synopsis # Sets the SpeechRecognizer to Disabled $Global:SpeechModuleListener.Enabled = $false Say "Speech Macros are disabled" Write-Host "Speech Macros are disabled" } function Suspend-Speech { $Global:SpeechModuleSpeaker.Pause() } function Resume-Speech { $Global:SpeechModuleSpeaker.Resume() } function Out-Speech { #.Synopsis # Speaks the input object #.Description # Uses the default SpeechSynthesizer settings to speak the string representation of the InputObject #.Parameter InputObject # The object to speak # NOTE: this should almost always be a pre-formatted string, # most objects don't render to very speakable text. Param( [Parameter(Position=0)] [Object[]] $Property , [Parameter()] [String] $Expand , [Parameter()] [Object] $GroupBy , [Parameter(ValueFromPipeline=$true)] [Alias("IO")] [PSObject]$InputObject , [Parameter()] [String] $View , [Parameter()] [Switch] $Async ) begin { if($Async) { $PSBoundParameters.Remove("Async") | out-null } $InputCollection = new-object System.Collections.Generic.List[PSObject] } process { $InputCollection.Add($InputObject) } end { # $PSBoundParameters.Remove("InputObject") | out-null $PSBoundParameters["InputObject"] = $InputCollection if(!$Async) { # Write-Host $($PSBoundParameters | ft |out-string) # Write-Host $($InputCollection | ft |out-string) -fore Gray # Format-List @PSBoundParameters | Out-String -Stream | Write-Host -Fore Yellow Format-List @PSBoundParameters | ForEach-Object { if($_.GetType().Name -eq "FormatEntryData") { $_; sleep -milli 500} else {$_} } | Out-String -stream | ForEach-Object { Write-Host $_ $Global:SpeechModuleSpeaker.Speak($_) } } else { $speech = (Format-List @PSBoundParameters | Out-String -width 1e4) -replace "`r`n`r`n","`r`n`r`n`r`n" Write-Host $speech $Global:SpeechModuleSpeaker.SpeakAsync($speech) | Out-Null } } } function Remove-SpeechXP { #.Synopis # Dispose of the SpeechModuleListener and SpeechModuleSpeaker $Global:SpeechModuleListener.Dispose(); $Global:SpeechModuleListener = $null $Global:SpeechModuleSpeaker.Dispose(); $Global:SpeechModuleSpeaker = $null } set-alias asc Add-SpeechCommands set-alias rsc Remove-SpeechCommands set-alias csc Clear-SpeechCommands set-alias say Out-Speech set-alias listen Start-Listening Export-ModuleMember -Function * -Alias * -Variable SpeachModuleListener, SpeechModuleSpeaker ################################################################################################### ## USAGE EXAMPLES: ################################################################################################### # Add-SpeechCommands @{ # "What time is it?" = { Say "It is $(Get-Date -f "h:mm tt")" } # "What day is it?" = { Say $(Get-Date -f "dddd, MMMM dd") } # "What's running?" = { # $proc = ps | sort ws -desc # Say $("$($proc.Count) processes, including $($proc[0].name), which is using " + # "$([int]($proc[0].ws/1mb)) megabytes of memory") # } # } -Computer "Laptop" -Verbose # # Add-SpeechCommands @{ "Run Notepad" = { & "C:\Programs\DevTools\Notepad++\notepad++.exe" } } # Add-SpeechCommands @{ "Check Gee Mail" = { Start-Process "https://mail.google.com" } } # Add-SpeechCommands @{ "Run Notepad" = { & "C:\Programs\DevTools\Notepad++\notepad++.exe" } }