MIDI(4) | Device Drivers Manual | MIDI(4) |
midi
—
midi* at midibus?
midi* at pcppi?
pseudo-device sequencer
#include <sys/types.h>
#include <sys/midiio.h>
midi
driver is the machine independent layer over
anything that can source or sink a MIDI data stream, whether 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 within a complex USB or IEEE1394 MIDI
device that has several such components and appears as several MIDI streams.
midi
is a promiscuous receiver,
capturing all messages regardless of channel number. For an outbound stream,
the writer can specify a channel number per message; there is 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 the
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. Information from
dmesg(8), and a diagram of any
external MIDI cabling, will help clarify the mapping.
midi
can attach 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 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.
midi
device instances can be through the raw
device nodes, /dev/rmidiN, or through the sequencer,
/dev/music.
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 do not complete a message, until 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 are complete, with a maximum of two received bytes remaining buffered
if they do not complete a message.
As 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 of which some bytes can be delivered before all are available.
System Realtime messages are passed with minimum delay in either direction, ahead of any possible buffered incomplete message. As a result, they will 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 does
not 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, as with
$ cat -u 0<>/dev/rmidi0
1>&0
$ cat /dev/rmidiN >foo; cat foo
>/dev/rmidiN
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
midi
ceases to send
Active Sense messages, and the receiving device will detect the loss and
silence any outstanding notes.
The /dev/music device can be opened only when it is not already open. When opened, the sequencer internally opens all MIDI instances existing in the system that are not 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
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.
| | 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
A user process must merge the tracks into a single stream of sequencer MIDI and timing events in order by desired timing. The sequencer obeys the timing events and distributes the MIDI events to the three destinations, in this case two external devices connected to a sound card UART and a USB interface, and an OPL tone generator on a sound card.
EWOULDBLOCK
if it begins with a timer-wait event, even
if
select(2)/poll(2)
reported the sequencer writable.
The delivery of a realtime message ahead of buffered bytes of an incomplete message may cause the realtime message to seem, in a saved byte stream, to have arrived up to 640 us earlier than it really did, at MIDI 1.0 data rates. Higher data rates make the effect less significant.
Another sequencer device, /dev/sequencer, is 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/music API should be used in new code. The /dev/sequencer emulation is implemented only for writing, and that might not be complete.
midi
lack
transmit-ready interrupts, and some have the capability in hardware but
currently lack driver support. They can be recognized by the annotation
(CPU-intensive output)
in
dmesg(8). While suitable for
music playback, they may have an objectionable impact on system responsiveness
during bulk transmission such as patch downloads, and are best avoided for
that purpose if other suitable devices are present.
Buffer space in midi
itself is adequate
for about 200 ms of traffic at MIDI 1.0 data rates, per stream.
Event counters record bytes and messages discarded because of
protocol errors or buffer overruns, and can be viewed with
vmstat -e
. They can be useful in diagnosing flaky
cables and other communication problems.
A raw sound generator uses the midisyn layer to
present a MIDI message-driven interface attachable by
midi
.
While midi
accepts messages for
transmission in any valid mixture of compressed or canonical form, they are
always presented to an underlying driver in the form it prefers. Drivers for
simple UART-like devices register their preference for a compressed byte
stream, while those like
umidi(4), which uses a packet
protocol, or midisyn, which interprets complete messages,
register for intact canonical messages. This design eliminates the need for
compression and canonicalization logic from all layers above and below
midi
itself.
EPROTO
can be returned if the bytes to be written on a
raw midi
device would violate MIDI protocol.
midi
driver first appeared in
NetBSD 1.4. It was overhauled and this manual page
rewritten for NetBSD 4.0.
<sys/midiio.h>
notes.
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.
The sequencer blocks (or returns
EWOULDBLOCK
) only when its buffer physically fills,
which can represent an arbitrary latency because of buffered timing events.
As a result, interrupting a process writing the sequencer may not interrupt
music playback for a considerable time. The sequencer could enforce a
reasonable latency bound by examining timing events as they are enqueued and
blocking appropriately.
FIOASYNC
enables signal delivery to the
calling process only; FIOSETOWN
is not
supported.
The sequencer can only be a timing master, but does not send timing messages to synchronize any slave device; it cannot be slaved to timing messages received on any interface (which would presumably require a PLL algorithm similar to NTP's, and expertise in that area to 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.
The SEQUENCER_TMR_TIMEBASE
ioctl(2) will report
successfully setting any timebase up to ridiculously high resolutions,
though the actual resolution, and therefore jitter, is constrained by
hz(9). Comparable sequencer
implementations typically allow a selection from available sources of time
interrupts that may be programmable.
The device number in a sequencer event is treated on write(2) as index into the array of MIDI devices the sequencer has opened, but on read(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 might not be the same otherwise.
There is at present no way to make reception nonpromiscuous, should anyone have a reason to want to.
There should be ways to override default Active Sense behavior. As
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
.
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
. 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.
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.
midisyn is 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 the audio(4) but not the midisyn interface. Voice stealing algorithm does not follow the General MIDI Developer Guidelines.
ALSA sequencer compatibility is lacking, but 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 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. 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.
I would rather have
open(2) itself return an error
(by the POSIX description ENODEV
looks most
appropriate) if a read or write mode is requested that is not supported by
the instance, rather than letting
open(2) succeed and
read(2) or
write(2) return -1, but so help
me, the latter seems the more common UNIX
practice.
April 28, 2017 | NetBSD 9.4 |