# 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-REWRITE` because it targeted the obsolete monolithic `@suaveplan/audio-engine` and predated the universal `audio-core` + `audio-timeline` + `audio-transport` split. 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: 1. **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. 2. **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. 3. **A component library** that maps the contract to UI: ``, ``, `` (drag-to-reorder, drop-to-insert), `` (bypass toggle, dry/wet for plugins that expose it, parameter knobs/sliders rendered from `parameterIndex`, preset selector), ``, `` (play/pause/stop/seek/loop/tempo), `` (track strips with gain/pan/mute/solo/sends), ``, ``, `` (learn-mode panel), `` (chain snapshot save/restore via Zod-validated JSON). 4. **Zustand stores** for cross-component UI state that does not belong on the engine (panel open/closed, theme, visualization mode, drag state). 5. **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-valuetext` with the unit, every chain UI is screen-reader navigable. 6. **Storybook stories** for every component with default + interactive + edge-case states (empty chain, bypassed plugin, mid-load reverb IR, sidechain bound). 7. **100% test coverage** with happy-dom + injected `OfflineAudioContext` per `audio-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 `` 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 ```text 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 change `enterprise-react-audio-player` consumes 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 change `enterprise-react-audio-stream` builds on this package's transport hooks; created against this spec) - `@suaveplan/react-audio-fingerprint` — [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: separate change `enterprise-react-audio-fingerprint` consumes `useAnalyser`; created against this spec) - `@suaveplan/r3f-audio` — [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: separate change `enterprise-gaming-r3f-audio` consumes `useTransport` + 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.