cue:midi — MIDI Note Output

midi(...) sends a MIDI note-on (and optional note-off) when triggered.

It is event-based, not continuous. It turns drawn objects into discrete MIDI note events, delivered via a virtual MIDI port (or a chosen hardware port) that any DAW, synthesiser, or MIDI-capable application can receive.

It works especially well with propagate() — a scatter of shapes in Inkscape becomes a chord or melodic phrase with pitch derived from each element's Y position.


BASIC FORM

midi(ch:, pitch:y, vel:size)

Parameters:

Key Meaning


ch MIDI channel 1–16 (default 1) pitch Pitch source: y maps Y position to note range (see below) vel Velocity source: size maps element size to 1–127 dur Duration in seconds — triggers note-off after this delay lo Lowest MIDI note for pitch:y mapping (default 36 = C2) hi Highest MIDI note for pitch:y mapping (default 84 = C6)


MIDI OUTPUT

Oscilla opens a virtual MIDI port named Oscilla on startup.

Connect any DAW, synth plugin host, or hardware bridge to this port:

To target a hardware port instead of the virtual port:

node server.js --midi-port-index 0

Run node server.js once to see available port indices in the console.


PITCH MAPPING

gate:0 — release a held note

A midi() cue with no dur: starts a note and leaves it sounding. gate:0 sends the note-off, which is how you pair two buttons into a hold and release:

button(label:"DRONE ON",  trigger:midi(ch:2, pitch:36, vel:96))
button(label:"DRONE OFF", trigger:midi(ch:2, pitch:36, gate:0))

vel:0 does the same thing, following the MIDI convention that a note-on at velocity zero is a release.


pitch:y — position-to-note

Y position is mapped linearly to a MIDI note range. Use lo: and hi: to set the range:

midi(ch:1, pitch:y, vel:size) # default lo:36 hi:84 midi(ch:1, pitch:y, lo:48, hi:96, vel:size) # C3–C7 midi(ch:1, pitch:y, lo:60, hi:72, vel:size) # C4–C5 (one octave)

Top of viewport = highest note. Bottom = lowest note.

Fixed note

midi(ch:1, note:60, vel:size) # always middle C


VELOCITY MAPPING

vel:size element visual size → 1–127 vel:64 fixed velocity (integer value, 1–127)


NOTE DURATION

Without dur:, only a note-on is sent. The receiving instrument sustains until it receives its own note-off (or you handle release externally).

With dur:, a note-off is sent automatically after the given time:

midi(ch:1, pitch:y, vel:size, dur:0.5)


TRIGGERS

Key Meaning


trig:auto Send immediately (default) trig:playhead Fire when playhead crosses the element trig:click Fire on user click


USE WITH propagate()

propagate() expands one cue template to every child element in a group, sampling each element's visual properties independently at trigger time.

propagate( midi(ch:1, pitch:y, vel:size, dur:0.3, trig:playhead) )

A cluster of circles becomes a chord. A diagonal scatter becomes a melodic phrase. Vertical position controls pitch; element size controls velocity.

Multiple channels

propagate(midi(ch:1, pitch:y, vel:size, dur:4, trig:playhead)) propagate(midi(ch:2, pitch:y, lo:24, hi:48, vel:size, dur:4, trig:playhead))

Use different channels to route to separate instruments in your DAW.


CLICK BUTTONS

midi(ch:10, note:36, vel:100, trig:click) # kick on ch10

Combined with a visible SVG rectangle, this creates a clickable pad that fires a MIDI note on demand.


MIDI MESSAGE FORMAT

Each trigger sends one or two raw MIDI messages:

Note-on: [0x90 | (ch-1), note, velocity] Note-off: [0x80 | (ch-1), note, 0] (only when dur: is set)

Note and velocity are clamped to 0–127.


SERVER FLAGS

Flag Default Description
--midi-port-index <n> — Open hardware port by index instead of virtual
MIDI_PORT_NAME env var Oscilla Name of the virtual port

SUMMARY

midi() converts visual positions into MIDI note events, routing through a virtual port that any DAW or instrument can receive — with pitch from Y position and velocity from element size.

MIDI INPUT

Oscilla can also receive MIDI, from a pedal or controller — the point being a footswitch that turns pages or fires cues while both hands are busy.

Where the device is plugged in decides the route, but not the behaviour:

Either way the message arrives as note, cc or pc with a channel, and fires on the "on" edge only — note-on, CC ≥ 64, any program change — so a sustain pedal or footswitch presses once per stomp and releasing does nothing.

Mapping a pedal to actions

In the MIDI panel, click Learn next to an action, then press the pedal once. Actions: play/pause, next or previous rehearsal mark, turn page (PageDown/PageUp — skips a page countdown or leaves a page), and fire cue by id, which triggers any cue element by its id, click-triggered ones included. Transport and page actions run the same bindings the keyboard uses, so a pedal mapped to "turn page" behaves exactly like the PageDown key. Mappings are saved in that browser.

Cues that listen themselves

Any animation cue can be triggered directly by a MIDI message, mirroring trig:osc:

rotate(dur:2, loop:0, trig:midi, midi:note60)     note 60, any channel
fade(mode:in, dur:1, trig:midi, midi:cc64@1)       CC 64 on channel 1
trans(to:[wpB], dur:3, trig:midi, midi:pc3)        program change 3

midi: takes note, cc or pc, a number 0–127, and an optional @channel (1–16). Without a channel, any channel matches. The spec is read from the cue's arguments, so it works on rotate, scale, color, trans, o2p and fade alike; the inspector offers it under trig.

Demo

See demo-midi-send — notes, channels and velocity driven from the score.

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

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