Concept

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

SNAPSHOT menu music sfx ui SNAPSHOT gameplay music sfx ui SNAPSHOT boss music sfx ui .apply({ fade: 0.25 }) crossfades the entire mix
Three named snapshots. apply() ramps every bus level (and parameter) in lockstep.

API surface

Snapshot ts
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

ts ts
// 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

ts ts
// 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

ts ts
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.

ts ts
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.

Pitfalls

Snapshots don't include voices.
They restore the mix, not what is playing on it. For a "boss music starts" moment, trigger the boss music yourself, then apply the snapshot.
Capturing twice does not snapshot motion.
A snapshot is a single moment. To animate a fade-in over time, drive a Parameter instead.

Related

  • Parameter handles continuous values.
  • Mixer holds the bus state being captured.