bake
local B = require("game.sound.bake") B.bakeBed("night") ; B.bakeBed("firstlight") ; B.bakeBed("day") ; B.bakeBed("dusk") B.bakeAir() -- valley, threat, ferment, beam B.bakeShots("ui") ; B.bakeShots("act") B.bakeShots("birds") ; B.bakeShots("…
bake — renders the whole soundtrack into .soundClip assets
local B = require("game.sound.bake")
B.bakeBed("night") ; B.bakeBed("firstlight") ; B.bakeBed("day") ; B.bakeBed("dusk")
B.bakeAir() -- valley, threat, ferment, beam
B.bakeShots("ui") ; B.bakeShots("act")
B.bakeShots("birds") ; B.bakeShots("stings")
B.report() -- audio.info on every clip
Run it from an execute, never from a component or a scene entrypoint.
An asset.create made from either is filed in the ephemeral
/runtime/assets/ store instead of /zero/source, and the clip would not
survive a reload. Everything is overwrite = true, so a re-bake keeps each
clip's guid and only its bytes change. The noise is a seeded LCG, so a
re-bake is byte-identical.
Why the beds are PCM and the one-shots are Opus
An Opus payload loses its first few milliseconds on decode, which a looping
bed hears as a click at every wrap. The beds and the air are therefore
codec = "pcm" — bigger, and they keep their head. A one-shot never wraps,
so it stays Opus and stays small. Both settings are passed in asset.create's
settings, at creation: creating first and calling setSettings afterwards
re-encodes the PCM through Opus and quietly undoes the choice.
The beds and the air run at 12 kHz, which is six kilohertz of bandwidth — all a drone, a wind and a ferment have in them — and keeps each uncompressed bed well under a megabyte for a browser to fetch. The one-shots run at 22.05 kHz where the top end matters.
The recipes
The four beds are one key, D, read four ways: night is the minor of it with nothing above the third octave; first light is the major, and everything the night was missing arrives at once; day opens the third out into a fifth and lets air through; dusk falls back to the minor seventh and loses the top. Each is twelve seconds long and locked to that length, so it loops.
report() is the check that matters: it reads every clip's own header back
off disk, so a clip that is missing, zero-length, or came out stereo shows up
as a row here rather than as silence in the game.
Scoped to this part · feeds back into the world's score.