diff --git a/README.md b/README.md index 4316fe23..dfd3d320 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ ForgeJS has the following dependencies: - [three.js](https://threejs.org/) r86 ([MIT license](https://github.com/mrdoob/three.js/blob/dev/LICENSE)) - [hammer.js](http://hammerjs.github.io/) 2.0.8 ([MIT license](https://github.com/hammerjs/hammer.js/blob/master/LICENSE.md)) -- [omnitone](http://googlechrome.github.io/omnitone/#home) 1.0.1 ([Apache 2.0 license](https://github.com/GoogleChrome/omnitone/blob/master/LICENSE)) +- [omnitone](http://googlechrome.github.io/omnitone/#home) 1.0.6 ([Apache 2.0 license](https://github.com/GoogleChrome/omnitone/blob/master/LICENSE)) - [dash.js](https://github.com/Dash-Industry-Forum/dash.js) 2.6.0 ([BSD license](https://github.com/Dash-Industry-Forum/dash.js/blob/development/LICENSE.md)) > NOTE: We made a custom build of three.js with some classes concatenated to it. These classes are included in the original three.js repository but not concatenated in the main build. We added EffectComposer, RenderPass, ClearPass, MaskPass, ShaderPass, TexturePass and CopyShader in our three.custom.min.js. diff --git a/externs/lib/omnitone.ext.js b/externs/lib/omnitone.ext.js index aeffaa67..032be811 100644 --- a/externs/lib/omnitone.ext.js +++ b/externs/lib/omnitone.ext.js @@ -9,51 +9,151 @@ var Omnitone = {}; /** - * Create a singleton FOADecoder instance. + * Create a FOARenderer, the first-order ambisonic decoder and the optimized binaural renderer. * @param {AudioContext} context - Associated AudioContext. - * @param {HTMLMediaElement} element - Video or Audio DOM element to be streamed. - * @param {FOADecoderOptions} options - Options for FOA decoder. - * @return {FOADecoder} + * @param {FOARendererConfig} config + * @return {FOARenderer} */ -Omnitone.createFOADecoder = function (context, element, options) {}; +Omnitone.createFOARenderer = function(context, config) {}; /** - * @typedef {{HRTFSetUrl:(string|undefined), postGainDB:(number|undefined), channelMap:(Array|undefined)}} - * @name FOADecoderOptions - * @property {string} HRTFSetUrl - Base URL for the cube HRTF sets. - * @property {number} postGainDB - Post-decoding gain compensation in dB. - * @property {Array} channelMap - Custom channel map. + * @typedef {{channelMap:(Array|undefined), hrirPathList:(Array|undefined), renderingMode:(string|undefined)}} + * @name FOARendererConfig + * @property {Array} channelMap - Custom channel routing map. Useful for handling the inconsistency in browser's multichannel audio decoding. + * @property {Array} hrirPathList - A list of paths to HRIR files. It overrides the internal HRIR list if given. + * @property {string} renderingMode - Rendering mode. */ -var FOADecoderOptions; +var FOARendererConfig; /** + * Creates HOARenderer for higher-order ambisonic decoding and the optimized binaural rendering. + * @param {AudioContext} context - Associated AudioContext. + * @param {HOARendererConfig} config + * @return {HOARenderer} + */ +Omnitone.createHOARenderer = function(context, config) {}; + +/** + * @typedef {{ambisonicOrder:(number|undefined), hrirPathList:(Array|undefined), renderingMode:(string|undefined)}} + * @name HOARendererConfig + * @property {number} ambisonicOrder - Ambisonic order. + * @property {Array} hrirPathList - A list of paths to HRIR files. It overrides the internal HRIR list if given. + * @property {string} renderingMode - Rendering mode. + */ +var HOARendererConfig; + +/** + * Omnitone FOA renderer class. Uses the optimized convolution technique. * @constructor * @param {AudioContext} context - Associated AudioContext. - * @param {HTMLMediaElement} element - Target video or audio element for streaming. - * @param {FOADecoderOptions} options - * @return {!FOADecoder} + * @param {FOARendererConfig} config + * @return {!FOARenderer} */ -function FOADecoder(context, element, options) {}; +function FOARenderer(context, config) {}; /** - * @param {Object} cameraMatrix - The Matrix4 object of the Three.js camera. + * @const */ -FOADecoder.prototype.setRotationMatrixFromCamera = function(cameraMatrix) {}; +Omnitone.FOARenderer = FOARenderer; /** - * @param {string} mode - Decoding mode. - * When the mode is 'bypass' the decoder is disabled and bypass the input stream to the output. - * Setting the mode to 'ambisonic' activates the decoder. - * When the mode is 'off', all the processing is completely turned off saving the CPU power. + * Initializes and loads the resource for the renderer. + */ +FOARenderer.prototype.initialize = function() {}; + +/** + * Set the channel map. + * @param {Array} channelMap - Custom channel routing for FOA stream. + */ +FOARenderer.prototype.setChannelMap = function(channelMap) {}; + +/** + * Updates the rotation matrix with 3x3 matrix. + * @param {Array} rotationMatrix3 - A 3x3 rotation matrix. (column-major) */ -FOADecoder.prototype.setMode = function(mode) {}; +FOARenderer.prototype.setRotationMatrix3 = function(rotationMatrix3) {}; /** - * + * Updates the rotation matrix with 4x4 matrix. + * @param {Array} rotationMatrix4 - A 4x4 rotation matrix. (column-major) */ -FOADecoder.prototype.initialize = function() {}; +FOARenderer.prototype.setRotationMatrix4 = function(rotationMatrix4) {}; + +/** + * Set the rotation matrix from a Three.js camera object. Deprecated in V1, and this exists only for the backward compatiblity. Instead, use |setRotatationMatrix4()| with Three.js |camera.worldMatrix.elements|. + * @deprecated + * @param {Object} cameraMatrix - Matrix4 from Three.js |camera.matrix|. + */ +FOARenderer.prototype.setRotationMatrixFromCamera = function(cameraMatrix) {}; + +/** + * Set the rendering mode. + * @param {string} mode - Rendering mode. + * - 'ambisonic': activates the ambisonic decoding/binaurl rendering. + * - 'bypass': bypasses the input stream directly to the output. No ambisonic + * decoding or encoding. + * - 'off': all the processing off saving the CPU power. + */ +FOARenderer.prototype.setRenderingMode = function(mode) {}; + +/** + * @type {GainNode} + */ +FOARenderer.prototype.input; + +/** + * @type {GainNode} + */ +FOARenderer.prototype.output; + +/** + * Omnitone HOA renderer class. Uses the optimized convolution technique. + * @constructor + * @param {AudioContext} context - Associated AudioContext. + * @param {HOARendererConfig} config + * @return {!HOARenderer} + */ +function HOARenderer(context, config) {}; /** * @const */ -Omnitone.FOADecoder = FOADecoder; +Omnitone.HOARenderer = HOARenderer; + +/** + * Initializes and loads the resource for the renderer. + * @return {Promise} + */ +HOARenderer.prototype.initialize = function() {}; + +/** + * Updates the rotation matrix with 3x3 matrix. + * @param {Array} rotationMatrix3 - A 3x3 rotation matrix. (column-major) + */ +HOARenderer.prototype.setRotationMatrix3 = function(rotationMatrix3) {}; + +/** + * Updates the rotation matrix with 4x4 matrix. + * @param {Array} rotationMatrix4 - A 4x4 rotation matrix. (column-major) + */ +HOARenderer.prototype.setRotationMatrix4 = function(rotationMatrix4) {}; + +/** + * Set the decoding mode. + * @param {string} mode - Decoding mode. + * - 'ambisonic': activates the ambisonic decoding/binaurl rendering. + * - 'bypass': bypasses the input stream directly to the output. No ambisonic + * decoding or encoding. + * - 'off': all the processing off saving the CPU power. + */ +HOARenderer.prototype.setRenderingMode = function(mode) {}; + +/** + * @type {GainNode} + */ +HOARenderer.prototype.input; + +/** + * @type {GainNode} + */ +HOARenderer.prototype.output; \ No newline at end of file diff --git a/lib/omnitone/omnitone.js b/lib/omnitone/omnitone.js index 8ee46613..32925ca1 100644 --- a/lib/omnitone/omnitone.js +++ b/lib/omnitone/omnitone.js @@ -1,2495 +1,5 @@ -(function webpackUniversalModuleDefinition(root, factory) { - if(typeof exports === 'object' && typeof module === 'object') - module.exports = factory(); - else if(typeof define === 'function' && define.amd) - define([], factory); - else { - var a = factory(); - for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i]; - } -})(this, function() { -return /******/ (function(modules) { // webpackBootstrap -/******/ // The module cache -/******/ var installedModules = {}; -/******/ -/******/ // The require function -/******/ function __webpack_require__(moduleId) { -/******/ -/******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) { -/******/ return installedModules[moduleId].exports; -/******/ } -/******/ // Create a new module (and put it into the cache) -/******/ var module = installedModules[moduleId] = { -/******/ i: moduleId, -/******/ l: false, -/******/ exports: {} -/******/ }; -/******/ -/******/ // Execute the module function -/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); -/******/ -/******/ // Flag the module as loaded -/******/ module.l = true; -/******/ -/******/ // Return the exports of the module -/******/ return module.exports; -/******/ } -/******/ -/******/ -/******/ // expose the modules object (__webpack_modules__) -/******/ __webpack_require__.m = modules; -/******/ -/******/ // expose the module cache -/******/ __webpack_require__.c = installedModules; -/******/ -/******/ // define getter function for harmony exports -/******/ __webpack_require__.d = function(exports, name, getter) { -/******/ if(!__webpack_require__.o(exports, name)) { -/******/ Object.defineProperty(exports, name, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); -/******/ } -/******/ }; -/******/ -/******/ // getDefaultExport function for compatibility with non-harmony modules -/******/ __webpack_require__.n = function(module) { -/******/ var getter = module && module.__esModule ? -/******/ function getDefault() { return module['default']; } : -/******/ function getModuleExports() { return module; }; -/******/ __webpack_require__.d(getter, 'a', getter); -/******/ return getter; -/******/ }; -/******/ -/******/ // Object.prototype.hasOwnProperty.call -/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; -/******/ -/******/ // __webpack_public_path__ -/******/ __webpack_require__.p = ""; -/******/ -/******/ // Load entry module and return exports -/******/ return __webpack_require__(__webpack_require__.s = 11); -/******/ }) -/************************************************************************/ -/******/ ([ -/* 0 */ -/***/ (function(module, exports) { - -/** - * Copyright 2016 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 Omnitone library common utilities. - */ - - -/** - * Omnitone library logging function. - * @param {any} Message to be printed out. - */ -exports.log = function() { - window.console.log.apply(window.console, [ - '%c[Omnitone]%c ' + Array.prototype.slice.call(arguments).join(' ') + - ' %c(@' + performance.now().toFixed(2) + 'ms)', - 'background: #BBDEFB; color: #FF5722; font-weight: 500', 'font-weight: 300', - 'color: #AAA', - ]); -}; - - -/** - * Omnitone library error-throwing function. - * @param {any} Message to be printed out. - */ -exports.throw = function() { - window.console.error.apply(window.console, [ - '%c[Omnitone]%c ' + Array.prototype.slice.call(arguments).join(' ') + - ' %c(@' + performance.now().toFixed(2) + 'ms)', - 'background: #C62828; color: #FFEBEE; font-weight: 800', 'font-weight: 400', - 'color: #AAA', - ]); - - throw new Error(false); -}; - - -// Static temp storage for matrix inversion. -let a00; -let a01; -let a02; -let a03; -let a10; -let a11; -let a12; -let a13; -let a20; -let a21; -let a22; -let a23; -let a30; -let a31; -let a32; -let a33; -let b00; -let b01; -let b02; -let b03; -let b04; -let b05; -let b06; -let b07; -let b08; -let b09; -let b10; -let b11; -let det; - - -/** - * A 4x4 matrix inversion utility. This does not handle the case when the - * arguments are not proper 4x4 matrices. - * @param {Float32Array} out The inverted result. - * @param {Float32Array} a The source matrix. - * @return {Float32Array} out - */ -exports.invertMatrix4 = function(out, a) { - a00 = a[0]; - a01 = a[1]; - a02 = a[2]; - a03 = a[3]; - a10 = a[4]; - a11 = a[5]; - a12 = a[6]; - a13 = a[7]; - a20 = a[8]; - a21 = a[9]; - a22 = a[10]; - a23 = a[11]; - a30 = a[12]; - a31 = a[13]; - a32 = a[14]; - a33 = a[15]; - b00 = a00 * a11 - a01 * a10; - b01 = a00 * a12 - a02 * a10; - b02 = a00 * a13 - a03 * a10; - b03 = a01 * a12 - a02 * a11; - b04 = a01 * a13 - a03 * a11; - b05 = a02 * a13 - a03 * a12; - b06 = a20 * a31 - a21 * a30; - b07 = a20 * a32 - a22 * a30; - b08 = a20 * a33 - a23 * a30; - b09 = a21 * a32 - a22 * a31; - b10 = a21 * a33 - a23 * a31; - b11 = a22 * a33 - a23 * a32; - det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; - - if (!det) { - return null; - } - - det = 1.0 / det; - out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; - out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det; - out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det; - out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det; - out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det; - out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det; - out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det; - out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det; - out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det; - out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det; - out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det; - out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det; - out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det; - out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det; - out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det; - out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det; - - return out; -}; - - -/** - * Check if the given object is an instance of BaseAudioContext. - * @param {AudioContext} context - A context object to be checked. - * @return {Boolean} - */ -exports.isAudioContext = function(context) { - // TODO(hoch): Update this when BaseAudioContext is available for all - // browsers. - return context instanceof AudioContext || - context instanceof OfflineAudioContext; -}; - - -/** - * Check if the given object is a valid AudioBuffer. - * @param {Object} audioBuffer An AudioBuffer object to be checked. - * @return {Boolean} - */ -exports.isAudioBuffer = function(audioBuffer) { - return audioBuffer instanceof AudioBuffer; -}; - - -/** - * Perform channel-wise merge on multiple AudioBuffers. The sample rate and - * the length of buffers to be merged must be identical. - * @param {BaseAudioContext} context - Associated BaseAudioContext. - * @param {AudioBuffer[]} bufferList - An array of AudioBuffers to be merged - * channel-wise. - * @return {AudioBuffer} - A single merged AudioBuffer. - */ -exports.mergeBufferListByChannel = function(context, bufferList) { - const bufferLength = bufferList[0].length; - const bufferSampleRate = bufferList[0].sampleRate; - let bufferNumberOfChannel = 0; - - for (let i = 0; i < bufferList.length; ++i) { - if (bufferNumberOfChannel > 32) { - exports.throw('Utils.mergeBuffer: Number of channels cannot exceed 32.' + - '(got ' + bufferNumberOfChannel + ')'); - } - if (bufferLength !== bufferList[i].length) { - exports.throw('Utils.mergeBuffer: AudioBuffer lengths are ' + - 'inconsistent. (expected ' + bufferLength + ' but got ' + - bufferList[i].length + ')'); - } - if (bufferSampleRate !== bufferList[i].sampleRate) { - exports.throw('Utils.mergeBuffer: AudioBuffer sample rates are ' + - 'inconsistent. (expected ' + bufferSampleRate + ' but got ' + - bufferList[i].sampleRate + ')'); - } - bufferNumberOfChannel += bufferList[i].numberOfChannels; - } - - const buffer = context.createBuffer(bufferNumberOfChannel, - bufferLength, - bufferSampleRate); - let destinationChannelIndex = 0; - for (let i = 0; i < bufferList.length; ++i) { - for (let j = 0; j < bufferList[i].numberOfChannels; ++j) { - buffer.getChannelData(destinationChannelIndex++).set( - bufferList[i].getChannelData(j)); - } - } - - return buffer; -}; - - -/** - * Perform channel-wise split by the given channel count. For example, - * 1 x AudioBuffer(8) -> splitBuffer(context, buffer, 2) -> 4 x AudioBuffer(2). - * @param {BaseAudioContext} context - Associated BaseAudioContext. - * @param {AudioBuffer} audioBuffer - An AudioBuffer to be splitted. - * @param {Number} splitBy - Number of channels to be splitted. - * @return {AudioBuffer[]} - An array of splitted AudioBuffers. - */ -exports.splitBufferbyChannel = function(context, audioBuffer, splitBy) { - if (audioBuffer.numberOfChannels <= splitBy) { - exports.throw('Utils.splitBuffer: Insufficient number of channels. (' + - audioBuffer.numberOfChannels + ' splitted by ' + splitBy + ')'); - } - - let bufflerList = []; - let sourceChannelIndex = 0; - const numberOfSplittedBuffer = - Math.ceil(audioBuffer.numberOfChannels / splitBy); - for (let i = 0; i < numberOfSplittedBuffer; ++i) { - let buffer = context.createBuffer(splitBy, - audioBuffer.length, - audioBuffer.sampleRate); - for (let j = 0; j < splitBy; ++j) { - if (sourceChannelIndex < audioBuffer.numberOfChannels) { - buffer.getChannelData(j).set( - audioBuffer.getChannelData(sourceChannelIndex++)); - } - } - bufflerList.push(buffer); - } - - return bufferList; -}; - - -/***/ }), -/* 1 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/** - * 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 Streamlined AudioBuffer loader. - */ - - - - -const Utils = __webpack_require__(0); - - -/** - * BufferList object mananges the async loading/decoding of multiple - * AudioBuffers from multiple URLs. - * @constructor - * @param {BaseAudioContext} context - Associated BaseAudioContext. - * @param {string[]} bufferData - An ordered list of URLs. - * @param {Object} options - Options - * @param {Boolean} options.verbose - Log verbosity. |true| prints the - * individual message from each URL and AudioBuffer. - */ -function BufferList(context, bufferData, options) { - this._context = Utils.isAudioContext(context) ? - context : - Utils.throw('BufferList: Invalid BaseAudioContext.'); - - this._bufferList = []; - this._bufferData = bufferData.slice(0); - this._numberOfTasks = this._bufferData.length; - - this._options = {verbose: false}; - if (options) { - this._options = Boolean(options.verbose); - } - - this._resolveHandler = null; - this._rejectHandler = new Function(); -} - - -/** - * Starts AudioBuffer loading tasks. - * @return {Promise} The promise resolves with an array of - * AudioBuffer. - */ -BufferList.prototype.load = function() { - return new Promise(this._promiseGenerator.bind(this)); -}; - - -/** - * Promise argument generator. Internally starts multiple async loading tasks. - * @private - * @param {function} resolve Promise resolver. - * @param {function} reject Promise reject. - */ -BufferList.prototype._promiseGenerator = function(resolve, reject) { - if (typeof resolve !== 'function') { - Utils.throw('BufferList: Invalid Promise resolver.'); - } else { - this._resolveHandler = resolve; - } - - if (typeof reject === 'function') { - this._rejectHandler = reject; - } - - for (let i = 0; i < this._bufferData.length; ++i) { - this._launchAsyncLoadTask(i); - } -}; - - -/** - * Individual async loading task. - * @private - * @param {Number} taskId Task ID number from |BufferData|. - */ -BufferList.prototype._launchAsyncLoadTask = function(taskId) { - const xhr = new XMLHttpRequest(); - xhr.open('GET', this._bufferData[taskId]); - xhr.responseType = 'arraybuffer'; - - const that = this; - xhr.onload = function() { - if (xhr.status === 200) { - that._context.decodeAudioData( - xhr.response, - function(audioBuffer) { - that._updateProgress(taskId, audioBuffer); - }, - function(errorMessage) { - that._updateProgress(taskId, null); - const message = 'BufferList: decoding "' + - that._bufferData[taskId] + '" failed. (' + errorMessage + ')'; - Utils.throw(message); - that._rejectHandler(message); - }); - } else { - const message = 'BufferList: XHR error while loading "' + - that._bufferData[taskId] + '(' + xhr.statusText + ')'; - Utils.throw(message); - that._rejectHandler(message); - } - }; - - xhr.onerror = function(event) { - Utils.throw( - 'BufferList: XHR network failed on loading "' + - that._bufferData[taskId] + '".'); - that._updateProgress(taskId, null); - that._rejectHandler(); - }; - - xhr.send(); -}; - - -/** - * Updates the overall progress on loading tasks. - * @param {Number} taskId Task ID number. - * @param {AudioBuffer} audioBuffer Decoded AudioBuffer object. - */ -BufferList.prototype._updateProgress = function(taskId, audioBuffer) { - this._bufferList[taskId] = audioBuffer; - - if (this._options.verbose) { - Utils.log( - 'BufferList: "' + this._bufferData[taskId] + '" successfully' + - 'loaded.'); - } - - if (--this._numberOfTasks === 0) { - Utils.log( - 'BufferList: ' + this._bufferData.length + ' files loaded ' + - 'successfully.'); - this._resolveHandler(this._bufferList); - } -}; - - -module.exports = BufferList; - - -/***/ }), -/* 2 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/** - * Copyright 2016 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 An audio channel router to resolve different channel layouts between - * browsers. - */ - - - - -/** - * @typedef {Number[]} ChannelMap - */ - -/** - * Channel map dictionary ENUM. - * @enum {ChannelMap} - */ -const ChannelMap = { - /** @type {Number[]} - ACN channel map for Chrome and FireFox. (FFMPEG) */ - DEFAULT: [0, 1, 2, 3], - /** @type {Number[]} - Safari's 4-channel map for AAC codec. */ - SAFARI: [2, 0, 1, 3], - /** @type {Number[]} - ACN > FuMa conversion map. */ - FUMA: [0, 3, 1, 2], -}; - - -/** - * Channel router for FOA stream. - * @constructor - * @param {AudioContext} context - Associated AudioContext. - * @param {Number[]} channelMap - Routing destination array. - */ -function FOARouter(context, channelMap) { - this._context = context; - - this._splitter = this._context.createChannelSplitter(4); - this._merger = this._context.createChannelMerger(4); - - // input/output proxy. - this.input = this._splitter; - this.output = this._merger; - - this.setChannelMap(channelMap || ChannelMap.DEFAULT); -} - - -/** - * Sets channel map. - * @param {Number[]} channelMap - A new channel map for FOA stream. - */ -FOARouter.prototype.setChannelMap = function(channelMap) { - if (!Array.isArray(channelMap)) { - return; - } - - this._channelMap = channelMap; - this._splitter.disconnect(); - this._splitter.connect(this._merger, 0, this._channelMap[0]); - this._splitter.connect(this._merger, 1, this._channelMap[1]); - this._splitter.connect(this._merger, 2, this._channelMap[2]); - this._splitter.connect(this._merger, 3, this._channelMap[3]); -}; - - -/** - * Static channel map ENUM. - * @static - * @type {ChannelMap} - */ -FOARouter.ChannelMap = ChannelMap; - - -module.exports = FOARouter; - - -/***/ }), -/* 3 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/** - * Copyright 2016 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 Sound field rotator for first-order-ambisonics decoding. - */ - - - - -/** - * First-order-ambisonic decoder based on gain node network. - * @constructor - * @param {AudioContext} context - Associated AudioContext. - */ -function FOARotator(context) { - this._context = context; - - this._splitter = this._context.createChannelSplitter(4); - this._inY = this._context.createGain(); - this._inZ = this._context.createGain(); - this._inX = this._context.createGain(); - this._m0 = this._context.createGain(); - this._m1 = this._context.createGain(); - this._m2 = this._context.createGain(); - this._m3 = this._context.createGain(); - this._m4 = this._context.createGain(); - this._m5 = this._context.createGain(); - this._m6 = this._context.createGain(); - this._m7 = this._context.createGain(); - this._m8 = this._context.createGain(); - this._outY = this._context.createGain(); - this._outZ = this._context.createGain(); - this._outX = this._context.createGain(); - this._merger = this._context.createChannelMerger(4); - - // ACN channel ordering: [1, 2, 3] => [-Y, Z, -X] - // Y (from channel 1) - this._splitter.connect(this._inY, 1); - // Z (from channel 2) - this._splitter.connect(this._inZ, 2); - // X (from channel 3) - this._splitter.connect(this._inX, 3); - this._inY.gain.value = -1; - this._inX.gain.value = -1; - - // Apply the rotation in the world space. - // |Y| | m0 m3 m6 | | Y * m0 + Z * m3 + X * m6 | | Yr | - // |Z| * | m1 m4 m7 | = | Y * m1 + Z * m4 + X * m7 | = | Zr | - // |X| | m2 m5 m8 | | Y * m2 + Z * m5 + X * m8 | | Xr | - this._inY.connect(this._m0); - this._inY.connect(this._m1); - this._inY.connect(this._m2); - this._inZ.connect(this._m3); - this._inZ.connect(this._m4); - this._inZ.connect(this._m5); - this._inX.connect(this._m6); - this._inX.connect(this._m7); - this._inX.connect(this._m8); - this._m0.connect(this._outY); - this._m1.connect(this._outZ); - this._m2.connect(this._outX); - this._m3.connect(this._outY); - this._m4.connect(this._outZ); - this._m5.connect(this._outX); - this._m6.connect(this._outY); - this._m7.connect(this._outZ); - this._m8.connect(this._outX); - - // Transform 3: world space to audio space. - // W -> W (to channel 0) - this._splitter.connect(this._merger, 0, 0); - // Y (to channel 1) - this._outY.connect(this._merger, 0, 1); - // Z (to channel 2) - this._outZ.connect(this._merger, 0, 2); - // X (to channel 3) - this._outX.connect(this._merger, 0, 3); - this._outY.gain.value = -1; - this._outX.gain.value = -1; - - this.setRotationMatrix3(new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1])); - - // input/output proxy. - this.input = this._splitter; - this.output = this._merger; -} - - -/** - * Updates the rotation matrix with 3x3 matrix. - * @param {Number[]} rotationMatrix3 - A 3x3 rotation matrix. (column-major) - */ -FOARotator.prototype.setRotationMatrix3 = function(rotationMatrix3) { - this._m0.gain.value = rotationMatrix3[0]; - this._m1.gain.value = rotationMatrix3[1]; - this._m2.gain.value = rotationMatrix3[2]; - this._m3.gain.value = rotationMatrix3[3]; - this._m4.gain.value = rotationMatrix3[4]; - this._m5.gain.value = rotationMatrix3[5]; - this._m6.gain.value = rotationMatrix3[6]; - this._m7.gain.value = rotationMatrix3[7]; - this._m8.gain.value = rotationMatrix3[8]; -}; - - -/** - * Updates the rotation matrix with 4x4 matrix. - * @param {Number[]} rotationMatrix4 - A 4x4 rotation matrix. (column-major) - */ -FOARotator.prototype.setRotationMatrix4 = function(rotationMatrix4) { - this._m0.gain.value = rotationMatrix4[0]; - this._m1.gain.value = rotationMatrix4[1]; - this._m2.gain.value = rotationMatrix4[2]; - this._m3.gain.value = rotationMatrix4[4]; - this._m4.gain.value = rotationMatrix4[5]; - this._m5.gain.value = rotationMatrix4[6]; - this._m6.gain.value = rotationMatrix4[8]; - this._m7.gain.value = rotationMatrix4[9]; - this._m8.gain.value = rotationMatrix4[10]; -}; - - -/** - * Returns the current 3x3 rotation matrix. - * @return {Number[]} - A 3x3 rotation matrix. (column-major) - */ -FOARotator.prototype.getRotationMatrix3 = function() { - return [ - this._m0.gain.value, this._m1.gain.value, this._m2.gain.value, - this._m3.gain.value, this._m4.gain.value, this._m5.gain.value, - this._m6.gain.value, this._m7.gain.value, this._m8.gain.value, - ]; -}; - - -/** - * Returns the current 4x4 rotation matrix. - * @return {Number[]} - A 4x4 rotation matrix. (column-major) - */ -FOARotator.prototype.getRotationMatrix4 = function() { - let rotationMatrix4 = new Float32Array(16); - rotationMatrix4[0] = this._m0.gain.value; - rotationMatrix4[1] = this._m1.gain.value; - rotationMatrix4[2] = this._m2.gain.value; - rotationMatrix4[4] = this._m3.gain.value; - rotationMatrix4[5] = this._m4.gain.value; - rotationMatrix4[6] = this._m5.gain.value; - rotationMatrix4[8] = this._m6.gain.value; - rotationMatrix4[9] = this._m7.gain.value; - rotationMatrix4[10] = this._m8.gain.value; - return rotationMatrix4; -}; - - -module.exports = FOARotator; - - -/***/ }), -/* 4 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/** - * 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 A collection of convolvers. Can be used for the optimized FOA binaural - * rendering. (e.g. SH-MaxRe HRTFs) - */ - - - - -/** - * FOAConvolver. A collection of 2 stereo convolvers for 4-channel FOA stream. - * @constructor - * @param {BaseAudioContext} context The associated AudioContext. - * @param {AudioBuffer[]} [hrirBufferList] - An ordered-list of stereo - * AudioBuffers for convolution. (i.e. 2 stereo AudioBuffers for FOA) - */ -function FOAConvolver(context, hrirBufferList) { - this._context = context; - - this._active = false; - this._isBufferLoaded = false; - - this._buildAudioGraph(); - - if (hrirBufferList) { - this.setHRIRBufferList(hrirBufferList); - } - - this.enable(); -} - - -/** - * Build the internal audio graph. - * - * @private - */ -FOAConvolver.prototype._buildAudioGraph = function() { - this._splitterWYZX = this._context.createChannelSplitter(4); - this._mergerWY = this._context.createChannelMerger(2); - this._mergerZX = this._context.createChannelMerger(2); - this._convolverWY = this._context.createConvolver(); - this._convolverZX = this._context.createConvolver(); - this._splitterWY = this._context.createChannelSplitter(2); - this._splitterZX = this._context.createChannelSplitter(2); - this._inverter = this._context.createGain(); - this._mergerBinaural = this._context.createChannelMerger(2); - this._summingBus = this._context.createGain(); - - // Group W and Y, then Z and X. - this._splitterWYZX.connect(this._mergerWY, 0, 0); - this._splitterWYZX.connect(this._mergerWY, 1, 1); - this._splitterWYZX.connect(this._mergerZX, 2, 0); - this._splitterWYZX.connect(this._mergerZX, 3, 1); - - // Create a network of convolvers using splitter/merger. - this._mergerWY.connect(this._convolverWY); - this._mergerZX.connect(this._convolverZX); - this._convolverWY.connect(this._splitterWY); - this._convolverZX.connect(this._splitterZX); - this._splitterWY.connect(this._mergerBinaural, 0, 0); - this._splitterWY.connect(this._mergerBinaural, 0, 1); - this._splitterWY.connect(this._mergerBinaural, 1, 0); - this._splitterWY.connect(this._inverter, 1, 0); - this._inverter.connect(this._mergerBinaural, 0, 1); - this._splitterZX.connect(this._mergerBinaural, 0, 0); - this._splitterZX.connect(this._mergerBinaural, 0, 1); - this._splitterZX.connect(this._mergerBinaural, 1, 0); - this._splitterZX.connect(this._mergerBinaural, 1, 1); - - // By default, WebAudio's convolver does the normalization based on IR's - // energy. For the precise convolution, it must be disabled before the buffer - // assignment. - this._convolverWY.normalize = false; - this._convolverZX.normalize = false; - - // For asymmetric degree. - this._inverter.gain.value = -1; - - // Input/output proxy. - this.input = this._splitterWYZX; - this.output = this._summingBus; -}; - - -/** - * Assigns 2 HRIR AudioBuffers to 2 convolvers: Note that we use 2 stereo - * convolutions for 4-channel direct convolution. Using mono convolver or - * 4-channel convolver is not viable because mono convolution wastefully - * produces the stereo outputs, and the 4-ch convolver does cross-channel - * convolution. (See Web Audio API spec) - * @param {AudioBuffer[]} hrirBufferList - An array of stereo AudioBuffers for - * convolvers. - */ -FOAConvolver.prototype.setHRIRBufferList = function(hrirBufferList) { - // After these assignments, the channel data in the buffer is immutable in - // FireFox. (i.e. neutered) So we should avoid re-assigning buffers, otherwise - // an exception will be thrown. - if (this._isBufferLoaded) { - return; - } - - this._convolverWY.buffer = hrirBufferList[0]; - this._convolverZX.buffer = hrirBufferList[1]; - this._isBufferLoaded = true; -}; - - -/** - * Enable FOAConvolver instance. The audio graph will be activated and pulled by - * the WebAudio engine. (i.e. consume CPU cycle) - */ -FOAConvolver.prototype.enable = function() { - this._mergerBinaural.connect(this._summingBus); - this._active = true; -}; - - -/** - * Disable FOAConvolver instance. The inner graph will be disconnected from the - * audio destination, thus no CPU cycle will be consumed. - */ -FOAConvolver.prototype.disable = function() { - this._mergerBinaural.disconnect(); - this._active = false; -}; - - -module.exports = FOAConvolver; - - -/***/ }), -/* 5 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/** - * Copyright 2016 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. - */ - -/** - * @fileOverview DEPRECATED at V1. Audio buffer loading utility. - */ - - - -const Utils = __webpack_require__(0); - -/** - * Streamlined audio file loader supports Promise. - * @param {Object} context AudioContext - * @param {Object} audioFileData Audio file info as [{name, url}] - * @param {Function} resolve Resolution handler for promise. - * @param {Function} reject Rejection handler for promise. - * @param {Function} progress Progress event handler. - */ -function AudioBufferManager(context, audioFileData, resolve, reject, progress) { - this._context = context; - - this._buffers = new Map(); - this._loadingTasks = {}; - - this._resolve = resolve; - this._reject = reject; - this._progress = progress; - - // Iterating file loading. - for (let i = 0; i < audioFileData.length; i++) { - const fileInfo = audioFileData[i]; - - // Check for duplicates filename and quit if it happens. - if (this._loadingTasks.hasOwnProperty(fileInfo.name)) { - Utils.log('Duplicated filename when loading: ' + fileInfo.name); - return; - } - - // Mark it as pending (0) - this._loadingTasks[fileInfo.name] = 0; - this._loadAudioFile(fileInfo); - } -} - -AudioBufferManager.prototype._loadAudioFile = function(fileInfo) { - const xhr = new XMLHttpRequest(); - xhr.open('GET', fileInfo.url); - xhr.responseType = 'arraybuffer'; - - const that = this; - xhr.onload = function() { - if (xhr.status === 200) { - that._context.decodeAudioData(xhr.response, - function(buffer) { - // Utils.log('File loaded: ' + fileInfo.url); - that._done(fileInfo.name, buffer); - }, - function(message) { - Utils.log('Decoding failure: ' - + fileInfo.url + ' (' + message + ')'); - that._done(fileInfo.name, null); - }); - } else { - Utils.log('XHR Error: ' + fileInfo.url + ' (' + xhr.statusText - + ')'); - that._done(fileInfo.name, null); - } - }; - - // TODO: fetch local resources if XHR fails. - xhr.onerror = function(event) { - Utils.log('XHR Network failure: ' + fileInfo.url); - that._done(fileInfo.name, null); - }; - - xhr.send(); -}; - -AudioBufferManager.prototype._done = function(filename, buffer) { - // Label the loading task. - this._loadingTasks[filename] = buffer !== null ? 'loaded' : 'failed'; - - // A failed task will be a null buffer. - this._buffers.set(filename, buffer); - - this._updateProgress(filename); -}; - -AudioBufferManager.prototype._updateProgress = function(filename) { - let numberOfFinishedTasks = 0; - let numberOfFailedTask = 0; - let numberOfTasks = 0; - - for (const task in this._loadingTasks) { - if (Object.prototype.hasOwnProperty.call(this._loadingTasks, task)) { - numberOfTasks++; - if (this._loadingTasks[task] === 'loaded') { - numberOfFinishedTasks++; - } else if (this._loadingTasks[task] === 'failed') { - numberOfFailedTask++; - } - } - } - - if (typeof this._progress === 'function') { - this._progress(filename, numberOfFinishedTasks, numberOfTasks); - return; - } - - if (numberOfFinishedTasks === numberOfTasks) { - this._resolve(this._buffers); - return; - } - - if (numberOfFinishedTasks + numberOfFailedTask === numberOfTasks) { - this._reject(this._buffers); - return; - } -}; - -module.exports = AudioBufferManager; - - -/***/ }), -/* 6 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/** - * Copyright 2016 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 Phase matched filter for first-order-ambisonics decoding. - */ - - - -const Utils = __webpack_require__(0); - - -// Static parameters. -const CROSSOVER_FREQUENCY = 690; -const GAIN_COEFFICIENTS = [1.4142, 0.8166, 0.8166, 0.8166]; - - -/** - * Generate the coefficients for dual band filter. - * @param {Number} crossoverFrequency - * @param {Number} sampleRate - * @return {Object} Filter coefficients. - */ -function generateDualBandCoefficients(crossoverFrequency, sampleRate) { - const k = Math.tan(Math.PI * crossoverFrequency / sampleRate); - const k2 = k * k; - const denominator = k2 + 2 * k + 1; - - return { - lowpassA: [1, 2 * (k2 - 1) / denominator, (k2 - 2 * k + 1) / denominator], - lowpassB: [k2 / denominator, 2 * k2 / denominator, k2 / denominator], - hipassA: [1, 2 * (k2 - 1) / denominator, (k2 - 2 * k + 1) / denominator], - hipassB: [1 / denominator, -2 * 1 / denominator, 1 / denominator], - }; -} - - -/** - * FOAPhaseMatchedFilter: A set of filters (LP/HP) with a crossover frequency to - * compensate the gain of high frequency contents without a phase difference. - * @constructor - * @param {AudioContext} context - Associated AudioContext. - */ -function FOAPhaseMatchedFilter(context) { - this._context = context; - - this._input = this._context.createGain(); - - if (!this._context.createIIRFilter) { - Utils.log('IIR filter is missing. Using Biquad filter instead.'); - this._lpf = this._context.createBiquadFilter(); - this._hpf = this._context.createBiquadFilter(); - this._lpf.frequency.value = CROSSOVER_FREQUENCY; - this._hpf.frequency.value = CROSSOVER_FREQUENCY; - this._hpf.type = 'highpass'; - } else { - const coef = generateDualBandCoefficients(CROSSOVER_FREQUENCY, - this._context.sampleRate); - this._lpf = this._context.createIIRFilter(coef.lowpassB, coef.lowpassA); - this._hpf = this._context.createIIRFilter(coef.hipassB, coef.hipassA); - } - - this._splitterLow = this._context.createChannelSplitter(4); - this._splitterHigh = this._context.createChannelSplitter(4); - this._gainHighW = this._context.createGain(); - this._gainHighY = this._context.createGain(); - this._gainHighZ = this._context.createGain(); - this._gainHighX = this._context.createGain(); - this._merger = this._context.createChannelMerger(4); - - this._input.connect(this._hpf); - this._hpf.connect(this._splitterHigh); - this._splitterHigh.connect(this._gainHighW, 0); - this._splitterHigh.connect(this._gainHighY, 1); - this._splitterHigh.connect(this._gainHighZ, 2); - this._splitterHigh.connect(this._gainHighX, 3); - this._gainHighW.connect(this._merger, 0, 0); - this._gainHighY.connect(this._merger, 0, 1); - this._gainHighZ.connect(this._merger, 0, 2); - this._gainHighX.connect(this._merger, 0, 3); - - this._input.connect(this._lpf); - this._lpf.connect(this._splitterLow); - this._splitterLow.connect(this._merger, 0, 0); - this._splitterLow.connect(this._merger, 1, 1); - this._splitterLow.connect(this._merger, 2, 2); - this._splitterLow.connect(this._merger, 3, 3); - - // Apply gain correction to hi-passed pressure and velocity components: - // Inverting sign is necessary as the low-passed and high-passed portion are - // out-of-phase after the filtering. - const now = this._context.currentTime; - this._gainHighW.gain.setValueAtTime(-1 * GAIN_COEFFICIENTS[0], now); - this._gainHighY.gain.setValueAtTime(-1 * GAIN_COEFFICIENTS[1], now); - this._gainHighZ.gain.setValueAtTime(-1 * GAIN_COEFFICIENTS[2], now); - this._gainHighX.gain.setValueAtTime(-1 * GAIN_COEFFICIENTS[3], now); - - // Input/output Proxy. - this.input = this._input; - this.output = this._merger; -} - - -module.exports = FOAPhaseMatchedFilter; - - -/***/ }), -/* 7 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/** - * Copyright 2016 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 Virtual speaker abstraction for first-order-ambisonics decoding. - */ - - - - -/** - * DEPRECATED at V1: A virtual speaker with ambisonic decoding gain coefficients - * and HRTF convolution for first-order-ambisonics stream. Note that the - * subgraph directly connects to context's destination. - * @constructor - * @param {AudioContext} context - Associated AudioContext. - * @param {Object} options - Options for speaker. - * @param {Number[]} options.coefficients - Decoding coefficients for (W,Y,Z,X). - * @param {AudioBuffer} options.IR - Stereo IR buffer for HRTF convolution. - * @param {Number} options.gain - Post-gain for the speaker. - */ -function FOAVirtualSpeaker(context, options) { - if (options.IR.numberOfChannels !== 2) { - throw new Error('IR does not have 2 channels. cannot proceed.'); - } - - this._active = false; - this._context = context; - - this._input = this._context.createChannelSplitter(4); - this._cW = this._context.createGain(); - this._cY = this._context.createGain(); - this._cZ = this._context.createGain(); - this._cX = this._context.createGain(); - this._convolver = this._context.createConvolver(); - this._gain = this._context.createGain(); - - this._input.connect(this._cW, 0); - this._input.connect(this._cY, 1); - this._input.connect(this._cZ, 2); - this._input.connect(this._cX, 3); - this._cW.connect(this._convolver); - this._cY.connect(this._convolver); - this._cZ.connect(this._convolver); - this._cX.connect(this._convolver); - this._convolver.connect(this._gain); - this._gain.connect(this._context.destination); - - this.enable(); - - this._convolver.normalize = false; - this._convolver.buffer = options.IR; - this._gain.gain.value = options.gain; - - // Set gain coefficients for FOA ambisonic streams. - this._cW.gain.value = options.coefficients[0]; - this._cY.gain.value = options.coefficients[1]; - this._cZ.gain.value = options.coefficients[2]; - this._cX.gain.value = options.coefficients[3]; - - // Input proxy. Output directly connects to the destination. - this.input = this._input; -} - - -FOAVirtualSpeaker.prototype.enable = function() { - this._gain.connect(this._context.destination); - this._active = true; -}; - - -FOAVirtualSpeaker.prototype.disable = function() { - this._gain.disconnect(); - this._active = false; -}; - - -module.exports = FOAVirtualSpeaker; - - -/***/ }), -/* 8 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/** - * 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 Static data manager for HRIR list and URLs. For Omnitone's HRIR list - * structure, see src/resources/README.md for the detail. - */ - - - - -const HRIRList = [ - // Zero-order ambisonic. Not supported. (0 files, 1 channel) - null, - // First-order ambisonic. (2 files, 4 channels) - ['omnitone-foa-1.wav', 'omnitone-foa-2.wav'], - // Second-order ambisonic. (5 files, 9 channels) - [ - 'omnitone-soa-1.wav', 'omnitone-soa-2.wav', 'omnitone-soa-3.wav', - 'omnitone-soa-4.wav', 'omnitone-soa-5.wav', - ], - // Third-order ambisonic. (8 files, 16 channels) - [ - 'omnitone-toa-1.wav', 'omnitone-toa-2.wav', 'omnitone-toa-3.wav', - 'omnitone-toa-4.wav', 'omnitone-toa-5.wav', 'omnitone-toa-6.wav', - 'omnitone-toa-7.wav', 'omnitone-toa-8.wav', - ], -]; - - -// 2 different types of base URL. -const SourceURL = { - GSTATIC: - 'https://www.gstatic.com/external_hosted/omnitone/build/resources/', - GITHUB: - 'https://cdn.rawgit.com/GoogleChrome/omnitone/master/build/resources/', -}; - - -/** - * [getPathSet description] - * @param {object} [setting] - Setting object. - * @param {string} [setting.source="github"] - The base location for the HRIR - * set. - * @param {number} [setting.ambisonicOrder=1] - Requested ambisonic order. - * @return {string[]} pathList - HRIR path set (2~8 URLs) - */ -module.exports.getPathList = function(setting) { - let filenames; - let staticPath; - let pathList; - - const setting_ = setting || {ambisonicOrder: 1, source: 'github'}; - - switch (setting_.ambisonicOrder) { - case 1: - case 2: - case 3: - filenames = HRIRList[setting_.ambisonicOrder]; - break; - default: - // Invalid order gets the null path list. - filenames = HRIRList[0]; - break; - } - - switch (setting_.source) { - case 'gstatic': - staticPath = SourceURL.GSTATIC; - break; - case 'github': - default: - // By default, use GitHub's CDN until Gstatic setup is completed. - staticPath = SourceURL.GITHUB; - break; - } - - if (filenames) { - pathList = []; - filenames.forEach(function(filename) { - pathList.push(staticPath + filename); - }); - } - - return pathList; -}; - - -/***/ }), -/* 9 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/** - * 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 A collection of convolvers. Can be used for the optimized HOA binaural - * rendering. (e.g. SH-MaxRe HRTFs) - */ - - - - -/** - * A convolver network for N-channel HOA stream. - * @constructor - * @param {AudioContext} context - Associated AudioContext. - * @param {Number} ambisonicOrder - Ambisonic order. (2 or 3) - * @param {AudioBuffer[]} [hrirBufferList] - An ordered-list of stereo - * AudioBuffers for convolution. (SOA: 5 AudioBuffers, TOA: 8 AudioBuffers) - */ -function HOAConvolver(context, ambisonicOrder, hrirBufferList) { - this._context = context; - - this._active = false; - this._isBufferLoaded = false; - - // The number of channels K based on the ambisonic order N where K = (N+1)^2. - this._ambisonicOrder = ambisonicOrder; - this._numberOfChannels = - (this._ambisonicOrder + 1) * (this._ambisonicOrder + 1); - - this._buildAudioGraph(); - if (hrirBufferList) { - this.setHRIRBufferList(hrirBufferList); - } - - this.enable(); -} - - -/** - * Build the internal audio graph. - * For TOA convolution: - * input -> splitter(16) -[0,1]-> merger(2) -> convolver(2) -> splitter(2) - * -[2,3]-> merger(2) -> convolver(2) -> splitter(2) - * -[4,5]-> ... (6 more, 8 branches total) - * @private - */ -HOAConvolver.prototype._buildAudioGraph = function() { - const numberOfStereoChannels = Math.ceil(this._numberOfChannels / 2); - - this._inputSplitter = - this._context.createChannelSplitter(this._numberOfChannels); - this._stereoMergers = []; - this._convolvers = []; - this._stereoSplitters = []; - this._positiveIndexSphericalHarmonics = this._context.createGain(); - this._negativeIndexSphericalHarmonics = this._context.createGain(); - this._inverter = this._context.createGain(); - this._binauralMerger = this._context.createChannelMerger(2); - this._outputGain = this._context.createGain(); - - for (let i = 0; i < numberOfStereoChannels; ++i) { - this._stereoMergers[i] = this._context.createChannelMerger(2); - this._convolvers[i] = this._context.createConvolver(); - this._stereoSplitters[i] = this._context.createChannelSplitter(2); - this._convolvers[i].normalize = false; - } - - for (let l = 0; l <= this._ambisonicOrder; ++l) { - for (let m = -l; m <= l; m++) { - // We compute the ACN index (k) of ambisonics channel using the degree (l) - // and index (m): k = l^2 + l + m - const acnIndex = l * l + l + m; - const stereoIndex = Math.floor(acnIndex / 2); - - // Split channels from input into array of stereo convolvers. - // Then create a network of mergers that produces the stereo output. - this._inputSplitter.connect( - this._stereoMergers[stereoIndex], acnIndex, acnIndex % 2); - this._stereoMergers[stereoIndex].connect(this._convolvers[stereoIndex]); - this._convolvers[stereoIndex].connect(this._stereoSplitters[stereoIndex]); - - // Positive index (m >= 0) spherical harmonics are symmetrical around the - // front axis, while negative index (m < 0) spherical harmonics are - // anti-symmetrical around the front axis. We will exploit this symmetry - // to reduce the number of convolutions required when rendering to a - // symmetrical binaural renderer. - if (m >= 0) { - this._stereoSplitters[stereoIndex].connect( - this._positiveIndexSphericalHarmonics, acnIndex % 2); - } else { - this._stereoSplitters[stereoIndex].connect( - this._negativeIndexSphericalHarmonics, acnIndex % 2); - } - } - } - - this._positiveIndexSphericalHarmonics.connect(this._binauralMerger, 0, 0); - this._positiveIndexSphericalHarmonics.connect(this._binauralMerger, 0, 1); - this._negativeIndexSphericalHarmonics.connect(this._binauralMerger, 0, 0); - this._negativeIndexSphericalHarmonics.connect(this._inverter); - this._inverter.connect(this._binauralMerger, 0, 1); - - // For asymmetric index. - this._inverter.gain.value = -1; - - // Input/Output proxy. - this.input = this._inputSplitter; - this.output = this._outputGain; -}; - - -/** - * Assigns N HRIR AudioBuffers to N convolvers: Note that we use 2 stereo - * convolutions for 4-channel direct convolution. Using mono convolver or - * 4-channel convolver is not viable because mono convolution wastefully - * produces the stereo outputs, and the 4-ch convolver does cross-channel - * convolution. (See Web Audio API spec) - * @param {AudioBuffer[]} hrirBufferList - An array of stereo AudioBuffers for - * convolvers. - */ -HOAConvolver.prototype.setHRIRBufferList = function(hrirBufferList) { - // After these assignments, the channel data in the buffer is immutable in - // FireFox. (i.e. neutered) So we should avoid re-assigning buffers, otherwise - // an exception will be thrown. - if (this._isBufferLoaded) { - return; - } - - for (let i = 0; i < hrirBufferList.length; ++i) { - this._convolvers[i].buffer = hrirBufferList[i]; - } - - this._isBufferLoaded = true; -}; - - -/** - * Enable HOAConvolver instance. The audio graph will be activated and pulled by - * the WebAudio engine. (i.e. consume CPU cycle) - */ -HOAConvolver.prototype.enable = function() { - this._binauralMerger.connect(this._outputGain); - this._active = true; -}; - - -/** - * Disable HOAConvolver instance. The inner graph will be disconnected from the - * audio destination, thus no CPU cycle will be consumed. - */ -HOAConvolver.prototype.disable = function() { - this._binauralMerger.disconnect(); - this._active = false; -}; - - -module.exports = HOAConvolver; - - -/***/ }), -/* 10 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/** - * Copyright 2016 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 Sound field rotator for higher-order-ambisonics decoding. - */ - - - - -/** - * Kronecker Delta function. - * @param {Number} i - * @param {Number} j - * @return {Number} - */ -function getKroneckerDelta(i, j) { - return i === j ? 1 : 0; -} - - -/** - * A helper function to allow us to access a matrix array in the same - * manner, assuming it is a (2l+1)x(2l+1) matrix. [2] uses an odd convention of - * referring to the rows and columns using centered indices, so the middle row - * and column are (0, 0) and the upper left would have negative coordinates. - * @param {Number[]} matrix - N matrices of gainNodes, each with (2n+1) x (2n+1) - * elements, where n=1,2,...,N. - * @param {Number} l - * @param {Number} i - * @param {Number} j - * @param {Number} gainValue - */ -function setCenteredElement(matrix, l, i, j, gainValue) { - const index = (j + l) * (2 * l + 1) + (i + l); - // Row-wise indexing. - matrix[l - 1][index].gain.value = gainValue; -} - - -/** - * This is a helper function to allow us to access a matrix array in the same - * manner, assuming it is a (2l+1) x (2l+1) matrix. - * @param {Number[]} matrix - N matrices of gainNodes, each with (2n+1) x (2n+1) - * elements, where n=1,2,...,N. - * @param {Number} l - * @param {Number} i - * @param {Number} j - * @return {Number} - */ -function getCenteredElement(matrix, l, i, j) { - // Row-wise indexing. - const index = (j + l) * (2 * l + 1) + (i + l); - return matrix[l - 1][index].gain.value; -} - - -/** - * Helper function defined in [2] that is used by the functions U, V, W. - * This should not be called on its own, as U, V, and W (and their coefficients) - * select the appropriate matrix elements to access arguments |a| and |b|. - * @param {Number[]} matrix - N matrices of gainNodes, each with (2n+1) x (2n+1) - * elements, where n=1,2,...,N. - * @param {Number} i - * @param {Number} a - * @param {Number} b - * @param {Number} l - * @return {Number} - */ -function getP(matrix, i, a, b, l) { - if (b === l) { - return getCenteredElement(matrix, 1, i, 1) * - getCenteredElement(matrix, l - 1, a, l - 1) - - getCenteredElement(matrix, 1, i, -1) * - getCenteredElement(matrix, l - 1, a, -l + 1); - } else if (b === -l) { - return getCenteredElement(matrix, 1, i, 1) * - getCenteredElement(matrix, l - 1, a, -l + 1) + - getCenteredElement(matrix, 1, i, -1) * - getCenteredElement(matrix, l - 1, a, l - 1); - } else { - return getCenteredElement(matrix, 1, i, 0) * - getCenteredElement(matrix, l - 1, a, b); - } -} - - -/** - * The functions U, V, and W should only be called if the correspondingly - * named coefficient u, v, w from the function ComputeUVWCoeff() is non-zero. - * When the coefficient is 0, these would attempt to access matrix elements that - * are out of bounds. The vector of rotations, |r|, must have the |l - 1| - * previously completed band rotations. These functions are valid for |l >= 2|. - * @param {Number[]} matrix - N matrices of gainNodes, each with (2n+1) x (2n+1) - * elements, where n=1,2,...,N. - * @param {Number} m - * @param {Number} n - * @param {Number} l - * @return {Number} - */ -function getU(matrix, m, n, l) { - // Although [1, 2] split U into three cases for m == 0, m < 0, m > 0 - // the actual values are the same for all three cases. - return getP(matrix, 0, m, n, l); -} - - -/** - * The functions U, V, and W should only be called if the correspondingly - * named coefficient u, v, w from the function ComputeUVWCoeff() is non-zero. - * When the coefficient is 0, these would attempt to access matrix elements that - * are out of bounds. The vector of rotations, |r|, must have the |l - 1| - * previously completed band rotations. These functions are valid for |l >= 2|. - * @param {Number[]} matrix - N matrices of gainNodes, each with (2n+1) x (2n+1) - * elements, where n=1,2,...,N. - * @param {Number} m - * @param {Number} n - * @param {Number} l - * @return {Number} - */ -function getV(matrix, m, n, l) { - if (m === 0) { - return getP(matrix, 1, 1, n, l) + getP(matrix, -1, -1, n, l); - } else if (m > 0) { - const d = getKroneckerDelta(m, 1); - return getP(matrix, 1, m - 1, n, l) * Math.sqrt(1 + d) - - getP(matrix, -1, -m + 1, n, l) * (1 - d); - } else { - // Note there is apparent errata in [1,2,2b] dealing with this particular - // case. [2b] writes it should be P*(1-d)+P*(1-d)^0.5 - // [1] writes it as P*(1+d)+P*(1-d)^0.5, but going through the math by hand, - // you must have it as P*(1-d)+P*(1+d)^0.5 to form a 2^.5 term, which - // parallels the case where m > 0. - const d = getKroneckerDelta(m, -1); - return getP(matrix, 1, m + 1, n, l) * (1 - d) + - getP(matrix, -1, -m - 1, n, l) * Math.sqrt(1 + d); - } -} - - -/** - * The functions U, V, and W should only be called if the correspondingly - * named coefficient u, v, w from the function ComputeUVWCoeff() is non-zero. - * When the coefficient is 0, these would attempt to access matrix elements that - * are out of bounds. The vector of rotations, |r|, must have the |l - 1| - * previously completed band rotations. These functions are valid for |l >= 2|. - * @param {Number[]} matrix N matrices of gainNodes, each with (2n+1) x (2n+1) - * elements, where n=1,2,...,N. - * @param {Number} m - * @param {Number} n - * @param {Number} l - * @return {Number} - */ -function getW(matrix, m, n, l) { - // Whenever this happens, w is also 0 so W can be anything. - if (m === 0) { - return 0; - } - - return m > 0 ? getP(matrix, 1, m + 1, n, l) + getP(matrix, -1, -m - 1, n, l) : - getP(matrix, 1, m - 1, n, l) - getP(matrix, -1, -m + 1, n, l); -} - - -/** - * Calculates the coefficients applied to the U, V, and W functions. Because - * their equations share many common terms they are computed simultaneously. - * @param {Number} m - * @param {Number} n - * @param {Number} l - * @return {Array} 3 coefficients for U, V and W functions. - */ -function computeUVWCoeff(m, n, l) { - const d = getKroneckerDelta(m, 0); - const reciprocalDenominator = - Math.abs(n) === l ? 1 / (2 * l * (2 * l - 1)) : 1 / ((l + n) * (l - n)); - - return [ - Math.sqrt((l + m) * (l - m) * reciprocalDenominator), - 0.5 * (1 - 2 * d) * Math.sqrt((1 + d) * - (l + Math.abs(m) - 1) * - (l + Math.abs(m)) * - reciprocalDenominator), - -0.5 * (1 - d) * Math.sqrt((l - Math.abs(m) - 1) * (l - Math.abs(m))) * - reciprocalDenominator, - ]; -} - - -/** - * Calculates the (2l+1) x (2l+1) rotation matrix for the band l. - * This uses the matrices computed for band 1 and band l-1 to compute the - * matrix for band l. |rotations| must contain the previously computed l-1 - * rotation matrices. - * This implementation comes from p. 5 (6346), Table 1 and 2 in [2] taking - * into account the corrections from [2b]. - * @param {Number[]} matrix - N matrices of gainNodes, each with where - * n=1,2,...,N. - * @param {Number} l - */ -function computeBandRotation(matrix, l) { - // The lth band rotation matrix has rows and columns equal to the number of - // coefficients within that band (-l <= m <= l implies 2l + 1 coefficients). - for (let m = -l; m <= l; m++) { - for (let n = -l; n <= l; n++) { - const uvwCoefficients = computeUVWCoeff(m, n, l); - - // The functions U, V, W are only safe to call if the coefficients - // u, v, w are not zero. - if (Math.abs(uvwCoefficients[0]) > 0) { - uvwCoefficients[0] *= getU(matrix, m, n, l); - } - if (Math.abs(uvwCoefficients[1]) > 0) { - uvwCoefficients[1] *= getV(matrix, m, n, l); - } - if (Math.abs(uvwCoefficients[2]) > 0) { - uvwCoefficients[2] *= getW(matrix, m, n, l); - } - - setCenteredElement( - matrix, l, m, n, - uvwCoefficients[0] + uvwCoefficients[1] + uvwCoefficients[2]); - } - } -} - - -/** - * Compute the HOA rotation matrix after setting the transform matrix. - * @param {Array} matrix - N matrices of gainNodes, each with (2n+1) x (2n+1) - * elements, where n=1,2,...,N. - */ -function computeHOAMatrices(matrix) { - // We start by computing the 2nd-order matrix from the 1st-order matrix. - for (let i = 2; i <= matrix.length; i++) { - computeBandRotation(matrix, i); - } -} - - -/** - * Higher-order-ambisonic decoder based on gain node network. We expect - * the order of the channels to conform to ACN ordering. Below are the helper - * methods to compute SH rotation using recursion. The code uses maths described - * in the following papers: - * [1] R. Green, "Spherical Harmonic Lighting: The Gritty Details", GDC 2003, - * http://www.research.scea.com/gdc2003/spherical-harmonic-lighting.pdf - * [2] J. Ivanic and K. Ruedenberg, "Rotation Matrices for Real - * Spherical Harmonics. Direct Determination by Recursion", J. Phys. - * Chem., vol. 100, no. 15, pp. 6342-6347, 1996. - * http://pubs.acs.org/doi/pdf/10.1021/jp953350u - * [2b] Corrections to initial publication: - * http://pubs.acs.org/doi/pdf/10.1021/jp9833350 - * @constructor - * @param {AudioContext} context - Associated AudioContext. - * @param {Number} ambisonicOrder - Ambisonic order. - */ -function HOARotator(context, ambisonicOrder) { - this._context = context; - this._ambisonicOrder = ambisonicOrder; - - // We need to determine the number of channels K based on the ambisonic order - // N where K = (N + 1)^2. - const numberOfChannels = (ambisonicOrder + 1) * (ambisonicOrder + 1); - - this._splitter = this._context.createChannelSplitter(numberOfChannels); - this._merger = this._context.createChannelMerger(numberOfChannels); - - // Create a set of per-order rotation matrices using gain nodes. - this._gainNodeMatrix = []; - let orderOffset; - let rows; - let inputIndex; - let outputIndex; - let matrixIndex; - for (let i = 1; i <= ambisonicOrder; i++) { - // Each ambisonic order requires a separate (2l + 1) x (2l + 1) rotation - // matrix. We compute the offset value as the first channel index of the - // current order where - // k_last = l^2 + l + m, - // and m = -l - // k_last = l^2 - orderOffset = i * i; - - // Uses row-major indexing. - rows = (2 * i + 1); - - this._gainNodeMatrix[i - 1] = []; - for (let j = 0; j < rows; j++) { - inputIndex = orderOffset + j; - for (let k = 0; k < rows; k++) { - outputIndex = orderOffset + k; - matrixIndex = j * rows + k; - this._gainNodeMatrix[i - 1][matrixIndex] = this._context.createGain(); - this._splitter.connect( - this._gainNodeMatrix[i - 1][matrixIndex], inputIndex); - this._gainNodeMatrix[i - 1][matrixIndex].connect( - this._merger, 0, outputIndex); - } - } - } - - // W-channel is not involved in rotation, skip straight to ouput. - this._splitter.connect(this._merger, 0, 0); - - // Default Identity matrix. - this.setRotationMatrix3(new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1])); - - // Input/Output proxy. - this.input = this._splitter; - this.output = this._merger; -} - - -/** - * Updates the rotation matrix with 3x3 matrix. - * @param {Number[]} rotationMatrix3 - A 3x3 rotation matrix. (column-major) - */ -HOARotator.prototype.setRotationMatrix3 = function(rotationMatrix3) { - for (let i = 0; i < 9; ++i) { - this._gainNodeMatrix[0][i].gain.value = rotationMatrix3[i]; - } - computeHOAMatrices(this._gainNodeMatrix); -}; - - -/** - * Updates the rotation matrix with 4x4 matrix. - * @param {Number[]} rotationMatrix4 - A 4x4 rotation matrix. (column-major) - */ -HOARotator.prototype.setRotationMatrix4 = function(rotationMatrix4) { - this._gainNodeMatrix[0][0].gain.value = rotationMatrix4[0]; - this._gainNodeMatrix[0][1].gain.value = rotationMatrix4[1]; - this._gainNodeMatrix[0][2].gain.value = rotationMatrix4[2]; - this._gainNodeMatrix[0][3].gain.value = rotationMatrix4[4]; - this._gainNodeMatrix[0][4].gain.value = rotationMatrix4[5]; - this._gainNodeMatrix[0][5].gain.value = rotationMatrix4[6]; - this._gainNodeMatrix[0][6].gain.value = rotationMatrix4[8]; - this._gainNodeMatrix[0][7].gain.value = rotationMatrix4[9]; - this._gainNodeMatrix[0][8].gain.value = rotationMatrix4[10]; - computeHOAMatrices(this._gainNodeMatrix); -}; - - -/** - * Returns the current 3x3 rotation matrix. - * @return {Number[]} - A 3x3 rotation matrix. (column-major) - */ -HOARotator.prototype.getRotationMatrix3 = function() { - let rotationMatrix3 = new Float32Array(9); - for (let i = 0; i < 9; ++i) { - rotationMatrix3[i] = this._gainNodeMatrix[0][i].gain.value; - } - return rotationMatrix3; -}; - - -/** - * Returns the current 4x4 rotation matrix. - * @return {Number[]} - A 4x4 rotation matrix. (column-major) - */ -HOARotator.prototype.getRotationMatrix4 = function() { - let rotationMatrix4 = new Float32Array(16); - rotationMatrix4[0] = this._gainNodeMatrix[0][0].gain.value; - rotationMatrix4[1] = this._gainNodeMatrix[0][1].gain.value; - rotationMatrix4[2] = this._gainNodeMatrix[0][2].gain.value; - rotationMatrix4[4] = this._gainNodeMatrix[0][3].gain.value; - rotationMatrix4[5] = this._gainNodeMatrix[0][4].gain.value; - rotationMatrix4[6] = this._gainNodeMatrix[0][5].gain.value; - rotationMatrix4[8] = this._gainNodeMatrix[0][6].gain.value; - rotationMatrix4[9] = this._gainNodeMatrix[0][7].gain.value; - rotationMatrix4[10] = this._gainNodeMatrix[0][8].gain.value; - return rotationMatrix4; -}; - - -/** - * Get the current ambisonic order. - * @return {Number} - */ -HOARotator.prototype.getAmbisonicOrder = function() { - return this._ambisonicOrder; -}; - - -module.exports = HOARotator; - - -/***/ }), -/* 11 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/** - * @license - * Copyright 2016 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 Namespace for Omnitone library. - */ - - - - -exports.Omnitone = __webpack_require__(12); - - -/***/ }), -/* 12 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/** - * Copyright 2016 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 Omnitone library name space and user-facing APIs. - */ - - - - -const BufferList = __webpack_require__(1); -const FOAConvolver = __webpack_require__(4); -const FOADecoder = __webpack_require__(13); -const FOAPhaseMatchedFilter = __webpack_require__(6); -const FOARenderer = __webpack_require__(15); -const FOARotator = __webpack_require__(3); -const FOARouter = __webpack_require__(2); -const FOAVirtualSpeaker = __webpack_require__(7); -const HOAConvolver = __webpack_require__(9); -const HOARenderer = __webpack_require__(16); -const HOARotator = __webpack_require__(10); -const Polyfill = __webpack_require__(17); -const Utils = __webpack_require__(0); -const Version = __webpack_require__(18); - -// DEPRECATED in V1, in favor of BufferList. -const AudioBufferManager = __webpack_require__(5); - - -/** - * Omnitone namespace. - * @namespace - */ -let Omnitone = {}; - - -/** - * @typedef {Object} BrowserInfo - * @property {string} name - Browser name. - * @property {string} version - Browser version. - */ - -/** - * An object contains the detected browser name and version. - * @memberOf Omnitone - * @static {BrowserInfo} - */ -Omnitone.browserInfo = Polyfill.getBrowserInfo(); - - -// DEPRECATED in V1. DO. NOT. USE. -Omnitone.loadAudioBuffers = function(context, speakerData) { - return new Promise(function(resolve, reject) { - new AudioBufferManager(context, speakerData, function(buffers) { - resolve(buffers); - }, reject); - }); -}; - - -/** - * Performs the async loading/decoding of multiple AudioBuffers from multiple - * URLs. - * @param {BaseAudioContext} context - Associated BaseAudioContext. - * @param {string[]} bufferData - An ordered list of URLs. - * @return {Promise} - The promise resolves with an array of - * AudioBuffer. - */ -Omnitone.createBufferList = function(context, bufferData) { - const bufferList = new BufferList(context, bufferData); - return bufferList.load(); -}; - - -/** - * Perform channel-wise merge on multiple AudioBuffers. The sample rate and - * the length of buffers to be merged must be identical. - * @static - * @function - * @param {BaseAudioContext} context - Associated BaseAudioContext. - * @param {AudioBuffer[]} bufferList - An array of AudioBuffers to be merged - * channel-wise. - * @return {AudioBuffer} - A single merged AudioBuffer. - */ -Omnitone.mergeBufferListByChannel = Utils.mergeBufferListByChannel; - - -/** - * Perform channel-wise split by the given channel count. For example, - * 1 x AudioBuffer(8) -> splitBuffer(context, buffer, 2) -> 4 x AudioBuffer(2). - * @static - * @function - * @param {BaseAudioContext} context - Associated BaseAudioContext. - * @param {AudioBuffer} audioBuffer - An AudioBuffer to be splitted. - * @param {Number} splitBy - Number of channels to be splitted. - * @return {AudioBuffer[]} - An array of splitted AudioBuffers. - */ -Omnitone.splitBufferbyChannel = Utils.splitBufferbyChannel; - - -/** - * Creates an instance of FOA Convolver. - * @see FOAConvolver - * @param {BaseAudioContext} context The associated AudioContext. - * @param {AudioBuffer[]} [hrirBufferList] - An ordered-list of stereo - * @return {FOAConvolver} - */ -Omnitone.createFOAConvolver = function(context, hrirBufferList) { - return new FOAConvolver(context, hrirBufferList); -}; - - -/** - * Create an instance of FOA Router. - * @see FOARouter - * @param {AudioContext} context - Associated AudioContext. - * @param {Number[]} channelMap - Routing destination array. - * @return {FOARouter} - */ -Omnitone.createFOARouter = function(context, channelMap) { - return new FOARouter(context, channelMap); -}; - - -/** - * Create an instance of FOA Rotator. - * @see FOARotator - * @param {AudioContext} context - Associated AudioContext. - * @return {FOARotator} - */ -Omnitone.createFOARotator = function(context) { - return new FOARotator(context); -}; - - -/** - * Create an instance of FOAPhaseMatchedFilter. - * @ignore - * @see FOAPhaseMatchedFilter - * @param {AudioContext} context - Associated AudioContext. - * @return {FOAPhaseMatchedFilter} - */ -Omnitone.createFOAPhaseMatchedFilter = function(context) { - return new FOAPhaseMatchedFilter(context); -}; - - -/** - * Create an instance of FOAVirtualSpeaker. For parameters, refer the - * definition of VirtualSpeaker class. - * @ignore - * @param {AudioContext} context - Associated AudioContext. - * @param {Object} options - Options. - * @return {FOAVirtualSpeaker} - */ -Omnitone.createFOAVirtualSpeaker = function(context, options) { - return new FOAVirtualSpeaker(context, options); -}; - - -/** - * DEPRECATED. Use FOARenderer instance. - * @see FOARenderer - * @param {AudioContext} context - Associated AudioContext. - * @param {DOMElement} videoElement - Video or Audio DOM element to be streamed. - * @param {Object} options - Options for FOA decoder. - * @param {String} options.baseResourceUrl - Base URL for resources. - * (base path for HRIR files) - * @param {Number} [options.postGain=26.0] - Post-decoding gain compensation. - * @param {Array} [options.routingDestination] Custom channel layout. - * @return {FOADecoder} - */ -Omnitone.createFOADecoder = function(context, videoElement, options) { - Utils.log('WARNING: FOADecoder is deprecated in favor of FOARenderer.'); - return new FOADecoder(context, videoElement, options); -}; - - -/** - * Create a FOARenderer, the first-order ambisonic decoder and the optimized - * binaural renderer. - * @param {AudioContext} context - Associated AudioContext. - * @param {Object} config - * @param {Array} [config.channelMap] - Custom channel routing map. Useful for - * handling the inconsistency in browser's multichannel audio decoding. - * @param {Array} [config.hrirPathList] - A list of paths to HRIR files. It - * overrides the internal HRIR list if given. - * @param {RenderingMode} [config.renderingMode='ambisonic'] - Rendering mode. - * @return {FOARenderer} - */ -Omnitone.createFOARenderer = function(context, config) { - return new FOARenderer(context, config); -}; - - -/** - * Creates HOARotator for higher-order ambisonics rotation. - * @param {AudioContext} context - Associated AudioContext. - * @param {Number} ambisonicOrder - Ambisonic order. - * @return {HOARotator} - */ -Omnitone.createHOARotator = function(context, ambisonicOrder) { - return new HOARotator(context, ambisonicOrder); -}; - - -/** - * Creates HOAConvolver performs the multi-channel convolution for the optmized - * binaural rendering. - * @param {AudioContext} context - Associated AudioContext. - * @param {Number} ambisonicOrder - Ambisonic order. (2 or 3) - * @param {AudioBuffer[]} [hrirBufferList] - An ordered-list of stereo - * AudioBuffers for convolution. (SOA: 5 AudioBuffers, TOA: 8 AudioBuffers) - * @return {HOAConvovler} - */ -Omnitone.createHOAConvolver = function( - context, ambisonicOrder, hrirBufferList) { - return new HOAConvolver(context, ambisonicOrder, hrirBufferList); -}; - - -/** - * Creates HOARenderer for higher-order ambisonic decoding and the optimized - * binaural rendering. - * @param {AudioContext} context - Associated AudioContext. - * @param {Object} config - * @param {Number} [config.ambisonicOrder=3] - Ambisonic order. - * @param {Array} [config.hrirPathList] - A list of paths to HRIR files. It - * overrides the internal HRIR list if given. - * @param {RenderingMode} [config.renderingMode='ambisonic'] - Rendering mode. - * @return {HOARenderer} - */ -Omnitone.createHOARenderer = function(context, config) { - return new HOARenderer(context, config); -}; - - -// Handler Preload Tasks. -// - Detects the browser information. -// - Prints out the version number. -(function() { - Utils.log('Version ' + Version + ' (running ' + - Omnitone.browserInfo.name + ' ' + Omnitone.browserInfo.version + - ' on ' + Omnitone.browserInfo.platform +')'); - if (Omnitone.browserInfo.name.toLowerCase() === 'safari') { - Polyfill.patchSafari(); - Utils.log(Omnitone.browserInfo.name + ' detected. Appliying polyfill...'); - } -})(); - - -module.exports = Omnitone; - - -/***/ }), -/* 13 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/** - * Copyright 2016 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 Omnitone FOA decoder, DEPRECATED in favor of FOARenderer. - */ - - - -const AudioBufferManager = __webpack_require__(5); -const FOARouter = __webpack_require__(2); -const FOARotator = __webpack_require__(3); -const FOAPhaseMatchedFilter = __webpack_require__(6); -const FOAVirtualSpeaker = __webpack_require__(7); -const FOASpeakerData = __webpack_require__(14); -const Utils = __webpack_require__(0); - -// By default, Omnitone fetches IR from the spatial media repository. -const HRTFSET_URL = 'https://raw.githubusercontent.com/GoogleChrome/omnitone/master/build/resources/'; - -// Post gain compensation value. -let POST_GAIN_DB = 0; - - -/** - * Omnitone FOA decoder. - * @constructor - * @param {AudioContext} context - Associated AudioContext. - * @param {VideoElement} videoElement - Target video (or audio) element for - * streaming. - * @param {Object} options - * @param {String} options.HRTFSetUrl - Base URL for the cube HRTF sets. - * @param {Number} options.postGainDB - Post-decoding gain compensation in dB. - * @param {Number[]} options.channelMap - Custom channel map. - */ -function FOADecoder(context, videoElement, options) { - this._isDecoderReady = false; - this._context = context; - this._videoElement = videoElement; - this._decodingMode = 'ambisonic'; - - this._postGainDB = POST_GAIN_DB; - this._HRTFSetUrl = HRTFSET_URL; - this._channelMap = FOARouter.ChannelMap.DEFAULT; // ACN - - if (options) { - if (options.postGainDB) { - this._postGainDB = options.postGainDB; - } - if (options.HRTFSetUrl) { - this._HRTFSetUrl = options.HRTFSetUrl; - } - if (options.channelMap) { - this._channelMap = options.channelMap; - } - } - - // Rearrange speaker data based on |options.HRTFSetUrl|. - this._speakerData = []; - for (let i = 0; i < FOASpeakerData.length; ++i) { - this._speakerData.push({ - name: FOASpeakerData[i].name, - url: this._HRTFSetUrl + '/' + FOASpeakerData[i].url, - coef: FOASpeakerData[i].coef, - }); - } - - this._tempMatrix4 = new Float32Array(16); -} - - -/** - * Initialize and load the resources for the decode. - * @return {Promise} - */ -FOADecoder.prototype.initialize = function() { - Utils.log('Initializing... (mode: ' + this._decodingMode + ')'); - - // Rerouting channels if necessary. - let channelMapString = this._channelMap.toString(); - let defaultChannelMapString = FOARouter.ChannelMap.DEFAULT.toString(); - if (channelMapString !== defaultChannelMapString) { - Utils.log('Remapping channels ([' + defaultChannelMapString + '] -> [' - + channelMapString + '])'); - } - - this._audioElementSource = - this._context.createMediaElementSource(this._videoElement); - this._foaRouter = new FOARouter(this._context, this._channelMap); - this._foaRotator = new FOARotator(this._context); - this._foaPhaseMatchedFilter = new FOAPhaseMatchedFilter(this._context); - - this._audioElementSource.connect(this._foaRouter.input); - this._foaRouter.output.connect(this._foaRotator.input); - this._foaRotator.output.connect(this._foaPhaseMatchedFilter.input); - - this._foaVirtualSpeakers = []; - - // Bypass signal path. - this._bypass = this._context.createGain(); - this._audioElementSource.connect(this._bypass); - - // Get the linear amplitude from the post gain option, which is in decibel. - const postGainLinear = Math.pow(10, this._postGainDB/20); - Utils.log('Gain compensation: ' + postGainLinear + ' (' + this._postGainDB - + 'dB)'); - - // This returns a promise so developers can use the decoder when it is ready. - const that = this; - return new Promise(function(resolve, reject) { - new AudioBufferManager(that._context, that._speakerData, - function(buffers) { - for (let i = 0; i < that._speakerData.length; ++i) { - that._foaVirtualSpeakers[i] = new FOAVirtualSpeaker(that._context, { - coefficients: that._speakerData[i].coef, - IR: buffers.get(that._speakerData[i].name), - gain: postGainLinear, - }); - - that._foaPhaseMatchedFilter.output.connect( - that._foaVirtualSpeakers[i].input); - } - - // Set the decoding mode. - that.setMode(that._decodingMode); - that._isDecoderReady = true; - Utils.log('HRTF IRs are loaded successfully. The decoder is ready.'); - resolve(); - }, reject); - }); -}; - -/** - * Set the rotation matrix for the sound field rotation. - * @param {Array} rotationMatrix 3x3 rotation matrix (row-major - * representation) - */ -FOADecoder.prototype.setRotationMatrix = function(rotationMatrix) { - this._foaRotator.setRotationMatrix(rotationMatrix); -}; - - -/** - * Update the rotation matrix from a Three.js camera object. - * @param {Object} cameraMatrix The Matrix4 obejct of Three.js the camera. - */ -FOADecoder.prototype.setRotationMatrixFromCamera = function(cameraMatrix) { - // Extract the inner array elements and inverse. (The actual view rotation is - // the opposite of the camera movement.) - Utils.invertMatrix4(this._tempMatrix4, cameraMatrix.elements); - this._foaRotator.setRotationMatrix4(this._tempMatrix4); -}; - -/** - * Set the decoding mode. - * @param {String} mode Decoding mode. When the mode is 'bypass' - * the decoder is disabled and bypass the - * input stream to the output. Setting the - * mode to 'ambisonic' activates the decoder. - * When the mode is 'off', all the - * processing is completely turned off saving - * the CPU power. - */ -FOADecoder.prototype.setMode = function(mode) { - if (mode === this._decodingMode) { - return; - } - - switch (mode) { - case 'bypass': - this._decodingMode = 'bypass'; - for (let i = 0; i < this._foaVirtualSpeakers.length; ++i) { - this._foaVirtualSpeakers[i].disable(); - } - this._bypass.connect(this._context.destination); - break; - - case 'ambisonic': - this._decodingMode = 'ambisonic'; - for (let i = 0; i < this._foaVirtualSpeakers.length; ++i) { - this._foaVirtualSpeakers[i].enable(); - } - this._bypass.disconnect(); - break; - - case 'off': - this._decodingMode = 'off'; - for (let i = 0; i < this._foaVirtualSpeakers.length; ++i) { - this._foaVirtualSpeakers[i].disable(); - } - this._bypass.disconnect(); - break; - - default: - break; - } - - Utils.log('Decoding mode changed. (' + mode + ')'); -}; - -module.exports = FOADecoder; - - -/***/ }), -/* 14 */ -/***/ (function(module, exports) { - /** + * @license * Copyright 2016 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. @@ -2504,706 +14,117 @@ module.exports = FOADecoder; * limitations under the License. */ - -/** - * The data for FOAVirtualSpeaker. Each entry contains the URL for IR files and - * the gain coefficients for the associated IR files. Note that the order of - * coefficients follows the ACN channel ordering. (W,Y,Z,X) - * @type {Object[]} - */ -const FOASpeakerData = [{ - name: 'E35_A135', - url: 'E35_A135.wav', - gainFactor: 1, - coef: [.1250, 0.216495, 0.21653, -0.216495], -}, { - name: 'E35_A-135', - url: 'E35_A-135.wav', - gainFactor: 1, - coef: [.1250, -0.216495, 0.21653, -0.216495], -}, { - name: 'E-35_A135', - url: 'E-35_A135.wav', - gainFactor: 1, - coef: [.1250, 0.216495, -0.21653, -0.216495], -}, { - name: 'E-35_A-135', - url: 'E-35_A-135.wav', - gainFactor: 1, - coef: [.1250, -0.216495, -0.21653, -0.216495], -}, { - name: 'E35_A45', - url: 'E35_A45.wav', - gainFactor: 1, - coef: [.1250, 0.216495, 0.21653, 0.216495], -}, { - name: 'E35_A-45', - url: 'E35_A-45.wav', - gainFactor: 1, - coef: [.1250, -0.216495, 0.21653, 0.216495], -}, { - name: 'E-35_A45', - url: 'E-35_A45.wav', - gainFactor: 1, - coef: [.1250, 0.216495, -0.21653, 0.216495], -}, { - name: 'E-35_A-45', - url: 'E-35_A-45.wav', - gainFactor: 1, - coef: [.1250, -0.216495, -0.21653, 0.216495], -}]; - - -module.exports = FOASpeakerData; - - -/***/ }), -/* 15 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/** - * 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 Omnitone FOARenderer. This is user-facing API for the first-order - * ambisonic decoder and the optimized binaural renderer. - */ - - - -const BufferList = __webpack_require__(1); -const FOAConvolver = __webpack_require__(4); -const FOARotator = __webpack_require__(3); -const FOARouter = __webpack_require__(2); -const HRIRManager = __webpack_require__(8); -const Utils = __webpack_require__(0); - - -/** - * @typedef {string} RenderingMode - */ - -/** - * Rendering mode ENUM. - * @enum {RenderingMode} - */ -const RenderingMode = { - /** @type {string} Use ambisonic rendering. */ - AMBISONIC: 'ambisonic', - /** @type {string} Bypass. No ambisonic rendering. */ - BYPASS: 'bypass', - /** @type {string} Disable audio output. */ - OFF: 'off', -}; - - -/** - * Omnitone FOA renderer class. Uses the optimized convolution technique. - * @constructor - * @param {AudioContext} context - Associated AudioContext. - * @param {Object} config - * @param {Array} [config.channelMap] - Custom channel routing map. Useful for - * handling the inconsistency in browser's multichannel audio decoding. - * @param {Array} [config.hrirPathList] - A list of paths to HRIR files. It - * overrides the internal HRIR list if given. - * @param {RenderingMode} [config.renderingMode='ambisonic'] - Rendering mode. - */ -function FOARenderer(context, config) { - this._context = Utils.isAudioContext(context) ? - context : - Utils.throw('FOARenderer: Invalid BaseAudioContext.'); - - this._config = { - channelMap: FOARouter.ChannelMap.DEFAULT, - renderingMode: RenderingMode.AMBISONIC, - }; - - if (config.channelMap) { - if (Array.isArray(config.channelMap) && config.channelMap.length === 4) { - this._config.channelMap = config.channelMap; - } else { - Utils.throw( - 'FOARenderer: Invalid channel map. (got ' + config.channelMap + ')'); - } - } - - if (config.hrirPathList) { - if (Array.isArray(config.hrirPathList) && - config.hrirPathList.length === 2) { - this._config.pathList = config.hrirPathList; - } else { - Utils.throw( - 'FOARenderer: Invalid HRIR URLs. It must be an array with ' + - '2 URLs to HRIR files. (got ' + config.hrirPathList + ')'); - } - } else { - // By default, the path list points to GitHub CDN with FOA files. - // TODO(hoch): update this to Gstatic server when it's available. - this._config.pathList = HRIRManager.getPathList(); - } - - if (config.renderingMode) { - if (Object.values(RenderingMode).includes(config.renderingMode)) { - this._config.renderingMode = config.renderingMode; - } else { - Utils.log( - 'FOARenderer: Invalid rendering mode order. (got' + - config.renderingMode + ') Fallbacks to the mode "ambisonic".'); - } - } - - this._buildAudioGraph(); - - this._tempMatrix4 = new Float32Array(16); - this._isRendererReady = false; -} - - -/** - * Builds the internal audio graph. - * @private - */ -FOARenderer.prototype._buildAudioGraph = function() { - this.input = this._context.createGain(); - this.output = this._context.createGain(); - this._bypass = this._context.createGain(); - this._foaRouter = new FOARouter(this._context, this._config.channelMap); - this._foaRotator = new FOARotator(this._context); - this._foaConvolver = new FOAConvolver(this._context); - this.input.connect(this._foaRouter.input); - this.input.connect(this._bypass); - this._foaRouter.output.connect(this._foaRotator.input); - this._foaRotator.output.connect(this._foaConvolver.input); - this._foaConvolver.output.connect(this.output); -}; - - -/** - * Internal callback handler for |initialize| method. - * @private - * @param {function} resolve - Resolution handler. - * @param {function} reject - Rejection handler. - */ -FOARenderer.prototype._initializeCallback = function(resolve, reject) { - let bufferLoaderData = []; - for (let i = 0; i < this._config.pathList.length; ++i) { - bufferLoaderData.push({name: i, url: this._config.pathList[i]}); - } - - const bufferList = new BufferList(this._context, this._config.pathList); - bufferList.load().then( - function(hrirBufferList) { - this._foaConvolver.setHRIRBufferList(hrirBufferList); - this.setRenderingMode(this._config.renderingMode); - this._isRendererReady = true; - Utils.log('FOARenderer: HRIRs loaded successfully. Ready.'); - resolve(); - }.bind(this), - function() { - const errorMessage = 'FOARenderer: HRIR loading/decoding failed.'; - Utils.throw(errorMessage); - reject(errorMessage); - }); -}; - - -/** - * Initializes and loads the resource for the renderer. - * @return {Promise} - */ -FOARenderer.prototype.initialize = function() { - Utils.log( - 'FOARenderer: Initializing... (mode: ' + this._config.renderingMode + - ')'); - - return new Promise(this._initializeCallback.bind(this), function(error) { - Utils.throw('FOARenderer: Initialization failed. (' + error + ')'); - }); -}; - - -/** - * Set the channel map. - * @param {Number[]} channelMap - Custom channel routing for FOA stream. - */ -FOARenderer.prototype.setChannelMap = function(channelMap) { - if (!this._isRendererReady) { - return; - } - - if (channelMap.toString() !== this._config.channelMap.toString()) { - Utils.log( - 'Remapping channels ([' + this._config.channelMap.toString() + - '] -> [' + channelMap.toString() + ']).'); - this._config.channelMap = channelMap.slice(); - this._foaRouter.setChannelMap(this._config.channelMap); - } -}; - - -/** - * Updates the rotation matrix with 3x3 matrix. - * @param {Number[]} rotationMatrix3 - A 3x3 rotation matrix. (column-major) - */ -FOARenderer.prototype.setRotationMatrix3 = function(rotationMatrix3) { - if (!this._isRendererReady) { - return; - } - - this._foaRotator.setRotationMatrix3(rotationMatrix3); -}; - - -/** - * Updates the rotation matrix with 4x4 matrix. - * @param {Number[]} rotationMatrix4 - A 4x4 rotation matrix. (column-major) - */ -FOARenderer.prototype.setRotationMatrix4 = function(rotationMatrix4) { - if (!this._isRendererReady) { - return; - } - - this._foaRotator.setRotationMatrix4(rotationMatrix4); -}; - - -/** - * Set the rotation matrix from a Three.js camera object. Depreated in V1, and - * this exists only for the backward compatiblity. Instead, use - * |setRotatationMatrix4()| with Three.js |camera.worldMatrix.elements|. - * @deprecated - * @param {Object} cameraMatrix - Matrix4 from Three.js |camera.matrix|. - */ -FOARenderer.prototype.setRotationMatrixFromCamera = function(cameraMatrix) { - if (!this._isRendererReady) { - return; - } - - // Extract the inner array elements and inverse. (The actual view rotation is - // the opposite of the camera movement.) - Utils.invertMatrix4(this._tempMatrix4, cameraMatrix.elements); - this._foaRotator.setRotationMatrix4(this._tempMatrix4); -}; - - -/** - * Set the rendering mode. - * @param {RenderingMode} mode - Rendering mode. - * - 'ambisonic': activates the ambisonic decoding/binaurl rendering. - * - 'bypass': bypasses the input stream directly to the output. No ambisonic - * decoding or encoding. - * - 'off': all the processing off saving the CPU power. - */ -FOARenderer.prototype.setRenderingMode = function(mode) { - if (mode === this._config.renderingMode) { - return; - } - - switch (mode) { - case RenderingMode.AMBISONIC: - this._foaConvolver.enable(); - this._bypass.disconnect(); - break; - case RenderingMode.BYPASS: - this._foaConvolver.disable(); - this._bypass.connect(this.output); - break; - case RenderingMode.OFF: - this._foaConvolver.disable(); - this._bypass.disconnect(); - break; - default: - Utils.log( - 'FOARenderer: Rendering mode "' + mode + '" is not ' + - 'supported.'); - return; - } - - this._config.renderingMode = mode; - Utils.log('FOARenderer: Rendering mode changed. (' + mode + ')'); -}; - - -module.exports = FOARenderer; - - -/***/ }), -/* 16 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/** - * 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 Omnitone HOA decoder. - */ - - - -const BufferList = __webpack_require__(1); -const HOAConvolver = __webpack_require__(9); -const HOARotator = __webpack_require__(10); -const HRIRManager = __webpack_require__(8); -const Utils = __webpack_require__(0); - - -/** - * @typedef {string} RenderingMode - */ - -/** - * Rendering mode ENUM. - * @enum {RenderingMode} - */ -const RenderingMode = { - /** @type {string} Use ambisonic rendering. */ - AMBISONIC: 'ambisonic', - /** @type {string} Bypass. No ambisonic rendering. */ - BYPASS: 'bypass', - /** @type {string} Disable audio output. */ - OFF: 'off', -}; - - -// Currently SOA and TOA are only supported. -const SupportedAmbisonicOrder = [2, 3]; - - -/** - * Omnitone HOA renderer class. Uses the optimized convolution technique. - * @constructor - * @param {AudioContext} context - Associated AudioContext. - * @param {Object} config - * @param {Number} [config.ambisonicOrder=3] - Ambisonic order. - * @param {Array} [config.hrirPathList] - A list of paths to HRIR files. It - * overrides the internal HRIR list if given. - * @param {RenderingMode} [config.renderingMode='ambisonic'] - Rendering mode. - */ -function HOARenderer(context, config) { - this._context = Utils.isAudioContext(context) ? - context : - Utils.throw('HOARenderer: Invalid BaseAudioContext.'); - - this._config = {ambisonicOrder: 3, renderingMode: RenderingMode.AMBISONIC}; - - if (config.ambisonicOrder) { - if (SupportedAmbisonicOrder.includes(config.ambisonicOrder)) { - this._config.ambisonicOrder = config.ambisonicOrder; - } else { - Utils.log( - 'HOARenderer: Invalid ambisonic order. (got ' + - config.ambisonicOrder + ') Fallbacks to 3rd-order ambisonic.'); - } - } - - this._config.numberOfChannels = - (this._config.ambisonicOrder + 1) * (this._config.ambisonicOrder + 1); - this._config.numberOfStereoChannels = - Math.ceil(this._config.numberOfChannels / 2); - - if (config.hrirPathList) { - if (Array.isArray(config.hrirPathList) && - config.hrirPathList.length === this._config.numberOfStereoChannels) { - this._config.pathList = config.hrirPathList; - } else { - Utils.throw( - 'HOARenderer: Invalid HRIR URLs. It must be an array with ' + - this._config.numberOfStereoChannels + ' URLs to HRIR files.' + - ' (got ' + config.hrirPathList + ')'); - } - } else { - // By default, the path list points to GitHub CDN with HOA files. - // TODO(hoch): update this to Gstatic server when it's available. - this._config.pathList = - HRIRManager.getPathList({ambisonicOrder: this._config.ambisonicOrder}); - } - - if (config.renderingMode) { - if (Object.values(RenderingMode).includes(config.renderingMode)) { - this._config.renderingMode = config.renderingMode; - } else { - Utils.log( - 'HOARenderer: Invalid rendering mode. (got ' + config.renderingMode + - ') Fallbacks to "ambisonic".'); - } - } - - this._buildAudioGraph(); - - this._isRendererReady = false; -} - - -/** - * Builds the internal audio graph. - * @private - */ -HOARenderer.prototype._buildAudioGraph = function() { - this.input = this._context.createGain(); - this.output = this._context.createGain(); - this._bypass = this._context.createGain(); - this._hoaRotator = new HOARotator(this._context, this._config.ambisonicOrder); - this._hoaConvolver = - new HOAConvolver(this._context, this._config.ambisonicOrder); - this.input.connect(this._hoaRotator.input); - this.input.connect(this._bypass); - this._hoaRotator.output.connect(this._hoaConvolver.input); - this._hoaConvolver.output.connect(this.output); -}; - - -/** - * Internal callback handler for |initialize| method. - * @private - * @param {function} resolve - Resolution handler. - * @param {function} reject - Rejection handler. - */ -HOARenderer.prototype._initializeCallback = function(resolve, reject) { - let bufferLoaderData = []; - for (let i = 0; i < this._config.pathList.length; ++i) { - bufferLoaderData.push({name: i, url: this._config.pathList[i]}); - } - - const bufferList = new BufferList(this._context, this._config.pathList); - bufferList.load().then( - function(hrirBufferList) { - this._hoaConvolver.setHRIRBufferList(hrirBufferList); - this.setRenderingMode(this._config.renderingMode); - this._isRendererReady = true; - Utils.log('HOARenderer: HRIRs loaded successfully. Ready.'); - resolve(); - }.bind(this), - function() { - const errorMessage = 'HOARenderer: HRIR loading/decoding failed.'; - Utils.throw(errorMessage); - reject(errorMessage); - }); -}; - - -/** - * Initializes and loads the resource for the renderer. - * @return {Promise} - */ -HOARenderer.prototype.initialize = function() { - Utils.log( - 'HOARenderer: Initializing... (mode: ' + this._config.renderingMode + - ', ambisonic order: ' + this._config.ambisonicOrder + ')'); - - return new Promise(this._initializeCallback.bind(this), function(error) { - Utils.throw('HOARenderer: Initialization failed. (' + error + ')'); - }); -}; - - -/** - * Updates the rotation matrix with 3x3 matrix. - * @param {Number[]} rotationMatrix3 - A 3x3 rotation matrix. (column-major) - */ -HOARenderer.prototype.setRotationMatrix3 = function(rotationMatrix3) { - if (!this._isRendererReady) { - return; - } - - this._hoaRotator.setRotationMatrix3(rotationMatrix3); -}; - - -/** - * Updates the rotation matrix with 4x4 matrix. - * @param {Number[]} rotationMatrix4 - A 4x4 rotation matrix. (column-major) - */ -HOARenderer.prototype.setRotationMatrix4 = function(rotationMatrix4) { - if (!this._isRendererReady) { - return; - } - - this._hoaRotator.setRotationMatrix4(rotationMatrix4); -}; - - -/** - * Set the decoding mode. - * @param {RenderingMode} mode - Decoding mode. - * - 'ambisonic': activates the ambisonic decoding/binaurl rendering. - * - 'bypass': bypasses the input stream directly to the output. No ambisonic - * decoding or encoding. - * - 'off': all the processing off saving the CPU power. - */ -HOARenderer.prototype.setRenderingMode = function(mode) { - if (mode === this._config.renderingMode) { - return; - } - - switch (mode) { - case RenderingMode.AMBISONIC: - this._hoaConvolver.enable(); - this._bypass.disconnect(); - break; - case RenderingMode.BYPASS: - this._hoaConvolver.disable(); - this._bypass.connect(this.output); - break; - case RenderingMode.OFF: - this._hoaConvolver.disable(); - this._bypass.disconnect(); - break; - default: - Utils.log( - 'HOARenderer: Rendering mode "' + mode + '" is not ' + - 'supported.'); - return; - } - - this._config.renderingMode = mode; - Utils.log('HOARenderer: Rendering mode changed. (' + mode + ')'); -}; - - -module.exports = HOARenderer; - - -/***/ }), -/* 17 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/** - * 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 Cross-browser support polyfill for Omnitone library. - */ - - - - -/** - * Detects browser type and version. - * @return {string[]} - An array contains the detected browser name and version. - */ -exports.getBrowserInfo = function() { - const ua = navigator.userAgent; - let M = ua.match( - /(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*([\d\.]+)/i) || - []; - let tem; - - if (/trident/i.test(M[1])) { - tem = /\brv[ :]+(\d+)/g.exec(ua) || []; - return {name: 'IE', version: (tem[1] || '')}; - } - - if (M[1] === 'Chrome') { - tem = ua.match(/\bOPR|Edge\/(\d+)/); - if (tem != null) { - return {name: 'Opera', version: tem[1]}; +(function n(e, A) { + if (typeof exports === "object" && typeof module === "object") module.exports = A(); else if (typeof define === "function" && define.amd) define([], A); else { + var t = A(); + for (var i in t) (typeof exports === "object" ? exports : e)[i] = t[i]; } - } - - M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?']; - if ((tem = ua.match(/version\/([\d.]+)/i)) != null) { - M.splice(1, 1, tem[1]); - } - - let platform = ua.match(/android|ipad|iphone/i); - if (!platform) { - platform = ua.match(/cros|linux|mac os x|windows/i); - } - - return { - name: M[0], - version: M[1], - platform: platform ? platform[0] : 'unknown', - }; -}; - - -/** - * Patches AudioContext if the prefixed API is found. - */ -exports.patchSafari = function() { - if (window.webkitAudioContext && window.webkitOfflineAudioContext) { - window.AudioContext = window.webkitAudioContext; - window.OfflineAudioContext = window.webkitOfflineAudioContext; - } -}; - - -/***/ }), -/* 18 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/** - * Copyright 2016 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 Omnitone version. - */ - - - - -/** - * Omnitone library version - * @type {String} - */ -module.exports = '1.0.1'; - - -/***/ }) -/******/ ]); +})(window, function() { + return function(n) { + var e = {}; + function A(t) { + if (e[t]) { + return e[t].exports; + } + var i = e[t] = { + i: t, + l: false, + exports: {} + }; + n[t].call(i.exports, i, i.exports, A); + i.l = true; + return i.exports; + } + A.m = n; + A.c = e; + A.d = function(n, e, t) { + if (!A.o(n, e)) { + Object.defineProperty(n, e, { + configurable: false, + enumerable: true, + get: t + }); + } + }; + A.r = function(n) { + Object.defineProperty(n, "__esModule", { + value: true + }); + }; + A.n = function(n) { + var e = n && n.__esModule ? function e() { + return n["default"]; + } : function e() { + return n; + }; + A.d(e, "a", e); + return e; + }; + A.o = function(n, e) { + return Object.prototype.hasOwnProperty.call(n, e); + }; + A.p = ""; + return A(A.s = "./src/main.js"); + }({ + "./src/buffer-list.js": function(module, exports, __webpack_require__) { + "use strict"; + eval("/**\n * @license\n * Copyright 2017 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @file Streamlined AudioBuffer loader.\n */\n\n\n\n\nconst Utils = __webpack_require__(/*! ./utils.js */ \"./src/utils.js\");\n\n/**\n * @typedef {string} BufferDataType\n */\n\n/**\n * Buffer data type for ENUM.\n * @enum {BufferDataType}\n */\nconst BufferDataType = {\n /** @type {string} The data contains Base64-encoded string.. */\n BASE64: 'base64',\n /** @type {string} The data is a URL for audio file. */\n URL: 'url',\n};\n\n\n/**\n * BufferList object mananges the async loading/decoding of multiple\n * AudioBuffers from multiple URLs.\n * @constructor\n * @param {BaseAudioContext} context - Associated BaseAudioContext.\n * @param {string[]} bufferData - An ordered list of URLs.\n * @param {Object} options - Options\n * @param {string} [options.dataType='base64'] - BufferDataType specifier.\n * @param {Boolean} [options.verbose=false] - Log verbosity. |true| prints the\n * individual message from each URL and AudioBuffer.\n */\nfunction BufferList(context, bufferData, options) {\n this._context = Utils.isAudioContext(context) ?\n context :\n Utils.throw('BufferList: Invalid BaseAudioContext.');\n\n this._options = {\n dataType: BufferDataType.BASE64,\n verbose: false,\n };\n\n if (options) {\n if (options.dataType &&\n Utils.isDefinedENUMEntry(BufferDataType, options.dataType)) {\n this._options.dataType = options.dataType;\n }\n if (options.verbose) {\n this._options.verbose = Boolean(options.verbose);\n }\n }\n\n this._bufferList = [];\n this._bufferData = this._options.dataType === BufferDataType.BASE64\n ? bufferData\n : bufferData.slice(0);\n this._numberOfTasks = this._bufferData.length;\n\n this._resolveHandler = null;\n this._rejectHandler = new Function();\n}\n\n\n/**\n * Starts AudioBuffer loading tasks.\n * @return {Promise} The promise resolves with an array of\n * AudioBuffer.\n */\nBufferList.prototype.load = function() {\n return new Promise(this._promiseGenerator.bind(this));\n};\n\n\n/**\n * Promise argument generator. Internally starts multiple async loading tasks.\n * @private\n * @param {function} resolve Promise resolver.\n * @param {function} reject Promise reject.\n */\nBufferList.prototype._promiseGenerator = function(resolve, reject) {\n if (typeof resolve !== 'function') {\n Utils.throw('BufferList: Invalid Promise resolver.');\n } else {\n this._resolveHandler = resolve;\n }\n\n if (typeof reject === 'function') {\n this._rejectHandler = reject;\n }\n\n for (let i = 0; i < this._bufferData.length; ++i) {\n this._options.dataType === BufferDataType.BASE64\n ? this._launchAsyncLoadTask(i)\n : this._launchAsyncLoadTaskXHR(i);\n }\n};\n\n\n/**\n * Run async loading task for Base64-encoded string.\n * @private\n * @param {Number} taskId Task ID number from the ordered list |bufferData|.\n */\nBufferList.prototype._launchAsyncLoadTask = function(taskId) {\n const that = this;\n this._context.decodeAudioData(\n Utils.getArrayBufferFromBase64String(this._bufferData[taskId]),\n function(audioBuffer) {\n that._updateProgress(taskId, audioBuffer);\n },\n function(errorMessage) {\n that._updateProgress(taskId, null);\n const message = 'BufferList: decoding ArrayByffer(\"' + taskId +\n '\" from Base64-encoded data failed. (' + errorMessage + ')';\n that._rejectHandler(message);\n Utils.throw(message);\n });\n};\n\n\n/**\n * Run async loading task via XHR for audio file URLs.\n * @private\n * @param {Number} taskId Task ID number from the ordered list |bufferData|.\n */\nBufferList.prototype._launchAsyncLoadTaskXHR = function(taskId) {\n const xhr = new XMLHttpRequest();\n xhr.open('GET', this._bufferData[taskId]);\n xhr.responseType = 'arraybuffer';\n\n const that = this;\n xhr.onload = function() {\n if (xhr.status === 200) {\n that._context.decodeAudioData(\n xhr.response,\n function(audioBuffer) {\n that._updateProgress(taskId, audioBuffer);\n },\n function(errorMessage) {\n that._updateProgress(taskId, null);\n const message = 'BufferList: decoding \"' +\n that._bufferData[taskId] + '\" failed. (' + errorMessage + ')';\n that._rejectHandler(message);\n Utils.log(message);\n });\n } else {\n const message = 'BufferList: XHR error while loading \"' +\n that._bufferData[taskId] + '\". (' + xhr.status + ' ' +\n xhr.statusText + ')';\n that._rejectHandler(message);\n Utils.log(message);\n }\n };\n\n xhr.onerror = function(event) {\n that._updateProgress(taskId, null);\n that._rejectHandler();\n Utils.log(\n 'BufferList: XHR network failed on loading \"' +\n that._bufferData[taskId] + '\".');\n };\n\n xhr.send();\n};\n\n\n/**\n * Updates the overall progress on loading tasks.\n * @param {Number} taskId Task ID number.\n * @param {AudioBuffer} audioBuffer Decoded AudioBuffer object.\n */\nBufferList.prototype._updateProgress = function(taskId, audioBuffer) {\n this._bufferList[taskId] = audioBuffer;\n\n if (this._options.verbose) {\n let messageString = this._options.dataType === BufferDataType.BASE64\n ? 'ArrayBuffer(' + taskId + ') from Base64-encoded HRIR'\n : '\"' + this._bufferData[taskId] + '\"';\n Utils.log('BufferList: ' + messageString + ' successfully loaded.');\n }\n\n if (--this._numberOfTasks === 0) {\n let messageString = this._options.dataType === BufferDataType.BASE64\n ? this._bufferData.length + ' AudioBuffers from Base64-encoded HRIRs'\n : this._bufferData.length + ' files via XHR';\n Utils.log('BufferList: ' + messageString + ' loaded successfully.');\n this._resolveHandler(this._bufferList);\n }\n};\n\n\nmodule.exports = BufferList;\n\n\n//# sourceURL=webpack:///./src/buffer-list.js?"); + }, + "./src/foa-convolver.js": function(module, exports, __webpack_require__) { + "use strict"; + eval('/**\n * @license\n * Copyright 2017 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the "License");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an "AS IS" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n/**\n * @file A collection of convolvers. Can be used for the optimized FOA binaural\n * rendering. (e.g. SH-MaxRe HRTFs)\n */\n\n\n\n\n/**\n * FOAConvolver. A collection of 2 stereo convolvers for 4-channel FOA stream.\n * @constructor\n * @param {BaseAudioContext} context The associated AudioContext.\n * @param {AudioBuffer[]} [hrirBufferList] - An ordered-list of stereo\n * AudioBuffers for convolution. (i.e. 2 stereo AudioBuffers for FOA)\n */\nfunction FOAConvolver(context, hrirBufferList) {\n this._context = context;\n\n this._active = false;\n this._isBufferLoaded = false;\n\n this._buildAudioGraph();\n\n if (hrirBufferList) {\n this.setHRIRBufferList(hrirBufferList);\n }\n\n this.enable();\n}\n\n\n/**\n * Build the internal audio graph.\n *\n * @private\n */\nFOAConvolver.prototype._buildAudioGraph = function() {\n this._splitterWYZX = this._context.createChannelSplitter(4);\n this._mergerWY = this._context.createChannelMerger(2);\n this._mergerZX = this._context.createChannelMerger(2);\n this._convolverWY = this._context.createConvolver();\n this._convolverZX = this._context.createConvolver();\n this._splitterWY = this._context.createChannelSplitter(2);\n this._splitterZX = this._context.createChannelSplitter(2);\n this._inverter = this._context.createGain();\n this._mergerBinaural = this._context.createChannelMerger(2);\n this._summingBus = this._context.createGain();\n\n // Group W and Y, then Z and X.\n this._splitterWYZX.connect(this._mergerWY, 0, 0);\n this._splitterWYZX.connect(this._mergerWY, 1, 1);\n this._splitterWYZX.connect(this._mergerZX, 2, 0);\n this._splitterWYZX.connect(this._mergerZX, 3, 1);\n\n // Create a network of convolvers using splitter/merger.\n this._mergerWY.connect(this._convolverWY);\n this._mergerZX.connect(this._convolverZX);\n this._convolverWY.connect(this._splitterWY);\n this._convolverZX.connect(this._splitterZX);\n this._splitterWY.connect(this._mergerBinaural, 0, 0);\n this._splitterWY.connect(this._mergerBinaural, 0, 1);\n this._splitterWY.connect(this._mergerBinaural, 1, 0);\n this._splitterWY.connect(this._inverter, 1, 0);\n this._inverter.connect(this._mergerBinaural, 0, 1);\n this._splitterZX.connect(this._mergerBinaural, 0, 0);\n this._splitterZX.connect(this._mergerBinaural, 0, 1);\n this._splitterZX.connect(this._mergerBinaural, 1, 0);\n this._splitterZX.connect(this._mergerBinaural, 1, 1);\n\n // By default, WebAudio\'s convolver does the normalization based on IR\'s\n // energy. For the precise convolution, it must be disabled before the buffer\n // assignment.\n this._convolverWY.normalize = false;\n this._convolverZX.normalize = false;\n\n // For asymmetric degree.\n this._inverter.gain.value = -1;\n\n // Input/output proxy.\n this.input = this._splitterWYZX;\n this.output = this._summingBus;\n};\n\n\n/**\n * Assigns 2 HRIR AudioBuffers to 2 convolvers: Note that we use 2 stereo\n * convolutions for 4-channel direct convolution. Using mono convolver or\n * 4-channel convolver is not viable because mono convolution wastefully\n * produces the stereo outputs, and the 4-ch convolver does cross-channel\n * convolution. (See Web Audio API spec)\n * @param {AudioBuffer[]} hrirBufferList - An array of stereo AudioBuffers for\n * convolvers.\n */\nFOAConvolver.prototype.setHRIRBufferList = function(hrirBufferList) {\n // After these assignments, the channel data in the buffer is immutable in\n // FireFox. (i.e. neutered) So we should avoid re-assigning buffers, otherwise\n // an exception will be thrown.\n if (this._isBufferLoaded) {\n return;\n }\n\n this._convolverWY.buffer = hrirBufferList[0];\n this._convolverZX.buffer = hrirBufferList[1];\n this._isBufferLoaded = true;\n};\n\n\n/**\n * Enable FOAConvolver instance. The audio graph will be activated and pulled by\n * the WebAudio engine. (i.e. consume CPU cycle)\n */\nFOAConvolver.prototype.enable = function() {\n this._mergerBinaural.connect(this._summingBus);\n this._active = true;\n};\n\n\n/**\n * Disable FOAConvolver instance. The inner graph will be disconnected from the\n * audio destination, thus no CPU cycle will be consumed.\n */\nFOAConvolver.prototype.disable = function() {\n this._mergerBinaural.disconnect();\n this._active = false;\n};\n\n\nmodule.exports = FOAConvolver;\n\n\n//# sourceURL=webpack:///./src/foa-convolver.js?'); + }, + "./src/foa-renderer.js": function(module, exports, __webpack_require__) { + "use strict"; + eval("/**\n * @license\n * Copyright 2017 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n/**\n * @file Omnitone FOARenderer. This is user-facing API for the first-order\n * ambisonic decoder and the optimized binaural renderer.\n */\n\n\n\nconst BufferList = __webpack_require__(/*! ./buffer-list.js */ \"./src/buffer-list.js\");\nconst FOAConvolver = __webpack_require__(/*! ./foa-convolver.js */ \"./src/foa-convolver.js\");\nconst FOAHrirBase64 = __webpack_require__(/*! ./resources/omnitone-foa-hrir-base64.js */ \"./src/resources/omnitone-foa-hrir-base64.js\");\nconst FOARotator = __webpack_require__(/*! ./foa-rotator.js */ \"./src/foa-rotator.js\");\nconst FOARouter = __webpack_require__(/*! ./foa-router.js */ \"./src/foa-router.js\");\nconst Utils = __webpack_require__(/*! ./utils.js */ \"./src/utils.js\");\n\n\n/**\n * @typedef {string} RenderingMode\n */\n\n/**\n * Rendering mode ENUM.\n * @enum {RenderingMode}\n */\nconst RenderingMode = {\n /** @type {string} Use ambisonic rendering. */\n AMBISONIC: 'ambisonic',\n /** @type {string} Bypass. No ambisonic rendering. */\n BYPASS: 'bypass',\n /** @type {string} Disable audio output. */\n OFF: 'off',\n};\n\n\n/**\n * Omnitone FOA renderer class. Uses the optimized convolution technique.\n * @constructor\n * @param {AudioContext} context - Associated AudioContext.\n * @param {Object} config\n * @param {Array} [config.channelMap] - Custom channel routing map. Useful for\n * handling the inconsistency in browser's multichannel audio decoding.\n * @param {Array} [config.hrirPathList] - A list of paths to HRIR files. It\n * overrides the internal HRIR list if given.\n * @param {RenderingMode} [config.renderingMode='ambisonic'] - Rendering mode.\n */\nfunction FOARenderer(context, config) {\n this._context = Utils.isAudioContext(context) ?\n context :\n Utils.throw('FOARenderer: Invalid BaseAudioContext.');\n\n this._config = {\n channelMap: FOARouter.ChannelMap.DEFAULT,\n renderingMode: RenderingMode.AMBISONIC,\n };\n\n if (config) {\n if (config.channelMap) {\n if (Array.isArray(config.channelMap) && config.channelMap.length === 4) {\n this._config.channelMap = config.channelMap;\n } else {\n Utils.throw(\n 'FOARenderer: Invalid channel map. (got ' + config.channelMap\n + ')');\n }\n }\n\n if (config.hrirPathList) {\n if (Array.isArray(config.hrirPathList) &&\n config.hrirPathList.length === 2) {\n this._config.pathList = config.hrirPathList;\n } else {\n Utils.throw(\n 'FOARenderer: Invalid HRIR URLs. It must be an array with ' +\n '2 URLs to HRIR files. (got ' + config.hrirPathList + ')');\n }\n }\n\n if (config.renderingMode) {\n if (Object.values(RenderingMode).includes(config.renderingMode)) {\n this._config.renderingMode = config.renderingMode;\n } else {\n Utils.log(\n 'FOARenderer: Invalid rendering mode order. (got' +\n config.renderingMode + ') Fallbacks to the mode \"ambisonic\".');\n }\n }\n }\n\n this._buildAudioGraph();\n\n this._tempMatrix4 = new Float32Array(16);\n this._isRendererReady = false;\n}\n\n\n/**\n * Builds the internal audio graph.\n * @private\n */\nFOARenderer.prototype._buildAudioGraph = function() {\n this.input = this._context.createGain();\n this.output = this._context.createGain();\n this._bypass = this._context.createGain();\n this._foaRouter = new FOARouter(this._context, this._config.channelMap);\n this._foaRotator = new FOARotator(this._context);\n this._foaConvolver = new FOAConvolver(this._context);\n this.input.connect(this._foaRouter.input);\n this.input.connect(this._bypass);\n this._foaRouter.output.connect(this._foaRotator.input);\n this._foaRotator.output.connect(this._foaConvolver.input);\n this._foaConvolver.output.connect(this.output);\n\n this.input.channelCount = 4;\n this.input.channelCountMode = 'explicit';\n this.input.channelInterpretation = 'discrete';\n};\n\n\n/**\n * Internal callback handler for |initialize| method.\n * @private\n * @param {function} resolve - Resolution handler.\n * @param {function} reject - Rejection handler.\n */\nFOARenderer.prototype._initializeCallback = function(resolve, reject) {\n const bufferList = this._config.pathList\n ? new BufferList(this._context, this._config.pathList, {dataType: 'url'})\n : new BufferList(this._context, FOAHrirBase64);\n bufferList.load().then(\n function(hrirBufferList) {\n this._foaConvolver.setHRIRBufferList(hrirBufferList);\n this.setRenderingMode(this._config.renderingMode);\n this._isRendererReady = true;\n Utils.log('FOARenderer: HRIRs loaded successfully. Ready.');\n resolve();\n }.bind(this),\n function() {\n const errorMessage = 'FOARenderer: HRIR loading/decoding failed.';\n reject(errorMessage);\n Utils.throw(errorMessage);\n });\n};\n\n\n/**\n * Initializes and loads the resource for the renderer.\n * @return {Promise}\n */\nFOARenderer.prototype.initialize = function() {\n Utils.log(\n 'FOARenderer: Initializing... (mode: ' + this._config.renderingMode +\n ')');\n\n return new Promise(this._initializeCallback.bind(this));\n};\n\n\n/**\n * Set the channel map.\n * @param {Number[]} channelMap - Custom channel routing for FOA stream.\n */\nFOARenderer.prototype.setChannelMap = function(channelMap) {\n if (!this._isRendererReady) {\n return;\n }\n\n if (channelMap.toString() !== this._config.channelMap.toString()) {\n Utils.log(\n 'Remapping channels ([' + this._config.channelMap.toString() +\n '] -> [' + channelMap.toString() + ']).');\n this._config.channelMap = channelMap.slice();\n this._foaRouter.setChannelMap(this._config.channelMap);\n }\n};\n\n\n/**\n * Updates the rotation matrix with 3x3 matrix.\n * @param {Number[]} rotationMatrix3 - A 3x3 rotation matrix. (column-major)\n */\nFOARenderer.prototype.setRotationMatrix3 = function(rotationMatrix3) {\n if (!this._isRendererReady) {\n return;\n }\n\n this._foaRotator.setRotationMatrix3(rotationMatrix3);\n};\n\n\n/**\n * Updates the rotation matrix with 4x4 matrix.\n * @param {Number[]} rotationMatrix4 - A 4x4 rotation matrix. (column-major)\n */\nFOARenderer.prototype.setRotationMatrix4 = function(rotationMatrix4) {\n if (!this._isRendererReady) {\n return;\n }\n\n this._foaRotator.setRotationMatrix4(rotationMatrix4);\n};\n\n\n/**\n * Set the rotation matrix from a Three.js camera object. Depreated in V1, and\n * this exists only for the backward compatiblity. Instead, use\n * |setRotatationMatrix4()| with Three.js |camera.worldMatrix.elements|.\n * @deprecated\n * @param {Object} cameraMatrix - Matrix4 from Three.js |camera.matrix|.\n */\nFOARenderer.prototype.setRotationMatrixFromCamera = function(cameraMatrix) {\n if (!this._isRendererReady) {\n return;\n }\n\n // Extract the inner array elements and inverse. (The actual view rotation is\n // the opposite of the camera movement.)\n Utils.invertMatrix4(this._tempMatrix4, cameraMatrix.elements);\n this._foaRotator.setRotationMatrix4(this._tempMatrix4);\n};\n\n\n/**\n * Set the rendering mode.\n * @param {RenderingMode} mode - Rendering mode.\n * - 'ambisonic': activates the ambisonic decoding/binaurl rendering.\n * - 'bypass': bypasses the input stream directly to the output. No ambisonic\n * decoding or encoding.\n * - 'off': all the processing off saving the CPU power.\n */\nFOARenderer.prototype.setRenderingMode = function(mode) {\n if (mode === this._config.renderingMode) {\n return;\n }\n\n switch (mode) {\n case RenderingMode.AMBISONIC:\n this._foaConvolver.enable();\n this._bypass.disconnect();\n break;\n case RenderingMode.BYPASS:\n this._foaConvolver.disable();\n this._bypass.connect(this.output);\n break;\n case RenderingMode.OFF:\n this._foaConvolver.disable();\n this._bypass.disconnect();\n break;\n default:\n Utils.log(\n 'FOARenderer: Rendering mode \"' + mode + '\" is not ' +\n 'supported.');\n return;\n }\n\n this._config.renderingMode = mode;\n Utils.log('FOARenderer: Rendering mode changed. (' + mode + ')');\n};\n\n\nmodule.exports = FOARenderer;\n\n\n//# sourceURL=webpack:///./src/foa-renderer.js?"); + }, + "./src/foa-rotator.js": function(module, exports, __webpack_require__) { + "use strict"; + eval('/**\n * @license\n * Copyright 2016 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the "License");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an "AS IS" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @file Sound field rotator for first-order-ambisonics decoding.\n */\n\n\n\n\n/**\n * First-order-ambisonic decoder based on gain node network.\n * @constructor\n * @param {AudioContext} context - Associated AudioContext.\n */\nfunction FOARotator(context) {\n this._context = context;\n\n this._splitter = this._context.createChannelSplitter(4);\n this._inY = this._context.createGain();\n this._inZ = this._context.createGain();\n this._inX = this._context.createGain();\n this._m0 = this._context.createGain();\n this._m1 = this._context.createGain();\n this._m2 = this._context.createGain();\n this._m3 = this._context.createGain();\n this._m4 = this._context.createGain();\n this._m5 = this._context.createGain();\n this._m6 = this._context.createGain();\n this._m7 = this._context.createGain();\n this._m8 = this._context.createGain();\n this._outY = this._context.createGain();\n this._outZ = this._context.createGain();\n this._outX = this._context.createGain();\n this._merger = this._context.createChannelMerger(4);\n\n // ACN channel ordering: [1, 2, 3] => [-Y, Z, -X]\n // Y (from channel 1)\n this._splitter.connect(this._inY, 1);\n // Z (from channel 2)\n this._splitter.connect(this._inZ, 2);\n // X (from channel 3)\n this._splitter.connect(this._inX, 3);\n this._inY.gain.value = -1;\n this._inX.gain.value = -1;\n\n // Apply the rotation in the world space.\n // |Y| | m0 m3 m6 | | Y * m0 + Z * m3 + X * m6 | | Yr |\n // |Z| * | m1 m4 m7 | = | Y * m1 + Z * m4 + X * m7 | = | Zr |\n // |X| | m2 m5 m8 | | Y * m2 + Z * m5 + X * m8 | | Xr |\n this._inY.connect(this._m0);\n this._inY.connect(this._m1);\n this._inY.connect(this._m2);\n this._inZ.connect(this._m3);\n this._inZ.connect(this._m4);\n this._inZ.connect(this._m5);\n this._inX.connect(this._m6);\n this._inX.connect(this._m7);\n this._inX.connect(this._m8);\n this._m0.connect(this._outY);\n this._m1.connect(this._outZ);\n this._m2.connect(this._outX);\n this._m3.connect(this._outY);\n this._m4.connect(this._outZ);\n this._m5.connect(this._outX);\n this._m6.connect(this._outY);\n this._m7.connect(this._outZ);\n this._m8.connect(this._outX);\n\n // Transform 3: world space to audio space.\n // W -> W (to channel 0)\n this._splitter.connect(this._merger, 0, 0);\n // Y (to channel 1)\n this._outY.connect(this._merger, 0, 1);\n // Z (to channel 2)\n this._outZ.connect(this._merger, 0, 2);\n // X (to channel 3)\n this._outX.connect(this._merger, 0, 3);\n this._outY.gain.value = -1;\n this._outX.gain.value = -1;\n\n this.setRotationMatrix3(new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]));\n\n // input/output proxy.\n this.input = this._splitter;\n this.output = this._merger;\n}\n\n\n/**\n * Updates the rotation matrix with 3x3 matrix.\n * @param {Number[]} rotationMatrix3 - A 3x3 rotation matrix. (column-major)\n */\nFOARotator.prototype.setRotationMatrix3 = function(rotationMatrix3) {\n this._m0.gain.value = rotationMatrix3[0];\n this._m1.gain.value = rotationMatrix3[1];\n this._m2.gain.value = rotationMatrix3[2];\n this._m3.gain.value = rotationMatrix3[3];\n this._m4.gain.value = rotationMatrix3[4];\n this._m5.gain.value = rotationMatrix3[5];\n this._m6.gain.value = rotationMatrix3[6];\n this._m7.gain.value = rotationMatrix3[7];\n this._m8.gain.value = rotationMatrix3[8];\n};\n\n\n/**\n * Updates the rotation matrix with 4x4 matrix.\n * @param {Number[]} rotationMatrix4 - A 4x4 rotation matrix. (column-major)\n */\nFOARotator.prototype.setRotationMatrix4 = function(rotationMatrix4) {\n this._m0.gain.value = rotationMatrix4[0];\n this._m1.gain.value = rotationMatrix4[1];\n this._m2.gain.value = rotationMatrix4[2];\n this._m3.gain.value = rotationMatrix4[4];\n this._m4.gain.value = rotationMatrix4[5];\n this._m5.gain.value = rotationMatrix4[6];\n this._m6.gain.value = rotationMatrix4[8];\n this._m7.gain.value = rotationMatrix4[9];\n this._m8.gain.value = rotationMatrix4[10];\n};\n\n\n/**\n * Returns the current 3x3 rotation matrix.\n * @return {Number[]} - A 3x3 rotation matrix. (column-major)\n */\nFOARotator.prototype.getRotationMatrix3 = function() {\n return [\n this._m0.gain.value, this._m1.gain.value, this._m2.gain.value,\n this._m3.gain.value, this._m4.gain.value, this._m5.gain.value,\n this._m6.gain.value, this._m7.gain.value, this._m8.gain.value,\n ];\n};\n\n\n/**\n * Returns the current 4x4 rotation matrix.\n * @return {Number[]} - A 4x4 rotation matrix. (column-major)\n */\nFOARotator.prototype.getRotationMatrix4 = function() {\n let rotationMatrix4 = new Float32Array(16);\n rotationMatrix4[0] = this._m0.gain.value;\n rotationMatrix4[1] = this._m1.gain.value;\n rotationMatrix4[2] = this._m2.gain.value;\n rotationMatrix4[4] = this._m3.gain.value;\n rotationMatrix4[5] = this._m4.gain.value;\n rotationMatrix4[6] = this._m5.gain.value;\n rotationMatrix4[8] = this._m6.gain.value;\n rotationMatrix4[9] = this._m7.gain.value;\n rotationMatrix4[10] = this._m8.gain.value;\n return rotationMatrix4;\n};\n\n\nmodule.exports = FOARotator;\n\n\n//# sourceURL=webpack:///./src/foa-rotator.js?'); + }, + "./src/foa-router.js": function(module, exports, __webpack_require__) { + "use strict"; + eval('/**\n * @license\n * Copyright 2016 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the "License");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an "AS IS" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @file An audio channel router to resolve different channel layouts between\n * browsers.\n */\n\n\n\n\n/**\n * @typedef {Number[]} ChannelMap\n */\n\n/**\n * Channel map dictionary ENUM.\n * @enum {ChannelMap}\n */\nconst ChannelMap = {\n /** @type {Number[]} - ACN channel map for Chrome and FireFox. (FFMPEG) */\n DEFAULT: [0, 1, 2, 3],\n /** @type {Number[]} - Safari\'s 4-channel map for AAC codec. */\n SAFARI: [2, 0, 1, 3],\n /** @type {Number[]} - ACN > FuMa conversion map. */\n FUMA: [0, 3, 1, 2],\n};\n\n\n/**\n * Channel router for FOA stream.\n * @constructor\n * @param {AudioContext} context - Associated AudioContext.\n * @param {Number[]} channelMap - Routing destination array.\n */\nfunction FOARouter(context, channelMap) {\n this._context = context;\n\n this._splitter = this._context.createChannelSplitter(4);\n this._merger = this._context.createChannelMerger(4);\n\n // input/output proxy.\n this.input = this._splitter;\n this.output = this._merger;\n\n this.setChannelMap(channelMap || ChannelMap.DEFAULT);\n}\n\n\n/**\n * Sets channel map.\n * @param {Number[]} channelMap - A new channel map for FOA stream.\n */\nFOARouter.prototype.setChannelMap = function(channelMap) {\n if (!Array.isArray(channelMap)) {\n return;\n }\n\n this._channelMap = channelMap;\n this._splitter.disconnect();\n this._splitter.connect(this._merger, 0, this._channelMap[0]);\n this._splitter.connect(this._merger, 1, this._channelMap[1]);\n this._splitter.connect(this._merger, 2, this._channelMap[2]);\n this._splitter.connect(this._merger, 3, this._channelMap[3]);\n};\n\n\n/**\n * Static channel map ENUM.\n * @static\n * @type {ChannelMap}\n */\nFOARouter.ChannelMap = ChannelMap;\n\n\nmodule.exports = FOARouter;\n\n\n//# sourceURL=webpack:///./src/foa-router.js?'); + }, + "./src/hoa-convolver.js": function(module, exports, __webpack_require__) { + "use strict"; + eval('/**\n * @license\n * Copyright 2017 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the "License");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an "AS IS" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n/**\n * @file A collection of convolvers. Can be used for the optimized HOA binaural\n * rendering. (e.g. SH-MaxRe HRTFs)\n */\n\n\n\n\n/**\n * A convolver network for N-channel HOA stream.\n * @constructor\n * @param {AudioContext} context - Associated AudioContext.\n * @param {Number} ambisonicOrder - Ambisonic order. (2 or 3)\n * @param {AudioBuffer[]} [hrirBufferList] - An ordered-list of stereo\n * AudioBuffers for convolution. (SOA: 5 AudioBuffers, TOA: 8 AudioBuffers)\n */\nfunction HOAConvolver(context, ambisonicOrder, hrirBufferList) {\n this._context = context;\n\n this._active = false;\n this._isBufferLoaded = false;\n\n // The number of channels K based on the ambisonic order N where K = (N+1)^2.\n this._ambisonicOrder = ambisonicOrder;\n this._numberOfChannels =\n (this._ambisonicOrder + 1) * (this._ambisonicOrder + 1);\n\n this._buildAudioGraph();\n if (hrirBufferList) {\n this.setHRIRBufferList(hrirBufferList);\n }\n\n this.enable();\n}\n\n\n/**\n * Build the internal audio graph.\n * For TOA convolution:\n * input -> splitter(16) -[0,1]-> merger(2) -> convolver(2) -> splitter(2)\n * -[2,3]-> merger(2) -> convolver(2) -> splitter(2)\n * -[4,5]-> ... (6 more, 8 branches total)\n * @private\n */\nHOAConvolver.prototype._buildAudioGraph = function() {\n const numberOfStereoChannels = Math.ceil(this._numberOfChannels / 2);\n\n this._inputSplitter =\n this._context.createChannelSplitter(this._numberOfChannels);\n this._stereoMergers = [];\n this._convolvers = [];\n this._stereoSplitters = [];\n this._positiveIndexSphericalHarmonics = this._context.createGain();\n this._negativeIndexSphericalHarmonics = this._context.createGain();\n this._inverter = this._context.createGain();\n this._binauralMerger = this._context.createChannelMerger(2);\n this._outputGain = this._context.createGain();\n\n for (let i = 0; i < numberOfStereoChannels; ++i) {\n this._stereoMergers[i] = this._context.createChannelMerger(2);\n this._convolvers[i] = this._context.createConvolver();\n this._stereoSplitters[i] = this._context.createChannelSplitter(2);\n this._convolvers[i].normalize = false;\n }\n\n for (let l = 0; l <= this._ambisonicOrder; ++l) {\n for (let m = -l; m <= l; m++) {\n // We compute the ACN index (k) of ambisonics channel using the degree (l)\n // and index (m): k = l^2 + l + m\n const acnIndex = l * l + l + m;\n const stereoIndex = Math.floor(acnIndex / 2);\n\n // Split channels from input into array of stereo convolvers.\n // Then create a network of mergers that produces the stereo output.\n this._inputSplitter.connect(\n this._stereoMergers[stereoIndex], acnIndex, acnIndex % 2);\n this._stereoMergers[stereoIndex].connect(this._convolvers[stereoIndex]);\n this._convolvers[stereoIndex].connect(this._stereoSplitters[stereoIndex]);\n\n // Positive index (m >= 0) spherical harmonics are symmetrical around the\n // front axis, while negative index (m < 0) spherical harmonics are\n // anti-symmetrical around the front axis. We will exploit this symmetry\n // to reduce the number of convolutions required when rendering to a\n // symmetrical binaural renderer.\n if (m >= 0) {\n this._stereoSplitters[stereoIndex].connect(\n this._positiveIndexSphericalHarmonics, acnIndex % 2);\n } else {\n this._stereoSplitters[stereoIndex].connect(\n this._negativeIndexSphericalHarmonics, acnIndex % 2);\n }\n }\n }\n\n this._positiveIndexSphericalHarmonics.connect(this._binauralMerger, 0, 0);\n this._positiveIndexSphericalHarmonics.connect(this._binauralMerger, 0, 1);\n this._negativeIndexSphericalHarmonics.connect(this._binauralMerger, 0, 0);\n this._negativeIndexSphericalHarmonics.connect(this._inverter);\n this._inverter.connect(this._binauralMerger, 0, 1);\n\n // For asymmetric index.\n this._inverter.gain.value = -1;\n\n // Input/Output proxy.\n this.input = this._inputSplitter;\n this.output = this._outputGain;\n};\n\n\n/**\n * Assigns N HRIR AudioBuffers to N convolvers: Note that we use 2 stereo\n * convolutions for 4-channel direct convolution. Using mono convolver or\n * 4-channel convolver is not viable because mono convolution wastefully\n * produces the stereo outputs, and the 4-ch convolver does cross-channel\n * convolution. (See Web Audio API spec)\n * @param {AudioBuffer[]} hrirBufferList - An array of stereo AudioBuffers for\n * convolvers.\n */\nHOAConvolver.prototype.setHRIRBufferList = function(hrirBufferList) {\n // After these assignments, the channel data in the buffer is immutable in\n // FireFox. (i.e. neutered) So we should avoid re-assigning buffers, otherwise\n // an exception will be thrown.\n if (this._isBufferLoaded) {\n return;\n }\n\n for (let i = 0; i < hrirBufferList.length; ++i) {\n this._convolvers[i].buffer = hrirBufferList[i];\n }\n\n this._isBufferLoaded = true;\n};\n\n\n/**\n * Enable HOAConvolver instance. The audio graph will be activated and pulled by\n * the WebAudio engine. (i.e. consume CPU cycle)\n */\nHOAConvolver.prototype.enable = function() {\n this._binauralMerger.connect(this._outputGain);\n this._active = true;\n};\n\n\n/**\n * Disable HOAConvolver instance. The inner graph will be disconnected from the\n * audio destination, thus no CPU cycle will be consumed.\n */\nHOAConvolver.prototype.disable = function() {\n this._binauralMerger.disconnect();\n this._active = false;\n};\n\n\nmodule.exports = HOAConvolver;\n\n\n//# sourceURL=webpack:///./src/hoa-convolver.js?'); + }, + "./src/hoa-renderer.js": function(module, exports, __webpack_require__) { + "use strict"; + eval("/**\n * @license\n * Copyright 2017 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n/**\n * @file Omnitone HOARenderer. This is user-facing API for the higher-order\n * ambisonic decoder and the optimized binaural renderer.\n */\n\n\n\nconst BufferList = __webpack_require__(/*! ./buffer-list.js */ \"./src/buffer-list.js\");\nconst HOAConvolver = __webpack_require__(/*! ./hoa-convolver.js */ \"./src/hoa-convolver.js\");\nconst HOARotator = __webpack_require__(/*! ./hoa-rotator.js */ \"./src/hoa-rotator.js\");\nconst TOAHrirBase64 = __webpack_require__(/*! ./resources/omnitone-toa-hrir-base64.js */ \"./src/resources/omnitone-toa-hrir-base64.js\");\nconst SOAHrirBase64 = __webpack_require__(/*! ./resources/omnitone-soa-hrir-base64.js */ \"./src/resources/omnitone-soa-hrir-base64.js\");\nconst Utils = __webpack_require__(/*! ./utils.js */ \"./src/utils.js\");\n\n\n/**\n * @typedef {string} RenderingMode\n */\n\n/**\n * Rendering mode ENUM.\n * @enum {RenderingMode}\n */\nconst RenderingMode = {\n /** @type {string} Use ambisonic rendering. */\n AMBISONIC: 'ambisonic',\n /** @type {string} Bypass. No ambisonic rendering. */\n BYPASS: 'bypass',\n /** @type {string} Disable audio output. */\n OFF: 'off',\n};\n\n\n// Currently SOA and TOA are only supported.\nconst SupportedAmbisonicOrder = [2, 3];\n\n\n/**\n * Omnitone HOA renderer class. Uses the optimized convolution technique.\n * @constructor\n * @param {AudioContext} context - Associated AudioContext.\n * @param {Object} config\n * @param {Number} [config.ambisonicOrder=3] - Ambisonic order.\n * @param {Array} [config.hrirPathList] - A list of paths to HRIR files. It\n * overrides the internal HRIR list if given.\n * @param {RenderingMode} [config.renderingMode='ambisonic'] - Rendering mode.\n */\nfunction HOARenderer(context, config) {\n this._context = Utils.isAudioContext(context) ?\n context :\n Utils.throw('HOARenderer: Invalid BaseAudioContext.');\n\n this._config = {\n ambisonicOrder: 3,\n renderingMode: RenderingMode.AMBISONIC,\n };\n\n if (config && config.ambisonicOrder) {\n if (SupportedAmbisonicOrder.includes(config.ambisonicOrder)) {\n this._config.ambisonicOrder = config.ambisonicOrder;\n } else {\n Utils.log(\n 'HOARenderer: Invalid ambisonic order. (got ' +\n config.ambisonicOrder + ') Fallbacks to 3rd-order ambisonic.');\n }\n }\n\n this._config.numberOfChannels =\n (this._config.ambisonicOrder + 1) * (this._config.ambisonicOrder + 1);\n this._config.numberOfStereoChannels =\n Math.ceil(this._config.numberOfChannels / 2);\n\n if (config && config.hrirPathList) {\n if (Array.isArray(config.hrirPathList) &&\n config.hrirPathList.length === this._config.numberOfStereoChannels) {\n this._config.pathList = config.hrirPathList;\n } else {\n Utils.throw(\n 'HOARenderer: Invalid HRIR URLs. It must be an array with ' +\n this._config.numberOfStereoChannels + ' URLs to HRIR files.' +\n ' (got ' + config.hrirPathList + ')');\n }\n }\n\n if (config && config.renderingMode) {\n if (Object.values(RenderingMode).includes(config.renderingMode)) {\n this._config.renderingMode = config.renderingMode;\n } else {\n Utils.log(\n 'HOARenderer: Invalid rendering mode. (got ' +\n config.renderingMode + ') Fallbacks to \"ambisonic\".');\n }\n }\n\n this._buildAudioGraph();\n\n this._isRendererReady = false;\n}\n\n\n/**\n * Builds the internal audio graph.\n * @private\n */\nHOARenderer.prototype._buildAudioGraph = function() {\n this.input = this._context.createGain();\n this.output = this._context.createGain();\n this._bypass = this._context.createGain();\n this._hoaRotator = new HOARotator(this._context, this._config.ambisonicOrder);\n this._hoaConvolver =\n new HOAConvolver(this._context, this._config.ambisonicOrder);\n this.input.connect(this._hoaRotator.input);\n this.input.connect(this._bypass);\n this._hoaRotator.output.connect(this._hoaConvolver.input);\n this._hoaConvolver.output.connect(this.output);\n\n this.input.channelCount = this._config.numberOfChannels;\n this.input.channelCountMode = 'explicit';\n this.input.channelInterpretation = 'discrete';\n};\n\n\n/**\n * Internal callback handler for |initialize| method.\n * @private\n * @param {function} resolve - Resolution handler.\n * @param {function} reject - Rejection handler.\n */\nHOARenderer.prototype._initializeCallback = function(resolve, reject) {\n let bufferList;\n if (this._config.pathList) {\n bufferList =\n new BufferList(this._context, this._config.pathList, {dataType: 'url'});\n } else {\n bufferList = this._config.ambisonicOrder === 2\n ? new BufferList(this._context, SOAHrirBase64)\n : new BufferList(this._context, TOAHrirBase64);\n }\n\n bufferList.load().then(\n function(hrirBufferList) {\n this._hoaConvolver.setHRIRBufferList(hrirBufferList);\n this.setRenderingMode(this._config.renderingMode);\n this._isRendererReady = true;\n Utils.log('HOARenderer: HRIRs loaded successfully. Ready.');\n resolve();\n }.bind(this),\n function() {\n const errorMessage = 'HOARenderer: HRIR loading/decoding failed.';\n reject(errorMessage);\n Utils.throw(errorMessage);\n });\n};\n\n\n/**\n * Initializes and loads the resource for the renderer.\n * @return {Promise}\n */\nHOARenderer.prototype.initialize = function() {\n Utils.log(\n 'HOARenderer: Initializing... (mode: ' + this._config.renderingMode +\n ', ambisonic order: ' + this._config.ambisonicOrder + ')');\n\n return new Promise(this._initializeCallback.bind(this));\n};\n\n\n/**\n * Updates the rotation matrix with 3x3 matrix.\n * @param {Number[]} rotationMatrix3 - A 3x3 rotation matrix. (column-major)\n */\nHOARenderer.prototype.setRotationMatrix3 = function(rotationMatrix3) {\n if (!this._isRendererReady) {\n return;\n }\n\n this._hoaRotator.setRotationMatrix3(rotationMatrix3);\n};\n\n\n/**\n * Updates the rotation matrix with 4x4 matrix.\n * @param {Number[]} rotationMatrix4 - A 4x4 rotation matrix. (column-major)\n */\nHOARenderer.prototype.setRotationMatrix4 = function(rotationMatrix4) {\n if (!this._isRendererReady) {\n return;\n }\n\n this._hoaRotator.setRotationMatrix4(rotationMatrix4);\n};\n\n\n/**\n * Set the decoding mode.\n * @param {RenderingMode} mode - Decoding mode.\n * - 'ambisonic': activates the ambisonic decoding/binaurl rendering.\n * - 'bypass': bypasses the input stream directly to the output. No ambisonic\n * decoding or encoding.\n * - 'off': all the processing off saving the CPU power.\n */\nHOARenderer.prototype.setRenderingMode = function(mode) {\n if (mode === this._config.renderingMode) {\n return;\n }\n\n switch (mode) {\n case RenderingMode.AMBISONIC:\n this._hoaConvolver.enable();\n this._bypass.disconnect();\n break;\n case RenderingMode.BYPASS:\n this._hoaConvolver.disable();\n this._bypass.connect(this.output);\n break;\n case RenderingMode.OFF:\n this._hoaConvolver.disable();\n this._bypass.disconnect();\n break;\n default:\n Utils.log(\n 'HOARenderer: Rendering mode \"' + mode + '\" is not ' +\n 'supported.');\n return;\n }\n\n this._config.renderingMode = mode;\n Utils.log('HOARenderer: Rendering mode changed. (' + mode + ')');\n};\n\n\nmodule.exports = HOARenderer;\n\n\n//# sourceURL=webpack:///./src/hoa-renderer.js?"); + }, + "./src/hoa-rotator.js": function(module, exports, __webpack_require__) { + "use strict"; + eval('/**\n * @license\n * Copyright 2016 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the "License");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an "AS IS" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @file Sound field rotator for higher-order-ambisonics decoding.\n */\n\n\n\n\n/**\n * Kronecker Delta function.\n * @param {Number} i\n * @param {Number} j\n * @return {Number}\n */\nfunction getKroneckerDelta(i, j) {\n return i === j ? 1 : 0;\n}\n\n\n/**\n * A helper function to allow us to access a matrix array in the same\n * manner, assuming it is a (2l+1)x(2l+1) matrix. [2] uses an odd convention of\n * referring to the rows and columns using centered indices, so the middle row\n * and column are (0, 0) and the upper left would have negative coordinates.\n * @param {Number[]} matrix - N matrices of gainNodes, each with (2n+1) x (2n+1)\n * elements, where n=1,2,...,N.\n * @param {Number} l\n * @param {Number} i\n * @param {Number} j\n * @param {Number} gainValue\n */\nfunction setCenteredElement(matrix, l, i, j, gainValue) {\n const index = (j + l) * (2 * l + 1) + (i + l);\n // Row-wise indexing.\n matrix[l - 1][index].gain.value = gainValue;\n}\n\n\n/**\n * This is a helper function to allow us to access a matrix array in the same\n * manner, assuming it is a (2l+1) x (2l+1) matrix.\n * @param {Number[]} matrix - N matrices of gainNodes, each with (2n+1) x (2n+1)\n * elements, where n=1,2,...,N.\n * @param {Number} l\n * @param {Number} i\n * @param {Number} j\n * @return {Number}\n */\nfunction getCenteredElement(matrix, l, i, j) {\n // Row-wise indexing.\n const index = (j + l) * (2 * l + 1) + (i + l);\n return matrix[l - 1][index].gain.value;\n}\n\n\n/**\n * Helper function defined in [2] that is used by the functions U, V, W.\n * This should not be called on its own, as U, V, and W (and their coefficients)\n * select the appropriate matrix elements to access arguments |a| and |b|.\n * @param {Number[]} matrix - N matrices of gainNodes, each with (2n+1) x (2n+1)\n * elements, where n=1,2,...,N.\n * @param {Number} i\n * @param {Number} a\n * @param {Number} b\n * @param {Number} l\n * @return {Number}\n */\nfunction getP(matrix, i, a, b, l) {\n if (b === l) {\n return getCenteredElement(matrix, 1, i, 1) *\n getCenteredElement(matrix, l - 1, a, l - 1) -\n getCenteredElement(matrix, 1, i, -1) *\n getCenteredElement(matrix, l - 1, a, -l + 1);\n } else if (b === -l) {\n return getCenteredElement(matrix, 1, i, 1) *\n getCenteredElement(matrix, l - 1, a, -l + 1) +\n getCenteredElement(matrix, 1, i, -1) *\n getCenteredElement(matrix, l - 1, a, l - 1);\n } else {\n return getCenteredElement(matrix, 1, i, 0) *\n getCenteredElement(matrix, l - 1, a, b);\n }\n}\n\n\n/**\n * The functions U, V, and W should only be called if the correspondingly\n * named coefficient u, v, w from the function ComputeUVWCoeff() is non-zero.\n * When the coefficient is 0, these would attempt to access matrix elements that\n * are out of bounds. The vector of rotations, |r|, must have the |l - 1|\n * previously completed band rotations. These functions are valid for |l >= 2|.\n * @param {Number[]} matrix - N matrices of gainNodes, each with (2n+1) x (2n+1)\n * elements, where n=1,2,...,N.\n * @param {Number} m\n * @param {Number} n\n * @param {Number} l\n * @return {Number}\n */\nfunction getU(matrix, m, n, l) {\n // Although [1, 2] split U into three cases for m == 0, m < 0, m > 0\n // the actual values are the same for all three cases.\n return getP(matrix, 0, m, n, l);\n}\n\n\n/**\n * The functions U, V, and W should only be called if the correspondingly\n * named coefficient u, v, w from the function ComputeUVWCoeff() is non-zero.\n * When the coefficient is 0, these would attempt to access matrix elements that\n * are out of bounds. The vector of rotations, |r|, must have the |l - 1|\n * previously completed band rotations. These functions are valid for |l >= 2|.\n * @param {Number[]} matrix - N matrices of gainNodes, each with (2n+1) x (2n+1)\n * elements, where n=1,2,...,N.\n * @param {Number} m\n * @param {Number} n\n * @param {Number} l\n * @return {Number}\n */\nfunction getV(matrix, m, n, l) {\n if (m === 0) {\n return getP(matrix, 1, 1, n, l) + getP(matrix, -1, -1, n, l);\n } else if (m > 0) {\n const d = getKroneckerDelta(m, 1);\n return getP(matrix, 1, m - 1, n, l) * Math.sqrt(1 + d) -\n getP(matrix, -1, -m + 1, n, l) * (1 - d);\n } else {\n // Note there is apparent errata in [1,2,2b] dealing with this particular\n // case. [2b] writes it should be P*(1-d)+P*(1-d)^0.5\n // [1] writes it as P*(1+d)+P*(1-d)^0.5, but going through the math by hand,\n // you must have it as P*(1-d)+P*(1+d)^0.5 to form a 2^.5 term, which\n // parallels the case where m > 0.\n const d = getKroneckerDelta(m, -1);\n return getP(matrix, 1, m + 1, n, l) * (1 - d) +\n getP(matrix, -1, -m - 1, n, l) * Math.sqrt(1 + d);\n }\n}\n\n\n/**\n * The functions U, V, and W should only be called if the correspondingly\n * named coefficient u, v, w from the function ComputeUVWCoeff() is non-zero.\n * When the coefficient is 0, these would attempt to access matrix elements that\n * are out of bounds. The vector of rotations, |r|, must have the |l - 1|\n * previously completed band rotations. These functions are valid for |l >= 2|.\n * @param {Number[]} matrix N matrices of gainNodes, each with (2n+1) x (2n+1)\n * elements, where n=1,2,...,N.\n * @param {Number} m\n * @param {Number} n\n * @param {Number} l\n * @return {Number}\n */\nfunction getW(matrix, m, n, l) {\n // Whenever this happens, w is also 0 so W can be anything.\n if (m === 0) {\n return 0;\n }\n\n return m > 0 ? getP(matrix, 1, m + 1, n, l) + getP(matrix, -1, -m - 1, n, l) :\n getP(matrix, 1, m - 1, n, l) - getP(matrix, -1, -m + 1, n, l);\n}\n\n\n/**\n * Calculates the coefficients applied to the U, V, and W functions. Because\n * their equations share many common terms they are computed simultaneously.\n * @param {Number} m\n * @param {Number} n\n * @param {Number} l\n * @return {Array} 3 coefficients for U, V and W functions.\n */\nfunction computeUVWCoeff(m, n, l) {\n const d = getKroneckerDelta(m, 0);\n const reciprocalDenominator =\n Math.abs(n) === l ? 1 / (2 * l * (2 * l - 1)) : 1 / ((l + n) * (l - n));\n\n return [\n Math.sqrt((l + m) * (l - m) * reciprocalDenominator),\n 0.5 * (1 - 2 * d) * Math.sqrt((1 + d) *\n (l + Math.abs(m) - 1) *\n (l + Math.abs(m)) *\n reciprocalDenominator),\n -0.5 * (1 - d) * Math.sqrt((l - Math.abs(m) - 1) * (l - Math.abs(m))) *\n reciprocalDenominator,\n ];\n}\n\n\n/**\n * Calculates the (2l+1) x (2l+1) rotation matrix for the band l.\n * This uses the matrices computed for band 1 and band l-1 to compute the\n * matrix for band l. |rotations| must contain the previously computed l-1\n * rotation matrices.\n * This implementation comes from p. 5 (6346), Table 1 and 2 in [2] taking\n * into account the corrections from [2b].\n * @param {Number[]} matrix - N matrices of gainNodes, each with where\n * n=1,2,...,N.\n * @param {Number} l\n */\nfunction computeBandRotation(matrix, l) {\n // The lth band rotation matrix has rows and columns equal to the number of\n // coefficients within that band (-l <= m <= l implies 2l + 1 coefficients).\n for (let m = -l; m <= l; m++) {\n for (let n = -l; n <= l; n++) {\n const uvwCoefficients = computeUVWCoeff(m, n, l);\n\n // The functions U, V, W are only safe to call if the coefficients\n // u, v, w are not zero.\n if (Math.abs(uvwCoefficients[0]) > 0) {\n uvwCoefficients[0] *= getU(matrix, m, n, l);\n }\n if (Math.abs(uvwCoefficients[1]) > 0) {\n uvwCoefficients[1] *= getV(matrix, m, n, l);\n }\n if (Math.abs(uvwCoefficients[2]) > 0) {\n uvwCoefficients[2] *= getW(matrix, m, n, l);\n }\n\n setCenteredElement(\n matrix, l, m, n,\n uvwCoefficients[0] + uvwCoefficients[1] + uvwCoefficients[2]);\n }\n }\n}\n\n\n/**\n * Compute the HOA rotation matrix after setting the transform matrix.\n * @param {Array} matrix - N matrices of gainNodes, each with (2n+1) x (2n+1)\n * elements, where n=1,2,...,N.\n */\nfunction computeHOAMatrices(matrix) {\n // We start by computing the 2nd-order matrix from the 1st-order matrix.\n for (let i = 2; i <= matrix.length; i++) {\n computeBandRotation(matrix, i);\n }\n}\n\n\n/**\n * Higher-order-ambisonic decoder based on gain node network. We expect\n * the order of the channels to conform to ACN ordering. Below are the helper\n * methods to compute SH rotation using recursion. The code uses maths described\n * in the following papers:\n * [1] R. Green, "Spherical Harmonic Lighting: The Gritty Details", GDC 2003,\n * http://www.research.scea.com/gdc2003/spherical-harmonic-lighting.pdf\n * [2] J. Ivanic and K. Ruedenberg, "Rotation Matrices for Real\n * Spherical Harmonics. Direct Determination by Recursion", J. Phys.\n * Chem., vol. 100, no. 15, pp. 6342-6347, 1996.\n * http://pubs.acs.org/doi/pdf/10.1021/jp953350u\n * [2b] Corrections to initial publication:\n * http://pubs.acs.org/doi/pdf/10.1021/jp9833350\n * @constructor\n * @param {AudioContext} context - Associated AudioContext.\n * @param {Number} ambisonicOrder - Ambisonic order.\n */\nfunction HOARotator(context, ambisonicOrder) {\n this._context = context;\n this._ambisonicOrder = ambisonicOrder;\n\n // We need to determine the number of channels K based on the ambisonic order\n // N where K = (N + 1)^2.\n const numberOfChannels = (ambisonicOrder + 1) * (ambisonicOrder + 1);\n\n this._splitter = this._context.createChannelSplitter(numberOfChannels);\n this._merger = this._context.createChannelMerger(numberOfChannels);\n\n // Create a set of per-order rotation matrices using gain nodes.\n this._gainNodeMatrix = [];\n let orderOffset;\n let rows;\n let inputIndex;\n let outputIndex;\n let matrixIndex;\n for (let i = 1; i <= ambisonicOrder; i++) {\n // Each ambisonic order requires a separate (2l + 1) x (2l + 1) rotation\n // matrix. We compute the offset value as the first channel index of the\n // current order where\n // k_last = l^2 + l + m,\n // and m = -l\n // k_last = l^2\n orderOffset = i * i;\n\n // Uses row-major indexing.\n rows = (2 * i + 1);\n\n this._gainNodeMatrix[i - 1] = [];\n for (let j = 0; j < rows; j++) {\n inputIndex = orderOffset + j;\n for (let k = 0; k < rows; k++) {\n outputIndex = orderOffset + k;\n matrixIndex = j * rows + k;\n this._gainNodeMatrix[i - 1][matrixIndex] = this._context.createGain();\n this._splitter.connect(\n this._gainNodeMatrix[i - 1][matrixIndex], inputIndex);\n this._gainNodeMatrix[i - 1][matrixIndex].connect(\n this._merger, 0, outputIndex);\n }\n }\n }\n\n // W-channel is not involved in rotation, skip straight to ouput.\n this._splitter.connect(this._merger, 0, 0);\n\n // Default Identity matrix.\n this.setRotationMatrix3(new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]));\n\n // Input/Output proxy.\n this.input = this._splitter;\n this.output = this._merger;\n}\n\n\n/**\n * Updates the rotation matrix with 3x3 matrix.\n * @param {Number[]} rotationMatrix3 - A 3x3 rotation matrix. (column-major)\n */\nHOARotator.prototype.setRotationMatrix3 = function(rotationMatrix3) {\n this._gainNodeMatrix[0][0].gain.value = -rotationMatrix3[0];\n this._gainNodeMatrix[0][1].gain.value = rotationMatrix3[1];\n this._gainNodeMatrix[0][2].gain.value = -rotationMatrix3[2];\n this._gainNodeMatrix[0][3].gain.value = -rotationMatrix3[3];\n this._gainNodeMatrix[0][4].gain.value = rotationMatrix3[4];\n this._gainNodeMatrix[0][5].gain.value = -rotationMatrix3[5];\n this._gainNodeMatrix[0][6].gain.value = -rotationMatrix3[6];\n this._gainNodeMatrix[0][7].gain.value = rotationMatrix3[7];\n this._gainNodeMatrix[0][8].gain.value = -rotationMatrix3[8];\n computeHOAMatrices(this._gainNodeMatrix);\n};\n\n\n/**\n * Updates the rotation matrix with 4x4 matrix.\n * @param {Number[]} rotationMatrix4 - A 4x4 rotation matrix. (column-major)\n */\nHOARotator.prototype.setRotationMatrix4 = function(rotationMatrix4) {\n this._gainNodeMatrix[0][0].gain.value = -rotationMatrix4[0];\n this._gainNodeMatrix[0][1].gain.value = rotationMatrix4[1];\n this._gainNodeMatrix[0][2].gain.value = -rotationMatrix4[2];\n this._gainNodeMatrix[0][3].gain.value = -rotationMatrix4[4];\n this._gainNodeMatrix[0][4].gain.value = rotationMatrix4[5];\n this._gainNodeMatrix[0][5].gain.value = -rotationMatrix4[6];\n this._gainNodeMatrix[0][6].gain.value = -rotationMatrix4[8];\n this._gainNodeMatrix[0][7].gain.value = rotationMatrix4[9];\n this._gainNodeMatrix[0][8].gain.value = -rotationMatrix4[10];\n computeHOAMatrices(this._gainNodeMatrix);\n};\n\n\n/**\n * Returns the current 3x3 rotation matrix.\n * @return {Number[]} - A 3x3 rotation matrix. (column-major)\n */\nHOARotator.prototype.getRotationMatrix3 = function() {\n let rotationMatrix3 = new Float32Array(9);\n rotationMatrix3[0] = -this._gainNodeMatrix[0][0].gain.value;\n rotationMatrix3[1] = this._gainNodeMatrix[0][1].gain.value;\n rotationMatrix3[2] = -this._gainNodeMatrix[0][2].gain.value;\n rotationMatrix3[4] = -this._gainNodeMatrix[0][3].gain.value;\n rotationMatrix3[5] = this._gainNodeMatrix[0][4].gain.value;\n rotationMatrix3[6] = -this._gainNodeMatrix[0][5].gain.value;\n rotationMatrix3[8] = -this._gainNodeMatrix[0][6].gain.value;\n rotationMatrix3[9] = this._gainNodeMatrix[0][7].gain.value;\n rotationMatrix3[10] = -this._gainNodeMatrix[0][8].gain.value;\n return rotationMatrix3;\n};\n\n\n/**\n * Returns the current 4x4 rotation matrix.\n * @return {Number[]} - A 4x4 rotation matrix. (column-major)\n */\nHOARotator.prototype.getRotationMatrix4 = function() {\n let rotationMatrix4 = new Float32Array(16);\n rotationMatrix4[0] = -this._gainNodeMatrix[0][0].gain.value;\n rotationMatrix4[1] = this._gainNodeMatrix[0][1].gain.value;\n rotationMatrix4[2] = -this._gainNodeMatrix[0][2].gain.value;\n rotationMatrix4[4] = -this._gainNodeMatrix[0][3].gain.value;\n rotationMatrix4[5] = this._gainNodeMatrix[0][4].gain.value;\n rotationMatrix4[6] = -this._gainNodeMatrix[0][5].gain.value;\n rotationMatrix4[8] = -this._gainNodeMatrix[0][6].gain.value;\n rotationMatrix4[9] = this._gainNodeMatrix[0][7].gain.value;\n rotationMatrix4[10] = -this._gainNodeMatrix[0][8].gain.value;\n return rotationMatrix4;\n};\n\n\n/**\n * Get the current ambisonic order.\n * @return {Number}\n */\nHOARotator.prototype.getAmbisonicOrder = function() {\n return this._ambisonicOrder;\n};\n\n\nmodule.exports = HOARotator;\n\n\n//# sourceURL=webpack:///./src/hoa-rotator.js?'); + }, + "./src/main.js": function(module, exports, __webpack_require__) { + "use strict"; + eval('/**\n * @license\n * Copyright 2016 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the "License");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an "AS IS" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @file Namespace for Omnitone library.\n */\n\n\n\n\nexports.Omnitone = __webpack_require__(/*! ./omnitone.js */ "./src/omnitone.js");\n\n\n//# sourceURL=webpack:///./src/main.js?'); + }, + "./src/omnitone.js": function(module, exports, __webpack_require__) { + "use strict"; + eval('/**\n * @license\n * Copyright 2016 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the "License");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an "AS IS" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @file Omnitone library name space and user-facing APIs.\n */\n\n\n\n\nconst BufferList = __webpack_require__(/*! ./buffer-list.js */ "./src/buffer-list.js");\nconst FOAConvolver = __webpack_require__(/*! ./foa-convolver.js */ "./src/foa-convolver.js");\nconst FOARenderer = __webpack_require__(/*! ./foa-renderer.js */ "./src/foa-renderer.js");\nconst FOARotator = __webpack_require__(/*! ./foa-rotator.js */ "./src/foa-rotator.js");\nconst FOARouter = __webpack_require__(/*! ./foa-router.js */ "./src/foa-router.js");\nconst HOAConvolver = __webpack_require__(/*! ./hoa-convolver.js */ "./src/hoa-convolver.js");\nconst HOARenderer = __webpack_require__(/*! ./hoa-renderer.js */ "./src/hoa-renderer.js");\nconst HOARotator = __webpack_require__(/*! ./hoa-rotator.js */ "./src/hoa-rotator.js");\nconst Polyfill = __webpack_require__(/*! ./polyfill.js */ "./src/polyfill.js");\nconst Utils = __webpack_require__(/*! ./utils.js */ "./src/utils.js");\nconst Version = __webpack_require__(/*! ./version.js */ "./src/version.js");\n\n\n/**\n * Omnitone namespace.\n * @namespace\n */\nlet Omnitone = {};\n\n\n/**\n * @typedef {Object} BrowserInfo\n * @property {string} name - Browser name.\n * @property {string} version - Browser version.\n */\n\n/**\n * An object contains the detected browser name and version.\n * @memberOf Omnitone\n * @static {BrowserInfo}\n */\nOmnitone.browserInfo = Polyfill.getBrowserInfo();\n\n\n/**\n * Performs the async loading/decoding of multiple AudioBuffers from multiple\n * URLs.\n * @param {BaseAudioContext} context - Associated BaseAudioContext.\n * @param {string[]} bufferData - An ordered list of URLs.\n * @param {Object} [options] - BufferList options.\n * @param {String} [options.dataType=\'url\'] - BufferList data type.\n * @return {Promise} - The promise resolves with an array of\n * AudioBuffer.\n */\nOmnitone.createBufferList = function(context, bufferData, options) {\n const bufferList =\n new BufferList(context, bufferData, options || {dataType: \'url\'});\n return bufferList.load();\n};\n\n\n/**\n * Perform channel-wise merge on multiple AudioBuffers. The sample rate and\n * the length of buffers to be merged must be identical.\n * @static\n * @function\n * @param {BaseAudioContext} context - Associated BaseAudioContext.\n * @param {AudioBuffer[]} bufferList - An array of AudioBuffers to be merged\n * channel-wise.\n * @return {AudioBuffer} - A single merged AudioBuffer.\n */\nOmnitone.mergeBufferListByChannel = Utils.mergeBufferListByChannel;\n\n\n/**\n * Perform channel-wise split by the given channel count. For example,\n * 1 x AudioBuffer(8) -> splitBuffer(context, buffer, 2) -> 4 x AudioBuffer(2).\n * @static\n * @function\n * @param {BaseAudioContext} context - Associated BaseAudioContext.\n * @param {AudioBuffer} audioBuffer - An AudioBuffer to be splitted.\n * @param {Number} splitBy - Number of channels to be splitted.\n * @return {AudioBuffer[]} - An array of splitted AudioBuffers.\n */\nOmnitone.splitBufferbyChannel = Utils.splitBufferbyChannel;\n\n\n/**\n * Creates an instance of FOA Convolver.\n * @see FOAConvolver\n * @param {BaseAudioContext} context The associated AudioContext.\n * @param {AudioBuffer[]} [hrirBufferList] - An ordered-list of stereo\n * @return {FOAConvolver}\n */\nOmnitone.createFOAConvolver = function(context, hrirBufferList) {\n return new FOAConvolver(context, hrirBufferList);\n};\n\n\n/**\n * Create an instance of FOA Router.\n * @see FOARouter\n * @param {AudioContext} context - Associated AudioContext.\n * @param {Number[]} channelMap - Routing destination array.\n * @return {FOARouter}\n */\nOmnitone.createFOARouter = function(context, channelMap) {\n return new FOARouter(context, channelMap);\n};\n\n\n/**\n * Create an instance of FOA Rotator.\n * @see FOARotator\n * @param {AudioContext} context - Associated AudioContext.\n * @return {FOARotator}\n */\nOmnitone.createFOARotator = function(context) {\n return new FOARotator(context);\n};\n\n\n/**\n * Creates HOARotator for higher-order ambisonics rotation.\n * @param {AudioContext} context - Associated AudioContext.\n * @param {Number} ambisonicOrder - Ambisonic order.\n * @return {HOARotator}\n */\nOmnitone.createHOARotator = function(context, ambisonicOrder) {\n return new HOARotator(context, ambisonicOrder);\n};\n\n\n/**\n * Creates HOAConvolver performs the multi-channel convolution for the optmized\n * binaural rendering.\n * @param {AudioContext} context - Associated AudioContext.\n * @param {Number} ambisonicOrder - Ambisonic order. (2 or 3)\n * @param {AudioBuffer[]} [hrirBufferList] - An ordered-list of stereo\n * AudioBuffers for convolution. (SOA: 5 AudioBuffers, TOA: 8 AudioBuffers)\n * @return {HOAConvovler}\n */\nOmnitone.createHOAConvolver = function(\n context, ambisonicOrder, hrirBufferList) {\n return new HOAConvolver(context, ambisonicOrder, hrirBufferList);\n};\n\n\n/**\n * Create a FOARenderer, the first-order ambisonic decoder and the optimized\n * binaural renderer.\n * @param {AudioContext} context - Associated AudioContext.\n * @param {Object} config\n * @param {Array} [config.channelMap] - Custom channel routing map. Useful for\n * handling the inconsistency in browser\'s multichannel audio decoding.\n * @param {Array} [config.hrirPathList] - A list of paths to HRIR files. It\n * overrides the internal HRIR list if given.\n * @param {RenderingMode} [config.renderingMode=\'ambisonic\'] - Rendering mode.\n * @return {FOARenderer}\n */\nOmnitone.createFOARenderer = function(context, config) {\n return new FOARenderer(context, config);\n};\n\n\n/**\n * Creates HOARenderer for higher-order ambisonic decoding and the optimized\n * binaural rendering.\n * @param {AudioContext} context - Associated AudioContext.\n * @param {Object} config\n * @param {Number} [config.ambisonicOrder=3] - Ambisonic order.\n * @param {Array} [config.hrirPathList] - A list of paths to HRIR files. It\n * overrides the internal HRIR list if given.\n * @param {RenderingMode} [config.renderingMode=\'ambisonic\'] - Rendering mode.\n * @return {HOARenderer}\n */\nOmnitone.createHOARenderer = function(context, config) {\n return new HOARenderer(context, config);\n};\n\n\n// Handle Pre-load Tasks: detects the browser information and prints out the\n// version number. If the browser is Safari, patch prefixed interfaces.\n(function() {\n Utils.log(\'Version \' + Version + \' (running \' +\n Omnitone.browserInfo.name + \' \' + Omnitone.browserInfo.version +\n \' on \' + Omnitone.browserInfo.platform +\')\');\n if (Omnitone.browserInfo.name.toLowerCase() === \'safari\') {\n Polyfill.patchSafari();\n Utils.log(Omnitone.browserInfo.name + \' detected. Polyfill applied.\');\n }\n})();\n\n\nmodule.exports = Omnitone;\n\n\n//# sourceURL=webpack:///./src/omnitone.js?'); + }, + "./src/polyfill.js": function(module, exports, __webpack_require__) { + "use strict"; + eval("/**\n * @license\n * Copyright 2017 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @file Cross-browser support polyfill for Omnitone library.\n */\n\n\n\n\n/**\n * Detects browser type and version.\n * @return {string[]} - An array contains the detected browser name and version.\n */\nexports.getBrowserInfo = function() {\n const ua = navigator.userAgent;\n let M = ua.match(\n /(opera|chrome|safari|firefox|msie|trident(?=\\/))\\/?\\s*([\\d\\.]+)/i) ||\n [];\n let tem;\n\n if (/trident/i.test(M[1])) {\n tem = /\\brv[ :]+(\\d+)/g.exec(ua) || [];\n return {name: 'IE', version: (tem[1] || '')};\n }\n\n if (M[1] === 'Chrome') {\n tem = ua.match(/\\bOPR|Edge\\/(\\d+)/);\n if (tem != null) {\n return {name: 'Opera', version: tem[1]};\n }\n }\n\n M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?'];\n if ((tem = ua.match(/version\\/([\\d.]+)/i)) != null) {\n M.splice(1, 1, tem[1]);\n }\n\n let platform = ua.match(/android|ipad|iphone/i);\n if (!platform) {\n platform = ua.match(/cros|linux|mac os x|windows/i);\n }\n\n return {\n name: M[0],\n version: M[1],\n platform: platform ? platform[0] : 'unknown',\n };\n};\n\n\n/**\n * Patches AudioContext if the prefixed API is found.\n */\nexports.patchSafari = function() {\n if (window.webkitAudioContext && window.webkitOfflineAudioContext) {\n window.AudioContext = window.webkitAudioContext;\n window.OfflineAudioContext = window.webkitOfflineAudioContext;\n }\n};\n\n\n//# sourceURL=webpack:///./src/polyfill.js?"); + }, + "./src/resources/omnitone-foa-hrir-base64.js": function(module, exports) { + eval('const OmnitoneFOAHrirBase64 = [\n"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",\n];\n\nmodule.exports = OmnitoneFOAHrirBase64;\n\n\n//# sourceURL=webpack:///./src/resources/omnitone-foa-hrir-base64.js?'); + }, + "./src/resources/omnitone-soa-hrir-base64.js": function(module, exports) { + eval('const OmnitoneSOAHrirBase64 = [\n"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/P////v//P8CAAQA+//+//3//f8CAAUA+v/9//////8AAAQA+v/8////AAD//wIA+//8/wAAAQD+/wEA+//8/wAAAgD9/////P/9/wEAAgD8//7//f/9/wAAAgD8//3//v/+////AQD8//z/////////AAD8//3///8AAP7/AAD9//7///8AAP7////+//////8AAP7////+////////////////////",\n"UklGRiQEAABXQVZFZm10IBAAAAABAAIAgLsAAADuAgAEABAAZGF0YQAEAAD//////v8AAP///////wAAAAAAAP7/AQABAAAABwD///X/BQAjAPL/CQDb/9D/GAAb/7sAYwCW/z0BcP/X/7T/2QDW+wH8yANCCCUJ5QT++UXmhPwhA78FuAxH+p78ifudBlAG9vmu/lAK2fdlB///cfjoCa0E7Akn9Yb/zvba+AkAHPywBGEBFwUNAL8AXAAGA20DFvmR/kz+F/06Ag/+GwHl/5EEKgJd/q0AP/ym/9n6EfxY/2H+/QFtAC4C6QBDAaMCo/20/+3/3f/p/fL9rv9V/6cBhQHuAX4AcwJYAaH/IP/P/gsApP0LAe7/sQBuAI0AAgGDAE4BzACe/5X//v+v/+f+Zf+gAOv/5QBhAOIApAANASYAuP+h/8b/HQBr/9//bACWAGEAFAB5AD0AWQDU/+D/Yf/p//D/s/+R/4QAMQBvABEAkQBfABQAJgDW/wwA8/8XALz/vf8zAFAAKwD1/zEAPwDJ/x0A7/8LAOX/FwDR//H/EQAdAO//6P8QAFEA2f8WABEAMgDy/xIA+f/s/xAALgDv////HQAvAPT/+f8iAAYAEgAFABoAGgD//w0A+f/0/xsAHgDx/9f/GAACAPH/8f8JAPf/GwALABEA7/8cAPT/CgD2//j/BQD8/+3/OgAgAAYA9f8PAN7/DgD9/9r/1//3/+3/9//1//b/8//5//f/AgAJAOf/+v8OAAMACwD9/+7/5f8eAAEA9//q//7/8P8WAP7/+//4/wIA+f8TAAIA9f/5/wcA+P8iAAgA9v/n/xoA//8gAAUABwDj/wAA9v8BAAUAFQDn/wMA7v8QABAAEQDm/wwA8f8aAAAABwDu/wcACgASAAEA7//w//f/BgARAAkA6P/3/wcADgAKAAYA4f/4/wYADgAAAPr/8P/9/xQACgAHAPn/7//9/xEAAgD+//L/8v/8/xUAAwDw//H/9f8CAAsA/v/q//L/+f8FAAYA/P/r//j///8GAAkA+//o//j/AQAIAP//+v/o//v/CAAIAPv/+P/w/wEACQAHAPj/+f/0/wIACwAFAPb/+f/4/wQACwACAPP/+f/+/wYACAD///L/+/8BAAYABQD9//P//P8FAAUAAgD7//T//f8HAAQA///7//f///8IAAMA/P/6//r/AQAIAAEA+v/6//3/AgAHAAAA+f/7/wAAAwAFAP7/+P/8/wIAAgACAP3/+f/9/wMAAwAAAPz/+v/+/wQAAgD+//z/+/8AAAQAAQD8//z//f8BAAQAAAD7//3///8BAAMA///7//3/AAACAAEA/v/7//7/AQABAAAA/v/9////AQAAAP///v/+////AAD/////////////////////////////",\n"UklGRiQEAABXQVZFZm10IBAAAAABAAIAgLsAAADuAgAEABAAZGF0YQAEAAD////////+//////8AAAAA/v/+/wAAAQD8//3/CQAJAP3/+v8PAAcApABlABkBkwCO/i//lfqa/HQAcf/3BdkCzwJcBCMC0wMN/9/9wgI7AaECYfxV/Tf83vhn/xrt8Owx/8n7cgHABYb43QcZDh4WugNrA7P74gHu/9z/zv0t/acCiQHY/iv4qQOl/ysCE/0//XT9Sf4O//j9xfupAn394gHO+rsCXAFIAxQC9wIXBgcD2AQuAnb/9gJh/6wAVfxEAI4Bvf7oAFv/bALsAMQBe/88/joAT/4dAH39/v9LAXn/gwDI//QBdABcAA0A7f4lAMn///+9/tv/iABp/13/pP/dALv/w/8MAHv//f+y/6////7U/5AAZP+Z/8r/nQDR/5r/DwDr/xAA4v+s/3z/+P9uAOv/t/82AGcAHgCb/yQAFQBGAM7/CgD3/xoAegAaAOz/CgBHAA8Adv8/AAAABQC2/xIAAAA7ABQAKgCj/z4AAQAXAJz/JAADAAcA8f/1/2AAAQAlAPD/NgDx/1wA7v/4/wMAZADv//3/HQAkAFoA8P9FAPv/FgBIAPf/WQAHAEUACQD0/xIAQwDu/wMAwP9VALn/XwCw/yEA5f8sAPj/FgDD/1YAyv8rAOX/HQDo//j/IQAQACAAHwD9/yQAHQBAABgABQAiAAUAKAD3/wkACwAKAAMABwAJAPb/+f8GAOr/JQAHABMA6P8TAA4AGgD//woA8/8ZAP//GADu/w0A9v8SAAMABwD4/wQA5P8XAAQACgDq/wUA+/8VAAcACADs/xIAAAATAPH/+v/1//T/7f///+z/+v/y/+//9/8KAAcACgAJAPT/BAAKAAAABgAIAPL/9v8KAAMABAACAPr/9v8OAAIA+P/x//v/+f8MAPb/+P/w/wQA9f8MAPn////7/woA/v8PAAEAAgD1/xAAAQAPAP//AwD//xQABwALAAAABgADABAAAgAHAAAACAABAA8ABQAFAAMABwAEAA4ABwADAAEACQAFAAoAAwD//wAACQADAAUAAQD/////CAABAAMAAAD/////BwACAAEAAAD/////BwACAP7///8BAAAABgABAP7///8CAAAABAAAAP7///8DAAAAAwAAAP3///8DAAAAAQAAAP3//v8EAAAAAAD+//////8EAP/////+/wAA/v8EAP/////+/wEA/v8EAP///v/+/wIA//8DAP///v/+/wIA//8BAP///v/+/wMA//8BAP/////+/wMA//8AAP//AAD+/wQA//8AAP7/AQD//wIA////////AQD//wIA////////AQAAAAEAAAAAAP//AQD//wEAAAAAAP//AQAAAAEAAAAAAAAA",\n"UklGRiQEAABXQVZFZm10IBAAAAABAAIAgLsAAADuAgAEABAAZGF0YQAEAAD+/wAA+v8AAPz/AAD//wAA/f8AAAEAAAD+/wAACQAAAAQAAAAZAAAAtgAAAFsBAABW/gAAH/oAAGcBAABoBwAAlAAAAO3/AAARAQAA+wIAAEoEAACe/gAAiv4AALD0AADJ8wAAkQQAAF34AABi8QAAPQAAAAH2AAD19AAADAMAAJwGAACTEAAA0AwAAJkHAACOBwAAuQEAANcDAAC6AgAAHwUAAHEFAAB0AwAAbgEAADz+AADYAQAAGAAAAJwCAADgAAAA//0AAMn+AAAT/AAAwP8AAOn9AAAJAAAAewEAAOn+AACN/wAAOv0AAO3+AADN/gAAcP8AACj/AACq/gAA+f4AAML9AACa/wAA/f4AAN7/AABo/wAA6/4AAE//AAAC/wAAEQAAAHX/AAB0AAAA5f8AAEwAAAB3AAAA5/8AAMIAAABCAAAAzgAAAE8AAAB3AAAAKAAAADMAAACqAAAALwAAAK4AAAASAAAAVgAAACgAAAAtAAAATAAAAP3/AAA7AAAA2/8AACQAAADw/wAALQAAADEAAAAlAAAAbAAAADMAAABUAAAAEAAAACgAAAD1/wAA9v8AAPr/AADu/wAALgAAABIAAABUAAAARAAAAGUAAABGAAAAOAAAAGAAAAAuAAAARQAAACEAAAAfAAAAAAAAAAkAAAAQAAAAAwAAABIAAADs/wAAEAAAAAYAAAASAAAAIgAAABEAAAADAAAABAAAAA8AAAD4/wAAHQAAAAsAAAAIAAAADgAAAP//AAAcAAAADwAAAAYAAAASAAAAFwAAAAMAAAAYAAAAEgAAAPr/AAAQAAAADQAAAAoAAAD3/wAABgAAAPb/AADf/wAA/v8AAPL/AAD6/wAAFAAAAAQAAAAEAAAAGwAAAAEAAAAMAAAAIAAAAAIAAAAdAAAAGAAAAAIAAAAcAAAAEgAAAAcAAAAeAAAADwAAAAQAAAAeAAAABAAAAAYAAAAZAAAAAQAAAA4AAAATAAAA/v8AAAoAAAAOAAAA+/8AAAsAAAAJAAAA+f8AAAsAAAABAAAA+f8AAAoAAAD9/wAA+v8AAAcAAAD5/wAA+v8AAAUAAAD3/wAA/f8AAAQAAAD2/wAAAAAAAAEAAAD3/wAAAgAAAAAAAAD4/wAAAwAAAP7/AAD6/wAABAAAAP3/AAD8/wAABAAAAPv/AAD+/wAAAwAAAPv/AAD//wAAAQAAAPv/AAAAAAAAAAAAAPv/AAACAAAA//8AAPz/AAACAAAA/v8AAP3/AAACAAAA/f8AAP7/AAABAAAA/f8AAP//AAABAAAA/f8AAAAAAAAAAAAA/v8AAAEAAAAAAAAA//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA",\n];\n\nmodule.exports = OmnitoneSOAHrirBase64;\n\n\n//# sourceURL=webpack:///./src/resources/omnitone-soa-hrir-base64.js?'); + }, + "./src/resources/omnitone-toa-hrir-base64.js": function(module, exports) { + eval('const OmnitoneTOAHrirBase64 = [\n"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AAD7/wEAAgD7//7/AQD8/wAAAwD8//3/AwD9/wAAAgD9//z/AwD/////AgD+//z/AwAAAP7/AQD///3/AgABAP3/AAAAAP3/AgACAPz///8BAP3/AQACAP3//v8BAP7/AAABAP3//v8CAP7///8BAP7//f8CAP////8AAAAA/v8CAAAAAAAAAAAA/v8BAAAAAAD//wAA//8AAP//AAD//wAA//8AAP//",\n"UklGRiQEAABXQVZFZm10IBAAAAABAAIAgLsAAADuAgAEABAAZGF0YQAEAAAAAP//AAD//wAA//8AAAAA/////wAAAQD+////AAAGAP3/OAABAIIAAwBv//f/E/0QAK0ADQCzA/7/8P4u/0cBDQCJA6ABbQDg/w7/z/9o+Vn/SPnL/1//Ef+2+jr9RfZgA5QFZwILDFj+PAb2/nEFKgKk/R0Dlv6b/FUDsP6YAoj9SgAT/iL/tAPwAv8A0P6zAr7/dwAnAf39uP22/skA2v///2YCoP4UAUsAZgF2AJH+4P70/rz9+f+U/Xv/8v7CAcb+TACS/kwAv/+x/tX9oP71/oL/1f8nAEUAZwGtAAgAIgC/AD4BaP8GAGH/dQDF/64Arf8nAakAhAH9/+kAQQD3AFb/q/8p/yIAR/8FAPD/ZAA/AIYA3v8tADQADQBp/3f/CwABAP3/Wf8OANj/WwDH/xoAe/8DAKz/zv96/z8A3f/J/5X/IAD5//j/q//c/+//RADq//D/vv8pADUAFQDI/y8ACAAbANb/OwD3/+3/9f/e/wcAIAAeAMH/8/8xAC0AEADW/+3/HAADAPv/8P8DAOL/OwD3/xcACQAHAM//5f8XAAcAz//T/9D/HgD9////yf/e//v/AgD//9H/6/////H/+/8hAAIA9//7/w0AFgAQAPL/2v/8/xsAGQABANz/9P8YAAQA/v/y/wMA5v8YAAkAAAAAAAMA7/8KABgADwDs//j/BwATABsA8P/1//z/BAAMAAAA9P/s/xAA/v8GAAkA/v/p/wMACwALAP7/9P/p/wcADQAFAPb/7//4/w0ACAD8//b//v/1/wMACwD1//T/8P/8/wAACQDz/+f/5P8GAAkABQD5//D/+v8FAA0AAwD///T/AgACABAA/v8CAPD/+/8FAAoA9f/3//f//v8GAP7/9v/t//z/+f8AAPj/+v/3/wEA+v8HAPr//P/5/wQA//8DAPr/+P/3/wYA///+//X/+//5/wQA/f/7//X/+//4/wMA/f/8//j//v/9/wYA///8//f/AgAAAAUA/f/6//n/AwACAAIA/f/7//z/AwACAAAA/f/6//3/AgADAP7//f/7/wAAAwAFAPz////8/wMAAgAEAPv//v/+/wMAAgADAPv//v///wMAAQABAPv//f8AAAIAAAD///v//f8BAAIA///+//z//v8CAAIA/v/9//3///8CAAEA/v/9//7/AAACAAAA/v/9////AAABAAAA/f/9/wAAAQABAP///f/+/wEAAQAAAP///v/+/wEAAQD///7//v///wEAAQD///7//v///wEAAAD+//7///8AAAAAAAD+//7///8AAAAA///+//7///8AAAAA////////AAAAAP////////////8AAP//////////",\n"UklGRiQEAABXQVZFZm10IBAAAAABAAIAgLsAAADuAgAEABAAZGF0YQAEAAAAAAAAAAABAAAAAAD//////////////v////3/////////+//8////AQD9//z/9f8BAAIA+f8dACgAWQBxAJX/qv+Y/uz9aP9k/7UDUQQBAiQA4Pgi/AkB0gKaBsD/+fxp/vz9CQSp/I/+ywDO+vMD0fzK/PABcgBeBfoBv/+uAuH9Sf5gAy39awMmBWUBuP9fA9/9fgDj/2/+EACaACcCSv9Z/2j/rv7hAA0AWf55/7L84P7E/SIAT/67AMv/tf+FAA7/1v+7/gv/IP+E/sQA+P5aAXz/tP9XAFX/tP8o/4r/j//e/yQAMv9mAJT/rgCr/9X/EwCb//H/9f7F/6D/EAAoAK3//v+e/zsAh/+B/7r/if/C/2r/4P/z/6//HwCy/0IA7/9ZALT/y/80ACgA9v/J/9//DgA5ADUALQARADIACwAfAOf/NgArACMACQBBAEcAGAAjAC4AWQBUAHcAAAAfACEAIAAcAPj/CADk/yQA7v89AEEAFwD5/xYA6f8aAOX/AADF/zQADwAUAOT/BQDr/yUA6P8XAOf/HADR/0AA8P8nAAgACQDt/ycAKAAHAPH/IQDz/xsACADn//n/DgADAA4A8P///8z/GgDN/yMA/f8QANj/MwACAC0ACwAOAO3/JgAZAAUACgAAAA4AIgAaAAkADwACAAAAHQATAAUABQACAAgACwAjAO////8AAA8ABQAPAPL//f8GAAsABgAGAPD/8v8GAPz/CAD6//H/6v8PAAgABgD4//3/9v8aAAgABwD1//7//v8QAAoACAD//wUA9v8QAAoABAAFAAgAAgAJAAoAAwD//w0AAgD//wcA/v8DAAoABQAFABUABAAKAAYABwAHAA8ACgAGAAwADwAMAAkAEAAJAAgADwAMAAgADgAJAAUACQAPAAUACwAHAAEABgAIAAEABAAGAP//AgAJAAAAAgAEAP7///8IAAIA//8GAAEAAQAJAAIA/v8EAAMA//8JAAEA/v8DAAMA/v8HAAMA/f8BAAUA/v8FAAMA/v8BAAcA//8DAAMA/v8BAAYA//8CAAMA/////wcAAAAAAAMAAAD//wYAAQD+/wMAAQD//wUAAQD+/wIAAgD//wQAAgD+/wEAAwD//wMAAwD+/wEAAwD//wIAAwD//wEABAAAAAEABAD//wAABAABAAAAAwAAAAAABAABAP//AwABAAAAAwACAP//AgACAAAAAwACAP//AgACAAAAAgACAAAAAQADAAAAAQACAAAAAQADAAAAAQACAAAAAAACAAEAAAACAAEAAAACAAEAAAABAAEAAAABAAEAAAABAAEAAAABAAEAAAABAAEAAAABAAEAAAAAAAAAAAAAAAAA",\n"UklGRiQEAABXQVZFZm10IBAAAAABAAIAgLsAAADuAgAEABAAZGF0YQAEAAAAAP//AAD//wAA//8AAAAA//8AAP//AAACAAAA+f8BAAYA///4/wIA//8AAA8A/v/V/wEAEwA9AAEBRwA2AF7/kfog/3gBwv99CDYBU/qtAUX/AP7OAfkAX/o9B38FSfwaAuT14/60BAr8CQAI/tfyIQTzAXP+egdUBBwBof7TBMT8bAWi/5EEWwBRAAAKyfxE/8b88vp6ACP+PAF4/qD8MQNM/ygCJ/2XAPD9kP5gAVT/iP9I/lEB4P8qAD0BFAGa/+7/DgB2AOP98gFm/u/+Vv5/AG8ASP9gAM//qv9w//oAcv+2/jIBHgA7/6D/oAAGAKH/lADT/wAAggC8AAYAkP9yAEcAkf8BAOD/RAAr/zUANwDt/xQAJQAkAMT/zwA/AOH/xv9zAGsANQBTAIcALAAvACIATACy/xMADADg/xcAWABvAJL/7f9VAPb/EgDt/wcA4f8kAPP/5P+h/wgACQDy//r/LgAQAMn/8/9CAOX/5v/S/9//3P8pABYAuP/s/w8AFgDt/+3/7v/w/9j/5/8GAOf/2P/2//P//v8kABMAuf/m/xoADADZ/+r/3P8KAAUAKwDe/wsA3P8VAAAADgAfAB0ACAAMAF4AGgAhAPL/MwDz/0kABAAKAPX/LwAbAAkA9v/s/+3/8/8CABAAAADm//n/BQALAAUAAQDj//n/JQAVAPX/9v/+/wIAEQABAPP/8P/1/wAABgD6/+3/7//o//j/DAD8/+b/8P8IAAkABgD4//D/8P8UAAoAAwD4/wAA+f8OAAcAAAAFAPX/9v8TAAkA8v8EAPb/9/8dAA0A7/8CAPn/+f8SAAQA8/8CAOf/+v8DAAgA9P////H//P8IAAUA8//0/wIAAQAGAAgA9//7/wAA+/8EAP//+P/+////AgACAAsA8v/+/wIABQD7/wgA9v/7/wMABAD5/wAA/P/3/wEAAQD7//7//P/1/wQA///3//r////3/wMAAwD1//r/AwD6////AgD4//n/AwD8//7/AgD4//n/AwD+//3/AQD4//n/BQD///n/AAD6//j/BAABAPj/AAD9//v/AwADAPj//v/+//z/AwAEAPj//v8BAP7/AQADAPj//f8CAP////8EAPr//P8DAAAA/v8CAPv//P8DAAEA/f8BAP3//f8DAAIA/P8AAP7//f8DAAIA/P///wAA/f8BAAIA+//+/wEA//8AAAEA+//+/wEA/////wEA/P/+/wEA///+/wAA/f/9/wEAAAD9/wAA/f/+/wEAAQD8/////v/+/wAAAQD8////////////AQD9////AAD/////AAD+////AAAAAP//AAD///////8AAP//AAD//wAA//8AAP//",\n];\n\nmodule.exports = OmnitoneTOAHrirBase64;\n\n\n//# sourceURL=webpack:///./src/resources/omnitone-toa-hrir-base64.js?'); + }, + "./src/utils.js": function(module, exports) { + eval("/**\n * @license\n * Copyright 2016 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @file Omnitone library common utilities.\n */\n\n\n/**\n * Omnitone library logging function.\n * @param {any} Message to be printed out.\n */\nexports.log = function() {\n let message = '[Omnitone] ' + Array.prototype.slice.call(arguments).join(' ')\n + ' (' + performance.now().toFixed(2) + 'ms)';\n window.console.log(message);\n};\n\n\n/**\n * Omnitone library error-throwing function.\n * @param {any} Message to be printed out.\n */\nexports.throw = function() {\n let message = '[Omnitone] ' + Array.prototype.slice.call(arguments).join(' ')\n + ' (' + performance.now().toFixed(2) + 'ms)';\n throw new Error(message);\n};\n\n\n// Static temp storage for matrix inversion.\nlet a00;\nlet a01;\nlet a02;\nlet a03;\nlet a10;\nlet a11;\nlet a12;\nlet a13;\nlet a20;\nlet a21;\nlet a22;\nlet a23;\nlet a30;\nlet a31;\nlet a32;\nlet a33;\nlet b00;\nlet b01;\nlet b02;\nlet b03;\nlet b04;\nlet b05;\nlet b06;\nlet b07;\nlet b08;\nlet b09;\nlet b10;\nlet b11;\nlet det;\n\n\n/**\n * A 4x4 matrix inversion utility. This does not handle the case when the\n * arguments are not proper 4x4 matrices.\n * @param {Float32Array} out The inverted result.\n * @param {Float32Array} a The source matrix.\n * @return {Float32Array} out\n */\nexports.invertMatrix4 = function(out, a) {\n a00 = a[0];\n a01 = a[1];\n a02 = a[2];\n a03 = a[3];\n a10 = a[4];\n a11 = a[5];\n a12 = a[6];\n a13 = a[7];\n a20 = a[8];\n a21 = a[9];\n a22 = a[10];\n a23 = a[11];\n a30 = a[12];\n a31 = a[13];\n a32 = a[14];\n a33 = a[15];\n b00 = a00 * a11 - a01 * a10;\n b01 = a00 * a12 - a02 * a10;\n b02 = a00 * a13 - a03 * a10;\n b03 = a01 * a12 - a02 * a11;\n b04 = a01 * a13 - a03 * a11;\n b05 = a02 * a13 - a03 * a12;\n b06 = a20 * a31 - a21 * a30;\n b07 = a20 * a32 - a22 * a30;\n b08 = a20 * a33 - a23 * a30;\n b09 = a21 * a32 - a22 * a31;\n b10 = a21 * a33 - a23 * a31;\n b11 = a22 * a33 - a23 * a32;\n det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;\n\n if (!det) {\n return null;\n }\n\n det = 1.0 / det;\n out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;\n out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;\n out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;\n out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;\n out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;\n out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;\n out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;\n out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;\n out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;\n out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;\n out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;\n out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;\n out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;\n out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;\n out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;\n out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;\n\n return out;\n};\n\n\n/**\n * Check if a value is defined in the ENUM dictionary.\n * @param {Object} enumDictionary - ENUM dictionary.\n * @param {Number|String} entryValue - a value to probe.\n * @return {Boolean}\n */\nexports.isDefinedENUMEntry = function(enumDictionary, entryValue) {\n for (let enumKey in enumDictionary) {\n if (entryValue === enumDictionary[enumKey]) {\n return true;\n }\n }\n return false;\n};\n\n\n/**\n * Check if the given object is an instance of BaseAudioContext.\n * @param {AudioContext} context - A context object to be checked.\n * @return {Boolean}\n */\nexports.isAudioContext = function(context) {\n // TODO(hoch): Update this when BaseAudioContext is available for all\n // browsers.\n return context instanceof AudioContext ||\n context instanceof OfflineAudioContext;\n};\n\n\n/**\n * Check if the given object is a valid AudioBuffer.\n * @param {Object} audioBuffer An AudioBuffer object to be checked.\n * @return {Boolean}\n */\nexports.isAudioBuffer = function(audioBuffer) {\n return audioBuffer instanceof AudioBuffer;\n};\n\n\n/**\n * Perform channel-wise merge on multiple AudioBuffers. The sample rate and\n * the length of buffers to be merged must be identical.\n * @param {BaseAudioContext} context - Associated BaseAudioContext.\n * @param {AudioBuffer[]} bufferList - An array of AudioBuffers to be merged\n * channel-wise.\n * @return {AudioBuffer} - A single merged AudioBuffer.\n */\nexports.mergeBufferListByChannel = function(context, bufferList) {\n const bufferLength = bufferList[0].length;\n const bufferSampleRate = bufferList[0].sampleRate;\n let bufferNumberOfChannel = 0;\n\n for (let i = 0; i < bufferList.length; ++i) {\n if (bufferNumberOfChannel > 32) {\n exports.throw('Utils.mergeBuffer: Number of channels cannot exceed 32.' +\n '(got ' + bufferNumberOfChannel + ')');\n }\n if (bufferLength !== bufferList[i].length) {\n exports.throw('Utils.mergeBuffer: AudioBuffer lengths are ' +\n 'inconsistent. (expected ' + bufferLength + ' but got ' +\n bufferList[i].length + ')');\n }\n if (bufferSampleRate !== bufferList[i].sampleRate) {\n exports.throw('Utils.mergeBuffer: AudioBuffer sample rates are ' +\n 'inconsistent. (expected ' + bufferSampleRate + ' but got ' +\n bufferList[i].sampleRate + ')');\n }\n bufferNumberOfChannel += bufferList[i].numberOfChannels;\n }\n\n const buffer = context.createBuffer(bufferNumberOfChannel,\n bufferLength,\n bufferSampleRate);\n let destinationChannelIndex = 0;\n for (let i = 0; i < bufferList.length; ++i) {\n for (let j = 0; j < bufferList[i].numberOfChannels; ++j) {\n buffer.getChannelData(destinationChannelIndex++).set(\n bufferList[i].getChannelData(j));\n }\n }\n\n return buffer;\n};\n\n\n/**\n * Perform channel-wise split by the given channel count. For example,\n * 1 x AudioBuffer(8) -> splitBuffer(context, buffer, 2) -> 4 x AudioBuffer(2).\n * @param {BaseAudioContext} context - Associated BaseAudioContext.\n * @param {AudioBuffer} audioBuffer - An AudioBuffer to be splitted.\n * @param {Number} splitBy - Number of channels to be splitted.\n * @return {AudioBuffer[]} - An array of splitted AudioBuffers.\n */\nexports.splitBufferbyChannel = function(context, audioBuffer, splitBy) {\n if (audioBuffer.numberOfChannels <= splitBy) {\n exports.throw('Utils.splitBuffer: Insufficient number of channels. (' +\n audioBuffer.numberOfChannels + ' splitted by ' + splitBy + ')');\n }\n\n let bufflerList = [];\n let sourceChannelIndex = 0;\n const numberOfSplittedBuffer =\n Math.ceil(audioBuffer.numberOfChannels / splitBy);\n for (let i = 0; i < numberOfSplittedBuffer; ++i) {\n let buffer = context.createBuffer(splitBy,\n audioBuffer.length,\n audioBuffer.sampleRate);\n for (let j = 0; j < splitBy; ++j) {\n if (sourceChannelIndex < audioBuffer.numberOfChannels) {\n buffer.getChannelData(j).set(\n audioBuffer.getChannelData(sourceChannelIndex++));\n }\n }\n bufflerList.push(buffer);\n }\n\n return bufferList;\n};\n\n\n/**\n * Converts Base64-encoded string to ArrayBuffer.\n * @param {string} base64String - Base64-encdoed string.\n * @return {ArrayByuffer} Converted ArrayBuffer object.\n */\nexports.getArrayBufferFromBase64String = function(base64String) {\n let binaryString = window.atob(base64String);\n let byteArray = new Uint8Array(binaryString.length);\n byteArray.forEach(\n (value, index) => byteArray[index] = binaryString.charCodeAt(index));\n return byteArray.buffer;\n};\n\n\n//# sourceURL=webpack:///./src/utils.js?"); + }, + "./src/version.js": function(module, exports, __webpack_require__) { + "use strict"; + eval('/**\n * @license\n * Copyright 2016 Google Inc. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the "License");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an "AS IS" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @file Omnitone version.\n */\n\n\n\n\n/**\n * Omnitone library version\n * @type {String}\n */\nmodule.exports = \'1.2.4\';\n\n\n//# sourceURL=webpack:///./src/version.js?'); + } + }); }); \ No newline at end of file diff --git a/lib/omnitone/omnitone.min.js b/lib/omnitone/omnitone.min.js index 61e759f0..fe510b37 100644 --- a/lib/omnitone/omnitone.min.js +++ b/lib/omnitone/omnitone.min.js @@ -1,4 +1,4 @@ -!function(t,e){if("object"==typeof exports&&"object"==typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var i=e();for(var n in i)("object"==typeof exports?exports:t)[n]=i[n]}}(this,function(){return function(t){function e(n){if(i[n])return i[n].exports;var o=i[n]={i:n,l:!1,exports:{}};return t[n].call(o.exports,o,o.exports,e),o.l=!0,o.exports}var i={};return e.m=t,e.c=i,e.d=function(t,i,n){e.o(t,i)||Object.defineProperty(t,i,{configurable:!1,enumerable:!0,get:n})},e.n=function(t){var i=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(i,"a",i),i},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=11)}([function(t,e){e.log=function(){window.console.log.apply(window.console,["%c[Omnitone]%c "+Array.prototype.slice.call(arguments).join(" ")+" %c(@"+performance.now().toFixed(2)+"ms)","background: #BBDEFB; color: #FF5722; font-weight: 500","font-weight: 300","color: #AAA"])},e.throw=function(){throw window.console.error.apply(window.console,["%c[Omnitone]%c "+Array.prototype.slice.call(arguments).join(" ")+" %c(@"+performance.now().toFixed(2)+"ms)","background: #C62828; color: #FFEBEE; font-weight: 800","font-weight: 400","color: #AAA"]),new Error(!1)};let i,n,o,s,r,a,h,c,_,l,u,f,p,d,g,m,v,x,b,M,R,w,y,A,C,O,B,I,F;e.invertMatrix4=function(t,e){return i=e[0],n=e[1],o=e[2],s=e[3],r=e[4],a=e[5],h=e[6],c=e[7],_=e[8],l=e[9],u=e[10],f=e[11],p=e[12],d=e[13],g=e[14],m=e[15],v=i*a-n*r,x=i*h-o*r,b=i*c-s*r,M=n*h-o*a,R=n*c-s*a,w=o*c-s*h,y=_*d-l*p,A=_*g-u*p,C=_*m-f*p,O=l*g-u*d,B=l*m-f*d,I=u*m-f*g,(F=v*I-x*B+b*O+M*C-R*A+w*y)?(F=1/F,t[0]=(a*I-h*B+c*O)*F,t[1]=(o*B-n*I-s*O)*F,t[2]=(d*w-g*R+m*M)*F,t[3]=(u*R-l*w-f*M)*F,t[4]=(h*C-r*I-c*A)*F,t[5]=(i*I-o*C+s*A)*F,t[6]=(g*b-p*w-m*x)*F,t[7]=(_*w-u*b+f*x)*F,t[8]=(r*B-a*C+c*y)*F,t[9]=(n*C-i*B-s*y)*F,t[10]=(p*R-d*b+m*v)*F,t[11]=(l*b-_*R-f*v)*F,t[12]=(a*A-r*O-h*y)*F,t[13]=(i*O-n*A+o*y)*F,t[14]=(d*x-p*M-g*v)*F,t[15]=(_*M-l*x+u*v)*F,t):null},e.isAudioContext=function(t){return t instanceof AudioContext||t instanceof OfflineAudioContext},e.isAudioBuffer=function(t){return t instanceof AudioBuffer},e.mergeBufferListByChannel=function(t,i){const n=i[0].length,o=i[0].sampleRate;let s=0;for(let h=0;h32&&e.throw("Utils.mergeBuffer: Number of channels cannot exceed 32.(got "+s+")"),n!==i[h].length&&e.throw("Utils.mergeBuffer: AudioBuffer lengths are inconsistent. (expected "+n+" but got "+i[h].length+")"),o!==i[h].sampleRate&&e.throw("Utils.mergeBuffer: AudioBuffer sample rates are inconsistent. (expected "+o+" but got "+i[h].sampleRate+")"),s+=i[h].numberOfChannels;const r=t.createBuffer(s,n,o);let a=0;for(let e=0;e=0?this._stereoSplitters[n].connect(this._positiveIndexSphericalHarmonics,i%2):this._stereoSplitters[n].connect(this._negativeIndexSphericalHarmonics,i%2)}this._positiveIndexSphericalHarmonics.connect(this._binauralMerger,0,0),this._positiveIndexSphericalHarmonics.connect(this._binauralMerger,0,1),this._negativeIndexSphericalHarmonics.connect(this._binauralMerger,0,0),this._negativeIndexSphericalHarmonics.connect(this._inverter),this._inverter.connect(this._binauralMerger,0,1),this._inverter.gain.value=-1,this.input=this._inputSplitter,this.output=this._outputGain},n.prototype.setHRIRBufferList=function(t){if(!this._isBufferLoaded){for(let e=0;e0){const s=n(e,1);return r(t,1,e-1,i,o)*Math.sqrt(1+s)-r(t,-1,1-e,i,o)*(1-s)}{const s=n(e,-1);return r(t,1,e+1,i,o)*(1-s)+r(t,-1,-e-1,i,o)*Math.sqrt(1+s)}}function c(t,e,i,n){return 0===e?0:e>0?r(t,1,e+1,i,n)+r(t,-1,-e-1,i,n):r(t,1,e-1,i,n)-r(t,-1,1-e,i,n)}function _(t,e,i){const o=n(t,0),s=Math.abs(e)===i?1/(2*i*(2*i-1)):1/((i+e)*(i-e));return[Math.sqrt((i+t)*(i-t)*s),.5*(1-2*o)*Math.sqrt((1+o)*(i+Math.abs(t)-1)*(i+Math.abs(t))*s),-.5*(1-o)*Math.sqrt((i-Math.abs(t)-1)*(i-Math.abs(t)))*s]}function l(t,e){for(let i=-e;i<=e;i++)for(let n=-e;n<=e;n++){const s=_(i,n,e);Math.abs(s[0])>0&&(s[0]*=a(t,i,n,e)),Math.abs(s[1])>0&&(s[1]*=h(t,i,n,e)),Math.abs(s[2])>0&&(s[2]*=c(t,i,n,e)),o(t,e,i,n,s[0]+s[1]+s[2])}}function u(t){for(let e=2;e<=t.length;e++)l(t,e)}function f(t,e){this._context=t,this._ambisonicOrder=e;const i=(e+1)*(e+1);this._splitter=this._context.createChannelSplitter(i),this._merger=this._context.createChannelMerger(i),this._gainNodeMatrix=[];let n,o,s,r,a;for(let h=1;h<=e;h++){n=h*h,o=2*h+1,this._gainNodeMatrix[h-1]=[];for(let t=0;t ["+t+"])"),this._audioElementSource=this._context.createMediaElementSource(this._videoElement),this._foaRouter=new s(this._context,this._channelMap),this._foaRotator=new r(this._context),this._foaPhaseMatchedFilter=new a(this._context),this._audioElementSource.connect(this._foaRouter.input),this._foaRouter.output.connect(this._foaRotator.input),this._foaRotator.output.connect(this._foaPhaseMatchedFilter.input),this._foaVirtualSpeakers=[],this._bypass=this._context.createGain(),this._audioElementSource.connect(this._bypass);const i=Math.pow(10,this._postGainDB/20);_.log("Gain compensation: "+i+" ("+this._postGainDB+"dB)");const n=this;return new Promise(function(t,e){new o(n._context,n._speakerData,function(e){for(let t=0;t ["+t.toString()+"])."),this._config.channelMap=t.slice(),this._foaRouter.setChannelMap(this._config.channelMap))},n.prototype.setRotationMatrix3=function(t){this._isRendererReady&&this._foaRotator.setRotationMatrix3(t)},n.prototype.setRotationMatrix4=function(t){this._isRendererReady&&this._foaRotator.setRotationMatrix4(t)},n.prototype.setRotationMatrixFromCamera=function(t){this._isRendererReady&&(c.invertMatrix4(this._tempMatrix4,t.elements),this._foaRotator.setRotationMatrix4(this._tempMatrix4))},n.prototype.setRenderingMode=function(t){if(t!==this._config.renderingMode){switch(t){case _.AMBISONIC:this._foaConvolver.enable(),this._bypass.disconnect();break;case _.BYPASS:this._foaConvolver.disable(),this._bypass.connect(this.output);break;case _.OFF:this._foaConvolver.disable(),this._bypass.disconnect();break;default:return void c.log('FOARenderer: Rendering mode "'+t+'" is not supported.')}this._config.renderingMode=t,c.log("FOARenderer: Rendering mode changed. ("+t+")")}},t.exports=n},function(t,e,i){"use strict";function n(t,e){this._context=h.isAudioContext(t)?t:h.throw("HOARenderer: Invalid BaseAudioContext."),this._config={ambisonicOrder:3,renderingMode:c.AMBISONIC},e.ambisonicOrder&&(_.includes(e.ambisonicOrder)?this._config.ambisonicOrder=e.ambisonicOrder:h.log("HOARenderer: Invalid ambisonic order. (got "+e.ambisonicOrder+") Fallbacks to 3rd-order ambisonic.")),this._config.numberOfChannels=(this._config.ambisonicOrder+1)*(this._config.ambisonicOrder+1),this._config.numberOfStereoChannels=Math.ceil(this._config.numberOfChannels/2),e.hrirPathList?Array.isArray(e.hrirPathList)&&e.hrirPathList.length===this._config.numberOfStereoChannels?this._config.pathList=e.hrirPathList:h.throw("HOARenderer: Invalid HRIR URLs. It must be an array with "+this._config.numberOfStereoChannels+" URLs to HRIR files. (got "+e.hrirPathList+")"):this._config.pathList=a.getPathList({ambisonicOrder:this._config.ambisonicOrder}),e.renderingMode&&(Object.values(c).includes(e.renderingMode)?this._config.renderingMode=e.renderingMode:h.log("HOARenderer: Invalid rendering mode. (got "+e.renderingMode+') Fallbacks to "ambisonic".')),this._buildAudioGraph(),this._isRendererReady=!1}const o=i(1),s=i(9),r=i(10),a=i(8),h=i(0),c={AMBISONIC:"ambisonic",BYPASS:"bypass",OFF:"off"},_=[2,3];n.prototype._buildAudioGraph=function(){this.input=this._context.createGain(),this.output=this._context.createGain(),this._bypass=this._context.createGain(),this._hoaRotator=new r(this._context,this._config.ambisonicOrder),this._hoaConvolver=new s(this._context,this._config.ambisonicOrder),this.input.connect(this._hoaRotator.input),this.input.connect(this._bypass),this._hoaRotator.output.connect(this._hoaConvolver.input),this._hoaConvolver.output.connect(this.output)},n.prototype._initializeCallback=function(t,e){let i=[];for(let n=0;n32&&t.throw("Utils.mergeBuffer: Number of channels cannot exceed 32.(got "+o+")"),i!==e[A].length&&t.throw("Utils.mergeBuffer: AudioBuffer lengths are inconsistent. (expected "+i+" but got "+e[A].length+")"),n!==e[A].sampleRate&&t.throw("Utils.mergeBuffer: AudioBuffer sample rates are inconsistent. (expected "+n+" but got "+e[A].sampleRate+")"),o+=e[A].numberOfChannels;const r=A.createBuffer(o,i,n);let s=0;for(let A=0;Ae[i]=t.charCodeAt(i)),e.buffer}},function(A,t,e){"use strict";const i=e(0),n={BASE64:"base64",URL:"url"};function o(A,t,e){this._context=i.isAudioContext(A)?A:i.throw("BufferList: Invalid BaseAudioContext."),this._options={dataType:n.BASE64,verbose:!1},e&&(e.dataType&&i.isDefinedENUMEntry(n,e.dataType)&&(this._options.dataType=e.dataType),e.verbose&&(this._options.verbose=Boolean(e.verbose))),this._bufferList=[],this._bufferData=this._options.dataType===n.BASE64?t:t.slice(0),this._numberOfTasks=this._bufferData.length,this._resolveHandler=null,this._rejectHandler=new Function}o.prototype.load=function(){return new Promise(this._promiseGenerator.bind(this))},o.prototype._promiseGenerator=function(A,t){"function"!=typeof A?i.throw("BufferList: Invalid Promise resolver."):this._resolveHandler=A,"function"==typeof t&&(this._rejectHandler=t);for(let A=0;A0){const o=i(t,1);return r(A,1,t-1,e,n)*Math.sqrt(1+o)-r(A,-1,1-t,e,n)*(1-o)}{const o=i(t,-1);return r(A,1,t+1,e,n)*(1-o)+r(A,-1,-t-1,e,n)*Math.sqrt(1+o)}}function f(A,t,e,i){return 0===t?0:t>0?r(A,1,t+1,e,i)+r(A,-1,-t-1,e,i):r(A,1,t-1,e,i)-r(A,-1,1-t,e,i)}function c(A,t,e){const n=i(A,0),o=Math.abs(t)===e?1/(2*e*(2*e-1)):1/((e+t)*(e-t));return[Math.sqrt((e+A)*(e-A)*o),.5*(1-2*n)*Math.sqrt((1+n)*(e+Math.abs(A)-1)*(e+Math.abs(A))*o),-.5*(1-n)*Math.sqrt((e-Math.abs(A)-1)*(e-Math.abs(A)))*o]}function h(A,t){for(let e=-t;e<=t;e++)for(let i=-t;i<=t;i++){const o=c(e,i,t);Math.abs(o[0])>0&&(o[0]*=s(A,e,i,t)),Math.abs(o[1])>0&&(o[1]*=a(A,e,i,t)),Math.abs(o[2])>0&&(o[2]*=f(A,e,i,t)),n(A,t,e,i,o[0]+o[1]+o[2])}}function g(A){for(let t=2;t<=A.length;t++)h(A,t)}function w(A,t){this._context=A,this._ambisonicOrder=t;const e=(t+1)*(t+1);let i,n,o,r,s;this._splitter=this._context.createChannelSplitter(e),this._merger=this._context.createChannelMerger(e),this._gainNodeMatrix=[];for(let A=1;A<=t;A++){i=A*A,n=2*A+1,this._gainNodeMatrix[A-1]=[];for(let t=0;t=0?this._stereoSplitters[i].connect(this._positiveIndexSphericalHarmonics,e%2):this._stereoSplitters[i].connect(this._negativeIndexSphericalHarmonics,e%2)}this._positiveIndexSphericalHarmonics.connect(this._binauralMerger,0,0),this._positiveIndexSphericalHarmonics.connect(this._binauralMerger,0,1),this._negativeIndexSphericalHarmonics.connect(this._binauralMerger,0,0),this._negativeIndexSphericalHarmonics.connect(this._inverter),this._inverter.connect(this._binauralMerger,0,1),this._inverter.gain.value=-1,this.input=this._inputSplitter,this.output=this._outputGain},i.prototype.setHRIRBufferList=function(A){if(!this._isBufferLoaded){for(let t=0;t ["+A.toString()+"])."),this._config.channelMap=A.slice(),this._foaRouter.setChannelMap(this._config.channelMap))},c.prototype.setRotationMatrix3=function(A){this._isRendererReady&&this._foaRotator.setRotationMatrix3(A)},c.prototype.setRotationMatrix4=function(A){this._isRendererReady&&this._foaRotator.setRotationMatrix4(A)},c.prototype.setRotationMatrixFromCamera=function(A){this._isRendererReady&&(a.invertMatrix4(this._tempMatrix4,A.elements),this._foaRotator.setRotationMatrix4(this._tempMatrix4))},c.prototype.setRenderingMode=function(A){if(A!==this._config.renderingMode){switch(A){case f.AMBISONIC:this._foaConvolver.enable(),this._bypass.disconnect();break;case f.BYPASS:this._foaConvolver.disable(),this._bypass.connect(this.output);break;case f.OFF:this._foaConvolver.disable(),this._bypass.disconnect();break;default:return void a.log('FOARenderer: Rendering mode "'+A+'" is not supported.')}this._config.renderingMode=A,a.log("FOARenderer: Rendering mode changed. ("+A+")")}},A.exports=c},function(A,t,e){"use strict";const i=e(1),n=e(6),o=e(13),r=e(5),s=e(4),a=e(3),f=e(11),c=e(2),h=e(8),g=e(0),w=e(7);let D={};D.browserInfo=h.getBrowserInfo(),D.createBufferList=function(A,t,e){return new i(A,t,e||{dataType:"url"}).load()},D.mergeBufferListByChannel=g.mergeBufferListByChannel,D.splitBufferbyChannel=g.splitBufferbyChannel,D.createFOAConvolver=function(A,t){return new n(A,t)},D.createFOARouter=function(A,t){return new s(A,t)},D.createFOARotator=function(A){return new r(A)},D.createHOARotator=function(A,t){return new c(A,t)},D.createHOAConvolver=function(A,t,e){return new a(A,t,e)},D.createFOARenderer=function(A,t){return new o(A,t)},D.createHOARenderer=function(A,t){return new f(A,t)},g.log("Version "+w+" (running "+D.browserInfo.name+" "+D.browserInfo.version+" on "+D.browserInfo.platform+")"),"safari"===D.browserInfo.name.toLowerCase()&&(h.patchSafari(),g.log(D.browserInfo.name+" detected. Polyfill applied.")),A.exports=D},function(A,t,e){"use strict";t.Omnitone=e(14)}])}); \ No newline at end of file diff --git a/src/audio/Sound.js b/src/audio/Sound.js index b9f56fac..5168d4eb 100755 --- a/src/audio/Sound.js +++ b/src/audio/Sound.js @@ -5,14 +5,14 @@ * @param {FORGE.Viewer} viewer - The {@link FORGE.Viewer} reference. * @param {string} key - The sound file id reference. * @param {string} url - The sound file url. - * @param {boolean=} ambisonic - Is the sound ambisonic and need binaural rendering? + * @param {number=} ambisonicOrder - Is it an ambisonic order sound? * @extends {FORGE.BaseObject} * * @todo Ability to force audio type into config * @todo Make a test plugin that creates sound, add sound to the PluginObjectFactory * @todo Loop during x steps (parameter) only if loop is true */ -FORGE.Sound = function(viewer, key, url, ambisonic) +FORGE.Sound = function(viewer, key, url, ambisonicOrder) { /** * The viewer reference. @@ -240,29 +240,44 @@ FORGE.Sound = function(viewer, key, url, ambisonic) this._z = 0; /** - * FOADecoder is a ready-made FOA decoder and binaural renderer. - * @name FORGE.Sound#_decoder - * @type {?FOADecoder} + * Ambisonic renderer is a ready-made FOA or HOA decoder and binaural renderer. + * @name FORGE.Sound#_ambisonicsRenderer + * @type {?(FOARenderer|HOARenderer)} * @private */ - this._decoder = null; + this._ambisonicsRenderer = null; /** - * Is it an ambisonical sound? - * @name FORGE.Sound#_ambisonic - * @type {boolean} + * Media Element Audio souce node element. + * @name FORGE.Sound#_soundElementSource + * @type {?MediaElementAudioSourceNode} * @private */ - this._ambisonic = ambisonic || false; + this._soundElementSource = null; /** - * Default channel map for ambisonic sound. + * Is it a an ambisonic order soundtrack? + * @name FORGE.Sound#_ambisonicOrder + * @type {number} + * @private + */ + this._ambisonicOrder = ambisonicOrder || 0; + + /** + * Default channel map for FOA ambisonic sound. * @name FORGE.Sound#_defaultChannelMap * @type {Array} * @private */ this._defaultChannelMap = [0, 1, 2, 3]; //AMBIX - // this._defaultChannelMap = [0, 3, 1, 2]; //FUMA + + /** + * Default ambisonics order for HOA renderer. + * @name FORGE.VideoHTML5#_defaultAmbisonicOrder + * @type {number} + * @private + */ + this._defaultAmbisonicOrder = 3; /** * To save the pending state to be applied after the sound object will be ready. @@ -424,10 +439,10 @@ FORGE.Sound.prototype.constructor = FORGE.Sound; */ FORGE.Sound.prototype._boot = function() { - if (this._ambisonic === true && this._isAmbisonic() === false) + if (this._isAmbisonic() === false) { this.log("FORGE.Sound: can't manage ambisonic sound without Google Chrome Omnitone library and WebAudio API."); - this._ambisonic = false; + this._ambisonicOrder = 0; } //register the uid @@ -452,11 +467,15 @@ FORGE.Sound.prototype._boot = function() this._gainNode.gain.value = this._volume * this._viewer.audio.volume; this._gainNode.connect(this._inputNode); } + + var loaded = false; if (this._viewer.audio.useAudioTag === true || this._isAmbisonic() === true) { if (this._viewer.cache.has(FORGE.Cache.types.SOUND, this._key) === true) { - this._loadComplete(this._viewer.cache.get(FORGE.Cache.types.SOUND, this._key)); + // wait long enough so that a frame has passed (here at 24fps) + window.setTimeout(this._loadComplete.bind(this, this._viewer.cache.get(FORGE.Cache.types.SOUND, this._key)), 40); + loaded = true; } //Listen to the main volume change to adapt the sound volume accordingly. @@ -467,7 +486,10 @@ FORGE.Sound.prototype._boot = function() if (this._url !== "") { - this._viewer.load.sound(this._key, this._url, this._loadComplete, this, this._isAmbisonic()); + if (loaded === false) + { + this._viewer.load.sound(this._key, this._url, this._loadComplete, this, this._isAmbisonic()); + } if (this._onLoadStart !== null) { @@ -492,6 +514,11 @@ FORGE.Sound.prototype._loadComplete = function(file) return; } + if (this._isAmbisonic() === true) + { + file.data = file.data.cloneNode(true); + } + this._soundFile = file; this._ready = true; @@ -531,16 +558,30 @@ FORGE.Sound.prototype._decode = function(file) if (this._isAmbisonic() === true) { - // FOA decoder and binaural renderer - this._decoder = Omnitone.createFOADecoder(this._context, file.data, + // Source + this._soundElementSource = this._context.createMediaElementSource(file.data); + + if (this._ambisonicOrder > 1) + { + //HOA decoder and binaural renderer (for 2nd and 3rd order) + this._ambisonicsRenderer = Omnitone.createHOARenderer(this._context, { + ambisonicOrder: (this._ambisonicOrder > 1 ? this._ambisonicOrder : this._defaultAmbisonicOrder), // can be 2 or 3 + hrirPathList: null, + renderingMode: 'ambisonic' + }); + } + else { - channelMap: this._defaultChannelMap - // HRTFSetUrl: 'YOUR_HRTF_SET_URL', //Base URL for the cube HRTF sets. - // postGainDB: 0, //Post-decoding gain compensation in dB. - }); + //FOA decoder and binaural renderer (for 1st order) + this._ambisonicsRenderer = Omnitone.createFOARenderer(this._context, { + channelMap: this._defaultChannelMap, // [0, 1, 2, 3]; for AMBIX & [0, 3, 1, 2] for FUMA + hrirPathList: null, + renderingMode: 'ambisonic' + }); + } - // Initialize the decoder - this._decoder.initialize().then(this._decodeCompleteBind, this._decodeErrorBind); + // Initialize the ambisonics renderer + this._ambisonicsRenderer.initialize().then(this._decodeCompleteBind, this._decodeErrorBind); } else { @@ -591,11 +632,10 @@ FORGE.Sound.prototype._dispatchDecodedEvents = function() * Event handler for decode complete event, it stores decoding data into the sound file object. * @method FORGE.Sound#_decodeComplete * @private - * @param {?AudioBuffer} buffer - The raw binary data buffer. */ FORGE.Sound.prototype._decodeComplete = function(buffer) { - if (this._soundFile === null) + if (this._ambisonicsRenderer === null) { this.log("FORGE.Sound._decodeComplete error, sound file is null"); return; @@ -606,6 +646,12 @@ FORGE.Sound.prototype._decodeComplete = function(buffer) this._soundFile.data = buffer; } + if (this._ambisonicsRenderer !== null) + { + this._soundElementSource.connect(this._ambisonicsRenderer.input); + this._ambisonicsRenderer.output.connect(this._context.destination); + } + this._decoded = true; this._dispatchDecodedEvents(); @@ -736,7 +782,7 @@ FORGE.Sound.prototype._applyPanner = function(connect) */ FORGE.Sound.prototype._isAmbisonic = function() { - return (this._ambisonic === true && this._viewer.audio.useWebAudio === true && typeof Omnitone !== "undefined"); + return (this._ambisonicOrder > 0 && this._viewer.audio.useWebAudio === true && typeof Omnitone !== "undefined"); }; /** @@ -775,11 +821,11 @@ FORGE.Sound.prototype.update = function() } } - if (this._decoder !== null && this._playing === true) + if (this._ambisonicsRenderer !== null && this._playing === true) { // Rotate the binaural renderer based on a Three.js camera object. - var m4 = this._viewer.renderer.camera.modelViewInverse; - this._decoder.setRotationMatrixFromCamera(m4); + var m4 = this._viewer.renderer.camera.modelView; + this._ambisonicsRenderer.setRotationMatrix4(m4.elements); } }; @@ -1173,6 +1219,14 @@ FORGE.Sound.prototype.destroy = function() this._viewer.audio.onVolumeChange.remove(this._mainVolumeChangeHandler, this); } + if (this._isAmbisonic() === true) + { + this._ambisonicsRenderer.output.disconnect(); + this._soundElementSource.disconnect(); + this._ambisonicsRenderer = null; + this._soundElementSource = null; + } + this._viewer.audio.onDisable.remove(this._disableSoundHandler, this); this._viewer.audio.remove(this); @@ -1363,7 +1417,7 @@ Object.defineProperty(FORGE.Sound.prototype, "ambisonic", /** @this {FORGE.Sound} */ get: function() { - return this._ambisonic; + return this._isAmbisonic(); } }); diff --git a/src/display/video/VideoHTML5.js b/src/display/video/VideoHTML5.js index fe5bc9c5..4e61eb7d 100644 --- a/src/display/video/VideoHTML5.js +++ b/src/display/video/VideoHTML5.js @@ -9,15 +9,14 @@ * @param {string} key - The video file id reference. * @param {?(string|FORGE.VideoQuality|Array<(string|FORGE.VideoQuality)>)=} config - Either a {@link FORGE.VideoQuality} or a String URL, or an array of strings or {@link FORGE.VideoQuality} if multiquality. * @param {?string=} qualityMode - The default quality mode. - * @param {?boolean=} ambisonic - Is the video sound ambisonic? + * @param {?number=} ambisonicOrder - Is it a video with ambisonic order sound? * @extends {FORGE.VideoBase} * - * @todo Define a config object for videos, maybe a class like VideoConfig to describe this porperly. + * @todo Define a config object for videos, maybe a class like VideoConfig to describe this properly. * @todo Make it work with several sources if the user wants to pass a mp4 + webm + ogg for example. - * @todo Deal with playback speeds. * @todo Add subtitles management with and VTT/TTML(EBU-TT-D) files */ -FORGE.VideoHTML5 = function(viewer, key, config, qualityMode, ambisonic) +FORGE.VideoHTML5 = function(viewer, key, config, qualityMode, ambisonicOrder) { /** * The video identifier. @@ -62,7 +61,7 @@ FORGE.VideoHTML5 = function(viewer, key, config, qualityMode, ambisonic) /** * Array of videos objects thaht handle the dom and some stats about each videos. * @name FORGE.VideoHTML5#_videos - * @type {Array} + * @type {?Array} * @private */ this._videos = null; @@ -165,29 +164,52 @@ FORGE.VideoHTML5 = function(viewer, key, config, qualityMode, ambisonic) this._playbackRate = 1; /** - * FOADecoder is a ready-made FOA decoder and binaural renderer. - * @name FORGE.VideoHTML5#_decoder - * @type {?FOADecoder} + * FOARenderer or HOARenderer are ready-made FOA/HOA decoder and binaural renderer. + * @name FORGE.VideoHTML5#_ambisonicsRenderer + * @type {?(FOARenderer|HOARenderer)} * @private */ - this._decoder = null; + this._ambisonicsRenderer = null; /** - * Is it an ambisonical video soundtrack? - * @name FORGE.VideoHTML5#_ambisonic - * @type {boolean} + * The AudioContext interface for ambisonics. + * @name FORGE.VideoHTML5#_context + * @type {?AudioContext} + * @private + */ + this._context = null; + + /** + * Media Element Audio souce node element. + * @name FORGE.Sound#_soundElementSource + * @type {?MediaElementAudioSourceNode} + * @private + */ + this._soundElementSource = null; + + /** + * Is it a video with ambisonic order soundtrack? + * @name FORGE.VideoHTML5#_ambisonicOrder + * @type {number} * @private */ - this._ambisonic = ambisonic || false; + this._ambisonicOrder = ambisonicOrder || 0; /** - * Default channel map for ambisonic sound. + * Default channel map for FOA renderer. * @name FORGE.VideoHTML5#_defaultChannelMap * @type {Array} * @private */ this._defaultChannelMap = [0, 1, 2, 3]; //AMBIX - //this._defaultChannelMap = [0, 3, 1, 2]; //FUMA + + /** + * Default ambisonics order for HOA renderer. + * @name FORGE.VideoHTML5#_defaultAmbisonicOrder + * @type {number} + * @private + */ + this._defaultAmbisonicOrder = 3; /** * Does the video have received its metaData? @@ -486,20 +508,20 @@ FORGE.VideoHTML5 = function(viewer, key, config, qualityMode, ambisonic) this._onEventBind = null; /** - * Event handler for FOA decoder initialized binded to this. - * @name FORGE.VideoHTML5#_decoderInitializedSuccessBind + * Event handler for ambisonics renderer initialized binded to this. + * @name FORGE.VideoHTML5#_ambisonicsRendererInitializedSuccessBind * @type {Function} * @private */ - this._decoderInitializedSuccessBind = null; + this._ambisonicsRendererInitializedSuccessBind = null; /** - * Event handler for FOA decoder initailization error binded to this. - * @name FORGE.VideoHTML5#_decoderInitializedErrorBind + * Event handler for ambisonics renderer initialization error binded to this. + * @name FORGE.VideoHTML5#_ambisonicsRendererInitializedErrorBind * @type {Function} * @private */ - this._decoderInitializedErrorBind = null; + this._ambisonicsRendererInitializedErrorBind = null; FORGE.VideoBase.call(this, viewer, "VideoHTML5"); }; @@ -516,10 +538,10 @@ FORGE.VideoHTML5.prototype._boot = function() { FORGE.VideoBase.prototype._boot.call(this); - if (this._ambisonic === true && this._isAmbisonic() === false) + if (this._ambisonicOrder > 0 && this._isAmbisonic() === false) { this.log("FORGE.VideoHTML5: can't manage ambisonic sound without Google Chrome Omnitone library and WebAudio API."); - this._ambisonic = false; + this._ambisonicOrder = 0; } //register the uid @@ -831,25 +853,28 @@ FORGE.VideoHTML5.prototype._createVideoAt = function(index) * Create the source tag into the video tag. * @method FORGE.VideoHTML5#_createSourceTags * @private - * @param {VideoHTML5Object} video - Video object to add the quality to. + * @param {?VideoHTML5Object} video - Video object to add the quality to. * @param {FORGE.VideoQuality} quality - The quality video source to attach to the video element. - * @return {VideoHTML5Object} The video object that contains the HTML5 Video Element in which the source is append to. + * @return {?VideoHTML5Object} The video object that contains the HTML5 Video Element in which the source is append to. */ FORGE.VideoHTML5.prototype._createSourceTags = function(video, quality) { - if (FORGE.Device.edge === true) - { - // EDGE is not able to restore the currentTime with source tag - video.element.src = quality.url; - } - else + if (video !== null) { - var source = document.createElement("source"); - source.addEventListener("error", this._onRequestErrorBind, false); - source.src = quality.url; - source.type = quality.mimeType; + if (FORGE.Device.edge === true) + { + // EDGE is not able to restore the currentTime with source tag + video.element.src = quality.url; + } + else + { + var source = document.createElement("source"); + source.addEventListener("error", this._onRequestErrorBind, false); + source.src = quality.url; + source.type = quality.mimeType; - video.element.appendChild(source); + video.element.appendChild(source); + } } return video; @@ -968,7 +993,7 @@ FORGE.VideoHTML5.prototype._setRequestIndex = function(index, force) //Create a video tag and get the quality var requestedVideo = this._createVideoAt(this._requestIndex); - requestedVideo.requestCount++; + this._getRequestedVideo().requestCount++; //Get the requested quality var quality = this._qualities[this._requestIndex]; @@ -977,30 +1002,47 @@ FORGE.VideoHTML5.prototype._setRequestIndex = function(index, force) if (this._currentIndex > -1) { //Add listener to begins transition between qualities - requestedVideo.element.addEventListener("loadstart", this._onRequestLoadStartBind, false); - requestedVideo.element.addEventListener("error", this._onRequestErrorBind, false); + this._getRequestedVideo().element.addEventListener("loadstart", this._onRequestLoadStartBind, false); + this._getRequestedVideo().element.addEventListener("error", this._onRequestErrorBind, false); } //Create source tags according to quality - this._createSourceTags(requestedVideo, quality); - - this._decoderInitializedSuccessBind = this._decoderInitializedSuccess.bind(this); + this._createSourceTags(this._getRequestedVideo(), quality); if (this._isAmbisonic() === true) { //get the global audio context this._context = this._viewer.audio.context; - //FOA decoder and binaural renderer - this._decoder = Omnitone.createFOADecoder(this._context, requestedVideo.element, { - channelMap: this._defaultChannelMap - //HRTFSetUrl: 'YOUR_HRTF_SET_URL', //Base URL for the cube HRTF sets. - //postGainDB: 0, //Post-decoding gain compensation in dB. - }); + // create source element + this._soundElementSource = this._context.createMediaElementSource( /** @type {HTMLVideoElement} */ (this._getRequestedVideo().element)); + + if (this._isHOAAmbisonic() === true) + { + //HOA decoder and binaural renderer (for 2nd and 3rd order) + this._ambisonicsRenderer = Omnitone.createHOARenderer(this._context, { + ambisonicOrder: (this._ambisonicOrder > 1 ? this._ambisonicOrder : this._defaultAmbisonicOrder), // can be 2 or 3_ + hrirPathList: null, + renderingMode: 'ambisonic' + }); + } + else + { + //FOA decoder and binaural renderer (for 1st order) + this._ambisonicsRenderer = Omnitone.createFOARenderer(this._context, { + channelMap: this._defaultChannelMap, // [0, 1, 2, 3]; for AMBIX & [0, 3, 1, 2] for FUMA + hrirPathList: null, + renderingMode: 'ambisonic' + }); + } + + this._ambisonicsRendererInitializedSuccessBind = this._ambisonicsRendererInitializedSuccess.bind(this); + this._ambisonicsRendererInitializedErrorBind = this._ambisonicsRendererInitializedError.bind(this); + + //Initialize the ambisonic renderer + this._ambisonicsRenderer.initialize().then(this._ambisonicsRendererInitializedSuccessBind, this._ambisonicsRendererInitializedErrorBind); - //Initialize the decoder - this._decoderInitializedErrorBind = this._decoderInitializedError.bind(this); - this._decoder.initialize().then(this._decoderInitializedSuccessBind, this._decoderInitializedErrorBind); + this._decoderInitializedSuccess(); } else { @@ -1023,7 +1065,7 @@ FORGE.VideoHTML5.prototype._setRequestIndex = function(index, force) }; /** - * The FOA decoder has been initialized. + * The decoder has been initialized. * @method FORGE.VideoHTML5#_decoderInitializedSuccess * @private */ @@ -1032,22 +1074,39 @@ FORGE.VideoHTML5.prototype._decoderInitializedSuccess = function() if (this._requestIndex === -1) { //get the current video if requested index is set - this._getCurrentVideo().element.load(); + // this._getCurrentVideo().element.load(); + this._getCurrentVideo().element.play(); } else { - this._getRequestedVideo().element.load(); + // this._getRequestedVideo().element.load(); + this._getRequestedVideo().element.play(); } }; /** - * The FOA decoder can't be initialized. - * @method FORGE.VideoHTML5#_decoderInitializedError + * The Ambisonics renderer is initialized. + * @method FORGE.VideoHTML5#_ambisonicsRendererInitializedSuccess * @private */ -FORGE.VideoHTML5.prototype._decoderInitializedError = function() +FORGE.VideoHTML5.prototype._ambisonicsRendererInitializedSuccess = function() { - this.log("FOA decoder could not be initialized"); + this.log("Ambisonics renderer is initialized"); + if (this._ambisonicsRenderer !== null) + { + this._soundElementSource.connect(this._ambisonicsRenderer.input); + this._ambisonicsRenderer.output.connect(this._context.destination); + } +}; + +/** + * The Ambisonics renderer can't be initialized. + * @method FORGE.VideoHTML5#_ambisonicsRendererInitializedError + * @private + */ +FORGE.VideoHTML5.prototype._ambisonicsRendererInitializedError = function() +{ + this.log("Ambisonics renderer could not be initialized"); }; /** @@ -1411,8 +1470,9 @@ FORGE.VideoHTML5.prototype._shouldAutoQualityDowngrade = function() { var currentVideo = this._getCurrentVideo(); var time = currentVideo.element.currentTime; + this.log("current time is : "+time+" ("+currentVideo.lastTimeStamp+")"); - if (currentVideo.element.playing === false) + if (this._playing === false) //currentVideo.element.playing { return false; } @@ -1423,7 +1483,7 @@ FORGE.VideoHTML5.prototype._shouldAutoQualityDowngrade = function() if (currentVideo.downCount >= 3) { currentVideo.lastTimeStamp = 0; - return true; + // return true; } } else @@ -1510,7 +1570,7 @@ FORGE.VideoHTML5.prototype._setQualityMode = function(mode) this._autoQualityTimer.stop(false); //If mode auto add and start a fresh timer - if (this._qualityMode === FORGE.VideoQualityMode.AUTO) + if (this._qualityMode === FORGE.VideoQualityMode.AUTO && this._qualities.length > 1) { this._autoQualityTimer.start(); } @@ -1899,7 +1959,18 @@ FORGE.VideoHTML5.prototype._onEventHandler = function(event) */ FORGE.VideoHTML5.prototype._isAmbisonic = function() { - return (this._ambisonic === true && this._viewer.audio.useWebAudio === true && typeof Omnitone !== "undefined"); + return (this._ambisonicOrder > 0 && this._viewer.audio.useWebAudio === true && typeof Omnitone !== "undefined"); +}; + +/** + * Does the video sound must be considered as FOA ambisonic? + * @method FORGE.VideoHTML5#_isFOAAmbisonic + * @return {boolean} Is a FOA ambisonic? + * @private + */ +FORGE.VideoHTML5.prototype._isHOAAmbisonic = function() +{ + return (this._ambisonicOrder > 1 && this._viewer.audio.useWebAudio === true && typeof Omnitone !== "undefined"); }; /** @@ -1908,11 +1979,11 @@ FORGE.VideoHTML5.prototype._isAmbisonic = function() */ FORGE.VideoHTML5.prototype.update = function() { - if(this._decoder !== null && this._playing === true) + if(this._ambisonicsRenderer !== null && this._playing === true) { //Rotate the binaural renderer based on a Three.js camera object. - var m4 = this._viewer.renderer.camera.modelViewInverse; - this._decoder.setRotationMatrixFromCamera(m4); + var m4 = this._viewer.renderer.camera.modelView; + this._ambisonicsRenderer.setRotationMatrix4(m4.elements); } }; @@ -2080,6 +2151,17 @@ FORGE.VideoHTML5.prototype.destroy = function() { this._clearRequestedVideo(); + if (this._isAmbisonic() === true) + { + this._ambisonicsRenderer.output.disconnect(); + this._soundElementSource.disconnect(); + this._ambisonicsRendererInitializedSuccessBind = null; + this._ambisonicsRendererInitializedErrorBind = null; + this._ambisonicsRenderer = null; + this._soundElementSource = null; + this._context = null; + } + this._requestTimer.destroy(); this._requestTimer = null; @@ -2255,9 +2337,6 @@ FORGE.VideoHTML5.prototype.destroy = function() } //Nullify event listeners binded to this! - this._decoderInitializedSuccessBind = null; - this._decoderInitializedErrorBind = null; - this._onRequestErrorBind = null; this._onRequestLoadStartBind = null; this._onRequestLoadedMetaDataBind = null; @@ -2399,7 +2478,7 @@ Object.defineProperty(FORGE.VideoHTML5.prototype, "currentIndex", * Get the video object array. * @name FORGE.VideoHTML5#videos * @readonly - * @type {Array} + * @type {?Array} */ Object.defineProperty(FORGE.VideoHTML5.prototype, "videos", { @@ -2734,7 +2813,7 @@ Object.defineProperty(FORGE.VideoHTML5.prototype, "ambisonic", /** @this {FORGE.VideoHTML5} */ get: function () { - return this._ambisonic; + return (this._ambisonicOrder > 0); } }); diff --git a/src/media/Media.js b/src/media/Media.js index 2773e2e2..f0cf393f 100644 --- a/src/media/Media.js +++ b/src/media/Media.js @@ -252,7 +252,12 @@ FORGE.Media.prototype._parseConfig = function(config) var scene = this._viewer.story.scene; // check of the ambisonic state of the video sound prior to the video instanciation - this._displayObject = new FORGE.VideoHTML5(this._viewer, this._uid, null, null, (scene.hasSoundTarget(this._uid) === true && scene.isAmbisonic() === true ? true : false)); + var ambisonicOrder = 0; + if (scene.hasSoundTarget(this._uid) === true && scene.isAmbisonic() > 0) + { + ambisonicOrder = scene.isAmbisonic(); + } + this._displayObject = new FORGE.VideoHTML5(this._viewer, this._uid, null, null, ambisonicOrder); } // At this point, source.url is either a streaming address, a simple diff --git a/src/plugin/PluginObjectFactory.js b/src/plugin/PluginObjectFactory.js index f2115235..7f56ff79 100644 --- a/src/plugin/PluginObjectFactory.js +++ b/src/plugin/PluginObjectFactory.js @@ -234,10 +234,10 @@ FORGE.PluginObjectFactory.prototype.button = function(config) * @param {?(string|FORGE.VideoQuality|Array<(FORGE.VideoQuality|string)>)=} config - The video configuration object * @param {string=} streaming - The video streaming format. Can be "HTML5" or "DASH". * @param {string=} qualityMode - The video quality mode. Can be "auto" or "manual". - * @param {boolean=} ambisonic - 3D sound including ambisonics. For "HTML5" video only. + * @param {number=} ambisonicOrder - Ambisonic order to use for FOA/HOA renderer. For "HTML5" video only. * @return {(FORGE.VideoHTML5|FORGE.VideoDash)} Returns the created FORGE.Video object. */ -FORGE.PluginObjectFactory.prototype.video = function(key, config, streaming, qualityMode, ambisonic) +FORGE.PluginObjectFactory.prototype.video = function(key, config, streaming, qualityMode, ambisonicOrder) { var video; @@ -247,7 +247,7 @@ FORGE.PluginObjectFactory.prototype.video = function(key, config, streaming, qua } else { - video = new FORGE.VideoHTML5(this._viewer, key, config, qualityMode, ambisonic); + video = new FORGE.VideoHTML5(this._viewer, key, config, qualityMode, ambisonicOrder); } video.onDestroy.addOnce(this._destroyObjectHandler, this); diff --git a/src/render/RenderManager.js b/src/render/RenderManager.js index 4f3de080..d0596c25 100644 --- a/src/render/RenderManager.js +++ b/src/render/RenderManager.js @@ -357,8 +357,22 @@ FORGE.RenderManager.prototype._initSound = function(sceneConfig) if (typeof soundConfig.source.url !== "undefined" && soundConfig.source.url !== "") { + // check of the ambisonic state of the video sound prior to the video instanciation + var ambisonicOrder = 0; + if (sceneConfig.sound.type === FORGE.SoundType.AMBISONIC) + { + if (typeof sceneConfig.sound.order !== "undefined" && sceneConfig.sound.order !== null && sceneConfig.sound.order > 1) + { + ambisonicOrder = sceneConfig.sound.order; // HOA + } + else + { + ambisonicOrder = 1; // FOA + } + } + // Warning : UID is not registered and applied to the FORGE.Sound object for registration - this._mediaSound = new FORGE.Sound(this._viewer, sceneConfig.sound.uid, sceneConfig.sound.source.url, (sceneConfig.sound.type === FORGE.SoundType.AMBISONIC)); + this._mediaSound = new FORGE.Sound(this._viewer, sceneConfig.sound.uid, sceneConfig.sound.source.url, ambisonicOrder); if (typeof soundConfig.options !== "undefined" && soundConfig.options !== null) { diff --git a/src/story/Scene.js b/src/story/Scene.js index 2ccec42c..3a2bf9d6 100755 --- a/src/story/Scene.js +++ b/src/story/Scene.js @@ -468,19 +468,24 @@ FORGE.Scene.prototype.hasSoundTarget = function(uid) }; /** - * Know if an ambisonic sound is attached to the scene? + * Know if an ambisonic order sound is attached to the scene? * @method FORGE.Scene#isAmbisonic - * @return {boolean} Returns true if the scene has an ambisonic sound source, false if not. + * @return {number} Returns the ambisonic order number if the scene has an ambisonic sound source, 0 if not. */ FORGE.Scene.prototype.isAmbisonic = function() { //@todo real check of the UID target object rather then the isAmbisonic method of the FORGE.Scene if (this.hasSoundSource() === true && this._config.sound.type === FORGE.SoundType.AMBISONIC) { - return true; + if (typeof this._config.sound.order !== "undefined" && this._config.sound.order !== null && this._config.sound.order > 1) + { + return this._config.sound.order; + } + + return 1; } - return false; + return 0; }; /**