You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
601 lines
29 KiB
601 lines
29 KiB
"""
|
|
Arpeggiator Controls - READABLE BUTTONS WITH PROPER SIZING
|
|
"""
|
|
|
|
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QGridLayout,
|
|
QGroupBox, QComboBox, QSlider, QSpinBox, QLabel,
|
|
QPushButton, QFrame)
|
|
from PyQt5.QtCore import Qt, pyqtSlot
|
|
|
|
class ArpeggiatorControls(QWidget):
|
|
"""Readable arpeggiator controls with properly sized buttons"""
|
|
|
|
def __init__(self, arpeggiator, channel_manager, simulator=None):
|
|
super().__init__()
|
|
self.arpeggiator = arpeggiator
|
|
self.channel_manager = channel_manager
|
|
self.simulator = simulator
|
|
|
|
# State tracking
|
|
self.presets = {}
|
|
self.current_preset = None
|
|
self.root_note_buttons = {}
|
|
self.octave_buttons = {}
|
|
self.scale_buttons = {}
|
|
self.pattern_buttons = {}
|
|
self.distribution_buttons = {}
|
|
self.speed_buttons = {}
|
|
|
|
self.current_root_note = 0
|
|
self.current_octave = 4
|
|
self.current_scale = "major"
|
|
self.current_pattern = "up"
|
|
self.current_distribution = "up"
|
|
self.current_speed = "1/8"
|
|
|
|
# Armed state tracking
|
|
self.armed_root_note_button = None
|
|
self.armed_octave_button = None
|
|
self.armed_scale_button = None
|
|
self.armed_pattern_button = None
|
|
self.armed_distribution_button = None
|
|
# Speed changes apply immediately - no armed state needed
|
|
|
|
self.setup_ui()
|
|
self.connect_signals()
|
|
|
|
def setup_ui(self):
|
|
"""Clean quadrant layout with readable buttons"""
|
|
# Main grid
|
|
main = QGridLayout(self)
|
|
main.setSpacing(8)
|
|
main.setContentsMargins(8, 8, 8, 8)
|
|
|
|
# Equal quadrants
|
|
main.addWidget(self.basic_quadrant(), 0, 0)
|
|
main.addWidget(self.distribution_quadrant(), 0, 1)
|
|
main.addWidget(self.pattern_quadrant(), 1, 0)
|
|
main.addWidget(self.timing_quadrant(), 1, 1)
|
|
|
|
main.setRowStretch(0, 1)
|
|
main.setRowStretch(1, 1)
|
|
main.setColumnStretch(0, 1)
|
|
main.setColumnStretch(1, 1)
|
|
|
|
def basic_quadrant(self):
|
|
"""Basic settings with readable buttons"""
|
|
group = QGroupBox("Basic Settings")
|
|
layout = QVBoxLayout(group)
|
|
layout.setSpacing(6)
|
|
layout.setContentsMargins(8, 8, 8, 8)
|
|
|
|
# Root notes - 12 buttons in horizontal row, NO spacing between buttons
|
|
layout.addWidget(QLabel("Root Note:"))
|
|
notes_widget = QWidget()
|
|
notes_layout = QHBoxLayout(notes_widget)
|
|
notes_layout.setSpacing(0) # NO spacing between buttons
|
|
notes_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
|
|
for i, note in enumerate(notes):
|
|
btn = QPushButton(note)
|
|
btn.setFixedSize(40, 22) # Taller buttons for better readability
|
|
btn.setCheckable(True)
|
|
btn.setStyleSheet("background: #3a3a3a; color: #ffffff; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #555555;")
|
|
btn.clicked.connect(lambda checked, n=i: self.on_root_note_clicked(n))
|
|
|
|
if i == 0:
|
|
btn.setChecked(True)
|
|
btn.setStyleSheet("background: #00aa44; color: white; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #00cc55;")
|
|
|
|
self.root_note_buttons[i] = btn
|
|
notes_layout.addWidget(btn)
|
|
|
|
layout.addWidget(notes_widget)
|
|
|
|
# Octaves - 6 buttons in horizontal row, NO spacing between buttons
|
|
layout.addWidget(QLabel("Octave:"))
|
|
octave_widget = QWidget()
|
|
octave_layout = QHBoxLayout(octave_widget)
|
|
octave_layout.setSpacing(0) # NO spacing between buttons
|
|
octave_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
for octave in range(3, 9): # C3 to C8
|
|
btn = QPushButton(f"C{octave}")
|
|
btn.setFixedSize(50, 22) # Taller buttons for better readability
|
|
btn.setCheckable(True)
|
|
btn.setStyleSheet("background: #3a3a3a; color: #ffffff; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #555555;")
|
|
btn.clicked.connect(lambda checked, o=octave: self.on_octave_clicked(o))
|
|
|
|
if octave == 4:
|
|
btn.setChecked(True)
|
|
btn.setStyleSheet("background: #00aa44; color: white; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #00cc55;")
|
|
|
|
self.octave_buttons[octave] = btn
|
|
octave_layout.addWidget(btn)
|
|
|
|
layout.addWidget(octave_widget)
|
|
|
|
# Scales - 2 rows of 4, minimal vertical spacing
|
|
layout.addWidget(QLabel("Scale:"))
|
|
scales_widget = QWidget()
|
|
scales_layout = QGridLayout(scales_widget)
|
|
scales_layout.setSpacing(0) # NO horizontal spacing
|
|
scales_layout.setVerticalSpacing(2) # Minimal vertical spacing
|
|
scales_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
main_scales = ["major", "minor", "dorian", "phrygian", "lydian", "mixolydian", "pentatonic_major", "pentatonic_minor"]
|
|
for i, scale in enumerate(main_scales):
|
|
display_name = scale.replace("_", " ").title()
|
|
if len(display_name) > 10:
|
|
display_name = display_name[:10]
|
|
|
|
btn = QPushButton(display_name)
|
|
btn.setFixedSize(120, 22) # Taller buttons for better readability
|
|
btn.setCheckable(True)
|
|
btn.setStyleSheet("background: #3a3a3a; color: #ffffff; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #555555;")
|
|
btn.clicked.connect(lambda checked, s=scale: self.on_scale_clicked(s))
|
|
|
|
if scale == "major":
|
|
btn.setChecked(True)
|
|
btn.setStyleSheet("background: #00aa44; color: white; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #00cc55;")
|
|
|
|
self.scale_buttons[scale] = btn
|
|
scales_layout.addWidget(btn, i // 4, i % 4)
|
|
|
|
layout.addWidget(scales_widget)
|
|
|
|
# Octave range dropdown
|
|
layout.addWidget(QLabel("Octave Range:"))
|
|
self.octave_range_combo = QComboBox()
|
|
self.octave_range_combo.setFixedHeight(20)
|
|
for i in range(1, 5):
|
|
self.octave_range_combo.addItem(f"{i} octave{'s' if i > 1 else ''}")
|
|
layout.addWidget(self.octave_range_combo)
|
|
|
|
return group
|
|
|
|
def distribution_quadrant(self):
|
|
"""Distribution with readable buttons and simulator display"""
|
|
group = QGroupBox("Channel Distribution")
|
|
layout = QVBoxLayout(group)
|
|
layout.setSpacing(6)
|
|
layout.setContentsMargins(8, 8, 8, 8)
|
|
|
|
layout.addWidget(QLabel("Distribution Pattern:"))
|
|
|
|
# 2 rows of 4 distribution buttons
|
|
dist_widget = QWidget()
|
|
dist_layout = QGridLayout(dist_widget)
|
|
dist_layout.setSpacing(0) # NO horizontal spacing
|
|
dist_layout.setVerticalSpacing(2) # Minimal vertical spacing
|
|
dist_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
patterns = ["up", "down", "up_down", "bounce", "random", "cycle", "alternating", "single_channel"]
|
|
for i, pattern in enumerate(patterns):
|
|
display_name = pattern.replace("_", " ").title()
|
|
if len(display_name) > 12:
|
|
display_name = display_name[:12]
|
|
|
|
btn = QPushButton(display_name)
|
|
btn.setFixedSize(120, 22) # Taller buttons for better readability
|
|
btn.setCheckable(True)
|
|
btn.setStyleSheet("background: #3a3a3a; color: #ffffff; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #555555;")
|
|
btn.clicked.connect(lambda checked, p=pattern: self.on_distribution_clicked(p))
|
|
|
|
if pattern == "up":
|
|
btn.setChecked(True)
|
|
btn.setStyleSheet("background: #0066cc; color: white; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #0088ee;")
|
|
|
|
self.distribution_buttons[pattern] = btn
|
|
dist_layout.addWidget(btn, i // 4, i % 4)
|
|
|
|
layout.addWidget(dist_widget)
|
|
|
|
# Description
|
|
self.dist_desc = QLabel("Channels: 1 → 2 → 3 → 4 → 5 → 6...")
|
|
self.dist_desc.setStyleSheet("font-size: 10px; color: gray;")
|
|
layout.addWidget(self.dist_desc)
|
|
|
|
# Simulator display
|
|
if self.simulator:
|
|
from .simulator_display import SimulatorDisplay
|
|
self.simulator_display = SimulatorDisplay(self.simulator, self.channel_manager)
|
|
layout.addWidget(self.simulator_display)
|
|
else:
|
|
# Create placeholder for now
|
|
placeholder = QLabel("Simulator display will appear here")
|
|
placeholder.setStyleSheet("font-size: 10px; color: gray; text-align: center;")
|
|
placeholder.setAlignment(Qt.AlignCenter)
|
|
layout.addWidget(placeholder)
|
|
|
|
return group
|
|
|
|
def pattern_quadrant(self):
|
|
"""Pattern with readable buttons"""
|
|
group = QGroupBox("Pattern Settings")
|
|
layout = QVBoxLayout(group)
|
|
layout.setSpacing(6)
|
|
layout.setContentsMargins(8, 8, 8, 8)
|
|
|
|
layout.addWidget(QLabel("Arpeggio Pattern:"))
|
|
|
|
# 2 rows of 4 pattern buttons
|
|
pattern_widget = QWidget()
|
|
pattern_layout = QGridLayout(pattern_widget)
|
|
pattern_layout.setSpacing(0) # NO horizontal spacing
|
|
pattern_layout.setVerticalSpacing(2) # Minimal vertical spacing
|
|
pattern_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
patterns = ["up", "down", "up_down", "down_up", "random", "chord", "note_order", "custom"]
|
|
for i, pattern in enumerate(patterns):
|
|
display_name = pattern.replace("_", " ").title()
|
|
if len(display_name) > 12:
|
|
display_name = display_name[:12]
|
|
|
|
btn = QPushButton(display_name)
|
|
btn.setFixedSize(120, 22) # Taller buttons for better readability
|
|
btn.setCheckable(True)
|
|
btn.setStyleSheet("background: #3a3a3a; color: #ffffff; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #555555;")
|
|
btn.clicked.connect(lambda checked, p=pattern: self.on_pattern_clicked(p))
|
|
|
|
if pattern == "up":
|
|
btn.setChecked(True)
|
|
btn.setStyleSheet("background: #cc6600; color: white; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #ee8800;")
|
|
|
|
self.pattern_buttons[pattern] = btn
|
|
pattern_layout.addWidget(btn, i // 4, i % 4)
|
|
|
|
layout.addWidget(pattern_widget)
|
|
|
|
return group
|
|
|
|
def timing_quadrant(self):
|
|
"""Timing with readable controls"""
|
|
group = QGroupBox("Timing Settings")
|
|
layout = QVBoxLayout(group)
|
|
layout.setSpacing(6)
|
|
layout.setContentsMargins(8, 8, 8, 8)
|
|
|
|
# Tempo
|
|
tempo_layout = QHBoxLayout()
|
|
tempo_layout.addWidget(QLabel("Tempo:"))
|
|
self.tempo_spin = QSpinBox()
|
|
self.tempo_spin.setRange(40, 200)
|
|
self.tempo_spin.setValue(120)
|
|
self.tempo_spin.setSuffix(" BPM")
|
|
self.tempo_spin.setFixedHeight(20)
|
|
tempo_layout.addWidget(self.tempo_spin)
|
|
layout.addLayout(tempo_layout)
|
|
|
|
# Speed buttons
|
|
layout.addWidget(QLabel("Note Speed:"))
|
|
speed_widget = QWidget()
|
|
speed_layout = QHBoxLayout(speed_widget)
|
|
speed_layout.setSpacing(0) # NO spacing between buttons
|
|
speed_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
self.speed_buttons = {}
|
|
speeds = ["1/32", "1/16", "1/8", "1/4", "1/2", "1/1"]
|
|
for speed in speeds:
|
|
btn = QPushButton(speed)
|
|
btn.setFixedSize(50, 22) # Taller buttons for better readability
|
|
btn.setCheckable(True)
|
|
btn.setStyleSheet("background: #3a3a3a; color: #ffffff; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #555555;")
|
|
btn.clicked.connect(lambda checked, s=speed: self.on_speed_clicked(s))
|
|
|
|
if speed == "1/8":
|
|
btn.setChecked(True)
|
|
btn.setStyleSheet("background: #9933cc; color: white; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #bb55ee;")
|
|
|
|
self.speed_buttons[speed] = btn
|
|
speed_layout.addWidget(btn)
|
|
|
|
layout.addWidget(speed_widget)
|
|
|
|
# Gate
|
|
gate_layout = QHBoxLayout()
|
|
gate_layout.addWidget(QLabel("Gate:"))
|
|
self.gate_slider = QSlider(Qt.Horizontal)
|
|
self.gate_slider.setRange(10, 200)
|
|
self.gate_slider.setValue(100)
|
|
self.gate_slider.setFixedHeight(20)
|
|
gate_layout.addWidget(self.gate_slider)
|
|
self.gate_label = QLabel("100%")
|
|
self.gate_label.setFixedWidth(40)
|
|
gate_layout.addWidget(self.gate_label)
|
|
layout.addLayout(gate_layout)
|
|
|
|
# Swing
|
|
swing_layout = QHBoxLayout()
|
|
swing_layout.addWidget(QLabel("Swing:"))
|
|
self.swing_slider = QSlider(Qt.Horizontal)
|
|
self.swing_slider.setRange(-100, 100)
|
|
self.swing_slider.setValue(0)
|
|
self.swing_slider.setFixedHeight(20)
|
|
swing_layout.addWidget(self.swing_slider)
|
|
self.swing_label = QLabel("0%")
|
|
self.swing_label.setFixedWidth(40)
|
|
swing_layout.addWidget(self.swing_label)
|
|
layout.addLayout(swing_layout)
|
|
|
|
# Velocity
|
|
velocity_layout = QHBoxLayout()
|
|
velocity_layout.addWidget(QLabel("Velocity:"))
|
|
self.velocity_slider = QSlider(Qt.Horizontal)
|
|
self.velocity_slider.setRange(1, 127)
|
|
self.velocity_slider.setValue(80)
|
|
self.velocity_slider.setFixedHeight(20)
|
|
velocity_layout.addWidget(self.velocity_slider)
|
|
self.velocity_label = QLabel("80")
|
|
self.velocity_label.setFixedWidth(40)
|
|
velocity_layout.addWidget(self.velocity_label)
|
|
layout.addLayout(velocity_layout)
|
|
|
|
# Presets
|
|
preset_layout = QHBoxLayout()
|
|
self.save_btn = QPushButton("Save Preset")
|
|
self.save_btn.setFixedSize(80, 20)
|
|
self.load_btn = QPushButton("Load Preset")
|
|
self.load_btn.setFixedSize(80, 20)
|
|
preset_layout.addWidget(self.save_btn)
|
|
preset_layout.addWidget(self.load_btn)
|
|
preset_layout.addStretch()
|
|
layout.addLayout(preset_layout)
|
|
|
|
return group
|
|
|
|
def connect_signals(self):
|
|
"""Connect all signals"""
|
|
self.tempo_spin.valueChanged.connect(self.on_tempo_changed)
|
|
# Speed is now handled by individual button click handlers
|
|
self.gate_slider.valueChanged.connect(self.on_gate_changed)
|
|
self.swing_slider.valueChanged.connect(self.on_swing_changed)
|
|
self.velocity_slider.valueChanged.connect(self.on_velocity_changed)
|
|
self.octave_range_combo.currentIndexChanged.connect(self.on_octave_range_changed)
|
|
self.save_btn.clicked.connect(self.save_preset)
|
|
self.load_btn.clicked.connect(self.load_preset)
|
|
|
|
if hasattr(self.arpeggiator, 'armed_state_changed'):
|
|
self.arpeggiator.armed_state_changed.connect(self.update_armed_states)
|
|
|
|
# Event handlers
|
|
def on_root_note_clicked(self, note_index):
|
|
midi_note = self.current_octave * 12 + note_index
|
|
|
|
if hasattr(self.arpeggiator, 'is_playing') and self.arpeggiator.is_playing:
|
|
# ARMED STATE - button turns orange, waits for pattern end
|
|
if self.armed_root_note_button:
|
|
self.armed_root_note_button.setStyleSheet("background: #3a3a3a; color: #ffffff; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #555555;")
|
|
|
|
self.armed_root_note_button = self.root_note_buttons[note_index]
|
|
self.root_note_buttons[note_index].setStyleSheet("background: #ff8800; color: white; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #ffaa00;")
|
|
|
|
if hasattr(self.arpeggiator, 'arm_root_note'):
|
|
self.arpeggiator.arm_root_note(midi_note)
|
|
else:
|
|
# IMMEDIATE CHANGE - apply right away
|
|
if self.current_root_note in self.root_note_buttons:
|
|
self.root_note_buttons[self.current_root_note].setStyleSheet("font-size: 12px; font-weight: bold; padding: 0px;")
|
|
|
|
self.current_root_note = note_index
|
|
self.root_note_buttons[note_index].setStyleSheet("background: #4CAF50; color: white; font-size: 12px; font-weight: bold; padding: 0px;")
|
|
|
|
if hasattr(self.arpeggiator, 'set_root_note'):
|
|
self.arpeggiator.set_root_note(midi_note)
|
|
|
|
def on_octave_clicked(self, octave):
|
|
midi_note = octave * 12 + self.current_root_note
|
|
|
|
if hasattr(self.arpeggiator, 'is_playing') and self.arpeggiator.is_playing:
|
|
# ARMED STATE - button turns orange
|
|
if self.armed_octave_button:
|
|
self.armed_octave_button.setStyleSheet("background: #3a3a3a; color: #ffffff; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #555555;")
|
|
|
|
self.armed_octave_button = self.octave_buttons[octave]
|
|
self.octave_buttons[octave].setStyleSheet("background: #ff8800; color: white; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #ffaa00;")
|
|
|
|
if hasattr(self.arpeggiator, 'arm_root_note'):
|
|
self.arpeggiator.arm_root_note(midi_note)
|
|
else:
|
|
# IMMEDIATE CHANGE
|
|
if self.current_octave in self.octave_buttons:
|
|
self.octave_buttons[self.current_octave].setStyleSheet("font-size: 12px; font-weight: bold; padding: 0px;")
|
|
|
|
self.current_octave = octave
|
|
self.octave_buttons[octave].setStyleSheet("background: #4CAF50; color: white; font-size: 12px; font-weight: bold; padding: 0px;")
|
|
|
|
if hasattr(self.arpeggiator, 'set_root_note'):
|
|
self.arpeggiator.set_root_note(midi_note)
|
|
|
|
def on_scale_clicked(self, scale):
|
|
if hasattr(self.arpeggiator, 'is_playing') and self.arpeggiator.is_playing:
|
|
# ARMED STATE - button turns orange
|
|
if self.armed_scale_button:
|
|
self.armed_scale_button.setStyleSheet("background: #3a3a3a; color: #ffffff; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #555555;")
|
|
|
|
self.armed_scale_button = self.scale_buttons[scale]
|
|
self.scale_buttons[scale].setStyleSheet("background: #ff8800; color: white; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #ffaa00;")
|
|
|
|
if hasattr(self.arpeggiator, 'arm_scale'):
|
|
self.arpeggiator.arm_scale(scale)
|
|
else:
|
|
# IMMEDIATE CHANGE
|
|
if self.current_scale in self.scale_buttons:
|
|
self.scale_buttons[self.current_scale].setStyleSheet("font-size: 12px; font-weight: bold; padding: 0px;")
|
|
|
|
self.current_scale = scale
|
|
self.scale_buttons[scale].setStyleSheet("background: #4CAF50; color: white; font-size: 12px; font-weight: bold; padding: 0px;")
|
|
|
|
if hasattr(self.arpeggiator, 'set_scale'):
|
|
self.arpeggiator.set_scale(scale)
|
|
|
|
def on_pattern_clicked(self, pattern):
|
|
if hasattr(self.arpeggiator, 'is_playing') and self.arpeggiator.is_playing:
|
|
# ARMED STATE - button turns orange
|
|
if self.armed_pattern_button:
|
|
self.armed_pattern_button.setStyleSheet("background: #3a3a3a; color: #ffffff; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #555555;")
|
|
|
|
self.armed_pattern_button = self.pattern_buttons[pattern]
|
|
self.pattern_buttons[pattern].setStyleSheet("background: #ff8800; color: white; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #ffaa00;")
|
|
|
|
if hasattr(self.arpeggiator, 'arm_pattern_type'):
|
|
self.arpeggiator.arm_pattern_type(pattern)
|
|
else:
|
|
# IMMEDIATE CHANGE
|
|
if self.current_pattern in self.pattern_buttons:
|
|
self.pattern_buttons[self.current_pattern].setStyleSheet("font-size: 12px; font-weight: bold; padding: 0px;")
|
|
|
|
self.current_pattern = pattern
|
|
self.pattern_buttons[pattern].setStyleSheet("background: #4CAF50; color: white; font-size: 12px; font-weight: bold; padding: 0px;")
|
|
|
|
if hasattr(self.arpeggiator, 'set_pattern_type'):
|
|
self.arpeggiator.set_pattern_type(pattern)
|
|
|
|
def on_distribution_clicked(self, pattern):
|
|
if hasattr(self.arpeggiator, 'is_playing') and self.arpeggiator.is_playing:
|
|
# ARMED STATE - button turns orange
|
|
if self.armed_distribution_button:
|
|
self.armed_distribution_button.setStyleSheet("background: #3a3a3a; color: #ffffff; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #555555;")
|
|
|
|
self.armed_distribution_button = self.distribution_buttons[pattern]
|
|
self.distribution_buttons[pattern].setStyleSheet("background: #ff8800; color: white; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #ffaa00;")
|
|
|
|
if hasattr(self.arpeggiator, 'arm_channel_distribution'):
|
|
self.arpeggiator.arm_channel_distribution(pattern)
|
|
else:
|
|
# IMMEDIATE CHANGE
|
|
if self.current_distribution in self.distribution_buttons:
|
|
self.distribution_buttons[self.current_distribution].setStyleSheet("font-size: 12px; font-weight: bold; padding: 0px;")
|
|
|
|
self.current_distribution = pattern
|
|
self.distribution_buttons[pattern].setStyleSheet("background: #4CAF50; color: white; font-size: 12px; font-weight: bold; padding: 0px;")
|
|
|
|
if hasattr(self.arpeggiator, 'set_channel_distribution'):
|
|
self.arpeggiator.set_channel_distribution(pattern)
|
|
|
|
def on_speed_clicked(self, speed):
|
|
# Speed changes apply immediately (no armed state needed for timing)
|
|
if self.current_speed in self.speed_buttons:
|
|
self.speed_buttons[self.current_speed].setStyleSheet("background: #3a3a3a; color: #ffffff; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #555555;")
|
|
|
|
self.current_speed = speed
|
|
self.speed_buttons[speed].setStyleSheet("background: #9933cc; color: white; font-size: 12px; font-weight: bold; padding: 0px; border: 1px solid #bb55ee;")
|
|
|
|
if hasattr(self.arpeggiator, 'set_note_speed'):
|
|
self.arpeggiator.set_note_speed(speed)
|
|
|
|
@pyqtSlot(int)
|
|
def on_tempo_changed(self, tempo):
|
|
if hasattr(self.arpeggiator, 'set_tempo'):
|
|
self.arpeggiator.set_tempo(float(tempo))
|
|
|
|
# on_speed_changed removed - now using on_speed_clicked with buttons
|
|
|
|
@pyqtSlot(int)
|
|
def on_gate_changed(self, value):
|
|
self.gate_label.setText(f"{value}%")
|
|
if hasattr(self.arpeggiator, 'set_gate'):
|
|
self.arpeggiator.set_gate(value / 100.0)
|
|
|
|
@pyqtSlot(int)
|
|
def on_swing_changed(self, value):
|
|
self.swing_label.setText(f"{value}%")
|
|
if hasattr(self.arpeggiator, 'set_swing'):
|
|
self.arpeggiator.set_swing(value / 100.0)
|
|
|
|
@pyqtSlot(int)
|
|
def on_velocity_changed(self, value):
|
|
self.velocity_label.setText(str(value))
|
|
if hasattr(self.arpeggiator, 'set_velocity'):
|
|
self.arpeggiator.set_velocity(value)
|
|
|
|
@pyqtSlot(int)
|
|
def on_octave_range_changed(self, index):
|
|
if hasattr(self.arpeggiator, 'set_octave_range'):
|
|
self.arpeggiator.set_octave_range(index + 1)
|
|
|
|
def save_preset(self):
|
|
preset_name = f"Preset_{len(self.presets) + 1}"
|
|
self.presets[preset_name] = {
|
|
'root_note': self.current_root_note,
|
|
'octave': self.current_octave,
|
|
'scale': self.current_scale,
|
|
'pattern': self.current_pattern,
|
|
'distribution': self.current_distribution
|
|
}
|
|
print(f"Saved {preset_name}")
|
|
|
|
def load_preset(self):
|
|
if not self.presets:
|
|
print("No presets saved")
|
|
return
|
|
preset_name = list(self.presets.keys())[0]
|
|
preset = self.presets[preset_name]
|
|
# Apply preset logic here
|
|
print(f"Loaded {preset_name}")
|
|
|
|
@pyqtSlot()
|
|
def update_armed_states(self):
|
|
"""Handle armed state updates - orange buttons become green at pattern end"""
|
|
# Check if armed states were applied (armed values become None when applied)
|
|
|
|
# Root note armed -> active
|
|
if self.armed_root_note_button and hasattr(self.arpeggiator, 'armed_root_note') and self.arpeggiator.armed_root_note is None:
|
|
# Find which note this was
|
|
for note_index, btn in self.root_note_buttons.items():
|
|
if btn == self.armed_root_note_button:
|
|
# Clear old active
|
|
if self.current_root_note in self.root_note_buttons:
|
|
self.root_note_buttons[self.current_root_note].setStyleSheet("font-size: 12px; font-weight: bold; padding: 0px;")
|
|
# Set new active (orange -> green)
|
|
self.current_root_note = note_index
|
|
btn.setStyleSheet("background: #4CAF50; color: white; font-size: 12px; font-weight: bold; padding: 0px;")
|
|
self.armed_root_note_button = None
|
|
break
|
|
|
|
# Octave armed -> active
|
|
if self.armed_octave_button and hasattr(self.arpeggiator, 'armed_root_note') and self.arpeggiator.armed_root_note is None:
|
|
for octave, btn in self.octave_buttons.items():
|
|
if btn == self.armed_octave_button:
|
|
if self.current_octave in self.octave_buttons:
|
|
self.octave_buttons[self.current_octave].setStyleSheet("font-size: 12px; font-weight: bold; padding: 0px;")
|
|
self.current_octave = octave
|
|
btn.setStyleSheet("background: #4CAF50; color: white; font-size: 12px; font-weight: bold; padding: 0px;")
|
|
self.armed_octave_button = None
|
|
break
|
|
|
|
# Scale armed -> active
|
|
if self.armed_scale_button and hasattr(self.arpeggiator, 'armed_scale') and self.arpeggiator.armed_scale is None:
|
|
for scale, btn in self.scale_buttons.items():
|
|
if btn == self.armed_scale_button:
|
|
if self.current_scale in self.scale_buttons:
|
|
self.scale_buttons[self.current_scale].setStyleSheet("font-size: 12px; font-weight: bold; padding: 0px;")
|
|
self.current_scale = scale
|
|
btn.setStyleSheet("background: #4CAF50; color: white; font-size: 12px; font-weight: bold; padding: 0px;")
|
|
self.armed_scale_button = None
|
|
break
|
|
|
|
# Pattern armed -> active
|
|
if self.armed_pattern_button and hasattr(self.arpeggiator, 'armed_pattern_type') and self.arpeggiator.armed_pattern_type is None:
|
|
for pattern, btn in self.pattern_buttons.items():
|
|
if btn == self.armed_pattern_button:
|
|
if self.current_pattern in self.pattern_buttons:
|
|
self.pattern_buttons[self.current_pattern].setStyleSheet("font-size: 12px; font-weight: bold; padding: 0px;")
|
|
self.current_pattern = pattern
|
|
btn.setStyleSheet("background: #4CAF50; color: white; font-size: 12px; font-weight: bold; padding: 0px;")
|
|
self.armed_pattern_button = None
|
|
break
|
|
|
|
# Distribution armed -> active
|
|
if self.armed_distribution_button and hasattr(self.arpeggiator, 'armed_channel_distribution') and self.arpeggiator.armed_channel_distribution is None:
|
|
for distribution, btn in self.distribution_buttons.items():
|
|
if btn == self.armed_distribution_button:
|
|
if self.current_distribution in self.distribution_buttons:
|
|
self.distribution_buttons[self.current_distribution].setStyleSheet("font-size: 12px; font-weight: bold; padding: 0px;")
|
|
self.current_distribution = distribution
|
|
btn.setStyleSheet("background: #4CAF50; color: white; font-size: 12px; font-weight: bold; padding: 0px;")
|
|
self.armed_distribution_button = None
|
|
break
|
|
|
|
# Speed changes apply immediately - no armed state needed
|