Snapshot
Capture a mix state. Apply it later with a smooth crossfade. Your menu/gameplay/boss switcher in one call.
Getting a mix right takes an afternoon of nudging eight sliders. Getting
back to it from the menu screen takes eight fadeTo calls
that all have to start together and land together, and one of them will
drift the day someone adds a ninth bus.
A Snapshot is an immutable
record of bus levels, mutes and parameter values. You capture one when
the mix sounds right and apply it later with a fade, in seconds, and the
whole mix crossfades in one call.
Mental model
API surface
engine.captureSnapshot(name): Snapshot
engine.snapshot(name, state): Snapshot
engine.blendSnapshots(a, b, t): void // snap mix to lerp(a, b, t)
class Snapshot {
readonly name: string;
readonly state: SnapshotState;
apply(options?: { fade?: number }): Promise<void>;
blendWith(other: Snapshot, t: number): void;
} Live demo
Three presets, 800 ms crossfade between mixes.
Recipes
Capture from the live engine
// Set the mix to a known-good "menu" state, then capture it.
engine.bus('music').level = 0.8;
engine.bus('sfx').level = 0.3;
const menu = engine.captureSnapshot('menu'); Build from explicit state
// Build a snapshot from explicit state — no need to mutate the live engine.
const boss = engine.snapshot('boss', {
buses: {
music: { level: 1.0, muted: false },
sfx: { level: 0.8, muted: false },
},
parameters: { intensity: 1.0 },
}); Switch with crossfade
await menu.apply({ fade: 0.25 }); // crossfade everything in 250ms
await boss.apply({ fade: 0.8 }); Blend continuously between two snapshots
apply is a one-shot crossfade. When you want a mix that
follows a knob instead of a switch, capture two snapshots and let a
Parameter drive
blendSnapshots. Game tension, distance to a boss room and
time of day all read naturally that way.
const calm = engine.captureSnapshot('calm');
// ...switch the mix to its combat shape, then re-capture.
const combat = engine.captureSnapshot('combat');
// Snap the mix to lerp(calm, combat, t). Drive a Parameter to animate.
engine.blendSnapshots(calm, combat, 0.4); // 40% of the way to combat
const tension = engine.parameter('tension', 0);
tension.subscribe((t) => engine.blendSnapshots(calm, combat, t));
tension.set(0.75); // per-frame friendly
Each call snaps the mix instantly, so calling it every frame is cheap.
Every bus-level setter still rides its own 10 ms anti-click ramp
underneath. Buses and parameters missing from either snapshot are
skipped, and mute flips at t = 0.5 rather than
interpolating. The snapshot-blend
example wires a slider through a Parameter end to end.