script
The cast, and the bake that gives every written line a voice.
sound.script
The cast, and the bake that gives every written line a voice.
This game is voiced. Somebody has to own turning the written script into audio
exactly once and keeping it, and that is this module. It harvests
tongue.lines and stage.lines — the two slices that write words meant to be
said aloud — and bakes each line into a .soundClip under
/source/audio/voice/.
46 lines, 6 speakers, 197 seconds of speech, 1.96 MB.
The cast
| Speaker | Voice | Treatment |
|---|---|---|
| The Lingster | a low, measured British read | none — he is a person |
| Ithkeel | young, flat | +7% pitch, 7.5 Hz throat |
| Haalwen | clipped, impatient | +12% pitch, 9.0 Hz throat |
| Skerr | territorial | −11% pitch, 5.5 Hz throat |
| Vrannock | old, slow | −7% pitch, 4.5 Hz throat |
| Oonhraa | quiet, uncertain | +3% pitch, 6.5 Hz throat |
Measured on the shipped clips, the six read at fundamentals of 100, 209, 214, 189, 190 and 134 Hz — the Lingster an octave under every bird.
The hernsews are birds saying words, so their reads are shifted off the human centre by resampling (which moves pitch and rate together, the way a smaller throat actually does), pushed through a resonance a chest does not have, and given a flutter. Not a disguise — the phonetics still have to arrive, because the player is matching them against glyphs.
Three rules it is built around
Generate once. A line whose clip already exists is skipped. A hot reload, a
re-run, a second agent calling bake() — none of them re-buy a line.
Never invent a voice id. Every id in CAST is a documented premade voice,
and each was proved against this service before it was used: the same text
through two of them comes back as two different reads of different length. An
id the service does not know does not fail — it quietly returns the
default read — so a made-up id would ship six characters sharing one voice and
nothing would say so.
Keep the text. Nothing here deletes a line it could not voice. The written form is owned by the slice that wrote it and drawn by the screens slice regardless; a failed generation leaves the line printed and unvoiced, which is quiet, not lost.
The generated bank
Script.writeBank() writes sound.voicebank — a module of literal
asset.resolve calls, one per baked line. Gameplay reaches a clip through
that rather than by building the identity string at play time, because a
computed identity is a reference the world cannot record, and a reference the
world cannot record is a silent gap in the world that installs this content.
Re-run the bake and the bank regenerates. Do not hand-edit it.
Surface
harvest()— every line meant to be said aloud, deduped, core-firstbake { only, limit, force, priority }— generation is a network call per line, so passlimitand work in batchesstatus()— how many lines there are, how many carry a clip, which do notwriteBank()— regeneratesound.voicebankcastOf(who),clipName(id),identity(id),voiced(id)
Scoped to this part · feeds back into the world's score.