libremidi puts one C++20 API over every desktop MIDI backend
A modern C++ MIDI 1 / MIDI 2 real-time & file I/O library. Supports Windows, macOS, Linux and WebMIDI.
At a glance
- What is it?
- libremidi is a modern C++20 rewrite of RtMidi and ModernMIDI that handles real-time MIDI 1 and MIDI 2 input and output plus file I/O across Windows, macOS, Linux and WebMIDI. It buys you one API and a UMP aware core, and it charges you a large surface of backend specific behaviour you still have to know.
- Who is it for?
- libremidi suits a C++ developer who has to ship on more than one operating system and has been forced to write the same port enumeration and message dispatch twice, because the single API and the UMP aware core remove that duplication for a price in build complexity.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 9 days ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A rewrite of two libraries, with MIDI 2 added on top
The README states the lineage plainly: libremidi is a fork and rewrite based on RtMidi and ModernMIDI, and for MIDI 2 parsing support it uses cmidi2. That lineage explains the shape of the project. RtMidi gave the world a portable real-time MIDI API, and its success was also its limitation, since a portable wrapper has to settle on the lowest common denominator of what each platform offers. libremidi's claim is that it modernises both for C++20 and then goes further, adding MIDI 2 across the desktop platforms and a UMP aware core. The licensing position is unusual and worth checking before you ship. The licence field for the project is recorded as Other, a custom licence GitHub cannot classify, with a LICENSE.md file at the root of the repository rather than a standard SPDX identifier, so the text of that file governs and your own legal review should cover it. A paper exists too: for academic use the README asks you to cite the SMC 2024 paper, and it gives the BibTeX entry for it.
One state machine for timestamps, sysex and MIDI 2
The most significant internal change in the project's history is described under the v4.5 entries, and it explains a lot of the current behaviour. The input logic was refactored across all backends, because previously not all backends had the same rules about timestamping and sysex handling, and now there is a single MIDI state machine which processes this. That is a design decision with visible consequences. A library with one state machine gives consistent semantics across platforms, which matters if the same application has to behave identically on Windows and Linux, and it also means that any quirk you hit is a quirk of the state machine rather than of the backend you happen to be running on. On top of that sits the MIDI 2 layer. From v5 the changelog states that libremidi finally supports MIDI 2 on all desktop platforms, and v4.4 added compatibility with ni-midi2, where the libremidi::ump type converts automatically to and from midi::universal_packet and some ni-midi2 data types can be sent directly through libremidi::midi_out. The v5.4 entries add many improvements to MIDI 1 to MIDI 2 conversion, with every MIDI 1 message supported now. One caveat is stated plainly: sysex handling on MIDI 2.0 is the responsibility of the user of the library, which the changelog says simplifies the design immensely.
The backend matrix is the real documentation
The topic tags and the examples directory give the full list of what has to work: ALSA, CoreMIDI, JACK, JACKAudio, UWP, Emscripten and WebMIDI. The examples make this concrete, with separate files for ALSA sharing, CoreMIDI sharing, JACK sharing, an Emscripten MIDI input example, a lookup example, a client, an echo, a MIDI 2 echo, a virtual port example, and a family of network and file tools including a MIDI to pattern converter and a MIDI file dump. The Windows story has two parts, and they are not equivalent. The Windows MIDI Services backend, which is the MIDI 2 path on Windows, was added in v5 using Developer Preview 9 and works on both MSVC and MinGW or MSYS2, with the support updated to the RC1 release headers in v5.4 along with a newly introduced COM fast path for maximum performance. Separately, WinUWP support exists on MinGW and MSYS2. On Linux, MIDI 2 arrives through the ALSA sequencer API and requires kernel 6.5 or newer with the latest libasound, while PipeWire and JACK UMP support arrived in v5.1 and require PipeWire v1.4 or newer. macOS MIDI 2 goes through CoreMIDI and needs macOS 11 or later. The 5.4 changelog also notes that ALSA observation will now be slightly delayed, because otherwise udev fields are sometimes not populated yet, which is the kind of detail you only learn from the changelog.
Port information, lookup helpers and a C API for bindings
A recurring practical problem with MIDI libraries is finding the right port. The v5.4 changelog shows libremidi treating this as a first class concern. Port information now carries as much metadata as the host API will provide, and port information no longer sorts or compares, because the changelog is explicit that there is no generally correct way to compare two port_information, input_port or output_port objects, so multiple ways of doing this comparison depending on the use case are provided as examples in <libremidi/port_comparison.hpp>. For the matching case there is a heuristics-based libremidi::find_closest_port(query, existing_ports) utility function that tries to look up the most likely candidate for a MIDI port across backends using whatever lookup information is available. On the C API side, the project exposes a C header, libremidi-c.h, for bindings to other languages, and there are Python, Haskell and Java bindings listed, with the Python one now carrying a pyproject.toml to ease integration with the Python ecosystem. The Python package is published as pylibremidi.
The Python route is a real build with scikit-build-core and nanobind rather than a thin wheel, which means a C++ toolchain is needed to install it.
Building the Python binding means compiling the C++ library
The Python package is published as pylibremidi, and its build configuration tells you what installing it costs you. The project section declares the name and the version, and the build system section names the two tools that do the work.
[build-system]
requires = ["scikit-build-core >=0.4.3", "nanobind >=1.3.2"]
build-backend = "scikit_build_core.build"
[project]
name = "pylibremidi"
version = "5.4.2"
requires-python = ">=3.10"So a pip install of pylibremidi pulls in scikit-build-core and nanobind and compiles the C++ library rather than downloading a prebuilt wheel, which means a working C++ toolchain and a compiler that supports the C++20 the library is written in. The C++ side of the project is built with CMake, since the repository root carries a CMakeLists.txt alongside a cmake/ directory, and the examples assume you are building against a configured tree. If you are only interested in the C++ library and not in the Python surface, none of this applies and the CMake path is the one to follow.
C++20 modules are behind a flag, and other compilers crash
The newest and least stable corner of the project is C++ module support, added in v5.4. The changelog is direct about the state of it: initial C++ modules support was added with import libremidi;, so far compilers other than Clang 20 and newer crash, and it is enabled with -DLIBREMIDI_LIBRARY_MODE=MODULE. There is also a constraint that changes how you structure code: one cannot mix a module based and a non module based API, with an example provided in examples/modules.cpp. That is a hard either or at the build level rather than a stylistic preference, so adopting modules means committing your whole integration to one API style. Elsewhere in v5.4 there is a smaller convenience addition, a libremidi::set_client_name and client_name function to simplify construction of an API object with a custom client name, which sounds trivial and in practice saves every project that has to pass a client name to each backend constructor. The v4.3 entries also added a Custom timestamping mechanism, which lets you provide a callback to run timestamping as close as possible to the event's reception, and midi_in::absolute_timestamp() to get the origin timestamp for driver-provided ticks, in practice the time near the ALSA queue creation or the WinMM MIDI open.
Where it will not fit, and what it costs to adopt
Several limits are visible in the changelog rather than stated as policy. The library is a C++20 project, and the module support additionally requires Clang 20 or newer, so a build matrix pinned to an older compiler is limited to the classic API. On Linux, MIDI 2 through ALSA needs a recent kernel, and PipeWire UMP needs a recent PipeWire, so a library that must run on long term support distributions is effectively restricted to MIDI 1 paths there. The API also has a large surface, since the changelog for v4.5 mentions that the input logic was refactored across all backends and thanks a contributor for extensive work on warning fixes, which tells you the header set is big and the compile times are not trivial. For a small utility that only needs to read a standard MIDI file, the standard library of C++ MIDI file readers is a better fit, because the file side is a secondary part of this project. The other alternative is the platform API directly, using CoreMIDI on macOS or the Windows MIDI Services on Windows, which gives you the best performance on one platform at the cost of the portability libremidi exists to provide. The trade libremidi offers is deliberate. One API, one state machine, one set of semantics, at the price of a header-heavy C++ library whose real documentation is the changelog.
Editorial conclusion
libremidi suits a C++ developer who has to ship on more than one operating system and has been forced to write the same port enumeration and message dispatch twice, because the single API and the UMP aware core remove that duplication for a price in build complexity. It is the wrong tool for a project that only reads standard MIDI files, since file parsing is the smaller part of the library, and for a team that wants a small dependency footprint, since the backend matrix pulls in ALSA, JACK, CoreMIDI and Windows MIDI Services. Before you commit, read the changelog entries for your target backend rather than the overview, because the ALSA sequence in v5.4 deliberately delays observation until udev fields are populated, and the JACK and PipeWire UMP support in v5.1 requires PipeWire v1.4 or newer.
Frequently asked questions
What is libremidi and which platforms does it support?
libremidi is a cross-platform C++20 library for real-time and MIDI file input and output, described as a fork and rewrite of RtMidi and ModernMIDI. It supports Windows, macOS, Linux and WebMIDI, with MIDI 2 support across the desktop platforms.
How do I install the Python binding for libremidi?
The Python package is named pylibremidi and requires Python 3.10 or newer. It is built with scikit-build-core and nanobind, so installing it means compiling the C++ library, and a C++ toolchain is needed.
Does libremidi support C++20 modules?
Initial support was added in v5.4 with import libremidi;, enabled through -DLIBREMIDI_LIBRARY_MODE=MODULE, and the changelog says compilers other than Clang 20 and newer crash. Module based and non module based APIs cannot be mixed in the same build.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/celtera-libremidi)Was this useful?