Skip to content

Instantly share code, notes, and snippets.

@asahilina
Created November 7, 2024 22:13
Show Gist options
  • Save asahilina/99400bfaba0ada774c5213b646d62014 to your computer and use it in GitHub Desktop.
Save asahilina/99400bfaba0ada774c5213b646d62014 to your computer and use it in GitHub Desktop.
-- As explained on: https://bennett.dev/auto-link-pipewire-ports-wireplumber/
-- 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.
-- -- 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
loopback_id = 0
loopback_nodes = {}
function auto_create_virtual_device(args)
local id = loopback_id
loopback_id = loopback_id + 1
SimpleEventHook {
name = "node/virtual/create-virtual-device",
interests = {
EventInterest {
Constraint { "event.type", "=", "node-added" },
args["parent"]
},
},
execute = function(event)
Log.warning ("LUA: Execute")
local parent = event:get_subject()
local pp = parent.properties
Log.warning ("LUA: " .. pp["node.name"])
local loopback_properties = {}
local name = args["name"]
local module_args = {
["audio.channels"] = 2,
["audio.position"] = "[FL,FR]",
["node.name"] = name:gsub("([^%w_%-%.])", "_"),
["node.description"] = name,
["node.autoconnect"] = false,
}
if args["args"] ~= nil then
for k, v in pairs(args["args"]) do
module_args[k] = v
end
end
if args["direction"] == "source" then
local playback_args = {
["media.class"] = "Audio/Source",
}
local capture_args = {
["media.class"] = "Stream/Input/Audio",
["node.passive"] = true,
["node.dont-fallback"] = true,
["node.autoconnect"] = false,
}
module_args["playback.props"] = Json.Object(playback_args)
module_args["capture.props"] = Json.Object(capture_args)
else
local capture_args = {
["media.class"] = "Audio/Sink",
}
local playback_args = {
["media.class"] = "Stream/Output/Audio",
["node.passive"] = true,
["node.dont-fallback"] = true,
["node.autoconnect"] = false,
}
module_args["playback.props"] = Json.Object(playback_args)
module_args["capture.props"] = Json.Object(capture_args)
end
local args_json = Json.Object(module_args)
local args_string = args_json:get_data()
local loopback = LocalModule("libpipewire-module-loopback", args_string, loopback_properties)
loopback_nodes[id] = loopback
end
}:register()
SimpleEventHook {
name = "node/virtual/destroy-virtual-device",
interests = {
EventInterest {
Constraint { "event.type", "=", "node-removed" },
args["parent"]
},
},
execute = function(event)
if loopback_nodes[id] then
loopback_nodes[id] = nil
end
end
}:register()
end
auto_create_virtual_device {
direction = "sink",
parent = Constraint { "node.name", "matches", "alsa_output.*MCHStreamer_ADAT*.pro-output-0"},
name = "ADAT Main"
}
auto_create_virtual_device {
direction = "sink",
parent = Constraint { "node.name", "matches", "alsa_output.*MCHStreamer_ADAT*.pro-output-0"},
name = "ADAT Monitor"
}
auto_create_virtual_device {
direction = "sink",
parent = Constraint { "node.name", "matches", "alsa_output.*MCHStreamer_ADAT*.pro-output-0"},
name = "ADAT AUX A"
}
auto_create_virtual_device {
direction = "sink",
parent = Constraint { "node.name", "matches", "alsa_output.*MCHStreamer_ADAT*.pro-output-0"},
name = "ADAT AUX B"
}
auto_connect_ports {
output = Constraint { "port.alias", "matches", "ADAT Main:output_*"},
input = Constraint { "port.alias", "matches", "MCHStreamer ADAT:playback_*"},
connect = {
["FL"] = "AUX0",
["FR"] = "AUX1"
}
}
auto_connect_ports {
output = Constraint { "port.alias", "matches", "ADAT Monitor:output_*"},
input = Constraint { "port.alias", "matches", "MCHStreamer ADAT:playback_*"},
connect = {
["FL"] = "AUX2",
["FR"] = "AUX3"
}
}
auto_connect_ports {
output = Constraint { "port.alias", "matches", "ADAT AUX A:output_*"},
input = Constraint { "port.alias", "matches", "MCHStreamer ADAT:playback_*"},
connect = {
["FL"] = "AUX4",
["FR"] = "AUX5"
}
}
auto_connect_ports {
output = Constraint { "port.alias", "matches", "ADAT AUX B:output_*"},
input = Constraint { "port.alias", "matches", "MCHStreamer ADAT:playback_*"},
connect = {
["FL"] = "AUX6",
["FR"] = "AUX7"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment