PicoWaveTracker/CellularAutomataStrategy.h
2026-02-18 19:31:38 +01:00

116 lines
4.1 KiB
C++

#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