diff --git a/CellularAutomataStrategy.h b/CellularAutomataStrategy.h new file mode 100644 index 0000000..54bfd53 --- /dev/null +++ b/CellularAutomataStrategy.h @@ -0,0 +1,116 @@ +#ifndef CELLULAR_AUTOMATA_STRATEGY_H +#define CELLULAR_AUTOMATA_STRATEGY_H + +#include "MelodyStrategy.h" +#include + +class CellularAutomataStrategy : public MelodyStrategy { +public: + void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed) override { + randomSeed(seed); + if (numScaleNotes == 0) return; + + // 1. Setup CA + // Pick a rule. Some rules are more musical (structured chaos) than others. + // Rule 30, 90, 110, 184 are classics. Random is fun too. + uint8_t rule = random(256); + + bool cells[NUM_STEPS]; + bool next_cells[NUM_STEPS]; + + // Init: 50% chance of single center seed, 50% random noise + if (random(2) == 0) { + for(int i=0; i> pattern) & 1; + } + for(int i=0; i> pattern) & 1; + + if (alive) { + if (center) { + // Survived: Keep note + next_seq[i] = sequence[track][i]; + } else { + // Born: New note + int octave = 3 + random(3); + next_seq[i].note = 12 * octave + scaleNotes[random(numScaleNotes)]; + next_seq[i].accent = (random(100) < 30); + next_seq[i].tie = false; + } + } else { + // Died + next_seq[i].note = -1; + next_seq[i].accent = false; + next_seq[i].tie = false; + } + } + + for(int i=0; i + +struct Rule { + char from; + const char* to; +}; + +struct RuleSet { + const char* axiom; + const Rule* rules; + uint8_t numRules; +}; + +// --- L-System Rule Definitions --- +const Rule rules1[] = { {'A', "B-A-B"}, {'B', "A+B+A"} }; +const Rule rules2[] = { {'F', "F+F-F-F+F"} }; +const Rule rules3[] = { {'X', "F+[[X]-X]-F[-FX]+X"}, {'F', "FF"} }; + +const RuleSet ruleSets[] = { + {"A", rules1, 2}, // Sierpinski triangle + {"F", rules2, 1}, // Koch curve + {"X", rules3, 2} // Fractal plant +}; +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) override { + randomSeed(seed); + if (numScaleNotes == 0) { + // Fill with silence if no scale + for (int i = 0; i < numSteps; i++) { + sequence[track][i].note = -1; + sequence[track][i].accent = false; + sequence[track][i].tie = false; + } + return; + } + + // 1. Select a rule set and expand the axiom + const RuleSet& selectedRuleSet = ruleSets[random(numRuleSets)]; + String currentString = selectedRuleSet.axiom; + int iterations = random(2, 5); // 2-4 iterations + + for (int i = 0; i < iterations; i++) { + String nextString = ""; + for (char c : currentString) { + bool replaced = false; + for (int j = 0; j < selectedRuleSet.numRules; j++) { + if (c == selectedRuleSet.rules[j].from) { + nextString += selectedRuleSet.rules[j].to; + replaced = true; + break; + } + } + if (!replaced) { + nextString += c; + } + if (nextString.length() > 256) break; // Memory safety + } + currentString = nextString; + if (currentString.length() > 256) break; + } + + // 2. Interpret the string to create the sequence + int stepIndex = 0; + int noteIndex = random(numScaleNotes); + int octave = 4; + + // Stack for branching rules + int note_stack[8]; + int octave_stack[8]; + int stack_ptr = 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) < 25); + sequence[track][stepIndex].tie = (random(100) < 10); + stepIndex++; + break; + case '+': // Go up in scale + noteIndex = (noteIndex + 1) % numScaleNotes; + break; + case '-': // Go down in scale + noteIndex = (noteIndex - 1 + numScaleNotes) % numScaleNotes; + break; + case '[': // Push current state + if(stack_ptr < 8) { + note_stack[stack_ptr] = noteIndex; + octave_stack[stack_ptr] = octave; + stack_ptr++; + } + break; + case ']': // Pop state + if(stack_ptr > 0) { + stack_ptr--; + noteIndex = note_stack[stack_ptr]; + octave = octave_stack[stack_ptr]; + } + break; + // Any other characters (like 'X' in rule 3) are ignored for drawing + } + } + + // 3. Fill any remaining steps with rests + for (int i = 0; i < numSteps; i++) { + if (i >= stepIndex) { + sequence[track][i].note = -1; + sequence[track][i].accent = false; + sequence[track][i].tie = false; + } + } + randomSeed(micros()); + } + + void generateScale(int* scaleNotes, int& numScaleNotes) override { + numScaleNotes = random(5, 8); + for (int i = 0; i < 12; i++) { + scaleNotes[i] = i; + } + 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) override { + // Swap two non-rest steps to create a variation + int s1 = random(numSteps); + int s2 = random(numSteps); + int attempts = 0; + + // Try to find two different, non-rest steps + while((sequence[track][s1].note == -1 || sequence[track][s2].note == -1 || s1 == s2) && attempts < 20) { + s1 = random(numSteps); + s2 = random(numSteps); + attempts++; + } + + if (sequence[track][s1].note != -1 && sequence[track][s2].note != -1) { + Step temp = sequence[track][s1]; + sequence[track][s1] = sequence[track][s2]; + sequence[track][s2] = temp; + } + } + + const char* getName() override { + return "L-System"; + } +}; + +#endif \ No newline at end of file diff --git a/MarkovStrategy.h b/MarkovStrategy.h new file mode 100644 index 0000000..65adcc3 --- /dev/null +++ b/MarkovStrategy.h @@ -0,0 +1,126 @@ +#ifndef MARKOV_STRATEGY_H +#define MARKOV_STRATEGY_H + +#include "MelodyStrategy.h" +#include + +class MarkovStrategy : public MelodyStrategy { +public: + void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed) override { + randomSeed(seed); + if (numScaleNotes == 0) return; + + // Transition matrix: weights for moving from index i to index j + // Max scale size is 12 + uint8_t matrix[12][12]; + + // Initialize matrix with weighted probabilities + for (int i = 0; i < numScaleNotes; i++) { + for (int j = 0; j < numScaleNotes; j++) { + // Base random weight + matrix[i][j] = random(1, 8); + + // Musical heuristics: favor small intervals + int dist = abs(i - j); + if (dist == 0) matrix[i][j] += 8; // Repeat note + else if (dist == 1) matrix[i][j] += 12; // Stepwise motion + else if (dist == 2) matrix[i][j] += 6; // Thirds + // Large jumps remain low probability + } + } + + int currentIdx = random(numScaleNotes); + + for (int i = 0; i < numSteps; i++) { + // 20% chance of rest + if (random(100) < 20) { + sequence[track][i].note = -1; + sequence[track][i].accent = false; + sequence[track][i].tie = false; + continue; + } + + // Select next note based on matrix + int totalWeight = 0; + for (int j = 0; j < numScaleNotes; j++) { + totalWeight += matrix[currentIdx][j]; + } + + int r = random(totalWeight); + int nextIdx = 0; + for (int j = 0; j < numScaleNotes; j++) { + r -= matrix[currentIdx][j]; + if (r < 0) { + nextIdx = j; + break; + } + } + currentIdx = nextIdx; + + // Determine Octave (weighted towards middle) + int octave = 4; + int rOct = random(100); + if (rOct < 20) octave = 3; + else if (rOct > 80) octave = 5; + + sequence[track][i].note = 12 * octave + scaleNotes[currentIdx]; + sequence[track][i].accent = (random(100) < 25); + sequence[track][i].tie = (random(100) < 15); + } + randomSeed(micros()); + } + + void generateScale(int* scaleNotes, int& numScaleNotes) override { + numScaleNotes = random(5, 8); // Pentatonic to Major + for (int i = 0; i < 12; i++) { + scaleNotes[i] = i; // Fill with all notes + } + // Shuffle + for (int i = 0; i < 12; i++) { + int j = random(12); + int temp = scaleNotes[i]; + scaleNotes[i] = scaleNotes[j]; + scaleNotes[j] = temp; + } + sortArray(scaleNotes, numScaleNotes); + } + + void mutate(Step (*sequence)[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) { + int currentNoteVal = sequence[track][s].note % 12; + int idx = -1; + // Find index in scale + for(int i=0; i