define(["Tone/core/Tone", "Tone/source/BufferSource", "Tone/core/Buffers", "Tone/source/Source", "Tone/component/Volume"], function (Tone) { /** * @class Tone.MultiPlayer is well suited for one-shots, multi-sampled instruments * or any time you need to play a bunch of audio buffers. * @param {Object|Array|Tone.Buffers} buffers The buffers which are available * to the MultiPlayer * @param {Function} onload The callback to invoke when all of the buffers are loaded. * @extends {Tone} * @example * var multiPlayer = new MultiPlayer({ * "kick" : "path/to/kick.mp3", * "snare" : "path/to/snare.mp3", * }, function(){ * multiPlayer.start("kick"); * }); * @example * //can also store the values in an array * var multiPlayer = new MultiPlayer(["path/to/kick.mp3", "path/to/snare.mp3"], * function(){ * //if an array is passed in, the samples are referenced to by index * multiPlayer.start(1); * }); */ Tone.MultiPlayer = function(urls){ //remove the urls from the options if (arguments.length === 1 && !Tone.isUndef(arguments[0]) && !arguments[0].hasOwnProperty("urls")){ urls = { "urls" : urls }; } var options = Tone.defaults(arguments, ["urls", "onload"], Tone.MultiPlayer); Tone.Source.call(this, options); if (options.urls instanceof Tone.Buffers){ /** * All the buffers belonging to the player. * @type {Tone.Buffers} */ this.buffers = options.urls; } else { this.buffers = new Tone.Buffers(options.urls, options.onload); } /** * Keeps track of the currently playing sources. * @type {Object} * @private */ this._activeSources = {}; /** * The fade in envelope which is applied * to the beginning of the BufferSource * @type {Time} */ this.fadeIn = options.fadeIn; /** * The fade out envelope which is applied * to the end of the BufferSource * @type {Time} */ this.fadeOut = options.fadeOut; }; Tone.extend(Tone.MultiPlayer, Tone.Source); /** * The defaults * @type {Object} */ Tone.MultiPlayer.defaults = { "onload" : Tone.noOp, "fadeIn" : 0, "fadeOut" : 0 }; /** * Make the source from the buffername * @param {String} bufferName * @return {Tone.BufferSource} * @private */ Tone.MultiPlayer.prototype._makeSource = function(bufferName){ var buffer; if (Tone.isString(bufferName) || Tone.isNumber(bufferName)){ buffer = this.buffers.get(bufferName).get(); } else if (bufferName instanceof Tone.Buffer){ buffer = bufferName.get(); } else if (bufferName instanceof AudioBuffer){ buffer = bufferName; } var source = new Tone.BufferSource(buffer).connect(this.output); if (!this._activeSources.hasOwnProperty(bufferName)){ this._activeSources[bufferName] = []; } this._activeSources[bufferName].push(source); return source; }; /** * Start a buffer by name. The `start` method allows a number of options * to be passed in such as offset, interval, and gain. This is good for multi-sampled * instruments and sound sprites where samples are repitched played back at different velocities. * @param {String} bufferName The name of the buffer to start. * @param {Time} time When to start the buffer. * @param {Time} [offset=0] The offset into the buffer to play from. * @param {Time=} duration How long to play the buffer for. * @param {Interval} [pitch=0] The interval to repitch the buffer. * @param {Gain} [gain=1] The gain to play the sample at. * @return {Tone.MultiPlayer} this */ Tone.MultiPlayer.prototype.start = function(bufferName, time, offset, duration, pitch, gain){ time = this.toSeconds(time); var source = this._makeSource(bufferName); source.start(time, offset, duration, Tone.defaultArg(gain, 1), this.fadeIn); if (duration){ source.stop(time + this.toSeconds(duration), this.fadeOut); } pitch = Tone.defaultArg(pitch, 0); source.playbackRate.value = Tone.intervalToFrequencyRatio(pitch); return this; }; /** * Start a looping buffer by name. Similar to `start`, but the buffer * is looped instead of played straight through. Can still be stopped with `stop`. * @param {String} bufferName The name of the buffer to start. * @param {Time} time When to start the buffer. * @param {Time} [offset=0] The offset into the buffer to play from. * @param {Time=} loopStart The start of the loop. * @param {Time=} loopEnd The end of the loop. * @param {Interval} [pitch=0] The interval to repitch the buffer. * @param {Gain} [gain=1] The gain to play the sample at. * @return {Tone.MultiPlayer} this */ Tone.MultiPlayer.prototype.startLoop = function(bufferName, time, offset, loopStart, loopEnd, pitch, gain){ time = this.toSeconds(time); var source = this._makeSource(bufferName); source.loop = true; source.loopStart = this.toSeconds(Tone.defaultArg(loopStart, 0)); source.loopEnd = this.toSeconds(Tone.defaultArg(loopEnd, 0)); source.start(time, offset, undefined, Tone.defaultArg(gain, 1), this.fadeIn); pitch = Tone.defaultArg(pitch, 0); source.playbackRate.value = Tone.intervalToFrequencyRatio(pitch); return this; }; /** * Stop the first played instance of the buffer name. * @param {String} bufferName The buffer to stop. * @param {Time=} time When to stop the buffer * @return {Tone.MultiPlayer} this */ Tone.MultiPlayer.prototype.stop = function(bufferName, time){ if (this._activeSources[bufferName] && this._activeSources[bufferName].length){ time = this.toSeconds(time); this._activeSources[bufferName].shift().stop(time, this.fadeOut); } else { throw new Error("Tone.MultiPlayer: cannot stop a buffer that hasn't been started or is already stopped"); } return this; }; /** * Stop all currently playing buffers at the given time. * @param {Time=} time When to stop the buffers. * @return {Tone.MultiPlayer} this */ Tone.MultiPlayer.prototype.stopAll = function(time){ time = this.toSeconds(time); for (var bufferName in this._activeSources){ var sources = this._activeSources[bufferName]; for (var i = 0; i < sources.length; i++){ sources[i].stop(time); } } return this; }; /** * Add another buffer to the available buffers. * @param {String} name The name to that the buffer is refered * to in start/stop methods. * @param {String|Tone.Buffer} url The url of the buffer to load * or the buffer. * @param {Function} callback The function to invoke after the buffer is loaded. */ Tone.MultiPlayer.prototype.add = function(name, url, callback){ this.buffers.add(name, url, callback); return this; }; /** * Returns the playback state of the source. "started" * if there are any buffers playing. "stopped" otherwise. * @type {Tone.State} * @readOnly * @memberOf Tone.MultiPlayer# * @name state */ Object.defineProperty(Tone.MultiPlayer.prototype, "state", { get : function(){ return this._activeSources.length > 0 ? Tone.State.Started : Tone.State.Stopped; } }); /** * Mute the output. * @memberOf Tone.MultiPlayer# * @type {boolean} * @name mute * @example * //mute the output * source.mute = true; */ Object.defineProperty(Tone.MultiPlayer.prototype, "mute", { get : function(){ return this._volume.mute; }, set : function(mute){ this._volume.mute = mute; } }); /** * Clean up. * @return {Tone.MultiPlayer} this */ Tone.MultiPlayer.prototype.dispose = function(){ Tone.Source.prototype.dispose.call(this); for (var bufferName in this._activeSources){ this._activeSources[bufferName].forEach(function(source){ source.dispose(); }); } this.buffers.dispose(); this.buffers = null; this._activeSources = null; return this; }; return Tone.MultiPlayer; });