def nvdaController_setPitch(nPitch): from synthDriverHandler import getSynth try: getSynth()._set_pitch(nPitch) except: pass return 0
def nvdaController_setRate(nRate): from synthDriverHandler import getSynth try: getSynth()._set_rate(nRate) except: pass return 0
def speak(self, speechSequence: SpeechSequence, priority: Spri): log._speechManagerUnitTest("speak (priority %r): %r", priority, speechSequence) interrupt = self._queueSpeechSequence(speechSequence, priority) self._doRemoveCancelledSpeechCommands() # If speech isn't already in progress, we need to push the first speech. push = self._hasNoMoreSpeech() or not self._synthStillSpeaking() log._speechManagerDebug( f"Will interrupt: {interrupt}" f" Will push: {push}" f" | _indexesSpeaking: {self._indexesSpeaking!r}" f" | _curPriQueue valid: {not self._hasNoMoreSpeech()}" f" | _shouldPushWhenDoneSpeaking: {self._shouldPushWhenDoneSpeaking}" f" | _cancelledLastSpeechWithSynth {self._cancelledLastSpeechWithSynth}" ) if interrupt: log._speechManagerDebug("Interrupting speech") getSynth().cancel() self._indexesSpeaking.clear() self._cancelCommandsForUtteranceBeingSpokenBySynth.clear() push = True if push: log._speechManagerDebug("Pushing next speech") self._pushNextSpeech(True) else: log._speechManagerDebug("Not pushing speech")
def update(self) -> Profile: """Stores all data of the current voice synthesizer in the profile. @return: updated voice synthesizer profile @rtype: Profile """ self._name = getSynth().name self._conf = dict(config.conf['speech'][getSynth().name].items()) self._lang = self._lang or '' return self
def speak(self, speechSequence: SpeechSequence, priority: Spri): # If speech isn't already in progress, we need to push the first speech. push = self._curPriQueue is None interrupt = self._queueSpeechSequence(speechSequence, priority) if interrupt: getSynth().cancel() push = True if push: self._pushNextSpeech(True)
def _pushNextSpeech(self, doneSpeaking: bool): log._speechManagerDebug( f"pushNextSpeech - doneSpeaking: {doneSpeaking}") queue = self._getNextPriority() if not queue: # No more speech. log._speechManagerDebug("No more speech") self._curPriQueue = None return if self._hasNoMoreSpeech(): # First utterance after no speech. self._curPriQueue = queue elif queue.priority > self._curPriQueue.priority: # Preempted by higher priority speech. if self._curPriQueue.enteredProfileTriggers: if not doneSpeaking: # Wait for the synth to finish speaking. # _handleDoneSpeaking will call us again. self._shouldPushWhenDoneSpeaking = True return self._exitProfileTriggers( self._curPriQueue.enteredProfileTriggers) self._curPriQueue = queue elif queue.priority < self._curPriQueue.priority: # Resuming a preempted, lower priority queue. if queue.enteredProfileTriggers: if not doneSpeaking: # Wait for the synth to finish speaking. # _handleDoneSpeaking will call us again. self._shouldPushWhenDoneSpeaking = True return self._restoreProfileTriggers(queue.enteredProfileTriggers) self._curPriQueue = queue while queue.pendingSequences and isinstance( queue.pendingSequences[0][0], ConfigProfileTriggerCommand): if not doneSpeaking: # Wait for the synth to finish speaking. # _handleDoneSpeaking will call us again. self._shouldPushWhenDoneSpeaking = True return self._switchProfile() if not queue.pendingSequences: # The last commands in this queue were profile switches. # Call this method again in case other queues are waiting. return self._pushNextSpeech(True) seq = self._buildNextUtterance() if seq: # So that we can handle any accidentally skipped indexes. for item in seq: if isinstance(item, IndexCommand): self._indexesSpeaking.append(item.index) self._cancelledLastSpeechWithSynth = False log._speechManagerUnitTest(f"Synth Gets: {seq}") getSynth().speak(seq)
def test_setSynth_auto_fallback_ifOneCoreDoesntSupportDefaultLanguage(self): """ Ensures that if oneCore doesn't support the current language, setSynth("auto") falls back to the current synth, or espeak if there is no current synth. """ globalVars.appArgs.language = "bar" # set the lang so it is not supported synthDriverHandler.setSynth("auto") self.assertEqual(synthDriverHandler.getSynth().name, FAKE_DEFAULT_SYNTH_NAME) synthDriverHandler.setSynth(None) # reset the synth so there is no fallback synthDriverHandler.setSynth("auto") self.assertEqual(synthDriverHandler.getSynth().name, "espeak")
def finish(synthName, synthspeechConfig, msg): # stop previous synth because oneCore voice switch don't work without it config.conf[SCT_Speech] = synthSpeechConfig.copy() setSynth(synthName) config.conf[SCT_Speech][synthName] = synthSpeechConfig[ synthName].copy() getSynth().loadSettings() # Reinitialize the tones module to update the audio device import tones tones.terminate() tones.initialize() if msg: ui.message(msg)
def resetSynth(self) -> None: """If the synthesizer is not initialized - repeat attempts to initialize it.""" if not synthDriverHandler.getSynth(): synthDriverHandler.initialize() i = 0 while not synthDriverHandler.getSynth() and i <= config.conf[ADDON_NAME]['retries']: synthDriverHandler.setSynth(config.conf['speech']['synth']) sleep(1) if config.conf[ADDON_NAME]['retries'] != 0: i += 1 else: if config.conf[ADDON_NAME]['playsound']: self.audioEnabledSound()
def _processMpSpeech(text, language): # MathPlayer's default rate is 180 wpm. # Assume that 0% is 80 wpm and 100% is 450 wpm and scale accordingly. synth = getSynth() wpm = synth._percentToParam(synth.rate, 80, 450) breakMulti = 180.0 / wpm out = [] if language: out.append(speech.LangChangeCommand(language)) resetProsody = set() for m in RE_MP_SPEECH.finditer(text): if m.lastgroup == "break": out.append(speech.BreakCommand(time=int(m.group("break")) * breakMulti)) elif m.lastgroup == "char": out.extend((speech.CharacterModeCommand(True), m.group("char"), speech.CharacterModeCommand(False))) elif m.lastgroup == "comma": out.append(speech.BreakCommand(time=100)) elif m.lastgroup in PROSODY_COMMANDS: command = PROSODY_COMMANDS[m.lastgroup] out.append(command(multiplier=int(m.group(m.lastgroup)) / 100.0)) resetProsody.add(command) elif m.lastgroup == "prosodyReset": for command in resetProsody: out.append(command(multiplier=1)) resetProsody.clear() elif m.lastgroup == "phonemeText": out.append(speech.PhonemeCommand(m.group("ipa"), text=m.group("phonemeText"))) elif m.lastgroup == "content": out.append(m.group(0)) if language: out.append(speech.LangChangeCommand(None)) return out
def _doRemoveCancelledSpeechCommands(self): if not _shouldCancelExpiredFocusEvents(): return # Don't delete commands while iterating over _cancelCommandsForUtteranceBeingSpokenBySynth. latestCancelledUtteranceIndex = self._getMostRecentlyCancelledUtterance() log._speechManagerDebug(f"Last index: {latestCancelledUtteranceIndex}") if latestCancelledUtteranceIndex is not None: log._speechManagerDebug(f"Cancel and push speech") # Minimise the number of calls to _removeCompletedFromQueue by using the most recently cancelled # utterance index. This will remove all older queued speech also. self._removeCompletedFromQueue(latestCancelledUtteranceIndex) getSynth().cancel() self._cancelledLastSpeechWithSynth = True self._cancelCommandsForUtteranceBeingSpokenBySynth.clear() self._indexesSpeaking.clear() self._pushNextSpeech(True)
def setTemporaryAudioOutputDevice(outputDevice): global _temporaryOutputDevice curSynth = getSynth() prevOutputDevice = config.conf["speech"]["outputDevice"] ret = setOutputDevice(curSynth, outputDevice) if not ret: return def confirm(synth, prevOutputDevice): global _temporaryOutputDevice from ..settings import _addonConfigManager timeToLive = _addonConfigManager.getConfirmAudioDeviceChangeTimeOut() from ..utils import PutWindowOnForeground dialog = ConfirmOutputDevice(None, timeToLive) with dialog as d: PutWindowOnForeground(d.GetHandle(), 10, 0.5) res = d.ShowModal() d.Destroy() if res == wx.ID_OK: _temporaryOutputDevice = outputDevice else: # return to previous output device setOutputDevice(synth, prevOutputDevice) from ..settings import toggleConfirmAudioDeviceChangeAdvancedOption if toggleConfirmAudioDeviceChangeAdvancedOption(False): wx.CallLater(50, confirm, curSynth, prevOutputDevice) else: _temporaryOutputDevice = outputDevice obj = api.getFocusObject() speech.speakObject(obj)
def getCurrentSynthVoiceAndVariant(self): def getCurrentSettingName(setting): try: cur = getattr(synth, setting.name) tempList = list( getattr(synth, "available%ss" % setting.name.capitalize()).values()) try: # for nvda >= 2019.3 i = [x.id for x in tempList].index(cur) return tempList[i].displayName except: # noqa:E722 i = [x.ID for x in tempList].index(cur) return tempList[i].name except: # noqa:E722 return "" synth = getSynth() voice = "" variant = "" for s in synth.supportedSettings: if s.name == "voice": voice = getCurrentSettingName(s) elif s.name == "variant": variant = getCurrentSettingName(s) return (voice, variant)
def test_setSynth_auto_fallbackMode(self): """ Ensures setSynth("auto") successfully sets a synth in defaultSynthPriorityList, and config is unchanged. """ synthDriverHandler.setSynth("auto", isFallback=True) self.assertIn(synthDriverHandler.getSynth().name, synthDriverHandler.defaultSynthPriorityList) self.assertEqual(FAKE_DEFAULT_LANG, config.conf["speech"]["synth"])
def _getVoiceDictionary(profile): from synthDriverHandler import getSynth synth = getSynth() dictionaryFilename = _getVoiceDictionaryFileName(synth) # if we are om default profile or the specific dictionary profile is already loaded if not profile.name or _hasVoiceDictionaryProfile(profile.name, synth.name, dictionaryFilename): # we are with the correct dictionary loaded. Just return it. log.debug( f"Voice dictionary, backed by {dictionaries['voice'].fileName} was requested" ) return dictionaries["voice"] # we are on a user profile for which there is no dictionary created for the current voice. # The current loaded dictionary is the default profile one. # As we have beem called to get the profile dictionary for the current voice and it still does not exist, # We will create it now and pass the new, empty dictionary to the caller, but won't save it. # This is a task the caller should do when and if they wish dic = speechDictHandler.SpeechDict() dic.create( os.path.join(getProfileVoiceDictsPath(), synth.name, dictionaryFilename)) log.debug( f"voice dictionary was requested for profile {profile.name}, but the backing file does not exist." f" A New dictionary was created, set to be backed by {dic.fileName} if it is ever saved." ) return dic
def set(self) -> bool: """Sets the profile as the current voice synthesizer. @return: an indication of whether the synthesizer has been successfully switched on @rtype: bool """ state = False try: config.conf.profiles[0]['speech'][self._name].clear() config.conf.profiles[0]['speech'][self._name].update(self._conf) config.conf['speech'][self._name]._cache.clear() state = setSynth(self._name) getSynth().saveSettings() except KeyError: pass self._status = state return state
def _onSynthIndexReached(self, synth=None, index=None): log._speechManagerUnitTest( f"synthReachedIndex: {index}, synth: {synth}") if synth != getSynth(): return # This needs to be handled in the main thread. queueHandler.queueFunction(queueHandler.eventQueue, self._handleIndex, index)
def test_setSynth_auto_usesOneCore_ifSupportsDefaultLanguage(self): """ Ensures that if oneCore supports the current language, setSynth("auto") uses "oneCore". """ # test setup ensures current NVDA language is supported for oneCore synthDriverHandler.setSynth(None) # reset the synth so there is no fallback synthDriverHandler.setSynth("auto") self.assertEqual(synthDriverHandler.getSynth().name, "oneCore")
def script_review_sayAll(self, gesture): synthInstance = getSynth() if synthInstance.name == 'WorldVoiceXVED2': SayAllHandler = sayAll._SayAllHandler( SpeechWithoutPauses(speakFunc=synthInstance.patchedSpeak)) SayAllHandler.readText(sayAll.CURSOR.REVIEW) else: sayAll.SayAllHandler.readText(sayAll.CURSOR.REVIEW)
def reloadDictionaries(): from synthDriverHandler import getSynth synth = getSynth() loadProfileDict() loadVoiceDict(synth) log.debug( f"loaded dictionaries for profile {config.conf.getActiveProfile().name or 'default'}" )
def _onSynthDoneSpeaking(self, synth: Optional[ synthDriverHandler.SynthDriver] = None): if synth != getSynth(): return # This needs to be handled in the main thread. queueHandler.queueFunction(queueHandler.eventQueue, self._handleDoneSpeaking)
def test_setSynth_auto(self): """ Ensures setSynth("auto") successfully sets a synth in defaultSynthPriorityList, and saves it to config. """ synthDriverHandler.setSynth("auto") autoSynthName = synthDriverHandler.getSynth().name self.assertIn(autoSynthName, synthDriverHandler.defaultSynthPriorityList) self.assertEqual(config.conf["speech"]["synth"], autoSynthName)
def _onSynthDoneSpeaking(self, synth: Optional[ synthDriverHandler.SynthDriver] = None): log._speechManagerUnitTest(f"synthDoneSpeaking synth:{synth}") if synth != getSynth(): return # This needs to be handled in the main thread. queueHandler.queueFunction(queueHandler.eventQueue, self._handleDoneSpeaking)
def checkForUpdate(auto=False): """Check for an updated version of NVDA. This will block, so it generally shouldn't be called from the main thread. @param auto: Whether this is an automatic check for updates. @type auto: bool @return: Information about the update or C{None} if there is no update. @rtype: dict @raise RuntimeError: If there is an error checking for an update. """ allowUsageStats=config.conf["update"]['allowUsageStats'] params = { "autoCheck": auto, "allowUsageStats":allowUsageStats, "version": versionInfo.version, "versionType": versionInfo.updateVersionType, "osVersion": winVersion.winVersionText, "x64": os.environ.get("PROCESSOR_ARCHITEW6432") == "AMD64", } if auto and allowUsageStats: synthDriverClass = synthDriverHandler.getSynth().__class__ brailleDisplayClass = braille.handler.display.__class__ if braille.handler else None # Following are parameters sent purely for stats gathering. # If new parameters are added here, they must be documented in the userGuide for transparency. extraParams={ "language": languageHandler.getLanguage(), "installed": config.isInstalledCopy(), "synthDriver":getQualifiedDriverClassNameForStats(synthDriverClass) if synthDriverClass else None, "brailleDisplay":getQualifiedDriverClassNameForStats(brailleDisplayClass) if brailleDisplayClass else None, "outputBrailleTable":config.conf['braille']['translationTable'] if brailleDisplayClass else None, } params.update(extraParams) url = "%s?%s" % (CHECK_URL, urllib.parse.urlencode(params)) try: res = urllib.request.urlopen(url) except IOError as e: if isinstance(e.strerror, ssl.SSLError) and e.strerror.reason == "CERTIFICATE_VERIFY_FAILED": # #4803: Windows fetches trusted root certificates on demand. # Python doesn't trigger this fetch (PythonIssue:20916), so try it ourselves _updateWindowsRootCertificates() # and then retry the update check. res = urllib.request.urlopen(url) else: raise if res.code != 200: raise RuntimeError("Checking for update failed with code %d" % res.code) info = {} for line in res: # #9819: update description resource returns bytes, so make it Unicode. line = line.decode("utf-8").rstrip() try: key, val = line.split(": ", 1) except ValueError: raise RuntimeError("Error in update check output") info[key] = val if not info: return None return info
def test_setSynth_defaultSynths_fallbackMode(self): """ For each synth in the synthDriverHandler.defaultSynthPriorityList, ensure they can be successfully set and the config is unchanged. """ for synthName in synthDriverHandler.defaultSynthPriorityList: synthDriverHandler.setSynth(synthName, isFallback=True) self.assertEqual(synthName, synthDriverHandler.getSynth().name) self.assertEqual(FAKE_DEFAULT_LANG, config.conf["speech"]["synth"])
def test_setSynth_defaultSynths(self): """ For each synth in the synthDriverHandler.defaultSynthPriorityList, ensure they can be successfully set and saved to config. """ for synthName in synthDriverHandler.defaultSynthPriorityList: synthDriverHandler.setSynth(synthName) self.assertEqual(synthName, synthDriverHandler.getSynth().name) self.assertEqual(synthName, config.conf["speech"]["synth"])
def switchToDefaultOutputDevice(self) -> None: """Switch NVDA audio output to the default audio device.""" device: str = self.getDefaultDeviceName() if config.conf['speech']['outputDevice'] not in ("Microsoft Sound Mapper", device,): config.conf['speech']['outputDevice'] = device if synthDriverHandler.setSynth(synthDriverHandler.getSynth().name): tones.terminate() tones.initialize() if config.conf[ADDON_NAME]['playsound']: self.audioEnabledSound()
def speak(self, speechSequence: SpeechSequence, priority: Spri): log._speechManagerDebug( "Speak called: %r", speechSequence) # expensive string to build - defer interrupt = self._queueSpeechSequence(speechSequence, priority) self.removeCancelledSpeechCommands() # If speech isn't already in progress, we need to push the first speech. push = self._curPriQueue is None or 1 > len(self._indexesSpeaking) if interrupt: log._speechManagerDebug("Interrupting speech") getSynth().cancel() self._indexesSpeaking.clear() self._cancelCommandsForUtteranceBeingSpokenBySynth.clear() push = True if push: log._speechManagerDebug("Pushing next speech") self._pushNextSpeech(True) else: log._speechManagerDebug("Not pushing speech")
def _processSpeechSequence(self, inSeq: SpeechSequence): paramTracker = ParamChangeTracker() enteredTriggers = [] outSeqs = [] paramsToReplay = [] currentSynth = getSynth() outSeq = [] for command in inSeq: if isinstance(command, BaseCallbackCommand): # When the synth reaches this point, we want to call the callback. speechIndex = next(self._indexCounter) outSeq.append(IndexCommand(speechIndex)) self._indexesToCallbacks[speechIndex] = command # We split at indexes so we easily know what has completed speaking. outSeqs.append(paramsToReplay + outSeq) paramsToReplay.clear() outSeq.clear() continue if isinstance(command, ConfigProfileTriggerCommand): if not command.trigger.hasProfile: # Ignore triggers that have no associated profile. continue if command.enter and command.trigger in enteredTriggers: log.debugWarning( "Request to enter trigger which has already been entered: %r" % command.trigger.spec) continue if not command.enter and command.trigger not in enteredTriggers: log.debugWarning( "Request to exit trigger which wasn't entered: %r" % command.trigger.spec) continue self._ensureEndUtterance(outSeq, outSeqs, paramsToReplay, paramTracker) outSeqs.append([command]) if command.enter: enteredTriggers.append(command.trigger) else: enteredTriggers.remove(command.trigger) continue if isinstance(command, EndUtteranceCommand): self._ensureEndUtterance(outSeq, outSeqs, paramsToReplay, paramTracker) continue if isinstance(command, SynthParamCommand): paramTracker.update(command) outSeq.append(command) # Add the last sequence and make sure the sequence ends the utterance. self._ensureEndUtterance(outSeq, outSeqs, paramsToReplay, paramTracker) # Exit any profile triggers the caller didn't exit. for trigger in reversed(enteredTriggers): command = ConfigProfileTriggerCommand(trigger, False) outSeqs.append([command]) return outSeqs
def setOutputDevice(self, name: str) -> None: """Switche the NVDA output to the audio device with the specified name. @param name: name of the audio output device @type name: str """ config.conf['speech']['outputDevice'] = name status: bool = setSynth(getSynth().name) if status: tones.terminate() tones.initialize() ui.message(name)