cue:o2p — Object-to-Path Animation
o2p(...) animates an SVG object along a named <path>.
It supports path traversal, directional modes, rotation modes, looping, OSC output,
partial-path motion, trigger-based activation, delayed start, visual pre-start states,
interactive touch/drag control, grouped fader presets with launcher bars,
pause-drag repositioning (spatial scores), live console hot-reload,
and multi-client sync.
BASIC FORM
o2p(path:<id>, dur:<seconds>, mode:fwd)
Required:
| Key | Meaning |
|---|---|
path |
The ID of the <path> element to follow |
TIMING
dur:<seconds>
Duration of one full traversal of the path.
o2p(path:orbit, dur:6)
In mode:alt, the full A→B→A cycle lasts dur seconds.
tdelay:<seconds>
Delays the start of the animation after the cue triggers.
o2p(path:orbit, tdelay:3)
tdelay is real-time and independent of score position.
SPEED CONTROL
bipolar:1
Enables bipolar speed mode for duration-bound animations. This transforms the speed fader from unipolar (slow→fast) to bipolar (reverse→stop→forward).
| Fader Position | Speed Effect |
|---|---|
| Left (0) | Maximum reverse speed (anticlockwise) |
| Center (0.5) | Stopped |
| Right (1) | Maximum forward speed (clockwise) |
o2p(path:orbit, dur:fader1.t[0.1,4], bipolar:1)
Use Cases:
- Bidirectional path animation with intuitive center-stop behavior
- Orbiting objects that can reverse direction
- Scratch/scrub-style controls for media
Without bipolar (default), the speed fader is unipolar:
- Left = slowest speed
- Right = fastest speed
- No reverse motion possible
INITIAL VISIBILITY
init:
Controls how the object looks before the animation begins.
| Value | Effect |
|---|---|
show |
Visible immediately (default) |
hide |
Hidden until triggered |
ghost |
Semi-transparent (30% opacity), no interaction |
fadein(seconds) |
Fade in over specified seconds |
armed |
Ghost opacity (0.7), click to start animation |
armed(opacity) |
Custom ghost opacity, click to start |
armed(opacity, fade) |
Custom opacity and fade duration, click to start |
The object is positioned at its correct starting location before motion begins.
o2p(path:orbit, tdelay:4, init:hide)
o2p(path:ring, dur:8, init:fadein(2), trig:playhead)
o2p(path:spiral, dur:12, init:armed(0.3))
DIRECTION MODES
| Mode | Meaning |
|---|---|
forward / fwd |
0 → 1 along path |
reverse / rev |
1 → 0 |
alternate / alt |
back-and-forth motion |
o2p(path:curve, mode:alt, dur:8)
Alternate and path shape
Alternate mode is path-shape-aware:
- Closed paths (circles, orbits — SVG paths ending with
Z): each direction wraps around the full loop.start:0.5means the object enters at the midpoint and orbits the full circle before reversing. - Open paths (lines, arcs): the object bounces between
startandendpositions. No wrapping.
PAUSE-DRAG REPOSITIONING
drag:1
Enables free drag repositioning when an armed animation is paused. Default is off.
o2p(path:orbit, dur:10, init:armed, drag:1)
Without drag (default): click pauses the animation in place, click again resumes
from the same position. Standard behaviour for musical scores.
With drag:1: click pauses, then the object can be dragged freely off the path.
On resume, the object snaps to the nearest point on the path and continues from there.
The original path range (start/end) is preserved — only the first cycle after drag
starts from the new position; subsequent cycles use the full original range.
This is designed for spatial audio scores where a performer repositions a sound source during a pause, then resumes the trajectory from the new location.
o2p(path:traj_spiral, dur:25, init:armed(0.7, 18), drag:1,
spatial:dome, bounds:dome_bounds, format:aed, osc:1,
oscAddr:"/iem/source/1")
PATH SEGMENTS
o2p(path:ring, start:0.2, end:0.8)
The object travels only between normalized positions start and end.
ROTATION
rotate: |
Description |
|---|---|
none |
no rotation |
aligned |
align to tangent direction |
locked |
fixed heading using rotlock |
spin |
continuous rotation (rotspeed, rotdir) |
Additional keys:
| Key | Meaning |
|---|---|
rotoffset |
add angular offset |
rotlock |
fixed heading in degrees |
rotspeed |
seconds per full rotation |
rotdir |
±1 direction multiplier |
LOOPING
o2p(path:orbit, loop:3)
o2p(path:orbit, loop:0) // infinite
OSC OUTPUT
o2p(path:orbit, osc:true)
Emits:
/o2p/<uid> <pathT> <normX> <normY> <angle>
Custom OSC address:
o2p(path:orbit, osc:true, oscAddr:myController)
Emits:
/myController <pathT> <normX> <normY> <angle>
TRIGGERING
| Trigger | Behavior |
|---|---|
trig:auto |
starts automatically in page mode |
trig:edge |
starts when the playhead intersects in scroll mode |
trig:touch |
no auto-animation; object is draggable along path |
Cues may also be activated programmatically.
tdelay applies after the trigger is detected.
TOUCH MODE (Interactive Control)
The trig:touch mode transforms the o2p animation into an interactive controller.
Instead of automatically animating, the object waits for user interaction.
o2p(path:fader, trig:touch, osc:true)
Behavior:
- Object is positioned at the start point (or
start:position) - No automatic animation occurs
- User can grab and drag the hit-label overlay
- Object follows the user's finger/mouse along the path
- If
osc:true, every movement emits OSC values
Use Cases:
- Browser-based OSC faders/sliders
- XY pads with constrained motion
- Rotary knobs (using circular paths)
- Custom UI controllers that follow any SVG path shape
Example — Linear Fader:
o2p(path:verticalLine, trig:touch, osc:true, oscAddr:fader1)
Example — Circular Knob:
o2p(path:knobArc, trig:touch, start:0.1, end:0.9, osc:true, oscAddr:knob1)
Visual Indicator: Touch mode objects display an orange hit-label ring (instead of purple) to indicate they are draggable.
FADER GROUPS AND PRESETS
Touch-mode faders can be collected into named groups. A group enables saving and recalling all fader positions as named presets, tweening between presets with easing, and sequencing preset recalls over time.
Grouping Faders
Add group:<name> and uid:<id> to each touch-mode fader:
o2p(path:track1, trig:touch, group:mixer1, uid:vol, osc:true)
o2p(path:track2, trig:touch, group:mixer1, uid:pan, osc:true)
o2p(path:track3, trig:touch, group:mixer1, uid:send, osc:true)
All three faders register under window._o2pTouchGroups["mixer1"] and can be
saved/recalled as a unit. The group value is freeform — any string.
Launcher Bar
When a group registers, a launcher bar appears below the group's bounding box. It provides direct access to presets and sequences without opening the full preset panel.
The launcher includes:
- Preset/Sequence slots — left-click to recall, long-press to store current positions
- Bank navigation — arrow buttons to page through banks of slots
- Mode toggle (P/S) — switch between preset and sequence slot modes
- Tween toggle (~) — smooth interpolation or instant jump on recall
- Sequence transport — play/stop buttons for sequence playback
- Settings gear — opens the full o2p Preset Manager panel
An orange toggle circle appears to the right of the group. Click it to show or hide the launcher bar.
Preset Manager Panel
The preset panel (keyboard shortcut Alt+Shift+O, or gear button on launcher) provides full preset management:
- Presets tab — save, recall, delete named presets with tween duration and easing
- Sequences tab — define ordered lists of presets with per-step timing
- Import/Export — save and load preset collections as JSON files
Console access: window.o2pPresetUI.toggle()
Preset Data
Each preset stores the normalized position of every fader in the group:
{
"mixer1": {
"vol": { "t": 0.75 },
"pan": { "t": 0.3 },
"send": { "t": 0.5 }
}
}
If a fader has a rotation handle (hmode:), a p value is also stored:
{ "vol": { "t": 0.75, "p": 0.6 } }
Console API
window.o2pPresets.save("intro") // save current positions
window.o2pPresets.recall("intro") // instant recall
window.o2pPresets.recall("intro", { dur: 2, ease: "easeInOutSine" }) // tween
window.o2pPresets.list() // list all preset names
window.o2pPresets.delete("intro") // delete a preset
// Sequences
window.o2pPresets.defineSequence("drift", [
{ preset: "intro", dur: 3 },
{ preset: "verse", dur: 2 },
{ preset: "chorus", dur: 4 }
], { loop: true })
window.o2pPresets.playSequence("drift")
window.o2pPresets.stopSequence()
ROTATION HANDLES
Touch-mode faders can have a secondary rotation handle that adds a second control dimension. This is useful for parameters like pan, filter cutoff, or any value that benefits from a rotary control alongside the linear fader.
hmode:<continuous|limited>
Determines the rotation behavior:
continuous— full 360-degree rotation with wraparound (suitable for azimuth, phase)limited— constrained arc with hard stops like a physical potentiometer (suitable for volume, gain)
rotrange:<degrees>
Sets the arc range for hmode:limited. Default is 270 degrees.
o2p(path:track1, trig:touch, group:mixer1, uid:vol, hmode:limited, rotrange:270, osc:true)
handle:<elementId>
Reference a user-drawn SVG element as the rotation handle instead of the auto-generated one:
o2p(path:track1, trig:touch, group:mixer1, uid:vol, hmode:limited, handle:knob1, osc:true)
Where knob1 is the ID of an existing SVG element in the score.
TARGET BUTTONS
Click-type o2p elements can control other faders in the same group using the
target parameter. This allows creating buttons that set specific fader
positions — useful for presets, mute/unmute toggles, or recall buttons.
Basic Target Button
Use target:<faderId> and targetT:<value> to create a button that sets
a fader to a specific position when clicked:
o2p(path:btnPath, trig:click, group:mixer1, target:vol, targetT:0.8)
When clicked, this sets the vol fader in mixer1 to t=0.8.
Toggle Button
Use toggle:<val1>,<val2> to alternate between two positions on each click:
o2p(path:btnPath, trig:click, group:mixer1, target:vol, toggle:0,0.8)
First click sets vol to t=0.8, second click sets it to t=0, and so on.
Example: Mute/Unity Buttons for a Mixer
<!-- Fader paths -->
<path id="track0" d="M 0,120 L 0,0" />
<path id="track1" d="M 30,120 L 30,0" />
<!-- Faders -->
<rect id="o2p(path:track0, trig:touch, group:mixer, uid:in0, osc:1)" ... />
<rect id="o2p(path:track1, trig:touch, group:mixer, uid:in1, osc:1)" ... />
<!-- Toggle button paths (minimal) -->
<path id="btn0" d="M 0,130 L 0,131" style="stroke:none" />
<path id="btn1" d="M 30,130 L 30,131" style="stroke:none" />
<!-- Toggle buttons: click to toggle fader between mute and 0dB -->
<rect id="o2p(path:btn0, trig:click, group:mixer, target:in0, toggle:0,0.8, osc:1)" ... />
<rect id="o2p(path:btn1, trig:click, group:mixer, target:in1, toggle:0,0.8, osc:1)" ... />
The toggle values 0,0.8 correspond to:
t=0— bottom of fader (mute)t=0.8— 80% position (0dB / unity gain with standard dB scaling)
Parameters
| Parameter | Description |
|---|---|
target:<id> |
The uid of the fader to control (must be in same group) |
targetT:<value> |
Set fader to this t value (0-1) on click |
toggle:<v1>,<v2> |
Alternate between two t values on each click |
Note: target requires group to be set. The target fader must also be
registered in the same group.
TOGGLE STATES (Interactive Claiming)
Toggle states provide an interactive claiming mechanism independent of OSC output. Performers can double-click to cycle through named states, with visual feedback and optional callbacks. This is useful for instrument assignment, ownership indication, or any multi-state interaction.
Basic Syntax
o2p(path:orbit, dur:10, toggleStates:[idle, claimed])
Parameters
| Key | Description |
|---|---|
toggleStates |
Array of state names to cycle through on double-click |
toggleColors |
Optional array of colors per state. Use #client for claiming client's color |
onToggle |
Cue expression to execute when state changes |
Toggle Colors
Define colors for each state:
o2p(path:orbit, dur:10,
toggleStates:[idle, claimed],
toggleColors:[default, #client])
| Color Value | Effect |
|---|---|
default |
Use the default overlay color |
#client |
Use the claiming client's assigned color |
#ff0000 |
Any hex color |
onToggle Callback
Execute a cue expression when the state changes. Supports variable substitution:
| Variable | Value |
|---|---|
$uid |
Animation UID |
$state |
New state index (0-based) |
$stateName |
New state name |
$clientIndex |
Client's 1-based index in playbackClientNames preference |
o2p(path:orbit, dur:10, uid:src1,
toggleStates:[idle, claimed],
toggleColors:[default, #client],
onToggle:"osc(addr:/source/claim, uid:$uid, state:$state, client:$clientIndex)")
State Persistence
Toggle states are:
- Persisted to localStorage per client
- Synced across all connected clients via WebSocket
- Restored on page reload
Independence from OSC
Toggle states are independent from osc:1. Having toggleStates does not
affect continuous OSC output. You can have claiming UI without OSC, OSC without
claiming, or both together.
LIVE CONSOLE EDITING
Running o2p animations can be modified in real time from the live console. Pick an element, edit DSL parameters in the editor, and evaluate.
What happens:
- If the animation is running, it restarts from its current position with
the new parameters (e.g. changed
dur,mode,ease). The transition is seamless — no stop/start needed. - If the animation is paused, the new parameters are stored and take effect when the performer clicks to resume.
- If the parameters haven't changed (e.g. playhead re-trigger), nothing happens.
Editable parameters: dur, loop, mode, ease, start, end, osc, oscAddr
Identity parameters (path, uid, init) cannot be changed mid-flight.
MULTI-CLIENT SYNC
Armed animations sync across connected clients via WebSocket:
- Start (armed → running): all clients start simultaneously
- Pause (running → paused): all clients pause
- Resume (paused → running): all clients resume, including drag position
With drag:1, the resume position is transmitted so remote clients restart
from the same path location.
Limitation: late-joining clients see armed elements but do not auto-start running animations. A connected client must stop and restart to bring late joiners into sync.
LIVE UPDATE (uid:)
o2p(path:ring, uid:a1)
o2p(uid:a1, mode:alt)
Later cues with the same uid modify running animations.
FULL PARAMETER LIST
| Key | Description |
|---|---|
path |
required path reference |
dur |
motion duration |
mode |
direction mode |
loop |
repeat count (0 = infinite) |
start, end |
normalized path range |
rotate |
rotation behavior |
rotoffset, rotlock, rotspeed, rotdir |
rotation parameters |
ease |
easing preset |
osc |
OSC emission |
oscAddr |
custom OSC address (without leading /) |
uid |
animation identity tag |
trig |
trigger mode (auto, edge, touch) |
tdelay |
time delay after trigger |
init |
initial visibility: show/hide/ghost/armed |
drag |
enable pause-drag repositioning (default: off) |
bipolar |
bipolar speed mode: center=stop, left=reverse, right=forward (default: off) |
spatial |
spatial mapping mode: dome, quad (see spatialisation docs) |
bounds |
ID of SVG element defining spatial bounds |
format |
spatial coordinate format: aed, xyz |
group |
fader group name (touch mode only) |
hmode |
rotation handle mode: continuous or limited (touch mode only) |
rotrange |
arc range in degrees for hmode:limited (default 270) |
handle |
ID of user-drawn SVG element to use as rotation handle |
launcher |
show launcher bar for group (default: true, set false to suppress) |
toggleStates |
array of state names for double-click cycling |
toggleColors |
array of colors per state (#client for client color) |
onToggle |
cue expression executed on state change |
EXAMPLES
o2p(path:orbitA, dur:8, tdelay:3, init:hide)
o2p(path:spiral, rotate:aligned, rotoffset:-90)
o2p(path:ring, start:0.2, end:0.9, mode:alt)
o2p(path:circle, rotate:spin, rotspeed:2, rotdir:-1)
// Touch mode controllers
o2p(path:faderTrack, trig:touch, osc:true, oscAddr:volume)
o2p(path:knobPath, trig:touch, start:0.15, end:0.85, osc:true)
// Grouped faders with preset system
o2p(path:track1, trig:touch, group:mixer1, uid:vol, osc:true)
o2p(path:track2, trig:touch, group:mixer1, uid:pan, osc:true)
o2p(path:track3, trig:touch, group:mixer1, uid:send, osc:true)
// Grouped fader with rotation handle
o2p(path:track1, trig:touch, group:mixer1, uid:vol, hmode:limited, rotrange:270, osc:true)
// Grouped fader with user-drawn rotation handle
o2p(path:track1, trig:touch, group:mixer1, uid:vol, hmode:continuous, handle:knob1, osc:true)
// Spatial score with pause-drag repositioning
o2p(path:traj_spiral, uid:src1, dur:25, mode:fwd, ease:linear,
init:armed(0.7, 18), drag:1, spatial:dome, bounds:dome_bounds,
format:aed, osc:1, oscAddr:"/iem/source/1")
// Armed alternate without drag (standard score)
o2p(path:path2, mode:alt, dur:5, loop:0, ease:linear, init:armed)
// Bipolar speed control (center=stop, left=reverse, right=forward)
o2p(path:orbit, dur:speedFader.t[0.5,8], bipolar:1, loop:0)
Tip: use ← → or ↑ ↓ to navigate the docs