diff --git a/main.py b/main.py index 270f1c1..5de6f0d 100644 --- a/main.py +++ b/main.py @@ -36,6 +36,12 @@ from styles import ( from presets import ( load_presets_file, save_presets_file, + ensure_preset_structure, + on_preset_selected, + save_preset, + save_preset_as, + delete_preset, + ) from zonebutton import ZoneButton @@ -129,97 +135,180 @@ def stop_osc_server(): _osc_server = None _osc_thread = None +def set_app_window(window: QMainWindow): + global app_window + app_window = window +#MARK: GLOBALS +ready = False +selected_strip = None +learning_strip = None +faders = [] +strip_width = 100 +spacing = 10 +padding = 40 +loaded_presets = load_presets_file() +app_window = None + +#MARK: CREATE APP +app = QApplication(sys.argv) +app.setStyle("Fusion") + +#MARK: MAIN WINDOW +containerMain = QWidget() + +#MARK: PRESET ROW +hstack_preset_row = QHBoxLayout() + +#MARK: PRESENT +menuPresets = QComboBox() +menuPresets.setMinimumWidth(160) +menuPresets.currentTextChanged.connect(lambda name: on_preset_selected(menuPresets, app_window, ready, loaded_presets)) + +#button preset save +buttonSave = QPushButton("Save") +buttonSave.setFixedWidth(50) +buttonSave.clicked.connect(lambda: save_preset(app_window, menuPresets, loaded_presets)) + +#button preset save as -> save_as_btn +buttonSaveAs = QPushButton("Save As") +buttonSaveAs.setFixedWidth(60) +buttonSaveAs.clicked.connect(lambda: save_preset_as(app_window, menuPresets, loaded_presets)) + +#button preset delete -> del_btn +buttonRemove = QPushButton("Remove") +buttonRemove.setFixedWidth(65) +buttonRemove.clicked.connect(lambda: delete_preset(app_window, menuPresets, loaded_presets)) + +#save status label (should be used for other stuff as well) -> save_status_label +labelStatus = QLabel("") +labelStatus.setStyleSheet("color: #00e676; font-style: italic;") + +#container io row +hstack_io_row = QHBoxLayout() +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) + +# hstack_midi_in_activity_and_label +hstack_midi_in_activity_and_label = QHBoxLayout() + +# activity_indicator_midi_in +activity_indicator_midi_in = QLabel("●") +activity_indicator_midi_in.setStyleSheet("color: #444; font-size: 14px; background-color: rgba(255, 0, 0, 0.15); border: 1px solid red; padding: 2px;") + +# label_connection_midi_in +label_connection_midi_in = QLabel("Not Connected") +label_connection_midi_in.setStyleSheet("color: #888; background-color: rgba(0, 0, 255, 0.15); border: 1px solid blue; padding: 2px;") + +# label_midi_in +label_midi_in = QLabel("MIDI Input") +label_midi_in.setStyleSheet("background-color: rgba(255, 255, 0, 0.15); border: 1px solid yellow; padding: 2px;") + +# picker_midi_in +picker_midi_in = QComboBox() +picker_midi_in.setFixedWidth(200) +picker_midi_in.setStyleSheet("background-color: rgba(0, 255, 0, 0.15); border: 1px solid green;") + - class MainWindow(QMainWindow): def __init__(self): super().__init__() - self.setWindowTitle("MIDI Controller") - self.ready = False - self.selected_strip = None - self.learning_strip = None - self.faders = [] - self.strip_width = 100 - self.spacing = 10 - self.padding = 40 - self.data = load_presets_file() - if "presets" not in self.data: - self.data = {"__last_used__": None, "presets": self.data} + global loaded_presets + global containerPresetRow + global menuPresets + global buttonSave + global buttonSaveAs + global buttonRemove + global labelStatus + global hstack_io_row + global available_ports_midi_in + + self.setWindowTitle("Controller") + #set app window reference + set_app_window(self) - central = QWidget() - self.setCentralWidget(central) - main_layout = QVBoxLayout(central) + # ui main window + self.setCentralWidget(containerMain) + main_layout = QVBoxLayout(containerMain) - # --- Preset manager --- - preset_row = QHBoxLayout() - - self.preset_combo = QComboBox() - self.preset_combo.setMinimumWidth(160) - self.preset_combo.currentTextChanged.connect(self.on_preset_selected) - preset_row.addWidget(self.preset_combo) - - save_btn = QPushButton("Save") - save_btn.setFixedWidth(50) - save_btn.clicked.connect(self.save_preset) - preset_row.addWidget(save_btn) - - save_as_btn = QPushButton("Save As") - save_as_btn.setFixedWidth(60) - save_as_btn.clicked.connect(self.save_preset_as) - preset_row.addWidget(save_as_btn) - - del_btn = QPushButton("Remove") - del_btn.setFixedWidth(65) - del_btn.clicked.connect(self.delete_preset) - preset_row.addWidget(del_btn) - - preset_row.addStretch() - - self.save_status_label = QLabel("") - self.save_status_label.setStyleSheet("color: #00e676; font-style: italic;") - main_layout.addLayout(preset_row) + #MARK: PRESETS ROW + # ui preset row + drop down + buttont + main_layout.addLayout(hstack_preset_row) + + hstack_preset_row.addWidget(menuPresets) #dropdown + hstack_preset_row.addWidget(buttonSave) #save button + hstack_preset_row.addWidget(buttonSaveAs) #save as button + hstack_preset_row.addWidget(buttonRemove) #remove button + hstack_preset_row.addStretch() + + #section seperator main_layout.addWidget(self._separator()) - # --- MIDI I/O row --- - io_row = QHBoxLayout() - io_row.setSpacing(16) + #MARK: MIDI IN COLUMN + #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) + hstack_midi_in_activity_and_label.addStretch() - # Input side - in_col = QVBoxLayout() - in_col.setSpacing(2) - in_top = QHBoxLayout() - self.in_blink = QLabel("●") - self.in_blink.setStyleSheet("color: #444; font-size: 14px;") - self.in_status = QLabel("Not Connected") - self.in_status.setStyleSheet("color: #888;") - in_top.addWidget(self.in_blink) - in_top.addWidget(self.in_status) - in_top.addStretch() - in_col.addLayout(in_top) - in_col.addWidget(QLabel("MIDI Input")) - self.in_combo = QComboBox() - self.in_combo.setFixedWidth(200) - self.available_in_ports = get_available_input_ports() # list of (display_name, index, available) - for display_name, idx, available in self.available_in_ports: - self.in_combo.addItem(display_name) + #add all 3 to the vstack + vstack_midi_in.addLayout(hstack_midi_in_activity_and_label) #activity indicator and connection label + vstack_midi_in.addWidget(label_midi_in) # midi input label + vstack_midi_in.addWidget(picker_midi_in) # dropdown menu + + # 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: + picker_midi_in.addItem(display_name) if not available: - # Gray out the item - item_index = self.in_combo.count() - 1 - self.in_combo.model().item(item_index).setEnabled(False) - self.in_combo.model().item(item_index).setForeground(QColor('#888888')) - self.in_combo.currentTextChanged.connect(self.on_input_port_select) - in_col.addWidget(self.in_combo) - io_row.addLayout(in_col) + item_index = picker_midi_in.count() - 1 + picker_midi_in.model().item(item_index).setEnabled(False) + picker_midi_in.model().item(item_index).setForeground(QColor('#888888')) + picker_midi_in.currentTextChanged.connect(self.on_input_port_select) + + + + + + + + + + + + + + + + + + #MARK: COL 2 MIDI OUT # Output side out_col = QVBoxLayout() out_col.setSpacing(2) @@ -238,8 +327,9 @@ class MainWindow(QMainWindow): self.port_combo.setFixedWidth(200) self.port_combo.currentTextChanged.connect(self.on_port_select) out_col.addWidget(self.port_combo) - io_row.addLayout(out_col) + hstack_io_row.addLayout(out_col) + #MARK: COL 3 TRANSPORT # Transport Output column t_col = QVBoxLayout() t_col.setSpacing(2) @@ -258,8 +348,10 @@ class MainWindow(QMainWindow): self.t_out_combo.setFixedWidth(200) self.t_out_combo.currentTextChanged.connect(self.on_transport_output_select) t_col.addWidget(self.t_out_combo) - io_row.addLayout(t_col) + hstack_io_row.addLayout(t_col) + + # #MARK: COL 4 OSC # OSC column osc_col = QVBoxLayout() osc_col.setSpacing(2) @@ -315,12 +407,14 @@ class MainWindow(QMainWindow): osc_btn_row.addWidget(self.osc_log_btn) osc_btn_row.addStretch() osc_col.addLayout(osc_btn_row) - io_row.addLayout(osc_col) + hstack_io_row.addLayout(osc_col) - io_row.addStretch() - main_layout.addLayout(io_row) + 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;") @@ -358,6 +452,7 @@ class MainWindow(QMainWindow): osc_div.setStyleSheet("background-color: #3a3a3a;") osc_panel_layout.addWidget(osc_div) + # Bottom row — track and preset info osc_info_row = QHBoxLayout() osc_info_row.setSpacing(8) @@ -455,6 +550,8 @@ class MainWindow(QMainWindow): 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() @@ -492,13 +589,15 @@ class MainWindow(QMainWindow): main_layout.addWidget(self._separator()) + + #MARK: TRANSPORT ROW # --- Transport --- self.transports = [] self.transport_strip_width = 100 self._capturing_key_for = None self.full_transport_row = QHBoxLayout() - self.full_transport_row.setSpacing(self.spacing) + self.full_transport_row.setSpacing(spacing) main_layout.addLayout(self.full_transport_row) self.transport_zone_left_slot = QHBoxLayout() @@ -513,7 +612,7 @@ class MainWindow(QMainWindow): self.full_transport_row.addWidget(self.transport_vsep_left) self.transport_row = QHBoxLayout() - self.transport_row.setSpacing(self.spacing) + self.transport_row.setSpacing(spacing) self.full_transport_row.addLayout(self.transport_row) self.transport_vsep_right = QFrame() @@ -529,6 +628,7 @@ class MainWindow(QMainWindow): main_layout.addWidget(self._separator()) + #MARK: SELECTED CHANNEL ROW # --- Selected channel label row --- info_row = QHBoxLayout() info_row.addStretch() @@ -537,13 +637,15 @@ class MainWindow(QMainWindow): self.selected_label.setAlignment(Qt.AlignmentFlag.AlignCenter) info_row.addWidget(self.selected_label) info_row.addStretch() - self.save_status_label.setParent(None) - info_row.addWidget(self.save_status_label) + labelStatus.setParent(None) + info_row.addWidget(labelStatus) main_layout.addLayout(info_row) + + #MARK: FADERS ROW # --- Faders row --- self.full_fader_row = QHBoxLayout() - self.full_fader_row.setSpacing(self.spacing) + self.full_fader_row.setSpacing(spacing) main_layout.addLayout(self.full_fader_row) # Zone Left slot @@ -561,7 +663,7 @@ class MainWindow(QMainWindow): # Custom fader row self.fader_row = QHBoxLayout() - self.fader_row.setSpacing(self.spacing) + self.fader_row.setSpacing(spacing) self.full_fader_row.addLayout(self.fader_row) # Right vertical separator — always visible @@ -578,12 +680,14 @@ class MainWindow(QMainWindow): self.full_fader_row.addLayout(self.zone_right_slot) 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(self.spacing) + self.full_toggle_row.setSpacing(spacing) main_layout.addLayout(self.full_toggle_row) self.toggle_zone_left_slot = QHBoxLayout() @@ -614,20 +718,29 @@ class MainWindow(QMainWindow): main_layout.addStretch() + + + #MARK: SET DEFAULTS # Add initial 2 faders, 1 toggle, 4 transport strips + + #load presets + loaded_presets = ensure_preset_structure(loaded_presets) + self.add_fader() self.add_fader() self.add_toggle() for name, cc in [("Play", 117), ("Stop", 116), ("Record", 118), ("Arm", 119)]: self.add_transport(name, cc) - self.ready = True + global ready + ready = True + self.refresh_preset_combo() self.restore_last_used() self.refresh_all_id_labels() self.refresh_port_availability() - midi_receiver.midi_in_signal.connect(lambda msg: self.blink(self.in_blink)) + 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.hw_cc_signal.connect(self.on_hw_cc) @@ -642,17 +755,22 @@ class MainWindow(QMainWindow): osc_receiver.raw_message_signal.connect(self.on_osc_raw) osc_receiver.osc_out_signal.connect(self.on_osc_out_raw) + + def refresh_all_id_labels(self): # Center faders - center = [f for f in self.faders if not f.zone] + + global faders + + center = [f for f in faders if not f.zone] for i, f in enumerate(center): f.update_id_label(None, i) # Left zone faders - left = sorted([f for f in self.faders if f.zone == "left"], key=lambda f: f.zone_index or 0) + left = sorted([f for f in faders if f.zone == "left"], key=lambda f: f.zone_index or 0) for i, f in enumerate(left): f.update_id_label("left", i) # Right zone faders - right = sorted([f for f in self.faders if f.zone == "right"], key=lambda f: f.zone_index or 0) + right = sorted([f for f in faders if f.zone == "right"], key=lambda f: f.zone_index or 0) for i, f in enumerate(right): f.update_id_label("right", i) # Center toggles @@ -717,10 +835,13 @@ class MainWindow(QMainWindow): def on_fader_zone_change(self, fader): # Save center index before removing from center row + + global faders + if fader.zone is not None and fader.center_index is None: # Being zoned for first time — save current center position - if fader in self.faders: - fader.center_index = self.faders.index(fader) + if fader in faders: + 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]: @@ -747,18 +868,20 @@ class MainWindow(QMainWindow): self.fader_row.insertWidget(idx, fader) fader.center_index = None fader.zone_index = None - self.vsep_left.setVisible(any(f.zone == "left" for f in self.faders)) - self.vsep_right.setVisible(any(f.zone == "right" for f in self.faders)) - has_left = any(f.zone == "left" for f in self.faders) - has_right = any(f.zone == "right" for f in self.faders) + self.vsep_left.setVisible(any(f.zone == "left" for f in faders)) + self.vsep_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) self.update_window_width() + self.refresh_all_id_labels() def select_strip(self, strip): - if self.selected_strip and self.selected_strip != strip: - self.selected_strip.set_selected(False) - self.selected_strip = strip + global selected_strip + if selected_strip and selected_strip != strip: + selected_strip.set_selected(False) + selected_strip = strip strip.set_selected(True) self.selected_label.setText(f"Selected: {strip.title_edit.text()}") @@ -781,11 +904,12 @@ class MainWindow(QMainWindow): self.refresh_all_id_labels() def remove_toggle(self, toggle): + global selected_strip if len(self.toggles) <= 1: return - if self.selected_strip == toggle: - self.selected_strip = None - self.selected_label.setText("Selected: None") + if selected_strip == toggle: + selected_strip = None + self.selected_label.setText("Selected: None") self.toggle_row.removeWidget(toggle) toggle.deleteLater() self.toggles.remove(toggle) @@ -793,14 +917,17 @@ class MainWindow(QMainWindow): self.refresh_all_id_labels() def add_fader(self, insert_after=None): - n = len(self.faders) + 1 + + global faders + + n = len(faders) + 1 fader = FaderWidget(f"Fader {n}", default_cc=1) - if insert_after is not None and insert_after in self.faders: - idx = self.faders.index(insert_after) - self.faders.insert(idx + 1, fader) + 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) else: - self.faders.append(fader) + faders.append(fader) self.fader_row.addWidget(fader) fader.plus_btn.clicked.connect(lambda: self.add_fader(insert_after=fader)) fader.minus_btn.clicked.connect(lambda: self.remove_fader(fader)) @@ -811,22 +938,31 @@ class MainWindow(QMainWindow): self.refresh_all_id_labels() def remove_fader(self, fader): - if len(self.faders) <= 1: + + global selected_strip + global faders + global spacing + + if len(faders) <= 1: return - if self.selected_strip == fader: - self.selected_strip = None + if selected_strip == fader: + selected_strip = None self.selected_label.setText("Selected: None") self.fader_row.removeWidget(fader) fader.deleteLater() - self.faders.remove(fader) + faders.remove(fader) self.update_window_width() self.refresh_all_id_labels() def update_window_width(self): - nf = len(self.faders) + + global faders + global strip_width + + nf = len(faders) nt = len(self.toggles) - fader_w = (nf * self.strip_width) + ((nf - 1) * self.spacing) + self.padding - toggle_w = (nt * self.toggle_strip_width) + ((nt - 1) * self.spacing) + self.padding + fader_w = (nf * strip_width) + ((nf - 1) * spacing) + padding + toggle_w = (nt * self.toggle_strip_width) + ((nt - 1) * spacing) + padding w = max(fader_w, toggle_w, 300) self.resize(w, self.height()) @@ -838,27 +974,34 @@ class MainWindow(QMainWindow): return sep def refresh_preset_combo(self): - self.preset_combo.blockSignals(True) - self.preset_combo.clear() - self.preset_combo.addItems(sorted(self.data["presets"].keys())) - self.preset_combo.blockSignals(False) + + global loaded_presets + global menuPresets + + menuPresets.blockSignals(True) + menuPresets.clear() + menuPresets.addItems(sorted(loaded_presets["presets"].keys())) + menuPresets.blockSignals(False) def restore_last_used(self): - last = self.data.get("__last_used__") - if last and last in self.data["presets"]: - self.preset_combo.blockSignals(True) - self.preset_combo.setCurrentText(last) - self.preset_combo.blockSignals(False) - self.apply_preset(self.data["presets"][last]) + + global loaded_presets + global menuPresets + + last = loaded_presets.get("__last_used__") + if last and last in loaded_presets["presets"]: + menuPresets.blockSignals(True) + menuPresets.setCurrentText(last) + menuPresets.blockSignals(False) + self.apply_preset(loaded_presets["presets"][last]) print(f"Restored last used preset: {last}") - def on_preset_selected(self, name): - if self.ready and name and name in self.data["presets"]: - self.data["__last_used__"] = name - save_presets_file(self.data) - self.apply_preset(self.data["presets"][name]) + def _show_save_status(self): + + global labelStatus + from datetime import datetime, date now = datetime.now() today = date.today() @@ -869,15 +1012,18 @@ class MainWindow(QMainWindow): time_str = f"Yesterday {time_part}" else: time_str = f"{now.strftime('%b')} {now.day} {time_part}" - self.save_status_label.setText(f"[ Saved {time_str} ]") - QTimer.singleShot(4000, lambda: self.save_status_label.setText("")) + labelStatus.setText(f"[ Saved {time_str} ]") + QTimer.singleShot(4000, lambda: labelStatus.setText("")) def _build_preset_data(self, name): + + global faders + return { "midi_port": self.port_combo.currentText(), - "midi_input": self.in_combo.currentText(), + "midi_input": picker_midi_in.currentText(), "transport_port": self.t_out_combo.currentText(), - "faders": [f.get_state() for f in self.faders], + "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": { @@ -891,52 +1037,12 @@ class MainWindow(QMainWindow): } } - def save_preset(self): - name = self.preset_combo.currentText() - if not name or name not in self.data["presets"]: - # No current preset — fall through to Save As - self.save_preset_as() - return - self.data["presets"][name] = self._build_preset_data(name) - self.data["__last_used__"] = name - save_presets_file(self.data) - self._show_save_status() - print(f"Saved preset: {name}") - - def save_preset_as(self): - name, ok = QInputDialog.getText(self, "Save As", "Preset name:") - if not ok or not name.strip(): - return - name = name.strip() - self.data["presets"][name] = self._build_preset_data(name) - self.data["__last_used__"] = name - save_presets_file(self.data) - self.refresh_preset_combo() - self.preset_combo.setCurrentText(name) - self._show_save_status() - print(f"Saved preset as: {name}") - - def delete_preset(self): - from PyQt6.QtWidgets import QMessageBox - name = self.preset_combo.currentText() - if not name or name not in self.data["presets"]: - return - msg = QMessageBox(self) - msg.setWindowTitle("Remove Preset") - msg.setText(f'Are you sure you want to remove "{name}"?') - msg.setIcon(QMessageBox.Icon.NoIcon) - msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) - reply = msg.exec() - if reply != QMessageBox.StandardButton.Yes: - return - del self.data["presets"][name] - if self.data["__last_used__"] == name: - self.data["__last_used__"] = None - save_presets_file(self.data) - self.refresh_preset_combo() - print(f"Deleted preset: {name}") def apply_preset(self, preset): + + global faders + global available_ports_midi_in + port_name = preset.get("midi_port", "") if port_name in available_ports: self.port_combo.setCurrentText(port_name) @@ -946,23 +1052,23 @@ class MainWindow(QMainWindow): self.t_out_combo.setCurrentText(transport_port) midi_input = preset.get("midi_input", "") - input_names = [entry[0] for entry in self.available_in_ports] + input_names = [entry[0] for entry in available_ports_midi_in] if midi_input in input_names: - self.in_combo.setCurrentText(midi_input) + picker_midi_in.setCurrentText(midi_input) fader_states = preset.get("faders", []) - while len(self.faders) < len(fader_states): + while len(faders) < len(fader_states): self.add_fader() - while len(self.faders) > len(fader_states): - widget = self.faders.pop() + while len(faders) > len(fader_states): + widget = faders.pop() self.fader_row.removeWidget(widget) widget.deleteLater() self.update_window_width() - for i, widget in enumerate(self.faders): + for i, widget in enumerate(faders): widget.set_state(fader_states[i]) # First place all center faders in their saved positions center_faders = sorted( - [f for f in self.faders if not f.zone], + [f for f in faders if not f.zone], key=lambda f: f.center_index if f.center_index is not None else 999 ) for f in center_faders: @@ -973,11 +1079,11 @@ class MainWindow(QMainWindow): # Then place zoned faders in zone order left_faders = sorted( - [f for f in self.faders if f.zone == "left"], + [f for f in faders if f.zone == "left"], key=lambda f: f.zone_index if f.zone_index is not None else 999 ) right_faders = sorted( - [f for f in self.faders if f.zone == "right"], + [f for f in faders if f.zone == "right"], key=lambda f: f.zone_index if f.zone_index is not None else 999 ) for f in left_faders: @@ -1057,16 +1163,24 @@ class MainWindow(QMainWindow): self.osc_port_spin.setValue(osc.get("listen_port", 9000)) self.osc_send_port_spin.setValue(osc.get("send_port", 8000)) + + + + + def on_input_port_select(self, display_name): + + global available_ports_midi_in + if not display_name: return - port_entry = next((entry for entry in self.available_in_ports if entry[0] == display_name), None) + port_entry = next((entry for entry in available_ports_midi_in if entry[0] == display_name), None) if port_entry is None: return display_name, original_index, available = port_entry if not available: - self.in_status.setText("In use by another program") - self.in_status.setStyleSheet("color: #e67e22;") + label_connection_midi_in.setText("In use by another program") + label_connection_midi_in.setStyleSheet("color: #e67e22;") return if hasattr(self, "midi_in"): close_midi_input_port(self.midi_in) @@ -1074,16 +1188,19 @@ class MainWindow(QMainWindow): try: self.midi_in = open_midi_input_port(original_index, self.on_midi_in) - self.in_status.setText("Connected") - self.in_status.setStyleSheet("color: #27ae60;") + label_connection_midi_in.setText("Connected") + label_connection_midi_in.setStyleSheet("color: #27ae60;") print(f"Opened input port: {display_name}") except Exception as e: print(f"Failed to open port {display_name}: {e}") - self.in_status.setText("Failed to open") - self.in_status.setStyleSheet("color: #c0392b;") + label_connection_midi_in.setText("Failed to open") + label_connection_midi_in.setStyleSheet("color: #c0392b;") def on_hw_cc(self, fader_uid, value): - for fader in self.faders: + + global faders + + for fader in faders: if fader.uid == fader_uid: out_cc = fader.cc_spin.value() print(f"[convert] HW CC{fader.hw_cc} ({value}) → CC{out_cc} ({value})") @@ -1121,12 +1238,19 @@ class MainWindow(QMainWindow): return def start_learn(self, fader): - if self.learning_strip and self.learning_strip != fader: - self.learning_strip.stop_learn() - self.learning_strip = fader + + global learning_strip + + if learning_strip and learning_strip != fader: + learning_strip.stop_learn() + learning_strip = fader fader.start_learn() def on_midi_in(self, event, data=None): + + global learning_strip + global faders + message, _ = event status = message[0] & 0xF0 cc_num = message[1] if len(message) >= 2 else None @@ -1136,7 +1260,7 @@ class MainWindow(QMainWindow): bound_fader = None bound_transport = None if status == 0xB0: - for fader in self.faders: + for fader in faders: if fader.hw_cc == cc_num: bound_fader = fader break @@ -1152,9 +1276,9 @@ class MainWindow(QMainWindow): break # Learning mode — bind CC to strip, don't forward - if status == 0xB0 and self.learning_strip: - self.learning_strip.bind_hw_cc(cc_num) - self.learning_strip = None + if status == 0xB0 and learning_strip: + learning_strip.bind_hw_cc(cc_num) + learning_strip = None return if bound_transport: @@ -1416,8 +1540,9 @@ class MainWindow(QMainWindow): super().keyPressEvent(event) -app = QApplication(sys.argv) -app.setStyle("Fusion") + + + # VS Code dark theme palette dark_palette = app.palette() diff --git a/presets.py b/presets.py index c8c8bf1..90f5518 100644 --- a/presets.py +++ b/presets.py @@ -1,11 +1,20 @@ import json import os +from PyQt6.QtWidgets import ( + QApplication, QMainWindow, QWidget, QVBoxLayout, + QHBoxLayout, QSlider, QComboBox, QLabel, QPushButton, + QSizePolicy, QSpinBox, QLineEdit, QInputDialog, QFrame, + QCheckBox, QMessageBox +) + + BASE_DIR = os.path.dirname(os.path.abspath(__file__)) PRESETS_DIR = os.path.join(BASE_DIR, "presets") PRESETS_FILE = os.path.join(PRESETS_DIR, "presets.json") os.makedirs(PRESETS_DIR, exist_ok=True) + def load_presets_file(): if os.path.exists(PRESETS_FILE): with open(PRESETS_FILE, "r") as f: @@ -14,4 +23,78 @@ def load_presets_file(): def save_presets_file(data): with open(PRESETS_FILE, "w") as f: - json.dump(data, f, indent=2) \ No newline at end of file + json.dump(data, f, indent=2) + +def ensure_preset_structure(presets): + if "presets" not in presets: + return {"__last_used__": None, "presets": presets} + return presets + +def on_preset_selected(menu_presets: QComboBox, app_window: QMainWindow, ready: bool, loaded_presets: dict): + + selectedOption = menu_presets.currentText() + + if ready and selectedOption and selectedOption in loaded_presets["presets"]: + loaded_presets["__last_used__"] = selectedOption + save_presets_file(loaded_presets) + app_window.apply_preset(loaded_presets["presets"][selectedOption]) + +def save_preset(app_window: QMainWindow, menu_presets: QComboBox, loaded_presets: dict): + + selectedOption = menu_presets.currentText() + + if not selectedOption or selectedOption not in loaded_presets["presets"]: + app_window.save_preset_as() # not self + return + loaded_presets["presets"][selectedOption] = app_window._build_preset_data(selectedOption) # not self + loaded_presets["__last_used__"] = selectedOption + save_presets_file(loaded_presets) + app_window._show_save_status() # not self + print(f"Saved preset: {selectedOption}") + +def save_preset_as(app_window: QMainWindow, menu_presets: QComboBox, loaded_presets: dict): + + print(f"save as button tapped") + + selectedOption = menu_presets.currentText() + + selectedOption, ok = QInputDialog.getText(app_window, "Save As", "Preset name:") + if not ok or not selectedOption.strip(): + return + selectedOption = selectedOption.strip() + loaded_presets["presets"][selectedOption] = app_window._build_preset_data(selectedOption) + loaded_presets["__last_used__"] = selectedOption + save_presets_file(loaded_presets) + app_window.refresh_preset_combo() + menu_presets.setCurrentText(selectedOption) + app_window._show_save_status() + print(f"Saved preset as: {selectedOption}") + + +def delete_preset(app_window: QMainWindow, menu_presets: QComboBox, loaded_presets: dict): + + selectedOption = menu_presets.currentText() + + selectedOption = menu_presets.currentText() + + if not selectedOption or selectedOption not in loaded_presets["presets"]: + return + + msg = QMessageBox(app_window) + msg.setWindowTitle("Remove Preset") + msg.setText(f'Are you sure you want to remove "{selectedOption}"?') + msg.setIcon(QMessageBox.Icon.NoIcon) + msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) + reply = msg.exec() + + if reply != QMessageBox.StandardButton.Yes: + return + + del loaded_presets["presets"][selectedOption] + + if loaded_presets["__last_used__"] == selectedOption: + loaded_presets["__last_used__"] = None + + save_presets_file(loaded_presets) + app_window.refresh_preset_combo() + print(f"Deleted preset: {selectedOption}") \ No newline at end of file diff --git a/presets/presets.json b/presets/presets.json index e94b86a..b3679c1 100644 --- a/presets/presets.json +++ b/presets/presets.json @@ -1,29 +1,42 @@ { - "__last_used__": "test1", + "__last_used__": null, "presets": { - "test1": { + "1": { "midi_port": "IAC Driver Bus 1", - "midi_input": "VMPK Output", + "midi_input": "IAC Driver Bus 1", "transport_port": "IAC Driver Bus 1", "faders": [ { - "uid": "5ca2258a-24da-41b1-8d4a-464b52fc620a", + "uid": "7aa48acf-7d6b-47fa-a8b4-e10223bb62c5", "label": "Fader 1", "cc": 1, - "value": 127, - "color": "Green", + "value": 64, + "color": "Orange", "pickup": false, - "last_sent": 127, - "hw_cc": 1, + "last_sent": 64, + "hw_cc": null, "zone": null, "center_index": null, "zone_index": null }, { - "uid": "9dd10748-366b-4305-80ae-70b087bd4900", + "uid": "2b5abf64-9465-4191-a0e3-f14f35fcaf71", "label": "Fader 2", "cc": 1, "value": 64, + "color": "Yellow", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "aa1bd51a-f53a-4b54-8d1e-44667f7c5678", + "label": "Fader 3", + "cc": 1, + "value": 64, "color": "Gray", "pickup": false, "last_sent": 64, @@ -33,7 +46,133 @@ "zone_index": null }, { - "uid": "a5b03539-5a4b-44cf-9e90-0d97c4c18292", + "uid": "a0ce87e5-29fa-47b9-bd69-8f958c5f0cbc", + "label": "Fader 4", + "cc": 1, + "value": 97, + "color": "Gray", + "pickup": false, + "last_sent": 97, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + } + ], + "toggles": [ + { + "uid": "2f1773e4-9df2-4c14-9960-68eb1fe1c98e", + "label": "Toggle 1", + "cc": 20, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null + } + ], + "transports": [ + { + "uid": "9ee09db7-2cf5-4ae6-a154-b25bb262565c", + "label": "Play", + "cc": 117, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null, + "output_mode": "midi", + "osc_addr": "/play", + "trigger_mode": "momentary" + }, + { + "uid": "e2e8eb84-c2c9-4060-a818-b087ebd5aabd", + "label": "Stop", + "cc": 116, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null, + "output_mode": "midi", + "osc_addr": "/play", + "trigger_mode": "momentary" + }, + { + "uid": "6b5bcaf7-1d01-4ccd-b23a-ebae8da57a7f", + "label": "Record", + "cc": 118, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null, + "output_mode": "midi", + "osc_addr": "/play", + "trigger_mode": "momentary" + }, + { + "uid": "2a1fcb50-b9f2-4c33-bce0-5bd5cadd9c46", + "label": "Arm", + "cc": 119, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null, + "output_mode": "midi", + "osc_addr": "/play", + "trigger_mode": "momentary" + } + ], + "browser": { + "back_cc": 22, + "fwd_cc": 23 + }, + "osc": { + "ip": "127.0.0.1", + "listen_port": 9000, + "send_port": 8000 + } + }, + "2": { + "midi_port": "IAC Driver Bus 1", + "midi_input": "IAC Driver Bus 1", + "transport_port": "IAC Driver Bus 1", + "faders": [ + { + "uid": "7aa48acf-7d6b-47fa-a8b4-e10223bb62c5", + "label": "Fader 1", + "cc": 1, + "value": 64, + "color": "Orange", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "2b5abf64-9465-4191-a0e3-f14f35fcaf71", + "label": "Fader 2", + "cc": 1, + "value": 64, + "color": "Yellow", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "aa1bd51a-f53a-4b54-8d1e-44667f7c5678", "label": "Fader 3", "cc": 1, "value": 64, @@ -44,15 +183,423 @@ "zone": null, "center_index": null, "zone_index": null + }, + { + "uid": "a0ce87e5-29fa-47b9-bd69-8f958c5f0cbc", + "label": "Fader 4", + "cc": 1, + "value": 64, + "color": "Gray", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "54bdc59c-1a87-4f95-a50d-7decadb6fdf8", + "label": "Fader 5", + "cc": 1, + "value": 64, + "color": "Blue", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "faaee6d8-1ec9-422a-a07e-385fc1cabbb5", + "label": "Fader 6", + "cc": 1, + "value": 64, + "color": "Blue", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + } + ], + "toggles": [ + { + "uid": "2f1773e4-9df2-4c14-9960-68eb1fe1c98e", + "label": "Toggle 1", + "cc": 20, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null + } + ], + "transports": [ + { + "uid": "9ee09db7-2cf5-4ae6-a154-b25bb262565c", + "label": "Play", + "cc": 117, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null, + "output_mode": "midi", + "osc_addr": "/play", + "trigger_mode": "momentary" + }, + { + "uid": "e2e8eb84-c2c9-4060-a818-b087ebd5aabd", + "label": "Stop", + "cc": 116, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null, + "output_mode": "midi", + "osc_addr": "/play", + "trigger_mode": "momentary" + }, + { + "uid": "6b5bcaf7-1d01-4ccd-b23a-ebae8da57a7f", + "label": "Record", + "cc": 118, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null, + "output_mode": "midi", + "osc_addr": "/play", + "trigger_mode": "momentary" + }, + { + "uid": "2a1fcb50-b9f2-4c33-bce0-5bd5cadd9c46", + "label": "Arm", + "cc": 119, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null, + "output_mode": "midi", + "osc_addr": "/play", + "trigger_mode": "momentary" + } + ], + "browser": { + "back_cc": 22, + "fwd_cc": 23 + }, + "osc": { + "ip": "127.0.0.1", + "listen_port": 9000, + "send_port": 8000 + } + }, + "gay": { + "midi_port": "IAC Driver Bus 1", + "midi_input": "IAC Driver Bus 1", + "transport_port": "IAC Driver Bus 1", + "faders": [ + { + "uid": "7aa48acf-7d6b-47fa-a8b4-e10223bb62c5", + "label": "Fader 1", + "cc": 1, + "value": 64, + "color": "Orange", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "2b5abf64-9465-4191-a0e3-f14f35fcaf71", + "label": "Fader 2", + "cc": 1, + "value": 64, + "color": "Yellow", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "aa1bd51a-f53a-4b54-8d1e-44667f7c5678", + "label": "Fader 3", + "cc": 1, + "value": 64, + "color": "Gray", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "a0ce87e5-29fa-47b9-bd69-8f958c5f0cbc", + "label": "Fader 4", + "cc": 1, + "value": 64, + "color": "Gray", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "54bdc59c-1a87-4f95-a50d-7decadb6fdf8", + "label": "Fader 5", + "cc": 1, + "value": 64, + "color": "Blue", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "faaee6d8-1ec9-422a-a07e-385fc1cabbb5", + "label": "Fader 6", + "cc": 1, + "value": 64, + "color": "Blue", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + } + ], + "toggles": [ + { + "uid": "2f1773e4-9df2-4c14-9960-68eb1fe1c98e", + "label": "Toggle 1", + "cc": 20, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null + } + ], + "transports": [ + { + "uid": "9ee09db7-2cf5-4ae6-a154-b25bb262565c", + "label": "Play", + "cc": 117, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null, + "output_mode": "midi", + "osc_addr": "/play", + "trigger_mode": "momentary" + }, + { + "uid": "e2e8eb84-c2c9-4060-a818-b087ebd5aabd", + "label": "Stop", + "cc": 116, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null, + "output_mode": "midi", + "osc_addr": "/play", + "trigger_mode": "momentary" + }, + { + "uid": "6b5bcaf7-1d01-4ccd-b23a-ebae8da57a7f", + "label": "Record", + "cc": 118, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null, + "output_mode": "midi", + "osc_addr": "/play", + "trigger_mode": "momentary" + }, + { + "uid": "2a1fcb50-b9f2-4c33-bce0-5bd5cadd9c46", + "label": "Arm", + "cc": 119, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null, + "output_mode": "midi", + "osc_addr": "/play", + "trigger_mode": "momentary" + } + ], + "browser": { + "back_cc": 22, + "fwd_cc": 23 + }, + "osc": { + "ip": "127.0.0.1", + "listen_port": 9000, + "send_port": 8000 + } + }, + "gay2": { + "midi_port": "IAC Driver Bus 1", + "midi_input": "IAC Driver Bus 1", + "transport_port": "IAC Driver Bus 1", + "faders": [ + { + "uid": "7aa48acf-7d6b-47fa-a8b4-e10223bb62c5", + "label": "Fader 1", + "cc": 1, + "value": 64, + "color": "Orange", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "2b5abf64-9465-4191-a0e3-f14f35fcaf71", + "label": "Fader 2", + "cc": 1, + "value": 64, + "color": "Yellow", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "aa1bd51a-f53a-4b54-8d1e-44667f7c5678", + "label": "Fader 3", + "cc": 1, + "value": 64, + "color": "Gray", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "a0ce87e5-29fa-47b9-bd69-8f958c5f0cbc", + "label": "Fader 4", + "cc": 1, + "value": 64, + "color": "Gray", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "54bdc59c-1a87-4f95-a50d-7decadb6fdf8", + "label": "Fader 5", + "cc": 1, + "value": 64, + "color": "Blue", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "faaee6d8-1ec9-422a-a07e-385fc1cabbb5", + "label": "Fader 6", + "cc": 1, + "value": 109, + "color": "Blue", + "pickup": false, + "last_sent": 109, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "da37cac1-d0d9-4a18-bb94-8b7345bb8ee5", + "label": "Fader 7", + "cc": 1, + "value": 64, + "color": "Gray", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "a77ac937-8661-4639-8091-bba5275d971f", + "label": "Fader 8", + "cc": 1, + "value": 64, + "color": "Gray", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "8e852e9a-a579-4c45-b260-a1311b144b12", + "label": "Fader 9", + "cc": 1, + "value": 64, + "color": "Gray", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null } ], "toggles": [ { - "uid": "2e87ec73-fcdc-4e7f-841b-9b701d03a576", + "uid": "2f1773e4-9df2-4c14-9960-68eb1fe1c98e", "label": "Toggle 1", "cc": 20, "state": false, - "color": "Blue", + "color": "Gray", "zone": null, "center_index": null, "zone_index": null, @@ -61,60 +608,468 @@ ], "transports": [ { - "uid": "c5e25c55-f0b3-464f-aba9-7ff44963543b", + "uid": "9ee09db7-2cf5-4ae6-a154-b25bb262565c", "label": "Play", "cc": 117, "state": false, - "color": "Green", + "color": "Gray", "zone": null, "center_index": null, "zone_index": null, "hw_cc": null, - "output_mode": "osc", + "output_mode": "midi", "osc_addr": "/play", - "trigger_mode": "toggle" + "trigger_mode": "momentary" }, { - "uid": "2b2b4e59-643a-44dc-ad54-71f565ad3bc7", + "uid": "e2e8eb84-c2c9-4060-a818-b087ebd5aabd", "label": "Stop", "cc": 116, "state": false, - "color": "Yellow", + "color": "Gray", "zone": null, "center_index": null, "zone_index": null, "hw_cc": null, - "output_mode": "osc", + "output_mode": "midi", "osc_addr": "/play", - "trigger_mode": "toggle" + "trigger_mode": "momentary" }, { - "uid": "b2b81948-59c9-4b1f-b185-d7ab244a195b", + "uid": "6b5bcaf7-1d01-4ccd-b23a-ebae8da57a7f", "label": "Record", "cc": 118, "state": false, - "color": "Red", + "color": "Gray", "zone": null, "center_index": null, "zone_index": null, "hw_cc": null, - "output_mode": "osc", + "output_mode": "midi", "osc_addr": "/play", - "trigger_mode": "toggle" + "trigger_mode": "momentary" }, { - "uid": "e27dec15-9de0-4eb6-a776-d3d4bc216cb8", + "uid": "2a1fcb50-b9f2-4c33-bce0-5bd5cadd9c46", "label": "Arm", "cc": 119, "state": false, - "color": "Red", + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null, + "output_mode": "midi", + "osc_addr": "/play", + "trigger_mode": "momentary" + } + ], + "browser": { + "back_cc": 22, + "fwd_cc": 23 + }, + "osc": { + "ip": "127.0.0.1", + "listen_port": 9000, + "send_port": 8000 + } + }, + "gay3": { + "midi_port": "IAC Driver Bus 1", + "midi_input": "IAC Driver Bus 1", + "transport_port": "IAC Driver Bus 1", + "faders": [ + { + "uid": "7aa48acf-7d6b-47fa-a8b4-e10223bb62c5", + "label": "Fader 1", + "cc": 1, + "value": 64, + "color": "Orange", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "2b5abf64-9465-4191-a0e3-f14f35fcaf71", + "label": "Fader 2", + "cc": 1, + "value": 64, + "color": "Yellow", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "aa1bd51a-f53a-4b54-8d1e-44667f7c5678", + "label": "Fader 3", + "cc": 1, + "value": 64, + "color": "Gray", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "a0ce87e5-29fa-47b9-bd69-8f958c5f0cbc", + "label": "Fader 4", + "cc": 1, + "value": 64, + "color": "Gray", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "54bdc59c-1a87-4f95-a50d-7decadb6fdf8", + "label": "Fader 5", + "cc": 1, + "value": 64, + "color": "Blue", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "faaee6d8-1ec9-422a-a07e-385fc1cabbb5", + "label": "Fader 6", + "cc": 1, + "value": 109, + "color": "Blue", + "pickup": false, + "last_sent": 109, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "da37cac1-d0d9-4a18-bb94-8b7345bb8ee5", + "label": "Fader 7", + "cc": 1, + "value": 64, + "color": "Gray", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "a77ac937-8661-4639-8091-bba5275d971f", + "label": "Fader 8", + "cc": 1, + "value": 94, + "color": "Yellow", + "pickup": false, + "last_sent": 94, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "8e852e9a-a579-4c45-b260-a1311b144b12", + "label": "Fader 9", + "cc": 1, + "value": 102, + "color": "Teal", + "pickup": false, + "last_sent": 102, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + } + ], + "toggles": [ + { + "uid": "2f1773e4-9df2-4c14-9960-68eb1fe1c98e", + "label": "Toggle 1", + "cc": 20, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null + } + ], + "transports": [ + { + "uid": "9ee09db7-2cf5-4ae6-a154-b25bb262565c", + "label": "Play", + "cc": 117, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null, + "output_mode": "midi", + "osc_addr": "/play", + "trigger_mode": "momentary" + }, + { + "uid": "e2e8eb84-c2c9-4060-a818-b087ebd5aabd", + "label": "Stop", + "cc": 116, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null, + "output_mode": "midi", + "osc_addr": "/play", + "trigger_mode": "momentary" + }, + { + "uid": "6b5bcaf7-1d01-4ccd-b23a-ebae8da57a7f", + "label": "Record", + "cc": 118, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null, + "output_mode": "midi", + "osc_addr": "/play", + "trigger_mode": "momentary" + }, + { + "uid": "2a1fcb50-b9f2-4c33-bce0-5bd5cadd9c46", + "label": "Arm", + "cc": 119, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null, + "output_mode": "midi", + "osc_addr": "/play", + "trigger_mode": "momentary" + } + ], + "browser": { + "back_cc": 22, + "fwd_cc": 23 + }, + "osc": { + "ip": "127.0.0.1", + "listen_port": 9000, + "send_port": 8000 + } + }, + "gay5": { + "midi_port": "IAC Driver Bus 1", + "midi_input": "IAC Driver Bus 1", + "transport_port": "IAC Driver Bus 1", + "faders": [ + { + "uid": "7aa48acf-7d6b-47fa-a8b4-e10223bb62c5", + "label": "Fader 1", + "cc": 1, + "value": 64, + "color": "Orange", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "2b5abf64-9465-4191-a0e3-f14f35fcaf71", + "label": "Fader 2", + "cc": 1, + "value": 64, + "color": "Yellow", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "aa1bd51a-f53a-4b54-8d1e-44667f7c5678", + "label": "Fader 3", + "cc": 1, + "value": 64, + "color": "Gray", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "a0ce87e5-29fa-47b9-bd69-8f958c5f0cbc", + "label": "Fader 4", + "cc": 1, + "value": 64, + "color": "Gray", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "54bdc59c-1a87-4f95-a50d-7decadb6fdf8", + "label": "Fader 5", + "cc": 1, + "value": 64, + "color": "Blue", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "faaee6d8-1ec9-422a-a07e-385fc1cabbb5", + "label": "Fader 6", + "cc": 1, + "value": 109, + "color": "Blue", + "pickup": false, + "last_sent": 109, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "da37cac1-d0d9-4a18-bb94-8b7345bb8ee5", + "label": "Fader 7", + "cc": 1, + "value": 64, + "color": "Gray", + "pickup": false, + "last_sent": 64, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "a77ac937-8661-4639-8091-bba5275d971f", + "label": "Fader 8", + "cc": 1, + "value": 94, + "color": "Yellow", + "pickup": false, + "last_sent": 94, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + }, + { + "uid": "8e852e9a-a579-4c45-b260-a1311b144b12", + "label": "Fader 9", + "cc": 1, + "value": 102, + "color": "White", + "pickup": false, + "last_sent": 102, + "hw_cc": null, + "zone": null, + "center_index": null, + "zone_index": null + } + ], + "toggles": [ + { + "uid": "2f1773e4-9df2-4c14-9960-68eb1fe1c98e", + "label": "Toggle 1", + "cc": 20, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null + } + ], + "transports": [ + { + "uid": "9ee09db7-2cf5-4ae6-a154-b25bb262565c", + "label": "Play", + "cc": 117, + "state": false, + "color": "Gray", "zone": null, "center_index": null, "zone_index": null, "hw_cc": null, "output_mode": "osc", "osc_addr": "/play", - "trigger_mode": "toggle" + "trigger_mode": "momentary" + }, + { + "uid": "e2e8eb84-c2c9-4060-a818-b087ebd5aabd", + "label": "Stop", + "cc": 116, + "state": false, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null, + "output_mode": "osc", + "osc_addr": "/play", + "trigger_mode": "momentary" + }, + { + "uid": "6b5bcaf7-1d01-4ccd-b23a-ebae8da57a7f", + "label": "Record", + "cc": 118, + "state": true, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null, + "output_mode": "osc", + "osc_addr": "/play", + "trigger_mode": "momentary" + }, + { + "uid": "2a1fcb50-b9f2-4c33-bce0-5bd5cadd9c46", + "label": "Arm", + "cc": 119, + "state": true, + "color": "Gray", + "zone": null, + "center_index": null, + "zone_index": null, + "hw_cc": null, + "output_mode": "osc", + "osc_addr": "/play", + "trigger_mode": "momentary" } ], "browser": {