From c98ed977f29c5d1f3616307c76d0f326a71ae9a2 Mon Sep 17 00:00:00 2001 From: Ben Date: Fri, 28 Jul 2017 17:06:55 +0200 Subject: [PATCH] Adding IPA output to emscripten js library and demo --- emscripten/demo.html | 9 ++++++++- emscripten/espeakng_glue.cpp | 29 +++++++++++++++++++++++++++++ emscripten/espeakng_glue.idl | 1 + emscripten/js/demo.js | 30 +++++++++++++++++++++++++++--- emscripten/js/espeakng.js | 3 ++- emscripten/post.js | 23 +++++++++++++++++++++++ 6 files changed, 90 insertions(+), 5 deletions(-) diff --git a/emscripten/demo.html b/emscripten/demo.html index 33d5dc6b..4a82ba84 100644 --- a/emscripten/demo.html +++ b/emscripten/demo.html @@ -52,6 +52,12 @@ display: block; margin-bottom: 1rem; } + #ipaarea { + width: 100%; + height: 8rem; + display: block; + margin-bottom: 1rem; + } p.gh { color: #333; text-align: right; @@ -100,6 +106,7 @@

espeakng.js 1.49.1 Demo

+
@@ -110,7 +117,7 @@
- +

Original Code for eSpeak: Eitan Isaacson

diff --git a/emscripten/espeakng_glue.cpp b/emscripten/espeakng_glue.cpp index 7bbb2963..24398dc1 100644 --- a/emscripten/espeakng_glue.cpp +++ b/emscripten/espeakng_glue.cpp @@ -50,6 +50,35 @@ public: espeak_SetSynthCallback(NULL); } + int synth_ipa_(const char* aText, const char* virtualFileName) { + + /* phoneme_mode + bit 1: 0=eSpeak's ascii phoneme names, 1= International Phonetic Alphabet (as UTF-8 characters). + bit 7: use (bits 8-23) as a tie within multi-letter phonemes names + bits 8-23: separator character, between phoneme names + */ + + espeak_SetSynthCallback(NULL); + + int phoneme_options = (1 << 1); // Use IPA + int use_custom_phoneme_separator = (0 << 7); + int phonemes_separator = ' '; // Use a default value + + int phoneme_conf = phoneme_options | (phonemes_separator << 8); + + FILE* f_phonemes_out = fopen(virtualFileName,"wb"); + if(!f_phonemes_out) + return -1; + + //espeak_ng_InitializeOutput(ENOUTPUT_MODE_SYNCHRONOUS, 0, NULL); + espeak_SetPhonemeTrace(phoneme_conf, f_phonemes_out); + espeak_Synth(aText, 0, 0, POS_CHARACTER, 0, 0, NULL, NULL); + espeak_SetPhonemeTrace(0, NULL); + fclose(f_phonemes_out); + + return 0; + } + long set_voice( const char* aName, const char* aLang=NULL, diff --git a/emscripten/espeakng_glue.idl b/emscripten/espeakng_glue.idl index 83b865a1..cac42999 100644 --- a/emscripten/espeakng_glue.idl +++ b/emscripten/espeakng_glue.idl @@ -52,6 +52,7 @@ interface espeak_EVENT { interface eSpeakNGWorker { void eSpeakNGWorker(); void synth_(DOMString aText, VoidPtr aCallback); + long synth_ipa_(DOMString aText, DOMString virtualFileName); long getSizeOfEventStruct_(); long set_voice(DOMString aName, DOMString aLang, optional octet gender=0, optional octet age=0, optional octet aVariant=0); [Const] attribute espeak_VOICE[] voices; diff --git a/emscripten/js/demo.js b/emscripten/js/demo.js index 086f7185..5a23c375 100644 --- a/emscripten/js/demo.js +++ b/emscripten/js/demo.js @@ -179,10 +179,12 @@ function speak() { pusher.connect(ctx.destination); console.log(' Creating pusher... done'); + var user_text = document.getElementById('texttospeak').value; + // actual synthesis console.log(' Calling synthesize...'); tts.synthesize( - document.getElementById('texttospeak').value, + user_text, function cb(samples, events) { //console.log(' Inside synt cb'); if (!samples) { @@ -203,11 +205,32 @@ function speak() { //console.log(' Leaving synt cb'); } // end of function cb ); // end of tts.synthesize() - console.log(' Calling synthesize... done'); - + console.log(' Calling synthesize... done'); console.log('Leaving speak()'); + } // end of speak() +function ipa() { + + console.log("Synthesizing ipa ... "); + var ts = new Date(); + var user_text = document.getElementById('texttospeak').value; + + //user_text = user_text.repeat(50); + + tts.set_voice(document.getElementById('voice').value); + tts.synthesize_ipa(user_text, function(result) { + var te = new Date(); + document.getElementById('ipaarea').value = result.ipa; + console.log("Ipa synthesis done in " + (te-ts) + " ms.") + }); +} + +function speakAndIpa() { + speak(); + ipa(); +} + function resetPitch() { document.getElementById('pitch').value = 50; } @@ -254,5 +277,6 @@ function initializeDemo() { console.log('Leaving cb1'); } // end of function cb1 ); + console.log('Creating eSpeakNG instance... done'); } diff --git a/emscripten/js/espeakng.js b/emscripten/js/espeakng.js index 1e88acf5..a2258350 100644 --- a/emscripten/js/espeakng.js +++ b/emscripten/js/espeakng.js @@ -63,7 +63,8 @@ for (var method of [ 'set_rate', 'set_pitch', 'set_voice', - 'synthesize' + 'synthesize', + 'synthesize_ipa' ]) { eSpeakNG.prototype[method] = _createAsyncMethod(method); } diff --git a/emscripten/post.js b/emscripten/post.js index 5311d7fc..f5e13bc6 100644 --- a/emscripten/post.js +++ b/emscripten/post.js @@ -93,6 +93,28 @@ eSpeakNGWorker.prototype.synthesize = function (aText, aCallback) { Runtime.removeFunction(fp); }; +eSpeakNGWorker.prototype.synthesize_ipa = function (aText, aCallback) { + + // Use a unique temp file for the worker. Avoid collisions, just in case. + var ipaVirtualFileName = "espeak-ng-ipa-tmp-" + Math.random().toString().substring(2); + + var res = ""; + var code = this.synth_ipa_(aText, ipaVirtualFileName); + + if(code == 0) + res = FS.readFile(ipaVirtualFileName, { encoding: 'utf8' }) + + // Clean up the tmp file + FS.unlink(ipaVirtualFileName); + + var ret = { + code: code, + ipa: res + } + + return ret; +}; + // Make this a worker if (typeof WorkerGlobalScope !== 'undefined') { @@ -104,6 +126,7 @@ if (typeof WorkerGlobalScope !== 'undefined') { }); onmessage = function(e) { + if (!worker) { throw 'eSpeakNGWorker worker not initialized'; }