Intensity setting

This commit is contained in:
Dejvino 2026-02-22 22:25:17 +01:00
parent e6a4711861
commit 33c6329f0b
13 changed files with 182 additions and 40 deletions

View File

@ -6,7 +6,7 @@
class ArpStrategy : public MelodyStrategy { class ArpStrategy : public MelodyStrategy {
public: public:
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed) override { void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed, int intensity) override {
randomSeed(seed); randomSeed(seed);
if (numScaleNotes == 0) return; if (numScaleNotes == 0) return;
@ -51,8 +51,10 @@ public:
} }
for (int i = 0; i < arpLength; i++) { for (int i = 0; i < arpLength; i++) {
// Chance of rest (15%) // Use intensity to control density (gaps)
if (random(100) < 15) { // Intensity 10 (max) = 5% rest chance. Intensity 1 (min) = 50% rest chance.
int restChance = 55 - (intensity * 5);
if (random(100) < restChance) {
arpPattern[i].note = -1; arpPattern[i].note = -1;
arpPattern[i].accent = false; arpPattern[i].accent = false;
arpPattern[i].tie = false; arpPattern[i].tie = false;
@ -60,10 +62,13 @@ public:
int octaveOffset = currentIndex / subsetSize; int octaveOffset = currentIndex / subsetSize;
int noteIndex = currentIndex % subsetSize; int noteIndex = currentIndex % subsetSize;
int octave = baseOctave + octaveOffset; int octave = baseOctave + octaveOffset;
// Use intensity to control note length (ties)
// Intensity 10 (max) = 4% tie chance. Intensity 1 (min) = 40% tie chance.
int tieChance = 44 - (intensity * 4);
arpPattern[i].note = 12 * octave + subset[noteIndex]; arpPattern[i].note = 12 * octave + subset[noteIndex];
arpPattern[i].accent = (i % 4 == 0); // Accent on beat arpPattern[i].accent = (i % 4 == 0); // Accent on beat
arpPattern[i].tie = false; arpPattern[i].tie = (random(100) < tieChance);
} }
if (mode == 0) { // Up if (mode == 0) { // Up
@ -106,7 +111,7 @@ public:
sortArray(scaleNotes, numScaleNotes); sortArray(scaleNotes, numScaleNotes);
} }
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes) override { void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int intensity) override {
// Swap two notes // Swap two notes
int s1 = random(numSteps); int s1 = random(numSteps);
int s2 = random(numSteps); int s2 = random(numSteps);

View File

@ -6,7 +6,7 @@
class CellularAutomataStrategy : public MelodyStrategy { class CellularAutomataStrategy : public MelodyStrategy {
public: public:
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed) override { void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed, int intensity) override {
randomSeed(seed); randomSeed(seed);
if (numScaleNotes == 0) return; if (numScaleNotes == 0) return;
@ -23,7 +23,8 @@ public:
for(int i=0; i<numSteps; i++) cells[i] = false; for(int i=0; i<numSteps; i++) cells[i] = false;
cells[numSteps/2] = true; cells[numSteps/2] = true;
} else { } else {
for(int i=0; i<numSteps; i++) cells[i] = (random(100) < 30); int initChance = intensity * 8; // 8% to 80%
for(int i=0; i<numSteps; i++) cells[i] = (random(100) < initChance);
} }
// Evolve for some generations to let patterns emerge // Evolve for some generations to let patterns emerge
@ -43,7 +44,9 @@ public:
// Map to notes // Map to notes
for (int i = 0; i < numSteps; i++) { for (int i = 0; i < numSteps; i++) {
if (cells[i]) { // Intensity also affects survival of active cells to notes
// Intensity 1 = 50% survival, Intensity 10 = 100% survival
if (cells[i] && random(100) < (50 + intensity * 5)) {
int octave = 3 + random(3); // 3, 4, 5 int octave = 3 + random(3); // 3, 4, 5
sequence[track][i].note = 12 * octave + scaleNotes[random(numScaleNotes)]; sequence[track][i].note = 12 * octave + scaleNotes[random(numScaleNotes)];
sequence[track][i].accent = (random(100) < 30); sequence[track][i].accent = (random(100) < 30);
@ -71,7 +74,7 @@ public:
sortArray(scaleNotes, numScaleNotes); sortArray(scaleNotes, numScaleNotes);
} }
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes) override { void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int intensity) override {
// Evolve the current sequence by one generation // Evolve the current sequence by one generation
// Use a random rule for mutation to keep it dynamic // Use a random rule for mutation to keep it dynamic
uint8_t rule = random(256); uint8_t rule = random(256);

View File

@ -6,11 +6,19 @@
class EuclideanStrategy : public MelodyStrategy { class EuclideanStrategy : public MelodyStrategy {
public: public:
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed) override { void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed, int intensity) override {
randomSeed(seed); randomSeed(seed);
if (numScaleNotes == 0) return; if (numScaleNotes == 0) return;
int pulses = random(1, numSteps + 1); // Intensity controls density.
// Intensity 1 = ~10% density, Intensity 10 = ~100% density
int targetPulses = (numSteps * intensity) / 10;
if (targetPulses < 1) targetPulses = 1;
// Add some variation (+/- 1 pulse)
int pulses = targetPulses + random(-1, 2);
if (pulses < 1) pulses = 1;
if (pulses > numSteps) pulses = numSteps;
int offset = random(numSteps); int offset = random(numSteps);
// Euclidean distribution (Bresenham) // Euclidean distribution (Bresenham)
@ -61,7 +69,7 @@ public:
sortArray(scaleNotes, numScaleNotes); sortArray(scaleNotes, numScaleNotes);
} }
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes) override { void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int intensity) override {
// Rotate sequence // Rotate sequence
if (random(2) == 0) { if (random(2) == 0) {
Step last = sequence[track][numSteps - 1]; Step last = sequence[track][numSteps - 1];

View File

@ -29,7 +29,7 @@ const uint8_t numRuleSets = sizeof(ruleSets) / sizeof(RuleSet);
class LSystemStrategy : public MelodyStrategy { class LSystemStrategy : public MelodyStrategy {
public: public:
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed) override { void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed, int intensity) override {
randomSeed(seed); randomSeed(seed);
if (numScaleNotes == 0) { if (numScaleNotes == 0) {
// Fill with silence if no scale // Fill with silence if no scale
@ -76,14 +76,19 @@ public:
int octave_stack[8]; int octave_stack[8];
int stack_ptr = 0; int stack_ptr = 0;
// High intensity = more accents, fewer ties (staccato)
// Low intensity = fewer accents, more ties (legato)
int accentChance = intensity * 6; // 6% to 60%
int tieChance = 50 - (intensity * 5); // 45% to 0%
for (char c : currentString) { for (char c : currentString) {
if (stepIndex >= numSteps) break; if (stepIndex >= numSteps) break;
switch(c) { switch(c) {
case 'F': case 'G': case 'A': case 'B': // Characters that draw notes case 'F': case 'G': case 'A': case 'B': // Characters that draw notes
sequence[track][stepIndex].note = 12 * octave + scaleNotes[noteIndex]; sequence[track][stepIndex].note = 12 * octave + scaleNotes[noteIndex];
sequence[track][stepIndex].accent = (random(100) < 25); sequence[track][stepIndex].accent = (random(100) < accentChance);
sequence[track][stepIndex].tie = (random(100) < 10); sequence[track][stepIndex].tie = (random(100) < tieChance);
stepIndex++; stepIndex++;
break; break;
case '+': // Go up in scale case '+': // Go up in scale
@ -135,7 +140,7 @@ public:
sortArray(scaleNotes, numScaleNotes); sortArray(scaleNotes, numScaleNotes);
} }
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes) override { void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int intensity) override {
// Swap two non-rest steps to create a variation // Swap two non-rest steps to create a variation
int s1 = random(numSteps); int s1 = random(numSteps);
int s2 = random(numSteps); int s2 = random(numSteps);

View File

@ -6,14 +6,17 @@
class LuckyStrategy : public MelodyStrategy { class LuckyStrategy : public MelodyStrategy {
public: public:
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed) override { void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed, int intensity) override {
randomSeed(seed); randomSeed(seed);
if (numScaleNotes == 0) return; if (numScaleNotes == 0) return;
int noteChance = intensity * 9; // 10% to 90%
int accentChance = intensity * 6; // 6% to 60%
for (int i = 0; i < numSteps; i++) { for (int i = 0; i < numSteps; i++) {
int octave = random(3) + 3; // 3, 4, 5 (Base is 4) int octave = random(3) + 3; // 3, 4, 5 (Base is 4)
sequence[track][i].note = (random(100) < 50) ? (12 * octave + scaleNotes[random(numScaleNotes)]) : -1; sequence[track][i].note = (random(100) < noteChance) ? (12 * octave + scaleNotes[random(numScaleNotes)]) : -1;
sequence[track][i].accent = (random(100) < 30); sequence[track][i].accent = (random(100) < accentChance);
sequence[track][i].tie = (random(100) < 20); sequence[track][i].tie = (random(100) < 20);
} }
randomSeed(micros()); randomSeed(micros());
@ -34,7 +37,7 @@ public:
sortArray(scaleNotes, numScaleNotes); sortArray(scaleNotes, numScaleNotes);
} }
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes) override { void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int intensity) override {
// Mutate 1 or 2 steps // Mutate 1 or 2 steps
int count = random(1, 3); int count = random(1, 3);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {

View File

@ -6,7 +6,7 @@
class MarkovStrategy : public MelodyStrategy { class MarkovStrategy : public MelodyStrategy {
public: public:
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed) override { void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed, int intensity) override {
randomSeed(seed); randomSeed(seed);
if (numScaleNotes == 0) return; if (numScaleNotes == 0) return;
@ -32,8 +32,10 @@ public:
int currentIdx = random(numScaleNotes); int currentIdx = random(numScaleNotes);
for (int i = 0; i < numSteps; i++) { for (int i = 0; i < numSteps; i++) {
// 20% chance of rest // Intensity 1 = 60% rest, Intensity 10 = 0% rest
if (random(100) < 20) { int restChance = 60 - (intensity * 6);
if (restChance < 0) restChance = 0;
if (random(100) < restChance) {
sequence[track][i].note = -1; sequence[track][i].note = -1;
sequence[track][i].accent = false; sequence[track][i].accent = false;
sequence[track][i].tie = false; sequence[track][i].tie = false;
@ -85,7 +87,7 @@ public:
sortArray(scaleNotes, numScaleNotes); sortArray(scaleNotes, numScaleNotes);
} }
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes) override { void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int intensity) override {
// Drift mutation: pick a note and move it stepwise in the scale // Drift mutation: pick a note and move it stepwise in the scale
int s = random(numSteps); int s = random(numSteps);
if (sequence[track][s].note != -1) { if (sequence[track][s].note != -1) {

View File

@ -6,9 +6,9 @@
class MelodyStrategy { class MelodyStrategy {
public: public:
virtual void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed) = 0; virtual void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed, int intensity) = 0;
virtual void generateScale(int* scaleNotes, int& numScaleNotes) = 0; virtual void generateScale(int* scaleNotes, int& numScaleNotes) = 0;
virtual void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes) = 0; virtual void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int intensity) = 0;
virtual const char* getName() = 0; virtual const char* getName() = 0;
virtual ~MelodyStrategy() {} virtual ~MelodyStrategy() {}
}; };

View File

@ -15,6 +15,7 @@ volatile bool needsPanic = false;
UIState currentState = UI_MENU_MAIN; UIState currentState = UI_MENU_MAIN;
bool protectedMode = false; bool protectedMode = false;
volatile int trackIntensity[NUM_TRACKS] = {10, 10, 10, 10};
// Menus // Menus
MenuItem menuItems[] = { MenuItem menuItems[] = {
{ "Main", MENU_ID_GROUP_MAIN, true, true, 0 }, { "Main", MENU_ID_GROUP_MAIN, true, true, 0 },
@ -28,6 +29,7 @@ MenuItem menuItems[] = {
{ "Track", MENU_ID_TRACK_SELECT, false, false, 1 }, { "Track", MENU_ID_TRACK_SELECT, false, false, 1 },
{ "Mute", MENU_ID_MUTE, false, false, 1 }, { "Mute", MENU_ID_MUTE, false, false, 1 },
{ "Flavour", MENU_ID_FLAVOUR, false, false, 1 }, { "Flavour", MENU_ID_FLAVOUR, false, false, 1 },
{ "Intensity", MENU_ID_INTENSITY, false, false, 1 },
{ "Mutation", MENU_ID_MUTATION, false, false, 1 }, { "Mutation", MENU_ID_MUTATION, false, false, 1 },
{ "Theme 1", MENU_ID_THEME_1, false, false, 1 }, { "Theme 1", MENU_ID_THEME_1, false, false, 1 },
{ "Theme 2", MENU_ID_THEME_2, false, false, 1 }, { "Theme 2", MENU_ID_THEME_2, false, false, 1 },
@ -109,7 +111,7 @@ 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 = 0x4242424D; extern const uint32_t EEPROM_MAGIC = 0x4242424E;
MelodyStrategy* strategies[] = { MelodyStrategy* strategies[] = {
new LuckyStrategy(), new ArpStrategy(), new EuclideanStrategy(), new LuckyStrategy(), new ArpStrategy(), new EuclideanStrategy(),

View File

@ -27,6 +27,7 @@ enum MenuItemID {
MENU_ID_TRACK_SELECT, MENU_ID_TRACK_SELECT,
MENU_ID_MUTE, MENU_ID_MUTE,
MENU_ID_FLAVOUR, MENU_ID_FLAVOUR,
MENU_ID_INTENSITY,
MENU_ID_MUTATION, MENU_ID_MUTATION,
MENU_ID_THEME_1, MENU_ID_THEME_1,
MENU_ID_THEME_2, MENU_ID_THEME_2,

View File

@ -21,6 +21,7 @@ enum UIState {
UI_EDIT_TEMPO, UI_EDIT_TEMPO,
UI_EDIT_STEPS, UI_EDIT_STEPS,
UI_EDIT_FLAVOUR, UI_EDIT_FLAVOUR,
UI_EDIT_INTENSITY,
UI_SETUP_PLAYMODE_EDIT, UI_SETUP_PLAYMODE_EDIT,
UI_RANDOMIZE_TRACK_EDIT, UI_RANDOMIZE_TRACK_EDIT,
UI_SCALE_EDIT, UI_SCALE_EDIT,

View File

@ -50,10 +50,10 @@ void UIManager::showMessage(const char* msg) {
void UIManager::draw(UIState currentState, int menuSelection, void UIManager::draw(UIState currentState, int menuSelection,
int midiChannel, int tempo, MelodyStrategy* currentStrategy, int midiChannel, int tempo, MelodyStrategy* currentStrategy,
int queuedTheme, int currentThemeIndex, int queuedTheme, int currentThemeIndex,
int numScaleNotes, const int* scaleNotes, int melodySeed, int numSteps, int numScaleNotes, const int* scaleNotes, int melodySeed, int numSteps,
bool mutationEnabled, bool songModeEnabled, bool mutationEnabled, bool songModeEnabled,
const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying, const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying,
int randomizeTrack, const bool* trackMute) { int randomizeTrack, const bool* trackMute, const int* trackIntensities) {
display.clearDisplay(); display.clearDisplay();
display.setTextSize(1); display.setTextSize(1);
@ -62,7 +62,7 @@ void UIManager::draw(UIState currentState, int menuSelection,
switch(currentState) { switch(currentState) {
case UI_MENU_MAIN: case UI_MENU_MAIN:
drawMenu(menuSelection, currentState, midiChannel, tempo, currentStrategy->getName(), queuedTheme, currentThemeIndex, numScaleNotes, scaleNotes, melodySeed, numSteps, mutationEnabled, songModeEnabled, isPlaying, randomizeTrack, trackMute); drawMenu(menuSelection, currentState, midiChannel, tempo, currentStrategy->getName(), queuedTheme, currentThemeIndex, numScaleNotes, scaleNotes, melodySeed, numSteps, mutationEnabled, songModeEnabled, isPlaying, randomizeTrack, trackMute, trackIntensities);
break; break;
case UI_SETUP_CHANNEL_EDIT: case UI_SETUP_CHANNEL_EDIT:
display.println(F("SET MIDI CHANNEL")); display.println(F("SET MIDI CHANNEL"));
@ -108,6 +108,9 @@ 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_INTENSITY:
drawNumberEditor("SET INTENSITY", trackIntensities[randomizeTrack], 1, 10);
break;
case UI_RANDOMIZE_TRACK_EDIT: case UI_RANDOMIZE_TRACK_EDIT:
display.println(F("SET TRACK")); display.println(F("SET TRACK"));
display.drawLine(0, 8, 128, 8, SSD1306_WHITE); display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
@ -202,10 +205,35 @@ void UIManager::draw(UIState currentState, int menuSelection,
display.display(); display.display();
} }
void UIManager::drawNumberEditor(const char* title, int value, int minVal, int maxVal) {
display.println(title);
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
// Display value
display.setCursor(20, 20);
display.setTextSize(2);
display.print(value);
// Graphical bar
int barWidth = 100;
int barX = (SCREEN_WIDTH - barWidth) / 2;
int barY = 40;
int barHeight = 10;
float percentage = (float)(value - minVal) / (maxVal - minVal);
int fillWidth = (int)(percentage * barWidth);
display.drawRect(barX, barY, barWidth, barHeight, SSD1306_WHITE);
display.fillRect(barX, barY, fillWidth, barHeight, SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 54);
display.println(F(" (Press to confirm)"));
}
void UIManager::drawMenu(int selection, UIState currentState, int midiChannel, int tempo, const char* flavourName, void UIManager::drawMenu(int selection, UIState currentState, int midiChannel, int tempo, const char* flavourName,
int queuedTheme, int currentThemeIndex, int numScaleNotes, int queuedTheme, int currentThemeIndex, int numScaleNotes,
const int* scaleNotes, int melodySeed, int numSteps, bool mutationEnabled, const int* scaleNotes, int melodySeed, int numSteps, bool mutationEnabled,
bool songModeEnabled, bool isPlaying, int randomizeTrack, const bool* trackMute) { bool songModeEnabled, bool isPlaying, int randomizeTrack, const bool* trackMute, const int* trackIntensities) {
// Calculate visual cursor position and scroll offset // Calculate visual cursor position and scroll offset
int visualCursor = 0; int visualCursor = 0;
@ -269,6 +297,18 @@ void UIManager::drawMenu(int selection, UIState currentState, int midiChannel, i
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_INTENSITY) {
display.print(F(": "));
display.print(trackIntensities[randomizeTrack]);
int val = trackIntensities[randomizeTrack];
int barX = display.getCursorX() + 3;
int barY = y + 2;
int maxW = 20;
int h = 5;
uint16_t color = (i == selection) ? SSD1306_BLACK : SSD1306_WHITE;
display.drawRect(barX, barY, maxW + 2, h, color);
display.fillRect(barX + 1, barY + 1, (val * maxW) / 10, h - 2, color);
}
else if (id == MENU_ID_MUTATION) { display.print(F(": ")); display.print(mutationEnabled ? F("ON") : F("OFF")); } else if (id == MENU_ID_MUTATION) { display.print(F(": ")); display.print(mutationEnabled ? F("ON") : F("OFF")); }
else if (id == MENU_ID_PROTECTED_MODE) { display.print(F(": ")); display.print(protectedMode ? F("ON") : F("OFF")); } else if (id == MENU_ID_PROTECTED_MODE) { display.print(F(": ")); display.print(protectedMode ? F("ON") : F("OFF")); }

View File

@ -20,7 +20,7 @@ public:
int numScaleNotes, const int* scaleNotes, int melodySeed, int numSteps, int numScaleNotes, const int* scaleNotes, int melodySeed, int numSteps,
bool mutationEnabled, bool songModeEnabled, bool mutationEnabled, bool songModeEnabled,
const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying, const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying,
int randomizeTrack, const bool* trackMute); int randomizeTrack, const bool* trackMute, const int* trackIntensities);
void updateLeds(const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying, void updateLeds(const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying,
UIState currentState, bool songModeEnabled, UIState currentState, bool songModeEnabled,
@ -35,7 +35,9 @@ private:
void drawMenu(int selection, UIState currentState, int midiChannel, int tempo, const char* flavourName, void drawMenu(int selection, UIState currentState, int midiChannel, int tempo, const char* flavourName,
int queuedTheme, int currentThemeIndex, int queuedTheme, int currentThemeIndex,
int numScaleNotes, const int* scaleNotes, int melodySeed, int numSteps, int numScaleNotes, const int* scaleNotes, int melodySeed, int numSteps,
bool mutationEnabled, bool songModeEnabled, bool isPlaying, int randomizeTrack, const bool* trackMute); bool mutationEnabled, bool songModeEnabled, bool isPlaying, int randomizeTrack, const bool* trackMute, const int* trackIntensities);
void drawNumberEditor(const char* title, int value, int minVal, int maxVal);
uint32_t getNoteColor(int note, bool dim); uint32_t getNoteColor(int note, bool dim);
int getPixelIndex(int x, int y); int getPixelIndex(int x, int y);

View File

@ -15,6 +15,8 @@
#include "UIThread.h" #include "UIThread.h"
#include "SharedState.h" #include "SharedState.h"
extern volatile int trackIntensity[NUM_TRACKS];
static Step local_sequence[NUM_TRACKS][NUM_STEPS]; static Step local_sequence[NUM_TRACKS][NUM_STEPS];
static void handleInput(); static void handleInput();
@ -40,6 +42,9 @@ void saveSequence(bool quiet) {
for(int i=0; i<NUM_TRACKS; i++) mutes[i] = trackMute[i]; for(int i=0; i<NUM_TRACKS; i++) mutes[i] = trackMute[i];
EEPROM.put(addr, mutes); addr += sizeof(mutes); EEPROM.put(addr, mutes); addr += sizeof(mutes);
EEPROM.put(addr, (int)tempo); addr += sizeof(int); EEPROM.put(addr, (int)tempo); addr += sizeof(int);
int intensities[NUM_TRACKS];
for(int i=0; i<NUM_TRACKS; i++) intensities[i] = trackIntensity[i];
EEPROM.put(addr, intensities); addr += sizeof(intensities);
EEPROM.put(addr, (int)numSteps); addr += sizeof(int); EEPROM.put(addr, (int)numSteps); addr += sizeof(int);
EEPROM.put(addr, numScaleNotes); addr += sizeof(numScaleNotes); EEPROM.put(addr, numScaleNotes); addr += sizeof(numScaleNotes);
@ -65,22 +70,41 @@ bool loadSequence() {
int channels[NUM_TRACKS]; int channels[NUM_TRACKS];
EEPROM.get(addr, channels); addr += sizeof(channels); EEPROM.get(addr, channels); addr += sizeof(channels);
for(int i=0; i<NUM_TRACKS; i++) midiChannels[i] = channels[i]; for(int i=0; i<NUM_TRACKS; i++) {
midiChannels[i] = channels[i];
if (midiChannels[i] < 1) midiChannels[i] = 1;
if (midiChannels[i] > 16) midiChannels[i] = 16;
}
EEPROM.get(addr, melodySeeds); addr += sizeof(melodySeeds); EEPROM.get(addr, melodySeeds); addr += sizeof(melodySeeds);
EEPROM.get(addr, currentStrategyIndices); addr += sizeof(currentStrategyIndices); EEPROM.get(addr, currentStrategyIndices); addr += sizeof(currentStrategyIndices);
for(int i=0; i<NUM_TRACKS; i++) {
if (currentStrategyIndices[i] < 0 || currentStrategyIndices[i] >= numStrategies) currentStrategyIndices[i] = 0;
}
bool mutes[NUM_TRACKS]; bool mutes[NUM_TRACKS];
EEPROM.get(addr, mutes); addr += sizeof(mutes); EEPROM.get(addr, mutes); addr += sizeof(mutes);
for(int i=0; i<NUM_TRACKS; i++) trackMute[i] = mutes[i]; for(int i=0; i<NUM_TRACKS; i++) trackMute[i] = mutes[i];
int t; int t;
EEPROM.get(addr, t); addr += sizeof(int); EEPROM.get(addr, t); addr += sizeof(int);
tempo = t; tempo = t;
if (tempo < 40) tempo = 40;
if (tempo > 240) tempo = 240;
int intensities[NUM_TRACKS];
EEPROM.get(addr, intensities); addr += sizeof(intensities);
for(int i=0; i<NUM_TRACKS; i++) {
trackIntensity[i] = intensities[i];
if (trackIntensity[i] < 1) trackIntensity[i] = 1;
if (trackIntensity[i] > 10) trackIntensity[i] = 10;
}
EEPROM.get(addr, t); addr += sizeof(int); EEPROM.get(addr, t); addr += sizeof(int);
numSteps = t; numSteps = t;
if (numSteps <= 0 || numSteps >= NUM_STEPS) numSteps = NUM_STEPS; if (numSteps <= 0 || numSteps >= NUM_STEPS) numSteps = NUM_STEPS;
EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes); EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes);
if (numScaleNotes < 0 || numScaleNotes > 12) numScaleNotes = 0;
for (int i = 0; i<12; i++) { for (int i = 0; i<12; i++) {
EEPROM.get(addr, scaleNotes[i]); addr += sizeof(int); EEPROM.get(addr, scaleNotes[i]); addr += sizeof(int);
if (scaleNotes[i] < 0) scaleNotes[i] = 0;
if (scaleNotes[i] > 11) scaleNotes[i] = 11;
} }
EEPROM.get(addr, sequence); addr += sizeof(sequence); EEPROM.get(addr, sequence); addr += sizeof(sequence);
@ -90,7 +114,7 @@ bool loadSequence() {
static void savePatch(int bank, int slot) { static void savePatch(int bank, int slot) {
int patchIndex = bank * 4 + slot; int patchIndex = bank * 4 + slot;
int addr = 512 + patchIndex * 128; // Start after main save, 128 bytes per patch int addr = 512 + patchIndex * 256; // Start after main save, 256 bytes per patch
midi.lock(); midi.lock();
EEPROM.put(addr, numScaleNotes); addr += sizeof(numScaleNotes); EEPROM.put(addr, numScaleNotes); addr += sizeof(numScaleNotes);
@ -103,6 +127,9 @@ static void savePatch(int bank, int slot) {
bool mutes[NUM_TRACKS]; bool mutes[NUM_TRACKS];
for(int i=0; i<NUM_TRACKS; i++) mutes[i] = trackMute[i]; for(int i=0; i<NUM_TRACKS; i++) mutes[i] = trackMute[i];
EEPROM.put(addr, mutes); addr += sizeof(mutes); EEPROM.put(addr, mutes); addr += sizeof(mutes);
int intensities[NUM_TRACKS];
for(int i=0; i<NUM_TRACKS; i++) intensities[i] = trackIntensity[i];
EEPROM.put(addr, intensities); addr += sizeof(intensities);
midi.unlock(); midi.unlock();
EEPROM.commit(); EEPROM.commit();
ui.showMessage("SAVED!"); ui.showMessage("SAVED!");
@ -110,14 +137,20 @@ static void savePatch(int bank, int slot) {
static void loadPatch(int bank, int slot) { static void loadPatch(int bank, int slot) {
int patchIndex = bank * 4 + slot; int patchIndex = bank * 4 + slot;
int addr = 512 + patchIndex * 128; int addr = 512 + patchIndex * 256;
midi.lock(); midi.lock();
EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes); EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes);
if (numScaleNotes < 0 || numScaleNotes > 12) numScaleNotes = 0;
for (int i = 0; i < 12; i++) { for (int i = 0; i < 12; i++) {
EEPROM.get(addr, scaleNotes[i]); addr += sizeof(int); EEPROM.get(addr, scaleNotes[i]); addr += sizeof(int);
if (scaleNotes[i] < 0) scaleNotes[i] = 0;
if (scaleNotes[i] > 11) scaleNotes[i] = 11;
} }
EEPROM.get(addr, currentStrategyIndices); addr += sizeof(currentStrategyIndices); EEPROM.get(addr, currentStrategyIndices); addr += sizeof(currentStrategyIndices);
for(int i=0; i<NUM_TRACKS; i++) {
if (currentStrategyIndices[i] < 0 || currentStrategyIndices[i] >= numStrategies) currentStrategyIndices[i] = 0;
}
EEPROM.get(addr, melodySeeds); addr += sizeof(melodySeeds); EEPROM.get(addr, melodySeeds); addr += sizeof(melodySeeds);
int t; int t;
EEPROM.get(addr, t); addr += sizeof(int); EEPROM.get(addr, t); addr += sizeof(int);
@ -127,6 +160,14 @@ static void loadPatch(int bank, int slot) {
bool mutes[NUM_TRACKS]; bool mutes[NUM_TRACKS];
EEPROM.get(addr, mutes); addr += sizeof(mutes); EEPROM.get(addr, mutes); addr += sizeof(mutes);
for(int i=0; i<NUM_TRACKS; i++) trackMute[i] = mutes[i]; for(int i=0; i<NUM_TRACKS; i++) trackMute[i] = mutes[i];
int intensities[NUM_TRACKS];
EEPROM.get(addr, intensities); addr += sizeof(intensities);
for(int i=0; i<NUM_TRACKS; i++) {
trackIntensity[i] = intensities[i];
if (trackIntensity[i] < 1) trackIntensity[i] = 1;
if (trackIntensity[i] > 10) trackIntensity[i] = 10;
}
if (isPlaying) { if (isPlaying) {
generateSequenceData(currentThemeIndex, nextSequence); generateSequenceData(currentThemeIndex, nextSequence);
@ -141,6 +182,9 @@ static void loadPatch(int bank, int slot) {
void factoryReset() { void factoryReset() {
ui.showMessage("RESETTING..."); ui.showMessage("RESETTING...");
for(int i=0; i<NUM_TRACKS; i++) {
trackIntensity[i] = 10;
}
uint32_t magic = 0; uint32_t magic = 0;
EEPROM.put(0, magic); EEPROM.put(0, magic);
EEPROM.commit(); EEPROM.commit();
@ -150,7 +194,7 @@ void factoryReset() {
static void generateTrackData(int track, int themeType, Step (*target)[NUM_STEPS]) { static void generateTrackData(int track, int themeType, Step (*target)[NUM_STEPS]) {
randomSeed(melodySeeds[track] + themeType * 12345); randomSeed(melodySeeds[track] + themeType * 12345);
strategies[currentStrategyIndices[track]]->generate(target, track, numSteps, scaleNotes, numScaleNotes, melodySeeds[track] + themeType * 12345); strategies[currentStrategyIndices[track]]->generate(target, track, numSteps, scaleNotes, numScaleNotes, melodySeeds[track] + themeType * 12345, trackIntensity[track]);
} }
void generateRandomScale() { void generateRandomScale() {
@ -184,7 +228,7 @@ void generateTheme(int themeType) {
} }
void mutateSequence(Step (*target)[NUM_STEPS]) { void mutateSequence(Step (*target)[NUM_STEPS]) {
for(int i=0; i<NUM_TRACKS; i++) strategies[currentStrategyIndices[i]]->mutate(target, i, numSteps, scaleNotes, numScaleNotes); for(int i=0; i<NUM_TRACKS; i++) strategies[currentStrategyIndices[i]]->mutate(target, i, numSteps, scaleNotes, numScaleNotes, trackIntensity[i]);
} }
static void handleInput() { static void handleInput() {
@ -234,6 +278,15 @@ static void handleInput() {
if (currentStrategyIndices[randomizeTrack] >= numStrategies) currentStrategyIndices[randomizeTrack] = 0; if (currentStrategyIndices[randomizeTrack] >= numStrategies) currentStrategyIndices[randomizeTrack] = 0;
} }
break; break;
case UI_EDIT_INTENSITY:
{
int current = trackIntensity[randomizeTrack];
current += delta;
if (current < 1) current = 1;
if (current > 10) current = 10;
trackIntensity[randomizeTrack] = current;
}
break;
case UI_SCALE_EDIT: case UI_SCALE_EDIT:
scaleEditSelection += (delta > 0 ? 1 : -1); scaleEditSelection += (delta > 0 ? 1 : -1);
if (scaleEditSelection < 0) scaleEditSelection = numScaleNotes + 4; if (scaleEditSelection < 0) scaleEditSelection = numScaleNotes + 4;
@ -346,6 +399,7 @@ static void handleInput() {
case MENU_ID_TRACK_SELECT: currentState = UI_RANDOMIZE_TRACK_EDIT; break; case MENU_ID_TRACK_SELECT: currentState = UI_RANDOMIZE_TRACK_EDIT; break;
case MENU_ID_MUTE: trackMute[randomizeTrack] = !trackMute[randomizeTrack]; break; case MENU_ID_MUTE: trackMute[randomizeTrack] = !trackMute[randomizeTrack]; break;
case MENU_ID_FLAVOUR: currentState = UI_EDIT_FLAVOUR; break; case MENU_ID_FLAVOUR: currentState = UI_EDIT_FLAVOUR; break;
case MENU_ID_INTENSITY: currentState = UI_EDIT_INTENSITY; break;
case MENU_ID_MUTATION: mutationEnabled = !mutationEnabled; break; case MENU_ID_MUTATION: mutationEnabled = !mutationEnabled; break;
case MENU_ID_CHANNEL: currentState = UI_SETUP_CHANNEL_EDIT; break; case MENU_ID_CHANNEL: currentState = UI_SETUP_CHANNEL_EDIT; break;
@ -405,6 +459,20 @@ static void handleInput() {
} }
saveSequence(true); saveSequence(true);
break; break;
case UI_EDIT_INTENSITY:
currentState = UI_MENU_MAIN;
if (isPlaying) {
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
midi.lock();
if (!sequenceChangeScheduled) {
memcpy(nextSequence, sequence, sizeof(sequence));
}
generateTrackData(randomizeTrack, theme, nextSequence);
sequenceChangeScheduled = true;
midi.unlock();
}
saveSequence(true);
break;
case UI_RANDOMIZE_TRACK_EDIT: case UI_RANDOMIZE_TRACK_EDIT:
currentState = UI_MENU_MAIN; currentState = UI_MENU_MAIN;
saveSequence(true); saveSequence(true);
@ -505,6 +573,7 @@ static void drawUI() {
bool local_mutationEnabled, local_songModeEnabled, local_isPlaying; bool local_mutationEnabled, local_songModeEnabled, local_isPlaying;
bool local_trackMute[NUM_TRACKS]; bool local_trackMute[NUM_TRACKS];
int local_midiChannel; int local_midiChannel;
int local_trackIntensities[NUM_TRACKS];
MelodyStrategy* local_strategy; MelodyStrategy* local_strategy;
int local_playbackStep; int local_playbackStep;
int local_scaleNotes[12]; int local_scaleNotes[12];
@ -533,12 +602,13 @@ static void drawUI() {
local_playbackStep = playbackStep; local_playbackStep = playbackStep;
local_isPlaying = isPlaying; local_isPlaying = isPlaying;
memcpy(local_trackMute, (const void*)trackMute, sizeof(local_trackMute)); memcpy(local_trackMute, (const void*)trackMute, sizeof(local_trackMute));
memcpy(local_trackIntensities, (const void*)trackIntensity, sizeof(local_trackIntensities));
midi.unlock(); midi.unlock();
ui.draw(local_currentState, local_menuSelection, ui.draw(local_currentState, local_menuSelection,
local_midiChannel, local_tempo, local_strategy, local_midiChannel, local_tempo, local_strategy,
local_queuedTheme, local_currentThemeIndex, local_numScaleNotes, local_scaleNotes, local_melodySeed, local_numSteps, local_queuedTheme, local_currentThemeIndex, local_numScaleNotes, local_scaleNotes, local_melodySeed, local_numSteps,
local_mutationEnabled, local_songModeEnabled, (const Step (*)[NUM_STEPS])local_sequence, local_playbackStep, local_isPlaying, local_randomizeTrack, (const bool*)local_trackMute); local_mutationEnabled, local_songModeEnabled, (const Step (*)[NUM_STEPS])local_sequence, local_playbackStep, local_isPlaying, local_randomizeTrack, (const bool*)local_trackMute, (const int*)local_trackIntensities);
} }
static void updateLeds() { static void updateLeds() {