Better arp
This commit is contained in:
parent
6734572a67
commit
225d04a53c
100
ArpStrategy.h
100
ArpStrategy.h
@ -10,24 +10,102 @@ public:
|
|||||||
randomSeed(seed);
|
randomSeed(seed);
|
||||||
if (numScaleNotes == 0) return;
|
if (numScaleNotes == 0) return;
|
||||||
|
|
||||||
int currentNoteIndex = 0;
|
// 1. Select Subset
|
||||||
int octave = 4;
|
int subsetSize = random(2, numScaleNotes + 1);
|
||||||
|
if (subsetSize > 12) subsetSize = 12;
|
||||||
|
int subset[12];
|
||||||
|
|
||||||
for (int i = 0; i < numSteps; i++) {
|
// Create indices to shuffle
|
||||||
sequence[i].note = 12 * octave + scaleNotes[currentNoteIndex];
|
int indices[12];
|
||||||
sequence[i].accent = (i % 4 == 0); // Accent on beat
|
for(int i=0; i<numScaleNotes; i++) indices[i] = i;
|
||||||
sequence[i].tie = false;
|
|
||||||
|
|
||||||
currentNoteIndex++;
|
// Shuffle indices
|
||||||
if (currentNoteIndex >= numScaleNotes) {
|
for(int i=0; i<numScaleNotes; i++) {
|
||||||
currentNoteIndex = 0;
|
int r = random(numScaleNotes);
|
||||||
octave++;
|
int temp = indices[i];
|
||||||
if (octave > 5) octave = 3;
|
indices[i] = indices[r];
|
||||||
|
indices[r] = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick subset
|
||||||
|
for(int i=0; i<subsetSize; i++) {
|
||||||
|
subset[i] = scaleNotes[indices[i]];
|
||||||
|
}
|
||||||
|
sortArray(subset, subsetSize);
|
||||||
|
|
||||||
|
// 2. Pick Arp Length & Pattern
|
||||||
|
int arpLength = random(2, 17); // 2 to 16
|
||||||
|
Step arpPattern[16];
|
||||||
|
|
||||||
|
int mode = random(3); // 0: Up, 1: Down, 2: Up/Down
|
||||||
|
int numOctaves = 3;
|
||||||
|
int baseOctave = 3;
|
||||||
|
int totalNotes = subsetSize * numOctaves;
|
||||||
|
|
||||||
|
int currentIndex = 0;
|
||||||
|
int direction = 1;
|
||||||
|
|
||||||
|
if (mode == 1) { // Down
|
||||||
|
currentIndex = totalNotes - 1;
|
||||||
|
direction = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < arpLength; i++) {
|
||||||
|
// Chance of rest (15%)
|
||||||
|
if (random(100) < 15) {
|
||||||
|
arpPattern[i].note = -1;
|
||||||
|
arpPattern[i].accent = false;
|
||||||
|
arpPattern[i].tie = false;
|
||||||
|
} else {
|
||||||
|
int octaveOffset = currentIndex / subsetSize;
|
||||||
|
int noteIndex = currentIndex % subsetSize;
|
||||||
|
int octave = baseOctave + octaveOffset;
|
||||||
|
|
||||||
|
arpPattern[i].note = 12 * octave + subset[noteIndex];
|
||||||
|
arpPattern[i].accent = (i % 4 == 0); // Accent on beat
|
||||||
|
arpPattern[i].tie = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == 0) { // Up
|
||||||
|
currentIndex++;
|
||||||
|
if (currentIndex >= totalNotes) currentIndex = 0;
|
||||||
|
} else if (mode == 1) { // Down
|
||||||
|
currentIndex--;
|
||||||
|
if (currentIndex < 0) currentIndex = totalNotes - 1;
|
||||||
|
} else { // Up/Down
|
||||||
|
currentIndex += direction;
|
||||||
|
if (currentIndex >= totalNotes) {
|
||||||
|
currentIndex = max(0, totalNotes - 2);
|
||||||
|
direction = -1;
|
||||||
|
} else if (currentIndex < 0) {
|
||||||
|
currentIndex = min(totalNotes - 1, 1);
|
||||||
|
direction = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Fill Sequence
|
||||||
|
for (int i = 0; i < numSteps; i++) {
|
||||||
|
sequence[i] = arpPattern[i % arpLength];
|
||||||
|
}
|
||||||
randomSeed(micros());
|
randomSeed(micros());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void generateScale(int* scaleNotes, int& numScaleNotes) override {
|
||||||
|
numScaleNotes = random(3, 13); // 3 to 12 notes
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
scaleNotes[i] = i; // Fill with all notes
|
||||||
|
}
|
||||||
|
// Shuffle
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
int j = random(12);
|
||||||
|
int temp = scaleNotes[i];
|
||||||
|
scaleNotes[i] = scaleNotes[j];
|
||||||
|
scaleNotes[j] = temp;
|
||||||
|
}
|
||||||
|
sortArray(scaleNotes, numScaleNotes);
|
||||||
|
}
|
||||||
|
|
||||||
void mutate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes) override {
|
void mutate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes) override {
|
||||||
// Swap two notes
|
// Swap two notes
|
||||||
int s1 = random(numSteps);
|
int s1 = random(numSteps);
|
||||||
|
|||||||
@ -19,6 +19,21 @@ public:
|
|||||||
randomSeed(micros());
|
randomSeed(micros());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void generateScale(int* scaleNotes, int& numScaleNotes) override {
|
||||||
|
numScaleNotes = random(3, 13); // 3 to 12 notes
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
scaleNotes[i] = i; // Fill with all notes
|
||||||
|
}
|
||||||
|
// Shuffle
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
int j = random(12);
|
||||||
|
int temp = scaleNotes[i];
|
||||||
|
scaleNotes[i] = scaleNotes[j];
|
||||||
|
scaleNotes[j] = temp;
|
||||||
|
}
|
||||||
|
sortArray(scaleNotes, numScaleNotes);
|
||||||
|
}
|
||||||
|
|
||||||
void mutate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes) override {
|
void mutate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes) override {
|
||||||
// Mutate 1 or 2 steps
|
// Mutate 1 or 2 steps
|
||||||
int count = random(1, 3);
|
int count = random(1, 3);
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
class MelodyStrategy {
|
class MelodyStrategy {
|
||||||
public:
|
public:
|
||||||
virtual void generate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes, int seed) = 0;
|
virtual void generate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes, int seed) = 0;
|
||||||
|
virtual void generateScale(int* scaleNotes, int& numScaleNotes) = 0;
|
||||||
virtual void mutate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes) = 0;
|
virtual void mutate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes) = 0;
|
||||||
virtual const char* getName() = 0;
|
virtual const char* getName() = 0;
|
||||||
virtual ~MelodyStrategy() {}
|
virtual ~MelodyStrategy() {}
|
||||||
|
|||||||
@ -34,7 +34,7 @@ UIState currentState = UI_MENU_MAIN;
|
|||||||
|
|
||||||
const char* mainMenu[] = { "Tracker", "Randomize", "Setup" };
|
const char* mainMenu[] = { "Tracker", "Randomize", "Setup" };
|
||||||
const int mainMenuCount = sizeof(mainMenu) / sizeof(char*);
|
const int mainMenuCount = sizeof(mainMenu) / sizeof(char*);
|
||||||
const char* randomizeMenu[] = { "Back", "Scale", "Melody", "Flavour", "Tempo", "Mutation", "Song Mode", "Theme 1", "Theme 2", "Theme 3", "Theme 4", "Theme 5", "Theme 6", "Theme 7" };
|
const char* randomizeMenu[] = { "Back", "Melody", "Flavour", "Scale", "Tempo", "Mutation", "Song Mode", "Theme 1", "Theme 2", "Theme 3", "Theme 4", "Theme 5", "Theme 6", "Theme 7" };
|
||||||
const int THEME_1_INDEX = 7;
|
const int THEME_1_INDEX = 7;
|
||||||
const int randomizeMenuCount = sizeof(randomizeMenu) / sizeof(char*);
|
const int randomizeMenuCount = sizeof(randomizeMenu) / sizeof(char*);
|
||||||
const char* setupMenu[] = { "Back", "Channel", "Save", "Load" };
|
const char* setupMenu[] = { "Back", "Channel", "Save", "Load" };
|
||||||
@ -100,18 +100,6 @@ void readEncoder() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sortArray(int arr[], int size) {
|
|
||||||
for (int i = 0; i < size - 1; i++) {
|
|
||||||
for (int j = 0; j < size - i - 1; j++) {
|
|
||||||
if (arr[j] > arr[j + 1]) {
|
|
||||||
int temp = arr[j];
|
|
||||||
arr[j] = arr[j + 1];
|
|
||||||
arr[j + 1] = temp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void saveSequence(bool quiet = false) {
|
void saveSequence(bool quiet = false) {
|
||||||
int addr = 0;
|
int addr = 0;
|
||||||
EEPROM.put(addr, EEPROM_MAGIC); addr += sizeof(EEPROM_MAGIC);
|
EEPROM.put(addr, EEPROM_MAGIC); addr += sizeof(EEPROM_MAGIC);
|
||||||
@ -194,18 +182,7 @@ void setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void generateRandomScale() {
|
void generateRandomScale() {
|
||||||
numScaleNotes = random(3, 13); // 3 to 12 notes
|
strategies[currentStrategyIndex]->generateScale(scaleNotes, numScaleNotes);
|
||||||
for (int i = 0; i < 12; i++) {
|
|
||||||
scaleNotes[i] = i; // Fill with all notes
|
|
||||||
}
|
|
||||||
// Shuffle
|
|
||||||
for (int i = 0; i < 12; i++) {
|
|
||||||
int j = random(12);
|
|
||||||
int temp = scaleNotes[i];
|
|
||||||
scaleNotes[i] = scaleNotes[j];
|
|
||||||
scaleNotes[j] = temp;
|
|
||||||
}
|
|
||||||
sortArray(scaleNotes, numScaleNotes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void generateSequenceData(int themeType, Step* target) {
|
void generateSequenceData(int themeType, Step* target) {
|
||||||
@ -333,17 +310,6 @@ void handleInput() {
|
|||||||
case UI_MENU_RANDOMIZE:
|
case UI_MENU_RANDOMIZE:
|
||||||
if (menuSelection == 0) { currentState = UI_MENU_MAIN; menuSelection = 1; }
|
if (menuSelection == 0) { currentState = UI_MENU_MAIN; menuSelection = 1; }
|
||||||
if (menuSelection == 1) {
|
if (menuSelection == 1) {
|
||||||
generateRandomScale(); // Scale
|
|
||||||
if (isPlaying) {
|
|
||||||
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
|
|
||||||
midi.lock();
|
|
||||||
generateSequenceData(theme, nextSequence);
|
|
||||||
sequenceChangeScheduled = true;
|
|
||||||
midi.unlock();
|
|
||||||
}
|
|
||||||
saveSequence(true);
|
|
||||||
}
|
|
||||||
if (menuSelection == 2) {
|
|
||||||
melodySeed = random(10000); // Melody
|
melodySeed = random(10000); // Melody
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
|
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
|
||||||
@ -354,7 +320,18 @@ void handleInput() {
|
|||||||
}
|
}
|
||||||
saveSequence(true);
|
saveSequence(true);
|
||||||
}
|
}
|
||||||
if (menuSelection == 3) currentState = UI_EDIT_FLAVOUR;
|
if (menuSelection == 2) currentState = UI_EDIT_FLAVOUR; // Flavour
|
||||||
|
if (menuSelection == 3) { // Scale
|
||||||
|
generateRandomScale();
|
||||||
|
if (isPlaying) {
|
||||||
|
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
|
||||||
|
midi.lock();
|
||||||
|
generateSequenceData(theme, nextSequence);
|
||||||
|
sequenceChangeScheduled = true;
|
||||||
|
midi.unlock();
|
||||||
|
}
|
||||||
|
saveSequence(true);
|
||||||
|
}
|
||||||
if (menuSelection == 4) currentState = UI_EDIT_TEMPO;
|
if (menuSelection == 4) currentState = UI_EDIT_TEMPO;
|
||||||
if (menuSelection == 5) mutationEnabled = !mutationEnabled;
|
if (menuSelection == 5) mutationEnabled = !mutationEnabled;
|
||||||
if (menuSelection == 6) {
|
if (menuSelection == 6) {
|
||||||
@ -398,6 +375,13 @@ void handleInput() {
|
|||||||
break;
|
break;
|
||||||
case UI_EDIT_FLAVOUR:
|
case UI_EDIT_FLAVOUR:
|
||||||
currentState = UI_MENU_RANDOMIZE;
|
currentState = UI_MENU_RANDOMIZE;
|
||||||
|
if (isPlaying) {
|
||||||
|
queuedTheme = currentThemeIndex;
|
||||||
|
midi.lock();
|
||||||
|
generateSequenceData(currentThemeIndex, nextSequence);
|
||||||
|
sequenceChangeScheduled = true;
|
||||||
|
midi.unlock();
|
||||||
|
}
|
||||||
saveSequence(true);
|
saveSequence(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,4 +19,16 @@ enum UIState {
|
|||||||
UI_EDIT_FLAVOUR
|
UI_EDIT_FLAVOUR
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline void sortArray(int arr[], int size) {
|
||||||
|
for (int i = 0; i < size - 1; i++) {
|
||||||
|
for (int j = 0; j < size - i - 1; j++) {
|
||||||
|
if (arr[j] > arr[j + 1]) {
|
||||||
|
int temp = arr[j];
|
||||||
|
arr[j] = arr[j + 1];
|
||||||
|
arr[j + 1] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@ -146,7 +146,11 @@ void UIManager::drawMenu(const char* title, const char* items[], int count, int
|
|||||||
display.print(F(" *"));
|
display.print(F(" *"));
|
||||||
}
|
}
|
||||||
if (currentState == UI_MENU_RANDOMIZE) {
|
if (currentState == UI_MENU_RANDOMIZE) {
|
||||||
if (i == 1) { // Scale
|
if (i == 1) { // Melody
|
||||||
|
display.print(F(": ")); display.print(melodySeed);
|
||||||
|
} else if (i == 2) { // Flavour
|
||||||
|
display.print(F(": ")); display.print(flavourName);
|
||||||
|
} else if (i == 3) { // Scale
|
||||||
display.print(F(": "));
|
display.print(F(": "));
|
||||||
if (numScaleNotes > 0) {
|
if (numScaleNotes > 0) {
|
||||||
const char* noteNames[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
|
const char* noteNames[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
|
||||||
@ -155,9 +159,7 @@ void UIManager::drawMenu(const char* title, const char* items[], int count, int
|
|||||||
if (j < min(numScaleNotes, 6) - 1) display.print(F(" "));
|
if (j < min(numScaleNotes, 6) - 1) display.print(F(" "));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (i == 2) { display.print(F(": ")); display.print(melodySeed); }
|
} else if (i == 4) { display.print(F(": ")); display.print(tempo); }
|
||||||
else if (i == 3) { display.print(F(": ")); display.print(flavourName); }
|
|
||||||
else if (i == 4) { display.print(F(": ")); display.print(tempo); }
|
|
||||||
else if (i == 5) { display.print(F(": ")); display.print(mutationEnabled ? F("ON") : F("OFF")); }
|
else if (i == 5) { display.print(F(": ")); display.print(mutationEnabled ? F("ON") : F("OFF")); }
|
||||||
else if (i == 6) { display.print(F(": ")); display.print(songModeEnabled ? F("ON") : F("OFF")); }
|
else if (i == 6) { display.print(F(": ")); display.print(songModeEnabled ? F("ON") : F("OFF")); }
|
||||||
}
|
}
|
||||||
@ -245,23 +247,13 @@ void UIManager::updateLeds(const Step* sequence, int navSelection, int playbackS
|
|||||||
else { c[1] = color; if (sequence[s].accent) { c[0] = dimColor; c[2] = dimColor; } }
|
else { c[1] = color; if (sequence[s].accent) { c[0] = dimColor; c[2] = dimColor; } }
|
||||||
}
|
}
|
||||||
int stepNavIndex = navSelection - 1;
|
int stepNavIndex = navSelection - 1;
|
||||||
if (isPlaying) {
|
|
||||||
c[3] = pixels.Color(0, 50, 0);
|
|
||||||
if (songModeEnabled && s >= NUM_STEPS/2 && x >= (8 - min(songRepeatsRemaining, 8))) {
|
|
||||||
c[3] = (songRepeatsRemaining == 1 && x == 7 && (millis()/250)%2) ? pixels.Color(0, 250, 0) : pixels.Color(80, 100, 0);
|
|
||||||
}
|
|
||||||
} else if (currentState == UI_TRACKER && s == stepNavIndex) {
|
|
||||||
c[3] = isEditing ? pixels.Color(50, 0, 0) : pixels.Color(40, 40, 40);
|
|
||||||
} else if (c[3] == 0 && (isPlaying && s == playbackStep || (!isPlaying && currentState == UI_TRACKER && s == stepNavIndex))) {
|
|
||||||
// Cursor logic handled above, this block seems redundant or malformed in original logic translation, simplifying:
|
|
||||||
}
|
|
||||||
// Apply cursor color logic from original code more strictly
|
// Apply cursor color logic from original code more strictly
|
||||||
uint32_t cursorColor = 0;
|
uint32_t cursorColor = 0;
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
cursorColor = pixels.Color(0, 50, 0);
|
cursorColor = pixels.Color(0, 50, 0);
|
||||||
if (songModeEnabled && s >= 56) {
|
if (songModeEnabled && s >= 8) {
|
||||||
int repeats = min(songRepeatsRemaining, 8);
|
int repeats = min(songRepeatsRemaining, 8);
|
||||||
if (x >= (8 - repeats)) cursorColor = (songRepeatsRemaining == 1 && x == 7 && (millis()/250)%2) ? pixels.Color(255, 200, 0) : pixels.Color(154, 205, 50);
|
if (x >= (8 - repeats)) cursorColor = (songRepeatsRemaining == 1 && x == 7 && (millis()/250)%2) ? pixels.Color(255, 200, 0) : pixels.Color(100, 220, 40);
|
||||||
}
|
}
|
||||||
} else if (currentState == UI_TRACKER) {
|
} else if (currentState == UI_TRACKER) {
|
||||||
cursorColor = isEditing ? pixels.Color(50, 0, 0) : pixels.Color(40, 40, 40);
|
cursorColor = isEditing ? pixels.Color(50, 0, 0) : pixels.Color(40, 40, 40);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user