Files
2026-05-27 23:29:53 +01:00

111 lines
7.1 KiB
Markdown

<!-- markdownlint-disable MD024 -->
# 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-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-<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-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.
<!-- markdownlint-enable MD024 -->