Browse Source

Fix button styling consistency and switch delay system to velocity-based

- 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 <noreply@anthropic.com>
master
melancholytron 2 months ago
parent
commit
5e982b478c
  1. 25
      core/arpeggiator_engine.py
  2. 13
      gui/preset_controls.py
  3. 15
      gui/volume_controls.py
  4. 51
      presets/butt.json
  5. 51
      presets/butt2.json

25
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

13
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

15
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;
}

51
presets/butt.json

@ -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": {}
}
}

51
presets/butt2.json

@ -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": {}
}
}
Loading…
Cancel
Save