define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function (Tone) { /** * @class Wrapper around the native BufferSourceNode. * @param {AudioBuffer|Tone.Buffer} buffer The buffer to play * @param {Function} onended The callback to invoke when the * buffer is done playing. */ Tone.BufferSource = function(){ var options = this.optionsObject(arguments, ["buffer", "onended"], Tone.BufferSource.defaults); /** * The callback to invoke after the * buffer source is done playing. * @type {Function} */ this.onended = options.onended; /** * The time that the buffer was started. * @type {Number} * @private */ this._startTime = -1; /** * The gain node which envelopes the BufferSource * @type {GainNode} * @private */ this._gainNode = this.output = this.context.createGain(); /** * The buffer source * @type {AudioBufferSourceNode} * @private */ this._source = this.context.createBufferSource(); this._source.connect(this._gainNode); this._source.onended = this._onended.bind(this); /** * The playbackRate of the buffer * @type {AudioParam} */ this.playbackRate = this._source.playbackRate; /** * The fadeIn time of the amplitude envelope. * @type {Time} */ this.fadeIn = options.fadeIn; /** * The fadeOut time of the amplitude envelope. * @type {Time} */ this.fadeOut = options.fadeOut; /** * The value that the buffer ramps to * @type {Gain} * @private */ this._gain = 1; //set the buffer initially if (!this.isUndef(options.buffer)){ this.buffer = options.buffer; } this.loop = options.loop; }; Tone.extend(Tone.BufferSource); /** * The defaults * @const * @type {Object} */ Tone.BufferSource.defaults = { "onended" : Tone.noOp, "fadeIn" : 0, "fadeOut" : 0 }; /** * Returns the playback state of the source, either "started" or "stopped". * @type {Tone.State} * @readOnly * @memberOf Tone.BufferSource# * @name state */ Object.defineProperty(Tone.BufferSource.prototype, "state", { get : function(){ var now = this.now(); if (this._startTime !== -1 && now > this._startTime){ return Tone.State.Started; } else { return Tone.State.Stopped; } } }); /** * Start the buffer * @param {Time} [startTime=now] When the player should start. * @param {Time} [offset=0] The offset from the beginning of the sample * to start at. * @param {Time=} duration How long the sample should play. If no duration * is given, it will default to the full length * of the sample (minus any offset) * @param {Gain} [gain=1] The gain to play the buffer back at. * @param {Time=} fadeInTime The optional fadeIn ramp time. * @return {Tone.BufferSource} this */ Tone.BufferSource.prototype.start = function(time, offset, duration, gain, fadeInTime){ if (this._startTime !== -1){ throw new Error("Tone.BufferSource: can only be started once."); } if (!this.buffer){ throw new Error("Tone.BufferSource: no buffer set."); } time = this.toSeconds(time); //if it's a loop the default offset is the loopstart point if (this.loop){ offset = this.defaultArg(offset, this.loopStart); } else { //otherwise the default offset is 0 offset = this.defaultArg(offset, 0); } offset = this.toSeconds(offset); duration = this.defaultArg(duration, this.buffer.duration - offset); //the values in seconds time = this.toSeconds(time); duration = this.toSeconds(duration); this._source.start(time, offset); if (!this.loop){ this.stop(time + duration, fadeInTime); } gain = this.defaultArg(gain, 1); this._gain = gain; //the fadeIn time if (this.isUndef(fadeInTime)){ fadeInTime = this.toSeconds(this.fadeIn); } else { fadeInTime = this.toSeconds(fadeInTime); } if (fadeInTime > 0){ this._gainNode.gain.setValueCurveAtTime(this._fadeInCurve, time, fadeInTime); time += fadeInTime; } else { this._gainNode.gain.setValueAtTime(gain, time); } this._startTime = time; return this; }; /** * Stop the buffer. Optionally add a ramp time to fade the * buffer out. * @param {Time=} time The time the buffer should stop. * @param {Time=} fadeOutTime How long the gain should fade out for * @return {Tone.BufferSource} this */ Tone.BufferSource.prototype.stop = function(time, fadeOutTime){ if (!this.buffer){ throw new Error("Tone.BufferSource: no buffer set."); } time = this.toSeconds(time); //the fadeOut time if (this.isUndef(fadeOutTime)){ fadeOutTime = this.toSeconds(this.fadeOut); } else { fadeOutTime = this.toSeconds(fadeOutTime); } //cancel the end curve this._gainNode.gain.cancelScheduledValues(this._startTime); //set a new one if (fadeOutTime > 0){ this._gainNode.gain.setValueCurveAtTime(this._fadeOutCurve, time, fadeOutTime); time += fadeOutTime; } else { this._gainNode.gain.setValueAtTime(0, time); } this._source.stop(time); return this; }; /** * Internal callback when the buffer is ended. * Invokes `onended` and disposes the node. * @private */ Tone.BufferSource.prototype._onended = function(){ this.onended(this); this.dispose(); }; /** * If loop is true, the loop will start at this position. * @memberOf Tone.BufferSource# * @type {Time} * @name loopStart */ Object.defineProperty(Tone.BufferSource.prototype, "loopStart", { get : function(){ return this._source.loopStart; }, set : function(loopStart){ this._source.loopStart = this.toSeconds(loopStart); } }); /** * If loop is true, the loop will end at this position. * @memberOf Tone.BufferSource# * @type {Time} * @name loopEnd */ Object.defineProperty(Tone.BufferSource.prototype, "loopEnd", { get : function(){ return this._source.loopEnd; }, set : function(loopEnd){ this._source.loopEnd = this.toSeconds(loopEnd); } }); /** * The audio buffer belonging to the player. * @memberOf Tone.BufferSource# * @type {AudioBuffer} * @name buffer */ Object.defineProperty(Tone.BufferSource.prototype, "buffer", { get : function(){ return this._source.buffer; }, set : function(buffer){ if (buffer instanceof Tone.Buffer){ this._source.buffer = buffer.get(); } else { this._source.buffer = buffer; } } }); /** * If the buffer should loop once it's over. * @memberOf Tone.BufferSource# * @type {boolean} * @name loop */ Object.defineProperty(Tone.BufferSource.prototype, "loop", { get : function(){ return this._source.loop; }, set : function(loop){ this._source.loop = loop; } }); //ENVELOPE CURVES (function setupEnvelopes(){ var len = 1024; /** * The fade in envelope which i used with * setValueCurveAtTime. * @type {Float32Array} * @private */ Tone.BufferSource.prototype._fadeInCurve = new Float32Array(len); for (var i = 0; i < len; i++){ Tone.BufferSource.prototype._fadeInCurve[i] = 0.5 * (1 - Math.cos((i / (len - 1)) * Math.PI)); } /** * The envelope used to fade out the gain. * @type {Float32Array} * @private */ Tone.BufferSource.prototype._fadeOutCurve = new Float32Array(len); for (var j = 0; j < len; j++){ Tone.BufferSource.prototype._fadeOutCurve[j] = Tone.BufferSource.prototype._fadeInCurve[len - 1 - j]; } })(); /** * Clean up. * @return {Tone.BufferSource} this */ Tone.BufferSource.prototype.dispose = function(){ this.onended = null; if (this._source){ this._source.disconnect(); this._source = null; } if (this._gainNode){ this._gainNode.disconnect(); this._gainNode = null; } this._startTime = -1; this.playbackRate = null; this.output = null; return this; }; return Tone.BufferSource; });