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