# _nvdajp_jtalk.py 
# -*- coding: utf-8 -*-
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2006-2010 NVDA Contributors <http://www.nvda-project.org/>
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.

# speech engine nvdajp_jtalk
# since 2010-08-31 by Takuya Nishimoto
# based on Open JTalk (bin/open_jtalk.c) http://github.com/nishimotz/libopenjtalk/
# based on NVDA (synthDrivers/_espeak.py)

from logHandler import log
import time
import threading
import Queue
import os
import codecs
import re
import string

import nvwave
from _jtalk_core import *

_jtalk_voice_m001 = {"id": "V1", "name": "m001", "samp_rate": 16000, "fperiod":  80, "alpha": 0.42, "dir": r"synthDrivers\jtalk\voice"}
_jtalk_voice_mei  = {"id": "V2", "name": "mei", "samp_rate": 48000, "fperiod": 240, "alpha": 0.55, "dir": r"synthDrivers\jtalk\mei_normal"}
_jtalk_voices = [_jtalk_voice_m001, _jtalk_voice_mei]
voice_args = _jtalk_voice_m001

# if samp_rate==16000: normal speed = 80samples period
fperiod = 80

DEBUG_INFO = None # 1

# for mecab dic
CODE = 'shift_jis' # CODE = 'utf16'
DIC = r"synthDrivers\jtalk\dic" # DIC = r"synthDrivers\jtalk\dic-utf16"

MECAB_DLL = r"synthDrivers\jtalk\libmecab.dll"
MECABRC = r"synthDrivers\jtalk\mecabrc"
JT_DLL = r"synthDrivers\jtalk\libopenjtalk.dll"

njd = NJD()
jpcommon = JPCommon()
engine = HTS_Engine()
libjt = None
predic = None
engdic = None
logwrite = None

lastIndex = None
currIndex = None

###########################################

def jtalk_refresh():
	libjt_refresh(libjt, engine, jpcommon, njd)
	Mecab_refresh()

def is_speaking_func():
	return isSpeaking

def jtalk_clear():
	libjt_clear(libjt, engine, jpcommon, njd)

def predic_build():
	global predic
	predic = [
# 		[re.compile(u'^ー'), u'チョーオン'],
# 		[re.compile(u'^？'), u'クエッションマーク'],
# 		[re.compile(u'^！'), u'びっくりマーク'],
# 		[re.compile(u'^ッ'), u'コモジノツ'], #カタカナ 
# 		[re.compile(u'^っ'), u'コモジノツ'], #ヒラガナ 
# 		[re.compile(u'^　'), u'ゼンカクスペース'],
# 		[re.compile(u'^。'), u'クテン'],
# 		[re.compile(u'^・'), u'ナカテン'],
# 		[re.compile(u'^、'), u'トウテン'],
# 		[re.compile(u'^．'), u'ピリオド'],
# 		[re.compile(u'^〔'), u'ヒラキキッコー'],

		[re.compile(u'(\\d+)\.(\\d+)'), u' \\1テン\\2 '],

		[re.compile(u'(（|\()月(）|\))'), u' カッコゲツヨー '],
		[re.compile(u'(（|\()火(）|\))'), u' カッコカヨー '],
		[re.compile(u'(（|\()水(）|\))'), u' カッコスイヨー '],
		[re.compile(u'(（|\()木(）|\))'), u' カッコモクヨー '],
		[re.compile(u'(（|\()金(）|\))'), u' カッコキンヨー '],
		[re.compile(u'(（|\()土(）|\))'), u' カッコドヨー '],
		[re.compile(u'(（|\()日(）|\))'), u' カッコニチヨー '],

		[re.compile(u'　'), ' '],
		[re.compile(u'。'), ' '],
		[re.compile(u'．'), ' '],
		[re.compile(u'・'), ' '],
		[re.compile(u'、'), ' '],
		[re.compile(u'，'), ' '],
		[re.compile(u'：'), ' '],
		[re.compile(u'；'), ' '],
		[re.compile(u'｜'), '|'],
		[re.compile(u'－'), '-'],
		[re.compile(u'～'), u'から'],
		[re.compile(u'~'), ' '],
		[re.compile(u'\u2014'), ' '],
		[re.compile(u'\u2022'), ' '],
		[re.compile(u'≫'), ' '],
		[re.compile(u'「'), ' '],
		[re.compile(u'」'), ' '],
		[re.compile(u'【'), ' '],
		[re.compile(u'】'), ' '],
		[re.compile(u'’'), ''],
		[re.compile(u'＜'), ' '],
		[re.compile(u'＞'), ' '],
		# [re.compile(u'を'), u'を '],
		[re.compile(u'（'), ' '],
		[re.compile(u'）'), ' '],
		[re.compile(u'●'), ' '],

		[re.compile(u'マイ '), u'マイ'],
		[re.compile(u'コントロール パネル'), u'コントロールパネル'],
		[re.compile(u'タスク バー'), u'タスクバー'],
		
		[re.compile(u'の '), u'の'], # remove space "1の 7" -> "1の7"
		[re.compile(u'読み込み中'), u'ヨミコミチュー'],
		[re.compile(u'一行'), u'イチギョー'],
		[re.compile(u'1行'), u'イチギョー'],
		[re.compile(u'2行'), u'ニギョー'],
		[re.compile(u'3行'), u'サンギョー'],
		[re.compile(u'空行'), u'クーギョー'],
		[re.compile(u'行末'), u'ギョーマツ'],
		[re.compile(u'複数行'), u'フクスーギョー'],
		[re.compile(u'→'), u'ミギヤジルシ'],
		[re.compile(u'←'), u'ヒダリヤジルシ'],
		[re.compile(u'↑'), u'ウエヤジルシ'],
		[re.compile(u'↓'), u'シタヤジルシ'],
		[re.compile('\.\.\.'), u' テンテンテン '],
		[re.compile(u'孫正義'), u'ソンマサヨシ'],
		[re.compile(u'池田信夫'), u'イケダノブオ'],
		[re.compile(u'蓮舫'), u'レンホー'],
		[re.compile(u'既読'), u'キドク'],
		[re.compile(u'新家'), u'シンケ'],
		[re.compile(u'障がい'), u'ショーガイ'],
		[re.compile(u'聾'), u'ロー'],
		[re.compile(u'盲'), u'モー'],
		[re.compile(u' 側'), u' ガワ'],
		[re.compile(u'元に'), u'モトニ'],
		[re.compile(u'明朝'), u'ミンチョー'],
		[re.compile(u'昔々'), u'ムカシムカシ'],
		[re.compile(u' 側'), u' ガワ'],
		[re.compile(u'ヨウ素'), u'ヨウソ'],
		[re.compile(u'から海'), u'カラウミ'],
		[re.compile(u'方は'), u'カタは'],
		[re.compile(u'方が'), u'カタが'],
		[re.compile(u'方の'), u'カタの'],
		[re.compile(u'方も'), u'カタも'],
		[re.compile(u'の方'), u'のカタ'],
		[re.compile(u'な方'), u'なカタ'],
		[re.compile(u'国など'), u'クニナド'],
		[re.compile(u'国へ'), u'クニエ'],
		[re.compile(u'国の'), u'クニノ'],
		[re.compile(u'国に'), u'クニに'],
		[re.compile(u'国から'), u'クニカラ'],
		[re.compile(u'数値'), u'スーチ'],
		[re.compile(u'値'), u'アタイ'],
		[re.compile(u'中程度'), u'チューテード'],
		[re.compile(u'各基'), u'カクキ'],
		[re.compile(u'少し'), u'すこし'],
		[re.compile(u'ただ今'), u'ただいま'],
		[re.compile(u'水沢'), u'ミズサワ'],
		[re.compile(u'２４時間'), u'ニジューヨジカン'],
		
		[re.compile(u'(Opensource|opensource|OPENSOURCE)'), u'オープンソース'],
		[re.compile(u'(Notepad|notepad|NOTEPAD)'), u'ノートパッド'],
		[re.compile(u'(Guidebook|guidebook|GUIDEBOOK)'), u'ガイドブック'],
		[re.compile(u'(Blog|blog|BLOG)'), u'ブログ'],
		[re.compile(u'(Matlab|matlab|MATLAB)'), u'マトラブ'],
		[re.compile(u'(Keyboard|keyboard|KEYBOARD)'), u'キーボード'],
		[re.compile(u'(Plugins|plugins|PLUGINS)'), u'プラグインズ'],
		[re.compile(u'(Facebook|facebook|FACEBOOK)'), u'フェイスブック'],
		[re.compile(u'(Desktop|desktop|DESKTOP)'), u'デスクトップ'],
		[re.compile(u'(Output|output|OUTPUT)'), u'アウトプット'],
		[re.compile(u'(Nullsoft|nullsoft|NULLSOFT)'), u'ヌルソフト'],
		[re.compile(u'(Cygdrive|cygdrive|CYGDRIVE)'), u'シグドライブ'],
		[re.compile(u'(Ustream|ustream|USTREAM)'), u'ユーストリーム'],
		[re.compile(u'(\\d+)MB'), u'\\1メガバイト'],
		
		[re.compile(u'(\\d+)\:00:00'), u'\\1ジレーフンレービョウ'],
		[re.compile(u'(\\d+)\:(\\d+):00'), u'\\1ジ\\2フンレービョウ'],
		[re.compile(u'(\\d+)\:00:(\\d+)'), u'\\1ジレイフン\\2ビョウ'],
		[re.compile(u'(\\d+)\:(\\d+)\:(\\d+)'), u'\\1ジ\\2フン\\3ビョウ'],
		
		[re.compile(u'19\:(\\d+)'), u'ジュークジ\\1フン'],
		[re.compile(u'9\:(\\d+)分'), u'クジ\\1フン'],
		[re.compile(u'9\:(\\d+)'), u'クジ\\1フン'],
		[re.compile(u'(\\d+)\:(\\d+)'), u'\\1ジ\\2フン'],
		
		[re.compile(u'(\\d)(\\d)(\\d)\-(\\d)(\\d)(\\d)(\\d)-(\\d)(\\d)(\\d)(\\d)'), 
			u' デンワバンゴー \\1 \\2 \\3の \\4 \\5 \\6 \\7の \\8 \\9 \\10 \\11 '],
		
		[re.compile(u'(\\d)(\\d)\-(\\d)(\\d)(\\d)(\\d)-(\\d)(\\d)(\\d)(\\d)'), 
			u' デンワバンゴー \\1 \\2の \\3 \\4 \\5 \\6の \\7 \\8 \\9 \\10 '],
		
		[re.compile(u'(\\d)(\\d)(\\d)\-(\\d)(\\d)(\\d)-(\\d)(\\d)(\\d)(\\d)'), 
			u' デンワバンゴー \\1 \\2 \\3の \\4 \\5 \\6の \\7 \\8 \\9 \\10 '],
		
		[re.compile(u'(\\d\\d\\d\\d)\-(\\d\\d)\-(\\d\\d)'), u'\\1ネン\\2ガツ\\3ニチ'],
		[re.compile(u'(\\d+)\/(\\d+)\/(\\d+)'), u'\\1ネン\\2ガツ\\3ニチ'],
		[re.compile(u'(\\d)\,(\\d\\d\\d)'), u'\\1\\2'],
		[re.compile(u'(\\d\\d)\,(\\d\\d\\d)'), u'\\1\\2'],
		[re.compile(u'(\\d\\d\\d)\,(\\d\\d\\d)'), u'\\1\\2'],

		[re.compile(u'ａｕ'), u'エーユー'],
		[re.compile(u'HORIJUN'), u'ホリジュン'],
		[re.compile(u'asianvoices'), u'アジアンボイシズ'],
		[re.compile(u'jishin'), u'ジシン'],
		[re.compile(u'jisin'), u'ジシン'],
		[re.compile(u'sagas'), u'サガス'],
		[re.compile(u'anpi'), u'アンピ'],
		[re.compile(u'kurogen'), u'クロゲン'],
		[re.compile(u'seikatsu'), u'セイカツ'],
		[re.compile(u'kabun'), u'カブン'],
		[re.compile(u'inosenaoki'), u'イノセナオキ'],
		[re.compile(u'(S|s)endai'), u'センダイ'],
		[re.compile(u'(F|f)ukushima'), u'フクシマ'],
		[re.compile(u'(H|h)okkaido'), u'ホッカイドー'],
		[re.compile(u'(Y|y)amagata'), u'ヤマガタ'],
		[re.compile(u'(M|m)iyagi'), u'ミヤギ'],
		[re.compile(u'(A|a)kita'), u'アキタ'],
		[re.compile(u'(A|a)omori'), u'アオモリ'],
		[re.compile(u'(I|i)wate'), u'イワテ'],
		[re.compile(u'(I|i)baraki'), u'イバラキ'],
		[re.compile(u'(T|t)sukuba'), u'ツクバ'],
		[re.compile(u'(O|o)shu'), u'オーシュー'],
		[re.compile(u'(H|h)achinohe'), u'ハチノヘ'],
		[re.compile(u'(S|s)himbun'), u'シンブン'],
		[re.compile(u'(A|a)sahi'), u'アサヒ'],
		[re.compile(u'(K|k)esennuma'), u'ケセンヌマ'],

		[re.compile(u'miz'), u'ミズ'],
		[re.compile(u'edu'), u'エデュー'],
		[re.compile(u'nhk\_'), u'nhk '],
		[re.compile(u'311care'), u'サンイチイチ ケアー'],
		[re.compile(u'１７１'), u'イチナナイチ'],

		[re.compile('\-'), ' '],
		[re.compile('\:'), 'コロン'],
		[re.compile('\/'), ' '],
		[re.compile('\\\\'), ' '],
		[re.compile('\+'), u'プラス'],
		[re.compile('\.'), u'ドット'],
		[re.compile('\_'), u'アンダースコア'],
		[re.compile('\='), u'イコール'],
		[re.compile('\;'), u'セミコロン'],
		[re.compile('\['), u' ダイカッコ '],
		[re.compile('\]'), u' '],
		[re.compile('\('), u' カッコ '],
		[re.compile('\)'), u' '],
		[re.compile('\|'), u'タテセン'],
		[re.compile('\#'), u'シャープ'],
		[re.compile('\"'), u'コーテーション'],
		[re.compile('\<'), u'ショーナリ'],
		[re.compile('\>'), u'ダイナリ'],
		[re.compile('\''), ' '],
	]

def predic_load():
	global predic
	if predic == None: predic_build()

############################################
# based on _espeak.py (nvda)

isSpeaking = False
lastIndex = None
bgThread = None
bgQueue = None
player = None

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:
				pass # 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)

# call from BgThread
def _speak(msg, index=None, isCharacter=False):
	global currIndex
	currIndex = index
	MSGLEN = 1000
	buff = create_string_buffer(MSGLEN)
	global isSpeaking
	isSpeaking = True
	for p in predic:
		try:
			msg = p[0].sub(p[1], msg)
		except:
			pass
	msg = msg.lower()
	for m in string.split(msg, ' '):
		m = m.replace(u'\u200b', ' ')
		try:
			text = m.encode(CODE, 'ignore')
			libjt_text2mecab(libjt, buff, text); str = buff.value #str = text2mecab(text.decode(CODE)).encode(CODE) # 
			if not isSpeaking: jtalk_refresh(); return
			if DEBUG_INFO: logwrite("_speak(%s) text2mecab(%s)" % (msg, str.decode(CODE)))
			[feature, size] = Mecab_analysis(str) # [feature, size] = Mecab_analysis_utf16(str, CODE_VOICE)
			if not isSpeaking: jtalk_refresh(); return
			if DEBUG_INFO: Mecab_print(feature, size, logwrite, CODE)
			libjt_synthesis(libjt, engine, jpcommon, njd, feature, size, fperiod, player.feed, is_speaking_func, 128) # player.feed() is called inside
			jtalk_refresh()
		except:
			pass
	global lastIndex
	lastIndex = currIndex
	currIndex = None
	isSpeaking = False

def speak(msg, index=None, isCharacter=False):
	if msg == '': return
	_execWhenDone(_speak, msg, index, isCharacter, mustBeAsync=True)

def stop():
	global isSpeaking, bgQueue
	# Kill all speech from now.
	# We still want parameter changes to occur, so requeue them.
	params = []
	stop_task_count = 0 # for log.info()
	try:
		while True:
			item = bgQueue.get_nowait() # [func, args, kwargs]
			if item[0] != _speak:
				params.append(item)
			else:
				stop_task_count = stop_task_count + 1
			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
	if DEBUG_INFO: logwrite("stop: %d task(s) stopping" % stop_task_count)
	player.stop()

def pause(switch):
	player.pause(switch)

def initialize(voice_args_ = _jtalk_voice_m001):
	global bgThread, bgQueue, player, libjt, logwrite, voice_args
	voice_args = voice_args_
	player = nvwave.WavePlayer(channels=1, samplesPerSec=voice_args['samp_rate'], bitsPerSample=16)
	bgQueue = Queue.Queue()
	bgThread = BgThread()
	bgThread.start()
	#
	libjt = libjt_initialize(JT_DLL, njd, jpcommon, engine, **voice_args)
	libjt_load(libjt, voice_args['dir'], engine)
	Mecab_initialize(MECAB_DLL, libjt)
	Mecab_load(DIC, MECABRC)
	predic_load()
	logwrite = log.info
	if DEBUG_INFO: logwrite("jtalk for NVDA started")

def terminate():
	global bgThread, bgQueue, player
	stop()
	bgQueue.put((None, None, None))
	bgThread.join()
	bgThread = None
	bgQueue = None
	player.close()
	player = None
	#
	Mecab_clear()
	jtalk_clear()

def get_rate():
	if voice_args['samp_rate'] == 16000:
		return 160 - 2 * fperiod
	if voice_args['samp_rate'] == 48000:
		return int((240 - fperiod) / 1.5)
	return 0

def set_rate(rate):
	global fperiod
	if voice_args['samp_rate'] == 16000:
		fperiod = int(80 - int(rate) / 2) # 80..30
	if voice_args['samp_rate'] == 48000:
		fperiod = int(240 - 1.5 * rate) # 240..90
