function SpeechPlugin() { 'use strict'; return { getModuleName: function() { return 'SpeechPlugin'; }, interpreterProxy: null, primHandler: null, voiceInput: null, semaphoreIndex: null, shouldListen: false, recognition: null, synth: self.speechSynthesis, setInterpreter: function(anInterpreter) { this.interpreterProxy = anInterpreter; this.primHandler = this.interpreterProxy.vm.primHandler; return true; }, /* * Speech Synthesis Primitive */ primitiveSpeak: function(argCount) { var text, voice; if (argCount === 1) { text = this.interpreterProxy.stackValue(0).bytesAsString(); } else if (argCount === 2) { text = this.interpreterProxy.stackValue(1).bytesAsString(); var voiceName = this.interpreterProxy.stackValue(0).bytesAsString(); voice = this.synth.getVoices().filter(function(voice) { return voice.name === voiceName; }); } else { return false; } var msg = new SpeechSynthesisUtterance(text); if (voice && voice.length > 0) { msg.voice = voice[0]; } this.synth.speak(msg); this.interpreterProxy.pop(argCount); return true; }, /* * Speech Recognition Primitives */ primitiveRegisterSemaphore: function(argCount) { if (argCount !== 1) return false; this.semaphoreIndex = this.interpreterProxy.stackIntegerValue(0); this.interpreterProxy.pop(argCount); return true; }, primitiveUnregisterSemaphore: function(argCount) { if (argCount !== 0) return false; this.semaphoreIndex = null; return true; }, primitiveGetLastRecognitionResult: function(argCount) { if (argCount !== 0) return false; if (this.semaphoreIndex === null) return false; if (this.voiceInput === null) return false; var stString = this.primHandler.makeStArray(this.voiceInput); this.voiceInput = []; this.interpreterProxy.popthenPush(1, stString); return true; }, primitiveStartListening: function(argCount) { if (argCount !== 0) return false; this.recognition = new webkitSpeechRecognition(); this.recognition.lang = 'en-US'; // en-GB this.recognition.interimResults = true; // this.recognition.maxAlternatives = 1; this.recognition.onresult = this._onRecognitionResult.bind(this); this.recognition.onerror = function(event) { console.log(event); }; /* * Imitate `this.recognition.continuous = true` because it is currently * only supported by Google Chrome. */ this.recognition.onend = function(event) { if (this.shouldListen) { this.recognition.start(); } }.bind(this); this.shouldListen = true; this.recognition.start(); return true; }, _onRecognitionResult: function(event) { this.voiceInput = []; for (var i = event.resultIndex; i < event.results.length; i++) { var result = event.results[i]; var sqResult = [result.isFinal, []]; for (var j = 0; j < result.length; j++) { var transcript = result[j].transcript; var now = Date.now(); sqResult[1].push([transcript, result[j].confidence]); } if (sqResult[1].length > 0) { this.voiceInput.push(sqResult); } } if (this.semaphoreIndex !== null && this.voiceInput.length > 0) { this.primHandler.signalSemaphoreWithIndex(this.semaphoreIndex); } }, primitiveStopListening: function(argCount) { if (argCount !== 0) return false; this.voiceInput = []; this.recognition.stop(); this.shouldListen = false; return true; }, }; } function registerSpeechPlugin() { if (typeof Squeak === "object" && Squeak.registerExternalModule) { if (('webkitSpeechRecognition' in self)) { Squeak.registerExternalModule('SpeechPlugin', SpeechPlugin()); } else { console.warn('SpeechPlugin: Web Speech API is not supported by this browser.'); } } else self.setTimeout(registerSpeechPlugin, 100); }; registerSpeechPlugin();