1 <?xml version="1.0" encoding="UTF-8"?>
2 <?xml-stylesheet href="xhtml-default.css" type="text/css" media="screen, aural, print" ?>
3 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN"
4 "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">
5 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" >
7 <link rel="shortcut icon" href="sgc.png" />
8 <link rel="icon" sizes="192x192" href="sgc.png" />
9 <link rel="manifest" href="manifest.json" />
10 <meta name="viewport" content="width=device-width" />
11 <meta name="mobile-web-app-capable" content="yes" />
12 <meta http-equiv="Content-Language" content="en" />
13 <title lang="en" xml:lang="en" dir="ltr">SpeakGoodChinese 3</title>
16 background-image: url("Background.png");
17 background-color: rgb(250,250,250);
18 background-repeat: no-repeat;
19 background-position: center center;
20 background-attachment: fixed;}
27 text-overflow: clip clip;
30 cursor:pointer; /*forces the cursor to change to a hand when the button is hovered*/
32 font: bold 4vmin "Helvetica";
33 background-color: rgb(220,220,220);
36 -webkit-box-shadow: inset 0px 1px 0px #3e9cbf, 0px 5px 0px 0px #205c73, 0px 10px 5px #999;
37 -moz-box-shadow: inset 0px 1px 0px #3e9cbf, 0px 5px 0px 0px #205c73, 0px 10px 5px #999;
38 box-shadow: inset 0px 1px 0px #3e9cbf, 0px 5px 0px 0px #205c73, 0px 10px 5px #999;
40 /*give the corners a small curve*/
41 -moz-border-radius: 7px;
42 -webkit-border-radius: 7px;
48 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
50 <script type="text/javascript" src="wordlists.js" ></script>
51 <script type="text/javascript" src="wordlists_plus.js" ></script>
52 <script type="text/javascript" src="internationalization_tables.js" ></script>
53 <script type="text/javascript" src="audioProcessing.js" ></script>
54 <script type="text/javascript" src="toneprot.js" ></script>
55 <script type="text/javascript" src="RecordRTC.min.js" defer="true" ></script>
56 <script type="text/javascript" src="jszip.min.js" defer="true" ></script>
57 <script type="text/javascript" src="mespeak/mespeak.full.js" defer="true" ></script>
58 <script type="text/javascript" src="fft.js/lib/real.js" defer="true" ></script>
59 <script type="text/javascript" src="fft.js/lib/complex.js" defer="true" ></script>
60 <script type="text/javascript" src="pitch.js/src/pitch.js" defer="true" ></script>
63 <body onload="if(window.speechSynthesis)window.speechSynthesis.getVoices();localStorage.sgc3_saveAudio = JSON.stringify(false);" onfocus="load_SGC3_settings ();" onblur="store_SGC3_settings ();" onunload="sgc3_settings.saveAudio = false; store_SGC3_settings (); if(settingsWindow)settingsWindow.close(); if(selectWordsWindow)selectWordsWindow.close();" contextmenu="mymenu" >
66 Copyright (C) 2016 R.J.J.H. van Son (r.j.j.h.vanson@gmail.com)
68 This program is free software; you can redistribute it and/or
69 modify it under the terms of the GNU General Public License
70 as published by the Free Software Foundation; either version 2
71 of the License, or (at your option) any later version.
73 This program is distributed in the hope that it will be useful,
74 but WITHOUT ANY WARRANTY; without even the implied warranty of
75 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
76 GNU General Public License for more details.
78 You can find a copy of the GNU General Public License at
79 http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
81 <menu type="context" id="mymenu" hidden='true' >
82 <menu type="context" id="GRADE" label="Grade" icon="sgc.png" title="Grade" >
83 <menuitem id="GRADE1" label="1" onclick="setGRADE(currentPinyin, 1);" icon="sgc.png" title="1" ></menuitem>
84 <menuitem id="GRADE2" label="2" onclick="setGRADE(currentPinyin,2);" icon="sgc.png" title="2" ></menuitem>
85 <menuitem id="GRADE3" label="3" onclick="setGRADE(currentPinyin,3);" icon="sgc.png" title="3" ></menuitem>
86 <menuitem id="GRADE4" label="4" onclick="setGRADE(currentPinyin,4);" icon="sgc.png" title="4" ></menuitem>
87 <menuitem id="GRADE5" label="5" onclick="setGRADE(currentPinyin,5);" icon="sgc.png" title="5" ></menuitem>
88 <menuitem id="GRADE6" label="6" onclick="setGRADE(currentPinyin,6);" icon="sgc.png" title="6" ></menuitem>
89 <menuitem id="GRADE7" label="7" onclick="setGRADE(currentPinyin,7);" icon="sgc.png" title="7" ></menuitem>
90 <menuitem id="GRADE8" label="8" onclick="setGRADE(currentPinyin,8);" icon="sgc.png" title="8" ></menuitem>
91 <menuitem id="GRADE9" label="9" onclick="setGRADE(currentPinyin,9);" icon="sgc.png" title="9" ></menuitem>
92 <menuitem id="GRADE10" label="10" onclick="setGRADE(currentPinyin,10);" icon="sgc.png" title="10" ></menuitem>
96 <div style="color: rgb(220,220,220); position: fixed; bottom: 36%; right: 2%; width: 17%; height: 5%; font-size: 3vmin; text-align: center; text-overflow: clip; vertical-align: bottom; " id="CollectionString" align="center"></div>
97 <div style="color: rgb(220,220,220); position: fixed; bottom: 24%; right: 2%; height: 14%; width: 17%; font-size: 6vmin; text-align: center; vertical-align: top; " id="SaveAudioLight" contextmenu="mymenu" ><a href="javascript:gradePrompt()" id='GradePrompt' style="color: inherit; text-decoration:none; " >● <br/>
98 <span id="GradeString" style="font-style: italic; font-size: 5vmin; "></span></a>
102 <div style="color: gray; position: fixed; top: 5%; left: 5%; font-size: 15vmin" id="RecordingLight">●</div>
103 <p><audio id='ExampleAudio' controls="false" hidden="true" ></audio></p>
104 <canvas id="TonePlot" width="1000" height="1000" style="position: fixed; top: 5%; left: 22.5%; width: 55%; height: 55%; "></canvas>
106 <div id="DisplayText" title="" >
107 <div style="color: black; position: fixed; top: 60%; left: 10%; width: 70%; font-size: 6vmin; text-align: center; " id="DisplayString" align="center" >
108 <span id="Pinyin">-</span> <span id="Character">-</span> <span id="Translation" style="font-style: italic; font-size: 6vmin; ">-</span></div>
109 <div style="color: green; position: fixed; top: 68%; left: 20%; width: 60%; font-size: 4vmin; text-align: center; text-overflow: clip; " id="ResultString" align="center">---</div>
110 <div style="color: green; position: fixed; top: 73%; left: 20%; width: 60%; font-size: 4vmin; text-align: center; text-overflow: clip; " id="FeedbackString" align="center">---</div>
114 <div style="color: blue; position: fixed; bottom: 18%; left: 5%; width: 90%; font-size: 3vmin; text-align: center; text-overflow: clip; " id="WordlistString" align="center">---</div>
116 <canvas id="LeftProgress" width="1000" height="2" style="position: fixed; bottom: 18%; left: 6%; width: 15%; height: 4px; "></canvas>
117 <button type="button" style="color: black; bottom: 2%; left: 5%" id="PreviousButton" ><span style="font-size: 100%">|◀<br /><span id="Previous" >Previous</span></span></button>
118 <button type="button" style="color: gray; bottom: 2%; left: 23%" id="RecordButton" disabled="true" ><span style="font-size: 100%; ">●<br /><span id="Record" >Record</span></span></button>
119 <button type="button" style="color: gray; bottom: 2%; left: 41%" id="PlayButton" disabled="true" ><span style="font-size: 100%">►<br /><span id="Play" >Play</span></span></button>
120 <button type="button" style="color: black; bottom: 2%; left: 59%" id="ExampleButton" ><span style="font-size: 100%">►<br /><span id="Example" >Example</span></span></button>
121 <canvas id="RightProgress" width="1000" height="2" style="position: fixed; bottom: 18%; left: 78%; width: 15%; height: 4px; "></canvas>
122 <button type="button" style="color: black; bottom: 2%; left: 77%; " id="NextButton" ><span style="font-size: 100%">▶|<br /><span id="Next" >Next</span></span></button>
124 <button type="button" style="color: black; bottom: 84%; right: 2%; height: 10%" id="Settings" ><span style="font-size: 100%">→ <span id="Config" >Settings</span></span></button>
127 <button type="button" style="color: black; bottom: 71%; right: 2%; height: 10%" id="PreviousWordlist" ><span style="font-size: 100%"><span id="WordlistUp" >Previous</span> ↑</span></button>
128 <button type="button" style="color: black; bottom: 50%; right: 2%; height: 10%" id="NextWordlist" ><span style="font-size: 100%"> <span id="WordlistDown" title="" >Next</span> ↓</span></button>
129 <select id="SelectWordList" style="position: fixed; color: black; bottom: 61%; right: 2%; height: 8%; width: 17%; font: 3vmin 'Helvetica'; background-color: rgb(220,220,220); " >
130 <option value="---" ><span id="SelectWords" title="" >Words</span></option>
131 <option value="---" ><span id="WordlistCaption" title="" >Word List</span></option>
134 <select id="Register" style="position: fixed; color: black; bottom: 39%; right: 2%; height: 8%; width: 17%; font: 3.5vmin 'Helvetica'; background-color: rgb(220,220,220); " onchange="sgc3_settings.register = getRegister();" >
135 <option value="---" ><span id="RegisterCaption" title="" >---</span></option>
138 <select id="Language" style="position: fixed; color: black; bottom: 28%; right: 2%; height: 8%; width: 17%; font: 3.5vmin 'Helvetica'; background-color: rgb(220,220,220); " onchange="sgc3_settings.language = change_mainpageLanguage(); " >
139 <option value="---" ><span id="LanguageCaption" title="" >---</span></option>
142 <script type="text/javascript">
144 // Install the service worker for offline use
145 if ('serviceWorker' in navigator) {
146 navigator.serviceWorker.register('sw.js').then(function(registration) {
147 // Registration was successful
148 console.log('ServiceWorker registration successful with scope: ', registration.scope);
149 }).catch(function(err) {
150 // registration failed :(
151 console.log('ServiceWorker registration failed: ', err);
155 // Set DOM parameters
157 var selectWordsWindow;
158 var gradingMenu = document.getElementById('GRADE');
159 var gradePromptAnchor = document.getElementById('GradePrompt');
160 var recordingLight = document.getElementById('RecordingLight');
161 var tonePlotArea = document.getElementById('TonePlot');
162 var record = document.getElementById('RecordButton');
163 var play = document.getElementById('PlayButton');
164 var example = document.getElementById('ExampleButton');
165 var previous = document.getElementById('PreviousButton');
166 var next = document.getElementById('NextButton');
167 var prevWordlist = document.getElementById('PreviousWordlist');
168 var nextWordlist = document.getElementById('NextWordlist');
169 var selectWordlist = document.getElementById('SelectWordList');
170 var exampleElement = document.getElementById('ExampleAudio');
171 var settingsButton = document.getElementById('Settings');
172 var displayText = document.getElementById('DisplayText');
173 var stopRecording, playSound;
174 var exampleURL = "-";
176 var currentRecordingTime = 3;
177 var clearRecordedData = function () { };
180 function keyPress (e) {
181 var key = e.key ? e.key : e.code;
182 if (key == "ArrowRight") {
184 } else if (key == ">" || key == ".") {
186 } else if (key == "ArrowLeft") {
188 } else if (key == "<" || key == ",") {
190 } else if (key == "ArrowUp") {
191 prevWordlist.onclick()
192 } else if (key == "ArrowDown") {
193 nextWordlist.onclick()
194 } else if (key == "Tab") {
196 } else if (key == "Enter") {
198 } else if (key == " " || key == "Space") {
201 if (["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"].indexOf(key) > -1) {
202 setGRADE(currentPinyin, key);
206 window.onkeydown = keyPress;
208 // Swipes with mouse or touch
210 tonePlotArea.addEventListener("touchstart", mouseDown, false);
211 tonePlotArea.addEventListener("touchend", mouseUp, false);
213 var verticalThreshold = tonePlotArea.height / 30;
214 var horizontalThreshold = tonePlotArea.width / 30;
215 var mouseDownCoordinates = {x: -1, y: -1};
216 var mouseUpCoordinates = {x: -1, y: -1};
217 // Mouse down or touch start
218 function mouseDown (e) {
219 if (e.touches) e.preventDefault();
220 mouseDownCoordinates.x = e.touches ? e.touches[0].clientX : e.clientX;
221 mouseDownCoordinates.y = e.touches ? e.touches[0].clientY : e.clientY;
222 mouseUpCoordinates.x = -1;
223 mouseUpCoordinates.y = -1;
225 // Mouse up or touch end
226 function mouseUp (e) {
227 mouseUpCoordinates.x = e.touches ? e.touches[e.touches.length - 1].clientX : e.clientX;
228 mouseUpCoordinates.y = e.touches ? e.touches[e.touches.length - 1].clientY : e.clientY;
230 if (mouseDownCoordinates.x > -1 && mouseDownCoordinates.y > -1) {
232 if (Math.abs(mouseUpCoordinates.y - mouseDownCoordinates.y) < verticalThreshold) {
234 if(mouseUpCoordinates.x - mouseDownCoordinates.x < -horizontalThreshold) {
237 } else if(mouseUpCoordinates.x - mouseDownCoordinates.x > horizontalThreshold) {
240 } else if (Math.abs(mouseUpCoordinates.x - mouseDownCoordinates.x) < horizontalThreshold) {
243 if(mouseUpCoordinates.y - mouseDownCoordinates.y < -verticalThreshold) {
244 nextWordlist.onclick();
246 } else if(mouseUpCoordinates.y - mouseDownCoordinates.y > verticalThreshold) {
247 prevWordlist.onclick();
252 mouseDownCoordinates.x = -1;
253 mouseDownCoordinates.y = -1;
254 mouseUpCoordinates.x = -1;
255 mouseUpCoordinates.y = -1;
258 tonePlotArea.onmousedown = mouseDown;
259 tonePlotArea.onmouseup = mouseUp;
260 tonePlotArea.onmouseout = mouseUp;
263 * Global variables from audioProcessing.js
265 * var recordedBlob, recordedBlobURL;
266 * var recordedArray, currentAudioWindow;
267 * var recordedSampleRate, recordedDuration;
272 // This setting is NOT stored
273 localStorage.sgc3_currentWord = JSON.stringify(0);
274 sessionStorage.lastPinyin = "";
275 localStorage.sgc3_saveAudio = JSON.stringify(false);
277 // These settings are stored
278 var sgc3_settings = {
284 register: 249, // Must match Register_249 Id
285 wordList: "20 basic tone combinations",
287 synthesis_eSpeak: false,
289 personalWordlists: [],
290 displayNumbers: false,
295 examplesDatabaseName: "SGC3 examples",
296 audioDatabaseName: "SGC3 audio",
297 currentCollection: "SGC3",
304 function store_SGC3_settings () {
305 for (x in sgc3_settings) {
306 localStorage["sgc3_"+x] = JSON.stringify(sgc3_settings[x]);
308 localStorage.sgc3_currentWord = JSON.stringify(currentWord);
311 function load_SGC3_settings () {
312 for (x in sgc3_settings) {
313 // For some reason, parsing the language goes wrong
314 if (localStorage["sgc3_"+x]) {
315 sgc3_settings[x] = JSON.parse(localStorage["sgc3_"+x]);
319 set_mainpageLanguage (sgc3_settings.language);
320 setRegister (sgc3_settings.register);
321 if (sgc3_settings.settingsRead) hideSelectors();
322 wordlists = combineWordlistLists(global_wordlists, sgc3_settings.personalWordlists);
323 get_wordlist(sgc3_settings.wordList);
324 add_wordlist_names_to_select ();
325 document.getElementById('WordlistString').textContent = sgc3_settings.wordList;
326 document.getElementById('SaveAudioLight').style.color = sgc3_settings.saveAudio ? 'blue' : 'rgb(250,250,250)';
327 var collectionString = document.getElementById('CollectionString');
328 if(collectionString) {
329 collectionString.textContent = sgc3_settings.saveAudio ? sgc3_settings.currentCollection : "";
330 collectionString.style.fontSize = collectionString.textContent.length > 12 ? "2.5vmin" : "3vmin";
331 collectionString.style.color = sgc3_settings.saveAudio ? 'blue' : 'rgb(250,250,250)';
334 // Disabling/hiding the menu does not work everywhere
335 gradingMenu.disabled = ! sgc3_settings.saveAudio;
336 gradingMenu.hidden = ! sgc3_settings.saveAudio;
338 // Reset currentWord if the wordlist has changed
340 if (localStorage.sgc3_currentWord) {
341 currentWord = JSON.parse(localStorage.sgc3_currentWord);
343 if (currentWord < 0){
344 clearRecordedData ();
347 // Make sure the current word is indeed active
348 while (currentWord < currentWordlist.length && ! itemIsActive (currentWord)) ++currentWord;
351 write_CurrentString ();
352 examplesDatabaseName = sgc3_settings.examplesDatabaseName;
353 audioDatabaseName = sgc3_settings.audioDatabaseName;
355 if(sgc3_settings.saveAudio) getCurrentMetaData (sgc3_settings.currentCollection, function (list){performanceRecord = objectList2performanceRecord(list);});
357 // Reset the language
358 if (!sgc3_settings.language) {
359 sgc3_settings.language = (userLanguage) ? userLanguage : "EN";
361 set_mainpageLanguage (sgc3_settings.language);
364 // Initialize to stored settings
365 if(localStorage.sgc3_language) {
366 load_SGC3_settings ();
368 // Do hard initialization of stored parameters
369 store_SGC3_settings ();
373 if (!sgc3_settings.language) {
374 sgc3_settings.language = (userLanguage) ? userLanguage : "EN";
376 set_mainpageLanguage (sgc3_settings.language);
379 function getTTSvoice(){
381 if (window.speechSynthesis) {
382 var voices = window.speechSynthesis.getVoices();
383 if (! voices) voices = window.speechSynthesis.getVoices();
384 for (x = 0; x < voices.length; ++x) {
385 if (voices[x].lang == "zh-CN") {
387 selectedVoice = voices[x];
391 return selectedVoice;
394 if (window.speechSynthesis) {
395 window.speechSynthesis.onvoiceschanged = function() {
401 setRegister (sgc3_settings.register);
403 // Hide Language and Register when settings page has been opened
404 function hideSelectors () {
405 var languageElement = document.getElementById('Language');
406 var registerElement = document.getElementById('Register');
407 languageElement.style.display = 'none' ;
408 registerElement.style.display = 'none' ;
412 function setProgressBar (id, fraction) {
413 var progressBar = document.getElementById(id);
414 var progressBarCtx = progressBar.getContext("2d");
415 progressBarCtx.clearRect(0, 0, progressBarCtx.canvas.width, progressBarCtx.canvas.height);
416 progressBarCtx.fillStyle = "rgb(250,250,250)";
417 progressBarCtx.fillRect(0, 0, progressBarCtx.canvas.width, progressBarCtx.canvas.height);
418 progressBarCtx.beginPath();
419 progressBarCtx.fillStyle = "#008080";
421 progressBarCtx.fillRect(0, 0, progressBarCtx.canvas.width*fraction, progressBarCtx.canvas.height);
423 progressBarCtx.fillRect(progressBarCtx.canvas.width*(1+fraction), 0, progressBarCtx.canvas.width, progressBarCtx.canvas.height);
425 progressBarCtx.stroke();
428 // Replace contents of displayed string
429 function write_CurrentString () {
430 // Store current settings
431 store_SGC3_settings ()
434 get_wordlist (sgc3_settings.wordList);
435 write_WordlistName();
438 document.getElementById("GradeString").textContent = "";
439 var pinyinText = "-";
440 var characterText = "-";
441 var translationText = "-";
442 var lessonText = "-";
444 if( currentWord >= 0 && currentWord < currentWordlist.length ) {
445 currentItem = currentWordlist[currentWord];
446 currentLesson = sgc3_settings.wordList;
447 currentLesson += (currentItem[4] && currentItem[4] != "-") ? " "+currentItem[4] : "";
449 currentPinyin = currentItem[0];
450 currentPinyin = add_missing_neutral_tones (currentPinyin);
452 pinyinText = currentItem[1]
453 characterText = currentItem[2];
454 translationText = currentItem[3];
455 lessonText = currentItem[4];
456 exampleURL = currentItem[currentItem.length - 1];
458 if(sgc3_settings.shuffleLists) currentWordlist.shuffle();
462 // This cannot be the recorded Pinyin, therefor, clear all recorded data
463 if (currentPinyin == "" || currentPinyin != sessionStorage.lastPinyin) clearRecordedData ();
464 sessionStorage.lastPinyin = currentPinyin;
466 document.getElementById('Pinyin').style.display = sgc3_settings.displayPinyin ? "inline" : "none";
467 document.getElementById('Pinyin').style.color = "black";
468 document.getElementById('Pinyin').textContent = sgc3_settings.displayNumbers ? currentPinyin : pinyinText;
470 document.getElementById('Character').style.display = sgc3_settings.displayChar ? "inline" : "none";
471 document.getElementById('Character').style.color = "black";
472 document.getElementById('Character').textContent = characterText;
474 document.getElementById('Translation').style.display = sgc3_settings.displayTrans ? "inline" : "none";
475 document.getElementById('Translation').style.color = "black";
476 document.getElementById('Translation').textContent = lessonText != "-" ? translationText + " ["+lessonText+"]": translationText;
478 document.getElementById('Translation').style.fontSize = "6vmin";
479 if (document.getElementById('Translation').textContent.length > 50) {
480 document.getElementById('Translation').style.fontSize = "3.5vmin";
481 } else if (document.getElementById('Translation').textContent.length > 30) {
482 document.getElementById('Translation').style.fontSize = "4.5vmin";
485 document.getElementById("ResultString").textContent = "";
486 document.getElementById("ResultString").style.color = "green";
487 document.getElementById("FeedbackString").textContent = "";
488 document.getElementById("FeedbackString").style.color = "green";
490 // Plot tones and set recording time (Watch out for text appending: "3" + 0 can become "30")
491 currentRecordingTime = Number(sgc3_settings.recSecs) + Number(1.0*Math.ceil((numSyllables(currentPinyin) - 2)/2));
494 var fillFraction = currentWord / (currentWordlist.length - 1);
495 setProgressBar ("LeftProgress", -1*fillFraction);
496 setProgressBar ("RightProgress", fillFraction);
500 initializeDrawingParam ("TonePlot");
501 draw_example_pinyin ("TonePlot", currentPinyin);
503 // Draw current sound
504 // First, check whether there is a recorded sound stored (catch an invalid call to the "Audio" database)
505 if(sgc3_settings.saveAudio && sgc3_settings.audioDatabaseName != "Audio") {
506 var lesson = (lessonText != "-") ? " "+lessonText : "";
507 getCurrentAudioWindow (sgc3_settings.currentCollection, sgc3_settings.wordList+lesson, currentPinyin+".wav");
509 processRecordedSound ()
513 function write_WordlistName () {
514 document.getElementById('WordlistString').textContent = sgc3_settings.wordList;
515 // Always reset the selection
516 selectWordlist.selectedIndex = 1;
519 // Create the list with wordlists
520 add_wordlist_names_to_select();
522 sgc3_settings.wordList = (sgc3_settings.wordList) ? sgc3_settings.wordList : "20 basic tone combinations";
523 get_wordlist (sgc3_settings.wordList);
524 if(sgc3_settings.shuffleLists) currentWordlist.shuffle();
526 var currentItem = currentWordlist[currentWord];
527 var currentPinyin = currentItem[0];
528 var currentCharacter = currentItem[1];
529 var currentString = currentItem[0] + " " + currentItem[1] + " " + currentItem[2];
530 var currentLesson = "";
531 write_CurrentString ();
535 var mediaConstraints = { audio: true, video: false };
536 var errorCallback = function(err){console.log("Error: " + err.name);};
538 // Reveal hidden display text
539 displayText.onclick = function () {
540 document.getElementById('Pinyin').style.display = "inline";
541 if(!sgc3_settings.displayPinyin) document.getElementById('Pinyin').style.color = "red";
542 document.getElementById('Character').style.display = "inline";
543 if(!sgc3_settings.displayChar) document.getElementById('Character').style.color = "red";
544 document.getElementById('Translation').style.display = "inline";
545 if(!sgc3_settings.displayTrans) document.getElementById('Translation').style.color = "red";
549 settingsButton.onclick = function () {
550 if (localStorage.sgc3_settingsWindow && settingsWindow) {
551 settingsWindow.focus();
553 if (settingsWindow) settingsWindow.close();
554 settingsWindow = window.open('SpeakGoodChinese3_Settings.xml', '_blank');
555 localStorage.sgc3_settingsWindow = JSON.stringify(true);
560 function meSpeakExample (pinyin ) {
561 meSpeak.loadConfig("mespeak/mespeak_config.json");
562 meSpeak.loadVoice('mespeak/voices/zh.json');
563 meSpeak.speak(pinyin, {speed: sgc3_settings.ttsSpeed, variant: sgc3_settings.ttsVariant});
566 function speakExample (voice, text, pinyin) {
567 if (window.speechSynthesis) {
568 var msg = new SpeechSynthesisUtterance();
569 msg.onerror = function (event) {
570 speechSynthesis.cancel();
571 meSpeakExample (pinyin);
576 speechSynthesis.speak(msg);
578 meSpeakExample (pinyin);
582 example.onclick = function() {
583 if(! sgc3_settings.synthesis_eSpeak) {
584 getAndPlayExample (sgc3_settings.wordList, currentPinyin, playBlob, generateExample);
586 meSpeakExample (currentPinyin);
590 function playBlob (blob) {
591 var exampleElement = document.getElementById('ExampleAudio');
592 exampleElement.src = URL.createObjectURL(blob);
593 exampleElement.play();
596 function generateExample () {
597 var voice = getTTSvoice();
598 if (exampleURL && exampleURL != "-") {
599 exampleElement.src = exampleURL;
600 exampleElement.onerror = function (event) {
601 currentText = currentWordlist[currentWord][2];
602 speakExample (voice, currentText, currentPinyin)
604 exampleElement.play();
605 } else if (voiceZH_CN >= 0) {
606 currentText = currentWordlist[currentWord][2];
607 speakExample (voice, currentText, currentPinyin)
609 meSpeakExample (currentPinyin);
614 previous.onclick = function() {
616 while(! itemIsActive(currentWord))--currentWord;
617 if( currentWord < 0 ) {
618 currentWord = currentWordlist.length;
619 // Make sure the current word is indeed active
620 while (currentWord > -1 && ! itemIsActive (currentWord)) --currentWord;
622 write_CurrentString();
626 next.onclick = function() {
628 while(! itemIsActive(currentWord))++currentWord;
629 if(currentWord > currentWordlist.length) {
631 // Make sure the current word is indeed active
632 while (currentWord < currentWordlist.length && ! itemIsActive (currentWord)) ++currentWord;
634 write_CurrentString();
638 prevWordlist.onclick = function() {
640 if( wordlistNumber < 0 ) {
641 wordlistNumber = wordlists.length - 1;
643 sgc3_settings.wordList = wordlists[wordlistNumber][0];
644 get_wordlist (sgc3_settings.wordList);
645 if (sgc3_settings.shuffleLists) currentWordlist.shuffle();
648 write_CurrentString();
649 store_SGC3_settings ();
653 nextWordlist.onclick = function() {
655 if( wordlistNumber >= wordlists.length ) {
658 sgc3_settings.wordList = wordlists[wordlistNumber][0];
659 get_wordlist (sgc3_settings.wordList);
660 if (sgc3_settings.shuffleLists) currentWordlist.shuffle();
663 write_CurrentString();
664 store_SGC3_settings ();
667 // select wordlist by name
668 selectWordlist.onchange = function() {
669 if (selectWordlist.selectedIndex > 1) {
670 sgc3_settings.wordList = selectWordlist.options[selectWordlist.selectedIndex].value;
671 get_wordlist (sgc3_settings.wordList);
672 if (sgc3_settings.shuffleLists) currentWordlist.shuffle();
675 write_CurrentString();
676 store_SGC3_settings ();
677 } else if (selectWordlist.selectedIndex == 0) {
678 // Always reset the selection
679 selectWordlist.selectedIndex = 1;
680 // Open Wordlist page
681 selectWordsWindow = window.open('SpeakGoodChinese3_SelectWords.xml', '_blank');
685 function gradePrompt() {
686 var promptText = mainpage_tables[sgc3_settings.language]["GRADE"][1];
687 var currentGrade = "";
688 if(performanceRecord && performanceRecord [currentLesson] && performanceRecord [currentLesson][currentPinyin].Grade >=0) {
689 currentGrade = performanceRecord [currentLesson][currentPinyin].Grade + "";
691 var grade = prompt(promptText, currentGrade);
692 if (sgc3_settings.saveAudio & grade != "" && grade >= 0 && grade <= 10 ) setGRADE(currentPinyin, grade);
697 // When the call to getUserMEdia is susccessfull
698 var successCallback = function(mediaStream){
701 console.log("No media stream: " + mediaStream);
704 record.disabled = false;
705 record.style.color = "red";
707 var recordRTC = RecordRTC(mediaStream, {
708 recorderType: StereoAudioRecorder // optionally force WebAudio API to record audio
711 recordRTC.mimeType = {audio: 'audio/wav'};
713 clearRecordedData = function () {
714 play.disabled = true;
715 play.style.color = "gray";
716 recordRTC.clearRecordedData();
721 // Play recorded sound
722 play.onclick = function() {
723 audioContext.resume();
724 play_soundArray (currentAudioWindow, recordedSampleRate, 0, 0);
728 stopRecording = function() {
729 recordRTC.stopRecording(function(audioURL) {
730 recordingLight.style.color = "gray";
731 recordedBlobURL = audioURL;
732 recordedBlob = recordRTC.getBlob();
733 // Do things with Blob!!!
734 processAudio (recordedBlob);
736 play.disabled = false;
737 record.disabled = false;
738 record.style.color = "red";
739 play.style.color = "red";
740 recordingLight.style.color = "gray";
744 record.onclick = function() {
745 audioContext.resume();
746 clearRecordedData ();
747 recordRTC.startRecording();
748 play.disabled = true;
749 record.disabled = true;
750 record.style.color = "gray";
751 play.style.color = "gray";
752 recordingLight.style.top = "5%";
753 recordingLight.style.left = "5%";
754 recordingLight.style.fontSize = "15vmin";
755 recordingLight.style.color = "red";
757 // Set Timeout for stop
758 setTimeout(stopRecording, currentRecordingTime * 1000);
763 if (navigator.mediaDevices.getUserMedia) {
764 navigator.mediaDevices.getUserMedia(mediaConstraints).then(successCallback).catch(errorCallback);
766 navigator.webkitGetUserMedia(mediaConstraints, successCallback, errorCallback);
769 // Initialize Audio storage
770 load_SGC3_settings ();
771 initializeDataStorage (sgc3_settings.currentCollection);
774 var url = window.location.href;
775 url = url.replace(/[^\/]+$/, "wordlists/");
776 readAllRemoteWordlists (url);