Files
skills/docs/media/audio-fx/audio-fx-plugin-base/src/chain/context/context.md
2026-05-29 08:20:56 +01:00

4.8 KiB
Raw Blame History

context

(no submodules)

Purpose

ChainContextFactory builds one ChainContext per plugin slot for the PluginChain class. The handed-out reference is stable for the chain's lifetime so plugins can capture it at setup() and dereference its slots every block; the factory rebuilds peer lists and updates the ambient slot getter so consumers always see the latest state without re-binding the context object itself. AmbientStateBox is the small mutable wrapper that lets the factory expose a getter-only ambient property pointing at whatever the chain just pushed; pluginHandleFor constructs the read-only PluginHandle peer view that lets plugins see each other without holding the mutator surface.

Features

  • One stable ChainContext reference per slot for the chain's lifetime; consumers may safely capture and dereference.
  • Getter-based position, predecessors, successors, ambient so the same context reference reflects subsequent rebuilds without re-allocation.
  • AmbientStateBox exposes current for the context's ambient getter; set(next) swaps the pointer in one assignment.
  • pluginHandleFor produces a read-only PluginHandle with bypassed as a getter against the slot record, so changing the slot's bypass flag propagates to every peer view immediately.

Usage: Basic Example

import {
    AmbientStateBox,
    ChainContextFactory,
    defaultAmbientState,
    pluginHandleFor,
} from "@suaveplan/audio-fx-plugin-base/chain";

const ambient = new AmbientStateBox(defaultAmbientState());
const factory = new ChainContextFactory({
    chain: { id: "t-1", name: "Track 1", channels: 2, sampleRate: 48_000, blockSize: 128, slotCount: 0 },
    buses: busRegistry,
    ambient,
});

factory.rebuild([
    { plugin: handleFor(eq, slotA), bypassed: false },
    { plugin: handleFor(comp, slotB), bypassed: false },
]);

await eq.setup(processorConfig, factory.contextFor(0));
await comp.setup(processorConfig, factory.contextFor(1));

Usage: Advanced Example

// Chain composition change: insert a limiter at slot 0.
factory.rebuild([
    { plugin: handleFor(lim, slotL), bypassed: false },
    { plugin: handleFor(eq, slotA), bypassed: false },
    { plugin: handleFor(comp, slotB), bypassed: false },
]);
// eq.chainContext.position now reports { index: 1, total: 3 } automatically
// because position is a getter that reads from the factory's slots array.

// Ambient push from the engine on the next render block:
ambient.set(applyAmbientPatch(ambient.current, { transport: { tempo: 140 } }));
// Every captured ChainContext.ambient.transport.tempo now reads 140.

API Reference

  • class ChainContextFactory({ chain, buses, ambient }) — builds and caches one ChainContext per slot.
    • rebuild(slots: ReadonlyArray<ChainSlotSnapshot>): void — recomputes the cached contexts; reuses existing references where possible.
    • contextFor(index: number): ChainContext — returns the cached context for a slot; throws on out-of-range index.
  • class AmbientStateBox(initial: AmbientState) — mutable wrapper around the current ambient state.
    • current: AmbientState — latest pushed value.
    • set(next: AmbientState): void — swap the pointer.
  • ChainSlotSnapshot — { plugin: PluginHandle; bypassed: boolean } per chain slot.
  • pluginHandleFor(plugin, slot): PluginHandle — wraps a plugin instance + its mutable slot record into the read-only peer view.

Implementation Notes

The ChainContext reference is stable per slot because the position, predecessors, successors, and ambient members are getters reading from the factory's mutable backing fields. A naive implementation that returned a fresh object on each rebuild would force every plugin to re-capture the context, breaking the documented "captured at setup()" contract. The getters make captured references self-updating.

predecessors / successors allocate a fresh array on each get because peers may have changed between blocks; the size is typically 0–10 (chain depth), so the allocation is negligible. Plugins that read peer state every block can cache the array length once between renders if they care about per-block allocation budgets.

pluginHandleFor accepts both the plugin's read-only surface AND the slot's mutable bypass flag so the bypassed getter resolves correctly when the chain flips bypass mid-render. Tests verify the getter sees subsequent mutations of the slot record.

The factory's cached-context behaviour preserves references when the chain shrinks: existing slot contexts are reused, trailing ones drop. Re-adding a slot later allocates a fresh context. This is the right shape because a plugin disposed off the end of the chain should not retain its old context; if it's re-added, it gets a clean view.