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.
Change creatobject to createobject . Dim msg, sapi Set sapi = createObject("sapi. spvoice") Set sapi. Voice = sapi.
It's .NET programming, SPVOICE is the object and the MSDN library provides all the methods you can use to enumerate and set voice patterns. Check that out and if you understand .NET you can get that to work.
The Voice property can be thought of as the person of a voice object; examples of Voices are "Microsoft Mary" and "Microsoft Mike.". Use the GetVoices method to determine what voices are available. SpVoice. The owning object.
This works on PowerShell 7.2.1 on Windows 11. Just tried and it played all 3 voices. 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).
Select a voice name in the list box and then click Command1; the Command1 procedure sets the voice object's Voice property to the selected name, and causes the voice to speak its new name.
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 callComInterop.ComBinder.TryBindSetMember
in PowerShell Core is a stub method).
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With