How to change the voice used for SAPI.SPVoice
Asked Answered
T

2

6

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.

Tadzhik answered 26/4, 2020 at 14:4 Comment(4)
Works on my machine, Win10 1909, I get Hazel (female), David (male), Zira (different female). Can you show us the output from $PromptTTS.GetVoices()?Dichlorodiphenyltrichloroethane
@mathias-r-jessen -- is this working in PowerShell Core?Tadzhik
This works on PowerShell 7.2.1 on Windows 11. Just tried and it played all 3 voices.Blush
You can always change default text to speech voice in windows control panel, look for 'text to speak' in start menuNegotiation
E
5

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).

Edessa answered 26/4, 2020 at 19:22 Comment(2)
mklement0 - I just did this on my Win10 PSv5x/PSv7 both work as expected.Milkweed
@postanote: That it works in Windows PowerShell (up to v5.1) is to be expected; in PowerShell (Core) 7.0 (and the latest preview) it does not work - the voice isn't changed - at least on my Windows 10 Pro (64-bit; Version 1909, OS Build: 18363.778) machine; but, to be clear: I don't think the problem is related to the specific Windows 10 version.Edessa
A
2

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
Am answered 23/3, 2022 at 12:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.