I want more control over what my microphone picks up on screen share in video conferences or during streaming but I don’t want to buy a hardware mixer. I also want to be able to disable the microphone with a hotkey but it doesn’t have any physical switch. So achieve all this I utilise PipeWire to run a bunch of virtual devices that I can control via pavucontrol and obs later. Video conferences get this as “default device” so they don’t get a chance to mess up my audio setup (looking at you Teams). The steps are the same for PulseAudio if you don’t have PipeWire (yet).

#!/bin/sh
# setup virtual device intended for monitoring
pactl load-module module-null-sink sink_name="BekoBlaster" device.icon_name="audio-card-analog" node.nick="BekoBlaster" node.description="BekoBlaster-16" sink_properties=device.description="BekoBlaster-16"
# setup virtual MIC so intended monitoring device can be recorded from as MIC
pactl load-module module-remap-source master="BekoBlaster.monitor" node.nick="BekoMic" device.icon_name="audio-input-microphone" source_name="BekoMic-16" source_properties=device.description="BekoMic-16"
# IMPORTANT:
# RUN `pavucontrol` => Select Tab Record => Set BekoMic-16 input to "Monitor of BekoBlaster-16"

The 16 is not important. It’s just my kind of humour as my first Linux PC had a SoundBlaster16 😛 It also is a pattern sufficient enough so I don’t mix this up with the zoo of real microphones or audio sinks attached to my computer.

This is already sufficient enough so that everything played on the device BekoBlaster-16 can be recorded on the BekoMic-16 again, that I select as input microphone for Browser (video conferences) or Discord at this point. This can be done with pavucontrol – or later in obs.

This isn’t enough, of course. In case of e.g. playing music (or streaming a game) I’d also want to hear the sound myself too. For this I create an additional null sink and a combined sink. With this approach I can later fine tune in obs what gets recorded to which audio track (where audio track 1 is the one used for streaming) and what ends up on the BekoBlaster-16, that acts as my monitor and due to the remapped source also as virtual mic.

# setup virtual device for games (or whatever OBS should record)
pactl load-module module-null-sink sink_name="OBS-Blaster" device.icon_name="audio-card-analog" node.nick="OBS-Blaster" node.description="OBS-Blaster" sink_properties=device.description="OBS-Blaster"
# OPTIONAL setup a combined sink so I can enjoy game sound while OBS gets a copy
pactl load-module module-combine-sink slaves="OBS-Blaster,bluez_output.10_4F_A8_84_18_01.a2dp-sink" node.nick="OBS-Blaster-AND-Headphones" node.description="OBS-Blaster-AND-Headphones" sink_properties=device.description="OBS-Blaster-AND-Headphones"
# Important tools to manipulate: `pw-cli list-objects`, `pw-cli destroy $id`, `pactl list short | grep module`, `pactl unload-module $id`

With this (and my headset connected) it starts to get crowded in my device list.

As you can hear err… hopefully see: The sink OBS-Blaster-AND-Headphones is now selected for playing music which results in the music being played on the next virtual sink OBS-Blaster and my h.ear (MDR-100ABN) headphones. The same could be done with the BekoBlaster-16, of course, but bear with me. We still don’t have any real microphone added to the mix and while this can be done with PipeWire or PulseAudio alone too I need this usually with video included too so obs it is.

Here the most important setting is the monitoring device, which is the BekoBlaster-16 from the beginning, that can be used as microphone in e.g. Discord later again.

Next is the set-up of the mixer where I’m interested in 4 devices only:

  • The BekoMic-16 without monitor (it is the monitor so this would result in an echo chamber) and optional track 5 for recording (so I’ll know later how the mix sounded – but this is never used for video editing later).
  • The desktop audio without monitor, so random system sounds (or other Discord voices!) don’t make it to any stream. It can be recorded on it’s own track tho in case I fcked up or need a reference later on during editing.
  • The Mic/Aux, which represents the real microphone used. It is echoed on the monitor microphone and on track 1 (send to my streaming server) and on track 2 so I have a separate microphone track later to work with in post edit.
  • The OBS-Blaster, which usually represents the game I’m playing. It is echoed on the monitor microphone and on track 1 (send to my streaming server) and on track 4 so I have a separate game/music track later to work with in post edit.

This way I can control in great detail what ends up on the Discord / a video conference / game streaming, while I get the full power of obs scenes (where I also do my greenscreen mixing), mute microphones as I see fit and have some material to work with later when I decide to make a video on stuff. Here I did set up Discord to read from the virtual BekoMic-16 and output to my headphones only (where no recording in OBS is done) – so perfect for most Discord / video conference sessions.

Don’t mind the flipped video preview. That’s perfectly fine and will look right for the viewers later. This is by the way the virtual camera sink feature of obs and the v4l2loopback kernel driver that I also read from in video conferences instead of the real webcam. This way I can also control exactly what the webcam shows – zoom / crop included.

The whole mess looks like this visualised in helvum, a patchbay for PipeWire.

Most of this explains itself. The WEBRTC VoiceEngine is the recording of Discord. Other devices may float around but are not used at the moment of this snapshot.

More on this and proper documentation: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Virtual-Devices

You probably heard about this before: An Arduino can be made into an excellent DIY joystick. Most examples use a Leonardo or Micro for this for a very good reason. They one comes basically with a chip that is recognized as HID (Human Interface Device) hardware on any modern operating system.

This is not the case with a Mega. This one has other perks but HID it is not. It sure shows up as USB device and a ttyUSB is raised where serial communications with the Arduino can be initiated. I’m also aware that some flash the built in programmer of the Mega so it starts operating like the others (which obviously removed the built in programmer). I’m on Linux PC though so I thought it’s basically a job of tricking the system into recognizing it as joystick and call it a day and OMG was I wrong!

How it’s not done

My train of thoughts was like this: Linux still supports plenty of old serial joysticks so how complicated can it be to send some bits an existing driver recognizes. Old hardware like this is usually glued to the driver with the tool inputattach of the Linux Console Project. This does basically initialise a joystick on some serial connection and sends it off to a fitting kernel driver. This way even non-USB, or let’s better say non-HID hardware, is mapped to a kernel driver who in return will set-up the joystick subsystem and manage the communication with the stick via a serial connection.

Turns out I’m not the first one with that idea and apparently someone made it work by connecting old Playstation Controller and a Wii Classic Controller to an Ardunio and fake a Stinger device without the use of HID so Kudos to Jarno Lehtinen here and his Linux-Arduino-Serial-Joystick repo – you sure did sent me down a rabbit hole of horror and amazement. I couldn’t even get inputattach to wait for that magic string to be sent with anything else than 9600 baud and aligned stars! I also had to throw socat into this horrible mix because the Arduino would insist on rebooting on init so a timeout was guaranteed! In case you wonder how I did this:

socat -r left.raw -R right.raw pipe:/dev/ttyUSB0 PTY,link=/dev/ttyUSB1,rawer
# and xdd to show me the debug juice
tail -f left.raw | xxd -c4
# and on yet another terminal
inputattach --baud 9600 --stinger /dev/ttyUSB1

This also meant that I had to tear everything down for reprogramming the Arduino. Anyway, in the end I could finally get through that init phase where the stinger related code in inputattach is waiting for the magic key after sending “ E5E5” to finally load the Stinger kernel driver – communication for both ways confirmed!

    // "\r\n0600520058C272";
    byte byteResponse[] = {0x0D, 0x0A, 0x30, 0x36, 0x30, 0x30, 0x35, 0x32, 0x30, 0x30, 0x35, 0x38, 0x43, 0x32, 0x37, 0x32};
    if (Serial.availableForWrite() >= sizeof(byteResponse))
    {
      Serial.write(byteResponse, sizeof(byteResponse));
    }

At this point I had a pipe to prevent the timeout due to the resetting Arduino, the _only_ working baud rate 9600 I could figure out with the Mega, a loaded driver that was recognized as joystick and was sitting put and did… absolutely nothing. Null. Nada. Not a single bit made it to the driver and I could not figure out why. My guess is it needs a change in the baud rate to the original 1200 (?) of the Stinger but I have no idea if this is true. I could also not find any way how the stream is controlled and since the driver would fill up 2 bytes all the time and interpret them there is a fair chance that it would simply be one byte off all the time. Speculations tho, I simply didn’t grasp the stinger.c source so this is all just a theory. I do not want to admit how much time I sunk into this and I was pretty frustrated at this point. Reading some stupid serial? Not like this! Too many hoops!

So I threw it all in the bin 🚮

How it’s probably done

Say hi to /dev/uinput where you can basically raise virtual devices, like a joystick, without [much?] pain. I’m not the first one, of course, and funny enough the reason behind is very similar to mine. Read more on Virtual joystick on Linux by Gwilym Kuiper where this is all explained in great detail. The referred code at https://github.com/gwilymk/arduino-joystick sure did help me to get started and even without having touched Rust ever before I was able to quickly adjust this for my needs, doubling the possible buttons and get it up and running in just a few hours for my Linux PC. Cheers mate (also Jarno Lehtinen – you teached me a lot that day :D) 🕹️

So here it is: A Mega acting as joystick without HID over a serial connection driven by a userspace daemon (means no kernel driver required) written in Rust providing a virtual uinput device for a joystick on the “modern” event system. Heck it’s even recognized in Wine!

What a journey to begin with. Now I need a back-channel for my blinky lights so I get my Raspberry Pi back from simpit duty 🙃

I like space and science fiction. Diving into epic stories set in some distant future amazes me since elementary school.

I’m also a gamer. And a tinkerer. It’s in the family.

I keep wondering: How can I improve the immersion of my games without going full VR?

DIY Headtracker for gaming (on Linux PC)

I used a triple screen set-up before. It consisted of different models in height and size. When one screen finally broke down I purchased 3 refurbished screens of the same brand and model. What a difference!

The kids love it too. Of course. Means less stick time for me. Anyway.

This is when I started to read about head tracking and went on a quest to get this working for the game X4. As a bonus on Linux PC, my preferred system also for gaming.

The thing is: “The” reference product for a headtracker is the TrackIR system. Price as of today: 220 EUR. Ouch! That’s like a cheap VR, right? And it’s Windows only. No thanks.

So I checked what’s in this thing. Apparently a cheap camera, some infra-red LED, and a filter allowing only infra-red waves. And software, of course.

Since this is for Linux I get to pick my poison for the software part, and I settled with Opentrack fast. Onwards to the hardware part. I abused my mobile phone for the testing, sending it’s Gyroscope data via wifi to my PC, and while it worked it also _sucked_. Both, phone and wifi I mean.

Head tracking is awesome. And I knew I want it. So I started prototyping. For this I went with a simple design that I eventually implemented on cardboard. It looks hilarious but it gets the job done.

The focus was on a long life cycle so I wouldn’t have to replace the rechargables in the middle of a session. To get this right I checked with the camera that I was going to use. See (video above), this is way to bright and by trying various resistors I could get this down to 33mA per LED and still get a decent detection rate with Opentrack.

Speaking about the camera. That’s nothing special. It’s a dead cheap 480p Logitech QuickCam Communicate STX that I got from a discounter a decade ago. It was so cheap it doesn’t even _have_ an infra-red filter that I’d have to remove first.

I used tape to attach the salvaged camera cover of a dead G20 controller. That’s a Wii Remote knock-off that does basically the same thing like a headtracker. Various other foils can be used for this as well, as long as they permit infra-red. The idea is to reduce or remove all other light waves but infra-red.

The trick is to also turn off auto exposure and fiddle with the contrast and sharpness until a decent frame rate and a clear infra-red wave source by the LED can be seen.

When I was satisfied with my meter readouts, and my highly professional scribbles, I started working on the prototype while streaming the whole process on the Discord channel of the awesome Fly Dangerous project. If you like racing with a space ship give it a shot.

The prototype is made of cardboard that doubles as isolation for the polarity. The rest is tape and hook-and-loop fastener to attach the headtracker to my headphones. No magic here. The whole contraption is powered by two 1.2V rechargeables. I opted for a micro switch and an additional LED as power indicator, that I dimmed down even more. I can after all not see infra-red so this seemed like a good idea to me. Spoiler: It is.

So how does it play? Over the next weeks I tried basically any game supporting head tracking that I could get my hands on. Please keep in mind that I usually play with lights off but started the studio lights for demo purposes. The tracker does still work just fine.

I quickly found out that each game needs it’s own profile for fine tuned settings. Good thing that Opentrack has me covered on this. First, my beloved X4 using Wine and the TrackIR protocol.

Sadly I came to the conclusion that my GPU is no longer up for the task and Wine would cost me too many frames. I switched Opentrack to emulate a joystick instead and mapped it to camera movements in the native X4 version. It’s not exactly the same but it’s okay-ish. I have an idea how to hack this properly into X4 using an extension and a UDP server but that’s a topic for another day.

Anyway, the same principle works with X Rebirth too, making me even happier. While dated it still has it’s charm and the verse still feels a lot more alive compared to X4. It’s also not taxing my GPU that much.

Now for something different. When Opentrack would list a “protocol” named FlightGear I became very curious. I installed this free and open source flight simulator and crashed my first Cessna into the ground minutes later. By now I’m confident that I can crash a Cessna just about anywhere. I’m not fond of flying in real-life but avionics sure are a fascinating topic.

This was the moment a Steam sale happened and I bagged various flight sims, Space Kerbal and House Of The Dying Sun. All with TrackIR support.

Little did I know what gem I bagged with House Of The Dying Sun by the way. Sadly it’s also very short but I enjoyed every minute of it and will probably play it again. The art, sound and music reminds me a lot of Battlestar Galactica. Easy win 😀

So yeah, this is my current gaming set-up. I built myself a head tracker for 5 EUR. On Linux PC.

I also may have fallen into the rabbit hole called “simpit”.

I love gaming over multiple monitors. It’s my current choice for work and games – especially simulations. Having several monitors attached to one computer (or graphic card) is not a big deal in 2021 any more. The framebuffer in recent graphic cards is insanely huge compared to some years ago, when one really had to think twice about the possible resolution when e.g. connecting a beamer to a laptop (good old SiS 630 anyone?).

xfce4-display-settings for my refurbished “new” set of displays

This couldn’t be easier nowadays. Even mixing the integrated graphic card of a recent Intel CPU with an NVIDIA or AMD dedicated graphic card does usually “just work”. Some driver specific mode may have to be set but that’s it. The workspace easily expands over multiple displays and windows can be moved around freely.

Games do not see one huge desktop but individual displays

There is however a catch. Games tend to read the primary display only and the maximum resolution offered usually comes with the readout of this very first display – or worse – the first display connected. This sucks especially when the monitors have different resolutions, as it was the case for me for several years now, because I didn’t just purchase a set but collected discarded monitors over the time. This can often be omitted by temporary disabling the “false” ones or by force window mode.

This results in hacky scripts involving xrandr, wmctrl and xdotool. This is for example how I hammered X4: Foundations into shape _after_ it started, because it would allow me to select a single display only. Set to window mode it can be freely scaled but that comes with a disturbing window decorations so with this the X4 window gets positioned to 0x0, expanded to 5770×1210 and the window decorations purged:

xdotool search --class "X4" windowmove --sync %@ 0 0 windowsize %@ 5770 1210 set_window --overrideredirect 1 %@
X4: Foundations on Extreme Multihead

That’s a pain to find out and the fun really stops when it comes to Proton or some games that would not allow resizing over their maximum detected resolution – like for example Everspace.

How about a virtual monitor?

So the idea was to introduce a completely virtual monitor to the systems with the resolution of choice. VNC servers do that all the time so it must be possible. The usual approach won’t work in this case though: When loading the dummy driver the real displays can usually no longer be used and the drivers for AMD and NVIDIA do not really offer such a feature at all.

It is perfectly possible to define virtual monitors with a recent xrand but they have to be mapped on an existing output (a real port). One can use an unused port (as in: no monitor connected) for this, add a Modeline and even force it as “online” like so: echo on > /sys/kernel/debug/dri/0/HDMI-A-1/force

I was delighted to see the display showing up briefly but the AMD driver made short work of my soaring hopes by forcing it off again in an instant. So close and yet so far. This would require some hardware hacking by creating a dummy plug for the port. That’s basically some resistors in the right place making the computer think a display is connected. I hear they can also be purchased and this may be a way for others.

Others seem to have had success by compiling the experimental DisplayLink driver that seems to offer (virtual) monitors but I really didn’t feel like fiddling with something even more alien that will probably break on the next kernel update again.

Intel to my aid!

The success for me was in the end to use of the Intel driver and it’s VirtualHeads feature. The caveat is that one probably needs an Intel CPU for this to work and has to create a X11 config file. If this is done without adding the usual driver people will experience black screens on reboot only. This may be a show stopper for inexperienced Linux users who don’t know how to recover from a broken X11 config (yet :D). For me this is an amdgpu so my file /etc/X11/xorg.conf.d/20-intel-virtual-and-amd.conf has to look like this:

Section "Device"
       Identifier "amdgpu0"
       Driver "amdgpu"
Endsection

Section "Device"
       Identifier "IntelVirtual0"
       Driver     "intel"
       Option     "VirtualHeads" "1" 
EndSection

Triple check that your driver is used in there instead or you will end up with a broken config without the possibility to log in to a graphical window manager. When in doubt start e.g. a new session to your liking on the next display server where you can switch back with the key combination ctrl+alt+F[1-0]: startx /usr/bin/startxfce4 :2

Once started a new provider shows up and the new output “VIRTUAL1” is available: xrandr --listproviders

Providers: number : 2
Provider 0: id: 0x59 cap: 0xf, Source Output, Sink Output, Source Offload, Sink Offload crtcs: 6 outputs: 4 associated providers: 1 name:AMD Radeon RX 5600 XT @ pci:0000:03:00.0
Provider 1: id: 0x9e cap: 0xb, Source Output, Sink Output, Sink Offload crtcs: 5 outputs: 4 associated providers: 1 name:Intel

Configuring the virtual monitor with xrandr

Now that we have a virtual monitor we need a Modeline for it. This is usually the current screen (of the framebuffer) and can be calculated (e.g. sum of all monitors x height and Hz of one monitor) or by asking the system: xrandr | grep Screen

gtf (or cvt) helps obtaining the Modeline: gtf 5760 1200 60

  # 5760x1200 @ 60.00 Hz (GTF) hsync: 74.52 kHz; pclk: 579.47 MHz
  Modeline "5760x1200_60.00"  579.47  5760 6144 6768 7776  1200 1201 1204 1242  -HSync +Vsync

Now all information needed to finally set up the virtual display is there. I’m creating the virtual display on top of the three real displays because I also want to see what’s drawn on it. That’s not strictly required though and in fact most graphical tools to configure the monitor location will even refuse this – because this use case is simply not considered or supported. Gnome for example really didn’t like this. XFCE4 didn’t care. Ymmv:

xrandr --newmode "5760x1200_60.00"  579.47  5760 6144 6768 7776  1200 1201 1204 1242  -HSync +Vsync
xrandr --addmode VIRTUAL1 "5760x1200_60.00"
xrandr --setmonitor Virtual 5760/1554x1200/324+0+0 VIRTUAL1 
xrandr --output VIRTUAL1 --mode "5760x1200_60.00" --pos 0x0 --primary

It’s done.

The virtual monitor becomes visible in display settings (xfce4-display-settings here)

And it works!

After a lot of research and fiddling (and breaking my X config several times) I finally found a working solution. Games let me select the virtual monitor or see at least my primary with my “maximum” resolution. Sometimes this still requires window mode but I could care less – the decorations are optional. And it works like a charm! Here is a small selection of the games I play most at the moment:

This is how my set-up looks:

Now I’ve another problem. With this my usual 1080p gaming resolution is no longer and my graphic card is simply not up for the job any more 🤣

At least gaming itself is easy as pie on Linux in 2021. Complex display set-ups? Not so much.

The State of Virtual Reality on Linux by patolinux (boilingsteam.com)
When I first learned about that virtual reality stuff, I was still a kid and it was through movies like The Lawnmower Man. I really did not understand what it was about: it was very confusing, some people in a place doing things… but they weren’t in that place, and those things were not real. La...

Looks like @patolinux wrote an in-depth article on virtual reality (VR) with a focus on his experiences on PC: https://boilingsteam.com/the-state-of-virtual-reality-on-linux/ (and how it spoils your average 3d game).

Wasteland 3 now available on Linux from inXile Entertainment by Liam DaweLiam Dawe (gamingonlinux.com)
inXile Entertainment have today officially released Wasteland 3 for Linux (and macOS), following on from their successful Fig campaign in 2016 and Windows release in August 2020. Acting as a sequel to their 2014 hit, Wasteland 3 is a squad-based RPG with challenging tactical turn-based combat and ...

Uh oh. Wasteland 3 is finally released for PC?

I’ll be in my bunk.