Created
June 18, 2025 17:47
-
-
Save Ph1syc/9d54816bcd9fd6f5dfcc1ebfedaf5990 to your computer and use it in GitHub Desktop.
Python script to use a PS3 DJ Hero controller on Spin Rhythm XD (might work with turntables for other platforms)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # This script should be fairly readable so I didn't bother adding comments but if you have any questions just contact me on discord, my username is ph1syc | |
| import evdev, time, math, fnmatch, os, json, sys, signal | |
| from math import tanh | |
| from time import sleep | |
| from evdev import ecodes as e | |
| from evdev import * | |
| from fnmatch import fnmatch | |
| if os.getuid() == 0: | |
| print("Please don't run this script as sudo.") | |
| sys.exit(0) | |
| def signal_handler(sig, frame): | |
| print('\n\nExiting\n') | |
| sys.exit(0) | |
| def DJFind(pattern): | |
| for path in list_devices(): | |
| global dev | |
| dev = InputDevice(path) | |
| if fnmatch(dev.name, pattern): | |
| return path | |
| return None | |
| path = DJFind("RedOctane DJ*") | |
| signal.signal(signal.SIGINT, signal_handler) | |
| if path: | |
| print(f"\nFound turntable at {path}!") | |
| else: | |
| devices = [evdev.InputDevice(path) for path in evdev.list_devices()] | |
| for device in devices: | |
| print(device.path, device.name) | |
| path = "/dev/input/event" + input("\nTurntable not found, please enter the device's number from the list above (or press CTRL+C to exit): ") | |
| print(f"\nTurntable is at {path}.") | |
| def ctr1p(): | |
| global joy | |
| joy = UInput.from_device(path, name='DJ-Left', version=0x3) | |
| joy.capabilities(verbose=True).keys() | |
| dev = InputDevice(path) | |
| dev.grab() | |
| def ctr2p(): | |
| global joy, joy2 | |
| joy = UInput.from_device(path, name='DJ-Left', version=0x3) | |
| joy.capabilities(verbose=True).keys() | |
| joy2 = UInput.from_device(path, name='DJ-Right', version=0x3) | |
| joy2.capabilities(verbose=True).keys() | |
| dev = InputDevice(path) | |
| dev.grab() | |
| def setup1p(): | |
| print("\nPress the tap button") | |
| for event in dev.read_loop(): | |
| if event.type == e.EV_KEY & event.value == 1: | |
| global tap | |
| tap = event.code | |
| break | |
| print("\nAdd an alternative tap button (or press the same button again to skip)") | |
| for event in dev.read_loop(): | |
| if event.type == e.EV_KEY & event.value == 1: | |
| global tap2 | |
| tap2 = event.code | |
| break | |
| print("\nAdd a third tap button (or once again press a previous one to skip)") | |
| for event in dev.read_loop(): | |
| if event.type == e.EV_KEY & event.value == 1: | |
| global tap3 | |
| tap3 = event.code | |
| break | |
| print("\nPress the beat button") | |
| for event in dev.read_loop(): | |
| if event.type == e.EV_KEY & event.value == 1: | |
| global beat | |
| beat = event.code | |
| break | |
| print("\nAnd finally, add an alternative beat button (or press the same one again to skip)") | |
| for event in dev.read_loop(): | |
| if event.type == e.EV_KEY & event.value == 1: | |
| global beat2 | |
| beat2 = event.code | |
| break | |
| binds = [{'tap': tap, 'beat': beat, 'tap2': tap2, 'beat2': beat2, 'tap3': tap3}] | |
| global cfg | |
| cfg = (f"{input("\nHow should this config be called? (please refrain from using file system related characters such as / and *):\n")}.1p.json") | |
| with open(f'{cfg}', "w") as acfg: | |
| json.dump(binds, acfg) | |
| def setup2p(): | |
| print("\nPress the tap button for player 1 (right wheel)") | |
| for event in dev.read_loop(): | |
| if event.type == e.EV_KEY & event.value == 1: | |
| global tap_r | |
| tap_r = event.code | |
| break | |
| print("\nPress the beat button for player 1 (right wheel)") | |
| for event in dev.read_loop(): | |
| if event.type == e.EV_KEY & event.value == 1: | |
| global beat_r | |
| beat_r = event.code | |
| break | |
| print("\n!!! REMEMBER: the red green and blue buttons on both wheels are read as the same input, make sure to not reuse them !!!") | |
| print("\nPress the tap button for the player 2 (left wheel)") | |
| for event in dev.read_loop(): | |
| if event.type == e.EV_KEY & event.value == 1: | |
| global tap_l | |
| tap_l = event.code | |
| break | |
| print("\nAnd finally, press the beat button for player 2 (left wheel)") | |
| for event in dev.read_loop(): | |
| if event.type == e.EV_KEY & event.value == 1: | |
| global beat_l | |
| beat_l = event.code | |
| break | |
| binds = [{'tap_r': tap_r, 'beat_r': beat_r, 'tap_l': tap_l, 'beat_l': beat_l}] | |
| global cfg | |
| cfg = (f"{input("\nHow should this config be called? (please refrain from using file system related characters such as / and *):\n")}.2p.json") | |
| with open(f'{cfg}', "w") as acfg: | |
| json.dump(binds, acfg) | |
| def cfg1p(): | |
| cfgdir = f'{os.path.expanduser("~")}/.config/DJCTR' | |
| if os.path.isdir(cfgdir): | |
| os.chdir(cfgdir) | |
| svs = [i for i in os.listdir(cfgdir) if i.endswith('.1p.json')] | |
| if svs: | |
| print("\nConfig directory found\n\n0) New config") | |
| for fls, file in enumerate(svs, 1): | |
| print(f"{fls}) {file.removesuffix('.1p.json')}") | |
| global cfg | |
| cfgid = int(input("\nEnter the desired config's number: "))-1 | |
| if cfgid < 0 or cfgid >= len(svs): | |
| setup1p() | |
| else: | |
| cfg = svs[cfgid] | |
| else: | |
| print("\nConfig directory found but no configs present, starting config setup.") | |
| setup1p() | |
| else: | |
| print("\nNo config directory found, creating one and beginning config setup.") | |
| os.mkdir(cfgdir) | |
| os.chdir(cfgdir) | |
| setup1p() | |
| def cfg2p(): | |
| cfgdir = f'{os.path.expanduser("~")}/.config/DJCTR' | |
| if os.path.isdir(cfgdir): | |
| os.chdir(cfgdir) | |
| svs = [i for i in os.listdir(cfgdir) if i.endswith('.2p.json')] | |
| if svs: | |
| print("\nConfig directory found\n\n0) New config") | |
| for fls, file in enumerate(svs, 1): | |
| print(f"{fls}) {file.removesuffix('.2p.json')}") | |
| global cfg | |
| cfgid = int(input("\nEnter the desired config's number: "))-1 | |
| if cfgid < 0 or cfgid >= len(svs): | |
| setup2p() | |
| else: | |
| cfg = svs[cfgid] | |
| else: | |
| print("\nConfig directory found but no configs present, starting config setup.") | |
| setup2p() | |
| else: | |
| print("\nNo config directory found, creating one and beginning config setup.") | |
| os.mkdir(cfgdir) | |
| os.chdir(cfgdir) | |
| setup2p() | |
| def start1p(): | |
| with open(f'{cfg}', "r") as inp: | |
| conf = json.load(inp)[0] | |
| tap = (conf["tap"]) | |
| beat = (conf["beat"]) | |
| tap2 = (conf["tap2"]) | |
| beat2 = (conf["beat2"]) | |
| tap3 = (conf["tap3"]) | |
| print("\nAll ready! Press CTRL+C to exit when you're done playing.") | |
| for event in dev.read_loop(): | |
| if event.type == e.EV_ABS and (event.code == e.ABS_Z or event.code == e.ABS_RZ): | |
| l = int((128*tanh(0.3*(event.value-128))))+128 | |
| joy.write(e.EV_ABS, e.ABS_X, l) | |
| joy.syn() | |
| sleep(0.002) | |
| if event.type == e.EV_KEY and (event.code == tap or event.code == tap2 or event.code == tap3): | |
| joy.write(e.EV_KEY, e.BTN_SOUTH, event.value) | |
| joy.syn() | |
| if event.type == e.EV_KEY and (event.code == beat or event.code == beat2): | |
| joy.write(e.EV_KEY, e.BTN_WEST, event.value) | |
| joy.syn() | |
| def start2p(): | |
| with open(f'{cfg}', "r") as inp: | |
| conf = json.load(inp)[0] | |
| tap_l = (conf["tap_l"]) | |
| beat_l = (conf["beat_l"]) | |
| tap_r = (conf["tap_r"]) | |
| beat_r = (conf["beat_r"]) | |
| print("\nAll ready! Press CTRL+C to exit when you're done playing.") | |
| for event in dev.read_loop(): | |
| if event.type == e.EV_ABS and event.code == e.ABS_Z: | |
| l = int((128*tanh(0.3*(event.value-128))))+128 | |
| joy.write(e.EV_ABS, e.ABS_X, l) | |
| joy.syn() | |
| sleep(0.002) | |
| if event.type == e.EV_ABS and event.code == e.ABS_RZ: | |
| r = int((128*tanh(0.3*(event.value-128))))+128 | |
| joy2.write(e.EV_ABS, e.ABS_X, r) | |
| joy2.syn() | |
| sleep(0.002) | |
| if event.type == e.EV_KEY and event.code == tap_l: | |
| joy.write(e.EV_KEY, e.BTN_SOUTH, event.value) | |
| joy.syn() | |
| if event.type == e.EV_KEY and event.code == beat_l: | |
| joy.write(e.EV_KEY, e.BTN_WEST, event.value) | |
| joy.syn() | |
| if event.type == e.EV_KEY and event.code == tap_r: | |
| joy2.write(e.EV_KEY, e.BTN_SOUTH, event.value) | |
| joy2.syn() | |
| if event.type == e.EV_KEY and event.code == beat_r: | |
| joy2.write(e.EV_KEY, e.BTN_WEST, event.value) | |
| joy2.syn() | |
| if int(input("\nAre 1 or 2 people playing? ")) == 1: | |
| ctr1p() | |
| cfg1p() | |
| start1p() | |
| else: | |
| ctr2p() | |
| cfg2p() | |
| start2p() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment