Skip to content

Instantly share code, notes, and snippets.

@IslandJohn
Last active May 17, 2022 02:27
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save IslandJohn/ee13718fce203af52f13bd3c8e0e0ae2 to your computer and use it in GitHub Desktop.
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.
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