# -*- coding: UTF-8 -*- #synthDrivers/_espeak.py #A part of NonVisual Desktop Access (NVDA) #Copyright (C) 2007-2012 NV Access Limited, Peter Vágner #This file is covered by the GNU General Public License. #See the file COPYING for more details. import time import nvwave import threading import Queue from ctypes import * import config import globalVars from logHandler import log import os import codecs isSpeaking = False lastIndex = None bgThread=None bgQueue = None player = None espeakDLL=None #Parameter bounds minRate=80 maxRate=450 minPitch=0 maxPitch=99 #event types espeakEVENT_LIST_TERMINATED=0 espeakEVENT_WORD=1 espeakEVENT_SENTENCE=2 espeakEVENT_MARK=3 espeakEVENT_PLAY=4 espeakEVENT_END=5 espeakEVENT_MSG_TERMINATED=6 espeakEVENT_PHONEME=7 #position types POS_CHARACTER=1 POS_WORD=2 POS_SENTENCE=3 #output types AUDIO_OUTPUT_PLAYBACK=0 AUDIO_OUTPUT_RETRIEVAL=1 AUDIO_OUTPUT_SYNCHRONOUS=2 AUDIO_OUTPUT_SYNCH_PLAYBACK=3 #synth flags espeakCHARS_AUTO=0 espeakCHARS_UTF8=1 espeakCHARS_8BIT=2 espeakCHARS_WCHAR=3 espeakSSML=0x10 espeakPHONEMES=0x100 espeakENDPAUSE=0x1000 espeakKEEP_NAMEDATA=0x2000 #speech parameters espeakSILENCE=0 espeakRATE=1 espeakVOLUME=2 espeakPITCH=3 espeakRANGE=4 espeakPUNCTUATION=5 espeakCAPITALS=6 espeakWORDGAP=7 espeakOPTIONS=8 # reserved for misc. options. not yet used espeakINTONATION=9 espeakRESERVED1=10 espeakRESERVED2=11 #error codes EE_OK=0 #EE_INTERNAL_ERROR=-1 #EE_BUFFER_FULL=1 #EE_NOT_FOUND=2 class espeak_EVENT_id(Union): _fields_=[ ('number',c_int), ('name',c_char_p), ('string',c_char*8), ] class espeak_EVENT(Structure): _fields_=[ ('type',c_int), ('unique_identifier',c_uint), ('text_position',c_int), ('length',c_int), ('audio_position',c_int), ('sample',c_int), ('user_data',c_void_p), ('id',espeak_EVENT_id), ] class espeak_VOICE(Structure): _fields_=[ ('name',c_char_p), ('languages',c_char_p), ('identifier',c_char_p), ('gender',c_byte), ('age',c_byte), ('variant',c_byte), ('xx1',c_byte), ('score',c_int), ('spare',c_void_p), ] def __eq__(self, other): return isinstance(other, type(self)) and addressof(self) == addressof(other) t_espeak_callback=CFUNCTYPE(c_int,POINTER(c_short),c_int,POINTER(espeak_EVENT)) @t_espeak_callback def callback(wav,numsamples,event): try: global player, isSpeaking, lastIndex if not isSpeaking: return 1 for e in event: if e.type==espeakEVENT_MARK: lastIndex=int(e.id.name) elif e.type==espeakEVENT_LIST_TERMINATED: break if not wav: player.idle() isSpeaking = False return 0 if numsamples > 0: try: player.feed(string_at(wav, numsamples * sizeof(c_short))) except: log.debugWarning("Error feeding audio to nvWave",exc_info=True) return 0 except: log.error("callback", exc_info=True) class BgThread(threading.Thread): def __init__(self): threading.Thread.__init__(self) self.setDaemon(True) def run(self): global isSpeaking while True: func, args, kwargs = bgQueue.get() if not func: break try: func(*args, **kwargs) except: log.error("Error running function from queue", exc_info=True) bgQueue.task_done() def _execWhenDone(func, *args, **kwargs): global bgQueue # This can't be a kwarg in the function definition because it will consume the first non-keywor dargument which is meant for func. mustBeAsync = kwargs.pop("mustBeAsync", False) if mustBeAsync or bgQueue.unfinished_tasks != 0: # Either this operation must be asynchronous or There is still an operation in progress. # Therefore, run this asynchronously in the background thread. bgQueue.put((func, args, kwargs)) else: func(*args, **kwargs) def _speak(text): global isSpeaking uniqueID=c_int() isSpeaking = True flags = espeakCHARS_WCHAR | espeakSSML | espeakPHONEMES return espeakDLL.espeak_Synth(text,0,0,0,0,flags,byref(uniqueID),0) def speak(text): global bgQueue _execWhenDone(_speak, text, mustBeAsync=True) def stop(): global isSpeaking, bgQueue, lastIndex # Kill all speech from now. # We still want parameter changes to occur, so requeue them. params = [] try: while True: item = bgQueue.get_nowait() if item[0] != _speak: params.append(item) bgQueue.task_done() except Queue.Empty: # Let the exception break us out of this loop, as queue.empty() is not reliable anyway. pass for item in params: bgQueue.put(item) isSpeaking = False player.stop() lastIndex=None def pause(switch): global player player.pause(switch) def setParameter(param,value,relative): _execWhenDone(espeakDLL.espeak_SetParameter,param,value,relative) def getParameter(param,current): return espeakDLL.espeak_GetParameter(param,current) def getVoiceList(): voices=espeakDLL.espeak_ListVoices(None) voiceList=[] for voice in voices: if not voice: break voiceList.append(voice.contents) return voiceList def getCurrentVoice(): voice = espeakDLL.espeak_GetCurrentVoice() if voice: return voice.contents else: return None def setVoice(voice): # For some weird reason, espeak_EspeakSetVoiceByProperties throws an integer divide by zero exception. setVoiceByName(voice.identifier) def setVoiceByName(name): _execWhenDone(espeakDLL.espeak_SetVoiceByName,name) def _setVoiceAndVariant(voice=None, variant=None): res = getCurrentVoice().identifier.split("+") if not voice: voice = res[0] if not variant: if len(res) == 2: variant = res[1] else: variant = "none" if variant == "none": espeakDLL.espeak_SetVoiceByName(voice) else: try: espeakDLL.espeak_SetVoiceByName("%s+%s" % (voice, variant)) except: espeakDLL.espeak_SetVoiceByName(voice) def setVoiceAndVariant(voice=None, variant=None): _execWhenDone(_setVoiceAndVariant, voice=voice, variant=variant) def _setVoiceByLanguage(lang): v=espeak_VOICE() lang=lang.replace('_','-') v.languages=lang try: espeakDLL.espeak_SetVoiceByProperties(byref(v)) except: v.languages="en" espeakDLL.espeak_SetVoiceByProperties(byref(v)) def setVoiceByLanguage(lang): _execWhenDone(_setVoiceByLanguage, lang) def espeak_errcheck(res, func, args): if res != EE_OK: raise RuntimeError("%s: code %d" % (func.__name__, res)) return res def initialize(): global espeakDLL, bgThread, bgQueue, player espeakDLL=cdll.LoadLibrary(r"synthDrivers\espeak.dll") espeakDLL.espeak_Info.restype=c_char_p espeakDLL.espeak_Synth.errcheck=espeak_errcheck espeakDLL.espeak_SetVoiceByName.errcheck=espeak_errcheck espeakDLL.espeak_SetVoiceByProperties.errcheck=espeak_errcheck espeakDLL.espeak_SetParameter.errcheck=espeak_errcheck espeakDLL.espeak_Terminate.errcheck=espeak_errcheck espeakDLL.espeak_ListVoices.restype=POINTER(POINTER(espeak_VOICE)) espeakDLL.espeak_GetCurrentVoice.restype=POINTER(espeak_VOICE) espeakDLL.espeak_SetVoiceByName.argtypes=(c_char_p,) sampleRate=espeakDLL.espeak_Initialize(AUDIO_OUTPUT_SYNCHRONOUS,300, os.path.abspath("synthDrivers"),0) if sampleRate<0: raise OSError("espeak_Initialize %d"%sampleRate) player = nvwave.WavePlayer(channels=1, samplesPerSec=sampleRate, bitsPerSample=16, outputDevice=config.conf["speech"]["outputDevice"]) espeakDLL.espeak_SetSynthCallback(callback) bgQueue = Queue.Queue() bgThread=BgThread() bgThread.start() def terminate(): global bgThread, bgQueue, player, espeakDLL stop() bgQueue.put((None, None, None)) bgThread.join() espeakDLL.espeak_Terminate() bgThread=None bgQueue=None player.close() player=None espeakDLL=None def info(): return espeakDLL.espeak_Info() def getVariantDict(): dir='synthDrivers\\espeak-data\\voices\\!v' # Translators: name of the default espeak varient. variantDict={"none": pgettext("espeakVarient", "none")} for fileName in os.listdir(dir): if os.path.isfile("%s\\%s"%(dir,fileName)): file=codecs.open("%s\\%s"%(dir,fileName)) for line in file: if line.startswith('name '): temp=line.split(" ") if len(temp) ==2: name=temp[1].rstrip() break name=None file.close() if name is not None: variantDict[fileName]=name return variantDict