Early Preview · Kiwisonic and Kiwisynth are free during early preview. The shape of both products is still being figured out, and feedback is what drives them forward.

Script handlers

All docs / Kiwisynth / Script handlers

Kiwisynth manual

Script handlers

A script reacts to events. Each event has its own handler, written as on <eventname> opening a block that closes with end. In Scoped mode the editor handles the on ... end wrapper for you; this page shows the assembled form.

Two handlers, note and release, run per voice. The rest run globally: there is one instance of the handler, not one per voice. Per-voice handlers see the voice's own voice var slots; global handlers do not.

Every handler can read the always-available clock variables bpm, time, and playing. The event-specific variables are listed under each handler.

For the language itself, the parameter primitives, and the editor, see the scripting overview.

on init

Fires once, on the first audio block after the script is applied (or the patch is loaded). Use it to log a startup line, prime a global counter from a tuning constant, or warm up state.

Bound variables: bpm, time, playing.

on init
    log("script loaded")
end

There is no matching shutdown handler. State that needs resetting is reset by re-applying the script.

on note

Fires per voice, at note-on, after the voice has been claimed but before audio for that note has rendered. Use it to make decisions that depend on which note was played and how hard.

Bound variables: pitch (0..127), velocity (0..127), channel (MIDI channel of the source note), plus the clock variables.

on note is the only handler where a voice var is initialised fresh. Inside its body, the voice variables are at their declared defaults; they keep their values across later on tick-style events only for this voice's lifetime.

on note
    if velocity > 100
        ramp("filt_cutoff", 4000, 80)
    end
end

A script-fired play(...) does not trigger another on note. The handler runs for notes received from the keyboard or the host only, not for notes the script itself starts.

on release

Fires per voice, at note-off. The voice is still alive at this point; it is in its release tail. Use it to schedule echoes, drop a parting modulation, or count releases per pitch.

Bound variables: pitch, channel, plus the clock variables. There is no velocity here because note-off does not carry release velocity in this version.

on release
    after 200
        play(pitch + 12, 50, 120)
    end
end

pitch is the note that was released, which is the same note the matching on note saw.

on tick

Fires globally, once per audio block. This is the tightest cadence a script can run at. The block length depends on the host's buffer size; a typical project hits this handler a few hundred times per second.

Bound variables: phase (0..1, the script clock's position within the current beat), index (block counter since script load), time (seconds since load), plus the clock variables.

Use on tick to shape modulation that the LFOs cannot, or to advance a step sequencer that the arpeggiator does not cover.

on tick
    # Asymmetric pulse: 70% high, 30% low.
    if phase < 0.7
        modulate("filt_cutoff", 2000)
    else
        modulate("filt_cutoff", -1000)
    end
end

on tick is global. To do per-voice work on every block, work from on note plus voice var plus after.

on beat

Fires globally on each musical beat boundary. The host's tempo and position drive the timing, so the handler is bar-locked across loops and bounces.

Bound variables: phase (0..1 within the beat at the moment the beat fires; usually zero, but non-zero if a block crosses a beat boundary), index (beat counter since load), plus the clock variables.

Use on beat for sequencers and step-counters. The handler is the right home for play calls that should land on the grid.

on beat
    # Two rhythms at once.
    if index % 4 == 0
        play(36, 110, 200)
    end
    if index % 6 == 0
        play(43, 80, 150)
    end
end

When the host is stopped, on beat does not fire. Beat counting is tied to host transport.

on cc(N)

Fires globally when a MIDI CC with number N arrives on any channel. N is part of the header: on cc(1) is the mod wheel, on cc(7) is channel volume, on cc(74) is the filter-cutoff CC. A script can have several on cc handlers, one per CC number.

Bound variables: value (0..1, the CC value normalised), channel, plus the clock variables.

Use on cc to turn a CC into something the mod matrix cannot do: fire a chord on a threshold, switch a discrete parameter, draw a non-linear curve.

on cc(1)
    if value > 0.5
        play(60, 100, 200)
        play(64, 100, 200)
        play(67, 100, 200)
    end
end

The CC also reaches the mod matrix in the normal way. The script handler runs in addition.

on pitchbend

Fires globally when the pitch wheel moves. Use it to draw a non-linear bend curve on top of the synth's built-in linear bend.

Bound variables: value (-1..+1, where 0 is centre), channel, plus the clock variables.

on pitchbend
    modulate("master_tune", value * value * value * 1200)
end

The synth's own pitch-bend handling still applies. The script's contribution adds on top, so a cube curve here gives a centre-soft, edge-aggressive bend without disabling the built-in one.

on aftertouch

Fires globally on channel aftertouch (the pressure value from many keyboards after a key is held). Polyphonic aftertouch is a separate modulation source in the mod matrix; this handler is the channel-pressure one.

Bound variables: value (0..1), channel, plus the clock variables.

on aftertouch
    if value > 0.85
        play(72, 60, 80)
    end
end

Aftertouch is a modulator in the mod matrix; turning it into a note trigger or a discrete switch is script territory.

on transport

Fires globally on the edges of the host transport: when the host starts playing or stops. Use it to reset script state at the start of a section, or to log when the host moved.

Bound variables: playing (true on a start edge, false on a stop edge), bpm.

on transport
    if playing
        note_count = 0
        log("transport started")
    end
end

on transport does not fire continuously while the host runs. It fires at the edges only. For "every block while the host runs", use on tick and read playing.