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

209 lines
8.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```ts
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
1. **Universal sequencer base** — `Sequencer` uses `@suaveplan/audio-transport` for sample-accurate scheduling, not `setInterval`.
2. **Transport-synced arpeggiator** — arpeggio patterns are `Transport` event subscribers, renderable offline.
3. **Hot-plug support** — `onStateChange` handles connect/disconnect with graceful state recovery.
4. **CC mapping DSL** — declarative control-change to parameter mapping with learn mode.
5. **LED feedback** — bidirectional MIDI for controller LED/state updates.
6. **OTel instrumentation** — spans for MIDI message handling, sequencer step timing.
7. **Property-based tests** — random step patterns → correct sequence, random CC values → mapped parameters.
8. **Benchmark suite** — MIDI message throughput, sequencer scheduling latency.
---
## File layout
```text
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.