diff --git a/addon/globalPlugins/ibmtts.py b/addon/globalPlugins/ibmtts.py index 0692894..da581ce 100644 --- a/addon/globalPlugins/ibmtts.py +++ b/addon/globalPlugins/ibmtts.py @@ -31,8 +31,8 @@ def makeSettings(self, settingsSizer): self._setValues() def _setValues(self): - self._ttsPath.SetValue(config.conf['ibmeci']['TTSPath']) - self._dllName.SetValue(config.conf['ibmeci']['dllName']) + self._ttsPath.SetValue(config.conf.profiles[0]['ibmeci']['TTSPath']) + self._dllName.SetValue(config.conf.profiles[0]['ibmeci']['dllName']) def _onBrowseClick(self, evt): # Translators: The message displayed in the dialog that allows you to look for the IBMTTS library. @@ -94,7 +94,7 @@ def _onSetLocalClick(self, evt): if res: self._ttsPath.SetValue("ibmtts") # this parameter is saved even if the user doesn't click accept button. - config.conf['ibmeci']['TTSPath'] = self._ttsPath.GetValue() + config.conf.profiles[0]['ibmeci']['TTSPath'] = self._ttsPath.GetValue() # Translators: The message displayed when copying IBMTTS files to Add-on was successful. gui.messageBox(_("Successfully copied IBMTTS files. The local copy will be used after restart NVDA."), # Translators: The title displayed when copying IBMTTS files to Add-on was successful. @@ -118,8 +118,8 @@ def copyTtsFiles(self): installer.tryCopyFile(sourceFilePath,destFilePath) def onSave(self): - config.conf['ibmeci']['dllName'] = self._dllName.GetValue() - config.conf['ibmeci']['TTSPath'] = self._ttsPath.GetValue() + config.conf.profiles[0]['ibmeci']['dllName'] = self._dllName.GetValue() + config.conf.profiles[0]['ibmeci']['TTSPath'] = self._ttsPath.GetValue() class GlobalPlugin(globalPluginHandler.GlobalPlugin): diff --git a/addon/installTasks.py b/addon/installTasks.py index 0072f22..bc76fe4 100644 --- a/addon/installTasks.py +++ b/addon/installTasks.py @@ -1,6 +1,7 @@ # -*- coding: UTF-8 -*- #Copyright (C) 2009 - 2019 David CM, released under the GPL. # Author: David CM and others. +# note: this file doesn't get settings from the base profile to avoid issues when updating from older versions. from synthDrivers import _settingsDB import config, gui, os, wx, addonHandler diff --git a/addon/synthDrivers/_ibmeci.py b/addon/synthDrivers/_ibmeci.py index 62217f1..b0f3403 100644 --- a/addon/synthDrivers/_ibmeci.py +++ b/addon/synthDrivers/_ibmeci.py @@ -35,8 +35,7 @@ class ECIParam: class ECIVoiceParam: params = range(1,8) eciGender, eciHeadSize, eciPitchBaseline, eciPitchFluctuation, eciRoughness, eciBreathiness, eciSpeed, eciVolume = range(8) - - + class ECIDictVolume: eciMainDict, eciRootDict, eciAbbvDict, eciMainDictExt = range(4) @@ -46,59 +45,63 @@ class ECIMessage: class ECICallbackReturn: eciDataNotProcessed, eciDataProcessed, eciDataAbort= range(3) +# constants +samples=3300 user32 = windll.user32 +WM_PROCESS = 1025 +WM_SILENCE = 1026 +WM_PARAM = 1027 +WM_VPARAM=1028 +WM_COPYVOICE=1029 +WM_KILL=1030 +WM_SYNTH=1031 +WM_INDEX=1032 + +langs={ + 'esp': (131072, _('Castilian Spanish'), 'es_ES', 'es'), + 'esm': (131073, _('Latin American Spanish'), 'es_ME', 'es_CO'), + 'ptb': (458752, _('Brazilian Portuguese'), 'pt_BR', 'pt'), + 'fra': (196608, _('French'), 'fr_FR', 'fr'), + 'frc': (196609, _('French Canadian'), 'fr_CA', ''), + 'fin': (589824, _('Finnish'), 'fi_FI', 'fi'), + 'chs': (393216, _('Chinese'), 'zh_GB', 'zh'), + 'jpn': (524288, _('Japanese'), 'ja_JA', 'jp'), + 'kor': (655360, _('Korean'), 'ko_KO', 'ko'), + 'deu': (262144, _('German'), 'de_DE', 'de'), + 'ita': (327680, _('Italian'), 'it_IT', 'it'), + 'enu': (65536, _('American English'), 'en_US', 'en'), + 'eng': (65537, _('British English'), 'en_UK', '') +} + audioStream = BytesIO() speaking=False eciThread = None +callbackQueue = None +callbackThread = None eciQueue = None eciThreadId = None -callbackThread = None -callbackQueue = None +idleTimer = None onIndexReached = None onDoneSpeaking = None -samples=3300 buffer = create_string_buffer(samples*2) -idleTimer = None + stopped = threading.Event() started = threading.Event() param_event = threading.Event() - - lastindex=0 -langs={'esp': (131072, _('Castilian Spanish'), 'es_ES', 'es'), -'esm': (131073, _('Latin American Spanish'), 'es_ME', 'es_CO'), -'ptb': (458752, _('Brazilian Portuguese'), 'pt_BR', 'pt'), -'fra': (196608, _('French'), 'fr_FR', 'fr'), -'frc': (196609, _('French Canadian'), 'fr_CA', ''), -'fin': (589824, _('Finnish'), 'fi_FI', 'fi'), -'chs': (393216, _('Chinese'), 'zh_GB', 'zh'), -'jpn': (524288, _('Japanese'), 'ja_JA', 'jp'), -'kor': (655360, _('Korean'), 'ko_KO', 'ko'), -'deu': (262144, _('German'), 'de_DE', 'de'), -'ita': (327680, _('Italian'), 'it_IT', 'it'), -'enu': (65536, _('American English'), 'en_US', 'en'), -'eng': (65537, _('British English'), 'en_UK', '')} - avLangs=0 ttsPath="" dllName="" -WM_PROCESS = 1025 -WM_SILENCE = 1026 -WM_PARAM = 1027 -WM_VPARAM=1028 -WM_COPYVOICE=1029 -WM_KILL=1030 -WM_SYNTH=1031 -WM_INDEX=1032 -params = {} -vparams = {} - #We can only have one of each in NVDA. Make this global dll = None handle = None +params = {} +vparams = {} + + class EciThread(threading.Thread): def run(self): global vparams, params, speaking, endMarkersCount @@ -170,13 +173,12 @@ def processEciQueue(): def eciCheck(): global ttsPath, dllName, dll - dllName = config.conf['ibmeci']['dllName'] - ttsPath = config.conf['ibmeci']['TTSPath'] + dllName = config.conf.profiles[0]['ibmeci']['dllName'] + ttsPath = config.conf.profiles[0]['ibmeci']['TTSPath'] if not path.isabs(ttsPath): ttsPath = path.join(path.abspath(path.dirname(__file__)), ttsPath) if path.exists(ttsPath): iniCheck() - print (ttsPath, path.exists(ttsPath)) if not path.exists(ttsPath): return False if dll: return True try: @@ -216,6 +218,7 @@ def eciNew(): def _callbackExec(func, *args, **kwargs): global callbackQueue callbackQueue.put((func, args, kwargs)) + def setLast(lp): global lastindex lastindex = lp @@ -305,9 +308,10 @@ def initialize(indexCallback, doneCallback): callbackThread.start() def speak(text): -#Sometimes the synth slows down for one string of text. Why? -#Trying to fix it here. - if ECIVoiceParam.eciSpeed in vparams: text = b"`vs%d%s" % (vparams[ECIVoiceParam.eciSpeed], text) + # deleted the following fix because is incompatible with NVDA's speech change command. Now send it from speak in ibmeci.py + #Sometimes the synth slows down for one string of text. Why? + #Trying to fix it here. + # if ECIVoiceParam.eciSpeed in vparams: text = b"`vs%d%s" % (vparams[ECIVoiceParam.eciSpeed], text) dll.eciAddText(handle, text) def index(x): diff --git a/addon/synthDrivers/_settingsDB.py b/addon/synthDrivers/_settingsDB.py index a129726..edc1109 100644 --- a/addon/synthDrivers/_settingsDB.py +++ b/addon/synthDrivers/_settingsDB.py @@ -9,4 +9,11 @@ "dllName": "string(default='eci.dll')", "TTSPath": "string(default='ibmtts')" } -config.conf.spec["ibmeci"]=confspec \ No newline at end of file +config.conf.spec["ibmeci"]=confspec + +def setConfig(): + d=config.conf['ibmeci'].dict() + if 'ibmeci' not in config.conf.profiles[0]: config.conf.profiles[0]['ibmeci'] = d + if 'TTSPath' not in config.conf.profiles[0]['ibmeci']: config.conf.profiles[0]['ibmeci']['TTSPath'] = d['TTSPath'] + if 'dllName' not in config.conf.profiles[0]['ibmeci']: config.conf.profiles[0]['ibmeci']['dllName'] = d['dllName'] +setConfig() diff --git a/addon/synthDrivers/ibmeci.py b/addon/synthDrivers/ibmeci.py index 4d6ae62..c0e1e71 100644 --- a/addon/synthDrivers/ibmeci.py +++ b/addon/synthDrivers/ibmeci.py @@ -28,7 +28,7 @@ def unicode(s): punctuation = "-,.?!:;" -minRate=0 +minRate=40 maxRate=156 pause_re = re.compile(br'([a-zA-Z])([-.(),:;!?])( |$)') @@ -37,61 +37,61 @@ def unicode(s): anticrash_res = { re.compile(br'\b(|\d+|\W+)(|un|anti|re)c(ae|\xe6)sur', re.I): br'\1\2seizur', re.compile(br"\b(|\d+|\W+)h'(r|v)[e]", re.I): br"\1h ' \2 e", -# re.compile(r"\b(|\d+|\W+)wed[h]esday", re.I): r"\1wed hesday", -re.compile(br'hesday'): b' hesday', - re.compile(br"\b(|\d+|\W+)tz[s]che", re.I): br"\1tz sche" + # re.compile(r"\b(|\d+|\W+)wed[h]esday", re.I): r"\1wed hesday", + re.compile(br'hesday'): b' hesday', + re.compile(br"\b(|\d+|\W+)tz[s]che", re.I): br"\1tz sche" } english_fixes = { -re.compile(r'(\w+)\.([a-zA-Z]+)'): r'\1 dot \2', -re.compile(r'([a-zA-Z0-9_]+)@(\w+)'): r'\1 at \2', -} -french_fixes = { -re.compile(r'([a-zA-Z0-9_]+)@(\w+)'): r'\1 arobase \2', + re.compile(r'(\w+)\.([a-zA-Z]+)'): r'\1 dot \2', + re.compile(r'([a-zA-Z0-9_]+)@(\w+)'): r'\1 at \2', } +french_fixes = { re.compile(r'([a-zA-Z0-9_]+)@(\w+)'): r'\1 arobase \2' } spanish_fixes = { -#for emails -re.compile(r'([a-zA-Z0-9_]+)@(\w+)'): r'\1 arroba \2', -re.compile(u'([€$]\d{1,3})((\s\d{3})+\.\d{2})'): r'\1 \2', + #for emails + re.compile(r'([a-zA-Z0-9_]+)@(\w+)'): r'\1 arroba \2', + re.compile(u'([€$]\d{1,3})((\s\d{3})+\.\d{2})'): r'\1 \2', } -variants = {1:"Reed", -2:"Shelley", -3:"Bobby", -4:"Rocko", -5:"Glen", -6:"Sandy", -7:"Grandma", -8:"Grandpa"} +variants = { + 1:"Reed", + 2:"Shelley", + 3:"Bobby", + 4:"Rocko", + 5:"Glen", + 6:"Sandy", + 7:"Grandma", + 8:"Grandpa" +} # For langChangeCommand langsAnnotations={ -"en":b"`l1", -"en_US":b"`l1.0", -"en_UK":b"`l1.1", -"en_GB":b"`l1.1", -"es":b"`l2", -"es_ES":b"`l2.0", -"es_ME":b"`l2.1", -"fr":b"`l3", -"fr_FR":b"`l3.0", -"fr_CA":b"`l3.1", -"de":b"`l4", -"de_DE":b"`l4", -"it":b"`l5", -"it_IT":b"`l5", -"zh":b"`l6", -"zh_gb":b"`l6.0", -"pt":b"`l7", -"pt_BR":b"`l7.0", -"pt_PT":b"`l7.1", -"ja":b"`l8", -"ja_ja":b"`l8.0", -"ko":b"`l10", -"ko_ko":b"`l10.0", -"fi":b"`l9", -"fi_FI":b"`l9.0" + "en":b"`l1", + "en_US":b"`l1.0", + "en_UK":b"`l1.1", + "en_GB":b"`l1.1", + "es":b"`l2", + "es_ES":b"`l2.0", + "es_ME":b"`l2.1", + "fr":b"`l3", + "fr_FR":b"`l3.0", + "fr_CA":b"`l3.1", + "de":b"`l4", + "de_DE":b"`l4", + "it":b"`l5", + "it_IT":b"`l5", + "zh":b"`l6", + "zh_gb":b"`l6.0", + "pt":b"`l7", + "pt_BR":b"`l7.0", + "pt_PT":b"`l7.1", + "ja":b"`l8", + "ja_ja":b"`l8.0", + "ko":b"`l10", + "ko_ko":b"`l10.0", + "fi":b"`l9", + "fi_FI":b"`l9.0" } class SynthDriver(synthDriverHandler.SynthDriver): @@ -132,19 +132,20 @@ def __init__(self): self.variant="1" PROSODY_ATTRS = { - speech.PitchCommand: "`vs", - speech.VolumeCommand: "`vv", - speech.RateCommand: "vb", + speech.PitchCommand: b'vb', + speech.VolumeCommand: b'vv', + speech.RateCommand: b'vs', } def speak(self,speechSequence): last = None defaultLanguage=self.language outlist = [] - outlist.append((_ibmeci.speak, (b"`ts0",))) + outlist.append((_ibmeci.speak, (b'`ts0 `pp0 `vb%d `vs%d `vv%d ' %(self.pitch, _ibmeci.getVParam(ECIVoiceParam.eciSpeed), self.volume),))) for item in speechSequence: if isinstance(item, string_types): s = self.processText(unicode(item)) + if not s: continue outlist.append((_ibmeci.speak, (s,))) last = s elif isinstance(item,speech.IndexCommand): @@ -164,11 +165,13 @@ def speak(self,speechSequence): outlist.append((_ibmeci.speak, (b"`ts1" if item.state else b"`ts0",))) elif isinstance(item,speech.BreakCommand): outlist.append((_ibmeci.speak, (b' `p%d ' %item.time,))) - elif isinstance(item,speech.SpeechCommand): - log.debugWarning("Unsupported speech command: %s"%item) + elif type(item) in self.PROSODY_ATTRS: + val = max(0, min(item.newValue, 100)) + if type(item) == speech.RateCommand: val = self.percentToRate(val) + outlist.append((_ibmeci.speak, (b' `%s%d ' %(self.PROSODY_ATTRS[type(item)], val),))) else: - log.error("Unknown speech: %s"%item) - if last is not None and not str(last[-1]) in punctuation: outlist.append((_ibmeci.speak, (b'`p1. ',))) + log.error("Unknown speech: %s" %item) + if last and str(last[-1]) not in punctuation: outlist.append((_ibmeci.speak, (b'`p1. ',))) outlist.append((_ibmeci.setEndStringMark, ())) outlist.append((_ibmeci.synth, ())) _ibmeci.eciQueue.put(outlist) @@ -181,9 +184,8 @@ def processText(self,text): if _ibmeci.params[9] in (196609, 196608): text = resub(french_fixes, text) text = text.replace('quil', 'qil') #Sometimes this string make everything buggy with IBMTTS in French - #if not self._backquoteVoiceTags: text = text.replace(u'‵', ' ') if self._backquoteVoiceTags: - text = "`pp0 `vv%d %s" % (self.getVParam(ECIVoiceParam.eciVolume), text.replace('`', ' ')) #no embedded commands + text = text.replace('`', ' ') #no embedded commands text = resub(anticrash_res, text) #this converts to ansi for anticrash. If this breaks with foreign langs, we can remove it. text = text.encode('mbcs', 'replace') @@ -191,7 +193,7 @@ def processText(self,text): #this converts to ansi for anticrash. If this breaks with foreign langs, we can remove it. text = text.encode('mbcs', 'replace') text = resub(anticrash_res, text) - text = b"`pp0 `vv%d %s" % (self.getVParam(ECIVoiceParam.eciVolume), text.replace(b'`', b' ')) #no embedded commands + text = text.replace(b'`', b' ') #no embedded commands text = pause_re.sub(br'\1 `p1\2\3', text) text = time_re.sub(br'\1:\2 \3', text) return text @@ -224,55 +226,59 @@ def _set_rateBoost(self, enable): def _get_rate(self): - val = self.getVParam(ECIVoiceParam.eciSpeed) + val = _ibmeci.getVParam(ECIVoiceParam.eciSpeed) if self._rateBoost: val=int(round(val/self.RATE_BOOST_MULTIPLIER)) return self._paramToPercent(val, minRate, maxRate) - def _set_rate(self,vl): - val = self._percentToParam(vl, minRate, maxRate) + def percentToRate(self, val): + val = self._percentToParam(val, minRate, maxRate) if self._rateBoost: val = int(round(val *self.RATE_BOOST_MULTIPLIER)) + return val + + def _set_rate(self,val): + val = self.percentToRate(val) self._rate = val - self.setVParam(ECIVoiceParam.eciSpeed, val) + _ibmeci.setVParam(ECIVoiceParam.eciSpeed, val) def _get_pitch(self): - return self.getVParam(ECIVoiceParam.eciPitchBaseline) + return _ibmeci.getVParam(ECIVoiceParam.eciPitchBaseline) def _set_pitch(self,vl): - self.setVParam(ECIVoiceParam.eciPitchBaseline,vl) + _ibmeci.setVParam(ECIVoiceParam.eciPitchBaseline,vl) def _get_volume(self): - return self.getVParam(ECIVoiceParam.eciVolume) + return _ibmeci.getVParam(ECIVoiceParam.eciVolume) def _set_volume(self,vl): - self.setVParam(ECIVoiceParam.eciVolume,int(vl)) + _ibmeci.setVParam(ECIVoiceParam.eciVolume,int(vl)) def _set_inflection(self,vl): vl = int(vl) - self.setVParam(ECIVoiceParam.eciPitchFluctuation,vl) + _ibmeci.setVParam(ECIVoiceParam.eciPitchFluctuation,vl) def _get_inflection(self): - return self.getVParam(ECIVoiceParam.eciPitchFluctuation) + return _ibmeci.getVParam(ECIVoiceParam.eciPitchFluctuation) def _set_hsz(self,vl): vl = int(vl) - self.setVParam(ECIVoiceParam.eciHeadSize,vl) + _ibmeci.setVParam(ECIVoiceParam.eciHeadSize,vl) def _get_hsz(self): - return self.getVParam(ECIVoiceParam.eciHeadSize) + return _ibmeci.getVParam(ECIVoiceParam.eciHeadSize) def _set_rgh(self,vl): vl = int(vl) - self.setVParam(ECIVoiceParam.eciRoughness,vl) + _ibmeci.setVParam(ECIVoiceParam.eciRoughness,vl) def _get_rgh(self): - return self.getVParam(ECIVoiceParam.eciRoughness) + return _ibmeci.getVParam(ECIVoiceParam.eciRoughness) def _set_bth(self,vl): vl = int(vl) - self.setVParam(ECIVoiceParam.eciBreathiness,vl) + _ibmeci.setVParam(ECIVoiceParam.eciBreathiness,vl) def _get_bth(self): - return self.getVParam(ECIVoiceParam.eciBreathiness) + return _ibmeci.getVParam(ECIVoiceParam.eciBreathiness) def _getAvailableVoices(self): o = OrderedDict() @@ -286,14 +292,9 @@ def _get_voice(self): return str(_ibmeci.params[_ibmeci.ECIParam.eciLanguageDialect]) def _set_voice(self,vl): _ibmeci.set_voice(vl) - def getVParam(self,pr): - return _ibmeci.getVParam(pr) - - def setVParam(self, pr,vl): - _ibmeci.setVParam(pr, vl) - + def _get_lastIndex(self): -#fix? + #fix? return _ibmeci.lastindex def cancel(self): @@ -307,9 +308,9 @@ def _set_variant(self, v): global variants self._variant = v if int(v) in variants else "1" _ibmeci.setVariant(int(v)) - self.setVParam(ECIVoiceParam.eciSpeed, self._rate) -# if 'ibmtts' in config.conf['speech']: -# config.conf['speech']['ibmtts']['pitch'] = self.pitch + _ibmeci.setVParam(ECIVoiceParam.eciSpeed, self._rate) + #if 'ibmtts' in config.conf['speech']: + #config.conf['speech']['ibmtts']['pitch'] = self.pitch def _get_variant(self): return self._variant diff --git a/buildVars.py b/buildVars.py index 04fd52d..eaf2521 100644 --- a/buildVars.py +++ b/buildVars.py @@ -19,7 +19,7 @@ # Translators: Long description to be shown for this add-on on add-on information from add-ons manager "addon_description" : _("""This is the IBMTTS synthesizer driver for NVDA."""), # version - "addon_version" : "19.8B2", + "addon_version" : "19.8B3", # Author(s) "addon_author" : u"David CM and others", # URL for the add-on documentation support