コード例 #1
0
    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 [
            BooleanDriverSetting(
                "shouldDoX",  # value stored in matching property name on class
                "Should Do X",
                defaultVal=True),
            BooleanDriverSetting(
                "shouldDoY",  # value stored in matching property name on class
                "Should Do Y",
                defaultVal=False),
            NumericDriverSetting(
                "amountOfZ",  # value stored in matching property name on class
                "Amount of Z",
                defaultVal=11),
            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",
            )
        ]
コード例 #2
0
 def _getAvailableRuntimeSettings(self) -> SupportedSettingType:
     settings = []
     if self._hasFeature("runtimeOnlySetting_externalValueLoad"):
         settings.extend([
             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([
             NumericDriverSetting(
                 "runtimeOnlySetting_localDefault",  # value stored in matching property name on class
                 "Runtime Only amount, local default",
                 defaultVal=50,
             ),
         ])
     return settings
コード例 #3
0
 def InflectionSetting(cls, minStep=1):
     """Factory function for creating inflection setting."""
     return NumericDriverSetting(
         "inflection",
         # Translators: Label for a setting in voice settings dialog.
         _("&Inflection"),
         minStep=minStep,
         availableInSettingsRing=True,
         # Translators: Label for a setting in synth settings ring.
         displayName=pgettext('synth setting', 'Inflection'),
     )
コード例 #4
0
 def VolumeSetting(cls, minStep=1):
     """Factory function for creating volume setting."""
     return NumericDriverSetting(
         "volume",
         # Translators: Label for a setting in voice settings dialog.
         _("V&olume"),
         minStep=minStep,
         normalStep=5,
         availableInSettingsRing=True,
         # Translators: Label for a setting in synth settings ring.
         displayName=pgettext('synth setting', 'Volume'),
     )
コード例 #5
0
 def __init__(self):
     if self.exposeExtraParams:
         self._extraParamNames = [x[0] for x in speechPlayer.Frame._fields_]
         self.supportedSettings = SynthDriver.supportedSettings + tuple(
             NumericDriverSetting(
                 "speechPlayer_%s" % x, "frame.%s" % x, normalStep=1)
             for x in self._extraParamNames)
         for x in self._extraParamNames:
             setattr(self, "speechPlayer_%s" % x, 50)
     self.player = speechPlayer.SpeechPlayer(16000)
     _espeak.initialize()
     _espeak.setVoiceByLanguage('en')
     self.pitch = 50
     self.rate = 50
     self.volume = 90
     self.inflection = 60
     self.audioThread = AudioThread(self, self.player, 16000)
コード例 #6
0
class SynthDriver(synthDriverHandler.SynthDriver):
	supportedSettings=(SynthDriver.VoiceSetting(), SynthDriver.VariantSetting(), SynthDriver.RateSetting(),
		BooleanDriverSetting("rateBoost", _("Rate boos&t"), True),
		SynthDriver.PitchSetting(), SynthDriver.InflectionSetting(), SynthDriver.VolumeSetting(),
		NumericDriverSetting("hsz", _("Head size"), False),
		NumericDriverSetting("rgh", _("Roughness"), False),
		NumericDriverSetting("bth", _("Breathiness"), False),
		BooleanDriverSetting("backquoteVoiceTags", _("Enable backquote voice &tags"), False),
		BooleanDriverSetting("ABRDICT", _("Enable &abbreviation dictionary"), False),
		BooleanDriverSetting("phrasePrediction", _("Enable phrase prediction"), False),
		BooleanDriverSetting("shortpause", _("&Shorten pauses"), False),
		BooleanDriverSetting("sendParams", _("Always Send Current Speech Settings (enable to prevent some tags from sticking, disable for viavoice binary compatibility)"), False))
	supportedCommands = {
		IndexCommand,
		CharacterModeCommand,
		LangChangeCommand,
		BreakCommand,
		PitchCommand,
		RateCommand,
		VolumeCommand
	}
	supportedNotifications = {synthIndexReached, synthDoneSpeaking}

	description='IBMTTS'
	name='ibmeci'
	speakingLanguage=""
	
	@classmethod
	def check(cls):
		return _ibmeci.eciCheck()

	def __init__(self):
		_ibmeci.initialize(self._onIndexReached, self._onDoneSpeaking)
		# This information doesn't really need to be displayed, and makes IBMTTS unusable if the addon is not in the same drive as NVDA executable.
		# But display it only on debug mode in case of it can be useful
		log.debug("Using IBMTTS version %s" % _ibmeci.eciVersion())
		lang = languageHandler.getLanguage()
		self.rate=50
		self.speakingLanguage=lang
		self.variant="1"
		self.currentEncoding = "mbcs"

	PROSODY_ATTRS = {
		PitchCommand: ECIVoiceParam.eciPitchBaseline,
		VolumeCommand: ECIVoiceParam.eciVolume,
		RateCommand: ECIVoiceParam.eciSpeed,
	}

	def speak(self,speechSequence):
		last = None
		defaultLanguage=self.language
		outlist = []
		charmode=False
		for item in speechSequence:
			if isinstance(item, string_types):
				s = self.processText(item)
				outlist.append((_ibmeci.speak, (s,)))
				last = s
			elif isinstance(item,IndexCommand):
				outlist.append((_ibmeci.index, (item.index,)))
			elif isinstance(item,LangChangeCommand):
				l=None
				if item.lang in langsAnnotations: l = langsAnnotations[item.lang]
				elif item.lang and item.lang[0:2] in langsAnnotations: l = langsAnnotations[item.lang[0:2]]
				if l:
					if item.lang != self.speakingLanguage and item.lang != self.speakingLanguage[0:2]:
						outlist.append((_ibmeci.speak, (l,)))
						self.speakingLanguage=item.lang
						self.updateEncoding(l)
				else:
					outlist.append((_ibmeci.speak, (langsAnnotations[defaultLanguage],)))
					self.speakingLanguage = defaultLanguage
			elif isinstance(item,CharacterModeCommand):
				outlist.append((_ibmeci.speak, (b"`ts1" if item.state else b"`ts0",)))
				if item.state:
					charmode=True
			elif isinstance(item,BreakCommand):
				# taken from eloquence_threshold (https://github.com/pumper42nickel/eloquence_threshold)
				# Eloquence doesn't respect delay time in milliseconds.
				# Therefore we need to adjust waiting time depending on current 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((_ibmeci.speak, (b' `p%d '%(pFactor),)))
			elif type(item) in self.PROSODY_ATTRS:
				val = max(0, min(item.newValue, 100))
				if type(item) == RateCommand: val = self.percentToRate(val)
				outlist.append((_ibmeci.setProsodyParam, (self.PROSODY_ATTRS[type(item)], val)))
			else:
				log.error("Unknown speech: %s"%item)
		if last is not None and last[-1] not in punctuation:
			# check if a pitch command is at the end of the list, because p1 need to be send before this.
			# index -2 is because -1 always seem to be an index command.
			if outlist[-2][0] == _ibmeci.setProsodyParam: outlist.insert(-2, (_ibmeci.speak, (b'`p1 ',)))
			else: outlist.append((_ibmeci.speak, (b'`p1 ',)))
		if charmode:
			outlist.append((_ibmeci.speak, (b"`ts0",)))
		outlist.append((_ibmeci.setEndStringMark, ()))
		outlist.append((_ibmeci.synth, ()))
		_ibmeci.eciQueue.put(outlist)
		_ibmeci.process()

	def processText(self,text):
		#this converts to ansi for anticrash. If this breaks with foreign langs, we can remove it.
		text = text.encode(self.currentEncoding, 'replace') # special unicode symbols may encode to backquote. For this reason, backquote processing is after this.
		text = text.rstrip()
		if _ibmeci.params[9] in (65536, 65537, 393216, 655360, 720897): text = resub(english_fixes, text) #Applies to all languages with dual language support.
		if _ibmeci.params[9] in (65536, 65537, 393216, 655360, 720897) and _ibmeci.isIBM: text = resub(english_ibm_fixes, text)
		if _ibmeci.params[9] in (131072,  131073) and not _ibmeci.isIBM: text = resub(spanish_fixes, text)
		if _ibmeci.params[9] in ('esp', 131072) and _ibmeci.isIBM: text = resub(spanish_ibm_fixes, text)
		if _ibmeci.params[9] in (196609, 196608):
			text = text.replace(br'quil', br'qil') #Sometimes this string make everything buggy with IBMTTS in French
		if  _ibmeci.params[9] in ('deu', 262144):
			text = resub(german_fixes, text)
		if  _ibmeci.params[9] in ('ptb', 458752) and _ibmeci.isIBM:
			text = resub(portuguese_ibm_fixes, text)
		if not self._backquoteVoiceTags:
			text=text.replace(b'`', b' ') # no embedded commands
		if self._shortpause:
			text = pause_re.sub(br'\1 `p1\2\3\4', text) # this enforces short, JAWS-like pauses.
		if not _ibmeci.isIBM:
			text = time_re.sub(br'\1:\2 \3', text) # apparently if this isn't done strings like 2:30:15 will only announce 2:30
		embeds=b''
		if self._ABRDICT:
			embeds+=b"`da1 "
		else:
			embeds+=b"`da0 "
		if self._phrasePrediction:
			embeds+=b"`pp1 "
		else:
			embeds+=b"`pp0 "
		if self._sendParams:
			embeds+=b"`vv%d `vs%d " % (_ibmeci.getVParam(ECIVoiceParam.eciVolume), _ibmeci.getVParam(ECIVoiceParam.eciSpeed))
		text = b"%s %s" % (embeds.rstrip(), text) # bring all the printf stuff into one call, in one string. This avoids all the concatonation and printf additions of the previous organization.
		return text
	def pause(self,switch):
		_ibmeci.pause(switch)

	def terminate(self):
		_ibmeci.terminate()

	_backquoteVoiceTags=False
	_ABRDICT=False
	_phrasePrediction=False
	_shortpause=False
	_sendParams=True
	def _get_backquoteVoiceTags(self):
		return self._backquoteVoiceTags

	def _set_backquoteVoiceTags(self, enable):
		if enable == self._backquoteVoiceTags:
			return
		self._backquoteVoiceTags = enable
	def _get_ABRDICT(self):
		return self._ABRDICT
	def _set_ABRDICT(self, enable):
		if enable == self._ABRDICT:
			return
		self._ABRDICT = enable
	def _get_phrasePrediction(self):
		return self._phrasePrediction
	def _set_phrasePrediction(self, enable):
		if enable == self._phrasePrediction:
			return
		self._phrasePrediction = enable
	def _get_shortpause(self):
		return self._shortpause
	def _set_shortpause(self, enable):
		if enable == self._shortpause:
			return
		self._shortpause = enable
	def _get_sendParams(self):
		return self._sendParams
	def _set_sendParams(self, enable):
		if enable == self._sendParams:
			return
		self._sendParams = enable
	_rateBoost = False
	RATE_BOOST_MULTIPLIER = 1.6
	def _get_rateBoost(self):
		return self._rateBoost

	def _set_rateBoost(self, enable):
		if enable != self._rateBoost:
			rate = self.rate
			self._rateBoost = enable
			self.rate = rate

	def _get_rate(self):
		val = _ibmeci.getVParam(ECIVoiceParam.eciSpeed)
		if self._rateBoost: val=int(round(val/self.RATE_BOOST_MULTIPLIER))
		return self._paramToPercent(val, 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
		_ibmeci.setVParam(ECIVoiceParam.eciSpeed, val)

	def _get_pitch(self):
		return _ibmeci.getVParam(ECIVoiceParam.eciPitchBaseline)

	def _set_pitch(self,vl):
		_ibmeci.setVParam(ECIVoiceParam.eciPitchBaseline,vl)

	def _get_volume(self):
		return _ibmeci.getVParam(ECIVoiceParam.eciVolume)

	def _set_volume(self,vl):
		_ibmeci.setVParam(ECIVoiceParam.eciVolume,int(vl))

	def _set_inflection(self,vl):
		vl = int(vl)
		_ibmeci.setVParam(ECIVoiceParam.eciPitchFluctuation,vl)

	def _get_inflection(self):
		return _ibmeci.getVParam(ECIVoiceParam.eciPitchFluctuation)

	def _set_hsz(self,vl):
		vl = int(vl)
		_ibmeci.setVParam(ECIVoiceParam.eciHeadSize,vl)

	def _get_hsz(self):
		return _ibmeci.getVParam(ECIVoiceParam.eciHeadSize)

	def _set_rgh(self,vl):
		vl = int(vl)
		_ibmeci.setVParam(ECIVoiceParam.eciRoughness,vl)

	def _get_rgh(self):
		return _ibmeci.getVParam(ECIVoiceParam.eciRoughness)

	def _set_bth(self,vl):
		vl = int(vl)
		_ibmeci.setVParam(ECIVoiceParam.eciBreathiness,vl)

	def _get_bth(self):
		return _ibmeci.getVParam(ECIVoiceParam.eciBreathiness)

	def _getAvailableVoices(self):
		o = OrderedDict()
		for name in os.listdir(_ibmeci.ttsPath):
			if name.lower().endswith('.syn'):
				info = _ibmeci.langs[name.lower()[:3]]
				o[str(info[0])] = VoiceInfo(str(info[0]), info[1], info[2])
		return o

	def _get_voice(self):
		return str(_ibmeci.params[_ibmeci.ECIParam.eciLanguageDialect])
	def _set_voice(self,vl):
		_ibmeci.setVoice(int(vl))
		self.updateEncoding(int(vl))

	def updateEncoding(self, lang): # lang must be a number asociated with IBMTTS languages or a string with an annotation language.
		# currently we don't need to consider the decimal part for the conversion.
		if isinstance(lang, bytes): lang = int(float(lang[2:])) * 65536
		#chinese
		if lang == 393216: self.currentEncoding = "gb2312"
		# japan
		elif lang == 524288: self.currentEncoding = "cp932"
		# korean
		elif lang == 655360: self.currentEncoding = "cp949"
		elif lang == 720897: self.currentEncoding = "big5"
		else: self.currentEncoding = "mbcs"

	def _get_lastIndex(self):
		#fix?
		return _ibmeci.lastindex

	def cancel(self):
		_ibmeci.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"
		_ibmeci.setVariant(int(v))
		_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

	def _onIndexReached(self, index): synthIndexReached.notify(synth=self, index=index)

	def _onDoneSpeaking(self): synthDoneSpeaking.notify(synth=self)
コード例 #7
0
class SynthDriver(synthDriverHandler.SynthDriver):
    supportedSettings = (SynthDriver.VoiceSetting(),
                         SynthDriver.VariantSetting(),
                         SynthDriver.RateSetting(), SynthDriver.PitchSetting(),
                         SynthDriver.InflectionSetting(),
                         SynthDriver.VolumeSetting(),
                         NumericDriverSetting("hsz", "Head Size"),
                         NumericDriverSetting("rgh", "Roughness"),
                         NumericDriverSetting("bth", "Breathiness"),
                         BooleanDriverSetting("backquoteVoiceTags",
                                              "Enable backquote voice &tags",
                                              True))
    supportedCommands = {
        IndexCommand,
        CharacterModeCommand,
        LangChangeCommand,
        BreakCommand,
        PitchCommand,
        RateCommand,
        VolumeCommand,
        PhonemeCommand,
    }
    supportedNotifications = {synthIndexReached, synthDoneSpeaking}
    PROSODY_ATTRS = {
        PitchCommand: _eloquence.pitch,
        VolumeCommand: _eloquence.vlm,
        RateCommand: _eloquence.rate,
    }

    description = 'Saksham-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, IndexCommand):
                outlist.append((_eloquence.index, (item.index, )))
            elif isinstance(item, BreakCommand):
                pFactor = 3 * item.time
                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)
コード例 #8
0
class SynthDriver(WorldVoiceBaseSynthDriver, SynthDriver):
    name = "WorldVoiceXVED2"
    description = "WorldVoice(VE)"
    supportedSettings = [
        SynthDriver.VoiceSetting(),
        # SynthDriver.VariantSetting(),
        SynthDriver.RateSetting(),
        SynthDriver.PitchSetting(),
        SynthDriver.VolumeSetting(),
        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"),
        ),
        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"),
        ),
        NumericDriverSetting(
            "chinesespace",
            # Translators: Label for a setting in voice settings dialog.
            _("Pause time when encountering spaces between Chinese"),
            defaultVal=0,
            minStep=1,
        ),
        BooleanDriverSetting(
            "cni",
            _("Ignore comma between number"),
            defaultVal=False,
        ),
        BooleanDriverSetting(
            "uwv",
            _("Enable WorldVoice setting rules to detect text language"),
            availableInSettingsRing=True,
            defaultVal=True,
            displayName=_("Enable WorldVoice rules"),
        ),
    ]
    supportedCommands = {
        IndexCommand,
        CharacterModeCommand,
        LangChangeCommand,
        BreakCommand,
        PitchCommand,
        RateCommand,
        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.speech.speak
        self._realSpellingFunc = speech.speech.speakSpelling
        speech.speech.speak = self.patchedSpeak
        speech.speech.speakSpelling = self.patchedSpeakSpelling

        self.speechSymbols = SpeechSymbols()
        self.speechSymbols.load('unicode.dic')
        self._languageDetector = languageDetection.LanguageDetector(
            list(self._voiceManager.languages), self.speechSymbols)

        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.speech.speak = self._realSpeakFunc
        speech.speech.speakSpelling = self._realSpellingFunc

        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.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, IndexCommand):
                chunks.append("\x1b\\mrk=%d\\" % command.index)
            elif isinstance(command, 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, CharacterModeCommand):
                charMode = command.state
                s = "\x1b\\tn=spell\\" if command.state else "\x1b\\tn=normal\\"
                chunks.append(s)
            elif isinstance(command, 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, 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:
            speechSequence = [
                comma_number_pattern.sub(lambda m: '', command) if isinstance(
                    command, str) else command for command in speechSequence
            ]
        speechSequence = self.patchedNumSpeechSequence(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": StringParameterInfo("default", _("default")),
        }, **{
            locale: 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": StringParameterInfo("value", _("value")),
            "number": 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 _getLocaleReadableName(self, locale):
        description = languageHandler.getLanguageDescription(locale)
        return "%s" % (description) if description else locale