Skip to main content

Music Decks (arabic_hits, egyptian_trends, arab_techno, english)

The four music decks are the heart of the rotation — real songs, fetched by yt-dlp against curated singer catalogs + trend queries, quality-gated, R128-mastered and tagged. Every non-imaging, non-cadence slot in the rotation is filled by one of these decks.

DeckFile prefixLive capDefault genre weight
arabic_hitsTRK_*.mp3600.45
egyptian_trendsTRK_*.mp3400.30
arab_technoTRK_*.mp3400.10
englishTRK_*.mp3400.15

Files are stored in media/<category>/ as TRK_<epoch>_<hash>.mp3.

Acquisition & the junk gate

Each refresh:

  1. Runs singer-catalog queries (curated per category) plus generic trend queries (ytsearchN where N = max(10, needed + 3)).
  2. Applies the junk gate at every stage — yt-dlp --reject-title pre-download, post-download BAD_TITLE/REJECT_TITLE, scrubCategory, and a deriveMeta re-check. It kills concerts/shows/news/covers/karaoke/remixes, AI-slop (Suno/UDIO/Melovox/حمو القماش), and Shaabi terms.
  3. Dedupes by song key (artist | title), by YouTube ID, and by dupKey.
  4. Masters to uniform format: 48 kHz, stereo, 320 k, R128 to −14 LUFS.
  5. Writes the meta_registry.json entry and ID3 tags (clean titles — no "Video"/"Official" wording ever).

The genre-sampling equation

When a slot needs music, the scheduler picks a deck by Dirichlet-style sampling over the genre weights (subject to recent-category anti-fatigue and daypart multipliers):

p(c)=wcMdaypart,cBcrowd,ciwiMdaypart,iBcrowd,ip(c) = \frac{w_c \cdot M_{daypart,c} \cdot B_{crowd,c}}{\sum_i w_i \cdot M_{daypart,i} \cdot B_{crowd,i}}
  • w_c — the deck's base weight (GENRE_SPLIT).
  • M_{daypart,c} — the daypart multiplier (e.g. english ×8 in deep night 02–05 with hard gate; techno ×3.5 late night; trends ×1.5 midday/evening).
  • B_{crowd,c} — crowd energy bias: at ≥ CROWD_SIZE_THRESHOLD = 5 listeners, arab_techno ×1.70 and arabic_hits ×1.35 get boosted.

Track scoring (NN/VAE)

Candidates are scored by a neural/VAE blend — the classifier predicts how well a track fits the current energy:

score(t)=0.7SNN(t)+0.3SVAE(t)\mathrm{score}(t) = 0.7 \cdot S_{NN}(t) + 0.3 \cdot S_{VAE}(t)

with L2-regularised training on play feedback (reward from real listener deltas and crowd votes). The success predictor adds experience per on-air track.

Anti-repeat & spacing

  • Cooldown: 3 h (COOLDOWN_PERIOD) — a song never repeats inside ~3 h; on top of that, MUSIC_FRESH_MS (3 h) keeps it out of the rotation window.
  • Song-key dedup: artist | title normalised (suffixes like "(TINI Version)" stripped) — catches the same song downloaded as two files.
  • Artist gap (ARTIST_GAP = 12): no artist repeats within ~12 slots (~40 min) of their last appearance.
  • Least-recently-aired pick: popFrom chooses the never-aired or longest-waiting eligible candidate — not the first off-cooldown one — so rebuilds recycle variety instead of re-playing the newest tracks.

Pool health & rotation health

  • checkPoolHealth() runs every 15 min and restores any deck below 50 % of its cap from the archive (newest stamps first), triggering a full fetch-refresh if the archive is exhausted.
  • The 6 h auto-refresh keeps each deck near its cap; updateM3U sorts fresh-first (newest addedAtOf first).
  • The 72 h staleness gate excludes music older than 72 h from the rotation while any younger track exists (relaxed valve fills ancient tracks rather than going silent).

Verify on air

  1. Deck sizes: arabic_hits ≈ 60, egyptian_trends ≈ 40, arab_techno ≈ 40, english ≈ 40 (English is legitimately thin right after a junk scrub — the next auto-refresh tops it up).
  2. Rotation m3u: music entries have no liq_cross_duration (full 2 s crossfade).
  3. /api/status now-playing shows clean titles — no "Video", no mojibake, no junk genres.
  4. No banned artists/terms in the rotation (grep the m3u for the ban list).