File: src\sound\AudioManager.ts
/**
* The namespace that holds all of the assets and functionality when dealing with Audio.
*
* @module Kiwi
* @submodule Sound
* @main Sound
*
*/
module Kiwi.Sound {
/**
* Manages the initialisation of assets necessary when dealing with audio in the game, either through Audio Tags or the Web Audio API.
* Also provides global sound controls that will be applyed to all Audio objects at the same time, within the Game this manager is a part of.
*
* @class AudioManager
* @constructor
* @namespace Kiwi.Sound
* @param game {Kiwi.Game} The game that this audio manager belongs to.
* @return {Kiwi.Sound.AudioManager}
*/
export class AudioManager {
constructor(game: Kiwi.Game) {
this._game = game;
}
/**
* The type of object that this is.
* @method objType
* @return {String} "AudioManager"
* @public
*/
public objType() {
return "AudioManager";
}
/**
* The game that this manager belongs to.
* @property _game
* @type Kiwi.Game
* @private
*/
private _game: Kiwi.Game;
/**
* The global volume of all of the games audio.
* @property _volume
* @type number
* @default 1
* @private
*/
private _volume: number;
/**
* A boolean that controls whether the whole games audio should be muted or not.
* @property _muted
* @type boolean
* @default false
* @private
*/
private _muted: boolean;
/**
* An array containing all of the sounds on the game.
* @property _sounds
* @type Array
* @private
*/
private _sounds: any;
/**
* The number of channels that are supported.
* Not in much use at this point in time.
* @property channels
* @type number
* @public
*/
public channels: number;
/**
* If the current game has audio support or not.
* Useful because audio support is spotty in mobile browsers.
* @property noAudio
* @type boolean
* @public
*/
public noAudio: boolean = false;
/**
* If the game is currently using the Web Audio API for the sound.
* @property usingWebAudio
* @type boolean
* @public
*/
public usingWebAudio: boolean = false;
/**
* If the game is using audio tags for the sound. This is the fallback if the web audio api is not supported.
* @property usingAudioTag
* @type boolean
* @public
*/
public usingAudioTag: boolean = false;
/**
* Web Audio API ONLY - The audio context that is used for decoding audio, e.t.c.
* @property context
* @type any
* @public
*/
public context: any = null;
/**
* Web Audio API ONLY - The master gain node through which all sounds play.
* @property masterGain
* @type any
* @public
*/
public masterGain: any = null;
/**
* The volume of the audio before it was muted. This is used so that when the audio is unmuted the volume will resume at this point.
* @property _muteVolume
* @type number
* @private
*/
private _muteVolume: number;
/**
* Indicates if the sounds is currently 'locked' or not.
* If it is 'locked' then no audio can play until a user touch's the device.
* @property _locked
* @type boolean
* @private
*/
private _locked: boolean;
/**
* Sound buffer
* @property _unlockedSource
* @type {any}
* @private
*/
private _unlockedSource: any = null;
/**
* Returns a boolean indicating whether the device has been touched or not. READ ONLY.
* @property locked
* @type boolean
* @public
*/
public get locked():boolean {
return this._locked;
}
/**
* The boot method is executed when all of the DOM elements needed for the game are loaded and the game is ready to 'start' up.
*
* @method boot
* @public
*/
public boot() {
this._volume = 1;
this._muted = false;
this._sounds = [];
//check to see if it is an iOS device and if it doesn't support webAudio
if (Kiwi.DEVICE.iOS && Kiwi.DEVICE.webaudio == false) {
this.channels = 1;
}
//Add mouse event here to 'unlock' the device.
if (Kiwi.DEVICE.iOS && this._game.deviceTargetOption !== Kiwi.TARGET_COCOON) {
this._locked = true;
this._game.input.onUp.addOnce(this._unlocked, this);
Kiwi.Log.log('Kiwi.AudioManager: Audio is currently Locked until at touch event.', '#audio', '#locked');
} else {
this._locked = false;
}
this.usingWebAudio = true; //we hope for the best....
this.usingAudioTag = false;
if (!!window['AudioContext']) {//if audio context is supported
this.context = new window['AudioContext']();
}
else if (!!window['webkitAudioContext']) {//if webkit audio context is supported
this.context = new window['webkitAudioContext']();
}
else if (!!window['Audio']) {//no awesome audio support...so maybe the audio tags?
this.usingWebAudio = false;
this.usingAudioTag = true;
}
else {//they have no audio support........no sound for you :(
this.usingWebAudio = false;
this.noAudio = true; //prepared for the worst :(
}
if (this.context !== null) {
if (this.context.createGain === undefined) {
this.masterGain = this.context.createGainNode();
} else {
this.masterGain = this.context.createGain();
}
this.masterGain.gain.value = 1;
this.masterGain.connect(this.context.destination);
}
}
/**
* Is executed when a mouse event is fired on the device. This is used to enabled playback of sounds on the current device if they were awaiting for a user event.
* @method _unlocked
* @private
*/
private _unlocked() {
Kiwi.Log.log('Kiwi.AudioManager: Audio now Unlocked.', '#audio', '#unlocked');
if (this.usingAudioTag) {
this._locked = false;
for (var i = 0; i < this._sounds.length; i++) {
this._sounds[i].playable = true;
}
} else {
// Create empty buffer and play it
var buffer = this.context.createBuffer(1, 1, 22050);
this._unlockedSource = this.context.createBufferSource();
this._unlockedSource.buffer = buffer;
this._unlockedSource.connect(this.context.destination);
if (this._unlockedSource.start === undefined) {
this._unlockedSource.noteOn(0);
} else {
this._unlockedSource.start(0);
}
}
}
/**
* Used to mute the audio on the device, or to check to see if the device is muted.
* This will not stop audio from being played, will just mean that the audio is silent.
*
* @property mute
* @type boolean
* @default false
* @public
*/
public set mute(value: boolean) {
if (value === true) {
if (this._muted) return;
this._muted = true;
//mute the sounds
if (this.usingWebAudio) {
this._muteVolume = this.masterGain.gain.value;
this.masterGain.gain.value = 0;
} else if (this.usingAudioTag) {
for (var i = 0; i < this._sounds.length; i++) {
this._sounds[i].mute = true;
}
}
} else {
if (this._muted == false) return;
this._muted = false;
if (this.usingWebAudio) {
this.masterGain.gain.value = this._muteVolume;
} else if (this.usingAudioTag) {
for (var i = 0; i < this._sounds.length; i++) {
this._sounds[i].mute = false;
}
}
}
}
public get mute(): boolean {
return this._muted;
}
/**
* Global setting and getting of the volume.
* A number between 0 (silence) and 1 (full volume).
*
* @property volume
* @type number
* @default 1
* @public
*/
public set volume(value: number) {
if (value !== undefined) {
value = Kiwi.Utils.GameMath.clamp(value, 1, 0);
this._volume = value;
if (this._muted) {
this._muteVolume = this._volume;
}
if (this.usingWebAudio) {
this.masterGain.gain.value = value;
} else if (this.usingAudioTag) {
for (var i = 0; i < this._sounds.length; i++) {
//for each sound tag to update.
this._sounds[i].volume = this._sounds[i].volume;
}
}
}
}
public get volume(): number {
return this._volume;
}
/**
* Indicates whether or not an Audio Object is registered with this Audio Manager or not. For Kiwi Internal use only.
* @method isRegistered
* @param sound {Audio} The Audio object you are checking for.
* @return {Boolean} If the piece of audio is registered or not.
* @public
*/
public isRegistered(sound: Kiwi.Sound.Audio):boolean {
if (this.noAudio) return;
if (this._sounds.indexOf(sound) !== -1) {
return true;
} else {
return false;
}
}
/**
* Registers an Audio Object with this audio manager. Also assign's the audio piece a unique ID to identify it by. Internal Kiwi use only.
* @method registerSound
* @param sound {Audio} The audio piece you are wanting to register.
* @return { Boolean } Indication of if the sound was successfully added or not.
* @public
*/
public registerSound(sound:Kiwi.Sound.Audio):boolean {
if (this.isRegistered(sound) === false) {
sound.id = this._game.rnd.uuid();
this._sounds.push(sound);
return true;
}
return false;
}
/**
* Used to create a new sound on the audio manager. Returns the newly created sound.
*
* @method add
* @param key {string} The key for the audio file that is to be loaded from the AudioLibrary.
* @param [volume=1] {number} The volume for the piece of audio.
* @param [loop=false] {boolean} If the audio should keep repeat when it gets to the end.
* @return {Kiwi.Sound.Audio}
* @public
*/
public add(key: string, volume: number = 1, loop: boolean = false): Kiwi.Sound.Audio {//rename to create
if (this.noAudio) return;
var sound: Kiwi.Sound.Audio = new Kiwi.Sound.Audio(this._game, key, volume, loop);
return sound;
}
/**
* Removes the passed sound from the AudioManager.
* If you remove a Audio Object from the AudioManager, then that Audio Object will not have a update loop.
*
* @method remove
* @param sound {Kiwi.Sound.Audio} The Audio object that you want to remove.
* @param [destory=true] If the Audio Object should be removed or not.
* @public
*/
public remove(sound: Kiwi.Sound.Audio, destroy:boolean=true) {
for (var i = 0; i < this._sounds.length; i++) {
if (sound.id == this._sounds[i].id) {
if (this.usingWebAudio && sound.gainNode) sound.gainNode.disconnect();
if (destroy == true) sound.destroy();
this._sounds.splice(i, 1);
return;
}
}
}
/**
* Plays all of the sounds listed in the AudioManager.
* @method playAll
* @public
*/
public playAll() {
for (var i = 0; i < this._sounds.length; i++) {
if (this._sounds[i]) {
this._sounds[i].play();
}
}
}
/**
* Stops all of the sounds that are listed in the AudioManager from playing.
* @method stopAll
* @public
*/
public stopAll() {
for (var i = 0; i < this._sounds.length; i++) {
if (this._sounds[i]) {
this._sounds[i].stop();
}
}
}
/**
* Pauses all of the sounds listed in the AudioManager.
* @method pauseAll
* @public
*/
public pauseAll() {
for (var i = 0; i < this._sounds.length; i++) {
if (this._sounds[i]) {
this._sounds[i].pause();
}
}
}
/**
* Resumes all of the sounds listed in the AudioManager.
* @method resumeAll
* @public
*/
public resumeAll() {
for (var i = 0; i < this._sounds.length; i++) {
if (this._sounds[i]) {
this._sounds[i].resume();
}
}
}
/**
* The update loop that is executed every frame.
* @method update
* @public
*/
public update() {
if (this._locked) {
if (this.usingWebAudio && this._unlockedSource !== null) {
if ((this._unlockedSource.playbackState === this._unlockedSource.PLAYING_STATE || this._unlockedSource.playbackState === this._unlockedSource.FINISHED_STATE))
{
this._locked = false;
this._unlockedSource = null;
for (var i = 0; i < this._sounds.length; i++) {
this._sounds[i].playable = true;
}
}
}
}
if (!this.noAudio) {
for (var i = 0; i < this._sounds.length; i++) {
this._sounds[i].update();
}
}
}
}
}