annotate core.js @ 22:1f375b7d75fd tip

updated warning for breaks during test
author Giulio Moro <giuliomoro@yahoo.it>
date Fri, 13 May 2016 19:01:08 +0100
parents a6ce34b4dd41
children
rev   line source
giuliomoro@0 1 /**
giuliomoro@0 2 * core.js
giuliomoro@0 3 *
giuliomoro@0 4 * Main script to run, calls all other core functions and manages loading/store to backend.
giuliomoro@0 5 * Also contains all global variables.
giuliomoro@0 6 */
giuliomoro@0 7
giuliomoro@0 8 /* create the web audio API context and store in audioContext*/
giuliomoro@0 9 var audioContext; // Hold the browser web audio API
giuliomoro@0 10 var projectXML; // Hold the parsed setup XML
giuliomoro@0 11 var schemaXSD; // Hold the parsed schema XSD
giuliomoro@0 12 var specification;
giuliomoro@0 13 var interfaceContext;
giuliomoro@0 14 var storage;
giuliomoro@0 15 var popup; // Hold the interfacePopup object
giuliomoro@0 16 var testState;
giuliomoro@0 17 var currentTrackOrder = []; // Hold the current XML tracks in their (randomised) order
giuliomoro@0 18 var audioEngineContext; // The custom AudioEngine object
giuliomoro@0 19 var projectReturn;
giuliomoro@0 20 var returnUrl; // Holds the url to be redirected to at the end of the test
giuliomoro@0 21
giuliomoro@0 22 // Add a prototype to the bufferSourceNode to reference to the audioObject holding it
giuliomoro@0 23 AudioBufferSourceNode.prototype.owner = undefined;
giuliomoro@0 24 // Add a prototype to the bufferSourceNode to hold when the object was given a play command
giuliomoro@0 25 AudioBufferSourceNode.prototype.playbackStartTime = undefined;
giuliomoro@0 26 // Add a prototype to the bufferNode to hold the desired LINEAR gain
giuliomoro@0 27 AudioBuffer.prototype.playbackGain = undefined;
giuliomoro@0 28 // Add a prototype to the bufferNode to hold the computed LUFS loudness
giuliomoro@0 29 AudioBuffer.prototype.lufs = undefined;
giuliomoro@0 30
giuliomoro@0 31 // Firefox does not have an XMLDocument.prototype.getElementsByName
giuliomoro@0 32 // and there is no searchAll style command, this custom function will
giuliomoro@0 33 // search all children recusrively for the name. Used for XSD where all
giuliomoro@0 34 // element nodes must have a name and therefore can pull the schema node
giuliomoro@0 35 XMLDocument.prototype.getAllElementsByName = function(name)
giuliomoro@0 36 {
giuliomoro@0 37 name = String(name);
giuliomoro@0 38 var selected = this.documentElement.getAllElementsByName(name);
giuliomoro@0 39 return selected;
giuliomoro@0 40 }
giuliomoro@0 41
giuliomoro@0 42 Element.prototype.getAllElementsByName = function(name)
giuliomoro@0 43 {
giuliomoro@0 44 name = String(name);
giuliomoro@0 45 var selected = [];
giuliomoro@0 46 var node = this.firstElementChild;
giuliomoro@0 47 while(node != null)
giuliomoro@0 48 {
giuliomoro@0 49 if (node.getAttribute('name') == name)
giuliomoro@0 50 {
giuliomoro@0 51 selected.push(node);
giuliomoro@0 52 }
giuliomoro@0 53 if (node.childElementCount > 0)
giuliomoro@0 54 {
giuliomoro@0 55 selected = selected.concat(node.getAllElementsByName(name));
giuliomoro@0 56 }
giuliomoro@0 57 node = node.nextElementSibling;
giuliomoro@0 58 }
giuliomoro@0 59 return selected;
giuliomoro@0 60 }
giuliomoro@0 61
giuliomoro@0 62 XMLDocument.prototype.getAllElementsByTagName = function(name)
giuliomoro@0 63 {
giuliomoro@0 64 name = String(name);
giuliomoro@0 65 var selected = this.documentElement.getAllElementsByTagName(name);
giuliomoro@0 66 return selected;
giuliomoro@0 67 }
giuliomoro@0 68
giuliomoro@0 69 Element.prototype.getAllElementsByTagName = function(name)
giuliomoro@0 70 {
giuliomoro@0 71 name = String(name);
giuliomoro@0 72 var selected = [];
giuliomoro@0 73 var node = this.firstElementChild;
giuliomoro@0 74 while(node != null)
giuliomoro@0 75 {
giuliomoro@0 76 if (node.nodeName == name)
giuliomoro@0 77 {
giuliomoro@0 78 selected.push(node);
giuliomoro@0 79 }
giuliomoro@0 80 if (node.childElementCount > 0)
giuliomoro@0 81 {
giuliomoro@0 82 selected = selected.concat(node.getAllElementsByTagName(name));
giuliomoro@0 83 }
giuliomoro@0 84 node = node.nextElementSibling;
giuliomoro@0 85 }
giuliomoro@0 86 return selected;
giuliomoro@0 87 }
giuliomoro@0 88
giuliomoro@0 89 // Firefox does not have an XMLDocument.prototype.getElementsByName
giuliomoro@0 90 if (typeof XMLDocument.prototype.getElementsByName != "function") {
giuliomoro@0 91 XMLDocument.prototype.getElementsByName = function(name)
giuliomoro@0 92 {
giuliomoro@0 93 name = String(name);
giuliomoro@0 94 var node = this.documentElement.firstElementChild;
giuliomoro@0 95 var selected = [];
giuliomoro@0 96 while(node != null)
giuliomoro@0 97 {
giuliomoro@0 98 if (node.getAttribute('name') == name)
giuliomoro@0 99 {
giuliomoro@0 100 selected.push(node);
giuliomoro@0 101 }
giuliomoro@0 102 node = node.nextElementSibling;
giuliomoro@0 103 }
giuliomoro@0 104 return selected;
giuliomoro@0 105 }
giuliomoro@0 106 }
giuliomoro@0 107
giuliomoro@0 108 window.onload = function() {
giuliomoro@0 109 // Function called once the browser has loaded all files.
giuliomoro@0 110 // This should perform any initial commands such as structure / loading documents
giuliomoro@0 111
giuliomoro@0 112 // Create a web audio API context
giuliomoro@0 113 // Fixed for cross-browser support
giuliomoro@0 114 var AudioContext = window.AudioContext || window.webkitAudioContext;
giuliomoro@0 115 audioContext = new AudioContext;
giuliomoro@0 116
giuliomoro@0 117 // Create test state
giuliomoro@0 118 testState = new stateMachine();
giuliomoro@0 119
giuliomoro@0 120 // Create the popup interface object
giuliomoro@0 121 popup = new interfacePopup();
giuliomoro@0 122
giuliomoro@0 123 // Create the specification object
giuliomoro@0 124 specification = new Specification();
giuliomoro@0 125
giuliomoro@0 126 // Create the interface object
giuliomoro@0 127 interfaceContext = new Interface(specification);
giuliomoro@0 128
giuliomoro@0 129 // Create the storage object
giuliomoro@0 130 storage = new Storage();
giuliomoro@0 131 // Define window callbacks for interface
giuliomoro@0 132 window.onresize = function(event){interfaceContext.resizeWindow(event);};
giuliomoro@0 133 };
giuliomoro@0 134
giuliomoro@0 135 function loadProjectSpec(url) {
giuliomoro@0 136 // Load the project document from the given URL, decode the XML and instruct audioEngine to get audio data
giuliomoro@0 137 // If url is null, request client to upload project XML document
giuliomoro@0 138 var xmlhttp = new XMLHttpRequest();
giuliomoro@0 139 xmlhttp.open("GET",'test-schema.xsd',true);
giuliomoro@0 140 xmlhttp.onload = function()
giuliomoro@0 141 {
giuliomoro@0 142 schemaXSD = xmlhttp.response;
giuliomoro@0 143 var parse = new DOMParser();
giuliomoro@0 144 specification.schema = parse.parseFromString(xmlhttp.response,'text/xml');
giuliomoro@0 145 var r = new XMLHttpRequest();
giuliomoro@0 146 r.open('GET',url,true);
giuliomoro@0 147 r.onload = function() {
giuliomoro@0 148 loadProjectSpecCallback(r.response);
giuliomoro@0 149 };
giuliomoro@0 150 r.onerror = function() {
giuliomoro@0 151 document.getElementsByTagName('body')[0].innerHTML = null;
giuliomoro@0 152 var msg = document.createElement("h3");
giuliomoro@0 153 msg.textContent = "FATAL ERROR";
giuliomoro@0 154 var span = document.createElement("p");
giuliomoro@0 155 span.textContent = "There was an error when loading your XML file. Please check your path in the URL. After the path to this page, there should be '?url=path/to/your/file.xml'. Check the spelling of your filename as well. If you are still having issues, check the log of the python server or your webserver distribution for 404 codes for your file.";
giuliomoro@0 156 document.getElementsByTagName('body')[0].appendChild(msg);
giuliomoro@0 157 document.getElementsByTagName('body')[0].appendChild(span);
giuliomoro@0 158 }
giuliomoro@0 159 r.send();
giuliomoro@0 160 };
giuliomoro@0 161 xmlhttp.send();
giuliomoro@0 162 };
giuliomoro@0 163
giuliomoro@0 164 function loadProjectSpecCallback(response) {
giuliomoro@0 165 // Function called after asynchronous download of XML project specification
giuliomoro@0 166 //var decode = $.parseXML(response);
giuliomoro@0 167 //projectXML = $(decode);
giuliomoro@0 168
giuliomoro@0 169 // Check if XML is new or a resumption
giuliomoro@0 170 var parse = new DOMParser();
giuliomoro@0 171 var responseDocument = parse.parseFromString(response,'text/xml');
giuliomoro@0 172 var errorNode = responseDocument.getElementsByTagName('parsererror');
giuliomoro@0 173 if (errorNode.length >= 1)
giuliomoro@0 174 {
giuliomoro@0 175 var msg = document.createElement("h3");
giuliomoro@0 176 msg.textContent = "FATAL ERROR";
giuliomoro@0 177 var span = document.createElement("span");
giuliomoro@0 178 span.textContent = "The XML parser returned the following errors when decoding your XML file";
giuliomoro@0 179 document.getElementsByTagName('body')[0].innerHTML = null;
giuliomoro@0 180 document.getElementsByTagName('body')[0].appendChild(msg);
giuliomoro@0 181 document.getElementsByTagName('body')[0].appendChild(span);
giuliomoro@0 182 document.getElementsByTagName('body')[0].appendChild(errorNode[0]);
giuliomoro@0 183 return;
giuliomoro@0 184 }
giuliomoro@0 185 if (responseDocument.children[0].nodeName == "waet") {
giuliomoro@0 186 // document is a specification
giuliomoro@0 187
giuliomoro@0 188 // Perform XML schema validation
giuliomoro@0 189 var Module = {
giuliomoro@0 190 xml: response,
giuliomoro@0 191 schema: schemaXSD,
giuliomoro@0 192 arguments:["--noout", "--schema", 'test-schema.xsd','document.xml']
giuliomoro@0 193 };
giuliomoro@0 194 projectXML = responseDocument;
giuliomoro@0 195 var xmllint = validateXML(Module);
giuliomoro@0 196 console.log(xmllint);
giuliomoro@0 197 if(xmllint != 'document.xml validates\n')
giuliomoro@0 198 {
giuliomoro@0 199 document.getElementsByTagName('body')[0].innerHTML = null;
giuliomoro@0 200 var msg = document.createElement("h3");
giuliomoro@0 201 msg.textContent = "FATAL ERROR";
giuliomoro@0 202 var span = document.createElement("h4");
giuliomoro@0 203 span.textContent = "The XML validator returned the following errors when decoding your XML file";
giuliomoro@0 204 document.getElementsByTagName('body')[0].appendChild(msg);
giuliomoro@0 205 document.getElementsByTagName('body')[0].appendChild(span);
giuliomoro@0 206 xmllint = xmllint.split('\n');
giuliomoro@0 207 for (var i in xmllint)
giuliomoro@0 208 {
giuliomoro@0 209 document.getElementsByTagName('body')[0].appendChild(document.createElement('br'));
giuliomoro@0 210 var span = document.createElement("span");
giuliomoro@0 211 span.textContent = xmllint[i];
giuliomoro@0 212 document.getElementsByTagName('body')[0].appendChild(span);
giuliomoro@0 213 }
giuliomoro@0 214 return;
giuliomoro@0 215 }
giuliomoro@0 216 // Build the specification
giuliomoro@0 217 specification.decode(projectXML);
giuliomoro@0 218 // Generate the session-key
giuliomoro@0 219 storage.initialise();
giuliomoro@0 220
giuliomoro@0 221 } else if (responseDocument.children[0].nodeName == "waetresult") {
giuliomoro@0 222 // document is a result
giuliomoro@0 223 projectXML = document.implementation.createDocument(null,"waet");
giuliomoro@0 224 projectXML.children[0].appendChild(responseDocument.getElementsByTagName('waet')[0].getElementsByTagName("setup")[0].cloneNode(true));
giuliomoro@0 225 var child = responseDocument.children[0].children[0];
giuliomoro@0 226 while (child != null) {
giuliomoro@0 227 if (child.nodeName == "survey") {
giuliomoro@0 228 // One of the global survey elements
giuliomoro@0 229 if (child.getAttribute("state") == "complete") {
giuliomoro@0 230 // We need to remove this survey from <setup>
giuliomoro@0 231 var location = child.getAttribute("location");
giuliomoro@0 232 var globalSurveys = projectXML.getElementsByTagName("setup")[0].getElementsByTagName("survey")[0];
giuliomoro@0 233 while(globalSurveys != null) {
giuliomoro@0 234 if (location == "pre" || location == "before") {
giuliomoro@0 235 if (globalSurveys.getAttribute("location") == "pre" || globalSurveys.getAttribute("location") == "before") {
giuliomoro@0 236 projectXML.getElementsByTagName("setup")[0].removeChild(globalSurveys);
giuliomoro@0 237 break;
giuliomoro@0 238 }
giuliomoro@0 239 } else {
giuliomoro@0 240 if (globalSurveys.getAttribute("location") == "post" || globalSurveys.getAttribute("location") == "after") {
giuliomoro@0 241 projectXML.getElementsByTagName("setup")[0].removeChild(globalSurveys);
giuliomoro@0 242 break;
giuliomoro@0 243 }
giuliomoro@0 244 }
giuliomoro@0 245 globalSurveys = globalSurveys.nextElementSibling;
giuliomoro@0 246 }
giuliomoro@0 247 } else {
giuliomoro@0 248 // We need to complete this, so it must be regenerated by store
giuliomoro@0 249 var copy = child;
giuliomoro@0 250 child = child.previousElementSibling;
giuliomoro@0 251 responseDocument.children[0].removeChild(copy);
giuliomoro@0 252 }
giuliomoro@0 253 } else if (child.nodeName == "page") {
giuliomoro@0 254 if (child.getAttribute("state") == "empty") {
giuliomoro@0 255 // We need to complete this page
giuliomoro@0 256 projectXML.children[0].appendChild(responseDocument.getElementById(child.getAttribute("ref")).cloneNode(true));
giuliomoro@0 257 var copy = child;
giuliomoro@0 258 child = child.previousElementSibling;
giuliomoro@0 259 responseDocument.children[0].removeChild(copy);
giuliomoro@0 260 }
giuliomoro@0 261 }
giuliomoro@0 262 child = child.nextElementSibling;
giuliomoro@0 263 }
giuliomoro@0 264 // Build the specification
giuliomoro@0 265 specification.decode(projectXML);
giuliomoro@0 266 // Use the original
giuliomoro@0 267 storage.initialise(responseDocument);
giuliomoro@0 268 }
giuliomoro@0 269 /// CHECK FOR SAMPLE RATE COMPATIBILITY
giuliomoro@0 270 if (specification.sampleRate != undefined) {
giuliomoro@0 271 if (Number(specification.sampleRate) != audioContext.sampleRate) {
giuliomoro@0 272 var errStr = 'Sample rates do not match! Requested '+Number(specification.sampleRate)+', got '+audioContext.sampleRate+'. Please set the sample rate to match before completing this test.';
giuliomoro@0 273 alert(errStr);
giuliomoro@0 274 return;
giuliomoro@0 275 }
giuliomoro@0 276 }
giuliomoro@0 277
giuliomoro@0 278 // Detect the interface to use and load the relevant javascripts.
giuliomoro@0 279 var interfaceJS = document.createElement('script');
giuliomoro@0 280 interfaceJS.setAttribute("type","text/javascript");
giuliomoro@0 281 switch(specification.interface)
giuliomoro@0 282 {
giuliomoro@0 283 case "APE":
giuliomoro@0 284 interfaceJS.setAttribute("src","interfaces/ape.js");
giuliomoro@0 285
giuliomoro@0 286 // APE comes with a css file
giuliomoro@0 287 var css = document.createElement('link');
giuliomoro@0 288 css.rel = 'stylesheet';
giuliomoro@0 289 css.type = 'text/css';
giuliomoro@0 290 css.href = 'interfaces/ape.css';
giuliomoro@0 291
giuliomoro@0 292 document.getElementsByTagName("head")[0].appendChild(css);
giuliomoro@0 293 break;
giuliomoro@0 294
giuliomoro@0 295 case "MUSHRA":
giuliomoro@0 296 interfaceJS.setAttribute("src","interfaces/mushra.js");
giuliomoro@0 297
giuliomoro@0 298 // MUSHRA comes with a css file
giuliomoro@0 299 var css = document.createElement('link');
giuliomoro@0 300 css.rel = 'stylesheet';
giuliomoro@0 301 css.type = 'text/css';
giuliomoro@0 302 css.href = 'interfaces/mushra.css';
giuliomoro@0 303
giuliomoro@0 304 document.getElementsByTagName("head")[0].appendChild(css);
giuliomoro@0 305 break;
giuliomoro@0 306
giuliomoro@0 307 case "AB":
giuliomoro@6 308 interfaceJS.setAttribute("src","interfaces/AB.js?"+Math.random());
giuliomoro@0 309
giuliomoro@0 310 // AB comes with a css file
giuliomoro@0 311 var css = document.createElement('link');
giuliomoro@0 312 css.rel = 'stylesheet';
giuliomoro@0 313 css.type = 'text/css';
giuliomoro@1 314 css.href = 'interfaces/AB.css?'+Math.random();
giuliomoro@0 315
giuliomoro@0 316 document.getElementsByTagName("head")[0].appendChild(css);
giuliomoro@0 317 break;
giuliomoro@0 318 case "Bipolar":
giuliomoro@0 319 case "ACR":
giuliomoro@0 320 case "DCR":
giuliomoro@0 321 case "CCR":
giuliomoro@0 322 case "ABC":
giuliomoro@0 323 // Above enumerate to horizontal sliders
giuliomoro@0 324 interfaceJS.setAttribute("src","interfaces/horizontal-sliders.js");
giuliomoro@0 325
giuliomoro@0 326 // horizontal-sliders comes with a css file
giuliomoro@0 327 var css = document.createElement('link');
giuliomoro@0 328 css.rel = 'stylesheet';
giuliomoro@0 329 css.type = 'text/css';
giuliomoro@0 330 css.href = 'interfaces/horizontal-sliders.css';
giuliomoro@0 331
giuliomoro@0 332 document.getElementsByTagName("head")[0].appendChild(css);
giuliomoro@0 333 break;
giuliomoro@0 334 case "discrete":
giuliomoro@0 335 case "likert":
giuliomoro@0 336 // Above enumerate to horizontal discrete radios
giuliomoro@0 337 interfaceJS.setAttribute("src","interfaces/discrete.js");
giuliomoro@0 338
giuliomoro@0 339 // horizontal-sliders comes with a css file
giuliomoro@0 340 var css = document.createElement('link');
giuliomoro@0 341 css.rel = 'stylesheet';
giuliomoro@0 342 css.type = 'text/css';
giuliomoro@0 343 css.href = 'interfaces/discrete.css';
giuliomoro@0 344
giuliomoro@0 345 document.getElementsByTagName("head")[0].appendChild(css);
giuliomoro@0 346 break;
giuliomoro@0 347 }
giuliomoro@0 348 document.getElementsByTagName("head")[0].appendChild(interfaceJS);
giuliomoro@0 349
giuliomoro@0 350 // Create the audio engine object
giuliomoro@0 351 audioEngineContext = new AudioEngine(specification);
giuliomoro@0 352 }
giuliomoro@0 353
giuliomoro@0 354 function createProjectSave(destURL) {
giuliomoro@0 355 // Save the data from interface into XML and send to destURL
giuliomoro@0 356 // If destURL is null then download XML in client
giuliomoro@0 357 // Now time to render file locally
giuliomoro@0 358 var xmlDoc = interfaceXMLSave();
giuliomoro@0 359 var parent = document.createElement("div");
giuliomoro@0 360 parent.appendChild(xmlDoc);
giuliomoro@0 361 var file = [parent.innerHTML];
giuliomoro@0 362 if (destURL == "local") {
giuliomoro@0 363 var bb = new Blob(file,{type : 'application/xml'});
giuliomoro@0 364 var dnlk = window.URL.createObjectURL(bb);
giuliomoro@0 365 var a = document.createElement("a");
giuliomoro@0 366 a.hidden = '';
giuliomoro@0 367 a.href = dnlk;
giuliomoro@0 368 a.download = "save.xml";
giuliomoro@0 369 a.textContent = "Save File";
giuliomoro@0 370
giuliomoro@0 371 popup.showPopup();
giuliomoro@0 372 popup.popupContent.innerHTML = "<span>Please save the file below to give to your test supervisor</span><br>";
giuliomoro@0 373 popup.popupContent.appendChild(a);
giuliomoro@0 374 } else {
giuliomoro@0 375 destUrlFull = destURL+"?key="+storage.SessionKey.key;
giuliomoro@0 376 var saveFilenamePrefix;
giuliomoro@0 377 // parse the querystring of destUrl, get the "id" (if any) and append it to destUrl
giuliomoro@0 378 if(typeof(returnUrl) !== "undefined"){
giuliomoro@0 379 var qs = returnUrl.split("?");
giuliomoro@0 380 if(qs.length == 2){
giuliomoro@0 381 qs = qs[1];
giuliomoro@0 382 qs = qs.split("&");
giuliomoro@0 383 for(var n = 0; n < qs.length; n++){
giuliomoro@0 384 var pair = qs[n].split("=");
giuliomoro@0 385 if (pair[0] == "id") {
giuliomoro@0 386 saveFilenamePrefix = pair[1];
giuliomoro@0 387 }
giuliomoro@0 388 }
giuliomoro@0 389 }
giuliomoro@0 390 }
giuliomoro@0 391 if(typeof(saveFilenamePrefix) !== "undefined"){
giuliomoro@0 392 destUrlFull+="&saveFilenamePrefix="+saveFilenamePrefix;
giuliomoro@0 393 }
giuliomoro@0 394 var xmlhttp = new XMLHttpRequest;
giuliomoro@0 395 xmlhttp.open("POST",destUrlFull,true);
giuliomoro@0 396 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
giuliomoro@0 397 xmlhttp.onerror = function(){
giuliomoro@0 398 console.log('Error saving file to server! Presenting download locally');
giuliomoro@0 399 createProjectSave("local");
giuliomoro@0 400 };
giuliomoro@0 401 xmlhttp.onload = function() {
giuliomoro@0 402 console.log(xmlhttp);
giuliomoro@0 403 if (this.status >= 300) {
giuliomoro@0 404 console.log("WARNING - Could not update at this time");
giuliomoro@0 405 createProjectSave("local");
giuliomoro@0 406 } else {
giuliomoro@0 407 var parser = new DOMParser();
giuliomoro@0 408 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
giuliomoro@0 409 var response = xmlDoc.getElementsByTagName('response')[0];
giuliomoro@0 410 window.onbeforeunload=null;
giuliomoro@0 411 if (response.getAttribute("state") == "OK") {
giuliomoro@0 412 var file = response.getElementsByTagName("file")[0];
giuliomoro@0 413 if(typeof(returnUrl) !== "undefined"){
giuliomoro@0 414 window.location = returnUrl;
giuliomoro@0 415 }
giuliomoro@0 416 console.log("Save: OK, written "+file.getAttribute("bytes")+"B");
giuliomoro@0 417 popup.popupContent.textContent = "Thank you. Your session has been saved.";
giuliomoro@0 418 } else {
giuliomoro@0 419 var message = response.getElementsByTagName("message");
giuliomoro@0 420 console.log("Save: Error! "+message.textContent);
giuliomoro@0 421 createProjectSave("local");
giuliomoro@0 422 }
giuliomoro@0 423 }
giuliomoro@0 424 };
giuliomoro@0 425 xmlhttp.send(file);
giuliomoro@0 426 popup.showPopup();
giuliomoro@0 427 popup.popupContent.innerHTML = null;
giuliomoro@0 428 popup.popupContent.textContent = "Submitting. Please Wait";
giuliomoro@0 429 popup.hideNextButton();
giuliomoro@0 430 popup.hidePreviousButton();
giuliomoro@0 431 }
giuliomoro@0 432 }
giuliomoro@0 433
giuliomoro@0 434 function errorSessionDump(msg){
giuliomoro@0 435 // Create the partial interface XML save
giuliomoro@0 436 // Include error node with message on why the dump occured
giuliomoro@0 437 popup.showPopup();
giuliomoro@0 438 popup.popupContent.innerHTML = null;
giuliomoro@0 439 var err = document.createElement('error');
giuliomoro@0 440 var parent = document.createElement("div");
giuliomoro@0 441 if (typeof msg === "object")
giuliomoro@0 442 {
giuliomoro@0 443 err.appendChild(msg);
giuliomoro@0 444 popup.popupContent.appendChild(msg);
giuliomoro@0 445
giuliomoro@0 446 } else {
giuliomoro@0 447 err.textContent = msg;
giuliomoro@0 448 popup.popupContent.innerHTML = "ERROR : "+msg;
giuliomoro@0 449 }
giuliomoro@0 450 var xmlDoc = interfaceXMLSave();
giuliomoro@0 451 xmlDoc.appendChild(err);
giuliomoro@0 452 parent.appendChild(xmlDoc);
giuliomoro@0 453 var file = [parent.innerHTML];
giuliomoro@0 454 var bb = new Blob(file,{type : 'application/xml'});
giuliomoro@0 455 var dnlk = window.URL.createObjectURL(bb);
giuliomoro@0 456 var a = document.createElement("a");
giuliomoro@0 457 a.hidden = '';
giuliomoro@0 458 a.href = dnlk;
giuliomoro@0 459 a.download = "save.xml";
giuliomoro@0 460 a.textContent = "Save File";
giuliomoro@0 461
giuliomoro@0 462
giuliomoro@0 463
giuliomoro@0 464 popup.popupContent.appendChild(a);
giuliomoro@0 465 }
giuliomoro@0 466
giuliomoro@0 467 // Only other global function which must be defined in the interface class. Determines how to create the XML document.
giuliomoro@0 468 function interfaceXMLSave(){
giuliomoro@0 469 // Create the XML string to be exported with results
giuliomoro@0 470 return storage.finish();
giuliomoro@0 471 }
giuliomoro@0 472
giuliomoro@0 473 function linearToDecibel(gain)
giuliomoro@0 474 {
giuliomoro@0 475 return 20.0*Math.log10(gain);
giuliomoro@0 476 }
giuliomoro@0 477
giuliomoro@0 478 function decibelToLinear(gain)
giuliomoro@0 479 {
giuliomoro@0 480 return Math.pow(10,gain/20.0);
giuliomoro@0 481 }
giuliomoro@0 482
giuliomoro@0 483 function randomString(length) {
giuliomoro@0 484 return Math.round((Math.pow(36, length + 1) - Math.random() * Math.pow(36, length))).toString(36).slice(1);
giuliomoro@0 485 }
giuliomoro@0 486
giuliomoro@0 487 function interfacePopup() {
giuliomoro@0 488 // Creates an object to manage the popup
giuliomoro@0 489 this.popup = null;
giuliomoro@0 490 this.popupContent = null;
giuliomoro@0 491 this.popupTitle = null;
giuliomoro@0 492 this.popupResponse = null;
giuliomoro@0 493 this.buttonProceed = null;
giuliomoro@0 494 this.buttonPrevious = null;
giuliomoro@0 495 this.popupOptions = null;
giuliomoro@0 496 this.currentIndex = null;
giuliomoro@0 497 this.node = null;
giuliomoro@0 498 this.store = null;
giuliomoro@0 499 $(window).keypress(function(e){
giuliomoro@0 500 if (e.keyCode == 13 && popup.popup.style.visibility == 'visible')
giuliomoro@0 501 {
giuliomoro@0 502 console.log(e);
giuliomoro@0 503 popup.buttonProceed.onclick();
giuliomoro@0 504 e.preventDefault();
giuliomoro@0 505 }
giuliomoro@0 506 });
giuliomoro@0 507
giuliomoro@0 508 this.createPopup = function(){
giuliomoro@0 509 // Create popup window interface
giuliomoro@0 510 var insertPoint = document.getElementById("topLevelBody");
giuliomoro@0 511
giuliomoro@0 512 this.popup = document.getElementById('popupHolder');
giuliomoro@0 513 this.popup.style.left = (window.innerWidth/2)-250 + 'px';
giuliomoro@0 514 this.popup.style.top = (window.innerHeight/2)-125 + 'px';
giuliomoro@0 515
giuliomoro@0 516 this.popupContent = document.getElementById('popupContent');
giuliomoro@0 517
giuliomoro@0 518 this.popupTitle = document.getElementById('popupTitle');
giuliomoro@0 519
giuliomoro@0 520 this.popupResponse = document.getElementById('popupResponse');
giuliomoro@0 521
giuliomoro@0 522 this.buttonProceed = document.getElementById('popup-proceed');
giuliomoro@0 523 this.buttonProceed.onclick = function(){popup.proceedClicked();};
giuliomoro@0 524
giuliomoro@0 525 this.buttonPrevious = document.getElementById('popup-previous');
giuliomoro@0 526 this.buttonPrevious.onclick = function(){popup.previousClick();};
giuliomoro@0 527
giuliomoro@0 528 this.hidePopup();
giuliomoro@0 529
giuliomoro@0 530 this.popup.style.zIndex = -1;
giuliomoro@0 531 this.popup.style.visibility = 'hidden';
giuliomoro@0 532 };
giuliomoro@0 533
giuliomoro@0 534 this.showPopup = function(){
giuliomoro@0 535 if (this.popup == null) {
giuliomoro@0 536 this.createPopup();
giuliomoro@0 537 }
giuliomoro@0 538 this.popup.style.zIndex = 3;
giuliomoro@0 539 this.popup.style.visibility = 'visible';
giuliomoro@0 540 var blank = document.getElementsByClassName('testHalt')[0];
giuliomoro@0 541 blank.style.zIndex = 2;
giuliomoro@0 542 blank.style.visibility = 'visible';
giuliomoro@0 543 };
giuliomoro@0 544
giuliomoro@0 545 this.hidePopup = function(){
giuliomoro@0 546 this.popup.style.zIndex = -1;
giuliomoro@0 547 this.popup.style.visibility = 'hidden';
giuliomoro@0 548 var blank = document.getElementsByClassName('testHalt')[0];
giuliomoro@0 549 blank.style.zIndex = -2;
giuliomoro@0 550 blank.style.visibility = 'hidden';
giuliomoro@0 551 this.buttonPrevious.style.visibility = 'inherit';
giuliomoro@0 552 };
giuliomoro@0 553
giuliomoro@0 554 this.postNode = function() {
giuliomoro@0 555 // This will take the node from the popupOptions and display it
giuliomoro@0 556 var node = this.popupOptions[this.currentIndex];
giuliomoro@0 557 this.popupResponse.innerHTML = null;
giuliomoro@0 558 this.popupTitle.textContent = node.specification.statement;
giuliomoro@0 559 if (node.specification.type == 'question') {
giuliomoro@0 560 var textArea = document.createElement('textarea');
giuliomoro@0 561 switch (node.specification.boxsize) {
giuliomoro@0 562 case 'small':
giuliomoro@0 563 textArea.cols = "20";
giuliomoro@0 564 textArea.rows = "1";
giuliomoro@0 565 break;
giuliomoro@0 566 case 'normal':
giuliomoro@0 567 textArea.cols = "30";
giuliomoro@0 568 textArea.rows = "2";
giuliomoro@0 569 break;
giuliomoro@0 570 case 'large':
giuliomoro@0 571 textArea.cols = "40";
giuliomoro@0 572 textArea.rows = "5";
giuliomoro@0 573 break;
giuliomoro@0 574 case 'huge':
giuliomoro@0 575 textArea.cols = "50";
giuliomoro@0 576 textArea.rows = "10";
giuliomoro@0 577 break;
giuliomoro@0 578 }
giuliomoro@0 579 if (node.response == undefined) {
giuliomoro@0 580 node.response = "";
giuliomoro@0 581 } else {
giuliomoro@0 582 textArea.value = node.response;
giuliomoro@0 583 }
giuliomoro@0 584 this.popupResponse.appendChild(textArea);
giuliomoro@0 585 textArea.focus();
giuliomoro@0 586 this.popupResponse.style.textAlign="center";
giuliomoro@0 587 this.popupResponse.style.left="0%";
giuliomoro@0 588 } else if (node.specification.type == 'checkbox') {
giuliomoro@0 589 if (node.response == undefined) {
giuliomoro@0 590 node.response = Array(node.specification.options.length);
giuliomoro@0 591 }
giuliomoro@0 592 var index = 0;
giuliomoro@0 593 var max_w = 0;
giuliomoro@0 594 for (var option of node.specification.options) {
giuliomoro@0 595 var input = document.createElement('input');
giuliomoro@0 596 input.id = option.name;
giuliomoro@0 597 input.type = 'checkbox';
giuliomoro@0 598 var span = document.createElement('span');
giuliomoro@0 599 span.textContent = option.text;
giuliomoro@0 600 var hold = document.createElement('div');
giuliomoro@0 601 hold.setAttribute('name','option');
giuliomoro@0 602 hold.style.padding = '4px';
giuliomoro@0 603 hold.appendChild(input);
giuliomoro@0 604 hold.appendChild(span);
giuliomoro@0 605 this.popupResponse.appendChild(hold);
giuliomoro@0 606 if (node.response[index] != undefined){
giuliomoro@0 607 if (node.response[index].checked == true) {
giuliomoro@0 608 input.checked = "true";
giuliomoro@0 609 }
giuliomoro@0 610 }
giuliomoro@0 611 var w = $(span).width();
giuliomoro@0 612 if (w > max_w)
giuliomoro@0 613 max_w = w;
giuliomoro@0 614 index++;
giuliomoro@0 615 }
giuliomoro@0 616 max_w += 12;
giuliomoro@0 617 this.popupResponse.style.textAlign="";
giuliomoro@0 618 var leftP = ((max_w/500)/2)*100;
giuliomoro@0 619 this.popupResponse.style.left=leftP+"%";
giuliomoro@0 620 } else if (node.specification.type == 'radio') {
giuliomoro@0 621 if (node.response == undefined) {
giuliomoro@0 622 node.response = {name: "", text: ""};
giuliomoro@0 623 }
giuliomoro@0 624 var index = 0;
giuliomoro@0 625 var max_w = 0;
giuliomoro@0 626 for (var option of node.specification.options) {
giuliomoro@0 627 var input = document.createElement('input');
giuliomoro@0 628 input.id = option.name;
giuliomoro@0 629 input.type = 'radio';
giuliomoro@0 630 input.name = node.specification.id;
giuliomoro@0 631 var span = document.createElement('span');
giuliomoro@0 632 span.textContent = option.text;
giuliomoro@0 633 var hold = document.createElement('div');
giuliomoro@0 634 hold.setAttribute('name','option');
giuliomoro@0 635 hold.style.padding = '4px';
giuliomoro@0 636 hold.appendChild(input);
giuliomoro@0 637 hold.appendChild(span);
giuliomoro@0 638 this.popupResponse.appendChild(hold);
giuliomoro@0 639 if (input.id == node.response.name) {
giuliomoro@0 640 input.checked = "true";
giuliomoro@0 641 }
giuliomoro@0 642 var w = $(span).width();
giuliomoro@0 643 if (w > max_w)
giuliomoro@0 644 max_w = w;
giuliomoro@0 645 }
giuliomoro@0 646 max_w += 12;
giuliomoro@0 647 this.popupResponse.style.textAlign="";
giuliomoro@0 648 var leftP = ((max_w/500)/2)*100;
giuliomoro@0 649 this.popupResponse.style.left=leftP+"%";
giuliomoro@0 650 } else if (node.specification.type == 'number') {
giuliomoro@0 651 var input = document.createElement('input');
giuliomoro@0 652 input.type = 'textarea';
giuliomoro@0 653 if (node.min != null) {input.min = node.specification.min;}
giuliomoro@0 654 if (node.max != null) {input.max = node.specification.max;}
giuliomoro@0 655 if (node.step != null) {input.step = node.specification.step;}
giuliomoro@0 656 if (node.response != undefined) {
giuliomoro@0 657 input.value = node.response;
giuliomoro@0 658 }
giuliomoro@0 659 this.popupResponse.appendChild(input);
giuliomoro@0 660 this.popupResponse.style.textAlign="center";
giuliomoro@0 661 this.popupResponse.style.left="0%";
giuliomoro@0 662 }
giuliomoro@0 663 if(this.currentIndex+1 == this.popupOptions.length) {
giuliomoro@0 664 if (this.node.location == "pre") {
giuliomoro@0 665 this.buttonProceed.textContent = 'Start';
giuliomoro@0 666 } else {
giuliomoro@0 667 this.buttonProceed.textContent = 'Submit';
giuliomoro@0 668 }
giuliomoro@0 669 } else {
giuliomoro@0 670 this.buttonProceed.textContent = 'Next';
giuliomoro@0 671 }
giuliomoro@0 672 if(this.currentIndex > 0)
giuliomoro@0 673 this.buttonPrevious.style.visibility = 'visible';
giuliomoro@0 674 else
giuliomoro@0 675 this.buttonPrevious.style.visibility = 'hidden';
giuliomoro@0 676 };
giuliomoro@0 677
giuliomoro@0 678 this.initState = function(node,store) {
giuliomoro@0 679 //Call this with your preTest and postTest nodes when needed to
giuliomoro@0 680 // initialise the popup procedure.
giuliomoro@0 681 if (node.options.length > 0) {
giuliomoro@0 682 this.popupOptions = [];
giuliomoro@0 683 this.node = node;
giuliomoro@0 684 this.store = store;
giuliomoro@0 685 for (var opt of node.options)
giuliomoro@0 686 {
giuliomoro@0 687 this.popupOptions.push({
giuliomoro@0 688 specification: opt,
giuliomoro@0 689 response: null
giuliomoro@0 690 });
giuliomoro@0 691 }
giuliomoro@0 692 this.currentIndex = 0;
giuliomoro@0 693 this.showPopup();
giuliomoro@0 694 this.postNode();
giuliomoro@0 695 } else {
giuliomoro@0 696 advanceState();
giuliomoro@0 697 }
giuliomoro@0 698 };
giuliomoro@0 699
giuliomoro@0 700 this.proceedClicked = function() {
giuliomoro@0 701 // Each time the popup button is clicked!
giuliomoro@0 702 var node = this.popupOptions[this.currentIndex];
giuliomoro@0 703 if (node.specification.type == 'question') {
giuliomoro@0 704 // Must extract the question data
giuliomoro@0 705 var textArea = $(popup.popupContent).find('textarea')[0];
giuliomoro@0 706 if (node.specification.mandatory == true && textArea.value.length == 0) {
giuliomoro@0 707 alert('This question is mandatory');
giuliomoro@0 708 return;
giuliomoro@0 709 } else {
giuliomoro@0 710 // Save the text content
giuliomoro@0 711 console.log("Question: "+ node.specification.statement);
giuliomoro@0 712 console.log("Question Response: "+ textArea.value);
giuliomoro@0 713 node.response = textArea.value;
giuliomoro@0 714 }
giuliomoro@0 715 } else if (node.specification.type == 'checkbox') {
giuliomoro@0 716 // Must extract checkbox data
giuliomoro@0 717 console.log("Checkbox: "+ node.specification.statement);
giuliomoro@0 718 var inputs = this.popupResponse.getElementsByTagName('input');
giuliomoro@0 719 node.response = [];
giuliomoro@0 720 for (var i=0; i<node.specification.options.length; i++) {
giuliomoro@0 721 node.response.push({
giuliomoro@0 722 name: node.specification.options[i].name,
giuliomoro@0 723 text: node.specification.options[i].text,
giuliomoro@0 724 checked: inputs[i].checked
giuliomoro@0 725 });
giuliomoro@0 726 console.log(node.specification.options[i].name+": "+ inputs[i].checked);
giuliomoro@0 727 }
giuliomoro@0 728 } else if (node.specification.type == "radio") {
giuliomoro@0 729 var optHold = this.popupResponse;
giuliomoro@0 730 console.log("Radio: "+ node.specification.statement);
giuliomoro@0 731 node.response = null;
giuliomoro@0 732 var i=0;
giuliomoro@0 733 var inputs = optHold.getElementsByTagName('input');
giuliomoro@0 734 while(node.response == null) {
giuliomoro@0 735 if (i == inputs.length)
giuliomoro@0 736 {
giuliomoro@0 737 if (node.specification.mandatory == true)
giuliomoro@0 738 {
giuliomoro@0 739 alert("This radio is mandatory");
giuliomoro@0 740 } else {
giuliomoro@0 741 node.response = -1;
giuliomoro@0 742 }
giuliomoro@0 743 return;
giuliomoro@0 744 }
giuliomoro@0 745 if (inputs[i].checked == true) {
giuliomoro@0 746 node.response = node.specification.options[i];
giuliomoro@0 747 console.log("Selected: "+ node.specification.options[i].name);
giuliomoro@0 748 }
giuliomoro@0 749 i++;
giuliomoro@0 750 }
giuliomoro@0 751 } else if (node.specification.type == "number") {
giuliomoro@0 752 var input = this.popupContent.getElementsByTagName('input')[0];
giuliomoro@0 753 if (node.mandatory == true && input.value.length == 0) {
giuliomoro@0 754 alert('This question is mandatory. Please enter a number');
giuliomoro@0 755 return;
giuliomoro@0 756 }
giuliomoro@0 757 var enteredNumber = Number(input.value);
giuliomoro@0 758 if (isNaN(enteredNumber)) {
giuliomoro@0 759 alert('Please enter a valid number');
giuliomoro@0 760 return;
giuliomoro@0 761 }
giuliomoro@0 762 if (enteredNumber < node.min && node.min != null) {
giuliomoro@0 763 alert('Number is below the minimum value of '+node.min);
giuliomoro@0 764 return;
giuliomoro@0 765 }
giuliomoro@0 766 if (enteredNumber > node.max && node.max != null) {
giuliomoro@0 767 alert('Number is above the maximum value of '+node.max);
giuliomoro@0 768 return;
giuliomoro@0 769 }
giuliomoro@0 770 node.response = input.value;
giuliomoro@0 771 }
giuliomoro@0 772 this.currentIndex++;
giuliomoro@0 773 if (this.currentIndex < this.popupOptions.length) {
giuliomoro@0 774 this.postNode();
giuliomoro@0 775 } else {
giuliomoro@0 776 // Reached the end of the popupOptions
giuliomoro@0 777 this.hidePopup();
giuliomoro@0 778 for (var node of this.popupOptions)
giuliomoro@0 779 {
giuliomoro@0 780 this.store.postResult(node);
giuliomoro@0 781 }
giuliomoro@0 782 this.store.complete();
giuliomoro@0 783 advanceState();
giuliomoro@0 784 }
giuliomoro@0 785 };
giuliomoro@0 786
giuliomoro@0 787 this.previousClick = function() {
giuliomoro@0 788 // Triggered when the 'Back' button is clicked in the survey
giuliomoro@0 789 if (this.currentIndex > 0) {
giuliomoro@0 790 this.currentIndex--;
giuliomoro@0 791 this.postNode();
giuliomoro@0 792 }
giuliomoro@0 793 };
giuliomoro@0 794
giuliomoro@0 795 this.resize = function(event)
giuliomoro@0 796 {
giuliomoro@0 797 // Called on window resize;
giuliomoro@0 798 if (this.popup != null) {
giuliomoro@0 799 this.popup.style.left = (window.innerWidth/2)-250 + 'px';
giuliomoro@0 800 this.popup.style.top = (window.innerHeight/2)-125 + 'px';
giuliomoro@0 801 var blank = document.getElementsByClassName('testHalt')[0];
giuliomoro@0 802 blank.style.width = window.innerWidth;
giuliomoro@0 803 blank.style.height = window.innerHeight;
giuliomoro@0 804 }
giuliomoro@0 805 };
giuliomoro@0 806 this.hideNextButton = function() {
giuliomoro@0 807 this.buttonProceed.style.visibility = "hidden";
giuliomoro@0 808 }
giuliomoro@0 809 this.hidePreviousButton = function() {
giuliomoro@0 810 this.buttonPrevious.style.visibility = "hidden";
giuliomoro@0 811 }
giuliomoro@0 812 this.showNextButton = function() {
giuliomoro@0 813 this.buttonProceed.style.visibility = "visible";
giuliomoro@0 814 }
giuliomoro@0 815 this.showPreviousButton = function() {
giuliomoro@0 816 this.buttonPrevious.style.visibility = "visible";
giuliomoro@0 817 }
giuliomoro@0 818 }
giuliomoro@0 819
giuliomoro@0 820 function advanceState()
giuliomoro@0 821 {
giuliomoro@0 822 // Just for complete clarity
giuliomoro@0 823 testState.advanceState();
giuliomoro@0 824 }
giuliomoro@0 825
giuliomoro@0 826 function stateMachine()
giuliomoro@0 827 {
giuliomoro@0 828 // Object prototype for tracking and managing the test state
giuliomoro@0 829 this.stateMap = [];
giuliomoro@0 830 this.preTestSurvey = null;
giuliomoro@0 831 this.postTestSurvey = null;
giuliomoro@0 832 this.stateIndex = null;
giuliomoro@0 833 this.currentStateMap = null;
giuliomoro@0 834 this.currentStatePosition = null;
giuliomoro@0 835 this.currentStore = null;
giuliomoro@0 836 this.initialise = function(){
giuliomoro@0 837
giuliomoro@0 838 // Get the data from Specification
giuliomoro@0 839 var pageHolder = [];
giuliomoro@0 840 for (var page of specification.pages)
giuliomoro@0 841 {
giuliomoro@0 842 var repeat = page.repeatCount;
giuliomoro@0 843 while(repeat >= 0)
giuliomoro@0 844 {
giuliomoro@0 845 pageHolder.push(page);
giuliomoro@0 846 repeat--;
giuliomoro@0 847 }
giuliomoro@0 848 }
giuliomoro@0 849 if (specification.randomiseOrder)
giuliomoro@0 850 {
giuliomoro@0 851 pageHolder = randomiseOrder(pageHolder);
giuliomoro@0 852 }
giuliomoro@0 853 for (var i=0; i<pageHolder.length; i++)
giuliomoro@0 854 {
giuliomoro@0 855 pageHolder[i].presentedId = i;
giuliomoro@0 856 }
giuliomoro@0 857 for (var i=0; i<specification.pages.length; i++)
giuliomoro@0 858 {
giuliomoro@0 859 if (specification.testPages <= i && specification.testPages != 0) {break;}
giuliomoro@0 860 this.stateMap.push(pageHolder[i]);
giuliomoro@0 861 storage.createTestPageStore(pageHolder[i]);
giuliomoro@0 862 for (var element of pageHolder[i].audioElements) {
giuliomoro@0 863 var URL = pageHolder[i].hostURL + element.url;
giuliomoro@0 864 var buffer = null;
giuliomoro@0 865 for (var buffObj of audioEngineContext.buffers) {
giuliomoro@0 866 if (URL == buffObj.url) {
giuliomoro@0 867 buffer = buffObj;
giuliomoro@0 868 break;
giuliomoro@0 869 }
giuliomoro@0 870 }
giuliomoro@0 871 if (buffer == null) {
giuliomoro@0 872 buffer = new audioEngineContext.bufferObj();
giuliomoro@0 873 buffer.getMedia(URL);
giuliomoro@0 874 audioEngineContext.buffers.push(buffer);
giuliomoro@0 875 }
giuliomoro@0 876 }
giuliomoro@0 877 }
giuliomoro@0 878
giuliomoro@0 879 if (specification.preTest != null) {this.preTestSurvey = specification.preTest;}
giuliomoro@0 880 if (specification.postTest != null) {this.postTestSurvey = specification.postTest;}
giuliomoro@0 881
giuliomoro@0 882 if (this.stateMap.length > 0) {
giuliomoro@0 883 if(this.stateIndex != null) {
giuliomoro@0 884 console.log('NOTE - State already initialise');
giuliomoro@0 885 }
giuliomoro@0 886 this.stateIndex = -1;
giuliomoro@0 887 } else {
giuliomoro@0 888 console.log('FATAL - StateMap not correctly constructed. EMPTY_STATE_MAP');
giuliomoro@0 889 }
giuliomoro@0 890 };
giuliomoro@0 891 this.advanceState = function(){
giuliomoro@0 892 if (this.stateIndex == null) {
giuliomoro@0 893 this.initialise();
giuliomoro@0 894 }
giuliomoro@1 895 $('#box-holders').fadeTo(200, 0.1, function(){
giuliomoro@1 896 $('#box-holders').fadeTo(200, 1);
giuliomoro@1 897 });
giuliomoro@1 898
giuliomoro@1 899
giuliomoro@1 900 storage.update();
giuliomoro@0 901 if (this.stateIndex == -1) {
giuliomoro@0 902 this.stateIndex++;
giuliomoro@0 903 console.log('Starting test...');
giuliomoro@0 904 if (this.preTestSurvey != null)
giuliomoro@0 905 {
giuliomoro@0 906 popup.initState(this.preTestSurvey,storage.globalPreTest);
giuliomoro@0 907 } else {
giuliomoro@0 908 this.advanceState();
giuliomoro@0 909 }
giuliomoro@0 910 } else if (this.stateIndex == this.stateMap.length)
giuliomoro@0 911 {
giuliomoro@0 912 // All test pages complete, post test
giuliomoro@0 913 console.log('Ending test ...');
giuliomoro@0 914 this.stateIndex++;
giuliomoro@0 915 if (this.postTestSurvey == null) {
giuliomoro@0 916 this.advanceState();
giuliomoro@0 917 } else {
giuliomoro@0 918 popup.initState(this.postTestSurvey,storage.globalPostTest);
giuliomoro@0 919 }
giuliomoro@0 920 } else if (this.stateIndex > this.stateMap.length)
giuliomoro@0 921 {
giuliomoro@0 922 createProjectSave(specification.projectReturn);
giuliomoro@0 923 }
giuliomoro@0 924 else
giuliomoro@0 925 {
giuliomoro@0 926 if (this.currentStateMap == null)
giuliomoro@0 927 {
giuliomoro@0 928 this.currentStateMap = this.stateMap[this.stateIndex];
giuliomoro@0 929 if (this.currentStateMap.randomiseOrder)
giuliomoro@0 930 {
giuliomoro@0 931 this.currentStateMap.audioElements = randomiseOrder(this.currentStateMap.audioElements);
giuliomoro@0 932 }
giuliomoro@0 933 this.currentStore = storage.testPages[this.stateIndex];
giuliomoro@0 934 if (this.currentStateMap.preTest != null)
giuliomoro@0 935 {
giuliomoro@0 936 this.currentStatePosition = 'pre';
giuliomoro@0 937 popup.initState(this.currentStateMap.preTest,storage.testPages[this.stateIndex].preTest);
giuliomoro@0 938 } else {
giuliomoro@0 939 this.currentStatePosition = 'test';
giuliomoro@0 940 }
giuliomoro@0 941 interfaceContext.newPage(this.currentStateMap,storage.testPages[this.stateIndex]);
giuliomoro@0 942 return;
giuliomoro@0 943 }
giuliomoro@0 944 switch(this.currentStatePosition)
giuliomoro@0 945 {
giuliomoro@0 946 case 'pre':
giuliomoro@0 947 this.currentStatePosition = 'test';
giuliomoro@0 948 break;
giuliomoro@0 949 case 'test':
giuliomoro@0 950 this.currentStatePosition = 'post';
giuliomoro@0 951 // Save the data
giuliomoro@0 952 this.testPageCompleted();
giuliomoro@0 953 if (this.currentStateMap.postTest == null)
giuliomoro@0 954 {
giuliomoro@0 955 this.advanceState();
giuliomoro@0 956 return;
giuliomoro@0 957 } else {
giuliomoro@0 958 popup.initState(this.currentStateMap.postTest,storage.testPages[this.stateIndex].postTest);
giuliomoro@0 959 }
giuliomoro@0 960 break;
giuliomoro@0 961 case 'post':
giuliomoro@0 962 this.stateIndex++;
giuliomoro@0 963 this.currentStateMap = null;
giuliomoro@0 964 this.advanceState();
giuliomoro@0 965 break;
giuliomoro@0 966 };
giuliomoro@0 967 }
giuliomoro@0 968 };
giuliomoro@0 969
giuliomoro@0 970 this.testPageCompleted = function() {
giuliomoro@0 971 // Function called each time a test page has been completed
giuliomoro@0 972 var storePoint = storage.testPages[this.stateIndex];
giuliomoro@0 973 // First get the test metric
giuliomoro@0 974
giuliomoro@0 975 var metric = storePoint.XMLDOM.getElementsByTagName('metric')[0];
giuliomoro@0 976 if (audioEngineContext.metric.enableTestTimer)
giuliomoro@0 977 {
giuliomoro@0 978 var testTime = storePoint.parent.document.createElement('metricresult');
giuliomoro@0 979 testTime.id = 'testTime';
giuliomoro@0 980 testTime.textContent = audioEngineContext.timer.testDuration;
giuliomoro@0 981 metric.appendChild(testTime);
giuliomoro@0 982 }
giuliomoro@0 983
giuliomoro@0 984 var audioObjects = audioEngineContext.audioObjects;
giuliomoro@0 985 for (var ao of audioEngineContext.audioObjects)
giuliomoro@0 986 {
giuliomoro@0 987 ao.exportXMLDOM();
giuliomoro@0 988 }
giuliomoro@0 989 for (var element of interfaceContext.commentQuestions)
giuliomoro@0 990 {
giuliomoro@0 991 element.exportXMLDOM(storePoint);
giuliomoro@0 992 }
giuliomoro@0 993 pageXMLSave(storePoint.XMLDOM, this.currentStateMap);
giuliomoro@0 994 storePoint.complete();
giuliomoro@0 995 };
giuliomoro@0 996 }
giuliomoro@0 997
giuliomoro@0 998 function AudioEngine(specification) {
giuliomoro@0 999
giuliomoro@0 1000 // Create two output paths, the main outputGain and fooGain.
giuliomoro@0 1001 // Output gain is default to 1 and any items for playback route here
giuliomoro@0 1002 // Foo gain is used for analysis to ensure paths get processed, but are not heard
giuliomoro@0 1003 // because web audio will optimise and any route which does not go to the destination gets ignored.
giuliomoro@0 1004 this.outputGain = audioContext.createGain();
giuliomoro@0 1005 this.fooGain = audioContext.createGain();
giuliomoro@0 1006 this.fooGain.gain = 0;
giuliomoro@0 1007
giuliomoro@0 1008 // Use this to detect playback state: 0 - stopped, 1 - playing
giuliomoro@0 1009 this.status = 0;
giuliomoro@0 1010
giuliomoro@0 1011 // Connect both gains to output
giuliomoro@0 1012 this.outputGain.connect(audioContext.destination);
giuliomoro@0 1013 this.fooGain.connect(audioContext.destination);
giuliomoro@0 1014
giuliomoro@0 1015 // Create the timer Object
giuliomoro@0 1016 this.timer = new timer();
giuliomoro@0 1017 // Create session metrics
giuliomoro@0 1018 this.metric = new sessionMetrics(this,specification);
giuliomoro@0 1019
giuliomoro@0 1020 this.loopPlayback = false;
giuliomoro@0 1021
giuliomoro@0 1022 this.pageStore = null;
giuliomoro@0 1023
giuliomoro@0 1024 // Create store for new audioObjects
giuliomoro@0 1025 this.audioObjects = [];
giuliomoro@0 1026
giuliomoro@0 1027 this.buffers = [];
giuliomoro@0 1028 this.bufferObj = function()
giuliomoro@0 1029 {
giuliomoro@0 1030 this.url = null;
giuliomoro@0 1031 this.buffer = null;
giuliomoro@0 1032 this.xmlRequest = new XMLHttpRequest();
giuliomoro@0 1033 this.xmlRequest.parent = this;
giuliomoro@0 1034 this.users = [];
giuliomoro@0 1035 this.progress = 0;
giuliomoro@0 1036 this.status = 0;
giuliomoro@0 1037 this.ready = function()
giuliomoro@0 1038 {
giuliomoro@0 1039 if (this.status >= 2)
giuliomoro@0 1040 {
giuliomoro@0 1041 this.status = 3;
giuliomoro@0 1042 }
giuliomoro@0 1043 for (var i=0; i<this.users.length; i++)
giuliomoro@0 1044 {
giuliomoro@0 1045 this.users[i].state = 1;
giuliomoro@0 1046 if (this.users[i].interfaceDOM != null)
giuliomoro@0 1047 {
giuliomoro@0 1048 this.users[i].bufferLoaded(this);
giuliomoro@0 1049 }
giuliomoro@0 1050 }
giuliomoro@0 1051 };
giuliomoro@0 1052 this.getMedia = function(url) {
giuliomoro@0 1053 this.url = url;
giuliomoro@0 1054 this.xmlRequest.open('GET',this.url,true);
giuliomoro@0 1055 this.xmlRequest.responseType = 'arraybuffer';
giuliomoro@0 1056
giuliomoro@0 1057 var bufferObj = this;
giuliomoro@0 1058
giuliomoro@0 1059 // Create callback to decode the data asynchronously
giuliomoro@0 1060 this.xmlRequest.onloadend = function() {
giuliomoro@0 1061 // Use inbuilt WAVE decoder first
giuliomoro@0 1062 if (this.status == -1) {return;}
giuliomoro@0 1063 var waveObj = new WAVE();
giuliomoro@0 1064 if (waveObj.open(bufferObj.xmlRequest.response) == 0)
giuliomoro@0 1065 {
giuliomoro@0 1066 bufferObj.buffer = audioContext.createBuffer(waveObj.num_channels,waveObj.num_samples,waveObj.sample_rate);
giuliomoro@0 1067 for (var c=0; c<waveObj.num_channels; c++)
giuliomoro@0 1068 {
giuliomoro@0 1069 var buffer_ptr = bufferObj.buffer.getChannelData(c);
giuliomoro@0 1070 for (var n=0; n<waveObj.num_samples; n++)
giuliomoro@0 1071 {
giuliomoro@0 1072 buffer_ptr[n] = waveObj.decoded_data[c][n];
giuliomoro@0 1073 }
giuliomoro@0 1074 }
giuliomoro@0 1075
giuliomoro@0 1076 delete waveObj;
giuliomoro@0 1077 } else {
giuliomoro@0 1078 audioContext.decodeAudioData(bufferObj.xmlRequest.response, function(decodedData) {
giuliomoro@0 1079 bufferObj.buffer = decodedData;
giuliomoro@0 1080 }, function(e){
giuliomoro@0 1081 // Should only be called if there was an error, but sometimes gets called continuously
giuliomoro@0 1082 // Check here if the error is genuine
giuliomoro@0 1083 if (bufferObj.xmlRequest.response == undefined) {
giuliomoro@0 1084 // Genuine error
giuliomoro@0 1085 console.log('FATAL - Error loading buffer on '+audioObj.id);
giuliomoro@0 1086 if (request.status == 404)
giuliomoro@0 1087 {
giuliomoro@0 1088 console.log('FATAL - Fragment '+audioObj.id+' 404 error');
giuliomoro@0 1089 console.log('URL: '+audioObj.url);
giuliomoro@0 1090 errorSessionDump('Fragment '+audioObj.id+' 404 error');
giuliomoro@0 1091 }
giuliomoro@0 1092 this.parent.status = -1;
giuliomoro@0 1093 }
giuliomoro@0 1094 });
giuliomoro@0 1095 }
giuliomoro@0 1096 if (bufferObj.buffer != undefined)
giuliomoro@0 1097 {
giuliomoro@0 1098 bufferObj.status = 2;
giuliomoro@0 1099 calculateLoudness(bufferObj,"I");
giuliomoro@0 1100 }
giuliomoro@0 1101 };
giuliomoro@0 1102
giuliomoro@0 1103 // Create callback for any error in loading
giuliomoro@0 1104 this.xmlRequest.onerror = function() {
giuliomoro@0 1105 this.parent.status = -1;
giuliomoro@0 1106 for (var i=0; i<this.parent.users.length; i++)
giuliomoro@0 1107 {
giuliomoro@0 1108 this.parent.users[i].state = -1;
giuliomoro@0 1109 if (this.parent.users[i].interfaceDOM != null)
giuliomoro@0 1110 {
giuliomoro@0 1111 this.parent.users[i].bufferLoaded(this);
giuliomoro@0 1112 }
giuliomoro@0 1113 }
giuliomoro@0 1114 }
giuliomoro@0 1115
giuliomoro@0 1116 this.progress = 0;
giuliomoro@0 1117 this.progressCallback = function(event){
giuliomoro@0 1118 if (event.lengthComputable)
giuliomoro@0 1119 {
giuliomoro@0 1120 this.parent.progress = event.loaded / event.total;
giuliomoro@0 1121 for (var i=0; i<this.parent.users.length; i++)
giuliomoro@0 1122 {
giuliomoro@0 1123 if(this.parent.users[i].interfaceDOM != null)
giuliomoro@0 1124 {
giuliomoro@0 1125 if (typeof this.parent.users[i].interfaceDOM.updateLoading === "function")
giuliomoro@0 1126 {
giuliomoro@0 1127 this.parent.users[i].interfaceDOM.updateLoading(this.parent.progress*100);
giuliomoro@0 1128 }
giuliomoro@0 1129 }
giuliomoro@0 1130 }
giuliomoro@0 1131 }
giuliomoro@0 1132 };
giuliomoro@0 1133 this.xmlRequest.addEventListener("progress", this.progressCallback);
giuliomoro@0 1134 this.status = 1;
giuliomoro@0 1135 this.xmlRequest.send();
giuliomoro@0 1136 };
giuliomoro@0 1137
giuliomoro@0 1138 this.registerAudioObject = function(audioObject)
giuliomoro@0 1139 {
giuliomoro@0 1140 // Called by an audioObject to register to the buffer for use
giuliomoro@0 1141 // First check if already in the register pool
giuliomoro@0 1142 for (var objects of this.users)
giuliomoro@0 1143 {
giuliomoro@0 1144 if (audioObject.id == objects.id){return 0;}
giuliomoro@0 1145 }
giuliomoro@0 1146 this.users.push(audioObject);
giuliomoro@0 1147 if (this.status == 3 || this.status == -1)
giuliomoro@0 1148 {
giuliomoro@0 1149 // The buffer is already ready, trigger bufferLoaded
giuliomoro@0 1150 audioObject.bufferLoaded(this);
giuliomoro@0 1151 }
giuliomoro@0 1152 }
giuliomoro@0 1153 };
giuliomoro@0 1154
giuliomoro@0 1155 this.play = function(id) {
giuliomoro@0 1156 // Start the timer and set the audioEngine state to playing (1)
giuliomoro@0 1157 if (this.status == 0 && this.loopPlayback) {
giuliomoro@0 1158 // Check if all audioObjects are ready
giuliomoro@0 1159 if(this.checkAllReady())
giuliomoro@0 1160 {
giuliomoro@0 1161 this.status = 1;
giuliomoro@0 1162 this.setSynchronousLoop();
giuliomoro@0 1163 }
giuliomoro@0 1164 }
giuliomoro@0 1165 else
giuliomoro@0 1166 {
giuliomoro@0 1167 this.status = 1;
giuliomoro@0 1168 }
giuliomoro@0 1169 if (this.status== 1) {
giuliomoro@0 1170 this.timer.startTest();
giuliomoro@0 1171 if (id == undefined) {
giuliomoro@0 1172 id = -1;
giuliomoro@0 1173 console.log('FATAL - Passed id was undefined - AudioEngineContext.play(id)');
giuliomoro@0 1174 return;
giuliomoro@0 1175 } else {
giuliomoro@0 1176 interfaceContext.playhead.setTimePerPixel(this.audioObjects[id]);
giuliomoro@0 1177 }
giuliomoro@0 1178 if (this.loopPlayback) {
giuliomoro@0 1179 var setTime = audioContext.currentTime;
giuliomoro@0 1180 for (var i=0; i<this.audioObjects.length; i++)
giuliomoro@0 1181 {
giuliomoro@0 1182 this.audioObjects[i].play(setTime);
giuliomoro@0 1183 if (id == i) {
giuliomoro@0 1184 this.audioObjects[i].loopStart(setTime);
giuliomoro@0 1185 } else {
giuliomoro@0 1186 this.audioObjects[i].loopStop(setTime);
giuliomoro@0 1187 }
giuliomoro@0 1188 }
giuliomoro@0 1189 } else {
giuliomoro@0 1190 var setTime = audioContext.currentTime+0.1;
giuliomoro@0 1191 for (var i=0; i<this.audioObjects.length; i++)
giuliomoro@0 1192 {
giuliomoro@0 1193 if (i != id) {
giuliomoro@0 1194 this.audioObjects[i].stop(setTime);
giuliomoro@0 1195 } else if (i == id) {
giuliomoro@0 1196 this.audioObjects[id].play(setTime);
giuliomoro@0 1197 }
giuliomoro@0 1198 }
giuliomoro@0 1199 }
giuliomoro@0 1200 interfaceContext.playhead.start();
giuliomoro@0 1201 }
giuliomoro@0 1202 };
giuliomoro@0 1203
giuliomoro@0 1204 this.stop = function() {
giuliomoro@0 1205 // Send stop and reset command to all playback buffers
giuliomoro@0 1206 if (this.status == 1) {
giuliomoro@0 1207 var setTime = audioContext.currentTime+0.1;
giuliomoro@0 1208 for (var i=0; i<this.audioObjects.length; i++)
giuliomoro@0 1209 {
giuliomoro@0 1210 this.audioObjects[i].stop(setTime);
giuliomoro@0 1211 }
giuliomoro@0 1212 interfaceContext.playhead.stop();
giuliomoro@0 1213 }
giuliomoro@0 1214 };
giuliomoro@0 1215
giuliomoro@0 1216 this.newTrack = function(element) {
giuliomoro@0 1217 // Pull data from given URL into new audio buffer
giuliomoro@0 1218 // URLs must either be from the same source OR be setup to 'Access-Control-Allow-Origin'
giuliomoro@0 1219
giuliomoro@0 1220 // Create the audioObject with ID of the new track length;
giuliomoro@0 1221 audioObjectId = this.audioObjects.length;
giuliomoro@0 1222 this.audioObjects[audioObjectId] = new audioObject(audioObjectId);
giuliomoro@0 1223
giuliomoro@0 1224 // Check if audioObject buffer is currently stored by full URL
giuliomoro@0 1225 var URL = testState.currentStateMap.hostURL + element.url;
giuliomoro@0 1226 var buffer = null;
giuliomoro@0 1227 for (var i=0; i<this.buffers.length; i++)
giuliomoro@0 1228 {
giuliomoro@0 1229 if (URL == this.buffers[i].url)
giuliomoro@0 1230 {
giuliomoro@0 1231 buffer = this.buffers[i];
giuliomoro@0 1232 break;
giuliomoro@0 1233 }
giuliomoro@0 1234 }
giuliomoro@0 1235 if (buffer == null)
giuliomoro@0 1236 {
giuliomoro@0 1237 console.log("[WARN]: Buffer was not loaded in pre-test! "+URL);
giuliomoro@0 1238 buffer = new this.bufferObj();
giuliomoro@0 1239 this.buffers.push(buffer);
giuliomoro@0 1240 buffer.getMedia(URL);
giuliomoro@0 1241 }
giuliomoro@0 1242 this.audioObjects[audioObjectId].specification = element;
giuliomoro@0 1243 this.audioObjects[audioObjectId].url = URL;
giuliomoro@0 1244 // Obtain store node
giuliomoro@0 1245 var aeNodes = this.pageStore.XMLDOM.getElementsByTagName('audioelement');
giuliomoro@0 1246 for (var i=0; i<aeNodes.length; i++)
giuliomoro@0 1247 {
giuliomoro@0 1248 if(aeNodes[i].getAttribute("ref") == element.id)
giuliomoro@0 1249 {
giuliomoro@0 1250 this.audioObjects[audioObjectId].storeDOM = aeNodes[i];
giuliomoro@0 1251 break;
giuliomoro@0 1252 }
giuliomoro@0 1253 }
giuliomoro@0 1254 buffer.registerAudioObject(this.audioObjects[audioObjectId]);
giuliomoro@0 1255 return this.audioObjects[audioObjectId];
giuliomoro@0 1256 };
giuliomoro@0 1257
giuliomoro@0 1258 this.newTestPage = function(audioHolderObject,store) {
giuliomoro@0 1259 this.pageStore = store;
giuliomoro@0 1260 this.status = 0;
giuliomoro@0 1261 this.audioObjectsReady = false;
giuliomoro@0 1262 this.metric.reset();
giuliomoro@0 1263 for (var i=0; i < this.buffers.length; i++)
giuliomoro@0 1264 {
giuliomoro@0 1265 this.buffers[i].users = [];
giuliomoro@0 1266 }
giuliomoro@0 1267 this.audioObjects = [];
giuliomoro@0 1268 this.timer = new timer();
giuliomoro@0 1269 this.loopPlayback = audioHolderObject.loop;
giuliomoro@0 1270 };
giuliomoro@0 1271
giuliomoro@0 1272 this.checkAllPlayed = function() {
giuliomoro@0 1273 arr = [];
giuliomoro@0 1274 for (var id=0; id<this.audioObjects.length; id++) {
giuliomoro@0 1275 if (this.audioObjects[id].metric.wasListenedTo == false) {
giuliomoro@0 1276 arr.push(this.audioObjects[id].id);
giuliomoro@0 1277 }
giuliomoro@0 1278 }
giuliomoro@0 1279 return arr;
giuliomoro@0 1280 };
giuliomoro@0 1281
giuliomoro@0 1282 this.checkAllReady = function() {
giuliomoro@0 1283 var ready = true;
giuliomoro@0 1284 for (var i=0; i<this.audioObjects.length; i++) {
giuliomoro@0 1285 if (this.audioObjects[i].state == 0) {
giuliomoro@0 1286 // Track not ready
giuliomoro@0 1287 console.log('WAIT -- audioObject '+i+' not ready yet!');
giuliomoro@0 1288 ready = false;
giuliomoro@0 1289 };
giuliomoro@0 1290 }
giuliomoro@0 1291 return ready;
giuliomoro@0 1292 };
giuliomoro@0 1293
giuliomoro@0 1294 this.setSynchronousLoop = function() {
giuliomoro@0 1295 // Pads the signals so they are all exactly the same length
giuliomoro@0 1296 var length = 0;
giuliomoro@0 1297 var maxId;
giuliomoro@0 1298 for (var i=0; i<this.audioObjects.length; i++)
giuliomoro@0 1299 {
giuliomoro@0 1300 if (length < this.audioObjects[i].buffer.buffer.length)
giuliomoro@0 1301 {
giuliomoro@0 1302 length = this.audioObjects[i].buffer.buffer.length;
giuliomoro@0 1303 maxId = i;
giuliomoro@0 1304 }
giuliomoro@0 1305 }
giuliomoro@0 1306 // Extract the audio and zero-pad
giuliomoro@0 1307 for (var i=0; i<this.audioObjects.length; i++)
giuliomoro@0 1308 {
giuliomoro@0 1309 var orig = this.audioObjects[i].buffer.buffer;
giuliomoro@0 1310 var hold = audioContext.createBuffer(orig.numberOfChannels,length,orig.sampleRate);
giuliomoro@0 1311 for (var c=0; c<orig.numberOfChannels; c++)
giuliomoro@0 1312 {
giuliomoro@0 1313 var inData = hold.getChannelData(c);
giuliomoro@0 1314 var outData = orig.getChannelData(c);
giuliomoro@0 1315 for (var n=0; n<orig.length; n++)
giuliomoro@0 1316 {inData[n] = outData[n];}
giuliomoro@0 1317 }
giuliomoro@0 1318 hold.playbackGain = orig.playbackGain;
giuliomoro@0 1319 hold.lufs = orig.lufs;
giuliomoro@0 1320 this.audioObjects[i].buffer.buffer = hold;
giuliomoro@0 1321 }
giuliomoro@0 1322 };
giuliomoro@0 1323
giuliomoro@0 1324 this.exportXML = function()
giuliomoro@0 1325 {
giuliomoro@0 1326
giuliomoro@0 1327 };
giuliomoro@0 1328
giuliomoro@0 1329 }
giuliomoro@0 1330
giuliomoro@0 1331 function audioObject(id) {
giuliomoro@0 1332 // The main buffer object with common control nodes to the AudioEngine
giuliomoro@0 1333
giuliomoro@0 1334 this.specification;
giuliomoro@0 1335 this.id = id;
giuliomoro@0 1336 this.state = 0; // 0 - no data, 1 - ready
giuliomoro@0 1337 this.url = null; // Hold the URL given for the output back to the results.
giuliomoro@0 1338 this.metric = new metricTracker(this);
giuliomoro@0 1339 this.storeDOM = null;
giuliomoro@0 1340
giuliomoro@0 1341 // Bindings for GUI
giuliomoro@0 1342 this.interfaceDOM = null;
giuliomoro@0 1343 this.commentDOM = null;
giuliomoro@0 1344
giuliomoro@0 1345 // Create a buffer and external gain control to allow internal patching of effects and volume leveling.
giuliomoro@0 1346 this.bufferNode = undefined;
giuliomoro@0 1347 this.outputGain = audioContext.createGain();
giuliomoro@0 1348
giuliomoro@0 1349 this.onplayGain = 1.0;
giuliomoro@0 1350
giuliomoro@0 1351 // Connect buffer to the audio graph
giuliomoro@0 1352 this.outputGain.connect(audioEngineContext.outputGain);
giuliomoro@0 1353
giuliomoro@0 1354 // the audiobuffer is not designed for multi-start playback
giuliomoro@0 1355 // When stopeed, the buffer node is deleted and recreated with the stored buffer.
giuliomoro@0 1356 this.buffer;
giuliomoro@0 1357
giuliomoro@0 1358 this.bufferLoaded = function(callee)
giuliomoro@0 1359 {
giuliomoro@0 1360 // Called by the associated buffer when it has finished loading, will then 'bind' the buffer to the
giuliomoro@0 1361 // audioObject and trigger the interfaceDOM.enable() function for user feedback
giuliomoro@0 1362 if (callee.status == -1) {
giuliomoro@0 1363 // ERROR
giuliomoro@0 1364 this.state = -1;
giuliomoro@0 1365 if (this.interfaceDOM != null) {this.interfaceDOM.error();}
giuliomoro@0 1366 this.buffer = callee;
giuliomoro@0 1367 return;
giuliomoro@0 1368 }
giuliomoro@0 1369 if (audioEngineContext.loopPlayback){
giuliomoro@0 1370 // First copy the buffer into this.buffer
giuliomoro@0 1371 this.buffer = new audioEngineContext.bufferObj();
giuliomoro@0 1372 this.buffer.url = callee.url;
giuliomoro@0 1373 this.buffer.buffer = audioContext.createBuffer(callee.buffer.numberOfChannels, callee.buffer.length, callee.buffer.sampleRate);
giuliomoro@0 1374 for (var c=0; c<callee.buffer.numberOfChannels; c++)
giuliomoro@0 1375 {
giuliomoro@0 1376 var src = callee.buffer.getChannelData(c);
giuliomoro@0 1377 var dst = this.buffer.buffer.getChannelData(c);
giuliomoro@0 1378 for (var n=0; n<src.length; n++)
giuliomoro@0 1379 {
giuliomoro@0 1380 dst[n] = src[n];
giuliomoro@0 1381 }
giuliomoro@0 1382 }
giuliomoro@0 1383 } else {
giuliomoro@0 1384 this.buffer = callee;
giuliomoro@0 1385 }
giuliomoro@0 1386 this.state = 1;
giuliomoro@0 1387 this.buffer.buffer.playbackGain = callee.buffer.playbackGain;
giuliomoro@0 1388 this.buffer.buffer.lufs = callee.buffer.lufs;
giuliomoro@0 1389 var targetLUFS = this.specification.parent.loudness || specification.loudness;
giuliomoro@0 1390 if (typeof targetLUFS === "number")
giuliomoro@0 1391 {
giuliomoro@0 1392 this.buffer.buffer.playbackGain = decibelToLinear(targetLUFS - this.buffer.buffer.lufs);
giuliomoro@0 1393 } else {
giuliomoro@0 1394 this.buffer.buffer.playbackGain = 1.0;
giuliomoro@0 1395 }
giuliomoro@0 1396 if (this.interfaceDOM != null) {
giuliomoro@0 1397 this.interfaceDOM.enable();
giuliomoro@0 1398 }
giuliomoro@0 1399 this.onplayGain = decibelToLinear(this.specification.gain)*this.buffer.buffer.playbackGain;
giuliomoro@0 1400 this.storeDOM.setAttribute('playGain',linearToDecibel(this.onplayGain));
giuliomoro@0 1401 };
giuliomoro@0 1402
giuliomoro@0 1403 this.bindInterface = function(interfaceObject)
giuliomoro@0 1404 {
giuliomoro@0 1405 this.interfaceDOM = interfaceObject;
giuliomoro@0 1406 this.metric.initialise(interfaceObject.getValue());
giuliomoro@0 1407 if (this.state == 1)
giuliomoro@0 1408 {
giuliomoro@0 1409 this.interfaceDOM.enable();
giuliomoro@0 1410 } else if (this.state == -1) {
giuliomoro@0 1411 // ERROR
giuliomoro@0 1412 this.interfaceDOM.error();
giuliomoro@0 1413 return;
giuliomoro@0 1414 }
giuliomoro@0 1415 this.storeDOM.setAttribute('presentedId',interfaceObject.getPresentedId());
giuliomoro@0 1416 };
giuliomoro@0 1417
giuliomoro@0 1418 this.loopStart = function(setTime) {
giuliomoro@0 1419 this.outputGain.gain.linearRampToValueAtTime(this.onplayGain,setTime);
giuliomoro@0 1420 this.metric.startListening(audioEngineContext.timer.getTestTime());
giuliomoro@0 1421 this.interfaceDOM.startPlayback();
giuliomoro@0 1422 };
giuliomoro@0 1423
giuliomoro@0 1424 this.loopStop = function(setTime) {
giuliomoro@0 1425 if (this.outputGain.gain.value != 0.0) {
giuliomoro@0 1426 this.outputGain.gain.linearRampToValueAtTime(0.0,setTime);
giuliomoro@0 1427 this.metric.stopListening(audioEngineContext.timer.getTestTime());
giuliomoro@0 1428 }
giuliomoro@0 1429 this.interfaceDOM.stopPlayback();
giuliomoro@0 1430 };
giuliomoro@0 1431
giuliomoro@0 1432 this.play = function(startTime) {
giuliomoro@0 1433 if (this.bufferNode == undefined && this.buffer.buffer != undefined) {
giuliomoro@0 1434 this.bufferNode = audioContext.createBufferSource();
giuliomoro@0 1435 this.bufferNode.owner = this;
giuliomoro@0 1436 this.bufferNode.connect(this.outputGain);
giuliomoro@0 1437 this.bufferNode.buffer = this.buffer.buffer;
giuliomoro@0 1438 this.bufferNode.loop = audioEngineContext.loopPlayback;
giuliomoro@0 1439 this.bufferNode.onended = function(event) {
giuliomoro@0 1440 // Safari does not like using 'this' to reference the calling object!
giuliomoro@0 1441 //event.currentTarget.owner.metric.stopListening(audioEngineContext.timer.getTestTime(),event.currentTarget.owner.getCurrentPosition());
giuliomoro@0 1442 if (event.currentTarget != null) {
giuliomoro@0 1443 event.currentTarget.owner.stop(audioContext.currentTime+1);
giuliomoro@0 1444 }
giuliomoro@0 1445 };
giuliomoro@0 1446 if (this.bufferNode.loop == false) {
giuliomoro@0 1447 this.metric.startListening(audioEngineContext.timer.getTestTime());
giuliomoro@0 1448 this.outputGain.gain.setValueAtTime(this.onplayGain,startTime);
giuliomoro@0 1449 this.interfaceDOM.startPlayback();
giuliomoro@0 1450 } else {
giuliomoro@0 1451 this.outputGain.gain.setValueAtTime(0.0,startTime);
giuliomoro@0 1452 }
giuliomoro@0 1453 this.bufferNode.start(startTime);
giuliomoro@0 1454 this.bufferNode.playbackStartTime = audioEngineContext.timer.getTestTime();
giuliomoro@0 1455 }
giuliomoro@0 1456 };
giuliomoro@0 1457
giuliomoro@0 1458 this.stop = function(stopTime) {
giuliomoro@0 1459 this.outputGain.gain.cancelScheduledValues(audioContext.currentTime);
giuliomoro@0 1460 if (this.bufferNode != undefined)
giuliomoro@0 1461 {
giuliomoro@0 1462 this.metric.stopListening(audioEngineContext.timer.getTestTime(),this.getCurrentPosition());
giuliomoro@0 1463 this.bufferNode.stop(stopTime);
giuliomoro@0 1464 this.bufferNode = undefined;
giuliomoro@0 1465 }
giuliomoro@0 1466 this.outputGain.gain.value = 0.0;
giuliomoro@0 1467 this.interfaceDOM.stopPlayback();
giuliomoro@0 1468 };
giuliomoro@0 1469
giuliomoro@0 1470 this.getCurrentPosition = function() {
giuliomoro@0 1471 var time = audioEngineContext.timer.getTestTime();
giuliomoro@0 1472 if (this.bufferNode != undefined) {
giuliomoro@0 1473 var position = (time - this.bufferNode.playbackStartTime)%this.buffer.buffer.duration;
giuliomoro@0 1474 if (isNaN(position)){return 0;}
giuliomoro@0 1475 return position;
giuliomoro@0 1476 } else {
giuliomoro@0 1477 return 0;
giuliomoro@0 1478 }
giuliomoro@0 1479 };
giuliomoro@0 1480
giuliomoro@0 1481 this.exportXMLDOM = function() {
giuliomoro@0 1482 var file = storage.document.createElement('file');
giuliomoro@0 1483 var buf;
giuliomoro@0 1484 if(typeof(this.buffer) !== "undefined"){
giuliomoro@0 1485 buf = this.buffer.buffer;
giuliomoro@0 1486 } else {
giuliomoro@0 1487 buf = {};
giuliomoro@0 1488 }
giuliomoro@0 1489 file.setAttribute('sampleRate', buf.sampleRate);
giuliomoro@0 1490 file.setAttribute('channels', buf.numberOfChannels);
giuliomoro@0 1491 file.setAttribute('sampleCount', buf.length);
giuliomoro@0 1492 file.setAttribute('duration', buf.duration);
giuliomoro@0 1493 this.storeDOM.appendChild(file);
giuliomoro@0 1494 if (this.specification.type != 'outside-reference') {
giuliomoro@0 1495 var interfaceXML = this.interfaceDOM.exportXMLDOM(this);
giuliomoro@0 1496 if (interfaceXML != null)
giuliomoro@0 1497 {
giuliomoro@0 1498 if (interfaceXML.length == undefined) {
giuliomoro@0 1499 this.storeDOM.appendChild(interfaceXML);
giuliomoro@0 1500 } else {
giuliomoro@0 1501 for (var i=0; i<interfaceXML.length; i++)
giuliomoro@0 1502 {
giuliomoro@0 1503 this.storeDOM.appendChild(interfaceXML[i]);
giuliomoro@0 1504 }
giuliomoro@0 1505 }
giuliomoro@0 1506 }
giuliomoro@0 1507 if (this.commentDOM != null) {
giuliomoro@0 1508 this.storeDOM.appendChild(this.commentDOM.exportXMLDOM(this));
giuliomoro@0 1509 }
giuliomoro@0 1510 }
giuliomoro@0 1511 var nodes = this.metric.exportXMLDOM();
giuliomoro@0 1512 var mroot = this.storeDOM.getElementsByTagName('metric')[0];
giuliomoro@0 1513 for (var i=0; i<nodes.length; i++)
giuliomoro@0 1514 {
giuliomoro@0 1515 mroot.appendChild(nodes[i]);
giuliomoro@0 1516 }
giuliomoro@0 1517 };
giuliomoro@0 1518 }
giuliomoro@0 1519
giuliomoro@0 1520 function timer()
giuliomoro@0 1521 {
giuliomoro@0 1522 /* Timer object used in audioEngine to keep track of session timings
giuliomoro@0 1523 * Uses the timer of the web audio API, so sample resolution
giuliomoro@0 1524 */
giuliomoro@0 1525 this.testStarted = false;
giuliomoro@0 1526 this.testStartTime = 0;
giuliomoro@0 1527 this.testDuration = 0;
giuliomoro@0 1528 this.minimumTestTime = 0; // No minimum test time
giuliomoro@0 1529 this.startTest = function()
giuliomoro@0 1530 {
giuliomoro@0 1531 if (this.testStarted == false)
giuliomoro@0 1532 {
giuliomoro@0 1533 this.testStartTime = audioContext.currentTime;
giuliomoro@0 1534 this.testStarted = true;
giuliomoro@0 1535 this.updateTestTime();
giuliomoro@0 1536 audioEngineContext.metric.initialiseTest();
giuliomoro@0 1537 }
giuliomoro@0 1538 };
giuliomoro@0 1539 this.stopTest = function()
giuliomoro@0 1540 {
giuliomoro@0 1541 if (this.testStarted)
giuliomoro@0 1542 {
giuliomoro@0 1543 this.testDuration = this.getTestTime();
giuliomoro@0 1544 this.testStarted = false;
giuliomoro@0 1545 } else {
giuliomoro@0 1546 console.log('ERR: Test tried to end before beginning');
giuliomoro@0 1547 }
giuliomoro@0 1548 };
giuliomoro@0 1549 this.updateTestTime = function()
giuliomoro@0 1550 {
giuliomoro@0 1551 if (this.testStarted)
giuliomoro@0 1552 {
giuliomoro@0 1553 this.testDuration = audioContext.currentTime - this.testStartTime;
giuliomoro@0 1554 }
giuliomoro@0 1555 };
giuliomoro@0 1556 this.getTestTime = function()
giuliomoro@0 1557 {
giuliomoro@0 1558 this.updateTestTime();
giuliomoro@0 1559 return this.testDuration;
giuliomoro@0 1560 };
giuliomoro@0 1561 }
giuliomoro@0 1562
giuliomoro@0 1563 function sessionMetrics(engine,specification)
giuliomoro@0 1564 {
giuliomoro@0 1565 /* Used by audioEngine to link to audioObjects to minimise the timer call timers;
giuliomoro@0 1566 */
giuliomoro@0 1567 this.engine = engine;
giuliomoro@0 1568 this.lastClicked = -1;
giuliomoro@0 1569 this.data = -1;
giuliomoro@0 1570 this.reset = function() {
giuliomoro@0 1571 this.lastClicked = -1;
giuliomoro@0 1572 this.data = -1;
giuliomoro@0 1573 };
giuliomoro@0 1574
giuliomoro@0 1575 this.enableElementInitialPosition = false;
giuliomoro@0 1576 this.enableElementListenTracker = false;
giuliomoro@0 1577 this.enableElementTimer = false;
giuliomoro@0 1578 this.enableElementTracker = false;
giuliomoro@0 1579 this.enableFlagListenedTo = false;
giuliomoro@0 1580 this.enableFlagMoved = false;
giuliomoro@0 1581 this.enableTestTimer = false;
giuliomoro@0 1582 // Obtain the metrics enabled
giuliomoro@0 1583 for (var i=0; i<specification.metrics.enabled.length; i++)
giuliomoro@0 1584 {
giuliomoro@0 1585 var node = specification.metrics.enabled[i];
giuliomoro@0 1586 switch(node)
giuliomoro@0 1587 {
giuliomoro@0 1588 case 'testTimer':
giuliomoro@0 1589 this.enableTestTimer = true;
giuliomoro@0 1590 break;
giuliomoro@0 1591 case 'elementTimer':
giuliomoro@0 1592 this.enableElementTimer = true;
giuliomoro@0 1593 break;
giuliomoro@0 1594 case 'elementTracker':
giuliomoro@0 1595 this.enableElementTracker = true;
giuliomoro@0 1596 break;
giuliomoro@0 1597 case 'elementListenTracker':
giuliomoro@0 1598 this.enableElementListenTracker = true;
giuliomoro@0 1599 break;
giuliomoro@0 1600 case 'elementInitialPosition':
giuliomoro@0 1601 this.enableElementInitialPosition = true;
giuliomoro@0 1602 break;
giuliomoro@0 1603 case 'elementFlagListenedTo':
giuliomoro@0 1604 this.enableFlagListenedTo = true;
giuliomoro@0 1605 break;
giuliomoro@0 1606 case 'elementFlagMoved':
giuliomoro@0 1607 this.enableFlagMoved = true;
giuliomoro@0 1608 break;
giuliomoro@0 1609 case 'elementFlagComments':
giuliomoro@0 1610 this.enableFlagComments = true;
giuliomoro@0 1611 break;
giuliomoro@0 1612 }
giuliomoro@0 1613 }
giuliomoro@0 1614 this.initialiseTest = function(){};
giuliomoro@0 1615 }
giuliomoro@0 1616
giuliomoro@0 1617 function metricTracker(caller)
giuliomoro@0 1618 {
giuliomoro@0 1619 /* Custom object to track and collect metric data
giuliomoro@0 1620 * Used only inside the audioObjects object.
giuliomoro@0 1621 */
giuliomoro@0 1622
giuliomoro@0 1623 this.listenedTimer = 0;
giuliomoro@0 1624 this.listenStart = 0;
giuliomoro@0 1625 this.listenHold = false;
giuliomoro@0 1626 this.initialPosition = -1;
giuliomoro@0 1627 this.movementTracker = [];
giuliomoro@0 1628 this.listenTracker =[];
giuliomoro@0 1629 this.wasListenedTo = false;
giuliomoro@0 1630 this.wasMoved = false;
giuliomoro@0 1631 this.hasComments = false;
giuliomoro@0 1632 this.parent = caller;
giuliomoro@0 1633
giuliomoro@0 1634 this.initialise = function(position)
giuliomoro@0 1635 {
giuliomoro@0 1636 if (this.initialPosition == -1) {
giuliomoro@0 1637 this.initialPosition = position;
giuliomoro@0 1638 this.moved(0,position);
giuliomoro@0 1639 }
giuliomoro@0 1640 };
giuliomoro@0 1641
giuliomoro@0 1642 this.moved = function(time,position)
giuliomoro@0 1643 {
giuliomoro@0 1644 if (time > 0) {this.wasMoved = true;}
giuliomoro@0 1645 this.movementTracker[this.movementTracker.length] = [time, position];
giuliomoro@0 1646 };
giuliomoro@0 1647
giuliomoro@0 1648 this.startListening = function(time)
giuliomoro@0 1649 {
giuliomoro@0 1650 if (this.listenHold == false)
giuliomoro@0 1651 {
giuliomoro@0 1652 this.wasListenedTo = true;
giuliomoro@0 1653 this.listenStart = time;
giuliomoro@0 1654 this.listenHold = true;
giuliomoro@0 1655
giuliomoro@0 1656 var evnt = document.createElement('event');
giuliomoro@0 1657 var testTime = document.createElement('testTime');
giuliomoro@0 1658 testTime.setAttribute('start',time);
giuliomoro@0 1659 var bufferTime = document.createElement('bufferTime');
giuliomoro@0 1660 bufferTime.setAttribute('start',this.parent.getCurrentPosition());
giuliomoro@0 1661 evnt.appendChild(testTime);
giuliomoro@0 1662 evnt.appendChild(bufferTime);
giuliomoro@0 1663 this.listenTracker.push(evnt);
giuliomoro@0 1664
giuliomoro@0 1665 console.log('slider ' + this.parent.id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id
giuliomoro@0 1666 }
giuliomoro@0 1667 };
giuliomoro@0 1668
giuliomoro@0 1669 this.stopListening = function(time,bufferStopTime)
giuliomoro@0 1670 {
giuliomoro@0 1671 if (this.listenHold == true)
giuliomoro@0 1672 {
giuliomoro@0 1673 var diff = time - this.listenStart;
giuliomoro@0 1674 this.listenedTimer += (diff);
giuliomoro@0 1675 this.listenStart = 0;
giuliomoro@0 1676 this.listenHold = false;
giuliomoro@0 1677
giuliomoro@0 1678 var evnt = this.listenTracker[this.listenTracker.length-1];
giuliomoro@0 1679 var testTime = evnt.getElementsByTagName('testTime')[0];
giuliomoro@0 1680 var bufferTime = evnt.getElementsByTagName('bufferTime')[0];
giuliomoro@0 1681 testTime.setAttribute('stop',time);
giuliomoro@0 1682 if (bufferStopTime == undefined) {
giuliomoro@0 1683 bufferTime.setAttribute('stop',this.parent.getCurrentPosition());
giuliomoro@0 1684 } else {
giuliomoro@0 1685 bufferTime.setAttribute('stop',bufferStopTime);
giuliomoro@0 1686 }
giuliomoro@0 1687 console.log('slider ' + this.parent.id + ' played for (' + diff + ')'); // DEBUG/SAFETY: show played slider id
giuliomoro@0 1688 }
giuliomoro@0 1689 };
giuliomoro@0 1690
giuliomoro@0 1691 this.exportXMLDOM = function() {
giuliomoro@0 1692 var storeDOM = [];
giuliomoro@0 1693 if (audioEngineContext.metric.enableElementTimer) {
giuliomoro@0 1694 var mElementTimer = storage.document.createElement('metricresult');
giuliomoro@0 1695 mElementTimer.setAttribute('name','enableElementTimer');
giuliomoro@0 1696 mElementTimer.textContent = this.listenedTimer;
giuliomoro@0 1697 storeDOM.push(mElementTimer);
giuliomoro@0 1698 }
giuliomoro@0 1699 if (audioEngineContext.metric.enableElementTracker) {
giuliomoro@0 1700 var elementTrackerFull = storage.document.createElement('metricResult');
giuliomoro@0 1701 elementTrackerFull.setAttribute('name','elementTrackerFull');
giuliomoro@0 1702 for (var k=0; k<this.movementTracker.length; k++)
giuliomoro@0 1703 {
giuliomoro@0 1704 var timePos = storage.document.createElement('movement');
giuliomoro@0 1705 timePos.setAttribute("time",this.movementTracker[k][0]);
giuliomoro@0 1706 timePos.setAttribute("value",this.movementTracker[k][1]);
giuliomoro@0 1707 elementTrackerFull.appendChild(timePos);
giuliomoro@0 1708 }
giuliomoro@0 1709 storeDOM.push(elementTrackerFull);
giuliomoro@0 1710 }
giuliomoro@0 1711 if (audioEngineContext.metric.enableElementListenTracker) {
giuliomoro@0 1712 var elementListenTracker = storage.document.createElement('metricResult');
giuliomoro@0 1713 elementListenTracker.setAttribute('name','elementListenTracker');
giuliomoro@0 1714 for (var k=0; k<this.listenTracker.length; k++) {
giuliomoro@0 1715 elementListenTracker.appendChild(this.listenTracker[k]);
giuliomoro@0 1716 }
giuliomoro@0 1717 storeDOM.push(elementListenTracker);
giuliomoro@0 1718 }
giuliomoro@0 1719 if (audioEngineContext.metric.enableElementInitialPosition) {
giuliomoro@0 1720 var elementInitial = storage.document.createElement('metricResult');
giuliomoro@0 1721 elementInitial.setAttribute('name','elementInitialPosition');
giuliomoro@0 1722 elementInitial.textContent = this.initialPosition;
giuliomoro@0 1723 storeDOM.push(elementInitial);
giuliomoro@0 1724 }
giuliomoro@0 1725 if (audioEngineContext.metric.enableFlagListenedTo) {
giuliomoro@0 1726 var flagListenedTo = storage.document.createElement('metricResult');
giuliomoro@0 1727 flagListenedTo.setAttribute('name','elementFlagListenedTo');
giuliomoro@0 1728 flagListenedTo.textContent = this.wasListenedTo;
giuliomoro@0 1729 storeDOM.push(flagListenedTo);
giuliomoro@0 1730 }
giuliomoro@0 1731 if (audioEngineContext.metric.enableFlagMoved) {
giuliomoro@0 1732 var flagMoved = storage.document.createElement('metricResult');
giuliomoro@0 1733 flagMoved.setAttribute('name','elementFlagMoved');
giuliomoro@0 1734 flagMoved.textContent = this.wasMoved;
giuliomoro@0 1735 storeDOM.push(flagMoved);
giuliomoro@0 1736 }
giuliomoro@0 1737 if (audioEngineContext.metric.enableFlagComments) {
giuliomoro@0 1738 var flagComments = storage.document.createElement('metricResult');
giuliomoro@0 1739 flagComments.setAttribute('name','elementFlagComments');
giuliomoro@0 1740 if (this.parent.commentDOM == null)
giuliomoro@0 1741 {flag.textContent = 'false';}
giuliomoro@0 1742 else if (this.parent.commentDOM.textContent.length == 0)
giuliomoro@0 1743 {flag.textContent = 'false';}
giuliomoro@0 1744 else
giuliomoro@0 1745 {flag.textContet = 'true';}
giuliomoro@0 1746 storeDOM.push(flagComments);
giuliomoro@0 1747 }
giuliomoro@0 1748 return storeDOM;
giuliomoro@0 1749 };
giuliomoro@0 1750 }
giuliomoro@0 1751
giuliomoro@0 1752 function randomiseOrder(input)
giuliomoro@0 1753 {
giuliomoro@0 1754 // This takes an array of information and randomises the order
giuliomoro@0 1755 var N = input.length;
giuliomoro@0 1756
giuliomoro@0 1757 var inputSequence = []; // For safety purposes: keep track of randomisation
giuliomoro@0 1758 for (var counter = 0; counter < N; ++counter)
giuliomoro@0 1759 inputSequence.push(counter) // Fill array
giuliomoro@0 1760 var inputSequenceClone = inputSequence.slice(0);
giuliomoro@0 1761
giuliomoro@0 1762 var holdArr = [];
giuliomoro@0 1763 var outputSequence = [];
giuliomoro@0 1764 for (var n=0; n<N; n++)
giuliomoro@0 1765 {
giuliomoro@0 1766 // First pick a random number
giuliomoro@0 1767 var r = Math.random();
giuliomoro@0 1768 // Multiply and floor by the number of elements left
giuliomoro@0 1769 r = Math.floor(r*input.length);
giuliomoro@0 1770 // Pick out that element and delete from the array
giuliomoro@0 1771 holdArr.push(input.splice(r,1)[0]);
giuliomoro@0 1772 // Do the same with sequence
giuliomoro@0 1773 outputSequence.push(inputSequence.splice(r,1)[0]);
giuliomoro@0 1774 }
giuliomoro@0 1775 console.log(inputSequenceClone.toString()); // print original array to console
giuliomoro@0 1776 console.log(outputSequence.toString()); // print randomised array to console
giuliomoro@0 1777 return holdArr;
giuliomoro@0 1778 }
giuliomoro@0 1779
giuliomoro@0 1780 function returnDateNode()
giuliomoro@0 1781 {
giuliomoro@0 1782 // Create an XML Node for the Date and Time a test was conducted
giuliomoro@0 1783 // Structure is
giuliomoro@0 1784 // <datetime>
giuliomoro@0 1785 // <date year="##" month="##" day="##">DD/MM/YY</date>
giuliomoro@0 1786 // <time hour="##" minute="##" sec="##">HH:MM:SS</time>
giuliomoro@0 1787 // </datetime>
giuliomoro@0 1788 var dateTime = new Date();
giuliomoro@0 1789 var year = document.createAttribute('year');
giuliomoro@0 1790 var month = document.createAttribute('month');
giuliomoro@0 1791 var day = document.createAttribute('day');
giuliomoro@0 1792 var hour = document.createAttribute('hour');
giuliomoro@0 1793 var minute = document.createAttribute('minute');
giuliomoro@0 1794 var secs = document.createAttribute('secs');
giuliomoro@0 1795
giuliomoro@0 1796 year.nodeValue = dateTime.getFullYear();
giuliomoro@0 1797 month.nodeValue = dateTime.getMonth()+1;
giuliomoro@0 1798 day.nodeValue = dateTime.getDate();
giuliomoro@0 1799 hour.nodeValue = dateTime.getHours();
giuliomoro@0 1800 minute.nodeValue = dateTime.getMinutes();
giuliomoro@0 1801 secs.nodeValue = dateTime.getSeconds();
giuliomoro@0 1802
giuliomoro@0 1803 var hold = document.createElement("datetime");
giuliomoro@0 1804 var date = document.createElement("date");
giuliomoro@0 1805 date.textContent = year.nodeValue+'/'+month.nodeValue+'/'+day.nodeValue;
giuliomoro@0 1806 var time = document.createElement("time");
giuliomoro@0 1807 time.textContent = hour.nodeValue+':'+minute.nodeValue+':'+secs.nodeValue;
giuliomoro@0 1808
giuliomoro@0 1809 date.setAttributeNode(year);
giuliomoro@0 1810 date.setAttributeNode(month);
giuliomoro@0 1811 date.setAttributeNode(day);
giuliomoro@0 1812 time.setAttributeNode(hour);
giuliomoro@0 1813 time.setAttributeNode(minute);
giuliomoro@0 1814 time.setAttributeNode(secs);
giuliomoro@0 1815
giuliomoro@0 1816 hold.appendChild(date);
giuliomoro@0 1817 hold.appendChild(time);
giuliomoro@0 1818 return hold;
giuliomoro@0 1819
giuliomoro@0 1820 }
giuliomoro@0 1821
giuliomoro@0 1822 function Specification() {
giuliomoro@0 1823 // Handles the decoding of the project specification XML into a simple JavaScript Object.
giuliomoro@0 1824
giuliomoro@0 1825 this.interface = null;
giuliomoro@0 1826 this.projectReturn = "null";
giuliomoro@0 1827 this.randomiseOrder = null;
giuliomoro@0 1828 this.testPages = null;
giuliomoro@0 1829 this.pages = [];
giuliomoro@0 1830 this.metrics = null;
giuliomoro@0 1831 this.interfaces = null;
giuliomoro@0 1832 this.loudness = null;
giuliomoro@0 1833 this.errors = [];
giuliomoro@0 1834 this.schema = null;
giuliomoro@0 1835
giuliomoro@0 1836 this.processAttribute = function(attribute,schema)
giuliomoro@0 1837 {
giuliomoro@0 1838 // attribute is the string returned from getAttribute on the XML
giuliomoro@0 1839 // schema is the <xs:attribute> node
giuliomoro@0 1840 if (schema.getAttribute('name') == undefined && schema.getAttribute('ref') != undefined)
giuliomoro@0 1841 {
giuliomoro@0 1842 schema = this.schema.getAllElementsByName(schema.getAttribute('ref'))[0];
giuliomoro@0 1843 }
giuliomoro@0 1844 var defaultOpt = schema.getAttribute('default');
giuliomoro@0 1845 if (attribute == null) {
giuliomoro@0 1846 attribute = defaultOpt;
giuliomoro@0 1847 }
giuliomoro@0 1848 var dataType = schema.getAttribute('type');
giuliomoro@0 1849 if (typeof dataType == "string") { dataType = dataType.substr(3);}
giuliomoro@0 1850 else {dataType = "string";}
giuliomoro@0 1851 if (attribute == null)
giuliomoro@0 1852 {
giuliomoro@0 1853 return attribute;
giuliomoro@0 1854 }
giuliomoro@0 1855 switch(dataType)
giuliomoro@0 1856 {
giuliomoro@0 1857 case "boolean":
giuliomoro@0 1858 if (attribute == 'true'){attribute = true;}else{attribute=false;}
giuliomoro@0 1859 break;
giuliomoro@0 1860 case "negativeInteger":
giuliomoro@0 1861 case "positiveInteger":
giuliomoro@0 1862 case "nonNegativeInteger":
giuliomoro@0 1863 case "nonPositiveInteger":
giuliomoro@0 1864 case "integer":
giuliomoro@0 1865 case "decimal":
giuliomoro@0 1866 case "short":
giuliomoro@0 1867 attribute = Number(attribute);
giuliomoro@0 1868 break;
giuliomoro@0 1869 case "string":
giuliomoro@0 1870 default:
giuliomoro@0 1871 attribute = String(attribute);
giuliomoro@0 1872 break;
giuliomoro@0 1873 }
giuliomoro@0 1874 return attribute;
giuliomoro@0 1875 };
giuliomoro@0 1876
giuliomoro@0 1877 this.decode = function(projectXML) {
giuliomoro@0 1878 this.errors = [];
giuliomoro@0 1879 // projectXML - DOM Parsed document
giuliomoro@0 1880 this.projectXML = projectXML.childNodes[0];
giuliomoro@0 1881 var setupNode = projectXML.getElementsByTagName('setup')[0];
giuliomoro@0 1882 var schemaSetup = this.schema.getAllElementsByName('setup')[0];
giuliomoro@0 1883 // First decode the attributes
giuliomoro@0 1884 var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
giuliomoro@0 1885 for (var i in attributes)
giuliomoro@0 1886 {
giuliomoro@0 1887 if (isNaN(Number(i)) == true){break;}
giuliomoro@0 1888 var attributeName = attributes[i].getAttribute('name');
giuliomoro@0 1889 var projectAttr = setupNode.getAttribute(attributeName);
giuliomoro@0 1890 projectAttr = this.processAttribute(projectAttr,attributes[i]);
giuliomoro@0 1891 switch(typeof projectAttr)
giuliomoro@0 1892 {
giuliomoro@0 1893 case "number":
giuliomoro@0 1894 case "boolean":
giuliomoro@0 1895 eval('this.'+attributeName+' = '+projectAttr);
giuliomoro@0 1896 break;
giuliomoro@0 1897 case "string":
giuliomoro@0 1898 eval('this.'+attributeName+' = "'+projectAttr+'"');
giuliomoro@0 1899 break;
giuliomoro@0 1900 }
giuliomoro@0 1901
giuliomoro@0 1902 }
giuliomoro@0 1903
giuliomoro@0 1904 this.metrics = new this.metricNode();
giuliomoro@0 1905
giuliomoro@0 1906 this.metrics.decode(this,setupNode.getElementsByTagName('metric')[0]);
giuliomoro@0 1907
giuliomoro@0 1908 // Now process the survey node options
giuliomoro@0 1909 var survey = setupNode.getElementsByTagName('survey');
giuliomoro@0 1910 for (var i in survey) {
giuliomoro@0 1911 if (isNaN(Number(i)) == true){break;}
giuliomoro@0 1912 var location = survey[i].getAttribute('location');
giuliomoro@0 1913 if (location == 'pre' || location == 'before')
giuliomoro@0 1914 {
giuliomoro@0 1915 if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
giuliomoro@0 1916 else {
giuliomoro@0 1917 this.preTest = new this.surveyNode();
giuliomoro@0 1918 this.preTest.decode(this,survey[i]);
giuliomoro@0 1919 }
giuliomoro@0 1920 } else if (location == 'post' || location == 'after') {
giuliomoro@0 1921 if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
giuliomoro@0 1922 else {
giuliomoro@0 1923 this.postTest = new this.surveyNode();
giuliomoro@0 1924 this.postTest.decode(this,survey[i]);
giuliomoro@0 1925 }
giuliomoro@0 1926 }
giuliomoro@0 1927 }
giuliomoro@0 1928
giuliomoro@0 1929 var interfaceNode = setupNode.getElementsByTagName('interface');
giuliomoro@0 1930 if (interfaceNode.length > 1)
giuliomoro@0 1931 {
giuliomoro@0 1932 this.errors.push("Only one <interface> node in the <setup> node allowed! Others except first ingnored!");
giuliomoro@0 1933 }
giuliomoro@0 1934 this.interfaces = new this.interfaceNode();
giuliomoro@0 1935 if (interfaceNode.length != 0)
giuliomoro@0 1936 {
giuliomoro@0 1937 interfaceNode = interfaceNode[0];
giuliomoro@0 1938 this.interfaces.decode(this,interfaceNode,this.schema.getAllElementsByName('interface')[1]);
giuliomoro@0 1939 }
giuliomoro@0 1940
giuliomoro@0 1941 // Page tags
giuliomoro@0 1942 var pageTags = projectXML.getElementsByTagName('page');
giuliomoro@0 1943 var pageSchema = this.schema.getAllElementsByName('page')[0];
giuliomoro@0 1944 for (var i=0; i<pageTags.length; i++)
giuliomoro@0 1945 {
giuliomoro@0 1946 var node = new this.page();
giuliomoro@0 1947 node.decode(this,pageTags[i],pageSchema);
giuliomoro@0 1948 this.pages.push(node);
giuliomoro@0 1949 }
giuliomoro@0 1950 };
giuliomoro@0 1951
giuliomoro@0 1952 this.encode = function()
giuliomoro@0 1953 {
giuliomoro@0 1954 var RootDocument = document.implementation.createDocument(null,"waet");
giuliomoro@0 1955 var root = RootDocument.children[0];
giuliomoro@0 1956 root.setAttribute("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
giuliomoro@0 1957 root.setAttribute("xsi:noNamespaceSchemaLocation","test-schema.xsd");
giuliomoro@0 1958 // Build setup node
giuliomoro@0 1959 var setup = RootDocument.createElement("setup");
giuliomoro@0 1960 var schemaSetup = this.schema.getAllElementsByName('setup')[0];
giuliomoro@0 1961 // First decode the attributes
giuliomoro@0 1962 var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
giuliomoro@0 1963 for (var i=0; i<attributes.length; i++)
giuliomoro@0 1964 {
giuliomoro@0 1965 var name = attributes[i].getAttribute("name");
giuliomoro@0 1966 if (name == undefined) {
giuliomoro@0 1967 name = attributes[i].getAttribute("ref");
giuliomoro@0 1968 }
giuliomoro@0 1969 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
giuliomoro@0 1970 {
giuliomoro@0 1971 eval("setup.setAttribute('"+name+"',this."+name+")");
giuliomoro@0 1972 }
giuliomoro@0 1973 }
giuliomoro@0 1974 root.appendChild(setup);
giuliomoro@0 1975 // Survey node
giuliomoro@0 1976 setup.appendChild(this.preTest.encode(RootDocument));
giuliomoro@0 1977 setup.appendChild(this.postTest.encode(RootDocument));
giuliomoro@0 1978 setup.appendChild(this.metrics.encode(RootDocument));
giuliomoro@0 1979 setup.appendChild(this.interfaces.encode(RootDocument));
giuliomoro@0 1980 for (var page of this.pages)
giuliomoro@0 1981 {
giuliomoro@0 1982 root.appendChild(page.encode(RootDocument));
giuliomoro@0 1983 }
giuliomoro@0 1984 return RootDocument;
giuliomoro@0 1985 };
giuliomoro@0 1986
giuliomoro@0 1987 this.surveyNode = function() {
giuliomoro@0 1988 this.location = null;
giuliomoro@0 1989 this.options = [];
giuliomoro@0 1990 this.schema = specification.schema.getAllElementsByName('survey')[0];
giuliomoro@0 1991
giuliomoro@0 1992 this.OptionNode = function() {
giuliomoro@0 1993 this.type = undefined;
giuliomoro@0 1994 this.schema = specification.schema.getAllElementsByName('surveyentry')[0];
giuliomoro@0 1995 this.id = undefined;
giuliomoro@0 1996 this.name = undefined;
giuliomoro@0 1997 this.mandatory = undefined;
giuliomoro@0 1998 this.statement = undefined;
giuliomoro@0 1999 this.boxsize = undefined;
giuliomoro@0 2000 this.options = [];
giuliomoro@0 2001 this.min = undefined;
giuliomoro@0 2002 this.max = undefined;
giuliomoro@0 2003 this.step = undefined;
giuliomoro@0 2004
giuliomoro@0 2005 this.decode = function(parent,child)
giuliomoro@0 2006 {
giuliomoro@0 2007 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
giuliomoro@0 2008 for (var i in attributeMap){
giuliomoro@0 2009 if(isNaN(Number(i)) == true){break;}
giuliomoro@0 2010 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
giuliomoro@0 2011 var projectAttr = child.getAttribute(attributeName);
giuliomoro@0 2012 projectAttr = parent.processAttribute(projectAttr,attributeMap[i]);
giuliomoro@0 2013 switch(typeof projectAttr)
giuliomoro@0 2014 {
giuliomoro@0 2015 case "number":
giuliomoro@0 2016 case "boolean":
giuliomoro@0 2017 eval('this.'+attributeName+' = '+projectAttr);
giuliomoro@0 2018 break;
giuliomoro@0 2019 case "string":
giuliomoro@0 2020 eval('this.'+attributeName+' = "'+projectAttr+'"');
giuliomoro@0 2021 break;
giuliomoro@0 2022 }
giuliomoro@0 2023 }
giuliomoro@0 2024 this.statement = child.getElementsByTagName('statement')[0].textContent;
giuliomoro@0 2025 if (this.type == "checkbox" || this.type == "radio") {
giuliomoro@0 2026 var children = child.getElementsByTagName('option');
giuliomoro@0 2027 if (children.length == null) {
giuliomoro@0 2028 console.log('Malformed' +child.nodeName+ 'entry');
giuliomoro@0 2029 this.statement = 'Malformed' +child.nodeName+ 'entry';
giuliomoro@0 2030 this.type = 'statement';
giuliomoro@0 2031 } else {
giuliomoro@0 2032 this.options = [];
giuliomoro@0 2033 for (var i in children)
giuliomoro@0 2034 {
giuliomoro@0 2035 if (isNaN(Number(i))==true){break;}
giuliomoro@0 2036 this.options.push({
giuliomoro@0 2037 name: children[i].getAttribute('name'),
giuliomoro@0 2038 text: children[i].textContent
giuliomoro@0 2039 });
giuliomoro@0 2040 }
giuliomoro@0 2041 }
giuliomoro@0 2042 }
giuliomoro@0 2043 };
giuliomoro@0 2044
giuliomoro@0 2045 this.exportXML = function(doc)
giuliomoro@0 2046 {
giuliomoro@0 2047 var node = doc.createElement('surveyentry');
giuliomoro@0 2048 node.setAttribute('type',this.type);
giuliomoro@0 2049 var statement = doc.createElement('statement');
giuliomoro@0 2050 statement.textContent = this.statement;
giuliomoro@0 2051 node.appendChild(statement);
giuliomoro@0 2052 if (this.type != "statement") {
giuliomoro@0 2053 node.id = this.id;
giuliomoro@0 2054 if (this.name != undefined) { node.setAttribute("name",this.name);}
giuliomoro@0 2055 if (this.mandatory != undefined) { node.setAttribute("mandatory",this.mandatory);}
giuliomoro@0 2056 switch(this.type)
giuliomoro@0 2057 {
giuliomoro@0 2058 case "question":
giuliomoro@0 2059 if (this.boxsize != undefined) {node.setAttribute("boxsize",this.boxsize);}
giuliomoro@0 2060 break;
giuliomoro@0 2061 case "number":
giuliomoro@0 2062 if (this.min != undefined) {node.setAttribute("min", this.min);}
giuliomoro@0 2063 if (this.max != undefined) {node.setAttribute("max", this.max);}
giuliomoro@0 2064 break;
giuliomoro@0 2065 case "checkbox":
giuliomoro@0 2066 case "radio":
giuliomoro@0 2067 for (var i=0; i<this.options.length; i++)
giuliomoro@0 2068 {
giuliomoro@0 2069 var option = this.options[i];
giuliomoro@0 2070 var optionNode = doc.createElement("option");
giuliomoro@0 2071 optionNode.setAttribute("name",option.name);
giuliomoro@0 2072 optionNode.textContent = option.text;
giuliomoro@0 2073 node.appendChild(optionNode);
giuliomoro@0 2074 }
giuliomoro@0 2075 break;
giuliomoro@0 2076 }
giuliomoro@0 2077 }
giuliomoro@0 2078 return node;
giuliomoro@0 2079 };
giuliomoro@0 2080 };
giuliomoro@0 2081 this.decode = function(parent,xml) {
giuliomoro@0 2082 this.location = xml.getAttribute('location');
giuliomoro@0 2083 if (this.location == 'before'){this.location = 'pre';}
giuliomoro@0 2084 else if (this.location == 'after'){this.location = 'post';}
giuliomoro@0 2085 for (var i in xml.children)
giuliomoro@0 2086 {
giuliomoro@0 2087 if(isNaN(Number(i))==true){break;}
giuliomoro@0 2088 var node = new this.OptionNode();
giuliomoro@0 2089 node.decode(parent,xml.children[i]);
giuliomoro@0 2090 this.options.push(node);
giuliomoro@0 2091 }
giuliomoro@0 2092 };
giuliomoro@0 2093 this.encode = function(doc) {
giuliomoro@0 2094 var node = doc.createElement('survey');
giuliomoro@0 2095 node.setAttribute('location',this.location);
giuliomoro@0 2096 for (var i=0; i<this.options.length; i++)
giuliomoro@0 2097 {
giuliomoro@0 2098 node.appendChild(this.options[i].exportXML(doc));
giuliomoro@0 2099 }
giuliomoro@0 2100 return node;
giuliomoro@0 2101 };
giuliomoro@0 2102 };
giuliomoro@0 2103
giuliomoro@0 2104 this.interfaceNode = function()
giuliomoro@0 2105 {
giuliomoro@0 2106 this.title = null;
giuliomoro@0 2107 this.name = null;
giuliomoro@0 2108 this.options = [];
giuliomoro@0 2109 this.scales = [];
giuliomoro@0 2110 this.schema = specification.schema.getAllElementsByName('interface')[1];
giuliomoro@0 2111
giuliomoro@0 2112 this.decode = function(parent,xml) {
giuliomoro@0 2113 this.name = xml.getAttribute('name');
giuliomoro@0 2114 var titleNode = xml.getElementsByTagName('title');
giuliomoro@0 2115 if (titleNode.length == 1)
giuliomoro@0 2116 {
giuliomoro@0 2117 this.title = titleNode[0].textContent;
giuliomoro@0 2118 }
giuliomoro@0 2119 var interfaceOptionNodes = xml.getElementsByTagName('interfaceoption');
giuliomoro@0 2120 // Extract interfaceoption node schema
giuliomoro@0 2121 var interfaceOptionNodeSchema = this.schema.getAllElementsByName('interfaceoption')[0];
giuliomoro@0 2122 var attributeMap = interfaceOptionNodeSchema.getAllElementsByTagName('xs:attribute');
giuliomoro@0 2123 for (var i=0; i<interfaceOptionNodes.length; i++)
giuliomoro@0 2124 {
giuliomoro@0 2125 var ioNode = interfaceOptionNodes[i];
giuliomoro@0 2126 var option = {};
giuliomoro@0 2127 for (var j=0; j<attributeMap.length; j++)
giuliomoro@0 2128 {
giuliomoro@0 2129 var attributeName = attributeMap[j].getAttribute('name') || attributeMap[j].getAttribute('ref');
giuliomoro@0 2130 var projectAttr = ioNode.getAttribute(attributeName);
giuliomoro@0 2131 projectAttr = parent.processAttribute(projectAttr,attributeMap[j]);
giuliomoro@0 2132 switch(typeof projectAttr)
giuliomoro@0 2133 {
giuliomoro@0 2134 case "number":
giuliomoro@0 2135 case "boolean":
giuliomoro@0 2136 eval('option.'+attributeName+' = '+projectAttr);
giuliomoro@0 2137 break;
giuliomoro@0 2138 case "string":
giuliomoro@0 2139 eval('option.'+attributeName+' = "'+projectAttr+'"');
giuliomoro@0 2140 break;
giuliomoro@0 2141 }
giuliomoro@0 2142 }
giuliomoro@0 2143 this.options.push(option);
giuliomoro@0 2144 }
giuliomoro@0 2145
giuliomoro@0 2146 // Now the scales nodes
giuliomoro@0 2147 var scaleParent = xml.getElementsByTagName('scales');
giuliomoro@0 2148 if (scaleParent.length == 1) {
giuliomoro@0 2149 scaleParent = scaleParent[0];
giuliomoro@0 2150 for (var i=0; i<scaleParent.children.length; i++) {
giuliomoro@0 2151 var child = scaleParent.children[i];
giuliomoro@0 2152 this.scales.push({
giuliomoro@0 2153 text: child.textContent,
giuliomoro@0 2154 position: Number(child.getAttribute('position'))
giuliomoro@0 2155 });
giuliomoro@0 2156 }
giuliomoro@0 2157 }
giuliomoro@0 2158 };
giuliomoro@0 2159
giuliomoro@0 2160 this.encode = function(doc) {
giuliomoro@0 2161 var node = doc.createElement("interface");
giuliomoro@0 2162 if (typeof name == "string")
giuliomoro@0 2163 node.setAttribute("name",this.name);
giuliomoro@0 2164 for (var option of this.options)
giuliomoro@0 2165 {
giuliomoro@0 2166 var child = doc.createElement("interfaceoption");
giuliomoro@0 2167 child.setAttribute("type",option.type);
giuliomoro@0 2168 child.setAttribute("name",option.name);
giuliomoro@0 2169 node.appendChild(child);
giuliomoro@0 2170 }
giuliomoro@0 2171 if (this.scales.length != 0) {
giuliomoro@0 2172 var scales = doc.createElement("scales");
giuliomoro@0 2173 for (var scale of this.scales)
giuliomoro@0 2174 {
giuliomoro@0 2175 var child = doc.createElement("scalelabel");
giuliomoro@0 2176 child.setAttribute("position",scale.position);
giuliomoro@0 2177 child.textContent = scale.text;
giuliomoro@0 2178 scales.appendChild(child);
giuliomoro@0 2179 }
giuliomoro@0 2180 node.appendChild(scales);
giuliomoro@0 2181 }
giuliomoro@0 2182 return node;
giuliomoro@0 2183 };
giuliomoro@0 2184 };
giuliomoro@0 2185
giuliomoro@0 2186 this.metricNode = function() {
giuliomoro@0 2187 this.enabled = [];
giuliomoro@0 2188 this.decode = function(parent, xml) {
giuliomoro@0 2189 var children = xml.getElementsByTagName('metricenable');
giuliomoro@0 2190 for (var i in children) {
giuliomoro@0 2191 if (isNaN(Number(i)) == true){break;}
giuliomoro@0 2192 this.enabled.push(children[i].textContent);
giuliomoro@0 2193 }
giuliomoro@0 2194 }
giuliomoro@0 2195 this.encode = function(doc) {
giuliomoro@0 2196 var node = doc.createElement('metric');
giuliomoro@0 2197 for (var i in this.enabled)
giuliomoro@0 2198 {
giuliomoro@0 2199 if (isNaN(Number(i)) == true){break;}
giuliomoro@0 2200 var child = doc.createElement('metricenable');
giuliomoro@0 2201 child.textContent = this.enabled[i];
giuliomoro@0 2202 node.appendChild(child);
giuliomoro@0 2203 }
giuliomoro@0 2204 return node;
giuliomoro@0 2205 }
giuliomoro@0 2206 }
giuliomoro@0 2207
giuliomoro@0 2208 this.page = function() {
giuliomoro@0 2209 this.presentedId = undefined;
giuliomoro@0 2210 this.id = undefined;
giuliomoro@0 2211 this.hostURL = undefined;
giuliomoro@0 2212 this.randomiseOrder = undefined;
giuliomoro@0 2213 this.loop = undefined;
giuliomoro@0 2214 this.showElementComments = undefined;
giuliomoro@0 2215 this.outsideReference = null;
giuliomoro@0 2216 this.loudness = null;
giuliomoro@0 2217 this.label = null;
giuliomoro@0 2218 this.preTest = null;
giuliomoro@0 2219 this.postTest = null;
giuliomoro@0 2220 this.interfaces = [];
giuliomoro@0 2221 this.commentBoxPrefix = "Comment on track";
giuliomoro@0 2222 this.audioElements = [];
giuliomoro@0 2223 this.commentQuestions = [];
giuliomoro@0 2224 this.schema = specification.schema.getAllElementsByName("page")[0];
giuliomoro@0 2225
giuliomoro@0 2226 this.decode = function(parent,xml)
giuliomoro@0 2227 {
giuliomoro@0 2228 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
giuliomoro@0 2229 for (var i=0; i<attributeMap.length; i++)
giuliomoro@0 2230 {
giuliomoro@0 2231 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
giuliomoro@0 2232 var projectAttr = xml.getAttribute(attributeName);
giuliomoro@0 2233 projectAttr = parent.processAttribute(projectAttr,attributeMap[i]);
giuliomoro@0 2234 switch(typeof projectAttr)
giuliomoro@0 2235 {
giuliomoro@0 2236 case "number":
giuliomoro@0 2237 case "boolean":
giuliomoro@0 2238 eval('this.'+attributeName+' = '+projectAttr);
giuliomoro@0 2239 break;
giuliomoro@0 2240 case "string":
giuliomoro@0 2241 eval('this.'+attributeName+' = "'+projectAttr+'"');
giuliomoro@0 2242 break;
giuliomoro@0 2243 }
giuliomoro@0 2244 }
giuliomoro@0 2245
giuliomoro@0 2246 // Get the Comment Box Prefix
giuliomoro@0 2247 var CBP = xml.getElementsByTagName('commentboxprefix');
giuliomoro@0 2248 if (CBP.length != 0) {
giuliomoro@0 2249 this.commentBoxPrefix = CBP[0].textContent;
giuliomoro@0 2250 }
giuliomoro@0 2251
giuliomoro@0 2252 // Now decode the interfaces
giuliomoro@0 2253 var interfaceNode = xml.getElementsByTagName('interface');
giuliomoro@0 2254 for (var i=0; i<interfaceNode.length; i++)
giuliomoro@0 2255 {
giuliomoro@0 2256 var node = new parent.interfaceNode();
giuliomoro@0 2257 node.decode(this,interfaceNode[i],parent.schema.getAllElementsByName('interface')[1]);
giuliomoro@0 2258 this.interfaces.push(node);
giuliomoro@0 2259 }
giuliomoro@0 2260
giuliomoro@0 2261 // Now process the survey node options
giuliomoro@0 2262 var survey = xml.getElementsByTagName('survey');
giuliomoro@0 2263 var surveySchema = parent.schema.getAllElementsByName('survey')[0];
giuliomoro@0 2264 for (var i in survey) {
giuliomoro@0 2265 if (isNaN(Number(i)) == true){break;}
giuliomoro@0 2266 var location = survey[i].getAttribute('location');
giuliomoro@0 2267 if (location == 'pre' || location == 'before')
giuliomoro@0 2268 {
giuliomoro@0 2269 if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
giuliomoro@0 2270 else {
giuliomoro@0 2271 this.preTest = new parent.surveyNode();
giuliomoro@0 2272 this.preTest.decode(parent,survey[i],surveySchema);
giuliomoro@0 2273 }
giuliomoro@0 2274 } else if (location == 'post' || location == 'after') {
giuliomoro@0 2275 if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
giuliomoro@0 2276 else {
giuliomoro@0 2277 this.postTest = new parent.surveyNode();
giuliomoro@0 2278 this.postTest.decode(parent,survey[i],surveySchema);
giuliomoro@0 2279 }
giuliomoro@0 2280 }
giuliomoro@0 2281 }
giuliomoro@0 2282
giuliomoro@0 2283 // Now process the audioelement tags
giuliomoro@0 2284 var audioElements = xml.getElementsByTagName('audioelement');
giuliomoro@0 2285 for (var i=0; i<audioElements.length; i++)
giuliomoro@0 2286 {
giuliomoro@0 2287 var node = new this.audioElementNode();
giuliomoro@0 2288 node.decode(this,audioElements[i]);
giuliomoro@0 2289 this.audioElements.push(node);
giuliomoro@0 2290 }
giuliomoro@0 2291
giuliomoro@0 2292 // Now decode the commentquestions
giuliomoro@0 2293 var commentQuestions = xml.getElementsByTagName('commentquestion');
giuliomoro@0 2294 for (var i=0; i<commentQuestions.length; i++)
giuliomoro@0 2295 {
giuliomoro@0 2296 var node = new this.commentQuestionNode();
giuliomoro@0 2297 node.decode(parent,commentQuestions[i]);
giuliomoro@0 2298 this.commentQuestions.push(node);
giuliomoro@0 2299 }
giuliomoro@0 2300 };
giuliomoro@0 2301
giuliomoro@0 2302 this.encode = function(root)
giuliomoro@0 2303 {
giuliomoro@0 2304 var AHNode = root.createElement("page");
giuliomoro@0 2305 // First decode the attributes
giuliomoro@0 2306 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
giuliomoro@0 2307 for (var i=0; i<attributes.length; i++)
giuliomoro@0 2308 {
giuliomoro@0 2309 var name = attributes[i].getAttribute("name");
giuliomoro@0 2310 if (name == undefined) {
giuliomoro@0 2311 name = attributes[i].getAttribute("ref");
giuliomoro@0 2312 }
giuliomoro@0 2313 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
giuliomoro@0 2314 {
giuliomoro@0 2315 eval("AHNode.setAttribute('"+name+"',this."+name+")");
giuliomoro@0 2316 }
giuliomoro@0 2317 }
giuliomoro@0 2318 if(this.loudness != null) {AHNode.setAttribute("loudness",this.loudness);}
giuliomoro@0 2319 // <commentboxprefix>
giuliomoro@0 2320 var commentboxprefix = root.createElement("commentboxprefix");
giuliomoro@0 2321 commentboxprefix.textContent = this.commentBoxPrefix;
giuliomoro@0 2322 AHNode.appendChild(commentboxprefix);
giuliomoro@0 2323
giuliomoro@0 2324 for (var i=0; i<this.interfaces.length; i++)
giuliomoro@0 2325 {
giuliomoro@0 2326 AHNode.appendChild(this.interfaces[i].encode(root));
giuliomoro@0 2327 }
giuliomoro@0 2328
giuliomoro@0 2329 for (var i=0; i<this.audioElements.length; i++) {
giuliomoro@0 2330 AHNode.appendChild(this.audioElements[i].encode(root));
giuliomoro@0 2331 }
giuliomoro@0 2332 // Create <CommentQuestion>
giuliomoro@0 2333 for (var i=0; i<this.commentQuestions.length; i++)
giuliomoro@0 2334 {
giuliomoro@0 2335 AHNode.appendChild(this.commentQuestions[i].encode(root));
giuliomoro@0 2336 }
giuliomoro@0 2337
giuliomoro@0 2338 AHNode.appendChild(this.preTest.encode(root));
giuliomoro@0 2339 AHNode.appendChild(this.postTest.encode(root));
giuliomoro@0 2340 return AHNode;
giuliomoro@0 2341 };
giuliomoro@0 2342
giuliomoro@0 2343 this.commentQuestionNode = function() {
giuliomoro@0 2344 this.id = null;
giuliomoro@0 2345 this.name = undefined;
giuliomoro@0 2346 this.type = undefined;
giuliomoro@0 2347 this.options = [];
giuliomoro@0 2348 this.statement = undefined;
giuliomoro@0 2349 this.schema = specification.schema.getAllElementsByName('commentquestion')[0];
giuliomoro@0 2350 this.decode = function(parent,xml)
giuliomoro@0 2351 {
giuliomoro@0 2352 this.id = xml.id;
giuliomoro@0 2353 this.name = xml.getAttribute('name');
giuliomoro@0 2354 this.type = xml.getAttribute('type');
giuliomoro@0 2355 this.statement = xml.getElementsByTagName('statement')[0].textContent;
giuliomoro@0 2356 var optNodes = xml.getElementsByTagName('option');
giuliomoro@0 2357 for (var i=0; i<optNodes.length; i++)
giuliomoro@0 2358 {
giuliomoro@0 2359 var optNode = optNodes[i];
giuliomoro@0 2360 this.options.push({
giuliomoro@0 2361 name: optNode.getAttribute('name'),
giuliomoro@0 2362 text: optNode.textContent
giuliomoro@0 2363 });
giuliomoro@0 2364 }
giuliomoro@0 2365 };
giuliomoro@0 2366
giuliomoro@0 2367 this.encode = function(root)
giuliomoro@0 2368 {
giuliomoro@0 2369 var node = root.createElement("commentquestion");
giuliomoro@0 2370 node.id = this.id;
giuliomoro@0 2371 node.setAttribute("type",this.type);
giuliomoro@0 2372 if (this.name != undefined){node.setAttribute("name",this.name);}
giuliomoro@0 2373 var statement = root.createElement("statement");
giuliomoro@0 2374 statement.textContent = this.statement;
giuliomoro@0 2375 node.appendChild(statement);
giuliomoro@0 2376 for (var option of this.options)
giuliomoro@0 2377 {
giuliomoro@0 2378 var child = root.createElement("option");
giuliomoro@0 2379 child.setAttribute("name",option.name);
giuliomoro@0 2380 child.textContent = option.text;
giuliomoro@0 2381 node.appendChild(child);
giuliomoro@0 2382 }
giuliomoro@0 2383 return node;
giuliomoro@0 2384 };
giuliomoro@0 2385 };
giuliomoro@0 2386
giuliomoro@0 2387 this.audioElementNode = function() {
giuliomoro@0 2388 this.url = null;
giuliomoro@0 2389 this.id = null;
giuliomoro@0 2390 this.name = null;
giuliomoro@0 2391 this.parent = null;
giuliomoro@0 2392 this.type = null;
giuliomoro@0 2393 this.marker = null;
giuliomoro@0 2394 this.enforce = false;
giuliomoro@0 2395 this.gain = 0.0;
giuliomoro@0 2396 this.schema = specification.schema.getAllElementsByName('audioelement')[0];;
giuliomoro@0 2397 this.parent = null;
giuliomoro@0 2398 this.decode = function(parent,xml)
giuliomoro@0 2399 {
giuliomoro@0 2400 this.parent = parent;
giuliomoro@0 2401 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
giuliomoro@0 2402 for (var i=0; i<attributeMap.length; i++)
giuliomoro@0 2403 {
giuliomoro@0 2404 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
giuliomoro@0 2405 var projectAttr = xml.getAttribute(attributeName);
giuliomoro@0 2406 projectAttr = specification.processAttribute(projectAttr,attributeMap[i]);
giuliomoro@0 2407 switch(typeof projectAttr)
giuliomoro@0 2408 {
giuliomoro@0 2409 case "number":
giuliomoro@0 2410 case "boolean":
giuliomoro@0 2411 eval('this.'+attributeName+' = '+projectAttr);
giuliomoro@0 2412 break;
giuliomoro@0 2413 case "string":
giuliomoro@0 2414 eval('this.'+attributeName+' = "'+projectAttr+'"');
giuliomoro@0 2415 break;
giuliomoro@0 2416 }
giuliomoro@0 2417 }
giuliomoro@0 2418
giuliomoro@0 2419 };
giuliomoro@0 2420 this.encode = function(root)
giuliomoro@0 2421 {
giuliomoro@0 2422 var AENode = root.createElement("audioelement");
giuliomoro@0 2423 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
giuliomoro@0 2424 for (var i=0; i<attributes.length; i++)
giuliomoro@0 2425 {
giuliomoro@0 2426 var name = attributes[i].getAttribute("name");
giuliomoro@0 2427 if (name == undefined) {
giuliomoro@0 2428 name = attributes[i].getAttribute("ref");
giuliomoro@0 2429 }
giuliomoro@0 2430 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
giuliomoro@0 2431 {
giuliomoro@0 2432 eval("AENode.setAttribute('"+name+"',this."+name+")");
giuliomoro@0 2433 }
giuliomoro@0 2434 }
giuliomoro@0 2435 return AENode;
giuliomoro@0 2436 };
giuliomoro@0 2437 };
giuliomoro@0 2438 };
giuliomoro@0 2439 }
giuliomoro@0 2440
giuliomoro@0 2441 function Interface(specificationObject) {
giuliomoro@0 2442 // This handles the bindings between the interface and the audioEngineContext;
giuliomoro@0 2443 this.specification = specificationObject;
giuliomoro@0 2444 this.insertPoint = document.getElementById("topLevelBody");
giuliomoro@0 2445
giuliomoro@0 2446 this.newPage = function(audioHolderObject,store)
giuliomoro@0 2447 {
giuliomoro@0 2448 audioEngineContext.newTestPage(audioHolderObject,store);
giuliomoro@0 2449 interfaceContext.commentBoxes.deleteCommentBoxes();
giuliomoro@0 2450 interfaceContext.deleteCommentQuestions();
giuliomoro@0 2451 loadTest(audioHolderObject,store);
giuliomoro@0 2452 if(audioHolderObject.hidden === true){
giuliomoro@0 2453 // work-around to have zero pages: set only one page with the attribute hidden=true and
giuliomoro@0 2454 // it will automatically skip over.
giuliomoro@0 2455 testState.advanceState();
giuliomoro@0 2456 }
giuliomoro@0 2457 };
giuliomoro@0 2458
giuliomoro@0 2459 // Bounded by interface!!
giuliomoro@0 2460 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels
giuliomoro@0 2461 // For example, APE returns the slider position normalised in a <value> tag.
giuliomoro@0 2462 this.interfaceObjects = [];
giuliomoro@0 2463 this.interfaceObject = function(){};
giuliomoro@0 2464
giuliomoro@0 2465 this.resizeWindow = function(event)
giuliomoro@0 2466 {
giuliomoro@0 2467 popup.resize(event);
giuliomoro@0 2468 for(var i=0; i<this.commentBoxes.length; i++)
giuliomoro@0 2469 {this.commentBoxes[i].resize();}
giuliomoro@0 2470 for(var i=0; i<this.commentQuestions.length; i++)
giuliomoro@0 2471 {this.commentQuestions[i].resize();}
giuliomoro@0 2472 try
giuliomoro@0 2473 {
giuliomoro@0 2474 resizeWindow(event);
giuliomoro@0 2475 }
giuliomoro@0 2476 catch(err)
giuliomoro@0 2477 {
giuliomoro@0 2478 console.log("Warning - Interface does not have Resize option");
giuliomoro@0 2479 console.log(err);
giuliomoro@0 2480 }
giuliomoro@0 2481 };
giuliomoro@0 2482
giuliomoro@0 2483 this.returnNavigator = function()
giuliomoro@0 2484 {
giuliomoro@0 2485 var node = storage.document.createElement("navigator");
giuliomoro@0 2486 var platform = storage.document.createElement("platform");
giuliomoro@0 2487 platform.textContent = navigator.platform;
giuliomoro@0 2488 var vendor = storage.document.createElement("vendor");
giuliomoro@0 2489 vendor.textContent = navigator.vendor;
giuliomoro@0 2490 var userAgent = storage.document.createElement("uagent");
giuliomoro@0 2491 userAgent.textContent = navigator.userAgent;
giuliomoro@0 2492 var screen = storage.document.createElement("window");
giuliomoro@0 2493 screen.setAttribute('innerWidth',window.innerWidth);
giuliomoro@0 2494 screen.setAttribute('innerHeight',window.innerHeight);
giuliomoro@0 2495 node.appendChild(platform);
giuliomoro@0 2496 node.appendChild(vendor);
giuliomoro@0 2497 node.appendChild(userAgent);
giuliomoro@0 2498 node.appendChild(screen);
giuliomoro@0 2499 return node;
giuliomoro@0 2500 };
giuliomoro@0 2501
giuliomoro@0 2502 this.commentBoxes = new function() {
giuliomoro@0 2503 this.boxes = [];
giuliomoro@0 2504 this.injectPoint = null;
giuliomoro@0 2505 this.elementCommentBox = function(audioObject) {
giuliomoro@0 2506 var element = audioObject.specification;
giuliomoro@0 2507 this.audioObject = audioObject;
giuliomoro@0 2508 this.id = audioObject.id;
giuliomoro@0 2509 var audioHolderObject = audioObject.specification.parent;
giuliomoro@0 2510 // Create document objects to hold the comment boxes
giuliomoro@0 2511 this.trackComment = document.createElement('div');
giuliomoro@0 2512 this.trackComment.className = 'comment-div';
giuliomoro@0 2513 this.trackComment.id = 'comment-div-'+audioObject.id;
giuliomoro@0 2514 // Create a string next to each comment asking for a comment
giuliomoro@0 2515 this.trackString = document.createElement('span');
giuliomoro@0 2516 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix+' '+audioObject.interfaceDOM.getPresentedId();
giuliomoro@0 2517 // Create the HTML5 comment box 'textarea'
giuliomoro@0 2518 this.trackCommentBox = document.createElement('textarea');
giuliomoro@0 2519 this.trackCommentBox.rows = '4';
giuliomoro@0 2520 this.trackCommentBox.cols = '100';
giuliomoro@0 2521 this.trackCommentBox.name = 'trackComment'+audioObject.id;
giuliomoro@0 2522 this.trackCommentBox.className = 'trackComment';
giuliomoro@0 2523 var br = document.createElement('br');
giuliomoro@0 2524 // Add to the holder.
giuliomoro@0 2525 this.trackComment.appendChild(this.trackString);
giuliomoro@0 2526 this.trackComment.appendChild(br);
giuliomoro@0 2527 this.trackComment.appendChild(this.trackCommentBox);
giuliomoro@0 2528
giuliomoro@0 2529 this.exportXMLDOM = function() {
giuliomoro@0 2530 var root = document.createElement('comment');
giuliomoro@0 2531 var question = document.createElement('question');
giuliomoro@0 2532 question.textContent = this.trackString.textContent;
giuliomoro@0 2533 var response = document.createElement('response');
giuliomoro@0 2534 response.textContent = this.trackCommentBox.value;
giuliomoro@0 2535 console.log("Comment frag-"+this.id+": "+response.textContent);
giuliomoro@0 2536 root.appendChild(question);
giuliomoro@0 2537 root.appendChild(response);
giuliomoro@0 2538 return root;
giuliomoro@0 2539 };
giuliomoro@0 2540 this.resize = function()
giuliomoro@0 2541 {
giuliomoro@0 2542 var boxwidth = (window.innerWidth-100)/2;
giuliomoro@0 2543 if (boxwidth >= 600)
giuliomoro@0 2544 {
giuliomoro@0 2545 boxwidth = 600;
giuliomoro@0 2546 }
giuliomoro@0 2547 else if (boxwidth < 400)
giuliomoro@0 2548 {
giuliomoro@0 2549 boxwidth = 400;
giuliomoro@0 2550 }
giuliomoro@0 2551 this.trackComment.style.width = boxwidth+"px";
giuliomoro@0 2552 this.trackCommentBox.style.width = boxwidth-6+"px";
giuliomoro@0 2553 };
giuliomoro@0 2554 this.resize();
giuliomoro@0 2555 };
giuliomoro@0 2556 this.createCommentBox = function(audioObject) {
giuliomoro@0 2557 var node = new this.elementCommentBox(audioObject);
giuliomoro@0 2558 this.boxes.push(node);
giuliomoro@0 2559 audioObject.commentDOM = node;
giuliomoro@0 2560 return node;
giuliomoro@0 2561 };
giuliomoro@0 2562 this.sortCommentBoxes = function() {
giuliomoro@0 2563 this.boxes.sort(function(a,b){return a.id - b.id;});
giuliomoro@0 2564 };
giuliomoro@0 2565
giuliomoro@0 2566 this.showCommentBoxes = function(inject, sort) {
giuliomoro@0 2567 this.injectPoint = inject;
giuliomoro@0 2568 if (sort) {this.sortCommentBoxes();}
giuliomoro@0 2569 for (var box of this.boxes) {
giuliomoro@0 2570 inject.appendChild(box.trackComment);
giuliomoro@0 2571 }
giuliomoro@0 2572 };
giuliomoro@0 2573
giuliomoro@0 2574 this.deleteCommentBoxes = function() {
giuliomoro@0 2575 if (this.injectPoint != null) {
giuliomoro@0 2576 for (var box of this.boxes) {
giuliomoro@0 2577 this.injectPoint.removeChild(box.trackComment);
giuliomoro@0 2578 }
giuliomoro@0 2579 this.injectPoint = null;
giuliomoro@0 2580 }
giuliomoro@0 2581 this.boxes = [];
giuliomoro@0 2582 };
giuliomoro@0 2583 }
giuliomoro@0 2584
giuliomoro@0 2585 this.commentQuestions = [];
giuliomoro@0 2586
giuliomoro@0 2587 this.commentBox = function(commentQuestion) {
giuliomoro@0 2588 this.specification = commentQuestion;
giuliomoro@0 2589 // Create document objects to hold the comment boxes
giuliomoro@0 2590 this.holder = document.createElement('div');
giuliomoro@0 2591 this.holder.className = 'comment-div';
giuliomoro@0 2592 // Create a string next to each comment asking for a comment
giuliomoro@0 2593 this.string = document.createElement('span');
giuliomoro@0 2594 this.string.innerHTML = commentQuestion.statement;
giuliomoro@0 2595 // Create the HTML5 comment box 'textarea'
giuliomoro@0 2596 this.textArea = document.createElement('textarea');
giuliomoro@0 2597 this.textArea.rows = '4';
giuliomoro@0 2598 this.textArea.cols = '100';
giuliomoro@0 2599 this.textArea.className = 'trackComment';
giuliomoro@0 2600 var br = document.createElement('br');
giuliomoro@0 2601 // Add to the holder.
giuliomoro@0 2602 this.holder.appendChild(this.string);
giuliomoro@0 2603 this.holder.appendChild(br);
giuliomoro@0 2604 this.holder.appendChild(this.textArea);
giuliomoro@0 2605
giuliomoro@0 2606 this.exportXMLDOM = function(storePoint) {
giuliomoro@0 2607 var root = storePoint.parent.document.createElement('comment');
giuliomoro@0 2608 root.id = this.specification.id;
giuliomoro@0 2609 root.setAttribute('type',this.specification.type);
giuliomoro@0 2610 console.log("Question: "+this.string.textContent);
giuliomoro@0 2611 console.log("Response: "+root.textContent);
giuliomoro@0 2612 var question = storePoint.parent.document.createElement('question');
giuliomoro@0 2613 question.textContent = this.string.textContent;
giuliomoro@0 2614 var response = storePoint.parent.document.createElement('response');
giuliomoro@0 2615 response.textContent = this.textArea.value;
giuliomoro@0 2616 root.appendChild(question);
giuliomoro@0 2617 root.appendChild(response);
giuliomoro@0 2618 storePoint.XMLDOM.appendChild(root);
giuliomoro@0 2619 return root;
giuliomoro@0 2620 };
giuliomoro@0 2621 this.resize = function()
giuliomoro@0 2622 {
giuliomoro@0 2623 var boxwidth = (window.innerWidth-100)/2;
giuliomoro@0 2624 if (boxwidth >= 600)
giuliomoro@0 2625 {
giuliomoro@0 2626 boxwidth = 600;
giuliomoro@0 2627 }
giuliomoro@0 2628 else if (boxwidth < 400)
giuliomoro@0 2629 {
giuliomoro@0 2630 boxwidth = 400;
giuliomoro@0 2631 }
giuliomoro@0 2632 this.holder.style.width = boxwidth+"px";
giuliomoro@0 2633 this.textArea.style.width = boxwidth-6+"px";
giuliomoro@0 2634 };
giuliomoro@0 2635 this.resize();
giuliomoro@0 2636 };
giuliomoro@0 2637
giuliomoro@0 2638 this.radioBox = function(commentQuestion) {
giuliomoro@0 2639 this.specification = commentQuestion;
giuliomoro@0 2640 // Create document objects to hold the comment boxes
giuliomoro@0 2641 this.holder = document.createElement('div');
giuliomoro@0 2642 this.holder.className = 'comment-div';
giuliomoro@0 2643 // Create a string next to each comment asking for a comment
giuliomoro@0 2644 this.string = document.createElement('span');
giuliomoro@0 2645 this.string.innerHTML = commentQuestion.statement;
giuliomoro@0 2646 var br = document.createElement('br');
giuliomoro@0 2647 // Add to the holder.
giuliomoro@0 2648 this.holder.appendChild(this.string);
giuliomoro@0 2649 this.holder.appendChild(br);
giuliomoro@0 2650 this.options = [];
giuliomoro@0 2651 this.inputs = document.createElement('div');
giuliomoro@0 2652 this.span = document.createElement('div');
giuliomoro@0 2653 this.inputs.align = 'center';
giuliomoro@0 2654 this.inputs.style.marginLeft = '12px';
giuliomoro@0 2655 this.span.style.marginLeft = '12px';
giuliomoro@0 2656 this.span.align = 'center';
giuliomoro@0 2657 this.span.style.marginTop = '15px';
giuliomoro@0 2658
giuliomoro@0 2659 var optCount = commentQuestion.options.length;
giuliomoro@0 2660 for (var optNode of commentQuestion.options)
giuliomoro@0 2661 {
giuliomoro@0 2662 var div = document.createElement('div');
giuliomoro@0 2663 div.style.width = '80px';
giuliomoro@0 2664 div.style.float = 'left';
giuliomoro@0 2665 var input = document.createElement('input');
giuliomoro@0 2666 input.type = 'radio';
giuliomoro@0 2667 input.name = commentQuestion.id;
giuliomoro@0 2668 input.setAttribute('setvalue',optNode.name);
giuliomoro@0 2669 input.className = 'comment-radio';
giuliomoro@0 2670 div.appendChild(input);
giuliomoro@0 2671 this.inputs.appendChild(div);
giuliomoro@0 2672
giuliomoro@0 2673
giuliomoro@0 2674 div = document.createElement('div');
giuliomoro@0 2675 div.style.width = '80px';
giuliomoro@0 2676 div.style.float = 'left';
giuliomoro@0 2677 div.align = 'center';
giuliomoro@0 2678 var span = document.createElement('span');
giuliomoro@0 2679 span.textContent = optNode.text;
giuliomoro@0 2680 span.className = 'comment-radio-span';
giuliomoro@0 2681 div.appendChild(span);
giuliomoro@0 2682 this.span.appendChild(div);
giuliomoro@0 2683 this.options.push(input);
giuliomoro@0 2684 }
giuliomoro@0 2685 this.holder.appendChild(this.span);
giuliomoro@0 2686 this.holder.appendChild(this.inputs);
giuliomoro@0 2687
giuliomoro@0 2688 this.exportXMLDOM = function(storePoint) {
giuliomoro@0 2689 var root = storePoint.parent.document.createElement('comment');
giuliomoro@0 2690 root.id = this.specification.id;
giuliomoro@0 2691 root.setAttribute('type',this.specification.type);
giuliomoro@0 2692 var question = document.createElement('question');
giuliomoro@0 2693 question.textContent = this.string.textContent;
giuliomoro@0 2694 var response = document.createElement('response');
giuliomoro@0 2695 var i=0;
giuliomoro@0 2696 while(this.options[i].checked == false) {
giuliomoro@0 2697 i++;
giuliomoro@0 2698 if (i >= this.options.length) {
giuliomoro@0 2699 break;
giuliomoro@0 2700 }
giuliomoro@0 2701 }
giuliomoro@0 2702 if (i >= this.options.length) {
giuliomoro@0 2703 response.textContent = 'null';
giuliomoro@0 2704 } else {
giuliomoro@0 2705 response.textContent = this.options[i].getAttribute('setvalue');
giuliomoro@0 2706 response.setAttribute('number',i);
giuliomoro@0 2707 }
giuliomoro@0 2708 console.log('Comment: '+question.textContent);
giuliomoro@0 2709 console.log('Response: '+response.textContent);
giuliomoro@0 2710 root.appendChild(question);
giuliomoro@0 2711 root.appendChild(response);
giuliomoro@0 2712 storePoint.XMLDOM.appendChild(root);
giuliomoro@0 2713 return root;
giuliomoro@0 2714 };
giuliomoro@0 2715 this.resize = function()
giuliomoro@0 2716 {
giuliomoro@0 2717 var boxwidth = (window.innerWidth-100)/2;
giuliomoro@0 2718 if (boxwidth >= 600)
giuliomoro@0 2719 {
giuliomoro@0 2720 boxwidth = 600;
giuliomoro@0 2721 }
giuliomoro@0 2722 else if (boxwidth < 400)
giuliomoro@0 2723 {
giuliomoro@0 2724 boxwidth = 400;
giuliomoro@0 2725 }
giuliomoro@0 2726 this.holder.style.width = boxwidth+"px";
giuliomoro@0 2727 var text = this.holder.children[2];
giuliomoro@0 2728 var options = this.holder.children[3];
giuliomoro@0 2729 var optCount = options.children.length;
giuliomoro@0 2730 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
giuliomoro@0 2731 var options = options.firstChild;
giuliomoro@0 2732 var text = text.firstChild;
giuliomoro@0 2733 options.style.marginRight = spanMargin;
giuliomoro@0 2734 options.style.marginLeft = spanMargin;
giuliomoro@0 2735 text.style.marginRight = spanMargin;
giuliomoro@0 2736 text.style.marginLeft = spanMargin;
giuliomoro@0 2737 while(options.nextSibling != undefined)
giuliomoro@0 2738 {
giuliomoro@0 2739 options = options.nextSibling;
giuliomoro@0 2740 text = text.nextSibling;
giuliomoro@0 2741 options.style.marginRight = spanMargin;
giuliomoro@0 2742 options.style.marginLeft = spanMargin;
giuliomoro@0 2743 text.style.marginRight = spanMargin;
giuliomoro@0 2744 text.style.marginLeft = spanMargin;
giuliomoro@0 2745 }
giuliomoro@0 2746 };
giuliomoro@0 2747 this.resize();
giuliomoro@0 2748 };
giuliomoro@0 2749
giuliomoro@0 2750 this.checkboxBox = function(commentQuestion) {
giuliomoro@0 2751 this.specification = commentQuestion;
giuliomoro@0 2752 // Create document objects to hold the comment boxes
giuliomoro@0 2753 this.holder = document.createElement('div');
giuliomoro@0 2754 this.holder.className = 'comment-div';
giuliomoro@0 2755 // Create a string next to each comment asking for a comment
giuliomoro@0 2756 this.string = document.createElement('span');
giuliomoro@0 2757 this.string.innerHTML = commentQuestion.statement;
giuliomoro@0 2758 var br = document.createElement('br');
giuliomoro@0 2759 // Add to the holder.
giuliomoro@0 2760 this.holder.appendChild(this.string);
giuliomoro@0 2761 this.holder.appendChild(br);
giuliomoro@0 2762 this.options = [];
giuliomoro@0 2763 this.inputs = document.createElement('div');
giuliomoro@0 2764 this.span = document.createElement('div');
giuliomoro@0 2765 this.inputs.align = 'center';
giuliomoro@0 2766 this.inputs.style.marginLeft = '12px';
giuliomoro@0 2767 this.span.style.marginLeft = '12px';
giuliomoro@0 2768 this.span.align = 'center';
giuliomoro@0 2769 this.span.style.marginTop = '15px';
giuliomoro@0 2770
giuliomoro@0 2771 var optCount = commentQuestion.options.length;
giuliomoro@0 2772 for (var i=0; i<optCount; i++)
giuliomoro@0 2773 {
giuliomoro@0 2774 var div = document.createElement('div');
giuliomoro@0 2775 div.style.width = '80px';
giuliomoro@0 2776 div.style.float = 'left';
giuliomoro@0 2777 var input = document.createElement('input');
giuliomoro@0 2778 input.type = 'checkbox';
giuliomoro@0 2779 input.name = commentQuestion.id;
giuliomoro@0 2780 input.setAttribute('setvalue',commentQuestion.options[i].name);
giuliomoro@0 2781 input.className = 'comment-radio';
giuliomoro@0 2782 div.appendChild(input);
giuliomoro@0 2783 this.inputs.appendChild(div);
giuliomoro@0 2784
giuliomoro@0 2785
giuliomoro@0 2786 div = document.createElement('div');
giuliomoro@0 2787 div.style.width = '80px';
giuliomoro@0 2788 div.style.float = 'left';
giuliomoro@0 2789 div.align = 'center';
giuliomoro@0 2790 var span = document.createElement('span');
giuliomoro@0 2791 span.textContent = commentQuestion.options[i].text;
giuliomoro@0 2792 span.className = 'comment-radio-span';
giuliomoro@0 2793 div.appendChild(span);
giuliomoro@0 2794 this.span.appendChild(div);
giuliomoro@0 2795 this.options.push(input);
giuliomoro@0 2796 }
giuliomoro@0 2797 this.holder.appendChild(this.span);
giuliomoro@0 2798 this.holder.appendChild(this.inputs);
giuliomoro@0 2799
giuliomoro@0 2800 this.exportXMLDOM = function(storePoint) {
giuliomoro@0 2801 var root = storePoint.parent.document.createElement('comment');
giuliomoro@0 2802 root.id = this.specification.id;
giuliomoro@0 2803 root.setAttribute('type',this.specification.type);
giuliomoro@0 2804 var question = document.createElement('question');
giuliomoro@0 2805 question.textContent = this.string.textContent;
giuliomoro@0 2806 root.appendChild(question);
giuliomoro@0 2807 console.log('Comment: '+question.textContent);
giuliomoro@0 2808 for (var i=0; i<this.options.length; i++) {
giuliomoro@0 2809 var response = document.createElement('response');
giuliomoro@0 2810 response.textContent = this.options[i].checked;
giuliomoro@0 2811 response.setAttribute('name',this.options[i].getAttribute('setvalue'));
giuliomoro@0 2812 root.appendChild(response);
giuliomoro@0 2813 console.log('Response '+response.getAttribute('name') +': '+response.textContent);
giuliomoro@0 2814 }
giuliomoro@0 2815 storePoint.XMLDOM.appendChild(root);
giuliomoro@0 2816 return root;
giuliomoro@0 2817 };
giuliomoro@0 2818 this.resize = function()
giuliomoro@0 2819 {
giuliomoro@0 2820 var boxwidth = (window.innerWidth-100)/2;
giuliomoro@0 2821 if (boxwidth >= 600)
giuliomoro@0 2822 {
giuliomoro@0 2823 boxwidth = 600;
giuliomoro@0 2824 }
giuliomoro@0 2825 else if (boxwidth < 400)
giuliomoro@0 2826 {
giuliomoro@0 2827 boxwidth = 400;
giuliomoro@0 2828 }
giuliomoro@0 2829 this.holder.style.width = boxwidth+"px";
giuliomoro@0 2830 var text = this.holder.children[2];
giuliomoro@0 2831 var options = this.holder.children[3];
giuliomoro@0 2832 var optCount = options.children.length;
giuliomoro@0 2833 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
giuliomoro@0 2834 var options = options.firstChild;
giuliomoro@0 2835 var text = text.firstChild;
giuliomoro@0 2836 options.style.marginRight = spanMargin;
giuliomoro@0 2837 options.style.marginLeft = spanMargin;
giuliomoro@0 2838 text.style.marginRight = spanMargin;
giuliomoro@0 2839 text.style.marginLeft = spanMargin;
giuliomoro@0 2840 while(options.nextSibling != undefined)
giuliomoro@0 2841 {
giuliomoro@0 2842 options = options.nextSibling;
giuliomoro@0 2843 text = text.nextSibling;
giuliomoro@0 2844 options.style.marginRight = spanMargin;
giuliomoro@0 2845 options.style.marginLeft = spanMargin;
giuliomoro@0 2846 text.style.marginRight = spanMargin;
giuliomoro@0 2847 text.style.marginLeft = spanMargin;
giuliomoro@0 2848 }
giuliomoro@0 2849 };
giuliomoro@0 2850 this.resize();
giuliomoro@0 2851 };
giuliomoro@0 2852
giuliomoro@0 2853 this.createCommentQuestion = function(element) {
giuliomoro@0 2854 var node;
giuliomoro@0 2855 if (element.type == 'question') {
giuliomoro@0 2856 node = new this.commentBox(element);
giuliomoro@0 2857 } else if (element.type == 'radio') {
giuliomoro@0 2858 node = new this.radioBox(element);
giuliomoro@0 2859 } else if (element.type == 'checkbox') {
giuliomoro@0 2860 node = new this.checkboxBox(element);
giuliomoro@0 2861 }
giuliomoro@0 2862 this.commentQuestions.push(node);
giuliomoro@0 2863 return node;
giuliomoro@0 2864 };
giuliomoro@0 2865
giuliomoro@0 2866 this.deleteCommentQuestions = function()
giuliomoro@0 2867 {
giuliomoro@0 2868 this.commentQuestions = [];
giuliomoro@0 2869 };
giuliomoro@0 2870
giuliomoro@0 2871 this.playhead = new function()
giuliomoro@0 2872 {
giuliomoro@0 2873 this.object = document.createElement('div');
giuliomoro@0 2874 this.object.className = 'playhead';
giuliomoro@0 2875 this.object.align = 'left';
giuliomoro@0 2876 var curTime = document.createElement('div');
giuliomoro@0 2877 curTime.style.width = '50px';
giuliomoro@0 2878 this.curTimeSpan = document.createElement('span');
giuliomoro@0 2879 this.curTimeSpan.textContent = '00:00';
giuliomoro@0 2880 curTime.appendChild(this.curTimeSpan);
giuliomoro@0 2881 this.object.appendChild(curTime);
giuliomoro@0 2882 this.scrubberTrack = document.createElement('div');
giuliomoro@0 2883 this.scrubberTrack.className = 'playhead-scrub-track';
giuliomoro@0 2884
giuliomoro@0 2885 this.scrubberHead = document.createElement('div');
giuliomoro@0 2886 this.scrubberHead.id = 'playhead-scrubber';
giuliomoro@0 2887 this.scrubberTrack.appendChild(this.scrubberHead);
giuliomoro@0 2888 this.object.appendChild(this.scrubberTrack);
giuliomoro@0 2889
giuliomoro@0 2890 this.timePerPixel = 0;
giuliomoro@0 2891 this.maxTime = 0;
giuliomoro@0 2892
giuliomoro@0 2893 this.playbackObject;
giuliomoro@0 2894
giuliomoro@0 2895 this.setTimePerPixel = function(audioObject) {
giuliomoro@0 2896 //maxTime must be in seconds
giuliomoro@0 2897 this.playbackObject = audioObject;
giuliomoro@0 2898 this.maxTime = audioObject.buffer.buffer.duration;
giuliomoro@0 2899 var width = 490; //500 - 10, 5 each side of the tracker head
giuliomoro@0 2900 this.timePerPixel = this.maxTime/490;
giuliomoro@0 2901 if (this.maxTime < 60) {
giuliomoro@0 2902 this.curTimeSpan.textContent = '0.00';
giuliomoro@0 2903 } else {
giuliomoro@0 2904 this.curTimeSpan.textContent = '00:00';
giuliomoro@0 2905 }
giuliomoro@0 2906 };
giuliomoro@0 2907
giuliomoro@0 2908 this.update = function() {
giuliomoro@0 2909 // Update the playhead position, startPlay must be called
giuliomoro@0 2910 if (this.timePerPixel > 0) {
giuliomoro@0 2911 var time = this.playbackObject.getCurrentPosition();
giuliomoro@0 2912 if (time > 0 && time < this.maxTime) {
giuliomoro@0 2913 var width = 490;
giuliomoro@0 2914 var pix = Math.floor(time/this.timePerPixel);
giuliomoro@0 2915 this.scrubberHead.style.left = pix+'px';
giuliomoro@0 2916 if (this.maxTime > 60.0) {
giuliomoro@0 2917 var secs = time%60;
giuliomoro@0 2918 var mins = Math.floor((time-secs)/60);
giuliomoro@0 2919 secs = secs.toString();
giuliomoro@0 2920 secs = secs.substr(0,2);
giuliomoro@0 2921 mins = mins.toString();
giuliomoro@0 2922 this.curTimeSpan.textContent = mins+':'+secs;
giuliomoro@0 2923 } else {
giuliomoro@0 2924 time = time.toString();
giuliomoro@0 2925 this.curTimeSpan.textContent = time.substr(0,4);
giuliomoro@0 2926 }
giuliomoro@0 2927 } else {
giuliomoro@0 2928 this.scrubberHead.style.left = '0px';
giuliomoro@0 2929 if (this.maxTime < 60) {
giuliomoro@0 2930 this.curTimeSpan.textContent = '0.00';
giuliomoro@0 2931 } else {
giuliomoro@0 2932 this.curTimeSpan.textContent = '00:00';
giuliomoro@0 2933 }
giuliomoro@0 2934 }
giuliomoro@0 2935 }
giuliomoro@0 2936 };
giuliomoro@0 2937
giuliomoro@0 2938 this.interval = undefined;
giuliomoro@0 2939
giuliomoro@0 2940 this.start = function() {
giuliomoro@0 2941 if (this.playbackObject != undefined && this.interval == undefined) {
giuliomoro@0 2942 if (this.maxTime < 60) {
giuliomoro@0 2943 this.interval = setInterval(function(){interfaceContext.playhead.update();},10);
giuliomoro@0 2944 } else {
giuliomoro@0 2945 this.interval = setInterval(function(){interfaceContext.playhead.update();},100);
giuliomoro@0 2946 }
giuliomoro@0 2947 }
giuliomoro@0 2948 };
giuliomoro@0 2949 this.stop = function() {
giuliomoro@0 2950 clearInterval(this.interval);
giuliomoro@0 2951 this.interval = undefined;
giuliomoro@0 2952 this.scrubberHead.style.left = '0px';
giuliomoro@0 2953 if (this.maxTime < 60) {
giuliomoro@0 2954 this.curTimeSpan.textContent = '0.00';
giuliomoro@0 2955 } else {
giuliomoro@0 2956 this.curTimeSpan.textContent = '00:00';
giuliomoro@0 2957 }
giuliomoro@0 2958 };
giuliomoro@0 2959 };
giuliomoro@0 2960
giuliomoro@0 2961 this.volume = new function()
giuliomoro@0 2962 {
giuliomoro@0 2963 // An in-built volume module which can be viewed on page
giuliomoro@0 2964 // Includes trackers on page-by-page data
giuliomoro@0 2965 // Volume does NOT reset to 0dB on each page load
giuliomoro@0 2966 this.valueLin = 1.0;
giuliomoro@0 2967 this.valueDB = 0.0;
giuliomoro@0 2968 this.object = document.createElement('div');
giuliomoro@0 2969 this.object.id = 'master-volume-holder';
giuliomoro@0 2970 this.slider = document.createElement('input');
giuliomoro@0 2971 this.slider.id = 'master-volume-control';
giuliomoro@0 2972 this.slider.type = 'range';
giuliomoro@0 2973 this.valueText = document.createElement('span');
giuliomoro@0 2974 this.valueText.id = 'master-volume-feedback';
giuliomoro@0 2975 this.valueText.textContent = '0dB';
giuliomoro@0 2976
giuliomoro@0 2977 this.slider.min = -60;
giuliomoro@0 2978 this.slider.max = 12;
giuliomoro@0 2979 this.slider.value = 0;
giuliomoro@0 2980 this.slider.step = 1;
giuliomoro@0 2981 this.slider.onmousemove = function(event)
giuliomoro@0 2982 {
giuliomoro@0 2983 interfaceContext.volume.valueDB = event.currentTarget.value;
giuliomoro@0 2984 interfaceContext.volume.valueLin = decibelToLinear(interfaceContext.volume.valueDB);
giuliomoro@0 2985 interfaceContext.volume.valueText.textContent = interfaceContext.volume.valueDB+'dB';
giuliomoro@0 2986 audioEngineContext.outputGain.gain.value = interfaceContext.volume.valueLin;
giuliomoro@0 2987 }
giuliomoro@0 2988 this.slider.onmouseup = function(event)
giuliomoro@0 2989 {
giuliomoro@0 2990 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
giuliomoro@0 2991 if (storePoint.length == 0)
giuliomoro@0 2992 {
giuliomoro@0 2993 storePoint = storage.document.createElement('metricresult');
giuliomoro@0 2994 storePoint.setAttribute('name','volumeTracker');
giuliomoro@0 2995 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
giuliomoro@0 2996 }
giuliomoro@0 2997 else {
giuliomoro@0 2998 storePoint = storePoint[0];
giuliomoro@0 2999 }
giuliomoro@0 3000 var node = storage.document.createElement('movement');
giuliomoro@0 3001 node.setAttribute('test-time',audioEngineContext.timer.getTestTime());
giuliomoro@0 3002 node.setAttribute('volume',interfaceContext.volume.valueDB);
giuliomoro@0 3003 node.setAttribute('format','dBFS');
giuliomoro@0 3004 storePoint.appendChild(node);
giuliomoro@0 3005 }
giuliomoro@0 3006
giuliomoro@0 3007 var title = document.createElement('div');
giuliomoro@0 3008 title.innerHTML = '<span>Master Volume Control</span>';
giuliomoro@0 3009 title.style.fontSize = '0.75em';
giuliomoro@0 3010 title.style.width = "100%";
giuliomoro@0 3011 title.align = 'center';
giuliomoro@0 3012 this.object.appendChild(title);
giuliomoro@0 3013
giuliomoro@0 3014 this.object.appendChild(this.slider);
giuliomoro@0 3015 this.object.appendChild(this.valueText);
giuliomoro@0 3016 }
giuliomoro@0 3017 // Global Checkers
giuliomoro@0 3018 // These functions will help enforce the checkers
giuliomoro@0 3019 this.checkHiddenAnchor = function()
giuliomoro@0 3020 {
giuliomoro@0 3021 for (var ao of audioEngineContext.audioObjects)
giuliomoro@0 3022 {
giuliomoro@0 3023 if (ao.specification.type == "anchor")
giuliomoro@0 3024 {
giuliomoro@0 3025 if (ao.interfaceDOM.getValue() > (ao.specification.marker/100) && ao.specification.marker > 0) {
giuliomoro@0 3026 // Anchor is not set below
giuliomoro@0 3027 console.log('Anchor node not below marker value');
giuliomoro@0 3028 alert('Please keep listening');
giuliomoro@0 3029 this.storeErrorNode('Anchor node not below marker value');
giuliomoro@0 3030 return false;
giuliomoro@0 3031 }
giuliomoro@0 3032 }
giuliomoro@0 3033 }
giuliomoro@0 3034 return true;
giuliomoro@0 3035 };
giuliomoro@0 3036
giuliomoro@0 3037 this.checkHiddenReference = function()
giuliomoro@0 3038 {
giuliomoro@0 3039 for (var ao of audioEngineContext.audioObjects)
giuliomoro@0 3040 {
giuliomoro@0 3041 if (ao.specification.type == "reference")
giuliomoro@0 3042 {
giuliomoro@0 3043 if (ao.interfaceDOM.getValue() < (ao.specification.marker/100) && ao.specification.marker > 0) {
giuliomoro@0 3044 // Anchor is not set below
giuliomoro@0 3045 console.log('Reference node not above marker value');
giuliomoro@0 3046 this.storeErrorNode('Reference node not above marker value');
giuliomoro@0 3047 alert('Please keep listening');
giuliomoro@0 3048 return false;
giuliomoro@0 3049 }
giuliomoro@0 3050 }
giuliomoro@0 3051 }
giuliomoro@0 3052 return true;
giuliomoro@0 3053 };
giuliomoro@0 3054
giuliomoro@0 3055 this.checkFragmentsFullyPlayed = function ()
giuliomoro@0 3056 {
giuliomoro@0 3057 // Checks the entire file has been played back
giuliomoro@0 3058 // NOTE ! This will return true IF playback is Looped!!!
giuliomoro@0 3059 if (audioEngineContext.loopPlayback)
giuliomoro@0 3060 {
giuliomoro@0 3061 console.log("WARNING - Looped source: Cannot check fragments are fully played");
giuliomoro@0 3062 return true;
giuliomoro@0 3063 }
giuliomoro@0 3064 var check_pass = true;
giuliomoro@0 3065 var error_obj = [];
giuliomoro@0 3066 for (var i = 0; i<audioEngineContext.audioObjects.length; i++)
giuliomoro@0 3067 {
giuliomoro@0 3068 var object = audioEngineContext.audioObjects[i];
giuliomoro@0 3069 var time = object.buffer.buffer.duration;
giuliomoro@0 3070 var metric = object.metric;
giuliomoro@0 3071 var passed = false;
giuliomoro@0 3072 for (var j=0; j<metric.listenTracker.length; j++)
giuliomoro@0 3073 {
giuliomoro@0 3074 var bt = metric.listenTracker[j].getElementsByTagName('buffertime');
giuliomoro@0 3075 var start_time = Number(bt[0].getAttribute('start'));
giuliomoro@0 3076 var stop_time = Number(bt[0].getAttribute('stop'));
giuliomoro@0 3077 var delta = stop_time - start_time;
giuliomoro@0 3078 if (delta >= time)
giuliomoro@0 3079 {
giuliomoro@0 3080 passed = true;
giuliomoro@0 3081 break;
giuliomoro@0 3082 }
giuliomoro@0 3083 }
giuliomoro@0 3084 if (passed == false)
giuliomoro@0 3085 {
giuliomoro@0 3086 check_pass = false;
giuliomoro@0 3087 console.log("Continue listening to track-"+object.interfaceDOM.getPresentedId());
giuliomoro@0 3088 error_obj.push(object.interfaceDOM.getPresentedId());
giuliomoro@0 3089 }
giuliomoro@0 3090 }
giuliomoro@0 3091 if (check_pass == false)
giuliomoro@0 3092 {
giuliomoro@0 3093 var str_start = "You have not completely listened to fragments ";
giuliomoro@0 3094 for (var i=0; i<error_obj.length; i++)
giuliomoro@0 3095 {
giuliomoro@0 3096 str_start += error_obj[i];
giuliomoro@0 3097 if (i != error_obj.length-1)
giuliomoro@0 3098 {
giuliomoro@0 3099 str_start += ', ';
giuliomoro@0 3100 }
giuliomoro@0 3101 }
giuliomoro@0 3102 str_start += ". Please keep listening";
giuliomoro@0 3103 console.log("[ALERT]: "+str_start);
giuliomoro@0 3104 this.storeErrorNode("[ALERT]: "+str_start);
giuliomoro@0 3105 alert(str_start);
giuliomoro@0 3106 }
giuliomoro@0 3107 };
giuliomoro@0 3108 this.checkAllMoved = function()
giuliomoro@0 3109 {
giuliomoro@0 3110 var str = "You have not moved ";
giuliomoro@0 3111 var failed = [];
giuliomoro@0 3112 for (var ao of audioEngineContext.audioObjects)
giuliomoro@0 3113 {
giuliomoro@0 3114 if(ao.metric.wasMoved == false && ao.interfaceDOM.canMove() == true)
giuliomoro@0 3115 {
giuliomoro@0 3116 failed.push(ao.interfaceDOM.getPresentedId());
giuliomoro@0 3117 }
giuliomoro@0 3118 }
giuliomoro@0 3119 if (failed.length == 0)
giuliomoro@0 3120 {
giuliomoro@0 3121 return true;
giuliomoro@0 3122 } else if (failed.length == 1)
giuliomoro@0 3123 {
giuliomoro@0 3124 str += 'track '+failed[0];
giuliomoro@0 3125 } else {
giuliomoro@0 3126 str += 'tracks ';
giuliomoro@0 3127 for (var i=0; i<failed.length-1; i++)
giuliomoro@0 3128 {
giuliomoro@0 3129 str += failed[i]+', ';
giuliomoro@0 3130 }
giuliomoro@0 3131 str += 'and '+failed[i];
giuliomoro@0 3132 }
giuliomoro@0 3133 str +='.';
giuliomoro@0 3134 alert(str);
giuliomoro@0 3135 console.log(str);
giuliomoro@6 3136 this.storeErrorNode(str);
giuliomoro@0 3137 return false;
giuliomoro@0 3138 };
giuliomoro@6 3139 this.checkOneFragmentSelected = function(){
giuliomoro@6 3140 console.log("checkOneFragmentSelected");
giuliomoro@6 3141 var str = "You should select an answer before continuing";
giuliomoro@8 3142 var isInactive = 0;
giuliomoro@8 3143 for (var ao of audioEngineContext.audioObjects)
giuliomoro@8 3144 {
giuliomoro@8 3145 if(ao.specification.inactive === true)
giuliomoro@8 3146 {
giuliomoro@8 3147 ++isInactive;
giuliomoro@8 3148 }
giuliomoro@8 3149 }
giuliomoro@8 3150 if(this.comparator.selected === null && isInactive===0){
giuliomoro@6 3151 alert(str);
giuliomoro@6 3152 return false;
giuliomoro@6 3153 } else {
giuliomoro@6 3154 return true;
giuliomoro@6 3155 }
giuliomoro@6 3156 };
giuliomoro@0 3157 this.checkAllPlayed = function()
giuliomoro@0 3158 {
giuliomoro@0 3159 var str = "You have not played ";
giuliomoro@0 3160 var failed = [];
giuliomoro@0 3161 for (var ao of audioEngineContext.audioObjects)
giuliomoro@0 3162 {
giuliomoro@0 3163 if(ao.metric.wasListenedTo == false)
giuliomoro@0 3164 {
giuliomoro@0 3165 failed.push(ao.interfaceDOM.getPresentedId());
giuliomoro@0 3166 }
giuliomoro@0 3167 }
giuliomoro@0 3168 if (failed.length == 0)
giuliomoro@0 3169 {
giuliomoro@0 3170 return true;
giuliomoro@0 3171 } else if (failed.length == 1)
giuliomoro@0 3172 {
giuliomoro@0 3173 str += 'track '+failed[0];
giuliomoro@0 3174 } else {
giuliomoro@0 3175 str += 'tracks ';
giuliomoro@0 3176 for (var i=0; i<failed.length-1; i++)
giuliomoro@0 3177 {
giuliomoro@0 3178 str += failed[i]+', ';
giuliomoro@0 3179 }
giuliomoro@0 3180 str += 'and '+failed[i];
giuliomoro@0 3181 }
giuliomoro@0 3182 str +='.';
giuliomoro@0 3183 alert(str);
giuliomoro@0 3184 console.log(str);
giuliomoro@0 3185 this.storeErrorNode(str);
giuliomoro@0 3186 return false;
giuliomoro@0 3187 };
giuliomoro@0 3188
giuliomoro@0 3189 this.storeErrorNode = function(errorMessage)
giuliomoro@0 3190 {
giuliomoro@0 3191 var time = audioEngineContext.timer.getTestTime();
giuliomoro@0 3192 var node = storage.document.createElement('error');
giuliomoro@0 3193 node.setAttribute('time',time);
giuliomoro@0 3194 node.textContent = errorMessage;
giuliomoro@0 3195 testState.currentStore.XMLDOM.appendChild(node);
giuliomoro@0 3196 };
giuliomoro@0 3197 }
giuliomoro@0 3198
giuliomoro@0 3199 function Storage()
giuliomoro@0 3200 {
giuliomoro@0 3201 // Holds results in XML format until ready for collection
giuliomoro@0 3202 this.globalPreTest = null;
giuliomoro@0 3203 this.globalPostTest = null;
giuliomoro@0 3204 this.testPages = [];
giuliomoro@0 3205 this.document = null;
giuliomoro@0 3206 this.root = null;
giuliomoro@0 3207 this.state = 0;
giuliomoro@0 3208
giuliomoro@0 3209 this.initialise = function(existingStore)
giuliomoro@0 3210 {
giuliomoro@0 3211 if (existingStore == undefined) {
giuliomoro@0 3212 // We need to get the sessionKey
giuliomoro@0 3213 this.SessionKey.generateKey();
giuliomoro@0 3214 this.document = document.implementation.createDocument(null,"waetresult");
giuliomoro@0 3215 this.root = this.document.childNodes[0];
giuliomoro@0 3216 var projectDocument = specification.projectXML;
giuliomoro@0 3217 projectDocument.setAttribute('file-name',url);
giuliomoro@0 3218 this.root.appendChild(projectDocument);
giuliomoro@0 3219 this.root.appendChild(returnDateNode());
giuliomoro@0 3220 this.root.appendChild(interfaceContext.returnNavigator());
giuliomoro@0 3221 } else {
giuliomoro@0 3222 this.document = existingStore;
giuliomoro@0 3223 this.root = existingStore.children[0];
giuliomoro@0 3224 this.SessionKey.key = this.root.getAttribute("key");
giuliomoro@0 3225 }
giuliomoro@0 3226 if (specification.preTest != undefined){this.globalPreTest = new this.surveyNode(this,this.root,specification.preTest);}
giuliomoro@0 3227 if (specification.postTest != undefined){this.globalPostTest = new this.surveyNode(this,this.root,specification.postTest);}
giuliomoro@0 3228 };
giuliomoro@0 3229
giuliomoro@0 3230 this.SessionKey = {
giuliomoro@0 3231 key: null,
giuliomoro@0 3232 request: new XMLHttpRequest(),
giuliomoro@0 3233 parent: this,
giuliomoro@0 3234 handleEvent: function() {
giuliomoro@0 3235 var parse = new DOMParser();
giuliomoro@0 3236 var xml = parse.parseFromString(this.request.response,"text/xml");
giuliomoro@0 3237 if (xml.getAllElementsByTagName("state")[0].textContent == "OK") {
giuliomoro@0 3238 this.key = xml.getAllElementsByTagName("key")[0].textContent;
giuliomoro@0 3239 this.parent.root.setAttribute("key",this.key);
giuliomoro@0 3240 this.parent.root.setAttribute("state","empty");
giuliomoro@0 3241 } else {
giuliomoro@0 3242 this.generateKey();
giuliomoro@0 3243 }
giuliomoro@0 3244 },
giuliomoro@0 3245 generateKey: function() {
giuliomoro@0 3246 var temp_key = randomString(32);
giuliomoro@0 3247 this.request.open("GET","keygen.php?key="+temp_key,true);
giuliomoro@0 3248 this.request.addEventListener("load",this);
giuliomoro@0 3249 this.request.send();
giuliomoro@0 3250 },
giuliomoro@0 3251 update: function() {
giuliomoro@0 3252 this.parent.root.setAttribute("state","update");
giuliomoro@0 3253 var xmlhttp = new XMLHttpRequest();
giuliomoro@0 3254 xmlhttp.open("POST",specification.projectReturn+"?key="+this.key);
giuliomoro@0 3255 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
giuliomoro@0 3256 xmlhttp.onerror = function(){
giuliomoro@0 3257 console.log('Error updating file to server!');
giuliomoro@0 3258 };
giuliomoro@0 3259 var hold = document.createElement("div");
giuliomoro@0 3260 var clone = this.parent.root.cloneNode(true);
giuliomoro@0 3261 hold.appendChild(clone);
giuliomoro@0 3262 xmlhttp.onload = function() {
giuliomoro@0 3263 if (this.status >= 300) {
giuliomoro@0 3264 console.log("WARNING - Could not update at this time");
giuliomoro@0 3265 } else {
giuliomoro@0 3266 var parser = new DOMParser();
giuliomoro@0 3267 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
giuliomoro@0 3268 var response = xmlDoc.getElementsByTagName('response')[0];
giuliomoro@0 3269 if (response.getAttribute("state") == "OK") {
giuliomoro@0 3270 var file = response.getElementsByTagName("file")[0];
giuliomoro@0 3271 console.log("Intermediate save: OK, written "+file.getAttribute("bytes")+"B");
giuliomoro@0 3272 } else {
giuliomoro@0 3273 var message = response.getElementsByTagName("message");
giuliomoro@0 3274 console.log("Intermediate save: Error! "+message.textContent);
giuliomoro@0 3275 }
giuliomoro@0 3276 }
giuliomoro@0 3277 }
giuliomoro@0 3278 xmlhttp.send([hold.innerHTML]);
giuliomoro@0 3279 }
giuliomoro@0 3280 }
giuliomoro@0 3281
giuliomoro@0 3282 this.createTestPageStore = function(specification)
giuliomoro@0 3283 {
giuliomoro@0 3284 var store = new this.pageNode(this,specification);
giuliomoro@0 3285 this.testPages.push(store);
giuliomoro@0 3286 return this.testPages[this.testPages.length-1];
giuliomoro@0 3287 };
giuliomoro@0 3288
giuliomoro@0 3289 this.surveyNode = function(parent,root,specification)
giuliomoro@0 3290 {
giuliomoro@0 3291 this.specification = specification;
giuliomoro@0 3292 this.parent = parent;
giuliomoro@0 3293 this.state = "empty";
giuliomoro@0 3294 this.XMLDOM = this.parent.document.createElement('survey');
giuliomoro@0 3295 this.XMLDOM.setAttribute('location',this.specification.location);
giuliomoro@0 3296 this.XMLDOM.setAttribute("state",this.state);
giuliomoro@0 3297 for (var optNode of this.specification.options)
giuliomoro@0 3298 {
giuliomoro@0 3299 if (optNode.type != 'statement')
giuliomoro@0 3300 {
giuliomoro@0 3301 var node = this.parent.document.createElement('surveyresult');
giuliomoro@0 3302 node.setAttribute("ref",optNode.id);
giuliomoro@0 3303 node.setAttribute('type',optNode.type);
giuliomoro@0 3304 this.XMLDOM.appendChild(node);
giuliomoro@0 3305 }
giuliomoro@0 3306 }
giuliomoro@0 3307 root.appendChild(this.XMLDOM);
giuliomoro@0 3308
giuliomoro@0 3309 this.postResult = function(node)
giuliomoro@0 3310 {
giuliomoro@0 3311 // From popup: node is the popupOption node containing both spec. and results
giuliomoro@0 3312 // ID is the position
giuliomoro@0 3313 if (node.specification.type == 'statement'){return;}
giuliomoro@0 3314 var surveyresult = this.XMLDOM.children[0];
giuliomoro@0 3315 while(surveyresult != null) {
giuliomoro@0 3316 if (surveyresult.getAttribute("ref") == node.specification.id)
giuliomoro@0 3317 {
giuliomoro@0 3318 break;
giuliomoro@0 3319 }
giuliomoro@0 3320 surveyresult = surveyresult.nextElementSibling;
giuliomoro@0 3321 }
giuliomoro@0 3322 switch(node.specification.type)
giuliomoro@0 3323 {
giuliomoro@0 3324 case "number":
giuliomoro@0 3325 case "question":
giuliomoro@0 3326 var child = this.parent.document.createElement('response');
giuliomoro@0 3327 child.textContent = node.response;
giuliomoro@0 3328 surveyresult.appendChild(child);
giuliomoro@0 3329 break;
giuliomoro@0 3330 case "radio":
giuliomoro@0 3331 var child = this.parent.document.createElement('response');
giuliomoro@0 3332 child.setAttribute('name',node.response.name);
giuliomoro@0 3333 child.textContent = node.response.text;
giuliomoro@0 3334 surveyresult.appendChild(child);
giuliomoro@0 3335 break;
giuliomoro@0 3336 case "checkbox":
giuliomoro@0 3337 for (var i=0; i<node.response.length; i++)
giuliomoro@0 3338 {
giuliomoro@0 3339 var checkNode = this.parent.document.createElement('response');
giuliomoro@0 3340 checkNode.setAttribute('name',node.response[i].name);
giuliomoro@0 3341 checkNode.setAttribute('checked',node.response[i].checked);
giuliomoro@0 3342 surveyresult.appendChild(checkNode);
giuliomoro@0 3343 }
giuliomoro@0 3344 break;
giuliomoro@0 3345 }
giuliomoro@0 3346 };
giuliomoro@0 3347 this.complete = function() {
giuliomoro@0 3348 this.state = "complete";
giuliomoro@0 3349 this.XMLDOM.setAttribute("state",this.state);
giuliomoro@0 3350 }
giuliomoro@0 3351 };
giuliomoro@0 3352
giuliomoro@0 3353 this.pageNode = function(parent,specification)
giuliomoro@0 3354 {
giuliomoro@0 3355 // Create one store per test page
giuliomoro@0 3356 this.specification = specification;
giuliomoro@0 3357 this.parent = parent;
giuliomoro@0 3358 this.state = "empty";
giuliomoro@0 3359 this.XMLDOM = this.parent.document.createElement('page');
giuliomoro@0 3360 this.XMLDOM.setAttribute('ref',specification.id);
giuliomoro@0 3361 this.XMLDOM.setAttribute('presentedId',specification.presentedId);
giuliomoro@0 3362 this.XMLDOM.setAttribute("state",this.state);
giuliomoro@0 3363 if (specification.preTest != undefined){this.preTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.preTest);}
giuliomoro@0 3364 if (specification.postTest != undefined){this.postTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.postTest);}
giuliomoro@0 3365
giuliomoro@0 3366 // Add any page metrics
giuliomoro@0 3367 var page_metric = this.parent.document.createElement('metric');
giuliomoro@0 3368 this.XMLDOM.appendChild(page_metric);
giuliomoro@0 3369
giuliomoro@0 3370 // Add the audioelement
giuliomoro@0 3371 for (var element of this.specification.audioElements)
giuliomoro@0 3372 {
giuliomoro@0 3373 var aeNode = this.parent.document.createElement('audioelement');
giuliomoro@0 3374 aeNode.setAttribute('ref',element.id);
giuliomoro@0 3375 if (element.name != undefined){aeNode.setAttribute('name',element.name)};
giuliomoro@0 3376 aeNode.setAttribute('type',element.type);
giuliomoro@0 3377 aeNode.setAttribute('url', element.url);
giuliomoro@0 3378 aeNode.setAttribute('gain', element.gain);
giuliomoro@0 3379 if (element.type == 'anchor' || element.type == 'reference')
giuliomoro@0 3380 {
giuliomoro@0 3381 if (element.marker > 0)
giuliomoro@0 3382 {
giuliomoro@0 3383 aeNode.setAttribute('marker',element.marker);
giuliomoro@0 3384 }
giuliomoro@0 3385 }
giuliomoro@0 3386 var ae_metric = this.parent.document.createElement('metric');
giuliomoro@0 3387 aeNode.appendChild(ae_metric);
giuliomoro@0 3388 this.XMLDOM.appendChild(aeNode);
giuliomoro@0 3389 }
giuliomoro@0 3390
giuliomoro@0 3391 this.parent.root.appendChild(this.XMLDOM);
giuliomoro@0 3392
giuliomoro@0 3393 this.complete = function() {
giuliomoro@0 3394 this.state = "complete";
giuliomoro@0 3395 this.XMLDOM.setAttribute("state","complete");
giuliomoro@0 3396 }
giuliomoro@0 3397 };
giuliomoro@0 3398 this.update = function() {
giuliomoro@0 3399 this.SessionKey.update();
giuliomoro@0 3400 }
giuliomoro@0 3401 this.finish = function()
giuliomoro@0 3402 {
giuliomoro@0 3403 if (this.state == 0)
giuliomoro@0 3404 {
giuliomoro@0 3405 this.update();
giuliomoro@0 3406 }
giuliomoro@0 3407 this.state = 1;
giuliomoro@0 3408 return this.root;
giuliomoro@0 3409 };
giuliomoro@0 3410 }