diff --git a/speech/talk.js b/speech/talk.js new file mode 100644 index 0000000000000000000000000000000000000000..c6b57660b4ba1fe70437b081813aef20d0e3fdfc --- /dev/null +++ b/speech/talk.js @@ -0,0 +1,289 @@ +// jshint esversion: 6 + const machine = document.location.search.indexOf('machine=')>-1? document.location.search.split('machine=')[1].split('&')[0]: 'fermi'; + initSearch('../' + machine + '_lattice.json'); + window.openData = []; + const t0 = + new Date(); + let mode = ''; + if (document.location.search.indexOf('background=')>-1) { + const a = document.location.search.split('background=')[1].split('&')[0]; + $('body').css('background-color', +a > 0? '#'+a: a); + } + const lang = document.location.search.indexOf('lang=')>-1 && document.location.search.split('lang=')[1].split('&')[0]=='en'? 'en-US': 'it-IT'; // BCP 47 language + const host = document.location.search.indexOf('talk=')>-1? document.location.search.split('talk=')[1].split('&')[0].replace('ee', 'pcl-elettra-cre-0').replace('ef','pcl-elettra-crf-0'): ''; + function switchLocale(newlang) {document.location = './talk.php?lang='+newlang+(host.length>0? '&talk='+host: '');} + let locale = {}; + function myload() { + fetch('./talk_locale.json').then((response) => {return response.json();}).then((rlocale) => { + locale = rlocale; + $('#title').html(rlocale[lang].talkto + host); + $('#micna').attr('title', rlocale[lang].micna); + $('#miclabel').html(rlocale[lang].miclabel + ' '); + }); + } + // https://stackoverflow.com/questions/64405532/why-speechsynthesisutterance-is-not-working-on-chrome + // It's because in Chrome speech synthasis requires user interaction before it speaks e.g. a button click. + // https://stackoverflow.com/questions/50490304/how-to-make-audio-autoplay-on-chrome + const synth = window.speechSynthesis; + const voices = synth.getVoices(); + const voiceLocale = {'it-IT': false, 'en-US': false}; + for (let i=voices.length-1; i>=0; i--) { + if (voices[i].lang=='it-IT') voiceLocale['it-IT'] = voices[i]; + if (voices[i].lang=='en-US') voiceLocale['en-US'] = voices[i]; + } + let oldText = ''; + let oldTime = 0; + function speakGenerated(textValue) { + if (textValue !== '') { + stopRec(); + if (synth.speaking) {console.error(locale.lang.speaking);showLog(locale.lang.speaking2);return;} + const utterThis = new SpeechSynthesisUtterance(textValue); + utterThis.onend = function (event) {startRec(); console.log('SpeechSynthesisUtterance.onend');showLog('..');}; + utterThis.onerror = function (event) {console.error('SpeechSynthesisUtterance.onerror', event.error, event);showLog(locale[lang].onerror+JSON.stringify(event.error));}; + utterThis.voice = voiceLocale[lang]===false? voices[0]: voiceLocale[lang]; + utterThis.pitch = 0.1; + utterThis.rate = 1; + utterThis.lang = lang; + showLog(lang); + synth.speak(utterThis); + } + } + let keepAlive = false; + const names = []; + let aliveTimer = -1; + function startRec() { + keepAlive = true; + aliveTimer = -1; + // showLog('start'); + recognition.start(); + document.getElementById('micstart').style.display = 'inline'; + document.getElementById('micstop').style.display = 'none'; + } + function stopRec() { + if (typeof window.SpeechRecognition == 'undefined') {$("#micstart").hide();$("#micna").show();} + keepAlive = false; + recognition.stop(); + } + function commandlistinfo() { + alert(locale[lang].commandlistinfo); + } + if (document.location.search.indexOf('debug')==-1) $('#log').hide(); + let speechStat = ''; + let firstRun = true; + function showLog(currentToken, p) { + console.log(p); + console.trace(currentToken); + // Show log in console and UI. + const logElement = document.querySelector('#log'); + logElement.textContent = logElement.textContent+'\n'+JSON.stringify(currentToken); + } + let finalTranscript = ''; + let recognition = new window.SpeechRecognition(); + recognition.maxAlternatives = 10; + recognition.continuous = true; + recognition.interimResults = true; + recognition.lang = lang; + recognition.maxAlternatives = 3; + showLog('starting...'+'\n'); + /* recognition.onresult = (event) => { + // The SpeechRecognitionEvent results property returns a SpeechRecognitionResultList object + // The SpeechRecognitionResultList object contains SpeechRecognitionResult objects. + // It has a getter so it can be accessed like an array + // The first [0] returns the SpeechRecognitionResult at position 0. + // Each SpeechRecognitionResult object contains SpeechRecognitionAlternative objects + // that contain individual results. + // These also have getters so they can be accessed like arrays. + // The second [0] returns the SpeechRecognitionAlternative at position 0. + // We then return the transcript property of the SpeechRecognitionAlternative object + const color = event.results[0][0].transcript; + diagnostic.textContent = `Result received: ${color}.`; + bg.style.backgroundColor = color; + };*/ + recognition.onerror = function(event) { + showLog(locale[lang].errorname + event.error+', '+event.message+', '+event.lineno+'\n'); + showLog(locale[lang].errormessage + JSON.stringify(event.error)); + console.log(event); + startRec(); + }; + recognition.onend = function(event) { + showLog('.'); + // document.getElementById('miclabel').style.display = 'none'; + document.getElementById('micna').style.display = 'none'; + document.getElementById('micstart').style.display = 'none'; + document.getElementById('micstop').style.display = 'inline'; + if (keepAlive && aliveTimer==-1) aliveTimer = setTimeout(startRec, 200); + }; + recognition.onstart = function(event) { + if (document.location.search.indexOf('debug')==-1) $('#log').hide(); + if (firstRun) { + firstRun = false; + recognition.stop(); + return; + } + showLog('-'); + document.getElementById('miclabel').style.display = 'none'; + document.getElementById('micna').style.display = 'none'; + document.getElementById('micstart').style.display = 'inline'; + document.getElementById('micstop').style.display = 'none'; + }; + function detect_token(transcript, token) { + const t = token.split(';'); + for (let i in t) { + if (transcript.indexOf(t[i])>-1) return transcript.indexOf(t[i]) + t[i].length+1; + } + return -1; + } + function quick_add(transcript) { + const t = + new Date(); + if ((transcript==oldText || (transcript=='te' && oldText.indexOf('te')>-1)) && t-oldTime<1500) return; + oldTime = t; + oldText = transcript; + speechStat = ''; + let txt = transcript.toLowerCase(); + if (txt.length>0) { + const tok = txt.split(' ')[0]; + if (locale[lang].open.indexOf(tok)>-1) { + mode = 'open'; + txt = txt.replace(tok, '').replace(' ', ''); + } + if (mode=='open' && txt.length>0) { + const snd = './talk.php?open='+txt.toLowerCase().replace('.', '')+'&token='+document.location.search.split('?d=')[1].split('&')[0]+"&lang="+lang+"&host="+document.location.search.split('host=')[1].split('&')[0]; + showLog(snd); + showLog(' host: '+host); + fetch(snd).then((response) => {return response.json();}).then((rlocale) => { + if (rlocale.length>=3 && rlocale[0].ws) { + $('#openTable').html('<tr><td colspan="5">' + locale[lang].openSelect + '</td></tr>'); + for (let i=0; i<3; i++) { + $('#openTable').html($('#openTable').html()+'<tr><td>'+(i+1)+'</td><td>'+rlocale[i].title+'</td><td>'+rlocale[i].description+'</td><td>'+rlocale[i].exename+'</td></tr>'); + window.openData.push(rlocale[i]); + } + } + window.openData.push(txt); + showLog('window.openData1'); + showLog(window.openData); + mode = 'openTable'; + txt = ''; + }); + } + if (mode=='openTable' && txt.length>0) { + showLog('window.openData2'); + showLog(window.openData); + showLog(txt); + txt = txt.replace('numero ', ''); + let num = -1; + if (txt.indexOf(locale[lang].zero)>-1) num = -1; + if (txt.indexOf(locale[lang].one)>-1) num = 0; + if (txt.indexOf(locale[lang].two)>-1) num = 1; + if (txt.indexOf(locale[lang].three)>-1) num = 2; + if (!isNaN(txt)) num = txt - 1; + showLog(num); + const exe = (window.openData[num].path? window.openData[num].path: '/runtime/bin/')+ window.openData[num].exename + '&host=' + document.location.search.split('host=')[1].split('&')[0]; + showLog(exe); + if (num>-1) { + showLog("https://puma-01.elettra.eu/knob/launcher.php?open="+exe); + fetch("https://puma-01.elettra.eu/knob/launcher.php?open="+exe) + .then((response) => {return response.json();}) + .then((rlocale) => {showLog(rlocale);}); + showLog("./talk.php?speech="+window.openData[3]+"&lang="+lang+"&id="+window.openData[num].id); + fetch("./talk.php?speech="+window.openData[3]+"&lang="+lang+"&id="+window.openData[num].id) + .then((response) => {return response.json();}) + .then((rlocale) => {showLog(rlocale);}); + } + mode = ''; + $('#openTable').html(''); + } + if (locale[lang].search.indexOf(tok)>-1) { + mode = 'search'; + // txt = txt.replace(tok+' ', '').replace('ing', 'inj').replace('inch', 'inj').split(' ').join('_').split('.').join('_').toUpperCase(); + txt = txt.replace(tok+' ', '').split(' ').join('_').split('.').join('_').toUpperCase(); + const token = document.location.search.indexOf('?d=')>-1? '&token='+document.location.search.split('?d=')[1].split('&')[0]: ''; + const snd = './talk.php?search='+txt+token+"&lang="+lang; + $('#openTable').html('<tr><td colspan="5">' + locale[lang].searchCorrect + '</td></tr>'); + $('#openTable').html('<tr><td>'+txt+'</td><td><input id="sname" class="sname"></input></td><td><button class="btn btn-primary" onClick="findComponent($(\"#sname\").val)">'+locale[lang].searchSubmit+'</button></td></tr>'); + $(function() {$(".sname").autocomplete({source: names, select: function(event, ui) {findComponent(ui.item.value, txt); return false;}});}); + showLog(snd); + fetch(snd).then((response) => {return response.text();}).then((rlocale) => {showLog(rlocale); /*alert(rlocale);*/}); + mode = ''; + } + if (locale[lang].list.indexOf(tok)>-1) { + mode = 'list'; + txt = txt.replace(tok, '').replace(' ', ''); + } + if (mode=='list') { + const snd = './talk.php?list='+tok+'&token='+document.location.search.split('?d=')[1].split('&')[0]; + fetch(snd).then((response) => {return response.json();}).then((rlocale) => {showLog(rlocale); alert(rlocale);}); + mode = ''; + } + showLog('mode: '+mode+', tok: '+tok); + } + return; + // transcript.replace('sky ',''); + } + + function findComponent(txt, speech) { + console.log(txt); + const token = document.location.search.indexOf('?d=')>-1? '&token='+document.location.search.split('?d=')[1].split('&')[0]: ''; + const snd = './talk.php?search='+txt+token+"&lang="+lang+"&speech="+speech; + showLog(snd); + fetch(snd).then((response) => {return response.text();}).then((rlocale) => {showLog(rlocale); /*alert(rlocale);*/}); + } + if (!String.prototype.replaceAll) { + String.prototype.replaceAll = function(search, replace) { + return this.split(search).join(replace); + }; + } + function extractId(name) { + if (name.indexOf('(')>-1) { + let tok = name.split('('); + return [tok[0].replaceAll('.','_').replaceAll(' ','_').replace(/_+$/, ''), tok[1].replaceAll('.','_').replaceAll(' ','_').replaceAll(')','').replace(/_+$/, '')]; + } + return [name.replaceAll('.','_').replaceAll(' ','_').replace(/_+$/, ''), false]; + } + function initLattice(sections) { + for (let j=0; j<sections.length; j++) { // if(i>1) break; + const components = sections[j].components; + if (components) for (let i=0; i<components.length; i++) { + const id = extractId(components[i].name); + names.push(id[0]); + if (components[i].embedded) { + for (let j=0; j<components[i].embedded.length; j++) {names.push(components[i].embedded[j]);} + } + } + } + } + function initSearch(latticeFile) { + fetch(latticeFile).then((response) => {return response.json();}).then((flattice) => { + lattice = flattice; + if (Object.keys(lattice).length>0) { + for (let i in lattice) { + if (lattice[i].sections) initLattice(lattice[i].sections); + } + } + }); + } + + recognition.onresult = (event) => { + let interimTranscript = ''; + for (let i = event.resultIndex, len = event.results.length; i < len; i++) { + let transcript = event.results[i][0].transcript; + // const dt = + new Time() - t0; + if (event.results[i][0].transcript.length) showLog(' mode '+mode+', i: '+i+', transcripts: '+event.results[i][0].transcript+(event.results[i][1]? ', transcripts: '+event.results[i][1].transcript:'')+'\n'); + if (event.results[i].isFinal) { + quick_add(transcript.toLowerCase()); + var meno = transcript.toLowerCase().indexOf('meno '); + console.log(meno, transcript.substring(meno+5,meno+6), transcript.substring(meno+5,meno+6) % 1); + if (meno>-1 && transcript.substring(meno+5,meno+6) % 1 === 0) { + console.log('replace'); + transcript = transcript.replace(transcript.substring(meno,meno+5), '-'); + } + // finalTranscript += transcript+'<br>\\n'; + if (transcript.length>0) finalTranscript = transcript; + } else { + interimTranscript += transcript+'<br>\\n'; + } + document.getElementById('transcriptDiv').innerHTML = '<i style=\"color:#ddd;\">' + interimTranscript + '</i>'; + } + // document.getElementById('transcriptDiv').innerHTML = finalTranscript + '<i style=\"color:#00d;\">' + interimTranscript + '</i>'; + document.getElementById('transcriptDiv').innerHTML = '<i style=\"color:#00d;\">' + finalTranscript + '</i>'; + }; + showLog('start'+'\n'); + recognition.start();