14
14
# Python 3 import
15
15
import queue
16
16
17
- import time , threading
18
- import nvwave , config , languageHandler , addonHandler
17
+ import threading , time
18
+ import config , languageHandler , nvwave , addonHandler
19
19
from logHandler import log
20
20
import _settingsDB
21
21
22
22
addonHandler .initTranslation ()
23
23
24
+ class ECIParam :
25
+ eciSynthMode = 0
26
+ eciInputType = 1
27
+ eciTextMode = 2
28
+ eciDictionary = 3
29
+ eciSampleRate = 5
30
+ eciWantPhonemeIndices = 7
31
+ eciRealWorldUnits = 8
32
+ eciLanguageDialect = 9
33
+ eciNumberMode = 10
34
+ eciWantWordIndex = 12
35
+
36
+ class ECIVoiceParam :
37
+ params = range (1 ,8 )
38
+ eciGender , eciHeadSize , eciPitchBaseline , eciPitchFluctuation , eciRoughness , eciBreathiness , eciSpeed , eciVolume = range (8 )
39
+
40
+
41
+ class ECIDictVolume :
42
+ eciMainDict , eciRootDict , eciAbbvDict , eciMainDictExt = range (4 )
43
+
44
+ class ECIMessage :
45
+ eciWaveformBuffer , eciPhonemeBuffer , eciIndexReply , eciPhonemeIndexReply , eciWordIndexReply = range (5 )
46
+
47
+ class ECICallbackReturn :
48
+ eciDataNotProcessed , eciDataProcessed , eciDataAbort = range (3 )
24
49
25
- gb = BytesIO ()
26
- speaking = False
27
50
user32 = windll .user32
28
- eci = None
29
- tid = None
30
- bgt = None
51
+ audioStream = BytesIO ()
52
+ speaking = False
53
+ eciThread = None
54
+ eciQueue = queue .Queue ()
55
+ eciThreadId = None
56
+ callbackThread = None
57
+ callbackQueue = queue .Queue ()
31
58
samples = 3300
32
59
buffer = create_string_buffer (samples * 2 )
33
- bgQueue = queue . Queue ()
34
- synthQueue = queue . Queue ()
60
+
61
+
35
62
stopped = threading .Event ()
36
63
started = threading .Event ()
37
64
param_event = threading .Event ()
38
- Callback = WINFUNCTYPE (c_int , c_int , c_int , c_int , c_void_p )
39
65
40
- # ECI constants.
41
- #speech parameters
42
- hsz = 1
43
- pitch = 2
44
- fluctuation = 3
45
- rgh = 4
46
- bth = 5
47
- rate = 6
48
- vlm = 7
49
66
50
67
lastindex = 0
51
68
langs = {'esp' : (131072 , _ ('Castilian Spanish' ), 'es_ES' , 'es' ),
80
97
dll = None
81
98
handle = None
82
99
83
- class eciThread (threading .Thread ):
100
+ class EciThread (threading .Thread ):
84
101
def run (self ):
85
102
global vparams , params , speaking , endMarkersCount
86
- global tid , dll , handle
87
- tid = windll .kernel32 .GetCurrentThreadId ()
103
+ global eciThreadId , dll , handle
104
+ eciThreadId = windll .kernel32 .GetCurrentThreadId ()
88
105
msg = wintypes .MSG ()
89
106
user32 .PeekMessageA (byref (msg ), None , 0x400 , 0x400 , 0 )
90
107
(dll , handle ) = eciNew ()
91
108
dll .eciRegisterCallback (handle , callback , None )
92
109
dll .eciSetOutputBuffer (handle , samples , pointer (buffer ))
93
- dll .eciSetParam (handle ,1 , 1 )
94
- dll .eciSetParam (handle ,3 , 1 ) #dictionary off
110
+ dll .eciSetParam (handle , ECIParam . eciInputType , 1 )
111
+ dll .eciSetParam (handle , ECIParam . eciDictionary , 1 ) #dictionary off
95
112
self .dictionaryHandle = dll .eciNewDict (handle )
96
113
dll .eciSetDict (handle , self .dictionaryHandle )
97
114
#0 = main dictionary
98
115
if path .exists (path .join (ttsPath , "main.dic" )):
99
116
dll .eciLoadDict (handle , self .dictionaryHandle , 0 , path .join (ttsPath , "main.dic" ))
100
117
if path .exists (path .join (ttsPath , "root.dic" )):
101
118
dll .eciLoadDict (handle , self .dictionaryHandle , 1 , path .join (ttsPath , "root.dic" ))
102
- params [9 ] = dll .eciGetParam (handle , 9 )
119
+ params [ECIParam . eciLanguageDialect ] = dll .eciGetParam (handle , ECIParam . eciLanguageDialect )
103
120
started .set ()
104
121
while True :
105
122
user32 .GetMessageA (byref (msg ), 0 , 0 , 0 )
106
123
user32 .TranslateMessage (byref (msg ))
107
124
if msg .message == WM_PROCESS :
108
- internal_process_queue ()
125
+ processEciQueue ()
109
126
elif msg .message == WM_SILENCE :
110
127
speaking = False
111
- gb .seek (0 )
112
- gb .truncate (0 )
128
+ audioStream .seek (0 )
129
+ audioStream .truncate (0 )
113
130
dll .eciStop (handle )
114
131
try :
115
132
while True :
116
- bgQueue .get_nowait ()
133
+ callbackQueue .get_nowait ()
134
+ callbackQueue .task_done ()
117
135
except :
118
136
pass
119
137
player .stop ()
@@ -132,7 +150,7 @@ def run(self):
132
150
param_event .set ()
133
151
elif msg .message == WM_COPYVOICE :
134
152
dll .eciCopyVoice (handle , msg .wParam , 0 )
135
- for i in ( rate , pitch , vlm , fluctuation , hsz , rgh , bth ) :
153
+ for i in ECIVoiceParam . params :
136
154
vparams [i ] = dll .eciGetVoiceParam (handle , 0 , i )
137
155
param_event .set ()
138
156
elif msg .message == WM_KILL :
@@ -142,6 +160,12 @@ def run(self):
142
160
else :
143
161
user32 .DispatchMessageA (byref (msg ))
144
162
163
+ def processEciQueue ():
164
+ lst = eciQueue .get ()
165
+ for (func , args ) in lst :
166
+ func (* args )
167
+ eciQueue .task_done ()
168
+
145
169
def eciCheck ():
146
170
global ttsPath , dllName , dll
147
171
dllName = config .conf ['ibmeci' ]['dllName' ]
@@ -181,14 +205,14 @@ def eciNew():
181
205
if 'ibmtts' in config .conf ['speech' ] and config .conf ['speech' ]['ibmtts' ]['voice' ] != '' :
182
206
handle = eci .eciNewEx (int (config .conf ['speech' ]['ibmtts' ]['voice' ]))
183
207
else : handle = eci .eciNewEx (getVoiceByLanguage (languageHandler .getLanguage ())[0 ])
184
- for i in ( rate , pitch , vlm , fluctuation , hsz , rgh , bth ) :
208
+ for i in ECIVoiceParam . params :
185
209
vparams [i ] = eci .eciGetVoiceParam (handle , 0 , i )
186
210
return eci ,handle
187
211
188
212
@WINFUNCTYPE (c_int ,c_int ,c_int ,c_long ,c_void_p )
189
- def _bgExec (func , * args , ** kwargs ):
190
- global bgQueue
191
- bgQueue .put ((func , args , kwargs ))
213
+ def _callbackExec (func , * args , ** kwargs ):
214
+ global callbackQueue
215
+ callbackQueue .put ((func , args , kwargs ))
192
216
def setLast (lp ):
193
217
global lastindex
194
218
lastindex = lp
@@ -212,72 +236,69 @@ def bgPlay(stri):
212
236
log .error ("Eloq speech failed to feed one buffer." )
213
237
214
238
curindex = None
239
+ Callback = WINFUNCTYPE (c_int , c_int , c_int , c_int , c_void_p )
215
240
@Callback
216
241
def callback (h , ms , lp , dt ):
217
- global gb , curindex , speaking , END_STRING_MARK , endMarkersCount
218
- #if not speaking: return 2
219
- #We need to buffer x amount of audio, and send the indexes after it.
220
- #Accuracy is lost with this method, but it should stop the say all breakage.
221
-
222
- if speaking and ms == 0 : #audio data
223
- gb .write (string_at (buffer , lp * 2 ))
224
- if gb .tell () >= samples * 2 :
225
- _bgExec (bgPlay , gb .getvalue ())
242
+ global audioStream , curindex , speaking , END_STRING_MARK , endMarkersCount
243
+ if speaking and ms == ECIMessage .eciWaveformBuffer :
244
+ audioStream .write (string_at (buffer , lp * 2 ))
245
+ if audioStream .tell () >= samples * 2 :
246
+ _callbackExec (bgPlay , audioStream .getvalue ())
226
247
if curindex is not None :
227
- _bgExec (setLast , curindex )
248
+ _callbackExec (setLast , curindex )
228
249
curindex = None
229
- gb .truncate (0 )
230
- gb .seek (0 )
231
- elif ms == 2 : #index
250
+ audioStream .truncate (0 )
251
+ audioStream .seek (0 )
252
+ elif ms == ECIMessage . eciIndexReply :
232
253
if lp != END_STRING_MARK : #end of string
233
254
curindex = lp
234
255
else : #We reached the end of string
235
- if gb .tell () > 0 :
236
- _bgExec (bgPlay , gb .getvalue ())
237
- gb .seek (0 )
238
- gb .truncate (0 )
256
+ if audioStream .tell () > 0 :
257
+ _callbackExec (bgPlay , audioStream .getvalue ())
258
+ audioStream .seek (0 )
259
+ audioStream .truncate (0 )
239
260
if curindex is not None :
240
- _bgExec (setLast , curindex )
261
+ _callbackExec (setLast , curindex )
241
262
curindex = None
242
- _bgExec (endStringEvent )
243
- return 1
263
+ _callbackExec (endStringEvent )
264
+ return ECICallbackReturn . eciDataProcessed
244
265
245
- class BgThread (threading .Thread ):
266
+ class CallbackThread (threading .Thread ):
246
267
def __init__ (self ):
247
268
threading .Thread .__init__ (self )
248
269
self .setDaemon (True )
249
270
250
271
def run (self ):
251
272
try :
252
273
while True :
253
- func , args , kwargs = bgQueue .get ()
274
+ func , args , kwargs = callbackQueue .get ()
254
275
if not func :
255
276
break
256
277
func (* args , ** kwargs )
257
- bgQueue .task_done ()
278
+ callbackQueue .task_done ()
258
279
except :
259
- log .error ("bgThread .run" , exc_info = True )
280
+ log .error ("CallbackThread .run" , exc_info = True )
260
281
261
- def _bgExec (func , * args , ** kwargs ):
262
- global bgQueue
263
- bgQueue .put ((func , args , kwargs ))
282
+ def _callbackExec (func , * args , ** kwargs ):
283
+ global callbackQueue
284
+ callbackQueue .put ((func , args , kwargs ))
264
285
265
286
def initialize ():
266
- global eci , player , bgt , dll , handle
287
+ global callbackThread , dll , eciThread , handle , player
267
288
player = nvwave .WavePlayer (1 , 11025 , 16 , outputDevice = config .conf ["speech" ]["outputDevice" ])
268
289
if not eciCheck ():
269
290
raise RuntimeError ("No IBMTTS synthesizer available" )
270
- eci = eciThread ()
271
- eci .start ()
291
+ eciThread = EciThread ()
292
+ eciThread .start ()
272
293
started .wait ()
273
294
started .clear ()
274
- bgt = BgThread ()
275
- bgt .start ()
295
+ callbackThread = CallbackThread ()
296
+ callbackThread .start ()
276
297
277
298
def speak (text ):
278
299
#Sometimes the synth slows down for one string of text. Why?
279
300
#Trying to fix it here.
280
- if rate in vparams : text = b"`vs%d%s" % (vparams [rate ], text )
301
+ if ECIVoiceParam . eciSpeed in vparams : text = b"`vs%d%s" % (vparams [ECIVoiceParam . eciSpeed ], text )
281
302
dll .eciAddText (handle , text )
282
303
283
304
def index (x ):
@@ -297,46 +318,43 @@ def synth():
297
318
dll .eciSynthesize (handle )
298
319
299
320
def stop ():
300
- user32 .PostThreadMessageA (tid , WM_SILENCE , 0 , 0 )
321
+ user32 .PostThreadMessageA (eciThreadId , WM_SILENCE , 0 , 0 )
301
322
302
323
def pause (switch ):
303
324
player .pause (switch )
304
325
305
326
def terminate ():
306
- global bgt , player
307
- user32 .PostThreadMessageA (tid , WM_KILL , 0 , 0 )
327
+ global callbackThread , eciThread , player
328
+ user32 .PostThreadMessageA (eciThreadId , WM_KILL , 0 , 0 )
308
329
stopped .wait ()
309
330
stopped .clear ()
310
- bgQueue .put ((None , None , None ))
311
- eci .join ()
312
- bgt .join ()
331
+ callbackQueue .put ((None , None , None ))
332
+ eciThread .join ()
333
+ callbackThread .join ()
313
334
player .close ()
314
335
player = None
315
- bgt = None
336
+ callbackThread = None
337
+ eciThread = None
338
+ dll = None
316
339
317
340
def set_voice (vl ):
318
- user32 .PostThreadMessageA (tid , WM_PARAM , int (vl ), 9 )
341
+ user32 .PostThreadMessageA (eciThreadId , WM_PARAM , int (vl ), ECIParam . eciLanguageDialect )
319
342
320
343
def getVParam (pr ):
321
344
return vparams [pr ]
322
345
323
346
def setVParam (pr , vl ):
324
- user32 .PostThreadMessageA (tid , WM_VPARAM , pr , vl )
347
+ user32 .PostThreadMessageA (eciThreadId , WM_VPARAM , pr , vl )
325
348
param_event .wait ()
326
349
param_event .clear ()
327
350
328
351
def setVariant (v ):
329
- user32 .PostThreadMessageA (tid , WM_COPYVOICE , v , 0 )
352
+ user32 .PostThreadMessageA (eciThreadId , WM_COPYVOICE , v , 0 )
330
353
param_event .wait ()
331
354
param_event .clear ()
332
355
333
356
def process ():
334
- user32 .PostThreadMessageA (tid , WM_PROCESS , 0 , 0 )
335
-
336
- def internal_process_queue ():
337
- lst = synthQueue .get ()
338
- for (func , args ) in lst :
339
- func (* args )
357
+ user32 .PostThreadMessageA (eciThreadId , WM_PROCESS , 0 , 0 )
340
358
341
359
def eciVersion ():
342
360
ptr = " "
@@ -362,6 +380,7 @@ def endStringEvent():
362
380
speaking = False
363
381
threading .Timer (0.3 , idlePlayer ).start ()
364
382
383
+
365
384
def idlePlayer ():
366
385
global player , speaking , endMarkersCount
367
386
if not speaking and endMarkersCount == 0 : player .idle ()
0 commit comments