Experimenting audioContext for Apple (Safari)
[sgc3.git] / SpeakGoodChinese3.xml
blob46f40e9563106bca3f843760f9de9c47ae5ef0e9
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" >
6 <head>
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>
14 <style>
15 body {
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;}
21 h1 {
22         text-align: center;
23         }
24 button {
25                 position: fixed;
26                 overflow: hidden;
27                 text-overflow: clip clip;
28                 width:17%;
29                 height:15%;
30                 cursor:pointer; /*forces the cursor to change to a hand when the button is hovered*/
31                 text-align:center;
32                 font: bold 4vmin "Helvetica";
33                 background-color: rgb(220,220,220);
34                 text-overflow: clip;
35                 
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;
39                 
40                 /*give the corners a small curve*/
41                 -moz-border-radius: 7px;
42                 -webkit-border-radius: 7px;
43                 border-radius: 7px;
44                 }
46 </style>
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>
61 </head>
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" >
64 <!--
65 SpeakGoodChinese 3
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
80 -->
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>
93                 </menu>
94         </menu>
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; " >&#9679; <br/>
98         <span id="GradeString" style="font-style: italic; font-size: 5vmin; "></span></a>
99         </div>
100         
101                 
102         <div style="color: gray; position: fixed; top: 5%; left: 5%; font-size: 15vmin" id="RecordingLight">&#9679;</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>     
105         
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>&#8195;<span id="Character">-</span>&#8195;<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>
111         </div>
112         
113         
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>
115         
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%">|&#9664;<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%; ">&#9679;<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%">&#9658;<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%">&#9658;<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%">&#9654;|<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%">&#8594; <span id="Config" >Settings</span></span></button>
126         <!-- Wordlists -->
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> &#8593;</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> &#8595;</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>
132         </select> 
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>
136         </select> 
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>
140         </select> 
142         <script type="text/javascript">
143         //<![CDATA[
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);
152           });
153         }
154         
155         // Set DOM parameters
156         var settingsWindow;
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 = "-";
175         var voiceZH_CN = -1;
176         var currentRecordingTime = 3;
177         var clearRecordedData = function () { };
178         
179         // Handle keypresses
180         function keyPress (e) {
181                 var key = e.key ? e.key : e.code;
182                 if (key == "ArrowRight") {
183                         next.onclick()
184                 } else if (key == ">" || key == ".") {
185                         next.onclick()
186                 } else if (key == "ArrowLeft") {
187                         previous.onclick()
188                 } else if (key == "<" || key == ",") {
189                         previous.onclick()
190                 } else if (key == "ArrowUp") {
191                         prevWordlist.onclick()
192                 } else if (key == "ArrowDown") {
193                         nextWordlist.onclick()
194                 } else if (key == "Tab") {
195                         play.onclick()
196                 } else if (key == "Enter") {
197                         record.onclick()
198                 } else if (key == " " || key == "Space") {
199                         example.onclick()
200                 }  else {
201                         if (["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"].indexOf(key) > -1) {
202                                 setGRADE(currentPinyin, key);
203                         };
204                 };
205         };
206         window.onkeydown = keyPress;
207         
208         // Swipes with mouse or touch
209         // Adde touch events
210         tonePlotArea.addEventListener("touchstart", mouseDown, false);
211         tonePlotArea.addEventListener("touchend", mouseUp, false);
212         // Parameters
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;
224         };
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;
229                 
230                 if (mouseDownCoordinates.x > -1 && mouseDownCoordinates.y > -1) {
231                         // Horizontal
232                         if (Math.abs(mouseUpCoordinates.y - mouseDownCoordinates.y) < verticalThreshold) {
233                                 // To the left
234                                 if(mouseUpCoordinates.x - mouseDownCoordinates.x < -horizontalThreshold) {
235                                         next.onclick();
236                                 // To the right
237                                 } else if(mouseUpCoordinates.x - mouseDownCoordinates.x > horizontalThreshold) {
238                                         previous.onclick();
239                                 };
240                         } else if (Math.abs(mouseUpCoordinates.x - mouseDownCoordinates.x) < horizontalThreshold) {
241                         // Vertical
242                                 // Up
243                                 if(mouseUpCoordinates.y - mouseDownCoordinates.y < -verticalThreshold) {
244                                         nextWordlist.onclick();
245                                 // Down
246                                 } else if(mouseUpCoordinates.y - mouseDownCoordinates.y > verticalThreshold) {
247                                         prevWordlist.onclick();
248                                 };
249                         };
250                 };
252                 mouseDownCoordinates.x = -1;
253                 mouseDownCoordinates.y = -1;
254                 mouseUpCoordinates.x = -1;
255                 mouseUpCoordinates.y = -1;
257         };
258         tonePlotArea.onmousedown = mouseDown;
259         tonePlotArea.onmouseup = mouseUp;
260         tonePlotArea.onmouseout = mouseUp;
261         
262         /*
263          * Global variables from audioProcessing.js
264          * 
265          * var recordedBlob, recordedBlobURL;
266          * var recordedArray, currentAudioWindow;
267          * var recordedSampleRate, recordedDuration;
268          * 
269          */
270          
271         // Global settings
272         // This setting is NOT stored
273         localStorage.sgc3_currentWord = JSON.stringify(0);
274         sessionStorage.lastPinyin = "";
275         localStorage.sgc3_saveAudio = JSON.stringify(false);
276         
277         // These settings are stored
278         var sgc3_settings = {
279                 settingsRead: false,
280                 recSecs: 3,
281                 ttsSpeed: 110,
282                 ttsVariant: "f3",
283                 shuffleLists: true,
284                 register: 249, // Must match Register_249 Id
285                 wordList: "20 basic tone combinations",
286                 language: "",
287                 synthesis_eSpeak: false,
288                 strict: 1,
289                 personalWordlists: [],
290                 displayNumbers: false,
291                 displayPinyin: true,
292                 displayChar: true,
293                 displayTrans: true,
294                 saveAudio: false,
295                 examplesDatabaseName: "SGC3 examples",
296                 audioDatabaseName: "SGC3 audio",
297                 currentCollection: "SGC3",
298                 selectedLessons: [],
299                 selectedTones: [],
300                 deselectedWords: []
301         };
302         
303         // Store settings
304         function store_SGC3_settings () {
305                 for (x in sgc3_settings) {
306                         localStorage["sgc3_"+x] = JSON.stringify(sgc3_settings[x]);
307                 };
308                 localStorage.sgc3_currentWord = JSON.stringify(currentWord);
309         };
310         
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]);
316                         };
317                 };
318                 
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)';
332                 };
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
339                 currentWord = 0;
340                 if (localStorage.sgc3_currentWord) {
341                         currentWord = JSON.parse(localStorage.sgc3_currentWord);
342                 };
343                 if (currentWord < 0){
344                         clearRecordedData ();
345                         currentWord = 0;
346                 };
347                 // Make sure the current word is indeed active
348                 while (currentWord < currentWordlist.length && ! itemIsActive (currentWord)) ++currentWord;
349                 
350                 // update display
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);});
356                 
357                 // Reset the language
358                 if (!sgc3_settings.language) {
359                         sgc3_settings.language = (userLanguage) ? userLanguage : "EN";
360                 };
361                 set_mainpageLanguage (sgc3_settings.language);
362         };
363         
364         // Initialize to stored settings
365         if(localStorage.sgc3_language) {
366                 load_SGC3_settings ();
367         } else {
368                 // Do hard initialization of stored parameters
369                 store_SGC3_settings ();
370         };
371         
372         // Set language
373         if (!sgc3_settings.language) {
374                 sgc3_settings.language = (userLanguage) ? userLanguage : "EN";
375         };
376         set_mainpageLanguage (sgc3_settings.language);
377         
378         // Set TTS
379         function getTTSvoice(){
380                 var selectedVoice;
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") { 
386                                         voiceZH_CN = x;
387                                         selectedVoice = voices[x];
388                                 };
389                         };
390                 }
391                 return selectedVoice;
392         };
393         
394         if (window.speechSynthesis) {
395                 window.speechSynthesis.onvoiceschanged = function() {
396                     getTTSvoice();
397                 };
398         }
400         // Set Register
401         setRegister (sgc3_settings.register);
402         
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' ;
409         };
411         // Set progress bars
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";
420                 if (fraction > 0) {
421                         progressBarCtx.fillRect(0, 0, progressBarCtx.canvas.width*fraction, progressBarCtx.canvas.height);
422                 } else {
423                         progressBarCtx.fillRect(progressBarCtx.canvas.width*(1+fraction), 0, progressBarCtx.canvas.width, progressBarCtx.canvas.height);
424                 };
425                 progressBarCtx.stroke();
426         };
427         
428         // Replace contents of displayed string
429         function write_CurrentString () {
430                 // Store current settings
431                 store_SGC3_settings ()
432                 
433                 // Get wordlist
434                 get_wordlist (sgc3_settings.wordList);
435                 write_WordlistName();
437                 // Get texts
438                 document.getElementById("GradeString").textContent = "";
439                 var pinyinText = "-";
440                 var characterText = "-";
441                 var translationText = "-";
442                 var lessonText = "-";
443                 exampleURL = "-";
444                 if( currentWord >= 0 && currentWord < currentWordlist.length ) {
445                         currentItem = currentWordlist[currentWord];
446                         currentLesson = sgc3_settings.wordList;
447                         currentLesson += (currentItem[4] && currentItem[4] != "-") ? " "+currentItem[4] : "";
448                         
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];
457                 } else {
458                         if(sgc3_settings.shuffleLists) currentWordlist.shuffle();
459                         currentPinyin = "";
460                         currentItem = [];
461                 };
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;
469                 
470                 document.getElementById('Character').style.display = sgc3_settings.displayChar ? "inline" : "none";
471                 document.getElementById('Character').style.color = "black";
472                 document.getElementById('Character').textContent = characterText;
473                 
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;
477                 
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";
483                 };
484                 
485                 document.getElementById("ResultString").textContent = "";
486                 document.getElementById("ResultString").style.color = "green";
487                 document.getElementById("FeedbackString").textContent = "";
488                 document.getElementById("FeedbackString").style.color = "green";
489                         
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));
492                 
493                 // Set progress bars
494                 var fillFraction = currentWord / (currentWordlist.length - 1);
495                 setProgressBar ("LeftProgress", -1*fillFraction);
496                 setProgressBar ("RightProgress", fillFraction);
497                 
498                 
499                 //Get tone contour
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");
508                 } else {
509                         processRecordedSound ()
510                 };
511         };
512         
513         function write_WordlistName () {
514                 document.getElementById('WordlistString').textContent = sgc3_settings.wordList;
515                 // Always reset the selection
516                 selectWordlist.selectedIndex = 1;       
517         };
518         
519         // Create the list with wordlists
520         add_wordlist_names_to_select();
521         
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();
525         var currentWord = 0;
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 ();
532                 
533         // Other parameters
534         var mediaStream;
535         var mediaConstraints = { audio: true, video: false };
536         var errorCallback = function(err){console.log("Error: " + err.name);};
537         
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";
546         };
547         
548         // Settings button
549         settingsButton.onclick = function () {
550                 if (localStorage.sgc3_settingsWindow && settingsWindow) {
551                         settingsWindow.focus();
552                 } else {
553                         if (settingsWindow) settingsWindow.close();
554                         settingsWindow = window.open('SpeakGoodChinese3_Settings.xml', '_blank');
555                         localStorage.sgc3_settingsWindow = JSON.stringify(true);
556                 }
557         };
558         
559         // Play example 
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});      
564         };
565         
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); 
572                         };
573                     msg.voice = voice;
574                     msg.rate = 0.7;
575                     msg.text = text;
576                         speechSynthesis.speak(msg);
577                 } else {
578                         meSpeakExample (pinyin); 
579                 };
580         };
581         
582         example.onclick = function() {
583                 if(! sgc3_settings.synthesis_eSpeak) {
584                         getAndPlayExample (sgc3_settings.wordList, currentPinyin, playBlob, generateExample);
585                 } else {
586                         meSpeakExample (currentPinyin);
587                 };
588         };
589         
590         function playBlob (blob) {
591                 var exampleElement = document.getElementById('ExampleAudio');
592                 exampleElement.src = URL.createObjectURL(blob);
593                 exampleElement.play();
594         };
595         
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)
603                         };
604                         exampleElement.play();
605                 } else if (voiceZH_CN >= 0) {
606                     currentText = currentWordlist[currentWord][2];
607                     speakExample (voice, currentText, currentPinyin)
608                 } else {
609                         meSpeakExample (currentPinyin);
610                 };
611         };
613         // Previous word 
614         previous.onclick = function() {
615                 --currentWord;
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;         
621                 };
622                 write_CurrentString();
623         };
625         // Next word 
626         next.onclick = function() {
627                 ++currentWord;          
628                 while(! itemIsActive(currentWord))++currentWord;
629                 if(currentWord > currentWordlist.length) {
630                         currentWord = 0;
631                         // Make sure the current word is indeed active
632                         while (currentWord < currentWordlist.length && ! itemIsActive (currentWord)) ++currentWord;
633                 };
634                 write_CurrentString();
635         };
637         // Previous wordlist
638         prevWordlist.onclick = function() {
639                 --wordlistNumber;
640                 if( wordlistNumber < 0 ) {
641                         wordlistNumber = wordlists.length - 1;
642                 };
643                 sgc3_settings.wordList = wordlists[wordlistNumber][0];
644                 get_wordlist (sgc3_settings.wordList);
645                 if (sgc3_settings.shuffleLists) currentWordlist.shuffle();
646                 currentWord = 0;
647                 
648                 write_CurrentString();
649                 store_SGC3_settings ();
650         };
652         // Next wordlist
653         nextWordlist.onclick = function() {
654                 ++wordlistNumber;
655                 if( wordlistNumber >= wordlists.length ) {
656                         wordlistNumber = 0;
657                 };
658                 sgc3_settings.wordList = wordlists[wordlistNumber][0];
659                 get_wordlist (sgc3_settings.wordList);
660                 if (sgc3_settings.shuffleLists) currentWordlist.shuffle();
661                 currentWord = 0;
662                 
663                 write_CurrentString();
664                 store_SGC3_settings ();
665         };
666         
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();
673                         currentWord = 0;
674                         
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');
682                 };
683         };
684                 
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 + "";
690                 };
691                 var grade = prompt(promptText, currentGrade);
692                 if (sgc3_settings.saveAudio & grade != "" && grade >= 0 && grade <= 10 ) setGRADE(currentPinyin, grade);
693                 return void(0);
694         };
697         // When the call to getUserMEdia is susccessfull
698         var successCallback = function(mediaStream){
699                 
700                 if(!mediaStream){ 
701                         console.log("No media stream: " + mediaStream);
702                 }
703                 
704             record.disabled = false;
705             record.style.color = "red";
706                 
707                 var recordRTC = RecordRTC(mediaStream, {
708                         recorderType: StereoAudioRecorder // optionally force WebAudio API to record audio
709                         
710                 }); 
711                 recordRTC.mimeType = {audio: 'audio/wav'};
712                 
713                 clearRecordedData = function () {
714                         play.disabled = true;
715                         play.style.color = "gray";
716                         recordRTC.clearRecordedData();
717                         recordRTC.reset();
718                         clearRecording ();
719                 };
720                 
721                 // Play recorded sound
722                 play.onclick = function() {
723                         audioContext.resume();
724                         play_soundArray (currentAudioWindow, recordedSampleRate, 0, 0);
725                 };
726                 
727                 // Stop
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);
735                         });
736                     play.disabled = false;
737                     record.disabled = false;
738                     record.style.color = "red";
739                     play.style.color = "red";
740                     recordingLight.style.color = "gray";
741                 }
742                 
743                 // onClick Start
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";
756                     
757                     // Set Timeout for stop
758                     setTimeout(stopRecording, currentRecordingTime * 1000);
759                 }
760         
761         };
762         
763         if (navigator.mediaDevices.getUserMedia) {
764                 navigator.mediaDevices.getUserMedia(mediaConstraints).then(successCallback).catch(errorCallback);
765         } else {
766                 navigator.webkitGetUserMedia(mediaConstraints, successCallback, errorCallback);
767         };
769         // Initialize Audio storage
770         load_SGC3_settings ();
771         initializeDataStorage (sgc3_settings.currentCollection);
772         
773         // Read remote lists
774         var url = window.location.href;
775         url = url.replace(/[^\/]+$/, "wordlists/");
776         readAllRemoteWordlists (url);
777         
778         //]]>   
779         </script>
780 </body>
781 </html>