@ -8,7 +8,8 @@ Generates arpeggio patterns, handles timing, and integrates with routing and vol
import time
import time
import math
import math
import threading
import threading
from typing import Dict , List , Optional , Tuple , Set
import random
from typing import Dict , List , Optional , Tuple , Set , Union
from PyQt5.QtCore import QObject , pyqtSignal , QTimer
from PyQt5.QtCore import QObject , pyqtSignal , QTimer
from .midi_channel_manager import MIDIChannelManager
from .midi_channel_manager import MIDIChannelManager
@ -237,13 +238,19 @@ class ArpeggiatorEngine(QObject):
""" Set base velocity 0-127 """
""" Set base velocity 0-127 """
self . velocity = max ( 0 , min ( 127 , velocity ) )
self . velocity = max ( 0 , min ( 127 , velocity ) )
def set_scale_note_start ( self , scale_note_index : int ) :
""" Set which scale note to start the arpeggio from """
def set_scale_note_start ( self , scale_note_index : Union [ int , str ] ) :
""" Set which scale note to start the arpeggio from (integer index or ' random ' ) """
if scale_note_index == " random " :
self . scale_note_start = " random "
else :
self . scale_note_start = max ( 0 , scale_note_index )
self . scale_note_start = max ( 0 , scale_note_index )
self . regenerate_pattern ( )
self . regenerate_pattern ( )
def arm_scale_note_start ( self , scale_note_index : int ) :
def arm_scale_note_start ( self , scale_note_index : Union [ int , str ] ) :
""" Arm a scale note start position to change at pattern end """
""" Arm a scale note start position to change at pattern end """
if scale_note_index == " random " :
self . armed_scale_note_start = " random "
else :
self . armed_scale_note_start = max ( 0 , scale_note_index )
self . armed_scale_note_start = max ( 0 , scale_note_index )
self . armed_state_changed . emit ( )
self . armed_state_changed . emit ( )
@ -618,7 +625,8 @@ class ArpeggiatorEngine(QObject):
root_in_octave = self . root_note % 12
root_in_octave = self . root_note % 12
# Get the interval for the selected scale degree
# Get the interval for the selected scale degree
start_degree = self . scale_note_start % len ( scale_intervals )
actual_start = self . _get_actual_scale_note_start ( )
start_degree = actual_start % len ( scale_intervals )
start_interval = scale_intervals [ start_degree ]
start_interval = scale_intervals [ start_degree ]
starting_note = base_octave * 12 + root_in_octave + start_interval
starting_note = base_octave * 12 + root_in_octave + start_interval
@ -651,7 +659,8 @@ class ArpeggiatorEngine(QObject):
root_in_octave = self . root_note % 12
root_in_octave = self . root_note % 12
# Get the interval for the selected scale degree
# Get the interval for the selected scale degree
start_degree = self . scale_note_start % len ( scale_intervals )
actual_start = self . _get_actual_scale_note_start ( )
start_degree = actual_start % len ( scale_intervals )
start_interval = scale_intervals [ start_degree ]
start_interval = scale_intervals [ start_degree ]
starting_note = base_octave * 12 + root_in_octave + start_interval
starting_note = base_octave * 12 + root_in_octave + start_interval
@ -695,7 +704,8 @@ class ArpeggiatorEngine(QObject):
root_in_octave = self . root_note % 12
root_in_octave = self . root_note % 12
# Get the interval for the selected scale degree
# Get the interval for the selected scale degree
start_degree = self . scale_note_start % len ( scale_intervals )
actual_start = self . _get_actual_scale_note_start ( )
start_degree = actual_start % len ( scale_intervals )
# Generate only the number of notes specified by note_limit
# Generate only the number of notes specified by note_limit
for i in range ( self . note_limit ) :
for i in range ( self . note_limit ) :
@ -713,6 +723,14 @@ class ArpeggiatorEngine(QObject):
return notes
return notes
def _get_actual_scale_note_start ( self ) - > int :
""" Get the actual scale note start index to use, handling random selection """
if self . scale_note_start == " random " :
scale_intervals = self . SCALES [ self . scale ]
return random . randint ( 0 , len ( scale_intervals ) - 1 )
else :
return self . scale_note_start
def _get_all_scale_notes ( self ) - > List [ int ] :
def _get_all_scale_notes ( self ) - > List [ int ] :
""" Get all available scale notes in both directions for patterns that need full range """
""" Get all available scale notes in both directions for patterns that need full range """
# Use limited scale notes if note_limit is less than full scale
# Use limited scale notes if note_limit is less than full scale