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:

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

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

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:

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


When to Use

Use propagate(...) when:


When Not to Use

Avoid propagate when:


Notes


Version Compatibility

This documentation refers to the new Oscilla DSL (2025) with:


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