3.6 KiB
PluginChain + BusRegistry + SidechainPublisher
(no submodules)
Purpose
PluginChain owns the ordered list of FX plugins attached to a single
track, the per-chain ambient state, the chain-level bypass flag, and the
effective plugin-delay-compensation latency for that track. DefaultBusRegistry
is the project-level registry passed to every chain via
ChainContext.buses; SidechainPublisher is the handle returned by
BusRegistry.publish. Together they implement plugin slot 5 of the audio
family's three-layer model — the "FX slot list" that the
ServerTimelineAdapter hosts on each track. Every plugin runs through its
BlockProcessor process() path; no createAudioNode or worklet path is
used on the server.
Features
- Ordered plugin list with
add/remove/setPluginBypass. - Chain-level bypass that short-circuits
processwhile preserving the hosted plugins. - Ambient state propagation —
updateAmbientmerges a patch intoChainContext.ambientand fires every plugin'sonAmbienthook before the next block runs. - Plugin-delay-compensation via
getEffectiveLatencySamples(); the chain emitslatency-changedwhenever the sum of un-bypassed plugin latencies changes so the host engine can recompute per-track PDC. - Typed event surface (
plugin-added,plugin-removed,bypass-changed,ambient-changed,latency-changed) withon(event, listener)returning an unsubscribe closure. DefaultBusRegistry—publishreturns aSidechainPublisher,subscribenotifies listeners on registration and on every publish / close, andget/listexpose the read surface plugins consume.
Usage: Basic
import { DefaultBusRegistry, PluginChain } from "@suaveplan/audio-engine-server";
const buses = new DefaultBusRegistry();
const chain = new PluginChain({
id: "track-1",
channels: 2,
sampleRate: 48_000,
blockSize: 512,
buses,
});
chain.process([new Float32Array(512), new Float32Array(512)], 0);
Usage: Advanced
const pub = buses.publish("kick", 2);
pub.write([kickLeft, kickRight]);
const offChain = chain.on("latency-changed", ({ effectiveLatencySamples }) => {
recomputePerTrackPdc(effectiveLatencySamples);
});
chain.updateAmbient({
track: { pan: -0.5 },
transport: { state: "playing", time: 1.25 },
});
offChain();
pub.close();
API Reference
class PluginChain—add,remove,setPluginBypass,updateAmbient,process,getEffectiveLatencySamples,on,dispose, plus read accessorsid,bypassed,context,buses,list.class DefaultBusRegistry implements BusRegistry—publish,subscribe,get,list.interface SidechainPublisher—bus,write,close.interface BusRegistry,interface ChainContext,interface ChainPlugin,interface AmbientState,interface AmbientStatePatch, plus theChainEvent/ChainEventPayloadpair.
Implementation Notes
The chain re-uses the same Float32Array[] block as both input and
output for each plugin's BlockProcessor.process call, so each plugin
writes into the channel arrays the next plugin reads from. Latency
recomputation runs after every mutation (add / remove /
setPluginBypass) and only fires latency-changed when the effective sum
actually changes — repeated no-op mutations stay quiet. The default bus
registry stores per-bus payloads as a Float32Array[] slot mutated by
SidechainPublisher.write; close drops the slot and notifies every
subscribed listener with undefined so plugins reading the bus on the
next block see silence rather than stale samples.