/**
* A FORGE.Sound is an object that manages a sound.
*
* @constructor FORGE.Sound
* @param {FORGE.Viewer} viewer - The {@link FORGE.Viewer} reference.
* @param {string} key - The sound file id reference.
* @param {string} url - The sound file url.
* @param {boolean=} ambisonic - Is the sound ambisonic and need binaural rendering?
* @extends {FORGE.BaseObject}
*
* @todo Ability to force audio type into config
* @todo Make a test plugin that creates sound, add sound to the PluginObjectFactory
* @todo Loop during x steps (parameter) only if loop is true
*/
FORGE.Sound = function(viewer, key, url, ambisonic)
{
/**
* The viewer reference.
* @name FORGE.Sound#_viewer
* @type {FORGE.Viewer}
* @private
*/
this._viewer = viewer;
/**
* The sound identifier.
* @name FORGE.Sound#_key
* @type {string}
* @private
*/
this._key = key;
/**
* The sound file url.
* @name FORGE.Sound#_url
* @type {string}
* @private
*/
this._url = url;
/**
* The current volume of the sound.
* @name FORGE.Sound#_volume
* @type {number}
* @private
*/
this._volume = 1;
/**
* The volume level before a mute of the sound.
* @name FORGE.Sound#_mutedVolume
* @type {number}
* @private
*/
this._mutedVolume = 1;
/**
* The muted state of the sound.
* @name FORGE.Sound#_muted
* @type {boolean}
* @private
*/
this._muted = false;
/**
* Is the sound enabled?
* @name FORGE.Sound#enabled
* @type {boolean}
* @private
*/
this._enabled = true;
/**
* Is the sound spatialized?
* @name FORGE.Sound#_spatialized
* @type {boolean}
* @private
*/
this._spatialized = false;
/**
* The duration in seconds of the sound.
* @name FORGE.Sound#_duration
* @type {number}
* @private
*/
this._duration = 0;
/**
* The duration in milliseconds of the sound.
* @name FORGE.Sound#_durationMS
* @type {number}
* @private
*/
this._durationMS = 0;
/**
* The start time in milliseconds of the sound linked to the global clock.
* @name FORGE.Sound#_startTime
* @type {number}
* @private
*/
this._startTime = 0;
/**
* The pause time in milliseconds of the sound linked to the global clock.
* @name FORGE.Sound#_pauseTime
* @type {number}
* @private
*/
this._pauseTime = 0;
/**
* The current time in milliseconds of the sound.<br>
* The current time value is based on duration minus time on the current clock because the AudioContext currentTime value is global to the audio context.
* @name FORGE.Sound#_currentTime
* @type {number}
* @private
*/
this._currentTime = 0;
/**
* The loop state of the sound.
* @name FORGE.Sound#_loop
* @type {boolean}
* @private
*/
this._loop = false;
/**
* The playing state of the sound.
* @name FORGE.Sound#_playing
* @type {boolean}
* @private
*/
this._playing = false;
/**
* The number of play of the sound.
* @name FORGE.Sound#_playCount
* @type {number}
* @private
*/
this._playCount = 0;
/**
* The paused state of the sound.
* @name FORGE.Sound#_paused
* @type {boolean}
* @private
*/
this._paused = false;
/**
* The resumed state of the sound.
* @name FORGE.Sound#_resumed
* @type {boolean}
* @private
*/
this._resumed = false;
/**
* The sound file with augmented properties.
* @property {AudioBuffer} data The sound file data contained into an AudioBuffer.
* @name FORGE.Sound#_soundFile
* @type {?FORGE.File}
* @private
*/
this._soundFile = null;
/**
* The AudioBufferSourceNode instance used to play audio data contained within an AudioBuffer object.
* @name FORGE.Sound#_sound
* @type {?AudioBufferSourceNode}
* @private
*/
this._sound = null;
/**
* The sound file data contained into an AudioBuffer.
* @name FORGE.Sound#_buffer
* @type {?AudioBuffer}
* @private
*/
this._buffer = null;
/**
* The sound file position data contained into a PannerNode.
* @name FORGE.Sound#_panner
* @type {?AudioPannerNode}
* @private
*/
this._panner = null;
/**
* The AudioContext interface.
* @name FORGE.Sound#_context
* @type {?AudioContext}
* @private
*/
this._context = null;
/**
* The AudioDestinationNode representing the final destination of all audio in the context.
* @name FORGE.Sound#_inputNode
* @type {?AudioDestinationNode}
* @private
*/
this._inputNode = null;
/**
* The GainNode which can be used to control the overall volume of the audio graph.
* @type {?GainNode}
* @private
*/
this._gainNode = null;
/**
* THREE Vector3 x coordinate.
* @name FORGE.Sound#_x
* @type {number}
* @private
*/
this._x = 0;
/**
* THREE Vector3 y coordinate.
* @name FORGE.Sound#_y
* @type {number}
* @private
*/
this._y = 0;
/**
* THREE Vector3 z coordinate.
* @name FORGE.Sound#_z
* @type {number}
* @private
*/
this._z = 0;
/**
* FOADecoder is a ready-made FOA decoder and binaural renderer.
* @name FORGE.Sound#_decoder
* @type {?FOADecoder}
* @private
*/
this._decoder = null;
/**
* Is it an ambisonical sound?
* @name FORGE.Sound#_ambisonic
* @type {boolean}
* @private
*/
this._ambisonic = ambisonic || false;
/**
* Default channel map for ambisonic sound.
* @name FORGE.Sound#_defaultChannelMap
* @type {Array<number>}
* @private
*/
this._defaultChannelMap = [0, 1, 2, 3]; //AMBIX
// this._defaultChannelMap = [0, 3, 1, 2]; //FUMA
/**
* To save the pending state to be applied after the sound object will be ready.
* @name FORGE.Sound#_pendingPlay
* @type {boolean}
* @private
*/
this._pendingPlay = false;
/**
* Is this sound object is ready?
* @name FORGE.Sound#_ready
* @type {boolean}
* @private
*/
this._ready = false;
/**
* This is a reference to decodeComplete function but with a different this bind reference.
* @name FORGE.Sound#_decodeCompleteBind
* @type {?Function}
* @private
*/
this._decodeCompleteBind = null;
/**
* This is a reference to decodeError function but with a different this bind reference.
* @name FORGE.Sound#_decodeErrorBind
* @type {?Function}
* @private
*/
this._decodeErrorBind = null;
/**
* Is the sound decoded?
* @name FORGE.Sound#_decoded
* @type {boolean}
* @private
*/
this._decoded = false;
/**
* On sound decoded event dispatcher.
* @name FORGE.Sound#_onSoundDecode
* @type {?FORGE.EventDispatcher}
* @private
*/
this._onSoundDecode = null;
/**
* On load start event dispatcher.
* @name FORGE.Sound#_onLoadStart
* @type {?FORGE.EventDispatcher}
* @private
*/
this._onLoadStart = null;
/**
* On loaded data event dispatcher.
* @name FORGE.Sound#_onLoadedData
* @type {?FORGE.EventDispatcher}
* @private
*/
this._onLoadedData = null;
/**
* On can play event dispatcher.
* @name FORGE.Sound#_onCanPlay
* @type {?FORGE.EventDispatcher}
* @private
*/
this._onCanPlay = null;
/**
* On can play through event dispatcher.
* @name FORGE.Sound#_onCanPlayThrough
* @type {?FORGE.EventDispatcher}
* @private
*/
this._onCanPlayThrough = null;
/**
* On sound muted event dispatcher.
* @name FORGE.Sound#_onMute
* @type {?FORGE.EventDispatcher}
* @private
*/
this._onMute = null;
/**
* On sound unmuted event dispatcher.
* @name FORGE.Sound#_onUnmute
* @type {?FORGE.EventDispatcher}
* @private
*/
this._onUnmute = null;
/**
* On sound volume change event dispatcher.
* @name FORGE.Sound#_onVolumeChange
* @type {?FORGE.EventDispatcher}
* @private
*/
this._onVolumeChange = null;
/**
* On sound play event dispatcher.
* @name FORGE.Sound#_onPlay
* @type {?FORGE.EventDispatcher}
* @private
*/
this._onPlay = null;
/**
* On sound stop event dispatcher.
* @name FORGE.Sound#_onStop
* @type {?FORGE.EventDispatcher}
* @private
*/
this._onStop = null;
/**
* On sound pause event dispatcher.
* @name FORGE.Sound#_onPause
* @type {?FORGE.EventDispatcher}
* @private
*/
this._onPause = null;
/**
* On sound resume event dispatcher.
* @name FORGE.Sound#_onResume
* @type {?FORGE.EventDispatcher}
* @private
*/
this._onResume = null;
/**
* On sound decoded event dispatcher.
* @name FORGE.Sound#_onEnded
* @type {?FORGE.EventDispatcher}
* @private
*/
this._onEnded = null;
FORGE.BaseObject.call(this, "Sound");
this._boot();
};
FORGE.Sound.prototype = Object.create(FORGE.BaseObject.prototype);
FORGE.Sound.prototype.constructor = FORGE.Sound;
/**
* Boot sequence.
* @method FORGE.Sound#_boot
* @private
* @suppress {deprecated}
*/
FORGE.Sound.prototype._boot = function()
{
if (this._ambisonic === true && this._isAmbisonic() === false)
{
this.log("FORGE.Sound: can't manage ambisonic sound without Google Chrome Omnitone library and WebAudio API.");
this._ambisonic = false;
}
//register the uid
this._uid = this._key;
this._register();
if (this._viewer.audio.useWebAudio === true)
{
this._context = this._viewer.audio.context;
this._inputNode = this._viewer.audio.inputNode;
if (typeof this._context.createGain === "undefined")
{
this._gainNode = this._context.createGainNode();
}
else
{
this._gainNode = this._context.createGain();
}
this._gainNode.gain.value = this._volume * this._viewer.audio.volume;
this._gainNode.connect(this._inputNode);
}
if (this._viewer.audio.useAudioTag === true || this._isAmbisonic() === true)
{
if (this._viewer.cache.has(FORGE.Cache.types.SOUND, this._key) === true)
{
this._loadComplete(this._viewer.cache.get(FORGE.Cache.types.SOUND, this._key));
}
//Listen to the main volume change to adapt the sound volume accordingly.
this._viewer.audio.onVolumeChange.add(this._mainVolumeChangeHandler, this);
}
this._viewer.audio.onDisable.add(this._disableSoundHandler, this);
if (this._url !== "")
{
this._viewer.load.sound(this._key, this._url, this._loadComplete, this, this._isAmbisonic());
if (this._onLoadStart !== null)
{
this._onLoadStart.dispatch();
}
}
this._viewer.audio.add(this);
};
/**
* Event handler for load complete event, it launch the decoding of the sound file.
* @method FORGE.Sound#_loadComplete
* @private
* @param {FORGE.File} file - The sound file.
*/
FORGE.Sound.prototype._loadComplete = function(file)
{
// In some case, the sound is destroyed before the loading
if(this._alive === false)
{
return;
}
this._soundFile = file;
this._ready = true;
// loaded events
if (this._onLoadedData !== null)
{
this._onLoadedData.dispatch();
}
if (this._viewer.audio.useWebAudio === true)
{
this._decode(this._soundFile);
}
else
{
this._dispatchDecodedEvents();
this._decodeComplete(null);
}
};
/**
* Decoding of the sound file.
* @method FORGE.Sound#_decode
* @private
* @param {Object} file - The sound file
*/
FORGE.Sound.prototype._decode = function(file)
{
if (file)
{
if (this._decoded === false)
{
this._decodeCompleteBind = this._decodeComplete.bind(this);
this._decodeErrorBind = this._decodeError.bind(this);
if (this._isAmbisonic() === true)
{
// FOA decoder and binaural renderer
this._decoder = Omnitone.createFOADecoder(this._context, file.data,
{
channelMap: this._defaultChannelMap
// HRTFSetUrl: 'YOUR_HRTF_SET_URL', //Base URL for the cube HRTF sets.
// postGainDB: 0, //Post-decoding gain compensation in dB.
});
// Initialize the decoder
this._decoder.initialize().then(this._decodeCompleteBind, this._decodeErrorBind);
}
else
{
this._context.decodeAudioData(file.data, this._decodeCompleteBind, this._decodeErrorBind);
}
}
}
};
/**
* Event handler for decode error event, it stores decoding data into the sound file object.
* @method FORGE.Sound#_decodeError
* @private
*/
FORGE.Sound.prototype._decodeError = function()
{
if (this._soundFile !== null)
{
this._soundFile.data = null;
this._decoded = false;
}
};
/**
* Dispatcher for decoded events.
* @method FORGE.Sound#_dispatchDecodedEvents
* @private
*/
FORGE.Sound.prototype._dispatchDecodedEvents = function()
{
if (this._onSoundDecode !== null)
{
this._onSoundDecode.dispatch();
}
if (this._onCanPlay !== null)
{
this._onCanPlay.dispatch();
}
if (this._onCanPlayThrough !== null)
{
this._onCanPlayThrough.dispatch();
}
};
/**
* Event handler for decode complete event, it stores decoding data into the sound file object.
* @method FORGE.Sound#_decodeComplete
* @private
* @param {?AudioBuffer} buffer - The raw binary data buffer.
*/
FORGE.Sound.prototype._decodeComplete = function(buffer)
{
if (this._soundFile === null)
{
this.log("FORGE.Sound._decodeComplete error, sound file is null");
return;
}
if (buffer)
{
this._soundFile.data = buffer;
}
this._decoded = true;
this._dispatchDecodedEvents();
if (this._viewer.audio.useAudioTag === true || this._isAmbisonic() === true)
{
this._sound = this._viewer.cache.get(FORGE.Cache.types.SOUND, this._key);
this._updateVolume();
this._duration = 0;
if (this._sound.data.duration)
{
this._duration = this._sound.data.duration;
this._durationMS = Math.ceil(this._duration * 1000);
}
}
if (this._pendingPlay === true)
{
this.play(this._currentTime, this._loop, true);
}
};
/**
* Handles the main volume change, update the volume factor to the sound volume.
* @method FORGE.Sound#_mainVolumeChangeHandler
* @private
*/
FORGE.Sound.prototype._mainVolumeChangeHandler = function()
{
this._updateVolume();
};
/**
* Apply the main volume factor to the sound volume.
* @method FORGE.Sound#_updateVolume
* @private
*/
FORGE.Sound.prototype._updateVolume = function()
{
if (this._sound !== null && (this._viewer.audio.useAudioTag === true || this._isAmbisonic() === true))
{
this._sound.data.volume = FORGE.Math.clamp(this._volume, 0, 1) * this._viewer.audio.volume;
}
};
/**
* Disable the sound.
* @method FORGE.Sound#_disableSoundHandler
* @private
*/
FORGE.Sound.prototype._disableSoundHandler = function()
{
if (this._playing === true)
{
this.pause();
}
else if (this.paused === false)
{
this.stop();
}
};
/**
* Reset of the sound values.
* @method FORGE.Sound#_reset
* @private
*/
FORGE.Sound.prototype._reset = function()
{
this._currentTime = 0;
this._pendingPlay = false;
this._playing = false;
this._paused = false;
};
/**
* Setup for sound panner.
* @method FORGE.Sound#_setupPanner
* @private
*/
FORGE.Sound.prototype._setupPanner = function()
{
this._panner = this._context.createPanner();
this._panner.panningModel = "HRTF";
this._panner.distanceModel = "inverse";
this._panner.refDistance = 1;
this._panner.maxDistance = 10000;
this._panner.rolloffFactor = 1;
this._panner.coneInnerAngle = 360;
this._panner.coneOuterAngle = 0;
this._panner.coneOuterGain = 0;
// look to listener position (x, y, z)
this._panner.setOrientation(0, 0, 0);
// init the 3D position of the panner (x, y, z)
this._panner.setPosition(0, 0, 0);
};
/**
* Apply sound panner orientation.
* @method FORGE.Sound#_applyPanner
* @param {boolean=} connect - Panner must be connected to sound and gainNode?
* @private
*/
FORGE.Sound.prototype._applyPanner = function(connect)
{
if (this._panner === null)
{
this._setupPanner();
}
this._panner.setPosition(this._x, this._y, this._z);
if (connect === true)
{
this._sound.connect(this._panner);
// Connect the "panner" object to the "destination" object.
this._panner.connect(this._gainNode);
}
};
/**
* Does the audio sound must be considered as ambisonic?
* @method FORGE.Sound#_isAmbisonic
* @return {boolean} Is ambisonic?
* @private
*/
FORGE.Sound.prototype._isAmbisonic = function()
{
return (this._ambisonic === true && this._viewer.audio.useWebAudio === true && typeof Omnitone !== "undefined");
};
/**
* Update method called by the viewer main loop.
* @method FORGE.Sound#update
*/
FORGE.Sound.prototype.update = function()
{
if (this._playing === true && this._paused === false)
{
var time = this._viewer.clock.time - this._startTime;
if (time >= this._durationMS)
{
this._currentTime = this._durationMS;
if (this._viewer.audio.useWebAudio === true || this._viewer.audio.useAudioTag === true)
{
this.stop(true);
if (this._onEnded !== null)
{
this._onEnded.dispatch();
}
if (this._loop === true)
{
this._currentTime = 0;
this.resume();
}
}
}
else
{
//also for this case when using streaming for data or bad headers : this._duration === Infinity
this._currentTime = time;
}
}
if (this._decoder !== null && this._playing === true)
{
// Rotate the binaural renderer based on a Three.js camera object.
var m4 = this._viewer.renderer.camera.modelViewInverse;
this._decoder.setRotationMatrixFromCamera(m4);
}
};
/**
* Play method of the sound.
* @method FORGE.Sound#play
* @param {number=} position - The start position to play the sound in milliseconds.
* @param {?boolean=} loop - The loop state of the sound.
* @param {?boolean=} forceRestart - If the sound is already playing you can set forceRestart to restart it from the beginning.
* @suppress {deprecated}
*/
FORGE.Sound.prototype.play = function(position, loop, forceRestart)
{
if (this._viewer.audio.enabled === false || this._enabled === false)
{
this._playing = false;
this._paused = false;
return;
}
this._loop = loop || this._loop;
if (this._playing === true)
{
if (forceRestart === true)
{
this.stop();
}
else
{
return;
}
}
if (this._ready === false)
{
this._pendingPlay = true;
return;
}
if (this._viewer.audio.useWebAudio === true && this._isAmbisonic() === false)
{
// Does the sound need decoding?
if (this._decoded === true)
{
// Do we need to do this every time we play? How about just if the buffer is empty?
if (this._buffer === null)
{
this._buffer = this._soundFile.data;
}
this._sound = this._context.createBufferSource();
this._sound.buffer = this._buffer;
if (this._spatialized === true)
{
this._applyPanner(true);
}
else
{
this._sound.connect(this._gainNode);
}
this._duration = this._sound.buffer.duration;
this._durationMS = Math.ceil(this._duration * 1000);
if (!isNaN(position) && position < this._durationMS)
{
this._startTime = this._viewer.clock.time - position;
}
else
{
position = 0;
this._startTime = this._viewer.clock.time;
}
var time = FORGE.Math.round10(position / 1000);
// Useful to cache this somewhere perhaps?
if (typeof this._sound.start === "undefined")
{
this._sound.noteGrainOn(0, time % this._duration, this._duration);
}
else
{
// Start playback, but make sure we stay in bound of the buffer.
this._sound.start(0, time % this._duration, this._duration);
}
this._playing = true;
this._currentTime = /** @type {number} */ (position);
this._playCount++;
if (this._onPlay !== null)
{
this._onPlay.dispatch();
}
}
else
{
this.log("Sound is not decoded yet");
this._pendingPlay = true;
return;
}
}
else if (this._viewer.audio.useAudioTag === true || this._isAmbisonic() === true)
{
if (this._duration === 0)
{
this._duration = this._sound.data.duration;
this._durationMS = Math.ceil(this._duration * 1000);
}
else if (this._duration === Infinity)
{
this._sound.data.loop = true;
}
if (!isNaN(position) && this._durationMS !== 0 && position < this._durationMS)
{
this._sound.data.currentTime = FORGE.Math.round10(position / 1000);
this._startTime = this._viewer.clock.time - position;
}
else
{
position = 0;
this._sound.data.currentTime = position;
this._startTime = this._viewer.clock.time;
}
this._sound.data.play();
this._playing = true;
this._currentTime = /** @type {number} */ (position);
this._playCount++;
if (this._onPlay !== null)
{
this._onPlay.dispatch();
}
}
this._viewer.audio.resume();
};
/**
* Stop method of the sound.
* @method FORGE.Sound#stop
* @param {boolean=} internal - Internal use: true prevents event firing.
*/
FORGE.Sound.prototype.stop = function(internal)
{
if (this._sound !== null)
{
this._stop(true);
this._pauseTime = this._viewer.clock.time;
this._startTime = this._viewer.clock.time;
this._pendingPlay = false;
this._playing = false;
this._paused = false;
if (this._onStop !== null && internal === true)
{
this._onStop.dispatch();
}
this._viewer.audio.suspend();
}
else if (this._ready === false || this._decoded !== true)
{
this._reset();
}
};
/**
* Stop actions to apply to the sound.
* @method FORGE.Sound#_stop
* @private
* @param {boolean} resetCurrentTime - To force a reset of the current time
* @suppress {deprecated}
*/
FORGE.Sound.prototype._stop = function(resetCurrentTime)
{
if (this._viewer.audio.useWebAudio === true && this._isAmbisonic() === false)
{
if (typeof this._sound.stop === "undefined")
{
this._sound.noteOff(0);
}
else
{
try
{
this._sound.stop(0);
}
catch (e)
{
}
}
// Clean up the buffer source
this._sound.disconnect(0);
if (resetCurrentTime === true)
{
this._currentTime = 0;
}
}
else if (this._viewer.audio.useAudioTag === true || this._isAmbisonic() === true)
{
this._sound.data.pause();
if (resetCurrentTime === true)
{
this._currentTime = 0;
this._sound.data.currentTime = 0;
}
}
};
/**
* Pause method of the sound.
* @method FORGE.Sound#pause
*/
FORGE.Sound.prototype.pause = function()
{
if (this._playing === true && this._sound !== null)
{
this._stop(false);
this._paused = true;
this._pauseTime = this._viewer.clock.time;
this._pendingPlay = false;
this._playing = false;
if (this._onPause !== null)
{
this._onPause.dispatch();
}
this._viewer.audio.suspend();
}
else if (this._ready === false || this._decoded !== true)
{
this._reset();
}
};
/**
* Resume method of the sound.
* @method FORGE.Sound#resume
* @suppress {deprecated}
*/
FORGE.Sound.prototype.resume = function()
{
if (this._viewer.audio.enabled === false || this._enabled === false)
{
return;
}
if (this._paused === true || this._playing === false || this._resumed === true)
{
if (this._sound === null)
{
this.play(this._currentTime, this._loop, true);
return;
}
else
{
this._startTime = this._viewer.clock.time - (this._pauseTime - this._startTime);
this._currentTime = this._viewer.clock.time - this._startTime; //force current time update
var time = FORGE.Math.round10(this._currentTime / 1000);
if (this._viewer.audio.useWebAudio === true && this._isAmbisonic() === false)
{
this._sound = this._context.createBufferSource();
this._sound.buffer = this._buffer;
if (this._spatialized === true)
{
this._applyPanner(true);
}
else
{
this._sound.connect(this._gainNode);
}
var duration = Math.ceil((this._durationMS - this._currentTime) / 1000);
if (typeof this._sound.start === "undefined")
{
this._sound.noteGrainOn(0, time % this._duration, duration);
}
else
{
this._sound.start(0, time % this._duration, duration);
}
}
else if (this._viewer.audio.useAudioTag === true || this._isAmbisonic() === true)
{
this._sound.data.currentTime = time;
this._sound.data.play();
}
}
this._playing = true;
this._paused = false;
if (this._onResume !== null)
{
this._onResume.dispatch();
}
this._viewer.audio.resume();
}
else if (this._ready === false || this._decoded === false)
{
this._reset();
this._pendingPlay = true;
}
};
/**
* Mute method of the sound.
* @method FORGE.Sound#mute
*/
FORGE.Sound.prototype.mute = function()
{
if (this._muted === true || this._viewer.audio.enabled === false || this._enabled === false)
{
return;
}
this._muted = true;
this._mutedVolume = this._volume;
this._volume = 0;
if (this._viewer.audio.useWebAudio === true && this._isAmbisonic() === false)
{
this._gainNode.gain.value = this._volume;
}
else if ((this._viewer.audio.useAudioTag === true || this._isAmbisonic() === true) && this._sound !== null)
{
this._sound.data.volume = this._volume;
}
if (this._onMute !== null)
{
this._onMute.dispatch();
}
};
/**
* Unmute method of the sound.
* @method FORGE.Sound#unmute
*/
FORGE.Sound.prototype.unmute = function()
{
if (this._muted === false || this._viewer.audio.enabled === false || this._enabled === false)
{
return;
}
this._muted = false;
this._volume = this._mutedVolume;
if (this._viewer.audio.useWebAudio === true && this._isAmbisonic() === false)
{
this._gainNode.gain.value = this._mutedVolume;
}
else if ((this._viewer.audio.useAudioTag === true || this._isAmbisonic() === true) && this._sound !== null)
{
this._sound.data.volume = this._mutedVolume;
}
if (this._onUnmute !== null)
{
this._onUnmute.dispatch();
}
};
/**
* Augmented destroy method.
* @method FORGE.Sound#destroy
*/
FORGE.Sound.prototype.destroy = function()
{
this.stop();
if (this._viewer.audio.useAudioTag === true || this._isAmbisonic() === true)
{
this._viewer.audio.onVolumeChange.remove(this._mainVolumeChangeHandler, this);
}
this._viewer.audio.onDisable.remove(this._disableSoundHandler, this);
this._viewer.audio.remove(this);
this._viewer = null;
this._soundFile = null;
this._sound = null;
this._buffer = null;
this._context = null;
this._inputNode = null;
this._gainNode = null;
this._panner = null;
this._decodeCompleteBind = null;
this._decodeErrorBind = null;
if (this._onSoundDecode !== null)
{
this._onSoundDecode.destroy();
this._onSoundDecode = null;
}
if (this._onLoadStart !== null)
{
this._onLoadStart.destroy();
this._onLoadStart = null;
}
if (this._onLoadedData !== null)
{
this._onLoadedData.destroy();
this._onLoadedData = null;
}
if (this._onCanPlay !== null)
{
this._onCanPlay.destroy();
this._onCanPlay = null;
}
if (this._onCanPlayThrough !== null)
{
this._onCanPlayThrough.destroy();
this._onCanPlayThrough = null;
}
if (this._onMute !== null)
{
this._onMute.destroy();
this._onMute = null;
}
if (this._onUnmute !== null)
{
this._onUnmute.destroy();
this._onUnmute = null;
}
if (this._onVolumeChange !== null)
{
this._onVolumeChange.destroy();
this._onVolumeChange = null;
}
if (this._onPlay !== null)
{
this._onPlay.destroy();
this._onPlay = null;
}
if (this._onStop !== null)
{
this._onStop.destroy();
this._onStop = null;
}
if (this._onPause !== null)
{
this._onPause.destroy();
this._onPause = null;
}
if (this._onResume !== null)
{
this._onResume.destroy();
this._onResume = null;
}
if (this._onEnded !== null)
{
this._onEnded.destroy();
this._onEnded = null;
}
FORGE.BaseObject.prototype.destroy.call(this);
};
/**
* Get and set the sound enabled status.
* @name FORGE.Sound#enabled
* @type {boolean}
*/
Object.defineProperty(FORGE.Sound.prototype, "enabled",
{
/** @this {FORGE.Sound} */
get: function()
{
return this._enabled;
},
/** @this {FORGE.Sound} */
set: function(value)
{
if (typeof value === "boolean")
{
this._enabled = value;
if (this._enabled === false && (this._playing === true || this._paused === true))
{
this.stop();
}
}
}
});
/**
* Get or set the current time in milliseconds of the sound.
* @name FORGE.Sound#currentTime
* @type {number}
*/
Object.defineProperty(FORGE.Sound.prototype, "currentTime",
{
/** @this {FORGE.Sound} */
get: function()
{
return this._currentTime;
},
/** @this {FORGE.Sound} */
set: function(value)
{
if (typeof value !== "number")
{
return;
}
this.pause();
this._currentTime = value;
if (this._playing === true)
{
this.resume();
}
}
});
/**
* Get or set the spatialized state of the sound.
* @name FORGE.Sound#spatialized
* @type {boolean}
*/
Object.defineProperty(FORGE.Sound.prototype, "spatialized",
{
/** @this {FORGE.Sound} */
get: function()
{
return this._spatialized;
},
/** @this {FORGE.Sound} */
set: function(value)
{
if (typeof value === "boolean")
{
this._spatialized = value;
}
}
});
/**
* Get the ambisonic state of the sound.
* @name FORGE.Sound#ambisonic
* @type {boolean}
*/
Object.defineProperty(FORGE.Sound.prototype, "ambisonic",
{
/** @this {FORGE.Sound} */
get: function()
{
return this._ambisonic;
}
});
/**
* Get the duration in seconds of the sound.
* @name FORGE.Sound#duration
* @readonly
* @type {number}
*/
Object.defineProperty(FORGE.Sound.prototype, "duration",
{
/** @this {FORGE.Sound} */
get: function()
{
return this._duration;
}
});
/**
* Get or set the muted state of the sound.
* @name FORGE.Sound#muted
* @type {boolean}
*/
Object.defineProperty(FORGE.Sound.prototype, "muted",
{
/** @this {FORGE.Sound} */
get: function()
{
return (this._muted || this._viewer.audio.mute);
},
/** @this {FORGE.Sound} */
set: function(value)
{
if (typeof value === "boolean")
{
if (value === true)
{
this.mute();
}
else
{
this.unmute();
}
}
}
});
/**
* Get or set the volume of the sound.
* @name FORGE.Sound#volume
* @type {number}
*/
Object.defineProperty(FORGE.Sound.prototype, "volume",
{
/** @this {FORGE.Sound} */
get: function()
{
return this._volume;
},
/** @this {FORGE.Sound} */
set: function(value)
{
if (typeof value !== "number" || isNaN(value) === true)
{
return;
}
value = FORGE.Math.clamp(value, 0, 1);
if (value === this._volume || this._viewer.audio.enabled === false || this._enabled === false)
{
return;
}
this._volume = value;
if (this._volume > 0)
{
this._muted = false;
}
if (this._viewer.audio.useWebAudio === true && this._isAmbisonic() === false)
{
this._gainNode.gain.value = value;
}
else
{
this._updateVolume();
}
if (this._onVolumeChange !== null)
{
this._onVolumeChange.dispatch();
}
}
});
/**
* Get the decoded status of the sound.
* @name FORGE.Sound#decoded
* @readonly
* @type {boolean}
*/
Object.defineProperty(FORGE.Sound.prototype, "decoded",
{
/** @this {FORGE.Sound} */
get: function()
{
if (this._soundFile === null)
{
return false;
}
return this._decoded;
}
});
/**
* Get the number of play of the sound.
* @name FORGE.Sound#playCount
* @readonly
* @type {boolean}
*/
Object.defineProperty(FORGE.Sound.prototype, "playCount",
{
/** @this {FORGE.Sound} */
get: function()
{
return this._playCount;
}
});
/**
* Get the playing status of the sound.
* @name FORGE.Sound#playing
* @readonly
* @type {boolean}
*/
Object.defineProperty(FORGE.Sound.prototype, "playing",
{
/** @this {FORGE.Sound} */
get: function()
{
return this._playing;
}
});
/**
* Get the ready status of the sound.
* @name FORGE.Sound#ready
* @readonly
* @type {boolean}
*/
Object.defineProperty(FORGE.Sound.prototype, "ready",
{
/** @this {FORGE.Sound} */
get: function()
{
return this._ready;
}
});
/**
* Get the paused status of the sound.
* @name FORGE.Sound#paused
* @readonly
* @type {boolean}
*/
Object.defineProperty(FORGE.Sound.prototype, "paused",
{
/** @this {FORGE.Sound} */
get: function()
{
return this._paused;
}
});
/**
* Get/Set the resumed status of the sound.
* @name FORGE.Sound#resumed
* @type {boolean}
*/
Object.defineProperty(FORGE.Sound.prototype, "resumed",
{
/** @this {FORGE.Sound} */
get: function()
{
return this._resumed;
},
/** @this {FORGE.Sound} */
set: function(value)
{
if (typeof value === "boolean")
{
this._resumed = value;
}
}
});
/**
* Get and set the loop status of the sound.
* @name FORGE.Sound#loop
* @type {boolean}
*/
Object.defineProperty(FORGE.Sound.prototype, "loop",
{
/** @this {FORGE.Sound} */
get: function()
{
return this._loop;
},
/** @this {FORGE.Sound} */
set: function(value)
{
if (typeof value === "boolean")
{
this._loop = value;
if ((this._viewer.audio.useAudioTag === true || this._isAmbisonic() === true) && this._sound !== null && this._duration === Infinity)
{
this._sound.data.loop = this._loop;
}
}
}
});
/**
* Get and set the x axis position of the sound.
* @name FORGE.Sound#x
* @type {number}
*/
Object.defineProperty(FORGE.Sound.prototype, "x",
{
/** @this {FORGE.Sound} */
get: function()
{
return this._x;
},
/** @this {FORGE.Sound} */
set: function(value)
{
if (typeof value === "number")
{
this._x = value;
if ((this._viewer.audio.useWebAudio === true && this._isAmbisonic() === false) && this._sound !== null && this._panner !== null && this._spatialized === true)
{
this._applyPanner();
}
}
}
});
/**
* Get and set the y axis position of the sound.
* @name FORGE.Sound#y
* @type {number}
*/
Object.defineProperty(FORGE.Sound.prototype, "y",
{
/** @this {FORGE.Sound} */
get: function()
{
return this._y;
},
/** @this {FORGE.Sound} */
set: function(value)
{
if (typeof value === "number")
{
this._y = value;
if ((this._viewer.audio.useWebAudio === true && this._isAmbisonic() === false) && this._sound !== null && this._panner !== null && this._spatialized === true)
{
this._applyPanner();
}
}
}
});
/**
* Get and set the z axis position of the sound.
* @name FORGE.Sound#z
* @type {number}
*/
Object.defineProperty(FORGE.Sound.prototype, "z",
{
/** @this {FORGE.Sound} */
get: function()
{
return this._y;
},
/** @this {FORGE.Sound} */
set: function(value)
{
if (typeof value === "number")
{
this._z = value;
if ((this._viewer.audio.useWebAudio === true && this._isAmbisonic() === false) && this._sound !== null && this._panner !== null && this._spatialized === true)
{
this._applyPanner();
}
}
}
});
/**
* Get the sound "onSoundDecode" event {@link FORGE.EventDispatcher}.
* The {@link FORGE.EventDispatcher} is created only if you ask for it.
* @name FORGE.Sound#onSoundDecode
* @type {FORGE.EventDispatcher}
*/
Object.defineProperty(FORGE.Sound.prototype, "onSoundDecode",
{
/** @this {FORGE.Sound} */
get: function()
{
if (this._onSoundDecode === null)
{
this._onSoundDecode = new FORGE.EventDispatcher(this);
}
return this._onSoundDecode;
}
});
/**
* Get the sound "onLoadStart" event {@link FORGE.EventDispatcher}.
* The {@link FORGE.EventDispatcher} is created only if you ask for it.
* @name FORGE.Sound#onLoadStart
* @type {FORGE.EventDispatcher}
*/
Object.defineProperty(FORGE.Sound.prototype, "onLoadStart",
{
/** @this {FORGE.Sound} */
get: function()
{
if (this._onLoadStart === null)
{
this._onLoadStart = new FORGE.EventDispatcher(this);
}
return this._onLoadStart;
}
});
/**
* Get the sound "onLoadedData" event {@link FORGE.EventDispatcher}.
* The {@link FORGE.EventDispatcher} is created only if you ask for it.
* @name FORGE.Sound#onLoadedData
* @type {FORGE.EventDispatcher}
*/
Object.defineProperty(FORGE.Sound.prototype, "onLoadedData",
{
/** @this {FORGE.Sound} */
get: function()
{
if (this._onLoadedData === null)
{
this._onLoadedData = new FORGE.EventDispatcher(this);
}
return this._onLoadedData;
}
});
/**
* Get the sound "onCanPlay" event {@link FORGE.EventDispatcher}.
* The {@link FORGE.EventDispatcher} is created only if you ask for it.
* @name FORGE.Sound#onCanPlay
* @type {FORGE.EventDispatcher}
*/
Object.defineProperty(FORGE.Sound.prototype, "onCanPlay",
{
/** @this {FORGE.Sound} */
get: function()
{
if (this._onCanPlay === null)
{
this._onCanPlay = new FORGE.EventDispatcher(this);
}
return this._onCanPlay;
}
});
/**
* Get the sound "onCanPlayThrough" event {@link FORGE.EventDispatcher}.
* The {@link FORGE.EventDispatcher} is created only if you ask for it.
* @name FORGE.Sound#onCanPlayThrough
* @type {FORGE.EventDispatcher}
*/
Object.defineProperty(FORGE.Sound.prototype, "onCanPlayThrough",
{
/** @this {FORGE.Sound} */
get: function()
{
if (this._onCanPlayThrough === null)
{
this._onCanPlayThrough = new FORGE.EventDispatcher(this);
}
return this._onCanPlayThrough;
}
});
/**
* Get the sound "onMute" event {@link FORGE.EventDispatcher}.
* The {@link FORGE.EventDispatcher} is created only if you ask for it.
* @name FORGE.Sound#onMute
* @type {FORGE.EventDispatcher}
*/
Object.defineProperty(FORGE.Sound.prototype, "onMute",
{
/** @this {FORGE.Sound} */
get: function()
{
if (this._onMute === null)
{
this._onMute = new FORGE.EventDispatcher(this);
}
return this._onMute;
}
});
/**
* Get the sound "onUnmute" event {@link FORGE.EventDispatcher}.
* The {@link FORGE.EventDispatcher} is created only if you ask for it.
* @name FORGE.Sound#onUnmute
* @type {FORGE.EventDispatcher}
*/
Object.defineProperty(FORGE.Sound.prototype, "onUnmute",
{
/** @this {FORGE.Sound} */
get: function()
{
if (this._onUnmute === null)
{
this._onUnmute = new FORGE.EventDispatcher(this);
}
return this._onUnmute;
}
});
/**
* Get the sound "onVolumeChange" event {@link FORGE.EventDispatcher}.
* The {@link FORGE.EventDispatcher} is created only if you ask for it.
* @name FORGE.Sound#onVolumeChange
* @type {FORGE.EventDispatcher}
*/
Object.defineProperty(FORGE.Sound.prototype, "onVolumeChange",
{
/** @this {FORGE.Sound} */
get: function()
{
if (this._onVolumeChange === null)
{
this._onVolumeChange = new FORGE.EventDispatcher(this);
}
return this._onVolumeChange;
}
});
/**
* Get the sound "onPlay" event {@link FORGE.EventDispatcher}.
* The {@link FORGE.EventDispatcher} is created only if you ask for it.
* @name FORGE.Sound#onPlay
* @type {FORGE.EventDispatcher}
*/
Object.defineProperty(FORGE.Sound.prototype, "onPlay",
{
/** @this {FORGE.Sound} */
get: function()
{
if (this._onPlay === null)
{
this._onPlay = new FORGE.EventDispatcher(this);
}
return this._onPlay;
}
});
/**
* Get the sound "onStop" event {@link FORGE.EventDispatcher}.
* The {@link FORGE.EventDispatcher} is created only if you ask for it.
* @name FORGE.Sound#onStop
* @type {FORGE.EventDispatcher}
*/
Object.defineProperty(FORGE.Sound.prototype, "onStop",
{
/** @this {FORGE.Sound} */
get: function()
{
if (this._onStop === null)
{
this._onStop = new FORGE.EventDispatcher(this);
}
return this._onStop;
}
});
/**
* Get the sound "onPause" event {@link FORGE.EventDispatcher}.
* The {@link FORGE.EventDispatcher} is created only if you ask for it.
* @name FORGE.Sound#onPause
* @type {FORGE.EventDispatcher}
*/
Object.defineProperty(FORGE.Sound.prototype, "onPause",
{
/** @this {FORGE.Sound} */
get: function()
{
if (this._onPause === null)
{
this._onPause = new FORGE.EventDispatcher(this);
}
return this._onPause;
}
});
/**
* Get the sound "onResume" event {@link FORGE.EventDispatcher}.
* The {@link FORGE.EventDispatcher} is created only if you ask for it.
* @name FORGE.Sound#onResume
* @type {FORGE.EventDispatcher}
*/
Object.defineProperty(FORGE.Sound.prototype, "onResume",
{
/** @this {FORGE.Sound} */
get: function()
{
if (this._onResume === null)
{
this._onResume = new FORGE.EventDispatcher(this);
}
return this._onResume;
}
});
/**
* Get the sound "onEnded" event {@link FORGE.EventDispatcher}.
* The {@link FORGE.EventDispatcher} is created only if you ask for it.
* @name FORGE.Sound#onEnded
* @type {FORGE.EventDispatcher}
*/
Object.defineProperty(FORGE.Sound.prototype, "onEnded",
{
/** @this {FORGE.Sound} */
get: function()
{
if (this._onEnded === null)
{
this._onEnded = new FORGE.EventDispatcher(this);
}
return this._onEnded;
}
});