Docs menu
Guides

Timelines

Build timecode timelines and cue lists, run the transport, chase incoming timecode and generate timecode outputs.

A timeline fires cues at set moments. Each cue sends a device action or an intent. Tangle core plays timelines on a dedicated timing thread, so cues fire on time even when the network is busy, and the same timeline runs in lockstep on every node of a redundant cluster.

Modes

Every timeline has a mode:

ModeWhat it does
timecodeFree-running. After play, cues fire at their position (atMs) on the timeline.
cuelistWaits for GO. Each go fires the next cue, in time order, and the timeline waits again.

Use timecode for shows that run to a clock: a pre-programmed sequence, a show synced to video or audio, a looping attract loop. Use cuelist when an operator calls each step, as in theatre.

In both modes the state reports the last cue fired (lastCueId) and the next one (nextCueId).

Tracks and cues

A timeline holds tracks, and each track holds cues. Tracks group cues for editing (lights, video, sound). A muted track does not fire its cues.

{
  "id": "show",
  "name": "Main show",
  "mode": "timecode",
  "frameRate": "25",
  "durationMs": 120000,
  "loop": false,
  "tracks": [
    {
      "id": "video",
      "name": "Video",
      "cues": [
        {
          "id": "c1",
          "name": "Projectors on",
          "atMs": 0,
          "payload": { "kind": "intent", "intent": { "name": "power.on", "target": { "kind": "room", "id": "room-a" } } }
        },
        {
          "id": "c2",
          "name": "Start clip",
          "atMs": 2520,
          "preRollMs": 200,
          "payload": { "kind": "action", "deviceId": "media-1", "capability": "tangle.transport", "action": "play" }
        }
      ]
    }
  ]
}

Timeline fields:

FieldRequiredMeaning
id, nameyesIdentifier and display name.
modeyestimecode or cuelist.
tracksyesTracks and their cues.
clocknoWhat drives the timeline. Default { "kind": "internal" }. See Clock sources.
frameRateno24, 25, 29.97df, 30, 50 or 60. Used for frame snapping in the designer and for timecode.
durationMsnoLength of the timeline. At the end it stops, or starts again from 0 if loop is true.
loopnoStart again from 0 at durationMs.
timecodeOutputsnoDevices that generate timecode following this timeline. See Timecode generation.

Cue fields:

FieldRequiredMeaning
idyesUnique within the timeline.
atMsyesPosition from the start of the timeline, in milliseconds.
payloadyes{ "kind": "intent", "intent": {...} }, { "kind": "action", "deviceId", "capability", "action", "params" }, or { "kind": "comment", "text" }: a note that plays nothing. A cue whose device was deleted with Keep timeline cues as comments becomes a comment cue that remembers the device and the action, so Point at device… can turn it back into a cue.
namenoDisplay name.
preRollMsnoSend the cue this many milliseconds early to absorb device latency. The device still gets the exact intended time as its execution time.
enablednofalse skips the cue. Default true.

Cues fire as intents with source timeline, so intent priorities and holds apply. Timelines use priority 50.

Note: A timeline with no durationMs keeps playing after its last cue until you stop it.

Clock sources

clock.kindStatus
internalDefault. Core's own clock.
timecodeChase a timecode input device, or the playhead of a media player or server. See Timecode chase.
wallclockFollow the time of day. See Time of day.
ptpRun at the rate of a PTP grandmaster. See PTP.
ltc, mtcOlder forms, not played. Use timecode with an LTC or MTC input device instead.

Transport

The transport operations are:

OperationTimecode modeCue list mode
playStarts playing from the current position.Arms the list: status waiting until GO.
pauseHolds the current position.Pauses the list.
stopReturns to 0 and forgets which cues fired.Returns to the first cue.
seekJumps to positionMs. Cues skipped over do not fire. A stopped timeline becomes paused at the new position.Moves to positionMs; cues before it count as played.
goNot used (returns unsupported).Fires the next cue. After the last cue the list stops.
loopSet loop: true. At durationMs the timeline starts again from 0, re-anchored on the exact end time so loops never drift.Not used.

After a pause, play resumes from the same position and no cue before it fires again. A cue that sits exactly at the new position of a seek fires right away.

Timeline status is one of stopped, playing, paused or waiting (a cue list waiting for GO, or a chasing timeline waiting for timecode).

Sending transport commands

Transport needs a token with the control scope.

REST:

curl -X POST http://127.0.0.1:7340/api/v1/timelines/show/transport \
  -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" \
  -d '{"op":"seek","positionMs":30000}'

The answer is the new timeline state. GET /api/v1/state/timelines returns the state of every timeline.

WebSocket:

{ "v": 1, "id": "42", "type": "timeline", "control": { "op": "go", "timelineId": "cues" } }

MCP: control_timeline and get_timeline_states. An assistant whose token lacks the live scope cannot change anything while a timeline plays. See MCP.

Automations can also drive the transport with a timeline action. See Automations.

Timeline state

Core publishes a timeline.state event (topic timeline.state:<timelineId>) on every change and 10 times per second while playing:

{
  "timelineId": "show",
  "status": "playing",
  "positionMs": 31250.0,
  "sampledAt": 1760362800000,
  "lastCueId": "c7",
  "nextCueId": "c8"
}

sampledAt is the wall-clock time at which positionMs was measured, so a client can move its playhead smoothly between events. Chasing timelines also report chasing and clockLost, time-of-day timelines chasing, and PTP timelines clockLost.

Editing in the designer

Open Timelines in the designer (/designer/timelines) and choose New timeline.

  1. Enter a name and choose the mode: Timecode or Cue list (GO).
  2. Set the clock, frame rate, duration and loop as needed.
  3. Add cues:
    • Timecode mode shows tracks as rows on a zoomable time ruler. Double-click a track to add a cue, and drag a cue to move it. Cues snap to frames, or to 100 ms when no frame rate is set.
    • Cue list mode shows cues as an ordered list in GO order. Use the up and down arrows to reorder them.
  4. Select a cue to edit its name, time, pre-roll, enabled flag and what it does: A device action or An intent.
  5. Choose Create timeline or Save.

Use Add track to add tracks. Each track has a name, a mute switch and a delete button.

Live control

The transport buttons (Play, Pause, Stop, or GO for a cue list) and seeking by clicking the ruler stay disabled until you tick Live control for the session. This prevents an accidental click from starting or moving a running show.

Editing while a timeline plays

While a timeline plays, the editor is read-only. Tick Allow live editing to change it anyway: changes apply to the running show. Through the REST API the same rule applies: a change to a playing timeline is refused with 409 timeline_running unless the request adds ?allowLive=true.

When a timeline is edited during playback, cues keep their "already fired" flag by id. A new cue placed before the current position counts as played.

Timecode chase

A timeline can follow incoming timecode. The input is any device with the tangle.timecode capability (LTC, MTC, RTP-MIDI, Kissbox or Art-Net timecode modules), or a media player or server with the tangle.playhead capability: VLC follows the clip it plays, Pixera has one playhead per timeline. Chasing and generating timecode need Tangle Pro or Ultimate (see Licensing).

{
  "id": "show", "name": "Show", "mode": "timecode", "frameRate": "25",
  "clock": { "kind": "timecode", "deviceId": "ltc-in", "offset": "01:00:00:00", "freewheelMs": 500 },
  "tracks": []
}
Clock fieldMeaning
deviceIdThe timecode input device, or the media player or server.
playheadFor a device with several playheads: which one to follow, for example the Pixera timeline Pre-show. Empty: the device's main playhead.
offsetThe timecode at which the timeline is at 0. Default 00:00:00:00. Format HH:MM:SS:FF (; before the frames for drop-frame).
freewheelMsHow long the timeline keeps playing when frames stop arriving, before it waits. Default 500, maximum 10000.

How chasing behaves:

  • Arming. play arms the timeline (chasing: true). Its status is waiting until timecode runs. pause and stop disarm it.
  • Following. Cues fire on the source's time. Core measures from the earliest-arriving frames, so pipe and network jitter do not move cues. Small drift moves the timeline smoothly.
  • Jumps. When the source jumps (two frames that agree on a new position), the timeline relocates there. Cues before the new position count as played.
  • Parked source. A source that is not running positions the timeline at its timecode.
  • Before the start. Timecode earlier than offset holds the timeline at 0.
  • Dropout. Without frames the timeline keeps playing for freewheelMs, then waits where the timecode stopped (status paused, still armed).
  • Lost signal. When the input reports no signal, or goes offline or times out, the state shows clockLost: true. The designer shows a "no timecode" badge.
  • End. At durationMs a chased timeline waits instead of stopping or looping. The source decides what comes next.

To set this up in the designer, choose Clock: Chase timecode (LTC, MTC, Art-Net, media servers), then pick the input device (and, for a media server, which of its timelines), the Start timecode and Freewheel (ms).

Automations and expressions read playheads too: device.timecode("pixera", "Pre-show"), device.position("vlc"). Dashboards show them with the Clock control.

Time of day

A timeline with a wallclock clock uses the time of day as its position: a cue at atMs 32400000 (09:00:00) fires at 9 am every day. Use it for building and room schedules.

{
  "id": "building-day",
  "name": "Building day",
  "mode": "timecode",
  "clock": { "kind": "wallclock", "timezone": "America/Montreal" },
  "tracks": []
}

timezone is an IANA zone name. Leave it empty to use the zone of the computer running core. An unknown zone makes play return an unsupported error naming it.

How it behaves:

  • Arming. play arms the timeline (chasing: true) and moves it to the current time of day. Cues earlier in the day do not fire. pause and stop disarm it. seek returns unsupported.
  • Every day. At midnight the timeline starts again from 0, and the day's cues fire again. durationMs and loop are ignored.
  • Clock changes. Core checks the timeline against the clock every second and moves it when they differ by more than 5 ms. When the clock moves forward by up to 65 minutes (a daylight-saving change), the cues in between fire at once, in order. A larger jump skips them. When the clock moves back, cues that already fired do not fire again.
  • After a restart. A time-of-day timeline does not arm by itself. To have it run whenever core starts, add an automation with the Core starts trigger that plays it.

In the designer, choose Clock: Time of day and set the Time zone.

PTP

A timeline with a ptp clock plays like the internal clock (play, pause, seek, loop), but at the rate of a PTP grandmaster. Over a long show it stays in step with media servers and other systems locked to the same grandmaster, instead of drifting with the computer's own clock (typically up to a few ms per minute).

"clock": { "kind": "ptp", "deviceId": "ptp-gm" }
Clock fieldMeaning
deviceIdThe PTP clock reference device (misc.ptp.timecode) to follow.
domainWhen deviceId is not set: follow the first PTP clock reference device on this domain. Default 0.

How it behaves:

  • Following. Core reads the device's offset to the grandmaster (a few times a second). When the timeline drifts more than 1 ms from PTP time, it moves by the drift. Cues fire on PTP time.
  • Steps. A change of 20 ms or more at once is a new reference (another grandmaster, or the computer's clock set by hand), not drift: the timeline keeps its position.
  • Not locked. When the device is not locked, offline or timed out, the timeline keeps playing on the computer's clock and the state shows clockLost: true. The designer shows a "PTP not locked" badge.
  • Redundancy. Leave the PTP device unpinned (no node), so it runs on the primary core.

In the designer, choose Clock: PTP and pick the PTP reference. PTP timelines need Tangle Pro or Ultimate.

Timecode generation

timecodeOutputs lists timecode output devices (tangle.timecode) that follow the timeline:

"timecodeOutputs": [
  { "deviceId": "mtc-out", "offset": "01:00:00:00" },
  { "deviceId": "artnet-tc" }
]

offset is the timecode sent when the timeline is at 0 (default 00:00:00:00).

Every transport change (play, pause, seek, loop, chase jump) is sent to each output:

  • While playing: start with the timecode of the next frame boundary about 40 ms ahead, and the exact time to start.
  • Otherwise: stop, parked on the current position.

Outputs report their own timecode. An output that drifts more than 2 frames (for example a sound card clock) is sent the timecode again.

A chased timeline with outputs works as a converter, for example LTC in, MTC and Art-Net timecode out.

In the designer, timecode outputs appear under Generate timecode on for timecode-mode timelines. Tick each device and enter its start timecode.

Frame rates

Timecode is counted at the rate the device reports (its fps state). When a device does not report one, core uses the timeline's frameRate, then 25. 29.97df is drop-frame: frame numbers 0 and 1 are skipped every minute except every tenth, so the count stays in step with real time.

Behaviour under redundancy

In a redundant cluster every node plays every timeline in lockstep on a shared cluster clock. Only the primary sends cues to devices; backups fire the same cues in shadow and record them in their cue log.

  • Transport commands go to the primary. A backup answers 409 not_primary.
  • If the primary fails, the new primary continues from its own position. It sends cues that the old primary may not have sent, if they were due in the last 2 seconds. No cue fires twice.
  • Only the primary chases timecode and drives timecode outputs. Backups replay its decisions, so their cue logs match. After a failover, the new primary sends the transport to every output again and starts its own chase.

Limits by edition

LiteProUltimate
Timelines and cue lists2 in totalunlimitedunlimited
Timecode chase and generationnoyesyes

A project that goes over its edition is refused at load, with a message naming the limit. See Licensing.