From 7ff8b42e90989206da128f4aa907b755ab8535b5 Mon Sep 17 00:00:00 2001 From: Waters Church Date: Wed, 2 Sep 2026 16:06:53 -0400 Subject: [PATCH] ltc-reader: pick the LTC channel out of a multichannel capture LTC usually rides one input of a console bus or Dante/MADI interface, and downmixing it into program audio (-ac 1) corrupts the bit transitions. New flags --channels (interleaved count of the stdin stream, no practical upper bound) and --ch (1-based pick) deinterleave in the reader, so the capture command stays a plain passthrough. The extractor keeps frame alignment across arbitrary chunk splits; selftest adds an LTC-on-5-of-8 case fed in odd-sized chunks. Mono remains the default. Co-Authored-By: Claude Fable 5 --- README.md | 8 ++-- tools/ltc-reader.js | 106 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 95 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index c207401..64bd454 100644 --- a/README.md +++ b/README.md @@ -100,9 +100,11 @@ are written by the app itself. machine that can hear the LTC signal; it decodes the SMPTE frames from a PCM pipe (ffmpeg/sox one-liners in its header) and POSTs running/stopped/no-signal, the timecode, and the frame rate (incl. - 29.97 drop-frame) to the module's `/ltc` ingest route. `--selftest` - checks the decoder; `--demo` feeds synthetic LTC end-to-end with no - audio hardware. + 29.97 drop-frame) to the module's `/ltc` ingest route. LTC on one + input of a multichannel interface: capture all channels and pick one + with `--channels 18 --ch 5` — never downmix LTC into program audio. + `--selftest` checks the decoder; `--demo` feeds synthetic LTC + end-to-end with no audio hardware. 2. A stage-display layout field labeled "LTC", read over ProPresenter's stage websocket — value and freshness only, no frame rate. - **Clock** — a big booth clock; also the smallest possible module and the diff --git a/tools/ltc-reader.js b/tools/ltc-reader.js index b1bf21f..1ac4408 100644 --- a/tools/ltc-reader.js +++ b/tools/ltc-reader.js @@ -6,10 +6,10 @@ * * ProPresenter's APIs expose no timecode (see modules/propresenter-timers), * so this tool reads LTC where it actually lives: the audio signal. Pipe - * mono PCM (signed 16-bit little-endian) into stdin from any capture - * front-end; the decoded state is POSTed to the Timers module's /ltc - * ingest route — ~4×/s while running, 1×/s as a keepalive otherwise, and - * immediately on every state change. + * PCM (signed 16-bit little-endian) into stdin from any capture front-end; + * the decoded state is POSTed to the Timers module's /ltc ingest route — + * ~4×/s while running, 1×/s as a keepalive otherwise, and immediately on + * every state change. * * macOS (ffmpeg; list input devices with * `ffmpeg -f avfoundation -list_devices true -i ""`): @@ -21,11 +21,23 @@ * ffmpeg -loglevel error -f dshow -i audio="" -ac 1 \ * -ar 48000 -f s16le - | node tools/ltc-reader.js * + * LTC on one input of a multichannel interface (a console bus, a Dante/ + * MADI card, input 5 of a Scarlett 18i20…): do NOT downmix with -ac 1 — + * summing LTC with program audio corrupts the bit transitions. Capture the + * channels as they are and tell the reader which one carries LTC (1-based): + * ffmpeg -loglevel error -f avfoundation -i ":0" -ar 48000 -f s16le - \ + * | node tools/ltc-reader.js --channels 18 --ch 5 + * (--channels must match what the capture actually emits; ffmpeg keeps + * the device's native count unless -ac says otherwise, and prints it on + * stderr at startup. sox: match its -c flag.) + * * Flags: * --url ProdDash base URL (default http://127.0.0.1:24500) * --module target module id (default propresenter-timers) * --token matches the module's "LTC reader token" admin setting * --sr stdin sample rate (default 48000) + * --channels interleaved channels in the stdin stream (default 1) + * --ch which channel carries LTC, 1-based (default 1) * --source