We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Nvda not announcing all the speeches when using the windows onecore synthesizer
I installed nvda next 14285 and noticed a problem using the windows onecore synthesizer.
By unchecking and selecting a checkbox several more times then nvda does not announce whether the box is unchecked or checked.
It is a problem in the windows onecore synthesizer because when using another synthesizer as nuance vocaliser exprecive and a voice sap5 the synthesizer announces quickly and accurately if the box has been selected or cleared.
Steps:
Go to a checkbox such as:
Save setting when exiting
Within the general options dialog of nvda.
Now press space to uncheck and check this box.
Do this several times.
The expected is that every time nvda announces the status of the box.
The thing is that the nvda announces some times the state of the box.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `source/synthDrivers/oneCore.py`
Content:
```
1 #synthDrivers/oneCore.py
2 #A part of NonVisual Desktop Access (NVDA)
3 #Copyright (C) 2016-2017 Tyler Spivey, NV Access Limited
4 #This file is covered by the GNU General Public License.
5 #See the file COPYING for more details.
6 
7 """Synth driver for Windows OneCore voices.
8 """
9 
10 import os
11 import sys
12 from collections import OrderedDict
13 import ctypes
14 import _winreg
15 from synthDriverHandler import SynthDriver, VoiceInfo
16 from logHandler import log
17 import config
18 import nvwave
19 import speech
20 import speechXml
21 import languageHandler
22 import winVersion
23 import NVDAHelper
24 
25 SAMPLES_PER_SEC = 22050
26 BITS_PER_SAMPLE = 16
27 BYTES_PER_SEC = SAMPLES_PER_SEC * (BITS_PER_SAMPLE / 8)
28 #: The number of 100-nanosecond units in 1 second.
29 HUNDRED_NS_PER_SEC = 10000000 # 1000000000 ns per sec / 100 ns
30 WAV_HEADER_LEN = 44
31 ocSpeech_Callback = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_int, ctypes.c_wchar_p)
32 
33 class _OcSsmlConverter(speechXml.SsmlConverter):
34 
35 	def __init__(self, defaultLanguage, rate, pitch, volume):
36 		super(_OcSsmlConverter, self).__init__(defaultLanguage)
37 		self._rate = rate
38 		self._pitch = pitch
39 		self._volume = volume
40 
41 	def generateBalancerCommands(self, speechSequence):
42 		commands = super(_OcSsmlConverter, self).generateBalancerCommands(speechSequence)
43 		# The EncloseAllCommand from SSML must be first.
44 		yield next(commands)
45 		# OneCore doesn't provide a way to set base prosody values.
46 		# Therefore, the base values need to be set using SSML.
47 		yield self.convertRateCommand(speech.RateCommand(multiplier=1))
48 		yield self.convertVolumeCommand(speech.VolumeCommand(multiplier=1))
49 		yield self.convertPitchCommand(speech.PitchCommand(multiplier=1))
50 		for command in commands:
51 			yield command
52 
53 	def _convertProsody(self, command, attr, default, base):
54 		if command.multiplier == 1 and base == default:
55 			# Returning to synth default.
56 			return speechXml.DelAttrCommand("prosody", attr)
57 		else:
58 			# Multiplication isn't supported, only addition/subtraction.
59 			# The final value must therefore be relative to the synthesizer's default.
60 			val = base * command.multiplier - default
61 			return speechXml.SetAttrCommand("prosody", attr, "%d%%" % val)
62 
63 	def convertRateCommand(self, command):
64 		return self._convertProsody(command, "rate", 50, self._rate)
65 	def convertPitchCommand(self, command):
66 		return self._convertProsody(command, "pitch", 50, self._pitch)
67 	def convertVolumeCommand(self, command):
68 		return self._convertProsody(command, "volume", 100, self._volume)
69 
70 	def convertCharacterModeCommand(self, command):
71 		# OneCore's character speech sounds weird and doesn't support pitch alteration.
72 		# Therefore, we don't use it.
73 		return None
74 
75 	def convertLangChangeCommand(self, command):
76 		lcid = languageHandler.localeNameToWindowsLCID(command.lang)
77 		if lcid is languageHandler.LCID_NONE:
78 			log.debugWarning("Invalid language: %s" % command.lang)
79 			return None
80 		return super(_OcSsmlConverter, self).convertLangChangeCommand(command)
81 
82 class SynthDriver(SynthDriver):
83 	name = "oneCore"
84 	# Translators: Description for a speech synthesizer.
85 	description = _("Windows OneCore voices")
86 	supportedSettings = (
87 		SynthDriver.VoiceSetting(),
88 		SynthDriver.RateSetting(),
89 		SynthDriver.PitchSetting(),
90 		SynthDriver.VolumeSetting(),
91 	)
92 	# These are all controlled via SSML, so we only need attributes, not properties.
93 	rate = None
94 	pitch = None
95 	volume = None
96 
97 	@classmethod
98 	def check(cls):
99 		if not hasattr(sys, "frozen"):
100 			# #3793: Source copies don't report the correct version on Windows 10 because Python isn't manifested for higher versions.
101 			# We want this driver to work for source copies on Windows 10, so just return True here.
102 			# If this isn't in fact Windows 10, it will fail when constructed, which is okay.
103 			return True
104 		# For binary copies, only present this as an available synth if this is Windows 10.
105 		return winVersion.winVersion.major >= 10
106 
107 	def __init__(self):
108 		super(SynthDriver, self).__init__()
109 		self._dll = NVDAHelper.getHelperLocalWin10Dll()
110 		self._dll.ocSpeech_getCurrentVoiceLanguage.restype = ctypes.c_wchar_p
111 		self._handle = self._dll.ocSpeech_initialize()
112 		self._callbackInst = ocSpeech_Callback(self._callback)
113 		self._dll.ocSpeech_setCallback(self._handle, self._callbackInst)
114 		self._dll.ocSpeech_getVoices.restype = NVDAHelper.bstrReturn
115 		self._dll.ocSpeech_getCurrentVoiceId.restype = ctypes.c_wchar_p
116 		self._player = nvwave.WavePlayer(1, SAMPLES_PER_SEC, BITS_PER_SAMPLE, outputDevice=config.conf["speech"]["outputDevice"])
117 		# Initialize state.
118 		self._queuedSpeech = []
119 		self._wasCancelled = False
120 		self._isProcessing = False
121 		# Set initial values for parameters that can't be queried.
122 		# This initialises our cache for the value.
123 		self.rate = 50
124 		self.pitch = 50
125 		self.volume = 100
126 
127 	def terminate(self):
128 		super(SynthDriver, self).terminate()
129 		self._dll.ocSpeech_terminate(self._handle)
130 		# Drop the ctypes function instance for the callback,
131 		# as it is holding a reference to an instance method, which causes a reference cycle.
132 		self._callbackInst = None
133 
134 	def cancel(self):
135 		# Set a flag to tell the callback not to push more audio.
136 		self._wasCancelled = True
137 		log.debug("Cancelling")
138 		# There might be more text pending. Throw it away.
139 		self._queuedSpeech = []
140 		self._player.stop()
141 
142 	def speak(self, speechSequence):
143 		conv = _OcSsmlConverter(self.language, self.rate, self.pitch, self.volume)
144 		text = conv.convertToXml(speechSequence)
145 		self._queueSpeech(text)
146 
147 	def _queueSpeech(self, item):
148 		self._queuedSpeech.append(item)
149 		# We only process the queue here if it isn't already being processed.
150 		if not self._isProcessing:
151 			self._processQueue()
152 
153 	def _processQueue(self):
154 		if self._queuedSpeech:
155 			item = self._queuedSpeech.pop(0)
156 			self._wasCancelled = False
157 			log.debug("Begin processing speech")
158 			self._isProcessing = True
159 			# ocSpeech_speak is async.
160 			# It will call _callback in a background thread once done,
161 			# which will eventually process the queue again.
162 			self._dll.ocSpeech_speak(self._handle, item)
163 			return
164 		self._player.idle()
165 		log.debug("Queue empty, done processing")
166 		self._isProcessing = False
167 
168 	def _callback(self, bytes, len, markers):
169 		if len == 0:
170 			# The C++ code will log an error with details.
171 			log.debugWarning("ocSpeech_speak failed!")
172 			self._processQueue()
173 			return
174 		# This gets called in a background thread.
175 		# Strip the wav header.
176 		assert len > WAV_HEADER_LEN
177 		bytes += WAV_HEADER_LEN
178 		len -= WAV_HEADER_LEN
179 		data = ctypes.string_at(bytes, len)
180 		if markers:
181 			markers = markers.split('|')
182 		else:
183 			markers = []
184 		prevMarker = None
185 		prevPos = 0
186 
187 		# Push audio up to each marker so we can sync the audio with the markers.
188 		for marker in markers:
189 			if self._wasCancelled:
190 				break
191 			name, pos = marker.split(':')
192 			pos = int(pos)
193 			# pos is a time offset in 100-nanosecond units.
194 			# Convert this to a byte offset.
195 			# Order the equation so we don't have to do floating point.
196 			pos = pos * BYTES_PER_SEC / HUNDRED_NS_PER_SEC
197 			# Push audio up to this marker.
198 			self._player.feed(data[prevPos:pos])
199 			# _player.feed blocks until the previous chunk of audio is complete, not the chunk we just pushed.
200 			# Therefore, indicate that we've reached the previous marker.
201 			if prevMarker:
202 				self.lastIndex = prevMarker
203 			prevMarker = int(name)
204 			prevPos = pos
205 		if self._wasCancelled:
206 			log.debug("Cancelled, stopped pushing audio")
207 		else:
208 			self._player.feed(data[prevPos:])
209 			if prevMarker:
210 				self.lastIndex = prevMarker
211 			log.debug("Done pushing audio")
212 		self._processQueue()
213 
214 	def _getAvailableVoices(self, onlyValid=True):
215 		voices = OrderedDict()
216 		voicesStr = self._dll.ocSpeech_getVoices(self._handle).split('|')
217 		for voiceStr in voicesStr:
218 			id, name = voiceStr.split(":")
219 			if onlyValid and not self._isVoiceValid(id):
220 				continue
221 			voices[id] = VoiceInfo(id, name)
222 		return voices
223 
224 	def _isVoiceValid(self, id):
225 		idParts = id.split('\\')
226 		rootKey = getattr(_winreg, idParts[0])
227 		subkey = "\\".join(idParts[1:])
228 		try:
229 			hkey = _winreg.OpenKey(rootKey, subkey)
230 		except WindowsError as e:
231 			log.debugWarning("Could not open registry key %s, %s" % (id, e))
232 			return False
233 		try:
234 			langDataPath = _winreg.QueryValueEx(hkey, 'langDataPath')
235 		except WindowsError as e:
236 			log.debugWarning("Could not open registry value 'langDataPath', %s" % e)
237 			return False
238 		if not langDataPath or not isinstance(langDataPath[0], basestring):
239 			log.debugWarning("Invalid langDataPath value")
240 			return False
241 		if not os.path.isfile(os.path.expandvars(langDataPath[0])):
242 			log.debugWarning("Missing language data file: %s" % langDataPath[0])
243 			return False
244 		try:
245 			voicePath = _winreg.QueryValueEx(hkey, 'voicePath')
246 		except WindowsError as e:
247 			log.debugWarning("Could not open registry value 'langDataPath', %s" % e)
248 			return False
249 		if not voicePath or not isinstance(voicePath[0],basestring):
250 			log.debugWarning("Invalid voicePath value")
251 			return False
252 		if not os.path.isfile(os.path.expandvars(voicePath[0] + '.apm')):
253 			log.debugWarning("Missing voice file: %s" % voicePath[0] + ".apm")
254 			return False
255 		return True
256 
257 	def _get_voice(self):
258 		return self._dll.ocSpeech_getCurrentVoiceId(self._handle)
259 
260 	def _set_voice(self, id):
261 		voices = self._getAvailableVoices(onlyValid=False)
262 		for index, voice in enumerate(voices):
263 			if voice == id:
264 				break
265 		else:
266 			raise LookupError("No such voice: %s" % id)
267 		self._dll.ocSpeech_setVoice(self._handle, index)
268 
269 	def _get_language(self):
270 		return self._dll.ocSpeech_getCurrentVoiceLanguage(self._handle)
271 
272 	def pause(self, switch):
273 		self._player.pause(switch)
274
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/source/synthDrivers/oneCore.py b/source/synthDrivers/oneCore.py
--- a/source/synthDrivers/oneCore.py
+++ b/source/synthDrivers/oneCore.py
@@ -161,7 +161,6 @@
 			# which will eventually process the queue again.
 			self._dll.ocSpeech_speak(self._handle, item)
 			return
-		self._player.idle()
 		log.debug("Queue empty, done processing")
 		self._isProcessing = False
 
@@ -209,6 +208,13 @@
 			if prevMarker:
 				self.lastIndex = prevMarker
 			log.debug("Done pushing audio")
+			if not self._queuedSpeech:
+				# There are no more queued utterances at this point, so call idle.
+				# This blocks while waiting for the final chunk to play,
+				# so by the time this is done, there might be something queued.
+				# The call to _processQueue will take care of this.
+				log.debug("Calling idle on audio player")
+				self._player.idle()
 		self._processQueue()
 
 	def _getAvailableVoices(self, onlyValid=True):

{"golden_diff": "diff --git a/source/synthDrivers/oneCore.py b/source/synthDrivers/oneCore.py\n--- a/source/synthDrivers/oneCore.py\n+++ b/source/synthDrivers/oneCore.py\n@@ -161,7 +161,6 @@\n \t\t\t# which will eventually process the queue again.\r\n \t\t\tself._dll.ocSpeech_speak(self._handle, item)\r\n \t\t\treturn\r\n-\t\tself._player.idle()\r\n \t\tlog.debug(\"Queue empty, done processing\")\r\n \t\tself._isProcessing = False\r\n \r\n@@ -209,6 +208,13 @@\n \t\t\tif prevMarker:\r\n \t\t\t\tself.lastIndex = prevMarker\r\n \t\t\tlog.debug(\"Done pushing audio\")\r\n+\t\t\tif not self._queuedSpeech:\r\n+\t\t\t\t# There are no more queued utterances at this point, so call idle.\r\n+\t\t\t\t# This blocks while waiting for the final chunk to play,\r\n+\t\t\t\t# so by the time this is done, there might be something queued.\r\n+\t\t\t\t# The call to _processQueue will take care of this.\r\n+\t\t\t\tlog.debug(\"Calling idle on audio player\")\r\n+\t\t\t\tself._player.idle()\r\n \t\tself._processQueue()\r\n \r\n \tdef _getAvailableVoices(self, onlyValid=True):\n", "issue": "Nvda not announcing all the speeches when using the windows onecore synthesizer\nI installed nvda next 14285 and noticed a problem using the windows onecore synthesizer.\r\nBy unchecking and selecting a checkbox several more times then nvda does not announce whether the box is unchecked or checked.\r\nIt is a problem in the windows onecore synthesizer because when using another synthesizer as nuance vocaliser exprecive and a voice sap5 the synthesizer announces quickly and accurately if the box has been selected or cleared.\r\nSteps:\r\nGo to a checkbox such as:\r\nSave setting when exiting\r\nWithin the general options dialog of nvda.\r\nNow press space to uncheck and check this box.\r\nDo this several times.\r\nThe expected is that every time nvda announces the status of the box.\r\nThe thing is that the nvda announces some times the state of the box.\n", "before_files": [{"content": "#synthDrivers/oneCore.py\r\n#A part of NonVisual Desktop Access (NVDA)\r\n#Copyright (C) 2016-2017 Tyler Spivey, NV Access Limited\r\n#This file is covered by the GNU General Public License.\r\n#See the file COPYING for more details.\r\n\r\n\"\"\"Synth driver for Windows OneCore voices.\r\n\"\"\"\r\n\r\nimport os\r\nimport sys\r\nfrom collections import OrderedDict\r\nimport ctypes\r\nimport _winreg\r\nfrom synthDriverHandler import SynthDriver, VoiceInfo\r\nfrom logHandler import log\r\nimport config\r\nimport nvwave\r\nimport speech\r\nimport speechXml\r\nimport languageHandler\r\nimport winVersion\r\nimport NVDAHelper\r\n\r\nSAMPLES_PER_SEC = 22050\r\nBITS_PER_SAMPLE = 16\r\nBYTES_PER_SEC = SAMPLES_PER_SEC * (BITS_PER_SAMPLE / 8)\r\n#: The number of 100-nanosecond units in 1 second.\r\nHUNDRED_NS_PER_SEC = 10000000 # 1000000000 ns per sec / 100 ns\r\nWAV_HEADER_LEN = 44\r\nocSpeech_Callback = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_int, ctypes.c_wchar_p)\r\n\r\nclass _OcSsmlConverter(speechXml.SsmlConverter):\r\n\r\n\tdef __init__(self, defaultLanguage, rate, pitch, volume):\r\n\t\tsuper(_OcSsmlConverter, self).__init__(defaultLanguage)\r\n\t\tself._rate = rate\r\n\t\tself._pitch = pitch\r\n\t\tself._volume = volume\r\n\r\n\tdef generateBalancerCommands(self, speechSequence):\r\n\t\tcommands = super(_OcSsmlConverter, self).generateBalancerCommands(speechSequence)\r\n\t\t# The EncloseAllCommand from SSML must be first.\r\n\t\tyield next(commands)\r\n\t\t# OneCore doesn't provide a way to set base prosody values.\r\n\t\t# Therefore, the base values need to be set using SSML.\r\n\t\tyield self.convertRateCommand(speech.RateCommand(multiplier=1))\r\n\t\tyield self.convertVolumeCommand(speech.VolumeCommand(multiplier=1))\r\n\t\tyield self.convertPitchCommand(speech.PitchCommand(multiplier=1))\r\n\t\tfor command in commands:\r\n\t\t\tyield command\r\n\r\n\tdef _convertProsody(self, command, attr, default, base):\r\n\t\tif command.multiplier == 1 and base == default:\r\n\t\t\t# Returning to synth default.\r\n\t\t\treturn speechXml.DelAttrCommand(\"prosody\", attr)\r\n\t\telse:\r\n\t\t\t# Multiplication isn't supported, only addition/subtraction.\r\n\t\t\t# The final value must therefore be relative to the synthesizer's default.\r\n\t\t\tval = base * command.multiplier - default\r\n\t\t\treturn speechXml.SetAttrCommand(\"prosody\", attr, \"%d%%\" % val)\r\n\r\n\tdef convertRateCommand(self, command):\r\n\t\treturn self._convertProsody(command, \"rate\", 50, self._rate)\r\n\tdef convertPitchCommand(self, command):\r\n\t\treturn self._convertProsody(command, \"pitch\", 50, self._pitch)\r\n\tdef convertVolumeCommand(self, command):\r\n\t\treturn self._convertProsody(command, \"volume\", 100, self._volume)\r\n\r\n\tdef convertCharacterModeCommand(self, command):\r\n\t\t# OneCore's character speech sounds weird and doesn't support pitch alteration.\r\n\t\t# Therefore, we don't use it.\r\n\t\treturn None\r\n\r\n\tdef convertLangChangeCommand(self, command):\r\n\t\tlcid = languageHandler.localeNameToWindowsLCID(command.lang)\r\n\t\tif lcid is languageHandler.LCID_NONE:\r\n\t\t\tlog.debugWarning(\"Invalid language: %s\" % command.lang)\r\n\t\t\treturn None\r\n\t\treturn super(_OcSsmlConverter, self).convertLangChangeCommand(command)\r\n\r\nclass SynthDriver(SynthDriver):\r\n\tname = \"oneCore\"\r\n\t# Translators: Description for a speech synthesizer.\r\n\tdescription = _(\"Windows OneCore voices\")\r\n\tsupportedSettings = (\r\n\t\tSynthDriver.VoiceSetting(),\r\n\t\tSynthDriver.RateSetting(),\r\n\t\tSynthDriver.PitchSetting(),\r\n\t\tSynthDriver.VolumeSetting(),\r\n\t)\r\n\t# These are all controlled via SSML, so we only need attributes, not properties.\r\n\trate = None\r\n\tpitch = None\r\n\tvolume = None\r\n\r\n\t@classmethod\r\n\tdef check(cls):\r\n\t\tif not hasattr(sys, \"frozen\"):\r\n\t\t\t# #3793: Source copies don't report the correct version on Windows 10 because Python isn't manifested for higher versions.\r\n\t\t\t# We want this driver to work for source copies on Windows 10, so just return True here.\r\n\t\t\t# If this isn't in fact Windows 10, it will fail when constructed, which is okay.\r\n\t\t\treturn True\r\n\t\t# For binary copies, only present this as an available synth if this is Windows 10.\r\n\t\treturn winVersion.winVersion.major >= 10\r\n\r\n\tdef __init__(self):\r\n\t\tsuper(SynthDriver, self).__init__()\r\n\t\tself._dll = NVDAHelper.getHelperLocalWin10Dll()\r\n\t\tself._dll.ocSpeech_getCurrentVoiceLanguage.restype = ctypes.c_wchar_p\r\n\t\tself._handle = self._dll.ocSpeech_initialize()\r\n\t\tself._callbackInst = ocSpeech_Callback(self._callback)\r\n\t\tself._dll.ocSpeech_setCallback(self._handle, self._callbackInst)\r\n\t\tself._dll.ocSpeech_getVoices.restype = NVDAHelper.bstrReturn\r\n\t\tself._dll.ocSpeech_getCurrentVoiceId.restype = ctypes.c_wchar_p\r\n\t\tself._player = nvwave.WavePlayer(1, SAMPLES_PER_SEC, BITS_PER_SAMPLE, outputDevice=config.conf[\"speech\"][\"outputDevice\"])\r\n\t\t# Initialize state.\r\n\t\tself._queuedSpeech = []\r\n\t\tself._wasCancelled = False\r\n\t\tself._isProcessing = False\r\n\t\t# Set initial values for parameters that can't be queried.\r\n\t\t# This initialises our cache for the value.\r\n\t\tself.rate = 50\r\n\t\tself.pitch = 50\r\n\t\tself.volume = 100\r\n\r\n\tdef terminate(self):\r\n\t\tsuper(SynthDriver, self).terminate()\r\n\t\tself._dll.ocSpeech_terminate(self._handle)\r\n\t\t# Drop the ctypes function instance for the callback,\r\n\t\t# as it is holding a reference to an instance method, which causes a reference cycle.\r\n\t\tself._callbackInst = None\r\n\r\n\tdef cancel(self):\r\n\t\t# Set a flag to tell the callback not to push more audio.\r\n\t\tself._wasCancelled = True\r\n\t\tlog.debug(\"Cancelling\")\r\n\t\t# There might be more text pending. Throw it away.\r\n\t\tself._queuedSpeech = []\r\n\t\tself._player.stop()\r\n\r\n\tdef speak(self, speechSequence):\r\n\t\tconv = _OcSsmlConverter(self.language, self.rate, self.pitch, self.volume)\r\n\t\ttext = conv.convertToXml(speechSequence)\r\n\t\tself._queueSpeech(text)\r\n\r\n\tdef _queueSpeech(self, item):\r\n\t\tself._queuedSpeech.append(item)\r\n\t\t# We only process the queue here if it isn't already being processed.\r\n\t\tif not self._isProcessing:\r\n\t\t\tself._processQueue()\r\n\r\n\tdef _processQueue(self):\r\n\t\tif self._queuedSpeech:\r\n\t\t\titem = self._queuedSpeech.pop(0)\r\n\t\t\tself._wasCancelled = False\r\n\t\t\tlog.debug(\"Begin processing speech\")\r\n\t\t\tself._isProcessing = True\r\n\t\t\t# ocSpeech_speak is async.\r\n\t\t\t# It will call _callback in a background thread once done,\r\n\t\t\t# which will eventually process the queue again.\r\n\t\t\tself._dll.ocSpeech_speak(self._handle, item)\r\n\t\t\treturn\r\n\t\tself._player.idle()\r\n\t\tlog.debug(\"Queue empty, done processing\")\r\n\t\tself._isProcessing = False\r\n\r\n\tdef _callback(self, bytes, len, markers):\r\n\t\tif len == 0:\r\n\t\t\t# The C++ code will log an error with details.\r\n\t\t\tlog.debugWarning(\"ocSpeech_speak failed!\")\r\n\t\t\tself._processQueue()\r\n\t\t\treturn\r\n\t\t# This gets called in a background thread.\r\n\t\t# Strip the wav header.\r\n\t\tassert len > WAV_HEADER_LEN\r\n\t\tbytes += WAV_HEADER_LEN\r\n\t\tlen -= WAV_HEADER_LEN\r\n\t\tdata = ctypes.string_at(bytes, len)\r\n\t\tif markers:\r\n\t\t\tmarkers = markers.split('|')\r\n\t\telse:\r\n\t\t\tmarkers = []\r\n\t\tprevMarker = None\r\n\t\tprevPos = 0\r\n\r\n\t\t# Push audio up to each marker so we can sync the audio with the markers.\r\n\t\tfor marker in markers:\r\n\t\t\tif self._wasCancelled:\r\n\t\t\t\tbreak\r\n\t\t\tname, pos = marker.split(':')\r\n\t\t\tpos = int(pos)\r\n\t\t\t# pos is a time offset in 100-nanosecond units.\r\n\t\t\t# Convert this to a byte offset.\r\n\t\t\t# Order the equation so we don't have to do floating point.\r\n\t\t\tpos = pos * BYTES_PER_SEC / HUNDRED_NS_PER_SEC\r\n\t\t\t# Push audio up to this marker.\r\n\t\t\tself._player.feed(data[prevPos:pos])\r\n\t\t\t# _player.feed blocks until the previous chunk of audio is complete, not the chunk we just pushed.\r\n\t\t\t# Therefore, indicate that we've reached the previous marker.\r\n\t\t\tif prevMarker:\r\n\t\t\t\tself.lastIndex = prevMarker\r\n\t\t\tprevMarker = int(name)\r\n\t\t\tprevPos = pos\r\n\t\tif self._wasCancelled:\r\n\t\t\tlog.debug(\"Cancelled, stopped pushing audio\")\r\n\t\telse:\r\n\t\t\tself._player.feed(data[prevPos:])\r\n\t\t\tif prevMarker:\r\n\t\t\t\tself.lastIndex = prevMarker\r\n\t\t\tlog.debug(\"Done pushing audio\")\r\n\t\tself._processQueue()\r\n\r\n\tdef _getAvailableVoices(self, onlyValid=True):\r\n\t\tvoices = OrderedDict()\r\n\t\tvoicesStr = self._dll.ocSpeech_getVoices(self._handle).split('|')\r\n\t\tfor voiceStr in voicesStr:\r\n\t\t\tid, name = voiceStr.split(\":\")\r\n\t\t\tif onlyValid and not self._isVoiceValid(id):\r\n\t\t\t\tcontinue\r\n\t\t\tvoices[id] = VoiceInfo(id, name)\r\n\t\treturn voices\r\n\r\n\tdef _isVoiceValid(self, id):\r\n\t\tidParts = id.split('\\\\')\r\n\t\trootKey = getattr(_winreg, idParts[0])\r\n\t\tsubkey = \"\\\\\".join(idParts[1:])\r\n\t\ttry:\r\n\t\t\thkey = _winreg.OpenKey(rootKey, subkey)\r\n\t\texcept WindowsError as e:\r\n\t\t\tlog.debugWarning(\"Could not open registry key %s, %s\" % (id, e))\r\n\t\t\treturn False\r\n\t\ttry:\r\n\t\t\tlangDataPath = _winreg.QueryValueEx(hkey, 'langDataPath')\r\n\t\texcept WindowsError as e:\r\n\t\t\tlog.debugWarning(\"Could not open registry value 'langDataPath', %s\" % e)\r\n\t\t\treturn False\r\n\t\tif not langDataPath or not isinstance(langDataPath[0], basestring):\r\n\t\t\tlog.debugWarning(\"Invalid langDataPath value\")\r\n\t\t\treturn False\r\n\t\tif not os.path.isfile(os.path.expandvars(langDataPath[0])):\r\n\t\t\tlog.debugWarning(\"Missing language data file: %s\" % langDataPath[0])\r\n\t\t\treturn False\r\n\t\ttry:\r\n\t\t\tvoicePath = _winreg.QueryValueEx(hkey, 'voicePath')\r\n\t\texcept WindowsError as e:\r\n\t\t\tlog.debugWarning(\"Could not open registry value 'langDataPath', %s\" % e)\r\n\t\t\treturn False\r\n\t\tif not voicePath or not isinstance(voicePath[0],basestring):\r\n\t\t\tlog.debugWarning(\"Invalid voicePath value\")\r\n\t\t\treturn False\r\n\t\tif not os.path.isfile(os.path.expandvars(voicePath[0] + '.apm')):\r\n\t\t\tlog.debugWarning(\"Missing voice file: %s\" % voicePath[0] + \".apm\")\r\n\t\t\treturn False\r\n\t\treturn True\r\n\r\n\tdef _get_voice(self):\r\n\t\treturn self._dll.ocSpeech_getCurrentVoiceId(self._handle)\r\n\r\n\tdef _set_voice(self, id):\r\n\t\tvoices = self._getAvailableVoices(onlyValid=False)\r\n\t\tfor index, voice in enumerate(voices):\r\n\t\t\tif voice == id:\r\n\t\t\t\tbreak\r\n\t\telse:\r\n\t\t\traise LookupError(\"No such voice: %s\" % id)\r\n\t\tself._dll.ocSpeech_setVoice(self._handle, index)\r\n\r\n\tdef _get_language(self):\r\n\t\treturn self._dll.ocSpeech_getCurrentVoiceLanguage(self._handle)\r\n\r\n\tdef pause(self, switch):\r\n\t\tself._player.pause(switch)\r\n", "path": "source/synthDrivers/oneCore.py"}], "after_files": [{"content": "#synthDrivers/oneCore.py\r\n#A part of NonVisual Desktop Access (NVDA)\r\n#Copyright (C) 2016-2017 Tyler Spivey, NV Access Limited\r\n#This file is covered by the GNU General Public License.\r\n#See the file COPYING for more details.\r\n\r\n\"\"\"Synth driver for Windows OneCore voices.\r\n\"\"\"\r\n\r\nimport os\r\nimport sys\r\nfrom collections import OrderedDict\r\nimport ctypes\r\nimport _winreg\r\nfrom synthDriverHandler import SynthDriver, VoiceInfo\r\nfrom logHandler import log\r\nimport config\r\nimport nvwave\r\nimport speech\r\nimport speechXml\r\nimport languageHandler\r\nimport winVersion\r\nimport NVDAHelper\r\n\r\nSAMPLES_PER_SEC = 22050\r\nBITS_PER_SAMPLE = 16\r\nBYTES_PER_SEC = SAMPLES_PER_SEC * (BITS_PER_SAMPLE / 8)\r\n#: The number of 100-nanosecond units in 1 second.\r\nHUNDRED_NS_PER_SEC = 10000000 # 1000000000 ns per sec / 100 ns\r\nWAV_HEADER_LEN = 44\r\nocSpeech_Callback = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_int, ctypes.c_wchar_p)\r\n\r\nclass _OcSsmlConverter(speechXml.SsmlConverter):\r\n\r\n\tdef __init__(self, defaultLanguage, rate, pitch, volume):\r\n\t\tsuper(_OcSsmlConverter, self).__init__(defaultLanguage)\r\n\t\tself._rate = rate\r\n\t\tself._pitch = pitch\r\n\t\tself._volume = volume\r\n\r\n\tdef generateBalancerCommands(self, speechSequence):\r\n\t\tcommands = super(_OcSsmlConverter, self).generateBalancerCommands(speechSequence)\r\n\t\t# The EncloseAllCommand from SSML must be first.\r\n\t\tyield next(commands)\r\n\t\t# OneCore doesn't provide a way to set base prosody values.\r\n\t\t# Therefore, the base values need to be set using SSML.\r\n\t\tyield self.convertRateCommand(speech.RateCommand(multiplier=1))\r\n\t\tyield self.convertVolumeCommand(speech.VolumeCommand(multiplier=1))\r\n\t\tyield self.convertPitchCommand(speech.PitchCommand(multiplier=1))\r\n\t\tfor command in commands:\r\n\t\t\tyield command\r\n\r\n\tdef _convertProsody(self, command, attr, default, base):\r\n\t\tif command.multiplier == 1 and base == default:\r\n\t\t\t# Returning to synth default.\r\n\t\t\treturn speechXml.DelAttrCommand(\"prosody\", attr)\r\n\t\telse:\r\n\t\t\t# Multiplication isn't supported, only addition/subtraction.\r\n\t\t\t# The final value must therefore be relative to the synthesizer's default.\r\n\t\t\tval = base * command.multiplier - default\r\n\t\t\treturn speechXml.SetAttrCommand(\"prosody\", attr, \"%d%%\" % val)\r\n\r\n\tdef convertRateCommand(self, command):\r\n\t\treturn self._convertProsody(command, \"rate\", 50, self._rate)\r\n\tdef convertPitchCommand(self, command):\r\n\t\treturn self._convertProsody(command, \"pitch\", 50, self._pitch)\r\n\tdef convertVolumeCommand(self, command):\r\n\t\treturn self._convertProsody(command, \"volume\", 100, self._volume)\r\n\r\n\tdef convertCharacterModeCommand(self, command):\r\n\t\t# OneCore's character speech sounds weird and doesn't support pitch alteration.\r\n\t\t# Therefore, we don't use it.\r\n\t\treturn None\r\n\r\n\tdef convertLangChangeCommand(self, command):\r\n\t\tlcid = languageHandler.localeNameToWindowsLCID(command.lang)\r\n\t\tif lcid is languageHandler.LCID_NONE:\r\n\t\t\tlog.debugWarning(\"Invalid language: %s\" % command.lang)\r\n\t\t\treturn None\r\n\t\treturn super(_OcSsmlConverter, self).convertLangChangeCommand(command)\r\n\r\nclass SynthDriver(SynthDriver):\r\n\tname = \"oneCore\"\r\n\t# Translators: Description for a speech synthesizer.\r\n\tdescription = _(\"Windows OneCore voices\")\r\n\tsupportedSettings = (\r\n\t\tSynthDriver.VoiceSetting(),\r\n\t\tSynthDriver.RateSetting(),\r\n\t\tSynthDriver.PitchSetting(),\r\n\t\tSynthDriver.VolumeSetting(),\r\n\t)\r\n\t# These are all controlled via SSML, so we only need attributes, not properties.\r\n\trate = None\r\n\tpitch = None\r\n\tvolume = None\r\n\r\n\t@classmethod\r\n\tdef check(cls):\r\n\t\tif not hasattr(sys, \"frozen\"):\r\n\t\t\t# #3793: Source copies don't report the correct version on Windows 10 because Python isn't manifested for higher versions.\r\n\t\t\t# We want this driver to work for source copies on Windows 10, so just return True here.\r\n\t\t\t# If this isn't in fact Windows 10, it will fail when constructed, which is okay.\r\n\t\t\treturn True\r\n\t\t# For binary copies, only present this as an available synth if this is Windows 10.\r\n\t\treturn winVersion.winVersion.major >= 10\r\n\r\n\tdef __init__(self):\r\n\t\tsuper(SynthDriver, self).__init__()\r\n\t\tself._dll = NVDAHelper.getHelperLocalWin10Dll()\r\n\t\tself._dll.ocSpeech_getCurrentVoiceLanguage.restype = ctypes.c_wchar_p\r\n\t\tself._handle = self._dll.ocSpeech_initialize()\r\n\t\tself._callbackInst = ocSpeech_Callback(self._callback)\r\n\t\tself._dll.ocSpeech_setCallback(self._handle, self._callbackInst)\r\n\t\tself._dll.ocSpeech_getVoices.restype = NVDAHelper.bstrReturn\r\n\t\tself._dll.ocSpeech_getCurrentVoiceId.restype = ctypes.c_wchar_p\r\n\t\tself._player = nvwave.WavePlayer(1, SAMPLES_PER_SEC, BITS_PER_SAMPLE, outputDevice=config.conf[\"speech\"][\"outputDevice\"])\r\n\t\t# Initialize state.\r\n\t\tself._queuedSpeech = []\r\n\t\tself._wasCancelled = False\r\n\t\tself._isProcessing = False\r\n\t\t# Set initial values for parameters that can't be queried.\r\n\t\t# This initialises our cache for the value.\r\n\t\tself.rate = 50\r\n\t\tself.pitch = 50\r\n\t\tself.volume = 100\r\n\r\n\tdef terminate(self):\r\n\t\tsuper(SynthDriver, self).terminate()\r\n\t\tself._dll.ocSpeech_terminate(self._handle)\r\n\t\t# Drop the ctypes function instance for the callback,\r\n\t\t# as it is holding a reference to an instance method, which causes a reference cycle.\r\n\t\tself._callbackInst = None\r\n\r\n\tdef cancel(self):\r\n\t\t# Set a flag to tell the callback not to push more audio.\r\n\t\tself._wasCancelled = True\r\n\t\tlog.debug(\"Cancelling\")\r\n\t\t# There might be more text pending. Throw it away.\r\n\t\tself._queuedSpeech = []\r\n\t\tself._player.stop()\r\n\r\n\tdef speak(self, speechSequence):\r\n\t\tconv = _OcSsmlConverter(self.language, self.rate, self.pitch, self.volume)\r\n\t\ttext = conv.convertToXml(speechSequence)\r\n\t\tself._queueSpeech(text)\r\n\r\n\tdef _queueSpeech(self, item):\r\n\t\tself._queuedSpeech.append(item)\r\n\t\t# We only process the queue here if it isn't already being processed.\r\n\t\tif not self._isProcessing:\r\n\t\t\tself._processQueue()\r\n\r\n\tdef _processQueue(self):\r\n\t\tif self._queuedSpeech:\r\n\t\t\titem = self._queuedSpeech.pop(0)\r\n\t\t\tself._wasCancelled = False\r\n\t\t\tlog.debug(\"Begin processing speech\")\r\n\t\t\tself._isProcessing = True\r\n\t\t\t# ocSpeech_speak is async.\r\n\t\t\t# It will call _callback in a background thread once done,\r\n\t\t\t# which will eventually process the queue again.\r\n\t\t\tself._dll.ocSpeech_speak(self._handle, item)\r\n\t\t\treturn\r\n\t\tlog.debug(\"Queue empty, done processing\")\r\n\t\tself._isProcessing = False\r\n\r\n\tdef _callback(self, bytes, len, markers):\r\n\t\tif len == 0:\r\n\t\t\t# The C++ code will log an error with details.\r\n\t\t\tlog.debugWarning(\"ocSpeech_speak failed!\")\r\n\t\t\tself._processQueue()\r\n\t\t\treturn\r\n\t\t# This gets called in a background thread.\r\n\t\t# Strip the wav header.\r\n\t\tassert len > WAV_HEADER_LEN\r\n\t\tbytes += WAV_HEADER_LEN\r\n\t\tlen -= WAV_HEADER_LEN\r\n\t\tdata = ctypes.string_at(bytes, len)\r\n\t\tif markers:\r\n\t\t\tmarkers = markers.split('|')\r\n\t\telse:\r\n\t\t\tmarkers = []\r\n\t\tprevMarker = None\r\n\t\tprevPos = 0\r\n\r\n\t\t# Push audio up to each marker so we can sync the audio with the markers.\r\n\t\tfor marker in markers:\r\n\t\t\tif self._wasCancelled:\r\n\t\t\t\tbreak\r\n\t\t\tname, pos = marker.split(':')\r\n\t\t\tpos = int(pos)\r\n\t\t\t# pos is a time offset in 100-nanosecond units.\r\n\t\t\t# Convert this to a byte offset.\r\n\t\t\t# Order the equation so we don't have to do floating point.\r\n\t\t\tpos = pos * BYTES_PER_SEC / HUNDRED_NS_PER_SEC\r\n\t\t\t# Push audio up to this marker.\r\n\t\t\tself._player.feed(data[prevPos:pos])\r\n\t\t\t# _player.feed blocks until the previous chunk of audio is complete, not the chunk we just pushed.\r\n\t\t\t# Therefore, indicate that we've reached the previous marker.\r\n\t\t\tif prevMarker:\r\n\t\t\t\tself.lastIndex = prevMarker\r\n\t\t\tprevMarker = int(name)\r\n\t\t\tprevPos = pos\r\n\t\tif self._wasCancelled:\r\n\t\t\tlog.debug(\"Cancelled, stopped pushing audio\")\r\n\t\telse:\r\n\t\t\tself._player.feed(data[prevPos:])\r\n\t\t\tif prevMarker:\r\n\t\t\t\tself.lastIndex = prevMarker\r\n\t\t\tlog.debug(\"Done pushing audio\")\r\n\t\t\tif not self._queuedSpeech:\r\n\t\t\t\t# There are no more queued utterances at this point, so call idle.\r\n\t\t\t\t# This blocks while waiting for the final chunk to play,\r\n\t\t\t\t# so by the time this is done, there might be something queued.\r\n\t\t\t\t# The call to _processQueue will take care of this.\r\n\t\t\t\tlog.debug(\"Calling idle on audio player\")\r\n\t\t\t\tself._player.idle()\r\n\t\tself._processQueue()\r\n\r\n\tdef _getAvailableVoices(self, onlyValid=True):\r\n\t\tvoices = OrderedDict()\r\n\t\tvoicesStr = self._dll.ocSpeech_getVoices(self._handle).split('|')\r\n\t\tfor voiceStr in voicesStr:\r\n\t\t\tid, name = voiceStr.split(\":\")\r\n\t\t\tif onlyValid and not self._isVoiceValid(id):\r\n\t\t\t\tcontinue\r\n\t\t\tvoices[id] = VoiceInfo(id, name)\r\n\t\treturn voices\r\n\r\n\tdef _isVoiceValid(self, id):\r\n\t\tidParts = id.split('\\\\')\r\n\t\trootKey = getattr(_winreg, idParts[0])\r\n\t\tsubkey = \"\\\\\".join(idParts[1:])\r\n\t\ttry:\r\n\t\t\thkey = _winreg.OpenKey(rootKey, subkey)\r\n\t\texcept WindowsError as e:\r\n\t\t\tlog.debugWarning(\"Could not open registry key %s, %s\" % (id, e))\r\n\t\t\treturn False\r\n\t\ttry:\r\n\t\t\tlangDataPath = _winreg.QueryValueEx(hkey, 'langDataPath')\r\n\t\texcept WindowsError as e:\r\n\t\t\tlog.debugWarning(\"Could not open registry value 'langDataPath', %s\" % e)\r\n\t\t\treturn False\r\n\t\tif not langDataPath or not isinstance(langDataPath[0], basestring):\r\n\t\t\tlog.debugWarning(\"Invalid langDataPath value\")\r\n\t\t\treturn False\r\n\t\tif not os.path.isfile(os.path.expandvars(langDataPath[0])):\r\n\t\t\tlog.debugWarning(\"Missing language data file: %s\" % langDataPath[0])\r\n\t\t\treturn False\r\n\t\ttry:\r\n\t\t\tvoicePath = _winreg.QueryValueEx(hkey, 'voicePath')\r\n\t\texcept WindowsError as e:\r\n\t\t\tlog.debugWarning(\"Could not open registry value 'langDataPath', %s\" % e)\r\n\t\t\treturn False\r\n\t\tif not voicePath or not isinstance(voicePath[0],basestring):\r\n\t\t\tlog.debugWarning(\"Invalid voicePath value\")\r\n\t\t\treturn False\r\n\t\tif not os.path.isfile(os.path.expandvars(voicePath[0] + '.apm')):\r\n\t\t\tlog.debugWarning(\"Missing voice file: %s\" % voicePath[0] + \".apm\")\r\n\t\t\treturn False\r\n\t\treturn True\r\n\r\n\tdef _get_voice(self):\r\n\t\treturn self._dll.ocSpeech_getCurrentVoiceId(self._handle)\r\n\r\n\tdef _set_voice(self, id):\r\n\t\tvoices = self._getAvailableVoices(onlyValid=False)\r\n\t\tfor index, voice in enumerate(voices):\r\n\t\t\tif voice == id:\r\n\t\t\t\tbreak\r\n\t\telse:\r\n\t\t\traise LookupError(\"No such voice: %s\" % id)\r\n\t\tself._dll.ocSpeech_setVoice(self._handle, index)\r\n\r\n\tdef _get_language(self):\r\n\t\treturn self._dll.ocSpeech_getCurrentVoiceLanguage(self._handle)\r\n\r\n\tdef pause(self, switch):\r\n\t\tself._player.pause(switch)\r\n", "path": "source/synthDrivers/oneCore.py"}]}