You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ws-client.js 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. // #region web speech recognition api
  2. var SpeechRecognition = SpeechRecognition || webkitSpeechRecognition;
  3. var SpeechGrammarList = SpeechGrammarList || webkitSpeechGrammarList;
  4. var SpeechRecognitionEvent = SpeechRecognitionEvent || webkitSpeechRecognitionEvent;
  5. // #endregion
  6. // #region state management
  7. var state = '';
  8. var question = 0;
  9. // currently disabled feature
  10. // var rePrompt = false;
  11. var partTwo = false;
  12. var questionThreeCount = 0;
  13. var strike = 0;
  14. // #endregion
  15. // #region questions
  16. const QUESTION_ONE = 'Ich werde Ihnen jetzt langsam eine Liste mit Worten vorlesen. Danach wiederholen Sie bitte möglichst viele dieser Worte. Auf die Reihenfolge kommt es nicht an.';
  17. const QUESTION_ONE_PT2 = 'Vielen Dank. Nun nenne ich Ihnen die gleichen 10 Worte ein zweites mal. Auch danach sollen Sie wieder möglichst viele Worte wiederholen';
  18. const QUESTION_TWO = 'Nennen Sie mir bitte so viel Dinge wie möglich, die man im Supermarkt kaufen kann. Sie haben dafür eine Minute Zeit. Und Los';
  19. const QUESTION_THREE = 'Ich werde Ihnen jetzt eine Zahlenreihe nennen, die Sie mir dann bitte in umgekehrter Reihenfolge wiederholen sollen. Wenn ich beispielsweise, vier - fünf sage, dann sagen Sie bitte, fünf - vier.';
  20. const QUESTION_FOUR = 'Wir kommen nun zur vierten Frage. Bitte zählen Sie von 76 an rückwärts. Nennen Sie die Zahlen abwechselnd als ganze Zahlen und als einzelne Ziffern. Zum Beispiel:';
  21. const QUESTION_FOUR_PT2 = ['34', '3, 3', '32', '3, 1'];
  22. const QUESTION_FIVE = 'Ganz am Anfang dieses Tests habe ich Ihnen 10 Worte genannt. Können Sie sich noch an diese Worte erinnern?';
  23. // #endregion
  24. const FINISH = 'Sie sind jetzt fertig mit dem Test, Vielen Dank.';
  25. // #region intents
  26. const WELCOME_INTENT = 'Default Welcome Intent';
  27. const WELCOME_FOLLOWUP_YES = 'Default Welcome Intent - yes';
  28. const WELCOME_FOLLOWUP_NO = 'Default Welcome Intent - no';
  29. // currently disabled feature
  30. // const MORE_TIME = 'Add Time Intent';
  31. // const MORE_TIME_YES = 'Add Time Intent - yes';
  32. // const MORE_TIME_NO = 'Add Time Intent - no';
  33. const QUIT_INTENT = 'Quit Intent';
  34. const FALLBACK_INTENT = 'Default Fallback Intent';
  35. const HELP_INTENT = 'Help Intent';
  36. const CHOOSE_QUESTION = 'Frage_Starten';
  37. // disabled feature
  38. // const NEXT_QUESTION = 'Nächste Frage';
  39. // #endregion
  40. // #region questions and expected results
  41. const QUESTION_ONE_ANSWERS = { 'teller': 1, 'hund': 1, 'lampe': 1, 'brief': 1, 'apfel': 1, 'apfelwiese': 2, 'apfelbaum': 2, 'und': 1, 'hose': 1, 'tisch': 1, 'wiese': 1, 'glas': 1, 'baum': 1 };
  42. const QUESTION_ONE_QUESTIONS = ['teller', 'hund', 'lampe', 'brief', 'apfel', 'hose', 'tisch', 'wiese', 'glas', 'baum'];
  43. const QUESTION_TWO_ANSWERS = {};
  44. var QUESTION_TWO_QUESTIONS;
  45. const QUESTION_THREE_QUESTIONS_PT1 = ['7, 2', '4, 7, 9', '5, 4, 9, 6', '2, 7, 5, 3, 6', '8, 1, 3, 5, 4, 2'];
  46. const QUESTION_THREE_QUESTIONS_PT2 = ['8, 6', '3, 1, 5', '1, 9, 7, 4', '1, 3, 5, 4, 8', '4, 1, 2, 7, 9, 5'];
  47. const QUESTION_THREE_ANSWERS_PT1 = ['27', '974', '6945', '63572', '245318'];
  48. const QUESTION_THREE_ANSWERS_PT2 = ['68', '513', '4791', '84531', '597214'];
  49. LoadQuestionTwo();
  50. function LoadQuestionTwo () {
  51. var xmlhttp;
  52. if (window.XMLHttpRequest) { // code for IE7+, Firefox, Chrome, Opera, Safari
  53. xmlhttp = new XMLHttpRequest();
  54. } else { // code for IE6, IE5
  55. xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
  56. }
  57. xmlhttp.onreadystatechange = function () {
  58. if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
  59. var text = xmlhttp.responseText.toLowerCase();
  60. // Now convert it into array using regex
  61. QUESTION_TWO_QUESTIONS = text.split('\r\n');
  62. for (let word of QUESTION_TWO_QUESTIONS) {
  63. QUESTION_TWO_ANSWERS[word] = 1;
  64. }
  65. }
  66. };
  67. xmlhttp.open('GET', 'lebensmittel.txt', true);
  68. xmlhttp.send();
  69. }
  70. // #endregion
  71. // #region points
  72. const questionPoints = {
  73. 1: 0,
  74. 2: 0,
  75. 3: 0,
  76. 4: 0,
  77. 5: 0 };
  78. // #endregion
  79. // tokenization
  80. const separators = [' ', '\\\+', '-', '\\\(', '\\\)', '\\*', '/', ':', '\\\?'];
  81. // Timers
  82. var timerId;
  83. // #region html elements
  84. var serverPara = document.querySelector('.server');
  85. var diagnosticPara = document.querySelector('.output');
  86. var testBtn = document.querySelector('button');
  87. var testBtn2 = document.getElementById('speechBtn');
  88. var infoPara = document.getElementById('info');
  89. var questionNumDisplay = document.querySelector('.quest');
  90. // #endregion
  91. // websocket to communicate with the server
  92. var ws = new WebSocket('wss://' + window.location.host + window.location.pathname + 'ws');
  93. // #region speech recognition initialization
  94. var recognition = new SpeechRecognition();
  95. recognition.lang = 'de-DE';
  96. // recognition.interimResults = false;
  97. recognition.maxAlternatives = 1;
  98. recognition.continuous = true;
  99. var answerQuery = '';
  100. var proceedWithRecording = false;
  101. // #endregion
  102. // #region speech synthesis initialization
  103. var speechsynth = new SpeechSynthesisUtterance();
  104. var listSpeechsynth = new SpeechSynthesisUtterance();
  105. var voices;
  106. // #endregion
  107. // #region speech events
  108. window.speechSynthesis.onvoiceschanged = function () {
  109. voices = window.speechSynthesis.getVoices();
  110. voices.forEach(element => {
  111. if (element.name === 'Google Deutsch') {
  112. speechsynth.voice = element;
  113. listSpeechsynth.voice = element;
  114. }
  115. });
  116. speechsynth.rate = 0.88;
  117. listSpeechsynth.rate = 0.7;
  118. };
  119. speechsynth.onend = function (event) {
  120. if (proceedWithRecording) {
  121. recognition.start();
  122. console.log('reocgnition started. Question: ' + question);
  123. }
  124. proceedWithRecording = false;
  125. diagnosticPara.textContent = '';
  126. console.log('global speech end');
  127. };
  128. // #endregion
  129. // #region websocket events
  130. ws.onopen = function () {
  131. serverPara.style.background = 'green';
  132. serverPara.innerHTML = 'Server online';
  133. };
  134. ws.onmessage = function (payload) {
  135. var dialogflowResult = JSON.parse(payload.data);
  136. checkIntent(dialogflowResult);
  137. };
  138. // #endregion
  139. // INTENT HANDLING
  140. function checkIntent (result) {
  141. switch (result.intent.displayName) {
  142. case QUIT_INTENT:
  143. state = 'quit';
  144. stopRecognitionFallback();
  145. speak('Beende die Durchführung.');
  146. break;
  147. case WELCOME_INTENT:
  148. state = 'detect';
  149. proceedWithRecording = true;
  150. speak(result.fulfillmentText);
  151. break;
  152. case WELCOME_FOLLOWUP_YES:
  153. startQuestion(1);
  154. break;
  155. case WELCOME_FOLLOWUP_NO:
  156. speak('Alles klar. Danke fürs Benutzen.');
  157. break;
  158. /// /// /// currently disabled feature
  159. // case MORE_TIME:
  160. // state = 'detect';
  161. // proceedWithRecording = true;
  162. // speak('Brauchen Sie noch etwas Zeit?');
  163. // break;
  164. // case MORE_TIME_YES:
  165. // rePrompt = true;
  166. // state = 'answer';
  167. // proceedWithRecording = true;
  168. // speak('Alles klar');
  169. // break;
  170. // case MORE_TIME_NO:
  171. // state = 'answer';
  172. // speak('Verstanden');
  173. // recognition.stop();
  174. // ws.send(answerQuery);
  175. // break;
  176. // case CHOOSE_QUESTION:
  177. // question = result.parameters.fields.num.numberValue;
  178. // state = 'answer';
  179. // handleQuestion();
  180. // break;
  181. /// /// ///
  182. case HELP_INTENT:
  183. stopRecognitionFallback();
  184. recognition.stop();
  185. startQuestion(question);
  186. break;
  187. case FALLBACK_INTENT:
  188. // fallback what should happen if no intent is detected
  189. // this is handled in the processSpeech() function
  190. break;
  191. default:
  192. break;
  193. }
  194. }
  195. // #region question handling
  196. function startQuestion (number) {
  197. question = number;
  198. state = 'answer';
  199. answerQuery = '';
  200. questionNumDisplay.textContent = 'Question: ' + question;
  201. handleQuestion();
  202. }
  203. function handleQuestion () {
  204. switch (question) {
  205. case 1:
  206. readQuestionOne(QUESTION_ONE);
  207. break;
  208. case 2:
  209. readQuestionTwo();
  210. break;
  211. case 3:
  212. readQuestionThree();
  213. break;
  214. case 4:
  215. readQuestionFour();
  216. break;
  217. case 5:
  218. proceedWithRecording = true;
  219. speak(QUESTION_FIVE);
  220. break;
  221. }
  222. }
  223. function readQuestionOne (questionText) {
  224. speak(questionText);
  225. for (let i = 0; i < QUESTION_ONE_QUESTIONS.length; i++) {
  226. let utterance = new SpeechSynthesisUtterance();
  227. utterance.voice = voices[2];
  228. utterance.rate = 0.75;
  229. utterance.text = QUESTION_ONE_QUESTIONS[i];
  230. window.speechSynthesis.speak(utterance);
  231. if (i === 9) {
  232. utterance.onend = function (event) {
  233. recognition.start();
  234. console.log('reocgnition started. Question: ' + question);
  235. };
  236. }
  237. }
  238. }
  239. function readQuestionTwo () {
  240. let utterance = new SpeechSynthesisUtterance();
  241. utterance.voice = voices[2];
  242. utterance.text = QUESTION_TWO;
  243. utterance.rate = 0.8;
  244. window.speechSynthesis.speak(utterance);
  245. utterance.onend = function (event) {
  246. window.setTimeout(
  247. function () {
  248. recognition.stop();
  249. window.setTimeout(
  250. function () {
  251. handleAnswer(answerQuery);
  252. answerQuery = '';
  253. }, 3000);
  254. }, 6000);
  255. recognition.start();
  256. console.log('reocgnition started. Question: ' + question);
  257. };
  258. }
  259. function readQuestionThree () {
  260. speak('Dankeschön, weiter geht es mit der nächsten Frage.');
  261. let utterance = new SpeechSynthesisUtterance();
  262. utterance.voice = voices[2];
  263. utterance.text = QUESTION_THREE;
  264. utterance.rate = 0.88;
  265. window.speechSynthesis.speak(utterance);
  266. utterance.onend = function (event) {
  267. proceedWithRecording = true;
  268. speak(QUESTION_THREE_QUESTIONS_PT1[questionThreeCount]);
  269. };
  270. }
  271. function readQuestionFour () {
  272. speak(QUESTION_FOUR);
  273. for (let i = 0; i < QUESTION_FOUR_PT2.length; i++) {
  274. let utterance = new SpeechSynthesisUtterance();
  275. utterance.voice = voices[2];
  276. utterance.rate = 0.75;
  277. utterance.text = QUESTION_FOUR_PT2[i];
  278. window.speechSynthesis.speak(utterance);
  279. }
  280. speak('Sie haben hierfür 1 Minute Zeit.');
  281. }
  282. function handleAnswer (query) {
  283. switch (question) {
  284. case 1:
  285. handleAnswerToFirstQuestion(query);
  286. break;
  287. case 2:
  288. handleAnswerToSecondQuestion(query);
  289. break;
  290. case 3:
  291. handleAnswerToThirdQuestion(query);
  292. break;
  293. case 4:
  294. // not possible with the current state of web speech api
  295. // handleAnswerToFourthQuestion(query);
  296. break;
  297. case 5:
  298. handleAnswerToFifthQuestion(query);
  299. break;
  300. }
  301. }
  302. function handleAnswerToFirstQuestion (answer) {
  303. deconstructAndCalculateAnswer(answer);
  304. if (partTwo) {
  305. partTwo = false;
  306. speak('Vielen Dank, nun geht es weiter mit der nächsten Frage');
  307. startQuestion(2);
  308. } else {
  309. // currently disabled feature
  310. // rePrompt = false;
  311. speak(QUESTION_ONE_PT2);
  312. readQuestionOne(QUESTION_ONE_PT2);
  313. partTwo = true;
  314. }
  315. }
  316. function handleAnswerToSecondQuestion (answer) {
  317. deconstructAndCalculateAnswer(answer);
  318. startQuestion(3);
  319. }
  320. function handleAnswerToThirdQuestion (query) {
  321. speechsynth.rate = 0.88;
  322. query = query.replace(' ', '');
  323. let answerArray;
  324. let questionArray;
  325. if (!partTwo) {
  326. answerArray = QUESTION_THREE_ANSWERS_PT1;
  327. } else {
  328. answerArray = QUESTION_THREE_ANSWERS_PT2;
  329. }
  330. if (query === answerArray[questionThreeCount]) {
  331. strike = 0;
  332. partTwo = false;
  333. questionThreeCount++;
  334. questionPoints[question] = questionThreeCount + 1;
  335. questionArray = QUESTION_THREE_QUESTIONS_PT1;
  336. } else {
  337. strike++;
  338. partTwo = true;
  339. questionArray = QUESTION_THREE_QUESTIONS_PT2;
  340. }
  341. if (strike === 2 || questionThreeCount === 5) {
  342. speechsynth.rate = 0.88;
  343. console.log('question 3 points: ' + questionPoints[question]);
  344. speak('weiter geht es mit der Nächsten Frage');
  345. // Question 4 is not possible to take, because of technical limitation
  346. // startQuestion(4);
  347. startQuestion(5);
  348. return;
  349. }
  350. proceedWithRecording = true;
  351. speak(questionArray[questionThreeCount]);
  352. console.log('count: ' + questionThreeCount + ', strike: ' + strike + ', points: ' + questionPoints[question]);
  353. }
  354. // not possible with the current state of web speech api
  355. // function handleAnswerToFourthQuestion (answer) {
  356. // // In order to evaluate the answer, the recognizer has to distinguish between the number 77 spoken "seventy seven" and 7-7 spoken "seven seven", which it doesnt
  357. // startQuestion(5);
  358. // }
  359. function handleAnswerToFifthQuestion (answer) {
  360. deconstructAndCalculateAnswer(answer);
  361. speak(FINISH);
  362. }
  363. // #endregion
  364. // NOT SUPPORTED
  365. // function recognizeSpeech () {
  366. // var arr;
  367. // switch (question) {
  368. // case 1:
  369. // arr = QUESTION_ONE_QUESTIONS;
  370. // break;
  371. // case 2:
  372. // arr = QUESTION_TWO_QUESTIONS;
  373. // break;
  374. // case 3:
  375. // arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
  376. // break;
  377. // case 4:
  378. // arr = ['eins', 'zwei', 'drei', 'vier', 'fünf', 'sechs', 'sieben', 'acht', 'neun'];
  379. // break;
  380. // case 5:
  381. // break;
  382. // }
  383. // var grammar = '#JSGF V1.0; grammar arr; public <arr> = ' + arr.join(' | ') + ' ;';
  384. // var speechRecognitionList = new SpeechGrammarList();
  385. // speechRecognitionList.addFromString(grammar, 1);
  386. // recognition.grammars = speechRecognitionList;
  387. // recognition.start();
  388. // }
  389. // #region speech recognition event
  390. recognition.onresult = function (event) {
  391. var last = event.results.length - 1;
  392. var speechResult = event.results[last][0].transcript.toLowerCase();
  393. diagnosticPara.textContent += speechResult + ' ';
  394. // console.log('Confidence: ' + event.results[0][0].confidence)
  395. console.log('process: ' + speechResult);
  396. processSpeech(speechResult);
  397. // testBtn.disabled = false
  398. // testBtn.textContent = 'record...'
  399. };
  400. recognition.onspeechend = function () {
  401. // recognition.stop();
  402. // testBtn.disabled = false;
  403. // testBtn.textContent = 'Start new test';
  404. };
  405. recognition.onerror = function (event) {
  406. testBtn.disabled = false;
  407. testBtn.textContent = 'Start new test';
  408. diagnosticPara.textContent = 'Error occurred in recognition: ' + event.error;
  409. };
  410. recognition.onaudiostart = function (event) {
  411. // Fired when the user agent has started to capture audio.
  412. };
  413. recognition.onaudioend = function (event) {
  414. };
  415. recognition.onend = function (event) {
  416. // Fired when the speech recognition service has disconnected.
  417. };
  418. recognition.onnomatch = function (event) {
  419. // Fired when the speech recognition service returns a final result with no significant recognition. This may involve some degree of recognition, which doesn't meet or exceed the confidence threshold.
  420. // console.log('SpeechRecognition.onnomatch')
  421. };
  422. recognition.onsoundstart = function (event) {
  423. // Fired when any sound — recognisable speech or not — has been detected.
  424. };
  425. recognition.onsoundend = function (event) {
  426. // Fired when any sound — recognisable speech or not — has stopped being detected.
  427. };
  428. recognition.onspeechstart = function (event) {
  429. // Fired when sound that is recognised by the speech recognition service as speech has been detected.
  430. };
  431. recognition.onstart = function (event) {
  432. // Fired when the speech recognition service has begun listening to incoming audio with intent to recognize grammars associated with the current SpeechRecognition.
  433. };
  434. // }
  435. // #endregion
  436. // #region global functions
  437. function processSpeech (speechResult) {
  438. console.log('To dialogflow: ' + speechResult);
  439. ws.send(speechResult);
  440. let timeOut;
  441. switch (question) {
  442. case 1:
  443. timeOut = 6500;
  444. break;
  445. case 2:
  446. answerQuery += speechResult;
  447. return;
  448. case 3:
  449. if (speechResult.includes('uhr')) {
  450. speechResult = speechResult.replace('uhr', '');
  451. }
  452. timeOut = 6500;
  453. break;
  454. case 4:
  455. break;
  456. case 5:
  457. timeOut = 6500;
  458. break;
  459. }
  460. if (state === 'answer') {
  461. stopRecognitionFallback();
  462. answerQuery += speechResult;
  463. timerId = window.setTimeout(
  464. function () {
  465. // currently disabled
  466. // if (!rePrompt) {
  467. // ws.send('ich brauche noch etwas Zeit')
  468. // } else {
  469. console.log('recording end. Evaluate: ' + answerQuery);
  470. handleAnswer(answerQuery);
  471. diagnosticPara.textContent = '';
  472. // }
  473. recognition.stop();
  474. console.log('timer fallback');
  475. }, timeOut);
  476. } else {
  477. console.log('recording end.');
  478. recognition.stop();
  479. }
  480. }
  481. function startDemenzScreening () {
  482. // ws.send('starte demenz test');
  483. startQuestion(4);
  484. testBtn.disabled = true;
  485. testBtn.textContent = 'Test in progress';
  486. infoPara.textContent = 'wait...';
  487. diagnosticPara.textContent = 'detecting...';
  488. }
  489. function testSpeechOut () {
  490. question = 4;
  491. }
  492. function speak (sentence) {
  493. speechsynth.text = sentence;
  494. window.speechSynthesis.speak(speechsynth);
  495. }
  496. function stopRecognitionFallback () {
  497. if (timerId != undefined) {
  498. clearTimeout(timerId);
  499. }
  500. }
  501. function deconstructAndCalculateAnswer (answer) {
  502. let questionAnswers;
  503. switch (question) {
  504. case 1:
  505. questionAnswers = QUESTION_ONE_ANSWERS;
  506. break;
  507. case 2:
  508. questionAnswers = QUESTION_TWO_ANSWERS;
  509. break;
  510. case 5:
  511. questionAnswers = QUESTION_ONE_ANSWERS;
  512. break;
  513. }
  514. var tokens = answer.split(new RegExp(separators.join('|'), 'g'));
  515. questionPoints[question] += calculatePoints(tokens, questionAnswers);
  516. }
  517. function calculatePoints (tokens, d) {
  518. let points = 0;
  519. let dict = {};
  520. Object.assign(dict, d);
  521. for (let word of tokens) {
  522. if (dict[word] !== undefined) {
  523. points += dict[word];
  524. delete dict[word];
  525. }
  526. }
  527. return points;
  528. }
  529. // #endregion
  530. testBtn.addEventListener('click', startDemenzScreening);
  531. testBtn2.addEventListener('click', testSpeechOut);