# midi2
## 🎹 The MIDI 2.0 core for platforms

*C99, portable, zero-allocation, MIT.* From 8-bit MCUs to 64-bit hosts.
[](LICENSE)
[]()
[]()
[](https://github.com/sponsors/sauloverissimo)
---
## The library
midi2 is a small piece of infrastructure for boards that already have too much to do. Sound chips, displays, USB stacks, sensors, network. MIDI 2.0 arrives at the transport layer as 32-bit words; midi2 turns those words into structured events and stays out of the way the rest of the time.
The library implements 100% of the UMP format (M2-104-UM v1.1.2) and 100% of MIDI-CI (M2-101-UM v1.2). The application owns memory and decides which modules to link. The transport (USB, BLE, network, serial) lives somewhere else, by design.
## Wrappers
midi2 is the C99 core. Higher-level wrappers vendor it and add language-specific ergonomics for each platform target.
## Contents
- [midi2](#midi2)
- [🎹 The MIDI 2.0 core for platforms](#-the-midi-20-core-for-platforms)
- [The library](#the-library)
- [Wrappers](#wrappers)
- [Contents](#contents)
- [Spec coverage](#spec-coverage)
- [Quick start](#quick-start)
- [Modules](#modules)
- [Memory](#memory)
- [Error handling](#error-handling)
- [Continuous integration](#continuous-integration)
- [Build and test locally](#build-and-test-locally)
- [Integration](#integration)
- [Single-header (stb-style)](#single-header-stb-style)
- [Manual vendor (amalgam pair)](#manual-vendor-amalgam-pair)
- [Module by module](#module-by-module)
- [Architecture](#architecture)
- [Design goals](#design-goals)
- [What this library is not](#what-this-library-is-not)
- [Sponsor](#sponsor)
- [About](#about)
- [Specifications and trademarks](#specifications-and-trademarks)
- [License](#license)
## Spec coverage
| Spec | Document | Coverage |
|------|----------|----------|
| **M2-104-UM v1.1.2** | UMP Format and MIDI 2.0 Protocol | 100% (all message types MT 0x0 through 0xF) |
| **M2-101-UM v1.2** | MIDI-CI | 100% (all 31 messages, 4 categories, Appendix E responder) |
| **M2-115-U v1.0.2** | Bit Scaling and Resolution | 100% (round-trip safe 7/14/16/32 bit) |
| **M2-116-U v1.0** | MIDI Clip File | Partial (Delta Clockstamp, Start/End of Clip) |
## Quick start
```c
#include "midi2.h"
uint32_t w[2];
midi2_msg_note_on(w, 0, 0, 60, 0xC000, 0);
uint8_t note = midi2_msg_get_note(w);
uint16_t vel = midi2_msg_get_velocity(w);
uint16_t vel16 = midi2_msg_scale_up_7to16(127);
```
With typed dispatch:
```c
#include "midi2.h"
void on_note_on(uint8_t group, uint8_t channel, uint8_t note,
uint16_t velocity, uint8_t attr_type,
uint16_t attr_data, void *ctx) {
/* application logic */
}
midi2_dispatch dp;
midi2_dispatch_init(&dp);
dp.on_note_on = on_note_on;
midi2_dispatch_feed(w, 2, &dp);
```
## Modules
The amalgam (`dist/midi2.h` + `dist/midi2.c`) is the recommended path. For finer control, modules can be picked individually:
| Module | Files | State | Purpose |
|--------|-------|-------|---------|
| `midi2_msg` | `.h` | Stateless | UMP construction, parsing, value scaling. All MTs. MT 0x2 / MT 0x4 protocol translation. USB MIDI 1.0 cable event to UMP. |
| `midi2_dispatch` | `.h` `.c` | Caller-allocated | Typed UMP dispatch (49 callbacks). Optional MT 0x2 to MT 0x4 upscale. |
| `midi2_proc` | `.h` `.c` | Caller-allocated | Group filtering and remap, SysEx7/8 reassembly, UMP Stream and SysEx8 fragmenting senders. |
| `midi2_conv` | `.h` `.c` | Caller-allocated | MIDI 1.0 byte stream to UMP (Running Status, streaming SysEx, MT 0x2 to MT 0x4 translation). |
| `midi2_ci_msg` | `.h` | Stateless | MIDI-CI message construction and parsing. All 32 messages. |
| `midi2_ci_dispatch` | `.h` `.c` | Caller-allocated | Typed CI dispatch (33 callbacks). |
| `midi2_ci` | `.h` `.c` | Caller-allocated | Convenience CI responder: Discovery, Profiles, PE (Subscribe/Notify), Process Inquiry, MUID collision detection, optional NAK-on-unknown. Appendix E complete. |
`midi2_msg` alone is a header include with zero overhead.
## Memory
Memory belongs to the application. midi2 does not call `malloc`, does not keep a hidden heap, and does not bake buffer sizes into headers. A 16 KB board declares small buffers. A desktop tool declares large ones. Same source, same API.
```c
uint8_t sysex_buf[256];
midi2_proc_state proc;
midi2_proc_init(&proc, sysex_buf, sizeof(sysex_buf), NULL, 0);
uint8_t profiles[4][5];
midi2_ci_property props[2];
midi2_ci_state ci;
midi2_ci_init(&ci, seed, profiles, 4, props, 2);
```
## Error handling
Functions that can fail return error codes. There is no global errno, no asserts that fire in release builds, no silent truncation.
```c
int rc = midi2_ci_add_profile(&ci, profile_id);
if (rc == MIDI2_CI_ERR_FULL) {
/* the application decides what to do */
}
```
## Continuous integration
321 tests across 8 suites compile clean with `-Wall -Wextra -Wpedantic`. CI runs 11 jobs on every push:
| Target | Type |
|--------|------|
| gcc Linux x64 | Compile and run |
| clang Linux x64 | Compile and run |
| Apple clang macOS | Compile and run |
| MSVC Windows | Compile and run |
| gcc 32-bit Linux x86 | Compile and run |
| gcc + ASan + UBSan | Compile and run |
| ARM Cortex-M4 | Cross-compile |
| AArch64 Cortex-A | Cross-compile |
| RISC-V 64 | Cross-compile |
| ESP32 (ESP-IDF component) | Cross-compile |
| AVR ATmega328P | Cross-compile (header-only) |
## Build and test locally
```bash
make test
make CC=clang test
```
## Integration
midi2 ships in four shapes. Pick the one that matches your build flow.
### Arduino IDE / arduino-cli
Install via Library Manager (search `midi2`, click Install) or manually drop the repo into `~/Arduino/libraries/midi2/`. The `library.properties` declares the library; Arduino IDE compiles the modular `src/*.c` files automatically. Sketches use the umbrella header:
```cpp
#include <midi2.h>
```
Two example sketches under `examples/` (`BasicUsage`, `CIDiscovery`) appear in the IDE's File > Examples menu after install. Validated on Arduino UNO (AVR) and Teensy 4.1.
### PlatformIO
`platformio.ini`:
```ini
lib_deps = sauloverissimo/midi2 @ ^0.3.1
```
Library Manager pulls the same `src/` modular layout via `library.json` (`srcDir = src`).
### Single-header (stb-style)
Drop `dist/midi2.h` only. In exactly one `.c` file:
```c
#define MIDI2_IMPLEMENTATION
#include "midi2.h"
```
### Manual vendor (amalgam pair)
Download `dist/midi2.h` and `dist/midi2.c` from the release page. Drop both into the tree. Compile `midi2.c` alongside the project.
```c
#include "midi2.h"
```
Works with Arduino, PlatformIO, ESP-IDF, CMake, Make, plain `gcc`.
### Module by module
For finer control, copy individual files from `src/`:
```
midi2_msg.h Always needed. Header-only.
+- midi2_dispatch Typed UMP callbacks.
+- midi2_proc SysEx reassembly, group filtering.
+- midi2_conv MIDI 1.0 byte stream to UMP.
+- midi2_ci_msg MIDI-CI message construction and parsing.
+- midi2_ci_dispatch Typed CI callbacks.
+- midi2_ci Convenience CI responder.
```
## Architecture
midi2: infrastructure layer 2 of a 4-layer MIDI 2.0 stack:

What each layer owns:
| Concern | Layer |
|---------|-------|
| USB descriptors, endpoints, DMA | Transport |
| UMP construction, parsing, scaling | midi2 |
| Typed dispatch (49 UMP + 33 CI callbacks) | midi2 |
| SysEx reassembly, group filtering | midi2 |
| CI construction and parsing | midi2 |
| MIDI 1.0 byte stream conversion | midi2 |
| CI convenience responder (optional) | midi2 |
| Arduino-style callbacks (`onNoteOn`) | Platform |
| Profile behavior (GM2, MPE) | Platform or App |
| PE JSON schema interpretation | Platform or App |
| FreeRTOS tasks, event queues | Platform |
| Musical application logic | App |
## Design goals
- **Embedded first.** The same source compiles for AVR ATmega328P, Cortex-M0, RISC-V 64, ESP32 Xtensa, and 64-bit desktops. The smallest target dictates the design.
- **Zero allocation.** No `malloc`, no hidden heap, no per-call buffers. The application sizes everything at init.
- **Stateless where possible.** `midi2_msg` keeps no state; the same word array round-trips through any number of helpers without coupling.
- **Three system headers.** `<stdint.h>`, `<stdbool.h>`, `<string.h>`. That is the dependency list.
- **Errors are codes, not exceptions.** Functions report what happened; the application decides.
- **Spec-complete on its own scope.** 100% of UMP. 100% of MIDI-CI. Anything outside those documents lives elsewhere.
## What this library is not
The boundary is drawn so the core stays portable. A few things deliberately do not belong here.
- **Not a transport.** USB-MIDI 2.0, BLE MIDI, Network MIDI 2.0, Serial DIN: each has its own driver. midi2 reads and writes UMP words; the wire is somebody else's responsibility.
- **Not a JSON parser.** Property Exchange headers are JSON, but parsing JSON in a zero-allocation embedded library is a poor fit. The application chooses a JSON parser at the platform layer.
- **Not a Profile implementation.** GM2, MPE, and the M2-118 through M2-125 profiles describe device behavior. midi2 carries the negotiation messages; the behavior lives in the application.
- **Not the fastest UMP library.** Speed was not a primary goal. Correctness, portability, and predictable memory were. On a Cortex-M4 at 168 MHz, midi2 dispatches faster than any USB transport can deliver words anyway.
## Sponsor
You can sponsor midi2 at [GitHub Sponsors](https://github.com/sponsors/sauloverissimo). Sponsorship funds spec access, hardware for cross-platform validation, and continued maintenance.
## About
midi2 is created and maintained by [Saulo Veríssimo](https://github.com/sauloverissimo).
## Specifications and trademarks
The MIDI 2.0 specifications referenced here are copyright of the [MIDI Association](https://midi.org) and available at https://midi.org/midi-2-0.
"MIDI" is a registered trademark of the MIDI Manufacturers Association (now MIDI Association). "MIDI 2.0", "MIDI-CI", and "UMP" are terms defined by the MIDI Association in the public specifications.
## License
MIT. Free for commercial and open-source use.
idf.py add-dependency "sauloverissimo/midi2^0.3.4"