From 12f50c41ea1f6574bdb869056ddc9d89f986236c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20M=C3=B3dolo?= Date: Thu, 10 Jul 2025 13:22:51 -0400 Subject: [PATCH 1/8] fix(audeze_maxwell): add delay to prevent rapid battery request issues, add mic status detection, and fix reconnect bugs --- src/devices/audeze_maxwell.c | 90 +++++++++++++++++++++++++++--------- 1 file changed, 67 insertions(+), 23 deletions(-) diff --git a/src/devices/audeze_maxwell.c b/src/devices/audeze_maxwell.c index ebdade28..4691cb4b 100644 --- a/src/devices/audeze_maxwell.c +++ b/src/devices/audeze_maxwell.c @@ -3,7 +3,9 @@ #include "hidapi.h" #include +#include #include +#include static struct device device_maxwell; @@ -45,26 +47,57 @@ void audeze_maxwell_init(struct device** device) *device = &device_maxwell; } -int read_audeze_maxwell_status(hid_device* device_handle, unsigned char* buff) +static int send_get_input_report(hid_device* device_handle, const uint8_t* data, uint8_t* buff) { - buff[0] = 0x7; - return hid_get_input_report(device_handle, buff, MSG_SIZE); + // Audeze HQ sends packets at intervals of approximately 60ms, so we do the same. Sending too quickly causes issues. + usleep(60000); + + int res = hid_write(device_handle, data, MSG_SIZE); + if (res >= 0) { + if (buff == NULL) { + uint8_t tempBuff[MSG_SIZE] = { 0x7 }; + buff = tempBuff; + } else { + buff[0] = 0x7; + } + res = hid_get_input_report(device_handle, buff, MSG_SIZE); + } + + return res; } +static const uint8_t UNIQUE_REQUESTS[13][MSG_SIZE] = { + { 0x6, 0x8, 0x80, 0x5, 0x5a, 0x4, 0x0, 0x1, 0x9, 0x20 }, + { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x01, 0x09, 0x25 }, + { 0x06, 0x07, 0x80, 0x05, 0x5A, 0x03, 0x00, 0x07, 0x1C }, + { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x01, 0x09, 0x28 }, + { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x83, 0x2C, 0x01 }, + { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x83, 0x2C, 0x07 }, + { 0x06, 0x07, 0x00, 0x05, 0x5A, 0x03, 0x00, 0x07, 0x1C }, + { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x01, 0x09, 0x2D }, + { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x01, 0x09, 0x2C }, + { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x01, 0x09 }, + { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x83, 0x2C, 0x0B }, + { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x01, 0x09, 0x2F }, + { 0x06, 0x07, 0x80, 0x05, 0x5A, 0x03, 0x00, 0xD6, 0x0C } // Sent after the first 12 unique requests and the 5 info requests +}; + +// INFO_REQUESTS is used to request information from the headset, such as battery status and microphone status. All the known return values are annotated in the comments. +static const uint8_t INFO_REQUESTS[5][MSG_SIZE] = { + { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x01, 0x09, 0x22 }, // Battery and Mic + { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x01, 0x09 }, // Battery and Mic + { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x83, 0x2C, 0x0B }, // Battery + { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x01, 0x09, 0x2C }, // Battery + { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x83, 0x2C, 0x07 } // Battery +}; + static BatteryInfo audeze_maxwell_get_battery(hid_device* device_handle) { BatteryInfo info = { .status = BATTERY_UNAVAILABLE, .level = -1 }; - unsigned char data_request[MSG_SIZE] = { 0x06, 0x07, 0x80, 0x05, 0x5A, 0x03, 0x00, 0xD6, 0x0C }; - - if (hid_write(device_handle, data_request, MSG_SIZE) < 0) { - info.status = BATTERY_HIDERROR; - return info; - } - - unsigned char buf[MSG_SIZE]; - switch (read_audeze_maxwell_status(device_handle, buf)) { + uint8_t buf[MSG_SIZE]; + switch (send_get_input_report(device_handle, INFO_REQUESTS[0], buf)) { case 0: info.status = BATTERY_TIMEOUT; break; @@ -75,6 +108,10 @@ static BatteryInfo audeze_maxwell_get_battery(hid_device* device_handle) break; } + if (buf[12] == 0xFF) { + info.microphone_status = MICROPHONE_UP; + } + for (int i = 0; i < MSG_SIZE - 4; ++i) { if (buf[i] == 0xD6 && buf[i + 1] == 0x0C && buf[i + 2] == 0x00 && buf[i + 3] == 0x00) { info.level = buf[i + 4]; @@ -89,24 +126,31 @@ static BatteryInfo audeze_maxwell_get_battery(hid_device* device_handle) break; } + if (info.status != BATTERY_AVAILABLE) { + // When initializing, Audeze MQ sends 18 packets: the first 12 are unique, followed by 5 which repeat every second and 1 unique. The following packet is the last unique one before the repetition cycle begins. + // This one seems to "reset" something in the headset, so we send it to ensure we get the correct data. It is necessary when the headset is turned off and on again. + // Sending the other 12 unique requests seems unnecessary. + send_get_input_report(device_handle, UNIQUE_REQUESTS[12], NULL); + } + return info; } -int audeze_maxwell_toggle_sidetone(hid_device* device_handle) +static int audeze_maxwell_toggle_sidetone(hid_device* device_handle) { // Audeze HQ changes the byte at index 11, but it has no effect, it’s always toggleable regardless of what’s sent. - unsigned char data_request[MSG_SIZE] = { 0x6, 0x9, 0x80, 0x5, 0x5a, 0x5, 0x0, 0x82, 0x2c, 0x7, 0x0, 0x1 }; + uint8_t data_request[MSG_SIZE] = { 0x6, 0x9, 0x80, 0x5, 0x5a, 0x5, 0x0, 0x82, 0x2c, 0x7, 0x0, 0x1 }; return hid_write(device_handle, data_request, MSG_SIZE); } -int audeze_maxwell_send_sidetone(hid_device* device_handle, uint8_t num) +static int audeze_maxwell_send_sidetone(hid_device* device_handle, uint8_t num) { // The range of the maxwell seems to be from 0 to 31 num = map(num, 0, 128, 0, 31); - unsigned char data_request[MSG_SIZE] = { 0x6, 0x9, 0x80, 0x5, 0x5a, 0x5, 0x0, 0x0, 0x9, 0x2c, 0x0, num }; + uint8_t data_request[MSG_SIZE] = { 0x6, 0x9, 0x80, 0x5, 0x5a, 0x5, 0x0, 0x0, 0x9, 0x2c, 0x0, num }; // The sidetone is enabled whenever its level changes. int res = hid_write(device_handle, data_request, MSG_SIZE); @@ -119,9 +163,9 @@ int audeze_maxwell_send_sidetone(hid_device* device_handle, uint8_t num) } // Audeze HQ supports up to 6 hours of idle time, but the send_inactive_time API caps it at 90 minutes. -int audeze_maxwell_send_inactive_time(hid_device* device_handle, uint8_t num) +static int audeze_maxwell_send_inactive_time(hid_device* device_handle, uint8_t num) { - unsigned char data_request[MSG_SIZE] = { 0x6, 0x10, 0x80, 0x5, 0x5a, 0xc, 0x0, 0x82, 0x2c, 0x1, 0x0 }; + uint8_t data_request[MSG_SIZE] = { 0x6, 0x10, 0x80, 0x5, 0x5a, 0xc, 0x0, 0x82, 0x2c, 0x1, 0x0 }; // If num is 0, the inactive time flag is set to 0x00, disabling the automatic shutdown feature. if (num > 0) { @@ -160,24 +204,24 @@ int audeze_maxwell_send_inactive_time(hid_device* device_handle, uint8_t num) return hid_write(device_handle, data_request, MSG_SIZE); } -int audeze_maxwell_send_volume_limiter(hid_device* hid_device, uint8_t on) +static int audeze_maxwell_send_volume_limiter(hid_device* hid_device, uint8_t on) { - unsigned char data_request[MSG_SIZE] = { 0x6, 0x9, 0x80, 0x5, 0x5a, 0x5, 0x0, 0x0, 0x9, 0x28, 0x0, on == 1 ? 0x88 : 0x8e }; + uint8_t data_request[MSG_SIZE] = { 0x6, 0x9, 0x80, 0x5, 0x5a, 0x5, 0x0, 0x0, 0x9, 0x28, 0x0, on == 1 ? 0x88 : 0x8e }; return hid_write(hid_device, data_request, MSG_SIZE); } // Audeze Maxwell has 6 default presets and 4 custom presets (Audeze, Treble Boost, Bass Boost, Immersive, Competition, Footsteps, Preset 1, Preset 2, Preset 3, Preset 4) -int audeze_maxwell_send_equalizer_preset(hid_device* hid_device, uint8_t num) +static int audeze_maxwell_send_equalizer_preset(hid_device* hid_device, uint8_t num) { if (num < 1 || num > 10) { return HSC_OUT_OF_BOUNDS; } - unsigned char data_request[MSG_SIZE] = { 0x6, 0x9, 0x80, 0x5, 0x5a, 0x5, 0x0, 0x0, 0x9, 0x0, 0x0, num }; + uint8_t data_request[MSG_SIZE] = { 0x6, 0x9, 0x80, 0x5, 0x5a, 0x5, 0x0, 0x0, 0x9, 0x0, 0x0, num }; return hid_write(hid_device, data_request, MSG_SIZE); } -int audeze_maxwell_send_equalizer_custom_preset(hid_device* hid_device, uint8_t num) +static int audeze_maxwell_send_equalizer_custom_preset(hid_device* hid_device, uint8_t num) { // Getting only custom presets return audeze_maxwell_send_equalizer_preset(hid_device, 7 + num); From 5910afad03d31fdc583c95cc95853ad52e03b907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20M=C3=B3dolo?= Date: Thu, 10 Jul 2025 13:32:13 -0400 Subject: [PATCH 2/8] fix(audeze_maxwell): fix format-check --- src/devices/audeze_maxwell.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/devices/audeze_maxwell.c b/src/devices/audeze_maxwell.c index 4691cb4b..c2c3cc4c 100644 --- a/src/devices/audeze_maxwell.c +++ b/src/devices/audeze_maxwell.c @@ -56,7 +56,7 @@ static int send_get_input_report(hid_device* device_handle, const uint8_t* data, if (res >= 0) { if (buff == NULL) { uint8_t tempBuff[MSG_SIZE] = { 0x7 }; - buff = tempBuff; + buff = tempBuff; } else { buff[0] = 0x7; } From aa9757b9694a16d063fdd8d87bbeeb0df4089c63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20M=C3=B3dolo?= Date: Thu, 10 Jul 2025 13:55:07 -0400 Subject: [PATCH 3/8] fix(audeze_maxwell): add all unique requests when battery is not available --- src/devices/audeze_maxwell.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/devices/audeze_maxwell.c b/src/devices/audeze_maxwell.c index c2c3cc4c..f80528c6 100644 --- a/src/devices/audeze_maxwell.c +++ b/src/devices/audeze_maxwell.c @@ -66,6 +66,7 @@ static int send_get_input_report(hid_device* device_handle, const uint8_t* data, return res; } +// When initializing, Audeze MQ sends 18 packets: the first 12 are unique, followed by 5 which repeat every second and 1 unique. The last package is sent after the first 12 unique requests and the 5 info requests. static const uint8_t UNIQUE_REQUESTS[13][MSG_SIZE] = { { 0x6, 0x8, 0x80, 0x5, 0x5a, 0x4, 0x0, 0x1, 0x9, 0x20 }, { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x01, 0x09, 0x25 }, @@ -127,10 +128,10 @@ static BatteryInfo audeze_maxwell_get_battery(hid_device* device_handle) } if (info.status != BATTERY_AVAILABLE) { - // When initializing, Audeze MQ sends 18 packets: the first 12 are unique, followed by 5 which repeat every second and 1 unique. The following packet is the last unique one before the repetition cycle begins. - // This one seems to "reset" something in the headset, so we send it to ensure we get the correct data. It is necessary when the headset is turned off and on again. - // Sending the other 12 unique requests seems unnecessary. - send_get_input_report(device_handle, UNIQUE_REQUESTS[12], NULL); + // Audeze HQ sends 13 unique requests when opened, so to ensure we get the correct information, we send all of them. + for (int i = 0; i < 13; ++i) { + send_get_input_report(device_handle, UNIQUE_REQUESTS[i], NULL); + } } return info; From a020fc8f2f11ce0ea24542017a3a9a341844b3ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20M=C3=B3dolo?= Date: Thu, 10 Jul 2025 16:29:18 -0400 Subject: [PATCH 4/8] fix(audeze_maxwell): ensure all requests use send_get_input_report for reliability --- src/devices/audeze_maxwell.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/devices/audeze_maxwell.c b/src/devices/audeze_maxwell.c index f80528c6..8cf885a0 100644 --- a/src/devices/audeze_maxwell.c +++ b/src/devices/audeze_maxwell.c @@ -142,7 +142,7 @@ static int audeze_maxwell_toggle_sidetone(hid_device* device_handle) // Audeze HQ changes the byte at index 11, but it has no effect, it’s always toggleable regardless of what’s sent. uint8_t data_request[MSG_SIZE] = { 0x6, 0x9, 0x80, 0x5, 0x5a, 0x5, 0x0, 0x82, 0x2c, 0x7, 0x0, 0x1 }; - return hid_write(device_handle, data_request, MSG_SIZE); + return send_get_input_report(device_handle, data_request, NULL); } static int audeze_maxwell_send_sidetone(hid_device* device_handle, uint8_t num) @@ -154,7 +154,7 @@ static int audeze_maxwell_send_sidetone(hid_device* device_handle, uint8_t num) uint8_t data_request[MSG_SIZE] = { 0x6, 0x9, 0x80, 0x5, 0x5a, 0x5, 0x0, 0x0, 0x9, 0x2c, 0x0, num }; // The sidetone is enabled whenever its level changes. - int res = hid_write(device_handle, data_request, MSG_SIZE); + int res = send_get_input_report(device_handle, data_request, NULL); if (num == 0) { return audeze_maxwell_toggle_sidetone(device_handle); @@ -202,13 +202,13 @@ static int audeze_maxwell_send_inactive_time(hid_device* device_handle, uint8_t data_request[18] = data_request[14]; // MSB Again } - return hid_write(device_handle, data_request, MSG_SIZE); + return send_get_input_report(device_handle, data_request, NULL); } static int audeze_maxwell_send_volume_limiter(hid_device* hid_device, uint8_t on) { uint8_t data_request[MSG_SIZE] = { 0x6, 0x9, 0x80, 0x5, 0x5a, 0x5, 0x0, 0x0, 0x9, 0x28, 0x0, on == 1 ? 0x88 : 0x8e }; - return hid_write(hid_device, data_request, MSG_SIZE); + return send_get_input_report(hid_device, data_request, NULL); } // Audeze Maxwell has 6 default presets and 4 custom presets (Audeze, Treble Boost, Bass Boost, Immersive, Competition, Footsteps, Preset 1, Preset 2, Preset 3, Preset 4) @@ -219,7 +219,7 @@ static int audeze_maxwell_send_equalizer_preset(hid_device* hid_device, uint8_t } uint8_t data_request[MSG_SIZE] = { 0x6, 0x9, 0x80, 0x5, 0x5a, 0x5, 0x0, 0x0, 0x9, 0x0, 0x0, num }; - return hid_write(hid_device, data_request, MSG_SIZE); + return send_get_input_report(hid_device, data_request, NULL); } static int audeze_maxwell_send_equalizer_custom_preset(hid_device* hid_device, uint8_t num) From 278949dc526280be4eb817a767abb2e8811dcddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20M=C3=B3dolo?= Date: Sun, 13 Jul 2025 17:53:51 -0400 Subject: [PATCH 5/8] feat(audeze_maxwell): improve status request, add chatmix and voice prompts support --- README.md | 2 +- src/devices/audeze_maxwell.c | 182 +++++++++++++++++++++++++---------- 2 files changed, 132 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 3e291831..09185fe3 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ talking. This differs from a simple loopback via PulseAudio as you won't have an | Device | sidetone | battery | notification sound | lights | inactive time | chatmix | voice prompts | rotate to mute | equalizer preset | equalizer | parametric equalizer | microphone mute led brightness | microphone volume | volume limiter | bluetooth when powered on | bluetooth call volume | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| Audeze Maxwell | x | x | | | x | | | | x | | | | | x | | | +| Audeze Maxwell | x | x | | | x | x | x | | x | | | | | x | | | | Corsair Headset Device | x | x | x | x | | | | | | | | | | | | | | HyperX Cloud Alpha Wireless | x | x | | | x | | x | | | | | | | | | | | HyperX Cloud Flight Wireless | | x | | | | | | | | | | | | | | | diff --git a/src/devices/audeze_maxwell.c b/src/devices/audeze_maxwell.c index 8cf885a0..aa455a86 100644 --- a/src/devices/audeze_maxwell.c +++ b/src/devices/audeze_maxwell.c @@ -19,6 +19,8 @@ static int audeze_maxwell_send_inactive_time(hid_device* device_handle, uint8_t static int audeze_maxwell_send_volume_limiter(hid_device* hid_device, uint8_t on); static int audeze_maxwell_send_equalizer_custom_preset(hid_device* hid_device, uint8_t num); static BatteryInfo audeze_maxwell_get_battery(hid_device* device_handle); +static int audeze_maxwell_get_chatmix(hid_device* device_handle); +static int audeze_maxwell_switch_voice_prompts(hid_device* device_handle, uint8_t on); #define MSG_SIZE 62 #define REPORT_ID 0x06 @@ -31,40 +33,36 @@ void audeze_maxwell_init(struct device** device) strncpy(device_maxwell.device_name, "Audeze Maxwell", sizeof(device_maxwell.device_name)); - device_maxwell.capabilities = B(CAP_SIDETONE) | B(CAP_INACTIVE_TIME) | B(CAP_VOLUME_LIMITER) | B(CAP_EQUALIZER_PRESET) | B(CAP_BATTERY_STATUS); + device_maxwell.capabilities = B(CAP_SIDETONE) | B(CAP_INACTIVE_TIME) | B(CAP_VOLUME_LIMITER) | B(CAP_EQUALIZER_PRESET) | B(CAP_BATTERY_STATUS) | B(CAP_CHATMIX_STATUS) | B(CAP_VOICE_PROMPTS); device_maxwell.capability_details[CAP_SIDETONE] = (struct capability_detail) { .usagepage = 0xff13, .usageid = 0x1, .interface = 0 }; device_maxwell.capability_details[CAP_INACTIVE_TIME] = (struct capability_detail) { .usagepage = 0xff13, .usageid = 0x1, .interface = 0 }; device_maxwell.capability_details[CAP_VOLUME_LIMITER] = (struct capability_detail) { .usagepage = 0xff13, .usageid = 0x1, .interface = 0 }; device_maxwell.capability_details[CAP_EQUALIZER_PRESET] = (struct capability_detail) { .usagepage = 0xff13, .usageid = 0x1, .interface = 0 }; device_maxwell.capability_details[CAP_BATTERY_STATUS] = (struct capability_detail) { .usagepage = 0xff13, .usageid = 0x1, .interface = 0 }; + device_maxwell.capability_details[CAP_CHATMIX_STATUS] = (struct capability_detail) { .usagepage = 0xff13, .usageid = 0x1, .interface = 0 }; + device_maxwell.capability_details[CAP_VOICE_PROMPTS] = (struct capability_detail) { .usagepage = 0xff13, .usageid = 0x1, .interface = 0 }; device_maxwell.send_sidetone = &audeze_maxwell_send_sidetone; device_maxwell.send_inactive_time = &audeze_maxwell_send_inactive_time; device_maxwell.send_volume_limiter = &audeze_maxwell_send_volume_limiter; device_maxwell.send_equalizer_preset = &audeze_maxwell_send_equalizer_custom_preset; device_maxwell.request_battery = &audeze_maxwell_get_battery; + device_maxwell.request_chatmix = &audeze_maxwell_get_chatmix; + device_maxwell.switch_voice_prompts = &audeze_maxwell_switch_voice_prompts; *device = &device_maxwell; } -static int send_get_input_report(hid_device* device_handle, const uint8_t* data, uint8_t* buff) -{ - // Audeze HQ sends packets at intervals of approximately 60ms, so we do the same. Sending too quickly causes issues. - usleep(60000); - - int res = hid_write(device_handle, data, MSG_SIZE); - if (res >= 0) { - if (buff == NULL) { - uint8_t tempBuff[MSG_SIZE] = { 0x7 }; - buff = tempBuff; - } else { - buff[0] = 0x7; - } - res = hid_get_input_report(device_handle, buff, MSG_SIZE); - } +typedef struct { + uint8_t sidetone_enabled; + int sidetone_level; +} AudezeMaxwellSidetoneInfo; - return res; -} +typedef struct { + BatteryInfo battery_info; + int chatmix_level; + AudezeMaxwellSidetoneInfo sidetone_info; +} AudezeMaxwellStatus; // When initializing, Audeze MQ sends 18 packets: the first 12 are unique, followed by 5 which repeat every second and 1 unique. The last package is sent after the first 12 unique requests and the 5 info requests. static const uint8_t UNIQUE_REQUESTS[13][MSG_SIZE] = { @@ -84,57 +82,110 @@ static const uint8_t UNIQUE_REQUESTS[13][MSG_SIZE] = { }; // INFO_REQUESTS is used to request information from the headset, such as battery status and microphone status. All the known return values are annotated in the comments. -static const uint8_t INFO_REQUESTS[5][MSG_SIZE] = { - { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x01, 0x09, 0x22 }, // Battery and Mic - { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x01, 0x09 }, // Battery and Mic +static const uint8_t STATUS_REQUESTS[5][MSG_SIZE] = { + { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x01, 0x09, 0x22 }, // Battery, Sidetone enabled + { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x01, 0x09 }, // Battery, Mic { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x83, 0x2C, 0x0B }, // Battery - { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x01, 0x09, 0x2C }, // Battery - { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x83, 0x2C, 0x07 } // Battery + { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x01, 0x09, 0x2C }, // Battery, ChatMix + { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x83, 0x2C, 0x07 } // Battery, Sidetone Level }; -static BatteryInfo audeze_maxwell_get_battery(hid_device* device_handle) +static int send_get_input_report(hid_device* device_handle, const uint8_t* data, uint8_t* buff) { + // Audeze HQ sends packets at intervals of approximately 60ms, so we do the same. Sending too quickly causes issues. + usleep(60000); - BatteryInfo info = { .status = BATTERY_UNAVAILABLE, .level = -1 }; - - uint8_t buf[MSG_SIZE]; - switch (send_get_input_report(device_handle, INFO_REQUESTS[0], buf)) { - case 0: - info.status = BATTERY_TIMEOUT; - break; - case MSG_SIZE: - // Headset Off - if (buf[1] == 0x00) { - info.status = BATTERY_UNAVAILABLE; - break; + int res = hid_write(device_handle, data, MSG_SIZE); + if (res == MSG_SIZE) { + if (buff == NULL) { + uint8_t tempBuff[MSG_SIZE] = { 0x7 }; + res = hid_get_input_report(device_handle, tempBuff, MSG_SIZE); + } else { + buff[0] = 0x7; + res = hid_get_input_report(device_handle, buff, MSG_SIZE); } + } - if (buf[12] == 0xFF) { - info.microphone_status = MICROPHONE_UP; - } + return res; +} - for (int i = 0; i < MSG_SIZE - 4; ++i) { - if (buf[i] == 0xD6 && buf[i + 1] == 0x0C && buf[i + 2] == 0x00 && buf[i + 3] == 0x00) { - info.level = buf[i + 4]; - info.status = BATTERY_AVAILABLE; - break; - } +static BatteryInfo audeze_maxwell_request_battery(const uint8_t(* status_buffs)[MSG_SIZE]) +{ + BatteryInfo battery_info = { .status = BATTERY_UNAVAILABLE, .level = -1 }; + + const uint8_t* buff = status_buffs[0]; + + // Headset Off + if (buff[1] == 0x00) { + battery_info.status = BATTERY_UNAVAILABLE; + return battery_info; + } + + if (status_buffs[1][12] == 0xFF) { + battery_info.microphone_status = MICROPHONE_UP; + } + + for (int i = 0; i < MSG_SIZE - 4; ++i) { + if (buff[i] == 0xD6 && buff[i + 1] == 0x0C && buff[i + 2] == 0x00 && buff[i + 3] == 0x00) { + battery_info.level = buff[i + 4]; + battery_info.status = BATTERY_AVAILABLE; + break; } + } + + return battery_info; +} + +static int audeze_maxwell_request_chatmix(const uint8_t(* status_buffs)[MSG_SIZE]) +{ + // The chatmix level is in the range of 0 to 20, where 10 is the center position. + return map(status_buffs[3][12], 0, 20, 0, 128); +} + +static AudezeMaxwellSidetoneInfo audeze_maxwell_request_sidetone(const uint8_t(* status_buffs)[MSG_SIZE]) +{ + AudezeMaxwellSidetoneInfo sidetone_info = { .sidetone_enabled = 0, .sidetone_level = -1 }; - break; - default: - info.status = BATTERY_HIDERROR; - break; + // The sidetone level is in the range of 0 to 31, where 15 is the center position. + sidetone_info.sidetone_level = map(status_buffs[4][12], 0, 31, 0, 128); + sidetone_info.sidetone_enabled = status_buffs[0][12]; + + return sidetone_info; +} + +// This function exists because getting just one piece of information from the headset can lead to inconsistencies and failures. So, we follow what Audeze HQ does, sending all packets every time we want to get the headset status. +static AudezeMaxwellStatus audeze_maxwell_status(hid_device* device_handle) +{ + AudezeMaxwellStatus status = { .battery_info = { .status = BATTERY_UNAVAILABLE, .level = -1 }, .chatmix_level = -1, .sidetone_info = { .sidetone_level = -1 } }; + + uint8_t status_buffs[5][MSG_SIZE]; + for (int i = 0; i < 5; ++i) { + if (send_get_input_report(device_handle, STATUS_REQUESTS[i], status_buffs[i]) != MSG_SIZE) { + status.battery_info.status = BATTERY_HIDERROR; + status.chatmix_level = HSC_ERROR; + status.sidetone_info.sidetone_level = HSC_ERROR; + + return status; + } } - if (info.status != BATTERY_AVAILABLE) { + status.battery_info = audeze_maxwell_request_battery(status_buffs); + status.chatmix_level = audeze_maxwell_request_chatmix(status_buffs); + status.sidetone_info = audeze_maxwell_request_sidetone(status_buffs); + + if (status.battery_info.status != BATTERY_AVAILABLE) { // Audeze HQ sends 13 unique requests when opened, so to ensure we get the correct information, we send all of them. for (int i = 0; i < 13; ++i) { send_get_input_report(device_handle, UNIQUE_REQUESTS[i], NULL); } } - return info; + return status; +} + +static BatteryInfo audeze_maxwell_get_battery(hid_device* device_handle) +{ + return audeze_maxwell_status(device_handle).battery_info; } static int audeze_maxwell_toggle_sidetone(hid_device* device_handle) @@ -227,3 +278,32 @@ static int audeze_maxwell_send_equalizer_custom_preset(hid_device* hid_device, u // Getting only custom presets return audeze_maxwell_send_equalizer_preset(hid_device, 7 + num); } + +// The chatmix level is in the range of 0 to 20, where 10 is the center position. +// static int audeze_maxwell_set_chatmix(hid_device* device_handle, uint8_t chatmix_level) +// { +// // The chatmix level is in the range of 0 to 20, where 10 is the center position. +// if (chatmix_level > 128 || chatmix_level < 0) { +// return HSC_OUT_OF_BOUNDS; +// } +// +// // Map the chatmix level from 0-128 to 0-20 +// chatmix_level = map(chatmix_level, 0, 128, 0, 20); +// +// uint8_t data_request[MSG_SIZE] = { 0x06, 0x09, 0x80, 0x05, 0x5A, 0x05, 0x00, 0x82, 0x2C, 0x0B, 0x00, chatmix_level }; +// +// return send_get_input_report(device_handle, data_request, NULL); +// } + +static int audeze_maxwell_get_chatmix(hid_device* device_handle) +{ + return audeze_maxwell_status(device_handle).chatmix_level; +} + +// The voice prompt level is in the range of 0 (0%) to 15 (100%), where 7 (50%) is the default level. +// The Headset Control API does not support setting the voice prompt level, so we set it to 7 (50%) when on, and 0 (0%) otherwise. +static int audeze_maxwell_switch_voice_prompts(hid_device* device_handle, uint8_t on) +{ + uint8_t data_request[MSG_SIZE] = { 0x06, 0x09, 0x80, 0x05, 0x5A, 0x05, 0x00, 0x00, 0x09, 0x20, 0x00, on == 1 ? 0x07 : 0x00 }; + return send_get_input_report(device_handle, data_request, NULL); +} From 52f58c888cb96c000dba436cd13c299e2b9be770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20M=C3=B3dolo?= Date: Sun, 13 Jul 2025 17:59:15 -0400 Subject: [PATCH 6/8] fix(audeze_maxwell): fix format-check --- src/devices/audeze_maxwell.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/devices/audeze_maxwell.c b/src/devices/audeze_maxwell.c index aa455a86..bea0b855 100644 --- a/src/devices/audeze_maxwell.c +++ b/src/devices/audeze_maxwell.c @@ -99,17 +99,17 @@ static int send_get_input_report(hid_device* device_handle, const uint8_t* data, if (res == MSG_SIZE) { if (buff == NULL) { uint8_t tempBuff[MSG_SIZE] = { 0x7 }; - res = hid_get_input_report(device_handle, tempBuff, MSG_SIZE); + res = hid_get_input_report(device_handle, tempBuff, MSG_SIZE); } else { buff[0] = 0x7; - res = hid_get_input_report(device_handle, buff, MSG_SIZE); + res = hid_get_input_report(device_handle, buff, MSG_SIZE); } } return res; } -static BatteryInfo audeze_maxwell_request_battery(const uint8_t(* status_buffs)[MSG_SIZE]) +static BatteryInfo audeze_maxwell_request_battery(const uint8_t(*status_buffs)[MSG_SIZE]) { BatteryInfo battery_info = { .status = BATTERY_UNAVAILABLE, .level = -1 }; @@ -136,18 +136,18 @@ static BatteryInfo audeze_maxwell_request_battery(const uint8_t(* status_buffs)[ return battery_info; } -static int audeze_maxwell_request_chatmix(const uint8_t(* status_buffs)[MSG_SIZE]) +static int audeze_maxwell_request_chatmix(const uint8_t (*status_buffs)[MSG_SIZE]) { // The chatmix level is in the range of 0 to 20, where 10 is the center position. return map(status_buffs[3][12], 0, 20, 0, 128); } -static AudezeMaxwellSidetoneInfo audeze_maxwell_request_sidetone(const uint8_t(* status_buffs)[MSG_SIZE]) +static AudezeMaxwellSidetoneInfo audeze_maxwell_request_sidetone(const uint8_t(*status_buffs)[MSG_SIZE]) { AudezeMaxwellSidetoneInfo sidetone_info = { .sidetone_enabled = 0, .sidetone_level = -1 }; // The sidetone level is in the range of 0 to 31, where 15 is the center position. - sidetone_info.sidetone_level = map(status_buffs[4][12], 0, 31, 0, 128); + sidetone_info.sidetone_level = map(status_buffs[4][12], 0, 31, 0, 128); sidetone_info.sidetone_enabled = status_buffs[0][12]; return sidetone_info; @@ -161,15 +161,15 @@ static AudezeMaxwellStatus audeze_maxwell_status(hid_device* device_handle) uint8_t status_buffs[5][MSG_SIZE]; for (int i = 0; i < 5; ++i) { if (send_get_input_report(device_handle, STATUS_REQUESTS[i], status_buffs[i]) != MSG_SIZE) { - status.battery_info.status = BATTERY_HIDERROR; - status.chatmix_level = HSC_ERROR; + status.battery_info.status = BATTERY_HIDERROR; + status.chatmix_level = HSC_ERROR; status.sidetone_info.sidetone_level = HSC_ERROR; return status; } } - status.battery_info = audeze_maxwell_request_battery(status_buffs); + status.battery_info = audeze_maxwell_request_battery(status_buffs); status.chatmix_level = audeze_maxwell_request_chatmix(status_buffs); status.sidetone_info = audeze_maxwell_request_sidetone(status_buffs); From b95e334f52ee3c008c96cd32320736d67dd0fe35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20M=C3=B3dolo?= Date: Sun, 13 Jul 2025 18:02:42 -0400 Subject: [PATCH 7/8] style(audeze_maxwell): fix format-check --- src/devices/audeze_maxwell.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/devices/audeze_maxwell.c b/src/devices/audeze_maxwell.c index bea0b855..0c44b601 100644 --- a/src/devices/audeze_maxwell.c +++ b/src/devices/audeze_maxwell.c @@ -109,7 +109,7 @@ static int send_get_input_report(hid_device* device_handle, const uint8_t* data, return res; } -static BatteryInfo audeze_maxwell_request_battery(const uint8_t(*status_buffs)[MSG_SIZE]) +static BatteryInfo audeze_maxwell_request_battery(const uint8_t (*status_buffs)[MSG_SIZE]) { BatteryInfo battery_info = { .status = BATTERY_UNAVAILABLE, .level = -1 }; @@ -142,7 +142,7 @@ static int audeze_maxwell_request_chatmix(const uint8_t (*status_buffs)[MSG_SIZE return map(status_buffs[3][12], 0, 20, 0, 128); } -static AudezeMaxwellSidetoneInfo audeze_maxwell_request_sidetone(const uint8_t(*status_buffs)[MSG_SIZE]) +static AudezeMaxwellSidetoneInfo audeze_maxwell_request_sidetone(const uint8_t (*status_buffs)[MSG_SIZE]) { AudezeMaxwellSidetoneInfo sidetone_info = { .sidetone_enabled = 0, .sidetone_level = -1 }; From c36c43f6c4c97398ff49a576a2081abfbec6755e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20M=C3=B3dolo?= Date: Mon, 14 Jul 2025 16:03:11 -0400 Subject: [PATCH 8/8] fix: fix -o querying each info capability twice --- src/main.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main.c b/src/main.c index 32006bfa..92405963 100644 --- a/src/main.c +++ b/src/main.c @@ -1020,7 +1020,6 @@ int main(int argc, char* argv[]) if (isExtendedOutput && feature_requests[i][j].type == CAPABILITYTYPE_INFO && !feature_requests[i][j].should_process) { if (device_has_capability(devices_found[i].device, feature_requests[i][j].cap)) { feature_requests[i][j].should_process = true; - feature_requests[i][j].result = handle_feature(devices_found[i].device, &device_handles[i], &hid_paths[i], feature_requests[i][j].cap, feature_requests[i][j].param); } } else if (feature_requests[i][j].type == CAPABILITYTYPE_ACTION && feature_requests[i][j].should_process) { if (headset_available > 1) {