From 69998df12420aa0d867a32d23c5860da0dd270a4 Mon Sep 17 00:00:00 2001 From: richard-dp <6692307+richard-dp@users.noreply.github.com> Date: Fri, 25 Jul 2025 18:44:14 +0200 Subject: [PATCH 01/10] Added support for XBOX variant --- nova-chatmix.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/nova-chatmix.py b/nova-chatmix.py index 5786c3e..f4961ab 100755 --- a/nova-chatmix.py +++ b/nova-chatmix.py @@ -51,9 +51,10 @@ def _set_volume(self, sink: str, volume: int): class NovaProWireless: - # USB IDs + # USB VendorID VID = 0x1038 - PID = 0x12E0 + # USB PIDs for Acrtis Nova Pro Wireless & XBOX variant + PID_LIST = [0x12E0, 0x12E5] # bInterfaceNumber INTERFACE = 0x4 @@ -100,16 +101,18 @@ class NovaProWireless: # Device not found error string ERR_NOTFOUND = "Device not found" + @staticmethod + def ResolveHidDevPath(): + for pid in NovaProWireless.PID_LIST: + for hiddev in hidenumerate(NovaProWireless.VID, pid): + if hiddev["interface_number"] == NovaProWireless.INTERFACE: + return hiddev["path"] + raise DeviceNotFoundException + # Selects correct device, and makes sure we can control it def __init__(self, output_sink=None): # Find HID device path - devpath = None - for hiddev in hidenumerate(self.VID, self.PID): - if hiddev["interface_number"] == self.INTERFACE: - devpath = hiddev["path"] - break - if not devpath: - raise DeviceNotFoundException + devpath = NovaProWireless.ResolveHidDevPath() # Try to automatically detect output sink, this is skipped if output_sink is given if not output_sink: From d55f3d8cfb545636c2295970656f265548b43adc Mon Sep 17 00:00:00 2001 From: richard-dp <6692307+richard-dp@users.noreply.github.com> Date: Fri, 25 Jul 2025 21:23:55 +0200 Subject: [PATCH 02/10] Updated 50-nova-pro-wireless.rules to include rule for xbox variant --- 50-nova-pro-wireless.rules | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/50-nova-pro-wireless.rules b/50-nova-pro-wireless.rules index 4938c06..f6a4f97 100644 --- a/50-nova-pro-wireless.rules +++ b/50-nova-pro-wireless.rules @@ -1 +1,5 @@ +# Arctis Nova Pro Wireless SUBSYSTEMS=="usb", ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12e0", TAG+="uaccess", ENV{SYSTEMD_USER_WANTS}="nova-chatmix.service" + +# Arctis Nova Pro Wireless (xbox variant) +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12e5", TAG+="uaccess", ENV{SYSTEMD_USER_WANTS}="nova-chatmix.service" From 630c862bf2d7e278dd8d5140b5df212931b61576 Mon Sep 17 00:00:00 2001 From: richard-dp <6692307+richard-dp@users.noreply.github.com> Date: Fri, 25 Jul 2025 18:44:14 +0200 Subject: [PATCH 03/10] Added support for XBOX variant --- nova-chatmix.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/nova-chatmix.py b/nova-chatmix.py index 5786c3e..f4961ab 100755 --- a/nova-chatmix.py +++ b/nova-chatmix.py @@ -51,9 +51,10 @@ def _set_volume(self, sink: str, volume: int): class NovaProWireless: - # USB IDs + # USB VendorID VID = 0x1038 - PID = 0x12E0 + # USB PIDs for Acrtis Nova Pro Wireless & XBOX variant + PID_LIST = [0x12E0, 0x12E5] # bInterfaceNumber INTERFACE = 0x4 @@ -100,16 +101,18 @@ class NovaProWireless: # Device not found error string ERR_NOTFOUND = "Device not found" + @staticmethod + def ResolveHidDevPath(): + for pid in NovaProWireless.PID_LIST: + for hiddev in hidenumerate(NovaProWireless.VID, pid): + if hiddev["interface_number"] == NovaProWireless.INTERFACE: + return hiddev["path"] + raise DeviceNotFoundException + # Selects correct device, and makes sure we can control it def __init__(self, output_sink=None): # Find HID device path - devpath = None - for hiddev in hidenumerate(self.VID, self.PID): - if hiddev["interface_number"] == self.INTERFACE: - devpath = hiddev["path"] - break - if not devpath: - raise DeviceNotFoundException + devpath = NovaProWireless.ResolveHidDevPath() # Try to automatically detect output sink, this is skipped if output_sink is given if not output_sink: From 093dd45a39a05eb16ea1566ced222bb5a51e6934 Mon Sep 17 00:00:00 2001 From: richard-dp <6692307+richard-dp@users.noreply.github.com> Date: Fri, 25 Jul 2025 21:23:55 +0200 Subject: [PATCH 04/10] Updated 50-nova-pro-wireless.rules to include rule for xbox variant --- 50-nova-pro-wireless.rules | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/50-nova-pro-wireless.rules b/50-nova-pro-wireless.rules index 4938c06..f6a4f97 100644 --- a/50-nova-pro-wireless.rules +++ b/50-nova-pro-wireless.rules @@ -1 +1,5 @@ +# Arctis Nova Pro Wireless SUBSYSTEMS=="usb", ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12e0", TAG+="uaccess", ENV{SYSTEMD_USER_WANTS}="nova-chatmix.service" + +# Arctis Nova Pro Wireless (xbox variant) +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12e5", TAG+="uaccess", ENV{SYSTEMD_USER_WANTS}="nova-chatmix.service" From e6398a0ab100bd7ed4f7bc7306291c46a1148720 Mon Sep 17 00:00:00 2001 From: Roni Laukkarinen Date: Sun, 27 Jul 2025 12:25:51 +0300 Subject: [PATCH 05/10] Wired version --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dc9e931..51d2258 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Arctis Nova Pro Wireless ChatMix on Linux +# Arctis Nova Pro ChatMix on Linux ## About this project @@ -6,7 +6,7 @@ Some SteelSeries headsets have a feature called ChatMix where you can easily adj In previous SteelSeries headsets ChatMix was always a hardware feature. It worked by providing 2 sound devices to the host, 1 for general audio and the other for chat audio. -In newer generations of their headsets however, in particular the Arctis Nova Pro Wireless, this feature was taken out of the hardware itself, and made into a feature of their audio software called Sonar. +In newer generations of their headsets however, in particular the Arctis Nova Pro, this feature was taken out of the hardware itself, and made into a feature of their audio software called Sonar. Sonar of course only works on Windows and requires a SteelSeries account. @@ -56,10 +56,10 @@ cd nova-chatmix-linux To be able to run the script as a non-root user, some udev rules need to be applied. This will allow regular users to access the base station USB device. It also starts the script when it gets plugged in (only when the systemd service is also set up). -Copy `50-nova-pro-wireless.rules` to `/etc/udev/rules.d` and reload udev rules: +Copy `50-nova-pro.rules` to `/etc/udev/rules.d` and reload udev rules: ``` -sudo cp 50-nova-pro-wireless.rules /etc/udev/rules.d/50-nova-pro-wireless.rules +sudo cp 50-nova-pro.rules /etc/udev/rules.d/ sudo udevadm control --reload-rules sudo udevadm trigger From cd5a970a96f69288d163306b97d2f48f69880723 Mon Sep 17 00:00:00 2001 From: Roni Laukkarinen Date: Sun, 27 Jul 2025 12:26:57 +0300 Subject: [PATCH 06/10] Formatting --- README.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 51d2258..e131123 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,7 @@ I wanted to be able to use ChatMix on linux, so I reverse engineered the communi ## Disclaimer THIS PROJECT HAS NO ASSOCIATION TO STEELSERIES, NOR IS IT IN ANY WAY SUPPORTED BY THEM. - I AM NOT RESPONSIBLE FOR BRICKED/BROKEN DEVICES NOR DO I GUARANTEE IT WILL WORK FOR YOU. - USING ANYTHING IN THIS PROJECT _MIGHT_ VOID YOUR WARRANTY AND IS AT YOUR OWN RISK. ## Usage @@ -35,13 +33,13 @@ For this project I created a simple Python program to both enable the controls a On Fedora these can be installed with: -``` +```bash sudo dnf install pulseaudio-utils python3 python3-hidapi ``` On Debian based systems (like Ubuntu or Pop!_OS) these can be installed with: -``` +```bash sudo apt install pulseaudio-utils python3 python3-hid ``` @@ -49,7 +47,7 @@ sudo apt install pulseaudio-utils python3 python3-hid Clone this repo and cd into it -``` +```bash git clone https://git.dymstro.nl/Dymstro/nova-chatmix-linux.git cd nova-chatmix-linux ``` @@ -58,7 +56,7 @@ To be able to run the script as a non-root user, some udev rules need to be appl Copy `50-nova-pro.rules` to `/etc/udev/rules.d` and reload udev rules: -``` +```bash sudo cp 50-nova-pro.rules /etc/udev/rules.d/ sudo udevadm control --reload-rules @@ -67,7 +65,7 @@ sudo udevadm trigger If you want to run this script on startup you can add and enable the systemd service -``` +```bash ## The systemd service expects the script in .local/bin # Create the folder if it doesn't exist mkdir -p ~/.local/bin @@ -92,7 +90,7 @@ This will create 2 virtual sound devices: - NovaGame for game/general audio - NovaChat for chat audio -``` +```bash # You do not need to run this if you installed the systemd unit! python nova-chatmix.py ``` From 0eed42a85f4353d69377a3625316ea3ed3a518fe Mon Sep 17 00:00:00 2001 From: Roni Laukkarinen Date: Sun, 27 Jul 2025 12:28:23 +0300 Subject: [PATCH 07/10] Update for wired version --- 50-nova-pro-wireless.rules => 50-nova-pro.rules | 3 +++ nova-chatmix.py | 2 +- nova-chatmix.service | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) rename 50-nova-pro-wireless.rules => 50-nova-pro.rules (68%) diff --git a/50-nova-pro-wireless.rules b/50-nova-pro.rules similarity index 68% rename from 50-nova-pro-wireless.rules rename to 50-nova-pro.rules index f6a4f97..b186fbf 100644 --- a/50-nova-pro-wireless.rules +++ b/50-nova-pro.rules @@ -3,3 +3,6 @@ SUBSYSTEMS=="usb", ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12e0", TAG+="uacc # Arctis Nova Pro Wireless (xbox variant) SUBSYSTEMS=="usb", ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12e5", TAG+="uaccess", ENV{SYSTEMD_USER_WANTS}="nova-chatmix.service" + +# Arctis Nova Pro +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12cb", TAG+="uaccess", ENV{SYSTEMD_USER_WANTS}="nova-chatmix.service" diff --git a/nova-chatmix.py b/nova-chatmix.py index f4961ab..b431ced 100755 --- a/nova-chatmix.py +++ b/nova-chatmix.py @@ -54,7 +54,7 @@ class NovaProWireless: # USB VendorID VID = 0x1038 # USB PIDs for Acrtis Nova Pro Wireless & XBOX variant - PID_LIST = [0x12E0, 0x12E5] + PID_LIST = [0x12E0, 0x12E5, 0x12CB] # bInterfaceNumber INTERFACE = 0x4 diff --git a/nova-chatmix.service b/nova-chatmix.service index 13d21b6..676e686 100644 --- a/nova-chatmix.service +++ b/nova-chatmix.service @@ -1,5 +1,5 @@ [Unit] -Description=Enable ChatMix for the Steelseries Arctis Nova Pro Wireless +Description=Enable ChatMix for the Steelseries Arctis Nova Pro After=pipewire.service pipewire-pulse.service Wants=network-online.target From 48b5c4c6297048bda9f231a2a4b22570958778f0 Mon Sep 17 00:00:00 2001 From: Roni Laukkarinen Date: Sun, 27 Jul 2025 12:32:34 +0300 Subject: [PATCH 08/10] Fix wired support --- nova-chatmix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova-chatmix.py b/nova-chatmix.py index b431ced..a909bfa 100755 --- a/nova-chatmix.py +++ b/nova-chatmix.py @@ -86,7 +86,7 @@ class NovaProWireless: # PipeWire Names ## String used to automatically select output sink - PW_OUTPUT_SINK_AUTODETECT = "SteelSeries_Arctis_Nova_Pro_Wireless" + PW_OUTPUT_SINK_AUTODETECT = "SteelSeries_Arctis_Nova_Pro" ## Names of virtual sound devices PW_GAME_SINK = "NovaGame" PW_CHAT_SINK = "NovaChat" From 3336e5c6e30b4e14ee479bd6243e91ff05dd84a6 Mon Sep 17 00:00:00 2001 From: Roni Laukkarinen Date: Sun, 27 Jul 2025 16:28:41 +0300 Subject: [PATCH 09/10] Fix audio distortion by matching sample rates: Set virtual sinks to 44.1kHz to match headset output and prevent sample rate conversion artifacts --- nova-chatmix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova-chatmix.py b/nova-chatmix.py index a909bfa..0dc2af0 100755 --- a/nova-chatmix.py +++ b/nova-chatmix.py @@ -40,7 +40,7 @@ def _create_virtual_sink(self, name: str, output_sink: str) -> Popen: CMD_PWLOOPBACK, "-P", output_sink, - "--capture-props=media.class=Audio/Sink", + "--capture-props=media.class=Audio/Sink,audio.rate=44100,audio.channels=2", "-n", name, ] From b332db7e57192a931d6996f4c59c03e5a1dc02bb Mon Sep 17 00:00:00 2001 From: richard-dp <6692307+richard-dp@users.noreply.github.com> Date: Sat, 30 Aug 2025 11:26:47 +0200 Subject: [PATCH 10/10] Added PID for another wired variant --- 50-nova-pro.rules | 7 +++---- nova-chatmix.py | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/50-nova-pro.rules b/50-nova-pro.rules index b186fbf..4677a7c 100644 --- a/50-nova-pro.rules +++ b/50-nova-pro.rules @@ -1,8 +1,7 @@ -# Arctis Nova Pro Wireless +# Arctis Nova Pro - Wireless SUBSYSTEMS=="usb", ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12e0", TAG+="uaccess", ENV{SYSTEMD_USER_WANTS}="nova-chatmix.service" - -# Arctis Nova Pro Wireless (xbox variant) SUBSYSTEMS=="usb", ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12e5", TAG+="uaccess", ENV{SYSTEMD_USER_WANTS}="nova-chatmix.service" -# Arctis Nova Pro +# Arctis Nova Pro - Wired SUBSYSTEMS=="usb", ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12cb", TAG+="uaccess", ENV{SYSTEMD_USER_WANTS}="nova-chatmix.service" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12cd", TAG+="uaccess", ENV{SYSTEMD_USER_WANTS}="nova-chatmix.service" diff --git a/nova-chatmix.py b/nova-chatmix.py index 0dc2af0..0c8b6a0 100755 --- a/nova-chatmix.py +++ b/nova-chatmix.py @@ -53,8 +53,8 @@ def _set_volume(self, sink: str, volume: int): class NovaProWireless: # USB VendorID VID = 0x1038 - # USB PIDs for Acrtis Nova Pro Wireless & XBOX variant - PID_LIST = [0x12E0, 0x12E5, 0x12CB] + # USB ProductIDs for Acrtis Nova Pro Wireless & Wired + PID_LIST = [0x12E0, 0x12E5, 0x12CB, 0x12CD] # bInterfaceNumber INTERFACE = 0x4