# `audio-player` _(no submodules)_ ## Purpose `AudioPlayerPlugin` is the universal audio-file player. It decodes a raw `Float32Array` PCM stream from a URL into an internal buffer and plays it back through the universal `BlockProcessor` path. The plugin is a source (`io.inputs[0].count === 0`, `io.outputs[0].count === 1`) so it slots into chains at position 0 — the chain's IO validator rejects placing it elsewhere. The plugin extends `AbstractAudioFxPlugin`, runs identically across browser / server / worklet renderers, and exposes the classic media-player surface (`load`, `play`, `pause`, `stop`, `seek`, `setVolume`, `setLoop`, `setPlaybackRate`, `getState`) the way a UI consumer expects. `load` is `AbortSignal`-aware: a pre-aborted signal short-circuits before any fetch. ## Features - Five-method transport: `play` / `pause` / `stop` / `seek(timeSec)` plus the bool-typed `setLoop`. - Three automatable parameters: `volume` (0..1), `loop` (enum), `playbackRate` (0.25..4). - Pitch-preserving playback is **not** included — `setPlaybackRate(2)` reads the buffer at 2× speed, so it doubles both pitch and consumption. For pitch-preserving rate change, chain a `PitchShifterPlugin` downstream. - `load(url, { signal? })` fetches a raw Float32 PCM buffer (same encoding as `SamplerPlugin.loadSample` / `ReverbPlugin.loadImpulseResponse`). Pre-aborted signal throws `AUDIO_FX.ABORTED`. - `play()` before any `load()` throws `AUDIO_FX.SAMPLE_NOT_LOADED` — the same error code the sampler uses for the same misuse. - `setLoop(true)` makes playback wrap back to the buffer start without producing silence in between. Without loop, playback stops cleanly at the end of the buffer and flips `state.playing` to `false`. - `getState()` returns a frozen snapshot of `{ playing, paused, currentTimeSec, durationSec, volume, loop, playbackRate }` so UI code never has to read internal fields. ## Usage: Basic Example ```ts import { AudioPlayerPlugin } from "@suaveplan/audio-fx-plugin-audio-player"; const player = new AudioPlayerPlugin(); await player.load("/audio/track-01.raw"); chain.add(player); player.play(); ``` ## Usage: Advanced Example ```ts import { AudioPlayerPlugin } from "@suaveplan/audio-fx-plugin-audio-player"; const player = new AudioPlayerPlugin({ initialVolume: 0.8, initialLoop: true, initialPlaybackRate: 1, }); await player.setup( { sampleRate: 48_000, channels: 1, blockSize: 512 }, ctx ); const ac = new AbortController(); try { await player.load("/audio/loop.raw", { signal: ac.signal }); } catch (error) { if (error instanceof AudioFxError && error.code === "AUDIO_FX.ABORTED") { console.warn("audio load aborted"); return; } throw error; } // Drive a UI scrubber from the live state. const ticker = setInterval(() => { const state = player.getState(); scrubber.setValue(state.currentTimeSec / state.durationSec); }, 50); player.play(); ``` ## API Reference - `AudioPlayerPlugin` (class extends `AbstractAudioFxPlugin`) - `new AudioPlayerPlugin(options?: AudioPlayerPluginOptions)` — throws `AUDIO_FX.INVALID_OPTIONS` on malformed options. - `load(url, { signal? }): Promise` — fetches and registers the playback buffer. - `play(): void` — throws `AUDIO_FX.SAMPLE_NOT_LOADED` if no buffer is loaded. - `pause(): void`, `stop(): void` - `seek(timeSec: number): void` — throws `AUDIO_FX.INVALID_OPTIONS` on negative or non-finite values. - `setVolume(v: 0..1): void`, `setLoop(loop: boolean): void`, `setPlaybackRate(rate: 0.25..4): void` - `getState(): AudioPlayerState` - Inherited accessors for `volume`, `loop`, `playbackRate`. ## Implementation Notes The buffer is a `Float32Array` allocated by `load`. Subsequent `load` calls replace the buffer and reset the read position to zero. There is no in-flight load tracking — concurrent `load` calls race; the last one to resolve wins. Most consumers wrap `load` calls in a single `AbortController` and abort the previous call before starting a new one, which is what the AbortSignal handling is designed for. `pause` is intentionally a no-op when `playing === false`. Without that guard, a `pause()` before any `play()` would set `paused = true`, which would then make the next `play()` start in a paused state (because `play` only flips `playing`). This matched the behaviour of the main-branch `HTMLAudioElement`-backed implementation and is the principle of least surprise for media-player consumers. `setPlaybackRate` validates at the setter rather than relying on the parameter's `min`/`max` clamping. The reason: the parameter declares `automatable: true`, so a parameter-curve automation could in principle write any number; the setter rejects out-of-range *direct* set calls so a UI's invalid input is caught at the boundary. Automation curves operate on a separate ramp and are clamped by the parameter range internally. Linear-interpolated read between adjacent samples handles fractional playback rates and seeks. The interpolator is the same shape used by `SamplerPlugin` — `s0 * (1 - frac) + s1 * frac` — so pitch-preservation behaviour is consistent across the two plugins. Looping wraps with `position -= (buffer.length - 1)` rather than `position = 0` so any fractional carry survives the wrap. `getState` is the only place that reads the live parameter values without going through `parameterValue` — that helper requires the chain context to compute scheduled automation values. Since `getState` is meant to be safe to call before `setup`, it falls back to `getParameter` (the static-value accessor) instead.