8.6 KiB
OpenSpec Change: enterprise-audio-controllers
- Change ID: enterprise-audio-controllers
- Package:
@suaveplan/audio-controllers - Category: browser
- Target path:
packages/audio/audio-controllers - Status: PROPOSED
- Date: 2026-05-14
- Design:
./design.md - Spec delta:
./specs/audio-controllers/spec.md
Summary
Deliver @suaveplan/audio-controllers on the genesis branch from scratch at enterprise quality. MIDI hardware controller integrations for the genesis monorepo. Built on @suaveplan/audio-core and @suaveplan/audio-transport for audio-clock-synced sequencing. Provides a universal MidiControllerPlugin base class with hot-plug support, LED feedback, CC-mapping DSL, and learn mode.
Controllers run in the browser (Web MIDI API) but their sequencer/arpeggio output feeds into the universal Transport and TimelineEngine, so patterns can be rendered offline or on the server.
Current-state audit (genesis)
The package does not exist on genesis yet — this is a fresh build. No prior source, no test results, no coverage data. The old enterprise-browser-audio-controllers was browser-only and not tied to the universal transport.
Parity Checklist (vs upstream ../suaveplan/packages/browser/audio-controllers/)
| Capability | Upstream | Migrated | Notes |
|---|---|---|---|
KontrolS88Plugin (88-key weighted keyboard) |
✅ | ✅ | Same surface; now consumes audio-transport for scale-aware arpeggios instead of setInterval. |
Scale modes (major, minor, chromatic, …) |
✅ | ✅ | Unchanged enum. |
| Arpeggiator | ✅ | ⬆️ | Now transport-synced via audio-transport Transport.schedule; upstream used setInterval. |
KontrolS88Preset save / load |
✅ | ✅ | Now Zod-validated through the package's own preset schemas. |
| Pitch-bend / mod-wheel CC mapping | ✅ | ✅ | Same mapping; routed through the new bindParameter helper for FX surfaces. |
LED feedback / flushLEDs |
✅ | ✅ | Same surface; batched per requirement "MIDI input and output mapping". |
MaschinePlugin (16-pad + step sequencer + sample slots) |
✅ | ✅ | Step sequencer now drives off Sequencer/Transport; sample loading accepts AbortSignal. |
TraktorS4Plugin (4-channel DJ controller) |
✅ | ✅ | Same surface; deck-sync now reads transport tempo from ChainContext.ambient.transport when hosted in a chain. |
Web MIDI access via navigator.requestMIDIAccess |
✅ | ⬆️ | Now reached only through the injected MidiAccessAdapter seam; tests inject fake-device fixtures rather than mocking the platform. |
| Connection-state FSM | ⚠ ad-hoc | ⬆️ | Now @suaveplan/fsm-driven with explicit disconnected/connecting/connected/error and graceful state recovery on reconnect. |
| Learn mode for CC mapping | – | ⬆️ | Genesis addition. |
Plugin base class (AudioPlugin) |
✅ via audio-engine |
⬆️ | Replaced by MidiControllerPlugin base whose plugins integrate with @suaveplan/audio-plugins-fx's AudioFxPlugin for parameter binding via bindParameter. |
*.types.ts files duplicating implementation declarations |
⚠ smell | ⬆️ | Consolidated; one types file per feature module. |
| Unit tests / coverage | ⚠ one .spec.ts only |
⬆️ | 100/100/100/100 across every module with real fake-device fixtures (no mocks). |
| Storybook coverage | – | – | n/a — controllers are non-visual. |
No 🚫 rows.
Available workspace deps on genesis
| Package | Role |
|---|---|
@suaveplan/audio-core (workspace:^) |
AudioDocument, BlockProcessor |
@suaveplan/audio-transport (workspace:^) |
Transport, TransportTime |
@suaveplan/events (workspace:^) |
Typed event bus for controller events |
@suaveplan/error (workspace:^) |
AudioControllersError base class; AUDIO_CONTROLLER.* codes |
@suaveplan/types (workspace:^) |
LoggerLike |
@suaveplan/opentelemetry (workspace:^) |
OTel spans, counters, histograms |
zod (catalog:forms) |
Zod schemas for option types |
Dev deps: vitest, @vitest/coverage-istanbul, fast-check, mitata (all catalog:testing).
Public API
Core types
abstract class MidiControllerPlugin {
readonly metadata: ControllerMetadata
async requestAccess(sysex?: boolean): Promise<MIDIAccess>
onStateChange(event: MIDIConnectionEvent): void
flushLEDs(updates: LEDUpdate[]): void
defineMappings(mappings: CCMapping[]): void
enterLearnMode(): void
exitLearnMode(): void
on<K extends keyof ControllerEventMap>(event: K, listener: ControllerEventMap[K]): void
off<K extends keyof ControllerEventMap>(event: K, listener: ControllerEventMap[K]): void
readonly sequencer: Sequencer
initialize(): Promise<void>
destroy(): Promise<void>
}
class Sequencer {
constructor(transport: Transport)
setTempo(bpm: number): void
setSteps(steps: boolean[]): void
setStep(index: number, active: boolean): void
start(): void
stop(): void
reset(): void
onStep(index: number, callback: () => void): void
}
class KontrolS88Plugin extends MidiControllerPlugin {
setScale(mode: ScaleMode): void
setArpMode(mode: ArpMode): void
setPitchBend(value: number): void
setModWheel(value: number): void
sendLED(keyIndex: number, color: LEDColor): void
}
class MaschinePlugin extends MidiControllerPlugin {
setPadVelocity(padIndex: number, velocity: number): void
setStep(padIndex: number, stepIndex: number, active: boolean): void
loadSample(slotIndex: number, sampleId: string): Promise<void>
}
class TraktorS4Plugin extends MidiControllerPlugin {
loadDeck(deckIndex: number, track: DeckTrack): void
setCrossfader(value: number): void
setChannelVolume(channel: number, value: number): void
}
New additions for genesis
- Universal sequencer base —
Sequenceruses@suaveplan/audio-transportfor sample-accurate scheduling, notsetInterval. - Transport-synced arpeggiator — arpeggio patterns are
Transportevent subscribers, renderable offline. - Hot-plug support —
onStateChangehandles connect/disconnect with graceful state recovery. - CC mapping DSL — declarative control-change to parameter mapping with learn mode.
- LED feedback — bidirectional MIDI for controller LED/state updates.
- OTel instrumentation — spans for MIDI message handling, sequencer step timing.
- Property-based tests — random step patterns → correct sequence, random CC values → mapped parameters.
- Benchmark suite — MIDI message throughput, sequencer scheduling latency.
File layout
packages/audio/audio-controllers/
package.json
tsconfig.json
vitest.config.ts
src/
index.ts
error-codes.ts
base/
midi-controller-plugin.ts
midi-controller-plugin.md
midi-controller-plugin.types.ts
midi-controller-plugin.test.ts
index.ts
sequencer/
sequencer.ts
sequencer.md
sequencer.types.ts
sequencer.test.ts
index.ts
arpeggiator/
arpeggiator.ts
arpeggiator.md
arpeggiator.test.ts
index.ts
learn/
learn-mode.ts
learn-mode.md
learn-mode.test.ts
index.ts
plugins/
kontrol-s88/
kontrol-s88.ts, kontrol-s88.md, kontrol-s88.test.ts
mapping.ts, state.ts, scale.ts, led.ts, preset.ts
index.ts
maschine/
maschine.ts, maschine.md, maschine.test.ts
mapping.ts, state.ts, sample-loader.ts, preset.ts
index.ts
traktor-s4/
traktor-s4.ts, traktor-s4.md, traktor-s4.test.ts
mapping.ts, deck.ts, mixer.ts, led.ts, preset.ts
index.ts
__benchmarks__/
midi.bench.ts
tests/
integration.test.ts
e2e/
kontrol-s88.e2e.test.ts
maschine.e2e.test.ts
traktor-s4.e2e.test.ts
Impact
Breaking changes: none — fresh build, package does not yet exist on genesis.
Prerequisites: @suaveplan/audio-core, @suaveplan/audio-transport, @suaveplan/events, @suaveplan/error, @suaveplan/types, @suaveplan/opentelemetry, @suaveplan/fsm published to Verdaccio.
Affected dependents
No dependents in this batch (verified by bunx turbo ls --filter=...@suaveplan/audio-controllers). @suaveplan/audio-controllers is a leaf integration package — it is consumed directly by application code (@suaveplan/react-audio*, game audio surfaces), none of which exist on genesis in this 13-change audio batch. Each future consumer is created against this package's spec delta, not retrofitted.
Once landed, any subsequent change to this package's public surface MUST run bunx turbo test --filter=...@suaveplan/audio-controllers per openspec/AGENTS.md §13 step 10.