SC timecode: LTC Listen.app decodes the tracks' LTC from SoundGrid; PROP shows TIMECODE LOST mid-song

ProPresenter can't report its timecode, so LTC Listen (a small app on SC's ProPresenter Mac, microphone
permission allowed on site 2026-10-26, LaunchAgent org.waterschurch.ltc-listen) reads the same SoundGrid
input (com_waves_WCAudioGridEngine, channel 1) and writes the running timecode. The prop guard logs a drop
in a song (2 s without timecode) with the song and timecode, and serves 127.0.0.1:8095/timecode, which SC's
PROP key checks (TIMECODE LOST). Decoder tested against generated LTC and live (00:27:37:28).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-26 14:22:07 -04:00
co-authored by Claude Opus 5.5
parent e191150f7a
commit a3b38cbd5d
5 changed files with 342 additions and 1 deletions
+34
View File
@@ -88,5 +88,39 @@ class TestGuard(unittest.TestCase):
self.assertEqual(self.fake.calls.count("/v1/presentation/active/timeline/play"), 1)
class TestTimecode(unittest.TestCase):
def test_timecode_lost_in_a_song_and_back(self):
fake, clock = FakeProP(), [1000.0]
fake.layout = "Worship"
orig_api, orig_ltc = pg.api, pg.Guard.ltc
ltc = {"running": True, "tc": "00:01:00:00"}
pg.api = fake
pg.Guard.ltc = lambda self: dict(ltc, time=clock[0])
try:
g = pg.Guard(now=lambda: clock[0])
def at(t):
clock[0] = 1000.0 + t
g.tick()
at(0)
at(5)
ltc["running"] = False # gone mid-song
at(6)
self.assertFalse(g.lost) # not yet 2 s
at(8.5)
self.assertTrue(g.lost)
self.assertEqual(g.state["ltc_drops"][0]["song"], "Jehovah")
ltc["running"] = True
at(10)
self.assertFalse(g.lost)
self.assertIn("back", g.state["ltc_drops"][0])
fake.song = None # between songs no timecode is fine
ltc["running"] = False
at(20)
at(30)
self.assertFalse(g.lost)
finally:
pg.api, pg.Guard.ltc = orig_api, orig_ltc
if __name__ == "__main__":
unittest.main()