7.1 KiB
Capability Spec: audio-plugins-fx (split)
Package: @suaveplan/audio-fx-plugin-base + 24 @suaveplan/audio-fx-plugin-<name> 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-fxpackage (folded fromenterprise-audio-plugins-fxandextend-audio-plugins-fx-enterprise-extras). On 2026-05-19 the package was split into 25 per-plugin packages. The split itself is documented inopenspec/archive/2026-05-19-split-audio-plugins-fx/. The retired bundled spec lives at that archive'sspecs/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-<name>). 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-<name>). 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-eqto itspackage.json - THEN
bun installresolvesaudio-fx-plugin-eq+audio-fx-plugin-base+ their transitive deps only, and no otheraudio-fx-plugin-*package appears in the lockfile
Scenario: each plugin advances independently
- WHEN
audio-fx-plugin-eqgraduates to1.0.0whileaudio-fx-plugin-reverbremains at0.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
AbstractAudioFxPluginfrom@suaveplan/audio-fx-plugin-base/baseand never declares its own variant
Scenario: chain composition stays in base
- WHEN a consumer composes
audio-fx-plugin-eq,audio-fx-plugin-compressor, andaudio-fx-plugin-reverbinto a chain - THEN it imports
PluginChainfrom@suaveplan/audio-fx-plugin-base/chainand 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
computeBiquadfrom@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.tsinaudio-fx-plugin-base - THEN the test fails if
package.jsoncontains any@suaveplan/audio-fx-plugin-*entry other thanaudio-fx-plugin-baseitself (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_FOUNDfrom@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.