Compare commits

..

No commits in common. "4e4ba0d0e44619cc0dee59052de6e70a8bc2efb3" and "e6a471186149aa91e50357c933d0f38d70fa6c4a" have entirely different histories.

18 changed files with 50 additions and 354 deletions

View File

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

View File

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

View File

@ -1,99 +0,0 @@
#ifndef DRONE_STRATEGY_H
#define DRONE_STRATEGY_H
#include "MelodyStrategy.h"
#include <Arduino.h>
class DroneStrategy : public MelodyStrategy {
public:
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed, int intensity) override {
randomSeed(seed);
if (numScaleNotes == 0) return;
// Intensity 1: Very long notes (1-2 per sequence)
// Intensity 10: Short notes (active)
// Map intensity to duration range
// Low intensity (1) -> Max duration is full sequence
// High intensity (10) -> Max duration is short (e.g. 4 steps)
int maxDur = map(intensity, 1, 10, numSteps, 4);
int minDur = map(intensity, 1, 10, numSteps, 1);
if (minDur < 1) minDur = 1;
if (maxDur < minDur) maxDur = minDur;
int currentStep = 0;
while (currentStep < numSteps) {
int duration = random(minDur, maxDur + 1);
// Pick a note from the scale
// Prefer lower octaves for drones (3 and 4)
int octave = 3 + random(2);
int noteIndex = random(numScaleNotes);
int note = 12 * octave + scaleNotes[noteIndex];
for (int k = 0; k < duration; k++) {
int stepIndex = currentStep + k;
if (stepIndex >= numSteps) break;
sequence[track][stepIndex].note = note;
// Tie notes to create continuous sound (legato)
sequence[track][stepIndex].tie = true;
// Accent only the start of the note
sequence[track][stepIndex].accent = (k == 0);
}
currentStep += duration;
}
randomSeed(micros());
}
void generateScale(int* scaleNotes, int& numScaleNotes) override {
// Drones work well with fewer notes (Pentatonic or Triad)
numScaleNotes = random(3, 6);
for (int i = 0; i < 12; i++) {
scaleNotes[i] = i;
}
// 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)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int intensity) override {
// Mutate by shifting the pitch of a random segment
int s = random(numSteps);
if (sequence[track][s].note != -1) {
int originalNote = sequence[track][s].note;
// Find the bounds of this note
int start = s;
while (start > 0 && sequence[track][start-1].note == originalNote && sequence[track][start-1].tie) {
start--;
}
int end = s;
while (end < numSteps - 1 && sequence[track][end].tie && sequence[track][end+1].note == originalNote) {
end++;
}
// Pick a new note
int octave = 3 + random(2);
int newNote = 12 * octave + scaleNotes[random(numScaleNotes)];
for (int i = start; i <= end; i++) {
sequence[track][i].note = newNote;
}
}
}
const char* getName() override {
return "Drone";
}
};
#endif

View File

@ -6,19 +6,11 @@
class EuclideanStrategy : public MelodyStrategy {
public:
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed, int intensity) override {
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed) override {
randomSeed(seed);
if (numScaleNotes == 0) return;
// 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 pulses = random(1, numSteps + 1);
int offset = random(numSteps);
// Euclidean distribution (Bresenham)
@ -69,7 +61,7 @@ public:
sortArray(scaleNotes, numScaleNotes);
}
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int intensity) override {
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes) override {
// Rotate sequence
if (random(2) == 0) {
Step last = sequence[track][numSteps - 1];

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,4 @@
#include "MidiDriver.h"
#include "config.h"
// MIDI UART Pins (GP0/GP1)
#define PIN_MIDI_TX 0
@ -8,7 +7,6 @@
MidiDriver midi;
MidiDriver::MidiDriver() : lastInputNote(-1), lastInputVelocity(0), _runningStatus(0), _byteIndex(0), _data1(0), _data2(0) {
memset(activeNotes, 0, sizeof(activeNotes));
}
void MidiDriver::begin() {
@ -90,20 +88,6 @@ void MidiDriver::unlock() {
}
void MidiDriver::sendNoteOn(uint8_t note, uint8_t velocity, uint8_t channel) {
#ifdef MIDI_DEBUG
Serial.print(F("["));
Serial.print(millis());
Serial.print(F("] "));
Serial.print(F("MIDI OUT: Note On CH:"));
Serial.print(channel);
Serial.print(F(" Note:"));
Serial.print(note);
Serial.print(F(" Vel:"));
Serial.println(velocity);
#endif
if (channel >= 1 && channel <= 16 && note < 128) {
activeNotes[channel - 1][note] = (velocity > 0);
}
uint8_t status = 0x90 | (channel - 1);
Serial1.write(status);
Serial1.write(note);
@ -111,18 +95,6 @@ void MidiDriver::sendNoteOn(uint8_t note, uint8_t velocity, uint8_t channel) {
}
void MidiDriver::sendNoteOff(uint8_t note, uint8_t channel) {
#ifdef MIDI_DEBUG
Serial.print(F("["));
Serial.print(millis());
Serial.print(F("] "));
Serial.print(F("MIDI OUT: Note Off CH:"));
Serial.print(channel);
Serial.print(F(" Note:"));
Serial.println(note);
#endif
if (channel >= 1 && channel <= 16 && note < 128) {
activeNotes[channel - 1][note] = false;
}
uint8_t status = 0x80 | (channel - 1);
Serial1.write(status);
Serial1.write(note);
@ -130,33 +102,10 @@ void MidiDriver::sendNoteOff(uint8_t note, uint8_t channel) {
}
void MidiDriver::sendRealtime(uint8_t status) {
#ifdef MIDI_DEBUG
if (status != 0xF8) {
Serial.print(F("["));
Serial.print(millis());
Serial.print(F("] "));
Serial.print(F("MIDI OUT: Realtime 0x"));
Serial.println(status, HEX);
}
#endif
Serial1.write(status);
}
void MidiDriver::panic(uint8_t channel) {
#ifdef MIDI_DEBUG
Serial.print(F("["));
Serial.print(millis());
Serial.print(F("] "));
Serial.print(F("MIDI OUT: Panic CH:"));
Serial.println(channel);
#endif
if (channel >= 1 && channel <= 16) {
for (int i = 0; i < 128; i++) {
if (activeNotes[channel - 1][i]) {
sendNoteOff(i, channel);
}
}
}
uint8_t status = 0xB0 | (channel - 1);
Serial1.write(status);
Serial1.write((uint8_t)123); // All Notes Off

View File

@ -27,7 +27,6 @@ private:
uint8_t _byteIndex;
uint8_t _data1;
uint8_t _data2;
bool activeNotes[16][128];
};
extern MidiDriver midi;

View File

@ -60,13 +60,9 @@ static void handlePlayback() {
}
playbackStep++;
bool justPanicked = false;
if (playbackStep >= numSteps) {
playbackStep = 0;
for (int i=0; i<NUM_TRACKS; i++) midi.panic(midiChannels[i]);
justPanicked = true;
// Theme change
if (sequenceChangeScheduled && queuedTheme != -1) {
currentThemeIndex = queuedTheme;
@ -86,6 +82,8 @@ static void handlePlayback() {
midi.unlock();
}
for (int i=0; i<NUM_TRACKS; i++) midi.panic(midiChannels[i]);
if (sequenceChangeScheduled) {
memcpy(local_sequence, local_nextSequence, sizeof(local_sequence));
midi.lock();
@ -114,21 +112,16 @@ static void handlePlayback() {
// Note On for new step
for(int t=0; t<NUM_TRACKS; t++) {
int trackChannel = midiChannels[t];
if (!trackMute[t] && local_sequence[t][playbackStep].note != -1) {
uint8_t velocity = local_sequence[t][playbackStep].accent ? 127 : 100;
midi.sendNoteOn(local_sequence[t][playbackStep].note, velocity, trackChannel);
}
int prevStep = (playbackStep == 0) ? numSteps - 1 : playbackStep - 1;
bool wasTied = local_sequence[t][prevStep].tie && (local_sequence[t][playbackStep].note != -1);
int prevNote = local_sequence[t][prevStep].note;
int currentNote = local_sequence[t][playbackStep].note;
// If tied to the SAME note, do not retrigger (sustain)
bool isContinuing = wasTied && (prevNote == currentNote) && !justPanicked;
if (!trackMute[t] && currentNote != -1 && !isContinuing) {
uint8_t velocity = local_sequence[t][playbackStep].accent ? 127 : 100;
midi.sendNoteOn(currentNote, velocity, trackChannel);
}
// Note Off for previous step (if tied - delayed Note Off)
if (wasTied && prevNote != -1 && !isContinuing && !justPanicked) {
if (wasTied && prevNote != -1) {
midi.sendNoteOff(prevNote, trackChannel);
}
}

View File

@ -5,7 +5,6 @@
#include "MarkovStrategy.h"
#include "CellularAutomataStrategy.h"
#include "LSystemStrategy.h"
#include "DroneStrategy.h"
// Global state variables
Step sequence[NUM_TRACKS][NUM_STEPS];
@ -16,7 +15,6 @@ volatile bool needsPanic = false;
UIState currentState = UI_MENU_MAIN;
bool protectedMode = false;
volatile int trackIntensity[NUM_TRACKS] = {10, 10, 10, 10};
// Menus
MenuItem menuItems[] = {
{ "Main", MENU_ID_GROUP_MAIN, true, true, 0 },
@ -30,7 +28,6 @@ MenuItem menuItems[] = {
{ "Track", MENU_ID_TRACK_SELECT, false, false, 1 },
{ "Mute", MENU_ID_MUTE, false, false, 1 },
{ "Flavour", MENU_ID_FLAVOUR, false, false, 1 },
{ "Intensity", MENU_ID_INTENSITY, false, false, 1 },
{ "Mutation", MENU_ID_MUTATION, false, false, 1 },
{ "Theme 1", MENU_ID_THEME_1, false, false, 1 },
{ "Theme 2", MENU_ID_THEME_2, false, false, 1 },
@ -112,12 +109,12 @@ int numScaleNotes = 0;
int melodySeeds[NUM_TRACKS];
volatile int queuedTheme = -1;
volatile int currentThemeIndex = 1;
extern const uint32_t EEPROM_MAGIC = 0x4242424E;
extern const uint32_t EEPROM_MAGIC = 0x4242424D;
MelodyStrategy* strategies[] = {
new LuckyStrategy(), new ArpStrategy(), new EuclideanStrategy(),
new MarkovStrategy(), new CellularAutomataStrategy(), new LSystemStrategy(), new DroneStrategy()};
extern const int numStrategies = 7;
new MarkovStrategy(), new CellularAutomataStrategy(), new LSystemStrategy()};
extern const int numStrategies = 6;
int currentStrategyIndices[NUM_TRACKS];
volatile PlayMode playMode = MODE_POLY;

View File

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

View File

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

View File

@ -50,10 +50,10 @@ void UIManager::showMessage(const char* msg) {
void UIManager::draw(UIState currentState, int menuSelection,
int midiChannel, int tempo, MelodyStrategy* currentStrategy,
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,
const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying,
int randomizeTrack, const bool* trackMute, const int* trackIntensities) {
int randomizeTrack, const bool* trackMute) {
display.clearDisplay();
display.setTextSize(1);
@ -62,7 +62,7 @@ void UIManager::draw(UIState currentState, int menuSelection,
switch(currentState) {
case UI_MENU_MAIN:
drawMenu(menuSelection, currentState, midiChannel, tempo, currentStrategy->getName(), queuedTheme, currentThemeIndex, numScaleNotes, scaleNotes, melodySeed, numSteps, mutationEnabled, songModeEnabled, isPlaying, randomizeTrack, trackMute, trackIntensities);
drawMenu(menuSelection, currentState, midiChannel, tempo, currentStrategy->getName(), queuedTheme, currentThemeIndex, numScaleNotes, scaleNotes, melodySeed, numSteps, mutationEnabled, songModeEnabled, isPlaying, randomizeTrack, trackMute);
break;
case UI_SETUP_CHANNEL_EDIT:
display.println(F("SET MIDI CHANNEL"));
@ -108,9 +108,6 @@ void UIManager::draw(UIState currentState, int menuSelection,
display.setCursor(0, 50);
display.println(F(" (Press to confirm)"));
break;
case UI_EDIT_INTENSITY:
drawNumberEditor("SET INTENSITY", trackIntensities[randomizeTrack], 1, 10);
break;
case UI_RANDOMIZE_TRACK_EDIT:
display.println(F("SET TRACK"));
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
@ -205,35 +202,10 @@ void UIManager::draw(UIState currentState, int menuSelection,
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,
int queuedTheme, int currentThemeIndex, int numScaleNotes,
const int* scaleNotes, int melodySeed, int numSteps, bool mutationEnabled,
bool songModeEnabled, bool isPlaying, int randomizeTrack, const bool* trackMute, const int* trackIntensities) {
bool songModeEnabled, bool isPlaying, int randomizeTrack, const bool* trackMute) {
// Calculate visual cursor position and scroll offset
int visualCursor = 0;
@ -297,18 +269,6 @@ 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_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_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_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,
bool mutationEnabled, bool songModeEnabled,
const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying,
int randomizeTrack, const bool* trackMute, const int* trackIntensities);
int randomizeTrack, const bool* trackMute);
void updateLeds(const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying,
UIState currentState, bool songModeEnabled,
@ -35,9 +35,7 @@ private:
void drawMenu(int selection, UIState currentState, int midiChannel, int tempo, const char* flavourName,
int queuedTheme, int currentThemeIndex,
int numScaleNotes, const int* scaleNotes, int melodySeed, int numSteps,
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);
bool mutationEnabled, bool songModeEnabled, bool isPlaying, int randomizeTrack, const bool* trackMute);
uint32_t getNoteColor(int note, bool dim);
int getPixelIndex(int x, int y);

View File

@ -9,15 +9,12 @@
#include "MarkovStrategy.h"
#include "CellularAutomataStrategy.h"
#include "LSystemStrategy.h"
#include "DroneStrategy.h"
#include "MidiDriver.h"
#include "UIManager.h"
#include "config.h"
#include "UIThread.h"
#include "SharedState.h"
extern volatile int trackIntensity[NUM_TRACKS];
static Step local_sequence[NUM_TRACKS][NUM_STEPS];
static void handleInput();
@ -43,9 +40,6 @@ void saveSequence(bool quiet) {
for(int i=0; i<NUM_TRACKS; i++) mutes[i] = trackMute[i];
EEPROM.put(addr, mutes); addr += sizeof(mutes);
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, numScaleNotes); addr += sizeof(numScaleNotes);
@ -71,41 +65,22 @@ bool loadSequence() {
int channels[NUM_TRACKS];
EEPROM.get(addr, channels); addr += sizeof(channels);
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;
}
for(int i=0; i<NUM_TRACKS; i++) midiChannels[i] = channels[i];
EEPROM.get(addr, melodySeeds); addr += sizeof(melodySeeds);
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];
EEPROM.get(addr, mutes); addr += sizeof(mutes);
for(int i=0; i<NUM_TRACKS; i++) trackMute[i] = mutes[i];
int t;
EEPROM.get(addr, t); addr += sizeof(int);
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);
numSteps = t;
if (numSteps <= 0 || numSteps >= NUM_STEPS) numSteps = NUM_STEPS;
EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes);
if (numScaleNotes < 0 || numScaleNotes > 12) numScaleNotes = 0;
for (int i = 0; i<12; i++) {
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);
@ -115,7 +90,7 @@ bool loadSequence() {
static void savePatch(int bank, int slot) {
int patchIndex = bank * 4 + slot;
int addr = 512 + patchIndex * 256; // Start after main save, 256 bytes per patch
int addr = 512 + patchIndex * 128; // Start after main save, 128 bytes per patch
midi.lock();
EEPROM.put(addr, numScaleNotes); addr += sizeof(numScaleNotes);
@ -128,9 +103,6 @@ static void savePatch(int bank, int slot) {
bool mutes[NUM_TRACKS];
for(int i=0; i<NUM_TRACKS; i++) mutes[i] = trackMute[i];
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();
EEPROM.commit();
ui.showMessage("SAVED!");
@ -138,20 +110,14 @@ static void savePatch(int bank, int slot) {
static void loadPatch(int bank, int slot) {
int patchIndex = bank * 4 + slot;
int addr = 512 + patchIndex * 256;
int addr = 512 + patchIndex * 128;
midi.lock();
EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes);
if (numScaleNotes < 0 || numScaleNotes > 12) numScaleNotes = 0;
for (int i = 0; i < 12; i++) {
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);
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);
int t;
EEPROM.get(addr, t); addr += sizeof(int);
@ -161,14 +127,6 @@ static void loadPatch(int bank, int slot) {
bool mutes[NUM_TRACKS];
EEPROM.get(addr, mutes); addr += sizeof(mutes);
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) {
generateSequenceData(currentThemeIndex, nextSequence);
@ -183,9 +141,6 @@ static void loadPatch(int bank, int slot) {
void factoryReset() {
ui.showMessage("RESETTING...");
for(int i=0; i<NUM_TRACKS; i++) {
trackIntensity[i] = 10;
}
uint32_t magic = 0;
EEPROM.put(0, magic);
EEPROM.commit();
@ -195,7 +150,7 @@ void factoryReset() {
static void generateTrackData(int track, int themeType, Step (*target)[NUM_STEPS]) {
randomSeed(melodySeeds[track] + themeType * 12345);
strategies[currentStrategyIndices[track]]->generate(target, track, numSteps, scaleNotes, numScaleNotes, melodySeeds[track] + themeType * 12345, trackIntensity[track]);
strategies[currentStrategyIndices[track]]->generate(target, track, numSteps, scaleNotes, numScaleNotes, melodySeeds[track] + themeType * 12345);
}
void generateRandomScale() {
@ -229,7 +184,7 @@ void generateTheme(int themeType) {
}
void mutateSequence(Step (*target)[NUM_STEPS]) {
for(int i=0; i<NUM_TRACKS; i++) strategies[currentStrategyIndices[i]]->mutate(target, i, numSteps, scaleNotes, numScaleNotes, trackIntensity[i]);
for(int i=0; i<NUM_TRACKS; i++) strategies[currentStrategyIndices[i]]->mutate(target, i, numSteps, scaleNotes, numScaleNotes);
}
static void handleInput() {
@ -279,15 +234,6 @@ static void handleInput() {
if (currentStrategyIndices[randomizeTrack] >= numStrategies) currentStrategyIndices[randomizeTrack] = 0;
}
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:
scaleEditSelection += (delta > 0 ? 1 : -1);
if (scaleEditSelection < 0) scaleEditSelection = numScaleNotes + 4;
@ -400,7 +346,6 @@ static void handleInput() {
case MENU_ID_TRACK_SELECT: currentState = UI_RANDOMIZE_TRACK_EDIT; break;
case MENU_ID_MUTE: trackMute[randomizeTrack] = !trackMute[randomizeTrack]; 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_CHANNEL: currentState = UI_SETUP_CHANNEL_EDIT; break;
@ -460,20 +405,6 @@ static void handleInput() {
}
saveSequence(true);
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:
currentState = UI_MENU_MAIN;
saveSequence(true);
@ -574,7 +505,6 @@ static void drawUI() {
bool local_mutationEnabled, local_songModeEnabled, local_isPlaying;
bool local_trackMute[NUM_TRACKS];
int local_midiChannel;
int local_trackIntensities[NUM_TRACKS];
MelodyStrategy* local_strategy;
int local_playbackStep;
int local_scaleNotes[12];
@ -603,13 +533,12 @@ static void drawUI() {
local_playbackStep = playbackStep;
local_isPlaying = isPlaying;
memcpy(local_trackMute, (const void*)trackMute, sizeof(local_trackMute));
memcpy(local_trackIntensities, (const void*)trackIntensity, sizeof(local_trackIntensities));
midi.unlock();
ui.draw(local_currentState, local_menuSelection,
local_midiChannel, local_tempo, local_strategy,
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, (const int*)local_trackIntensities);
local_mutationEnabled, local_songModeEnabled, (const Step (*)[NUM_STEPS])local_sequence, local_playbackStep, local_isPlaying, local_randomizeTrack, (const bool*)local_trackMute);
}
static void updateLeds() {

View File

@ -19,6 +19,4 @@
#define NUM_STEPS 8
#define NUM_TRACKS 4
#define MIDI_DEBUG
#endif