Skip to content

Instantly share code, notes, and snippets.

@IslandJohn
Last active November 15, 2020 14:54
Show Gist options
  • Save IslandJohn/809fb7822b806092ee8a4a313d04a9ac to your computer and use it in GitHub Desktop.
Save IslandJohn/809fb7822b806092ee8a4a313d04a9ac to your computer and use it in GitHub Desktop.
Joystick Gremlin plugin to remap, and optionally decode, physical encoder directions to two sets of accelerated, simulated by multiple presses, virtual directions differentiated by a push button modifier.
import gremlin
import threading
import time
from gremlin.user_plugin import *
mode = ModeVariable(
"Mode",
"The mode to use for this instance"
)
native_m = BoolVariable(
"Native Mode",
"Decode native encoder input states (when not handled by controller).",
True
)
pulse_c = IntegerVariable(
"Pulses per Detent",
"Pulses per encoder detent/cycle when in native mode.",
4,
1,
4
)
joy_1 = PhysicalInputVariable(
"Physical LEFT",
"Physical left direction.",
[gremlin.common.InputType.JoystickButton]
)
joy_2 = PhysicalInputVariable(
"Physical RIGHT",
"Physical right direction.",
[gremlin.common.InputType.JoystickButton]
)
joy_3 = PhysicalInputVariable(
"Physical MOD",
"Physical modifier (e.g. push button) for secondary virtual direction.",
[gremlin.common.InputType.JoystickButton]
)
rate_l = IntegerVariable(
"Rate Limit",
"Limit rate of incoming or decoded direction signal (ms).",
40,
10,
1000
)
min_a = IntegerVariable(
"Min. Acceleration",
"Duration between physical pulses without acceleration (ms).",
200,
10,
1000
)
max_a = IntegerVariable(
"Max. Acceleration",
"Duration between physical pulses for maximum acceleration (ms).",
100,
10,
1000
)
exp_a = FloatVariable(
"Exponent",
"Exponential scaling of the acceleration as (min/max)^exp.",
2.0,
1.0,
4.0
)
vjoy_1 = VirtualInputVariable(
"Virtual Pri. LEFT",
"Virtual primary left direction.",
[gremlin.common.InputType.JoystickButton]
)
vjoy_2 = VirtualInputVariable(
"Virtual Pri. RIGHT",
"Virtual primary right direction.",
[gremlin.common.InputType.JoystickButton]
)
vjoy_3 = VirtualInputVariable(
"Virtual Sec. LEFT",
"Virtual secondary left direction while modifier is pressed.",
[gremlin.common.InputType.JoystickButton]
)
vjoy_4 = VirtualInputVariable(
"Virtual Sec. RIGHT",
"Virtual secondary right direction while modifier is pressed.",
[gremlin.common.InputType.JoystickButton]
)
pulse_d = IntegerVariable(
"Pulse Duration",
"Pulse duration for virtual button press/release cycle (ms).",
40,
10,
1000
)
decorator_1 = joy_1.create_decorator(mode.value)
decorator_2 = joy_2.create_decorator(mode.value)
decorator_3 = joy_3.create_decorator(mode.value)
current_state = [False, False]
direction_counter = 0
direction_mod = False
thread_l_pri = None
thread_l_sec = None
thread_r_pri = None
thread_r_sec = None
pulse_l_pri = 0;
pulse_l_sec = 0;
pulse_r_pri = 0;
pulse_r_sec = 0;
last_l_pri = 0;
last_l_sec = 0;
last_r_pri = 0;
last_r_sec = 0;
@decorator_1.button(joy_1.input_id)
def joy_event_1(event, vjoy):
global current_state
dir = -1 if event.is_pressed else 0
if native_m.value:
dir = decode_state([event.is_pressed, current_state[1]])
if dir < 0: joy_left(vjoy)
elif dir > 0: joy_right(vjoy)
@decorator_2.button(joy_2.input_id)
def joy_event_2(event, vjoy):
global current_state
dir = 1 if event.is_pressed else 0
if native_m.value:
dir = decode_state([current_state[0], event.is_pressed])
if dir < 0: joy_left(vjoy)
elif dir > 0: joy_right(vjoy)
@decorator_3.button(joy_3.input_id)
def joy_event_3(event, vjoy):
global direction_mod
direction_mod = event.is_pressed
def decode_state(new_state):
global current_state, direction_counter
direction_counter += -1 if new_state[0] != current_state[1] else 1
current_state = new_state
if abs(direction_counter) < pulse_c.value: return 0
ret = 1 if direction_counter > 0 else -1
direction_counter = 0
return ret
def joy_left(vjoy):
global direction_mod
if direction_mod: joy_left_sec(vjoy)
else: joy_left_pri(vjoy)
def joy_right(vjoy):
global direction_mod
if direction_mod: joy_right_sec(vjoy)
else: joy_right_pri(vjoy)
def joy_left_pri(vjoy):
global thread_l_pri, pulse_l_pri, last_l_pri
last_l_pri, delta_c = calc_accel(last_l_pri)
pulse_l_pri += delta_c
if thread_l_pri is None and pulse_l_pri > 0:
thread_l_pri = threading.Timer(0, pulse_left_pri, [vjoy])
thread_l_pri.start()
def joy_left_sec(vjoy):
global thread_l_sec, pulse_l_sec, last_l_sec
last_l_sec, delta_c = calc_accel(last_l_sec)
pulse_l_sec += delta_c
if thread_l_sec is None and pulse_l_sec > 0:
thread_l_sec = threading.Timer(0, pulse_left_sec, [vjoy])
thread_l_sec.start()
def joy_right_pri(vjoy):
global thread_r_pri, pulse_r_pri, last_r_pri
last_r_pri, delta_c = calc_accel(last_r_pri)
pulse_r_pri += delta_c
if thread_r_pri is None and pulse_r_pri > 0:
thread_r_pri = threading.Timer(0, pulse_right_pri, [vjoy])
thread_r_pri.start()
def joy_right_sec(vjoy):
global thread_r_sec, pulse_r_sec, last_r_sec
last_r_sec, delta_c = calc_accel(last_r_sec)
pulse_r_sec += delta_c
if thread_r_sec is None and pulse_r_sec > 0:
thread_r_sec = threading.Timer(0, pulse_right_sec, [vjoy])
thread_r_sec.start()
def calc_accel(last_t):
now_t = time.monotonic()
delta_t = (now_t - last_t) * 1000.0
if delta_t < rate_l.value: return last_t, 0
delta_c = min_a.value / min(min_a.value, max(max_a.value, delta_t))
delta_c = pow(delta_c, exp_a.value)
return now_t, int(round(delta_c))
def pulse_left_pri(vjoy):
global thread_l_pri, pulse_l_pri
btn = vjoy[vjoy_1.value["device_id"]].button(vjoy_1.value["input_id"])
while pulse_l_pri > 0:
pulse_button(btn, pulse_d.value / 2000.0)
pulse_l_pri -= 1
thread_l_pri = None
def pulse_left_sec(vjoy):
global thread_l_sec, pulse_l_sec
btn = vjoy[vjoy_3.value["device_id"]].button(vjoy_3.value["input_id"])
while pulse_l_sec > 0:
pulse_button(btn, pulse_d.value / 2000.0)
pulse_l_sec -= 1
thread_l_sec = None
def pulse_right_pri(vjoy):
global thread_r_pri, pulse_r_pri
btn = vjoy[vjoy_2.value["device_id"]].button(vjoy_2.value["input_id"])
while pulse_r_pri > 0:
pulse_button(btn, pulse_d.value / 2000.0)
pulse_r_pri -= 1
thread_r_pri = None
def pulse_right_sec(vjoy):
global thread_r_sec, pulse_r_sec
btn = vjoy[vjoy_4.value["device_id"]].button(vjoy_4.value["input_id"])
while pulse_r_sec > 0:
pulse_button(btn, pulse_d.value / 2000.0)
pulse_r_sec -= 1
thread_r_sec = None
def pulse_button(btn, delay):
btn.is_pressed = True
time.sleep(delay)
btn.is_pressed = False
time.sleep(delay)
@gremlin.input_devices.periodic(1)
def periodic_event(joy, vjoy):
input_devices.periodic_registry.stop()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment