Endpoints
Inputs, outputs, side channels, endpoint arrays, and the five dimensions of a note.
Anyone wiring a patch to a host, a DAW or a keyboard.
An endpoint is a wire in or out of a processor. Reading an input gives you this frame's value from the host or another node. Writing an output gives the host or the next node a result. The name is yours to choose; the declaration states its direction, shape and role.
Audio and control streams use the same per-frame arithmetic. What makes a stream “pitch” or “level” is the contract between the processor and its host, not a different numeric type.
input stream float pitch;
input stream float vel;
output stream float<2> out;Inputs are read by name; outputs are written with <-. Reading an output or
writing an input is an error (POLE0301, POLE0302).
The first output is the audio#
Use output stream float out; for mono or output stream float<2> out;
for stereo. A stereo value carries left in lane 0 and right in lane 1.
Declaring two separate scalar outputs does not give the same interface: the
second endpoint is a side channel.
Every output after the first is a side channel — a level for a meter, a frame for a spectrum, a tap for an effects send. The host reads them by name and never mixes them into the audio.
output stream float<2> out; // the audio
output stream float level; // a meter reads this
output stream float busTap; // an effects send reads thisThis was capped at two outputs outright until it became clear that a scope, a send bus and a signal probe are all the same thing — an endpoint that is not a channel — and that all three were therefore unwritable.
Endpoint arrays#
An input may be an array, which is what makes polyphony writable:
input stream float voicePitch[8];
input stream float voiceGate[8];
var mix = 0.0f;
for (wrap<8> v) { mix = mix + voicePitch[v] * voiceGate[v]; }
out <- mix * 0.05f;Eight voices is one declaration and a loop, not sixteen declarations. Sizes are powers of two and the index is masked, exactly like state arrays.
Inputs are effectively unbounded — the cap is 4096 slots, sized from the largest real thing that has to fit rather than from a guess about what is reasonable.
A note has five dimensions#
When a patch is played live, inputs are fed in declaration order from the host's controls. A note is not one number:
| index | meaning |
|---|---|
| 0 | pitch in Hz — and it holds through the release |
| 1 | velocity, 0–1 |
| 2 | mod wheel (CC 1), 0–1 |
| 3 | gate — 1 while held, 0 once released |
| 4 | pressure, 0–1 — aftertouch |
| 5 | brightness, 0–1 — MPE timbre (CC 74) |
| 6–13 | pitch per voice, 8 voices |
| 14–21 | velocity per voice |
| 22–29 | gate per voice |
| 30–37 | pressure per voice |
| 38–45 | brightness per voice |
polec --note-layout prints these rather than anything mirroring them.
Why gate is separate from pitch
The gate used to be pitch — pitch > 0 meant a note was on — on the
reasoning that it is how a modular synth would wire it. That is a 1970s
control-voltage convention, and here it was wrong in a way you could hear: a
note-off zeroed the pitch, so the oscillator stopped while the envelope was
still releasing, and every release tail came out as DC instead of a fading
tone.
Silencing a voice is the envelope's job. So pitch holds and gate falls.
Pressure and brightness are MPE's two expression axes. Without them a patch cannot respond to a controller anyone currently owns.
Monophonic and polyphonic#
A monophonic patch reads indices 0–5 and ignores the rest. A polyphonic one
declares voicePitch[8] / voiceGate[8] and loops over them; the host
allocates voices from MIDI with last-note stealing.
Offline, --controls=0.9,0.5 sets endpoints 1..N to fixed values for a render.
If you would rather receive real note events than read a CV convention, the language has those too — see events.