Song progression
This commit is contained in:
parent
bbe6c4ff2f
commit
99add4e2ac
@ -26,9 +26,11 @@ void Persistence::saveSequence(bool quiet) {
|
|||||||
for(int i=0; i<NUM_TRACKS; i++) steps[i] = numSteps[i];
|
for(int i=0; i<NUM_TRACKS; i++) steps[i] = numSteps[i];
|
||||||
EEPROM.put(addr, steps); addr += sizeof(steps);
|
EEPROM.put(addr, steps); addr += sizeof(steps);
|
||||||
|
|
||||||
EEPROM.put(addr, currentRoot); addr += sizeof(currentRoot);
|
EEPROM.put(addr, globalRoot); addr += sizeof(globalRoot);
|
||||||
|
EEPROM.put(addr, currentProgression); addr += sizeof(currentProgression);
|
||||||
|
EEPROM.put(addr, progressionOrder); addr += sizeof(progressionOrder);
|
||||||
|
|
||||||
EEPROM.put(addr, currentScaleType); addr += sizeof(currentScaleType);
|
EEPROM.put(addr, currentScaleType); addr += sizeof(currentScaleType);
|
||||||
EEPROM.put(addr, enabledScaleTypes); addr += sizeof(enabledScaleTypes);
|
|
||||||
EEPROM.put(addr, numScaleNotes); addr += sizeof(numScaleNotes);
|
EEPROM.put(addr, numScaleNotes); addr += sizeof(numScaleNotes);
|
||||||
for (int i = 0; i<12; i++) {
|
for (int i = 0; i<12; i++) {
|
||||||
EEPROM.put(addr, scaleNotes[i]); addr += sizeof(int);
|
EEPROM.put(addr, scaleNotes[i]); addr += sizeof(int);
|
||||||
@ -86,9 +88,12 @@ bool Persistence::loadSequence() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EEPROM.get(addr, currentRoot); addr += sizeof(currentRoot);
|
EEPROM.get(addr, globalRoot); addr += sizeof(globalRoot);
|
||||||
|
currentRoot = globalRoot; // Sync
|
||||||
|
EEPROM.get(addr, currentProgression); addr += sizeof(currentProgression);
|
||||||
|
EEPROM.get(addr, progressionOrder); addr += sizeof(progressionOrder);
|
||||||
|
|
||||||
EEPROM.get(addr, currentScaleType); addr += sizeof(currentScaleType);
|
EEPROM.get(addr, currentScaleType); addr += sizeof(currentScaleType);
|
||||||
EEPROM.get(addr, enabledScaleTypes); addr += sizeof(enabledScaleTypes);
|
|
||||||
EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes);
|
EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes);
|
||||||
if (numScaleNotes < 0 || numScaleNotes > 12) numScaleNotes = 0;
|
if (numScaleNotes < 0 || numScaleNotes > 12) numScaleNotes = 0;
|
||||||
for (int i = 0; i<12; i++) {
|
for (int i = 0; i<12; i++) {
|
||||||
@ -109,7 +114,6 @@ void Persistence::savePatch(int bank, int slot) {
|
|||||||
midi.lock();
|
midi.lock();
|
||||||
EEPROM.put(addr, currentRoot); addr += sizeof(currentRoot);
|
EEPROM.put(addr, currentRoot); addr += sizeof(currentRoot);
|
||||||
EEPROM.put(addr, currentScaleType); addr += sizeof(currentScaleType);
|
EEPROM.put(addr, currentScaleType); addr += sizeof(currentScaleType);
|
||||||
EEPROM.put(addr, enabledScaleTypes); addr += sizeof(enabledScaleTypes);
|
|
||||||
EEPROM.put(addr, numScaleNotes); addr += sizeof(numScaleNotes);
|
EEPROM.put(addr, numScaleNotes); addr += sizeof(numScaleNotes);
|
||||||
for (int i = 0; i < 12; i++) {
|
for (int i = 0; i < 12; i++) {
|
||||||
EEPROM.put(addr, scaleNotes[i]); addr += sizeof(int);
|
EEPROM.put(addr, scaleNotes[i]); addr += sizeof(int);
|
||||||
@ -137,7 +141,6 @@ void Persistence::loadPatch(int bank, int slot, Step (*scratchBuffer)[NUM_STEPS]
|
|||||||
midi.lock();
|
midi.lock();
|
||||||
EEPROM.get(addr, currentRoot); addr += sizeof(currentRoot);
|
EEPROM.get(addr, currentRoot); addr += sizeof(currentRoot);
|
||||||
EEPROM.get(addr, currentScaleType); addr += sizeof(currentScaleType);
|
EEPROM.get(addr, currentScaleType); addr += sizeof(currentScaleType);
|
||||||
EEPROM.get(addr, enabledScaleTypes); addr += sizeof(enabledScaleTypes);
|
|
||||||
EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes);
|
EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes);
|
||||||
if (numScaleNotes < 0 || numScaleNotes > 12) numScaleNotes = 0;
|
if (numScaleNotes < 0 || numScaleNotes > 12) numScaleNotes = 0;
|
||||||
for (int i = 0; i < 12; i++) {
|
for (int i = 0; i < 12; i++) {
|
||||||
|
|||||||
@ -97,6 +97,7 @@ static void handlePlayback() {
|
|||||||
if (sequenceChangeScheduled) {
|
if (sequenceChangeScheduled) {
|
||||||
memcpy(local_sequence, local_nextSequence, sizeof(local_sequence));
|
memcpy(local_sequence, local_nextSequence, sizeof(local_sequence));
|
||||||
midi.lock();
|
midi.lock();
|
||||||
|
visualProgressionStep = queuedProgressionStep;
|
||||||
memcpy(sequence, local_sequence, sizeof(local_sequence));
|
memcpy(sequence, local_sequence, sizeof(local_sequence));
|
||||||
sequenceChangeScheduled = false;
|
sequenceChangeScheduled = false;
|
||||||
midi.unlock();
|
midi.unlock();
|
||||||
|
|||||||
@ -80,15 +80,6 @@ void SequenceGenerator::pickRandomScaleType(int themeType) {
|
|||||||
for(int i=0; i<NUM_TRACKS; i++) seed += melodySeeds[i];
|
for(int i=0; i<NUM_TRACKS; i++) seed += melodySeeds[i];
|
||||||
randomSeed(seed);
|
randomSeed(seed);
|
||||||
|
|
||||||
int candidates[10];
|
currentScaleType = random(10);
|
||||||
int count = 0;
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
if (enabledScaleTypes & (1 << i)) {
|
|
||||||
candidates[count++] = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (count > 0) {
|
|
||||||
currentScaleType = candidates[random(count)];
|
|
||||||
SequenceGenerator::updateScale();
|
SequenceGenerator::updateScale();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@ -21,22 +21,34 @@ bool protectedMode = false;
|
|||||||
|
|
||||||
volatile int trackIntensity[NUM_TRACKS] = {10, 10, 10, 10};
|
volatile int trackIntensity[NUM_TRACKS] = {10, 10, 10, 10};
|
||||||
// Menus
|
// Menus
|
||||||
|
|
||||||
|
#define PROG_STEP_ITEMS(n) \
|
||||||
|
{ "[" #n "] Mod", (MenuItemID)(MENU_ID_PROG_STEP_START + (n-1)*3), false, false, 3 }, \
|
||||||
|
{ "[" #n "] Type", (MenuItemID)(MENU_ID_PROG_STEP_START + (n-1)*3 + 1), false, false, 3 }, \
|
||||||
|
{ "[" #n "] Reps", (MenuItemID)(MENU_ID_PROG_STEP_START + (n-1)*3 + 2), false, false, 3 }
|
||||||
|
|
||||||
MenuItem menuItems[] = {
|
MenuItem menuItems[] = {
|
||||||
{ "Main", MENU_ID_GROUP_MAIN, true, true, 0 },
|
{ "Main", MENU_ID_GROUP_MAIN, true, true, 0 },
|
||||||
{ "Playback", MENU_ID_PLAYBACK, false, false, 1 },
|
{ "Playback", MENU_ID_PLAYBACK, false, false, 1 },
|
||||||
{ "Melody", MENU_ID_MELODY, false, false, 1 },
|
{ "Melody", MENU_ID_MELODY, false, false, 1 },
|
||||||
{ "Root", MENU_ID_ROOT, false, false, 1 },
|
{ "Root", MENU_ID_ROOT, false, false, 1 },
|
||||||
{ "Scale Type", MENU_ID_SCALE_TYPE, true, false, 1 },
|
{ "Progression", MENU_ID_GROUP_PROGRESSION, true, false, 1 },
|
||||||
{ "Chromatic", MENU_ID_SCALE_TYPE_CHROMATIC, false, false, 2 },
|
{ "Order", MENU_ID_PROG_ORDER, false, false, 2 },
|
||||||
{ "Major", MENU_ID_SCALE_TYPE_MAJOR, false, false, 2 },
|
{ "Apply Template", MENU_ID_PROG_APPLY_TEMPLATE, false, false, 2 },
|
||||||
{ "Minor", MENU_ID_SCALE_TYPE_MINOR, false, false, 2 },
|
{ "Sequence", MENU_ID_GROUP_PROG_SEQUENCE, true, false, 2 },
|
||||||
{ "Harm. Min", MENU_ID_SCALE_TYPE_HARM_MIN, false, false, 2 },
|
{ "Length", MENU_ID_PROG_LENGTH, false, false, 3 },
|
||||||
{ "Pent. Maj", MENU_ID_SCALE_TYPE_PENT_MAJ, false, false, 2 },
|
PROG_STEP_ITEMS(1),
|
||||||
{ "Pent. Min", MENU_ID_SCALE_TYPE_PENT_MIN, false, false, 2 },
|
PROG_STEP_ITEMS(2),
|
||||||
{ "Chord Maj", MENU_ID_SCALE_TYPE_CHORD_MAJ, false, false, 2 },
|
PROG_STEP_ITEMS(3),
|
||||||
{ "Chord Min", MENU_ID_SCALE_TYPE_CHORD_MIN, false, false, 2 },
|
PROG_STEP_ITEMS(4),
|
||||||
{ "Chord Dim", MENU_ID_SCALE_TYPE_CHORD_DIM, false, false, 2 },
|
PROG_STEP_ITEMS(5),
|
||||||
{ "Chord 7", MENU_ID_SCALE_TYPE_CHORD_7, false, false, 2 },
|
PROG_STEP_ITEMS(6),
|
||||||
|
PROG_STEP_ITEMS(7),
|
||||||
|
PROG_STEP_ITEMS(8),
|
||||||
|
PROG_STEP_ITEMS(9),
|
||||||
|
PROG_STEP_ITEMS(10),
|
||||||
|
PROG_STEP_ITEMS(11),
|
||||||
|
PROG_STEP_ITEMS(12),
|
||||||
{ "Tempo", MENU_ID_TEMPO, false, false, 1 },
|
{ "Tempo", MENU_ID_TEMPO, false, false, 1 },
|
||||||
{ "Song Mode", MENU_ID_SONG_MODE, false, false, 1 },
|
{ "Song Mode", MENU_ID_SONG_MODE, false, false, 1 },
|
||||||
{ "Track", MENU_ID_GROUP_TRACK, true, true, 0 },
|
{ "Track", MENU_ID_GROUP_TRACK, true, true, 0 },
|
||||||
@ -106,6 +118,13 @@ bool isItemVisible(int index) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide progression steps beyond current length
|
||||||
|
MenuItemID id = menuItems[index].id;
|
||||||
|
if (id >= MENU_ID_PROG_STEP_START && id < MENU_ID_PROG_STEP_END) {
|
||||||
|
int stepIdx = (id - MENU_ID_PROG_STEP_START) / 3;
|
||||||
|
if (stepIdx >= currentProgression.length) return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (menuItems[index].indentLevel == 0) return true;
|
if (menuItems[index].indentLevel == 0) return true;
|
||||||
for (int i = index - 1; i >= 0; i--) {
|
for (int i = index - 1; i >= 0; i--) {
|
||||||
if (menuItems[i].indentLevel < menuItems[index].indentLevel) {
|
if (menuItems[i].indentLevel < menuItems[index].indentLevel) {
|
||||||
@ -126,10 +145,27 @@ int numScaleNotes = 0;
|
|||||||
int melodySeeds[NUM_TRACKS];
|
int melodySeeds[NUM_TRACKS];
|
||||||
volatile int queuedTheme = -1;
|
volatile int queuedTheme = -1;
|
||||||
volatile int currentThemeIndex = 1;
|
volatile int currentThemeIndex = 1;
|
||||||
|
int globalRoot = 0; // The "Key" of the song
|
||||||
int currentRoot = 0; // C
|
int currentRoot = 0; // C
|
||||||
int currentScaleType = 1; // Major
|
int currentScaleType = 1; // Major
|
||||||
int enabledScaleTypes = 2; // Major (1<<1)
|
extern const uint32_t EEPROM_MAGIC = 0x42424253;
|
||||||
extern const uint32_t EEPROM_MAGIC = 0x42424250;
|
|
||||||
|
const ChordProgression progressions[] = {
|
||||||
|
{ "Off", 0, { {0,0,1} } }, // Dummy
|
||||||
|
{ "I-IV-V-I", 4, { {0, 1, 1}, {5, 1, 1}, {7, 1, 1}, {0, 1, 1} } }, // Major
|
||||||
|
{ "ii-V-I", 3, { {2, 2, 1}, {7, 1, 1}, {0, 1, 1} } }, // min, Maj, Maj
|
||||||
|
{ "I-vi-IV-V", 4, { {0, 1, 1}, {9, 2, 1}, {5, 1, 1}, {7, 1, 1} } }, // 50s progression
|
||||||
|
{ "i-bVI-bIII-bVII", 4, { {0, 2, 1}, {8, 1, 1}, {3, 1, 1}, {10, 1, 1} } }, // Axis of Awesome (Am, F, C, G relative to Am)
|
||||||
|
{ "Blues 12", 12, { {0,9,1}, {0,9,1}, {0,9,1}, {0,9,1}, {5,9,1}, {5,9,1}, {0,9,1}, {0,9,1}, {7,9,1}, {5,9,1}, {0,9,1}, {7,9,1} } } // Dom7 chords
|
||||||
|
};
|
||||||
|
ChordProgression currentProgression = progressions[0]; // Start with Off/Empty
|
||||||
|
ProgressionOrder progressionOrder = PROG_ORDER_SEQUENCE;
|
||||||
|
int progressionStep = 0;
|
||||||
|
volatile int queuedProgressionStep = 0;
|
||||||
|
volatile int visualProgressionStep = 0;
|
||||||
|
const int numProgressions = 6;
|
||||||
|
int templateSelectionIndex = 0;
|
||||||
|
int stepEditIndex = 0;
|
||||||
|
|
||||||
MelodyStrategy* strategies[] = {
|
MelodyStrategy* strategies[] = {
|
||||||
new LuckyStrategy(), new ArpStrategy(), new EuclideanStrategy(),
|
new LuckyStrategy(), new ArpStrategy(), new EuclideanStrategy(),
|
||||||
|
|||||||
@ -19,17 +19,15 @@ enum MenuItemID {
|
|||||||
MENU_ID_PLAYBACK,
|
MENU_ID_PLAYBACK,
|
||||||
MENU_ID_MELODY,
|
MENU_ID_MELODY,
|
||||||
MENU_ID_ROOT,
|
MENU_ID_ROOT,
|
||||||
MENU_ID_SCALE_TYPE,
|
|
||||||
MENU_ID_SCALE_TYPE_CHROMATIC,
|
MENU_ID_GROUP_PROGRESSION,
|
||||||
MENU_ID_SCALE_TYPE_MAJOR,
|
MENU_ID_PROG_ORDER,
|
||||||
MENU_ID_SCALE_TYPE_MINOR,
|
MENU_ID_PROG_APPLY_TEMPLATE,
|
||||||
MENU_ID_SCALE_TYPE_HARM_MIN,
|
MENU_ID_GROUP_PROG_SEQUENCE,
|
||||||
MENU_ID_SCALE_TYPE_PENT_MAJ,
|
MENU_ID_PROG_LENGTH,
|
||||||
MENU_ID_SCALE_TYPE_PENT_MIN,
|
MENU_ID_PROG_STEP_START,
|
||||||
MENU_ID_SCALE_TYPE_CHORD_MAJ,
|
MENU_ID_PROG_STEP_END = MENU_ID_PROG_STEP_START + (MAX_PROG_STEPS * 3),
|
||||||
MENU_ID_SCALE_TYPE_CHORD_MIN,
|
|
||||||
MENU_ID_SCALE_TYPE_CHORD_DIM,
|
|
||||||
MENU_ID_SCALE_TYPE_CHORD_7,
|
|
||||||
MENU_ID_TEMPO,
|
MENU_ID_TEMPO,
|
||||||
MENU_ID_SONG_MODE,
|
MENU_ID_SONG_MODE,
|
||||||
|
|
||||||
@ -84,10 +82,19 @@ extern int numScaleNotes;
|
|||||||
extern int melodySeeds[NUM_TRACKS];
|
extern int melodySeeds[NUM_TRACKS];
|
||||||
extern volatile int queuedTheme;
|
extern volatile int queuedTheme;
|
||||||
extern volatile int currentThemeIndex;
|
extern volatile int currentThemeIndex;
|
||||||
|
extern int globalRoot;
|
||||||
extern int currentRoot;
|
extern int currentRoot;
|
||||||
extern volatile int trackIntensity[NUM_TRACKS];
|
extern volatile int trackIntensity[NUM_TRACKS];
|
||||||
extern int currentScaleType;
|
extern int currentScaleType;
|
||||||
extern int enabledScaleTypes;
|
extern ChordProgression currentProgression;
|
||||||
|
extern ProgressionOrder progressionOrder;
|
||||||
|
extern int progressionStep;
|
||||||
|
extern volatile int queuedProgressionStep;
|
||||||
|
extern volatile int visualProgressionStep;
|
||||||
|
extern const ChordProgression progressions[];
|
||||||
|
extern const int numProgressions;
|
||||||
|
extern int templateSelectionIndex;
|
||||||
|
extern int stepEditIndex;
|
||||||
extern const uint32_t EEPROM_MAGIC;
|
extern const uint32_t EEPROM_MAGIC;
|
||||||
|
|
||||||
extern MelodyStrategy* strategies[];
|
extern MelodyStrategy* strategies[];
|
||||||
|
|||||||
@ -10,6 +10,25 @@ struct Step {
|
|||||||
bool tie;
|
bool tie;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ChordStep {
|
||||||
|
int8_t rootOffset; // Semitones relative to key
|
||||||
|
int8_t scaleType; // Scale type index
|
||||||
|
uint8_t repeats; // Number of repetitions
|
||||||
|
};
|
||||||
|
|
||||||
|
#define MAX_PROG_STEPS 12
|
||||||
|
|
||||||
|
struct ChordProgression {
|
||||||
|
const char* name;
|
||||||
|
int length;
|
||||||
|
ChordStep steps[MAX_PROG_STEPS]; // Max 12 steps
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ProgressionOrder {
|
||||||
|
PROG_ORDER_SEQUENCE,
|
||||||
|
PROG_ORDER_RANDOM
|
||||||
|
};
|
||||||
|
|
||||||
enum PlayMode {
|
enum PlayMode {
|
||||||
MODE_MONO,
|
MODE_MONO,
|
||||||
MODE_POLY
|
MODE_POLY
|
||||||
@ -24,7 +43,13 @@ enum UIState {
|
|||||||
UI_EDIT_INTENSITY,
|
UI_EDIT_INTENSITY,
|
||||||
UI_SETUP_PLAYMODE_EDIT,
|
UI_SETUP_PLAYMODE_EDIT,
|
||||||
UI_RANDOMIZE_TRACK_EDIT,
|
UI_RANDOMIZE_TRACK_EDIT,
|
||||||
UI_EDIT_ROOT
|
UI_EDIT_ROOT,
|
||||||
|
UI_EDIT_PROG_ORDER,
|
||||||
|
UI_EDIT_PROG_TEMPLATE,
|
||||||
|
UI_EDIT_PROG_LENGTH,
|
||||||
|
UI_EDIT_PROG_STEP_ROOT,
|
||||||
|
UI_EDIT_PROG_STEP_TYPE,
|
||||||
|
UI_EDIT_PROG_STEP_REPS
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void sortArray(int arr[], int size) {
|
inline void sortArray(int arr[], int size) {
|
||||||
|
|||||||
@ -72,9 +72,34 @@ void UIManager::draw(UIState currentState, int menuSelection,
|
|||||||
case UI_EDIT_ROOT:
|
case UI_EDIT_ROOT:
|
||||||
{
|
{
|
||||||
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"};
|
||||||
drawEditScreen("SET ROOT NOTE", "", noteNames[currentRoot % 12]);
|
drawEditScreen("SET KEY", "", noteNames[globalRoot % 12]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case UI_EDIT_PROG_ORDER:
|
||||||
|
drawEditScreen("PROG ORDER", "", (progressionOrder == PROG_ORDER_SEQUENCE) ? "Sequence" : "Random");
|
||||||
|
break;
|
||||||
|
case UI_EDIT_PROG_TEMPLATE:
|
||||||
|
drawEditScreen("APPLY TEMPLATE", "", progressions[templateSelectionIndex].name);
|
||||||
|
break;
|
||||||
|
case UI_EDIT_PROG_LENGTH:
|
||||||
|
drawEditScreen("PROG LENGTH", "Steps: ", currentProgression.length);
|
||||||
|
break;
|
||||||
|
case UI_EDIT_PROG_STEP_ROOT:
|
||||||
|
drawEditScreen("STEP MODULATION", "Semi: ", currentProgression.steps[stepEditIndex/3].rootOffset);
|
||||||
|
break;
|
||||||
|
case UI_EDIT_PROG_STEP_TYPE:
|
||||||
|
{
|
||||||
|
const char* typeNames[] = {"Chrom", "Major", "Minor", "H.Min", "P.Maj", "P.Min", "ChdMaj", "ChdMin", "ChdDim", "Chd7"};
|
||||||
|
int type = currentProgression.steps[stepEditIndex/3].scaleType;
|
||||||
|
if (type >= 0 && type < 10)
|
||||||
|
drawEditScreen("STEP CHORD", "", typeNames[type]);
|
||||||
|
else
|
||||||
|
drawEditScreen("STEP CHORD", "", "?");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UI_EDIT_PROG_STEP_REPS:
|
||||||
|
drawEditScreen("STEP REPEATS", "Reps: ", currentProgression.steps[stepEditIndex/3].repeats);
|
||||||
|
break;
|
||||||
case UI_EDIT_INTENSITY:
|
case UI_EDIT_INTENSITY:
|
||||||
drawNumberEditor("SET INTENSITY", trackIntensities[randomizeTrack], 1, 10);
|
drawNumberEditor("SET INTENSITY", trackIntensities[randomizeTrack], 1, 10);
|
||||||
break;
|
break;
|
||||||
@ -154,6 +179,8 @@ void UIManager::drawMenu(int selection, UIState currentState, int midiChannel, i
|
|||||||
if (currentVisualIndex >= startVisualIndex) {
|
if (currentVisualIndex >= startVisualIndex) {
|
||||||
if (y > 55) break;
|
if (y > 55) break;
|
||||||
|
|
||||||
|
MenuItemID id = menuItems[i].id;
|
||||||
|
|
||||||
if (i == selection) {
|
if (i == selection) {
|
||||||
display.fillRect(0, y, 128, 9, SSD1306_WHITE);
|
display.fillRect(0, y, 128, 9, SSD1306_WHITE);
|
||||||
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
|
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
|
||||||
@ -168,22 +195,18 @@ void UIManager::drawMenu(int selection, UIState currentState, int midiChannel, i
|
|||||||
display.print(menuItems[i].expanded ? F("v ") : F("> "));
|
display.print(menuItems[i].expanded ? F("v ") : F("> "));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (id >= MENU_ID_PROG_STEP_START && id < MENU_ID_PROG_STEP_END) {
|
||||||
|
int stepIdx = (id - MENU_ID_PROG_STEP_START) / 3;
|
||||||
|
if (stepIdx == visualProgressionStep) {
|
||||||
|
display.setCursor(x - 6 * 2, y + 1);
|
||||||
|
display.print(F("| "));
|
||||||
|
} else if (stepIdx == queuedProgressionStep) {
|
||||||
|
display.setCursor(x - 6 * 2, y + 1);
|
||||||
|
display.print(F(", "));
|
||||||
|
}
|
||||||
|
}
|
||||||
display.print(menuItems[i].label);
|
display.print(menuItems[i].label);
|
||||||
|
|
||||||
// Render checkbox for scale types
|
|
||||||
if (menuItems[i].id >= MENU_ID_SCALE_TYPE_CHROMATIC && menuItems[i].id <= MENU_ID_SCALE_TYPE_CHORD_7) {
|
|
||||||
int typeIndex = menuItems[i].id - MENU_ID_SCALE_TYPE_CHROMATIC;
|
|
||||||
bool enabled = (enabledScaleTypes & (1 << typeIndex));
|
|
||||||
int boxX = x - 10;
|
|
||||||
display.drawRect(boxX, y + 1, 7, 7, (i == selection) ? SSD1306_BLACK : SSD1306_WHITE);
|
|
||||||
if (enabled) display.fillRect(boxX + 2, y + 3, 3, 3, (i == selection) ? SSD1306_BLACK : SSD1306_WHITE);
|
|
||||||
if (typeIndex == currentScaleType) {
|
|
||||||
display.print(F(" *"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItemID id = menuItems[i].id;
|
|
||||||
|
|
||||||
if (id == MENU_ID_CHANNEL) {
|
if (id == MENU_ID_CHANNEL) {
|
||||||
display.print(F(": ")); display.print(midiChannel);
|
display.print(F(": ")); display.print(midiChannel);
|
||||||
}
|
}
|
||||||
@ -195,13 +218,37 @@ void UIManager::drawMenu(int selection, UIState currentState, int midiChannel, i
|
|||||||
} else if (id == MENU_ID_TEMPO) { display.print(F(": ")); display.print(tempo); }
|
} else if (id == MENU_ID_TEMPO) { display.print(F(": ")); display.print(tempo); }
|
||||||
else if (id == MENU_ID_ROOT) {
|
else if (id == MENU_ID_ROOT) {
|
||||||
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"};
|
||||||
display.print(F(": ")); display.print(noteNames[currentRoot % 12]);
|
display.print(F(": ")); display.print(noteNames[globalRoot % 12]);
|
||||||
}
|
}
|
||||||
else if (id == MENU_ID_STEPS) { display.print(F(": ")); display.print(currentTrackNumSteps); }
|
else if (id == MENU_ID_STEPS) { display.print(F(": ")); display.print(currentTrackNumSteps); }
|
||||||
else if (id == MENU_ID_SONG_MODE) { display.print(F(": ")); display.print(songModeEnabled ? F("ON") : F("OFF")); }
|
else if (id == MENU_ID_SONG_MODE) { display.print(F(": ")); display.print(songModeEnabled ? F("ON") : F("OFF")); }
|
||||||
else if (id == MENU_ID_TRACK_SELECT) { display.print(F(": ")); display.print(randomizeTrack + 1); }
|
else if (id == MENU_ID_TRACK_SELECT) { display.print(F(": ")); display.print(randomizeTrack + 1); }
|
||||||
else if (id == MENU_ID_MUTE) { display.print(F(": ")); display.print(trackMute[randomizeTrack] ? F("YES") : F("NO")); }
|
else if (id == MENU_ID_MUTE) { display.print(F(": ")); display.print(trackMute[randomizeTrack] ? F("YES") : F("NO")); }
|
||||||
else if (id == MENU_ID_FLAVOUR) { display.print(F(": ")); display.print(flavourName); }
|
else if (id == MENU_ID_FLAVOUR) { display.print(F(": ")); display.print(flavourName); }
|
||||||
|
else if (id == MENU_ID_PROG_ORDER) { display.print(F(": ")); display.print((progressionOrder == PROG_ORDER_SEQUENCE) ? F("Seq") : F("Rnd")); }
|
||||||
|
else if (id == MENU_ID_PROG_LENGTH) { display.print(F(": ")); display.print(currentProgression.length); }
|
||||||
|
else if (id >= MENU_ID_PROG_STEP_START && id < MENU_ID_PROG_STEP_END) {
|
||||||
|
// Only show steps that are within the current length
|
||||||
|
int stepIdx = (id - MENU_ID_PROG_STEP_START) / 3;
|
||||||
|
if (stepIdx >= currentProgression.length) {
|
||||||
|
continue; // Skip rendering this item
|
||||||
|
}
|
||||||
|
int idx = id - MENU_ID_PROG_STEP_START;
|
||||||
|
int step = idx / 3;
|
||||||
|
int type = idx % 3; // 0=Root, 1=Type, 2=Reps
|
||||||
|
display.print(F(": "));
|
||||||
|
if (type == 1) {
|
||||||
|
const char* typeNames[] = {"Chrom", "Maj", "Min", "HMin", "PMaj", "PMin", "CMaj", "CMin", "CDim", "C7"};
|
||||||
|
int t = currentProgression.steps[step].scaleType;
|
||||||
|
if (t >= 0 && t < 10) display.print(typeNames[t]);
|
||||||
|
} else if (type == 2) {
|
||||||
|
display.print(currentProgression.steps[step].repeats);
|
||||||
|
} else {
|
||||||
|
int root = currentProgression.steps[step].rootOffset;
|
||||||
|
if (root > 0) display.print(F("+"));
|
||||||
|
display.print(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (id == MENU_ID_INTENSITY) {
|
else if (id == MENU_ID_INTENSITY) {
|
||||||
display.print(F(": "));
|
display.print(F(": "));
|
||||||
display.print(trackIntensities[randomizeTrack]);
|
display.print(trackIntensities[randomizeTrack]);
|
||||||
|
|||||||
108
UIThread.cpp
108
UIThread.cpp
@ -94,15 +94,58 @@ static void handleInput() {
|
|||||||
if (numSteps[randomizeTrack] > NUM_STEPS) numSteps[randomizeTrack] = NUM_STEPS;
|
if (numSteps[randomizeTrack] > NUM_STEPS) numSteps[randomizeTrack] = NUM_STEPS;
|
||||||
break;
|
break;
|
||||||
case UI_EDIT_ROOT:
|
case UI_EDIT_ROOT:
|
||||||
currentRoot += delta;
|
globalRoot += delta;
|
||||||
if (currentRoot < 0) currentRoot = 11;
|
if (globalRoot < 0) globalRoot = 11;
|
||||||
if (currentRoot > 11) currentRoot = 0;
|
if (globalRoot > 11) globalRoot = 0;
|
||||||
|
currentRoot = globalRoot; // Reset current to global
|
||||||
SequenceGenerator::updateScale();
|
SequenceGenerator::updateScale();
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
sequenceChangeScheduled = true;
|
sequenceChangeScheduled = true;
|
||||||
SequenceGenerator::generateSequenceData((queuedTheme != -1) ? queuedTheme : currentThemeIndex, nextSequence);
|
SequenceGenerator::generateSequenceData((queuedTheme != -1) ? queuedTheme : currentThemeIndex, nextSequence);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case UI_EDIT_PROG_ORDER:
|
||||||
|
if (delta > 0) progressionOrder = PROG_ORDER_RANDOM;
|
||||||
|
else progressionOrder = PROG_ORDER_SEQUENCE;
|
||||||
|
break;
|
||||||
|
case UI_EDIT_PROG_TEMPLATE:
|
||||||
|
templateSelectionIndex += delta;
|
||||||
|
if (templateSelectionIndex < 0) templateSelectionIndex = numProgressions - 1;
|
||||||
|
if (templateSelectionIndex >= numProgressions) templateSelectionIndex = 0;
|
||||||
|
break;
|
||||||
|
case UI_EDIT_PROG_LENGTH:
|
||||||
|
currentProgression.length += delta;
|
||||||
|
if (currentProgression.length < 1) currentProgression.length = 1;
|
||||||
|
if (currentProgression.length > MAX_PROG_STEPS) currentProgression.length = MAX_PROG_STEPS;
|
||||||
|
progressionStep = 0; // Reset step
|
||||||
|
break;
|
||||||
|
case UI_EDIT_PROG_STEP_ROOT:
|
||||||
|
{
|
||||||
|
int stepIdx = stepEditIndex / 3;
|
||||||
|
currentProgression.steps[stepIdx].rootOffset += delta;
|
||||||
|
if (currentProgression.steps[stepIdx].rootOffset < -12) currentProgression.steps[stepIdx].rootOffset = -12;
|
||||||
|
if (currentProgression.steps[stepIdx].rootOffset > 12) currentProgression.steps[stepIdx].rootOffset = 12;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UI_EDIT_PROG_STEP_TYPE:
|
||||||
|
{
|
||||||
|
int stepIdx = stepEditIndex / 3;
|
||||||
|
int type = currentProgression.steps[stepIdx].scaleType;
|
||||||
|
type += delta;
|
||||||
|
if (type < 0) type = 9;
|
||||||
|
if (type > 9) type = 0;
|
||||||
|
currentProgression.steps[stepIdx].scaleType = type;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UI_EDIT_PROG_STEP_REPS:
|
||||||
|
{
|
||||||
|
int stepIdx = stepEditIndex / 3;
|
||||||
|
int val = currentProgression.steps[stepIdx].repeats + delta;
|
||||||
|
if (val < 1) val = 1;
|
||||||
|
if (val > 16) val = 16;
|
||||||
|
currentProgression.steps[stepIdx].repeats = val;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case UI_EDIT_FLAVOUR:
|
case UI_EDIT_FLAVOUR:
|
||||||
{
|
{
|
||||||
currentStrategyIndices[randomizeTrack] += (delta > 0 ? 1 : -1);
|
currentStrategyIndices[randomizeTrack] += (delta > 0 ? 1 : -1);
|
||||||
@ -186,6 +229,17 @@ static void handleInput() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case MENU_ID_ROOT: currentState = UI_EDIT_ROOT; break;
|
case MENU_ID_ROOT: currentState = UI_EDIT_ROOT; break;
|
||||||
|
case MENU_ID_PROG_ORDER: currentState = UI_EDIT_PROG_ORDER; break;
|
||||||
|
case MENU_ID_PROG_APPLY_TEMPLATE: currentState = UI_EDIT_PROG_TEMPLATE; templateSelectionIndex = 0; break;
|
||||||
|
case MENU_ID_PROG_LENGTH: currentState = UI_EDIT_PROG_LENGTH; break;
|
||||||
|
|
||||||
|
case MENU_ID_PROG_STEP_START ... (MENU_ID_PROG_STEP_END - 1):
|
||||||
|
stepEditIndex = menuItems[menuSelection].id - MENU_ID_PROG_STEP_START;
|
||||||
|
if (stepEditIndex % 3 == 0) currentState = UI_EDIT_PROG_STEP_ROOT;
|
||||||
|
else if (stepEditIndex % 3 == 1) currentState = UI_EDIT_PROG_STEP_TYPE;
|
||||||
|
else currentState = UI_EDIT_PROG_STEP_REPS;
|
||||||
|
break;
|
||||||
|
|
||||||
case MENU_ID_TEMPO: currentState = UI_EDIT_TEMPO; break;
|
case MENU_ID_TEMPO: currentState = UI_EDIT_TEMPO; break;
|
||||||
case MENU_ID_STEPS: currentState = UI_EDIT_STEPS; break;
|
case MENU_ID_STEPS: currentState = UI_EDIT_STEPS; break;
|
||||||
|
|
||||||
@ -207,12 +261,6 @@ static void handleInput() {
|
|||||||
case MENU_ID_RESET: factoryReset(); break;
|
case MENU_ID_RESET: factoryReset(); break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (menuItems[menuSelection].id >= MENU_ID_SCALE_TYPE_CHROMATIC && menuItems[menuSelection].id <= MENU_ID_SCALE_TYPE_CHORD_7) {
|
|
||||||
int typeIndex = menuItems[menuSelection].id - MENU_ID_SCALE_TYPE_CHROMATIC;
|
|
||||||
enabledScaleTypes ^= (1 << typeIndex);
|
|
||||||
if (enabledScaleTypes == 0) enabledScaleTypes = (1 << typeIndex); // Prevent disabling all
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (menuItems[menuSelection].id >= MENU_ID_THEME_1 && menuItems[menuSelection].id <= MENU_ID_THEME_7) {
|
if (menuItems[menuSelection].id >= MENU_ID_THEME_1 && menuItems[menuSelection].id <= MENU_ID_THEME_7) {
|
||||||
const int selectedTheme = menuItems[menuSelection].id - MENU_ID_THEME_1 + 1;
|
const int selectedTheme = menuItems[menuSelection].id - MENU_ID_THEME_1 + 1;
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
@ -256,6 +304,29 @@ static void handleInput() {
|
|||||||
currentState = UI_MENU_MAIN;
|
currentState = UI_MENU_MAIN;
|
||||||
saveSequence(true);
|
saveSequence(true);
|
||||||
break;
|
break;
|
||||||
|
case UI_EDIT_PROG_ORDER:
|
||||||
|
currentState = UI_MENU_MAIN;
|
||||||
|
saveSequence(true);
|
||||||
|
break;
|
||||||
|
case UI_EDIT_PROG_TEMPLATE:
|
||||||
|
// Apply template
|
||||||
|
if (templateSelectionIndex > 0) {
|
||||||
|
currentProgression = progressions[templateSelectionIndex];
|
||||||
|
progressionStep = 0;
|
||||||
|
} else {
|
||||||
|
// Off/Clear
|
||||||
|
currentProgression.length = 0;
|
||||||
|
}
|
||||||
|
currentState = UI_MENU_MAIN;
|
||||||
|
saveSequence(true);
|
||||||
|
break;
|
||||||
|
case UI_EDIT_PROG_LENGTH:
|
||||||
|
case UI_EDIT_PROG_STEP_ROOT:
|
||||||
|
case UI_EDIT_PROG_STEP_TYPE:
|
||||||
|
case UI_EDIT_PROG_STEP_REPS:
|
||||||
|
currentState = UI_MENU_MAIN;
|
||||||
|
saveSequence(true);
|
||||||
|
break;
|
||||||
case UI_EDIT_FLAVOUR:
|
case UI_EDIT_FLAVOUR:
|
||||||
currentState = UI_MENU_MAIN;
|
currentState = UI_MENU_MAIN;
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
@ -412,7 +483,26 @@ void loopUI() {
|
|||||||
int nextTheme = random(1, 8); // Themes 1-7
|
int nextTheme = random(1, 8); // Themes 1-7
|
||||||
int repeats = random(1, 9); // 1-8 repeats
|
int repeats = random(1, 9); // 1-8 repeats
|
||||||
|
|
||||||
|
if (currentProgression.length > 0) {
|
||||||
|
// Progression Mode
|
||||||
|
const ChordProgression& prog = currentProgression;
|
||||||
|
|
||||||
|
currentRoot = (globalRoot + prog.steps[progressionStep].rootOffset) % 12;
|
||||||
|
if (currentRoot < 0) currentRoot += 12;
|
||||||
|
currentScaleType = prog.steps[progressionStep].scaleType;
|
||||||
|
SequenceGenerator::updateScale();
|
||||||
|
|
||||||
|
queuedProgressionStep = progressionStep;
|
||||||
|
repeats = prog.steps[progressionStep].repeats;
|
||||||
|
if (progressionOrder == PROG_ORDER_SEQUENCE)
|
||||||
|
progressionStep = (progressionStep + 1) % prog.length;
|
||||||
|
else
|
||||||
|
progressionStep = random(prog.length);
|
||||||
|
} else {
|
||||||
|
// Random Mode
|
||||||
SequenceGenerator::pickRandomScaleType(nextTheme);
|
SequenceGenerator::pickRandomScaleType(nextTheme);
|
||||||
|
}
|
||||||
|
|
||||||
SequenceGenerator::generateSequenceData(nextTheme, nextSequence);
|
SequenceGenerator::generateSequenceData(nextTheme, nextSequence);
|
||||||
queuedTheme = nextTheme;
|
queuedTheme = nextTheme;
|
||||||
nextSongRepeats = repeats;
|
nextSongRepeats = repeats;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user