Skip to content

Instantly share code, notes, and snippets.

@dannytheliar
Created January 3, 2026 21:25
Show Gist options
  • Select an option

  • Save dannytheliar/10e0c9ed9066bd713e887ad2220dfad6 to your computer and use it in GitHub Desktop.

Select an option

Save dannytheliar/10e0c9ed9066bd713e887ad2220dfad6 to your computer and use it in GitHub Desktop.
freepie drum midi controller for elden ring
import time
class KeyDef:
tap_duration = 50
hold_duration = 200
max_scalable_duration = 400
def __init__(self, name, key, notes, threshold = 0.1, holdable = False, scale_with_velocity = False):
"""
Define a MIDI key definition.
:param name: Readable name of the input
:param key: FreePIE output key
:param notes: MIDI notes which will trigger this key
:param threshold: Velocity threshold for triggering this key
:param holdable: Use longer hold duration instead of tap duration. For holdable keys
:param scale_with_velocity: Hold duration will scale with velocity
"""
self.name = name
self.key = key
self.notes = notes
self.threshold = threshold
self.holdable = holdable
self.scale_with_velocity = scale_with_velocity
KEYS = [
KeyDef('Snare', Key.W, [38], holdable=True),
KeyDef('Snare Rim', Key.UpArrow, [37, 40], threshold=0.6, scale_with_velocity=False),
KeyDef('Tom 1', Key.A, [48], holdable=True),
KeyDef('Tom 1 Rim', Key.LeftArrow, [50], threshold=0.2, scale_with_velocity=False),
KeyDef('Tom 2', Key.D, [45], holdable=True),
KeyDef('Tom 2 Rim', Key.RightArrow, [47], threshold=0.2, scale_with_velocity=False),
KeyDef('Tom 3', Key.S, [43], holdable=True),
KeyDef('Tom 3 Rim', Key.DownArrow, [58], threshold=0.5, scale_with_velocity=False),
KeyDef('Tom 4', Key.R, [41], threshold=0.15),
KeyDef('Tom 4 Rim', Key.Escape, [39], threshold=0.6),
KeyDef('Hihat Open', Key.P, [26, 46]),
KeyDef('Hihat Closed', Key.P, [22, 42]),
KeyDef('Crash 1', Key.Q, [49]),
KeyDef('Crash 2', Key.B, [52, 57], threshold=0.3),
KeyDef('Crash 3', Key.M, [27, 28], holdable=True),
KeyDef('Ride 1', Key.N, [51], holdable=True),
KeyDef('Ride 1 Bell', Key.O, [53]),
KeyDef('Ride 2', Key.E, [29, 30], holdable=True),
KeyDef('Splash 1', Key.V, [33]),
KeyDef('Splash 2', Key.F, [31]),
KeyDef('Kick', Key.Space, [36]),
]
# Hihat pedal config
PEDAL_TAP_KEY = Key.F
PEDAL_HOLD_KEY = Key.Space
PEDAL_HOLD_MIN_DURATION = 200 # Tap if pedal is released under min duration, else hold
PEDAL_PRESS_THRESHOLD = 0.4 # Trigger if pedal is down more than this amount
PEDAL_MIDI_NOTE = 4 # TODO: Look more into what this actually is?
# Drum module kit change buttons
# TODO: Differentiate between program change up and down
PROGRAM_CHANGE_KEY = Key.X
VELOCITY_MAX = 127
MIDI_DEVICE_INDEX = 0
def get_key_def(note):
for key in KEYS:
if note in key.notes:
return key
def update():
global state
payload = midi[0].data
midi_note, velocity = payload.buffer
t = int(time.time() * 1000)
if payload.status == MidiStatus.NoteOn:
# Standard MIDI Note On, a key was just pressed
key_def = get_key_def(midi_note)
if key_def:
scaled_velocity = float(velocity) / VELOCITY_MAX
if scaled_velocity >= key_def.threshold:
duration = KeyDef.hold_duration if key_def.holdable else KeyDef.tap_duration
if key_def.scale_with_velocity:
scaled_duration = scaled_velocity * KeyDef.max_scalable_duration
duration = max(KeyDef.tap_duration, scaled_duration)
if key_def.name == "Kick":
#if state['pedal_is_held']:
# keyboard.setKeyDown(Key.F)
# state['pressed_keys'][Key.F] = t + 50
#else:
keyboard.setKeyDown(key_def.key)
state['pressed_keys'][key_def.key] = t + 50
else:
keyboard.setKeyDown(key_def.key)
state['pressed_keys'][key_def.key] = t + duration
diagnostics.debug("MIDI {} ({}): Press {} for {}ms".format(midi_note, key_def.name, key_def.key, duration))
else:
diagnostics.debug("MIDI {}: Unknown".format(midi_note))
elif payload.status == MidiStatus.Control and midi_note == PEDAL_MIDI_NOTE:
# Control message sent from analog pedal input change
if velocity >= PEDAL_PRESS_THRESHOLD * VELOCITY_MAX and state['pedal_press_time'] is None:
# Pedal was pressed
state['pedal_press_time'] = t
elif velocity < PEDAL_PRESS_THRESHOLD * VELOCITY_MAX and state['pedal_press_time'] is not None:
# Pedal was released
if t - state['pedal_press_time'] < PEDAL_HOLD_MIN_DURATION:
# Within tap duration, handle like a normal key press
keyboard.setKeyDown(PEDAL_TAP_KEY)
state['pressed_keys'][PEDAL_TAP_KEY] = t + KeyDef.tap_duration
diagnostics.debug("Pedal Tap: {}".format(PEDAL_TAP_KEY))
else:
# Release the hold key, pressing it is handled separately
keyboard.setKeyUp(PEDAL_HOLD_KEY)
state['pedal_is_held'] = False
diagnostics.debug("Pedal Hold release: {}".format(PEDAL_HOLD_KEY))
state['pedal_press_time'] = None
elif payload.status == MidiStatus.ProgramChange:
# Program Change message from module changing kit
# TODO: Separately handle changing kit up vs down
keyboard.setKeyDown(PROGRAM_CHANGE_KEY)
state['pressed_keys'][PROGRAM_CHANGE_KEY] = t + KeyDef.tap_duration
diagnostics.debug("Key down {} [MIDI program change]".format(PROGRAM_CHANGE_KEY))
if not state['pedal_is_held'] and state['pedal_press_time'] and t - state['pedal_press_time'] > PEDAL_HOLD_MIN_DURATION:
# Pedal has been held down for longer than the tap threshold
keyboard.setKeyDown(PEDAL_HOLD_KEY)
state['pedal_is_held'] = True
diagnostics.debug("Pedal Hold on: {}".format(PEDAL_HOLD_KEY))
# Release any keys which have been pressed for long enough
for key, key_release_time in state['pressed_keys'].items():
if t >= key_release_time:
keyboard.setKeyUp(key)
diagnostics.debug("Key up {}".format(key))
del state['pressed_keys'][key]
if starting:
state = {
# Mapping of key: release_time
'pressed_keys': {},
# Time of the pedal being pressed, or None if not pressed
'pedal_press_time': None,
# Whether hold threshold for pedal has been reached
'pedal_is_held': False,
}
midi[MIDI_DEVICE_INDEX].update += update
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment