Skip to content

Commit 996c4c5

Browse files
committed
added basic support for one-time-lang option in 'speak'
1 parent e090c6d commit 996c4c5

File tree

4 files changed

+72
-49
lines changed

4 files changed

+72
-49
lines changed

www/scripts/sepiaFW.assistant.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,13 @@ function sepiaFW_build_assistant(sepiaSessionId){
253253

254254
//------------------ SOME METHODS TO ENGAGE USER ------------------
255255

256+
//NOTE: Check as well:
257+
//- SepiaFW.events.setProActiveBackgroundNotification(action); - uses: 'waitForOpportunityAndSay'
258+
256259
/**
257260
* Wait for the right opportunity (e.g. idle time) and let the assistant say a text
258261
* loaded from server.
262+
* NOTE: This will send the text to the server first (currently it does not support 'speakOptions')
259263
*/
260264
Assistant.waitForOpportunityAndSay = function(dialogTagOrText, fallbackAction, minWait, maxWait, doneCallback){
261265
if (!minWait) minWait = 2000; //NOTE: <2000 not allowed and will be used in any case!
@@ -265,7 +269,7 @@ function sepiaFW_build_assistant(sepiaSessionId){
265269
var options = {}; //things like skipTTS etc. (see sendCommand function)
266270
var dataset = {
267271
info: "direct_cmd",
268-
cmd: "chat;;reply=" + dialogTagOrText + ";;",
272+
cmd: "chat;;reply=" + dialogTagOrText + ";;", //TODO: does this support one-time-language switch?
269273
newReceiver: SepiaFW.assistant.id
270274
};
271275
SepiaFW.client.sendCommand(dataset, options);
@@ -277,18 +281,19 @@ function sepiaFW_build_assistant(sepiaSessionId){
277281
}
278282
/**
279283
* Wait for the right opportunity (e.g. idle time), let the assistant say a localized text
280-
* and then run some action. NOTE: This will be a bit more agressive compared to 'waitForOpportunityAndSay'.
284+
* and then run some action.
285+
* NOTE: This will be a bit more agressive compared to 'waitForOpportunityAndSay'.
281286
*/
282-
Assistant.waitForOpportunitySayLocalTextAndRunAction = function(localizedText, actionFun, fallbackAction, maxWait){
287+
Assistant.waitForOpportunitySayLocalTextAndRunAction = function(localizedText, actionFun, fallbackAction, maxWait, speakOptions){
283288
if (!fallbackAction) fallbackAction = actionFun; //NOTE: this is different to function above. We try to run even on error!
284289
var minWait = 2000; //NOTE: <2000 not allowed, but will only be used when not idle in first place
285290
if (!maxWait) maxWait = 30000;
286291
if (SepiaFW.animate.assistant.getState() == "idle"){
287-
SepiaFW.speech.speak(localizedText, actionFun, fallbackAction);
292+
SepiaFW.speech.speak(localizedText, actionFun, fallbackAction, function(){}, speakOptions);
288293
}else{
289294
SepiaFW.client.queueIdleTimeEvent(function(){
290295
//Start
291-
SepiaFW.speech.speak(localizedText, actionFun, fallbackAction);
296+
SepiaFW.speech.speak(localizedText, actionFun, fallbackAction, function(){}, speakOptions);
292297
}, minWait, maxWait, function(){
293298
//Fallback
294299
fallbackAction();

www/scripts/sepiaFW.audio.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ function sepiaFW_build_audio(){
585585
}
586586

587587
//use TTS endpoint to generate soundfile and speak answer
588-
TTS.speak = function(message, onStartCallback, onEndCallback, onErrorCallback){
588+
TTS.speak = function(message, onStartCallback, onEndCallback, onErrorCallback, options){
589589
//NOTE: For the state-ful version of 'speak' (with settings and events) use: 'SepiaFW.speech.speak'
590590
//gets URL and calls play(URL)
591591
SepiaFW.speech.getTtsStreamURL(message, function(audioUrl){

www/scripts/sepiaFW.speechSynthesis.js

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -331,14 +331,10 @@ function sepiaFW_build_speech_synthesis(Speech, sepiaSessionId){
331331
//native voices
332332
selectedVoice = newVoice;
333333
if (selectedVoice){
334-
var selectedVoiceObjectArray = speechSynthesis.getVoices().filter(function(voice){
334+
selectedVoiceObject = window.speechSynthesis.getVoices().find(function(voice){
335335
return (voice && voice.name && (voice.name === selectedVoice));
336336
});
337-
if (selectedVoiceObjectArray.length == 0){
338-
selectedVoiceObject = {};
339-
}else{
340-
selectedVoiceObject = selectedVoiceObjectArray[0];
341-
}
337+
if (!selectedVoiceObject) selectedVoiceObject = {};
342338
}else{
343339
selectedVoiceObject = {};
344340
}
@@ -360,18 +356,31 @@ function sepiaFW_build_speech_synthesis(Speech, sepiaSessionId){
360356
setVoiceOnce();
361357
}
362358
}
359+
Speech.findVoiceObjForLanguage = function(lang){
360+
if (voices && voices.length > 0){
361+
lang = lang.replace(/(-|_).*/, "").trim().toLowerCase();
362+
var knownSelection = SepiaFW.data.getPermanent(lang + "-voice");
363+
var bestFitV;
364+
if (knownSelection) bestFitV = voices.find(function(v){ return (v.name && v.name == knownSelection); });
365+
if (!bestFitV) bestFitV = voices.find(function(v){ return (v.lang && v.lang.indexOf(lang) == 0); });
366+
return bestFitV;
367+
}else{
368+
return; //we only search in pre-loaded voices for now
369+
}
370+
}
363371

364372
//speak an utterance
365-
Speech.speak = function(text, finishedCallback, errorCallback, startedCallback){
373+
Speech.speak = function(text, finishedCallback, errorCallback, startedCallback, options){
366374
//NOTE: this is the high level function with all events etc...
367-
// If you the direct interface with default settings use 'SepiaFW.audio.tts.speak'
375+
// If you need the direct interface with default settings use 'SepiaFW.audio.tts.speak'
376+
if (!options) options = {};
368377

369378
//stop running stuff
370379
if (isSpeaking){
371380
Speech.stopSpeech();
372381
clearTimeout(stopSpeechTimeout);
373382
stopSpeechTimeout = setTimeout(function(){
374-
Speech.speak(text, finishedCallback, errorCallback, startedCallback);
383+
Speech.speak(text, finishedCallback, errorCallback, startedCallback, options);
375384
}, 500);
376385
return;
377386
}
@@ -399,7 +408,8 @@ function sepiaFW_build_speech_synthesis(Speech, sepiaSessionId){
399408
}
400409

401410
}else{
402-
//chunk text if there is a limit - TODO: 'isChromiumDesktop' is not the entire truth, it depends on the selected voice!
411+
//chunk text if there is a limit
412+
//TODO: 'isChromiumDesktop' is not the entire truth, it depends on the selected voice!
403413
if (SepiaFW.ui.isChromiumDesktop && text && !Speech.skipTTS && Speech.isTtsSupported){
404414
text = chunkUtterance(text);
405415
}
@@ -414,6 +424,14 @@ function sepiaFW_build_speech_synthesis(Speech, sepiaSessionId){
414424
return;
415425
}
416426

427+
//one-time voice change? - TODO: not all engines support this yet
428+
var oneTimeVoice;
429+
if (options.oneTimeLanguage && Speech.getLanguage() != options.oneTimeLanguage){
430+
oneTimeVoice = Speech.findVoiceObjForLanguage(options.oneTimeLanguage);
431+
}
432+
if (oneTimeVoice) options.oneTimeVoice = oneTimeVoice;
433+
else options.oneTimeVoice = undefined; //make sure this is only set after 'findVoiceForLanguage'
434+
417435
//Streaming audio server (e.g. SEPIA, MARY-TTS API)
418436
if (Speech.voiceEngine == 'sepia' || Speech.voiceEngine == 'custom-mary-api'){
419437
broadcastTtsRequested();
@@ -434,7 +452,7 @@ function sepiaFW_build_speech_synthesis(Speech, sepiaSessionId){
434452
var event = {};
435453
event.msg = reason;
436454
onTtsError(event, errorCallback);
437-
});
455+
}, options);
438456

439457
//NATIVE-TTS
440458
}else if (SepiaFW.ui.isCordova){
@@ -449,7 +467,7 @@ function sepiaFW_build_speech_synthesis(Speech, sepiaSessionId){
449467
onTtsStart(undefined, startedCallback, errorCallback);
450468
TTS.speak({ //Cordova plugin!
451469
text: text,
452-
locale: Speech.getLongLanguageCode(Speech.getLanguage()),
470+
locale: Speech.getLongLanguageCode(Speech.getLanguage()), //TODO: support one-time lang.? Check support!
453471
rate: 1.00
454472

455473
}, function () {
@@ -468,9 +486,15 @@ function sepiaFW_build_speech_synthesis(Speech, sepiaSessionId){
468486
window.sepia_tts_utterances = []; //This is a bug-fix to prevent utterance from getting garbage collected
469487
var utterance = new SpeechSynthesisUtterance();
470488
utterance.text = text;
471-
utterance.lang = Speech.getLongLanguageCode(Speech.getLanguage());
472-
//set voice if valid one was selected
473-
if (selectedVoiceObject && selectedVoiceObject.name) utterance.voice = selectedVoiceObject;
489+
if (options.oneTimeVoice){
490+
//support for one-time language switch
491+
utterance.lang = Speech.getLongLanguageCode(options.oneTimeLanguage);
492+
utterance.voice = options.oneTimeVoice;
493+
}else{
494+
utterance.lang = Speech.getLongLanguageCode(Speech.getLanguage());
495+
//set voice if valid one was selected
496+
if (selectedVoiceObject && selectedVoiceObject.name) utterance.voice = selectedVoiceObject;
497+
}
474498
utterance.pitch = 1.0; //accepted values: 0-2 inclusive, default value: 1
475499
utterance.rate = 1.0; //accepted values: 0.1-10 inclusive, default value: 1
476500
utterance.volume = 1.0; //accepted values: 0-1, default value: 1
@@ -702,13 +726,13 @@ function sepiaFW_build_speech_synthesis(Speech, sepiaSessionId){
702726

703727
//--- Common Stream TTS Interface ---
704728

705-
Speech.getTtsStreamURL = function(message, successCallback, errorCallback){
729+
Speech.getTtsStreamURL = function(message, successCallback, errorCallback, options){
706730
//SEPIA server
707731
if (Speech.voiceEngine == 'sepia'){
708-
Speech.sepiaTTS.getURL(message, successCallback, errorCallback);
732+
Speech.sepiaTTS.getURL(message, successCallback, errorCallback, options);
709733
//CUSTOM MARY-TTS API
710734
}else if (Speech.voiceEngine == 'custom-mary-api'){
711-
Speech.maryTTS.getURL(message, successCallback, errorCallback);
735+
Speech.maryTTS.getURL(message, successCallback, errorCallback, options);
712736
//NONE
713737
}else{
714738
if (errorCallback) errorCallback({name: "NotSupported", message: "The selected voice engine is missing a required function."});
@@ -749,7 +773,8 @@ function sepiaFW_build_speech_synthesis(Speech, sepiaSessionId){
749773
}
750774

751775
//get audio URL
752-
Speech.sepiaTTS.getURL = function(message, successCallback, errorCallback){
776+
Speech.sepiaTTS.getURL = function(message, successCallback, errorCallback, options){
777+
//TODO: implement 'options.oneTimeVoice' and 'options.oneTimeLanguage'
753778
var apiUrl = SepiaFW.config.assistAPI + "tts";
754779
var submitData = {
755780
text: message,
@@ -857,7 +882,8 @@ function sepiaFW_build_speech_synthesis(Speech, sepiaSessionId){
857882
Speech.maryTTS.settings.maxChunkLength = (Settings.maxChunkLength)? Settings.maxChunkLength : 600;
858883
}
859884

860-
Speech.maryTTS.getURL = function(message, successCallback, errorCallback){
885+
Speech.maryTTS.getURL = function(message, successCallback, errorCallback, options){
886+
//TODO: implement 'options.oneTimeVoice' and 'options.oneTimeLanguage'
861887
if (!Speech.voiceCustomServer){
862888
var err = {name: "MissingServerInfo", message: "Custom Mary-TTS API is missing server URL."};
863889
SepiaFW.debug.error("Speech.maryTTS - getURL ERROR: " + JSON.stringify(err));
@@ -876,9 +902,9 @@ function sepiaFW_build_speech_synthesis(Speech, sepiaSessionId){
876902
}else if (!Speech.maryTTS.settings.voice || Speech.maryTTS.settings.voice == "default"){
877903
//console.log("voices", voices); //DEBUG
878904
var lang = Speech.getLanguage();
879-
var bestFitV = voices.find(function(v){ return (v.lang && v.lang.indexOf(lang) == 0); })
905+
var bestFitV = Speech.findVoiceObjForLanguage(lang)
880906
|| voices.find(function(v){ return (v.name && (v.name.indexOf(lang + "-") == 0 || v.name.indexOf(lang + "_") == 0)); })
881-
|| voices[0].name
907+
|| voices[0];
882908
if (bestFitV){
883909
Speech.maryTTS.settings.voice = bestFitV.name;
884910
}else{

www/scripts/sepiaFW.ui.js

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,27 +1346,19 @@ function sepiaFW_build_ui(){
13461346
btn1.html(config.buttonOneName);
13471347
btn1.off().on('click', function(){
13481348
config.buttonOneAction(
1349-
this,
1350-
$input1.val(),
1351-
$input2.val(),
1352-
$input1[0],
1353-
$input2[0]
1349+
this, $input1.val(), $input2.val(), $input1[0], $input2[0]
13541350
);
13551351
UI.hidePopup();
13561352
});
13571353
}else{
13581354
btn1.html('OK');
1359-
btn1.off().on('click', function(){ UI.hidePopup(); });
1355+
btn1.off().on('click', function(){ UI.hidePopup(); });
13601356
}
13611357
if (config.buttonTwoName && config.buttonTwoAction){
13621358
btn2.html(config.buttonTwoName).show();
13631359
btn2.off().on('click', function(){
13641360
config.buttonTwoAction(
1365-
this,
1366-
$input1.val(),
1367-
$input2.val(),
1368-
$input1[0],
1369-
$input2[0]
1361+
this, $input1.val(), $input2.val(), $input1[0], $input2[0]
13701362
);
13711363
UI.hidePopup();
13721364
});
@@ -1375,13 +1367,13 @@ function sepiaFW_build_ui(){
13751367
}
13761368
if (config.buttonThreeName && config.buttonThreeAction){
13771369
btn3.html(config.buttonThreeName).show();
1378-
btn3.off().on('click', function(){ config.buttonThreeAction(this); UI.hidePopup(); });
1370+
btn3.off().on('click', function(){ config.buttonThreeAction(this); UI.hidePopup(); });
13791371
}else{
13801372
btn3.off().hide();
13811373
}
13821374
if (config.buttonFourName && config.buttonFourAction){
13831375
btn4.html(config.buttonFourName).show();
1384-
btn4.off().on('click', function(){ config.buttonFourAction(this); UI.hidePopup(); });
1376+
btn4.off().on('click', function(){ config.buttonFourAction(this); UI.hidePopup(); });
13851377
}else{
13861378
btn4.off().hide();
13871379
}
@@ -1431,7 +1423,7 @@ function sepiaFW_build_ui(){
14311423
}, 1000);
14321424
}
14331425
//open
1434-
$('#sepiaFW-cover-layer').fadeIn(200);
1426+
$('#sepiaFW-cover-layer').stop().fadeIn(200);
14351427
//$('#sepiaFW-popup-message').fadeIn(300);
14361428
}
14371429
UI.hidePopup = function(){
@@ -1449,27 +1441,27 @@ function sepiaFW_build_ui(){
14491441
UI.askForPermissionToExecute = function(question, allowedCallback, refusedCallback){
14501442
var request = SepiaFW.local.g('allowedToExecuteThisCommand') + "<br>" + question;
14511443
UI.showPopup(request, {
1452-
buttonOneName : SepiaFW.local.g('looksGood'),
1453-
buttonOneAction : function(){
1444+
buttonOneName: SepiaFW.local.g('looksGood'),
1445+
buttonOneAction: function(){
14541446
//yes
14551447
if (allowedCallback) allowedCallback();
14561448
},
1457-
buttonTwoName : SepiaFW.local.g('betterNot'),
1458-
buttonTwoAction : function(){
1449+
buttonTwoName: SepiaFW.local.g('betterNot'),
1450+
buttonTwoAction: function(){
14591451
//no
14601452
if (refusedCallback) refusedCallback();
14611453
}
14621454
});
14631455
}
14641456
UI.askForConfirmation = function(question, allowedCallback, refusedCallback, alternativeCallback, alternativeLabel){
14651457
var config = {
1466-
buttonOneName : SepiaFW.local.g('ok'),
1458+
buttonOneName: SepiaFW.local.g('ok'),
14671459
buttonOneAction : function(){
14681460
//yes
14691461
if (allowedCallback) allowedCallback();
14701462
},
1471-
buttonTwoName : SepiaFW.local.g('abort'),
1472-
buttonTwoAction : function(){
1463+
buttonTwoName: SepiaFW.local.g('abort'),
1464+
buttonTwoAction: function(){
14731465
//no
14741466
if (refusedCallback) refusedCallback();
14751467
}

0 commit comments

Comments
 (0)