diff --git a/README.md b/README.md index 8172951..8175b7c 100644 --- a/README.md +++ b/README.md @@ -9,33 +9,33 @@ talking. This differs from a simple loopback via PulseAudio as you won't have an ## Supported Headsets -| Device | sidetone | battery | notification sound | lights | inactive time | chatmix | voice prompts | rotate to mute | equalizer preset | equalizer | microphone mute led brightness | microphone volume | volume limiter | bluetooth when powered on | bluetooth call volume | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| Corsair Headset Device | x | x | x | x | | | | | | | | | | | | -| HyperX Cloud Alpha Wireless | x | x | | | x | | x | | | | | | | | | -| HyperX Cloud Flight Wireless | | x | | | | | | | | | | | | | | -| HyperX Cloud 3 | x | | | | | | | | | | | | | | | -| Logitech G430 | x | | | | | | | | | | | | | | | -| Logitech G432/G433 | x | | | | | | | | | | | | | | | -| Logitech G533 | x | x | | | x | | | | | | | | | | | -| Logitech G535 | x | x | | | x | | | | | | | | | | | -| Logitech G930 | x | x | | | | | | | | | | | | | | -| Logitech G633/G635/G733/G933/G935 | x | x | | x | | | | | | | | | | | | -| Logitech G PRO Series | x | x | | | x | | | | | | | | | | | -| Logitech G PRO X 2 | x | | | | x | | | | | | | | | | | -| Logitech Zone Wired/Zone 750 | x | | | | | | x | x | | | | | | | | -| SteelSeries Arctis (1/7X/7P) Wireless | x | x | | | x | | | | | | | | | | | -| SteelSeries Arctis (7/Pro) | x | x | | x | x | x | | | | | | | | | | -| SteelSeries Arctis 9 | x | x | | | x | x | | | | | | | | | | -| SteelSeries Arctis Pro Wireless | x | x | | | x | | | | | | | | | | | -| ROCCAT Elo 7.1 Air | | | | x | x | | | | | | | | | | | -| ROCCAT Elo 7.1 USB | | | | x | | | | | | | | | | | | -| SteelSeries Arctis Nova 3 | x | | | | | | | | x | x | x | x | | | | -| SteelSeries Arctis Nova (5/5X) | x | x | | | x | x | | | x | x | x | x | x | | | -| SteelSeries Arctis Nova 7 | x | x | | | x | x | | | x | x | x | x | x | x | x | -| SteelSeries Arctis 7+ | x | x | | | x | x | | | x | x | | | | | | -| SteelSeries Arctis Nova Pro Wireless | x | x | | x | x | | | | x | x | | | | | | -| HeadsetControl Test device | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | +| 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 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| Corsair Headset Device | x | x | x | x | | | | | | | | | | | | | +| HyperX Cloud Alpha Wireless | x | x | | | x | | x | | | | | | | | | | +| HyperX Cloud Flight Wireless | | x | | | | | | | | | | | | | | | +| HyperX Cloud 3 | x | | | | | | | | | | | | | | | | +| Logitech G430 | x | | | | | | | | | | | | | | | | +| Logitech G432/G433 | x | | | | | | | | | | | | | | | | +| Logitech G533 | x | x | | | x | | | | | | | | | | | | +| Logitech G535 | x | x | | | x | | | | | | | | | | | | +| Logitech G930 | x | x | | | | | | | | | | | | | | | +| Logitech G633/G635/G733/G933/G935 | x | x | | x | | | | | | | | | | | | | +| Logitech G PRO Series | x | x | | | x | | | | | | | | | | | | +| Logitech G PRO X 2 | x | | | | x | | | | | | | | | | | | +| Logitech Zone Wired/Zone 750 | x | | | | | | x | x | | | | | | | | | +| SteelSeries Arctis (1/7X/7P) Wireless | x | x | | | x | | | | | | | | | | | | +| SteelSeries Arctis (7/Pro) | x | x | | x | x | x | | | | | | | | | | | +| SteelSeries Arctis 9 | x | x | | | x | x | | | | | | | | | | | +| SteelSeries Arctis Pro Wireless | x | x | | | x | | | | | | | | | | | | +| ROCCAT Elo 7.1 Air | | | | x | x | | | | | | | | | | | | +| ROCCAT Elo 7.1 USB | | | | x | | | | | | | | | | | | | +| SteelSeries Arctis Nova 3 | x | | | | | | | | x | x | | x | x | | | | +| SteelSeries Arctis Nova (5/5X) | x | x | | | x | x | | | x | x | x | x | x | x | | | +| SteelSeries Arctis Nova 7 | x | x | | | x | x | | | x | x | | x | x | x | x | x | +| SteelSeries Arctis 7+ | x | x | | | x | x | | | x | x | | | | | | | +| SteelSeries Arctis Nova Pro Wireless | x | x | | x | x | | | | x | x | | | | | | | +| HeadsetControl Test device | x | x | x | x | x | x | x | x | x | x | | x | x | x | x | x | For non-supported headsets on Linux: There is a chance that you can set the sidetone via AlsaMixer diff --git a/src/arrays.xml b/src/arrays.xml new file mode 100644 index 0000000..8c64960 --- /dev/null +++ b/src/arrays.xml @@ -0,0 +1,73 @@ + + + + @array/com_google_android_gms_fonts_certs_dev + @array/com_google_android_gms_fonts_certs_prod + + + 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 + + + Select + 0x09 Save Profile + 0x10 Get FW Version + 0x11 Get Headset Status + 0x12 Get Serial Number + 0x18 Get EQ Preset Name (2.4) + 0x18 Get EQ Preset Name (BT) + 0x19 Set EQ Preset Name (2.4) + 0x19 Set EQ Preset Name (BT) + 0x20 Get Audio Settings + 0x21 Set Audio Settings + 0x26 Get Volume Limiter + 0x27 Set Volume Limiter (on) + 0x27 Set Volume Limiter (off) + 0x28 Set Custom Bluetooth EQ Band + 0x31 Set Custom EQ Band + 0x33 Set Custom EQ + 0x36 Set Custom Bluetooth EQ + 0x37 Set Mic Volume (max) + 0x37 Set Mic Volume (min) + 0x39 Set Sidetone (max) + 0x39 Set Sidetone (off) + 0x3B Set Onboard Effects Status (on) + 0x3B Set Onboard Effects Status (off) + 0xA7 Set EQ Sync Status (on) + 0xA7 Set EQ Sync Status (off) + 0xA8 Get EQ Sync Status + 0xAA Set Audio Mode (2.4) + 0xAA Set Audio Mode (BT) + 0xAB Get Audio Mode + 0xB0 Get Headset/Wireless Settings + 0xB1 Set Headset/Wireless Settings + 0xB8 Set ANC Level (3/High) + 0xB8 Set ANC Level (1/Low) + 0xB9 Set Transparent Level (10) + 0xB9 Set Transparent Level (1) + 0xBB Set Mic Muted (on) + 0xBB Set Mic Muted (off) + 0xBD Enable Transparent / ANC (ANC) + 0xBD Enable Transparent / ANC (T) + 0xBD Enable Transparent / ANC (off) + 0xC0 Get Auto-Off Timer Delay + 0xC1 Set Auto-Off Timer Delay (1min) + 0xC1 Set Auto-Off Timer Delay (off) + 0xC4 Get Wear Sense Config + 0xC5 Set Wear Sense Config (both) + 0xC5 Set Wear Sense Config (left) + 0xC5 Set Wear Sense Config (right) + 0xC5 Set Wear Sense Config (off) + 0xC8 Get Button Mappings + 0xC9 Set Button Mappings + 0xFD Factory Reset + + + @font/inter + @font/inter_bold + @font/inter_medium + @font/inter_semibold + + diff --git a/src/device.c b/src/device.c index 6c22f40..46bc605 100644 --- a/src/device.c +++ b/src/device.c @@ -12,6 +12,7 @@ const char* const capabilities_str[NUM_CAPABILITIES] [CAP_ROTATE_TO_MUTE] = "rotate to mute", [CAP_EQUALIZER_PRESET] = "equalizer preset", [CAP_EQUALIZER] = "equalizer", + [CAP_PARAMETRIC_EQUALIZER] = "parametric equalizer", [CAP_MICROPHONE_MUTE_LED_BRIGHTNESS] = "microphone mute led brightness", [CAP_MICROPHONE_VOLUME] = "microphone volume", [CAP_VOLUME_LIMITER] = "volume limiter", @@ -31,6 +32,7 @@ const char* const capabilities_str_enum[NUM_CAPABILITIES] [CAP_ROTATE_TO_MUTE] = "CAP_ROTATE_TO_MUTE", [CAP_EQUALIZER_PRESET] = "CAP_EQUALIZER_PRESET", [CAP_EQUALIZER] = "CAP_EQUALIZER", + [CAP_PARAMETRIC_EQUALIZER] = "CAP_PARAMETRIC_EQUALIZER", [CAP_MICROPHONE_MUTE_LED_BRIGHTNESS] = "CAP_MICROPHONE_MUTE_LED_BRIGHTNESS", [CAP_MICROPHONE_VOLUME] = "CAP_MICROPHONE_VOLUME", [CAP_VOLUME_LIMITER] = "CAP_VOLUME_LIMITER", @@ -50,6 +52,7 @@ const char capabilities_str_short[NUM_CAPABILITIES] [CAP_ROTATE_TO_MUTE] = 'r', [CAP_EQUALIZER_PRESET] = 'p', [CAP_EQUALIZER] = 'e', + [CAP_PARAMETRIC_EQUALIZER] = 'q', [CAP_MICROPHONE_MUTE_LED_BRIGHTNESS] = 't', [CAP_MICROPHONE_VOLUME] = 'o', // new capabilities since short output was deprecated @@ -57,3 +60,13 @@ const char capabilities_str_short[NUM_CAPABILITIES] [CAP_BT_WHEN_POWERED_ON] = '\0', [CAP_BT_CALL_VOLUME] = '\0' }; + +const char* const equalizer_filter_type_str[NUM_EQ_FILTER_TYPES] + = { + [EQ_FILTER_LOWSHELF] = "lowshelf", + [EQ_FILTER_LOWPASS] = "lowpass", + [EQ_FILTER_PEAKING] = "peaking", + [EQ_FILTER_HIGHPASS] = "highpass", + [EQ_FILTER_HIGHSHELF] = "highshelf" + + }; diff --git a/src/device.h b/src/device.h index 71f6f60..c73885e 100644 --- a/src/device.h +++ b/src/device.h @@ -34,6 +34,7 @@ enum capabilities { CAP_ROTATE_TO_MUTE, CAP_EQUALIZER_PRESET, CAP_EQUALIZER, + CAP_PARAMETRIC_EQUALIZER, CAP_MICROPHONE_MUTE_LED_BRIGHTNESS, CAP_MICROPHONE_VOLUME, CAP_VOLUME_LIMITER, @@ -103,6 +104,19 @@ typedef struct { float* values; } EqualizerPreset; +typedef struct { + int bands_count; + float gain_base; // default/base gain + float gain_step; + float gain_min; + float gain_max; + float q_factor_min; // q factor + float q_factor_max; + int freq_min; // frequency + int freq_max; + int filter_types; // bitmap containing available filter types +} ParametricEqualizerInfo; + typedef struct { int count; EqualizerPreset presets[]; @@ -112,6 +126,7 @@ enum headsetcontrol_errors { HSC_ERROR = -100, HSC_READ_TIMEOUT = -101, HSC_OUT_OF_BOUNDS = -102, + HSC_INVALID_ARG = -103, }; typedef enum { @@ -140,7 +155,7 @@ typedef struct { FeatureResult result; } FeatureRequest; -/** @brief Defines equalizer custom setings +/** @brief Defines equalizer custom settings */ struct equalizer_settings { /// The size of the bands array @@ -149,6 +164,36 @@ struct equalizer_settings { float* bands_values; }; +typedef enum { + EQ_FILTER_LOWSHELF, + EQ_FILTER_LOWPASS, + EQ_FILTER_PEAKING, + EQ_FILTER_HIGHPASS, + EQ_FILTER_HIGHSHELF, + NUM_EQ_FILTER_TYPES +} EqualizerFilterType; + +/// Enum name of every parametric equalizer filter type +extern const char* const equalizer_filter_type_str[NUM_EQ_FILTER_TYPES]; + +/** @brief Defines parametric equalizer custom settings + */ +struct parametric_equalizer_settings { + /// The size of the bands array + int size; + /// The equalizer bands + struct parametric_equalizer_band* bands; +}; + +/** @brief Defines parameteric equalizer band + */ +struct parametric_equalizer_band { + float frequency; + float gain; + float q_factor; + EqualizerFilterType type; +}; + /** @brief Defines the basic data of a device * * Also defines function pointers for using supported features @@ -168,7 +213,8 @@ struct device { // Equalizer Infos EqualizerInfo* equalizer; - EqualizerPresets* eqaulizer_presets; + EqualizerPresets* equalizer_presets; + ParametricEqualizerInfo* parametric_equalizer; wchar_t device_hid_vendorname[64]; wchar_t device_hid_productname[64]; @@ -318,6 +364,24 @@ struct device { */ int (*send_equalizer)(hid_device* hid_device, struct equalizer_settings* settings); + /** @brief Function pointer for setting headset parametric equalizer + * + * Forwards the request to the device specific implementation + * + * @param device_handle The hidapi handle. Must be the same + * device as defined here (same ids) + * @param settings The parametric equalizer settings containing + * the filter values + * + * @returns > 0 on success + * HSC_OUT_OF_BOUNDS on equalizer settings size out of range + * specific to this hardware + * HSC_INVALID_ARG on equalizer filter type invalid/unsupported + * specific to this hardware + * -1 HIDAPI error + */ + int (*send_parametric_equalizer)(hid_device* hid_device, struct parametric_equalizer_settings* settings); + /** @brief Function pointer for setting headset microphone mute LED brightness * * Forwards the request to the device specific implementation diff --git a/src/devices/headsetcontrol_test.c b/src/devices/headsetcontrol_test.c index 6c83bfe..5694f1f 100644 --- a/src/devices/headsetcontrol_test.c +++ b/src/devices/headsetcontrol_test.c @@ -32,6 +32,7 @@ static int headsetcontrol_test_notification_sound(hid_device* device_handle, uin static int headsetcontrol_test_lights(hid_device* device_handle, uint8_t on); static int headsetcontrol_test_send_equalizer_preset(hid_device* device_handle, uint8_t num); static int headsetcontrol_test_send_equalizer(hid_device* device_handle, struct equalizer_settings* settings); +static int headsetcontrol_test_send_parametric_equalizer(hid_device* device_handle, struct parametric_equalizer_settings* settings); static int headsetcontrol_test_send_microphone_mute_led_brightness(hid_device* device_handle, uint8_t num); static int headsetcontrol_test_send_microphone_volume(hid_device* device_handle, uint8_t num); static int headsetcontrol_test_switch_voice_prompts(hid_device* device_handle, uint8_t on); @@ -55,7 +56,7 @@ void headsetcontrol_test_init(struct device** device) device_headsetcontrol_test.idProductsSupported = PRODUCT_IDS; device_headsetcontrol_test.numIdProducts = 1; device_headsetcontrol_test.equalizer = &EQUALIZER; - device_headsetcontrol_test.eqaulizer_presets = &EQUALIZER_PRESETS; + device_headsetcontrol_test.equalizer_presets = &EQUALIZER_PRESETS; strncpy(device_headsetcontrol_test.device_name, "HeadsetControl Test device", sizeof(device_headsetcontrol_test.device_name)); // normally filled by hid in main.c @@ -63,7 +64,7 @@ void headsetcontrol_test_init(struct device** device) wcsncpy(device_headsetcontrol_test.device_hid_productname, L"Test device", sizeof(device_headsetcontrol_test.device_hid_productname) / sizeof(device_headsetcontrol_test.device_hid_productname[0])); if (test_profile != 10) { - device_headsetcontrol_test.capabilities = B(CAP_SIDETONE) | B(CAP_BATTERY_STATUS) | B(CAP_NOTIFICATION_SOUND) | B(CAP_LIGHTS) | B(CAP_INACTIVE_TIME) | B(CAP_CHATMIX_STATUS) | B(CAP_VOICE_PROMPTS) | B(CAP_ROTATE_TO_MUTE) | B(CAP_EQUALIZER_PRESET) | B(CAP_EQUALIZER) | B(CAP_MICROPHONE_MUTE_LED_BRIGHTNESS) | B(CAP_MICROPHONE_VOLUME) | B(CAP_VOLUME_LIMITER) | B(CAP_BT_WHEN_POWERED_ON) | B(CAP_BT_CALL_VOLUME); + device_headsetcontrol_test.capabilities = B(CAP_SIDETONE) | B(CAP_BATTERY_STATUS) | B(CAP_NOTIFICATION_SOUND) | B(CAP_LIGHTS) | B(CAP_INACTIVE_TIME) | B(CAP_CHATMIX_STATUS) | B(CAP_VOICE_PROMPTS) | B(CAP_ROTATE_TO_MUTE) | B(CAP_EQUALIZER_PRESET) | B(CAP_EQUALIZER) | B(CAP_PARAMETRIC_EQUALIZER) | B(CAP_MICROPHONE_MUTE_LED_BRIGHTNESS) | B(CAP_MICROPHONE_VOLUME) | B(CAP_VOLUME_LIMITER) | B(CAP_BT_WHEN_POWERED_ON) | B(CAP_BT_CALL_VOLUME); } else { device_headsetcontrol_test.capabilities = B(CAP_SIDETONE) | B(CAP_LIGHTS) | B(CAP_BATTERY_STATUS); } @@ -78,6 +79,7 @@ void headsetcontrol_test_init(struct device** device) device_headsetcontrol_test.switch_rotate_to_mute = &headsetcontrol_test_switch_rotate_to_mute; device_headsetcontrol_test.send_equalizer_preset = &headsetcontrol_test_send_equalizer_preset; device_headsetcontrol_test.send_equalizer = &headsetcontrol_test_send_equalizer; + device_headsetcontrol_test.send_parametric_equalizer = &headsetcontrol_test_send_parametric_equalizer; device_headsetcontrol_test.send_microphone_mute_led_brightness = &headsetcontrol_test_send_microphone_mute_led_brightness; device_headsetcontrol_test.send_microphone_volume = &headsetcontrol_test_send_microphone_volume; device_headsetcontrol_test.send_volume_limiter = &headsetcontrol_test_volume_limiter; @@ -147,6 +149,11 @@ static int headsetcontrol_test_send_equalizer(hid_device* device_handle, struct return TESTBYTES_SEND; } +static int headsetcontrol_test_send_parametric_equalizer(hid_device* device_handle, struct parametric_equalizer_settings* settings) +{ + return TESTBYTES_SEND; +} + static int headsetcontrol_test_send_microphone_mute_led_brightness(hid_device* device_handle, uint8_t num) { return TESTBYTES_SEND; diff --git a/src/devices/steelseries_arctis_nova_5.c b/src/devices/steelseries_arctis_nova_5.c index 7c11b23..2cbb1f5 100644 --- a/src/devices/steelseries_arctis_nova_5.c +++ b/src/devices/steelseries_arctis_nova_5.c @@ -8,61 +8,106 @@ static struct device device_arctis; -enum { - ID_ARCTIS_NOVA_5_BASE_STATION = 0x2232, - ID_ARCTIS_NOVA_5X_BASE_STATION = 0x2253, -}; +#define ID_ARCTIS_NOVA_5_BASE_STATION 0x2232 +#define ID_ARCTIS_NOVA_5X_BASE_STATION 0x2253 + +#define MSG_SIZE 64 +#define STATUS_BUF_SIZE 128 + +#define CONNECTION_STATUS_BYTE 0x01 +#define BATTERY_LEVEL_BYTE 0x03 +#define BATTERY_STATUS_BYTE 0x04 + +#define HEADSET_CHARGING 0x01 +#define HEADSET_OFFLINE 0x02 + +// general equalizer +#define EQUALIZER_BANDS_COUNT 10 +#define EQUALIZER_GAIN_STEP 0.5 +#define EQUALIZER_GAIN_BASE 20 // default gain +#define EQUALIZER_GAIN_MIN -10 // gain +#define EQUALIZER_GAIN_MAX 10 +// equalizer presets +#define EQUALIZER_PRESETS_COUNT 4 + +// parametric equalizer only +#define EQUALIZER_Q_FACTOR_MIN 0.2 // q factor +#define EQUALIZER_Q_FACTOR_MAX 10.0 +#define EQUALIZER_Q_FACTOR_DEFAULT 1.414 +#define EQUALIZER_FREQ_MIN 20 // frequency +#define EQUALIZER_FREQ_MAX 20000 +#define EQUALIZER_FREQ_DISABLED 20001 +#define EQUALIZER_FILTERS (B(EQ_FILTER_LOWSHELF) | B(EQ_FILTER_LOWPASS) | B(EQ_FILTER_PEAKING) | B(EQ_FILTER_HIGHPASS) | B(EQ_FILTER_HIGHSHELF)) + +static const uint16_t PRODUCT_IDS[] = { ID_ARCTIS_NOVA_5_BASE_STATION, ID_ARCTIS_NOVA_5X_BASE_STATION }; +static const uint8_t SAVE_DATA1[MSG_SIZE] = { 0x0, 0x09 }; // Command1 to save settings to headset +static const uint8_t SAVE_DATA2[MSG_SIZE] = { 0x0, 0x35, 0x01 }; // Command2 to save settings to headset -enum { - MSG_SIZE = 64, - STATUS_BUF_SIZE = 128, -}; +static float flat[EQUALIZER_BANDS_COUNT] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +static float bass[EQUALIZER_BANDS_COUNT] = { 3.5f, 5.5f, 4, 1, -1.5f, -1.5f, -1, -1, -1, -1 }; +static float focus[EQUALIZER_BANDS_COUNT] = { -5, -3.5f, -1, -3.5f, -2.5f, 4, 6, -3.5f, 0 }; +static float smiley[EQUALIZER_BANDS_COUNT] = { 3, 3.5f, 1.5f, -1.5f, -4, -4, -2.5f, 1.5f, 3, 4 }; -enum { - CONNECTION_STATUS_BYTE = 0x01, - BATTERY_LEVEL_BYTE = 0x03, - BATTERY_STATUS_BYTE = 0x04, -}; +static EqualizerInfo EQUALIZER = { EQUALIZER_BANDS_COUNT, 0, 0.5, EQUALIZER_GAIN_MIN, EQUALIZER_GAIN_MAX }; -enum { - HEADSET_CHARGING = 0x01, - HEADSET_OFFLINE = 0x02, +static EqualizerPresets EQUALIZER_PRESETS = { + EQUALIZER_PRESETS_COUNT, + { { "flat", flat }, + { "bass", bass }, + { "focus", focus }, + { "smiley", smiley } } }; -enum { - EQUALIZER_BANDS_COUNT = 10, - EQUALIZER_BAND_MIN = -10, - EQUALIZER_BAND_MAX = 10, - EQUALIZER_BAND_BASE = 0x14, + +static ParametricEqualizerInfo PARAMETRIC_EQUALIZER = { + EQUALIZER_BANDS_COUNT, + EQUALIZER_GAIN_BASE, + EQUALIZER_GAIN_STEP, + EQUALIZER_GAIN_MIN, + EQUALIZER_GAIN_MAX, + EQUALIZER_Q_FACTOR_MIN, + EQUALIZER_Q_FACTOR_MAX, + EQUALIZER_FREQ_MIN, + EQUALIZER_FREQ_MAX, + EQUALIZER_FILTERS }; -static const uint16_t PRODUCT_IDS[] - = { ID_ARCTIS_NOVA_5_BASE_STATION, ID_ARCTIS_NOVA_5X_BASE_STATION }; +// map filter types to device band filter byte +static uint8_t EQUALIZER_FILTER_MAP[NUM_EQ_FILTER_TYPES] = { + [EQ_FILTER_PEAKING] = 1, + [EQ_FILTER_LOWPASS] = 2, + [EQ_FILTER_HIGHPASS] = 3, + [EQ_FILTER_LOWSHELF] = 4, + [EQ_FILTER_HIGHSHELF] = 5, +}; -static const uint8_t SAVE_DATA1[MSG_SIZE] = { 0x0, 0x09 }; // Command1 to save settings to headset -static const uint8_t SAVE_DATA2[MSG_SIZE] = { 0x0, 0x35, 0x01 }; // Command2 to save settings to headset +static int nova_5_send_sidetone(hid_device* device_handle, uint8_t num); +static int nova_5_send_microhpone_mute_led_brightness(hid_device* device_handle, uint8_t num); +static int nova_5_send_microphone_volume(hid_device* device_handle, uint8_t num); +static int nova_5_send_inactive_time(hid_device* device_handle, uint8_t num); +static int nova_5_send_volume_limiter(hid_device* device_handle, uint8_t num); +static int nova_5_send_equalizer_preset(hid_device* device_handle, uint8_t num); +static int nova_5_send_equalizer(hid_device* device_handle, struct equalizer_settings* settings); +static int nova_5_send_parametric_equalizer(hid_device* device_handle, struct parametric_equalizer_settings* settings); +static int nova_5_write_device_band(struct parametric_equalizer_band* filter, uint8_t* data); -static int set_sidetone(hid_device* device_handle, uint8_t num); -static int set_mic_mute_led_brightness(hid_device* device_handle, uint8_t num); -static int set_mic_volume(hid_device* device_handle, uint8_t num); -static int set_inactive_time(hid_device* device_handle, uint8_t num); -static int set_volume_limiter(hid_device* device_handle, uint8_t num); -static int set_eq_preset(hid_device* device_handle, uint8_t num); -static int set_eq(hid_device* device_handle, struct equalizer_settings* settings); -static BatteryInfo get_battery(hid_device* device_handle); -static int get_chatmix(hid_device* device_handle); +static BatteryInfo nova_5_get_battery(hid_device* device_handle); +static int nova_5_get_chatmix(hid_device* device_handle); -static int read_device_status(hid_device* device_handle, unsigned char* data_read); -static int save_state(hid_device* device_handle); +static int nova_5_read_device_status(hid_device* device_handle, unsigned char* data_read); +static int nova_5_save_state(hid_device* device_handle); void arctis_nova_5_init(struct device** device) { - device_arctis.idVendor = VENDOR_STEELSERIES; - device_arctis.idProductsSupported = PRODUCT_IDS; - device_arctis.numIdProducts = sizeof(PRODUCT_IDS) / sizeof(PRODUCT_IDS[0]); + device_arctis.idVendor = VENDOR_STEELSERIES; + device_arctis.idProductsSupported = PRODUCT_IDS; + device_arctis.numIdProducts = sizeof(PRODUCT_IDS) / sizeof(PRODUCT_IDS[0]); + device_arctis.equalizer = &EQUALIZER; + device_arctis.equalizer_presets = &EQUALIZER_PRESETS; + device_arctis.parametric_equalizer = &PARAMETRIC_EQUALIZER; strncpy(device_arctis.device_name, "SteelSeries Arctis Nova (5/5X)", sizeof(device_arctis.device_name)); - device_arctis.capabilities = B(CAP_SIDETONE) | B(CAP_BATTERY_STATUS) | B(CAP_CHATMIX_STATUS) | B(CAP_MICROPHONE_MUTE_LED_BRIGHTNESS) | B(CAP_MICROPHONE_VOLUME) | B(CAP_INACTIVE_TIME) | B(CAP_VOLUME_LIMITER) | B(CAP_EQUALIZER_PRESET) | B(CAP_EQUALIZER); + device_arctis.capabilities = B(CAP_SIDETONE) | B(CAP_BATTERY_STATUS) | B(CAP_CHATMIX_STATUS) | B(CAP_MICROPHONE_MUTE_LED_BRIGHTNESS) | B(CAP_MICROPHONE_VOLUME) | B(CAP_INACTIVE_TIME) | B(CAP_VOLUME_LIMITER) | B(CAP_EQUALIZER_PRESET) | B(CAP_EQUALIZER) | B(CAP_PARAMETRIC_EQUALIZER); device_arctis.capability_details[CAP_SIDETONE] = (struct capability_detail) { .usagepage = 0xffc0, .usageid = 0x1, .interface = 3 }; device_arctis.capability_details[CAP_BATTERY_STATUS] = (struct capability_detail) { .usagepage = 0xffc0, .usageid = 0x1, .interface = 3 }; device_arctis.capability_details[CAP_CHATMIX_STATUS] = (struct capability_detail) { .usagepage = 0xffc0, .usageid = 0x1, .interface = 3 }; @@ -73,19 +118,20 @@ void arctis_nova_5_init(struct device** device) device_arctis.capability_details[CAP_EQUALIZER_PRESET] = (struct capability_detail) { .usagepage = 0xffc0, .usageid = 0x1, .interface = 3 }; device_arctis.capability_details[CAP_EQUALIZER] = (struct capability_detail) { .usagepage = 0xffc0, .usageid = 0x1, .interface = 3 }; - device_arctis.send_sidetone = &set_sidetone; - device_arctis.send_microphone_mute_led_brightness = &set_mic_mute_led_brightness; - device_arctis.send_microphone_volume = &set_mic_volume; - device_arctis.request_battery = &get_battery; - device_arctis.request_chatmix = &get_chatmix; - device_arctis.send_inactive_time = &set_inactive_time; - device_arctis.send_volume_limiter = &set_volume_limiter; - device_arctis.send_equalizer_preset = &set_eq_preset; - device_arctis.send_equalizer = &set_eq; + device_arctis.send_sidetone = &nova_5_send_sidetone; + device_arctis.send_microphone_mute_led_brightness = &nova_5_send_microhpone_mute_led_brightness; + device_arctis.send_microphone_volume = &nova_5_send_microphone_volume; + device_arctis.request_battery = &nova_5_get_battery; + device_arctis.request_chatmix = &nova_5_get_chatmix; + device_arctis.send_inactive_time = &nova_5_send_inactive_time; + device_arctis.send_volume_limiter = &nova_5_send_volume_limiter; + device_arctis.send_equalizer_preset = &nova_5_send_equalizer_preset; + device_arctis.send_equalizer = &nova_5_send_equalizer; + device_arctis.send_parametric_equalizer = &nova_5_send_parametric_equalizer; *device = &device_arctis; } -static int read_device_status(hid_device* device_handle, unsigned char* data_read) +static int nova_5_read_device_status(hid_device* device_handle, unsigned char* data_read) { unsigned char data_request[2] = { 0x0, 0xB0 }; int r = hid_write(device_handle, data_request, sizeof(data_request)); @@ -96,7 +142,7 @@ static int read_device_status(hid_device* device_handle, unsigned char* data_rea return hid_read_timeout(device_handle, data_read, STATUS_BUF_SIZE, hsc_device_timeout); } -static int save_state(hid_device* device_handle) +static int nova_5_save_state(hid_device* device_handle) { int r = hid_write(device_handle, SAVE_DATA1, MSG_SIZE); if (r < 0) @@ -105,7 +151,7 @@ static int save_state(hid_device* device_handle) return hid_write(device_handle, SAVE_DATA2, MSG_SIZE); } -static int set_sidetone(hid_device* device_handle, uint8_t num) +static int nova_5_send_sidetone(hid_device* device_handle, uint8_t num) { // This headset only supports 10 levels of sidetone volume, // but we allow a full range of 0-128 for it. Map the volume to the correct numbers. @@ -137,10 +183,10 @@ static int set_sidetone(hid_device* device_handle, uint8_t num) if (r < 0) return r; - return save_state(device_handle); + return nova_5_save_state(device_handle); } -static int set_mic_volume(hid_device* device_handle, uint8_t num) +static int nova_5_send_microphone_volume(hid_device* device_handle, uint8_t num) { // 0x00 off // step + 0x01 @@ -154,10 +200,10 @@ static int set_mic_volume(hid_device* device_handle, uint8_t num) if (r < 0) return r; - return save_state(device_handle); + return nova_5_save_state(device_handle); } -static int set_mic_mute_led_brightness(hid_device* device_handle, uint8_t num) +static int nova_5_send_microhpone_mute_led_brightness(hid_device* device_handle, uint8_t num) { // Off = 0x00 // low = 0x01 @@ -175,13 +221,13 @@ static int set_mic_mute_led_brightness(hid_device* device_handle, uint8_t num) if (r < 0) return r; - return save_state(device_handle); + return nova_5_save_state(device_handle); } -static BatteryInfo get_battery(hid_device* device_handle) +static BatteryInfo nova_5_get_battery(hid_device* device_handle) { unsigned char data_read[STATUS_BUF_SIZE]; - int r = read_device_status(device_handle, data_read); + int r = nova_5_read_device_status(device_handle, data_read); BatteryInfo info = { .status = BATTERY_UNAVAILABLE, .level = -1 }; @@ -217,7 +263,7 @@ static BatteryInfo get_battery(hid_device* device_handle) return info; } -static int get_chatmix(hid_device* device_handle) +static int nova_5_get_chatmix(hid_device* device_handle) { // modified from SteelSeries Arctis Nova 7 // max chat 0x00, 0x64 @@ -225,7 +271,7 @@ static int get_chatmix(hid_device* device_handle) // neutral 0x64, 0x64 // read device info unsigned char data_read[STATUS_BUF_SIZE]; - int r = read_device_status(device_handle, data_read); + int r = nova_5_read_device_status(device_handle, data_read); if (r < 0) return r; @@ -247,14 +293,14 @@ static int get_chatmix(hid_device* device_handle) return 64 - (chat + game); } -static int set_inactive_time(hid_device* device_handle, uint8_t num) +static int nova_5_send_inactive_time(hid_device* device_handle, uint8_t num) { uint8_t data[MSG_SIZE] = { 0x0, 0xA3, num }; return hid_write(device_handle, data, MSG_SIZE); } -static int set_volume_limiter(hid_device* device_handle, uint8_t num) +static int nova_5_send_volume_limiter(hid_device* device_handle, uint8_t num) { unsigned char data[MSG_SIZE] = { 0x0, 0x27, num }; int r = hid_write(device_handle, data, MSG_SIZE); @@ -262,17 +308,13 @@ static int set_volume_limiter(hid_device* device_handle, uint8_t num) if (r < 0) return r; - return save_state(device_handle); + return nova_5_save_state(device_handle); } -static int set_eq_preset(hid_device* device_handle, uint8_t num) +static int nova_5_send_equalizer_preset(hid_device* device_handle, uint8_t num) { struct equalizer_settings preset; - preset.size = EQUALIZER_BANDS_COUNT; - float flat[EQUALIZER_BANDS_COUNT] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - float bass[EQUALIZER_BANDS_COUNT] = { 3.5f, 5.5f, 4, 1, -1.5f, -1.5f, -1, -1, -1, -1 }; - float focus[EQUALIZER_BANDS_COUNT] = { -5, -3.5f, -1, -3.5f, -2.5f, 4, 6, -3.5f, 0 }; - float smiley[EQUALIZER_BANDS_COUNT] = { 3, 3.5f, 1.5f, -1.5f, -4, -4, -2.5f, 1.5f, 3, 4 }; + preset.size = EQUALIZER_BANDS_COUNT; switch (num) { case 0: { preset.bands_values = &flat[0]; @@ -294,13 +336,13 @@ static int set_eq_preset(hid_device* device_handle, uint8_t num) printf("Preset %d out of bounds\n", num); return HSC_OUT_OF_BOUNDS; } - return set_eq(device_handle, &preset); + return nova_5_send_equalizer(device_handle, &preset); } /** * This headset is using a Peaking EQ with configurable frequency center of a EQ band, gain and a quality factor */ -static int set_eq(hid_device* device_handle, struct equalizer_settings* settings) +static int nova_5_send_equalizer(hid_device* device_handle, struct equalizer_settings* settings) { if (settings->size != EQUALIZER_BANDS_COUNT) { printf("Device only supports %d bands.\n", EQUALIZER_BANDS_COUNT); @@ -335,14 +377,14 @@ static int set_eq(hid_device* device_handle, struct equalizer_settings* settings }; for (size_t i = 0; i < settings->size; i++) { - float band_value = settings->bands_values[i]; - if (band_value < EQUALIZER_BAND_MIN || band_value > EQUALIZER_BAND_MAX) { - printf("Device only supports bands ranging from %d to %d.\n", EQUALIZER_BAND_MIN, EQUALIZER_BAND_MAX); + float gain_value = settings->bands_values[i]; + if (gain_value < EQUALIZER_GAIN_MIN || gain_value > EQUALIZER_GAIN_MAX) { + printf("Device only supports gains ranging from %d to %d.\n", EQUALIZER_GAIN_MIN, EQUALIZER_GAIN_MAX); return HSC_OUT_OF_BOUNDS; } - uint8_t raw_gain_value = (uint8_t)(EQUALIZER_BAND_BASE + band_value * 2); + uint8_t raw_gain_value = (uint8_t)(EQUALIZER_GAIN_BASE + gain_value * 2); uint8_t gain_flag = 0x01; - if (raw_gain_value != EQUALIZER_BAND_BASE) { + if (raw_gain_value != EQUALIZER_GAIN_BASE) { if (i == 0) { gain_flag = 0x04; } else if (i == (settings->size - 1)) { @@ -362,5 +404,81 @@ static int set_eq(hid_device* device_handle, struct equalizer_settings* settings if (r < 0) return r; - return save_state(device_handle); + return nova_5_save_state(device_handle); +} + +static int nova_5_send_parametric_equalizer(hid_device* device_handle, struct parametric_equalizer_settings* settings) +{ + struct parametric_equalizer_band disabled_band = { + .frequency = EQUALIZER_FREQ_DISABLED, + .gain = 0, + .q_factor = EQUALIZER_Q_FACTOR_DEFAULT, + .type = EQ_FILTER_PEAKING, + }; + + if (settings->size > EQUALIZER_BANDS_COUNT) { + printf("Device only supports up to %d equalizer bands.\n", EQUALIZER_BANDS_COUNT); + return HSC_OUT_OF_BOUNDS; + } + + uint8_t data[MSG_SIZE] = { 0x0, 0x33 }; + int error = 0; + + for (size_t i = 0; i < EQUALIZER_BANDS_COUNT; i++) { + if (i < settings->size) { + error = nova_5_write_device_band(&settings->bands[i], &data[2 + i * 6]); + } else { + error = nova_5_write_device_band(&disabled_band, &data[2 + i * 6]); + } + if (error != 0) { + return error; + } + } + + int r = hid_write(device_handle, data, MSG_SIZE); + if (r < 0) + return r; + + return nova_5_save_state(device_handle); + return 0; +} + +static int nova_5_write_device_band(struct parametric_equalizer_band* filter, uint8_t* data) +{ + // fprintf(stderr, "freq: %g; gain: %g; q: %g; type: %d\n", filter->frequency, filter->gain, filter->q_factor, filter->type); + if (filter->frequency != EQUALIZER_FREQ_DISABLED) { + if (filter->frequency < EQUALIZER_FREQ_MIN || filter->frequency > EQUALIZER_FREQ_MAX) { + printf("Device only supports filter frequencies ranging from %d to %d.\n", EQUALIZER_FREQ_MIN, EQUALIZER_FREQ_MAX); + return HSC_OUT_OF_BOUNDS; + } + } + if (!has_capability(EQUALIZER_FILTERS, filter->type)) { + printf("Unsupported filter type.\n"); + return HSC_ERROR; + } + if (filter->q_factor < EQUALIZER_Q_FACTOR_MIN || filter->q_factor > EQUALIZER_Q_FACTOR_MAX) { + printf("Device only supports filter q-factor ranging from %g to %g.\n", EQUALIZER_Q_FACTOR_MIN, EQUALIZER_Q_FACTOR_MAX); + return HSC_OUT_OF_BOUNDS; + } + if (filter->gain < EQUALIZER_GAIN_MIN || filter->gain > EQUALIZER_GAIN_MAX) { + printf("Device only supports filter gains ranging from %d to %d.\n", EQUALIZER_GAIN_MIN, EQUALIZER_GAIN_MAX); + return HSC_OUT_OF_BOUNDS; + } + + // write filter frequency + data[0] = (uint16_t)filter->frequency & 0xFF; // low byte + data[1] = ((uint16_t)filter->frequency >> 8) & 0xFF; // high byte + + // write filter type + data[2] = EQUALIZER_FILTER_MAP[filter->type]; + + // write filter gain + data[3] = (filter->gain + 10.0) * 2.0; + + // write filter q-factor + uint16_t q = filter->q_factor * 1000; + data[4] = q & 0xFF; // low byte + data[5] = (q >> 8) & 0xFF; // high byte + + return 0; } diff --git a/src/devices/steelseries_arctis_nova_7.c b/src/devices/steelseries_arctis_nova_7.c index 91a0ca3..80e0e02 100644 --- a/src/devices/steelseries_arctis_nova_7.c +++ b/src/devices/steelseries_arctis_nova_7.c @@ -66,7 +66,7 @@ void arctis_nova_7_init(struct device** device) device_arctis.idProductsSupported = PRODUCT_IDS; device_arctis.numIdProducts = sizeof(PRODUCT_IDS) / sizeof(PRODUCT_IDS[0]); device_arctis.equalizer = &EQUALIZER; - device_arctis.eqaulizer_presets = &EQUALIZER_PRESETS; + device_arctis.equalizer_presets = &EQUALIZER_PRESETS; strncpy(device_arctis.device_name, "SteelSeries Arctis Nova 7", sizeof(device_arctis.device_name)); diff --git a/src/main.c b/src/main.c index 338521d..96791fe 100644 --- a/src/main.c +++ b/src/main.c @@ -216,7 +216,7 @@ static FeatureResult handle_feature(struct device* device_found, hid_device** de switch (cap) { case CAP_SIDETONE: - ret = device_found->send_sidetone(*device_handle, (uint8_t) * (int*)param); + ret = device_found->send_sidetone(*device_handle, (uint8_t)*(int*)param); break; case CAP_BATTERY_STATUS: { @@ -294,6 +294,10 @@ static FeatureResult handle_feature(struct device* device_found, hid_device** de ret = device_found->send_equalizer(*device_handle, (struct equalizer_settings*)param); break; + case CAP_PARAMETRIC_EQUALIZER: + ret = device_found->send_parametric_equalizer(*device_handle, (struct parametric_equalizer_settings*)param); + break; + case CAP_MICROPHONE_MUTE_LED_BRIGHTNESS: ret = device_found->send_microphone_mute_led_brightness(*device_handle, (uint8_t) * (int*)param); break; @@ -344,6 +348,9 @@ static FeatureResult handle_feature(struct device* device_found, hid_device** de case HSC_OUT_OF_BOUNDS: _asprintf(&result.message, "Failed to set/request %s. Provided parameter out of boundaries", capabilities_str[cap]); break; + case HSC_INVALID_ARG: + _asprintf(&result.message, "Failed to set/request %s. Provided parameter invalid", capabilities_str[cap]); + break; default: // Must be a HID error if (device_found->idProduct != PRODUCT_TESTDEVICE) _asprintf(&result.message, "Failed to set/request %s. Error: %d: %ls", capabilities_str[cap], ret, hid_error(*device_handle)); @@ -418,8 +425,9 @@ void print_help(char* programname, struct device* device_found, bool _show_all) // ------ // ------ Category: Equalizer - bool show_equalizer = show_all || has_capability(device_found->capabilities, CAP_EQUALIZER); - bool show_equalizer_preset = show_all || has_capability(device_found->capabilities, CAP_EQUALIZER_PRESET); + bool show_equalizer = show_all || has_capability(device_found->capabilities, CAP_EQUALIZER); + bool show_equalizer_preset = show_all || has_capability(device_found->capabilities, CAP_EQUALIZER_PRESET); + bool show_parametric_equalizer = show_all || has_capability(device_found->capabilities, CAP_PARAMETRIC_EQUALIZER); if (show_equalizer || show_equalizer_preset) { printf("Equalizer:\n"); @@ -431,6 +439,26 @@ void print_help(char* programname, struct device* device_found, bool _show_all) } printf("\n"); } + + if (show_parametric_equalizer) { + // TODO parametric equalizer + printf("Parametric Equalizer:\n"); + printf(" --parametric-equalizer STRING\t\tSet equalizer bands (bands separated by semicolon)\n"); + printf(" Band format:\t\t\tFREQUENCY,GAIN,Q_FACTOR,FILTER_TYPE\n"); + printf(" Availabe filter types:\t\t"); + for (int i = 0; i < NUM_EQ_FILTER_TYPES; i++) { + if (show_all || has_capability(device_found->parametric_equalizer->filter_types, i)) { + printf("%s, ", equalizer_filter_type_str[i]); + } + } + printf("\n\n"); + printf(" Examples:\t--parametric-equalizer\t'300,3.5,1.5,peaking;14000,-2,1.414,highshelf'\n"); + printf(" \t\t\t\t\tSets a 300Hz +3.5dB Q1.5 peaking filter\n\t\t\t\t\tand a 14kHz -2dB Q1.414 highshelf filter\n"); + printf("\n"); + printf(" \t\t--parametric-equalizer\treset\n"); + printf(" \t\t\t\t\tResets/disables all bands"); + printf("\n"); + } // ------ // ------ Category: Microphone @@ -521,28 +549,29 @@ int main(int argc, char* argv[]) { int c; - int should_print_help = 0; - int should_print_help_all = 0; - int print_udev_rules = 0; - int sidetone_loudness = -1; - int request_battery = 0; - int request_chatmix = 0; - int request_connected = 0; - int notification_sound = -1; - int lights = -1; - int inactive_time = -1; - int voice_prompts = -1; - int rotate_to_mute = -1; - int print_capabilities = -1; - int equalizer_preset = -1; - int microphone_mute_led_brightness = -1; - int microphone_volume = -1; - int volume_limiter = -1; - int bt_when_powered_on = -1; - int bt_call_volume = -1; - int dev_mode = 0; - unsigned follow_sec = 2; - struct equalizer_settings* equalizer = NULL; + int should_print_help = 0; + int should_print_help_all = 0; + int print_udev_rules = 0; + int sidetone_loudness = -1; + int request_battery = 0; + int request_chatmix = 0; + int request_connected = 0; + int notification_sound = -1; + int lights = -1; + int inactive_time = -1; + int voice_prompts = -1; + int rotate_to_mute = -1; + int print_capabilities = -1; + int equalizer_preset = -1; + int microphone_mute_led_brightness = -1; + int microphone_volume = -1; + int volume_limiter = -1; + int bt_when_powered_on = -1; + int bt_call_volume = -1; + int dev_mode = 0; + unsigned follow_sec = 2; + struct equalizer_settings* equalizer = NULL; + struct parametric_equalizer_settings* parametric_equalizer = NULL; OutputType output_format = OUTPUT_STANDARD; int test_device = 0; @@ -562,6 +591,7 @@ int main(int argc, char* argv[]) { "help-all", no_argument, NULL, 0 }, { "equalizer", required_argument, NULL, 'e' }, { "equalizer-preset", required_argument, NULL, 'p' }, + { "parametric-equalizer", required_argument, NULL, 0 }, { "microphone-mute-led-brightness", required_argument, NULL, 0 }, { "microphone-volume", required_argument, NULL, 0 }, { "inactive-time", required_argument, NULL, 'i' }, @@ -788,6 +818,16 @@ int main(int argc, char* argv[]) } } // fall through + } else if (strcmp(opts[option_index].name, "parametric-equalizer") == 0) { + parametric_equalizer = parse_parametric_equalizer_settings(optarg); + + for (int i = 0; i < parametric_equalizer->size; i++) { + if ((int)parametric_equalizer->bands[i].type == HSC_INVALID_ARG) { + fprintf(stderr, "Unknown filter type specified to --parametric-equalizer\n"); + return 1; + } + } + // fall through } else if (strcmp(opts[option_index].name, "readme-helper") == 0) { // We need to initialize it at this point init_devices(); @@ -868,6 +908,7 @@ int main(int argc, char* argv[]) { CAP_MICROPHONE_MUTE_LED_BRIGHTNESS, CAPABILITYTYPE_ACTION, µphone_mute_led_brightness, microphone_mute_led_brightness != -1, {} }, { CAP_MICROPHONE_VOLUME, CAPABILITYTYPE_ACTION, µphone_volume, microphone_volume != -1, {} }, { CAP_EQUALIZER, CAPABILITYTYPE_ACTION, equalizer, equalizer != NULL, {} }, + { CAP_PARAMETRIC_EQUALIZER, CAPABILITYTYPE_ACTION, parametric_equalizer, parametric_equalizer != NULL, {} }, { CAP_VOLUME_LIMITER, CAPABILITYTYPE_ACTION, &volume_limiter, volume_limiter != -1, {} }, { CAP_BT_WHEN_POWERED_ON, CAPABILITYTYPE_ACTION, &bt_when_powered_on, bt_when_powered_on != -1, {} }, { CAP_BT_CALL_VOLUME, CAPABILITYTYPE_ACTION, &bt_call_volume, bt_call_volume != -1, {} } @@ -953,8 +994,13 @@ int main(int argc, char* argv[]) if (equalizer != NULL) { free(equalizer->bands_values); + free(equalizer); + } + + if (parametric_equalizer != NULL) { + free(parametric_equalizer->bands); + free(parametric_equalizer); } - free(equalizer); terminate_hid(&device_handle, &hid_path); return 0; diff --git a/src/output.c b/src/output.c index a0419c1..b52058e 100644 --- a/src/output.c +++ b/src/output.c @@ -168,10 +168,13 @@ void initializeHeadsetInfo(HeadsetInfo* info, struct device* device) info->product_name = device->device_hid_productname; info->equalizer = device->equalizer; - info->equalizer_presets = device->eqaulizer_presets, + info->equalizer_presets = device->equalizer_presets, info->has_equalizer_info = info->equalizer != NULL; info->has_equalizer_presets_info = info->equalizer_presets != NULL; + info->parametric_equalizer = device->parametric_equalizer, + info->has_parametric_equalizer_info = info->parametric_equalizer != NULL; + info->capabilities_amount = 0; for (int i = 0; i < NUM_CAPABILITIES; i++) { @@ -411,6 +414,33 @@ void output_json(HeadsetControlStatus* status, HeadsetInfo* infos) } } + if (info->has_parametric_equalizer_info) { + printf(",\n \"parametric_equalizer\": {\n"); + printf(" \"bands\": %d,\n", info->parametric_equalizer->bands_count); + printf(" \"gain\": {\n"); + printf(" \"step\": %g,\n", info->parametric_equalizer->gain_step); + printf(" \"min\": %g,\n", info->parametric_equalizer->gain_min); + printf(" \"max\": %g,\n", info->parametric_equalizer->gain_max); + printf(" \"base\": %g\n", info->parametric_equalizer->gain_base); + printf(" },\n"); + printf(" \"q_factor\": {\n"); + printf(" \"min\": %g,\n", info->parametric_equalizer->q_factor_min); + printf(" \"max\": %g\n", info->parametric_equalizer->q_factor_max); + printf(" },\n"); + printf(" \"frequency\": {\n"); + printf(" \"min\": %d,\n", info->parametric_equalizer->freq_min); + printf(" \"max\": %d\n", info->parametric_equalizer->freq_max); + printf(" },\n"); + printf(" \"filter_types:\" [\n"); + for (int i = 0; i < NUM_EQ_FILTER_TYPES; i++) { + if (has_capability(info->parametric_equalizer->filter_types, i)) { + printf(" \"%s\",\n", equalizer_filter_type_str[i]); + } + } + printf(" ]\n"); + printf(" }"); + } + if (info->has_chatmix_info) { printf(",\n \"chatmix\": %d", info->chatmix); } @@ -466,6 +496,14 @@ void yaml_printint(const char* key, const int value, int indent) printf("%s: %d\n", yaml_replace_spaces_with_dash(key), value); } +void yaml_printfloat(const char* key, const float value, int indent) +{ + for (int i = 0; i < indent; i++) { + putchar(' '); + } + printf("%s: %g\n", yaml_replace_spaces_with_dash(key), value); +} + const char* yaml_replace_spaces_with_dash(const char* str) { assert(strlen(str) < 64 && "replace_spaces_with_dash: too long"); @@ -600,6 +638,28 @@ void output_yaml(HeadsetControlStatus* status, HeadsetInfo* infos) } } + if (info->has_parametric_equalizer_info) { + yaml_print("parametric_equalizer", "", 4); + yaml_printint("bands", info->parametric_equalizer->bands_count, 6); + yaml_print("gain", "", 6); + yaml_printfloat("step", info->parametric_equalizer->gain_step, 8); + yaml_printfloat("min", info->parametric_equalizer->gain_min, 8); + yaml_printfloat("max", info->parametric_equalizer->gain_max, 8); + yaml_printfloat("base", info->parametric_equalizer->gain_base, 8); + yaml_print("q_factor", "", 6); + yaml_printfloat("min", info->parametric_equalizer->q_factor_min, 8); + yaml_printfloat("max", info->parametric_equalizer->q_factor_max, 8); + yaml_print("frequency", "", 6); + yaml_printint("min", info->parametric_equalizer->freq_min, 8); + yaml_printint("max", info->parametric_equalizer->freq_max, 8); + yaml_print("filter_types", "", 6); + for (int i = 0; i < NUM_EQ_FILTER_TYPES; i++) { + if (has_capability(info->parametric_equalizer->filter_types, i)) { + yaml_print_listitem(equalizer_filter_type_str[i], 8, true); + } + } + } + if (info->has_chatmix_info) { yaml_printint("chatmix", info->chatmix, 4); } diff --git a/src/output.h b/src/output.h index d2efd96..24859b8 100644 --- a/src/output.h +++ b/src/output.h @@ -77,6 +77,8 @@ typedef struct { EqualizerInfo* equalizer; bool has_equalizer_presets_info; EqualizerPresets* equalizer_presets; + bool has_parametric_equalizer_info; + ParametricEqualizerInfo* parametric_equalizer; Action actions[MAX_ACTIONS]; int action_count; diff --git a/src/utility.c b/src/utility.c index b618563..df8b96f 100644 --- a/src/utility.c +++ b/src/utility.c @@ -7,6 +7,7 @@ #include #include +#include "device.h" #include "utility.h" int map(int x, int in_min, int in_max, int out_min, int out_max) @@ -137,6 +138,136 @@ int get_float_data_from_parameter(char* input, float* dest, size_t len) return i; } +/** + * Converts a filter type string to the corresponding EqualizerFilterType enum + * Returns HSC_INVALID_ARG if the string does not match a known filter type. + */ +static EqualizerFilterType parse_eq_filter_type(const char* input) +{ + for (int i = 0; i < NUM_EQ_FILTER_TYPES; i++) { + if (strcmp(input, equalizer_filter_type_str[i]) == 0) { + return i; + } + } + + return HSC_INVALID_ARG; +} + +/** + * Parses a equalizer band string into a struct parametric_equalizer_band. + * + * Expected band_str format: "float,float,float,string" + * Expected fields: "frequency,gain,q-factor,filter-type" + * + * Returns HSC_INVALID_ARG if the string can't be parsed. + */ +static int parse_parametric_equalizer_band(const char* band_str, struct parametric_equalizer_band* out_band) +{ + const char* delim = " ,"; + + // Make a modifiable copy of input, because strtok modifies the string. + char* tmp = strdup(band_str); + if (!tmp) { + return -1; + } + + // parse freq, gain, q_factor, type + char* token = strtok(tmp, delim); + if (!token) { + free(tmp); + return HSC_INVALID_ARG; + } + out_band->frequency = strtof(token, NULL); + + token = strtok(NULL, delim); + if (!token) { + free(tmp); + return HSC_INVALID_ARG; + } + out_band->gain = strtof(token, NULL); + + token = strtok(NULL, delim); + if (!token) { + free(tmp); + return HSC_INVALID_ARG; + } + out_band->q_factor = strtof(token, NULL); + + token = strtok(NULL, delim); + if (!token) { + free(tmp); + return HSC_INVALID_ARG; + } + + out_band->type = parse_eq_filter_type(token); + if ((int)out_band->type == HSC_INVALID_ARG) { + printf("Couldn't parse filter type: %s\n", token); + free(tmp); + return HSC_INVALID_ARG; + } + + free(tmp); + return 0; +} + +/** + * Parses the full parametric equalizer string that can contain multiple band + * definitions separated by ';' into a parametric_equalizer_settings object. + * + * Example of input format: + * "100.0,3.5,1.0,lowshelf;500.0,-2.0,1.2,peaking;2000.0,5.0,0.7,highshelf" + */ +struct parametric_equalizer_settings* parse_parametric_equalizer_settings(const char* input) +{ + struct parametric_equalizer_settings* settings = malloc(sizeof(struct parametric_equalizer_settings)); + settings->size = 0; + settings->bands = NULL; + + if (!input || !*input || (strcmp(input, "reset") == 0)) { + // Return empty if null/empty or reset of bands requested. + // The device implementation is responsible for resetting + // all remaining bands that are not provided by the user (all in this case). + return settings; + } + + // create a modifiable copy of the input + char* input_copy = strdup(input); + if (!input_copy) { + return settings; + } + + // Count how many bands we have by counting the number of semicolons + 1 + int band_count = 1; + for (const char* p = input; *p; ++p) { + if (*p == ';') { + band_count++; + } + } + + // Allocate space for bands + struct parametric_equalizer_band* bands = (struct parametric_equalizer_band*)calloc(band_count, sizeof(struct parametric_equalizer_band)); + + if (!bands) { + free(input_copy); + return settings; + } + + // Tokenize by ';' and parse each band definition + char* context = NULL; + char* band_str = strtok_r(input_copy, ";", &context); + int i = 0; + while (band_str && i < band_count) { + parse_parametric_equalizer_band(band_str, &bands[i++]); + band_str = strtok_r(NULL, ";", &context); + } + + settings->size = i; + settings->bands = bands; + + free(input_copy); + return settings; +} + // ----------------- asprintf / vasprintf ----------------- /* * Copyright (c) 2004 Darren Tucker. diff --git a/src/utility.h b/src/utility.h index f12568d..e98c6e1 100644 --- a/src/utility.h +++ b/src/utility.h @@ -92,6 +92,8 @@ int get_byte_data_from_parameter(char* input, unsigned char* dest, size_t len); */ int get_float_data_from_parameter(char* input, float* dest, size_t len); +struct parametric_equalizer_settings* parse_parametric_equalizer_settings(const char* input); + int vasprintf(char** str, const char* fmt, va_list ap); int _asprintf(char** str, const char* fmt, ...);