diff --git a/core/arpeggiator_engine.py b/core/arpeggiator_engine.py index 24ee0c1..ed52054 100644 --- a/core/arpeggiator_engine.py +++ b/core/arpeggiator_engine.py @@ -482,10 +482,15 @@ class ArpeggiatorEngine(QObject): def stop(self): """Stop arpeggiator playback""" + print(f"DEBUG: stop() called, is_playing = {self.is_playing}") if self.is_playing: + print("DEBUG: Stopping playback") self.is_playing = False self.all_notes_off() self.playing_state_changed.emit(False) + print("DEBUG: Playback stopped, emitted playing_state_changed(False)") + else: + print("DEBUG: Already stopped, nothing to do") def all_notes_off(self): """Send note off for all active notes""" @@ -526,32 +531,69 @@ class ArpeggiatorEngine(QObject): if self.current_pattern: original_pattern = self.current_pattern.copy() - # For directional patterns, adapt the pattern to fit the length + # For directional patterns, create proper up/down sequences if self.pattern_type in ["up_down", "down_up"] and self.user_pattern_length >= 4: - # For up_down with 4 steps: take first half up, second half down scale_notes = self._get_all_scale_notes() half_length = self.user_pattern_length // 2 if self.pattern_type == "up_down": - # First half: up progression - up_part = scale_notes[:half_length] if len(scale_notes) >= half_length else scale_notes * ((half_length // len(scale_notes)) + 1) - up_part = up_part[:half_length] + # Create up_down pattern: go up then reverse direction + pattern = [] + + # First generate an up pattern for the full length + up_pattern = [] + for i in range(self.user_pattern_length): + note_idx = i % len(scale_notes) + up_pattern.append(scale_notes[note_idx]) - # Second half: down progression - down_part = list(reversed(scale_notes))[:half_length] if len(scale_notes) >= half_length else list(reversed(scale_notes)) * ((half_length // len(scale_notes)) + 1) - down_part = down_part[:half_length] + # Now create the actual pattern: up for half, then down for half + for i in range(self.user_pattern_length): + if i < half_length: + # First half: go up + pattern.append(up_pattern[i]) + else: + # Second half: go down from where we were + # Start going backwards from the last up note + reverse_idx = half_length - 1 - (i - half_length) + if reverse_idx < 0: + # If we've gone past the beginning, continue the pattern + reverse_idx = (i - half_length) % len(scale_notes) + pattern.append(scale_notes[reverse_idx]) + else: + pattern.append(up_pattern[reverse_idx]) + + self.current_pattern = pattern - self.current_pattern = up_part + down_part elif self.pattern_type == "down_up": - # First half: down progression - down_part = list(reversed(scale_notes))[:half_length] if len(scale_notes) >= half_length else list(reversed(scale_notes)) * ((half_length // len(scale_notes)) + 1) - down_part = down_part[:half_length] + # Create down_up pattern: start high, go down then reverse direction + pattern = [] + + # First generate a down pattern starting from the highest note + down_pattern = [] + for i in range(self.user_pattern_length): + # Start from highest note and go down + note_idx = (len(scale_notes) - 1 - i) % len(scale_notes) + if note_idx < 0: + note_idx = len(scale_notes) + note_idx + down_pattern.append(scale_notes[note_idx]) - # Second half: up progression - up_part = scale_notes[:half_length] if len(scale_notes) >= half_length else scale_notes * ((half_length // len(scale_notes)) + 1) - up_part = up_part[:half_length] + # Now create the actual pattern: down for half, then up for half + for i in range(self.user_pattern_length): + if i < half_length: + # First half: go down from highest + pattern.append(down_pattern[i]) + else: + # Second half: go up from where we were + # Start going backwards from the last down note + reverse_idx = half_length - 1 - (i - half_length) + if reverse_idx < 0: + # If we've gone past the beginning, continue the pattern + reverse_idx = (i - half_length) % len(scale_notes) + pattern.append(scale_notes[reverse_idx]) + else: + pattern.append(down_pattern[reverse_idx]) - self.current_pattern = down_part + up_part + self.current_pattern = pattern else: # For other patterns, use the original logic if len(self.current_pattern) > self.user_pattern_length: diff --git a/gui/arpeggiator_controls.py b/gui/arpeggiator_controls.py index 22e567c..8bf615e 100644 --- a/gui/arpeggiator_controls.py +++ b/gui/arpeggiator_controls.py @@ -54,6 +54,30 @@ class ArpeggiatorControls(QWidget): self.armed_note_limit_button = None # Speed changes apply immediately - no armed state needed + # Parameter override tracking - when checked, preset changes ignore these parameters + self.parameter_overrides = { + 'root_note': False, + 'octave': False, + 'scale': False, + 'scale_note_start': False, + 'pattern_type': False, + 'pattern_length': False, + 'note_limit': False, + 'channel_distribution': False, + 'note_speed': False, + 'gate': False, + 'swing': False, + 'velocity': False, + 'tempo': False, + 'delay_enabled': False, + 'delay_length': False, + 'delay_timing': False, + 'delay_fade': False + } + + # Override checkbox widgets + self.override_checkboxes = {} + self.setup_ui() self.connect_signals() @@ -169,6 +193,39 @@ class ArpeggiatorControls(QWidget): else: self.apply_button_style(button, 12, "normal") + def create_parameter_label_with_override(self, text, param_name): + """Create a label with an override checkbox""" + from PyQt5.QtWidgets import QCheckBox + + container = QWidget() + layout = QHBoxLayout(container) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(8) + + # Create override checkbox + checkbox = QCheckBox() + checkbox.setFixedSize(16, 16) + checkbox.setToolTip(f"Override {text} - when checked, presets won't change this parameter") + checkbox.stateChanged.connect(lambda state, param=param_name: self.on_parameter_override_changed(param, state == 2)) + + # Create label + label = QLabel(text) + + # Add to layout + layout.addWidget(checkbox) + layout.addWidget(label) + layout.addStretch() # Push everything to the left + + # Store checkbox reference + self.override_checkboxes[param_name] = checkbox + + return container + + def on_parameter_override_changed(self, param_name, is_overridden): + """Handle parameter override checkbox changes""" + self.parameter_overrides[param_name] = is_overridden + print(f"DEBUG: Parameter {param_name} override: {is_overridden}") + def setup_ui(self): """Clean quadrant layout with readable buttons""" # Main grid with better spacing and expansion @@ -207,7 +264,7 @@ class ArpeggiatorControls(QWidget): layout.setContentsMargins(8, 8, 8, 8) # Root notes - 12 buttons in horizontal row, NO spacing between buttons - layout.addWidget(QLabel("Root Note:")) + layout.addWidget(self.create_parameter_label_with_override("Root Note:", "root_note")) notes_widget = QWidget() notes_layout = QHBoxLayout(notes_widget) notes_layout.setSpacing(0) # NO spacing between buttons @@ -228,7 +285,7 @@ class ArpeggiatorControls(QWidget): layout.addWidget(notes_widget) # Octaves - 6 buttons in horizontal row, NO spacing between buttons - layout.addWidget(QLabel("Octave:")) + layout.addWidget(self.create_parameter_label_with_override("Octave:", "octave")) octave_widget = QWidget() octave_layout = QHBoxLayout(octave_widget) octave_layout.setSpacing(0) # NO spacing between buttons @@ -248,7 +305,7 @@ class ArpeggiatorControls(QWidget): layout.addWidget(octave_widget) # Scales - 2 rows of 4, minimal vertical spacing - layout.addWidget(QLabel("Scale:")) + layout.addWidget(self.create_parameter_label_with_override("Scale:", "scale")) scales_widget = QWidget() scales_layout = QGridLayout(scales_widget) scales_layout.setSpacing(0) # NO horizontal spacing @@ -274,7 +331,7 @@ class ArpeggiatorControls(QWidget): layout.addWidget(scales_widget) # Scale notes selection - layout.addWidget(QLabel("Scale Notes:")) + layout.addWidget(self.create_parameter_label_with_override("Scale Notes:", "scale_note_start")) scale_notes_widget = QWidget() self.scale_notes_layout = QGridLayout(scale_notes_widget) self.scale_notes_layout.setSpacing(2) @@ -302,7 +359,7 @@ class ArpeggiatorControls(QWidget): layout.setSpacing(6) layout.setContentsMargins(8, 8, 8, 8) - layout.addWidget(QLabel("Distribution Pattern:")) + layout.addWidget(self.create_parameter_label_with_override("Distribution Pattern:", "channel_distribution")) # 2 rows of 4 distribution buttons dist_widget = QWidget() @@ -358,7 +415,7 @@ class ArpeggiatorControls(QWidget): layout.setSpacing(6) layout.setContentsMargins(8, 8, 8, 8) - layout.addWidget(QLabel("Arpeggio Pattern:")) + layout.addWidget(self.create_parameter_label_with_override("Arpeggio Pattern:", "pattern_type")) # 2 rows of 4 pattern buttons pattern_widget = QWidget() @@ -389,7 +446,7 @@ class ArpeggiatorControls(QWidget): layout.addWidget(pattern_widget) # Pattern length buttons - layout.addWidget(QLabel("Pattern Length:")) + layout.addWidget(self.create_parameter_label_with_override("Pattern Length:", "pattern_length")) length_widget = QWidget() length_layout = QGridLayout(length_widget) length_layout.setSpacing(0) # NO horizontal spacing @@ -414,7 +471,7 @@ class ArpeggiatorControls(QWidget): layout.addWidget(length_widget) # Note limit buttons - layout.addWidget(QLabel("Note Limit:")) + layout.addWidget(self.create_parameter_label_with_override("Note Limit:", "note_limit")) note_limit_widget = QWidget() note_limit_layout = QGridLayout(note_limit_widget) note_limit_layout.setSpacing(0) # NO horizontal spacing @@ -446,7 +503,7 @@ class ArpeggiatorControls(QWidget): # Tempo tempo_layout = QHBoxLayout() - tempo_layout.addWidget(QLabel("Tempo:")) + tempo_layout.addWidget(self.create_parameter_label_with_override("Tempo:", "tempo")) self.tempo_spin = QSpinBox() self.tempo_spin.setRange(40, 200) self.tempo_spin.setValue(120) @@ -456,7 +513,7 @@ class ArpeggiatorControls(QWidget): layout.addLayout(tempo_layout) # Speed buttons - layout.addWidget(QLabel("Note Speed:")) + layout.addWidget(self.create_parameter_label_with_override("Note Speed:", "note_speed")) speed_widget = QWidget() speed_layout = QHBoxLayout(speed_widget) speed_layout.setSpacing(0) # NO spacing between buttons @@ -482,7 +539,7 @@ class ArpeggiatorControls(QWidget): # Gate gate_layout = QHBoxLayout() - gate_layout.addWidget(QLabel("Gate:")) + gate_layout.addWidget(self.create_parameter_label_with_override("Gate:", "gate")) self.gate_slider = QSlider(Qt.Horizontal) self.gate_slider.setRange(10, 200) self.gate_slider.setValue(100) @@ -495,7 +552,7 @@ class ArpeggiatorControls(QWidget): # Swing swing_layout = QHBoxLayout() - swing_layout.addWidget(QLabel("Swing:")) + swing_layout.addWidget(self.create_parameter_label_with_override("Swing:", "swing")) self.swing_slider = QSlider(Qt.Horizontal) self.swing_slider.setRange(-100, 100) self.swing_slider.setValue(0) @@ -508,7 +565,7 @@ class ArpeggiatorControls(QWidget): # Velocity velocity_layout = QHBoxLayout() - velocity_layout.addWidget(QLabel("Velocity:")) + velocity_layout.addWidget(self.create_parameter_label_with_override("Velocity:", "velocity")) self.velocity_slider = QSlider(Qt.Horizontal) self.velocity_slider.setRange(1, 127) self.velocity_slider.setValue(80) @@ -525,7 +582,7 @@ class ArpeggiatorControls(QWidget): # Delay toggle self.delay_enabled = False delay_toggle_layout = QHBoxLayout() - delay_toggle_layout.addWidget(QLabel("Delay/Echo:")) + delay_toggle_layout.addWidget(self.create_parameter_label_with_override("Delay/Echo:", "delay_enabled")) self.delay_toggle = QPushButton("OFF") self.delay_toggle.setFixedSize(50, 20) self.delay_toggle.setCheckable(True) @@ -537,7 +594,7 @@ class ArpeggiatorControls(QWidget): # Delay length (0-8 repeats) delay_length_layout = QHBoxLayout() - delay_length_layout.addWidget(QLabel("Delay Length:")) + delay_length_layout.addWidget(self.create_parameter_label_with_override("Delay Length:", "delay_length")) self.delay_length_spin = QSpinBox() self.delay_length_spin.setRange(0, 8) self.delay_length_spin.setValue(3) @@ -548,7 +605,7 @@ class ArpeggiatorControls(QWidget): delay_layout.addLayout(delay_length_layout) # Delay timing buttons (same as note speed) - delay_timing_label = QLabel("Delay Timing:") + delay_timing_label = self.create_parameter_label_with_override("Delay Timing:", "delay_timing") delay_timing_label.setEnabled(False) delay_layout.addWidget(delay_timing_label) self.delay_timing_label = delay_timing_label @@ -583,7 +640,7 @@ class ArpeggiatorControls(QWidget): # Delay fade slider (percentage) delay_fade_layout = QHBoxLayout() - delay_fade_label = QLabel("Delay Fade:") + delay_fade_label = self.create_parameter_label_with_override("Delay Fade:", "delay_fade") delay_fade_label.setEnabled(False) delay_fade_layout.addWidget(delay_fade_label) self.delay_fade_label = delay_fade_label diff --git a/gui/main_window.py b/gui/main_window.py index 7ca2559..390f47f 100644 --- a/gui/main_window.py +++ b/gui/main_window.py @@ -8,7 +8,7 @@ Integrates all GUI components into a cohesive interface. from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QGridLayout, QPushButton, QLabel, QSlider, QComboBox, QSpinBox, QGroupBox, QTabWidget, QSplitter, QFrame, - QSizePolicy) + QSizePolicy, QShortcut) from PyQt5.QtCore import Qt, QTimer, pyqtSlot, QSize from PyQt5.QtGui import QFont, QPalette, QColor, QKeySequence @@ -102,27 +102,15 @@ class MainWindow(QMainWindow): self.scaling_enabled = True # Keyboard note mapping - self.keyboard_notes = { - Qt.Key_A: 60, # C - Qt.Key_W: 61, # C# - Qt.Key_S: 62, # D - Qt.Key_E: 63, # D# - Qt.Key_D: 64, # E - Qt.Key_F: 65, # F - Qt.Key_T: 66, # F# - Qt.Key_G: 67, # G - Qt.Key_Y: 68, # G# - Qt.Key_H: 69, # A - Qt.Key_U: 70, # A# - Qt.Key_J: 71, # B - Qt.Key_K: 72, # C (next octave) - } - self.held_keys = set() + # No keyboard note input - removed per user request self.setup_ui() self.setup_connections() self.apply_dark_theme() + # Set up global keyboard shortcut for emergency stop + self.setup_emergency_stop() + def setup_ui(self): """Initialize the user interface""" central_widget = QWidget() @@ -161,7 +149,7 @@ class MainWindow(QMainWindow): # Simulator display now integrated into arpeggiator tab - removed standalone tab # Presets tab - self.preset_controls = PresetControls(self.arpeggiator, self.channel_manager, self.volume_engine) + self.preset_controls = PresetControls(self.arpeggiator, self.channel_manager, self.volume_engine, self.arp_controls) tab_widget.addTab(self.preset_controls, "Presets") # Set up preset callback for armed preset system @@ -172,11 +160,46 @@ class MainWindow(QMainWindow): main_layout.addWidget(status_frame) # Create status bar - self.statusBar().showMessage("Ready - Use keyboard (AWSDFGTGHYUJ) to play notes, SPACE for emergency stop") + self.statusBar().showMessage("Ready - SPACE for emergency stop") # Create menu bar self.create_menu_bar() + def setup_emergency_stop(self): + """Set up global emergency stop that works even with text boxes focused""" + # Install event filter on the application to catch spacebar before any widget gets it + from PyQt5.QtWidgets import QApplication + app = QApplication.instance() + app.installEventFilter(self) + print("DEBUG: Emergency stop event filter installed") + + def emergency_stop(self): + """Emergency stop triggered by spacebar shortcut""" + print(f"DEBUG: Emergency stop activated, arpeggiator.is_playing = {self.arpeggiator.is_playing}") + if self.arpeggiator.is_playing: + print("DEBUG: Calling arpeggiator.stop()") + self.arpeggiator.stop() + self.statusBar().showMessage("Emergency stop activated", 1000) + print(f"DEBUG: After stop, is_playing = {self.arpeggiator.is_playing}") + else: + print("DEBUG: Arpeggiator not playing, ignoring emergency stop") + + def eventFilter(self, source, event): + """Global event filter to catch spacebar for emergency stop""" + from PyQt5.QtCore import QEvent + from PyQt5.QtGui import QKeyEvent + + # Only handle key press events + if event.type() == QEvent.KeyPress: + key_event = event + if key_event.key() == Qt.Key_Space and not key_event.isAutoRepeat(): + print(f"DEBUG: Spacebar intercepted from {source.__class__.__name__}") + self.emergency_stop() + return True # Consume the event to prevent text boxes from getting it + + # Let other events pass through normally + return super().eventFilter(source, event) + # Removed create_control_panel and create_display_panel methods - now using direct tab layout def create_transport_controls(self) -> QFrame: @@ -648,45 +671,7 @@ class MainWindow(QMainWindow): "• Built-in simulator mode\n" "• Native Instruments Maschine integration") - def keyPressEvent(self, event): - """Handle key press for note input""" - key = event.key() - - # Avoid key repeat - if event.isAutoRepeat(): - return - - if key in self.keyboard_notes: - note = self.keyboard_notes[key] - if note not in self.held_keys: - self.held_keys.add(note) - self.arpeggiator.note_on(note) - self.statusBar().showMessage(f"Note ON: {note}", 500) - - elif key == Qt.Key_Space: - # Spacebar emergency stop only (does not start playback) - if self.arpeggiator.is_playing: - self.on_stop_clicked() - self.statusBar().showMessage("Emergency stop activated", 1000) - - super().keyPressEvent(event) - - def keyReleaseEvent(self, event): - """Handle key release for note input""" - key = event.key() - - # Avoid key repeat - if event.isAutoRepeat(): - return - - if key in self.keyboard_notes: - note = self.keyboard_notes[key] - if note in self.held_keys: - self.held_keys.remove(note) - self.arpeggiator.note_off(note) - self.statusBar().showMessage(f"Note OFF: {note}", 500) - - super().keyReleaseEvent(event) + # Keyboard event handling removed - using QShortcut for emergency stop instead def resizeEvent(self, event): """Handle window resize for dynamic scaling""" diff --git a/gui/preset_controls.py b/gui/preset_controls.py index fe7110a..2b29507 100644 --- a/gui/preset_controls.py +++ b/gui/preset_controls.py @@ -17,11 +17,12 @@ import random class PresetControls(QWidget): """Control panel for preset management""" - def __init__(self, arpeggiator, channel_manager, volume_engine): + def __init__(self, arpeggiator, channel_manager, volume_engine, arpeggiator_controls=None): super().__init__() self.arpeggiator = arpeggiator self.channel_manager = channel_manager self.volume_engine = volume_engine + self.arpeggiator_controls = arpeggiator_controls # Preset storage self.presets = {} @@ -370,39 +371,58 @@ class PresetControls(QWidget): if data == preset: preset_name = name break - # Apply arpeggiator settings + # Apply arpeggiator settings (check for overrides first) arp_settings = preset.get("arpeggiator", {}) - self.arpeggiator.set_root_note(arp_settings.get("root_note", 60)) - self.arpeggiator.set_scale(arp_settings.get("scale", "major")) - self.arpeggiator.set_scale_note_start(arp_settings.get("scale_note_start", 0)) - self.arpeggiator.set_pattern_type(arp_settings.get("pattern_type", "up")) - self.arpeggiator.set_octave_range(arp_settings.get("octave_range", 1)) - self.arpeggiator.set_note_speed(arp_settings.get("note_speed", "1/8")) - self.arpeggiator.set_gate(arp_settings.get("gate", 1.0)) - self.arpeggiator.set_swing(arp_settings.get("swing", 0.0)) - self.arpeggiator.set_velocity(arp_settings.get("velocity", 80)) - self.arpeggiator.set_tempo(arp_settings.get("tempo", 120.0)) + + # Only apply settings that aren't overridden + if not self._is_parameter_overridden('root_note'): + self.arpeggiator.set_root_note(arp_settings.get("root_note", 60)) + if not self._is_parameter_overridden('scale'): + self.arpeggiator.set_scale(arp_settings.get("scale", "major")) + if not self._is_parameter_overridden('scale_note_start'): + self.arpeggiator.set_scale_note_start(arp_settings.get("scale_note_start", 0)) + if not self._is_parameter_overridden('pattern_type'): + self.arpeggiator.set_pattern_type(arp_settings.get("pattern_type", "up")) + if not self._is_parameter_overridden('octave'): + self.arpeggiator.set_octave_range(arp_settings.get("octave_range", 1)) + if not self._is_parameter_overridden('note_speed'): + self.arpeggiator.set_note_speed(arp_settings.get("note_speed", "1/8")) + if not self._is_parameter_overridden('gate'): + self.arpeggiator.set_gate(arp_settings.get("gate", 1.0)) + if not self._is_parameter_overridden('swing'): + self.arpeggiator.set_swing(arp_settings.get("swing", 0.0)) + if not self._is_parameter_overridden('velocity'): + self.arpeggiator.set_velocity(arp_settings.get("velocity", 80)) + if not self._is_parameter_overridden('tempo'): + self.arpeggiator.set_tempo(arp_settings.get("tempo", 120.0)) # Apply user pattern length (check both old and new names for compatibility) - pattern_length = arp_settings.get("user_pattern_length") or arp_settings.get("pattern_length", 8) - if hasattr(self.arpeggiator, 'set_user_pattern_length'): - self.arpeggiator.set_user_pattern_length(pattern_length) - elif hasattr(self.arpeggiator, 'set_pattern_length'): - self.arpeggiator.set_pattern_length(pattern_length) + if not self._is_parameter_overridden('pattern_length'): + pattern_length = arp_settings.get("user_pattern_length") or arp_settings.get("pattern_length", 8) + if hasattr(self.arpeggiator, 'set_user_pattern_length'): + self.arpeggiator.set_user_pattern_length(pattern_length) + elif hasattr(self.arpeggiator, 'set_pattern_length'): + self.arpeggiator.set_pattern_length(pattern_length) # Apply note limit - note_limit = arp_settings.get("note_limit", 7) - if hasattr(self.arpeggiator, 'set_note_limit'): - self.arpeggiator.set_note_limit(note_limit) + if not self._is_parameter_overridden('note_limit'): + note_limit = arp_settings.get("note_limit", 7) + if hasattr(self.arpeggiator, 'set_note_limit'): + self.arpeggiator.set_note_limit(note_limit) # Apply channel distribution - self.arpeggiator.set_channel_distribution(arp_settings.get("channel_distribution", "up")) + if not self._is_parameter_overridden('channel_distribution'): + self.arpeggiator.set_channel_distribution(arp_settings.get("channel_distribution", "up")) # Apply delay settings - self.arpeggiator.set_delay_enabled(arp_settings.get("delay_enabled", False)) - self.arpeggiator.set_delay_length(arp_settings.get("delay_length", 3)) - self.arpeggiator.set_delay_timing(arp_settings.get("delay_timing", "1/4")) - self.arpeggiator.set_delay_fade(arp_settings.get("delay_fade", 0.3)) + if not self._is_parameter_overridden('delay_enabled'): + self.arpeggiator.set_delay_enabled(arp_settings.get("delay_enabled", False)) + if not self._is_parameter_overridden('delay_length'): + self.arpeggiator.set_delay_length(arp_settings.get("delay_length", 3)) + if not self._is_parameter_overridden('delay_timing'): + self.arpeggiator.set_delay_timing(arp_settings.get("delay_timing", "1/4")) + if not self._is_parameter_overridden('delay_fade'): + self.arpeggiator.set_delay_fade(arp_settings.get("delay_fade", 0.3)) # Apply channel settings channel_settings = preset.get("channels", {}) @@ -702,6 +722,15 @@ class PresetControls(QWidget): except Exception as e: QMessageBox.critical(self, "Export Error", f"Error exporting preset: {str(e)}") + def _is_parameter_overridden(self, param_name): + """Check if a parameter is overridden (checkbox checked)""" + if self.arpeggiator_controls and hasattr(self.arpeggiator_controls, 'parameter_overrides'): + is_overridden = self.arpeggiator_controls.parameter_overrides.get(param_name, False) + if is_overridden: + print(f"DEBUG: Skipping {param_name} - parameter is overridden") + return is_overridden + return False + def load_presets_from_directory(self): """Load all presets from the presets directory""" if not os.path.exists(self.presets_directory):