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,122 @@
# `Html5AudioFallback`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`Html5AudioFallback` is the graceful-degradation path for browsers and
runtimes without the Web Audio API. It implements the same
`TimelineAdapter` interface as `BrowserTimelineAdapter` so consumers
swap engines without conditional branching, but every operation that the
underlying `HTMLAudioElement` cannot honour — per-track volume / pan /
solo / mute, `setTempo`, FX hosting, sample-accurate scheduling — is a
defined no-op. Unsupported per-track operations on **unknown** track ids
still raise `AUDIO_ENGINE_BROWSER.TRACK_NOT_FOUND` so caller bugs are
detected.
The fallback never constructs an `AudioContext`. It is feature-bounded by
design — `<audio>` plays a single source at a time, so the adapter
honours the first track's first clip and ignores the rest. Consumers
that need polyphony or per-track FX use `BrowserTimelineAdapter`.
## Features
- Implements `TimelineAdapter` (every member callable).
- Real transport (`play` / `pause` / `stop` / `seek` / `getCurrentTime`)
through an injectable `HTMLAudioElement`-shaped value.
- `setMasterVolume` clamped to `[0, 1]`.
- `setLoop` honours the `enabled` flag by toggling the element's `loop`
property and records the window for `getLoopWindow()` debugging.
- Per-track ops validate the track id and then no-op.
- `dispose()` is idempotent.
- Methods after `dispose()` raise
`AUDIO_ENGINE_BROWSER.ALREADY_DISPOSED`.
## Usage: Basic Example
```ts
import { Html5AudioFallback } from "@suaveplan/audio-engine-browser/fallback/html5-fallback";
const fallback = new Html5AudioFallback();
fallback.init();
fallback.setTracks([{ id: "t1", name: "lead", clips: [{ id: "c1", documentId: "/song.mp3", startSample: 0, offsetSamples: 0, durationSamples: 0 }], volume: 1, muted: false, soloed: false, pan: 0 }]);
fallback.play();
```
## Usage: Advanced Example
```ts
import { Html5AudioFallback } from "@suaveplan/audio-engine-browser/fallback/html5-fallback";
// Inject a custom element (e.g. a test double or a pre-buffered element)
const fallback = new Html5AudioFallback({
element: customAudioElement,
volume: 0.8,
loop: true,
});
fallback.init({ signal: controller.signal });
fallback.setMasterVolume(0.5);
fallback.setLoop(0, 8, true);
fallback.play(2); // start at 2s
console.log(fallback.getCurrentTime());
fallback.dispose(); // idempotent — subsequent ops throw ALREADY_DISPOSED
```
## API Reference
### `new Html5AudioFallback(options?)`
- `options.element` — pre-constructed `HTMLAudioElement` (default
`new Audio()`).
- `options.volume` — initial master volume.
- `options.loop` — initial loop flag.
- `options.lookAhead` / `options.ppqn` — readonly properties surfaced
for `TimelineAdapter` parity.
### `init(options?) → void`
Synchronous in this implementation. Honours a pre-aborted `AbortSignal`
by throwing `AUDIO_ENGINE_BROWSER.CONTEXT_FAILED`.
### `setTracks(tracks)`, `addTrack(track)`, `removeTrack(trackId)`, `updateTrack(trackId, track)`
Maintain the track table; the first track's first clip becomes the
element's `src`.
### `play(time?)`, `pause(time?)`, `stop(time?)`, `seek(time)`, `getCurrentTime()`
Drive the underlying element.
### `setLoop(start, end, enabled)`, `setMasterVolume(volume)`
Honoured operations.
### `setTrackVolume(trackId, _)`, `setTrackMute`, `setTrackSolo`, `setTrackPan`, `setTempo`
Defined no-ops with track-id validation.
### `dispose() → void`
Idempotent. Pauses the element and marks the fallback disposed.
### `getLoopWindow() → { start, end, enabled }`
Returns the configured loop window for tests / debug.
## Implementation Notes
- The fallback chooses the first clip of the first track as `src`. A
full multi-clip mix is out of scope — `<audio>` is single-source.
- `setMasterVolume` clamps to `[0, 1]`; some platforms throw on
out-of-range values, so the clamp is the caller-friendly behaviour.
- The Promise returned by `HTMLAudioElement.play()` rejects on
autoplay-policy violations; the rejection is swallowed because the
spec contract is synchronous and reporting a rejection here would
surface a different shape per platform.
- `dispose()` swallows pause errors because some embedded `<audio>`
shims throw after detachment.