Skip to content

The [hold] table — re-sample on a musical edge, hold in between

[latch] answered the event half of [smoothing] shapes a value over time but never holds one”. [hold] answers the value half.

Bind an integer the obvious way and it re-picks itself every frame:

[params]
n = "3 + floor(bass * 5)" # a rose that flickers between 3 and 8 petals

Sixty times a second, that is not a rose changing its mind on the music — it is noise. A [hold] entry says when the value is allowed to change:

[params]
n = "3 + floor(bass * 5)"
[hold]
n = "bar" # take the value on the downbeat; hold it until the next

The expression is still evaluated every frame — the grammar stays a pure function of its variables (ADR-0180). What the hold decides is which frame’s value the scene is shown.

The vocabulary. Three spellings, one per entry:

entrywhen the value is re-taken
"beat"every frame the beat detector fires. That is the onset gate — the same one beat reads in an expression — which fires 1.2x-2.3x per musical beat depending on the material.
"bar"every time the bar counter moves. Read the caveat below before you reach for this.
a numberevery n seconds of render time, n > 0. Write it bare (2.5) or quoted ("2.5"); both mean seconds. The one edge that still steps on a silent stream.

Anything else is a load error naming what was written and what was expected.

A number here is an interval between re-samples, not the length of anything. It is unrelated to [latch]’s own hold, which is how long a fired latch keeps reading 1.0 — the two spell the same word and take the same bare seconds, and they are the two different things above.

bar is only as good as the downbeat tracker, and the downbeat tracker locks about 3 % of audible time (backlog 0042). The rest of the time the bar counter is derived from the tempo grid’s beat count rather than estimated — it steps on something regular and musical, but it is not a promise that you are on the downbeat. Note that this is the grid’s beat, not the detector’s: beat_index counts onsets at the 1.2x-2.3x above, and folding a bar over those is the error ADR-0109 exists to end. So what [hold] n = "bar" reliably buys is slow: roughly one change every four musical beats instead of sixty a second. Where you want the change on a hit rather than on a phrase, "beat" says what it means.

The first frame always takes a value. A held binding samples on the frame the preset becomes active, before any edge has fired — a preset never opens on a default it did not ask for. That is also what makes "bar" correct on silence, where the counter never moves.

With [smoothing], the order is evaluate → hold → smooth. The smoother eases toward the held value exactly as it eases toward any other, so a parameter that is both held and eased travels to each new figure instead of stepping to it:

[hold]
n = "bar"
[smoothing]
n = 0.35 # the petal count glides to its new value over a third of a second

Leave the parameter out of [smoothing] for a clean jump. Note that a structural parameter walks through the intervening whole numbers on the way — see the two groups in Parameter roster.

What cannot be held. A binding that reads index or lives in [per_vertex] is evaluated many times per frame and has no single value to hold, so a [hold] entry naming one is a load error — not the warning the equivalent [smoothing] entry gets. An easing constant has a degraded form to fall back to and a hold has none: the binding would go on flickering while the table looked applied. An entry naming a parameter the preset does not bind is a warning, like an [occupancy] exempt entry that names nothing.

A layer holds its own. [layer.hold] reaches the layer’s [layer.params] and its bindable mix, indexed within the layer, exactly as [layer.smoothing] does.

A hold weakens the static reading of a preset, in one specific way. The reachability walk sees a live expression naming bass; it cannot see that the engine took that expression’s value once this bar. So a held preset can look more reactive on paper than it is. shot --report names every held binding and its edge on a HELD: line for exactly this reason — read the reactivity columns beside it as the response the hold allows.


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