Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(7683)

Unified Diff: chrome/common/extensions/docs/examples/extensions/ttsdebug/ttsdebug.js

Issue 8114011: Add sample extensions that use the text-to-speech (TTS) API. (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 9 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/common/extensions/docs/examples/extensions/ttsdebug/ttsdebug.js
===================================================================
--- chrome/common/extensions/docs/examples/extensions/ttsdebug/ttsdebug.js (revision 0)
+++ chrome/common/extensions/docs/examples/extensions/ttsdebug/ttsdebug.js (revision 0)
@@ -0,0 +1,685 @@
+var voiceArray;
+var trials = 3;
+var resultMap = {};
+var updateDependencyFunctions = [];
+var testRunIndex = 0;
+var emergencyStop = false;
+
+function $(id) {
+ return document.getElementById(id);
+}
+
+function isErrorEvent(evt) {
+ return (evt.type == 'error' ||
+ evt.type == 'interrupted' ||
+ evt.type == 'cancelled');
+}
+
+function logEvent(callTime, testRunName, evt) {
+ var elapsed = ((new Date() - callTime) / 1000).toFixed(3);
+ while (elapsed.length < 7) {
+ elapsed = ' ' + elapsed;
+ }
+ console.log(elapsed + ' ' + testRunName + ': ' + JSON.stringify(evt));
+}
+
+function logSpeakCall(utterance, options, callback) {
+ var optionsCopy = {};
+ for (var key in options) {
+ if (key != 'onEvent') {
+ optionsCopy[key] = options[key];
+ }
+ }
+ console.log('Calling chrome.tts.speak(\'' +
+ utterance + '\', ' +
+ JSON.stringify(optionsCopy) + ')');
+ if (callback)
+ chrome.tts.speak(utterance, options, callback);
+ else
+ chrome.tts.speak(utterance, options);
+}
+
+var tests = [
+ {
+ name: 'Baseline',
+ description: 'Ensures that the speech engine sends both start and ' +
+ 'end events, and establishes a baseline time to speak a ' +
+ 'key phrase, to compare other tests against.',
+ dependencies: [],
+ trials: 3,
+ run: function(testRunName, voiceName, callback) {
+ var callTime = new Date();
+ var startTime;
+ var warnings = [];
+ var errors = [];
+ logSpeakCall('Alpha Bravo Charlie Delta Echo', {
+ voiceName: voiceName,
+ onEvent: function(evt) {
+ logEvent(callTime, testRunName, evt);
+ if (isErrorEvent(evt)) {
+ callback(false, null, []);
+ } else if (evt.type == 'start') {
+ startTime = new Date();
+ if (evt.charIndex != 0) {
+ errors.push('Error: start event should have a charIndex of 0.');
+ }
+ } else if (evt.type == 'end') {
+ if (startTime == undefined) {
+ errors.push('Error: no "start" event received!');
+ startTime = callTime;
+ }
+ if (evt.charIndex != 30) {
+ errors.push('Error: end event should have a charIndex of 30.');
+ }
+ var endTime = new Date();
+ if (startTime - callTime > 1000) {
+ var delta = ((startTime - callTime) / 1000).toFixed(3);
+ warnings.push('Note: Delay of ' + delta +
+ ' before speech started. ' +
+ 'Less than 1.0 s latency is recommended.');
+ }
+ var delta = (endTime - startTime) / 1000;
+ if (delta < 1.0) {
+ warnings.push('Warning: Default speech rate seems too fast.');
+ } else if (delta > 3.0) {
+ warnings.push('Warning: Default speech rate seems too slow.');
+ }
+ callback(errors.length == 0, delta, warnings.concat(errors));
+ }
+ }
+ });
+ }
+ },
+ {
+ name: 'Fast',
+ description: 'Speaks twice as fast and compares the time to the baseline.',
+ dependencies: ['Baseline'],
+ trials: 3,
+ run: function(testRunName, voiceName, callback) {
+ var callTime = new Date();
+ var startTime;
+ var errors = [];
+ logSpeakCall('Alpha Bravo Charlie Delta Echo', {
+ voiceName: voiceName,
+ rate: 2.0,
+ onEvent: function(evt) {
+ logEvent(callTime, testRunName, evt);
+ if (isErrorEvent(evt)) {
+ callback(false, null, []);
+ } else if (evt.type == 'start') {
+ startTime = new Date();
+ } else if (evt.type == 'end') {
+ if (startTime == undefined)
+ startTime = callTime;
+ var endTime = new Date();
+ var delta = (endTime - startTime) / 1000;
+ var relative = delta / resultMap['Baseline'];
+ if (relative < 0.35) {
+ errors.push('2x speech rate seems too fast.');
+ } else if (relative > 0.65) {
+ errors.push('2x speech rate seems too slow.');
+ }
+ callback(errors.length == 0, delta, errors);
+ }
+ }
+ });
+ }
+ },
+ {
+ name: 'Slow',
+ description: 'Speaks twice as slow and compares the time to the baseline.',
+ dependencies: ['Baseline'],
+ trials: 3,
+ run: function(testRunName, voiceName, callback) {
+ var callTime = new Date();
+ var startTime;
+ var errors = [];
+ logSpeakCall('Alpha Bravo Charlie Delta Echo', {
+ voiceName: voiceName,
+ rate: 0.5,
+ onEvent: function(evt) {
+ logEvent(callTime, testRunName, evt);
+ if (isErrorEvent(evt)) {
+ callback(false, null, []);
+ } else if (evt.type == 'start') {
+ startTime = new Date();
+ } else if (evt.type == 'end') {
+ if (startTime == undefined)
+ startTime = callTime;
+ var endTime = new Date();
+ var delta = (endTime - startTime) / 1000;
+ var relative = delta / resultMap['Baseline'];
+ if (relative < 1.6) {
+ errors.push('Half-speed speech rate seems too fast.');
+ } else if (relative > 2.4) {
+ errors.push('Half-speed speech rate seems too slow.');
+ }
+ callback(errors.length == 0, delta, errors);
+ }
+ }
+ });
+ }
+ },
+ {
+ name: 'Interrupt and restart',
+ description: 'Interrupts partway through a long sentence and then ' +
+ 'the baseline utterance, to make sure that speech after ' +
+ 'an interruption works correctly.',
+ dependencies: ['Baseline'],
+ trials: 1,
+ run: function(testRunName, voiceName, callback) {
+ var callTime = new Date();
+ var startTime;
+ var errors = [];
+ logSpeakCall('When in the course of human events it becomes ' +
+ 'necessary for one people to dissolve the political ' +
+ 'bands which have connected them ', {
+ voiceName: voiceName,
+ onEvent: function(evt) {
+ logEvent(callTime, testRunName, evt);
+ }
+ });
+ window.setTimeout(function() {
+ logSpeakCall('Alpha Bravo Charlie Delta Echo', {
+ voiceName: voiceName,
+ onEvent: function(evt) {
+ logEvent(callTime, testRunName, evt);
+ if (isErrorEvent(evt)) {
+ callback(false, null, []);
+ } else if (evt.type == 'start') {
+ startTime = new Date();
+ } else if (evt.type == 'end') {
+ if (startTime == undefined)
+ startTime = callTime;
+ var endTime = new Date();
+ var delta = (endTime - startTime) / 1000;
+ var relative = delta / resultMap['Baseline'];
+ if (relative < 0.9) {
+ errors.push('Interrupting speech seems too short.');
+ } else if (relative > 1.1) {
+ errors.push('Interrupting speech seems too long.');
+ }
+ callback(errors.length == 0, delta, errors);
+ }
+ }
+ });
+ }, 4000);
+ }
+ },
+ {
+ name: 'Low volume',
+ description: '<b>Manual</b> test - verify that the volume is lower.',
+ dependencies: [],
+ trials: 1,
+ run: function(testRunName, voiceName, callback) {
+ var callTime = new Date();
+ logSpeakCall('Alpha Bravo Charlie Delta Echo', {
+ voiceName: voiceName,
+ volume: 0.5,
+ onEvent: function(evt) {
+ logEvent(callTime, testRunName, evt);
+ if (isErrorEvent(evt)) {
+ callback(false, null, []);
+ } else if (evt.type == 'end') {
+ callback(true, null, []);
+ }
+ }
+ });
+ }
+ },
+ {
+ name: 'High pitch',
+ description: '<b>Manual</b> test - verify that the pitch is ' +
+ 'moderately higher, but quite understandable.',
+ dependencies: [],
+ trials: 1,
+ run: function(testRunName, voiceName, callback) {
+ var callTime = new Date();
+ logSpeakCall('Alpha Bravo Charlie Delta Echo', {
+ voiceName: voiceName,
+ pitch: 1.2,
+ onEvent: function(evt) {
+ logEvent(callTime, testRunName, evt);
+ if (isErrorEvent(evt)) {
+ callback(false, null, []);
+ } else if (evt.type == 'end') {
+ callback(true, null, []);
+ }
+ }
+ });
+ }
+ },
+ {
+ name: 'Low pitch',
+ description: '<b>Manual</b> test - verify that the pitch is ' +
+ 'moderately lower, but quite understandable.',
+ dependencies: [],
+ trials: 1,
+ run: function(testRunName, voiceName, callback) {
+ var callTime = new Date();
+ logSpeakCall('Alpha Bravo Charlie Delta Echo', {
+ voiceName: voiceName,
+ pitch: 0.8,
+ onEvent: function(evt) {
+ logEvent(callTime, testRunName, evt);
+ if (isErrorEvent(evt)) {
+ callback(false, null, []);
+ } else if (evt.type == 'end') {
+ callback(true, null, []);
+ }
+ }
+ });
+ }
+ },
+ {
+ name: 'Word and sentence callbacks',
+ description: 'Checks to see if proper word and sentence callbacks ' +
+ 'are received.',
+ dependencies: ['Baseline'],
+ trials: 1,
+ run: function(testRunName, voiceName, callback) {
+ var callTime = new Date();
+ var startTime;
+ var errors = [];
+ var wordExpected = [{min: 5, max: 6},
+ {min: 11, max: 12},
+ {min: 19, max: 20},
+ {min: 25, max: 26},
+ {min: 30, max: 32},
+ {min: 37, max: 38},
+ {min: 43, max: 44},
+ {min: 51, max: 52},
+ {min: 57, max: 58}];
+ var sentenceExpected = [{min: 30, max: 32}]
+ var wordCount = 0;
+ var sentenceCount = 0;
+ var lastWordTime = callTime;
+ var lastSentenceTime = callTime;
+ var avgWordTime = resultMap['Baseline'] / 5;
+ logSpeakCall('Alpha Bravo Charlie Delta Echo. ' +
+ 'Alpha Bravo Charlie Delta Echo.', {
+ voiceName: voiceName,
+ onEvent: function(evt) {
+ logEvent(callTime, testRunName, evt);
+ if (isErrorEvent(evt)) {
+ callback(false, null, []);
+ } else if (evt.type == 'start') {
+ startTime = new Date();
+ lastWordTime = startTime;
+ lastSentenceTime = startTime;
+ } else if (evt.type == 'word') {
+ if (evt.charIndex > 0 && evt.charIndex < 62) {
+ var min = wordExpected[wordCount].min;
+ var max = wordExpected[wordCount].max;
+ if (evt.charIndex < min || evt.charIndex > max) {
+ errors.push('Got word at charIndex ' + evt.charIndex + ', ' +
+ 'was expecting next word callback charIndex ' +
+ 'in the range ' + min + ':' + max + '.');
+ }
+ if (wordCount != 4) {
+ var delta = (new Date() - lastWordTime) / 1000;
+ if (delta < 0.6 * avgWordTime) {
+ errors.push('Word at charIndex ' + evt.charIndex +
+ ' came after only ' + delta.toFixed(3) +
+ ' s, which seems too short.');
+ } else if (delta > 1.3 * avgWordTime) {
+ errors.push('Word at charIndex ' + evt.charIndex +
+ ' came after ' + delta.toFixed(3) +
+ ' s, which seems too long.');
+ }
+ }
+ wordCount++;
+ }
+ lastWordTime = new Date();
+ } else if (evt.type == 'sentence') {
+ if (evt.charIndex > 0 && evt.charIndex < 62) {
+ var min = sentenceExpected[sentenceCount].min;
+ var max = sentenceExpected[sentenceCount].max;
+ if (evt.charIndex < min || evt.charIndex > max) {
+ errors.push('Got sentence at charIndex ' + evt.charIndex +
+ ', was expecting next callback charIndex ' +
+ 'in the range ' + min + ':' + max + '.');
+ }
+ var delta = (new Date() - lastSentenceTime) / 1000;
+ if (delta < 0.75 * resultMap['Baseline']) {
+ errors.push('Sentence at charIndex ' + evt.charIndex +
+ ' came after only ' + delta.toFixed(3) +
+ ' s, which seems too short.');
+ } else if (delta > 1.25 * resultMap['Baseline']) {
+ errors.push('Sentence at charIndex ' + evt.charIndex +
+ ' came after ' + delta.toFixed(3) +
+ ' s, which seems too long.');
+ }
+ sentenceCount++;
+ }
+ lastSentenceTime = new Date();
+ } else if (evt.type == 'end') {
+ if (wordCount == 0) {
+ errors.push('Didn\'t get any word callbacks.');
+ } else if (wordCount < wordExpected.length) {
+ errors.push('Not enough word callbacks.');
+ } else if (wordCount > wordExpected.length) {
+ errors.push('Too many word callbacks.');
+ }
+ if (sentenceCount == 0) {
+ errors.push('Didn\'t get any sentence callbacks.');
+ } else if (sentenceCount < sentenceExpected.length) {
+ errors.push('Not enough sentence callbacks.');
+ } else if (sentenceCount > sentenceExpected.length) {
+ errors.push('Too many sentence callbacks.');
+ }
+ if (startTime == undefined) {
+ errors.push('Error: no "start" event received!');
+ startTime = callTime;
+ }
+ var endTime = new Date();
+ var delta = (endTime - startTime) / 1000;
+ if (delta < 2.5) {
+ errors.push('Default speech rate seems too fast.');
+ } else if (delta > 7.0) {
+ errors.push('Default speech rate seems too slow.');
+ }
+ callback(errors.length == 0, delta, errors);
+ }
+ }
+ });
+ }
+ },
+ {
+ name: 'Baseline Queueing Test',
+ description: 'Establishes a baseline time to speak a ' +
+ 'sequence of three enqueued phrases, to compare ' +
+ 'other tests against.',
+ dependencies: [],
+ trials: 3,
+ run: function(testRunName, voiceName, callback) {
+ var callTime = new Date();
+ var startTime;
+ var errors = [];
+ logSpeakCall('Alpha Alpha', {
+ voiceName: voiceName,
+ onEvent: function(evt) {
+ logEvent(callTime, testRunName, evt);
+ if (isErrorEvent(evt)) {
+ callback(false, null, []);
+ } else if (evt.type == 'start') {
+ startTime = new Date();
+ }
+ }
+ });
+ logSpeakCall('Bravo bravo.', {
+ voiceName: voiceName,
+ enqueue: true,
+ onEvent: function(evt) {
+ logEvent(callTime, testRunName, evt);
+ if (isErrorEvent(evt)) {
+ callback(false, null, []);
+ }
+ }
+ });
+ logSpeakCall('Charlie charlie', {
+ voiceName: voiceName,
+ enqueue: true,
+ onEvent: function(evt) {
+ logEvent(callTime, testRunName, evt);
+ if (isErrorEvent(evt)) {
+ callback(false, null, []);
+ } else if (evt.type == 'end') {
+ if (startTime == undefined) {
+ errors.push('Error: no "start" event received!');
+ startTime = callTime;
+ }
+ var endTime = new Date();
+ var delta = (endTime - startTime) / 1000;
+ callback(errors.length == 0, delta, errors);
+ }
+ }
+ });
+ }
+ },
+ {
+ name: 'Interruption with Queueing',
+ description: 'Queue a sequence of three utterances, then before they ' +
+ 'are finished, interrupt and queue a sequence of three ' +
+ 'more utterances. Make sure that interrupting and ' +
+ 'cancelling the previous utterances doesn\'t interfere ' +
+ 'with the interrupting utterances.',
+ dependencies: ['Baseline Queueing Test'],
+ trials: 1,
+ run: function(testRunName, voiceName, callback) {
+ var callTime = new Date();
+ var startTime;
+ var errors = [];
+
+ logSpeakCall('Just when I\'m about to say something interesting,', {
+ voiceName: voiceName
+ });
+ logSpeakCall('it seems that I always get interrupted.', {
+ voiceName: voiceName,
+ enqueue: true,
+ });
+ logSpeakCall('How rude! Will you ever let me finish?', {
+ voiceName: voiceName,
+ enqueue: true,
+ });
+
+ window.setTimeout(function() {
+ logSpeakCall('Alpha Alpha', {
+ voiceName: voiceName,
+ onEvent: function(evt) {
+ logEvent(callTime, testRunName, evt);
+ if (isErrorEvent(evt)) {
+ callback(false, null, []);
+ } else if (evt.type == 'start') {
+ startTime = new Date();
+ }
+ }
+ });
+ logSpeakCall('Bravo bravo.', {
+ voiceName: voiceName,
+ enqueue: true,
+ onEvent: function(evt) {
+ logEvent(callTime, testRunName, evt);
+ if (isErrorEvent(evt)) {
+ callback(false, null, []);
+ }
+ }
+ });
+ logSpeakCall('Charlie charlie', {
+ voiceName: voiceName,
+ enqueue: true,
+ onEvent: function(evt) {
+ logEvent(callTime, testRunName, evt);
+ if (isErrorEvent(evt)) {
+ callback(false, null, []);
+ } else if (evt.type == 'end') {
+ if (startTime == undefined) {
+ errors.push('Error: no "start" event received!');
+ startTime = callTime;
+ }
+ var endTime = new Date();
+ var delta = (endTime - startTime) / 1000;
+ var relative = delta / resultMap['Baseline Queueing Test'];
+ if (relative < 0.9) {
+ errors.push('Interrupting speech seems too short.');
+ } else if (relative > 1.1) {
+ errors.push('Interrupting speech seems too long.');
+ }
+ callback(errors.length == 0, delta, errors);
+ }
+ }
+ });
+ }, 4000);
+ }
+ }
+];
+
+function updateDependencies() {
+ for (var i = 0; i < updateDependencyFunctions.length; i++) {
+ updateDependencyFunctions[i]();
+ }
+}
+
+function registerTest(test) {
+ var outer = document.createElement('div');
+ outer.className = 'outer';
+ $('container').appendChild(outer);
+
+ var buttonWrap = document.createElement('div');
+ buttonWrap.className = 'buttonWrap';
+ outer.appendChild(buttonWrap);
+
+ var button = document.createElement('button');
+ button.className = 'runTestButton';
+ button.innerText = test.name;
+ buttonWrap.appendChild(button);
+
+ var busy = document.createElement('img');
+ busy.src = 'pacman.gif';
+ busy.alt = 'Busy indicator';
+ buttonWrap.appendChild(busy);
+ busy.style.visibility = 'hidden';
+
+ var description = document.createElement('div');
+ description.className = 'description';
+ description.innerHTML = test.description;
+ outer.appendChild(description);
+
+ var resultsWrap = document.createElement('div');
+ resultsWrap.className = 'results';
+ outer.appendChild(resultsWrap);
+ var results = [];
+ for (var j = 0; j < test.trials; j++) {
+ var result = document.createElement('span');
+ resultsWrap.appendChild(result);
+ results.push(result);
+ }
+ var avg = document.createElement('span');
+ resultsWrap.appendChild(avg);
+
+ var messagesWrap = document.createElement('div');
+ messagesWrap.className = 'messages';
+ outer.appendChild(messagesWrap);
+
+ var totalTime;
+ var successCount;
+
+ function finishTrials() {
+ busy.style.visibility = 'hidden';
+ if (successCount == test.trials) {
+ console.log('Test succeeded.');
+ var success = document.createElement('div');
+ success.className = 'success';
+ success.innerText = 'Test succeeded.';
+ messagesWrap.appendChild(success);
+ if (totalTime > 0.0) {
+ var avgTime = totalTime / test.trials;
+ avg.className = 'result';
+ avg.innerText = 'Avg: ' + avgTime.toFixed(3) + ' s';
+ resultMap[test.name] = avgTime;
+ updateDependencies();
+ }
+ } else {
+ console.log('Test failed.');
+ var failure = document.createElement('div');
+ failure.className = 'failure';
+ failure.innerText = 'Test failed.';
+ messagesWrap.appendChild(failure);
+ }
+ }
+
+ function runTest(index, voiceName) {
+ if (emergencyStop) {
+ busy.style.visibility = 'hidden';
+ emergencyStop = false;
+ return;
+ }
+ var testRunName = 'Test run ' + testRunIndex + ', ' +
+ test.name + ', trial ' + (index+1) + ' of ' +
+ test.trials;
+ console.log('*** Beginning ' + testRunName +
+ ' with voice ' + voiceName);
+ test.run(testRunName, voiceName, function(success, resultTime, errors) {
+ if (success) {
+ successCount++;
+ }
+ for (var i = 0; i < errors.length; i++) {
+ console.log(errors[i]);
+ var error = document.createElement('div');
+ error.className = 'error';
+ error.innerText = errors[i];
+ messagesWrap.appendChild(error);
+ }
+ if (resultTime != null) {
+ results[index].className = 'result';
+ results[index].innerText = resultTime.toFixed(3) + ' s';
+ totalTime += resultTime;
+ }
+ index++;
+ if (index < test.trials) {
+ runTest(index, voiceName);
+ } else {
+ finishTrials();
+ }
+ });
+ }
+
+ button.addEventListener('click', function() {
+ var voiceIndex = $('voices').selectedIndex - 1;
+ if (voiceIndex < 0) {
+ alert('Please select a voice first!');
+ return;
+ }
+ testRunIndex++;
+ busy.style.visibility = 'visible';
+ totalTime = 0.0;
+ successCount = 0;
+ messagesWrap.innerHTML = '';
+ var voiceName = voiceArray[voiceIndex].voiceName;
+ runTest(0, voiceName);
+ }, false);
+
+ updateDependencyFunctions.push(function() {
+ for (var i = 0; i < test.dependencies.length; i++) {
+ if (resultMap[test.dependencies[i]] != undefined) {
+ button.disabled = false;
+ outer.className = 'outer';
+ } else {
+ button.disabled = true;
+ outer.className = 'outer disabled';
+ }
+ }
+ });
+}
+
+function load() {
+ var voice = localStorage['voice'];
+ chrome.tts.getVoices(function(va) {
+ voiceArray = va;
+ for (var i = 0; i < voiceArray.length; i++) {
+ var opt = document.createElement('option');
+ var name = voiceArray[i].voiceName;
+ if (name == localStorage['voice']) {
+ opt.setAttribute('selected', '');
+ }
+ opt.setAttribute('value', name);
+ opt.innerText = voiceArray[i].voiceName;
+ $('voices').appendChild(opt);
+ }
+ });
+ $('voices').addEventListener('change', function() {
+ var i = $('voices').selectedIndex;
+ localStorage['voice'] = $('voices').item(i).value;
+ }, false);
+
+ for (var i = 0; i < tests.length; i++) {
+ registerTest(tests[i]);
+ }
+ updateDependencies();
+}
+
+function stop() {
+ console.log('*** Emergency stop!');
+ emergencyStop = true;
+ chrome.tts.stop();
+}
Property changes on: chrome/common/extensions/docs/examples/extensions/ttsdebug/ttsdebug.js
___________________________________________________________________
Added: svn:eol-style
+ LF

Powered by Google App Engine
This is Rietveld 408576698