# Define a Processor module

> Connect a module declaration, realtime Processor, parameters, interface and tests, then publish the project with maru publish.

Canonical: https://maru.audio/docs/processor-module
Documentation index: https://maru.audio/llms.txt

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:

```bash
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](https://maru.audio/docs/quickstart.md) covers toolchain access.
[Create a project](https://maru.audio/docs/create.md) 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:

```json
{
  "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](https://maru.audio/docs/project-structure.md)
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:

```ts
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](https://maru.audio/docs/dsp-fundamentals.md) explains I/O, state, units and
the reusable building blocks, with category references for the toolkit. For the
installed version's detailed contract, run:

```bash
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](https://maru.audio/docs/ui.md).

Add optional contracts only when the tool needs them:

- **Notes and MIDI:** implement the appropriate `handleEvent`, `handleMidi`
  and output-event functions and declare the required capacities. Keep voice
  handling and musical event transformations in the Processor.
- **Prepared resources:** declare bounded `implementation.resources` slots and
  implement `loadResource`. The host loads and prepares content outside the
  realtime callback.
- **Durable content beyond parameters:** declare `state: { version, maxBytes }`
  and implement both `saveState` and `restoreState`. 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](https://maru.audio/docs/integrate-maru.md) maps the entry points.

The versioned CLI guides cover these contracts in more detail:

```bash
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:

```bash
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](https://maru.audio/docs/export.md) 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:` and `file:` dependencies with distributable packages.
- The listing license, source visibility and `maru.share` reuse 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 doctor` reports missing
  tools. Select the Programs and targets you have tested.

Run a dry run with the same build options you intend to publish:

```bash
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](https://maru.audio/docs/maru-bundle.md).

The command prints the publication reference and page URL. Keep that reference
with your test results. [Publishing](https://maru.audio/docs/publishing.md) covers release
preparation and updates;
[Build and publish in CI](https://maru.audio/docs/build-and-publish-in-ci.md) 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:

```bash
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](https://maru.audio/blog/restricted-typescript-in-a-dsp-kernel.md).
For a port from another plugin framework, see
[Constraints for plugin developers](https://maru.audio/docs/constraints-for-plugin-developers.md).
