/** * 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; }