changeset 1688:a449b8cdfb31

Standalone version. Movable sliders with rating Comment boxes Submission to XML file TODO: Click track to listen
author Nicholas Jillings <nickjillings@users.noreply.github.com>
date Wed, 25 Mar 2015 12:48:29 +0000
parents 8c942feff9aa
children 486b7ee55123
files ape.js core.js
diffstat 2 files changed, 171 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- a/ape.js	Tue Mar 24 15:07:11 2015 +0000
+++ b/ape.js	Wed Mar 25 12:48:29 2015 +0000
@@ -17,6 +17,7 @@
 	// The injection point into the HTML page
 	var insertPoint = document.getElementById("topLevelBody");
 	
+	
 	// Decode parts of the xmlDoc that are needed
 	// xmlDoc MUST already be parsed by jQuery!
 	var xmlSetup = xmlDoc.find('setup');
@@ -38,6 +39,41 @@
 	// Insert the titleSpan element into the title div element.
 	title.appendChild(titleSpan);
 	
+	// Store the return URL path in global projectReturn
+	projectReturn = xmlSetup[0].attributes['projectReturn'].value;
+	
+	// Create Interface buttons!
+	var interfaceButtons = document.createElement('div');
+	interfaceButtons.id = 'interface-buttons';
+	
+	// MANUAL DOWNLOAD POINT
+	// If project return is null, this MUST be specified as the location to create the download link
+	var downloadPoint = document.createElement('div');
+	downloadPoint.id = 'download-point';
+	
+	// Create playback start/stop points
+	var playback = document.createElement("button");
+	playback.innerText = 'Start';
+	playback.onclick = function() {
+		if (audioEngineContext.status == 0) {
+			audioEngineContext.play();
+			this.innerText = 'Stop';
+		} else {
+			audioEngineContext.stop();
+			this.innerText = 'Start';
+		}
+	}
+	// Create Submit (save) button
+	var submit = document.createElement("button");
+	submit.innerText = 'Submit';
+	submit.onclick = function() {
+		createProjectSave(projectReturn)
+	}
+	
+	interfaceButtons.appendChild(playback);
+	interfaceButtons.appendChild(submit);
+	interfaceButtons.appendChild(downloadPoint);
+	
 	// Now create the slider and HTML5 canvas boxes
 	
 	var sliderBox = document.createElement('div');
@@ -56,8 +92,41 @@
 
 	var feedbackHolder = document.createElement('div');
 	
+	var tracks = xmlDoc.find('tracks');
+	tracks = tracks[0];
+	var hostURL = tracks.attributes['hostURL'];
+	if (hostURL == undefined) {
+		hostURL = "";
+	} else {
+		hostURL = hostURL.value;
+	}
+	
+	var hostFs = tracks.attributes['sampleRate'];
+	var hostFsExplicit = tracks.attributes['sampleRateExplicit'];
+	if (hostFs == undefined) {
+		hostFsExplicit = false;
+	} else {
+		hostFs = hostFs.value;
+		if (hostFsExplicit != undefined) {
+			hostFsExplicit = hostFsExplicit.value;
+		}
+	}
+	
+	/// CHECK FOR SAMPLE RATE COMPATIBILITY
+	if (hostFsExplicit == true) {
+		if (Number(hostFs) != audioContext.sampleRate) {
+			var errStr = 'Sample rates do not match! Requested '+Number(hostFs)+', got '+audioContext.sampleRate+'. Please set the sample rate to match before completing this test.';
+			alert(errStr);
+			return;
+		}
+	}
+	
 	var tracksXML = xmlDoc.find('track');
 	tracksXML.each(function(index,element){
+		// Find URL of track
+		var trackURL = hostURL + this.attributes['url'].value;
+		// Now load each track in
+		audioEngineContext.newTrack(trackURL);
 		var trackObj = document.createElement('div');
 		var trackTitle = document.createElement('span');
 		trackTitle.innerText = 'Comment on track '+index;
@@ -93,6 +162,7 @@
 	// Inject into HTML
 	insertPoint.innerHTML = null; // Clear the current schema
 	insertPoint.appendChild(title); // Insert the title
+	insertPoint.appendChild(interfaceButtons);
 	insertPoint.appendChild(sliderBox);
 	insertPoint.appendChild(feedbackHolder);
 }
@@ -109,3 +179,30 @@
 		}
 	}
 }
+
+// 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");
+	var trackSliderObjects = document.getElementsByClassName('track-slider');
+	var commentObjects = document.getElementsByClassName('trackComment');
+	var rateMin = 50;
+	var rateMax = window.innerWidth-50;
+	for (var i=0; i<trackSliderObjects.length; i++)
+	{
+		var trackObj = document.createElement("Track");
+		trackObj.id = i;
+		var slider = document.createElement("Rating");
+		var rate = Number(trackSliderObjects[i].style.left.substr(0,trackSliderObjects[i].style.left.length-2));
+		rate = (rate-rateMin)/rateMax;
+		slider.innerText = Math.floor(rate*100);
+		var comment = document.createElement("Comment");
+		comment.innerText = commentObjects[i].value;
+		trackObj.appendChild(slider);
+		trackObj.appendChild(comment);
+		xmlDoc.appendChild(trackObj);
+	}
+	
+	return xmlDoc;
+}
+
--- a/core.js	Tue Mar 24 15:07:11 2015 +0000
+++ b/core.js	Wed Mar 25 12:48:29 2015 +0000
@@ -9,6 +9,7 @@
 var audioContext;
 var projectXML;
 var audioEngineContext;
+var projectReturn;
 
 window.onload = function() {
 	// Function called once the browser has loaded all files.
@@ -16,7 +17,7 @@
 	
 	// Create a web audio API context
 	// NORE: Currently this will only work with webkit browsers (Chrome/Safari)!
-	audioContext = new webkitAudioContext;
+	audioContext = new AudioContext;
 	
 	// Create the audio engine object
 	audioEngineContext = new AudioEngine();
@@ -52,6 +53,23 @@
 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);
+	}
 }
 
 function AudioEngine() {
@@ -64,6 +82,9 @@
 	this.fooGain = audioContext.createGain();
 	this.fooGain.gain = 0;
 	
+	// Use this to detect playback state: 0 - stopped, 1 - playing
+	this.status = 0;
+	
 	// Connect both gains to output
 	this.outputGain.connect(audioContext.destination);
 	this.fooGain.connect(audioContext.destination);
@@ -74,32 +95,42 @@
 	this.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) {
+			// First get current clock
+			var timer = audioContext.currentTime;
+			// Add 3 seconds
+			timer += 3.0;
+			
+			// Send play to all tracks
+			for (var i=0; i<this.audioObjects.length; i++)
+			{
+				this.audioObjects[i].play(timer);
+			}
+			this.status = 1;
+		}
 	}
 	
 	this.stop = function() {
 		// Send stop and reset command to all playback buffers
+		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'
-		var request = new XMLHttpRequest();
-		request.open('GET',url,true);
-		request.responseType = 'arraybuffer';
+		
 		// Create the audioObject with ID of the new track length;
 		audioObjectId = this.audioObjects.length
 		this.audioObjects[audioObjectId] = new audioObject(audioObjectId);
-		
-		// Create callback to decode the data asynchronously
-		request.onload = function() {
-			audioContext.decodeAudioData(request.response, function(decodedData) {
-				audioObj = audioEngineContext.audioObjects[audioObjectId];
-				audioObj.buffer = decodedData;
-				audioObj.bufferNode.buffer = audioObj.buffer;
-				audioObj.state = 1;
-			}, console.log("Err - Buffer not added to " + audioObjectId));
-		}
-		request.send();
+
+		// AudioObject will get track itself.
+		this.audioObjects[audioObjectId].constructTrack(url);
 	}
 	
 }
@@ -131,6 +162,33 @@
 		this.bufferNode = audioContext.createBufferSource();
 		this.bufferNode.connect(this.outputGain);
 		this.bufferNode.buffer = this.buffer;
+		this.bufferNode.loop = true;
 	}
 	
-}
+	this.constructTrack = function(url) {
+		var request = new XMLHttpRequest();
+		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.bufferNode.buffer = audioObj.buffer;
+				audioObj.bufferNode.loop = true;
+				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();
+	}
+	
+}
\ No newline at end of file