# `adapter-track-ops` ## Purpose `adapter-track-ops` packs every track-scoped mutation that the `BrowserTimelineAdapter` exposes — adding, removing, updating, and the five per-track setters (`volume`, `mute`, `solo`, `pan`, plus the chain + subgraph teardown helper) — into pure functions that operate on an explicit `TrackOpEnv` rather than on adapter-private state. The adapter itself becomes a thin coordinator: it owns the lifecycle invariants and the lazy environment, and delegates the actual work here. Pulling the work out of the class keeps `browser-adapter.ts` inside the 300-line hard cap and makes every track operation unit-testable by passing a hand-built `TrackOpEnv` with deterministic state lambdas. The adapter file still exposes the same public methods; readers should follow the call chain to this module for the implementation. ## Features + `TrackOpEnv` interface — explicit dependency injection of the track map, graph, context, bus orchestrator, and the four lazy state accessors (`currentTime`, `playState`, `masterVolume`). + `addTrackImpl` — builds the subgraph, constructs the chain, wires the latency-changed event, applies mixer + ambient state. + `releaseTrackImpl` — symmetric teardown: dispose chain, tear down subgraph, drop from the map. + `updateTrackImpl`, `setTrack{Volume,Mute,Solo,Pan}Impl` — small composable mutations sharing the same epilogue (mixer / ambient recompute). ## Usage: Basic Example ```ts import { addTrackImpl } from "@suaveplan/audio-engine-browser/adapter/adapter-track-ops"; addTrackImpl(env, track); ``` ## Usage: Advanced Example ```ts import { addTrackImpl, releaseTrackImpl, setTrackPanImpl, setTrackVolumeImpl, } from "@suaveplan/audio-engine-browser/adapter/adapter-track-ops"; addTrackImpl(env, track); setTrackVolumeImpl(env, rec, 0.8); setTrackPanImpl(env, rec, -0.3); releaseTrackImpl(env, track.id); ``` ## API Reference ### `TrackOpEnv` ```ts interface TrackOpEnv { tracks: Map; graph: WebAudioGraph; context: ContextLike; busOrch: BusOrchestrator; currentTime(): number; playState(): "stopped" | "playing" | "paused"; masterVolume(): number; } ``` ### `addTrackImpl(env, track) → void` Builds the per-track subgraph, constructs a `PluginChain` bound to the project's shared `BusRegistry`, subscribes to `latency-changed`, and applies the mixer + ambient state. ### `releaseTrackImpl(env, trackId) → void` Removes the chain + subgraph for `trackId`. Idempotent for missing ids. ### `updateTrackImpl(env, rec, track) → void` Mutates the `TrackRecord` in place, refreshes the pan node, and re-applies mixer + ambient state. ### `setTrackVolumeImpl / setTrackMuteImpl / setTrackSoloImpl / setTrackPanImpl` Single-purpose updaters. Each mutates the relevant fields and runs the small epilogue: `applyMixerState` then `applyAmbientForTrack`. ## Implementation Notes + The OTel counter is module-scoped because every adapter shares the same series; the adapter itself owns the `kind=fx` increment, and this module owns `kind=track-subgraph` (+7 per added track). + `TrackOpEnv` accessors are lambdas rather than direct fields so the adapter can pass `() => this.currentTime` without leaking `this` binding. This keeps the helpers callable from any code path (top-level, tests, future async restoration). + `releaseTrackImpl` swallows chain dispose errors so a misbehaving plugin disposal cannot prevent the subgraph teardown — the adapter invariant is "track must be fully released by `removeTrack`". + Adding the same track id again replays the release path first, which is how the adapter handles `updateTrack` on a recreated chain shape.