7.1 KiB
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
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
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
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,MIDIPermissionDeniedtyped errors) - Hot-plug:
navigator.requestMIDIAccess().onstatechangelistener; auto-rebind on reconnect;device:connected/device:disconnectedevents - LED diff/flush engine with per-frame byte budget
- Declarative CC-mapping DSL:
cc(0x10).controls('encoder[0]')with static type checking @suaveplan/eventsEventEmitter— all three controllers useEventEmitter<*Events>instead ofEventTarget
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
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.