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:
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:
- macOS — visible in GarageBand, Logic, Ableton, etc. as "Oscilla"
- Linux — appears as an ALSA MIDI source; use a2jmidid for JACK
- Windows — use a virtual MIDI cable (e.g. loopMIDI) to route it
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:
- Into the tablet or laptop running the browser — the MIDI button in the topbar opens the MIDI panel, which asks the browser for WebMIDI access (Chrome, Edge, Electron; not Safari/iOS).
- Into the machine running the server — start the server with
--midi-in-port-index <n>or--midi-in-port <name>, and every message is relayed to all connected clients. A missing port warns and lists what exists.
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