sconscript 4.76 KB
Import([
	'env',
	'sourceDir',
])

import os
import ctypes
from glob import glob

class AutoFreeCDLL(ctypes.CDLL):
	def __del__(self):
		ctypes.windll.kernel32.FreeLibrary(self._handle)

synthDriversDir=sourceDir.Dir('synthDrivers')
espeakRepo=Dir("#include/espeak")
espeakSrcDir=espeakRepo.Dir('src')
espeakIncludeDir=espeakSrcDir.Dir('include')
sonicSrcDir=Dir("#include/sonic")

class espeak_VOICE(ctypes.Structure):
	_fields_=[
		('name',ctypes.c_char_p),
		('languages',ctypes.c_char_p),
		('identifier',ctypes.c_char_p),
		('gender',ctypes.c_byte),
		('age',ctypes.c_byte),
		('variant',ctypes.c_byte),
		('xx1',ctypes.c_byte),
		('score',ctypes.c_int),
		('spare',ctypes.c_void_p),
	]

env=env.Clone()
#Whole-program optimization causes eSpeak to distort and worble with its Klatt4 voice
#Therefore specifically force it off
env.Append(CCFLAGS='/GL-')

env.Append(CCFLAGS='/W0')
env.Append(CPPPATH=['#nvdaHelper/espeak',espeakIncludeDir,espeakIncludeDir.Dir('compat'),sonicSrcDir])

def espeak_compilePhonemeData_buildEmitter(target,source,env):
	phSourceIgnores=['error_log','error_intonation','compile_prog_log','compile_report','envelopes.png']
	phSources=env.Flatten([[Dir(topDir).File(f) for f in files if f not in phSourceIgnores] for topDir,subdirs,files in os.walk(source[0].abspath)])
	sources=env.Flatten([phSources])
	targets=[target[0].File(f) for f in ['intonations','phondata','phondata-manifest','phonindex','phontab']]
	phSideEffects=[source[0].File(x) for x in phSourceIgnores]
	env.SideEffect(phSideEffects,targets)
	return targets,sources

def espeak_compilePhonemeData_buildAction(target,source,env):
	# We want the eSpeak dll to be freed after each dictionary.
	# This is because it writes to stderr but doesn't flush it.
	# Unfortunately, there's no way we can flush it or use a different stream
	# because our eSpeak statically links the CRT.
	espeak=AutoFreeCDLL(espeakLib[0].abspath)
	espeak.espeak_ng_InitializePath(espeakRepo.abspath)
	espeak.espeak_ng_CompileIntonation(None,None)
	espeak.espeak_ng_CompilePhonemeData(22050,None,None)
	espeak.espeak_Terminate()

env['BUILDERS']['espeak_compilePhonemeData']=Builder(action=env.Action(espeak_compilePhonemeData_buildAction,"Compiling phoneme data"),emitter=espeak_compilePhonemeData_buildEmitter)

def espeak_compileDict_buildAction(target,source,env):
	# We want the eSpeak dll to be freed after each dictionary.
	# This is because it writes to stderr but doesn't flush it.
	# Unfortunately, there's no way we can flush it or use a different stream
	# because our eSpeak statically links the CRT.
	espeak=AutoFreeCDLL(espeakLib[0].abspath)
	espeak.espeak_Initialize(0,0,target[0].Dir('..').abspath,0x8000)
	try:
		lang=source[0].name.split('_')[0]
		v=espeak_VOICE(languages=lang+'\x00')
		if espeak.espeak_SetVoiceByProperties(ctypes.byref(v))!=0:
			print "espeak_compileDict_action: failed to switch to language %s"%lang
			return 1
		dictPath=os.path.split(source[0].abspath)[0]+'/'
		if espeak.espeak_ng_CompileDictionary(dictPath,None,0,None)!=0:
			print "espeak_compileDict_action: failed to compile dictionary for  language %s"%lang
			return
	finally:
		espeak.espeak_Terminate()

sonicLib=env.StaticLibrary(
	target='sonic',
	srcdir=sonicSrcDir.abspath,
	source='sonic.c',
)

espeakLib=env.SharedLibrary(
	target='espeak',
	srcdir=espeakSrcDir.Dir('libespeak-ng').abspath,
	source=[
		"speech.c",
		"readclause.c",
		"compiledict.c",
		"dictionary.c",
		"intonation.c",
		"setlengths.c",
		"numbers.c",
		"synth_mbrola.c",
		"synthdata.c",
		"synthesize.c",
		"translate.c",
		"tr_languages.c",
		"voices.c",
		"wavegen.c",
		"phonemelist.c",
		"klatt.c",
		"compiledata.c",
		"compilembrola.c",
		"ieee80.c",
		"spect.c",
		"espeak_api.c",
		"error.c",
		sonicLib,
	],
	LIBS=['advapi32'],
)

phonemeData=env.espeak_compilePhonemeData(espeakRepo.Dir('espeak-data'),espeakRepo.Dir('phsource'))
env.Depends(phonemeData,espeakLib)

# Move any extra dictionaries into dictsource for compilation
env.Install(espeakRepo.Dir('dictsource'),env.Glob(os.path.join(espeakRepo.abspath,'dictsource','extra','*_*')))

#Compile all dictionaries
missingDicts=[] #'mt','tn','tt']
dictSourcePath=espeakRepo.Dir('dictsource').abspath
for f in env.Glob(os.path.join(dictSourcePath,'*_rules')):
	lang=f.name.split('_')[0]
	if lang in missingDicts: continue
	dictFile=env.Command(espeakRepo.Dir('espeak-data').File(lang+'_dict'),f,espeak_compileDict_buildAction)
	dictDeps=env.Glob(os.path.join(espeakRepo.abspath,'dictsource',lang+'_*'))
	dictDeps.remove(f)
	env.Depends(dictFile,dictDeps)
	env.Depends(dictFile,[espeakLib,phonemeData])
	#Dictionaries can not be compiled in parallel, force SCons not to do this
	env.SideEffect('_espeak_compileDict',dictFile)

env.Install(synthDriversDir,espeakLib)
env.RecursiveInstall(synthDriversDir.Dir('espeak-data'),espeakRepo.Dir('espeak-data').abspath)