murmuraba
Version:
Real-time audio noise reduction with advanced chunked processing for web applications
86 lines (85 loc) • 3.49 kB
JavaScript
export class AudioResampler {
static resamplePCMIfNeeded(pcmData, options) {
const { targetSampleRate, inputSampleRate, logger } = options;
// Validate input parameters
if (!pcmData || pcmData.length === 0) {
throw new Error('PCM data cannot be empty');
}
if (!Number.isFinite(inputSampleRate) || inputSampleRate <= 0) {
throw new Error(`Invalid input sample rate: ${inputSampleRate}`);
}
if (!Number.isFinite(targetSampleRate) || targetSampleRate <= 0) {
throw new Error(`Invalid target sample rate: ${targetSampleRate}`);
}
// No resampling needed
if (inputSampleRate === targetSampleRate) {
logger?.debug(`No resampling needed: already at ${targetSampleRate}Hz`);
return {
resampledData: pcmData,
outputSampleRate: targetSampleRate,
wasResampled: false
};
}
logger?.info(`Resampling from ${inputSampleRate}Hz to ${targetSampleRate}Hz...`);
try {
const resampled = this.resamplePCM(pcmData, inputSampleRate, targetSampleRate);
logger?.debug(`Resampling complete: ${resampled.length} samples at ${targetSampleRate}Hz`);
return {
resampledData: resampled,
outputSampleRate: targetSampleRate,
wasResampled: true
};
}
catch (error) {
throw new Error(`Resampling failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
static resampleToRNNoiseRate(pcmData, inputSampleRate, logger) {
return this.resamplePCMIfNeeded(pcmData, {
targetSampleRate: this.TARGET_SAMPLE_RATE,
inputSampleRate,
logger
});
}
static pcm16ToFloat32(pcm) {
const f = new Float32Array(pcm.length);
for (let i = 0; i < pcm.length; ++i) {
f[i] = pcm[i] / 32768.0;
}
return f;
}
static float32ToPcm16(float32) {
const pcm = new Int16Array(float32.length);
for (let i = 0; i < float32.length; ++i) {
pcm[i] = Math.max(-32768, Math.min(32767, Math.round(float32[i] * 32768)));
}
return pcm;
}
static resamplePCM(pcm, fromRate, toRate) {
const input = this.pcm16ToFloat32(pcm);
const output = this.linearInterpolationResample(input, fromRate, toRate);
return this.float32ToPcm16(output);
}
/**
* Simple linear interpolation resampler
* This is a basic implementation that should work for most audio resampling needs
*/
static linearInterpolationResample(input, fromRate, toRate) {
if (fromRate === toRate) {
return input;
}
const ratio = fromRate / toRate;
const outputLength = Math.floor(input.length / ratio);
const output = new Float32Array(outputLength);
for (let i = 0; i < outputLength; i++) {
const srcIndex = i * ratio;
const srcIndexFloor = Math.floor(srcIndex);
const srcIndexCeil = Math.min(srcIndexFloor + 1, input.length - 1);
const fraction = srcIndex - srcIndexFloor;
// Linear interpolation between two samples
output[i] = input[srcIndexFloor] * (1 - fraction) + input[srcIndexCeil] * fraction;
}
return output;
}
}
AudioResampler.TARGET_SAMPLE_RATE = 48000;