-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add 'joystick2midi.py' example
Signed-off-by: Christopher Arndt <[email protected]>
- Loading branch information
1 parent
ca228ea
commit 4ade90d
Showing
1 changed file
with
113 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
#!/usr/bin/env python | ||
"""Convert joystick axis position into MIDI Control Change value in-/decrements.""" | ||
|
||
import os | ||
import sys | ||
|
||
import pygame | ||
from rtmidi.midiutil import open_midioutput | ||
|
||
|
||
class Mapping: | ||
def __init__(self, cc=7, ch=0, inc=1, minval=0, maxval=127): | ||
self.cc = max(0, min(127, cc)) | ||
self.ch = max(0, min(15, ch)) | ||
self.minval = max(0, min(127, minval)) | ||
self.maxval = max(0, min(127, maxval)) | ||
self.inc = inc | ||
self.value = 0.0 | ||
self.cc_value = -1 | ||
|
||
def update(self, value, threshold): | ||
if abs(value) >= threshold: | ||
self.value = max(self.minval, min(self.maxval, self.value + self.inc * value)) | ||
|
||
if int(self.value) != int(self.cc_value): | ||
self.cc_value = self.value | ||
return True | ||
|
||
|
||
# Configuration | ||
|
||
# How fast the joystick events are polled. | ||
UPDATES_PER_SECOND = 20 | ||
# Axis absolute value must exceed this threshold before controler value changes | ||
THRESHOLD = 0.3 | ||
# Which axis maps to which control change | ||
CONFIG = { | ||
# Config for Axis 1 | ||
1: Mapping(cc=11, inc=-0.3), | ||
# Config for Axis 0 | ||
0: Mapping(cc=12), | ||
# etc... | ||
4: Mapping(cc=15, inc=-0.3), | ||
3: Mapping(cc=14), | ||
} | ||
|
||
# We need to init the pygame.display module to use the event queue | ||
os.environ["SDL_VIDEODRIVER"] = "dummy" | ||
pygame.display.init() | ||
clock = pygame.time.Clock() | ||
|
||
# Initialize the first joystick. | ||
pygame.joystick.init() | ||
|
||
if pygame.joystick.get_count(): | ||
joystick = pygame.joystick.Joystick(0) | ||
joystick.init() | ||
else: | ||
sys.exit("No joysticks found.") | ||
|
||
|
||
try: | ||
midiout, name = open_midioutput(sys.argv[1] if len(sys.argv) > 1 else None) | ||
except (EOFError, KeyboardInterrupt): | ||
sys.exit() | ||
else: | ||
print("Opened MIDI output {}.".format(name)) | ||
|
||
# Main Program Loop | ||
try: | ||
# Loop until the user clicks the close button. | ||
done = False | ||
active = [] | ||
|
||
while not done: | ||
# Possible joystick actions: JOYAXISMOTION, JOYBALLMOTION, JOYBUTTONDOWN, | ||
# JOYBUTTONUP, JOYHATMOTION | ||
for event in pygame.event.get(): # User did something. | ||
if event.type == pygame.QUIT: # If user clicked close. | ||
done = True # Flag that we are done so we exit this loop. | ||
elif event.type == pygame.JOYBUTTONUP: | ||
print("Joystick button {} released.".format(event.button)) | ||
if event.button == 2: # 'X' button | ||
done = True | ||
print("Exit by button press.") | ||
elif event.type == pygame.JOYAXISMOTION: | ||
axis = event.axis | ||
if axis in CONFIG: | ||
if abs(event.value) >= THRESHOLD: | ||
active.append(axis) | ||
elif axis in active: | ||
active.remove(axis) | ||
|
||
# we're polling instead of using the event queue, | ||
# so holding the joystick in one direction keeps updating the value | ||
for axis in active: | ||
mapping = CONFIG[axis] | ||
value = joystick.get_axis(axis) | ||
|
||
#print("Axis {} value: {:>6.3f}".format(axis, value)) | ||
|
||
if mapping.update(value, THRESHOLD): | ||
msg = [0xB0 + mapping.ch, mapping.cc, int(mapping.value) & 0x7F] | ||
print("SEND: {!r}".format(msg)) | ||
midiout.send_message(msg) | ||
|
||
|
||
# Limit poll rate to UPDATES_PER_SECOND. | ||
clock.tick(UPDATES_PER_SECOND) | ||
except KeyboardInterrupt: | ||
print("\nInterrupted.") | ||
|
||
pygame.quit() |