2023-10-05 21:27:34 +08:00
|
|
|
/**
|
|
|
|
* Encodes PCM float-32 in native endianness into a WAV binary.
|
|
|
|
*/
|
|
|
|
export function encodePcmAsWav(buffer, numChannels, samplingRate) {
|
|
|
|
// See http://soundfile.sapp.org/doc/WaveFormat
|
|
|
|
|
|
|
|
const HEADER_SIZE = 44;
|
|
|
|
|
|
|
|
const wavBuffer = new ArrayBuffer(HEADER_SIZE + buffer.byteLength);
|
|
|
|
const view = new DataView(wavBuffer);
|
|
|
|
|
|
|
|
const numFrames = buffer.byteLength / 4;
|
|
|
|
const bytesPerSample = 4;
|
|
|
|
|
|
|
|
const blockAlign = numChannels * bytesPerSample;
|
|
|
|
const byteRate = samplingRate * blockAlign;
|
|
|
|
const dataSize = numFrames * blockAlign;
|
|
|
|
|
|
|
|
let offset = 0;
|
|
|
|
|
|
|
|
function writeUint32Big(int) {
|
|
|
|
view.setUint32(offset, int, false);
|
|
|
|
offset += 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
function writeUint32(int) {
|
|
|
|
view.setUint32(offset, int, true);
|
|
|
|
offset += 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
function writeUint16(int) {
|
|
|
|
view.setUint16(offset, int, true);
|
|
|
|
offset += 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
function writeFloat32(int) {
|
|
|
|
view.setFloat32(offset, int, true);
|
|
|
|
offset += 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
writeUint32Big(0x52494646);
|
|
|
|
writeUint32(36 + dataSize);
|
|
|
|
writeUint32Big(0x57415645);
|
|
|
|
|
|
|
|
writeUint32Big(0x666d7420);
|
|
|
|
writeUint32(16);
|
|
|
|
writeUint16(3); // 3 represents 32-bit float PCM
|
|
|
|
writeUint16(numChannels);
|
|
|
|
writeUint32(samplingRate);
|
|
|
|
writeUint32(byteRate);
|
|
|
|
writeUint16(blockAlign);
|
|
|
|
writeUint16(bytesPerSample * 8);
|
|
|
|
|
|
|
|
writeUint32Big(0x64617461);
|
|
|
|
writeUint32(dataSize);
|
|
|
|
|
|
|
|
const array = new Float32Array(buffer);
|
|
|
|
|
|
|
|
for (let i = 0; i < array.length; i++) {
|
|
|
|
writeFloat32(array[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return wavBuffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
const HEADER_LENGTH = 4;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Builds a single buffer with JSON-serialized `meta` and `buffer`.
|
|
|
|
*/
|
|
|
|
export function encodeAnnotatedBuffer(meta, buffer) {
|
|
|
|
const encoder = new TextEncoder();
|
|
|
|
const metaArray = encoder.encode(JSON.stringify(meta));
|
|
|
|
|
|
|
|
const raw = new ArrayBuffer(
|
2024-02-03 00:56:38 +08:00
|
|
|
HEADER_LENGTH + metaArray.byteLength + buffer.byteLength,
|
2023-10-05 21:27:34 +08:00
|
|
|
);
|
|
|
|
const view = new DataView(raw);
|
|
|
|
|
|
|
|
view.setUint32(0, metaArray.byteLength);
|
|
|
|
new Uint8Array(raw, HEADER_LENGTH, metaArray.byteLength).set(metaArray);
|
|
|
|
new Uint8Array(raw, HEADER_LENGTH + metaArray.byteLength).set(
|
2024-02-03 00:56:38 +08:00
|
|
|
new Uint8Array(buffer),
|
2023-10-05 21:27:34 +08:00
|
|
|
);
|
|
|
|
|
|
|
|
return raw;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Decodes binary annotated with JSON-serialized metadata.
|
|
|
|
*/
|
|
|
|
export function decodeAnnotatedBuffer(raw) {
|
|
|
|
const view = new DataView(raw);
|
|
|
|
const metaArrayLength = view.getUint32(0);
|
|
|
|
|
|
|
|
const metaArray = new Uint8Array(raw, HEADER_LENGTH, metaArrayLength);
|
|
|
|
const buffer = raw.slice(HEADER_LENGTH + metaArrayLength);
|
|
|
|
|
|
|
|
const decoder = new TextDecoder();
|
|
|
|
const meta = JSON.parse(decoder.decode(metaArray));
|
|
|
|
|
|
|
|
return [meta, buffer];
|
|
|
|
}
|