@flare-engine/audio
Web-Audio-shaped runtime audio — AudioManager (init / suspend / resume), SFXPool (pre-decoded buffers + polyphony cap), Music (single-track with crossfade), createChannels (master / sfx / music gain mixer), renderSynth (procedural SFX to PCM). Lifecycle is manual: call suspend()/resume() from your own background/foreground handler (see below).
Install
bun add @flare-engine/audio
Native: pair with react-native-audio-api (Web Audio API on native). It is an optional peer, so
install it yourself in an app that plays audio on native (bun add react-native-audio-api); the
package never imports it. On web, the browser's AudioContext needs nothing installed.
Usage
import { AudioManager, SFXPool, createChannels } from '@flare-engine/audio';
import type { AudioContextLike, AudioBufferLike } from '@flare-engine/audio';
// AudioContextLike is the engine's interface — obtain a real one from
// `new AudioContext()` (react-native-audio-api on native, the Web Audio global on web).
declare const ctx: AudioContextLike;
declare const shootBuffer: AudioBufferLike;
// One AudioContext, created lazily via the manager's factory.
const manager = new AudioManager();
manager.init(() => ctx);
// master ← sfx / music gain mixer.
const channels = createChannels(ctx);
// SFX pool: cap simultaneous voices, route through the sfx channel.
const sfx = new SFXPool(8);
sfx.connect(ctx, channels.get('sfx')!.node);
// Register pre-decoded buffers (decode with react-native-audio-api), then play.
sfx.register('shoot', shootBuffer);
sfx.play('shoot', { volume: 0.8, rate: 1.25 });
sfx.play('hit', { delay: 0.35 }); // start 350 ms from now on the audio clock
Synthesised SFX
renderSynth renders a described sound (sine / square / saw / noise, ADSR envelope, linear frequency sweep) into a caller-owned mono Float32Array; fillPCMBuffer copies it into a buffer the context created.
import { fillPCMBuffer, renderSynth, synthSampleCount } from '@flare-engine/audio';
import type { AudioContextLike, SFXPool } from '@flare-engine/audio';
declare const ctx: AudioContextLike;
declare const sfx: SFXPool;
const rate = 44100;
const n = synthSampleCount(0.15, rate);
const pcm = new Float32Array(n);
renderSynth({ wave: 'square', frequency: 880, endFrequency: 220, duration: 0.15 }, rate, pcm);
const buffer = ctx.createBuffer?.(1, n, rate); // optional on AudioContextLike
if (buffer) {
fillPCMBuffer(buffer, pcm);
sfx.register('blip', buffer);
}
Lifecycle (manual)
There is no automatic background/foreground handling — nothing subscribes to AppState or visibilitychange for you. Drive suspend()/resume() from your own lifecycle event:
import { AppState } from 'react-native';
import { AudioManager } from '@flare-engine/audio';
declare const manager: AudioManager;
AppState.addEventListener('change', (state) => {
if (state === 'active') manager.resume();
else manager.suspend();
});
On web, subscribe to visibilitychange:
import { AudioManager } from '@flare-engine/audio';
declare const manager: AudioManager;
document.addEventListener('visibilitychange', () => {
document.hidden ? manager.suspend() : manager.resume();
});
See API spec.
License
Apache-2.0