READY page: song keys, Waves Tune, live routing and in-ear sends

- SONG KEYS: each song's PCO key against the set's locator, the pad under it and Finished Songs
- W TUNE: the Waves Tune clip starting each song is the PCO key (and sends that key's program)
- ROUTING / IN EARS: read live over AbletonOSC (then handed back to Companion); the hold, and
  auto-fix in its hours, put stems back on the routing template (from 261025 NA Project) and
  raise in-ear sends at -inf to 0 dB

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-22 10:03:49 -04:00
co-authored by Claude Opus 5.5
parent 51a14140cc
commit f179df59f1
8 changed files with 923 additions and 21 deletions
+181 -3
View File
@@ -256,6 +256,184 @@ class TestAbleton(unittest.TestCase):
self.assertEqual(json.loads(params)["date"], "261018")
FINISHED = "/Drive/Ableton/Finished Songs"
def keyed(keys):
"""plan() with PCO keys on its songs ({title: key})."""
p = plan()
for it in p["items"]:
it["key"] = keys.get(it["title"], "")
return p
def with_pads_and_finished(al=None):
"""The set with the real pads of 261025 (named by key) under its songs, and a slice of Finished Songs."""
al = al or ableton()
al["summary"]["pads"] = [[0, 95, "A# - WARM - CHURCHFRONT PADS"], [95, 196, "D# - WARM - CHURCHFRONT PADS"],
[196, 300, "B - WARM - CHURCHFRONT PADS"], [300, 400, "B - WARM - CHURCHFRONT PADS"]]
al["finished"] = {FINISHED: ["CAN'T HELP BUT PRAISE LM [Eb]", "CAN'T HELP BUT PRAISE [D]", "CAN'T HELP BUT PRAISE [Eb]",
"COVERED BY THE BLOOD [B]", "HERE AGAIN UNFINISHED [C]", "HOLY FOREVER [B]",
"REJOICE AGAIN [B]", "zHOLY FOREVER [F]"]}
return al
GOOD = {"Rejoice Again": "Bb", "Can't Help But Praise (Live)": "Eb", "Holy Forever": "B", "Covered By The Blood TAG": "B"}
class TestKeys(unittest.TestCase):
def test_key_names(self):
self.assertTrue(rd.same_key("Eb", "D#"))
self.assertTrue(rd.same_key("Bb", "A#"))
self.assertFalse(rd.same_key("B", "Bb"))
self.assertFalse(rd.same_key("Em", "E"))
self.assertTrue(rd.same_key("Em", "E", mode=False))
self.assertIsNone(rd.key_of("LOOP"))
def test_every_song_in_its_key(self):
al = with_pads_and_finished()
al["finished"][FINISHED].append("REJOICE AGAIN [Bb]")
line = rd.eval_keys(RC, keyed(GOOD), al)
self.assertEqual(msgs(line), [])
self.assertEqual(line.info, "4 KEYS OK")
def test_the_set_in_another_key(self):
keys = dict(GOOD, **{"Holy Forever": "C"})
line = rd.eval_keys(RC, keyed(keys), with_pads_and_finished())
# Rejoice Again: Finished Songs only has it in B (PCO and the set say Bb)
self.assertEqual(msgs(line), ["REJOICE AGAIN ONLY B", "HOLY FOREVER B NOT C", "HOLY FOREVER PAD B NOT C",
"HOLY FOREVER ONLY B"])
self.assertEqual([lv for lv, _, _ in line.faults], [rd.WARN, rd.ERR, rd.ERR, rd.WARN])
def test_a_song_pco_has_no_key_for(self):
line = rd.eval_keys(RC, keyed(dict(GOOD, **{"Holy Forever": ""})), with_pads_and_finished())
self.assertIn("NO KEY IN PCO HOLY FOREVER", msgs(line))
def test_nothing_to_check_a_key_by(self):
al = ableton()
al["summary"]["locators"] = [{"name": "HOLY FOREVER", "time": 200}]
al["summary"]["tracks"] = [t for t in al["summary"]["tracks"] if t["id"] in ("30", "31")]
line = rd.eval_keys(RC, keyed(GOOD), al)
self.assertEqual(msgs(line), ["KEY UNCHECKED REJOICE AGAIN", "KEY UNCHECKED CANT HELP BUT P.",
"KEY UNCHECKED HOLY FOREVER", "KEY UNCHECKED COVERED BY THE."])
def test_a_song_missing_from_finished_songs_is_only_noted(self):
al = with_pads_and_finished()
al["finished"][FINISHED].remove("HOLY FOREVER [B]")
line = rd.eval_keys(RC, keyed(GOOD), al)
self.assertNotIn("HOLY FOREVER", " ".join(msgs(line)))
self.assertIn("Holy Forever is not in Finished Songs", line.as_dict()["details"])
def test_no_set(self):
self.assertEqual(msgs(rd.eval_keys(RC, keyed(GOOD), {"set": None})), ["NO SET 261018"])
def live(name, index, grouped=True, type_="Ext. Out", channel="5/6 Keys", send=1.0, group=False, audio=True):
return {"name": name, "index": index, "group": group, "grouped": grouped, "audio": audio, "type": type_,
"channel": channel, "send": send}
def live_set(**over):
"""What AbletonOSC said about 261025 NA Project on 2026-10-22, trimmed: a song dragged in last (its stems'
in-ear sends at -inf, one routed to Master) and a new stem name."""
tracks = [
live("TEMPO", 0, False, "Master", "", 0.0),
live("LTC", 1, False, "Ext. Out", "16 LTC", 0.0),
live("MIDI GROUP", 2, False, group=True),
live("WAVES TUNE", 3, audio=False),
live("RESI TRACKS", 4, False, group=True),
live("RESI TRACKS", 5, True, "Ext. Out", "3/4 IN-EARS", 0.0),
live("RESI KEYS", 6, True, "RESI TRACKS", "", 0.0),
live("AMBIENT PADS", 7, False, "Ext. Out", "5/6 Keys", 0.36),
live("CAN'T HELP BUT PRAISE", 8, False, group=True),
live("21-Piano", 9),
live("22-Piano 2", 10, send=0.75),
live("29-EG 1", 11, channel="7/8 GTRS", send=0.44),
live("JEHOVAH", 12, False, group=True),
live("50-Keys 1 [2026-10-21 132128]", 13, send=0.0),
live("56-EG 13 [2026-10-21 132128]", 14, channel="7/8 GTRS", send=0.0),
live("62-Bass [2026-10-21 132128]", 15, True, "Master", "", 0.0),
live("70-Banjo [2026-10-21 132128]", 16, channel="7/8 GTRS", send=0.5),
]
out = {"playing": False, "tracks": tracks}
out.update(over)
return out
class TestAbletonLive(unittest.TestCase):
def test_routing_and_in_ears(self):
routing, ears, changes = rd.mix_plan(RC, live_set())
self.assertEqual(msgs(routing), ["BASS TO MASTER", "NEW STEM BANJO"])
self.assertEqual(msgs(ears), ["EARS OFF JEHOVAH"])
self.assertIn("50-Keys 1 [2026-10-21 132128] in JEHOVAH: send A is at -inf", ears.as_dict()["details"])
# EG 13 is not in the template, but every EG goes to 7/8 GTRS; RESI and top-level tracks keep their -inf sends
self.assertEqual(changes, [
{"index": 13, "name": "50-Keys 1 [2026-10-21 132128]", "send": 1.0},
{"index": 14, "name": "56-EG 13 [2026-10-21 132128]", "send": 1.0},
{"index": 15, "name": "62-Bass [2026-10-21 132128]", "type": "Ext. Out", "channel": "7/8 GTRS", "send": 1.0}])
self.assertEqual(ears.info, "4 SENDS ON")
def test_stem_names(self):
self.assertEqual(rd.stem_name("50-Keys 1 [2026-10-21 132128]"), "KEYS 1")
self.assertEqual(rd.stem_name("105-Drums (Live)"), "DRUMS (LIVE)")
self.assertEqual(rd.template_for(RC["routing"], "88-Drums (Live) [2026-10-19 132517]"), ["Ext. Out", "9/10 PERC / FX"])
self.assertIsNone(rd.template_for(RC["routing"], "Banjo"))
def test_ableton_not_answering(self):
routing, ears, changes = rd.mix_plan(RC, {"error": "ABLETON NOT ANSWERING"})
self.assertEqual((msgs(routing), msgs(ears), changes), (["ABLETON NOT ANSWERING"], ["ABLETON NOT ANSWERING"], []))
def test_the_live_script_runs(self):
for apply in (None, [{"index": 1, "name": "x", "send": 1.0}]):
compile(rd.LIVE_SCRIPT % json.dumps({"send_index": 0, "apply": apply}), "remote", "exec")
def test_the_hold_reads_then_sets(self):
calls = []
def fake(cfg, rc, apply=None):
calls.append(apply)
return live_set() if apply is None else {"done": [c["name"] for c in apply], "skipped": []}
real, rd.live_look = rd.live_look, fake
try:
self.assertTrue(rd.run_action(CONFIG, "ableton_mix"))
finally:
rd.live_look = real
self.assertEqual([None if c is None else len(c) for c in calls], [None, 3])
def test_osc_hand_back_message(self):
self.assertEqual(rd.osc_message("/live/song/get/tempo"), b"/live/song/get/tempo\0\0\0\0,\0\0\0")
def with_tune(al=None):
al = al or ableton()
al["summary"]["tune"] = [[0, 1, "Bbmaj", 2], [100, 101, "Ebmaj", 7], [200, 201, "Bmaj", 3], [300, 301, "Emaj", 8]]
al["tune_library"] = {"Bbmaj": 2, "Bmaj": 3, "Ebmaj": 7, "Emaj": 8, "OFF | Chromatic": 0, "LOOP CUE": None}
return al
class TestWavesTune(unittest.TestCase):
def test_clip_names(self):
self.assertEqual([rd.tune_key(n) for n in ("Ebmaj", "C#min", "Bmaj", "OFF | Chromatic", "LOOP CUE")],
["Eb", "C#m", "B", None, None])
self.assertTrue(rd.same_tune("G", "Em"))
self.assertFalse(rd.same_tune("E", "Em"))
def test_each_song_on_its_key(self):
line = rd.eval_tune(RC, keyed(GOOD), with_tune())
# Covered By The Blood is in B, but its song starts on the Emaj clip
self.assertEqual(msgs(line), ["COVERED BY TH. TUNE E"])
self.assertIn("put the Bmaj clip there instead", line.as_dict()["details"][0])
self.assertEqual(line.info, "3 SONGS IN KEY")
def test_a_renamed_clip_sends_the_wrong_program(self):
al = with_tune()
al["summary"]["tune"][2][3] = 8 # named Bmaj, sends Emaj's program
self.assertIn("HOLY FOREVER TUNE PGM", msgs(rd.eval_tune(RC, keyed(GOOD), al)))
def test_no_clips(self):
self.assertEqual(msgs(rd.eval_tune(RC, keyed(GOOD), ableton())), ["NO TUNE CLIPS"])
class TestTimecode(unittest.TestCase):
def test_seconds_through_the_tempo_map(self):
tempo = ableton()["summary"]["tempo"]
@@ -361,7 +539,7 @@ class TestLook(unittest.TestCase):
src = dict(pco=plan, prop=lambda p: prop(), ableton=lambda p: ableton(),
lineup=lambda: lineup(["Jonah Perdue", "Krista Perdue", "Kevin Michel", "Anthony Hoyt", "Tim Hatch"]),
resolume=composition, prop_bundle=lambda plan: [["Message Templates.probundle", WEDNESDAY]],
macros=macros_found, companion=lambda n: COMPANION.get(n, {}))
macros=macros_found, companion=lambda n: COMPANION.get(n, {}), live=live_set)
src.update(over)
return src
@@ -389,7 +567,7 @@ class TestLook(unittest.TestCase):
cfg["ready"].pop(k)
cfg["ready"]["prop_url"] = ""
r = rd.look(cfg, now=WEDNESDAY, sources=self.sources())
self.assertEqual(sorted(r["items"]), ["lights", "mics", "mix_foh", "timecode", "tracks"]) # LTC: tracks vs PCO
self.assertEqual(sorted(r["items"]), ["ears", "keys", "lights", "mics", "mix_foh", "routing", "timecode", "tracks", "tune"]) # LTC: tracks vs PCO
engine, snap = sp.Engine(cfg, None), sp.Snapshot()
snap.ready = r
tiles = engine.step(snap, WEDNESDAY)
@@ -445,7 +623,7 @@ class TestWatchdog(unittest.TestCase):
self.step(WEDNESDAY)
plan = self.engine.fix_plan("ready", self.snap)
fixes = sorted(what for _, kind, what, _ in plan if kind == "fix")
self.assertEqual(fixes, ["ready_copy_tracks", "ready_led_cross", "ready_lineup", "ready_ltc", "ready_recheck"])
self.assertEqual(fixes, ["ready_ableton_mix", "ready_copy_tracks", "ready_led_cross", "ready_lineup", "ready_ltc", "ready_recheck"])
ran = []
self.engine.executor = lambda cfg, fix_id, chk: ran.append(fix_id) or True
self.engine.run_fixes("ready", self.snap, None, WEDNESDAY + 10)