Skip to content

Repository files navigation

Show_Timecode

Show time code tracking


🎬 Quick Start β€” Running the Show (Zero Tech Experience Needed)

This section is for anyone running the cue sheet who's never touched Docker, Terminal, or MIDI before. Just follow the steps in order, top to bottom. πŸ™‚

πŸ’‘ This walkthrough assumes the show computer is already set up the way iPat configured it (Horae routed to IAC, the auto-start relay service installed). You're just turning things on, not building them.

βœ… Before the show β€” one-time checklist

  • 🐳 Docker Desktop is installed on this Mac
  • 🎼 Horae is installed
  • βš™οΈ The auto-start relay service has been set up once by iPat (you won't need to touch Terminal for this β€” it starts itself)

If all three are already true, skip straight to "Show day" below. πŸ‘‡


🎭 Show Day β€” Step by Step

1️⃣ Start Docker Desktop

Click the Docker whale icon 🐳 in your Applications folder (or menu bar if it's already running). Wait until the icon in the top menu bar stops animating β€” that means it's ready.

2️⃣ Start the cue sheet website

Open Terminal, paste this one line, press Enter:

cd ~/Documents/Git/Show_Timecode && docker compose up -d cuesheet

⚠️ Only start cuesheet here β€” not relay. The timecode relay for this show runs a different way (see step 3), and starting both at once will fight over the same connection.

3️⃣ Open Horae and get it talking

  1. Open Horae 🎼
  2. Make sure IAC Driver Bus 1 has a βœ… checkmark in the MTC Destination list on the right
  3. Press ▢️ Play on the timecode generator

4️⃣ Check the relay is running

It should already be running quietly in the background (it auto-starts when you log in). To double check:

launchctl list | grep tc-relay

If you see a line printed back, βœ… you're good β€” skip to step 5.

If you see nothing printed, start it manually:

cd ~/Documents/Git/Show_Timecode
uv run --with websockets --with python-rtmidi python3 tc_relay.py --mtc "IAC"

Leave that Terminal window open in the background while the show runs.

5️⃣ Open the cue sheet

In your web browser, go to:

http://localhost:8080

(Someone viewing from a different laptop/tablet on the same WiFi? Use the show computer's network address instead of localhost β€” ask iPat for that number if you don't have it.)

6️⃣ Connect it to the timecode

At the top of the cue sheet, in the Chase bar:

  1. Find the Source dropdown β†’ choose WebSocket
  2. In the box that appears, type: ws://localhost:8765
  3. Click Connect

7️⃣ You're live! πŸŽ‰

  • The big clock at the top should turn πŸ”΅ blue and start counting, matching Horae's numbers
  • As each cue's moment arrives, its Status turns 🟒 green "Running" automatically, then back to 🟑 Standby when it's done

That's it β€” you're ready for the show. 🎬✨


πŸ†˜ Something's not working?

What you see What it means What to do
Cue sheet page won't load at all Docker isn't running Go back to Step 1 🐳
Clock stays gray, says --:--:--:-- Not connected yet Redo Step 6 β€” check you typed ws://localhost:8765 exactly
Clock is blue but numbers aren't moving Horae isn't sending timecode In Horae: is ▢️ Play actually pressed? Is IAC Driver Bus 1 checked?
launchctl list | grep tc-relay shows nothing, and manual start doesn't help either Ask iPat β€” this needs the one-time setup checked πŸ“ž
Cues never turn green even though the clock is moving Wrong Act selected Check the Act dropdown next to the ▢️ START button matches the act actually playing

When in doubt: close the browser tab, reopen http://localhost:8080, and redo Steps 5–6. That fixes almost everything. πŸ”„


tc_relay.py β€” Timecode WebSocket Relay

Bridges MIDI Time Code (MTC) β€” local, virtual bus, or network (RTP-MIDI) β€” or a free-run generator to any number of WebSocket clients (browsers, OSC bridges, etc.).
Used as the TC source backend for timecode_cue_sheet.html.


Requirements

pip install websockets          # all modes
pip install python-rtmidi       # MTC mode only

Raspberry Pi / Debian: add --break-system-packages if using system Python.

pip install websockets python-rtmidi --break-system-packages

Modes

1. MTC β€” MIDI Time Code (local, virtual bus, or network)

Decodes MTC quarter-frames and full-frame SysEx from any MIDI input.
Framerate (24 / 25 / 29.97DF / 30) is auto-detected from the MTC stream.

python3 tc_relay.py --mtc "MOTU"

--mtc accepts a substring of the MIDI port name (case-insensitive).
To list available MIDI ports without starting the relay:

python3 - <<'EOF'
import rtmidi
m = rtmidi.MidiIn()
print(m.get_ports())
EOF

Quarter-frame note: MTC encodes TC across 8 QF messages spanning 2 frames.
The relay applies the standard +2 frame latency compensation so the displayed TC matches the actual playhead position.

MTC over the network (Horae / Visual Productions TimeCore):
tc_relay.py only ever talks to a named MIDI port β€” it doesn't care whether that port is local hardware, a virtual bus (IAC), or a network session. To get MTC across the network:

  1. Have Horae or a TimeCore send RTP-MIDI (AppleMIDI) on the network β€” TimeCore supports RTP-MIDI natively; in Horae, add an RTP-MIDI destination alongside (or instead of) IAC.
  2. Join that RTP-MIDI session at the OS level on the machine running the relay:
    • macOS: Audio MIDI Setup β†’ Window β†’ MIDI Studio β†’ MIDI Network Setup β†’ connect to the session.
    • Linux: rtpmidid creates a matching ALSA MIDI port for any RTP-MIDI session it discovers/joins on the network.
    • Windows: install the Tobias Erichsen rtpMIDI driver and add the session there.
  3. Once joined, the session shows up as a normal MIDI port β€” point --mtc at a substring of its name, same as any local port:
python3 tc_relay.py --mtc "TimeCore"

2. Fake β€” Free-run generator (no hardware needed)

Generates TC from a given start point, incrementing in real time. Useful for testing the cue sheet without any MIDI hardware or network session.

python3 tc_relay.py --fake 01:00:00:00
python3 tc_relay.py --fake 00:59:50:00 --fps 25
Argument Default Description
--fake β€” Start TC HH:MM:SS:FF
--fps 30 Framerate for the generator

The generator broadcasts at 10 Hz β€” sufficient for smooth UI updates.


WebSocket output

All modes broadcast to ws://0.0.0.0:<ws-port> (default 8765).

Message format: plain JSON, one per frame update:

{"tc": "01:00:23:14"}

The cue sheet HTML accepts both plain "HH:MM:SS:FF" strings and the JSON wrapper.
Any other client (Node, Max/MSP, TouchOSC, etc.) can consume the same stream.

Argument Default Description
--ws-port 8765 WebSocket server port

Multiple clients are supported simultaneously β€” the relay broadcasts to all connected sockets concurrently.


Connecting the cue sheet

In timecode_cue_sheet.html:

  1. Chase bar β†’ Source: WebSocket
  2. Enter ws://<Pi IP>:8765
  3. Click Connect
  4. TC display turns blue when the WS feed is active

If the relay is running on the same machine as the browser, use ws://localhost:8765.


Editing the cue sheet

Everything's editable directly in the table β€” no re-import needed:

  • Add a cue β€” toolbar οΌ‹ Add Cue (appends to the current/default act), or hover any row and click its οΌ‹ to insert right after it.
  • Delete a cue β€” hover a row, click βœ• (confirms first).
  • Rename / edit fields β€” click into Cue#, Label/Scene, Trigger, Dept, Device, Action, Fade, or Notes and type. Commits on blur or Enter.
  • Edit Duration β€” click the Dur cell, type HH:MM:SS:FF. TC In/Out for every following cue ripples forward automatically β€” same mechanism as drag-reordering.
  • All of the above feed the existing ⎌ Undo button and autosave (localStorage), so none of this is riskier than a drag-reorder.

Live sync across hosts β€” any edit (rename, add/delete/reorder, status, notes, meta fields, TC color) is pushed to every other browser connected via Source: WebSocket to the same relay, over that same connection β€” no separate setup. A browser that joins later gets the current cue sheet immediately (the relay caches the latest snapshot for new connections). The one thing that's deliberately not synced is the transient "Running" highlight β€” each host computes that itself from the shared TC feed, so it stays consistent without fighting over the wire. Hosts using MTC (Web MIDI) as their TC source instead of WebSocket aren't on this channel and won't send or receive edits.

Status β€” a dropdown per cue: Standby / Ready / Running / Hold / Done / Cut. While a cue is the one currently under the TC playhead, its Status is automatically forced to "Running" (green, pulsing) β€” this overwrites whatever was set manually. The instant that cue's TC window ends, it reverts to "Standby" (not whatever it was before β€” a cue left on "Hold" or "Cut" will read "Standby" after the playhead passes through it). Driven purely by the chase engine, so it behaves the same regardless of TC source (Manual / WebSocket / MTC).


Docker

Dockerfile + docker-compose.yml run the relay and the cue sheet as two containers β€” relay (the WS relay, multi-stage build so the final image only carries the ALSA runtime lib, not the compiler) and cuesheet (nginx serving timecode_cue_sheet_4.html as a static site).

docker compose up -d --build
  • Relay: ws://<host>:8765 β€” defaults to the free-run generator from 01:00:00:00, so it works out of the box with zero hardware/config, good for testing the cue sheet.
  • Cue sheet UI: http://<host>:8080

Changing ports β€” both are env-var driven (${RELAY_WS_PORT:-8765} / ${CUESHEET_PORT:-8080} in docker-compose.yml):

CUESHEET_PORT=9090 docker compose up -d          # one-off, this run only

cp .env.example .env                              # persistent β€” compose auto-reads .env
echo "CUESHEET_PORT=9090" >> .env
docker compose up -d

docker compose up -d --force-recreate cuesheet    # already running, just remap (no rebuild)

Recreating a container to change its published port is the ceiling of what Docker's port-publishing model allows β€” there's no true hot remap on a running container.

Switching modes β€” edit command: in docker-compose.yml for a persistent deployment:

command: ["--mtc", "TimeCore"]

or override ad hoc without touching the file:

docker compose run --rm -p 8765:8765 relay --mtc "TimeCore"

then docker compose up -d to apply.

Fake mode works anywhere β€” generated data, no L2 or USB dependency, so default bridge networking is fine on any Docker host, including Docker Desktop on macOS.

MTC mode needs Linux + USB/network MIDI passthrough β€” MIDI access doesn't cross the Docker Desktop macOS VM boundary cleanly. On a Linux host, uncomment in docker-compose.yml:

devices:
  - /dev/snd:/dev/snd
group_add:
  - audio
command: ["--mtc", "MOTU"]

That covers local/USB MIDI. For an RTP-MIDI network session (Horae or TimeCore), join the session with rtpmidid on the Linux host first (outside the container) β€” it creates a regular ALSA MIDI port, which then just needs the same /dev/snd passthrough to reach the container.

If the container still can't see the interface after that, privileged: true is the blunt fallback β€” wider blast radius, only reach for it if the device mapping + group approach doesn't resolve ALSA permissions. On macOS, run tc_relay.py --mtc "..." natively for MTC (see Requirements above) β€” Docker isn't the right tool for that mode there.

vs. systemd β€” for a dedicated always-on Pi doing MTC capture, the systemd unit below is still the better fit: direct hardware access, no container/USB passthrough fuss, journalctl for logs. Docker's the better fit for Fake mode, or when you want the relay + cue sheet reproducible/portable across machines.

Running as a systemd service (Raspberry Pi / Linux)

sudo tee /etc/systemd/system/tc-relay.service << 'EOF'
[Unit]
Description=Timecode WebSocket Relay
After=network.target

[Service]
WorkingDirectory=/var/www/showcontrol
ExecStart=/usr/bin/python3 /var/www/showcontrol/tc_relay.py --mtc "MOTU" --ws-port 8765
Restart=always
RestartSec=3
User=pi

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl enable --now tc-relay

# check status
sudo systemctl status tc-relay
journalctl -u tc-relay -f

Change --mtc "MOTU" to --mtc "TimeCore" (or whatever the RTP-MIDI session/port is named once joined) or --fake 01:00:00:00 as needed.


Quick reference

Mode Command
MTC from MOTU interface python3 tc_relay.py --mtc "MOTU"
MTC over RTP-MIDI (Horae/TimeCore) python3 tc_relay.py --mtc "TimeCore"
MTC, custom WS port python3 tc_relay.py --mtc "USB MIDI" --ws-port 9000
Free-run from 1h python3 tc_relay.py --fake 01:00:00:00
Free-run, 25fps python3 tc_relay.py --fake 00:59:50:00 --fps 25

Troubleshooting

No module named rtmidi

pip install python-rtmidi --break-system-packages

MIDI port not found

python3 -c "import rtmidi; print(rtmidi.MidiIn().get_ports())"
# use a substring of the printed port name in --mtc

RTP-MIDI session (Horae/TimeCore) not showing up as a MIDI port

  • The relay never talks to the network directly β€” the OS must join the RTP-MIDI session first (Audio MIDI Setup on macOS, rtpmidid on Linux, the Tobias Erichsen driver on Windows)
  • Once joined it appears in rtmidi.MidiIn().get_ports() like any other port β€” if it's not there, the session wasn't joined, not a relay problem

Browser says WS connection refused

  • Confirm the relay is running: sudo systemctl status tc-relay
  • Confirm the port is open: ss -tlnp | grep 8765
  • Confirm no firewall blocking: sudo ufw allow 8765/tcp

TC displayed but frozen in browser

  • MTC source stopped sending β€” relay freewheels for 2s then freezes
  • Check MIDI cable / interface driver, or that Horae/TimeCore is still transmitting on the network session

Web MIDI (in-browser MTC) not working on LAN

  • Chrome requires a secure context for Web MIDI (https:// or localhost)
  • On plain http://192.168.x.x, use the relay instead β€” connect via WebSocket source in the cue sheet

Architecture summary

MTC hardware  ──USB/DIN───────▢ python-rtmidi
MTC network   ──RTP-MIDI──────▢ (OS joins session, appears as a MIDI port)
  (Horae / TimeCore)                          β”‚
Free-run generator                            β–Ό
                                    asyncio broadcast ──▢ ws://host:8765
                                                                β”‚
                                                     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                                Browser 1   Browser 2   Browser N
                                              (cue sheet HTML, any device on LAN)

About

Show time code tracking

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages