hugmys0ul
hugmys0ul

Reputation: 143

How to change the voice used for SAPI.SPVoice

I would like to be able to select an alternative voice for my Text-To-Speech output.
I am using the ComObject SAPI.SPVoice but I am finding that I cannot change the actual voice used.
(BTW - I am using SAPI.SPVoice because it works in both PowerShell Core and PowerShell Desktop on Windows 10)

${PromptTTS} = New-Object -ComObject SAPI.SPVoice
❯ $PromptTTS | gm
   TypeName: System.__ComObject#{269316d8-57bd-11d2-9eee-00c04f797396}

Name                                   MemberType   Definition
----                                   ----------   ----------
DisplayUI                              Method       void DisplayUI (int hWndParent, string Title, string TypeOfUI, Variant ExtraData)
GetAudioOutputs                        Method       ISpeechObjectTokens GetAudioOutputs (string RequiredAttributes, string OptionalAttributes)
GetVoices                              Method       ISpeechObjectTokens GetVoices (string RequiredAttributes, string OptionalAttributes)
IsUISupported                          Method       bool IsUISupported (string TypeOfUI, Variant ExtraData)
Pause                                  Method       void Pause ()
Resume                                 Method       void Resume ()
Skip                                   Method       int Skip (string Type, int NumItems)
Speak                                  Method       int Speak (string Text, SpeechVoiceSpeakFlags Flags)
SpeakCompleteEvent                     Method       int SpeakCompleteEvent ()
SpeakStream                            Method       int SpeakStream (ISpeechBaseStream Stream, SpeechVoiceSpeakFlags Flags)
WaitUntilDone                          Method       bool WaitUntilDone (int msTimeout)
AlertBoundary                          Property     SpeechVoiceEvents AlertBoundary () {get} {set}
AllowAudioOutputFormatChangesOnNextSet Property     bool AllowAudioOutputFormatChangesOnNextSet () {get} {set}
AudioOutput                            Property     ISpeechObjectToken AudioOutput () {get} {set by ref}
AudioOutputStream                      Property     ISpeechBaseStream AudioOutputStream () {get} {set by ref}
EventInterests                         Property     SpeechVoiceEvents EventInterests () {get} {set}
Priority                               Property     SpeechVoicePriority Priority () {get} {set}
Rate                                   Property     int Rate () {get} {set}
Status                                 Property     ISpeechVoiceStatus Status () {get}
SynchronousSpeakTimeout                Property     int SynchronousSpeakTimeout () {get} {set}
Voice                                  Property     ISpeechObjectToken Voice () {get} {set by ref}
Volume                                 Property     int Volume () {get} {set}
queryMSDNClassInfo                     ScriptMethod System.Object queryMSDNClassInfo();

My research indicates that I should be able to:

❯ $PromptTTS.Voice = ${PromptTTS}.GetVoices().Item(0) ; $PromptTTS.Speak("Hello voice 0")
❯ $PromptTTS.Voice = ${PromptTTS}.GetVoices().Item(1) ; $PromptTTS.Speak("Hello voice 1")
❯ $PromptTTS.Voice = ${PromptTTS}.GetVoices().Item(2) ; $PromptTTS.Speak("Hello voice 2")

and so on.

However, whilst the commands execute without error, the voice used /heard does not change.

Upvotes: 9

Views: 11846

Answers (2)

mklement0
mklement0

Reputation: 437953

Update:


Unfortunately, assigning to .Voice in order to change the speaking voice does not work in PowerShell Core, as of v7.1.0-preview.2 - it only works in Windows PowerShell (PowerShell versions up to v5.1).

.NET Core's COM support is limited, and while PowerShell (Core) in part compensates for that, there are things that still do not work.

In effect, the following assignment is quietly ignored in PowerShell (Core) 6+:

# !! IGNORED in PowerShell [Core] 6+ - the default voice (David)
# !! is NOT changed (to Hedda).
$PromptTTS.Voice = $PromptTTS.GetVoices().Item(1)

Personally, I do not know of a workaround (at least with PowerShell code alone).


Technical background:

Inspecting the .Voice property with $PromptTTS | Get-Member Voice yields:

   TypeName: System.__ComObject#{269316d8-57bd-11d2-9eee-00c04f797396}

Name  MemberType Definition                                    
----  ---------- ----------                                    
Voice Property   ISpeechObjectToken Voice () {get} {set by ref}

I suspect that the set by ref part is the problem, which may be related to the following problem, quoted from this GitHub issue:

the ComBinder is not supported in .NET Core (the call ComInterop.ComBinder.TryBindSetMember in PowerShell Core is a stub method).

Upvotes: 5

PrakashKumar
PrakashKumar

Reputation: 61

Here is what I tried and it seems to work just fine on PowerShell 7.1

Param (
    [switch] $Male,
    [switch] $Female,
    $Text = "Please input a text in double quotes"
 )
if ($Female)
  {$voice = 1}
else
  {$voice = 0}
$Speak = New-Object -ComObject SAPI.SPVoice
$Speak.Voice = $Speak.GetVoices().Item($voice)
$Speak.Speak("$text") |Out-Null

Upvotes: 4

Related Questions