Topics

How to implement automatic switching of speech synthesizers?


Oleksandr Gryshchenko
 

Hi colleagues,
Faced an issue that I can't solve.
If you could, tell me please how to switch in the Python code the synthesizer to another, read the message and switch the synthesizer back to the original?
I studied the code of several add-ons, and if I enter commands in the NVDA Python console - everything works well.

import speech, ui

>>> speech.setSynth("RHVoice")
turns on the RHVoice synthesizer

>>> ui.message("My message")
reads message using RHVoice synthesizer

>>> speech.setSynth("espeak")
turns on the eSpeak synthesizer

But when I try to execute these commands in a row, the synthesizers are switch, but the message does not sound.
For example:

def messages():
    speech.setSynth("RHVoice")
    ui.message("First message")
    speech.setSynth("espeak")
    ui.message("Second message")

When executing this code, I hear nothing but silence...
What is my mistake? What did i miss?

Grateful for any tips.
Oleksandr


Stefan Moisei
 

Hi,
I am not the most qualified to answer this, but, since no one did...
I think the reason this happens is that the speak function returns immediately, without waiting until the message is spoken. therefore, after the first speak call, the synth is changed immediately, before the text is spoken. To test if this theory is true, try adding a wx.callLater between the speech and set synth calls. this is of course not the solution, as you can’t estimate how long a text will take. the right way is to use a callback command to let you know when the speech has finished, so you can change the synth.
 

From: Oleksandr Gryshchenko
Sent: Wednesday, October 7, 2020 12:29 AM
To: nvda-addons@nvda-addons.groups.io
Subject: [nvda-addons] How to implement automatic switching of speech synthesizers?
 
Hi colleagues,
Faced an issue that I can't solve.
If you could, tell me please how to switch in the Python code the synthesizer to another, read the message and switch the synthesizer back to the original?
I studied the code of several add-ons, and if I enter commands in the NVDA Python console - everything works well.

import speech, ui

>>> speech.setSynth("RHVoice")
turns on the RHVoice synthesizer

>>> ui.message("My message")
reads message using RHVoice synthesizer

>>> speech.setSynth("espeak")
turns on the eSpeak synthesizer

But when I try to execute these commands in a row, the synthesizers are switch, but the message does not sound.
For example:

def messages():
    speech.setSynth("RHVoice")
    ui.message("First message")
    speech.setSynth("espeak")
    ui.message("Second message")

When executing this code, I hear nothing but silence...
What is my mistake? What did i miss?

Grateful for any tips.
Oleksandr


Oleksandr Gryshchenko
 

Hi Stefan,

I'm very grateful to you for the answer!
I also came to the same conclusion experimentally.
Using a delay between changing synthesizers with time.sleep() I can hear some part of the message of the first synthesizer.

On GitHub, I found a few issues where were discussed callbacks that are caused when speech stops.
For example, calllbacks which are called when a given synth finishes speaking and when overall speech has stopped:
https://github.com/nvaccess/nvda/issues/4877

But I don't understand what objects we can use to access these features.
I wrote here with the hope that I will get help from more experienced developers and save some time.
I guess I'll have to learn the NVDA API better.

Stefan, Thank you again for hinting in which direction I need to look for information.
I will continue to study this question.

I wish everyone a wonderful weekend!
Oleksandr


Stefan Moisei
 

Hi Oleksandr,
look in speech.py. I think CallbackCommand is what you want. something like:
s=[“example text”, CallbackCommand(finished, args)]
def finished....
change synth here
speech.speak(s)
Hopefully one of the experts might provide working code. I remember getting this to work once, but can’t remember exactly how.
Please post a snippet if you get it working, I think this would come useful forf a lot of addons.
hth

From: Oleksandr Gryshchenko
Sent: Friday, October 9, 2020 11:43 PM
To: nvda-addons@nvda-addons.groups.io
Subject: Re: [nvda-addons] How to implement automatic switching of speech synthesizers?
 
Hi Stefan,

I'm very grateful to you for the answer!
I also came to the same conclusion experimentally.
Using a delay between changing synthesizers with time.sleep() I can hear some part of the message of the first synthesizer.

On GitHub, I found a few issues where were discussed callbacks that are caused when speech stops.
For example, calllbacks which are called when a given synth finishes speaking and when overall speech has stopped:
https://github.com/nvaccess/nvda/issues/4877

But I don't understand what objects we can use to access these features.
I wrote here with the hope that I will get help from more experienced developers and save some time.
I guess I'll have to learn the NVDA API better.

Stefan, Thank you again for hinting in which direction I need to look for information.
I will continue to study this question.

I wish everyone a wonderful weekend!
Oleksandr


Oleksandr Gryshchenko
 

Hi all!
Stefan, thank you again for your help!
That was what was needed!
Thanks to your help, I was able to understand this issue.
Everything turned out to be very simple :)

The speech.commands module contains a set of classes that can perform various actions in the order of the queue passed to the speech.speak() function.
Including there is also a class CallbackCommand. It can be imported directly from the speech module.
Below is a fragment of working code, maybe this question will be useful for someone else.

from speech import CallbackCommand, speak

def restoreDefaultSynth():
    # restores the default speech synthesizer

sequence = []
sequence.append(text)
sequence.append(CallbackCommand(callback=restoreDefaultSynth))
speak(speechSequence)

Good luck to all!
And, Stefan, thank you very much for your help!


ChrisLM
 

Oh well! Great...

I'm curious to understand how do you reload the previow synth only.

E.G., using espeak as default synth, I want set sapi5 to say something, then go back to the synthesizer that  usually use.

I tried with the following code, but that's not exactly what I want:


Import config

    def restoreDefaultSynth():

        config.conf.reset(factoryDefaults=False)
        nameSynth = config.conf["speech"]["synth"]
        speech.setSynth(nameSynth)



Thanks,

Chris.

Oleksandr Gryshchenko ha scritto il 10/10/2020 alle 19:59:

from speech import CallbackCommand, speak

def restoreDefaultSynth():
    # restores the default speech synthesizer

sequence = []
sequence.append(text)
sequence.append(CallbackCommand(callback=restoreDefaultSynth))
speak(speechSequence)


Oleksandr Gryshchenko
 

Hello Chris,

As I understand it, when set a new synthesizer you also need to use a dict with its configuration.
This is very well shown in Switch Synth Add-on by Tyler Spivey:
https://addons.nvda-project.org/addons/switchSynth.en.html

For example:
import speech, config

# Get the current synthesizer configuration
name = speech.getSynth().name
conf = dict(config.conf['speech'][name].items())

# Restore a previously saved synthesizer
config.conf.profiles[0]['speech'][name].clear()
config.conf.profiles[0]['speech'][name].update(conf)
config.conf['speech'][name]._cache.clear()
speech.setSynth(name)
speech.getSynth().saveSettings()

I use this and it works.
Good luck!


Locutor Antonio Cezar
 

Hello, Oleksandr. Is that from your commitment and dedication, I can hope to use InstantTranslate with a second speech to pronounce the translation into my language in an appropriate synthesizer for her, as I listen to the texts in their original languages ​​with their Correspondents synthesizers !? ... If this is possible, I will be very happy and I'm sure I will greatly enhance my learning English, for example, because it's making me really miss now so I can advance in my profession ... Thank you.



Locutor Antonio Cezar

Em 11/10/2020 15:04, Oleksandr Gryshchenko escreveu:

Hello Chris,

As I understand it, when set a new synthesizer you also need to use a dict with its configuration.
This is very well shown in Switch Synth Add-on by Tyler Spivey:
https://addons.nvda-project.org/addons/switchSynth.en.html

For example:
import speech, config

# Get the current synthesizer configuration
name = speech.getSynth().name
conf = dict(config.conf['speech'][name].items())

# Restore a previously saved synthesizer
config.conf.profiles[0]['speech'][name].clear()
config.conf.profiles[0]['speech'][name].update(conf)
config.conf['speech'][name]._cache.clear()
speech.setSynth(name)
speech.getSynth().saveSettings()

I use this and it works.
Good luck!


Oleksandr Gryshchenko
 

Hi Antonio,
Yes, I've added this feature to the Quick Dictionary add-on, which I know well.
Now I will update ReadMe and I will post a link to the updated version in this group.
If everything will works well, we will be able to transfer this functionality to Instant Translate.
Good luck to all!


Locutor Antonio Cezar
 

My eternal gratitude to you and to all who dedicate their time and expertise in the maintenance and development of Add-Ons for the community of our NVDA... Thanks.



Locutor Antonio Cezar

Em 11/10/2020 16:49, Oleksandr Gryshchenko escreveu:

Hi Antonio,
Yes, I've added this feature to the Quick Dictionary add-on, which I know well.
Now I will update ReadMe and I will post a link to the updated version in this group.
If everything will works well, we will be able to transfer this functionality to Instant Translate.
Good luck to all!


ChrisLM
 

ok! In this way works better and more effective.

Thanks and good luck!

Ciao,


Chris.

Oleksandr Gryshchenko ha scritto il 11/10/2020 alle 20:04:

For example:
import speech, config

# Get the current synthesizer configuration
name = speech.getSynth().name
conf = dict(config.conf['speech'][name].items())

# Restore a previously saved synthesizer
config.conf.profiles[0]['speech'][name].clear()
config.conf.profiles[0]['speech'][name].update(conf)
config.conf['speech'][name]._cache.clear()
speech.setSynth(name)
speech.getSynth().saveSettings()


Stefan Moisei
 

Hi,
You are welcome. Do you have any idea how one might pass function arguements to callback? i.e. let’s say the function is not called restore synth, but set sinth and that it receives the synth name as a parameter.
thanks
 

From: Oleksandr Gryshchenko
Sent: Saturday, October 10, 2020 8:59 PM
To: nvda-addons@nvda-addons.groups.io
Subject: Re: [nvda-addons] How to implement automatic switching of speech synthesizers?
 
Hi all!
Stefan, thank you again for your help!
That was what was needed!
Thanks to your help, I was able to understand this issue.
Everything turned out to be very simple :)

The speech.commands module contains a set of classes that can perform various actions in the order of the queue passed to the speech.speak() function.
Including there is also a class CallbackCommand. It can be imported directly from the speech module.
Below is a fragment of working code, maybe this question will be useful for someone else.

from speech import CallbackCommand, speak

def restoreDefaultSynth():
    # restores the default speech synthesizer

sequence = []
sequence.append(text)
sequence.append(CallbackCommand(callback=restoreDefaultSynth))
speak(speechSequence)

Good luck to all!
And, Stefan, thank you very much for your help!


Oleksandr Gryshchenko
 

Hi colleagues,

Stefan, I reviewed the source code of the speech module and came to the conclusion that there is no simple way to transfer the parameters of the call-back function.
Below I quote a fragment of the code of the speech.commands module:

```
class CallbackCommand(BaseCallbackCommand):

    def __init__(self, callback, name: Optional[str] = None):
        self._callback = callback
        self._name = name if name else repr(callback)

    def run(self,*args, **kwargs):
        return self._callback(*args,**kwargs)
```

Thus it is possible to transfer parameters to the run() method.
For example:

def changeSynth(synthName):
    # change voice synthesizer

cb = CallbackCommand(callback=changeSynth)
cb.run(synthName)

But the speak() function calls the run() method on its own, so the parameter must be passed in some other way.
The above code also shows that the CallbackCommand class constructor accepts only two parameters: callback and name.
Unfortunately, I didn't understand how to do it, maybe it can be suggested by one of the more experienced developers.

I change the synthesizer separately before calling the speak() function. For example:

changeSynth(synthName)
sequence = [
    text,
    CallbackCommand(callback=restoreDefaultSynth)
]
speak(sequence)

Also in the speech module there is the following comment:

```
from .commands import (  # noqa: F401
    # F401 imported but unused:
    # The following are imported here because other files that speech.py
    # previously relied on "import * from .commands"
    # New commands added to commands.py should be directly imported only where needed.
    # Usage of these imports is deprecated and will be removed in 2021.1
    SynthCommand,
    IndexCommand,
    SynthParamCommand,
    BreakCommand,
    BaseProsodyCommand,
    VolumeCommand,
    RateCommand,
    PhonemeCommand,
    BaseCallbackCommand,
    CallbackCommand,
    WaveFileCommand,
    ConfigProfileTriggerCommand,
)
```

Therefore, all these commands must be imported directly from the speech.commands module, otherwise from 2021 they will stop working.
Good luck to all!
Oleksandr


Cyrille
 

Hi Stefan

 

This snippet should work :

 

sequence = [

    text,

    CallbackCommand(callback=lambda: changeSynth(synthName))

]

speak(sequence)

 

The expression

lambda: changeSynth(synthName)

allows you to create on the fly a function that takes nothing as argument (so OK for callback running) and executes changeSynth(synthName)

 

The following code is equivalent:

def myCallback():

    changeSynth(synthName)

sequence = [

    text,

    CallbackCommand(callback=myCallback)

]

speak(sequence)

 

 

HTH

Cheers,

 

Cyrille

 

 

De : nvda-addons@nvda-addons.groups.io <nvda-addons@nvda-addons.groups.io> De la part de Oleksandr Gryshchenko
Envoyé : mardi 13 octobre 2020 18:23
À : nvda-addons@nvda-addons.groups.io
Objet : Re: [nvda-addons] How to implement automatic switching of speech synthesizers?

 

Hi colleagues,

Stefan, I reviewed the source code of the speech module and came to the conclusion that there is no simple way to transfer the parameters of the call-back function.
Below I quote a fragment of the code of the speech.commands module:

```
class CallbackCommand(BaseCallbackCommand):

    def __init__(self, callback, name: Optional[str] = None):
        self._callback = callback
        self._name = name if name else repr(callback)

    def run(self,*args, **kwargs):
        return self._callback(*args,**kwargs)
```

Thus it is possible to transfer parameters to the run() method.
For example:

def changeSynth(synthName):
    # change voice synthesizer

cb = CallbackCommand(callback=changeSynth)
cb.run(synthName)

But the speak() function calls the run() method on its own, so the parameter must be passed in some other way.
The above code also shows that the CallbackCommand class constructor accepts only two parameters: callback and name.
Unfortunately, I didn't understand how to do it, maybe it can be suggested by one of the more experienced developers.

I change the synthesizer separately before calling the speak() function. For example:

changeSynth(synthName)
sequence = [
    text,
    CallbackCommand(callback=restoreDefaultSynth)
]
speak(sequence)

Also in the speech module there is the following comment:

```
from .commands import (  # noqa: F401
    # F401 imported but unused:
    # The following are imported here because other files that speech.py
    # previously relied on "import * from .commands"
    # New commands added to commands.py should be directly imported only where needed.
    # Usage of these imports is deprecated and will be removed in 2021.1
    SynthCommand,
    IndexCommand,
    SynthParamCommand,
    BreakCommand,
    BaseProsodyCommand,
    VolumeCommand,
    RateCommand,
    PhonemeCommand,
    BaseCallbackCommand,
    CallbackCommand,
    WaveFileCommand,
    ConfigProfileTriggerCommand,
)
```

Therefore, all these commands must be imported directly from the speech.commands module, otherwise from 2021 they will stop working.
Good luck to all!
Oleksandr