Skip to content

MIDI

Midi reads live MIDI input from a device — a USB or virtual MIDI keyboard — as normalized events. Enumerate the input ports, open one, and poll the note / controller / pitch-bend events each frame. The events are drained off the device’s callback thread onto the main thread by the engine, so Midi.poll() is always safe to call. All Midi methods are static. Powered by libremidi; introduced by engine PLM-135 (ADR 0044).

The note source is arbitrary — feed these events to a SoundFontInstrument, to gameplay, or to anything else.

Desktop only. On a build without a MIDI backend every call is a safe no-op (no devices, never opens, no events).

Returns: List of device-name Strings (possibly empty), index-aligned with openInput.

Open a MIDI input. Closes any currently-open input first.

Parameters:

  • device — a Num index into inputDevices, or a String matched as a substring of a device name.

Returns: Bool — true if a port was opened.

Close the open input (a safe no-op if nothing is open).

Returns: Bool — whether an input is currently open.

Returns: String — the name of the open device, or "".

Returns: List of event Maps received since the last call (drained each frame). Each map has:

KeyTypeMeaning
typeString"noteOn", "noteOff", "controlChange", "programChange", "pitchBend", "channelPressure", "polyPressure", "clock", or "other"
channelNumMIDI channel 015
data1Numnote / controller / program (payload byte 1)
data2Numvelocity / value (payload byte 2)
value14Numpitch bend 016383 (8192 centered), for pitchBend

Payload meaning by type: noteOn/noteOffdata1 = note, data2 = velocity; controlChangedata1 = controller, data2 = value; programChangedata1 = program; pitchBendvalue14.

import "engine" for Midi
class Game {
init() {
if (Midi.inputDevices.count > 0) Midi.openInput(0)
}
update(dt) {
for (ev in Midi.poll()) {
if (ev["type"] == "noteOn") {
System.print("note %(ev["data1"]) vel %(ev["data2"]) on ch %(ev["channel"])")
}
}
}
quit() { Midi.closeInput() }
}

Feed incoming MIDI events straight to a SoundFontInstrument in the engine — the native, low-latency route. After opening a device, this one call makes a MIDI keyboard play the instrument (note on/off, control change, pitch bend, program change) with no per-note Wren. The events still reach poll() for gameplay.

Stop routing (also cleared automatically if the routed instrument is collected).

import "engine" for Midi, SoundFontInstrument
var piano = SoundFontInstrument.new("soundfonts/piano.sf2", 48000, 2)
if (Midi.inputDevices.count > 0 && Midi.openInput(0)) {
Midi.route(piano) // the keyboard now plays the SoundFont
}
  • Main-thread safe. The device delivers bytes on its own thread; the engine parses and queues them there, then hands them to poll() on the main thread. Your Wren code never runs on the device thread.
  • Poll every frame. Events accumulate between poll() calls (bounded), so poll each frame to stay current.
  • Drive an instrument. Route noteOn/noteOff/controlChange/pitchBend straight into a SoundFontInstrument to play a MIDI keyboard through a SoundFont.