score
Everything Ashbloom's soundtrack does is a pure function of `t`, film-seconds, exactly like the picture. This module holds the numbers. `AshbloomSound.component` holds the entities and does the writing.
ashbloom.sound.score — the soundtrack, as data
Everything Ashbloom's soundtrack does is a pure function of t, film-seconds,
exactly like the picture. This module holds the numbers.
AshbloomSound.component holds the entities and does the writing.
local score = require("ashbloom.sound.score")
score.snapshot(115) -- the whole mix at film time 115, without entering play
score.observe() -- what the MIXER is doing right now, beside what was asked for
score.cuesheet() -- every clip against film time, for laying the track on a host
The score — five cues, two decks
Five generated orchestral cues, 40–48 s each. Never one long track: a generation request is abandoned at a fixed transport deadline, and a three-minute request is exactly the one that dies.
| cue | clip | film t | what it is |
|---|---|---|---|
| ash | ab_cue_ash | 0 – 40 | sub-bass drone, hollow metal, bowed cello. No pulse. |
| rain | ab_cue_rain | 34 – 82 | low strings gathering under a distant pulse |
| bloom | ab_cue_bloom | 76 – 122 | glass bells and celesta over warm strings |
| zenith | ab_cue_zenith | 116 – 144 | wordless choir, harp, the crest |
| dawn | ab_cue_dawn | 138 – 178 | solo piano resolving into stillness |
Cues alternate between two decks so consecutive cues crossfade over six seconds rather than butting together — one deck could only cut. Each cue's clip is at least as long as the window it owns, so nothing loops mid-film.
The world's own sound — nine sources
Four non-spatial beds (wind, ash, rain, the bloom pad) hold the floor of the mix at every camera height, so the soundtrack never goes dead on a 92 m wide. Five placed sources are spatial and are what a cut re-poses.
| source | clip | channel | driven by |
|---|---|---|---|
Snd_Wind | ab_amb_wind | ambient | thins under the rain, returns for the dawn |
Snd_Ash | ab_amb_ash | ambient | film.wetness — gone once the stone is wet |
Snd_Rain | ab_amb_rain | ambient | film.rainfall |
Snd_Bloom | ab_amb_bloom | ambient | film.glow — arrives and gutters with the light |
Snd_Drops | ab_sfx_drops | sfx | the onset only, t 33–52 |
Snd_Channel | ab_sfx_channel | sfx | ramps in t 46–62 and runs to the end |
Snd_Pool | ab_sfx_pool | sfx | ramps in t 66–80 as the basin fills |
Snd_Moths | ab_sfx_moths | sfx | in t 124–143, out t 162–178. Its entity travels. |
Snd_Rim | ab_amb_wind | ambient | a spatial wind at the rim, 260 m carry |
score.mothPos(t) flies the swarm's sound over the north rim, down into the
bowl, around the pool, and back out over the south rim as the film ends.
The mix moves on three things at once
- the curves —
film.rainfall,wetness,growth,glow - the cut list — every shot has a perspective in
score.PERSPECTIVE, 0 for a 92 m wide and 1 for a lens 6 cm off a petal. The mix pulls in on a macro (the world's own detail forward, the beds back) and opens out on a wide, eased over 0.35 s so a hard cut does not click the mixer. - the cues — the five-cue crossfade above
score.channels(t) folds all three into the three mixer levels. It never
returns a zero, so nothing in this film is ever explained by a channel being off.
Laying the soundtrack on a host
The engine plays the mix in real time while the film clock advances one
FILM_DT per rendered frame. At any render rate other than exactly 24 fps
the two run at different speeds, so a take whose picture is retimed to 24 fps
cannot carry the engine's own audio track in sync.
score.cuesheet() is the answer: every clip, its film-time in and out, and its
gain sampled on a fixed grid with the channel level already folded in — enough
to build the finished soundtrack against film time, independent of how fast the
engine rendered.
Scoped to this part · feeds back into the world's score.