K

Respond to clicks

Handle canvas clicks and build a grid.

When a knob or a drag zone isn't the right control (a step grid, a note matrix, a pad bank), declare a command region on a canvas. A click there dispatches onCommand, which runs off the audio thread, so you can branch and allocate freely.

Note

Keep grid state in m.state with a BitGrid, not one param per cell. A 12x8 grid is 96 cells, not 96 knobs.

Name your commands

Put the ids in a commands.ts enum. Both index.ts and ui.ts import it, so the id space is one source of truth.

commands.ts
export enum Cmd {
  ToggleCell,
  ClearAll,
}

Add a command region in ui.ts

Overlay it on the canvas. x/y/w/h are canvas fractions (0..1); here it covers the whole grid.

ui.ts
import { Cmd } from "./commands";

Canvas({
  id: "grid",
  inputs: [channel("grid"), Theme.colors.accent],
  commands: [{ command: Cmd.ToggleCell, x: 0, y: 0, w: 1, h: 1 }],
});

Handle it in index.ts

Decode the hit with event.col(cols) / event.row(rows), edit the grid, and commitState to save it.

index.ts
import { Cmd } from "./commands";

class Node extends Device {
  private grid: BitGrid = new BitGrid(12, 8);
  private viz: Float32Array = new Float32Array(12 * 8);

  onCommand(id: i32, e: CommandEvent, ctx: ControlCtx): void {
    if (id == Cmd.ClearAll) {
      this.grid.clear();
      ctx.commitState();
      return;
    }
    if (id == Cmd.ToggleCell) {
      this.grid.toggle(e.col(12), e.row(8));
      ctx.commitState();
    }
  }
}

To draw the grid, mirror it to the channel each block and let the canvas read it:

process(ctx: Ctx): void {
  this.grid.toChannel(this.viz);
  ctx.writeChannel(0, this.viz);
}

getState(): Uint8Array { return this.grid.bytes(); }
setState(b: Uint8Array): void { this.grid.load(b); }

A click reflects within a block. For the full set of handlers, events, and BitGrid methods, see the control surface reference.