Audio and captions
The Go PCM mixer, automatic sound design derived from a scene's own cuts and stings, the reusable SFX library, and burned-in captions.
Audio is a separate track. A Go PCM mixer builds the full bed in memory (music, sound effects, voiceover), writes a WAV, and ffmpeg muxes it onto the video. Because it never touches renderFrame, adding audio leaves every frame byte-identical.
The audio object
{ "audio": { "silent": true } }The audio object takes music, sting, vo, musicGain, and silent. While you author, silent: true is common: it skips music and sound entirely. The mixer runs at 44100 Hz, loops music under the video, ducks it under a voiceover, and applies a soft limiter.
Automatic sound design
The most useful mode derives sound cues from the scene's own timing:
{ "audio": { "auto": true } }With auto, the scene reads its own layers and stings and places effects deterministically:
- a
whooshon every cut, at the cutting layer'sstart - a
revealon every sting, at the sting'st
Cue times are a pure function of the JSON, so audio stays reproducible and frames stay snap-identical. You get a scored video without hand-placing a single sound.
The SFX library
Every sound is synthesized from parameters, not downloaded. The cue library is
Cuelume v0.1.2 (MIT), ported verbatim: 14 interaction cues, each a
small spec (noise or a tone, a filter, an envelope) that renderCue walks sample by sample at 44.1kHz
with a seeded PRNG for the noise. Same spec, same seed, same bytes, on any machine, with no licence
obligation and no network.
make audio # bake every cue into assets/sfx/ (additive; --force to re-bake)
make sfx-check # is each cue the SHAPE its role claims?The cues: chime, sparkle, droplet, bloom, whisper, tick, press, release, toggle,
success, error, page, loading, ready. whoosh and reveal are the engine's own role names
(they alias whisper and chime), which is what the automatic mode reaches for. Nothing is committed;
assets/sfx/ is gitignored and self-heals from make audio.
Do not hand-edit the specs in core/audio-kit.mjs. They were hand-ported once and drifted from the
real library on 7 of 14 cues, which is a difference you hear and cannot debug from the JSON.
Typing
A typing layer emits one press per revealed character automatically. core/layers/text.js reveals
character i at start + (i+1)/cps, so the cue is that same expression — change the copy or the
speed and the clicks follow. Variation comes from dynamics (a deterministic ±12% gain per index),
because a real keyboard varies in force, not in identity. keyClicks: false silences it; keyGain
sets the level.
make sfx-check exists because a downloaded file named click was once 19.6 seconds long and the
typing stacked it into a drone. It decodes each WAV and measures how long the signal is actually
audible (not how long the file is), then holds it to a cap derived from the cue's role.
Captions
Auto-time a script into burned-in subtitles:
make captions D=formats/scene/my-video.json TEXT="your script here"Each caption's duration is proportional to its word count, computed deterministically, and written into the scene's captions array. Pair it with "captionMode": "pop" for the muted-social look, where words punch in one at a time. The three modes are sentence, word, and pop.