From 5e982b478ccac0cb544b7ae62803f9efd72f5020 Mon Sep 17 00:00:00 2001 From: melancholytron Date: Tue, 9 Sep 2025 12:52:17 -0500 Subject: [PATCH] Fix button styling consistency and switch delay system to velocity-based MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update volume and preset control buttons to use consistent dark theme styling - Fix volume pattern buttons with proper background colors and contrast - Apply consistent styling to all preset operation buttons (Load, Save, Delete, etc.) - Switch delay/echo system from channel volume to velocity-based fading - Prevents delay echoes from affecting original note volume when still playing - Each delayed note now uses individual velocity instead of channel-wide volume 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- core/arpeggiator_engine.py | 25 ++++++++----------- gui/preset_controls.py | 13 +++++++--- gui/volume_controls.py | 15 ++++++++--- presets/butt.json | 51 -------------------------------------- presets/butt2.json | 51 -------------------------------------- 5 files changed, 31 insertions(+), 124 deletions(-) delete mode 100644 presets/butt.json delete mode 100644 presets/butt2.json diff --git a/core/arpeggiator_engine.py b/core/arpeggiator_engine.py index c6b159f..2c4352b 100644 --- a/core/arpeggiator_engine.py +++ b/core/arpeggiator_engine.py @@ -123,7 +123,7 @@ class ArpeggiatorEngine(QObject): self.delay_timing = "1/4" # Timing between delays self.delay_fade = 0.3 # Volume fade per repeat (0.0-1.0) self.delay_step_duration = 0.0 # Calculated delay timing - self.scheduled_delays = [] # List of (time, channel, note, volume) tuples + self.scheduled_delays = [] # List of delay dicts with time, channel, note, velocity, duration # Input notes (what's being held down) self.held_notes: Set[int] = set() @@ -380,21 +380,21 @@ class ArpeggiatorEngine(QObject): # Convert to seconds self.delay_step_duration = delay_interval_beats / beats_per_second - def schedule_delays(self, channel: int, note: int, original_volume: int): + def schedule_delays(self, channel: int, note: int, original_velocity: int): """Schedule delay/echo repeats for a note""" current_time = time.time() - current_volume = original_volume + current_velocity = original_velocity for delay_step in range(1, self.delay_length + 1): # Calculate delay time delay_time = current_time + (delay_step * self.delay_step_duration) - # Calculate faded volume for this delay step + # Calculate faded velocity for this delay step fade_factor = (1.0 - self.delay_fade) ** delay_step - delayed_volume = int(current_volume * fade_factor) + delayed_velocity = int(current_velocity * fade_factor) - # Don't schedule if volume becomes too quiet - if delayed_volume < 5: + # Don't schedule if velocity becomes too quiet (MIDI velocity range is 1-127) + if delayed_velocity < 5: break # Schedule the delayed note @@ -402,8 +402,7 @@ class ArpeggiatorEngine(QObject): 'time': delay_time, 'channel': channel, 'note': note, - 'volume': delayed_volume, - 'velocity': self.velocity, # Use original velocity + 'velocity': delayed_velocity, # Use faded velocity 'duration': self.step_duration * self.gate }) @@ -417,14 +416,10 @@ class ArpeggiatorEngine(QObject): # Time to play this delayed note channel = delay['channel'] note = delay['note'] - volume = delay['volume'] velocity = delay['velocity'] duration = delay['duration'] - # Set the faded volume - self.output_manager.send_volume_change(channel, volume) - - # Send the delayed note + # Send the delayed note with faded velocity (no volume change needed) self.output_manager.send_note_on(channel, note, velocity) # Schedule note off for delayed note @@ -863,7 +858,7 @@ class ArpeggiatorEngine(QObject): # Schedule delay/echo if enabled if self.delay_enabled and self.delay_length > 0: - self.schedule_delays(target_channel, note, midi_volume) + self.schedule_delays(target_channel, note, static_velocity) # Schedule note off self.active_notes[(target_channel, note)] = note_end_time diff --git a/gui/preset_controls.py b/gui/preset_controls.py index 36062c5..f930463 100644 --- a/gui/preset_controls.py +++ b/gui/preset_controls.py @@ -83,47 +83,52 @@ class PresetControls(QWidget): self.load_button = QPushButton("Load Preset") self.load_button.setEnabled(False) self.load_button.clicked.connect(self.load_selected_preset) + self.load_button.setStyleSheet("background: #3a3a3a; color: #ffffff; font-weight: bold; font-size: 12px; border: 1px solid #555555; padding: 5px 10px;") layout.addWidget(self.load_button, 0, 0) # Save current as new preset self.save_new_button = QPushButton("Save as New...") self.save_new_button.clicked.connect(self.save_new_preset) + self.save_new_button.setStyleSheet("background: #3a3a3a; color: #ffffff; font-weight: bold; font-size: 12px; border: 1px solid #555555; padding: 5px 10px;") layout.addWidget(self.save_new_button, 0, 1) # Update selected preset self.update_button = QPushButton("Update Selected") self.update_button.setEnabled(False) self.update_button.clicked.connect(self.update_selected_preset) + self.update_button.setStyleSheet("background: #3a3a3a; color: #ffffff; font-weight: bold; font-size: 12px; border: 1px solid #555555; padding: 5px 10px;") layout.addWidget(self.update_button, 1, 0) # Delete preset self.delete_button = QPushButton("Delete Selected") self.delete_button.setEnabled(False) self.delete_button.clicked.connect(self.delete_selected_preset) - self.delete_button.setStyleSheet("color: #aa6666;") + self.delete_button.setStyleSheet("background: #5a2d2d; color: #ff9999; font-weight: bold; font-size: 12px; border: 1px solid #aa5555; padding: 5px 10px;") layout.addWidget(self.delete_button, 1, 1) # Rename preset self.rename_button = QPushButton("Rename Selected") self.rename_button.setEnabled(False) self.rename_button.clicked.connect(self.rename_selected_preset) + self.rename_button.setStyleSheet("background: #3a3a3a; color: #ffffff; font-weight: bold; font-size: 12px; border: 1px solid #555555; padding: 5px 10px;") layout.addWidget(self.rename_button, 2, 0) # Duplicate preset self.duplicate_button = QPushButton("Duplicate Selected") self.duplicate_button.setEnabled(False) self.duplicate_button.clicked.connect(self.duplicate_selected_preset) + self.duplicate_button.setStyleSheet("background: #3a3a3a; color: #ffffff; font-weight: bold; font-size: 12px; border: 1px solid #555555; padding: 5px 10px;") layout.addWidget(self.duplicate_button, 2, 1) # Safety controls self.force_apply_button = QPushButton("Force Apply Armed") self.force_apply_button.clicked.connect(self.force_apply_armed) - self.force_apply_button.setStyleSheet("color: #ffaa00; font-weight: bold;") + self.force_apply_button.setStyleSheet("background: #5a4d2d; color: #ffcc66; font-weight: bold; font-size: 12px; border: 1px solid #8a7a4a; padding: 5px 10px;") layout.addWidget(self.force_apply_button, 3, 0) self.clear_armed_button = QPushButton("Clear Armed") self.clear_armed_button.clicked.connect(self.clear_armed) - self.clear_armed_button.setStyleSheet("color: #aa6666;") + self.clear_armed_button.setStyleSheet("background: #5a2d2d; color: #ff9999; font-weight: bold; font-size: 12px; border: 1px solid #aa5555; padding: 5px 10px;") layout.addWidget(self.clear_armed_button, 3, 1) return group @@ -136,12 +141,14 @@ class PresetControls(QWidget): # Import preset self.import_button = QPushButton("Import Preset...") self.import_button.clicked.connect(self.import_preset) + self.import_button.setStyleSheet("background: #3a3a3a; color: #ffffff; font-weight: bold; font-size: 12px; border: 1px solid #555555; padding: 5px 10px;") layout.addWidget(self.import_button) # Export preset self.export_button = QPushButton("Export Selected...") self.export_button.setEnabled(False) self.export_button.clicked.connect(self.export_selected_preset) + self.export_button.setStyleSheet("background: #3a3a3a; color: #ffffff; font-weight: bold; font-size: 12px; border: 1px solid #555555; padding: 5px 10px;") layout.addWidget(self.export_button) return group diff --git a/gui/volume_controls.py b/gui/volume_controls.py index 4ad141d..aad743d 100644 --- a/gui/volume_controls.py +++ b/gui/volume_controls.py @@ -108,10 +108,11 @@ class VolumeControls(QWidget): if state == "active": button.setStyleSheet(""" QPushButton { - background-color: #2d5a2d; + background: #2d5a2d; color: white; - border: 2px solid #4a8a4a; + border: 1px solid #4a8a4a; font-weight: bold; + font-size: 12px; min-height: 30px; padding: 5px 10px; } @@ -119,10 +120,11 @@ class VolumeControls(QWidget): elif state == "armed": button.setStyleSheet(""" QPushButton { - background-color: #5a4d2d; + background: #ff8800; color: white; - border: 2px solid #8a7a4a; + border: 1px solid #ffaa00; font-weight: bold; + font-size: 12px; min-height: 30px; padding: 5px 10px; } @@ -130,6 +132,11 @@ class VolumeControls(QWidget): else: # inactive button.setStyleSheet(""" QPushButton { + background: #3a3a3a; + color: #ffffff; + border: 1px solid #555555; + font-weight: bold; + font-size: 12px; min-height: 30px; padding: 5px 10px; } diff --git a/presets/butt.json b/presets/butt.json deleted file mode 100644 index 653a540..0000000 --- a/presets/butt.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "version": "1.0", - "timestamp": "2025-09-07T23:11:06.343054", - "arpeggiator": { - "root_note": 50, - "scale": "pentatonic_major", - "pattern_type": "up", - "octave_range": 1, - "note_speed": "1/2", - "gate": 1.0, - "swing": 0.0, - "velocity": 80, - "tempo": 120.0 - }, - "channels": { - "active_synth_count": 3, - "channel_instruments": { - "1": 0, - "2": 0, - "3": 0, - "4": 0, - "5": 0, - "6": 0, - "7": 0, - "8": 0, - "9": 0, - "10": 0, - "11": 0, - "12": 0, - "13": 0, - "14": 0, - "15": 0, - "16": 0 - } - }, - "volume_patterns": { - "current_pattern": "swell", - "pattern_speed": 2.0, - "pattern_intensity": 1.0, - "global_volume_range": [ - 0.2, - 1.0 - ], - "global_velocity_range": [ - 40, - 127 - ], - "channel_volume_ranges": {}, - "velocity_ranges": {} - } -} \ No newline at end of file diff --git a/presets/butt2.json b/presets/butt2.json deleted file mode 100644 index 63aeb69..0000000 --- a/presets/butt2.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "version": "1.0", - "timestamp": "2025-09-08T11:17:34.584516", - "arpeggiator": { - "root_note": 60, - "scale": "major", - "pattern_type": "up", - "octave_range": 1, - "note_speed": "1/1", - "gate": 0.78, - "swing": 0.0, - "velocity": 100, - "tempo": 66.0 - }, - "channels": { - "active_synth_count": 8, - "channel_instruments": { - "1": 0, - "2": 0, - "3": 0, - "4": 0, - "5": 0, - "6": 0, - "7": 0, - "8": 0, - "9": 0, - "10": 0, - "11": 0, - "12": 0, - "13": 0, - "14": 0, - "15": 0, - "16": 0 - } - }, - "volume_patterns": { - "current_pattern": "random_sparkle", - "pattern_speed": 0.5, - "pattern_intensity": 1.0, - "global_volume_range": [ - 0.0, - 1.0 - ], - "global_velocity_range": [ - 126, - 127 - ], - "channel_volume_ranges": {}, - "velocity_ranges": {} - } -} \ No newline at end of file