207 lines
7.1 KiB
Markdown
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.
|