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, ...);