Tone.js/Tone/core/Buffer.js

355 lines
9.1 KiB
JavaScript
Raw Normal View History

define(["Tone/core/Tone", "Tone/core/Emitter"], function(Tone){
2014-10-21 18:44:02 +00:00
"use strict";
/**
2015-02-25 21:20:12 +00:00
* @class Buffer loading and storage. Tone.Buffer is used internally by all
2015-06-13 23:29:25 +00:00
* classes that make requests for audio files such as Tone.Player,
* Tone.Sampler and Tone.Convolver.
* <br><br>
* Aside from load callbacks from individual buffers, Tone.Buffer
2015-02-25 21:20:12 +00:00
* provides static methods which keep track of the loading progress
2015-06-20 19:50:57 +00:00
* of all of the buffers. These methods are Tone.Buffer.onload, Tone.Buffer.onprogress,
* and Tone.Buffer.onerror.
2015-02-25 21:20:12 +00:00
*
2014-10-21 18:44:02 +00:00
* @constructor
2015-05-05 20:40:52 +00:00
* @extends {Tone}
2015-06-14 00:52:51 +00:00
* @param {AudioBuffer|string} url The url to load, or the audio buffer to set.
* @param {function=} onload A callback which is invoked after the buffer is loaded.
* It's recommended to use Tone.Buffer.onload instead
* since it will give you a callback when ALL buffers are loaded.
* @example
* var buffer = new Tone.Buffer("path/to/sound.mp3", function(){
* //the buffer is now available.
* var buff = buffer.get();
* });
2014-10-21 18:44:02 +00:00
*/
Tone.Buffer = function(){
var options = this.optionsObject(arguments, ["url", "onload"], Tone.Buffer.defaults);
2014-10-21 18:44:02 +00:00
/**
* stores the loaded AudioBuffer
* @type {AudioBuffer}
* @private
*/
this._buffer = null;
2015-03-26 14:51:44 +00:00
/**
* indicates if the buffer should be reversed or not
* @type {boolean}
* @private
*/
this._reversed = options.reverse;
/**
2015-06-14 00:52:51 +00:00
* The url of the buffer. <code>undefined</code> if it was
2015-02-02 01:02:54 +00:00
* constructed with a buffer
* @type {string}
2015-02-02 01:02:54 +00:00
* @readOnly
*/
this.url = undefined;
2015-02-02 02:32:49 +00:00
/**
2015-06-14 00:52:51 +00:00
* Indicates if the buffer is loaded or not.
2015-02-02 02:32:49 +00:00
* @type {boolean}
* @readOnly
*/
this.loaded = false;
/**
2015-06-14 00:52:51 +00:00
* The callback to invoke when everything is loaded.
* @type {function}
*/
2015-02-04 15:16:49 +00:00
this.onload = options.onload.bind(this, this);
if (options.url instanceof AudioBuffer || options.url instanceof Tone.Buffer){
this.set(options.url);
this.onload(this);
2015-10-21 17:13:15 +00:00
} else if (this.isString(options.url)){
this.url = options.url;
Tone.Buffer._addToQueue(options.url, this);
}
2014-10-21 18:44:02 +00:00
};
2014-10-21 18:44:02 +00:00
Tone.extend(Tone.Buffer);
2014-10-21 18:44:02 +00:00
/**
* the default parameters
* @type {Object}
*/
Tone.Buffer.defaults = {
"url" : undefined,
2015-06-13 23:29:25 +00:00
"onload" : Tone.noOp,
2015-03-26 14:51:44 +00:00
"reverse" : false
};
/**
2015-06-14 00:52:51 +00:00
* Pass in an AudioBuffer or Tone.Buffer to set the value
* of this buffer.
* @param {AudioBuffer|Tone.Buffer} buffer the buffer
2015-06-14 00:52:51 +00:00
* @returns {Tone.Buffer} this
*/
Tone.Buffer.prototype.set = function(buffer){
if (buffer instanceof Tone.Buffer){
this._buffer = buffer.get();
} else {
this._buffer = buffer;
}
2015-02-04 15:16:49 +00:00
this.loaded = true;
return this;
};
/**
2015-06-14 00:52:51 +00:00
* @return {AudioBuffer} The audio buffer stored in the object.
*/
Tone.Buffer.prototype.get = function(){
return this._buffer;
};
/**
2015-06-14 00:52:51 +00:00
* Load url into the buffer.
* @param {String} url The url to load
* @param {Function=} callback The callback to invoke on load.
* don't need to set if `onload` is
* already set.
2015-06-14 00:52:51 +00:00
* @returns {Tone.Buffer} this
*/
Tone.Buffer.prototype.load = function(url, callback){
this.url = url;
this.onload = this.defaultArg(callback, this.onload);
Tone.Buffer._addToQueue(url, this);
2015-01-06 04:33:05 +00:00
return this;
};
/**
* dispose and disconnect
2015-06-14 00:52:51 +00:00
* @returns {Tone.Buffer} this
*/
2015-02-02 03:05:24 +00:00
Tone.Buffer.prototype.dispose = function(){
Tone.prototype.dispose.call(this);
Tone.Buffer._removeFromQueue(this);
this._buffer = null;
this.onload = Tone.Buffer.defaults.onload;
2015-02-02 03:05:24 +00:00
return this;
};
2015-02-02 01:02:54 +00:00
/**
2015-06-14 00:52:51 +00:00
* The duration of the buffer.
2015-02-02 01:02:54 +00:00
* @memberOf Tone.Buffer#
* @type {number}
* @name duration
* @readOnly
*/
Object.defineProperty(Tone.Buffer.prototype, "duration", {
get : function(){
2015-02-02 01:02:54 +00:00
if (this._buffer){
return this._buffer.duration;
} else {
return 0;
}
},
});
2015-03-26 14:51:44 +00:00
/**
2015-06-14 00:52:51 +00:00
* Reverse the buffer.
2015-03-26 14:51:44 +00:00
* @private
2015-06-14 00:52:51 +00:00
* @return {Tone.Buffer} this
2015-03-26 14:51:44 +00:00
*/
Tone.Buffer.prototype._reverse = function(){
if (this.loaded){
for (var i = 0; i < this._buffer.numberOfChannels; i++){
Array.prototype.reverse.call(this._buffer.getChannelData(i));
}
}
return this;
};
/**
2015-06-14 00:52:51 +00:00
* Reverse the buffer.
2015-03-26 14:51:44 +00:00
* @memberOf Tone.Buffer#
* @type {boolean}
* @name reverse
*/
Object.defineProperty(Tone.Buffer.prototype, "reverse", {
get : function(){
return this._reversed;
},
set : function(rev){
if (this._reversed !== rev){
this._reversed = rev;
this._reverse();
}
},
});
///////////////////////////////////////////////////////////////////////////
// STATIC METHODS
///////////////////////////////////////////////////////////////////////////
//statically inherits Emitter methods
Tone.Emitter.mixin(Tone.Buffer);
/**
* the static queue for all of the xhr requests
* @type {Array}
* @private
*/
Tone.Buffer._queue = [];
/**
* the array of current downloads
* @type {Array}
* @private
*/
Tone.Buffer._currentDownloads = [];
/**
* the total number of downloads
* @type {number}
* @private
*/
Tone.Buffer._totalDownloads = 0;
/**
* the maximum number of simultaneous downloads
* @static
* @type {number}
*/
Tone.Buffer.MAX_SIMULTANEOUS_DOWNLOADS = 6;
/**
* Adds a file to be loaded to the loading queue
* @param {string} url the url to load
* @param {function} callback the callback to invoke once it's loaded
* @private
*/
Tone.Buffer._addToQueue = function(url, buffer){
Tone.Buffer._queue.push({
url : url,
Buffer : buffer,
progress : 0,
xhr : null
});
this._totalDownloads++;
Tone.Buffer._next();
};
/**
* Remove an object from the queue's (if it's still there)
* Abort the XHR if it's in progress
* @param {Tone.Buffer} buffer the buffer to remove
* @private
*/
Tone.Buffer._removeFromQueue = function(buffer){
var i;
for (i = 0; i < Tone.Buffer._queue.length; i++){
var q = Tone.Buffer._queue[i];
if (q.Buffer === buffer){
Tone.Buffer._queue.splice(i, 1);
}
}
for (i = 0; i < Tone.Buffer._currentDownloads.length; i++){
var dl = Tone.Buffer._currentDownloads[i];
if (dl.Buffer === buffer){
Tone.Buffer._currentDownloads.splice(i, 1);
dl.xhr.abort();
dl.xhr.onprogress = null;
dl.xhr.onload = null;
dl.xhr.onerror = null;
}
}
};
/**
* load the next buffer in the queue
* @private
*/
Tone.Buffer._next = function(){
if (Tone.Buffer._queue.length > 0){
if (Tone.Buffer._currentDownloads.length < Tone.Buffer.MAX_SIMULTANEOUS_DOWNLOADS){
var next = Tone.Buffer._queue.shift();
Tone.Buffer._currentDownloads.push(next);
next.xhr = Tone.Buffer.load(next.url, function(buffer){
//remove this one from the queue
var index = Tone.Buffer._currentDownloads.indexOf(next);
Tone.Buffer._currentDownloads.splice(index, 1);
next.Buffer.set(buffer);
if (next.Buffer._reversed){
next.Buffer._reverse();
}
next.Buffer.onload(next.Buffer);
Tone.Buffer._onprogress();
Tone.Buffer._next();
});
next.xhr.onprogress = function(event){
next.progress = event.loaded / event.total;
Tone.Buffer._onprogress();
};
next.xhr.onerror = function(e){
Tone.Buffer.trigger("error", e);
};
}
} else if (Tone.Buffer._currentDownloads.length === 0){
Tone.Buffer.trigger("load");
//reset the downloads
Tone.Buffer._totalDownloads = 0;
}
};
/**
* internal progress event handler
* @private
*/
Tone.Buffer._onprogress = function(){
var curretDownloadsProgress = 0;
var currentDLLen = Tone.Buffer._currentDownloads.length;
var inprogress = 0;
if (currentDLLen > 0){
for (var i = 0; i < currentDLLen; i++){
var dl = Tone.Buffer._currentDownloads[i];
curretDownloadsProgress += dl.progress;
}
inprogress = curretDownloadsProgress;
}
var currentDownloadProgress = currentDLLen - inprogress;
var completed = Tone.Buffer._totalDownloads - Tone.Buffer._queue.length - currentDownloadProgress;
Tone.Buffer.trigger("progress", completed / Tone.Buffer._totalDownloads);
};
2014-10-21 18:44:02 +00:00
/**
* A path which is prefixed before every url.
* @type {String}
* @static
*/
Tone.Buffer.baseUrl = "";
2014-10-21 18:44:02 +00:00
/**
2015-06-14 00:52:51 +00:00
* Makes an xhr reqest for the selected url then decodes
* the file as an audio buffer. Invokes
2014-10-21 18:44:02 +00:00
* the callback once the audio buffer loads.
2015-06-14 00:52:51 +00:00
* @param {string} url The url of the buffer to load.
2014-10-21 18:44:02 +00:00
* filetype support depends on the
* browser.
2015-06-14 00:52:51 +00:00
* @param {function} callback The function to invoke when the url is loaded.
* @returns {XMLHttpRequest} returns the XHR
2014-10-21 18:44:02 +00:00
*/
Tone.Buffer.load = function(url, callback){
2014-10-21 18:44:02 +00:00
var request = new XMLHttpRequest();
request.open("GET", Tone.Buffer.baseUrl + url, true);
2014-10-21 18:44:02 +00:00
request.responseType = "arraybuffer";
// decode asynchronously
request.onload = function() {
Tone.context.decodeAudioData(request.response, function(buff) {
2014-10-21 18:44:02 +00:00
callback(buff);
}, function(){
2016-05-26 00:50:46 +00:00
throw new Error("Tone.Buffer: could not decode audio data:" + url);
2014-10-21 18:44:02 +00:00
});
};
//send the request
request.send();
return request;
};
2014-10-21 18:44:02 +00:00
return Tone.Buffer;
});