/**
 * @license
 * Copyright 2017 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * @file Listener model to spatialize sources in an environment.
 * @author Andrew Allen <bitllama@google.com>
 */
'use strict';
// Internal dependencies.
import Omnitone from 'omnitone/build/omnitone.esm';
import Encoder from './encoder.js';
import Utils from './utils.js';
/**
 * @class Listener
 * @description Listener model to spatialize sources in an environment.
 * @param {AudioContext} context
 * Associated {@link
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}.
 * @param {Object} options
 * @param {Number} options.ambisonicOrder
 * Desired ambisonic order. Defaults to
 * {@linkcode Utils.DEFAULT_AMBISONIC_ORDER DEFAULT_AMBISONIC_ORDER}.
 * @param {Float32Array} options.position
 * Initial position (in meters), where origin is the center of
 * the room. Defaults to
 * {@linkcode Utils.DEFAULT_POSITION DEFAULT_POSITION}.
 * @param {Float32Array} options.forward
 * The listener's initial forward vector. Defaults to
 * {@linkcode Utils.DEFAULT_FORWARD DEFAULT_FORWARD}.
 * @param {Float32Array} options.up
 * The listener's initial up vector. Defaults to
 * {@linkcode Utils.DEFAULT_UP DEFAULT_UP}.
 */
class Listener {
    constructor(context, options) {
        // Public variables.
        /**
         * Position (in meters).
         * @member {Float32Array} position
         * @memberof Listener
         * @instance
         */
        /**
         * Ambisonic (multichannel) input {@link
         * https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
         * @member {AudioNode} input
         * @memberof Listener
         * @instance
         */
        /**
         * Binaurally-rendered stereo (2-channel) output {@link
         * https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
         * @member {AudioNode} output
         * @memberof Listener
         * @instance
         */
        /**
         * Ambisonic (multichannel) output {@link
         * https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
         * @member {AudioNode} ambisonicOutput
         * @memberof Listener
         * @instance
         */
        // Use defaults for undefined arguments.
        if (options == undefined) {
            options = {};
        }
        if (options.ambisonicOrder == undefined) {
            options.ambisonicOrder = Utils.DEFAULT_AMBISONIC_ORDER;
        }
        if (options.position == undefined) {
            options.position = Utils.DEFAULT_POSITION.slice();
        }
        if (options.forward == undefined) {
            options.forward = Utils.DEFAULT_FORWARD.slice();
        }
        if (options.up == undefined) {
            options.up = Utils.DEFAULT_UP.slice();
        }
        // Member variables.
        this.position = new Float32Array(3);
        this._tempMatrix3 = new Float32Array(9);
        // Select the appropriate HRIR filters using 2-channel chunks since
        // multichannel audio is not yet supported by a majority of browsers.
        this._ambisonicOrder =
            Encoder.validateAmbisonicOrder(options.ambisonicOrder);
        // Create audio nodes.
        this._context = context;
        if (this._ambisonicOrder == 1) {
            this._renderer = Omnitone.createFOARenderer(context, {});
        }
        else if (this._ambisonicOrder > 1) {
            this._renderer = Omnitone.createHOARenderer(context, {
                ambisonicOrder: this._ambisonicOrder,
            });
        }
        // These nodes are created in order to safely asynchronously load Omnitone
        // while the rest of the scene is being created.
        this.input = context.createGain();
        this.output = context.createGain();
        this.ambisonicOutput = context.createGain();
        // Initialize Omnitone (async) and connect to audio graph when complete.
        let that = this;
        this._renderer.initialize().then(() => {
            // Connect pre-rotated soundfield to renderer.
            that.input.connect(that._renderer.input);
            // Connect rotated soundfield to ambisonic output.
            if (that._ambisonicOrder > 1) {
                that._renderer._hoaRotator.output.connect(that.ambisonicOutput);
            }
            else {
                that._renderer._foaRotator.output.connect(that.ambisonicOutput);
            }
            // Connect binaurally-rendered soundfield to binaural output.
            that._renderer.output.connect(that.output);
        });
        // Set orientation and update rotation matrix accordingly.
        this.setOrientation(options.forward[0], options.forward[1], options.forward[2], options.up[0], options.up[1], options.up[2]);
    }
    /**
     * Set the source's orientation using forward and up vectors.
     * @param {Number} forwardX
     * @param {Number} forwardY
     * @param {Number} forwardZ
     * @param {Number} upX
     * @param {Number} upY
     * @param {Number} upZ
     */
    setOrientation(forwardX, forwardY, forwardZ, upX, upY, upZ) {
        let right = Utils.crossProduct([forwardX, forwardY, forwardZ], [upX, upY, upZ]);
        this._tempMatrix3[0] = right[0];
        this._tempMatrix3[1] = right[1];
        this._tempMatrix3[2] = right[2];
        this._tempMatrix3[3] = upX;
        this._tempMatrix3[4] = upY;
        this._tempMatrix3[5] = upZ;
        this._tempMatrix3[6] = forwardX;
        this._tempMatrix3[7] = forwardY;
        this._tempMatrix3[8] = forwardZ;
        this._renderer.setRotationMatrix3(this._tempMatrix3);
    }
    /**
     * Set the listener's position and orientation using a Three.js Matrix4 object.
     * @param {Object} matrix4
     * The Three.js Matrix4 object representing the listener's world transform.
     */
    setFromMatrix(matrix4) {
        // Update ambisonic rotation matrix internally.
        this._renderer.setRotationMatrix4(matrix4.elements);
        // Extract position from matrix.
        this.position[0] = matrix4.elements[12];
        this.position[1] = matrix4.elements[13];
        this.position[2] = matrix4.elements[14];
    }
}
export default Listener;
