6.8 KiB
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
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
- Injectable fs adapter —
FileSourceAdapterandFileSinkAdapteraccept anFsLikeinterface for testing without touching the real filesystem. - Streaming load/save —
createReadStream/createWriteStreamreturn WHATWG streams compatible with@suaveplan/streams. - Batch helpers —
loadMany/saveManyfor bulk operations. - Server playback = render —
ServerTimelineAdapter.play()renders as fast as possible viaaudio-core.streamPlan()piped to codec encoder and file sink. - OTel instrumentation — spans for
file.load,file.save,server.render, counters for bytes read/written. - Property-based tests — load then save → file exists, round-trip preserves sample count.
- Benchmark suite — load 100MB WAV, save 100MB FLAC.
File layout
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 inenterprise-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.