Hi Undertone team,
I've been reverse-engineering the first-generation Elgato Wave:3 USB protocol for a native Linux daemon over at LukasParke/wave3-research. After reviewing your project, I believe we have substantial data that would let you move the Wave:3 hardware control from the ALSA fallback to native USB control, and add several hardware features that are currently listed as deferred / unimplemented.
What Undertone appears to be doing today
From crates/undertone-hid/src/device.rs and crates/undertone-hid/src/alsa_fallback.rs:
What we mapped on the hardware
1. The wIndex routing trick
snd-usb-audio owns AudioControl interface 0, so direct UAC requests on interface 0 fail. The Wave:3 firmware will honor UAC and proprietary class requests if you route them through the unclaimed vendor interface 3 while embedding the entity in the high byte of wIndex:
wIndex = (entity << 8) | 3
For example, the microphone feature unit is entity 6. With this trick you can read/write it without detaching the kernel driver.
2. Standard UAC controls that work
| Control |
Entity |
Selector |
Direction |
Notes |
| Mic gain |
6 |
UAC_VOLUME (0x02) |
Read only from software |
GET_CUR works; SET_CUR returns success but the firmware ignores it on this revision. The physical dial is the only way to move mic gain. |
| Mic mute |
6 |
UAC_MUTE (0x01) |
Read/write |
Fully bidirectional. |
| HP volume |
5 |
UAC_VOLUME (0x02) |
Read/write |
Range -60.0 dB … 0.0 dB. |
| HP mute |
5 |
UAC_MUTE (0x01) |
Read/write |
Fully bidirectional. |
3. Proprietary 16-byte class config block
Accessed with class-type control transfers on wIndex = 0x3303:
Read config: bmRequestType=0xA1, bRequest=0x85, wValue=0x0000, wLength=16
Write config: bmRequestType=0x21, bRequest=0x05, wValue=0x0000, wLength=16
Read meter: bmRequestType=0xA1, bRequest=0x85, wValue=0x0001, wLength=8
Read info: bmRequestType=0xA1, bRequest=0x85, wValue=0x000A, wLength=51
Config block layout:
| Offset |
Field |
| 0 |
Dial value low byte |
| 1 |
Dial value high byte |
| 4 |
Mic mute (0x00 live, 0x01 muted) |
| 5 |
Clipguard (0x00 off, 0x01 on) |
| 7 |
Dial flag (toggles 0x00/0x80 while adjusting HP volume) |
| 8 |
Headphone volume (signed 8-bit dB attenuation) |
| 9 |
Headphone mute |
| 10 |
Indicator / mute-ring R |
| 11 |
Indicator / mute-ring G; also monitor-mix value in mix mode |
| 12 |
Dial mode (0x01=mic gain, 0x02=HP volume, 0x03=monitor mix) |
| 13 |
Indicator / mute-ring B |
| 14 |
Direct monitor mix (0x00=mic only, 0xFF=PC only) |
| 15 |
LED brightness (0x00 off, 0xFF max) |
This gives you Clipguard, direct monitor mix, mute-ring RGB color, LED brightness, dial mode, dial value, and VU-meter-style input/playback levels.
4. What is NOT exposed in hardware
The first-gen Wave:3 has no hardware low-cut filter, no hardware compressor/EQ, and no headphone color LED. Those are handled in Wave Link's host-side DSP. Calling them "not supported" is the correct behavior.
5. Safety note
Vendor-type control transfers (bmRequestType=0xC1/0x41) caused our test unit to reboot into DFU mode. Use class-type requests (0xA1/0x21) for config/meter access.
Suggested integration for Undertone
- Use
rusb to claim interface 3 and use the wIndex = (entity << 8) | 3 trick for UAC controls.
- Use the proprietary class config block for Clipguard, direct monitor mix, LED brightness, mute-ring color, and dial/meter state.
- Keep the ALSA fallback as a fallback, but you can now sync the hardware mute button and provide true hardware Clipguard / monitor-mix controls.
- The WirePlumber topology you already ship (
wave3-source, wave3-sink, wave3-null-sink) is the same one we converged on; our configs may differ only in default descriptions and sample-rate hints.
Reference material
I'm happy to answer questions or help test a Rust implementation. We tested all of this on a live first-gen Wave:3 under Arch Linux.
Hi Undertone team,
I've been reverse-engineering the first-generation Elgato Wave:3 USB protocol for a native Linux daemon over at LukasParke/wave3-research. After reviewing your project, I believe we have substantial data that would let you move the Wave:3 hardware control from the ALSA fallback to native USB control, and add several hardware features that are currently listed as deferred / unimplemented.
What Undertone appears to be doing today
From
crates/undertone-hid/src/device.rsandcrates/undertone-hid/src/alsa_fallback.rs:0x0fd9:0x0070) and enumerates the ALSA card.amixer sset Mic .../sset Mic mute/unmute.PROGRESS.mdlists "Wave:3 HID integration (reverse-engineer protocol)" as Deferred.What we mapped on the hardware
1. The
wIndexrouting tricksnd-usb-audioowns AudioControl interface 0, so direct UAC requests on interface 0 fail. The Wave:3 firmware will honor UAC and proprietary class requests if you route them through the unclaimed vendor interface 3 while embedding the entity in the high byte ofwIndex:For example, the microphone feature unit is entity
6. With this trick you can read/write it without detaching the kernel driver.2. Standard UAC controls that work
UAC_VOLUME(0x02)UAC_MUTE(0x01)UAC_VOLUME(0x02)-60.0 dB … 0.0 dB.UAC_MUTE(0x01)3. Proprietary 16-byte class config block
Accessed with class-type control transfers on
wIndex = 0x3303:Config block layout:
0x00live,0x01muted)0x00off,0x01on)0x00/0x80while adjusting HP volume)0x01=mic gain,0x02=HP volume,0x03=monitor mix)0x00=mic only,0xFF=PC only)0x00off,0xFFmax)This gives you Clipguard, direct monitor mix, mute-ring RGB color, LED brightness, dial mode, dial value, and VU-meter-style input/playback levels.
4. What is NOT exposed in hardware
The first-gen Wave:3 has no hardware low-cut filter, no hardware compressor/EQ, and no headphone color LED. Those are handled in Wave Link's host-side DSP. Calling them "not supported" is the correct behavior.
5. Safety note
Vendor-type control transfers (
bmRequestType=0xC1/0x41) caused our test unit to reboot into DFU mode. Use class-type requests (0xA1/0x21) for config/meter access.Suggested integration for Undertone
rusbto claim interface 3 and use thewIndex = (entity << 8) | 3trick for UAC controls.wave3-source,wave3-sink,wave3-null-sink) is the same one we converged on; our configs may differ only in default descriptions and sample-rate hints.Reference material
WAVE3_PROTOCOL_SUMMARY.mdnative-linux/src/wave3-daemon.cnative-linux/src/pipewire-sync.cnative-linux/wireplumber/scripts/elgato-wave3.luaI'm happy to answer questions or help test a Rust implementation. We tested all of this on a live first-gen Wave:3 under Arch Linux.