nickjillings@1682: /**
nickjillings@1682: * core.js
nickjillings@1682: *
nickjillings@1682: * Main script to run, calls all other core functions and manages loading/store to backend.
nickjillings@1682: * Also contains all global variables.
nickjillings@1682: */
nickjillings@1682:
nickjillings@1682: /* create the web audio API context and store in audioContext*/
nickjillings@1643: var audioContext; // Hold the browser web audio API
nickjillings@1643: var projectXML; // Hold the parsed setup XML
nickjillings@1324: var schemaXSD; // Hold the parsed schema XSD
nickjillings@1581: var specification;
nickjillings@1582: var interfaceContext;
nickjillings@1324: var storage;
nickjillings@1622: var popup; // Hold the interfacePopup object
nickjillings@1634: var testState;
nickjillings@1655: var currentTrackOrder = []; // Hold the current XML tracks in their (randomised) order
nickjillings@1643: var audioEngineContext; // The custome AudioEngine object
nickjillings@1643: var projectReturn; // Hold the URL for the return
nickjillings@2013:
nickjillings@1318:
nickjillings@1318: // Add a prototype to the bufferSourceNode to reference to the audioObject holding it
nickjillings@1318: AudioBufferSourceNode.prototype.owner = undefined;
nickjillings@2101: // Add a prototype to the bufferSourceNode to hold when the object was given a play command
nickjillings@2101: AudioBufferSourceNode.prototype.playbackStartTime = undefined;
nickjillings@1318: // Add a prototype to the bufferNode to hold the desired LINEAR gain
nickjillings@1320: AudioBuffer.prototype.playbackGain = undefined;
nickjillings@1318: // Add a prototype to the bufferNode to hold the computed LUFS loudness
nickjillings@1318: AudioBuffer.prototype.lufs = undefined;
nickjillings@1318:
nickjillings@2192: // Convert relative URLs into absolutes
nickjillings@2192: function escapeHTML(s) {
nickjillings@2192: return s.split('&').join('&').split('<').join('<').split('"').join('"');
nickjillings@2192: }
nickjillings@2192: function qualifyURL(url) {
nickjillings@2192: var el= document.createElement('div');
nickjillings@2192: el.innerHTML= 'x';
nickjillings@2192: return el.firstChild.href;
nickjillings@2192: }
nickjillings@2192:
nickjillings@1348: // Firefox does not have an XMLDocument.prototype.getElementsByName
nickjillings@1348: // and there is no searchAll style command, this custom function will
nickjillings@1348: // search all children recusrively for the name. Used for XSD where all
nickjillings@1348: // element nodes must have a name and therefore can pull the schema node
nickjillings@1348: XMLDocument.prototype.getAllElementsByName = function(name)
nickjillings@1348: {
nickjillings@1348: name = String(name);
nickjillings@1348: var selected = this.documentElement.getAllElementsByName(name);
nickjillings@1348: return selected;
nickjillings@1348: }
nickjillings@1348:
nickjillings@1348: Element.prototype.getAllElementsByName = function(name)
nickjillings@1348: {
nickjillings@1348: name = String(name);
nickjillings@1348: var selected = [];
nickjillings@1348: var node = this.firstElementChild;
nickjillings@1348: while(node != null)
nickjillings@1348: {
nickjillings@1348: if (node.getAttribute('name') == name)
nickjillings@1348: {
nickjillings@1348: selected.push(node);
nickjillings@1348: }
nickjillings@1348: if (node.childElementCount > 0)
nickjillings@1348: {
nickjillings@1348: selected = selected.concat(node.getAllElementsByName(name));
nickjillings@1348: }
nickjillings@1348: node = node.nextElementSibling;
nickjillings@1348: }
nickjillings@1348: return selected;
nickjillings@1348: }
nickjillings@1348:
nickjillings@1348: XMLDocument.prototype.getAllElementsByTagName = function(name)
nickjillings@1348: {
nickjillings@1348: name = String(name);
nickjillings@1348: var selected = this.documentElement.getAllElementsByTagName(name);
nickjillings@1348: return selected;
nickjillings@1348: }
nickjillings@1348:
nickjillings@1348: Element.prototype.getAllElementsByTagName = function(name)
nickjillings@1348: {
nickjillings@1348: name = String(name);
nickjillings@1348: var selected = [];
nickjillings@1348: var node = this.firstElementChild;
nickjillings@1348: while(node != null)
nickjillings@1348: {
nickjillings@1348: if (node.nodeName == name)
nickjillings@1348: {
nickjillings@1348: selected.push(node);
nickjillings@1348: }
nickjillings@1348: if (node.childElementCount > 0)
nickjillings@1348: {
nickjillings@1348: selected = selected.concat(node.getAllElementsByTagName(name));
nickjillings@1348: }
nickjillings@1348: node = node.nextElementSibling;
nickjillings@1348: }
nickjillings@1348: return selected;
nickjillings@1348: }
nickjillings@1348:
nickjillings@1348: // Firefox does not have an XMLDocument.prototype.getElementsByName
nickjillings@1348: if (typeof XMLDocument.prototype.getElementsByName != "function") {
nickjillings@1348: XMLDocument.prototype.getElementsByName = function(name)
nickjillings@1348: {
nickjillings@1348: name = String(name);
nickjillings@1348: var node = this.documentElement.firstElementChild;
nickjillings@1348: var selected = [];
nickjillings@1348: while(node != null)
nickjillings@1348: {
nickjillings@1348: if (node.getAttribute('name') == name)
nickjillings@1348: {
nickjillings@1348: selected.push(node);
nickjillings@1348: }
nickjillings@1348: node = node.nextElementSibling;
nickjillings@1348: }
nickjillings@1348: return selected;
nickjillings@1348: }
nickjillings@1348: }
nickjillings@1348:
nickjillings@1682: window.onload = function() {
nickjillings@1682: // Function called once the browser has loaded all files.
nickjillings@1682: // This should perform any initial commands such as structure / loading documents
nickjillings@1682:
nickjillings@1682: // Create a web audio API context
nickjillings@1701: // Fixed for cross-browser support
nickjillings@1701: var AudioContext = window.AudioContext || window.webkitAudioContext;
nickjillings@1688: audioContext = new AudioContext;
nickjillings@1682:
nickjillings@1634: // Create test state
nickjillings@1634: testState = new stateMachine();
nickjillings@1634:
nickjillings@1622: // Create the popup interface object
nickjillings@1622: popup = new interfacePopup();
nickjillings@1370:
nickjillings@1370: // Create the specification object
nickjillings@1581: specification = new Specification();
nickjillings@1582:
nickjillings@1582: // Create the interface object
nickjillings@1582: interfaceContext = new Interface(specification);
nickjillings@1324:
nickjillings@1324: // Create the storage object
nickjillings@1324: storage = new Storage();
nickjillings@1410: // Define window callbacks for interface
nickjillings@1410: window.onresize = function(event){interfaceContext.resizeWindow(event);};
nickjillings@1697: };
nickjillings@1682:
nickjillings@1408: function loadProjectSpec(url) {
nickjillings@1408: // Load the project document from the given URL, decode the XML and instruct audioEngine to get audio data
nickjillings@1408: // If url is null, request client to upload project XML document
nickjillings@1324: var xmlhttp = new XMLHttpRequest();
nickjillings@1324: xmlhttp.open("GET",'test-schema.xsd',true);
nickjillings@1324: xmlhttp.onload = function()
nickjillings@1324: {
nickjillings@1324: schemaXSD = xmlhttp.response;
nickjillings@1324: var parse = new DOMParser();
nickjillings@1324: specification.schema = parse.parseFromString(xmlhttp.response,'text/xml');
nickjillings@1324: var r = new XMLHttpRequest();
nickjillings@1324: r.open('GET',url,true);
nickjillings@1324: r.onload = function() {
nickjillings@1324: loadProjectSpecCallback(r.response);
nickjillings@1324: };
nickjillings@2110: r.onerror = function() {
nickjillings@2110: document.getElementsByTagName('body')[0].innerHTML = null;
nickjillings@2110: var msg = document.createElement("h3");
nickjillings@2110: msg.textContent = "FATAL ERROR";
nickjillings@2110: var span = document.createElement("p");
nickjillings@2110: 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.";
nickjillings@2110: document.getElementsByTagName('body')[0].appendChild(msg);
nickjillings@2110: document.getElementsByTagName('body')[0].appendChild(span);
nickjillings@2110: }
nickjillings@1324: r.send();
nickjillings@1408: };
nickjillings@1324: xmlhttp.send();
nickjillings@1408: };
nickjillings@1408:
nickjillings@1408: function loadProjectSpecCallback(response) {
nickjillings@1408: // Function called after asynchronous download of XML project specification
nickjillings@1408: //var decode = $.parseXML(response);
nickjillings@1408: //projectXML = $(decode);
nickjillings@1408:
nickjillings@2147: // Check if XML is new or a resumption
nickjillings@2147: var parse = new DOMParser();
nickjillings@2147: var responseDocument = parse.parseFromString(response,'text/xml');
nickjillings@2147: var errorNode = responseDocument.getElementsByTagName('parsererror');
nickjillings@1443: if (errorNode.length >= 1)
nickjillings@1443: {
nickjillings@1443: var msg = document.createElement("h3");
nickjillings@1443: msg.textContent = "FATAL ERROR";
nickjillings@1443: var span = document.createElement("span");
nickjillings@1443: span.textContent = "The XML parser returned the following errors when decoding your XML file";
nickjillings@1445: document.getElementsByTagName('body')[0].innerHTML = null;
nickjillings@1443: document.getElementsByTagName('body')[0].appendChild(msg);
nickjillings@1443: document.getElementsByTagName('body')[0].appendChild(span);
nickjillings@1443: document.getElementsByTagName('body')[0].appendChild(errorNode[0]);
nickjillings@1443: return;
nickjillings@1443: }
nickjillings@2169: if (responseDocument == undefined) {
nickjillings@2169: var msg = document.createElement("h3");
nickjillings@2169: msg.textContent = "FATAL ERROR";
nickjillings@2169: var span = document.createElement("span");
nickjillings@2169: span.textContent = "The project XML was not decoded properly, try refreshing your browser and clearing caches. If the problem persists, contact the test creator.";
nickjillings@2169: document.getElementsByTagName('body')[0].innerHTML = null;
nickjillings@2169: document.getElementsByTagName('body')[0].appendChild(msg);
nickjillings@2169: document.getElementsByTagName('body')[0].appendChild(span);
nickjillings@2169: return;
nickjillings@2169: }
nickjillings@2147: if (responseDocument.children[0].nodeName == "waet") {
nickjillings@2147: // document is a specification
nickjillings@2147:
nickjillings@2147: // Perform XML schema validation
nickjillings@2147: var Module = {
nickjillings@2147: xml: response,
nickjillings@2147: schema: schemaXSD,
nickjillings@2147: arguments:["--noout", "--schema", 'test-schema.xsd','document.xml']
nickjillings@2147: };
nickjillings@2147: projectXML = responseDocument;
nickjillings@2147: var xmllint = validateXML(Module);
nickjillings@2147: console.log(xmllint);
nickjillings@2147: if(xmllint != 'document.xml validates\n')
nickjillings@2147: {
nickjillings@2147: document.getElementsByTagName('body')[0].innerHTML = null;
nickjillings@2147: var msg = document.createElement("h3");
nickjillings@2147: msg.textContent = "FATAL ERROR";
nickjillings@2147: var span = document.createElement("h4");
nickjillings@2147: span.textContent = "The XML validator returned the following errors when decoding your XML file";
nickjillings@2147: document.getElementsByTagName('body')[0].appendChild(msg);
nickjillings@2147: document.getElementsByTagName('body')[0].appendChild(span);
nickjillings@2147: xmllint = xmllint.split('\n');
nickjillings@2147: for (var i in xmllint)
nickjillings@2147: {
nickjillings@2147: document.getElementsByTagName('body')[0].appendChild(document.createElement('br'));
nickjillings@2147: var span = document.createElement("span");
nickjillings@2147: span.textContent = xmllint[i];
nickjillings@2147: document.getElementsByTagName('body')[0].appendChild(span);
nickjillings@2147: }
nickjillings@2147: return;
nickjillings@2147: }
nickjillings@2149: // Build the specification
nickjillings@2149: specification.decode(projectXML);
nickjillings@2147: // Generate the session-key
nickjillings@2147: storage.initialise();
nickjillings@2147:
nickjillings@2147: } else if (responseDocument.children[0].nodeName == "waetresult") {
nickjillings@2147: // document is a result
nickjillings@1294: projectXML = document.implementation.createDocument(null,"waet");
nickjillings@1294: projectXML.children[0].appendChild(responseDocument.getElementsByTagName('waet')[0].getElementsByTagName("setup")[0].cloneNode(true));
nickjillings@1294: var child = responseDocument.children[0].children[0];
nickjillings@1294: while (child != null) {
nickjillings@1294: if (child.nodeName == "survey") {
nickjillings@1294: // One of the global survey elements
nickjillings@1294: if (child.getAttribute("state") == "complete") {
nickjillings@1294: // We need to remove this survey from
nickjillings@1294: var location = child.getAttribute("location");
nickjillings@1294: var globalSurveys = projectXML.getElementsByTagName("setup")[0].getElementsByTagName("survey")[0];
nickjillings@1294: while(globalSurveys != null) {
nickjillings@1294: if (location == "pre" || location == "before") {
nickjillings@1294: if (globalSurveys.getAttribute("location") == "pre" || globalSurveys.getAttribute("location") == "before") {
nickjillings@1294: projectXML.getElementsByTagName("setup")[0].removeChild(globalSurveys);
nickjillings@1294: break;
nickjillings@1294: }
nickjillings@1294: } else {
nickjillings@1294: if (globalSurveys.getAttribute("location") == "post" || globalSurveys.getAttribute("location") == "after") {
nickjillings@1294: projectXML.getElementsByTagName("setup")[0].removeChild(globalSurveys);
nickjillings@1294: break;
nickjillings@1294: }
nickjillings@1294: }
nickjillings@1294: globalSurveys = globalSurveys.nextElementSibling;
nickjillings@1294: }
nickjillings@1294: } else {
nickjillings@1294: // We need to complete this, so it must be regenerated by store
nickjillings@1294: var copy = child;
nickjillings@1294: child = child.previousElementSibling;
nickjillings@1294: responseDocument.children[0].removeChild(copy);
nickjillings@1294: }
nickjillings@1294: } else if (child.nodeName == "page") {
nickjillings@1294: if (child.getAttribute("state") == "empty") {
nickjillings@1294: // We need to complete this page
nickjillings@1294: projectXML.children[0].appendChild(responseDocument.getElementById(child.getAttribute("ref")).cloneNode(true));
nickjillings@1294: var copy = child;
nickjillings@1294: child = child.previousElementSibling;
nickjillings@1294: responseDocument.children[0].removeChild(copy);
nickjillings@1294: }
nickjillings@1294: }
nickjillings@1294: child = child.nextElementSibling;
nickjillings@1294: }
nickjillings@2149: // Build the specification
nickjillings@2149: specification.decode(projectXML);
nickjillings@1294: // Use the original
nickjillings@1294: storage.initialise(responseDocument);
nickjillings@2147: }
nickjillings@1339: /// CHECK FOR SAMPLE RATE COMPATIBILITY
nickjillings@1339: if (specification.sampleRate != undefined) {
nickjillings@1339: if (Number(specification.sampleRate) != audioContext.sampleRate) {
nickjillings@1339: 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.';
nickjillings@1339: alert(errStr);
nickjillings@1339: return;
nickjillings@1339: }
nickjillings@1339: }
nickjillings@1408:
nickjillings@1408: // Detect the interface to use and load the relevant javascripts.
nickjillings@1408: var interfaceJS = document.createElement('script');
nickjillings@1408: interfaceJS.setAttribute("type","text/javascript");
nickjillings@1329: switch(specification.interface)
nickjillings@1329: {
nickjillings@1329: case "APE":
nickjillings@2161: interfaceJS.setAttribute("src","interfaces/ape.js");
nickjillings@2161:
nickjillings@2161: // APE comes with a css file
nickjillings@2161: var css = document.createElement('link');
nickjillings@2161: css.rel = 'stylesheet';
nickjillings@2161: css.type = 'text/css';
nickjillings@2161: css.href = 'interfaces/ape.css';
nickjillings@2161:
nickjillings@2161: document.getElementsByTagName("head")[0].appendChild(css);
nickjillings@2161: break;
nickjillings@2161:
nickjillings@1329: case "MUSHRA":
nickjillings@2161: interfaceJS.setAttribute("src","interfaces/mushra.js");
nickjillings@2161:
nickjillings@2161: // MUSHRA comes with a css file
nickjillings@2161: var css = document.createElement('link');
nickjillings@2161: css.rel = 'stylesheet';
nickjillings@2161: css.type = 'text/css';
nickjillings@2161: css.href = 'interfaces/mushra.css';
nickjillings@2161:
nickjillings@2161: document.getElementsByTagName("head")[0].appendChild(css);
nickjillings@2161: break;
nickjillings@1329:
nickjillings@1329: case "AB":
nickjillings@2161: interfaceJS.setAttribute("src","interfaces/AB.js");
nickjillings@2161:
nickjillings@2161: // AB comes with a css file
nickjillings@2161: var css = document.createElement('link');
nickjillings@2161: css.rel = 'stylesheet';
nickjillings@2161: css.type = 'text/css';
nickjillings@2161: css.href = 'interfaces/AB.css';
nickjillings@2161:
nickjillings@2161: document.getElementsByTagName("head")[0].appendChild(css);
nickjillings@2161: break;
nickjillings@2161:
nickjillings@2161: case "ABX":
nickjillings@2161: interfaceJS.setAttribute("src","interfaces/ABX.js");
nickjillings@2161:
nickjillings@2161: // AB comes with a css file
nickjillings@2161: var css = document.createElement('link');
nickjillings@2161: css.rel = 'stylesheet';
nickjillings@2161: css.type = 'text/css';
nickjillings@2161: css.href = 'interfaces/ABX.css';
nickjillings@2161:
nickjillings@2161: document.getElementsByTagName("head")[0].appendChild(css);
nickjillings@2161: break;
nickjillings@2161:
nickjillings@1345: case "Bipolar":
nickjillings@1345: case "ACR":
nickjillings@1345: case "DCR":
nickjillings@1345: case "CCR":
nickjillings@1343: case "ABC":
nickjillings@2161: // Above enumerate to horizontal sliders
nickjillings@2161: interfaceJS.setAttribute("src","interfaces/horizontal-sliders.js");
nickjillings@2161:
nickjillings@2161: // horizontal-sliders comes with a css file
nickjillings@2161: var css = document.createElement('link');
nickjillings@2161: css.rel = 'stylesheet';
nickjillings@2161: css.type = 'text/css';
nickjillings@2161: css.href = 'interfaces/horizontal-sliders.css';
nickjillings@2161:
nickjillings@2161: document.getElementsByTagName("head")[0].appendChild(css);
nickjillings@2161: break;
nickjillings@1345: case "discrete":
nickjillings@1345: case "likert":
nickjillings@2161: // Above enumerate to horizontal discrete radios
nickjillings@2161: interfaceJS.setAttribute("src","interfaces/discrete.js");
nickjillings@2161:
nickjillings@2161: // horizontal-sliders comes with a css file
nickjillings@2161: var css = document.createElement('link');
nickjillings@2161: css.rel = 'stylesheet';
nickjillings@2161: css.type = 'text/css';
nickjillings@2161: css.href = 'interfaces/discrete.css';
nickjillings@2161:
nickjillings@2161: document.getElementsByTagName("head")[0].appendChild(css);
nickjillings@2161: break;
nickjillings@1408: }
nickjillings@1408: document.getElementsByTagName("head")[0].appendChild(interfaceJS);
nickjillings@1408:
nickjillings@1410: // Create the audio engine object
nickjillings@1410: audioEngineContext = new AudioEngine(specification);
nickjillings@1408: }
nickjillings@1408:
nickjillings@1408: function createProjectSave(destURL) {
nickjillings@2167: // Clear the window.onbeforeunload
nickjillings@2167: window.onbeforeunload = null;
nickjillings@1408: // Save the data from interface into XML and send to destURL
nickjillings@1408: // If destURL is null then download XML in client
nickjillings@1408: // Now time to render file locally
nickjillings@1408: var xmlDoc = interfaceXMLSave();
nickjillings@1408: var parent = document.createElement("div");
nickjillings@1408: parent.appendChild(xmlDoc);
nickjillings@1408: var file = [parent.innerHTML];
nickjillings@2150: if (destURL == "local") {
nickjillings@1408: var bb = new Blob(file,{type : 'application/xml'});
nickjillings@1408: var dnlk = window.URL.createObjectURL(bb);
nickjillings@1408: var a = document.createElement("a");
nickjillings@1408: a.hidden = '';
nickjillings@1408: a.href = dnlk;
nickjillings@1408: a.download = "save.xml";
nickjillings@1408: a.textContent = "Save File";
nickjillings@1408:
nickjillings@1408: popup.showPopup();
nickjillings@1332: popup.popupContent.innerHTML = "Please save the file below to give to your test supervisor
";
nickjillings@1408: popup.popupContent.appendChild(a);
nickjillings@1408: } else {
nickjillings@1408: var xmlhttp = new XMLHttpRequest;
nickjillings@1293: xmlhttp.open("POST","\save.php?key="+storage.SessionKey.key,true);
nickjillings@1408: xmlhttp.setRequestHeader('Content-Type', 'text/xml');
nickjillings@1408: xmlhttp.onerror = function(){
nickjillings@1408: console.log('Error saving file to server! Presenting download locally');
nickjillings@1293: createProjectSave("local");
nickjillings@1408: };
nickjillings@2150: xmlhttp.onload = function() {
nickjillings@2150: console.log(xmlhttp);
nickjillings@2150: if (this.status >= 300) {
nickjillings@2150: console.log("WARNING - Could not update at this time");
nickjillings@1293: createProjectSave("local");
nickjillings@2150: } else {
nickjillings@2150: var parser = new DOMParser();
nickjillings@2150: var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
nickjillings@2150: var response = xmlDoc.getElementsByTagName('response')[0];
nickjillings@2150: if (response.getAttribute("state") == "OK") {
nickjillings@2150: var file = response.getElementsByTagName("file")[0];
nickjillings@2150: console.log("Save: OK, written "+file.getAttribute("bytes")+"B");
nickjillings@2179: popup.popupContent.textContent = specification.exitText;
nickjillings@2150: } else {
nickjillings@2150: var message = response.getElementsByTagName("message");
nickjillings@2150: console.log("Save: Error! "+message.textContent);
nickjillings@2150: createProjectSave("local");
nickjillings@2150: }
nickjillings@2150: }
nickjillings@2150: };
nickjillings@1408: xmlhttp.send(file);
nickjillings@1332: popup.showPopup();
nickjillings@1332: popup.popupContent.innerHTML = null;
nickjillings@1332: popup.popupContent.textContent = "Submitting. Please Wait";
nickjillings@2106: popup.hideNextButton();
nickjillings@2106: popup.hidePreviousButton();
nickjillings@1408: }
nickjillings@1408: }
nickjillings@1408:
nickjillings@1408: function errorSessionDump(msg){
nickjillings@1408: // Create the partial interface XML save
nickjillings@1408: // Include error node with message on why the dump occured
nickjillings@1443: popup.showPopup();
nickjillings@1443: popup.popupContent.innerHTML = null;
nickjillings@1443: var err = document.createElement('error');
nickjillings@1443: var parent = document.createElement("div");
nickjillings@1443: if (typeof msg === "object")
nickjillings@1443: {
nickjillings@1443: err.appendChild(msg);
nickjillings@1443: popup.popupContent.appendChild(msg);
nickjillings@1443:
nickjillings@1443: } else {
nickjillings@1443: err.textContent = msg;
nickjillings@1443: popup.popupContent.innerHTML = "ERROR : "+msg;
nickjillings@1443: }
nickjillings@1408: var xmlDoc = interfaceXMLSave();
nickjillings@1408: xmlDoc.appendChild(err);
nickjillings@1408: parent.appendChild(xmlDoc);
nickjillings@1408: var file = [parent.innerHTML];
nickjillings@1408: var bb = new Blob(file,{type : 'application/xml'});
nickjillings@1408: var dnlk = window.URL.createObjectURL(bb);
nickjillings@1408: var a = document.createElement("a");
nickjillings@1408: a.hidden = '';
nickjillings@1408: a.href = dnlk;
nickjillings@1408: a.download = "save.xml";
nickjillings@1408: a.textContent = "Save File";
nickjillings@1408:
nickjillings@1443:
nickjillings@1443:
nickjillings@1408: popup.popupContent.appendChild(a);
nickjillings@1408: }
nickjillings@1408:
nickjillings@1408: // Only other global function which must be defined in the interface class. Determines how to create the XML document.
nickjillings@1408: function interfaceXMLSave(){
nickjillings@1408: // Create the XML string to be exported with results
nickjillings@1324: return storage.finish();
nickjillings@1408: }
nickjillings@1408:
nickjillings@1426: function linearToDecibel(gain)
nickjillings@1426: {
nickjillings@1426: return 20.0*Math.log10(gain);
nickjillings@1426: }
nickjillings@1426:
nickjillings@1426: function decibelToLinear(gain)
nickjillings@1426: {
nickjillings@1426: return Math.pow(10,gain/20.0);
nickjillings@1426: }
nickjillings@1426:
nickjillings@2147: function randomString(length) {
nickjillings@2147: return Math.round((Math.pow(36, length + 1) - Math.random() * Math.pow(36, length))).toString(36).slice(1);
nickjillings@2147: }
nickjillings@2147:
nickjillings@1622: function interfacePopup() {
nickjillings@1622: // Creates an object to manage the popup
nickjillings@1622: this.popup = null;
nickjillings@1622: this.popupContent = null;
nickjillings@1526: this.popupTitle = null;
nickjillings@1526: this.popupResponse = null;
nickjillings@1574: this.buttonProceed = null;
nickjillings@2034: this.buttonPrevious = null;
nickjillings@1622: this.popupOptions = null;
nickjillings@1622: this.currentIndex = null;
nickjillings@1324: this.node = null;
nickjillings@1324: this.store = null;
nickjillings@1422: $(window).keypress(function(e){
nickjillings@1422: if (e.keyCode == 13 && popup.popup.style.visibility == 'visible')
nickjillings@1422: {
nickjillings@1422: console.log(e);
nickjillings@1422: popup.buttonProceed.onclick();
nickjillings@1424: e.preventDefault();
nickjillings@1422: }
nickjillings@1422: });
nickjillings@1581:
nickjillings@1622: this.createPopup = function(){
nickjillings@1622: // Create popup window interface
nickjillings@1622: var insertPoint = document.getElementById("topLevelBody");
nickjillings@1622:
nickjillings@1379: this.popup = document.getElementById('popupHolder');
nickjillings@1622: this.popup.style.left = (window.innerWidth/2)-250 + 'px';
nickjillings@1622: this.popup.style.top = (window.innerHeight/2)-125 + 'px';
nickjillings@1622:
nickjillings@1379: this.popupContent = document.getElementById('popupContent');
nickjillings@1622:
nickjillings@1379: this.popupTitle = document.getElementById('popupTitle');
nickjillings@1526:
nickjillings@1379: this.popupResponse = document.getElementById('popupResponse');
nickjillings@1526:
nickjillings@1379: this.buttonProceed = document.getElementById('popup-proceed');
nickjillings@1574: this.buttonProceed.onclick = function(){popup.proceedClicked();};
nickjillings@2034:
nickjillings@1379: this.buttonPrevious = document.getElementById('popup-previous');
nickjillings@2034: this.buttonPrevious.onclick = function(){popup.previousClick();};
nickjillings@2034:
nickjillings@1379: this.hidePopup();
nickjillings@1379:
nickjillings@1581: this.popup.style.zIndex = -1;
nickjillings@1581: this.popup.style.visibility = 'hidden';
nickjillings@1622: };
nickjillings@1621:
nickjillings@1622: this.showPopup = function(){
nickjillings@1581: if (this.popup == null) {
nickjillings@1622: this.createPopup();
nickjillings@1622: }
nickjillings@1622: this.popup.style.zIndex = 3;
nickjillings@1622: this.popup.style.visibility = 'visible';
nickjillings@1622: var blank = document.getElementsByClassName('testHalt')[0];
nickjillings@1622: blank.style.zIndex = 2;
nickjillings@1622: blank.style.visibility = 'visible';
nickjillings@2186: this.popupResponse.style.left="0%";
nickjillings@1622: };
nickjillings@1622:
nickjillings@1622: this.hidePopup = function(){
nickjillings@1622: this.popup.style.zIndex = -1;
nickjillings@1622: this.popup.style.visibility = 'hidden';
nickjillings@1622: var blank = document.getElementsByClassName('testHalt')[0];
nickjillings@1622: blank.style.zIndex = -2;
nickjillings@1622: blank.style.visibility = 'hidden';
nickjillings@1526: this.buttonPrevious.style.visibility = 'inherit';
nickjillings@1622: };
nickjillings@1622:
nickjillings@1622: this.postNode = function() {
nickjillings@1622: // This will take the node from the popupOptions and display it
nickjillings@1622: var node = this.popupOptions[this.currentIndex];
nickjillings@1526: this.popupResponse.innerHTML = null;
nickjillings@1324: this.popupTitle.textContent = node.specification.statement;
nickjillings@1324: if (node.specification.type == 'question') {
nickjillings@1622: var textArea = document.createElement('textarea');
nickjillings@1324: switch (node.specification.boxsize) {
nickjillings@2030: case 'small':
nickjillings@2030: textArea.cols = "20";
nickjillings@2030: textArea.rows = "1";
nickjillings@2030: break;
nickjillings@2030: case 'normal':
nickjillings@2030: textArea.cols = "30";
nickjillings@2030: textArea.rows = "2";
nickjillings@2030: break;
nickjillings@2030: case 'large':
nickjillings@2030: textArea.cols = "40";
nickjillings@2030: textArea.rows = "5";
nickjillings@2030: break;
nickjillings@2030: case 'huge':
nickjillings@2030: textArea.cols = "50";
nickjillings@2030: textArea.rows = "10";
nickjillings@2030: break;
nickjillings@2030: }
nickjillings@1361: if (node.response == undefined) {
nickjillings@1361: node.response = "";
nickjillings@1361: } else {
nickjillings@1361: textArea.value = node.response;
nickjillings@1361: }
nickjillings@1526: this.popupResponse.appendChild(textArea);
nickjillings@1526: textArea.focus();
nickjillings@1382: this.popupResponse.style.textAlign="center";
nickjillings@1382: this.popupResponse.style.left="0%";
nickjillings@1324: } else if (node.specification.type == 'checkbox') {
nickjillings@1361: if (node.response == undefined) {
nickjillings@1361: node.response = Array(node.specification.options.length);
nickjillings@1361: }
nickjillings@1361: var index = 0;
nickjillings@1382: var max_w = 0;
nickjillings@1324: for (var option of node.specification.options) {
nickjillings@1588: var input = document.createElement('input');
nickjillings@2093: input.id = option.name;
nickjillings@1588: input.type = 'checkbox';
nickjillings@1588: var span = document.createElement('span');
nickjillings@1588: span.textContent = option.text;
nickjillings@1588: var hold = document.createElement('div');
nickjillings@1588: hold.setAttribute('name','option');
nickjillings@2178: hold.className = "popup-option-checbox";
nickjillings@1588: hold.appendChild(input);
nickjillings@1588: hold.appendChild(span);
nickjillings@1324: this.popupResponse.appendChild(hold);
nickjillings@1361: if (node.response[index] != undefined){
nickjillings@1361: if (node.response[index].checked == true) {
nickjillings@1361: input.checked = "true";
nickjillings@1361: }
nickjillings@1361: }
nickjillings@2178: var w = $(hold).width();
nickjillings@1382: if (w > max_w)
nickjillings@1382: max_w = w;
nickjillings@1361: index++;
nickjillings@1588: }
nickjillings@1382: this.popupResponse.style.textAlign="";
nickjillings@2178: var leftP = 50-(((max_w/$('#popupContent').width())/2)*100);
nickjillings@1382: this.popupResponse.style.left=leftP+"%";
nickjillings@1324: } else if (node.specification.type == 'radio') {
nickjillings@1361: if (node.response == undefined) {
nickjillings@1361: node.response = {name: "", text: ""};
nickjillings@1361: }
nickjillings@1361: var index = 0;
nickjillings@1382: var max_w = 0;
nickjillings@1324: for (var option of node.specification.options) {
nickjillings@1589: var input = document.createElement('input');
nickjillings@1589: input.id = option.name;
nickjillings@1589: input.type = 'radio';
nickjillings@1324: input.name = node.specification.id;
nickjillings@1589: var span = document.createElement('span');
nickjillings@1589: span.textContent = option.text;
nickjillings@1589: var hold = document.createElement('div');
nickjillings@1589: hold.setAttribute('name','option');
nickjillings@2178: hold.className = "popup-option-checbox";
nickjillings@1589: hold.appendChild(input);
nickjillings@1589: hold.appendChild(span);
nickjillings@1324: this.popupResponse.appendChild(hold);
nickjillings@1361: if (input.id == node.response.name) {
nickjillings@1361: input.checked = "true";
nickjillings@1361: }
nickjillings@2178: var w = $(hold).width();
nickjillings@1382: if (w > max_w)
nickjillings@1382: max_w = w;
nickjillings@1589: }
nickjillings@1382: this.popupResponse.style.textAlign="";
nickjillings@2178: var leftP = 50-(((max_w/$('#popupContent').width())/2)*100);
nickjillings@1382: this.popupResponse.style.left=leftP+"%";
nickjillings@1324: } else if (node.specification.type == 'number') {
nickjillings@1573: var input = document.createElement('input');
nickjillings@2044: input.type = 'textarea';
nickjillings@1324: if (node.min != null) {input.min = node.specification.min;}
nickjillings@1324: if (node.max != null) {input.max = node.specification.max;}
nickjillings@1324: if (node.step != null) {input.step = node.specification.step;}
nickjillings@1361: if (node.response != undefined) {
nickjillings@1361: input.value = node.response;
nickjillings@1361: }
nickjillings@1526: this.popupResponse.appendChild(input);
nickjillings@1382: this.popupResponse.style.textAlign="center";
nickjillings@1382: this.popupResponse.style.left="0%";
nickjillings@1622: }
nickjillings@2034: if(this.currentIndex+1 == this.popupOptions.length) {
nickjillings@1324: if (this.node.location == "pre") {
nickjillings@1531: this.buttonProceed.textContent = 'Start';
nickjillings@1531: } else {
nickjillings@1531: this.buttonProceed.textContent = 'Submit';
nickjillings@1531: }
nickjillings@2034: } else {
nickjillings@2034: this.buttonProceed.textContent = 'Next';
nickjillings@2034: }
nickjillings@2034: if(this.currentIndex > 0)
nickjillings@1526: this.buttonPrevious.style.visibility = 'visible';
nickjillings@1526: else
nickjillings@1526: this.buttonPrevious.style.visibility = 'hidden';
nickjillings@2015: };
nickjillings@1622:
nickjillings@1324: this.initState = function(node,store) {
nickjillings@1622: //Call this with your preTest and postTest nodes when needed to
nickjillings@1622: // initialise the popup procedure.
nickjillings@1324: if (node.options.length > 0) {
nickjillings@1324: this.popupOptions = [];
nickjillings@1324: this.node = node;
nickjillings@1324: this.store = store;
nickjillings@1324: for (var opt of node.options)
nickjillings@1324: {
nickjillings@1324: this.popupOptions.push({
nickjillings@1324: specification: opt,
nickjillings@1324: response: null
nickjillings@1324: });
nickjillings@1324: }
nickjillings@1622: this.currentIndex = 0;
nickjillings@1622: this.showPopup();
nickjillings@1622: this.postNode();
nickjillings@1581: } else {
nickjillings@1581: advanceState();
nickjillings@1622: }
nickjillings@2015: };
nickjillings@1622:
nickjillings@1574: this.proceedClicked = function() {
nickjillings@1622: // Each time the popup button is clicked!
nickjillings@2186: if (testState.stateIndex == 0 && specification.calibration) {
nickjillings@2186: interfaceContext.calibrationModuleObject.collect();
nickjillings@2186: advanceState();
nickjillings@2186: return;
nickjillings@2186: }
nickjillings@1622: var node = this.popupOptions[this.currentIndex];
nickjillings@1324: if (node.specification.type == 'question') {
nickjillings@1622: // Must extract the question data
nickjillings@1622: var textArea = $(popup.popupContent).find('textarea')[0];
nickjillings@1324: if (node.specification.mandatory == true && textArea.value.length == 0) {
nickjillings@1622: alert('This question is mandatory');
nickjillings@1622: return;
nickjillings@1622: } else {
nickjillings@1622: // Save the text content
nickjillings@1324: console.log("Question: "+ node.specification.statement);
nickjillings@1623: console.log("Question Response: "+ textArea.value);
nickjillings@1324: node.response = textArea.value;
nickjillings@1622: }
nickjillings@1324: } else if (node.specification.type == 'checkbox') {
nickjillings@1588: // Must extract checkbox data
nickjillings@1326: console.log("Checkbox: "+ node.specification.statement);
nickjillings@1324: var inputs = this.popupResponse.getElementsByTagName('input');
nickjillings@1324: node.response = [];
nickjillings@1324: for (var i=0; i node.max && node.max != null) {
nickjillings@1574: alert('Number is above the maximum value of '+node.max);
nickjillings@1573: return;
nickjillings@1573: }
nickjillings@1324: node.response = input.value;
nickjillings@1622: }
nickjillings@1622: this.currentIndex++;
nickjillings@1622: if (this.currentIndex < this.popupOptions.length) {
nickjillings@1622: this.postNode();
nickjillings@1622: } else {
nickjillings@1622: // Reached the end of the popupOptions
nickjillings@1622: this.hidePopup();
nickjillings@1324: for (var node of this.popupOptions)
nickjillings@1324: {
nickjillings@1324: this.store.postResult(node);
nickjillings@1634: }
nickjillings@1294: this.store.complete();
nickjillings@1622: advanceState();
nickjillings@1622: }
nickjillings@2015: };
nickjillings@2034:
nickjillings@2034: this.previousClick = function() {
nickjillings@2034: // Triggered when the 'Back' button is clicked in the survey
nickjillings@2034: if (this.currentIndex > 0) {
nickjillings@2034: this.currentIndex--;
nickjillings@2034: this.postNode();
nickjillings@2034: }
nickjillings@2034: };
nickjillings@1421:
nickjillings@1421: this.resize = function(event)
nickjillings@1421: {
nickjillings@1421: // Called on window resize;
nickjillings@1344: if (this.popup != null) {
nickjillings@1344: this.popup.style.left = (window.innerWidth/2)-250 + 'px';
nickjillings@1344: this.popup.style.top = (window.innerHeight/2)-125 + 'px';
nickjillings@1344: var blank = document.getElementsByClassName('testHalt')[0];
nickjillings@1344: blank.style.width = window.innerWidth;
nickjillings@1344: blank.style.height = window.innerHeight;
nickjillings@1344: }
nickjillings@1421: };
nickjillings@2106: this.hideNextButton = function() {
nickjillings@2106: this.buttonProceed.style.visibility = "hidden";
nickjillings@2106: }
nickjillings@2106: this.hidePreviousButton = function() {
nickjillings@2106: this.buttonPrevious.style.visibility = "hidden";
nickjillings@2106: }
nickjillings@2106: this.showNextButton = function() {
nickjillings@2106: this.buttonProceed.style.visibility = "visible";
nickjillings@2106: }
nickjillings@2106: this.showPreviousButton = function() {
nickjillings@2106: this.buttonPrevious.style.visibility = "visible";
nickjillings@2106: }
nickjillings@1621: }
nickjillings@1621:
nickjillings@1622: function advanceState()
nickjillings@1621: {
nickjillings@1634: // Just for complete clarity
nickjillings@1634: testState.advanceState();
nickjillings@1634: }
nickjillings@1634:
nickjillings@1634: function stateMachine()
nickjillings@1634: {
nickjillings@1634: // Object prototype for tracking and managing the test state
nickjillings@1634: this.stateMap = [];
nickjillings@1324: this.preTestSurvey = null;
nickjillings@1324: this.postTestSurvey = null;
nickjillings@1634: this.stateIndex = null;
nickjillings@1324: this.currentStateMap = null;
nickjillings@1324: this.currentStatePosition = null;
nickjillings@1354: this.currentStore = null;
nickjillings@1634: this.initialise = function(){
nickjillings@1324:
nickjillings@1324: // Get the data from Specification
nickjillings@1324: var pageHolder = [];
nickjillings@1324: for (var page of specification.pages)
nickjillings@1324: {
nickjillings@1380: var repeat = page.repeatCount;
nickjillings@1380: while(repeat >= 0)
nickjillings@1380: {
nickjillings@1380: pageHolder.push(page);
nickjillings@1380: repeat--;
nickjillings@1380: }
nickjillings@1324: }
nickjillings@1324: if (specification.randomiseOrder)
nickjillings@1324: {
nickjillings@1324: pageHolder = randomiseOrder(pageHolder);
nickjillings@1324: }
nickjillings@1324: for (var i=0; i 0) {
nickjillings@1634: if(this.stateIndex != null) {
nickjillings@1634: console.log('NOTE - State already initialise');
nickjillings@1634: }
nickjillings@2186: this.stateIndex = -2;
nickjillings@2186: console.log('Starting test...');
nickjillings@1634: } else {
b@2059: console.log('FATAL - StateMap not correctly constructed. EMPTY_STATE_MAP');
nickjillings@1622: }
nickjillings@1634: };
nickjillings@1634: this.advanceState = function(){
nickjillings@1634: if (this.stateIndex == null) {
nickjillings@1634: this.initialise();
nickjillings@1634: }
nickjillings@2150: storage.update();
nickjillings@2186: if (this.stateIndex == -2) {
nickjillings@2186: this.stateIndex++;
nickjillings@1324: if (this.preTestSurvey != null)
nickjillings@1324: {
nickjillings@1324: popup.initState(this.preTestSurvey,storage.globalPreTest);
nickjillings@1342: } else {
nickjillings@1342: this.advanceState();
nickjillings@1318: }
nickjillings@2186: } else if (this.stateIndex == -1) {
nickjillings@2186: this.stateIndex++;
nickjillings@2186: if (specification.calibration) {
nickjillings@2186: popup.showPopup();
nickjillings@2186: popup.popupTitle.textContent = "Calibration. Set the levels so all tones are of equal amplitude. Move your mouse over the sliders to hear the tones. The red slider is the reference tone";
nickjillings@2186: interfaceContext.calibrationModuleObject = new interfaceContext.calibrationModule();
nickjillings@2186: interfaceContext.calibrationModuleObject.build(popup.popupResponse);
nickjillings@2186: popup.hidePreviousButton();
nickjillings@2186: } else {
nickjillings@2186: this.advanceState();
nickjillings@2186: }
nickjillings@2186: }
nickjillings@2186: else if (this.stateIndex == this.stateMap.length)
nickjillings@1324: {
nickjillings@1324: // All test pages complete, post test
nickjillings@1324: console.log('Ending test ...');
nickjillings@1324: this.stateIndex++;
nickjillings@1324: if (this.postTestSurvey == null) {
nickjillings@1324: this.advanceState();
nickjillings@1318: } else {
nickjillings@1324: popup.initState(this.postTestSurvey,storage.globalPostTest);
nickjillings@1324: }
nickjillings@1324: } else if (this.stateIndex > this.stateMap.length)
nickjillings@1324: {
nickjillings@1324: createProjectSave(specification.projectReturn);
nickjillings@1634: }
nickjillings@1324: else
nickjillings@1324: {
nickjillings@2186: popup.hidePopup();
nickjillings@1324: if (this.currentStateMap == null)
nickjillings@1324: {
nickjillings@1318: this.currentStateMap = this.stateMap[this.stateIndex];
nickjillings@1334: if (this.currentStateMap.randomiseOrder)
nickjillings@1334: {
nickjillings@1334: this.currentStateMap.audioElements = randomiseOrder(this.currentStateMap.audioElements);
nickjillings@1334: }
nickjillings@2149: this.currentStore = storage.testPages[this.stateIndex];
nickjillings@1324: if (this.currentStateMap.preTest != null)
nickjillings@1324: {
nickjillings@1324: this.currentStatePosition = 'pre';
nickjillings@1324: popup.initState(this.currentStateMap.preTest,storage.testPages[this.stateIndex].preTest);
nickjillings@1318: } else {
nickjillings@1324: this.currentStatePosition = 'test';
nickjillings@1324: }
nickjillings@1324: interfaceContext.newPage(this.currentStateMap,storage.testPages[this.stateIndex]);
nickjillings@1324: return;
nickjillings@1634: }
nickjillings@1324: switch(this.currentStatePosition)
nickjillings@1324: {
nickjillings@1324: case 'pre':
nickjillings@1324: this.currentStatePosition = 'test';
nickjillings@1324: break;
nickjillings@1324: case 'test':
nickjillings@1324: this.currentStatePosition = 'post';
nickjillings@1324: // Save the data
nickjillings@1324: this.testPageCompleted();
nickjillings@1324: if (this.currentStateMap.postTest == null)
nickjillings@1324: {
nickjillings@1318: this.advanceState();
nickjillings@1324: return;
nickjillings@1634: } else {
nickjillings@1324: popup.initState(this.currentStateMap.postTest,storage.testPages[this.stateIndex].postTest);
nickjillings@1634: }
nickjillings@1324: break;
nickjillings@1324: case 'post':
nickjillings@1324: this.stateIndex++;
nickjillings@1324: this.currentStateMap = null;
nickjillings@1324: this.advanceState();
nickjillings@1324: break;
nickjillings@1324: };
nickjillings@1634: }
nickjillings@1634: };
nickjillings@1634:
nickjillings@1324: this.testPageCompleted = function() {
nickjillings@1634: // Function called each time a test page has been completed
nickjillings@1324: var storePoint = storage.testPages[this.stateIndex];
nickjillings@1324: // First get the test metric
nickjillings@1324:
nickjillings@1324: var metric = storePoint.XMLDOM.getElementsByTagName('metric')[0];
nickjillings@1412: if (audioEngineContext.metric.enableTestTimer)
nickjillings@1412: {
nickjillings@1324: var testTime = storePoint.parent.document.createElement('metricresult');
nickjillings@1412: testTime.id = 'testTime';
nickjillings@1412: testTime.textContent = audioEngineContext.timer.testDuration;
nickjillings@1412: metric.appendChild(testTime);
nickjillings@1412: }
nickjillings@1324:
nickjillings@1412: var audioObjects = audioEngineContext.audioObjects;
nickjillings@1324: for (var ao of audioEngineContext.audioObjects)
nickjillings@1412: {
nickjillings@1324: ao.exportXMLDOM();
nickjillings@1412: }
nickjillings@1324: for (var element of interfaceContext.commentQuestions)
nickjillings@1324: {
nickjillings@1324: element.exportXMLDOM(storePoint);
nickjillings@1324: }
nickjillings@1324: pageXMLSave(storePoint.XMLDOM, this.currentStateMap);
nickjillings@1294: storePoint.complete();
nickjillings@1576: };
nickjillings@1621: }
nickjillings@1621:
nickjillings@1408: function AudioEngine(specification) {
nickjillings@1682:
nickjillings@1682: // Create two output paths, the main outputGain and fooGain.
nickjillings@1682: // Output gain is default to 1 and any items for playback route here
nickjillings@1682: // Foo gain is used for analysis to ensure paths get processed, but are not heard
nickjillings@1682: // because web audio will optimise and any route which does not go to the destination gets ignored.
nickjillings@1682: this.outputGain = audioContext.createGain();
nickjillings@1682: this.fooGain = audioContext.createGain();
nickjillings@1682: this.fooGain.gain = 0;
nickjillings@1682:
nickjillings@1688: // Use this to detect playback state: 0 - stopped, 1 - playing
nickjillings@1688: this.status = 0;
nickjillings@1688:
nickjillings@1682: // Connect both gains to output
nickjillings@1682: this.outputGain.connect(audioContext.destination);
nickjillings@1682: this.fooGain.connect(audioContext.destination);
nickjillings@1682:
nickjillings@1659: // Create the timer Object
nickjillings@1659: this.timer = new timer();
nickjillings@1659: // Create session metrics
nickjillings@1408: this.metric = new sessionMetrics(this,specification);
nickjillings@1659:
nickjillings@1667: this.loopPlayback = false;
nickjillings@1667:
nickjillings@1324: this.pageStore = null;
nickjillings@1324:
nickjillings@1682: // Create store for new audioObjects
nickjillings@1682: this.audioObjects = [];
nickjillings@1682:
nickjillings@1410: this.buffers = [];
nickjillings@1430: this.bufferObj = function()
nickjillings@1410: {
nickjillings@1430: this.url = null;
nickjillings@1410: this.buffer = null;
nickjillings@1410: this.xmlRequest = new XMLHttpRequest();
nickjillings@1396: this.xmlRequest.parent = this;
nickjillings@1410: this.users = [];
nickjillings@1316: this.progress = 0;
nickjillings@1316: this.status = 0;
nickjillings@1342: this.ready = function()
nickjillings@1342: {
nickjillings@1316: if (this.status >= 2)
nickjillings@1316: {
nickjillings@1316: this.status = 3;
nickjillings@1316: }
nickjillings@1342: for (var i=0; i 0) {this.wasMoved = true;}
nickjillings@1659: this.movementTracker[this.movementTracker.length] = [time, position];
nickjillings@1659: };
nickjillings@1659:
nickjillings@1637: this.startListening = function(time)
nickjillings@1659: {
nickjillings@1617: if (this.listenHold == false)
nickjillings@1659: {
nickjillings@1659: this.wasListenedTo = true;
nickjillings@1659: this.listenStart = time;
nickjillings@1617: this.listenHold = true;
nickjillings@2019:
nickjillings@2019: var evnt = document.createElement('event');
nickjillings@2019: var testTime = document.createElement('testTime');
nickjillings@2019: testTime.setAttribute('start',time);
nickjillings@2019: var bufferTime = document.createElement('bufferTime');
nickjillings@2019: bufferTime.setAttribute('start',this.parent.getCurrentPosition());
nickjillings@2019: evnt.appendChild(testTime);
nickjillings@2019: evnt.appendChild(bufferTime);
nickjillings@2019: this.listenTracker.push(evnt);
nickjillings@2019:
nickjillings@1602: console.log('slider ' + this.parent.id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id
nickjillings@1602: }
nickjillings@1602: };
nickjillings@1637:
nickjillings@1566: this.stopListening = function(time,bufferStopTime)
nickjillings@1637: {
nickjillings@1637: if (this.listenHold == true)
nickjillings@1637: {
nickjillings@2019: var diff = time - this.listenStart;
nickjillings@2019: this.listenedTimer += (diff);
nickjillings@1659: this.listenStart = 0;
nickjillings@1617: this.listenHold = false;
nickjillings@2019:
nickjillings@2019: var evnt = this.listenTracker[this.listenTracker.length-1];
nickjillings@2019: var testTime = evnt.getElementsByTagName('testTime')[0];
nickjillings@2019: var bufferTime = evnt.getElementsByTagName('bufferTime')[0];
nickjillings@2019: testTime.setAttribute('stop',time);
nickjillings@1566: if (bufferStopTime == undefined) {
nickjillings@1566: bufferTime.setAttribute('stop',this.parent.getCurrentPosition());
nickjillings@1566: } else {
nickjillings@1566: bufferTime.setAttribute('stop',bufferStopTime);
nickjillings@1566: }
nickjillings@2019: console.log('slider ' + this.parent.id + ' played for (' + diff + ')'); // DEBUG/SAFETY: show played slider id
nickjillings@1659: }
nickjillings@1659: };
nickjillings@1577:
nickjillings@1577: this.exportXMLDOM = function() {
nickjillings@1324: var storeDOM = [];
nickjillings@1577: if (audioEngineContext.metric.enableElementTimer) {
nickjillings@1324: var mElementTimer = storage.document.createElement('metricresult');
nickjillings@1577: mElementTimer.setAttribute('name','enableElementTimer');
nickjillings@1577: mElementTimer.textContent = this.listenedTimer;
nickjillings@1324: storeDOM.push(mElementTimer);
nickjillings@1577: }
nickjillings@1577: if (audioEngineContext.metric.enableElementTracker) {
nickjillings@1324: var elementTrackerFull = storage.document.createElement('metricResult');
nickjillings@1577: elementTrackerFull.setAttribute('name','elementTrackerFull');
nickjillings@1577: for (var k=0; k tag.
nickjillings@1582: this.interfaceObjects = [];
nickjillings@1582: this.interfaceObject = function(){};
nickjillings@1582:
nickjillings@1525: this.resizeWindow = function(event)
nickjillings@1525: {
nickjillings@1421: popup.resize(event);
nickjillings@1525: for(var i=0; i
nickjillings@2170: // DD/MM/YY
nickjillings@2170: //
nickjillings@2170: //
nickjillings@2170: var dateTime = new Date();
nickjillings@2170: var hold = storage.document.createElement("datetime");
nickjillings@2170: var date = storage.document.createElement("date");
nickjillings@2170: var time = storage.document.createElement("time");
nickjillings@2170: date.setAttribute('year',dateTime.getFullYear());
nickjillings@2170: date.setAttribute('month',dateTime.getMonth()+1);
nickjillings@2170: date.setAttribute('day',dateTime.getDate());
nickjillings@2170: time.setAttribute('hour',dateTime.getHours());
nickjillings@2175: time.setAttribute('minute',dateTime.getMinutes());
nickjillings@2170: time.setAttribute('secs',dateTime.getSeconds());
nickjillings@2170:
nickjillings@2170: hold.appendChild(date);
nickjillings@2170: hold.appendChild(time);
nickjillings@2170: return hold;
nickjillings@2170:
nickjillings@2170: }
nickjillings@1465:
nickjillings@2117: this.commentBoxes = new function() {
nickjillings@2117: this.boxes = [];
nickjillings@2117: this.injectPoint = null;
nickjillings@2117: this.elementCommentBox = function(audioObject) {
nickjillings@2117: var element = audioObject.specification;
nickjillings@2117: this.audioObject = audioObject;
nickjillings@2117: this.id = audioObject.id;
nickjillings@2117: var audioHolderObject = audioObject.specification.parent;
nickjillings@2117: // Create document objects to hold the comment boxes
nickjillings@2117: this.trackComment = document.createElement('div');
nickjillings@2117: this.trackComment.className = 'comment-div';
nickjillings@2117: this.trackComment.id = 'comment-div-'+audioObject.id;
nickjillings@2117: // Create a string next to each comment asking for a comment
nickjillings@2117: this.trackString = document.createElement('span');
nickjillings@2117: this.trackString.innerHTML = audioHolderObject.commentBoxPrefix+' '+audioObject.interfaceDOM.getPresentedId();
nickjillings@2117: // Create the HTML5 comment box 'textarea'
nickjillings@2117: this.trackCommentBox = document.createElement('textarea');
nickjillings@2117: this.trackCommentBox.rows = '4';
nickjillings@2117: this.trackCommentBox.cols = '100';
nickjillings@2117: this.trackCommentBox.name = 'trackComment'+audioObject.id;
nickjillings@2117: this.trackCommentBox.className = 'trackComment';
nickjillings@2117: var br = document.createElement('br');
nickjillings@2117: // Add to the holder.
nickjillings@2117: this.trackComment.appendChild(this.trackString);
nickjillings@2117: this.trackComment.appendChild(br);
nickjillings@2117: this.trackComment.appendChild(this.trackCommentBox);
nickjillings@2117:
nickjillings@2117: this.exportXMLDOM = function() {
nickjillings@2117: var root = document.createElement('comment');
nickjillings@2117: var question = document.createElement('question');
nickjillings@2117: question.textContent = this.trackString.textContent;
nickjillings@2117: var response = document.createElement('response');
nickjillings@2117: response.textContent = this.trackCommentBox.value;
nickjillings@2117: console.log("Comment frag-"+this.id+": "+response.textContent);
nickjillings@2117: root.appendChild(question);
nickjillings@2117: root.appendChild(response);
nickjillings@2117: return root;
nickjillings@2117: };
nickjillings@2117: this.resize = function()
nickjillings@2117: {
nickjillings@2117: var boxwidth = (window.innerWidth-100)/2;
nickjillings@2117: if (boxwidth >= 600)
nickjillings@2117: {
nickjillings@2117: boxwidth = 600;
nickjillings@2117: }
nickjillings@2117: else if (boxwidth < 400)
nickjillings@2117: {
nickjillings@2117: boxwidth = 400;
nickjillings@2117: }
nickjillings@2117: this.trackComment.style.width = boxwidth+"px";
nickjillings@2117: this.trackCommentBox.style.width = boxwidth-6+"px";
nickjillings@2117: };
nickjillings@2117: this.resize();
nickjillings@2117: };
nickjillings@2117: this.createCommentBox = function(audioObject) {
nickjillings@2117: var node = new this.elementCommentBox(audioObject);
nickjillings@2117: this.boxes.push(node);
nickjillings@2117: audioObject.commentDOM = node;
nickjillings@2117: return node;
nickjillings@2117: };
nickjillings@2117: this.sortCommentBoxes = function() {
nickjillings@2117: this.boxes.sort(function(a,b){return a.id - b.id;});
nickjillings@2117: };
nickjillings@2117:
nickjillings@2117: this.showCommentBoxes = function(inject, sort) {
nickjillings@2117: this.injectPoint = inject;
nickjillings@2117: if (sort) {this.sortCommentBoxes();}
nickjillings@2117: for (var box of this.boxes) {
nickjillings@2117: inject.appendChild(box.trackComment);
nickjillings@2117: }
nickjillings@2117: };
nickjillings@2117:
nickjillings@2117: this.deleteCommentBoxes = function() {
nickjillings@2117: if (this.injectPoint != null) {
nickjillings@2117: for (var box of this.boxes) {
nickjillings@2117: this.injectPoint.removeChild(box.trackComment);
nickjillings@2117: }
nickjillings@2117: this.injectPoint = null;
nickjillings@2117: }
nickjillings@2117: this.boxes = [];
nickjillings@2117: };
nickjillings@2117: }
nickjillings@1582:
nickjillings@2032: this.commentQuestions = [];
nickjillings@2032:
nickjillings@2032: this.commentBox = function(commentQuestion) {
nickjillings@2032: this.specification = commentQuestion;
nickjillings@2032: // Create document objects to hold the comment boxes
nickjillings@2032: this.holder = document.createElement('div');
nickjillings@2032: this.holder.className = 'comment-div';
nickjillings@2032: // Create a string next to each comment asking for a comment
nickjillings@2032: this.string = document.createElement('span');
nickjillings@1324: this.string.innerHTML = commentQuestion.statement;
nickjillings@2032: // Create the HTML5 comment box 'textarea'
nickjillings@2032: this.textArea = document.createElement('textarea');
nickjillings@2032: this.textArea.rows = '4';
nickjillings@2032: this.textArea.cols = '100';
nickjillings@2032: this.textArea.className = 'trackComment';
nickjillings@2032: var br = document.createElement('br');
nickjillings@2032: // Add to the holder.
nickjillings@2032: this.holder.appendChild(this.string);
nickjillings@2032: this.holder.appendChild(br);
nickjillings@2032: this.holder.appendChild(this.textArea);
nickjillings@2032:
nickjillings@2097: this.exportXMLDOM = function(storePoint) {
nickjillings@2097: var root = storePoint.parent.document.createElement('comment');
nickjillings@2032: root.id = this.specification.id;
nickjillings@2032: root.setAttribute('type',this.specification.type);
b@2059: console.log("Question: "+this.string.textContent);
b@2059: console.log("Response: "+root.textContent);
nickjillings@2097: var question = storePoint.parent.document.createElement('question');
nickjillings@2097: question.textContent = this.string.textContent;
nickjillings@2097: var response = storePoint.parent.document.createElement('response');
nickjillings@2097: response.textContent = this.textArea.value;
nickjillings@2097: root.appendChild(question);
nickjillings@2097: root.appendChild(response);
nickjillings@2097: storePoint.XMLDOM.appendChild(root);
nickjillings@2032: return root;
nickjillings@2032: };
nickjillings@1525: this.resize = function()
nickjillings@1525: {
nickjillings@1525: var boxwidth = (window.innerWidth-100)/2;
nickjillings@1525: if (boxwidth >= 600)
nickjillings@1525: {
nickjillings@1525: boxwidth = 600;
nickjillings@1525: }
nickjillings@1525: else if (boxwidth < 400)
nickjillings@1525: {
nickjillings@1525: boxwidth = 400;
nickjillings@1525: }
nickjillings@1525: this.holder.style.width = boxwidth+"px";
nickjillings@1525: this.textArea.style.width = boxwidth-6+"px";
nickjillings@1525: };
nickjillings@1525: this.resize();
nickjillings@2032: };
nickjillings@2032:
nickjillings@2032: this.radioBox = function(commentQuestion) {
nickjillings@2032: this.specification = commentQuestion;
nickjillings@2032: // Create document objects to hold the comment boxes
nickjillings@2032: this.holder = document.createElement('div');
nickjillings@2032: this.holder.className = 'comment-div';
nickjillings@2032: // Create a string next to each comment asking for a comment
nickjillings@2032: this.string = document.createElement('span');
nickjillings@2032: this.string.innerHTML = commentQuestion.statement;
nickjillings@2032: var br = document.createElement('br');
nickjillings@2032: // Add to the holder.
nickjillings@2032: this.holder.appendChild(this.string);
nickjillings@2032: this.holder.appendChild(br);
nickjillings@2032: this.options = [];
nickjillings@2032: this.inputs = document.createElement('div');
nickjillings@2032: this.span = document.createElement('div');
nickjillings@2032: this.inputs.align = 'center';
nickjillings@2032: this.inputs.style.marginLeft = '12px';
nickjillings@2032: this.span.style.marginLeft = '12px';
nickjillings@2032: this.span.align = 'center';
nickjillings@2032: this.span.style.marginTop = '15px';
nickjillings@2032:
nickjillings@2032: var optCount = commentQuestion.options.length;
nickjillings@1324: for (var optNode of commentQuestion.options)
nickjillings@2032: {
nickjillings@2032: var div = document.createElement('div');
nickjillings@1524: div.style.width = '80px';
nickjillings@2032: div.style.float = 'left';
nickjillings@2032: var input = document.createElement('input');
nickjillings@2032: input.type = 'radio';
nickjillings@2032: input.name = commentQuestion.id;
nickjillings@1324: input.setAttribute('setvalue',optNode.name);
nickjillings@2032: input.className = 'comment-radio';
nickjillings@2032: div.appendChild(input);
nickjillings@2032: this.inputs.appendChild(div);
nickjillings@2032:
nickjillings@2032:
nickjillings@2032: div = document.createElement('div');
nickjillings@1524: div.style.width = '80px';
nickjillings@2032: div.style.float = 'left';
nickjillings@2032: div.align = 'center';
nickjillings@2032: var span = document.createElement('span');
nickjillings@1324: span.textContent = optNode.text;
nickjillings@2032: span.className = 'comment-radio-span';
nickjillings@2032: div.appendChild(span);
nickjillings@2032: this.span.appendChild(div);
nickjillings@2032: this.options.push(input);
nickjillings@2032: }
nickjillings@2032: this.holder.appendChild(this.span);
nickjillings@2032: this.holder.appendChild(this.inputs);
nickjillings@2032:
nickjillings@2097: this.exportXMLDOM = function(storePoint) {
nickjillings@2097: var root = storePoint.parent.document.createElement('comment');
nickjillings@2032: root.id = this.specification.id;
nickjillings@2032: root.setAttribute('type',this.specification.type);
nickjillings@2032: var question = document.createElement('question');
nickjillings@2032: question.textContent = this.string.textContent;
nickjillings@2032: var response = document.createElement('response');
nickjillings@2032: var i=0;
nickjillings@2032: while(this.options[i].checked == false) {
nickjillings@2032: i++;
nickjillings@2032: if (i >= this.options.length) {
nickjillings@2032: break;
nickjillings@2032: }
nickjillings@2032: }
nickjillings@2032: if (i >= this.options.length) {
nickjillings@2032: response.textContent = 'null';
nickjillings@2032: } else {
nickjillings@2032: response.textContent = this.options[i].getAttribute('setvalue');
nickjillings@2032: response.setAttribute('number',i);
nickjillings@2032: }
nickjillings@1572: console.log('Comment: '+question.textContent);
nickjillings@1572: console.log('Response: '+response.textContent);
nickjillings@2032: root.appendChild(question);
nickjillings@2032: root.appendChild(response);
nickjillings@2097: storePoint.XMLDOM.appendChild(root);
nickjillings@2032: return root;
nickjillings@2032: };
nickjillings@1525: this.resize = function()
nickjillings@1525: {
nickjillings@1525: var boxwidth = (window.innerWidth-100)/2;
nickjillings@1525: if (boxwidth >= 600)
nickjillings@1525: {
nickjillings@1525: boxwidth = 600;
nickjillings@1525: }
nickjillings@1525: else if (boxwidth < 400)
nickjillings@1525: {
nickjillings@1525: boxwidth = 400;
nickjillings@1525: }
nickjillings@1525: this.holder.style.width = boxwidth+"px";
nickjillings@1525: var text = this.holder.children[2];
nickjillings@1525: var options = this.holder.children[3];
nickjillings@1525: var optCount = options.children.length;
nickjillings@1525: var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
nickjillings@1525: var options = options.firstChild;
nickjillings@1525: var text = text.firstChild;
nickjillings@1525: options.style.marginRight = spanMargin;
nickjillings@1525: options.style.marginLeft = spanMargin;
nickjillings@1525: text.style.marginRight = spanMargin;
nickjillings@1525: text.style.marginLeft = spanMargin;
nickjillings@1525: while(options.nextSibling != undefined)
nickjillings@1525: {
nickjillings@1525: options = options.nextSibling;
nickjillings@1525: text = text.nextSibling;
nickjillings@1525: options.style.marginRight = spanMargin;
nickjillings@1525: options.style.marginLeft = spanMargin;
nickjillings@1525: text.style.marginRight = spanMargin;
nickjillings@1525: text.style.marginLeft = spanMargin;
nickjillings@1525: }
nickjillings@1525: };
nickjillings@1525: this.resize();
nickjillings@2032: };
nickjillings@2032:
nickjillings@1572: this.checkboxBox = function(commentQuestion) {
nickjillings@1572: this.specification = commentQuestion;
nickjillings@1572: // Create document objects to hold the comment boxes
nickjillings@1572: this.holder = document.createElement('div');
nickjillings@1572: this.holder.className = 'comment-div';
nickjillings@1572: // Create a string next to each comment asking for a comment
nickjillings@1572: this.string = document.createElement('span');
nickjillings@1572: this.string.innerHTML = commentQuestion.statement;
nickjillings@1572: var br = document.createElement('br');
nickjillings@1572: // Add to the holder.
nickjillings@1572: this.holder.appendChild(this.string);
nickjillings@1572: this.holder.appendChild(br);
nickjillings@1572: this.options = [];
nickjillings@1572: this.inputs = document.createElement('div');
nickjillings@1572: this.span = document.createElement('div');
nickjillings@1572: this.inputs.align = 'center';
nickjillings@1572: this.inputs.style.marginLeft = '12px';
nickjillings@1572: this.span.style.marginLeft = '12px';
nickjillings@1572: this.span.align = 'center';
nickjillings@1572: this.span.style.marginTop = '15px';
nickjillings@1572:
nickjillings@1572: var optCount = commentQuestion.options.length;
nickjillings@1572: for (var i=0; i= 600)
nickjillings@1525: {
nickjillings@1525: boxwidth = 600;
nickjillings@1525: }
nickjillings@1525: else if (boxwidth < 400)
nickjillings@1525: {
nickjillings@1525: boxwidth = 400;
nickjillings@1525: }
nickjillings@1525: this.holder.style.width = boxwidth+"px";
nickjillings@1525: var text = this.holder.children[2];
nickjillings@1525: var options = this.holder.children[3];
nickjillings@1525: var optCount = options.children.length;
nickjillings@1525: var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
nickjillings@1525: var options = options.firstChild;
nickjillings@1525: var text = text.firstChild;
nickjillings@1525: options.style.marginRight = spanMargin;
nickjillings@1525: options.style.marginLeft = spanMargin;
nickjillings@1525: text.style.marginRight = spanMargin;
nickjillings@1525: text.style.marginLeft = spanMargin;
nickjillings@1525: while(options.nextSibling != undefined)
nickjillings@1525: {
nickjillings@1525: options = options.nextSibling;
nickjillings@1525: text = text.nextSibling;
nickjillings@1525: options.style.marginRight = spanMargin;
nickjillings@1525: options.style.marginLeft = spanMargin;
nickjillings@1525: text.style.marginRight = spanMargin;
nickjillings@1525: text.style.marginLeft = spanMargin;
nickjillings@1525: }
nickjillings@1525: };
nickjillings@1525: this.resize();
nickjillings@1572: };
nickjillings@1570:
nickjillings@2032: this.createCommentQuestion = function(element) {
nickjillings@2032: var node;
nickjillings@1324: if (element.type == 'question') {
nickjillings@2032: node = new this.commentBox(element);
nickjillings@2032: } else if (element.type == 'radio') {
nickjillings@2032: node = new this.radioBox(element);
nickjillings@1572: } else if (element.type == 'checkbox') {
nickjillings@1572: node = new this.checkboxBox(element);
nickjillings@2032: }
nickjillings@2032: this.commentQuestions.push(node);
nickjillings@2032: return node;
nickjillings@2032: };
nickjillings@1564:
nickjillings@2051: this.deleteCommentQuestions = function()
nickjillings@2051: {
nickjillings@2051: this.commentQuestions = [];
nickjillings@2051: };
nickjillings@2176:
nickjillings@2176: this.outsideReferenceDOM = function(audioObject,index,inject)
nickjillings@2176: {
nickjillings@2176: this.parent = audioObject;
nickjillings@2176: this.outsideReferenceHolder = document.createElement('button');
nickjillings@2176: this.outsideReferenceHolder.id = 'outside-reference';
nickjillings@2176: this.outsideReferenceHolder.className = 'outside-reference';
nickjillings@2176: this.outsideReferenceHolder.setAttribute('track-id',index);
nickjillings@2176: this.outsideReferenceHolder.textContent = "Play Reference";
nickjillings@2176: this.outsideReferenceHolder.disabled = true;
nickjillings@2176:
nickjillings@2176: this.outsideReferenceHolder.onclick = function(event)
nickjillings@2176: {
nickjillings@2176: audioEngineContext.play(event.currentTarget.getAttribute('track-id'));
nickjillings@2176: };
nickjillings@2176: inject.appendChild(this.outsideReferenceHolder);
nickjillings@2176: this.enable = function()
nickjillings@2176: {
nickjillings@2176: if (this.parent.state == 1)
nickjillings@2176: {
nickjillings@2176: this.outsideReferenceHolder.disabled = false;
nickjillings@2176: }
nickjillings@2176: };
nickjillings@2176: this.updateLoading = function(progress)
nickjillings@2176: {
nickjillings@2176: if (progress != 100)
nickjillings@2176: {
nickjillings@2176: progress = String(progress);
nickjillings@2176: progress = progress.split('.')[0];
nickjillings@2176: this.outsideReferenceHolder.textContent = progress+'%';
nickjillings@2176: } else {
nickjillings@2176: this.outsideReferenceHolder.textContent = "Play Reference";
nickjillings@2176: }
nickjillings@2176: };
nickjillings@2176: this.startPlayback = function()
nickjillings@2176: {
nickjillings@2176: // Called when playback has begun
nickjillings@2176: $('.track-slider').removeClass('track-slider-playing');
nickjillings@2176: $('.comment-div').removeClass('comment-box-playing');
nickjillings@2176: this.outsideReferenceHolder.style.backgroundColor = "#FDD";
nickjillings@2176: };
nickjillings@2176: this.stopPlayback = function()
nickjillings@2176: {
nickjillings@2176: // Called when playback has stopped. This gets called even if playback never started!
nickjillings@2176: this.outsideReferenceHolder.style.backgroundColor = "";
nickjillings@2176: };
nickjillings@2176: this.exportXMLDOM = function(audioObject)
nickjillings@2176: {
nickjillings@2176: return null;
nickjillings@2176: };
nickjillings@2176: this.getValue = function()
nickjillings@2176: {
nickjillings@2176: return 0;
nickjillings@2176: };
nickjillings@2176: this.getPresentedId = function()
nickjillings@2176: {
nickjillings@2176: return 'Reference';
nickjillings@2176: };
nickjillings@2176: this.canMove = function()
nickjillings@2176: {
nickjillings@2176: return false;
nickjillings@2176: };
nickjillings@2176: this.error = function() {
nickjillings@2176: // audioObject has an error!!
nickjillings@2176: this.outsideReferenceHolder.textContent = "Error";
nickjillings@2176: this.outsideReferenceHolder.style.backgroundColor = "#F00";
nickjillings@2176: }
nickjillings@2176: }
nickjillings@2051:
nickjillings@1564: this.playhead = new function()
nickjillings@1564: {
nickjillings@1564: this.object = document.createElement('div');
nickjillings@1564: this.object.className = 'playhead';
nickjillings@1564: this.object.align = 'left';
nickjillings@1564: var curTime = document.createElement('div');
nickjillings@1564: curTime.style.width = '50px';
nickjillings@1564: this.curTimeSpan = document.createElement('span');
nickjillings@1564: this.curTimeSpan.textContent = '00:00';
nickjillings@1564: curTime.appendChild(this.curTimeSpan);
nickjillings@1564: this.object.appendChild(curTime);
nickjillings@1564: this.scrubberTrack = document.createElement('div');
nickjillings@1564: this.scrubberTrack.className = 'playhead-scrub-track';
nickjillings@1564:
nickjillings@1564: this.scrubberHead = document.createElement('div');
nickjillings@1564: this.scrubberHead.id = 'playhead-scrubber';
nickjillings@1564: this.scrubberTrack.appendChild(this.scrubberHead);
nickjillings@1564: this.object.appendChild(this.scrubberTrack);
nickjillings@1564:
nickjillings@1564: this.timePerPixel = 0;
nickjillings@1564: this.maxTime = 0;
nickjillings@1564:
nickjillings@1567: this.playbackObject;
nickjillings@1567:
nickjillings@1567: this.setTimePerPixel = function(audioObject) {
nickjillings@1564: //maxTime must be in seconds
nickjillings@1567: this.playbackObject = audioObject;
nickjillings@1410: this.maxTime = audioObject.buffer.buffer.duration;
nickjillings@1564: var width = 490; //500 - 10, 5 each side of the tracker head
nickjillings@1567: this.timePerPixel = this.maxTime/490;
nickjillings@1567: if (this.maxTime < 60) {
nickjillings@1564: this.curTimeSpan.textContent = '0.00';
nickjillings@1564: } else {
nickjillings@1564: this.curTimeSpan.textContent = '00:00';
nickjillings@1564: }
nickjillings@1564: };
nickjillings@1564:
nickjillings@1567: this.update = function() {
nickjillings@1564: // Update the playhead position, startPlay must be called
nickjillings@1564: if (this.timePerPixel > 0) {
nickjillings@1567: var time = this.playbackObject.getCurrentPosition();
nickjillings@1367: if (time > 0 && time < this.maxTime) {
nickjillings@1530: var width = 490;
nickjillings@1530: var pix = Math.floor(time/this.timePerPixel);
nickjillings@1530: this.scrubberHead.style.left = pix+'px';
nickjillings@1530: if (this.maxTime > 60.0) {
nickjillings@1530: var secs = time%60;
nickjillings@1530: var mins = Math.floor((time-secs)/60);
nickjillings@1530: secs = secs.toString();
nickjillings@1530: secs = secs.substr(0,2);
nickjillings@1530: mins = mins.toString();
nickjillings@1530: this.curTimeSpan.textContent = mins+':'+secs;
nickjillings@1530: } else {
nickjillings@1530: time = time.toString();
nickjillings@1530: this.curTimeSpan.textContent = time.substr(0,4);
nickjillings@1530: }
nickjillings@1564: } else {
nickjillings@1530: this.scrubberHead.style.left = '0px';
nickjillings@1530: if (this.maxTime < 60) {
nickjillings@1530: this.curTimeSpan.textContent = '0.00';
nickjillings@1530: } else {
nickjillings@1530: this.curTimeSpan.textContent = '00:00';
nickjillings@1530: }
nickjillings@1564: }
nickjillings@1564: }
nickjillings@1564: };
nickjillings@1567:
nickjillings@1567: this.interval = undefined;
nickjillings@1567:
nickjillings@1567: this.start = function() {
nickjillings@1567: if (this.playbackObject != undefined && this.interval == undefined) {
nickjillings@1530: if (this.maxTime < 60) {
nickjillings@1530: this.interval = setInterval(function(){interfaceContext.playhead.update();},10);
nickjillings@1530: } else {
nickjillings@1530: this.interval = setInterval(function(){interfaceContext.playhead.update();},100);
nickjillings@1530: }
nickjillings@1567: }
nickjillings@1567: };
nickjillings@1567: this.stop = function() {
nickjillings@1567: clearInterval(this.interval);
nickjillings@1567: this.interval = undefined;
nickjillings@2101: this.scrubberHead.style.left = '0px';
nickjillings@1530: if (this.maxTime < 60) {
nickjillings@1530: this.curTimeSpan.textContent = '0.00';
nickjillings@1530: } else {
nickjillings@1530: this.curTimeSpan.textContent = '00:00';
nickjillings@1530: }
nickjillings@1567: };
nickjillings@1564: };
nickjillings@1354:
nickjillings@1354: this.volume = new function()
nickjillings@1354: {
nickjillings@1354: // An in-built volume module which can be viewed on page
nickjillings@1354: // Includes trackers on page-by-page data
nickjillings@1354: // Volume does NOT reset to 0dB on each page load
nickjillings@1354: this.valueLin = 1.0;
nickjillings@1354: this.valueDB = 0.0;
nickjillings@1354: this.object = document.createElement('div');
nickjillings@1354: this.object.id = 'master-volume-holder';
nickjillings@1354: this.slider = document.createElement('input');
nickjillings@1354: this.slider.id = 'master-volume-control';
nickjillings@1354: this.slider.type = 'range';
nickjillings@1354: this.valueText = document.createElement('span');
nickjillings@1354: this.valueText.id = 'master-volume-feedback';
nickjillings@1354: this.valueText.textContent = '0dB';
nickjillings@1354:
nickjillings@1354: this.slider.min = -60;
nickjillings@1354: this.slider.max = 12;
nickjillings@1354: this.slider.value = 0;
nickjillings@1354: this.slider.step = 1;
nickjillings@1354: this.slider.onmousemove = function(event)
nickjillings@1354: {
nickjillings@1354: interfaceContext.volume.valueDB = event.currentTarget.value;
nickjillings@1354: interfaceContext.volume.valueLin = decibelToLinear(interfaceContext.volume.valueDB);
nickjillings@1354: interfaceContext.volume.valueText.textContent = interfaceContext.volume.valueDB+'dB';
nickjillings@1354: audioEngineContext.outputGain.gain.value = interfaceContext.volume.valueLin;
nickjillings@1354: }
nickjillings@1354: this.slider.onmouseup = function(event)
nickjillings@1354: {
nickjillings@2100: var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
nickjillings@1354: if (storePoint.length == 0)
nickjillings@1354: {
nickjillings@1354: storePoint = storage.document.createElement('metricresult');
nickjillings@1354: storePoint.setAttribute('name','volumeTracker');
nickjillings@2100: testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
nickjillings@1354: }
nickjillings@1354: else {
nickjillings@1354: storePoint = storePoint[0];
nickjillings@1354: }
nickjillings@1354: var node = storage.document.createElement('movement');
nickjillings@1354: node.setAttribute('test-time',audioEngineContext.timer.getTestTime());
nickjillings@1354: node.setAttribute('volume',interfaceContext.volume.valueDB);
nickjillings@1354: node.setAttribute('format','dBFS');
nickjillings@1354: storePoint.appendChild(node);
nickjillings@1354: }
nickjillings@1354:
nickjillings@1355: var title = document.createElement('div');
nickjillings@1355: title.innerHTML = 'Master Volume Control';
nickjillings@1355: title.style.fontSize = '0.75em';
nickjillings@1355: title.style.width = "100%";
nickjillings@1355: title.align = 'center';
nickjillings@1355: this.object.appendChild(title);
nickjillings@1355:
nickjillings@1354: this.object.appendChild(this.slider);
nickjillings@1354: this.object.appendChild(this.valueText);
nickjillings@1354: }
nickjillings@2185:
nickjillings@2186: this.calibrationModuleObject = null;
nickjillings@2185: this.calibrationModule = function() {
nickjillings@2185: // This creates an on-page calibration module
nickjillings@2185: this.storeDOM = storage.document.createElement("calibration");
nickjillings@2186: storage.root.appendChild(this.storeDOM);
nickjillings@2185: // The calibration is a fixed state module
nickjillings@2185: this.calibrationNodes = [];
nickjillings@2186: this.holder = null;
nickjillings@2186: this.build = function(inject) {
nickjillings@2186: var f0 = 62.5;
nickjillings@2186: this.holder = document.createElement("div");
nickjillings@2186: this.holder.className = "calibration-holder";
nickjillings@2186: this.calibrationNodes = [];
nickjillings@2186: while(f0 < 20000) {
nickjillings@2186: var obj = {
nickjillings@2186: root: document.createElement("div"),
nickjillings@2186: input: document.createElement("input"),
nickjillings@2186: oscillator: audioContext.createOscillator(),
nickjillings@2186: gain: audioContext.createGain(),
nickjillings@2186: f: f0,
nickjillings@2186: parent: this,
nickjillings@2186: handleEvent: function(event) {
nickjillings@2186: switch(event.type) {
nickjillings@2186: case "mouseenter":
nickjillings@2186: this.oscillator.start(0);
nickjillings@2186: break;
nickjillings@2186: case "mouseleave":
nickjillings@2186: this.oscillator.stop(0);
nickjillings@2186: this.oscillator = audioContext.createOscillator();
nickjillings@2186: this.oscillator.connect(this.gain);
nickjillings@2186: this.oscillator.frequency.value = this.f;
nickjillings@2186: break;
nickjillings@2191: case "mousemove":
nickjillings@2191: var value = Math.pow(10,this.input.value/20);
nickjillings@2191: if (this.f == 1000) {
nickjillings@2191: audioEngineContext.outputGain.gain.value = value;
nickjillings@2191: interfaceContext.volume.slider.value = this.input.value;
nickjillings@2191: } else {
nickjillings@2191: this.gain.gain.value = value
nickjillings@2191: }
nickjillings@2191: break;
nickjillings@2186: }
nickjillings@2186: },
nickjillings@2186: disconnect: function() {
nickjillings@2186: this.gain.disconnect();
nickjillings@2186: }
nickjillings@2185: }
nickjillings@2186: obj.root.className = "calibration-slider";
nickjillings@2186: obj.root.appendChild(obj.input);
nickjillings@2186: obj.oscillator.connect(obj.gain);
nickjillings@2190: obj.gain.connect(audioEngineContext.outputGain);
nickjillings@2186: obj.gain.gain.value = Math.random()*2;
nickjillings@2186: obj.input.value = obj.gain.gain.value;
nickjillings@2186: obj.input.setAttribute('orient','vertical');
nickjillings@2186: obj.input.type = "range";
nickjillings@2186: obj.input.min = -6;
nickjillings@2186: obj.input.max = 6;
nickjillings@2186: obj.input.step = 0.25;
nickjillings@2186: if (f0 != 1000) {
nickjillings@2186: obj.input.value = (Math.random()*12)-6;
nickjillings@2186: } else {
nickjillings@2186: obj.input.value = 0;
nickjillings@2186: obj.root.style.backgroundColor="rgb(255,125,125)";
nickjillings@2186: }
nickjillings@2191: obj.input.addEventListener("mousemove",obj);
nickjillings@2186: obj.input.addEventListener("mouseenter",obj);
nickjillings@2186: obj.input.addEventListener("mouseleave",obj);
nickjillings@2186: obj.gain.gain.value = Math.pow(10,obj.input.value/20);
nickjillings@2186: obj.oscillator.frequency.value = f0;
nickjillings@2186: this.calibrationNodes.push(obj);
nickjillings@2186: this.holder.appendChild(obj.root);
nickjillings@2186: f0 *= 2;
nickjillings@2185: }
nickjillings@2186: inject.appendChild(this.holder);
nickjillings@2186: }
nickjillings@2186: this.collect = function() {
nickjillings@2186: for (var obj of this.calibrationNodes) {
nickjillings@2186: var node = storage.document.createElement("calibrationresult");
nickjillings@2186: node.setAttribute("frequency",obj.f);
nickjillings@2186: node.setAttribute("range-min",obj.input.min);
nickjillings@2186: node.setAttribute("range-max",obj.input.max);
nickjillings@2186: node.setAttribute("gain-lin",obj.gain.gain.value);
nickjillings@2186: this.storeDOM.appendChild(node);
nickjillings@2186: }
nickjillings@2185: }
nickjillings@2185: }
nickjillings@2185:
nickjillings@2185:
nickjillings@2049: // Global Checkers
nickjillings@2049: // These functions will help enforce the checkers
nickjillings@2049: this.checkHiddenAnchor = function()
nickjillings@2049: {
nickjillings@1324: for (var ao of audioEngineContext.audioObjects)
nickjillings@2049: {
nickjillings@1324: if (ao.specification.type == "anchor")
nickjillings@2049: {
nickjillings@1325: if (ao.interfaceDOM.getValue() > (ao.specification.marker/100) && ao.specification.marker > 0) {
nickjillings@1324: // Anchor is not set below
nickjillings@1324: console.log('Anchor node not below marker value');
nickjillings@1324: alert('Please keep listening');
nickjillings@1367: this.storeErrorNode('Anchor node not below marker value');
nickjillings@1324: return false;
nickjillings@1324: }
nickjillings@2049: }
nickjillings@2049: }
nickjillings@2049: return true;
nickjillings@2049: };
nickjillings@2049:
nickjillings@2049: this.checkHiddenReference = function()
nickjillings@2049: {
nickjillings@1324: for (var ao of audioEngineContext.audioObjects)
nickjillings@2049: {
nickjillings@1324: if (ao.specification.type == "reference")
nickjillings@2049: {
nickjillings@1325: if (ao.interfaceDOM.getValue() < (ao.specification.marker/100) && ao.specification.marker > 0) {
nickjillings@1324: // Anchor is not set below
nickjillings@1367: console.log('Reference node not above marker value');
nickjillings@1367: this.storeErrorNode('Reference node not above marker value');
nickjillings@1324: alert('Please keep listening');
nickjillings@1324: return false;
nickjillings@1324: }
nickjillings@2049: }
nickjillings@2049: }
nickjillings@2049: return true;
nickjillings@2049: };
nickjillings@1474:
nickjillings@1474: this.checkFragmentsFullyPlayed = function ()
nickjillings@1474: {
nickjillings@1474: // Checks the entire file has been played back
nickjillings@1474: // NOTE ! This will return true IF playback is Looped!!!
nickjillings@1474: if (audioEngineContext.loopPlayback)
nickjillings@1474: {
nickjillings@1474: console.log("WARNING - Looped source: Cannot check fragments are fully played");
nickjillings@1474: return true;
nickjillings@1474: }
nickjillings@1474: var check_pass = true;
nickjillings@1474: var error_obj = [];
nickjillings@1474: for (var i = 0; i= time)
nickjillings@1474: {
nickjillings@1474: passed = true;
nickjillings@1474: break;
nickjillings@1474: }
nickjillings@1474: }
nickjillings@1474: if (passed == false)
nickjillings@1474: {
nickjillings@1474: check_pass = false;
nickjillings@1290: console.log("Continue listening to track-"+object.interfaceDOM.getPresentedId());
nickjillings@1290: error_obj.push(object.interfaceDOM.getPresentedId());
nickjillings@1474: }
nickjillings@1474: }
nickjillings@1474: if (check_pass == false)
nickjillings@1474: {
nickjillings@1393: var str_start = "You have not completely listened to fragments ";
nickjillings@1474: for (var i=0; i= 300) {
nickjillings@2150: console.log("WARNING - Could not update at this time");
nickjillings@2150: } else {
nickjillings@2150: var parser = new DOMParser();
nickjillings@2150: var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
nickjillings@2150: var response = xmlDoc.getElementsByTagName('response')[0];
nickjillings@2150: if (response.getAttribute("state") == "OK") {
nickjillings@2150: var file = response.getElementsByTagName("file")[0];
nickjillings@2150: console.log("Intermediate save: OK, written "+file.getAttribute("bytes")+"B");
nickjillings@2150: } else {
nickjillings@2150: var message = response.getElementsByTagName("message");
nickjillings@2150: console.log("Intermediate save: Error! "+message.textContent);
nickjillings@2150: }
nickjillings@2149: }
nickjillings@2149: }
nickjillings@2150: xmlhttp.send([hold.innerHTML]);
nickjillings@2147: }
nickjillings@2147: }
nickjillings@1324:
nickjillings@1324: this.createTestPageStore = function(specification)
nickjillings@1324: {
nickjillings@1324: var store = new this.pageNode(this,specification);
nickjillings@1324: this.testPages.push(store);
nickjillings@1324: return this.testPages[this.testPages.length-1];
nickjillings@1324: };
nickjillings@1324:
nickjillings@1324: this.surveyNode = function(parent,root,specification)
nickjillings@1324: {
nickjillings@1324: this.specification = specification;
nickjillings@1324: this.parent = parent;
nickjillings@1294: this.state = "empty";
nickjillings@1324: this.XMLDOM = this.parent.document.createElement('survey');
nickjillings@1324: this.XMLDOM.setAttribute('location',this.specification.location);
nickjillings@1294: this.XMLDOM.setAttribute("state",this.state);
nickjillings@1324: for (var optNode of this.specification.options)
nickjillings@1324: {
nickjillings@1324: if (optNode.type != 'statement')
nickjillings@1324: {
nickjillings@1324: var node = this.parent.document.createElement('surveyresult');
nickjillings@1294: node.setAttribute("ref",optNode.id);
nickjillings@1324: node.setAttribute('type',optNode.type);
nickjillings@1324: this.XMLDOM.appendChild(node);
nickjillings@1324: }
nickjillings@1324: }
nickjillings@1324: root.appendChild(this.XMLDOM);
nickjillings@1324:
nickjillings@1324: this.postResult = function(node)
nickjillings@1324: {
nickjillings@1324: // From popup: node is the popupOption node containing both spec. and results
nickjillings@1324: // ID is the position
nickjillings@1324: if (node.specification.type == 'statement'){return;}
nickjillings@1294: var surveyresult = this.XMLDOM.children[0];
nickjillings@1294: while(surveyresult != null) {
nickjillings@1294: if (surveyresult.getAttribute("ref") == node.specification.id)
nickjillings@1294: {
nickjillings@1294: break;
nickjillings@1294: }
nickjillings@1294: surveyresult = surveyresult.nextElementSibling;
nickjillings@1294: }
nickjillings@1324: switch(node.specification.type)
nickjillings@1324: {
nickjillings@1324: case "number":
nickjillings@1324: case "question":
nickjillings@1324: var child = this.parent.document.createElement('response');
nickjillings@1324: child.textContent = node.response;
nickjillings@1324: surveyresult.appendChild(child);
nickjillings@1324: break;
nickjillings@1324: case "radio":
nickjillings@1324: var child = this.parent.document.createElement('response');
nickjillings@1324: child.setAttribute('name',node.response.name);
nickjillings@1324: child.textContent = node.response.text;
nickjillings@1324: surveyresult.appendChild(child);
nickjillings@1324: break;
nickjillings@1324: case "checkbox":
nickjillings@1324: for (var i=0; i 0)
nickjillings@1324: {
nickjillings@1324: aeNode.setAttribute('marker',element.marker);
nickjillings@1324: }
nickjillings@1324: }
nickjillings@1324: var ae_metric = this.parent.document.createElement('metric');
nickjillings@1324: aeNode.appendChild(ae_metric);
nickjillings@1324: this.XMLDOM.appendChild(aeNode);
nickjillings@1324: }
nickjillings@1324:
nickjillings@1324: this.parent.root.appendChild(this.XMLDOM);
nickjillings@1294:
nickjillings@1294: this.complete = function() {
nickjillings@1294: this.state = "complete";
nickjillings@1294: this.XMLDOM.setAttribute("state","complete");
nickjillings@1294: }
nickjillings@1324: };
nickjillings@2150: this.update = function() {
nickjillings@2150: this.SessionKey.update();
nickjillings@2150: }
nickjillings@1324: this.finish = function()
nickjillings@1324: {
nickjillings@1324: if (this.state == 0)
nickjillings@1324: {
nickjillings@2150: this.update();
nickjillings@1324: }
nickjillings@1324: this.state = 1;
nickjillings@1324: return this.root;
nickjillings@1324: };
nickjillings@1324: }