Last active
May 17, 2022 02:27
-
-
Save IslandJohn/ee13718fce203af52f13bd3c8e0e0ae2 to your computer and use it in GitHub Desktop.
Joystick Gremlin plugin to remap, and optionally decode, physical encoder directions to accelerated, simulated by multiple presses, virtual directions.
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] | |
) | |
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] | |
) | |
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) | |
current_state = [False, False] | |
direction_counter = 0 | |
thread_l = None | |
thread_r = None | |
pulse_l = 0; | |
pulse_r = 0; | |
last_l = 0; | |
last_r = 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) | |
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 thread_l, pulse_l, last_l | |
last_l, delta_c = calc_accel(last_l) | |
pulse_l += delta_c | |
if thread_l is None and pulse_l > 0: | |
thread_l = threading.Timer(0, pulse_left, [vjoy]) | |
thread_l.start() | |
def joy_right(vjoy): | |
global thread_r, pulse_r, last_r | |
last_r, delta_c = calc_accel(last_r) | |
pulse_r += delta_c | |
if thread_r is None and pulse_r > 0: | |
thread_r = threading.Timer(0, pulse_right, [vjoy]) | |
thread_r.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(vjoy): | |
global thread_l, pulse_l | |
btn = vjoy[vjoy_1.value["device_id"]].button(vjoy_1.value["input_id"]) | |
while pulse_l > 0: | |
pulse_button(btn, pulse_d.value / 2000.0) | |
pulse_l -= 1 | |
thread_l = None | |
def pulse_right(vjoy): | |
global thread_r, pulse_r | |
btn = vjoy[vjoy_2.value["device_id"]].button(vjoy_2.value["input_id"]) | |
while pulse_r > 0: | |
pulse_button(btn, pulse_d.value / 2000.0) | |
pulse_r -= 1 | |
thread_r = None | |
def pulse_button(btn, delay): | |
btn.is_pressed = True | |
time.sleep(delay) | |
btn.is_pressed = False | |
time.sleep(delay) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment