89 lines
2.2 KiB
TypeScript
89 lines
2.2 KiB
TypeScript
export interface Pcm16Wav {
|
|
sampleRate: number;
|
|
samples: Int16Array;
|
|
}
|
|
|
|
export function parsePcm16Wav(buffer: ArrayBuffer): Pcm16Wav | null {
|
|
if (buffer.byteLength < 44) {
|
|
return null;
|
|
}
|
|
|
|
const view = new DataView(buffer);
|
|
|
|
function readAscii(offset: number, length: number): string {
|
|
let out = "";
|
|
for (let i = 0; i < length; i += 1) {
|
|
out += String.fromCharCode(view.getUint8(offset + i));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
if (readAscii(0, 4) !== "RIFF" || readAscii(8, 4) !== "WAVE") {
|
|
return null;
|
|
}
|
|
|
|
let offset = 12;
|
|
let channels = 0;
|
|
let sampleRate = 0;
|
|
let bitsPerSample = 0;
|
|
let dataOffset = 0;
|
|
let dataSize = 0;
|
|
|
|
while (offset + 8 <= buffer.byteLength) {
|
|
const chunkId = readAscii(offset, 4);
|
|
const chunkSize = view.getUint32(offset + 4, true);
|
|
const chunkDataOffset = offset + 8;
|
|
|
|
if (chunkDataOffset + chunkSize > buffer.byteLength) {
|
|
return null;
|
|
}
|
|
|
|
if (chunkId === "fmt " && chunkSize >= 16) {
|
|
const audioFormat = view.getUint16(chunkDataOffset, true);
|
|
channels = view.getUint16(chunkDataOffset + 2, true);
|
|
sampleRate = view.getUint32(chunkDataOffset + 4, true);
|
|
bitsPerSample = view.getUint16(chunkDataOffset + 14, true);
|
|
if (audioFormat !== 1) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if (chunkId === "data") {
|
|
dataOffset = chunkDataOffset;
|
|
dataSize = chunkSize;
|
|
break;
|
|
}
|
|
|
|
offset = chunkDataOffset + chunkSize + (chunkSize % 2);
|
|
}
|
|
|
|
if (!dataOffset || dataSize <= 0 || sampleRate <= 0 || bitsPerSample !== 16 || channels <= 0) {
|
|
return null;
|
|
}
|
|
|
|
const sampleCount = Math.floor(dataSize / 2);
|
|
const interleaved = new Int16Array(buffer, dataOffset, sampleCount);
|
|
|
|
if (channels === 1) {
|
|
return {
|
|
sampleRate,
|
|
samples: new Int16Array(interleaved),
|
|
};
|
|
}
|
|
|
|
const frameCount = Math.floor(interleaved.length / channels);
|
|
const mono = new Int16Array(frameCount);
|
|
for (let frame = 0; frame < frameCount; frame += 1) {
|
|
let sum = 0;
|
|
for (let channel = 0; channel < channels; channel += 1) {
|
|
sum += interleaved[frame * channels + channel] ?? 0;
|
|
}
|
|
mono[frame] = Math.round(sum / channels);
|
|
}
|
|
|
|
return {
|
|
sampleRate,
|
|
samples: mono,
|
|
};
|
|
}
|