Skip to content

The [latch] table — arm on one thing, fire on another

[smoothing] shapes a value over time but never holds one, so there is a thing the two of them together still cannot say: fire once inside a window, on the music. min(mod(time, 100) > 90, onset > 0.6) is an and over two instantaneous readings — it goes true on every onset inside the window, not on the first — and an edge-triggered parameter reading it re-fires each time.

A [latch] says it:

[latch]
# armed while `arm` holds; on the first rise of `fire` inside that window it
# reads 1.0 for `hold` seconds, and does not read 1.0 again until `arm` has
# fallen and risen
recut = { arm = "mod(time, 100) > 90", fire = "onset > 0.6", hold = 0.5 }
[params]
recompose = "recut"

The name is yours. It becomes an ordinary variable in every expression of the preset — [params], [per_vertex] and [layer.params] alike — so it composes with everything else: multiply it, gate on it, ease it through [smoothing], read it from several bindings at once.

The three keys.

keywhat it is
arman expression. While it reads above 0.5 the latch is in an arming window. Its fall closes the window; its next rise opens a new one.
firean expression. The latch fires on its rising edge — a value that was already above 0.5 when the window opened is not an edge.
holdseconds the fired latch reads 1.0, a bare number rather than an expression. Optional; 0 means a single frame, which is the right answer for a parameter that acts on the rise and ignores the rest.

One rise per arming window. However many fire edges a window contains, the latch rises on the first and on no other. The next rise requires arm to have fallen and risen again. A window with no fire edge in it produces no rise at all — a latch is not a timer with a jitter, it is a permission that gets spent.

hold is a duration, measured on real elapsed time like [smoothing], so it is the same length at any refresh rate.

This hold is not the [hold] table below, and both take a bare number of seconds, so it is worth one line. A latch’s hold says how long a fired event keeps reading 1.0. A [hold] entry says how often a binding re-takes its value. One is the length of a pulse, the other the interval between samples.

A preset may declare up to four latches. Asking for more is a load error naming the cap; the storage is a fixed block resolved at load, so this is a wall rather than a slower path. A latch name may not be one the grammar already resolves (bass, time, pi, sin, …) — that is a load error too, because a binding naming it would silently read the built-in. And a latch’s own arm and fire cannot name another latch.

A latch is the one part of the preset surface that depends on frame history, and two consequences follow that nothing else here has.

A single-frame probe reads it at rest. Anything that evaluates one frame in isolation sees 0.0 for every latch and cannot tell a latch that can never fire from one that simply has not. shot --report drives a frame sequence, so its reachability walk does see a latch; a static reading of one frame does not.

It resets on a preset switch. An arming window does not survive the preset that opened it, and a hold in progress is dropped — the same rule [smoothing]’s state follows, for the same reason.

Full reference, including where the cap comes from: Parameter roster.


Built from a8ce055 at version 0.115.0. This site tracks main and is not versioned per release.