Initial commit
This commit is contained in:
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
# OPT-IN: PipeWire/WirePlumber pro-audio routing for the RME Fireface 802.
|
||||
#
|
||||
# Creates a stereo null sink ("rme-nullsink") and statically patches it to the
|
||||
# RME's AES outs (AUX12/13), sets the card to the "pro-audio" profile, and keeps
|
||||
# the null sink linked to whatever output is active — so Pulse/Wine apps always
|
||||
# see a stable stereo device. (guide: bennett.dev/auto-link-pipewire-ports-wireplumber)
|
||||
#
|
||||
# Hardware-specific: the RME serial is baked into rme-proaudio/auto-connect-ports.lua
|
||||
# ("Fireface 802 (24240739)") and rme-proaudio/alsa.conf (device.name) — edit those
|
||||
# for a different unit. Run on the machine with the interface, as your normal user:
|
||||
# ./rme-proaudio.sh
|
||||
set -euo pipefail
|
||||
[ "$(id -u)" -ne 0 ] || { echo "run as your user, not root (writes ~/.config)"; exit 1; }
|
||||
cd "$(dirname "$0")"
|
||||
src=rme-proaudio
|
||||
|
||||
PW="$HOME/.config/pipewire/pipewire.conf.d"
|
||||
WP="$HOME/.config/wireplumber"
|
||||
|
||||
install -Dm644 "$src/nullsink.conf" "$PW/nullsink.conf"
|
||||
install -Dm644 "$src/auto-connect-ports.lua" "$WP/scripts/auto-connect-ports.lua"
|
||||
install -Dm644 "$src/alsa.conf" "$WP/wireplumber.conf.d/alsa.conf"
|
||||
|
||||
# The component file registers the lua by ABSOLUTE path (~ isn't expanded here),
|
||||
# so generate it with $HOME rather than shipping a hardcoded path.
|
||||
install -Dm644 /dev/stdin "$WP/wireplumber.conf.d/99-auto-connect-ports.conf" <<EOF
|
||||
wireplumber.components = [
|
||||
{
|
||||
name = $WP/scripts/auto-connect-ports.lua, type = script/lua
|
||||
provides = custom.auto-connect-ports
|
||||
}
|
||||
]
|
||||
|
||||
wireplumber.profiles = {
|
||||
main = {
|
||||
custom.auto-connect-ports = required
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "RME pro-audio config installed. Restart audio to apply:"
|
||||
echo " systemctl --user restart wireplumber pipewire pipewire-pulse"
|
||||
@@ -0,0 +1,16 @@
|
||||
monitor.alsa.rules = [
|
||||
|
||||
{
|
||||
# Sets pro audio profile for RME Fireface
|
||||
matches = [
|
||||
{
|
||||
device.name = "alsa_card.usb-RME_Fireface_802__24240739__3A179EAE2137208-00"
|
||||
}
|
||||
]
|
||||
actions = {
|
||||
update-props = {
|
||||
device.profile = "pro-audio"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,196 @@
|
||||
-- As explained on: https://bennett.dev/auto-link-pipewire-ports-wireplumber/
|
||||
--
|
||||
-- This script keeps my stereo-null-sink connected to whatever output I'm currently using.
|
||||
-- I do this so Pulseaudio (and Wine) always sees a stereo output plus I can swap the output
|
||||
-- without needing to reconnect everything.
|
||||
|
||||
-- Link two ports together
|
||||
function link_port(output_port, input_port)
|
||||
if not input_port or not output_port then
|
||||
return nil
|
||||
end
|
||||
|
||||
local link_args = {
|
||||
["link.input.node"] = input_port.properties["node.id"],
|
||||
["link.input.port"] = input_port.properties["object.id"],
|
||||
|
||||
["link.output.node"] = output_port.properties["node.id"],
|
||||
["link.output.port"] = output_port.properties["object.id"],
|
||||
|
||||
-- The node never got created if it didn't have this field set to something
|
||||
["object.id"] = nil,
|
||||
|
||||
-- I was running into issues when I didn't have this set
|
||||
["object.linger"] = true,
|
||||
|
||||
["node.description"] = "Link created by auto_connect_ports"
|
||||
}
|
||||
|
||||
local link = Link("link-factory", link_args)
|
||||
link:activate(1)
|
||||
|
||||
return link
|
||||
end
|
||||
|
||||
-- Automatically link ports together by their specific audio channels.
|
||||
--
|
||||
-- ┌──────────────────┐ ┌───────────────────┐
|
||||
-- │ │ │ │
|
||||
-- │ FL ├────────►│ AUX0 │
|
||||
-- │ OUTPUT │ │ │
|
||||
-- │ FR ├────────►│ AUX1 INPUT │
|
||||
-- │ │ │ │
|
||||
-- └──────────────────┘ │ AUX2 │
|
||||
-- │ │
|
||||
-- └───────────────────┘
|
||||
--
|
||||
-- -- Call this method inside a script in global scope
|
||||
--
|
||||
-- auto_connect_ports {
|
||||
--
|
||||
-- -- A constraint for all the required ports of the output device
|
||||
-- output = Constraint { "node.name"}
|
||||
--
|
||||
-- -- A constraint for all the required ports of the input device
|
||||
-- input = Constraint { .. }
|
||||
--
|
||||
-- -- A mapping of output audio channels to input audio channels
|
||||
--
|
||||
-- connections = {
|
||||
-- ["FL"] = "AUX0"
|
||||
-- ["FR"] = "AUX1"
|
||||
-- }
|
||||
--
|
||||
-- }
|
||||
--
|
||||
function auto_connect_ports(args)
|
||||
local output_om = ObjectManager {
|
||||
Interest {
|
||||
type = "port",
|
||||
args["output"],
|
||||
Constraint { "port.direction", "equals", "out" }
|
||||
}
|
||||
}
|
||||
|
||||
local links = {}
|
||||
|
||||
local input_om = ObjectManager {
|
||||
Interest {
|
||||
type = "port",
|
||||
args["input"],
|
||||
Constraint { "port.direction", "equals", "in" }
|
||||
}
|
||||
}
|
||||
|
||||
local all_links = ObjectManager {
|
||||
Interest {
|
||||
type = "link",
|
||||
}
|
||||
}
|
||||
|
||||
local unless = nil
|
||||
|
||||
if args["unless"] then
|
||||
unless = ObjectManager {
|
||||
Interest {
|
||||
type = "port",
|
||||
args["unless"],
|
||||
Constraint { "port.direction", "equals", "in" }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
function _connect()
|
||||
local delete_links = unless and unless:get_n_objects() > 0
|
||||
|
||||
if delete_links then
|
||||
for _i, link in pairs(links) do
|
||||
link:request_destroy()
|
||||
end
|
||||
|
||||
links = {}
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
for output_name, input_names in pairs(args.connect) do
|
||||
local input_names = input_names[1] == nil and { input_names } or input_names
|
||||
|
||||
if delete_links then
|
||||
else
|
||||
-- Iterate through all the output ports with the correct channel name
|
||||
for output in output_om:iterate { Constraint { "audio.channel", "equals", output_name } } do
|
||||
|
||||
for _i, input_name in pairs(input_names) do
|
||||
-- Iterate through all the input ports with the correct channel name
|
||||
for input in input_om:iterate { Constraint { "audio.channel", "equals", input_name } } do
|
||||
-- Link all the nodes
|
||||
local link = link_port(output, input)
|
||||
|
||||
if link then
|
||||
table.insert(links, link)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
output_om:connect("object-added", _connect)
|
||||
input_om:connect("object-added", _connect)
|
||||
all_links:connect("object-added", _connect)
|
||||
|
||||
output_om:activate()
|
||||
input_om:activate()
|
||||
all_links:activate()
|
||||
|
||||
if unless then
|
||||
unless:connect("object-added", _connect)
|
||||
unless:connect("object-removed", _connect)
|
||||
unless:activate()
|
||||
end
|
||||
end
|
||||
|
||||
-- Auto connect the stereo null sink to the first two channels of the RME Fireface
|
||||
auto_connect_ports {
|
||||
output = Constraint { "object.path", "matches", "rme-nullsink:*" },
|
||||
input = Constraint { "port.alias", "matches", "Fireface 802 (24240739):*" },
|
||||
connect = {
|
||||
["FL"] = "AUX12",
|
||||
["FR"] = "AUX13"
|
||||
}
|
||||
}
|
||||
|
||||
-- Auto connect the stereo null sink to the jack_sink for when the jack server gets started
|
||||
auto_connect_ports {
|
||||
output = Constraint { "object.path", "matches", "rme-nullsink:*" },
|
||||
input = Constraint { "object.path", "matches", "jack_sink:*" },
|
||||
connect = {
|
||||
["FL"] = "FL",
|
||||
["FR"] = "FR"
|
||||
}
|
||||
}
|
||||
|
||||
auto_connect_ports {
|
||||
output = Constraint { "object.path", "matches", "rme-nullsink:*" },
|
||||
input = Constraint { "object.path", "matches", "alsa:*" },
|
||||
connect = {
|
||||
["FL"] = "FL",
|
||||
["FR"] = "FR"
|
||||
},
|
||||
|
||||
-- Don't connect to speakers if there are bluetooth headphones plugged in
|
||||
-- unless = Constraint { "object.path", "matches", "bluez_output.*" }
|
||||
}
|
||||
|
||||
-- Auto connect the stereo null sink to bluetooth headphones
|
||||
-- auto_connect_ports {
|
||||
-- output = Constraint { "object.path", "matches", "rme-nullsink:*" },
|
||||
-- input = Constraint { "object.path", "matches", "bluez_output.*" },
|
||||
-- connect = {
|
||||
-- -- Connect to the correct channel or "MONO" if the output is in "headset" mode
|
||||
-- ["FL"] = { "FL", "MONO" },
|
||||
-- ["FR"] = { "FR", "MONO" }
|
||||
-- }
|
||||
--}
|
||||
@@ -0,0 +1,14 @@
|
||||
context.objects = [
|
||||
{ factory = adapter
|
||||
args = {
|
||||
factory.name = support.null-audio-sink
|
||||
node.name = "rme-nullsink"
|
||||
node.description = "RME Nullsink"
|
||||
media.class = Audio/Sink
|
||||
object.linger = true
|
||||
audio.position = [ FL FR ]
|
||||
monitor.channel-volumes = true
|
||||
monitor.passthrough = true
|
||||
}
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user