This is the technical companion to my DAWs on Linux post. The code lives at github.com/Esl1h/linux-daw-ssl-lowlatency. Here I explain what it does and why, no hand-holding.
The problem
Under PipeWire, the sound card is never yours. PipeWire keeps a node on the ALSA device and mixes everything through it. For desktop audio that is correct. For playing an instrument through an amp simulator it adds a layer of buffering you do not want. The lowest latency path is the ALSA backend opening the hardware device directly (hw:CARD), in exclusive mode, which means PipeWire has to let go of the card first.
Ardour and its derivatives (Mixbus, LiveTrax) request that release through the D-Bus device reservation protocol (org.freedesktop.ReserveDevice1), and PipeWire yields. REAPER does not implement reservation, so it collides with PipeWire and either fails to open the device or falls back to something else. The whole repository exists to close that gap uniformly for every DAW.
The mechanism
WirePlumber exposes the card as a device object with selectable profiles. For the SSL 2+ MkII those are off (index 0), HiFi (1) and pro-audio (2). Setting the profile to off makes PipeWire release the kernel PCM, which stays available to any ALSA client. So the wrapper is conceptually three steps: read the device id and its current profile, set the profile to off, run the DAW, and on exit set the profile back.
The wrapper
bin/daw-alsa-ssl is the whole thing. The interesting part is read_dev, which pulls the device id and active profile index out of pw-dump JSON, matching by device.name:
read_dev() {
local dump
dump="$(pw-dump 2>/dev/null)" || return 0
[ -n "$dump" ] || return 0
if command -v python3 >/dev/null 2>&1; then
printf '%s' "$dump" | python3 -c '
import json, sys
match = sys.argv[1]
try:
data = json.load(sys.stdin)
except Exception:
sys.exit(0)
for o in data:
info = o.get("info") or {}
props = info.get("props") or {}
if props.get("device.name") == match:
idx = 1
for p in (info.get("params") or {}).get("Profile", []):
if p.get("index") is not None:
idx = p["index"]
print(o.get("id"), idx)
break
' "$CARD_MATCH"
elif command -v jq >/dev/null 2>&1; then
printf '%s' "$dump" | jq -r --arg m "$CARD_MATCH" '
[ .[] | select(.info.props."device.name" == $m) ] as $x
| if ($x | length) == 0 then empty
else $x[0] as $o
| (($o.info.params.Profile // []) | map(.index) | last) as $idx
| "\($o.id) \(if $idx == null then 1 else $idx end)"
end'
fi
}
Three decisions worth calling out.
Matching is by device.name (alsa_card.usb-...), not by the numeric WirePlumber id, because that id changes across reboots and replugs. The name is stable.
Parsing JSON needs a real parser. Pure bash with grep and sed on pw-dump output is fragile: the friendly name “SSL 2+ Mk II” contains digits, so a naive number extraction from wpctl status returns garbage (572 instead of 57). So the wrapper prefers python3 (stdlib json, present on essentially every distro) and falls back to jq. Neither is a hard dependency you install for this; python3 is already there, and jq covers the rare case it is not.
One bug worth documenting. The first version fed pw-dump on a pipe while passing the Python script as a heredoc to python3 -. With both a pipe and a heredoc, the pipe wins as stdin, so Python read the JSON as its program and threw a traceback. read_dev silently returned nothing on every run, which meant the card was never freed and REAPER never got exclusive access. The fix is to pass the program with python3 -c and keep stdin for the data. Obvious in hindsight, invisible until you check /proc/asound.
The rest is bookkeeping. Read the id and profile, set a trap so the profile is restored on any exit (normal, INT or TERM), switch to off, then exec the DAW:
DEV_LINE="$(read_dev)"
DEV_ID="${DEV_LINE%% *}"
PREV_PROFILE="${DEV_LINE##* }"
restore() {
[ -n "${DEV_ID:-}" ] && command -v wpctl >/dev/null 2>&1 \
&& wpctl set-profile "$DEV_ID" "${PREV_PROFILE:-1}" >/dev/null 2>&1
}
trap restore EXIT INT TERM
if [ -n "${DEV_ID:-}" ] && command -v wpctl >/dev/null 2>&1; then
wpctl set-profile "$DEV_ID" 0 >/dev/null 2>&1
sleep 0.4
fi
"$@"
The target card is DAW_ALSA_CARD, defaulting to the SSL 2+ MkII. Point it at any interface by exporting the PipeWire device.name (find it with pw-dump | grep device.name). If the card is not found, or neither python3 nor jq nor wpctl is present, the wrapper degrades gracefully: it just runs the DAW, and the Ardour family still frees the card via D-Bus on its own. Only REAPER loses the automatic release.
Launchers and installer
The launchers are plain .desktop templates with a __WRAPPER__ placeholder in the Exec line:
Exec=__WRAPPER__ /opt/REAPER/reaper %F
install.sh substitutes the placeholder with the installed wrapper path, writes each launcher into ~/.local/share/applications/, and skips any DAW whose binary is not present, so you do not get dead menu entries for software you never installed. It is XDG-aware (~/.local/bin, ~/.local/share), needs no root, and runs update-desktop-database at the end. uninstall.sh reverses it.
REAPER settings
REAPER does not do device reservation, so it is the reason the wrapper exists, and it needs its audio backend pointed at the raw device. The relevant reaper.ini keys, after configuring ALSA in Preferences:
linux_audio_mode=1 ; ALSA
linux_audio_bsize=128 ; block size
linux_audio_bufs=2 ; number of blocks
linux_audio_srate=48000
mode=1 is ALSA. With a 128 sample block and 2 blocks at 48 kHz, the device buffer is 256 frames. Dropping from 3 blocks to 2 is the difference between a 384 and a 256 frame buffer, a real latency cut you take as far as your plugin load stays glitch free.
System tuning
Three things at the OS level matter more than any per-DAW knob. The CPU governor should be performance, not schedutil, so clocks do not ramp during a take. PipeWire’s default clock rate belongs at 48000 (default.clock.rate = 48000 in a pipewire.conf.d drop-in), with 44100 kept in allowed-rates so plain playback can still switch down. And realtime scheduling should be granted, either through RTKit (which PipeWire uses by default) or by adding your user to a group with an rtprio limit (on Fedora, the @pipewire group carries rtprio 70 and memlock).
Verifying it actually works
Do not trust the DAW’s own latency readout. Check the kernel. With the DAW open on the SSL, hw_params shows the live format:
$ cat /proc/asound/card2/pcm0p/sub0/hw_params
format: S32_LE
rate: 48000 (48000/1)
period_size: 128
buffer_size: 256
fuser -v /dev/snd/pcmC2D0p /dev/snd/pcmC2D0c shows reaper holding both the playback and capture PCM, and the SSL’s PipeWire profile reads off. Together that is the proof: PipeWire has let go, the DAW owns the hardware directly, at the buffer you asked for. Close the DAW and the profile goes back to HiFi, desktop audio returns, and hw_params reads closed.
Install
git clone https://github.com/Esl1h/linux-daw-ssl-lowlatency.git
cd linux-daw-ssl-lowlatency
./install.sh
Then set the ALSA device once in each DAW and open them from the generated launchers.
PT/BR
Este é o complemento técnico do meu post DAWs no Linux. O código está em github.com/Esl1h/linux-daw-ssl-lowlatency.
O problema
O PipeWire mantém um nó no dispositivo ALSA e mistura tudo por ele. Para o áudio do desktop isso é o certo. Para tocar um instrumento por um simulador de amplificador, adiciona uma camada de buffer que você não quer. O caminho de menor latência é o backend ALSA abrindo o dispositivo de hardware direto (hw:CARD), em modo exclusivo, o que exige que o PipeWire solte a placa antes.
O Ardour e derivados (Mixbus, LiveTrax) pedem essa liberação pelo protocolo de reserva de dispositivo via D-Bus (org.freedesktop.ReserveDevice1), e o PipeWire cede. O REAPER não implementa reserva, então ele colide com o PipeWire e ou falha ao abrir o dispositivo ou cai em outro backend. O repositório inteiro existe para fechar essa lacuna de forma uniforme para todos os DAWs.
O mecanismo
O WirePlumber expõe a placa como um objeto device com perfis selecionáveis. Para a SSL 2+ MkII eles são off (índice 0), HiFi (1) e pro-audio (2). Colocar o perfil em off faz o PipeWire liberar o PCM do kernel, que fica disponível para qualquer cliente ALSA. Então o wrapper é, conceitualmente, três passos: ler o id do device e o perfil atual, pôr o perfil em off, rodar o DAW e, na saída, devolver o perfil.
O wrapper
O bin/daw-alsa-ssl é tudo. A parte interessante é o read_dev, que extrai o id do device e o índice do perfil ativo do JSON do pw-dump, casando por device.name. Três decisões merecem destaque.
O casamento é por device.name (alsa_card.usb-...), não pelo id numérico do WirePlumber, porque esse id muda entre reboots e replugues. O nome é estável.
Parsear JSON exige um parser de verdade. Bash puro com grep e sed na saída do pw-dump é frágil: o nome amigável “SSL 2+ Mk II” tem dígitos, então uma extração ingênua de número do wpctl status devolve lixo (572 em vez de 57). Por isso o wrapper prefere python3 (o módulo json da stdlib, presente em praticamente toda distro) e cai para jq. Nenhum dos dois é dependência que você instala para isso; o python3 já está lá, e o jq cobre o caso raro de não estar.
Um bug que vale documentar. A primeira versão passava o pw-dump por um pipe enquanto entregava o script Python por heredoc ao python3 -. Com pipe e heredoc juntos, o pipe vence como stdin, então o Python leu o JSON como programa e deu traceback. O read_dev retornava vazio silenciosamente a cada execução, o que significa que a placa nunca era liberada e o REAPER nunca ganhava acesso exclusivo. A correção é passar o programa com python3 -c e deixar o stdin para os dados. Óbvio em retrospecto, invisível até você conferir o /proc/asound.
O resto é contabilidade: ler id e perfil, armar um trap que restaura o perfil em qualquer saída (normal, INT ou TERM), trocar para off e então executar o DAW. A placa alvo é a DAW_ALSA_CARD, com padrão SSL 2+ MkII. Aponte para qualquer interface exportando o device.name do PipeWire (ache com pw-dump | grep device.name). Se a placa não for encontrada, ou se faltar python3, jq e wpctl, o wrapper degrada com elegância: só roda o DAW, e a família Ardour ainda libera a placa via D-Bus sozinha. Só o REAPER perde a liberação automática.
Launchers e instalador
Os launchers são templates .desktop com um placeholder __WRAPPER__ na linha Exec. O install.sh substitui o placeholder pelo caminho instalado do wrapper, escreve cada launcher em ~/.local/share/applications/ e pula qualquer DAW cujo binário não exista, então você não fica com entradas mortas no menu para software que nunca instalou. Ele respeita o XDG (~/.local/bin, ~/.local/share), não precisa de root e roda o update-desktop-database no fim. O uninstall.sh reverte.
Ajustes do REAPER
O REAPER não faz reserva de dispositivo, por isso é a razão de o wrapper existir, e precisa do backend de áudio apontado para o dispositivo cru. As chaves relevantes do reaper.ini, depois de configurar ALSA nas Preferences:
linux_audio_mode=1 ; ALSA
linux_audio_bsize=128 ; block size
linux_audio_bufs=2 ; número de blocos
linux_audio_srate=48000
mode=1 é ALSA. Com bloco de 128 amostras e 2 blocos a 48 kHz, o buffer do dispositivo é 256 frames. Baixar de 3 para 2 blocos é a diferença entre um buffer de 384 e um de 256 frames, um corte real de latência que você leva até onde a carga de plugins ficar sem estalos.
Ajustes de sistema
Três coisas no nível do SO importam mais que qualquer botão por DAW. O governor da CPU deve ser performance, não schedutil, para os clocks não oscilarem durante uma gravação. O clock padrão do PipeWire deve ficar em 48000 (default.clock.rate = 48000 num drop-in em pipewire.conf.d), com 44100 mantido em allowed-rates para a reprodução comum ainda poder cair para 44.1. E o escalonamento em tempo real deve ser concedido, seja pelo RTKit (que o PipeWire usa por padrão) ou adicionando seu usuário a um grupo com limite rtprio (no Fedora, o grupo @pipewire carrega rtprio 70 e memlock).
Verificando que funciona de verdade
Não confie no medidor de latência do próprio DAW. Confira o kernel. Com o DAW aberto na SSL, o hw_params mostra o formato ao vivo:
$ cat /proc/asound/card2/pcm0p/sub0/hw_params
format: S32_LE
rate: 48000 (48000/1)
period_size: 128
buffer_size: 256
O fuser -v /dev/snd/pcmC2D0p /dev/snd/pcmC2D0c mostra o reaper detendo o PCM de saída e o de entrada, e o perfil do SSL no PipeWire lê off. Juntos, isso é a prova: o PipeWire soltou, o DAW é dono do hardware direto, no buffer que você pediu. Feche o DAW e o perfil volta para HiFi, o áudio do desktop retorna e o hw_params lê closed.
Instalação
git clone https://github.com/Esl1h/linux-daw-ssl-lowlatency.git
cd linux-daw-ssl-lowlatency
./install.sh
Depois, ajuste o dispositivo ALSA uma vez em cada DAW e abra pelos launchers gerados.