audio/manager.js

import { Utils } from "../utils/index.js"
import { Sfx } from "./audio.js"

/**
 * Manages playing of audio using Web Audio.
 */
export class AudioHandler {
  /**
   * Audio context to use.
   * 
   *  @private
   * @type {AudioContext}
   */
  ctx = new AudioContext()
  /**
   * List of audio buffers to use.
   * 
   *  @private
   * @type {Object<string,AudioBuffer>}
   */
  sfx = {}
  /**
   * The name of the background music playing.
   * 
   *  @private
   * @type {string}
   */
  _backname = ""
  /**
   * The audiobuffer of the background music.
   * 
   *  @private
   * @type {Sfx | null }
   */
  _background = null
  /**
   * List of playing sounds
   * 
   * @private
   * @type {Sfx[]}
   */
  playing = []
  /**
   * What to play after loading the audiobuffers.
   * 
   * @private
   * @type {Record<string,number>}
   */
  toplay = {}
  /**
   * Volume to resume playing when unmuted.
   * 
   * @private
   * @type {number}
   */
  _mute = 1
  /**
   * Master volume for all sounds.
   * 
   * @private
   * @type {GainNode}
   */
  masterGainNode
  /**
   * @type {string}
   */
  baseUrl = ""
    /**
     * If the manager can play a sound.
   * @type boolean
   */
  canPlay = false
  constructor() {
    this.masterGainNode = this.ctx.createGain()
    this.masterGainNode.connect(this.ctx.destination)
    this.canPlay = this.ctx.state == "running"
    let that = this
    window.addEventListener("pointerdown", function resume() {
      that.ctx.resume()
      if (that.ctx.state == "running") {
        removeEventListener("pointerdown", resume)
        that.canPlay = true
      }
    })
  }
  /**
   * Load a sound into a sound manager
   * 
   * @param {string} src
   */
  load(src) {
    let name = src.split(".")[0]
    fetch(this.baseUrl + "/" + src)
      .then(e => e.arrayBuffer())
      .then(e => this.ctx.decodeAudioData(e))
      .then(e => {
        this.sfx[name] = e
        if (this._backname == name)
          this.playBackgroundMusic(name)
        if (name in this.toplay) {
          this.playEffect(name)
        }
      }).catch(err => console.log(err))
  }
  /**
   * Loads all audio from the loader.
   * 
   * @param {*} loader
   */
  /*loadFromLoader(loader) {
    for (var n in loader.sfx) {
      let name = n
      this.ctx.decodeAudioData(loader.sfx[n]).then(e => {
        this.sfx[n] = e
        if (this._backname == name)
          this.playMusic(name)
        if (name in this.toplay) {
          this.playEffect(name)
        }
      })
    }
  }*/
  /**
   * Plays a single audio as the background in a loop throughout the game
   * 
   * @param {string} name
   */
  playBackgroundMusic(name) {
    this._backname = name
    if (!(name in this.sfx))
      return
    this._background = new Sfx(this, this.sfx[name])
    this._background.loop = true
    this._background.play()
  }
  /**
   * Plays a sound only once.
   * 
   * @param {string} name Name of audio to play.
   * @param {number} [offset] Where to start playing the audio.It is in seconds.
   * @param {number} [duration] how long in seconds will the audio defaults to total duration of the selected audio. 
   */
  playEffect(name, offset = 0, duration = 0) {
    if (!(name in this.sfx)) {
      this.toplay[name] = 1
      return
    }
    let s = new Sfx(this, this.sfx[name])
    let id = this.playing.length
    s.id = id
    s.offset = offset
    if (duration)
      s.duration = duration
    this.playing.push(s)
    s.play()
  }

  /**
   * Creates and returns an SFX.
   * 
   * @param {string} name
   * @rerurns Sfx
   */
  createSfx(name) {
    ///throw error if name is not in this.
    return new Sfx(this, this.sfx[name])
  }
  /**
   * Pauses currently playing sounds.
   */
  pauseAll() {
    this.playing.forEach(sound => {
      sound.pause()
    })
  }
  /**
   * Sets the volume to zero.Sounds will continue playing but not be audible.
   */
  mute() {
    if(!this.masterGainNode)return
    this._mute = this.masterGainNode.gain.value
    this.masterGainNode.gain.value = 0

  }
  /**
   * Restores the volume before it was muted.
   */
  unmute() {
    this.masterGainNode.gain.value = this._mute
  }
  /**
   * Removes an sfx from the handler and disconnects it from its destination.
   * 
   * @param {Sfx} sfx
   */
  remove(sfx) {
    let id = this.playing.indexOf(sfx)
    if (id == -1) return
    sfx.disconnect()
    Utils.removeElement(this.playing, id)
  }
}