propagate() — Group-Level Parameter Propagation
propagate(...) is a pre-parser macro that expands a cue or animation template across all direct children of an SVG <g> element. Each child receives a unique instance of the template with independently evaluated parameters.
Purpose
To apply variations of an animation or cue to multiple similar objects without manually writing separate IDs. Typical uses:
- evenly distributing random timing or values across many elements
- giving each object slightly different speed, scale, rotation, or other parameters
- generating unique
uid:values for each item in a group
propagate(...) runs before cue/animation parsing and writes a fully expanded DSL string into each child's data-oscilla. The children keep their own ids.
Syntax
propagate(
TEMPLATE,
ARG1,
ARG2,
...
)
• TEMPLATE
A new-DSL animation or cue definition, e.g.:
scale(values:[${1}, ${2}], mode:alternate, dur:${3}, uid:circs)
• ${n} placeholders
Inside the template, ${1}, ${2}, ${3}, ... are replaced with the evaluated values of ARG1, ARG2, ARG3, etc.
Each occurrence is evaluated independently, allowing fresh randomisation.
UID Handling
- If
uid:NAMEappears in the template → becomesuid:NAME_0,uid:NAME_1, … - If no
uid:is present → one is appended automatically:
uid:prop_GROUPINDEX_CHILDINDEX
Example: uid:prop_3_2
Example
Group of 6 circles with unique scale range and timing between 1–2:
propagate(
scale(values:[${1}, ${2}], mode:alternate, dur:${3}, uid:circs),
rnd(1,2), // ${1}: min scale
rnd(1,2), // ${2}: max scale
rnd(0.4,1.2) // ${3}: duration
)
Possible expansions (one per child, in its data-oscilla):
scale(values:[1.14,1.92], mode:alternate, dur:0.66, uid:circs_0)
scale(values:[1.83,1.21], mode:alternate, dur:1.07, uid:circs_1)
scale(values:[1.05,1.99], mode:alternate, dur:0.48, uid:circs_2)
...
Evaluation Rules
- Arguments (
ARG1,ARG2, …) may contain:rnd(min,max)— uniform random float, rolled once at expansionirnd(min,max)— uniform random integer (inclusive), rolled at expansionrnd([a,b,c])— pick one item (numbers or literals like colour names)Pseq(...)/Prand(...)patterns andrand(min,max)— passed through verbatim, so each child gets its own live pattern/random that keeps rolling at runtime- numeric literals
- string literals
- Each
${n}in the template triggers an independent evaluation ofARGn.
Compile-time vs runtime in one line:
propagate(scale(values:[${1},1], dur:${2}, uid:sq), rnd(0.8,1.2), Prand(0.5,1,2,inf))
// each child: a FIXED random start scale, and its OWN endlessly-rolling dur pattern
This enables effects like:
scale(values:[${1},${1}], dur:${2})
Where the two ${1} occurrences receive different random values.
Where it lives
On the group, in data-oscilla, like every other cue:
<g id="cluster-a" data-oscilla="propagate(scale(values:[1,${1}], uid:m), rnd(0.5, 2))">
The id stays a plain name. An SVG id is an XML ID and cannot contain (,
:, , or spaces, so DSL in an id is invalid markup — it only ever worked
because nothing validated it. Scores written the old way, with the template
in the id, still load exactly as before; the next save through the DSL editor
moves it to data-oscilla and leaves a plain id behind.
At runtime
Expansion happens in the browser when the score loads; nothing is written
back to the file. The group keeps its id, its template is parked in
data-oscilla-propagate-id (so data-oscilla is free for the group's own
cues), and a group that had no id is given propagate-<uid> from the
template's first uid:. Each child keeps its own id and carries its expanded
cue in data-oscilla, marked data-oscilla-propagate-child.
Editing in the DSL Editor
Above the template field, Presets offers common usages grouped by cue —
pick scale, fade, rotate, color or compound from the dropdown and
click a preset to fill the template and its argument rows in one go. Every
preset uses trig:playhead, so on a scrolling score nothing moves until the
playhead reaches it, and because each member triggers at its own position a
spread-out group comes alive as a ripple. A fresh uid suffix is added each
time, so two groups on the same preset never share member uids. Edit anything
afterwards; the preset is only a starting point.
Clicking any expanded child in the DSL editor opens the group's propagate template in a structured form:
- cue template(s) — the template text, with
${N}placeholders - Arguments — one row per
${N}expression, with chips for the common forms: rangernd(0.5, 2), intirnd(2, 6), choosernd([red, teal, orange]), patternPrand(0.5, 1, 2, inf), live randrand(0.5, 2), fixed - + add argument — adds a row and drops the matching
${N}at the template cursor - a live check that every
${N}used has an argument (and none go unused) - 🎲 example child — one rolled expansion, click to reroll
Saving re-expands the whole group with fresh random values, no reload needed.
A cue on the whole cluster
The group can carry cues of its own alongside the template — a rotate()
that spins the entire cluster while each member breathes and wanders inside
it, a fade() that brings the whole thing in, a pin() that holds it at the
playhead. In the DSL editor, open the group and add the cue: it is applied
in place rather than wrapping the group (a wrapper would give
propagate() exactly one child — the group — and the template would run once
instead of per member).
The two live in different attributes, which is what makes it possible:
<g id="cluster-a"
data-oscilla="propagate(scale(values:[1,${1}], trig:playhead, uid:m), rnd(0.5, 2))
rotate(dur:20, dir:1, trig:auto, uid:wholeSpin)"> … </g>
They share data-oscilla, so order is up to you, and saving the group writes
the whole attribute back. A second propagate() is the one exception —
nesting one cluster inside another wraps, as before.
Non-Recursive
Only direct children of the group are expanded.
Nested groups are left as-is unless you manually apply propagate to them.
Execution Order
propagate(svgElement) must run before any animation or cue parsing, typically inside initializeSVG():
// Expand propagate(...) groups before parsing
propagate(svgElement);
If used in page overlays, it must run before animationAssign(...) and cue scanning.
Supported Contexts
- Main score SVG
- Subpages / page mode SVG
- Cue-triggered dynamic page loads
When to Use
Use propagate(...) when:
- you have many similar shapes needing slight variation
- you want per-object timing/scale/rotation differences
- you want a template-based param generator for groups
- you want unique uids for each instance automatically
When Not to Use
Avoid propagate when:
- objects require coordinated timing (use shared seqdur instead)
- nested group structure must remain untouched
- ordering or indexing inside the SVG is unpredictable
Notes
propagate(...)is not parsed by the DSL directly.- It is a preprocessor that rewrites IDs into pure DSL expressions.
- After expansion, each child is parsed as if you wrote it manually.
Version Compatibility
This documentation refers to the new Oscilla DSL (2025) with:
- function-style cues
- named parameters
- canonical
uid:handling - no legacy
_uid(...)microsyntax
Examples
See the demo-propagate project: one-template basics, a placeholder
used twice (independent re-rolls), a compound color+scale glitch grid,
rnd([list]) colour choices with irnd() spins, runtime Prand
passthrough beside compile-time rnd(), and a closing SCATTER section
where one template makes every member breathe, spin and wander —
trans(to:[scatter, scatter, scatter], mode:loop) sends each square
touring its own random points inside the group's box.
http://localhost:8001/?project=demo-propagate
A pattern fill as members
An Inkscape pattern fill — polka dots, stripes, a tile of your own — is a
<pattern> in the document's defs that the shape merely refers to; nothing
in it is an element. Put a propagate() on the shape anyway:
<ellipse id="dots"
data-oscilla="propagate(scale(values:[1,${1}], dur:${2}, mode:alternate, trig:playhead, uid:pd), rnd(0.6,1.6), rnd(0.8,2.5))"
style="fill:url(#pattern123)" … />
At load Oscilla lays the tile out for real over the shape — a group of the
pattern's objects placed exactly where the pattern drew them, clipped to the
shape — and the template applies to every one of them: the dots breathe, the
ellipse keeps its stroke and loses its fill for the session. Nothing is
written to the score, so change the pattern's scale or spacing with
Inkscape's pattern tool and the dots follow on the next load. In the DSL
editor the shape offers propagate() like a group does, clicking a dot leads
to the template, and saving the template writes it back to the shape.
data-pattern-trim="inside" on the shape keeps whole objects whose centre is
inside instead of clipping; "none" keeps the whole lattice over the shape's
box. Only patternUnits="userSpaceOnUse" patterns are supported, which is
what Inkscape makes. For dots you want to hand-tune — delete one, recolour
one — the Inkscape extension Expand Pattern Fill to Objects does the same
expansion once, into the file.
Tip: use ← → or ↑ ↓ to navigate the docs