Stingers & Sound FX (sfx)
Stingers are the station's short sound-design punctuation: sub-drops, risers,
whooshes, zaps, glitch stutters and impacts that lead into or land on a beat.
Every one is synthesised from scratch in the ffmpeg lavfi graph — no samples,
no licensing, deterministic and regenerable. They live in media/sfx and carry
SFX_ prefixes; two generators exist:
| Generator | Module | File prefix | Designs | Length |
|---|---|---|---|---|
imaging.renderStinger | imaging-engine.js | SFX_<DESIGN>_<n>.mp3 | 16 (STINGER_DESIGNS) | 0.22–1.63 s |
fun-jingles.generateSoundDesign | fun-jingles.js | SFX_<NAME>_<n>.mp3 | 24 (SOUND_DESIGNS) | 0.35–2.6 s |
Both are Break-tagged (artist/title/genre = Break) so on-air metadata stays
clean, and both run two-pass R128 to −14 LUFS / −1.5 dBTP.
The two synthesised libraries
STINGER_DESIGNS (imaging-engine, 16 designs)
Each design is 2–3 layered sources mixed with amix=normalize=0, widened with
stereotools=mlev=1:slev=1.4, limited, then R128-mastered. The layers combine
evalsrc tones (frequency-swept sines) with noise beds (pink or white):
| Design | Duration | Character |
|---|---|---|
RISER | 1.6 s | 120 Hz → +3.2 octaves exponential sweep + pink noise build |
RISER_DOWN | 1.1 s | 2800 Hz → −4 octaves sweep + low-passed pink |
WHOOSH_UP | 1.0 s | pink noise high-passed, fade-in swell + low sweep |
WHOOSH_DOWN | 0.9 s | pink noise low-passed decay + descending sweep |
ZAP | 0.35 s | 2600 → −4200 Hz/s linear sweep + white-noise click |
BLIP_UP | 0.28 s | 500 → +3400 Hz/s chirp, exp decay |
BLIP_DOWN | 0.28 s | 3600 → −3400 Hz/s chirp, exp decay |
GLITCH | 0.6 s | 0.12 s gated tone bursts (1300 Hz rising) + 220 Hz tremolo |
DROP | 0.8 s | 130 Hz → −3.5 octaves crash + low-passed white noise |
METALLIC | 0.7 s | 820/1640/2460 Hz harmonic stack with pitch glides |
SWEEP | 1.1 s | 150/225 Hz +3 octaves dual-sine + band-passed pink |
SHIMMER | 1.3 s | 880/885/884.4 Hz beating detune + 1320 Hz overtone |
PULSE | 1.0 s | 220 Hz with 8 Hz AM envelope + 110 Hz sub |
LASER | 0.22 s | 450 → +8800 Hz/s rising chirp (the shortest design) |
SUBDROP | 1.2 s | 55 Hz sub + 80 Hz → −2.5 octaves + high noise shard |
IMPACT | 0.6 s | 60 Hz thump + 2000 Hz → sweep + low-passed noise crash |
SOUND_DESIGNS (fun-jingles, 24 designs)
A broader effects library — instrumental hits and ambient textures: DEEP_BOOM,
SNARE, CLAP, CYMBAL, TOM, REVERSE_CYMBAL, VINYL_SCRATCH, PHONE_RING,
STATIC_BURST, AIR_HORN, RISER_LONG, BRIDGE, DOWNFALL, ALERT,
SOFT_BEEP, GLITCH_STUTTER, HEARTBEAT, SWEEP_UP, IMPACT_BIG, SHOCK,
BREEZE, SIGNAL, BEAT_DROP, REVERSE_BOOM. These reuse the same lavfi
recipe (evalsrc/noise → chain → alimiter → R128).
The synthesis equations
1. Exponential frequency sweep
Most risers and falls use an octave-exponential sweep — constant musical slope, so it sounds like a climb rather than an accelerating whine:
where f0 is the start frequency, f1 the end frequency and T the duration.
RISER uses f0 = 120, k = 3.2; RISER_DOWN uses f0 = 2800, k = −4.
2. Linear (chirp) sweep
The zaps and lasers are linear chirps — a fixed Hz-per-second slope:
ZAP runs 2600 − 4200·t Hz/s over 0.35 s; LASER runs 450 + 8800·t Hz/s over
0.22 s.
3. Exponential decay (the "thump")
Sub-drops and impacts shape the amplitude with an exponential decay envelope:
with α = 3 to 8 (IMPACT uses α = 8 — a very fast thump; SUBDROP α = 4).
DEEP_BOOM also pitch-falls while it decays: f = 48·exp(−3t).
4. AM / tremolo pulses
PULSE and GLITCH gate or tremolo a carrier:
PULSE uses fc = 220, fm = 8; the GLITCH gate is a square window of
lt(mod(t,0.12),0.05) (0.05 s on, 0.07 s off).
5. Beat detuning (SHIMMER)
Two-plus sines at slightly different frequencies produce a beating shimmer whose beat rate is the difference:
with a third partial at 880·1.005 for a second, slower beating layer.
6. The < gotcha (why expressions use lt)
The lavfi aevalsrc on this ffmpeg build rejects the < character in
expressions (silent Invalid argument). All comparators are written as
functions — lt(a,b), gt(a,b), mod(t,x), and if(c,a,b) instead of the
ternary ?:. VINYL_SCRATCH demonstrates both fixes:
Stereo width
Tonal layers are mixed mono (mlev=1) then widened with
stereotools=mlev=1:slev=1.4 (or 1.3–1.5) — the mid stays intact while the
sides get a 40–50 % boost, which reads as "big" on small speakers.
How the scheduler uses them
- Stochastic sfx (
STOCHASTIC_SFX_CHANCE = 0.30): before many music picks the scheduler may air a stinger to lead into the track. Itsliq_cross_durationoverride is 0.2 s (below the shortest 0.24 s design) socrossnever starves. - Stinger-led mashups: the mashup cadence slot plays a stinger then the
mashup in the same slot (the old code
continued and burned the mashup pick — fixed 2026-08-06). - Element cooldown: 10 min (
ELEMENT_COOLDOWN), not the 3 h music cooldown.
Endpoints & CLI
POST /api/imaging/generate(all four generators),POST /api/imaging/stingers({count, archiveOld}); each resyncs decks + pushes rotation.- MCP:
generate_stingers. - CLI:
node scripts/run-imaging.js --stingers N; fun-jingles:node scripts/run-fun-jingles.js --fx N. - Regenerating with
archiveOld: truearchives the previousSFX_*set topro_archive/sounddesign_old_<ts>(fun-jingles) or the imaging stamp set.
Verify on air
ls media/sfx | wc -lshould show both libraries (16 + 24 designs).- Rotation m3u entries for sfx carry
annotate:type=sfx,liq_cross_duration=0.2:. - ffprobe a generated file: expect ~48 kHz stereo mp3,
TXXX:breaktag, and R128 integrated ≈ −14 LUFS. - Remote
radio.logmust show zero[cross:1] End of track reached while bufferingwarnings (that is the symptom theliq_cross_durationoverride eliminated).