first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,112 @@
# SpritePlaybackAdapter
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`SpritePlaybackAdapter` is the engine-tier seam that the universal `@suaveplan/audio-sprite-player` reaches through to make real audio. The interface is intentionally tiny — three methods — so engine packages such as `@suaveplan/audio-engine-browser` (Web Audio API) and `@suaveplan/audio-engine-server` (native bindings) can implement it without inheriting any orchestration concerns.
Adapters are responsible for the work that requires platform globals: decoding the sprite blob, allocating mixer nodes, ramping gain for fade-in/out, scheduling pitch (playback rate), and disconnecting the voice after `stop`. They are NOT responsible for manifest validation, entry lookup, voice management, scheduling between sequence entries, or telemetry — those belong to the universal layer.
## Features
- Tiny three-method contract: `init`, `play`, `dispose`.
- `ResolvedPlayOptions` is the only option shape the adapter ever sees — the universal layer applies defaults and Zod validation upstream.
- `init` and `play` accept an optional `AbortSignal`; the contract requires adapters to honour it.
- The adapter receives the entire `AudioSprite` (blob + manifest) at `init` time so it can decode the data once and keep decoded buffers for the lifetime of the player.
## Usage: Basic Example
```ts
import type { SpritePlaybackAdapter } from "@suaveplan/audio-sprite-player";
class MyAdapter implements SpritePlaybackAdapter {
async init() {}
async play(entry, opts) {
return {
id: crypto.randomUUID(),
entryName: entry.name,
startedAt: Date.now(),
async stop() {},
setVolume() {},
isPlaying: () => false,
};
}
async dispose() {}
}
```
## Usage: Advanced Example
```ts
import type {
AudioSprite,
ResolvedPlayOptions,
SpritePlaybackAdapter,
SpritePlayHandle,
} from "@suaveplan/audio-sprite-player";
class BrowserAdapter implements SpritePlaybackAdapter {
private buffer: AudioBuffer | null = null;
constructor(private ctx: AudioContext) {}
async init(sprite: AudioSprite, signal?: AbortSignal) {
if (signal?.aborted) throw new Error("aborted");
this.buffer = await this.ctx.decodeAudioData(sprite.data.slice().buffer);
}
async play(
entry: { name: string; offset: number; byteLength: number; durationSeconds: number },
opts: ResolvedPlayOptions,
): Promise<SpritePlayHandle> {
const source = this.ctx.createBufferSource();
source.buffer = this.buffer;
source.playbackRate.value = opts.pitch;
source.loop = opts.loop;
const gain = this.ctx.createGain();
gain.gain.setValueAtTime(0, this.ctx.currentTime);
gain.gain.linearRampToValueAtTime(
opts.volume,
this.ctx.currentTime + opts.fadeInMs / 1000,
);
source.connect(gain).connect(this.ctx.destination);
source.start(opts.when / 1000, entry.offset / entry.durationSeconds);
// ... build and return SpritePlayHandle
throw new Error("snippet only");
}
async dispose() {
this.buffer = null;
}
}
```
## API Reference
### `interface SpritePlaybackAdapter`
```ts
interface SpritePlaybackAdapter {
init(sprite: AudioSprite, signal?: AbortSignal): Promise<void>;
play(entry: AudioSpriteEntry, opts: ResolvedPlayOptions, signal?: AbortSignal): Promise<SpritePlayHandle>;
dispose(): Promise<void>;
}
```
### Contract for `ResolvedPlayOptions`
- `volume` ∈ `[0, 1]` — final gain.
- `pitch` ∈ `[0.5, 2]` — playback rate.
- `loop` — when `true`, the adapter MUST loop the voice and MUST NOT auto-emit completion.
- `fadeInMs` / `fadeOutMs` — millisecond gain ramps; `0` means an instant transition.
- `startOffsetMs` — offset INTO the entry, not the sprite.
- `durationMs` — voice duration the player expects; adapters MAY honour or ignore depending on capability.
- `when` — absolute schedule time in the player's clock domain; adapters convert to their own time base.
## Implementation Notes
- The adapter SHOULD honour `AbortSignal` during `init` and `play` and throw a real error on abortion; the universal player wraps it into `AUDIO_SPRITE_PLAYER.ABORTED`.
- The `SpritePlayHandle` returned by `play` is returned directly to the caller, so its `stop`, `setVolume`, and `isPlaying` methods must be safe to call from any context.
- Engine packages MUST NOT re-implement manifest validation, voice management, or sequence scheduling — those live in `@suaveplan/audio-sprite-player`. Adding them in an adapter is a contract violation.