UNPKG

audio-inspect

Version:

Lightweight yet powerful audio analysis library

2 lines (1 loc) 7.76 kB
var p=class s 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,s)}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 z=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 i=0;i<this.size;i++)t[i*2]=e[i]||0,t[i*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 i=0;i<a.length;i++){let n=r[i*2]||0,l=r[i*2+1]||0;a[i]=Math.sqrt(n*n+l*l),o[i]=Math.atan2(l,n),c[i]=i*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}}},O=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 n=0;n<this.size;n++){let l=this.bitReversalTable[n];l!==void 0&&(t[l]=e[n]||0,r[l]=0)}for(let n=1;n<this.size;n*=2){let l=n*2,u=this.size/l;for(let f=0;f<this.size;f+=l)for(let m=0;m<n;m++){let d=m*u,b=this.twiddleFactorsReal[d]||0,w=this.twiddleFactorsImag[d]||0,F=f+m,y=f+m+n,A=t[F]||0,T=r[F]||0,g=t[y]||0,I=r[y]||0,R=g*b-I*w,P=g*w+I*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),i=new Float32Array(this.size/2+1);for(let n=0;n<this.size;n++)if(a[n*2]=t[n]||0,a[n*2+1]=r[n]||0,n<=this.size/2){let l=t[n]||0,u=r[n]||0;o[n]=Math.sqrt(l*l+u*u),c[n]=Math.atan2(u,l),i[n]=n*this.sampleRate/this.size}return{complex:a,magnitude:o,phase:c,frequencies:i}}dispose(){}},v=class{static async createProvider(e){switch(e.type){case"webfft":{let t=new z(e.fftSize,e.sampleRate,e.enableProfiling);return await t.waitForInitialization(),t}case"native":return new O(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 E(s,e){let t=new Float32Array(s.length),r=s.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]=(s[a]||0)*o}return t}function D(s,e){if(e===-1){let r=new Float32Array(s.length);for(let a=0;a<s.length;a++){let o=0;for(let c=0;c<s.numberOfChannels;c++){let i=s.channelData[c];i&&a<i.length&&(o+=i[a])}r[a]=o/s.numberOfChannels}return r}if(e<-1||e>=s.numberOfChannels)throw new p("INVALID_INPUT",`Invalid channel number: ${e}`);let t=s.channelData[e];if(!t)throw new p("INVALID_INPUT",`Channel ${e} data does not exist`);return t}async function N(s,e={}){let{fftSize:t=2048,windowFunction:r="hann",channel:a=0,provider:o="native",enableProfiling:c=!1}=e,i=D(s,a),n;i.length<t?(n=new Float32Array(t),n.set(i)):n=i.slice(0,t);let l=E(n,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:s.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 j(s,e={}){let{fftSize:t=2048,minFrequency:r=0,maxFrequency:a=s.sampleRate/2,decibels:o=!0,timeFrames:c=1,overlap:i=.5,...n}=e,l=D(s,e.channel||0);if(c===1){let u=await N(s,{...n,fftSize:t}),f=q(u,r,a),m={frequencies:f.frequencies,magnitudes:f.magnitude};return o&&(m.decibels=M(f.magnitude)),m}else{let u=await C(l,s.sampleRate,t,c,i,{...n,minFrequency:r,maxFrequency:a,decibels:o});return{frequencies:u.frequencies,magnitudes:new Float32Array,spectrogram:u}}}function q(s,e,t){let{frequencies:r,magnitude:a,phase:o,complex:c}=s,i=r.findIndex(u=>u>=e),n=r.findIndex(u=>u>t),l=n===-1?r.length:n;return{frequencies:r.slice(i,l),magnitude:a.slice(i,l),phase:o.slice(i,l),complex:c.slice(i*2,l*2)}}function M(s){let e=new Float32Array(s.length);for(let t=0;t<s.length;t++){let r=s[t]||0;e[t]=r>0?20*Math.log10(r):-1/0}return e}async function C(s,e,t,r,a,o){let c=Math.floor(t*(1-a)),i;s.length===0?i=0:s.length<t?i=1:i=Math.floor((s.length-t)/c)+1;let n=Math.min(r,i),l=new Float32Array(n),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<n;F++){let y=F*c,A=new Float32Array(t);for(let h=0;h<t;h++)A[h]=y+h<s.length&&s[y+h]||0;let T=E(A,o.windowFunction||"hann"),g=await w.fft(T);if(F===0){f=g.frequencies;let h=o.minFrequency||0,k=o.maxFrequency||e/2;d=f.findIndex(x=>x>=h),d===-1&&(d=0);let S=f.findIndex(x=>x>k);b=S===-1?f.length:S,m=f.slice(d,b)}let R=g.magnitude.slice(d,b),P=o.decibels?M(R):R;u.push(P),l[F]=(y+t/2)/e}}finally{w.dispose()}return{times:l,frequencies:m,intensities:u,timeFrames:n,frequencyBins:m.length}}export{N as getFFT,j as getSpectrum};