UNPKG

audio-inspect

Version:

Lightweight yet powerful audio analysis library

2 lines (1 loc) 15.8 kB
var h=class e extends Error{constructor(o,n,r,s){super(n,{cause:r});this.code=o;this.details=s;this.cause=r,Error.captureStackTrace&&Error.captureStackTrace(this,e)}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}}};function j(e){return e>0&&(e&e-1)===0}function B(e){if(e<=0||!isFinite(e)||isNaN(e))return 1;if(j(e))return e;let t=1;for(;t<e&&t<Number.MAX_SAFE_INTEGER;)t*=2;return t}function S(e){return e==null||isNaN(e)||!isFinite(e)?0:e}var k=class{constructor(t,o,n=!1){this.size=t;this.sampleRate=o;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 o=(await import("webfft")).default;this.fftInstance=new o(this.size),this.enableProfiling&&this.fftInstance?.profile&&await this.fftInstance.profile()}catch(t){throw new h("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 h("UNSUPPORTED_FORMAT","WebFFT initialization failed");if(t.length!==this.size)throw new h("INVALID_INPUT",`\u5165\u529B\u30B5\u30A4\u30BA\u304C\u4E0D\u6B63\u3067\u3059\u3002\u671F\u5F85\u5024: ${this.size}, \u5B9F\u969B: ${t.length}`);let o=new Float32Array(this.size*2);for(let i=0;i<this.size;i++)o[i*2]=t[i]||0,o[i*2+1]=0;let n=this.fftInstance.fft(o),r=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<r.length;i++){let a=n[i*2]||0,c=n[i*2+1]||0;r[i]=Math.sqrt(a*a+c*c),s[i]=Math.atan2(c,a),l[i]=i*this.sampleRate/this.size}return{complex:n,magnitude:r,phase:s,frequencies:l}}async profile(){if(this.initializationPromise&&await this.initializationPromise,!this.fftInstance||!this.fftInstance.profile)throw new h("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}}},U=class{constructor(t,o){this.size=t;this.sampleRate=o;if(!this.isPowerOfTwo(t))throw new h("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 r=0;for(let s=0;s<t;s++)r=r<<1|n>>s&1;this.bitReversalTable[n]=r}let o=this.size/2;this.twiddleFactorsReal=new Float32Array(o),this.twiddleFactorsImag=new Float32Array(o);for(let n=0;n<o;n++){let r=-2*Math.PI*n/this.size;this.twiddleFactorsReal[n]=Math.cos(r),this.twiddleFactorsImag[n]=Math.sin(r)}}fft(t){if(t.length!==this.size)throw new h("INVALID_INPUT",`\u5165\u529B\u30B5\u30A4\u30BA\u304C\u4E0D\u6B63\u3067\u3059\u3002\u671F\u5F85\u5024: ${this.size}, \u5B9F\u969B: ${t.length}`);let o=new Float32Array(this.size),n=new Float32Array(this.size);for(let a=0;a<this.size;a++){let c=this.bitReversalTable[a];c!==void 0&&(o[c]=t[a]||0,n[c]=0)}for(let a=1;a<this.size;a*=2){let c=a*2,f=this.size/c;for(let u=0;u<this.size;u+=c)for(let m=0;m<a;m++){let p=m*f,b=this.twiddleFactorsReal[p]||0,F=this.twiddleFactorsImag[p]||0,y=u+m,d=u+m+a,D=o[y]||0,I=n[y]||0,M=o[d]||0,g=n[d]||0,A=M*b-g*F,P=M*F+g*b;o[y]=D+A,n[y]=I+P,o[d]=D-A,n[d]=I-P}}let r=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 a=0;a<this.size;a++)if(r[a*2]=o[a]||0,r[a*2+1]=n[a]||0,a<=this.size/2){let c=o[a]||0,f=n[a]||0;s[a]=Math.sqrt(c*c+f*f),l[a]=Math.atan2(f,c),i[a]=a*this.sampleRate/this.size}return{complex:r,magnitude:s,phase:l,frequencies:i}}dispose(){}},N=class{static async createProvider(t){switch(t.type){case"webfft":{let o=new k(t.fftSize,t.sampleRate,t.enableProfiling);return await o.waitForInitialization(),o}case"native":return new U(t.fftSize,t.sampleRate);case"custom":if(!t.customProvider)throw new h("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 o=t.type;throw new h("UNSUPPORTED_FORMAT",`\u672A\u5BFE\u5FDC\u306EFFT\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC: ${String(o)}`)}}}static getAvailableProviders(){return["webfft","native"]}};function G(e,t){let o=new Float32Array(e.length),n=e.length;for(let r=0;r<n;r++){let s=1;switch(t){case"hann":s=.5*(1-Math.cos(2*Math.PI*r/(n-1)));break;case"hamming":s=.54-.46*Math.cos(2*Math.PI*r/(n-1));break;case"blackman":s=.42-.5*Math.cos(2*Math.PI*r/(n-1))+.08*Math.cos(4*Math.PI*r/(n-1));break;case"rectangular":default:s=1;break}o[r]=(e[r]||0)*s}return o}function H(e,t){if(t===-1){let n=new Float32Array(e.length);for(let r=0;r<e.length;r++){let s=0;for(let l=0;l<e.numberOfChannels;l++){let i=e.channelData[l];i&&r<i.length&&(s+=i[r])}n[r]=s/e.numberOfChannels}return n}if(t<-1||t>=e.numberOfChannels)throw new h("INVALID_INPUT",`Invalid channel number: ${t}`);let o=e.channelData[t];if(!o)throw new h("INVALID_INPUT",`Channel ${t} data does not exist`);return o}async function C(e,t={}){let{fftSize:o=2048,windowFunction:n="hann",channel:r=0,provider:s="native",enableProfiling:l=!1}=t,i=H(e,r),a;i.length<o?(a=new Float32Array(o),a.set(i)):a=i.slice(0,o);let c=G(a,n),f=s==="webfft"?["webfft","native"]:[s],u,m=null;for(let p of f)try{m=await N.createProvider({type:p,fftSize:o,sampleRate:e.sampleRate,enableProfiling:l});break}catch(b){if(u=b,p==="webfft"&&f.includes("native"))continue}if(!m)throw u||new h("FFT_PROVIDER_ERROR","Failed to create FFT provider");try{return{...await m.fft(c),fftSize:o,windowFunction:n,providerName:m.name}}finally{m.dispose()}}function K(e,t,o,n){let r=0,s=0;for(let l=0;l<e.length&&l<t.length;l++){let i=t[l],a=e[l];i!==void 0&&a!==void 0&&i>=o&&i<=n&&(r+=i*a,s+=a)}return s>1e-10?r/s:0}function Q(e,t,o,n,r){let s=0,l=0;for(let i=0;i<e.length&&i<t.length;i++){let a=t[i],c=e[i];if(a!==void 0&&c!==void 0&&a>=n&&a<=r){let f=a-o;s+=f*f*c,l+=c}}return l>1e-10?Math.sqrt(s/l):0}function Z(e,t,o,n,r){let s=0;for(let a=0;a<e.length&&a<t.length;a++){let c=t[a],f=e[a];c!==void 0&&f!==void 0&&c>=n&&c<=r&&(s+=f*f)}let l=s*o,i=0;for(let a=0;a<e.length&&a<t.length;a++){let c=t[a],f=e[a];if(c!==void 0&&f!==void 0&&c>=n&&c<=r&&(i+=f*f,i>=l))return c}return r}function J(e,t,o){let n=0,r=0,s=0;for(let l=t;l<=o&&l<e.length;l++){let i=e[l];if(i!==void 0){let a=Math.max(i,1e-10);n+=Math.log(a),r+=a,s++}}return s===0?0:(n=Math.exp(n/s),r=r/s,r>1e-10?n/r:0)}function X(e){if(e.length<2)return 0;let t=0;for(let o=1;o<e.length;o++){let n=S(e[o-1]??0),r=S(e[o]??0);(n>=0&&r<0||n<0&&r>=0)&&t++}return t/(e.length-1)}function Y(e,t,o={normalize:!0}){if(!t||t.length===0)return 0;let n=Math.min(e.length,t.length);if(n===0)return 0;let r=e,s=t;if(o.normalize!==!1){let i=0,a=0;for(let u=0;u<n;u++){let m=e[u]??0,p=t[u]??0;i+=m*m,a+=p*p}let c=Math.sqrt(i),f=Math.sqrt(a);if(c>1e-10&&f>1e-10){r=new Float32Array(n),s=new Float32Array(n);for(let u=0;u<n;u++)r[u]=(e[u]??0)/c,s[u]=(t[u]??0)/f}}let l=0;for(let i=0;i<n;i++){let a=(r[i]??0)-(s[i]??0);l+=a*a}return Math.sqrt(l)}async function ee(e,t={}){let{fftSize:o=2048,windowFunction:n="hann",channel:r=0,minFrequency:s=0,maxFrequency:l=e.sampleRate/2,rolloffThreshold:i=.85}=t;if(r>=e.numberOfChannels)throw new h("INVALID_INPUT",`\u7121\u52B9\u306A\u30C1\u30E3\u30F3\u30CD\u30EB\u756A\u53F7: ${r}`);let a=await C(e,{fftSize:o,windowFunction:n,channel:r}),c=Math.max(0,Math.floor(s*o/e.sampleRate)),f=Math.min(a.frequencies.length-1,Math.floor(l*o/e.sampleRate)),u=K(a.magnitude,a.frequencies,s,l),m=Q(a.magnitude,a.frequencies,u,s,l),p=Z(a.magnitude,a.frequencies,i,s,l),b=J(a.magnitude,c,f),F=e.channelData[r];if(!F)throw new h("INVALID_INPUT",`\u30C1\u30E3\u30F3\u30CD\u30EB ${r} \u306E\u30C7\u30FC\u30BF\u304C\u5B58\u5728\u3057\u307E\u305B\u3093`);let y=X(F);return{spectralCentroid:u,spectralBandwidth:m,spectralRolloff:p,spectralFlatness:b,zeroCrossingRate:y,frequencyRange:{min:s,max:l}}}async function ze(e,t={}){let{frameSize:o=2048,hopSize:n=o/2,fftSize:r=o,windowFunction:s="hann",channel:l=0,minFrequency:i=0,maxFrequency:a=e.sampleRate/2,rolloffThreshold:c=.85,numFrames:f}=t;if(l>=e.numberOfChannels)throw new h("INVALID_INPUT",`\u7121\u52B9\u306A\u30C1\u30E3\u30F3\u30CD\u30EB\u756A\u53F7: ${l}`);let u=e.channelData[l];if(!u)throw new h("INVALID_INPUT",`\u30C1\u30E3\u30F3\u30CD\u30EB ${l} \u306E\u30C7\u30FC\u30BF\u304C\u5B58\u5728\u3057\u307E\u305B\u3093`);let m=f||Math.floor((u.length-o)/n)+1;if(m<=0)throw new h("INVALID_INPUT","\u30D5\u30EC\u30FC\u30E0\u6570\u304C\u4E0D\u6B63\u3067\u3059");let p=new Float32Array(m),b=new Float32Array(m),F=new Float32Array(m),y=new Float32Array(m),d=new Float32Array(m),D=new Float32Array(m),I=new Float32Array(m),M;for(let g=0;g<m;g++){let A=g*n,P=Math.min(A+o,u.length);p[g]=A/e.sampleRate;let T=u.subarray(A,P),E=new Float32Array(r),z=Math.min(T.length,r);z>0&&E.set(T.subarray(0,z));let R={channelData:[E],sampleRate:e.sampleRate,numberOfChannels:1,length:r,duration:r/e.sampleRate},x=await ee(R,{fftSize:r,windowFunction:s,channel:0,minFrequency:i,maxFrequency:a,rolloffThreshold:c});b[g]=x.spectralCentroid,F[g]=x.spectralBandwidth,y[g]=x.spectralRolloff,d[g]=x.spectralFlatness,I[g]=x.zeroCrossingRate;let v=await C(R,{fftSize:r,windowFunction:s,channel:0});D[g]=Y(v.magnitude,M),M=new Float32Array(v.magnitude)}return{times:p,spectralCentroid:b,spectralBandwidth:F,spectralRolloff:y,spectralFlatness:d,spectralFlux:D,zeroCrossingRate:I,frameInfo:{frameSize:o,hopSize:n,numFrames:m}}}function te(e,t,o){let n=[],r=0;for(let c=t;c<=o&&c<e.length;c++){let f=e[c];if(f!==void 0){let u=f*f;n.push(u),r+=u}}if(n.length===0||r<=1e-10)return{entropy:0,entropyNorm:0};let s=n.map(c=>c/r),l=0;for(let c of s)c>1e-10&&(l-=c*Math.log2(c));let i=Math.log2(n.length),a=i>0?l/i:0;return{entropy:l,entropyNorm:a}}function ne(e,t,o){let n=0,r=0,s=0;for(let a=t;a<=o&&a<e.length;a++){let c=e[a];c!==void 0&&(n=Math.max(n,c),r+=c,s++)}if(s===0)return{crest:0,peak:0,average:0};let l=r/s;return{crest:l>1e-10?n/l:0,peak:n,average:l}}async function Ne(e,t={}){let{fftSize:o=2048,windowFunction:n="hann",channel:r=0,minFrequency:s=0,maxFrequency:l=e.sampleRate/2}=t;if(r>=e.numberOfChannels)throw new h("INVALID_INPUT",`\u7121\u52B9\u306A\u30C1\u30E3\u30F3\u30CD\u30EB\u756A\u53F7: ${r}`);let i=await C(e,{fftSize:o,windowFunction:n,channel:r}),a=Math.max(0,Math.floor(s*o/e.sampleRate)),c=Math.min(i.frequencies.length-1,Math.floor(l*o/e.sampleRate)),{entropy:f,entropyNorm:u}=te(i.magnitude,a,c);return{entropy:f,entropyNorm:u,frequencyRange:{min:s,max:l}}}async function Ee(e,t={}){let{fftSize:o=2048,windowFunction:n="hann",channel:r=0,minFrequency:s=0,maxFrequency:l=e.sampleRate/2,asDB:i=!1}=t;if(r>=e.numberOfChannels)throw new h("INVALID_INPUT",`\u7121\u52B9\u306A\u30C1\u30E3\u30F3\u30CD\u30EB\u756A\u53F7: ${r}`);let a=await C(e,{fftSize:o,windowFunction:n,channel:r}),c=Math.max(0,Math.floor(s*o/e.sampleRate)),f=Math.min(a.frequencies.length-1,Math.floor(l*o/e.sampleRate)),{crest:u,peak:m,average:p}=ne(a.magnitude,c,f),b={crest:u,peak:m,average:p,frequencyRange:{min:s,max:l}};return i&&(b.crestDB=u>0?20*Math.log10(u):-1/0),b}function re(e,t){let o=new Float32Array(t);for(let n=0;n<t;n++)switch(e){case"hann":o[n]=.5-.5*Math.cos(2*Math.PI*n/(t-1));break;case"hamming":o[n]=.54-.46*Math.cos(2*Math.PI*n/(t-1));break;case"blackman":o[n]=.42-.5*Math.cos(2*Math.PI*n/(t-1))+.08*Math.cos(4*Math.PI*n/(t-1));break;case"rectangular":default:o[n]=1;break}return o}function ae(e,t){let o=re(t,e.length),n=new Float32Array(e.length);for(let r=0;r<e.length;r++)n[r]=S(e[r]??0)*(o[r]??0);return n}function W(e){return 2595*Math.log10(1+e/700)}function oe(e){return 700*(Math.pow(10,e/2595)-1)}function ie(e,t,o,n,r){let s=[],l=o/2,i=Math.floor(t/2)+1,a=W(n),c=W(r),f=new Array(e+2);for(let m=0;m<e+2;m++)f[m]=a+(c-a)*m/(e+1);let u=f.map(m=>{let p=oe(m);return Math.floor(p/l*(i-1))});for(let m=1;m<=e;m++){let p=new Float32Array(i),b=u[m-1],F=u[m],y=u[m+1];if(!(b===void 0||F===void 0||y===void 0)){for(let d=b;d<=y;d++)d<0||d>=i||d>=p.length||(d<F?F-b>0&&(p[d]=(d-b)/(F-b)):y-F>0&&(p[d]=(y-d)/(y-F)));s.push(p)}}return s}function se(e,t){let o=e.length,n=new Array(t);for(let r=0;r<t;r++){let s=0;for(let i=0;i<o;i++){let a=e[i];a!==void 0&&(s+=a*Math.cos(Math.PI*r*(i+.5)/o))}let l=Math.sqrt(r===0?1/o:2/o);n[r]=s*l}return n}function le(e,t){let o=new Float32Array(e.length);o[0]=S(e[0]??0);for(let n=1;n<e.length;n++){let r=S(e[n]??0),s=S(e[n-1]??0);o[n]=r-t*s}return o}function ce(e,t){return e.map((o,n)=>{if(t===0)return o;let r=1+t/2*Math.sin(Math.PI*n/t);return o*r})}async function ue(e,t={}){let{frameSizeMs:o=25,hopSizeMs:n=10,windowFunction:r="hamming",channel:s=0,numMelFilters:l=40,numMfccCoeffs:i=13,minFrequency:a=0,maxFrequency:c=e.sampleRate/2,preEmphasis:f=.97,lifterCoeff:u=22}=t;if(s>=e.numberOfChannels)throw new h("INVALID_INPUT",`\u7121\u52B9\u306A\u30C1\u30E3\u30F3\u30CD\u30EB\u756A\u53F7: ${s}`);let m=Math.round(o/1e3*e.sampleRate),p=Math.round(n/1e3*e.sampleRate);if(m<=0||p<=0)throw new h("INVALID_INPUT","\u30D5\u30EC\u30FC\u30E0\u30B5\u30A4\u30BA\u307E\u305F\u306F\u30DB\u30C3\u30D7\u30B5\u30A4\u30BA\u304C\u5C0F\u3055\u3059\u304E\u307E\u3059");let b=t.fftSize??Math.max(1024,B(m)),F=e.channelData[s];if(!F)throw new h("INVALID_INPUT",`\u30C1\u30E3\u30F3\u30CD\u30EB ${s} \u306E\u30C7\u30FC\u30BF\u304C\u5B58\u5728\u3057\u307E\u305B\u3093`);let y=le(F,f),d=Math.floor((y.length-m)/p)+1;if(d<=0)throw new h("INSUFFICIENT_DATA","\u30D5\u30EC\u30FC\u30E0\u6570\u304C\u4E0D\u6B63\u3067\u3059");let D=ie(l,b,e.sampleRate,a,c),I=[],M=new Float32Array(d);for(let g=0;g<d;g++){let A=g*p;M[g]=A/e.sampleRate;let P=y.subarray(A,A+m),T;P.length>b?T=P.subarray(0,b):(T=new Float32Array(b),T.set(P));let z={channelData:[ae(T,r)],sampleRate:e.sampleRate,numberOfChannels:1,length:b,duration:b/e.sampleRate},R=await C(z,{fftSize:b,windowFunction:"none",channel:0}),x=new Array(R.magnitude.length);for(let w=0;w<R.magnitude.length;w++){let q=R.magnitude[w]??0;x[w]=q*q}let v=new Array(l);for(let w=0;w<l;w++){let q=0,_=D[w];if(_)for(let O=0;O<_.length&&O<x.length;O++)q+=(x[O]??0)*(_[O]??0);v[w]=Math.max(q,1e-10)}let L=v.map(w=>Math.log(w)),V=se(L,i),$=ce(V,u);I.push($)}return{mfcc:I,times:M,frameInfo:{frameSizeMs:o,hopSizeMs:n,numFrames:d,numCoeffs:i},frequencyRange:{min:a,max:c}}}function me(e,t=2){let o=e.length,n=e[0]?.length||0,r=[],s=[];for(let l=0;l<o;l++){let i=[];for(let a=0;a<n;a++){let c=0,f=0;for(let u=-t;u<=t;u++){let m=Math.max(0,Math.min(o-1,l+u)),p=e[m]?.[a]||0;c+=u*p,f+=u*u}i[a]=f>0?c/f:0}r.push(i)}for(let l=0;l<o;l++){let i=[];for(let a=0;a<n;a++){let c=0,f=0;for(let u=-t;u<=t;u++){let m=Math.max(0,Math.min(o-1,l+u)),p=r[m]?.[a]||0;c+=u*p,f+=u*u}i[a]=f>0?c/f:0}s.push(i)}return{delta:r,deltaDelta:s}}async function _e(e,t={}){let{deltaWindowSize:o=2,computeDelta:n=!0,computeDeltaDelta:r=!0,...s}=t,l=await ue(e,s),i={...l};if(n||r){let a=me(l.mfcc,o);n&&(i.delta=a.delta),r&&(i.deltaDelta=a.deltaDelta)}return i}export{me as computeDeltaCoefficients,ue as getMFCC,_e as getMFCCWithDelta,Ee as getSpectralCrest,Ne as getSpectralEntropy,ee as getSpectralFeatures,ze as getTimeVaryingSpectralFeatures};