def getPreInitSettings(cls) -> SupportedSettingType: """Get settings that can be configured before the provider is initialized. This is a class method because it does not rely on any instance state in this class. """ return [ driverHandler.BooleanDriverSetting( "shouldDoX", # value stored in matching property name on class "Should Do X", defaultVal=True ), driverHandler.BooleanDriverSetting( "shouldDoY", # value stored in matching property name on class "Should Do Y", defaultVal=False ), driverHandler.NumericDriverSetting( "amountOfZ", # value stored in matching property name on class "Amount of Z", defaultVal=11 ), driverHandler.DriverSetting( # options for this come from a property with name generated by # f"available{settingID.capitalize()}s" # Note: # First letter of Id becomes capital, the rest lowercase. # the 's' character on the end. # result: 'availableNameofsomethings' "nameOfSomething", # value stored in matching property name on class "Name of something", ) ]
def VolumeSetting(cls,minStep=1): """Factory function for creating volume setting.""" # Translators: Label for a setting in voice settings dialog. return driverHandler.NumericDriverSetting("volume",_("V&olume"),minStep=minStep,normalStep=5,availableInSettingsRing=True, # Translators: Label for a setting in synth settings ring. displayName=pgettext('synth setting','Volume'))
def InflectionSetting(cls, minStep=1): """Factory function for creating inflection setting.""" # Translators: Label for a setting in voice settings dialog. return driverHandler.NumericDriverSetting( "inflection", _("&Inflection"), minStep=minStep, availableInSettingsRing=True, # Translators: Label for a setting in synth settings ring. displayName=pgettext('synth setting', 'Inflection'))
def _getAvailableRuntimeSettings(self) -> SupportedSettingType: settings = [] if self._hasFeature("runtimeOnlySetting_externalValueLoad"): settings.extend([ driverHandler.NumericDriverSetting( "runtimeOnlySetting_externalValueLoad", # value stored in matching property name on class "Runtime Only amount, external value load", # no GUI default ), ]) if self._hasFeature("runtimeOnlySetting_localDefault"): settings.extend([ driverHandler.NumericDriverSetting( "runtimeOnlySetting_localDefault", # value stored in matching property name on class "Runtime Only amount, local default", defaultVal=50, ), ]) return settings
def PitchSetting(cls, minStep=1): """Factory function for creating pitch setting.""" return driverHandler.NumericDriverSetting( "pitch", # Translators: Label for a setting in voice settings dialog. _("&Pitch"), minStep=minStep, availableInSettingsRing=True, # Translators: Label for a setting in synth settings ring. displayName=pgettext('synth setting', 'Pitch'), )
class SynthDriver(SynthDriver): name = "WorldVoiceXVED2" description = "WorldVoice(VE)" supportedSettings = [ SynthDriver.VoiceSetting(), # SynthDriver.VariantSetting(), SynthDriver.RateSetting(), SynthDriver.PitchSetting(), SynthDriver.VolumeSetting(), driverHandler.DriverSetting( "numlan", # Translators: Label for a setting in voice settings dialog. _("Number &Language"), availableInSettingsRing=True, defaultVal="default", # Translators: Label for a setting in synth settings ring. displayName=_("Number Language"), ), driverHandler.DriverSetting( "nummod", # Translators: Label for a setting in voice settings dialog. _("Number &Mode"), availableInSettingsRing=True, defaultVal="value", # Translators: Label for a setting in synth settings ring. displayName=_("Number Mode"), ), driverHandler.NumericDriverSetting( "chinesespace", # Translators: Label for a setting in voice settings dialog. _("Pause time when encountering spaces between Chinese"), defaultVal=0, minStep=1, ), driverHandler.BooleanDriverSetting( "cni", _("Ignore comma between number"), defaultVal=False, ), driverHandler.BooleanDriverSetting( "dli", _("Ignore language information of document"), defaultVal=False, ), driverHandler.BooleanDriverSetting( "uwv", _("Enable WorldVoice setting rules to detect text language"), availableInSettingsRing=True, defaultVal=True, displayName=_("Enable WorldVoice rules"), ), ] supportedCommands = { speech.IndexCommand, speech.CharacterModeCommand, speech.LangChangeCommand, speech.BreakCommand, speech.PitchCommand, speech.RateCommand, speech.VolumeCommand, } supportedNotifications = {synthIndexReached, synthDoneSpeaking} @classmethod def check(cls): with _vocalizer.preOpenVocalizer() as check: return check def __init__(self): _config.load() # Initialize the driver try: _vocalizer.initialize(self._onIndexReached) log.debug("Vocalizer info: %s" % self._info()) except _vocalizer.VeError as e: if e.code == _vocalizer.VAUTONVDA_ERROR_INVALID: log.info("Vocalizer license for NVDA is Invalid") elif e.code == _vocalizer.VAUTONVDA_ERROR_DEMO_EXPIRED: log.info("Vocalizer demo license for NVDA as expired.") raise self._voiceManager = VoiceManager() self._realSpeakFunc = speech.speak self._realSpellingFunc = speech.speakSpelling speech.speak = self.patchedSpeak speech.speakSpelling = self.patchedSpeakSpelling speechSymbols = SpeechSymbols() speechSymbols.load('unicode.dic') self._languageDetector = languageDetection.LanguageDetector( list(self._voiceManager.languages), speechSymbols) speech._speakWithoutPauses = speech.SpeechWithoutPauses( speakFunc=self.patchedSpeak) speech.speakWithoutPauses = speech._speakWithoutPauses.speakWithoutPauses self._localeToVoices = self._voiceManager.localeToVoicesMap self._locales = sorted([ l for l in self._localeToVoices if len(self._localeToVoices[l]) > 0 ]) self._localeNames = list( map(self._getLocaleReadableName, self._locales)) self._voice = None def _onIndexReached(self, index): if index is not None: synthIndexReached.notify(synth=self, index=index) else: synthDoneSpeaking.notify(synth=self) def terminate(self): speech.speak = self._realSpeakFunc speech.speakSpelling = self._realSpellingFunc speech._speakWithoutPauses = speech.SpeechWithoutPauses( speakFunc=speech.speak) speech.speakWithoutPauses = speech._speakWithoutPauses.speakWithoutPauses try: self.cancel() self._voiceManager.close() _vocalizer.terminate() except RuntimeError: log.error("Vocalizer terminate", exc_info=True) def speak(self, speechSequence): if self.uwv \ and _config.vocalizerConfig['autoLanguageSwitching']['useUnicodeLanguageDetection'] \ and _config.vocalizerConfig['autoLanguageSwitching']['afterSymbolDetection']: speechSequence = self._languageDetector.add_detected_language_commands( speechSequence) speechSequence = list(speechSequence) speechSequence = self.patchedNumSpeechSequence(speechSequence) speechSequence = self.patchedSpaceSpeechSequence(speechSequence) currentInstance = defaultInstance = self._voiceManager.defaultVoiceInstance.token currentLanguage = defaultLanguage = self.language chunks = [] hasText = False charMode = False for command in speechSequence: if isinstance(command, str): command = command.strip() if not command: continue # If character mode is on use lower case characters # Because the synth does not allow to turn off the caps reporting if charMode or len(command) == 1: command = command.lower() # replace the excape character since it is used for parameter changing chunks.append(command.replace("\x1b", "")) hasText = True elif isinstance(command, speech.IndexCommand): chunks.append("\x1b\\mrk=%d\\" % command.index) elif isinstance(command, speech.BreakCommand): maxTime = 6553 if self.variant == "bet2" else 65535 breakTime = max(1, min(command.time, maxTime)) self._speak(currentInstance, chunks) chunks = [] hasText = False _vocalizer.processBreak(currentInstance, breakTime) elif isinstance(command, speech.CharacterModeCommand): charMode = command.state s = "\x1b\\tn=spell\\" if command.state else "\x1b\\tn=normal\\" chunks.append(s) elif isinstance(command, speech.LangChangeCommand) or isinstance( command, speechcommand.WVLangChangeCommand): if command.lang == currentLanguage: # Keep on the same voice. continue if command.lang is None: # No language, use default. currentInstance = defaultInstance currentLanguage = defaultLanguage continue # Changed language, lets see what we have. currentLanguage = command.lang newVoiceName = self._voiceManager.getVoiceNameForLanguage( currentLanguage) if newVoiceName is None: # No voice for this language, use default. newInstance = defaultInstance else: newInstance = self._voiceManager.getVoiceInstance( newVoiceName).token if newInstance == currentInstance: # Same voice, next command. continue if hasText: # We changed voice, send text we already have to vocalizer. self._speak(currentInstance, chunks) chunks = [] hasText = False currentInstance = newInstance elif isinstance(command, speech.PitchCommand): pitch = self._voiceManager.getVoiceParameter( currentInstance, _vocalizer.VE_PARAM_PITCH, type_=int) pitchOffset = self._percentToParam( command.offset, _vocalizer.PITCH_MIN, _vocalizer.PITCH_MAX) - _vocalizer.PITCH_MIN chunks.append("\x1b\\pitch=%d\\" % (pitch + pitchOffset)) elif isinstance(command, speechcommand.SplitCommand): self._speak(currentInstance, chunks) chunks = [] hasText = False if chunks: self._speak(currentInstance, chunks) def _speak(self, voiceInstance, chunks): text = speech.CHUNK_SEPARATOR.join(chunks).replace(" \x1b", "\x1b") _vocalizer.processText2Speech(voiceInstance, text) def patchedSpeak(self, speechSequence, symbolLevel=None, priority=None): if self._cni: temp = [] for command in speechSequence: if isinstance(command, str): temp.append(comma_number_pattern.sub( lambda m: '', command)) else: temp.append(command) speechSequence = temp if self._dli: speechSequence = self.patchedRemoveLangChangeCommandSpeechSequence( speechSequence) if self.uwv \ and _config.vocalizerConfig['autoLanguageSwitching']['useUnicodeLanguageDetection'] \ and not _config.vocalizerConfig['autoLanguageSwitching']['afterSymbolDetection']: speechSequence = self._languageDetector.add_detected_language_commands( speechSequence) speechSequence = list(speechSequence) self._realSpeakFunc(speechSequence, symbolLevel, priority=priority) def patchedSpeakSpelling(self, text, locale=None, useCharacterDescriptions=False, priority=None): if config.conf["speech"]["autoLanguageSwitching"] \ and _config.vocalizerConfig['autoLanguageSwitching']['useUnicodeLanguageDetection'] \ and config.conf["speech"]["trustVoiceLanguage"]: for text, loc in self._languageDetector.process_for_spelling( text, locale): self._realSpellingFunc(text, loc, useCharacterDescriptions, priority=priority) else: self._realSpellingFunc(text, locale, useCharacterDescriptions, priority=priority) def cancel(self): _vocalizer.stop() def pause(self, switch): if switch: _vocalizer.pause() else: _vocalizer.resume() def _get_volume(self): return self._voiceManager.defaultVoiceInstance.volume def _set_volume(self, value): self._voiceManager.defaultVoiceInstance.volume = value self._voiceManager.defaultVoiceInstance.commit() def _get_rate(self): return self._voiceManager.defaultVoiceInstance.rate def _set_rate(self, value): self._voiceManager.defaultVoiceInstance.rate = value self._voiceManager.defaultVoiceInstance.commit() def _get_pitch(self): return self._voiceManager.defaultVoiceInstance.pitch def _set_pitch(self, value): self._voiceManager.defaultVoiceInstance.pitch = value self._voiceManager.defaultVoiceInstance.commit() def _getAvailableVoices(self): return self._voiceManager.voiceInfos def _get_voice(self): if self._voice is None: voice = self._voiceManager.getVoiceNameForLanguage( languageHandler.getLanguage()) if voice is None: voice = list(self.availableVoices.keys())[0] return voice return self._voiceManager.defaultVoiceName def _set_voice(self, voiceName): self._voice = voiceName if voiceName == self._voiceManager.defaultVoiceName: return # Stop speech before setting a new voice to avoid voice instances # continuing speaking when changing voices for, e.g., say-all # See NVDA ticket #3540 _vocalizer.stop() self._voiceManager.setDefaultVoice(voiceName) # Available variants are cached by default. As variants maybe different for each voice remove the cached value # if hasattr(self, '_availableVariants'): # del self._availableVariants # Synchronize with the synth so the parameters # we report are not from the previous voice. # _vocalizer.sync() def _get_variant(self): return self._voiceManager.defaultVoiceInstance.variant def _set_variant(self, name): self.cancel() self._voiceManager.defaultVoiceInstance.variant = name def _getAvailableVariants(self): dbs = self._voiceManager.defaultVoiceInstance.variants return OrderedDict([(d, VoiceInfo(d, d)) for d in dbs]) def _get_availableLanguages(self): return self._voiceManager.languages def _get_language(self): return self._voiceManager.getVoiceLanguage() def _info(self): s = [self.description] return ", ".join(s) def _get_availableNumlans(self): return dict( { "default": driverHandler.StringParameterInfo("default", _("default")), }, **{ locale: driverHandler.StringParameterInfo(locale, name) for locale, name in zip(self._locales, self._localeNames) }) def _get_numlan(self): return self._numlan def _set_numlan(self, value): self._numlan = value def _get_availableNummods(self): return dict({ "value": driverHandler.StringParameterInfo("value", _("value")), "number": driverHandler.StringParameterInfo("number", _("number")), }) def _get_nummod(self): return self._nummod def _set_nummod(self, value): self._nummod = value def _get_chinesespace(self): return self._chinesespace def _set_chinesespace(self, value): self._chinesespace = value def _get_cni(self): return self._cni def _set_cni(self, value): self._cni = value def _get_dli(self): return self._dli def _set_dli(self, value): self._dli = value def patchedNumSpeechSequence(self, speechSequence): return self.coercionNumberLangChange(speechSequence, self._numlan, self._nummod) def patchedSpaceSpeechSequence(self, speechSequence): if not int(self._chinesespace) == 0: joinString = "" tempSpeechSequence = [] for command in speechSequence: if not isinstance(command, str): tempSpeechSequence.append(joinString) tempSpeechSequence.append(command) joinString = "" else: joinString += command tempSpeechSequence.append(joinString) speechSequence = tempSpeechSequence tempSpeechSequence = [] for command in speechSequence: if isinstance(command, str): result = re.split(chinese_space_pattern, command) if len(result) == 1: tempSpeechSequence.append(command) else: temp = [] for i in result: temp.append(i) temp.append( speech.BreakCommand( int(self._chinesespace) * 5)) temp = temp[:-1] tempSpeechSequence += temp else: tempSpeechSequence.append(command) speechSequence = tempSpeechSequence return speechSequence def patchedRemoveLangChangeCommandSpeechSequence(self, speechSequence): result = [] for command in speechSequence: if not isinstance(command, speech.LangChangeCommand): result.append(command) return result def patchedLengthSpeechSequence(self, speechSequence): result = [] for command in speechSequence: if isinstance(command, str): result.extend(self.lengthsplit(command, 100)) else: result.append(command) return result def lengthsplit(self, string, length): result = [] pattern = re.compile(r"[\s]") spaces = pattern.findall(string) others = pattern.split(string) fragment = "" for other, space in zip(others, spaces): fragment += other + space if len(fragment) > length: result.append(fragment) result.append(speechcommand.SplitCommand()) fragment = "" fragment += others[-1] result.append(fragment) return result def resplit(self, pattern, string, mode): result = [] numbers = pattern.findall(string) others = pattern.split(string) for other, number in zip(others, numbers): if mode == 'value': result.extend([ other, speech.LangChangeCommand('StartNumber'), number, speech.LangChangeCommand('EndNumber') ]) elif mode == 'number': result.extend([ other, speech.LangChangeCommand('StartNumber'), ' '.join(number).replace(" . ", "."), speech.LangChangeCommand('EndNumber') ]) result.append(others[-1]) return result def coercionNumberLangChange(self, speechSequence, numberLanguage, mode): result = [] for command in speechSequence: if isinstance(command, str): result.extend(self.resplit(number_pattern, command, mode)) else: result.append(command) currentLang = self.language for command in result: if isinstance(command, speech.LangChangeCommand): if command.lang == 'StartNumber': command.lang = numberLanguage elif command.lang == 'EndNumber': command.lang = currentLang else: currentLang = command.lang return result def _getLocaleReadableName(self, locale): description = languageHandler.getLanguageDescription(locale) return "%s" % (description) if description else locale
class SynthDriver(synthDriverHandler.SynthDriver): supportedSettings = (SynthDriver.VoiceSetting(), SynthDriver.VariantSetting(), SynthDriver.RateSetting(), SynthDriver.PitchSetting(), SynthDriver.InflectionSetting(), SynthDriver.VolumeSetting(), driverHandler.NumericDriverSetting( "hsz", "Head Size"), driverHandler.NumericDriverSetting( "rgh", "Roughness"), driverHandler.NumericDriverSetting( "bth", "Breathiness"), driverHandler.BooleanDriverSetting( "backquoteVoiceTags", "Enable backquote voice &tags", True)) supportedCommands = { speech.IndexCommand, speech.CharacterModeCommand, speech.LangChangeCommand, speech.BreakCommand, speech.PitchCommand, speech.RateCommand, speech.VolumeCommand, speech.PhonemeCommand, } supportedNotifications = {synthIndexReached, synthDoneSpeaking} PROSODY_ATTRS = { speech.PitchCommand: _eloquence.pitch, speech.VolumeCommand: _eloquence.vlm, speech.RateCommand: _eloquence.rate, } description = 'ETI-Eloquence' name = 'eloquence' @classmethod def check(cls): return _eloquence.eciCheck() def __init__(self): _eloquence.initialize(self._onIndexReached) self.curvoice = "enu" self.rate = 50 self.variant = "1" def speak(self, speechSequence): last = None outlist = [] for item in speechSequence: if isinstance(item, str): s = str(item) s = self.xspeakText(s) outlist.append((_eloquence.speak, (s, ))) last = s elif isinstance(item, speech.IndexCommand): outlist.append((_eloquence.index, (item.index, ))) elif isinstance(item, speech.BreakCommand): # Eloquence doesn't respect delay time in milliseconds. # Therefor we need to adjust waiting time depending on curernt speech rate # The following table of adjustments has been measured empirically # Then we do linear approximation coefficients = { 10: 1, 43: 2, 60: 3, 75: 4, 85: 5, } ck = sorted(coefficients.keys()) if self.rate <= ck[0]: factor = coefficients[ck[0]] elif self.rate >= ck[-1]: factor = coefficients[ck[-1]] elif self.rate in ck: factor = coefficients[self.rate] else: li = [ index for index, r in enumerate(ck) if r < self.rate ][-1] ri = li + 1 ra = ck[li] rb = ck[ri] factor = 1.0 * coefficients[ra] + ( coefficients[rb] - coefficients[ra]) * (self.rate - ra) / (rb - ra) pFactor = factor * item.time pFactor = int(pFactor) outlist.append((_eloquence.speak, (f'`p{pFactor}.', ))) elif type(item) in self.PROSODY_ATTRS: pr = self.PROSODY_ATTRS[type(item)] if item.multiplier == 1: # Revert back to defaults outlist.append((_eloquence.cmdProsody, ( pr, None, ))) else: outlist.append((_eloquence.cmdProsody, ( pr, item.multiplier, ))) if last is not None and not last.rstrip()[-1] in punctuation: outlist.append((_eloquence.speak, ('`p1.', ))) outlist.append((_eloquence.index, (0xffff, ))) outlist.append((_eloquence.synth, ())) _eloquence.synth_queue.put(outlist) _eloquence.process() def xspeakText(self, text, should_pause=False): if _eloquence.params[9] == 65536 or _eloquence.params[9] == 65537: text = resub(english_fixes, text) if _eloquence.params[9] == 131072 or _eloquence.params[9] == 131073: text = resub(spanish_fixes, text) if _eloquence.params[9] in (196609, 196608): text = resub(french_fixes, text) #this converts to ansi for anticrash. If this breaks with foreign langs, we can remove it. #text = text.encode('mbcs') text = normalizeText(text) text = resub(anticrash_res, text) if not self._backquoteVoiceTags: text = text.replace('`', ' ') text = "`pp0 `vv%d %s" % (self.getVParam(_eloquence.vlm), text ) #no embedded commands text = pause_re.sub(r'\1 `p1\2\3', text) text = time_re.sub(r'\1:\2 \3', text) #if two strings are sent separately, pause between them. This might fix some of the audio issues we're having. if should_pause: text = text + ' `p1.' return text # _eloquence.speak(text, index) # def cancel(self): # self.dll.eciStop(self.handle) def pause(self, switch): _eloquence.pause(switch) # self.dll.eciPause(self.handle,switch) def terminate(self): _eloquence.terminate() _backquoteVoiceTags = False def _get_backquoteVoiceTags(self): return self._backquoteVoiceTags def _set_backquoteVoiceTags(self, enable): if enable == self._backquoteVoiceTags: return self._backquoteVoiceTags = enable def _get_rate(self): return self._paramToPercent(self.getVParam(_eloquence.rate), minRate, maxRate) def _set_rate(self, vl): self._rate = self._percentToParam(vl, minRate, maxRate) self.setVParam(_eloquence.rate, self._percentToParam(vl, minRate, maxRate)) def _get_pitch(self): return self.getVParam(_eloquence.pitch) def _set_pitch(self, vl): self.setVParam(_eloquence.pitch, vl) def _get_volume(self): return self.getVParam(_eloquence.vlm) def _set_volume(self, vl): self.setVParam(_eloquence.vlm, int(vl)) def _set_inflection(self, vl): vl = int(vl) self.setVParam(_eloquence.fluctuation, vl) def _get_inflection(self): return self.getVParam(_eloquence.fluctuation) def _set_hsz(self, vl): vl = int(vl) self.setVParam(_eloquence.hsz, vl) def _get_hsz(self): return self.getVParam(_eloquence.hsz) def _set_rgh(self, vl): vl = int(vl) self.setVParam(_eloquence.rgh, vl) def _get_rgh(self): return self.getVParam(_eloquence.rgh) def _set_bth(self, vl): vl = int(vl) self.setVParam(_eloquence.bth, vl) def _get_bth(self): return self.getVParam(_eloquence.bth) def _getAvailableVoices(self): o = OrderedDict() for name in os.listdir(_eloquence.eciPath[:-8]): if not name.lower().endswith('.syn'): continue info = _eloquence.langs[name.lower()[:-4]] o[str(info[0])] = synthDriverHandler.VoiceInfo( str(info[0]), info[1], None) return o def _get_voice(self): return str(_eloquence.params[9]) def _set_voice(self, vl): _eloquence.set_voice(vl) self.curvoice = vl def getVParam(self, pr): return _eloquence.getVParam(pr) def setVParam(self, pr, vl): _eloquence.setVParam(pr, vl) def _get_lastIndex(self): #fix? return _eloquence.lastindex def cancel(self): _eloquence.stop() def _getAvailableVariants(self): global variants return OrderedDict( (str(id), synthDriverHandler.VoiceInfo(str(id), name)) for id, name in variants.items()) def _set_variant(self, v): global variants self._variant = v if int(v) in variants else "1" _eloquence.setVariant(int(v)) self.setVParam(_eloquence.rate, self._rate) # if 'eloquence' in config.conf['speech']: # config.conf['speech']['eloquence']['pitch'] = self.pitch def _get_variant(self): return self._variant def _onIndexReached(self, index): if index is not None: synthIndexReached.notify(synth=self, index=index) else: synthDoneSpeaking.notify(synth=self)