2017-08-27 21:50:31 +00:00
|
|
|
define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source", "Tone/core/Gain", "Tone/core/AudioNode"], function (Tone) {
|
2016-06-14 23:57:46 +00:00
|
|
|
|
2017-03-13 05:12:20 +00:00
|
|
|
/**
|
|
|
|
* BufferSource polyfill
|
|
|
|
*/
|
|
|
|
if (window.AudioBufferSourceNode && !AudioBufferSourceNode.prototype.start){
|
|
|
|
AudioBufferSourceNode.prototype.start = AudioBufferSourceNode.prototype.noteGrainOn;
|
|
|
|
AudioBufferSourceNode.prototype.stop = AudioBufferSourceNode.prototype.noteOff;
|
|
|
|
}
|
|
|
|
|
2016-06-14 23:57:46 +00:00
|
|
|
/**
|
|
|
|
* @class Wrapper around the native BufferSourceNode.
|
2017-08-27 21:50:31 +00:00
|
|
|
* @extends {Tone.AudioNode}
|
2016-06-14 23:57:46 +00:00
|
|
|
* @param {AudioBuffer|Tone.Buffer} buffer The buffer to play
|
2017-08-27 21:50:31 +00:00
|
|
|
* @param {Function} onload The callback to invoke when the
|
2016-06-14 23:57:46 +00:00
|
|
|
* buffer is done playing.
|
|
|
|
*/
|
|
|
|
Tone.BufferSource = function(){
|
|
|
|
|
2017-05-01 22:52:53 +00:00
|
|
|
var options = Tone.defaults(arguments, ["buffer", "onload"], Tone.BufferSource);
|
2017-08-27 21:50:31 +00:00
|
|
|
Tone.AudioNode.call(this);
|
2016-06-14 23:57:46 +00:00
|
|
|
|
|
|
|
/**
|
2017-08-27 21:50:31 +00:00
|
|
|
* The callback to invoke after the
|
|
|
|
* buffer source is done playing.
|
2016-06-14 23:57:46 +00:00
|
|
|
* @type {Function}
|
|
|
|
*/
|
|
|
|
this.onended = options.onended;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The time that the buffer was started.
|
|
|
|
* @type {Number}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
this._startTime = -1;
|
|
|
|
|
2016-12-15 17:48:06 +00:00
|
|
|
/**
|
|
|
|
* The time that the buffer is scheduled to stop.
|
|
|
|
* @type {Number}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
this._stopTime = -1;
|
|
|
|
|
2016-06-14 23:57:46 +00:00
|
|
|
/**
|
|
|
|
* The gain node which envelopes the BufferSource
|
2016-09-20 03:53:07 +00:00
|
|
|
* @type {Tone.Gain}
|
2016-06-14 23:57:46 +00:00
|
|
|
* @private
|
|
|
|
*/
|
2016-09-20 03:53:07 +00:00
|
|
|
this._gainNode = this.output = new Tone.Gain();
|
2016-06-14 23:57:46 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The buffer source
|
|
|
|
* @type {AudioBufferSourceNode}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
this._source = this.context.createBufferSource();
|
|
|
|
this._source.connect(this._gainNode);
|
2017-05-01 22:52:53 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The private buffer instance
|
|
|
|
* @type {Tone.Buffer}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
this._buffer = new Tone.Buffer(options.buffer, options.onload);
|
2017-08-27 21:50:31 +00:00
|
|
|
|
2016-06-14 23:57:46 +00:00
|
|
|
/**
|
|
|
|
* The playbackRate of the buffer
|
2016-09-22 22:36:44 +00:00
|
|
|
* @type {Positive}
|
|
|
|
* @signal
|
2016-06-14 23:57:46 +00:00
|
|
|
*/
|
2016-09-22 22:36:44 +00:00
|
|
|
this.playbackRate = new Tone.Param(this._source.playbackRate, Tone.Type.Positive);
|
2016-06-14 23:57:46 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
|
2016-12-15 17:48:06 +00:00
|
|
|
/**
|
|
|
|
* The onended timeout
|
|
|
|
* @type {Number}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
this._onendedTimeout = -1;
|
|
|
|
|
2016-06-14 23:57:46 +00:00
|
|
|
this.loop = options.loop;
|
2017-05-02 16:15:00 +00:00
|
|
|
this.loopStart = options.loopStart;
|
|
|
|
this.loopEnd = options.loopEnd;
|
|
|
|
this.playbackRate.value = options.playbackRate;
|
2016-06-14 23:57:46 +00:00
|
|
|
};
|
|
|
|
|
2017-08-27 21:50:31 +00:00
|
|
|
Tone.extend(Tone.BufferSource, Tone.AudioNode);
|
2016-06-14 23:57:46 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The defaults
|
|
|
|
* @const
|
|
|
|
* @type {Object}
|
|
|
|
*/
|
|
|
|
Tone.BufferSource.defaults = {
|
|
|
|
"onended" : Tone.noOp,
|
2017-05-01 22:52:53 +00:00
|
|
|
"onload" : Tone.noOp,
|
2017-05-02 16:15:00 +00:00
|
|
|
"loop" : false,
|
|
|
|
"loopStart" : 0,
|
|
|
|
"loopEnd" : 0,
|
2016-06-14 23:57:46 +00:00
|
|
|
"fadeIn" : 0,
|
2017-05-02 16:15:00 +00:00
|
|
|
"fadeOut" : 0,
|
|
|
|
"playbackRate" : 1
|
2016-06-14 23:57:46 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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();
|
2016-12-15 17:48:06 +00:00
|
|
|
if (this._startTime !== -1 && now >= this._startTime && now < this._stopTime){
|
2016-06-14 23:57:46 +00:00
|
|
|
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
|
2017-08-27 21:50:31 +00:00
|
|
|
* to start at.
|
2016-06-14 23:57:46 +00:00
|
|
|
* @param {Time=} duration How long the sample should play. If no duration
|
2017-08-27 21:50:31 +00:00
|
|
|
* is given, it will default to the full length
|
2016-06-14 23:57:46 +00:00
|
|
|
* 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){
|
2017-04-26 04:00:01 +00:00
|
|
|
throw new Error("Tone.BufferSource can only be started once.");
|
2016-06-14 23:57:46 +00:00
|
|
|
}
|
|
|
|
|
2017-05-01 22:52:53 +00:00
|
|
|
if (this.buffer.loaded){
|
2016-08-09 05:14:21 +00:00
|
|
|
time = this.toSeconds(time);
|
|
|
|
//if it's a loop the default offset is the loopstart point
|
|
|
|
if (this.loop){
|
2017-04-30 19:03:49 +00:00
|
|
|
offset = Tone.defaultArg(offset, this.loopStart);
|
2016-08-09 05:14:21 +00:00
|
|
|
} else {
|
|
|
|
//otherwise the default offset is 0
|
2017-04-30 19:03:49 +00:00
|
|
|
offset = Tone.defaultArg(offset, 0);
|
2016-08-09 05:14:21 +00:00
|
|
|
}
|
|
|
|
offset = this.toSeconds(offset);
|
|
|
|
//the values in seconds
|
|
|
|
time = this.toSeconds(time);
|
2016-06-14 23:57:46 +00:00
|
|
|
|
2017-04-30 19:03:49 +00:00
|
|
|
gain = Tone.defaultArg(gain, 1);
|
2016-08-09 05:14:21 +00:00
|
|
|
this._gain = gain;
|
2016-06-14 23:57:46 +00:00
|
|
|
|
2016-08-09 05:14:21 +00:00
|
|
|
//the fadeIn time
|
2017-04-26 04:24:19 +00:00
|
|
|
if (Tone.isUndef(fadeInTime)){
|
2016-08-09 05:14:21 +00:00
|
|
|
fadeInTime = this.toSeconds(this.fadeIn);
|
|
|
|
} else {
|
|
|
|
fadeInTime = this.toSeconds(fadeInTime);
|
|
|
|
}
|
2016-06-14 23:57:46 +00:00
|
|
|
|
2016-08-09 05:14:21 +00:00
|
|
|
if (fadeInTime > 0){
|
|
|
|
this._gainNode.gain.setValueAtTime(0, time);
|
|
|
|
this._gainNode.gain.linearRampToValueAtTime(this._gain, time + fadeInTime);
|
|
|
|
} else {
|
|
|
|
this._gainNode.gain.setValueAtTime(gain, time);
|
|
|
|
}
|
2016-06-14 23:57:46 +00:00
|
|
|
|
2016-08-09 05:14:21 +00:00
|
|
|
this._startTime = time + fadeInTime;
|
2016-06-17 15:32:34 +00:00
|
|
|
|
2017-05-01 21:46:51 +00:00
|
|
|
var computedDur = Tone.defaultArg(duration, this.buffer.duration - offset);
|
|
|
|
computedDur = this.toSeconds(computedDur);
|
2017-05-02 16:15:00 +00:00
|
|
|
computedDur = Math.max(computedDur, 0);
|
2017-05-01 21:46:51 +00:00
|
|
|
|
|
|
|
if (!this.loop || (this.loop && !Tone.isUndef(duration))){
|
2017-05-01 22:00:31 +00:00
|
|
|
//clip the duration when not looping
|
|
|
|
if (!this.loop){
|
|
|
|
computedDur = Math.min(computedDur, this.buffer.duration - offset);
|
|
|
|
}
|
2017-06-19 19:03:10 +00:00
|
|
|
this.stop(time + computedDur + fadeInTime, this.fadeOut);
|
2016-08-09 05:14:21 +00:00
|
|
|
}
|
2017-05-02 16:15:00 +00:00
|
|
|
|
|
|
|
//start the buffer source
|
|
|
|
if (this.loop){
|
|
|
|
//modify the offset if it's greater than the loop time
|
|
|
|
var loopEnd = this.loopEnd || this.buffer.duration;
|
|
|
|
var loopStart = this.loopStart;
|
|
|
|
var loopDuration = loopEnd - loopStart;
|
|
|
|
//move the offset back
|
2017-05-23 13:30:17 +00:00
|
|
|
if (offset > loopEnd){
|
|
|
|
offset = ((offset - loopStart) % loopDuration) + loopStart;
|
2017-05-02 16:15:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
this._source.buffer = this.buffer.get();
|
2017-05-23 13:30:17 +00:00
|
|
|
this._source.loopEnd = this.loopEnd || this.buffer.duration;
|
2017-05-02 16:15:00 +00:00
|
|
|
this._source.start(time, offset);
|
2017-05-01 22:52:53 +00:00
|
|
|
} else {
|
2017-05-01 23:00:31 +00:00
|
|
|
throw new Error("Tone.BufferSource: buffer is either not set or not loaded.");
|
2016-06-17 15:32:34 +00:00
|
|
|
}
|
2016-06-14 23:57:46 +00:00
|
|
|
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2017-08-27 21:50:31 +00:00
|
|
|
* Stop the buffer. Optionally add a ramp time to fade the
|
|
|
|
* buffer out.
|
2016-06-14 23:57:46 +00:00
|
|
|
* @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){
|
2017-05-01 22:52:53 +00:00
|
|
|
if (this.buffer.loaded){
|
2016-06-14 23:57:46 +00:00
|
|
|
|
2016-08-09 05:14:21 +00:00
|
|
|
time = this.toSeconds(time);
|
2017-08-27 21:50:31 +00:00
|
|
|
|
2016-08-09 05:14:21 +00:00
|
|
|
//the fadeOut time
|
2017-04-26 04:24:19 +00:00
|
|
|
if (Tone.isUndef(fadeOutTime)){
|
2016-08-09 05:14:21 +00:00
|
|
|
fadeOutTime = this.toSeconds(this.fadeOut);
|
|
|
|
} else {
|
|
|
|
fadeOutTime = this.toSeconds(fadeOutTime);
|
2017-08-27 21:50:31 +00:00
|
|
|
}
|
2016-06-14 23:57:46 +00:00
|
|
|
|
2017-05-29 01:20:17 +00:00
|
|
|
//only stop if the last stop was scheduled later
|
2017-06-19 19:03:10 +00:00
|
|
|
if (this._stopTime === -1 || this._stopTime > time){
|
2017-08-27 21:50:31 +00:00
|
|
|
this._stopTime = time;
|
2017-05-29 01:20:17 +00:00
|
|
|
|
|
|
|
//cancel the end curve
|
|
|
|
this._gainNode.gain.cancelScheduledValues(this._startTime + this.sampleTime);
|
|
|
|
time = Math.max(this._startTime, time);
|
|
|
|
|
|
|
|
//set a new one
|
|
|
|
if (fadeOutTime > 0){
|
2017-06-19 19:03:10 +00:00
|
|
|
var startFade = Math.max(this._startTime, time - fadeOutTime);
|
|
|
|
this._gainNode.gain.setValueAtTime(this._gain, startFade);
|
2017-05-29 01:20:17 +00:00
|
|
|
this._gainNode.gain.linearRampToValueAtTime(0, time);
|
|
|
|
} else {
|
|
|
|
this._gainNode.gain.setValueAtTime(0, time);
|
|
|
|
}
|
2016-06-14 23:57:46 +00:00
|
|
|
|
2017-05-29 01:20:17 +00:00
|
|
|
Tone.context.clearTimeout(this._onendedTimeout);
|
|
|
|
this._onendedTimeout = Tone.context.setTimeout(this._onended.bind(this), this._stopTime - this.now());
|
2016-08-09 05:14:21 +00:00
|
|
|
}
|
2017-05-01 22:52:53 +00:00
|
|
|
} else {
|
2017-05-01 23:00:31 +00:00
|
|
|
throw new Error("Tone.BufferSource: buffer is either not set or not loaded.");
|
2016-06-14 23:57:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2017-08-27 21:50:31 +00:00
|
|
|
* Internal callback when the buffer is ended.
|
2016-06-14 23:57:46 +00:00
|
|
|
* Invokes `onended` and disposes the node.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Tone.BufferSource.prototype._onended = function(){
|
|
|
|
this.onended(this);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2017-08-27 21:50:31 +00:00
|
|
|
* If loop is true, the loop will start at this position.
|
2016-06-14 23:57:46 +00:00
|
|
|
* @memberOf Tone.BufferSource#
|
|
|
|
* @type {Time}
|
|
|
|
* @name loopStart
|
|
|
|
*/
|
|
|
|
Object.defineProperty(Tone.BufferSource.prototype, "loopStart", {
|
|
|
|
get : function(){
|
|
|
|
return this._source.loopStart;
|
2017-08-27 21:50:31 +00:00
|
|
|
},
|
2016-06-14 23:57:46 +00:00
|
|
|
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;
|
2017-08-27 21:50:31 +00:00
|
|
|
},
|
2016-06-14 23:57:46 +00:00
|
|
|
set : function(loopEnd){
|
|
|
|
this._source.loopEnd = this.toSeconds(loopEnd);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
2017-08-27 21:50:31 +00:00
|
|
|
* The audio buffer belonging to the player.
|
2016-06-14 23:57:46 +00:00
|
|
|
* @memberOf Tone.BufferSource#
|
2017-05-01 22:52:53 +00:00
|
|
|
* @type {Tone.Buffer}
|
2016-06-14 23:57:46 +00:00
|
|
|
* @name buffer
|
|
|
|
*/
|
|
|
|
Object.defineProperty(Tone.BufferSource.prototype, "buffer", {
|
|
|
|
get : function(){
|
2017-05-01 22:52:53 +00:00
|
|
|
return this._buffer;
|
2017-08-27 21:50:31 +00:00
|
|
|
},
|
2016-06-14 23:57:46 +00:00
|
|
|
set : function(buffer){
|
2017-05-01 22:52:53 +00:00
|
|
|
this._buffer.set(buffer);
|
2016-06-14 23:57:46 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
2017-08-27 21:50:31 +00:00
|
|
|
* If the buffer should loop once it's over.
|
2016-06-14 23:57:46 +00:00
|
|
|
* @memberOf Tone.BufferSource#
|
2017-05-01 22:52:53 +00:00
|
|
|
* @type {Boolean}
|
2016-06-14 23:57:46 +00:00
|
|
|
* @name loop
|
|
|
|
*/
|
|
|
|
Object.defineProperty(Tone.BufferSource.prototype, "loop", {
|
|
|
|
get : function(){
|
|
|
|
return this._source.loop;
|
2017-08-27 21:50:31 +00:00
|
|
|
},
|
2016-06-14 23:57:46 +00:00
|
|
|
set : function(loop){
|
|
|
|
this._source.loop = loop;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clean up.
|
|
|
|
* @return {Tone.BufferSource} this
|
|
|
|
*/
|
|
|
|
Tone.BufferSource.prototype.dispose = function(){
|
2017-08-27 21:50:31 +00:00
|
|
|
Tone.AudioNode.prototype.dispose.call(this);
|
2016-06-14 23:57:46 +00:00
|
|
|
this.onended = null;
|
2017-05-01 22:52:53 +00:00
|
|
|
this._source.disconnect();
|
|
|
|
this._source = null;
|
|
|
|
this._gainNode.dispose();
|
|
|
|
this._gainNode = null;
|
|
|
|
this._buffer.dispose();
|
|
|
|
this._buffer = null;
|
2016-06-14 23:57:46 +00:00
|
|
|
this._startTime = -1;
|
|
|
|
this.playbackRate = null;
|
2017-05-01 21:46:51 +00:00
|
|
|
Tone.context.clearTimeout(this._onendedTimeout);
|
2016-06-14 23:57:46 +00:00
|
|
|
return this;
|
|
|
|
};
|
|
|
|
|
|
|
|
return Tone.BufferSource;
|
2017-08-27 21:50:31 +00:00
|
|
|
});
|