ratandeep commited on
Commit
2e8c86f
·
verified ·
1 Parent(s): 8961e98

SP3 generative song cards

Browse files
Files changed (1) hide show
  1. proxy.py +90 -0
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: