Mercurial > hg > webaudioevaluationtool
view core.js @ 951:55bf2500f278
Popup now in core.js, universal to interface.
author | Nicholas Jillings <nicholas.jillings@eecs.qmul.ac.uk> |
---|---|
date | Tue, 26 May 2015 11:40:17 +0100 |
parents | 05e7edb032b9 |
children | c3a66f0b33cc |
line wrap: on
line source
/** * core.js * * Main script to run, calls all other core functions and manages loading/store to backend. * Also contains all global variables. */ /* create the web audio API context and store in audioContext*/ var audioContext; // Hold the browser web audio API var projectXML; // Hold the parsed setup XML var testXMLSetups = []; // Hold the parsed test instances var testResultsHolders =[]; // Hold the results from each test for publishing to XML var currentTrackOrder = []; // Hold the current XML tracks in their (randomised) order var currentTestHolder; // Hold any intermediate results during test - metrics var audioEngineContext; // The custome AudioEngine object var projectReturn; // Hold the URL for the return var preTestQuestions = document.createElement('PreTest'); // Store any pre-test question response var postTestQuestions = document.createElement('PostTest'); // Store any post-test question response // Add a prototype to the bufferSourceNode to reference to the audioObject holding it AudioBufferSourceNode.prototype.owner = undefined; window.onload = function() { // Function called once the browser has loaded all files. // This should perform any initial commands such as structure / loading documents // Create a web audio API context // Fixed for cross-browser support var AudioContext = window.AudioContext || window.webkitAudioContext; audioContext = new AudioContext; // Create the audio engine object audioEngineContext = new AudioEngine(); }; function createPopup() { // Create popup window interface var insertPoint = document.getElementById("topLevelBody"); 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); } function showPopup() { var popupHolder = document.getElementById('popupHolder'); if (popupHolder == null || popupHolder == undefined) { createPopup(); 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 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 var r = new XMLHttpRequest(); r.open('GET',url,true); r.onload = function() { loadProjectSpecCallback(r.response); }; r.send(); }; function loadProjectSpecCallback(response) { // Function called after asynchronous download of XML project specification var decode = $.parseXML(response); projectXML = $(decode); // Now extract the setup tag var xmlSetup = projectXML.find('setup'); // Detect the interface to use and load the relevant javascripts. var interfaceType = xmlSetup[0].attributes['interface']; var interfaceJS = document.createElement('script'); interfaceJS.setAttribute("type","text/javascript"); if (interfaceType.value == 'APE') { interfaceJS.setAttribute("src","ape.js"); // APE comes with a css file var css = document.createElement('link'); css.rel = 'stylesheet'; css.type = 'text/css'; css.href = 'ape.css'; document.getElementsByTagName("head")[0].appendChild(css); } document.getElementsByTagName("head")[0].appendChild(interfaceJS); } function createProjectSave(destURL) { // Save the data from interface into XML and send to destURL // If destURL is null then download XML in client // Now time to render file locally var xmlDoc = interfaceXMLSave(); 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"); a.hidden = ''; a.href = dnlk; a.download = "save.xml"; a.textContent = "Save File"; var submitDiv = document.getElementById('download-point'); submitDiv.appendChild(a); } return submitDiv; } function AudioEngine() { // Create two output paths, the main outputGain and fooGain. // Output gain is default to 1 and any items for playback route here // Foo gain is used for analysis to ensure paths get processed, but are not heard // because web audio will optimise and any route which does not go to the destination gets ignored. this.outputGain = audioContext.createGain(); this.fooGain = audioContext.createGain(); this.fooGain.gain = 0; // Use this to detect playback state: 0 - stopped, 1 - playing this.status = 0; this.audioObjectsReady = false; // Connect both gains to output this.outputGain.connect(audioContext.destination); this.fooGain.connect(audioContext.destination); // Create the timer Object this.timer = new timer(); // Create session metrics this.metric = new sessionMetrics(this); this.loopPlayback = false; // Create store for new audioObjects this.audioObjects = []; this.play = function() { // Start the timer and set the audioEngine state to playing (1) if (this.status == 0) { // Check if all audioObjects are ready if (this.audioObjectsReady == false) { this.audioObjectsReady = this.checkAllReady(); } if (this.audioObjectsReady == true) { this.timer.startTest(); this.status = 1; } } }; this.stop = function() { // Send stop and reset command to all playback buffers and set audioEngine state to stopped (1) if (this.status == 1) { for (var i=0; i<this.audioObjects.length; i++) { this.audioObjects[i].stop(); } this.status = 0; } }; this.newTrack = function(url) { // Pull data from given URL into new audio buffer // URLs must either be from the same source OR be setup to 'Access-Control-Allow-Origin' // Create the audioObject with ID of the new track length; audioObjectId = this.audioObjects.length; this.audioObjects[audioObjectId] = new audioObject(audioObjectId); // AudioObject will get track itself. this.audioObjects[audioObjectId].constructTrack(url); }; this.newTestPage = function() { this.state = 0; this.audioObjectsReady = false; this.metric.reset(); this.audioObjects = []; }; this.checkAllPlayed = function() { arr = []; for (var id=0; id<this.audioObjects.length; id++) { if (this.audioObjects[id].played == false) { arr.push(this.audioObjects[id].id); } } return arr; }; this.checkAllReady = function() { var ready = true; for (var i=0; i<this.audioObjects.length; i++) { if (this.audioObjects[i].state == 0) { // Track not ready console.log('WAIT -- audioObject '+i+' not ready yet!'); ready = false; }; } return ready; }; } function audioObject(id) { // The main buffer object with common control nodes to the AudioEngine this.id = id; this.state = 0; // 0 - no data, 1 - ready this.url = null; // Hold the URL given for the output back to the results. this.metric = new metricTracker(); this.played = false; // Create a buffer and external gain control to allow internal patching of effects and volume leveling. this.bufferNode = undefined; this.outputGain = audioContext.createGain(); // Default output gain to be zero this.outputGain.gain.value = 0.0; // Connect buffer to the audio graph this.outputGain.connect(audioEngineContext.outputGain); // the audiobuffer is not designed for multi-start playback // When stopeed, the buffer node is deleted and recreated with the stored buffer. this.buffer; this.play = function(startTime) { this.bufferNode = audioContext.createBufferSource(); this.bufferNode.owner = this; this.bufferNode.connect(this.outputGain); this.bufferNode.buffer = this.buffer; this.bufferNode.loop = audioEngineContext.loopPlayback; if (this.bufferNode.loop == false) { this.bufferNode.onended = function() { this.owner.metric.listening(audioEngineContext.timer.getTestTime()); }; } this.metric.listening(audioEngineContext.timer.getTestTime()); this.bufferNode.start(startTime); this.played = true; }; this.stop = function() { if (this.bufferNode != undefined) { this.bufferNode.stop(0); this.bufferNode = undefined; this.metric.listening(audioEngineContext.timer.getTestTime()); } }; this.constructTrack = function(url) { var request = new XMLHttpRequest(); this.url = url; request.open('GET',url,true); request.responseType = 'arraybuffer'; var audioObj = this; // Create callback to decode the data asynchronously request.onloadend = function() { audioContext.decodeAudioData(request.response, function(decodedData) { audioObj.buffer = decodedData; audioObj.state = 1; }, function(){ // Should only be called if there was an error, but sometimes gets called continuously // Check here if the error is genuine if (audioObj.state == 0 || audioObj.buffer == undefined) { // Genuine error console.log('FATAL - Error loading buffer on '+audioObj.id); } }); }; request.send(); }; } function timer() { /* Timer object used in audioEngine to keep track of session timings * Uses the timer of the web audio API, so sample resolution */ this.testStarted = false; this.testStartTime = 0; this.testDuration = 0; this.minimumTestTime = 0; // No minimum test time this.startTest = function() { if (this.testStarted == false) { this.testStartTime = audioContext.currentTime; this.testStarted = true; this.updateTestTime(); audioEngineContext.metric.initialiseTest(); } }; this.stopTest = function() { if (this.testStarted) { this.testDuration = this.getTestTime(); this.testStarted = false; } else { console.log('ERR: Test tried to end before beginning'); } }; this.updateTestTime = function() { if (this.testStarted) { this.testDuration = audioContext.currentTime - this.testStartTime; } }; this.getTestTime = function() { this.updateTestTime(); return this.testDuration; }; } function sessionMetrics(engine) { /* Used by audioEngine to link to audioObjects to minimise the timer call timers; */ this.engine = engine; this.lastClicked = -1; this.data = -1; this.reset = function() { this.lastClicked = -1; this.data = -1; }; this.initialiseTest = function(){}; } function metricTracker() { /* Custom object to track and collect metric data * Used only inside the audioObjects object. */ this.listenedTimer = 0; this.listenStart = 0; this.listenHold = false; this.initialPosition = -1; this.movementTracker = []; this.wasListenedTo = false; this.wasMoved = false; this.hasComments = false; this.initialised = function(position) { if (this.initialPosition == -1) { this.initialPosition = position; } }; this.moved = function(time,position) { this.wasMoved = true; this.movementTracker[this.movementTracker.length] = [time, position]; }; this.listening = function(time) { if (this.listenHold == false) { this.wasListenedTo = true; this.listenStart = time; this.listenHold = true; } else { this.listenedTimer += (time - this.listenStart); this.listenStart = 0; this.listenHold = false; } }; } function randomiseOrder(input) { // This takes an array of information and randomises the order var N = input.length; var K = N; var holdArr = []; for (var n=0; n<N; n++) { // First pick a random number var r = Math.random(); // Multiply and floor by the number of elements left r = Math.floor(r*input.length); // Pick out that element and delete from the array holdArr.push(input.splice(r,1)[0]); } return holdArr; }