SFX Config Reference — sfx_<slug>_<TAG>.json
The SFX config file maps every sound direction in a parsed episode script to its audio
source and playback parameters. One file covers an entire episode; it is read by
xil produce, xil sfx, xil mix, and xil daw.
Filename pattern: sfx_<slug>_<TAG>.json
(e.g. sfx_the413_S03E02.json, sfx_nightowls_V01C03.json)
Top-level Structure
{
"show": "The 4 1 3",
"season": 3,
"episode": 2,
"defaults": { ... },
"effects": { ... }
}
| Field | Type | Description |
|---|---|---|
show | string | Show name (informational; slug is derived from project.json) |
season | int | Season number |
episode | int | Episode number |
defaults | object | Fallback values applied to all effects unless overridden per-entry |
effects | object | Map of direction text → effect config |
defaults Block
All keys are optional. Per-entry values override these.
"defaults": {
"prompt_influence": 0.3,
"volume_percentage": 20,
"ramp_in_seconds": 1.0,
"ramp_out_seconds": 1.0,
"ambience_volume_percentage": 30,
"ambience_ramp_in_seconds": 1.0,
"ambience_ramp_out_seconds": 1.0
}
| Key | Type | Description |
|---|---|---|
prompt_influence | float 0–1 | How closely the API follows the prompt vs. free creativity. Default 0.3. |
volume_percentage | float | Playback volume for SFX and MUSIC entries. 100 = unity gain. |
ramp_in_seconds | float | Fade-in duration in seconds for SFX and MUSIC entries. |
ramp_out_seconds | float | Fade-out duration in seconds for SFX and MUSIC entries. |
ambience_volume_percentage | float | Volume for AMBIENCE entries specifically. |
ambience_ramp_in_seconds | float | Fade-in for AMBIENCE entries. |
ambience_ramp_out_seconds | float | Fade-out for AMBIENCE entries. |
Category-specific keys (ambience_*) take precedence over the generic ones for
AMBIENCE entries. If no category-specific key exists, the generic key is the fallback.
effects Keys
Each key must exactly match the text field of a parsed direction entry. The parser
emits direction text verbatim from the script, so capitalisation and punctuation matter.
"AMBIENCE: RADIO BOOTH — SOFT EQUIPMENT HUM": { ... }
Effect Entry Fields
type
Controls how audio is produced. Most entries don’t need this field — the default is
inferred from the key prefix (AMBIENCE:, MUSIC:, SFX:, BEAT, etc.) by the
direction classifier.
| Value | Behaviour |
|---|---|
"sfx" | (default) Generate via ElevenLabs Sound Effects API or copy from source. |
"silence" | Generate local silent audio. No API call. |
source
Path to an existing audio file (relative to the project root or absolute).
"SFX: RADIO STATIC — BRIEF TUNING": {
"source": "SFX/sfx_radio-static-tuning-transition.mp3",
"duration_seconds": 1.5
}
- Resolved with
os.path.realpath()before use (symlinks are followed; path traversal is blocked). - If present and the file exists → the file is copied to the shared SFX library. No API credit is spent.
- If present but the file is missing and a
promptis also set → falls back to API generation with a warning. - If present but the file is missing and no
promptis set → raisesFileNotFoundError.
prompt
Text sent to the ElevenLabs Sound Effects API when no usable source file is found.
"MUSIC: STING OUT": {
"prompt": "MUSIC: STING OUT",
"duration_seconds": 15.0
}
- If
sourceis absent → API is always called using this prompt. prompt_influence(from this entry ordefaults) controls creativity vs. adherence.
duration_seconds
Meaning depends on context:
| Context | Meaning |
|---|---|
Entry has source, no play_duration | Clip the source file to this many seconds at mix time |
Entry has no source (API generation) | Requested generation length sent to ElevenLabs API |
type: "silence" | Duration of the generated silence |
type: "silence", value 0.0 | Stop-marker — inserts a boundary in the timeline with no audio |
"AMBIENCE: RADIO BOOTH — SOFT EQUIPMENT HUM": {
"source": "SFX/ambience_radio-booth.mp3",
"duration_seconds": 30.0,
"loop": true
}
Watch this one on
sourcecues.xil parsewrites a default of5.0into every skeleton entry, so a long music bed dropped into a hinted cue is clipped to five seconds unless you change it. To see every cue this is affecting across the workspace, runxil sfx-impact— it grades each one by how much audio is being cut and names the edit that would restore it, without modifying anything.To un-clip a cue, set
play_duration: 100rather thanduration_seconds: 0. Both play the whole file, butduration_secondsis not inSFX_EDIT_FIELDS, so it is not replayed by the timeline edit journal — aduration_secondsedit reverts to5.0the next time the config is rebuilt from a skeleton, silently re-clipping the cue.play_durationsurvives regeneration.Better still, fix it in the script: a
play_duration_pctpipe-hint on the direction setsplay_durationand removesduration_secondsfrom source-backed cues, so the contradiction never gets written in the first place.
play_duration
Percentage of the source file to use (0–100). Applied at stem copy time (XILP002), so the resulting stem file is already trimmed — all downstream tools see the correct duration.
"OUTRO MUSIC": {
"source": "SFX/The Porch Light.mp3",
"volume_percentage": 40,
"play_duration": 25
}
25= use the first 25% of “The Porch Light.mp3”.- Only valid when
sourceis set. - Mutually exclusive with
duration_secondsfor music entries; use one or the other. - Ignored on looped layers (AMBIENCE, VINTAGE FILTER) — they tile to fill the scene.
- Best for music where the natural length is unknown and you want a proportional excerpt rather than a hard-coded second count.
It can also come straight from the script. A play_duration_pct pipe-hint on the
direction sets this field, and the script wins over whatever the config holds:
[INTRO MUSIC | The Porch Light.mp3 | play_duration_pct=35%]
On a cue that has a source, the hint also removes duration_seconds, since the two
are mutually exclusive and the skeleton’s default 5.0 would otherwise sit in the config
contradicting the hint. On a generated cue (no source) duration_seconds is kept — there
it is the requested generation length sent to the SFX API, not a trim.
A hint on an AMBIENCE or VINTAGE FILTER cue is dropped with a warning at parse time; a
play_volume_pct on the same direction still applies.
loop
Boolean. Controls whether the audio tiles to fill the scene duration.
| Value | Behaviour |
|---|---|
true (default for AMBIENCE) | Tile the file repeatedly to cover the full scene |
false | Play the file once; silence fills the remainder of the scene |
"AMBIENCE: OUTDOOR SPRING": {
"source": "SFX/AMBPark-Outdoor_spring_ambie-Elevenlabs.mp3",
"duration_seconds": 30.0,
"loop": true
}
volume_percentage
Per-entry volume override. Overrides the defaults category key for this entry only.
"INTRO MUSIC": {
"source": "SFX/The Porch Light.mp3",
"volume_percentage": 40,
"play_duration": 30
}
100= unity gain (no change).40= 40% of full volume (roughly −8 dB).- Applies to SFX, MUSIC, and AMBIENCE entries.
It can also come straight from the script. A play_volume_pct pipe-hint on the
direction sets this field, and the script wins over whatever the config holds:
[OUTRO MUSIC | sundy3M4_v3.mp3 | play_volume_pct=20%]
Reparsing (xil parse) or xil sfx-hydrate applies it; xil regen writes it back
into the markdown, so the annotation survives a full round-trip. Hand-editing this
field is still fine — just remember a later re-parse will restore the script’s value
if the direction still carries the hint.
The edit journal beats the script hint on a full re-parse.
xil parsewrites the skeleton from the script, then replayssfx_<TAG>.json’s edit journal on top. So avolume_percentageorplay_durationyou set in the xil-gui Timeline wins over a fresh hint in the script.xil sfx-hydratehas the opposite precedence — there the script always wins. If a hint appears not to take, checksfx_<TAG>_edits.jsonl(seexil sfx-restore).
prompt_influence
Per-entry override for ElevenLabs API creativity control (0.0–1.0).
0.0= maximum creative freedom; ignores prompt text almost entirely.1.0= strict adherence to the prompt.- Falls back to
defaults.prompt_influencewhen absent.
Reserved Keys
Two effect keys have special pipeline behaviour beyond normal playback:
| Key | Behaviour |
|---|---|
"INTRO MUSIC" | XILP002 reads source and copies it to n001_preamble_sfx.mp3. The play_duration trim is applied at copy time. No API call. |
"OUTRO MUSIC" | Same as INTRO MUSIC but written as the postamble SFX stem. |
Stop Markers
Use type: "silence" with duration_seconds: 0.0 to insert an ambience boundary
without generating audio. The mixer treats this as an instruction to stop the current
ambience loop at that timeline position.
"AMBIENCE: STOP": {
"type": "silence",
"duration_seconds": 0.0
},
"MUSIC: THEME FADES OUT": {
"type": "silence",
"duration_seconds": 0.0
}
The source field is ignored when duration_seconds is 0.0.
How Fields Combine — Decision Tree
effect entry
│
├── source present and file exists?
│ ├── play_duration set? → trim to % of file at copy time (stem file is pre-trimmed)
│ └── duration_seconds set? → clip to N seconds at mix time
│
├── source absent (or file missing) and prompt set?
│ └── call ElevenLabs API; duration_seconds = requested length
│
└── type = "silence"?
└── duration_seconds = 0.0 → stop marker (no audio)
duration_seconds > 0 → generate local silence
Field Reference Summary
| Field | Type | Default | When used |
|---|---|---|---|
type | "sfx" | "silence" | "sfx" | All entries |
source | string (path) | — | Source-based entries |
prompt | string | — | API-generated entries |
duration_seconds | float | — | Clip/generate/silence length |
play_duration | float (0–100) | — | Source music % trim (script hint: play_duration_pct) |
loop | bool | true for AMBIENCE | Tile vs. one-shot |
volume_percentage | float | from defaults | Per-entry volume (script hint: play_volume_pct) |
prompt_influence | float 0–1 | from defaults | API creativity control |
Complete Entry Examples
Silence Beat
"BEAT": {
"type": "silence",
"duration_seconds": 1.0
}
Looping Ambience (source)
"AMBIENCE: RADIO BOOTH — SOFT EQUIPMENT HUM, SLIGHT STATIC, INTIMATE": {
"source": "SFX/ambience_radio-booth-soft-equipment-hum-slight-static-intimate.mp3",
"duration_seconds": 30.0,
"loop": true
}
One-shot SFX (source, clipped)
"SFX: RADIO STATIC — BRIEF TUNING": {
"source": "SFX/sfx_radio-static-tuning-transition.mp3",
"duration_seconds": 1.5
}
Music Sting (source, clipped)
"MUSIC: THEME — BRIEF STING, RETURNS SOFT": {
"source": "SFX/MUSCStngr-Short_medieval_melod-Elevenlabs.mp3",
"duration_seconds": 5.0
}
Music with Percentage Trim (source, play_duration)
"OUTRO MUSIC": {
"source": "SFX/The Porch Light.mp3",
"volume_percentage": 40,
"play_duration": 25
}
API-Generated Music
"MUSIC: STING OUT": {
"prompt": "MUSIC: STING OUT",
"duration_seconds": 15.0
}
Ambience Stop Marker
"MUSIC: UNDERSCORE FADES OUT": {
"type": "silence",
"duration_seconds": 0.0
}
See Also
- SFX Reuse Guide — how to minimise ElevenLabs credit spend
- DIRECTION_TYPES — how the parser classifies direction text
xil sfx --dry-run— preview EXISTS / CACHED / NEW status before generatingxil produce --dry-run— full voice + SFX cost estimate