โŒ˜K

Device API

The methods a device implements, the manifest it declares, and the process context.

A device extends Device and registers itself once at module top level:

import {
  Device,
  Manifest,
  Ctx,
  register,
} from "@msh/device-sdk/device/assembly";

class MyDevice extends Device {
  describe(): Manifest {
    /* ... */
  }
  process(ctx: Ctx): void {
    /* ... */
  }
}

register(new MyDevice());

Below are its methods, what you declare in the manifest, and everything on ctx.

Device methods

Only describe and process are required. Everything else has a default.

MethodWhen it runsPurpose
describe(): ManifestOnce, on loadDeclare controls and ports. Required.
prepare(sampleRate: f64, maxBlock: i32): voidBefore audio, and on format changeAllocate filters, buffers, LUTs.
process(ctx: Ctx): voidOnce per block, on the audio threadProcess the sound. Required.
processSpectrum(frame: SpectralFrame, ctx: Ctx): voidOnce per STFT hopEdit the spectrum when using Spectral. See DSP blocks.
getState(): Uint8ArrayOn save and before hot-reloadReturn bytes to persist.
setState(bytes: Uint8Array): voidOn load and after hot-reloadRestore from saved bytes.
onCommand(id, event, ctx): voidA button or canvas command regionReact to a UI command. See Control surface.
onParamChanged(index, ctx): voidA discrete param editBuild macros and linked params. See Control surface.
onAsset(slot, ctx): voidAfter a requested file/sample is installedDecode the asset (loadAsset / decodeWav) once it is available.

onCommand and onParamChanged run off the audio thread and take a ControlCtx, not a Ctx. Allocation and branching are fine there.

Manifest

Build it in describe, chaining calls, then return it. Each call returns the manifest so they chain.

CallPurpose
addParam(def)Add a modulatable control. See Parameters.
addSetting(def)Add a non-modulatable setting (same ParamDef, no depth wheel).
addPort(port)Add an audio, MIDI, or modulation plug. See Ports.
channel(name, size, role)Declare a live display feed. Roles: CHANNEL_SPECTRUM, CHANNEL_SPECTRUM_POST, CHANNEL_CURVE.
meter(name, segments)Declare a scalar DSP-to-UI readout, updated with ctx.setMeter(idx, value).
curveParam(id, name, size, defaultPoints)Declare a user-drawable curve, read with ctx.curve(idx, x). size defaults to 256; defaultPoints (from curvePoint(x, y, power)) sets the starting shape and the resetCurve target.
bucket(name, shape, dim0, dim1, format, transport)Declare a realtime data bucket for a shader. See Data buckets.
texture(name, w, h, format)A 2D bucket. See Data buckets.
ring(name, w, frames, format)A scrolling-history bucket. See Data buckets.
array(name, len, format)A flat 1D bucket. See Data buckets.
latency(samples)Report any delay the device adds, for delay compensation.
state(maxBytes)Reserve room for getState / setState. 0 (default) means stateless.
gpuAudioData(floats)Reserve the per-block CPU-to-GPU payload for a GPU audio stage.
gpuAnalysis(floats)Reserve GPU-to-CPU analysis readback.
gpuStatic(floats) / gpuState(floats)Reserve bulk read-only GPU data / grow GPU persistent state.
gpuParamRamps([ids]) / gpuPipelineDepth(n)Stream params to the GPU as per-sample ramps / set pipeline depth (1-4).

The gpu* builders pair with a gpuAudio.ts program. See GPU audio.

The process context (ctx)

ctx is a typed, per-sample view handed to process. Members marked as a call (()) are methods; the rest are fields.

Block and audio

MemberReturnsMeaning
ctx.ni32Samples in this block.
ctx.sampleRatef64Current sample rate.
ctx.inChannels() / ctx.outChannels()i32Channel counts. Loop to these, don't hardcode 2.
ctx.input(ch, i)f32Read an input sample. In-bounds for ch < inChannels().
ctx.output(ch, i, v)Write an output sample.
ctx.inPtr(ch) / ctx.outPtr(ch)i32Byte pointer to a channel's sample plane (for block-oriented DSP).

Parameters and modulation

MemberReturnsMeaning
ctx.param(idx)f32A param's value, no modulation.
ctx.modulatedParam(idx, i)f32A param's per-sample value with modulation folded in. Read this for anything modulatable.
ctx.paramIndex(id)i32Resolve a param's index by name. Call in prepare, cache it. -1 if absent.
ctx.modActive(ord)boolIs a raw modulation source connected on ordinal ord?
ctx.mod(ord, i)f32A raw modulation source's per-sample value, 0..1.

idx is the order you declared params in describe, starting at 0. Resolve by name with paramIndex so reordering can't silently read the wrong param.

MIDI

See MIDI for the full typed surface (ctx.midiEvent, ctx.noteOn, ctx.cc, and the rest). The raw accessors:

MemberReturnsMeaning
ctx.midiInCount()i32Incoming events this block.
ctx.midiInOffset(e)i32Event e's sample offset.
ctx.midiInByte(e, b)i32Byte b of event e (0 = status, 1 = note, 2 = velocity).
ctx.emitMidi(offset, status, d1, d2)Send a MIDI event. 0x90 = note on, 0x80 = note off.

Modulation and display output

MemberMeaning
ctx.modOut(i, value)Write this device's primary modulation output (needs a modulationOut port).
ctx.modOutAt(out, i, value)Write modulation output out (declared modulationOut ports in order, 0 = the primary).
ctx.writeChannel(idx, data)Fill a display channel (data is a Float32Array). Once per block.
ctx.setMeter(idx, value)Push a scalar to a m.meter readout.
ctx.writeBucket(idx, data)Fill a data bucket from f32s (32-bit formats and ring rows). See Data buckets.
ctx.writeBucketBytes(idx, bytes)Fill a bucket from pre-packed bytes (half/byte formats).
ctx.curve(idx, x)Read a drawn curve at x (0 to 1). Safe per sample.

Transport

MemberReturnsMeaning
ctx.tempo()f64Tempo in BPM, 0 when stopped.
ctx.isPlaying()boolIs the transport running?
ctx.ppqPosition()f64Song position in quarter notes.
ctx.bar() / ctx.beat()i32Current bar and beat within the bar (0-based).
ctx.beatPhase()f64Progress through the current beat, 0 to 1.
ctx.timeSigNumerator() / ctx.timeSigDenominator()i32Time signature, 0 if unknown.

Scale and key

MemberReturnsMeaning
ctx.meshKey() / ctx.meshScaleMask()i32The mesh's key (pitch class 0-11) and 12-bit scale mask.
ctx.globalKey() / ctx.globalScaleMask()i32The session override key and scale mask.
ctx.noteInScale(note, mask)boolIs a note's pitch class in the given scale mask?
ctx.meshScale() / ctx.globalScale()ScaleBuild a Scale from the project key/scale (allocates: call in prepare). See Harmony.

Runtime reconfiguration

MemberMeaning
ctx.setLatency(samples)Re-report latency when it changes (e.g. FFT size). Declare the max in m.latency.
ctx.setPorts(audioIn, audioOut, midiIn, midiOut, modOut)Activate the first N of each declared port. Call when the active set changes, not every block.

GPU audio

For a device with a gpuAudio.ts compute post-stage. Full detail in GPU audio.

MemberMeaning
ctx.writeGpuAudioData(i, v)Write slot i of the per-block CPU-to-GPU payload (m.gpuAudioData).
ctx.gpuAnalysis(i) / ctx.gpuAnalysisCount()Read back a GPU analysis result and its length (m.gpuAnalysis).
ctx.setGpuDispatch(pass, threads)Set a dynamic pass's per-block thread count.
ctx.gpuActivetrue while the GPU stage runs this session. Fall back to CPU DSP when false.

Assets

Bundle a file with the device, or ask the user for one, and read it on the audio thread. Request from a ControlCtx (inside an on* handler), then decode in onAsset once it lands.

MemberMeaning
ctx.requestFile(slot) / ctx.requestSample(slot)On a ControlCtx: prompt the user for a file / audio sample into slot.
loadAsset(name)Read a bundled asset's bytes as a Uint8Array.
pickedAssetName(slot)The file name the user picked for slot, or empty.
decodeWav(bytes)Decode WAV bytes to a WavAudio (planar f32 channels).