Notes and events
An endpoint that carries a record at a frame rather than a number every sample.
Anyone whose patch should respond to a keyboard.
An endpoint carries either a number every sample (stream) or a record at a
frame (event).
A stream is a value you can read on every frame, like a voltage on a wire. An event is a message: “this key was pressed,” “this key was released,” or “the pressure changed.” The message has named fields, and a handler chooses which of them to remember.
Use streams when continuous values fit your patch naturally. Use events when you need note identity, several related values together, or exact native event timing. Both approaches still produce audio in the same frame loop.
Declare an endpoint and its handler#
input event note noteIn;
float gate = 0.0f;
float frequency = 220.0f;
on noteIn (note n)
{
gate = n.on ? 1.0f : 0.0f;
if (n.on) { frequency = n.pitch; }
}noteIn is the endpoint name and n is the local name for each payload.
This fragment shows the receiving side; the next chapter
uses the stored values in a complete playable voice.
Note payload#
| Field | Pole type | Meaning |
|---|---|---|
pitch | float | Frequency in Hz. Preserve the last sounding pitch when releasing a voice. |
velocity | float | Normalized note velocity, 0…1. |
pressure | float | Normalized expression pressure, 0…1. |
brightness | float | Normalized timbre expression, 0…1. |
channel | int | MIDI channel identity supplied by the host. |
key | int | MIDI key identity; distinct from frequency in Hz. |
kind | int | 1 = note-on, 2 = note-off, 3 = expression, 4 = voice steal. |
on | Condition | Whether the note is active. Use in if, or convert with n.on ? 1.0f : 0.0f. |
legato | Condition | Host-supplied legato status. |
on and legato are conditions, not numeric floats: if (n.on) works and
n.on * 2.0f does not. kind distinguishes a new note from an expression
update, so changing pressure need not restart the attack.
Controller and bend payloads#
| Payload | Field | Pole type | Meaning |
|---|---|---|---|
cc | channel | int | MIDI channel identity. |
cc | controller | int | Controller number; for example, 1 for the mod wheel. |
cc | value | float | Normalized controller value, 0…1. |
bend | channel | int | MIDI channel identity. |
bend | value | float | Normalized bend, -1…1. The host's chosen pitch range is a separate convention. |
Declare these with input event cc controlIn; or
input event bend bendIn;, and provide a matching handler. The field types
come from the compiler and host's shared event schema.
Timing#
A handler runs between the two samples either side of its frame: every event stamped at frame i executes before sample i is rendered, and several at one frame run in the order they were submitted.
A note therefore lands where it was played, rather than on the next block boundary.
The batch is validated in full before anything renders, so a malformed late event cannot leave a processor half-advanced or a note-on with no note-off. A frame at or past the block's length is refused rather than clamped, because it belongs to the next block.
Note In the browser, events are applied at block boundaries rather than on the stamped frame, so a note lands within about 3 ms of where it was asked for. That is a limit of the browser runtime, not of the language.
Events reserve no words#
event, on, note, cc and bend are all recognised by position, so
let on = gate > 0.5f; and let note = 60.0f; keep working. Both already
appear in shipped code.
An event endpoint occupies no slot#
Declaring one between two streams does not move the second one's slot, so a block can gain an event input without changing anything about how its numbers are addressed.
Streams or events?#
Both work, and they are different tools:
- Streams — the
pitch/gate/velocityconvention described under endpoints — need no handler and are what most patches on this site use. - Events give you the note as a record, with channel, key and MPE expression, at the frame it happened.