forked from unom/punktfunk
docs: answer the "set the controller audio to Pro Audio" question our sink cannot offer a switch for
The community fix for DualSense haptics on Linux is to open sound settings and flip the pad's card profile to Pro Audio, and a field report compared that against our minted sink: theirs has a Profile dropdown reading "Pro Audio", ours has no dropdown at all. That reads like something is missing, and it is worth saying plainly that it is not — a real pad is a USB sound card and gets a profile selector, ours is a software device with no card, and the layout the selector exists to reach is the one we are already minted in. New page covers the whole feature end to end: what a DualSense's speaker and voice coils actually are (a four-channel audio stream, not rumble), the USB-only requirement on the client, GE-Proton 11-5 on the host, why every other card profile folds the coil channels away, the three host log lines that say how far it got, and the per-game Proton launch options that force GE onto its most direct route. That last one now has teeth: with the sink advertising a split parent, GE's `PROTON_DUALSENSE_HAPTICS_PREFER_NON_EVENT=1` opens our node by name and writes the four channels in with no remix anywhere in between. Known limits are stated rather than left to be rediscovered: Bluetooth pads have no audio interface, container-ID matching cannot work behind a uhid pad with no USB device to derive one from, and a real pad plugged into the host itself can win the match against the one you are streaming to.
This commit is contained in:
@@ -146,9 +146,10 @@ See your desktop page ([KDE](/docs/kde), [GNOME](/docs/gnome)) for when to set t
|
||||
|---|---|---|
|
||||
| `PUNKTFUNK_GAMEPAD` | `xbox360` · `xboxone` · `dualsense` · `dualsenseedge` · `dualshock4` · `steamdeck` · `switchpro` · `steamcontroller` · `steamcontroller2` (aliases: `ps5`, `edge`, `ps4`, `deck`, `switch`, `sc2`, `ibex`, …) | The virtual pad the host creates. Usually **auto-resolved from the client's physical controller** — set this only to force a type. `xbox360` (XInput) is the universal fallback. `dualsenseedge` gives the client's back paddles native buttons; `switchpro` gives Nintendo-family pads correct glyphs/layout + gyro. `steamcontroller2` (the 2026 Steam Controller) is passed through **as-is** — the host presents a real SC2 (`28DE:1302`) that Steam Input drives directly, mirroring the physical pad's raw reports (Linux only). DualSense (Edge)/DualShock 4 work on Linux (UHID) and Windows (UMDF); the Steam Deck pad too (Windows via the promoted UMDF identity); Switch Pro and the classic Steam Controller need Linux UHID. Unsupported choices fold to Xbox 360. |
|
||||
| `PUNKTFUNK_STEAM_GADGET` | `1` · `0` | Force the raw USB-gadget virtual Steam Deck on/off. **On by default on SteamOS**, off elsewhere. Lets Steam promote the virtual Deck to full Steam Input. |
|
||||
| `PUNKTFUNK_PAD_AUDIO` | `1` · `0` *(default on)* | Controller audio: what a game plays through the DualSense's built-in speaker and voice-coil haptics is streamed to the client's physical pad as its own low-latency plane. On by default and free while idle — silence is never encoded or sent; `0` turns it off host-wide. On Windows the pad's audio device is a pre-provisioned virtual endpoint; on Linux it is a per-pad PipeWire sink minted with the DualSense identity games match on. |
|
||||
| `PUNKTFUNK_PAD_AUDIO` | `1` · `0` *(default on)* | Controller audio: what a game plays through the DualSense's built-in speaker and voice-coil haptics is streamed to the client's physical pad as its own low-latency plane. On by default and free while idle — silence is never encoded or sent; `0` turns it off host-wide. On Windows the pad's audio device is a pre-provisioned virtual endpoint; on Linux it is a per-pad PipeWire sink minted with the DualSense identity games match on — see [Controller speaker and haptics](/docs/controller-audio). |
|
||||
| `PUNKTFUNK_PAD_AUDIO_SLOTS` | `1`–`4` *(default: Windows `1`, Linux `4`)* | How many controllers can have their own audio at once. On Windows each slot is a pre-provisioned virtual endpoint, so the default stays at one; a Linux sink is minted lazily and costs nothing idle, so every slot is on. |
|
||||
| `PUNKTFUNK_PAD_SINK_NAME` / `PUNKTFUNK_PAD_SINK_DESC` | templates | **(Linux, field debugging)** Override the minted pad sink's `node.name` / `node.description`. `{pad}` and `{mac}` expand per pad. Only for chasing a title whose device matcher wants different strings — the defaults carry every known match surface. |
|
||||
| `PUNKTFUNK_PAD_SINK_SPLIT_NAME` | node name · `0` *(default: the sink's own name)* | **(Linux, field debugging)** The `api.alsa.split.name` the pad sink advertises. GE-Proton opens that node as `pipewire:NODE=…` with AUX channels for its preferred haptic path; on a real pad it names the hidden 4-channel parent behind the mono speaker split, and our sink has no split, so it names itself. `0` drops the key, which pushes GE onto its Pulse-routed leg instead. |
|
||||
|
||||
## Audio / microphone
|
||||
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
title: Controller speaker and haptics
|
||||
description: DualSense voice-coil haptics and the pad's built-in speaker, streamed from the host to the controller in your hands — what to enable, and what "set it to Pro Audio" means on a Linux host.
|
||||
---
|
||||
|
||||
A DualSense is partly an audio device. Its little speaker and its two voice-coil motors — the
|
||||
actuators that make a PS5 pad feel like sand, rain or a bowstring instead of a buzzing phone —
|
||||
are all driven by a four-channel audio stream, not by rumble commands. Games that support them
|
||||
write PCM into "the controller's audio device".
|
||||
|
||||
Punktfunk gives that device to the game on the host, captures what the game writes, and streams
|
||||
it to the controller physically in your hands, on its own low-latency plane. Channels 1–2 are the
|
||||
pad's speaker, channels 3–4 are the voice coils.
|
||||
|
||||
## What you need
|
||||
|
||||
- **A DualSense or DualSense Edge plugged in over USB** on the client. Bluetooth pads expose no
|
||||
audio interface at all, so they fall back to ordinary rumble — this is a limit of the
|
||||
controller, not of Punktfunk.
|
||||
- On the client, **Controller haptics** is on by default. **Controller speaker** is opt-in: turn
|
||||
it on if you want game audio coming out of the pad as well as your speakers.
|
||||
- On a **Linux host**, a game that speaks DualSense — which in practice means running it under
|
||||
**GE-Proton 11-5 or newer**. Stock Proton does not route controller audio.
|
||||
- On the host, controller audio is on by default (`PUNKTFUNK_PAD_AUDIO`).
|
||||
|
||||
Nothing is sent while the pad is quiet, so leaving it on costs nothing.
|
||||
|
||||
## "Set the controller audio to Pro Audio" — you don't have to
|
||||
|
||||
If you have looked into DualSense haptics on Linux before, you have probably run into this
|
||||
advice: plug the pad into the Linux box, open your sound settings, find *DualSense wireless
|
||||
controller (PS5)*, and switch its **Profile** to **Pro Audio**. That advice is real and it is
|
||||
correct — for a pad plugged directly into the host.
|
||||
|
||||
The reason is channel layout. A pad's other profiles present it as a mono speaker, a stereo
|
||||
headphone jack, or a positioned four-channel "surround" device. Games write their haptics as four
|
||||
*unpositioned* channels, so on any of those profiles the audio system helpfully re-mixes them into
|
||||
the speaker pair and the voice-coil channels are folded away. You feel nothing. Pro Audio is the
|
||||
one profile that hands the four channels through untouched, in order.
|
||||
|
||||
**Punktfunk's controller audio device is already in that shape.** It is created as four raw
|
||||
channels with no re-mixing, which is exactly what Pro Audio produces — so there is nothing to
|
||||
switch, and no switch to make.
|
||||
|
||||
That is also why it looks different in your sound settings. A real pad is a USB sound card, so it
|
||||
gets a **Profile** dropdown; Punktfunk's is a software device, so it has no card and no dropdown.
|
||||
Seeing **Wireless Controller** with a volume slider and no profile selector is what a correctly
|
||||
minted controller-audio device looks like. It is not a sign that something is missing.
|
||||
|
||||
## Checking it is working
|
||||
|
||||
On the host, one line per pad is logged when the device is created:
|
||||
|
||||
```
|
||||
pad-audio sink minted (Pro Audio shape: 4ch AUX0..AUX3, ch0/1 speaker, ch2/3 coils)
|
||||
```
|
||||
|
||||
and, once a client that can render it connects:
|
||||
|
||||
```
|
||||
pad audio streaming (0xD1, Opus 48 kHz, silence-gated)
|
||||
```
|
||||
|
||||
When a game actually starts driving the actuators, the pad's own driver reports it:
|
||||
|
||||
```
|
||||
DS5 title asserted haptics-select (audio haptics) pad=0
|
||||
```
|
||||
|
||||
That last line is the one that matters: it means a title recognised the controller as an audio
|
||||
device and switched the pad out of plain rumble. If you see it and still feel nothing, the problem
|
||||
is downstream — on the client or the pad. If you never see it, the game never found the device.
|
||||
|
||||
You can also look at the device directly:
|
||||
|
||||
```sh
|
||||
pactl list sinks | grep -A25 Speaker__sink
|
||||
```
|
||||
|
||||
The line to check is `audio.position = "AUX0,AUX1,AUX2,AUX3"` — four unpositioned channels is the
|
||||
layout that reaches the voice coils. Anything positioned (`FL,FR,RL,RR`) would not.
|
||||
|
||||
## If a game does not find it
|
||||
|
||||
Games identify the controller's audio device by name and by USB ids, and different titles check
|
||||
different things. GE-Proton has several routes to the pad, and a couple of them are opt-in per
|
||||
game. Add these as launch options if a title is not cooperating:
|
||||
|
||||
```
|
||||
PROTON_DUALSENSE_HAPTICS_PREFER_NON_EVENT=1 %command%
|
||||
```
|
||||
|
||||
This forces GE onto its most direct route — it opens Punktfunk's controller-audio device by name
|
||||
and writes the four channels straight into it, with no re-mixing anywhere in between. It is the
|
||||
first thing to try.
|
||||
|
||||
Some titles additionally want:
|
||||
|
||||
```
|
||||
PROTON_SONY_WINDOWS_DEVICE_NAMES=1 PROTON_KEEP_SONY_AUDIO_ENDPOINT_VISIBLE=1 %command%
|
||||
```
|
||||
|
||||
and *Death Stranding Director's Cut* has its own:
|
||||
|
||||
```
|
||||
PROTON_DUALSENSE_SPLIT_AUDIO=1 %command%
|
||||
```
|
||||
|
||||
To see which route GE took, launch the game with `WINEDEBUG=+pulse` and look for a line beginning
|
||||
`Routing DualSense`. It names the device it chose and how it opened it.
|
||||
|
||||
## Known limits
|
||||
|
||||
- **Bluetooth client pads get rumble, not haptics.** No audio interface exists over BT.
|
||||
- **Titles that match the controller by container ID** — a Windows notion of "these devices are
|
||||
the same physical thing" — will not recognise the pairing on a Linux host, because the virtual
|
||||
pad has no USB device behind it to derive one from. Titles that match by name or by USB ids are
|
||||
unaffected, which is most of them.
|
||||
- **A pad plugged into the host itself can steal the audio.** If a real DualSense is connected to
|
||||
the host while you are streaming to a different one, some titles will find the local pad's sound
|
||||
card first. Unplug it, or stream from a host that has no pad attached.
|
||||
@@ -42,6 +42,7 @@
|
||||
"moonlight",
|
||||
"---Using Punktfunk---",
|
||||
"input",
|
||||
"controller-audio",
|
||||
"client-settings",
|
||||
"profiles-and-links",
|
||||
"game-library",
|
||||
|
||||
Reference in New Issue
Block a user