Digital piano: Systemd user service to start low-latency synth and bind to MIDI controller

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

Playing with PostgreSQL: Window functions

Selecting items before/after match

Create test data

--
CREATE TABLE test (x SERIAL, y INT);
INSERT INTO test (y) VALUES
	(64), (48), (32), (16),
	(50), (40), (30), (20),
	(10), (60), (70), (80);
--

View data

--
SELECT * FROM test;
 
 x  | y  
----+----
  1 | 64
  2 | 48
  3 | 32
  4 | 16
  5 | 50
  6 | 40
  7 | 30
  8 | 20
  9 | 10
 10 | 60
 11 | 70
 12 | 80

--

With respect to x-ordering, get y-value of the item before and the item after the item with y=50:

--
SELECT * FROM (
	SELECT
		x,
		lag(y,1,0) OVER (ORDER BY x) AS y_prev,
		y AS y_this,
		lead(y,1,0) OVER (ORDER BY x) AS y_next
	FROM test) AS tmp
WHERE this = 50;

| x | y_prev | y_this | y_next |
+---+--------+--------+--------+
| 5 |     16 |     50 |     40 |

--

C++: Generic locker class for thread-safe proxy-access to arbitrary objects

I created a template class which can be used to provide thread-safe access to methods and fields of some target object.
It provides a convenient double-smart-pointer proxy mechanism for accessing one method or field of the target, and a functor mechanism for executing multiple operations on the target within one lock.
Naturally with a RAII solution, the double-smart-pointer style provides no thread-safety for iterator/pointer-based access or any other kind of access which involves taking pointers/references to the target and accessing them outside the lock. For such use cases, wrap the entire operation in the functor style instead.

Source is below and also available on Github

#if 0
(
set -eu
declare -r tmp="$(mktemp)"
g++ -std=c++14 -Wall -Wextra -o "$tmp" "$0"
valgrind --quiet "$tmp"
)
exit 0
#endif
/* Run this file with bash to compile+execute it */
#include <mutex>
#include <iostream>
#include <type_traits>

/* Demo: double-smart pointer for automatic locking of arbitrary class */

template <typename T>
class ThreadSafe
{
	T t;
	mutable std::mutex mx;
	class Proxy
	{
		std::unique_lock<std::mutex> lock;
		T& t;
	public:
		explicit Proxy(std::mutex& mx, T& t) : lock(mx), t(t) { std::cout << "  (lock)" << std::endl; }
		Proxy(Proxy&& src) : lock(std::move(src.lock)), t(src.t) { }
		~Proxy() { std::cout << "  (unlock)" << std::endl; }
		T *operator -> () { return &t; }
	};
public:
	template <typename ...Args>
	explicit ThreadSafe(Args&&... args) : t(std::forward<Args>(args)...) { }
	Proxy operator -> () { return Proxy(mx, t); }
	template <typename Func, typename R = typename std::enable_if<!std::is_void<typename std::result_of<Func(T&)>::type>::value>::type>
	auto operator () (const Func& func) {
		std::lock_guard<std::mutex> lock(mx);
		return func(t);
	}
	template <typename Func, typename R = typename std::enable_if<std::is_void<typename std::result_of<Func(T&)>::type>::value>::type>
	void operator () (const Func& func) {
		func( * operator -> () . operator -> () );
	}
};

int main(int argc, char *argv[])
{
	(void) argc;
	(void) argv;
	/* Initialise s as a thread-safe wrapper around a std::string */
	ThreadSafe<std::string> s("potato");
	/* For doing one operation, the pointer proxy style is convenient */
	s->append("es are awesome");
	/* Function style: Passed function is executed within lock and may do multiple operations */
	s([] (auto& str) { str += "!"; });
	/* Not thread safe, as our lock object expires before the pointer is used.  Use the function style in place of this: */
	std::cout << s->c_str() << std::endl;
	return 0;
}