Skip to content

Repository files navigation

MidiConform

Prove a MIDI asset fits its player before the player drops notes, rejects events, or hangs a voice.

中文 · Profile reference · Rules · Troubleshooting

CI Release Python License: MIT

A Standard MIDI File can be perfectly parseable and still exceed the engine that must play it. Embedded synths, games, web players, karaoke systems, and older hardware differ in track, event, polyphony, channel, controller, program, metadata, and SysEx support. Playback spot checks usually find those mismatches after integration.

MidiConform turns the target's limits into a small TOML contract. It parses the actual event stream, simulates pressed and sustain-held notes, measures the file's capability envelope, and points to the exact tick, track, and event that breaks the contract. It is a real local CLI and Python package—no upload, account, MIDI device, or AI model.

See the proof in under a minute

Python 3.11+ and uv are required for source development.

git clone https://github.com/KanadeK/midiconform.git
cd midiconform
uv sync --locked --all-extras

# Must pass with exit 0.
uv run midiconform check examples/compatible.mid \
  --profile examples/pocket-player.toml

# Must fail the contract with exit 1 and exact evidence.
uv run midiconform check examples/incompatible.mid \
  --profile examples/pocket-player.toml

The failing sample is deliberately too wide for the fictional pocket player:

MidiConform FAIL — Pocket Player Demo

FAIL examples/incompatible.mid · type 1 · 2 track(s) · 12 event(s)
  peak 3 voice(s) · 3 SysEx byte(s)
  ERROR MC012 tick 0 track 0 event 2: meta message lyrics is not allowed
  ERROR MC008 tick 0 track 1 event 1: channel 2 is not allowed
  ERROR MC009 tick 0 track 1 event 1: program 40 is not allowed
  ERROR MC013 tick 0 track 1 event 2: SysEx is forbidden but the file contains 1 event(s) totaling 3 data byte(s)
  ERROR MC101 tick 0 track 1 event 4: note 60 is pressed again before release on channel 2
  …

The examples are original synthetic data. Their readable generator is scripts/generate_examples.py; CI regenerates them and rejects binary drift.

Describe the player, not an imaginary universal target

version = 1
name = "Pocket Player"

[limits]
file_bytes = 65536
tracks = 4
events = 5000
ticks_per_beat = 480
total_polyphony = 16
channel_polyphony = 8
sysex_bytes = 0
min_note_ticks = 2

[allow]
file_types = [0, 1]
channels = [1, 2, 10]
programs = [0, 24, 32]
controllers = [1, 7, 10, 64, 120, 123]
channel_messages = ["note_on", "note_off", "program_change", "control_change"]
meta_messages = ["track_name", "set_tempo", "time_signature", "end_of_track"]
sysex = false

Every allow-list except file_types is optional, while the sysex boolean is required. An omitted list means unconstrained; an empty list means nothing in that category is allowed. Channel numbers are 1..16; program and controller values are the MIDI data values 0..127.

What the first release checks

Layer Deterministic evidence
File envelope SMF type, bytes, tracks, events, ticks per beat
Playback load Sustain-aware total and per-channel sounding-note peaks
Capability use Channels, programs, controllers, channel messages, meta messages, SysEx bytes
Note lifecycle Repeated Note On, orphan Note Off, dangling notes, held sustain, zero/short notes
Source location Absolute tick plus zero-based track and event indexes

MIDI Note On with velocity zero is correctly treated as Note Off. SMF type 2 tracks are independent sequences, so their peaks are compared rather than incorrectly summed.

CI and machine output

uv run midiconform check assets/theme.mid assets/boss.mid \
  --profile targets/handheld.toml \
  --format json --output artifacts/midi-conformance.json \
  --fail-on warning

Exit codes are stable:

  • 0: the selected threshold passed;
  • 1: a conformance finding met the threshold;
  • 2: invocation, profile, filesystem, safety-limit, or parsing failure.

JSON uses schema_version: 1, preserves input order, and sorts findings deterministically. See the complete profile reference and rule catalog.

Install a release artifact

Download the wheel from the latest GitHub Release, then install it into a virtual environment:

python -m pip install midiconform-0.1.0-py3-none-any.whl
midiconform --version

MidiConform v0.1.0 is distributed through GitHub Releases. It is not claimed to be published on PyPI.

Full acceptance

uv sync --locked --all-extras
uv run python scripts/check.py

That command regenerates fixtures, checks binary drift, formatting, lint, strict typing, 90% branch coverage, wheel/sdist metadata, dependency advisories, and all three CLI exit paths. If a step fails, run that printed command alone, follow the repair table, then rerun the full gate. Detailed manual acceptance is in docs/acceptance.md.

Honest boundaries

  • MidiConform proves only the declared contract. Wrong or incomplete device limits produce an incomplete conclusion.
  • Polyphony is an event/sustain envelope, not a model of sample layers, envelopes, voice stealing, or device-specific synthesis.
  • It does not judge musical quality, harmony, timing feel, or instrument choice.
  • It never plays or repairs files. In particular, SysEx bytes remain inert data.
  • Automatic repair is omitted because changing note timing, programs, or controller events can change musical intent.

The design decision, competitor comparison, and threat boundary are recorded in ADR 0001, research, and the threat model.

Contributing and security

See CONTRIBUTING.md for the development gate. Please report parser or crafted-file security issues through the private process in SECURITY.md.

License

MIT. The code and generated example MIDI files are original project assets; see LICENSE.

About

Check Standard MIDI Files against explicit playback capability contracts.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages