1
#synthDrivers/__init__.py
1
#synthDriverHandler.py
2
2
#A part of NonVisual Desktop Access (NVDA)
3
#Copyright (C) 2006-2007 NVDA Contributors <http://www.nvda-project.org/>
4
3
#This file is covered by the GNU General Public License.
5
4
#See the file COPYING for more details.
5
#Copyright (C) 2006-2010 Michael Curran <mick@kulgan.net>, James Teh <jamie@jantrid.net>, Peter Vágner <peter.v@datagate.sk>, Aleksey Sadovoy <lex@onm.su>
7
7
from copy import deepcopy
21
22
config.addConfigDirsToPythonPackagePath(synthDrivers)
23
24
def changeVoice(synth, voice):
25
voiceName = synth.getVoiceInfoByID(voice).name
27
fileName=r"%s\%s-%s.dic"%(speechDictHandler.speechDictsPath,synth.name,api.filterFileName(voiceName))
28
speechDictHandler.dictionaries["voice"].load(fileName)
25
# This function can be called with no voice if the synth doesn't support the voice setting (only has one voice).
29
28
c=config.conf["speech"][synth.name]
30
29
c.configspec=synth.getConfigSpec()
31
30
config.conf.validate(config.val, copy = True,section = c)
32
31
#start or update the synthSettingsRing
33
32
if globalVars.settingsRing: globalVars.settingsRing.updateSupportedSettings(synth)
34
33
else: globalVars.settingsRing = SynthSettingsRing(synth)
34
speechDictHandler.loadVoiceDict(synth)
36
36
def _getSynthDriver(name):
37
37
return __import__("synthDrivers.%s" % name, globals(), locals(), ("synthDrivers",)).SynthDriver
75
75
prevSynthName = None
77
77
newSynth=_getSynthDriver(name)()
78
updatedConfig=config.updateSynthConfig(newSynth)
78
if name in config.conf["speech"]:
80
79
newSynth.loadSettings()
81
# Create the new section.
82
config.conf["speech"][name]={}
82
83
if newSynth.isSupported("voice"):
83
#We need to call changeVoice here so voice dictionaries can be managed
84
changeVoice(newSynth,newSynth.voice)
87
# We need to call changeVoice here so that required initialisation can be performed.
88
changeVoice(newSynth,voice)
85
89
newSynth.saveSettings() #save defaults
87
#start or update the synthSettingsRing (for those synths which do not support 'voice')
88
if not newSynth.isSupported('voice'):
89
if globalVars.settingsRing: globalVars.settingsRing.updateSupportedSettings(newSynth)
90
else: globalVars.settingsRing = SynthSettingsRing(newSynth)
91
91
config.conf["speech"]["synth"]=name
92
92
log.info("Loaded synthDriver %s"%name)
140
140
self.normalStep=max(normalStep,minStep)
141
141
self.largeStep=max(largeStep,self.normalStep)
143
class BooleanSynthSetting(SynthSetting):
144
"""Represents a boolean synthesiser setting such as rate boost.
146
configSpec = "boolean(default=False)"
148
def __init__(self, name, i18nName, availableInSynthSettingsRing=False):
149
super(BooleanSynthSetting, self).__init__(name, i18nName, availableInSynthSettingsRing)
143
151
class SynthDriver(baseObject.AutoPropertyObject):
144
152
"""Abstract base synthesizer driver.
145
153
Each synthesizer driver should be a separate Python module in the root synthDrivers directory containing a SynthDriver class which inherits from this base class.
156
164
@ivar voice: Unique string identifying the current voice.
158
166
@ivar availableVoices: The available voices.
159
@ivar availableVoices: [L{VoiceInfo}, ...]
167
@type availableVoices: OrderedDict of L{VoiceInfo} keyed by VoiceInfo's ID
160
168
@ivar pitch: The current pitch; ranges between 0 and 100.
162
170
@ivar rate: The current rate; ranges between 0 and 100.
166
174
@ivar variant: The current variant of the voice.
167
175
@type variant: str
168
176
@ivar availableVariants: The available variants of the voice.
169
@type availableVariants: [L{VoiceInfo}, ...]
177
@type availableVariants: OrderedDict of [L{VoiceInfo} keyed by VoiceInfo's ID
170
178
@ivar inflection: The current inflection; ranges between 0 and 100.
171
179
@type inflection: int
172
180
@ivar lastIndex: The index of the chunk of text which was last spoken or C{None} if no index.
173
181
@type lastIndex: int
174
@warning: The has* and *MinStep attributes (e.g. hasPitch and pitchMinStep) are deprecated and should not be used in new drivers.
177
184
#: The name of the synth; must be the original module file name.
192
def LanguageSetting(cls):
193
"""Factory function for creating a language setting."""
194
return SynthSetting("language",_("&Language"))
185
197
def VoiceSetting(cls):
186
198
"""Factory function for creating voice setting."""
187
199
return SynthSetting("voice",_("&Voice"))
194
206
def RateSetting(cls,minStep=1):
195
207
"""Factory function for creating rate setting."""
196
return NumericSynthSetting("rate",_("&Rate"),minStep)
208
return NumericSynthSetting("rate",_("&Rate"),minStep=minStep)
198
210
def VolumeSetting(cls,minStep=1):
199
211
"""Factory function for creating volume setting."""
202
214
def PitchSetting(cls,minStep=1):
203
215
"""Factory function for creating pitch setting."""
204
return NumericSynthSetting("pitch",_("&Pitch"),minStep)
216
return NumericSynthSetting("pitch",_("&Pitch"),minStep=minStep)
207
219
def InflectionSetting(cls,minStep=1):
208
220
"""Factory function for creating inflection setting."""
209
return NumericSynthSetting("inflection",_("&Inflection"),minStep)
221
return NumericSynthSetting("inflection",_("&Inflection"),minStep=minStep)
232
244
@postcondition: This driver can no longer be used.
247
def speak(self,speechSequence):
249
Speaks the given sequence of text and speech commands.
250
This base implementation will fallback to making use of the old speakText and speakCharacter methods. But new synths should override this method to support its full functionality.
251
@param speechSequence: a list of text strings and SpeechCommand objects (such as index and parameter changes).
252
@type speechSequence: list of string and L{speechCommand}
257
origSpeakFunc=self.speakText
258
speechSequence=iter(speechSequence)
260
item = next(speechSequence,None)
261
if text and (item is None or isinstance(item,(speech.IndexCommand,speech.CharacterModeCommand))):
262
# Either we're about to handle a command or this is the end of the sequence.
263
# Speak the text since the last command we handled.
264
origSpeakFunc(text,index=lastIndex)
270
if isinstance(item,basestring):
271
# Merge the text between commands into a single chunk.
273
elif isinstance(item,speech.IndexCommand):
275
elif isinstance(item,speech.CharacterModeCommand):
276
origSpeakFunc=self.speakCharacter if item.state else self.speakText
277
elif isinstance(item,speech.SpeechCommand):
278
log.debugWarning("Unknown speech command: %s"%item)
280
log.error("Unknown item in speech sequence: %s"%item)
235
282
def speakText(self, text, index=None):
236
283
"""Speak some text.
284
This method is deprecated. Instead implement speak.
237
285
@param text: The chunk of text to speak.
239
287
@param index: An index (bookmark) to associate with this chunk of text, C{None} if no index.
241
289
@note: If C{index} is provided, the C{lastIndex} property should return this index when the synth is speaking this chunk of text.
291
raise NotImplementedError
244
293
def speakCharacter(self, character, index=None):
245
294
"""Speak some character.
295
This method is deprecated. Instead implement speak.
246
296
@param character: The character to speak.
247
297
@type character: str
248
298
@param index: An index (bookmark) to associate with this chunk of speech, C{None} if no index.
264
314
"""Silence speech immediately.
317
def _get_language(self):
318
return self.availableVoices[self.voice].language
320
def _set_language(self,language):
321
raise NotImplementedError
323
def _get_availableLanguages(self):
324
raise NotImplementedError
267
326
def _get_voice(self):
268
327
raise NotImplementedError
273
332
def _getAvailableVoices(self):
274
"""fetches a list of voices that the synth supports.
275
@returns: a list of L{VoiceInfo} instances representing the available voices
333
"""fetches an ordered dictionary of voices that the synth supports.
334
@returns: an OrderedDict of L{VoiceInfo} instances representing the available voices, keyed by ID
337
raise NotImplementedError
279
339
def _get_availableVoices(self):
280
340
if not hasattr(self,'_availableVoices'):
308
368
def _getAvailableVariants(self):
309
"""fetches a list of variants that the synth supports.
310
@returns: a list of L{VoiceInfo} instances representing the available variants
369
"""fetches an ordered dictionary of variants that the synth supports, keyed by ID
370
@returns: an ordered dictionary of L{VoiceInfo} instances representing the available variants
373
raise NotImplementedError
314
375
def _get_availableVariants(self):
315
376
if not hasattr(self,'_availableVariants'):
317
378
return self._availableVariants
319
380
def _get_supportedSettings(self):
320
"""This base implementation checks old-style 'has_xxx' and constructs the list of settings.
321
@returns: list of supported settings
325
settings=(("voice",self.VoiceSetting),("variant",self.VariantSetting),("rate",self.RateSetting),("pitch",self.PitchSetting),("inflection",self.InflectionSetting),("volume",self.VolumeSetting))
326
for name,setting in settings:
327
if not getattr(self,"has%s"%name.capitalize(),False): continue
328
if hasattr(self,"%sMinStep"%name):
329
result.append(setting(getattr(self,"%sMinStep"%name)))
331
result.append(setting())
381
raise NotImplementedError
334
383
def getConfigSpec(self):
335
384
spec=deepcopy(config.synthSpec)
375
424
return int(round(float(percent) / 100 * (max - min) + min))
377
def getVoiceInfoByID(self,ID):
378
"""Looks up a L{VoiceInfo} instance representing a particular voice, by its ID.
379
@param ID: the ID of the voice
381
@returns: the voice info instance
383
@raise LookupError: If there was no such voice.
385
for v in self.availableVoices:
388
raise LookupError("No such voice")
390
426
def isSupported(self,settingName):
391
427
"""Checks whether given setting is supported by the synthesizer.
403
439
def loadSettings(self):
404
440
c=config.conf["speech"][self.name]
405
441
if self.isSupported("voice"):
442
voice=c.get("voice",None)
408
444
changeVoice(self,voice)
409
445
except LookupError:
410
446
log.warning("No such voice: %s" % voice)
411
447
# Update the configuration with the correct voice.
412
448
c["voice"]=self.voice
413
# We need to call changeVoice here so voice dictionaries can be managed
449
# We need to call changeVoice here so that required initialisation can be performed.
414
450
changeVoice(self,self.voice)
415
[setattr(self,s.name,c[s.name]) for s in self.supportedSettings if not s.name=="voice" and c[s.name] is not None]
452
changeVoice(self,None)
453
for s in self.supportedSettings:
454
if s.name=="voice" or c[s.name] is None:
456
setattr(self,s.name,c[s.name])
417
458
def _get_initialSettingsRingSetting (self):
418
459
if not self.isSupported("rate") and len(self.supportedSettings)>0:
424
465
if s.name=="rate": return i
427
class VoiceInfo(object):
428
"""Provides information about a single synthesizer voice.
468
class StringParameterInfo(object):
470
The base class used to represent a value of a string synth setting.
431
473
def __init__(self,ID,name):
432
#: The unique identifier of the voice.
474
#: The unique identifier of the value.
435
#: The name of the voice, visible to the user.
477
#: The name of the value, visible to the user.
481
class VoiceInfo(StringParameterInfo):
482
"""Provides information about a single synthesizer voice.
485
def __init__(self,ID,name,language=None):
486
#: The ID of the language this voice speaks, or None if not known or the synth implements language separate from voices
487
self.language=language
488
super(VoiceInfo,self).__init__(ID,name)
490
class LanguageInfo(StringParameterInfo):
491
"""Holds information for a particular language"""
493
def __init__(self,ID):
494
"""Given a language ID (locale name) the description is automatically calculated."""
495
name=languageHandler.getLanguageDescription(ID)
496
super(LanguageInfo,self).__init__(ID,name)