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).
Devices
Section titled “Devices”Midi.inputDevices
Section titled “Midi.inputDevices”Returns: List of device-name Strings (possibly empty), index-aligned with openInput.
Midi.openInput(device)
Section titled “Midi.openInput(device)”Open a MIDI input. Closes any currently-open input first.
Parameters:
device— aNumindex intoinputDevices, or aStringmatched as a substring of a device name.
Returns: Bool — true if a port was opened.
Midi.closeInput()
Section titled “Midi.closeInput()”Close the open input (a safe no-op if nothing is open).
Midi.isOpen
Section titled “Midi.isOpen”Returns: Bool — whether an input is currently open.
Midi.openedDevice
Section titled “Midi.openedDevice”Returns: String — the name of the open device, or "".
Events
Section titled “Events”Midi.poll()
Section titled “Midi.poll()”Returns: List of event Maps received since the last call (drained each frame). Each map has:
| Key | Type | Meaning |
|---|---|---|
type | String | "noteOn", "noteOff", "controlChange", "programChange", "pitchBend", "channelPressure", "polyPressure", "clock", or "other" |
channel | Num | MIDI channel 0–15 |
data1 | Num | note / controller / program (payload byte 1) |
data2 | Num | velocity / value (payload byte 2) |
value14 | Num | pitch bend 0–16383 (8192 centered), for pitchBend |
Payload meaning by type: noteOn/noteOff → data1 = note, data2 = velocity; controlChange →
data1 = controller, data2 = value; programChange → data1 = program; pitchBend → value14.
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() }}Playing a SoundFont from a keyboard
Section titled “Playing a SoundFont from a keyboard”Midi.route(instrument)
Section titled “Midi.route(instrument)”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.
Midi.clearRoute()
Section titled “Midi.clearRoute()”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/pitchBendstraight into aSoundFontInstrumentto play a MIDI keyboard through a SoundFont.
Related
Section titled “Related”- SoundFontInstrument — turn MIDI events into audio.
- Example: MIDI Monitor — list devices and watch incoming events.