Events

Handlers

Turn a note message into state, then let the audio loop turn that state into sound.

Anyone connecting a keyboard or controller to a processor.

An event and an audio sample live at different speeds. A keyboard may send one note-on and one note-off while an oscillator produces thousands of samples. The handler remembers what the event said; the frame loop uses that memory until another event changes it.

That division is the basic pattern: events update state, audio reads state. The handler does not need to generate the entire note when the message arrives.

Read the handler declaration#

on noteIn (note n) { ... }
PartMeaning
noteInThe input event endpoint this handler receives from.
noteThe payload type, matching the endpoint declaration.
nA local name for the current event's fields, such as n.pitch.
BodyWork to perform when the host delivers an event. There is no returned audio value.

You do not call on as a function. Declare the endpoint and the handler inside the processor, then let the host deliver messages. Each input event endpoint must have exactly one handler; missing one is a compiler error.

A complete event-driven voice#

A note message becomes a soft attack and releasePlayground
processor EventVoice
{
    input event note noteIn;
    output stream float out;
    param float release = 0.25f [0.01f, 2.0f] smooth 10.0f;

    float frequency = 220.0f;
    float velocity = 0.0f;
    float gate = 0.0f;
    float envelope = 0.0f;
    float phase = 0.0f;

    on noteIn (note n)
    {
        gate = n.on ? 1.0f : 0.0f;
        if (n.on)
        {
            frequency = n.pitch;
            velocity = n.velocity;
        }
    }

    void main()
    {
        loop
        {
            let seconds = gate > 0.5f ? 0.005f : release;
            let coefficient = exp(-1.0f / (seconds * processor.frequency));
            envelope = gate + (envelope - gate) * coefficient;
            let next = phase + frequency / processor.frequency;
            phase = next - floor(next);
            out <- sin(6.2831853f * phase) * envelope * velocity * 0.25f;
            advance();
        }
    }
}

graph EventInstrument
{
    input event note notes;
    param float release = 0.25f [0.01f, 2.0f];
    output stream float out;
    node voice = EventVoice;

    connection
    {
        notes -> voice.noteIn;
        release -> voice.release;
        voice.out -> out;
    }
}

The small graph at the end connects the host's note endpoint to the voice's handler and exposes its release control. This explicit event route is needed by the current browser runtime; a bare event processor can compile without providing the route the in-page keyboard needs.

On note-on, the handler stores the frequency and velocity, and raises the gate. The audio loop moves the envelope toward 1 with a 5 ms time constant. On note-off, only the gate falls; retaining frequency and velocity lets the existing tone fade naturally. A note-off payload need not repeat a useful pitch, so the handler deliberately does not copy it into the oscillator.

release is a time constant in seconds. At one time constant the released envelope has about 37% of its initial value left; at three it has about 5%. It approaches zero gradually rather than stopping at an exact deadline.

Identity and expression#

This small voice is monophonic and follows the events routed to it. It does not allocate voices or decide which overlapping key should win. Use a host allocator and node arrays for polyphony.

A note event can also be an expression update. If a more advanced envelope restarts on note-on, inspect n.kind so a pressure update does not restart it. The payload reference lists the field types and kind values.

What belongs in a handler?#

Set note identity, targets and envelope state here. Keep continuous oscillator and filter updates in the frame loop. A bounded for or while is allowed, so a handler can perform a finite search, but it cannot call advance(), contain the audio loop, or use every.

A processor can receive events but cannot emit them yet. In the current browser runtime, delivery occurs at block boundaries; native timeline rendering can place events before the exact sample they address.

Try it: hold a note, release it, then increase release and repeat. The handler receives the same kind of message both times; the frame loop turns that remembered gate change into different audible tails.