Initial Commit
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
# OpenSpec Change: enterprise-audio-engine-server
|
||||
|
||||
- **Change ID**: enterprise-audio-engine-server
|
||||
- **Package**: `@suaveplan/audio-engine-server`
|
||||
- **Category**: server
|
||||
- **Target path**: `packages/media/audio/audio-engine-server`
|
||||
- **Status**: PROPOSED
|
||||
- **Date**: 2026-05-14
|
||||
- **Design**: `./design.md`
|
||||
- **Spec delta**: `./specs/audio-engine-server/spec.md`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Deliver `@suaveplan/audio-engine-server` on the genesis branch from scratch at enterprise quality. Server-side file I/O adapters for the genesis audio ecosystem. This package is **intentionally thin** — all DSP, planning, streaming, and timeline logic lives in universal packages (`audio-core`, `audio-timeline`, `audio-transport`). This package only provides file source adapters, file sink adapters, and a server timeline adapter that renders to file output instead of speakers.
|
||||
|
||||
Batch processing is universal (uses `@suaveplan/queue` + `@suaveplan/streams`) and is documented in the master spec as a cross-cutting pattern, not a server-specific feature.
|
||||
|
||||
---
|
||||
|
||||
## 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-engine` was browser-only and had no server equivalent.
|
||||
|
||||
---
|
||||
|
||||
## Available workspace deps on genesis
|
||||
|
||||
| Package | Role |
|
||||
|---|---|
|
||||
| `@suaveplan/audio-core` (`workspace:^`) | `AudioDocument`, `PlanEngine`, `ProcessPipeline` |
|
||||
| `@suaveplan/audio-timeline` (`workspace:^`) | `TimelineEngine`, `Track`, `Clip` |
|
||||
| `@suaveplan/audio-transport` (`workspace:^`) | `Transport` |
|
||||
| `@suaveplan/audio-codecs` (`workspace:^`) | `CodecRegistry`, `encodeTo`, `decodeSource` |
|
||||
| `@suaveplan/streams` (`workspace:^`) | `TransformStream`, `ReadableStream`, `WritableStream` primitives |
|
||||
| `@suaveplan/error` (`workspace:^`) | `SuaveplanError` + `defineCodes`; the package exports `AudioEngineServerError` (extending `AudioError` from `@suaveplan/audio-core`) carrying `AUDIO_ENGINE_SERVER.*` 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
|
||||
interface FileSourceOptions {
|
||||
fs?: FsLike // injectable fs adapter for testing
|
||||
fetch?: typeof fetch // injectable fetch for URLs
|
||||
}
|
||||
|
||||
class FileSourceAdapter {
|
||||
constructor(options?: FileSourceOptions)
|
||||
async load(path: string, opts?: LoadOptions): Promise<AudioDocument>
|
||||
createReadStream(path: string): ReadableStream<Uint8Array>
|
||||
async loadMany(paths: string[], opts?: LoadOptions): Promise<AudioDocument[]>
|
||||
}
|
||||
|
||||
interface FileSinkOptions {
|
||||
fs?: FsLike
|
||||
}
|
||||
|
||||
class FileSinkAdapter {
|
||||
constructor(options?: FileSinkOptions)
|
||||
async save(doc: AudioDocument, path: string, format: string, opts?: SaveOptions): Promise<void>
|
||||
createWriteStream(path: string): WritableStream<Uint8Array>
|
||||
async saveMany(docs: AudioDocument[], pathFormat: string): Promise<void>
|
||||
}
|
||||
|
||||
interface ServerAdapterOptions {
|
||||
outputPath?: string
|
||||
format?: string
|
||||
sampleRate?: number
|
||||
ppqn?: number
|
||||
}
|
||||
|
||||
class ServerTimelineAdapter implements TimelineAdapter {
|
||||
constructor(options?: ServerAdapterOptions)
|
||||
init(options?: { signal?: AbortSignal }): Promise<void>
|
||||
setTracks(tracks: Track[]): void
|
||||
addTrack(track: Track): void
|
||||
removeTrack(trackId: string): void
|
||||
updateTrack(trackId: string, track: Track): void
|
||||
play(time?: number): void
|
||||
pause(time?: number): void
|
||||
stop(time?: number): void
|
||||
seek(time: number): void
|
||||
getCurrentTime(): number
|
||||
setLoop(start: number, end: number, enabled: boolean): void
|
||||
setMasterVolume(volume: number): void
|
||||
setTrackVolume(trackId: string, volume: number): void
|
||||
setTrackMute(trackId: string, muted: boolean): void
|
||||
setTrackSolo(trackId: string, soloed: boolean): void
|
||||
setTrackPan(trackId: string, pan: number): void
|
||||
setTempo(bpm: number, atTick?: number): void
|
||||
ticksToSeconds(ticks: number): number
|
||||
secondsToTicks(seconds: number): number
|
||||
readonly lookAhead: 0
|
||||
readonly ppqn: number
|
||||
dispose(): void
|
||||
/** Extra non-interface method: render the timeline to a finite output, useful for non-realtime export. */
|
||||
render(options?: { startTime?: number; endTime?: number; signal?: AbortSignal }): Promise<void>
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## New additions for genesis
|
||||
|
||||
1. **Injectable fs adapter** — `FileSourceAdapter` and `FileSinkAdapter` accept an `FsLike` interface for testing without touching the real filesystem.
|
||||
2. **Streaming load/save** — `createReadStream` / `createWriteStream` return WHATWG streams compatible with `@suaveplan/streams`.
|
||||
3. **Batch helpers** — `loadMany` / `saveMany` for bulk operations.
|
||||
4. **Server playback = render** — `ServerTimelineAdapter.play()` renders as fast as possible via `audio-core.streamPlan()` piped to codec encoder and file sink.
|
||||
5. **OTel instrumentation** — spans for `file.load`, `file.save`, `server.render`, counters for bytes read/written.
|
||||
6. **Property-based tests** — load then save → file exists, round-trip preserves sample count.
|
||||
7. **Benchmark suite** — load 100MB WAV, save 100MB FLAC.
|
||||
|
||||
---
|
||||
|
||||
## File layout
|
||||
|
||||
```text
|
||||
packages/media/audio/audio-engine-server/
|
||||
package.json
|
||||
tsconfig.json
|
||||
vitest.config.ts
|
||||
src/
|
||||
index.ts
|
||||
error-codes.ts
|
||||
source/
|
||||
file-source.ts
|
||||
file-source.md
|
||||
file-source.types.ts
|
||||
file-source.test.ts
|
||||
index.ts
|
||||
sink/
|
||||
file-sink.ts
|
||||
file-sink.md
|
||||
file-sink.types.ts
|
||||
file-sink.test.ts
|
||||
index.ts
|
||||
adapter/
|
||||
server-adapter.ts
|
||||
server-adapter.md
|
||||
server-adapter.types.ts
|
||||
server-adapter.test.ts
|
||||
index.ts
|
||||
__benchmarks__/
|
||||
io.bench.ts
|
||||
tests/
|
||||
integration.test.ts
|
||||
property.test.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Impact
|
||||
|
||||
**Breaking changes**: none — fresh build, package does not yet exist on genesis.
|
||||
|
||||
**Prerequisites**: `@suaveplan/audio-core`, `@suaveplan/audio-timeline`, `@suaveplan/audio-transport`, `@suaveplan/audio-codecs`, `@suaveplan/streams`, `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/opentelemetry` published to Verdaccio.
|
||||
|
||||
**No universal package may depend on this.** It is a leaf node in the dependency graph.
|
||||
|
||||
### Affected dependents
|
||||
|
||||
`@suaveplan/audio-engine-server` is a leaf node — its only downstream consumer is the audio CLI,
|
||||
which does not exist on genesis yet (it lands in a later change in this same batch). It is created
|
||||
against this package's spec delta, not retrofitted.
|
||||
|
||||
- `@suaveplan/audio-cli` — [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: does not exist yet; created in `enterprise-audio-cli`)
|
||||
|
||||
Once landed, any subsequent change to this package's public surface MUST run
|
||||
`bunx turbo test --filter=...@suaveplan/audio-engine-server` per `openspec/AGENTS.md` §13 step 10.
|
||||
Reference in New Issue
Block a user