Skip to main content

Minimal Linux ALSA Raw MIDI output, with Python bindings via PyO3

Project description

minimidi

CI

Minimal Linux ALSA Raw MIDI output, written in Rust (PyO3), fully typed for Python 3.13+.

minimidi does one thing: open an ALSA Raw MIDI device (/dev/snd/midiC*D*) write-only and send MIDI bytes to it. No input, no sequencer scheduling, no SysEx framing, no cross-platform abstraction — just note-on / note-off / CC helpers and a raw-bytes escape hatch, shipped as a single abi3 wheel with no system dependencies.

  • Linux only (ALSA Raw MIDI).
  • Output only.
  • Typed. PEP 561 py.typed + hand-written stubs; clean under mypy --strict.
  • Thread-safe. One port can be shared across Python threads; writes serialise internally and release the GIL across the write(2) syscall.

See AGENTS.md for developer / agent workflow instructions.

Install

pip install minimidi
# or
uv add minimidi

Prebuilt abi3 wheels for x86_64 and aarch64 manylinux (Python ≥3.13) — no Rust toolchain or system packages needed. To install from source instead (needs Rust stable):

pip install git+https://github.com/ModularDevicesMalleableAudio/minimidi

Quick start

import minimidi

with minimidi.RawMidiOut("VirMIDI", device=0) as port:
    port.note_on(channel=1, note=60, velocity=100)   # middle C on
    port.cc(channel=1, controller=74, value=64)      # filter cutoff
    port.note_off(channel=1, note=60)                # note off (velocity 0)
# port auto-closes on context exit

Channels are 1-indexed (1–16), matching common MIDI convention. Notes, velocities, controllers, and values are 0–127. Out-of-range arguments raise MinimidiError before anything is written.

Finding your card

Ports are addressed by ALSA card id (the bracketed name in /proc/asound/cards) plus a raw device number:

import minimidi

minimidi.list_cards()
# [(0, "PCH"), (1, "VirMIDI"), (3, "pisound")]

minimidi.find_card("pisound")
# 3 — raises MinimidiError if not present

port = minimidi.RawMidiOut("pisound", device=0)
# opens /dev/snd/midiC3D0 write-only

Card-id matching is exact and case-sensitive (ALSA's bracket padding is trimmed). device is typically 0–3 for snd-virmidi; a non-existent device raises FileNotFoundError at open time.

Escape hatch: raw bytes

Messages without a first-class helper (pitch bend, program change, aftertouch, ...) can be written verbatim:

channel = 1
port.send_bytes(bytes([0xE0 | (channel - 1), 0x00, 0x40]))  # pitch bend, centre

send_bytes accepts only bytes (not bytearray/memoryview/str), length 1–1024, and performs no MIDI semantic validation — the caller owns correctness.

Errors and recovery

Condition Exception
Unknown card id, out-of-range argument, write on closed port MinimidiError
Device node missing, no permission, device disappeared mid-write FileNotFoundError / PermissionError / BrokenPipeError (standard errno-derived OSError subclasses)
Non-bytes payload to send_bytes TypeError

A failed write does not close the port: is_open means "not explicitly closed", and a retry raises the same OS error again. After device loss (USB hot-replug, module reload), construct a new RawMidiOut — that re-resolves the card number from /proc/asound/cards, which is correct when the kernel may have re-numbered the card. Closed is final: there is no open()/reopen method.

Testing without hardware

Load the kernel's virtual Raw MIDI card and minimidi can talk to it like any other device:

sudo modprobe snd-virmidi        # persistent: echo snd-virmidi | sudo tee /etc/modules-load.d/virmidi.conf
port = minimidi.RawMidiOut("VirMIDI", device=0)

Development

uv sync --all-groups               # venv + dev deps
uv run maturin develop --release   # build the Rust extension into the venv
./scripts/test.sh                  # cargo test + ruff + mypy + pytest

Integration tests (tests/integration/) need snd-virmidi loaded and aconnect (alsa-utils) installed; they skip cleanly otherwise.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

minimidi-0.1.0.tar.gz (58.6 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

minimidi-0.1.0-cp313-abi3-manylinux_2_28_x86_64.whl (242.5 kB view details)

Uploaded CPython 3.13+manylinux: glibc 2.28+ x86-64

minimidi-0.1.0-cp313-abi3-manylinux_2_28_aarch64.whl (232.9 kB view details)

Uploaded CPython 3.13+manylinux: glibc 2.28+ ARM64

File details

Details for the file minimidi-0.1.0.tar.gz.

File metadata

  • Download URL: minimidi-0.1.0.tar.gz
  • Upload date:
  • Size: 58.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for minimidi-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f51a0883265d8d9be6469f19c6df89d6d307e20e5b1592d89ab9f7ca2d3673c5
MD5 d401a439a246bb0f4ec9f5b432c78808
BLAKE2b-256 0c033e96de6b6a863f823ef9240fc4006bd1f161da29d08152944d91b5af1589

See more details on using hashes here.

Provenance

The following attestation bundles were made for minimidi-0.1.0.tar.gz:

Publisher: release.yml on ModularDevicesMalleableAudio/minimidi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file minimidi-0.1.0-cp313-abi3-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for minimidi-0.1.0-cp313-abi3-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 81e3e9ec52bae3085a7424680a46af6493a8da075e58291bb49e76508d32ed16
MD5 c0a0b47333559674f675ae585d8c4ec4
BLAKE2b-256 e974fbe4d882f1dc5677c7002da0f7ee658e79e10b103b6bd7625561fd55b829

See more details on using hashes here.

Provenance

The following attestation bundles were made for minimidi-0.1.0-cp313-abi3-manylinux_2_28_x86_64.whl:

Publisher: release.yml on ModularDevicesMalleableAudio/minimidi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file minimidi-0.1.0-cp313-abi3-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for minimidi-0.1.0-cp313-abi3-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 265a3836ff41e9e52cf3744a6e6c9bd170816cde96eb83e07e5f884d640f4319
MD5 c1d89d63f318df4d54b97077df716d60
BLAKE2b-256 fd8ee94da11ad58d53dcf19694cead81ab29e69aa4be8f818a6bfd5b6afed434

See more details on using hashes here.

Provenance

The following attestation bundles were made for minimidi-0.1.0-cp313-abi3-manylinux_2_28_aarch64.whl:

Publisher: release.yml on ModularDevicesMalleableAudio/minimidi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page