Last active
November 15, 2020 14:54
-
-
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.
This file contains 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
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