preset browser widget / lock preset
This commit is contained in:
@@ -19,6 +19,7 @@ import threading
|
|||||||
from togglewidget import ToggleWidget
|
from togglewidget import ToggleWidget
|
||||||
from faderwidget import FaderWidget
|
from faderwidget import FaderWidget
|
||||||
from transportwidget import TransportWidget
|
from transportwidget import TransportWidget
|
||||||
|
from presetwidget import PresetWidget
|
||||||
|
|
||||||
from palette import (
|
from palette import (
|
||||||
PALETTE,
|
PALETTE,
|
||||||
@@ -100,6 +101,7 @@ midi_in_port = None
|
|||||||
fader_show_hide_config = False
|
fader_show_hide_config = False
|
||||||
transport_show_hide_config = False
|
transport_show_hide_config = False
|
||||||
toggle_show_hide_config = False
|
toggle_show_hide_config = False
|
||||||
|
preset_switch_locked = False
|
||||||
|
|
||||||
uuid_presets_matched = []
|
uuid_presets_matched = []
|
||||||
uuid_presets_received_values = []
|
uuid_presets_received_values = []
|
||||||
@@ -163,6 +165,14 @@ buttonImport = QPushButton("Import")
|
|||||||
buttonImport.setFixedWidth(55)
|
buttonImport.setFixedWidth(55)
|
||||||
buttonImport.clicked.connect(lambda: import_preset())
|
buttonImport.clicked.connect(lambda: import_preset())
|
||||||
|
|
||||||
|
checkbox_lock_preset = QCheckBox("Lock Preset")
|
||||||
|
checkbox_lock_preset.setStyleSheet("color: #aaa; font-size: 11px;")
|
||||||
|
checkbox_lock_preset.setChecked(False)
|
||||||
|
def on_lock_preset_toggled(checked):
|
||||||
|
global preset_switch_locked
|
||||||
|
preset_switch_locked = checked
|
||||||
|
checkbox_lock_preset.toggled.connect(on_lock_preset_toggled)
|
||||||
|
|
||||||
#save status label (should be used for other stuff as well) -> save_status_label
|
#save status label (should be used for other stuff as well) -> save_status_label
|
||||||
labelStatus = QLabel("")
|
labelStatus = QLabel("")
|
||||||
labelStatus.setStyleSheet("color: #00e676; font-style: italic;")
|
labelStatus.setStyleSheet("color: #00e676; font-style: italic;")
|
||||||
@@ -488,42 +498,16 @@ textedit_osc_out_log.setStyleSheet("""
|
|||||||
""")
|
""")
|
||||||
|
|
||||||
# MARK: PRESET BROWSER ROW MODULE
|
# 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")
|
label_preset_browser_title = QLabel("Preset Browser")
|
||||||
|
|
||||||
# Layout: hstack_preset_browser
|
|
||||||
hstack_preset_browser = QHBoxLayout()
|
hstack_preset_browser = QHBoxLayout()
|
||||||
hstack_preset_browser.setSpacing(8)
|
hstack_preset_browser.setSpacing(8)
|
||||||
|
|
||||||
# Widget: button_preset_browser_back
|
preset_browser_back = PresetWidget(label="Back", default_cc=22)
|
||||||
button_preset_browser_back = QPushButton("◀")
|
preset_browser_fwd = PresetWidget(label="Fwd", default_cc=23)
|
||||||
button_preset_browser_back.setFixedSize(36, 28)
|
preset_browsers = [preset_browser_back, preset_browser_fwd]
|
||||||
|
|
||||||
# Widget: spinner_preset_browser_back
|
# show/hide state (mirrors fader/toggle pattern)
|
||||||
spinner_preset_browser_back = QSpinBox()
|
preset_browser_show_hide_config = False
|
||||||
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
|
# MARK: TRANSPORT MODULE
|
||||||
# REFACTOR MAP: Old names → New names
|
# REFACTOR MAP: Old names → New names
|
||||||
@@ -701,6 +685,13 @@ button_transport_show_hide_config.setStyleSheet(_show_hide_btn_style)
|
|||||||
hstack_row_toggle_control_buttons = QHBoxLayout()
|
hstack_row_toggle_control_buttons = QHBoxLayout()
|
||||||
hstack_row_toggle_control_buttons.setSpacing(8)
|
hstack_row_toggle_control_buttons.setSpacing(8)
|
||||||
|
|
||||||
|
# Widget: button_preset_browser_show_hide_config
|
||||||
|
button_preset_browser_show_hide_config = QPushButton("Hide Parameters")
|
||||||
|
button_preset_browser_show_hide_config.setFixedWidth(120)
|
||||||
|
button_preset_browser_show_hide_config.setCheckable(True)
|
||||||
|
button_preset_browser_show_hide_config.setFlat(True)
|
||||||
|
button_preset_browser_show_hide_config.setStyleSheet(_show_hide_btn_style)
|
||||||
|
|
||||||
# Widget: button_toggle_show_hide_config
|
# Widget: button_toggle_show_hide_config
|
||||||
button_toggle_show_hide_config = QPushButton("Hide Parameters")
|
button_toggle_show_hide_config = QPushButton("Hide Parameters")
|
||||||
button_toggle_show_hide_config.setFixedWidth(120)
|
button_toggle_show_hide_config.setFixedWidth(120)
|
||||||
@@ -756,6 +747,7 @@ class MainWindow(QMainWindow):
|
|||||||
hstack_preset_row.addWidget(buttonRemove) #remove button
|
hstack_preset_row.addWidget(buttonRemove) #remove button
|
||||||
hstack_preset_row.addWidget(buttonExport)
|
hstack_preset_row.addWidget(buttonExport)
|
||||||
hstack_preset_row.addWidget(buttonImport)
|
hstack_preset_row.addWidget(buttonImport)
|
||||||
|
hstack_preset_row.addWidget(checkbox_lock_preset)
|
||||||
hstack_preset_row.addWidget(label_preset_uuid)
|
hstack_preset_row.addWidget(label_preset_uuid)
|
||||||
hstack_preset_row.addWidget(button_preset_uuid_gen)
|
hstack_preset_row.addWidget(button_preset_uuid_gen)
|
||||||
hstack_preset_row.addWidget(button_preset_uuid_copy)
|
hstack_preset_row.addWidget(button_preset_uuid_copy)
|
||||||
@@ -1031,32 +1023,26 @@ class MainWindow(QMainWindow):
|
|||||||
global container_preset_browser
|
global container_preset_browser
|
||||||
global label_preset_browser_title
|
global label_preset_browser_title
|
||||||
global hstack_preset_browser
|
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
|
|
||||||
|
|
||||||
container_preset_browser = QWidget()
|
container_preset_browser = QWidget()
|
||||||
vstack_preset_browser_container = QVBoxLayout(container_preset_browser)
|
vstack_preset_browser_container = QVBoxLayout(container_preset_browser)
|
||||||
vstack_preset_browser_container.setContentsMargins(0, 0, 0, 0)
|
vstack_preset_browser_container.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
# assemble browser controls
|
hstack_preset_browser.addWidget(preset_browser_back)
|
||||||
hstack_preset_browser.addWidget(button_preset_browser_back)
|
hstack_preset_browser.addWidget(preset_browser_fwd)
|
||||||
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(1)
|
hstack_preset_browser.addStretch(1)
|
||||||
|
|
||||||
|
hstack_preset_browser_controls = QHBoxLayout()
|
||||||
|
hstack_preset_browser_controls.addWidget(button_preset_browser_show_hide_config)
|
||||||
|
hstack_preset_browser_controls.addStretch()
|
||||||
|
button_preset_browser_show_hide_config.clicked.connect(preset_browser_show_hide_config_toggle)
|
||||||
|
|
||||||
vstack_preset_browser_container.addWidget(make_separator())
|
vstack_preset_browser_container.addWidget(make_separator())
|
||||||
vstack_preset_browser_container.addWidget(label_preset_browser_title)
|
vstack_preset_browser_container.addWidget(label_preset_browser_title)
|
||||||
|
vstack_preset_browser_container.addLayout(hstack_preset_browser_controls)
|
||||||
vstack_preset_browser_container.addLayout(hstack_preset_browser)
|
vstack_preset_browser_container.addLayout(hstack_preset_browser)
|
||||||
main_layout.addWidget(container_preset_browser)
|
main_layout.addWidget(container_preset_browser)
|
||||||
|
|
||||||
# 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))
|
|
||||||
|
|
||||||
# MARK: CONTAINER ROWS AND WIDGETS (fader + toggle only)
|
# MARK: CONTAINER ROWS AND WIDGETS (fader + toggle only)
|
||||||
global container_rows_and_widgets
|
global container_rows_and_widgets
|
||||||
@@ -1285,7 +1271,7 @@ def new_blank_preset():
|
|||||||
if not ok or not name.strip():
|
if not ok or not name.strip():
|
||||||
return
|
return
|
||||||
name = name.strip()
|
name = name.strip()
|
||||||
loaded_presets["presets"][name] = {"faders": [], "toggles": [], "preset_uuid": "", "browser": {}, "show_hide": {}}
|
loaded_presets["presets"][name] = {"faders": [], "toggles": [], "preset_uuid": "", "browser": {"back": preset_browser_back.get_state(), "fwd": preset_browser_fwd.get_state()}, "show_hide": {}}
|
||||||
loaded_presets["__last_used__"] = name
|
loaded_presets["__last_used__"] = name
|
||||||
save_presets_file(loaded_presets)
|
save_presets_file(loaded_presets)
|
||||||
populate_preset_dropdown_menu()
|
populate_preset_dropdown_menu()
|
||||||
@@ -1449,6 +1435,7 @@ def _check_track_uuid_arrived():
|
|||||||
_track_uuid_check_pending = False
|
_track_uuid_check_pending = False
|
||||||
label_osc_status.setText("No preset linked to this track")
|
label_osc_status.setText("No preset linked to this track")
|
||||||
label_osc_status.setStyleSheet("color: #f0c040; font-size: 11px; font-style: italic;")
|
label_osc_status.setStyleSheet("color: #f0c040; font-size: 11px; font-style: italic;")
|
||||||
|
if not preset_switch_locked:
|
||||||
ui_show_uuid_error_overlay("No preset linked to this track", "#f0c040")
|
ui_show_uuid_error_overlay("No preset linked to this track", "#f0c040")
|
||||||
|
|
||||||
def update_ui_on_osc_track_received(name):
|
def update_ui_on_osc_track_received(name):
|
||||||
@@ -1816,14 +1803,15 @@ def preset_build_snapshot(name):
|
|||||||
"faders": [f.get_state() for f in faders],
|
"faders": [f.get_state() for f in faders],
|
||||||
"toggles": [t.get_state() for t in toggles],
|
"toggles": [t.get_state() for t in toggles],
|
||||||
"browser": {
|
"browser": {
|
||||||
"back_cc": spinner_preset_browser_back.value(),
|
"back": preset_browser_back.get_state(),
|
||||||
"fwd_cc": spinner_preset_browser_fwd.value()
|
"fwd": preset_browser_fwd.get_state(),
|
||||||
},
|
},
|
||||||
"preset_uuid": label_preset_uuid.text().strip(),
|
"preset_uuid": label_preset_uuid.text().strip(),
|
||||||
"show_hide": {
|
"show_hide": {
|
||||||
"fader": fader_show_hide_config,
|
"fader": fader_show_hide_config,
|
||||||
"toggle": toggle_show_hide_config,
|
"toggle": toggle_show_hide_config,
|
||||||
"transport": transport_show_hide_config,
|
"transport": transport_show_hide_config,
|
||||||
|
"browser": preset_browser_show_hide_config,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1916,8 +1904,14 @@ def preset_load(preset):
|
|||||||
hstack_toggle_zone_right.addWidget(t)
|
hstack_toggle_zone_right.addWidget(t)
|
||||||
|
|
||||||
browser = preset.get("browser", {})
|
browser = preset.get("browser", {})
|
||||||
spinner_preset_browser_back.setValue(browser.get("back_cc", 22))
|
# migrate old format that only stored cc numbers
|
||||||
spinner_preset_browser_fwd.setValue(browser.get("fwd_cc", 23))
|
if "back" not in browser and "back_cc" in browser:
|
||||||
|
browser = {
|
||||||
|
"back": {"label": "Back", "cc": browser.get("back_cc", 22), "ch": 1},
|
||||||
|
"fwd": {"label": "Fwd", "cc": browser.get("fwd_cc", 23), "ch": 1},
|
||||||
|
}
|
||||||
|
preset_browser_back.set_state(browser.get("back", {"label": "Back", "cc": 22, "ch": 1}))
|
||||||
|
preset_browser_fwd.set_state(browser.get("fwd", {"label": "Fwd", "cc": 23, "ch": 1}))
|
||||||
|
|
||||||
show_hide = preset.get("show_hide", {})
|
show_hide = preset.get("show_hide", {})
|
||||||
fader_show_hide_config_set(show_hide.get("fader", False))
|
fader_show_hide_config_set(show_hide.get("fader", False))
|
||||||
@@ -1928,6 +1922,10 @@ def preset_load(preset):
|
|||||||
button_toggle_show_hide_config.setText("Show Parameters" if show_hide.get("toggle", False) else "Hide Parameters")
|
button_toggle_show_hide_config.setText("Show Parameters" if show_hide.get("toggle", False) else "Hide Parameters")
|
||||||
button_toggle_show_hide_config.setChecked(show_hide.get("toggle", False))
|
button_toggle_show_hide_config.setChecked(show_hide.get("toggle", False))
|
||||||
|
|
||||||
|
preset_browser_show_hide_config_set(show_hide.get("browser", False))
|
||||||
|
button_preset_browser_show_hide_config.setText("Show Parameters" if show_hide.get("browser", False) else "Hide Parameters")
|
||||||
|
button_preset_browser_show_hide_config.setChecked(show_hide.get("browser", False))
|
||||||
|
|
||||||
label_preset_uuid.setText(preset.get("preset_uuid", ""))
|
label_preset_uuid.setText(preset.get("preset_uuid", ""))
|
||||||
check_and_index_strip_labels()
|
check_and_index_strip_labels()
|
||||||
|
|
||||||
@@ -2012,20 +2010,23 @@ def resolve_uuid_batch():
|
|||||||
global uuid_presets_matched, uuid_presets_received_values
|
global uuid_presets_matched, uuid_presets_received_values
|
||||||
uuid_presets_matched = list(dict.fromkeys(uuid_presets_matched))
|
uuid_presets_matched = list(dict.fromkeys(uuid_presets_matched))
|
||||||
if len(uuid_presets_matched) == 1:
|
if len(uuid_presets_matched) == 1:
|
||||||
menuPresets.setCurrentText(uuid_presets_matched[0])
|
|
||||||
track_name = label_osc_track.text()
|
track_name = label_osc_track.text()
|
||||||
label_osc_preset.setText(uuid_presets_matched[0])
|
label_osc_preset.setText(uuid_presets_matched[0])
|
||||||
label_osc_status.setText(f"DAW Track Selected: {track_name}")
|
label_osc_status.setText(f"DAW Track Selected: {track_name}")
|
||||||
label_osc_status.setStyleSheet("color: #00e676; font-size: 11px; font-style: italic;")
|
label_osc_status.setStyleSheet("color: #00e676; font-size: 11px; font-style: italic;")
|
||||||
|
if not preset_switch_locked:
|
||||||
|
menuPresets.setCurrentText(uuid_presets_matched[0])
|
||||||
ui_show_rows_and_widgets()
|
ui_show_rows_and_widgets()
|
||||||
elif len(uuid_presets_matched) > 1:
|
elif len(uuid_presets_matched) > 1:
|
||||||
label_osc_status.setText("MULTIPLE PRESET IDs DETECTED")
|
label_osc_status.setText("MULTIPLE PRESET IDs DETECTED")
|
||||||
label_osc_status.setStyleSheet("color: #ff4444; font-size: 11px; font-style: italic;")
|
label_osc_status.setStyleSheet("color: #ff4444; font-size: 11px; font-style: italic;")
|
||||||
|
if not preset_switch_locked:
|
||||||
ui_show_uuid_error_overlay("MULTIPLE PRESET IDs DETECTED", "#ff4444")
|
ui_show_uuid_error_overlay("MULTIPLE PRESET IDs DETECTED", "#ff4444")
|
||||||
QTimer.singleShot(4000, lambda: (label_osc_status.setText(""), label_osc_status.setStyleSheet("color: #00e676; font-size: 11px; font-style: italic;")))
|
QTimer.singleShot(4000, lambda: (label_osc_status.setText(""), label_osc_status.setStyleSheet("color: #00e676; font-size: 11px; font-style: italic;")))
|
||||||
else:
|
else:
|
||||||
label_osc_status.setText("NO MATCHING ID IN PRESET LIST")
|
label_osc_status.setText("NO MATCHING ID IN PRESET LIST")
|
||||||
label_osc_status.setStyleSheet("color: #ff4444; font-size: 11px; font-style: italic;")
|
label_osc_status.setStyleSheet("color: #ff4444; font-size: 11px; font-style: italic;")
|
||||||
|
if not preset_switch_locked:
|
||||||
ui_show_uuid_error_overlay("NO MATCHING ID IN PRESET LIST", "#ff4444")
|
ui_show_uuid_error_overlay("NO MATCHING ID IN PRESET LIST", "#ff4444")
|
||||||
QTimer.singleShot(4000, lambda: (label_osc_status.setText(""), label_osc_status.setStyleSheet("color: #00e676; font-size: 11px; font-style: italic;")))
|
QTimer.singleShot(4000, lambda: (label_osc_status.setText(""), label_osc_status.setStyleSheet("color: #00e676; font-size: 11px; font-style: italic;")))
|
||||||
uuid_presets_matched.clear()
|
uuid_presets_matched.clear()
|
||||||
@@ -2086,6 +2087,7 @@ def route_midi_message_input(event, data=None):
|
|||||||
|
|
||||||
bound_fader = None
|
bound_fader = None
|
||||||
bound_transport = None
|
bound_transport = None
|
||||||
|
bound_browser = None
|
||||||
if status == 0xB0:
|
if status == 0xB0:
|
||||||
for fader in faders:
|
for fader in faders:
|
||||||
if fader.hw_cc == cc_num:
|
if fader.hw_cc == cc_num:
|
||||||
@@ -2101,13 +2103,20 @@ def route_midi_message_input(event, data=None):
|
|||||||
if hasattr(t, 'hw_cc') and t.hw_cc == cc_num:
|
if hasattr(t, 'hw_cc') and t.hw_cc == cc_num:
|
||||||
bound_transport = t
|
bound_transport = t
|
||||||
break
|
break
|
||||||
|
if not bound_fader and not bound_transport:
|
||||||
|
for w in preset_browsers:
|
||||||
|
if w.hw_cc == cc_num:
|
||||||
|
bound_browser = w
|
||||||
|
break
|
||||||
|
|
||||||
if status == 0xB0 and learning_strip:
|
if status == 0xB0 and learning_strip:
|
||||||
learning_strip.bind_hw_cc(cc_num, channel=channel)
|
learning_strip.bind_hw_cc(cc_num, channel=channel)
|
||||||
learning_strip = None
|
learning_strip = None
|
||||||
return
|
return
|
||||||
|
|
||||||
if bound_transport:
|
if bound_browser:
|
||||||
|
bound_browser.on_hw_trigger(cc_val)
|
||||||
|
elif bound_transport:
|
||||||
print(f"[transport hw] CC{cc_num} val={cc_val} → uid={bound_transport.uid}")
|
print(f"[transport hw] CC{cc_num} val={cc_val} → uid={bound_transport.uid}")
|
||||||
midi_receiver.hw_cc_signal.emit(bound_transport.uid, cc_val)
|
midi_receiver.hw_cc_signal.emit(bound_transport.uid, cc_val)
|
||||||
elif bound_fader:
|
elif bound_fader:
|
||||||
@@ -2333,7 +2342,30 @@ def toggle_show_hide_config_toggle():
|
|||||||
button_toggle_show_hide_config.setText("Show Parameters" if enabled else "Hide Parameters")
|
button_toggle_show_hide_config.setText("Show Parameters" if enabled else "Hide Parameters")
|
||||||
button_toggle_show_hide_config.setChecked(enabled)
|
button_toggle_show_hide_config.setChecked(enabled)
|
||||||
|
|
||||||
|
# MARK: PRESET BROWSER SHOW HIDE CONFIG
|
||||||
|
def preset_browser_show_hide_config_set(enabled):
|
||||||
|
global preset_browser_show_hide_config
|
||||||
|
preset_browser_show_hide_config = enabled
|
||||||
|
for w in preset_browsers:
|
||||||
|
w.grp_learn.setVisible(not enabled)
|
||||||
|
w.arrow_lbl.setVisible(not enabled)
|
||||||
|
w.grp_cc.setVisible(not enabled)
|
||||||
|
w.title_edit.setVisible(not enabled)
|
||||||
|
w.momentary_btn.setVisible(not enabled)
|
||||||
|
w.toggle_mode_btn.setVisible(not enabled)
|
||||||
|
w.btn.setFixedHeight(24 if enabled else 40)
|
||||||
|
w.container.setStyleSheet(
|
||||||
|
"QFrame#presetContainer { background: transparent; border: none; }"
|
||||||
|
if enabled else
|
||||||
|
"QFrame#presetContainer { background-color: #4a5568; border-radius: 6px; border: 2px solid rgba(0,0,0,0.3); }"
|
||||||
|
)
|
||||||
|
|
||||||
|
def preset_browser_show_hide_config_toggle():
|
||||||
|
global preset_browser_show_hide_config
|
||||||
|
enabled = not preset_browser_show_hide_config
|
||||||
|
preset_browser_show_hide_config_set(enabled)
|
||||||
|
button_preset_browser_show_hide_config.setText("Show Parameters" if enabled else "Hide Parameters")
|
||||||
|
button_preset_browser_show_hide_config.setChecked(enabled)
|
||||||
|
|
||||||
|
|
||||||
# VS Code dark theme palette
|
# VS Code dark theme palette
|
||||||
|
|||||||
+44
-5
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"__last_used__": "preset1",
|
"__last_used__": "preset5",
|
||||||
"presets": {
|
"presets": {
|
||||||
"preset1": {
|
"preset1": {
|
||||||
"faders": [
|
"faders": [
|
||||||
@@ -231,14 +231,29 @@
|
|||||||
],
|
],
|
||||||
"toggles": [],
|
"toggles": [],
|
||||||
"browser": {
|
"browser": {
|
||||||
"back_cc": 22,
|
"back": {
|
||||||
"fwd_cc": 23
|
"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": "e658b6bf-4bb7-4d68-bd8b-07775ee80c53",
|
"preset_uuid": "e658b6bf-4bb7-4d68-bd8b-07775ee80c53",
|
||||||
"show_hide": {
|
"show_hide": {
|
||||||
"fader": false,
|
"fader": false,
|
||||||
"toggle": false,
|
"toggle": false,
|
||||||
"transport": false
|
"transport": true,
|
||||||
|
"browser": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"whatever": {
|
"whatever": {
|
||||||
@@ -367,6 +382,30 @@
|
|||||||
"toggle": false,
|
"toggle": false,
|
||||||
"transport": false
|
"transport": false
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"new preset browser shit": {
|
||||||
|
"faders": [],
|
||||||
|
"toggles": [],
|
||||||
|
"preset_uuid": "",
|
||||||
|
"browser": {
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"show_hide": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"transport_preset": [
|
"transport_preset": [
|
||||||
@@ -427,7 +466,7 @@
|
|||||||
"trigger_mode": "momentary"
|
"trigger_mode": "momentary"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"transport_show_hide": false,
|
"transport_show_hide": true,
|
||||||
"io_config": {
|
"io_config": {
|
||||||
"midi_out": "Not Connected",
|
"midi_out": "Not Connected",
|
||||||
"midi_in": "VMPK Output",
|
"midi_in": "VMPK Output",
|
||||||
|
|||||||
+258
@@ -0,0 +1,258 @@
|
|||||||
|
from PyQt6.QtWidgets import (
|
||||||
|
QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
|
||||||
|
QLabel, QSpinBox, QLineEdit, QFrame, QSizePolicy
|
||||||
|
)
|
||||||
|
from PyQt6.QtCore import Qt
|
||||||
|
|
||||||
|
from styles import TITLE_STYLE
|
||||||
|
from midi_sender import send_cc
|
||||||
|
|
||||||
|
|
||||||
|
class PresetWidget(QWidget):
|
||||||
|
def __init__(self, label="Preset", default_cc=1):
|
||||||
|
super().__init__()
|
||||||
|
self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
|
||||||
|
self.setFixedWidth(115)
|
||||||
|
|
||||||
|
self.hw_cc = None
|
||||||
|
self.hw_channel = None
|
||||||
|
self.is_learning = False
|
||||||
|
|
||||||
|
outer = QVBoxLayout(self)
|
||||||
|
outer.setContentsMargins(0, 0, 0, 0)
|
||||||
|
outer.setSpacing(2)
|
||||||
|
|
||||||
|
self.container = QFrame()
|
||||||
|
self.container.setFrameShape(QFrame.Shape.StyledPanel)
|
||||||
|
self.container.setObjectName("presetContainer")
|
||||||
|
self.container.setStyleSheet("""
|
||||||
|
QFrame#presetContainer {
|
||||||
|
background-color: #4a5568;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 2px solid rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
|
||||||
|
inner = QVBoxLayout(self.container)
|
||||||
|
inner.setContentsMargins(4, 6, 4, 6)
|
||||||
|
inner.setSpacing(4)
|
||||||
|
inner.setAlignment(Qt.AlignmentFlag.AlignHCenter)
|
||||||
|
|
||||||
|
_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)
|
||||||
|
self.learn_btn.clicked.connect(self.start_learn)
|
||||||
|
_grp1_layout.addWidget(self.learn_btn)
|
||||||
|
|
||||||
|
self.trigger_mode = "momentary"
|
||||||
|
self.toggle_state = False
|
||||||
|
|
||||||
|
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; border: none;")
|
||||||
|
self.cc_input_lbl.setFixedHeight(16)
|
||||||
|
_grp1_layout.addWidget(self.cc_input_lbl)
|
||||||
|
|
||||||
|
inner.addWidget(self.grp_learn)
|
||||||
|
|
||||||
|
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: Mom./Tog. + CC spin + CH spin + output 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)
|
||||||
|
|
||||||
|
# Momentary / Toggle selector
|
||||||
|
_trigger_row = QHBoxLayout()
|
||||||
|
_trigger_row.setContentsMargins(0, 0, 0, 0)
|
||||||
|
_trigger_row.setSpacing(2)
|
||||||
|
self.momentary_btn = QPushButton("Mom.")
|
||||||
|
self.momentary_btn.setFixedHeight(18)
|
||||||
|
self.momentary_btn.setCheckable(True)
|
||||||
|
self.momentary_btn.setChecked(True)
|
||||||
|
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.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)
|
||||||
|
_grp2_layout.addLayout(_trigger_row)
|
||||||
|
|
||||||
|
self.cc_spin = QSpinBox()
|
||||||
|
self.cc_spin.setMinimum(0)
|
||||||
|
self.cc_spin.setMaximum(127)
|
||||||
|
self.cc_spin.setValue(default_cc)
|
||||||
|
self.cc_spin.setFixedWidth(55)
|
||||||
|
_cc_lbl = QLabel("CC")
|
||||||
|
_cc_lbl.setStyleSheet("color: #fff; font-size: 10px; border: none;")
|
||||||
|
self.cc_spin_row = QWidget()
|
||||||
|
_hbox_cc = QHBoxLayout(self.cc_spin_row)
|
||||||
|
_hbox_cc.setContentsMargins(0, 0, 0, 0)
|
||||||
|
_hbox_cc.setSpacing(4)
|
||||||
|
_hbox_cc.addWidget(_cc_lbl)
|
||||||
|
_hbox_cc.addWidget(self.cc_spin)
|
||||||
|
_grp2_layout.addWidget(self.cc_spin_row)
|
||||||
|
|
||||||
|
self.ch_spin = QSpinBox()
|
||||||
|
self.ch_spin.setMinimum(1)
|
||||||
|
self.ch_spin.setMaximum(16)
|
||||||
|
self.ch_spin.setValue(1)
|
||||||
|
self.ch_spin.setFixedWidth(55)
|
||||||
|
_ch_lbl = QLabel("CH")
|
||||||
|
_ch_lbl.setStyleSheet("color: #fff; font-size: 10px; border: none;")
|
||||||
|
self.ch_spin_row = QWidget()
|
||||||
|
_hbox_ch = QHBoxLayout(self.ch_spin_row)
|
||||||
|
_hbox_ch.setContentsMargins(0, 0, 0, 0)
|
||||||
|
_hbox_ch.setSpacing(4)
|
||||||
|
_hbox_ch.addWidget(_ch_lbl)
|
||||||
|
_hbox_ch.addWidget(self.ch_spin)
|
||||||
|
_grp2_layout.addWidget(self.ch_spin_row)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Trigger button
|
||||||
|
self.btn = QPushButton(label)
|
||||||
|
self.btn.setFixedHeight(40)
|
||||||
|
self.btn.clicked.connect(self.on_click)
|
||||||
|
inner.addWidget(self.btn)
|
||||||
|
|
||||||
|
# Scribble strip
|
||||||
|
self.title_edit = QLineEdit(label)
|
||||||
|
self.title_edit.setAlignment(Qt.AlignmentFlag.AlignHCenter)
|
||||||
|
self.title_edit.setStyleSheet(TITLE_STYLE)
|
||||||
|
self.title_edit.textChanged.connect(self.btn.setText)
|
||||||
|
inner.addWidget(self.title_edit)
|
||||||
|
|
||||||
|
outer.addWidget(self.container)
|
||||||
|
|
||||||
|
def _cc_input_label(self, vel="--"):
|
||||||
|
if self.hw_cc is None:
|
||||||
|
return ""
|
||||||
|
ch = f" CH{self.hw_channel}" if self.hw_channel is not None else ""
|
||||||
|
return f"CC{self.hw_cc}{ch} [{vel}]"
|
||||||
|
|
||||||
|
def _cc_output_label(self, vel="--"):
|
||||||
|
return f"CC{self.cc_spin.value()} CH{self.ch_spin.value()} [{vel}]"
|
||||||
|
|
||||||
|
def start_learn(self):
|
||||||
|
self.is_learning = True
|
||||||
|
self.learn_btn.setText("Listening...")
|
||||||
|
self.learn_btn.setStyleSheet("color: orange; font-weight: bold; border: none;")
|
||||||
|
|
||||||
|
def stop_learn(self):
|
||||||
|
self.is_learning = False
|
||||||
|
self.learn_btn.setStyleSheet("border: none;")
|
||||||
|
if self.hw_cc is not None:
|
||||||
|
self.learn_btn.setText(f"HW: CC{self.hw_cc}")
|
||||||
|
self.cc_input_lbl.setText(self._cc_input_label())
|
||||||
|
self.cc_input_lbl.setStyleSheet("background-color: #000000; color: #00e676; font-size: 10px; padding: 1px; border: none;")
|
||||||
|
else:
|
||||||
|
self.learn_btn.setText("Learn")
|
||||||
|
self.cc_input_lbl.setText("")
|
||||||
|
self.cc_input_lbl.setStyleSheet("background-color: #000000; color: #888888; font-size: 10px; padding: 1px; border: none;")
|
||||||
|
|
||||||
|
def bind_hw_cc(self, cc_number, channel=None):
|
||||||
|
self.hw_cc = cc_number
|
||||||
|
self.hw_channel = channel
|
||||||
|
self.stop_learn()
|
||||||
|
|
||||||
|
def on_learn_context_menu(self, pos):
|
||||||
|
from PyQt6.QtWidgets import QMenu
|
||||||
|
if self.hw_cc is None:
|
||||||
|
return
|
||||||
|
menu = QMenu(self)
|
||||||
|
clear_action = menu.addAction("Clear Input Assignment")
|
||||||
|
action = menu.exec(self.learn_btn.mapToGlobal(pos))
|
||||||
|
if action == clear_action:
|
||||||
|
self.hw_cc = None
|
||||||
|
self.hw_channel = None
|
||||||
|
self.is_learning = False
|
||||||
|
self.learn_btn.setText("Learn")
|
||||||
|
self.learn_btn.setStyleSheet("border: none;")
|
||||||
|
self.cc_input_lbl.setText("")
|
||||||
|
self.cc_input_lbl.setStyleSheet("background-color: #000000; color: #888888; font-size: 10px; padding: 1px; border: none;")
|
||||||
|
|
||||||
|
def set_trigger_mode(self, mode):
|
||||||
|
self.trigger_mode = mode
|
||||||
|
if mode == "momentary":
|
||||||
|
self.momentary_btn.setChecked(True)
|
||||||
|
self.toggle_mode_btn.setChecked(False)
|
||||||
|
self.momentary_btn.setStyleSheet("font-weight: bold; color: #00e676;")
|
||||||
|
self.toggle_mode_btn.setStyleSheet("")
|
||||||
|
else:
|
||||||
|
self.toggle_mode_btn.setChecked(True)
|
||||||
|
self.momentary_btn.setChecked(False)
|
||||||
|
self.toggle_mode_btn.setStyleSheet("font-weight: bold; color: #00e676;")
|
||||||
|
self.momentary_btn.setStyleSheet("")
|
||||||
|
|
||||||
|
def _fire(self):
|
||||||
|
out_cc = self.cc_spin.value()
|
||||||
|
out_ch = self.ch_spin.value()
|
||||||
|
if self.trigger_mode == "momentary":
|
||||||
|
out_val = 127
|
||||||
|
else:
|
||||||
|
self.toggle_state = not self.toggle_state
|
||||||
|
out_val = 127 if self.toggle_state else 0
|
||||||
|
print(f"[preset] CC{out_cc} CH{out_ch} ({out_val})")
|
||||||
|
send_cc(out_cc, out_val, channel=out_ch)
|
||||||
|
self.cc_output_lbl.setText(self._cc_output_label(out_val))
|
||||||
|
|
||||||
|
def on_click(self):
|
||||||
|
self._fire()
|
||||||
|
|
||||||
|
def on_hw_trigger(self, value):
|
||||||
|
if value == 0:
|
||||||
|
return # ignore releases — normalized by mom/tog logic
|
||||||
|
self.cc_input_lbl.setText(self._cc_input_label(value))
|
||||||
|
self._fire()
|
||||||
|
|
||||||
|
def get_state(self):
|
||||||
|
return {
|
||||||
|
"label": self.title_edit.text(),
|
||||||
|
"cc": self.cc_spin.value(),
|
||||||
|
"ch": self.ch_spin.value(),
|
||||||
|
"hw_cc": self.hw_cc,
|
||||||
|
"hw_channel": self.hw_channel,
|
||||||
|
"trigger_mode": self.trigger_mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
def set_state(self, state):
|
||||||
|
self.title_edit.setText(state.get("label", self.title_edit.text()))
|
||||||
|
self.cc_spin.setValue(state.get("cc", 1))
|
||||||
|
self.ch_spin.setValue(state.get("ch", 1))
|
||||||
|
self.hw_cc = state.get("hw_cc", None)
|
||||||
|
self.hw_channel = state.get("hw_channel", None)
|
||||||
|
self.set_trigger_mode(state.get("trigger_mode", "momentary"))
|
||||||
|
if self.hw_cc is not None:
|
||||||
|
self.learn_btn.setText(f"HW: CC{self.hw_cc}")
|
||||||
|
self.cc_input_lbl.setText(self._cc_input_label())
|
||||||
|
self.cc_input_lbl.setStyleSheet("background-color: #000000; color: #00e676; font-size: 10px; padding: 1px; border: none;")
|
||||||
|
else:
|
||||||
|
self.learn_btn.setText("Learn")
|
||||||
|
self.cc_input_lbl.setText("")
|
||||||
|
self.cc_input_lbl.setStyleSheet("background-color: #000000; color: #888888; font-size: 10px; padding: 1px; border: none;")
|
||||||
Reference in New Issue
Block a user