Home
Reference

API reference

Public entry points, input ranges, return values, and lifecycle contracts.

Import browser runtime APIs from gesture-audio. Import the filesystem-backed verifier only from gesture-audio/build-tools.

Entry pointIntended environmentIncludes
gesture-audioModern browser runtime; safe to import during SSRlifecycle, Tone bus, resolver, and preferences APIs
gesture-audio/howlerModern browser runtime; safe to import during SSRlifecycle (unlocking Howler's context) and resolver APIs; never imports tone
gesture-audio/tone-lazyModern browser runtime; safe to import during SSRasynchronous Tone lifecycle and post-unlock bus runtime; does not evaluate Tone while imported
gesture-audio/build-toolsNode.js 22, 24 and 26 build or CI processaudio-asset verification APIs

gesture-audio/howler is for applications that play pre-rendered audio through Howler sprites and build no Tone nodes. Its module graph is verified to contain no tone import, so only Howler's AudioContext exists. It exports the startAudioEngine, registerAudioGestureTrigger, isAudioEngineStarted and _resetAudioEngine names with the contract below, except that the unlock step resumes Howler's context (creating it if needed, or doing nothing when Howler has fallen back to HTML5 Audio) instead of calling Tone.start(), and the gesture trigger also listens for pointerdown. It also exports the whole sprite resolver. It omits the Tone bus graph and the preferences bridge. Use one entry point or the other in an application, never both.

Underscored exports (_resetAudioEngine, _getCueMap, _getLastResolverOptions, and _getMasterBusName) support tests and hot reload. Do not use them as application integration APIs.

Lazy Tone lifecycle

gesture-audio/tone-lazy is for a Tone application that must be mounted before the browser permits audio. Its module import does not evaluate Tone or create an AudioContext. Use this entry instead of the root entry for that application; the root entry deliberately retains its synchronous Tone bus API for existing consumers.

The consumer must also defer its own Tone-dependent synthesizers, effects, and voice imports until the lifecycle bootstrap runs. A static tone import or a module that creates Tone nodes during application startup defeats this entry's deferred-context guarantee.

import { registerToneLazyGestureTrigger } from 'gesture-audio/tone-lazy';

const removeAudioTrigger = registerToneLazyGestureTrigger(async (audio) => {
  audio.buildBuses(['master', 'sfx']);
  audio.getBuses().sfx.gain.gain.value = 0.8;
});

startToneLazyEngine(bootstrap)

Dynamically loads Tone, calls Tone.start(), then calls bootstrap(runtime). Call it directly only from a real user-input handler. runtime exposes the Tone bus APIs buildBuses, getBuses, setBusVolume, muteBus, duckBus, and disposeBuses. It is unavailable before this transaction succeeds.

registerToneLazyGestureTrigger(bootstrap)

Registers the same capture-phase click, keydown, and touchstart trigger as the root lifecycle. It defers the Tone runtime import until the first gesture. An uncached dynamic module may finish after a browser's transient activation has expired, so the first attempt can reject on a slow first load; the trigger stays armed and the next real gesture retries. Test first-load behavior in the target browser before treating audio as ready.

isToneLazyEngineStarted()

Returns true only after the dynamic Tone load, Tone unlock, and the caller's bootstrap have all completed. _resetToneLazyEngine() is a test and hot-reload escape hatch, not application API.

setToneLazySuspended(suspended)

Suspends (true) or resumes (false) Tone's AudioContext, for example from an app lifecycle's pause and resume, so scheduled notes, loops and ramps hold their place while the app is backgrounded instead of running on silently. It is the Tone counterpart of setAudioSuspended. Until a start has unlocked Tone it resolves without loading Tone or touching any context, since resuming a context no gesture unlocked would try to start audio outside one. It is a no-op on a closed context or one already in the requested state, and it never throws. A resume outside a user gesture may stay pending until the browser allows it.

Lifecycle

startAudioEngine(bootstrap)

Runs Tone.start() and then bootstrap as one asynchronous, retryable transaction. Call it from a real user-input handler. It resolves only when both steps complete. Concurrent calls share the same attempt; after success, later calls are no-ops. A rejection leaves the engine unstarted so a later gesture can retry.

registerAudioGestureTrigger(bootstrap)

Registers capture-phase click, keydown, and touchstart listeners on document, and returns () => void to remove them. It is a browser-only convenience: on the server it returns a no-op cleanup. The listener removes itself only after a successful start; failures are warned and remain retryable.

isAudioEngineStarted()

Returns true only after both the Tone unlock and the caller bootstrap have succeeded.

Tone bus graph

buildBuses(names) and getBuses()

buildBuses accepts a non-empty, unique list of caller-owned names. The first name is the master/root. Each other Tone.Gain connects to it, and the master connects through a Tone.Limiter(-1) to the device destination. limiter is a reserved name. It returns an AudioBuses map keyed by your names plus limiter.

Calling it again with the exact same list returns the existing graph. To change the topology, first call disposeBuses(). getBuses() throws before a graph has been built.

setBusVolume(bus, linear, rampMs?)

Sets a bus's remembered linear gain. linear is finite and is clamped to 0..1; rampMs defaults to 50, must be finite, and must not be negative. Unknown buses are harmless no-ops. A change while muted or ducked is retained and becomes audible when those layers clear.

muteBus(bus, muted) and duckBus(bus, duckDb, durationMs?)

muteBus adds or removes the manual mute layer with a short click-free ramp. duckBus applies a non-positive decibel attenuation; a positive value throws. Providing durationMs schedules restoration, and a new duck replaces an older timed duck for that bus. A muted or unknown bus is left unchanged.

disposeBuses()

Clears timed ducks, disposes every Tone node and limiter, and clears mix state. Call it before rebuilding a different graph or during application teardown.

Sprite resolver

initSpriteResolver(options?)

Fetches a JSON map and creates one Howler Howl per unique sprite file. Identical concurrent options share one initialization; changing options after initialization requires disposeSpriteResolver() first.

OptionDefaultContract
spriteMapUrl/audio/sprite-map.jsonNon-empty URL for the JSON map.
spriteMapUrlsnoneNon-empty array of distinct, non-empty URLs, fetched and merged into one cue map; use instead of spriteMapUrl (passing both throws). A cue defined in more than one map is an error under strict; otherwise the first definition wins and a warning names both maps. A failed request for any map fails the initialization.
defaultPannernonePannerOptions applied to any playCue that gives a position without its own panner. Changing it requires disposeSpriteResolver() first.
audioBaseUrl/audioNon-empty base path for sprite files.
formats['webm', 'm4a']Non-empty list of letter/digit extensions, in Howler preference order.
preloadtrueWhen false, validates the JSON map and creates sheet definitions without fetching or decoding audio. The first playCue for each sheet queues playback and calls public Howl.load() for that sheet.
strictfalseReject map/network/validation failures instead of warning and using an empty resolver.

The map can be flat (cue → entry) or grouped (group → cue → entry). Every entry has start_ms, end_ms, and an extensionless, traversal-safe relative file path. An entry can also declare formats as a readonly ordered list of encoded extensions and loop: true for an individual looping sprite. Every cue sharing one file must declare the same ordered format list (or resolve to the same resolver default); strict mode rejects a conflict. See sprite maps for examples.

playCue(cueName, busTarget?) and playCue(cueName, options?)

playCue starts a known cue and returns its Howler sound ID. It returns -1 when the resolver is not ready, the cue is absent, or its sheet is unavailable. busTarget defaults to sfx; the additive options shape is { bus?, gain?, position?: readonly [x, y, z] }. Logical gain is clamped to 0..1 and composes with current master and target-bus gains and mutes. A position is sent through Howler's public spatial API.

panner (PannerOptions: panningModel, distanceModel, refDistance, rolloffFactor, maxDistance, coneInnerAngle, coneOuterAngle, coneOuterGain, all optional) is applied with Howler's pannerAttr for that sound ID whenever a position is given, merged field by field over the resolver's defaultPanner. If the cue is positioned later with setCuePosition, the attributes are applied then, once. Values are validated (refDistance and rolloffFactor >= 0, maxDistance > 0, cone angles 0..360, coneOuterGain 0..1, models from the Web Audio enums) and invalid ones throw. Howler's defaults are HRTF panning and no maximum distance; equalpower is much cheaper on the audio thread.

stopCue(id) stops an active, non-negative ID and is otherwise a no-op. setCueGain(id, gain) updates one active cue's logical gain and returns whether the ID exists. pauseCue(id) pauses an active ID and returns whether it did; resumeCue(id) resumes that same ID and returns it, or -1 when it cannot. fadeCue(id, toGain, durationMs) fades logical gain while still following live bus/master volume and mute changes; a pause freezes that fade. setCuePosition returns whether the ID exists after validating its position. Looping sprite IDs remain active between loop-end events, so these controls continue to affect them until stopped.

With preload: false, the resolver registers the returned ID before it starts the sheet load. stopCue, mix updates, fades, pause/resume, and spatial updates therefore still apply while the load is pending. A failed sheet removes its pending IDs without throwing from playCue; a later cue can request that sheet again.

setAudioSuspended(suspended)

Returns a promise that resolves once Howler's AudioContext has been suspended (true) or resumed (false), for an application that is backgrounded or foregrounded. Suspending freezes the audio clock, so playing sprites, loops and fades hold their position. It is a no-op, and never throws, before Howler has created its context, when Howler fell back to HTML5 Audio, when the context is already in the requested state, and after the context is closed; a context that rejects the transition is left as it is. Resuming outside a user gesture may stay pending until the browser allows it. Exported from both gesture-audio and gesture-audio/howler, and it never imports Tone.

setAudioListener(position, forward, up?)

Sets the global Howler listener with public Howler.pos and Howler.orientation APIs. All vectors are finite three-number tuples; up defaults to [0, 1, 0].

setResolverVolume(bus, linear), setResolverMute(bus, muted), and setResolverMasterBus(bus)

Resolver volume is finite and clamped to 0..1; an empty bus name throws. Volume and mute updates affect active sounds as well as future cues. Designate the bus that should globally scale/mute all Howler cues with setResolverMasterBus('master'); pass null to remove that override.

disposeSpriteResolver()

Unloads all Howls and clears cues, active sound bookkeeping, listener registrations, pending logical fades, resolver mix state, and initialization epochs. It is safe to call during teardown and is required before reinitializing with different options.

Preferences bridge

interface AudioPrefsSnapshot {
  audioVolumes: Record<string, number>; // persisted integer percentages
  muteOnFocusLoss?: boolean;
  muteAll?: boolean;
}

interface AudioPrefsStore {
  get(): Promise<AudioPrefsSnapshot>;
  update(patch: { audioVolumes: Record<string, number> }): Promise<void>;
}

All preference volumes are rounded and clamped to integer 0..100. Store updates are serialized per store; a failed write restores the preceding live mix without allowing an older failed slider operation to overwrite a newer one.

FunctionContract
applyPersistedAudioPrefs(store, busNames, defaultVolume100?)Reads and applies volumes to Tone and Howler, then applies focus-loss and muteAll layers. Missing values use the 75 default or the supplied fallback.
setAndPersistBusVolume(bus, vol100, store)Applies one normalized percentage and persists a complete merged audioVolumes map; rolls back the live value if persistence rejects.
setAndPersistBusMute(bus, muted)Applies manual mute to Tone and Howler only. It does not write storage.
syncAudioPrefsFromSettings(audioVolumes, busNames, store, defaultVolume100?)Normalizes, merges, applies, and persists several slider values as one serialized update.
registerFocusLossMute(enabled, store?, busNames?)Registers/removes browser blur/focus listeners. Focus removes only the focus-loss layer, preserving manual and global mutes.
setTransientAudioMute(busNames, reason, muted)Adds or removes a named runtime-only mute layer in both Tone and Howler. It never reads or writes persisted preferences and cannot remove a manual mute layer.

Node asset verification

verifySprites(options) returns { failures, warnings, lines } and never exits the process. runVerifySpritesCli(options) prints the lines and exits with status 1 when failures are present.

OptionMeaning
audioRootRequired root directory containing audio assets.
spriteBusesRequired sprite subdirectories; each expects sprite.json and every configured encoded format.
formatsEncoded extensions; defaults to webm and m4a.
flatGroupsOptional extensionless-key files such as music tracks; set required: true to make missing files fail.
fastSkips ffprobe duration and ffmpeg loudness measurement while retaining structural checks.
lufsTarget, lufsToleranceLoudness target and permitted deviation; defaults to -14 ± 3.
maxTotalBytesOptional total directory-size budget.

The verifier rejects absolute paths and traversal in bus, group, and key configuration before reading the filesystem. Full mode needs ffmpeg and ffprobe on PATH; an unavailable measurement is a warning, while malformed assets and required missing files are failures.