For use in Reapers super 8 looper. Experimented with using on release instead of on press (like in loopy pro) but the travel distance of this piano sustain pedal is too large to be accurate so I had to remove my double and triple tap functions, still awesome being able to double your midi inputs for 0$ was hoping to get all the functions mapped though.. looking at the m vave chocolate foot switch though looks pretty cool for 50$
import time
import rtmidi
from rtmidi.midiconstants import CONTROL_CHANGE
# Configuration - Customized for single pedal operation
SUSTAIN_CC = 64 # Default sustain pedal CC - Used for both functions
RECORD_TOGGLE_CC = 64 # Record/Play/Overdub (same as sustain)
CLEAR_LOOP_CC = 65 # Clear loop CC to be triggered by holding sustain
MIDI_CHANNEL = 0 # MIDI channel (0-15)
HOLD_THRESHOLD = 1.0 # Time in seconds required to hold sustain to clear loop
class LooperPedalController:
def __init__(self):
# Set up MIDI input/output
self.midi_in = rtmidi.MidiIn()
self.midi_out = rtmidi.MidiOut()
# State variables
self.is_recording = False
self.is_playing = False
self.pedal_down = False
self.press_start_time = 0
self.action_triggered = False
self.setup_midi()
def setup_midi(self):
# List available ports
in_ports = self.midi_in.get_ports()
out_ports = self.midi_out.get_ports()
print("Available MIDI input ports:")
for i, port in enumerate(in_ports):
print(f" {i}: {port}")
print("Available MIDI output ports:")
for i, port in enumerate(out_ports):
print(f" {i}: {port}")
# Let user select ports
in_port = int(input("Select MIDI input port number: "))
out_port = int(input("Select MIDI output port number: "))
# Open ports
self.midi_in.open_port(in_port)
self.midi_out.open_port(out_port)
# Set callback
self.midi_in.set_callback(self.midi_callback)
def midi_callback(self, event, data=None):
message, delta_time = event
# Process Control Change messages for the sustain pedal
if len(message) == 3 and message[0] == CONTROL_CHANGE | MIDI_CHANNEL and message[1] == SUSTAIN_CC:
value = message[2]
current_time = time.time()
if value >= 64: # Pedal down
self.handle_pedal_down(current_time)
else: # Pedal up
self.handle_pedal_up(current_time)
def handle_pedal_down(self, current_time):
# Record when the pedal was pressed
self.press_start_time = current_time
self.pedal_down = True
self.action_triggered = False
def handle_pedal_up(self, current_time):
duration = current_time - self.press_start_time
self.pedal_down = False
# If action was already triggered (by hold), do nothing more
if self.action_triggered:
return
# If pedal was pressed briefly (not held), trigger record/play/overdub
if duration < HOLD_THRESHOLD:
self.handle_record_toggle()
def handle_record_toggle(self):
# Toggle between record, play, and overdub
print("Quick press - Record/Play/Overdub toggle")
self.send_cc(RECORD_TOGGLE_CC, 127)
time.sleep(0.05)
self.send_cc(RECORD_TOGGLE_CC, 0)
# Update internal state (for display purposes)
if not self.is_recording and not self.is_playing:
self.is_recording = True
print("State: Recording")
elif self.is_recording:
self.is_recording = False
self.is_playing = True
print("State: Playing")
else:
self.is_recording = True
print("State: Overdubbing")
def handle_clear_loop(self):
print("Hold detected - Clearing loop (sending CC 65)")
self.send_cc(CLEAR_LOOP_CC, 127)
time.sleep(0.05)
self.send_cc(CLEAR_LOOP_CC, 0)
# Update internal state
self.is_recording = False
self.is_playing = False
print("State: Cleared")
self.action_triggered = True
def send_cc(self, cc_number, value):
message = [CONTROL_CHANGE | MIDI_CHANNEL, cc_number, value]
self.midi_out.send_message(message)
def run(self):
print("\n=== Single Pedal Looper Controller ===")
print(f"- Quick press CC {SUSTAIN_CC}: Record/Play/Overdub")
print(f"- Hold CC {SUSTAIN_CC} for {HOLD_THRESHOLD}+ sec: Clear loop (sends CC {CLEAR_LOOP_CC})")
print("=====================================\n")
print("Current state: Stopped")
try:
# Keep the script running
while True:
time.sleep(0.05) # Check frequently for responsive hold detection
# Check for hold while pedal is down
if self.pedal_down and not self.action_triggered:
current_time = time.time()
if current_time - self.press_start_time >= HOLD_THRESHOLD:
self.handle_clear_loop()
except KeyboardInterrupt:
print("Exiting...")
if __name__ == "__main__":
controller = LooperPedalController()
controller.run()
No comments:
Post a Comment