Skip to content

Commit 18e0ee0

Browse files
authored
Merge f8256a4 into 5bab7db
2 parents 5bab7db + f8256a4 commit 18e0ee0

File tree

4 files changed

+123
-1
lines changed

4 files changed

+123
-1
lines changed

source/config/configSpec.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@
5151
sayCapForCapitals = boolean(default=false)
5252
beepForCapitals = boolean(default=false)
5353
useSpellingFunctionality = boolean(default=true)
54+
delayedPhoneticDescriptions = boolean(default=false)
55+
delayedPhoneticDescriptionsTimeoutMs = integer(default=1000,min=50,max=5000)
56+
5457
5558
# Audio settings
5659
[audio]

source/gui/settingsDialogs.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1618,6 +1618,50 @@ def makeSettings(self, settingsSizer):
16181618
config.conf["speech"][self.driver.name]["useSpellingFunctionality"]
16191619
)
16201620

1621+
# Translators: This is the label for a checkbox in the
1622+
# voice settings panel.
1623+
delayedPhoneticDescriptionsText = _("&Delayed phonetic descriptions for characters on cursor movement")
1624+
self.delayedPhoneticDescriptionsCheckBox = settingsSizerHelper.addItem(
1625+
wx.CheckBox(self, label=delayedPhoneticDescriptionsText)
1626+
)
1627+
self.bindHelpEvent("delayedPhoneticDescriptions", self.delayedPhoneticDescriptionsCheckBox)
1628+
self.delayedPhoneticDescriptionsCheckBox.SetValue(
1629+
config.conf["speech"][self.driver.name]["delayedPhoneticDescriptions"]
1630+
)
1631+
self.delayedPhoneticDescriptionsCheckBox .Bind(
1632+
wx.EVT_CHECKBOX,
1633+
self.onToggleDelayDescriptions
1634+
)
1635+
1636+
minDelay = int(config.conf.getConfigValidation(
1637+
("speech", self.driver.name, "delayedPhoneticDescriptionsTimeoutMs")
1638+
).kwargs["min"])
1639+
maxDelay = int(config.conf.getConfigValidation(
1640+
("speech", self.driver.name, "delayedPhoneticDescriptionsTimeoutMs")
1641+
).kwargs["max"])
1642+
1643+
# Translators: This is a label for a setting in voice settings (an edit box to change
1644+
# Time for the delayed phonetic descriptions in ms;
1645+
delayedPhoneticDescriptionsTimeoutMsLabel = _("&Time for delayed phonetic descriptions (ms)")
1646+
self.delayedPhoneticDescriptionsTimeoutMsEdit = settingsSizerHelper.addLabeledControl(
1647+
delayedPhoneticDescriptionsTimeoutMsLabel,
1648+
nvdaControls.SelectOnFocusSpinCtrl,
1649+
min=minDelay,
1650+
max=maxDelay,
1651+
initial=config.conf["speech"][self.driver.name]["delayedPhoneticDescriptionsTimeoutMs"])
1652+
self.bindHelpEvent(
1653+
"delayedPhoneticDescriptionsTimeoutMs",
1654+
self.delayedPhoneticDescriptionsTimeoutMsEdit
1655+
)
1656+
if not config.conf["speech"][self.driver.name]["delayedPhoneticDescriptions"]:
1657+
self.delayedPhoneticDescriptionsTimeoutMsEdit.Hide()
1658+
1659+
def onToggleDelayDescriptions(self, evt):
1660+
if evt.IsChecked():
1661+
self.delayedPhoneticDescriptionsTimeoutMsEdit.Show()
1662+
else:
1663+
self.delayedPhoneticDescriptionsTimeoutMsEdit.Hide()
1664+
16211665
def onSave(self):
16221666
AutoSettingsMixin.onSave(self)
16231667

@@ -1636,6 +1680,10 @@ def onSave(self):
16361680
config.conf["speech"][self.driver.name]["sayCapForCapitals"]=self.sayCapForCapsCheckBox.IsChecked()
16371681
config.conf["speech"][self.driver.name]["beepForCapitals"]=self.beepForCapsCheckBox.IsChecked()
16381682
config.conf["speech"][self.driver.name]["useSpellingFunctionality"]=self.useSpellingFunctionalityCheckBox.IsChecked()
1683+
delayedDescriptions = self.delayedPhoneticDescriptionsCheckBox.IsChecked()
1684+
config.conf["speech"][self.driver.name]["delayedPhoneticDescriptions"] = delayedDescriptions
1685+
delayedDescriptionsMs = self.delayedPhoneticDescriptionsTimeoutMsEdit.Value
1686+
config.conf["speech"][self.driver.name]["delayedPhoneticDescriptionsTimeoutMs"] = delayedDescriptionsMs
16391687

16401688
class KeyboardSettingsPanel(SettingsPanel):
16411689
# Translators: This is the label for the keyboard settings panel.

source/speech/speech.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,13 @@
5959
from enum import IntEnum
6060
from dataclasses import dataclass
6161
from copy import copy
62-
62+
from core import callLater
6363
if typing.TYPE_CHECKING:
6464
import NVDAObjects
6565

6666
_speechState: Optional['SpeechState'] = None
6767
_curWordChars: List[str] = []
68+
phoneticDescriptionTimer = None
6869

6970

7071
class SpeechMode(IntEnum):
@@ -146,6 +147,7 @@ def cancelSpeech():
146147
return
147148
elif _speechState.speechMode == SpeechMode.beeps:
148149
return
150+
cancelPhoneticDescriptionTimer()
149151
_manager.cancel()
150152
_speechState.beenCanceled = True
151153
_speechState.isPaused = False
@@ -810,6 +812,8 @@ def speak( # noqa: C901
810812

811813
if not speechSequence: # Pointless - nothing to speak
812814
return
815+
# cancel any delaied phonetic description if exists.
816+
cancelPhoneticDescriptionTimer()
813817
import speechViewer
814818
if speechViewer.isActive:
815819
speechViewer.appendSpeechSequence(speechSequence)
@@ -1136,6 +1140,7 @@ def speakTextInfo(
11361140
suppressBlanks: bool = False,
11371141
priority: Optional[Spri] = None
11381142
) -> bool:
1143+
global phoneticDescriptionTimer
11391144
speechGen = getTextInfoSpeech(
11401145
info,
11411146
useCache,
@@ -1150,6 +1155,16 @@ def speakTextInfo(
11501155
speechGen = GeneratorWithReturn(speechGen)
11511156
for seq in speechGen:
11521157
speak(seq, priority=priority)
1158+
if (
1159+
config.conf["speech"][getSynth().name]["delayedPhoneticDescriptions"]
1160+
and reason == OutputReason.CARET
1161+
and unit == textInfos.UNIT_CHARACTER
1162+
):
1163+
phoneticDescriptionTimer = callLater(
1164+
config.conf["speech"][getSynth().name]["delayedPhoneticDescriptionsTimeoutMs"],
1165+
speakDelayedDescription,
1166+
_FakeTextInfo(info)
1167+
)
11531168
return speechGen.returnValue
11541169

11551170

@@ -2595,3 +2610,46 @@ def clearTypedWordBuffer() -> None:
25952610
complete the word (such as a focus change or choosing to move the caret).
25962611
"""
25972612
_curWordChars.clear()
2613+
2614+
2615+
def cancelPhoneticDescriptionTimer() -> None:
2616+
"""
2617+
this stops the timer used for delaied phonetic descriptions.
2618+
This should be called when a new sentence is send or the user stops the synth.
2619+
E.G, by pressing control key.
2620+
"""
2621+
global phoneticDescriptionTimer
2622+
if phoneticDescriptionTimer and phoneticDescriptionTimer.IsRunning():
2623+
phoneticDescriptionTimer.Stop()
2624+
phoneticDescriptionTimer = None
2625+
2626+
2627+
class _FakeTextInfo():
2628+
"""
2629+
this class is used to preserve the information of the old object that contain the text.
2630+
It's useful to use with delayed descriptions.
2631+
"""
2632+
2633+
def __init__(self, origTextInfo: textInfos.TextInfo):
2634+
self.text = origTextInfo.text
2635+
self.fields = origTextInfo.getTextWithFields({})
2636+
2637+
def getTextWithFields(self, _=None):
2638+
return self.fields
2639+
2640+
2641+
def speakDelayedDescription(info: _FakeTextInfo):
2642+
"""
2643+
this function is used to announce the delayed descriptions.
2644+
We can't call spellTextInfo directly because we need to check if the description is available first.
2645+
"""
2646+
if info.text.strip() == "":
2647+
return
2648+
curLang = getCurrentLanguage()
2649+
if config.conf['speech']['autoLanguageSwitching']:
2650+
for k in info.fields:
2651+
if isinstance(k, textInfos.FieldCommand) and k.command == "formatChange":
2652+
curLang = k.field.get('language', curLang)
2653+
_, description = getCharDescListFromText(info.text, locale=curLang)[0]
2654+
if description:
2655+
spellTextInfo(info, useCharacterDescriptions=True)

user_docs/en/userGuide.t2t

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,6 +1260,19 @@ This option should generally be enabled.
12601260
However, some Microsoft Speech API synthesizers do not implement this correctly and behave strangely when it is enabled.
12611261
If you are having problems with the pronunciation of individual characters, try disabling this option.
12621262

1263+
==== Delayed phonetic descriptions for characters on cursor movement ====[delayedPhoneticDescriptions]
1264+
This setting is a checkbox that, when checked, tells NVDA to say the phonetic description when you move by characters, after an user defined pause. Default delay setting is 1000 ms (1 sec).
1265+
1266+
The phonetic description delay will be cancelled if another text is spoken during that time or if you press control key.
1267+
1268+
For example, if you review this line by characters, when you read the letter "b", NVDA will say "Bravo" after the defined pause.
1269+
1270+
This can be useful if your current speech synthesizer pronounces some characters in a similar way. Also, can be useful if you have some degree of deafness. In this case, could be a good idea to set NVDA to use small delays.
1271+
1272+
==== Time for delayed phonetic descriptions (ms) ====[delayedPhoneticDescriptionsTimeoutMs]
1273+
This edit field allows you to type the pause time that NVDA will take to say the phonetic description when you move by characters.
1274+
This setting is specified in milliseconds (ms).
1275+
12631276
+++ Select Synthesizer (NVDA+control+s) +++[SelectSynthesizer]
12641277
The Synthesizer dialog, which can be opened by activating the Change... button in the speech category of the NVDA settings dialog, allows you to select which Synthesizer NVDA should use to speak with.
12651278
Once you have selected your synthesizer of choice, you can press Ok and NVDA will load the selected Synthesizer.

0 commit comments

Comments
 (0)