For years, I’ve had an M-Audio Evolution MK449C MIDI controller. It has plenty of nice controls: 8 rotary, 9 sliders, 10 toggleable buttons, 1 wheel, 1 return-to-centre wheel, and obviously also four octaves keys.
While (for the ~£50 I paid for it back in 2008) I can’t find any reason to fault it massively, I have finally decided to replace/augment it with a Roland A-88. That’s a full 88 weighted keys with piano mechanisms to add to the immersion. But also 3x the weight and with less controls.
Back when I used a desktop PC, I used the ultra-low latency JACK audio environment as standard, with the more friendly PulseAudio running on top of it. This way, I could plug the inputs and outputs of any programs together, for chaining effects, mixing, monitoring, etc. Like what KX Studio did for Creative sound cards, but on steroids.
On a laptop though, this isn’t so practical. JACK (and also PulseAudio with low fragment sizes) consumes quite a bit of CPU and battery, which isn’t ideal on a portable device. So I needed a convenient way to switch between PulseAudio→ALSA for normal use, and PulseAudio→JACK→ALSA for real-time use. Ideally in such a way that plugging in a MIDI controller causes switching to the JACK-enabled stack where PulseAudio has low fragment sizes (for low latency), and removing all MIDI controllers causes switching back to the (boot-default) PulseAudio with large fragment sizes (for low CPU usage).
Systemd to the rescue!
In addition to general system administration, systemd can also be used on a per-user basis (therefore not requiring root either). I chose to use systemd user services to orchestrate the audio configuration, since this is the exact kind of task that systemd excels at.
Simplified configuration is provided here:
Preparation
# Create directory in home folder for user-systemd units mkdir -p ~/.config/systemd/user # Enter the directory cd ~/.config/systemd/user # Create the unit files (use your preferred editor, e.g. gedit, nano, ed) vim pulse.service jack.service synth@.service keyboard.service
pulse.service
[Unit] Description=Pulseaudio Conflicts=jack.service [Service] Type=simple Environment="DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus" ExecStart=/usr/bin/pulseaudio -nF /etc/pulse/default.pa [Install] WantedBy=multi-user.target
jack.service
[Unit] Description=Jack Conflicts=pulse.service [Service] # Grant your self the right to use such limits in /etc/security/limits.d/ LimitNICE=-15 LimitRTPRIO=99 # Configure DEVICE environment variable to the ALSA name of your output device Type=simple Environment=DEVICE=hw:0 Environment=JACK_NO_AUDIO_RESERVATION=1 ExecStartPre=-/usr/bin/pulseaudio -k # Configure appropriate period size/count and sample-rate ExecStart=/usr/bin/jackd -d alsa -P ${DEVICE} -n 2 -p 128 -r 48000 -X raw [Install] WantedBy=multi-user.target
synth@.service
[Unit] Description=Fluidsynth with %I soundfont BindsTo=jack.service After=jack.service Before=keyboard.service Wants=keyboard.service [Service] LimitNICE=-15 LimitRTPRIO=99 # Configure path to your soundfonts Environment=SOUNDFONT=/opt/soundfonts/%I.sf2 Type=simple # Wait for JACK to start ExecStartPre=/usr/bin/sleep 1 # Configure appropriate sample rate & buffer size ExecStart=/usr/bin/fluidsynth --server --no-shell --audio-driver=jack --audio-bufcount=2 --midi-driver=jack --sample-rate=48000 --audio-bufsize=64 --connect-jack-outputs --gain 1 ${SOUNDFONT} [Install] WantedBy=multi-user.target
keyboard.service
[Unit] Description=Connect MIDI keyboard to Fluidsynth [Service] Type=oneshot # Wait for synth to start ExecStartPre=/usr/bin/sleep 2 # Configure regexes as appropriate ExecStart=/usr/bin/sh -c "/usr/bin/jack_connect $(jack_lsp | grep -xP 'system:midi_capture_\\d+' | tail -n1) $(jack_lsp | grep -xP 'fluidsynth:midi_\\d+' | tail -n1)" [Install] WantedBy=multi-user.target
Helper script
Helper script, placed in my user bin folder (~/.bin, or whatever you decided to call yours):
#!/bin/bash set -euo pipefail declare -r patch="${1:---help}" if [ "$patch" == '--help' ]; then echo "Patches:" ls -1d /opt/soundfonts/*.sf2 | xargs -n1 -I{} echo ' * {}' exit 1 fi systemctl --user stop "synth@*" if [ "$patch" == '--stop' ]; then systemctl --user start pulse echo 'Synth stopped' exit 0 fi systemctl --user start "synth@${patch}"
Usage
With soundfonts in the corresponding directory, it’s just a case of using udev rules to run the helper script with the name of the default soundfont as the argument when the controller is plugged in, and then to run the script with –stop argument when the last controller is removed.
The synth@.service and keyboard.service files aren’t necessary if you always use some other program which can connect things up for you (e.g. Ardour). I use them for convenience to give a quick and simple plug’n’play experience for when I’m feeling lazy.
I called the helper script “synth”, and can use it to run FluidSynth with a specific soundfont:
# Loads bosendorfer.sf2 synth bosendorfer