MIDI(4)
π MIDI(4) β Kernel Interfaces Manual π
okay so bestie, we're looking at the midi page from the Kernel Interfaces Manual, and it's literally the device-independent MIDI driver layer. It's giving very "I sit over anything that can source or sink a MIDI stream" energy. π
π NAME π
midi β device-independent MIDI driver layer
β¨ SYNOPSIS β¨
midi* at midibus?
midi* at pcppi?
pseudo-device sequencer
#include <sys/types.h>
#include <sys/midiio.h>
πΈ DESCRIPTION πΈ
The midi driver is the machine-independent layer over anything that can source or sink a MIDI data stream. That includes a physical MIDI IN or MIDI OUT jack on a soundcard, cabled to some external synthesizer or input controller, an on-board programmable tone generator, or a single jack, synthesizer, or controller component inside a complex USB or IEEE1394 MIDI device that has several such components and shows up as several MIDI streams. π¦
π§ Concepts π§
One MIDI data stream is a unidirectional stream of MIDI messages, like what could be carried over one MIDI cable in the MIDI 1.0 specification. Many MIDI messages carry a four-bit channel number, which creates up to 16 MIDI channels inside a single MIDI stream. A stream can have multiple consumers, and each one can be configured to react only to messages on specific channels; the sets of channels different consumers react to don't have to be disjoint. Lots of modern devices β multitimbral keyboards and tone generators, for example β listen on all 16 channels, or you can think of them as collections of 16 independent consumers that each listen on one channel. MIDI also defines some messages that take no channel number, and those apply to all consumers of the stream they're sent on. For an inbound stream, midi is a promiscuous receiver: it captures all messages no matter the channel number. For an outbound stream, the writer can specify a channel number per message; there's no notion of binding the stream to one destination channel in advance. π«Ά
A single midi device instance is the endpoint of one outbound stream, one inbound stream, or one of each. In that third case, the write and read sides are independent MIDI streams. For example, a soundcard driver may map its MIDI OUT and MIDI IN jacks to the write and read sides of a single device instance, but those jacks can be cabled to completely different pieces of gear. Info from dmesg(8), plus a diagram of any external MIDI cabling, will help clarify the mapping. π¦
π Underlying drivers and MIDI protocol π
Drivers that midi can attach to include soundcard drivers β many of which support a UART resembling Roland's MPU401 and handled by mpu(4) β USB MIDI devices via umidi(4), and on-board devices that can make sounds, whether that's a lowly PC speaker or a Yamaha OPL. Serial port and IEEE1394 connections are currently science fiction. π
The MIDI protocol permits some forms of message compression, such as running status and hidden note-off. Received messages on inbound streams are always canonicalized by midi before presentation to higher layers. Messages for transmission are accepted by midi in any valid form. β¨
πͺ Device access πͺ
You can get at midi device instances through the raw device nodes, /dev/rmidiN, or through the sequencer, /dev/music.
π§ Raw MIDI access π§
A /dev/rmidiN device supports read(2), write(2), ioctl(2), select(2)/poll(2) and the corresponding kevent(2) filters, and it may be opened only when it's not already open. It can be opened in O_RDONLY, O_WRONLY, or O_RDWR mode, but a later read(2) or write(2) will return -1 if the device has no associated input or output stream, respectively.
Bytes written are passed as quickly as possible to the underlying driver as complete MIDI messages; a maximum of two bytes at the end of a write(2) may remain buffered if they don't complete a message, until they're completed by a following write(2). π«Ά
A read(2) will not block or return EWOULDBLOCK when it could immediately return any nonzero count, and MIDI messages received are available to read(2) as soon as they're complete, with a maximum of two received bytes remaining buffered if they don't complete a message.
Since all MIDI messages are three bytes or fewer except for System Exclusive, which can have arbitrary length, these rules imply that System Exclusive messages are the only ones where some bytes can be delivered before all of them are available. π
System Realtime messages are passed with minimum delay in either direction, ahead of any possible buffered incomplete message. Because of that, they'll never interrupt any MIDI message except possibly System Exclusive.
A read(2) with a buffer large enough to accommodate the first complete message available will be satisfied with as many complete messages as will fit. A buffer too small for the first complete message will be filled to capacity. Therefore, an application that reads from an rmidi device with buffers of three bytes or larger need never parse across read boundaries to assemble a received message, except possibly in the case of a System Exclusive message. However, if the application reads through a buffering layer such as fread(3), this property will not be preserved. π
The midi driver itself supports the ioctl(2) operations FIOASYNC, FIONBIO, and FIONREAD. Underlying devices may support others. The value returned for FIONREAD reflects the size in bytes of complete messages (or System Exclusive chunks) ready to read. If the ioctl(2) returns n and a read(2) of size n is issued, n bytes will be read, but if a read(2) of size m < n is issued, fewer than m bytes may be read if m doesn't fall on a message/chunk boundary. π
Raw MIDI access can be used to receive bulk dumps from synthesizers, download bulk data to them, and so on. Simple patching of one device to another can be done at the command line, like this:
$ cat -u 0<>/dev/rmidi0 1>&0
That will loop all messages received on the input stream of rmidi0 back to its output stream in real time. But an attempt to record and play back music with
$ cat /dev/rmidiN >foo; cat foo >/dev/rmidiN
will be disappointing. The file foo will contain all of the notes that were played, but because MIDI messages carry no explicit timing, the βplaybackβ will reproduce them all at once, as fast as they can be transmitted. To preserve timing information, the sequencer device can be used. π
π Active Sensing π
The MIDI protocol includes a keepalive function called Active Sensing. In any receiver that hasn't received at least one Active Sense MIDI message, the feature is suppressed and no timeout applies. If at least one such message has been received, then the lapse of any subsequent 300 ms interval without receipt of any message reflects loss of communication, and the receiver should silence any currently sounding notes and return to non-Active-Sensing behavior. A sender using Active Sensing generally avoids 300 ms gaps in transmission by sending Active Sense messages (which have no other effect) as needed when there's no other traffic to send in the interval. This feature can be important for MIDI, which relies on separate Note On and Note Off messages, to avoid notes stuck on indefinitely if communication is interrupted before a Note Off message arrives. π
This protocol is supported in midi. An outbound stream will be kept alive by sending Active Sense messages as needed, beginning after any real traffic is sent on the stream, and continuing until the stream is closed. On an inbound stream, if any Active Sense has been received, then a process reading an rmidi device will see an end-of-file indication if the input timeout elapses. The stream remains open, the driver reverts to enforcing no timeout, and the process may continue to read for more input. Subsequent receipt of an Active Sense message will re-arm the timeout. As received Active Sense messages are handled by midi, they are not included among messages read from the /dev/rmidiN device. π
These rules support end-to-end Active Sensing behavior in simple cases without special action in an application. For example, in
$ cat -u /dev/rmidi0 >/dev/rmidi1
if the input stream to rmidi0 is lost, the cat(1) command exits; on the close(2) of rmidi1, midi ceases to send Active Sense messages, and the receiving device will detect the loss and silence any outstanding notes. π
πΉ Access through the sequencer πΉ
To play music using the raw MIDI API would require an application to issue many small writes with very precise timing. The sequencer device, /dev/music, can manage the timing of MIDI data in the kernel, to avoid such demanding real-time constraints on a user process. β¨
The /dev/music device can be opened only when it's not already open. When opened, the sequencer internally opens all MIDI instances existing in the system that aren't already open at their raw nodes; any attempts to open them at their raw nodes while the sequencer is open will fail. All access to the corresponding MIDI streams will then be through the sequencer.
Reads and writes of /dev/music pass eight-byte event structures defined in <sys/midiio.h> (which see for their documentation and examples of use). Some events correspond to MIDI messages, and carry an integer device field to identify one of the MIDI devices opened by the sequencer. Other events carry timing information interpreted or generated by the sequencer itself. π§
A message received on an input stream is wrapped in a sequencer event along with the device index of the stream it arrived on, and queued for the reader of /dev/music. If a measurable time interval passed since the last preceding message, a timing event that represents a delay for that interval is queued ahead of the received event. The sequencer handles output events by interpreting any timing event, and routing any MIDI message event at the proper time to an underlying output stream according to its device index. Therefore
$ cat /dev/music >foo; cat foo >/dev/music
can be expected to capture and reproduce an input performance including timing. π
The process of playing back a complex MIDI file is illustrated below. The file may contain several tracks β four, in this example β of MIDI events, each marked with a device index and a time stamp, that may overlap in time. In the example, a, b, and c are device indices of the three output MIDI streams; the left-hand digit in each input event represents a MIDI channel on the selected stream, and the right-hand digit represents a time for the event's occurrence. As illustrated, the input tracks are not firmly associated with output streams; any track may contain events for any stream. πΆ
So here's the whole picture, bestie β and honestly it's kind of iconic how much is going on here π
| | a2|4 |
a0|3 | c1|3 c0|3
| b0|2 b1|2 |
| b1|1 | c0|1
a0|0 | b0|0 |
v v v v
+---------------------------+
| merge to 1 ordered stream |
| user code, eg midiplay(1) |
+---------------------------+
b1|2
b0|2
c0|1
b1|1
b0|0
a0|0
v
_______+-------------+_______user
| /dev/music | kernel
| (sequencer) |
+-------------+
| 1 0
+-----' | '-----.
0 0 |
v v v
+-------+ +--------+ +---------+
|midi(4)| |midi(4) | |midi(4) |
|rmidia | |rmidib | |rmidic |
+-------+ +--------+ +---------+
| mpu(4)| |umidi(4)| |midisyn |
+-------+ +--------+ +---------+
| HW | | | opl(4) |
| MIDI | U +---------+
| UART | S | internal|
+-------+ B | tone |
| | |generator|
v | +---------+
external v
MIDI device external
MIDI device
Okay so this diagram is the main character of the whole page, ngl. Here's the vibe: your user process has to merge the tracks into a single stream of sequencer MIDI and timing events, in order by the timing you actually want β¨. Then the sequencer obeys those timing events and distributes the MIDI events out to the three destinations β in this particular picture that's two external devices (one hanging off a sound card UART, one off a USB interface) plus an OPL tone generator on a sound card. Slay, honestly π
π¬ NOTES (the fine print, bestie)
- Using
select(2)/poll(2)with the sequencer is supported, but here's the catch π« : there's no guarantee that awrite(2)won't block or returnEWOULDBLOCKif it begins with a timer-wait event β even ifselect(2)/poll(2)literally just told you the sequencer was writable. Rude, but that's the deal. - Okay this one's a little cursed π: if a realtime message gets delivered ahead of buffered bytes of an incomplete message, then in a saved byte stream that realtime message can look like it arrived up to 640 us earlier than it actually did β at MIDI 1.0 data rates. Higher data rates make the effect less significant, so at least there's that π
- And now for the deprecated one, put your seatbelt on π: there's another sequencer device,
/dev/sequencer, and it's provided only for backward compatibility with an obsolete OSS interface in which some sequencer events were four-byte records. It is not further documented here, and the/dev/musicAPI should be used in new code, periodt. Also the/dev/sequenceremulation is implemented only for writing, and even that might not be complete. Soβ¦ yeah, iykyk π
π οΈ IMPLEMENTATION NOTES
- Some hardware devices that support midi justβ¦ lack transmit-ready interrupts π. And some have the capability in hardware but currently lack driver support. How do you spot them? They get the annotation
(CPU-intensive output)indmesg(8)π. While they're suitable for music playback, they may have an objectionable impact on system responsiveness during bulk transmission β stuff like patch downloads β and are best avoided for that purpose if other suitable devices are present. Consider yourself warned, bestie π«Ά - Buffer space in midi itself is adequate for about 200 ms of traffic at MIDI 1.0 data rates, per stream. That's the whole budget, don't spend it all at once β
- Event counters record bytes and messages discarded because of protocol errors or buffer overruns, and you can view them with
vmstat -e. Genuinely useful for diagnosing flaky cables and other communication problems β like a little detective for your cables π΅οΈββοΈ - A raw sound generator uses the
midisynlayer to present a MIDI message-driven interface that midi can attach to. - Here's the clever bit β¨: midi accepts messages for transmission in any valid mixture of compressed or canonical form, but they're always presented to the underlying driver in the form it prefers. Drivers for simple UART-like devices register their preference for a compressed byte stream, while ones like
umidi(4)(which uses a packet protocol) ormidisyn(which interprets complete messages) register for intact canonical messages. This design eliminates the need for compression and canonicalization logic from every layer above and below midi itself. Highkey elegant πΈ
π FILES
/dev/rmidiN/dev/music/dev/sequencer
π ERRORS
On top of the other errors documented for the write(2) family of system calls, you can get EPROTO back if the bytes you're trying to write on a raw midi device would violate MIDI protocol π«. The protocol said no, babes.
π SEE ALSO
For ports using the ISA bus π§:
For ports using the PCI bus π:
π°οΈ HISTORY
The midi driver first appeared in NetBSD 1.4 π. It was overhauled and this manual page was rewritten for NetBSD 4.0. A glow-up for the ages π
π BUGS (aka the tea β)
- Some OSS sequencer events and
ioctl(2)operations are unimplemented, as<sys/midiio.h>notes. Not me pointing at the header file like π π - OSS source-compatible sequencer macros should be added to
<sys/soundcard.h>, implemented with the NetBSD ones in<sys/midiio.h>, so sources written for OSS can be easily compiled. That's the dream π«Ά - The sequencer blocks (or returns
EWOULDBLOCK) only when its buffer physically fills β which can represent an arbitrary latency because of buffered timing events. The consequence is kind of a lot: interrupting a process writing the sequencer may not interrupt music playback for a considerable time π. The fix would be for the sequencer to enforce a reasonable latency bound by examining timing events as they are enqueued and blocking appropriately. Future goals, bestie β¨ FIOASYNCenables signal delivery to the calling process only;FIOSETOWNis not supported. That's it, that's the whole bullet π- The sequencer can only be a timing master, but it does not send timing messages to synchronize any slave device. It also can't be slaved to timing messages received on any interface β which would presumably require a PLL algorithm similar to NTP's, plus expertise in that area to actually implement it. The sequencer ignores timing messages received on any interface and does not pass them along to the reading process, and the OSS operations to change that behavior are unimplemented. Soβ¦ it's giving "independent queen who doesn't text back" π
- The
SEQUENCER_TMR_TIMEBASEioctl(2)will report successfully setting any timebase up to ridiculously high resolutions, though the actual resolution β and therefore the jitter β is constrained byhz(9). Comparable sequencer implementations typically allow a selection from available sources of time interrupts that may be programmable. We're not doing that, apparently π - Okay this one's a real gotcha, pay attention π’: the device number in a sequencer event is treated on
write(2)as an index into the array of MIDI devices the sequencer has opened, but onread(2)as the unit number of the source MIDI device. These are usually the same if the sequencer has opened all the MIDI devices (that is, none was already open at its raw node when the sequencer was opened) β but they might not be the same otherwise. Two meanings, one field. Chaos π - There is at present no way to make reception nonpromiscuous, should anyone have a reason to want to. If you have a reason, sorry babe π
- There should be ways to override default Active Sense behavior. One obvious case: if an application is seen to send Active Sense explicitly, midi should refrain from adding its own. On receive, there should be an option to pass Active Sense through rather than interpreting it β for apps that wish to handle or ignore it themselves and never see EOF. Not implemented, but likeβ¦ it should be π
- When a midi stream is open by the sequencer, Active Sense messages received on the stream are passed to the sequencer and not interpreted by midi. And the sequencer at present neither does anything itself with Active Sense messages received, nor supports the OSS API for making them available to the user process. So they just sort ofβ¦ vibe there π¦
- System Exclusive messages can be received by reading a raw device, but not by reading the sequencer. They are discarded on receipt when the stream is open by the sequencer, rather than being presented as the OSS-defined sequencer events. Gone. Poof. π
midisynis too rudimentary at present to get satisfactory results from any onboard synth π. It lacks the required special interpretation of the General MIDI percussion channel in GM mode. More devices should be supported β some sound cards with synthesis capability have NetBSD drivers that implement theaudio(4)interface but not themidisyninterface. And the voice stealing algorithm does not follow the General MIDI Developer Guidelines. It's trying its best, okay π₯Ί- ALSA sequencer compatibility is lacking, but it's becoming important to applications π. It would require the function of merging multiple tracks into a single ordered stream to be moved from user space into the sequencer. Assuming the sequencer is driven by periodic interrupts, timing wheels could be used as in
hardclock(9)itself. Similar functionality will be in OSS4; with the right infrastructure it should be possible to support both π. And when merging MIDI streams, a notion of transaction is needed to group critical message sequences β if ALSA or OSS4 have no such notion, it should be provided as an upward-compatible extension. Big plans, bestie β¨ - And finally, our author's personal plea π«Ά: "I would rather have
open(2)itself return an error (by the POSIX descriptionENODEVlooks most appropriate) if a read or write mode is requested that is not supported by the instance, rather than lettingopen(2)succeed andread(2)orwrite(2)return-1, but so help me, the latter seems the more common Unix practice." We've all been there, king π
And that's the whole page, bestie! π This one came from the Debian folks, dated April 28, 2017, and you can find it filed under section MIDI(4) in your manual pages. Go forth and make some music, no cap πβ¨
π¦ where this page comes from
- nixpkgs unstable: netbsd-source 11.0
- Copyright (c) 1999-2017 The NetBSD Foundation, Inc
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms