Update framework
This commit is contained in:
300
framework/resonator/vendor/resonance-es6/room.js
vendored
Normal file
300
framework/resonator/vendor/resonance-es6/room.js
vendored
Normal file
@@ -0,0 +1,300 @@
|
||||
/**
|
||||
* @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 Complete room model with early and late reflections.
|
||||
* @author Andrew Allen <bitllama@google.com>
|
||||
*/
|
||||
'use strict';
|
||||
// Internal dependencies.
|
||||
import LateReflections from './late-reflections.js';
|
||||
import EarlyReflections from './early-reflections.js';
|
||||
import Utils from './utils.js';
|
||||
/**
|
||||
* Generate absorption coefficients from material names.
|
||||
* @param {Object} materials
|
||||
* @return {Object}
|
||||
*/
|
||||
function _getCoefficientsFromMaterials(materials) {
|
||||
// Initialize coefficients to use defaults.
|
||||
let coefficients = {};
|
||||
for (let property in Utils.DEFAULT_ROOM_MATERIALS) {
|
||||
if (Utils.DEFAULT_ROOM_MATERIALS.hasOwnProperty(property)) {
|
||||
coefficients[property] = Utils.ROOM_MATERIAL_COEFFICIENTS[Utils.DEFAULT_ROOM_MATERIALS[property]];
|
||||
}
|
||||
}
|
||||
// Sanitize materials.
|
||||
if (materials == undefined) {
|
||||
materials = {};
|
||||
Object.assign(materials, Utils.DEFAULT_ROOM_MATERIALS);
|
||||
}
|
||||
// Assign coefficients using provided materials.
|
||||
for (let property in Utils.DEFAULT_ROOM_MATERIALS) {
|
||||
if (Utils.DEFAULT_ROOM_MATERIALS.hasOwnProperty(property) &&
|
||||
materials.hasOwnProperty(property)) {
|
||||
if (materials[property] in Utils.ROOM_MATERIAL_COEFFICIENTS) {
|
||||
coefficients[property] =
|
||||
Utils.ROOM_MATERIAL_COEFFICIENTS[materials[property]];
|
||||
}
|
||||
else {
|
||||
Utils.log('Material \"' + materials[property] + '\" on wall \"' +
|
||||
property + '\" not found. Using \"' +
|
||||
Utils.DEFAULT_ROOM_MATERIALS[property] + '\".');
|
||||
}
|
||||
}
|
||||
else {
|
||||
Utils.log('Wall \"' + property + '\" is not defined. Default used.');
|
||||
}
|
||||
}
|
||||
return coefficients;
|
||||
}
|
||||
/**
|
||||
* Sanitize coefficients.
|
||||
* @param {Object} coefficients
|
||||
* @return {Object}
|
||||
*/
|
||||
function _sanitizeCoefficients(coefficients) {
|
||||
if (coefficients == undefined) {
|
||||
coefficients = {};
|
||||
}
|
||||
for (let property in Utils.DEFAULT_ROOM_MATERIALS) {
|
||||
if (!(coefficients.hasOwnProperty(property))) {
|
||||
// If element is not present, use default coefficients.
|
||||
coefficients[property] = Utils.ROOM_MATERIAL_COEFFICIENTS[Utils.DEFAULT_ROOM_MATERIALS[property]];
|
||||
}
|
||||
}
|
||||
return coefficients;
|
||||
}
|
||||
/**
|
||||
* Sanitize dimensions.
|
||||
* @param {Utils~RoomDimensions} dimensions
|
||||
* @return {Utils~RoomDimensions}
|
||||
*/
|
||||
function _sanitizeDimensions(dimensions) {
|
||||
if (dimensions == undefined) {
|
||||
dimensions = {};
|
||||
}
|
||||
for (let property in Utils.DEFAULT_ROOM_DIMENSIONS) {
|
||||
if (!(dimensions.hasOwnProperty(property))) {
|
||||
dimensions[property] = Utils.DEFAULT_ROOM_DIMENSIONS[property];
|
||||
}
|
||||
}
|
||||
return dimensions;
|
||||
}
|
||||
/**
|
||||
* Compute frequency-dependent reverb durations.
|
||||
* @param {Utils~RoomDimensions} dimensions
|
||||
* @param {Object} coefficients
|
||||
* @param {Number} speedOfSound
|
||||
* @return {Array}
|
||||
*/
|
||||
function _getDurationsFromProperties(dimensions, coefficients, speedOfSound) {
|
||||
let durations = new Float32Array(Utils.NUMBER_REVERB_FREQUENCY_BANDS);
|
||||
// Sanitize inputs.
|
||||
dimensions = _sanitizeDimensions(dimensions);
|
||||
coefficients = _sanitizeCoefficients(coefficients);
|
||||
if (speedOfSound == undefined) {
|
||||
speedOfSound = Utils.DEFAULT_SPEED_OF_SOUND;
|
||||
}
|
||||
// Acoustic constant.
|
||||
let k = Utils.TWENTY_FOUR_LOG10 / speedOfSound;
|
||||
// Compute volume, skip if room is not present.
|
||||
let volume = dimensions.width * dimensions.height * dimensions.depth;
|
||||
if (volume < Utils.ROOM_MIN_VOLUME) {
|
||||
return durations;
|
||||
}
|
||||
// Room surface area.
|
||||
let leftRightArea = dimensions.width * dimensions.height;
|
||||
let floorCeilingArea = dimensions.width * dimensions.depth;
|
||||
let frontBackArea = dimensions.depth * dimensions.height;
|
||||
let totalArea = 2 * (leftRightArea + floorCeilingArea + frontBackArea);
|
||||
for (let i = 0; i < Utils.NUMBER_REVERB_FREQUENCY_BANDS; i++) {
|
||||
// Effective absorptive area.
|
||||
let absorbtionArea = (coefficients.left[i] + coefficients.right[i]) * leftRightArea +
|
||||
(coefficients.down[i] + coefficients.up[i]) * floorCeilingArea +
|
||||
(coefficients.front[i] + coefficients.back[i]) * frontBackArea;
|
||||
let meanAbsorbtionArea = absorbtionArea / totalArea;
|
||||
// Compute reverberation using Eyring equation [1].
|
||||
// [1] Beranek, Leo L. "Analysis of Sabine and Eyring equations and their
|
||||
// application to concert hall audience and chair absorption." The
|
||||
// Journal of the Acoustical Society of America, Vol. 120, No. 3.
|
||||
// (2006), pp. 1399-1399.
|
||||
durations[i] = Utils.ROOM_EYRING_CORRECTION_COEFFICIENT * k * volume /
|
||||
(-totalArea * Math.log(1 - meanAbsorbtionArea) + 4 *
|
||||
Utils.ROOM_AIR_ABSORPTION_COEFFICIENTS[i] * volume);
|
||||
}
|
||||
return durations;
|
||||
}
|
||||
/**
|
||||
* Compute reflection coefficients from absorption coefficients.
|
||||
* @param {Object} absorptionCoefficients
|
||||
* @return {Object}
|
||||
*/
|
||||
function _computeReflectionCoefficients(absorptionCoefficients) {
|
||||
let reflectionCoefficients = [];
|
||||
for (let property in Utils.DEFAULT_REFLECTION_COEFFICIENTS) {
|
||||
if (Utils.DEFAULT_REFLECTION_COEFFICIENTS
|
||||
.hasOwnProperty(property)) {
|
||||
// Compute average absorption coefficient (per wall).
|
||||
reflectionCoefficients[property] = 0;
|
||||
for (let j = 0; j < Utils.NUMBER_REFLECTION_AVERAGING_BANDS; j++) {
|
||||
let bandIndex = j + Utils.ROOM_STARTING_AVERAGING_BAND;
|
||||
reflectionCoefficients[property] +=
|
||||
absorptionCoefficients[property][bandIndex];
|
||||
}
|
||||
reflectionCoefficients[property] /=
|
||||
Utils.NUMBER_REFLECTION_AVERAGING_BANDS;
|
||||
// Convert absorption coefficient to reflection coefficient.
|
||||
reflectionCoefficients[property] =
|
||||
Math.sqrt(1 - reflectionCoefficients[property]);
|
||||
}
|
||||
}
|
||||
return reflectionCoefficients;
|
||||
}
|
||||
/**
|
||||
* @class Room
|
||||
* @description Model that manages early and late reflections using acoustic
|
||||
* properties and listener position relative to a rectangular room.
|
||||
* @param {AudioContext} context
|
||||
* Associated {@link
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext}.
|
||||
* @param {Object} options
|
||||
* @param {Float32Array} options.listenerPosition
|
||||
* The listener's initial position (in meters), where origin is the center of
|
||||
* the room. Defaults to {@linkcode Utils.DEFAULT_POSITION DEFAULT_POSITION}.
|
||||
* @param {Utils~RoomDimensions} options.dimensions Room dimensions (in meters). Defaults to
|
||||
* {@linkcode Utils.DEFAULT_ROOM_DIMENSIONS DEFAULT_ROOM_DIMENSIONS}.
|
||||
* @param {Utils~RoomMaterials} options.materials Named acoustic materials per wall.
|
||||
* Defaults to {@linkcode Utils.DEFAULT_ROOM_MATERIALS DEFAULT_ROOM_MATERIALS}.
|
||||
* @param {Number} options.speedOfSound
|
||||
* (in meters/second). Defaults to
|
||||
* {@linkcode Utils.DEFAULT_SPEED_OF_SOUND DEFAULT_SPEED_OF_SOUND}.
|
||||
*/
|
||||
class Room {
|
||||
constructor(context, options) {
|
||||
// Public variables.
|
||||
/**
|
||||
* EarlyReflections {@link EarlyReflections EarlyReflections} submodule.
|
||||
* @member {AudioNode} early
|
||||
* @memberof Room
|
||||
* @instance
|
||||
*/
|
||||
/**
|
||||
* LateReflections {@link LateReflections LateReflections} submodule.
|
||||
* @member {AudioNode} late
|
||||
* @memberof Room
|
||||
* @instance
|
||||
*/
|
||||
/**
|
||||
* Ambisonic (multichannel) output {@link
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/AudioNode AudioNode}.
|
||||
* @member {AudioNode} output
|
||||
* @memberof Room
|
||||
* @instance
|
||||
*/
|
||||
// Use defaults for undefined arguments.
|
||||
if (options == undefined) {
|
||||
options = {};
|
||||
}
|
||||
if (options.listenerPosition == undefined) {
|
||||
options.listenerPosition = Utils.DEFAULT_POSITION.slice();
|
||||
}
|
||||
if (options.dimensions == undefined) {
|
||||
options.dimensions = {};
|
||||
Object.assign(options.dimensions, Utils.DEFAULT_ROOM_DIMENSIONS);
|
||||
}
|
||||
if (options.materials == undefined) {
|
||||
options.materials = {};
|
||||
Object.assign(options.materials, Utils.DEFAULT_ROOM_MATERIALS);
|
||||
}
|
||||
if (options.speedOfSound == undefined) {
|
||||
options.speedOfSound = Utils.DEFAULT_SPEED_OF_SOUND;
|
||||
}
|
||||
// Sanitize room-properties-related arguments.
|
||||
options.dimensions = _sanitizeDimensions(options.dimensions);
|
||||
let absorptionCoefficients = _getCoefficientsFromMaterials(options.materials);
|
||||
let reflectionCoefficients = _computeReflectionCoefficients(absorptionCoefficients);
|
||||
let durations = _getDurationsFromProperties(options.dimensions, absorptionCoefficients, options.speedOfSound);
|
||||
// Construct submodules for early and late reflections.
|
||||
this.early = new EarlyReflections(context, {
|
||||
dimensions: options.dimensions,
|
||||
coefficients: reflectionCoefficients,
|
||||
speedOfSound: options.speedOfSound,
|
||||
listenerPosition: options.listenerPosition,
|
||||
});
|
||||
this.late = new LateReflections(context, {
|
||||
durations: durations,
|
||||
});
|
||||
this.speedOfSound = options.speedOfSound;
|
||||
// Construct auxillary audio nodes.
|
||||
this.output = context.createGain();
|
||||
this.early.output.connect(this.output);
|
||||
this._merger = context.createChannelMerger(4);
|
||||
this.late.output.connect(this._merger, 0, 0);
|
||||
this._merger.connect(this.output);
|
||||
}
|
||||
/**
|
||||
* Set the room's dimensions and wall materials.
|
||||
* @param {Utils~RoomDimensions} dimensions Room dimensions (in meters). Defaults to
|
||||
* {@linkcode Utils.DEFAULT_ROOM_DIMENSIONS DEFAULT_ROOM_DIMENSIONS}.
|
||||
* @param {Utils~RoomMaterials} materials Named acoustic materials per wall. Defaults to
|
||||
* {@linkcode Utils.DEFAULT_ROOM_MATERIALS DEFAULT_ROOM_MATERIALS}.
|
||||
*/
|
||||
setProperties(dimensions, materials) {
|
||||
// Compute late response.
|
||||
let absorptionCoefficients = _getCoefficientsFromMaterials(materials);
|
||||
let durations = _getDurationsFromProperties(dimensions, absorptionCoefficients, this.speedOfSound);
|
||||
this.late.setDurations(durations);
|
||||
// Compute early response.
|
||||
this.early.speedOfSound = this.speedOfSound;
|
||||
let reflectionCoefficients = _computeReflectionCoefficients(absorptionCoefficients);
|
||||
this.early.setRoomProperties(dimensions, reflectionCoefficients);
|
||||
}
|
||||
/**
|
||||
* Set the listener's position (in meters), where origin is the center of
|
||||
* the room.
|
||||
* @param {Number} x
|
||||
* @param {Number} y
|
||||
* @param {Number} z
|
||||
*/
|
||||
setListenerPosition(x, y, z) {
|
||||
this.early.speedOfSound = this.speedOfSound;
|
||||
this.early.setListenerPosition(x, y, z);
|
||||
// Disable room effects if the listener is outside the room boundaries.
|
||||
let distance = this.getDistanceOutsideRoom(x, y, z);
|
||||
let gain = 1;
|
||||
if (distance > Utils.EPSILON_FLOAT) {
|
||||
gain = 1 - distance / Utils.LISTENER_MAX_OUTSIDE_ROOM_DISTANCE;
|
||||
// Clamp gain between 0 and 1.
|
||||
gain = Math.max(0, Math.min(1, gain));
|
||||
}
|
||||
this.output.gain.value = gain;
|
||||
}
|
||||
/**
|
||||
* Compute distance outside room of provided position (in meters).
|
||||
* @param {Number} x
|
||||
* @param {Number} y
|
||||
* @param {Number} z
|
||||
* @return {Number}
|
||||
* Distance outside room (in meters). Returns 0 if inside room.
|
||||
*/
|
||||
getDistanceOutsideRoom(x, y, z) {
|
||||
let dx = Math.max(0, -this.early._halfDimensions.width - x, x - this.early._halfDimensions.width);
|
||||
let dy = Math.max(0, -this.early._halfDimensions.height - y, y - this.early._halfDimensions.height);
|
||||
let dz = Math.max(0, -this.early._halfDimensions.depth - z, z - this.early._halfDimensions.depth);
|
||||
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
}
|
||||
}
|
||||
export default Room;
|
Reference in New Issue
Block a user