Pages

Friday, February 21, 2025

python to send midi ccs using usb snes gampad

super 8 preset and logitech f310 to cc mapping

instead of starting on cc 64 I started mapping from cc65 so I could use my piano sustain pedal to trigger loops and use the dpad to select the loops.
x doubles, a halves the length.
the middle guide (logitech) button dumps the loops to the timeline (add to project)
L button kills all the loops

Originally I did it with this 2$ ali express gamepad, later I wanted to get it working with this logitech ps1 style controller that has more buttons (more possibilities).

dropbox link to snes-midicc.py

Using It within Super 8

my super 8 cc mapping

import pygame
import rtmidi
import time
import sys

def initialize_midi():
    midi_out = rtmidi.MidiOut()
    available_ports = midi_out.get_ports()
    
    if not available_ports:
        print("No MIDI output ports found. Creating virtual port 'Gamepad MIDI'...")
        midi_out.open_virtual_port("Gamepad MIDI")
    else:
        print("Available MIDI ports:", available_ports)
        midi_out.open_port(0)
        print(f"Connected to: {available_ports[0]}")
    
    return midi_out

def initialize_joystick():
    pygame.init()
    pygame.joystick.init()
    
    if pygame.joystick.get_count() == 0:
        print("No gamepad detected!")
        sys.exit(1)
    
    joystick = pygame.joystick.Joystick(0)
    joystick.init()
    print(f"Detected gamepad: {joystick.get_name()}")
    return joystick

def main():
    midi_out = initialize_midi()
    joystick = initialize_joystick()
    
    button_states = {
        'left': False,
        'right': False,
        'up': False,
        'down': False
    }
    
    prev_buttons = [False] * joystick.get_numbuttons()
    
    print("Gamepad to MIDI mapper running. Press Ctrl+C to exit.")
    
    try:
        while True:
            for event in pygame.event.get():
                if event.type == pygame.JOYAXISMOTION:
                    if event.axis == 0:  # Left/Right
                        if event.value < -0.5 and not button_states['left']:
                            midi_out.send_message([0xB0, 64, 127])
                            button_states['left'] = True
                        elif event.value > -0.5 and button_states['left']:
                            midi_out.send_message([0xB0, 64, 0])
                            button_states['left'] = False
                            
                        if event.value > 0.5 and not button_states['right']:
                            midi_out.send_message([0xB0, 66, 127])
                            button_states['right'] = True
                        elif event.value < 0.5 and button_states['right']:
                            midi_out.send_message([0xB0, 66, 0])
                            button_states['right'] = False
                            
                    elif event.axis == 1:  # Up/Down
                        if event.value < -0.5 and not button_states['up']:
                            midi_out.send_message([0xB0, 65, 127])
                            button_states['up'] = True
                        elif event.value > -0.5 and button_states['up']:
                            midi_out.send_message([0xB0, 65, 0])
                            button_states['up'] = False
                            
                        if event.value > 0.5 and not button_states['down']:
                            midi_out.send_message([0xB0, 67, 127])
                            button_states['down'] = True
                        elif event.value < 0.5 and button_states['down']:
                            midi_out.send_message([0xB0, 67, 0])
                            button_states['down'] = False
                
                elif event.type == pygame.JOYBUTTONDOWN:
                    button_num = event.button
                    cc_number = 68 + button_num
                    midi_out.send_message([0xB0, cc_number, 127])
                    prev_buttons[button_num] = True
                    
                elif event.type == pygame.JOYBUTTONUP:
                    button_num = event.button
                    cc_number = 68 + button_num
                    midi_out.send_message([0xB0, cc_number, 0])
                    prev_buttons[button_num] = False
                    
            time.sleep(0.001)
            
    except KeyboardInterrupt:
        print("\nExiting...")
    finally:
        midi_out.close_port()
        pygame.quit()

if __name__ == "__main__":
    main()

original attempt

starts from cc64 and up
blue X = cc69
red A = cc70
yellow b = cc71
green y = cc72
select = cc77
start = cc78
Lt = cc73
Rt = cc74
up/down = 0/ 126
left/right = 0/126
I want to fix this so each direction sends on its own cc as momentary but this is fine for now


import pygame
import mido

MIDI_CC_START = 64

# Initialize MIDI output
midi_out = mido.open_output(mido.get_output_names()[0])

def send_midi_cc(cc, value):
    msg = mido.Message('control_change', control=cc, value=value)
    midi_out.send(msg)

def main():
    pygame.init()
    pygame.joystick.init()
    
    if pygame.joystick.get_count() == 0:
        print("No gamepad detected.")
        return

    joystick = pygame.joystick.Joystick(0)
    joystick.init()
    
    print(f"Gamepad detected: {joystick.get_name()}")
    print("Press Ctrl+C to exit.")
    
    prev_axes = [0] * joystick.get_numaxes()
    prev_buttons = [0] * joystick.get_numbuttons()
    prev_hats = [(0, 0)] * joystick.get_numhats()
    
    try:
        while True:
            pygame.event.pump()
            
            # Read axis values
            axes = [joystick.get_axis(i) for i in range(joystick.get_numaxes())]
            for i, value in enumerate(axes):
                if abs(value) > 0.1 and abs(value - prev_axes[i]) > 0.01:
                    send_midi_cc(MIDI_CC_START + i, int((value + 1) / 2 * 127))  # Normalize to 0-127
                prev_axes[i] = value
            
            # Read button states
            buttons = [joystick.get_button(i) for i in range(joystick.get_numbuttons())]
            for i, pressed in enumerate(buttons):
                cc_number = MIDI_CC_START + 5 + i  # Buttons start after D-pad CCs
                if pressed and not prev_buttons[i]:
                    send_midi_cc(cc_number, 127)
                elif not pressed and prev_buttons[i]:
                    send_midi_cc(cc_number, 0)
                prev_buttons[i] = pressed
            
            # Read hat (D-pad) states (momentary, dedicated CCs)
            hats = [joystick.get_hat(i) for i in range(joystick.get_numhats())]
            for i, hat in enumerate(hats):
                left_cc = 64
                up_cc = 65
                right_cc = 66
                down_cc = 67
                
                if hat[0] == -1 and prev_hats[i][0] != -1:
                    send_midi_cc(left_cc, 127)
                elif hat[0] != -1 and prev_hats[i][0] == -1:
                    send_midi_cc(left_cc, 0)
                
                if hat[1] == 1 and prev_hats[i][1] != 1:
                    send_midi_cc(up_cc, 127)
                elif hat[1] != 1 and prev_hats[i][1] == 1:
                    send_midi_cc(up_cc, 0)
                
                if hat[0] == 1 and prev_hats[i][0] != 1:
                    send_midi_cc(right_cc, 127)
                elif hat[0] != 1 and prev_hats[i][0] == 1:
                    send_midi_cc(right_cc, 0)
                
                if hat[1] == -1 and prev_hats[i][1] != -1:
                    send_midi_cc(down_cc, 127)
                elif hat[1] != -1 and prev_hats[i][1] == -1:
                    send_midi_cc(down_cc, 0)
                
                prev_hats[i] = hat
            
            pygame.time.wait(100)  # Reduce CPU usage
    except KeyboardInterrupt:
        print("\nExiting...")
    finally:
        joystick.quit()
        pygame.quit()
        midi_out.close()

if __name__ == "__main__":
    main()

No comments:

Post a Comment