K

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

HookFires 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

CallDoes
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.

FieldMeaning
idThe command id.
x, yHit position on the canvas region, 0..1.
valueStatic payload from the region (else 0).
buttons, mods, phaseMouse buttons, modifier keys, gesture phase.
MethodReturns
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);
MethodDoes
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.