Score

cue:osc --- Discrete OSC Event Cue

osc(...) sends a single OSC message when triggered.

It is event-based, not continuous. It turns drawn objects into discrete control events, typically consumed by environments like SuperCollider, Pd, or Max.

It works especially well with propagate(), because a simple drawing can become many individual OSC triggers.


BASIC FORM

osc(addr:, :, ...)

Required:

Key Meaning


addr OSC address name (without leading /)

Example:

osc(addr:voice, pitch:y, env:size)


EVENT BEHAVIOUR

osc() is a logical cue, not an animation cue.


VISUAL PARAMETER SOURCES (NORMALISED)

All visual sources are normalised to 0–1.

Source Meaning


x Object centre X position y Object centre Y position size Max(width, height) mapped to viewport scale Current visual scale value rotation 0--360 mapped to 0--1 opacity Computed opacity fill Numeric colour hash

Example:

osc(addr:voice, pitch:y, amp:size)

Here the Y-axis produces a continuous control pitch, interpreted downstream.


ONLY WHAT IS ON SCREEN — onscreen:

The playhead is a line through the whole height of the score, so zoomed in, a cue crossing it above or below the visible part still fires. onscreen:1 makes a cue fire only if its element is on screen at that moment:

osc(addr:star, pitch:0.4, trig:playhead, onscreen:1)

It works on any playhead-fired cue, not only osc(). A cue held back counts as fired for that pass; a re-entrant one (osc, midi, synth) fires again the next time it crosses while visible.

The same can be switched on for a whole client: Preferences → UI → "Trigger only on screen" (stored on this machine, not in the project). It covers the cues that sound or animate and leaves score control — pause, zoom, nav, page, stop, speed, stopwatch, metro, ui — to fire wherever its marker is. onscreen:0 exempts a cue from the preference. With it on, each performer's zoom and pan decide what they play.


FIXED CONTROL PITCH — pitch:<number>

pitch:0.63 sends a fixed 0–1 value in the same form as pitch:y (pitchType 4), so a receiver built for pitch:y needs no change. Use it when an object's note should not depend on where it happens to be on screen — zoomed, panned or rotated:

osc(addr:star, pitch:0.63, env:size, trig:playhead)


SEMANTIC PITCH VALUES

Beyond visual values, osc() supports typed pitch declarations.

Hz (absolute)

osc(addr:voice, pitch:hz(440))

MIDI note number

osc(addr:voice, pitch:midi(60))

Scale degree + octave

osc(addr:voice, pitch:deg(5,3))

Represents:

The receiver chooses how to interpret the scale.


OPTIONAL ROOT OVERRIDE

You may specify a root in the cue:

osc(addr:voice, pitch:deg(2,4), root:48)

If omitted, the receiver uses its global default (~oscRoot in SC).


PITCH PRIORITY

When multiple forms are present, resolution is:

  1. deg(d,o) (scale-based symbolic pitch)
  2. hz() / midi() (absolute pitch)
  3. visual mapping (e.g., pitch:y)

Only one pitch representation is used per event.


NOTE EVENTS — TWO APPROACHES

Pair two elements: an on-rect and an off-rect positioned at the note boundaries. Both use pitch:y so moving them in Inkscape changes pitch. Scaling the containing group on X adjusts timing without affecting pitch or note-off alignment.

gate:1 and gate:0 are appended as the last OSC arg. No setTimeout is used. Generated automatically by scripts/midi-to-svg.js.

dur: — timer-based

When dur: is supplied, a single element sends note-on at trigger time and note-off after dur beats via setTimeout. Useful for hand-authored cues where element geometry doesn't represent note duration.

osc(addr:voice, pitch:midi(60), dur:1.5, trig:playhead)

Caveat: scaling the element in Inkscape after the fact does not update the baked-in dur: value.


AMPLITUDE OVERRIDE (amp:)

amp: accepts a static 0–1 value that overrides the visual size field in the OSC payload. Useful when notes are generated from MIDI velocity rather than element geometry.

osc(addr:voice, pitch:midi(64), amp:0.75, dur:0.5, trig:playhead)


TRIGGERS

Key Meaning


trig:auto Send immediately trig:playhead Fire when playhead intersects trig:click Fire on user click prestate:ghostClickable Arm interaction ahead of time

Example:

osc(addr:voice, pitch:deg(0,4), trig:playhead)


INTERACTIVE BUTTON MODE (click:1)

Adding click:1 to any osc() element turns it into a clickable button. An invisible HTML overlay is placed over the SVG element; clicks trigger the cue. The SVG element itself provides the visual appearance.

BYPASS

toggle:1 — binary toggle with explicit state

toggle:1 makes the button cycle between two states. A green outline appears when the button is on. Crucially, unlike a plain trigger, a toggle button sends the new state explicitly as 0 or 1 (not always 1).

osc(addr:"arp/v0/mute", click:1, toggle:1)

SuperCollider — SET state, do not toggle blindly:

// ✓ Correct — sets state from explicit value OSCdef(\mute, { |msg| ~muted = msg[1] > 0.5; }, '/oscilla/arp/v0/mute');

// ✗ Wrong — will desync if any message is lost OSCdef(\mute, { |msg| ~muted = ~muted.not; // never do this with toggle:1 }, '/oscilla/arp/v0/mute');

Because msg[1] is the authoritative new state, a single missed or duplicated message will not cause permanent desync. The next click always resynchronises both sides.

radio:groupName — exclusive selection

Radio buttons form a group where only one can be active at a time. Clicking a radio button activates it and deactivates all others in the same group. The active button gets a green outline.

<rect data-oscilla="osc(addr:"gong/a/lp", click:1, radio:filterA)" .../> <rect data-oscilla="osc(addr:"gong/a/bp", click:1, radio:filterA)" .../> <rect data-oscilla="osc(addr:"gong/a/hp", click:1, radio:filterA)" .../>

Each radio button fires its own OSC address when selected. SC sets the parameter to a fixed value per button (not toggle logic):

OSCdef(\lp, { |m| ~synth.set(\filterType, 0) }, '/oscilla/gong/a/lp'); OSCdef(\bp, { |m| ~synth.set(\filterType, 1) }, '/oscilla/gong/a/bp'); OSCdef(\hp, { |m| ~synth.set(\filterType, 2) }, '/oscilla/gong/a/hp');

labels:'OFF_TEXT,ON_TEXT' — two-state button text

When combined with toggle:1, swaps the text of the next sibling SVG <text> element each time the button is clicked.

<rect data-oscilla="osc(addr:&quot;arp/v0/mute&quot;, click:1, toggle:1,
             labels:'PAUSE,PLAY')"
      x="189" y="928" width="90" height="55" rx="6"
      style="fill:#666666"/>
<text x="234" y="959" text-anchor="middle" fill="white">PLAY</text>

Initial state: buttons with labels: initialise in the on state (toggle on, green outline, showing the on-label). This means the SVG initial text should be the on-label (e.g. PLAY). The corresponding SC state should also start in the "on" (e.g. paused) condition so both sides agree without a first-click resync.

group:groupName — preset group registration

Registers the button with a named o2p fader group so its state is captured and restored when presets are saved and recalled.

osc(addr:"arp/v0/mute", click:1, toggle:1, group:arpFaders, labels:'PAUSE,PLAY')

The group name must match the group: parameter used on the fader elements (o2p(path:..., group:arpFaders, ...)). On preset recall, toggle buttons are restored by programmatically clicking them, which also fires the OSC message to resync SC.

Combined example — full mute button

<!-- Voice 0 MUTE — starts paused (PLAY showing), click to run, click again to pause -->
<rect
  data-oscilla="osc(addr:&quot;arp/v0/mute&quot;, click:1, toggle:1, group:arpFaders, labels:'PAUSE,PLAY')"
  x="189" y="928" width="90" height="55" rx="6"
  style="fill:#666666;stroke:#444444;stroke-width:2"/>
<text x="234" y="959" font-size="17px" font-weight="700"
      text-anchor="middle" fill="white" pointer-events="none">PLAY</text>

SuperCollider side:

// Start paused (matches button initial on-state)
~players[0].pause;
~muted[0] = true;

OSCdef(\arp_v0_mute, { |msg|
    ~muted[0] = msg[1] > 0.5;
    if (~muted[0]) { ~players[0].pause } { ~players[0].resume };
}, '/oscilla/arp/v0/mute');

Parameter reference

Parameter Values Description
click:1 1 Make SVG element clickable via invisible overlay
toggle:1 1 Binary toggle; sends explicit 0 or 1 to SC
radio:name string Exclusive radio group name
labels:'A,B' 'off_text,on_text' Two-state sibling text update
group:name string Register with o2p preset group

VISUAL FEEDBACK — flash:

flash:<scale> makes the element swell to <scale> and brighten to full opacity each time the message is sent, then settle back. It follows the OSC itself, so it also shows when an animation (e.g. a rotating group) carries the element into a stopped playhead. flashdur:<beats> sets the length (default 0.35).

osc(addr:star, pitch:y, env:size, trig:playhead, flash:3) propagate(osc(addr:grain, pitch:y, env:size, trig:playhead, flash:2, flashdur:0.5))


UID (OPTIONAL)

osc(addr:voice, pitch:y, uid:v1)

UID can be used downstream to associate events with voices/players/etc.


OSC OUTPUT FORMAT (AUTHORITATIVE)

Ready-made receivers for Pd, Max and SuperCollider, plus the address table for every other OSC-sending object, are on OSC Output.

osc() sends positional arguments, not keyed pairs — the layout below, unless args: gives the arguments itself.

YOUR OWN ARGUMENTS — args:[…]

osc(addr:parallaxis/p1/event, args:[5, 0.092], trig:ph1) osc(addr:hit, args:[2, y, size, rand(0,1), "low"])

The message is exactly that list, in that order: numbers as written, rand() drawn each time it fires, a visual source (x, y, size, scale, rotation, opacity, fill, width, height, area, aspect) sampled as it fires, and any other word as a string. gate:/dur: still append the gate, and pos:1 the frame position.

GENERAL PACKET FORMAT (without dur:)

/oscilla/ pitchType, pitchA, pitchB, size, env, density, [optional root]

PACKET FORMAT WITH dur: (note events)

/oscilla/ pitchType, pitchA, pitchB, size, env, density, [optional root], gate

Where gate is 1 for note-on and 0 for note-off. All other fields are identical between the two messages.

Where:

Field Meaning


pitchType 0=none, 1=deg/oct, 2=hz, 3=midi, 4=y-control pitchA meaning depends on pitchType pitchB octave (deg mode only) size 0--1 (overridden by amp: if supplied) env 0--1 density 0--1 (area-derived) root optional per-event root override gate 1=note-on, 0=note-off (present when gate: or dur: set)

SuperCollider decoding for piano-roll scores (pitchType 4 = y-control):

~pitchMin = 24; ~pitchMax = 108; midiNote = (~pitchMin + msg[1] * (~pitchMax - ~pitchMin)).round; gate = msg[msg.size - 1]; // 1 = on, 0 = off

Examples

Control pitch from Y

/oscilla/voice 1 5 3 0.41 0.41 0.12

Absolute pitch

/oscilla/voice 2 440 0 0.18 0.22 0.10

Degree + octave + root override

/oscilla/voice 1 5 3 0.33 0.20 0.15 48

Receivers are expected to decode according to this positional layout.


USE WITH propagate()

propagate( osc( addr:voice, pitch:y, env:size, trig:playhead ) )

Degree constellation examples

propagate( osc( addr:pontalist, pitch:deg(irand(0,11), irand(0,2)), env:size ) )

propagate( osc( addr:pontalist, pitch:deg(${1}, ${2}), env:size, root:48 ), rnd([0,2,4,5,7,9,11]), rnd([0,1,2,3,4,5]) )


DESIGN NOTES


SUMMARY

osc() converts visual gestures into one-shot OSC control events, supporting symbolic, absolute, and control-driven pitch --- while leaving musical semantics to the instrument.

Demo

See demo-osc-send — OSC output from cues, control lanes and animations.

http://localhost:8001/?project=demo-osc-send

Where the object is — pos:

pos:1 adds two values to the end of the message: where the object sits inside its osc frame, from -1 to 1 about the frame's centre, y up.

… osc(addr:"star/Lyr", pitch:y, pos:1, trig:playhead, uid:st)

/oscilla/star/Lyr [pitchType, pitchA, pitchB, size, env, density, posX, posY]

This is not the screen position sampleVisual() reports, and the difference is the point. An object triggered as it crosses the playhead is, by definition, at the playhead when it fires — on screen every one of them shares the same x, so screen position says nothing about which object spoke. Measured in the frame, each keeps its own place in the drawing however the view is scrolled, zoomed or turned.

Which frame you name decides whether the position turns with the drawing. Name a frame outside a rotate() and the position rotates with it; name one inside and it holds still. A star map turning overhead can send either the star's fixed place in the sky or where it has been carried to, by marking one group or the other — no change to the cue.

A receiver can read the pair as a direction: atan2(posY, posX) for azimuth and hypot(posX, posY) for distance from the centre. Where a drawing is an azimuthal projection — a polar star map, a plan of a room — that is already a bearing and an elevation, so the same message serves a stereo desk and a 24-speaker dome without the score changing. See Spatialisation.

root: is appended before posX, when both are present.

Tip: use ← → or ↑ ↓ to navigate the docs