READY: LIGHT TIMING (flash and FX cues against the drum hits); auto-fix on for testing

- LIGHT TIMING: every blinder flash / FX cue is timed against the Drums (Live) stem under it
  (deactivated clips count) on the Tracks Mac; cues a strong hit is more than 25 ms away from are named
  with song and bar
- Settings Overview: "Auto-fix on for testing" for 15 min to 2 h, outside the hours and over a pause;
  the AUTO-FIX key says TESTING, and it goes back to the hours by itself

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-22 10:18:46 -04:00
co-authored by Claude Opus 5.5
parent f179df59f1
commit d4e4092a9d
9 changed files with 300 additions and 19 deletions
+51 -1
View File
@@ -434,6 +434,56 @@ class TestWavesTune(unittest.TestCase):
self.assertEqual(msgs(rd.eval_tune(RC, keyed(GOOD), ableton())), ["NO TUNE CLIPS"])
def with_cues(al=None):
"""Flashes over Can't Help But Praise's drums (beats 100-190 of the fixture, at 151 BPM there)."""
al = al or ableton()
al["summary"]["tracks"].append(track("25", "42-Drums (Live)", group="20", clips=((100, 190, None),)))
al["summary"]["cues"] = [[104, 30], [104, 36], [108, 31], [112, 35], [116, 30], [120, 99]]
al["summary"]["hit_src"] = [["25", 100, 190, "/Projects/NA/261018 NA Project/MultiTracks/Drums (Live).wav", False, 0.0]]
al["light_names"] = {"30": "Blinder Flash 1", "31": "Blinder Flash 2", "35": "Blinder FX 1", "36": "Blinder FX 2"}
return al
class TestLightTiming(unittest.TestCase):
def test_cue_times_in_the_stem(self):
cues = rd.cue_times(RC, with_cues()["summary"])
self.assertEqual([(c[0], c[1], c[2]) for c in cues], [(104, 30, "CAN'T HELP BUT PRAISE"), (108, 31, "CAN'T HELP BUT PRAISE"),
(112, 35, "CAN'T HELP BUT PRAISE"), (116, 30, "CAN'T HELP BUT PRAISE")])
tempo = with_cues()["summary"]["tempo"]
self.assertAlmostEqual(cues[0][4], rd.seconds_at(tempo, 104) - rd.seconds_at(tempo, 100))
self.assertAlmostEqual(cues[0][5], 0.45 * 60 / 151 / 4, places=4) # under half a 16th at 151 BPM
def test_off_cues_are_named(self):
# [hit - cue (s), the hit's strength, strength under the cue]
hits = [[0.005, 5000, 5000], # on time
[0.037, 5200, 2000], # 37 ms early, a strong hit, little under the cue
[-0.053, 900, 300], # off, but a weak hit for this song: left alone
[0.040, 5100, 4800]] # off, but the cue already sits on a hit as strong: left alone
line = rd.eval_light_timing(RC, with_cues(), hits)
self.assertEqual(msgs(line), ["CUE OFF 28.1.1"])
self.assertEqual(line.as_dict()["details"], ["CAN'T HELP BUT PRAISE bar 28.1.1: Blinder Flash 2 is 37 ms before the hit"])
self.assertEqual(line.info, "3 CUES ON TIME")
def test_several_off(self):
hits = [[-0.04, 5000, 0], [0.037, 5200, 2000], [0.0, 5000, 5000], [0.0, 5000, 5000]]
line = rd.eval_light_timing(RC, with_cues(), hits)
self.assertEqual(msgs(line), ["2 CUES OFF"])
self.assertEqual(len(line.as_dict()["details"]), 2)
self.assertIn("Blinder Flash 1 is 40 ms after the hit", line.as_dict()["details"][0])
def test_a_stem_it_cannot_read(self):
line = rd.eval_light_timing(RC, with_cues(), ["unknown format: 3"] * 4)
self.assertEqual(msgs(line), [])
self.assertEqual(line.as_dict()["details"], ["could not read Drums (Live).wav"])
def test_bars_as_live_shows_them(self):
self.assertEqual(rd.bar_of(1315.75), "329.4.4")
self.assertEqual(rd.bar_of(0), "1.1.1")
def test_the_listener_runs(self):
compile(rd.HIT_SCRIPT % json.dumps({"cues": [["/x.wav", 1.5, 0.05]]}), "remote", "exec")
class TestTimecode(unittest.TestCase):
def test_seconds_through_the_tempo_map(self):
tempo = ableton()["summary"]["tempo"]
@@ -567,7 +617,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"]), ["ears", "keys", "lights", "mics", "mix_foh", "routing", "timecode", "tracks", "tune"]) # LTC: tracks vs PCO
self.assertEqual(sorted(r["items"]), ["ears", "keys", "light_timing", "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)