Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 610
hugmys0ul Avatar asked Apr 26 '20 14:04

hugmys0ul


People also ask

How do I change the voice of Sapi?

Change creatobject to createobject . Dim msg, sapi Set sapi = createObject("sapi. spvoice") Set sapi. Voice = sapi.

What is a spvoice object?

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.

What is the difference between voice and spvoice?

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.

Is it possible to change the speaking voice in PowerShell?

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

How do I change the name of a voice object?

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.


2 Answers

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

like image 123
mklement0 Avatar answered Oct 21 '22 21:10

mklement0


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
like image 29
PrakashKumar Avatar answered Oct 21 '22 21:10

PrakashKumar