Initial Commit
This commit is contained in:
@@ -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.
|
||||
@@ -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 -->
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user