From c5c555aba4742769842a0152c4097c06a2dda3cf Mon Sep 17 00:00:00 2001 From: Tobias Ellinghaus Date: Thu, 9 Apr 2020 17:01:40 +0200 Subject: [PATCH 1/8] Fix WebSocketApp callbacks The callbacks need to be wrapped, otherwise they either don't get called at all or with the wrong arguments --- main.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) mode change 100644 => 100755 main.py diff --git a/main.py b/main.py old mode 100644 new mode 100755 index 01ff006..7dab9dc --- a/main.py +++ b/main.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 from __future__ import division from websocket import WebSocketApp from tinydb import TinyDB @@ -136,10 +137,10 @@ def __init__(self, config_path="config.json", ws_server="localhost", ws_port=444 # setting up a Websocket client self.log.debug("Attempting to connect to OBS using websocket protocol") self.obs_socket = WebSocketApp("ws://%s:%d" % (ws_server, ws_port)) - self.obs_socket.on_message = self.handle_obs_message - self.obs_socket.on_error = self.handle_obs_error - self.obs_socket.on_close = self.handle_obs_close - self.obs_socket.on_open = self.handle_obs_open + self.obs_socket.on_message = lambda ws, message: self.handle_obs_message(ws, message) + self.obs_socket.on_error = lambda ws, error: self.handle_obs_error(ws, error) + self.obs_socket.on_close = lambda ws: self.handle_obs_close(ws) + self.obs_socket.on_open = lambda ws: self.handle_obs_open(ws) def handle_midi_input(self, message, deviceID, deviceName): self.log.debug("Received %s %s %s %s %s", str(message), "from device", deviceID, "/", deviceName) @@ -201,7 +202,7 @@ def handle_midi_fader(self, deviceID, control, value): if command == "SetSourceRotation" or command == "SetTransitionDuration" or command == "SetSyncOffset" or command == "SetSourcePosition": self.obs_socket.send(action % int(scaled)) - def handle_obs_message(self, message): + def handle_obs_message(self, ws, message): self.log.debug("Received new message from OBS") payload = json.loads(message) @@ -259,6 +260,7 @@ def handle_obs_error(self, ws, error=None): def handle_obs_close(self, ws): self.log.error("OBS has disconnected, timed out or isn't running") self.log.error("Please reopen OBS and restart the script") + self.close(teardown=False) def handle_obs_open(self, ws): self.log.info("Successfully connected to OBS") From f138cb8b4101f9847bcaacb7194351c2a95cc1a7 Mon Sep 17 00:00:00 2001 From: Tobias Ellinghaus Date: Thu, 9 Apr 2020 17:04:23 +0200 Subject: [PATCH 2/8] React to OBS state changes with MIDI messages This will add a "bidirectional" option for buttons that change the current or preview scene. When we see an update from OBS that the scene changed we try to inform the MIDI controls that are associated with that scene about it if it's bidirectional. In other words: If your scene changing button has an LED it will turn on/off. I tested this with a KORG nanoKONTROL2 where it works beautifully. --- main.py | 32 ++++++++++++++++++++++++++++++-- setup.py | 20 +++++++++++++------- 2 files changed, 43 insertions(+), 9 deletions(-) mode change 100644 => 100755 setup.py diff --git a/main.py b/main.py index 7dab9dc..e62e55a 100755 --- a/main.py +++ b/main.py @@ -29,6 +29,16 @@ "message-id": "%d", "sourceName": "%s", "filterName": "%s" +}""", +"SetCurrentScene": """{ + "request-type": "GetCurrentScene", + "message-id": "%d", + "_unused": "%s" +}""", +"SetPreviewScene": """{ + "request-type": "GetPreviewScene", + "message-id": "%d", + "_unused": "%s" }""" } @@ -63,7 +73,7 @@ def __init__(self, device, deviceid): try: self.log.debug("Attempting to open midi port `%s`" % self._devicename) - self._port = mido.open_input(name=self._devicename, callback=self.callback) + self._port = mido.open_ioport(name=self._devicename, callback=self.callback, autoreset=True) except: self.log.critical("\nCould not open", self._devicename) self.log.critical("The midi device might be used by another application/not plugged in/have a different name.") @@ -126,7 +136,7 @@ def __init__(self, config_path="config.json", ws_server="localhost", ws_port=444 self.log.debug("Retrieved MIDI port name(s) `%s`" % result) #create new class with handler and open from there, just create new instances for device in result: - self._portobjects.append(DeviceHandler(device, device.doc_id)) + self._portobjects.append((DeviceHandler(device, device.doc_id), device.doc_id)) self.log.info("Successfully initialized midi port(s)") del result @@ -142,6 +152,11 @@ def __init__(self, config_path="config.json", ws_server="localhost", ws_port=444 self.obs_socket.on_close = lambda ws: self.handle_obs_close(ws) self.obs_socket.on_open = lambda ws: self.handle_obs_open(ws) + def getPortObjectFromDeviceID(self, deviceID): + for portobject, _deviceID in self._portobjects: + if _deviceID == deviceID: + return portobject + def handle_midi_input(self, message, deviceID, deviceName): self.log.debug("Received %s %s %s %s %s", str(message), "from device", deviceID, "/", deviceName) @@ -265,6 +280,10 @@ def handle_obs_close(self, ws): def handle_obs_open(self, ws): self.log.info("Successfully connected to OBS") + # initialize bidirectional controls + self.send_action({"action": 'GetCurrentScene', "request": "SetCurrentScene", "target": ":-)"}) + self.send_action({"action": 'GetPreviewScene', "request": "SetPreviewScene", "target": ":-)"}) + def send_action(self, action_request): action = action_request.get("action") if not action: @@ -310,6 +329,15 @@ def start(self): self.obs_socket.run_forever() def close(self, teardown=False): + # set bidirectional controls to their 0 state (i.e., turn off LEDs) + self.log.debug("Attempting to turn off bidirectional controls") + result = self.mappingdb.getmany(self.mappingdb.find('bidirectional == 1')) + if result: + for row in result: + portobject = self.getPortObjectFromDeviceID(row["deviceID"]) + if portobject: + portobject._port.send(mido.Message(row["msg_type"], channel=0, control=row["msgNoC"], value=0)) + self.log.debug("Attempting to close midi port(s)") result = self.devdb.all() for device in result: diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index 3e238b7..d553e5d --- a/setup.py +++ b/setup.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 import mido, threading, sys, atexit, json, time, signal from tinydb import TinyDB, Query from websocket import create_connection @@ -247,13 +248,15 @@ def setupButtonEvents(action, NoC, msgType, deviceID): if action == "SetCurrentScene": updateSceneList() scene = printArraySelect(sceneListShort) + bidirectional = askForBidirectional() action = jsonArchive["SetCurrentScene"] % scene - saveButtonToFile(msgType, NoC, "button" , action, deviceID) + saveButtonToFile(msgType, NoC, "button" , action, deviceID, bidirectional) elif action == "SetPreviewScene": updateSceneList() scene = printArraySelect(sceneListShort) + bidirectional = askForBidirectional() action = jsonArchive["SetPreviewScene"] % scene - saveButtonToFile(msgType, NoC, "button" , action, deviceID) + saveButtonToFile(msgType, NoC, "button" , action, deviceID, bidirectional) elif action == "TransitionToProgram": updateTransitionList() print("Please select a transition to be used:") @@ -524,15 +527,13 @@ def saveFaderToFile(msg_type, msgNoC, input_type, action, scale, cmd, deviceID): else: db.insert({"msg_type": msg_type, "msgNoC": msgNoC, "input_type": input_type, "scale_low": scale[0], "scale_high": scale[1], "action": action, "cmd": cmd, "deviceID": deviceID}) -def saveButtonToFile(msg_type, msgNoC, input_type, action, deviceID): - print("Saved %s with note/control %s for action %s on device %s" % (msg_type, msgNoC, action, deviceID)) +def saveButtonToFile(msg_type, msgNoC, input_type, action, deviceID, bidirectional=False): + print("Saved %s with note/control %s for action %s on device %s, bidirectional: %d" % (msg_type, msgNoC, action, deviceID, bidirectional)) Search = Query() result = db.search((Search.msg_type == msg_type) & (Search.msgNoC == msgNoC) & (Search.deviceID == deviceID)) if result: db.remove((Search.msgNoC == msgNoC) & (Search.deviceID == deviceID)) - db.insert({"msg_type": msg_type, "msgNoC": msgNoC, "input_type": input_type, "action" : action, "deviceID": deviceID}) - else: - db.insert({"msg_type": msg_type, "msgNoC": msgNoC, "input_type": input_type, "action" : action, "deviceID": deviceID}) + db.insert({"msg_type": msg_type, "msgNoC": msgNoC, "input_type": input_type, "action" : action, "deviceID": deviceID, "bidirectional": bidirectional}) def saveTODOButtonToFile(msg_type, msgNoC, input_type, action, request, target, field2, deviceID): print("Saved %s with note/control %s for action %s on device %s" % (msg_type, msgNoC, action, deviceID)) @@ -560,6 +561,11 @@ def askForInputScaling(): high = int(input("Select higher output value: ")) return low, high +def askForBidirectional(): + print("Do you want the control to be bidirectional?\n1: Yes\n2: No") + bidirectional = int(input("Select 1 or 2: ")) + return bidirectional == 1 + def updateTransitionList(): global transitionList ws = create_connection("ws://" + serverIP + ":" + serverPort) From 9ecd097eb0030f68d2c50a56ee8e6811fb9e9044 Mon Sep 17 00:00:00 2001 From: Tobias Ellinghaus Date: Thu, 9 Apr 2020 17:52:08 +0200 Subject: [PATCH 3/8] Fix last commit It seems I had forgotten to add most of my changes. :-/ --- main.py | 85 +++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 30 deletions(-) diff --git a/main.py b/main.py index e62e55a..89e7302 100755 --- a/main.py +++ b/main.py @@ -227,39 +227,64 @@ def handle_obs_message(self, ws, message): self.log.error("OBS returned error: %s" % payload["error"]) return - message_id = payload["message-id"] + if "message-id" in payload: + message_id = payload["message-id"] - self.log.debug("Looking for action with message id `%s`" % message_id) - for action in self._action_buffer: - (buffered_id, template, kind) = action + self.log.debug("Looking for action with message id `%s`" % message_id) + for action in self._action_buffer: + (buffered_id, template, kind) = action - if buffered_id != int(payload["message-id"]): - continue + if buffered_id != int(payload["message-id"]): + continue - del buffered_id - self.log.info("Action `%s` was requested by OBS" % kind) - - if kind == "ToggleSourceVisibility": - # Dear lain, I so miss decent ternary operators... - invisible = "false" if payload["visible"] else "true" - self.obs_socket.send(template % invisible) - elif kind == "ReloadBrowserSource": - source = payload["sourceSettings"]["url"] - target = source[0:-1] if source[-1] == '#' else source + '#' - self.obs_socket.send(template % target) - elif kind == "ToggleSourceFilter": - invisible = "false" if payload["enabled"] else "true" - self.obs_socket.send(template % invisible) - - self.log.debug("Removing action with message id %s from buffer" % message_id) - self._action_buffer.remove(action) - break - - if message_id == "MIDItoOBSscreenshot": - if payload["status"] == "ok": - with open(str(time()) + ".png", "wb") as fh: - fh.write(base64.decodebytes(payload["img"][22:].encode())) - + del buffered_id + self.log.info("Action `%s` was requested by OBS" % kind) + + if kind == "ToggleSourceVisibility": + # Dear lain, I so miss decent ternary operators... + invisible = "false" if payload["visible"] else "true" + self.obs_socket.send(template % invisible) + elif kind == "ReloadBrowserSource": + source = payload["sourceSettings"]["url"] + target = source[0:-1] if source[-1] == '#' else source + '#' + self.obs_socket.send(template % target) + elif kind == "ToggleSourceFilter": + invisible = "false" if payload["enabled"] else "true" + self.obs_socket.send(template % invisible) + elif kind in ["SetCurrentScene", "SetPreviewScene"]: + self.sceneChanged(kind, payload["name"]) + + self.log.debug("Removing action with message id %s from buffer" % message_id) + self._action_buffer.remove(action) + break + + if message_id == "MIDItoOBSscreenshot": + if payload["status"] == "ok": + with open(str(time()) + ".png", "wb") as fh: + fh.write(base64.decodebytes(payload["img"][22:].encode())) + + elif "update-type" in payload: + update_type = payload["update-type"] + + request_types = {"PreviewSceneChanged": "SetPreviewScene", "SwitchScenes": "SetCurrentScene"} + if update_type in request_types: + scene_name = payload["scene-name"] + self.sceneChanged(request_types[update_type], scene_name) + + def sceneChanged(self, event_type, scene_name): + self.log.debug("Scene changed, event: %s, name: %s" % (event_type, scene_name)) + # only buttons can change the scene, so we can limit our search to those + results = self.mappingdb.getmany(self.mappingdb.find('input_type == "button" and bidirectional == 1')) + if not results: + return + for result in results: + j = json.loads(result["action"]) + if j["request-type"] != event_type: + continue + value = 127 if j["scene-name"] == scene_name else 0 + portobject = self.getPortObjectFromDeviceID(result["deviceID"]) + if portobject: + portobject._port.send(mido.Message(result["msg_type"], channel=0, control=result["msgNoC"], value=value)) def handle_obs_error(self, ws, error=None): # Protection against potential inconsistencies in `inspect.ismethod` From 06f91673d506f9819f39b96ce6f9fc9b7a28c4ad Mon Sep 17 00:00:00 2001 From: Tobias Ellinghaus Date: Fri, 10 Apr 2020 21:55:37 +0200 Subject: [PATCH 4/8] Check if device is IO capable before opening it --- main.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index 89e7302..e7f7dc3 100755 --- a/main.py +++ b/main.py @@ -73,11 +73,17 @@ def __init__(self, device, deviceid): try: self.log.debug("Attempting to open midi port `%s`" % self._devicename) - self._port = mido.open_ioport(name=self._devicename, callback=self.callback, autoreset=True) + if self._devicename in mido.get_ioport_names(): + self._port = mido.open_ioport(name=self._devicename, callback=self.callback, autoreset=True) + else: + self._port = mido.open_input(name=self._devicename, callback=self.callback) except: self.log.critical("\nCould not open", self._devicename) self.log.critical("The midi device might be used by another application/not plugged in/have a different name.") - self.log.critical("Please close the device in the other application/plug it in/select the rename option in the device management menu and restart this script.\n") + self.log.critical("Please close the device in the other application/plug it in/select the rename option in the device management menu and restart this script.") + self.log.critical("Currently connected devices:") + for name in mido.get_input_names(): + self.log.critical(" - %s" % name) # EIO 5 (Input/output error) exit(5) @@ -283,7 +289,7 @@ def sceneChanged(self, event_type, scene_name): continue value = 127 if j["scene-name"] == scene_name else 0 portobject = self.getPortObjectFromDeviceID(result["deviceID"]) - if portobject: + if portobject and portobject._port.is_output: portobject._port.send(mido.Message(result["msg_type"], channel=0, control=result["msgNoC"], value=value)) def handle_obs_error(self, ws, error=None): @@ -360,7 +366,7 @@ def close(self, teardown=False): if result: for row in result: portobject = self.getPortObjectFromDeviceID(row["deviceID"]) - if portobject: + if portobject and portobject._port.is_output: portobject._port.send(mido.Message(row["msg_type"], channel=0, control=row["msgNoC"], value=0)) self.log.debug("Attempting to close midi port(s)") From 52ed359a496cd4a29e4b1774db199faca0b7e92a Mon Sep 17 00:00:00 2001 From: Tobias Ellinghaus Date: Sat, 11 Apr 2020 16:41:20 +0200 Subject: [PATCH 5/8] Change shutdown sequence This should call self.close() exactly once, no matter if the program was terminated with ctrl-c or because OBS died. I am not 100% sure about this one. --- main.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/main.py b/main.py index e7f7dc3..b75be94 100755 --- a/main.py +++ b/main.py @@ -299,14 +299,13 @@ def handle_obs_error(self, ws, error=None): if isinstance(error, (KeyboardInterrupt, SystemExit)): self.log.info("Keyboard interrupt received, gracefully exiting...") - self.close(teardown=True) else: self.log.error("Websocket error: %" % str(error)) def handle_obs_close(self, ws): self.log.error("OBS has disconnected, timed out or isn't running") self.log.error("Please reopen OBS and restart the script") - self.close(teardown=False) + self.close(teardown=True) def handle_obs_open(self, ws): self.log.info("Successfully connected to OBS") From 2fc71245da0aa520a1d8bf55a511893177c26382 Mon Sep 17 00:00:00 2001 From: Tobias Ellinghaus Date: Sat, 11 Apr 2020 16:59:33 +0200 Subject: [PATCH 6/8] Separate in and out ports If the same midi device can be used for input and output, or if it's an ioport, then the "bidirectional" key in the config should be enough, if you have a separate device for output, then add it as a 2nd device and add "out_deviceID": to the config. It's not done in setup.py for you. --- main.py | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/main.py b/main.py index b75be94..c584bc8 100755 --- a/main.py +++ b/main.py @@ -69,14 +69,22 @@ def __init__(self, device, deviceid): self.log = get_logger("midi_to_obs_device") self._id = deviceid self._devicename = device["devicename"] - self._port = 0 + self._port_in = 0 + self._port_out = 0 try: self.log.debug("Attempting to open midi port `%s`" % self._devicename) + # a device can be input, output or ioport. in the latter case it can also be the other two + # so we first check if we can use it as an ioport if self._devicename in mido.get_ioport_names(): - self._port = mido.open_ioport(name=self._devicename, callback=self.callback, autoreset=True) + self._port_in = mido.open_ioport(name=self._devicename, callback=self.callback, autoreset=True) + self._port_out = self._port_in + # otherwise we try to use it separately as input and output else: - self._port = mido.open_input(name=self._devicename, callback=self.callback) + if self._devicename in mido.get_input_names(): + self._port_in = mido.open_input(name=self._devicename, callback=self.callback) + if self._devicename in mido.get_output_names(): + self._port_out = mido.open_output(name=self._devicename, callback=self.callback, autoreset=True) except: self.log.critical("\nCould not open", self._devicename) self.log.critical("The midi device might be used by another application/not plugged in/have a different name.") @@ -91,7 +99,13 @@ def callback(self, msg): handler.handle_midi_input(msg, self._id, self._devicename) def close(self): - self._port_close() + if self._port_in: + self._port_in.close() + # when it's an ioport we don't want to close the port twice + if self._port_out and self._port_out != self._port_in: + self._port_out.close() + self._port_in = 0 + self._port_out = 0 class MidiHandler: # Initializes the handler class @@ -158,7 +172,8 @@ def __init__(self, config_path="config.json", ws_server="localhost", ws_port=444 self.obs_socket.on_close = lambda ws: self.handle_obs_close(ws) self.obs_socket.on_open = lambda ws: self.handle_obs_open(ws) - def getPortObjectFromDeviceID(self, deviceID): + def getPortObject(self, mapping): + deviceID = mapping.get("out_deviceID", mapping["deviceID"]) for portobject, _deviceID in self._portobjects: if _deviceID == deviceID: return portobject @@ -288,9 +303,9 @@ def sceneChanged(self, event_type, scene_name): if j["request-type"] != event_type: continue value = 127 if j["scene-name"] == scene_name else 0 - portobject = self.getPortObjectFromDeviceID(result["deviceID"]) - if portobject and portobject._port.is_output: - portobject._port.send(mido.Message(result["msg_type"], channel=0, control=result["msgNoC"], value=value)) + portobject = self.getPortObject(result) + if portobject and portobject._port_out: + portobject._port_out.send(mido.Message(result["msg_type"], channel=0, control=result["msgNoC"], value=value)) def handle_obs_error(self, ws, error=None): # Protection against potential inconsistencies in `inspect.ismethod` @@ -364,14 +379,13 @@ def close(self, teardown=False): result = self.mappingdb.getmany(self.mappingdb.find('bidirectional == 1')) if result: for row in result: - portobject = self.getPortObjectFromDeviceID(row["deviceID"]) - if portobject and portobject._port.is_output: - portobject._port.send(mido.Message(row["msg_type"], channel=0, control=row["msgNoC"], value=0)) + portobject = self.getPortObject(row) + if portobject and portobject._port_out: + portobject._port_out.send(mido.Message(row["msg_type"], channel=0, control=row["msgNoC"], value=0)) self.log.debug("Attempting to close midi port(s)") - result = self.devdb.all() - for device in result: - device.close() + for portobject, _ in self._portobjects: + portobject.close() self.log.info("Midi connection has been closed successfully") From 27ae42f1611a587072a4f2f5ca66d4a8b6916c8f Mon Sep 17 00:00:00 2001 From: Tobias Ellinghaus Date: Sun, 12 Apr 2020 00:32:17 +0200 Subject: [PATCH 7/8] Bidirectional: Try to support X-Touch Mini ... ... or probably devices that are sending NOTE_ON in general. --- main.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/main.py b/main.py index c584bc8..d10b8c0 100755 --- a/main.py +++ b/main.py @@ -94,7 +94,7 @@ def __init__(self, device, deviceid): self.log.critical(" - %s" % name) # EIO 5 (Input/output error) exit(5) - + def callback(self, msg): handler.handle_midi_input(msg, self._id, self._devicename) @@ -144,7 +144,7 @@ def __init__(self, config_path="config.json", ws_server="localhost", ws_port=444 exit(2) self.log.debug("Successfully imported mapping database") - + result = tiny_devdb.all() if not result: self.log.critical("Config file %s doesn't exist or is damaged" % config_path) @@ -160,7 +160,7 @@ def __init__(self, config_path="config.json", ws_server="localhost", ws_port=444 self.log.info("Successfully initialized midi port(s)") del result - + # close tinydb tiny_database.close() @@ -302,10 +302,14 @@ def sceneChanged(self, event_type, scene_name): j = json.loads(result["action"]) if j["request-type"] != event_type: continue - value = 127 if j["scene-name"] == scene_name else 0 portobject = self.getPortObject(result) if portobject and portobject._port_out: - portobject._port_out.send(mido.Message(result["msg_type"], channel=0, control=result["msgNoC"], value=value)) + if result["msg_type"] == "control_change": + value = 127 if j["scene-name"] == scene_name else 0 + portobject._port_out.send(mido.Message(type="control_change", channel=0, control=result["msgNoC"], value=value)) + elif result["msg_type"] == "note_on": + velocity = 1 if j["scene-name"] == scene_name else 0 + portobject._port_out.send(mido.Message(type="note_on", channel=0, note=result["msgNoC"], velocity=velocity)) def handle_obs_error(self, ws, error=None): # Protection against potential inconsistencies in `inspect.ismethod` @@ -381,7 +385,10 @@ def close(self, teardown=False): for row in result: portobject = self.getPortObject(row) if portobject and portobject._port_out: - portobject._port_out.send(mido.Message(row["msg_type"], channel=0, control=row["msgNoC"], value=0)) + if row["msg_type"] == "control_change": + portobject._port_out.send(mido.Message(type="control_change", channel=0, control=row["msgNoC"], value=0)) + elif row["msg_type"] == "note_on": + portobject._port_out.send(mido.Message(type="note_on", channel=0, note=row["msgNoC"], velocity=0)) self.log.debug("Attempting to close midi port(s)") for portobject, _ in self._portobjects: From 0a63c68f60fd842c728f9c3890536a32df3bfed7 Mon Sep 17 00:00:00 2001 From: Tobias Ellinghaus Date: Sun, 12 Apr 2020 14:03:09 +0200 Subject: [PATCH 8/8] bidirectional: Add support for out_msgNoC in config Some MIDI devices like the X-Touch Mini have different notes for sending and receiving. In those cases you can specify the output note with out_msgNoC in your config.json. --- main.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index d10b8c0..2536917 100755 --- a/main.py +++ b/main.py @@ -302,14 +302,15 @@ def sceneChanged(self, event_type, scene_name): j = json.loads(result["action"]) if j["request-type"] != event_type: continue + msgNoC = result.get("out_msgNoC", result["msgNoC"]) portobject = self.getPortObject(result) if portobject and portobject._port_out: if result["msg_type"] == "control_change": value = 127 if j["scene-name"] == scene_name else 0 - portobject._port_out.send(mido.Message(type="control_change", channel=0, control=result["msgNoC"], value=value)) + portobject._port_out.send(mido.Message(type="control_change", channel=0, control=msgNoC, value=value)) elif result["msg_type"] == "note_on": velocity = 1 if j["scene-name"] == scene_name else 0 - portobject._port_out.send(mido.Message(type="note_on", channel=0, note=result["msgNoC"], velocity=velocity)) + portobject._port_out.send(mido.Message(type="note_on", channel=0, note=msgNoC, velocity=velocity)) def handle_obs_error(self, ws, error=None): # Protection against potential inconsistencies in `inspect.ismethod` @@ -383,12 +384,13 @@ def close(self, teardown=False): result = self.mappingdb.getmany(self.mappingdb.find('bidirectional == 1')) if result: for row in result: + msgNoC = row.get("out_msgNoC", row["msgNoC"]) portobject = self.getPortObject(row) if portobject and portobject._port_out: if row["msg_type"] == "control_change": - portobject._port_out.send(mido.Message(type="control_change", channel=0, control=row["msgNoC"], value=0)) + portobject._port_out.send(mido.Message(type="control_change", channel=0, control=msgNoC, value=0)) elif row["msg_type"] == "note_on": - portobject._port_out.send(mido.Message(type="note_on", channel=0, note=row["msgNoC"], velocity=0)) + portobject._port_out.send(mido.Message(type="note_on", channel=0, note=msgNoC, velocity=0)) self.log.debug("Attempting to close midi port(s)") for portobject, _ in self._portobjects: