-
Notifications
You must be signed in to change notification settings - Fork 146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
native_midi: Add support for native MIDI on Linux #595
base: main
Are you sure you want to change the base?
Conversation
I guess alsa-lib-devel needs to be present for this (version min required?) and that needs checking in cmake'ry and condition added to new source. (@madebr may help with that if this is accepted.) |
Indeed. I have no idea what the minimum version is, as if I recall correctly none of the
I assumed that a Linux system will always come with headers for libasound, at least in the context of SDL_Mixer, but you are correct. I'll add a proper check in cmake. |
elseif("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") | ||
list(APPEND SDL3MIXER_MIDI_NATIVE_ENABLED TRUE) | ||
target_sources(${sdl3_mixer_target_name} PRIVATE src/codecs/native_midi/native_midi_linux_alsa.c ${midi_common_sources}) | ||
target_link_libraries(${sdl3_mixer_target_name} PRIVATE asound) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think linking to asound
might be an issue: not everybody has alsa installed. It should also be possible to load alsa dynamically (using SDL_LoadObject
).
Also, should we call alsa "native"? Isn't it "just" a library, that happens to be *nix-only, just like pipewire? Perhaps define a new SDL3MIXER_MIDI_ALSA
option?
cmake_dependent_option(SDL3MIXER_MIDI_ALSA "Support for playing MIDI with alsa-lib" ON "SDL3MIXER_MIDI;UNIX;NOT APPLE" OFF)`
cmake_dependent_option(SDL3MIXER_MIDI_ALSA_SHARED "Load alsa-lib dynamically" ON "SDL3MIXER_MIDI_ALSA" OFF)`
With those addressed, I'll help with the cmake changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it "just" a library, that happens to be Linux-only, just like pipewire?
ALSA (and in this case snd_seq.*
) is a kernel module/implementation. libasound is a library to make talking to the kernel easier. PipeWire is a userspace daemon on the other hand that also depends on ALSA support in the kernel (to output audio to physical ports), and runs on top of it as that is how the Linux kernel itself implements audio.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, it's a "helper library", so not guaranteed to be installed on a system (forget about my remark about pipewire).
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.
e912fc3
to
d7f5641
Compare
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:
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 variableSDL_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. For example, on a modern system with PipeWire one might want to route the application to a PW MIDI client through the bridge: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 byaplaymidi
. 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.This is the SDL3 port of my patch, and has only been tested against
playmus
, as I know of no SDL3 applications that support MIDI playback, and the SDL2 ones that do, still load the SDL2 version of SDL_Mixer when ran with sdl2-compat.The SDL2 version can be found at native-midi-linux, and once this is reviewed and hopefully merged, I'd like to get that one in for SDL2_Mixer due to the above, although I understand if you do not want to introduce new code to it.