diff --git a/source/speech/commands.py b/source/speech/commands.py index 48d561f1a4e..bb7ab2fb179 100644 --- a/source/speech/commands.py +++ b/source/speech/commands.py @@ -177,6 +177,7 @@ class EndUtteranceCommand(SpeechCommand): def __repr__(self): return "EndUtteranceCommand()" + class BaseProsodyCommand(SynthParamCommand): """Base class for commands which change voice prosody; i.e. pitch, rate, etc. The change to the setting is specified using either an offset or a multiplier, but not both. @@ -184,18 +185,20 @@ class BaseProsodyCommand(SynthParamCommand): To return to the default value, specify neither. This base class should not be instantiated directly. """ - #: The name of the setting in the configuration; e.g. pitch, rate, etc. - settingName = None - def __init__(self, offset=0, multiplier=1): + settingName: Optional[str] = None + """ + The name of the setting in the configuration; e.g. pitch, rate, etc. + Should have an integer value from 0-100 in the config. + """ + + def __init__(self, offset: int = 0, multiplier: float = 1): """Constructor. Either of C{offset} or C{multiplier} may be specified, but not both. @param offset: The amount by which to increase/decrease the user configured setting; e.g. 30 increases by 30, -10 decreases by 10, 0 returns to the configured setting. - @type offset: int @param multiplier: The number by which to multiply the user configured setting; e.g. 0.5 is half, 1 returns to the configured setting. - @param multiplier: int/float """ if offset != 0 and multiplier != 1: raise ValueError("offset and multiplier both specified") @@ -204,15 +207,16 @@ def __init__(self, offset=0, multiplier=1): self.isDefault = offset == 0 and multiplier == 1 @property - def defaultValue(self): + def defaultValue(self) -> int: """The default value for the setting as configured by the user. + @returns: a settings value from 0-100 such as pitch, volume or rate. """ synth = getSynth() synthConf = config.conf["speech"][synth.name] return synthConf[self.settingName] @property - def multiplier(self): + def multiplier(self) -> float: """The number by which to multiply the default value. """ if self._multiplier != 1: @@ -221,13 +225,16 @@ def multiplier(self): if self._offset == 0: # Returning to default. return 1 - # Calculate multiplier from default value and offset. defaultVal = self.defaultValue + if self.defaultValue == 0: + # If value is 0, division by 0 error occurs + defaultVal = 1 + # Calculate multiplier from default value and offset. newVal = defaultVal + self._offset return float(newVal) / defaultVal @property - def offset(self): + def offset(self) -> int: """The amount by which to increase/decrease the default value. """ if self._offset != 0: @@ -242,7 +249,7 @@ def offset(self): return int(newVal - defaultVal) @property - def newValue(self): + def newValue(self) -> int: """The new absolute value after the offset or multiplier is applied to the default value. """ if self._offset != 0: @@ -254,7 +261,7 @@ def newValue(self): # Returning to default. return self.defaultValue - def __repr__(self): + def __repr__(self) -> str: if self._offset != 0: param = "offset=%d" % self._offset elif self._multiplier != 1: diff --git a/source/synthDrivers/espeak.py b/source/synthDrivers/espeak.py index eb73a6bcba9..8e01445ce01 100644 --- a/source/synthDrivers/espeak.py +++ b/source/synthDrivers/espeak.py @@ -24,6 +24,7 @@ from speech.types import SpeechSequence from speech.commands import ( + BaseProsodyCommand, IndexCommand, CharacterModeCommand, LangChangeCommand, @@ -312,10 +313,40 @@ def _handleLangChangeCommand( voiceChangeXML += "" return voiceChangeXML - # C901 'speak' is too complex - # Note: when working on speak, look for opportunities to simplify - # and move logic out into smaller helper functions. - def speak(self, speechSequence: SpeechSequence): # noqa: C901 + def _handleProsodyCommands( + self, + prosody: Dict[str, int], + item: BaseProsodyCommand + ) -> str: + textList = [] + if prosody: + # Close previous prosody tag. + textList.append("") + + attr = self.PROSODY_ATTRS[type(item)] + + if item.multiplier == 1 and item.offset == 0: + # Default values, returning to normal. + try: + del prosody[attr] + except KeyError: + pass + elif item.offset != 0: + prosody[attr] = int(item.offset) + else: + prosody[attr] = int(item.multiplier * 100) + + if not prosody: + return "" + + textList.append("") + + return "".join(textList) + + def speak(self, speechSequence: SpeechSequence): textList: List[str] = [] langChanged = False prosody: Dict[str, int] = {} @@ -336,24 +367,7 @@ def speak(self, speechSequence: SpeechSequence): # noqa: C901 elif isinstance(item, BreakCommand): textList.append(f'') elif type(item) in self.PROSODY_ATTRS: - if prosody: - # Close previous prosody tag. - textList.append("") - attr=self.PROSODY_ATTRS[type(item)] - if item.multiplier==1: - # Returning to normal. - try: - del prosody[attr] - except KeyError: - pass - else: - prosody[attr]=int(item.multiplier* 100) - if not prosody: - continue - textList.append("") + textList.append(self._handleProsodyCommands(prosody, item)) elif isinstance(item, PhonemeCommand): # We can't use str.translate because we want to reject unknown characters. try: