BrechtDeMan@938: /** BrechtDeMan@938: * ape.js BrechtDeMan@938: * Create the APE interface BrechtDeMan@938: */ BrechtDeMan@938: BrechtDeMan@938: // preTest - In preTest state BrechtDeMan@938: // testRun-ID - In test running, test Id number at the end 'testRun-2' BrechtDeMan@938: // testRunPost-ID - Post test of test ID BrechtDeMan@938: // testRunPre-ID - Pre-test of test ID BrechtDeMan@938: // postTest - End of test, final submission! BrechtDeMan@938: BrechtDeMan@938: BrechtDeMan@938: // Once this is loaded and parsed, begin execution BrechtDeMan@938: loadInterface(projectXML); BrechtDeMan@938: BrechtDeMan@938: function loadInterface(xmlDoc) { BrechtDeMan@938: BrechtDeMan@938: // Get the dimensions of the screen available to the page BrechtDeMan@938: var width = window.innerWidth; BrechtDeMan@938: var height = window.innerHeight; BrechtDeMan@938: BrechtDeMan@938: // The injection point into the HTML page BrechtDeMan@938: var insertPoint = document.getElementById("topLevelBody"); BrechtDeMan@938: var testContent = document.createElement('div'); BrechtDeMan@938: testContent.id = 'testContent'; BrechtDeMan@938: BrechtDeMan@938: BrechtDeMan@938: // Decode parts of the xmlDoc that are needed BrechtDeMan@938: // xmlDoc MUST already be parsed by jQuery! BrechtDeMan@938: var xmlSetup = xmlDoc.find('setup'); BrechtDeMan@938: // Should put in an error function here incase of malprocessed or malformed XML BrechtDeMan@938: nicholas@964: // Create pre and post test questions nicholas@964: nicholas@964: var preTest = xmlSetup.find('PreTest'); nicholas@964: var postTest = xmlSetup.find('PostTest'); nicholas@964: preTest = preTest[0]; nicholas@964: postTest = postTest[0]; nicholas@964: nicholas@964: if (preTest == undefined) {preTest = document.createElement("preTest");} nicholas@964: if (postTest == undefined){postTest= document.createElement("postTest");} nicholas@964: nicholas@964: testState.stateMap.push(preTest); nicholas@964: BrechtDeMan@938: // Extract the different test XML DOM trees BrechtDeMan@938: var audioHolders = xmlDoc.find('audioHolder'); nicholas@964: var testXMLSetups = []; BrechtDeMan@938: audioHolders.each(function(index,element) { BrechtDeMan@938: var repeatN = element.attributes['repeatCount'].value; BrechtDeMan@938: for (var r=0; r<=repeatN; r++) { nicholas@964: testXMLSetups.push(element); BrechtDeMan@938: } BrechtDeMan@938: }); BrechtDeMan@938: BrechtDeMan@938: // New check if we need to randomise the test order BrechtDeMan@938: var randomise = xmlSetup[0].attributes['randomiseOrder']; BrechtDeMan@938: if (randomise != undefined) { nicholas@946: if (randomise.value === 'true'){ nicholas@946: randomise = true; nicholas@946: } else { nicholas@946: randomise = false; nicholas@946: } BrechtDeMan@938: } else { BrechtDeMan@938: randomise = false; BrechtDeMan@938: } nicholas@946: BrechtDeMan@938: if (randomise) BrechtDeMan@938: { BrechtDeMan@938: testXMLSetups = randomiseOrder(testXMLSetups); BrechtDeMan@938: } nicholas@964: nicholas@964: $(testXMLSetups).each(function(index,elem){ nicholas@964: testState.stateMap.push(elem); nicholas@964: }) nicholas@964: nicholas@964: testState.stateMap.push(postTest); BrechtDeMan@938: BrechtDeMan@938: // Obtain the metrics enabled BrechtDeMan@938: var metricNode = xmlSetup.find('Metric'); BrechtDeMan@938: var metricNode = metricNode.find('metricEnable'); BrechtDeMan@938: metricNode.each(function(index,node){ BrechtDeMan@938: var enabled = node.textContent; BrechtDeMan@938: switch(enabled) BrechtDeMan@938: { BrechtDeMan@938: case 'testTimer': BrechtDeMan@938: sessionMetrics.prototype.enableTestTimer = true; BrechtDeMan@938: break; BrechtDeMan@938: case 'elementTimer': BrechtDeMan@938: sessionMetrics.prototype.enableElementTimer = true; BrechtDeMan@938: break; BrechtDeMan@938: case 'elementTracker': BrechtDeMan@938: sessionMetrics.prototype.enableElementTracker = true; BrechtDeMan@938: break; BrechtDeMan@938: case 'elementInitalPosition': BrechtDeMan@938: sessionMetrics.prototype.enableElementInitialPosition = true; BrechtDeMan@938: break; BrechtDeMan@938: case 'elementFlagListenedTo': BrechtDeMan@938: sessionMetrics.prototype.enableFlagListenedTo = true; BrechtDeMan@938: break; BrechtDeMan@938: case 'elementFlagMoved': BrechtDeMan@938: sessionMetrics.prototype.enableFlagMoved = true; BrechtDeMan@938: break; BrechtDeMan@938: case 'elementFlagComments': BrechtDeMan@938: sessionMetrics.prototype.enableFlagComments = true; BrechtDeMan@938: break; BrechtDeMan@938: } BrechtDeMan@938: }); BrechtDeMan@938: BrechtDeMan@938: // Create APE specific metric functions BrechtDeMan@938: audioEngineContext.metric.initialiseTest = function() BrechtDeMan@938: { BrechtDeMan@938: var sliders = document.getElementsByClassName('track-slider'); BrechtDeMan@938: for (var i=0; i= 0) BrechtDeMan@938: { BrechtDeMan@938: audioEngineContext.audioObjects[this.lastClicked].metric.listening(time); BrechtDeMan@938: } BrechtDeMan@938: this.lastClicked = id; BrechtDeMan@938: audioEngineContext.audioObjects[id].metric.listening(time); BrechtDeMan@938: } BrechtDeMan@936: console.log('slider ' + id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id BrechtDeMan@938: }; BrechtDeMan@938: BrechtDeMan@938: // Create the top div for the Title element BrechtDeMan@938: var titleAttr = xmlSetup[0].attributes['title']; BrechtDeMan@938: var title = document.createElement('div'); BrechtDeMan@938: title.className = "title"; BrechtDeMan@938: title.align = "center"; BrechtDeMan@938: var titleSpan = document.createElement('span'); BrechtDeMan@938: BrechtDeMan@938: // Set title to that defined in XML, else set to default BrechtDeMan@938: if (titleAttr != undefined) { BrechtDeMan@938: titleSpan.innerHTML = titleAttr.value; BrechtDeMan@938: } else { BrechtDeMan@938: titleSpan.innerHTML = 'Listening test'; BrechtDeMan@938: } BrechtDeMan@938: // Insert the titleSpan element into the title div element. BrechtDeMan@938: title.appendChild(titleSpan); BrechtDeMan@938: BrechtDeMan@938: var pagetitle = document.createElement('div'); BrechtDeMan@938: pagetitle.className = "pageTitle"; BrechtDeMan@938: pagetitle.align = "center"; BrechtDeMan@938: var titleSpan = document.createElement('span'); BrechtDeMan@938: titleSpan.id = "pageTitle"; BrechtDeMan@938: pagetitle.appendChild(titleSpan); BrechtDeMan@938: BrechtDeMan@938: // Store the return URL path in global projectReturn BrechtDeMan@938: projectReturn = xmlSetup[0].attributes['projectReturn'].value; BrechtDeMan@938: BrechtDeMan@938: // Create Interface buttons! BrechtDeMan@938: var interfaceButtons = document.createElement('div'); BrechtDeMan@938: interfaceButtons.id = 'interface-buttons'; BrechtDeMan@938: BrechtDeMan@938: // MANUAL DOWNLOAD POINT BrechtDeMan@938: // If project return is null, this MUST be specified as the location to create the download link BrechtDeMan@938: var downloadPoint = document.createElement('div'); BrechtDeMan@938: downloadPoint.id = 'download-point'; BrechtDeMan@938: BrechtDeMan@938: // Create playback start/stop points BrechtDeMan@938: var playback = document.createElement("button"); BrechtDeMan@939: playback.innerHTML = 'Stop'; BrechtDeMan@938: playback.id = 'playback-button'; BrechtDeMan@938: // onclick function. Check if it is playing or not, call the correct function in the BrechtDeMan@938: // audioEngine, change the button text to reflect the next state. BrechtDeMan@938: playback.onclick = function() { BrechtDeMan@939: if (audioEngineContext.status == 1) { BrechtDeMan@939: audioEngineContext.stop(); BrechtDeMan@938: this.innerHTML = 'Stop'; BrechtDeMan@936: var time = audioEngineContext.timer.getTestTime(); BrechtDeMan@936: console.log('Stopped at ' + time); // DEBUG/SAFETY BrechtDeMan@938: } BrechtDeMan@938: }; BrechtDeMan@938: // Create Submit (save) button BrechtDeMan@938: var submit = document.createElement("button"); BrechtDeMan@938: submit.innerHTML = 'Submit'; BrechtDeMan@938: submit.onclick = buttonSubmitClick; BrechtDeMan@938: submit.id = 'submit-button'; BrechtDeMan@938: // Append the interface buttons into the interfaceButtons object. BrechtDeMan@938: interfaceButtons.appendChild(playback); BrechtDeMan@938: interfaceButtons.appendChild(submit); BrechtDeMan@938: interfaceButtons.appendChild(downloadPoint); BrechtDeMan@938: BrechtDeMan@938: // Now create the slider and HTML5 canvas boxes BrechtDeMan@938: BrechtDeMan@938: // Create the div box to center align BrechtDeMan@938: var sliderBox = document.createElement('div'); BrechtDeMan@938: sliderBox.className = 'sliderCanvasDiv'; BrechtDeMan@938: sliderBox.id = 'sliderCanvasHolder'; BrechtDeMan@938: BrechtDeMan@938: // Create the slider box to hold the slider elements BrechtDeMan@938: var canvas = document.createElement('div'); BrechtDeMan@938: canvas.id = 'slider'; n@963: canvas.align = "left"; n@963: canvas.addEventListener('dragover',function(event){ n@963: event.preventDefault(); n@963: return false; n@963: },false); n@963: var sliderMargin = document.createAttribute('marginsize'); n@963: sliderMargin.nodeValue = 42; // Set default margins to 42px either side BrechtDeMan@938: // Must have a known EXACT width, as this is used later to determine the ratings n@963: var w = (Number(sliderMargin.nodeValue)+8)*2; n@963: canvas.style.width = width - w +"px"; n@963: canvas.style.marginLeft = sliderMargin.nodeValue +'px'; n@963: canvas.setAttributeNode(sliderMargin); BrechtDeMan@938: sliderBox.appendChild(canvas); BrechtDeMan@938: BrechtDeMan@938: // Create the div to hold any scale objects BrechtDeMan@938: var scale = document.createElement('div'); BrechtDeMan@938: scale.className = 'sliderScale'; BrechtDeMan@938: scale.id = 'sliderScaleHolder'; BrechtDeMan@938: scale.align = 'left'; BrechtDeMan@938: sliderBox.appendChild(scale); BrechtDeMan@938: BrechtDeMan@938: // Global parent for the comment boxes on the page BrechtDeMan@938: var feedbackHolder = document.createElement('div'); BrechtDeMan@938: feedbackHolder.id = 'feedbackHolder'; BrechtDeMan@938: BrechtDeMan@938: testContent.style.zIndex = 1; BrechtDeMan@938: insertPoint.innerHTML = null; // Clear the current schema BrechtDeMan@938: BrechtDeMan@938: currentState = 'preTest'; BrechtDeMan@938: BrechtDeMan@938: // Inject into HTML BrechtDeMan@938: testContent.appendChild(title); // Insert the title BrechtDeMan@938: testContent.appendChild(pagetitle); BrechtDeMan@938: testContent.appendChild(interfaceButtons); BrechtDeMan@938: testContent.appendChild(sliderBox); BrechtDeMan@938: testContent.appendChild(feedbackHolder); BrechtDeMan@938: insertPoint.appendChild(testContent); BrechtDeMan@938: BrechtDeMan@938: // Load the full interface nicholas@964: testState.initialise(); nicholas@964: testState.advanceState(); BrechtDeMan@938: } BrechtDeMan@938: nicholas@964: function loadTest(textXML) BrechtDeMan@938: { nicholas@947: nicholas@947: // Reset audioEngineContext.Metric globals for new test n@950: audioEngineContext.newTestPage(); nicholas@947: nicholas@964: var id = textXML.id; BrechtDeMan@938: BrechtDeMan@938: var feedbackHolder = document.getElementById('feedbackHolder'); BrechtDeMan@938: var canvas = document.getElementById('slider'); BrechtDeMan@938: feedbackHolder.innerHTML = null; BrechtDeMan@938: canvas.innerHTML = null; BrechtDeMan@938: BrechtDeMan@938: // Setup question title BrechtDeMan@938: var interfaceObj = $(textXML).find('interface'); BrechtDeMan@938: var titleNode = interfaceObj.find('title'); BrechtDeMan@938: if (titleNode[0] != undefined) BrechtDeMan@938: { BrechtDeMan@938: document.getElementById('pageTitle').textContent = titleNode[0].textContent; BrechtDeMan@938: } BrechtDeMan@938: var positionScale = canvas.style.width.substr(0,canvas.style.width.length-2); n@963: var offset = Number(document.getElementById('slider').attributes['marginsize'].value); BrechtDeMan@938: var scale = document.getElementById('sliderScaleHolder'); BrechtDeMan@938: scale.innerHTML = null; BrechtDeMan@938: interfaceObj.find('scale').each(function(index,scaleObj){ n@963: var value = document.createAttribute('value'); BrechtDeMan@938: var position = Number(scaleObj.attributes['position'].value)*0.01; n@963: value.nodeValue = position; BrechtDeMan@938: var pixelPosition = (position*positionScale)+offset; BrechtDeMan@938: var scaleDOM = document.createElement('span'); BrechtDeMan@938: scaleDOM.textContent = scaleObj.textContent; BrechtDeMan@938: scale.appendChild(scaleDOM); BrechtDeMan@938: scaleDOM.style.left = Math.floor((pixelPosition-($(scaleDOM).width()/2)))+'px'; n@963: scaleDOM.setAttributeNode(value); BrechtDeMan@938: }); BrechtDeMan@938: BrechtDeMan@938: // Extract the hostURL attribute. If not set, create an empty string. BrechtDeMan@938: var hostURL = textXML.attributes['hostURL']; BrechtDeMan@938: if (hostURL == undefined) { BrechtDeMan@938: hostURL = ""; BrechtDeMan@938: } else { BrechtDeMan@938: hostURL = hostURL.value; BrechtDeMan@938: } BrechtDeMan@938: // Extract the sampleRate. If set, convert the string to a Number. BrechtDeMan@938: var hostFs = textXML.attributes['sampleRate']; BrechtDeMan@938: if (hostFs != undefined) { BrechtDeMan@938: hostFs = Number(hostFs.value); BrechtDeMan@938: } BrechtDeMan@938: BrechtDeMan@938: /// CHECK FOR SAMPLE RATE COMPATIBILITY BrechtDeMan@938: if (hostFs != undefined) { BrechtDeMan@938: if (Number(hostFs) != audioContext.sampleRate) { BrechtDeMan@938: var errStr = 'Sample rates do not match! Requested '+Number(hostFs)+', got '+audioContext.sampleRate+'. Please set the sample rate to match before completing this test.'; BrechtDeMan@938: alert(errStr); BrechtDeMan@938: return; BrechtDeMan@938: } BrechtDeMan@938: } BrechtDeMan@938: BrechtDeMan@938: var commentShow = textXML.attributes['elementComments']; BrechtDeMan@938: if (commentShow != undefined) { BrechtDeMan@938: if (commentShow.value == 'false') {commentShow = false;} BrechtDeMan@938: else {commentShow = true;} BrechtDeMan@938: } else {commentShow = true;} BrechtDeMan@938: BrechtDeMan@938: var loopPlayback = textXML.attributes['loop']; BrechtDeMan@938: if (loopPlayback != undefined) BrechtDeMan@938: { BrechtDeMan@938: loopPlayback = loopPlayback.value; BrechtDeMan@938: if (loopPlayback == 'true') { BrechtDeMan@938: loopPlayback = true; BrechtDeMan@938: } else { BrechtDeMan@938: loopPlayback = false; BrechtDeMan@938: } BrechtDeMan@938: } else { BrechtDeMan@938: loopPlayback = false; BrechtDeMan@938: } BrechtDeMan@938: audioEngineContext.loopPlayback = loopPlayback; nicholas@947: loopPlayback = false; BrechtDeMan@938: // Create AudioEngine bindings for playback BrechtDeMan@938: if (loopPlayback) { BrechtDeMan@938: audioEngineContext.selectedTrack = function(id) { BrechtDeMan@938: for (var i=0; i'; BrechtDeMan@938: trackSliderObj.draggable = true; BrechtDeMan@938: trackSliderObj.ondragend = dragEnd; BrechtDeMan@938: trackSliderObj.ondragstart = function() BrechtDeMan@938: { BrechtDeMan@938: var id = Number(this.id.substr(13,2)); // Maximum theoretical tracks is 99! BrechtDeMan@938: audioEngineContext.metric.sliderMoveStart(id); BrechtDeMan@938: }; BrechtDeMan@938: BrechtDeMan@938: // Onclick, switch playback to that track BrechtDeMan@938: trackSliderObj.onclick = function() { nicholas@945: // Start the test on first click, that way timings are more accurate. nicholas@945: audioEngineContext.play(); BrechtDeMan@938: // Get the track ID from the object ID BrechtDeMan@938: var id = Number(this.id.substr(13,2)); // Maximum theoretical tracks is 99! nicholas@947: //audioEngineContext.metric.sliderPlayed(id); BrechtDeMan@938: audioEngineContext.selectedTrack(id); BrechtDeMan@938: // Currently playing track red, rest green BrechtDeMan@938: document.getElementById('track-slider-'+index).style.backgroundColor = "#FF0000"; BrechtDeMan@938: for (var i = 0; i<$(currentTrackOrder).length; i++) BrechtDeMan@938: { BrechtDeMan@940: if (i!=index) // Make all other sliders green BrechtDeMan@938: { BrechtDeMan@938: document.getElementById('track-slider-'+i).style.backgroundColor = "rgb(100,200,100)"; BrechtDeMan@938: } BrechtDeMan@938: BrechtDeMan@938: } BrechtDeMan@938: }; BrechtDeMan@938: BrechtDeMan@938: canvas.appendChild(trackSliderObj); BrechtDeMan@940: BrechtDeMan@938: }); BrechtDeMan@938: BrechtDeMan@938: // Append any commentQuestion boxes BrechtDeMan@938: var commentQuestions = $(textXML).find('CommentQuestion'); BrechtDeMan@938: $(commentQuestions).each(function(index,element) { BrechtDeMan@938: // Create document objects to hold the comment boxes BrechtDeMan@938: var trackComment = document.createElement('div'); BrechtDeMan@938: trackComment.className = 'comment-div commentQuestion'; BrechtDeMan@938: trackComment.id = element.attributes['id'].value; BrechtDeMan@938: // Create a string next to each comment asking for a comment BrechtDeMan@938: var trackString = document.createElement('span'); BrechtDeMan@938: trackString.innerHTML = element.textContent; BrechtDeMan@938: // Create the HTML5 comment box 'textarea' BrechtDeMan@938: var trackCommentBox = document.createElement('textarea'); BrechtDeMan@938: trackCommentBox.rows = '4'; BrechtDeMan@938: trackCommentBox.cols = '100'; BrechtDeMan@938: trackCommentBox.name = 'commentQuestion'+index; BrechtDeMan@938: trackCommentBox.className = 'trackComment'; BrechtDeMan@938: var br = document.createElement('br'); BrechtDeMan@938: // Add to the holder. BrechtDeMan@938: trackComment.appendChild(trackString); BrechtDeMan@938: trackComment.appendChild(br); BrechtDeMan@938: trackComment.appendChild(trackCommentBox); BrechtDeMan@938: feedbackHolder.appendChild(trackComment); BrechtDeMan@938: }); BrechtDeMan@938: } BrechtDeMan@938: nicholas@955: BrechtDeMan@938: function dragEnd(ev) { BrechtDeMan@938: // Function call when a div has been dropped BrechtDeMan@938: var slider = document.getElementById('slider'); n@963: var marginSize = Number(slider.attributes['marginsize'].value); BrechtDeMan@938: var w = slider.style.width; BrechtDeMan@938: w = Number(w.substr(0,w.length-2)); n@963: var x = ev.x - ev.view.screenX; n@963: if (x >= marginSize && x < w+marginSize) { BrechtDeMan@938: this.style.left = (x)+'px'; BrechtDeMan@938: } else { n@963: if (x 1) { nicholas@944: for (var i=0; i