Spaces:
Running
Running
SP3 generative song cards
Browse files
proxy.py
CHANGED
|
@@ -495,6 +495,93 @@ def _templated_text(kind: str, ctx: Optional[Dict[str, Any]] = None) -> str:
|
|
| 495 |
return _pick(content.STATION_IDS) # station_id (default)
|
| 496 |
|
| 497 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 498 |
_FRAGMENT_FLAVORS = ["dedication", "numbers_weather", "station_id", "dream", "song_title"]
|
| 499 |
_FRAGMENT_HINT = {
|
| 500 |
"dedication": "a half-heard dedication to someone you will never name",
|
|
@@ -571,6 +658,9 @@ def segment_turn(kind: str, ctx: Optional[Dict[str, Any]] = None) -> Dict[str, A
|
|
| 571 |
" A listener, %s, sent this one in -- give them a warm little shout "
|
| 572 |
"as you bring it in." % rb
|
| 573 |
)
|
|
|
|
|
|
|
|
|
|
| 574 |
elif kind == "local_weather":
|
| 575 |
user = _local_weather_user(ctx)
|
| 576 |
else:
|
|
|
|
| 495 |
return _pick(content.STATION_IDS) # station_id (default)
|
| 496 |
|
| 497 |
|
| 498 |
+
# ---------------------------------------------------------------------------
|
| 499 |
+
# Generative song cards (SP3): model writes title+artist; server picks the vibe
|
| 500 |
+
# and assigns ALWAYS-VALID musical params so a card can never break the engine.
|
| 501 |
+
# ---------------------------------------------------------------------------
|
| 502 |
+
_VIBE_MUSIC = {
|
| 503 |
+
"melancholy": ("minor", "rhodes", 70, ("A", "E", "D")),
|
| 504 |
+
"lonely": ("minor", "music_box", 64, ("A", "E", "Bb")),
|
| 505 |
+
"tender": ("major", "music_box", 68, ("C", "F", "Bb")),
|
| 506 |
+
"warm": ("major", "rhodes", 78, ("C", "D", "G")),
|
| 507 |
+
"cozy": ("major", "rhodes", 80, ("F", "C", "Bb")),
|
| 508 |
+
"hopeful": ("lydian", "soft_saw", 80, ("D", "G", "C")),
|
| 509 |
+
"nostalgic": ("major", "triangle_pluck", 74, ("C", "Bb", "G")),
|
| 510 |
+
"wistful": ("dorian", "sine_pad", 72, ("C", "D", "F")),
|
| 511 |
+
"bittersweet": ("minor", "rhodes", 70, ("Bb", "A", "E")),
|
| 512 |
+
"jazzy": ("dorian", "rhodes", 84, ("D", "F", "G")),
|
| 513 |
+
"dreamy": ("lydian", "sine_pad", 64, ("F", "D", "Eb")),
|
| 514 |
+
"pensive": ("dorian", "sine_pad", 76, ("C", "E", "G")),
|
| 515 |
+
"hypnotic": ("pentatonic_minor", "soft_saw", 70, ("G", "A", "D")),
|
| 516 |
+
"mysterious": ("lydian", "sine_pad", 68, ("Eb", "A", "F")),
|
| 517 |
+
"eerie": ("pentatonic_minor", "sine_pad", 60, ("A", "Eb", "G")),
|
| 518 |
+
"ambient": ("lydian", "sine_pad", 64, ("F", "D", "C")),
|
| 519 |
+
"romantic": ("minor", "rhodes", 66, ("Ab", "D", "A")),
|
| 520 |
+
"gentle": ("major", "triangle_pluck", 75, ("C", "G", "D")),
|
| 521 |
+
"breezy": ("major", "triangle_pluck", 88, ("G", "D", "C")),
|
| 522 |
+
}
|
| 523 |
+
_DEFAULT_MUSIC = ("major", "rhodes", 74, ("C", "D", "G"))
|
| 524 |
+
_MOOD_TO_VIBE = {
|
| 525 |
+
"warm": "warm", "nostalgic": "nostalgic", "tender": "tender",
|
| 526 |
+
"searching": "pensive", "hollow": "lonely", "uneasy": "eerie",
|
| 527 |
+
}
|
| 528 |
+
|
| 529 |
+
|
| 530 |
+
def _parse_title_artist(text):
|
| 531 |
+
t = re.sub(r"\s+", " ", text or "").strip().strip("\"'")
|
| 532 |
+
t = re.sub(r"^(?:title|song)\s*[:=]\s*", "", t, flags=re.IGNORECASE)
|
| 533 |
+
parts = re.split(r"\s+by\s+", t, maxsplit=1, flags=re.IGNORECASE)
|
| 534 |
+
if len(parts) != 2:
|
| 535 |
+
return None, None
|
| 536 |
+
title = parts[0].strip().strip("\"'")[:42]
|
| 537 |
+
# Drop trailing model chatter ("... by The Velvet Sundays. Hope you enjoy!")
|
| 538 |
+
# at the first real sentence break -- a period after >=4 letters then space --
|
| 539 |
+
# so abbreviations like "St. Vincent" survive.
|
| 540 |
+
artist = re.split(r"(?<=[A-Za-z]{4})\.\s", parts[1].strip().strip("\"'"), maxsplit=1)[0].strip()[:42]
|
| 541 |
+
if 2 <= len(title) <= 42 and 2 <= len(artist) <= 42 and not any(c in t for c in "<>{}[]") and ":" not in title:
|
| 542 |
+
return title, artist
|
| 543 |
+
return None, None
|
| 544 |
+
|
| 545 |
+
|
| 546 |
+
def _procedural_title():
|
| 547 |
+
return "%s %s" % (random.choice(content.CARD_TITLE_A), random.choice(content.CARD_TITLE_B))
|
| 548 |
+
|
| 549 |
+
|
| 550 |
+
def _procedural_artist():
|
| 551 |
+
return "%s %s" % (random.choice(content.CARD_ARTIST_A), random.choice(content.CARD_ARTIST_B))
|
| 552 |
+
|
| 553 |
+
|
| 554 |
+
def _generate_title_artist(ctx, vibe):
|
| 555 |
+
try:
|
| 556 |
+
flavor = ""
|
| 557 |
+
if ctx.get("topic"):
|
| 558 |
+
flavor = " inspired by someone who said: %s" % str(ctx["topic"])[:80]
|
| 559 |
+
elif ctx.get("city"):
|
| 560 |
+
flavor = " for a night in %s" % ctx["city"]
|
| 561 |
+
user = ("Invent a fictional late-night record with a %s feeling%s. Reply with ONLY "
|
| 562 |
+
"the song title and the artist in the form: <title> by <artist>." % (vibe, flavor))
|
| 563 |
+
brain = call_brain(arc.build_song_card_prompt(), [{"role": "user", "content": user}])
|
| 564 |
+
title, artist = _parse_title_artist(brain.get("text", ""))
|
| 565 |
+
if title and artist:
|
| 566 |
+
return title, artist
|
| 567 |
+
except Exception:
|
| 568 |
+
pass
|
| 569 |
+
return _procedural_title(), _procedural_artist()
|
| 570 |
+
|
| 571 |
+
|
| 572 |
+
def make_song_card(ctx=None):
|
| 573 |
+
"""Invent ONE fictional record. Musical params are always valid engine enums."""
|
| 574 |
+
ctx = ctx or {}
|
| 575 |
+
vibe = _MOOD_TO_VIBE.get(ctx.get("mood")) or random.choice(list(_VIBE_MUSIC.keys()))
|
| 576 |
+
scale, timbre, tempo, keys = _VIBE_MUSIC.get(vibe, _DEFAULT_MUSIC)
|
| 577 |
+
title, artist = _generate_title_artist(ctx, vibe)
|
| 578 |
+
return {
|
| 579 |
+
"title": title, "artist": artist, "vibe": vibe,
|
| 580 |
+
"key": random.choice(keys), "scale": scale, "tempo": tempo, "timbre": timbre,
|
| 581 |
+
"recommended_by": None, "generated": True,
|
| 582 |
+
}
|
| 583 |
+
|
| 584 |
+
|
| 585 |
_FRAGMENT_FLAVORS = ["dedication", "numbers_weather", "station_id", "dream", "song_title"]
|
| 586 |
_FRAGMENT_HINT = {
|
| 587 |
"dedication": "a half-heard dedication to someone you will never name",
|
|
|
|
| 658 |
" A listener, %s, sent this one in -- give them a warm little shout "
|
| 659 |
"as you bring it in." % rb
|
| 660 |
)
|
| 661 |
+
if ctx.get("fresh"):
|
| 662 |
+
user += (" Mention warmly that this record is brand new -- pressed just "
|
| 663 |
+
"tonight, never heard before.")
|
| 664 |
elif kind == "local_weather":
|
| 665 |
user = _local_weather_user(ctx)
|
| 666 |
else:
|