Skip to content

Test the audio graph by rendering it - #32

Merged
ibrahimweng merged 1 commit into
mainfrom
claude/audio-render-tests
Aug 30, 2026
Merged

Test the audio graph by rendering it#32
ibrahimweng merged 1 commit into
mainfrom
claude/audio-render-tests

Conversation

@ibrahimweng

Copy link
Copy Markdown
Owner

Replaces #31, which was branched off the wrong base and so conflicted. Same commit, rebased onto main.

The README said the graph could not be tested, and audio-buffer.ts gave the reason: Node has no Web Audio, so the encoders could be checked against a stub of an AudioBuffer while the thing that fills one could not.

That was true of the stub and not of the graph. The export path is an OfflineAudioContext rendering faster than real time, which is a plain function of its inputs in every sense that matters — the same project gives the same samples, with no page, no device and no clock. It only ever needed an implementation. node-web-audio-api is one, in Rust, and it carries every node this app builds with: convolver, biquad, compressor, shaper, panner and the rest. It goes in as a devDependency and stays out of the bundle; test/web-audio.ts puts its names on the global before the tests run, so nothing under audio/ imports from a test package. The package ships prebuilt N-API binaries inside the tarball for every platform including linux-x64-gnu, so there is no compile step on install and the same binary serves Node 20 and 22.

What is checked

Fifteen tests, measured off the rendered samples rather than reasoned about.

Where a sound lands

  • a cue starts on the sample its time works out to, and there is silence before it. This is what makes an exported file line up when it is dropped on a track at zero, so it is the deliverable rather than an internal detail
  • an end-anchored cue begins a length before its marker, which is the rule the whole anchor idea rests on

What silences it

  • a muted cue and a muted layer both go silent, and the mix is still rendered at the right length rather than skipped
  • solo on one layer silences the others, including layers nobody touched

How loud it is

  • gain on a cue and gain on a layer are both applied

The same sound twice

  • the same placed sound renders identically every time, sample for sample
  • two placements of the same sound differ. The seed comes from the cue's own id, and a seed that were not actually read would pass the first of these and fail the second

Stems

  • they add back up to the mix, sample for sample. This is the claim renderStems makes, and the first thing to break if anything in the chain is drawn fresh per render
  • they are all the same length, so they stay lined up

How long the file is

  • it runs past the video so a tail is not cut off, stops at the video when asked, and always starts at zero

Checking

Every test was checked by putting its fault back into the code and watching it fail: the end anchor ignored, solo ignored, the seed made random, every cue nudged eighty milliseconds late, and trimToDuration ignored. Six tests failed across those five faults, each one the test that names it.

Writing them also cost a minute a run until I noticed my own sum was calling getChannelData inside its per-sample loop. It hands back a copy here rather than a view onto the buffer, so that was a copy of the whole channel per sample. Hoisted, the file runs in six seconds.

214 unit tests (was 199) in 6.7s, 55 browser tests, build clean — all re-run on a clean clone from npm ci.

The README's "what is not tested yet" now names the live playback path — the clock, the buses and the scheduling that happen while you are listening rather than exporting.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ux1kydvUkLRoMbHp82ofDi


Generated by Claude Code

The README said the graph could not be tested, and gave the reason in
audio-buffer.ts: Node has no Web Audio, so the encoders could be checked
against a stub of an AudioBuffer while the thing that fills one could
not.

That was true of the stub and not of the graph. The export path is an
OfflineAudioContext rendering faster than real time, which is a plain
function of its inputs in every sense that matters — the same project
gives the same samples, with no page, no device and no clock. It only
ever needed an implementation. node-web-audio-api is one, in Rust, and
it carries every node this app builds with.

So fifteen tests that ask the questions that are actually about sound,
measured off the rendered samples rather than reasoned about:

- a cue starts on the sample its time works out to, and there is
  silence before it. This is what makes an exported file line up when
  it is dropped on a track at zero, so it is the deliverable rather
  than an internal detail
- an end-anchored cue begins a length before its marker, which is the
  rule the whole anchor idea rests on
- mute silences a cue and a layer; solo silences everything else
- gain on a cue and on a layer are both applied
- the same placed sound renders identically every time, and two
  placements of it differ — the seed comes from the cue's own id, and a
  seed that were not actually read would pass the first and fail the
  second
- the stems add back up to the mix, sample for sample, which is the
  claim renderStems makes and the one that fails first if anything in
  the chain is drawn fresh per render
- the file runs past the video so a tail is not cut off, stops at it
  when asked, and always starts at zero

Every one was checked by putting its fault back into the code and
watching it fail: the anchor ignored, solo ignored, the seed made
random, every cue nudged eighty milliseconds late, and trimToDuration
ignored. Six tests failed across those five, each the one that names
the fault.

Writing them also cost a minute a run until I noticed the sum was
calling getChannelData inside its own per-sample loop. It hands back a
copy here rather than a view, so that was a copy of the whole channel
per sample. Hoisted, the file runs in six seconds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ux1kydvUkLRoMbHp82ofDi
@vercel

vercel Bot commented Aug 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
beat-studio Ready Ready Preview Aug 30, 2026 11:47am

@ibrahimweng
ibrahimweng merged commit e2cadff into main Aug 30, 2026
5 checks passed
@ibrahimweng
ibrahimweng deleted the claude/audio-render-tests branch August 30, 2026 11:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant