44 lines
3.0 KiB
JavaScript
44 lines
3.0 KiB
JavaScript
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
const copyBuffer=(buffer,rate=buffer.sampleRate)=>{
|
|
const channels=Array.from({length:buffer.numberOfChannels},(_,i)=>new Float32Array(buffer.getChannelData(i)));
|
|
return {sampleRate:rate,numberOfChannels:channels.length,length:channels[0]?.length||0,getChannelData:i=>channels[i]};
|
|
};
|
|
export async function decodeIR(context,bytes) {
|
|
const decoded=await context.decodeAudioData(bytes.slice(0));
|
|
return resampleIR(decoded,context.sampleRate);
|
|
}
|
|
export function resampleIR(buffer,targetRate) {
|
|
if(!Number.isFinite(targetRate)||targetRate<8000||targetRate>96000||!buffer?.numberOfChannels||buffer.numberOfChannels>4||!buffer.length) throw new RangeError('Invalid IR or target sample rate');
|
|
if(buffer.sampleRate===targetRate) return copyBuffer(buffer,targetRate);
|
|
const length=Math.max(1,Math.round(buffer.length*targetRate/buffer.sampleRate));
|
|
const channels=[];
|
|
for(let ch=0;ch<buffer.numberOfChannels;ch++) {
|
|
const input=buffer.getChannelData(ch), output=new Float32Array(length), ratio=buffer.sampleRate/targetRate;
|
|
for(let i=0;i<length;i++) {const x=i*ratio, a=Math.min(Math.floor(x),input.length-1), b=Math.min(a+1,input.length-1), f=x-a;output[i]=input[a]*(1-f)+input[b]*f;}
|
|
channels.push(output);
|
|
}
|
|
return {sampleRate:targetRate,numberOfChannels:channels.length,length,getChannelData:i=>channels[i]};
|
|
}
|
|
export function trimIR(buffer,{thresholdDb=-80}={}) {
|
|
if(!Number.isFinite(thresholdDb)||thresholdDb>0||thresholdDb<-160) throw new RangeError('Invalid trim threshold');
|
|
const channels=Array.from({length:buffer.numberOfChannels},(_,i)=>buffer.getChannelData(i));
|
|
const threshold=10**(thresholdDb/20);let first=buffer.length,last=-1;
|
|
for(let i=0;i<buffer.length;i++) {let peak=0;for(const channel of channels) peak=Math.max(peak,Math.abs(channel[i]));if(peak>=threshold){first=Math.min(first,i);last=i;}}
|
|
if(last<0) first=last=0;
|
|
const result=channels.map(channel=>channel.slice(first,last+1));
|
|
return {buffer:{sampleRate:buffer.sampleRate,numberOfChannels:result.length,length:result[0]?.length||0,getChannelData:i=>result[i]},startFrame:first,endFrame:last+1};
|
|
}
|
|
export function normalizeIR(buffer,{peak=0.5}={}) {
|
|
if(!Number.isFinite(peak)||peak<=0||peak>1) throw new RangeError('Peak must be in (0, 1]');
|
|
const channels=Array.from({length:buffer.numberOfChannels},(_,i)=>new Float32Array(buffer.getChannelData(i)));
|
|
let max=0;for(const channel of channels) for(const value of channel) max=Math.max(max,Math.abs(value));
|
|
const gain=max===0?1:peak/max;for(const channel of channels) for(let i=0;i<channel.length;i++) channel[i]*=gain;
|
|
return {sampleRate:buffer.sampleRate,numberOfChannels:channels.length,length:channels[0]?.length||0,getChannelData:i=>channels[i]};
|
|
}
|
|
export function prepareIR(context,buffer,{trim=false,normalize=true,peak=0.5,thresholdDb=-80}={}) {
|
|
let result=resampleIR(buffer,context.sampleRate);
|
|
if(trim) result=trimIR(result,{thresholdDb}).buffer;
|
|
if(normalize) result=normalizeIR(result,{peak});
|
|
return result;
|
|
}
|