Skip to content

Commit f3ead48

Browse files
authored
Merge e4516fa into ad5666b
2 parents ad5666b + e4516fa commit f3ead48

File tree

4 files changed

+101
-1
lines changed

4 files changed

+101
-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: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1618,6 +1618,38 @@ 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+
1632+
minDelay = int(config.conf.getConfigValidation(
1633+
("speech", self.driver.name, "delayedPhoneticDescriptionsTimeoutMs")
1634+
).kwargs["min"])
1635+
maxDelay = int(config.conf.getConfigValidation(
1636+
("speech", self.driver.name, "delayedPhoneticDescriptionsTimeoutMs")
1637+
).kwargs["max"])
1638+
1639+
# Translators: This is a label for a setting in voice settings (an edit box to change
1640+
# Time for the delayed phonetic descriptions in ms;
1641+
delayedPhoneticDescriptionsTimeoutMsLabel = _("&Time for delayed phonetic descriptions (ms)")
1642+
self.delayedPhoneticDescriptionsTimeoutMsEdit = settingsSizerHelper.addLabeledControl(
1643+
delayedPhoneticDescriptionsTimeoutMsLabel,
1644+
nvdaControls.SelectOnFocusSpinCtrl,
1645+
min=minDelay,
1646+
max=maxDelay,
1647+
initial=config.conf["speech"][self.driver.name]["delayedPhoneticDescriptionsTimeoutMs"])
1648+
self.bindHelpEvent(
1649+
"delayedPhoneticDescriptionsTimeoutMs",
1650+
self.delayedPhoneticDescriptionsTimeoutMsEdit
1651+
)
1652+
16211653
def onSave(self):
16221654
AutoSettingsMixin.onSave(self)
16231655

@@ -1636,6 +1668,8 @@ def onSave(self):
16361668
config.conf["speech"][self.driver.name]["sayCapForCapitals"]=self.sayCapForCapsCheckBox.IsChecked()
16371669
config.conf["speech"][self.driver.name]["beepForCapitals"]=self.beepForCapsCheckBox.IsChecked()
16381670
config.conf["speech"][self.driver.name]["useSpellingFunctionality"]=self.useSpellingFunctionalityCheckBox.IsChecked()
1671+
config.conf["speech"][self.driver.name]["delayedPhoneticDescriptions"] = self.delayedPhoneticDescriptionsCheckBox.IsChecked()
1672+
config.conf["speech"][self.driver.name]["delayedPhoneticDescriptionsTimeoutMs"] = self.delayedPhoneticDescriptionsTimeoutMsEdit.Value
16391673

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

source/speech/speech.py

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

user_docs/en/userGuide.t2t

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

1255+
==== Delayed phonetic descriptions for characters on cursor movement ====[delayedPhoneticDescriptions]
1256+
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).
1257+
1258+
The phonetic description delay will be cancelled if another text is spoken during that time or if you press control key.
1259+
1260+
For example, if you review this line by characters, when you read the letter "b", NVDA will say "Bravo" after the defined pause.
1261+
1262+
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.
1263+
1264+
==== Time for delayed phonetic descriptions (ms) ====[delayedPhoneticDescriptionsTimeoutMs]
1265+
This edit field allows you to type the pause time that NVDA will take to say the phonetic description when you move by characters.
1266+
This setting is specified in milliseconds (ms).
1267+
12551268
+++ Select Synthesizer (NVDA+control+s) +++[SelectSynthesizer]
12561269
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.
12571270
Once you have selected your synthesizer of choice, you can press Ok and NVDA will load the selected Synthesizer.

0 commit comments

Comments
 (0)