choir
The mix, the score, the vault's breath, the Engine's own bronze resonance, the one-shot library the other slices fire, and every word anybody says.
choir — the sound of The Acceding
The mix, the score, the vault's breath, the Engine's own bronze resonance, the one-shot library the other slices fire, and every word anybody says.
What another slice calls
local Choir = require("choir")
Choir.line(id) -- the WRITTEN form: { id, text, speaker, voice, clip }
Choir.speak(id) -- say it aloud on the narrative voice
Choir.speakNow(id) -- say it aloud on the Litanist's own voice, immediately
Choir.sfx(name) -- a one-shot from the library
Choir.ui(name) -- the interface's own one-shots, on a quieter channel
Choir.music(phase) -- crossfade the score
Choir.demandLine(k, a) -- the line id for a gate's demand
Choir.glyphLine(id) -- the line id for a spoken glyph
Choir.report() -- what sounds, and what does not
line always answers, whether or not the audio arrived: the written form is
the source of truth and the screen shows it either way. speak returns false
when a line has no clip, and records the gap in Choir.missing rather than
losing the line.
What it owns
Nine emitters under the Choir anchor — the bed, the Engine's resonance,
the standing water, two music decks, the narrative voice, the Litanist's own
voice, and the two one-shot sources. Assets are snd_* under /source/audio.
Sixty-five clips: forty-five spoken lines, eight CPU-synthesised sounds, eight generated one-shots and four pieces of score.
Hearing it
choir:say:<lineId>, choir:play:<clipName>, choir:install, choir:generate
and choir:report are callback
ids the driver forwards here, which is how a sound is auditioned from outside
the game: a module required from a loose execute is a second copy with no
emitters on it, and a call made there plays nothing.
Each writes its answer to /source/audio/audition.txt.
Interface
What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.
conforms to
zero/source-extract/v2SLICE: CHOIR — the sound of The Acceding, and every word anybody says. Owned by the CHOIR slice. It owns the mix, the score, the vault's breath, the Engine's own bronze resonance, the one-shot library the other slices fire, and the SPOKEN SCRIPT: this game is VOICED, and the voice is here. The interface the rest of the game codes against — these are the calls other slices make, and they answer whether or not any audio ever arrived: M.line(id) -> { id, text, speaker, voice, clip } the WRITTEN form M.speak(id) -> boolean say it aloud (and emit "line" so LITANY prints it) M.sfx(id) -> boolean fire a one-shot from the library M.music(key) -> boolean crossfade the score to a named track M.demandLine(kind, arg) -> string? the line id for a gate's demand M.glyphLine(glyphId) -> string the line id for a spoken glyph M.report() -> a full account of what sounds and what does not KEEP THE TEXT. Every spoken line ships with its written form as well. If a generation never arrived the game still SAYS the line, this slice records the gap in `M.missing`, and nothing is deleted to hide it.
demandLine(kind: string, arg: string?) → string
The line id for a gate's demand. RITE and LITANY call this rather than knowing the naming scheme; a kind with no recorded line answers nil and the caller falls back to the written liturgy alone.
| arg | type | description |
|---|---|---|
| kind | string | |
| arg | string? |
glyphLine(glyphId: string) → string
| arg | type | description |
|---|---|---|
| glyphId | string |
durationOf(name: string) → number
A clip's length, read from its own payload the first time it is wanted and remembered after. `audio.info` answers without decoding, so this is cheap.
| arg | type | description |
|---|---|---|
| name | string |
emitter(key: string) → any
| arg | type | description |
|---|---|---|
| key | string |
audioOf(key: string) → any
| arg | type | description |
|---|---|---|
| key | string |
seedNoise(s: number) → void
| arg | type | description |
|---|---|---|
| s | number |
noise( ) → number
makeBandpass(freq: number, q: number) → void
A two-pole bandpass, the whole of the metal in these sounds: struck brass is a handful of narrow resonances over a noise burst.
| arg | type | description |
|---|---|---|
| freq | number | |
| q | number |
makeLowpass(freq: number) → void
| arg | type | description |
|---|---|---|
| freq | number |
newBuf(seconds: number) → void
| arg | type | description |
|---|---|---|
| seconds | number |
addAt(buf: buffer, frames: number, i: number, v: number) → void
| arg | type | description |
|---|---|---|
| buf | buffer | |
| frames | number | |
| i | number | |
| v | number |
partial(buf: buffer, frames: number, cycles: number, amp: number, phase: number) → void
A sine partial that completes a WHOLE NUMBER OF CYCLES in the clip, so the waveform joins end to end and a looped bed has no seam to click at.
| arg | type | description |
|---|---|---|
| buf | buffer | |
| frames | number | |
| cycles | number | |
| amp | number | |
| phase | number |
normalise(buf: buffer, frames: number, peak: number) → void
| arg | type | description |
|---|---|---|
| buf | buffer | |
| frames | number | |
| peak | number |
bake(name: string, buf: buffer, frames: number, codec: string) → void
| arg | type | description |
|---|---|---|
| name | string | |
| buf | buffer | |
| frames | number | |
| codec | string |
snd_place( ) → void
A glyph slab set down: wet stone under a small brass edge.
snd_unplace( ) → void
Taking one back out: lower, softer, and it does not ring.
snd_hover( ) → void
The pointer crossing a slab. Nearly nothing — it should register below attention, which is the only way a hover sound is bearable.
snd_press( ) → void
SPEAK. A heavy mechanical latch throwing — the one button that commits.
snd_touch( ) → void
A touch meter filling one notch: brittle porcelain, high and dry.
snd_swell( ) → void
The litany leaving your mouth: a short swell up into the verdict.
snd_bed( ) → void
THE VAULT'S BREATH. Eight seconds, seamless end to end: every partial completes a whole number of cycles in the clip, so the last sample joins the first. This is the floor the whole mix stands on and the reason the game is never silent even if every generation fails.
snd_resonance( ) → void
THE ENGINE'S RESONANCE. Six seconds, seamless, and deliberately INHARMONIC — bronze, not a string. It is played back with its pitch and its level driven by the Engine's patience, so the machine audibly tightens as the meter fills.
indexClips( ) → void
LOOKING UP WHAT EXISTS Clips are found by NAME off `asset.list`, never by a computed identity: `asset.resolve` takes a static literal and a concatenated one raises `asset-ref-not-literal`, which refuses the whole world at publish time.
bakeMissing( ) → number
Everything the CPU can make, made — but only what is not already there. A bake is cheap; doing all nine on every hot reload, six agents over, is not.
loopify(pcmStr: string, sr: number, ch: number, fadeSec: number) → void
Crossfade a clip's tail over its own head so it cycles with no seam. The loop-point fields are stored and read by no playback surface (#8874), so a loop that must not click has to be seamless IN THE SAMPLES.
| arg | type | description |
|---|---|---|
| pcmStr | string | |
| sr | number | |
| ch | number | |
| fadeSec | number |
fileClip(name: string, srcPath: string, loop: boolean?) → void
File a finished generation under OUR name. A generated asset is named from a slug of its own prompt, so two lines that read alike would collide and nothing could reference either one by a name it knew in advance.
| arg | type | description |
|---|---|---|
| name | string | |
| srcPath | string | |
| loop | boolean? |
awaitJob(id: string, budget: number) → void
| arg | type | description |
|---|---|---|
| id | string | |
| budget | number |
manifest( ) → void
Everything the library wants, and where each piece comes from.
generate( ) → void
Buy every clip that is not already on disk. Returns immediately; watch `M.progress`. Nothing here is bought twice.
pruneHandlers( ) → void
INSTALL
listen(event: string, fn: ?) → void
A bus handler that survives a hot reload cleanly. It is a callable TABLE carrying a mark, because `install` runs again every time any of the six agents saves and a slice that only ever subscribes ends up handling each event five times over — and after a reload the module's own upvalues are fresh, so the marks on the bus are the only record of what is still on it.
| arg | type | description |
|---|---|---|
| event | string | |
| fn | ? |
install(ctx: ?) → void
| arg | type | description |
|---|---|---|
| ctx | ? |
make(key: string, fields: ?) → void
| arg | type | description |
|---|---|---|
| key | string | |
| fields | ? |
onCallback(id: string, _data: any) → void
| arg | type | description |
|---|---|---|
| id | string | |
| _data | any |
pick(ids: { string }) → string
PLAYING IT
| arg | type | description |
|---|---|---|
| ids | { string } |
resolve(id: string, redraw: boolean?) → string
| arg | type | description |
|---|---|---|
| id | string | |
| redraw | boolean? |
line(id: string) → void
| arg | type | description |
|---|---|---|
| id | string |
say(id: string, emitterKey: string) → boolean
Say it aloud on the emitter that suits it. Returns false when no audio for the line ever arrived — the caller still shows the text, and the gap is recorded rather than hidden.
| arg | type | description |
|---|---|---|
| id | string | |
| emitterKey | string |
speak(id: string) → boolean
The narrative voice, queued: two lines never talk over each other.
| arg | type | description |
|---|---|---|
| id | string |
speakNow(id: string) → boolean
Your own word, immediate: it answers a click.
| arg | type | description |
|---|---|---|
| id | string |
enqueue(id: string, delay: number?) → void
Put a line in the queue, `delay` seconds after the one before it clears.
| arg | type | description |
|---|---|---|
| id | string | |
| delay | number? |
playOpening( ) → void
stopVoice( ) → void
sfx(id: string) → boolean
A one-shot from the library. `id` is a name in M.SFX_IDS.
| arg | type | description |
|---|---|---|
| id | string |
ui(id: string) → boolean
The interface's own one-shots, on their own quieter channel.
| arg | type | description |
|---|---|---|
| id | string |
music(key: string) → boolean
Crossfade the score. `key` is a phase name, or a track name directly.
| arg | type | description |
|---|---|---|
| key | string |
healAmbience( ) → void
PER FRAME — the queue, the crossfade, and the Engine tightening. Bind any continuous layer whose clip was not indexed when it was spawned, and start it. It runs twice a second rather than every frame.
tick(dt: number) → void
| arg | type | description |
|---|---|---|
| dt | number |
emitterCount( ) → number
report( ) → void
WHAT SOUNDS AND WHAT DOES NOT The honest account. Every line in the script is either playable or named here; nothing is quietly dropped.
Sub-parts
Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.
Showing up to 3 levels deep. Open a part to see what it holds further down.
Problems
Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.
agent_score is exposed.+ quality × 0.35
+ performance × 0.25
± compat factor
Usability ratings
Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".
Scoped to this part · feeds back into the world's score.