File:SoundInstance.js

/**
 * @module Sound
 * @namespace springroll
 * @requires Core
 */
(function()
{
	var Sound;

	/**
	 * A playing instance of a sound (or promise to play as soon as it loads). These can only
	 * be created through springroll.Sound.instance.play().
	 * @class SoundInstance
	 */
	var SoundInstance = function()
	{
		if (!Sound)
		{
			Sound = include('springroll.Sound');
		}

		/**
		 * SoundJS SoundInstance, essentially a sound channel.
		 * @property {createjs.SoundInstance} _channel
		 * @private
		 */
		this._channel = null;

		/**
		 * Internal callback function for when the sound ends.
		 * @property {function} _endFunc
		 * @private
		 */
		this._endFunc = null;

		/**
		 * User's callback function for when the sound ends.
		 * @property {function} _endCallback
		 * @private
		 */
		this._endCallback = null;

		/**
		 * User's callback function for when the sound starts.
		 * This is only used if the sound wasn't loaded before play() was called.
		 * @property {function} _startFunc
		 * @private
		 */
		this._startFunc = null;

		/**
		 * An array of relevant parameters passed to play(). This is only used if
		 * the sound wasn't loaded before play() was called.
		 * @property {Array} _startParams
		 * @private
		 */
		this._startParams = null;

		/**
		 * The alias for the sound that this instance was created from.
		 * @property {String} alias
		 * @public
		 * @readOnly
		 */
		this.alias = null;

		/**
		 * The current time in milliseconds for the fade that this sound instance is performing.
		 * @property {Number} _fTime
		 * @private
		 */
		this._fTime = 0;

		/**
		 * The duration in milliseconds for the fade that this sound instance is performing.
		 * @property {Number} _fDur
		 * @private
		 */
		this._fDur = 0;

		/**
		 * The starting volume for the fade that this sound instance is performing.
		 * @property {Number} _fEnd
		 * @private
		 */
		this._fStart = 0;

		/**
		 * The ending volume for the fade that this sound instance is performing.
		 * @property {Number} _fEnd
		 * @private
		 */
		this._fEnd = 0;

		/**
		 * The current sound volume (0 to 1). This is multiplied by the sound context's volume.
		 * Setting this won't take effect until updateVolume() is called.
		 * @property {Number} curVol
		 * @protected
		 * @readOnly
		 */
		this.curVol = 0;

		/**
		 * The sound pan value, from -1 (left) to 1 (right).
		 * @property {Number} _pan
		 * @private
		 * @readOnly
		 */
		this._pan = 0;

		/**
		 * The length of the sound in milliseconds. This is 0 if it hasn't finished loading.
		 * @property {Number} length
		 * @public
		 */
		this.length = 0;

		/**
		 * If the sound is currently paused. Setting this has no effect - use pause()
		 * and resume().
		 * @property {Boolean} paused
		 * @public
		 * @readOnly
		 */
		this.paused = false;

		/**
		 * If the sound is paused due to a global pause, probably from the Application.
		 * @property {Boolean} globallyPaused
		 * @readOnly
		 */
		this.globallyPaused = false;

		/**
		 * An active SoundInstance should always be valid, but if you keep a reference after a
		 * sound stops it will no longer be valid (until the SoundInstance is reused for a
		 * new sound).
		 * @property {Boolean} isValid
		 * @public
		 * @readOnly
		 */
		this.isValid = true;
	};

	// Reference to the prototype
	var p = extend(SoundInstance);

	/**
	 * The position of the sound playhead in milliseconds, or 0 if it hasn't started playing yet.
	 * @property {Number} position
	 * @public
	 * @readOnly
	 */
	Object.defineProperty(p, "position",
	{
		get: function()
		{
			return this._channel ? this._channel.getPosition() : 0;
		}
	});

	/**
	 * Stops this SoundInstance.
	 * @method stop
	 * @public
	 */
	p.stop = function()
	{
		var s = Sound.instance;
		if (s)
		{
			var sound = s._sounds[this.alias];
			//in case this SoundInstance is not valid any more for some reason
			if (!sound) return;

			var index = sound.playing.indexOf(this);
			if (index > -1)
				sound.playing.splice(index, 1);

			index = sound.waitingToPlay.indexOf(this);
			if (index > -1)
				sound.waitingToPlay.splice(index, 1);

			s._stopInst(this);
		}
	};

	/**
	 * Updates the volume of this SoundInstance.
	 * @method updateVolume
	 * @protected
	 * @param {Number} contextVol The volume of the sound context that the sound belongs to. If
	 *                          omitted, the volume is automatically collected.
	 */
	p.updateVolume = function(contextVol)
	{
		if (!this._channel) return;
		if (contextVol === undefined)
		{
			var s = Sound.instance;
			var sound = s._sounds[this.alias];
			if (sound.context)
			{
				var context = s._contexts[sound.context];
				contextVol = context.muted ? 0 : context.volume;
			}
			else
				contextVol = 1;
		}
		this._channel.setVolume(contextVol * this.curVol);
	};

	/**
	 * The current sound volume (0 to 1). This is multiplied by the sound context's volume to
	 * get the actual sound volume.
	 * @property {Number} volume
	 * @public
	 */
	Object.defineProperty(p, "volume",
	{
		get: function()
		{
			return this.curVol;
		},
		set: function(value)
		{
			this.curVol = value;
			this.updateVolume();
		}
	});

	/**
	 * The sound pan value, from -1 (left) to 1 (right).
	 * @property {Number} pan
	 * @public
	 */
	Object.defineProperty(p, "pan",
	{
		get: function()
		{
			return this._pan;
		},
		set: function(value)
		{
			this._pan = value;
			if (this._channel)
				this._channel.pan = value;
		}
	});

	/**
	 * Pauses this SoundInstance.
	 * @method pause
	 * @public
	 */
	p.pause = function()
	{
		//ensure that this is marked as a manual pause
		this.globallyPaused = false;
		if (this.paused) return;
		this.paused = true;
		if (!this._channel) return;
		this._channel.paused = true;
		Sound.instance._onInstancePaused();
	};

	/**
	 * Unpauses this SoundInstance.
	 * @method resume
	 * @public
	 */
	p.resume = function()
	{
		if (!this.paused) return;
		this.paused = false;
		if (!this._channel) return;
		Sound.instance._onInstanceResume();
		this._channel.paused = false;
		if (this._channel.gainNode)
		{
			//reset values on the channel to ensure that the volume update takes -
			//the default volume on the audio after playing/resuming will be 1
			this._channel._volume = -1;
			this._channel.gainNode.gain.value = 0;
		}
		this.updateVolume();
	};

	namespace('springroll').SoundInstance = SoundInstance;

}());