Record & replay
A sketch run is a performance. Four things drive it: the seed the run started from, the clock it followed, every pointer move and key press, and every parameter you adjusted. Ollin can write that performance down as a take and play it back exactly. Frame N of the replay is then frame N of the original, pixel for pixel. You can record a session of live tuning and step back through it to find the moment you want to keep. You can also re-render the whole performance offline at export quality.
Contents
- Recording a take
- What a take holds
- Replaying in the window
- Re-rendering a performance
- Same gestures, different world
- The
Taketype - What replays, and what cannot
Recording a take
Add --record-take <file> to any windowed run:
swift run --package-path Examples Example-Basic-HelloCircle --record-take take.json
ollin Dots.swift --record-take take.json # a loose file, in the live host
The run starts recording at its own frame 0. In the live host, that is the moment the first compile is on screen. The take ends when the run ends, so quitting writes the file. The recorder also autosaves along the way, which means a run that crashes still leaves its take behind. In the live host, saving an edit ends the take too, because an edited sketch is a different run. Ollin writes the file before it swaps in the edited sketch.
Restarting the seed from the inspector's Variation card starts the take over on the new seed. Ollin does this because a take that changed seed partway through could never play back the same way.
What a take holds
A take is one JSON file, so you can read it and diff it. It holds:
- the
variationseed the run started from, - one clock sample per frame (
time,deltaTime,frameRate), exactly as the live display drove them, jitter included, - every input event (pointer, buttons, pressure, scroll, pinch and turn, keys, modifiers), stamped with the frame it preceded,
- the
@Paramparameter values at the start, and every change after, stamped the same way.
That is everything that drives a deterministic sketch. Play the same file into a fresh instance and it goes through the same frames.
Replaying in the window
swift run --package-path Examples Example-Basic-HelloCircle --replay take.json
ollin Dots.swift --replay take.json
The sketch restarts under the take's seed and parameter values, and the recording drives it instead of your mouse. Live input is turned off, so the keyboard is free to control playback:
| Key | Does |
|---|---|
| Space | Pause and resume; at the end, start over |
| ← / → | Step one frame (paused) |
| ⇧← / ⇧→ | Jump thirty frames |
Home, 0 | Back to the first frame |
| End | Jump to the last frame |
Stepping backward restarts from frame 0 and simulates forward again. Because the replay is deterministic, the result is exact. The cost is the time to simulate the frames in between. Scrubbing near the start is instant, but scrubbing deep into a long, heavy take can take a moment.
When the take runs out, the replay holds its last frame. Space starts it over.
Re-rendering a performance
--replay works with every export flag, so you can re-render a live performance offline, exactly as it was played:
swift run MySketch --replay take.json --export-video performance.mov
swift run MySketch --replay take.json --export still.png --frame 240
swift run MySketch --replay take.json --path-traced --export-video film.mov
A video-shaped export with no --frames/--seconds renders the whole take. The export uses the recorded clock, so it reproduces the exact motion of the live run rather than an idealized fixed-step version of it.
Same gestures, different world
--seed N beside --replay re-seeds the replayed run on purpose. The recorded gestures and parameter changes play back unchanged, while random() and noise() follow a different variation. That lets you play one good performance under many variations:
swift run MySketch --replay take.json --seed 511 --export-video v511.mov
The Take type
The flags are a shortcut for a public value type, so a host built on Ollin can do the same:
let take = try Take.load(from: url) // read, verify the format version
try take.write(to: url) // write, atomically, sorted JSON
let sketch = MySketch()
take.install(on: sketch) // seed + starting parameters + the player
// ...then drive the sketch however you like; every frame replays.
A take holds what it needs to stand a run up again: sketchType is the name of the class it was recorded from, seed the variation behind random() and noise(), initialParams every parameter's value at the first frame, changes the parameter moves after it with the frame each landed on, and events the input, each a Take.StampedEvent pairing a frame with the event that arrived on it. An event is a Take.Event: pointer, button and rightButton (which fire the press and release hooks), buttonState (the button alone, with no hooks, as a host widget's drag sends it), pressure and stylus, scroll, pinch and turn, modifiers, key, keysCleared for the moment the canvas loses the keyboard and drops what it was holding, and typed and composing for typed text. pinch and turn are two fingers on the trackpad, and they fire fingersPinched() and fingersTurned(). typed carries text the input system typed and fires textTyped(), and composing what an input method was composing at that moment. Take.currentVersion is the format version a file is written at and read back against.
Take.install(on:) prepares a fresh instance before its first frame. It applies the seed, restores the starting parameter values, and attaches the player. The player overrides the clock and feeds the recorded inputs through the same paths that live input takes, so the input hooks fire again. The runner carries recording from code and the window's playback controls: beginTake(writingTo:), finishTake(), replay(_:), and scrub(to:).
What replays, and what cannot
A take captures what drives a sketch: time, inputs, parameters, and the seed behind random() and noise(). A sketch driven only by those replays exactly, and the tests check that down to the byte.
What a take does not capture keeps running live during a replay. That includes a camera or microphone feed, incoming OSC or MIDI, network data, and a wall clock read directly (Date()). A sketch that depends on those follows the recorded gestures, but its surroundings are whatever is live at the time. The host's own controls (the camera menu's orthographic toggle, window placement) are also outside the take.