-
-
Notifications
You must be signed in to change notification settings - Fork 216
Audeze Maxwell Noise Filter Capability and Audeze Maxwell 2 Protocol #517
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,10 +22,11 @@ namespace headsetcontrol { | |
| * - Battery status | ||
| * - Chatmix | ||
| * - Voice prompts | ||
| * - Mic noise filter | ||
| * | ||
| * Special Requirements: | ||
| * - Complex 18-packet initialization sequence | ||
| * - 60ms delay between packets (mimics official software) | ||
| * - 15-packet initialization sequence followed by 6 status requests | ||
| * - 60ms delay between packets (official software sends at 52ms but 60ms is more reliable in testing) | ||
| * - Uses hid_get_input_report for responses | ||
| */ | ||
| class AudezeMaxwell : public HIDDevice { | ||
|
|
@@ -39,8 +40,8 @@ class AudezeMaxwell : public HIDDevice { | |
| static constexpr int REPORT_ID = 0x06; | ||
| static constexpr int DELAY_US = 60000; // 60ms | ||
|
|
||
| // 18-packet initialization sequence (12 unique + 5 status + 1 final) | ||
| static constexpr std::array<std::array<uint8_t, MSG_SIZE>, 13> UNIQUE_REQUESTS { { { 0x6, 0x8, 0x80, 0x5, 0x5a, 0x4, 0x0, 0x1, 0x9, 0x20 }, | ||
| // 15-packet initialization sequence | ||
| static constexpr std::array<std::array<uint8_t, MSG_SIZE>, 15> UNIQUE_REQUESTS { { { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x01, 0x09, 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 }, | ||
|
|
@@ -51,17 +52,18 @@ class AudezeMaxwell : public HIDDevice { | |
| { 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, 0x24 }, | ||
| { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x01, 0x09, 0x2F }, | ||
| { 0x06, 0x09, 0x80, 0x05, 0x5A, 0x05, 0x00, 0x00, 0x09, 0x25, 0x00, 0x7A }, | ||
| { 0x06, 0x07, 0x80, 0x05, 0x5A, 0x03, 0x00, 0xD6, 0x0C } } }; | ||
|
|
||
| // Status requests (5 packets for battery, sidetone, chatmix info) | ||
| static constexpr std::array<std::array<uint8_t, MSG_SIZE>, 5> STATUS_REQUESTS { { | ||
| { 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, ChatMix | ||
| { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x83, 0x2C, 0x07 } // Battery, Sidetone Level | ||
| } }; | ||
| // 6 packet status request sequence | ||
| static constexpr std::array<std::array<uint8_t, MSG_SIZE>, 6> STATUS_REQUESTS { { { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x01, 0x09, 0x22 }, | ||
| { 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, 0x24 }, | ||
| { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x01, 0x09, 0x2C }, | ||
| { 0x06, 0x08, 0x80, 0x05, 0x5A, 0x04, 0x00, 0x83, 0x2C, 0x07 } } }; | ||
|
|
||
| constexpr uint16_t getVendorId() const override | ||
| { | ||
|
|
@@ -82,7 +84,7 @@ class AudezeMaxwell : public HIDDevice { | |
| { | ||
| return 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); | ||
| | B(CAP_VOICE_PROMPTS) | B(CAP_NOISE_FILTER); | ||
| } | ||
|
|
||
| constexpr capability_detail getCapabilityDetail([[maybe_unused]] enum capabilities cap) const override | ||
|
|
@@ -129,64 +131,87 @@ class AudezeMaxwell : public HIDDevice { | |
| } | ||
|
|
||
| /** | ||
| * @brief Get comprehensive device status (battery, chatmix, sidetone) | ||
| * @brief Get device status (battery, chatmix, sidetone, mic mute) | ||
| * | ||
| * Must send all 5 status requests to get consistent information | ||
| * Must send all 6 status requests to get consistent information | ||
| */ | ||
| struct MaxwellStatus { | ||
| BatteryResult battery; | ||
| int chatmix_level; | ||
| int sidetone_level; | ||
| int eq_setting; | ||
| int ainf_level; | ||
| bool sidetone_enabled; | ||
| bool mic_muted; | ||
| }; | ||
|
|
||
| Result<MaxwellStatus> getDeviceStatus(hid_device* device_handle) const | ||
| { | ||
| std::array<std::array<uint8_t, MSG_SIZE>, 5> status_buffs {}; | ||
| std::array<std::array<uint8_t, MSG_SIZE>, 6> status_buffs {}; | ||
|
|
||
| for (int i = 0; i < 5; ++i) { | ||
| auto result = sendGetInputReport(device_handle, STATUS_REQUESTS[i], status_buffs[i]); | ||
| if (!result) { | ||
| // Send UNIQUE_REQUESTS packets | ||
| for (auto unique_request : UNIQUE_REQUESTS) { | ||
| if (auto result = sendGetInputReport(device_handle, unique_request); !result) { | ||
| return result.error(); | ||
| } | ||
| } | ||
|
|
||
| // Send STATUS_REQUESTS packets | ||
| for (int i = 0; i < 6; ++i) { | ||
| if (auto result = sendGetInputReport(device_handle, STATUS_REQUESTS[i], status_buffs[i]); !result) { | ||
| return result.error(); | ||
| } | ||
| } | ||
|
|
||
| // Status_buffs structure | ||
| // status_buffs[0] = battery_status | ||
| // status_buffs[1] = mic_status | ||
| // status_buffs[2] = eq | ||
| // status_buffs[3] = chatmix | ||
| // status_buffs[4] = ainf_level | ||
| // status_buffs[5] = sidetone_level | ||
|
|
||
| MaxwellStatus status {}; | ||
|
|
||
| // Parse battery (from first status response) | ||
| const auto& buff = status_buffs[0]; | ||
| if (buff[1] == 0x00) { | ||
|
|
||
| // Initialize battery status as unavailable | ||
| status.battery.status = BATTERY_UNAVAILABLE; | ||
| status.battery.level_percent = -1; | ||
|
|
||
| // Parse battery level | ||
| if (buff[0] == 0x00) { | ||
| status.battery.status = BATTERY_UNAVAILABLE; | ||
| status.battery.level_percent = -1; | ||
| } else { | ||
| // Find battery level in response | ||
| // Find battery level in response (level is first byte after d6 0c 00 00 in packet) | ||
| 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) { | ||
| status.battery.level_percent = buff[i + 4]; | ||
| status.battery.status = BATTERY_AVAILABLE; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| // Check microphone status (note: BatteryResult has mic_status field) | ||
| if (status_buffs[1][12] == 0xFF) { | ||
| status.battery.mic_status = MICROPHONE_UP; | ||
| } | ||
| } | ||
|
|
||
| // Parse microphone mute status (FF = unmuted) | ||
| status.mic_muted = status_buffs[1][12] != 0xFF; | ||
|
|
||
| // Parse current eq setting | ||
| // 1 = Audeze, 2 = Treble Boost, 3 = Bass Boost, 4 = Immersive, 5 = Competition, | ||
| // 6 = Footsteps, 7 = EQ1, 8 = EQ2, 9 = EQ3, 10 = EQ4 | ||
| status.eq_setting = status_buffs[2][12]; | ||
|
|
||
| // Parse chatmix (0-20 range, center at 10) | ||
| status.chatmix_level = map(status_buffs[3][12], 0, 20, 0, 128); | ||
|
|
||
| // Parse sidetone (0-31 range) | ||
| status.sidetone_level = map(status_buffs[4][12], 0, 31, 0, 128); | ||
| status.sidetone_enabled = status_buffs[0][12] != 0; | ||
| // Parse current mic noise filter level | ||
| // 0 = Off, 1 = Low, 2 = High | ||
| status.ainf_level = status_buffs[4][12]; | ||
|
|
||
| // If battery unavailable, run full init sequence | ||
| if (status.battery.status != BATTERY_AVAILABLE) { | ||
| for (int i = 0; i < 13; ++i) { | ||
| (void)sendGetInputReport(device_handle, UNIQUE_REQUESTS[i]); | ||
| } | ||
| } | ||
| // Parse sidetone (0-31 range) | ||
| status.sidetone_level = map(status_buffs[5][12], 0, 31, 0, 128); | ||
| status.sidetone_enabled = status_buffs[5][12] != 0; | ||
|
|
||
| return status; | ||
| } | ||
|
|
@@ -208,7 +233,7 @@ class AudezeMaxwell : public HIDDevice { | |
| // Maxwell range: 0 to 31 | ||
| uint8_t mapped = map<uint8_t>(level, 0, 128, 0, 31); | ||
|
|
||
| std::array<uint8_t, MSG_SIZE> cmd { 0x6, 0x9, 0x80, 0x5, 0x5a, 0x5, 0x0, 0x0, 0x9, 0x2c, 0x0, mapped }; | ||
| std::array<uint8_t, MSG_SIZE> cmd { 0x06, 0x09, 0x80, 0x05, 0x5A, 0x05, 0x00, 0x00, 0x09, 0x2C, 0x00, mapped }; | ||
|
|
||
| // Sidetone is enabled whenever level changes | ||
| auto result = sendGetInputReport(device_handle, cmd); | ||
|
|
@@ -218,7 +243,7 @@ class AudezeMaxwell : public HIDDevice { | |
|
|
||
| if (mapped == 0) { | ||
| // Toggle sidetone off | ||
| std::array<uint8_t, MSG_SIZE> toggle_cmd { 0x6, 0x9, 0x80, 0x5, 0x5a, 0x5, 0x0, 0x82, 0x2c, 0x7, 0x0, 0x1 }; | ||
| std::array<uint8_t, MSG_SIZE> toggle_cmd { 0x06, 0x09, 0x80, 0x05, 0x5A, 0x05, 0x00, 0x82, 0x2C, 0x07, 0x00, 0x00 }; | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was it before the other way around? As its now 0x00 as last byte |
||
| result = sendGetInputReport(device_handle, toggle_cmd); | ||
| if (!result) { | ||
| return result.error(); | ||
|
|
@@ -236,29 +261,11 @@ class AudezeMaxwell : public HIDDevice { | |
|
|
||
| Result<InactiveTimeResult> setInactiveTime(hid_device* device_handle, uint8_t minutes) override | ||
| { | ||
| std::array<uint8_t, MSG_SIZE> cmd { 0x6, 0x10, 0x80, 0x5, 0x5a, 0xc, 0x0, 0x82, 0x2c, 0x1, 0x0 }; | ||
| std::array<uint8_t, MSG_SIZE> cmd { 0x06, 0x10, 0x80, 0x05, 0x5A, 0x0C, 0x00, 0x82, 0x2C, 0x01, 0x00 }; | ||
|
|
||
| if (minutes > 0) { | ||
| // Round to supported values | ||
| uint16_t final_minutes = 360; | ||
| if (minutes <= 5) | ||
| final_minutes = 5; | ||
| else if (minutes <= 10) | ||
| final_minutes = 10; | ||
| else if (minutes <= 15) | ||
| final_minutes = 15; | ||
| else if (minutes <= 30) | ||
| final_minutes = 30; | ||
| else if (minutes <= 45) | ||
| final_minutes = 45; | ||
| else if (minutes <= 60) | ||
| final_minutes = 60; | ||
| else if (minutes <= 90) | ||
| final_minutes = 90; | ||
| else if (minutes <= 120) | ||
| final_minutes = 120; | ||
| else if (minutes <= 240) | ||
| final_minutes = 240; | ||
| uint16_t final_minutes = map(minutes, 0, 90, 0, 360); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems to be semantically incorrect. Passing in 5 minutes, the user would get 20 |
||
|
|
||
| uint16_t seconds = final_minutes * 60; | ||
|
|
||
|
|
@@ -349,5 +356,22 @@ class AudezeMaxwell : public HIDDevice { | |
|
|
||
| return EqualizerPresetResult { .preset = preset, .total_presets = EQUALIZER_PRESETS_COUNT }; | ||
| } | ||
|
|
||
| Result<NoiseFilterResult> setNoiseFilter(hid_device* device_handle, uint8_t level) override | ||
| { | ||
| // Maxwell 2 has three levels for the noise filter: high, low, and | ||
| // off (2,1 and 0) | ||
| if (level > 2) { | ||
| return DeviceError::invalidParameter("Noise filter level must be 0, 1, or 2"); | ||
| } | ||
| std::array<uint8_t, MSG_SIZE> cmd { 0x06, 0x09, 0x80, 0x05, 0x5A, 0x05, 0x00, 0x00, 0x09, 0x24, 0x00, level }; | ||
|
|
||
| auto result = sendGetInputReport(device_handle, cmd); | ||
| if (!result) { | ||
| return result.error(); | ||
| } | ||
|
|
||
| return NoiseFilterResult { .level = level }; | ||
| } | ||
| }; | ||
| } // namespace headsetcontrol | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could in pass that information btw in BatteryResult back