annotate core.js @ 0:d2eb0e6ccaaf

initial commit
author Giulio Moro <giuliomoro@yahoo.it>
date Wed, 20 Apr 2016 16:26:57 +0100
parents
children 5b34e5fed7bb
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@0 308 interfaceJS.setAttribute("src","interfaces/AB.js");
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@0 314 css.href = 'interfaces/AB.css';
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@0 895 storage.update();
giuliomoro@0 896 if (this.stateIndex == -1) {
giuliomoro@0 897 this.stateIndex++;
giuliomoro@0 898 console.log('Starting test...');
giuliomoro@0 899 if (this.preTestSurvey != null)
giuliomoro@0 900 {
giuliomoro@0 901 popup.initState(this.preTestSurvey,storage.globalPreTest);
giuliomoro@0 902 } else {
giuliomoro@0 903 this.advanceState();
giuliomoro@0 904 }
giuliomoro@0 905 } else if (this.stateIndex == this.stateMap.length)
giuliomoro@0 906 {
giuliomoro@0 907 // All test pages complete, post test
giuliomoro@0 908 console.log('Ending test ...');
giuliomoro@0 909 this.stateIndex++;
giuliomoro@0 910 if (this.postTestSurvey == null) {
giuliomoro@0 911 this.advanceState();
giuliomoro@0 912 } else {
giuliomoro@0 913 popup.initState(this.postTestSurvey,storage.globalPostTest);
giuliomoro@0 914 }
giuliomoro@0 915 } else if (this.stateIndex > this.stateMap.length)
giuliomoro@0 916 {
giuliomoro@0 917 createProjectSave(specification.projectReturn);
giuliomoro@0 918 }
giuliomoro@0 919 else
giuliomoro@0 920 {
giuliomoro@0 921 if (this.currentStateMap == null)
giuliomoro@0 922 {
giuliomoro@0 923 this.currentStateMap = this.stateMap[this.stateIndex];
giuliomoro@0 924 if (this.currentStateMap.randomiseOrder)
giuliomoro@0 925 {
giuliomoro@0 926 this.currentStateMap.audioElements = randomiseOrder(this.currentStateMap.audioElements);
giuliomoro@0 927 }
giuliomoro@0 928 this.currentStore = storage.testPages[this.stateIndex];
giuliomoro@0 929 if (this.currentStateMap.preTest != null)
giuliomoro@0 930 {
giuliomoro@0 931 this.currentStatePosition = 'pre';
giuliomoro@0 932 popup.initState(this.currentStateMap.preTest,storage.testPages[this.stateIndex].preTest);
giuliomoro@0 933 } else {
giuliomoro@0 934 this.currentStatePosition = 'test';
giuliomoro@0 935 }
giuliomoro@0 936 interfaceContext.newPage(this.currentStateMap,storage.testPages[this.stateIndex]);
giuliomoro@0 937 return;
giuliomoro@0 938 }
giuliomoro@0 939 switch(this.currentStatePosition)
giuliomoro@0 940 {
giuliomoro@0 941 case 'pre':
giuliomoro@0 942 this.currentStatePosition = 'test';
giuliomoro@0 943 break;
giuliomoro@0 944 case 'test':
giuliomoro@0 945 this.currentStatePosition = 'post';
giuliomoro@0 946 // Save the data
giuliomoro@0 947 this.testPageCompleted();
giuliomoro@0 948 if (this.currentStateMap.postTest == null)
giuliomoro@0 949 {
giuliomoro@0 950 this.advanceState();
giuliomoro@0 951 return;
giuliomoro@0 952 } else {
giuliomoro@0 953 popup.initState(this.currentStateMap.postTest,storage.testPages[this.stateIndex].postTest);
giuliomoro@0 954 }
giuliomoro@0 955 break;
giuliomoro@0 956 case 'post':
giuliomoro@0 957 this.stateIndex++;
giuliomoro@0 958 this.currentStateMap = null;
giuliomoro@0 959 this.advanceState();
giuliomoro@0 960 break;
giuliomoro@0 961 };
giuliomoro@0 962 }
giuliomoro@0 963 };
giuliomoro@0 964
giuliomoro@0 965 this.testPageCompleted = function() {
giuliomoro@0 966 // Function called each time a test page has been completed
giuliomoro@0 967 var storePoint = storage.testPages[this.stateIndex];
giuliomoro@0 968 // First get the test metric
giuliomoro@0 969
giuliomoro@0 970 var metric = storePoint.XMLDOM.getElementsByTagName('metric')[0];
giuliomoro@0 971 if (audioEngineContext.metric.enableTestTimer)
giuliomoro@0 972 {
giuliomoro@0 973 var testTime = storePoint.parent.document.createElement('metricresult');
giuliomoro@0 974 testTime.id = 'testTime';
giuliomoro@0 975 testTime.textContent = audioEngineContext.timer.testDuration;
giuliomoro@0 976 metric.appendChild(testTime);
giuliomoro@0 977 }
giuliomoro@0 978
giuliomoro@0 979 var audioObjects = audioEngineContext.audioObjects;
giuliomoro@0 980 for (var ao of audioEngineContext.audioObjects)
giuliomoro@0 981 {
giuliomoro@0 982 ao.exportXMLDOM();
giuliomoro@0 983 }
giuliomoro@0 984 for (var element of interfaceContext.commentQuestions)
giuliomoro@0 985 {
giuliomoro@0 986 element.exportXMLDOM(storePoint);
giuliomoro@0 987 }
giuliomoro@0 988 pageXMLSave(storePoint.XMLDOM, this.currentStateMap);
giuliomoro@0 989 storePoint.complete();
giuliomoro@0 990 };
giuliomoro@0 991 }
giuliomoro@0 992
giuliomoro@0 993 function AudioEngine(specification) {
giuliomoro@0 994
giuliomoro@0 995 // Create two output paths, the main outputGain and fooGain.
giuliomoro@0 996 // Output gain is default to 1 and any items for playback route here
giuliomoro@0 997 // Foo gain is used for analysis to ensure paths get processed, but are not heard
giuliomoro@0 998 // because web audio will optimise and any route which does not go to the destination gets ignored.
giuliomoro@0 999 this.outputGain = audioContext.createGain();
giuliomoro@0 1000 this.fooGain = audioContext.createGain();
giuliomoro@0 1001 this.fooGain.gain = 0;
giuliomoro@0 1002
giuliomoro@0 1003 // Use this to detect playback state: 0 - stopped, 1 - playing
giuliomoro@0 1004 this.status = 0;
giuliomoro@0 1005
giuliomoro@0 1006 // Connect both gains to output
giuliomoro@0 1007 this.outputGain.connect(audioContext.destination);
giuliomoro@0 1008 this.fooGain.connect(audioContext.destination);
giuliomoro@0 1009
giuliomoro@0 1010 // Create the timer Object
giuliomoro@0 1011 this.timer = new timer();
giuliomoro@0 1012 // Create session metrics
giuliomoro@0 1013 this.metric = new sessionMetrics(this,specification);
giuliomoro@0 1014
giuliomoro@0 1015 this.loopPlayback = false;
giuliomoro@0 1016
giuliomoro@0 1017 this.pageStore = null;
giuliomoro@0 1018
giuliomoro@0 1019 // Create store for new audioObjects
giuliomoro@0 1020 this.audioObjects = [];
giuliomoro@0 1021
giuliomoro@0 1022 this.buffers = [];
giuliomoro@0 1023 this.bufferObj = function()
giuliomoro@0 1024 {
giuliomoro@0 1025 this.url = null;
giuliomoro@0 1026 this.buffer = null;
giuliomoro@0 1027 this.xmlRequest = new XMLHttpRequest();
giuliomoro@0 1028 this.xmlRequest.parent = this;
giuliomoro@0 1029 this.users = [];
giuliomoro@0 1030 this.progress = 0;
giuliomoro@0 1031 this.status = 0;
giuliomoro@0 1032 this.ready = function()
giuliomoro@0 1033 {
giuliomoro@0 1034 if (this.status >= 2)
giuliomoro@0 1035 {
giuliomoro@0 1036 this.status = 3;
giuliomoro@0 1037 }
giuliomoro@0 1038 for (var i=0; i<this.users.length; i++)
giuliomoro@0 1039 {
giuliomoro@0 1040 this.users[i].state = 1;
giuliomoro@0 1041 if (this.users[i].interfaceDOM != null)
giuliomoro@0 1042 {
giuliomoro@0 1043 this.users[i].bufferLoaded(this);
giuliomoro@0 1044 }
giuliomoro@0 1045 }
giuliomoro@0 1046 };
giuliomoro@0 1047 this.getMedia = function(url) {
giuliomoro@0 1048 this.url = url;
giuliomoro@0 1049 this.xmlRequest.open('GET',this.url,true);
giuliomoro@0 1050 this.xmlRequest.responseType = 'arraybuffer';
giuliomoro@0 1051
giuliomoro@0 1052 var bufferObj = this;
giuliomoro@0 1053
giuliomoro@0 1054 // Create callback to decode the data asynchronously
giuliomoro@0 1055 this.xmlRequest.onloadend = function() {
giuliomoro@0 1056 // Use inbuilt WAVE decoder first
giuliomoro@0 1057 if (this.status == -1) {return;}
giuliomoro@0 1058 var waveObj = new WAVE();
giuliomoro@0 1059 if (waveObj.open(bufferObj.xmlRequest.response) == 0)
giuliomoro@0 1060 {
giuliomoro@0 1061 bufferObj.buffer = audioContext.createBuffer(waveObj.num_channels,waveObj.num_samples,waveObj.sample_rate);
giuliomoro@0 1062 for (var c=0; c<waveObj.num_channels; c++)
giuliomoro@0 1063 {
giuliomoro@0 1064 var buffer_ptr = bufferObj.buffer.getChannelData(c);
giuliomoro@0 1065 for (var n=0; n<waveObj.num_samples; n++)
giuliomoro@0 1066 {
giuliomoro@0 1067 buffer_ptr[n] = waveObj.decoded_data[c][n];
giuliomoro@0 1068 }
giuliomoro@0 1069 }
giuliomoro@0 1070
giuliomoro@0 1071 delete waveObj;
giuliomoro@0 1072 } else {
giuliomoro@0 1073 audioContext.decodeAudioData(bufferObj.xmlRequest.response, function(decodedData) {
giuliomoro@0 1074 bufferObj.buffer = decodedData;
giuliomoro@0 1075 }, function(e){
giuliomoro@0 1076 // Should only be called if there was an error, but sometimes gets called continuously
giuliomoro@0 1077 // Check here if the error is genuine
giuliomoro@0 1078 if (bufferObj.xmlRequest.response == undefined) {
giuliomoro@0 1079 // Genuine error
giuliomoro@0 1080 console.log('FATAL - Error loading buffer on '+audioObj.id);
giuliomoro@0 1081 if (request.status == 404)
giuliomoro@0 1082 {
giuliomoro@0 1083 console.log('FATAL - Fragment '+audioObj.id+' 404 error');
giuliomoro@0 1084 console.log('URL: '+audioObj.url);
giuliomoro@0 1085 errorSessionDump('Fragment '+audioObj.id+' 404 error');
giuliomoro@0 1086 }
giuliomoro@0 1087 this.parent.status = -1;
giuliomoro@0 1088 }
giuliomoro@0 1089 });
giuliomoro@0 1090 }
giuliomoro@0 1091 if (bufferObj.buffer != undefined)
giuliomoro@0 1092 {
giuliomoro@0 1093 bufferObj.status = 2;
giuliomoro@0 1094 calculateLoudness(bufferObj,"I");
giuliomoro@0 1095 }
giuliomoro@0 1096 };
giuliomoro@0 1097
giuliomoro@0 1098 // Create callback for any error in loading
giuliomoro@0 1099 this.xmlRequest.onerror = function() {
giuliomoro@0 1100 this.parent.status = -1;
giuliomoro@0 1101 for (var i=0; i<this.parent.users.length; i++)
giuliomoro@0 1102 {
giuliomoro@0 1103 this.parent.users[i].state = -1;
giuliomoro@0 1104 if (this.parent.users[i].interfaceDOM != null)
giuliomoro@0 1105 {
giuliomoro@0 1106 this.parent.users[i].bufferLoaded(this);
giuliomoro@0 1107 }
giuliomoro@0 1108 }
giuliomoro@0 1109 }
giuliomoro@0 1110
giuliomoro@0 1111 this.progress = 0;
giuliomoro@0 1112 this.progressCallback = function(event){
giuliomoro@0 1113 if (event.lengthComputable)
giuliomoro@0 1114 {
giuliomoro@0 1115 this.parent.progress = event.loaded / event.total;
giuliomoro@0 1116 for (var i=0; i<this.parent.users.length; i++)
giuliomoro@0 1117 {
giuliomoro@0 1118 if(this.parent.users[i].interfaceDOM != null)
giuliomoro@0 1119 {
giuliomoro@0 1120 if (typeof this.parent.users[i].interfaceDOM.updateLoading === "function")
giuliomoro@0 1121 {
giuliomoro@0 1122 this.parent.users[i].interfaceDOM.updateLoading(this.parent.progress*100);
giuliomoro@0 1123 }
giuliomoro@0 1124 }
giuliomoro@0 1125 }
giuliomoro@0 1126 }
giuliomoro@0 1127 };
giuliomoro@0 1128 this.xmlRequest.addEventListener("progress", this.progressCallback);
giuliomoro@0 1129 this.status = 1;
giuliomoro@0 1130 this.xmlRequest.send();
giuliomoro@0 1131 };
giuliomoro@0 1132
giuliomoro@0 1133 this.registerAudioObject = function(audioObject)
giuliomoro@0 1134 {
giuliomoro@0 1135 // Called by an audioObject to register to the buffer for use
giuliomoro@0 1136 // First check if already in the register pool
giuliomoro@0 1137 for (var objects of this.users)
giuliomoro@0 1138 {
giuliomoro@0 1139 if (audioObject.id == objects.id){return 0;}
giuliomoro@0 1140 }
giuliomoro@0 1141 this.users.push(audioObject);
giuliomoro@0 1142 if (this.status == 3 || this.status == -1)
giuliomoro@0 1143 {
giuliomoro@0 1144 // The buffer is already ready, trigger bufferLoaded
giuliomoro@0 1145 audioObject.bufferLoaded(this);
giuliomoro@0 1146 }
giuliomoro@0 1147 }
giuliomoro@0 1148 };
giuliomoro@0 1149
giuliomoro@0 1150 this.play = function(id) {
giuliomoro@0 1151 // Start the timer and set the audioEngine state to playing (1)
giuliomoro@0 1152 if (this.status == 0 && this.loopPlayback) {
giuliomoro@0 1153 // Check if all audioObjects are ready
giuliomoro@0 1154 if(this.checkAllReady())
giuliomoro@0 1155 {
giuliomoro@0 1156 this.status = 1;
giuliomoro@0 1157 this.setSynchronousLoop();
giuliomoro@0 1158 }
giuliomoro@0 1159 }
giuliomoro@0 1160 else
giuliomoro@0 1161 {
giuliomoro@0 1162 this.status = 1;
giuliomoro@0 1163 }
giuliomoro@0 1164 if (this.status== 1) {
giuliomoro@0 1165 this.timer.startTest();
giuliomoro@0 1166 if (id == undefined) {
giuliomoro@0 1167 id = -1;
giuliomoro@0 1168 console.log('FATAL - Passed id was undefined - AudioEngineContext.play(id)');
giuliomoro@0 1169 return;
giuliomoro@0 1170 } else {
giuliomoro@0 1171 interfaceContext.playhead.setTimePerPixel(this.audioObjects[id]);
giuliomoro@0 1172 }
giuliomoro@0 1173 if (this.loopPlayback) {
giuliomoro@0 1174 var setTime = audioContext.currentTime;
giuliomoro@0 1175 for (var i=0; i<this.audioObjects.length; i++)
giuliomoro@0 1176 {
giuliomoro@0 1177 this.audioObjects[i].play(setTime);
giuliomoro@0 1178 if (id == i) {
giuliomoro@0 1179 this.audioObjects[i].loopStart(setTime);
giuliomoro@0 1180 } else {
giuliomoro@0 1181 this.audioObjects[i].loopStop(setTime);
giuliomoro@0 1182 }
giuliomoro@0 1183 }
giuliomoro@0 1184 } else {
giuliomoro@0 1185 var setTime = audioContext.currentTime+0.1;
giuliomoro@0 1186 for (var i=0; i<this.audioObjects.length; i++)
giuliomoro@0 1187 {
giuliomoro@0 1188 if (i != id) {
giuliomoro@0 1189 this.audioObjects[i].stop(setTime);
giuliomoro@0 1190 } else if (i == id) {
giuliomoro@0 1191 this.audioObjects[id].play(setTime);
giuliomoro@0 1192 }
giuliomoro@0 1193 }
giuliomoro@0 1194 }
giuliomoro@0 1195 interfaceContext.playhead.start();
giuliomoro@0 1196 }
giuliomoro@0 1197 };
giuliomoro@0 1198
giuliomoro@0 1199 this.stop = function() {
giuliomoro@0 1200 // Send stop and reset command to all playback buffers
giuliomoro@0 1201 if (this.status == 1) {
giuliomoro@0 1202 var setTime = audioContext.currentTime+0.1;
giuliomoro@0 1203 for (var i=0; i<this.audioObjects.length; i++)
giuliomoro@0 1204 {
giuliomoro@0 1205 this.audioObjects[i].stop(setTime);
giuliomoro@0 1206 }
giuliomoro@0 1207 interfaceContext.playhead.stop();
giuliomoro@0 1208 }
giuliomoro@0 1209 };
giuliomoro@0 1210
giuliomoro@0 1211 this.newTrack = function(element) {
giuliomoro@0 1212 // Pull data from given URL into new audio buffer
giuliomoro@0 1213 // URLs must either be from the same source OR be setup to 'Access-Control-Allow-Origin'
giuliomoro@0 1214
giuliomoro@0 1215 // Create the audioObject with ID of the new track length;
giuliomoro@0 1216 audioObjectId = this.audioObjects.length;
giuliomoro@0 1217 this.audioObjects[audioObjectId] = new audioObject(audioObjectId);
giuliomoro@0 1218
giuliomoro@0 1219 // Check if audioObject buffer is currently stored by full URL
giuliomoro@0 1220 var URL = testState.currentStateMap.hostURL + element.url;
giuliomoro@0 1221 var buffer = null;
giuliomoro@0 1222 for (var i=0; i<this.buffers.length; i++)
giuliomoro@0 1223 {
giuliomoro@0 1224 if (URL == this.buffers[i].url)
giuliomoro@0 1225 {
giuliomoro@0 1226 buffer = this.buffers[i];
giuliomoro@0 1227 break;
giuliomoro@0 1228 }
giuliomoro@0 1229 }
giuliomoro@0 1230 if (buffer == null)
giuliomoro@0 1231 {
giuliomoro@0 1232 console.log("[WARN]: Buffer was not loaded in pre-test! "+URL);
giuliomoro@0 1233 buffer = new this.bufferObj();
giuliomoro@0 1234 this.buffers.push(buffer);
giuliomoro@0 1235 buffer.getMedia(URL);
giuliomoro@0 1236 }
giuliomoro@0 1237 this.audioObjects[audioObjectId].specification = element;
giuliomoro@0 1238 this.audioObjects[audioObjectId].url = URL;
giuliomoro@0 1239 // Obtain store node
giuliomoro@0 1240 var aeNodes = this.pageStore.XMLDOM.getElementsByTagName('audioelement');
giuliomoro@0 1241 for (var i=0; i<aeNodes.length; i++)
giuliomoro@0 1242 {
giuliomoro@0 1243 if(aeNodes[i].getAttribute("ref") == element.id)
giuliomoro@0 1244 {
giuliomoro@0 1245 this.audioObjects[audioObjectId].storeDOM = aeNodes[i];
giuliomoro@0 1246 break;
giuliomoro@0 1247 }
giuliomoro@0 1248 }
giuliomoro@0 1249 buffer.registerAudioObject(this.audioObjects[audioObjectId]);
giuliomoro@0 1250 return this.audioObjects[audioObjectId];
giuliomoro@0 1251 };
giuliomoro@0 1252
giuliomoro@0 1253 this.newTestPage = function(audioHolderObject,store) {
giuliomoro@0 1254 this.pageStore = store;
giuliomoro@0 1255 this.status = 0;
giuliomoro@0 1256 this.audioObjectsReady = false;
giuliomoro@0 1257 this.metric.reset();
giuliomoro@0 1258 for (var i=0; i < this.buffers.length; i++)
giuliomoro@0 1259 {
giuliomoro@0 1260 this.buffers[i].users = [];
giuliomoro@0 1261 }
giuliomoro@0 1262 this.audioObjects = [];
giuliomoro@0 1263 this.timer = new timer();
giuliomoro@0 1264 this.loopPlayback = audioHolderObject.loop;
giuliomoro@0 1265 };
giuliomoro@0 1266
giuliomoro@0 1267 this.checkAllPlayed = function() {
giuliomoro@0 1268 arr = [];
giuliomoro@0 1269 for (var id=0; id<this.audioObjects.length; id++) {
giuliomoro@0 1270 if (this.audioObjects[id].metric.wasListenedTo == false) {
giuliomoro@0 1271 arr.push(this.audioObjects[id].id);
giuliomoro@0 1272 }
giuliomoro@0 1273 }
giuliomoro@0 1274 return arr;
giuliomoro@0 1275 };
giuliomoro@0 1276
giuliomoro@0 1277 this.checkAllReady = function() {
giuliomoro@0 1278 var ready = true;
giuliomoro@0 1279 for (var i=0; i<this.audioObjects.length; i++) {
giuliomoro@0 1280 if (this.audioObjects[i].state == 0) {
giuliomoro@0 1281 // Track not ready
giuliomoro@0 1282 console.log('WAIT -- audioObject '+i+' not ready yet!');
giuliomoro@0 1283 ready = false;
giuliomoro@0 1284 };
giuliomoro@0 1285 }
giuliomoro@0 1286 return ready;
giuliomoro@0 1287 };
giuliomoro@0 1288
giuliomoro@0 1289 this.setSynchronousLoop = function() {
giuliomoro@0 1290 // Pads the signals so they are all exactly the same length
giuliomoro@0 1291 var length = 0;
giuliomoro@0 1292 var maxId;
giuliomoro@0 1293 for (var i=0; i<this.audioObjects.length; i++)
giuliomoro@0 1294 {
giuliomoro@0 1295 if (length < this.audioObjects[i].buffer.buffer.length)
giuliomoro@0 1296 {
giuliomoro@0 1297 length = this.audioObjects[i].buffer.buffer.length;
giuliomoro@0 1298 maxId = i;
giuliomoro@0 1299 }
giuliomoro@0 1300 }
giuliomoro@0 1301 // Extract the audio and zero-pad
giuliomoro@0 1302 for (var i=0; i<this.audioObjects.length; i++)
giuliomoro@0 1303 {
giuliomoro@0 1304 var orig = this.audioObjects[i].buffer.buffer;
giuliomoro@0 1305 var hold = audioContext.createBuffer(orig.numberOfChannels,length,orig.sampleRate);
giuliomoro@0 1306 for (var c=0; c<orig.numberOfChannels; c++)
giuliomoro@0 1307 {
giuliomoro@0 1308 var inData = hold.getChannelData(c);
giuliomoro@0 1309 var outData = orig.getChannelData(c);
giuliomoro@0 1310 for (var n=0; n<orig.length; n++)
giuliomoro@0 1311 {inData[n] = outData[n];}
giuliomoro@0 1312 }
giuliomoro@0 1313 hold.playbackGain = orig.playbackGain;
giuliomoro@0 1314 hold.lufs = orig.lufs;
giuliomoro@0 1315 this.audioObjects[i].buffer.buffer = hold;
giuliomoro@0 1316 }
giuliomoro@0 1317 };
giuliomoro@0 1318
giuliomoro@0 1319 this.exportXML = function()
giuliomoro@0 1320 {
giuliomoro@0 1321
giuliomoro@0 1322 };
giuliomoro@0 1323
giuliomoro@0 1324 }
giuliomoro@0 1325
giuliomoro@0 1326 function audioObject(id) {
giuliomoro@0 1327 // The main buffer object with common control nodes to the AudioEngine
giuliomoro@0 1328
giuliomoro@0 1329 this.specification;
giuliomoro@0 1330 this.id = id;
giuliomoro@0 1331 this.state = 0; // 0 - no data, 1 - ready
giuliomoro@0 1332 this.url = null; // Hold the URL given for the output back to the results.
giuliomoro@0 1333 this.metric = new metricTracker(this);
giuliomoro@0 1334 this.storeDOM = null;
giuliomoro@0 1335
giuliomoro@0 1336 // Bindings for GUI
giuliomoro@0 1337 this.interfaceDOM = null;
giuliomoro@0 1338 this.commentDOM = null;
giuliomoro@0 1339
giuliomoro@0 1340 // Create a buffer and external gain control to allow internal patching of effects and volume leveling.
giuliomoro@0 1341 this.bufferNode = undefined;
giuliomoro@0 1342 this.outputGain = audioContext.createGain();
giuliomoro@0 1343
giuliomoro@0 1344 this.onplayGain = 1.0;
giuliomoro@0 1345
giuliomoro@0 1346 // Connect buffer to the audio graph
giuliomoro@0 1347 this.outputGain.connect(audioEngineContext.outputGain);
giuliomoro@0 1348
giuliomoro@0 1349 // the audiobuffer is not designed for multi-start playback
giuliomoro@0 1350 // When stopeed, the buffer node is deleted and recreated with the stored buffer.
giuliomoro@0 1351 this.buffer;
giuliomoro@0 1352
giuliomoro@0 1353 this.bufferLoaded = function(callee)
giuliomoro@0 1354 {
giuliomoro@0 1355 // Called by the associated buffer when it has finished loading, will then 'bind' the buffer to the
giuliomoro@0 1356 // audioObject and trigger the interfaceDOM.enable() function for user feedback
giuliomoro@0 1357 if (callee.status == -1) {
giuliomoro@0 1358 // ERROR
giuliomoro@0 1359 this.state = -1;
giuliomoro@0 1360 if (this.interfaceDOM != null) {this.interfaceDOM.error();}
giuliomoro@0 1361 this.buffer = callee;
giuliomoro@0 1362 return;
giuliomoro@0 1363 }
giuliomoro@0 1364 if (audioEngineContext.loopPlayback){
giuliomoro@0 1365 // First copy the buffer into this.buffer
giuliomoro@0 1366 this.buffer = new audioEngineContext.bufferObj();
giuliomoro@0 1367 this.buffer.url = callee.url;
giuliomoro@0 1368 this.buffer.buffer = audioContext.createBuffer(callee.buffer.numberOfChannels, callee.buffer.length, callee.buffer.sampleRate);
giuliomoro@0 1369 for (var c=0; c<callee.buffer.numberOfChannels; c++)
giuliomoro@0 1370 {
giuliomoro@0 1371 var src = callee.buffer.getChannelData(c);
giuliomoro@0 1372 var dst = this.buffer.buffer.getChannelData(c);
giuliomoro@0 1373 for (var n=0; n<src.length; n++)
giuliomoro@0 1374 {
giuliomoro@0 1375 dst[n] = src[n];
giuliomoro@0 1376 }
giuliomoro@0 1377 }
giuliomoro@0 1378 } else {
giuliomoro@0 1379 this.buffer = callee;
giuliomoro@0 1380 }
giuliomoro@0 1381 this.state = 1;
giuliomoro@0 1382 this.buffer.buffer.playbackGain = callee.buffer.playbackGain;
giuliomoro@0 1383 this.buffer.buffer.lufs = callee.buffer.lufs;
giuliomoro@0 1384 var targetLUFS = this.specification.parent.loudness || specification.loudness;
giuliomoro@0 1385 if (typeof targetLUFS === "number")
giuliomoro@0 1386 {
giuliomoro@0 1387 this.buffer.buffer.playbackGain = decibelToLinear(targetLUFS - this.buffer.buffer.lufs);
giuliomoro@0 1388 } else {
giuliomoro@0 1389 this.buffer.buffer.playbackGain = 1.0;
giuliomoro@0 1390 }
giuliomoro@0 1391 if (this.interfaceDOM != null) {
giuliomoro@0 1392 this.interfaceDOM.enable();
giuliomoro@0 1393 }
giuliomoro@0 1394 this.onplayGain = decibelToLinear(this.specification.gain)*this.buffer.buffer.playbackGain;
giuliomoro@0 1395 this.storeDOM.setAttribute('playGain',linearToDecibel(this.onplayGain));
giuliomoro@0 1396 };
giuliomoro@0 1397
giuliomoro@0 1398 this.bindInterface = function(interfaceObject)
giuliomoro@0 1399 {
giuliomoro@0 1400 this.interfaceDOM = interfaceObject;
giuliomoro@0 1401 this.metric.initialise(interfaceObject.getValue());
giuliomoro@0 1402 if (this.state == 1)
giuliomoro@0 1403 {
giuliomoro@0 1404 this.interfaceDOM.enable();
giuliomoro@0 1405 } else if (this.state == -1) {
giuliomoro@0 1406 // ERROR
giuliomoro@0 1407 this.interfaceDOM.error();
giuliomoro@0 1408 return;
giuliomoro@0 1409 }
giuliomoro@0 1410 this.storeDOM.setAttribute('presentedId',interfaceObject.getPresentedId());
giuliomoro@0 1411 };
giuliomoro@0 1412
giuliomoro@0 1413 this.loopStart = function(setTime) {
giuliomoro@0 1414 this.outputGain.gain.linearRampToValueAtTime(this.onplayGain,setTime);
giuliomoro@0 1415 this.metric.startListening(audioEngineContext.timer.getTestTime());
giuliomoro@0 1416 this.interfaceDOM.startPlayback();
giuliomoro@0 1417 };
giuliomoro@0 1418
giuliomoro@0 1419 this.loopStop = function(setTime) {
giuliomoro@0 1420 if (this.outputGain.gain.value != 0.0) {
giuliomoro@0 1421 this.outputGain.gain.linearRampToValueAtTime(0.0,setTime);
giuliomoro@0 1422 this.metric.stopListening(audioEngineContext.timer.getTestTime());
giuliomoro@0 1423 }
giuliomoro@0 1424 this.interfaceDOM.stopPlayback();
giuliomoro@0 1425 };
giuliomoro@0 1426
giuliomoro@0 1427 this.play = function(startTime) {
giuliomoro@0 1428 if (this.bufferNode == undefined && this.buffer.buffer != undefined) {
giuliomoro@0 1429 this.bufferNode = audioContext.createBufferSource();
giuliomoro@0 1430 this.bufferNode.owner = this;
giuliomoro@0 1431 this.bufferNode.connect(this.outputGain);
giuliomoro@0 1432 this.bufferNode.buffer = this.buffer.buffer;
giuliomoro@0 1433 this.bufferNode.loop = audioEngineContext.loopPlayback;
giuliomoro@0 1434 this.bufferNode.onended = function(event) {
giuliomoro@0 1435 // Safari does not like using 'this' to reference the calling object!
giuliomoro@0 1436 //event.currentTarget.owner.metric.stopListening(audioEngineContext.timer.getTestTime(),event.currentTarget.owner.getCurrentPosition());
giuliomoro@0 1437 if (event.currentTarget != null) {
giuliomoro@0 1438 event.currentTarget.owner.stop(audioContext.currentTime+1);
giuliomoro@0 1439 }
giuliomoro@0 1440 };
giuliomoro@0 1441 if (this.bufferNode.loop == false) {
giuliomoro@0 1442 this.metric.startListening(audioEngineContext.timer.getTestTime());
giuliomoro@0 1443 this.outputGain.gain.setValueAtTime(this.onplayGain,startTime);
giuliomoro@0 1444 this.interfaceDOM.startPlayback();
giuliomoro@0 1445 } else {
giuliomoro@0 1446 this.outputGain.gain.setValueAtTime(0.0,startTime);
giuliomoro@0 1447 }
giuliomoro@0 1448 this.bufferNode.start(startTime);
giuliomoro@0 1449 this.bufferNode.playbackStartTime = audioEngineContext.timer.getTestTime();
giuliomoro@0 1450 }
giuliomoro@0 1451 };
giuliomoro@0 1452
giuliomoro@0 1453 this.stop = function(stopTime) {
giuliomoro@0 1454 this.outputGain.gain.cancelScheduledValues(audioContext.currentTime);
giuliomoro@0 1455 if (this.bufferNode != undefined)
giuliomoro@0 1456 {
giuliomoro@0 1457 this.metric.stopListening(audioEngineContext.timer.getTestTime(),this.getCurrentPosition());
giuliomoro@0 1458 this.bufferNode.stop(stopTime);
giuliomoro@0 1459 this.bufferNode = undefined;
giuliomoro@0 1460 }
giuliomoro@0 1461 this.outputGain.gain.value = 0.0;
giuliomoro@0 1462 this.interfaceDOM.stopPlayback();
giuliomoro@0 1463 };
giuliomoro@0 1464
giuliomoro@0 1465 this.getCurrentPosition = function() {
giuliomoro@0 1466 var time = audioEngineContext.timer.getTestTime();
giuliomoro@0 1467 if (this.bufferNode != undefined) {
giuliomoro@0 1468 var position = (time - this.bufferNode.playbackStartTime)%this.buffer.buffer.duration;
giuliomoro@0 1469 if (isNaN(position)){return 0;}
giuliomoro@0 1470 return position;
giuliomoro@0 1471 } else {
giuliomoro@0 1472 return 0;
giuliomoro@0 1473 }
giuliomoro@0 1474 };
giuliomoro@0 1475
giuliomoro@0 1476 this.exportXMLDOM = function() {
giuliomoro@0 1477 var file = storage.document.createElement('file');
giuliomoro@0 1478 var buf;
giuliomoro@0 1479 if(typeof(this.buffer) !== "undefined"){
giuliomoro@0 1480 buf = this.buffer.buffer;
giuliomoro@0 1481 } else {
giuliomoro@0 1482 buf = {};
giuliomoro@0 1483 }
giuliomoro@0 1484 file.setAttribute('sampleRate', buf.sampleRate);
giuliomoro@0 1485 file.setAttribute('channels', buf.numberOfChannels);
giuliomoro@0 1486 file.setAttribute('sampleCount', buf.length);
giuliomoro@0 1487 file.setAttribute('duration', buf.duration);
giuliomoro@0 1488 this.storeDOM.appendChild(file);
giuliomoro@0 1489 if (this.specification.type != 'outside-reference') {
giuliomoro@0 1490 var interfaceXML = this.interfaceDOM.exportXMLDOM(this);
giuliomoro@0 1491 if (interfaceXML != null)
giuliomoro@0 1492 {
giuliomoro@0 1493 if (interfaceXML.length == undefined) {
giuliomoro@0 1494 this.storeDOM.appendChild(interfaceXML);
giuliomoro@0 1495 } else {
giuliomoro@0 1496 for (var i=0; i<interfaceXML.length; i++)
giuliomoro@0 1497 {
giuliomoro@0 1498 this.storeDOM.appendChild(interfaceXML[i]);
giuliomoro@0 1499 }
giuliomoro@0 1500 }
giuliomoro@0 1501 }
giuliomoro@0 1502 if (this.commentDOM != null) {
giuliomoro@0 1503 this.storeDOM.appendChild(this.commentDOM.exportXMLDOM(this));
giuliomoro@0 1504 }
giuliomoro@0 1505 }
giuliomoro@0 1506 var nodes = this.metric.exportXMLDOM();
giuliomoro@0 1507 var mroot = this.storeDOM.getElementsByTagName('metric')[0];
giuliomoro@0 1508 for (var i=0; i<nodes.length; i++)
giuliomoro@0 1509 {
giuliomoro@0 1510 mroot.appendChild(nodes[i]);
giuliomoro@0 1511 }
giuliomoro@0 1512 };
giuliomoro@0 1513 }
giuliomoro@0 1514
giuliomoro@0 1515 function timer()
giuliomoro@0 1516 {
giuliomoro@0 1517 /* Timer object used in audioEngine to keep track of session timings
giuliomoro@0 1518 * Uses the timer of the web audio API, so sample resolution
giuliomoro@0 1519 */
giuliomoro@0 1520 this.testStarted = false;
giuliomoro@0 1521 this.testStartTime = 0;
giuliomoro@0 1522 this.testDuration = 0;
giuliomoro@0 1523 this.minimumTestTime = 0; // No minimum test time
giuliomoro@0 1524 this.startTest = function()
giuliomoro@0 1525 {
giuliomoro@0 1526 if (this.testStarted == false)
giuliomoro@0 1527 {
giuliomoro@0 1528 this.testStartTime = audioContext.currentTime;
giuliomoro@0 1529 this.testStarted = true;
giuliomoro@0 1530 this.updateTestTime();
giuliomoro@0 1531 audioEngineContext.metric.initialiseTest();
giuliomoro@0 1532 }
giuliomoro@0 1533 };
giuliomoro@0 1534 this.stopTest = function()
giuliomoro@0 1535 {
giuliomoro@0 1536 if (this.testStarted)
giuliomoro@0 1537 {
giuliomoro@0 1538 this.testDuration = this.getTestTime();
giuliomoro@0 1539 this.testStarted = false;
giuliomoro@0 1540 } else {
giuliomoro@0 1541 console.log('ERR: Test tried to end before beginning');
giuliomoro@0 1542 }
giuliomoro@0 1543 };
giuliomoro@0 1544 this.updateTestTime = function()
giuliomoro@0 1545 {
giuliomoro@0 1546 if (this.testStarted)
giuliomoro@0 1547 {
giuliomoro@0 1548 this.testDuration = audioContext.currentTime - this.testStartTime;
giuliomoro@0 1549 }
giuliomoro@0 1550 };
giuliomoro@0 1551 this.getTestTime = function()
giuliomoro@0 1552 {
giuliomoro@0 1553 this.updateTestTime();
giuliomoro@0 1554 return this.testDuration;
giuliomoro@0 1555 };
giuliomoro@0 1556 }
giuliomoro@0 1557
giuliomoro@0 1558 function sessionMetrics(engine,specification)
giuliomoro@0 1559 {
giuliomoro@0 1560 /* Used by audioEngine to link to audioObjects to minimise the timer call timers;
giuliomoro@0 1561 */
giuliomoro@0 1562 this.engine = engine;
giuliomoro@0 1563 this.lastClicked = -1;
giuliomoro@0 1564 this.data = -1;
giuliomoro@0 1565 this.reset = function() {
giuliomoro@0 1566 this.lastClicked = -1;
giuliomoro@0 1567 this.data = -1;
giuliomoro@0 1568 };
giuliomoro@0 1569
giuliomoro@0 1570 this.enableElementInitialPosition = false;
giuliomoro@0 1571 this.enableElementListenTracker = false;
giuliomoro@0 1572 this.enableElementTimer = false;
giuliomoro@0 1573 this.enableElementTracker = false;
giuliomoro@0 1574 this.enableFlagListenedTo = false;
giuliomoro@0 1575 this.enableFlagMoved = false;
giuliomoro@0 1576 this.enableTestTimer = false;
giuliomoro@0 1577 // Obtain the metrics enabled
giuliomoro@0 1578 for (var i=0; i<specification.metrics.enabled.length; i++)
giuliomoro@0 1579 {
giuliomoro@0 1580 var node = specification.metrics.enabled[i];
giuliomoro@0 1581 switch(node)
giuliomoro@0 1582 {
giuliomoro@0 1583 case 'testTimer':
giuliomoro@0 1584 this.enableTestTimer = true;
giuliomoro@0 1585 break;
giuliomoro@0 1586 case 'elementTimer':
giuliomoro@0 1587 this.enableElementTimer = true;
giuliomoro@0 1588 break;
giuliomoro@0 1589 case 'elementTracker':
giuliomoro@0 1590 this.enableElementTracker = true;
giuliomoro@0 1591 break;
giuliomoro@0 1592 case 'elementListenTracker':
giuliomoro@0 1593 this.enableElementListenTracker = true;
giuliomoro@0 1594 break;
giuliomoro@0 1595 case 'elementInitialPosition':
giuliomoro@0 1596 this.enableElementInitialPosition = true;
giuliomoro@0 1597 break;
giuliomoro@0 1598 case 'elementFlagListenedTo':
giuliomoro@0 1599 this.enableFlagListenedTo = true;
giuliomoro@0 1600 break;
giuliomoro@0 1601 case 'elementFlagMoved':
giuliomoro@0 1602 this.enableFlagMoved = true;
giuliomoro@0 1603 break;
giuliomoro@0 1604 case 'elementFlagComments':
giuliomoro@0 1605 this.enableFlagComments = true;
giuliomoro@0 1606 break;
giuliomoro@0 1607 }
giuliomoro@0 1608 }
giuliomoro@0 1609 this.initialiseTest = function(){};
giuliomoro@0 1610 }
giuliomoro@0 1611
giuliomoro@0 1612 function metricTracker(caller)
giuliomoro@0 1613 {
giuliomoro@0 1614 /* Custom object to track and collect metric data
giuliomoro@0 1615 * Used only inside the audioObjects object.
giuliomoro@0 1616 */
giuliomoro@0 1617
giuliomoro@0 1618 this.listenedTimer = 0;
giuliomoro@0 1619 this.listenStart = 0;
giuliomoro@0 1620 this.listenHold = false;
giuliomoro@0 1621 this.initialPosition = -1;
giuliomoro@0 1622 this.movementTracker = [];
giuliomoro@0 1623 this.listenTracker =[];
giuliomoro@0 1624 this.wasListenedTo = false;
giuliomoro@0 1625 this.wasMoved = false;
giuliomoro@0 1626 this.hasComments = false;
giuliomoro@0 1627 this.parent = caller;
giuliomoro@0 1628
giuliomoro@0 1629 this.initialise = function(position)
giuliomoro@0 1630 {
giuliomoro@0 1631 if (this.initialPosition == -1) {
giuliomoro@0 1632 this.initialPosition = position;
giuliomoro@0 1633 this.moved(0,position);
giuliomoro@0 1634 }
giuliomoro@0 1635 };
giuliomoro@0 1636
giuliomoro@0 1637 this.moved = function(time,position)
giuliomoro@0 1638 {
giuliomoro@0 1639 if (time > 0) {this.wasMoved = true;}
giuliomoro@0 1640 this.movementTracker[this.movementTracker.length] = [time, position];
giuliomoro@0 1641 };
giuliomoro@0 1642
giuliomoro@0 1643 this.startListening = function(time)
giuliomoro@0 1644 {
giuliomoro@0 1645 if (this.listenHold == false)
giuliomoro@0 1646 {
giuliomoro@0 1647 this.wasListenedTo = true;
giuliomoro@0 1648 this.listenStart = time;
giuliomoro@0 1649 this.listenHold = true;
giuliomoro@0 1650
giuliomoro@0 1651 var evnt = document.createElement('event');
giuliomoro@0 1652 var testTime = document.createElement('testTime');
giuliomoro@0 1653 testTime.setAttribute('start',time);
giuliomoro@0 1654 var bufferTime = document.createElement('bufferTime');
giuliomoro@0 1655 bufferTime.setAttribute('start',this.parent.getCurrentPosition());
giuliomoro@0 1656 evnt.appendChild(testTime);
giuliomoro@0 1657 evnt.appendChild(bufferTime);
giuliomoro@0 1658 this.listenTracker.push(evnt);
giuliomoro@0 1659
giuliomoro@0 1660 console.log('slider ' + this.parent.id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id
giuliomoro@0 1661 }
giuliomoro@0 1662 };
giuliomoro@0 1663
giuliomoro@0 1664 this.stopListening = function(time,bufferStopTime)
giuliomoro@0 1665 {
giuliomoro@0 1666 if (this.listenHold == true)
giuliomoro@0 1667 {
giuliomoro@0 1668 var diff = time - this.listenStart;
giuliomoro@0 1669 this.listenedTimer += (diff);
giuliomoro@0 1670 this.listenStart = 0;
giuliomoro@0 1671 this.listenHold = false;
giuliomoro@0 1672
giuliomoro@0 1673 var evnt = this.listenTracker[this.listenTracker.length-1];
giuliomoro@0 1674 var testTime = evnt.getElementsByTagName('testTime')[0];
giuliomoro@0 1675 var bufferTime = evnt.getElementsByTagName('bufferTime')[0];
giuliomoro@0 1676 testTime.setAttribute('stop',time);
giuliomoro@0 1677 if (bufferStopTime == undefined) {
giuliomoro@0 1678 bufferTime.setAttribute('stop',this.parent.getCurrentPosition());
giuliomoro@0 1679 } else {
giuliomoro@0 1680 bufferTime.setAttribute('stop',bufferStopTime);
giuliomoro@0 1681 }
giuliomoro@0 1682 console.log('slider ' + this.parent.id + ' played for (' + diff + ')'); // DEBUG/SAFETY: show played slider id
giuliomoro@0 1683 }
giuliomoro@0 1684 };
giuliomoro@0 1685
giuliomoro@0 1686 this.exportXMLDOM = function() {
giuliomoro@0 1687 var storeDOM = [];
giuliomoro@0 1688 if (audioEngineContext.metric.enableElementTimer) {
giuliomoro@0 1689 var mElementTimer = storage.document.createElement('metricresult');
giuliomoro@0 1690 mElementTimer.setAttribute('name','enableElementTimer');
giuliomoro@0 1691 mElementTimer.textContent = this.listenedTimer;
giuliomoro@0 1692 storeDOM.push(mElementTimer);
giuliomoro@0 1693 }
giuliomoro@0 1694 if (audioEngineContext.metric.enableElementTracker) {
giuliomoro@0 1695 var elementTrackerFull = storage.document.createElement('metricResult');
giuliomoro@0 1696 elementTrackerFull.setAttribute('name','elementTrackerFull');
giuliomoro@0 1697 for (var k=0; k<this.movementTracker.length; k++)
giuliomoro@0 1698 {
giuliomoro@0 1699 var timePos = storage.document.createElement('movement');
giuliomoro@0 1700 timePos.setAttribute("time",this.movementTracker[k][0]);
giuliomoro@0 1701 timePos.setAttribute("value",this.movementTracker[k][1]);
giuliomoro@0 1702 elementTrackerFull.appendChild(timePos);
giuliomoro@0 1703 }
giuliomoro@0 1704 storeDOM.push(elementTrackerFull);
giuliomoro@0 1705 }
giuliomoro@0 1706 if (audioEngineContext.metric.enableElementListenTracker) {
giuliomoro@0 1707 var elementListenTracker = storage.document.createElement('metricResult');
giuliomoro@0 1708 elementListenTracker.setAttribute('name','elementListenTracker');
giuliomoro@0 1709 for (var k=0; k<this.listenTracker.length; k++) {
giuliomoro@0 1710 elementListenTracker.appendChild(this.listenTracker[k]);
giuliomoro@0 1711 }
giuliomoro@0 1712 storeDOM.push(elementListenTracker);
giuliomoro@0 1713 }
giuliomoro@0 1714 if (audioEngineContext.metric.enableElementInitialPosition) {
giuliomoro@0 1715 var elementInitial = storage.document.createElement('metricResult');
giuliomoro@0 1716 elementInitial.setAttribute('name','elementInitialPosition');
giuliomoro@0 1717 elementInitial.textContent = this.initialPosition;
giuliomoro@0 1718 storeDOM.push(elementInitial);
giuliomoro@0 1719 }
giuliomoro@0 1720 if (audioEngineContext.metric.enableFlagListenedTo) {
giuliomoro@0 1721 var flagListenedTo = storage.document.createElement('metricResult');
giuliomoro@0 1722 flagListenedTo.setAttribute('name','elementFlagListenedTo');
giuliomoro@0 1723 flagListenedTo.textContent = this.wasListenedTo;
giuliomoro@0 1724 storeDOM.push(flagListenedTo);
giuliomoro@0 1725 }
giuliomoro@0 1726 if (audioEngineContext.metric.enableFlagMoved) {
giuliomoro@0 1727 var flagMoved = storage.document.createElement('metricResult');
giuliomoro@0 1728 flagMoved.setAttribute('name','elementFlagMoved');
giuliomoro@0 1729 flagMoved.textContent = this.wasMoved;
giuliomoro@0 1730 storeDOM.push(flagMoved);
giuliomoro@0 1731 }
giuliomoro@0 1732 if (audioEngineContext.metric.enableFlagComments) {
giuliomoro@0 1733 var flagComments = storage.document.createElement('metricResult');
giuliomoro@0 1734 flagComments.setAttribute('name','elementFlagComments');
giuliomoro@0 1735 if (this.parent.commentDOM == null)
giuliomoro@0 1736 {flag.textContent = 'false';}
giuliomoro@0 1737 else if (this.parent.commentDOM.textContent.length == 0)
giuliomoro@0 1738 {flag.textContent = 'false';}
giuliomoro@0 1739 else
giuliomoro@0 1740 {flag.textContet = 'true';}
giuliomoro@0 1741 storeDOM.push(flagComments);
giuliomoro@0 1742 }
giuliomoro@0 1743 return storeDOM;
giuliomoro@0 1744 };
giuliomoro@0 1745 }
giuliomoro@0 1746
giuliomoro@0 1747 function randomiseOrder(input)
giuliomoro@0 1748 {
giuliomoro@0 1749 // This takes an array of information and randomises the order
giuliomoro@0 1750 var N = input.length;
giuliomoro@0 1751
giuliomoro@0 1752 var inputSequence = []; // For safety purposes: keep track of randomisation
giuliomoro@0 1753 for (var counter = 0; counter < N; ++counter)
giuliomoro@0 1754 inputSequence.push(counter) // Fill array
giuliomoro@0 1755 var inputSequenceClone = inputSequence.slice(0);
giuliomoro@0 1756
giuliomoro@0 1757 var holdArr = [];
giuliomoro@0 1758 var outputSequence = [];
giuliomoro@0 1759 for (var n=0; n<N; n++)
giuliomoro@0 1760 {
giuliomoro@0 1761 // First pick a random number
giuliomoro@0 1762 var r = Math.random();
giuliomoro@0 1763 // Multiply and floor by the number of elements left
giuliomoro@0 1764 r = Math.floor(r*input.length);
giuliomoro@0 1765 // Pick out that element and delete from the array
giuliomoro@0 1766 holdArr.push(input.splice(r,1)[0]);
giuliomoro@0 1767 // Do the same with sequence
giuliomoro@0 1768 outputSequence.push(inputSequence.splice(r,1)[0]);
giuliomoro@0 1769 }
giuliomoro@0 1770 console.log(inputSequenceClone.toString()); // print original array to console
giuliomoro@0 1771 console.log(outputSequence.toString()); // print randomised array to console
giuliomoro@0 1772 return holdArr;
giuliomoro@0 1773 }
giuliomoro@0 1774
giuliomoro@0 1775 function returnDateNode()
giuliomoro@0 1776 {
giuliomoro@0 1777 // Create an XML Node for the Date and Time a test was conducted
giuliomoro@0 1778 // Structure is
giuliomoro@0 1779 // <datetime>
giuliomoro@0 1780 // <date year="##" month="##" day="##">DD/MM/YY</date>
giuliomoro@0 1781 // <time hour="##" minute="##" sec="##">HH:MM:SS</time>
giuliomoro@0 1782 // </datetime>
giuliomoro@0 1783 var dateTime = new Date();
giuliomoro@0 1784 var year = document.createAttribute('year');
giuliomoro@0 1785 var month = document.createAttribute('month');
giuliomoro@0 1786 var day = document.createAttribute('day');
giuliomoro@0 1787 var hour = document.createAttribute('hour');
giuliomoro@0 1788 var minute = document.createAttribute('minute');
giuliomoro@0 1789 var secs = document.createAttribute('secs');
giuliomoro@0 1790
giuliomoro@0 1791 year.nodeValue = dateTime.getFullYear();
giuliomoro@0 1792 month.nodeValue = dateTime.getMonth()+1;
giuliomoro@0 1793 day.nodeValue = dateTime.getDate();
giuliomoro@0 1794 hour.nodeValue = dateTime.getHours();
giuliomoro@0 1795 minute.nodeValue = dateTime.getMinutes();
giuliomoro@0 1796 secs.nodeValue = dateTime.getSeconds();
giuliomoro@0 1797
giuliomoro@0 1798 var hold = document.createElement("datetime");
giuliomoro@0 1799 var date = document.createElement("date");
giuliomoro@0 1800 date.textContent = year.nodeValue+'/'+month.nodeValue+'/'+day.nodeValue;
giuliomoro@0 1801 var time = document.createElement("time");
giuliomoro@0 1802 time.textContent = hour.nodeValue+':'+minute.nodeValue+':'+secs.nodeValue;
giuliomoro@0 1803
giuliomoro@0 1804 date.setAttributeNode(year);
giuliomoro@0 1805 date.setAttributeNode(month);
giuliomoro@0 1806 date.setAttributeNode(day);
giuliomoro@0 1807 time.setAttributeNode(hour);
giuliomoro@0 1808 time.setAttributeNode(minute);
giuliomoro@0 1809 time.setAttributeNode(secs);
giuliomoro@0 1810
giuliomoro@0 1811 hold.appendChild(date);
giuliomoro@0 1812 hold.appendChild(time);
giuliomoro@0 1813 return hold;
giuliomoro@0 1814
giuliomoro@0 1815 }
giuliomoro@0 1816
giuliomoro@0 1817 function Specification() {
giuliomoro@0 1818 // Handles the decoding of the project specification XML into a simple JavaScript Object.
giuliomoro@0 1819
giuliomoro@0 1820 this.interface = null;
giuliomoro@0 1821 this.projectReturn = "null";
giuliomoro@0 1822 this.randomiseOrder = null;
giuliomoro@0 1823 this.testPages = null;
giuliomoro@0 1824 this.pages = [];
giuliomoro@0 1825 this.metrics = null;
giuliomoro@0 1826 this.interfaces = null;
giuliomoro@0 1827 this.loudness = null;
giuliomoro@0 1828 this.errors = [];
giuliomoro@0 1829 this.schema = null;
giuliomoro@0 1830
giuliomoro@0 1831 this.processAttribute = function(attribute,schema)
giuliomoro@0 1832 {
giuliomoro@0 1833 // attribute is the string returned from getAttribute on the XML
giuliomoro@0 1834 // schema is the <xs:attribute> node
giuliomoro@0 1835 if (schema.getAttribute('name') == undefined && schema.getAttribute('ref') != undefined)
giuliomoro@0 1836 {
giuliomoro@0 1837 schema = this.schema.getAllElementsByName(schema.getAttribute('ref'))[0];
giuliomoro@0 1838 }
giuliomoro@0 1839 var defaultOpt = schema.getAttribute('default');
giuliomoro@0 1840 if (attribute == null) {
giuliomoro@0 1841 attribute = defaultOpt;
giuliomoro@0 1842 }
giuliomoro@0 1843 var dataType = schema.getAttribute('type');
giuliomoro@0 1844 if (typeof dataType == "string") { dataType = dataType.substr(3);}
giuliomoro@0 1845 else {dataType = "string";}
giuliomoro@0 1846 if (attribute == null)
giuliomoro@0 1847 {
giuliomoro@0 1848 return attribute;
giuliomoro@0 1849 }
giuliomoro@0 1850 switch(dataType)
giuliomoro@0 1851 {
giuliomoro@0 1852 case "boolean":
giuliomoro@0 1853 if (attribute == 'true'){attribute = true;}else{attribute=false;}
giuliomoro@0 1854 break;
giuliomoro@0 1855 case "negativeInteger":
giuliomoro@0 1856 case "positiveInteger":
giuliomoro@0 1857 case "nonNegativeInteger":
giuliomoro@0 1858 case "nonPositiveInteger":
giuliomoro@0 1859 case "integer":
giuliomoro@0 1860 case "decimal":
giuliomoro@0 1861 case "short":
giuliomoro@0 1862 attribute = Number(attribute);
giuliomoro@0 1863 break;
giuliomoro@0 1864 case "string":
giuliomoro@0 1865 default:
giuliomoro@0 1866 attribute = String(attribute);
giuliomoro@0 1867 break;
giuliomoro@0 1868 }
giuliomoro@0 1869 return attribute;
giuliomoro@0 1870 };
giuliomoro@0 1871
giuliomoro@0 1872 this.decode = function(projectXML) {
giuliomoro@0 1873 this.errors = [];
giuliomoro@0 1874 // projectXML - DOM Parsed document
giuliomoro@0 1875 this.projectXML = projectXML.childNodes[0];
giuliomoro@0 1876 var setupNode = projectXML.getElementsByTagName('setup')[0];
giuliomoro@0 1877 var schemaSetup = this.schema.getAllElementsByName('setup')[0];
giuliomoro@0 1878 // First decode the attributes
giuliomoro@0 1879 var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
giuliomoro@0 1880 for (var i in attributes)
giuliomoro@0 1881 {
giuliomoro@0 1882 if (isNaN(Number(i)) == true){break;}
giuliomoro@0 1883 var attributeName = attributes[i].getAttribute('name');
giuliomoro@0 1884 var projectAttr = setupNode.getAttribute(attributeName);
giuliomoro@0 1885 projectAttr = this.processAttribute(projectAttr,attributes[i]);
giuliomoro@0 1886 switch(typeof projectAttr)
giuliomoro@0 1887 {
giuliomoro@0 1888 case "number":
giuliomoro@0 1889 case "boolean":
giuliomoro@0 1890 eval('this.'+attributeName+' = '+projectAttr);
giuliomoro@0 1891 break;
giuliomoro@0 1892 case "string":
giuliomoro@0 1893 eval('this.'+attributeName+' = "'+projectAttr+'"');
giuliomoro@0 1894 break;
giuliomoro@0 1895 }
giuliomoro@0 1896
giuliomoro@0 1897 }
giuliomoro@0 1898
giuliomoro@0 1899 this.metrics = new this.metricNode();
giuliomoro@0 1900
giuliomoro@0 1901 this.metrics.decode(this,setupNode.getElementsByTagName('metric')[0]);
giuliomoro@0 1902
giuliomoro@0 1903 // Now process the survey node options
giuliomoro@0 1904 var survey = setupNode.getElementsByTagName('survey');
giuliomoro@0 1905 for (var i in survey) {
giuliomoro@0 1906 if (isNaN(Number(i)) == true){break;}
giuliomoro@0 1907 var location = survey[i].getAttribute('location');
giuliomoro@0 1908 if (location == 'pre' || location == 'before')
giuliomoro@0 1909 {
giuliomoro@0 1910 if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
giuliomoro@0 1911 else {
giuliomoro@0 1912 this.preTest = new this.surveyNode();
giuliomoro@0 1913 this.preTest.decode(this,survey[i]);
giuliomoro@0 1914 }
giuliomoro@0 1915 } else if (location == 'post' || location == 'after') {
giuliomoro@0 1916 if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
giuliomoro@0 1917 else {
giuliomoro@0 1918 this.postTest = new this.surveyNode();
giuliomoro@0 1919 this.postTest.decode(this,survey[i]);
giuliomoro@0 1920 }
giuliomoro@0 1921 }
giuliomoro@0 1922 }
giuliomoro@0 1923
giuliomoro@0 1924 var interfaceNode = setupNode.getElementsByTagName('interface');
giuliomoro@0 1925 if (interfaceNode.length > 1)
giuliomoro@0 1926 {
giuliomoro@0 1927 this.errors.push("Only one <interface> node in the <setup> node allowed! Others except first ingnored!");
giuliomoro@0 1928 }
giuliomoro@0 1929 this.interfaces = new this.interfaceNode();
giuliomoro@0 1930 if (interfaceNode.length != 0)
giuliomoro@0 1931 {
giuliomoro@0 1932 interfaceNode = interfaceNode[0];
giuliomoro@0 1933 this.interfaces.decode(this,interfaceNode,this.schema.getAllElementsByName('interface')[1]);
giuliomoro@0 1934 }
giuliomoro@0 1935
giuliomoro@0 1936 // Page tags
giuliomoro@0 1937 var pageTags = projectXML.getElementsByTagName('page');
giuliomoro@0 1938 var pageSchema = this.schema.getAllElementsByName('page')[0];
giuliomoro@0 1939 for (var i=0; i<pageTags.length; i++)
giuliomoro@0 1940 {
giuliomoro@0 1941 var node = new this.page();
giuliomoro@0 1942 node.decode(this,pageTags[i],pageSchema);
giuliomoro@0 1943 this.pages.push(node);
giuliomoro@0 1944 }
giuliomoro@0 1945 };
giuliomoro@0 1946
giuliomoro@0 1947 this.encode = function()
giuliomoro@0 1948 {
giuliomoro@0 1949 var RootDocument = document.implementation.createDocument(null,"waet");
giuliomoro@0 1950 var root = RootDocument.children[0];
giuliomoro@0 1951 root.setAttribute("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
giuliomoro@0 1952 root.setAttribute("xsi:noNamespaceSchemaLocation","test-schema.xsd");
giuliomoro@0 1953 // Build setup node
giuliomoro@0 1954 var setup = RootDocument.createElement("setup");
giuliomoro@0 1955 var schemaSetup = this.schema.getAllElementsByName('setup')[0];
giuliomoro@0 1956 // First decode the attributes
giuliomoro@0 1957 var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
giuliomoro@0 1958 for (var i=0; i<attributes.length; i++)
giuliomoro@0 1959 {
giuliomoro@0 1960 var name = attributes[i].getAttribute("name");
giuliomoro@0 1961 if (name == undefined) {
giuliomoro@0 1962 name = attributes[i].getAttribute("ref");
giuliomoro@0 1963 }
giuliomoro@0 1964 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
giuliomoro@0 1965 {
giuliomoro@0 1966 eval("setup.setAttribute('"+name+"',this."+name+")");
giuliomoro@0 1967 }
giuliomoro@0 1968 }
giuliomoro@0 1969 root.appendChild(setup);
giuliomoro@0 1970 // Survey node
giuliomoro@0 1971 setup.appendChild(this.preTest.encode(RootDocument));
giuliomoro@0 1972 setup.appendChild(this.postTest.encode(RootDocument));
giuliomoro@0 1973 setup.appendChild(this.metrics.encode(RootDocument));
giuliomoro@0 1974 setup.appendChild(this.interfaces.encode(RootDocument));
giuliomoro@0 1975 for (var page of this.pages)
giuliomoro@0 1976 {
giuliomoro@0 1977 root.appendChild(page.encode(RootDocument));
giuliomoro@0 1978 }
giuliomoro@0 1979 return RootDocument;
giuliomoro@0 1980 };
giuliomoro@0 1981
giuliomoro@0 1982 this.surveyNode = function() {
giuliomoro@0 1983 this.location = null;
giuliomoro@0 1984 this.options = [];
giuliomoro@0 1985 this.schema = specification.schema.getAllElementsByName('survey')[0];
giuliomoro@0 1986
giuliomoro@0 1987 this.OptionNode = function() {
giuliomoro@0 1988 this.type = undefined;
giuliomoro@0 1989 this.schema = specification.schema.getAllElementsByName('surveyentry')[0];
giuliomoro@0 1990 this.id = undefined;
giuliomoro@0 1991 this.name = undefined;
giuliomoro@0 1992 this.mandatory = undefined;
giuliomoro@0 1993 this.statement = undefined;
giuliomoro@0 1994 this.boxsize = undefined;
giuliomoro@0 1995 this.options = [];
giuliomoro@0 1996 this.min = undefined;
giuliomoro@0 1997 this.max = undefined;
giuliomoro@0 1998 this.step = undefined;
giuliomoro@0 1999
giuliomoro@0 2000 this.decode = function(parent,child)
giuliomoro@0 2001 {
giuliomoro@0 2002 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
giuliomoro@0 2003 for (var i in attributeMap){
giuliomoro@0 2004 if(isNaN(Number(i)) == true){break;}
giuliomoro@0 2005 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
giuliomoro@0 2006 var projectAttr = child.getAttribute(attributeName);
giuliomoro@0 2007 projectAttr = parent.processAttribute(projectAttr,attributeMap[i]);
giuliomoro@0 2008 switch(typeof projectAttr)
giuliomoro@0 2009 {
giuliomoro@0 2010 case "number":
giuliomoro@0 2011 case "boolean":
giuliomoro@0 2012 eval('this.'+attributeName+' = '+projectAttr);
giuliomoro@0 2013 break;
giuliomoro@0 2014 case "string":
giuliomoro@0 2015 eval('this.'+attributeName+' = "'+projectAttr+'"');
giuliomoro@0 2016 break;
giuliomoro@0 2017 }
giuliomoro@0 2018 }
giuliomoro@0 2019 this.statement = child.getElementsByTagName('statement')[0].textContent;
giuliomoro@0 2020 if (this.type == "checkbox" || this.type == "radio") {
giuliomoro@0 2021 var children = child.getElementsByTagName('option');
giuliomoro@0 2022 if (children.length == null) {
giuliomoro@0 2023 console.log('Malformed' +child.nodeName+ 'entry');
giuliomoro@0 2024 this.statement = 'Malformed' +child.nodeName+ 'entry';
giuliomoro@0 2025 this.type = 'statement';
giuliomoro@0 2026 } else {
giuliomoro@0 2027 this.options = [];
giuliomoro@0 2028 for (var i in children)
giuliomoro@0 2029 {
giuliomoro@0 2030 if (isNaN(Number(i))==true){break;}
giuliomoro@0 2031 this.options.push({
giuliomoro@0 2032 name: children[i].getAttribute('name'),
giuliomoro@0 2033 text: children[i].textContent
giuliomoro@0 2034 });
giuliomoro@0 2035 }
giuliomoro@0 2036 }
giuliomoro@0 2037 }
giuliomoro@0 2038 };
giuliomoro@0 2039
giuliomoro@0 2040 this.exportXML = function(doc)
giuliomoro@0 2041 {
giuliomoro@0 2042 var node = doc.createElement('surveyentry');
giuliomoro@0 2043 node.setAttribute('type',this.type);
giuliomoro@0 2044 var statement = doc.createElement('statement');
giuliomoro@0 2045 statement.textContent = this.statement;
giuliomoro@0 2046 node.appendChild(statement);
giuliomoro@0 2047 if (this.type != "statement") {
giuliomoro@0 2048 node.id = this.id;
giuliomoro@0 2049 if (this.name != undefined) { node.setAttribute("name",this.name);}
giuliomoro@0 2050 if (this.mandatory != undefined) { node.setAttribute("mandatory",this.mandatory);}
giuliomoro@0 2051 switch(this.type)
giuliomoro@0 2052 {
giuliomoro@0 2053 case "question":
giuliomoro@0 2054 if (this.boxsize != undefined) {node.setAttribute("boxsize",this.boxsize);}
giuliomoro@0 2055 break;
giuliomoro@0 2056 case "number":
giuliomoro@0 2057 if (this.min != undefined) {node.setAttribute("min", this.min);}
giuliomoro@0 2058 if (this.max != undefined) {node.setAttribute("max", this.max);}
giuliomoro@0 2059 break;
giuliomoro@0 2060 case "checkbox":
giuliomoro@0 2061 case "radio":
giuliomoro@0 2062 for (var i=0; i<this.options.length; i++)
giuliomoro@0 2063 {
giuliomoro@0 2064 var option = this.options[i];
giuliomoro@0 2065 var optionNode = doc.createElement("option");
giuliomoro@0 2066 optionNode.setAttribute("name",option.name);
giuliomoro@0 2067 optionNode.textContent = option.text;
giuliomoro@0 2068 node.appendChild(optionNode);
giuliomoro@0 2069 }
giuliomoro@0 2070 break;
giuliomoro@0 2071 }
giuliomoro@0 2072 }
giuliomoro@0 2073 return node;
giuliomoro@0 2074 };
giuliomoro@0 2075 };
giuliomoro@0 2076 this.decode = function(parent,xml) {
giuliomoro@0 2077 this.location = xml.getAttribute('location');
giuliomoro@0 2078 if (this.location == 'before'){this.location = 'pre';}
giuliomoro@0 2079 else if (this.location == 'after'){this.location = 'post';}
giuliomoro@0 2080 for (var i in xml.children)
giuliomoro@0 2081 {
giuliomoro@0 2082 if(isNaN(Number(i))==true){break;}
giuliomoro@0 2083 var node = new this.OptionNode();
giuliomoro@0 2084 node.decode(parent,xml.children[i]);
giuliomoro@0 2085 this.options.push(node);
giuliomoro@0 2086 }
giuliomoro@0 2087 };
giuliomoro@0 2088 this.encode = function(doc) {
giuliomoro@0 2089 var node = doc.createElement('survey');
giuliomoro@0 2090 node.setAttribute('location',this.location);
giuliomoro@0 2091 for (var i=0; i<this.options.length; i++)
giuliomoro@0 2092 {
giuliomoro@0 2093 node.appendChild(this.options[i].exportXML(doc));
giuliomoro@0 2094 }
giuliomoro@0 2095 return node;
giuliomoro@0 2096 };
giuliomoro@0 2097 };
giuliomoro@0 2098
giuliomoro@0 2099 this.interfaceNode = function()
giuliomoro@0 2100 {
giuliomoro@0 2101 this.title = null;
giuliomoro@0 2102 this.name = null;
giuliomoro@0 2103 this.options = [];
giuliomoro@0 2104 this.scales = [];
giuliomoro@0 2105 this.schema = specification.schema.getAllElementsByName('interface')[1];
giuliomoro@0 2106
giuliomoro@0 2107 this.decode = function(parent,xml) {
giuliomoro@0 2108 this.name = xml.getAttribute('name');
giuliomoro@0 2109 var titleNode = xml.getElementsByTagName('title');
giuliomoro@0 2110 if (titleNode.length == 1)
giuliomoro@0 2111 {
giuliomoro@0 2112 this.title = titleNode[0].textContent;
giuliomoro@0 2113 }
giuliomoro@0 2114 var interfaceOptionNodes = xml.getElementsByTagName('interfaceoption');
giuliomoro@0 2115 // Extract interfaceoption node schema
giuliomoro@0 2116 var interfaceOptionNodeSchema = this.schema.getAllElementsByName('interfaceoption')[0];
giuliomoro@0 2117 var attributeMap = interfaceOptionNodeSchema.getAllElementsByTagName('xs:attribute');
giuliomoro@0 2118 for (var i=0; i<interfaceOptionNodes.length; i++)
giuliomoro@0 2119 {
giuliomoro@0 2120 var ioNode = interfaceOptionNodes[i];
giuliomoro@0 2121 var option = {};
giuliomoro@0 2122 for (var j=0; j<attributeMap.length; j++)
giuliomoro@0 2123 {
giuliomoro@0 2124 var attributeName = attributeMap[j].getAttribute('name') || attributeMap[j].getAttribute('ref');
giuliomoro@0 2125 var projectAttr = ioNode.getAttribute(attributeName);
giuliomoro@0 2126 projectAttr = parent.processAttribute(projectAttr,attributeMap[j]);
giuliomoro@0 2127 switch(typeof projectAttr)
giuliomoro@0 2128 {
giuliomoro@0 2129 case "number":
giuliomoro@0 2130 case "boolean":
giuliomoro@0 2131 eval('option.'+attributeName+' = '+projectAttr);
giuliomoro@0 2132 break;
giuliomoro@0 2133 case "string":
giuliomoro@0 2134 eval('option.'+attributeName+' = "'+projectAttr+'"');
giuliomoro@0 2135 break;
giuliomoro@0 2136 }
giuliomoro@0 2137 }
giuliomoro@0 2138 this.options.push(option);
giuliomoro@0 2139 }
giuliomoro@0 2140
giuliomoro@0 2141 // Now the scales nodes
giuliomoro@0 2142 var scaleParent = xml.getElementsByTagName('scales');
giuliomoro@0 2143 if (scaleParent.length == 1) {
giuliomoro@0 2144 scaleParent = scaleParent[0];
giuliomoro@0 2145 for (var i=0; i<scaleParent.children.length; i++) {
giuliomoro@0 2146 var child = scaleParent.children[i];
giuliomoro@0 2147 this.scales.push({
giuliomoro@0 2148 text: child.textContent,
giuliomoro@0 2149 position: Number(child.getAttribute('position'))
giuliomoro@0 2150 });
giuliomoro@0 2151 }
giuliomoro@0 2152 }
giuliomoro@0 2153 };
giuliomoro@0 2154
giuliomoro@0 2155 this.encode = function(doc) {
giuliomoro@0 2156 var node = doc.createElement("interface");
giuliomoro@0 2157 if (typeof name == "string")
giuliomoro@0 2158 node.setAttribute("name",this.name);
giuliomoro@0 2159 for (var option of this.options)
giuliomoro@0 2160 {
giuliomoro@0 2161 var child = doc.createElement("interfaceoption");
giuliomoro@0 2162 child.setAttribute("type",option.type);
giuliomoro@0 2163 child.setAttribute("name",option.name);
giuliomoro@0 2164 node.appendChild(child);
giuliomoro@0 2165 }
giuliomoro@0 2166 if (this.scales.length != 0) {
giuliomoro@0 2167 var scales = doc.createElement("scales");
giuliomoro@0 2168 for (var scale of this.scales)
giuliomoro@0 2169 {
giuliomoro@0 2170 var child = doc.createElement("scalelabel");
giuliomoro@0 2171 child.setAttribute("position",scale.position);
giuliomoro@0 2172 child.textContent = scale.text;
giuliomoro@0 2173 scales.appendChild(child);
giuliomoro@0 2174 }
giuliomoro@0 2175 node.appendChild(scales);
giuliomoro@0 2176 }
giuliomoro@0 2177 return node;
giuliomoro@0 2178 };
giuliomoro@0 2179 };
giuliomoro@0 2180
giuliomoro@0 2181 this.metricNode = function() {
giuliomoro@0 2182 this.enabled = [];
giuliomoro@0 2183 this.decode = function(parent, xml) {
giuliomoro@0 2184 var children = xml.getElementsByTagName('metricenable');
giuliomoro@0 2185 for (var i in children) {
giuliomoro@0 2186 if (isNaN(Number(i)) == true){break;}
giuliomoro@0 2187 this.enabled.push(children[i].textContent);
giuliomoro@0 2188 }
giuliomoro@0 2189 }
giuliomoro@0 2190 this.encode = function(doc) {
giuliomoro@0 2191 var node = doc.createElement('metric');
giuliomoro@0 2192 for (var i in this.enabled)
giuliomoro@0 2193 {
giuliomoro@0 2194 if (isNaN(Number(i)) == true){break;}
giuliomoro@0 2195 var child = doc.createElement('metricenable');
giuliomoro@0 2196 child.textContent = this.enabled[i];
giuliomoro@0 2197 node.appendChild(child);
giuliomoro@0 2198 }
giuliomoro@0 2199 return node;
giuliomoro@0 2200 }
giuliomoro@0 2201 }
giuliomoro@0 2202
giuliomoro@0 2203 this.page = function() {
giuliomoro@0 2204 this.presentedId = undefined;
giuliomoro@0 2205 this.id = undefined;
giuliomoro@0 2206 this.hostURL = undefined;
giuliomoro@0 2207 this.randomiseOrder = undefined;
giuliomoro@0 2208 this.loop = undefined;
giuliomoro@0 2209 this.showElementComments = undefined;
giuliomoro@0 2210 this.outsideReference = null;
giuliomoro@0 2211 this.loudness = null;
giuliomoro@0 2212 this.label = null;
giuliomoro@0 2213 this.preTest = null;
giuliomoro@0 2214 this.postTest = null;
giuliomoro@0 2215 this.interfaces = [];
giuliomoro@0 2216 this.commentBoxPrefix = "Comment on track";
giuliomoro@0 2217 this.audioElements = [];
giuliomoro@0 2218 this.commentQuestions = [];
giuliomoro@0 2219 this.schema = specification.schema.getAllElementsByName("page")[0];
giuliomoro@0 2220
giuliomoro@0 2221 this.decode = function(parent,xml)
giuliomoro@0 2222 {
giuliomoro@0 2223 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
giuliomoro@0 2224 for (var i=0; i<attributeMap.length; i++)
giuliomoro@0 2225 {
giuliomoro@0 2226 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
giuliomoro@0 2227 var projectAttr = xml.getAttribute(attributeName);
giuliomoro@0 2228 projectAttr = parent.processAttribute(projectAttr,attributeMap[i]);
giuliomoro@0 2229 switch(typeof projectAttr)
giuliomoro@0 2230 {
giuliomoro@0 2231 case "number":
giuliomoro@0 2232 case "boolean":
giuliomoro@0 2233 eval('this.'+attributeName+' = '+projectAttr);
giuliomoro@0 2234 break;
giuliomoro@0 2235 case "string":
giuliomoro@0 2236 eval('this.'+attributeName+' = "'+projectAttr+'"');
giuliomoro@0 2237 break;
giuliomoro@0 2238 }
giuliomoro@0 2239 }
giuliomoro@0 2240
giuliomoro@0 2241 // Get the Comment Box Prefix
giuliomoro@0 2242 var CBP = xml.getElementsByTagName('commentboxprefix');
giuliomoro@0 2243 if (CBP.length != 0) {
giuliomoro@0 2244 this.commentBoxPrefix = CBP[0].textContent;
giuliomoro@0 2245 }
giuliomoro@0 2246
giuliomoro@0 2247 // Now decode the interfaces
giuliomoro@0 2248 var interfaceNode = xml.getElementsByTagName('interface');
giuliomoro@0 2249 for (var i=0; i<interfaceNode.length; i++)
giuliomoro@0 2250 {
giuliomoro@0 2251 var node = new parent.interfaceNode();
giuliomoro@0 2252 node.decode(this,interfaceNode[i],parent.schema.getAllElementsByName('interface')[1]);
giuliomoro@0 2253 this.interfaces.push(node);
giuliomoro@0 2254 }
giuliomoro@0 2255
giuliomoro@0 2256 // Now process the survey node options
giuliomoro@0 2257 var survey = xml.getElementsByTagName('survey');
giuliomoro@0 2258 var surveySchema = parent.schema.getAllElementsByName('survey')[0];
giuliomoro@0 2259 for (var i in survey) {
giuliomoro@0 2260 if (isNaN(Number(i)) == true){break;}
giuliomoro@0 2261 var location = survey[i].getAttribute('location');
giuliomoro@0 2262 if (location == 'pre' || location == 'before')
giuliomoro@0 2263 {
giuliomoro@0 2264 if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
giuliomoro@0 2265 else {
giuliomoro@0 2266 this.preTest = new parent.surveyNode();
giuliomoro@0 2267 this.preTest.decode(parent,survey[i],surveySchema);
giuliomoro@0 2268 }
giuliomoro@0 2269 } else if (location == 'post' || location == 'after') {
giuliomoro@0 2270 if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
giuliomoro@0 2271 else {
giuliomoro@0 2272 this.postTest = new parent.surveyNode();
giuliomoro@0 2273 this.postTest.decode(parent,survey[i],surveySchema);
giuliomoro@0 2274 }
giuliomoro@0 2275 }
giuliomoro@0 2276 }
giuliomoro@0 2277
giuliomoro@0 2278 // Now process the audioelement tags
giuliomoro@0 2279 var audioElements = xml.getElementsByTagName('audioelement');
giuliomoro@0 2280 for (var i=0; i<audioElements.length; i++)
giuliomoro@0 2281 {
giuliomoro@0 2282 var node = new this.audioElementNode();
giuliomoro@0 2283 node.decode(this,audioElements[i]);
giuliomoro@0 2284 this.audioElements.push(node);
giuliomoro@0 2285 }
giuliomoro@0 2286
giuliomoro@0 2287 // Now decode the commentquestions
giuliomoro@0 2288 var commentQuestions = xml.getElementsByTagName('commentquestion');
giuliomoro@0 2289 for (var i=0; i<commentQuestions.length; i++)
giuliomoro@0 2290 {
giuliomoro@0 2291 var node = new this.commentQuestionNode();
giuliomoro@0 2292 node.decode(parent,commentQuestions[i]);
giuliomoro@0 2293 this.commentQuestions.push(node);
giuliomoro@0 2294 }
giuliomoro@0 2295 };
giuliomoro@0 2296
giuliomoro@0 2297 this.encode = function(root)
giuliomoro@0 2298 {
giuliomoro@0 2299 var AHNode = root.createElement("page");
giuliomoro@0 2300 // First decode the attributes
giuliomoro@0 2301 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
giuliomoro@0 2302 for (var i=0; i<attributes.length; i++)
giuliomoro@0 2303 {
giuliomoro@0 2304 var name = attributes[i].getAttribute("name");
giuliomoro@0 2305 if (name == undefined) {
giuliomoro@0 2306 name = attributes[i].getAttribute("ref");
giuliomoro@0 2307 }
giuliomoro@0 2308 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
giuliomoro@0 2309 {
giuliomoro@0 2310 eval("AHNode.setAttribute('"+name+"',this."+name+")");
giuliomoro@0 2311 }
giuliomoro@0 2312 }
giuliomoro@0 2313 if(this.loudness != null) {AHNode.setAttribute("loudness",this.loudness);}
giuliomoro@0 2314 // <commentboxprefix>
giuliomoro@0 2315 var commentboxprefix = root.createElement("commentboxprefix");
giuliomoro@0 2316 commentboxprefix.textContent = this.commentBoxPrefix;
giuliomoro@0 2317 AHNode.appendChild(commentboxprefix);
giuliomoro@0 2318
giuliomoro@0 2319 for (var i=0; i<this.interfaces.length; i++)
giuliomoro@0 2320 {
giuliomoro@0 2321 AHNode.appendChild(this.interfaces[i].encode(root));
giuliomoro@0 2322 }
giuliomoro@0 2323
giuliomoro@0 2324 for (var i=0; i<this.audioElements.length; i++) {
giuliomoro@0 2325 AHNode.appendChild(this.audioElements[i].encode(root));
giuliomoro@0 2326 }
giuliomoro@0 2327 // Create <CommentQuestion>
giuliomoro@0 2328 for (var i=0; i<this.commentQuestions.length; i++)
giuliomoro@0 2329 {
giuliomoro@0 2330 AHNode.appendChild(this.commentQuestions[i].encode(root));
giuliomoro@0 2331 }
giuliomoro@0 2332
giuliomoro@0 2333 AHNode.appendChild(this.preTest.encode(root));
giuliomoro@0 2334 AHNode.appendChild(this.postTest.encode(root));
giuliomoro@0 2335 return AHNode;
giuliomoro@0 2336 };
giuliomoro@0 2337
giuliomoro@0 2338 this.commentQuestionNode = function() {
giuliomoro@0 2339 this.id = null;
giuliomoro@0 2340 this.name = undefined;
giuliomoro@0 2341 this.type = undefined;
giuliomoro@0 2342 this.options = [];
giuliomoro@0 2343 this.statement = undefined;
giuliomoro@0 2344 this.schema = specification.schema.getAllElementsByName('commentquestion')[0];
giuliomoro@0 2345 this.decode = function(parent,xml)
giuliomoro@0 2346 {
giuliomoro@0 2347 this.id = xml.id;
giuliomoro@0 2348 this.name = xml.getAttribute('name');
giuliomoro@0 2349 this.type = xml.getAttribute('type');
giuliomoro@0 2350 this.statement = xml.getElementsByTagName('statement')[0].textContent;
giuliomoro@0 2351 var optNodes = xml.getElementsByTagName('option');
giuliomoro@0 2352 for (var i=0; i<optNodes.length; i++)
giuliomoro@0 2353 {
giuliomoro@0 2354 var optNode = optNodes[i];
giuliomoro@0 2355 this.options.push({
giuliomoro@0 2356 name: optNode.getAttribute('name'),
giuliomoro@0 2357 text: optNode.textContent
giuliomoro@0 2358 });
giuliomoro@0 2359 }
giuliomoro@0 2360 };
giuliomoro@0 2361
giuliomoro@0 2362 this.encode = function(root)
giuliomoro@0 2363 {
giuliomoro@0 2364 var node = root.createElement("commentquestion");
giuliomoro@0 2365 node.id = this.id;
giuliomoro@0 2366 node.setAttribute("type",this.type);
giuliomoro@0 2367 if (this.name != undefined){node.setAttribute("name",this.name);}
giuliomoro@0 2368 var statement = root.createElement("statement");
giuliomoro@0 2369 statement.textContent = this.statement;
giuliomoro@0 2370 node.appendChild(statement);
giuliomoro@0 2371 for (var option of this.options)
giuliomoro@0 2372 {
giuliomoro@0 2373 var child = root.createElement("option");
giuliomoro@0 2374 child.setAttribute("name",option.name);
giuliomoro@0 2375 child.textContent = option.text;
giuliomoro@0 2376 node.appendChild(child);
giuliomoro@0 2377 }
giuliomoro@0 2378 return node;
giuliomoro@0 2379 };
giuliomoro@0 2380 };
giuliomoro@0 2381
giuliomoro@0 2382 this.audioElementNode = function() {
giuliomoro@0 2383 this.url = null;
giuliomoro@0 2384 this.id = null;
giuliomoro@0 2385 this.name = null;
giuliomoro@0 2386 this.parent = null;
giuliomoro@0 2387 this.type = null;
giuliomoro@0 2388 this.marker = null;
giuliomoro@0 2389 this.enforce = false;
giuliomoro@0 2390 this.gain = 0.0;
giuliomoro@0 2391 this.schema = specification.schema.getAllElementsByName('audioelement')[0];;
giuliomoro@0 2392 this.parent = null;
giuliomoro@0 2393 this.decode = function(parent,xml)
giuliomoro@0 2394 {
giuliomoro@0 2395 this.parent = parent;
giuliomoro@0 2396 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
giuliomoro@0 2397 for (var i=0; i<attributeMap.length; i++)
giuliomoro@0 2398 {
giuliomoro@0 2399 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
giuliomoro@0 2400 var projectAttr = xml.getAttribute(attributeName);
giuliomoro@0 2401 projectAttr = specification.processAttribute(projectAttr,attributeMap[i]);
giuliomoro@0 2402 switch(typeof projectAttr)
giuliomoro@0 2403 {
giuliomoro@0 2404 case "number":
giuliomoro@0 2405 case "boolean":
giuliomoro@0 2406 eval('this.'+attributeName+' = '+projectAttr);
giuliomoro@0 2407 break;
giuliomoro@0 2408 case "string":
giuliomoro@0 2409 eval('this.'+attributeName+' = "'+projectAttr+'"');
giuliomoro@0 2410 break;
giuliomoro@0 2411 }
giuliomoro@0 2412 }
giuliomoro@0 2413
giuliomoro@0 2414 };
giuliomoro@0 2415 this.encode = function(root)
giuliomoro@0 2416 {
giuliomoro@0 2417 var AENode = root.createElement("audioelement");
giuliomoro@0 2418 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
giuliomoro@0 2419 for (var i=0; i<attributes.length; i++)
giuliomoro@0 2420 {
giuliomoro@0 2421 var name = attributes[i].getAttribute("name");
giuliomoro@0 2422 if (name == undefined) {
giuliomoro@0 2423 name = attributes[i].getAttribute("ref");
giuliomoro@0 2424 }
giuliomoro@0 2425 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
giuliomoro@0 2426 {
giuliomoro@0 2427 eval("AENode.setAttribute('"+name+"',this."+name+")");
giuliomoro@0 2428 }
giuliomoro@0 2429 }
giuliomoro@0 2430 return AENode;
giuliomoro@0 2431 };
giuliomoro@0 2432 };
giuliomoro@0 2433 };
giuliomoro@0 2434 }
giuliomoro@0 2435
giuliomoro@0 2436 function Interface(specificationObject) {
giuliomoro@0 2437 // This handles the bindings between the interface and the audioEngineContext;
giuliomoro@0 2438 this.specification = specificationObject;
giuliomoro@0 2439 this.insertPoint = document.getElementById("topLevelBody");
giuliomoro@0 2440
giuliomoro@0 2441 this.newPage = function(audioHolderObject,store)
giuliomoro@0 2442 {
giuliomoro@0 2443 audioEngineContext.newTestPage(audioHolderObject,store);
giuliomoro@0 2444 interfaceContext.commentBoxes.deleteCommentBoxes();
giuliomoro@0 2445 interfaceContext.deleteCommentQuestions();
giuliomoro@0 2446 loadTest(audioHolderObject,store);
giuliomoro@0 2447 if(audioHolderObject.hidden === true){
giuliomoro@0 2448 // work-around to have zero pages: set only one page with the attribute hidden=true and
giuliomoro@0 2449 // it will automatically skip over.
giuliomoro@0 2450 testState.advanceState();
giuliomoro@0 2451 }
giuliomoro@0 2452 };
giuliomoro@0 2453
giuliomoro@0 2454 // Bounded by interface!!
giuliomoro@0 2455 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels
giuliomoro@0 2456 // For example, APE returns the slider position normalised in a <value> tag.
giuliomoro@0 2457 this.interfaceObjects = [];
giuliomoro@0 2458 this.interfaceObject = function(){};
giuliomoro@0 2459
giuliomoro@0 2460 this.resizeWindow = function(event)
giuliomoro@0 2461 {
giuliomoro@0 2462 popup.resize(event);
giuliomoro@0 2463 for(var i=0; i<this.commentBoxes.length; i++)
giuliomoro@0 2464 {this.commentBoxes[i].resize();}
giuliomoro@0 2465 for(var i=0; i<this.commentQuestions.length; i++)
giuliomoro@0 2466 {this.commentQuestions[i].resize();}
giuliomoro@0 2467 try
giuliomoro@0 2468 {
giuliomoro@0 2469 resizeWindow(event);
giuliomoro@0 2470 }
giuliomoro@0 2471 catch(err)
giuliomoro@0 2472 {
giuliomoro@0 2473 console.log("Warning - Interface does not have Resize option");
giuliomoro@0 2474 console.log(err);
giuliomoro@0 2475 }
giuliomoro@0 2476 };
giuliomoro@0 2477
giuliomoro@0 2478 this.returnNavigator = function()
giuliomoro@0 2479 {
giuliomoro@0 2480 var node = storage.document.createElement("navigator");
giuliomoro@0 2481 var platform = storage.document.createElement("platform");
giuliomoro@0 2482 platform.textContent = navigator.platform;
giuliomoro@0 2483 var vendor = storage.document.createElement("vendor");
giuliomoro@0 2484 vendor.textContent = navigator.vendor;
giuliomoro@0 2485 var userAgent = storage.document.createElement("uagent");
giuliomoro@0 2486 userAgent.textContent = navigator.userAgent;
giuliomoro@0 2487 var screen = storage.document.createElement("window");
giuliomoro@0 2488 screen.setAttribute('innerWidth',window.innerWidth);
giuliomoro@0 2489 screen.setAttribute('innerHeight',window.innerHeight);
giuliomoro@0 2490 node.appendChild(platform);
giuliomoro@0 2491 node.appendChild(vendor);
giuliomoro@0 2492 node.appendChild(userAgent);
giuliomoro@0 2493 node.appendChild(screen);
giuliomoro@0 2494 return node;
giuliomoro@0 2495 };
giuliomoro@0 2496
giuliomoro@0 2497 this.commentBoxes = new function() {
giuliomoro@0 2498 this.boxes = [];
giuliomoro@0 2499 this.injectPoint = null;
giuliomoro@0 2500 this.elementCommentBox = function(audioObject) {
giuliomoro@0 2501 var element = audioObject.specification;
giuliomoro@0 2502 this.audioObject = audioObject;
giuliomoro@0 2503 this.id = audioObject.id;
giuliomoro@0 2504 var audioHolderObject = audioObject.specification.parent;
giuliomoro@0 2505 // Create document objects to hold the comment boxes
giuliomoro@0 2506 this.trackComment = document.createElement('div');
giuliomoro@0 2507 this.trackComment.className = 'comment-div';
giuliomoro@0 2508 this.trackComment.id = 'comment-div-'+audioObject.id;
giuliomoro@0 2509 // Create a string next to each comment asking for a comment
giuliomoro@0 2510 this.trackString = document.createElement('span');
giuliomoro@0 2511 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix+' '+audioObject.interfaceDOM.getPresentedId();
giuliomoro@0 2512 // Create the HTML5 comment box 'textarea'
giuliomoro@0 2513 this.trackCommentBox = document.createElement('textarea');
giuliomoro@0 2514 this.trackCommentBox.rows = '4';
giuliomoro@0 2515 this.trackCommentBox.cols = '100';
giuliomoro@0 2516 this.trackCommentBox.name = 'trackComment'+audioObject.id;
giuliomoro@0 2517 this.trackCommentBox.className = 'trackComment';
giuliomoro@0 2518 var br = document.createElement('br');
giuliomoro@0 2519 // Add to the holder.
giuliomoro@0 2520 this.trackComment.appendChild(this.trackString);
giuliomoro@0 2521 this.trackComment.appendChild(br);
giuliomoro@0 2522 this.trackComment.appendChild(this.trackCommentBox);
giuliomoro@0 2523
giuliomoro@0 2524 this.exportXMLDOM = function() {
giuliomoro@0 2525 var root = document.createElement('comment');
giuliomoro@0 2526 var question = document.createElement('question');
giuliomoro@0 2527 question.textContent = this.trackString.textContent;
giuliomoro@0 2528 var response = document.createElement('response');
giuliomoro@0 2529 response.textContent = this.trackCommentBox.value;
giuliomoro@0 2530 console.log("Comment frag-"+this.id+": "+response.textContent);
giuliomoro@0 2531 root.appendChild(question);
giuliomoro@0 2532 root.appendChild(response);
giuliomoro@0 2533 return root;
giuliomoro@0 2534 };
giuliomoro@0 2535 this.resize = function()
giuliomoro@0 2536 {
giuliomoro@0 2537 var boxwidth = (window.innerWidth-100)/2;
giuliomoro@0 2538 if (boxwidth >= 600)
giuliomoro@0 2539 {
giuliomoro@0 2540 boxwidth = 600;
giuliomoro@0 2541 }
giuliomoro@0 2542 else if (boxwidth < 400)
giuliomoro@0 2543 {
giuliomoro@0 2544 boxwidth = 400;
giuliomoro@0 2545 }
giuliomoro@0 2546 this.trackComment.style.width = boxwidth+"px";
giuliomoro@0 2547 this.trackCommentBox.style.width = boxwidth-6+"px";
giuliomoro@0 2548 };
giuliomoro@0 2549 this.resize();
giuliomoro@0 2550 };
giuliomoro@0 2551 this.createCommentBox = function(audioObject) {
giuliomoro@0 2552 var node = new this.elementCommentBox(audioObject);
giuliomoro@0 2553 this.boxes.push(node);
giuliomoro@0 2554 audioObject.commentDOM = node;
giuliomoro@0 2555 return node;
giuliomoro@0 2556 };
giuliomoro@0 2557 this.sortCommentBoxes = function() {
giuliomoro@0 2558 this.boxes.sort(function(a,b){return a.id - b.id;});
giuliomoro@0 2559 };
giuliomoro@0 2560
giuliomoro@0 2561 this.showCommentBoxes = function(inject, sort) {
giuliomoro@0 2562 this.injectPoint = inject;
giuliomoro@0 2563 if (sort) {this.sortCommentBoxes();}
giuliomoro@0 2564 for (var box of this.boxes) {
giuliomoro@0 2565 inject.appendChild(box.trackComment);
giuliomoro@0 2566 }
giuliomoro@0 2567 };
giuliomoro@0 2568
giuliomoro@0 2569 this.deleteCommentBoxes = function() {
giuliomoro@0 2570 if (this.injectPoint != null) {
giuliomoro@0 2571 for (var box of this.boxes) {
giuliomoro@0 2572 this.injectPoint.removeChild(box.trackComment);
giuliomoro@0 2573 }
giuliomoro@0 2574 this.injectPoint = null;
giuliomoro@0 2575 }
giuliomoro@0 2576 this.boxes = [];
giuliomoro@0 2577 };
giuliomoro@0 2578 }
giuliomoro@0 2579
giuliomoro@0 2580 this.commentQuestions = [];
giuliomoro@0 2581
giuliomoro@0 2582 this.commentBox = function(commentQuestion) {
giuliomoro@0 2583 this.specification = commentQuestion;
giuliomoro@0 2584 // Create document objects to hold the comment boxes
giuliomoro@0 2585 this.holder = document.createElement('div');
giuliomoro@0 2586 this.holder.className = 'comment-div';
giuliomoro@0 2587 // Create a string next to each comment asking for a comment
giuliomoro@0 2588 this.string = document.createElement('span');
giuliomoro@0 2589 this.string.innerHTML = commentQuestion.statement;
giuliomoro@0 2590 // Create the HTML5 comment box 'textarea'
giuliomoro@0 2591 this.textArea = document.createElement('textarea');
giuliomoro@0 2592 this.textArea.rows = '4';
giuliomoro@0 2593 this.textArea.cols = '100';
giuliomoro@0 2594 this.textArea.className = 'trackComment';
giuliomoro@0 2595 var br = document.createElement('br');
giuliomoro@0 2596 // Add to the holder.
giuliomoro@0 2597 this.holder.appendChild(this.string);
giuliomoro@0 2598 this.holder.appendChild(br);
giuliomoro@0 2599 this.holder.appendChild(this.textArea);
giuliomoro@0 2600
giuliomoro@0 2601 this.exportXMLDOM = function(storePoint) {
giuliomoro@0 2602 var root = storePoint.parent.document.createElement('comment');
giuliomoro@0 2603 root.id = this.specification.id;
giuliomoro@0 2604 root.setAttribute('type',this.specification.type);
giuliomoro@0 2605 console.log("Question: "+this.string.textContent);
giuliomoro@0 2606 console.log("Response: "+root.textContent);
giuliomoro@0 2607 var question = storePoint.parent.document.createElement('question');
giuliomoro@0 2608 question.textContent = this.string.textContent;
giuliomoro@0 2609 var response = storePoint.parent.document.createElement('response');
giuliomoro@0 2610 response.textContent = this.textArea.value;
giuliomoro@0 2611 root.appendChild(question);
giuliomoro@0 2612 root.appendChild(response);
giuliomoro@0 2613 storePoint.XMLDOM.appendChild(root);
giuliomoro@0 2614 return root;
giuliomoro@0 2615 };
giuliomoro@0 2616 this.resize = function()
giuliomoro@0 2617 {
giuliomoro@0 2618 var boxwidth = (window.innerWidth-100)/2;
giuliomoro@0 2619 if (boxwidth >= 600)
giuliomoro@0 2620 {
giuliomoro@0 2621 boxwidth = 600;
giuliomoro@0 2622 }
giuliomoro@0 2623 else if (boxwidth < 400)
giuliomoro@0 2624 {
giuliomoro@0 2625 boxwidth = 400;
giuliomoro@0 2626 }
giuliomoro@0 2627 this.holder.style.width = boxwidth+"px";
giuliomoro@0 2628 this.textArea.style.width = boxwidth-6+"px";
giuliomoro@0 2629 };
giuliomoro@0 2630 this.resize();
giuliomoro@0 2631 };
giuliomoro@0 2632
giuliomoro@0 2633 this.radioBox = function(commentQuestion) {
giuliomoro@0 2634 this.specification = commentQuestion;
giuliomoro@0 2635 // Create document objects to hold the comment boxes
giuliomoro@0 2636 this.holder = document.createElement('div');
giuliomoro@0 2637 this.holder.className = 'comment-div';
giuliomoro@0 2638 // Create a string next to each comment asking for a comment
giuliomoro@0 2639 this.string = document.createElement('span');
giuliomoro@0 2640 this.string.innerHTML = commentQuestion.statement;
giuliomoro@0 2641 var br = document.createElement('br');
giuliomoro@0 2642 // Add to the holder.
giuliomoro@0 2643 this.holder.appendChild(this.string);
giuliomoro@0 2644 this.holder.appendChild(br);
giuliomoro@0 2645 this.options = [];
giuliomoro@0 2646 this.inputs = document.createElement('div');
giuliomoro@0 2647 this.span = document.createElement('div');
giuliomoro@0 2648 this.inputs.align = 'center';
giuliomoro@0 2649 this.inputs.style.marginLeft = '12px';
giuliomoro@0 2650 this.span.style.marginLeft = '12px';
giuliomoro@0 2651 this.span.align = 'center';
giuliomoro@0 2652 this.span.style.marginTop = '15px';
giuliomoro@0 2653
giuliomoro@0 2654 var optCount = commentQuestion.options.length;
giuliomoro@0 2655 for (var optNode of commentQuestion.options)
giuliomoro@0 2656 {
giuliomoro@0 2657 var div = document.createElement('div');
giuliomoro@0 2658 div.style.width = '80px';
giuliomoro@0 2659 div.style.float = 'left';
giuliomoro@0 2660 var input = document.createElement('input');
giuliomoro@0 2661 input.type = 'radio';
giuliomoro@0 2662 input.name = commentQuestion.id;
giuliomoro@0 2663 input.setAttribute('setvalue',optNode.name);
giuliomoro@0 2664 input.className = 'comment-radio';
giuliomoro@0 2665 div.appendChild(input);
giuliomoro@0 2666 this.inputs.appendChild(div);
giuliomoro@0 2667
giuliomoro@0 2668
giuliomoro@0 2669 div = document.createElement('div');
giuliomoro@0 2670 div.style.width = '80px';
giuliomoro@0 2671 div.style.float = 'left';
giuliomoro@0 2672 div.align = 'center';
giuliomoro@0 2673 var span = document.createElement('span');
giuliomoro@0 2674 span.textContent = optNode.text;
giuliomoro@0 2675 span.className = 'comment-radio-span';
giuliomoro@0 2676 div.appendChild(span);
giuliomoro@0 2677 this.span.appendChild(div);
giuliomoro@0 2678 this.options.push(input);
giuliomoro@0 2679 }
giuliomoro@0 2680 this.holder.appendChild(this.span);
giuliomoro@0 2681 this.holder.appendChild(this.inputs);
giuliomoro@0 2682
giuliomoro@0 2683 this.exportXMLDOM = function(storePoint) {
giuliomoro@0 2684 var root = storePoint.parent.document.createElement('comment');
giuliomoro@0 2685 root.id = this.specification.id;
giuliomoro@0 2686 root.setAttribute('type',this.specification.type);
giuliomoro@0 2687 var question = document.createElement('question');
giuliomoro@0 2688 question.textContent = this.string.textContent;
giuliomoro@0 2689 var response = document.createElement('response');
giuliomoro@0 2690 var i=0;
giuliomoro@0 2691 while(this.options[i].checked == false) {
giuliomoro@0 2692 i++;
giuliomoro@0 2693 if (i >= this.options.length) {
giuliomoro@0 2694 break;
giuliomoro@0 2695 }
giuliomoro@0 2696 }
giuliomoro@0 2697 if (i >= this.options.length) {
giuliomoro@0 2698 response.textContent = 'null';
giuliomoro@0 2699 } else {
giuliomoro@0 2700 response.textContent = this.options[i].getAttribute('setvalue');
giuliomoro@0 2701 response.setAttribute('number',i);
giuliomoro@0 2702 }
giuliomoro@0 2703 console.log('Comment: '+question.textContent);
giuliomoro@0 2704 console.log('Response: '+response.textContent);
giuliomoro@0 2705 root.appendChild(question);
giuliomoro@0 2706 root.appendChild(response);
giuliomoro@0 2707 storePoint.XMLDOM.appendChild(root);
giuliomoro@0 2708 return root;
giuliomoro@0 2709 };
giuliomoro@0 2710 this.resize = function()
giuliomoro@0 2711 {
giuliomoro@0 2712 var boxwidth = (window.innerWidth-100)/2;
giuliomoro@0 2713 if (boxwidth >= 600)
giuliomoro@0 2714 {
giuliomoro@0 2715 boxwidth = 600;
giuliomoro@0 2716 }
giuliomoro@0 2717 else if (boxwidth < 400)
giuliomoro@0 2718 {
giuliomoro@0 2719 boxwidth = 400;
giuliomoro@0 2720 }
giuliomoro@0 2721 this.holder.style.width = boxwidth+"px";
giuliomoro@0 2722 var text = this.holder.children[2];
giuliomoro@0 2723 var options = this.holder.children[3];
giuliomoro@0 2724 var optCount = options.children.length;
giuliomoro@0 2725 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
giuliomoro@0 2726 var options = options.firstChild;
giuliomoro@0 2727 var text = text.firstChild;
giuliomoro@0 2728 options.style.marginRight = spanMargin;
giuliomoro@0 2729 options.style.marginLeft = spanMargin;
giuliomoro@0 2730 text.style.marginRight = spanMargin;
giuliomoro@0 2731 text.style.marginLeft = spanMargin;
giuliomoro@0 2732 while(options.nextSibling != undefined)
giuliomoro@0 2733 {
giuliomoro@0 2734 options = options.nextSibling;
giuliomoro@0 2735 text = text.nextSibling;
giuliomoro@0 2736 options.style.marginRight = spanMargin;
giuliomoro@0 2737 options.style.marginLeft = spanMargin;
giuliomoro@0 2738 text.style.marginRight = spanMargin;
giuliomoro@0 2739 text.style.marginLeft = spanMargin;
giuliomoro@0 2740 }
giuliomoro@0 2741 };
giuliomoro@0 2742 this.resize();
giuliomoro@0 2743 };
giuliomoro@0 2744
giuliomoro@0 2745 this.checkboxBox = function(commentQuestion) {
giuliomoro@0 2746 this.specification = commentQuestion;
giuliomoro@0 2747 // Create document objects to hold the comment boxes
giuliomoro@0 2748 this.holder = document.createElement('div');
giuliomoro@0 2749 this.holder.className = 'comment-div';
giuliomoro@0 2750 // Create a string next to each comment asking for a comment
giuliomoro@0 2751 this.string = document.createElement('span');
giuliomoro@0 2752 this.string.innerHTML = commentQuestion.statement;
giuliomoro@0 2753 var br = document.createElement('br');
giuliomoro@0 2754 // Add to the holder.
giuliomoro@0 2755 this.holder.appendChild(this.string);
giuliomoro@0 2756 this.holder.appendChild(br);
giuliomoro@0 2757 this.options = [];
giuliomoro@0 2758 this.inputs = document.createElement('div');
giuliomoro@0 2759 this.span = document.createElement('div');
giuliomoro@0 2760 this.inputs.align = 'center';
giuliomoro@0 2761 this.inputs.style.marginLeft = '12px';
giuliomoro@0 2762 this.span.style.marginLeft = '12px';
giuliomoro@0 2763 this.span.align = 'center';
giuliomoro@0 2764 this.span.style.marginTop = '15px';
giuliomoro@0 2765
giuliomoro@0 2766 var optCount = commentQuestion.options.length;
giuliomoro@0 2767 for (var i=0; i<optCount; i++)
giuliomoro@0 2768 {
giuliomoro@0 2769 var div = document.createElement('div');
giuliomoro@0 2770 div.style.width = '80px';
giuliomoro@0 2771 div.style.float = 'left';
giuliomoro@0 2772 var input = document.createElement('input');
giuliomoro@0 2773 input.type = 'checkbox';
giuliomoro@0 2774 input.name = commentQuestion.id;
giuliomoro@0 2775 input.setAttribute('setvalue',commentQuestion.options[i].name);
giuliomoro@0 2776 input.className = 'comment-radio';
giuliomoro@0 2777 div.appendChild(input);
giuliomoro@0 2778 this.inputs.appendChild(div);
giuliomoro@0 2779
giuliomoro@0 2780
giuliomoro@0 2781 div = document.createElement('div');
giuliomoro@0 2782 div.style.width = '80px';
giuliomoro@0 2783 div.style.float = 'left';
giuliomoro@0 2784 div.align = 'center';
giuliomoro@0 2785 var span = document.createElement('span');
giuliomoro@0 2786 span.textContent = commentQuestion.options[i].text;
giuliomoro@0 2787 span.className = 'comment-radio-span';
giuliomoro@0 2788 div.appendChild(span);
giuliomoro@0 2789 this.span.appendChild(div);
giuliomoro@0 2790 this.options.push(input);
giuliomoro@0 2791 }
giuliomoro@0 2792 this.holder.appendChild(this.span);
giuliomoro@0 2793 this.holder.appendChild(this.inputs);
giuliomoro@0 2794
giuliomoro@0 2795 this.exportXMLDOM = function(storePoint) {
giuliomoro@0 2796 var root = storePoint.parent.document.createElement('comment');
giuliomoro@0 2797 root.id = this.specification.id;
giuliomoro@0 2798 root.setAttribute('type',this.specification.type);
giuliomoro@0 2799 var question = document.createElement('question');
giuliomoro@0 2800 question.textContent = this.string.textContent;
giuliomoro@0 2801 root.appendChild(question);
giuliomoro@0 2802 console.log('Comment: '+question.textContent);
giuliomoro@0 2803 for (var i=0; i<this.options.length; i++) {
giuliomoro@0 2804 var response = document.createElement('response');
giuliomoro@0 2805 response.textContent = this.options[i].checked;
giuliomoro@0 2806 response.setAttribute('name',this.options[i].getAttribute('setvalue'));
giuliomoro@0 2807 root.appendChild(response);
giuliomoro@0 2808 console.log('Response '+response.getAttribute('name') +': '+response.textContent);
giuliomoro@0 2809 }
giuliomoro@0 2810 storePoint.XMLDOM.appendChild(root);
giuliomoro@0 2811 return root;
giuliomoro@0 2812 };
giuliomoro@0 2813 this.resize = function()
giuliomoro@0 2814 {
giuliomoro@0 2815 var boxwidth = (window.innerWidth-100)/2;
giuliomoro@0 2816 if (boxwidth >= 600)
giuliomoro@0 2817 {
giuliomoro@0 2818 boxwidth = 600;
giuliomoro@0 2819 }
giuliomoro@0 2820 else if (boxwidth < 400)
giuliomoro@0 2821 {
giuliomoro@0 2822 boxwidth = 400;
giuliomoro@0 2823 }
giuliomoro@0 2824 this.holder.style.width = boxwidth+"px";
giuliomoro@0 2825 var text = this.holder.children[2];
giuliomoro@0 2826 var options = this.holder.children[3];
giuliomoro@0 2827 var optCount = options.children.length;
giuliomoro@0 2828 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
giuliomoro@0 2829 var options = options.firstChild;
giuliomoro@0 2830 var text = text.firstChild;
giuliomoro@0 2831 options.style.marginRight = spanMargin;
giuliomoro@0 2832 options.style.marginLeft = spanMargin;
giuliomoro@0 2833 text.style.marginRight = spanMargin;
giuliomoro@0 2834 text.style.marginLeft = spanMargin;
giuliomoro@0 2835 while(options.nextSibling != undefined)
giuliomoro@0 2836 {
giuliomoro@0 2837 options = options.nextSibling;
giuliomoro@0 2838 text = text.nextSibling;
giuliomoro@0 2839 options.style.marginRight = spanMargin;
giuliomoro@0 2840 options.style.marginLeft = spanMargin;
giuliomoro@0 2841 text.style.marginRight = spanMargin;
giuliomoro@0 2842 text.style.marginLeft = spanMargin;
giuliomoro@0 2843 }
giuliomoro@0 2844 };
giuliomoro@0 2845 this.resize();
giuliomoro@0 2846 };
giuliomoro@0 2847
giuliomoro@0 2848 this.createCommentQuestion = function(element) {
giuliomoro@0 2849 var node;
giuliomoro@0 2850 if (element.type == 'question') {
giuliomoro@0 2851 node = new this.commentBox(element);
giuliomoro@0 2852 } else if (element.type == 'radio') {
giuliomoro@0 2853 node = new this.radioBox(element);
giuliomoro@0 2854 } else if (element.type == 'checkbox') {
giuliomoro@0 2855 node = new this.checkboxBox(element);
giuliomoro@0 2856 }
giuliomoro@0 2857 this.commentQuestions.push(node);
giuliomoro@0 2858 return node;
giuliomoro@0 2859 };
giuliomoro@0 2860
giuliomoro@0 2861 this.deleteCommentQuestions = function()
giuliomoro@0 2862 {
giuliomoro@0 2863 this.commentQuestions = [];
giuliomoro@0 2864 };
giuliomoro@0 2865
giuliomoro@0 2866 this.playhead = new function()
giuliomoro@0 2867 {
giuliomoro@0 2868 this.object = document.createElement('div');
giuliomoro@0 2869 this.object.className = 'playhead';
giuliomoro@0 2870 this.object.align = 'left';
giuliomoro@0 2871 var curTime = document.createElement('div');
giuliomoro@0 2872 curTime.style.width = '50px';
giuliomoro@0 2873 this.curTimeSpan = document.createElement('span');
giuliomoro@0 2874 this.curTimeSpan.textContent = '00:00';
giuliomoro@0 2875 curTime.appendChild(this.curTimeSpan);
giuliomoro@0 2876 this.object.appendChild(curTime);
giuliomoro@0 2877 this.scrubberTrack = document.createElement('div');
giuliomoro@0 2878 this.scrubberTrack.className = 'playhead-scrub-track';
giuliomoro@0 2879
giuliomoro@0 2880 this.scrubberHead = document.createElement('div');
giuliomoro@0 2881 this.scrubberHead.id = 'playhead-scrubber';
giuliomoro@0 2882 this.scrubberTrack.appendChild(this.scrubberHead);
giuliomoro@0 2883 this.object.appendChild(this.scrubberTrack);
giuliomoro@0 2884
giuliomoro@0 2885 this.timePerPixel = 0;
giuliomoro@0 2886 this.maxTime = 0;
giuliomoro@0 2887
giuliomoro@0 2888 this.playbackObject;
giuliomoro@0 2889
giuliomoro@0 2890 this.setTimePerPixel = function(audioObject) {
giuliomoro@0 2891 //maxTime must be in seconds
giuliomoro@0 2892 this.playbackObject = audioObject;
giuliomoro@0 2893 this.maxTime = audioObject.buffer.buffer.duration;
giuliomoro@0 2894 var width = 490; //500 - 10, 5 each side of the tracker head
giuliomoro@0 2895 this.timePerPixel = this.maxTime/490;
giuliomoro@0 2896 if (this.maxTime < 60) {
giuliomoro@0 2897 this.curTimeSpan.textContent = '0.00';
giuliomoro@0 2898 } else {
giuliomoro@0 2899 this.curTimeSpan.textContent = '00:00';
giuliomoro@0 2900 }
giuliomoro@0 2901 };
giuliomoro@0 2902
giuliomoro@0 2903 this.update = function() {
giuliomoro@0 2904 // Update the playhead position, startPlay must be called
giuliomoro@0 2905 if (this.timePerPixel > 0) {
giuliomoro@0 2906 var time = this.playbackObject.getCurrentPosition();
giuliomoro@0 2907 if (time > 0 && time < this.maxTime) {
giuliomoro@0 2908 var width = 490;
giuliomoro@0 2909 var pix = Math.floor(time/this.timePerPixel);
giuliomoro@0 2910 this.scrubberHead.style.left = pix+'px';
giuliomoro@0 2911 if (this.maxTime > 60.0) {
giuliomoro@0 2912 var secs = time%60;
giuliomoro@0 2913 var mins = Math.floor((time-secs)/60);
giuliomoro@0 2914 secs = secs.toString();
giuliomoro@0 2915 secs = secs.substr(0,2);
giuliomoro@0 2916 mins = mins.toString();
giuliomoro@0 2917 this.curTimeSpan.textContent = mins+':'+secs;
giuliomoro@0 2918 } else {
giuliomoro@0 2919 time = time.toString();
giuliomoro@0 2920 this.curTimeSpan.textContent = time.substr(0,4);
giuliomoro@0 2921 }
giuliomoro@0 2922 } else {
giuliomoro@0 2923 this.scrubberHead.style.left = '0px';
giuliomoro@0 2924 if (this.maxTime < 60) {
giuliomoro@0 2925 this.curTimeSpan.textContent = '0.00';
giuliomoro@0 2926 } else {
giuliomoro@0 2927 this.curTimeSpan.textContent = '00:00';
giuliomoro@0 2928 }
giuliomoro@0 2929 }
giuliomoro@0 2930 }
giuliomoro@0 2931 };
giuliomoro@0 2932
giuliomoro@0 2933 this.interval = undefined;
giuliomoro@0 2934
giuliomoro@0 2935 this.start = function() {
giuliomoro@0 2936 if (this.playbackObject != undefined && this.interval == undefined) {
giuliomoro@0 2937 if (this.maxTime < 60) {
giuliomoro@0 2938 this.interval = setInterval(function(){interfaceContext.playhead.update();},10);
giuliomoro@0 2939 } else {
giuliomoro@0 2940 this.interval = setInterval(function(){interfaceContext.playhead.update();},100);
giuliomoro@0 2941 }
giuliomoro@0 2942 }
giuliomoro@0 2943 };
giuliomoro@0 2944 this.stop = function() {
giuliomoro@0 2945 clearInterval(this.interval);
giuliomoro@0 2946 this.interval = undefined;
giuliomoro@0 2947 this.scrubberHead.style.left = '0px';
giuliomoro@0 2948 if (this.maxTime < 60) {
giuliomoro@0 2949 this.curTimeSpan.textContent = '0.00';
giuliomoro@0 2950 } else {
giuliomoro@0 2951 this.curTimeSpan.textContent = '00:00';
giuliomoro@0 2952 }
giuliomoro@0 2953 };
giuliomoro@0 2954 };
giuliomoro@0 2955
giuliomoro@0 2956 this.volume = new function()
giuliomoro@0 2957 {
giuliomoro@0 2958 // An in-built volume module which can be viewed on page
giuliomoro@0 2959 // Includes trackers on page-by-page data
giuliomoro@0 2960 // Volume does NOT reset to 0dB on each page load
giuliomoro@0 2961 this.valueLin = 1.0;
giuliomoro@0 2962 this.valueDB = 0.0;
giuliomoro@0 2963 this.object = document.createElement('div');
giuliomoro@0 2964 this.object.id = 'master-volume-holder';
giuliomoro@0 2965 this.slider = document.createElement('input');
giuliomoro@0 2966 this.slider.id = 'master-volume-control';
giuliomoro@0 2967 this.slider.type = 'range';
giuliomoro@0 2968 this.valueText = document.createElement('span');
giuliomoro@0 2969 this.valueText.id = 'master-volume-feedback';
giuliomoro@0 2970 this.valueText.textContent = '0dB';
giuliomoro@0 2971
giuliomoro@0 2972 this.slider.min = -60;
giuliomoro@0 2973 this.slider.max = 12;
giuliomoro@0 2974 this.slider.value = 0;
giuliomoro@0 2975 this.slider.step = 1;
giuliomoro@0 2976 this.slider.onmousemove = function(event)
giuliomoro@0 2977 {
giuliomoro@0 2978 interfaceContext.volume.valueDB = event.currentTarget.value;
giuliomoro@0 2979 interfaceContext.volume.valueLin = decibelToLinear(interfaceContext.volume.valueDB);
giuliomoro@0 2980 interfaceContext.volume.valueText.textContent = interfaceContext.volume.valueDB+'dB';
giuliomoro@0 2981 audioEngineContext.outputGain.gain.value = interfaceContext.volume.valueLin;
giuliomoro@0 2982 }
giuliomoro@0 2983 this.slider.onmouseup = function(event)
giuliomoro@0 2984 {
giuliomoro@0 2985 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
giuliomoro@0 2986 if (storePoint.length == 0)
giuliomoro@0 2987 {
giuliomoro@0 2988 storePoint = storage.document.createElement('metricresult');
giuliomoro@0 2989 storePoint.setAttribute('name','volumeTracker');
giuliomoro@0 2990 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
giuliomoro@0 2991 }
giuliomoro@0 2992 else {
giuliomoro@0 2993 storePoint = storePoint[0];
giuliomoro@0 2994 }
giuliomoro@0 2995 var node = storage.document.createElement('movement');
giuliomoro@0 2996 node.setAttribute('test-time',audioEngineContext.timer.getTestTime());
giuliomoro@0 2997 node.setAttribute('volume',interfaceContext.volume.valueDB);
giuliomoro@0 2998 node.setAttribute('format','dBFS');
giuliomoro@0 2999 storePoint.appendChild(node);
giuliomoro@0 3000 }
giuliomoro@0 3001
giuliomoro@0 3002 var title = document.createElement('div');
giuliomoro@0 3003 title.innerHTML = '<span>Master Volume Control</span>';
giuliomoro@0 3004 title.style.fontSize = '0.75em';
giuliomoro@0 3005 title.style.width = "100%";
giuliomoro@0 3006 title.align = 'center';
giuliomoro@0 3007 this.object.appendChild(title);
giuliomoro@0 3008
giuliomoro@0 3009 this.object.appendChild(this.slider);
giuliomoro@0 3010 this.object.appendChild(this.valueText);
giuliomoro@0 3011 }
giuliomoro@0 3012 // Global Checkers
giuliomoro@0 3013 // These functions will help enforce the checkers
giuliomoro@0 3014 this.checkHiddenAnchor = function()
giuliomoro@0 3015 {
giuliomoro@0 3016 for (var ao of audioEngineContext.audioObjects)
giuliomoro@0 3017 {
giuliomoro@0 3018 if (ao.specification.type == "anchor")
giuliomoro@0 3019 {
giuliomoro@0 3020 if (ao.interfaceDOM.getValue() > (ao.specification.marker/100) && ao.specification.marker > 0) {
giuliomoro@0 3021 // Anchor is not set below
giuliomoro@0 3022 console.log('Anchor node not below marker value');
giuliomoro@0 3023 alert('Please keep listening');
giuliomoro@0 3024 this.storeErrorNode('Anchor node not below marker value');
giuliomoro@0 3025 return false;
giuliomoro@0 3026 }
giuliomoro@0 3027 }
giuliomoro@0 3028 }
giuliomoro@0 3029 return true;
giuliomoro@0 3030 };
giuliomoro@0 3031
giuliomoro@0 3032 this.checkHiddenReference = function()
giuliomoro@0 3033 {
giuliomoro@0 3034 for (var ao of audioEngineContext.audioObjects)
giuliomoro@0 3035 {
giuliomoro@0 3036 if (ao.specification.type == "reference")
giuliomoro@0 3037 {
giuliomoro@0 3038 if (ao.interfaceDOM.getValue() < (ao.specification.marker/100) && ao.specification.marker > 0) {
giuliomoro@0 3039 // Anchor is not set below
giuliomoro@0 3040 console.log('Reference node not above marker value');
giuliomoro@0 3041 this.storeErrorNode('Reference node not above marker value');
giuliomoro@0 3042 alert('Please keep listening');
giuliomoro@0 3043 return false;
giuliomoro@0 3044 }
giuliomoro@0 3045 }
giuliomoro@0 3046 }
giuliomoro@0 3047 return true;
giuliomoro@0 3048 };
giuliomoro@0 3049
giuliomoro@0 3050 this.checkFragmentsFullyPlayed = function ()
giuliomoro@0 3051 {
giuliomoro@0 3052 // Checks the entire file has been played back
giuliomoro@0 3053 // NOTE ! This will return true IF playback is Looped!!!
giuliomoro@0 3054 if (audioEngineContext.loopPlayback)
giuliomoro@0 3055 {
giuliomoro@0 3056 console.log("WARNING - Looped source: Cannot check fragments are fully played");
giuliomoro@0 3057 return true;
giuliomoro@0 3058 }
giuliomoro@0 3059 var check_pass = true;
giuliomoro@0 3060 var error_obj = [];
giuliomoro@0 3061 for (var i = 0; i<audioEngineContext.audioObjects.length; i++)
giuliomoro@0 3062 {
giuliomoro@0 3063 var object = audioEngineContext.audioObjects[i];
giuliomoro@0 3064 var time = object.buffer.buffer.duration;
giuliomoro@0 3065 var metric = object.metric;
giuliomoro@0 3066 var passed = false;
giuliomoro@0 3067 for (var j=0; j<metric.listenTracker.length; j++)
giuliomoro@0 3068 {
giuliomoro@0 3069 var bt = metric.listenTracker[j].getElementsByTagName('buffertime');
giuliomoro@0 3070 var start_time = Number(bt[0].getAttribute('start'));
giuliomoro@0 3071 var stop_time = Number(bt[0].getAttribute('stop'));
giuliomoro@0 3072 var delta = stop_time - start_time;
giuliomoro@0 3073 if (delta >= time)
giuliomoro@0 3074 {
giuliomoro@0 3075 passed = true;
giuliomoro@0 3076 break;
giuliomoro@0 3077 }
giuliomoro@0 3078 }
giuliomoro@0 3079 if (passed == false)
giuliomoro@0 3080 {
giuliomoro@0 3081 check_pass = false;
giuliomoro@0 3082 console.log("Continue listening to track-"+object.interfaceDOM.getPresentedId());
giuliomoro@0 3083 error_obj.push(object.interfaceDOM.getPresentedId());
giuliomoro@0 3084 }
giuliomoro@0 3085 }
giuliomoro@0 3086 if (check_pass == false)
giuliomoro@0 3087 {
giuliomoro@0 3088 var str_start = "You have not completely listened to fragments ";
giuliomoro@0 3089 for (var i=0; i<error_obj.length; i++)
giuliomoro@0 3090 {
giuliomoro@0 3091 str_start += error_obj[i];
giuliomoro@0 3092 if (i != error_obj.length-1)
giuliomoro@0 3093 {
giuliomoro@0 3094 str_start += ', ';
giuliomoro@0 3095 }
giuliomoro@0 3096 }
giuliomoro@0 3097 str_start += ". Please keep listening";
giuliomoro@0 3098 console.log("[ALERT]: "+str_start);
giuliomoro@0 3099 this.storeErrorNode("[ALERT]: "+str_start);
giuliomoro@0 3100 alert(str_start);
giuliomoro@0 3101 }
giuliomoro@0 3102 };
giuliomoro@0 3103 this.checkAllMoved = function()
giuliomoro@0 3104 {
giuliomoro@0 3105 var str = "You have not moved ";
giuliomoro@0 3106 var failed = [];
giuliomoro@0 3107 for (var ao of audioEngineContext.audioObjects)
giuliomoro@0 3108 {
giuliomoro@0 3109 if(ao.metric.wasMoved == false && ao.interfaceDOM.canMove() == true)
giuliomoro@0 3110 {
giuliomoro@0 3111 failed.push(ao.interfaceDOM.getPresentedId());
giuliomoro@0 3112 }
giuliomoro@0 3113 }
giuliomoro@0 3114 if (failed.length == 0)
giuliomoro@0 3115 {
giuliomoro@0 3116 return true;
giuliomoro@0 3117 } else if (failed.length == 1)
giuliomoro@0 3118 {
giuliomoro@0 3119 str += 'track '+failed[0];
giuliomoro@0 3120 } else {
giuliomoro@0 3121 str += 'tracks ';
giuliomoro@0 3122 for (var i=0; i<failed.length-1; i++)
giuliomoro@0 3123 {
giuliomoro@0 3124 str += failed[i]+', ';
giuliomoro@0 3125 }
giuliomoro@0 3126 str += 'and '+failed[i];
giuliomoro@0 3127 }
giuliomoro@0 3128 str +='.';
giuliomoro@0 3129 alert(str);
giuliomoro@0 3130 console.log(str);
giuliomoro@0 3131 this.storeErrorNode(str);
giuliomoro@0 3132 return false;
giuliomoro@0 3133 };
giuliomoro@0 3134 this.checkAllPlayed = function()
giuliomoro@0 3135 {
giuliomoro@0 3136 var str = "You have not played ";
giuliomoro@0 3137 var failed = [];
giuliomoro@0 3138 for (var ao of audioEngineContext.audioObjects)
giuliomoro@0 3139 {
giuliomoro@0 3140 if(ao.metric.wasListenedTo == false)
giuliomoro@0 3141 {
giuliomoro@0 3142 failed.push(ao.interfaceDOM.getPresentedId());
giuliomoro@0 3143 }
giuliomoro@0 3144 }
giuliomoro@0 3145 if (failed.length == 0)
giuliomoro@0 3146 {
giuliomoro@0 3147 return true;
giuliomoro@0 3148 } else if (failed.length == 1)
giuliomoro@0 3149 {
giuliomoro@0 3150 str += 'track '+failed[0];
giuliomoro@0 3151 } else {
giuliomoro@0 3152 str += 'tracks ';
giuliomoro@0 3153 for (var i=0; i<failed.length-1; i++)
giuliomoro@0 3154 {
giuliomoro@0 3155 str += failed[i]+', ';
giuliomoro@0 3156 }
giuliomoro@0 3157 str += 'and '+failed[i];
giuliomoro@0 3158 }
giuliomoro@0 3159 str +='.';
giuliomoro@0 3160 alert(str);
giuliomoro@0 3161 console.log(str);
giuliomoro@0 3162 this.storeErrorNode(str);
giuliomoro@0 3163 return false;
giuliomoro@0 3164 };
giuliomoro@0 3165
giuliomoro@0 3166 this.storeErrorNode = function(errorMessage)
giuliomoro@0 3167 {
giuliomoro@0 3168 var time = audioEngineContext.timer.getTestTime();
giuliomoro@0 3169 var node = storage.document.createElement('error');
giuliomoro@0 3170 node.setAttribute('time',time);
giuliomoro@0 3171 node.textContent = errorMessage;
giuliomoro@0 3172 testState.currentStore.XMLDOM.appendChild(node);
giuliomoro@0 3173 };
giuliomoro@0 3174 }
giuliomoro@0 3175
giuliomoro@0 3176 function Storage()
giuliomoro@0 3177 {
giuliomoro@0 3178 // Holds results in XML format until ready for collection
giuliomoro@0 3179 this.globalPreTest = null;
giuliomoro@0 3180 this.globalPostTest = null;
giuliomoro@0 3181 this.testPages = [];
giuliomoro@0 3182 this.document = null;
giuliomoro@0 3183 this.root = null;
giuliomoro@0 3184 this.state = 0;
giuliomoro@0 3185
giuliomoro@0 3186 this.initialise = function(existingStore)
giuliomoro@0 3187 {
giuliomoro@0 3188 if (existingStore == undefined) {
giuliomoro@0 3189 // We need to get the sessionKey
giuliomoro@0 3190 this.SessionKey.generateKey();
giuliomoro@0 3191 this.document = document.implementation.createDocument(null,"waetresult");
giuliomoro@0 3192 this.root = this.document.childNodes[0];
giuliomoro@0 3193 var projectDocument = specification.projectXML;
giuliomoro@0 3194 projectDocument.setAttribute('file-name',url);
giuliomoro@0 3195 this.root.appendChild(projectDocument);
giuliomoro@0 3196 this.root.appendChild(returnDateNode());
giuliomoro@0 3197 this.root.appendChild(interfaceContext.returnNavigator());
giuliomoro@0 3198 } else {
giuliomoro@0 3199 this.document = existingStore;
giuliomoro@0 3200 this.root = existingStore.children[0];
giuliomoro@0 3201 this.SessionKey.key = this.root.getAttribute("key");
giuliomoro@0 3202 }
giuliomoro@0 3203 if (specification.preTest != undefined){this.globalPreTest = new this.surveyNode(this,this.root,specification.preTest);}
giuliomoro@0 3204 if (specification.postTest != undefined){this.globalPostTest = new this.surveyNode(this,this.root,specification.postTest);}
giuliomoro@0 3205 };
giuliomoro@0 3206
giuliomoro@0 3207 this.SessionKey = {
giuliomoro@0 3208 key: null,
giuliomoro@0 3209 request: new XMLHttpRequest(),
giuliomoro@0 3210 parent: this,
giuliomoro@0 3211 handleEvent: function() {
giuliomoro@0 3212 var parse = new DOMParser();
giuliomoro@0 3213 var xml = parse.parseFromString(this.request.response,"text/xml");
giuliomoro@0 3214 if (xml.getAllElementsByTagName("state")[0].textContent == "OK") {
giuliomoro@0 3215 this.key = xml.getAllElementsByTagName("key")[0].textContent;
giuliomoro@0 3216 this.parent.root.setAttribute("key",this.key);
giuliomoro@0 3217 this.parent.root.setAttribute("state","empty");
giuliomoro@0 3218 } else {
giuliomoro@0 3219 this.generateKey();
giuliomoro@0 3220 }
giuliomoro@0 3221 },
giuliomoro@0 3222 generateKey: function() {
giuliomoro@0 3223 var temp_key = randomString(32);
giuliomoro@0 3224 this.request.open("GET","keygen.php?key="+temp_key,true);
giuliomoro@0 3225 this.request.addEventListener("load",this);
giuliomoro@0 3226 this.request.send();
giuliomoro@0 3227 },
giuliomoro@0 3228 update: function() {
giuliomoro@0 3229 this.parent.root.setAttribute("state","update");
giuliomoro@0 3230 var xmlhttp = new XMLHttpRequest();
giuliomoro@0 3231 xmlhttp.open("POST",specification.projectReturn+"?key="+this.key);
giuliomoro@0 3232 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
giuliomoro@0 3233 xmlhttp.onerror = function(){
giuliomoro@0 3234 console.log('Error updating file to server!');
giuliomoro@0 3235 };
giuliomoro@0 3236 var hold = document.createElement("div");
giuliomoro@0 3237 var clone = this.parent.root.cloneNode(true);
giuliomoro@0 3238 hold.appendChild(clone);
giuliomoro@0 3239 xmlhttp.onload = function() {
giuliomoro@0 3240 if (this.status >= 300) {
giuliomoro@0 3241 console.log("WARNING - Could not update at this time");
giuliomoro@0 3242 } else {
giuliomoro@0 3243 var parser = new DOMParser();
giuliomoro@0 3244 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
giuliomoro@0 3245 var response = xmlDoc.getElementsByTagName('response')[0];
giuliomoro@0 3246 if (response.getAttribute("state") == "OK") {
giuliomoro@0 3247 var file = response.getElementsByTagName("file")[0];
giuliomoro@0 3248 console.log("Intermediate save: OK, written "+file.getAttribute("bytes")+"B");
giuliomoro@0 3249 } else {
giuliomoro@0 3250 var message = response.getElementsByTagName("message");
giuliomoro@0 3251 console.log("Intermediate save: Error! "+message.textContent);
giuliomoro@0 3252 }
giuliomoro@0 3253 }
giuliomoro@0 3254 }
giuliomoro@0 3255 xmlhttp.send([hold.innerHTML]);
giuliomoro@0 3256 }
giuliomoro@0 3257 }
giuliomoro@0 3258
giuliomoro@0 3259 this.createTestPageStore = function(specification)
giuliomoro@0 3260 {
giuliomoro@0 3261 var store = new this.pageNode(this,specification);
giuliomoro@0 3262 this.testPages.push(store);
giuliomoro@0 3263 return this.testPages[this.testPages.length-1];
giuliomoro@0 3264 };
giuliomoro@0 3265
giuliomoro@0 3266 this.surveyNode = function(parent,root,specification)
giuliomoro@0 3267 {
giuliomoro@0 3268 this.specification = specification;
giuliomoro@0 3269 this.parent = parent;
giuliomoro@0 3270 this.state = "empty";
giuliomoro@0 3271 this.XMLDOM = this.parent.document.createElement('survey');
giuliomoro@0 3272 this.XMLDOM.setAttribute('location',this.specification.location);
giuliomoro@0 3273 this.XMLDOM.setAttribute("state",this.state);
giuliomoro@0 3274 for (var optNode of this.specification.options)
giuliomoro@0 3275 {
giuliomoro@0 3276 if (optNode.type != 'statement')
giuliomoro@0 3277 {
giuliomoro@0 3278 var node = this.parent.document.createElement('surveyresult');
giuliomoro@0 3279 node.setAttribute("ref",optNode.id);
giuliomoro@0 3280 node.setAttribute('type',optNode.type);
giuliomoro@0 3281 this.XMLDOM.appendChild(node);
giuliomoro@0 3282 }
giuliomoro@0 3283 }
giuliomoro@0 3284 root.appendChild(this.XMLDOM);
giuliomoro@0 3285
giuliomoro@0 3286 this.postResult = function(node)
giuliomoro@0 3287 {
giuliomoro@0 3288 // From popup: node is the popupOption node containing both spec. and results
giuliomoro@0 3289 // ID is the position
giuliomoro@0 3290 if (node.specification.type == 'statement'){return;}
giuliomoro@0 3291 var surveyresult = this.XMLDOM.children[0];
giuliomoro@0 3292 while(surveyresult != null) {
giuliomoro@0 3293 if (surveyresult.getAttribute("ref") == node.specification.id)
giuliomoro@0 3294 {
giuliomoro@0 3295 break;
giuliomoro@0 3296 }
giuliomoro@0 3297 surveyresult = surveyresult.nextElementSibling;
giuliomoro@0 3298 }
giuliomoro@0 3299 switch(node.specification.type)
giuliomoro@0 3300 {
giuliomoro@0 3301 case "number":
giuliomoro@0 3302 case "question":
giuliomoro@0 3303 var child = this.parent.document.createElement('response');
giuliomoro@0 3304 child.textContent = node.response;
giuliomoro@0 3305 surveyresult.appendChild(child);
giuliomoro@0 3306 break;
giuliomoro@0 3307 case "radio":
giuliomoro@0 3308 var child = this.parent.document.createElement('response');
giuliomoro@0 3309 child.setAttribute('name',node.response.name);
giuliomoro@0 3310 child.textContent = node.response.text;
giuliomoro@0 3311 surveyresult.appendChild(child);
giuliomoro@0 3312 break;
giuliomoro@0 3313 case "checkbox":
giuliomoro@0 3314 for (var i=0; i<node.response.length; i++)
giuliomoro@0 3315 {
giuliomoro@0 3316 var checkNode = this.parent.document.createElement('response');
giuliomoro@0 3317 checkNode.setAttribute('name',node.response[i].name);
giuliomoro@0 3318 checkNode.setAttribute('checked',node.response[i].checked);
giuliomoro@0 3319 surveyresult.appendChild(checkNode);
giuliomoro@0 3320 }
giuliomoro@0 3321 break;
giuliomoro@0 3322 }
giuliomoro@0 3323 };
giuliomoro@0 3324 this.complete = function() {
giuliomoro@0 3325 this.state = "complete";
giuliomoro@0 3326 this.XMLDOM.setAttribute("state",this.state);
giuliomoro@0 3327 }
giuliomoro@0 3328 };
giuliomoro@0 3329
giuliomoro@0 3330 this.pageNode = function(parent,specification)
giuliomoro@0 3331 {
giuliomoro@0 3332 // Create one store per test page
giuliomoro@0 3333 this.specification = specification;
giuliomoro@0 3334 this.parent = parent;
giuliomoro@0 3335 this.state = "empty";
giuliomoro@0 3336 this.XMLDOM = this.parent.document.createElement('page');
giuliomoro@0 3337 this.XMLDOM.setAttribute('ref',specification.id);
giuliomoro@0 3338 this.XMLDOM.setAttribute('presentedId',specification.presentedId);
giuliomoro@0 3339 this.XMLDOM.setAttribute("state",this.state);
giuliomoro@0 3340 if (specification.preTest != undefined){this.preTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.preTest);}
giuliomoro@0 3341 if (specification.postTest != undefined){this.postTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.postTest);}
giuliomoro@0 3342
giuliomoro@0 3343 // Add any page metrics
giuliomoro@0 3344 var page_metric = this.parent.document.createElement('metric');
giuliomoro@0 3345 this.XMLDOM.appendChild(page_metric);
giuliomoro@0 3346
giuliomoro@0 3347 // Add the audioelement
giuliomoro@0 3348 for (var element of this.specification.audioElements)
giuliomoro@0 3349 {
giuliomoro@0 3350 var aeNode = this.parent.document.createElement('audioelement');
giuliomoro@0 3351 aeNode.setAttribute('ref',element.id);
giuliomoro@0 3352 if (element.name != undefined){aeNode.setAttribute('name',element.name)};
giuliomoro@0 3353 aeNode.setAttribute('type',element.type);
giuliomoro@0 3354 aeNode.setAttribute('url', element.url);
giuliomoro@0 3355 aeNode.setAttribute('gain', element.gain);
giuliomoro@0 3356 if (element.type == 'anchor' || element.type == 'reference')
giuliomoro@0 3357 {
giuliomoro@0 3358 if (element.marker > 0)
giuliomoro@0 3359 {
giuliomoro@0 3360 aeNode.setAttribute('marker',element.marker);
giuliomoro@0 3361 }
giuliomoro@0 3362 }
giuliomoro@0 3363 var ae_metric = this.parent.document.createElement('metric');
giuliomoro@0 3364 aeNode.appendChild(ae_metric);
giuliomoro@0 3365 this.XMLDOM.appendChild(aeNode);
giuliomoro@0 3366 }
giuliomoro@0 3367
giuliomoro@0 3368 this.parent.root.appendChild(this.XMLDOM);
giuliomoro@0 3369
giuliomoro@0 3370 this.complete = function() {
giuliomoro@0 3371 this.state = "complete";
giuliomoro@0 3372 this.XMLDOM.setAttribute("state","complete");
giuliomoro@0 3373 }
giuliomoro@0 3374 };
giuliomoro@0 3375 this.update = function() {
giuliomoro@0 3376 this.SessionKey.update();
giuliomoro@0 3377 }
giuliomoro@0 3378 this.finish = function()
giuliomoro@0 3379 {
giuliomoro@0 3380 if (this.state == 0)
giuliomoro@0 3381 {
giuliomoro@0 3382 this.update();
giuliomoro@0 3383 }
giuliomoro@0 3384 this.state = 1;
giuliomoro@0 3385 return this.root;
giuliomoro@0 3386 };
giuliomoro@0 3387 }