hello everyone! ![]()
i have decided to micspam with pipewire, that is literally playing sounds/music into the virtual microphone via software only, so, hardware-less, oh, you know, funny haha “soundboards” and all, on nixos, of all things. am i crazy? yes.
so anyway, bit of context, i’ve been using this neat little tool:
and it’s been fantastic (the part where you can create a virtual input/combined/output device). BUT!
what is not fantastic is that streams or clients (of "media.class") (such as apps, that are playing back audio, like a browser or a media player, e.g. "~alsa_playback.*") automatically disconnect or suspend the links between the nodes after 3-5 seconds (or IMMEDIATELY!) when they are either:
A) “idling”, muting or skipping (seeking to a different time position) (e.g. tab or youtube html5 player), or playing NO sound at 0 volume, this does NOT include fadeouts in-between tracks in a continuous album as one file
B) browser tab reloading (from going to the next track or simply PAUSING the track) and/or music player (e.g. mpv, vlc, moc, ft2 (i know its not a “player”, its a tracker, but im just listing things), etc.) ending its track/playback
with A, i understand how this works: keep. playing. constant. audio. FOREVER. or else you will lose connection (the link). that’s funny! …that you have to reconnect the source to the microphone EVERY TIME for whatever reason when it’s not playing audio. i do it via pkgs.helvum, btw.
with B, i have no idea why this is a thing, but sometimes the apps (sources, but actually clients, i believe? aka internally alsa_playback) change their node AND serial id’s - that is NOT funny. HONESTLY! i was pretty frustrated with this and i honestly think that micro$oft winblows did it better, even with their shitty latency. it was just easier (VAC and/or voicemeeter). on linux, they disappear from the pipewire’s session forever??? wow thank you. sorry. fak corpos tho.
ANYWAY!
the so-called “node suspension” is the problem, right? no? NO! it didnt work! nothing down below worked. now this right here is hilarious:
services.pipewire.wireplumber.extraConfig."99-disable-suspend" = {
"monitor.alsa.rules" = [
{
matches = [
{
"node.name" = "~alsa_input.*"; # sources
}
{
"node.name" = "~alsa_output.*"; # sinks
}
{
"node.name" = "~alsa_playback.*"; # apps/clients -- EDIT: WRONG! this is supposed to be in "services.pipewire.extraConfig.client" option!
}
# ...
];
actions = {
update-props = { # NONE of this works btw
"session.suspend-timeout-seconds" = 0; # 'systemctl --user restart wireplumber'!
"node.pause-on-idle" = false;
"node.suspend-on-idle" = false;
"node.always-process" = true;
"dither.method" = "wannamaker3"; # OPTIONAL shape -- rectangular, triangular, triangular-hf, wannamaker3, shaped5
"dither.noise" = 2; # integer -- doesnt work???
"object.linger" = true; # keep linked even if destroyed -- DOESNT WORK BTW
# ...
};
};
}
];
};
EDIT: as i later found out, this is the only WIREPLUMBER config that works - for inputs/outputs/sources/sinks, but not for clients/apps/playback, apparently, where no properties are added at all. for PIPEWIRE, there is extraConfig.client, which i will talk about later on, sorry for the confusion, but despite it actually applying properties, SOME of them have no effect whatsoever, and it is driving me nuts. ALSO, i appended this snippet to EVERY single other pw/wp config section, just in case i “missed” some “incompatible” properties.
…which is taken and improved on from
SO! none of the above works btw. the update-props (pipewire wiki) thingie. OH! does this file even exist? that’s a good question! gonna check it in real time… it sure does! multiples of it in the nix store. not a write issue.
syntax issue? looks evaluable to me… hmm, would you look at that! even updating works! that is incredible. i wonder what else could it be…?!
aaaand once again, "session.suspend-timeout-seconds" and "node.always-process" (and others) are the things that i need for nodes to, ya know, never disconnect or be deleted on “idle” or whatever. one option is more underdocumented than the other. then i need "dither.noise" to produce a constant background noise for the nodes to… again, not disconnect, haha! going insane! i EVEN tried cranking up the noise to 10, 50, 100 (very loud), but i heard nothing. it’s just nothing. THAT is also weird.
speaking of noise, only from the pw-dump cli do i get more information about it, instead of the docs. epic.
{
"name": "dither.noise",
"description": "Add noise bits",
"type": { "default": 0, "min": 0, "max": 16 },
"params": true
},
(apparently i couldnt have used a value larger than 16, but once again, it doesnt matter, because it simply doesnt work)
funny. found someone on reddit (ugh) with the same problem (and on a steam deck too!)
after using it for a bit, I noticed it resets whenever I pause or switch to a different video/tab. Is there a way around this so it always is connected, preferably for all audio sources? thanks
(talking about helvum, but is actually alsa_playback, AGAIN)
i dont know what am i doing wrong. should i be creating this "99-disable-suspend" with services.pipewire.extraConfig.pipewire instead? or services.pipewire.extraConfig.pipewire-pulse? because librewolf uses alsa… does anyone know? but i dont think it has the same syntax as wireplumber… that’d make them redundant! no? ALSO ALSO, yes, pipewire should be the primary audio server (for me), as per services.pipewire.audio.enable (more on that below). and also also also, do i even need to do the double digit numbers for the config file name? they were popular in the pre-json era, nowadays its just one word like shit.conf (we omit the .conf part in nix, right? not a file name issue, right?)
EDIT: ohhhh. 1 mystery solved. pipewire dropped lua for json for its configs since version 0.5, which was about a year ago or so. this includes apply_properties which is now update-props. the more you know!
but i do have a suspicion. i am on a steam deck. it has a pretty wacky audio card (chip) called AMD ACP5x (snd_soc_acp5x_mach). what is even more wacky is that i have to use jovian-nixos (drivers & kernel) flake for a functional GPU that is apparently unusable on the main kernel, and so what it ALSO has is its own pkgs.pipewire and pkgs.wireplumber for some reason. could… that mean anything? EDIT: nope. SORRY, i forgot i have jovian.devices.steamdeck.enableSoundSupport disabled (cos i dont like anyone or anything tinkering with my intricate audio config). EDIT2: i ended up getting rid of jovian-nixos at all, i fixed my GPU freezes and hangs! \BREAK\ …though the fact that the steam deck has a combined jack for mic and headphones (which is exploitable if one were to buy a splitter) and the fact that the aforementioned snd_soc_acp5x_mach module is BOTH an input and an output device (meaning you cant disable the microphone as a kernel module without disabling the whole audio)… AND! if you ever manage to disable the internal microphone (EDIT: i did, will post soon), it defaults to your monitors, because valve, haha, epic (actually amd APU). i would not be surprised if someone told me to just stop, “its not worth it”, ya know?
unless someone could perhaps help me, someone who also does wacky stuff like me with PIPEWIRE? someone who isnt weak and willing to downgrade to pulse/alsa/windows the moment there is an obstacle that is preventing progress. it’s very niche. and every other “tutorial” i’ve read is written in imperative code, i dont understand it. EDIT: speaking of which, its your lucky day imperative distro users, because pipewire uses json (more on that later) for its configs, so it will be pretty easy to understand nix too! (not that nix is like json, no its more like lua, but um, whatever)
for example:
looks promising. but it’s using pactl which is literally pulseaudio, which is afaik superseded by wpctl and pw-link, etc.
and here’s what i’ve been raving about: NODE SUSPENSION (and idle states, which are apparently two different things!). this person made a script that LITERALLY forcefully relinks broken nodes EVERY second. that is absolutely hilarious. so, see what i mean? perhaps by declaratively making alsa_playback to work with my config that specifically prevents this scenario would fix the issue! but nothing works for me at ALL! see?
…
sorry. i just dont like being misled. it says pipewire is made easy, but nothing makes sense or work…
anyway. here’s what i’m doing (to recreate):
- EDIT: not anymore, but previously i was using
sonusmixto imperatively create a virtual microphone, now i am using a null sink (that is actually a source???) declaratively, in a config… \ make a virtual device (microphone), which is not actually a device NOR a default input, according towpctl set-default, but it doesnt matter (i think?). but it really bothers me that i have to usepactlfrompkgs.pulseaudioto set it as the default input, instead ofwpctl set-default, which requires it to be a device node ofmedia.class(EDIT: apparently this is not implemented, see further below). anyway, worst case scenario, with older games (goldsrc), it captures/defaults to your default input (makes sense), which may not be necessarily yoursonusmixvirtual input, but actually your internal microphone (which, again, in my case, i am unable to disable) (EDIT: i managed to disable the steam deck’s internal microphone as a device via pipewire entirely WITHOUT tinkering with priorities, will share the config if i will remember to!), so i have to take the L and usepkgs.pulseaudio’spactl set-default-source, alas - play some music via the browser/player for it to appear as a playback application, because it’s stupid and doesnt know otherwise.
- wire/route/link/connect nodes (left, right) from the browser to the virtual microphone (via
sonusmix, or viapw-link, or viahelvumand the like) - unwire/unlink/disconnect playback nodes from the browser to the headphones (because i dont want to be able to hear music twice with the
voice_loopback 1anyway) - if you didnt notice already, once the music stops, you have to do it all again, sometimes it doesnt even allow you to reconnect, because nothing is working
and this is where i am. the only, like, possible chance of it working is by playing the whole album, a mix, compilation or long tracks, so that you have ENOUGH TIME to relink everything. it’s stupid. it’s annoying. i CANNOT believe this is a thing that is happening… AND NO, this is NOT sonusmix. i’m pretty confident. just like helvum or pw-link, sonusmix also randomly loses connections. it has a feature to prevent sources/sinks from changing node links, but it doesnt seem to work, when the node ITSELF deletes itself, ya know??? so no, it’s not sonusmix, it’s the way playback audio pops in and out of existence. sorry, just had to clarify.
i also tried manually creating null sinks (sonusmix already does that). problem is, it’s not a device, once again, so… ok. im going to try it again after i finish this…
i tried… options snd_soc_acp5x_mach power_save=0 power_save_controller=N in boot.extraModprobeConfig to disable any “power management” for audio, just in case it’s actually the chip being naughty. especially in TLP audio power management (and the like).
what i didnt try is the “pro audio” profile (underdocumented). i think it’s a wee bit overkill for what im trying to do, right? no one’s gonna bother explaining that to me and i’m lazy, so nah, thanks.
so yeah. those with short attention span, once again, i cant manage pipewire’s and/or wireplumber’s extraConfig to work, it seems to not do anything, when i told it to, for example, play a constant stream of noise so that it doesnt unlink the nodes for being “silent” or “idle” or whatever…
tl;dr it is impossible for me to stream my browser’s audio to the microphone without it randomly disconnecting and remembering my patches/links!!!
sorry once again. im just gonna hit the send button…
THANKS IN ADVNCE FOR ANY HELP GUYS!!
p.s. imagine if someone randomly mixed up your patchbay on your eurorack. that’d be hilarious.
EDIT: okay. checked the properties with pkgs.coppwr (check it out!)… so, apparently, the problematic node alsa_playback.librewolf, which is obviously media.class of Stream/Output/Audio… has no "session.suspend-timeout-seconds", nor "node.always-process" properties! it’s not a device, it’s not anything! BUT IT SHOULD BE! when i told "node.name" = "~alsa_playback.*" to target everything with regex!!! angry face.
in other news, APPARENTLY it DOES apply update-props to my other sources and sinks, except sonusmix and the alsa_playback apps (clients)! and EVEN THEN, the properties that it did apply to seem to not do anything? for example, i should be able to hear noise both in my microphone and my headphones, but there is none? the property is there, but it’s just nothing! it’s like they’re sentient or something. okay, i am going deeper and deeper into this rabbithole. it could even be… the fact that playback is always pure alsa that isnt even routed to pipewire? that would be actually insane.
wait a minute, what’s this?
it’s written in pre-0.5 lua though. would no longer work. is this deprecated and misleading information or? EDIT of an EDIT: i WILL confuse wireplumber with pipewire and its versions A LOT. it doesnt matter that much.
EDIT2: forgot to mention that i have the usual options for music production, i do have rtkit, pipewire with wireplumber and its alsa.enable (NOT hardware.alsa.enable!), pulse.enable (NOT services.pulseaudio.enable!), jack.enable (NOT services.jack.*.enable!), so basically pipewire and pipewire-pulse, that’s it. …you know what would be funny? routing everything to JACK as a proxy. lmao
EDIT3: hmm. remember when i said
should i be creating this
"99-disable-suspend"withservices.pipewire.extraConfig.pipewireinstead? orservices.pipewire.extraConfig.pipewire-pulse? because librewolf uses alsa… does anyone know?
well, there is one more option that i missed: services.pipewire.extraConfig.client which sounds promising, it’s about APPLICATIONS, so most likely, the ones that are for playback. that is very confusing. wireplumber’s extraConfig can only do so much, apparently. im going to try it after i set up a declarative null sink, one moment please…
EDIT4: okay a bit of progress, but still no way of fixing the node links disconnecting. here’s where i am so far:
the virtual microphone source
services.pipewire.extraConfig.pipewire."91-null-sinks" = {
"context.objects" = [ # https://docs.pipewire.org/page_man_pipewire_conf_5.html#pipewire_conf__context_objects
{
factory = "adapter";
args = {
"factory.name" = "support.null-audio-sink"; # "api.alsa.pcm.source"? -- also couldnt find any documentation on this
"node.name" = "Microphone-Proxy";
"node.description" = "micspam";
"media.class" = "Audio/Source/Virtual"; # "Audio/Source"? -- "Audio/Sink"?
"audio.position" = "MONO"; # "FL,FR"
# "priority.driver" = 8000; # sources = 1600-2000; sinks = 600-1000 -- redundant IF the only microphone
# "priority.session" = 8000; # sources = 1600-2000; sinks = 600-1000 -- redundant IF the only microphone
"node.dont-fallback" = true; # "node.autoconnect"?
"object.linger" = true; # keep linked even if destroyed -- DOESNT WORK BTW
# ...
};
}
];
};
the playback client
services.pipewire.extraConfig.client."95-librewolf-alsa" = {
"monitor.alsa.rules" = [ # EDIT: AND "stream.rules" !!!
{
matches = [
{
"node.name" = "alsa_playback.librewolf"; # the browser
}
{
"application.process.binary" = "librewolf"; # just in case
}
];
actions = {
update-props = { # aka "apply_preferences"
"session.suspend-timeout-seconds" = 0;
"node.pause-on-idle" = false;
"node.suspend-on-idle" = false;
"node.always-process" = true;
"node.autoconnect" = false; # disable automatic linking to headphones (default/best available node)
# "node.dont-reconnect" = true; # maybe?
"target.object" = "Microphone-Proxy"; # "node.name" OR "object.serial" -- DOESNT WORK BTW -- they recommend the latter but its actually useless since its always random after the connection is destroyed
"node.dont-fallback" = true; # "node.autoconnect"?
"object.linger" = true; # keep linked even if destroyed -- DOESNT WORK BTW
# ...
};
};
}
];
};
sry i might’ve messed up the syntax, its so late rn.
anyway, so this is as far as i have gotten. it’s a bit easier now to connect stuff to the virtual microphone, but the fact that i still have to do it every time, you know, is suboptimal. oh ALSO, "dither.noise" doesnt work. i DONT understand why. itts very very annoying.
i’ll look into some other stuff tmrw… such as adding more args and preferences to the virtual mic?
EDIT5: okay wow. crazy. so, so, the "target.object" of my stream (client) of the librewolf audio (and NOT vice versa, according to the linking policy, see below), is supposed to connect to "node.name" of my virtual mic but it just doesnt??? cool, but… it doesnt say anywhere that im supposed to also provide a port, yknow, the input/output, left/right/mono? do i have to? i dont know. im figuring this out. here’s what im reading:
https://pipewire.pages.freedesktop.org/wireplumber/policies/linking.html
https://pipewire.pages.freedesktop.org/wireplumber/daemon/configuration/settings.html
https://docs.pipewire.org/page_streams.html
so yeah. dead end. there is 0 documentation on this, like i said. on ONE HAND you have the "target.object" (i’ve also tried the legacy "node.target", but it obviously didnt work), and on another hand you have deprecated tutorials in lua that tell you that it’s impossible to do via pipewire/wireplumber config.
to my understanding, the ONLY WAY to keep connections alive is to literally run a script in the background which spams pw-link command. that’s hilarious. im so tired…
EDIT6: updated the title to better reflect whats happening
EDIT7: i feel like the title still doesnt represent the content…