Files
suaveplan/openspec/archive/2026-05-19-enterprise-audio-controllers/proposal.md
2026-05-27 23:29:53 +01:00

8.6 KiB
Raw Blame History

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

  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

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.