audio-inspect
Version:
Lightweight yet powerful audio analysis library
2 lines (1 loc) • 9.02 kB
JavaScript
var u=class o extends Error{constructor(r,n,a,s){super(n,{cause:a});this.code=r;this.details=s;this.cause=a,Error.captureStackTrace&&Error.captureStackTrace(this,o)}name="AudioInspectError";timestamp=new Date;cause;toJSON(){return{name:this.name,code:this.code,message:this.message,details:this.details,timestamp:this.timestamp,stack:this.stack,cause:this.cause}}};var E=class{constructor(t,r,n=!1){this.size=t;this.sampleRate=r;this.enableProfiling=n;this.initializationPromise=this.initializeWebFFT()}fftInstance=null;initializationPromise=null;get name(){return"WebFFT"}async waitForInitialization(){this.initializationPromise&&await this.initializationPromise}async initializeWebFFT(){try{let r=(await import("webfft")).default;this.fftInstance=new r(this.size),this.enableProfiling&&this.fftInstance?.profile&&await this.fftInstance.profile()}catch(t){throw new u("UNSUPPORTED_FORMAT",`WebFFT\u306E\u521D\u671F\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${t instanceof Error?t.message:String(t)}`)}}async fft(t){if(this.initializationPromise&&await this.initializationPromise,!this.fftInstance)throw new u("UNSUPPORTED_FORMAT","WebFFT initialization failed");if(t.length!==this.size)throw new u("INVALID_INPUT",`\u5165\u529B\u30B5\u30A4\u30BA\u304C\u4E0D\u6B63\u3067\u3059\u3002\u671F\u5F85\u5024: ${this.size}, \u5B9F\u969B: ${t.length}`);let r=new Float32Array(this.size*2);for(let i=0;i<this.size;i++)r[i*2]=t[i]||0,r[i*2+1]=0;let n=this.fftInstance.fft(r),a=new Float32Array(this.size/2+1),s=new Float32Array(this.size/2+1),l=new Float32Array(this.size/2+1);for(let i=0;i<a.length;i++){let e=n[i*2]||0,c=n[i*2+1]||0;a[i]=Math.sqrt(e*e+c*c),s[i]=Math.atan2(c,e),l[i]=i*this.sampleRate/this.size}return{complex:n,magnitude:a,phase:s,frequencies:l}}async profile(){if(this.initializationPromise&&await this.initializationPromise,!this.fftInstance||!this.fftInstance.profile)throw new u("UNSUPPORTED_FORMAT","WebFFT\u304C\u521D\u671F\u5316\u3055\u308C\u3066\u3044\u307E\u305B\u3093");await this.fftInstance.profile()}dispose(){if(this.fftInstance)try{this.fftInstance.dispose&&this.fftInstance.dispose()}catch(t){console.warn("FFT instance disposal failed:",t)}finally{this.fftInstance=null,this.initializationPromise=null}}},_=class{constructor(t,r){this.size=t;this.sampleRate=r;if(!this.isPowerOfTwo(t))throw new u("INVALID_INPUT","FFT\u30B5\u30A4\u30BA\u306F2\u306E\u51AA\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059");this.precomputeTables()}bitReversalTable;twiddleFactorsReal;twiddleFactorsImag;get name(){return"Native FFT (Cooley-Tukey)"}isPowerOfTwo(t){return t>0&&(t&t-1)===0}precomputeTables(){this.bitReversalTable=new Uint32Array(this.size);let t=Math.log2(this.size);for(let n=0;n<this.size;n++){let a=0;for(let s=0;s<t;s++)a=a<<1|n>>s&1;this.bitReversalTable[n]=a}let r=this.size/2;this.twiddleFactorsReal=new Float32Array(r),this.twiddleFactorsImag=new Float32Array(r);for(let n=0;n<r;n++){let a=-2*Math.PI*n/this.size;this.twiddleFactorsReal[n]=Math.cos(a),this.twiddleFactorsImag[n]=Math.sin(a)}}fft(t){if(t.length!==this.size)throw new u("INVALID_INPUT",`\u5165\u529B\u30B5\u30A4\u30BA\u304C\u4E0D\u6B63\u3067\u3059\u3002\u671F\u5F85\u5024: ${this.size}, \u5B9F\u969B: ${t.length}`);let r=new Float32Array(this.size),n=new Float32Array(this.size);for(let e=0;e<this.size;e++){let c=this.bitReversalTable[e];c!==void 0&&(r[c]=t[e]||0,n[c]=0)}for(let e=1;e<this.size;e*=2){let c=e*2,p=this.size/c;for(let h=0;h<this.size;h+=c)for(let f=0;f<e;f++){let y=f*p,F=this.twiddleFactorsReal[y]||0,I=this.twiddleFactorsImag[y]||0,A=h+f,g=h+f+e,v=r[A]||0,R=n[A]||0,P=r[g]||0,M=n[g]||0,q=P*F-M*I,S=P*I+M*F;r[A]=v+q,n[A]=R+S,r[g]=v-q,n[g]=R-S}}let a=new Float32Array(this.size*2),s=new Float32Array(this.size/2+1),l=new Float32Array(this.size/2+1),i=new Float32Array(this.size/2+1);for(let e=0;e<this.size;e++)if(a[e*2]=r[e]||0,a[e*2+1]=n[e]||0,e<=this.size/2){let c=r[e]||0,p=n[e]||0;s[e]=Math.sqrt(c*c+p*p),l[e]=Math.atan2(p,c),i[e]=e*this.sampleRate/this.size}return{complex:a,magnitude:s,phase:l,frequencies:i}}dispose(){}},z=class{static async createProvider(t){switch(t.type){case"webfft":{let r=new E(t.fftSize,t.sampleRate,t.enableProfiling);return await r.waitForInitialization(),r}case"native":return new _(t.fftSize,t.sampleRate);case"custom":if(!t.customProvider)throw new u("INVALID_INPUT","\u30AB\u30B9\u30BF\u30E0\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u304C\u6307\u5B9A\u3055\u308C\u3066\u3044\u307E\u305B\u3093");return t.customProvider;default:{let r=t.type;throw new u("UNSUPPORTED_FORMAT",`\u672A\u5BFE\u5FDC\u306EFFT\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC: ${String(r)}`)}}}static getAvailableProviders(){return["webfft","native"]}};function Q(o,t){let r=new Float32Array(o.length),n=o.length;for(let a=0;a<n;a++){let s=1;switch(t){case"hann":s=.5*(1-Math.cos(2*Math.PI*a/(n-1)));break;case"hamming":s=.54-.46*Math.cos(2*Math.PI*a/(n-1));break;case"blackman":s=.42-.5*Math.cos(2*Math.PI*a/(n-1))+.08*Math.cos(4*Math.PI*a/(n-1));break;case"rectangular":default:s=1;break}r[a]=(o[a]||0)*s}return r}function J(o,t){if(t===-1){let n=new Float32Array(o.length);for(let a=0;a<o.length;a++){let s=0;for(let l=0;l<o.numberOfChannels;l++){let i=o.channelData[l];i&&a<i.length&&(s+=i[a])}n[a]=s/o.numberOfChannels}return n}if(t<-1||t>=o.numberOfChannels)throw new u("INVALID_INPUT",`Invalid channel number: ${t}`);let r=o.channelData[t];if(!r)throw new u("INVALID_INPUT",`Channel ${t} data does not exist`);return r}async function k(o,t={}){let{fftSize:r=2048,windowFunction:n="hann",channel:a=0,provider:s="native",enableProfiling:l=!1}=t,i=J(o,a),e;i.length<r?(e=new Float32Array(r),e.set(i)):e=i.slice(0,r);let c=Q(e,n),p=s==="webfft"?["webfft","native"]:[s],h,f=null;for(let y of p)try{f=await z.createProvider({type:y,fftSize:r,sampleRate:o.sampleRate,enableProfiling:l});break}catch(F){if(h=F,y==="webfft"&&p.includes("native"))continue}if(!f)throw h||new u("FFT_PROVIDER_ERROR","Failed to create FFT provider");try{return{...await f.fft(c),fftSize:r,windowFunction:n,providerName:f.name}}finally{f.dispose()}}function O(o){return o==null||isNaN(o)||!isFinite(o)?0:o}function X(o,t,r=44){let n=Math.min(o.length,t.length),a=-1/0,s=0;for(let l=-r;l<=r;l++){let i=0,e=0;for(let c=0;c<n;c++){let p=c,h=c+l;if(h>=0&&h<n){let f=O(o[p]??0),y=O(t[h]??0);i+=f*y,e++}}e>0&&(i/=e,i>a&&(a=i,s=l))}return s}async function Y(o,t){let r=new Float32Array(o.magnitude.length);for(let n=0;n<r.length;n++){let a=o.magnitude[n]??0,s=t.magnitude[n]??0,l=(o.phase[n]??0)-(t.phase[n]??0),i=a*s*Math.cos(l),e=a*a*s*s;r[n]=e>1e-10?i*i/e:0}return r}function Z(o,t,r,n){let a=new Float32Array(o.length);for(let s=0;s<a.length;s++){let l=o[s]||0,i=t[s]||0,e=r[s]||0,c=n[s]||0,p=e-c,h=Math.abs(l+i)/2,f=Math.abs(l-i)/2,y=Math.abs(Math.sin(p/2)),F=f/(h+f+1e-10);a[s]=Math.max(F,y)}return a}async function we(o,t={}){if(o.numberOfChannels<2)throw new u("INVALID_INPUT","\u30B9\u30C6\u30EC\u30AA\u89E3\u6790\u306B\u306F2\u30C1\u30E3\u30F3\u30CD\u30EB\u4EE5\u4E0A\u306E\u97F3\u58F0\u304C\u5FC5\u8981\u3067\u3059");let{frameSize:r=Math.min(8192,o.length),calculatePhase:n=!1,calculateITD:a=!1,calculateILD:s=!1}=t,l=o.channelData[0],i=o.channelData[1];if(!l||!i)throw new u("INVALID_INPUT","L/R\u30C1\u30E3\u30F3\u30CD\u30EB\u306E\u30C7\u30FC\u30BF\u304C\u5B58\u5728\u3057\u307E\u305B\u3093");let e=Math.min(l.length,i.length);if(e===0)return{correlation:0,width:0,balance:0,midSideRatio:0};let c=0,p=0,h=0,f=0,y=0,F=0,I=0,A=0,g=0,v=new Float32Array(e),R=new Float32Array(e);for(let m=0;m<e;m++){let b=O(l[m]??0),d=O(i[m]??0);c+=b,p+=d,h+=b*d,f+=b*b,y+=d*d,F+=b*b,I+=d*d;let w=(b+d)*.5,T=(b-d)*.5;A+=w*w,g+=T*T,v[m]=w,R[m]=T}let P=c/e,M=p/e,q=h/e-P*M,S=Math.sqrt(f/e-P*P),U=Math.sqrt(y/e-M*M),W=S>1e-10&&U>1e-10?q/(S*U):0,B=A+g>1e-10?g/(A+g):0,V=F+I>1e-10?(I-F)/(F+I):0,$=g>1e-10?10*Math.log10(A/g):1/0,x={correlation:W,width:B,balance:V,midSideRatio:$};if(x.goniometer={x:R,y:v},n){let m=Math.min(r,e),b=Math.pow(2,Math.ceil(Math.log2(m))),[d,w]=await Promise.all([k({channelData:[l.subarray(0,m)],sampleRate:o.sampleRate,numberOfChannels:1,length:m,duration:m/o.sampleRate},{fftSize:b}),k({channelData:[i.subarray(0,m)],sampleRate:o.sampleRate,numberOfChannels:1,length:m,duration:m/o.sampleRate},{fftSize:b})]);x.coherence=await Y(d,w),x.widthFrequency=Z(d.magnitude,w.magnitude,d.phase,w.phase);let T=0,C=0;for(let D=1;D<d.phase.length;D++){let j=d.magnitude[D]||0,G=w.magnitude[D]||0,H=d.phase[D]||0,K=w.phase[D]||0,L=j*G,N=H-K;N=(N+Math.PI)%(2*Math.PI)-Math.PI,T+=N*L,C+=L}x.phaseDifference=C>1e-10?T/C*180/Math.PI:0}if(a){let m=X(l.subarray(0,Math.min(r,e)),i.subarray(0,Math.min(r,e)));x.itd=m/o.sampleRate*1e3}if(s){let m=Math.sqrt(F/e),b=Math.sqrt(I/e);x.ild=m>1e-10&&b>1e-10?20*Math.log10(b/m):0}return x}function Ie(o,t={}){return Promise.reject(new u("UNSUPPORTED_FORMAT","\u6642\u7CFB\u5217\u30B9\u30C6\u30EC\u30AA\u89E3\u6790\u306F\u5C06\u6765\u306E\u30D0\u30FC\u30B8\u30E7\u30F3\u3067\u5B9F\u88C5\u4E88\u5B9A\u3067\u3059"))}export{we as getStereoAnalysis,Ie as getTimeVaryingStereoAnalysis};