Browse Source

Add parameter override checkboxes and fix spacebar emergency stop

- Add checkboxes next to all parameter labels to prevent preset changes during group cycling
- Enable selective parameter locking for live manipulation without preset interference
- Fix spacebar emergency stop with global event filter to work when text boxes are focused
- Remove keyboard note functionality (AWSDFGTGHYUJ keys) as requested
- Fix up_down and down_up pattern algorithms for proper note sequence reversals
- Improve debug logging for arpeggiator state changes

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
master
melancholytron 2 months ago
parent
commit
451aeec5fa
  1. 74
      core/arpeggiator_engine.py
  2. 91
      gui/arpeggiator_controls.py
  3. 101
      gui/main_window.py
  4. 79
      gui/preset_controls.py

74
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:

91
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

101
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"""

79
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):

Loading…
Cancel
Save