3.6 KiB
3.6 KiB
renderOffline
(no submodules)
Purpose
renderOffline is the bridge to non-realtime export. It wires a
BrowserTimelineAdapter to an OfflineAudioContext, attaches the
adapter to the supplied TimelineEngine, drives playback from 0, and
resolves with the rendered AudioBuffer. Because the adapter accepts an
injected context, offline render reuses the exact same rendering code
path as realtime playback — the "tests" and the "export feature" share
one seam.
Failure modes are uniform: pre-flight checks (bad duration, bad sample
rate, pre-aborted signal), context construction failure, and
startRendering() rejection all raise
BrowserAudioError(OFFLINE_RENDER_FAILED) with the underlying error
attached as cause.
Features
- Pre-flight validation of
duration(> 0, finite) andsampleRate(> 0, finite). AbortSignalhonoured at the boundary.- Optional
contextFactoryfor tests that cannot construct a realOfflineAudioContext(e.g.happy-dom); production callers omit it and the function dynamically importsstandardized-audio-context. - OTel span (
engine_browser.offline_render) and duration histogram (audio_engine_browser_offline_render_duration_ms). - Disposes the adapter in
finallyso a render failure does not leak resources.
Usage: Basic Example
import { renderOffline } from "@suaveplan/audio-engine-browser/offline/offline-render";
const buffer = await renderOffline(timeline, 2, 48000);
console.log(buffer.length, buffer.sampleRate, buffer.numberOfChannels);
Usage: Advanced Example
import { renderOffline } from "@suaveplan/audio-engine-browser/offline/offline-render";
const buffer = await renderOffline(timeline, 4, 44100, {
signal: controller.signal,
contextFactory: ({ length, sampleRate, channels }) =>
new MyDeterministicOfflineContext({ length, sampleRate, channels }),
});
// buffer.length === Math.ceil(4 * 44100)
API Reference
renderOffline(timeline, duration, sampleRate, options?) → Promise<AudioBuffer>
timeline— theTimelineEngineto drive.duration— seconds; must be finite and positive.sampleRate— Hz; must be finite and positive.options.signal— optionalAbortSignal; an already-aborted signal short-circuits before any context work.options.contextFactory— optional injected factory. Production omits this; tests provide one.
Returns the AudioBuffer from OfflineAudioContext.startRendering.
RenderOfflineOptions
interface RenderOfflineOptions {
signal?: AbortSignal;
contextFactory?: (args: {
length: number;
sampleRate: number;
channels: number;
}) => OfflineContextLike;
}
Implementation Notes
- Length is computed as
Math.ceil(duration * sampleRate)so a 2-second render at 48 kHz produces exactly 96 000 frames. - The dynamic
import("standardized-audio-context")keeps the shim out of the import graph whencontextFactoryis always supplied (e.g. in the test suite). - The adapter is disposed in
finally. The injectedOfflineAudioContextis NOT closed by the adapter because the adapter sees it as injected; the caller owns the context lifetime if they passed one in via the factory. The function does not callclose()itself either, becauseOfflineAudioContext.startRenderingresolves to a single buffer and the spec considers the context spent at that point. - The duration histogram is recorded in
finallyso both success and failure produce a sample. Failure samples are useful for SLO monitoring (the percentile of failed renders).