mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-11 11:19:24 +00:00
81 lines
3.1 KiB
JavaScript
81 lines
3.1 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Minimal PCM WAV parser for Node.js test environments.
|
|
* Supports: mono/stereo, 16-bit and 24-bit signed PCM (audio format 1).
|
|
* Mixes all channels down to mono and returns a normalized Float32Array in [-1, 1].
|
|
*/
|
|
function parseWav(nodeBuffer) {
|
|
// Node.js Buffer.buffer is a pooled ArrayBuffer; slice to get an exact view.
|
|
const ab = nodeBuffer.buffer.slice(
|
|
nodeBuffer.byteOffset,
|
|
nodeBuffer.byteOffset + nodeBuffer.byteLength
|
|
);
|
|
const view = new DataView(ab);
|
|
const bytes = new Uint8Array(ab);
|
|
|
|
function fourCC(offset) {
|
|
return String.fromCharCode(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]);
|
|
}
|
|
|
|
if (fourCC(0) !== 'RIFF' || fourCC(8) !== 'WAVE') {
|
|
throw new Error('Not a valid WAV file');
|
|
}
|
|
|
|
let audioFormat = 0, numChannels = 0, sampleRate = 0, bitsPerSample = 0;
|
|
let dataStart = -1, dataSize = 0;
|
|
|
|
let offset = 12;
|
|
while (offset + 8 <= ab.byteLength) {
|
|
const id = fourCC(offset);
|
|
const size = view.getUint32(offset + 4, true);
|
|
if (id === 'fmt ') {
|
|
audioFormat = view.getUint16(offset + 8, true);
|
|
numChannels = view.getUint16(offset + 10, true);
|
|
sampleRate = view.getUint32(offset + 12, true);
|
|
bitsPerSample = view.getUint16(offset + 22, true);
|
|
} else if (id === 'data') {
|
|
dataStart = offset + 8;
|
|
dataSize = size;
|
|
break;
|
|
}
|
|
offset += 8 + size + (size & 1); // chunks are word-aligned
|
|
}
|
|
|
|
if (dataStart === -1) throw new Error('No data chunk found in WAV file');
|
|
if (audioFormat !== 1) throw new Error(`Only PCM WAV supported (audio format ${audioFormat})`);
|
|
if (bitsPerSample !== 16 && bitsPerSample !== 24) {
|
|
throw new Error(`Only 16-bit and 24-bit PCM supported (got ${bitsPerSample}-bit)`);
|
|
}
|
|
|
|
const bytesPerSample = bitsPerSample >> 3;
|
|
const bytesPerFrame = numChannels * bytesPerSample;
|
|
const nFrames = Math.floor(dataSize / bytesPerFrame);
|
|
if (dataStart + nFrames * bytesPerFrame > ab.byteLength) {
|
|
throw new Error('WAV data chunk extends beyond end of file');
|
|
}
|
|
const samples = new Float32Array(nFrames);
|
|
|
|
for (let i = 0; i < nFrames; i++) {
|
|
let sum = 0;
|
|
for (let ch = 0; ch < numChannels; ch++) {
|
|
const byteOffset = dataStart + i * bytesPerFrame + ch * bytesPerSample;
|
|
if (bitsPerSample === 16) {
|
|
sum += view.getInt16(byteOffset, true);
|
|
} else {
|
|
// 24-bit: read 3 bytes little-endian, sign-extend from bit 23.
|
|
const lo = bytes[byteOffset];
|
|
const mi = bytes[byteOffset + 1];
|
|
const hi = bytes[byteOffset + 2];
|
|
const raw = (lo | (mi << 8) | (hi << 16));
|
|
sum += raw & 0x800000 ? raw - 0x1000000 : raw;
|
|
}
|
|
}
|
|
const scale = bitsPerSample === 16 ? 32768.0 : 8388608.0;
|
|
samples[i] = (sum / numChannels) / scale;
|
|
}
|
|
return { samples, sampleRate };
|
|
}
|
|
|
|
module.exports = { parseWav };
|