Skip to content

Reverse-engineered Wave:3 native control protocol (data for HID implementation) #2

Description

@LukasParke

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

  1. Use rusb to claim interface 3 and use the wIndex = (entity << 8) | 3 trick for UAC controls.
  2. Use the proprietary class config block for Clipguard, direct monitor mix, LED brightness, mute-ring color, and dial/meter state.
  3. Keep the ALSA fallback as a fallback, but you can now sync the hardware mute button and provide true hardware Clipguard / monitor-mix controls.
  4. 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.

  • Luke

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions