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:
| Mode | What it does |
|---|---|
timecode | Free-running. After play, cues fire at their position (atMs) on the timeline. |
cuelist | Waits 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:
| Field | Required | Meaning |
|---|---|---|
id, name | yes | Identifier and display name. |
mode | yes | timecode or cuelist. |
tracks | yes | Tracks and their cues. |
clock | no | What drives the timeline. Default { "kind": "internal" }. See Clock sources. |
frameRate | no | 24, 25, 29.97df, 30, 50 or 60. Used for frame snapping in the designer and for timecode. |
durationMs | no | Length of the timeline. At the end it stops, or starts again from 0 if loop is true. |
loop | no | Start again from 0 at durationMs. |
timecodeOutputs | no | Devices that generate timecode following this timeline. See Timecode generation. |
Cue fields:
| Field | Required | Meaning |
|---|---|---|
id | yes | Unique within the timeline. |
atMs | yes | Position from the start of the timeline, in milliseconds. |
payload | yes | { "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. |
name | no | Display name. |
preRollMs | no | Send the cue this many milliseconds early to absorb device latency. The device still gets the exact intended time as its execution time. |
enabled | no | false 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
durationMskeeps playing after its last cue until you stop it.
Clock sources
clock.kind | Status |
|---|---|
internal | Default. Core's own clock. |
timecode | Chase a timecode input device, or the playhead of a media player or server. See Timecode chase. |
wallclock | Follow the time of day. See Time of day. |
ptp | Run at the rate of a PTP grandmaster. See PTP. |
ltc, mtc | Older forms, not played. Use timecode with an LTC or MTC input device instead. |
Transport
The transport operations are:
| Operation | Timecode mode | Cue list mode |
|---|---|---|
play | Starts playing from the current position. | Arms the list: status waiting until GO. |
pause | Holds the current position. | Pauses the list. |
stop | Returns to 0 and forgets which cues fired. | Returns to the first cue. |
seek | Jumps 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. |
go | Not used (returns unsupported). | Fires the next cue. After the last cue the list stops. |
| loop | Set 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.
- Enter a name and choose the mode: Timecode or Cue list (GO).
- Set the clock, frame rate, duration and loop as needed.
- 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.
- Select a cue to edit its name, time, pre-roll, enabled flag and what it does: A device action or An intent.
- 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 field | Meaning |
|---|---|
deviceId | The timecode input device, or the media player or server. |
playhead | For a device with several playheads: which one to follow, for example the Pixera timeline Pre-show. Empty: the device's main playhead. |
offset | The timecode at which the timeline is at 0. Default 00:00:00:00. Format HH:MM:SS:FF (; before the frames for drop-frame). |
freewheelMs | How long the timeline keeps playing when frames stop arriving, before it waits. Default 500, maximum 10000. |
How chasing behaves:
- Arming.
playarms the timeline (chasing: true). Its status iswaitinguntil timecode runs.pauseandstopdisarm 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
offsetholds the timeline at 0. - Dropout. Without frames the timeline keeps playing for
freewheelMs, then waits where the timecode stopped (statuspaused, 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
durationMsa 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.
playarms the timeline (chasing: true) and moves it to the current time of day. Cues earlier in the day do not fire.pauseandstopdisarm it.seekreturnsunsupported. - Every day. At midnight the timeline starts again from 0, and the day's cues fire again.
durationMsandloopare 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 field | Meaning |
|---|---|
deviceId | The PTP clock reference device (misc.ptp.timecode) to follow. |
domain | When 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:
startwith 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
| Lite | Pro | Ultimate | |
|---|---|---|---|
| Timelines and cue lists | 2 in total | unlimited | unlimited |
| Timecode chase and generation | no | yes | yes |
A project that goes over its edition is refused at load, with a message naming the limit. See Licensing.