speed()
Controls the playback scroll speed of the score.
The speed() cue sets the playback speed multiplier. Speed values are relative to the base speed set in project preferences. Speed changes may be applied instantly or gradually over time.
Syntax
Positional marker form (position watcher — crossing-based, direction-aware):
speed(<multiplier>)
speed(<multiplier>, uid:<id>, loop:<N>)
Keyed trigger form (fires like any other cue; supports ramping):
speed(
value:<multiplier>,
add:<offset>,
dur:<seconds>,
ease:<easing>,
uid:<id>,
revtrig:1
)
The leading bare number is what makes a marker: speed(2) and
speed(-1, loop:2) are markers; speed(value:2, dur:3) is a trigger cue.
Parameters
-
value
Speed multiplier relative to the base speed.
1= base speed,0.5= half base speed,2= double base speed.
Negative values play the score in reverse (see below). -
uid
Optional unique identifier. Give repeated markers with the same value (twospeed(1)elements, say) distinct uids so each has its own identity. -
loop (marker form)
The marker applies at most N times, then goes inert — the playhead passes straight through. See Bounded Loops. -
add (trigger form)
Relative adjustment applied to the current speed multiplier. -
dur (trigger form)
Duration of the speed change in seconds.
If omitted, the speed change is applied immediately.
dur:edgeties the ramp to the element's own drawn width instead of a time: the change starts as its left edge crosses the playhead and completes exactly at its right edge. Draw the rall as a group (or a box with an extent handle) and you can see on the score where the speed changes and how long it takes:<g id="speed(value:0.5, dur:edge, uid:rall1)"> … the rall region … </g>A width converted to seconds would drift — the ramp itself changes the speed, so the box takes more (or less) time to cross than the entry speed predicts.
dur:edgefollows the playhead's position across the box, so it stays visually exact, and scrubbing backwards inside the box walks the ramp back too. -
ease (trigger form)
Curve of the speed change, for timed anddur:edgeramps alike:linear(default),out,in,inout.
A linear rall is end-weighted in time: the fast part of the box flies by, so most of the ramp's duration happens in its slow tail (a 3→1 rall spends about a third of its time in the first half of the box and two thirds in the second).ease:outcounters that by making the change bite early;ease:indelays it further;inoutrounds both corners.speed(1, dur:edge, ease:out, uid:rall1) -
revtrig (trigger form)
Lets the trigger-form cue also fire when crossed during reverse playback:revtrig:1fires at the right edge (reverse entry),revtrig:2at the left edge (same spot as the forward trigger) — see Cue Triggering in Reverse. Markers don't need it — they are crossing-based and direction-aware by nature.
Base Speed Multiplier (Preferences)
The base speed multiplier is set in your project's preferences.json:
{
"defaultPlaybackSpeed": 0.3
}
All speed() cues multiply against this base value:
| Preference | Cue | Actual Speed |
|---|---|---|
0.3 |
speed(1) |
0.3 (1 × 0.3) |
0.3 |
speed(2) |
0.6 (2 × 0.3) |
0.3 |
speed(3) |
0.9 (3 × 0.3) |
1.0 |
speed(0.5) |
0.5 (0.5 × 1.0) |
This allows you to:
- Set a global tempo for the entire piece in preferences
- Use
speed()cues for relative tempo changes within the score - Adjust overall tempo without editing individual cues
Two Types of Speed Cues
1. Position-Based Speed Markers
Simple speed markers embedded in the SVG that apply instantly when the playhead crosses them:
speed(1)
speed(2)
speed(0.5)
The marker DSL can live in the element's id (authored directly in the
SVG) or in its data-oscilla attribute (where the DSL editor saves it).
Markers:
- Apply immediately when crossed
- Are position-aware (rewinding past a marker reverts to the previous speed)
- Automatically sync with the server
- Multiply against
baseSpeedMultiplier - Are handled exclusively by the position watcher — they are not
trigger cues, so
trig:/tdelay:etc. don't apply
2. Triggered Speed Cues
Keyed syntax with ramping support:
speed(value:2, dur:3)
These support gradual transitions and are triggered like other cues.
Adding dur: or add: is what makes a speed cue trigger-based.
Editing in the DSL editor
The DSL editor exposes all speed options
(value, uid, loop, dur, add, revtrig) and writes the correct
form automatically: with dur or add set it saves the keyed trigger
form, otherwise the positional marker form. Saving rebuilds the watcher
map immediately (no reload needed) and resets loop:N counters, so an
exhausted bounded loop re-arms.
Position Awareness
When you rewind or seek to a position before a speed marker, the system automatically applies the correct speed for that position:
Position: 0 -------- 5000 -------- 10000 -------- 15000
Markers: speed(1) speed(3) speed(1)
Playhead at 7000 → speed = 3 × base
Rewind to 3000 → speed = 1 × base (automatically updated)
This ensures consistent playback regardless of navigation direction.
Reverse Playback (negative speed)
A negative speed value scrolls the score backwards:
speed(-1) // reverse at normal rate
speed(-0.5) // reverse at half rate
speed(value:-1, dur:4) // ramp into reverse over 4 seconds
The playhead moves back through the score and clamps at the start. While
moving in reverse, speed markers are applied by crossing, in the direction
of travel: the last speed() cue the playhead crossed determines the
current speed. This makes region loops easy to author:
Position: ...... A ............ B ......
Markers: speed(1) speed(-1)
Forward playback crosses A (speed 1) then B (reverse).
Moving backwards it re-crosses A (forward again) → the
playhead loops the region between A and B indefinitely.
When a score repeats the same marker value (two speed(1) elements, say),
give each a uid: so every cue has a distinct identity:
speed(1, uid:loopA)
speed(-1, uid:loopB)
Bounded Loops — loop:N
A speed marker with loop:N applies at most N times, then goes inert —
the playhead passes straight through. On a reversing marker, loop:N means
N reversals, giving the loop a natural exit:
speed(1, uid:loopA) ......... speed(-1, loop:2, uid:loopB)
The region between A and B plays forward three times and backward twice, then playback continues past B. A full rewind to the start resets the counters so the loop replays.
speed() loops are palindromes — the score physically plays backwards
through the region. For classic jump-based repeats (instant cut back to a
marker, repeat-barline style) use nav() instead:
nav(scroll@A, repeats:3, uid:rep1)
Cue Triggering in Reverse
By default, cues only fire on forward crossings — reversing over a score
does not re-fire its cues. Audio-family region cues (audio, audioPool,
audioImpulse, synth) are the exception: they are region-based, so they
still sound while the playhead is inside their region from either direction.
A cue can opt in to reverse firing with revtrig. The value picks which
edge fires on the reverse crossing:
revtrig:1— fires at the right edge of the bounding box: the first edge the reversing playhead meets, mirroring the forward trigger's left-edge entry.revtrig:2— fires at the left edge: the exact same score location as the forward trigger, approached from the other side.
fade(target:out, dur:2, revtrig:1)
color(vals:[#f00, #0f0], dur:0.5, revtrig:2)
Each reverse crossing re-arms the forward trigger — so a playhead
ping-ponging over the cue keeps firing it.
Supported on animation cues (rotate, scale, fade, color), text,
metro, video, ui, and ext.
Behaviour
- Speed values multiply against
baseSpeedMultiplierfrom preferences - Position-based markers (
speed(x)) apply instantly when crossed - Triggered cues (
speed(value:…)) support ramping withdurparameter - Rewinding past a speed marker reverts to the previous speed
- Speed changes sync to all connected clients via the server
- A new speed cue replaces any previously active speed change
- Speed changes affect scrolling, timing, and all time-based cues
Examples
Simple Position Markers (in SVG)
speed(1)
Base playback speed.
speed(0.5)
Half of base speed.
speed(2)
Double base speed.
Triggered Cues with Ramping
speed(value:1.25, dur:3)
Gradually change to 1.25× base speed over 3 seconds.
speed(add:-0.5, dur:2)
Gradually reduce speed multiplier by 0.5 over 2 seconds.
speed(value:2, dur:6, revtrig:1)
Ramp to 2× base speed; also fires if crossed during reverse playback.
Project Setup
preferences.json
{
"defaultPlaybackSpeed": 0.3,
"duration_minutes": 10,
"darkMode": false
}
SVG Score
Place speed markers as elements with appropriate IDs:
<circle id="speed(1)" cx="100" cy="50" r="5" fill="blue"/>
<circle id="speed(3)" cx="5000" cy="50" r="5" fill="red"/>
<circle id="speed(1)" cx="10000" cy="50" r="5" fill="blue"/>
The visual element (circle, rect, path, etc.) marks the position; only the id matters for the cue system.
Server Synchronization
Speed changes are broadcast to all connected clients:
{
type: "set_speed_multiplier",
multiplier: 0.9, // actual speed (cue × base)
cueSpeed: 3, // raw cue value
baseMultiplier: 0.3, // from preferences
source: "position_watch"
}
The server maintains the authoritative speed state and syncs it at ~4Hz.
Debugging
Check speed state in browser console:
console.log("baseSpeedMultiplier:", window.baseSpeedMultiplier);
console.log("speedMultiplier:", window.speedMultiplier);
console.log("speedCueMap:", window.speedCueMap); // position-based markers
Watch speed changes in real-time:
[speedWatch] pos=5000, cue=3, base=0.3, actual=0.90, current=0.30
[speedWatch] ⚡ SPEED CHANGE: 0.30 → 0.90 (cue=3 × base=0.3)
Notes
- Speed cues modify global playback behaviour
- Only one speed is active at a time
- The performer sees only a cue symbol in the score
- The microsyntax lives in the SVG
idfield or thedata-oscillaattribute - Position-based speed is automatically restored when seeking/rewinding
- Base speed from preferences allows global tempo control
Demo
See demo-speed — speed changes as values and as ramps, and how they interact with the playhead.
http://localhost:8001/?project=demo-speed
Jumping into a speed region
The speed that applies after a jump — a rehearsal mark, the seek bar, the
overview strip, a nav() — is that of the last speed cue at or before where
the playhead lands, whether or not it was crossed on the way. A ramp
(speed(N, dur:…)) counts at its target, since that is what the region after
it runs at: jump from a section at speed 3 straight into one that follows a
speed(1, dur:3) and playback continues at 1.
Tip: use ← → or ↑ ↓ to navigate the docs