# arrangement-engine

> Plays an arrangement on the audio clock: its audio clips, its notes through each track's instrument, its device chains, sends and returns, and an offline render to a buffer, with a hook for the transport, the playhead and the meters

```bash
npx shadcn@latest add @audio-ui/arrangement-engine
```

```ts
import { START_SECONDS, createArrangementEngine, renderArrangement, renderTrackClips } from "@/lib/arrangement-engine";
```

- Files: `lib/arrangement-engine.ts`, `hooks/use-arrangement-engine.ts`
- Depends on: `drum-synthesis`, `arrangement`, `use-arrangement`, `clip-player`, `device-library`, `mixer-engine`, `rack`, `drum-rack`, `instrument-rack`, `voice-pool`, `web-audio`; npm: `@audio-ui/engine`, `@audio-ui/react`
- Used by: `arrangement-view`, `block-audio-editor`, `block-device-chain`, `block-mixer`, `block-timeline`, `mixer-view`, `use-midi-playback`

| Export | Kind | What it is |
| --- | --- | --- |
| `START_SECONDS` | const | How far ahead a play is scheduled, so every part of the engine has heard of it before its first frame. |
| `MetronomeSettings` | interface | What the metronome does. |
| `ArrangementEngineOptions` | interface |  |
| `ArrangementEngine` | interface |  |
| `createArrangementEngine` | function | Plays an arrangement on a context: a clip player for each audio track, an instrument with a voice per held note for each MIDI track, each track's device chain, and the mixer they all go through, on one transport counted in frames of the audio clock. |
| `RenderOptions` | interface |  |
| `renderArrangement` | function | Renders the arrangement's main output to a buffer, faster than real time, through the same engine that plays it: the same frames come out. |
| `renderTrackClips` | function | What a track's clips play over `[from, to)` of the arrangement, and nothing else: their fades, gains and crossfades, without the track's devices, its mix, its automation or anything sent to it. |

## Usage

`arrangement-engine` plays the document `useArrangement` holds: each audio track through a clip player, each MIDI track through the instrument of its chain with a voice for each note it holds (so a monophonic instrument plays chords), every device chain, every send and return, and the main track, on one transport counted in frames of the audio clock. The document comes in as a value; the engine follows it while it plays, and the interface never touches the audio.

```tsx
import { useArrangement } from "@/hooks/use-arrangement";
import { useArrangementEngine } from "@/hooks/use-arrangement-engine";

const arrangement = useArrangement({ duration, initialTracks, tempo });
const engine = useArrangementEngine({
  doc: arrangement.doc,
  metronome: { on: clicking, rhythm }, // heard in playback, never in an export
  recordings, // a memoized Map of recording id to AudioBuffer
});

<Button onClick={() => (engine.playing ? engine.stop() : engine.play(cursor))}>Play</Button>;
<TimelinePlayhead source={engine.position} />; // seconds, as the speakers play them
<Meter source={useAudioLevels(engine.meter(track.id) ?? NONE)} />;
```

- `engine.noteOn(track, pitch, velocity)` and `engine.noteOff(track, pitch)` play a note on a MIDI track's instrument now, through its chain, as a key of a clip's piano roll does.
- `play(from)` starts 12 ms from now (`START_SECONDS`), so every track has heard of it before its first frame, round the document's loop when it is on; a note already sounding where it starts plays from there. `stop()` stops the clips and the notes and lets the effects' tails ring.
- `position` is a live source of the seconds the speakers play: the audio clock, less the output's latency and the engine's delay compensation, so the playhead is where the sound is. The engine stops itself at the end of the arrangement.
- `status` is the context's sample rate, its latency in seconds (the output's and the delay compensation's), and its state: what an engine line shows.
- A device switched off passes the sound on unchanged, as late as it would make it, so switching it moves nothing in time.

`renderArrangement(doc, recordings, { from, to, sampleRate })` renders the main output to a buffer through the same engine on an offline context, faster than real time, scheduling the notes a second at a time with `onProgress` as it goes: the same frames come out as play, and a neutral clip comes out bit for bit.

## To a file

`audio-encode` writes rendered sound to a WAV, FLAC or MP3 file. It encodes in chunks with a paint between two, so a long file never freezes the page, and loads each format's encoder (WebAssembly for FLAC and MP3) the first time it is used.

```bash
npx shadcn@latest add @audio-ui/audio-encode
```

```ts
import { renderArrangement } from "@/lib/arrangement-engine";
import { downloadFile, encodeAudio } from "@audio-ui/engine/files";

const rendered = await renderArrangement(doc, recordings, { from: 0, sampleRate: 48_000, to: 30 });
if (rendered) {
  const file = await encodeAudio([rendered.getChannelData(0), rendered.getChannelData(1)], {
    bitrate: 320,
    format: "mp3",
    onProgress: (done) => console.log(`${Math.round(done * 100)}%`),
    sampleRate: rendered.sampleRate,
  });
  downloadFile(file, "arrangement.mp3");
}
```

- `format` is `"wav"`, `"flac"` or `"mp3"`. `bitDepth` is 16 (the default) or 24 for WAV and FLAC, or 32 for a WAV of floats, which keeps what goes past full scale; anywhere else a sample past full scale is clipped. `bitrate` is an MP3's, in kbps, 128 by default.
- An MP3 comes out at the nearest rate MP3 has: 48 kHz for a render at 96 kHz.
