djmoffat@1099: /** djmoffat@1099: * core.js djmoffat@1099: * djmoffat@1099: * Main script to run, calls all other core functions and manages loading/store to backend. djmoffat@1099: * Also contains all global variables. djmoffat@1099: */ djmoffat@1099: djmoffat@1099: /* create the web audio API context and store in audioContext*/ djmoffat@1099: var audioContext; // Hold the browser web audio API djmoffat@1099: var projectXML; // Hold the parsed setup XML djmoffat@1099: var schemaXSD; // Hold the parsed schema XSD djmoffat@1099: var specification; djmoffat@1099: var interfaceContext; djmoffat@1099: var storage; djmoffat@1099: var popup; // Hold the interfacePopup object djmoffat@1099: var testState; djmoffat@1099: var currentTrackOrder = []; // Hold the current XML tracks in their (randomised) order djmoffat@1099: var audioEngineContext; // The custome AudioEngine object djmoffat@1099: var projectReturn; // Hold the URL for the return djmoffat@1099: djmoffat@1099: djmoffat@1099: // Add a prototype to the bufferSourceNode to reference to the audioObject holding it djmoffat@1099: AudioBufferSourceNode.prototype.owner = undefined; djmoffat@1099: // Add a prototype to the bufferSourceNode to hold when the object was given a play command djmoffat@1099: AudioBufferSourceNode.prototype.playbackStartTime = undefined; djmoffat@1099: // Add a prototype to the bufferNode to hold the desired LINEAR gain djmoffat@1099: AudioBuffer.prototype.playbackGain = undefined; djmoffat@1099: // Add a prototype to the bufferNode to hold the computed LUFS loudness djmoffat@1099: AudioBuffer.prototype.lufs = undefined; djmoffat@1099: djmoffat@1099: // Firefox does not have an XMLDocument.prototype.getElementsByName djmoffat@1099: // and there is no searchAll style command, this custom function will djmoffat@1099: // search all children recusrively for the name. Used for XSD where all djmoffat@1099: // element nodes must have a name and therefore can pull the schema node djmoffat@1099: XMLDocument.prototype.getAllElementsByName = function(name) djmoffat@1099: { djmoffat@1099: name = String(name); djmoffat@1099: var selected = this.documentElement.getAllElementsByName(name); djmoffat@1099: return selected; djmoffat@1099: } djmoffat@1099: djmoffat@1099: Element.prototype.getAllElementsByName = function(name) djmoffat@1099: { djmoffat@1099: name = String(name); djmoffat@1099: var selected = []; djmoffat@1099: var node = this.firstElementChild; djmoffat@1099: while(node != null) djmoffat@1099: { djmoffat@1099: if (node.getAttribute('name') == name) djmoffat@1099: { djmoffat@1099: selected.push(node); djmoffat@1099: } djmoffat@1099: if (node.childElementCount > 0) djmoffat@1099: { djmoffat@1099: selected = selected.concat(node.getAllElementsByName(name)); djmoffat@1099: } djmoffat@1099: node = node.nextElementSibling; djmoffat@1099: } djmoffat@1099: return selected; djmoffat@1099: } djmoffat@1099: djmoffat@1099: XMLDocument.prototype.getAllElementsByTagName = function(name) djmoffat@1099: { djmoffat@1099: name = String(name); djmoffat@1099: var selected = this.documentElement.getAllElementsByTagName(name); djmoffat@1099: return selected; djmoffat@1099: } djmoffat@1099: djmoffat@1099: Element.prototype.getAllElementsByTagName = function(name) djmoffat@1099: { djmoffat@1099: name = String(name); djmoffat@1099: var selected = []; djmoffat@1099: var node = this.firstElementChild; djmoffat@1099: while(node != null) djmoffat@1099: { djmoffat@1099: if (node.nodeName == name) djmoffat@1099: { djmoffat@1099: selected.push(node); djmoffat@1099: } djmoffat@1099: if (node.childElementCount > 0) djmoffat@1099: { djmoffat@1099: selected = selected.concat(node.getAllElementsByTagName(name)); djmoffat@1099: } djmoffat@1099: node = node.nextElementSibling; djmoffat@1099: } djmoffat@1099: return selected; djmoffat@1099: } djmoffat@1099: djmoffat@1099: // Firefox does not have an XMLDocument.prototype.getElementsByName djmoffat@1099: if (typeof XMLDocument.prototype.getElementsByName != "function") { djmoffat@1099: XMLDocument.prototype.getElementsByName = function(name) djmoffat@1099: { djmoffat@1099: name = String(name); djmoffat@1099: var node = this.documentElement.firstElementChild; djmoffat@1099: var selected = []; djmoffat@1099: while(node != null) djmoffat@1099: { djmoffat@1099: if (node.getAttribute('name') == name) djmoffat@1099: { djmoffat@1099: selected.push(node); djmoffat@1099: } djmoffat@1099: node = node.nextElementSibling; djmoffat@1099: } djmoffat@1099: return selected; djmoffat@1099: } djmoffat@1099: } djmoffat@1099: djmoffat@1099: window.onload = function() { djmoffat@1099: // Function called once the browser has loaded all files. djmoffat@1099: // This should perform any initial commands such as structure / loading documents djmoffat@1099: djmoffat@1099: // Create a web audio API context djmoffat@1099: // Fixed for cross-browser support djmoffat@1099: var AudioContext = window.AudioContext || window.webkitAudioContext; djmoffat@1099: audioContext = new AudioContext; djmoffat@1099: djmoffat@1099: // Create test state djmoffat@1099: testState = new stateMachine(); djmoffat@1099: djmoffat@1099: // Create the popup interface object djmoffat@1099: popup = new interfacePopup(); djmoffat@1099: djmoffat@1099: // Create the specification object djmoffat@1099: specification = new Specification(); djmoffat@1099: djmoffat@1099: // Create the interface object djmoffat@1099: interfaceContext = new Interface(specification); djmoffat@1099: djmoffat@1099: // Create the storage object djmoffat@1099: storage = new Storage(); djmoffat@1099: // Define window callbacks for interface djmoffat@1099: window.onresize = function(event){interfaceContext.resizeWindow(event);}; djmoffat@1099: }; djmoffat@1099: djmoffat@1099: function loadProjectSpec(url) { djmoffat@1099: // Load the project document from the given URL, decode the XML and instruct audioEngine to get audio data djmoffat@1099: // If url is null, request client to upload project XML document djmoffat@1099: var xmlhttp = new XMLHttpRequest(); djmoffat@1099: xmlhttp.open("GET",'test-schema.xsd',true); djmoffat@1099: xmlhttp.onload = function() djmoffat@1099: { djmoffat@1099: schemaXSD = xmlhttp.response; djmoffat@1099: var parse = new DOMParser(); djmoffat@1099: specification.schema = parse.parseFromString(xmlhttp.response,'text/xml'); djmoffat@1099: var r = new XMLHttpRequest(); djmoffat@1099: r.open('GET',url,true); djmoffat@1099: r.onload = function() { djmoffat@1099: loadProjectSpecCallback(r.response); djmoffat@1099: }; n@1102: r.onerror = function() { n@1102: document.getElementsByTagName('body')[0].innerHTML = null; n@1102: var msg = document.createElement("h3"); n@1102: msg.textContent = "FATAL ERROR"; n@1102: var span = document.createElement("p"); n@1102: 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."; n@1102: document.getElementsByTagName('body')[0].appendChild(msg); n@1102: document.getElementsByTagName('body')[0].appendChild(span); n@1102: } djmoffat@1099: r.send(); djmoffat@1099: }; djmoffat@1099: xmlhttp.send(); djmoffat@1099: }; djmoffat@1099: djmoffat@1099: function loadProjectSpecCallback(response) { djmoffat@1099: // Function called after asynchronous download of XML project specification djmoffat@1099: //var decode = $.parseXML(response); djmoffat@1099: //projectXML = $(decode); djmoffat@1099: djmoffat@1099: // First perform XML schema validation djmoffat@1099: var Module = { djmoffat@1099: xml: response, djmoffat@1099: schema: schemaXSD, djmoffat@1099: arguments:["--noout", "--schema", 'test-schema.xsd','document.xml'] djmoffat@1099: }; djmoffat@1099: djmoffat@1099: var xmllint = validateXML(Module); djmoffat@1099: console.log(xmllint); djmoffat@1099: if(xmllint != 'document.xml validates\n') djmoffat@1099: { djmoffat@1099: document.getElementsByTagName('body')[0].innerHTML = null; djmoffat@1099: var msg = document.createElement("h3"); djmoffat@1099: msg.textContent = "FATAL ERROR"; djmoffat@1099: var span = document.createElement("h4"); djmoffat@1099: span.textContent = "The XML validator returned the following errors when decoding your XML file"; djmoffat@1099: document.getElementsByTagName('body')[0].appendChild(msg); djmoffat@1099: document.getElementsByTagName('body')[0].appendChild(span); djmoffat@1099: xmllint = xmllint.split('\n'); djmoffat@1099: for (var i in xmllint) djmoffat@1099: { djmoffat@1099: document.getElementsByTagName('body')[0].appendChild(document.createElement('br')); djmoffat@1099: var span = document.createElement("span"); djmoffat@1099: span.textContent = xmllint[i]; djmoffat@1099: document.getElementsByTagName('body')[0].appendChild(span); djmoffat@1099: } djmoffat@1099: return; djmoffat@1099: } djmoffat@1099: djmoffat@1099: var parse = new DOMParser(); djmoffat@1099: projectXML = parse.parseFromString(response,'text/xml'); djmoffat@1099: var errorNode = projectXML.getElementsByTagName('parsererror'); djmoffat@1099: if (errorNode.length >= 1) djmoffat@1099: { djmoffat@1099: var msg = document.createElement("h3"); djmoffat@1099: msg.textContent = "FATAL ERROR"; djmoffat@1099: var span = document.createElement("span"); djmoffat@1099: span.textContent = "The XML parser returned the following errors when decoding your XML file"; djmoffat@1099: document.getElementsByTagName('body')[0].innerHTML = null; djmoffat@1099: document.getElementsByTagName('body')[0].appendChild(msg); djmoffat@1099: document.getElementsByTagName('body')[0].appendChild(span); djmoffat@1099: document.getElementsByTagName('body')[0].appendChild(errorNode[0]); djmoffat@1099: return; djmoffat@1099: } djmoffat@1099: djmoffat@1099: // Build the specification djmoffat@1099: specification.decode(projectXML); djmoffat@1099: storage.initialise(); djmoffat@1099: /// CHECK FOR SAMPLE RATE COMPATIBILITY djmoffat@1099: if (specification.sampleRate != undefined) { djmoffat@1099: if (Number(specification.sampleRate) != audioContext.sampleRate) { djmoffat@1099: 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.'; djmoffat@1099: alert(errStr); djmoffat@1099: return; djmoffat@1099: } djmoffat@1099: } djmoffat@1099: djmoffat@1099: // Detect the interface to use and load the relevant javascripts. djmoffat@1099: var interfaceJS = document.createElement('script'); djmoffat@1099: interfaceJS.setAttribute("type","text/javascript"); djmoffat@1099: switch(specification.interface) djmoffat@1099: { djmoffat@1099: case "APE": djmoffat@1099: interfaceJS.setAttribute("src","interfaces/ape.js"); djmoffat@1099: djmoffat@1099: // APE comes with a css file djmoffat@1099: var css = document.createElement('link'); djmoffat@1099: css.rel = 'stylesheet'; djmoffat@1099: css.type = 'text/css'; djmoffat@1099: css.href = 'interfaces/ape.css'; djmoffat@1099: djmoffat@1099: document.getElementsByTagName("head")[0].appendChild(css); djmoffat@1099: break; djmoffat@1099: djmoffat@1099: case "MUSHRA": djmoffat@1099: interfaceJS.setAttribute("src","interfaces/mushra.js"); djmoffat@1099: djmoffat@1099: // MUSHRA comes with a css file djmoffat@1099: var css = document.createElement('link'); djmoffat@1099: css.rel = 'stylesheet'; djmoffat@1099: css.type = 'text/css'; djmoffat@1099: css.href = 'interfaces/mushra.css'; djmoffat@1099: djmoffat@1099: document.getElementsByTagName("head")[0].appendChild(css); djmoffat@1099: break; djmoffat@1099: djmoffat@1099: case "AB": djmoffat@1099: interfaceJS.setAttribute("src","interfaces/AB.js"); djmoffat@1099: djmoffat@1099: // AB comes with a css file djmoffat@1099: var css = document.createElement('link'); djmoffat@1099: css.rel = 'stylesheet'; djmoffat@1099: css.type = 'text/css'; djmoffat@1099: css.href = 'interfaces/AB.css'; djmoffat@1099: djmoffat@1099: document.getElementsByTagName("head")[0].appendChild(css); djmoffat@1099: break; djmoffat@1099: case "Bipolar": djmoffat@1099: case "ACR": djmoffat@1099: case "DCR": djmoffat@1099: case "CCR": djmoffat@1099: case "ABC": djmoffat@1099: // Above enumerate to horizontal sliders djmoffat@1099: interfaceJS.setAttribute("src","interfaces/horizontal-sliders.js"); djmoffat@1099: djmoffat@1099: // horizontal-sliders comes with a css file djmoffat@1099: var css = document.createElement('link'); djmoffat@1099: css.rel = 'stylesheet'; djmoffat@1099: css.type = 'text/css'; djmoffat@1099: css.href = 'interfaces/horizontal-sliders.css'; djmoffat@1099: djmoffat@1099: document.getElementsByTagName("head")[0].appendChild(css); djmoffat@1099: break; djmoffat@1099: case "discrete": djmoffat@1099: case "likert": djmoffat@1099: // Above enumerate to horizontal discrete radios djmoffat@1099: interfaceJS.setAttribute("src","interfaces/discrete.js"); djmoffat@1099: djmoffat@1099: // horizontal-sliders comes with a css file djmoffat@1099: var css = document.createElement('link'); djmoffat@1099: css.rel = 'stylesheet'; djmoffat@1099: css.type = 'text/css'; djmoffat@1099: css.href = 'interfaces/discrete.css'; djmoffat@1099: djmoffat@1099: document.getElementsByTagName("head")[0].appendChild(css); djmoffat@1099: break; djmoffat@1099: } djmoffat@1099: document.getElementsByTagName("head")[0].appendChild(interfaceJS); djmoffat@1099: djmoffat@1099: // Create the audio engine object djmoffat@1099: audioEngineContext = new AudioEngine(specification); djmoffat@1099: djmoffat@1099: $(specification.pages).each(function(index,elem){ djmoffat@1099: $(elem.audioElements).each(function(i,audioElem){ djmoffat@1099: var URL = elem.hostURL + audioElem.url; djmoffat@1099: var buffer = null; djmoffat@1099: for (var i=0; i max_w) djmoffat@1099: max_w = w; djmoffat@1099: index++; djmoffat@1099: } djmoffat@1099: max_w += 12; djmoffat@1099: this.popupResponse.style.textAlign=""; djmoffat@1099: var leftP = ((max_w/500)/2)*100; djmoffat@1099: this.popupResponse.style.left=leftP+"%"; djmoffat@1099: } else if (node.specification.type == 'radio') { djmoffat@1099: if (node.response == undefined) { djmoffat@1099: node.response = {name: "", text: ""}; djmoffat@1099: } djmoffat@1099: var index = 0; djmoffat@1099: var max_w = 0; djmoffat@1099: for (var option of node.specification.options) { djmoffat@1099: var input = document.createElement('input'); djmoffat@1099: input.id = option.name; djmoffat@1099: input.type = 'radio'; djmoffat@1099: input.name = node.specification.id; djmoffat@1099: var span = document.createElement('span'); djmoffat@1099: span.textContent = option.text; djmoffat@1099: var hold = document.createElement('div'); djmoffat@1099: hold.setAttribute('name','option'); djmoffat@1099: hold.style.padding = '4px'; djmoffat@1099: hold.appendChild(input); djmoffat@1099: hold.appendChild(span); djmoffat@1099: this.popupResponse.appendChild(hold); djmoffat@1099: if (input.id == node.response.name) { djmoffat@1099: input.checked = "true"; djmoffat@1099: } djmoffat@1099: var w = $(span).width(); djmoffat@1099: if (w > max_w) djmoffat@1099: max_w = w; djmoffat@1099: } djmoffat@1099: max_w += 12; djmoffat@1099: this.popupResponse.style.textAlign=""; djmoffat@1099: var leftP = ((max_w/500)/2)*100; djmoffat@1099: this.popupResponse.style.left=leftP+"%"; djmoffat@1099: } else if (node.specification.type == 'number') { djmoffat@1099: var input = document.createElement('input'); djmoffat@1099: input.type = 'textarea'; djmoffat@1099: if (node.min != null) {input.min = node.specification.min;} djmoffat@1099: if (node.max != null) {input.max = node.specification.max;} djmoffat@1099: if (node.step != null) {input.step = node.specification.step;} djmoffat@1099: if (node.response != undefined) { djmoffat@1099: input.value = node.response; djmoffat@1099: } djmoffat@1099: this.popupResponse.appendChild(input); djmoffat@1099: this.popupResponse.style.textAlign="center"; djmoffat@1099: this.popupResponse.style.left="0%"; djmoffat@1099: } djmoffat@1099: if(this.currentIndex+1 == this.popupOptions.length) { djmoffat@1099: if (this.node.location == "pre") { djmoffat@1099: this.buttonProceed.textContent = 'Start'; djmoffat@1099: } else { djmoffat@1099: this.buttonProceed.textContent = 'Submit'; djmoffat@1099: } djmoffat@1099: } else { djmoffat@1099: this.buttonProceed.textContent = 'Next'; djmoffat@1099: } djmoffat@1099: if(this.currentIndex > 0) djmoffat@1099: this.buttonPrevious.style.visibility = 'visible'; djmoffat@1099: else djmoffat@1099: this.buttonPrevious.style.visibility = 'hidden'; djmoffat@1099: }; djmoffat@1099: djmoffat@1099: this.initState = function(node,store) { djmoffat@1099: //Call this with your preTest and postTest nodes when needed to djmoffat@1099: // initialise the popup procedure. djmoffat@1099: if (node.options.length > 0) { djmoffat@1099: this.popupOptions = []; djmoffat@1099: this.node = node; djmoffat@1099: this.store = store; djmoffat@1099: for (var opt of node.options) djmoffat@1099: { djmoffat@1099: this.popupOptions.push({ djmoffat@1099: specification: opt, djmoffat@1099: response: null djmoffat@1099: }); djmoffat@1099: } djmoffat@1099: this.currentIndex = 0; djmoffat@1099: this.showPopup(); djmoffat@1099: this.postNode(); djmoffat@1099: } else { djmoffat@1099: advanceState(); djmoffat@1099: } djmoffat@1099: }; djmoffat@1099: djmoffat@1099: this.proceedClicked = function() { djmoffat@1099: // Each time the popup button is clicked! djmoffat@1099: var node = this.popupOptions[this.currentIndex]; djmoffat@1099: if (node.specification.type == 'question') { djmoffat@1099: // Must extract the question data djmoffat@1099: var textArea = $(popup.popupContent).find('textarea')[0]; djmoffat@1099: if (node.specification.mandatory == true && textArea.value.length == 0) { djmoffat@1099: alert('This question is mandatory'); djmoffat@1099: return; djmoffat@1099: } else { djmoffat@1099: // Save the text content djmoffat@1099: console.log("Question: "+ node.specification.statement); djmoffat@1099: console.log("Question Response: "+ textArea.value); djmoffat@1099: node.response = textArea.value; djmoffat@1099: } djmoffat@1099: } else if (node.specification.type == 'checkbox') { djmoffat@1099: // Must extract checkbox data djmoffat@1099: console.log("Checkbox: "+ node.specification.statement); djmoffat@1099: var inputs = this.popupResponse.getElementsByTagName('input'); djmoffat@1099: node.response = []; djmoffat@1099: for (var i=0; i node.max && node.max != null) { djmoffat@1099: alert('Number is above the maximum value of '+node.max); djmoffat@1099: return; djmoffat@1099: } djmoffat@1099: node.response = input.value; djmoffat@1099: } djmoffat@1099: this.currentIndex++; djmoffat@1099: if (this.currentIndex < this.popupOptions.length) { djmoffat@1099: this.postNode(); djmoffat@1099: } else { djmoffat@1099: // Reached the end of the popupOptions djmoffat@1099: this.hidePopup(); djmoffat@1099: for (var node of this.popupOptions) djmoffat@1099: { djmoffat@1099: this.store.postResult(node); djmoffat@1099: } djmoffat@1099: advanceState(); djmoffat@1099: } djmoffat@1099: }; djmoffat@1099: djmoffat@1099: this.previousClick = function() { djmoffat@1099: // Triggered when the 'Back' button is clicked in the survey djmoffat@1099: if (this.currentIndex > 0) { djmoffat@1099: this.currentIndex--; djmoffat@1099: this.postNode(); djmoffat@1099: } djmoffat@1099: }; djmoffat@1099: djmoffat@1099: this.resize = function(event) djmoffat@1099: { djmoffat@1099: // Called on window resize; djmoffat@1099: if (this.popup != null) { djmoffat@1099: this.popup.style.left = (window.innerWidth/2)-250 + 'px'; djmoffat@1099: this.popup.style.top = (window.innerHeight/2)-125 + 'px'; djmoffat@1099: var blank = document.getElementsByClassName('testHalt')[0]; djmoffat@1099: blank.style.width = window.innerWidth; djmoffat@1099: blank.style.height = window.innerHeight; djmoffat@1099: } djmoffat@1099: }; n@1102: this.hideNextButton = function() { n@1102: this.buttonProceed.style.visibility = "hidden"; n@1102: } n@1102: this.hidePreviousButton = function() { n@1102: this.buttonPrevious.style.visibility = "hidden"; n@1102: } n@1102: this.showNextButton = function() { n@1102: this.buttonProceed.style.visibility = "visible"; n@1102: } n@1102: this.showPreviousButton = function() { n@1102: this.buttonPrevious.style.visibility = "visible"; n@1102: } djmoffat@1099: } djmoffat@1099: djmoffat@1099: function advanceState() djmoffat@1099: { djmoffat@1099: // Just for complete clarity djmoffat@1099: testState.advanceState(); djmoffat@1099: } djmoffat@1099: djmoffat@1099: function stateMachine() djmoffat@1099: { djmoffat@1099: // Object prototype for tracking and managing the test state djmoffat@1099: this.stateMap = []; djmoffat@1099: this.preTestSurvey = null; djmoffat@1099: this.postTestSurvey = null; djmoffat@1099: this.stateIndex = null; djmoffat@1099: this.currentStateMap = null; djmoffat@1099: this.currentStatePosition = null; djmoffat@1099: this.currentStore = null; djmoffat@1099: this.initialise = function(){ djmoffat@1099: djmoffat@1099: // Get the data from Specification djmoffat@1099: var pageHolder = []; djmoffat@1099: for (var page of specification.pages) djmoffat@1099: { djmoffat@1099: var repeat = page.repeatCount; djmoffat@1099: while(repeat >= 0) djmoffat@1099: { djmoffat@1099: pageHolder.push(page); djmoffat@1099: repeat--; djmoffat@1099: } djmoffat@1099: } djmoffat@1099: if (specification.randomiseOrder) djmoffat@1099: { djmoffat@1099: pageHolder = randomiseOrder(pageHolder); djmoffat@1099: } djmoffat@1099: for (var i=0; i 0) { djmoffat@1099: if(this.stateIndex != null) { djmoffat@1099: console.log('NOTE - State already initialise'); djmoffat@1099: } djmoffat@1099: this.stateIndex = -1; djmoffat@1099: } else { djmoffat@1099: console.log('FATAL - StateMap not correctly constructed. EMPTY_STATE_MAP'); djmoffat@1099: } djmoffat@1099: }; djmoffat@1099: this.advanceState = function(){ djmoffat@1099: if (this.stateIndex == null) { djmoffat@1099: this.initialise(); djmoffat@1099: } djmoffat@1099: if (this.stateIndex == -1) { djmoffat@1099: this.stateIndex++; djmoffat@1099: console.log('Starting test...'); djmoffat@1099: if (this.preTestSurvey != null) djmoffat@1099: { djmoffat@1099: popup.initState(this.preTestSurvey,storage.globalPreTest); djmoffat@1099: } else { djmoffat@1099: this.advanceState(); djmoffat@1099: } djmoffat@1099: } else if (this.stateIndex == this.stateMap.length) djmoffat@1099: { djmoffat@1099: // All test pages complete, post test djmoffat@1099: console.log('Ending test ...'); djmoffat@1099: this.stateIndex++; djmoffat@1099: if (this.postTestSurvey == null) { djmoffat@1099: this.advanceState(); djmoffat@1099: } else { djmoffat@1099: popup.initState(this.postTestSurvey,storage.globalPostTest); djmoffat@1099: } djmoffat@1099: } else if (this.stateIndex > this.stateMap.length) djmoffat@1099: { djmoffat@1099: createProjectSave(specification.projectReturn); djmoffat@1099: } djmoffat@1099: else djmoffat@1099: { djmoffat@1099: if (this.currentStateMap == null) djmoffat@1099: { djmoffat@1099: this.currentStateMap = this.stateMap[this.stateIndex]; djmoffat@1099: if (this.currentStateMap.randomiseOrder) djmoffat@1099: { djmoffat@1099: this.currentStateMap.audioElements = randomiseOrder(this.currentStateMap.audioElements); djmoffat@1099: } djmoffat@1099: this.currentStore = storage.createTestPageStore(this.currentStateMap); djmoffat@1099: if (this.currentStateMap.preTest != null) djmoffat@1099: { djmoffat@1099: this.currentStatePosition = 'pre'; djmoffat@1099: popup.initState(this.currentStateMap.preTest,storage.testPages[this.stateIndex].preTest); djmoffat@1099: } else { djmoffat@1099: this.currentStatePosition = 'test'; djmoffat@1099: } djmoffat@1099: interfaceContext.newPage(this.currentStateMap,storage.testPages[this.stateIndex]); djmoffat@1099: return; djmoffat@1099: } djmoffat@1099: switch(this.currentStatePosition) djmoffat@1099: { djmoffat@1099: case 'pre': djmoffat@1099: this.currentStatePosition = 'test'; djmoffat@1099: break; djmoffat@1099: case 'test': djmoffat@1099: this.currentStatePosition = 'post'; djmoffat@1099: // Save the data djmoffat@1099: this.testPageCompleted(); djmoffat@1099: if (this.currentStateMap.postTest == null) djmoffat@1099: { djmoffat@1099: this.advanceState(); djmoffat@1099: return; djmoffat@1099: } else { djmoffat@1099: popup.initState(this.currentStateMap.postTest,storage.testPages[this.stateIndex].postTest); djmoffat@1099: } djmoffat@1099: break; djmoffat@1099: case 'post': djmoffat@1099: this.stateIndex++; djmoffat@1099: this.currentStateMap = null; djmoffat@1099: this.advanceState(); djmoffat@1099: break; djmoffat@1099: }; djmoffat@1099: } djmoffat@1099: }; djmoffat@1099: djmoffat@1099: this.testPageCompleted = function() { djmoffat@1099: // Function called each time a test page has been completed djmoffat@1099: var storePoint = storage.testPages[this.stateIndex]; djmoffat@1099: // First get the test metric djmoffat@1099: djmoffat@1099: var metric = storePoint.XMLDOM.getElementsByTagName('metric')[0]; djmoffat@1099: if (audioEngineContext.metric.enableTestTimer) djmoffat@1099: { djmoffat@1099: var testTime = storePoint.parent.document.createElement('metricresult'); djmoffat@1099: testTime.id = 'testTime'; djmoffat@1099: testTime.textContent = audioEngineContext.timer.testDuration; djmoffat@1099: metric.appendChild(testTime); djmoffat@1099: } djmoffat@1099: djmoffat@1099: var audioObjects = audioEngineContext.audioObjects; djmoffat@1099: for (var ao of audioEngineContext.audioObjects) djmoffat@1099: { djmoffat@1099: ao.exportXMLDOM(); djmoffat@1099: } djmoffat@1099: for (var element of interfaceContext.commentQuestions) djmoffat@1099: { djmoffat@1099: element.exportXMLDOM(storePoint); djmoffat@1099: } djmoffat@1099: pageXMLSave(storePoint.XMLDOM, this.currentStateMap); djmoffat@1099: }; djmoffat@1099: } djmoffat@1099: djmoffat@1099: function AudioEngine(specification) { djmoffat@1099: djmoffat@1099: // Create two output paths, the main outputGain and fooGain. djmoffat@1099: // Output gain is default to 1 and any items for playback route here djmoffat@1099: // Foo gain is used for analysis to ensure paths get processed, but are not heard djmoffat@1099: // because web audio will optimise and any route which does not go to the destination gets ignored. djmoffat@1099: this.outputGain = audioContext.createGain(); djmoffat@1099: this.fooGain = audioContext.createGain(); djmoffat@1099: this.fooGain.gain = 0; djmoffat@1099: djmoffat@1099: // Use this to detect playback state: 0 - stopped, 1 - playing djmoffat@1099: this.status = 0; djmoffat@1099: djmoffat@1099: // Connect both gains to output djmoffat@1099: this.outputGain.connect(audioContext.destination); djmoffat@1099: this.fooGain.connect(audioContext.destination); djmoffat@1099: djmoffat@1099: // Create the timer Object djmoffat@1099: this.timer = new timer(); djmoffat@1099: // Create session metrics djmoffat@1099: this.metric = new sessionMetrics(this,specification); djmoffat@1099: djmoffat@1099: this.loopPlayback = false; djmoffat@1099: djmoffat@1099: this.pageStore = null; djmoffat@1099: djmoffat@1099: // Create store for new audioObjects djmoffat@1099: this.audioObjects = []; djmoffat@1099: djmoffat@1099: this.buffers = []; djmoffat@1099: this.bufferObj = function() djmoffat@1099: { djmoffat@1099: this.url = null; djmoffat@1099: this.buffer = null; djmoffat@1099: this.xmlRequest = new XMLHttpRequest(); djmoffat@1099: this.xmlRequest.parent = this; djmoffat@1099: this.users = []; djmoffat@1099: this.progress = 0; djmoffat@1099: this.status = 0; djmoffat@1099: this.ready = function() djmoffat@1099: { djmoffat@1099: if (this.status >= 2) djmoffat@1099: { djmoffat@1099: this.status = 3; djmoffat@1099: } djmoffat@1099: for (var i=0; i 0) {this.wasMoved = true;} djmoffat@1099: this.movementTracker[this.movementTracker.length] = [time, position]; djmoffat@1099: }; djmoffat@1099: djmoffat@1099: this.startListening = function(time) djmoffat@1099: { djmoffat@1099: if (this.listenHold == false) djmoffat@1099: { djmoffat@1099: this.wasListenedTo = true; djmoffat@1099: this.listenStart = time; djmoffat@1099: this.listenHold = true; djmoffat@1099: djmoffat@1099: var evnt = document.createElement('event'); djmoffat@1099: var testTime = document.createElement('testTime'); djmoffat@1099: testTime.setAttribute('start',time); djmoffat@1099: var bufferTime = document.createElement('bufferTime'); djmoffat@1099: bufferTime.setAttribute('start',this.parent.getCurrentPosition()); djmoffat@1099: evnt.appendChild(testTime); djmoffat@1099: evnt.appendChild(bufferTime); djmoffat@1099: this.listenTracker.push(evnt); djmoffat@1099: djmoffat@1099: console.log('slider ' + this.parent.id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id djmoffat@1099: } djmoffat@1099: }; djmoffat@1099: djmoffat@1099: this.stopListening = function(time,bufferStopTime) djmoffat@1099: { djmoffat@1099: if (this.listenHold == true) djmoffat@1099: { djmoffat@1099: var diff = time - this.listenStart; djmoffat@1099: this.listenedTimer += (diff); djmoffat@1099: this.listenStart = 0; djmoffat@1099: this.listenHold = false; djmoffat@1099: djmoffat@1099: var evnt = this.listenTracker[this.listenTracker.length-1]; djmoffat@1099: var testTime = evnt.getElementsByTagName('testTime')[0]; djmoffat@1099: var bufferTime = evnt.getElementsByTagName('bufferTime')[0]; djmoffat@1099: testTime.setAttribute('stop',time); djmoffat@1099: if (bufferStopTime == undefined) { djmoffat@1099: bufferTime.setAttribute('stop',this.parent.getCurrentPosition()); djmoffat@1099: } else { djmoffat@1099: bufferTime.setAttribute('stop',bufferStopTime); djmoffat@1099: } djmoffat@1099: console.log('slider ' + this.parent.id + ' played for (' + diff + ')'); // DEBUG/SAFETY: show played slider id djmoffat@1099: } djmoffat@1099: }; djmoffat@1099: djmoffat@1099: this.exportXMLDOM = function() { djmoffat@1099: var storeDOM = []; djmoffat@1099: if (audioEngineContext.metric.enableElementTimer) { djmoffat@1099: var mElementTimer = storage.document.createElement('metricresult'); djmoffat@1099: mElementTimer.setAttribute('name','enableElementTimer'); djmoffat@1099: mElementTimer.textContent = this.listenedTimer; djmoffat@1099: storeDOM.push(mElementTimer); djmoffat@1099: } djmoffat@1099: if (audioEngineContext.metric.enableElementTracker) { djmoffat@1099: var elementTrackerFull = storage.document.createElement('metricResult'); djmoffat@1099: elementTrackerFull.setAttribute('name','elementTrackerFull'); djmoffat@1099: for (var k=0; k djmoffat@1099: // DD/MM/YY djmoffat@1099: // djmoffat@1099: // djmoffat@1099: var dateTime = new Date(); djmoffat@1099: var year = document.createAttribute('year'); djmoffat@1099: var month = document.createAttribute('month'); djmoffat@1099: var day = document.createAttribute('day'); djmoffat@1099: var hour = document.createAttribute('hour'); djmoffat@1099: var minute = document.createAttribute('minute'); djmoffat@1099: var secs = document.createAttribute('secs'); djmoffat@1099: djmoffat@1099: year.nodeValue = dateTime.getFullYear(); djmoffat@1099: month.nodeValue = dateTime.getMonth()+1; djmoffat@1099: day.nodeValue = dateTime.getDate(); djmoffat@1099: hour.nodeValue = dateTime.getHours(); djmoffat@1099: minute.nodeValue = dateTime.getMinutes(); djmoffat@1099: secs.nodeValue = dateTime.getSeconds(); djmoffat@1099: djmoffat@1099: var hold = document.createElement("datetime"); djmoffat@1099: var date = document.createElement("date"); djmoffat@1099: date.textContent = year.nodeValue+'/'+month.nodeValue+'/'+day.nodeValue; djmoffat@1099: var time = document.createElement("time"); djmoffat@1099: time.textContent = hour.nodeValue+':'+minute.nodeValue+':'+secs.nodeValue; djmoffat@1099: djmoffat@1099: date.setAttributeNode(year); djmoffat@1099: date.setAttributeNode(month); djmoffat@1099: date.setAttributeNode(day); djmoffat@1099: time.setAttributeNode(hour); djmoffat@1099: time.setAttributeNode(minute); djmoffat@1099: time.setAttributeNode(secs); djmoffat@1099: djmoffat@1099: hold.appendChild(date); djmoffat@1099: hold.appendChild(time); djmoffat@1099: return hold; djmoffat@1099: djmoffat@1099: } djmoffat@1099: djmoffat@1099: function Specification() { djmoffat@1099: // Handles the decoding of the project specification XML into a simple JavaScript Object. djmoffat@1099: djmoffat@1099: this.interface = null; djmoffat@1099: this.projectReturn = "null"; djmoffat@1099: this.randomiseOrder = null; djmoffat@1099: this.testPages = null; djmoffat@1099: this.pages = []; djmoffat@1099: this.metrics = null; djmoffat@1099: this.interfaces = null; djmoffat@1099: this.loudness = null; djmoffat@1099: this.errors = []; djmoffat@1099: this.schema = null; djmoffat@1099: djmoffat@1099: this.processAttribute = function(attribute,schema) djmoffat@1099: { djmoffat@1099: // attribute is the string returned from getAttribute on the XML djmoffat@1099: // schema is the node djmoffat@1099: if (schema.getAttribute('name') == undefined && schema.getAttribute('ref') != undefined) djmoffat@1099: { djmoffat@1099: schema = this.schema.getAllElementsByName(schema.getAttribute('ref'))[0]; djmoffat@1099: } djmoffat@1099: var defaultOpt = schema.getAttribute('default'); djmoffat@1099: if (attribute == null) { djmoffat@1099: attribute = defaultOpt; djmoffat@1099: } djmoffat@1099: var dataType = schema.getAttribute('type'); djmoffat@1099: if (typeof dataType == "string") { dataType = dataType.substr(3);} djmoffat@1099: else {dataType = "string";} djmoffat@1099: if (attribute == null) djmoffat@1099: { djmoffat@1099: return attribute; djmoffat@1099: } djmoffat@1099: switch(dataType) djmoffat@1099: { djmoffat@1099: case "boolean": djmoffat@1099: if (attribute == 'true'){attribute = true;}else{attribute=false;} djmoffat@1099: break; djmoffat@1099: case "negativeInteger": djmoffat@1099: case "positiveInteger": djmoffat@1099: case "nonNegativeInteger": djmoffat@1099: case "nonPositiveInteger": djmoffat@1099: case "integer": djmoffat@1099: case "decimal": djmoffat@1099: case "short": djmoffat@1099: attribute = Number(attribute); djmoffat@1099: break; djmoffat@1099: case "string": djmoffat@1099: default: djmoffat@1099: attribute = String(attribute); djmoffat@1099: break; djmoffat@1099: } djmoffat@1099: return attribute; djmoffat@1099: }; djmoffat@1099: djmoffat@1099: this.decode = function(projectXML) { djmoffat@1099: this.errors = []; djmoffat@1099: // projectXML - DOM Parsed document djmoffat@1099: this.projectXML = projectXML.childNodes[0]; djmoffat@1099: var setupNode = projectXML.getElementsByTagName('setup')[0]; djmoffat@1099: var schemaSetup = this.schema.getAllElementsByName('setup')[0]; djmoffat@1099: // First decode the attributes djmoffat@1099: var attributes = schemaSetup.getAllElementsByTagName('xs:attribute'); djmoffat@1099: for (var i in attributes) djmoffat@1099: { djmoffat@1099: if (isNaN(Number(i)) == true){break;} djmoffat@1099: var attributeName = attributes[i].getAttribute('name'); djmoffat@1099: var projectAttr = setupNode.getAttribute(attributeName); djmoffat@1099: projectAttr = this.processAttribute(projectAttr,attributes[i]); djmoffat@1099: switch(typeof projectAttr) djmoffat@1099: { djmoffat@1099: case "number": djmoffat@1099: case "boolean": djmoffat@1099: eval('this.'+attributeName+' = '+projectAttr); djmoffat@1099: break; djmoffat@1099: case "string": djmoffat@1099: eval('this.'+attributeName+' = "'+projectAttr+'"'); djmoffat@1099: break; djmoffat@1099: } djmoffat@1099: djmoffat@1099: } djmoffat@1099: djmoffat@1099: this.metrics = new this.metricNode(); djmoffat@1099: djmoffat@1099: this.metrics.decode(this,setupNode.getElementsByTagName('metric')[0]); djmoffat@1099: djmoffat@1099: // Now process the survey node options djmoffat@1099: var survey = setupNode.getElementsByTagName('survey'); djmoffat@1099: for (var i in survey) { djmoffat@1099: if (isNaN(Number(i)) == true){break;} djmoffat@1099: var location = survey[i].getAttribute('location'); djmoffat@1099: if (location == 'pre' || location == 'before') djmoffat@1099: { djmoffat@1099: if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");} djmoffat@1099: else { djmoffat@1099: this.preTest = new this.surveyNode(); djmoffat@1099: this.preTest.decode(this,survey[i]); djmoffat@1099: } djmoffat@1099: } else if (location == 'post' || location == 'after') { djmoffat@1099: if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");} djmoffat@1099: else { djmoffat@1099: this.postTest = new this.surveyNode(); djmoffat@1099: this.postTest.decode(this,survey[i]); djmoffat@1099: } djmoffat@1099: } djmoffat@1099: } djmoffat@1099: djmoffat@1099: var interfaceNode = setupNode.getElementsByTagName('interface'); djmoffat@1099: if (interfaceNode.length > 1) djmoffat@1099: { djmoffat@1099: this.errors.push("Only one node in the node allowed! Others except first ingnored!"); djmoffat@1099: } djmoffat@1099: this.interfaces = new this.interfaceNode(); djmoffat@1099: if (interfaceNode.length != 0) djmoffat@1099: { djmoffat@1099: interfaceNode = interfaceNode[0]; djmoffat@1099: this.interfaces.decode(this,interfaceNode,this.schema.getAllElementsByName('interface')[1]); djmoffat@1099: } djmoffat@1099: djmoffat@1099: // Page tags djmoffat@1099: var pageTags = projectXML.getElementsByTagName('page'); djmoffat@1099: var pageSchema = this.schema.getAllElementsByName('page')[0]; djmoffat@1099: for (var i=0; i djmoffat@1099: var commentboxprefix = root.createElement("commentboxprefix"); djmoffat@1099: commentboxprefix.textContent = this.commentBoxPrefix; djmoffat@1099: AHNode.appendChild(commentboxprefix); djmoffat@1099: djmoffat@1099: for (var i=0; i djmoffat@1099: for (var i=0; i tag. djmoffat@1099: this.interfaceObjects = []; djmoffat@1099: this.interfaceObject = function(){}; djmoffat@1099: djmoffat@1099: this.resizeWindow = function(event) djmoffat@1099: { djmoffat@1099: popup.resize(event); djmoffat@1099: for(var i=0; i= 600) n@1102: { n@1102: boxwidth = 600; n@1102: } n@1102: else if (boxwidth < 400) n@1102: { n@1102: boxwidth = 400; n@1102: } n@1102: this.trackComment.style.width = boxwidth+"px"; n@1102: this.trackCommentBox.style.width = boxwidth-6+"px"; n@1102: }; n@1102: this.resize(); n@1102: }; n@1102: this.createCommentBox = function(audioObject) { n@1102: var node = new this.elementCommentBox(audioObject); n@1102: this.boxes.push(node); n@1102: audioObject.commentDOM = node; n@1102: return node; n@1102: }; n@1102: this.sortCommentBoxes = function() { n@1102: this.boxes.sort(function(a,b){return a.id - b.id;}); n@1102: }; n@1102: n@1102: this.showCommentBoxes = function(inject, sort) { n@1102: this.injectPoint = inject; n@1102: if (sort) {this.sortCommentBoxes();} n@1102: for (var box of this.boxes) { n@1102: inject.appendChild(box.trackComment); n@1102: } n@1102: }; n@1102: n@1102: this.deleteCommentBoxes = function() { n@1102: if (this.injectPoint != null) { n@1102: for (var box of this.boxes) { n@1102: this.injectPoint.removeChild(box.trackComment); n@1102: } n@1102: this.injectPoint = null; n@1102: } n@1102: this.boxes = []; n@1102: }; n@1102: } djmoffat@1099: djmoffat@1099: this.commentQuestions = []; djmoffat@1099: djmoffat@1099: this.commentBox = function(commentQuestion) { djmoffat@1099: this.specification = commentQuestion; djmoffat@1099: // Create document objects to hold the comment boxes djmoffat@1099: this.holder = document.createElement('div'); djmoffat@1099: this.holder.className = 'comment-div'; djmoffat@1099: // Create a string next to each comment asking for a comment djmoffat@1099: this.string = document.createElement('span'); djmoffat@1099: this.string.innerHTML = commentQuestion.statement; djmoffat@1099: // Create the HTML5 comment box 'textarea' djmoffat@1099: this.textArea = document.createElement('textarea'); djmoffat@1099: this.textArea.rows = '4'; djmoffat@1099: this.textArea.cols = '100'; djmoffat@1099: this.textArea.className = 'trackComment'; djmoffat@1099: var br = document.createElement('br'); djmoffat@1099: // Add to the holder. djmoffat@1099: this.holder.appendChild(this.string); djmoffat@1099: this.holder.appendChild(br); djmoffat@1099: this.holder.appendChild(this.textArea); djmoffat@1099: djmoffat@1099: this.exportXMLDOM = function(storePoint) { djmoffat@1099: var root = storePoint.parent.document.createElement('comment'); djmoffat@1099: root.id = this.specification.id; djmoffat@1099: root.setAttribute('type',this.specification.type); djmoffat@1099: console.log("Question: "+this.string.textContent); djmoffat@1099: console.log("Response: "+root.textContent); djmoffat@1099: var question = storePoint.parent.document.createElement('question'); djmoffat@1099: question.textContent = this.string.textContent; djmoffat@1099: var response = storePoint.parent.document.createElement('response'); djmoffat@1099: response.textContent = this.textArea.value; djmoffat@1099: root.appendChild(question); djmoffat@1099: root.appendChild(response); djmoffat@1099: storePoint.XMLDOM.appendChild(root); djmoffat@1099: return root; djmoffat@1099: }; djmoffat@1099: this.resize = function() djmoffat@1099: { djmoffat@1099: var boxwidth = (window.innerWidth-100)/2; djmoffat@1099: if (boxwidth >= 600) djmoffat@1099: { djmoffat@1099: boxwidth = 600; djmoffat@1099: } djmoffat@1099: else if (boxwidth < 400) djmoffat@1099: { djmoffat@1099: boxwidth = 400; djmoffat@1099: } djmoffat@1099: this.holder.style.width = boxwidth+"px"; djmoffat@1099: this.textArea.style.width = boxwidth-6+"px"; djmoffat@1099: }; djmoffat@1099: this.resize(); djmoffat@1099: }; djmoffat@1099: djmoffat@1099: this.radioBox = function(commentQuestion) { djmoffat@1099: this.specification = commentQuestion; djmoffat@1099: // Create document objects to hold the comment boxes djmoffat@1099: this.holder = document.createElement('div'); djmoffat@1099: this.holder.className = 'comment-div'; djmoffat@1099: // Create a string next to each comment asking for a comment djmoffat@1099: this.string = document.createElement('span'); djmoffat@1099: this.string.innerHTML = commentQuestion.statement; djmoffat@1099: var br = document.createElement('br'); djmoffat@1099: // Add to the holder. djmoffat@1099: this.holder.appendChild(this.string); djmoffat@1099: this.holder.appendChild(br); djmoffat@1099: this.options = []; djmoffat@1099: this.inputs = document.createElement('div'); djmoffat@1099: this.span = document.createElement('div'); djmoffat@1099: this.inputs.align = 'center'; djmoffat@1099: this.inputs.style.marginLeft = '12px'; djmoffat@1099: this.span.style.marginLeft = '12px'; djmoffat@1099: this.span.align = 'center'; djmoffat@1099: this.span.style.marginTop = '15px'; djmoffat@1099: djmoffat@1099: var optCount = commentQuestion.options.length; djmoffat@1099: for (var optNode of commentQuestion.options) djmoffat@1099: { djmoffat@1099: var div = document.createElement('div'); djmoffat@1099: div.style.width = '80px'; djmoffat@1099: div.style.float = 'left'; djmoffat@1099: var input = document.createElement('input'); djmoffat@1099: input.type = 'radio'; djmoffat@1099: input.name = commentQuestion.id; djmoffat@1099: input.setAttribute('setvalue',optNode.name); djmoffat@1099: input.className = 'comment-radio'; djmoffat@1099: div.appendChild(input); djmoffat@1099: this.inputs.appendChild(div); djmoffat@1099: djmoffat@1099: djmoffat@1099: div = document.createElement('div'); djmoffat@1099: div.style.width = '80px'; djmoffat@1099: div.style.float = 'left'; djmoffat@1099: div.align = 'center'; djmoffat@1099: var span = document.createElement('span'); djmoffat@1099: span.textContent = optNode.text; djmoffat@1099: span.className = 'comment-radio-span'; djmoffat@1099: div.appendChild(span); djmoffat@1099: this.span.appendChild(div); djmoffat@1099: this.options.push(input); djmoffat@1099: } djmoffat@1099: this.holder.appendChild(this.span); djmoffat@1099: this.holder.appendChild(this.inputs); djmoffat@1099: djmoffat@1099: this.exportXMLDOM = function(storePoint) { djmoffat@1099: var root = storePoint.parent.document.createElement('comment'); djmoffat@1099: root.id = this.specification.id; djmoffat@1099: root.setAttribute('type',this.specification.type); djmoffat@1099: var question = document.createElement('question'); djmoffat@1099: question.textContent = this.string.textContent; djmoffat@1099: var response = document.createElement('response'); djmoffat@1099: var i=0; djmoffat@1099: while(this.options[i].checked == false) { djmoffat@1099: i++; djmoffat@1099: if (i >= this.options.length) { djmoffat@1099: break; djmoffat@1099: } djmoffat@1099: } djmoffat@1099: if (i >= this.options.length) { djmoffat@1099: response.textContent = 'null'; djmoffat@1099: } else { djmoffat@1099: response.textContent = this.options[i].getAttribute('setvalue'); djmoffat@1099: response.setAttribute('number',i); djmoffat@1099: } djmoffat@1099: console.log('Comment: '+question.textContent); djmoffat@1099: console.log('Response: '+response.textContent); djmoffat@1099: root.appendChild(question); djmoffat@1099: root.appendChild(response); djmoffat@1099: storePoint.XMLDOM.appendChild(root); djmoffat@1099: return root; djmoffat@1099: }; djmoffat@1099: this.resize = function() djmoffat@1099: { djmoffat@1099: var boxwidth = (window.innerWidth-100)/2; djmoffat@1099: if (boxwidth >= 600) djmoffat@1099: { djmoffat@1099: boxwidth = 600; djmoffat@1099: } djmoffat@1099: else if (boxwidth < 400) djmoffat@1099: { djmoffat@1099: boxwidth = 400; djmoffat@1099: } djmoffat@1099: this.holder.style.width = boxwidth+"px"; djmoffat@1099: var text = this.holder.children[2]; djmoffat@1099: var options = this.holder.children[3]; djmoffat@1099: var optCount = options.children.length; djmoffat@1099: var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px'; djmoffat@1099: var options = options.firstChild; djmoffat@1099: var text = text.firstChild; djmoffat@1099: options.style.marginRight = spanMargin; djmoffat@1099: options.style.marginLeft = spanMargin; djmoffat@1099: text.style.marginRight = spanMargin; djmoffat@1099: text.style.marginLeft = spanMargin; djmoffat@1099: while(options.nextSibling != undefined) djmoffat@1099: { djmoffat@1099: options = options.nextSibling; djmoffat@1099: text = text.nextSibling; djmoffat@1099: options.style.marginRight = spanMargin; djmoffat@1099: options.style.marginLeft = spanMargin; djmoffat@1099: text.style.marginRight = spanMargin; djmoffat@1099: text.style.marginLeft = spanMargin; djmoffat@1099: } djmoffat@1099: }; djmoffat@1099: this.resize(); djmoffat@1099: }; djmoffat@1099: djmoffat@1099: this.checkboxBox = function(commentQuestion) { djmoffat@1099: this.specification = commentQuestion; djmoffat@1099: // Create document objects to hold the comment boxes djmoffat@1099: this.holder = document.createElement('div'); djmoffat@1099: this.holder.className = 'comment-div'; djmoffat@1099: // Create a string next to each comment asking for a comment djmoffat@1099: this.string = document.createElement('span'); djmoffat@1099: this.string.innerHTML = commentQuestion.statement; djmoffat@1099: var br = document.createElement('br'); djmoffat@1099: // Add to the holder. djmoffat@1099: this.holder.appendChild(this.string); djmoffat@1099: this.holder.appendChild(br); djmoffat@1099: this.options = []; djmoffat@1099: this.inputs = document.createElement('div'); djmoffat@1099: this.span = document.createElement('div'); djmoffat@1099: this.inputs.align = 'center'; djmoffat@1099: this.inputs.style.marginLeft = '12px'; djmoffat@1099: this.span.style.marginLeft = '12px'; djmoffat@1099: this.span.align = 'center'; djmoffat@1099: this.span.style.marginTop = '15px'; djmoffat@1099: djmoffat@1099: var optCount = commentQuestion.options.length; djmoffat@1099: for (var i=0; i= 600) djmoffat@1099: { djmoffat@1099: boxwidth = 600; djmoffat@1099: } djmoffat@1099: else if (boxwidth < 400) djmoffat@1099: { djmoffat@1099: boxwidth = 400; djmoffat@1099: } djmoffat@1099: this.holder.style.width = boxwidth+"px"; djmoffat@1099: var text = this.holder.children[2]; djmoffat@1099: var options = this.holder.children[3]; djmoffat@1099: var optCount = options.children.length; djmoffat@1099: var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px'; djmoffat@1099: var options = options.firstChild; djmoffat@1099: var text = text.firstChild; djmoffat@1099: options.style.marginRight = spanMargin; djmoffat@1099: options.style.marginLeft = spanMargin; djmoffat@1099: text.style.marginRight = spanMargin; djmoffat@1099: text.style.marginLeft = spanMargin; djmoffat@1099: while(options.nextSibling != undefined) djmoffat@1099: { djmoffat@1099: options = options.nextSibling; djmoffat@1099: text = text.nextSibling; djmoffat@1099: options.style.marginRight = spanMargin; djmoffat@1099: options.style.marginLeft = spanMargin; djmoffat@1099: text.style.marginRight = spanMargin; djmoffat@1099: text.style.marginLeft = spanMargin; djmoffat@1099: } djmoffat@1099: }; djmoffat@1099: this.resize(); djmoffat@1099: }; djmoffat@1099: djmoffat@1099: this.createCommentQuestion = function(element) { djmoffat@1099: var node; djmoffat@1099: if (element.type == 'question') { djmoffat@1099: node = new this.commentBox(element); djmoffat@1099: } else if (element.type == 'radio') { djmoffat@1099: node = new this.radioBox(element); djmoffat@1099: } else if (element.type == 'checkbox') { djmoffat@1099: node = new this.checkboxBox(element); djmoffat@1099: } djmoffat@1099: this.commentQuestions.push(node); djmoffat@1099: return node; djmoffat@1099: }; djmoffat@1099: djmoffat@1099: this.deleteCommentQuestions = function() djmoffat@1099: { djmoffat@1099: this.commentQuestions = []; djmoffat@1099: }; djmoffat@1099: djmoffat@1099: this.playhead = new function() djmoffat@1099: { djmoffat@1099: this.object = document.createElement('div'); djmoffat@1099: this.object.className = 'playhead'; djmoffat@1099: this.object.align = 'left'; djmoffat@1099: var curTime = document.createElement('div'); djmoffat@1099: curTime.style.width = '50px'; djmoffat@1099: this.curTimeSpan = document.createElement('span'); djmoffat@1099: this.curTimeSpan.textContent = '00:00'; djmoffat@1099: curTime.appendChild(this.curTimeSpan); djmoffat@1099: this.object.appendChild(curTime); djmoffat@1099: this.scrubberTrack = document.createElement('div'); djmoffat@1099: this.scrubberTrack.className = 'playhead-scrub-track'; djmoffat@1099: djmoffat@1099: this.scrubberHead = document.createElement('div'); djmoffat@1099: this.scrubberHead.id = 'playhead-scrubber'; djmoffat@1099: this.scrubberTrack.appendChild(this.scrubberHead); djmoffat@1099: this.object.appendChild(this.scrubberTrack); djmoffat@1099: djmoffat@1099: this.timePerPixel = 0; djmoffat@1099: this.maxTime = 0; djmoffat@1099: djmoffat@1099: this.playbackObject; djmoffat@1099: djmoffat@1099: this.setTimePerPixel = function(audioObject) { djmoffat@1099: //maxTime must be in seconds djmoffat@1099: this.playbackObject = audioObject; djmoffat@1099: this.maxTime = audioObject.buffer.buffer.duration; djmoffat@1099: var width = 490; //500 - 10, 5 each side of the tracker head djmoffat@1099: this.timePerPixel = this.maxTime/490; djmoffat@1099: if (this.maxTime < 60) { djmoffat@1099: this.curTimeSpan.textContent = '0.00'; djmoffat@1099: } else { djmoffat@1099: this.curTimeSpan.textContent = '00:00'; djmoffat@1099: } djmoffat@1099: }; djmoffat@1099: djmoffat@1099: this.update = function() { djmoffat@1099: // Update the playhead position, startPlay must be called djmoffat@1099: if (this.timePerPixel > 0) { djmoffat@1099: var time = this.playbackObject.getCurrentPosition(); djmoffat@1099: if (time > 0 && time < this.maxTime) { djmoffat@1099: var width = 490; djmoffat@1099: var pix = Math.floor(time/this.timePerPixel); djmoffat@1099: this.scrubberHead.style.left = pix+'px'; djmoffat@1099: if (this.maxTime > 60.0) { djmoffat@1099: var secs = time%60; djmoffat@1099: var mins = Math.floor((time-secs)/60); djmoffat@1099: secs = secs.toString(); djmoffat@1099: secs = secs.substr(0,2); djmoffat@1099: mins = mins.toString(); djmoffat@1099: this.curTimeSpan.textContent = mins+':'+secs; djmoffat@1099: } else { djmoffat@1099: time = time.toString(); djmoffat@1099: this.curTimeSpan.textContent = time.substr(0,4); djmoffat@1099: } djmoffat@1099: } else { djmoffat@1099: this.scrubberHead.style.left = '0px'; djmoffat@1099: if (this.maxTime < 60) { djmoffat@1099: this.curTimeSpan.textContent = '0.00'; djmoffat@1099: } else { djmoffat@1099: this.curTimeSpan.textContent = '00:00'; djmoffat@1099: } djmoffat@1099: } djmoffat@1099: } djmoffat@1099: }; djmoffat@1099: djmoffat@1099: this.interval = undefined; djmoffat@1099: djmoffat@1099: this.start = function() { djmoffat@1099: if (this.playbackObject != undefined && this.interval == undefined) { djmoffat@1099: if (this.maxTime < 60) { djmoffat@1099: this.interval = setInterval(function(){interfaceContext.playhead.update();},10); djmoffat@1099: } else { djmoffat@1099: this.interval = setInterval(function(){interfaceContext.playhead.update();},100); djmoffat@1099: } djmoffat@1099: } djmoffat@1099: }; djmoffat@1099: this.stop = function() { djmoffat@1099: clearInterval(this.interval); djmoffat@1099: this.interval = undefined; djmoffat@1099: this.scrubberHead.style.left = '0px'; djmoffat@1099: if (this.maxTime < 60) { djmoffat@1099: this.curTimeSpan.textContent = '0.00'; djmoffat@1099: } else { djmoffat@1099: this.curTimeSpan.textContent = '00:00'; djmoffat@1099: } djmoffat@1099: }; djmoffat@1099: }; djmoffat@1099: djmoffat@1099: this.volume = new function() djmoffat@1099: { djmoffat@1099: // An in-built volume module which can be viewed on page djmoffat@1099: // Includes trackers on page-by-page data djmoffat@1099: // Volume does NOT reset to 0dB on each page load djmoffat@1099: this.valueLin = 1.0; djmoffat@1099: this.valueDB = 0.0; djmoffat@1099: this.object = document.createElement('div'); djmoffat@1099: this.object.id = 'master-volume-holder'; djmoffat@1099: this.slider = document.createElement('input'); djmoffat@1099: this.slider.id = 'master-volume-control'; djmoffat@1099: this.slider.type = 'range'; djmoffat@1099: this.valueText = document.createElement('span'); djmoffat@1099: this.valueText.id = 'master-volume-feedback'; djmoffat@1099: this.valueText.textContent = '0dB'; djmoffat@1099: djmoffat@1099: this.slider.min = -60; djmoffat@1099: this.slider.max = 12; djmoffat@1099: this.slider.value = 0; djmoffat@1099: this.slider.step = 1; djmoffat@1099: this.slider.onmousemove = function(event) djmoffat@1099: { djmoffat@1099: interfaceContext.volume.valueDB = event.currentTarget.value; djmoffat@1099: interfaceContext.volume.valueLin = decibelToLinear(interfaceContext.volume.valueDB); djmoffat@1099: interfaceContext.volume.valueText.textContent = interfaceContext.volume.valueDB+'dB'; djmoffat@1099: audioEngineContext.outputGain.gain.value = interfaceContext.volume.valueLin; djmoffat@1099: } djmoffat@1099: this.slider.onmouseup = function(event) djmoffat@1099: { djmoffat@1099: var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker'); djmoffat@1099: if (storePoint.length == 0) djmoffat@1099: { djmoffat@1099: storePoint = storage.document.createElement('metricresult'); djmoffat@1099: storePoint.setAttribute('name','volumeTracker'); djmoffat@1099: testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint); djmoffat@1099: } djmoffat@1099: else { djmoffat@1099: storePoint = storePoint[0]; djmoffat@1099: } djmoffat@1099: var node = storage.document.createElement('movement'); djmoffat@1099: node.setAttribute('test-time',audioEngineContext.timer.getTestTime()); djmoffat@1099: node.setAttribute('volume',interfaceContext.volume.valueDB); djmoffat@1099: node.setAttribute('format','dBFS'); djmoffat@1099: storePoint.appendChild(node); djmoffat@1099: } djmoffat@1099: djmoffat@1099: var title = document.createElement('div'); djmoffat@1099: title.innerHTML = 'Master Volume Control'; djmoffat@1099: title.style.fontSize = '0.75em'; djmoffat@1099: title.style.width = "100%"; djmoffat@1099: title.align = 'center'; djmoffat@1099: this.object.appendChild(title); djmoffat@1099: djmoffat@1099: this.object.appendChild(this.slider); djmoffat@1099: this.object.appendChild(this.valueText); djmoffat@1099: } djmoffat@1099: // Global Checkers djmoffat@1099: // These functions will help enforce the checkers djmoffat@1099: this.checkHiddenAnchor = function() djmoffat@1099: { djmoffat@1099: for (var ao of audioEngineContext.audioObjects) djmoffat@1099: { djmoffat@1099: if (ao.specification.type == "anchor") djmoffat@1099: { djmoffat@1099: if (ao.interfaceDOM.getValue() > (ao.specification.marker/100) && ao.specification.marker > 0) { djmoffat@1099: // Anchor is not set below djmoffat@1099: console.log('Anchor node not below marker value'); djmoffat@1099: alert('Please keep listening'); djmoffat@1099: this.storeErrorNode('Anchor node not below marker value'); djmoffat@1099: return false; djmoffat@1099: } djmoffat@1099: } djmoffat@1099: } djmoffat@1099: return true; djmoffat@1099: }; djmoffat@1099: djmoffat@1099: this.checkHiddenReference = function() djmoffat@1099: { djmoffat@1099: for (var ao of audioEngineContext.audioObjects) djmoffat@1099: { djmoffat@1099: if (ao.specification.type == "reference") djmoffat@1099: { djmoffat@1099: if (ao.interfaceDOM.getValue() < (ao.specification.marker/100) && ao.specification.marker > 0) { djmoffat@1099: // Anchor is not set below djmoffat@1099: console.log('Reference node not above marker value'); djmoffat@1099: this.storeErrorNode('Reference node not above marker value'); djmoffat@1099: alert('Please keep listening'); djmoffat@1099: return false; djmoffat@1099: } djmoffat@1099: } djmoffat@1099: } djmoffat@1099: return true; djmoffat@1099: }; djmoffat@1099: djmoffat@1099: this.checkFragmentsFullyPlayed = function () djmoffat@1099: { djmoffat@1099: // Checks the entire file has been played back djmoffat@1099: // NOTE ! This will return true IF playback is Looped!!! djmoffat@1099: if (audioEngineContext.loopPlayback) djmoffat@1099: { djmoffat@1099: console.log("WARNING - Looped source: Cannot check fragments are fully played"); djmoffat@1099: return true; djmoffat@1099: } djmoffat@1099: var check_pass = true; djmoffat@1099: var error_obj = []; djmoffat@1099: for (var i = 0; i= time) djmoffat@1099: { djmoffat@1099: passed = true; djmoffat@1099: break; djmoffat@1099: } djmoffat@1099: } djmoffat@1099: if (passed == false) djmoffat@1099: { djmoffat@1099: check_pass = false; djmoffat@1099: console.log("Continue listening to track-"+audioEngineContext.audioObjects.interfaceDOM.getPresentedId()); djmoffat@1099: error_obj.push(audioEngineContext.audioObjects.interfaceDOM.getPresentedId()); djmoffat@1099: } djmoffat@1099: } djmoffat@1099: if (check_pass == false) djmoffat@1099: { djmoffat@1099: var str_start = "You have not completely listened to fragments "; djmoffat@1099: for (var i=0; i 0) djmoffat@1099: { djmoffat@1099: aeNode.setAttribute('marker',element.marker); djmoffat@1099: } djmoffat@1099: } djmoffat@1099: var ae_metric = this.parent.document.createElement('metric'); djmoffat@1099: aeNode.appendChild(ae_metric); djmoffat@1099: this.XMLDOM.appendChild(aeNode); djmoffat@1099: } djmoffat@1099: djmoffat@1099: this.parent.root.appendChild(this.XMLDOM); djmoffat@1099: }; djmoffat@1099: this.finish = function() djmoffat@1099: { djmoffat@1099: if (this.state == 0) djmoffat@1099: { djmoffat@1099: var projectDocument = specification.projectXML; djmoffat@1099: projectDocument.setAttribute('file-name',url); djmoffat@1099: this.root.appendChild(projectDocument); djmoffat@1099: this.root.appendChild(returnDateNode()); djmoffat@1099: this.root.appendChild(interfaceContext.returnNavigator()); djmoffat@1099: } djmoffat@1099: this.state = 1; djmoffat@1099: return this.root; djmoffat@1099: }; djmoffat@1099: }