Skip to content

Commands

m4bwav edited this page Nov 7, 2026 · 3 revisions

sfx.py has 30 commands. Most print one JSON object. check, status and doc print text unless asked otherwise.

Exit codes

Code Means
0 The command ran. This includes flags and a BLOCKED answer. Read flags or answer; do not rely on the exit code to gate a build
1 "ok": false: a missing file, a refused edit, an invalid list, a failed name --check
2 Bad command line, from argparse: usage on stderr

All output in the transcripts merges stdout and stderr.

Plan

Command Flags
init LIST --project, --use {game,film,video,app,library,other}, --commercial {yes,no} (all required), --frame-rate, --style, --force
add LIST ID --event, --kind {one-shot,loop,ambience,sequence,stinger,ui}, --length (all required), --variations (1), --route (undecided), --format (wav), --category, --brief, --layers, --loudness, --sync-point, --at, --trigger, --priority, --max-instances, --spatial {2D,3D}, --gain (dB applied by place), --sync-class {tight,loose,bed}, --role {hard,background,foley,design,ui}
validate LIST, status LIST none
plan --axis name=count (repeat), --variations (3), --budget

plan compares three shapes before you commit to files:

$ python sfx.py plan --axis surface=6 --axis force=3 --variations 4 --budget 100
{"ok": true, "axes": {"surface": 6, "force": 3}, "variations_each": 4, "full_product_files": 72, "shared_layers_files": 36, "shared_layers_means": "one layer set per axis value, combined at play time", "parameter_files": 24, "parameter_means": "record only surface; drive the other axes with pitch, filter or volume", "ask": ["Would a player notice if this axis were removed?", "Is it heard often enough to pay for itself?", "Can a parameter do it instead of a file?"]}
$ echo $?
0

Make

Command Flags
synth RECIPE --out (required: a .wav file for one take, or a folder), --name, --takes (1), --seed (1), --sr (44100), --param name=value (repeat)
synth list none; prints every recipe, its parameters and defaults, and the materials as JSON
gen PROMPT --seconds (3), --takes (4), --seed (1), --out (takes), --name (take), --model, --host, --timeout (900), --workflow-out
library FILES --licence (required), --type {library,recording,generated,other}, --library, --title, --author, --url, --ai {yes,no}
register LIST ID FILES none

gen with no server fails with exit 1 and, given --workflow-out, writes the workflow. A run against a real server is not shown here: these transcripts point SFX_COMFY_URLS at a dead port.

$ python sfx.py gen "heavy wooden crate dropped on concrete, dead room" --seconds 2 --takes 2 --out takes --name crate --workflow-out crate_workflow.json
{"ok": false, "error": "no reachable ComfyUI with an audio model", "fallback": "use `sfx.py synth` or a CC0 library, or run the workflow on any ComfyUI", "workflow": "crate_workflow.json"}
$ echo $?
1

The workflow asks for stable_audio_3_small_sfx.safetensors and adds the prompt wrapper TrackType: SFX, ... Length: 2 seconds.

Check

Command Flags
check [FILES] --list, --id, --seconds, --loop, --waive FLAGS, --json
set FILES none; at least two files
timing FILE none
degrade IN OUT --fault {click,dc,clip,cut-head,cut-tail,seam,noise,pad} (required), --amount, --at, --seed

Readings are explained on Checks and flags.

Edit

Every edit writes a new file and a new sidecar, and takes --override-licence.

Command Flags
edit IN OUT --op {tidy,trim,fade,gain,peak,pitch,stretch,reverse,convolve,dc} (required), --value, --start, --end, --fade-in (2 ms), --fade-out (20 ms), --ir
layer FILES --out (required), --gains, --offsets, --align {peak,start}, --recipe {bigger,pitch,offset}
vary FILES --out (required), --name (var), --count (6), --pitch (1), --gain (1.5), --seed
sequence FILES --out (required), --count (8), --gap (0.55), --jitter (0.08), --limp (1.03), --gain (2), --breaks, --seed
loop IN OUT --crossfade (1)
place LIST --out, --sr (48000)
tone IN --out, --seconds, --min-seconds, --override-licence
pop IN --out, --fps, --level, --verify, --tolerance
record-sheet LIST --out, --all

tone finds a quiet stretch of a recording and can write a fill from it. pop adds or measures a 2 pop for sync. record-sheet writes a recording plan for sounds someone will record. layer --recipe stacks one file at several speeds or start points:

$ python sfx.py tone bed.wav --out fill.wav --seconds 10
{"ok": true, "start_s": 4.9, "seconds": 3.1, "rms_db": -19.1, "flags": ["TONE_SHORT"], "bands_low_mid_high_db": [-43.9, -27.5, -33.4], "out": "fill.wav", "fill_seconds": 10.0}
$ python sfx.py layer hit.wav --out bigger.wav --recipe bigger --gains 0,-3,-6
{"ok": true, "out": "bigger.wav", "layer_peaks_db": {"hit.wav #0": -3.1, "hit.wav #1": -3.1, "hit.wav #2": -3.1}, "flags": ["END_SILENCE"]}
$ python sfx.py record-sheet film.json --all
{"ok": true, "sounds": 1, "sheet": "# Recording sheet: p\n\nBefore the first take: stop the fridge, heating and fans, with your keys in the fridge so you restart it. Stand where the sound will be and listen. Clap, record and play back. Wear quiet clothes.\n\n48 kHz 24-bit; 96 kHz if it will be pitched far down. Watch peaks and leave at least 6 dB. Do not cut rumble now. Say the slate before each take. Vary each take: soft, hard, short, long. Record 30 s of the room with nobody moving.\n\n| Slate | Event | Layers | Length | Takes | Surface, material, distance | Done |\n|---|---|---|---|---|---|---|\n| door | slam | whole | 0.1 s | 3 |  |  |\n"}

The recipes are one engineer's ear each. In one blind listen of five sounds, bigger and generated parts each won once, the same as chance. Layering is an option, not a proven upgrade.

What each edit prints, on one synth creak:

$ python sfx.py edit creak.wav e_tidy.wav --op tidy
{"ok": true, "out": "e_tidy.wav", "route": "tidy", "flags": []}
$ echo $?
0
$ python sfx.py edit creak.wav e_trim.wav --op trim --start 0.1 --end 0.5
{"ok": true, "out": "e_trim.wav", "route": "trim", "flags": ["CUT_HEAD", "CUT_TAIL"]}
$ echo $?
0
$ python sfx.py edit creak.wav e_pitch.wav --op pitch --value -5
{"ok": true, "out": "e_pitch.wav", "route": "varispeed", "flags": []}
$ echo $?
0
$ python sfx.py edit creak.wav e_stretch.wav --op stretch --value 1.5
{"ok": true, "out": "e_stretch.wav", "route": "stretch", "warning": "stretch on an impact smears the attack; prefer pitch (varispeed)", "flags": ["CUT_TAIL"]}
$ echo $?
0

Finish and document

Command Flags
finish IN OUT --dest (required), --ceiling, --format, --sr, --bits {16,24} (24), --keep-ends
name --check NAMES, --style {ucs,short}, --fx, --catid, --creator, --source, --user, --index
sheet FILES --out (required), --width (900): a waveform PNG
review FILES --out (required), --gap (0.6), --match: one clip to listen to, with times
doc LIST --kind {cue,impl,credits} (required), --format {md,csv}, --out
ship LIST --picked-only, --sell-as-pack
budget LIST none
doctor --host
$ python sfx.py name --fx "Crate Drop Heavy" --catid WOODImpt --creator ME --source GAME
{"ok": true, "name": "WOODImpt_Crate Drop Heavy_ME_GAME", "warning": null}
$ echo $?
0
$ python sfx.py name --fx "Crate Drop Heavy" --style short
{"ok": true, "name": "crate_drop_heavy", "warning": null}
$ echo $?
0
$ python sfx.py review s/hit_0[1-3].wav --out review.wav --match
{"ok": true, "out": "review.wav", "index": [{"at_s": 0.0, "file": "hit_01.wav"}, {"at_s": 1.0, "file": "hit_02.wav"}, {"at_s": 2.0, "file": "hit_03.wav"}]}
$ echo $?
0

Destinations for finish, with the caveat that they are file-level peak references and not delivery specs:

--dest Peak ceiling Rate Format
game-sfx, unity -3 dB 48 kHz wav
game-foley -12 dB 48 kHz wav
game-ambience -14 dB 48 kHz ogg
roblox -3 dB 48 kHz ogg
film -1 dB 48 kHz wav
web -3 dB 44.1 kHz ogg
library -1 dB 96 kHz wav

unity is from the repository's finish reference and was not run; the others were.

Clone this wiki locally