Package synthDrivers :: Module sapi4
[hide private]
[frames] | no frames]

Source Code for Module synthDrivers.sapi4

  1  #synthDrivers/sapi4.py 
  2  #A part of NonVisual Desktop Access (NVDA) 
  3  #Copyright (C) 2006-2009 NVDA Contributors <http://www.nvda-project.org/> 
  4  #This file is covered by the GNU General Public License. 
  5  #See the file COPYING for more details. 
  6   
  7  import locale 
  8  from collections import OrderedDict 
  9  import _winreg 
 10  from comtypes import COMObject, COMError 
 11  from ctypes import * 
 12  from synthDriverHandler import SynthDriver,VoiceInfo 
 13  from logHandler import log 
 14  import speech 
 15  from _sapi4 import * 
 16  import config 
 17  import nvwave 
18 19 -class SynthDriverBufSink(COMObject):
20 _com_interfaces_ = [ITTSBufNotifySink] 21
22 - def __init__(self,synthDriver):
23 self._synthDriver=synthDriver 24 self._allowDelete = True 25 super(SynthDriverBufSink,self).__init__()
26
27 - def ITTSBufNotifySink_BookMark(self, this, qTimeStamp, dwMarkNum):
28 self._synthDriver.lastIndex=dwMarkNum
29
30 - def IUnknown_Release(self, this, *args, **kwargs):
31 if not self._allowDelete and self._refcnt.value == 1: 32 log.debugWarning("ITTSBufNotifySink::Release called too many times by engine") 33 return 1 34 return super(SynthDriverBufSink, self).IUnknown_Release(this, *args, **kwargs)
35
36 -class SynthDriver(SynthDriver):
37 38 name="sapi4" 39 description="Microsoft Speech API version 4" 40 supportedSettings=[SynthDriver.VoiceSetting()] 41 42 @classmethod
43 - def check(cls):
44 try: 45 _winreg.OpenKey(_winreg.HKEY_CLASSES_ROOT, r"CLSID\%s" % CLSID_TTSEnumerator).Close() 46 return True 47 except WindowsError: 48 return False
49
50 - def _fetchEnginesList(self):
51 enginesList=[] 52 self._ttsEngines.Reset() 53 while True: 54 mode=TTSMODEINFO() 55 fetched=c_ulong() 56 try: 57 self._ttsEngines.Next(1,byref(mode),byref(fetched)) 58 except: 59 log.error("can't get next engine",exc_info=True) 60 break 61 if fetched.value==0: 62 break 63 enginesList.append(mode) 64 return enginesList
65
66 - def __init__(self):
67 self.lastIndex=None 68 self._bufSink=SynthDriverBufSink(self) 69 self._bufSinkPtr=self._bufSink.QueryInterface(ITTSBufNotifySink) 70 # HACK: Some buggy engines call Release() too many times on our buf sink. 71 # Therefore, don't let the buf sink be deleted before we release it ourselves. 72 self._bufSink._allowDelete=False 73 self._ttsEngines=CoCreateInstance(CLSID_TTSEnumerator, ITTSEnumW) 74 self._enginesList=self._fetchEnginesList() 75 if len(self._enginesList)==0: 76 raise RuntimeError("No Sapi4 engines available") 77 self.voice=str(self._enginesList[0].gModeID)
78
79 - def terminate(self):
80 self._bufSink._allowDelete = True
81
82 - def speak(self,speechSequence):
83 textList=[] 84 charMode=False 85 for item in speechSequence: 86 if isinstance(item,basestring): 87 textList.append(item.replace('\\','\\\\')) 88 elif isinstance(item,speech.IndexCommand): 89 textList.append("\\mrk=%d\\"%item.index) 90 elif isinstance(item,speech.CharacterModeCommand): 91 textList.append("\\RmS=1\\" if item.state else "\\RmS=0\\") 92 charMode=item.state 93 elif isinstance(item,speech.SpeechCommand): 94 log.debugWarning("Unsupported speech command: %s"%item) 95 else: 96 log.error("Unknown speech: %s"%item) 97 if charMode: 98 # Some synths stay in character mode if we don't explicitly disable it. 99 textList.append("\\RmS=0\\") 100 text="".join(textList) 101 flags=TTSDATAFLAG_TAGGED 102 self._ttsCentral.TextData(VOICECHARSET.CHARSET_TEXT, flags,TextSDATA(text),self._bufSinkPtr,ITTSBufNotifySink._iid_)
103
104 - def cancel(self):
105 self._ttsCentral.AudioReset() 106 self.lastIndex=None
107
108 - def pause(self,switch):
109 if switch: 110 try: 111 self._ttsCentral.AudioPause() 112 except COMError: 113 pass 114 else: 115 self._ttsCentral.AudioResume()
116
117 - def removeSetting(self,name):
118 #Putting it here because currently no other synths make use of it. OrderedDict, where you are? 119 for i,s in enumerate(self.supportedSettings): 120 if s.name==name: 121 del self.supportedSettings[i] 122 return
123
124 - def _set_voice(self,val):
125 try: 126 val=GUID(val) 127 except: 128 val=self._enginesList[0].gModeID 129 mode=None 130 for mode in self._enginesList: 131 if mode.gModeID==val: 132 break 133 if mode is None: 134 raise ValueError("no such mode: %s"%val) 135 self._currentMode=mode 136 self._ttsAudio=CoCreateInstance(CLSID_MMAudioDest,IAudioMultiMediaDevice) 137 self._ttsAudio.DeviceNumSet(nvwave.outputDeviceNameToID(config.conf["speech"]["outputDevice"], True)) 138 self._ttsCentral=POINTER(ITTSCentralW)() 139 self._ttsEngines.Select(self._currentMode.gModeID,byref(self._ttsCentral),self._ttsAudio) 140 self._ttsAttrs=self._ttsCentral.QueryInterface(ITTSAttributes) 141 #Find out rate limits 142 hasRate=bool(mode.dwFeatures&TTSFEATURE_SPEED) 143 if hasRate: 144 try: 145 oldVal=DWORD() 146 self._ttsAttrs.SpeedGet(byref(oldVal)) 147 self._ttsAttrs.SpeedSet(TTSATTR_MINSPEED) 148 newVal=DWORD() 149 self._ttsAttrs.SpeedGet(byref(newVal)) 150 self._minRate=newVal.value 151 self._ttsAttrs.SpeedSet(TTSATTR_MAXSPEED) 152 self._ttsAttrs.SpeedGet(byref(newVal)) 153 # ViaVoice (and perhaps other synths) doesn't seem to like the speed being set to maximum. 154 self._maxRate=newVal.value-1 155 self._ttsAttrs.SpeedSet(oldVal.value) 156 if self._maxRate<=self._minRate: 157 hasRate=False 158 except COMError: 159 hasRate=False 160 if hasRate: 161 if not self.isSupported('rate'): 162 self.supportedSettings.insert(1,SynthDriver.RateSetting()) 163 else: 164 if self.isSupported("rate"): self.removeSetting("rate") 165 #Find out pitch limits 166 hasPitch=bool(mode.dwFeatures&TTSFEATURE_PITCH) 167 if hasPitch: 168 try: 169 oldVal=WORD() 170 self._ttsAttrs.PitchGet(byref(oldVal)) 171 self._ttsAttrs.PitchSet(TTSATTR_MINPITCH) 172 newVal=WORD() 173 self._ttsAttrs.PitchGet(byref(newVal)) 174 self._minPitch=newVal.value 175 self._ttsAttrs.PitchSet(TTSATTR_MAXPITCH) 176 self._ttsAttrs.PitchGet(byref(newVal)) 177 self._maxPitch=newVal.value 178 self._ttsAttrs.PitchSet(oldVal.value) 179 if self._maxPitch<=self._minPitch: 180 hasPitch=False 181 except COMError: 182 hasPitch=False 183 if hasPitch: 184 if not self.isSupported('pitch'): 185 self.supportedSettings.insert(2,SynthDriver.PitchSetting()) 186 else: 187 if self.isSupported('pitch'): self.removeSetting('pitch') 188 #Find volume limits 189 hasVolume=bool(mode.dwFeatures&TTSFEATURE_VOLUME) 190 if hasVolume: 191 try: 192 oldVal=DWORD() 193 self._ttsAttrs.VolumeGet(byref(oldVal)) 194 self._ttsAttrs.VolumeSet(TTSATTR_MINVOLUME) 195 newVal=DWORD() 196 self._ttsAttrs.VolumeGet(byref(newVal)) 197 self._minVolume=newVal.value 198 self._ttsAttrs.VolumeSet(TTSATTR_MAXVOLUME) 199 self._ttsAttrs.VolumeGet(byref(newVal)) 200 self._maxVolume=newVal.value 201 self._ttsAttrs.VolumeSet(oldVal.value) 202 if self._maxVolume<=self._minVolume: 203 hasVolume=False 204 except COMError: 205 hasVolume=False 206 if hasVolume: 207 if not self.isSupported('volume'): 208 self.supportedSettings.insert(3,SynthDriver.VolumeSetting()) 209 else: 210 if self.isSupported('volume'): self.removeSetting('volume')
211
212 - def _get_voice(self):
213 return str(self._currentMode.gModeID)
214
215 - def _getAvailableVoices(self):
216 voices=OrderedDict() 217 for mode in self._enginesList: 218 ID=str(mode.gModeID) 219 name="%s - %s"%(mode.szModeName,mode.szProductName) 220 try: 221 language=locale.windows_locale[mode.language.LanguageID] 222 except KeyError: 223 language=None 224 voices[ID]=VoiceInfo(ID,name,language) 225 return voices
226
227 - def _get_rate(self):
228 val=DWORD() 229 self._ttsAttrs.SpeedGet(byref(val)) 230 return self._paramToPercent(val.value,self._minRate,self._maxRate)
231
232 - def _set_rate(self,val):
233 val=self._percentToParam(val,self._minRate,self._maxRate) 234 self._ttsAttrs.SpeedSet(val)
235
236 - def _get_pitch(self):
237 val=WORD() 238 self._ttsAttrs.PitchGet(byref(val)) 239 return self._paramToPercent(val.value,self._minPitch,self._maxPitch)
240
241 - def _set_pitch(self,val):
242 val=self._percentToParam(val,self._minPitch,self._maxPitch) 243 self._ttsAttrs.PitchSet(val)
244
245 - def _get_volume(self):
246 val=DWORD() 247 self._ttsAttrs.VolumeGet(byref(val)) 248 return self._paramToPercent(val.value&0xffff,self._minVolume&0xffff,self._maxVolume&0xffff)
249
250 - def _set_volume(self,val):
251 val=self._percentToParam(val,self._minVolume&0xffff,self._maxVolume&0xffff) 252 val+=val<<16 253 self._ttsAttrs.VolumeSet(val)
254