diff --git a/main.py b/main.py index 4ceb1ef..c0b4da6 100644 --- a/main.py +++ b/main.py @@ -1480,6 +1480,9 @@ def check_and_start_midi_learn(strip): learning_strip = strip strip.start_learn() +preset_browser_back.learn_btn.clicked.connect(lambda: check_and_start_midi_learn(preset_browser_back)) +preset_browser_fwd.learn_btn.clicked.connect(lambda: check_and_start_midi_learn(preset_browser_fwd)) + def update_window_width(): nf = len(faders) nt = len(toggles) @@ -1716,8 +1719,7 @@ def transport_toggle_add(label, default_cc, insert_after=None): t.plus_btn.clicked.connect(lambda: transport_toggle_add(f"T{len(transports)+1}", 20, insert_after=t)) t.minus_btn.clicked.connect(lambda: transport_remove(t)) params_visible = not transport_show_hide_config - for attr in ['learn_btn', 'cc_input_lbl', 'cc_spin', 'cc_output_lbl', 'midi_btn', 'osc_btn', - 'momentary_btn', 'toggle_mode_btn', 'osc_addr_edit', 'zone_btn', 'minus_btn', + for attr in ['grp_learn', 'arrow_lbl', 'grp_cc', 'zone_btn', 'minus_btn', 'plus_btn', 'title_edit', 'id_sep', 'id_label', 'color_swatch_btn']: getattr(t, attr).setVisible(params_visible) if insert_after is not None and insert_after in transports: @@ -2299,15 +2301,9 @@ def transport_show_hide_config_set(enabled): button_transport_show_hide_config.setText("Show Parameters" if enabled else "Hide Parameters") button_transport_show_hide_config.setChecked(enabled) for t in transports: - t.learn_btn.setVisible(not enabled) - t.cc_input_lbl.setVisible(not enabled) - t.cc_spin.setVisible(not enabled) - t.cc_output_lbl.setVisible(not enabled) - t.midi_btn.setVisible(not enabled) - t.osc_btn.setVisible(not enabled) - t.momentary_btn.setVisible(not enabled) - t.toggle_mode_btn.setVisible(not enabled) - t.osc_addr_edit.setVisible(not enabled) + t.grp_learn.setVisible(not enabled) + t.arrow_lbl.setVisible(not enabled) + t.grp_cc.setVisible(not enabled) t.zone_btn.setVisible(not enabled) t.minus_btn.setVisible(not enabled) t.plus_btn.setVisible(not enabled) diff --git a/presets/presets.json b/presets/presets.json index 0b3d4e7..938f502 100644 --- a/presets/presets.json +++ b/presets/presets.json @@ -373,14 +373,29 @@ ], "toggles": [], "browser": { - "back_cc": 22, - "fwd_cc": 23 + "back": { + "label": "Back", + "cc": 22, + "ch": 1, + "hw_cc": null, + "hw_channel": null, + "trigger_mode": "momentary" + }, + "fwd": { + "label": "Fwd", + "cc": 23, + "ch": 1, + "hw_cc": null, + "hw_channel": null, + "trigger_mode": "momentary" + } }, "preset_uuid": "3e9d51d2-cf55-49f4-80a4-2f7f4466f959", "show_hide": { "fader": false, "toggle": false, - "transport": false + "transport": false, + "browser": true } }, "new preset browser shit": { @@ -466,7 +481,7 @@ "trigger_mode": "momentary" } ], - "transport_show_hide": true, + "transport_show_hide": false, "io_config": { "midi_out": "Not Connected", "midi_in": "VMPK Output", diff --git a/transportwidget.py b/transportwidget.py index d59527c..329f532 100644 --- a/transportwidget.py +++ b/transportwidget.py @@ -46,34 +46,42 @@ class TransportWidget(QWidget): inner.setSpacing(4) inner.setAlignment(Qt.AlignmentFlag.AlignHCenter) - # Learn button + _grp_style = "QFrame { border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; }" + + # Group 1: Learn + input readout + self.grp_learn = QFrame() + self.grp_learn.setStyleSheet(_grp_style) + _grp1_layout = QVBoxLayout(self.grp_learn) + _grp1_layout.setContentsMargins(4, 4, 4, 4) + _grp1_layout.setSpacing(3) + self.learn_btn = QPushButton("Learn") self.learn_btn.setFixedHeight(20) + self.learn_btn.setStyleSheet("border: none;") self.learn_btn.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.learn_btn.customContextMenuRequested.connect(self.on_learn_context_menu) - inner.addWidget(self.learn_btn) + _grp1_layout.addWidget(self.learn_btn) - # CC In label self.cc_input_lbl = QLabel("") self.cc_input_lbl.setAlignment(Qt.AlignmentFlag.AlignHCenter) - self.cc_input_lbl.setStyleSheet("background-color: #000000; color: #888888; font-size: 10px; padding: 1px;") + self.cc_input_lbl.setStyleSheet("background-color: #000000; color: #888888; font-size: 10px; padding: 1px; border: none;") self.cc_input_lbl.setFixedHeight(16) - inner.addWidget(self.cc_input_lbl) + _grp1_layout.addWidget(self.cc_input_lbl) - # CC spin - self.cc_spin = QSpinBox() - self.cc_spin.setMinimum(1) - self.cc_spin.setMaximum(127) - self.cc_spin.setValue(default_cc) - self.cc_spin.setFixedWidth(75) - inner.addWidget(self.cc_spin, alignment=Qt.AlignmentFlag.AlignHCenter) + inner.addWidget(self.grp_learn) - # CC Out label - self.cc_output_lbl = QLabel(f"Out CC{default_cc} [--]") - self.cc_output_lbl.setAlignment(Qt.AlignmentFlag.AlignHCenter) - self.cc_output_lbl.setStyleSheet("background-color: #000000; color: #00e676; font-size: 10px; padding: 1px;") - self.cc_output_lbl.setFixedHeight(16) - inner.addWidget(self.cc_output_lbl) + self.arrow_lbl = QLabel("▼") + self.arrow_lbl.setAlignment(Qt.AlignmentFlag.AlignHCenter) + self.arrow_lbl.setStyleSheet("color: rgba(255,255,255,0.3); font-size: 9px; background: transparent;") + self.arrow_lbl.setFixedHeight(12) + inner.addWidget(self.arrow_lbl) + + # Group 2: output controls + readout + self.grp_cc = QFrame() + self.grp_cc.setStyleSheet(_grp_style) + _grp2_layout = QVBoxLayout(self.grp_cc) + _grp2_layout.setContentsMargins(4, 4, 4, 4) + _grp2_layout.setSpacing(3) # MIDI / OSC mode selector mode_row = QHBoxLayout() @@ -83,15 +91,17 @@ class TransportWidget(QWidget): self.midi_btn.setFixedHeight(18) self.midi_btn.setCheckable(True) self.midi_btn.setChecked(True) + self.midi_btn.setStyleSheet("border: none;") self.osc_btn = QPushButton("OSC") self.osc_btn.setFixedHeight(18) self.osc_btn.setCheckable(True) self.osc_btn.setChecked(False) + self.osc_btn.setStyleSheet("border: none;") self.midi_btn.clicked.connect(lambda: self.set_output_mode("midi")) self.osc_btn.clicked.connect(lambda: self.set_output_mode("osc")) mode_row.addWidget(self.midi_btn) mode_row.addWidget(self.osc_btn) - inner.addLayout(mode_row) + _grp2_layout.addLayout(mode_row) # Momentary / Toggle selector trigger_row = QHBoxLayout() @@ -101,26 +111,46 @@ class TransportWidget(QWidget): self.momentary_btn.setFixedHeight(18) self.momentary_btn.setCheckable(True) self.momentary_btn.setChecked(True) + self.momentary_btn.setStyleSheet("border: none;") self.toggle_mode_btn = QPushButton("Tog.") self.toggle_mode_btn.setFixedHeight(18) self.toggle_mode_btn.setCheckable(True) self.toggle_mode_btn.setChecked(False) + self.toggle_mode_btn.setStyleSheet("border: none;") self.momentary_btn.clicked.connect(lambda: self.set_trigger_mode("momentary")) self.toggle_mode_btn.clicked.connect(lambda: self.set_trigger_mode("toggle")) trigger_row.addWidget(self.momentary_btn) trigger_row.addWidget(self.toggle_mode_btn) - inner.addLayout(trigger_row) + _grp2_layout.addLayout(trigger_row) - self.trigger_mode = "momentary" # "momentary" or "toggle" + self.trigger_mode = "momentary" + + # CC spin (MIDI mode) + self.cc_spin = QSpinBox() + self.cc_spin.setMinimum(1) + self.cc_spin.setMaximum(127) + self.cc_spin.setValue(default_cc) + self.cc_spin.setFixedWidth(75) + _grp2_layout.addWidget(self.cc_spin, alignment=Qt.AlignmentFlag.AlignHCenter) # OSC address field (hidden by default) self.osc_addr_edit = QLineEdit("/play") self.osc_addr_edit.setAlignment(Qt.AlignmentFlag.AlignHCenter) self.osc_addr_edit.setPlaceholderText("/address") self.osc_addr_edit.setVisible(False) - inner.addWidget(self.osc_addr_edit) + self.osc_addr_edit.setStyleSheet("border: none;") + _grp2_layout.addWidget(self.osc_addr_edit) - self.output_mode = "midi" # "midi" or "osc" + # Output readout — always visible, content adapts to mode + self.cc_output_lbl = QLabel(f"CC{default_cc} [--]") + self.cc_output_lbl.setAlignment(Qt.AlignmentFlag.AlignHCenter) + self.cc_output_lbl.setStyleSheet("background-color: #000000; color: #00e676; font-size: 10px; padding: 1px; border: none;") + self.cc_output_lbl.setFixedHeight(16) + _grp2_layout.addWidget(self.cc_output_lbl) + + inner.addWidget(self.grp_cc) + + self.output_mode = "midi" # Transport button with LED dot btn_container = QWidget() @@ -219,27 +249,28 @@ class TransportWidget(QWidget): self.midi_btn.setStyleSheet("font-weight: bold; color: #00e676;") self.osc_btn.setStyleSheet("") self.cc_spin.setVisible(True) - self.cc_output_lbl.setVisible(True) self.osc_addr_edit.setVisible(False) + self.cc_output_lbl.setText(f"CC{self.cc_spin.value()} [--]") else: self.osc_btn.setChecked(True) self.midi_btn.setChecked(False) self.osc_btn.setStyleSheet("font-weight: bold; color: #00e676;") self.midi_btn.setStyleSheet("") self.cc_spin.setVisible(False) - self.cc_output_lbl.setVisible(False) self.osc_addr_edit.setVisible(True) + addr = self.osc_addr_edit.text().strip() or "/play" + self.cc_output_lbl.setText(f"{addr} [--]") def on_click(self): if self.trigger_mode == "momentary": if self.output_mode == "osc": addr = self.osc_addr_edit.text().strip() or "/play" send_osc_message(addr, 1.0) - self.cc_output_lbl.setText(f"OSC {addr}") + self.cc_output_lbl.setText(f"{addr} [1.0]") else: cc_num = self.cc_spin.value() send_transport_cc(cc_num, 127) - self.cc_output_lbl.setText(f"Out CC{cc_num} [127]") + self.cc_output_lbl.setText(f"CC{cc_num} [127]") self.led_dot.setStyleSheet("background-color: #00e676; border-radius: 3px;") QTimer.singleShot(150, lambda: self.led_dot.setStyleSheet("background-color: #3c3c3c; border-radius: 3px;")) else: @@ -247,12 +278,13 @@ class TransportWidget(QWidget): value = 127 if self.toggle_state else 0 if self.output_mode == "osc": addr = self.osc_addr_edit.text().strip() or "/play" - send_osc_message(addr, 1.0 if self.toggle_state else 0.0) - self.cc_output_lbl.setText(f"OSC {addr}") + osc_val = 1.0 if self.toggle_state else 0.0 + send_osc_message(addr, osc_val) + self.cc_output_lbl.setText(f"{addr} [{osc_val}]") else: cc_num = self.cc_spin.value() send_transport_cc(cc_num, value) - self.cc_output_lbl.setText(f"Out CC{cc_num} [{value}]") + self.cc_output_lbl.setText(f"CC{cc_num} [{value}]") self._update_btn_style() def _update_btn_style(self): @@ -315,7 +347,7 @@ class TransportWidget(QWidget): self.learn_btn.setText("Learn") self.cc_input_lbl.setText("") - def bind_hw_cc(self, cc_number): + def bind_hw_cc(self, cc_number, channel=None): self.hw_cc = cc_number self.stop_learn()