Host a module in React

Embed a compiled module and its face with a headless React provider, parameter hooks and audio handles.

Import the headless host from maruaudio/module-player/react to play a built module inside your own React application. You supply the module declaration, its compiled Release, an optional face and your application controls. The provider renders its children without adding a playback bar, styles or a global session. React and React DOM are supplied by your application.

Add a module and face

Install maruaudio, React and React DOM in your application. Build the module and copy its complete web package into your app's public assets, for example public/modules/my-module/. Keep the Release, Programs, resources and hashed assets together. The imported declaration must describe that same module.

tsx
import {
  ModulePlayerProvider,
  ModulePlayerFace,
  useModulePlayer,
  useModuleParameter,
} from "maruaudio/module-player/react"
import { AUDIO_MODULE } from "./my-module"
import MyModuleFace from "./my-module/ui"

const module = {
  definition: AUDIO_MODULE,
  releaseUrl: "/modules/my-module/release.json",
}

function Controls() {
  const { phase, error, controller } = useModulePlayer()
  const drive = useModuleParameter("drive")
  return <>
    <button onClick={() => controller.start().catch(console.error)}>
      {phase === "starting" ? "Loading…" : "Start audio"}
    </button>
    <button onClick={() => void controller.stop()}>Stop</button>
    <input aria-label="Drive" type="range" min={0} max={1} step={0.01}
      value={drive.value}
      onChange={event => drive.setValue(Number(event.target.value))} />
    {error && <p role="alert">{error}</p>}
    <ModulePlayerFace component={MyModuleFace} />
  </>
}

export function App() {
  return <ModulePlayerProvider module={module}>
    <Controls />
  </ModulePlayerProvider>
}

Replace the imports and parameter path with your module's exports. A face is optional: remove ModulePlayerFace and draw every control yourself, or use useModuleFace() to pass the standard AudioModuleFaceProps into a custom layout. Import the face's own styles as required by its package. The host does not import the UI kit, application fonts or global styles.

Mounting does not create an AudioContext or ask for device permissions. Start audio from a user gesture. An effect also needs an input source; an instrument needs its note or MIDI input. The face sends edits to the same host that processes audio.

Hooks and controls

APIWhat it provides
useModulePlayer()Reactive phase, error, parameter values, preset identity, dirty state and live audio handles, plus the controller
useModulePlayerController()Stable imperative handles for event handlers and external integrations
useModuleParameter(path)One engine-unit value, its descriptor, setValue, beginEdit and endEdit
useModuleFace()Host-bound values, parameters, edit gestures, factory preset library, notes, levels and audio readiness
useModulePlayerSession()The canonical session snapshot and its controller for MIDI devices, CC mappings and active notes
useModuleLevels(enabled)Opt-in input/output meter sampling while running

The controller exposes start, stop, setParam, getParam, selectPreset, noteOn, noteOff, panic, setMonitorLevel, connectInput, loadResource, saveState, restoreState, send, sendMidi and onMidiOutput. beginEdit/endEdit report gestures through options.onParamEdit for a host that records automation. send is for non-parameter events; write parameters through setParam so the engine and face remain synchronized.

Parameter values use schema engine units, such as hertz or a numeric enum index. Bounds, steps and taper come from the module schema. The host passes engine values through the SDK event API; it does not normalize them a second time. Factory presets apply one parameter snapshot and retain selection/dirty identity. Saving user presets or offering generation, camera and file import services requires the owning application's services; this provider does not advertise absent capabilities.

Connect an audio source

After Start resolves, controller.getSnapshot().audio contains the active context, SDK host, output analyser and input analyser (null for a module without audio input). For example, a host that has already acquired a microphone stream can connect it like this:

ts
await controller.start()
const { context } = controller.getSnapshot().audio!
const source = context.createMediaStreamSource(stream)
const disconnect = controller.connectInput(source)

// When the input changes or the host closes:
disconnect()
// Stop stream tracks only if this application owns the stream.

connectInput removes only the connection it owns. The player never stops caller-owned sources or media tracks. Start an input source such as a buffer player in your app and connect it in the same way. Monitor level is host gain, separate from the module's own parameters. Read the output analyser for a custom visualizer, or use the opt-in meter hook.

Raw MIDI bytes go to sendMidi; its boolean result reports whether the running Program accepts that dialect. Use noteOn/noteOff for note-based interaction. onMidiOutput subscribes to the Program's emitted batches; the application chooses how to route those events. Device selection and CC mapping use the exposed session controller rather than a second MIDI handler inside the face.

Supply a Release directly

A module package may already export a parsed Release and its file source. Pass { definition, release, source } instead of { definition, releaseUrl }. The source implements the SDK's browser Release file-source contract. URL loading supports optional sha256 and fetchImpl on the module descriptor. The SDK verifies artifact/file hashes and chooses the requested available Program; stale or failed qualification is not silently bypassed.

For an evidence-empty draft, a measuring harness or an intentionally unqualified JS-only Release, supply an explicit SDK activation policy:

tsx
<ModulePlayerProvider module={module} options={{
  prequalification: {
    purpose: "unqualified-release",
    artifactId: "program:js",
  },
}}>
  <Controls />
</ModulePlayerProvider>

development and qualification are the other explicit purposes. This option names an exact Program and applies only to evidence-empty Releases. Read the active SDK host's qualificationStatus for its actual status.

Ownership, state and teardown

The player creates its own AudioContext on Start and closes it on Stop or unmount. Pass options.context to borrow an application-owned context; the player resumes it on Start and never closes or suspends it. All connected nodes must belong to that same context. options.destination selects the output node; false leaves the output for your surrounding host to connect.

Keep the declaration, Release and file source stable across renders. Changing them replaces the player and disposes its old runtime. Inline option objects and callback updates do not restart an otherwise unchanged host. React StrictMode effect replay does not dispose a still-mounted player. Unmount cancels pending loads, releases owned connections and closes owned audio resources. Independent providers have independent state.

saveState() returns the module ID and parameter snapshot, including before Start. saveState({ includeProcessor: true }) also requests opaque Processor bytes from the running Program; it rejects when that Program has no codec. Opaque Processor bytes are bound to that Program's artifact identity. restoreState() refuses another module or an incompatible Program. Parameters can be restored before Start; opaque state needs an active host with the matching codec. The application owns storage and serialization of returned byte arrays.

For a non-React host, createModulePlayer(module, options) exposes the same controller. Call dispose() when finished. A rack authored as a NodeGraphPatch uses the SDK rack host to connect declared module ports; mounting separate providers or iframe players does not create that rack.

Serve your app over HTTPS or localhost. Cross-origin payloads need CORS and appropriate content types. An iframe should embed your complete player page, with its Start action and permissions, rather than the module's bare ui/index.html.

See Module UI for face authoring and Check, build and export for producing the module payload.