# Snapshot · zvuk

zvuk v1.14.1 · MIT · https://zvuk.schmooky.dev/concepts/snapshot/

Capture a mix state. Apply it later with a smooth crossfade. Your menu/gameplay/boss switcher in one call.

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`](/api/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

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.

Use your own sound

music busidle

snapshotmenusnapshotgameplaysnapshotboss

music level: 0.80

sfx level: 0.30

Unlock & start

## 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](/concepts/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](/examples/snapshot-blend/) 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](/concepts/parameter/) instead.

## Related

-   [Parameter](/concepts/parameter/) handles continuous values.
-   [Mixer](/concepts/mixer/) holds the bus state being captured.
