Files
suaveplan/openspec/archive/2026-05-15-superseded-enterprise-browser-audio-controllers/proposal.md
2026-05-27 23:29:53 +01:00

207 lines
7.1 KiB
Markdown

# OpenSpec Change: enterprise-browser-audio-controllers
- **Change ID**: enterprise-browser-audio-controllers
- **Package**: `@suaveplan/audio-controllers`
- **Category**: browser
- **Target path**: `browser/audio/audio-controllers`
- **Status**: IN PROGRESS
- **Date**: 2026-04-24
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/audio-controllers.md`
---
## Summary
Deliver `@suaveplan/audio-controllers` on the genesis branch from scratch at enterprise quality. The package ships MIDI hardware controller integrations built on `@suaveplan/audio-engine`: `KontrolS88Plugin` (88-key weighted keyboard, scale modes, arpeggiator, LED feedback), `MaschinePlugin` (16-pad controller, step sequencer, sample slots), and `TraktorS4Plugin` (4-channel DJ controller, crossfader, decks, LED feedback). Each plugin owns its MIDI mapping, state machine, parameters, and presets, integrating with the Web MIDI API and the audio graph.
---
## Current-state audit (genesis)
The package does not exist on genesis yet — fresh build.
---
## Available workspace deps on genesis
| Package | Role |
|---|---|
| `@suaveplan/audio-engine` (`workspace:^`) | `AudioPlugin` base class, `AudioGraph`, `PluginState`, `PluginParameter`, `PluginMetadata` |
| `@suaveplan/error` (`workspace:^`) | Typed MIDI/controller errors |
| `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `EventBusLike` |
| `@suaveplan/events` (`workspace:^`) | `EventEmitter` for typed per-controller event maps |
| `@suaveplan/schema-registry` (`workspace:^`) | Register preset Zod schemas |
| `zod` (`catalog:forms`) | Schema for every `*Preset` type |
| `@opentelemetry/api` (`catalog:observability`) | MIDI message counters, LED frame histograms |
| `standardized-audio-context` (`catalog:browser`) | Web Audio context for audio node creation |
| `tslib` (`catalog:build`) | Runtime helpers |
Dev deps: `vitest`, `@vitest/coverage-istanbul` (`catalog:testing`), `@suaveplan/testing workspace:^`.
---
## Preserved public API surface (from main branch)
### KontrolS88Plugin
```ts
class KontrolS88Plugin extends AudioPlugin {
config: KontrolS88Config
getPreset(): KontrolS88Preset
applyPreset(preset: KontrolS88Preset): void
setScale(mode: ScaleMode): void
setArpMode(mode: ArpMode): void
setPitchBend(value: number): void
setModWheel(value: number): void
sendLED(keyIndex: number, color: LEDColor): void
onMIDIMessage(message: MIDIMessageEvent): void
}
// + KontrolS88Config, KontrolS88Events, KontrolS88Metrics, KontrolS88Preset, KontrolS88State
// + ArpMode, ScaleMode, VelocityCurve, KeyState, TransportState, MIDIKontrolS88Mapping
```
### MaschinePlugin
```ts
class MaschinePlugin extends AudioPlugin {
getPreset(): MaschinePreset
applyPreset(preset: MaschinePreset): void
setPadVelocity(padIndex: number, velocity: number): void
setStep(padIndex: number, stepIndex: number, active: boolean): void
loadSample(slotIndex: number, sampleId: string): Promise<void>
onMIDIMessage(message: MIDIMessageEvent): void
}
// + MaschineConfig, MaschineEvents, MaschinePreset, MaschineState, PadState, SequencerState, SampleSlotState
```
### TraktorS4Plugin
```ts
class TraktorS4Plugin extends AudioPlugin {
getPreset(): TraktorS4Preset
applyPreset(preset: TraktorS4Preset): void
loadDeck(deckIndex: number, track: DeckTrack): void
setCrossfader(value: number): void
setChannelVolume(channel: number, value: number): void
onMIDIMessage(message: MIDIMessageEvent): void
}
// + TraktorS4Config, TraktorS4Events, TraktorS4Preset, DeckState, DeckTrack, MixerState
```
---
## New additions for genesis
### 1. `MidiControllerPlugin` base class
Common abstract base class at `src/base/midi-controller-plugin.ts` owning:
- Web MIDI `requestMIDIAccess()` with permission model (`MIDIUnavailable`, `MIDIPermissionDenied` typed errors)
- Hot-plug: `navigator.requestMIDIAccess().onstatechange` listener; auto-rebind on reconnect; `device:connected` / `device:disconnected` events
- LED diff/flush engine with per-frame byte budget
- Declarative CC-mapping DSL: `cc(0x10).controls('encoder[0]')` with static type checking
- `@suaveplan/events` `EventEmitter` — all three controllers use `EventEmitter<*Events>` instead of `EventTarget`
### 2. Structured errors via `@suaveplan/error`
Error codes: `MIDI_UNAVAILABLE`, `MIDI_PERMISSION_DENIED`, `MIDI_DEVICE_DISCONNECTED`, `MIDI_SYSEX_REJECTED`, `CONTROLLER_PRESET_INVALID`, `SAMPLE_LOADER_FAILED`.
### 3. Arpeggiator / sequencer shared primitive
`src/sequencer/sequencer.ts` — shared audio-clock-synced arpeggiator/step-sequencer primitive used by both Kontrol arpeggio and Maschine step sequencer. Clocked off the engine's audio clock, not `setInterval`.
### 4. Learn mode
`src/learn/learn-mode.ts` — runtime CC-learn UX: listens for MIDI CC input, stores remapping via `@suaveplan/storage-browser` (injected adapter).
### 5. OTel metrics
Counters `midi.messages_in_total`, `midi.messages_out_total`, `led.frames_total`, `midi.xrun_total`; histogram `midi.input_to_audio_latency_ms`.
### 6. Preset Zod schemas
Zod schemas for `KontrolS88Preset`, `MaschinePreset`, `TraktorS4Preset` registered in `@suaveplan/schema-registry`.
### 7. SysEx safety
SysEx handled only when `sysex: true` is requested in `requestMIDIAccess`. Fuzz test for malformed SysEx frames.
---
## File layout
```text
browser/audio/audio-controllers/
package.json
tsconfig.json
vitest.config.ts
src/
index.ts
error-codes.ts
base/
midi-controller-plugin/
midi-controller-plugin.ts
midi-controller-plugin.md
midi-controller-plugin.types.ts
midi-controller-plugin.test.ts
index.ts
plugins/
kontrol-s88/
mapping.ts (CC/note → parameter mapping DSL)
state.ts (KeyState, TransportState machine)
arpeggio.ts (arpeggiator with scale quantizer)
scale.ts (scale/chord transform)
preset.ts (KontrolS88Preset Zod schema + serializer)
led.ts (LED feedback engine)
kontrol-s88.ts (plugin class orchestrator)
kontrol-s88.md
kontrol-s88.types.ts
kontrol-s88.test.ts
index.ts
maschine/
mapping.ts
state.ts
sequencer.ts (step-sequencer with step grid)
sample-loader.ts (real sample-loader contract, no placeholder)
preset.ts
maschine.ts
maschine.md
maschine.types.ts
maschine.test.ts
index.ts
traktor-s4/
mapping.ts
deck.ts
mixer.ts
led.ts
preset.ts
traktor-s4.ts
traktor-s4.md
traktor-s4.types.ts
traktor-s4.test.ts
index.ts
sequencer/
sequencer/
sequencer.ts
sequencer.md
sequencer.test.ts
index.ts
learn/
learn-mode/
learn-mode.ts
learn-mode.md
learn-mode.test.ts
index.ts
tests/
integration.test.ts
e2e/ (Playwright specs with virtual MIDI device)
```
---
## Impact
**Downstream**: `@suaveplan/react-audio`, game audio surfaces.
**Prerequisites**: `@suaveplan/audio-engine`, `@suaveplan/error`, `@suaveplan/events` published to Verdaccio.