Define a Processor module
Connect a module declaration, realtime Processor, parameters, interface and tests, then publish the project with maru publish.
A Processor module keeps your musical behavior, controls, interface and assets
in one editable project. You write target-neutral processing in restricted
TypeScript; Maru supplies the host integration and builds the supported outputs.
This guide connects the pieces you define before running maru publish.
Start with a complete example
Use the effect starter for audio processing or the instrument starter for a note-driven sound:
maru init my-effect --kind processor-effect cd my-effect npm install maru dev --open
For an instrument, use --kind processor-instrument when creating the project.
The effect implements drive and tone stages; the instrument implements eight
velocity-sensitive oscillator/ADSR voices with note transitions and release.
Both include a schema, presets, a face and a qualification test. Change these
working examples together as you develop your own sound.
The Quickstart covers toolchain access. Create a project covers the other starters, including Graph modules that connect existing nodes instead of authoring a Processor.
The pieces you define
| Piece | Starter location | What you define |
|---|---|---|
| Project entry | package.json | Dependencies and maru.entry; development source paths identify the DSP, face and qualification test. |
| Module declaration | src/index.ts | Identity, module kind, channel layout, parameter schema, presets, face and processing implementation. |
| Parameter contract | src/contract.ts | Paths, types, ranges, defaults, response curves and display units. |
| Processing | src/dsp/processor.ts | Per-instance state, preparation, parameter changes, block processing and reset; events and resources where needed. |
| Factory sounds | src/factory-bank.ts | Preset IDs, names and values keyed by the schema's paths. |
| Interface | src/ui/index.tsx | A face bound to the host's parameters and preset selection. |
| Verification | src/qualification.ts | Repeatable input/events, observation duration and expected behavior. |
| Supporting content | Project assets and documentation | Resources the module uses, a README explaining the tool, and licenses and attribution for included material. |
Keep the generated package's dependencies and scripts. Its entry configuration connects the authoring files to the tooling:
{
"maru": {
"type": "audio-module",
"entry": "./src/index.ts",
"development": {
"sources": {
"module": "src/index.ts",
"dsp": "src/dsp/processor.ts",
"react": "src/ui/index.tsx",
"qualification": "src/qualification.ts"
}
}
}
}
This is the maru block inside package.json, not a replacement for the rest
of that file. Project structure
explains authored files and generated output.
Connect the declaration to the Processor
In the effect starter, the declaration has this shape. The imported schema and preset bank are the files the starter creates:
import { defineEffect } from "maruaudio/audio/define"
import { PROCESSOR_EFFECT_SCHEMA } from "./contract"
import { factoryPresets } from "./factory-bank"
export const AUDIO_MODULE = defineEffect({
id: "com.example.my-effect",
name: "My Effect",
channels: 2,
schema: PROCESSOR_EFFECT_SCHEMA,
presets: factoryPresets,
ui: {
entry: "./ui/index.tsx",
minSize: { width: 320, height: 420 },
defaultSize: { width: 680, height: 420 },
maxSize: { width: 1200, height: 900 },
},
implementation: {
kind: "processor",
source: "src/dsp/processor.ts",
},
})
Choose your own stable id before the first publication, replacing the starter's
provisional local. identity. It identifies the module across builds and hosts.
Use your own namespace instead of the example above.
This example explicitly declares stereo. defineEffect defaults to mono;
accepting stereo inside prepare alone does not change its ports. When changing
the starter to stereo, also give its qualification input tape two channels.
An asymmetric effect can use inputChannels and outputChannels instead.
Use defineInstrument for an instrument. Its starter also declares event and
voice bounds. A MIDI tool uses defineMidiModule and declares event capacity;
its Processor can run with zero audio channels. Keep ports and bounds aligned
with what the processing actually consumes and produces.
Implement the lifecycle
The Processor file default-exports an object satisfying
ProcessorDefinition<State> from maruaudio/dsp/processor:
| Function | Responsibility |
|---|---|
createState() | Allocate bounded per-instance storage. Size rate-dependent storage for the highest supported processing rate. |
prepare(state, spec) | Check the sample rate, maximum block size and channel layout; prepare coefficients and lengths. Return false for an unsupported configuration. |
setParameter(state, path, value) | Apply a declared parameter. Floats are numeric, booleans arrive as 0 or 1, and enums as option indices. Return false for an unknown path. |
process(state, block) | Read and write the current frames using the supplied buffers. Allocate no memory, acquire no locks, perform no I/O and call no browser APIs. |
reset(state) | Clear transient processing history so the instance behaves like a fresh one. The adapter reapplies schema defaults afterward. |
The adapter applies schema defaults after preparation too. Keep defaults in
the schema, with the Processor accepting them through setParameter.
ProcessorBlock uses planar buffers: a channel starts at
channel * channelStride + frameOffset, and this call covers frameCount
frames. Write only that region. The host owns the buffers and event timing;
an event-only Processor writes no audio output.
Compose processing from focused maruaudio/dsp/... imports and your own DSP
helpers. Keep compiler, browser and plugin-format choices outside the algorithm.
DSP fundamentals explains I/O, state, units and
the reusable building blocks, with category references for the toolkit. For the
installed version's detailed contract, run:
maru guide read processor-authoring restricted-typescript
Make controls, presets and state agree
Declare each control once in the schema, then implement its path in
setParameter. Put its response curve in the descriptor's taper, so face
controls, MIDI CC and host automation agree. Factory preset values use those
same paths and valid ranges.
The face receives AudioModuleFaceProps from the host. Use the starter's
faceParamBinding, edit gestures, UI-kit controls and FaceModuleNavbar to
connect controls and preset selection. The host owns live parameter state,
device access and audio execution. See Module UI.
Add optional contracts only when the tool needs them:
- Notes and MIDI: implement the appropriate
handleEvent,handleMidiand output-event functions and declare the required capacities. Keep voice handling and musical event transformations in the Processor. - Prepared resources: declare bounded
implementation.resourcesslots and implementloadResource. The host loads and prepares content outside the realtime callback. - Durable content beyond parameters: declare
state: { version, maxBytes }and implement bothsaveStateandrestoreState. Parameters are restored separately; test the two together. - Host services: declare the face capabilities needed for sign-in, generation or other services. Use the host bridge for those operations. Integration reference maps the entry points.
The versioned CLI guides cover these contracts in more detail:
maru guide read parameters-and-schema presets-and-state maru guide read midi-and-event-modules assets-and-resources
Verify the musical tool
Keep src/qualification.ts aligned with the declaration. For the effect
starter, the input is 48 kHz with one array per input channel. Each array has
(warmupQuanta + measuredQuanta) * 128 samples. The starter's 2 warmup and 30
measured quanta therefore need 4096 samples per channel. Its event tape exercises
parameter changes; update it as the product changes.
Run the checks and use the workbench to exercise the complete tool:
npm run typecheck maru module validate maru module check maru module check . --portability
Test controls, preset recall, reset and saved state. For instruments, include overlapping notes, release, voice stealing and live control changes. Listen to the result as well as measuring it. Add module-specific tests for behavior the starter does not cover. Check, build and export explains offline rendering, levels, build reports and native verification.
Prepare and publish
Before uploading, confirm these parts of your project:
- The entry resolves to the intended declaration and Processor; the identity, channel layout, schema, presets and referenced files agree.
- The README describes the sound, controls, setup and supported targets. Included code and assets have appropriate rights, licenses and attribution.
- Dependencies can be installed outside your checkout. For a source upload,
replace
workspace:andfile:dependencies with distributable packages. - The listing license, source visibility and
maru.sharereuse settings match how you want to distribute the work. Reusable DSP source also needs the package export required by that sharing mode; keep the CLI's sharing checks. - Your machine has the required toolchain.
maru module doctorreports missing tools. Select the Programs and targets you have tested.
Run a dry run with the same build options you intend to publish:
maru publish --dry-run maru login maru publish
The default project publication builds a development JavaScript release. For a
qualified JavaScript and WebAssembly release, use
maru publish --qualify --programs js,wasm --dry-run, then the same command
without --dry-run. Qualification needs the corresponding compiler and browser
tools. Native plugin builds remain a separate target workflow.
Project publication uploads source by default so you can open it in Studio.
Source is private unless already shared or explicitly made public. Use
--source-visibility public with a suitable source license to share it, or
--no-source to publish only the built release. A source-closed listing license
requires the build-only path. This upload choice is separate from optional
source/ inside a .maru bundle.
The command prints the publication reference and page URL. Keep that reference with your test results. Publishing covers release preparation and updates; Build and publish in CI extends the workflow to automation.
Find more examples
The two generated Processor starters are the smallest complete runnable examples. For other musical structures, inspect the examples and library available through your installed CLI:
maru module examples maru module list maru module spec <slug> maru module params <slug> maru module files <slug> maru module read <slug> src/index.ts
Read the declaration first to identify Processor versus Graph, then follow its
implementation source, contract, face and qualification files. Source access
and permission to reuse an existing module depend on its author's settings and
license. Use the public maruaudio/... imports from your starter when adapting
examples that use workspace package names.
For the architectural reason behind restricted TypeScript, read Restricted TypeScript in a DSP kernel. For a port from another plugin framework, see Constraints for plugin developers.