Home | History | Annotate | Download | only in ttsdebug
      1 /**
      2  * Copyright (c) 2011 The Chromium Authors. All rights reserved.
      3  * Use of this source code is governed by a BSD-style license that can be
      4  * found in the LICENSE file.
      5  */
      6 
      7 var voiceArray;
      8 var trials = 3;
      9 var resultMap = {};
     10 var updateDependencyFunctions = [];
     11 var testRunIndex = 0;
     12 var emergencyStop = false;
     13 
     14 function $(id) {
     15   return document.getElementById(id);
     16 }
     17 
     18 function isErrorEvent(evt) {
     19   return (evt.type == 'error' ||
     20           evt.type == 'interrupted' ||
     21           evt.type == 'cancelled');
     22 }
     23 
     24 function logEvent(callTime, testRunName, evt) {
     25   var elapsed = ((new Date() - callTime) / 1000).toFixed(3);
     26   while (elapsed.length < 7) {
     27     elapsed = ' ' + elapsed;
     28   }
     29   console.log(elapsed + ' ' + testRunName + ': ' + JSON.stringify(evt));
     30 }
     31 
     32 function logSpeakCall(utterance, options, callback) {
     33   var optionsCopy = {};
     34   for (var key in options) {
     35     if (key != 'onEvent') {
     36       optionsCopy[key] = options[key];
     37     }
     38   }
     39   console.log('Calling chrome.tts.speak(\'' +
     40               utterance + '\', ' +
     41               JSON.stringify(optionsCopy) + ')');
     42   if (callback)
     43     chrome.tts.speak(utterance, options, callback);
     44   else
     45     chrome.tts.speak(utterance, options);
     46 }
     47 
     48 var tests = [
     49   {
     50     name: 'Baseline',
     51     description: 'Ensures that the speech engine sends both start and ' +
     52                  'end events, and establishes a baseline time to speak a ' +
     53                  'key phrase, to compare other tests against.',
     54     dependencies: [],
     55     trials: 3,
     56     run: function(testRunName, voiceName, callback) {
     57       var callTime = new Date();
     58       var startTime;
     59       var warnings = [];
     60       var errors = [];
     61       logSpeakCall('Alpha Bravo Charlie Delta Echo', {
     62         voiceName: voiceName,
     63         onEvent: function(evt) {
     64           logEvent(callTime, testRunName, evt);
     65           if (isErrorEvent(evt)) {
     66             callback(false, null, []);
     67           } else if (evt.type == 'start') {
     68             startTime = new Date();
     69             if (evt.charIndex != 0) {
     70               errors.push('Error: start event should have a charIndex of 0.');
     71             }
     72           } else if (evt.type == 'end') {
     73             if (startTime == undefined) {
     74               errors.push('Error: no "start" event received!');
     75               startTime = callTime;
     76             }
     77             if (evt.charIndex != 30) {
     78               errors.push('Error: end event should have a charIndex of 30.');
     79             }
     80             var endTime = new Date();
     81             if (startTime - callTime > 1000) {
     82               var delta = ((startTime - callTime) / 1000).toFixed(3);
     83               warnings.push('Note: Delay of ' + delta +
     84                             ' before speech started. ' +
     85                             'Less than 1.0 s latency is recommended.');
     86             }
     87             var delta = (endTime - startTime) / 1000;
     88             if (delta < 1.0) {
     89               warnings.push('Warning: Default speech rate seems too fast.');
     90             } else if (delta > 3.0) {
     91               warnings.push('Warning: Default speech rate seems too slow.');
     92             }
     93             callback(errors.length == 0, delta, warnings.concat(errors));
     94           }
     95         }
     96       });
     97     }
     98   },
     99   {
    100     name: 'Fast',
    101     description: 'Speaks twice as fast and compares the time to the baseline.',
    102     dependencies: ['Baseline'],
    103     trials: 3,
    104     run: function(testRunName, voiceName, callback) {
    105       var callTime = new Date();
    106       var startTime;
    107       var errors = [];
    108       logSpeakCall('Alpha Bravo Charlie Delta Echo', {
    109         voiceName: voiceName,
    110         rate: 2.0,
    111         onEvent: function(evt) {
    112           logEvent(callTime, testRunName, evt);
    113           if (isErrorEvent(evt)) {
    114             callback(false, null, []);
    115           } else if (evt.type == 'start') {
    116             startTime = new Date();
    117           } else if (evt.type == 'end') {
    118             if (startTime == undefined)
    119               startTime = callTime;
    120             var endTime = new Date();
    121             var delta = (endTime - startTime) / 1000;
    122             var relative = delta / resultMap['Baseline'];
    123             if (relative < 0.35) {
    124               errors.push('2x speech rate seems too fast.');
    125             } else if (relative > 0.65) {
    126               errors.push('2x speech rate seems too slow.');
    127             }
    128             callback(errors.length == 0, delta, errors);
    129           }
    130         }
    131       });
    132     }
    133   },
    134   {
    135     name: 'Slow',
    136     description: 'Speaks twice as slow and compares the time to the baseline.',
    137     dependencies: ['Baseline'],
    138     trials: 3,
    139     run: function(testRunName, voiceName, callback) {
    140       var callTime = new Date();
    141       var startTime;
    142       var errors = [];
    143       logSpeakCall('Alpha Bravo Charlie Delta Echo', {
    144         voiceName: voiceName,
    145         rate: 0.5,
    146         onEvent: function(evt) {
    147           logEvent(callTime, testRunName, evt);
    148           if (isErrorEvent(evt)) {
    149             callback(false, null, []);
    150           } else if (evt.type == 'start') {
    151             startTime = new Date();
    152           } else if (evt.type == 'end') {
    153             if (startTime == undefined)
    154               startTime = callTime;
    155             var endTime = new Date();
    156             var delta = (endTime - startTime) / 1000;
    157             var relative = delta / resultMap['Baseline'];
    158             if (relative < 1.6) {
    159               errors.push('Half-speed speech rate seems too fast.');
    160             } else if (relative > 2.4) {
    161               errors.push('Half-speed speech rate seems too slow.');
    162             }
    163             callback(errors.length == 0, delta, errors);
    164           }
    165         }
    166       });
    167     }
    168   },
    169   {
    170     name: 'Interrupt and restart',
    171     description: 'Interrupts partway through a long sentence and then ' +
    172                  'the baseline utterance, to make sure that speech after ' +
    173                  'an interruption works correctly.',
    174     dependencies: ['Baseline'],
    175     trials: 1,
    176     run: function(testRunName, voiceName, callback) {
    177       var callTime = new Date();
    178       var startTime;
    179       var errors = [];
    180       logSpeakCall('When in the course of human events it becomes ' +
    181                        'necessary for one people to dissolve the political ' +
    182                        'bands which have connected them ', {
    183         voiceName: voiceName,
    184         onEvent: function(evt) {
    185           logEvent(callTime, testRunName, evt);
    186         }
    187       });
    188       window.setTimeout(function() {
    189         logSpeakCall('Alpha Bravo Charlie Delta Echo', {
    190           voiceName: voiceName,
    191           onEvent: function(evt) {
    192             logEvent(callTime, testRunName, evt);
    193             if (isErrorEvent(evt)) {
    194               callback(false, null, []);
    195             } else if (evt.type == 'start') {
    196               startTime = new Date();
    197             } else if (evt.type == 'end') {
    198               if (startTime == undefined)
    199                 startTime = callTime;
    200               var endTime = new Date();
    201               var delta = (endTime - startTime) / 1000;
    202               var relative = delta / resultMap['Baseline'];
    203               if (relative < 0.9) {
    204                 errors.push('Interrupting speech seems too short.');
    205               } else if (relative > 1.1) {
    206                 errors.push('Interrupting speech seems too long.');
    207               }
    208               callback(errors.length == 0, delta, errors);
    209             }
    210           }
    211         });
    212       }, 4000);
    213     }
    214   },
    215   {
    216     name: 'Low volume',
    217     description: '<b>Manual</b> test - verify that the volume is lower.',
    218     dependencies: [],
    219     trials: 1,
    220     run: function(testRunName, voiceName, callback) {
    221       var callTime = new Date();
    222       logSpeakCall('Alpha Bravo Charlie Delta Echo', {
    223         voiceName: voiceName,
    224         volume: 0.5,
    225         onEvent: function(evt) {
    226           logEvent(callTime, testRunName, evt);
    227           if (isErrorEvent(evt)) {
    228             callback(false, null, []);
    229           } else if (evt.type == 'end') {
    230             callback(true, null, []);
    231           }
    232         }
    233       });
    234     }
    235   },
    236   {
    237     name: 'High pitch',
    238     description: '<b>Manual</b> test - verify that the pitch is ' +
    239                  'moderately higher, but quite understandable.',
    240     dependencies: [],
    241     trials: 1,
    242     run: function(testRunName, voiceName, callback) {
    243       var callTime = new Date();
    244       logSpeakCall('Alpha Bravo Charlie Delta Echo', {
    245         voiceName: voiceName,
    246         pitch: 1.2,
    247         onEvent: function(evt) {
    248           logEvent(callTime, testRunName, evt);
    249           if (isErrorEvent(evt)) {
    250             callback(false, null, []);
    251           } else if (evt.type == 'end') {
    252             callback(true, null, []);
    253           }
    254         }
    255       });
    256     }
    257   },
    258   {
    259     name: 'Low pitch',
    260     description: '<b>Manual</b> test - verify that the pitch is ' +
    261                  'moderately lower, but quite understandable.',
    262     dependencies: [],
    263     trials: 1,
    264     run: function(testRunName, voiceName, callback) {
    265       var callTime = new Date();
    266       logSpeakCall('Alpha Bravo Charlie Delta Echo', {
    267         voiceName: voiceName,
    268         pitch: 0.8,
    269         onEvent: function(evt) {
    270           logEvent(callTime, testRunName, evt);
    271           if (isErrorEvent(evt)) {
    272             callback(false, null, []);
    273           } else if (evt.type == 'end') {
    274             callback(true, null, []);
    275           }
    276         }
    277       });
    278     }
    279   },
    280   {
    281     name: 'Word and sentence callbacks',
    282     description: 'Checks to see if proper word and sentence callbacks ' +
    283                  'are received.',
    284     dependencies: ['Baseline'],
    285     trials: 1,
    286     run: function(testRunName, voiceName, callback) {
    287       var callTime = new Date();
    288       var startTime;
    289       var errors = [];
    290       var wordExpected = [{min: 5, max: 6},
    291                           {min: 11, max: 12},
    292                           {min: 19, max: 20},
    293                           {min: 25, max: 26},
    294                           {min: 30, max: 32},
    295                           {min: 37, max: 38},
    296                           {min: 43, max: 44},
    297                           {min: 51, max: 52},
    298                           {min: 57, max: 58}];
    299       var sentenceExpected = [{min: 30, max: 32}]
    300       var wordCount = 0;
    301       var sentenceCount = 0;
    302       var lastWordTime = callTime;
    303       var lastSentenceTime = callTime;
    304       var avgWordTime = resultMap['Baseline'] / 5;
    305       logSpeakCall('Alpha Bravo Charlie Delta Echo. ' +
    306                        'Alpha Bravo Charlie Delta Echo.', {
    307         voiceName: voiceName,
    308         onEvent: function(evt) {
    309           logEvent(callTime, testRunName, evt);
    310           if (isErrorEvent(evt)) {
    311             callback(false, null, []);
    312           } else if (evt.type == 'start') {
    313             startTime = new Date();
    314             lastWordTime = startTime;
    315             lastSentenceTime = startTime;
    316           } else if (evt.type == 'word') {
    317             if (evt.charIndex > 0 && evt.charIndex < 62) {
    318               var min = wordExpected[wordCount].min;
    319               var max = wordExpected[wordCount].max;
    320               if (evt.charIndex < min || evt.charIndex > max) {
    321                 errors.push('Got word at charIndex ' + evt.charIndex + ', ' +
    322                             'was expecting next word callback charIndex ' +
    323                             'in the range ' + min + ':' + max + '.');
    324               }
    325               if (wordCount != 4) {
    326                 var delta = (new Date() - lastWordTime) / 1000;
    327                 if (delta < 0.6 * avgWordTime) {
    328                   errors.push('Word at charIndex ' + evt.charIndex +
    329                               ' came after only ' + delta.toFixed(3) +
    330                               ' s, which seems too short.');
    331                 } else if (delta > 1.3 * avgWordTime) {
    332                   errors.push('Word at charIndex ' + evt.charIndex +
    333                               ' came after ' + delta.toFixed(3) +
    334                               ' s, which seems too long.');
    335                 }
    336               }
    337               wordCount++;
    338             }
    339             lastWordTime = new Date();
    340           } else if (evt.type == 'sentence') {
    341             if (evt.charIndex > 0 && evt.charIndex < 62) {
    342               var min = sentenceExpected[sentenceCount].min;
    343               var max = sentenceExpected[sentenceCount].max;
    344               if (evt.charIndex < min || evt.charIndex > max) {
    345                 errors.push('Got sentence at charIndex ' + evt.charIndex +
    346                             ', was expecting next callback charIndex ' +
    347                             'in the range ' + min + ':' + max + '.');
    348               }
    349               var delta = (new Date() - lastSentenceTime) / 1000;
    350               if (delta < 0.75 * resultMap['Baseline']) {
    351                 errors.push('Sentence at charIndex ' + evt.charIndex +
    352                             ' came after only ' + delta.toFixed(3) +
    353                             ' s, which seems too short.');
    354               } else if (delta > 1.25 * resultMap['Baseline']) {
    355                 errors.push('Sentence at charIndex ' + evt.charIndex +
    356                             ' came after ' + delta.toFixed(3) +
    357                             ' s, which seems too long.');
    358               }
    359               sentenceCount++;
    360             }
    361             lastSentenceTime = new Date();
    362           } else if (evt.type == 'end') {
    363             if (wordCount == 0) {
    364               errors.push('Didn\'t get any word callbacks.');
    365             } else if (wordCount < wordExpected.length) {
    366               errors.push('Not enough word callbacks.');
    367             } else if (wordCount > wordExpected.length) {
    368               errors.push('Too many word callbacks.');
    369             }
    370             if (sentenceCount == 0) {
    371               errors.push('Didn\'t get any sentence callbacks.');
    372             } else if (sentenceCount < sentenceExpected.length) {
    373               errors.push('Not enough sentence callbacks.');
    374             } else if (sentenceCount > sentenceExpected.length) {
    375               errors.push('Too many sentence callbacks.');
    376             }
    377             if (startTime == undefined) {
    378               errors.push('Error: no "start" event received!');
    379               startTime = callTime;
    380             }
    381             var endTime = new Date();
    382             var delta = (endTime - startTime) / 1000;
    383             if (delta < 2.5) {
    384               errors.push('Default speech rate seems too fast.');
    385             } else if (delta > 7.0) {
    386               errors.push('Default speech rate seems too slow.');
    387             }
    388             callback(errors.length == 0, delta, errors);
    389           }
    390         }
    391       });
    392     }
    393   },
    394   {
    395     name: 'Baseline Queueing Test',
    396     description: 'Establishes a baseline time to speak a ' +
    397                  'sequence of three enqueued phrases, to compare ' +
    398                  'other tests against.',
    399     dependencies: [],
    400     trials: 3,
    401     run: function(testRunName, voiceName, callback) {
    402       var callTime = new Date();
    403       var startTime;
    404       var errors = [];
    405       logSpeakCall('Alpha Alpha', {
    406         voiceName: voiceName,
    407         onEvent: function(evt) {
    408           logEvent(callTime, testRunName, evt);
    409           if (isErrorEvent(evt)) {
    410             callback(false, null, []);
    411           } else if (evt.type == 'start') {
    412             startTime = new Date();
    413           }
    414         }
    415       });
    416       logSpeakCall('Bravo bravo.', {
    417         voiceName: voiceName,
    418         enqueue: true,
    419         onEvent: function(evt) {
    420           logEvent(callTime, testRunName, evt);
    421           if (isErrorEvent(evt)) {
    422             callback(false, null, []);
    423           }
    424         }
    425       });
    426       logSpeakCall('Charlie charlie', {
    427         voiceName: voiceName,
    428         enqueue: true,
    429         onEvent: function(evt) {
    430           logEvent(callTime, testRunName, evt);
    431           if (isErrorEvent(evt)) {
    432             callback(false, null, []);
    433           } else if (evt.type == 'end') {
    434             if (startTime == undefined) {
    435               errors.push('Error: no "start" event received!');
    436               startTime = callTime;
    437             }
    438             var endTime = new Date();
    439             var delta = (endTime - startTime) / 1000;
    440             callback(errors.length == 0, delta, errors);
    441           }
    442         }
    443       });
    444     }
    445   },
    446   {
    447     name: 'Interruption with Queueing',
    448     description: 'Queue a sequence of three utterances, then before they ' +
    449                  'are finished, interrupt and queue a sequence of three ' +
    450                  'more utterances. Make sure that interrupting and ' +
    451                  'cancelling the previous utterances doesn\'t interfere ' +
    452                  'with the interrupting utterances.',
    453     dependencies: ['Baseline Queueing Test'],
    454     trials: 1,
    455     run: function(testRunName, voiceName, callback) {
    456       var callTime = new Date();
    457       var startTime;
    458       var errors = [];
    459 
    460       logSpeakCall('Just when I\'m about to say something interesting,', {
    461         voiceName: voiceName
    462       });
    463       logSpeakCall('it seems that I always get interrupted.', {
    464         voiceName: voiceName,
    465         enqueue: true,
    466       });
    467       logSpeakCall('How rude! Will you ever let me finish?', {
    468         voiceName: voiceName,
    469         enqueue: true,
    470       });
    471 
    472       window.setTimeout(function() {
    473         logSpeakCall('Alpha Alpha', {
    474           voiceName: voiceName,
    475           onEvent: function(evt) {
    476             logEvent(callTime, testRunName, evt);
    477             if (isErrorEvent(evt)) {
    478               callback(false, null, []);
    479             } else if (evt.type == 'start') {
    480               startTime = new Date();
    481             }
    482           }
    483         });
    484         logSpeakCall('Bravo bravo.', {
    485           voiceName: voiceName,
    486           enqueue: true,
    487           onEvent: function(evt) {
    488             logEvent(callTime, testRunName, evt);
    489             if (isErrorEvent(evt)) {
    490               callback(false, null, []);
    491             }
    492           }
    493         });
    494         logSpeakCall('Charlie charlie', {
    495           voiceName: voiceName,
    496           enqueue: true,
    497           onEvent: function(evt) {
    498             logEvent(callTime, testRunName, evt);
    499             if (isErrorEvent(evt)) {
    500               callback(false, null, []);
    501             } else if (evt.type == 'end') {
    502               if (startTime == undefined) {
    503                 errors.push('Error: no "start" event received!');
    504                 startTime = callTime;
    505               }
    506               var endTime = new Date();
    507               var delta = (endTime - startTime) / 1000;
    508               var relative = delta / resultMap['Baseline Queueing Test'];
    509               if (relative < 0.9) {
    510                 errors.push('Interrupting speech seems too short.');
    511               } else if (relative > 1.1) {
    512                 errors.push('Interrupting speech seems too long.');
    513               }
    514               callback(errors.length == 0, delta, errors);
    515             }
    516           }
    517         });
    518       }, 4000);
    519     }
    520   }
    521 ];
    522 
    523 function updateDependencies() {
    524   for (var i = 0; i < updateDependencyFunctions.length; i++) {
    525     updateDependencyFunctions[i]();
    526   }
    527 }
    528 
    529 function registerTest(test) {
    530   var outer = document.createElement('div');
    531   outer.className = 'outer';
    532   $('container').appendChild(outer);
    533 
    534   var buttonWrap = document.createElement('div');
    535   buttonWrap.className = 'buttonWrap';
    536   outer.appendChild(buttonWrap);
    537 
    538   var button = document.createElement('button');
    539   button.className = 'runTestButton';
    540   button.innerText = test.name;
    541   buttonWrap.appendChild(button);
    542 
    543   var busy = document.createElement('img');
    544   busy.src = 'pacman.gif';
    545   busy.alt = 'Busy indicator';
    546   buttonWrap.appendChild(busy);
    547   busy.style.visibility = 'hidden';
    548 
    549   var description = document.createElement('div');
    550   description.className = 'description';
    551   description.innerHTML = test.description;
    552   outer.appendChild(description);
    553 
    554   var resultsWrap = document.createElement('div');
    555   resultsWrap.className = 'results';
    556   outer.appendChild(resultsWrap);
    557   var results = [];
    558   for (var j = 0; j < test.trials; j++) {
    559     var result = document.createElement('span');
    560     resultsWrap.appendChild(result);
    561     results.push(result);
    562   }
    563   var avg = document.createElement('span');
    564   resultsWrap.appendChild(avg);
    565 
    566   var messagesWrap = document.createElement('div');
    567   messagesWrap.className = 'messages';
    568   outer.appendChild(messagesWrap);
    569 
    570   var totalTime;
    571   var successCount;
    572 
    573   function finishTrials() {
    574     busy.style.visibility = 'hidden';
    575     if (successCount == test.trials) {
    576       console.log('Test succeeded.');
    577       var success = document.createElement('div');
    578       success.className = 'success';
    579       success.innerText = 'Test succeeded.';
    580       messagesWrap.appendChild(success);
    581       if (totalTime > 0.0) {
    582         var avgTime = totalTime / test.trials;
    583         avg.className = 'result';
    584         avg.innerText = 'Avg: ' + avgTime.toFixed(3) + ' s';
    585         resultMap[test.name] = avgTime;
    586         updateDependencies();
    587       }
    588     } else {
    589       console.log('Test failed.');
    590       var failure = document.createElement('div');
    591       failure.className = 'failure';
    592       failure.innerText = 'Test failed.';
    593       messagesWrap.appendChild(failure);
    594     }
    595   }
    596 
    597   function runTest(index, voiceName) {
    598     if (emergencyStop) {
    599       busy.style.visibility = 'hidden';
    600       emergencyStop = false;
    601       return;
    602     }
    603     var testRunName = 'Test run ' + testRunIndex + ', ' +
    604                       test.name + ', trial ' + (index+1) + ' of ' +
    605                       test.trials;
    606     console.log('*** Beginning ' + testRunName +
    607                 ' with voice ' + voiceName);
    608     test.run(testRunName, voiceName, function(success, resultTime, errors) {
    609       if (success) {
    610         successCount++;
    611       }
    612       for (var i = 0; i < errors.length; i++) {
    613         console.log(errors[i]);
    614         var error = document.createElement('div');
    615         error.className = 'error';
    616         error.innerText = errors[i];
    617         messagesWrap.appendChild(error);
    618       }
    619       if (resultTime != null) {
    620         results[index].className = 'result';
    621         results[index].innerText = resultTime.toFixed(3) + ' s';
    622         totalTime += resultTime;
    623       }
    624       index++;
    625       if (index < test.trials) {
    626         runTest(index, voiceName);
    627       } else {
    628         finishTrials();
    629       }
    630     });
    631   }
    632 
    633   button.addEventListener('click', function() {
    634     var voiceIndex = $('voices').selectedIndex - 1;
    635     if (voiceIndex < 0) {
    636       alert('Please select a voice first!');
    637       return;
    638     }
    639     testRunIndex++;
    640     busy.style.visibility = 'visible';
    641     totalTime = 0.0;
    642     successCount = 0;
    643     messagesWrap.innerHTML = '';
    644     var voiceName = voiceArray[voiceIndex].voiceName;
    645     runTest(0, voiceName);
    646   }, false);
    647 
    648   updateDependencyFunctions.push(function() {
    649     for (var i = 0; i < test.dependencies.length; i++) {
    650       if (resultMap[test.dependencies[i]] != undefined) {
    651         button.disabled = false;
    652         outer.className = 'outer';
    653       } else {
    654         button.disabled = true;
    655         outer.className = 'outer disabled';
    656       }
    657     }
    658   });
    659 }
    660 
    661 function load() {
    662   var voice = localStorage['voice'];
    663   chrome.tts.getVoices(function(va) {
    664     voiceArray = va;
    665     for (var i = 0; i < voiceArray.length; i++) {
    666       var opt = document.createElement('option');
    667       var name = voiceArray[i].voiceName;
    668       if (name == localStorage['voice']) {
    669         opt.setAttribute('selected', '');
    670       }
    671       opt.setAttribute('value', name);
    672       opt.innerText = voiceArray[i].voiceName;
    673       $('voices').appendChild(opt);
    674     }
    675   });
    676   $('voices').addEventListener('change', function() {
    677     var i = $('voices').selectedIndex;
    678     localStorage['voice'] = $('voices').item(i).value;
    679   }, false);
    680   $('stop').addEventListener('click', stop);
    681 
    682   for (var i = 0; i < tests.length; i++) {
    683     registerTest(tests[i]);
    684   }
    685   updateDependencies();
    686 }
    687 
    688 function stop() {
    689   console.log('*** Emergency stop!');
    690   emergencyStop = true;
    691   chrome.tts.stop();
    692 }
    693 
    694 document.addEventListener('DOMContentLoaded', load);
    695