Reference
OSC and WebSocket API
Draft. This API is planned for a coming release and isn’t in the current preview build. Addresses may change before then.
Overview
Chronos Studio accepts OSC commands to run the show and pushes live status back to anyone who subscribes. Consoles, QLab, Bitfocus Companion, TouchOSC or a tablet can drive it and show its state without touching the Chronos screen.
- Two directions: control in (transport, locate, sources, outputs, Chronos Sync, Chronos Cue) and live status out (timecode, lock, transport, sync health, cues fired).
- Separate from Chronos Cue’s own OSC events. Cue sends whatever addresses the show designer programs at set timecodes. This API is Chronos’s own fixed address space.
- Status is for display, not sync. OSC over a network can’t hold frame accuracy, so machines still sync over LTC, MTC or Art-Net.
- Versioned: every address starts with
/chronos/./chronos/versionreplies with the API version (1.0) and the app version. Breaking changes would move to/chronos/v2/. - Licence-aware: addresses for modules the licence doesn’t include reply
module_lockedinstead of acting.
Everything the app can do or show has an address. Every setting, every show-file edit and every value on screen can be read, changed and subscribed to over OSC. The only exceptions are licence keys and the OSC passcode itself, which stay in the app. Controllers can discover the full address tree with OSCQuery (see Show files, settings and devices).
Connection
Chronos listens on port 53800 for UDP, TCP, WebSocket and HTTP (changeable in Settings), and only on the network interface the operator picks.
| Transport | Framing | Use it for |
|---|---|---|
| UDP | One OSC packet per datagram (OSC 1.0) | Consoles, Companion, TouchOSC; control and live status |
| TCP | SLIP-framed OSC (OSC 1.1) | Controllers that need every reply delivered; show-control software |
| WebSocket | ws://<machine>:53800/chronos: binary frames = one OSC packet each; text frames = JSON {"address": "/chronos/transport/play", "args": []} |
Browser and web-app controllers, dashboards, scripts; same addresses, replies and subscriptions |
| HTTP | OSCQuery on the same port | Discovering the address tree (see Show files, settings and devices) |
- Replies go back to the sender’s address and source port. A controller that listens elsewhere sends
/chronos/replyport <i:port>once. - Discovery: Chronos advertises
_osc._udpand_osc._tcpover Bonjour/mDNS with the machine name, so controllers can find it without typing an IP. - Security (off by default):
- Allowed addresses: a list of IPs or subnets; everything else is ignored silently.
- Passcode: when set, a client sends
/chronos/connect <s:passcode>first. The session lasts until 60 s without traffic. - Read-only mode: status and queries work, control replies
not_allowed.
- Loopback: 127.0.0.1 is always allowed, for software on the same machine.
Conventions
Send an address with arguments to change something; send it with no arguments to ask for its current value.
- Addresses are lowercase,
/chronos/<area>/<item>, with ids as path parts where needed (/chronos/output/ltc1/enable). - Types:
sstring,iint32,ffloat32. Booleans are ints 0 or 1, since many controllers can’t send OSCT/F. - Timecode is a string
HH:MM:SS:FF, with;before the frames for drop-frame (01:00:00;00). Status messages also carry the absolute frame count as an int. - Rates are strings:
23.976,24,25,29.97df,29.97,30. - Ids are short and stable: outputs
ltc1,mtc1,artnet1; sourcesltc1,mtc1,artnet1,link,internal. - Replies: every message gets
/chronos/reply/<original address>followed bys:okand any values, ors:error,s:<code>,s:<message>./chronos/replies <i:0|1>turns replies off for fire-and-forget controllers. - Bundles are applied together in one frame, for example a rate change and a locate.
- Wildcards (
*,?,[ ],{a,b}from OSC 1.0) work on ids:/chronos/output/*/enable 0mutes every output.
Control: transport and generator
These run Chronos Core, so every licence has them (Chronos Edit accepts them but sends no live output).
| Address | Arguments | What it does |
|---|---|---|
/chronos/mode |
s: generate | chase | convert |
Sets how Chronos makes its clock |
/chronos/transport/play |
none | Runs the generator from the current position |
/chronos/transport/pause |
none | Holds the current frame, outputs keep sending it |
/chronos/transport/stop |
none | Stops and returns to the start time |
/chronos/transport/locate |
s: timecode |
Jumps to a time; keeps playing if it was playing |
/chronos/transport/nudge |
i: frames (signed) |
Moves the clock by whole frames |
/chronos/generator/start |
s: timecode |
Sets the start time that Stop returns to |
/chronos/generator/rate |
s: rate |
Sets the generator frame rate |
/chronos/generator/offset |
s: timecode, optional i: 1 for negative |
Global offset added before every output |
With no arguments, /chronos/mode, /chronos/generator/rate and the other setters reply with their current value.
Sources, chase and outputs
Sources and outputs are named by id; /chronos/source/list and /chronos/output/list reply with the ids this machine has.
| Address | Arguments | What it does | Module |
|---|---|---|---|
/chronos/source/select |
s: id |
Picks what Chronos chases (ltc1, mtc1, artnet1, link) |
Core; link needs Link |
/chronos/source/list |
none | Replies with every source id and whether it has signal | Core |
/chronos/chase/freewheel |
i: frames |
How long to keep running after the source drops | Core |
/chronos/chase/jam |
i: 0 | 1 |
Jam-sync: lock once, then run on the internal clock | Core |
/chronos/output/list |
none | Replies with every output id and whether it’s on | Core |
/chronos/output/<id>/enable |
i: 0 | 1 |
Turns one output on or off | Core |
/chronos/output/<id>/offset |
s: timecode, optional i: 1 for negative |
Offset for that output only | Core |
/chronos/output/<id>/rate |
s: rate |
Output frame rate when converting | Core |
/chronos/link/follow |
s: master | deck1 … deck6 |
Which CDJ or mixer channel Chronos Link follows | Link |
Tracks and markers
Chronos can say what is playing: the track on each DJ deck (Chronos Link) and the named sections of the show timeline (markers such as songs or scenes, saved in the show file). Query them here, or subscribe to the track topic under Live status.
| Address | Arguments | What it replies | Module |
|---|---|---|---|
/chronos/track/now |
none | What Chronos is following: s: source (deck2, marker), s: title, s: artist, f: BPM, s: position, s: remaining |
Core (markers); Link (decks) |
/chronos/track/next |
none | The next marker, or the track loaded on the other deck: s: title, s: starts at |
Core; Link |
/chronos/link/decks |
none | Every deck: s: deck, s: title, s: artist, f: BPM, s: key, i: on air |
Link |
/chronos/marker/list |
none | Every marker: s: number, s: name, s: start timecode, s: end timecode |
Core |
/chronos/marker/<n>/locate |
none | Jumps the generator to that marker’s start | Core |
/chronos/marker/add |
s: name, optional s: timecode (default: now) |
Adds a marker to the show file, e.g. while rehearsing | Core |
Titles and artists come from the DJ gear’s own metadata; Chronos only reports them.
Show files, settings and devices
Everything on the app’s settings screens is one settings tree, so a controller can read or change any of it with two addresses, without the API growing a new address for each option.
| Address | Arguments | What it does |
|---|---|---|
/chronos/settings/get |
s: path (e.g. output/ltc1/level, or / for all) |
Replies with the value (or every path and value under it) |
/chronos/settings/set |
s: path, then the value |
Changes a setting; replies with the value actually applied |
/chronos/settings/reset |
s: path |
Back to the default |
/chronos/show/new |
optional s: name |
New empty show |
/chronos/show/open |
s: name (from the show list) |
Opens a saved show |
/chronos/show/list |
none | Replies with saved show names and when each was saved |
/chronos/show/save / saveas |
none / s: name |
Saves the show |
/chronos/show/revert |
none | Drops unsaved changes |
/chronos/undo / redo |
none | Same as the app’s Undo and Redo, for any change made by hand or over OSC |
/chronos/device/audio |
none | Replies with audio inputs and outputs (for LTC) |
/chronos/device/midi |
none | Replies with MIDI ports (for MTC and MSC) |
/chronos/device/network |
none | Replies with network interfaces and addresses (for Art-Net and OSC) |
/chronos/view/page |
s: main | outputs | cue | sync | settings |
Switches what the app shows |
/chronos/view/fullscreen |
i: 0 | 1 |
Full-screen clock display |
/chronos/view/lock |
i: 0 | 1 |
Locks the app’s own controls, so only OSC can change the show |
/chronos/app/identify |
none | Flashes the clock on screen, to find which machine is which |
Discovery: Chronos also answers OSCQuery over HTTP on the same port. It returns every address with its argument types, ranges, current value and a description, so tools like Companion or TouchOSC can build a control page on their own. /chronos/describe <s:path> returns the same over plain OSC.
Chronos Sync
The manual switch to a warm spare works on every edition; automatic failover needs Studio, the Event Pass or the Preview. Send these to either machine; Chronos forwards them to its partner.
| Address | Arguments | What it does | Needs |
|---|---|---|---|
/chronos/sync/role |
none | Replies primary or backup, and the partner’s name |
backup |
/chronos/sync/take |
none | Manual switch: the backup takes over outputs now | backup |
/chronos/sync/giveback |
none | Hands outputs back to the primary | backup |
/chronos/sync/failover/arm |
i: 0 | 1 |
Arms or disarms automatic failover | failover |
/chronos/sync/health |
none | Replies partner reachable, clock difference in frames, last heartbeat age in ms | backup |
A manual take or giveback happens on a frame boundary so the timecode doesn’t jump.
Chronos Cue (Studio only)
These control the timecode-triggered event list; on Stage they reply module_locked. Events are numbered as in the app (12, 12.5) and belong to the active list.
| Address | Arguments | What it does |
|---|---|---|
/chronos/cue/lists |
none | Replies with the event list names |
/chronos/cue/list/select |
s: name |
Makes a list active |
/chronos/cue/arm |
i: 0 | 1 |
Arms or disarms the whole list (disarmed = nothing fires) |
/chronos/cue/<n>/enable |
i: 0 | 1 |
Skips or restores one event |
/chronos/cue/<n>/fire |
none | Fires one event now, ignoring its timecode |
/chronos/cue/<n>/time |
s: timecode |
Moves an event’s trigger time |
/chronos/cue/next |
none | Replies with the next event’s number, name and trigger time |
/chronos/cue/panic |
none | Disarms the list and sends each output’s “all off” (MIDI all notes off, Art-Net blackout if set) |
The OSC, MIDI, MSC and Art-Net DMX messages that events send are part of the show file, not this API.
Editing events over OSC (Studio), so a console or script can build and change the list:
| Address | Arguments | What it does |
|---|---|---|
/chronos/cue/list/new |
s: name |
Creates an empty list |
/chronos/cue/list/rename |
s: old name, s: new name |
Renames a list |
/chronos/cue/list/delete |
s: name |
Deletes a list (not the active one) |
/chronos/cue/new |
s: number, s: name, s: timecode |
Adds an event to the active list |
/chronos/cue/<n>/name |
s: name |
Renames an event |
/chronos/cue/<n>/renumber |
s: new number |
Renumbers an event |
/chronos/cue/<n>/action/add |
s: osc | midi | msc | artnet, then that type’s fields (below) |
Adds a message the event sends |
/chronos/cue/<n>/action/<k>/set |
same fields as add |
Changes one of the event’s messages |
/chronos/cue/<n>/action/<k>/delete |
none | Removes one message |
/chronos/cue/<n>/actions |
none | Replies with every message the event sends |
/chronos/cue/<n>/duplicate |
s: new number, optional s: timecode |
Copies an event |
/chronos/cue/<n>/delete |
none | Deletes an event |
Message fields: osc = s: host, i: port, s: address, then its arguments; midi = s: port, s: note / cc / program, i: channel, i: number, i: value; msc = s: port, i: device id, s: command, s: cue number; artnet = i: universe, i: channel, i: value, optional i: fade ms.
Live status
A controller subscribes to the topics it wants, and Chronos pushes each change to it until the subscription lapses. Status goes to the address and port the subscription came from (or its /chronos/replyport).
- Subscribe:
/chronos/subscribe <s:topic> [<s:topic> …], orall. Chronos replies with the current value of each topic straight away. - Stay subscribed: a subscription ends after 10 s without any message from that controller;
/chronos/ping(reply/chronos/pong) or re-subscribing keeps it alive. TCP subscriptions last until the connection closes. - Unsubscribe:
/chronos/unsubscribe <s:topic>, orall. - Limit: 16 subscribed controllers per machine.
| Topic | Pushed as | Arguments | When |
|---|---|---|---|
timecode |
/chronos/status/timecode |
s: timecode, i: frame count, s: rate |
Every frame by default; /chronos/subscribe timecode <i:per second> for fewer (e.g. 10 for a display) |
transport |
/chronos/status/transport |
s: playing | paused | stopped | chasing, s: mode |
On change |
lock |
/chronos/status/lock |
s: locked | freewheel | lost, s: source id, i: freewheel frames left |
On change, plus every second while freewheeling |
sources |
/chronos/status/source |
s: id, i: signal present, s: rate detected, f: level dBFS (LTC), f: jitter ms |
Once a second per source |
outputs |
/chronos/status/output |
s: id, i: enabled, i: sending, s: timecode sent |
On change; timecode sent with the timecode topic rate |
sync |
/chronos/status/sync |
s: role, s: primary | backup has outputs, i: partner reachable, i: clock difference in frames, i: failover armed |
On change, and every second |
cue |
/chronos/status/cue |
s: number, s: name, s: timecode fired, s: next number, s: next timecode |
Each event fired; the list’s armed state on change (Studio) |
track |
/chronos/status/track |
s: source, s: title, s: artist, f: BPM, s: next title |
When the followed track or marker changes; /chronos/status/track/position (s: position, s: remaining) once a second |
decks |
/chronos/status/deck |
s: deck, s: title, s: artist, f: BPM, i: on air |
When any deck loads a track or goes on or off air (Link) |
settings |
/chronos/status/setting |
s: path, then the new value |
Any setting changed, in the app or over OSC |
show |
/chronos/status/show |
s: name, i: unsaved changes, s: last saved |
On open, save and first unsaved change |
devices |
/chronos/status/device |
s: audio | midi | network, s: name, i: connected |
When a device is plugged in or removed |
diagnostics |
/chronos/status/diagnostics |
f: CPU %, f: audio buffer ms, i: dropped frames, i: GC pauses |
Every 5 seconds |
log |
/chronos/status/log |
s: info | warning | error, s: message |
Each warning or error (info only if asked: /chronos/subscribe log info) |
licence |
/chronos/status/licence |
s: edition, s: valid until, i: offline days left, i: devices used, i: devices max |
On change and at subscribe |
clients |
/chronos/status/client |
s: address, s: name, i: connected |
When an OSC controller connects or its subscription lapses |
heartbeat |
/chronos/status/heartbeat |
i: uptime seconds, s: machine name |
Every second, always sent to subscribers |
Timecode pushes are best-effort UDP: good for displays and triggers, but a late packet is dropped rather than queued.
Licensing gates and errors
The OSC API follows the same licence as the app: nothing extra to buy, and module addresses lock with their module.
| Licence | What works over OSC |
|---|---|
| Chronos Edit (no licence) | Queries, status and programming; transport runs but outputs stay off |
| Chronos Stage | Core, Link and manual backup |
| Chronos Studio, Event Pass, Preview | Everything, including Chronos Cue and automatic failover |
| Error code | Meaning |
|---|---|
bad_address |
No such address or id |
bad_args |
Wrong number or type of arguments |
out_of_range |
Value not allowed (e.g. an unknown rate or a time past 23:59:59) |
module_locked |
The licence doesn’t include that module |
not_allowed |
Read-only mode, or no /chronos/connect when a passcode is set |
busy |
Chronos can’t do it right now (e.g. a Sync take while the partner is unreachable) |