annotate core.js @ 705:f38946647666

Added cross-browser support for webkit / non-webkit audioContext. Confirmed Chrome & Safari Support. Firefox: does not support innerText or dragging!
author Nicholas Jillings <nicholas.jillings@eecs.qmul.ac.uk>
date Wed, 08 Apr 2015 18:18:17 +0100
parents 375410a5571d
children f5de8699e2b6
rev   line source
nicholas@1 1 /**
nicholas@1 2 * core.js
nicholas@1 3 *
nicholas@1 4 * Main script to run, calls all other core functions and manages loading/store to backend.
nicholas@1 5 * Also contains all global variables.
nicholas@1 6 */
nicholas@1 7
nicholas@1 8 /* create the web audio API context and store in audioContext*/
nicholas@1 9 var audioContext;
nicholas@2 10 var projectXML;
nicholas@1 11 var audioEngineContext;
nicholas@7 12 var projectReturn;
nicholas@1 13
nicholas@1 14 window.onload = function() {
nicholas@1 15 // Function called once the browser has loaded all files.
nicholas@1 16 // This should perform any initial commands such as structure / loading documents
nicholas@1 17
nicholas@1 18 // Create a web audio API context
nicholas@705 19 // Fixed for cross-browser support
nicholas@705 20 var AudioContext = window.AudioContext || window.webkitAudioContext;
nicholas@7 21 audioContext = new AudioContext;
nicholas@1 22
nicholas@1 23 // Create the audio engine object
nicholas@1 24 audioEngineContext = new AudioEngine();
n@701 25 };
nicholas@1 26
nicholas@1 27 function loadProjectSpec(url) {
nicholas@1 28 // Load the project document from the given URL, decode the XML and instruct audioEngine to get audio data
nicholas@1 29 // If url is null, request client to upload project XML document
nicholas@2 30 var r = new XMLHttpRequest();
nicholas@2 31 r.open('GET',url,true);
nicholas@2 32 r.onload = function() {
nicholas@2 33 loadProjectSpecCallback(r.response);
n@701 34 };
nicholas@2 35 r.send();
n@701 36 };
nicholas@2 37
nicholas@2 38 function loadProjectSpecCallback(response) {
nicholas@2 39 // Function called after asynchronous download of XML project specification
nicholas@2 40 var decode = $.parseXML(response);
nicholas@2 41 projectXML = $(decode);
nicholas@2 42
nicholas@2 43 // Now extract the setup tag
nicholas@2 44 var xmlSetup = projectXML.find('setup');
n@701 45 // Detect the interface to use and load the relevant javascripts.
nicholas@2 46 var interfaceType = xmlSetup[0].attributes['interface'];
nicholas@2 47 var interfaceJS = document.createElement('script');
nicholas@2 48 interfaceJS.setAttribute("type","text/javascript");
nicholas@2 49 if (interfaceType.value == 'APE') {
nicholas@2 50 interfaceJS.setAttribute("src","ape.js");
nicholas@2 51 }
nicholas@2 52 document.getElementsByTagName("head")[0].appendChild(interfaceJS);
nicholas@1 53 }
nicholas@1 54
nicholas@1 55 function createProjectSave(destURL) {
nicholas@1 56 // Save the data from interface into XML and send to destURL
nicholas@1 57 // If destURL is null then download XML in client
nicholas@7 58 // Now time to render file locally
nicholas@7 59 var xmlDoc = interfaceXMLSave();
nicholas@7 60 if (destURL == "null" || destURL == undefined) {
nicholas@7 61 var parent = document.createElement("div");
nicholas@7 62 parent.appendChild(xmlDoc);
nicholas@7 63 var file = [parent.innerHTML];
nicholas@7 64 var bb = new Blob(file,{type : 'application/xml'});
nicholas@7 65 var dnlk = window.URL.createObjectURL(bb);
nicholas@7 66 var a = document.createElement("a");
nicholas@7 67 a.hidden = '';
nicholas@7 68 a.href = dnlk;
nicholas@7 69 a.download = "save.xml";
nicholas@7 70 a.textContent = "Save File";
nicholas@7 71
nicholas@7 72 var submitDiv = document.getElementById('download-point');
nicholas@7 73 submitDiv.appendChild(a);
nicholas@7 74 }
nicholas@1 75 }
nicholas@1 76
nicholas@1 77 function AudioEngine() {
nicholas@1 78
nicholas@1 79 // Create two output paths, the main outputGain and fooGain.
nicholas@1 80 // Output gain is default to 1 and any items for playback route here
nicholas@1 81 // Foo gain is used for analysis to ensure paths get processed, but are not heard
nicholas@1 82 // because web audio will optimise and any route which does not go to the destination gets ignored.
nicholas@1 83 this.outputGain = audioContext.createGain();
nicholas@1 84 this.fooGain = audioContext.createGain();
nicholas@1 85 this.fooGain.gain = 0;
nicholas@1 86
nicholas@7 87 // Use this to detect playback state: 0 - stopped, 1 - playing
nicholas@7 88 this.status = 0;
nicholas@7 89
nicholas@1 90 // Connect both gains to output
nicholas@1 91 this.outputGain.connect(audioContext.destination);
nicholas@1 92 this.fooGain.connect(audioContext.destination);
nicholas@1 93
nicholas@1 94 // Create store for new audioObjects
nicholas@1 95 this.audioObjects = [];
nicholas@1 96
nicholas@1 97 this.play = function() {
nicholas@1 98 // Send play command to all playback buffers for synchronised start
nicholas@1 99 // Also start timer callbacks to detect if playback has finished
nicholas@7 100 if (this.status == 0) {
nicholas@7 101 // First get current clock
nicholas@7 102 var timer = audioContext.currentTime;
nicholas@7 103 // Add 3 seconds
nicholas@7 104 timer += 3.0;
nicholas@7 105
nicholas@7 106 // Send play to all tracks
nicholas@7 107 for (var i=0; i<this.audioObjects.length; i++)
nicholas@7 108 {
nicholas@7 109 this.audioObjects[i].play(timer);
nicholas@7 110 }
nicholas@7 111 this.status = 1;
nicholas@7 112 }
n@701 113 };
nicholas@1 114
nicholas@1 115 this.stop = function() {
nicholas@1 116 // Send stop and reset command to all playback buffers
nicholas@7 117 if (this.status == 1) {
nicholas@7 118 for (var i=0; i<this.audioObjects.length; i++)
nicholas@7 119 {
nicholas@7 120 this.audioObjects[i].stop();
nicholas@7 121 }
nicholas@7 122 this.status = 0;
nicholas@7 123 }
n@701 124 };
nicholas@1 125
nicholas@8 126 this.selectedTrack = function(id) {
nicholas@8 127 for (var i=0; i<this.audioObjects.length; i++)
nicholas@8 128 {
nicholas@8 129 if (id == i) {
nicholas@8 130 this.audioObjects[i].outputGain.gain.value = 1.0;
nicholas@8 131 } else {
nicholas@8 132 this.audioObjects[i].outputGain.gain.value = 0.0;
nicholas@8 133 }
nicholas@8 134 }
n@701 135 };
nicholas@8 136
nicholas@8 137
nicholas@1 138 this.newTrack = function(url) {
nicholas@1 139 // Pull data from given URL into new audio buffer
nicholas@1 140 // URLs must either be from the same source OR be setup to 'Access-Control-Allow-Origin'
nicholas@7 141
nicholas@1 142 // Create the audioObject with ID of the new track length;
nicholas@1 143 audioObjectId = this.audioObjects.length
nicholas@1 144 this.audioObjects[audioObjectId] = new audioObject(audioObjectId);
nicholas@7 145
nicholas@7 146 // AudioObject will get track itself.
nicholas@7 147 this.audioObjects[audioObjectId].constructTrack(url);
n@701 148 };
nicholas@1 149
nicholas@1 150 }
nicholas@1 151
nicholas@1 152 function audioObject(id) {
nicholas@1 153 // The main buffer object with common control nodes to the AudioEngine
nicholas@1 154
nicholas@1 155 this.id = id;
nicholas@1 156 this.state = 0; // 0 - no data, 1 - ready
nicholas@1 157
nicholas@1 158 // Create a buffer and external gain control to allow internal patching of effects and volume leveling.
nicholas@1 159 this.bufferNode = audioContext.createBufferSource();
nicholas@1 160 this.outputGain = audioContext.createGain();
nicholas@1 161
nicholas@8 162 // Default output gain to be zero
nicholas@8 163 this.outputGain.gain.value = 0.0;
nicholas@8 164
nicholas@1 165 // Connect buffer to the audio graph
nicholas@1 166 this.bufferNode.connect(this.outputGain);
nicholas@1 167 this.outputGain.connect(audioEngineContext.outputGain);
nicholas@1 168
nicholas@1 169 // the audiobuffer is not designed for multi-start playback
nicholas@1 170 // When stopeed, the buffer node is deleted and recreated with the stored buffer.
nicholas@1 171 this.buffer;
nicholas@1 172
nicholas@1 173 this.play = function(startTime) {
nicholas@1 174 this.bufferNode.start(startTime);
n@701 175 };
nicholas@1 176
nicholas@1 177 this.stop = function() {
nicholas@1 178 this.bufferNode.stop(0);
nicholas@1 179 this.bufferNode = audioContext.createBufferSource();
nicholas@1 180 this.bufferNode.connect(this.outputGain);
nicholas@1 181 this.bufferNode.buffer = this.buffer;
nicholas@7 182 this.bufferNode.loop = true;
n@701 183 };
nicholas@8 184
nicholas@7 185 this.constructTrack = function(url) {
nicholas@7 186 var request = new XMLHttpRequest();
nicholas@7 187 request.open('GET',url,true);
nicholas@7 188 request.responseType = 'arraybuffer';
nicholas@7 189
nicholas@7 190 var audioObj = this;
nicholas@7 191
nicholas@7 192 // Create callback to decode the data asynchronously
nicholas@7 193 request.onloadend = function() {
nicholas@7 194 audioContext.decodeAudioData(request.response, function(decodedData) {
nicholas@7 195 audioObj.buffer = decodedData;
nicholas@7 196 audioObj.bufferNode.buffer = audioObj.buffer;
nicholas@7 197 audioObj.bufferNode.loop = true;
nicholas@7 198 audioObj.state = 1;
nicholas@7 199 }, function(){
nicholas@7 200 // Should only be called if there was an error, but sometimes gets called continuously
nicholas@7 201 // Check here if the error is genuine
nicholas@7 202 if (audioObj.state == 0 || audioObj.buffer == undefined) {
nicholas@7 203 // Genuine error
nicholas@7 204 console.log('FATAL - Error loading buffer on '+audioObj.id);
nicholas@7 205 }
nicholas@7 206 });
n@701 207 };
nicholas@7 208 request.send();
n@701 209 };
nicholas@7 210
nicholas@7 211 }