The GUI protocol (/gui_*)

The GUI host is a separate peer, not part of the audio server: it owns the windows, the widgets and the GPU, and a script drives it over OSC — the same encoding the audio server speaks, only the vocabulary differs. Its default port is 57210 (clear of the audio server's 57110/57120), on UDP and TCP alike: like the audio server, the host accepts length-prefixed OSC over TCP by default (--no-tcp disables it, --max-frame sets the frame ceiling, default 16 MiB), and the Python GuiHost connects over TCP by default — so a /gui_def tree with its blobs, the largest payload in the system, is not bounded by a UDP datagram.

This page is the wire reference. The why behind it — the host's two roles, the declarative protocol, the GPU substrate, the composition views — is in Clients and language bindings; its internals and the recipe for adding a widget are in Architecture.

Commands

MessageMeaning
/gui_def id json [blob…]Build a whole widget tree in one message. json is the GuiDef document (below); trailing blobs carry bulk data a widget references by index. Re-sending an existing id redefines it (the old subtree is freed first), exactly as re-sending a SynthDef replaces it.
/gui_set id key value …Update one live widget's properties. Types are preserved (an OSC int stays an int). A value that is logically an array (a curve's break-points, a patch's wires) rides as its JSON string, since an OSC key/value is a scalar.
/gui_free idDestroy a widget and its subtree. Freeing a window-rooted def closes its window.
/gui_query idAsk for a widget's state. Replies /gui_info id type key value …; an empty type ("") means no such widget — the host answers either way, as the audio server replies even on a miss.
/gui_bind id "server" address prefix…Forward this widget's value straight to the audio server, bypassing the script: on every change the host sends address with the fixed prefix arguments followed by the value (e.g. "/n_set" 1001 "freq" makes the widget send /n_set 1001 freq <value>). A bound widget stops emitting /gui_event.
/gui_bind id(no target) Remove the binding; the widget emits events again.
/gui_load nameInstantiate a persisted GuiDef by name (the host replays it as its saved /gui_def). Needs a data directory.

There is no save command: a GuiDef whose root carries a name prop is persisted on /gui_def, the way a named def is persisted on /d_recv. That is what lets a host boot a whole interface with no script attached (the standalone path).

The GuiDef document

One tree, one document — mirroring SynthDef/GraphDef. Every node is:

{"id": 10, "type": "slider", "min": 0.0, "max": 1.0, "label": "gain",
 "children": []}
  • type names the widget; every other key is a property of it.
  • id addresses the node for /gui_set, /gui_free, /gui_query, bindings and events. The root's id is the one given to /gui_def.
  • children nests (containers only: window, panel, track).
  • bind as an inline prop registers a binding declaratively, so a saved GuiDef carries its own (no separate /gui_bind at boot).

The wire form is deliberately generic ({id, type, props, children}): a new widget kind never changes the protocol, and a host that does not know a type lays it out but does not paint it — old hosts and new scripts still interoperate.

Bulk data (waveform samples, a peak cache) never rides the JSON: a widget names a local path/cache the host maps (or fetches, in a browser), a server buffer it pulls over its client leg, or — for small bodies only — a trailing blob.

Events

The host pushes back to the script that built the window:

MessageMeaning
/gui_event id <value>A control changed: a float (slider/knob/number), an int (toggle 0/1, menu index, button press), or a string (text).
/gui_event id <tag> <flat values…>A view wrote data back. The tag names what was edited; the values are flat OSC primitives (never a new address — see below).
/gui_closed idThe window was closed by the user.

The edit-back payloads:

TagArgumentsSent by
"points"t v shape curve per break-pointthe bpf editor, and an automation clip — one payload, whichever view drew the curve
"notes"start dur pitch velocity channel per notethe pianoroll view (and a clip's roll) — MIDI notes edited
"osc"time label per eventthe pianoroll view — OSC event markers edited
"clip"offset dur (timeline units)a clip moved or resized
"wire"member control bus (an empty bus = unwired)a graph patch rewired
"locate"position (timeline units)a lane's time ruler (or its empty space) clicked — the transport is being seeked there
"selection"start len (samples)a selection dragged on a timeline view
"view"start len (samples)the shared navigation window zoomed or panned
"view_y"start len (0..1)the vertical display window zoomed or panned

Edited data flows as a payload, never a new address: the /gui_* family does not grow per widget.

The widget catalog

The authoritative per-widget reference — every property, its default and its meaning — is the Python client's builder documentation, since that is how a script actually names these. The catalog itself:

TypeWhat it isNotable properties
windowA top-level window (a GuiDef root)title, w, h, layout
panelA nestable containerlayout
labelStatic texttext
knob, slider, numberContinuous controlsmin, max, value, label (vertical on a slider)
button, toggleMomentary / latchinglabel, value
text, menuA string field, a choicevalue / options, index
meterA control-bus level, read from the server's shared segmentbus, min, max
scopeAn oscilloscope: a control bus, or an audio tapbus / tap, trigger, hold
phasescopeA goniometer (stereo field) over two tapstap, tap2, hold
spectrumA live spectroscope over a taptap, fft_size, averaging
nodetreeThe server's node graph, livegroup, controls
waveformThe editor-grade waveform: multichannel lanes, rulers, selection, playhead, linked navigationthe data (data/blob/buffer/path/cache), channels, ruler, ruler_y, sel_*, playhead_at, link, offset
spectrogramThe editor-grade spectrogram, the same chromethe data, window_size, hop, freq_scale, db_floor/db_ceil, colormap
bpfA drawable break-point envelope, played by the server's own shape mathpoints, min, max, duration, exp
pianorollThe editor-grade piano-roll: a keyboard, a MIDI-note grid, a velocity lane and an OSC-event lane; the same chrome and navigation as the heavy viewsnotes (start dur pitch velocity channel quintuples), osc (time label pairs), min/max (pitch window), snap, velocity, osc_lane, midi_in (live MIDI painting: the native host opens its virtual input port and paints incoming notes — at the running playhead, or step-entry on the snap grid), ruler, sel_*, playhead_at, y_start/y_len, link
plotA static plot of a signaldata/blob/path, min, max
trackA multitrack lane, holding clip children on the window's shared time axislabel, height, snap, ruler, tempo, sample_rate, playhead_at, playhead, link
clipA placed rectangle spanning [offset, offset + dur] — the graphic unit. Its bodies layer: a take, a piano-roll of events, and an automation curve over themoffset, dur, the take (buffer/path/cache/data/blob), notes, points (+ points_min/points_max, the curve's own value axis), min, max, label
graphA patcher of a bus-wired node graph: member boxes, bus nodes, a wire per connectionmembers, buses, wires, label
canvasA script-supplied WGSL shader over the widget areashader, params, buses

A timeline view (and a lane) shows the transport two ways, and they are different things: playhead_at anchors the line to the engine clock (it is the clock value at timeline position 0, so the line sweeps as the audio runs), while playhead is a static cursor — where a located, stopped transport sits. Both are group-wide, so every lane shows the one cursor; a negative value is none. Clicking a lane's ruler moves the cursor and sends "locate".

The lanes of a window share one navigation group: they zoom, pan and carry a playhead as one, and the axis spans the composition (the longest clip end). The same group model links the heavy views — an explicit link id joins or splits it, and a /gui_set of view_*/sel_*/playhead_at on any member applies group-wide.