API Docs for: 2.0.0

public/src/SAGE2_Speech.js

// SAGE2 is available for use under the SAGE2 Software License
//
// University of Illinois at Chicago's Electronic Visualization Laboratory (EVL)
// and University of Hawai'i at Manoa's Laboratory for Advanced Visualization and
// Applications (LAVA)
//
// See full text, terms and conditions in the LICENSE.txt included file
//
// Copyright (c) 2014-17

"use strict";

/* global showSAGE2Message webkitSpeechGrammarList displayUI*/

var SAGE2_speech = {};
SAGE2_speech.hasInit            = false;
SAGE2_speech.webkitSR           = null;
SAGE2_speech.recognizing        = false;
SAGE2_speech.final_transcript   = false;
SAGE2_speech.interim_transcript = false;
SAGE2_speech.firstNameMention   = false;
// Should be set by server, this space is critical
SAGE2_speech.nameMarker         = "sage ";
SAGE2_speech.errorNotAllowed    = false;
SAGE2_speech.errorCount         = -1;
SAGE2_speech.errorTime          = null;
SAGE2_speech.interimStart       = -1;
SAGE2_speech.interimId          = null;
// Restart listener for mouse hold
SAGE2_speech.mouseHoldTimeNeeded = 400; // Milliseconds
SAGE2_speech.mouseHoldTimeoutId  = null;
SAGE2_speech.mouseHoldActivated  = false;
SAGE2_speech.mouseHoldStartPos   = {x: -100, y: -100};
SAGE2_speech.mouseHoldMoveLimit  = {x: 10, y: 10};
SAGE2_speech.mouseIsDown         = false;
SAGE2_speech.mouseUpCancel       = false;
// Listening variables
SAGE2_speech.showListening      = false;
SAGE2_speech.listentingInfo     = null;
SAGE2_speech.mousePosition      = {
	x: window.innerWidth / 2,
	y: 0,
	dx: 0,
	dy: 0
};
// Speech synthesis
SAGE2_speech.ttsConverter       = null;
SAGE2_speech.synth              = null;
SAGE2_speech.voices             = null;
SAGE2_speech.isEnabled          = true;
SAGE2_speech.uiMenuEntryEnable  = true;
SAGE2_speech.uiMenuEntryDisable = true;


/**
 * Intializes the speech recognition object.
 *
 * @method init
 */
SAGE2_speech.init = function() {
	this.hasInit = true;
	if (!("webkitSpeechRecognition" in window)) {
		console.log("SpeechRecognition> This browser doesn't support SpeechRecognition API");
	} else {
		console.log("SpeechRecognition> API exists, beginning setup");
		// The contructor is actually lower case
		this.webkitSR = new webkitSpeechRecognition(); // eslint-disable-line
		this.webkitSR.continuous = false;
		this.webkitSR.interimResults = true;

		// Give weight to the system name
		var grammar = '#JSGF V1.0; grammar noun; public <noun> = ' + 'Sage' + ' ;';
		if (webkitSpeechGrammarList) {
			var speechRecognitionList = this.webkitSR.grammars;
			// 0->1 weight of grammar likely to happen compared to others.
			speechRecognitionList.addFromString(grammar, 1);
			this.webkitSR.grammars = speechRecognitionList;
		}

		// Initialize other parts
		this.initMouseholdToStart();
		this.speechSynthesisInit();
		this.listeningVisualInit();

		// Triggers with event onstart, reset variables

		this.webkitSR.onstart = function() {
			console.log("SpeechRecognition> SAGE2_speech started");
			this.recognizing = true;
			// Cleanup variables
			SAGE2_speech.showListening = true;
			SAGE2_speech.firstNameMention = false;
			SAGE2_speech.listentingInfo.imageDetectedInterim = false;
			SAGE2_speech.listentingInfo.imageCycleFrame = 0;
			SAGE2_speech.mouseUpCancel = false;
			// Start blank
			document.getElementById(SAGE2_speech.listentingInfo.transcriptId).textContent = "";
			// Start blank
			document.getElementById(SAGE2_speech.listentingInfo.transcriptId).style.width = "1px";
			document.getElementById(SAGE2_speech.listentingInfo.transcriptId).style.visibility = "hidden";
		};

		// After getting a result, but this also includes pieces
		// That aren't detected as full sentences
		this.webkitSR.onresult = function(event) {
			this.interim_transcript = " ";
			for (var i = event.resultIndex; i < event.results.length; ++i) {
				if (event.results[i].isFinal) {
					if (SAGE2_speech.mouseUpCancel) {
						return;
					}
					this.final_transcript = event.results[i][0].transcript;
					if (SAGE2_speech.mouseHoldActivated
						&& !this.final_transcript.toLowerCase().includes(SAGE2_speech.nameMarker)) {
						this.final_transcript = SAGE2_speech.nameMarker + this.final_transcript;
					}
					console.log("SpeechRecognition> final_transcript(",
						event.results[i][0].confidence, "%):" + this.final_transcript);

					// Remove the checker for stuck transcript
					if (SAGE2_speech.interimId) {
						window.clearTimeout(SAGE2_speech.interimId);
						SAGE2_speech.interimId = null;
					}

					// If not a local activation give to server
					if (!SAGE2_speech.doesTranscriptActivateLocally(this.final_transcript)) {
						// This is the final transcript to log
						wsio.emit("serverDataSetValue", {
							nameOfValue: "voiceToActionInterimTranscript",
							value: "Submitted phrase:" + this.final_transcript,
							confidence: event.results[i][0].confidence
						});
						// Do something with it now
						wsio.emit('voiceToAction', {words: this.final_transcript, confidence: event.results[i][0].confidence});
						document.getElementById("voiceTranscriptActual").textContent =
							"(" + parseInt(event.results[i][0].confidence * 100) + "%) " + this.final_transcript;
					}
				} else {
					this.interim_transcript += event.results[i][0].transcript;
					console.log("SpeechRecognition> interim_transcript:", this.interim_transcript);
					// Put transcript in visual
					let tdiv  = document.getElementById(SAGE2_speech.listentingInfo.transcriptId);
					tdiv.textContent = this.interim_transcript;
					// Calculate how wide to make it
					let tmeasureDiv  = document.getElementById(SAGE2_speech.listentingInfo.tMeasureId);
					tmeasureDiv.textContent = this.interim_transcript;
					tdiv.style.width = tmeasureDiv.clientWidth + "px";
					// Make the element visibe on screen
					tdiv.style.visibility = "visible";
					SAGE2_speech.listentingInfo.imageDetectedInterim = true;
					if (SAGE2_speech.interimId) {
						// This should get cleared out each transcript change
						window.clearTimeout(SAGE2_speech.interimId);
						SAGE2_speech.interimId = null;
					}

					// If ever gets stuck on a single interim transcript for timeout restart
					SAGE2_speech.interimId = setTimeout(() => {
						SAGE2_speech.init();
					}, 6000);

					// If this is the first time name is said then play listening sound
					if ((!SAGE2_speech.firstNameMention) &&
						(!SAGE2_speech.mouseHoldActivated) &&
						this.interim_transcript.toLowerCase().includes(SAGE2_speech.nameMarker)) {
						SAGE2_speech.firstNameMention = true;
						console.log("SpeechRecognition> speech marker detected");
					} if (SAGE2_speech.firstNameMention) {
						// If first name is mentioned set the transcript
						let transcript = this.interim_transcript.toLowerCase();
						// Possible that the transcript changes with an update
						if (!transcript.includes(SAGE2_speech.nameMarker)) {
							SAGE2_speech.firstNameMention = false;
						} else {
							transcript = transcript.substring(transcript.indexOf(SAGE2_speech.nameMarker));
							transcript = transcript.substring(transcript.indexOf(" ") + 1);
							document.getElementById("voiceTranscriptActual").textContent = transcript;
						}
					}
					wsio.emit("serverDataSetValue", {
						nameOfValue: "voiceToActionInterimTranscript",
						value: "Detected Words:" + this.interim_transcript
					});
				}
			}
		}; //end onresult

		this.webkitSR.onerror = function(e) {
			console.log("SpeechRecognition> webkitSpeechRecognition error:", e.error);
			console.dir("SpeechRecognition>", e);
			// This particular error will be triggered if the microphone is blocked.
			if (e.error === "not-allowed") {
				SAGE2_speech.errorNotAllowed = true;
				// The toggle wil swap it to false
				SAGE2_speech.isEnabled = true;
				SAGE2_speech.toggleVoiceRecognition();
			}
		};

		// After ending restart
		this.webkitSR.onend = function() {
			this.recognizing = false;
			SAGE2_speech.showListening = false;
			// Cleanup variables
			SAGE2_speech.mouseHoldActivated = false;
		};
		this.toggleSAGE2_speech();
	}
};

/**
 * Toggles speech recongition on / off.
 *
 * @method toggleSAGE2_speech
 */
SAGE2_speech.toggleSAGE2_speech = function() {
	if (this.recognizing) {
		this.webkitSR.stop();
		return;
	}
	this.final_transcript = " ";
	this.webkitSR.lang = "en-US";
};

/**
 * Check if transcript is a local speech command.
 *
 * @method doesTranscriptActivateLocally
 */
SAGE2_speech.doesTranscriptActivateLocally = function(transcript) {
	// UI debug activator check
	transcript = transcript.toLowerCase();

	// Local checks
	var localActions = {
		showSpeechDebug: {
			keywords: ["show voice debug", "show speech debug"],
			successFunction: SAGE2_speech.activateSpeechDebug
		},
		tellTheTime: {
			keywords: ["what time"],
			successFunction: SAGE2_speech.sayTheTime
		},
		tellTheDate: {
			keywords: ["what today", "what date"],
			successFunction: SAGE2_speech.sayTheDate
		},
		openHelp: {
			keywords: ["help", "what command use", "what command available", "what can say"],
			successFunction: SAGE2_speech.openVoiceHelpPage
		}
	};
	var actionNames = Object.keys(localActions);
	var keywords;
	for (let i = 0; i < actionNames.length; i++) {
		for (let keySets = 0; keySets < localActions[actionNames[i]].keywords.length; keySets++) {
			keywords = localActions[actionNames[i]].keywords[keySets].toLowerCase().split(" ");
			for (let k = 0; k < keywords.length; k++) {
				if (!transcript.includes(keywords[k])) {
					// Exits out of keyword loop
					break;
				} else if (k == keywords.length - 1) {
					console.log("SpeechRecognition> Local voice function:" + actionNames[i]);
					localActions[actionNames[i]].successFunction();
					return true;
				}
			}
		}
	}
	return false;
};

/**
 * Makes the speech debug text visible.
 *
 * @method activateSpeechDebug
 */
SAGE2_speech.activateSpeechDebug = function() {
	document.getElementById("voiceTranscriptOuterContainer").style.visibility = "visible";
	document.getElementById("voiceTranscriptActual").textContent = "Voice debug activated";
};

/**
 * UI will speak the time.
 *
 * @method sayTheTime
 */
SAGE2_speech.sayTheTime = function() {
	var d = new Date();
	var whatToSay;
	if (d.getHours() > 12) {
		whatToSay = (d.getHours() - 12) + " " + (d.getMinutes()) +  " P M";
	} else {
		whatToSay = d.getHours() + " " + (d.getMinutes()) +  " A M";
	}
	SAGE2_speech.textToSpeech(whatToSay);
};

/**
 * UI will speak the date.
 *
 * @method sayTheDate
 */
SAGE2_speech.sayTheDate = function() {
	var d = new Date();
	var whatToSay = "";
	switch (d.getDay()) {
		case 0: whatToSay = "Sunday ";    break;
		case 1: whatToSay = "Monday ";    break;
		case 2: whatToSay = "Tuesday ";   break;
		case 3: whatToSay = "Wednesday "; break;
		case 4: whatToSay = "Thursday ";  break;
		case 5: whatToSay = "Friday ";    break;
		case 6: whatToSay = "Saturday ";  break;
	}
	switch (d.getMonth()) {
		case  0: whatToSay += "January ";   break;
		case  1: whatToSay += "February ";  break;
		case  2: whatToSay += "March ";     break;
		case  3: whatToSay += "April ";     break;
		case  4: whatToSay += "May ";       break;
		case  5: whatToSay += "June ";      break;
		case  6: whatToSay += "July ";      break;
		case  7: whatToSay += "August ";    break;
		case  8: whatToSay += "September "; break;
		case  9: whatToSay += "October ";   break;
		case 10: whatToSay += "November ";  break;
		case 11: whatToSay += "December ";  break;
	}
	whatToSay += d.getDate();
	SAGE2_speech.textToSpeech(whatToSay);
};

/**
 * Will open a page containing the voice help.
 *
 * @method openVoiceHelpPage
 */
SAGE2_speech.openVoiceHelpPage = function() {
	window.open("help/index.html#voiceQuickReference");
};

/**
 * Initializes by creating listeners to restart and enable sending if mouse is held down.
 *
 * @method initMouseholdToStart
 */
SAGE2_speech.initMouseholdToStart = function() {
	// On mouse down, if over canvas, set a timeout to check for listening
	document.addEventListener("mousedown", function(e) {
		if (!SAGE2_speech.isEnabled) {
			// If speech recognition was disabled by user, don't
			return;
		}
		// Reset the move
		SAGE2_speech.mousePosition.dx = 0;
		SAGE2_speech.mousePosition.dy = 0;
		SAGE2_speech.mouseHoldTimeoutId = null;
		// Only activate if over the sage2UICanvas
		if (event.target.id === "sage2UICanvas"
			|| document.getElementById("sage2pointerDialog").style.display === "block") {
			SAGE2_speech.mouseHoldStartPos.x = e.clientX;
			SAGE2_speech.mouseHoldStartPos.y = e.clientY;
			// Clear out existing timeouts if they exist.
			if (SAGE2_speech.mouseHoldTimeoutId) {
				window.clearTimeout(SAGE2_speech.mouseHoldTimeoutId);
				SAGE2_speech.mouseHoldTimeoutId = null;
			}
			SAGE2_speech.mouseIsDown = true;
			// After timeout attempt speech recognition if valid
			SAGE2_speech.mouseHoldTimeoutId = setTimeout(function() {
				// Mouseup will set SAGE2_speech.mouseIsDown to false
				if (SAGE2_speech.mouseIsDown) {
					SAGE2_speech.mouseHoldTimeoutId = null;
					SAGE2_speech.enableMouseholdToStart(e);
				}
			}, SAGE2_speech.mouseHoldTimeNeeded);
		}
	});
	// On mouse up, if there is a timer waiting for speech recognition remove it
	document.addEventListener("mouseup", function() {
		SAGE2_speech.mouseIsDown = false;
		if (SAGE2_speech.mouseHoldTimeoutId) {
			window.clearTimeout(SAGE2_speech.mouseHoldTimeoutId);
			SAGE2_speech.mouseHoldTimeoutId = null;
		}
		SAGE2_speech.mouseUpCancel = true;
		SAGE2_speech.webkitSR.stop();
	});
};

/**
 * Starts the mouse hold recognition.
 * If there has not been a 1st name mention. Restart listener.
 * Show listening.
 * Mark this as getting an automatic name mention.
 *
 * @method enableMouseholdToStart
 */
SAGE2_speech.enableMouseholdToStart = function(e) {
	var dx = Math.abs(SAGE2_speech.mousePosition.dx);
	var dy = Math.abs(SAGE2_speech.mousePosition.dy);
	// If mouse cursor is still within move limit
	if (dx < SAGE2_speech.mouseHoldMoveLimit.x && dy < SAGE2_speech.mouseHoldMoveLimit.y) {
		// If it isn't listening.
		if (!SAGE2_speech.firstNameMention) {
			this.webkitSR.stop(); // Stop
			setTimeout(function() {
				SAGE2_speech.mouseHoldActivated = true;
				// Start
				SAGE2_speech.webkitSR.start();
			}, SAGE2_speech.mouseHoldTimeNeeded / 2);
			// Not sure best minimum
		}
	}
};

/**
 * Creates and intializes the listening visual.
 *
 * @method listeningVisualInit
 */
SAGE2_speech.listeningVisualInit = function () {
	if (SAGE2_speech.listentingInfo) {
		return;
	}
	SAGE2_speech.listentingInfo = {
		divId: "divForListentingVisual",
		canvasId: "canvasVoiceListening",
		canvasWidth:  100,
		canvasHeight:  20,
		circleRadius:  25,
		// Swap to images
		// Multiple images
		imageId: ["imageEar0", "imageEar1", "imageEar2", "imageEar3"],
		imageWidth:  40,
		imageHeight: 40,
		imageFrameDuration: 75, // In ms
		imageFrameStartTime: 0,
		imageCycleFrame: 0,
		imageDetectedInterim: false,
		transcriptId: "listeningTranscriptDiv",
		tMeasureId: "measurementForTranscriptDiv",
		transcriptCharacterPadding: 7
	};

	var d = document.createElement("div");
	d.id = SAGE2_speech.listentingInfo.divId;
	d.style.position = "absolute";
	// unsure of highest value
	d.style.zIndex = 10000;

	// Transcript holder appendChild before images to make transcript visually behind them.
	var transcriptDiv = document.createElement("div");
	transcriptDiv.id = SAGE2_speech.listentingInfo.transcriptId;
	transcriptDiv.style.position = "absolute";
	// Not as big as image
	transcriptDiv.style.height = (SAGE2_speech.listentingInfo.imageHeight / 2) + "px";
	// Center the transcript
	transcriptDiv.style.top = (SAGE2_speech.listentingInfo.imageHeight / 4 - 6) + "px";
	// Put right of image
	transcriptDiv.style.left = (SAGE2_speech.listentingInfo.imageWidth - 20) + "px";
	// Match the left offset from image
	transcriptDiv.style.paddingLeft = "25px";
	// Match the left offset from image
	transcriptDiv.style.paddingTop = "5px";
	transcriptDiv.style.border = "1px solid black";
	transcriptDiv.style.borderRadius = "1px 20px 20px 1px";
	transcriptDiv.style.background   = "#e6e6e6";
	// Start blank
	transcriptDiv.textContent = "";
	transcriptDiv.style.width = (transcriptDiv.textContent.length
		* SAGE2_speech.listentingInfo.transcriptCharacterPadding) + "px";
	d.appendChild(transcriptDiv);

	// Create measurement div
	var transcriptMeasuringDiv = document.createElement("div");
	transcriptMeasuringDiv.id = SAGE2_speech.listentingInfo.tMeasureId;
	transcriptMeasuringDiv.style.paddingLeft = "25px";
	transcriptMeasuringDiv.style.paddingTop = "5px";
	transcriptMeasuringDiv.style.position = "absolute";
	transcriptMeasuringDiv.style.float = "left";
	transcriptMeasuringDiv.style.whiteSpace = "nowrap";
	transcriptMeasuringDiv.style.visibility = "hidden";
	d.appendChild(transcriptMeasuringDiv);

	// Images
	// Currently 4 frames 0,1,2,3
	for (let i = 0; i < SAGE2_speech.listentingInfo.imageId.length; i++) {
		let c = document.createElement("img");
		c.id = SAGE2_speech.listentingInfo.imageId[i];
		c.style.width = SAGE2_speech.listentingInfo.imageWidth + "px";
		c.style.height = SAGE2_speech.listentingInfo.imageHeight + "px";
		c.src = "images/speech-ear" + i + ".png";
		c.style.position = "absolute";
		if (i > 0) {
			c.style.display = "none";
		}
		d.appendChild(c);
	}

	// Add to page
	document.body.appendChild(d);

	// Add listener for mouse move
	document.addEventListener("mousemove", SAGE2_speech.mouseMoveListener);

	// Start drawing
	window.requestAnimationFrame(SAGE2_speech.drawListeningVisual);
};

/**
 * Updates position of mouse location to move the listening visual.
 *
 * @method mouseMoveListener
 * @param {Object} e - The mouse event, should be standard.
 */
SAGE2_speech.mouseMoveListener = function (e) {
	SAGE2_speech.mousePosition.x  =  e.clientX;
	SAGE2_speech.mousePosition.y  =  e.clientY;
	SAGE2_speech.mousePosition.dx += e.movementX;
	SAGE2_speech.mousePosition.dy += e.movementY;
};

/**
 * Animated the listening visual
 *
 * @method drawListeningVisual
 */
SAGE2_speech.drawListeningVisual = function () {
	// Move to below mouse cursor
	var listeningDiv = document.getElementById(SAGE2_speech.listentingInfo.divId);
	if (SAGE2_speech.showListening) {
		listeningDiv.style.left = (SAGE2_speech.mousePosition.x
			- SAGE2_speech.listentingInfo.imageWidth / 2) + "px";
		listeningDiv.style.top = (SAGE2_speech.mousePosition.y - 10
			- SAGE2_speech.listentingInfo.imageHeight) + "px";
		if (document.pointerLockElement) {
			listeningDiv.style.left = "50px";
			listeningDiv.style.top  = "50px";
		}
	} else {
		listeningDiv.style.left = "-100px";
		listeningDiv.style.top  = "-100px";
		window.requestAnimationFrame(SAGE2_speech.drawListeningVisual);
		// Dont forget to recall frame if not showing, otherwise will not restart.
		return;
	}

	// Only draw animation if interim activated
	if (SAGE2_speech.listentingInfo.imageDetectedInterim) {
		if ((Date.now() - SAGE2_speech.listentingInfo.imageFrameStartTime)
			>= SAGE2_speech.listentingInfo.imageFrameDuration) {
			SAGE2_speech.listentingInfo.imageCycleFrame++;
			if (SAGE2_speech.listentingInfo.imageCycleFrame >= SAGE2_speech.listentingInfo.imageId.length) {
				SAGE2_speech.listentingInfo.imageCycleFrame = 0;
			}
			for (let i = 0; i < SAGE2_speech.listentingInfo.imageId.length; i++) {
				if (i === SAGE2_speech.listentingInfo.imageCycleFrame) {
					document.getElementById(SAGE2_speech.listentingInfo.imageId[i]).style.display = "block";
				} else {
					document.getElementById(SAGE2_speech.listentingInfo.imageId[i]).style.display = "none";
				}
			}
			SAGE2_speech.listentingInfo.imageFrameStartTime = Date.now();
		}
	}

	// Set into next animation frame.
	window.requestAnimationFrame(SAGE2_speech.drawListeningVisual);
};

/**
 * Set whether or not to show the listening visual.
 *
 * @method setListeningVisual
 * @param {Boolean} shouldShow - true to show, otherwise false.
 */
SAGE2_speech.setListeningVisual = function(shouldShow) {
	// In case called without params
	shouldShow = (shouldShow) ? shouldShow : false;
	SAGE2_speech.showListening = shouldShow;
};

/**
 * Initialize speech synthesis
 *
 * @method speechSynthesisInit
 */
SAGE2_speech.speechSynthesisInit = function() {
	// Speech setup
	SAGE2_speech.ttsConverter = new SpeechSynthesisUtterance();
	SAGE2_speech.synth        = window.speechSynthesis;
	// Timeout needed because the synch seemed to be an asynchronous action.
	setTimeout(function() {
		SAGE2_speech.voices = SAGE2_speech.synth.getVoices();
		var kyoko    = {found: false};
		var samantha = {found: false};
		var victoria = {found: false};
		if (SAGE2_speech.voices.length > 0) {
			for (let i = 0; i < SAGE2_speech.voices.length; i++) {
				if (SAGE2_speech.voices[i].name === "Samantha") {
					samantha.found = true;
					samantha.index = i;
				} else if (SAGE2_speech.voices[i].name === "Victoria") {
					victoria.found = true;
					victoria.index = i;
				} else if (SAGE2_speech.voices[i].name === "Kyoko") {
					kyoko.found = true;
					kyoko.index = i;
				}
			}
		} else {
			console.log("SpeechRecognition> Speech synthesis voices not available");
		}
		if (samantha.found) {
			// Priority to samantha
			SAGE2_speech.ttsConverter.voice = SAGE2_speech.voices[samantha.index];
		} else if (kyoko.found) {
			// Then kyoko
			SAGE2_speech.ttsConverter.voice = SAGE2_speech.voices[kyoko.index];
		} else if (victoria.found) {
			// Finally victoria
			SAGE2_speech.ttsConverter.voice = SAGE2_speech.voices[victoria.index];
		}
		SAGE2_speech.ttsConverter.lang = 'en-US';
	}, 2000);
};

/**
 * Will attempt to voice the given what to say.
 *
 * @method textToSpeech
 * @param {String} whatToSay - will attempt to say this.
 */
SAGE2_speech.textToSpeech = function (whatToSay) {
	if (!SAGE2_speech.ttsConverter) {
		return;
	}
	try {
		console.log("SpeechRecognition> Speech:", whatToSay);
		SAGE2_speech.ttsConverter.text = whatToSay;
		window.speechSynthesis.speak(SAGE2_speech.ttsConverter);
		setTimeout(function() {
			SAGE2_speech.restartIfMouseStillDownAndNotTalking();
		}, 100);
	} catch (e) {
		console.log("SpeechRecognition> Error with textToSpeech");
	}
};

/**
 * Will toggle whether or not voice recognition starts for long click and hold.
 *
 * @method toggleVoiceRecognition
 */
SAGE2_speech.toggleVoiceRecognition = function () {
	// Toogle: if enabled, disable and make menu allow reenable
	// In the top menu bar (webix ui)
	if (SAGE2_speech.isEnabled) {
		$$('topmenu').showItem('voiceserviceEnable_menu');
		$$('topmenu').hideItem('voiceserviceDisable_menu');
	} else {
		// Else was disabled, enable it and allow disable
		$$('topmenu').showItem('voiceserviceDisable_menu');
		$$('topmenu').hideItem('voiceserviceEnable_menu');
		if (!SAGE2_speech.hasInit) {
			SAGE2_speech.init();
		}
	}
	SAGE2_speech.isEnabled = !SAGE2_speech.isEnabled;
};

/**
 * Activates after receiving from server what the name marker is.
 * Only after receiving will it init the voice recognition.
 *
 * @method setNameMarker
 */
SAGE2_speech.setNameMarker = function (nameMarkerFromServer) {
	// Console.log("Voice marker:'" + nameMarkerFromServer + "'");
	// Server should include a space
	SAGE2_speech.nameMarker = nameMarkerFromServer.toLowerCase();

	// If voice is enabled, begin init
	if (displayUI.config.voice_commands.enabled) {
		SAGE2_speech.init();
		// Toggle will flip
		SAGE2_speech.isEnabled = false;
	} else {
		// Otherwise, begin disabled. Toggle will flip value from true to false
		SAGE2_speech.isEnabled = true;
	}
	SAGE2_speech.toggleVoiceRecognition();
};

/**
 * Restarts the voice recognition if mouse is held down, after response speech is done.
 *
 * @method restartIfMouseStillDownAndNotTalking
 */
SAGE2_speech.restartIfMouseStillDownAndNotTalking = function () {
	// If computer response is still going and user mouse still down, delay check
	if (window.speechSynthesis.speaking && SAGE2_speech.mouseIsDown) {
		setTimeout(function() {
			SAGE2_speech.restartIfMouseStillDownAndNotTalking();
		}, 100);
	} else if (SAGE2_speech.mouseIsDown) {
		// If not speaking and mouse is still down, restart
		setTimeout(function() {
			SAGE2_speech.mouseHoldActivated = true;
			SAGE2_speech.webkitSR.start();
		}, 100);
	}
};