heygen-com/hyperframes · error · AudioFxRenderError

Not a WAV file: ${path}

Error message

Not a WAV file: ${path}

What it means

readWav() reads a file synchronously and rejects it as `Not a WAV file` if it is shorter than 44 bytes (too small to contain even the canonical header) or its first four bytes are not the ASCII `RIFF` magic. This is the minimal sanity gate before walking WAV chunks; only files the engine's own mixer/extract pipeline emits are supported.

Source

Thrown at packages/engine/src/services/audioFxRender.ts:72

    const size = buf.readUInt32LE(offset + 4);
    if (id === "fmt ") {
      head.format = buf.readUInt16LE(offset + 8);
      head.channels = buf.readUInt16LE(offset + 10);
      head.sampleRate = buf.readUInt32LE(offset + 12);
      head.bits = buf.readUInt16LE(offset + 22);
    } else if (id === "data") {
      data = buf.subarray(offset + 8, Math.min(buf.length, offset + 8 + size));
      break;
    }
    offset += 8 + size + (size % 2);
  }
  return { ...head, data };
}

export function readWav(path: string): WavData {
  const buf = readFileSync(path);
  if (buf.length < 44 || buf.toString("ascii", 0, 4) !== "RIFF") {
    throw new AudioFxRenderError(`Not a WAV file: ${path}`);
  }
  const { format, channels, sampleRate, bits, data } = readWavChunks(buf);
  if (!data) throw new AudioFxRenderError(`WAV has no data chunk: ${path}`);
  return { samples: decodeSamples(data, format, bits, path), sampleRate, channels };
}

/** Interleaved samples as floats, for the two formats the mixer emits upstream. */
function decodeSamples(data: Buffer, format: number, bits: number, path: string): Float32Array {
  if (format === 3 && bits === 32) {
    const n = Math.floor(data.length / 4);
    // A Float32Array view demands a 4-aligned offset, and chunk layouts that put
    // `data` on an odd boundary (an 18-byte fmt plus a fact chunk, which
    // ffmpeg's pcm_f32le writes) would otherwise throw RangeError. Copy then.
    if (data.byteOffset % 4 === 0) return new Float32Array(data.buffer, data.byteOffset, n);
    const copied = new Float32Array(n);
    for (let i = 0; i < n; i++) copied[i] = data.readFloatLE(i * 4);
    return copied;
  }

View on GitHub (pinned to c2996c8626)

Solutions

  1. Confirm the file is actually WAV PCM/float: `file <path>` or inspect the first bytes (`xxd <path> | head -1`) — expect `RIFF`.
  2. If it is a different format, convert upstream: `ffmpeg -i in.mp3 -c:a pcm_s16le out.wav` (16-bit PCM) or `pcm_f32le` for float.
  3. If the file is empty/truncated, re-run the upstream render/mix step that produced it.
  4. Ensure readWav is only called on files produced by the engine's own mixer/extract pipeline.

Example fix

# before: source is mp3 mislabeled
$ file track.wav  # -> MPEG ADTS

# after: transcode to the WAV the mixer emits
$ ffmpeg -i track.wav -c:a pcm_s16le -ar 48000 -ac 2 track.pcm.wav
Defensive patterns

Strategy: type-guard

Validate before calling

import { readFileSync, statSync } from "node:fs";

function isWavFile(path: string): boolean {
  let buf;
  try { buf = readFileSync(path); } catch { return false; }
  return buf.length >= 44 && buf.toString("ascii", 0, 4) === "RIFF";
}

if (!isWavFile(path)) throw new Error(`not a WAV file: ${path}`);

Type guard

import { readFileSync } from "node:fs";

function isPcmWav(path: string): boolean {
  try {
    const b = readFileSync(path, { start: 0, end: 44 });
    return b.toString("ascii", 0, 4) === "RIFF" && b.toString("ascii", 8, 12) === "WAVE";
  } catch { return false; }
}

Try / catch

import { AudioFxRenderError } from "@hyperframes/engine/services/audioFxRender";

try {
  return readWav(path);
} catch (e) {
  if (e instanceof AudioFxRenderError && /Not a WAV file/.test(e.message)) {
    throw new Error(`upstream produced a non-WAV audio asset at ${path}; check the mixer/extract step`, { cause: e });
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling readWav(path) on a non-WAV file (MP3, AAC, FLAC, OGG), a truncated/empty file, a RIFX (big-endian) WAV, or a file written with a non-RIFF container. The audio FX render step hands it the WAV produced upstream by the mixer; pointing it at anything else triggers this.

Common situations: An upstream audio step wrote MP3/AAC instead of WAV (codec mismatch); a render was interrupted leaving a 0-byte WAV; a user dropped an .mp3 renamed to .wav; the source asset is big-endian RIFX from an uncommon encoder.

Related errors


AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12). Data as JSON: /api/errors/145f9e8753969b8a. Report an issue: GitHub.