Comparing version 0.2.2 to 1.0.2
@@ -1,16 +0,16 @@ | ||
!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 s=i[n]={exports:{},id:n,loaded:!1};return t[n].call(s.exports,s,s.exports,e),s.loaded=!0,s.exports}var i={};return e.m=t,e.c=i,e.p="",e(0)}([function(t,e,i){/** | ||
* @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. | ||
*/ | ||
"use strict";e.Omnitone=i(1)},function(t,e,i){"use strict";var n={},s=i(2),o=i(4),a=i(5),r=i(6),h=i(7),c=i(8),_=i(9),l=i(12);n.loadAudioBuffers=function(t,e){return new Promise(function(i,n){new s(t,e,function(t){i(t)},n)})},n.createFOAConvolver=function(t,e){return new o(t,e)},n.createFOARouter=function(t,e){return new a(t,e)},n.createFOARotator=function(t){return new r(t)},n.createFOAPhaseMatchedFilter=function(t){return new h(t)},n.createFOAVirtualSpeaker=function(t,e){return new c(t,e)},n.createFOADecoder=function(t,e,i){return new _(t,e,i)},n.createFOARenderer=function(t,e){return new l(t,e)},t.exports=n},function(t,e,i){"use strict";function n(t,e,i,n,o){this._context=t,this._buffers=new Map,this._loadingTasks={},this._resolve=i,this._reject=n,this._progress=o;for(var a=0;a<e.length;a++){var r=e[a];if(this._loadingTasks.hasOwnProperty(r.name))return void s.log("Duplicated filename when loading: "+r.name);this._loadingTasks[r.name]=0,this._loadAudioFile(r)}}var s=i(3);n.prototype._loadAudioFile=function(t){var e=new XMLHttpRequest;e.open("GET",t.url),e.responseType="arraybuffer";var i=this;e.onload=function(){200===e.status?i._context.decodeAudioData(e.response,function(e){i._done(t.name,e)},function(e){s.log("Decoding failure: "+t.url+" ("+e+")"),i._done(t.name,null)}):(s.log("XHR Error: "+t.url+" ("+e.statusText+")"),i._done(t.name,null))},e.onerror=function(e){s.log("XHR Network failure: "+t.url),i._done(t.name,null)},e.send()},n.prototype._done=function(t,e){this._loadingTasks[t]=null!==e?"loaded":"failed",this._buffers.set(t,e),this._updateProgress(t)},n.prototype._updateProgress=function(t){var e=0,i=0,n=0;for(var s in this._loadingTasks)n++,"loaded"===this._loadingTasks[s]?e++:"failed"===this._loadingTasks[s]&&i++;return"function"==typeof this._progress?void this._progress(t,e,n):e===n?void this._resolve(this._buffers):e+i===n?void this._reject(this._buffers):void 0},t.exports=n},function(t,e){"use strict";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: 700","font-weight: 400","color: #AAA"])},e.invertMatrix4=function(t,e){var i=e[0],n=e[1],s=e[2],o=e[3],a=e[4],r=e[5],h=e[6],c=e[7],_=e[8],l=e[9],u=e[10],p=e[11],g=e[12],f=e[13],m=e[14],d=e[15],v=i*r-n*a,x=i*h-s*a,R=i*c-o*a,M=n*h-s*r,y=n*c-o*r,w=s*c-o*h,A=_*f-l*g,b=_*m-u*g,F=_*d-p*g,C=l*m-u*f,H=l*d-p*f,X=u*d-p*m,G=v*X-x*H+R*C+M*F-y*b+w*A;return G?(G=1/G,t[0]=(r*X-h*H+c*C)*G,t[1]=(s*H-n*X-o*C)*G,t[2]=(f*w-m*y+d*M)*G,t[3]=(u*y-l*w-p*M)*G,t[4]=(h*F-a*X-c*b)*G,t[5]=(i*X-s*F+o*b)*G,t[6]=(m*R-g*w-d*x)*G,t[7]=(_*w-u*R+p*x)*G,t[8]=(a*H-r*F+c*A)*G,t[9]=(n*F-i*H-o*A)*G,t[10]=(g*y-f*R+d*v)*G,t[11]=(l*R-_*y-p*v)*G,t[12]=(r*b-a*C-h*A)*G,t[13]=(i*C-n*b+s*A)*G,t[14]=(f*x-g*M-m*v)*G,t[15]=(_*M-l*x+u*v)*G,t):null}},function(t,e){"use strict";function i(t,e){if(4!==e.IR.numberOfChannels)throw"IR does not have 4 channels. cannot proceed.";this._active=!1,this._context=t,this._input=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(),this._input.connect(this._mergerWY,0,0),this._input.connect(this._mergerWY,1,1),this._input.connect(this._mergerZX,2,0),this._input.connect(this._mergerZX,3,1),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),this._convolverWY.normalize=!1,this._convolverZX.normalize=!1,this._setHRIRBuffers(e.IR),this._inverter.gain.value=-1,this.input=this._input,this.output=this._summingBus,this.enable()}i.prototype._setHRIRBuffers=function(t){this._hrirWY=this._context.createBuffer(2,t.length,t.sampleRate),this._hrirZX=this._context.createBuffer(2,t.length,t.sampleRate),this._hrirWY.getChannelData(0).set(t.getChannelData(0)),this._hrirWY.getChannelData(1).set(t.getChannelData(1)),this._hrirZX.getChannelData(0).set(t.getChannelData(2)),this._hrirZX.getChannelData(1).set(t.getChannelData(3)),this._convolverWY.buffer=this._hrirWY,this._convolverZX.buffer=this._hrirZX},i.prototype.enable=function(){this._mergerBinaural.connect(this._summingBus),this._active=!0},i.prototype.disable=function(){this._mergerBinaural.disconnect(),this._active=!1},t.exports=i},function(t,e){"use strict";function i(t,e){this._context=t,this._splitter=this._context.createChannelSplitter(4),this._merger=this._context.createChannelMerger(4),this._channelMap=e||n.DEFAULT,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]),this.input=this._splitter,this.output=this._merger}var n={DEFAULT:[0,1,2,3],APPLE:[2,0,1,3],FUMA:[0,3,1,2]};i.prototype.setChannelMap=function(t){t&&(this._channelMap=t,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]))},i.CHANNEL_MAP=n,t.exports=i},function(t,e){"use strict";function i(t){this._context=t,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),this._splitter.connect(this._inY,1),this._splitter.connect(this._inZ,2),this._splitter.connect(this._inX,3),this._inY.gain.value=-1,this._inX.gain.value=-1,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),this._splitter.connect(this._merger,0,0),this._outY.connect(this._merger,0,1),this._outZ.connect(this._merger,0,2),this._outX.connect(this._merger,0,3),this._outY.gain.value=-1,this._outX.gain.value=-1,this.setRotationMatrix(new Float32Array([1,0,0,0,1,0,0,0,1])),this.input=this._splitter,this.output=this._merger}i.prototype.setRotationMatrix=function(t){this._m0.gain.value=t[0],this._m1.gain.value=t[1],this._m2.gain.value=t[2],this._m3.gain.value=t[3],this._m4.gain.value=t[4],this._m5.gain.value=t[5],this._m6.gain.value=t[6],this._m7.gain.value=t[7],this._m8.gain.value=t[8]},i.prototype.setRotationMatrix4=function(t){this._m0.gain.value=t[0],this._m1.gain.value=t[1],this._m2.gain.value=t[2],this._m3.gain.value=t[4],this._m4.gain.value=t[5],this._m5.gain.value=t[6],this._m6.gain.value=t[8],this._m7.gain.value=t[9],this._m8.gain.value=t[10]},i.prototype.getRotationMatrix=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]},t.exports=i},function(t,e,i){"use strict";function n(t,e){var i=Math.tan(Math.PI*t/e),n=i*i,s=n+2*i+1;return{lowpassA:[1,2*(n-1)/s,(n-2*i+1)/s],lowpassB:[n/s,2*n/s,n/s],hipassA:[1,2*(n-1)/s,(n-2*i+1)/s],hipassB:[1/s,-2/s,1/s]}}function s(t){if(this._context=t,this._input=this._context.createGain(),this._context.createIIRFilter){var e=n(a,this._context.sampleRate);this._lpf=this._context.createIIRFilter(e.lowpassB,e.lowpassA),this._hpf=this._context.createIIRFilter(e.hipassB,e.hipassA)}else o.log("IIR filter is missing. Using Biquad filter instead."),this._lpf=this._context.createBiquadFilter(),this._hpf=this._context.createBiquadFilter(),this._lpf.frequency.value=a,this._hpf.frequency.value=a,this._hpf.type="highpass";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);var i=this._context.currentTime;this._gainHighW.gain.setValueAtTime(-1*r[0],i),this._gainHighY.gain.setValueAtTime(-1*r[1],i),this._gainHighZ.gain.setValueAtTime(-1*r[2],i),this._gainHighX.gain.setValueAtTime(-1*r[3],i),this.input=this._input,this.output=this._merger}var o=i(3),a=690,r=[1.4142,.8166,.8166,.8166];t.exports=s},function(t,e){"use strict";function i(t,e){if(2!==e.IR.numberOfChannels)throw"IR does not have 2 channels. cannot proceed.";this._active=!1,this._context=t,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=!1,this._convolver.buffer=e.IR,this._gain.gain.value=e.gain,this._cW.gain.value=e.coefficients[0],this._cY.gain.value=e.coefficients[1],this._cZ.gain.value=e.coefficients[2],this._cX.gain.value=e.coefficients[3],this.input=this._input}i.prototype.enable=function(){this._gain.connect(this._context.destination),this._active=!0},i.prototype.disable=function(){this._gain.disconnect(),this._active=!1},t.exports=i},function(t,e,i){"use strict";function n(t,e,i){this._isDecoderReady=!1,this._context=t,this._videoElement=e,this._decodingMode="ambisonic",this._postGainDB=p,this._HRTFSetUrl=u,this._channelMap=o.CHANNEL_MAP.DEFAULT,i&&(i.postGainDB&&(this._postGainDB=i.postGainDB),i.HRTFSetUrl&&(this._HRTFSetUrl=i.HRTFSetUrl),i.channelMap&&(this._channelMap=i.channelMap)),this._speakerData=[];for(var n=0;n<c.length;++n)this._speakerData.push({name:c[n].name,url:this._HRTFSetUrl+"/"+c[n].url,coef:c[n].coef});this._tempMatrix4=new Float32Array(16)}var s=i(2),o=i(5),a=i(6),r=i(7),h=i(8),c=i(10),_=i(3),l=i(11),u="https://raw.githubusercontent.com/GoogleChrome/omnitone/master/build/resources/",p=0;n.prototype.initialize=function(){_.log("Version: "+l),_.log("Initializing... (mode: "+this._decodingMode+")");var t=this._channelMap.toString(),e=o.CHANNEL_MAP.DEFAULT.toString();t!==e&&_.log("Remapping channels (["+e+"] -> ["+t+"])"),this._audioElementSource=this._context.createMediaElementSource(this._videoElement),this._foaRouter=new o(this._context,this._channelMap),this._foaRotator=new a(this._context),this._foaPhaseMatchedFilter=new r(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);var i=Math.pow(10,this._postGainDB/20);_.log("Gain compensation: "+i+" ("+this._postGainDB+"dB)");var n=this;return new Promise(function(t,e){new s(n._context,n._speakerData,function(e){for(var s=0;s<n._speakerData.length;++s)n._foaVirtualSpeakers[s]=new h(n._context,{coefficients:n._speakerData[s].coef,IR:e.get(n._speakerData[s].name),gain:i}),n._foaPhaseMatchedFilter.output.connect(n._foaVirtualSpeakers[s].input);n.setMode(n._decodingMode),n._isDecoderReady=!0,_.log("HRTF IRs are loaded successfully. The decoder is ready."),t()},e)})},n.prototype.setRotationMatrix=function(t){this._foaRotator.setRotationMatrix(t)},n.prototype.setRotationMatrixFromCamera=function(t){_.invertMatrix4(this._tempMatrix4,t.elements),this._foaRotator.setRotationMatrix4(this._tempMatrix4)},n.prototype.setMode=function(t){if(t!==this._decodingMode){switch(t){case"bypass":this._decodingMode="bypass";for(var e=0;e<this._foaVirtualSpeakers.length;++e)this._foaVirtualSpeakers[e].disable();this._bypass.connect(this._context.destination);break;case"ambisonic":this._decodingMode="ambisonic";for(var e=0;e<this._foaVirtualSpeakers.length;++e)this._foaVirtualSpeakers[e].enable();this._bypass.disconnect();break;case"off":this._decodingMode="off";for(var e=0;e<this._foaVirtualSpeakers.length;++e)this._foaVirtualSpeakers[e].disable();this._bypass.disconnect()}_.log("Decoding mode changed. ("+t+")")}},t.exports=n},function(t,e){var i=[{name:"E35_A135",url:"E35_A135.wav",gainFactor:1,coef:[.125,.216495,.21653,-.216495]},{name:"E35_A-135",url:"E35_A-135.wav",gainFactor:1,coef:[.125,-.216495,.21653,-.216495]},{name:"E-35_A135",url:"E-35_A135.wav",gainFactor:1,coef:[.125,.216495,-.21653,-.216495]},{name:"E-35_A-135",url:"E-35_A-135.wav",gainFactor:1,coef:[.125,-.216495,-.21653,-.216495]},{name:"E35_A45",url:"E35_A45.wav",gainFactor:1,coef:[.125,.216495,.21653,.216495]},{name:"E35_A-45",url:"E35_A-45.wav",gainFactor:1,coef:[.125,-.216495,.21653,.216495]},{name:"E-35_A45",url:"E-35_A45.wav",gainFactor:1,coef:[.125,.216495,-.21653,.216495]},{name:"E-35_A-45",url:"E-35_A-45.wav",gainFactor:1,coef:[.125,-.216495,-.21653,.216495]}];t.exports=i},function(t,e){"use strict";t.exports="0.2.2"},function(t,e,i){"use strict";function n(t,e){this._context=t,this._HRIRUrl=_,this._channelMap=o.CHANNEL_MAP.DEFAULT,this._renderingMode="ambisonic",e&&(e.HRIRUrl&&(this._HRIRUrl=e.HRIRUrl),e.renderingMode&&(this._renderingMode=e.renderingMode),e.channelMap&&(this._channelMap=e.channelMap)),this._isRendererReady=!1}var s=i(2),o=i(5),a=i(6),r=i(4),h=i(3),c=i(11),_="resources/sh_hrir_o_1.wav";n.prototype.initialize=function(){return h.log("Version: "+c),h.log("Initializing... (mode: "+this._renderingMode+")"),h.log("Rendering via SH-MaxRE convolution."),this._tempMatrix4=new Float32Array(16),new Promise(this._initializeCallback.bind(this))},n.prototype._initializeCallback=function(t,e){var i="FOA_HRIR_AUDIOBUFFER";new s(this._context,[{name:i,url:this._HRIRUrl}],function(e){this.input=this._context.createGain(),this._bypass=this._context.createGain(),this._foaRouter=new o(this._context,this._channelMap),this._foaRotator=new a(this._context),this._foaConvolver=new r(this._context,{IR:e.get(i)}),this.output=this._context.createGain(),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),this.setChannelMap(this._channelMap),this.setRenderingMode(this._renderingMode),this._isRendererReady=!0,h.log("HRIRs are loaded successfully. The renderer is ready."),t()}.bind(this),function(t){var n="Initialization failed: "+i+" is "+t.get(0)+".";h.log(n),e(n)})},n.prototype.setChannelMap=function(t){this._isRendererReady&&t.toString()!==this._channelMap.toString()&&(h.log("Remapping channels (["+this._channelMap.toString()+"] -> ["+t.toString()+"])."),this._channelMap=t.slice(),this._foaRouter.setChannelMap(this._channelMap))},n.prototype.setRotationMatrix=function(t){this._isRendererReady&&this._foaRotator.setRotationMatrix(t)},n.prototype.setRotationMatrixFromCamera=function(t){this._isRendererReady&&(h.invertMatrix4(this._tempMatrix4,t.elements),this._foaRotator.setRotationMatrix4(this._tempMatrix4))},n.prototype.setRenderingMode=function(t){if(t!==this._renderingMode){switch(t){case"bypass":this._renderingMode="bypass",this._foaConvolver.disable(),this._bypass.connect(this.output);break;case"ambisonic":this._renderingMode="ambisonic",this._foaConvolver.enable(),this._bypass.disconnect();break;case"off":this._renderingMode="off",this._foaConvolver.disable(),this._bypass.disconnect();break;default:return void h.log('Rendering mode "'+t+'" is not supported.')}h.log("Rendering mode changed. ("+t+")")}},t.exports=n}])}); | ||
!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,F,H;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,F=u*m-f*g,(H=v*F-x*B+b*O+M*C-R*A+w*y)?(H=1/H,t[0]=(a*F-h*B+c*O)*H,t[1]=(o*B-n*F-s*O)*H,t[2]=(d*w-g*R+m*M)*H,t[3]=(u*R-l*w-f*M)*H,t[4]=(h*C-r*F-c*A)*H,t[5]=(i*F-o*C+s*A)*H,t[6]=(g*b-p*w-m*x)*H,t[7]=(_*w-u*b+f*x)*H,t[8]=(r*B-a*C+c*y)*H,t[9]=(n*C-i*B-s*y)*H,t[10]=(p*R-d*b+m*v)*H,t[11]=(l*b-_*R-f*v)*H,t[12]=(a*A-r*O-h*y)*H,t[13]=(i*O-n*A+o*y)*H,t[14]=(d*x-p*M-g*v)*H,t[15]=(_*M-l*x+u*v)*H,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;h<i.length;++h)s>32&&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<i.length;++e)for(let t=0;t<i[e].numberOfChannels;++t)r.getChannelData(a++).set(i[e].getChannelData(t));return r},e.splitBufferbyChannel=function(t,i,n){i.numberOfChannels<=n&&e.throw("Utils.splitBuffer: Insufficient number of channels. ("+i.numberOfChannels+" splitted by "+n+")");let o=[],s=0;const r=Math.ceil(i.numberOfChannels/n);for(let e=0;e<r;++e){let e=t.createBuffer(n,i.length,i.sampleRate);for(let t=0;t<n;++t)s<i.numberOfChannels&&e.getChannelData(t).set(i.getChannelData(s++));o.push(e)}return bufferList}},function(t,e,i){"use strict";function n(t,e,i){this._context=o.isAudioContext(t)?t:o.throw("BufferList: Invalid BaseAudioContext."),this._bufferList=[],this._bufferData=e.slice(0),this._numberOfTasks=this._bufferData.length,this._options={verbose:!1},i&&(this._options=Boolean(i.verbose)),this._resolveHandler=null,this._rejectHandler=new Function}const o=i(0);n.prototype.load=function(){return new Promise(this._promiseGenerator.bind(this))},n.prototype._promiseGenerator=function(t,e){"function"!=typeof t?o.throw("BufferList: Invalid Promise resolver."):this._resolveHandler=t,"function"==typeof e&&(this._rejectHandler=e);for(let i=0;i<this._bufferData.length;++i)this._launchAsyncLoadTask(i)},n.prototype._launchAsyncLoadTask=function(t){const e=new XMLHttpRequest;e.open("GET",this._bufferData[t]),e.responseType="arraybuffer";const i=this;e.onload=function(){if(200===e.status)i._context.decodeAudioData(e.response,function(e){i._updateProgress(t,e)},function(e){i._updateProgress(t,null);const n='BufferList: decoding "'+i._bufferData[t]+'" failed. ('+e+")";o.throw(n),i._rejectHandler(n)});else{const n='BufferList: XHR error while loading "'+i._bufferData[t]+"("+e.statusText+")";o.throw(n),i._rejectHandler(n)}},e.onerror=function(e){o.throw('BufferList: XHR network failed on loading "'+i._bufferData[t]+'".'),i._updateProgress(t,null),i._rejectHandler()},e.send()},n.prototype._updateProgress=function(t,e){this._bufferList[t]=e,this._options.verbose&&o.log('BufferList: "'+this._bufferData[t]+'" successfullyloaded.'),0==--this._numberOfTasks&&(o.log("BufferList: "+this._bufferData.length+" files loaded successfully."),this._resolveHandler(this._bufferList))},t.exports=n},function(t,e,i){"use strict";function n(t,e){this._context=t,this._splitter=this._context.createChannelSplitter(4),this._merger=this._context.createChannelMerger(4),this.input=this._splitter,this.output=this._merger,this.setChannelMap(e||o.DEFAULT)}const o={DEFAULT:[0,1,2,3],SAFARI:[2,0,1,3],FUMA:[0,3,1,2]};n.prototype.setChannelMap=function(t){Array.isArray(t)&&(this._channelMap=t,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]))},n.ChannelMap=o,t.exports=n},function(t,e,i){"use strict";function n(t){this._context=t,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),this._splitter.connect(this._inY,1),this._splitter.connect(this._inZ,2),this._splitter.connect(this._inX,3),this._inY.gain.value=-1,this._inX.gain.value=-1,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),this._splitter.connect(this._merger,0,0),this._outY.connect(this._merger,0,1),this._outZ.connect(this._merger,0,2),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])),this.input=this._splitter,this.output=this._merger}n.prototype.setRotationMatrix3=function(t){this._m0.gain.value=t[0],this._m1.gain.value=t[1],this._m2.gain.value=t[2],this._m3.gain.value=t[3],this._m4.gain.value=t[4],this._m5.gain.value=t[5],this._m6.gain.value=t[6],this._m7.gain.value=t[7],this._m8.gain.value=t[8]},n.prototype.setRotationMatrix4=function(t){this._m0.gain.value=t[0],this._m1.gain.value=t[1],this._m2.gain.value=t[2],this._m3.gain.value=t[4],this._m4.gain.value=t[5],this._m5.gain.value=t[6],this._m6.gain.value=t[8],this._m7.gain.value=t[9],this._m8.gain.value=t[10]},n.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]},n.prototype.getRotationMatrix4=function(){let t=new Float32Array(16);return t[0]=this._m0.gain.value,t[1]=this._m1.gain.value,t[2]=this._m2.gain.value,t[4]=this._m3.gain.value,t[5]=this._m4.gain.value,t[6]=this._m5.gain.value,t[8]=this._m6.gain.value,t[9]=this._m7.gain.value,t[10]=this._m8.gain.value,t},t.exports=n},function(t,e,i){"use strict";function n(t,e){this._context=t,this._active=!1,this._isBufferLoaded=!1,this._buildAudioGraph(),e&&this.setHRIRBufferList(e),this.enable()}n.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(),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),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),this._convolverWY.normalize=!1,this._convolverZX.normalize=!1,this._inverter.gain.value=-1,this.input=this._splitterWYZX,this.output=this._summingBus},n.prototype.setHRIRBufferList=function(t){this._isBufferLoaded||(this._convolverWY.buffer=t[0],this._convolverZX.buffer=t[1],this._isBufferLoaded=!0)},n.prototype.enable=function(){this._mergerBinaural.connect(this._summingBus),this._active=!0},n.prototype.disable=function(){this._mergerBinaural.disconnect(),this._active=!1},t.exports=n},function(t,e,i){"use strict";function n(t,e,i,n,s){this._context=t,this._buffers=new Map,this._loadingTasks={},this._resolve=i,this._reject=n,this._progress=s;for(let r=0;r<e.length;r++){const t=e[r];if(this._loadingTasks.hasOwnProperty(t.name))return void o.log("Duplicated filename when loading: "+t.name);this._loadingTasks[t.name]=0,this._loadAudioFile(t)}}const o=i(0);n.prototype._loadAudioFile=function(t){const e=new XMLHttpRequest;e.open("GET",t.url),e.responseType="arraybuffer";const i=this;e.onload=function(){200===e.status?i._context.decodeAudioData(e.response,function(e){i._done(t.name,e)},function(e){o.log("Decoding failure: "+t.url+" ("+e+")"),i._done(t.name,null)}):(o.log("XHR Error: "+t.url+" ("+e.statusText+")"),i._done(t.name,null))},e.onerror=function(e){o.log("XHR Network failure: "+t.url),i._done(t.name,null)},e.send()},n.prototype._done=function(t,e){this._loadingTasks[t]=null!==e?"loaded":"failed",this._buffers.set(t,e),this._updateProgress(t)},n.prototype._updateProgress=function(t){let e=0,i=0,n=0;for(const o in this._loadingTasks)Object.prototype.hasOwnProperty.call(this._loadingTasks,o)&&(n++,"loaded"===this._loadingTasks[o]?e++:"failed"===this._loadingTasks[o]&&i++);"function"!=typeof this._progress?e!==n?e+i!==n||this._reject(this._buffers):this._resolve(this._buffers):this._progress(t,e,n)},t.exports=n},function(t,e,i){"use strict";function n(t,e){const i=Math.tan(Math.PI*t/e),n=i*i,o=n+2*i+1;return{lowpassA:[1,2*(n-1)/o,(n-2*i+1)/o],lowpassB:[n/o,2*n/o,n/o],hipassA:[1,2*(n-1)/o,(n-2*i+1)/o],hipassB:[1/o,-2/o,1/o]}}const o=i(0),s=690,r=[1.4142,.8166,.8166,.8166];t.exports=function(t){if(this._context=t,this._input=this._context.createGain(),this._context.createIIRFilter){const t=n(s,this._context.sampleRate);this._lpf=this._context.createIIRFilter(t.lowpassB,t.lowpassA),this._hpf=this._context.createIIRFilter(t.hipassB,t.hipassA)}else o.log("IIR filter is missing. Using Biquad filter instead."),this._lpf=this._context.createBiquadFilter(),this._hpf=this._context.createBiquadFilter(),this._lpf.frequency.value=s,this._hpf.frequency.value=s,this._hpf.type="highpass";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);const e=this._context.currentTime;this._gainHighW.gain.setValueAtTime(-1*r[0],e),this._gainHighY.gain.setValueAtTime(-1*r[1],e),this._gainHighZ.gain.setValueAtTime(-1*r[2],e),this._gainHighX.gain.setValueAtTime(-1*r[3],e),this.input=this._input,this.output=this._merger}},function(t,e,i){"use strict";function n(t,e){if(2!==e.IR.numberOfChannels)throw new Error("IR does not have 2 channels. cannot proceed.");this._active=!1,this._context=t,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=!1,this._convolver.buffer=e.IR,this._gain.gain.value=e.gain,this._cW.gain.value=e.coefficients[0],this._cY.gain.value=e.coefficients[1],this._cZ.gain.value=e.coefficients[2],this._cX.gain.value=e.coefficients[3],this.input=this._input}n.prototype.enable=function(){this._gain.connect(this._context.destination),this._active=!0},n.prototype.disable=function(){this._gain.disconnect(),this._active=!1},t.exports=n},function(t,e,i){"use strict";const n=[null,["omnitone-foa-1.wav","omnitone-foa-2.wav"],["omnitone-soa-1.wav","omnitone-soa-2.wav","omnitone-soa-3.wav","omnitone-soa-4.wav","omnitone-soa-5.wav"],["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"]],o={GITHUB:"https://cdn.rawgit.com/GoogleChrome/omnitone/master/build/resources/"};t.exports.getPathList=function(t){let e,i,s;const r=t||{ambisonicOrder:1,source:"github"};switch(r.ambisonicOrder){case 1:case 2:case 3:e=n[r.ambisonicOrder];break;default:e=n[0]}switch(r.source){case"github":default:i=o.GITHUB}return e&&(s=[],e.forEach(function(t){s.push(i+t)})),s}},function(t,e,i){"use strict";function n(t,e,i){this._context=t,this._active=!1,this._isBufferLoaded=!1,this._ambisonicOrder=e,this._numberOfChannels=(this._ambisonicOrder+1)*(this._ambisonicOrder+1),this._buildAudioGraph(),i&&this.setHRIRBufferList(i),this.enable()}n.prototype._buildAudioGraph=function(){const t=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 e=0;e<t;++e)this._stereoMergers[e]=this._context.createChannelMerger(2),this._convolvers[e]=this._context.createConvolver(),this._stereoSplitters[e]=this._context.createChannelSplitter(2),this._convolvers[e].normalize=!1;for(let e=0;e<=this._ambisonicOrder;++e)for(let t=-e;t<=e;t++){const i=e*e+e+t,n=Math.floor(i/2);this._inputSplitter.connect(this._stereoMergers[n],i,i%2),this._stereoMergers[n].connect(this._convolvers[n]),this._convolvers[n].connect(this._stereoSplitters[n]),t>=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;e<t.length;++e)this._convolvers[e].buffer=t[e];this._isBufferLoaded=!0}},n.prototype.enable=function(){this._binauralMerger.connect(this._outputGain),this._active=!0},n.prototype.disable=function(){this._binauralMerger.disconnect(),this._active=!1},t.exports=n},function(t,e,i){"use strict";function n(t,e){return t===e?1:0}function o(t,e,i,n,o){const s=(n+e)*(2*e+1)+(i+e);t[e-1][s].gain.value=o}function s(t,e,i,n){const o=(n+e)*(2*e+1)+(i+e);return t[e-1][o].gain.value}function r(t,e,i,n,o){return n===o?s(t,1,e,1)*s(t,o-1,i,o-1)-s(t,1,e,-1)*s(t,o-1,i,1-o):n===-o?s(t,1,e,1)*s(t,o-1,i,1-o)+s(t,1,e,-1)*s(t,o-1,i,o-1):s(t,1,e,0)*s(t,o-1,i,n)}function a(t,e,i,n){return r(t,0,e,i,n)}function h(t,e,i,o){if(0===e)return r(t,1,1,i,o)+r(t,-1,-1,i,o);if(e>0){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<o;t++){s=n+t;for(let e=0;e<o;e++)r=n+e,a=t*o+e,this._gainNodeMatrix[h-1][a]=this._context.createGain(),this._splitter.connect(this._gainNodeMatrix[h-1][a],s),this._gainNodeMatrix[h-1][a].connect(this._merger,0,r)}}this._splitter.connect(this._merger,0,0),this.setRotationMatrix3(new Float32Array([1,0,0,0,1,0,0,0,1])),this.input=this._splitter,this.output=this._merger}f.prototype.setRotationMatrix3=function(t){for(let e=0;e<9;++e)this._gainNodeMatrix[0][e].gain.value=t[e];u(this._gainNodeMatrix)},f.prototype.setRotationMatrix4=function(t){this._gainNodeMatrix[0][0].gain.value=t[0],this._gainNodeMatrix[0][1].gain.value=t[1],this._gainNodeMatrix[0][2].gain.value=t[2],this._gainNodeMatrix[0][3].gain.value=t[4],this._gainNodeMatrix[0][4].gain.value=t[5],this._gainNodeMatrix[0][5].gain.value=t[6],this._gainNodeMatrix[0][6].gain.value=t[8],this._gainNodeMatrix[0][7].gain.value=t[9],this._gainNodeMatrix[0][8].gain.value=t[10],u(this._gainNodeMatrix)},f.prototype.getRotationMatrix3=function(){let t=new Float32Array(9);for(let e=0;e<9;++e)t[e]=this._gainNodeMatrix[0][e].gain.value;return t},f.prototype.getRotationMatrix4=function(){let t=new Float32Array(16);return t[0]=this._gainNodeMatrix[0][0].gain.value,t[1]=this._gainNodeMatrix[0][1].gain.value,t[2]=this._gainNodeMatrix[0][2].gain.value,t[4]=this._gainNodeMatrix[0][3].gain.value,t[5]=this._gainNodeMatrix[0][4].gain.value,t[6]=this._gainNodeMatrix[0][5].gain.value,t[8]=this._gainNodeMatrix[0][6].gain.value,t[9]=this._gainNodeMatrix[0][7].gain.value,t[10]=this._gainNodeMatrix[0][8].gain.value,t},f.prototype.getAmbisonicOrder=function(){return this._ambisonicOrder},t.exports=f},function(t,e,i){"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. | ||
*/ | ||
e.Omnitone=i(12)},function(t,e,i){"use strict";const n=i(1),o=i(4),s=i(13),r=i(6),a=i(15),h=i(3),c=i(2),_=i(7),l=i(9),u=i(16),f=i(10),p=i(17),d=i(0),g=i(18),m=i(5);let v={};v.browserInfo=p.getBrowserInfo(),v.loadAudioBuffers=function(t,e){return new Promise(function(i,n){new m(t,e,function(t){i(t)},n)})},v.createBufferList=function(t,e){return new n(t,e).load()},v.mergeBufferListByChannel=d.mergeBufferListByChannel,v.splitBufferbyChannel=d.splitBufferbyChannel,v.createFOAConvolver=function(t,e){return new o(t,e)},v.createFOARouter=function(t,e){return new c(t,e)},v.createFOARotator=function(t){return new h(t)},v.createFOAPhaseMatchedFilter=function(t){return new r(t)},v.createFOAVirtualSpeaker=function(t,e){return new _(t,e)},v.createFOADecoder=function(t,e,i){return d.log("WARNING: FOADecoder is deprecated in favor of FOARenderer."),new s(t,e,i)},v.createFOARenderer=function(t,e){return new a(t,e)},v.createHOARotator=function(t,e){return new f(t,e)},v.createHOAConvolver=function(t,e,i){return new l(t,e,i)},v.createHOARenderer=function(t,e){return new u(t,e)},d.log("Version "+g+" (running "+v.browserInfo.name+" "+v.browserInfo.version+" on "+v.browserInfo.platform+")"),"safari"===v.browserInfo.name.toLowerCase()&&(p.patchSafari(),d.log(v.browserInfo.name+" detected. Appliying polyfill...")),t.exports=v},function(t,e,i){"use strict";function n(t,e,i){this._isDecoderReady=!1,this._context=t,this._videoElement=e,this._decodingMode="ambisonic",this._postGainDB=u,this._HRTFSetUrl=l,this._channelMap=s.ChannelMap.DEFAULT,i&&(i.postGainDB&&(this._postGainDB=i.postGainDB),i.HRTFSetUrl&&(this._HRTFSetUrl=i.HRTFSetUrl),i.channelMap&&(this._channelMap=i.channelMap)),this._speakerData=[];for(let n=0;n<c.length;++n)this._speakerData.push({name:c[n].name,url:this._HRTFSetUrl+"/"+c[n].url,coef:c[n].coef});this._tempMatrix4=new Float32Array(16)}const o=i(5),s=i(2),r=i(3),a=i(6),h=i(7),c=i(14),_=i(0),l="https://raw.githubusercontent.com/GoogleChrome/omnitone/master/build/resources/";let u=0;n.prototype.initialize=function(){_.log("Initializing... (mode: "+this._decodingMode+")");let t=this._channelMap.toString(),e=s.ChannelMap.DEFAULT.toString();t!==e&&_.log("Remapping channels (["+e+"] -> ["+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<n._speakerData.length;++t)n._foaVirtualSpeakers[t]=new h(n._context,{coefficients:n._speakerData[t].coef,IR:e.get(n._speakerData[t].name),gain:i}),n._foaPhaseMatchedFilter.output.connect(n._foaVirtualSpeakers[t].input);n.setMode(n._decodingMode),n._isDecoderReady=!0,_.log("HRTF IRs are loaded successfully. The decoder is ready."),t()},e)})},n.prototype.setRotationMatrix=function(t){this._foaRotator.setRotationMatrix(t)},n.prototype.setRotationMatrixFromCamera=function(t){_.invertMatrix4(this._tempMatrix4,t.elements),this._foaRotator.setRotationMatrix4(this._tempMatrix4)},n.prototype.setMode=function(t){if(t!==this._decodingMode){switch(t){case"bypass":this._decodingMode="bypass";for(let t=0;t<this._foaVirtualSpeakers.length;++t)this._foaVirtualSpeakers[t].disable();this._bypass.connect(this._context.destination);break;case"ambisonic":this._decodingMode="ambisonic";for(let t=0;t<this._foaVirtualSpeakers.length;++t)this._foaVirtualSpeakers[t].enable();this._bypass.disconnect();break;case"off":this._decodingMode="off";for(let t=0;t<this._foaVirtualSpeakers.length;++t)this._foaVirtualSpeakers[t].disable();this._bypass.disconnect()}_.log("Decoding mode changed. ("+t+")")}},t.exports=n},function(t,e){const i=[{name:"E35_A135",url:"E35_A135.wav",gainFactor:1,coef:[.125,.216495,.21653,-.216495]},{name:"E35_A-135",url:"E35_A-135.wav",gainFactor:1,coef:[.125,-.216495,.21653,-.216495]},{name:"E-35_A135",url:"E-35_A135.wav",gainFactor:1,coef:[.125,.216495,-.21653,-.216495]},{name:"E-35_A-135",url:"E-35_A-135.wav",gainFactor:1,coef:[.125,-.216495,-.21653,-.216495]},{name:"E35_A45",url:"E35_A45.wav",gainFactor:1,coef:[.125,.216495,.21653,.216495]},{name:"E35_A-45",url:"E35_A-45.wav",gainFactor:1,coef:[.125,-.216495,.21653,.216495]},{name:"E-35_A45",url:"E-35_A45.wav",gainFactor:1,coef:[.125,.216495,-.21653,.216495]},{name:"E-35_A-45",url:"E-35_A-45.wav",gainFactor:1,coef:[.125,-.216495,-.21653,.216495]}];t.exports=i},function(t,e,i){"use strict";function n(t,e){this._context=c.isAudioContext(t)?t:c.throw("FOARenderer: Invalid BaseAudioContext."),this._config={channelMap:a.ChannelMap.DEFAULT,renderingMode:_.AMBISONIC},e.channelMap&&(Array.isArray(e.channelMap)&&4===e.channelMap.length?this._config.channelMap=e.channelMap:c.throw("FOARenderer: Invalid channel map. (got "+e.channelMap+")")),e.hrirPathList?Array.isArray(e.hrirPathList)&&2===e.hrirPathList.length?this._config.pathList=e.hrirPathList:c.throw("FOARenderer: Invalid HRIR URLs. It must be an array with 2 URLs to HRIR files. (got "+e.hrirPathList+")"):this._config.pathList=h.getPathList(),e.renderingMode&&(Object.values(_).includes(e.renderingMode)?this._config.renderingMode=e.renderingMode:c.log("FOARenderer: Invalid rendering mode order. (got"+e.renderingMode+') Fallbacks to the mode "ambisonic".')),this._buildAudioGraph(),this._tempMatrix4=new Float32Array(16),this._isRendererReady=!1}const o=i(1),s=i(4),r=i(3),a=i(2),h=i(8),c=i(0),_={AMBISONIC:"ambisonic",BYPASS:"bypass",OFF:"off"};n.prototype._buildAudioGraph=function(){this.input=this._context.createGain(),this.output=this._context.createGain(),this._bypass=this._context.createGain(),this._foaRouter=new a(this._context,this._config.channelMap),this._foaRotator=new r(this._context),this._foaConvolver=new s(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)},n.prototype._initializeCallback=function(t,e){let i=[];for(let n=0;n<this._config.pathList.length;++n)i.push({name:n,url:this._config.pathList[n]});new o(this._context,this._config.pathList).load().then(function(e){this._foaConvolver.setHRIRBufferList(e),this.setRenderingMode(this._config.renderingMode),this._isRendererReady=!0,c.log("FOARenderer: HRIRs loaded successfully. Ready."),t()}.bind(this),function(){const t="FOARenderer: HRIR loading/decoding failed.";c.throw(t),e(t)})},n.prototype.initialize=function(){return c.log("FOARenderer: Initializing... (mode: "+this._config.renderingMode+")"),new Promise(this._initializeCallback.bind(this),function(t){c.throw("FOARenderer: Initialization failed. ("+t+")")})},n.prototype.setChannelMap=function(t){this._isRendererReady&&t.toString()!==this._config.channelMap.toString()&&(c.log("Remapping channels (["+this._config.channelMap.toString()+"] -> ["+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;n<this._config.pathList.length;++n)i.push({name:n,url:this._config.pathList[n]});new o(this._context,this._config.pathList).load().then(function(e){this._hoaConvolver.setHRIRBufferList(e),this.setRenderingMode(this._config.renderingMode),this._isRendererReady=!0,h.log("HOARenderer: HRIRs loaded successfully. Ready."),t()}.bind(this),function(){const t="HOARenderer: HRIR loading/decoding failed.";h.throw(t),e(t)})},n.prototype.initialize=function(){return h.log("HOARenderer: Initializing... (mode: "+this._config.renderingMode+", ambisonic order: "+this._config.ambisonicOrder+")"),new Promise(this._initializeCallback.bind(this),function(t){h.throw("HOARenderer: Initialization failed. ("+t+")")})},n.prototype.setRotationMatrix3=function(t){this._isRendererReady&&this._hoaRotator.setRotationMatrix3(t)},n.prototype.setRotationMatrix4=function(t){this._isRendererReady&&this._hoaRotator.setRotationMatrix4(t)},n.prototype.setRenderingMode=function(t){if(t!==this._config.renderingMode){switch(t){case c.AMBISONIC:this._hoaConvolver.enable(),this._bypass.disconnect();break;case c.BYPASS:this._hoaConvolver.disable(),this._bypass.connect(this.output);break;case c.OFF:this._hoaConvolver.disable(),this._bypass.disconnect();break;default:return void h.log('HOARenderer: Rendering mode "'+t+'" is not supported.')}this._config.renderingMode=t,h.log("HOARenderer: Rendering mode changed. ("+t+")")}},t.exports=n},function(t,e,i){"use strict";e.getBrowserInfo=function(){const t=navigator.userAgent;let e,i=t.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*([\d\.]+)/i)||[];if(/trident/i.test(i[1]))return e=/\brv[ :]+(\d+)/g.exec(t)||[],{name:"IE",version:e[1]||""};if("Chrome"===i[1]&&null!=(e=t.match(/\bOPR|Edge\/(\d+)/)))return{name:"Opera",version:e[1]};i=i[2]?[i[1],i[2]]:[navigator.appName,navigator.appVersion,"-?"],null!=(e=t.match(/version\/([\d.]+)/i))&&i.splice(1,1,e[1]);let n=t.match(/android|ipad|iphone/i);return n||(n=t.match(/cros|linux|mac os x|windows/i)),{name:i[0],version:i[1],platform:n?n[0]:"unknown"}},e.patchSafari=function(){window.webkitAudioContext&&window.webkitOfflineAudioContext&&(window.AudioContext=window.webkitAudioContext,window.OfflineAudioContext=window.webkitOfflineAudioContext)}},function(t,e,i){"use strict";t.exports="1.0.2"}])}); |
{ | ||
"name": "omnitone", | ||
"version": "0.2.2", | ||
"version": "1.0.2", | ||
"description": "Spatial Audio Decoder in Web Audio API", | ||
@@ -15,3 +15,6 @@ "main": "build/omnitone.js", | ||
"chai": "^3.5.0", | ||
"copy-webpack-plugin": "^3.0.1", | ||
"copy-webpack-plugin": "^4.0.1", | ||
"eslint": "^4.7.0", | ||
"eslint-config-google": "^0.9.1", | ||
"jsdoc": "^3.5.4", | ||
"karma": "^1.6.0", | ||
@@ -23,9 +26,13 @@ "karma-chai": "^0.1.0", | ||
"mocha": "^3.0.0", | ||
"webpack": "^1.13.0" | ||
"uglifyjs-webpack-plugin": "^1.0.0-beta.2", | ||
"webpack": "^3.6.0" | ||
}, | ||
"scripts": { | ||
"build": "webpack --progress --color", | ||
"watch": "webpack --progress --color --watch", | ||
"build-all": "webpack --progress --color && webpack --config webpack.config.all.js --progress --color", | ||
"test": "node_modules/karma/bin/karma start" | ||
"build-omnitone-hrir": "./build-hrir.sh", | ||
"build-doc": "node_modules/.bin/jsdoc src/omnitone.js -d doc/", | ||
"eslint": "./node_modules/.bin/eslint src/*", | ||
"test": "node_modules/karma/bin/karma start", | ||
"watch": "webpack --progress --color --watch" | ||
}, | ||
@@ -32,0 +39,0 @@ "homepage": "https://github.com/GoogleChrome/omnitone", |
291
README.md
@@ -1,33 +0,36 @@ | ||
# Omnitone: Spatial Audio on the Web | ||
# Omnitone: Spatial Audio Rendering on the Web | ||
[![Travis](https://img.shields.io/travis/GoogleChrome/omnitone.svg)](https://travis-ci.org/GoogleChrome/omnitone) [![npm](https://img.shields.io/npm/v/omnitone.svg?colorB=4bc51d)](https://www.npmjs.com/package/omnitone) [![GitHub license](https://img.shields.io/badge/license-Apache%202-brightgreen.svg)](https://raw.githubusercontent.com/GoogleChrome/omnitone/master/LICENSE) | ||
Omnitone is a robust implementation of [FOA (first-order-ambisonic)](https://en.wikipedia.org/wiki/Ambisonics) decoder with binaural rendering written in Web Audio API. Its decoding process is based on multiple gain nodes for ambisonic gain matrix and convolutions for [HRTF](https://en.wikipedia.org/wiki/Head-related_transfer_function) binaural rendering, ensuring the optimum performance. | ||
Omnitone is a robust implementation of [ambisonic](https://en.wikipedia.org/wiki/Ambisonics) decoding and binaural rendering written in Web Audio API. Its rendering process is powered by the fast native features from Web Audio API (GainNode and Convolver), ensuring the optimum performance. | ||
See Omnitone in action: | ||
The implementation of Omnitone is based on the [Google spatial media](https://github.com/google/spatial-media) specification. The incoming ambisonic stream MUST be configured to [ACN channel layout with SN3D normalization](https://en.wikipedia.org/wiki/Ambisonic_data_exchange_formats#ACN). | ||
- [Usage](#usage) | ||
+ [FOARenderer](#foarenderer) | ||
+ __[HOARenderer](#hoarenderer) (New!)__ | ||
+ [Rotation and Rendering Mode](#rotation-and-rendering-mode) | ||
- [API Documentation](https://rawgit.com/GoogleChrome/omnitone/master/doc/Omnitone.html) | ||
- [Development](#development) | ||
- [Audio Codec Compatibility](#audio-codec-compatibility) | ||
If you are looking for interactive panning based on Omnitone's ambisonic rendering, be sure to check out [Songbird](https://github.com/google/songbird) project! | ||
### Feature Highlights | ||
Omnitone offers __ambisonic decoding__ and __binaural rendering__ of: | ||
- First-order-ambisonic stream | ||
- High-order-ambisonic stream: 2nd and 3rd order. | ||
### Omnitone in action: | ||
- __[Project Home](https://googlechrome.github.io/omnitone/#home)__ | ||
- __[Omnitone Examples](https://rawgit.com/GoogleChrome/omnitone/master/examples/index.html)__ | ||
- __[Songbird WebGL Demo](https://cdn.rawgit.com/google/songbird/master/examples/webgl-demo.html)__ | ||
- __[JauntVR Gallery: Music](https://www.jauntvr.com/lobby/MusicLobby)__ | ||
- __[Plan8 Ambisonic Player](http://labs.plan8.se/ambisonics-webplayer/)__ | ||
- __[Forge.js](http://forgejs.org/samples/ambisonics)__ | ||
The implementation of Omnitone is based on the [Google spatial media](https://github.com/google/spatial-media) specification. The FOA input stream must be configured to ACN channel layout with SN3D normalization. | ||
- [Installation](#installation) | ||
- [Usage](#usage) | ||
- [Advanced Usage](#advanced-usage) | ||
+ __[FOARenderer](#foarenderer) (NEW in 0.2.0)__ | ||
+ [FOADecoder](#foadecoder) | ||
+ [FOARouter](#foarouter) | ||
+ [FOARotator](#foarotator) | ||
+ [FOAPhaseMatchedFilter](#foaphasematchedfilter) | ||
+ [FOAVirtualSpeaker](#foavirtualspeaker) | ||
- [Building](#building) | ||
- __[Test](#test) (NEW in 0.2.1)__ | ||
- [Audio Codec compatibility](#audio-codec-compatibility) | ||
- [Related Resources](#related-resouces) | ||
## How it works | ||
Omnitone abstracts various technical layers of audio spatialization. The input audio stream can be either a media element (video or audio tags) or a multichannel web audio source. The rotation of the sound field also can be easily linked to the mobile phone's sensor or the on-screen user interaction. | ||
The input audio stream can be either an `HTMLMediaElement` (`<video>` or `<audio>` tag) or a multichannel `AudioBufferSourceNode`. The rotation of the sound field also can be easily linked to the mobile phone's sensor or the on-screen user interaction. | ||
@@ -39,11 +42,2 @@ <p align="center"> | ||
## Installation | ||
Omnitone is designed to be used for the web front-end projects. So [NPM](https://www.npmjs.com/) is recommended if you want to install the library to your web project. You can also clone this repository and use the library file as usual. | ||
```bash | ||
npm install omnitone | ||
``` | ||
## Usage | ||
@@ -54,17 +48,20 @@ | ||
```html | ||
<!-- Use Omnitone from installed node_modules/ --> | ||
<script src="node_modules/omnitone/build/omnitone.min.js"></script> | ||
<script src="https://cdn.rawgit.com/GoogleChrome/omnitone/master/build/omnitone.min.js"></script> | ||
``` | ||
<!-- if you prefer to use CDN --> | ||
<script src="https://cdn.rawgit.com/GoogleChrome/omnitone/962089ca/build/omnitone.min.js"></script> | ||
Alternatively, you can install Omnitone as a part of your local development via [NPM](https://www.npmjs.com/package/omnitone). | ||
```bash | ||
npm install omnitone | ||
``` | ||
There are two different ways of rendering the ambisonic input stream with Omnitone. | ||
You can also `git clone` the repository and use the library file as usual. | ||
- [FOARenderer (Optimized)](#foarenderer-optimized) | ||
- [FOADeocoder (Fully-configurable)](#foadeocoder-fully-configurable) | ||
```bash | ||
git clone https://github.com/GoogleChrome/omnitone.git | ||
``` | ||
### FOARenderer (Optimized) | ||
### FOARenderer | ||
Newly introduced with the version 0.2.0, `FOARenderer` is a highly optimized ambisonic renderer that outperforms the original renderer by __~100%__. The renderer instance also behaves like an AudioNode, so it can be integrated to the existing WebAudio audio graph easily. | ||
`FOARenderer` is for the first-order-ambisonic stream, which contains 4 channels. | ||
@@ -80,211 +77,70 @@ ```js | ||
audioContext.createMediaElementSource(audioElement); | ||
var foaRenderer = Omnitone.createFOARenderer(audioContext, { | ||
HRIRUrl: 'https://cdn.rawgit.com/GoogleChrome/omnitone/962089ca/build/resources/sh_hrir_o_1.wav' | ||
}); | ||
var foaRenderer = Omnitone.createFOARenderer(audioContext); | ||
// Make connection and start play. | ||
foaRenderer.initialize().then(function () { | ||
audioElementSource.connect(foaRenderer.input); | ||
foaRenderer.output.connect(audioContext.destination); | ||
audioElement.play(); | ||
}); | ||
} | ||
foaRenderer.initialize().then(function() { | ||
audioElementSource.connect(foaRenderer.input); | ||
foaRenderer.output.connect(audioContext.destination); | ||
audioElement.play(); | ||
}); | ||
``` | ||
Currently the HRIR for `FOARenderer` is available on Omnitone's repository. If you do not need a configurable audio path for ambisonic rendering, `FOARenderer` is strongly recommended. See the example [here](https://cdn.rawgit.com/GoogleChrome/omnitone/0.2.2/examples/foa-renderer.html). | ||
### HOARenderer | ||
### FOADeocoder (Fully-configurable) | ||
`HOARenderer` is for the higher-order-ambisonic stream. Currently Omnitone supports 2nd and 3rd order ambisonics, which contain 9 channels and 16 channels respectively. | ||
The `FOADecoder` directly takes `<audio>` or `<video>` element along with the associated `AudioContext`. This object is a thin wrapper of building blocks described in the [advanced usage](#advanced-usage). | ||
```js | ||
// Prepare audio element to feed the ambisonic source audio feed. | ||
var audioElement = document.createElement('audio'); | ||
audioElement.src = 'resources/4ch-spatial-audio-file.wav'; | ||
// Create an AudioContext and an Omnitone decoder. | ||
var audioContext = new AudioContext(); | ||
var decoder = Omnitone.createFOADecoder(audioContext, audioElement); | ||
// Initialize and then start playing the audio element. | ||
decoder.initialize().then(function () { | ||
audioElement.play(); | ||
}, function (onInitializationError) { | ||
console.error(onInitializationError); | ||
}); | ||
// Works exactly the same way with FOARenderer. See the usage above. | ||
var hoaRenderer = Omnitone.createHOARenderer(audioContext); | ||
``` | ||
The decoder constructor accepts the context and the element as arguments. `FOADecoder` uses [HRIRs](https://github.com/google/spatial-media/tree/master/support/hrtfs/cube) from Google spatial media repository, but you can use a custom set of HRIR files as well. The initialization of a decoder instance returns a promise which resolves when the resources (i.e. impulse responses) are fully loaded. See the example [here](https://cdn.rawgit.com/GoogleChrome/omnitone/0.2.2/examples/foa-decoder.html). | ||
### Rotation and Rendering Mode | ||
### Basic Features: Rotation, ChannelMap, Rendering Mode | ||
The rotation matrix in Omnitone renderer can be updated inside of the application's animation loop to rotate the entire sound field. Omnitone supports both 3x3 and 4x4 rotation matrices(column-major). | ||
The rotation matrix (3x3, row-major) in the decoder can be updated inside of the graphics render loop. This operation rotates the entire sound field. The rotation matrix is commonly derived from the quaternion of the orientation sensor on the VR headset or the smartphone. Also Omnitone converts the coordinate system from the WebGL space to the audio space internally, so you need not to transform the matrix manually. | ||
Note that | ||
```js | ||
// Sound field rotation with 3x3 matrix. | ||
foaRenderer.setRotationMatrix(rotationMatrix); | ||
foaDecoder.setRotationMatrix(rotationMatrix); | ||
// Rotation with 3x3 or 4x4 matrix. | ||
renderer.setRotationMatrix3(rotationMatrix3); | ||
renderer.setRotationMatrix4(rotationMatrix4); | ||
``` | ||
If you prefer to work with 4x4 rotation matrix (e.g. Three.js camera), you can use the following method instead. | ||
For example, if you want to hook up the Three.js perspective camera: | ||
```js | ||
// Rotate the sound field by passing Three.js camera object. (4x4 matrix) | ||
foaRenderer.setRotationMatrixFromCamera(camera.matrix); | ||
foaDecoder.setRotationMatrixFromCamera(camera.matrix); | ||
renderer.setRotationMatrix4(camera.matrixWorld.elements); | ||
``` | ||
Use `setRenderingMode` or `setMode` method to change the setting of the decoder. This is useful when the media source does not have spatially encoded (e.g. stereo or mono) or when you want to reduce the CPU usage or the power consumption by disabling the decoder. | ||
Use `setRenderingMode` method to change the operation of the decoder. This is useful when switching between spatial media (ambisonic) and non-spatial media (mono or stereo) or when you want to save the CPU power by disabling the decoder. | ||
```js | ||
// Mono or regular multi-channel layouts. | ||
foaRenderer.setRenderingMode('bypass'); | ||
foaDecoder.setMode('bypass'); | ||
renderer.setRenderingMode('bypass'); | ||
// Use ambisonic rendering. | ||
foaRenderer.setRenderingMode('ambisonic'); | ||
foaDecoder.setMode('ambisonic'); | ||
renderer.setRenderingMode('ambisonic'); | ||
// Disable encoding completely. (audio processing disabled) | ||
foaRenderer.setRenderingMode('off'); | ||
foaDecoder.setMode('off'); | ||
renderer.setRenderingMode('off'); | ||
``` | ||
## Advanced Usage | ||
## Development | ||
Omnitone also provides various building blocks for the first-order-ambisonic decoding and the binaural rendering. The `FOADecoder` is just a ready-made object built with those components. You can create them and connect together build your own decoding mechanism. | ||
### Building Omnitone Locally | ||
### FOARenderer | ||
For the development, get a copy of the repository first and run the following script to build the library. Omnitone uses [WebPack](https://webpack.github.io/) to compile the sources. | ||
`FOARenderer` is an optimized FOA stream binaural renderer based on SH-MaxRe HRIR. It uses a specially crafted HRIR for the optimized audio processing, and the URL for HRIR is shown below. `FOARenderer` must be initialized before its usage. | ||
```js | ||
var foaRenderer = Omnitone.createFOARenderer(audioContext, { | ||
HRIRUrl: 'https://cdn.rawgit.com/GoogleChrome/omnitone/962089ca/build/resources/sh_hrir_o_1.wav', | ||
channelMap: [0, 1, 2, 3] | ||
}); | ||
foaRenderer.initialize().then(/* do stuff when FOARenderer is ready. */); | ||
``` | ||
* context (AudioContext): an AudioContext object. | ||
* options (Object): options for decoder. | ||
- HRIRUrl (String): URL for the SH-MaxRe HRIR. | ||
- channelMap (Array): A custom channel map. | ||
```js | ||
foaRenderer.input // A GainNode as an input of FOARenderer. | ||
foaRenderer.output // A GainNode as an output of FOARenderer. | ||
``` | ||
Note that a `FOARenderer` instance has `input` and `output` GainNode. These nodes can be connected to the other AudioNodes for pre/post-processing. | ||
### FOADecoder | ||
`FOADecoder` is a ready-made package of ambisonic gain decoder and binaural renderer. | ||
```js | ||
var foaDecoder = Omnitone.createFOADecoder(context, element, { | ||
HRTFSetUrl: 'YOUR_HRTF_SET_URL', | ||
postGainDB: 0, | ||
channelMap: [0, 1, 2, 3] | ||
}); | ||
``` | ||
* context (AudioContext): an AudioContext object. | ||
* element (MediaElement): A target video or audio element for streaming. | ||
* options (Object): options for decoder. | ||
- HRTFSetUrl (String): Base URL for the cube HRTF sets. | ||
- postGainDB (Number): Post-decoding gain compensation in dB. | ||
- channelMap (Array): A custom channel map. | ||
### FOARouter | ||
`FOARouter` is useful when you need to change the channel layout of the incoming multichannel audio stream. This is necessary because the channel layout changes depending on the audio codec in the browser. | ||
```js | ||
var router = Omnitone.createFOARouter(context, channelMap); | ||
``` | ||
* context (AudioContext): an AudioContext object. | ||
* channelMap (Array): an array represents the target channel layout. | ||
#### Methods | ||
```js | ||
router.setChannelMap([0, 1, 2, 3]); // 4-ch AAC in Chrome (default). | ||
router.setChannelMap([1, 2, 0, 3]); // 4-ch AAC in Safari. | ||
``` | ||
### FOARotator | ||
`FOARotator` is a sound field rotator for the first-order-ambisonic decoding. It also performs the coordinate transformation between the world space and the audio space. | ||
```js | ||
var rotator = Omnitone.createFOARotator(context); | ||
``` | ||
* context (AudioContext): an AudioContext object. | ||
#### Methods | ||
```js | ||
rotator.setRotationMatrix([1, 0, 0, 0, 1, 0, 0, 0, 1]); // 3x3 row-major matrix. | ||
rotator.setRotationMatrix4([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); // 4x4 row-major matrix. | ||
``` | ||
* rotationMatrix (Array): 3x3 row-major matrix. | ||
* rotationMatrix4 (Array): 4x4 row-major matrix. | ||
### FOAPhaseMatchedFilter | ||
`FOAPhaseMatchedFilter` is a pair of pass filters (LP/HP) with a crossover frequency to compensate the gain of high frequency contents without a phase difference. | ||
```js | ||
var filter = Omnitone.createFOAPhaseMatchedFilter(context); | ||
``` | ||
* context (AudioContext): an AudioContext object. | ||
### FOAVirtualSpeaker | ||
`FOAVirtualSpeaker` is a virtual speaker abstraction with the decoding gain coefficients and HRTF convolution for the first-order-ambisonic audio stream. Note that the speaker instance directly connects to the destination node of `AudioContext`. So you cannot apply additional audio processing after this component. | ||
```js | ||
var speaker = Omnitone.createFOAVirtualSpeaker(context, options); | ||
``` | ||
* context (AudioContext): an AudioContext object. | ||
* options (Object): options for speaker. | ||
- coefficients: decoding coefficients for (W,X,Y,Z). | ||
- IR: stereo IR buffer for HRTF convolution. | ||
- gain: post-gain for the speaker. | ||
#### Methods | ||
```js | ||
speaker.enable(); // activate the speaker. | ||
speaker.disable(); // deactivate the speaker. | ||
``` | ||
Deactivating a virtual speaker can save CPU powers. Running multiple HRTF convolution can be computationally expensive, so disabling a speaker might be helpful when the binaural rendering is not necessary. | ||
## Building | ||
Omnitone uses [WebPack](https://webpack.github.io/) to build the minified library and to manage dependencies. | ||
```bash | ||
npm install # install dependencies. | ||
npm run build # build a non-minified library. | ||
npm run watch # recompile whenever any source file changes. | ||
npm run build-all # build a minified library and copy static resources. | ||
npm run build-doc # build JSDoc3 documentation. | ||
``` | ||
### Test | ||
## Test | ||
Omnitone uses [Travis](https://travis-ci.org/) and [Karma](https://karma-runner.github.io/1.0/index.html) test runner for the automated testing. To run the test suite locally, make sure to install dependencies before launch the local test runner. The test suite requires the promisifed version of OfflineAudioContext, so the Karma test runner will choose Chrome as a default test runner. | ||
Omnitone uses [Travis](https://travis-ci.org/) and [Karma](https://karma-runner.github.io/1.0/index.html) test runner for continuous integration. (The index HTML page for the local testing is deprecated in v0.2.1.) To run the test suite locally, you have to clone the repository, install dependencies and launch the test runner: | ||
```bash | ||
@@ -294,8 +150,16 @@ npm test | ||
Note that unit tests require the promisified version of `OfflineAudioContext`, so they might not run on outdate browsers. Omnitone's Travis CI is using the latest stable version of Chrome. | ||
#### Local Testing on Linux | ||
Since the test suite requires Chromium-based browser, the following set up might be necessary for Karma to run properly on Linux distros without Chromium-based browser. | ||
```bash | ||
# Tested with Ubuntu 16.04 | ||
sudo apt install chromium-browser | ||
export CHROME_BIN=chromium-browser | ||
``` | ||
## Audio Codec Compatibility | ||
Omnitone is designed to run any browser that supports Web Audio API, however, it does not address the incompatibility issue around various media codecs in the browsers. At the time of writing, the decoding of compressed multichannel audio via `<video>` or `<audio>` elements is not fully supported by the majority of mobile browsers. | ||
Omnitone is designed to run on any browser that supports Web Audio API, however, it does not address the incompatibility issue around various media codecs in the browser. At the time of writing, the decoding of compressed multichannel audio via `<video>` or `<audio>` elements is not fully supported by the majority of mobile browsers. | ||
@@ -306,4 +170,4 @@ | ||
* [Google Spatial Media](https://github.com/google/spatial-media) | ||
* [Web Audio API](https://webaudio.github.io/web-audio-api/) | ||
* [WebVR](https://webvr.info/) | ||
* [Web Audio API](https://webaudio.github.io/web-audio-api) | ||
* [WebVR](https://webvr.info) | ||
@@ -332,2 +196,1 @@ | ||
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. | ||
@@ -16,4 +16,4 @@ /** | ||
var webpack = require('webpack'); | ||
var CopyWebpackPlugin = require('copy-webpack-plugin'); | ||
const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); | ||
const CopyWebpackPlugin = require('copy-webpack-plugin'); | ||
@@ -28,8 +28,22 @@ module.exports = { | ||
plugins: [ | ||
new webpack.optimize.UglifyJsPlugin(), | ||
new UglifyJSPlugin({ | ||
uglifyOptions: { | ||
mangle: { | ||
// To address the 'let' bug in Safari 10. See: | ||
// https://github.com/mishoo/UglifyJS2/issues/1753 | ||
'safari10': true | ||
} | ||
} | ||
}), | ||
new CopyWebpackPlugin([{ | ||
from: './src/resources', | ||
to: 'resources' | ||
to: 'resources', | ||
ignore: [ | ||
'sh_*', | ||
'README.md', | ||
'LICENSE', | ||
'cube.config' | ||
] | ||
}]) | ||
] | ||
}; |
@@ -16,4 +16,2 @@ /** | ||
var webpack = require('webpack'); | ||
module.exports = { | ||
@@ -20,0 +18,0 @@ entry: './src/main.js', |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 15 instances in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 2 instances in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
233129
41
3926
0
13
191
23