1# -*- coding: utf-8; mode: Python; indent-tabs-mode: t -*-
2# Copyright (C) 2010, 2011, 2012, 2013, 2018, 2019  Olga Yakovleva <yakovleva.o.v@gmail.com>
3# Copyright (C) 2019  Beqa Gozalishvili <beqaprogger@gmail.com>
4
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 2 of the License, or
8# (at your option) any later version.
9
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14
15# You should have received a copy of the GNU General Public License
16# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18import sys
19import os.path
20try:
21	import Queue
22except ImportError:
23	import queue as Queue
24from collections import OrderedDict,defaultdict
25import threading
26import ctypes
27from ctypes import c_char_p,c_wchar_p,c_void_p,c_short,c_int,c_uint,c_double,POINTER,Structure,sizeof,string_at,CFUNCTYPE,byref,cast
28import re
29import copy
30
31try:
32	from io import StringIO
33except ImportError:
34	from StringIO import StringIO
35
36PY2 = sys.version_info[0] == 2
37
38import config
39import globalVars
40import nvwave
41from logHandler import log
42import synthDriverHandler
43import speech
44import languageHandler
45import addonHandler
46
47api_version_0=(0,0,0)
48api_version_2019_3=(2019,3,0)
49api_version=api_version_0
50try:
51	import AddonAPIVersion
52	api_version=addonAPIVersion.CURRENT
53except ImportError:
54	pass
55
56if PY2:
57	# Convert __file__ to unicode to avoid problems on Windows
58	# https://stackoverflow.com/questions/35117936/save-file-with-russian-letters-in-the-file-name
59	fs_encoding = sys.getfilesystemencoding()
60	module_dir=os.path.dirname(__file__.decode(fs_encoding))
61else:
62	module_dir=os.path.dirname(__file__)
63lib_path=os.path.join(module_dir,"RHVoice.dll")
64config_path=os.path.join(globalVars.appArgs.configPath,"RHVoice-config")
65
66try:
67	basestring
68	unicode
69except NameError:
70	basestring = str
71	unicode = str
72
73class nvda_notification_wrapper(object):
74	def __init__(self,name,lst):
75		self.name=name
76		try:
77			self.target=getattr(synthDriverHandler,name)
78			lst.append(self.target)
79		except AttributeError:
80			self.target=None
81
82	def is_supported(self):
83		return (self.target is not None)
84
85	def notify(self,synth,**kw0):
86		if not self.is_supported():
87			return;
88		kw={"synth":synth}
89		kw.update(kw0)
90		self.target.notify(**kw)
91
92nvda_notifications=[]
93nvda_notification_synthIndexReached=nvda_notification_wrapper("synthIndexReached",nvda_notifications)
94nvda_notification_synthDoneSpeaking=nvda_notification_wrapper("synthDoneSpeaking",nvda_notifications)
95
96data_addon_name_pattern=re.compile("^RHVoice-.*(voice|language).*")
97
98class RHVoice_tts_engine_struct(Structure):
99	pass
100RHVoice_tts_engine=POINTER(RHVoice_tts_engine_struct)
101
102class RHVoice_message_struct(Structure):
103	pass
104RHVoice_message=POINTER(RHVoice_message_struct)
105
106class RHVoice_callback_types:
107	set_sample_rate=CFUNCTYPE(c_int,c_int,c_void_p)
108	play_speech=CFUNCTYPE(c_int,POINTER(c_short),c_uint,c_void_p)
109	process_mark=CFUNCTYPE(c_int,c_char_p,c_void_p)
110	word_starts=CFUNCTYPE(c_int,c_uint,c_uint,c_void_p)
111	word_ends=CFUNCTYPE(c_int,c_uint,c_uint,c_void_p)
112	sentence_starts=CFUNCTYPE(c_int,c_uint,c_uint,c_void_p)
113	sentence_ends=CFUNCTYPE(c_int,c_uint,c_uint,c_void_p)
114	play_audio=CFUNCTYPE(c_int,c_char_p,c_void_p)
115	done=CFUNCTYPE(None,c_void_p)
116
117class RHVoice_callbacks(Structure):
118	_fields_=[("set_sample_rate",RHVoice_callback_types.set_sample_rate),
119				  ("play_speech",RHVoice_callback_types.play_speech),
120			  ("process_mark",RHVoice_callback_types.process_mark),
121			  ("word_starts",RHVoice_callback_types.word_starts),
122			  ("word_ends",RHVoice_callback_types.word_ends),
123			  ("sentence_starts",RHVoice_callback_types.sentence_starts),
124			  ("sentence_ends",RHVoice_callback_types.sentence_ends),
125			  ("play_audio",RHVoice_callback_types.play_audio),
126			  ("done",RHVoice_callback_types.done)]
127
128class RHVoice_init_params(Structure):
129	_fields_=[("data_path",c_char_p),
130			  ("config_path",c_char_p),
131			  ("resource_paths",POINTER(c_char_p)),
132			  ("callbacks",RHVoice_callbacks),
133			  ("options",c_uint)]
134
135class RHVoice_message_type:
136	text=0
137	ssml=1
138	characters=2
139
140class RHVoice_voice_gender:
141	unknown=0
142	male=1
143	female=2
144
145class RHVoice_voice_info(Structure):
146	_fields_=[("language",c_char_p),
147			  ("name",c_char_p),
148			  ("gender",c_int),
149			  ("country",c_char_p)]
150
151class RHVoice_punctuation_mode:
152	default=0
153	none=1
154	all=2
155	some=3
156
157class RHVoice_capitals_mode:
158	default=0
159	off=1
160	word=2
161	pitch=3
162	sound=4
163
164class RHVoice_synth_flag:
165	dont_clip_rate=1
166
167class RHVoice_synth_params(Structure):
168	_fields_=[("voice_profile",c_char_p),
169			  ("absolute_rate",c_double),
170			  ("absolute_pitch",c_double),
171			  ("absolute_volume",c_double),
172			  ("relative_rate",c_double),
173			  ("relative_pitch",c_double),
174			  ("relative_volume",c_double),
175			  ("punctuation_mode",c_int),
176			  ("punctuation_list",c_char_p),
177			  ("capitals_mode",c_int),
178			  ("flags",c_int)]
179
180def load_tts_library():
181	lib=ctypes.CDLL(lib_path)
182	lib.RHVoice_get_version.restype=c_char_p
183	lib.RHVoice_new_tts_engine.argtypes=(POINTER(RHVoice_init_params),)
184	lib.RHVoice_new_tts_engine.restype=RHVoice_tts_engine
185	lib.RHVoice_delete_tts_engine.argtypes=(RHVoice_tts_engine,)
186	lib.RHVoice_delete_tts_engine.restype=None
187	lib.RHVoice_get_number_of_voices.argtypes=(RHVoice_tts_engine,)
188	lib.RHVoice_get_number_of_voices.restype=c_uint
189	lib.RHVoice_get_voices.argtypes=(RHVoice_tts_engine,)
190	lib.RHVoice_get_voices.restype=POINTER(RHVoice_voice_info)
191	lib.RHVoice_get_number_of_voice_profiles.argtypes=(RHVoice_tts_engine,)
192	lib.RHVoice_get_number_of_voice_profiles.restype=c_uint
193	lib.RHVoice_get_voice_profiles.argtypes=(RHVoice_tts_engine,)
194	lib.RHVoice_get_voice_profiles.restype=POINTER(c_char_p)
195	lib.RHVoice_are_languages_compatible.argtypes=(RHVoice_tts_engine,c_char_p,c_char_p)
196	lib.RHVoice_are_languages_compatible.restype=c_int
197	lib.RHVoice_new_message.argtypes=(RHVoice_tts_engine,c_char_p,c_uint,c_int,POINTER(RHVoice_synth_params),c_void_p)
198	lib.RHVoice_new_message.restype=RHVoice_message
199	lib.RHVoice_delete_message.arg_types=(RHVoice_message,)
200	lib.RHVoice_delete_message.restype=None
201	lib.RHVoice_speak.argtypes=(RHVoice_message,)
202	lib.RHVoice_speak.restype=c_int
203	return lib
204
205def escape_text(text):
206	parts=list()
207	for c in text:
208		if c.isspace():
209			part=u"&#{};".format(ord(c))
210		elif c=="<":
211			part=u"&lt;"
212		elif c==">":
213			part=u"&gt;"
214		elif c=="&":
215			part=u"&amp;"
216		elif c=="'":
217			part=u"&apos;"
218		elif c=='"':
219			part=u"&quot;"
220		else:
221			part=c
222		parts.append(part)
223	return u"".join(parts)
224
225class audio_player(object):
226	def __init__(self,synth,cancel_flag):
227		self.__synth=synth
228		self.__cancel_flag=cancel_flag
229		self.__sample_rate=0
230		self.__players={}
231		self.__lock=threading.Lock()
232		self.__closed=False
233
234	def do_get_player(self):
235		if self.__closed:
236			return None
237		if self.__sample_rate==0:
238			return None
239		player=self.__players.get(self.__sample_rate,None)
240		if player is None:
241			player=nvwave.WavePlayer(channels=1,samplesPerSec=self.__sample_rate,bitsPerSample=16,outputDevice=config.conf["speech"]["outputDevice"])
242			self.__players[self.__sample_rate]=player
243		return player
244
245	def get_player(self):
246		with self.__lock:
247			return self.do_get_player()
248
249	def close(self):
250		with self.__lock:
251			self.__closed=True
252			players=list(self.__players.values())
253		for p in players:
254			p.close()
255
256	def set_sample_rate(self,sr):
257		with self.__lock:
258			if self.__closed:
259				return
260			if self.__sample_rate==0:
261				self.__sample_rate=sr
262				return
263			if self.__sample_rate==sr:
264				return
265			old_player=self.__players.get(self.__sample_rate,None)
266		if old_player is not None:
267			old_player.idle()
268		with self.__lock:
269			self.__sample_rate=sr
270
271	def do_play(self,data,index=None):
272		player=self.get_player()
273		if player is not None and not self.__cancel_flag.is_set():
274			if index is None:
275				player.feed(data)
276			else:
277				player.feed(data,onDone=lambda synth=self.__synth,next_index=index: nvda_notification_synthIndexReached.notify(synth,index=next_index))
278			if self.__cancel_flag.is_set():
279				player.stop()
280
281	def play(self,data):
282		if self.__prev_data is None:
283			self.__prev_data=data
284			return
285		self.do_play(self.__prev_data)
286		self.__prev_data=data
287
288	def stop(self):
289		player=self.get_player()
290		if player is not None:
291			player.stop()
292
293	def pause(self,switch):
294		player=self.get_player()
295		if player is not None:
296			player.pause(switch)
297
298	def idle(self):
299		player=self.get_player()
300		if player is not None:
301			player.idle()
302
303	def on_new_message(self):
304		self.__prev_data=None
305
306	def on_done(self):
307		if self.__prev_data is None:
308			return
309		self.do_play(self.__prev_data)
310		self.__prev_data=None
311
312	def on_index(self,index):
313		data=self.__prev_data
314		self.__prev_data=None
315		if data is None:
316			data=b""
317		self.do_play(data,index)
318
319class sample_rate_callback(object):
320	def __init__(self,lib,player):
321		self.__lib=lib
322		self.__player=player
323
324	def __call__(self,sample_rate,user_data):
325		try:
326			self.__player.set_sample_rate(sample_rate)
327			return 1
328		except:
329			log.error("RHVoice sample rate callback",exc_info=True)
330			return 0
331
332class speech_callback(object):
333	def __init__(self,lib,player,cancel_flag):
334		self.__lib=lib
335		self.__player=player
336		self.__cancel_flag=cancel_flag
337
338	def __call__(self,samples,count,user_data):
339		try:
340			if self.__cancel_flag.is_set():
341				return 0
342			try:
343				self.__player.play(string_at(samples,count*sizeof(c_short)))
344			except:
345				log.debugWarning("Error feeding audio to nvWave",exc_info=True)
346			return 1
347		except:
348			log.error("RHVoice speech callback",exc_info=True)
349			return 0
350
351class mark_callback(object):
352	def __init__(self,lib,player):
353		self.__lib=lib
354		self.__player=player
355		self.__lock=threading.Lock()
356		self.__index=None
357
358	@property
359	def index(self):
360		with self.__lock:
361			return self.__index
362
363	@index.setter
364	def index(self,value):
365		with self.__lock:
366			self.__index=value
367
368	def __call__(self,name,user_data):
369		try:
370			index=int(name)
371			if nvda_notification_synthIndexReached.is_supported():
372				self.__player.on_index(index)
373			else:
374				self.index=index
375			return 1
376		except:
377			log.error("RHVoice mark callback",exc_info=True)
378			return 0
379
380class done_callback(object):
381	def __init__(self,synth,lib,player,cancel_flag):
382		self.__synth=synth
383		self.__lib=lib
384		self.__player=player
385		self.__cancel_flag=cancel_flag
386
387	def __call__(self,user_data):
388		try:
389			if self.__cancel_flag.is_set():
390				return
391			self.__player.on_done()
392			self.__player.idle()
393			if self.__cancel_flag.is_set():
394				return
395			nvda_notification_synthDoneSpeaking.notify(self.__synth)
396		except:
397			log.error("RHVoice done callback",exc_info=True)
398
399class speak_text(object):
400	def __init__(self,lib,tts_engine,text,cancel_flag,player):
401		self.__lib=lib
402		self.__tts_engine=tts_engine
403		self.__text=text.encode("utf-8", errors="ignore")
404		self.__cancel_flag=cancel_flag
405		self.__player=player
406		self.__synth_params=RHVoice_synth_params(voice_profile=None,
407												 absolute_rate=0,
408												 relative_rate=1,
409												 absolute_pitch=0,
410												 relative_pitch=1,
411												 absolute_volume=0,
412												 relative_volume=1,
413												 punctuation_mode=RHVoice_punctuation_mode.default,
414												 punctuation_list=None,
415												 capitals_mode=RHVoice_capitals_mode.default,
416												 flags=0)
417
418	def set_rate(self,rate):
419		self.__synth_params.absolute_rate=rate/50.0-1
420
421	def set_pitch(self,pitch):
422		self.__synth_params.absolute_pitch=pitch/50.0-1
423
424	def set_volume(self,volume):
425		self.__synth_params.absolute_volume=volume/50.0-1
426
427	def set_voice_profile(self,name):
428		self.__synth_params.voice_profile=name.encode("utf-8")
429
430	def configure_rate_boost(self,flag):
431		if not flag:
432			return
433		self.__synth_params.relative_rate=2.5
434		self.__synth_params.flags|=RHVoice_synth_flag.dont_clip_rate
435
436	def __call__(self):
437		if self.__cancel_flag.is_set():
438			return
439		msg=self.__lib.RHVoice_new_message(self.__tts_engine,
440										   self.__text,
441										   len(self.__text),
442										   RHVoice_message_type.ssml,
443										   byref(self.__synth_params),
444										   None)
445		if msg:
446			self.__player.on_new_message()
447			self.__lib.RHVoice_speak(msg)
448			self.__player.idle()
449			self.__lib.RHVoice_delete_message(msg)
450
451class TTSThread(threading.Thread):
452	def __init__(self,tts_queue):
453		self.__queue=tts_queue
454		threading.Thread.__init__(self)
455		self.daemon=True
456
457	def run(self):
458		while True:
459			try:
460				task=self.__queue.get()
461				if task is None:
462					break
463				else:
464					task()
465			except:
466				log.error("RHVoice: error while executing a tts task",exc_info=True)
467
468class nvda_speak_argument_converter(object):
469	def outputs_element(self):
470		return True
471
472	def get_ssml_tag_name(self):
473		raise NotImplementedError
474
475	def get_ssml_attributes(self):
476		return {}
477
478	def get_text(self):
479		return u""
480
481	def write(self,out):
482		txt=self.get_text()
483		if txt:
484			out.write(txt)
485			return
486		if not self.outputs_element():
487			for child in self.children:
488				child.write(out)
489			return
490		tag=self.get_ssml_tag_name()
491		out.write(u'<')
492		out.write(tag)
493		a=self.get_ssml_attributes()
494		for k,v in a.items():
495			out.write(u' {}="{}"'.format(k,v))
496		if len(self.children)==0:
497			out.write(u'/>')
498			return
499		out.write(u'>')
500		for child in self.children:
501			child.write(out)
502		out.write(u'</')
503		out.write(tag)
504		out.write(u'>')
505
506class nvda_speech_item_converter(nvda_speak_argument_converter):
507	def check_item_class(self):
508		try:
509			cls=self.get_item_class()
510			return True
511		except AttributeError:
512			return False
513
514	def accepts(self,item):
515		return isinstance(item,self.get_item_class())
516
517	def get_item_class(self):
518		raise NotImplementedError
519
520	def converts_speech_command(self):
521		return False
522
523	def on_create(self):
524		pass
525
526	def create(self,synthDriver,item=None):
527		obj=type(self)()
528		obj.synthDriver=synthDriver
529		obj.item=item
530		obj.children=[]
531		obj.on_create()
532		return obj
533
534	def converts_mode_command(self):
535		return False
536
537class nvda_speech_command_converter(nvda_speech_item_converter):
538	def converts_speech_command(self):
539		return True
540
541class nvda_text_item_converter(nvda_speech_item_converter):
542	def get_item_class(self):
543		return basestring
544
545	def outputs_element(self):
546		return False
547
548	def get_text(self):
549		return escape_text(unicode(self.item))
550
551class nvda_index_command_converter(nvda_speech_command_converter):
552	def get_item_class(self):
553		return speech.IndexCommand
554
555	def get_ssml_tag_name(self):
556		return u"mark"
557
558	def get_ssml_attributes(self):
559		return {"name":self.item.index}
560
561class nvda_speech_mode_command_converter(nvda_speech_command_converter):
562	def converts_mode_command(self):
563		return True
564
565	def is_default(self):
566		return (self.item is None)
567
568	def outputs_element(self):
569		return (not self.is_default())
570
571	def is_empty(self):
572		n=len(self.children)
573		if n==0:
574			return True
575		if n>1:
576			return False
577		if not self.children[0].converts_mode_command():
578			return False
579		return self.children[0].is_empty()
580
581	def on_clone(self,src):
582		pass
583
584	def clone(self,item=None):
585		obj=type(self)()
586		obj.children=[]
587		obj.synthDriver=self.synthDriver
588		if item:
589			obj.item=item
590			obj.on_create()
591		else:
592			obj.item=self.item
593			obj.on_clone(self)
594		if len(self.children)>0 and self.children[-1].converts_mode_command():
595			obj.children.append(self.children[-1].clone())
596		return obj
597
598class nvda_char_mode_command_converter(nvda_speech_mode_command_converter):
599	def get_item_class(self):
600		return speech.CharacterModeCommand
601
602	def is_default(self):
603		return not (self.item is not None and self.item.state)
604
605	def get_ssml_tag_name(self):
606		return u"say-as"
607
608	def get_ssml_attributes(self):
609		return {"interpret-as":"characters"}
610
611class nvda_lang_change_command_converter(nvda_speech_mode_command_converter):
612	def get_item_class(self):
613		return speech.LangChangeCommand
614
615	def get_ssml_tag_name(self):
616		return u"voice"
617
618	def get_ssml_attributes(self):
619		return {"xml:lang":self.lang}
620
621	def is_default(self):
622		return (self.lang is None)
623
624	def on_create(self):
625		self.lang=None
626		if self.item is None:
627			return
628		if not self.item.lang:
629			return
630		lang="_".join(self.item.lang.split("_")[:2])
631		if lang not in self.synthDriver._SynthDriver__languages:
632			return
633		if self.synthDriver._SynthDriver__languages_match(lang,self.synthDriver._SynthDriver__voice_languages[self.synthDriver._SynthDriver__profile.split("+")[0]]):
634			return
635		self.lang=lang.replace("_","-")
636
637	def on_clone(self,src):
638		self.lang=src.lang
639
640class nvda_prosody_command_converter(nvda_speech_mode_command_converter):
641	def get_ssml_tag_name(self):
642		return u"prosody"
643
644	def get_ssml_attribute_name(self):
645		raise NotImplementedError
646
647	def get_ssml_attributes(self):
648		value="{}%".format(int(round(self.item.multiplier*100.0)))
649		return {self.get_ssml_attribute_name():value}
650
651	def is_default(self):
652		return (self.item is None or self.item.multiplier==1)
653
654class nvda_pitch_command_converter(nvda_prosody_command_converter):
655	def get_item_class(self):
656		return speech.PitchCommand
657
658	def get_ssml_attribute_name(self):
659		return "pitch"
660
661class nvda_volume_command_converter(nvda_prosody_command_converter):
662	def get_item_class(self):
663		return speech.VolumeCommand
664
665	def get_ssml_attribute_name(self):
666		return "volume"
667
668class nvda_rate_command_converter(nvda_prosody_command_converter):
669	def get_item_class(self):
670		return speech.RateCommand
671
672	def get_ssml_attribute_name(self):
673		return "rate"
674
675all_speech_item_converters=[nvda_lang_change_command_converter(),nvda_pitch_command_converter(),nvda_rate_command_converter(),nvda_volume_command_converter(),nvda_char_mode_command_converter(),nvda_index_command_converter(),nvda_text_item_converter()]
676speech_item_converters=[cnv for cnv in all_speech_item_converters if cnv.check_item_class()]
677speech_command_converters=[cnv for cnv in speech_item_converters if cnv.converts_speech_command()]
678mode_command_converters=[cnv for cnv in speech_command_converters if cnv.converts_mode_command()]
679content_item_converters=[cnv for cnv in speech_item_converters if not cnv.converts_mode_command()]
680
681class nvda_speech_sequence_converter(nvda_speak_argument_converter):
682	def __init__(self,synthDriver):
683		self.synthDriver=synthDriver
684		self.children=[]
685		self.current=self
686		for cnv in mode_command_converters:
687			self.current.children.append(cnv.create(self.synthDriver))
688			self.current=self.current.children[-1]
689
690	def append(self,item):
691		for cnv in content_item_converters:
692			if cnv.accepts(item):
693				self.current.children.append(cnv.create(self.synthDriver,item))
694				return
695		found=False
696		p=self
697		while p.children and p.children[-1].converts_mode_command():
698			n=p.children[-1]
699			if found:
700				self.current=n
701			elif n.accepts(item):
702				found=True
703				if n.is_empty():
704					n.item=item
705					n.on_create()
706					return
707				else:
708					n=n.clone(item)
709					p.children.append(n)
710					self.current=n
711			p=n
712		if not found:
713			log.debugWarning(u"RHVoice: unsupported item: {}".format(item))
714
715	def get_ssml_tag_name(self):
716		return u"speak"
717
718class SynthDriver(synthDriverHandler.SynthDriver):
719	name="RHVoice"
720	description="RHVoice"
721
722	supportedSettings=[synthDriverHandler.SynthDriver.VoiceSetting(),
723					   synthDriverHandler.SynthDriver.RateSetting(),
724					   synthDriverHandler.SynthDriver.PitchSetting(),
725					   synthDriverHandler.SynthDriver.VolumeSetting()]
726	if hasattr(synthDriverHandler.SynthDriver,"RateBoostSetting"):
727		supportedSettings.append(synthDriverHandler.SynthDriver.RateBoostSetting())
728
729	if api_version>=api_version_2019_3:
730		supportedCommands=frozenset([cnv.get_item_class() for cnv in speech_command_converters])
731		supportedNotifications=frozenset(nvda_notifications)
732
733	@classmethod
734	def check(cls):
735		return os.path.isfile(lib_path)
736
737	def __languages_match(self,code1,code2,full=True):
738		lang1=code1.split("_")
739		lang2=code2.split("_")
740		if lang1[0]!=lang2[0]:
741			return False
742		if len(lang1)<2 or len(lang2)<2:
743			return True
744		if not full:
745			return True
746		return (lang1[1]==lang2[1])
747
748	def __get_resource_paths(self):
749		paths=[]
750		for addon in addonHandler.getRunningAddons():
751			if not data_addon_name_pattern.match(addon.name):
752				continue
753			for name in ["data","langdata"]:
754				data_path=os.path.join(addon.path,name)
755				if os.path.isdir(data_path):
756					paths.append(data_path.encode("UTF-8"))
757		return paths
758
759	def __init__(self):
760		self.__lib=load_tts_library()
761		self.__cancel_flag=threading.Event()
762		self.__player=audio_player(self,self.__cancel_flag)
763		self.__sample_rate_callback=sample_rate_callback(self.__lib,self.__player)
764		self.__c_sample_rate_callback=RHVoice_callback_types.set_sample_rate(self.__sample_rate_callback)
765		self.__speech_callback=speech_callback(self.__lib,self.__player,self.__cancel_flag)
766		self.__c_speech_callback=RHVoice_callback_types.play_speech(self.__speech_callback)
767		self.__mark_callback=mark_callback(self.__lib,self.__player)
768		self.__c_mark_callback=RHVoice_callback_types.process_mark(self.__mark_callback)
769		self.__done_callback=done_callback(self,self.__lib,self.__player,self.__cancel_flag)
770		self.__c_done_callback=RHVoice_callback_types.done(self.__done_callback)
771		resource_paths=self.__get_resource_paths()
772		c_resource_paths=(c_char_p*(len(resource_paths)+1))(*(resource_paths+[None]))
773		init_params=RHVoice_init_params(None,
774										config_path.encode("utf-8"),
775										c_resource_paths,
776										RHVoice_callbacks(self.__c_sample_rate_callback,
777															  self.__c_speech_callback,
778														  self.__c_mark_callback,
779														  cast(None,RHVoice_callback_types.word_starts),
780														  cast(None,RHVoice_callback_types.word_ends),
781														  cast(None,RHVoice_callback_types.sentence_starts),
782														  cast(None,RHVoice_callback_types.sentence_ends),
783														  cast(None,RHVoice_callback_types.play_audio),
784														  self.__c_done_callback),
785										0)
786		self.__tts_engine=self.__lib.RHVoice_new_tts_engine(byref(init_params))
787		if not self.__tts_engine:
788			raise RuntimeError("RHVoice: initialization error")
789		nvda_language=languageHandler.getLanguage()
790		number_of_voices=self.__lib.RHVoice_get_number_of_voices(self.__tts_engine)
791		native_voices=self.__lib.RHVoice_get_voices(self.__tts_engine)
792		self.__voice_languages=dict()
793		self.__languages=set()
794		for i in range(number_of_voices):
795			native_voice=native_voices[i]
796			voice_language=native_voice.language.decode("utf-8")
797			if native_voice.country:
798				self.__languages.add(voice_language)
799				voice_language=voice_language+"_"+native_voice.country.decode("utf-8")
800			self.__voice_languages[native_voice.name.decode("utf-8")]=voice_language
801			self.__languages.add(voice_language)
802		self.__profile=None
803		self.__profiles=list()
804		number_of_profiles=self.__lib.RHVoice_get_number_of_voice_profiles(self.__tts_engine)
805		native_profile_names=self.__lib.RHVoice_get_voice_profiles(self.__tts_engine)
806		for i in range(number_of_profiles):
807			name=native_profile_names[i].decode("utf-8")
808			self.__profiles.append(name)
809			if (self.__profile is None) and self.__languages_match(nvda_language,self.__voice_languages[name.split("+")[0]]):
810				self.__profile=name
811		if self.__profile is None:
812			self.__profile=self.__profiles[0]
813		self.__rate=50
814		self.__pitch=50
815		self.__volume=50
816		self.__rate_boost=False
817		self.__tts_queue=Queue.Queue()
818		self.__tts_thread=TTSThread(self.__tts_queue)
819		self.__tts_thread.start()
820		log.info("Using RHVoice version {}".format(self.__lib.RHVoice_get_version()))
821
822	def terminate(self):
823		self.cancel()
824		self.__tts_queue.put(None)
825		self.__tts_thread.join()
826		self.__player.close()
827		self.__lib.RHVoice_delete_tts_engine(self.__tts_engine)
828		self.__tts_engine=None
829
830	def speak(self,speech_sequence):
831		cnv=nvda_speech_sequence_converter(self)
832		for item in speech_sequence:
833			cnv.append(item)
834		out=StringIO()
835		cnv.write(out)
836		text=out.getvalue()
837		out.close()
838		task=speak_text(self.__lib,self.__tts_engine,text,self.__cancel_flag,self.__player)
839		task.set_voice_profile(self.__profile)
840		task.set_rate(self.__rate)
841		task.set_pitch(self.__pitch)
842		task.set_volume(self.__volume)
843		task.configure_rate_boost(self.__rate_boost)
844		self.__tts_queue.put(task)
845
846	def pause(self,switch):
847		self.__player.pause(switch)
848
849	def cancel(self):
850		try:
851			while True:
852				self.__tts_queue.get_nowait()
853		except Queue.Empty:
854			self.__cancel_flag.set()
855			self.__tts_queue.put(self.__cancel_flag.clear)
856			self.__player.stop()
857
858	def _get_lastIndex(self):
859		return self.__mark_callback.index
860
861	def _get_availableVoices(self):
862		return OrderedDict((profile,synthDriverHandler.VoiceInfo(profile,profile,self.__voice_languages[profile.split("+")[0]])) for profile in self.__profiles)
863
864	def _get_language(self):
865		return self.__voice_languages[self.__profile.split("+")[0]]
866
867	def _get_rate(self):
868		return self.__rate
869
870	def _set_rate(self,rate):
871		self.__rate=max(0,min(100,rate))
872
873	def _get_pitch(self):
874		return self.__pitch
875
876	def _set_pitch(self,pitch):
877		self.__pitch=max(0,min(100,pitch))
878
879	def _get_volume(self):
880		return self.__volume
881
882	def _set_volume(self,volume):
883		self.__volume=max(0,min(100,volume))
884
885	def _get_voice(self):
886		return self.__profile
887
888	def _set_voice(self,voice):
889		if voice in self.__profiles:
890			self.__profile=voice
891
892	def _get_rateBoost(self):
893		return self.__rate_boost
894
895	def _set_rateBoost(self,flag):
896		self.__rate_boost=flag
897