sauloverissimo/midi2

0.3.4

Latest
uploaded 18 hours ago
Portable MIDI 2.0 UMP library, C99 with zero dependencies and zero allocation.

readme

# midi2

## 🎹 The MIDI 2.0 core for platforms

![midi2](logo_midi2.png)

*C99, portable, zero-allocation, MIT.* From 8-bit MCUs to 64-bit hosts.

[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
[![C99](https://img.shields.io/badge/standard-C99-blue.svg)]()
[![Zero Alloc](https://img.shields.io/badge/allocation-zero-orange.svg)]()
[![Sponsor](https://img.shields.io/badge/sponsor-%E2%9D%A4-pink.svg)](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:

![midi2](architecture.png)

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.

Links

Supports all targets

Maintainer

  • Saulo Verissimo <sauloverissimo@gmail.com>

License: MIT

To add this component to your project, run:

idf.py add-dependency "sauloverissimo/midi2^0.3.4"

download archive

Stats

  • Archive size
    Archive size ~ 1.26 MB
  • Downloaded in total
    Downloaded in total 0 times
  • Downloaded this version
    This version: 0 times

Badge

sauloverissimo/midi2 version: 0.3.4
|