Reusable Blocks with use(name)
use(name) allows you to define a visual or interactive structure once and reuse it anywhere in the score or on any page. This avoids copy/paste in Inkscape and keeps layouts consistent.
Defining a Reusable Block
Any SVG group whose ID begins with:
reuse-
is treated as a reusable block.
Example:
<g id="reuse-mainMenu">
<!-- Buttons, text, shapes, animations -->
</g>
This block is registered automatically when its SVG is loaded.
Using the Block Elsewhere
To insert the reusable block somewhere else, place a placeholder group:
<g id="use(mainMenu)"></g>
When the score initializes, the placeholder is replaced with a cloned instance of the reusable block, preserving:
- cueButtons
- cueText elements
- animations and object-followers
- internal transforms and visual structure
The placeholder itself is removed.
Placement Modes
There are two placement modes depending on how use() is written:
| Syntax | Placement Behaviour |
|---|---|
use(name) |
Inserts the block at its original authored coordinates (as designed in its source SVG). |
use(name)@self |
Inserts the block at the location of the placeholder, inheriting the placeholder’s transform. |
Example (@self Placement)
<g transform="translate(300,200)">
<g id="use(mainMenu)@self"></g>
</g>
Result: mainMenu appears exactly at (300,200).
Behaviour and Guarantees
- The injected block receives unique IDs to avoid collisions.
- The placeholder
<g id="use(...)" />is removed automatically. - Reusable blocks can be defined in any SVG inside
/pages/. - All internal behaviour is preserved, including:
- cue triggers
- animations (rotate, scale, obj2path)
- cue buttons and overlays
Summary
Define once:
<g id="reuse-name">…</g>
Use anywhere:
<g id="use(name)"></g>
Or place at placeholder position:
<g id="use(name)@self"></g>
This system keeps Oscilla scores modular, clean, and visually authored directly in Inkscape—no handwritten layout code required.
Folders of fragments — use(path:...)
A block does not have to live inside a score. use(path:...) pulls whole SVG
files out of the project's blocks/ folder and inlines them:
use(path:gestures) the first file
use(path:gestures, index:2) the third file (0-based)
use(path:gestures, index:rand(0,3)) a different one every load
use(path:gestures, index:Pseq(0,1,2,inf)) stepped across placeholders
Put the files anywhere under blocks/ in the project directory:
myscore/
score.svg
blocks/
gestures/
spin.svg
pulse.svg
turn.svg
The files are listed in name order, and index: selects one. An index past the
end wraps rather than failing, so removing a file cannot blank a score that
indexed past it.
The fragments are score, not pictures
This is the difference from image(path:...), which
also accepts a folder of SVGs. image() renders a file through <image href>
— a replaced element, whose content never enters the document. Any cue DSL
inside it is invisible and nothing in it can animate.
use() inlines the file. Its cues are ordinary cues from that moment on:
<!-- blocks/gestures/spin.svg -->
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="160" viewBox="0 0 200 160">
<rect id="fragSpin" data-oscilla="rotate(uid:fragSpin, dur:4)"
x="70" y="40" width="60" height="60" fill="#2980b9"/>
</svg>
Dropped into a score with use(path:gestures), that rectangle spins like any
other rotate().
Note where the cue lives. Typing it into the id is the Inkscape shortcut and
Oscilla still reads it, but a fragment is written once and inlined many times —
so an id carrying the DSL is repeated verbatim into every copy, and ids are
meant to be unique. Anything generated by a tool should write data-oscilla
and leave the id as the plain uid:. See authoring.
Expansion happens before cue assignment, so a fragment's cues are picked up on the same pass as the rest of the score.
Each fragment is inserted as a nested <svg> sized to the placeholder, so it
keeps its own coordinate system — author the file at whatever size suits it and
its viewBox scales it into the slot.
A trailing bare number is a repeat count, not an index.
Prand(0,1,2)means "0 and 1, twice" in every pattern in the DSL, and quietly drops the 2. WritePrand(0,1,2,inf)to choose between all three. Oscilla warns when it sees a repeat count on ause()index, since it is almost never meant.
Limits
- Selection happens once, at load.
index:cannot follow a live signal yet — swapping an already-inlined fragment means tearing down its cues, bindings and animations first. - uids are not namespaced. Two placeholders showing the same file give
two elements with the same uid. Use
index:to show different files, or give each fragment its own uids.
Demo
See demo-reuse — one navigation menu written once and injected into every page.
http://localhost:8001/?project=demo-reuse
Tip: use ← → or ↑ ↓ to navigate the docs