PluginUI

Maru integration

Connect your visual interface to the Maru framework for plugin development.

PluginUI is built on top of the Maru framework for plugin development. Its visual components can also be used with another engine. Maru supplies the module contract, parameters, presets, MIDI input and runtime host.

Use the module face contract

A Maru face receives AudioModuleFaceProps. Prefer the UI kit's host-bound controls when a control corresponds to a declared parameter:

tsx
import type { AudioModuleFaceProps } from "maruaudio/contracts"
import {
  ParameterProvider,
} from "maruaudio/ui/shared/parameter-binding"
import { ParameterKnob } from "@/components/ui/parameter-controls"

export function MyFace(face: AudioModuleFaceProps) {
  return (
    <ParameterProvider source={face}>
      <ParameterKnob path="filter.cutoff" />
    </ParameterProvider>
  )
}

The path must exist in the module's parameter schema. The bound control reads the value, range, default and taper from the host and sends edits back through the same contract.

The source is a UI-facing descriptor/value/callback contract, not a DSP instance. You can also pass source={face} directly to a bound control, or spread useParameterControl("filter.cutoff", face) into a normal Knob/Fader with custom artwork. Standalone controls and signal displays require no module source. Use a separate provider for each module instance in a rack.

Keep sound and visuals separate

The module owns its declaration and processing. The face owns layout and presentation. It does not generate audio, open devices or duplicate the parameter schema. The host supplies meter data and other runtime capabilities.

Read the canonical Maru module UI guide for face authoring and Audio SDK guide for module contracts.

Try a hosted module

The OSC Mini embed runs the existing module through the Maru host. Use the hosted product path when you want to play an instrument rather than preview a silent visual assembly.

Typed and local parameter bindings

The package binding API can derive readonly handles from the module's existing literal schema. Keep the schema in a lightweight shared module contract:

tsx
const parameters = useParameters(FILTER_SCHEMA, face)

<ParameterKnob
  parameter={parameters["filter.frequency"]}
  renderDesign={(state) => <IndexedKnobDesign {...state} />}
/>

Use useLocalParameters(PROTOTYPE_SCHEMA) for local prototype state with the same controls. These hooks come from maruaudio/ui/shared/parameter-binding; keep schema objects stable. The parameter and path props are mutually exclusive. Dynamic paths remain available and unknown paths throw. Standalone installed Knob/Fader controls still accept ordinary controlled values, ranges and units. Typed/local bindings are also source-installable through @pluginui/core-parameter-binding, @pluginui/core-parameter-controls and @pluginui/core-parameter-select. These entries install the required Maru dependency. The optional pluginui CLI provides the same installation with friendly names; it is not a runtime library. Import providers and hooks directly from maruaudio/ui/shared/parameter-binding alongside the copied controls; they share contexts with maruaudio/ui. The existing Maru face supplies its state and callbacks once, without a parallel module host. Visual examples can keep ordinary controlled props and local React state.

Units, ranges, defaults and taper come from the descriptor. A normalized mix can opt into display: { format: "percent" }; the shared formatter displays 0.7 as 70.0% while host values remain 0..1. A 0..1 range alone does not imply percent.