assassin-bug/framework/resonator/vendor/resonance-es6/early-reflections.js

213 lines
9.6 KiB
JavaScript

/**
* @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 Ray-tracing-based early reflections model.
* @author Andrew Allen <bitllama@google.com>
*/
'use strict';
// Internal dependencies.
import Utils from './utils.js';
/**
* @class EarlyReflections
* @description Ray-tracing-based early reflections model.
* @param {AudioContext} context
* Associated {@link
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}.
* @param {Object} options
* @param {Utils~RoomDimensions} options.dimensions
* Room dimensions (in meters). Defaults to
* {@linkcode Utils.DEFAULT_ROOM_DIMENSIONS DEFAULT_ROOM_DIMENSIONS}.
* @param {Object} options.coefficients
* Frequency-independent reflection coeffs per wall. Defaults to
* {@linkcode Utils.DEFAULT_REFLECTION_COEFFICIENTS
* DEFAULT_REFLECTION_COEFFICIENTS}.
* @param {Number} options.speedOfSound
* (in meters / second). Defaults to {@linkcode Utils.DEFAULT_SPEED_OF_SOUND
* DEFAULT_SPEED_OF_SOUND}.
* @param {Float32Array} options.listenerPosition
* (in meters). Defaults to
* {@linkcode Utils.DEFAULT_POSITION DEFAULT_POSITION}.
*/
class EarlyReflections {
constructor(context, options) {
// Public variables.
/**
* The room's speed of sound (in meters/second).
* @member {Number} speedOfSound
* @memberof EarlyReflections
* @instance
*/
/**
* Mono (1-channel) input {@link
* https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
* @member {AudioNode} input
* @memberof EarlyReflections
* @instance
*/
/**
* First-order ambisonic (4-channel) output {@link
* https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
* @member {AudioNode} output
* @memberof EarlyReflections
* @instance
*/
// Use defaults for undefined arguments.
if (options == undefined) {
options = {};
}
if (options.speedOfSound == undefined) {
options.speedOfSound = Utils.DEFAULT_SPEED_OF_SOUND;
}
if (options.listenerPosition == undefined) {
options.listenerPosition = Utils.DEFAULT_POSITION.slice();
}
if (options.coefficients == undefined) {
options.coefficients = {};
Object.assign(options.coefficients, Utils.DEFAULT_REFLECTION_COEFFICIENTS);
}
// Assign room's speed of sound.
this.speedOfSound = options.speedOfSound;
// Create nodes.
this.input = context.createGain();
this.output = context.createGain();
this._lowpass = context.createBiquadFilter();
this._delays = {};
this._gains = {}; // gainPerWall = (ReflectionCoeff / Attenuation)
this._inverters = {}; // 3 of these are needed for right/back/down walls.
this._merger = context.createChannelMerger(4); // First-order encoding only.
// Connect audio graph for each wall reflection.
for (let property in Utils.DEFAULT_REFLECTION_COEFFICIENTS) {
if (Utils.DEFAULT_REFLECTION_COEFFICIENTS
.hasOwnProperty(property)) {
this._delays[property] =
context.createDelay(Utils.MAX_DURATION);
this._gains[property] = context.createGain();
}
}
this._inverters.right = context.createGain();
this._inverters.down = context.createGain();
this._inverters.back = context.createGain();
// Initialize lowpass filter.
this._lowpass.type = 'lowpass';
this._lowpass.frequency.value = Utils.DEFAULT_REFLECTION_CUTOFF_FREQUENCY;
this._lowpass.Q.value = 0;
// Initialize encoder directions, set delay times and gains to 0.
for (let property in Utils.DEFAULT_REFLECTION_COEFFICIENTS) {
if (Utils.DEFAULT_REFLECTION_COEFFICIENTS
.hasOwnProperty(property)) {
this._delays[property].delayTime.value = 0;
this._gains[property].gain.value = 0;
}
}
// Initialize inverters for opposite walls ('right', 'down', 'back' only).
this._inverters.right.gain.value = -1;
this._inverters.down.gain.value = -1;
this._inverters.back.gain.value = -1;
// Connect nodes.
this.input.connect(this._lowpass);
for (let property in Utils.DEFAULT_REFLECTION_COEFFICIENTS) {
if (Utils.DEFAULT_REFLECTION_COEFFICIENTS
.hasOwnProperty(property)) {
this._lowpass.connect(this._delays[property]);
this._delays[property].connect(this._gains[property]);
this._gains[property].connect(this._merger, 0, 0);
}
}
// Connect gains to ambisonic channel output.
// Left: [1 1 0 0]
// Right: [1 -1 0 0]
// Up: [1 0 1 0]
// Down: [1 0 -1 0]
// Front: [1 0 0 1]
// Back: [1 0 0 -1]
this._gains.left.connect(this._merger, 0, 1);
this._gains.right.connect(this._inverters.right);
this._inverters.right.connect(this._merger, 0, 1);
this._gains.up.connect(this._merger, 0, 2);
this._gains.down.connect(this._inverters.down);
this._inverters.down.connect(this._merger, 0, 2);
this._gains.front.connect(this._merger, 0, 3);
this._gains.back.connect(this._inverters.back);
this._inverters.back.connect(this._merger, 0, 3);
this._merger.connect(this.output);
// Initialize.
this._listenerPosition = options.listenerPosition;
this.setRoomProperties(options.dimensions, options.coefficients);
}
/**
* Set the listener's position (in meters),
* where [0,0,0] is the center of the room.
* @param {Number} x
* @param {Number} y
* @param {Number} z
*/
setListenerPosition(x, y, z) {
// Assign listener position.
this._listenerPosition = [x, y, z];
// Determine distances to each wall.
let distances = {
left: Utils.DEFAULT_REFLECTION_MULTIPLIER * Math.max(0, this._halfDimensions.width + x) + Utils.DEFAULT_REFLECTION_MIN_DISTANCE,
right: Utils.DEFAULT_REFLECTION_MULTIPLIER * Math.max(0, this._halfDimensions.width - x) + Utils.DEFAULT_REFLECTION_MIN_DISTANCE,
front: Utils.DEFAULT_REFLECTION_MULTIPLIER * Math.max(0, this._halfDimensions.depth + z) + Utils.DEFAULT_REFLECTION_MIN_DISTANCE,
back: Utils.DEFAULT_REFLECTION_MULTIPLIER * Math.max(0, this._halfDimensions.depth - z) + Utils.DEFAULT_REFLECTION_MIN_DISTANCE,
down: Utils.DEFAULT_REFLECTION_MULTIPLIER * Math.max(0, this._halfDimensions.height + y) + Utils.DEFAULT_REFLECTION_MIN_DISTANCE,
up: Utils.DEFAULT_REFLECTION_MULTIPLIER * Math.max(0, this._halfDimensions.height - y) + Utils.DEFAULT_REFLECTION_MIN_DISTANCE,
};
// Assign delay & attenuation values using distances.
for (let property in Utils.DEFAULT_REFLECTION_COEFFICIENTS) {
if (Utils.DEFAULT_REFLECTION_COEFFICIENTS
.hasOwnProperty(property)) {
// Compute and assign delay (in seconds).
let delayInSecs = distances[property] / this.speedOfSound;
this._delays[property].delayTime.value = delayInSecs;
// Compute and assign gain, uses logarithmic rolloff: "g = R / (d + 1)"
let attenuation = this._coefficients[property] / distances[property];
this._gains[property].gain.value = attenuation;
}
}
}
/**
* Set the room's properties which determines the characteristics of
* reflections.
* @param {Utils~RoomDimensions} dimensions
* Room dimensions (in meters). Defaults to
* {@linkcode Utils.DEFAULT_ROOM_DIMENSIONS DEFAULT_ROOM_DIMENSIONS}.
* @param {Object} coefficients
* Frequency-independent reflection coeffs per wall. Defaults to
* {@linkcode Utils.DEFAULT_REFLECTION_COEFFICIENTS
* DEFAULT_REFLECTION_COEFFICIENTS}.
*/
setRoomProperties(dimensions, coefficients) {
if (dimensions == undefined) {
dimensions = {};
Object.assign(dimensions, Utils.DEFAULT_ROOM_DIMENSIONS);
}
if (coefficients == undefined) {
coefficients = {};
Object.assign(coefficients, Utils.DEFAULT_REFLECTION_COEFFICIENTS);
}
this._coefficients = coefficients;
// Sanitize dimensions and store half-dimensions.
this._halfDimensions = {};
this._halfDimensions.width = dimensions.width * 0.5;
this._halfDimensions.height = dimensions.height * 0.5;
this._halfDimensions.depth = dimensions.depth * 0.5;
// Update listener position with new room properties.
this.setListenerPosition(this._listenerPosition[0], this._listenerPosition[1], this._listenerPosition[2]);
}
}
export default EarlyReflections;