minimidi 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- minimidi-0.1.0/.github/workflows/ci.yml +50 -0
- minimidi-0.1.0/.github/workflows/release.yml +61 -0
- minimidi-0.1.0/.gitignore +28 -0
- minimidi-0.1.0/.python-version +1 -0
- minimidi-0.1.0/AGENTS.md +45 -0
- minimidi-0.1.0/CHANGELOG.md +30 -0
- minimidi-0.1.0/CLAUDE.md +45 -0
- minimidi-0.1.0/Cargo.lock +133 -0
- minimidi-0.1.0/Cargo.toml +14 -0
- minimidi-0.1.0/LICENSE +21 -0
- minimidi-0.1.0/PKG-INFO +148 -0
- minimidi-0.1.0/PLAN.md +1404 -0
- minimidi-0.1.0/README.md +127 -0
- minimidi-0.1.0/pyproject.toml +59 -0
- minimidi-0.1.0/python/minimidi/__init__.py +17 -0
- minimidi-0.1.0/python/minimidi/_minimidi.pyi +112 -0
- minimidi-0.1.0/python/minimidi/py.typed +0 -0
- minimidi-0.1.0/scripts/test.sh +60 -0
- minimidi-0.1.0/src/lib.rs +420 -0
- minimidi-0.1.0/tests/conftest.py +75 -0
- minimidi-0.1.0/tests/integration/conftest.py +127 -0
- minimidi-0.1.0/tests/integration/test_roundtrip.py +56 -0
- minimidi-0.1.0/tests/test_cards.py +66 -0
- minimidi-0.1.0/tests/test_context_manager.py +77 -0
- minimidi-0.1.0/tests/test_messages.py +72 -0
- minimidi-0.1.0/tests/test_os_errors.py +81 -0
- minimidi-0.1.0/tests/test_send_bytes.py +96 -0
- minimidi-0.1.0/tests/test_stubs.py +67 -0
- minimidi-0.1.0/tests/test_threading.py +87 -0
- minimidi-0.1.0/tests/test_validation.py +120 -0
- minimidi-0.1.0/uv.lock +326 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
rust:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
14
|
+
with:
|
|
15
|
+
components: clippy, rustfmt
|
|
16
|
+
- uses: Swatinem/rust-cache@v2
|
|
17
|
+
- run: cargo fmt --check
|
|
18
|
+
- run: cargo clippy --all-targets -- -D warnings
|
|
19
|
+
- run: cargo test --release
|
|
20
|
+
|
|
21
|
+
python:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
strategy:
|
|
24
|
+
fail-fast: false
|
|
25
|
+
matrix:
|
|
26
|
+
python-version: ["3.13", "3.14"]
|
|
27
|
+
experimental: [false]
|
|
28
|
+
include:
|
|
29
|
+
# Advance warning if a future Python breaks PyO3 abi3; never
|
|
30
|
+
# blocks the build.
|
|
31
|
+
- python-version: "3.15"
|
|
32
|
+
experimental: true
|
|
33
|
+
continue-on-error: ${{ matrix.experimental }}
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v4
|
|
36
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
37
|
+
- uses: Swatinem/rust-cache@v2
|
|
38
|
+
- uses: astral-sh/setup-uv@v6
|
|
39
|
+
- run: uv python install ${{ matrix.python-version }}
|
|
40
|
+
- run: uv sync --all-groups --python ${{ matrix.python-version }}
|
|
41
|
+
- run: uv run maturin develop --release
|
|
42
|
+
- run: uv run ruff check
|
|
43
|
+
- run: uv run ruff format --check
|
|
44
|
+
- run: uv run mypy
|
|
45
|
+
- run: uv run pytest tests -x
|
|
46
|
+
|
|
47
|
+
# No integration job: GitHub's hosted runners use Azure kernels whose
|
|
48
|
+
# linux-modules-extra package does not ship sound modules (snd-virmidi),
|
|
49
|
+
# so the VirMIDI round-trip can never run here. Integration tests run
|
|
50
|
+
# locally / on real hardware via scripts/test.sh with snd-virmidi loaded.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
wheels:
|
|
9
|
+
strategy:
|
|
10
|
+
matrix:
|
|
11
|
+
include:
|
|
12
|
+
- runner: ubuntu-latest
|
|
13
|
+
target: x86_64
|
|
14
|
+
- runner: ubuntu-24.04-arm # native aarch64, no QEMU
|
|
15
|
+
target: aarch64
|
|
16
|
+
runs-on: ${{ matrix.runner }}
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: PyO3/maturin-action@v1
|
|
20
|
+
with:
|
|
21
|
+
target: ${{ matrix.target }}
|
|
22
|
+
manylinux: 2_28
|
|
23
|
+
args: --release --out dist
|
|
24
|
+
- uses: astral-sh/setup-uv@v6
|
|
25
|
+
- name: Smoke-test wheel
|
|
26
|
+
run: |
|
|
27
|
+
uv venv --python 3.13 /tmp/smoke
|
|
28
|
+
uv pip install --python /tmp/smoke/bin/python dist/*.whl
|
|
29
|
+
/tmp/smoke/bin/python -c "import minimidi; print(minimidi.__version__)"
|
|
30
|
+
- uses: actions/upload-artifact@v4
|
|
31
|
+
with:
|
|
32
|
+
name: wheels-${{ matrix.target }}
|
|
33
|
+
path: dist/*.whl
|
|
34
|
+
|
|
35
|
+
sdist:
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v4
|
|
39
|
+
- uses: PyO3/maturin-action@v1
|
|
40
|
+
with:
|
|
41
|
+
command: sdist
|
|
42
|
+
args: --out dist
|
|
43
|
+
- uses: actions/upload-artifact@v4
|
|
44
|
+
with:
|
|
45
|
+
name: sdist
|
|
46
|
+
path: dist/*.tar.gz
|
|
47
|
+
|
|
48
|
+
publish:
|
|
49
|
+
needs: [wheels, sdist]
|
|
50
|
+
runs-on: ubuntu-latest
|
|
51
|
+
environment: pypi
|
|
52
|
+
permissions:
|
|
53
|
+
id-token: write # PyPI trusted publishing (OIDC) — no API tokens
|
|
54
|
+
steps:
|
|
55
|
+
- uses: actions/download-artifact@v4
|
|
56
|
+
with:
|
|
57
|
+
path: dist
|
|
58
|
+
merge-multiple: true
|
|
59
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
60
|
+
with:
|
|
61
|
+
packages-dir: dist
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Rust / Cargo
|
|
2
|
+
target/
|
|
3
|
+
*.so
|
|
4
|
+
|
|
5
|
+
# Python
|
|
6
|
+
__pycache__/
|
|
7
|
+
*.py[cod]
|
|
8
|
+
*$py.class
|
|
9
|
+
.mypy_cache/
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
|
|
13
|
+
# Virtual envs (.venv plus scripts/test.sh --python venvs, e.g. .venv-py3.14)
|
|
14
|
+
.venv*/
|
|
15
|
+
|
|
16
|
+
# Build artefacts
|
|
17
|
+
dist/
|
|
18
|
+
build/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
|
|
21
|
+
# uv
|
|
22
|
+
.python-version-cache
|
|
23
|
+
|
|
24
|
+
# Editors / OS
|
|
25
|
+
.idea/
|
|
26
|
+
.vscode/
|
|
27
|
+
.DS_Store
|
|
28
|
+
*.swp
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
minimidi-0.1.0/AGENTS.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Agent Instructions
|
|
2
|
+
|
|
3
|
+
Mixed Rust + Python project.
|
|
4
|
+
|
|
5
|
+
## Toolchain
|
|
6
|
+
- Python: `uv` (reads `.python-version`, pins 3.13)
|
|
7
|
+
- Rust: `cargo` (stable) + `maturin` for PyO3 builds
|
|
8
|
+
- Build backend: `maturin` (mixed layout, Rust in `src/`, Python in `python/minimidi/`)
|
|
9
|
+
|
|
10
|
+
## Setup
|
|
11
|
+
```
|
|
12
|
+
uv sync --all-groups # creates .venv, installs dev deps
|
|
13
|
+
uv run maturin develop --release # builds Rust ext into the venv
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Tests
|
|
17
|
+
Run both Rust and Python suites:
|
|
18
|
+
```
|
|
19
|
+
./scripts/test.sh # rebuilds ext, runs cargo test + pytest
|
|
20
|
+
./scripts/test.sh --no-build # skip maturin rebuild (Rust-only changes still need it)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## File-Scoped Commands
|
|
24
|
+
| Task | Command |
|
|
25
|
+
|------|---------|
|
|
26
|
+
| Python test file | `uv run pytest tests/test_<name>.py` |
|
|
27
|
+
| Rust unit tests | `cargo test` |
|
|
28
|
+
| Rust lint | `cargo clippy -- -D warnings` |
|
|
29
|
+
| Rust format check | `cargo fmt --check` |
|
|
30
|
+
| Python lint | `uv run ruff check python/minimidi tests` |
|
|
31
|
+
| Python typecheck | `uv run mypy python/minimidi tests` |
|
|
32
|
+
|
|
33
|
+
After editing `src/lib.rs`, rerun `uv run maturin develop --release` before `pytest`.
|
|
34
|
+
|
|
35
|
+
## Layout
|
|
36
|
+
- `src/lib.rs` — Rust extension (PyO3, abi3-py313)
|
|
37
|
+
- `python/minimidi/` — Python wrapper + `.pyi` stubs + `py.typed`
|
|
38
|
+
- `tests/` — Python tests (unit + `tests/integration/` for VirMIDI-dependent)
|
|
39
|
+
|
|
40
|
+
## Conventions
|
|
41
|
+
- Docstrings: opening `"""` on its own line, content underneath, closing `"""` on its own line.
|
|
42
|
+
- All `.pyi` stubs are hand-written (single source of truth for static types).
|
|
43
|
+
- `mypy --strict` must stay clean.
|
|
44
|
+
- Integration tests under `tests/integration/` are skipped when `snd-virmidi` isn't loaded.
|
|
45
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
|
|
7
|
+
(pre-1.0: breaking changes only at minor-version bumps).
|
|
8
|
+
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
Nothing yet.
|
|
12
|
+
|
|
13
|
+
## [0.1.0] - 2026-06-11
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Initial public API: `RawMidiOut` with `note_on()`, `note_off()`,
|
|
18
|
+
`cc()`, `send_bytes()`, `close()`, context-manager support, and `card_id` /
|
|
19
|
+
`device` / `is_open` properties.
|
|
20
|
+
- Card lookup helpers: `list_cards()` and `find_card()`. A host without
|
|
21
|
+
ALSA (no `/proc/asound/cards`) behaves like a host with zero cards.
|
|
22
|
+
- `MinimidiError` exception type; OS-level failures propagate as the
|
|
23
|
+
standard errno-derived built-ins (`FileNotFoundError`, `PermissionError`,
|
|
24
|
+
`BrokenPipeError`, ...).
|
|
25
|
+
- PEP 561 typing: `py.typed` marker and hand-written `_minimidi.pyi` stubs;
|
|
26
|
+
clean under `mypy --strict`.
|
|
27
|
+
- Prebuilt `abi3` wheels (Python ≥3.13) for x86_64 and aarch64 manylinux.
|
|
28
|
+
|
|
29
|
+
[Unreleased]: https://github.com/ModularDevicesMalleableAudio/minimidi/compare/v0.1.0...HEAD
|
|
30
|
+
[0.1.0]: https://github.com/ModularDevicesMalleableAudio/minimidi/releases/tag/v0.1.0
|
minimidi-0.1.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Agent Instructions
|
|
2
|
+
|
|
3
|
+
Mixed Rust + Python project.
|
|
4
|
+
|
|
5
|
+
## Toolchain
|
|
6
|
+
- Python: `uv` (reads `.python-version`, pins 3.13)
|
|
7
|
+
- Rust: `cargo` (stable) + `maturin` for PyO3 builds
|
|
8
|
+
- Build backend: `maturin` (mixed layout, Rust in `src/`, Python in `python/minimidi/`)
|
|
9
|
+
|
|
10
|
+
## Setup
|
|
11
|
+
```
|
|
12
|
+
uv sync --all-groups # creates .venv, installs dev deps
|
|
13
|
+
uv run maturin develop --release # builds Rust ext into the venv
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Tests
|
|
17
|
+
Run both Rust and Python suites:
|
|
18
|
+
```
|
|
19
|
+
./scripts/test.sh # rebuilds ext, runs cargo test + pytest
|
|
20
|
+
./scripts/test.sh --no-build # skip maturin rebuild (Rust-only changes still need it)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## File-Scoped Commands
|
|
24
|
+
| Task | Command |
|
|
25
|
+
|------|---------|
|
|
26
|
+
| Python test file | `uv run pytest tests/test_<name>.py` |
|
|
27
|
+
| Rust unit tests | `cargo test` |
|
|
28
|
+
| Rust lint | `cargo clippy -- -D warnings` |
|
|
29
|
+
| Rust format check | `cargo fmt --check` |
|
|
30
|
+
| Python lint | `uv run ruff check python/minimidi tests` |
|
|
31
|
+
| Python typecheck | `uv run mypy python/minimidi tests` |
|
|
32
|
+
|
|
33
|
+
After editing `src/lib.rs`, rerun `uv run maturin develop --release` before `pytest`.
|
|
34
|
+
|
|
35
|
+
## Layout
|
|
36
|
+
- `src/lib.rs` — Rust extension (PyO3, abi3-py313)
|
|
37
|
+
- `python/minimidi/` — Python wrapper + `.pyi` stubs + `py.typed`
|
|
38
|
+
- `tests/` — Python tests (unit + `tests/integration/` for VirMIDI-dependent)
|
|
39
|
+
|
|
40
|
+
## Conventions
|
|
41
|
+
- Docstrings: opening `"""` on its own line, content underneath, closing `"""` on its own line.
|
|
42
|
+
- All `.pyi` stubs are hand-written (single source of truth for static types).
|
|
43
|
+
- `mypy --strict` must stay clean.
|
|
44
|
+
- Integration tests under `tests/integration/` are skipped when `snd-virmidi` isn't loaded.
|
|
45
|
+
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# This file is automatically @generated by Cargo.
|
|
2
|
+
# It is not intended for manual editing.
|
|
3
|
+
version = 4
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "heck"
|
|
7
|
+
version = "0.5.0"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
10
|
+
|
|
11
|
+
[[package]]
|
|
12
|
+
name = "libc"
|
|
13
|
+
version = "0.2.186"
|
|
14
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
15
|
+
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
|
16
|
+
|
|
17
|
+
[[package]]
|
|
18
|
+
name = "minimidi"
|
|
19
|
+
version = "0.1.0"
|
|
20
|
+
dependencies = [
|
|
21
|
+
"pyo3",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[[package]]
|
|
25
|
+
name = "once_cell"
|
|
26
|
+
version = "1.21.4"
|
|
27
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
28
|
+
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
29
|
+
|
|
30
|
+
[[package]]
|
|
31
|
+
name = "portable-atomic"
|
|
32
|
+
version = "1.13.1"
|
|
33
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
34
|
+
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
|
35
|
+
|
|
36
|
+
[[package]]
|
|
37
|
+
name = "proc-macro2"
|
|
38
|
+
version = "1.0.106"
|
|
39
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
40
|
+
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
|
41
|
+
dependencies = [
|
|
42
|
+
"unicode-ident",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[[package]]
|
|
46
|
+
name = "pyo3"
|
|
47
|
+
version = "0.28.3"
|
|
48
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
49
|
+
checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12"
|
|
50
|
+
dependencies = [
|
|
51
|
+
"libc",
|
|
52
|
+
"once_cell",
|
|
53
|
+
"portable-atomic",
|
|
54
|
+
"pyo3-build-config",
|
|
55
|
+
"pyo3-ffi",
|
|
56
|
+
"pyo3-macros",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[[package]]
|
|
60
|
+
name = "pyo3-build-config"
|
|
61
|
+
version = "0.28.3"
|
|
62
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
63
|
+
checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e"
|
|
64
|
+
dependencies = [
|
|
65
|
+
"target-lexicon",
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
[[package]]
|
|
69
|
+
name = "pyo3-ffi"
|
|
70
|
+
version = "0.28.3"
|
|
71
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
72
|
+
checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e"
|
|
73
|
+
dependencies = [
|
|
74
|
+
"libc",
|
|
75
|
+
"pyo3-build-config",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
[[package]]
|
|
79
|
+
name = "pyo3-macros"
|
|
80
|
+
version = "0.28.3"
|
|
81
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
82
|
+
checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813"
|
|
83
|
+
dependencies = [
|
|
84
|
+
"proc-macro2",
|
|
85
|
+
"pyo3-macros-backend",
|
|
86
|
+
"quote",
|
|
87
|
+
"syn",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
[[package]]
|
|
91
|
+
name = "pyo3-macros-backend"
|
|
92
|
+
version = "0.28.3"
|
|
93
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
94
|
+
checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb"
|
|
95
|
+
dependencies = [
|
|
96
|
+
"heck",
|
|
97
|
+
"proc-macro2",
|
|
98
|
+
"pyo3-build-config",
|
|
99
|
+
"quote",
|
|
100
|
+
"syn",
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
[[package]]
|
|
104
|
+
name = "quote"
|
|
105
|
+
version = "1.0.45"
|
|
106
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
107
|
+
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
|
108
|
+
dependencies = [
|
|
109
|
+
"proc-macro2",
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
[[package]]
|
|
113
|
+
name = "syn"
|
|
114
|
+
version = "2.0.117"
|
|
115
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
116
|
+
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
|
117
|
+
dependencies = [
|
|
118
|
+
"proc-macro2",
|
|
119
|
+
"quote",
|
|
120
|
+
"unicode-ident",
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
[[package]]
|
|
124
|
+
name = "target-lexicon"
|
|
125
|
+
version = "0.13.5"
|
|
126
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
127
|
+
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
128
|
+
|
|
129
|
+
[[package]]
|
|
130
|
+
name = "unicode-ident"
|
|
131
|
+
version = "1.0.24"
|
|
132
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
133
|
+
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "minimidi"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
license = "MIT"
|
|
6
|
+
description = "Minimal Linux ALSA Raw MIDI output, with Python bindings via PyO3"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
|
|
9
|
+
[lib]
|
|
10
|
+
name = "_minimidi" # match #[pymodule] name in lib.rs
|
|
11
|
+
crate-type = ["cdylib"] # dynamic library for Python to load
|
|
12
|
+
|
|
13
|
+
[dependencies]
|
|
14
|
+
pyo3 = { version = "0.28", features = ["extension-module", "abi3-py313"] }
|
minimidi-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 minimidi authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
minimidi-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: minimidi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Intended Audience :: Developers
|
|
6
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
7
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Classifier: Programming Language :: Rust
|
|
11
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: MIDI
|
|
12
|
+
Classifier: Typing :: Typed
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Summary: Minimal Linux ALSA Raw MIDI output, with Python bindings via PyO3
|
|
15
|
+
Keywords: midi,alsa,linux,rust,pyo3
|
|
16
|
+
Author: minimidi authors
|
|
17
|
+
License: MIT
|
|
18
|
+
Requires-Python: >=3.13
|
|
19
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
20
|
+
|
|
21
|
+
# minimidi
|
|
22
|
+
|
|
23
|
+
[](https://github.com/ModularDevicesMalleableAudio/minimidi/actions/workflows/ci.yml)
|
|
24
|
+
|
|
25
|
+
Minimal Linux ALSA Raw MIDI **output**, written in Rust (PyO3), fully typed for Python 3.13+.
|
|
26
|
+
|
|
27
|
+
`minimidi` does one thing: open an ALSA Raw MIDI device (`/dev/snd/midiC*D*`) write-only
|
|
28
|
+
and send MIDI bytes to it. No input, no sequencer scheduling, no SysEx framing, no
|
|
29
|
+
cross-platform abstraction — just note-on / note-off / CC helpers and a raw-bytes
|
|
30
|
+
escape hatch, shipped as a single `abi3` wheel with no system dependencies.
|
|
31
|
+
|
|
32
|
+
- **Linux only** (ALSA Raw MIDI).
|
|
33
|
+
- **Output only.**
|
|
34
|
+
- **Typed.** PEP 561 `py.typed` + hand-written stubs; clean under `mypy --strict`.
|
|
35
|
+
- **Thread-safe.** One port can be shared across Python threads; writes serialise
|
|
36
|
+
internally and release the GIL across the `write(2)` syscall.
|
|
37
|
+
|
|
38
|
+
See [`AGENTS.md`](./AGENTS.md) for developer / agent workflow instructions.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install minimidi
|
|
44
|
+
# or
|
|
45
|
+
uv add minimidi
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Prebuilt `abi3` wheels for x86_64 and aarch64 manylinux (Python ≥3.13) — no
|
|
49
|
+
Rust toolchain or system packages needed. To install from source instead
|
|
50
|
+
(needs Rust stable):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install git+https://github.com/ModularDevicesMalleableAudio/minimidi
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Quick start
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import minimidi
|
|
60
|
+
|
|
61
|
+
with minimidi.RawMidiOut("VirMIDI", device=0) as port:
|
|
62
|
+
port.note_on(channel=1, note=60, velocity=100) # middle C on
|
|
63
|
+
port.cc(channel=1, controller=74, value=64) # filter cutoff
|
|
64
|
+
port.note_off(channel=1, note=60) # note off (velocity 0)
|
|
65
|
+
# port auto-closes on context exit
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Channels are **1-indexed** (1–16), matching common MIDI convention. Notes,
|
|
69
|
+
velocities, controllers, and values are 0–127. Out-of-range arguments raise
|
|
70
|
+
`MinimidiError` before anything is written.
|
|
71
|
+
|
|
72
|
+
## Finding your card
|
|
73
|
+
|
|
74
|
+
Ports are addressed by ALSA card id (the bracketed name in `/proc/asound/cards`)
|
|
75
|
+
plus a raw device number:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
import minimidi
|
|
79
|
+
|
|
80
|
+
minimidi.list_cards()
|
|
81
|
+
# [(0, "PCH"), (1, "VirMIDI"), (3, "pisound")]
|
|
82
|
+
|
|
83
|
+
minimidi.find_card("pisound")
|
|
84
|
+
# 3 — raises MinimidiError if not present
|
|
85
|
+
|
|
86
|
+
port = minimidi.RawMidiOut("pisound", device=0)
|
|
87
|
+
# opens /dev/snd/midiC3D0 write-only
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Card-id matching is exact and case-sensitive (ALSA's bracket padding is trimmed).
|
|
91
|
+
`device` is typically 0–3 for `snd-virmidi`; a non-existent device raises
|
|
92
|
+
`FileNotFoundError` at open time.
|
|
93
|
+
|
|
94
|
+
## Escape hatch: raw bytes
|
|
95
|
+
|
|
96
|
+
Messages without a first-class helper (pitch bend, program change, aftertouch, ...)
|
|
97
|
+
can be written verbatim:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
channel = 1
|
|
101
|
+
port.send_bytes(bytes([0xE0 | (channel - 1), 0x00, 0x40])) # pitch bend, centre
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`send_bytes` accepts only `bytes` (not `bytearray`/`memoryview`/`str`), length
|
|
105
|
+
1–1024, and performs **no MIDI semantic validation** — the caller owns correctness.
|
|
106
|
+
|
|
107
|
+
## Errors and recovery
|
|
108
|
+
|
|
109
|
+
| Condition | Exception |
|
|
110
|
+
|---|---|
|
|
111
|
+
| Unknown card id, out-of-range argument, write on closed port | `MinimidiError` |
|
|
112
|
+
| Device node missing, no permission, device disappeared mid-write | `FileNotFoundError` / `PermissionError` / `BrokenPipeError` (standard errno-derived `OSError` subclasses) |
|
|
113
|
+
| Non-`bytes` payload to `send_bytes` | `TypeError` |
|
|
114
|
+
|
|
115
|
+
A failed write does **not** close the port: `is_open` means "not explicitly
|
|
116
|
+
closed", and a retry raises the same OS error again. After device loss (USB
|
|
117
|
+
hot-replug, module reload), construct a *new* `RawMidiOut` — that re-resolves the
|
|
118
|
+
card number from `/proc/asound/cards`, which is correct when the kernel may have
|
|
119
|
+
re-numbered the card. Closed is final: there is no `open()`/reopen method.
|
|
120
|
+
|
|
121
|
+
## Testing without hardware
|
|
122
|
+
|
|
123
|
+
Load the kernel's virtual Raw MIDI card and `minimidi` can talk to it like any
|
|
124
|
+
other device:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
sudo modprobe snd-virmidi # persistent: echo snd-virmidi | sudo tee /etc/modules-load.d/virmidi.conf
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
port = minimidi.RawMidiOut("VirMIDI", device=0)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Development
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
uv sync --all-groups # venv + dev deps
|
|
138
|
+
uv run maturin develop --release # build the Rust extension into the venv
|
|
139
|
+
./scripts/test.sh # cargo test + ruff + mypy + pytest
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Integration tests (`tests/integration/`) need `snd-virmidi` loaded and `aconnect`
|
|
143
|
+
(alsa-utils) installed; they skip cleanly otherwise.
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT — see [`LICENSE`](./LICENSE).
|
|
148
|
+
|