Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
# Extend audio-plugins-fx with the enterprise extras
**Package**: `@suaveplan/audio-plugins-fx`
**Status**: PROPOSED
**Type**: feature
## Why
The in-flight `enterprise-audio-plugins-fx` change shipped 19 plugins matching the genesis-original spec: EQ, Filter, Compressor, Limiter, Gate, Reverb, Delay, Chorus, Phaser, Flanger, Tremolo, AutoFilter, Distortion, Sampler, Synthesizer, PitchShifter, Spatial, Mixer, StereoWidener. The enterprise spec at `../suaveplan/enterprise/audio-plugins-fx.md` lists five further plugins that the main-branch package ships but genesis does not yet cover: `AudioAnalyzer`, `MicrophoneSimulationPlugin`, `GLaDOSPlugin`, `GameAudioPlugin`, and `AudioPlayerPlugin`. These plugins drive the React audio surface (visualisers, voice-effect demos), the React audio player, and the game-audio composite used by the game packages. Without them in genesis, downstream packages cannot be rebuilt at parity with main.
This change adds the five plugins on their own subpaths so consumers can tree-shake to exactly the surface they need, keeps every plugin universal-tier under the same `AudioFxPlugin` contract that the existing 19 already satisfy, and routes any browser-only fast paths through the optional `createAudioNode` / `getWorkletProcessor` hooks declared in the base contract.
## What changes
- ADDED: `./audio-analyzer` subpath exporting `AudioAnalyzerPlugin` — tap-point analyser exposing `getFrequencyData()`, `getWaveformData()`, async getters for `tempo`, `pitch`, `spectralFeatures`, `energy`, `key`, plus an `onAnalysisUpdate(callback)` subscription and `applyPreset(name)` for `music | speech | percussion | ambient`. Pass-through processing on the audio path.
- ADDED: `./microphone` subpath exporting `MicrophoneSimulationPlugin` and `createMicrophonePlugin(options)` factory — source plugin (`io.inputs[0].count === 0`, `outputs.count === 1`) that synthesises a microphone-coloured signal from white-noise + oscillator + frequency-resonance-distortion + dynamics + wow-and-flutter stages, all configurable via a typed `MicrophoneConfig`. Includes the structured UI control descriptor (`MicrophoneUISection[]`) so editors can render the simulation's parameter surface without hard-coding it.
- ADDED: `./glados` subpath exporting `GLaDOSPlugin` — voice-effect chain combining pitch shift, formant shift, robotic intensity, distortion, reverb, and low-pass filtering with named presets (`glados-mainline | glados-test-chamber | glados-cake-deception | wheatley | cave-johnson | robotic-generic`). Insert plugin (`io.inputs[0].count === outputs[0].count === "preserve"`).
- ADDED: `./game` subpath exporting `GameAudioPlugin` — composite plugin for game audio: spatial sources (`createSpatialSource`, position/orientation/Doppler/occlusion), music-layer mixing (`addMusicLayer`, `crossfadeMusicLayers`), and proximity voice chat (`enableProximityVoiceChat`, `addVoiceChatParticipant`, distance-based attenuation, bandwidth optimisation tier). Effects bus with blendable voice effects.
- ADDED: `./audio-player` subpath exporting `AudioPlayerPlugin` — source plugin that decodes an audio asset from a URL into a `Float32Array` PCM buffer (universal path) or routes through `HTMLAudioElement.captureStream()` (browser path via `createAudioNode`). Exposes `load(url, { signal? })`, `play()`, `pause()`, `stop()`, `seek(time)`, `setVolume`, `setLoop`, `setPlaybackRate`, `getState()`.
- MODIFIED: package `vite.config.ts` and `package.json` to declare the five new subpath exports.
- MODIFIED: `openspec/specs/audio-plugins-fx/spec.md` — folded delta adds five new Requirement blocks, each with at least one `Verified by:` line pointing at a real test path.
## Out of scope
- Browser-engine integration (`@suaveplan/audio-engine-browser` wiring `createAudioNode` into a Web Audio graph) — separate change in that package.
- React bindings (`@suaveplan/react-audio-analyzer-viz`, `@suaveplan/react-audio-player`) — handled in their own packages once these plugins land.
- The `essentia.js` optional analyser bridge from main-branch — deferred behind a `peerDependenciesMeta.optional` declaration in a follow-up; the universal analyser uses the built-in browser-equivalent FFT path only.
- Worklet hot-reload, oversampling on Compressor/Distortion/PitchShifter, mid/side processing on EQ/Compressor/Reverb, the convolution IR library, parameter-automation lanes beyond the existing universal `setParameterCurve` surface — all listed in the enterprise plan's §4 enhancements, all deferred to dedicated change proposals so this one stays scoped to "add the missing plugins".
- The package split into per-plugin packages (`split-audio-plugins-fx`) — a separate in-flight change; lands after this one once the surface is complete.
## Source mapping
> **REQUIRED.** This change derives from `../suaveplan/enterprise/audio-plugins-fx.md` plus the implementation under `../suaveplan/packages/browser/audio-plugins-fx/src/plugins/<name>/`.
| Requirement | Source lines | Notes |
|---|---|---|
| Audio analyzer plugin | `enterprise/audio-plugins-fx.md` §1 line 11; `packages/browser/audio-plugins-fx/src/plugins/audio-analyzer/audio-analyzer.ts:28-870` | Reworded for universal tier; FFT path stays, essentia bridge deferred |
| Microphone simulation plugin | `enterprise/audio-plugins-fx.md` §1 line 11; `packages/browser/audio-plugins-fx/src/plugins/microphone/microphone.ts:10-490` | UI section descriptor preserved verbatim |
| GLaDOS voice-effect plugin | `enterprise/audio-plugins-fx.md` §1 line 11; `packages/browser/audio-plugins-fx/src/plugins/glados/glados.ts:21-700` | Preset set expanded per §4.7 (Wheatley, Cave Johnson, robotic-generic) |
| Game audio plugin | `enterprise/audio-plugins-fx.md` §1 line 11; `packages/browser/audio-plugins-fx/src/plugins/game/game-audio.ts:22-820` | Composite of spatial + music-layer + proximity-voice surfaces |
| Audio player plugin | `enterprise/audio-plugins-fx.md` §1 line 11; `packages/browser/audio-plugins-fx/src/plugins/audio-player/audio-player.ts:18-413` | Universal decode path replaces HTMLAudioElement-only impl from main |
### Requirements with no source line
| Requirement | Justification |
|---|---|
| `AbortSignal` on every async load entry point | Aligns with the genesis-side `loadImpulseResponse` / `loadSample` precedent — no main-branch equivalent because main's `load(url)` ignores abort |
| Universal-tier purity (no browser globals on the `BlockProcessor` path) | New constraint introduced by the genesis package design; the main-branch plugins assume an `AudioContext` and are not portable |
| Zod boundary validation on every plugin's options + preset shape | Already required by the existing `enterprise-audio-plugins-fx` change for the first 19 plugins; this extension keeps the policy consistent |
## Verification approach
Each new plugin lands with the same five-file co-location pattern the existing 19 already use: `<name>.ts` (implementation), `<name>.types.ts`, `<name>.test.ts`, `<name>.md`, plus `schemas/{schemas.ts, schemas.md, index.ts}` and `index.ts`. Tests run under vitest with no mocks — the audio path uses real `Float32Array` arithmetic, the URL loaders use `data:` URLs the same way `ReverbPlugin.loadImpulseResponse` already does, and any browser-only paths are exercised via `happy-dom` polyfills.
- `src/plugins/audio-analyzer/audio-analyzer.test.ts` — covers analyzer surface (FFT, presets, callback subscription).
- `src/plugins/microphone/microphone.test.ts` — covers source-plugin behaviour (output non-silent, options validated, UI section descriptor preserved).
- `src/plugins/glados/glados.test.ts` — covers voice-chain wiring, parameter setters, preset application.
- `src/plugins/game/game-audio.test.ts` — covers spatial source lifecycle, music-layer crossfade, proximity-voice attenuation.
- `src/plugins/audio-player/audio-player.test.ts` — covers load / play / stop / seek / loop / playbackRate, AbortSignal handling.
- `bunx turbo test:coverage --filter=@suaveplan/audio-plugins-fx` — must hit 100/100/100/100 on every new module the same way the existing 19 do.
## Migration impact
None — additive only. The five new subpath exports do not modify or remove any existing export. Consumers of the existing 19 plugins continue to work without changes. The `split-audio-plugins-fx` change (in-flight) will relocate every plugin including these five into its own package, but the API surface stays stable across that move.

View File

@@ -0,0 +1,135 @@
<!-- markdownlint-disable MD024 -->
# Capability Spec Delta: audio-plugins-fx (extend-audio-plugins-fx-enterprise-extras)
**Package**: `@suaveplan/audio-plugins-fx`
**Status**: PROPOSED
> Every requirement below carries a `**Verified by:**` line at authoring time. No exceptions. (See `openspec/AGENTS.md` Section 13.)
## ADDED Requirements
### Requirement: Audio analyzer plugin
The `./audio-analyzer` subpath SHALL export `AudioAnalyzerPlugin`, an analysis-only tap point that passes input audio through unchanged while extracting frequency-domain and time-domain features. The plugin SHALL expose synchronous `getFrequencyData()` and `getWaveformData()` accessors returning the latest FFT magnitudes and the most recent input window respectively, plus async `getTempo()`, `getPitch()`, `getSpectralFeatures()`, `getEnergy()`, and `getKey()` getters that return immutable result records. It SHALL expose `onAnalysisUpdate(callback)` returning an unsubscribe function and call `callback` on a configurable cadence with a snapshot of the enabled analyses. It SHALL expose `applyPreset(name)` accepting one of `"music" | "speech" | "percussion" | "ambient"`, each preset toggling a documented subset of analyses on or off. `enable<X>Detection(boolean)` setters and `is<X>DetectionEnabled()` accessors SHALL be available for tempo / pitch / spectral / beat analyses so callers can tune the CPU budget per use case.
#### Scenario: passes audio through unchanged
- **WHEN** `AudioAnalyzerPlugin` processes a non-silent input block
- **THEN** the output block equals the input block sample-for-sample within IEEE-clean tolerance
#### Scenario: applyPreset toggles the documented subset of analyses
- **WHEN** `applyPreset("speech")` is called on a default instance
- **THEN** `isPitchDetectionEnabled()` becomes `true`, `isBeatDetectionEnabled()` becomes `false`, and the same toggles persist across the next block
#### Scenario: onAnalysisUpdate fires on the configured cadence
- **WHEN** `onAnalysisUpdate(callback)` is registered and the plugin processes blocks for at least the configured `updateIntervalMs`
- **THEN** `callback` is invoked at least once with an `AnalysisUpdate` carrying the enabled analyses, and the returned unsubscribe stops further invocations
**Verified by:** `src/plugins/audio-analyzer/audio-analyzer.test.ts::"output equals input sample-for-sample on the audio path"` AND `src/plugins/audio-analyzer/audio-analyzer.test.ts::"applyPreset('speech') toggles the documented analyses"` AND `src/plugins/audio-analyzer/audio-analyzer.test.ts::"onAnalysisUpdate fires once per updateInterval and unsubscribe stops it"`
---
### Requirement: Microphone simulation plugin
The `./microphone` subpath SHALL export `MicrophoneSimulationPlugin` and a `createMicrophonePlugin(options)` factory. The plugin SHALL be a source (`io.inputs[0].count === 0`, `io.outputs[0].count === 1`) that synthesises microphone-coloured audio from an oscillator + filtered-noise + dynamics + wow-and-flutter chain. Construction options SHALL include the structured control groups documented in the main-branch `microphone.types.ts` (`FrequencyControls`, `ResonanceControls`, `DistortionControls`, `NoiseControls`, `DynamicsControls`, `CompressorControls`, `GainControls`, `CharacterControls`, `WowAndFlutterControls`). The plugin SHALL expose a `getUISections()` accessor returning a `ReadonlyArray<MicrophoneUISection>` describing every control as a typed `MicrophoneUIControl` (id, label, min, max, step, unit, group) so editors can render the UI without hard-coding the layout.
#### Scenario: source plugin produces non-silent output
- **WHEN** `MicrophoneSimulationPlugin` is constructed with defaults and processes a 4096-sample block
- **THEN** the output's RMS exceeds `1e-4` and every sample is finite
#### Scenario: UI section descriptor mirrors the configured controls
- **WHEN** `getUISections()` is called on a default instance
- **THEN** the returned sections enumerate every `*Controls` field in `MicrophoneConfig` with stable `id` strings and bounded `min`/`max` values
#### Scenario: preset round-trip preserves the full control state
- **WHEN** `getPreset()` is called, the resulting payload is fed into `applyPreset()` on a fresh instance, and the second instance's controls are read
- **THEN** every nested field matches the source instance's pre-snapshot state
**Verified by:** `src/plugins/microphone/microphone.test.ts::"default microphone produces non-silent output"` AND `src/plugins/microphone/microphone.test.ts::"getUISections enumerates every configured control"` AND `src/plugins/microphone/microphone.test.ts::"preset round-trip preserves the full microphone control state"`
---
### Requirement: GLaDOS voice-effect plugin
The `./glados` subpath SHALL export `GLaDOSPlugin`, an insert plugin (`io.inputs[0].count === io.outputs[0].count === "preserve"`) that runs the input audio through a fixed signal chain of pitch-shift → formant-shift → robotic-intensity tremolo → distortion → low-pass → reverb. The plugin SHALL expose typed setters for every stage: `setPitchShift`, `setFormantShift`, `setRoboticIntensity`, `setDistortionAmount`, `setReverbAmount`, `setLowPassCutoff`. It SHALL expose `applyPreset(name)` accepting one of `"glados-mainline" | "glados-test-chamber" | "glados-cake-deception" | "wheatley" | "cave-johnson" | "robotic-generic"`, each preset setting all six stage values together. Calling a setter with an out-of-range value SHALL throw `AudioFxError` with code `AUDIO_FX.INVALID_OPTIONS`.
#### Scenario: applyPreset sets every stage at once
- **WHEN** `applyPreset("wheatley")` is called on a default instance
- **THEN** the six stage parameter values match the documented Wheatley preset record
#### Scenario: voice chain colours the input audibly
- **WHEN** the plugin processes a non-silent input block at the default `"glados-mainline"` preset
- **THEN** the output differs from the input (per-sample RMS difference > `1e-3`) and every sample remains finite
#### Scenario: out-of-range setter is rejected
- **WHEN** `setRoboticIntensity(99)` is called with the parameter range documented as `[0, 1]`
- **THEN** the call throws `AudioFxError` whose `code === "AUDIO_FX.INVALID_OPTIONS"` with the offending value in `context`
**Verified by:** `src/plugins/glados/glados.test.ts::"applyPreset('wheatley') sets every stage to the wheatley preset values"` AND `src/plugins/glados/glados.test.ts::"glados-mainline preset colours the input audibly"` AND `src/plugins/glados/glados.test.ts::"setRoboticIntensity rejects out-of-range values with AUDIO_FX.INVALID_OPTIONS"`
---
### Requirement: Game audio plugin
The `./game` subpath SHALL export `GameAudioPlugin`, a composite source plugin that drives game-audio scenarios. It SHALL maintain a registry of spatial sources, music layers, and proximity-voice-chat participants:
- Spatial sources: `createSpatialSource(id, position)`, `updateSpatialSourcePosition(id, position)`, `applyDopplerEffect(id, velocity)`, `applyOcclusion(id, level)`, `cullDistantSources(maxDistance)`, `getActiveSpatialSourceCount()`.
- Music layers: `addMusicLayer(name, buffer)`, `removeMusicLayer(name)`, `getMusicLayers()`, `crossfadeMusicLayers(from, to, durationMs)`, `isCrossfading()`.
- Proximity voice chat: `enableProximityVoiceChat(config)`, `addVoiceChatParticipant(id, position)`, `removeVoiceChatParticipant(id)`, `updateVoiceChatParticipantPosition(id, position)`, `calculateVoiceAttenuation(sourcePosition)`, `optimizeVoiceChatBandwidth()`, `getVoiceChatOptimizationLevel()`.
The plugin SHALL expose `setListenerPosition` / `setListenerOrientation` and apply distance attenuation per the Web Audio inverse-distance model (matching `SpatialAudioPlugin`'s contract). Voice attenuation SHALL fall off monotonically as the participant moves away from the listener through the configured `[minDistance, maxDistance]` band.
#### Scenario: spatial source lifecycle is symmetric
- **WHEN** a caller `createSpatialSource("a", { x: 0, y: 0, z: 0 })` then queries `getActiveSpatialSourceCount()`
- **THEN** the count is `1`; after `cullDistantSources(0)` the count is `0`
#### Scenario: voice attenuation falls off monotonically with distance
- **WHEN** `calculateVoiceAttenuation` is called for participants placed at increasing distances within `[minDistance, maxDistance]`
- **THEN** the returned attenuation values strictly decrease across the sequence
#### Scenario: music-layer crossfade reports its in-flight state
- **WHEN** `crossfadeMusicLayers("intro", "main", 200)` is called and immediately after `isCrossfading()` is queried
- **THEN** the call returns `true`; after at least `200` ms of process time has elapsed (verified via the universal `nowSec` opt), it returns `false`
**Verified by:** `src/plugins/game/game-audio.test.ts::"createSpatialSource + cullDistantSources balance the source registry"` AND `src/plugins/game/game-audio.test.ts::"calculateVoiceAttenuation decreases monotonically with distance"` AND `src/plugins/game/game-audio.test.ts::"crossfadeMusicLayers flips isCrossfading until the configured duration elapses"`
---
### Requirement: Audio player plugin
The `./audio-player` subpath SHALL export `AudioPlayerPlugin`, a source plugin (`io.inputs[0].count === 0`, `io.outputs[0].count === 1`) that decodes a raw Float32 PCM stream from a URL into an internal buffer and plays it back through the universal `BlockProcessor` path. The plugin SHALL expose `load(url, { signal? }): Promise<void>` accepting an `AbortSignal`; pre-aborted signal SHALL throw `AudioFxError` with code `AUDIO_FX.ABORTED` before any fetch. After load it SHALL expose `play()`, `pause()`, `stop()`, `seek(timeSec)`, `setVolume(0..1)`, `setLoop(boolean)`, `setPlaybackRate(0.25..4)`, and `getState()` returning `{ playing, paused, currentTimeSec, durationSec, volume, loop, playbackRate }`. Triggering `play()` before any `load()` SHALL throw `AudioFxError` with code `AUDIO_FX.SAMPLE_NOT_LOADED`.
#### Scenario: load + play renders the loaded audio
- **WHEN** `load(dataUrl)` resolves and `play()` is called, then the plugin processes a block
- **THEN** the output buffer contains the first samples of the loaded PCM scaled by `volume`
#### Scenario: play without load is rejected
- **WHEN** `play()` is called before any `load()`
- **THEN** the call throws `AudioFxError` whose `code === "AUDIO_FX.SAMPLE_NOT_LOADED"`
#### Scenario: pre-aborted signal short-circuits load
- **WHEN** `load(url, { signal })` is called with an already-aborted signal
- **THEN** the call throws `AudioFxError` whose `code === "AUDIO_FX.ABORTED"` before any fetch is issued
#### Scenario: setLoop with `true` re-plays after the end of the buffer
- **WHEN** a short buffer is loaded, `setLoop(true)` is called, and the plugin processes more samples than the buffer contains
- **THEN** the output stream wraps back to the start of the buffer without producing silence in between
**Verified by:** `src/plugins/audio-player/audio-player.test.ts::"load + play renders the loaded buffer on the next process block"` AND `src/plugins/audio-player/audio-player.test.ts::"play before load throws AUDIO_FX.SAMPLE_NOT_LOADED"` AND `src/plugins/audio-player/audio-player.test.ts::"pre-aborted signal short-circuits before fetch"` AND `src/plugins/audio-player/audio-player.test.ts::"setLoop wraps playback back to the buffer start"`
<!-- markdownlint-enable MD024 -->

View File

@@ -0,0 +1,86 @@
# Tasks — Extend audio-plugins-fx with the enterprise extras
> Every checkbox MUST reflect work actually done in this change.
## 1. Spec authoring
- [ ] 1.1 Read `openspec/AGENTS.md` Sections 13 (Verified by) and 14 (Source mapping)
- [ ] 1.2 Draft `proposal.md` (this file's sibling)
- [ ] 1.3 Draft `specs/audio-plugins-fx/spec.md` with five ADDED Requirement blocks
- [ ] 1.4 Every requirement carries a `**Verified by:**` line naming concrete `it(...)` titles
## 2. Implementation — `AudioAnalyzerPlugin`
- [ ] 2.1 `mkdir -p packages/audio/audio-plugins-fx/src/plugins/audio-analyzer/schemas`
- [ ] 2.2 Author `audio-analyzer.ts` — tap-point analyser; `getFrequencyData`, `getWaveformData`, async `getTempo`/`getPitch`/`getSpectralFeatures`/`getEnergy`/`getKey`, `onAnalysisUpdate(callback)`, `applyPreset(name)`
- [ ] 2.3 Author `audio-analyzer.types.ts` (analysis update shape, preset enum, callback alias)
- [ ] 2.4 Author `schemas/schemas.ts` (Zod for options + preset enum)
- [ ] 2.5 Author `audio-analyzer.test.ts` — happy-dom not required (universal FFT path)
- [ ] 2.6 Sibling `.md` docs on every `.ts` (≥200 words, basic + advanced examples, 6 sections)
- [ ] 2.7 Wire subpath export in `package.json` + `vite.config.ts`
## 3. Implementation — `MicrophoneSimulationPlugin`
- [ ] 3.1 `mkdir -p packages/audio/audio-plugins-fx/src/plugins/microphone/schemas`
- [ ] 3.2 Author `microphone.ts` — source plugin synthesising mic-coloured audio from oscillator + filtered noise + dynamics + character + wow/flutter stages
- [ ] 3.3 Author `microphone.types.ts` carrying every `*Controls` interface verbatim from main-branch (`FrequencyControls`, `ResonanceControls`, …, `MicrophoneUISection[]` shape)
- [ ] 3.4 Author `createMicrophonePlugin(options)` factory matching main-branch surface
- [ ] 3.5 Author `schemas/schemas.ts` (Zod for `MicrophoneConfig` + `MicrophonePreset`)
- [ ] 3.6 Author `microphone.test.ts` — covers source-plugin behaviour, preset round-trip, UI section integrity
- [ ] 3.7 Sibling `.md` docs on every `.ts`
- [ ] 3.8 Wire subpath export in `package.json` + `vite.config.ts`
## 4. Implementation — `GLaDOSPlugin`
- [ ] 4.1 `mkdir -p packages/audio/audio-plugins-fx/src/plugins/glados/schemas`
- [ ] 4.2 Author `glados.ts` — voice-effect chain: pitch-shift → formant-shift → robotic-intensity tremolo → distortion → low-pass → reverb. Reuses internal building blocks (biquad filter math, etc.) without depending on sibling plugin classes
- [ ] 4.3 Author `glados.types.ts` carrying `GLaDOSConfig`, `GLaDOSPreset` union, `GLaDOSPresetConfig` shape
- [ ] 4.4 Author `schemas/schemas.ts` (Zod for options + the six built-in presets)
- [ ] 4.5 Author `glados.test.ts` — preset application changes parameter values; voice chain produces non-silent output for non-silent input
- [ ] 4.6 Sibling `.md` docs on every `.ts`
- [ ] 4.7 Wire subpath export in `package.json` + `vite.config.ts`
## 5. Implementation — `GameAudioPlugin`
- [ ] 5.1 `mkdir -p packages/audio/audio-plugins-fx/src/plugins/game/schemas`
- [ ] 5.2 Author `game-audio.ts` — composite with three internal sub-systems: `SpatialSourceManager`, `MusicLayerEngine`, `ProximityVoiceChat`; size budget likely requires extracting these to siblings under `game/spatial/`, `game/music/`, `game/voice/`
- [ ] 5.3 Author `game-audio.types.ts` carrying `Vector3`, `GameVoiceEffect`, `SpatialAudioSource`, `VoiceChatParticipant`, `ProximityVoiceChatConfig`, `GameState`, `MusicLayer`, `VoiceTransformationConfig`, `GameAudioConfig`, `GameAudioMetrics`, `ListenerOrientation`
- [ ] 5.4 Author `schemas/schemas.ts` (Zod for every config + the `Vector3`/orientation shapes)
- [ ] 5.5 Author `game-audio.test.ts` covering spatial source lifecycle, distance-based voice attenuation, music-layer crossfade
- [ ] 5.6 Sibling `.md` docs on every `.ts`
- [ ] 5.7 Wire subpath export in `package.json` + `vite.config.ts`
## 6. Implementation — `AudioPlayerPlugin`
- [ ] 6.1 `mkdir -p packages/audio/audio-plugins-fx/src/plugins/audio-player/schemas`
- [ ] 6.2 Author `audio-player.ts` — source plugin loading raw Float32 PCM via `data:` URL or via `fetch(url, { signal })`; `play`, `pause`, `stop`, `seek`, `setVolume`, `setLoop`, `setPlaybackRate`, `getState`
- [ ] 6.3 Author `audio-player.types.ts` carrying `AudioPlayerConfig`, `AudioPlayerState`, `AudioPlayerLoadOptions`
- [ ] 6.4 Author `schemas/schemas.ts` (Zod for options + load options)
- [ ] 6.5 Author `audio-player.test.ts` covering load + play + stop + AbortSignal + state round-trip
- [ ] 6.6 Sibling `.md` docs on every `.ts`
- [ ] 6.7 Wire subpath export in `package.json` + `vite.config.ts`
## 7. Pre-archive verification
- [ ] 7.1 `bun run check` — Biome 0 errors / 0 warnings across the whole monorepo
- [ ] 7.2 `bunx turbo lint typecheck test --filter=@suaveplan/audio-plugins-fx` — green
- [ ] 7.3 `bunx turbo test:coverage --filter=@suaveplan/audio-plugins-fx` — 100/100/100/100 across the package
- [ ] 7.4 `bun scripts/openspec-conformance.ts @suaveplan/audio-plugins-fx` — exit 0
- [ ] 7.5 `bunx turbo build` — whole-monorepo build green (catches missed import rewrites)
- [ ] 7.6 No file over 300 lines (source) or 500 lines (test); split per `game-audio` plan if needed
## 8. Pre-archive 7-row checklist (AGENTS.md Section 13)
- [ ] 8.1 Generics threaded; `expectTypeOf` assertions on every literal-type claim
- [ ] 8.2 Zod schemas wired at every declared boundary
- [ ] 8.3 Every new error message key thrown by at least one production path; error code reuses `AUDIO_FX.*` namespace
- [ ] 8.4 Strict-mode behaviors tested on and off where applicable
- [ ] 8.5 OTel instruments asserted where introduced
- [ ] 8.6 Async-load idempotency tests (loadSample-style)
- [ ] 8.7 No retroactive tick — every box above reflects work done in this change
## 9. Archive
- [ ] 9.1 Fold spec delta into `openspec/specs/audio-plugins-fx/spec.md` (add the five new Requirement blocks)
- [ ] 9.2 Move `openspec/changes/extend-audio-plugins-fx-enterprise-extras/` → `openspec/archive/<yyyy-mm-dd>-extend-audio-plugins-fx-enterprise-extras/`
- [ ] 9.3 Refresh `MANIFEST.md` via `bun scripts/folder-reorg/generate-manifest.ts` (if new dirs were added)