Skip to content

midi

tested on simulator

Tier Tier 1
Protocol MIDI 1.0 over USB MIDI class, MIDI 1.0 over DIN through an interface, virtual MIDI ports
Covers pad and knob controllers, keyboards, synthesizers, control surfaces with LEDs and motorised faders, DIY USB MIDI boards
Hazard default low
Conformance control
Tested with fake mido backend with a simulated pad controller, real mido messages

Estimated reach. Thousands of models. Every class-compliant USB MIDI device works without a vendor driver on Linux, macOS and Windows, and MIDI 1.0 has been the standard for controllers and instruments since 1983. Any device whose MIDI implementation chart lists its CCs and notes can be described in a device file. Devices that only work through vendor SysEx are not covered.

The midi driver reads knobs, faders and pads from MIDI controllers and sends notes and control changes to light pad LEDs, move motorised faders and set synth parameters. The driver uses mido with python-rtmidi on ALSA, CoreMIDI or WinMM.

Supports

  • Control Change, Note On and Off, Program Change and Pitch Bend, both directions.
  • Bool reads are a CC at 64 or above or a held note. A note write on a number channel sends the value as velocity, often the LED colour.
  • discover() lists ports, and describe() returns a channel for every control moved while listening.

Setup

uv tool install --compile-bytecode "halkit[midi] @ git+https://github.com/sohan-bhat/OpenHAL"

Where python-rtmidi has no wheel, pip builds the package with a C++ compiler, plus libasound2-dev on Linux. backend picks another mido backend such as mido.backends.portmidi.

ParameterDefaultMeaning
nameautoPart of the port name, any case. auto takes the only port. midi: "LPD8" is shorthand
input, outputnoneExact port names when input and output differ
channel1Default MIDI channel, 1 to 16
backendrtmidimido backend module
timeout2 sSend deadline per message

Channel maps

read:
  knob_1: { unit: percent, stream: true, map: { cc: 1 } }
  pad_1: { type: bool, stream: true, map: { note: 36 } }
  bend: { unit: percent, map: { pitchwheel: true } }
write:
  pad_1_led: { type: bool, map: { note: 36 } }
  pad_2_colour: { unit: dimensionless, min: 0, max: 127, map: { note: 37 } }
  cutoff: { unit: percent, min: 0, max: 100, map: { cc: 74, channel: 2 } }
KeyDefaultMeaning
cc, notenoneController or note number, 0 to 127. Give exactly one of cc, note, program: true or pitchwheel: true
programfalseProgram Change
pitchwheelfalsePitch Bend
channelthe device'sMIDI channel, 1 to 16
velocity127Velocity for bool note writes
on, off127, 0CC values for bool CC writes

dimensionless channels carry the raw value, 0 to 127 or -8192 to 8191 for pitch bend. percent maps 0 to 100 onto 0 to 127, or -100 to 100 onto pitch bend, rounded, so 50 % reads back as 50.4 %.

Controllers can't be queried, so reads return the newest message since connecting, or None until the control first moves. Write channels report the last value sent.

Example

openhal/drivers/midi/examples/lpd8-pads.hal.yaml reads two knobs and a pad of an Akai LPD8 and drives two pad LEDs.

Errors and health

  • No matching port, several matches, a port that fails to open and out-of-range raw values raise DriverError, with nothing sent.
  • health() checks that the ports are still listed, so an unplugged controller shows disconnected.

Limitations

  • No SysEx, NRPN, RPN, 14-bit CC pairs, aftertouch or MIDI 2.0.
  • Timestamps are arrival times.
  • No actions. Model each message as a write channel.

Hazard default and safe state

Low. safe_state_support is file, sent like any message. A MIDI device driving something physical, such as a motorised fader or a DMX bridge, needs a higher hazard.

Sources