JACK backend: override default output connection

I’m on Arch using Pipewire, and while the default behavior of the JACK backend works well for the general case, being able to set a node name/id for it to try to connect to on startup would be a huge QoL feature for my setup.

I use EasyEffects as a DSP for my Framework’s speakers, and while EE itself has no functionality for automatically picking up and redirecting JACK clients as it does for PulseAudio ones, manually changing the connections after launch with qpwgraph gives not only the benefit of speaker EQ correction, but also automatically following headphone plug/unplug with little to no appreciable additional latency. Being able to set up Renoise to connect to the EasyEffects sink instead of the nominal “default” would sand off a proverbial rough patch in my use case.

Personally I don’t think that is the job of the Renoise standard functions.
I would suggest using qjackctl’s patchbay or something similar.

I mean, I’d set it up to automatically happen on pipewire’s end, but wireplumber’s configuration is frankly inscrutable (I’ve been doing it manually with a Pipewire-specific equivalent of that application, I just figured I might as well ask and see if it’s an easy add or not)

Yes, the pipewire and wireplumber settings are strictly documented and if done well are very flexible and stable, but they are difficult because they require interpretation and application work.
Is it possible to tell us the details of how you configured them?
I would be happy to paste the contents of the configuration file.
I think qjackctl’s patchbay is relatively easy to configure.
I don’t use qpwgraph because it seems a bit cumbersome, but I think you can save the setup and edit the configuration file to reproduce the intended connection.
It may be useful if used well.

Sorry, this setup seems quite difficult.
After starting Renoise, the default output configured in pulseaudio is inevitably connected to it, and I have to disconnect it manually.
Perhaps I can use wireplumber to make it not connect to the default output, but I haven’t figured out how to do that yet.

@syliph Ok, I see, if I enable the option Reset all connections on patchbay activation in qjackctl and then invoke Renoise in the following form, I can have Renoise disconnect the default connection.

pkill -x qjackctl ; renoise & ; sleep 2 ; qjackctl &

However, it would certainly be nice to be able to set the default output destination in Renoise so that we do not have to do this.
Changing the output destination after starting Renoise may cause crackling noise. This is obviously undesirable when assuming that you are in a live performance, etc.

I’m not too familiar with the inner workings but it seems like for other applications like for example mpv, will allow specifying the output port, but by default will pick the first physical device. (like mpv -ao=jack --jack-port=“DT770 EQ Filter” audio.wav)
For renoise however, it doesn’t allow specifying and it will always go to some “default” audio device, which is way too loud and bypasses my global EQ. It’s not even the device that is set as the default in wireplumber, but maybe this is not exposed through the jack interface.

I’m also running just pipewire & wireplumber, with pipewire also acting as a jack server for jack clients.

I have tried to configure this using wireplumber however the documentation for configuring jack clients seems to be very sparse.

Anyone know how to handle this?
Maybe if any renoise devs can check if it’s picking some kind of default physical jack device, which seems to be the case.

My current conclusion is that the most reliable way to manage nodes is essentially to use pw-link or similar tools via shell scripts.
I believe that using qjackctl or other node management programs could lead to conflicts with WirePlumber, thereby complicating matters.
While I wish there were a simpler method, I haven’t found any other way to ensure relatively reliable operation during live performances in front of an audience.

#!/bin/bash
#### pw-link-live.sh
#set -x

# function
# --- Common Helper: Get ID Array ---
_get_id_array() {
    local mode=$1; shift
    local patterns=("$@")
    local ids=()
    for patt in "${patterns[@]}"; do
        while read -r id; do
            [[ -n "$id" ]] && ids+=("$id")
        done < <(pw-link "$mode"I | grep -E "$patt" | awk '{print $1}')
    done
    echo "${ids[@]}"
}

# --- seri_patch: 1-to-1 sequential patching, stops when outputs (o) are exhausted ---
seri_patch() {
    local -n o_p=$1; local -n i_p=$2
    local i_ids=( $(_get_id_array -i "${i_p[@]}") )
    local i_count=${#i_ids[@]}

    for patt in "${o_p[@]}"; do
        local o_ids=( $(_get_id_array -o "$patt") )
        # Connect 1-to-1 until either output or input ports are exhausted
        local limit=$(( ${#o_ids[@]} < i_count ? ${#o_ids[@]} : i_count ))
        
        for (( idx=0; idx<limit; idx++ )); do
            pw-link "${o_ids[$idx]}" "${i_ids[$idx]}" 2>/dev/null
        done
    done
}

# --- para_patch: Parallel patching while maintaining channel attributes (L/R) ---
para_patch() {
    local -n o_p=$1; local -n i_p=$2
    
    # Retrieve all ports on the input side
    local i_ids=( $(_get_id_array -i "${i_p[@]}") )
    local i_count=${#i_ids[@]}

    if [[ $i_count -eq 0 ]]; then return 1; fi

    echo "=== Starting Stereo Distribution (Total Inputs: $i_count) ==="

    for patt in "${o_p[@]}"; do
        local o_ids=( $(_get_id_array -o "$patt") )
        
        # Process each port on the output side
        for (( o_idx=0; o_idx<${#o_ids[@]}; o_idx++ )); do
            local oid=${o_ids[$o_idx]}
            
            # Identify channel attribute (0=L, 1=R, 2=L...)
            local o_chan=$(( o_idx % 2 ))

            # Scan all input ports and connect only to matching attributes
            for (( i_idx=0; i_idx<$i_count; i_idx++ )); do
                local i_chan=$(( i_idx % 2 ))

                if [[ $o_chan -eq $i_chan ]]; then
                    pw-link "$oid" "${i_ids[$i_idx]}" 2>/dev/null
                fi
            done
        done
        echo "  [OK] '$patt' -> Distributed to corresponding channels of all targets"
    done
}

~/.local/bin/pw-link-disconnect-all.sh
# pw-link-disconnect-all.sh:
# #!/bin/bash
# pw-link -lI | grep " |[->]" | awk '{ print $1,$3 }' | xargs -I{} pw-link -d {}

# --- Configuration: Listed in order of priority ---
out_to_monitor=(
    "renoise:output_01"
    "REAPER:out[12]$"
)
in_monitor=(
    "alsa_output.pci-0000_00_1f.3.analog-stereo:playback_F"
    "alsa_output.usb-LOUD_Technologies_Inc._ProFX-00.pro-output-0:playback_AUX[23]$"
)
out_to_floor=(
    renoise:output_02
    REAPER:out[34]$
)
in_floor=(
    alsa_output.usb-BurrBrown_from_Texas_Instruments_USB_AUDIO_CODEC-00.pro-output-0:playback_AUX[01]$
    alsa_output.usb-ZOOM_Corporation_UAC-2_000000000000000000000000250B81D4-00.pro-output-0:playback_AUX[01]$
)
out_to_default_capture_1=(
    alsa_input.usb-LOUD_Technologies_Inc._ProFX-00.pro-input-0:capture_AUX[01]$
)
in_default_capture_1=(
    renoise:input_01
    REAPER:in[12]$
)
out_to_default_capture_2=(
    alsa_input.usb-ZOOM_Corporation_UAC-2_000000000000000000000000250B81D4-00.pro-input-0:capture_AUX[01]$
)
in_default_capture_2=(
    REAPER:in[34]$
)
out_loopback_floor=(
    alsa_output.usb-ZOOM_Corporation_UAC-2_000000000000000000000000250B81D4-00.pro-output-0:monitor_AUX[01]$
)
in_loopback_capture=(
    REAPER:in25[56]$
)
if [ "$1" == "super-renoise-capture" ] ; then
    out_renoise_to_reaper=(
        renoise:output_
    )
else
    out_renoise_to_reaper=(
        renoise:output_02
    )
fi
in_reaper_renoise=( # REAPER: in17-254
    'REAPER:in(1[7-9]|[2-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-4])$'
)

# --- Execution ---

# To Monitor
para_patch out_to_monitor            in_monitor

# To Floor
para_patch out_to_floor              in_floor

# To Capture
# REAPER: in1-8 are reserved for any physical inputs
#         in9-16 are reserved for R16 inputs
para_patch out_to_default_capture_1 in_default_capture_1
para_patch out_to_default_capture_2 in_default_capture_2

# To Loopback
# REAPER: in255-256 for Loopback recording (typically records Floor output)
para_patch out_loopback_floor in_loopback_capture

# REAPER: in17-254 for Renoise
# (Simplified version uses output2 L/R only; full version uses all outputs)
seri_patch out_renoise_to_reaper in_reaper_renoise

For now I’ve just settled for opening qpwgraph(similar to qjackctl but for pipewire) whenever I open renoise, have it load a graph setup with exclusively renoise connected to the desired node, and then just disable exclusive mode in qpwgraph if I want to run other apps that use sound. It’s a bit janky but works, would still prefer some kind of default output setting (or no default, so I can link it manually).

I have considered something like that script with pw-link however when renoise decides to reinitialize the audio, for example when you click reinitialize in the settings, it will just reconnect to some “default” output, so you’d have to rerun the script or keep it running and polling.