Skip to content

Commit 61e8463

Browse files
committed
test(engine): address review notes on volume-envelope baking
- WAV parser now scans chunks order-independently (no longer assumes `fmt ` precedes `data`); added a data-before-fmt test to pin it. - Assert the base-volume degradation backstop surfaces its reason instead of silently succeeding (directly tests layer 3). - Reword preview/render parity claim: the bake reproduces the curve at the probe's 60 Hz resolution, not the continuous GSAP curve.
1 parent 2ad5fc0 commit 61e8463

3 files changed

Lines changed: 36 additions & 2 deletions

File tree

packages/engine/src/services/audioMixer.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ describe("processCompositionAudio", () => {
213213
expect(result.success).toBe(true);
214214
expect(result.tracksProcessed).toBe(1);
215215
expect(runFfmpegMock).toHaveBeenCalledTimes(3);
216+
// Degradation is surfaced, not silent — the track rendered at base volume.
217+
expect(result.error).toMatch(/base volume/i);
216218

217219
// The fallback mix omits the automation expression (base volume only).
218220
const fallbackArgs = runFfmpegMock.mock.calls[2]?.[0];

packages/engine/src/services/audioVolumeEnvelope.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,32 @@ describe("applyVolumeEnvelopeToWav", () => {
119119
expect(applyVolumeEnvelopeToWav(path, keyframes, 0, 0)).toBe(true);
120120
});
121121

122+
it("parses chunks in any order (data before fmt)", () => {
123+
const path = join(tmp(), "order.wav");
124+
const frames = 4;
125+
const dataSize = frames * CHANNELS * 2;
126+
// Lay the data chunk before fmt to exercise order-independent scanning.
127+
const buffer = Buffer.alloc(12 + (8 + dataSize) + (8 + 16));
128+
buffer.write("RIFF", 0, "ascii");
129+
buffer.writeUInt32LE(buffer.length - 8, 4);
130+
buffer.write("WAVE", 8, "ascii");
131+
let o = 12;
132+
buffer.write("data", o, "ascii");
133+
buffer.writeUInt32LE(dataSize, o + 4);
134+
for (let i = 0; i < frames * CHANNELS; i += 1) buffer.writeInt16LE(10000, o + 8 + i * 2);
135+
o += 8 + dataSize;
136+
buffer.write("fmt ", o, "ascii");
137+
buffer.writeUInt32LE(16, o + 4);
138+
buffer.writeUInt16LE(1, o + 8);
139+
buffer.writeUInt16LE(CHANNELS, o + 10);
140+
buffer.writeUInt32LE(SAMPLE_RATE, o + 12);
141+
buffer.writeUInt16LE(16, o + 22);
142+
writeFileSync(path, buffer);
143+
144+
expect(applyVolumeEnvelopeToWav(path, [{ time: 0, volume: 0 }], 0, 0)).toBe(true);
145+
expect(readFileSync(path).readInt16LE(12 + 8)).toBe(0); // first sample muted
146+
});
147+
122148
it("rejects non-16-bit PCM so the caller can fall back", () => {
123149
const path = join(tmp(), "e.wav");
124150
// 24-bit PCM header (bitsPerSample = 24); body contents are irrelevant.

packages/engine/src/services/audioVolumeEnvelope.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,14 @@ interface WavLayout {
2828
dataSize: number;
2929
}
3030

31-
/** Locate the `fmt ` and `data` chunks and validate the format we know how to edit. */
31+
/**
32+
* Locate the `fmt ` and `data` chunks and validate the format we know how to edit.
33+
*
34+
* Scans every chunk rather than assuming an ordering: the loop always advances
35+
* past a chunk's body (using its declared size), so `data` may precede `fmt `
36+
* and trailing chunks (LIST/fact/etc.) are skipped harmlessly. Returns null on
37+
* anything unexpected so the caller falls back to the expression path.
38+
*/
3239
function parseWavLayout(buffer: Buffer): WavLayout | null {
3340
if (buffer.length < 12 || buffer.toString("ascii", 0, 4) !== "RIFF") return null;
3441
if (buffer.toString("ascii", 8, 12) !== "WAVE") return null;
@@ -50,7 +57,6 @@ function parseWavLayout(buffer: Buffer): WavLayout | null {
5057
};
5158
} else if (chunkId === "data") {
5259
data = { offset: body, size: Math.min(chunkSize, buffer.length - body) };
53-
break; // sample data follows; no need to scan further
5460
}
5561
// Chunks are word-aligned: an odd size carries a trailing pad byte.
5662
offset = body + chunkSize + (chunkSize % 2);

0 commit comments

Comments
 (0)