#ifndef LSYSTEM_STRATEGY_H #define LSYSTEM_STRATEGY_H #include "MelodyStrategy.h" #include 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