Using JACK for lower-latency audio on Linux for piano practice

3 minute read

About a month and a bit in me learning the piano, I’ve had to rethink my remote piano learning setup. The latency was at points really annnoying (sometimes nearly a second) - it’s confusing when you hit a key and only hear it when you’re hitting the next key. So, time to look for a lower-latency setup.

The go-to (at this point atleast) piece of software for low-latency audio is JACK audio - I didn’t want to use it before because it sounded like too much of a hassle for my usecase, but in the end it was very easy to configure.

As before, I’m using FluidSynth, as a software synthesiser. Generated audio goes from FluidSynth and my headset microphone to JACK, and sent out to PulseAudio. On the other hand, audio generated by client applications (such as Skype and Zoom) is sent from PulseAudio to JACK, and then finally outputted to the headset.

Installing FluidSynth

Installing FluidSynth is as easy as apt install fluidsynth and adding the line

OTHER_OPTS='-a jack -m alsa_seq -r 44100'

to /etc/defaults/fluidsynth

Why did I pick a sampling rate of 44kHz? Because this is what my headset works with.
I’ve also removed the auto-start as I want this to only be running when JACK is running - I’ve noticed that FluidSynth starts spinning like mad after a while when running without anything attached to it.

JACK and Qjackctl

I’m using Qjackctl, a Qt application to control the JACK sound server daemon. When you install Qjackctl it will automatically pull in jackd. You’ll also want pulseaudio-module-jack, which gives integration between PulseAudio and JACK.

qjackctl default screenshot

Installing them is easy: apt install qjackctl pulseaudio-module-jack.

Qjackctl configuration

The parameters I changed in the config are:

Setup Settings

qjackctl setup settings

Item Value Description
Driver ALSA  
Interface hw:Seri This determines the default capture and playback device for JACK
(see Soundcard Selection to find which one)
Sample rate 44100 matching what my headset can process, and what I configured for FluidSynth
Frames/Period 256 Number of frames between JACK process() calls. The lower the better (until you start getting xruns)
Periods/Buffer 2 Number of periods (above) of playback latency.

Setup Options

qjackctl setup options

Setup Misc

qjackctl setup misc

  • start JACK audio server on application startup
  • don’t save JACK audio server configuration
  • Enable D-BUS interface
  • Enable JACK D-BUS interface

Startup script

I’m using the following script which is run by qjackctl before starting jackd:

$ cat jackd-start
systemctl restart fluidsynth --user
amixer -q -c Seri set Headset 100% unmute

pacmd set-sink-mute jack_out 0
pacmd set-default-sink jack_out
pacmd set-default-source jack_in

This script will (re)start FluidSynth, unmute my headset in ALSA, unmute the Pulseaudio jack_out sink and finally set the default sink and source for PulseAudio to the jack_out and jack_in respectively. This will make applications like Zoom or Skype pick those inputs and outputs by default.

To find which soundcard to use, see Soundcard selection.

Shutdown script

This stop script gets run before JACK shuts down:

$ cat jackd-stop
systemctl stop fluidsynth --user
pacmd set-sink-mute alsa_output.usb-Plantronics_Plantronics_Blackwire_3225_Series_1129BBD004004FF4BD2E6F2248C0D73E-00.analog-stereo 1

This will stop FluidSynth, and mute the headset. To figure out the device to mute, please refer to my previous article.

Patchbay configuration

Configuring the routing in qjackctl is easily through the patchbay or through the graph. qjackctl patchbay

qjackctl graph

  • The PulseAudio JACK sink will relay the output of your system to the system playback (the input from eg. Skype or Zoom)
  • Fluidsynth will take the MIDI input, and generate audio, which is sent to the system playback and to the PulseAudio Jack Source (which can be used with Skype or Zoom as a virtual microphone device)
  • The system capture device will be sent to the PulseAudio Jack Source, capturing the microphone of the system

The above links will make the magic work. Once configured (either in the patchbay or in the graph), you can save the setup to an XML file which can then be used at JACK startup so that it routes the audio the way you want.

Soundcard selection

Figuring out the soundcard to pass to amixer and JACK can be done by checking /proc/asound/cards

$ cat /proc/asound/cards
 0 [PCH            ]: HDA-Intel - HDA Intel PCH
                      HDA Intel PCH at 0xd0510000 irq 29
 1 [Loopback       ]: Loopback - Loopback
                      Loopback 1
 2 [Seri           ]: USB-Audio - Plantronics Blackwire 3225 Seri
                      Plantronics Plantronics Blackwire 3225 Seri at usb-0000:00:14.0-1.4, full speed
 3 [RD             ]: USB-Audio - RD
                      Roland RD at usb-0000:00:14.0-2, full speed

where you can see that the Plantronics headset has card identifier ‘Seri’. You can also use the number, but as these might move around, I wanted to use the name instead.

After applying the above configuration I’ve found that the latency for the piano audio is negligible! Happy me ;)