Search guides and the APICtrl K
  1. API reference
  2. MIDI and OSC

midi_input.h

Live MIDI input for the standalone build.

#include <sonore/midi_input.h>
On this page

Design notes

A standalone instrument that cannot be played by a keyboard is a demo, not an application. This is the smallest honest implementation of "the notes a device sends reach the DSP": one platform backend each, everything loaded at runtime so a machine without MIDI still builds and runs.

The device thread and the audio thread never share anything but a lock-free ring, because the audio callback must not wait for a driver.

Types 3

Single-producer/single-consumer ring of 3-byte messages. The device thread writes, the audio thread drains; neither ever blocks the other.

Bytes in, messages out, for ONE source.

The byte-stream backends -- ALSA rawmidi, and CoreMIDI, whose packets are byte runs that may split a SysEx -- each had a parser of their own, and each got different things wrong:

  • Linux treated every 0xF1..0xF7 status as a three-byte message that set running status. MTC quarter frames, song select and tune request never came out, and the bytes after a SysEx came out as [F7 xx yy] events.
  • macOS shared one SysEx assembler between every source, so a note from one keyboard in the middle of another's SysEx was lost with the SysEx; it dropped a clock byte inside a SysEx packet and anything after the F7 in the same packet; and it read a data byte past the packet's end.

One parser, then, with the rules written once: realtime bytes pass through wherever they fall, SysEx is collected until its F7 or until a status byte ends it (which then starts its own message), system common messages are sized by shortMessageLength and cancel running status, and channel messages keep it. Platform-free, so it is tested on any machine.

One open MIDI input, feeding a MessageQueue: winmm here, ALSA rawmidi on Linux, CoreMIDI on macOS, the same five calls on each.

open() and close() belong to the main thread; the bytes arrive on the driver's thread (a winmm callback, a CoreMIDI read proc, a reader thread of this class's own on Linux), which pushes into the queue, and the audio thread drains the queue. The queue is the caller's: it must outlive the device's open life, and only one Device may feed it, since it has one producer side. open() closes whatever was open first; the destructor closes too. It owns OS handles: do not copy one.

On failure open() returns false and error() says why in a sentence fit for a user; an open() that succeeds leaves it empty. No device at all is the common case and an ordinary answer.

↑↓ to move↵ to openEvery public name in the headers, and every guide