Control surface
React to clicks, drags, and param edits off the audio thread.process runs on the audio thread. Control logic runs off it. Override these Device
hooks to react to UI events and lifecycle. They get a ControlCtx, and allocation and
branching are fine here. Enable them by re-exporting the ABI with
export * from "@msh/device-sdk/device/assembly" (the template already does).
Hooks
| Hook | Fires on |
|---|---|
onCommand(id, event, ctx) | A Button or canvas commands region with an { kind: "command", id } action. |
onParamChanged(index, ctx) | A discrete param edit (knob release, preset, automation point). Not continuous modulation. |
Use onParamChanged for macros and linked params. Use onCommand for buttons,
grids, and step cells.
ControlCtx
| Call | Does |
|---|---|
ctx.param(idx) | Read a param. |
ctx.paramIndex(id) | Resolve a param index by name. |
ctx.setParam(idx, value) | Set another param (committed, undoable). |
ctx.commitState() | Persist m.state now (saves with the mesh; not on the undo stack). |
ctx.tempo(), ctx.isPlaying() | Transport. |
ctx.meshKey(), ctx.meshScaleMask(), ctx.globalKey(), ctx.globalScaleMask() | Key and scale. |
CommandEvent
Passed to onCommand. One reused instance per dispatch, copy fields you need to keep.
| Field | Meaning |
|---|---|
id | The command id. |
x, y | Hit position on the canvas region, 0..1. |
value | Static payload from the region (else 0). |
buttons, mods, phase | Mouse buttons, modifier keys, gesture phase. |
| Method | Returns |
|---|---|
col(cols) / row(rows) | Grid column / row at the hit position. |
shift() / ctrl() / alt() / meta() | Modifier key held. |
rightClick() | Right mouse button. |
phase is a CommandPhase: Click (discrete), Begin / Move / End (a drag).
BitGrid
A boolean grid for matrix and step-sequencer UIs, stored in m.state instead of one
param per cell.
private grid: BitGrid = new BitGrid(12, 13);| Method | Does |
|---|---|
get(col, row) / set(col, row, on) / toggle(col, row) | Read / write / flip a cell. |
setColumnExclusive(col, row) | Turn on exactly one cell in a column. |
clear() | Clear every cell. |
bytes() / load(bytes) | Serialize for getState / setState. |
toChannel(out) | Copy to a display channel (1.0/0.0) for the canvas to draw. |
Wiring a clickable grid
// commands.ts (imported by both index.ts and ui.ts)
export enum Cmd {
ToggleCell,
ClearAll,
}// ui.ts
Canvas({
id: "grid",
inputs: [channel("grid")],
commands: [{ command: Cmd.ToggleCell, x: 0, y: 0, w: 1, h: 1 }],
});// index.ts
onCommand(id: i32, e: CommandEvent, ctx: ControlCtx): void {
if (id == Cmd.ToggleCell) {
this.grid.toggle(e.col(COLS), e.row(ROWS));
ctx.commitState();
}
}See the how-to: Respond to clicks and grids and Link params.