# Constraints for plugin developers

> The few rules Maru's architecture depends on, for developers coming from JUCE or another framework, with links to the guides that own the details.

Canonical: https://maru.audio/docs/constraints-for-plugin-developers
Documentation index: https://maru.audio/llms.txt

If you have built plugins before, most of Maru will feel familiar: parameters,
presets, audio and MIDI processing, a UI and a set of plugin formats. A few
rules differ, and they are what let one source play in a browser, a DAW and
other targets. This page names them. The details belong to the linked guides,
and they evolve as the platform does.

## Realtime code stays realtime

Sound processing is written as restricted TypeScript that compiles to each
target. The audio callback must not allocate, lock, block or do I/O. State is
prepared ahead of processing, and controls change through the building blocks'
own setters. Model inference, file access and network calls happen outside it.
See the [Audio SDK](https://maru.audio/docs/audio-sdk.md) and [DSP library](https://maru.audio/docs/dsp.md).

## A module owns the sound; the host owns services

A module declares its ports, parameters, presets, assets and interface. Devices,
files, sign-in, account access and other services come from the host. Faces
never produce audio or open devices. See [How it works](https://maru.audio/docs/how-it-works.md).

## Parameters mean the same thing everywhere

Each parameter's range, steps and response curve are declared once, on the
parameter. Host automation, MIDI CC and the interface all read that declaration,
so they agree position for position. Shape a parameter's feel there, not in the
face. See [Contracts](https://maru.audio/docs/audio-contracts.md).

## One source, qualified targets

The browser, native plugin formats and devices are built from the same source.
Each target is built and checked separately, and each release lists what it
supports. A browser demo does not by itself establish a native or hardware
target. See [Check, build and export](https://maru.audio/docs/export.md) and
[Native plugins](https://maru.audio/docs/native-plugins.md).

## Reuse before writing new DSP

Prefer the library's effects and building blocks, and extend a shared building
block rather than copying it into a module. `maru dsp effects`, `maru dsp nodes`
and `maru module list` show what exists. See
[Effects, instruments and custom DSP](https://maru.audio/docs/effects-and-instruments.md).

## Porting an existing plugin

A JUCE effect maps onto one Processor module. Scaffold it with
`maru init <name> --kind processor-effect`, then read the guides the CLI
ships: `maru guide read processor-authoring restricted-typescript
parameters-and-schema presets-and-state build-preview-export`. They cover the
contract and are versioned with your installed CLI.

| In the JUCE plugin | In the Maru module |
|---|---|
| `prepareToPlay` / `processBlock` / `reset` | `prepare` / `process` / `reset` in `src/dsp/processor.ts` |
| `AudioProcessorValueTreeState` parameters | the schema in `src/contract.ts`; keep the original ids and ranges |
| factory presets / programs | `presets: [{ id, name, values }]` on the declaration |
| stereo bus layout | `channels: 2` on `defineEffect`; an effect is mono when it is omitted |
| editor | a face in `src/ui/`, built from UI-kit controls bound to parameters |
| unit tests against golden data | a script that drives your Processor, plus `maru render` on the packed Release |

Four rules catch most ports:

- **Declare stereo.** Without `channels: 2` the module is mono, and the
  checks pass anyway. `maru render` with a stereo file is the first thing
  that refuses it.
- **Output must not depend on block size.** If the original splits each host
  block into fixed sub-blocks (for example 64-sample coefficient updates),
  count them on the audio stream instead. That keeps the result the same
  for any host block size.
- **`reset` must return the instance to its freshly created sound.** A value
  the original kept across resets has to be cleared; the check names it.
- **Keep the original parameter mapping when you need fidelity.** If the
  original maps a 0..1 control through its own curve, keep the 0..1 parameter
  and the curve in the DSP, and show the unit (Hz, dB) in the face.

Then prove the port: render the original's test input through the packed
Release with `maru render` and compare against the original's output or
golden values, see [Check, build and export](https://maru.audio/docs/export.md#check-the-module).
Publish with `maru publish`, see
[Publish to maruaudio.com](https://maru.audio/docs/export.md#publish-to-maruaudiocom). If the
original is under a copyleft license such as GPL or AGPL, your port is a
derivative work: keep its license and attribution, and choose the listing
license to match.

## Availability and licensing

The `maruaudio` package is published on npm. The local workbench and plugin
builds currently need a toolchain provided in the selected-developer beta; see
the [Quickstart](https://maru.audio/docs/quickstart.md). The SDK, CLI and generated plugin shells are
not published under a general open-source license; see
[Licensing](https://maru.audio/docs/licensing.md). [Choosing Maru](https://maru.audio/docs/choosing-maru.md) compares the
approach with JUCE, Cmajor and RNBO.
