diff --git a/main.py b/main.py index 2b12076..abf606e 100644 --- a/main.py +++ b/main.py @@ -497,6 +497,62 @@ textedit_osc_out_log.setStyleSheet(""" } """) +# Container: container_midi_in_log +container_midi_in_log = QWidget(None, Qt.WindowType.Window) +container_midi_in_log.setWindowTitle("MIDI In — Incoming") +container_midi_in_log.setStyleSheet("background-color: #1a1a1a;") +container_midi_in_log.setFixedWidth(360) + +vstack_midi_in_log = QVBoxLayout(container_midi_in_log) +vstack_midi_in_log.setContentsMargins(6, 6, 6, 6) +vstack_midi_in_log.setSpacing(4) + +hstack_midi_in_log_header = QHBoxLayout() +label_midi_in_log_title = QLabel("MIDI In — Incoming") +label_midi_in_log_title.setStyleSheet("color: #ff8c00; font-weight: bold; font-size: 11px;") +button_midi_in_clear = QPushButton("Clear") +button_midi_in_clear.setFixedSize(45, 18) + +textedit_midi_in_log = QTextEdit() +textedit_midi_in_log.setReadOnly(True) +textedit_midi_in_log.setStyleSheet(""" + QTextEdit { + background-color: #111111; + color: #ff8c00; + font-family: monospace; + font-size: 10px; + border: none; + } +""") + +# Container: container_midi_out_log +container_midi_out_log = QWidget(None, Qt.WindowType.Window) +container_midi_out_log.setWindowTitle("MIDI Out — Outgoing") +container_midi_out_log.setStyleSheet("background-color: #1a1a1a;") +container_midi_out_log.setFixedWidth(360) + +vstack_midi_out_log = QVBoxLayout(container_midi_out_log) +vstack_midi_out_log.setContentsMargins(6, 6, 6, 6) +vstack_midi_out_log.setSpacing(4) + +hstack_midi_out_log_header = QHBoxLayout() +label_midi_out_log_title = QLabel("MIDI Out — Outgoing") +label_midi_out_log_title.setStyleSheet("color: #00bcd4; font-weight: bold; font-size: 11px;") +button_midi_out_clear = QPushButton("Clear") +button_midi_out_clear.setFixedSize(45, 18) + +textedit_midi_out_log = QTextEdit() +textedit_midi_out_log.setReadOnly(True) +textedit_midi_out_log.setStyleSheet(""" + QTextEdit { + background-color: #111111; + color: #00bcd4; + font-family: monospace; + font-size: 10px; + border: none; + } +""") + # MARK: PRESET BROWSER ROW MODULE label_preset_browser_title = QLabel("Preset Browser") hstack_preset_browser = QHBoxLayout() @@ -986,7 +1042,25 @@ class MainWindow(QMainWindow): button_osc_out_clear.clicked.connect(lambda: textedit_osc_out_log.clear()) container_osc_out_log.setVisible(False) - + + #MARK: MIDI IN LOG CONTAINER + hstack_midi_in_log_header.addWidget(label_midi_in_log_title) + hstack_midi_in_log_header.addStretch() + hstack_midi_in_log_header.addWidget(button_midi_in_clear) + vstack_midi_in_log.addLayout(hstack_midi_in_log_header) + vstack_midi_in_log.addWidget(textedit_midi_in_log) + button_midi_in_clear.clicked.connect(lambda: textedit_midi_in_log.clear()) + container_midi_in_log.setVisible(False) + + #MARK: MIDI OUT LOG CONTAINER + hstack_midi_out_log_header.addWidget(label_midi_out_log_title) + hstack_midi_out_log_header.addStretch() + hstack_midi_out_log_header.addWidget(button_midi_out_clear) + vstack_midi_out_log.addLayout(hstack_midi_out_log_header) + vstack_midi_out_log.addWidget(textedit_midi_out_log) + button_midi_out_clear.clicked.connect(lambda: textedit_midi_out_log.clear()) + container_midi_out_log.setVisible(False) + main_layout.addWidget(make_separator()) # MARK: TRANSPORT ROW CONTROLS @@ -1185,7 +1259,9 @@ class MainWindow(QMainWindow): check_midi_port_availability() midi_receiver.midi_in_signal.connect(lambda msg: make_activity_blink(activity_indicator_midi_in)) - midi_receiver.midi_out_signal.connect(lambda: make_activity_blink(activity_indicator_midi_out)) + midi_receiver.midi_in_signal.connect(log_midi_incoming) + midi_receiver.midi_out_signal.connect(lambda _: make_activity_blink(activity_indicator_midi_out)) + midi_receiver.midi_out_signal.connect(log_midi_outgoing) midi_receiver.transport_out_signal.connect(lambda: make_activity_blink(activity_indicator_transport_out)) midi_receiver.hw_cc_signal.connect(midi_message_route_hw_to_output_cc) @@ -1357,34 +1433,46 @@ def set_port_transport_output(port_name): check_midi_port_availability() save_io_config() +def _append_log(textedit, line): + textedit.append(line) + if textedit.document().blockCount() > 200: + cursor = textedit.textCursor() + cursor.movePosition(cursor.MoveOperation.Start) + cursor.select(cursor.SelectionType.LineUnderCursor) + cursor.removeSelectedText() + cursor.deleteChar() + +def _midi_msg_str(msg): + if len(msg) >= 3: + status = msg[0] & 0xF0 + ch = (msg[0] & 0x0F) + 1 + if status == 0xB0: + return f"CC{msg[1]} CH{ch} [{msg[2]}]" + return " ".join(f"{b:02X}" for b in msg) + +def log_midi_incoming(msg): + from datetime import datetime + ts = datetime.now().strftime("%H:%M:%S") + line = f"{ts} {_midi_msg_str(msg)}" + _append_log(textedit_midi_in_log, line) + +def log_midi_outgoing(msg): + from datetime import datetime + ts = datetime.now().strftime("%H:%M:%S") + line = f"{ts} {_midi_msg_str(msg)}" + _append_log(textedit_midi_out_log, line) + def log_osc_incoming(addr, val): from datetime import datetime ts = datetime.now().strftime("%H:%M:%S") line = f"{ts} {addr} {val}" - textedit_osc_in_log.append(line) - doc = textedit_osc_in_log.document() - if doc.blockCount() > 200: - cursor = textedit_osc_in_log.textCursor() - cursor.movePosition(cursor.MoveOperation.Start) - cursor.select(cursor.SelectionType.LineUnderCursor) - cursor.removeSelectedText() - cursor.deleteChar() + _append_log(textedit_osc_in_log, line) def log_osc_outgoing(addr, val): - from datetime import datetime - ts = datetime.now().strftime("%H:%M:%S") - line = f"{ts} {addr} {val}" - textedit_osc_out_log.append(line) - doc = textedit_osc_out_log.document() - if doc.blockCount() > 200: - cursor = textedit_osc_out_log.textCursor() - cursor.movePosition(cursor.MoveOperation.Start) - cursor.select(cursor.SelectionType.LineUnderCursor) - cursor.removeSelectedText() - cursor.deleteChar() + _append_log(textedit_osc_out_log, line) def update_osc_server_ip(ip): set_osc_target(ip=ip.strip() or "127.0.0.1") @@ -2191,18 +2279,29 @@ def midi_message_route_hw_to_output_cc(fader_uid, value): def log_ui_show_hide(checked): container_osc_in_log.setVisible(checked) container_osc_out_log.setVisible(checked) + container_midi_in_log.setVisible(checked) + container_midi_out_log.setVisible(checked) log_ui_window_calc() def log_ui_window_calc(): geo = app_window.frameGeometry() h = geo.height() - container_osc_in_log.setFixedHeight(h) + half = h // 2 - 2 + # Left column: OSC In (top) + OSC Out (bottom) + container_osc_in_log.setFixedWidth(360) + container_osc_in_log.setFixedHeight(half) container_osc_in_log.move(geo.right() + 4, geo.top()) container_osc_out_log.setFixedWidth(360) - container_osc_out_log.setFixedHeight(h // 2) - container_osc_out_log.move(geo.right() + 4, geo.top() + h // 2 + 4) - container_osc_in_log.setFixedHeight(h // 2 - 4) + container_osc_out_log.setFixedHeight(half) + container_osc_out_log.move(geo.right() + 4, geo.top() + half + 4) + # Right column: MIDI In (top) + MIDI Out (bottom) + container_midi_in_log.setFixedWidth(360) + container_midi_in_log.setFixedHeight(half) + container_midi_in_log.move(geo.right() + 368, geo.top()) + container_midi_out_log.setFixedWidth(360) + container_midi_out_log.setFixedHeight(half) + container_midi_out_log.move(geo.right() + 368, geo.top() + half + 4) def start_osc_server(port=9000): global _osc_server, _osc_thread diff --git a/midi_receiver.py b/midi_receiver.py index eff0c72..71e41ad 100644 --- a/midi_receiver.py +++ b/midi_receiver.py @@ -2,7 +2,7 @@ from PyQt6.QtCore import QObject, pyqtSignal class MidiReceiver(QObject): midi_in_signal = pyqtSignal(list) - midi_out_signal = pyqtSignal() + midi_out_signal = pyqtSignal(list) transport_out_signal = pyqtSignal() hw_cc_signal = pyqtSignal(str, int) diff --git a/midi_sender.py b/midi_sender.py index 8dfd305..1feb6b4 100644 --- a/midi_sender.py +++ b/midi_sender.py @@ -14,12 +14,14 @@ print("Available MIDI ports:", available_ports) def send_cc(cc_number, value, channel=None): ch = (channel - 1) if channel is not None else MIDI_CH + msg = [0xB0 | ch, cc_number, value] if midi_out.is_port_open(): - midi_out.send_message([0xB0 | ch, cc_number, value]) - midi_receiver.midi_out_signal.emit() + midi_out.send_message(msg) + midi_receiver.midi_out_signal.emit(msg) def send_transport_cc(cc_number, value): + msg = [0xB0 | TRANSPORT_MIDI_CH, cc_number, value] if transport_midi_out.is_port_open(): - transport_midi_out.send_message([0xB0 | TRANSPORT_MIDI_CH, cc_number, value]) - midi_receiver.transport_out_signal.emit() \ No newline at end of file + transport_midi_out.send_message(msg) + midi_receiver.midi_out_signal.emit(msg) \ No newline at end of file diff --git a/presets/presets.json b/presets/presets.json index 938f502..956ba9d 100644 --- a/presets/presets.json +++ b/presets/presets.json @@ -483,8 +483,8 @@ ], "transport_show_hide": false, "io_config": { - "midi_out": "Not Connected", - "midi_in": "VMPK Output", + "midi_out": "IAC Driver Bus 1", + "midi_in": "Not Connected", "transport_out": "Not Connected", "osc_ip": "127.0.0.1", "osc_listen_port": 9000,