โŒ˜K

Canvas API

The drawing commands, inputs, and interaction regions of a paint.ts canvas.

A canvas is declared in ui.ts and drawn in canvases/<id>/paint.ts. The paint module runs on the UI thread (its own wasm), once per frame. For the how-to, see Draw your own canvas.

canvases/<id>/paint.ts
import { Paint, definePaint } from "@msh/device-sdk/device/assembly/paint";

definePaint((p: Paint): void => {
  // draw here, runs once per frame
});

export {
  mshPaint,
  paintCmdPtr,
  paintInputPtr,
  paintInputCapacity,
  paintStrPtr,
} from "@msh/device-sdk/device/assembly/paint";

Coordinates are logical pixels in [0, p.width] by [0, p.height]. Colours are four 0..1 floats. Always re-export the paint* ABI line at the bottom.

For a GPU-drawn canvas, declare type: "webgpu" and write a fragment shader instead. See GPU shader canvas.

Reading inputs

Inputs come from the Canvas({ inputs }) list, read by position. Each input is a param scalar, a colour token, a channel, a modulation("id"), or a curve("id").

CallReads
p.scalar(i)A scalar input (a param, modulated value, or setting), normalized.
p.color(i, ch)A colour input; ch 0 to 3 is r, g, b, a (0 to 1).
p.channelLen(i)Bin count of a channel input.
p.channel(i, bin)A bin of a channel input.
p.input(idx)Raw f32 at a flat word index (escape hatch).

Modulation state

Feed a param's full live modulation state with modulation("id") to draw your own dial with a mod arc:

CallReads
p.modBase(i)The knob's set value, normalized.
p.modValue(i)The live post-modulation value, animates each frame.
p.modLow(i) / p.modHigh(i)Combined modulation range edges.
p.modActive(i)Whether the param has any active source.
p.modSourceCount(i)Number of modulation sources.
p.modSourceLow(i, k) / p.modSourceHigh(i, k)Source k's range edges.
p.modSourceMuted(i, k)Whether source k is muted.
p.modSourceColor(i, k, ch)Source k's accent colour (ch 0 to 2 = r, g, b).
p.drawModArc(i, cx, cy, r, lineWidth)Draws Msh's standard value + range arc and live dot for you.

Curve points

A curve("id") input exposes the editable curve's control points:

CallReads
p.curve(i, x)Sample the piecewise curve at x in [0, 1], returns y in [0, 1].
p.curvePointCount(i)Number of control points.
p.curvePointX(i, k) / p.curvePointY(i, k)Point k's position.
p.curvePointPower(i, k)Segment power (bias) starting at point k.
p.curvePointCurved(i, k)Whether the segment is curved or forced linear.

Drawing

A Canvas2D-style API on p. Out-of-capacity ops are dropped, never out of bounds.

  • Style: fillStyle(r,g,b,a), strokeStyle(r,g,b,a), lineWidth, globalAlpha, lineCap, lineJoin, miterLimit, setLineDash, clearLineDash, lineDashOffset.
  • Shapes: fillRect, strokeRect, clearRect.
  • Paths: beginPath, moveTo, lineTo, arc, arcTo, ellipse, rect, roundRect, quadraticCurveTo, bezierCurveTo, closePath, fill, stroke, clip. Pass true to fill / clip for the even-odd rule (holes).
  • Transforms: save, restore, translate, scale, rotate, transform(a..f).
  • Gradients: createLinearGradient(id, ...), createRadialGradient(id, ...), createConicGradient(id, startAngle, x, y), then gradientColorStop(id, t, r,g,b,a) per stop, then fillGradient(id) / strokeGradient(id).
  • Text: font(css), textAlign, textBaseline, letterSpacing, wordSpacing, direction, fontKerning, fillText(text, x, y, maxWidth?), strokeText.
  • Effects: shadow(dx, dy, blur, r, g, b, a) / clearShadow(), blur(px), globalCompositeOperation(CompositeOp.X). An inner shadow is a drop shadow clipped to the shape.
  • Images: declare with Canvas({ images: [...] }), draw with p.drawImage(index, dx, dy, dw, dh) or p.drawImageRect(index, sx,sy,sw,sh, dx,dy,dw,dh) for one cell of a sprite sheet. Toggle p.imageSmoothing(false) for crisp pixels.

Interaction

Interaction is declared next to the Canvas in ui.ts, not drawn. Regions use canvas fractions (0 to 1), so they track the art as the canvas resizes.

RegionBehaves like
hits: [{ bind, x, y, w, h, axis, fine }]A hidden knob. Dragging edits the bound param. axis is "vertical" (default) or "horizontal"; fine (0 to 1) tunes Shift-drag slowdown.
clicks: [{ bind, x, y, w, h, action, value, step }]A button on a param. action: "toggle" (default), "set" (to value), or "cycle" (by step, wrapping).
commands: [{ command, x, y, w, h, value }]Dispatches command to Device.onCommand with the hit position. See Control surface.

Msh provides the modulation wheel tray and right-click settings on hits and clicks. You choose whether to draw the dial arc (p.drawModArc does it to spec).

Canvas({
  id: "viz",
  width: "fill",
  height: Theme.sizing(20),
  inputs: [param("amount"), channel("spectrum"), Theme.colors.accent],
  hits: [
    { bind: param("amount"), x: 0.3, y: 0, w: 0.4, h: 1, axis: "vertical" },
  ],
});