Source: stage/sound/Sound.js

/**
 * Loads an audio file and provides several playback options.
 * @param {String} audioPath
 * @class
 * @constructor
 */
Ayce.Sound = function(audioPath) {

    var scope = this;
    this.soundLoaded = false;
    this.position = new Ayce.Vector3(0,0,0);
    this.orientationFront = new Ayce.Vector3(0,0,0);
    this.orientationUp = new Ayce.Vector3(0,1,0);
    this.listenerPosition = new Ayce.Vector3(0,0,0);
    this.listenerOrientationFront = new Ayce.Vector3(0,0,0);
    this.listenerOrientationUp = new Ayce.Vector3(0,1,0);
    this.loop = true;
    this.is3D = true;
    this.volume = 1;
    this.isPlaying = false;

    var context;
    var panner;
    var source;
    var buffer;

    var gainNode;
    var startTime;
    var pauseTime = 0;

    /**
     * Loads audio file
     * @param {Ayce.AudioContext} audioContext
     */
    this.init = function(audioContext){
        context = audioContext.audioContext;

        var request = new XMLHttpRequest();
        request.open("GET", audioPath, true);
        request.responseType = "arraybuffer";
        request.onload = function () {
            context.decodeAudioData(this.response, function(data) {
                buffer = data;
                scope.soundLoaded = true;
            }, function onFailure() {
                alert("Decoding the audio buffer failed");
            });
        };
        request.send();
    };

    /**
     * Updates audio file parameters
     */
    this.update = function () {
        if(scope.soundLoaded&&scope.isPlaying) {
            if(scope.is3D) {
                panner.setPosition(
                    scope.position.x,
                    scope.position.y,
                    scope.position.z);
                panner.panningModel = "HRTF";
                panner.distanceModel = "linear";
                context.listener.setOrientation(
                    -scope.listenerOrientationFront.x,
                    -scope.listenerOrientationFront.y,
                    scope.listenerOrientationFront.z,
                    scope.listenerOrientationUp.x,
                    scope.listenerOrientationUp.y,
                    -scope.listenerOrientationUp.z);
                context.listener.setPosition(
                    -scope.listenerPosition.x,
                    -scope.listenerPosition.y,
                    -scope.listenerPosition.z);
            }
            gainNode.gain.value = this.volume;
            source.loop = this.loop;
        }
    };

    /**
     * Initializes and plays audio file
     */
    this.play = function(){
        if(scope.soundLoaded) {
            source = context.createBufferSource();
            source.buffer = buffer;

            gainNode = context.createGain();

            if(this.is3D) {
                panner = context.createPanner();
                source.connect(panner);
                panner.connect(gainNode);
                panner.panningModel = "HRTF";
                panner.distanceModel = "linear";
            }else{
                source.connect(gainNode);
            }
            gainNode.connect(context.destination);
            scope.update();
            source.start(pauseTime);
            startTime = Date.now();
            this.isPlaying = true;
        }else{
            console.error("The audio file is not done loading.")
        }
    };

    /**
     * Pauses audio playback
     */
    this.pause = function(){
        if(scope.soundLoaded) {
            source.stop();
            this.isPlaying = false;
            pauseTime = ((Date.now()-startTime)%Math.floor(buffer.duration*1000))/1000;
        }else{
            console.error("The audio file is not done loading.")
        }
    };

    /**
     * Stops audio playback
     */
    this.stop = function(){
        if(scope.soundLoaded) {
            source.stop();
            this.isPlaying = false;
            pauseTime = 0;
        }else{
            console.error("The audio file is not done loading.")
        }
    }
};