# `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 ```ts 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 ```ts // 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): 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.