Skip to main content

Jingles (jingles)

Jingles are the station's sung identity: short branded phrases ("لوك لوك إف إم") turned into actual mini-songs. The jingles deck mixes three kinds of asset — the signature jingle (loklok1_mixdown.mp3) that airs on a recurring cadence, karaoke jingles (JKL_*) generated by fun-jingles.js, and the legacy identity stingers (FX_LOKLOK_*).

AssetGeneratorPrefixWhat it isLength
Signature jingle(static asset)loklok1_mixdown.mp3The "LOKLOK FM" brand song~9 s
Karaoke jinglefun-jingles.generateKaraokeJingleJKL_<ts>_<bpm>.mp3Sung hook + beat groove~7.3–7.4 s
Identity stingerscontent-manager.generateIdentityStingersFX_LOKLOK_*Voice FX stickers~4 s

All are Break-tagged and R128-mastered to −14 LUFS / −1.5 dBTP.

The karaoke jingle recipe

generateKaraokeJingle({ text, bpm, count, archiveOld }) builds a miniature song:

  1. Spoken hook: TTS of the station phrase (ar-EG-SalmaNeural, rate +12 %).
  2. Sung harmony stack: the hook is pitch-shifted into 4 layers — semitones 0 / +3 / +5 / −2 — staggered by beat so it sounds like a choir.
  3. Beat + bass groove: makeBeatLoop synthesises a 2-bar groove (kick four-on-the-floor, hats on eighths, bassline) — all pure aevalsrc.
  4. Riser intro + sub impact for the hit.
  5. Mix: amixacompressoralimiter → two-pass R128.

The harmony stack — pitch-shift equation

Pitch is shifted s semitones without changing tempo by resampling with the ratio:

r=2s/12r = 2^{s/12}
LayerSemitones sRatio rRole
lead01.0000the melody
third+31.1892major-third harmony
fifth+51.3348perfect-fifth harmony
low−20.8909sub voice (octave-ish weight)

The beat equation

One beat at bpm lasts:

B=60bpm secondsB = \frac{60}{bpm}\ \mathrm{seconds}

makeBeatLoop uses B to place kick on every beat (mod four-on-the-floor), hats on eighth notes (B/2), and a bass note per bar. count loops with bpm + 6·i per iteration so each requested jingle is a distinct tempo variant (112, 118, 124 BPM, …).

The karaoke amix gotcha

amix must be told the real number of labelled inputs: riser, impact, beat loop, hook plus one per harmony layer. If it says inputs=5+layers but only 4+layers streams are labelled, ffmpeg fails with Cannot find a matching stream for unlabeled input pad amix. The generator uses 4 + layerWavs.length.

Signature jingle cadence (the "every 3rd imaging slot" rule)

The signature jingle must air recurring, not once per rotation. The scheduler reserves dedicated slots for it:

  • The jingles snapshot excludes it — it can never air from the random pool.
  • A reserved cadence slot fires it when imagingSlotsFilled >= nextSigAt, where nextSigAt is re-rolled after each firing into SIG_JINGLE_MIN_SLOTS .. SIG_JINGLE_MAX_SLOTS (default 2–5, mean ~3.5 — ≈ every 30–45 min). A fixed ROTATION.SIG_JINGLE_EVERY_SLOTS override still forces a metronomic cadence.

The repeatable push parameter bypasses the path-based seen dedup so the same file can legitimately recur within one rotation — verified at ~7 airings per 120 slots at varying spacing (gaps of 12–20 tracks).

How the scheduler uses them

  • Imaging cooldown (10 min) — jingles are elements, not music.
  • liq_cross_duration = 0.5 (both the 7.4 s karaoke and ~9 s signature are far longer than the 0.2 s stingers, but still under the 2 s cross default context).
  • Regenerated/rotated with every content refresh.

Endpoints & CLI

  • CLI (standalone, not yet wired to server/MCP): node scripts/run-fun-jingles.js [--text "لوك لوك إف إم"] [--bpm 112] [--jingles N] [--fx N] [--no-archive]
  • Rotation tuning: SCHEDULER_CONFIG.ROTATION.SIG_JINGLE_MIN_SLOTS / SIG_JINGLE_MAX_SLOTS / SIG_JINGLE_EVERY_SLOTS (via POST /api/station/config or set_config MCP).

Verify on air

  1. media/jingles contains JKL_* (karaoke) + loklok1_mixdown.mp3.
  2. m3u: annotate:type=jingles,liq_cross_duration=0.5: entries; the signature appears ~6–7× per 120-track rotation at varying spacing.
  3. ffprobe a JKL file: ~7.4 s, 48 kHz stereo, Break-tagged, R128 ≈ −14 LUFS.
  4. Remote grep -c loklok1_mixdown main.m3u should show the cadence count.