diff --git a/main.py b/main.py index 5de6f0d..120b5fa 100644 --- a/main.py +++ b/main.py @@ -7,7 +7,7 @@ from PyQt6.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QSlider, QComboBox, QLabel, QPushButton, QSizePolicy, QSpinBox, QLineEdit, QInputDialog, QFrame, - QCheckBox + QCheckBox, QTextEdit ) from PyQt6.QtCore import Qt, QObject, pyqtSignal, QTimer, QEvent from PyQt6.QtGui import QAction, QColor @@ -192,14 +192,12 @@ hstack_io_row.setSpacing(16) -# ===== MODULE LEVEL ===== # REFACTOR MAP: Old names → New names # self.in_blink → activity_indicator_midi_in # self.in_status → label_connection_midi_in # self.in_combo → picker_midi_in # self.available_in_ports → available_ports_midi_in - # vstack_midi_in vstack_midi_in = QVBoxLayout() vstack_midi_in.setSpacing(2) @@ -225,8 +223,439 @@ picker_midi_in.setFixedWidth(200) picker_midi_in.setStyleSheet("background-color: rgba(0, 255, 0, 0.15); border: 1px solid green;") +# REFACTOR MAP: Old names → New names +# self.out_blink → activity_indicator_midi_out +# self.out_status → label_connection_midi_out +# self.port_combo → picker_midi_out + +# vstack_midi_out +vstack_midi_out = QVBoxLayout() +vstack_midi_out.setSpacing(2) + +# hstack_midi_out_activity_and_label +hstack_midi_out_activity_and_label = QHBoxLayout() + +# activity_indicator_midi_out +activity_indicator_midi_out = QLabel("●") +activity_indicator_midi_out.setStyleSheet("color: #444; font-size: 14px; background-color: rgba(255, 0, 0, 0.15); border: 1px solid red; padding: 2px;") + +# label_connection_midi_out +label_connection_midi_out = QLabel("Not Connected") +label_connection_midi_out.setStyleSheet("color: #888; background-color: rgba(0, 0, 255, 0.15); border: 1px solid blue; padding: 2px;") + +# label_midi_out +label_midi_out = QLabel("MIDI Output") +label_midi_out.setStyleSheet("background-color: rgba(255, 255, 0, 0.15); border: 1px solid yellow; padding: 2px;") + +# picker_midi_out +picker_midi_out = QComboBox() +picker_midi_out.setFixedWidth(200) +picker_midi_out.setStyleSheet("background-color: rgba(0, 255, 0, 0.15); border: 1px solid green;") + +# REFACTOR MAP: Old names → New names +# self.t_out_blink → activity_indicator_transport_out +# self.t_out_status → label_connection_transport_out +# self.t_out_combo → picker_transport_out + +# vstack_transport_out +vstack_transport_out = QVBoxLayout() +vstack_transport_out.setSpacing(2) + +# hstack_transport_out_activity_and_label +hstack_transport_out_activity_and_label = QHBoxLayout() + +# activity_indicator_transport_out +activity_indicator_transport_out = QLabel("●") +activity_indicator_transport_out.setStyleSheet("color: #444; font-size: 14px; background-color: rgba(255, 0, 0, 0.15); border: 1px solid red; padding: 2px;") + +# label_connection_transport_out +label_connection_transport_out = QLabel("Not Connected") +label_connection_transport_out.setStyleSheet("color: #888; background-color: rgba(0, 0, 255, 0.15); border: 1px solid blue; padding: 2px;") + +# label_transport_out +label_transport_out = QLabel("Transport Output") +label_transport_out.setStyleSheet("background-color: rgba(255, 255, 0, 0.15); border: 1px solid yellow; padding: 2px;") + +# picker_transport_out +picker_transport_out = QComboBox() +picker_transport_out.setFixedWidth(200) +picker_transport_out.setStyleSheet("background-color: rgba(0, 255, 0, 0.15); border: 1px solid green;") + +# REFACTOR MAP: Old names → New names +# self.osc_blink → activity_indicator_osc +# self.osc_status → label_connection_osc +# self.osc_ip_edit → textbox_osc_daw_ip +# self.osc_port_spin → spinner_osc_listen_port +# self.osc_send_port_spin → spinner_osc_send_port +# self.osc_toggle_btn → button_osc_toggle +# self.osc_log_btn → button_osc_messages_log + +# Layout: vstack_osc +vstack_osc = QVBoxLayout() +vstack_osc.setSpacing(2) + +# Layout: hstack_osc_activity_and_label +hstack_osc_activity_and_label = QHBoxLayout() + +# Layout: hstack_osc_ip +hstack_osc_ip = QHBoxLayout() + +# Layout: hstack_osc_ports +hstack_osc_ports = QHBoxLayout() + +# Layout: hstack_osc_buttons +hstack_osc_buttons = QHBoxLayout() + +# Widget: activity_indicator_osc +activity_indicator_osc = QLabel("●") +activity_indicator_osc.setStyleSheet("color: #444; font-size: 14px; background-color: rgba(255, 0, 0, 0.15); border: 1px solid red; padding: 2px;") + +# Widget: label_connection_osc +label_connection_osc = QLabel("Not Listening") +label_connection_osc.setStyleSheet("color: #888; background-color: rgba(0, 0, 255, 0.15); border: 1px solid blue; padding: 2px;") + +# Widget: textbox_osc_daw_ip +textbox_osc_daw_ip = QLineEdit("127.0.0.1") +textbox_osc_daw_ip.setFixedWidth(110) +textbox_osc_daw_ip.setPlaceholderText("127.0.0.1") + +# Widget: spinner_osc_listen_port +spinner_osc_listen_port = QSpinBox() +spinner_osc_listen_port.setMinimum(1024) +spinner_osc_listen_port.setMaximum(65535) +spinner_osc_listen_port.setValue(9000) +spinner_osc_listen_port.setFixedWidth(65) + +# Widget: spinner_osc_send_port +spinner_osc_send_port = QSpinBox() +spinner_osc_send_port.setMinimum(1024) +spinner_osc_send_port.setMaximum(65535) +spinner_osc_send_port.setValue(8000) +spinner_osc_send_port.setFixedWidth(65) + +# Widget: button_osc_toggle +button_osc_toggle = QPushButton("Start OSC") +button_osc_toggle.setFixedWidth(90) + +# Widget: button_osc_messages_log +button_osc_messages_log = QPushButton("Messages") +button_osc_messages_log.setFixedWidth(80) +button_osc_messages_log.setCheckable(True) +# MARK: OSC STATUS ROW MODULE +# REFACTOR MAP: Old names → New names +# self.osc_panel → container_osc_feedback +# self.osc_play_lbl → label_osc_play_state +# self.osc_record_lbl → label_osc_record_state +# self.osc_tempo_lbl → label_osc_tempo +# self.osc_pos_lbl → label_osc_position +# self.osc_track_lbl → label_osc_track +# self.osc_preset_lbl → label_osc_preset +# self.osc_log_panel → container_osc_in_log +# self.osc_log → textedit_osc_in_log +# self.osc_out_log_panel → container_osc_out_log +# self.osc_out_log → textedit_osc_out_log + +# Container: container_osc_feedback +container_osc_feedback = QWidget() +container_osc_feedback.setStyleSheet("background-color: #252526; border-radius: 4px;") + +# Layout: vstack_osc_feedback +vstack_osc_feedback = QVBoxLayout(container_osc_feedback) +vstack_osc_feedback.setContentsMargins(8, 6, 8, 6) +vstack_osc_feedback.setSpacing(4) + +# Layout: hstack_osc_transport_state +hstack_osc_transport_state = QHBoxLayout() +hstack_osc_transport_state.setSpacing(12) + +# Widget: label_osc_play_state +label_osc_play_state = QLabel("■ STOPPED") +label_osc_play_state.setStyleSheet("color: #555; font-size: 11px; font-weight: bold;") + +# Widget: label_osc_record_state +label_osc_record_state = QLabel("● REC") +label_osc_record_state.setStyleSheet("color: #555; font-size: 11px; font-weight: bold;") + +# Widget: label_osc_tempo +label_osc_tempo = QLabel("BPM: —") +label_osc_tempo.setStyleSheet("color: #888; font-size: 11px;") + +# Widget: label_osc_position +label_osc_position = QLabel("Pos: —") +label_osc_position.setStyleSheet("color: #888; font-size: 11px;") + +# Widget: divider_osc_feedback +divider_osc_feedback = QWidget() +divider_osc_feedback.setFixedHeight(1) +divider_osc_feedback.setStyleSheet("background-color: #3a3a3a;") + +# Layout: hstack_osc_info +hstack_osc_info = QHBoxLayout() +hstack_osc_info.setSpacing(8) + +# Widget: label_track_header +label_track_header = QLabel("TRACK") +label_track_header.setStyleSheet("color: #555; font-size: 9px; font-weight: bold;") + +# Widget: label_osc_track +label_osc_track = QLabel("—") +label_osc_track.setStyleSheet("color: #d4d4d4; font-size: 12px; font-weight: bold;") + +# Widget: label_preset_header +label_preset_header = QLabel("PRESET") +label_preset_header.setStyleSheet("color: #555; font-size: 9px; font-weight: bold;") + +# Widget: label_osc_preset +label_osc_preset = QLabel("—") +label_osc_preset.setStyleSheet("color: #00e676; font-size: 12px; font-weight: bold;") + +# Widget: spacer_osc_info +spacer_osc_info = QWidget() +spacer_osc_info.setFixedWidth(16) + +# Container: container_osc_in_log +container_osc_in_log = QWidget(None, Qt.WindowType.Tool) +container_osc_in_log.setWindowTitle("OSC In — Incoming") +container_osc_in_log.setStyleSheet("background-color: #1a1a1a;") +container_osc_in_log.setFixedWidth(360) + +# Layout: vstack_osc_in_log +vstack_osc_in_log = QVBoxLayout(container_osc_in_log) +vstack_osc_in_log.setContentsMargins(6, 6, 6, 6) +vstack_osc_in_log.setSpacing(4) + +# Layout: hstack_osc_in_log_header +hstack_osc_in_log_header = QHBoxLayout() + +# Widget: label_osc_in_log_title +label_osc_in_log_title = QLabel("OSC In — Incoming from REAPER") +label_osc_in_log_title.setStyleSheet("color: #00e676; font-weight: bold; font-size: 11px;") + +# Widget: button_osc_in_clear +button_osc_in_clear = QPushButton("Clear") +button_osc_in_clear.setFixedSize(45, 18) + +# Widget: textedit_osc_in_log +textedit_osc_in_log = QTextEdit() +textedit_osc_in_log.setReadOnly(True) +textedit_osc_in_log.setStyleSheet(""" + QTextEdit { + background-color: #111111; + color: #00e676; + font-family: monospace; + font-size: 10px; + border: none; + } +""") + +# Container: container_osc_out_log +container_osc_out_log = QWidget(None, Qt.WindowType.Tool) +container_osc_out_log.setWindowTitle("OSC Out — Outgoing") +container_osc_out_log.setStyleSheet("background-color: #1a1a1a;") +container_osc_out_log.setFixedWidth(360) + +# Layout: vstack_osc_out_log +vstack_osc_out_log = QVBoxLayout(container_osc_out_log) +vstack_osc_out_log.setContentsMargins(6, 6, 6, 6) +vstack_osc_out_log.setSpacing(4) + +# Layout: hstack_osc_out_log_header +hstack_osc_out_log_header = QHBoxLayout() + +# Widget: label_osc_out_log_title +label_osc_out_log_title = QLabel("OSC Out — Sent to REAPER") +label_osc_out_log_title.setStyleSheet("color: #1e88e5; font-weight: bold; font-size: 11px;") + +# Widget: button_osc_out_clear +button_osc_out_clear = QPushButton("Clear") +button_osc_out_clear.setFixedSize(45, 18) + +# Widget: textedit_osc_out_log +textedit_osc_out_log = QTextEdit() +textedit_osc_out_log.setReadOnly(True) +textedit_osc_out_log.setStyleSheet(""" + QTextEdit { + background-color: #111111; + color: #1e88e5; + font-family: monospace; + font-size: 10px; + border: none; + } +""") + +# MARK: PRESET BROWSER ROW MODULE +# REFACTOR MAP: Old names → New names +# self.browser_back_spin → spinner_preset_browser_back +# self.browser_fwd_spin → spinner_preset_browser_fwd + +# Widget: label_preset_browser_title +label_preset_browser_title = QLabel("Preset Browser") + +# Layout: hstack_preset_browser +hstack_preset_browser = QHBoxLayout() +hstack_preset_browser.setSpacing(8) + +# Widget: button_preset_browser_back +button_preset_browser_back = QPushButton("◀") +button_preset_browser_back.setFixedSize(36, 28) + +# Widget: spinner_preset_browser_back +spinner_preset_browser_back = QSpinBox() +spinner_preset_browser_back.setMinimum(1) +spinner_preset_browser_back.setMaximum(127) +spinner_preset_browser_back.setValue(22) +spinner_preset_browser_back.setFixedWidth(55) + +# Widget: button_preset_browser_fwd +button_preset_browser_fwd = QPushButton("▶") +button_preset_browser_fwd.setFixedSize(36, 28) + +# Widget: spinner_preset_browser_fwd +spinner_preset_browser_fwd = QSpinBox() +spinner_preset_browser_fwd.setMinimum(1) +spinner_preset_browser_fwd.setMaximum(127) +spinner_preset_browser_fwd.setValue(23) +spinner_preset_browser_fwd.setFixedWidth(55) + +# Widget: spacer_preset_browser +spacer_preset_browser = QWidget() +spacer_preset_browser.setFixedWidth(12) + +# MARK: TRANSPORT MODULE +# REFACTOR MAP: Old names → New names +# self.full_transport_row → hstack_full_transport +# self.transport_zone_left_slot → hstack_transport_zone_left +# self.transport_vsep_left → vsep_transport_left +# self.transport_row → hstack_transport_center +# self.transport_vsep_right → vsep_transport_right +# self.transport_zone_right_slot → hstack_transport_zone_right + +# Layout: hstack_full_transport +hstack_full_transport = QHBoxLayout() +hstack_full_transport.setSpacing(spacing) + +# Layout: hstack_transport_zone_left +hstack_transport_zone_left = QHBoxLayout() +hstack_transport_zone_left.setSpacing(0) + +# Widget: vsep_transport_left +vsep_transport_left = QFrame() +vsep_transport_left.setFrameShape(QFrame.Shape.VLine) +vsep_transport_left.setFixedWidth(1) +vsep_transport_left.setStyleSheet("background-color: #3a3a3a; border: none;") +vsep_transport_left.setVisible(False) + +# Layout: hstack_transport_center +hstack_transport_center = QHBoxLayout() +hstack_transport_center.setSpacing(spacing) + +# Widget: vsep_transport_right +vsep_transport_right = QFrame() +vsep_transport_right.setFrameShape(QFrame.Shape.VLine) +vsep_transport_right.setFixedWidth(1) +vsep_transport_right.setStyleSheet("background-color: #3a3a3a; border: none;") +vsep_transport_right.setVisible(False) + +# Layout: hstack_transport_zone_right +hstack_transport_zone_right = QHBoxLayout() +hstack_transport_zone_right.setSpacing(0) + +# MARK: SELECTED CHANNEL ROW MODULE +# REFACTOR MAP: Old names → New names +# self.selected_label → label_selected_channel + +# Layout: hstack_selected_channel_and_save_status +hstack_selected_channel_and_save_status = QHBoxLayout() + +# Widget: label_selected_channel +label_selected_channel = QLabel("Selected: None") +label_selected_channel.setStyleSheet("color: #00ff88; font-weight: bold;") +label_selected_channel.setAlignment(Qt.AlignmentFlag.AlignCenter) + +# MARK: FADERS ROW MODULE +# REFACTOR MAP: Old names → New names +# self.full_fader_row → hstack_full_fader +# self.zone_left_slot → hstack_fader_zone_left +# self.vsep_left → separator_vertical_fader_zone_left +# self.fader_row → hstack_fader_center +# self.vsep_right → separator_vertical_fader_zone_right +# self.zone_right_slot → hstack_fader_zone_right + +# Layout: hstack_full_fader +hstack_full_fader = QHBoxLayout() +hstack_full_fader.setSpacing(spacing) + +# Layout: hstack_fader_zone_left +hstack_fader_zone_left = QHBoxLayout() +hstack_fader_zone_left.setSpacing(0) + +# Widget: separator_vertical_fader_zone_left +separator_vertical_fader_zone_left = QFrame() +separator_vertical_fader_zone_left.setFrameShape(QFrame.Shape.VLine) +separator_vertical_fader_zone_left.setFixedWidth(1) +separator_vertical_fader_zone_left.setStyleSheet("background-color: #3a3a3a; border: none;") +separator_vertical_fader_zone_left.setVisible(False) + +# Layout: hstack_fader_center +hstack_fader_center = QHBoxLayout() +hstack_fader_center.setSpacing(spacing) + +# Widget: separator_vertical_fader_zone_right +separator_vertical_fader_zone_right = QFrame() +separator_vertical_fader_zone_right.setFrameShape(QFrame.Shape.VLine) +separator_vertical_fader_zone_right.setFixedWidth(1) +separator_vertical_fader_zone_right.setStyleSheet("background-color: #3a3a3a; border: none;") +separator_vertical_fader_zone_right.setVisible(False) + +# Layout: hstack_fader_zone_right +hstack_fader_zone_right = QHBoxLayout() +hstack_fader_zone_right.setSpacing(0) + +# MARK: TOGGLE ROW MODULE +# REFACTOR MAP: Old names → New names +# self.full_toggle_row → hstack_full_toggle +# self.toggle_zone_left_slot → hstack_toggle_zone_left +# self.toggle_vsep_left → separator_vertical_toggle_zone_left +# self.toggle_row → hstack_toggle_center +# self.toggle_vsep_right → separator_vertical_toggle_zone_right +# self.toggle_zone_right_slot → hstack_toggle_zone_right + +# Layout: hstack_full_toggle +hstack_full_toggle = QHBoxLayout() +hstack_full_toggle.setSpacing(spacing) + +# Layout: hstack_toggle_zone_left +hstack_toggle_zone_left = QHBoxLayout() +hstack_toggle_zone_left.setSpacing(0) + +# Widget: separator_vertical_toggle_zone_left +separator_vertical_toggle_zone_left = QFrame() +separator_vertical_toggle_zone_left.setFrameShape(QFrame.Shape.VLine) +separator_vertical_toggle_zone_left.setFixedWidth(1) +separator_vertical_toggle_zone_left.setStyleSheet("background-color: #3a3a3a; border: none;") +separator_vertical_toggle_zone_left.setVisible(False) + +# Layout: hstack_toggle_center +hstack_toggle_center = QHBoxLayout() +hstack_toggle_center.setSpacing(10) + +# Widget: separator_vertical_toggle_zone_right +separator_vertical_toggle_zone_right = QFrame() +separator_vertical_toggle_zone_right.setFrameShape(QFrame.Shape.VLine) +separator_vertical_toggle_zone_right.setFixedWidth(1) +separator_vertical_toggle_zone_right.setStyleSheet("background-color: #3a3a3a; border: none;") +separator_vertical_toggle_zone_right.setVisible(False) + +# Layout: hstack_toggle_zone_right +hstack_toggle_zone_right = QHBoxLayout() +hstack_toggle_zone_right.setSpacing(0) + + + + +#MARK: MAIN WINDOW START class MainWindow(QMainWindow): def __init__(self): super().__init__() @@ -240,6 +669,12 @@ class MainWindow(QMainWindow): global labelStatus global hstack_io_row global available_ports_midi_in + global picker_midi_in + global label_midi_in + global label_connection_midi_in + global activity_indicator_midi_in + global hstack_midi_in_activity_and_label + global vstack_midi_in self.setWindowTitle("Controller") #set app window reference @@ -262,7 +697,7 @@ class MainWindow(QMainWindow): #section seperator main_layout.addWidget(self._separator()) - #MARK: MIDI IN COLUMN + #MARK: MIDI IN COLUMN 1 #build the hstack with activity indicator and connection state label hstack_midi_in_activity_and_label.addWidget(activity_indicator_midi_in) hstack_midi_in_activity_and_label.addWidget(label_connection_midi_in) @@ -276,11 +711,6 @@ class MainWindow(QMainWindow): # add the combined stack to the row hstack_io_row.addLayout(vstack_midi_in) - - - - - # picker_midi_in population & signal available_ports_midi_in = get_available_input_ports() for display_name, idx, available in available_ports_midi_in: @@ -293,437 +723,316 @@ class MainWindow(QMainWindow): picker_midi_in.currentTextChanged.connect(self.on_input_port_select) + #MARK: MIDI OUT COLUMN 2 + # Globals for mutation safety + global vstack_midi_out + global hstack_midi_out_activity_and_label + global activity_indicator_midi_out + global label_connection_midi_out + global label_midi_out + global picker_midi_out + + # build the hstack with activity indicator and connection state label + hstack_midi_out_activity_and_label.addWidget(activity_indicator_midi_out) + hstack_midi_out_activity_and_label.addWidget(label_connection_midi_out) + hstack_midi_out_activity_and_label.addStretch() + + # add all 3 to the vstack + vstack_midi_out.addLayout(hstack_midi_out_activity_and_label) # activity indicator and connection label + vstack_midi_out.addWidget(label_midi_out) # midi output label + vstack_midi_out.addWidget(picker_midi_out) # port dropdown + + # populate the dropdown + picker_midi_out.addItems(available_ports) + picker_midi_out.currentTextChanged.connect(self.on_port_select) + + # add the combined stack to the row + hstack_io_row.addLayout(vstack_midi_out) - - - - - - - - + #MARK: TRANSPORT OUT COLUMN 3 + # Globals for mutation safety + global vstack_transport_out + global hstack_transport_out_activity_and_label + global activity_indicator_transport_out + global label_connection_transport_out + global label_transport_out + global picker_transport_out + # build the hstack with activity indicator and connection state label + hstack_transport_out_activity_and_label.addWidget(activity_indicator_transport_out) + hstack_transport_out_activity_and_label.addWidget(label_connection_transport_out) + hstack_transport_out_activity_and_label.addStretch() + # add all 3 to the vstack + vstack_transport_out.addLayout(hstack_transport_out_activity_and_label) # activity indicator and connection label + vstack_transport_out.addWidget(label_transport_out) # transport output label + vstack_transport_out.addWidget(picker_transport_out) # port dropdown + # populate the dropdown + picker_transport_out.addItems(available_ports) + picker_transport_out.currentTextChanged.connect(self.on_transport_output_select) + # add the combined stack to the row + hstack_io_row.addLayout(vstack_transport_out) + #MARK: OSC COLUMN 4 + # Globals for mutation safety + global vstack_osc + global hstack_osc_activity_and_label + global activity_indicator_osc + global label_connection_osc + global hstack_osc_ip + global textbox_osc_daw_ip + global hstack_osc_ports + global spinner_osc_listen_port + global spinner_osc_send_port + global hstack_osc_buttons + global button_osc_toggle + global button_osc_messages_log - #MARK: COL 2 MIDI OUT - # Output side - out_col = QVBoxLayout() - out_col.setSpacing(2) - out_top = QHBoxLayout() - self.out_blink = QLabel("●") - self.out_blink.setStyleSheet("color: #444; font-size: 14px;") - self.out_status = QLabel("Not Connected") - self.out_status.setStyleSheet("color: #888;") - out_top.addWidget(self.out_blink) - out_top.addWidget(self.out_status) - out_top.addStretch() - out_col.addLayout(out_top) - out_col.addWidget(QLabel("MIDI Output")) - self.port_combo = QComboBox() - self.port_combo.addItems(available_ports) - self.port_combo.setFixedWidth(200) - self.port_combo.currentTextChanged.connect(self.on_port_select) - out_col.addWidget(self.port_combo) - hstack_io_row.addLayout(out_col) + # build hstack with activity indicator and connection label + hstack_osc_activity_and_label.addWidget(activity_indicator_osc) + hstack_osc_activity_and_label.addWidget(label_connection_osc) + hstack_osc_activity_and_label.addStretch() - #MARK: COL 3 TRANSPORT - # Transport Output column - t_col = QVBoxLayout() - t_col.setSpacing(2) - t_out_top = QHBoxLayout() - self.t_out_blink = QLabel("●") - self.t_out_blink.setStyleSheet("color: #444; font-size: 14px;") - self.t_out_status = QLabel("Not Connected") - self.t_out_status.setStyleSheet("color: #888;") - t_out_top.addWidget(self.t_out_blink) - t_out_top.addWidget(self.t_out_status) - t_out_top.addStretch() - t_col.addLayout(t_out_top) - t_col.addWidget(QLabel("Transport Output")) - self.t_out_combo = QComboBox() - self.t_out_combo.addItems(available_ports) - self.t_out_combo.setFixedWidth(200) - self.t_out_combo.currentTextChanged.connect(self.on_transport_output_select) - t_col.addWidget(self.t_out_combo) - hstack_io_row.addLayout(t_col) + # build hstack for DAW IP configuration + hstack_osc_ip.addWidget(QLabel("DAW IP")) + hstack_osc_ip.addWidget(textbox_osc_daw_ip) + hstack_osc_ip.addStretch() + # build hstack for listen/send ports + hstack_osc_ports.addWidget(QLabel("Listen")) + hstack_osc_ports.addWidget(spinner_osc_listen_port) + hstack_osc_ports.addSpacing(6) + hstack_osc_ports.addWidget(QLabel("Send")) + hstack_osc_ports.addWidget(spinner_osc_send_port) + hstack_osc_ports.addStretch() - # #MARK: COL 4 OSC - # OSC column - osc_col = QVBoxLayout() - osc_col.setSpacing(2) - osc_top = QHBoxLayout() - self.osc_blink = QLabel("●") - self.osc_blink.setStyleSheet("color: #444; font-size: 14px;") - self.osc_status = QLabel("Not Listening") - self.osc_status.setStyleSheet("color: #888;") - osc_top.addWidget(self.osc_blink) - osc_top.addWidget(self.osc_status) - osc_top.addStretch() - osc_col.addLayout(osc_top) - # IP address row - osc_ip_row = QHBoxLayout() - osc_ip_row.addWidget(QLabel("DAW IP")) - self.osc_ip_edit = QLineEdit("127.0.0.1") - self.osc_ip_edit.setFixedWidth(110) - self.osc_ip_edit.setPlaceholderText("127.0.0.1") - self.osc_ip_edit.textChanged.connect(self.on_osc_ip_changed) - osc_ip_row.addWidget(self.osc_ip_edit) - osc_ip_row.addStretch() - osc_col.addLayout(osc_ip_row) + # build hstack for control buttons + hstack_osc_buttons.addWidget(button_osc_toggle) + hstack_osc_buttons.addWidget(button_osc_messages_log) + hstack_osc_buttons.addStretch() - # Port row - osc_port_row = QHBoxLayout() - osc_port_row.addWidget(QLabel("Listen")) - self.osc_port_spin = QSpinBox() - self.osc_port_spin.setMinimum(1024) - self.osc_port_spin.setMaximum(65535) - self.osc_port_spin.setValue(9000) - self.osc_port_spin.setFixedWidth(65) - osc_port_row.addWidget(self.osc_port_spin) - osc_port_row.addSpacing(6) - osc_port_row.addWidget(QLabel("Send")) - self.osc_send_port_spin = QSpinBox() - self.osc_send_port_spin.setMinimum(1024) - self.osc_send_port_spin.setMaximum(65535) - self.osc_send_port_spin.setValue(8000) - self.osc_send_port_spin.setFixedWidth(65) - self.osc_send_port_spin.valueChanged.connect(self.on_osc_send_port_changed) - osc_port_row.addWidget(self.osc_send_port_spin) - osc_port_row.addStretch() - osc_col.addLayout(osc_port_row) - osc_btn_row = QHBoxLayout() - self.osc_toggle_btn = QPushButton("Start OSC") - self.osc_toggle_btn.setFixedWidth(90) - self.osc_toggle_btn.clicked.connect(self.toggle_osc) - osc_btn_row.addWidget(self.osc_toggle_btn) - self.osc_log_btn = QPushButton("Messages") - self.osc_log_btn.setFixedWidth(80) - self.osc_log_btn.setCheckable(True) - self.osc_log_btn.clicked.connect(self.toggle_osc_log) - osc_btn_row.addWidget(self.osc_log_btn) - osc_btn_row.addStretch() - osc_col.addLayout(osc_btn_row) - hstack_io_row.addLayout(osc_col) + # add all rows to vstack + vstack_osc.addLayout(hstack_osc_activity_and_label) # activity indicator and connection label + vstack_osc.addLayout(hstack_osc_ip) # DAW IP input + vstack_osc.addLayout(hstack_osc_ports) # listen and send ports + vstack_osc.addLayout(hstack_osc_buttons) # control buttons + + # connect signals + textbox_osc_daw_ip.textChanged.connect(self.on_osc_ip_changed) + spinner_osc_send_port.valueChanged.connect(self.on_osc_send_port_changed) + button_osc_toggle.clicked.connect(self.toggle_osc) + button_osc_messages_log.clicked.connect(self.toggle_osc_log) + + # add the combined stack to the row + hstack_io_row.addLayout(vstack_osc) + + # add the combined stack to the row + hstack_io_row.addLayout(vstack_osc) hstack_io_row.addStretch() main_layout.addLayout(hstack_io_row) main_layout.addWidget(self._separator()) - # #MARK: OSC STATUS ROW - # --- OSC Feedback panel --- - self.osc_panel = QWidget() - self.osc_panel.setStyleSheet("background-color: #252526; border-radius: 4px;") - osc_panel_layout = QVBoxLayout(self.osc_panel) - osc_panel_layout.setContentsMargins(8, 6, 8, 6) - osc_panel_layout.setSpacing(4) + # MARK: OSC STATUS ROW + global container_osc_feedback + global vstack_osc_feedback + global hstack_osc_transport_state + global label_osc_play_state + global label_osc_record_state + global label_osc_tempo + global label_osc_position + global divider_osc_feedback + global hstack_osc_info + global label_track_header + global label_osc_track + global label_preset_header + global label_osc_preset + global spacer_osc_info + global container_osc_in_log + global vstack_osc_in_log + global hstack_osc_in_log_header + global label_osc_in_log_title + global button_osc_in_clear + global textedit_osc_in_log + global container_osc_out_log + global vstack_osc_out_log + global hstack_osc_out_log_header + global label_osc_out_log_title + global button_osc_out_clear + global textedit_osc_out_log - # Top row — transport state indicators - osc_transport_row = QHBoxLayout() - osc_transport_row.setSpacing(12) + #MARK: OSC FEEDBACK CONTAINER + # assemble transport state row + hstack_osc_transport_state.addWidget(label_osc_play_state) + hstack_osc_transport_state.addWidget(label_osc_record_state) + hstack_osc_transport_state.addStretch() + hstack_osc_transport_state.addWidget(label_osc_tempo) + hstack_osc_transport_state.addWidget(label_osc_position) - self.osc_play_lbl = QLabel("■ STOPPED") - self.osc_play_lbl.setStyleSheet("color: #555; font-size: 11px; font-weight: bold;") - osc_transport_row.addWidget(self.osc_play_lbl) + # assemble track and preset info row + hstack_osc_info.addWidget(label_track_header) + hstack_osc_info.addWidget(label_osc_track) + hstack_osc_info.addWidget(spacer_osc_info) + hstack_osc_info.addWidget(label_preset_header) + hstack_osc_info.addWidget(label_osc_preset) + hstack_osc_info.addStretch() - self.osc_record_lbl = QLabel("● REC") - self.osc_record_lbl.setStyleSheet("color: #555; font-size: 11px; font-weight: bold;") - osc_transport_row.addWidget(self.osc_record_lbl) + # assemble main feedback container + vstack_osc_feedback.addLayout(hstack_osc_transport_state) # play, record, tempo, position + vstack_osc_feedback.addWidget(divider_osc_feedback) # divider line + vstack_osc_feedback.addLayout(hstack_osc_info) # track and preset labels - osc_transport_row.addStretch() + container_osc_feedback.setVisible(False) + main_layout.addWidget(container_osc_feedback) - self.osc_tempo_lbl = QLabel("BPM: —") - self.osc_tempo_lbl.setStyleSheet("color: #888; font-size: 11px;") - osc_transport_row.addWidget(self.osc_tempo_lbl) + #MARK: OSC IN LOG CONTAINER + # assemble header + hstack_osc_in_log_header.addWidget(label_osc_in_log_title) + hstack_osc_in_log_header.addStretch() + hstack_osc_in_log_header.addWidget(button_osc_in_clear) - self.osc_pos_lbl = QLabel("Pos: —") - self.osc_pos_lbl.setStyleSheet("color: #888; font-size: 11px;") - osc_transport_row.addWidget(self.osc_pos_lbl) + # assemble container + vstack_osc_in_log.addLayout(hstack_osc_in_log_header) + vstack_osc_in_log.addWidget(textedit_osc_in_log) - osc_panel_layout.addLayout(osc_transport_row) + # connect signal + button_osc_in_clear.clicked.connect(lambda: textedit_osc_in_log.clear()) - # Divider - osc_div = QWidget() - osc_div.setFixedHeight(1) - osc_div.setStyleSheet("background-color: #3a3a3a;") - osc_panel_layout.addWidget(osc_div) + container_osc_in_log.setVisible(False) + #MARK: OSC OUT LOG CONTAINER + # assemble header + hstack_osc_out_log_header.addWidget(label_osc_out_log_title) + hstack_osc_out_log_header.addStretch() + hstack_osc_out_log_header.addWidget(button_osc_out_clear) - # Bottom row — track and preset info - osc_info_row = QHBoxLayout() - osc_info_row.setSpacing(8) + # assemble container + vstack_osc_out_log.addLayout(hstack_osc_out_log_header) + vstack_osc_out_log.addWidget(textedit_osc_out_log) - track_header = QLabel("TRACK") - track_header.setStyleSheet("color: #555; font-size: 9px; font-weight: bold;") - osc_info_row.addWidget(track_header) + # connect signal + button_osc_out_clear.clicked.connect(lambda: textedit_osc_out_log.clear()) - self.osc_track_lbl = QLabel("—") - self.osc_track_lbl.setStyleSheet("color: #d4d4d4; font-size: 12px; font-weight: bold;") - osc_info_row.addWidget(self.osc_track_lbl) + container_osc_out_log.setVisible(False) + + #MARK: PRESET BROWSER + # Globals for mutation safety + global label_preset_browser_title + global hstack_preset_browser + global button_preset_browser_back + global spinner_preset_browser_back + global button_preset_browser_fwd + global spinner_preset_browser_fwd + global spacer_preset_browser - osc_info_row.addSpacing(16) + # assemble browser controls + hstack_preset_browser.addWidget(button_preset_browser_back) + hstack_preset_browser.addWidget(spinner_preset_browser_back) + hstack_preset_browser.addWidget(spacer_preset_browser) + hstack_preset_browser.addWidget(button_preset_browser_fwd) + hstack_preset_browser.addWidget(spinner_preset_browser_fwd) + hstack_preset_browser.addStretch() - preset_header = QLabel("PRESET") - preset_header.setStyleSheet("color: #555; font-size: 9px; font-weight: bold;") - osc_info_row.addWidget(preset_header) + # add to main layout + main_layout.addWidget(label_preset_browser_title) + main_layout.addLayout(hstack_preset_browser) - self.osc_preset_lbl = QLabel("—") - self.osc_preset_lbl.setStyleSheet("color: #00e676; font-size: 12px; font-weight: bold;") - osc_info_row.addWidget(self.osc_preset_lbl) - - osc_info_row.addStretch() - osc_panel_layout.addLayout(osc_info_row) - - self.osc_panel.setVisible(False) - main_layout.addWidget(self.osc_panel) - - # OSC In log panel - from PyQt6.QtWidgets import QTextEdit - self.osc_log_panel = QWidget(None, Qt.WindowType.Tool) - self.osc_log_panel.setWindowTitle("OSC In — Incoming") - self.osc_log_panel.setStyleSheet("background-color: #1a1a1a;") - self.osc_log_panel.setFixedWidth(360) - log_layout = QVBoxLayout(self.osc_log_panel) - log_layout.setContentsMargins(6, 6, 6, 6) - log_layout.setSpacing(4) - - log_header = QHBoxLayout() - log_title = QLabel("OSC In — Incoming from REAPER") - log_title.setStyleSheet("color: #00e676; font-weight: bold; font-size: 11px;") - log_header.addWidget(log_title) - log_header.addStretch() - clear_in_btn = QPushButton("Clear") - clear_in_btn.setFixedSize(45, 18) - clear_in_btn.clicked.connect(lambda: self.osc_log.clear()) - log_header.addWidget(clear_in_btn) - log_layout.addLayout(log_header) - - self.osc_log = QTextEdit() - self.osc_log.setReadOnly(True) - self.osc_log.setStyleSheet(""" - QTextEdit { - background-color: #111111; - color: #00e676; - font-family: monospace; - font-size: 10px; - border: none; - } - """) - log_layout.addWidget(self.osc_log) - self.osc_log_panel.setVisible(False) - - # OSC Out log panel - self.osc_out_log_panel = QWidget(None, Qt.WindowType.Tool) - self.osc_out_log_panel.setWindowTitle("OSC Out — Outgoing") - self.osc_out_log_panel.setStyleSheet("background-color: #1a1a1a;") - self.osc_out_log_panel.setFixedWidth(360) - out_log_layout = QVBoxLayout(self.osc_out_log_panel) - out_log_layout.setContentsMargins(6, 6, 6, 6) - out_log_layout.setSpacing(4) - - out_log_header = QHBoxLayout() - out_log_title = QLabel("OSC Out — Sent to REAPER") - out_log_title.setStyleSheet("color: #1e88e5; font-weight: bold; font-size: 11px;") - out_log_header.addWidget(out_log_title) - out_log_header.addStretch() - clear_out_btn = QPushButton("Clear") - clear_out_btn.setFixedSize(45, 18) - clear_out_btn.clicked.connect(lambda: self.osc_out_log.clear()) - out_log_header.addWidget(clear_out_btn) - out_log_layout.addLayout(out_log_header) - - self.osc_out_log = QTextEdit() - self.osc_out_log.setReadOnly(True) - self.osc_out_log.setStyleSheet(""" - QTextEdit { - background-color: #111111; - color: #1e88e5; - font-family: monospace; - font-size: 10px; - border: none; - } - """) - out_log_layout.addWidget(self.osc_out_log) - self.osc_out_log_panel.setVisible(False) - - - #MARK: PRESET ROW - # --- Preset browser --- - main_layout.addWidget(QLabel("Preset Browser")) - browser_row = QHBoxLayout() - browser_row.setSpacing(8) - - back_btn = QPushButton("◀") - back_btn.setFixedSize(36, 28) - browser_row.addWidget(back_btn) - - self.browser_back_spin = QSpinBox() - self.browser_back_spin.setMinimum(1) - self.browser_back_spin.setMaximum(127) - self.browser_back_spin.setValue(22) - self.browser_back_spin.setFixedWidth(55) - browser_row.addWidget(self.browser_back_spin) - - browser_row.addSpacing(12) - - fwd_btn = QPushButton("▶") - fwd_btn.setFixedSize(36, 28) - browser_row.addWidget(fwd_btn) - - self.browser_fwd_spin = QSpinBox() - self.browser_fwd_spin.setMinimum(1) - self.browser_fwd_spin.setMaximum(127) - self.browser_fwd_spin.setValue(23) - self.browser_fwd_spin.setFixedWidth(55) - browser_row.addWidget(self.browser_fwd_spin) - - browser_row.addStretch() - main_layout.addLayout(browser_row) - - back_btn.clicked.connect(lambda: send_cc(self.browser_back_spin.value(), 127)) - fwd_btn.clicked.connect(lambda: send_cc(self.browser_fwd_spin.value(), 127)) + # connect signals + button_preset_browser_back.clicked.connect(lambda: send_cc(spinner_preset_browser_back.value(), 127)) + button_preset_browser_fwd.clicked.connect(lambda: send_cc(spinner_preset_browser_fwd.value(), 127)) main_layout.addWidget(self._separator()) - #MARK: TRANSPORT ROW - # --- Transport --- + # Instance state self.transports = [] self.transport_strip_width = 100 self._capturing_key_for = None - self.full_transport_row = QHBoxLayout() - self.full_transport_row.setSpacing(spacing) - main_layout.addLayout(self.full_transport_row) + # Globals for mutation safety + global hstack_full_transport + global hstack_transport_zone_left + global vsep_transport_left + global hstack_transport_center + global vsep_transport_right + global hstack_transport_zone_right - self.transport_zone_left_slot = QHBoxLayout() - self.transport_zone_left_slot.setSpacing(0) - self.full_transport_row.addLayout(self.transport_zone_left_slot) + #MARK: TRANSPORT ROW + # assemble full transport row + hstack_full_transport.addLayout(hstack_transport_zone_left) # left zone slot + hstack_full_transport.addWidget(vsep_transport_left) # left separator + hstack_full_transport.addLayout(hstack_transport_center) # center controls + hstack_full_transport.addWidget(vsep_transport_right) # right separator + hstack_full_transport.addLayout(hstack_transport_zone_right) # right zone slot - self.transport_vsep_left = QFrame() - self.transport_vsep_left.setFrameShape(QFrame.Shape.VLine) - self.transport_vsep_left.setFixedWidth(1) - self.transport_vsep_left.setStyleSheet("background-color: #3a3a3a; border: none;") - self.transport_vsep_left.setVisible(False) - self.full_transport_row.addWidget(self.transport_vsep_left) + # add to main layout + main_layout.addLayout(hstack_full_transport) - self.transport_row = QHBoxLayout() - self.transport_row.setSpacing(spacing) - self.full_transport_row.addLayout(self.transport_row) + # MARK: SELECTED CHANNEL ROW + global hstack_selected_channel_and_save_status + global label_selected_channel + global labelStatus - self.transport_vsep_right = QFrame() - self.transport_vsep_right.setFrameShape(QFrame.Shape.VLine) - self.transport_vsep_right.setFixedWidth(1) - self.transport_vsep_right.setStyleSheet("background-color: #3a3a3a; border: none;") - self.transport_vsep_right.setVisible(False) - self.full_transport_row.addWidget(self.transport_vsep_right) - - self.transport_zone_right_slot = QHBoxLayout() - self.transport_zone_right_slot.setSpacing(0) - self.full_transport_row.addLayout(self.transport_zone_right_slot) - - main_layout.addWidget(self._separator()) - - #MARK: SELECTED CHANNEL ROW - # --- Selected channel label row --- - info_row = QHBoxLayout() - info_row.addStretch() - self.selected_label = QLabel("Selected: None") - self.selected_label.setStyleSheet("color: #00ff88; font-weight: bold;") - self.selected_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - info_row.addWidget(self.selected_label) - info_row.addStretch() - labelStatus.setParent(None) - info_row.addWidget(labelStatus) - main_layout.addLayout(info_row) + #MARK: SELECTED CHANNEL AND SAVE STATUS ROW + # assemble info row + hstack_selected_channel_and_save_status.addStretch() + hstack_selected_channel_and_save_status.addWidget(label_selected_channel) + hstack_selected_channel_and_save_status.addStretch() + labelStatus.setParent(None) # reparent status label + hstack_selected_channel_and_save_status.addWidget(labelStatus) + # add to main layout + main_layout.addLayout(hstack_selected_channel_and_save_status) #MARK: FADERS ROW - # --- Faders row --- - self.full_fader_row = QHBoxLayout() - self.full_fader_row.setSpacing(spacing) - main_layout.addLayout(self.full_fader_row) + global hstack_full_fader + global hstack_fader_zone_left + global separator_vertical_fader_zone_left + global hstack_fader_center + global separator_vertical_fader_zone_right + global hstack_fader_zone_right - # Zone Left slot - self.zone_left_slot = QHBoxLayout() - self.zone_left_slot.setSpacing(0) - self.full_fader_row.addLayout(self.zone_left_slot) + # assemble full fader row + hstack_full_fader.addLayout(hstack_fader_zone_left) # left zone slot + hstack_full_fader.addWidget(separator_vertical_fader_zone_left) # left separator + hstack_full_fader.addLayout(hstack_fader_center) # center faders + hstack_full_fader.addWidget(separator_vertical_fader_zone_right) # right separator + hstack_full_fader.addLayout(hstack_fader_zone_right) # right zone slot - # Left vertical separator — always visible - self.vsep_left = QFrame() - self.vsep_left.setFrameShape(QFrame.Shape.VLine) - self.vsep_left.setFixedWidth(1) - self.vsep_left.setStyleSheet("background-color: #3a3a3a; border: none;") - self.vsep_left.setVisible(False) - self.full_fader_row.addWidget(self.vsep_left) - - # Custom fader row - self.fader_row = QHBoxLayout() - self.fader_row.setSpacing(spacing) - self.full_fader_row.addLayout(self.fader_row) - - # Right vertical separator — always visible - self.vsep_right = QFrame() - self.vsep_right.setFrameShape(QFrame.Shape.VLine) - self.vsep_right.setFixedWidth(1) - self.vsep_right.setStyleSheet("background-color: #3a3a3a; border: none;") - self.vsep_right.setVisible(False) - self.full_fader_row.addWidget(self.vsep_right) - - # Zone Right slot - self.zone_right_slot = QHBoxLayout() - self.zone_right_slot.setSpacing(0) - self.full_fader_row.addLayout(self.zone_right_slot) + # add to main layout + main_layout.addLayout(hstack_full_fader) main_layout.addWidget(self._separator()) - #MARK: TOGGLE ROW - # --- Toggle --- self.toggles = [] self.toggle_strip_width = 100 - self.full_toggle_row = QHBoxLayout() - self.full_toggle_row.setSpacing(spacing) - main_layout.addLayout(self.full_toggle_row) + # Globals for mutation safety + global hstack_full_toggle + global hstack_toggle_zone_left + global separator_vertical_toggle_zone_left + global hstack_toggle_center + global separator_vertical_toggle_zone_right + global hstack_toggle_zone_right - self.toggle_zone_left_slot = QHBoxLayout() - self.toggle_zone_left_slot.setSpacing(0) - self.full_toggle_row.addLayout(self.toggle_zone_left_slot) - - self.toggle_vsep_left = QFrame() - self.toggle_vsep_left.setFrameShape(QFrame.Shape.VLine) - self.toggle_vsep_left.setFixedWidth(1) - self.toggle_vsep_left.setStyleSheet("background-color: #3a3a3a; border: none;") - self.toggle_vsep_left.setVisible(False) - self.full_toggle_row.addWidget(self.toggle_vsep_left) - - self.toggle_row = QHBoxLayout() - self.toggle_row.setSpacing(10) - self.full_toggle_row.addLayout(self.toggle_row) - - self.toggle_vsep_right = QFrame() - self.toggle_vsep_right.setFrameShape(QFrame.Shape.VLine) - self.toggle_vsep_right.setFixedWidth(1) - self.toggle_vsep_right.setStyleSheet("background-color: #3a3a3a; border: none;") - self.toggle_vsep_right.setVisible(False) - self.full_toggle_row.addWidget(self.toggle_vsep_right) - - self.toggle_zone_right_slot = QHBoxLayout() - self.toggle_zone_right_slot.setSpacing(0) - self.full_toggle_row.addLayout(self.toggle_zone_right_slot) + #MARK: TOGGLES ROW + # assemble full toggle row + hstack_full_toggle.addLayout(hstack_toggle_zone_left) # left zone slot + hstack_full_toggle.addWidget(separator_vertical_toggle_zone_left) # left separator + hstack_full_toggle.addLayout(hstack_toggle_center) # center toggles + hstack_full_toggle.addWidget(separator_vertical_toggle_zone_right) # right separator + hstack_full_toggle.addLayout(hstack_toggle_zone_right) # right zone slot + # add to main layout + main_layout.addLayout(hstack_full_toggle) main_layout.addStretch() - - - #MARK: SET DEFAULTS + #MARK: SET DEFAULTS / LOAD PRESETS # Add initial 2 faders, 1 toggle, 4 transport strips - #load presets loaded_presets = ensure_preset_structure(loaded_presets) self.add_fader() @@ -741,8 +1050,8 @@ class MainWindow(QMainWindow): self.refresh_port_availability() midi_receiver.midi_in_signal.connect(lambda msg: self.blink(activity_indicator_midi_in)) - midi_receiver.midi_out_signal.connect(lambda: self.blink(self.out_blink)) - midi_receiver.transport_out_signal.connect(lambda: self.blink(self.t_out_blink)) + midi_receiver.midi_out_signal.connect(lambda: self.blink(activity_indicator_midi_out)) + midi_receiver.transport_out_signal.connect(lambda: self.blink(activity_indicator_transport_out)) midi_receiver.hw_cc_signal.connect(self.on_hw_cc) # OSC signals @@ -801,7 +1110,7 @@ class MainWindow(QMainWindow): if toggle in self.toggles: toggle.center_index = self.toggles.index(toggle) - for layout in [self.toggle_row, self.toggle_zone_left_slot, self.toggle_zone_right_slot]: + for layout in [hstack_toggle_center, hstack_toggle_zone_left, hstack_toggle_zone_right]: for i in range(layout.count()): item = layout.itemAt(i) if item and item.widget() and hasattr(item.widget(), 'uid') and item.widget().uid == toggle.uid: @@ -810,23 +1119,23 @@ class MainWindow(QMainWindow): break if toggle.zone == "left": - toggle.zone_index = self.toggle_zone_left_slot.count() - 1 - self.toggle_zone_left_slot.addWidget(toggle) - self.toggle_vsep_left.setVisible(True) + toggle.zone_index = hstack_toggle_zone_left.count() - 1 + hstack_toggle_zone_left.addWidget(toggle) + separator_vertical_toggle_zone_left.setVisible(True) elif toggle.zone == "right": - toggle.zone_index = self.toggle_zone_right_slot.count() - 1 - self.toggle_zone_right_slot.addWidget(toggle) - self.toggle_vsep_right.setVisible(True) + toggle.zone_index = hstack_toggle_zone_right.count() - 1 + hstack_toggle_zone_right.addWidget(toggle) + separator_vertical_toggle_zone_right.setVisible(True) else: - idx = toggle.center_index if toggle.center_index is not None else self.toggle_row.count() - idx = min(idx, self.toggle_row.count()) - self.toggle_row.insertWidget(idx, toggle) + idx = toggle.center_index if toggle.center_index is not None else hstack_toggle_center.count() + idx = min(idx, hstack_toggle_center.count()) + hstack_toggle_center.insertWidget(idx, toggle) toggle.center_index = None toggle.zone_index = None - self.toggle_vsep_left.setVisible(any(t.zone == "left" for t in self.toggles)) - self.toggle_vsep_right.setVisible(any(t.zone == "right" for t in self.toggles)) + separator_vertical_toggle_zone_left.setVisible(any(t.zone == "left" for t in self.toggles)) + separator_vertical_toggle_zone_right.setVisible(any(t.zone == "right" for t in self.toggles)) has_left = any(t.zone == "left" for t in self.toggles) has_right = any(t.zone == "right" for t in self.toggles) @@ -844,7 +1153,7 @@ class MainWindow(QMainWindow): fader.center_index = faders.index(fader) # Remove by UUID from whichever slot it's in - for layout in [self.fader_row, self.zone_left_slot, self.zone_right_slot]: + for layout in [hstack_fader_center, hstack_fader_zone_left, hstack_fader_zone_right]: for i in range(layout.count()): item = layout.itemAt(i) if item and item.widget() and hasattr(item.widget(), 'uid') and item.widget().uid == fader.uid: @@ -853,23 +1162,23 @@ class MainWindow(QMainWindow): break if fader.zone == "left": - fader.zone_index = self.zone_left_slot.count() - 1 # subtract placeholder - self.zone_left_slot.addWidget(fader) - self.vsep_left.setVisible(True) + fader.zone_index = hstack_fader_zone_left.count() - 1 # subtract placeholder + hstack_fader_zone_left.addWidget(fader) + separator_vertical_fader_zone_left.setVisible(True) elif fader.zone == "right": - fader.zone_index = self.zone_right_slot.count() - 1 - self.zone_right_slot.addWidget(fader) - self.vsep_right.setVisible(True) + fader.zone_index = hstack_fader_zone_right.count() - 1 + hstack_fader_zone_right.addWidget(fader) + separator_vertical_fader_zone_right.setVisible(True) else: - idx = fader.center_index if fader.center_index is not None else self.fader_row.count() - idx = min(idx, self.fader_row.count()) - self.fader_row.insertWidget(idx, fader) + idx = fader.center_index if fader.center_index is not None else hstack_fader_center.count() + idx = min(idx, hstack_fader_center.count()) + hstack_fader_center.insertWidget(idx, fader) fader.center_index = None fader.zone_index = None - self.vsep_left.setVisible(any(f.zone == "left" for f in faders)) - self.vsep_right.setVisible(any(f.zone == "right" for f in faders)) + separator_vertical_fader_zone_left.setVisible(any(f.zone == "left" for f in faders)) + separator_vertical_fader_zone_right.setVisible(any(f.zone == "right" for f in faders)) has_left = any(f.zone == "left" for f in faders) has_right = any(f.zone == "right" for f in faders) @@ -883,7 +1192,7 @@ class MainWindow(QMainWindow): selected_strip.set_selected(False) selected_strip = strip strip.set_selected(True) - self.selected_label.setText(f"Selected: {strip.title_edit.text()}") + label_selected_channel.setText(f"Selected: {strip.title_edit.text()}") def add_toggle(self, insert_after=None): n = len(self.toggles) + 1 @@ -891,10 +1200,10 @@ class MainWindow(QMainWindow): if insert_after is not None and insert_after in self.toggles: idx = self.toggles.index(insert_after) self.toggles.insert(idx + 1, toggle) - self.toggle_row.insertWidget(idx + 1, toggle) + hstack_toggle_center.insertWidget(idx + 1, toggle) else: self.toggles.append(toggle) - self.toggle_row.addWidget(toggle) + hstack_toggle_center.addWidget(toggle) toggle.plus_btn.clicked.connect(lambda: self.add_toggle(insert_after=toggle)) toggle.minus_btn.clicked.connect(lambda: self.remove_toggle(toggle)) toggle.on_select_callback = self.select_strip @@ -909,8 +1218,8 @@ class MainWindow(QMainWindow): return if selected_strip == toggle: selected_strip = None - self.selected_label.setText("Selected: None") - self.toggle_row.removeWidget(toggle) + label_selected_channel.setText("Selected: None") + hstack_toggle_center.removeWidget(toggle) toggle.deleteLater() self.toggles.remove(toggle) self.update_window_width() @@ -925,10 +1234,10 @@ class MainWindow(QMainWindow): if insert_after is not None and insert_after in faders: idx = faders.index(insert_after) faders.insert(idx + 1, fader) - self.fader_row.insertWidget(idx + 1, fader) + hstack_fader_center.insertWidget(idx + 1, fader) else: faders.append(fader) - self.fader_row.addWidget(fader) + hstack_fader_center.addWidget(fader) fader.plus_btn.clicked.connect(lambda: self.add_fader(insert_after=fader)) fader.minus_btn.clicked.connect(lambda: self.remove_fader(fader)) fader.learn_btn.clicked.connect(lambda: self.start_learn(fader)) @@ -947,8 +1256,8 @@ class MainWindow(QMainWindow): return if selected_strip == fader: selected_strip = None - self.selected_label.setText("Selected: None") - self.fader_row.removeWidget(fader) + label_selected_channel.setText("Selected: None") + hstack_fader_center.removeWidget(fader) fader.deleteLater() faders.remove(fader) self.update_window_width() @@ -1020,20 +1329,20 @@ class MainWindow(QMainWindow): global faders return { - "midi_port": self.port_combo.currentText(), + "midi_port": picker_midi_out.currentText(), "midi_input": picker_midi_in.currentText(), - "transport_port": self.t_out_combo.currentText(), + "transport_port": picker_transport_out.currentText(), "faders": [f.get_state() for f in faders], "toggles": [t.get_state() for t in self.toggles], "transports": [t.get_state() for t in self.transports], "browser": { - "back_cc": self.browser_back_spin.value(), - "fwd_cc": self.browser_fwd_spin.value() + "back_cc": spinner_preset_browser_back.value(), + "fwd_cc": spinner_preset_browser_fwd.value() }, "osc": { - "ip": self.osc_ip_edit.text(), - "listen_port": self.osc_port_spin.value(), - "send_port": self.osc_send_port_spin.value(), + "ip": textbox_osc_daw_ip.text(), + "listen_port": spinner_osc_listen_port.value(), + "send_port": spinner_osc_send_port.value(), } } @@ -1045,11 +1354,11 @@ class MainWindow(QMainWindow): port_name = preset.get("midi_port", "") if port_name in available_ports: - self.port_combo.setCurrentText(port_name) + picker_midi_out.setCurrentText(port_name) transport_port = preset.get("transport_port", "") if transport_port in available_ports: - self.t_out_combo.setCurrentText(transport_port) + picker_transport_out.setCurrentText(transport_port) midi_input = preset.get("midi_input", "") input_names = [entry[0] for entry in available_ports_midi_in] @@ -1060,7 +1369,7 @@ class MainWindow(QMainWindow): self.add_fader() while len(faders) > len(fader_states): widget = faders.pop() - self.fader_row.removeWidget(widget) + hstack_fader_center.removeWidget(widget) widget.deleteLater() self.update_window_width() for i, widget in enumerate(faders): @@ -1072,10 +1381,10 @@ class MainWindow(QMainWindow): key=lambda f: f.center_index if f.center_index is not None else 999 ) for f in center_faders: - self.fader_row.removeWidget(f) + hstack_fader_center.removeWidget(f) f.setParent(None) for f in center_faders: - self.fader_row.addWidget(f) + hstack_fader_center.addWidget(f) # Then place zoned faders in zone order left_faders = sorted( @@ -1087,13 +1396,13 @@ class MainWindow(QMainWindow): key=lambda f: f.zone_index if f.zone_index is not None else 999 ) for f in left_faders: - self.fader_row.removeWidget(f) + hstack_fader_center.removeWidget(f) f.setParent(None) - self.zone_left_slot.addWidget(f) + hstack_fader_zone_left.addWidget(f) for f in right_faders: - self.fader_row.removeWidget(f) + hstack_fader_center.removeWidget(f) f.setParent(None) - self.zone_right_slot.addWidget(f) + hstack_fader_zone_right.addWidget(f) self.update_window_width() # Migration: old presets have 'toggle' (singular), new have 'toggles' (list) @@ -1112,7 +1421,7 @@ class MainWindow(QMainWindow): self.add_toggle() while len(self.toggles) > len(toggle_states): widget = self.toggles.pop() - self.toggle_row.removeWidget(widget) + hstack_toggle_center.removeWidget(widget) widget.deleteLater() self.update_window_width() for i, widget in enumerate(self.toggles): @@ -1124,10 +1433,10 @@ class MainWindow(QMainWindow): key=lambda t: t.center_index if t.center_index is not None else 999 ) for t in center_toggles: - self.toggle_row.removeWidget(t) + hstack_toggle_center.removeWidget(t) t.setParent(None) for t in center_toggles: - self.toggle_row.addWidget(t) + hstack_toggle_center.addWidget(t) # Place zoned toggles left_toggles = sorted( @@ -1139,18 +1448,18 @@ class MainWindow(QMainWindow): key=lambda t: t.zone_index if t.zone_index is not None else 999 ) for t in left_toggles: - self.toggle_row.removeWidget(t) + hstack_toggle_center.removeWidget(t) t.setParent(None) - self.toggle_zone_left_slot.addWidget(t) + hstack_toggle_zone_left.addWidget(t) for t in right_toggles: - self.toggle_row.removeWidget(t) + hstack_toggle_center.removeWidget(t) t.setParent(None) - self.toggle_zone_right_slot.addWidget(t) + hstack_toggle_zone_right.addWidget(t) browser = preset.get("browser", {}) - self.browser_back_spin.setValue(browser.get("back_cc", 22)) - self.browser_fwd_spin.setValue(browser.get("fwd_cc", 23)) + spinner_preset_browser_back.setValue(browser.get("back_cc", 22)) + spinner_preset_browser_fwd.setValue(browser.get("fwd_cc", 23)) transport_states = preset.get("transports", []) for i, t in enumerate(self.transports): @@ -1159,9 +1468,9 @@ class MainWindow(QMainWindow): osc = preset.get("osc", {}) if osc: - self.osc_ip_edit.setText(osc.get("ip", "127.0.0.1")) - self.osc_port_spin.setValue(osc.get("listen_port", 9000)) - self.osc_send_port_spin.setValue(osc.get("send_port", 8000)) + textbox_osc_daw_ip.setText(osc.get("ip", "127.0.0.1")) + spinner_osc_listen_port.setValue(osc.get("listen_port", 9000)) + spinner_osc_send_port.setValue(osc.get("send_port", 8000)) @@ -1303,12 +1612,12 @@ class MainWindow(QMainWindow): def refresh_port_availability(self): """Gray out ports already in use by another dropdown.""" - out_port = self.port_combo.currentText() - t_port = self.t_out_combo.currentText() + out_port = picker_midi_out.currentText() + t_port = picker_transport_out.currentText() # Output combo — gray out transport port - for i in range(self.port_combo.model().rowCount()): - item = self.port_combo.model().item(i) + for i in range(picker_midi_out.model().rowCount()): + item = picker_midi_out.model().item(i) # Strip any existing [in use] tag before comparing base = item.text().replace(" [in use]", "") if base == t_port and base != out_port: @@ -1321,8 +1630,8 @@ class MainWindow(QMainWindow): item.setForeground(QColor('#ffffff')) # Transport combo — gray out output port - for i in range(self.t_out_combo.model().rowCount()): - item = self.t_out_combo.model().item(i) + for i in range(picker_transport_out.model().rowCount()): + item = picker_transport_out.model().item(i) base = item.text().replace(" [in use]", "") if base == out_port and base != t_port: item.setText(f"{base} [in use]") @@ -1338,8 +1647,8 @@ class MainWindow(QMainWindow): midi_out.close_port() port_index = available_ports.index(port_name) midi_out.open_port(port_index) - self.out_status.setText("Connected") - self.out_status.setStyleSheet("color: #27ae60;") + label_connection_midi_out.setText("Connected") + label_connection_midi_out.setStyleSheet("color: #27ae60;") print(f"Opened output port: {port_name}") self.refresh_port_availability() @@ -1350,8 +1659,8 @@ class MainWindow(QMainWindow): transport_midi_out.close_port() port_index = available_ports.index(port_name) transport_midi_out.open_port(port_index) - self.t_out_status.setText("Connected") - self.t_out_status.setStyleSheet("color: #27ae60;") + label_connection_transport_out.setText("Connected") + label_connection_transport_out.setStyleSheet("color: #27ae60;") print(f"Opened transport output port: {port_name}") self.refresh_port_availability() @@ -1361,14 +1670,14 @@ class MainWindow(QMainWindow): t.learn_btn.clicked.connect(lambda: self.start_learn(t)) t.on_zone_change_callback = self.on_transport_zone_change self.transports.append(t) - self.transport_row.addWidget(t) + hstack_transport_center.addWidget(t) self.refresh_all_id_labels() def on_transport_zone_change(self, transport): if transport.zone is not None and transport.center_index is None: if transport in self.transports: transport.center_index = self.transports.index(transport) - for layout in [self.transport_row, self.transport_zone_left_slot, self.transport_zone_right_slot]: + for layout in [hstack_transport_center, hstack_transport_zone_left, hstack_transport_zone_right]: for i in range(layout.count()): item = layout.itemAt(i) if item and item.widget() and hasattr(item.widget(), 'uid') and item.widget().uid == transport.uid: @@ -1376,58 +1685,58 @@ class MainWindow(QMainWindow): transport.setParent(None) break if transport.zone == "left": - transport.zone_index = self.transport_zone_left_slot.count() - self.transport_zone_left_slot.addWidget(transport) - self.transport_vsep_left.setVisible(True) + transport.zone_index = hstack_transport_zone_left.count() + hstack_transport_zone_left.addWidget(transport) + vsep_transport_left.setVisible(True) elif transport.zone == "right": - transport.zone_index = self.transport_zone_right_slot.count() - self.transport_zone_right_slot.addWidget(transport) - self.transport_vsep_right.setVisible(True) + transport.zone_index = hstack_transport_zone_right.count() + hstack_transport_zone_right.addWidget(transport) + vsep_transport_right.setVisible(True) else: - idx = transport.center_index if transport.center_index is not None else self.transport_row.count() - self.transport_row.insertWidget(min(idx, self.transport_row.count()), transport) + idx = transport.center_index if transport.center_index is not None else hstack_transport_center.count() + hstack_transport_center.insertWidget(min(idx, hstack_transport_center.count()), transport) transport.center_index = None transport.zone_index = None - self.transport_vsep_left.setVisible(any(t.zone == "left" for t in self.transports)) - self.transport_vsep_right.setVisible(any(t.zone == "right" for t in self.transports)) + vsep_transport_left.setVisible(any(t.zone == "left" for t in self.transports)) + vsep_transport_right.setVisible(any(t.zone == "right" for t in self.transports)) self.refresh_all_id_labels() def toggle_osc_log(self, checked): - self.osc_log_panel.setVisible(checked) - self.osc_out_log_panel.setVisible(checked) + container_osc_in_log.setVisible(checked) + container_osc_out_log.setVisible(checked) self._position_osc_log() def _position_osc_log(self): geo = self.frameGeometry() h = geo.height() # In panel to the right - self.osc_log_panel.setFixedHeight(h) - self.osc_log_panel.move(geo.right() + 4, geo.top()) + container_osc_in_log.setFixedHeight(h) + container_osc_in_log.move(geo.right() + 4, geo.top()) # Out panel stacked below in panel - self.osc_out_log_panel.setFixedWidth(360) - self.osc_out_log_panel.setFixedHeight(h // 2) - self.osc_out_log_panel.move(geo.right() + 4, geo.top() + h // 2 + 4) + 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) # Adjust in panel height - self.osc_log_panel.setFixedHeight(h // 2 - 4) + container_osc_in_log.setFixedHeight(h // 2 - 4) def resizeEvent(self, event): super().resizeEvent(event) - if self.osc_log_panel.isVisible(): + if container_osc_in_log.isVisible(): self._position_osc_log() def moveEvent(self, event): super().moveEvent(event) - if self.osc_log_panel.isVisible(): + if container_osc_in_log.isVisible(): self._position_osc_log() def on_osc_raw(self, addr, val): from datetime import datetime ts = datetime.now().strftime("%H:%M:%S") line = f"{ts} {addr} {val}" - self.osc_log.append(line) - doc = self.osc_log.document() + textedit_osc_in_log.append(line) + doc = textedit_osc_in_log.document() if doc.blockCount() > 200: - cursor = self.osc_log.textCursor() + cursor = textedit_osc_in_log.textCursor() cursor.movePosition(cursor.MoveOperation.Start) cursor.select(cursor.SelectionType.LineUnderCursor) cursor.removeSelectedText() @@ -1437,10 +1746,10 @@ class MainWindow(QMainWindow): from datetime import datetime ts = datetime.now().strftime("%H:%M:%S") line = f"{ts} {addr} {val}" - self.osc_out_log.append(line) - doc = self.osc_out_log.document() + textedit_osc_out_log.append(line) + doc = textedit_osc_out_log.document() if doc.blockCount() > 200: - cursor = self.osc_out_log.textCursor() + cursor = textedit_osc_out_log.textCursor() cursor.movePosition(cursor.MoveOperation.Start) cursor.select(cursor.SelectionType.LineUnderCursor) cursor.removeSelectedText() @@ -1455,53 +1764,53 @@ class MainWindow(QMainWindow): def toggle_osc(self): if _osc_server is not None: stop_osc_server() - self.osc_status.setText("Not Listening") - self.osc_status.setStyleSheet("color: #888;") - self.osc_toggle_btn.setText("Start OSC") - self.osc_blink.setStyleSheet("color: #444; font-size: 14px;") - self.osc_panel.setVisible(False) + label_connection_osc.setText("Not Listening") + label_connection_osc.setStyleSheet("color: #888;") + button_osc_toggle.setText("Start OSC") + activity_indicator_osc.setStyleSheet("color: #444; font-size: 14px;") + container_osc_feedback.setVisible(False) else: - port = self.osc_port_spin.value() + port = spinner_osc_listen_port.value() ok = start_osc_server(port) if ok: - self.osc_status.setText(f"Listening :{port}") - self.osc_status.setStyleSheet("color: #27ae60;") - self.osc_toggle_btn.setText("Stop OSC") - self.osc_blink.setStyleSheet("color: #2ecc71; font-size: 14px;") - self.osc_panel.setVisible(True) + label_connection_osc.setText(f"Listening :{port}") + label_connection_osc.setStyleSheet("color: #27ae60;") + button_osc_toggle.setText("Stop OSC") + activity_indicator_osc.setStyleSheet("color: #2ecc71; font-size: 14px;") + container_osc_feedback.setVisible(True) else: - self.osc_status.setText("Failed") - self.osc_status.setStyleSheet("color: #c0392b;") + label_connection_osc.setText("Failed") + label_connection_osc.setStyleSheet("color: #c0392b;") def on_osc_track(self, name): - self.osc_track_lbl.setText(name) - self.blink(self.osc_blink) + label_osc_track.setText(name) + self.blink(activity_indicator_osc) def on_osc_preset(self, name): - self.osc_preset_lbl.setText(name) - self.blink(self.osc_blink) + label_osc_preset.setText(name) + self.blink(activity_indicator_osc) def on_osc_tempo(self, bpm): - self.osc_tempo_lbl.setText(f"BPM: {bpm:.1f}") + label_osc_tempo.setText(f"BPM: {bpm:.1f}") def on_osc_position(self, pos): - self.osc_pos_lbl.setText(f"Pos: {pos}") + label_osc_position.setText(f"Pos: {pos}") def on_osc_play(self, state): if state: - self.osc_play_lbl.setText("▶ PLAYING") - self.osc_play_lbl.setStyleSheet("color: #00e676; font-size: 11px; font-weight: bold;") + label_osc_play_state.setText("▶ PLAYING") + label_osc_play_state.setStyleSheet("color: #00e676; font-size: 11px; font-weight: bold;") else: - self.osc_play_lbl.setText("■ STOPPED") - self.osc_play_lbl.setStyleSheet("color: #555; font-size: 11px; font-weight: bold;") + label_osc_play_state.setText("■ STOPPED") + label_osc_play_state.setStyleSheet("color: #555; font-size: 11px; font-weight: bold;") def on_osc_record(self, state): if state: - self.osc_record_lbl.setText("● REC") - self.osc_record_lbl.setStyleSheet("color: #e53935; font-size: 11px; font-weight: bold;") + label_osc_record_state.setText("● REC") + label_osc_record_state.setStyleSheet("color: #e53935; font-size: 11px; font-weight: bold;") else: - self.osc_record_lbl.setText("● REC") - self.osc_record_lbl.setStyleSheet("color: #555; font-size: 11px; font-weight: bold;") + label_osc_record_state.setText("● REC") + label_osc_record_state.setStyleSheet("color: #555; font-size: 11px; font-weight: bold;") def fire_transport(self, name): for t in self.transports: diff --git a/presets/presets.json b/presets/presets.json index b3679c1..30d9a07 100644 --- a/presets/presets.json +++ b/presets/presets.json @@ -1,5 +1,5 @@ { - "__last_used__": null, + "__last_used__": "gay3", "presets": { "1": { "midi_port": "IAC Driver Bus 1", @@ -1027,7 +1027,7 @@ "hw_cc": null, "output_mode": "osc", "osc_addr": "/play", - "trigger_mode": "momentary" + "trigger_mode": "toggle" }, { "uid": "e2e8eb84-c2c9-4060-a818-b087ebd5aabd", @@ -1041,7 +1041,7 @@ "hw_cc": null, "output_mode": "osc", "osc_addr": "/play", - "trigger_mode": "momentary" + "trigger_mode": "toggle" }, { "uid": "6b5bcaf7-1d01-4ccd-b23a-ebae8da57a7f", @@ -1055,7 +1055,7 @@ "hw_cc": null, "output_mode": "osc", "osc_addr": "/play", - "trigger_mode": "momentary" + "trigger_mode": "toggle" }, { "uid": "2a1fcb50-b9f2-4c33-bce0-5bd5cadd9c46", @@ -1069,7 +1069,7 @@ "hw_cc": null, "output_mode": "osc", "osc_addr": "/play", - "trigger_mode": "momentary" + "trigger_mode": "toggle" } ], "browser": {