text: define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source", "Tone/source/TickSource",
  	"Tone/source/BufferSource"], function(Tone){
  
  	"use strict";
  
  	/**
  	 *  @class  Tone.Player is an audio file player with start, loop, and stop functions.
  	 *
  	 *  @constructor
  	 *  @extends {Tone.Source}
  	 *  @param {string|AudioBuffer} url Either the AudioBuffer or the url from
  	 *                                  which to load the AudioBuffer
  	 *  @param {Function=} onload The function to invoke when the buffer is loaded.
  	 *                            Recommended to use Tone.Buffer.on('load') instead.
  	 *  @example
  	 * var player = new Tone.Player("./path/to/sample.mp3").toMaster();
  	 * //play as soon as the buffer is loaded
  	 * player.autostart = true;
  	 */
  	Tone.Player = function(url){
  
  		var options;
  		if (url instanceof Tone.Buffer && url.loaded){
  			url = url.get();
  			options = Tone.Player.defaults;
  		} else {
  			options = Tone.defaults(arguments, ["url", "onload"], Tone.Player);
  		}
  		Tone.Source.call(this, options);
  
  		/**
  		 *  If the file should play as soon
  		 *  as the buffer is loaded.
  		 *  @type {Boolean}
  		 *  @example
  		 * //will play as soon as it's loaded
  		 * var player = new Tone.Player({
  		 * 	"url" : "./path/to/sample.mp3",
  		 * 	"autostart" : true,
  		 * }).toMaster();
  		 */
  		this.autostart = options.autostart;
  
  		/**
  		 *  the buffer
  		 *  @private
  		 *  @type {Tone.Buffer}
  		 */
  		this._buffer = new Tone.Buffer({
  			"url" : options.url,
  			"onload" : this._onload.bind(this, options.onload),
  			"reverse" : options.reverse
  		});
  		if (url instanceof AudioBuffer){
  			this._buffer.set(url);
  		}
  
  		/**
  		 *  if the buffer should loop once it's over
  		 *  @type {Boolean}
  		 *  @private
  		 */
  		this._loop = options.loop;
  
  		/**
  		 *  if 'loop' is true, the loop will start at this position
  		 *  @type {Time}
  		 *  @private
  		 */
  		this._loopStart = options.loopStart;
  
  		/**
  		 *  if 'loop' is true, the loop will end at this position
  		 *  @type {Time}
  		 *  @private
  		 */
  		this._loopEnd = options.loopEnd;
  
  		/**
  		 *  the playback rate
  		 *  @private
  		 *  @type {Number}
  		 */
  		this._playbackRate = options.playbackRate;
  
  		/**
  		 *  All of the active buffer source nodes
  		 *  @type {Array<Tone.BufferSource>}
  		 *  @private
  		 */
  		this._activeSources = [];
  
  		/**
  		 *  The elapsed time counter.
  		 *  @type {Tone.TickSource}
  		 *  @private
  		 */
  		this._elapsedTime = new Tone.TickSource(options.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;
  	};
  
  	Tone.extend(Tone.Player, Tone.Source);
  
  	/**
  	 *  the default parameters
  	 *  @static
  	 *  @const
  	 *  @type {Object}
  	 */
  	Tone.Player.defaults = {
  		"onload" : Tone.noOp,
  		"playbackRate" : 1,
  		"loop" : false,
  		"autostart" : false,
  		"loopStart" : 0,
  		"loopEnd" : 0,
  		"retrigger" : false,
  		"reverse" : false,
  		"fadeIn" : 0,
  		"fadeOut" : 0
  	};
  
  	/**
  	 *  Load the audio file as an audio buffer.
  	 *  Decodes the audio asynchronously and invokes
  	 *  the callback once the audio buffer loads.
  	 *  Note: this does not need to be called if a url
  	 *  was passed in to the constructor. Only use this
  	 *  if you want to manually load a new url.
  	 * @param {string} url The url of the buffer to load.
  	 *                     Filetype support depends on the
  	 *                     browser.
  	 *  @param  {Function=} callback The function to invoke once
  	 *                               the sample is loaded.
  	 *  @returns {Promise}
  	 */
  	Tone.Player.prototype.load = function(url, callback){
  		return this._buffer.load(url, this._onload.bind(this, callback));
  	};
  
  	/**
  	 * Internal callback when the buffer is loaded.
  	 * @private
  	 */
  	Tone.Player.prototype._onload = function(callback){
  		callback = Tone.defaultArg(callback, Tone.noOp);
  		callback(this);
  		if (this.autostart){
  			this.start();
  		}
  	};
  
  	/**
  	 * Internal callback when the buffer is done playing.
  	 * @private
  	 */
  	Tone.Player.prototype._onSourceEnd = function(source){
  		var index = this._activeSources.indexOf(source);
  		this._activeSources.splice(index, 1);
  	};
  
  	/**
  	 *  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.Player} this
  	 *  @memberOf Tone.Player#
  	 *  @method start
  	 *  @name start
  	 */
  
  	/**
  	 *  Internal start method
  	 *  @private
  	 */
  	Tone.Player.prototype._start = function(startTime, offset, duration){
  		//if it's a loop the default offset is the loopstart point
  		if (this._loop){
  			offset = Tone.defaultArg(offset, this._loopStart);
  		} else {
  			//otherwise the default offset is 0
  			offset = Tone.defaultArg(offset, 0);
  		}
  
  		//compute the values in seconds
  		offset = this.toSeconds(offset);
  		var computedDuration = Tone.defaultArg(duration, Math.max(this._buffer.duration - offset, 0));
  		computedDuration = this.toSeconds(computedDuration);
  		startTime = this.toSeconds(startTime);
  
  		//start the elapsed time counter
  		this._elapsedTime.start(startTime, offset);
  
  		//make the source
  		var source = new Tone.BufferSource({
  			"buffer" : this._buffer,
  			"loop" : this._loop,
  			"loopStart" : this._loopStart,
  			"loopEnd" : this._loopEnd,
  			"onended" : this._onSourceEnd.bind(this),
  			"playbackRate" : this._playbackRate,
  			"fadeIn" : this.fadeIn,
  			"fadeOut" : this.fadeOut,
  		}).connect(this.output);
  
  		//set the looping properties
  		if (!this._loop && !this._synced){
  			//if it's not looping, set the state change at the end of the sample
  			this._state.setStateAtTime(Tone.State.Stopped, startTime + computedDuration / this._playbackRate);
  		}
  
  		//add it to the array of active sources
  		this._activeSources.push(source);
  
  		//start it
  		if (this._loop && Tone.isUndef(duration)){
  			source.start(startTime, offset);
  		} else {
  			source.start(startTime, offset, computedDuration);
  		}
  		return this;
  	};
  
  	/**
  	 *  Stop playback.
  	 *  @private
  	 *  @param  {Time} [time=now]
  	 *  @returns {Tone.Player} this
  	 */
  	Tone.Player.prototype._stop = function(time){
  		time = this.toSeconds(time);
  		this._elapsedTime.stop(time);
  		this._activeSources.forEach(function(source){
  			source.stop(time);
  		});
  		return this;
  	};
  
  	/**
  	 * Stop and then restart the player from the beginning (or offset)
  	 *  @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.Player} this
  	 */
  	Tone.Player.prototype.restart = function(time, offset, duration){
  		this._stop(time);
  		this._start(time, offset, duration);
  		return this;
  	};
  
  	/**
  	 *  Seek to a specific time in the player's buffer. If the
  	 *  source is no longer playing at that time, it will stop.
  	 *  If you seek to a time that
  	 *  @param {Time} offset The time to seek to.
  	 *  @param {Time=} time The time for the seek event to occur.
  	 *  @return {Tone.Player} this
  	 *  @example
  	 * source.start(0.2);
  	 * source.stop(0.4);
  	 */
  	Tone.Player.prototype.seek = function(offset, time){
  		time = this.toSeconds(time);
  		if (this._state.getValueAtTime(time) === Tone.State.Started){
  			offset = this.toSeconds(offset);
  			// if it's currently playing, stop it
  			this._stop(time);
  			//restart it at the given time
  			this._start(time, offset);
  		}
  		return this;
  	};
  
  	/**
  	 *  Set the loop start and end. Will only loop if loop is
  	 *  set to true.
  	 *  @param {Time} loopStart The loop end time
  	 *  @param {Time} loopEnd The loop end time
  	 *  @returns {Tone.Player} this
  	 *  @example
  	 * //loop 0.1 seconds of the file.
  	 * player.setLoopPoints(0.2, 0.3);
  	 * player.loop = true;
  	 */
  	Tone.Player.prototype.setLoopPoints = function(loopStart, loopEnd){
  		this.loopStart = loopStart;
  		this.loopEnd = loopEnd;
  		return this;
  	};
  
  	/**
  	 * If loop is true, the loop will start at this position.
  	 * @memberOf Tone.Player#
  	 * @type {Time}
  	 * @name loopStart
  	 */
  	Object.defineProperty(Tone.Player.prototype, "loopStart", {
  		get : function(){
  			return this._loopStart;
  		},
  		set : function(loopStart){
  			this._loopStart = loopStart;
  			//get the current source
  			this._activeSources.forEach(function(source){
  				source.loopStart = loopStart;
  			});
  		}
  	});
  
  	/**
  	 * If loop is true, the loop will end at this position.
  	 * @memberOf Tone.Player#
  	 * @type {Time}
  	 * @name loopEnd
  	 */
  	Object.defineProperty(Tone.Player.prototype, "loopEnd", {
  		get : function(){
  			return this._loopEnd;
  		},
  		set : function(loopEnd){
  			this._loopEnd = loopEnd;
  			//get the current source
  			this._activeSources.forEach(function(source){
  				source.loopEnd = loopEnd;
  			});
  		}
  	});
  
  	/**
  	 * The audio buffer belonging to the player.
  	 * @memberOf Tone.Player#
  	 * @type {Tone.Buffer}
  	 * @name buffer
  	 */
  	Object.defineProperty(Tone.Player.prototype, "buffer", {
  		get : function(){
  			return this._buffer;
  		},
  		set : function(buffer){
  			this._buffer.set(buffer);
  		}
  	});
  
  	/**
  	 * If the buffer should loop once it's over.
  	 * @memberOf Tone.Player#
  	 * @type {Boolean}
  	 * @name loop
  	 */
  	Object.defineProperty(Tone.Player.prototype, "loop", {
  		get : function(){
  			return this._loop;
  		},
  		set : function(loop){
  			//if no change, do nothing
  			if (this._loop === loop){
  				return;
  			}
  			this._loop = loop;
  			var now = this.now();
  			if (!loop){
  				//stop the playback on the next cycle
  				this._stopAtNextIteration(now);
  			} else {
  				//remove the next stopEvent
  				var stopEvent = this._state.getNextState(Tone.State.Stopped, now);
  				if (stopEvent){
  					this._activeSources.forEach(function(source){
  						source.loop = loop;
  					});
  					this._state.cancel(stopEvent.time);
  					this._elapsedTime.cancel(stopEvent.time);
  				}
  			}
  		}
  	});
  
  	/**
  	 *  Schedules a stop event at the next full iteration. Used
  	 *  for scheduling stop when the loop state or playbackRate changes
  	 *  @param  {Number}  now  The current time
  	 *  @private
  	 */
  	Tone.Player.prototype._stopAtNextIteration = function(now){
  		if (this._state.getValueAtTime(now) === Tone.State.Started){
  			var nextStop = this._state.getNextState(Tone.State.Stopped, now);
  			var position = this._elapsedTime.getTicksAtTime(now);
  			var iterations = Math.max(Math.ceil(position / this.buffer.duration), 1);
  			var stopTime = this._elapsedTime.getTimeOfTick(iterations * this.buffer.duration, nextStop ? nextStop.time - this.sampleTime : Infinity);
  			this.stop(stopTime);
  		}
  	};
  
  	/**
  	 * The playback speed. 1 is normal speed. This is not a signal because
  	 * Safari and iOS currently don't support playbackRate as a signal.
  	 * @memberOf Tone.Player#
  	 * @type {Number}
  	 * @name playbackRate
  	 */
  	Object.defineProperty(Tone.Player.prototype, "playbackRate", {
  		get : function(){
  			return this._playbackRate;
  		},
  		set : function(rate){
  			this._playbackRate = rate;
  			var now = this.now();
  			this._elapsedTime.frequency.setValueAtTime(rate, now);
  			//if it's not looping
  			if (!this._loop){
  				this._stopAtNextIteration(now);
  			}
  			//set all the sources
  			this._activeSources.forEach(function(source){
  				source.playbackRate.setValueAtTime(rate, now);
  			});
  		}
  	});
  
  	/**
  	 * The current playback position of the buffer. 
  	 * @memberOf Tone.Player#
  	 * @type {Number}
  	 * @name position
  	 */
  	Object.defineProperty(Tone.Player.prototype, "position", {
  		get : function(){
  			var now = this.now();
  			if (this._state.getValueAtTime(now) === Tone.State.Started && this.loaded){
  				var duration = this.buffer.duration;
  				var position = this._elapsedTime.getTicksAtTime(now);
  				return position % duration;
  			} else {
  				return 0;
  			}
  		}
  	});
  
  	/**
  	 * The direction the buffer should play in
  	 * @memberOf Tone.Player#
  	 * @type {Boolean}
  	 * @name reverse
  	 */
  	Object.defineProperty(Tone.Player.prototype, "reverse", {
  		get : function(){
  			return this._buffer.reverse;
  		},
  		set : function(rev){
  			this._buffer.reverse = rev;
  		}
  	});
  
  	/**
  	 * If all the buffer is loaded
  	 * @memberOf Tone.Player#
  	 * @type {Boolean}
  	 * @name loaded
  	 * @readOnly
  	 */
  	Object.defineProperty(Tone.Player.prototype, "loaded", {
  		get : function(){
  			return this._buffer.loaded;
  		}
  	});
  
  	/**
  	 *  Dispose and disconnect.
  	 *  @return {Tone.Player} this
  	 */
  	Tone.Player.prototype.dispose = function(){
  		//disconnect all of the players
  		this._activeSources.forEach(function(source){
  			source.dispose();
  		});
  		this._activeSources = null;
  		Tone.Source.prototype.dispose.call(this);
  		this._buffer.dispose();
  		this._buffer = null;
  		this._elapsedTime.dispose();
  		this._elapsedTime = null;
  		return this;
  	};
  
  	return Tone.Player;
  });
  