Root note and bitmap of scale types

This commit is contained in:
Dejvino 2026-03-06 21:29:17 +01:00
parent 84ef40a555
commit b6150cbacd
5 changed files with 167 additions and 2 deletions

View File

@ -23,6 +23,8 @@ MenuItem menuItems[] = {
{ "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 },
{ "Scale", MENU_ID_SCALE, false, false, 1 }, { "Scale", MENU_ID_SCALE, false, false, 1 },
{ "Root", MENU_ID_ROOT, false, false, 1 },
{ "Type", MENU_ID_SCALE_TYPE, false, false, 1 },
{ "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 },
@ -112,7 +114,10 @@ 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;
extern const uint32_t EEPROM_MAGIC = 0x4242424E; int currentRoot = 0; // C
int currentScaleType = 1; // Major
int enabledScaleTypes = 2; // Major (1<<1)
extern const uint32_t EEPROM_MAGIC = 0x42424250;
MelodyStrategy* strategies[] = { MelodyStrategy* strategies[] = {
new LuckyStrategy(), new ArpStrategy(), new EuclideanStrategy(), new LuckyStrategy(), new ArpStrategy(), new EuclideanStrategy(),

View File

@ -19,6 +19,8 @@ enum MenuItemID {
MENU_ID_PLAYBACK, MENU_ID_PLAYBACK,
MENU_ID_MELODY, MENU_ID_MELODY,
MENU_ID_SCALE, MENU_ID_SCALE,
MENU_ID_ROOT,
MENU_ID_SCALE_TYPE,
MENU_ID_TEMPO, MENU_ID_TEMPO,
MENU_ID_SONG_MODE, MENU_ID_SONG_MODE,
@ -73,6 +75,9 @@ 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 currentRoot;
extern int currentScaleType;
extern int enabledScaleTypes;
extern const uint32_t EEPROM_MAGIC; extern const uint32_t EEPROM_MAGIC;
extern MelodyStrategy* strategies[]; extern MelodyStrategy* strategies[];

View File

@ -26,7 +26,9 @@ enum UIState {
UI_RANDOMIZE_TRACK_EDIT, UI_RANDOMIZE_TRACK_EDIT,
UI_SCALE_EDIT, UI_SCALE_EDIT,
UI_SCALE_NOTE_EDIT, UI_SCALE_NOTE_EDIT,
UI_SCALE_TRANSPOSE UI_SCALE_TRANSPOSE,
UI_EDIT_ROOT,
UI_EDIT_SCALE_TYPE
}; };
inline void sortArray(int arr[], int size) { inline void sortArray(int arr[], int size) {

View File

@ -110,6 +110,39 @@ void UIManager::draw(UIState currentState, int menuSelection,
display.setCursor(0, 50); display.setCursor(0, 50);
display.println(F(" (Press to confirm)")); display.println(F(" (Press to confirm)"));
break; break;
case UI_EDIT_ROOT:
display.println(F("SET ROOT NOTE"));
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
display.setCursor(20, 25);
display.setTextSize(2);
{
const char* noteNames[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
display.print(noteNames[currentRoot % 12]);
}
display.setTextSize(1);
display.setCursor(0, 50);
display.println(F(" (Press to confirm)"));
break;
case UI_EDIT_SCALE_TYPE:
display.println(F("ENABLED SCALES"));
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
display.setCursor(0, 20);
if (menuSelection < 10) {
const char* typeNames[] = {"Chrom", "Major", "Minor", "H.Min", "P.Maj", "P.Min", "ChdMaj", "ChdMin", "ChdDim", "Chd7"};
display.setTextSize(2);
display.print(typeNames[menuSelection]);
display.setTextSize(1);
display.setCursor(0, 45);
bool isEnabled = (enabledScaleTypes & (1 << menuSelection));
display.print(isEnabled ? F("[ENABLED]") : F("[DISABLED]"));
} else {
display.setTextSize(2);
display.print(F("Back"));
}
display.setTextSize(1);
display.setCursor(0, 55);
display.println(F(" (Press to toggle)"));
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;
@ -294,6 +327,14 @@ 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) {
const char* noteNames[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
display.print(F(": ")); display.print(noteNames[currentRoot % 12]);
}
else if (id == MENU_ID_SCALE_TYPE) {
const char* typeNames[] = {"Chrom", "Major", "Minor", "H.Min", "P.Maj", "P.Min", "ChdMaj", "ChdMin", "ChdDim", "Chd7"};
display.print(F(": ")); if (currentScaleType >= 0 && currentScaleType < 10) display.print(typeNames[currentScaleType]);
}
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); }

View File

@ -22,6 +22,7 @@ static Step local_sequence[NUM_TRACKS][NUM_STEPS];
static void handleInput(); static void handleInput();
static int scaleEditSelection = 0; static int scaleEditSelection = 0;
static int scaleTypeEditIndex = 0;
static int scaleEditNoteIndex = 0; static int scaleEditNoteIndex = 0;
static void drawUI(); static void drawUI();
static void updateLeds(); static void updateLeds();
@ -29,6 +30,8 @@ static void generateTrackData(int track, int themeType, Step (*target)[NUM_STEPS
static void generateSequenceData(int themeType, Step (*target)[NUM_STEPS]); static void generateSequenceData(int themeType, Step (*target)[NUM_STEPS]);
static void savePatch(int bank, int slot); static void savePatch(int bank, int slot);
static void loadPatch(int bank, int slot); static void loadPatch(int bank, int slot);
static void updateScale();
static void pickRandomScaleType();
void saveSequence(bool quiet) { void saveSequence(bool quiet) {
midi.lock(); midi.lock();
@ -50,6 +53,9 @@ void 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, 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);
@ -107,6 +113,9 @@ bool loadSequence() {
} }
} }
EEPROM.get(addr, currentRoot); addr += sizeof(currentRoot);
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++) {
@ -125,6 +134,9 @@ static void savePatch(int bank, int slot) {
int addr = 512 + patchIndex * 256; // Start after main save, 256 bytes per patch int addr = 512 + patchIndex * 256; // Start after main save, 256 bytes per patch
midi.lock(); midi.lock();
EEPROM.put(addr, currentRoot); addr += sizeof(currentRoot);
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);
@ -150,6 +162,9 @@ static void loadPatch(int bank, int slot) {
int addr = 512 + patchIndex * 256; int addr = 512 + patchIndex * 256;
midi.lock(); midi.lock();
EEPROM.get(addr, currentRoot); addr += sizeof(currentRoot);
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++) {
@ -225,6 +240,7 @@ static void generateSequenceData(int themeType, Step (*target)[NUM_STEPS]) {
} }
void generateTheme(int themeType) { void generateTheme(int themeType) {
pickRandomScaleType();
generateSequenceData(themeType, local_sequence); generateSequenceData(themeType, local_sequence);
Serial.println(F("Generating theme.")); Serial.println(F("Generating theme."));
@ -249,6 +265,67 @@ void mutateSequence(Step (*target)[NUM_STEPS]) {
} }
} }
static void updateScale() {
// 0: Chromatic, 1: Major, 2: Minor, 3: Harm Min, 4: Pent Maj, 5: Pent Min, 6: Chord Maj, 7: Chord Min, 8: Chord Dim, 9: Chord 7
int intervals[12];
int count = 0;
switch(currentScaleType) {
case 0: // Chromatic
for(int i=0; i<12; i++) intervals[count++] = i;
break;
case 1: // Major
intervals[0]=0; intervals[1]=2; intervals[2]=4; intervals[3]=5; intervals[4]=7; intervals[5]=9; intervals[6]=11; count=7;
break;
case 2: // Minor
intervals[0]=0; intervals[1]=2; intervals[2]=3; intervals[3]=5; intervals[4]=7; intervals[5]=8; intervals[6]=10; count=7;
break;
case 3: // Harmonic Minor
intervals[0]=0; intervals[1]=2; intervals[2]=3; intervals[3]=5; intervals[4]=7; intervals[5]=8; intervals[6]=11; count=7;
break;
case 4: // Pentatonic Major
intervals[0]=0; intervals[1]=2; intervals[2]=4; intervals[3]=7; intervals[4]=9; count=5;
break;
case 5: // Pentatonic Minor
intervals[0]=0; intervals[1]=3; intervals[2]=5; intervals[3]=7; intervals[4]=10; count=5;
break;
case 6: // Chord Major
intervals[0]=0; intervals[1]=4; intervals[2]=7; count=3;
break;
case 7: // Chord Minor
intervals[0]=0; intervals[1]=3; intervals[2]=7; count=3;
break;
case 8: // Chord Dim
intervals[0]=0; intervals[1]=3; intervals[2]=6; count=3;
break;
case 9: // Chord 7
intervals[0]=0; intervals[1]=4; intervals[2]=7; intervals[3]=10; count=4;
break;
}
midi.lock();
numScaleNotes = count;
for(int i=0; i<count; i++) {
scaleNotes[i] = (currentRoot + intervals[i]) % 12;
}
sortArray(scaleNotes, numScaleNotes);
midi.unlock();
}
static void pickRandomScaleType() {
int candidates[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)];
updateScale();
}
}
static void handleInput() { static void handleInput() {
// Handle Encoder Rotation // Handle Encoder Rotation
int delta = 0; int delta = 0;
@ -289,6 +366,21 @@ static void handleInput() {
if (numSteps[randomizeTrack] < 1) numSteps[randomizeTrack] = 1; if (numSteps[randomizeTrack] < 1) numSteps[randomizeTrack] = 1;
if (numSteps[randomizeTrack] > NUM_STEPS) numSteps[randomizeTrack] = NUM_STEPS; if (numSteps[randomizeTrack] > NUM_STEPS) numSteps[randomizeTrack] = NUM_STEPS;
break; break;
case UI_EDIT_ROOT:
currentRoot += delta;
if (currentRoot < 0) currentRoot = 11;
if (currentRoot > 11) currentRoot = 0;
updateScale();
if (isPlaying) {
sequenceChangeScheduled = true;
generateSequenceData((queuedTheme != -1) ? queuedTheme : currentThemeIndex, nextSequence);
}
break;
case UI_EDIT_SCALE_TYPE:
scaleTypeEditIndex += delta;
if (scaleTypeEditIndex < 0) scaleTypeEditIndex = 10;
if (scaleTypeEditIndex > 10) scaleTypeEditIndex = 0;
break;
case UI_EDIT_FLAVOUR: case UI_EDIT_FLAVOUR:
{ {
currentStrategyIndices[randomizeTrack] += (delta > 0 ? 1 : -1); currentStrategyIndices[randomizeTrack] += (delta > 0 ? 1 : -1);
@ -408,6 +500,11 @@ static void handleInput() {
scaleEditSelection = 0; scaleEditSelection = 0;
break; break;
case MENU_ID_ROOT: currentState = UI_EDIT_ROOT; break;
case MENU_ID_SCALE_TYPE:
currentState = UI_EDIT_SCALE_TYPE;
scaleTypeEditIndex = 0;
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;
@ -467,6 +564,19 @@ static void handleInput() {
currentState = UI_MENU_MAIN; currentState = UI_MENU_MAIN;
saveSequence(true); saveSequence(true);
break; break;
case UI_EDIT_ROOT:
currentState = UI_MENU_MAIN;
saveSequence(true);
break;
case UI_EDIT_SCALE_TYPE:
if (scaleTypeEditIndex == 10) {
currentState = UI_MENU_MAIN;
saveSequence(true);
} else {
enabledScaleTypes ^= (1 << scaleTypeEditIndex);
if (enabledScaleTypes == 0) enabledScaleTypes = (1 << scaleTypeEditIndex); // Prevent disabling all
}
break;
case UI_EDIT_FLAVOUR: case UI_EDIT_FLAVOUR:
currentState = UI_MENU_MAIN; currentState = UI_MENU_MAIN;
if (isPlaying) { if (isPlaying) {
@ -606,6 +716,8 @@ static void drawUI() {
local_currentState = currentState; local_currentState = currentState;
if (local_currentState == UI_SCALE_EDIT || local_currentState == UI_SCALE_NOTE_EDIT || local_currentState == UI_SCALE_TRANSPOSE) { if (local_currentState == UI_SCALE_EDIT || local_currentState == UI_SCALE_NOTE_EDIT || local_currentState == UI_SCALE_TRANSPOSE) {
local_menuSelection = scaleEditSelection; local_menuSelection = scaleEditSelection;
} else if (local_currentState == UI_EDIT_SCALE_TYPE) {
local_menuSelection = scaleTypeEditIndex;
} else { } else {
local_menuSelection = menuSelection; local_menuSelection = menuSelection;
} }