Core Concepts
Before diving into the walkthrough, this chapter gives you the mental model. If you’ve done KSP scripting in Kontakt, much of this will feel familiar.
A form is a rectangular region that:
- has its own size properties (
width,height) - holds an ordered list of controls
- can optionally have a Lua backing script
Each form lives in its own .frm file — a JSON container holding the YAML
form definition and its optional Lua script. Forms are the unit of reuse in the UI
Builder. You can build a “labeled knob” form once and drop it into your main UI
as many times as you need — with different parameters passed in each time via
macros.
Form Anatomy
Section titled “Form Anatomy”A minimal but realistic form file looks like this:
width: 600height: 300color: "#248"
controls: cutoff_label: type: text visible: true pos: {x: 11, y: 69, w: 80, h: 20} props: text: Cutoff color: "#ccf"
cutoff_knob: type: knob anchors: {left: 10, top: 10, width: 50, height: 50} props: bipolar: false displayMode: phaseBitmap bitmap: cutoff_knob.png parameterUid: "08afbf03-738b-48ad-a340-0c482f505683"A few things to note:
- Form properties (
width,height,color) are top-level in the file. controls:is a map — each key is the control’s ID (used to reference it from Lua scripts and sub-form macro parameters).- Top-level control fields:
type(required),visible, and placement (posoranchors). These are the same for every control type. props:contains all type-specific properties. This is where appearance, behavior, and bindings live.- Most
propsare optional. Omitting a property falls back to its internal default — which for visual properties is typically the corresponding stylesheet macro (e.g. omittingcoloron a text control defaults to$text-color).
Controls
Section titled “Controls”A control is a typed widget placed inside a form. All controls share the same
top-level fields (type, visible, placement) and each type adds its own
type-specific props on top.
The full list of control types and their properties is in the Reference. The walkthrough in this chapter introduces the most important types in context.
Placement
Section titled “Placement”Controls can be placed in two ways — you can mix both in the same form:
pos— absolute placement:pos: {x: N, y: N, w: N, h: N}in pixels.anchors— constraint-driven: pin edges (left,right,top,bottom) relative to the form boundary and supplywidthorheightwhere needed. The engine resolves the geometry at render time, so anchor-based layouts adapt to different form sizes automatically.
Note that pos uses w/h (short form), while anchors uses the full
width/height. Both are top-level fields on the control, not inside props.
Layout is covered in full in Layout.
The Style System and Macros
Section titled “The Style System and Macros”The UI Builder has a global stylesheet — a flat list of macro definitions that map symbolic names to concrete values:
$text-color = #FFFFFF$knob-track-color-active = #C0C7FF$fader-track-width = 4Wherever a macro token appears in a property value, it is replaced by its definition before rendering. You can override any macro for a specific form or pass macro values into sub-forms, which is the primary mechanism for building parameterized, reusable components.
Macros are covered in full in Appearance & Styling.
Parameters and Bindings
Section titled “Parameters and Bindings”A parameter is a named, typed value that the UI can read and write. Parameters come from two sources:
- Engine parameters — defined by the audio modules. These have a UUID but no user-visible name; always reference them by UUID.
- User-defined global parameters — created by you in the patch settings.
These have both a name you choose (e.g.
tab-index) and an auto-assigned UUID. You can reference them by either — the name is usually more readable.
A binding connects a control’s value to a parameter directly — no Lua required. When the user moves a knob, the bound parameter updates. When the parameter changes from the engine side, the knob moves. Bindings are always the preferred approach; reach for Lua only when bindings aren’t expressive enough.
Lua Backing Scripts
Section titled “Lua Backing Scripts”Each form can have a Lua script. The script has access to:
- all controls in its form (by name) - most of their properties can be changed at will
- all parameters via explicit subscriptions / setters
- control value change events via explicit handler definitions by the controls
Parameter functions (sys_set_parameter_value, sys_subscribe_to_parameter)
accept the same identifiers as the parameterUid prop in YAML — a UUID always
works, and a parameter’s name works too if it has one (global parameters do;
engine parameters don’t).
The scripting surface is intentionally slim right now — it covers the cases where declarative bindings fall short. The full API is in the Reference.
Composition
Section titled “Composition”Controls that host sub-forms are the key to building complex UIs without repetition. A Subform control renders another form inside itself. You pass configuration into the sub-form via macro parameters, so the same form definition can serve as a reusable component with different data wired up each time.
This recursive model — forms containing controls containing forms — is how large, production-quality instrument UIs are structured. It is covered step by step in Sub-Forms and Composition.