# OpenSpec Change: enterprise-audio-transport - **Change ID**: enterprise-audio-transport - **Package**: `@suaveplan/audio-transport` - **Category**: universal - **Target path**: `packages/audio/audio-transport` - **Status**: PROPOSED - **Date**: 2026-05-14 - **Design**: `./design.md` - **Spec delta**: `./specs/audio-transport/spec.md` --- ## Summary Deliver `@suaveplan/audio-transport` on the genesis branch from scratch at enterprise quality. The package is a musical clock and transport providing sample-accurate scheduling with PPQ (pulses per quarter), BPM automation, swing, time signatures, and loop regions. It is built on `@suaveplan/timing` for the lookahead scheduler and `@suaveplan/fsm` for playback state. Inspired by Tone.js `Transport`, but universal and injectable. --- ## 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/timing` (`workspace:^`) | `createScheduler`, `Signal`, `FakeClock` for lookahead and tempo curves | | `@suaveplan/events` (`workspace:^`) | Typed event emitter for `TransportEventMap` | | `@suaveplan/fsm` (`workspace:^`) | Playback state machine (`stopped` → `playing` → `paused`) | | `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `Clock` | | `@suaveplan/error` (`workspace:^`) | Base class for `TransportError`; `AUDIO_TRANSPORT.*` codes | | `zod` (`catalog:forms`) | Zod schemas for all public option types | Dev deps: `vitest`, `@vitest/coverage-istanbul`, `fast-check` (all `catalog:testing`). --- ## Public API ### Core types ```ts interface TransportOptions { bpm?: number ppq?: number swing?: number swingSubdivision?: Subdivision timeSignature?: number loop?: boolean loopStart?: TransportTime loopEnd?: TransportTime logger?: LoggerLike } type TransportTime = string | number | Ticks type Ticks = number type BarsBeatsSixteenths = string type BPM = number type Subdivision = '4n' | '8n' | '16n' | '8t' | '16t' type TransportCallback = (time: number) => void interface TransportEventMap { start: (time: number) => void stop: (time: number) => void pause: (time: number) => void loop: (time: number) => void bpm: (bpm: number, time: number) => void } class Transport { constructor(options?: TransportOptions) get state(): 'stopped' | 'playing' | 'paused' get position(): BarsBeatsSixteenths get seconds(): number get ticks(): number start(time?: number, offset?: Ticks): void stop(time?: number): void pause(time?: number): void schedule(callback: TransportCallback, time: TransportTime): number scheduleRepeat(callback: TransportCallback, interval: TransportTime, start?: TransportTime): number scheduleOnce(callback: TransportCallback, time: TransportTime): number cancel(eventId: number): void cancelAfter(time: number): void bpm: Signal timeSignature: number ppq: number loop: boolean loopStart: TransportTime loopEnd: TransportTime swing: number swingSubdivision: Subdivision toSeconds(time: TransportTime): number toTicks(time: TransportTime): Ticks toBarsBeatsSixteenths(ticks: Ticks): BarsBeatsSixteenths on(event: K, listener: TransportEventMap[K]): void off(event: K, listener: TransportEventMap[K]): void dispose(): void } class TransportScheduler { constructor(transport: Transport, clock: Clock) start(): void stop(): void } class TickSource { frequency: Signal<'bpm'> getTicksAtTime(time: number): Ticks getTimeOfTick(tick: Ticks, before?: number): number setTicksAtTime(ticks: Ticks, time: number): void start(time: number, offset?: Ticks): void stop(time: number): void pause(time: number): void forEachTickBetween(start: number, end: number, callback: (time: number, ticks: Ticks) => void): void } ``` ### Error codes ```ts type TransportErrorCode = | 'AUDIO_TRANSPORT_INVALID_TIME' | 'AUDIO_TRANSPORT_INVALID_BPM' | 'AUDIO_TRANSPORT_SCHEDULER_NOT_STARTED' | 'AUDIO_TRANSPORT_EVENT_NOT_FOUND' | 'AUDIO_TRANSPORT_ALREADY_DISPOSED' const AUDIO_TRANSPORT_ERROR_METADATA: Readonly> function registerTransportErrorCodes(): void ``` ### Zod schemas ```ts TransportOptionsSchema TransportTimeSchema TicksSchema BarsBeatsSixteenthsSchema ``` --- ## File layout ```text packages/audio/audio-transport/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts transport/ transport.ts transport.md transport.types.ts transport.test.ts index.ts scheduler/ scheduler.ts scheduler.md scheduler.types.ts scheduler.test.ts index.ts tick-source/ tick-source.ts tick-source.md tick-source.types.ts tick-source.test.ts index.ts signal/ signal.ts signal.md signal.types.ts signal.test.ts index.ts time/ time.ts time.md time.types.ts time.test.ts index.ts __benchmarks__/ transport.bench.ts tests/ integration.test.ts ``` --- ## Impact **Breaking changes**: none — fresh build, package does not yet exist on genesis. **Prerequisites**: `@suaveplan/timing`, `@suaveplan/events`, `@suaveplan/fsm`, `@suaveplan/types`, `@suaveplan/error` published to Verdaccio. ### Affected dependents `@suaveplan/audio-transport` is consumed by the timeline, both rendering engines, and the controller packages. At the time of this change none of the dependents exist on genesis yet — they land in later changes in this same batch — so there is no existing dependent surface to test or migrate. Each dependent is created against this package's spec delta, not retrofitted. - `@suaveplan/audio-timeline` — [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: does not exist yet; created in `enterprise-audio-timeline`) - `@suaveplan/audio-engine-browser` — [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: does not exist yet; created in `enterprise-audio-engine-browser`) - `@suaveplan/audio-engine-server` — [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: does not exist yet; created in `enterprise-audio-engine-server`) - `@suaveplan/audio-controllers` — [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: does not exist yet; created in `enterprise-audio-controllers`) Once landed, any subsequent change to this package's public surface MUST run `bunx turbo test --filter=...@suaveplan/audio-transport` per `openspec/AGENTS.md` §13 step 10.