Add accessibility option for mono audio

I’m deaf in one ear and everytime i’m using a device to listen to something i need to set it up to mono in case it sends different sounds to different sides like in binaural sounds. Over the years using linux i found ways to manually set this up that are mostly hit or miss depending on whether you’re using pulse audio or pipewire.

What i’m asking here is that an option to turn sound output into mono be added to the accessibility panel on the settings, it would make my life really easier as i would not have the need to set it manually or with scripts everytime i change my earbuds or headsets, thank you.

I know this may seem like a distro specific issue but from my years using linux i realized not a single desktop has this accessibility option for me, i can do this on android and windows, but on linux it always comes back to “set up a script or use wirepubler”, i just want to make it easier for other people that may not be as knowledged when it comes to solving these issues.

1 Like

The need for mono audio for a11y reasons is surprisingly common.

This seems like a good job for easyeffects, what do you think OP?

Hi - just for folks’ reference, there is an existing feature request in the KDE Bugtracking System for this option: 462527 – Feature request/Accessibility: Allow downmixing audio to mono

I’m not sure where in the stack that would need to be implemented, but it does sound extremely helpful for accessibility. It’s even helpful for working around device issues - for example, the right speaker on the headphones I use with my phone doesn’t work, so I turn on mono audio in the iOS settings to still be able to hear everything.

2 Likes

I used easy effects for this before, it does the job if you thinker enough, but i wish to see an option for this in the settings because it would make it easier for people who just want to use the desktop without having to set this up.

Hello, i’m not very knowledge when in comes to what would be needed to make this work, but i know its a needed feature for a lot of people and i’ve seen it requested across many linux projects, i do use it on android as well so good to know iOS has this too!
The only desktop i know that plans to implement this feature soon is cosmic, i’ve seen it on their tracker, but besides them the request for mono in other projects has been either ignored or denied for some reason or another.

I do hope some plasma dev sees this and implements it as its my favorite DE by far.

I see what you did there :grin:

For something simple like a mono mixdown output over both channels, wireplumber and pipewire have all the tools built-in, so it’s basically a simple matter of dropping a config file. The new audio device renaming feature has that kind of capability, so I feel like plasma could do this job pretty easily.

A couple of links in case the “how” is interesting:

https://pipewire.pages.freedesktop.org/wireplumber/policies/smart_filters.html

I haven’t tested this beyond about 10 seconds, but I’m pretty sure it works: you make a smart filter, with no target set, mono capture and unset channels on the playback stream, and allow it to upmix.

It’ll inject itself transparently before the selected output device, which will cause apps targeting that device to use mono playback, then upmix it and play that through however many channels the device really has.

~/.config/pipewire/pipewire.conf.d/99-mono-downmix.conf

context.modules = [
    {   name = libpipewire-module-loopback
        args = {
            node.name = mono-filter
            node.description = "Mono Mixdown Filter"
            capture.props = {
                audio.position = [ MONO ]
                media.class = Audio/Sink
                filter.smart = true
                filter.smart.name = mono-filter
            }
            playback.props = {
                node.passive = true
                node.dont-remix = false
                channelmix.upmix = true
                channelmix.upmix-method = simple
            }
        }
    }
]

Tada, instant global mono downmix. It effectively makes all your sound cards’ outputs look mono to your apps.

Off the top of my head I can think of like 6 different ways to do this, so if that doesn’t work, don’t be put off, that’s just me ‘doodling’. I’m sure it can work and be something very simple like a single config file, which would be easy for a skilled individual to put in place with a button click or a toggle switch or whatever.

Do I seem to be vaguely on the right track, @kuroki100 ? If I could come up with something that worked for you, do you think it would work for others if it was behind a toggle switch or something? Would it need options or something? Are there guidelines that should be followed for how this sort of thing should work? I appreciate any advice :slight_smile:

2 Likes

I have something like you did there setup here, the issues i’ve ran into is that the output it creates sometimes doesn’t actually work as an volume slider. So what happens, for example, is that if i have a browser tab playing something using a headset and i try to change the volume slider on the mono output it doesn’t do anything, i have to either change it on the output of the headset or the browser tab, which can be confusing if you have several sound sources having to figure out which one is the one you need to reduce the volume for it to work.

I think having it so when you enable the toggle it just changes the actual device to mono instead of creating a new one would be a good way to prevent this, if its a recurring issue with other methods of mono downmixing.

Unfortunately, not all sound cards have a mono profile to select. But smart filters are specially made for purposes like this (or for example, to put special treatment in front of mac speakers) so generally should be fairly reliable. I use them literally all day every day here and have not seen that volume failure - and I’ve fixed two volume bugs today. (which is to say, I’ve been trying to break volume control)

That’s how I tested this, it seemed to work? It sounds like you are using a loopback device, as I showed, but you are not using it with smart filters, but as a normal filter chain creating a virtual sink. The code may look similar, but they act kinda different.

If you use smart filters as I showed above, the filter is ‘invisible’ to the system, and you don’t get a separate volume control, or see a new device in your apps - there is no “device it creates” as far as the visible part of the system is concerned. Your apps have no idea the filter exists, they see only your real sound cards. Unless you want them to see it, so use filter.smart.targetable=true

You can see it in apps like qpwgraph, but it is marked virtual, so it will be hidden in for example your browser or media player or plasma volume controls. You just use the normal volume controls and select the normal soundcard as your output like it’s all totally normal and there is no mono downmix.

It’s also much nicer because now you can select the physical device you are actually listening to from the Plasma GUI, not a new device that is created and makes routing difficult. Everything starts to work as it is intended for a normal device.

There was a bug regarding volume changes and loopbacks recently, too, so maybe you saw that. But perhaps you might try to temporarily rename your pipewire config dir and a copy paste of the above configuration, restart pipewire and see how it works. I can happily test it all day but I am not deaf in one ear so what would I know if the result is acceptable or not? :laughing: Anyway I am sure you will prefer smart filters when you try them. It’s very elegant.

2 Likes

Apologies for replying to an older post, but I’ve been searching for a solution like this for quite a long time.

I tried out your suggested config, and with my Bluetooth headphones it works fine in the ‘headset’ configuration, but the output quality isn’t the best. However in the ‘headphone’ configuration where the audio quality is great, the audio only comes out of the left side, which is the side I’m mostly deaf on.

I did do some searching on this and watched the video you linked to, so I think this is something to do with the different profiles when it comes to Bluetooth devices, but I haven’t been able to work out a way to get the audio to come out from both sides.

Well, that’s inconvenient to say the least! I can’t reproduce that here but I’ll see what I can do to help.

Do those headphones work in normal stereo without this config?

The output of pw-dump will almost certainly contain the answer. It’ll also be awfully long and hard to parse. You might want to take a look at/screenshot of a few of the GUI tools like qpwgraph (What ports are available on the device? What are linked to where?), the KDE Sound Settings (Is the appropriate A2DP profile selected when in ‘headphone’ mode?) or maybe coppwr (that’s what I’d use but it gets a bit nerdy in there).

Something you could try real quick for testing: Change

            playback.props = {
                node.passive = true

to

            playback.props = {
                audio.position = [ FL FR ]
                node.passive = true

This will force stereo output from the filter (well, two channel, upmixed from mono so will still be effectively mono) which is not optimal as it should already adapt to whatever the channel map is on the device. But if there’s something strange about the channel map on the device, this might work around it.

Let me know how that goes and we’ll see what we can do about it.

1 Like

Thanks again for helping with this. It really seems like it should be something that’s been solved before now and I’m certain there’s a workable solution within pipewire to achieve it.

I tried the change you suggested, but it made no difference to the audio just coming out of the left side. It’s definitely a mono output, as if I do the audio test it says “front left/right” both on the left side.

Here is a shot from qpwgraph while something was playing:

and I’ll try and send you a link to the output of pw-dump while something was playing, it was too large to paste into this post and it won’t let me post a link.

No worries mate. I’m also confident it can be done in pipewire, just not sure of the finer points. Maybe we might even find bugs, but they can be fixed and the pipewire team are very helpful. We’ll get there!

To fix it, first we gotta figure out what’s wrong…

OK interesting. That and the screenshot show that the mono input part is working, so I wonder if the upmix from mono to stereo is working. Could you try disconnecting and reconnecting these two links, in reverse:

If you do that, is it still only coming from the left speaker, or now only coming from the right? (I assume it was not coming from the right side before, and will not come from both sides this time, correct me if I’m wrong!)

Sorry, I figured pw-dump might be too big to upload. Let’s see how far we get without, for now.

1 Like

Sorry to post again but I’m hoping I can save you some time, I think I see the problem(s)

Firstly, from pw’s docs:

channelmix.upmix = true

    Enables up-mixing of the front center (FC) when the target has a FC channel. The sum of the stereo channels is used and an optional lowpass filter can be used (see channelmix.fc-cutoff).

    Also enabled up-mixing of LFE when channelmix.lfe-cutoff is set to something else than 0 and the target has an LFE channel. The LFE channel is produced by adding the stereo channels.

    If channelmix.upmix is true, the up-mixing of the rear channels is also enabled and controlled with the channelmix-upmix-method property.

No mention of mono upmix to stereo there!

And I’ve just tested this and I make the same observation: audio only from the left side.

I said “problem(S)”, plural, because the second part of it was my testing procedure when I first wrote this. I tested one ear. The left. :man_facepalming:

My code was just bad, based on my misunderstanding of the upmixer. Give me a sec to fix it.

This should do the trick:

context.modules = [
    {   name = libpipewire-module-loopback
        args = {
            node.name = mono-filter
            node.description = "Mono Mixdown Filter"
            capture.props = {
                audio.position = [ MONO ]
                media.class = Audio/Sink
                filter.smart = true
                filter.smart.name = mono-filter
            }
            playback.props = {
                node.passive = true
                audio.position = [ MONO ]
            }
        }
    }
]

Sorry it took me so long to get back to you. It seemed like this might only work on some devices which is pretty limiting, but I’ve tested it across a bunch of different hardware and I think it should work OK for just about everyone.

Nerdy detail: I was concerned that this would only work on devices which have a mono profile, but it’s actually doing the channel conversion in the node before the device, the device will keep the channel map from the Profile.

The one exception is for devices which use the ‘Pro Audio’ Profile. In that case, it’ll stay mono, but just route that to the first channel on the device (AUX 0) Since there’s a native mono-to-mono connection to be had it doesn’t do any remapping.

This is common to all mono streams being sent to Pro Audio devices, so they probably want to solve that problem in general and separately to this, so I won’t hijack this thread with that topic other than to say: Setup a loopback with ports named as per standard stereo/surround devices ( FL, FR, C, LFE, etc)

For actual mono-capable devices it’ll use that and should sound fine.

Hope that helps @Stuart_Luscombe and I’m terribly sorry that my short-sighted testing regime didn’t catch my error and wasted your time.


Oh no! I hadn’t clicked ‘send’ :person_facepalming: :man_facepalming: :woman_facepalming: Man I just can’t win today!

Speaking of bad testing process… After I did this for you, I restored my config and… it didn’t work! Restored the entire system from yesterday when it was working…Nothing! Power cycled everything, restored again, you name it, nothing! What the heck?! I ended up spending the next 5 hours troubleshooting my extremely complex pipewire installation, to no avail…

Then I finally figured out that it wasn’t pipewire that was broken - it’s *KDE’s Sound Settings ‘Test’ Button. It’s crashing pipewire. Probably broken since 6.3.5, but I just noticed today.

I share this embarrassing tale of woe so that:

a) others can learn from my mistake in not testing it by another means and trusting that one method (which admittedly has never failed me in years, but that’s why it’s a strong lesson)

b) you won’t try to use that button to test this!

Good luck, and again, apologies for the delays.

2 Likes

No apologies needed at all.

It works! :grinning_face_with_smiling_eyes:

I just changed the file out for what you put in your last post and it’s working perfectly. When you mentioned about ‘Pro Audio’ I did wonder if that was part of the issue, but no, I just restart pipewire and pipewire-pulse for the final time and did the sound test, perfection!

Thank you so much for your help, this issue has really been the one thing that has kept me from daily driving Linux for so long and after the numerous threads I’ve opened on different sites over the years, you’ve solved it in 2 days!

Myself and other people with single-sided deafness are in your debt!

2 Likes