12 KiB
OpenSpec Change: enterprise-react-audio
- Change ID: enterprise-react-audio
- Package:
@suaveplan/react-audio - Category: react
- Target path:
packages/react/react-audio - Status: PROPOSED
- Date: 2026-05-15
- Design:
./design.md - Spec delta:
./specs/react-audio/spec.md
Replaces the prior 2026-04-24 draft that was flagged
NEEDS-REWRITEbecause it targeted the obsolete monolithic@suaveplan/audio-engineand predated the universalaudio-core+audio-timeline+audio-transportsplit. See../REACT_AUDIO_WAVE_REWRITE_COMPLETE.md.
Summary
Deliver @suaveplan/react-audio on genesis from scratch at enterprise quality. The package is the React surface for the unified audio architecture: it composes @suaveplan/audio-engine-browser (the TimelineAdapter rendering backend), @suaveplan/audio-fx-plugin-base (the AudioFxPlugin contract + PluginChain primitive + AmbientState), @suaveplan/audio-timeline (TimelineEngine), @suaveplan/audio-transport (musical clock), and @suaveplan/audio-controllers (MIDI binding) into:
- A single React context provider (
AudioProvider) that owns one engine adapter, one timeline engine, one transport, one project-level bus registry, and one plugin registry for the React subtree below it. Strict-mode safe (double-render does not produce double-engine). Disposable; unmounting the provider tears down the engine cleanly. - A typed hook layer that exposes the unified architecture without leaking engine types:
useAudioEngine,useTimeline,useTransport,useTrack(id),usePluginChain(trackId),useChainPlugin(trackId, slotIndex),useParameter(plugin, paramId)(subscribes to the parameter's per-block value with sample-bounded updates),usePreset(plugin),useBus(name),useAnalyser(trackId | "master"),useTransportTime(),useEngineState(),useMidiController(controllerId). Hooks subscribe via the plugin / chain / transport event surfaces; updates are React-batched. - A component library that maps the contract to UI:
<TrackList>,<TrackRow>,<PluginChainView>(drag-to-reorder, drop-to-insert),<PluginSlot>(bypass toggle, dry/wet for plugins that expose it, parameter knobs/sliders rendered fromparameterIndex, preset selector),<MasterMeter>,<TransportControls>(play/pause/stop/seek/loop/tempo),<Mixer>(track strips with gain/pan/mute/solo/sends),<MicrophoneControls>,<GLaDOSControls>,<MidiControllerSetup>(learn-mode panel),<PresetManager>(chain snapshot save/restore via Zod-validated JSON). - Zustand stores for cross-component UI state that does not belong on the engine (panel open/closed, theme, visualization mode, drag state).
- First-class accessibility — every parameter control is keyboard-driven (arrow keys for fine, page-up/down for coarse, shift for ×10, alt for ×0.1), every knob/slider has a labelled
aria-valuetextwith the unit, every chain UI is screen-reader navigable. - Storybook stories for every component with default + interactive + edge-case states (empty chain, bypassed plugin, mid-load reverb IR, sidechain bound).
- 100% test coverage with happy-dom + injected
OfflineAudioContextperaudio-engine-browser's test seam.
The package contains no DSP, no plugin instance state, no transport scheduler, no parameter automation logic — every one of those lives below and is consumed by reference. React's job is presentation and event wiring.
Current-state audit (genesis)
The package does not exist on genesis yet — this is a fresh build replacing the obsolete pre-tier 2026-04-24 draft. The upstream react/media/react-audio/ is built on the monolithic @suaveplan/audio-engine; its public surface (12 hooks, 14 components, 2 stores) is captured here and re-derived against the unified architecture so prior consumer expectations carry over where they remain meaningful.
Parity Checklist (vs upstream ../suaveplan/packages/react/media/react-audio/)
| Capability | Upstream | Migrated | Notes |
|---|---|---|---|
| AudioProvider context | ✅ | ✅ | Now wraps BrowserTimelineAdapter + PluginRegistry + BusRegistry instead of the old monolithic engine. |
| useAudioEngine hook | ✅ | ⬆️ | Now exposes the TimelineEngine + Transport + chain APIs, not the obsolete AudioEngine class. |
| useAudioPlayer | ✅ | ⬆️ | Built on AudioPlayerPlugin from @suaveplan/audio-fx-plugin-audio-player; reads transport time from useTransportTime. |
| useAudioRecorder | ✅ | ✅ | Wraps MediaRecorder + the engine's tap-to-bus surface for in-app capture. |
| useAudioVisualizer | ✅ | ✅ | Reads useAnalyser(trackId) rather than reaching into the engine. |
| useDragDropQueue | ✅ | ✅ | Reuses the existing drag/drop utilities; unchanged surface. |
| useKeyboardShortcuts | ✅ | ✅ | Unchanged. |
| useMobileGestures | ✅ | ✅ | Unchanged. |
| usePlaylist | ✅ | ✅ | Built on AudioPlayerPlugin's loaded-source queue. |
| usePlugin (single plugin) | ✅ | ✅ | Now over AudioFxPlugin from @suaveplan/audio-fx-plugin-base. |
| usePluginChain | ✅ | ⬆️ | Now reflects the universal PluginChain primitive (insert / remove / move / replace / bypass / snapshot / restore / events). |
| usePresetManager | ✅ | ⬆️ | Plugin-level via usePreset(plugin) AND chain-level via useChainSnapshot(chain) for FX-rack-style "track presets". |
| useQueue | ✅ | ✅ | Unchanged. |
| audio-store (Zustand) | ✅ | ⬆️ | Slimmer; no playback state (that lives on transport). UI state only. |
| ui-store (Zustand) | ✅ | ✅ | Unchanged. |
| audio-dashboard component | ✅ | ✅ | Re-enabled; tree-shaking proven via subpath export bundle test. |
| audio-player component | ✅ | ✅ | Re-enabled; consumes AudioPlayerPlugin. |
| audio-provider component | ✅ | ⬆️ | Now also owns PluginRegistry and BusRegistry. |
| audio-visualizer | ✅ | ✅ | Same surface; reads useAnalyser. |
| basic-player | ✅ | ✅ | Slim variant. |
| premium-player | ✅ | ⬆️ | Adds chain UI for inserting FX plugins from the registry. |
| plugin-chain UI | ✅ | ⬆️ | Drag-to-reorder, drop-to-insert, bypass toggle, parameter rendering from parameterIndex. |
| plugin-controls (EQ/reverb/delay/compressor) | ✅ | ✅ | Replaced with generic <PluginSlot> that renders any AudioFxPlugin from its parameters declaration. EQ/reverb/delay/compressor get optional specialised UIs that opt in. |
| plugin-loader UI | ✅ | ⬆️ | Now reads from PluginRegistry.list(); supports dynamic registration. |
| mixer | ✅ | ✅ | Built on MixerPlugin from @suaveplan/audio-fx-plugin-mixer. |
| preset-manager | ✅ | ✅ | Chain-snapshot save/restore. |
| microphone-controls | ✅ | ✅ | Built on MicrophonePlugin. |
| vocoder-controls (GLaDOS) | ✅ | ✅ | Built on GLaDOSPlugin. |
| MIDI controller setup (NEW) | – | ⬆️ | Genesis addition; learn-mode panel for MidiControllerPlugin from audio-controllers. |
| Storybook coverage | ⚠ partial | ✅ | Every component gets default / interactive / edge-case stories. |
| Coverage | ~70 % | ✅ | 100/100/100/100 per genesis policy. |
No 🚫 rows.
Available workspace deps on genesis
| Package | Role |
|---|---|
@suaveplan/audio-engine-browser (workspace:^) |
BrowserTimelineAdapter + DefaultBusRegistry |
@suaveplan/audio-timeline (workspace:^) |
TimelineEngine, Track, Clip |
@suaveplan/audio-transport (workspace:^) |
Transport musical clock |
@suaveplan/audio-fx-plugin-base (workspace:^) |
AudioFxPlugin, PluginChain, PluginRegistry, every plugin |
@suaveplan/audio-controllers (workspace:^) |
MidiControllerPlugin, bindParameter |
@suaveplan/events (workspace:^) |
Typed event subscriptions for hook updates |
@suaveplan/error (workspace:^) |
defineCodes + AudioError |
@suaveplan/types (workspace:^) |
LoggerLike |
@suaveplan/opentelemetry (workspace:^) |
OTel spans on lifecycle events |
react (peerDependency) |
React 19 peer |
zustand (catalog:state) |
UI store |
framer-motion (catalog:animation) |
Knob / slider animation |
lucide-react (catalog:ui) |
Icons |
zod (catalog:forms) |
Provider-options validation |
Dev deps: vitest, @vitest/coverage-istanbul, happy-dom, @storybook/react, @testing-library/react, @testing-library/user-event, playwright (all catalog:testing).
File layout
packages/react/react-audio/
package.json
tsconfig.json
vitest.config.ts
src/
index.ts
error-codes.ts (+ .test.ts .md)
provider/
audio-provider.tsx (+ .md .types.ts .test.tsx .stories.tsx)
provider-options.ts (+ .schemas.ts .md .test.ts)
index.ts
hooks/
use-audio-engine.ts (+ .md .test.tsx)
use-timeline.ts (+ .md .test.tsx)
use-transport.ts (+ .md .test.tsx)
use-transport-time.ts (+ .md .test.tsx)
use-engine-state.ts (+ .md .test.tsx)
use-track.ts (+ .md .test.tsx)
use-plugin-chain.ts (+ .md .test.tsx)
use-chain-plugin.ts (+ .md .test.tsx)
use-audio-fx-plugin.ts (+ .md .test.tsx)
use-parameter.ts (+ .md .test.tsx)
use-preset.ts (+ .md .test.tsx)
use-chain-snapshot.ts (+ .md .test.tsx)
use-bus.ts (+ .md .test.tsx)
use-analyser.ts (+ .md .test.tsx)
use-audio-recorder.ts (+ .md .test.tsx)
use-audio-visualizer.ts (+ .md .test.tsx)
use-drag-drop-queue.ts (+ .md .test.tsx)
use-keyboard-shortcuts.ts (+ .md .test.tsx)
use-mobile-gestures.ts (+ .md .test.tsx)
use-playlist.ts (+ .md .test.tsx)
use-queue.ts (+ .md .test.tsx)
use-midi-controller.ts (+ .md .test.tsx)
index.ts
stores/
audio-store.ts (+ .md .test.ts)
ui-store.ts (+ .md .test.ts)
index.ts
components/
transport-controls/ (.tsx .md .test.tsx .stories.tsx)
track-list/ ...
track-row/ ...
plugin-chain-view/ ...
plugin-slot/ ...
plugin-parameter-knob/ ...
plugin-parameter-slider/ ...
preset-selector/ ...
preset-manager/ ...
master-meter/ ...
mixer/ ...
audio-provider-component/ ...
audio-visualizer/ ...
audio-dashboard/ ...
basic-player/ ...
premium-player/ ...
audio-player/ ...
microphone-controls/ ...
vocoder-controls/ ...
plugin-loader/ ...
midi-controller-setup/ ...
a11y/
keyboard-bindings.ts (+ .md .test.ts)
aria-value-text.ts (+ .md .test.ts)
index.ts
__benchmarks__/
hook-rerender.bench.ts
chain-ui.bench.ts
tests/
integration.test.tsx
purity.test.ts
exports.test.ts
bundle-size/
hooks-only.size.test.ts
provider-only.size.test.ts
e2e/
audio.spec.ts
plugin-chain.spec.ts
midi-learn.spec.ts
Impact
Breaking changes: none — fresh build.
Prerequisites: audio-engine-browser, audio-fx-plugin-base + every audio-fx-plugin-* package, audio-controllers, audio-timeline, audio-transport, events, error, types, opentelemetry shipped to Verdaccio.
Affected dependents
@suaveplan/react-audio-player— [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: separate changeenterprise-react-audio-playerconsumes this package's hooks + provider; created against this spec, not retrofitted)@suaveplan/react-audio-stream— [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: separate changeenterprise-react-audio-streambuilds on this package's transport hooks; created against this spec)@suaveplan/react-audio-fingerprint— [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: separate changeenterprise-react-audio-fingerprintconsumesuseAnalyser; created against this spec)@suaveplan/r3f-audio— [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: separate changeenterprise-gaming-r3f-audioconsumesuseTransport+ the game-audio building blocks from the@suaveplan/audio-fx-plugin-*family; created against this spec)
Once landed, any subsequent change to this package's public surface MUST run bunx turbo test --filter=...@suaveplan/react-audio per openspec/AGENTS.md §13 step 10.