diff --git a/gateway/player.html b/gateway/player.html
index a92434f..1927765 100644
--- a/gateway/player.html
+++ b/gateway/player.html
@@ -299,7 +299,30 @@
line-height: 1.4;
}
.status.generating { color: var(--accent); }
+ .status.ready-flash { color: #6bcf8e; font-weight: 600; }
.status.error { color: #ff5c7a; }
+ .gen-ready-toast {
+ position: fixed;
+ top: 1rem;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 9999;
+ background: linear-gradient(135deg, #1e3a2f, #2a4a3a);
+ border: 1px solid #6bcf8e;
+ color: #e8fff0;
+ padding: 0.85rem 1.25rem;
+ border-radius: 12px;
+ font-size: 0.85rem;
+ font-weight: 600;
+ box-shadow: 0 8px 32px rgba(0,0,0,0.45);
+ animation: toast-in 0.35s ease;
+ max-width: 90vw;
+ text-align: center;
+ }
+ @keyframes toast-in {
+ from { opacity: 0; transform: translateX(-50%) translateY(-12px); }
+ to { opacity: 1; transform: translateX(-50%) translateY(0); }
+ }
.stack {
font-size: 0.7rem;
color: #555568;
@@ -626,6 +649,7 @@
Connectingβ¦
+
@@ -674,7 +698,10 @@
const moodEl = document.getElementById('mood');
const djEl = document.getElementById('dj');
const statusEl = document.getElementById('status');
+ const genReadyToast = document.getElementById('genReadyToast');
const genBtn = document.getElementById('genBtn');
+ let pollStartedAt = null;
+ let lastNotifiedCompletedAt = null;
const skipBtn = document.getElementById('skipBtn');
const refreshBtn = document.getElementById('refreshBtn');
const chatLog = document.getElementById('chatLog');
@@ -978,8 +1005,11 @@
const costs = stats.costs || {};
statSpent.textContent = fmtUsd(today.estimated_usd);
statPerTrack.textContent = `~${fmtUsd(costs.per_track_estimate_usd)} / track`;
- statGenerated.textContent = `${today.generated || 0} / ${today.max_per_day || 10}`;
- statRemaining.textContent = `${today.remaining ?? 0} remaining`;
+ const unlimited = today.unlimited || today.max_per_day === 0;
+ statGenerated.textContent = unlimited
+ ? `${today.generated || 0} (unlimited)`
+ : `${today.generated || 0} / ${today.max_per_day || 10}`;
+ statRemaining.textContent = unlimited ? 'β' : `${today.remaining ?? 0} remaining`;
statMaxBudget.textContent = fmtUsd(stats.projected_daily_max_usd);
if (stats.playback) {
radioSettings.playback = stats.playback;
@@ -1161,22 +1191,63 @@
}
let pollTimer = null;
+ let toastTimer = null;
+
+ function showReadyToast(title) {
+ genReadyToast.textContent = `Ready Β· ${title}`;
+ genReadyToast.hidden = false;
+ clearTimeout(toastTimer);
+ toastTimer = setTimeout(() => { genReadyToast.hidden = true; }, 8000);
+ }
+
+ function notifyTrackReady(track, gen) {
+ const title = track?.title || gen?.last_completed_title || 'New track';
+ statusEl.textContent = `Ready Β· playing ${title}`;
+ statusEl.classList.remove('generating', 'error');
+ statusEl.classList.add('ready-flash');
+ setTimeout(() => statusEl.classList.remove('ready-flash'), 5000);
+ showReadyToast(title);
+ addBubble('dj', `Fresh cut ready: ${title} β on air now.`);
+ if (typeof Notification !== 'undefined' && Notification.permission === 'granted') {
+ new Notification('Live Ozan Radio', { body: `Ready: ${title}`, tag: 'track-ready' });
+ }
+ lastNotifiedCompletedAt = gen?.last_completed_at || null;
+ }
+
function pollForNewTrack() {
let n = 0;
+ pollStartedAt = Date.now();
clearInterval(pollTimer);
pollTimer = setInterval(async () => {
n += 1;
const stats = await loadStats(true);
- if (stats?.generation?.busy) return;
- if (stats?.generation?.error) {
+ const gen = stats?.generation;
+ if (gen?.busy) return;
+ if (gen?.error) {
+ clearInterval(pollTimer);
+ statusEl.textContent = gen.error;
+ statusEl.classList.add('error');
+ addBubble('dj', `Couldn't finish that track: ${gen.error}`);
+ return;
+ }
+ const completedAt = gen?.last_completed_at;
+ const isNewCompletion = completedAt
+ && completedAt !== lastNotifiedCompletedAt
+ && (!pollStartedAt || new Date(completedAt).getTime() >= pollStartedAt - 2000);
+ await refreshNow();
+ const now = await fetch(`${API}/api/now`).then(r => r.json()).catch(() => null);
+ if (isNewCompletion && now?.track) {
+ applyTrack(now.track, 'generated');
+ notifyTrackReady(now.track, gen);
clearInterval(pollTimer);
return;
}
- await refreshNow();
- const now = await fetch(`${API}/api/now`).then(r => r.json()).catch(() => null);
if (now?.track?.track_id !== currentTrackId) clearInterval(pollTimer);
- if (n > 60) clearInterval(pollTimer);
- }, 3000);
+ if (n > 90) {
+ clearInterval(pollTimer);
+ statusEl.textContent = 'Generation taking longer than usual β still pollingβ¦';
+ }
+ }, 2000);
}
async function refreshNow() {
@@ -1197,22 +1268,35 @@
}
async function generate() {
+ if (typeof Notification !== 'undefined' && Notification.permission === 'default') {
+ Notification.requestPermission();
+ }
genBtn.disabled = true;
- statusEl.textContent = 'DeepSeek is planningβ¦ Lyria is composingβ¦';
+ statusEl.textContent = 'Startingβ¦ DeepSeek plans, then Lyria composes (~30β90s)';
+ statusEl.classList.add('generating');
try {
const res = await fetch(`${API}/api/generate`, { method: 'POST' });
const data = await res.json();
if (data.status === 'busy') {
statusEl.textContent = data.message;
+ applyGenerationState(data.generation);
+ pollForNewTrack();
} else if (data.status === 'limit') {
statusEl.textContent = data.message || 'Daily limit reached';
+ statusEl.classList.remove('generating');
updateDashboard({ today: data.budget, costs: radioSettings.costs });
} else if (data.status === 'error') {
statusEl.textContent = data.message || 'Generation failed';
statusEl.classList.add('error');
applyGenerationState(data.generation || { error: data.message });
+ } else if (data.status === 'accepted' || data.generating) {
+ applyGenerationState(data.generation || { busy: true, phase: 'planning' });
+ pollForNewTrack();
} else if (data.status === 'ok') {
- if (data.track) applyTrack(data.track, data.source || 'generated');
+ if (data.track) {
+ applyTrack(data.track, data.source || 'generated');
+ notifyTrackReady(data.track, data.generation);
+ }
await loadStats();
} else {
statusEl.textContent = 'Generation failed β check server logs';
diff --git a/settings.json b/settings.json
index 542787e..39de4ef 100644
--- a/settings.json
+++ b/settings.json
@@ -1,71 +1,67 @@
{
"listener": "ozan",
- "profile_note": "Modern electronica meets ethnic fusion β Jon Hopkins Singularity, Karsh Kale, Transglobal Underground. Not Turkish folk. Updated 2026-06-07.",
+ "profile_note": "Techno-ethnic station β Bonobo, Jon Hopkins, Jamaica dub, Sahel, Mongolia overtone vocals, Urdu colour. Beautiful cross-cultural blends. Not Turkish folk. Updated 2026-06-07.",
"station": "Live Ozan Radio",
"taste": {
- "summary": "Modern electronica meets ethnic fusion β Jon Hopkins Singularity lush synth builds, Karsh Kale tabla breaks, Transglobal dervish-trance. Electronic production first, world percussion and organic colour woven in. Not Turkish folk.",
+ "summary": "Techno-ethnic β beautiful electronica meets world dub. Bonobo lush downtempo, Jon Hopkins Singularity builds, Jamaica spring-reverb dub, Sahara/Sahel warmth, Mongolian overtone throat textures, Urdu vocal colour, Karsh Kale tabla breaks. Many cultures woven into one gorgeous track β not one country alone.",
"genres": [
+ "techno-ethnic",
+ "ethnic dub",
+ "bonobo-style downtempo electronica",
+ "jamaican dub and reggae-dub",
"modern electronica",
- "ambient techno meets world",
- "ethnic fusion",
- "dervish trance",
- "tabla electronica",
- "asian underground",
+ "sahel desert electronica",
+ "mongolian overtone electronica",
+ "urdu-world fusion",
"global dub",
"dubtronica",
- "world electronica",
- "cinematic electronic",
- "trip-hop dub"
+ "trip-hop dub",
+ "tabla electronica",
+ "cinematic electronic"
],
"mood": [
+ "beautiful",
"hypnotic",
- "trance-like pulse",
"warm",
"spacious",
"late-night",
- "cinematic gothic",
+ "lush and melodic",
"meditative but danceable",
- "euphoric electronic builds",
- "immersive and cinematic",
- "cross-cultural blend"
+ "euphoric builds",
+ "immersive",
+ "cross-cultural wonder"
],
"instruments": [
- "lush synth pads and arpeggiated electronica (Singularity-style)",
- "tabla and Indian percussion driving the groove",
- "sitar and synth leads blended",
- "organic hand percussion meets digital pulse",
- "oud and kora as colour accents",
- "sub bass and sidechain warmth",
- "dub delay and spring reverb",
- "wordless vocal textures",
- "Gregorian-adjacent choral layers (Frostbite lane)",
- "ceremonial ritual chants (Chac's Dub lane)",
- "breakbeat or four-on-floor-lite hypnotic pulse",
- "analog warmth and wide stereo immersion"
+ "lush Bonobo-style synths and melodic electronica",
+ "Jamaica dub β spring reverb, one-drop feel, sub bass culture",
+ "Sahel kora and desert percussion accents",
+ "Mongolian overtone throat singing as wordless texture layer",
+ "Urdu male vocal colour β poetic, not Bollywood pop",
+ "tabla and hand percussion meets digital pulse",
+ "dub delay and tape echo",
+ "Jon Hopkins arpeggiated builds",
+ "organic samples woven through electronic production"
],
- "tempo_bpm": [90, 124],
- "references": "Jon Hopkins Singularity β modern electronica with organic world percussion and euphoric synth builds; Karsh Kale Milan (tabla electronica); Transglobal Underground Dervish Trans; Thievery Corporation lounge; Shye Ben Tzur / Anoushka Shankar Indian fusion; Baaba Maal Sahel accent; Glass Beams psychedelic tint. Electronic production leads β world colour is woven in, not folk-first.",
+ "tempo_bpm": [88, 118],
+ "references": "Bonobo β lush melodic downtempo, beautiful ethnic electronica; Jon Hopkins Singularity; Jamaica dub (Kingston spring reverb, Mungo's Hi Fi); Baaba Maal / Sahara Sahel warmth; Karsh Kale Milan; Transglobal Dervish Trans; Thievery Corporation; Shye Ben Tzur Urdu-Indian fusion without pop. We are inventing techno-ethnic β electronic beauty first, world voices as colour.",
"avoid": [
- "Turkish-specific tracks β Ney Vakti, Anatolian night, Ottoman court, baΔlama/saz as the identity",
- "Turkish language lead vocals or Turkish folk melody as the hook",
- "Anadolu psych or AltΔ±n GΓΌn-style as the main flavour",
- "Indian pop or Rajasthani folk dub β dholak, kartal, sarangi Bollywood-adjacent feel",
- "Japanese enka shamisen slow-burn under 88 BPM β Kurai Yoru too slow",
- "Armenian duduk hymn dub β slow mournful sacred chant, Duduk Zomari too slow",
- "single-country homages of any nation",
- "lead vocals locked to one national folk tradition",
- "West African highlife and palm-wine guitar",
- "slow ney-and-baΔlama meditation",
+ "Turkish folk β Ney Vakti, Anatolian, Ottoman, baΔlama/saz as identity",
+ "Indian pop / Rajasthani folk dub",
+ "Japanese enka shamisen slow-burn",
+ "Armenian duduk hymn slow sacred chant",
+ "single-country homages",
+ "slow sluggish tracks under 88 BPM",
+ "West African highlife palm-wine guitar",
"big-room EDM drops",
"four-on-the-floor house",
- "fuzz electric guitar on griot or lead vocal tracks",
- "sluggish tempo under 88 BPM"
+ "fuzz electric guitar on vocal tracks"
]
},
"dj": {
"variety": true,
"default_length": "1-2 minutes",
- "prefer_fusion_over_locale": true
+ "prefer_fusion_over_locale": true,
+ "station_mission": "techno-ethnic β Bonobo-beautiful blends across Jamaica, Sahara, Mongolia, Urdu, and beyond"
},
"playback": {
"shuffle": true,
diff --git a/songs/genres.json b/songs/genres.json
index 8065872..1fdc2c7 100644
--- a/songs/genres.json
+++ b/songs/genres.json
@@ -1,26 +1,29 @@
{
"updated": "2026-06-07",
- "description": "Station genre taxonomy β tags on each track; categories are broader browse buckets.",
+ "description": "Techno-ethnic taxonomy β Bonobo-beautiful electronica meets world dub and cross-cultural vocals.",
+ "station_lane": "techno-ethnic",
"categories": {
- "fusion-electronic": "Jon Hopkins / Karsh Kale β modern electronica meets world percussion",
+ "techno-ethnic": "Bonobo / Hopkins β lush electronic beauty meets world colour",
+ "jamaica-dub": "Kingston spring reverb, reggae-dub bass culture",
+ "sahara-sahel": "Desert warmth, kora, Sahel pulse",
+ "mongolia-overtone": "Throat singing as wordless ethereal layer",
+ "urdu-fusion": "Urdu poetic vocal colour in electronica",
"dub-space": "Dub delay, sub bass, spacious mixes",
- "ceremonial-world": "Ritual chant, temple/ceremony energy β Chac's Dub lane",
- "cinematic-gothic": "Cold ether, choral depth, Only Lovers Left Alive mood β Frostbite / Gregorian lane",
- "desert-dub": "Sahel warmth, caravan pulse, desert blues texture",
- "vocal-ethnic": "Wordless or blended ethnic vocals, not folk-pop"
+ "ceremonial-world": "Ritual chant energy β Chac's Dub",
+ "cinematic-gothic": "Gregorian ether β Frostbite Dub"
},
"genres": {
- "modern-electronica": { "category": "fusion-electronic", "label": "Modern electronica meets world" },
- "tabla-electronica": { "category": "fusion-electronic", "label": "Karsh Kale / Milan-style" },
- "dervish-trance": { "category": "fusion-electronic", "label": "Transglobal hypnotic breaks" },
- "ceremonial-dub": { "category": "ceremonial-world", "label": "Ceremonial chant + dub space" },
- "mesoamerican-electronica": { "category": "ceremonial-world", "label": "Clay flute, ritual pulse" },
- "gregorian-ether": { "category": "cinematic-gothic", "label": "Gregorian-adjacent choral depth" },
- "nordic-dub": { "category": "cinematic-gothic", "label": "Icelandic ether + sub bass" },
- "cinematic-gothic": { "category": "cinematic-gothic", "label": "Cold cinematic gothic dub" },
- "desert-dub": { "category": "desert-dub", "label": "Desert dubtronica" },
- "sahel-blues": { "category": "desert-dub", "label": "Sahel griot warmth" },
- "gnawa-dub": { "category": "dub-space", "label": "Gnawa meets dubtronica" },
+ "techno-ethnic": { "category": "techno-ethnic", "label": "Core station lane β beautiful blended electronica" },
+ "bonobo-downtempo": { "category": "techno-ethnic", "label": "Lush melodic Bonobo-style electronica" },
+ "modern-electronica": { "category": "techno-ethnic", "label": "Jon Hopkins Singularity builds" },
+ "jamaican-dub": { "category": "jamaica-dub", "label": "Jamaica dub and reggae-dub space" },
+ "sahel-electronica": { "category": "sahara-sahel", "label": "Sahara / Sahel desert electronica" },
+ "mongolian-overtone": { "category": "mongolia-overtone", "label": "Overtone throat textures in mix" },
+ "urdu-electronica": { "category": "urdu-fusion", "label": "Urdu vocal colour, not Bollywood" },
+ "tabla-electronica": { "category": "techno-ethnic", "label": "Karsh Kale / Milan-style" },
+ "ceremonial-dub": { "category": "ceremonial-world", "label": "Ceremonial chant + dub" },
+ "gregorian-ether": { "category": "cinematic-gothic", "label": "Gregorian-adjacent choral dub" },
+ "desert-dub": { "category": "sahara-sahel", "label": "Desert dubtronica" },
"world-dub": { "category": "dub-space", "label": "Pan-ethnic dub lounge" }
}
}
diff --git a/songs/library_index.json b/songs/library_index.json
index 8ce6700..0557a76 100644
--- a/songs/library_index.json
+++ b/songs/library_index.json
@@ -1,7 +1,7 @@
{
"updated": "2026-06-07",
"listener": "ozan",
- "summary": "Batch purge 2026-06-07 β kept original 5 + Chac's Dub + Frostbite Dub only. Lane: Hopkins/Karsh Kale fusion, not slow country-specific gens.",
+ "summary": "Techno-ethnic station β Bonobo, Jamaica dub, Sahara, Mongolia overtone, Urdu, Hopkins. Seven keepers. Beautiful cross-cultural blends, not single-nation folk.",
"genre_taxonomy": "songs/genres.json",
"global_avoid": [
"fuzz electric guitar on griot/vocal tracks",
@@ -19,14 +19,16 @@
"Armenian duduk hymn dub β slow sacred chant, listener hated Duduk Zomari (same problems)"
],
"global_love": [
- "Jon Hopkins Singularity β modern electronica meets organic world percussion",
- "ceremonial dub β Chac's Dub temple-step energy",
- "Gregorian-adjacent choral ether β Frostbite Dub (listener used to love Gregorian)",
- "lush arpeggiated synths and euphoric cinematic builds",
- "Karsh Kale / Milan-style tabla electronica",
- "Transglobal Underground dervish-trance pulse",
- "sub bass pulse and warm analog dub space",
- "wordless vocal textures"
+ "Bonobo β lush beautiful melodic downtempo ethnic electronica",
+ "techno-ethnic blends β Jamaica dub + Sahara + Mongolia + Urdu in one track",
+ "Jamaica spring-reverb dub and reggae-dub bass culture",
+ "Sahara / Sahel desert warmth as electronic colour",
+ "Mongolian overtone throat singing as wordless ethereal layer",
+ "Urdu poetic male vocal colour β fusion not Bollywood pop",
+ "Jon Hopkins Singularity builds",
+ "ceremonial dub β Chac's Dub",
+ "Gregorian ether β Frostbite Dub",
+ "sub bass and warm dub space"
],
"gold_standard_id": "1c1d7b8a",
"tracks": [
diff --git a/songs/manifest.json b/songs/manifest.json
index e0a96d4..8ade0db 100644
--- a/songs/manifest.json
+++ b/songs/manifest.json
@@ -1,5 +1,5 @@
{
- "index": 6,
+ "index": 0,
"count": 7,
"tracks": [
{
diff --git a/src/ozan_radio/batch_prompts.py b/src/ozan_radio/batch_prompts.py
index cd676f0..04f884b 100644
--- a/src/ozan_radio/batch_prompts.py
+++ b/src/ozan_radio/batch_prompts.py
@@ -1,94 +1,84 @@
-"""Curated batch directions β Jon Hopkins Singularity meets ethnic fusion, not Turkish folk."""
+"""Techno-ethnic batch β Bonobo-beautiful blends: Jamaica, Sahara, Mongolia, Urdu, and more."""
VOCAL_BATCH: list[str] = [
(
- "Jon Hopkins Singularity-style β lush arpeggiated synths, organic tabla and "
- "hand percussion, euphoric build, sub bass pulse, wordless vocal textures, 118 BPM. "
- "Modern electronica meets world colour."
+ "Bonobo-style techno-ethnic β lush melodic synths, soft breakbeat, "
+ "Jamaica dub spring reverb on percussion, 96 BPM. Beautiful and spacious."
),
(
- "Karsh Kale-style tabla electronica β driving tabla breakbeat, sitar riff "
- "with synth pad, sub bass, wordless male vocal textures, 100 BPM. Indian-world fusion."
+ "Jamaica meets Sahara dub β Kingston one-drop sub bass, kora accents, "
+ "wordless Sahel male chant, tape delay, Mungo's Hi Fi warmth, 92 BPM."
),
(
- "Transglobal Underground dervish-trance β hypnotic ethnic breakbeat, oud and "
- "synth swirl, wordless call-and-response vocals, dub delay, 96 BPM pulse."
+ "Mongolia overtone electronica β wordless throat-singing texture layer "
+ "over Bonobo lush synth pad, tabla pulse, sub bass, 94 BPM ethereal blend."
),
(
- "Milan-style fusion dub β tabla and sitar over downtempo break, male wordless "
- "vocal hums, bass pulse, spring reverb, Thievery Corporation lounge energy."
+ "Urdu-world Bonobo dub β male poetic Urdu vocal colour over downtempo "
+ "electronica, tabla, dub delay, NOT Bollywood pop, 90 BPM beautiful."
),
(
- "Sahel meets Asian underground β kora and tabla together, deep sub bass, "
- "wordless griot-style chants, hand percussion, 94 BPM. No Turkish instruments lead."
+ "Jon Hopkins meets Jamaica β arpeggiated synth build, reggae-dub bass, "
+ "spring reverb, hand percussion, euphoric forward pulse 110 BPM."
),
(
- "Ethiopian-jazz world breaks β krar, breathy wordless vocals, piano, synth pad, "
- "tabla accents, sub bass, 90 BPM. Pan-ethnic not national."
+ "Sahara Sahel techno-ethnic β desert kora and talking drum with electronic "
+ "kick, wordless griot chant, Bonobo melodic warmth, 95 BPM."
),
(
- "Persian-Sahel lounge fusion β oud and tar with sitar colour, baritone wordless "
- "vocal, tabla groove, hand drums, cinematic ether 92 BPM."
+ "Karsh Kale Milan energy β tabla breakbeat, sitar-synth hook, sub bass, "
+ "Urdu wordless alaap texture blended in, 100 BPM."
),
(
- "Balkan-brass world breaks β female vocalise, muted trumpet, tabla and darbuka "
- "blend, sub bass, 98 BPM dark danceable mix."
- ),
- (
- "Atlantic-Desert electronica β soft wordless vocals, fingerpicked warmth, "
- "tabla pulse, oceanic reverb, sub bass fusion lounge 88 BPM."
- ),
- (
- "Sahel-Asian underground fusion β kora and tabla with synth arpeggio, "
- "wordless male chants, sub bass 93 BPM. No Rajasthani folk or sarangi pop."
- ),
- (
- "Desert caravan dub β Bambara wordless chants, kora and oud, tabla breakbeat "
- "from bar one, sub bass, NO baΔlama or ney as lead, 95 BPM."
- ),
- (
- "Mediterranean dubtronica mix β bouzouki colour as accent only, tabla groove, "
- "wordless vocals, sub bass, 92 BPM. Not country-specific."
- ),
- (
- "Kurdish-Sahel storytelling dub β tanbur drone as texture, tabla pulse, "
- "wordless narrative vocal, synth pad, 91 BPM cross-cultural."
- ),
- (
- "Nordic-desert ether fusion β breathy female wordless vocals, piano, synth, "
- "tabla accents, sub bass, cold cinematic dub 90 BPM."
- ),
- (
- "East-West break fusion β sitar hook with tabla breakbeat at 96 BPM, "
- "synth arpeggio, wordless vocal hum. No shamisen or enka slow-burn."
- ),
- (
- "Indo-dub lounge β female wordless alaap, tabla and sitar, synth bass, "
- "Karsh Kale energy, 97 BPM. Fusion not Bollywood-only."
- ),
- (
- "Synth-pad world breaks β tabla groove, wordless male chant, sub bass, "
- "96 BPM forward pulse. No duduk hymn or slow sacred woodwind lead."
+ "Transglobal dervish-trance β hypnotic ethnic breakbeat, oud swirl, "
+ "wordless vocals, dub space, 98 BPM danceable."
),
(
"Chac's Dub successor β ceremonial world dub, clay flute ritual chants, "
- "baritone chest voice, darbuka, sub bass, temple-step energy 85 BPM."
+ "baritone chest voice, darbuka, sub bass, Bonobo spacious mix 85 BPM."
),
(
- "Frostbite / Gregorian-ether dub β choral wordless female vocals, piano, "
- "sub bass, cinematic gothic cold warmth, sacred choral through dub filter 90 BPM."
+ "Frostbite Gregorian-ether β choral wordless vocals, piano, sub bass, "
+ "cinematic gothic through Bonobo-style electronic warmth 90 BPM."
),
(
- "Emerald Rush energy β driving Jon Hopkins electronica, pulsing synth arpeggio, "
- "organic percussion layer, immersive forward momentum, sub bass, 120 BPM. "
- "Electronic-first with subtle world accents."
+ "Jamaica-Mongolia fusion β dub bass and spring reverb, overtone throat "
+ "texture as ethereal pad, organic hand drums, lush synth, 93 BPM."
),
(
- "Atlas-Indian dub β oud and sitar blended, tabla driving, wordless chant, "
- "darbuka accents, sub bass 96 BPM. Multi-region not Turkish."
+ "Urdu-Sahel Bonobo lounge β poetic male Urdu hum, kora sparkle, "
+ "downtempo electronica, dub echo, beautiful late-night 88 BPM."
),
(
- "Afro-Asian highlife-dub fusion β wordless call-and-response vocals, clean "
- "plucked strings, talking drum with tabla, ney as light accent only, 94 BPM."
+ "Ethiopian-jazz techno-ethnic β krar, breathy vocals, piano, Bonobo synth "
+ "pad, forward pulse 91 BPM."
+ ),
+ (
+ "Persian-Sahel Bonobo blend β oud and tar with lush electronica, wordless "
+ "baritone, tabla groove, 94 BPM."
+ ),
+ (
+ "Balkan-brass world breaks β female vocalise, muted trumpet, tabla, "
+ "sub bass, 98 BPM dark beautiful mix."
+ ),
+ (
+ "Indo-dub Bonobo β female wordless alaap, tabla, sitar colour, "
+ "lush melodic synth, 97 BPM not pop."
+ ),
+ (
+ "Ceremonial techno-ethnic β ritual wordless chants, clay flute, "
+ "Bonobo downtempo pulse, deep sub, 92 BPM."
+ ),
+ (
+ "Emerald Rush energy β driving Hopkins electronica, Jamaica dub bass, "
+ "Sahel percussion layer, 118 BPM immersive."
+ ),
+ (
+ "Atlas-Indian dub banger β oud and sitar with tabla, wordless chant, "
+ "Bonobo beauty, punchy sub 96 BPM."
+ ),
+ (
+ "Pan-world Bonobo finale β Jamaica dub + Sahara kora + Mongolia overtone "
+ "texture + Urdu vocal colour + lush synths, 95 BPM gorgeous blend."
),
]
diff --git a/src/ozan_radio/dj.py b/src/ozan_radio/dj.py
index 59eaf00..f0b4575 100644
--- a/src/ozan_radio/dj.py
+++ b/src/ozan_radio/dj.py
@@ -45,14 +45,13 @@ Your job:
"melodic call-and-response". Avoid "vocal sample" / "griot sample" phrasing.
9. Read listener curation metadata β clone what they loved, hard-avoid what they disliked
(e.g. fuzz electric guitar on vocal tracks, long guitar-only intros before saz).
-10. Positive references: Jon Hopkins Singularity (modern electronica meets organic
- world β lush synth arpeggios, euphoric builds, immersive pulse); Karsh Kale (Milan);
- Transglobal Underground (Dervish Trans). Electronic production leads, ethnic colour woven in. Also clone keepers by genre:
- ceremonial-dub (Chac's Dub), gregorian-ether / cinematic-gothic (Frostbite Dub).
-11. Listener likes ETHNIC FUSION MIXES but NOT TURKISH β no Ney Vakti, no Anatolian
- folk, no Ottoman court, no baΔlama/saz as the lead identity, no Turkish vocals.
- Blend Sahel, Indian fusion, Middle Eastern accents, and dub. Vocals: wordless
- textures only, not national folk traditions.
+10. Station mission: TECHNO-ETHNIC β beautiful electronica meets world dub. Big
+ inspirations: Bonobo (lush melodic downtempo), Jon Hopkins Singularity, Jamaica
+ dub (spring reverb, bass culture), Sahara/Sahel warmth, Mongolian overtone throat
+ textures (wordless ethereal layer), Urdu poetic vocal colour. Blend many cultures
+ in one gorgeous track β Jamaica + Sahara + Mongolia + Urdu + synth beauty.
+11. NOT Turkish folk, NOT slow country homages, NOT Indian pop. Clone keepers:
+ ceremonial-dub (Chac's Dub), gregorian-ether (Frostbite Dub). Forward tempo 88+ BPM.
Respond with JSON only:
{
diff --git a/src/ozan_radio/server.py b/src/ozan_radio/server.py
index 3697b3b..70dcc5b 100644
--- a/src/ozan_radio/server.py
+++ b/src/ozan_radio/server.py
@@ -3,6 +3,7 @@ from __future__ import annotations
import asyncio
import json
import random
+from datetime import datetime, timezone
from pathlib import Path
from fastapi import BackgroundTasks, FastAPI, HTTPException
@@ -35,7 +36,15 @@ app.add_middleware(
_config: Config | None = None
_queue: RadioQueue | None = None
_generating = False
-_generation_state: dict = {"busy": False, "phase": None, "error": None, "track_title": None}
+_generation_state: dict = {
+ "busy": False,
+ "phase": None,
+ "error": None,
+ "track_title": None,
+ "last_completed_at": None,
+ "last_completed_title": None,
+ "last_completed_id": None,
+}
_chat = ChatStore()
@@ -101,14 +110,29 @@ def _play_library_entry(q: RadioQueue, cfg: Config, entry: dict) -> dict:
return {"status": "ok", "source": "library", "track": np.__dict__ if np else None}
-def _set_generation(*, busy: bool, phase: str | None = None, error: str | None = None, title: str | None = None) -> None:
+def _set_generation(
+ *,
+ busy: bool,
+ phase: str | None = None,
+ error: str | None = None,
+ title: str | None = None,
+ completed_id: str | None = None,
+) -> None:
global _generation_state
- _generation_state = {
+ state = {
"busy": busy,
"phase": phase,
"error": error,
"track_title": title,
+ "last_completed_at": _generation_state.get("last_completed_at"),
+ "last_completed_title": _generation_state.get("last_completed_title"),
+ "last_completed_id": _generation_state.get("last_completed_id"),
}
+ if completed_id and title:
+ state["last_completed_at"] = datetime.now(timezone.utc).isoformat()
+ state["last_completed_title"] = title
+ state["last_completed_id"] = completed_id
+ _generation_state = state
async def _compose_track(request: str | None = None, *, check_limit: bool = True) -> dict:
@@ -138,12 +162,17 @@ async def _compose_track(request: str | None = None, *, check_limit: bool = True
_set_generation(busy=True, phase="composing", title=plan.title)
track = LyriaEngine(cfg).generate(plan)
q.add(track)
+ q.play_id(track.plan.id)
rs = load_radio_settings(lyria_model=cfg.lyria_model)
cost = cost_per_track(cfg.lyria_model, rs.costs.__dict__)
record_generation(cfg.output_dir, cost, track.plan.id, track.plan.title)
np = q.now_playing()
_, budget = _can_generate_today(cfg)
- _set_generation(busy=False)
+ _set_generation(
+ busy=False,
+ title=track.plan.title,
+ completed_id=track.plan.id,
+ )
return {
"status": "ok",
"source": "generated",
@@ -262,8 +291,30 @@ async def patch_settings(body: SettingsPatch) -> dict:
@app.post("/api/generate")
-async def generate_track() -> dict:
- return await _compose_track()
+async def generate_track(background: BackgroundTasks) -> dict:
+ """Start generation in background β poll /api/stats for progress and ready state."""
+ cfg = _get_config()
+ if _generating:
+ return {
+ "status": "busy",
+ "message": "Already generating a track",
+ "generation": _generation_state,
+ }
+ ok, budget = _can_generate_today(cfg)
+ if not ok:
+ return {
+ "status": "limit",
+ "message": f"Daily limit reached ({budget['max_per_day']} new songs)",
+ "budget": budget,
+ }
+ background.add_task(_background_compose, None)
+ return {
+ "status": "accepted",
+ "generating": True,
+ "message": "Generating β planning then Lyria compose (~30β90s)",
+ "budget": budget,
+ "generation": _generation_state,
+ }
@app.post("/api/shuffle/next")
diff --git a/taste_seeds.json b/taste_seeds.json
index d4d1bc5..7bdb95d 100644
--- a/taste_seeds.json
+++ b/taste_seeds.json
@@ -9,64 +9,61 @@
"Your Top Songs 2025"
],
"artists": [
+ "Bonobo",
+ "Jon Hopkins",
"Karsh Kale",
"Transglobal Underground",
"Baaba Maal",
"Thievery Corporation",
+ "Mungo's Hi Fi",
"islandman",
"Kaya Project",
- "Blanco White",
"Shye Ben Tzur",
"Anoushka Shankar",
- "Jon Hopkins",
"Glass Beams",
"Francis Bebey",
- "Mungo's Hi Fi",
"Matthew Halsall"
],
"albums": [
- { "title": "Garip", "artist": "AltΔ±n GΓΌn", "vibe": "Anadolu psych-folk, baΔlama, fuzzy warmth" },
+ { "title": "Migration", "artist": "Bonobo", "vibe": "lush melodic downtempo, beautiful ethnic electronica, organic samples" },
+ { "title": "Singularity", "artist": "Jon Hopkins", "vibe": "modern electronica meets organic world, euphoric builds" },
{ "title": "Estuaire", "artist": "Ablaye Cissoko", "vibe": "kora, West African elegance, spacious" },
{ "title": "Mirage", "artist": "Glass Beams", "vibe": "psychedelic instrumental, Middle Eastern tint" },
- { "title": "African Electronic Music 1975-1982", "artist": "Francis Bebey", "vibe": "early African electronic, playful hypnotic" },
- { "title": "Singularity", "artist": "Jon Hopkins", "vibe": "modern electronica meets organic world β lush synth builds, immersive pulse, euphoric cinematic" }
+ { "title": "African Electronic Music 1975-1982", "artist": "Francis Bebey", "vibe": "early African electronic, playful hypnotic" }
],
- "preference": "Modern electronica meets ethnic fusion β Jon Hopkins Singularity, Karsh Kale, Dervish Trans. Turkish folk no.",
+ "preference": "Techno-ethnic β Bonobo-beautiful electronica meets Jamaica dub, Sahara, Mongolia overtone vocals, Urdu colour. Cross-cultural blends, not single-nation folk.",
"reference_tracks": [
- { "title": "Singularity", "artist": "Jon Hopkins", "vibe": "lush arpeggiated synths, organic percussion, euphoric builds, modern electronic meets world" },
- { "title": "Emerald Rush", "artist": "Jon Hopkins", "vibe": "driving hypnotic electronica, pulsing bass, immersive forward momentum" },
+ { "title": "Kerala", "artist": "Bonobo", "vibe": "lush downtempo, beautiful world electronica" },
+ { "title": "Singularity", "artist": "Jon Hopkins", "vibe": "lush synths, organic percussion, euphoric electronic meets world" },
+ { "title": "Milan", "artist": "Karsh Kale", "vibe": "tabla electronica, sitar-synth fusion" },
{ "title": "Dervish Trans", "artist": "Transglobal Underground", "vibe": "dervish trance breaks, hypnotic ethnic pulse" },
- { "title": "Milan", "artist": "Karsh Kale", "vibe": "tabla electronica, sitar-synth fusion, driving world breakbeat" }
+ { "title": "Boboyillo", "artist": "Baaba Maal", "vibe": "Sahara Sahel warmth, rolling desert pulse" }
+ ],
+ "cultural_palette": [
+ "Jamaica β dub spring reverb, reggae-dub bass culture",
+ "Sahara / Sahel β desert warmth, kora, griot colour",
+ "Mongolia β overtone throat singing as ethereal wordless layer",
+ "Urdu β poetic male vocal colour in fusion (not Bollywood pop)",
+ "and more β always blended, always beautiful"
],
"genres": [
+ "techno-ethnic",
+ "bonobo-style downtempo",
+ "ethnic dub",
+ "jamaican dub",
+ "sahel electronica",
"modern electronica",
- "ambient techno meets world",
- "ethnic fusion",
- "ethnic chill",
"dubtronica",
"world dub",
"trip-hop",
- "global fusion jazz",
- "afro-dub",
- "cinematic electronic",
- "psychedelic world"
+ "global fusion"
],
"tracks": [
{
"title": "Boboyillo",
"artists": ["Baaba Maal", "Rougi"],
"album": "Being",
- "vibe": "Sahel warmth, rolling desert pulse, call-and-response, spiritual but danceable"
- },
- {
- "title": "Olalla",
- "artists": ["Blanco White"],
- "vibe": "melancholic indie folk, Spanish ether, intimate late-night"
- },
- {
- "title": "Daily Mix 01",
- "artists": ["BALTHVS", "Pentagram", "Cem Karaca"],
- "vibe": "Turkish rock and psych crossover"
+ "vibe": "Sahara Sahel warmth, rolling desert pulse, spiritual but danceable"
},
{
"title": "Daily Mix 03",
@@ -76,7 +73,7 @@
{
"title": "Daily Mix 04",
"artists": ["Shye Ben Tzur", "Anoushka Shankar"],
- "vibe": "Indian classical fusion, devotional texture, world spiritual"
+ "vibe": "Urdu-Indian fusion, devotional texture, world spiritual β not pop"
}
]
}