~stomato463/+junk/nvdajp

« back to all changes in this revision

Viewing changes to source/synthDriverHandler.py

  • Committer: Masataka Shinke
  • Date: 2011-10-25 12:35:26 UTC
  • mfrom: (4185 jpmain)
  • mto: This revision was merged to the branch mainline in revision 4211.
  • Revision ID: mshinke@users.sourceforge.jp-20111025123526-ze527a2rl3z0g2ky
lp:~nishimotz/nvdajp/main : 4185 をマージ

Show diffs side-by-side

added added

removed removed

Lines of Context:
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>
6
6
 
7
7
from copy import deepcopy
8
8
import os
12
12
import globalVars
13
13
from logHandler import log
14
14
from  synthSettingsRing import SynthSettingsRing
 
15
import languageHandler
15
16
import speechDictHandler
16
17
import synthDrivers
17
18
 
21
22
        config.addConfigDirsToPythonPackagePath(synthDrivers)
22
23
 
23
24
def changeVoice(synth, voice):
24
 
        import api
25
 
        voiceName = synth.getVoiceInfoByID(voice).name
26
 
        synth.voice = voice
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).
 
26
        if voice:
 
27
                synth.voice = 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)
35
35
 
36
36
def _getSynthDriver(name):
37
37
        return __import__("synthDrivers.%s" % name, globals(), locals(), ("synthDrivers",)).SynthDriver
75
75
                prevSynthName = None
76
76
        try:
77
77
                newSynth=_getSynthDriver(name)()
78
 
                updatedConfig=config.updateSynthConfig(newSynth)
79
 
                if not updatedConfig:
 
78
                if name in config.conf["speech"]:
80
79
                        newSynth.loadSettings()
81
80
                else:
 
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)
 
84
                                voice=newSynth.voice
 
85
                        else:
 
86
                                voice=None
 
87
                        # We need to call changeVoice here so that required initialisation can be performed.
 
88
                        changeVoice(newSynth,voice)
85
89
                        newSynth.saveSettings() #save defaults
86
90
                _curSynth=newSynth
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)
93
93
                return True
140
140
                self.normalStep=max(normalStep,minStep)
141
141
                self.largeStep=max(largeStep,self.normalStep)
142
142
 
 
143
class BooleanSynthSetting(SynthSetting):
 
144
        """Represents a boolean synthesiser setting such as rate boost.
 
145
        """
 
146
        configSpec = "boolean(default=False)"
 
147
 
 
148
        def __init__(self, name, i18nName, availableInSynthSettingsRing=False):
 
149
                super(BooleanSynthSetting, self).__init__(name, i18nName, availableInSynthSettingsRing)
 
150
 
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.
157
165
        @type voice: str
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.
161
169
        @type pitch: int
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.
175
182
        """
176
183
 
177
184
        #: The name of the synth; must be the original module file name.
182
189
        description = ""
183
190
 
184
191
        @classmethod
 
192
        def LanguageSetting(cls):
 
193
                """Factory function for creating a language setting."""
 
194
                return SynthSetting("language",_("&Language"))
 
195
 
 
196
        @classmethod
185
197
        def VoiceSetting(cls):
186
198
                """Factory function for creating voice setting."""
187
199
                return SynthSetting("voice",_("&Voice"))
193
205
        @classmethod
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)
197
209
        @classmethod
198
210
        def VolumeSetting(cls,minStep=1):
199
211
                """Factory function for creating volume setting."""
201
213
        @classmethod
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)
205
217
 
206
218
        @classmethod
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)
210
222
 
211
223
        @classmethod
212
224
        def check(cls):
232
244
                @postcondition: This driver can no longer be used.
233
245
                """
234
246
 
 
247
        def speak(self,speechSequence):
 
248
                """
 
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}
 
253
                """
 
254
                import speech
 
255
                lastIndex=None
 
256
                text=""
 
257
                origSpeakFunc=self.speakText
 
258
                speechSequence=iter(speechSequence)
 
259
                while True:
 
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)
 
265
                                text=""
 
266
                                lastIndex=None
 
267
                        if item is None:
 
268
                                # No more items.
 
269
                                break
 
270
                        if isinstance(item,basestring):
 
271
                                # Merge the text between commands into a single chunk.
 
272
                                text+=item
 
273
                        elif isinstance(item,speech.IndexCommand):
 
274
                                lastIndex=item.index
 
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)
 
279
                        else:
 
280
                                log.error("Unknown item in speech sequence: %s"%item)
 
281
 
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.
238
286
                @type text: str
239
287
                @param index: An index (bookmark) to associate with this chunk of text, C{None} if no index.
240
288
                @type index: int
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.
242
290
                """
 
291
                raise NotImplementedError
243
292
 
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.
265
315
                """
266
316
 
 
317
        def _get_language(self):
 
318
                return self.availableVoices[self.voice].language
 
319
 
 
320
        def _set_language(self,language):
 
321
                raise NotImplementedError
 
322
 
 
323
        def _get_availableLanguages(self):
 
324
                raise NotImplementedError
 
325
 
267
326
        def _get_voice(self):
268
327
                raise NotImplementedError
269
328
 
271
330
                pass
272
331
 
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
276
 
                @rtype: list
 
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
 
335
                @rtype: OrderedDict
277
336
                """
 
337
                raise NotImplementedError
278
338
 
279
339
        def _get_availableVoices(self):
280
340
                if not hasattr(self,'_availableVoices'):
306
366
                pass
307
367
 
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
311
 
                @rtype: list
 
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
 
371
                @rtype: OrderedDict
312
372
                """
 
373
                raise NotImplementedError
313
374
 
314
375
        def _get_availableVariants(self):
315
376
                if not hasattr(self,'_availableVariants'):
317
378
                return self._availableVariants
318
379
 
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
322
 
                @rtype: l{tuple}
323
 
                """
324
 
                result=[]
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)))
330
 
                        else:
331
 
                                result.append(setting())
332
 
                return tuple(result)
 
381
                raise NotImplementedError
333
382
 
334
383
        def getConfigSpec(self):
335
384
                spec=deepcopy(config.synthSpec)
374
423
                """
375
424
                return int(round(float(percent) / 100 * (max - min) + min))
376
425
 
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
380
 
                @type ID: string
381
 
                @returns: the voice info instance
382
 
                @rtype: L{VoiceInfo}
383
 
                @raise LookupError: If there was no such voice.
384
 
                """
385
 
                for v in self.availableVoices:
386
 
                        if v.ID==ID:
387
 
                                return v
388
 
                raise LookupError("No such voice")
389
 
 
390
426
        def isSupported(self,settingName):
391
427
                """Checks whether given setting is supported by the synthesizer.
392
428
                @rtype: l{bool}
403
439
        def loadSettings(self):
404
440
                c=config.conf["speech"][self.name]
405
441
                if self.isSupported("voice"):
406
 
                        voice=c["voice"]
 
442
                        voice=c.get("voice",None)
407
443
                        try:
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]
 
451
                else:
 
452
                        changeVoice(self,None)
 
453
                for s in self.supportedSettings:
 
454
                        if s.name=="voice" or c[s.name] is None:
 
455
                                continue
 
456
                        setattr(self,s.name,c[s.name])
416
457
 
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
425
466
                return None
426
467
 
427
 
class VoiceInfo(object):
428
 
        """Provides information about a single synthesizer voice.
 
468
class StringParameterInfo(object):
 
469
        """
 
470
        The base class used to represent a value of a string synth setting.
429
471
        """
430
472
 
431
473
        def __init__(self,ID,name):
432
 
                #: The unique identifier of the voice.
 
474
                #: The unique identifier of the value.
433
475
                #: @type: str
434
476
                self.ID=ID
435
 
                #: The name of the voice, visible to the user.
 
477
                #: The name of the value, visible to the user.
436
478
                #: @type: str
437
479
                self.name=name
 
480
 
 
481
class VoiceInfo(StringParameterInfo):
 
482
        """Provides information about a single synthesizer voice.
 
483
        """
 
484
 
 
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)
 
489
 
 
490
class LanguageInfo(StringParameterInfo):
 
491
        """Holds information for a particular language"""
 
492
 
 
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)
 
497