Voice is POST /v1/audio/speech and word timings come from POST /v1/audio/transcriptions over the rendered audio. Music, sound effects, images, and video generate through /v1/audio/music, /v1/audio/foley, /v1/images/generations, and /v1/videos/generations. The shared catalog is a listing under media/<type>/ in our own object store, browsed through /v1/s3 — an empty prefix reports itself as empty rather than reading as a bad query. One client (scripts/lib/api.mjs) holds every route and the one credential: a Hanzo IAM token in $HANZO_API_KEY, read from Hanzo KMS. No credential file, no per-repo .env, no per-provider CLI to install or keep current. $HANZO_BASE_URL points the same code at a local Hanzo Engine. Word timings are passed through, never interpolated: a caption cut on invented boundaries reads as correct and drifts against the voice. When the service returns text without them, the word array is empty and the caller is told. Deletes the provider cascade this replaces, its model ladders, and the account linking in telemetry — the install id stands alone.
2.6 KiB
Sound effects (SFX)
Named sound effects, produced by the shared audio engine (scripts/audio.mjs ->
scripts/lib/sfx.mjs). Each cue is resolved in the same order, cheapest first:
- library — the 21 files bundled with this skill (
assets/sfx/+manifest.json). Offline, deterministic, free, and the same bytes every run. - catalog —
media/sfx/in the shared catalog, for the long tail the 21 miss. - foley —
POST /v1/audio/foleywhen neither has it.
A cue nothing can answer is skipped and recorded as an anomaly; SFX never blocks a render.
Cues — request to meta
Each line names the effects it wants: lines[].sfx: ["whoosh", "ui click"]. The
engine flattens these into cues, resolves them, dedupes identical (id, name)
pairs (the same effect named twice resolves once), and writes audio_meta.sfx[]:
{
"id": "3", // joins the cue to the caller's model (frame / scene / segment)
"name": "whoosh",
"file": "assets/sfx/whoosh.mp3", // relative to project root
"source": "library" | "catalog" | "foley",
"offset_s": 0, // delay from the line's start
"duration_s": 0.57,
"volume": 0.35 // SFX sit UNDER voice + BGM
}
The bundled library
21 curated files in assets/sfx/, indexed by manifest.json —
{ file, duration, description } per key (whoosh, pop, click, chime,
riser, impact-bass-1, glitch-1, typing, …). A cue name resolves by
manifest key, file basename, or slug, so whoosh, whoosh.mp3, and
"ui click" all land. Matched files are copied into the project's
assets/sfx/; duration_s comes from the manifest, so timing is known
offline — riser is 10.03s, so it fires at climax - 10.03s. The manifest's
description carries placement hints; read assets/sfx/manifest.json for the
full set.
The catalog and foley rungs
Same catalog as BGM, under media/sfx/: a key IS its description, and an empty
prefix says so rather than reading as a bad query. When the shelf cannot answer,
POST /v1/audio/foley makes the effect from the cue name.
Name effects concretely — glass shatter, not dramatic sound. A vague name is
a vague match on the shelf and a vague prompt at the foley route.
Rules
- Volume 0.35. SFX sit under narration and BGM, not against them.
- No match, no failure. A missing effect logs an anomaly and moves on.
- One asset per distinct name. Reuse across lines is deduped to a single resolve, many cues.
- Cheapest rung first. The bundled 21 cost nothing and never change; reach past them only for what they do not have.