midi_output.h
Where a plugin's own MIDI goes.
#include <sonore/midi_output.h>- 3types
- Guides: MIDI and MPE
On this page
Design notes
The standalone could be PLAYED and could not play anything back. Its comment said so plainly: "No host to route emitted MIDI to: the buffer exists so the DSP can write safely, and is dropped after the call." For an effect that is nothing; for the arpeggiator, the note splitter, or anything else built on producesMidi, it means the entire output of the plugin was written into a buffer and thrown away. A MIDI plugin you cannot hear is not a plugin.
Two sinks, one interface, because the two useful answers to "where does it go" are a port and a file:
Device: a hardware or virtual MIDI port, one backend per platform, every
symbol loaded at runtime so a machine with no MIDI still builds
and runs.
File: a Standard MIDI File, written when the sink closes. Offline
rendering, and the only one of the two that a test can read back
and check, which is why it exists rather than being a nicety.
Timestamps are seconds from the start of the run. A port ignores them and sends now, because that is what a port is; a file keeps them, because that is what a file is for.
Types 3
Where produced MIDI goes.
Deliberately not a queue: a standalone calls send() from the audio thread for a port (winmm and ALSA both take a short message without allocating) and from anywhere for a file. What each sink may do on that thread is the sink's business and is documented on the sink, rather than being hidden behind a ring buffer that every caller then pays for.
class FileSink final : public Sink
Everything sent, written out as one track when the sink closes.
As constructed it ALLOCATES on send, which is right for an offline render that calls it on its own thread. A sink fed from a live audio thread calls startLive() first: send() then only writes into a ring, a thread of the sink's own moves the ring into the track, and nothing is lost unless the ring overflows between two of its passes -- which is counted and reported on close, never silent.
class Device final : public Sink
A MIDI output port as a Sink: winmm here, ALSA's virtual rawmidi port on Linux, a CoreMIDI destination on macOS, with the same calls on each.
enumerate(), open() and close() belong to the main thread. send() is the one call made from the audio thread: it sends at once, ignores the timestamp, and takes short messages only (SysEx has no path out here). open(index) takes an index into enumerate()'s list, closes whatever was open first, and says why in error() when it fails (and clears it when it does not: error() is about the latest open). close() -- also run by the destructor -- releases the port; winmm's resets it first, so no note is left sounding on the other end. Owns OS handles: do not copy one.
send() never waits for the port. A message the driver will not take now -- winmm busy, ALSA's buffer full, CoreMIDI refusing -- is dropped and counted: droppedEvents() says how many since open(), and error() says so after close(), as FileSink does for its ring.