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_*).
| Asset | Generator | Prefix | What it is | Length |
|---|---|---|---|---|
| Signature jingle | (static asset) | loklok1_mixdown.mp3 | The "LOKLOK FM" brand song | ~9 s |
| Karaoke jingle | fun-jingles.generateKaraokeJingle | JKL_<ts>_<bpm>.mp3 | Sung hook + beat groove | ~7.3–7.4 s |
| Identity stingers | content-manager.generateIdentityStingers | FX_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:
- Spoken hook: TTS of the station phrase (
ar-EG-SalmaNeural, rate +12 %). - 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.
- Beat + bass groove:
makeBeatLoopsynthesises a 2-bar groove (kick four-on-the-floor, hats on eighths, bassline) — all pureaevalsrc. - Riser intro + sub impact for the hit.
- Mix:
amix→acompressor→alimiter→ two-pass R128.
The harmony stack — pitch-shift equation
Pitch is shifted s semitones without changing tempo by resampling with the
ratio:
| Layer | Semitones s | Ratio r | Role |
|---|---|---|---|
| lead | 0 | 1.0000 | the melody |
| third | +3 | 1.1892 | major-third harmony |
| fifth | +5 | 1.3348 | perfect-fifth harmony |
| low | −2 | 0.8909 | sub voice (octave-ish weight) |
The beat equation
One beat at bpm lasts:
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, wherenextSigAtis re-rolled after each firing intoSIG_JINGLE_MIN_SLOTS .. SIG_JINGLE_MAX_SLOTS(default 2–5, mean ~3.5 — ≈ every 30–45 min). A fixedROTATION.SIG_JINGLE_EVERY_SLOTSoverride 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(viaPOST /api/station/configorset_configMCP).
Verify on air
media/jinglescontainsJKL_*(karaoke) +loklok1_mixdown.mp3.- m3u:
annotate:type=jingles,liq_cross_duration=0.5:entries; the signature appears ~6–7× per 120-track rotation at varying spacing. - ffprobe a JKL file: ~7.4 s, 48 kHz stereo, Break-tagged, R128 ≈ −14 LUFS.
- Remote
grep -c loklok1_mixdown main.m3ushould show the cadence count.