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

12 KiB
Raw Permalink Blame History

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: <TrackList>, <TrackRow>, <PluginChainView> (drag-to-reorder, drop-to-insert), <PluginSlot> (bypass toggle, dry/wet for plugins that expose it, parameter knobs/sliders rendered from parameterIndex, 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).
  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 <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 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.