text: /**
   * @author       Richard Davey <rich@photonstorm.com>
   * @author       Pavle Goloskokovic <pgoloskokovic@gmail.com> (http://prunegames.com)
   * @copyright    2018 Photon Storm Ltd.
   * @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
   */
  
  var Class = require('../utils/Class');
  var Clone = require('../utils/object/Clone');
  var EventEmitter = require('eventemitter3');
  var NOOP = require('../utils/NOOP');
  
  /**
   * @callback EachActiveSoundCallback
   *
   * @param {Phaser.Sound.BaseSoundManager} manager - The SoundManager
   * @param {Phaser.Sound.BaseSound} sound - The current active Sound
   * @param {number} index - The index of the current active Sound
   * @param {Phaser.Sound.BaseSound[]} sounds - All sounds
   */
  
  /**
   * Audio sprite sound type.
   *
   * @typedef {Phaser.Sound.BaseSound} Phaser.Sound.BaseSound.AudioSpriteSound
   *
   * @property {object} spritemap - Local reference to 'spritemap' object form json file generated by audiosprite tool.
   */
  
  /**
   * @classdesc
   * The sound manager is responsible for playing back audio via Web Audio API or HTML Audio tag as fallback.
   * The audio file type and the encoding of those files are extremely important.
   *
   * Not all browsers can play all audio formats.
   *
   * There is a good guide to what's supported [here](https://developer.mozilla.org/en-US/Apps/Fundamentals/Audio_and_video_delivery/Cross-browser_audio_basics#Audio_Codec_Support).
   *
   * @class BaseSoundManager
   * @extends Phaser.Events.EventEmitter
   * @memberOf Phaser.Sound
   * @constructor
   * @since 3.0.0
   *
   * @param {Phaser.Game} game - Reference to the current game instance.
   */
  var BaseSoundManager = new Class({
  
      Extends: EventEmitter,
  
      initialize:
  
      function BaseSoundManager (game)
      {
          EventEmitter.call(this);
  
          /**
           * Local reference to game.
           *
           * @name Phaser.Sound.BaseSoundManager#game
           * @type {Phaser.Game}
           * @readOnly
           * @since 3.0.0
           */
          this.game = game;
  
          /**
           * Local reference to the JSON Cache, as used by Audio Sprites.
           *
           * @name Phaser.Sound.BaseSoundManager#jsonCache
           * @type {Phaser.Cache.BaseCache}
           * @readOnly
           * @since 3.7.0
           */
          this.jsonCache = game.cache.json;
  
          /**
           * An array containing all added sounds.
           *
           * @name Phaser.Sound.BaseSoundManager#sounds
           * @type {Phaser.Sound.BaseSound[]}
           * @default []
           * @private
           * @since 3.0.0
           */
          this.sounds = [];
  
          /**
           * Global mute setting.
           *
           * @name Phaser.Sound.BaseSoundManager#mute
           * @type {boolean}
           * @default false
           * @since 3.0.0
           */
          this.mute = false;
  
          /**
           * Global volume setting.
           *
           * @name Phaser.Sound.BaseSoundManager#volume
           * @type {number}
           * @default 1
           * @since 3.0.0
           */
          this.volume = 1;
  
          /**
           * Flag indicating if sounds should be paused when game looses focus,
           * for instance when user switches to another tab/program/app.
           *
           * @name Phaser.Sound.BaseSoundManager#pauseOnBlur
           * @type {boolean}
           * @default true
           * @since 3.0.0
           */
          this.pauseOnBlur = true;
  
          /**
           * Property that actually holds the value of global playback rate.
           *
           * @name Phaser.Sound.BaseSoundManager#_rate
           * @type {number}
           * @private
           * @default 1
           * @since 3.0.0
           */
          this._rate = 1;
  
          /**
           * Property that actually holds the value of global detune.
           *
           * @name Phaser.Sound.BaseSoundManager#_detune
           * @type {number}
           * @private
           * @default 0
           * @since 3.0.0
           */
          this._detune = 0;
  
          /**
           * Mobile devices require sounds to be triggered from an explicit user action,
           * such as a tap, before any sound can be loaded/played on a web page.
           * Set to true if the audio system is currently locked awaiting user interaction.
           *
           * @name Phaser.Sound.BaseSoundManager#locked
           * @type {boolean}
           * @readOnly
           * @since 3.0.0
           */
          this.locked = this.locked || false;
  
          /**
           * Flag used internally for handling when the audio system
           * has been unlocked, if there ever was a need for it.
           *
           * @name Phaser.Sound.BaseSoundManager#unlocked
           * @type {boolean}
           * @default false
           * @private
           * @since 3.0.0
           */
          this.unlocked = false;
  
          game.events.on('blur', function ()
          {
              if (this.pauseOnBlur)
              {
                  this.onBlur();
              }
          }, this);
  
          game.events.on('focus', function ()
          {
              if (this.pauseOnBlur)
              {
                  this.onFocus();
              }
          }, this);
  
          game.events.on('prestep', this.update, this);
          game.events.once('destroy', this.destroy, this);
      },
  
      /**
       * Adds a new sound into the sound manager.
       *
       * @method Phaser.Sound.BaseSoundManager#add
       * @override
       * @since 3.0.0
       *
       * @param {string} key - Asset key for the sound.
       * @param {SoundConfig} [config] - An optional config object containing default sound settings.
       *
       * @return {Phaser.Sound.BaseSound} The new sound instance.
       */
      add: NOOP,
  
      /**
       * Adds a new audio sprite sound into the sound manager.
       * Audio Sprites are a combination of audio files and a JSON configuration.
       * The JSON follows the format of that created by https://github.com/tonistiigi/audiosprite
       *
       * @method Phaser.Sound.BaseSoundManager#addAudioSprite
       * @since 3.0.0
       *
       * @param {string} key - Asset key for the sound.
       * @param {SoundConfig} [config] - An optional config object containing default sound settings.
       *
       * @return {Phaser.Sound.BaseSound.AudioSpriteSound} The new audio sprite sound instance.
       */
      addAudioSprite: function (key, config)
      {
          if (config === undefined) { config = {}; }
  
          var sound = this.add(key, config);
  
          sound.spritemap = this.jsonCache.get(key).spritemap;
  
          for (var markerName in sound.spritemap)
          {
              if (!sound.spritemap.hasOwnProperty(markerName))
              {
                  continue;
              }
  
              var markerConfig = Clone(config);
  
              var marker = sound.spritemap[markerName];
  
              markerConfig.loop = (marker.hasOwnProperty('loop')) ? marker.loop : false;
  
              sound.addMarker({
                  name: markerName,
                  start: marker.start,
                  duration: marker.end - marker.start,
                  config: markerConfig
              });
          }
  
          return sound;
      },
  
      /**
       * Enables playing sound on the fly without the need to keep a reference to it.
       * Sound will auto destroy once its playback ends.
       *
       * @method Phaser.Sound.BaseSoundManager#play
       * @since 3.0.0
       *
       * @param {string} key - Asset key for the sound.
       * @param {(SoundConfig|SoundMarker)} [extra] - An optional additional object containing settings to be applied to the sound. It could be either config or marker object.
       *
       * @return {boolean} Whether the sound started playing successfully.
       */
      play: function (key, extra)
      {
          var sound = this.add(key);
  
          sound.once('ended', sound.destroy, sound);
  
          if (extra)
          {
              if (extra.name)
              {
                  sound.addMarker(extra);
  
                  return sound.play(extra.name);
              }
              else
              {
                  return sound.play(extra);
              }
          }
          else
          {
              return sound.play();
          }
      },
  
      /**
       * Enables playing audio sprite sound on the fly without the need to keep a reference to it.
       * Sound will auto destroy once its playback ends.
       *
       * @method Phaser.Sound.BaseSoundManager#playAudioSprite
       * @since 3.0.0
       *
       * @param {string} key - Asset key for the sound.
       * @param {string} spriteName - The name of the sound sprite to play.
       * @param {SoundConfig} [config] - An optional config object containing default sound settings.
       *
       * @return {boolean} Whether the audio sprite sound started playing successfully.
       */
      playAudioSprite: function (key, spriteName, config)
      {
          var sound = this.addAudioSprite(key);
  
          sound.once('ended', sound.destroy, sound);
  
          return sound.play(spriteName, config);
      },
  
      /**
       * Removes a sound from the sound manager.
       * The removed sound is destroyed before removal.
       *
       * @method Phaser.Sound.BaseSoundManager#remove
       * @since 3.0.0
       *
       * @param {Phaser.Sound.BaseSound} sound - The sound object to remove.
       *
       * @return {boolean} True if the sound was removed successfully, otherwise false.
       */
      remove: function (sound)
      {
          var index = this.sounds.indexOf(sound);
  
          if (index !== -1)
          {
              sound.destroy();
  
              this.sounds.splice(index, 1);
  
              return true;
          }
  
          return false;
      },
  
      /**
       * Removes all sounds from the sound manager that have an asset key matching the given value.
       * The removed sounds are destroyed before removal.
       *
       * @method Phaser.Sound.BaseSoundManager#removeByKey
       * @since 3.0.0
       *
       * @param {string} key - The key to match when removing sound objects.
       *
       * @return {number} The number of matching sound objects that were removed.
       */
      removeByKey: function (key)
      {
          var removed = 0;
  
          for (var i = this.sounds.length - 1; i >= 0; i--)
          {
              var sound = this.sounds[i];
  
              if (sound.key === key)
              {
                  sound.destroy();
  
                  this.sounds.splice(i, 1);
  
                  removed++;
              }
          }
  
          return removed;
      },
  
      /**
       * @event Phaser.Sound.BaseSoundManager#pauseall
       * @param {Phaser.Sound.BaseSoundManager} soundManager - Reference to the sound manager that emitted event.
       */
  
      /**
       * Pauses all the sounds in the game.
       *
       * @method Phaser.Sound.BaseSoundManager#pauseAll
       * @fires Phaser.Sound.BaseSoundManager#pauseall
       * @since 3.0.0
       */
      pauseAll: function ()
      {
          this.forEachActiveSound(function (sound)
          {
              sound.pause();
          });
  
          this.emit('pauseall', this);
      },
  
      /**
       * @event Phaser.Sound.BaseSoundManager#resumeall
       * @param {Phaser.Sound.BaseSoundManager} soundManager - Reference to the sound manager that emitted event.
       */
  
      /**
       * Resumes all the sounds in the game.
       *
       * @method Phaser.Sound.BaseSoundManager#resumeAll
       * @fires Phaser.Sound.BaseSoundManager#resumeall
       * @since 3.0.0
       */
      resumeAll: function ()
      {
          this.forEachActiveSound(function (sound)
          {
              sound.resume();
          });
  
          this.emit('resumeall', this);
      },
  
      /**
       * @event Phaser.Sound.BaseSoundManager#stopall
       * @param {Phaser.Sound.BaseSoundManager} soundManager - Reference to the sound manager that emitted event.
       */
  
      /**
       * Stops all the sounds in the game.
       *
       * @method Phaser.Sound.BaseSoundManager#stopAll
       * @fires Phaser.Sound.BaseSoundManager#stopall
       * @since 3.0.0
       */
      stopAll: function ()
      {
          this.forEachActiveSound(function (sound)
          {
              sound.stop();
          });
  
          this.emit('stopall', this);
      },
  
      /**
       * Method used internally for unlocking audio playback on devices that
       * require user interaction before any sound can be played on a web page.
       *
       * Read more about how this issue is handled here in [this article](https://medium.com/@pgoloskokovic/unlocking-web-audio-the-smarter-way-8858218c0e09).
       *
       * @method Phaser.Sound.BaseSoundManager#unlock
       * @override
       * @protected
       * @since 3.0.0
       */
      unlock: NOOP,
  
      /**
       * Method used internally for pausing sound manager if
       * Phaser.Sound.BaseSoundManager#pauseOnBlur is set to true.
       *
       * @method Phaser.Sound.BaseSoundManager#onBlur
       * @override
       * @protected
       * @since 3.0.0
       */
      onBlur: NOOP,
  
      /**
       * Method used internally for resuming sound manager if
       * Phaser.Sound.BaseSoundManager#pauseOnBlur is set to true.
       *
       * @method Phaser.Sound.BaseSoundManager#onFocus
       * @override
       * @protected
       * @since 3.0.0
       */
      onFocus: NOOP,
  
      /**
       * Update method called on every game step.
       * Removes destroyed sounds and updates every active sound in the game.
       *
       * @method Phaser.Sound.BaseSoundManager#update
       * @protected
       * @since 3.0.0
       *
       * @param {number} time - The current timestamp as generated by the Request Animation Frame or SetTimeout.
       * @param {number} delta - The delta time elapsed since the last frame.
       */
      update: function (time, delta)
      {
          if (this.unlocked)
          {
              this.unlocked = false;
              this.locked = false;
  
              /**
               * @event Phaser.Sound.BaseSoundManager#unlocked
               * @param {Phaser.Sound.BaseSoundManager} soundManager - Reference to the sound manager that emitted event.
               */
              this.emit('unlocked', this);
          }
  
          for (var i = this.sounds.length - 1; i >= 0; i--)
          {
              if (this.sounds[i].pendingRemove)
              {
                  this.sounds.splice(i, 1);
              }
          }
  
          this.sounds.forEach(function (sound)
          {
              sound.update(time, delta);
          });
      },
  
      /**
       * Destroys all the sounds in the game and all associated events.
       *
       * @method Phaser.Sound.BaseSoundManager#destroy
       * @since 3.0.0
       */
      destroy: function ()
      {
          this.removeAllListeners();
  
          this.forEachActiveSound(function (sound)
          {
              sound.destroy();
          });
  
          this.sounds.length = 0;
          this.sounds = null;
  
          this.game = null;
      },
  
      /**
       * Method used internally for iterating only over active sounds and skipping sounds that are marked for removal.
       *
       * @method Phaser.Sound.BaseSoundManager#forEachActiveSound
       * @private
       * @since 3.0.0
       *
       * @param {EachActiveSoundCallback} callback - Callback function. (manager: Phaser.Sound.BaseSoundManager, sound: Phaser.Sound.BaseSound, index: number, sounds: Phaser.Manager.BaseSound[]) => void
       * @param {*} [scope] - Callback context.
       */
      forEachActiveSound: function (callback, scope)
      {
          var _this = this;
  
          this.sounds.forEach(function (sound, index)
          {
              if (!sound.pendingRemove)
              {
                  callback.call(scope || _this, sound, index, _this.sounds);
              }
          });
      },
  
      /**
       * @event Phaser.Sound.BaseSoundManager#rate
       * @param {Phaser.Sound.BaseSoundManager} soundManager - Reference to the sound manager that emitted event.
       * @param {number} value - An updated value of Phaser.Sound.BaseSoundManager#rate property.
       */
  
      /**
       * Sets the global playback rate at which all the sounds will be played.
       *
       * For example, a value of 1.0 plays the audio at full speed, 0.5 plays the audio at half speed
       * and 2.0 doubles the audios playback speed.
       *
       * @method Phaser.Sound.BaseSoundManager#setRate
       * @fires Phaser.Sound.BaseSoundManager#rate
       * @since 3.3.0
       *
       * @param {number} value - Global playback rate at which all the sounds will be played.
       *
       * @return {Phaser.Sound.BaseSoundManager} This Sound Manager.
       */
      setRate: function (value)
      {
          this.rate = value;
  
          return this;
      },
  
      /**
       * Global playback rate at which all the sounds will be played.
       * Value of 1.0 plays the audio at full speed, 0.5 plays the audio at half speed
       * and 2.0 doubles the audio's playback speed.
       *
       * @name Phaser.Sound.BaseSoundManager#rate
       * @type {number}
       * @default 1
       * @since 3.0.0
       */
      rate: {
  
          get: function ()
          {
              return this._rate;
          },
  
          set: function (value)
          {
              this._rate = value;
  
              this.forEachActiveSound(function (sound)
              {
                  sound.calculateRate();
              });
  
              this.emit('rate', this, value);
          }
  
      },
  
      /**
       * Sets the global detuning of all sounds in [cents](https://en.wikipedia.org/wiki/Cent_%28music%29).
       * The range of the value is -1200 to 1200, but we recommend setting it to [50](https://en.wikipedia.org/wiki/50_Cent).
       *
       * @method Phaser.Sound.BaseSoundManager#setDetune
       * @fires Phaser.Sound.BaseSoundManager#detune
       * @since 3.3.0
       *
       * @param {number} value - The range of the value is -1200 to 1200, but we recommend setting it to [50](https://en.wikipedia.org/wiki/50_Cent).
       *
       * @return {Phaser.Sound.BaseSoundManager} This Sound Manager.
       */
      setDetune: function (value)
      {
          this.detune = value;
  
          return this;
      },
  
      /**
       * @event Phaser.Sound.BaseSoundManager#detune
       * @param {Phaser.Sound.BaseSoundManager} soundManager - Reference to the sound manager that emitted event.
       * @param {number} value - An updated value of Phaser.Sound.BaseSoundManager#detune property.
       */
  
      /**
       * Global detuning of all sounds in [cents](https://en.wikipedia.org/wiki/Cent_%28music%29).
       * The range of the value is -1200 to 1200, but we recommend setting it to [50](https://en.wikipedia.org/wiki/50_Cent).
       *
       * @name Phaser.Sound.BaseSoundManager#detune
       * @type {number}
       * @default 0
       * @since 3.0.0
       */
      detune: {
  
          get: function ()
          {
              return this._detune;
          },
  
          set: function (value)
          {
              this._detune = value;
  
              this.forEachActiveSound(function (sound)
              {
                  sound.calculateRate();
              });
  
              this.emit('detune', this, value);
          }
  
      }
  
  });
  
  module.exports = BaseSoundManager;
  