116 lines
4.1 KiB
C++
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 |