Repository: nvaccess/nvda

Issue:
Switching from SAPI4 to another synth still shows SAPI4 voices list
### Steps to reproduce:
1. Select a SAPI4 synthesizer
2. From the speech panel, choose change, and in the synthesizer selection dialog, choose espeak.
3. Press ok
4. Go to the list of voices

### Actual behavior:
SAPI4 voices are shown

### Expected behavior:
ESpeak voices are shown.

### Explanation
SAPI4 also has a sync object that keeps a hard reference to the driver. I have a branch ready that fixes this, will pr it later today.


Discussion:

Resolving patch:
diff --git a/source/synthDrivers/sapi4.py b/source/synthDrivers/sapi4.py
--- a/source/synthDrivers/sapi4.py
+++ b/source/synthDrivers/sapi4.py
@@ -15,20 +15,26 @@
 from ._sapi4 import *
 import config
 import nvwave
+import weakref
+
 
 class SynthDriverBufSink(COMObject):
 	_com_interfaces_ = [ITTSBufNotifySink]
 
-	def __init__(self,synthDriver):
-		self._synthDriver=synthDriver
+	def __init__(self, synthRef: weakref.ReferenceType):
+		self.synthRef = synthRef
 		self._allowDelete = True
 		super(SynthDriverBufSink,self).__init__()
 
 	def ITTSBufNotifySink_BookMark(self, this, qTimeStamp, dwMarkNum):
-		synthIndexReached.notify(synth=self._synthDriver,index=dwMarkNum)
-		if self._synthDriver._finalIndex==dwMarkNum:
-			self._synthDriver._finalIndex=None
-			synthDoneSpeaking.notify(synth=self._synthDriver)
+		synth = self.synthRef()
+		if synth is None:
+			log.debugWarning("Called ITTSBufNotifySink_BookMark method on ITTSBufNotifySink while driver is dead")
+			return
+		synthIndexReached.notify(synth=synth, index=dwMarkNum)
+		if synth._finalIndex == dwMarkNum:
+			synth._finalIndex = None
+			synthDoneSpeaking.notify(synth=synth)
 
 	def IUnknown_Release(self, this, *args, **kwargs):
 		if not self._allowDelete and self._refcnt.value == 1:
@@ -69,7 +75,7 @@ def _fetchEnginesList(self):
 
 	def __init__(self):
 		self._finalIndex=None
-		self._bufSink=SynthDriverBufSink(self)
+		self._bufSink = SynthDriverBufSink(weakref.ref(self))
 		self._bufSinkPtr=self._bufSink.QueryInterface(ITTSBufNotifySink)
 		# HACK: Some buggy engines call Release() too many times on our buf sink.
 		# Therefore, don't let the buf sink be deleted before we release it ourselves.
diff --git a/source/synthDrivers/sapi5.py b/source/synthDrivers/sapi5.py
--- a/source/synthDrivers/sapi5.py
+++ b/source/synthDrivers/sapi5.py
@@ -105,8 +105,8 @@ class SapiSink(object):
 	See https://msdn.microsoft.com/en-us/library/ms723587(v=vs.85).aspx
 	"""
 
-	def __init__(self, synth):
-		self.synthRef = weakref.ref(synth)
+	def __init__(self, synthRef: weakref.ReferenceType):
+		self.synthRef = synthRef
 
 	def Bookmark(self, streamNum, pos, bookmark, bookmarkId):
 		synth = self.synthRef()
@@ -230,7 +230,7 @@ def _initTts(self, voice=None):
 		outputDeviceID=nvwave.outputDeviceNameToID(config.conf["speech"]["outputDevice"], True)
 		if outputDeviceID>=0:
 			self.tts.audioOutput=self.tts.getAudioOutputs()[outputDeviceID]
-		self._eventsConnection = comtypes.client.GetEvents(self.tts, SapiSink(self))
+		self._eventsConnection = comtypes.client.GetEvents(self.tts, SapiSink(weakref.ref(self)))
 		self.tts.EventInterests = constants.SVEBookmark | constants.SVEEndInputStream
 		from comInterfaces.SpeechLib import ISpAudio
 		try:


Test patch: