Source: resources/audio.js

/*
 * File: audio.js
 *
 * logics for loading an audio file into the resource_map and
 * provides control of the loaded audio 
 */
"use strict";

import * as map from "../core/resource_map.js";
// functions from resource_map
let unload = map.unload;
let has = map.has;


let mAudioContext = null;
let mBackgroundAudio = null;

// volume control support
// https://www.davrous.com/2015/11/05/creating-fun-immersive-audio-experiences-with-web-audio/
// https://developer.mozilla.org/en-US/docs/Web/API/GainNode/gain
// https://www.html5rocks.com/en/tutorials/webaudio/positional_audio/
let mBackgroundGain = null;         // background volume
let mCueGain = null;        // cue/special effects volume
let mMasterGain = null;     // overall/master volume

let kDefaultInitGain = 0.1;

/**
 * logic for loading audio files into the resource_map and
 * provides control of the loaded audio 
 * 
 * <p><strong>Exports the unload() and has() functions from </strong> 
 * {@link https://mylesacd.github.io/build-your-own-2d-game-engine-2e-doc/AdditionalMaterials/Documentation/module-resource_map.html resource map}</p>
 * 
 * <p>Found in Chapter 4, page 172 of the textbook </p>
 * Example:
 * {@link https://mylesacd.github.io/build-your-own-2d-game-engine-2e-doc/BookSourceCode/chapter4/4.6.audio_support/index.html 4.6 Audio Support}
 * @module audio
 */

/**
 * Closes the support for audio 
 * @static
 */
function cleanUp() {
    mAudioContext.close();
    mAudioContext = null;
}

/**
 * Initialize the web audio system support
 * @static
 */
function init() {
    try {
        let AudioContext = window.AudioContext || window.webkitAudioContext;
        mAudioContext = new AudioContext();

        // connect Master volume control
        mMasterGain = mAudioContext.createGain();
        mMasterGain.connect(mAudioContext.destination);
        // set default Master volume
        mMasterGain.gain.value = kDefaultInitGain;
         
        // connect Background volume control
        mBackgroundGain = mAudioContext.createGain();
        mBackgroundGain.connect(mMasterGain);
        // set default Background volume
        mBackgroundGain.gain.value = 1.0;
         
        // connect Cuevolume control
        mCueGain = mAudioContext.createGain();
        mCueGain.connect(mMasterGain);
        // set default Cue volume
        mCueGain.gain.value = 1.0;
    } catch (e) {
        throw new Error("Web Audio is not supported. Engine initialization failed.");
    }
}

function decodeResource(data) {
    return data.arrayBuffer();
}
function parseResource(data) {
    return mAudioContext.decodeAudioData(data);
}

/**
 * Load an audio file into the resource map
 * @static
 * @param {string} path - the path to the audio file 
 * @returns {}
 */
function load(path) {
    return map.loadDecodeParse(path, decodeResource, parseResource);
}

/**
 * Play an audio cue
 * @static
 * @param {string} path - the path to the audio file 
 * @param {float} volume - the volume to play the audio at
 */
function playCue(path, volume) {
    let source = mAudioContext.createBufferSource();
    source.buffer = map.get(path);
    source.start(0);

    // volume support for cue
    source.connect(mCueGain);
    mCueGain.gain.value = volume;
}

/**
 * Begins playing background audio file
 * @static
 * @param {string} path - path to the audio file
 * @param {float} volume - the volume 
 */
function playBackground(path, volume) {
    if (has(path)) {
        stopBackground();
        mBackgroundAudio = mAudioContext.createBufferSource();
        mBackgroundAudio.buffer = map.get(path);
        mBackgroundAudio.loop = true;
        mBackgroundAudio.start(0);

         // connect volume accordingly
         mBackgroundAudio.connect(mBackgroundGain);
         setBackgroundVolume(volume);
    }
}

/**
 * Set the volume of the background audio clip
 * @static
 * @param {float} volume - the new background volume
 */
function setBackgroundVolume(volume) {
    if (mBackgroundGain !== null) {
        mBackgroundGain.gain.value = volume;
    }
}

/**
 * Increment the volume of the background audio clip
 * @static
 * @param {float} increment - value to add to background volume
 */
function  incBackgroundVolume(increment) {
    if (mBackgroundGain !== null) {
        mBackgroundGain.gain.value += increment;

        // need this since volume increases when negative
        if (mBackgroundGain.gain.value < 0) {
            setBackgroundVolume(0);
        }
    }
}

/**
 * Set the Master volume
 * @static
 * @param {float} volume - the new master volume
 */
function  setMasterVolume(volume) {
    if (mMasterGain !== null) {
        mMasterGain.gain.value = volume;
    }
}

/**
 * Increment the Master volume
 * @static
 * @param {float} increment - the value to add to the volume
 */
function  incMasterVolume(increment) {
    if (mMasterGain !== null) {
        mMasterGain.gain.value += increment;

        // need this since volume increases when negative
        if (mMasterGain.gain.value < 0) {
            mMasterGain.gain.value = 0;
        }
    }
}

/**
 * Stop playing background music and clear the variable
 * @static
 */
function stopBackground() {
    if (mBackgroundAudio !== null) {
        mBackgroundAudio.stop(0);
        mBackgroundAudio = null;
    }
}

/**
 * Returns if there is background audio playing
 * @static
 * @returns {boolean} true if there is background audio
 */
function isBackgroundPlaying() {
    return (mBackgroundAudio !== null);
}


export {init, cleanUp,
        has, load, unload,

        playCue,  

        playBackground, stopBackground, isBackgroundPlaying,
        setBackgroundVolume, incBackgroundVolume,

        setMasterVolume, incMasterVolume
    }