This adds support for native MIDI on Linux using the ALSA sequencer API.
Playback is performed by spawning a thread which processes SMF events,
converts them to ALSA SEQ ones, and forwards them to a synth client.
To ensure responsiveness (and not cause applications to freeze), the
sequencer API is used in nonblock mode. When an event is added to a
queue, it is first sent to a userspace buffer, which is eventually
flushed to a kernel buffer and then sent to the destination. This means
that events are processed in chunks until the buffer is filled which
then gets drained, and not in realtime.
A socketpair is set up for the main thread to control the playback
thread, which uses poll() to wait until IO can be performed on the
sequencer or a command can be received from the main thread.
The playback thread reports its status by writing to an atomic enum.
Two new hints are introduced:
- SDL_NATIVE_MUSIC_ALLOW_PAUSE
- SDL_NATIVE_MUSIC_NO_CONNECT_PORTS
Pausing is implemented by stopping the queue and setting the volume to 0
with a GM SysEx Master Volume message. Since at the time of writing none
of the most common use cases support it (FluidSynth, Emu10k1 WaveTable,
Linux OPL3 MIDI Synth), pausing is disabled by default as it is
preferred to have music playing instead of listening to hanging notes.
The hint SDL_NATIVE_MUSIC_ALLOW_PAUSE was added so that a user with a
compatible MIDI device can simply set the environment variable
SDL_NATIVE_MUSIC_ALLOW_PAUSE=1 to enable pausing.
The implementation outputs events to any client subscribed to its port.
When the hint SDL_NATIVE_MUSIC_NO_CONNECT_PORTS is set, it does not
attempt to automatically connect the port to a client. This might be
desired if the end user uses an external patchbay application.
Otherwise, it first checks for the environment variable
ALSA_OUTPUT_PORTS, and if successfully parsed and the client is found,
the output port is connected to it automatically. This env var is used
by aplaymidi. If it could not be parsed, or it is not set, then the
first MIDI synth client is preferred. If one is not found, then the
application connects its port to any available client as a last resort.