Design a custom look
Lay out your device's controls your way.By default Msh lays your controls out in a row. Want more say? Add a ui.ts that
describes the look. Editing it never touches the sound, so you can redesign while the
device plays.
export function createUI(_node: NodeContext): UI {
return ui(
{
size: { graph: { w: Theme.sizing(80), h: Theme.sizing(48) } },
palette: { accent: Theme.colors.signal.audio },
},
Box({ fill: true }, [
Spectrum({ input: channel("spectrum"), color: Theme.colors.accent }),
Stack({ dir: "row", justify: "between", pad: 2 }, [
Knob({ bind: param("cutoff"), label: "Cutoff" }),
Knob({ bind: param("q"), label: "Q" }),
]),
]),
);
}A tree: a container with controls and displays inside. Each control binds to a param
from your describe, so modulation, automation, and undo keep working. Full set in
the widget reference.
Buttons that do things
A Button carries an action, a plain data object (no closures). Use it to set,
toggle, or reset params:
Button({ label: "Reset", action: { kind: "resetParams", prefix: "pt_" } });The kinds:
{ kind: "setParam", id, value }set one param to a value.{ kind: "toggleSetting", id }/{ kind: "cycleSetting", id }flip or advance a setting.{ kind: "setParams", prefix, value }set every param whose id starts withprefix.{ kind: "resetParams", prefix }reset that whole bank to defaults.
One button can reset a 16-point curve's pt_* params instead of editing each.
A full editor window
For a bigger view than the tile, export createEditorUI alongside createUI.
The tile header's "open editor" icon opens it in a floating window. It reuses
the same bound params, so edits round-trip.
export function createEditorUI(node: NodeContext): UI {
return ui(
{
size: { graph: { w: Theme.sizing(120), h: Theme.sizing(80) } },
palette: { accent: Theme.colors.signal.audio },
},
/* a larger root */
);
}Besides the header icon, an { kind: "openEditor" } Button opens it too. Set
editorTrigger: "doubleClick" on the graph ui() config to also open it by
double-clicking the tile body (default "none", since double-click collides
with the reset gesture on knobs and pads).
Two rules
- No pixel sizes. Use
Theme.sizing(...),fill, orgrow, so it looks right at any size. - Prefer theme colours. Pick from
Theme.colors(Theme.colors.accent,Theme.colors.grey[600]) so the device matches the user's theme. For a deliberately fixed colour, passTheme.raw("#ff3300")or any CSS colour string; it stays constant across themes.
You can give the device its own accent, background, text colour, and a background image
from assets. When the widgets aren't enough, draw your
own.