Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ZeroDivisionError when pitch is 0 announcing 'cap' #13921

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 18 additions & 11 deletions source/speech/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,25 +177,28 @@ 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.
The L{offset} and L{multiplier} properties convert between the two if necessary.
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")
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand Down
58 changes: 36 additions & 22 deletions source/synthDrivers/espeak.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

from speech.types import SpeechSequence
from speech.commands import (
BaseProsodyCommand,
IndexCommand,
CharacterModeCommand,
LangChangeCommand,
Expand Down Expand Up @@ -312,10 +313,40 @@ def _handleLangChangeCommand(
voiceChangeXML += "<voice>"
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("</prosody>")

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("<prosody")
for attr, val in prosody.items():
textList.append(f' {attr}="{val}%"')
textList.append(">")

return "".join(textList)

def speak(self, speechSequence: SpeechSequence):
textList: List[str] = []
langChanged = False
prosody: Dict[str, int] = {}
Expand All @@ -336,24 +367,7 @@ def speak(self, speechSequence: SpeechSequence): # noqa: C901
elif isinstance(item, BreakCommand):
textList.append(f'<break time="{item.time}ms" />')
elif type(item) in self.PROSODY_ATTRS:
if prosody:
# Close previous prosody tag.
textList.append("</prosody>")
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("<prosody")
for attr,val in prosody.items():
textList.append(' %s="%d%%"'%(attr,val))
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:
Expand Down