Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix SAPI4 failing to load some voices #17726

Merged
merged 7 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions source/synthDrivers/sapi4.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,13 @@ def __init__(self):
self._rateDelta = 0
self._pitchDelta = 0
self._volume = 100
self._paused = False
self.voice = str(self._enginesList[0].gModeID)

def terminate(self):
self._bufSink._allowDelete = True
self._ttsCentral = None
self._ttsAttrs = None

def speak(self, speechSequence: SpeechSequence):
textList = []
Expand Down Expand Up @@ -268,6 +271,12 @@ def cancel(self):
# cancel all pending bookmarks
self._bookmarkLists.clear()
self._bookmarks = None
if self._paused:
# Unpause the voice before resetting,
# because some voices keep the pausing state
# even after resetting.
self._ttsCentral.AudioResume()
self._paused = False
self._ttsCentral.AudioReset()
except COMError:
log.error("Error cancelling speech", exc_info=True)
Expand All @@ -282,11 +291,12 @@ def pause(self, switch: bool):
log.debugWarning("Error pausing speech", exc_info=True)
else:
self._ttsCentral.AudioResume()
self._paused = switch

def removeSetting(self, name):
# Putting it here because currently no other synths make use of it. OrderedDict, where you are?
for i, s in enumerate(self.supportedSettings):
if s.name == name:
if s.id == name:
del self.supportedSettings[i]
return

Expand All @@ -305,7 +315,17 @@ def _set_voice(self, val):
self._ttsAudio = CoCreateInstance(CLSID_MMAudioDest, IAudioMultiMediaDevice)
self._ttsAudio.DeviceNumSet(_mmDeviceEndpointIdToWaveOutId(config.conf["audio"]["outputDevice"]))
if self._ttsCentral:
self._ttsCentral.UnRegister(self._sinkRegKey)
try:
# Some SAPI4 synthesizers may fail this call.
self._ttsCentral.UnRegister(self._sinkRegKey)
except COMError:
log.debugWarning("Error unregistering ITTSCentral sink", exc_info=True)
# Some SAPI4 synthesizers assume that only one instance of ITTSCentral
# will be created by the client, and will stop working if more are created.
# Here we make sure that the previous _ttsCentral is released
# before the next _ttsCentral is created.
self._ttsCentral = None
self._ttsAttrs = None
self._ttsCentral = POINTER(ITTSCentralW)()
self._ttsEngines.Select(self._currentMode.gModeID, byref(self._ttsCentral), self._ttsAudio)
self._ttsCentral.Register(self._sinkPtr, ITTSNotifySinkW._iid_, byref(self._sinkRegKey))
Expand Down
1 change: 1 addition & 0 deletions user_docs/en/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ In any document, if the cursor is on the last line, it will be moved to the end
* When anchor links point to the same object as the virtual caret is placed, NVDA no longer fails to scroll to the link destination. (#17669, @nvdaes)
* Voice parameters, such as rate and volume, will no longer be reset to default when using the synth settings ring to change between voices in the SAPI5 and SAPI4 synthesizer. (#17693, #2320, @gexgd0419)
* The NVDA Highlighter Window icon is no longer fixed in the taskbar after restarting Explorer. (#17696, @hwf1324)
* Fixed an issue where some SAPI4 voices cannot be loaded and report errors when selected. (#17726, @gexgd0419)

### Changes for Developers

Expand Down