API Docs for: v0.1.0
Show:

File: src\audio\AudioManager.js

var AudioPlayer = require('./AudioPlayer'),
    inherit = require('../utils/inherit'),
    support = require('../utils/support');

//you can only have 1 audio context on a page, so we store one for use in each manager
var __AudioCtx = window.AudioContext || window.webkitAudioContext || window.mozAudioContext,
    __audioctx = support.webAudio ? new __AudioCtx() : null;

/**
 * Grapefruit Audio API, provides an easy interface to use WebAudoiAPI with a fallback to HTML5 Audio
 * The GF Audio API was originally based on [Howler.js](https://github.com/goldfire/howler.js)
 * Generally you will use this via the `game.audio` or `state.audio` properties.
 *
 * @class AudioManager
 * @extends Object
 * @constructor
 * @param game {Game} The game instance this manager belongs to
 * @param parent {AudioManager} The parent audio manager this manager belongs to.
 *      This is used to create a web audio API node heirarchy.
 */
var AudioManager = function(game, parent) {
    /**
     * The game instance this manager belongs to
     *
     * @property game
     * @type Game
     */
    this.game = game;

    /**
     * The parent for this audio manager
     *
     * @property parent
     * @type AudioManager
     */
    this.parent = parent;

    /**
     * Whether the player is muted or not
     *
     * @property muted
     * @type Boolean
     * @default false
     * @private
     */
    this._muted = false;

    /**
     * The master volume of the player
     *
     * @property _volume
     * @type Number
     * @default 1
     * @private
     */
    this._volume = 1;

    /**
     * The master volume of all the audio playing
     *
     * @property volume
     * @type Number
     * @default 1
     */
    Object.defineProperty(this, 'volume', {
        get: this.getVolume.bind(this),
        set: this.setVolume.bind(this)
    });

    /**
     * The Web Audio API context if we are using it
     *
     * @property ctx
     * @type AudioContext
     * @readOnly
     */
    this.ctx = __audioctx;

    /**
     * If we have some way of playing audio
     *
     * @property canPlay
     * @type Boolean
     * @readOnly
     */
    this.canPlay = support.webAudio || support.htmlAudio;

    //if we are using web audio, we need a master gain node
    if(support.webAudio) {
        this.masterGain = this.ctx.createGain ? this.ctx.createGain() : this.ctx.createGainNode();
        this.masterGain.gain.value = 1;
        this.setParent(parent);
    }

    //map of elements to play audio with
    this.sounds = {};
};

inherit(AudioManager, Object, {
    /**
     * Returns the current master volume
     *
     * @method getVolume
     */
    getVolume: function() {
        return this._volume;
    },
    /**
     * Sets the current master volume
     *
     * @method setVolume
     * @param value {Number}
     */
    setVolume: function(v) {
        v = parseFloat(v, 10);

        if(!isNaN(v) && v >= 0 && v <= 1) {
            this._volume = v;

            if(support.webAudio)
                this.masterGain.gain.value = v;

            //go through each audio element and change their volume
            for(var key in this.sounds) {
                if(this.sounds.hasOwnProperty(key) && this.sounds[key]._webAudio === false) {
                    var player = this.sounds[key];
                    //loop through the audio nodes
                    for(var i = 0, il = player._nodes.length; i < il; ++i) {
                        player._nodes[i].volume = player._volume * this._volume;
                    }
                }
            }
        }
    },
    /**
     * Mutes all playing audio
     *
     * @method mute
     * @return {AudioManager} Returns itself.
     * @chainable
     */
    mute: function() {
        return this.setMuted(true);
    },
    /**
     * Unmutes all playing audio
     *
     * @method unmute
     * @return {AudioManager} Returns itself.
     * @chainable
     */
    unmute: function() {
        return this.setMuted(false);
    },
    /**
     * Sets whether or not this manager is muted
     *
     * @method setMuted
     * @return {AudioManager} Returns itself.
     * @chainable
     */
    setMuted: function(m) {
        this._muted = m = !!m;

        if(support.webAudio)
            this.masterGain.gain.value = m ? 0 : this._volume;

        //go through each audio element and mute/unmute them
        for(var key in this.sounds) {
            if(this.sounds.hasOwnProperty(key) && this.sounds[key]._webAudio === false) {
                var player = this.sounds[key];
                //loop through the audio nodes
                for(var i = 0, il = player._nodes.length; i < il; ++i) {
                    player._nodes[i].mute();
                }
            }
        }

        return this;
    },
    /**
     * Sets the parent of this audio manager, if using webAudio this
     * means that we connect to the parent masterGain node and inherit
     * anything that happens to it (such as muting).
     *
     * @method setParent
     * @param parent {AudioManager} The parent to connect to, or `null` to connect to the global context
     * @return {AudioManager} Returns itself.
     * @chainable
     */
    setParent: function(parent) {
        this.parent = parent;

        if(support.webAudio) {
            //attach to parent gain
            if(parent && parent.masterGain) {
                this.masterGain.connect(parent.masterGain);
            }
            //attach to audio context
            else {
                this.masterGain.connect(this.ctx.destination);
            }
        }

        return this;
    },
    /**
     * Attaches an AudioPlayer to this manager, if using webAudio this means
     * that the sound will connect to this masterGain node and inherit anything
     * that happens to it (such as muting).
     *
     * @method attach
     * @param sound {AudioPlayer} The player to attach to this manager
     * @return {AudioPlayer} The newly attached audio player
     */
    attach: function(sound) {
        if(sound._manager !== this) {
            //remove from other manager
            if(sound._manager)
                sound._manager.detach(sound);

            this.sounds[sound.key] = sound;
            sound._manager = this;

            if(support.webAudio) {
                for(var i = 0; i < sound._nodes.length; ++i) {
                    sound._nodes[i].connect(this.masterGain);
                }
            }
        }

        return sound;
    },
    /**
     * Detaches an AudioPlayer from this manager, if using webAudio this means
     * that the sound will disconnect from this masterGain node and stop inheriting
     * anything that happens to it (such as muting).
     *
     * @method detach
     * @param sound {AudioPlayer} The player to detach from this manager
     * @return {AudioPlayer} The detached audio player
     */
    detach: function(sound) {
        if(sound._manager !== this) {
            delete this.sounds[sound.key];
            sound._manager = null;

            if(support.webAudio) {
                for(var i = 0; i < sound._nodes.length; ++i) {
                    sound._nodes[i].disconnect();
                }
            }
        }

        return sound;
    },
    /**
     * Creates a new audio player for a peice of audio
     *
     * @method add
     * @param key {String} The unique cache key for the preloaded audio
     * @param [settings] {Object} All the settings for the audio player
     * @param [settings.volume] {Number} The volume of this audio clip
     * @param [settings.autoplay] {Boolean} Automatically start playing after loaded
     * @param [settings.loop] {Boolean} Replay the audio when it finishes
     * @param [settings.sprite] {Object} A map of string names -> [start, duration] arrays. You can use it to put multiple sounds in one file
     * @param [settings.pos3d] {Array<Number>} 3D coords of where the audio should sound as if it came from (only works with WebAudio)
     * @param [settings.buffer] {Boolean} WebAudio will load the entire file before playing, making this true forces HTML5Audio which will buffer and play
     * @param [settings.format] {String} Force an extension override
     * @return {AudioPlayer} Will return the new audio player, or false if we couldn't determine a compatible url
     */
    add: function(key, settings) {
        //if we can't play audio return false
        if(!this.canPlay) {
            return false;
        }

        var audio = this.game.cache.getAudio(key);

        if(!audio.player)
            audio.player = new AudioPlayer(this, audio, settings);

        return this.sounds[key] = audio.player;
    },
    /**
     * Removes an audio player from the manager
     *
     * @method remove
     * @param key {String} The unique cache key for the preloaded audio
     * @return {AudioPlayer} Will return the audio player removed, or false if none was removed
     */
    remove: function(key) {
        var audio = this.sounds[key];

        if(audio) {
            audio.stop();
        }

        delete this.sounds[key];

        return audio ? audio : false;
    }
});

module.exports = AudioManager;