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