More strategies
This commit is contained in:
parent
da30471f31
commit
07c30d20ef
116
CellularAutomataStrategy.h
Normal file
116
CellularAutomataStrategy.h
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
#ifndef CELLULAR_AUTOMATA_STRATEGY_H
|
||||||
|
#define CELLULAR_AUTOMATA_STRATEGY_H
|
||||||
|
|
||||||
|
#include "MelodyStrategy.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
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<numSteps; i++) cells[i] = false;
|
||||||
|
cells[numSteps/2] = true;
|
||||||
|
} else {
|
||||||
|
for(int i=0; i<numSteps; i++) cells[i] = (random(100) < 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evolve for some generations to let patterns emerge
|
||||||
|
int generations = numSteps + random(16);
|
||||||
|
|
||||||
|
for (int g = 0; g < generations; g++) {
|
||||||
|
for (int i = 0; i < numSteps; i++) {
|
||||||
|
bool left = cells[(i - 1 + numSteps) % numSteps];
|
||||||
|
bool center = cells[i];
|
||||||
|
bool right = cells[(i + 1) % numSteps];
|
||||||
|
|
||||||
|
uint8_t pattern = (left ? 4 : 0) | (center ? 2 : 0) | (right ? 1 : 0);
|
||||||
|
next_cells[i] = (rule >> pattern) & 1;
|
||||||
|
}
|
||||||
|
for(int i=0; i<numSteps; i++) cells[i] = next_cells[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map to notes
|
||||||
|
for (int i = 0; i < numSteps; i++) {
|
||||||
|
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);
|
||||||
|
sequence[track][i].tie = (random(100) < 15);
|
||||||
|
} else {
|
||||||
|
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 {
|
||||||
|
// Evolve the current sequence by one generation
|
||||||
|
// Use a random rule for mutation to keep it dynamic
|
||||||
|
uint8_t rule = random(256);
|
||||||
|
|
||||||
|
Step next_seq[NUM_STEPS];
|
||||||
|
|
||||||
|
for (int i = 0; i < numSteps; i++) {
|
||||||
|
bool left = (sequence[track][(i - 1 + numSteps) % numSteps].note != -1);
|
||||||
|
bool center = (sequence[track][i].note != -1);
|
||||||
|
bool right = (sequence[track][(i + 1) % numSteps].note != -1);
|
||||||
|
|
||||||
|
uint8_t pattern = (left ? 4 : 0) | (center ? 2 : 0) | (right ? 1 : 0);
|
||||||
|
bool alive = (rule >> 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<numSteps; i++) sequence[track][i] = next_seq[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* getName() override {
|
||||||
|
return "Cellular";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
163
LSystemStrategy.h
Normal file
163
LSystemStrategy.h
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
#ifndef LSYSTEM_STRATEGY_H
|
||||||
|
#define LSYSTEM_STRATEGY_H
|
||||||
|
|
||||||
|
#include "MelodyStrategy.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
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
|
||||||
126
MarkovStrategy.h
Normal file
126
MarkovStrategy.h
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
#ifndef MARKOV_STRATEGY_H
|
||||||
|
#define MARKOV_STRATEGY_H
|
||||||
|
|
||||||
|
#include "MelodyStrategy.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
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<numScaleNotes; i++) {
|
||||||
|
if (scaleNotes[i] == currentNoteVal) {
|
||||||
|
idx = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx != -1) {
|
||||||
|
// Move up or down 1 step in scale
|
||||||
|
if (random(2) == 0) idx = (idx + 1) % numScaleNotes;
|
||||||
|
else idx = (idx - 1 + numScaleNotes) % numScaleNotes;
|
||||||
|
|
||||||
|
int octave = sequence[track][s].note / 12;
|
||||||
|
sequence[track][s].note = 12 * octave + scaleNotes[idx];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Chance to fill a rest
|
||||||
|
if (random(100) < 25) {
|
||||||
|
int octave = 3 + random(3);
|
||||||
|
sequence[track][s].note = 12 * octave + scaleNotes[random(numScaleNotes)];
|
||||||
|
sequence[track][s].accent = false;
|
||||||
|
sequence[track][s].tie = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* getName() override {
|
||||||
|
return "Markov";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -6,6 +6,9 @@
|
|||||||
#include "LuckyStrategy.h"
|
#include "LuckyStrategy.h"
|
||||||
#include "ArpStrategy.h"
|
#include "ArpStrategy.h"
|
||||||
#include "EuclideanStrategy.h"
|
#include "EuclideanStrategy.h"
|
||||||
|
#include "MarkovStrategy.h"
|
||||||
|
#include "CellularAutomataStrategy.h"
|
||||||
|
#include "LSystemStrategy.h"
|
||||||
#include "MidiDriver.h"
|
#include "MidiDriver.h"
|
||||||
#include "UIManager.h"
|
#include "UIManager.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
@ -43,8 +46,10 @@ volatile int queuedTheme = -1;
|
|||||||
volatile int currentThemeIndex = 1;
|
volatile int currentThemeIndex = 1;
|
||||||
const uint32_t EEPROM_MAGIC = 0x4242424B;
|
const uint32_t EEPROM_MAGIC = 0x4242424B;
|
||||||
|
|
||||||
MelodyStrategy* strategies[] = { new LuckyStrategy(), new ArpStrategy(), new EuclideanStrategy() };
|
MelodyStrategy* strategies[] = {
|
||||||
const int numStrategies = 3;
|
new LuckyStrategy(), new ArpStrategy(), new EuclideanStrategy(),
|
||||||
|
new MarkovStrategy(), new CellularAutomataStrategy(), new LSystemStrategy()};
|
||||||
|
const int numStrategies = 6;
|
||||||
int currentStrategyIndices[NUM_TRACKS];
|
int currentStrategyIndices[NUM_TRACKS];
|
||||||
|
|
||||||
volatile PlayMode playMode = MODE_MONO;
|
volatile PlayMode playMode = MODE_MONO;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user