Skip to content

Commit 4ade90d

Browse files
committed
feat: add 'joystick2midi.py' example
Signed-off-by: Christopher Arndt <chris@chrisarndt.de>
1 parent ca228ea commit 4ade90d

1 file changed

Lines changed: 113 additions & 0 deletions

File tree

examples/joystick/joystick2midi.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#!/usr/bin/env python
2+
"""Convert joystick axis position into MIDI Control Change value in-/decrements."""
3+
4+
import os
5+
import sys
6+
7+
import pygame
8+
from rtmidi.midiutil import open_midioutput
9+
10+
11+
class Mapping:
12+
def __init__(self, cc=7, ch=0, inc=1, minval=0, maxval=127):
13+
self.cc = max(0, min(127, cc))
14+
self.ch = max(0, min(15, ch))
15+
self.minval = max(0, min(127, minval))
16+
self.maxval = max(0, min(127, maxval))
17+
self.inc = inc
18+
self.value = 0.0
19+
self.cc_value = -1
20+
21+
def update(self, value, threshold):
22+
if abs(value) >= threshold:
23+
self.value = max(self.minval, min(self.maxval, self.value + self.inc * value))
24+
25+
if int(self.value) != int(self.cc_value):
26+
self.cc_value = self.value
27+
return True
28+
29+
30+
# Configuration
31+
32+
# How fast the joystick events are polled.
33+
UPDATES_PER_SECOND = 20
34+
# Axis absolute value must exceed this threshold before controler value changes
35+
THRESHOLD = 0.3
36+
# Which axis maps to which control change
37+
CONFIG = {
38+
# Config for Axis 1
39+
1: Mapping(cc=11, inc=-0.3),
40+
# Config for Axis 0
41+
0: Mapping(cc=12),
42+
# etc...
43+
4: Mapping(cc=15, inc=-0.3),
44+
3: Mapping(cc=14),
45+
}
46+
47+
# We need to init the pygame.display module to use the event queue
48+
os.environ["SDL_VIDEODRIVER"] = "dummy"
49+
pygame.display.init()
50+
clock = pygame.time.Clock()
51+
52+
# Initialize the first joystick.
53+
pygame.joystick.init()
54+
55+
if pygame.joystick.get_count():
56+
joystick = pygame.joystick.Joystick(0)
57+
joystick.init()
58+
else:
59+
sys.exit("No joysticks found.")
60+
61+
62+
try:
63+
midiout, name = open_midioutput(sys.argv[1] if len(sys.argv) > 1 else None)
64+
except (EOFError, KeyboardInterrupt):
65+
sys.exit()
66+
else:
67+
print("Opened MIDI output {}.".format(name))
68+
69+
# Main Program Loop
70+
try:
71+
# Loop until the user clicks the close button.
72+
done = False
73+
active = []
74+
75+
while not done:
76+
# Possible joystick actions: JOYAXISMOTION, JOYBALLMOTION, JOYBUTTONDOWN,
77+
# JOYBUTTONUP, JOYHATMOTION
78+
for event in pygame.event.get(): # User did something.
79+
if event.type == pygame.QUIT: # If user clicked close.
80+
done = True # Flag that we are done so we exit this loop.
81+
elif event.type == pygame.JOYBUTTONUP:
82+
print("Joystick button {} released.".format(event.button))
83+
if event.button == 2: # 'X' button
84+
done = True
85+
print("Exit by button press.")
86+
elif event.type == pygame.JOYAXISMOTION:
87+
axis = event.axis
88+
if axis in CONFIG:
89+
if abs(event.value) >= THRESHOLD:
90+
active.append(axis)
91+
elif axis in active:
92+
active.remove(axis)
93+
94+
# we're polling instead of using the event queue,
95+
# so holding the joystick in one direction keeps updating the value
96+
for axis in active:
97+
mapping = CONFIG[axis]
98+
value = joystick.get_axis(axis)
99+
100+
#print("Axis {} value: {:>6.3f}".format(axis, value))
101+
102+
if mapping.update(value, THRESHOLD):
103+
msg = [0xB0 + mapping.ch, mapping.cc, int(mapping.value) & 0x7F]
104+
print("SEND: {!r}".format(msg))
105+
midiout.send_message(msg)
106+
107+
108+
# Limit poll rate to UPDATES_PER_SECOND.
109+
clock.tick(UPDATES_PER_SECOND)
110+
except KeyboardInterrupt:
111+
print("\nInterrupted.")
112+
113+
pygame.quit()

0 commit comments

Comments
 (0)