Skip to content

Instantly share code, notes, and snippets.

@Ph1syc
Created June 18, 2025 17:47
Show Gist options
  • Select an option

  • Save Ph1syc/9d54816bcd9fd6f5dfcc1ebfedaf5990 to your computer and use it in GitHub Desktop.

Select an option

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 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