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-engine-browser/` (browser-tier `TimelineAdapter` implementation)
|
||||
- `openspec/changes/enterprise-audio-engine-server/` (server-tier `TimelineAdapter` implementation)
|
||||
|
||||
## Why
|
||||
|
||||
This is the older flat-named twin of `enterprise-audio-engine` (both targeted the package `@suaveplan/audio-engine`). It uses the pre-tier `browser/audio/audio-engine` target path that predates the `packages/<plugin>/<pkg>` convention. It also predates the `audio-core` / `audio-timeline` / `audio-transport` split. See the SUPERSEDED.md in `2026-05-15-superseded-enterprise-audio-engine/` for the full rationale — both proposals were displaced by the same architecture change.
|
||||
|
||||
No code was implemented for this proposal.
|
||||
@@ -0,0 +1,335 @@
|
||||
# OpenSpec Change: enterprise-browser-audio-engine
|
||||
|
||||
- **Change ID**: enterprise-browser-audio-engine
|
||||
- **Package**: `@suaveplan/audio-engine`
|
||||
- **Category**: browser
|
||||
- **Target path**: `browser/audio/audio-engine`
|
||||
- **Status**: IN PROGRESS
|
||||
- **Date**: 2026-04-24
|
||||
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/audio-engine.md`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Deliver `@suaveplan/audio-engine` on the genesis branch from scratch at enterprise quality. The package is the core browser-side Web Audio engine consumed by every React and R3F audio surface in the monorepo. It owns the global `AudioContext` lifecycle, a typed plugin system (`AudioPlugin` base class, `PluginRegistry`, `AudioEngine`), an `AudioGraph` node-routing layer, an `AudioWorkletManager` for loading and messaging worklet modules, an `AudioQueueEngine` for cue/sequence playback, and an `EventBus` facade. Zero React dependencies — consumed by `@suaveplan/audio-controllers`, `@suaveplan/audio-plugins-fx`, and React audio packages.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
---
|
||||
|
||||
## Available workspace deps on genesis
|
||||
|
||||
| Package | Role |
|
||||
|---|---|
|
||||
| `@suaveplan/error` (`workspace:^`) | Base class for `AudioEngineError`; `defineCodes` for `AUDIO.*` error codes |
|
||||
| `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `EventBusLike`, `Clock` for injectable clock support |
|
||||
| `@suaveplan/events` (`workspace:^`) | `EventEmitter` for `AudioEngineEventMap`; typed event bus |
|
||||
| `@suaveplan/schema-registry` (`workspace:^`) | Register `AudioPluginDescriptor` Zod schema |
|
||||
| `@suaveplan/api-schemas` (`workspace:^`) | Shared schema registration surface |
|
||||
| `zod` (`catalog:forms`) | Zod schemas for `PluginMetadata`, `AudioPluginDescriptor`, configs |
|
||||
| `@opentelemetry/api` (`catalog:observability`) | Counters + histograms for plugin lifecycle and latency |
|
||||
| `standardized-audio-context` (`catalog:browser`) | Cross-browser `AudioContext` + `OfflineAudioContext` shim |
|
||||
| `tslib` (`catalog:build`) | Runtime TS helpers |
|
||||
|
||||
Dev deps: `vitest`, `@vitest/coverage-istanbul`, `mitata` (all `catalog:testing`).
|
||||
|
||||
---
|
||||
|
||||
## Preserved public API surface (from main branch)
|
||||
|
||||
### Core
|
||||
|
||||
```ts
|
||||
class AudioEngine {
|
||||
constructor(config?: AudioEngineConfig, context?: BaseAudioContext)
|
||||
start(): Promise<void>
|
||||
stop(): Promise<void>
|
||||
getState(): AudioEngineState
|
||||
getStateInfo(): AudioEngineStateInfo
|
||||
getMetrics(): AudioEngineMetrics
|
||||
loadPlugin(id: string, plugin: AudioPlugin): Promise<void>
|
||||
unloadPlugin(id: string): Promise<void>
|
||||
getPlugin<T extends AudioPlugin>(id: string): T | undefined
|
||||
on<K extends keyof AudioEngineEventMap>(event: K, listener: AudioEngineEventMap[K]): void
|
||||
off<K extends keyof AudioEngineEventMap>(event: K, listener: AudioEngineEventMap[K]): void
|
||||
}
|
||||
|
||||
interface AudioEngineConfig { sampleRate?: number; latencyHint?: AudioContextLatencyCategory; bufferSize?: number; maxPlugins?: number }
|
||||
type AudioEngineState = 'uninitialized' | 'starting' | 'running' | 'suspended' | 'stopping' | 'stopped' | 'error'
|
||||
interface AudioEngineStateInfo { state: AudioEngineState; previousState: AudioEngineState; timestamp: number }
|
||||
interface AudioEngineMetrics { pluginCount: number; activeNodes: number; cpuUsage: number; sampleRate: number; currentTime: number }
|
||||
type AudioEngineEventMap = { 'state:change': (info: AudioEngineStateInfo) => void; 'plugin:loaded': (id: string) => void; 'plugin:unloaded': (id: string) => void; 'plugin:error': (id: string, error: Error) => void; 'error': (error: Error) => void }
|
||||
```
|
||||
|
||||
### AudioGraph
|
||||
|
||||
```ts
|
||||
class AudioGraph {
|
||||
addNode(id: string, node: AudioNode): void
|
||||
removeNode(id: string): void
|
||||
connect(sourceId: string, destinationId: string, outputIndex?: number, inputIndex?: number): void
|
||||
disconnect(sourceId: string, destinationId?: string): void
|
||||
getNode(id: string): AudioNode | undefined
|
||||
traverse(startId: string): string[]
|
||||
snapshot(): AudioGraphSnapshot
|
||||
restore(snapshot: AudioGraphSnapshot): void
|
||||
}
|
||||
```
|
||||
|
||||
### AudioQueueEngine
|
||||
|
||||
```ts
|
||||
class AudioQueueEngine {
|
||||
enqueue(cue: AudioCue): string
|
||||
dequeue(cueId: string): void
|
||||
play(cueId: string): Promise<void>
|
||||
stop(cueId: string): void
|
||||
getQueue(): AudioCue[]
|
||||
getSequences(): AudioSequence[]
|
||||
addSequence(seq: AudioSequence): void
|
||||
removeSequence(seqId: string): void
|
||||
}
|
||||
|
||||
interface AudioCue { id?: string; buffer: AudioBuffer; startTime?: number; offset?: number; duration?: number; gain?: number; loop?: boolean }
|
||||
interface AudioSequence { id: string; cues: AudioCue[]; loop?: boolean; tempo?: number }
|
||||
```
|
||||
|
||||
### AudioWorkletManager
|
||||
|
||||
```ts
|
||||
class AudioWorkletManager {
|
||||
addModule(url: string, options?: WorkletModuleOptions): Promise<void>
|
||||
createNode(processorName: string, options?: AudioWorkletNodeOptions): AudioWorkletNode
|
||||
postMessage(processorName: string, message: unknown, transfer?: Transferable[]): void
|
||||
onMessage(processorName: string, handler: (event: MessageEvent) => void): void
|
||||
isLoaded(url: string): boolean
|
||||
}
|
||||
|
||||
interface WorkletModuleOptions { allowedUrls?: string[]; credentials?: RequestCredentials }
|
||||
```
|
||||
|
||||
### Plugin system
|
||||
|
||||
```ts
|
||||
abstract class AudioPlugin {
|
||||
abstract readonly metadata: PluginMetadata
|
||||
state: PluginState
|
||||
abstract initialize(context: BaseAudioContext): Promise<void>
|
||||
abstract connect(graph: AudioGraph): void
|
||||
abstract disconnect(): void
|
||||
abstract destroy(): Promise<void>
|
||||
setParameter(id: string, value: number): void
|
||||
getParameter(id: string): number | undefined
|
||||
getParameters(): PluginParameter[]
|
||||
}
|
||||
|
||||
type PluginState = 'uninitialized' | 'initializing' | 'initialized' | 'active' | 'inactive' | 'error' | 'destroyed'
|
||||
interface PluginMetadata { id: string; name: string; version: string; category: string; description?: string }
|
||||
interface PluginParameter { id: string; name: string; value: number; min: number; max: number; default: number; automatable?: boolean }
|
||||
|
||||
class PluginRegistry {
|
||||
register(descriptor: AudioPluginDescriptor): void
|
||||
unregister(id: string): void
|
||||
get(id: string): AudioPluginDescriptor | undefined
|
||||
list(): AudioPluginDescriptor[]
|
||||
instantiate(id: string): AudioPlugin
|
||||
}
|
||||
|
||||
interface AudioPluginDescriptor { id: string; name: string; version: string; factory: () => AudioPlugin; metadata: PluginMetadata }
|
||||
```
|
||||
|
||||
### EventBus facade
|
||||
|
||||
```ts
|
||||
class EventBus {
|
||||
constructor(bus?: EventBusLike)
|
||||
emit<K extends keyof AudioEngineEventMap>(event: K, ...args: Parameters<AudioEngineEventMap[K]>): void
|
||||
on<K extends keyof AudioEngineEventMap>(event: K, listener: AudioEngineEventMap[K]): void
|
||||
off<K extends keyof AudioEngineEventMap>(event: K, listener: AudioEngineEventMap[K]): void
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## New additions for genesis
|
||||
|
||||
### 1. Structured errors via `@suaveplan/error`
|
||||
|
||||
Error codes with stable `AUDIO.*` namespace:
|
||||
|
||||
| Code | Severity | Retryable |
|
||||
|---|---|---|
|
||||
| `AUDIO_ENGINE_STATE_INVALID` | `error` | `false` |
|
||||
| `AUDIO_PLUGIN_NOT_FOUND` | `error` | `false` |
|
||||
| `AUDIO_PLUGIN_LOAD_FAILED` | `error` | `true` |
|
||||
| `AUDIO_WORKLET_LOAD_FAILED` | `error` | `true` |
|
||||
| `AUDIO_WORKLET_URL_REJECTED` | `error` | `false` |
|
||||
| `AUDIO_GRAPH_CONNECT_FAILED` | `error` | `false` |
|
||||
| `AUDIO_CONTEXT_UNAVAILABLE` | `fatal` | `false` |
|
||||
|
||||
### 2. AudioContext injection seam
|
||||
|
||||
`AudioEngine` constructor accepts an optional pre-built `BaseAudioContext` so tests and hosts can inject a `standardized-audio-context` context or a shared singleton without touching `globalThis`.
|
||||
|
||||
### 3. Plugin quarantine / error isolation
|
||||
|
||||
A plugin that throws during `process` or `initialize` is quarantined: marked `ERROR` state, disconnected from the graph, and a `plugin:error` event is emitted. The engine and other plugins continue operating.
|
||||
|
||||
### 4. Typed `AudioEngineEventMap`
|
||||
|
||||
Replace string-enum `AudioEngineEvents` with `AudioEngineEventMap` so `EventEmitter<AudioEngineEventMap>` provides per-event argument typing.
|
||||
|
||||
### 5. Graph snapshot / restore
|
||||
|
||||
`AudioGraph.snapshot()` serializes the routing topology (node IDs and connections) to a plain JSON object; `restore()` re-wires from that snapshot.
|
||||
|
||||
### 6. Worklet URL allow-list
|
||||
|
||||
`AudioWorkletManager` rejects `addModule` calls for URLs not present in the engine's configured `allowedWorkletUrls` list; throws `AUDIO_WORKLET_URL_REJECTED`.
|
||||
|
||||
### 7. OTel metrics
|
||||
|
||||
- Counter `audio.plugin_loaded_total`
|
||||
- Counter `audio.plugin_failed_total`
|
||||
- Histogram `audio.engine_latency_ms`
|
||||
- Histogram `audio.worklet_roundtrip_ms`
|
||||
- Histogram `audio.graph_connect_ms`
|
||||
|
||||
### 8. Render-on-demand offline mode
|
||||
|
||||
`AudioEngine.renderOffline(durationSeconds, sampleRate)` drives an `OfflineAudioContext` and returns an `AudioBuffer` for export / bounce workflows.
|
||||
|
||||
### 9. Parameter automation
|
||||
|
||||
`AudioPlugin.setParameterCurve(id, curve: ParameterCurve)` schedules sample-accurate linear/exponential/target-at-time automation via the Web Audio API param scheduling methods.
|
||||
|
||||
### 10. Benchmarks
|
||||
|
||||
`src/__benchmarks__/graph.bench.ts`, `queue.bench.ts`, `worklet.bench.ts` with mitata.
|
||||
|
||||
---
|
||||
|
||||
## File layout
|
||||
|
||||
```text
|
||||
browser/audio/audio-engine/
|
||||
package.json
|
||||
tsconfig.json
|
||||
vitest.config.ts
|
||||
src/
|
||||
index.ts
|
||||
error-codes.ts
|
||||
constants/
|
||||
index.ts
|
||||
types/
|
||||
common.ts
|
||||
common.md
|
||||
engine/
|
||||
engine/
|
||||
engine.ts
|
||||
engine.md
|
||||
engine.types.ts
|
||||
engine.test.ts
|
||||
index.ts
|
||||
engine-state/
|
||||
engine-state.ts
|
||||
engine-state.md
|
||||
engine-state.test.ts
|
||||
index.ts
|
||||
engine-metrics/
|
||||
engine-metrics.ts
|
||||
engine-metrics.md
|
||||
engine-metrics.test.ts
|
||||
index.ts
|
||||
graph/
|
||||
graph/
|
||||
graph.ts
|
||||
graph.md
|
||||
graph.types.ts
|
||||
graph.test.ts
|
||||
index.ts
|
||||
graph-connect/
|
||||
graph-connect.ts
|
||||
graph-connect.md
|
||||
graph-connect.test.ts
|
||||
index.ts
|
||||
graph-traverse/
|
||||
graph-traverse.ts
|
||||
graph-traverse.md
|
||||
graph-traverse.test.ts
|
||||
index.ts
|
||||
queue-engine/
|
||||
queue-engine/
|
||||
queue-engine.ts
|
||||
queue-engine.md
|
||||
queue-engine.types.ts
|
||||
queue-engine.test.ts
|
||||
index.ts
|
||||
cue/
|
||||
cue.ts
|
||||
cue.md
|
||||
cue.test.ts
|
||||
index.ts
|
||||
scheduler/
|
||||
scheduler.ts
|
||||
scheduler.md
|
||||
scheduler.test.ts
|
||||
index.ts
|
||||
worklet/
|
||||
worklet-loader/
|
||||
worklet-loader.ts
|
||||
worklet-loader.md
|
||||
worklet-loader.test.ts
|
||||
index.ts
|
||||
worklet-messaging/
|
||||
worklet-messaging.ts
|
||||
worklet-messaging.md
|
||||
worklet-messaging.test.ts
|
||||
index.ts
|
||||
worklet-registry/
|
||||
worklet-registry.ts
|
||||
worklet-registry.md
|
||||
worklet-registry.test.ts
|
||||
index.ts
|
||||
plugin/
|
||||
plugin/
|
||||
plugin.ts
|
||||
plugin.md
|
||||
plugin.types.ts
|
||||
plugin.test.ts
|
||||
index.ts
|
||||
plugin-registry/
|
||||
plugin-registry.ts
|
||||
plugin-registry.md
|
||||
plugin-registry.types.ts
|
||||
plugin-registry.test.ts
|
||||
index.ts
|
||||
event-bus/
|
||||
event-bus/
|
||||
event-bus.ts
|
||||
event-bus.md
|
||||
event-bus.types.ts
|
||||
event-bus.test.ts
|
||||
index.ts
|
||||
__benchmarks__/
|
||||
graph.bench.ts
|
||||
queue.bench.ts
|
||||
worklet.bench.ts
|
||||
tests/
|
||||
integration.test.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Impact
|
||||
|
||||
**Downstream packages that will consume `@suaveplan/audio-engine`**: `@suaveplan/audio-plugins-fx`, `@suaveplan/audio-controllers`, `@suaveplan/react-audio`, `@suaveplan/react-audio-player`, `@suaveplan/r3f-audio`, `@suaveplan/react-audio-stream`.
|
||||
|
||||
**Breaking changes from main-branch API**: none. All main-branch call sites compile unchanged. New optional fields and methods are additive.
|
||||
|
||||
**Workspace dep prerequisites**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events` must be published to Verdaccio before this package can be built or tested.
|
||||
@@ -0,0 +1,182 @@
|
||||
# Tasks: enterprise-browser-audio-engine
|
||||
|
||||
**Change ID**: enterprise-browser-audio-engine
|
||||
**Package**: `@suaveplan/audio-engine`
|
||||
**Target path**: `browser/audio/audio-engine`
|
||||
**Prerequisite packages on Verdaccio**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`
|
||||
|
||||
---
|
||||
|
||||
## 1. Scaffold
|
||||
|
||||
- [ ] Create `browser/audio/audio-engine/` with `src/`, `tests/`, `src/__benchmarks__/` and all feature subdirectories per the layout in `proposal.md`.
|
||||
- [ ] Write `package.json` at version `0.1.0`:
|
||||
- `"type": "module"`, `"sideEffects": false`
|
||||
- `exports` map with top-level `"."` plus subpath per feature module
|
||||
- `dependencies`: `@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`, `mitata catalog:testing`, `@suaveplan/testing workspace:^`
|
||||
- [ ] Write `tsconfig.json` extending `@suaveplan/typescript-config/library-browser.json`.
|
||||
- [ ] Write `vitest.config.ts` using `createTestConfig()` from `@suaveplan/testing/config` with `pool: 'forks'`.
|
||||
- [ ] Add `lint`, `typecheck`, `test`, `test:coverage`, `build`, `docs:check`, `bench` scripts.
|
||||
|
||||
---
|
||||
|
||||
## 2. Error codes (`src/error-codes.ts`)
|
||||
|
||||
- [ ] Define `AudioEngineErrorCode` union: `AUDIO_ENGINE_STATE_INVALID`, `AUDIO_PLUGIN_NOT_FOUND`, `AUDIO_PLUGIN_LOAD_FAILED`, `AUDIO_WORKLET_LOAD_FAILED`, `AUDIO_WORKLET_URL_REJECTED`, `AUDIO_GRAPH_CONNECT_FAILED`, `AUDIO_CONTEXT_UNAVAILABLE`.
|
||||
- [ ] Define `AUDIO_ENGINE_ERROR_METADATA` with severity, retryable, httpStatus, userMessageKey, category, description per code.
|
||||
- [ ] Export `registerAudioEngineErrorCodes()` calling `ErrorCodes.registerMany(...)`. Call it eagerly at module scope.
|
||||
- [ ] Re-export `AudioEngineError extends SuaveplanError` (not a local subclass).
|
||||
|
||||
---
|
||||
|
||||
## 3. Types (`src/types/common.ts` + `common.md`)
|
||||
|
||||
- [ ] Define all shared interfaces: `AudioEngineConfig`, `AudioEngineState`, `AudioEngineStateInfo`, `AudioEngineMetrics`, `AudioEngineEventMap`, `AudioGraphSnapshot`, `ParameterCurve`, `PerformanceMetrics`.
|
||||
- [ ] Write `common.md` (≥200 words, simple + advanced examples, API reference).
|
||||
|
||||
---
|
||||
|
||||
## 4. Plugin system (`src/plugin/`)
|
||||
|
||||
### 4a. `plugin/plugin.ts`
|
||||
|
||||
- [ ] Implement abstract `AudioPlugin` class with full lifecycle: state machine `uninitialized → initializing → initialized → active ↔ inactive → destroyed`, quarantine on error.
|
||||
- [ ] Implement parameter get/set with automatable support.
|
||||
- [ ] Write `plugin.test.ts`: state transitions, quarantine on thrown init, parameter CRUD.
|
||||
- [ ] Write `plugin.md`, `plugin.types.ts`.
|
||||
|
||||
### 4b. `plugin-registry/plugin-registry.ts`
|
||||
|
||||
- [ ] Implement `PluginRegistry`: register, unregister, get, list, instantiate.
|
||||
- [ ] Add Zod schema `AudioPluginDescriptorSchema`; validate descriptor on register.
|
||||
- [ ] Write `plugin-registry.test.ts`, `plugin-registry.md`, `plugin-registry.types.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 5. EventBus (`src/event-bus/event-bus/`)
|
||||
|
||||
- [ ] Implement `EventBus` thin wrapper around `@suaveplan/events` EventEmitter with `AudioEngineEventMap` type parameter.
|
||||
- [ ] Write `event-bus.test.ts`, `event-bus.md`, `event-bus.types.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Audio graph (`src/graph/`)
|
||||
|
||||
### 6a. `graph/graph.ts`
|
||||
|
||||
- [ ] Implement `AudioGraph`: add/remove/get nodes, connect/disconnect, traverse DFS, snapshot/restore.
|
||||
- [ ] Write `graph.test.ts`, `graph.md`, `graph.types.ts`.
|
||||
|
||||
### 6b. `graph-connect/graph-connect.ts`
|
||||
|
||||
- [ ] Extract connection management logic (connect/disconnect with index params, validation, `AUDIO_GRAPH_CONNECT_FAILED`).
|
||||
- [ ] OTel histogram `audio.graph_connect_ms`.
|
||||
- [ ] Write `graph-connect.test.ts`, `graph-connect.md`.
|
||||
|
||||
### 6c. `graph-traverse/graph-traverse.ts`
|
||||
|
||||
- [ ] Extract DFS traversal with cycle detection.
|
||||
- [ ] Write `graph-traverse.test.ts`, `graph-traverse.md`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Queue engine (`src/queue-engine/`)
|
||||
|
||||
### 7a. `queue-engine/queue-engine.ts`
|
||||
|
||||
- [ ] Implement `AudioQueueEngine`: enqueue/dequeue/play/stop, manage cue and sequence collections.
|
||||
- [ ] Write `queue-engine.test.ts`, `queue-engine.md`, `queue-engine.types.ts`.
|
||||
|
||||
### 7b. `cue/cue.ts`
|
||||
|
||||
- [ ] Implement `AudioCue` scheduling logic (start time, offset, gain, loop via `AudioBufferSourceNode`).
|
||||
- [ ] Write `cue.test.ts`, `cue.md`.
|
||||
|
||||
### 7c. `scheduler/scheduler.ts`
|
||||
|
||||
- [ ] Implement sequence scheduling with tempo and loop support.
|
||||
- [ ] Write `scheduler.test.ts`, `scheduler.md`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Worklet manager (`src/worklet/`)
|
||||
|
||||
### 8a. `worklet-loader/worklet-loader.ts`
|
||||
|
||||
- [ ] Implement `addModule(url, options)` with allow-list gate — throw `AUDIO_WORKLET_URL_REJECTED` on non-allowed URLs.
|
||||
- [ ] Cache loaded module URLs; avoid double-load.
|
||||
- [ ] OTel histogram `audio.worklet_roundtrip_ms`.
|
||||
- [ ] Write `worklet-loader.test.ts`, `worklet-loader.md`.
|
||||
|
||||
### 8b. `worklet-messaging/worklet-messaging.ts`
|
||||
|
||||
- [ ] Implement `postMessage` / `onMessage` wiring per processor name.
|
||||
- [ ] Write `worklet-messaging.test.ts`, `worklet-messaging.md`.
|
||||
|
||||
### 8c. `worklet-registry/worklet-registry.ts`
|
||||
|
||||
- [ ] Track loaded processors and their associated `AudioWorkletNode` factory.
|
||||
- [ ] Write `worklet-registry.test.ts`, `worklet-registry.md`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Engine (`src/engine/`)
|
||||
|
||||
### 9a. `engine-state/engine-state.ts`
|
||||
|
||||
- [ ] Implement state machine: `uninitialized → starting → running → suspended → stopping → stopped → error`. Validate transitions; throw `AUDIO_ENGINE_STATE_INVALID` on illegal transition.
|
||||
- [ ] Write `engine-state.test.ts`, `engine-state.md`.
|
||||
|
||||
### 9b. `engine-metrics/engine-metrics.ts`
|
||||
|
||||
- [ ] Collect CPU load estimate via `BaseAudioContext.baseLatency` + analyzer node.
|
||||
- [ ] Wire OTel counters `audio.plugin_loaded_total`, `audio.plugin_failed_total`; histograms `audio.engine_latency_ms`.
|
||||
- [ ] Write `engine-metrics.test.ts`, `engine-metrics.md`.
|
||||
|
||||
### 9c. `engine/engine.ts`
|
||||
|
||||
- [ ] Implement `AudioEngine`: constructor with injected `BaseAudioContext`, plugin map, load/unload with quarantine, start/stop lifecycle, offline render, expose `AudioGraph`, `AudioQueueEngine`, `AudioWorkletManager` as members.
|
||||
- [ ] Wire quarantine: catch plugin errors, mark `ERROR`, disconnect, emit `plugin:error`, continue.
|
||||
- [ ] Write `engine.test.ts` (no globalThis touch — use injected context), `engine.md`, `engine.types.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 10. Integration tests (`tests/integration.test.ts`)
|
||||
|
||||
- [ ] Full `AudioEngine` lifecycle: start → load plugin → active → unload → stop using standardized-audio-context.
|
||||
- [ ] Graph: connect/disconnect, snapshot/restore, traverse.
|
||||
- [ ] Queue: enqueue → play sequence → loop → stop.
|
||||
- [ ] Worklet: addModule with allow-list passes; rejected URL throws typed error.
|
||||
- [ ] Quarantine: a plugin that throws in `initialize` does not bring down the engine.
|
||||
- [ ] Offline render: `renderOffline()` returns non-empty `AudioBuffer`.
|
||||
|
||||
---
|
||||
|
||||
## 11. Benchmarks (`src/__benchmarks__/`)
|
||||
|
||||
- [ ] `graph.bench.ts`: connect/disconnect 100 nodes, traverse 50-node graph.
|
||||
- [ ] `queue.bench.ts`: enqueue 1000 cues, dequeue all.
|
||||
- [ ] `worklet.bench.ts`: postMessage round-trip latency.
|
||||
|
||||
---
|
||||
|
||||
## 12. Docs
|
||||
|
||||
- [ ] Write `.md` for every exported class/type (see layout in `proposal.md`).
|
||||
- [ ] Write `browser/audio/audio-engine/README.md` with purpose, install, quick-start, subpath table, benchmarks table, dependencies table.
|
||||
|
||||
---
|
||||
|
||||
## 13. Verify and publish
|
||||
|
||||
- [ ] `bunx turbo lint --filter=@suaveplan/audio-engine` — zero errors.
|
||||
- [ ] `bunx turbo typecheck --filter=@suaveplan/audio-engine` — zero errors.
|
||||
- [ ] `bunx turbo test --filter=@suaveplan/audio-engine` — all tests pass.
|
||||
- [ ] `bunx turbo test:coverage --filter=@suaveplan/audio-engine` — 100% on all touched modules.
|
||||
- [ ] `bun run docs:check --filter=@suaveplan/audio-engine` — zero errors.
|
||||
- [ ] Confirm no `TODO`, `FIXME`, `any`, `ts-ignore`, `.skip`, `vi.mock` remain in `src/`.
|
||||
- [ ] `"sideEffects": false` set in `package.json`.
|
||||
- [ ] `bun scripts/publish.ts --package @suaveplan/audio-engine`.
|
||||
- [ ] Update `CLAUDE.md` package listing.
|
||||
- [ ] Archive: move to `openspec/archive/2026-04-24-enterprise-browser-audio-engine/` and fold spec delta into `openspec/specs/audio/spec.md`.
|
||||
Reference in New Issue
Block a user