Skip to content

Commit 4e33c1f

Browse files
committed
update the code is now more understandable and updated with eci constants.
add break command pause feature. update speak function in ibmeci: now `ts0 annotation is added first in the queue. it fix some ignored punctuation symbols at the end of a lists of sentences. update now the driver doesn't replace commas by dash at the end of a string. by changing ts0 in the queue automatically fix it. update french strings and documentation, thanks to Rémy Ruiz. update english readme documentation and install_tasks.py, thanks to Andre Fisher. update translation strings catalog.
1 parent a3cbc67 commit 4e33c1f

File tree

6 files changed

+134
-118
lines changed

6 files changed

+134
-118
lines changed

addon/doc/fr/readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
# Caractéristiques :
77
* Prise en charge des paramètres de voix, variante, débit, hauteur, inflexion et volume.
8-
* Prise en charge des paramètres supplémentaire taille de la tête, enrouement , respiration. Créez votre propre voix !
8+
* Prise en charge des paramètres supplémentaire taille de la tête, raucité, souffle. Créez votre propre voix !
99
* Activer ou désactiver les balises de changement de voix. Désactivez-les pour vous protéger contre les codes malveillants des farceurs, activez-les pour permettre de faire beaucoup de choses amusantes avec le synthétiseur. Un plaisir garanti en toute sécurité !
1010
* Voix turbo. Si le synthétiseur ne vous parle pas assez vite, activez la voix turbo et obtenez la vitesse maximale !
1111
* Changement automatique de langue. Laissez le synthétiseur parler dans la bonne langue !

addon/installTasks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
msg=""
1010
if not os.path.exists(os.path.join(os.path.dirname(__file__), 'synthDrivers', config.conf['ibmeci']['TTSPath'], config.conf['ibmeci']['dllName'])):
1111
# Translators: the message shown if the driver can't find libraries during installation.
12-
msg = _("""The synthesizer won't be available until you set IBMTTS files. NVDA won't show this synthesizer in teh synthesizers list because you need to set the IBMTTS files location first.
12+
msg = _("""The synthesizer won't be available until you set IBMTTS files. NVDA won't show this synthesizer in teh synthesizers lists because you need to set the IBMTTS files location first.
1313
To do it open the NVDA settings dialog, select IBMTTS category and use the "Browse for IBMTTS library" button to select the IBMTTS files folder.\n""")
1414

1515
# Translators: message box when user is installing the addon in NVDA.

addon/locale/es/LC_MESSAGES/nvda.po

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ msgstr "Error al copiar los archivos de IBMTTS"
227227
#: addon\installTasks.py:9
228228
msgid ""
229229
"The synthesizer won't be available until you set IBMTTS files. NVDA won't "
230-
"show this synthesizer in teh synthesizers list because you need to set the "
230+
"show this synthesizer in teh synthesizers lists because you need to set the "
231231
"IBMTTS files location first.\n"
232232
"\tTo do it open the NVDA settings dialog, select IBMTTS category and use the "
233233
"\"Browse for IBMTTS library\" button to select the IBMTTS files folder.\n"

addon/locale/fr/LC_MESSAGES/nvda.po

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ msgstr ""
88
"Project-Id-Version: IBMTTS for NVDA\n"
99
"Report-Msgid-Bugs-To: 'nvda-translations@freelists.org'\n"
1010
"POT-Creation-Date: 2019-04-22 12:29+0200\n"
11-
"PO-Revision-Date: 2019-04-22 12:48+0200\n"
11+
"PO-Revision-Date: 2019-04-27 09:30+0200\n"
1212
"Last-Translator: Rémy Ruiz <remyruiz@gmail.com>\n"
1313
"Language-Team: \n"
1414
"Language: fr\n"
@@ -80,11 +80,11 @@ msgstr "Taille de la tête"
8080

8181
#: addon\synthDrivers\ibmeci.py:92
8282
msgid "Roughness"
83-
msgstr "Enrouement"
83+
msgstr "Raucité"
8484

8585
#: addon\synthDrivers\ibmeci.py:92
8686
msgid "Breathiness"
87-
msgstr "Respiration"
87+
msgstr "Souffle"
8888

8989
#: addon\synthDrivers\ibmeci.py:92
9090
msgid "Enable backquote voice &tags"
@@ -227,7 +227,7 @@ msgstr "Erreur lors de la copie des fichiers IBMTTS"
227227
#: addon\installTasks.py:9
228228
msgid ""
229229
"The synthesizer won't be available until you set IBMTTS files. NVDA won't "
230-
"show this synthesizer in teh synthesizers list because you need to set the "
230+
"show this synthesizer in teh synthesizers lists because you need to set the "
231231
"IBMTTS files location first.\n"
232232
"\tTo do it open the NVDA settings dialog, select IBMTTS category and use the "
233233
"\"Browse for IBMTTS library\" button to select the IBMTTS files folder.\n"

addon/synthDrivers/_ibmeci.py

Lines changed: 102 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -14,38 +14,55 @@
1414
# Python 3 import
1515
import queue
1616

17-
import time, threading
18-
import nvwave, config, languageHandler, addonHandler
17+
import threading, time
18+
import config, languageHandler, nvwave, addonHandler
1919
from logHandler import log
2020
import _settingsDB
2121

2222
addonHandler.initTranslation()
2323

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)
2449

25-
gb = BytesIO()
26-
speaking=False
2750
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()
3158
samples=3300
3259
buffer = create_string_buffer(samples*2)
33-
bgQueue = queue.Queue()
34-
synthQueue = queue.Queue()
60+
61+
3562
stopped = threading.Event()
3663
started = threading.Event()
3764
param_event = threading.Event()
38-
Callback = WINFUNCTYPE(c_int, c_int, c_int, c_int, c_void_p)
3965

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
4966

5067
lastindex=0
5168
langs={'esp': (131072, _('Castilian Spanish'), 'es_ES', 'es'),
@@ -80,40 +97,41 @@
8097
dll = None
8198
handle = None
8299

83-
class eciThread(threading.Thread):
100+
class EciThread(threading.Thread):
84101
def run(self):
85102
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()
88105
msg = wintypes.MSG()
89106
user32.PeekMessageA(byref(msg), None, 0x400, 0x400, 0)
90107
(dll, handle) = eciNew()
91108
dll.eciRegisterCallback(handle, callback, None)
92109
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
95112
self.dictionaryHandle = dll.eciNewDict(handle)
96113
dll.eciSetDict(handle, self.dictionaryHandle)
97114
#0 = main dictionary
98115
if path.exists(path.join(ttsPath, "main.dic")):
99116
dll.eciLoadDict(handle, self.dictionaryHandle, 0, path.join(ttsPath, "main.dic"))
100117
if path.exists(path.join(ttsPath, "root.dic")):
101118
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)
103120
started.set()
104121
while True:
105122
user32.GetMessageA(byref(msg), 0, 0, 0)
106123
user32.TranslateMessage(byref(msg))
107124
if msg.message == WM_PROCESS:
108-
internal_process_queue()
125+
processEciQueue()
109126
elif msg.message == WM_SILENCE:
110127
speaking=False
111-
gb.seek(0)
112-
gb.truncate(0)
128+
audioStream.seek(0)
129+
audioStream.truncate(0)
113130
dll.eciStop(handle)
114131
try:
115132
while True:
116-
bgQueue.get_nowait()
133+
callbackQueue.get_nowait()
134+
callbackQueue.task_done()
117135
except:
118136
pass
119137
player.stop()
@@ -132,7 +150,7 @@ def run(self):
132150
param_event.set()
133151
elif msg.message == WM_COPYVOICE:
134152
dll.eciCopyVoice(handle, msg.wParam, 0)
135-
for i in (rate, pitch, vlm, fluctuation, hsz, rgh, bth):
153+
for i in ECIVoiceParam.params:
136154
vparams[i] = dll.eciGetVoiceParam(handle, 0, i)
137155
param_event.set()
138156
elif msg.message == WM_KILL:
@@ -142,6 +160,12 @@ def run(self):
142160
else:
143161
user32.DispatchMessageA(byref(msg))
144162

163+
def processEciQueue():
164+
lst = eciQueue.get()
165+
for (func, args) in lst:
166+
func(*args)
167+
eciQueue.task_done()
168+
145169
def eciCheck():
146170
global ttsPath, dllName, dll
147171
dllName = config.conf['ibmeci']['dllName']
@@ -181,14 +205,14 @@ def eciNew():
181205
if 'ibmtts' in config.conf['speech'] and config.conf['speech']['ibmtts']['voice'] != '':
182206
handle=eci.eciNewEx(int(config.conf['speech']['ibmtts']['voice']))
183207
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:
185209
vparams[i] = eci.eciGetVoiceParam(handle, 0, i)
186210
return eci,handle
187211

188212
@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))
192216
def setLast(lp):
193217
global lastindex
194218
lastindex = lp
@@ -212,72 +236,69 @@ def bgPlay(stri):
212236
log.error("Eloq speech failed to feed one buffer.")
213237

214238
curindex=None
239+
Callback = WINFUNCTYPE(c_int, c_int, c_int, c_int, c_void_p)
215240
@Callback
216241
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())
226247
if curindex is not None:
227-
_bgExec(setLast, curindex)
248+
_callbackExec(setLast, curindex)
228249
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:
232253
if lp != END_STRING_MARK: #end of string
233254
curindex = lp
234255
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)
239260
if curindex is not None:
240-
_bgExec(setLast, curindex)
261+
_callbackExec(setLast, curindex)
241262
curindex=None
242-
_bgExec(endStringEvent)
243-
return 1
263+
_callbackExec(endStringEvent)
264+
return ECICallbackReturn.eciDataProcessed
244265

245-
class BgThread(threading.Thread):
266+
class CallbackThread(threading.Thread):
246267
def __init__(self):
247268
threading.Thread.__init__(self)
248269
self.setDaemon(True)
249270

250271
def run(self):
251272
try:
252273
while True:
253-
func, args, kwargs = bgQueue.get()
274+
func, args, kwargs = callbackQueue.get()
254275
if not func:
255276
break
256277
func(*args, **kwargs)
257-
bgQueue.task_done()
278+
callbackQueue.task_done()
258279
except:
259-
log.error("bgThread.run", exc_info=True)
280+
log.error("CallbackThread.run", exc_info=True)
260281

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))
264285

265286
def initialize():
266-
global eci, player, bgt, dll, handle
287+
global callbackThread, dll, eciThread, handle, player
267288
player = nvwave.WavePlayer(1, 11025, 16, outputDevice=config.conf["speech"]["outputDevice"])
268289
if not eciCheck():
269290
raise RuntimeError("No IBMTTS synthesizer available")
270-
eci = eciThread()
271-
eci.start()
291+
eciThread = EciThread()
292+
eciThread.start()
272293
started.wait()
273294
started.clear()
274-
bgt = BgThread()
275-
bgt.start()
295+
callbackThread = CallbackThread()
296+
callbackThread.start()
276297

277298
def speak(text):
278299
#Sometimes the synth slows down for one string of text. Why?
279300
#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)
281302
dll.eciAddText(handle, text)
282303

283304
def index(x):
@@ -297,46 +318,43 @@ def synth():
297318
dll.eciSynthesize(handle)
298319

299320
def stop():
300-
user32.PostThreadMessageA(tid, WM_SILENCE, 0, 0)
321+
user32.PostThreadMessageA(eciThreadId, WM_SILENCE, 0, 0)
301322

302323
def pause(switch):
303324
player.pause(switch)
304325

305326
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)
308329
stopped.wait()
309330
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()
313334
player.close()
314335
player = None
315-
bgt = None
336+
callbackThread = None
337+
eciThread= None
338+
dll=None
316339

317340
def set_voice(vl):
318-
user32.PostThreadMessageA(tid, WM_PARAM, int(vl), 9)
341+
user32.PostThreadMessageA(eciThreadId, WM_PARAM, int(vl), ECIParam.eciLanguageDialect)
319342

320343
def getVParam(pr):
321344
return vparams[pr]
322345

323346
def setVParam(pr, vl):
324-
user32.PostThreadMessageA(tid, WM_VPARAM, pr, vl)
347+
user32.PostThreadMessageA(eciThreadId, WM_VPARAM, pr, vl)
325348
param_event.wait()
326349
param_event.clear()
327350

328351
def setVariant(v):
329-
user32.PostThreadMessageA(tid, WM_COPYVOICE, v, 0)
352+
user32.PostThreadMessageA(eciThreadId, WM_COPYVOICE, v, 0)
330353
param_event.wait()
331354
param_event.clear()
332355

333356
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)
340358

341359
def eciVersion():
342360
ptr=" "
@@ -362,6 +380,7 @@ def endStringEvent():
362380
speaking = False
363381
threading.Timer(0.3, idlePlayer).start()
364382

383+
365384
def idlePlayer():
366385
global player, speaking, endMarkersCount
367386
if not speaking and endMarkersCount == 0: player.idle()

0 commit comments

Comments
 (0)