Created
November 7, 2024 22:13
-
-
Save asahilina/99400bfaba0ada774c5213b646d62014 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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