Skip to content

Technical reference

What this does: the facts IT and support ask for. Paths, ports, formats, limits and timings. You do not need this page to run a show.

  • Desktop app: Tauri 2. The interface is Svelte in the system webview. The engine is Rust.
  • Rust owns: LTC on the audio device, MTC on its own timing thread, Art-Net and MIDI Clock, OSC to and from the desk, the sessions host and peer, the project store, backups, cloud sync, uploads and downloads.
  • The webview owns: the interface, audio playback of tracks and lanes, and the video and image players.
  • Two clocks: the audio engine’s and the timecode engine’s. They are reconciled continuously. Playback sync in Settings → Audio Devices is the trim.

Everything below lives under ~/Library/Application Support/ShowLink Cues/. The folder keeps the old product name on purpose: renaming it would strand every existing install.

PathHoldsKept
shows/<id>.jsonOne project per file. Saved on every change, through a temp file and a rename.Until you delete the project
shows/<id>.json.bak, .bak.1–.bak.3The previous saves, rotated on every save4
backups/<id>/<yyyy-mm-dd-HHMM>.json.gzA snapshot on save when the newest is an hour old. Local time. A second one in the same minute gets -2, -3. A restore snapshots the current state first.The newest 24, plus the first of each of the last 14 days. 38 at most.
media/store/<sha256>.<ext>Cues’ own copies, one per distinct contentUntil moved to Trash
media/dropped/Landing spot for dropped files, moved into the store at once
media/trash/<date>/ + manifest.jsonDeleted media30 days. Purged at launch and every 24 h.
media/sha-cache.jsonPath to sha256, size and modified time, so files are not hashed twiceRebuilt if deleted
sessions/This Mac’s session certificate and key, the device book (devices.json), this Mac’s label (label.txt)
sessions/media/Media a session peer got from a host, named by sha256Never touched by Reclaim space
sessions/peer-cache/<host fingerprint>/On a peer: the host’s last show. A rejoin needs only the changes.
cloud-uploads/upload-resume.json (open multipart uploads), upload-ledger.json (bytes sent per day), upload-settings.jsonLedger: 14 days
waveforms/<track_id>.jsonTrack waveform cacheRebuilt if deleted
cache/peaks/ · cache/thumbs/Lane waveforms and video filmstripsRebuilt if deleted. Never synced, never backed up.
logs/cues.YYYY-MM-DD.logThe log, one file per UTC day7 files
crashes/<time>.txtOne report per crashUntil you delete them
config.jsonThe grandMA2 plugin server: allowlist, port, bind address

Outside that folder:

PathHolds
~/Library/Application Support/helm-cues/cloud-media/<project>/<file id>.<ext>Media downloaded from Helm Cloud. The Media Library shows the real name.
~/Library/Application Support/com.elabs.helm.cues/licence.jsonThe licence key, its last check, this Mac’s device ID and the signed entitlements
~/Library/Application Support/com.elabs.helm.cues/updates.jsonThe previous build and the What’s new notes
~/.helm/cues-control.jsonThe Stream Deck socket’s port and token. Mode 0600, rewritten on every launch.
/tmp/helm_*.txtThe desk file bridge, when grandMA3 onPC runs on this Mac. See Desk OSC commands.
macOS keychain, helm-cues-cloudThe Helm Cloud token
The webview’s local storageApp settings, workspaces (helm-cues:workspaces, versioned JSON), onboarding progress, per-track sync records, the daily upload limit

Name the risk: licence.json holds the full licence key. Treat a copy of that folder like the key itself. A support bundle never includes it.

Files you picked from elsewhere, such as ~/Music or a USB drive, are used in place. Cues never moves or trashes them.

  • One JSON document per project: id, name, artist, frame_rate, team_roles, tracks, created_at, updated_at, cloud_project_id, cloud_version, versions, metadata.
  • Tracks carry their stacks, cues and audio settings. metadata carries the media lanes, the running orders, the media list and the cloud upload mode.
  • Waveform peaks are never written into it.
  • The cloud holds the same project, one record per track plus the show-level fields. See Cloud sync.
  • A ZIP: manifest.json, show.json and media/, stored uncompressed.
  • The manifest lists each media file’s sha256, size and original name.
  • Export writes a .part file, then renames it.
  • Import checks every sha256 and size. One mismatch stops the import and removes what it added.
  • A package with the same project id replaces the open one after a backup, or comes in as a copy with a new id.
  • File → Export Show Package… (⇧⌘S) and Import Show Package… See Backups and show packages.
  • File → Import / Export data… (⌘I), or Export → Track → Helm CSV… (⌘E).
  • Header: Timecode,Label,CueNumber,StackName,Sector,Command,TargetCue,FadeTime,DelayTime,Notes.
  • Name the risk: import reads timecode, label, cue number, target, command, fade and notes. StackName, Sector and DelayTime are dropped on import.

File → Import Show (grandMA3 / Reaper)… reads a grandMA3 timecode XML, a Reaper .rpp or a Reaper marker CSV. See Import from grandMA3 and Reaper. QLab is not implemented.

Until 0.3.0, every save also writes the older fields 0.2.12 needs. A project saved by today’s build still opens in 0.2.12 and later.

  • Name the risk: an older build knows nothing of newer features. Edit a project in the newest build you have.
  • Reinstall previous version warns: Shows saved by <current> may not open in <previous>. See Updates.
  • At 0.3.0 the rule ends.

LTC, MTC and Art-Net each run their own frame clock. The player’s heard position steers it; it never sets the labels. Operator rules: Timecode.

ItemValue
RateLive: 10 a second. Build: about 30 a second (once per 1/30 s of track time)
StampEach position carries the page’s performance.now() when it was read
AgeingAge = (arrival − stamp) − the lowest (arrival − stamp) of the last 5–10 s. A position held behind the main thread is moved on by its age
Settling flagSet while the player’s heard time is a guess: inside the 2 s start band, or before the element clock moves off the play or seek point
Silence3 s with no position: the output pauses until the next one (was 1 s)
ItemValue
LabelsA frame counter: +1 a frame (LTC, Art-Net), +2 a cycle (MTC)
StartStart cues the position. The clock rolls on the first settled position that moves at up to 1.5 × real speed
SlewRate change = error ÷ 1 s, at most ±1 %
RelockError over 1.5 frames on two positions within a frame of each other, or over 0.5 s on one
Seek while settlingA settling position 0.5 s or more from the clock cues there
StallThe position not moving (±2 ms) for 0.25 s: pause; it never steers
Below zeroSilent on every output; starts on 00:00:00:00
HoursWrap at 24
Rates refused50, 59.94, 60 on LTC, MTC and Art-Net
ItemValue
Quarter frames4 a frame: 10.0 ms at 25, 8.34 ms at 29.97, 8.33 ms at 30, 10.4 ms at 24
Cycle8 pieces, 2 frames, every piece the label of the frame piece 0 starts. Piece 0 on its frame’s start; nibbles low first; rate in piece 7 bits 1–2
Full frameOn a cue (start, seek, scrub while cued); before piece 0 after a roll or relock; on a rate change; every 1 s while held
ThreadOwn thread, USER_INTERACTIVE QoS. Sleeps to 1.5 ms before each message, then spins
Late wakeOver 2 quarter frames late: no burst. The next cycle starts on the next frame
PortUSB MIDI moves each message by up to 1 ms
ItemValue
Bit lengthsample rate ÷ (fps × 80), fractional: 1601.6 samples a frame at 29.97 fps and 48 kHz. Whole-sample bits ran 29.97 as 30.0, and 30 fps at 44.1 kHz 2 % fast
Bit layoutThe shared Helm encoder (Helm DJ’s): drop flag bit 10, BGF0 bit 43 at 25 fps, polarity bit 27 over bits 0–63, sync word bits 64–79
Level±0.8 (−1.9 dBFS) square wave
TimingEach buffer is stamped with callback time + the stream’s output latency (capped at 250 ms)
SlewThe next frame’s bits run up to 1 % short or long. A frame in progress is never cut
RelockSilence to the new label’s exact frame boundary, then the frame
Late callbackThe device plays a gap; the stream resumes where it was and is slewed or relocked back

One ArtTimeCode packet a frame on its frame’s start, from the same clock. Over 2 frames late: no burst. Held: the frame repeats each frame period.

WhereResult
Field log, 0.2.17, 22–24 Oct 2026Quarter frames 0.8–1.9 ms late on average, 4.3 ms worst. 271 of 282 MTC plays sent a full frame mid-run, typically 0.7–0.9 s after the music started and 0.30–0.38 s back. A 1.2 s main-thread stall (session hosting, desk hello) stopped MTC, which came back with two full frames 11 ms apart and 1 s of timecode apart
Tests, 0.2.1810 simulated minutes per rate (MTC: 23.976, 24, 25, 29.97 DF and NDF, 30; LTC at 25 and 29.97 DF): every label in order, piece 0 / frame start within 1 ms of the player, quarter-frame spacing within ±1 %
Real thread, 0.2.18A loaded test run, the show lock held while a multi-MB show serialises, and the feed stalled 300 ms: no relock, no extra full frame, piece 0 within 2.8 ms, worst quarter frame 6.3 ms late
Project saveA 4.2 MB project: 72 ms to serialise on the main thread in a dev build. The timing threads never take the show lock

Every 10 s while MTC runs: MTC timing stats: <n> quarter frames, <x>% success rate, avg delay: <ms>, max delay: <ms>, full frames <n>, relocks <n>, late skips <n>, max error <ms>. Also MTC: relocked to <s> (app <ms> from the clock) and MTC: app time standing at <s> — quarter frames paused.

PortProtocolDirectionWhat
8000UDP / OSCCues → deskThe desk’s Helm IN row. Change it in Settings → Helm Sessions → Desk → Change ports… → Desk input port.
8001UDP / OSCdesk → CuesThe desk’s Helm OUT row. Fixed. Open only while this Mac hosts a session.
8005UDP broadcastbothThe session beacon, every 2 s. Every Cues listens to list the sessions on the network.
6454UDP / Art-Netout, inArt-Net timecode out, when on. In, only when Art-Net is the chase source.
5355–5362UDP / QUICpeers → hostSessions. The host binds the first free port in the range.
5353mDNS, Bonjour _helm._udpCues → LANThe session advert Helm Monitor finds. Sent by macOS; Cues opens no socket for it.
8765TCP / HTTPMA2 → CuesThe grandMA2 plugin server. Starts only when MA2 sync runs.
47811–47818WebSocket, localhost onlyStream Deck → CuesControl socket. 8 ports tried.
443HTTPSCues → internetapp.helm.show: projects, files, entitlements. helm.show: licence checks and updates. *.r2.cloudflarestorage.com: media uploads and downloads, by signed link.
  • The beacon is a broadcast. It goes to each interface’s subnet and to 255.255.255.255. It does not cross a router. A switch that filters broadcast hides it. Join by address… needs no beacon.
  • Two interfaces? The beacon leaves each one carrying that interface’s own address. Settings → Network → Primary Network Interface narrows it to one.
  • Sessions use TLS 1.3 over QUIC with a pinned certificate per host. A device is known by its certificate fingerprint: 26 characters.
  • The show network needs no internet. The desk bridge and sessions are LAN only.

On the Mac: allow incoming connections for Helm Cues in the macOS firewall. macOS asks the first time a session is hosted.

Between devices:

AllowOnFor
UDP 8001 inThe hosting MacDesks talking to Cues
UDP 5355–5362 inThe hosting MacSession peers: another Cues, Helm Monitor, Helm DJ, Helm Clock
UDP 8000 inEach grandMA3Cues talking to the desk. Or the Desk input port set in Cues.
UDP 8005 broadcastThe show subnetThe session list
UDP 5353 multicastThe show subnetHelm Monitor’s session list
UDP 6454The Art-Net subnetOnly when Art-Net timecode is on, out or in
TCP 8765 inThe Cues MacOnly while grandMA2 sync runs
TCP 443 outapp.helm.show, helm.show, *.r2.cloudflarestorage.comCloud sync, licence, updates, media
  • WebSocket on 127.0.0.1, port 47811. Taken? The next one, up to 47818.
  • A fresh 32-character token every launch. Cues writes the port, the token and its process ID to ~/.helm/cues-control.json, mode 0600.
  • The first message must carry the token within 3 s, or the socket closes.
  • A connection with an Origin header is refused. No web page can reach it.
  • Nothing off this Mac can reach it. Nothing to open in a firewall.
  • Log lines start control-surface:.

For programmers writing their own grandMA3 macros. The Helm plugin’s playhead macros and the cue Commands of Desk GO drives Cues and Cues command… send some of these. Any macro can send any of them.

SendOSC <line> "/Helm/start,s,<track>|<mark>"
  • <line> is the Helm OUT row’s number in Menu → In & Out → OSC.
  • <mark> is a cue label (any case), a desk cue number, start, or <seq>:<cue>: the desk cue on that sequence, by its name (05 - Main:3) or, from older builds, its number. No commas or | inside it.
  • <track> is the track number or name. Leave out <track>| to stay on the loaded track. A named track loads even with Follow the desk’s track load off: the command names it.
  • <seq>:<cue> finds the track by its sequence when the track number on the desk is out of date.
  • Joined desks only. Cues ignores a desk that has not joined the hosted session. It logs one line per desk per minute.
  • Cues answers in the Global var HELM_SHOW_ACK: one line, up to 160 characters. Live shows it in its header.
SendOSC 2 "/Helm/start,s,verse"
SendOSC 2 "/Helm/start,s,3|chorus"
SendOSC 2 "/Helm/goto,s,Blue|05 - Main:3"
SendOSC 2 "/Helm/load,s,3"
SendOSC 2 "/Helm/stop,s,now"
AddressPayloadCues does
/Helm/loadTrack number or nameLoads it in Live, no play. Needs Follow the desk’s track load on, Desk control on, nothing playing.
/Helm/start<mark>, or <track>|<mark>. Empty is start.Loads the track if named, jumps Live to the mark and plays. Needs Live armed and Desk control on.
/Helm/gotoAs /Helm/startLoads the track if named, jumps Live to the mark and stays parked. Pauses Live if it was playing. Needs Live armed and Desk control on.
/Helm/pauseAnyPauses Live. Needs Live armed and Desk control on.
/Helm/resumeAnyPlays on from the pause. Needs Live armed and Desk control on.
/Helm/stopAnyStops at the track top. Needs Live armed and Desk control on.
/Helm/transportplay, pause, stop, rew, ff, prev, nextDrives Build’s player when Build is on screen. Otherwise Live, with Desk control on.
/Helm/selectTrack number, digits onlyBuild selects it. Live loads it, no play, with Desk control on.
/Helm/trackTrack nameBuild selects it. Live loads it with Desk control on.
/Helm/autosyncon or offSets this desk’s auto-sync
/Helm/edit<seq name>|<cue>|<seconds>Moves the matching cue to that time. Sent when a Helm event moves on the desk. Plugin 0.16.4 names the sequence; older ones number it.
/Helm/pullall, track:<num>, list or sessionsSends the show, one track, the track list alone (the packed track vars with the push handshake, no sequences: plugin 0.16.15’s pickers) or the list of sessions on the network
/Helm/helloMA3|<version>|<session>@<host>|<tile>|<sync version>|<ask>Records the desk. Answers in HELM_LINK only a desk’s first hello of Cues’ run, a hello naming another session or plugin version than its last, and one with ask (plugin 0.16.12: Connect, Setup Desk, a press on a desk that is not LINKED). On a join it sends the cue times the desk missed (The live link). An empty session is a leave: the desk is forgotten, no answer. <sync version> is the desk’s HELM_SYNC_VERSION, empty when it holds none. Plugin 0.16.2; older plugins send four fields.
/Helm/edited<seq name>|<cue>|ok|miss|err|<detail>|<timecode name>The plugin’s answer to each HelmEditEvent, HelmRetargetEvent, HelmAddEvent, HelmDelEvent and (0.16.6) HelmCue. <cue> is the number Cues addressed the event or cue by. miss or err: the track (found by the timecode name, else the sequence name) needs Import on that desk at once, logged every time. Nothing is resent. Plugin 0.16.4; 0.16.2 sends a sequence number and no timecode. HelmCue sends no timecode. OSC only. Details: How the desk finds an event, HelmCue.
/Helm/diagcues|<seq>|<k>/<n>|<cue>:<name>|…, cues|<seq>|missing, event|<tc>|<seq>|<k>/<n>|<PROP>=<value>|…, event|<tc>|<seq>|none|<why>, token|<wanted>|<key>=<read back>|set|fallback|refused|unverifiedLogs each line at INFO as MA3 diag <desk>: …. A complete cues list is compared with Cues’ names; each difference is logged and marks the track Import needed on that desk. Plugin 0.16.6. OSC only. Details: Desk report.

/Helm/hello and /Helm/pull with sessions are taken from any desk. Everything else needs a joined desk: one whose last hello named this session, allowed, not since dropped. A desk is dropped when three sends to it fail in a row (MA3 desk <ip> dropped from the session: …); its next hello rejoins it. No clock ages a desk out.

  • Echo guard. A /Helm/start or /Helm/goto that lands within 0.5 s of where Live already plays that track is ignored and logged: it is Live’s own timecode firing a desk cue that calls back.
  • The same command twice within 750 ms is dropped. The macros send OSC and the file bridge together.

Cues sends grandMA3 commands to the Helm IN port as /cmd. A batch goes out 5 ms apart, so the desk’s queue drops none.

Global varHolds
HELM_LINKjoined, pending, denied or nosession. Cues’ answer to a hello it answers, and the only var a joined desk’s answer carries (plus HELM_SESSION when the desk’s lacks the host). Also on Allow and an approval flip that changes the desk’s verdict; on Remove and stop hosting only to a plugin before 0.16.13 (0.16.13 gets forget). The plugin writes nolink itself after an Import that failed.
HELM_SESSION, HELM_AUTOSYNC, HELM_CUES_TRACKSent with a desk’s first joined hello of a session, then only when they change.
HELM_SHOW_ACKCues’ answer to a show command
HELM_CFG_BACKUP_OFFSETSettings → Desk import → Backup offset. Sent with a desk’s first joined hello, at the head of every push, and to every joined desk when it changes. 0 = no backups.

HELM_CFG_SET_BPM is never sent (Cues 0.2.18, plugin 0.16.11): TRACK_SETUP line 2 is always Master <HELM_CFG_BPM_MASTER> At BPM $CURRENT_TRACK_BPM, and Cues never runs rebuild_setup.

Live BPM to the desk (Settings → Desk import, off by default, metadata.deskLiveBpm): when the Live player, or Build’s while Build is in front, plays across a tempo marker of the track’s beat grid that changes the BPM, Cues sends each joined desk taking auto-sync one line, Master <m> At BPM <bpm>, the BPM to one decimal, logged at INFO as MA3 live BPM → <desk>: …. <m> is the master in the desk’s master|<m>|… diag line (Setup Desk, diag) since Cues started, else BPM master (3.1). A forward step longer than 1.5 s, a seek, track select, play and stop send nothing; one marker sends once per 250 ms.

By name. After an object exists, every command addresses it by name: Sequence "01 - Main", Page "Intro", Timecode "Intro", Macro "HELM …". Names drop . , ; " \ $ & * ? ^ { | } ~ (MA3 manual, Label keyword) and turn ' into ’.

A push (the Import path), per track:

LineDoes
Lua "if HelmEnsure then HelmEnsure('Page',<n>,'<track>','<old names>') end"The page named after the track; else one under an old name, relabelled; else a new one at page <n>, or the next free number above
Lua "if HelmEnsure then HelmEnsure('Sequence',<n>,'<prefix> - <stack>','<old names>') end"The same for each visible stack’s sequence, created at Starting Sequence # + its place
Lua "if HelmCue then HelmCue([==[<seq>]==],<n>,[==[<name>]==],<fade>,<delay>,[==[<note>]==]) end"Each desk cue, by name: found, created only when missing, named, timed (nil = leave the desk’s own), noted (plugin 0.16.13; '' clears). See HelmCue.
Lua "if HelmAssign then HelmAssign('<seq>','<page>',<exec>) end"The sequence on its executor of the page found by name
SetGlobalVariable "HELM_TRACK_<i>_SEQ_<j>" <n>, …_LABEL "<name>", …_WAS "<old names>"The plugin’s build data: names to find, numbers only to create

HelmEnsure and HelmAssign are plugin 0.16.4 globals, HelmCue 0.16.6. Pool numbers appear in nothing else. No Move, never a store over another object.

Plugin 0.16.6. One line per desk cue, on Import and live. It replaces Store + Label + Set … "CueFade" / "CueDelay": on the 2026-10-22 desk test some cue 1s arrived unnamed, and nothing said so.

  1. The sequence named <seq>, as it is now. None: err|no sequence named <seq> on the desk.
  2. Its cue numbered <n>, numbers compared as numbers.
  3. Missing: created. First as an object (GetChildClass must read Cue, then Append and Set('No'), checked; a mis-numbered one is deleted). Else Store Sequence "<seq>" Cue <n> /NoConfirmation (MA3 manual, cue_store: “programmer values are not needed to store cues”). An existing cue is never stored over: Store merges the programmer into it.
  4. The name: Set('Name') on the cue (MA3 manual, lua_object_set), read back; else Label Sequence "<seq>" Cue <n> "<name>", read back. Empty: the desk’s name stays.
  5. Fade, delay: Set('CueFade') / Set('CueDelay') on the cue, read back; else Set Sequence "<seq>" Cue <n> Property "CueFade" "<s>" (MA3 manual, keyword_set). nil: the desk’s own stays.
  6. Note (plugin 0.16.13, the one argument added to this line): Set('Note') on the cue, read back; else Set Sequence "<seq>" Cue <n> Property "Note" "<note>". '' clears it. A Cues that sends five arguments leaves the desk’s note. Cues sends the cue’s notes with quotes as ’ ”, ; as ,, each line break as ⏎ (plugin 0.16.16 puts the break back; an older plugin shows the mark), \\ and $ as spaces, at most 160 characters.
AnswerDetail
okcue <n> '<name>', plus how: (new), (new by Store; object: <why>), (named by Label), (CueFade by command), note '<text>', note cleared, … by command, note did not land (reads '…')
errno sequence named …, cue <n> is not in <seq> after …, or cue <n> reads '<x>' after Name and Label; Cues has '<y>'

GetChildClass, Append and Set('No') are not in the manual’s Object API list; the answer says which path ran.

VarSet by
CURRENT_TRACK, CURRENT_TRACK_NUM, CURRENT_TRACK_BPMEach track nav macro, in the plugin’s var_scope
TRACK_<num>, TRACK_<num>_BPM, SHOW_NAME, SHOW_ARTIST, SHOW_TRACK_COUNTImport, in the plugin’s var_scope

Cues also runs Plugin "<tile>" "tick" on the desk: once per Cues run before its first live line (the plugin’s globals must exist), and after Allow, an approval flip that changes the desk’s verdict, or an auto-sync flip in Cues. Stop hosting and Remove (or Deny) send one Plugin "<tile>" "forget ended" / "forget removed" to plugin 0.16.13: the desk forgets the session, sends nothing back and opens no dialog. An older plugin gets HELM_LINK and a tick, as before. Plugin 0.16.12’s tick says no hello and repaints only what changed. "trackchanged" relabels HELM TRACK when Cues’ track changes. "hello" (plugin 0.16.2) runs only for Check MA3 connection…‘s round trip. None opens a menu. Cues never runs "import": the operator does. Every poke logs MA3 → <desk>: Plugin "<verb>" (<why>).

What Cues sends a desk, and when. Nothing on a clock: no heartbeat, no periodic poke, nothing as hosting starts but the same-Mac port release (Cues 0.2.18).

SendsTrigger
The pushThe desk’s Import (a pull), Push now, Desk import → Apply
Live lines (HelmEditEvent, HelmCue, …)An edit in Cues, a drag, a desk joining after it missed edits
HELM_LINK (+ HELM_SESSION, HELM_AUTOSYNC, HELM_CUES_TRACK, HELM_CFG_BACKUP_OFFSET on a join)A hello Cues answers (above)
HELM_SESSIONS, HELM_SESSIONS_VThe desk’s Connect asks for the session list
HELM_CUES_TRACK + trackchangedCues’ track changes (Build selection, Live load)
HELM_AUTOSYNC (+ tick from Cues’ side)An auto-sync flip, on the desk or in Cues
HELM_LINK + tickAllow, an approval flip that changes the verdict; Remove and stop hosting (HELM_SESSIONS cleared too) to a plugin before 0.16.13
Plugin "<tile>" "forget ended" / "forget removed"Stop hosting / Remove or Deny, to plugin 0.16.13: one line, the desk forgets the session
HELM_CFG_BACKUP_OFFSETDesk import → Backup offset changed
HELM_SHOW_ACKAnswer to a desk show command
Master <m> At BPM <bpm>Live BPM to the desk on, the playhead crosses a tempo change
Off Sequence "<name>"Panic
Plugin … "tick"Once per run before a desk’s first live line
Plugin … "hello"Check MA3 connection…, twice
Set OSC Property "EnableOutput" false / trueOnce as a session is hosted (same-Mac onPC port release)

Two paths to a desk.

  • Live: one short line per change, by name. Joined desks taking auto-sync on plugin 0.16.6, as you edit:
EditLines
TimeLua "HelmEditEvent('<tc>','<seq>','<cue>',<old s>,<new s>)". A Flash: one for its Off event, then one for its On.
Name, fade, delay, noteOne HelmCue line with the desk cue’s name, fade, delay and note (its first cue’s). An emptied name or cleared fade or delay sends nothing; cleared notes send ''.
New cueHelmCue (only a desk cue the sequence does not have), then Lua "HelmAddEvent('<tc>','<seq>','<cue>',<s>,'<token>')"
Deleted cueLua "HelmDelEvent('<tc>','<seq>','<cue>',<s>)", then Delete Sequence "<seq>" Cue <n> /NoConfirmation when no other cue fires it
Renumbered cueCopy Sequence "<seq>" Cue <old> At Sequence "<seq>" Cue <new> /NoConfirmation, Lua "HelmRetargetEvent('<tc>','<seq>','<old>','<new>',<old s>,<new s>)", Delete Sequence "<seq>" Cue <old> /NoConfirmation. Onto a desk cue in use: the retarget only. A chain goes from the top; two cues trading numbers park one on a free 0.001 step first.
Command (Go → Flash …)HelmDelEvent, then HelmAddEvent

<tc> is the track’s timecode name, <seq> the sequence name, times are track-relative seconds with 3 decimals. The plugin resolves each name to the object as it is now (its number at that moment) and compares event destinations against it. The MA3 manual has no command to renumber a cue (“There is no command or keyword to renumber cues”, cue_renumber); Move is documented for objects with a macro example only (keyword_move), so a renumber copies (cue_copy, /NoConfirmation: ok_noconfirmation) and deletes (keyword_delete). Nothing else goes live: no Command, appearance, macro, poke or push.

  • Import: everything. Only when the operator asks: Import Tracks / Import Track… on the desk (it pulls), or Push now. Desk import’s Apply sends nothing.

Import needed. Recorded per desk and track in the show (metadata.ma3Desks, importNeeded) when the desk cannot follow live:

CauseLive lines for the track
A fader level or Desk GO of a cue; a stack recoloured or in a new place; a new Executor Start or track numberStill go
miss or err from the plugin (marked at once, every one logged)Still go
A stack or the track renamed, the prefix changed, a new track start (names; stored as numbers)Stop for that desk
The desk holds a sync version Cues did not send itStill go
A new Starting Sequence #, page or timecode pool; Desk import’s ApplyNo mark: numbers only say where a missing object is created

Only a push the desk asked for clears it, for the tracks it carried, once the plugin builds them. Push now does not.

Plugin older than 0.16.8. It has no HelmCue (before 0.16.4 it also finds sequences by number), so a new cue’s HelmAddEvent would miss its cue: no live line goes to it. The hello logs plugin 0.16.6 needed, one toast, and the chip reads MA3 · plugin 0.16.6 needed.

A desk that was away. An edit a desk did not get (not joined, auto-sync off, no session) marks the track behind for it (dirty) and, in memory, where the desk still has each cue. On its join or auto-sync resume it gets the lines a live edit would have sent, from there. Behind with nothing in memory (Cues restarted): the track needs Import. A desk with no Cues data, or new to this show, gets nothing until its Import.

Pushes go in parts of 40 commands, each answered by the desk. A push the desk asked for (Import) waits up to 1.5 s for each answer. A push Cues starts itself (Push now) waits 1.5 s once; with no answer it waits 0.15 s after each part from then on, less when an answer comes. A whole show of 3000 commands takes under 30 s. Plugin 0.16.3–0.16.11 answered these from a 3 s check; 0.16.12 has no timer and answers only when it runs. The log says MA3 push <id>: no acks from <desk> — pacing by time.

Live drag. Each step (about 10 a second, latest wins; a group steps every 6 ms per cue, at least 100 ms) sends HelmEditEvent from where the last step left the event on that desk. The drop’s save (update_cue, shift_cues) starts from there. A drag that ends without a save moves the events back. A desk’s own /Helm/edit is not sent back to it.

Log. One INFO line per send per desk: MA3 live → 10.0.0.20 "Intro": 3 line(s) — "01 - Main" cue 5 12.000→12.500; "01 - Main" cue 3→3.5; …, the first 6 changes, then … and N more. A miss: MA3 desk 10.0.0.20 miss: "01 - Main" cue 3 — no event of cue 3 near 8.332 s; 12 event(s) on the track: Sequence 110 Cue 1@1.000=1; … → Import needed on that desk. Drag steps log at debug. An edit a desk did not get: MA3 live: N cue(s) of track "Opener" not sent to 10.0.0.20 (…) — the times follow when it joins. A join: MA3 desk 10.0.0.20 joined: 1 track(s) missed edits → 2 line(s) of times. A new mark: MA3 desk 10.0.0.20: Import needed for 1 track(s) — <why>.

Plugin 0.16.5. The plugin reads each event of the track’s timecode track for the sequence (every time range, every subtrack) and resolves what it fires to a cue: the CUEDESTINATION property as a handle, or as text in any form a 2.x desk gives (Sequence 110 Cue 3, Sequence 110.3 or .003, Sequence "01 - Main" Cue 3, an address ending Sequence 110.Cue 3, Cue 3, 3, the cue’s name), then the text through ObjectList and FromAddr. The cue’s number and sequence are compared with the sequence found by name. Numbers compare as numbers: 3 = 3.000.

The track holds, of that cueThe plugin
One eventTakes it, whatever its time. A desk that missed an edit still follows the move.
Several (a repeat)Takes the one nearest the old time Cues sent
NoneAnswers miss
Delete with no cue numberThe nearest event of the sequence within 0.05 s only

Plugin 0.16.4 compared the text with Sequence <no>.<cue> only, and required the old time within 0.05 s. On a 2.x desk it missed every event (2026-10-22).

The answer’s <detail>, at most 240 characters:

StatusDetail
okmoved from <desk time> to <new> s, plus (Cues had <old>) when the desk held it more than 0.05 s from where Cues thought, and (nearest of N) for a repeat. A retarget adds ; now cue <n>. A delete: deleted the event at <t> s.
missno sequence named <seq> on the desk, no timecode named <tc>, no Helm group <tc> in its timecode, no timecode track for <seq> in <tc>; tracks target: <TARGET as read>; …, or no event of cue <n> near <old> s; <N> event(s) on the track: <destination>@<time>=<cue>; …; TARGET=<as read>. The first three events, in time order. <destination> is CUEDESTINATION as read (cut at 40 characters); =<cue> is the cue it resolved to in this sequence, =? none. " reads '.

The desk’s command line shows the same line. /tmp/helm_events.txt on the desk gets every event of that track and, once per session, every property of its first event.

Plugin 0.16.6. The MA3 manual (Timecode > Events) says an event carries a token, “the actions an event triggers”, and Flash a status On or Off; it names no Lua property. The plugin sets the event’s CUEDESTINATION first, then the token, so a destination set later cannot put it back to the default. It tries, in order:

  1. Keys TOKEN, Token, token.
  2. For each key: the spellings (Go+, Go +, GoPlus, Go for Go+; Goto, GoTo, Go To; the others as named), the full keyword from GetTokenName, then the keyword index from GetTokenNameByIndex, as a number and as text.
  3. After each, Get(key) and Get(key, Enums.Roles.Edit / Display) (lua_object_get: a role “will make the returned value a text string”).

A read-back counts when its letters name the token (goplus = go+), when it is an index GetTokenNameByIndex names it by, or a short keyword GetTokenName expands to it. The winning key and form serve the rest of the import. Goto only after every key and form; nothing readable at all keeps the first spelling, counted as unverified. Plugin 0.16.5 compared the read-back with the exact text it sent and wrote Goto over anything else: Go cues imported as Goto (2026-10-22). A retarget keeps the event’s token.

Plugin 0.16.6. The desk sends /Helm/diag lines after every Import (the tracks it built) and on Plugin "<tile>" "diag" (every track, plus the last import’s token lines). Each under 200 characters; | , ; " in a name become spaces.

LineHolds
cues|<seq>|<k>/<n>|<cue>:<name>|…Part k of n of the sequence’s cues, as the desk has them (cue 0 left out)
cues|<seq>|missingNo sequence of that name
event|<tc>|<seq>|<k>/<n>|<PROP>=<value>|…The first event of that track’s timecode track for the first stack with cues: every property PropertyCount / PropertyName give, Get, plus the Edit role’s text in brackets where it differs
event|<tc>|<seq>|none|<why>No such event
token|<wanted>|<key>=<read back>|<verdict>Once per token per import: set, fallback (Goto), refused (Flash) or unverified

Cues logs each as MA3 diag <desk>: <line>. It joins a sequence’s parts (30 s to arrive) and compares the list with what it means for that sequence: per desk cue number, the name of the first cue firing it, as a push names it. A cue Cues has no name for is not compared; desk cues Cues does not fire are left alone. Each difference is logged (MA3 diag 10.0.0.20: "01 - Main" desk cue 1 is "", Cues has "SET UP") and marks the track Import needed on that desk with the first one as the reason. A missing sequence does too. A token line other than set also logs a warning.

Cues writes a desk cue’s Command only when it has something for it, and records what it wrote per stack. It clears only what it recorded. A Command typed on the desk is never touched.

Cues hasCommand written
A fader levelFaderMaster Sequence "05 - Main" At 100 Fade 2
Desk GO drives Cues, Play from the cueLua "if HelmStart then HelmStart([==[Blue]==],[==[05 - Main]==],3) end"
Desk GO drives Cues, Line up on the cueLua "if HelmGoto then HelmGoto([==[Blue]==],[==[05 - Main]==],3) end"
BothFaderMaster Sequence "05 - Main" At 100 Fade 2; Lua "if HelmStart then HelmStart([==[Blue]==],[==[05 - Main]==],3) end"
  • The call names the track, the stack’s sequence and the desk cue, by name. Names go in Lua long strings ([==[…]==]): the Command already nests '…' around "…".
  • HelmStart / HelmGoto are plugin globals. They send /Helm/start,s,Blue|05 - Main:3 (or /Helm/goto) on the Helm OUT row the plugin finds by port, and append the same line to the show file bridge. Cues turns the sequence name back into the stack. The if keeps a desk without the plugin loaded quiet.
  • Sent as Set Sequence "05 - Main" Cue 3 Property "Command" '<command>': a Command holding double quotes goes in single quotes, one quote style nested in the other. ; chains the two commands (MA3 manual: Semicolon keyword).
  • Which desk cues: every desk cue of the track’s Main stack (the stack on its Starting Sequence #), or of every visible stack. Not a desk cue fired more than once in its stack, and not a Flash, On, Off, Kill or Top cue.
  • Rewritten on every push and Import, never live: a renumbered, moved or deleted cue, a renamed stack or track, the switch off or a scope change. A change marks the track Import needed.
  • Cues command… in the plugin (0.16.5) builds the same call for a track and a cue of its Main stack, and can write it into any cue the operator names: Set Sequence "<name>" Cue <n> Property "Command" '<Command>; <call>', after the cue’s own Command, read back, then the cue object’s own Set if the command line form did not take. Cues does not record it. On a desk cue Cues writes, Cues’ next push replaces it. The plugin writes nothing when it cannot read the cue’s Command.

There are none. MA3 prints every command a desk runs on its command line, Helm’s own included, so neither side runs anything on a clock (Cues 0.2.18, plugin 0.16.12).

WhatRule
Joined, in CuesFrom the desk’s hello until it says hello with no session (Disconnect), three sends to it fail in a row, or Cues restarts. lastHeard (Devices, the MA3 chip, ma3_connected_desks) is when anything last arrived from it.
Hello, from the deskA press (any Helm tile or control macro), before an Import (not when one went in the last 10 s), Connect, Setup Desk, Disconnect. Never on a clock, never on Cues’ tick.
HELM CONNECT, on the deskChanges only on an exchange: an answered hello, a completed Import (· <session>), an Import with no answer or a stall (· NO LINK), Disconnect, Cues’ Allow / Remove / stop hosting.
Cues quitsThe desk finds out at its next Import or press.
Desk assigned to a peerThe peer gone: its commands are dropped and Devices reads Joined · driver gone. The desk is not told.
Session beaconEvery 2 s, Cues to Cues on UDP 8005. The desk does not hear it.
  • A 0.16.11 or older plugin with this Cues reads HELM CONNECT · OFFLINE 30 s after Cues starts: its watchdog waits for a heartbeat that no longer comes. 0.16.12 pins that old watchdog’s state so a timer left running in the desk’s Lua state stays silent.

On the computer running grandMA3:

FileHolds
/tmp/helm_setup.txtThe Setup Desk log
/tmp/helm_events.txtOne line per timecode event the plugin builds: wanted=<token> <key>=<read back>, FALLBACK Goto (…) or UNVERIFIED. Plugin 0.16.6: every token try (token try wanted=Go+ TOKEN=Go+ (string) read=…) and every /Helm/diag line (DIAG …). Plugin 0.16.5: each missed edit, with every event of that track (<destination> @<time> = <cue>) and, once per session, every property of one event.
/tmp/helm_oscdump.txtThe desk’s OSC rows, before and after the plugin’s repair. Rewritten on every tile press, by Setup Desk and by diagnose.

OSC rows look right but nothing flows? Run:

Plugin "Helm 0.16.6" "diagnose"

It repairs empty OSC rows, writes /tmp/helm_oscdump.txt and shows the transport bridge state. "oscrestart" restarts the OSC engine.

On the Cues Mac, with onPC on the same Mac: onPC holds UDP 8001. Cues then reads the desk through /tmp/helm_hello.txt, helm_pull.txt, helm_transport.txt, helm_track.txt, helm_select.txt, helm_autosync.txt, helm_edit.txt and helm_show.txt, every 300 ms. The Desk card reads File bridge only · MA3 holds UDP 8001 on this Mac. A desk on another machine cannot reach this Cues then. The plugin writes no file for /Helm/edited: a missed edit on that onPC is not resent.

Per Mac, unless it says the cloud.

FailureNext try
1st1 min
2nd5 min
3rd15 min
4th60 min
5thStops: Upload failed. Only Retry starts it again, with 5 more tries.
  • The count survives a relaunch. It is kept with the project’s media list on this Mac and never synced.
  • Not a try: a pause, a cancel, a rate limit (waits at least 60 s), signed out, storage full.
  • Signed out or storage full stops every upload of the project with one message.
  • Inside one try, each request (a part, a link, start, finish) gets 5 attempts.

One pause holds every upload.

CausePauseLifted by
Cloud rate limit (HTTP 429)Its Retry-After, at most 5 min. 60 s when it gives none.Waiting it out
10 network, stall or server failures in a row10 minResume
Daily limit. Default 10 GB.Until local midnightResume sends anyway for the rest of the day
  • Only progress resets the failure count: bytes landed, a part confirmed, a file finished.
  • The ledger counts every byte sent, failed tries included, per local day.
FileSent as
Up to 100 MiBOne transfer
Over 100 MiB64 MiB parts. 3 transfers at once, across all uploads.
Over 100 MiB, link under 2 MB/s16 MiB parts on a new upload. One transfer at a time.
  • Stalled: a transfer still running after 60 s plus its size at 128 KiB/s. It counts as a network failure.
  • Resume: open uploads are kept in cloud-uploads/upload-resume.json. After a relaunch Cues asks the cloud which parts it holds and sends the rest.
  • Each file streams to <name>.part beside its final name, hashed as it lands. It is renamed only when its sha256 matches.
  • No bytes for 60 s: the download fails. Retry in the toast.
LimitValue
Requests600 a minute per paired device. Over it, Cues waits as told, at most 60 s, and retries up to 3 times.
Server errors502, 503 and 504 retried after 1, 2 and 4 s
Upload attempts per fileCapped per UTC day: 20 starts, and part links scaled to the file’s size. Past it, that file waits until 00:00 UTC. The rest carry on.
Idle deviceUnused for 90 days: signed out. Pair again.
LimitValue
Members per host16. The 17th is told the session is full.
Devices waiting, not yet members4 at once, 2 per address. A newcomer past the cap closes the oldest waiting one. Members are never displaced.
Passcode6 digits
Passcode tries per address3 at once, then 1 a second
Passcode tries per host10 at once, then 4 a second, across all addresses
After a wrong code or a DenyThat device waits 1, 2, 4, 8, 16, then 30 s
Lockout10 wrong codes or Denies from one device: locked until the passcode changes
Code change20 wrong codes across all devices draw a new passcode. At most once every 5 min.
WhatTiming
BeaconEvery 2 s on UDP 8005
Playhead to peers30 times a second rolling, twice a second parked
Joined · quiet n sNothing from a member for over 3 s
LINK LOST on a peerNo packet for 1 s rolling, 3 s parked. The playhead freezes.
Desk assigned to a peerONLINE only while that peer was heard in the last 3 s
RuleValue
Files at once2 per peer, 6 across the host
CheckSize and sha256. A file the host lacks, or one that fails, comes from the cloud. That host is not asked for it again that session.
Slow linkThe host resets a transfer still running after 60 s plus 1 s per MB
Wheresessions/media/ on the peer
  • Where: com.elabs.helm.cues/licence.json. See Files on disk.
  • Signed. The cloud signs each answer (Ed25519). Cues checks the signature on every read. A cache that fails the check is deleted.
  • Offline: valid for 7 days from the last good check. Then free rules.
  • Devices: a licence covers 3 devices by default.
  • Versions: a licence covers releases up to its major version.
  • In logs and bundles the key is cut to its first 8 characters, 4 for a short key.

In the log, each launch writes licence key=<first 8>… valid=… tier=… scopes=n checked_at=… and entitlements mode=… fetched_at=… verified=…. entitlements: none cached means no signed answer is on this Mac yet.

  • One file per UTC day, logs/cues.YYYY-MM-DD.log, 7 kept. INFO and up.
  • Each launch logs Helm Cues <version> (<build>, <os> <arch>) os=<version> pid=<pid> data=<folder>, the log folder, the licence and the cached entitlements.
  • Upload warnings come once a minute per file, with a count: [cloud:upload] <file>: <kind> ×n in the last minute.
  • Upload links never reach the log. On launch Cues scrubs links from older logs and logs [cloud] log scrub: n lines with presigned-URL values redacted in n files.

Help → Save Support Bundle… writes helm-cues-support-YYYY-MM-DD-HH-MM-SS.zip, stamped in UTC.

FileHolds
report.txtThe Diagnostics report, as Copy report gives it
device.jsonDevice ID, version and build, memory, licence summary, entitlements
state.jsonTimecode, clocks, session and MA3 bridge, as they stood
settings.jsonThe Cues settings
config.jsonThe grandMA2 plugin server: allowlist, port, flags
devices.jsonEvery audio and MIDI input and output
log-tail.txtThe last 200 log lines
logs/Every log file kept
crashes/Every crash report
show/<id>.jsonThe open show
  • Never in it: the full licence key, the Helm Cloud token (it stays in the keychain), the Sessions certificate and key, the session passcode, media.
  • Every file is redacted on the way in. Signed upload links read X-Amz-Credential=<redacted>.
  • It does hold desk and peer IP addresses, device names, the masked email, settings and the open show with its track and cue names.

crashes/<time>.txt holds the time, version, uptime, thread, message, location, a backtrace and the last 200 log lines.

See Diagnostics and support.

So nobody plans a show around it:

  • Outputs beyond the desk bridge, timecode and MIDI Clock: no Art-Net DMX or sACN, no MIDI Show Control, no per-cue MIDI or OSC output, no OSC remote-control API, no MIDI-in GO.
  • LTC in. The reader exists in the engine. Nothing in the interface starts it.
  • Automatic failover. A standby takes over only when someone presses TAKE twice. See Sessions.
  • Windows. Windows build not yet available.
  • Crash upload. Crash files stay on the Mac until you send a bundle.
  • Proxies or transcodes. A peer gets full-size media, from the host or the cloud.