Initial Commit
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user