echogarden
Version:
An easy-to-use speech toolset. Includes tools for synthesis, recognition, alignment, speech translation, language detection, source separation and more.
90 lines • 4.92 kB
JavaScript
import { cloneRawAudio } from '../audio/AudioUtilities.js';
import { concatFloat32Arrays, isWasmSimdSupported } from '../utilities/Utilities.js';
import { wrapEmscriptenModuleHeap } from 'wasm-heap-manager';
let speexResamplerInstance;
export async function resampleAudioSpeex(rawAudio, outSampleRate, quality = 0) {
const channelCount = rawAudio.audioChannels.length;
const inSampleRate = rawAudio.sampleRate;
const totalSampleCount = rawAudio.audioChannels[0].length;
const sampleRateRatio = outSampleRate / inSampleRate;
if (inSampleRate === outSampleRate) {
return cloneRawAudio(rawAudio);
}
if (totalSampleCount === 0) {
return {
...cloneRawAudio(rawAudio),
sampleRate: outSampleRate
};
}
const m = await getSpeexResamplerInstance();
const wasmHeap = wrapEmscriptenModuleHeap(m);
function speexResultCodeToString(resultCode) {
const errorStrPtr = m._speex_resampler_strerror(resultCode);
const messageRef = wasmHeap.wrapNullTerminatedUtf8String(errorStrPtr);
const message = messageRef.value;
return message;
}
const initErrRef = wasmHeap.allocInt32();
const resamplerStateAddress = m._speex_resampler_init(channelCount, inSampleRate, outSampleRate, quality, initErrRef.address);
let resultCode = initErrRef.value;
if (resultCode != 0) {
throw new Error(`Speex resampler failed while initializing with code ${resultCode}: ${speexResultCodeToString(resultCode)}`);
}
const inputLatency = m._speex_resampler_get_input_latency(resamplerStateAddress);
const outputLatency = m._speex_resampler_get_output_latency(resamplerStateAddress);
const maxChunkSize = 2 ** 20;
const inputChunkSampleCountRef = wasmHeap.allocInt32();
const outputChunkSampleCountRef = wasmHeap.allocInt32();
const inputChunkSamplesRef = wasmHeap.allocFloat32Array(maxChunkSize * 2);
const outputChunkSamplesRef = wasmHeap.allocFloat32Array(Math.floor(maxChunkSize * sampleRateRatio) * 2);
const resampledAudioChunksForChannels = [];
for (let channelIndex = 0; channelIndex < channelCount; channelIndex++) {
resampledAudioChunksForChannels.push([]);
}
for (let channelIndex = 0; channelIndex < channelCount; channelIndex++) {
for (let readOffset = 0; readOffset < totalSampleCount;) {
const isLastChunk = readOffset + maxChunkSize >= totalSampleCount;
const inputPaddingSize = isLastChunk ? inputLatency : 0;
const maxSamplesToRead = Math.min(maxChunkSize, totalSampleCount - readOffset) + inputPaddingSize;
const maxSamplesToWrite = outputChunkSamplesRef.elementCount;
const inputChunkSamplesForChannel = rawAudio.audioChannels[channelIndex].slice(readOffset, readOffset + maxSamplesToRead);
inputChunkSampleCountRef.value = maxSamplesToRead;
outputChunkSampleCountRef.value = maxSamplesToWrite;
inputChunkSamplesRef.view.set(inputChunkSamplesForChannel);
resultCode = m._speex_resampler_process_float(resamplerStateAddress, channelIndex, inputChunkSamplesRef.address, inputChunkSampleCountRef.address, outputChunkSamplesRef.address, outputChunkSampleCountRef.address);
if (resultCode != 0) {
throw new Error(`Speex resampler failed while resampling with code ${resultCode}: ${speexResultCodeToString(resultCode)}`);
}
const samplesReadCount = inputChunkSampleCountRef.value;
const samplesWrittenCount = outputChunkSampleCountRef.value;
const resampledChannelAudio = outputChunkSamplesRef.view.slice(0, samplesWrittenCount);
resampledAudioChunksForChannels[channelIndex].push(resampledChannelAudio);
readOffset += samplesReadCount;
}
}
m._speex_resampler_destroy(resamplerStateAddress);
wasmHeap.freeAll();
const resampledAudio = {
audioChannels: [],
sampleRate: outSampleRate
};
for (let i = 0; i < channelCount; i++) {
resampledAudioChunksForChannels[i][0] = resampledAudioChunksForChannels[i][0].slice(outputLatency);
resampledAudio.audioChannels.push(concatFloat32Arrays(resampledAudioChunksForChannels[i]));
}
return resampledAudio;
}
export async function getSpeexResamplerInstance() {
if (!speexResamplerInstance) {
if (await isWasmSimdSupported()) {
const { default: SpeexResamplerInitializer } = await import('@echogarden/speex-resampler-wasm/simd');
speexResamplerInstance = await SpeexResamplerInitializer();
}
else {
const { default: SpeexResamplerInitializer } = await import('@echogarden/speex-resampler-wasm');
speexResamplerInstance = await SpeexResamplerInitializer();
}
}
return speexResamplerInstance;
}
//# sourceMappingURL=SpeexResampler.js.map