MIDI Show Control: Still the Most Reliable Cue Trigger
MSC is a 1991 spec that still runs theatres. The SysEx byte format, the command families, the 128-byte cap, and four ways it fails without an error.
CueSync TeamStage Automation Engineers
A Protocol That Stopped Changing
MIDI Show Control was standardised as RP-002 in 1991 and extended once, as RP-014. That is the whole history. A console built in 1998 and one shipped this year parse the same bytes the same way, which in show control is a rarer property than it sounds.
It is fire-and-forget. There is no handshake, no acknowledgement, no error channel. That design is why it is dependable — there is nothing to negotiate and nothing to time out — and also why every mistake in it is silent. A malformed MSC message does not raise an error. It produces a cue that does not fire, in front of an audience, with nothing in any log.
So the byte-level detail is not pedantry. It is the only defence available.
The Message
F0 7F <deviceId> 02 <commandFormat> <commandType> <data...> F7
| Byte | Value | Meaning |
|---|---|---|
F0 | fixed | SysEx start |
7F | fixed | Universal Real-Time |
deviceId | 00–6F, or 7F | Target device, or all-call |
02 | fixed | MSC sub-ID |
commandFormat | see below | Target subsystem family |
commandType | see below | GO, STOP, RESUME, … |
data | ASCII | Cue number, list, path — 00 delimited |
F7 | fixed | SysEx end |
Seven envelope bytes. Everything else is payload.
A GO for cue 12.5 on list 3, all-call, lighting family:
F0 7F 7F 02 01 01 31 32 2E 35 00 33 F7
│ │ │ │ │ └── '3' cue list
│ │ │ │ └───── 00 delimiter
│ │ │ └──────── '5'
│ │ └─────────── '.'
│ └────────────── '2'
└───────────────── '1' cue number "12.5"
The cue number is text, not a number. 12.5 is four ASCII characters.
Command Types
The ones that carry a show:
| Code | Command | Use |
|---|---|---|
01 | GO | Fire the cue |
02 | STOP | Halt it |
03 | RESUME | Continue a halted cue |
04 | TIMED_GO | Fire with an explicit fade time |
05 | LOAD | Prepare without firing |
07 | FIRE | Fire a macro |
08 | ALL_OFF | Release everything |
0B | GO_OFF | Release a specific cue |
11/12 | STANDBY ± | Move the standby pointer |
18 | SET_CLOCK | Set the internal clock |
19/1A | MTC_CHASE ON/OFF | Start or stop chasing MTC |
That 18 is worth dwelling on. It is easy to omit when transcribing the table, and omitting it shifts every code below it down by one. MTC_CHASE_ON then goes out as 18 — which is SET_CLOCK — and a spec-conformant console jams its clock when it was asked to chase MTC. CueSync shipped precisely that bug; the fix was restoring SET_CLOCK so the codes below it line up with RP-002 §4.4 again.
Command Formats — the Trap
The command format byte says what kind of device should act. The families are not contiguous, and the gaps are where mistakes live:
| Range | Family |
|---|---|
01–06 | Lighting — general, moving lights, colour changers, strobes, lasers, chasers |
10–18 | Sound — general, music, playback, intercoms, amplifiers, effects, EQ |
20–2A | Machinery — rigging, flys, lifts, turntables, trusses, robots |
30–38 | Video |
40–45 | Projection — film, slide, video projectors, dissolvers, shutters |
50–58 | Process control — hydraulics, water, CO₂, air, gas, fog, smoke, haze |
60–64 | Pyro — fireworks, explosions, flame, smoke pots |
7F | All types |
Note that Projection sits at 40 and Process Control at 50. If Projection is missing from an implementation's table, Process Control slides up into 40, and now a FOG cue goes out as 46 — undefined inside the Projection family — while process-control commands address projectors. CueSync had this exact defect before the families were checked against RP-002 §4.1.
The general point: a wrong command format is not a rejected message. It is a valid message delivered to the wrong kind of device, in a protocol where devices do not answer back.
The 128-Byte Cap
RP-002 §2.1 note 2 states that a Show Control message should not exceed 128 bytes total. Seven of those are envelope, leaving 121 for payload.
Console parsers size their buffers to that number. A longer message is not politely refused — it is truncated at the buffer boundary or dropped, and the symptom is a dead cue. Any implementation that permits an unbounded payload will eventually interpolate a long user-supplied string into a cue path and produce exactly that.
Cue Strings Are Narrower Than You Think
RP-002 §3.1 permits ASCII digits 0–9 (0x30–0x39) and . (0x2E) as a subsection delimiter. That is the complete set.
Space is not in it. A padded cue number — "12.5 ", or " 3" — is not a formatting nicety. It is either parsed as a different cue or dropped, and it will not announce itself. Validate cue strings at the sending end, because nothing downstream will.
Two more constraints follow from MIDI itself: every data byte must be 7-bit (0x00–0x7F), and F0/F7 must never appear inside the payload or the SysEx truncates on the wire.
TIMED_GO and the Hour Byte
TIMED_GO prefixes the cue data with a five-byte Standard Time Code block — hr mn sc fr ff — per RP-002 §3.2.1.
The hour byte is not a plain integer. Its layout is 0 tt hhhhh: five hour bits, with the SMPTE type in bits 5–6:
| Bits | Rate |
|---|---|
00 | 24 fps |
01 | 25 fps |
10 | 30 drop-frame |
11 | 30 non-drop |
Two consequences. Hours are 0–23, not 0–99 — a larger value spills into the type field and silently relabels the fade's frame rate. And every Standard Time Code block declares a rate whether you meant to or not; omitting it implicitly claims 24 fps for every fade time you send.
Frames are 0 g i fffff, so 0–29. Not 30.
MSC or OSC?
| MSC | OSC | |
|---|---|---|
| Vocabulary | Fixed by spec | Per-vendor |
| Transport | MIDI cable or virtual port | Network |
| Arguments | ASCII cue identifiers | Typed, structured |
| Unknown message | Ignored silently | Ignored silently |
| Universality | Very high | Depends on both ends |
Use OSC when both ends speak it well and you need arguments MSC cannot express. Use MSC when any part of the chain is old, mixed, or unverified — the vocabulary is fixed, so there is no per-vendor grammar to discover and no risk of inventing an address the console has never heard of.
CueSync implements MSC to RP-002/RP-014 with the full command-type and command-format tables, the 128-byte cap enforced at the sender, and cue-string validation that rejects anything outside digits and dots before it reaches the wire. It is one of the 13 output protocols in every edition.
Further Reading
- Audio-Reactive Cues Over OSC — the other way to fire a cue list
- Where Should Your Show's Timecode Come From? — MSC's relationship to a clock
- MIDI & OSC Integration — ports, virtual devices, setup
- CueSync for Theatre Operators — cue stacks, Show Editor, Run Mode
Frequently Asked Questions
No, and its age is closer to a feature than a fault. MSC was standardised in 1991 and has not needed to change, which means a console from 1998 and one shipped this year interpret the same bytes identically. In theatre it remains the most reliable way to make one desk fire another's cues, precisely because there is nothing to negotiate — no handshake, no version discovery, no transport configuration beyond a MIDI cable or a virtual port. Newer protocols do more; almost none of them are as certain to work.
Keep Reading
Audio-Reactive Cues Over OSC: What QLab Can and Cannot Do
Reacting to live audio usually gets built from QLab network cues, OSC queries and patience. What that does, where it stops, and what analysis adds.
ReadWhere Should Your Show's Timecode Come From?
Reaper, QLab, the console's internal clock, a hardware generator, or the live performance itself. Four architectures, and how to pick between them.
ReadHow to Send Timecode to GrandMA3: LTC, MTC, Art-Net and TCNet
GrandMA3 can follow SMPTE/LTC, MIDI timecode or Art-Net timecode. How each reaches the console, which to choose, and what happens when the source drops.
Read