Skip to content

Add Elgato Wave:3 support#3

Open
AlexDicy wants to merge 5 commits into
rikkichy:mainfrom
AlexDicy:main
Open

Add Elgato Wave:3 support#3
AlexDicy wants to merge 5 commits into
rikkichy:mainfrom
AlexDicy:main

Conversation

@AlexDicy

Copy link
Copy Markdown

Hello there! Thank you for this awesome project, really really useful as I just started out with CachyOS and it's a pain to use the Elgato Wave 3 with linux.

I did create most of the code changes using Claude Code, but I manually reviewed every patch and adjusted where needed. Feel free to change as needed but I hope this PR proves useful.

I tested changing gain, volume, mix and mute status from the OpenWave UI, physically from the microphone, and from the OS's volumes panel. They're all synchronized (as long as OpenWave is running, at least minimized).
Volume restore also seems to be working as expected (OpenWave sends the gain/volume to ALSA, WirePlumber/PipeWire recognizes it, at reboot it sets it back as expected).

Feel free to edit/override as you see fit.

Now to the full description:

OpenWave supports the Wave XLR (0fd9:007d). This PR adds support for the Wave:3 microphone (0fd9:0070): gain, mute, headphone volume, monitor mix, dial-mode display, bidirectional hardware ↔ OS volume sync, the capture-fix daemon, and first-run setup.

Approach

The Wave:3 speaks the same vendor protocol as the XLR (control transfers via the unclaimed vendor interface, wIndex=0x3303), but with a different config-block layout. All device-specific constants were moved into per-model profiles (wavexlr/profiles.py); device.py is parameterized by profile and connects to the first supported device it finds (one device at a time). The Wave XLR profile reproduces the previously hardcoded constants 1:1, so XLR behavior is unchanged.

The Wave:3 layout was reverse-engineered and verified on hardware (fw 1.2.2) — 16-byte config block:

Field Offset Encoding
Gain 0–1 uint16 LE, Q8.8 dB (0 … +40 dB)
Mute 4 u8, same as XLR
HP volume 7–8 int16 LE, Q8.8 dB
Monitor mix 10–11 uint16 LE, Q8.8 percent (0–100%)
Dial mode 12 1=gain, 2=headphones, 3=monitor mix

The UI adapts to the connected model's capabilities: monitor-mix slider (Wave:3), low-impedance toggle (XLR only), gain shown in dB where the firmware works in dB. The probe CLI used for the mapping (python3 -m wavexlr.probedump/watch/poke) is included to make supporting future devices easier.

Also in this PR

  • udev rules cover both PIDs; setup now detects outdated rules/configs on upgrade and re-installs them
  • Daemon, mixer, and WirePlumber node matching generalized to the shared Elgato_Wave_ prefix
  • Wave:3 only: firmware gain changes are pushed to the ALSA Mic Capture Volume control, so desktop volume panels follow the physical dial (the XLR exposes no ALSA gain control)
  • New openwave-autostart.desktop template (installed to share/openwave/, opt-in via ~/.config/autostart/)
  • README fix: the start-hidden flag is --hide, not -- --hide — GLib treats -- as end-of-options, so the documented form never worked

Notes

  • Wave XLR changes are verified by inspection only (profile values diff 1:1 against the old constants; the ALSA sync logic is control-flow identical) — I don't own an XLR to retest on hardware.
  • The Wave:3 firmware services vendor requests from one process at a time; concurrent access fails with I/O errors. Documented in the README — quit OpenWave before running the probe.
  • Clipguard is not implemented: it has no physical control to diff against; config bytes 3 and 5 remain unidentified candidates.

AlexDicy and others added 5 commits June 10, 2026 21:43
Device layer now picks a per-model DeviceProfile (profiles.py); the
XLR profile carries the old device.py constants unchanged, connect()
takes the first supported model found.

Wave:3 vendor protocol mapped on hardware — same 0x85/0x05 transfers
and wIndex 0x3303, different blocks: 16-byte config with gain uint16
Q8.8 dB @0 (max 0x2800), mute @4, HP volume int16 Q8.8 @7, monitor
mix uint16 Q8.8 percent @10 (0..0x6400), dial mode @12 (1=gain 2=hp
3=mix); 8-byte meters; fw version @21-23 and serial @36-47 in devinfo.
ALSA numids/ranges match the XLR, so the firmware<->ALSA sync carries
over as-is. probe.py (dump/watch/poke) is the CLI the mapping was
done with.

UI adapts to the connected profile: gain shown in dB, low-Z row
hidden, monitor mix slider, dial-mode label. udev rule now covers
both PIDs and setup re-runs when installed rules/configs are stale.
Daemon keepalive, mixer node lookup and the WirePlumber rule match
the shared Elgato_Wave_ prefix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant