Minimal Linux ALSA Raw MIDI output, with Python bindings via PyO3
Project description
minimidi
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 undermypy --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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f51a0883265d8d9be6469f19c6df89d6d307e20e5b1592d89ab9f7ca2d3673c5
|
|
| MD5 |
d401a439a246bb0f4ec9f5b432c78808
|
|
| BLAKE2b-256 |
0c033e96de6b6a863f823ef9240fc4006bd1f161da29d08152944d91b5af1589
|
Provenance
The following attestation bundles were made for minimidi-0.1.0.tar.gz:
Publisher:
release.yml on ModularDevicesMalleableAudio/minimidi
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
minimidi-0.1.0.tar.gz -
Subject digest:
f51a0883265d8d9be6469f19c6df89d6d307e20e5b1592d89ab9f7ca2d3673c5 - Sigstore transparency entry: 1791925777
- Sigstore integration time:
-
Permalink:
ModularDevicesMalleableAudio/minimidi@b10292310dab63c67ddad15b158fc55b75b8ee21 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ModularDevicesMalleableAudio
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b10292310dab63c67ddad15b158fc55b75b8ee21 -
Trigger Event:
push
-
Statement type:
File details
Details for the file minimidi-0.1.0-cp313-abi3-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: minimidi-0.1.0-cp313-abi3-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 242.5 kB
- Tags: CPython 3.13+, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
81e3e9ec52bae3085a7424680a46af6493a8da075e58291bb49e76508d32ed16
|
|
| MD5 |
c0a0b47333559674f675ae585d8c4ec4
|
|
| BLAKE2b-256 |
e974fbe4d882f1dc5677c7002da0f7ee658e79e10b103b6bd7625561fd55b829
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
minimidi-0.1.0-cp313-abi3-manylinux_2_28_x86_64.whl -
Subject digest:
81e3e9ec52bae3085a7424680a46af6493a8da075e58291bb49e76508d32ed16 - Sigstore transparency entry: 1791926041
- Sigstore integration time:
-
Permalink:
ModularDevicesMalleableAudio/minimidi@b10292310dab63c67ddad15b158fc55b75b8ee21 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ModularDevicesMalleableAudio
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b10292310dab63c67ddad15b158fc55b75b8ee21 -
Trigger Event:
push
-
Statement type:
File details
Details for the file minimidi-0.1.0-cp313-abi3-manylinux_2_28_aarch64.whl.
File metadata
- Download URL: minimidi-0.1.0-cp313-abi3-manylinux_2_28_aarch64.whl
- Upload date:
- Size: 232.9 kB
- Tags: CPython 3.13+, manylinux: glibc 2.28+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
265a3836ff41e9e52cf3744a6e6c9bd170816cde96eb83e07e5f884d640f4319
|
|
| MD5 |
c1d89d63f318df4d54b97077df716d60
|
|
| BLAKE2b-256 |
fd8ee94da11ad58d53dcf19694cead81ab29e69aa4be8f818a6bfd5b6afed434
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
minimidi-0.1.0-cp313-abi3-manylinux_2_28_aarch64.whl -
Subject digest:
265a3836ff41e9e52cf3744a6e6c9bd170816cde96eb83e07e5f884d640f4319 - Sigstore transparency entry: 1791925896
- Sigstore integration time:
-
Permalink:
ModularDevicesMalleableAudio/minimidi@b10292310dab63c67ddad15b158fc55b75b8ee21 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ModularDevicesMalleableAudio
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b10292310dab63c67ddad15b158fc55b75b8ee21 -
Trigger Event:
push
-
Statement type: