Skip to main content

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:

GeneratorModuleFile prefixDesignsLength
imaging.renderStingerimaging-engine.jsSFX_<DESIGN>_<n>.mp316 (STINGER_DESIGNS)0.22–1.63 s
fun-jingles.generateSoundDesignfun-jingles.jsSFX_<NAME>_<n>.mp324 (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):

DesignDurationCharacter
RISER1.6 s120 Hz → +3.2 octaves exponential sweep + pink noise build
RISER_DOWN1.1 s2800 Hz → −4 octaves sweep + low-passed pink
WHOOSH_UP1.0 spink noise high-passed, fade-in swell + low sweep
WHOOSH_DOWN0.9 spink noise low-passed decay + descending sweep
ZAP0.35 s2600 → −4200 Hz/s linear sweep + white-noise click
BLIP_UP0.28 s500 → +3400 Hz/s chirp, exp decay
BLIP_DOWN0.28 s3600 → −3400 Hz/s chirp, exp decay
GLITCH0.6 s0.12 s gated tone bursts (1300 Hz rising) + 220 Hz tremolo
DROP0.8 s130 Hz → −3.5 octaves crash + low-passed white noise
METALLIC0.7 s820/1640/2460 Hz harmonic stack with pitch glides
SWEEP1.1 s150/225 Hz +3 octaves dual-sine + band-passed pink
SHIMMER1.3 s880/885/884.4 Hz beating detune + 1320 Hz overtone
PULSE1.0 s220 Hz with 8 Hz AM envelope + 110 Hz sub
LASER0.22 s450 → +8800 Hz/s rising chirp (the shortest design)
SUBDROP1.2 s55 Hz sub + 80 Hz → −2.5 octaves + high noise shard
IMPACT0.6 s60 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:

f(t)=f02kt,k=log2(f1/f0)Tf(t) = f_0 \cdot 2^{\,k \cdot t},\qquad k = \frac{\log_2(f_1/f_0)}{T}

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:

f(t)=f0+ct,c=f1f0Tf(t) = f_0 + c \cdot t,\qquad c = \frac{f_1 - f_0}{T}

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:

A(t)=A0eαtA(t) = A_0 \cdot e^{-\alpha t}

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:

x(t)=sin(2πfct)(0.5+0.5sin(2πfmt))x(t) = \sin(2\pi f_c t) \cdot \bigl(0.5 + 0.5\sin(2\pi f_m t)\bigr)

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:

fbeat=f1f2=880885=5 Hzf_{beat} = |f_1 - f_2| = |880 - 885| = 5\ \mathrm{Hz}

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:

g(t)={mod(t,0.33)if mod(t,0.33)<0.120.01otherwiseg(t) = \begin{cases} \mathrm{mod}(t, 0.33) & \text{if } \mathrm{mod}(t,0.33) < 0.12 \\[2pt] 0.01 & \text{otherwise} \end{cases}

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. Its liq_cross_duration override is 0.2 s (below the shortest 0.24 s design) so cross never 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: true archives the previous SFX_* set to pro_archive/sounddesign_old_<ts> (fun-jingles) or the imaging stamp set.

Verify on air

  1. ls media/sfx | wc -l should show both libraries (16 + 24 designs).
  2. Rotation m3u entries for sfx carry annotate:type=sfx,liq_cross_duration=0.2:.
  3. ffprobe a generated file: expect ~48 kHz stereo mp3, TXXX:break tag, and R128 integrated ≈ −14 LUFS.
  4. Remote radio.log must show zero [cross:1] End of track reached while buffering warnings (that is the symptom the liq_cross_duration override eliminated).