Initial Commit
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
# Superseded — not folded into `openspec/specs/`
|
||||
|
||||
**Date superseded**: 2026-05-15
|
||||
**Superseded by**: `openspec/changes/enterprise-audio-controllers/`
|
||||
|
||||
## Why
|
||||
|
||||
This proposal targeted MIDI hardware controllers built on the now-superseded monolithic `@suaveplan/audio-engine`. It uses the pre-tier `browser/audio/audio-controllers` target path and the obsolete `AudioPlugin` / `AudioGraph` / `PluginRegistry` API surface.
|
||||
|
||||
The replacement (`enterprise-audio-controllers`) consumes `@suaveplan/audio-core` and `@suaveplan/audio-transport` directly so that controller patterns can drive offline renders and server-side timelines as well as Web MIDI in the browser.
|
||||
|
||||
No code was implemented for this proposal.
|
||||
@@ -0,0 +1,206 @@
|
||||
# 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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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`, `MIDIPermissionDenied` typed errors)
|
||||
- Hot-plug: `navigator.requestMIDIAccess().onstatechange` listener; auto-rebind on reconnect; `device:connected` / `device:disconnected` events
|
||||
- LED diff/flush engine with per-frame byte budget
|
||||
- Declarative CC-mapping DSL: `cc(0x10).controls('encoder[0]')` with static type checking
|
||||
- `@suaveplan/events` `EventEmitter` — all three controllers use `EventEmitter<*Events>` instead of `EventTarget`
|
||||
|
||||
### 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
|
||||
|
||||
```text
|
||||
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.
|
||||
@@ -0,0 +1,138 @@
|
||||
# Tasks: enterprise-browser-audio-controllers
|
||||
|
||||
**Change ID**: enterprise-browser-audio-controllers
|
||||
**Package**: `@suaveplan/audio-controllers`
|
||||
**Target path**: `browser/audio/audio-controllers`
|
||||
**Prerequisite packages on Verdaccio**: `@suaveplan/audio-engine`, `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`
|
||||
|
||||
---
|
||||
|
||||
## 1. Scaffold
|
||||
|
||||
- [ ] Create `browser/audio/audio-controllers/` directory tree per layout in `proposal.md`.
|
||||
- [ ] Write `package.json` at version `0.1.0`:
|
||||
- `"sideEffects": false`
|
||||
- `exports`: top-level `"."` plus subpaths for each controller and sequencer
|
||||
- `dependencies`: `@suaveplan/audio-engine workspace:^`, `@suaveplan/error workspace:^`, `@suaveplan/types workspace:^`, `@suaveplan/events workspace:^`, `standardized-audio-context catalog:browser`, `zod catalog:forms`, `@opentelemetry/api catalog:observability`, `tslib catalog:build`
|
||||
- `devDependencies`: `vitest catalog:testing`, `@vitest/coverage-istanbul catalog:testing`, `@suaveplan/testing workspace:^`, `@playwright/test catalog:testing`
|
||||
- [ ] Write `tsconfig.json` and `vitest.config.ts` (pool: forks).
|
||||
|
||||
---
|
||||
|
||||
## 2. Error codes (`src/error-codes.ts`)
|
||||
|
||||
- [ ] Define `MidiControllerErrorCode`: `MIDI_UNAVAILABLE`, `MIDI_PERMISSION_DENIED`, `MIDI_DEVICE_DISCONNECTED`, `MIDI_SYSEX_REJECTED`, `CONTROLLER_PRESET_INVALID`, `SAMPLE_LOADER_FAILED`.
|
||||
- [ ] Export `registerMidiControllerErrorCodes()` and call eagerly.
|
||||
|
||||
---
|
||||
|
||||
## 3. Base class (`src/base/midi-controller-plugin/`)
|
||||
|
||||
- [ ] Implement abstract `MidiControllerPlugin extends AudioPlugin`:
|
||||
- `requestMIDIAccess(sysex?: boolean)`: calls `navigator.requestMIDIAccess`; throws typed `MIDI_UNAVAILABLE` / `MIDI_PERMISSION_DENIED` on failure.
|
||||
- `onStateChange(event: MIDIConnectionEvent)`: detects disconnect / reconnect, emits typed events, auto-rebinds.
|
||||
- `flushLEDs(updates: LEDUpdate[])`: diff engine with per-frame byte budget.
|
||||
- `defineMappings(mappings: CCMapping[])`: CC-learn DSL storage.
|
||||
- Protected `EventEmitter<MidiControllerEventMap>` member — all subclasses use it.
|
||||
- [ ] Write `midi-controller-plugin.test.ts`: permission flow, hot-plug disconnect + reconnect, LED flush budget.
|
||||
- [ ] Write `midi-controller-plugin.md`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Shared sequencer (`src/sequencer/sequencer/`)
|
||||
|
||||
- [ ] Implement `Sequencer`: audio-clock-synced step grid, tempo (BPM), step on/off, playback start/stop/reset.
|
||||
- [ ] Clock source: inject via constructor (accepts `AudioContext` for `currentTime`-based scheduling, not `setInterval`).
|
||||
- [ ] Write `sequencer.test.ts`, `sequencer.md`.
|
||||
|
||||
---
|
||||
|
||||
## 5. KontrolS88Plugin (`src/plugins/kontrol-s88/`)
|
||||
|
||||
- [ ] `mapping.ts`: CC/note-number → parameter map, type-safe DSL.
|
||||
- [ ] `state.ts`: `KeyState[]`, `TransportState` machine.
|
||||
- [ ] `arpeggio.ts`: `ArpMode` implementations (up/down/random/chord), clock-synced via shared `Sequencer`.
|
||||
- [ ] `scale.ts`: `ScaleMode` quantizer (major, minor, chromatic, etc.), chord voicing.
|
||||
- [ ] `led.ts`: per-key LED feedback, batch flush.
|
||||
- [ ] `preset.ts`: `KontrolS88Preset` Zod schema, serialize/deserialize.
|
||||
- [ ] `kontrol-s88.ts`: plugin orchestrator, hooks into `MidiControllerPlugin` base.
|
||||
- [ ] `kontrol-s88.test.ts`: MIDI byte → state, state → MIDI out, velocity curve, scale transform, arpeggio clock, preset round-trip, hot-plug disconnect + reconnect.
|
||||
- [ ] `kontrol-s88.md`.
|
||||
|
||||
---
|
||||
|
||||
## 6. MaschinePlugin (`src/plugins/maschine/`)
|
||||
|
||||
- [ ] `mapping.ts`: pad/CC/button mapping.
|
||||
- [ ] `state.ts`: pad grid state, group states.
|
||||
- [ ] `sequencer.ts`: 16-step sequencer per pad using shared `Sequencer`.
|
||||
- [ ] `sample-loader.ts`: real `SampleLoader` interface — no placeholder; loads via `AudioContext.decodeAudioData`.
|
||||
- [ ] `preset.ts`: `MaschinePreset` Zod schema.
|
||||
- [ ] `maschine.ts`: plugin orchestrator. Event system wired via `EventEmitter` — no placeholder comments.
|
||||
- [ ] `maschine.test.ts`: pad velocity, step grid, sample load, preset round-trip.
|
||||
- [ ] `maschine.md`.
|
||||
|
||||
---
|
||||
|
||||
## 7. TraktorS4Plugin (`src/plugins/traktor-s4/`)
|
||||
|
||||
- [ ] `mapping.ts`: CC/button mapping per channel.
|
||||
- [ ] `deck.ts`: `DeckState`, load/play/pause/cue/sync, pitch/tempo.
|
||||
- [ ] `mixer.ts`: channel volumes, EQ per channel, master gain.
|
||||
- [ ] `led.ts`: LED ring/button feedback.
|
||||
- [ ] `preset.ts`: `TraktorS4Preset` Zod schema.
|
||||
- [ ] `traktor-s4.ts`: plugin orchestrator. Event system wired — no placeholder comments.
|
||||
- [ ] `traktor-s4.test.ts`: crossfader, deck sync, mixer state, preset round-trip.
|
||||
- [ ] `traktor-s4.md`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Learn mode (`src/learn/learn-mode/`)
|
||||
|
||||
- [ ] Implement `LearnMode`: listens for MIDI CC events, stores custom remapping, persists via injected `StorageAdapter`.
|
||||
- [ ] Default adapter: in-memory; inject `@suaveplan/storage-browser` for durable persistence.
|
||||
- [ ] Write `learn-mode.test.ts`, `learn-mode.md`.
|
||||
|
||||
---
|
||||
|
||||
## 9. SysEx fuzz test (`tests/integration.test.ts`)
|
||||
|
||||
- [ ] Write a property-based test (fast-check) that feeds malformed SysEx byte arrays to `onMIDIMessage` on each controller and verifies the plugin does not crash and emits a typed `MIDI_SYSEX_REJECTED` event or safely ignores.
|
||||
|
||||
---
|
||||
|
||||
## 10. Playwright E2E (`tests/e2e/`)
|
||||
|
||||
- [ ] Write E2E spec using a virtual MIDI device (Web MIDI API mock via Playwright page context) for each controller:
|
||||
- KontrolS88: MIDI note-on → `KeyState` update, arpeggio output, LED feedback.
|
||||
- Maschine: pad velocity → step sequencer trigger.
|
||||
- TraktorS4: crossfader CC → mixer state.
|
||||
- [ ] Verify hot-plug: simulate device disconnect + reconnect event.
|
||||
|
||||
---
|
||||
|
||||
## 11. OTel metrics
|
||||
|
||||
- [ ] Wire in `MidiControllerPlugin` base:
|
||||
- Counter `midi.messages_in_total{controller}`, `midi.messages_out_total{controller}`
|
||||
- Counter `led.frames_total{controller}`
|
||||
- Histogram `midi.input_to_audio_latency_ms{controller}`
|
||||
|
||||
---
|
||||
|
||||
## 12. Docs
|
||||
|
||||
- [ ] Write `.md` for every exported module (≥200 words, API reference, examples).
|
||||
- [ ] Write `README.md` with controller matrix table (every supported CC/note/pad per device), preset-pack list, MIDI permission guidance.
|
||||
|
||||
---
|
||||
|
||||
## 13. Verify and publish
|
||||
|
||||
- [ ] `bunx turbo lint typecheck test --filter=@suaveplan/audio-controllers` — green.
|
||||
- [ ] `bunx turbo test:coverage --filter=@suaveplan/audio-controllers` — 100%.
|
||||
- [ ] `bun run docs:check --filter=@suaveplan/audio-controllers` — zero errors.
|
||||
- [ ] No `TODO`, placeholder, `any`, `ts-ignore`, `vi.mock`, `.skip` in `src/`.
|
||||
- [ ] Playwright E2E green for all three controllers.
|
||||
- [ ] `bun scripts/publish.ts --package @suaveplan/audio-controllers`.
|
||||
- [ ] Archive to `openspec/archive/2026-04-24-enterprise-browser-audio-controllers/`.
|
||||
Reference in New Issue
Block a user