define(["Tone/core/Tone", "Tone/source/Source", "Tone/core/Buffer", "Tone/source/BufferSource"], function(Tone){ /** * @class Tone.GrainPlayer implements [granular synthesis](https://en.wikipedia.org/wiki/Granular_synthesis). * Granular Synthesis enables you to adjust pitch and playback rate independently. The grainSize is the * amount of time each small chunk of audio is played for and the overlap is the * amount of crossfading transition time between successive grains. * @extends {Tone.Source} * @param {String|Tone.Buffer} url The url to load, or the Tone.Buffer to play. * @param {Function=} callback The callback to invoke after the url is loaded. */ Tone.GrainPlayer = function(){ var options = Tone.defaults(arguments, ["url", "onload"], Tone.GrainPlayer); Tone.Source.call(this, options); /** * The audio buffer belonging to the player. * @type {Tone.Buffer} */ this.buffer = new Tone.Buffer(options.url, options.onload); /** * Create a repeating tick to schedule * the grains. * @type {Tone.Clock} * @private */ this._clock = new Tone.Clock(this._tick.bind(this), options.grainSize); /** * @type {Number} * @private */ this._loopStart = 0; /** * @type {Number} * @private */ this._loopEnd = 0; /** * All of the currently playing BufferSources * @type {Array} * @private */ this._activeSources = []; /** * @type {Number} * @private */ this._playbackRate = options.playbackRate; /** * @type {Number} * @private */ this._grainSize = options.grainSize; /** * @private * @type {Number} */ this._overlap = options.overlap; /** * Adjust the pitch independently of the playbackRate. * @type {Cents} */ this.detune = options.detune; //setup this.overlap = options.overlap; this.loop = options.loop; this.playbackRate = options.playbackRate; this.grainSize = options.grainSize; this.loopStart = options.loopStart; this.loopEnd = options.loopEnd; this.reverse = options.reverse; this._clock.on("stop", this._onstop.bind(this)); }; Tone.extend(Tone.GrainPlayer, Tone.Source); /** * the default parameters * @static * @const * @type {Object} */ Tone.GrainPlayer.defaults = { "onload" : Tone.noOp, "overlap" : 0.1, "grainSize" : 0.2, "playbackRate" : 1, "detune" : 0, "loop" : false, "loopStart" : 0, "loopEnd" : 0, "reverse" : false }; /** * Play the buffer at the given startTime. Optionally add an offset * and/or duration which will play the buffer from a position * within the buffer for the given duration. * * @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) * @returns {Tone.GrainPlayer} this * @memberOf Tone.GrainPlayer# * @method start * @name start */ /** * Internal start method * @param {Time} time * @param {Time} offset * @private */ Tone.GrainPlayer.prototype._start = function(time, offset, duration){ offset = Tone.defaultArg(offset, 0); offset = this.toSeconds(offset); time = this.toSeconds(time); this._offset = offset; this._clock.start(time); if (duration){ this.stop(time + this.toSeconds(duration)); } }; /** * Internal start method * @param {Time} time * @private */ Tone.GrainPlayer.prototype._stop = function(time){ this._clock.stop(time); }; /** * Invoked when the clock is stopped * @param {Number} time * @private */ Tone.GrainPlayer.prototype._onstop = function(time){ //stop the players this._activeSources.forEach(function(source){ source.stop(time, 0); }); }; /** * Invoked on each clock tick. scheduled a new * grain at this time. * @param {Time} time * @private */ Tone.GrainPlayer.prototype._tick = function(time){ var fadeIn = this._offset < this._overlap ? 0 : this._overlap; var source = new Tone.BufferSource({ "buffer" : this.buffer, "fadeIn" : fadeIn, "fadeOut" : this._overlap, "loop" : this.loop, "loopStart" : this._loopStart, "loopEnd" : this._loopEnd, "playbackRate" : Tone.intervalToFrequencyRatio(this.detune / 100) }).connect(this.output); source.start(time, this._offset); this._offset += this.grainSize; source.stop(time + this.grainSize); //add it to the active sources this._activeSources.push(source); //remove it when it's done source.onended = function(){ var index = this._activeSources.indexOf(source); if (index !== -1){ this._activeSources.splice(index, 1); } }.bind(this); }; /** * Jump to a specific time and play it. * @param {Time} offset The offset to jump to. * @param {Time=} time When to make the jump. * @return {Tone.GrainPlayer} this */ Tone.GrainPlayer.prototype.seek = function(offset, time){ this._offset = this.toSeconds(offset); this._tick(this.toSeconds(time)); return this; }; /** * The playback rate of the sample * @memberOf Tone.GrainPlayer# * @type {Positive} * @name playbackRate */ Object.defineProperty(Tone.GrainPlayer.prototype, "playbackRate", { get : function(){ return this._playbackRate; }, set : function(rate){ this._playbackRate = rate; this.grainSize = this._grainSize; } }); /** * The loop start time. * @memberOf Tone.GrainPlayer# * @type {Time} * @name loopStart */ Object.defineProperty(Tone.GrainPlayer.prototype, "loopStart", { get : function(){ return this._loopStart; }, set : function(time){ this._loopStart = this.toSeconds(time); } }); /** * The loop end time. * @memberOf Tone.GrainPlayer# * @type {Time} * @name loopEnd */ Object.defineProperty(Tone.GrainPlayer.prototype, "loopEnd", { get : function(){ return this._loopEnd; }, set : function(time){ this._loopEnd = this.toSeconds(time); } }); /** * The direction the buffer should play in * @memberOf Tone.GrainPlayer# * @type {boolean} * @name reverse */ Object.defineProperty(Tone.GrainPlayer.prototype, "reverse", { get : function(){ return this.buffer.reverse; }, set : function(rev){ this.buffer.reverse = rev; } }); /** * The size of each chunk of audio that the * buffer is chopped into and played back at. * @memberOf Tone.GrainPlayer# * @type {Time} * @name grainSize */ Object.defineProperty(Tone.GrainPlayer.prototype, "grainSize", { get : function(){ return this._grainSize; }, set : function(size){ this._grainSize = this.toSeconds(size); this._clock.frequency.value = this._playbackRate / this._grainSize; } }); /** * This is the duration of the cross-fade between * sucessive grains. * @memberOf Tone.GrainPlayer# * @type {Time} * @name overlap */ Object.defineProperty(Tone.GrainPlayer.prototype, "overlap", { get : function(){ return this._overlap; }, set : function(time){ this._overlap = this.toSeconds(time); } }); /** * Clean up * @return {Tone.GrainPlayer} this */ Tone.GrainPlayer.prototype.dispose = function(){ Tone.Source.prototype.dispose.call(this); this.buffer.dispose(); this.buffer = null; this._clock.dispose(); this._clock = null; this._activeSources.forEach(function(source){ source.dispose(); }); this._activeSources = null; return this; }; return Tone.GrainPlayer; });