Skip to content

Commit 54960db

Browse files
authored
Merge 4314edc into a198c9b
2 parents a198c9b + 4314edc commit 54960db

1 file changed

Lines changed: 50 additions & 35 deletions

File tree

source/synthDrivers/sapi4.py

Lines changed: 50 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# A part of NonVisual Desktop Access (NVDA)
2-
# Copyright (C) 2006-2022 NV Access Limited
2+
# Copyright (C) 2006-2023 NV Access Limited, Leonard de Ruijter
33
# This file is covered by the GNU General Public License.
44
# See the file COPYING for more details.
55

@@ -43,8 +43,11 @@
4343
CharacterModeCommand,
4444
BreakCommand,
4545
PitchCommand,
46+
RateCommand,
47+
VolumeCommand,
4648
)
4749

50+
4851
class SynthDriverBufSink(COMObject):
4952
_com_interfaces_ = [ITTSBufNotifySink]
5053

@@ -74,6 +77,11 @@ class SynthDriver(SynthDriver):
7477
name="sapi4"
7578
description="Microsoft Speech API version 4"
7679
supportedSettings=[SynthDriver.VoiceSetting()]
80+
supportedCommands = {
81+
IndexCommand,
82+
CharacterModeCommand,
83+
BreakCommand,
84+
}
7785
supportedNotifications={synthIndexReached,synthDoneSpeaking}
7886

7987
@classmethod
@@ -120,10 +128,9 @@ def speak(self,speechSequence):
120128
textList=[]
121129
charMode=False
122130
item=None
123-
isPitchCommand = False
124-
pitch = WORD()
125-
self._ttsAttrs.PitchGet(byref(pitch))
126-
oldPitch = pitch.value
131+
oldPitch = self.pitch
132+
oldVolume = self.volume
133+
oldRate = self.rate
127134

128135
for item in speechSequence:
129136
if isinstance(item,str):
@@ -136,15 +143,22 @@ def speak(self,speechSequence):
136143
elif isinstance(item, BreakCommand):
137144
textList.append(f"\\Pau={item.time}\\")
138145
elif isinstance(item, PitchCommand):
139-
offset = int(config.conf["speech"]['sapi4']["capPitchChange"])
140-
offset = int((self._maxPitch - self._minPitch) * offset / 100)
141-
val = oldPitch + offset
142-
if val > self._maxPitch:
143-
val = self._maxPitch
144-
if val < self._minPitch:
145-
val = self._minPitch
146-
self._ttsAttrs.PitchSet(val)
147-
isPitchCommand = True
146+
val = oldPitch + item.offset
147+
val = self._percentToParam(val, self._minPitch, self._maxPitch)
148+
textList.append(f"\\Pit={val}\\")
149+
elif isinstance(item, RateCommand):
150+
val = oldRate + item.offset
151+
val = self._percentToParam(val, self._minRate, self._maxRate)
152+
textList.append(f"\\Spd={val}\\")
153+
elif isinstance(item, VolumeCommand):
154+
val = oldVolume + item.offset
155+
val = self._percentToParam(
156+
val,
157+
self._minVolume & 0xffff,
158+
self._maxVolume & 0xffff
159+
)
160+
val+= val << 16
161+
textList.append(f"\\Vol={val}\\")
148162
elif isinstance(item, SpeechCommand):
149163
log.debugWarning("Unsupported speech command: %s"%item)
150164
else:
@@ -161,24 +175,13 @@ def speak(self,speechSequence):
161175
textList.append("\\PAU=1\\")
162176
text="".join(textList)
163177
flags=TTSDATAFLAG_TAGGED
164-
if isPitchCommand:
165-
self._ttsCentral.TextData(
166-
VOICECHARSET.CHARSET_TEXT,
167-
flags,
168-
TextSDATA(text),
169-
self._bufSinkPtr,
170-
ITTSBufNotifySink._iid_
171-
)
172-
self._ttsAttrs.PitchSet(oldPitch)
173-
isPitchCommand = False
174-
else:
175-
self._ttsCentral.TextData(
176-
VOICECHARSET.CHARSET_TEXT,
177-
flags,
178-
TextSDATA(text),
179-
self._bufSinkPtr,
180-
ITTSBufNotifySink._iid_
181-
)
178+
self._ttsCentral.TextData(
179+
VOICECHARSET.CHARSET_TEXT,
180+
flags,
181+
TextSDATA(text),
182+
self._bufSinkPtr,
183+
ITTSBufNotifySink._iid_
184+
)
182185

183186
def cancel(self):
184187
self._ttsCentral.AudioReset()
@@ -239,8 +242,12 @@ def _set_voice(self,val):
239242
if hasRate:
240243
if not self.isSupported('rate'):
241244
self.supportedSettings.insert(1,SynthDriver.RateSetting())
245+
self.supportedCommands.add(RateCommand)
242246
else:
243-
if self.isSupported("rate"): self.removeSetting("rate")
247+
if self.isSupported("rate"):
248+
self.removeSetting("rate")
249+
if RateCommand in self.supportedCommands:
250+
self.supportedCommands.remove(RateCommand)
244251
#Find out pitch limits
245252
hasPitch=bool(mode.dwFeatures&TTSFEATURE_PITCH)
246253
if hasPitch:
@@ -262,8 +269,12 @@ def _set_voice(self,val):
262269
if hasPitch:
263270
if not self.isSupported('pitch'):
264271
self.supportedSettings.insert(2,SynthDriver.PitchSetting())
272+
self.supportedCommands.add(PitchCommand)
265273
else:
266-
if self.isSupported('pitch'): self.removeSetting('pitch')
274+
if self.isSupported('pitch'):
275+
self.removeSetting('pitch')
276+
if PitchCommand in self.supportedCommands:
277+
self.supportedCommands.remove(PitchCommand)
267278
#Find volume limits
268279
hasVolume=bool(mode.dwFeatures&TTSFEATURE_VOLUME)
269280
if hasVolume:
@@ -285,8 +296,12 @@ def _set_voice(self,val):
285296
if hasVolume:
286297
if not self.isSupported('volume'):
287298
self.supportedSettings.insert(3,SynthDriver.VolumeSetting())
299+
self.supportedCommands.add(VolumeCommand)
288300
else:
289-
if self.isSupported('volume'): self.removeSetting('volume')
301+
if self.isSupported('volume'):
302+
self.removeSetting('volume')
303+
if VolumeCommand in self.supportedCommands:
304+
self.supportedCommands.remove(VolumeCommand)
290305

291306
def _get_voice(self):
292307
return str(self._currentMode.gModeID)

0 commit comments

Comments
 (0)