# HG changeset patch # User Nicholas Jillings # Date 1432897628 -3600 # Node ID ffeef0ac7a5f758355d10d4c0298781b5fa0c8f8 # Parent dea30ed2b549aa35924c5ff7439d355e1e3afae6# Parent 355631b936d38b03b9f3307bb19517e43a563be2 Merge from the default branch diff -r 355631b936d3 -r ffeef0ac7a5f ape.css --- a/ape.css Fri May 01 16:18:25 2015 +0100 +++ b/ape.css Fri May 29 12:07:08 2015 +0100 @@ -14,6 +14,11 @@ div.pageTitle { width: auto; height: 20px; + margin-top: 20px; +} + +div.pageTitle span{ + font-size: 1.5em; } div.testHalt { @@ -59,7 +64,7 @@ } div.track-slider { - /* Specify any strcture for the slider objects */ + /* Specify any structure for the slider objects */ position: absolute; height: inherit; width: 12px; @@ -103,3 +108,8 @@ border-style: solid; background-color: #fff; } + +textarea.trackComment { + width: 618px; + margin-right:15px; +} diff -r 355631b936d3 -r ffeef0ac7a5f ape.js --- a/ape.js Fri May 01 16:18:25 2015 +0100 +++ b/ape.js Fri May 29 12:07:08 2015 +0100 @@ -3,7 +3,6 @@ * Create the APE interface */ -var currentState; // Keep track of the current state (pre/post test, which test, final test? first test?) // preTest - In preTest state // testRun-ID - In test running, test Id number at the end 'testRun-2' // testRunPost-ID - Post test of test ID @@ -23,6 +22,7 @@ // The injection point into the HTML page var insertPoint = document.getElementById("topLevelBody"); var testContent = document.createElement('div'); + testContent.id = 'testContent'; @@ -31,26 +31,50 @@ var xmlSetup = xmlDoc.find('setup'); // Should put in an error function here incase of malprocessed or malformed XML + // Create pre and post test questions + + var preTest = xmlSetup.find('PreTest'); + var postTest = xmlSetup.find('PostTest'); + preTest = preTest[0]; + postTest = postTest[0]; + + if (preTest == undefined) {preTest = document.createElement("preTest");} + if (postTest == undefined){postTest= document.createElement("postTest");} + + testState.stateMap.push(preTest); + // Extract the different test XML DOM trees var audioHolders = xmlDoc.find('audioHolder'); + var testXMLSetups = []; audioHolders.each(function(index,element) { var repeatN = element.attributes['repeatCount'].value; for (var r=0; r<=repeatN; r++) { - testXMLSetups[testXMLSetups.length] = element; + testXMLSetups.push(element); } }); // New check if we need to randomise the test order var randomise = xmlSetup[0].attributes['randomiseOrder']; if (randomise != undefined) { - randomise = Boolean(randomise.value); + if (randomise.value === 'true'){ + randomise = true; + } else { + randomise = false; + } } else { randomise = false; } + if (randomise) { testXMLSetups = randomiseOrder(testXMLSetups); } + + $(testXMLSetups).each(function(index,elem){ + testState.stateMap.push(elem); + }) + + testState.stateMap.push(postTest); // Obtain the metrics enabled var metricNode = xmlSetup.find('Metric'); @@ -109,6 +133,7 @@ var id = this.data; this.data = -1; var position = convSliderPosToRate(id); + console.log('slider ' + id + ': '+ position + ' (' + time + ')'); // DEBUG/SAFETY: show position and slider id if (audioEngineContext.timer.testStarted) { audioEngineContext.audioObjects[id].metric.moved(time,position); @@ -127,6 +152,7 @@ this.lastClicked = id; audioEngineContext.audioObjects[id].metric.listening(time); } + console.log('slider ' + id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id }; // Create the top div for the Title element @@ -166,17 +192,16 @@ // Create playback start/stop points var playback = document.createElement("button"); - playback.innerHTML = 'Start'; + playback.innerHTML = 'Stop'; playback.id = 'playback-button'; // onclick function. Check if it is playing or not, call the correct function in the // audioEngine, change the button text to reflect the next state. playback.onclick = function() { - if (audioEngineContext.status == 0) { - audioEngineContext.play(); + if (audioEngineContext.status == 1) { + audioEngineContext.stop(); this.innerHTML = 'Stop'; - } else { - audioEngineContext.stop(); - this.innerHTML = 'Start'; + var time = audioEngineContext.timer.getTestTime(); + console.log('Stopped at ' + time); // DEBUG/SAFETY } }; // Create Submit (save) button @@ -195,14 +220,22 @@ var sliderBox = document.createElement('div'); sliderBox.className = 'sliderCanvasDiv'; sliderBox.id = 'sliderCanvasHolder'; - sliderBox.align = 'center'; // Create the slider box to hold the slider elements var canvas = document.createElement('div'); canvas.id = 'slider'; + canvas.align = "left"; + canvas.addEventListener('dragover',function(event){ + event.preventDefault(); + return false; + },false); + var sliderMargin = document.createAttribute('marginsize'); + sliderMargin.nodeValue = 42; // Set default margins to 42px either side // Must have a known EXACT width, as this is used later to determine the ratings - canvas.style.width = width - 100 +"px"; - canvas.align = "left"; + var w = (Number(sliderMargin.nodeValue)+8)*2; + canvas.style.width = width - w +"px"; + canvas.style.marginLeft = sliderMargin.nodeValue +'px'; + canvas.setAttributeNode(sliderMargin); sliderBox.appendChild(canvas); // Create the div to hold any scale objects @@ -219,34 +252,8 @@ testContent.style.zIndex = 1; insertPoint.innerHTML = null; // Clear the current schema - // Create pre and post test questions - var blank = document.createElement('div'); - blank.className = 'testHalt'; - - var popupHolder = document.createElement('div'); - popupHolder.id = 'popupHolder'; - popupHolder.className = 'popupHolder'; - popupHolder.style.position = 'absolute'; - popupHolder.style.left = (window.innerWidth/2)-250 + 'px'; - popupHolder.style.top = (window.innerHeight/2)-125 + 'px'; - insertPoint.appendChild(popupHolder); - insertPoint.appendChild(blank); - hidePopup(); - - var preTest = xmlSetup.find('PreTest'); - var postTest = xmlSetup.find('PostTest'); - preTest = preTest[0]; - postTest = postTest[0]; - currentState = 'preTest'; - // Create Pre-Test Box - if (preTest != undefined && preTest.children.length >= 1) - { - showPopup(); - preTestPopupStart(preTest); - } - // Inject into HTML testContent.appendChild(title); // Insert the title testContent.appendChild(pagetitle); @@ -256,13 +263,19 @@ insertPoint.appendChild(testContent); // Load the full interface + testState.initialise(); + testState.advanceState(); + testWaitIndicator(); } -function loadTest(id) +function loadTest(textXML) { - // Used to load a specific test page - var textXML = testXMLSetups[id]; + + // Reset audioEngineContext.Metric globals for new test + audioEngineContext.newTestPage(); + + var id = textXML.id; var feedbackHolder = document.getElementById('feedbackHolder'); var canvas = document.getElementById('slider'); @@ -277,17 +290,19 @@ document.getElementById('pageTitle').textContent = titleNode[0].textContent; } var positionScale = canvas.style.width.substr(0,canvas.style.width.length-2); - var offset = 50-8; // Half the offset of the slider (window width -100) minus the body padding of 8 - // TODO: AUTOMATE ABOVE!! + var offset = Number(document.getElementById('slider').attributes['marginsize'].value); var scale = document.getElementById('sliderScaleHolder'); scale.innerHTML = null; interfaceObj.find('scale').each(function(index,scaleObj){ + var value = document.createAttribute('value'); var position = Number(scaleObj.attributes['position'].value)*0.01; + value.nodeValue = position; var pixelPosition = (position*positionScale)+offset; var scaleDOM = document.createElement('span'); scaleDOM.textContent = scaleObj.textContent; scale.appendChild(scaleDOM); scaleDOM.style.left = Math.floor((pixelPosition-($(scaleDOM).width()/2)))+'px'; + scaleDOM.setAttributeNode(value); }); // Extract the hostURL attribute. If not set, create an empty string. @@ -331,71 +346,19 @@ loopPlayback = false; } audioEngineContext.loopPlayback = loopPlayback; - // Create AudioEngine bindings for playback if (loopPlayback) { - audioEngineContext.play = function() { - // Send play command to all playback buffers for synchronised start - // Also start timer callbacks to detect if playback has finished - if (this.status == 0) { - this.timer.startTest(); - // First get current clock - var timer = audioContext.currentTime; - // Add 3 seconds - timer += 3.0; - // Send play to all tracks - for (var i=0; i 0) - { - currentState = 'testRunPre-'+id; - preTestPopupStart(preTest); - showPopup(); - } else { - currentState = 'testRun-'+id; - } } -function preTestPopupStart(preTest) -{ - var popupHolder = document.getElementById('popupHolder'); - popupHolder.innerHTML = null; - // Parse the first box - var preTestOption = document.createElement('div'); - preTestOption.id = 'preTest'; - preTestOption.style.marginTop = '25px'; - preTestOption.align = "center"; - var child = preTest.children[0]; - if (child.nodeName == 'statement') - { - preTestOption.innerHTML = ''+child.innerHTML+''; - } else if (child.nodeName == 'question') - { - var questionId = child.attributes['id'].value; - var textHold = document.createElement('span'); - textHold.innerHTML = child.innerHTML; - textHold.id = questionId + 'response'; - var textEnter = document.createElement('textarea'); - preTestOption.appendChild(textHold); - preTestOption.appendChild(textEnter); - } - var nextButton = document.createElement('button'); - nextButton.className = 'popupButton'; - nextButton.value = '0'; - nextButton.innerHTML = 'Next'; - nextButton.onclick = popupButtonClick; - - popupHolder.appendChild(preTestOption); - popupHolder.appendChild(nextButton); -} - -function popupButtonClick() -{ - // Global call from the 'Next' button click - if (currentState == 'preTest') - { - // At the start of the preTest routine! - var xmlTree = projectXML.find('setup'); - var preTest = xmlTree.find('PreTest')[0]; - this.value = preTestButtonClick(preTest,this.value); - } else if (currentState.substr(0,10) == 'testRunPre') - { - //Specific test pre-test - var testId = currentState.substr(11,currentState.length-10); - var preTest = $(testXMLSetups[testId]).find('PreTest')[0]; - this.value = preTestButtonClick(preTest,this.value); - } else if (currentState.substr(0,11) == 'testRunPost') - { - // Specific test post-test - var testId = currentState.substr(12,currentState.length-11); - var preTest = $(testXMLSetups[testId]).find('PostTest')[0]; - this.value = preTestButtonClick(preTest,this.value); - } else if (currentState == 'postTest') - { - // At the end of the test, running global post test - var xmlTree = projectXML.find('setup'); - var PostTest = xmlTree.find('PostTest')[0]; - this.value = preTestButtonClick(PostTest,this.value); - } -} - -function preTestButtonClick(preTest,index) -{ - // Called on click of pre-test button - // Need to find and parse preTest again! - var preTestOption = document.getElementById('preTest'); - // Check if current state is a question! - if (preTest.children[index].nodeName == 'question') { - var questionId = preTest.children[index].attributes['id'].value; - var questionHold = document.createElement('comment'); - var questionResponse = document.getElementById(questionId + 'response'); - var mandatory = preTest.children[index].attributes['mandatory']; - if (mandatory != undefined){ - if (mandatory.value == 'true') {mandatory = true;} - else {mandatory = false;} - } else {mandatory = false;} - if (mandatory == true && questionResponse.value.length == 0) { - return index; - } - questionHold.id = questionId; - questionHold.innerHTML = questionResponse.value; - postPopupResponse(questionHold); - } - index++; - if (index < preTest.children.length) - { - // More to process - var child = preTest.children[index]; - if (child.nodeName == 'statement') - { - preTestOption.innerHTML = ''+child.innerHTML+''; - } else if (child.nodeName == 'question') - { - var textHold = document.createElement('span'); - textHold.innerHTML = child.innerHTML; - var textEnter = document.createElement('textarea'); - textEnter.id = child.attributes['id'].value + 'response'; - var br = document.createElement('br'); - preTestOption.innerHTML = null; - preTestOption.appendChild(textHold); - preTestOption.appendChild(br); - preTestOption.appendChild(textEnter); - } - } else { - // Time to clear - preTestOption.innerHTML = null; - if (currentState != 'postTest') { - hidePopup(); - // Progress the state! - advanceState(); - } else { - a = createProjectSave(projectReturn); - preTestOption.appendChild(a); - } - } - return index; -} - -function postPopupResponse(response) -{ - if (currentState == 'preTest') { - preTestQuestions.appendChild(response); - } else if (currentState == 'postTest') { - postTestQuestions.appendChild(response); - } else { - // Inside a specific test - if (currentState.substr(0,10) == 'testRunPre') { - // Pre Test - var store = $(currentTestHolder).find('preTest'); - } else { - // Post Test - var store = $(currentTestHolder).find('postTest'); - } - store[0].appendChild(response); - } -} - -function showPopup() -{ - var popupHolder = document.getElementById('popupHolder'); - popupHolder.style.zIndex = 3; - popupHolder.style.visibility = 'visible'; - var blank = document.getElementsByClassName('testHalt')[0]; - blank.style.zIndex = 2; - blank.style.visibility = 'visible'; -} - -function hidePopup() -{ - var popupHolder = document.getElementById('popupHolder'); - popupHolder.style.zIndex = -1; - popupHolder.style.visibility = 'hidden'; - var blank = document.getElementsByClassName('testHalt')[0]; - blank.style.zIndex = -2; - blank.style.visibility = 'hidden'; -} function dragEnd(ev) { // Function call when a div has been dropped var slider = document.getElementById('slider'); + var marginSize = Number(slider.attributes['marginsize'].value); var w = slider.style.width; w = Number(w.substr(0,w.length-2)); var x = ev.x; - if (x >= 42 && x < w+42) { + if (x >= marginSize && x < w+marginSize) { this.style.left = (x)+'px'; } else { - if (x<42) { - this.style.left = '42px'; + if (x 0) - { - currentState = 'testRunPost-'+testId; - showPopup(); - preTestPopupStart(postXML); - } - else { - - - // No post tests, check if we have another test to perform instead - - } - } - console.log(currentState); -} - -function testEnded(testId) -{ - pageXMLSave(testId); - if (testXMLSetups.length-1 > testId) - { - // Yes we have another test to perform - testId = (Number(testId)+1); - currentState = 'testRun-'+testId; - loadTest(testId); - } else { - console.log('Testing Completed!'); - currentState = 'postTest'; - // Check for any post tests - var xmlSetup = projectXML.find('setup'); - var postTest = xmlSetup.find('PostTest')[0]; - showPopup(); - preTestPopupStart(postTest); - } -} - -function buttonSubmitClick() -{ - if (audioEngineContext.status == 1) { - var playback = document.getElementById('playback-button'); - playback.click(); - // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options - } else - { - if (audioEngineContext.timer.testStarted == false) - { - alert('You have not started the test! Please press start to begin the test!'); - return; - } - } - if (currentState.substr(0,7) == 'testRun') - { - audioEngineContext.timer.stopTest(); - advanceState(); - } + hasBeenPlayed = audioEngineContext.checkAllPlayed(); + if (hasBeenPlayed.length == 0) { + if (audioEngineContext.status == 1) { + var playback = document.getElementById('playback-button'); + playback.click(); + // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options + } else + { + if (audioEngineContext.timer.testStarted == false) + { + alert('You have not started the test! Please press start to begin the test!'); + return; + } + } + testState.advanceState(); + } else // if a fragment has not been played yet + { + str = ""; + if (hasBeenPlayed.length > 1) { + for (var i=0; i 0) { + if (node.nodeName == 'preTest' || node.nodeName == 'PreTest') { + this.responses = document.createElement('PreTest'); + } else if (node.nodeName == 'postTest' || node.nodeName == 'PostTest') { + this.responses = document.createElement('PostTest'); + } else { + console.log ('WARNING - popup node neither pre or post!'); + this.responses = document.createElement('responses'); + } + this.currentIndex = 0; + this.showPopup(); + this.postNode(); + } + } + + this.buttonClicked = function() { + // Each time the popup button is clicked! + var node = this.popupOptions[this.currentIndex]; + if (node.nodeName == 'question') { + // Must extract the question data + var mandatory = node.attributes['mandatory']; + if (mandatory == undefined) { + mandatory = false; + } else { + if (mandatory.value == 'true'){mandatory = true;} + else {mandatory = false;} + } + var textArea = $(popup.popupContent).find('textarea')[0]; + if (mandatory == true && textArea.value.length == 0) { + alert('This question is mandatory'); + return; + } else { + // Save the text content + var hold = document.createElement('comment'); + hold.id = node.attributes['id'].value; + hold.innerHTML = textArea.value; + console.log("Question: "+ node.textContent); + console.log("Question Response: "+ textArea.value); + this.responses.appendChild(hold); + } + } + this.currentIndex++; + if (this.currentIndex < this.popupOptions.length) { + this.postNode(); + } else { + // Reached the end of the popupOptions + this.hidePopup(); + if (this.responses.nodeName == testState.stateResults[testState.stateIndex].nodeName) { + testState.stateResults[testState.stateIndex] = this.responses; + } else { + testState.stateResults[testState.stateIndex].appendChild(this.responses); + } + advanceState(); + } + } +} + +function advanceState() +{ + // Just for complete clarity + testState.advanceState(); +} + +function stateMachine() +{ + // Object prototype for tracking and managing the test state + this.stateMap = []; + this.stateIndex = null; + this.currentStateMap = []; + this.currentIndex = null; + this.currentTestId = 0; + this.stateResults = []; + this.timerCallBackHolders = null; + this.initialise = function(){ + if (this.stateMap.length > 0) { + if(this.stateIndex != null) { + console.log('NOTE - State already initialise'); + } + this.stateIndex = -1; + var that = this; + for (var id=0; id= this.stateMap.length) { + console.log('Test Completed'); + createProjectSave(projectReturn); + } else { + this.currentStateMap = this.stateMap[this.stateIndex]; + if (this.currentStateMap.nodeName == "audioHolder") { + console.log('Loading test page'); + loadTest(this.currentStateMap); + this.initialiseInnerState(this.currentStateMap); + } else if (this.currentStateMap.nodeName == "PreTest" || this.currentStateMap.nodeName == "PostTest") { + if (this.currentStateMap.childElementCount >= 1) { + popup.initState(this.currentStateMap); + } else { + this.advanceState(); + } + } else { + this.advanceState(); + } + } + } else { + this.advanceInnerState(); + } + }; + + this.testPageCompleted = function(store, testXML, testId) { + // Function called each time a test page has been completed + // Can be used to over-rule default behaviour + + pageXMLSave(store, testXML, testId); + } + + this.initialiseInnerState = function(testXML) { + // Parses the received testXML for pre and post test options + this.currentStateMap = []; + var preTest = $(testXML).find('PreTest')[0]; + var postTest = $(testXML).find('PostTest')[0]; + if (preTest == undefined) {preTest = document.createElement("preTest");} + if (postTest == undefined){postTest= document.createElement("postTest");} + this.currentStateMap.push(preTest); + this.currentStateMap.push(testXML); + this.currentStateMap.push(postTest); + this.currentIndex = -1; + this.advanceInnerState(); + } + + this.advanceInnerState = function() { + this.currentIndex++; + if (this.currentIndex >= this.currentStateMap.length) { + this.currentIndex = null; + this.currentStateMap = this.stateMap[this.stateIndex]; + this.advanceState(); + } else { + if (this.currentStateMap[this.currentIndex].nodeName == "audioHolder") { + console.log("Loading test page"+this.currentTestId); + } else if (this.currentStateMap[this.currentIndex].nodeName == "PreTest") { + popup.initState(this.currentStateMap[this.currentIndex]); + } else if (this.currentStateMap[this.currentIndex].nodeName == "PostTest") { + popup.initState(this.currentStateMap[this.currentIndex]); + } else { + this.advanceInnerState(); + } + } + } + + this.previousState = function(){}; +} + +function testEnded(testId) +{ + pageXMLSave(testId); + if (testXMLSetups.length-1 > testId) + { + // Yes we have another test to perform + testId = (Number(testId)+1); + currentState = 'testRun-'+testId; + loadTest(testId); + } else { + console.log('Testing Completed!'); + currentState = 'postTest'; + // Check for any post tests + var xmlSetup = projectXML.find('setup'); + var postTest = xmlSetup.find('PostTest')[0]; + popup.initState(postTest); + } +} + function loadProjectSpec(url) { // Load the project document from the given URL, decode the XML and instruct audioEngine to get audio data // If url is null, request client to upload project XML document @@ -68,6 +343,9 @@ document.getElementsByTagName("head")[0].appendChild(css); } document.getElementsByTagName("head")[0].appendChild(interfaceJS); + + // Define window callbacks for interface + window.onresize = function(event){resizeWindow(event);}; } function createProjectSave(destURL) { @@ -75,10 +353,10 @@ // If destURL is null then download XML in client // Now time to render file locally var xmlDoc = interfaceXMLSave(); + var parent = document.createElement("div"); + parent.appendChild(xmlDoc); + var file = [parent.innerHTML]; if (destURL == "null" || destURL == undefined) { - var parent = document.createElement("div"); - parent.appendChild(xmlDoc); - var file = [parent.innerHTML]; var bb = new Blob(file,{type : 'application/xml'}); var dnlk = window.URL.createObjectURL(bb); var a = document.createElement("a"); @@ -89,10 +367,35 @@ var submitDiv = document.getElementById('download-point'); submitDiv.appendChild(a); + popup.showPopup(); + popup.popupContent.innerHTML = null; + popup.popupContent.appendChild(submitDiv) + } else { + var xmlhttp = new XMLHttpRequest; + xmlhttp.open("POST",destURL,true); + xmlhttp.setRequestHeader('Content-Type', 'text/xml'); + xmlhttp.onerror = function(){ + console.log('Error saving file to server! Presenting download locally'); + createProjectSave(null); + }; + xmlhttp.send(file); } return submitDiv; } +// Only other global function which must be defined in the interface class. Determines how to create the XML document. +function interfaceXMLSave(){ + // Create the XML string to be exported with results + var xmlDoc = document.createElement("BrowserEvaluationResult"); + xmlDoc.appendChild(returnDateNode()); + for (var i=0; i + // DD/MM/YY + // + // + var dateTime = new Date(); + var year = document.createAttribute('year'); + var month = document.createAttribute('month'); + var day = document.createAttribute('day'); + var hour = document.createAttribute('hour'); + var minute = document.createAttribute('minute'); + var secs = document.createAttribute('secs'); + + year.nodeValue = dateTime.getFullYear(); + month.nodeValue = dateTime.getMonth()+1; + day.nodeValue = dateTime.getDate(); + hour.nodeValue = dateTime.getHours(); + minute.nodeValue = dateTime.getMinutes(); + secs.nodeValue = dateTime.getSeconds(); + + var hold = document.createElement("datetime"); + var date = document.createElement("date"); + date.textContent = year.nodeValue+'/'+month.nodeValue+'/'+day.nodeValue; + var time = document.createElement("time"); + time.textContent = hour.nodeValue+':'+minute.nodeValue+':'+secs.nodeValue; + + date.setAttributeNode(year); + date.setAttributeNode(month); + date.setAttributeNode(day); + time.setAttributeNode(hour); + time.setAttributeNode(minute); + time.setAttributeNode(secs); + + hold.appendChild(date); + hold.appendChild(time); + return hold + +} + +function testWaitIndicator() { + var hold = document.createElement("div"); + hold.id = "testWaitIndicator"; + hold.style.position = "absolute"; + hold.style.left = "100px"; + hold.style.top = "10px"; + hold.style.width = "500px"; + hold.style.height = "100px"; + hold.style.padding = "20px"; + hold.style.backgroundColor = "rgb(100,200,200)"; + hold.style.visibility = "hidden"; + var span = document.createElement("span"); + span.textContent = "Please wait! Elements still loading"; + hold.appendChild(span); + var body = document.getElementsByTagName('body')[0]; + body.appendChild(hold); +} + +var hidetestwait = null; diff -r 355631b936d3 -r ffeef0ac7a5f example_eval/project.xml --- a/example_eval/project.xml Fri May 01 16:18:25 2015 +0100 +++ b/example_eval/project.xml Fri May 29 12:07:08 2015 +0100 @@ -1,13 +1,13 @@ - + + Please enter your location. Please listen to all mixes - Please enter your listening location + Please enter your name. Thank you for taking this listening test. - Please enter your name. testTimer @@ -18,7 +18,7 @@ elementFlagMoved - + Example Test Question Min @@ -38,12 +38,13 @@ --> What is your mixing experiance + - Please take a break before the next test - How did you find the test - + Please enter the genre + \ No newline at end of file diff -r 355631b936d3 -r ffeef0ac7a5f index.html --- a/index.html Fri May 01 16:18:25 2015 +0100 +++ b/index.html Fri May 29 12:07:08 2015 +0100 @@ -16,7 +16,8 @@ - + +