Files
status-indicators/tests/ltc_gen.py
T
christianandClaude Opus 5.5 a3b38cbd5d 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>
2026-10-26 14:22:07 -04:00

43 lines
1.5 KiB
Python

"""Writes a test LTC file (biphase mark, 30 fps, 48 kHz) for ltc_listen.swift's decoder test."""
import struct
import sys
import wave
def frame_bits(h, m, s, f):
bits = [0] * 80
def put(v, at, n):
for i in range(n):
bits[at + i] = (v >> i) & 1
put(f % 10, 0, 4); put(f // 10, 8, 2); put(s % 10, 16, 4); put(s // 10, 24, 3)
put(m % 10, 32, 4); put(m // 10, 40, 3); put(h % 10, 48, 4); put(h // 10, 56, 2)
bits[64:80] = [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1]
return bits
def write(path, start=(1, 2, 3, 4), seconds=2, rate=48000, fps=30, amp=0.5):
h, m, s, f = start
per_bit = rate / (fps * 80.0)
level, t, samples = 1, 0.0, []
for _ in range(seconds * fps):
for b in frame_bits(h, m, s, f):
level = -level # a transition at every bit start
half = per_bit / 2
for k in range(2):
if k == 1 and b:
level = -level # and one in the middle of a 1
t += half
while len(samples) < int(round(t)):
samples.append(level * amp)
f += 1
if f == fps:
f, s = 0, s + 1
w = wave.open(path, "wb")
w.setnchannels(1); w.setsampwidth(2); w.setframerate(rate)
w.writeframes(b"".join(struct.pack("<h", int(x * 32767)) for x in samples))
w.close()
if __name__ == "__main__":
write(sys.argv[1])