audio-inspect
Version:
Lightweight yet powerful audio analysis library
2 lines (1 loc) • 8.36 kB
JavaScript
"use strict";var U=Object.create;var I=Object.defineProperty;var W=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var B=Object.getPrototypeOf,L=Object.prototype.hasOwnProperty;var V=(n,e)=>{for(var t in e)I(n,t,{get:e[t],enumerable:!0})},D=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of _(e))!L.call(n,a)&&a!==t&&I(n,a,{get:()=>e[a],enumerable:!(r=W(e,a))||r.enumerable});return n};var $=(n,e,t)=>(t=n!=null?U(B(n)):{},D(e||!n||!n.__esModule?I(t,"default",{value:n,enumerable:!0}):t,n)),j=n=>D(I({},"__esModule",{value:!0}),n);var J={};V(J,{getFFT:()=>N,getSpectrum:()=>H});module.exports=j(J);var p=class n extends Error{constructor(t,r,a,o){super(r,{cause:a});this.code=t;this.details=o;this.cause=a,Error.captureStackTrace&&Error.captureStackTrace(this,n)}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 O=class{constructor(e,t,r=!1){this.size=e;this.sampleRate=t;this.enableProfiling=r;this.initializationPromise=this.initializeWebFFT()}fftInstance=null;initializationPromise=null;get name(){return"WebFFT"}async waitForInitialization(){this.initializationPromise&&await this.initializationPromise}async initializeWebFFT(){try{let t=(await import("webfft")).default;this.fftInstance=new t(this.size),this.enableProfiling&&this.fftInstance?.profile&&await this.fftInstance.profile()}catch(e){throw new p("UNSUPPORTED_FORMAT",`WebFFT\u306E\u521D\u671F\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${e instanceof Error?e.message:String(e)}`)}}async fft(e){if(this.initializationPromise&&await this.initializationPromise,!this.fftInstance)throw new p("UNSUPPORTED_FORMAT","WebFFT initialization failed");if(e.length!==this.size)throw new p("INVALID_INPUT",`\u5165\u529B\u30B5\u30A4\u30BA\u304C\u4E0D\u6B63\u3067\u3059\u3002\u671F\u5F85\u5024: ${this.size}, \u5B9F\u969B: ${e.length}`);let t=new Float32Array(this.size*2);for(let s=0;s<this.size;s++)t[s*2]=e[s]||0,t[s*2+1]=0;let r=this.fftInstance.fft(t),a=new Float32Array(this.size/2+1),o=new Float32Array(this.size/2+1),c=new Float32Array(this.size/2+1);for(let s=0;s<a.length;s++){let i=r[s*2]||0,l=r[s*2+1]||0;a[s]=Math.sqrt(i*i+l*l),o[s]=Math.atan2(l,i),c[s]=s*this.sampleRate/this.size}return{complex:r,magnitude:a,phase:o,frequencies:c}}async profile(){if(this.initializationPromise&&await this.initializationPromise,!this.fftInstance||!this.fftInstance.profile)throw new p("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(e){console.warn("FFT instance disposal failed:",e)}finally{this.fftInstance=null,this.initializationPromise=null}}},S=class{constructor(e,t){this.size=e;this.sampleRate=t;if(!this.isPowerOfTwo(e))throw new p("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(e){return e>0&&(e&e-1)===0}precomputeTables(){this.bitReversalTable=new Uint32Array(this.size);let e=Math.log2(this.size);for(let r=0;r<this.size;r++){let a=0;for(let o=0;o<e;o++)a=a<<1|r>>o&1;this.bitReversalTable[r]=a}let t=this.size/2;this.twiddleFactorsReal=new Float32Array(t),this.twiddleFactorsImag=new Float32Array(t);for(let r=0;r<t;r++){let a=-2*Math.PI*r/this.size;this.twiddleFactorsReal[r]=Math.cos(a),this.twiddleFactorsImag[r]=Math.sin(a)}}fft(e){if(e.length!==this.size)throw new p("INVALID_INPUT",`\u5165\u529B\u30B5\u30A4\u30BA\u304C\u4E0D\u6B63\u3067\u3059\u3002\u671F\u5F85\u5024: ${this.size}, \u5B9F\u969B: ${e.length}`);let t=new Float32Array(this.size),r=new Float32Array(this.size);for(let i=0;i<this.size;i++){let l=this.bitReversalTable[i];l!==void 0&&(t[l]=e[i]||0,r[l]=0)}for(let i=1;i<this.size;i*=2){let l=i*2,u=this.size/l;for(let f=0;f<this.size;f+=l)for(let m=0;m<i;m++){let d=m*u,b=this.twiddleFactorsReal[d]||0,w=this.twiddleFactorsImag[d]||0,F=f+m,y=f+m+i,A=t[F]||0,T=r[F]||0,g=t[y]||0,x=r[y]||0,R=g*b-x*w,P=g*w+x*b;t[F]=A+R,r[F]=T+P,t[y]=A-R,r[y]=T-P}}let a=new Float32Array(this.size*2),o=new Float32Array(this.size/2+1),c=new Float32Array(this.size/2+1),s=new Float32Array(this.size/2+1);for(let i=0;i<this.size;i++)if(a[i*2]=t[i]||0,a[i*2+1]=r[i]||0,i<=this.size/2){let l=t[i]||0,u=r[i]||0;o[i]=Math.sqrt(l*l+u*u),c[i]=Math.atan2(u,l),s[i]=i*this.sampleRate/this.size}return{complex:a,magnitude:o,phase:c,frequencies:s}}dispose(){}},v=class{static async createProvider(e){switch(e.type){case"webfft":{let t=new O(e.fftSize,e.sampleRate,e.enableProfiling);return await t.waitForInitialization(),t}case"native":return new S(e.fftSize,e.sampleRate);case"custom":if(!e.customProvider)throw new p("INVALID_INPUT","\u30AB\u30B9\u30BF\u30E0\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u304C\u6307\u5B9A\u3055\u308C\u3066\u3044\u307E\u305B\u3093");return e.customProvider;default:{let t=e.type;throw new p("UNSUPPORTED_FORMAT",`\u672A\u5BFE\u5FDC\u306EFFT\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC: ${String(t)}`)}}}static getAvailableProviders(){return["webfft","native"]}};function M(n,e){let t=new Float32Array(n.length),r=n.length;for(let a=0;a<r;a++){let o=1;switch(e){case"hann":o=.5*(1-Math.cos(2*Math.PI*a/(r-1)));break;case"hamming":o=.54-.46*Math.cos(2*Math.PI*a/(r-1));break;case"blackman":o=.42-.5*Math.cos(2*Math.PI*a/(r-1))+.08*Math.cos(4*Math.PI*a/(r-1));break;case"rectangular":default:o=1;break}t[a]=(n[a]||0)*o}return t}function k(n,e){if(e===-1){let r=new Float32Array(n.length);for(let a=0;a<n.length;a++){let o=0;for(let c=0;c<n.numberOfChannels;c++){let s=n.channelData[c];s&&a<s.length&&(o+=s[a])}r[a]=o/n.numberOfChannels}return r}if(e<-1||e>=n.numberOfChannels)throw new p("INVALID_INPUT",`Invalid channel number: ${e}`);let t=n.channelData[e];if(!t)throw new p("INVALID_INPUT",`Channel ${e} data does not exist`);return t}async function N(n,e={}){let{fftSize:t=2048,windowFunction:r="hann",channel:a=0,provider:o="native",enableProfiling:c=!1}=e,s=k(n,a),i;s.length<t?(i=new Float32Array(t),i.set(s)):i=s.slice(0,t);let l=M(i,r),u=o==="webfft"?["webfft","native"]:[o],f,m=null;for(let d of u)try{m=await v.createProvider({type:d,fftSize:t,sampleRate:n.sampleRate,enableProfiling:c});break}catch(b){if(f=b,d==="webfft"&&u.includes("native"))continue}if(!m)throw f||new p("FFT_PROVIDER_ERROR","Failed to create FFT provider");try{return{...await m.fft(l),fftSize:t,windowFunction:r,providerName:m.name}}finally{m.dispose()}}async function H(n,e={}){let{fftSize:t=2048,minFrequency:r=0,maxFrequency:a=n.sampleRate/2,decibels:o=!0,timeFrames:c=1,overlap:s=.5,...i}=e,l=k(n,e.channel||0);if(c===1){let u=await N(n,{...i,fftSize:t}),f=K(u,r,a),m={frequencies:f.frequencies,magnitudes:f.magnitude};return o&&(m.decibels=q(f.magnitude)),m}else{let u=await G(l,n.sampleRate,t,c,s,{...i,minFrequency:r,maxFrequency:a,decibels:o});return{frequencies:u.frequencies,magnitudes:new Float32Array,spectrogram:u}}}function K(n,e,t){let{frequencies:r,magnitude:a,phase:o,complex:c}=n,s=r.findIndex(u=>u>=e),i=r.findIndex(u=>u>t),l=i===-1?r.length:i;return{frequencies:r.slice(s,l),magnitude:a.slice(s,l),phase:o.slice(s,l),complex:c.slice(s*2,l*2)}}function q(n){let e=new Float32Array(n.length);for(let t=0;t<n.length;t++){let r=n[t]||0;e[t]=r>0?20*Math.log10(r):-1/0}return e}async function G(n,e,t,r,a,o){let c=Math.floor(t*(1-a)),s;n.length===0?s=0:n.length<t?s=1:s=Math.floor((n.length-t)/c)+1;let i=Math.min(r,s),l=new Float32Array(i),u=[],f=new Float32Array,m=new Float32Array,d=0,b=0,w=await v.createProvider({type:o.provider||"native",fftSize:t,sampleRate:e,enableProfiling:o.enableProfiling||!1});try{for(let F=0;F<i;F++){let y=F*c,A=new Float32Array(t);for(let h=0;h<t;h++)A[h]=y+h<n.length&&n[y+h]||0;let T=M(A,o.windowFunction||"hann"),g=await w.fft(T);if(F===0){f=g.frequencies;let h=o.minFrequency||0,C=o.maxFrequency||e/2;d=f.findIndex(z=>z>=h),d===-1&&(d=0);let E=f.findIndex(z=>z>C);b=E===-1?f.length:E,m=f.slice(d,b)}let R=g.magnitude.slice(d,b),P=o.decibels?q(R):R;u.push(P),l[F]=(y+t/2)/e}}finally{w.dispose()}return{times:l,frequencies:m,intensities:u,timeFrames:i,frequencyBins:m.length}}