# Capability Spec: audio-plugins-fx (split) **Package**: `@suaveplan/audio-fx-plugin-base` + 24 `@suaveplan/audio-fx-plugin-` packages **Status**: SHIPPED — bundled package retired 2026-05-19 via split-audio-plugins-fx > Every requirement carries a `**Verified by:**` line. > > Predecessor history: this capability previously lived inside the single > `@suaveplan/audio-plugins-fx` package (folded from `enterprise-audio-plugins-fx` > and `extend-audio-plugins-fx-enterprise-extras`). On 2026-05-19 the package > was split into 25 per-plugin packages. The split itself is documented in > `openspec/archive/2026-05-19-split-audio-plugins-fx/`. The retired bundled > spec lives at that archive's `specs/audio-plugins-fx/spec.md`. ## Family layout The audio FX capability is distributed across 25 packages: one foundation (`@suaveplan/audio-fx-plugin-base`) plus one per concrete FX plugin (`@suaveplan/audio-fx-plugin-`). The previously-bundled `@suaveplan/audio-plugins-fx` was deleted on 2026-05-19. ## Requirements ### Requirement: Per-plugin package layout The audio FX capability SHALL be distributed as one foundation package (`@suaveplan/audio-fx-plugin-base`) plus one package per concrete plugin (`@suaveplan/audio-fx-plugin-`). No package SHALL bundle more than one plugin's DSP, parameter surface, or registry entry. #### Scenario: each plugin is independently installable - **WHEN** a consumer adds only `@suaveplan/audio-fx-plugin-eq` to its `package.json` - **THEN** `bun install` resolves `audio-fx-plugin-eq` + `audio-fx-plugin-base` + their transitive deps only, and no other `audio-fx-plugin-*` package appears in the lockfile #### Scenario: each plugin advances independently - **WHEN** `audio-fx-plugin-eq` graduates to `1.0.0` while `audio-fx-plugin-reverb` remains at `0.1.0` - **THEN** both packages publish from the same monorepo commit without forcing the other to bump **Verified by:** `packages/audio-fx/audio-fx-plugin-base/tests/dep-graph.test.ts::"declares no @suaveplan/audio-fx-plugin-* deps"` AND each plugin's own test suite green at 100/100/100/100 --- ### Requirement: `audio-fx-plugin-base` owns the shared contract surface `@suaveplan/audio-fx-plugin-base` SHALL export the `AudioFxPlugin` contract, `AbstractAudioFxPlugin`, `PluginChain`, `ChainContext`, `AmbientState`, `BusRegistry`, `PluginRegistry`, `ChainSnapshot`, `ChainEventBus`, `ParameterSchedule`, `PresetSnapshot`, the biquad DSP helpers (`computeBiquad`, `BiquadCoeffs`, `BiquadState`, `BiquadBand`, `BiquadBandType`, `identityCoeffs`), and the shared `AUDIO_FX.*` `error-codes` registry. No concrete plugin SHALL re-implement these primitives. #### Scenario: contract is the single source of truth - **WHEN** a new plugin package is authored - **THEN** it imports `AbstractAudioFxPlugin` from `@suaveplan/audio-fx-plugin-base/base` and never declares its own variant #### Scenario: chain composition stays in base - **WHEN** a consumer composes `audio-fx-plugin-eq`, `audio-fx-plugin-compressor`, and `audio-fx-plugin-reverb` into a chain - **THEN** it imports `PluginChain` from `@suaveplan/audio-fx-plugin-base/chain` and the chain accepts every plugin without per-plugin glue #### Scenario: biquad is shared, not duplicated - **WHEN** `audio-fx-plugin-eq`, `audio-fx-plugin-filter`, and any other biquad-consuming plugin compute filter coefficients - **THEN** each imports `computeBiquad` from `@suaveplan/audio-fx-plugin-base/biquad` (not from a sibling plugin) **Verified by:** `packages/audio-fx/audio-fx-plugin-base/src/chain/chain.test.ts::"PluginChain accepts any AudioFxPlugin implementation"` AND `packages/audio-fx/audio-fx-plugin-base/src/registry/registry.spec-conformance.test.ts::"PluginRegistry register/has/create/list invariants"` AND `packages/audio-fx/audio-fx-plugin-base/src/biquad/biquad.test.ts::"emits finite coefficients for low-shelf, high-shelf, low-pass, high-pass, peaking"` --- ### Requirement: `audio-fx-plugin-base` is dep-graph-isolated from concrete plugins `@suaveplan/audio-fx-plugin-base` SHALL declare zero dependencies on any `@suaveplan/audio-fx-plugin-*` package. The dependency arrow MUST point only from concrete plugins to base, never base to plugins. #### Scenario: no upward dep - **WHEN** CI runs `tests/dep-graph.test.ts` in `audio-fx-plugin-base` - **THEN** the test fails if `package.json` contains any `@suaveplan/audio-fx-plugin-*` entry other than `audio-fx-plugin-base` itself (which would be a self-dep and is also rejected) **Verified by:** `packages/audio-fx/audio-fx-plugin-base/tests/dep-graph.test.ts::"declares no @suaveplan/audio-fx-plugin-* deps"` --- ### Requirement: Error-code namespace preserved across the split Every error code that existed in `@suaveplan/audio-plugins-fx` SHALL remain reachable under the same `AUDIO_FX.*` namespace from `@suaveplan/audio-fx-plugin-base/error-codes`. Concrete plugins SHALL throw the existing codes via `AudioFxError` constructed from the same registry. No code SHALL be renamed, removed, or re-numbered by the split. #### Scenario: shared codes resolve from base - **WHEN** a caller imports `AUDIO_FX_CODES.CHAIN_SLOT_NOT_FOUND` from `@suaveplan/audio-fx-plugin-base/error-codes` - **THEN** the value is byte-identical to the value previously exported from `@suaveplan/audio-plugins-fx/error-codes` **Verified by:** `packages/audio-fx/audio-fx-plugin-base/src/error-codes/error-codes.test.ts` — every `AUDIO_FX_CODES` entry preserved and thrown by at least one production path --- ### Requirement: Migration leaves zero references to the old package After this change ships, no file under `packages/`, `apps/`, `scripts/` (excluding `scripts/split-audio-fx-plugin/`), `docs/`, `openspec/changes/`, or `openspec/specs/` SHALL import from `@suaveplan/audio-plugins-fx`. The old package directory SHALL be deleted. References inside `openspec/archive/` are immutable history and are exempt; the `scripts/split-audio-fx-plugin/` scaffold script may reference the old name as part of its rewrite logic. #### Scenario: no stale imports - **WHEN** CI runs `rg "@suaveplan/audio-plugins-fx" packages/ apps/ scripts/ docs/ openspec/changes/ openspec/specs/ | grep -v scripts/split-audio-fx-plugin` - **THEN** the command exits with status 1 (no matches found) #### Scenario: old package directory removed - **WHEN** the post-archive tree is inspected - **THEN** `packages/audio/audio-plugins-fx/` does not exist **Verified by:** `bunx turbo build` whole-monorepo green (catches missed import rewrites) AND `test ! -d packages/audio/audio-plugins-fx` ## Historical: bundled package The bundled `@suaveplan/audio-plugins-fx@0.1.0` (~503 tests, 100% coverage, 24 plugins under `src/plugins/`) was retired on 2026-05-19 in favour of this per-plugin layout. The bundled error-code registry, plugin contract, chain primitive, and registry now live in `@suaveplan/audio-fx-plugin-base`; every concrete plugin moved verbatim into its own package, with the shared biquad DSP math promoted to `@suaveplan/audio-fx-plugin-base/biquad`. No DSP, parameter, or error-code changes accompanied the split. See `openspec/archive/2026-05-19-split-audio-plugins-fx/` for the full proposal + tasks.