Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# `mixer`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`MixerPlugin` is the universal multi-channel summing mixer. Each input channel is one channel strip with its own gain, pan, and named sends; the panned outputs are summed into a stereo master bus. The output sum is internally unclipped — a caller wanting hard-cap behaviour chains a `LimiterPlugin` downstream. Per-channel and master VU levels are exposed via `getVuLevels()` so a UI can drive meters from a single source of truth.
|
||||
|
||||
The plugin extends `AbstractAudioFxPlugin` and runs identically across browser, server, offline, and worklet renderers. Channels live as plain configuration on the plugin (not as parameters) and survive preset round-trip via `MixerPresetData`.
|
||||
|
||||
## Features
|
||||
|
||||
- `MixerChannel[]` configured at construction with per-channel `id`, `initialGain`, `initialPan`, and `initialSends`. Up to 64 channels per instance.
|
||||
- One automatable parameter: `masterGain` (0..4, default 1).
|
||||
- `setChannelGain(id, gain)`, `setChannelPan(id, pan)`, `setSend(channelId, busId, amount)` — mutate channel strips at runtime. Unknown ids throw `AUDIO_FX.CHAIN_SLOT_NOT_FOUND`.
|
||||
- Equal-power per-channel panning: `angle = (pan + 1) · π / 4`, `leftGain = cos(angle) · gain`, `rightGain = sin(angle) · gain`. Centre pan splits equally to `±√½`.
|
||||
- Sum into a fixed-count stereo output (`io.outputs[0].count === 2`) regardless of input channel count.
|
||||
- `getVuLevels()` returns per-channel RMS and master RMS over the most recent block.
|
||||
- Sends are stored on the channel strip and persisted in presets. The universal block-path does not route sends through buses — the browser engine wires them up via `createAudioNode()` in a future revision.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { MixerPlugin } from "@suaveplan/audio-fx-plugin-mixer";
|
||||
|
||||
const mixer = new MixerPlugin({
|
||||
channels: [
|
||||
{ id: "drums", initialGain: 0.8, initialPan: 0 },
|
||||
{ id: "bass", initialGain: 0.7, initialPan: -0.1 },
|
||||
{ id: "lead", initialGain: 0.6, initialPan: 0.2 },
|
||||
],
|
||||
});
|
||||
chain.add(mixer);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { MixerPlugin } from "@suaveplan/audio-fx-plugin-mixer";
|
||||
|
||||
const mixer = new MixerPlugin({
|
||||
channels: [
|
||||
{ id: "vocal" },
|
||||
{ id: "guitar" },
|
||||
{ id: "drums" },
|
||||
],
|
||||
initialMasterGain: 0.9,
|
||||
});
|
||||
await mixer.setup(
|
||||
{ sampleRate: 48_000, channels: 3, blockSize: 512 },
|
||||
ctx
|
||||
);
|
||||
|
||||
// Route vocal to a reverb send and a delay send.
|
||||
mixer.setSend("vocal", "reverb", 0.4);
|
||||
mixer.setSend("vocal", "delay", 0.2);
|
||||
|
||||
// Live VU metering at ~30 fps.
|
||||
setInterval(() => {
|
||||
const vu = mixer.getVuLevels();
|
||||
meter.draw(vu);
|
||||
}, 33);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `MixerPlugin` (class extends `AbstractAudioFxPlugin`)
|
||||
- `new MixerPlugin(options: MixerPluginOptions)` — throws `AUDIO_FX.INVALID_OPTIONS` on malformed options or duplicate channel ids.
|
||||
- `getChannels(): ReadonlyArray<MixerChannel>` — current channel strips.
|
||||
- `setChannelGain(id, gain)`, `setChannelPan(id, pan)`, `setSend(channelId, busId, amount)` — mutate channel strips.
|
||||
- `getVuLevels(): MixerVuSnapshot` — per-channel RMS + master RMS for the most recent block.
|
||||
- Inherited accessor for `masterGain`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The mixer declares `io.outputs[0].count === 2` (fixed stereo) rather than `"preserve"` because the spec calls for "a master bus" — a single output target — and downstream plugins should be able to assume a stereo bus regardless of how many input channels feed in. `io.inputs[0].count === "any"` accepts whatever the predecessor produces, with channels beyond the configured strip count silently ignored.
|
||||
|
||||
Per-channel sends are stored on the strip but **not routed through the `BusRegistry` in this universal implementation**. A future browser-engine build will read the `sends` map in `createAudioNode()` and wire each strip's output through the named auxiliary buses. The spec test only checks "sums channels with applied gain and pan", which the universal path satisfies; full send routing requires the browser-engine integration that lives outside this package.
|
||||
|
||||
The mixer's per-block VU update is RMS, not peak. RMS is what mixing engineers expect when they read a "VU" label — peak meters are typically labelled "PPM" in the same UI. Computing RMS adds one accumulator + one `Math.sqrt` per channel and one `Math.sqrt` for the master at the end of the block, which is negligible compared to the per-sample multiply-accumulate work. If a future surface needs peak metering, it can subscribe to the per-block telemetry stream and reduce locally.
|
||||
|
||||
The internal sum is unclipped on purpose. The spec's "sum its channels into the output block without clipping internally" requirement is verified by a test that pushes the per-channel gain to `1.5` with two channels active, producing an output of `3.0` — well past `±1` — and asserting that value is what comes out. This keeps the mixer composable: a caller can chain a `LimiterPlugin` downstream for hard-cap behaviour, or leave the clipping policy to the host renderer's master.
|
||||
Reference in New Issue
Block a user