Initial Commit
This commit is contained in:
156
packages/media/audio/audio-decode-wav/tests/fixtures/build-wav.ts
vendored
Normal file
156
packages/media/audio/audio-decode-wav/tests/fixtures/build-wav.ts
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Programmatic WAV fixture builders used by the test suite. No binary files
|
||||
* are checked in — every fixture is generated deterministically from
|
||||
* primitives at test time. Each builder returns a `Uint8Array` ready to be
|
||||
* fed to `decodeWav` or `createWavStreamDecoder`.
|
||||
*/
|
||||
|
||||
export interface BuildWavInput {
|
||||
readonly sampleRate: number;
|
||||
readonly channels: number;
|
||||
readonly bitsPerSample: number;
|
||||
readonly samplesPerChannel: number;
|
||||
/** `1` for integer PCM, `3` for IEEE float PCM. */
|
||||
readonly formatCode: 1 | 3;
|
||||
/** Per-channel sample generator. Defaults to silence. */
|
||||
readonly sampler?: (channel: number, index: number) => number;
|
||||
}
|
||||
|
||||
/** Build a well-formed RIFF/WAVE byte buffer. */
|
||||
export function buildWav(input: BuildWavInput): Uint8Array {
|
||||
const {
|
||||
sampleRate,
|
||||
channels,
|
||||
bitsPerSample,
|
||||
samplesPerChannel,
|
||||
formatCode,
|
||||
sampler = () => 0,
|
||||
} = input;
|
||||
const bytesPerSample = bitsPerSample / 8;
|
||||
const dataBytes = samplesPerChannel * channels * bytesPerSample;
|
||||
const headerBytes = 44;
|
||||
const buffer = new Uint8Array(headerBytes + dataBytes);
|
||||
const dv = new DataView(buffer.buffer);
|
||||
writeAscii(buffer, 0, "RIFF");
|
||||
dv.setUint32(4, headerBytes - 8 + dataBytes, true);
|
||||
writeAscii(buffer, 8, "WAVE");
|
||||
writeAscii(buffer, 12, "fmt ");
|
||||
dv.setUint32(16, 16, true);
|
||||
dv.setUint16(20, formatCode, true);
|
||||
dv.setUint16(22, channels, true);
|
||||
dv.setUint32(24, sampleRate, true);
|
||||
dv.setUint32(28, sampleRate * channels * bytesPerSample, true);
|
||||
dv.setUint16(32, channels * bytesPerSample, true);
|
||||
dv.setUint16(34, bitsPerSample, true);
|
||||
writeAscii(buffer, 36, "data");
|
||||
dv.setUint32(40, dataBytes, true);
|
||||
let p = headerBytes;
|
||||
for (let i = 0; i < samplesPerChannel; i++) {
|
||||
for (let c = 0; c < channels; c++) {
|
||||
const v = sampler(c, i);
|
||||
p += writeSample(dv, p, v, bitsPerSample, formatCode);
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
function writeAscii(b: Uint8Array, off: number, s: string): void {
|
||||
for (let i = 0; i < s.length; i++) b[off + i] = s.charCodeAt(i);
|
||||
}
|
||||
|
||||
function writeSample(
|
||||
dv: DataView,
|
||||
pos: number,
|
||||
value: number,
|
||||
bits: number,
|
||||
fmt: 1 | 3
|
||||
): number {
|
||||
if (fmt === 3) {
|
||||
if (bits === 32) {
|
||||
dv.setFloat32(pos, value, true);
|
||||
return 4;
|
||||
}
|
||||
dv.setFloat64(pos, value, true);
|
||||
return 8;
|
||||
}
|
||||
if (bits === 16) {
|
||||
const clipped = clip(value);
|
||||
const v = Math.round(clipped < 0 ? clipped * 32768 : clipped * 32767);
|
||||
dv.setInt16(pos, v, true);
|
||||
return 2;
|
||||
}
|
||||
if (bits === 24) {
|
||||
const clipped = clip(value);
|
||||
const v = Math.round(
|
||||
clipped < 0 ? clipped * 8388608 : clipped * 8388607
|
||||
);
|
||||
dv.setUint8(pos, v & 0xff);
|
||||
dv.setUint8(pos + 1, (v >> 8) & 0xff);
|
||||
dv.setUint8(pos + 2, (v >> 16) & 0xff);
|
||||
return 3;
|
||||
}
|
||||
const clipped = clip(value);
|
||||
const v = Math.round(
|
||||
clipped < 0 ? clipped * 2147483648 : clipped * 2147483647
|
||||
);
|
||||
dv.setInt32(pos, v, true);
|
||||
return 4;
|
||||
}
|
||||
|
||||
function clip(v: number): number {
|
||||
if (v > 1) return 1;
|
||||
if (v < -1) return -1;
|
||||
return v;
|
||||
}
|
||||
|
||||
/** Build an A-law WAV header — the body is silence; we only verify rejection. */
|
||||
export function buildAlawWav(): Uint8Array {
|
||||
const dataBytes = 32;
|
||||
const buffer = new Uint8Array(44 + dataBytes);
|
||||
const dv = new DataView(buffer.buffer);
|
||||
writeAscii(buffer, 0, "RIFF");
|
||||
dv.setUint32(4, 36 + dataBytes, true);
|
||||
writeAscii(buffer, 8, "WAVE");
|
||||
writeAscii(buffer, 12, "fmt ");
|
||||
dv.setUint32(16, 16, true);
|
||||
dv.setUint16(20, 6, true); // A-law format code
|
||||
dv.setUint16(22, 1, true);
|
||||
dv.setUint32(24, 8000, true);
|
||||
dv.setUint32(28, 8000, true);
|
||||
dv.setUint16(32, 1, true);
|
||||
dv.setUint16(34, 8, true);
|
||||
writeAscii(buffer, 36, "data");
|
||||
dv.setUint32(40, dataBytes, true);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/** Build a WAV whose `data` chunk declares double the bytes present. */
|
||||
export function buildTruncatedWav(): Uint8Array {
|
||||
const real = buildWav({
|
||||
sampleRate: 8000,
|
||||
channels: 1,
|
||||
bitsPerSample: 16,
|
||||
samplesPerChannel: 100,
|
||||
formatCode: 1,
|
||||
});
|
||||
const dv = new DataView(real.buffer);
|
||||
const declared = dv.getUint32(40, true);
|
||||
dv.setUint32(40, declared * 2, true);
|
||||
return real;
|
||||
}
|
||||
|
||||
/** Build a buffer whose first 4 bytes are zeroed (no RIFF magic). */
|
||||
export function buildCorruptHeaderWav(): Uint8Array {
|
||||
const real = buildWav({
|
||||
sampleRate: 8000,
|
||||
channels: 1,
|
||||
bitsPerSample: 16,
|
||||
samplesPerChannel: 8,
|
||||
formatCode: 1,
|
||||
});
|
||||
real[0] = 0;
|
||||
real[1] = 0;
|
||||
real[2] = 0;
|
||||
real[3] = 0;
|
||||
return real;
|
||||
}
|
||||
Reference in New Issue
Block a user