Generate audio in a module

Let a musician describe a sound, audition generated takes and play the chosen one through your Processor, with the host choosing between on-device and hosted generation.

A musician types "a soft wooden knock", presses Generate, auditions four takes and keeps one. The kept take plays through your instrument at once. This page shows how to build that flow. It is part of AI tasks in your module: your module asks for the task, and the host decides which model and runtime produce the audio. For choosing when generation belongs in a product at all, read Generation in your instruments and apps.

Choose the capability

Two face capabilities generate audio. They differ in where the result goes.

You wantCapabilityResultWho pays
The take to play in your instrumentaudio.generation/v1 (face.generation)Installed into your Processor's sample slotsDepends on the runtime: on-device generation uses the person's own hardware; hosted generation is metered on their account
A sound saved to the person's library, attributed to your appapp.generation/v1 (face.appGeneration)A library take; the face receives status, not audioThe person's credits, after consent to your registered app

This page covers audio.generation/v1. For app.generation/v1, see Add generation to an instrument.

1. Declare where takes go

A generation target names a place a take can fill and what fits there. The host derives its limits, its install and its analysis from this declaration.

ts
generation: {
  targets: [
    {
      id: "take",
      audioTypes: ["one-shot", "loop"],
      maxSeconds: 8,
      maxVariations: 8,
      install: { resources: ["takeB", "takeC"], meta: "genMeta" },
      analysis: ["pitch", "silence", "loop"],
      spectrogram: true,
    },
  ],
},
ui: {
  entry: "./ui/index.tsx",
  hostCapabilities: { optional: ["audio.generation/v1"] },
},
  • audioTypes is any of one-shot, loop, sfx and drum-kit.
  • maxSeconds is the longest take your slots are sized for (at most 120).
  • install.resources names exactly two sample resources (f32): the host writes the inactive one and then switches, so the Processor never reads a half-written buffer.
  • install.meta names an f32 data resource that tells the Processor which slot is active and what the host found in the take.
  • analysis asks the host for a root note (pitch), trimmed leading silence (silence) and a loop suggestion (loop).

A module may declare up to 16 targets, for example one per drum pad.

2. Read the take in the Processor

Declare the two sample slots and the meta slot as ordinary resources. The host fills them through the same loadResource path as any other resource; your Processor reads them the same way it reads a factory sample.

The meta slot holds 11 f32 values (GENERATED_TAKE_META in @maruaudio/audio-contracts):

IndexFieldMeaning
0source−1 for a generated take; the factory source index otherwise
1versionMeta layout version (1)
2activeSlot0 or 1: which of install.resources holds the take
3–5rootMidi, rootCents, confidenceSuggested root key; −1 and 0 for an unpitched take
6character0 silent, 1 tonal, 2 percussive, 3 textural
7–9loopStartFrame, loopEndFrame, loopScoreLoop suggestion, −1 when none
10onsetFrameLeading frames the host trimmed

Frame positions are at the installed clip's own sample rate. Switch to the new slot at a block boundary and fade voices that still read the old one. The sampler-zone DSP leaf does this for you. Because factory takes can reach the same meta slot, one Processor path plays both.

3. Build the face

The face reads face.generation and calls its intent methods. Everything it sees is display-safe: status, progress, peaks, analysis and provenance.

tsx
const generation = face.generation

function onGenerate(prompt: string) {
  if (!generation || generation.state.availability !== "ready") return
  generation.generate({
    target: "take",
    prompt,
    seconds: 2,
    variations: 4,
    audioType: "one-shot",
  })
}

const target = generation?.state.targets.take
// target.phase: "idle" | "generating" | "ready" | "error"
// target.takes: each take's status, progress, peaks and analysis
// generation.selectTake("take", takeId) installs a take into the Processor
MethodEffect
generate(request)Starts takes for one target: prompt, seconds, variations, optional audioType, model and negativePrompt
selectTake(target, takeId)Installs the take; target.installedTakeId confirms it
regenerateTake(takeId)Same request, new seed, replaces only that take
pinTake, discardTakeKeep a take through a regenerate, or drop it
cancel(target)Stops that target's running job
setConnector(id)Switches between the runtimes the host offers
requestAuth()Opens the host's sign-in when a hosted runtime needs it

state.availability is ready, needs-auth, unavailable or error. When face.generation is absent, or unavailable, hide the Generate controls and keep the factory takes playable.

Optional host services — a take library, WAV export and Send to Sampler — appear in state.services. Check that object rather than the method before showing those actions.

The Crea Synth module is the complete reference: its target is in modules/crea-synth/src/index.ts and its Sound tab in modules/crea-synth/src/ui/index.tsx.

Runtimes: on this device or hosted

state.connectors lists the runtimes the host offers for this face. Each one reports a kind, a status, its limits, an optional cost and the models it can run:

KindRunsStatus to expect
local-workerIn the browser, on the person's GPU (WebGPU)needs-download until the person agrees to download the model, then available
hostedOn Maru's servers, metered on the person's accountneeds-auth until they sign in and are admitted
local-daemonA desktop companion app (planned)—
mockDeterministic test tones in developmentavailable

The host asks for consent before any model download and caches the model afterwards. Show the connector's label, status and statusDetail so a person understands why a choice is unavailable, and let them switch with setConnector.

Leave model unset to let the runtime choose for the audio type. Pass a model id only when your sound design depends on one; the ids a runtime accepts are listed in its connector's models, and an id a runtime does not list is refused rather than replaced.

Writing prompts

Describe the sound, not the instrument you are building. Name the source, articulation, space and length: "a dry wooden percussion hit, close microphone", "an evolving glassy pad, slow attack, wide stereo". The runtime adds any preamble its model expects for one-shots or loops, so do not add model-specific tags yourself.

Generate several variations per request and let the person choose. The host assigns seeds and records each take's seed and model in its provenance; regenerateTake tries a new seed for the same request.

Current implementations

Today's runtimes use the Stable Audio 3 family. These details can change as hosts add other models for the same task; your module does not need to change.

RuntimeModelAudio typesLongest take
This device, WebGPUStable Audio 3 Small SFXone-shot, sfx8 s
This device, WebGPUStable Audio 3 Small Musicloop30 s
This device, browsers without WebGPUStable Audio 3 Small SFX can run on WASM, but website hosts do not offer it yet——
Hosted (preview)Stable Audio 3 Medium and the Small modelsone-shot, loop, sfxReported in the connector's models and limits

The on-device model is a download of about 1.6 GB. Hosted generation needs an account admitted to the preview. Stable Audio output terms are under review, and connectors report the model licence as unknown until that review completes.

Where it works

HostGeneration
Website module pagesOn this device and hosted
module:dev workbenchTest runtime, this device and a configured hosted proxy
Studio and native plug-insNot yet; factory and saved takes play
Mobile and hardwareNo

Further reading