/**
 * @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 Late reverberation filter for Ambisonic content.
 * @author Andrew Allen <bitllama@google.com>
 */
'use strict';
// Internal dependencies.
import Utils from './utils.js';
/**
 * @class LateReflections
 * @description Late-reflections reverberation filter for Ambisonic content.
 * @param {AudioContext} context
 * Associated {@link
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}.
 * @param {Object} options
 * @param {Array} options.durations
 * Multiband RT60 durations (in seconds) for each frequency band, listed as
 * {@linkcode Utils.DEFAULT_REVERB_FREQUENCY_BANDS
 * FREQUDEFAULT_REVERB_FREQUENCY_BANDSENCY_BANDS}. Defaults to
 * {@linkcode Utils.DEFAULT_REVERB_DURATIONS DEFAULT_REVERB_DURATIONS}.
 * @param {Number} options.predelay Pre-delay (in milliseconds). Defaults to
 * {@linkcode Utils.DEFAULT_REVERB_PREDELAY DEFAULT_REVERB_PREDELAY}.
 * @param {Number} options.gain Output gain (linear). Defaults to
 * {@linkcode Utils.DEFAULT_REVERB_GAIN DEFAULT_REVERB_GAIN}.
 * @param {Number} options.bandwidth Bandwidth (in octaves) for each frequency
 * band. Defaults to
 * {@linkcode Utils.DEFAULT_REVERB_BANDWIDTH DEFAULT_REVERB_BANDWIDTH}.
 * @param {Number} options.tailonset Length (in milliseconds) of impulse
 * response to apply a half-Hann window. Defaults to
 * {@linkcode Utils.DEFAULT_REVERB_TAIL_ONSET DEFAULT_REVERB_TAIL_ONSET}.
 */
class LateReflections {
    constructor(context, options) {
        // Public variables.
        /**
         * Mono (1-channel) input {@link
         * https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
         * @member {AudioNode} input
         * @memberof LateReflections
         * @instance
         */
        /**
         * Mono (1-channel) output {@link
         * https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
         * @member {AudioNode} output
         * @memberof LateReflections
         * @instance
         */
        // Use defaults for undefined arguments.
        if (options == undefined) {
            options = {};
        }
        if (options.durations == undefined) {
            options.durations = Utils.DEFAULT_REVERB_DURATIONS.slice();
        }
        if (options.predelay == undefined) {
            options.predelay = Utils.DEFAULT_REVERB_PREDELAY;
        }
        if (options.gain == undefined) {
            options.gain = Utils.DEFAULT_REVERB_GAIN;
        }
        if (options.bandwidth == undefined) {
            options.bandwidth = Utils.DEFAULT_REVERB_BANDWIDTH;
        }
        if (options.tailonset == undefined) {
            options.tailonset = Utils.DEFAULT_REVERB_TAIL_ONSET;
        }
        // Assign pre-computed variables.
        let delaySecs = options.predelay / 1000;
        this._bandwidthCoeff = options.bandwidth * Utils.LOG2_DIV2;
        this._tailonsetSamples = options.tailonset / 1000;
        // Create nodes.
        this._context = context;
        this.input = context.createGain();
        this._predelay = context.createDelay(delaySecs);
        this._convolver = context.createConvolver();
        this.output = context.createGain();
        // Set reverb attenuation.
        this.output.gain.value = options.gain;
        // Disable normalization.
        this._convolver.normalize = false;
        // Connect nodes.
        this.input.connect(this._predelay);
        this._predelay.connect(this._convolver);
        this._convolver.connect(this.output);
        // Compute IR using RT60 values.
        this.setDurations(options.durations);
    }
    /**
     * Re-compute a new impulse response by providing Multiband RT60 durations.
     * @param {Array} durations
     * Multiband RT60 durations (in seconds) for each frequency band, listed as
     * {@linkcode Utils.DEFAULT_REVERB_FREQUENCY_BANDS
     * DEFAULT_REVERB_FREQUENCY_BANDS}.
     */
    setDurations(durations) {
        if (durations.length !== Utils.NUMBER_REVERB_FREQUENCY_BANDS) {
            Utils.log('Warning: invalid number of RT60 values provided to reverb.');
            return;
        }
        // Compute impulse response.
        let durationsSamples = new Float32Array(Utils.NUMBER_REVERB_FREQUENCY_BANDS);
        let sampleRate = this._context.sampleRate;
        for (let i = 0; i < durations.length; i++) {
            // Clamp within suitable range.
            durations[i] =
                Math.max(0, Math.min(Utils.DEFAULT_REVERB_MAX_DURATION, durations[i]));
            // Convert seconds to samples.
            durationsSamples[i] = Math.round(durations[i] * sampleRate *
                Utils.DEFAULT_REVERB_DURATION_MULTIPLIER);
        }
        ;
        // Determine max RT60 length in samples.
        let durationsSamplesMax = 0;
        for (let i = 0; i < durationsSamples.length; i++) {
            if (durationsSamples[i] > durationsSamplesMax) {
                durationsSamplesMax = durationsSamples[i];
            }
        }
        // Skip this step if there is no reverberation to compute.
        if (durationsSamplesMax < 1) {
            durationsSamplesMax = 1;
        }
        // Create impulse response buffer.
        let buffer = this._context.createBuffer(1, durationsSamplesMax, sampleRate);
        let bufferData = buffer.getChannelData(0);
        // Create noise signal (computed once, referenced in each band's routine).
        let noiseSignal = new Float32Array(durationsSamplesMax);
        for (let i = 0; i < durationsSamplesMax; i++) {
            noiseSignal[i] = Math.random() * 2 - 1;
        }
        // Compute the decay rate per-band and filter the decaying noise signal.
        for (let i = 0; i < Utils.NUMBER_REVERB_FREQUENCY_BANDS; i++) {
            // Compute decay rate.
            let decayRate = -Utils.LOG1000 / durationsSamples[i];
            // Construct a standard one-zero, two-pole bandpass filter:
            // H(z) = (b0 * z^0 + b1 * z^-1 + b2 * z^-2) / (1 + a1 * z^-1 + a2 * z^-2)
            let omega = Utils.TWO_PI *
                Utils.DEFAULT_REVERB_FREQUENCY_BANDS[i] / sampleRate;
            let sinOmega = Math.sin(omega);
            let alpha = sinOmega * Math.sinh(this._bandwidthCoeff * omega / sinOmega);
            let a0CoeffReciprocal = 1 / (1 + alpha);
            let b0Coeff = alpha * a0CoeffReciprocal;
            let a1Coeff = -2 * Math.cos(omega) * a0CoeffReciprocal;
            let a2Coeff = (1 - alpha) * a0CoeffReciprocal;
            // We optimize since b2 = -b0, b1 = 0.
            // Update equation for two-pole bandpass filter:
            //   u[n] = x[n] - a1 * x[n-1] - a2 * x[n-2]
            //   y[n] = b0 * (u[n] - u[n-2])
            let um1 = 0;
            let um2 = 0;
            for (let j = 0; j < durationsSamples[i]; j++) {
                // Exponentially-decaying white noise.
                let x = noiseSignal[j] * Math.exp(decayRate * j);
                // Filter signal with bandpass filter and add to output.
                let u = x - a1Coeff * um1 - a2Coeff * um2;
                bufferData[j] += b0Coeff * (u - um2);
                // Update coefficients.
                um2 = um1;
                um1 = u;
            }
        }
        // Create and apply half of a Hann window to the beginning of the
        // impulse response.
        let halfHannLength = Math.round(this._tailonsetSamples);
        for (let i = 0; i < Math.min(bufferData.length, halfHannLength); i++) {
            let halfHann = 0.5 * (1 - Math.cos(Utils.TWO_PI * i / (2 * halfHannLength - 1)));
            bufferData[i] *= halfHann;
        }
        this._convolver.buffer = buffer;
    }
}
export default LateReflections;
