UNPKG

audio-inspect

Version:

Lightweight yet powerful audio analysis library

2 lines (1 loc) 10.3 kB
var g=class e extends Error{name="AudioInspectError";code;cause;details;constructor(r,t,o,a){super(t,{cause:o}),this.code=r,this.cause=o,this.details=a,Error.captureStackTrace&&Error.captureStackTrace(this,e)}};function j(e){if(![2,4,8].includes(e))throw new g("INVALID_INPUT","Oversampling factor must be 2, 4, or 8. Sinc interpolation quality is not guaranteed for other values.")}function z(e,r={}){if(e.length===0)return 0;let{factor:t=4,interpolation:o="cubic"}=r;j(t);let a=0;if(t<=1||e.length===1){for(let n=0;n<e.length;n++){let l=Math.abs(e[n]);l>a&&(a=l)}return a}if(o==="sinc"){let n=i=>{if(Math.abs(i)<1e-10)return 1;let u=Math.PI*i;return Math.abs(i)<3?3*Math.sin(u/3)*Math.sin(u)/(u*u):0},l=(e.length-1)*t+1;for(let i=0;i<l;i++){let u=i/t,c=0;for(let m=-3;m<=3;m++){let f=Math.floor(u)+m;f>=0&&f<e.length&&(c+=e[f]*n(u-f))}let h=Math.abs(c);h>a&&(a=h)}return a}for(let n=0;n<e.length-1;n++){let l=e[n],i=e[n+1],u=l,c=i;o==="cubic"&&(u=e[Math.max(0,n-1)],c=e[Math.min(e.length-1,n+2)]);for(let h=0;h<t;h++){let m=h/t,f=o==="linear"?l+(i-l)*m:.5*(2*l+(-u+i)*m+(2*u-5*l+4*i-c)*m*m+(-u+3*l-3*i+c)*m*m*m),d=Math.abs(f);d>a&&(a=d)}}let s=Math.abs(e[e.length-1]);return s>a&&(a=s),a}var U=Object.freeze([Object.freeze([.001708984375,.010986328125,-.0196533203125,.033203125,-.0594482421875,.1373291015625,.97216796875,-.102294921875,.047607421875,-.026611328125,.014892578125,-.00830078125]),Object.freeze([-.0291748046875,.029296875,-.0517578125,.089111328125,-.16650390625,.465087890625,.77978515625,-.2003173828125,.1015625,-.0582275390625,.0330810546875,-.0189208984375]),Object.freeze([-.0189208984375,.0330810546875,-.0582275390625,.1015625,-.2003173828125,.77978515625,.465087890625,-.16650390625,.089111328125,-.0517578125,.029296875,-.0291748046875]),Object.freeze([-.00830078125,.014892578125,-.026611328125,.047607421875,-.102294921875,.97216796875,.1373291015625,-.0594482421875,.033203125,-.0196533203125,.010986328125,.001708984375])]);function G(e){if(e!==2&&e!==4)throw new g("INVALID_INPUT","BS.1770 true peak supports oversampling factor 2 or 4")}function H(e){let r=0;for(let t=0;t<e.length;t++){let o=Math.abs(e[t]);o>r&&(r=o)}return r}function Q(e,r){let t=U[0];if(!t||t.length===0)return 0;let o=t.length,a=new Float64Array(o),s=0,n=0;for(let l=0;l<e.length;l++){a[s]=e[l];for(let i of r){let u=U[i];if(!u)continue;let c=0;for(let m=0;m<o;m++){let f=s-m;f<0&&(f+=o),c+=a[f]*u[m]}let h=Math.abs(c);h>n&&(n=h)}s=(s+1)%o}return n}function q(e,r={}){if(e.length===0)return 0;let t=r.factor??4;G(t);let o=H(e),s=Q(e,t===2?[0,2]:[0,1,2,3]);return s>o&&(o=s),o}function E(e,r=1,t=-1/0){return!Number.isFinite(e)||!Number.isFinite(r)||e<=0||r<=0?t:20*Math.log10(e/r)}function w(e,r=1,t=-1/0){return!Number.isFinite(e)||!Number.isFinite(r)||e<=0||r<=0?t:10*Math.log10(e/r)}var V=new Map;function X(e){if(!isFinite(e)||e<=0)throw new g("INVALID_INPUT","Sample rate must be a positive finite value");if(e<8e3||e>384e3)throw new g("UNSUPPORTED_FORMAT",`Sample rate ${e} Hz is not supported`)}function $(e){X(e);let r=V.get(e);if(r)return r;let t=[];if(e===48e3)t.push({b0:1.53512485958697,b1:-2.69169618940638,b2:1.19839281085285,a0:1,a1:-1.69065929318241,a2:.73248077421585}),t.push({b0:1,b1:-2,b2:1,a0:1,a1:-1.99004745483398,a2:.99007225036621});else{let o=1681.974450955532,a=3.99984385397,s=.7071752369554193,n=Math.tan(Math.PI*o/e),l=Math.pow(10,a/20),i=Math.pow(l,.499666774155),u=1+n/s+n*n;t.push({b0:(l+i*n/s+n*n)/u,b1:2*(n*n-l)/u,b2:(l-i*n/s+n*n)/u,a0:1,a1:2*(n*n-1)/u,a2:(1-n/s+n*n)/u});let c=38.13547087613982,h=.5003270373253953,m=Math.tan(Math.PI*c/e),f=1+m/h+m*m;t.push({b0:1,b1:-2,b2:1,a0:1,a1:2*(m*m-1)/f,a2:(1-m/h+m*m)/f});let y=1/Y(t,997,e).magnitude;t[0]&&(t[0].b0*=y,t[0].b1*=y,t[0].b2*=y)}return V.set(e,t),t}function Y(e,r,t){let o=2*Math.PI*r/t,a=1,s=0;for(let i of e){let u=Math.cos(o),c=Math.sin(o),h=Math.cos(2*o),m=Math.sin(2*o),f=i.b0+i.b1*u+i.b2*h,d=-i.b1*c-i.b2*m,y=i.a0+i.a1*u+i.a2*h,k=-i.a1*c-i.a2*m,F=y*y+k*k;if(F===0)throw new g("INVALID_INPUT","Division by zero occurred while calculating frequency response");let I=(f*y+d*k)/F,M=(d*y-f*k)/F,O=a*I-s*M,R=a*M+s*I;a=O,s=R}let n=Math.sqrt(a*a+s*s),l=Math.atan2(s,a);return{magnitude:n,phase:l}}function _(e){return $(e)}var Z=-70,J=10,D=400,K=.75,ee=3e3,te=400;function W(e,r,t,o,a){if(a<=0)return;let{x1:s,x2:n,y1:l,y2:i}=o;for(let u=0;u<a;u++){let c=e[u],h=t.b0*c+t.b1*s+t.b2*n-t.a1*l-t.a2*i;r[u]=h,n=s,s=c,i=l,l=h}o.x1=s,o.x2=n,o.y1=l,o.y2=i}function ne(e,r=e.length){let t=0,o=Math.min(r,e.length);if(o===0)return-1/0;for(let a=0;a<o;a++){let s=e[a];if(!s||s.length===0)continue;let n=0;for(let i=0;i<s.length;i++){let u=s[i];n+=u*u}if(s.length===0)continue;t+=1*(n/s.length)}return-.691+w(Math.max(1e-15,t),1)}function v(e){if(e.length===0)return-1/0;let r=e.reduce((t,o)=>t+Math.pow(10,(o+.691)/10),0);return-.691+w(r/e.length,1)}function de(e){return _(e)}function ye(e,r={}){let{channelMode:t=e.numberOfChannels>=2?"stereo":"mono",gated:o=!0,calculateShortTerm:a=!1,calculateMomentary:s=!1,collectSeries:n=!1,calculateLoudnessRange:l=!1,calculateTruePeak:i=!1,truePeakMethod:u="bs1770",truePeakOversamplingFactor:c=4,truePeakInterpolation:h="sinc"}=r;if(e.numberOfChannels===0)throw new g("INVALID_INPUT","No processable channels available");let m=re(e.sampleRate,{channelMode:t,gated:o,maxDurationMs:e.duration*1e3+5e3}),f=[];if(t==="mono"){let b=e.channelData[0];b&&f.push(b)}else{let b=e.channelData[0],p=e.channelData[1];b&&f.push(b),p&&f.push(p)}if(f.length===0)throw new g("INVALID_INPUT","No processable channels available");let d=[],y=f[0].length,k=D*(1-K),F=Math.max(1,Math.floor(k/1e3*e.sampleRate));for(let b=0;b<y;b+=F){let p=Math.min(b+F,y),A=f.map(T=>T.subarray(b,p));d.push(A)}let I=n===!0||n==="both"||n==="shortTerm",M=n===!0||n==="both"||n==="momentary",O=l,R=[],C=[],N=[],B=[],S;for(let b=0;b<d.length;b++){let p=d[b];S=m.process(p);let T=Math.min((b+1)*F,y)/e.sampleRate;if((I||M)&&R.push(T),M){let P=Number.isFinite(S.momentary)?S.momentary:-1/0;C.push(P)}if(I){let P=Number.isFinite(S.shortTerm)?S.shortTerm:-1/0;N.push(P)}O&&B.push(S.shortTerm)}let x={integrated:S?.integrated??-1/0};if(a&&(x.shortTerm=S?.shortTerm??-1/0),s&&(x.momentary=S?.momentary??-1/0),l&&B.length>0){let b=B.filter(p=>p>-70&&isFinite(p)).sort((p,A)=>p-A);if(b.length>0){let p=Math.floor(b.length*.1),A=Math.floor(b.length*.95),T=b[p]??-1/0,P=b[A]??-1/0;x.loudnessRange=P-T,x.statistics={percentile10:T,percentile95:P}}}if((I||M)&&(x.series={times:Float32Array.from(R),...I?{shortTerm:Float32Array.from(N)}:{},...M?{momentary:Float32Array.from(C)}:{}}),i){if(u==="bs1770"&&c===8)throw new g("INVALID_INPUT","truePeakOversamplingFactor=8 is unsupported for truePeakMethod='bs1770'; use factor 2 or 4, or switch to interSamplePeak");x.truePeak=f.map(b=>{if(u==="interSamplePeak"){let A=z(b,{factor:c,interpolation:h});return E(A,1)}let p=q(b,{factor:c===2?2:4});return E(p,1)})}return x}var L=class{sampleRate;channelMode;blockSize;hopSize;blockBuffer=[];maxBlocks;currentSamples=[];filterStage1Scratch=[];filterStage2Scratch=[];sampleCount=0;biquadStates=[];totalSamplesProcessed=0;gated;constructor(r,t="stereo",o=3e4,a=!0){if(this.sampleRate=r,this.channelMode=t,this.gated=a,this.blockSize=Math.floor(D/1e3*r),this.blockSize===0)throw new g("INVALID_INPUT",`Sample rate ${r} Hz is too low for realtime LUFS processing. Minimum required sample rate is ${Math.ceil(1e3/D)} Hz.`);this.hopSize=Math.floor(this.blockSize*(1-K)),this.maxBlocks=Math.ceil(o/(this.hopSize/r*1e3));let s=t==="stereo"?2:1;for(let n=0;n<s;n++)this.currentSamples.push(new Float32Array(this.blockSize)),this.filterStage1Scratch.push(new Float32Array(this.blockSize)),this.filterStage2Scratch.push(new Float32Array(this.blockSize)),this.biquadStates.push([{x1:0,x2:0,y1:0,y2:0},{x1:0,x2:0,y1:0,y2:0}])}ensureFilterScratchCapacity(r,t){for(let o=0;o<t;o++){let a=this.filterStage1Scratch[o],s=this.filterStage2Scratch[o];(!a||a.length<r)&&(this.filterStage1Scratch[o]=new Float32Array(r)),(!s||s.length<r)&&(this.filterStage2Scratch[o]=new Float32Array(r))}}process(r){let t=this.channelMode==="stereo"?Math.min(r.length,2):1,o=_(this.sampleRate),a=r[0]?.length||0;if(a===0)return this.calculateCurrentLUFS();this.ensureFilterScratchCapacity(a,t);for(let n=0;n<t;n++){let l=r[n],i=this.filterStage1Scratch[n],u=this.filterStage2Scratch[n];if(!(!i||!u)){if(!l){u.fill(0,0,a);continue}W(l,i,o[0],this.biquadStates[n][0],a),W(i,u,o[1],this.biquadStates[n][1],a)}}let s=0;for(;s<a;){let n=this.blockSize-this.sampleCount,l=Math.min(a-s,n);for(let i=0;i<t;i++){let u=this.filterStage2Scratch[i],c=this.currentSamples[i];u&&c.set(u.subarray(s,s+l),this.sampleCount)}if(this.sampleCount+=l,s+=l,this.sampleCount>=this.blockSize){let i=ne(this.currentSamples,t);if(isFinite(i)){let u=this.totalSamplesProcessed+this.blockSize/2;this.blockBuffer.push([i,u]),this.blockBuffer.length>this.maxBlocks&&this.blockBuffer.shift()}for(let u=0;u<t;u++)this.currentSamples[u].copyWithin(0,this.hopSize);this.sampleCount=this.blockSize-this.hopSize,this.totalSamplesProcessed+=this.hopSize}}return this.calculateCurrentLUFS()}calculateCurrentLUFS(){if(this.blockBuffer.length===0)return{integrated:-1/0,momentary:-1/0,shortTerm:-1/0};let r=this.totalSamplesProcessed+this.sampleCount,t=te/1e3*this.sampleRate,o=ee/1e3*this.sampleRate,a=this.blockBuffer.filter(([c,h])=>r-h<=t).map(([c])=>c),s=this.blockBuffer.filter(([c,h])=>r-h<=o).map(([c])=>c),n=-1/0,l=this.blockBuffer.map(([c])=>c).filter(c=>isFinite(c));if(!this.gated)n=v(l);else if(l.length>0){let c=l.filter(h=>h>=Z);if(c.length>0){let m=v(c)-J,f=c.filter(d=>d>=m);n=v(f)}}let i=-1/0;if(a.length>0){let c=a.reduce((h,m)=>h+Math.pow(10,(m+.691)/10),0);i=-.691+w(c/a.length,1)}let u=-1/0;if(s.length>0){let c=s.reduce((h,m)=>h+Math.pow(10,(m+.691)/10),0);u=-.691+w(c/s.length,1)}return{integrated:n,momentary:i,shortTerm:u}}reset(){this.blockBuffer=[],this.sampleCount=0,this.totalSamplesProcessed=0;for(let r=0;r<this.currentSamples.length;r++){this.currentSamples[r].fill(0);for(let t=0;t<this.biquadStates[r].length;t++)this.biquadStates[r][t]={x1:0,x2:0,y1:0,y2:0}}}getBufferSize(){return this.blockBuffer.length}};function re(e,r={}){let{channelMode:t="stereo",maxDurationMs:o=3e4,gated:a=!0}=r;return new L(e,t,o,a)}export{de as getKWeightingCoeffs,ye as getLUFS,re as getLUFSRealtime};