Last active
May 13, 2024 12:42
-
-
Save ksamuel/49365b74bf6c87588bb5c93db7b4e5d3 to your computer and use it in GitHub Desktop.
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
import os | |
import queue | |
import re | |
import time | |
import mido | |
import requests | |
# I don't even remember why it's there. Permission issue I worked | |
# around? Maybe I replaced that with an udev rules? | |
os.geteuid = lambda: print("nope") or 0 | |
CONTROL_MAPPING = { | |
9: "K2", | |
20: "F1", | |
21: "F2", | |
28: "K1", | |
} | |
NOTE_MAPPING = { | |
22: "Pad_12", | |
23: "Pad_13", | |
24: "Pad_14", | |
25: "Pad_15", | |
26: "Pad_16", | |
11: "Pad_1", | |
12: "Pad_2", | |
13: "Pad_3", | |
14: "Pad_4", | |
15: "Pad_5", | |
16: "Pad_6", | |
17: "Pad_7", | |
18: "Pad_8", | |
19: "Pad_9", | |
20: "Pad_10", | |
21: "Pad_11", | |
} | |
def messagereceived(key, value): | |
print(key, value) | |
try: | |
requests.post(f"http://localhost:8000/relay/?key={key}&value={value}") | |
except Exception as e: | |
print("error", e) | |
pass | |
# There is zero reason for a class to be used here. I probably | |
# had the grand delusion of making it pluggable. | |
class Controller: | |
def __init__(self): | |
self.midi_backend = mido.Backend("alsa_midi.mido_backend") | |
def listen( | |
self, | |
device_name: str, | |
# Yeah, definitely tried to make that generic. | |
device_name_is_regex: bool, | |
first_discovery_timeout: int = 30, | |
): | |
# Seems I loop trying to find the device, which is likely | |
# to allow for plugging and unplugging it | |
error = "" | |
for i in range(first_discovery_timeout * 2): | |
try: | |
device = self.find_device(device_name, device_name_is_regex) | |
break | |
except SystemError as e: | |
if not i: | |
print("Waiting for device...") | |
error = str(e) | |
time.sleep(0.5) | |
else: | |
# We can't find a device, no point in holding to life | |
raise SystemExit(str(error)) | |
port = self.midi_backend.open_input(device) | |
# That's an event loop folks. Don't let people tell you otherwise. | |
while True: | |
try: | |
if self.is_device_connected(device_name, device_name_is_regex): | |
with self.midi_backend.open_input(device) as port: | |
print("Device connected:", device_name) | |
while self.is_device_connected( | |
device_name, device_name_is_regex | |
): | |
try: | |
for message in port.iter_pending(): | |
if message.type == "note_on": | |
print(message.note) | |
name = NOTE_MAPPING.get(message.note, "Unknown") | |
messagereceived(name, message.velocity) | |
elif message.type == "control_change": | |
name = CONTROL_MAPPING.get( | |
message.control, "Unknown" | |
) | |
messagereceived(name, message.value) | |
except queue.Empty: | |
time.sleep(0.1) | |
# unplug tolerance again, and beautiful black whole for any other source | |
# of error | |
except OSError: | |
pass | |
print("Device disconnected", name) | |
print("Waiting for device...") | |
time.sleep(0.5) | |
def list_devices(self): | |
return self.midi_backend.get_input_names() | |
def find_device(self, name, regex=False): | |
# I don't even think I own another midi device, but hey | |
inputs = self.list_devices() | |
if not regex: | |
matches = [n for n in inputs if name.lower() in n.lower()] | |
else: | |
matches = [n for n in inputs if re.search(name, n)] | |
if not matches: | |
raise SystemError(f"No device matching '{name}' found") | |
if len(matches) > 1: | |
raise SystemError(f"{len(matches)} devices matching '{name}' found") | |
return matches[0] | |
def is_device_connected(self, name, regex=False): | |
try: | |
return self.find_device(name, regex) | |
# When you catch SystemError, you know you reached the bottom | |
except SystemError: | |
return False |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment