More harmony through chord notes
This commit is contained in:
parent
f34f960358
commit
aa51e1e95a
@ -6,30 +6,34 @@
|
||||
|
||||
class ArpStrategy : public MelodyStrategy {
|
||||
public:
|
||||
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed, int intensity) override {
|
||||
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int seed, int intensity) override {
|
||||
randomSeed(seed);
|
||||
if (numScaleNotes == 0) return;
|
||||
|
||||
// 1. Select Subset
|
||||
int subsetSize = random(2, numScaleNotes + 1);
|
||||
if (subsetSize > 12) subsetSize = 12;
|
||||
int subset[12];
|
||||
|
||||
// Create indices to shuffle
|
||||
int indices[12];
|
||||
for(int i=0; i<numScaleNotes; i++) indices[i] = i;
|
||||
|
||||
// Shuffle indices
|
||||
for(int i=0; i<numScaleNotes; i++) {
|
||||
int r = random(numScaleNotes);
|
||||
int temp = indices[i];
|
||||
indices[i] = indices[r];
|
||||
indices[r] = temp;
|
||||
}
|
||||
|
||||
// Pick subset
|
||||
for(int i=0; i<subsetSize; i++) {
|
||||
subset[i] = scaleNotes[indices[i]];
|
||||
int subsetSize = 0;
|
||||
|
||||
// Prioritize chord notes for the arpeggio
|
||||
if (numChordNotes > 0) {
|
||||
subsetSize = numChordNotes;
|
||||
for (int i = 0; i < subsetSize; i++) {
|
||||
subset[i] = chordNotes[i];
|
||||
}
|
||||
} else {
|
||||
// Fallback to a random subset of the scale if no chord notes are available
|
||||
subsetSize = random(2, numScaleNotes + 1);
|
||||
if (subsetSize > 12) subsetSize = 12;
|
||||
int indices[12];
|
||||
for(int i=0; i<numScaleNotes; i++) indices[i] = i;
|
||||
for(int i=0; i<numScaleNotes; i++) {
|
||||
int r = random(numScaleNotes);
|
||||
int temp = indices[i];
|
||||
indices[i] = indices[r];
|
||||
indices[r] = temp;
|
||||
}
|
||||
for(int i=0; i<subsetSize; i++) {
|
||||
subset[i] = scaleNotes[indices[i]];
|
||||
}
|
||||
}
|
||||
sortArray(subset, subsetSize);
|
||||
|
||||
@ -111,7 +115,7 @@ public:
|
||||
sortArray(scaleNotes, numScaleNotes);
|
||||
}
|
||||
|
||||
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int intensity) override {
|
||||
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int intensity) override {
|
||||
// Swap two notes
|
||||
int s1 = random(numSteps);
|
||||
int s2 = random(numSteps);
|
||||
|
||||
@ -1,91 +1,64 @@
|
||||
#include "MelodyStrategy.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifndef CALL_AND_RESPONSE_STRATEGY_H
|
||||
#define CALL_AND_RESPONSE_STRATEGY_H
|
||||
|
||||
#include "MelodyStrategy.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
class CallAndResponseStrategy : public MelodyStrategy {
|
||||
public:
|
||||
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed, int intensity) override {
|
||||
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int seed, int intensity) override {
|
||||
randomSeed(seed);
|
||||
if (numScaleNotes == 0) return;
|
||||
int* sourceNotes = (numChordNotes > 0) ? chordNotes : scaleNotes;
|
||||
int sourceNoteCount = (numChordNotes > 0) ? numChordNotes : numScaleNotes;
|
||||
if (sourceNoteCount == 0) return;
|
||||
|
||||
int halfSteps = numSteps / 2;
|
||||
if (halfSteps < 1) halfSteps = 1;
|
||||
// Clear sequence first
|
||||
for (int i = 0; i < numSteps; i++) {
|
||||
sequence[track][i].note = -1;
|
||||
sequence[track][i].accent = false;
|
||||
sequence[track][i].tie = false;
|
||||
}
|
||||
|
||||
// Generate Call (First Half)
|
||||
for (int i = 0; i < halfSteps; i++) {
|
||||
// Simple random generation for the call, weighted by intensity
|
||||
if (random(100) < (intensity * 8 + 20)) {
|
||||
int callLength = random(2, numSteps / 2);
|
||||
int responseLength = callLength;
|
||||
int callStart = 0;
|
||||
int responseStart = numSteps / 2;
|
||||
|
||||
// Generate Call
|
||||
for (int i = 0; i < callLength; i++) {
|
||||
if (random(100) < intensity * 9) {
|
||||
int octave = 3 + random(3);
|
||||
sequence[track][i].note = 12 * octave + scaleNotes[random(numScaleNotes)];
|
||||
sequence[track][i].accent = (random(100) < 30);
|
||||
sequence[track][i].tie = (random(100) < 10);
|
||||
} else {
|
||||
sequence[track][i].note = -1;
|
||||
sequence[track][i].accent = false;
|
||||
sequence[track][i].tie = false;
|
||||
sequence[track][callStart + i].note = 12 * octave + sourceNotes[random(sourceNoteCount)];
|
||||
sequence[track][callStart + i].accent = (i == 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate Response (Second Half)
|
||||
for (int i = halfSteps; i < numSteps; i++) {
|
||||
int srcIndex = i - halfSteps;
|
||||
Step srcStep = sequence[track][srcIndex];
|
||||
|
||||
// Default: Copy
|
||||
sequence[track][i] = srcStep;
|
||||
|
||||
// Variation based on intensity
|
||||
if (srcStep.note != -1) {
|
||||
int r = random(100);
|
||||
if (r < intensity * 5) {
|
||||
// Transpose / Shift
|
||||
int shift = random(-2, 3);
|
||||
int octave = srcStep.note / 12;
|
||||
int noteVal = srcStep.note % 12;
|
||||
|
||||
// Find index in scale
|
||||
int idx = 0;
|
||||
for(int k=0; k<numScaleNotes; k++) if(scaleNotes[k] == noteVal) idx = k;
|
||||
|
||||
idx = (idx + shift + numScaleNotes) % numScaleNotes;
|
||||
sequence[track][i].note = 12 * octave + scaleNotes[idx];
|
||||
} else if (r < intensity * 8) {
|
||||
// New random note (Variation)
|
||||
int octave = 3 + random(3);
|
||||
sequence[track][i].note = 12 * octave + scaleNotes[random(numScaleNotes)];
|
||||
}
|
||||
// Generate Response (variation of call)
|
||||
for (int i = 0; i < responseLength; i++) {
|
||||
sequence[track][responseStart + i] = sequence[track][callStart + i];
|
||||
// Small chance to change note
|
||||
if (sequence[track][responseStart + i].note != -1 && random(100) < 30) {
|
||||
int octave = (sequence[track][responseStart + i].note / 12) + random(-1, 2);
|
||||
if (octave < 3) octave = 3;
|
||||
if (octave > 5) octave = 5;
|
||||
sequence[track][responseStart + i].note = 12 * octave + sourceNotes[random(sourceNoteCount)];
|
||||
}
|
||||
}
|
||||
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);
|
||||
// This strategy doesn't generate its own scales, but the method must be implemented.
|
||||
}
|
||||
|
||||
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int intensity) override {
|
||||
// Swap call and response halves
|
||||
int half = numSteps / 2;
|
||||
for(int i=0; i<half; i++) {
|
||||
Step temp = sequence[track][i];
|
||||
sequence[track][i] = sequence[track][i+half];
|
||||
sequence[track][i+half] = temp;
|
||||
}
|
||||
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int intensity) override {
|
||||
// For simplicity, just regenerate with a new seed.
|
||||
generate(sequence, track, numSteps, scaleNotes, numScaleNotes, chordNotes, numChordNotes, random(65536), intensity);
|
||||
}
|
||||
|
||||
const char* getName() override {
|
||||
return "CallResp";
|
||||
return "Call/Resp";
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@ -6,51 +6,37 @@
|
||||
|
||||
class CellularAutomataStrategy : public MelodyStrategy {
|
||||
public:
|
||||
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed, int intensity) override {
|
||||
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int seed, int intensity) override {
|
||||
randomSeed(seed);
|
||||
if (numScaleNotes == 0) return;
|
||||
int* sourceNotes = (numChordNotes > 0) ? chordNotes : scaleNotes;
|
||||
int sourceNoteCount = (numChordNotes > 0) ? numChordNotes : numScaleNotes;
|
||||
if (sourceNoteCount == 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 {
|
||||
int initChance = intensity * 8; // 8% to 80%
|
||||
for(int i=0; i<numSteps; i++) cells[i] = (random(100) < initChance);
|
||||
for (int i = 0; i < numSteps; i++) {
|
||||
cells[i] = (random(100) < (intensity * 8)); // Initial density
|
||||
}
|
||||
|
||||
// Evolve for some generations to let patterns emerge
|
||||
int generations = numSteps + random(16);
|
||||
int rule = random(256); // Wolfram rule
|
||||
|
||||
for (int g = 0; g < generations; g++) {
|
||||
for (int gen = 0; gen < 4; gen++) { // A few generations
|
||||
bool next_cells[NUM_STEPS];
|
||||
for (int i = 0; i < numSteps; i++) {
|
||||
bool left = cells[(i - 1 + numSteps) % numSteps];
|
||||
bool center = cells[i];
|
||||
bool middle = 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;
|
||||
int index = (left << 2) | (middle << 1) | right;
|
||||
next_cells[i] = (rule >> index) & 1;
|
||||
}
|
||||
for(int i=0; i<numSteps; i++) cells[i] = next_cells[i];
|
||||
memcpy(cells, next_cells, sizeof(cells));
|
||||
}
|
||||
|
||||
// Map to notes
|
||||
for (int i = 0; i < numSteps; i++) {
|
||||
// Intensity also affects survival of active cells to notes
|
||||
// Intensity 1 = 50% survival, Intensity 10 = 100% survival
|
||||
if (cells[i] && random(100) < (50 + intensity * 5)) {
|
||||
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);
|
||||
if (cells[i]) {
|
||||
int octave = 3 + random(3);
|
||||
sequence[track][i].note = 12 * octave + sourceNotes[random(sourceNoteCount)];
|
||||
sequence[track][i].accent = (random(100) < 20);
|
||||
sequence[track][i].tie = false;
|
||||
} else {
|
||||
sequence[track][i].note = -1;
|
||||
sequence[track][i].accent = false;
|
||||
@ -61,54 +47,12 @@ public:
|
||||
}
|
||||
|
||||
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);
|
||||
// This strategy doesn't generate its own scales, but the method must be implemented.
|
||||
}
|
||||
|
||||
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int intensity) 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];
|
||||
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int intensity) override {
|
||||
// For simplicity, just regenerate with a new seed.
|
||||
generate(sequence, track, numSteps, scaleNotes, numScaleNotes, chordNotes, numChordNotes, random(65536), intensity);
|
||||
}
|
||||
|
||||
const char* getName() override {
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
|
||||
class DroneStrategy : public MelodyStrategy {
|
||||
public:
|
||||
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed, int intensity) override {
|
||||
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int seed, int intensity) override {
|
||||
randomSeed(seed);
|
||||
if (numScaleNotes == 0) return;
|
||||
|
||||
@ -30,8 +30,12 @@ public:
|
||||
// Pick a note from the scale
|
||||
// Prefer lower octaves for drones (3 and 4)
|
||||
int octave = 3 + random(2);
|
||||
int noteIndex = random(numScaleNotes);
|
||||
int note = 12 * octave + scaleNotes[noteIndex];
|
||||
int note;
|
||||
if (numChordNotes > 0) {
|
||||
note = 12 * octave + chordNotes[random(numChordNotes)];
|
||||
} else {
|
||||
note = 12 * octave + scaleNotes[random(numScaleNotes)];
|
||||
}
|
||||
|
||||
for (int k = 0; k < duration; k++) {
|
||||
int stepIndex = currentStep + k;
|
||||
@ -65,7 +69,7 @@ public:
|
||||
sortArray(scaleNotes, numScaleNotes);
|
||||
}
|
||||
|
||||
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int intensity) override {
|
||||
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int intensity) override {
|
||||
// Mutate by shifting the pitch of a random segment
|
||||
int s = random(numSteps);
|
||||
if (sequence[track][s].note != -1) {
|
||||
@ -83,7 +87,12 @@ public:
|
||||
|
||||
// Pick a new note
|
||||
int octave = 3 + random(2);
|
||||
int newNote = 12 * octave + scaleNotes[random(numScaleNotes)];
|
||||
int newNote;
|
||||
if (numChordNotes > 0) {
|
||||
newNote = 12 * octave + chordNotes[random(numChordNotes)];
|
||||
} else {
|
||||
newNote = 12 * octave + scaleNotes[random(numScaleNotes)];
|
||||
}
|
||||
|
||||
for (int i = start; i <= end; i++) {
|
||||
sequence[track][i].note = newNote;
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
|
||||
class EuclideanStrategy : public MelodyStrategy {
|
||||
public:
|
||||
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed, int intensity) override {
|
||||
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int seed, int intensity) override {
|
||||
randomSeed(seed);
|
||||
if (numScaleNotes == 0) return;
|
||||
|
||||
@ -42,7 +42,13 @@ public:
|
||||
|
||||
if (pattern[i]) {
|
||||
int octave = random(3) + 3; // 3, 4, 5
|
||||
sequence[track][stepIndex].note = 12 * octave + scaleNotes[random(numScaleNotes)];
|
||||
int note;
|
||||
if (numChordNotes > 0) {
|
||||
note = 12 * octave + chordNotes[random(numChordNotes)];
|
||||
} else {
|
||||
note = 12 * octave + scaleNotes[random(numScaleNotes)];
|
||||
}
|
||||
sequence[track][stepIndex].note = note;
|
||||
sequence[track][stepIndex].accent = (random(100) < 30);
|
||||
sequence[track][stepIndex].tie = (random(100) < 10);
|
||||
} else {
|
||||
@ -69,7 +75,7 @@ public:
|
||||
sortArray(scaleNotes, numScaleNotes);
|
||||
}
|
||||
|
||||
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int intensity) override {
|
||||
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int intensity) override {
|
||||
// Rotate sequence
|
||||
if (random(2) == 0) {
|
||||
Step last = sequence[track][numSteps - 1];
|
||||
@ -82,7 +88,13 @@ public:
|
||||
int s = random(numSteps);
|
||||
if (sequence[track][s].note != -1) {
|
||||
int octave = random(3) + 3;
|
||||
sequence[track][s].note = 12 * octave + scaleNotes[random(numScaleNotes)];
|
||||
int note;
|
||||
if (numChordNotes > 0) {
|
||||
note = 12 * octave + chordNotes[random(numChordNotes)];
|
||||
} else {
|
||||
note = 12 * octave + scaleNotes[random(numScaleNotes)];
|
||||
}
|
||||
sequence[track][s].note = note;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,28 +6,31 @@
|
||||
|
||||
class IsorhythmStrategy : public MelodyStrategy {
|
||||
public:
|
||||
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed, int intensity) override {
|
||||
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int seed, int intensity) override {
|
||||
randomSeed(seed);
|
||||
if (numScaleNotes == 0) return;
|
||||
|
||||
int* sourceNotes = (numChordNotes > 0) ? chordNotes : scaleNotes;
|
||||
int sourceNoteCount = (numChordNotes > 0) ? numChordNotes : numScaleNotes;
|
||||
if (sourceNoteCount == 0) return;
|
||||
|
||||
// 1. Create the Color (pitch pattern)
|
||||
// Intensity affects color length. Higher intensity = longer, more complex color.
|
||||
int colorLength = 2 + random(intensity + 2); // 2 to intensity+3 notes
|
||||
if (colorLength > numScaleNotes) colorLength = numScaleNotes;
|
||||
if (colorLength > sourceNoteCount) colorLength = sourceNoteCount;
|
||||
if (colorLength > 8) colorLength = 8; // Keep it reasonable
|
||||
|
||||
int color[8];
|
||||
// Pick unique notes from the scale for the color
|
||||
int scaleIndices[12];
|
||||
for(int i=0; i<numScaleNotes; i++) scaleIndices[i] = i;
|
||||
for(int i=0; i<numScaleNotes; i++) { // shuffle
|
||||
int r = random(numScaleNotes);
|
||||
int temp = scaleIndices[i];
|
||||
scaleIndices[i] = scaleIndices[r];
|
||||
scaleIndices[r] = temp;
|
||||
int indices[12];
|
||||
for(int i=0; i<sourceNoteCount; i++) indices[i] = i;
|
||||
for(int i=0; i<sourceNoteCount; i++) { // shuffle
|
||||
int r = random(sourceNoteCount);
|
||||
int temp = indices[i];
|
||||
indices[i] = indices[r];
|
||||
indices[r] = temp;
|
||||
}
|
||||
for(int i=0; i<colorLength; i++) {
|
||||
color[i] = scaleIndices[i];
|
||||
color[i] = sourceNotes[indices[i]];
|
||||
}
|
||||
|
||||
// 2. Create the Talea (rhythmic pattern)
|
||||
@ -56,8 +59,8 @@ public:
|
||||
int taleaIdx = i % taleaLength;
|
||||
if (talea[taleaIdx]) {
|
||||
int octave = 3 + random(3);
|
||||
int noteScaleIndex = color[colorIdx % colorLength];
|
||||
sequence[track][i].note = 12 * octave + scaleNotes[noteScaleIndex];
|
||||
int noteValue = color[colorIdx % colorLength];
|
||||
sequence[track][i].note = 12 * octave + noteValue;
|
||||
sequence[track][i].accent = (i % 4 == 0); // Accent on downbeats
|
||||
sequence[track][i].tie = false;
|
||||
colorIdx++;
|
||||
@ -82,7 +85,7 @@ public:
|
||||
sortArray(scaleNotes, numScaleNotes);
|
||||
}
|
||||
|
||||
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int intensity) override {
|
||||
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int intensity) override {
|
||||
// Mutation: rotate the talea (rhythmic displacement)
|
||||
int rotation = random(1, numSteps);
|
||||
Step temp[NUM_STEPS];
|
||||
|
||||
@ -29,9 +29,13 @@ 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, int intensity) override {
|
||||
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int seed, int intensity) override {
|
||||
randomSeed(seed);
|
||||
if (numScaleNotes == 0) {
|
||||
|
||||
int* sourceNotes = (numChordNotes > 0) ? chordNotes : scaleNotes;
|
||||
int sourceNoteCount = (numChordNotes > 0) ? numChordNotes : numScaleNotes;
|
||||
|
||||
if (sourceNoteCount == 0) {
|
||||
// Fill with silence if no scale
|
||||
for (int i = 0; i < numSteps; i++) {
|
||||
sequence[track][i].note = -1;
|
||||
@ -68,7 +72,7 @@ public:
|
||||
|
||||
// 2. Interpret the string to create the sequence
|
||||
int stepIndex = 0;
|
||||
int noteIndex = random(numScaleNotes);
|
||||
int noteIndex = random(sourceNoteCount);
|
||||
int octave = 4;
|
||||
|
||||
// Stack for branching rules
|
||||
@ -86,16 +90,16 @@ public:
|
||||
|
||||
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].note = 12 * octave + sourceNotes[noteIndex];
|
||||
sequence[track][stepIndex].accent = (random(100) < accentChance);
|
||||
sequence[track][stepIndex].tie = (random(100) < tieChance);
|
||||
stepIndex++;
|
||||
break;
|
||||
case '+': // Go up in scale
|
||||
noteIndex = (noteIndex + 1) % numScaleNotes;
|
||||
noteIndex = (noteIndex + 1) % sourceNoteCount;
|
||||
break;
|
||||
case '-': // Go down in scale
|
||||
noteIndex = (noteIndex - 1 + numScaleNotes) % numScaleNotes;
|
||||
noteIndex = (noteIndex - 1 + sourceNoteCount) % sourceNoteCount;
|
||||
break;
|
||||
case '[': // Push current state
|
||||
if(stack_ptr < 8) {
|
||||
@ -140,7 +144,7 @@ public:
|
||||
sortArray(scaleNotes, numScaleNotes);
|
||||
}
|
||||
|
||||
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int intensity) override {
|
||||
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int intensity) override {
|
||||
// Swap two non-rest steps to create a variation
|
||||
int s1 = random(numSteps);
|
||||
int s2 = random(numSteps);
|
||||
|
||||
@ -6,16 +6,23 @@
|
||||
|
||||
class LuckyStrategy : public MelodyStrategy {
|
||||
public:
|
||||
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed, int intensity) override {
|
||||
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int seed, int intensity) override {
|
||||
randomSeed(seed);
|
||||
if (numScaleNotes == 0) return;
|
||||
|
||||
int noteChance = intensity * 9; // 10% to 90%
|
||||
int accentChance = intensity * 6; // 6% to 60%
|
||||
|
||||
for (int i = 0; i < numSteps; i++) {
|
||||
int octave = random(3) + 3; // 3, 4, 5 (Base is 4)
|
||||
sequence[track][i].note = (random(100) < noteChance) ? (12 * octave + scaleNotes[random(numScaleNotes)]) : -1;
|
||||
int note = -1;
|
||||
if (random(100) < noteChance) {
|
||||
if (numChordNotes > 0) {
|
||||
note = 12 * octave + chordNotes[random(numChordNotes)];
|
||||
} else if (numScaleNotes > 0) {
|
||||
note = 12 * octave + scaleNotes[random(numScaleNotes)];
|
||||
}
|
||||
}
|
||||
sequence[track][i].note = note;
|
||||
sequence[track][i].accent = (random(100) < accentChance);
|
||||
sequence[track][i].tie = (random(100) < 20);
|
||||
}
|
||||
@ -37,7 +44,7 @@ public:
|
||||
sortArray(scaleNotes, numScaleNotes);
|
||||
}
|
||||
|
||||
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int intensity) override {
|
||||
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int intensity) override {
|
||||
// Mutate 1 or 2 steps
|
||||
int count = random(1, 3);
|
||||
for (int i = 0; i < count; i++) {
|
||||
|
||||
@ -6,17 +6,21 @@
|
||||
|
||||
class MarkovStrategy : public MelodyStrategy {
|
||||
public:
|
||||
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed, int intensity) override {
|
||||
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int seed, int intensity) override {
|
||||
randomSeed(seed);
|
||||
if (numScaleNotes == 0) return;
|
||||
|
||||
int* sourceNotes = (numChordNotes > 0) ? chordNotes : scaleNotes;
|
||||
int sourceNoteCount = (numChordNotes > 0) ? numChordNotes : numScaleNotes;
|
||||
|
||||
if (sourceNoteCount == 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++) {
|
||||
for (int i = 0; i < sourceNoteCount; i++) {
|
||||
for (int j = 0; j < sourceNoteCount; j++) {
|
||||
// Base random weight
|
||||
matrix[i][j] = random(1, 8);
|
||||
|
||||
@ -29,7 +33,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
int currentIdx = random(numScaleNotes);
|
||||
int currentIdx = random(sourceNoteCount);
|
||||
|
||||
for (int i = 0; i < numSteps; i++) {
|
||||
// Intensity 1 = 60% rest, Intensity 10 = 0% rest
|
||||
@ -44,13 +48,13 @@ public:
|
||||
|
||||
// Select next note based on matrix
|
||||
int totalWeight = 0;
|
||||
for (int j = 0; j < numScaleNotes; j++) {
|
||||
for (int j = 0; j < sourceNoteCount; j++) {
|
||||
totalWeight += matrix[currentIdx][j];
|
||||
}
|
||||
|
||||
int r = random(totalWeight);
|
||||
int nextIdx = 0;
|
||||
for (int j = 0; j < numScaleNotes; j++) {
|
||||
for (int j = 0; j < sourceNoteCount; j++) {
|
||||
r -= matrix[currentIdx][j];
|
||||
if (r < 0) {
|
||||
nextIdx = j;
|
||||
@ -65,7 +69,7 @@ public:
|
||||
if (rOct < 20) octave = 3;
|
||||
else if (rOct > 80) octave = 5;
|
||||
|
||||
sequence[track][i].note = 12 * octave + scaleNotes[currentIdx];
|
||||
sequence[track][i].note = 12 * octave + sourceNotes[currentIdx];
|
||||
sequence[track][i].accent = (random(100) < 25);
|
||||
sequence[track][i].tie = (random(100) < 15);
|
||||
}
|
||||
@ -87,15 +91,19 @@ public:
|
||||
sortArray(scaleNotes, numScaleNotes);
|
||||
}
|
||||
|
||||
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int intensity) override {
|
||||
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int intensity) override {
|
||||
int* sourceNotes = (numChordNotes > 0) ? chordNotes : scaleNotes;
|
||||
int sourceNoteCount = (numChordNotes > 0) ? numChordNotes : numScaleNotes;
|
||||
if (sourceNoteCount == 0) return;
|
||||
|
||||
// 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) {
|
||||
for(int i=0; i<sourceNoteCount; i++) {
|
||||
if (sourceNotes[i] == currentNoteVal) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
@ -103,17 +111,17 @@ public:
|
||||
|
||||
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;
|
||||
if (random(2) == 0) idx = (idx + 1) % sourceNoteCount;
|
||||
else idx = (idx - 1 + sourceNoteCount) % sourceNoteCount;
|
||||
|
||||
int octave = sequence[track][s].note / 12;
|
||||
sequence[track][s].note = 12 * octave + scaleNotes[idx];
|
||||
sequence[track][s].note = 12 * octave + sourceNotes[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].note = 12 * octave + sourceNotes[random(sourceNoteCount)];
|
||||
sequence[track][s].accent = false;
|
||||
sequence[track][s].tie = false;
|
||||
}
|
||||
|
||||
@ -6,9 +6,9 @@
|
||||
|
||||
class MelodyStrategy {
|
||||
public:
|
||||
virtual void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed, int intensity) = 0;
|
||||
virtual void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int seed, int intensity) = 0;
|
||||
virtual void generateScale(int* scaleNotes, int& numScaleNotes) = 0;
|
||||
virtual void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int intensity) = 0;
|
||||
virtual void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int intensity) = 0;
|
||||
virtual const char* getName() = 0;
|
||||
virtual ~MelodyStrategy() {}
|
||||
};
|
||||
|
||||
@ -3,24 +3,65 @@
|
||||
#include "MidiDriver.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
void SequenceGenerator::getChordNotes(int* chordNotes, int& numChordNotes) {
|
||||
numChordNotes = 0;
|
||||
if (numScaleNotes == 0) return;
|
||||
|
||||
// For explicit chord types, chord notes are the scale notes
|
||||
if (currentScaleType >= 6 && currentScaleType <= 9) {
|
||||
numChordNotes = numScaleNotes;
|
||||
for (int i = 0; i < numScaleNotes; i++) {
|
||||
chordNotes[i] = scaleNotes[i];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// For other scales, derive the basic triad (root, third, fifth) from the current scale
|
||||
chordNotes[numChordNotes++] = currentRoot;
|
||||
|
||||
// Find the third (major or minor)
|
||||
int majorThird = (currentRoot + 4) % 12;
|
||||
int minorThird = (currentRoot + 3) % 12;
|
||||
bool thirdFound = false;
|
||||
for (int i = 0; i < numScaleNotes; i++) {
|
||||
if (scaleNotes[i] == majorThird) {
|
||||
chordNotes[numChordNotes++] = majorThird;
|
||||
thirdFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!thirdFound) {
|
||||
for (int i = 0; i < numScaleNotes; i++) {
|
||||
if (scaleNotes[i] == minorThird) {
|
||||
chordNotes[numChordNotes++] = minorThird;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the fifth (perfect, or diminished if not found)
|
||||
int perfectFifth = (currentRoot + 7) % 12;
|
||||
int diminishedFifth = (currentRoot + 6) % 12;
|
||||
for (int i = 0; i < numScaleNotes; i++) {
|
||||
if (scaleNotes[i] == perfectFifth) {
|
||||
chordNotes[numChordNotes++] = perfectFifth;
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < numScaleNotes; i++) {
|
||||
if (scaleNotes[i] == diminishedFifth) {
|
||||
chordNotes[numChordNotes++] = diminishedFifth;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SequenceGenerator::generateTrackData(int track, int themeType, Step (*target)[NUM_STEPS]) {
|
||||
randomSeed(melodySeeds[track] + themeType * 12345);
|
||||
strategies[currentStrategyIndices[track]]->generate(target, track, numSteps[track], scaleNotes, numScaleNotes, melodySeeds[track] + themeType * 12345, trackIntensity[track]);
|
||||
}
|
||||
|
||||
void SequenceGenerator::generateSequenceData(int themeType, Step (*target)[NUM_STEPS]) {
|
||||
Serial.println(F("Generating sequence."));
|
||||
for(int i=0; i<NUM_TRACKS; i++) {
|
||||
SequenceGenerator::generateTrackData(i, themeType, target);
|
||||
}
|
||||
}
|
||||
|
||||
void SequenceGenerator::mutateSequence(Step (*target)[NUM_STEPS]) {
|
||||
for(int i=0; i<NUM_TRACKS; i++) {
|
||||
if (random(100) < (trackIntensity[i] * 10)) {
|
||||
strategies[currentStrategyIndices[i]]->mutate(target, i, numSteps[i], scaleNotes, numScaleNotes, trackIntensity[i]);
|
||||
}
|
||||
}
|
||||
int chordNotes[12];
|
||||
int numChordNotes = 0;
|
||||
getChordNotes(chordNotes, numChordNotes);
|
||||
strategies[currentStrategyIndices[track]]->generate(target, track, numSteps[track], scaleNotes, numScaleNotes, chordNotes, numChordNotes, melodySeeds[track] + themeType * 12345, trackIntensity[track]);
|
||||
}
|
||||
|
||||
void SequenceGenerator::generateRandomScale() {
|
||||
@ -75,6 +116,24 @@ void SequenceGenerator::updateScale() {
|
||||
midi.unlock();
|
||||
}
|
||||
|
||||
void SequenceGenerator::generateSequenceData(int themeType, Step (*target)[NUM_STEPS]) {
|
||||
Serial.println(F("Generating sequence."));
|
||||
for(int i=0; i<NUM_TRACKS; i++) {
|
||||
SequenceGenerator::generateTrackData(i, themeType, target);
|
||||
}
|
||||
}
|
||||
|
||||
void SequenceGenerator::mutateSequence(Step (*target)[NUM_STEPS]) {
|
||||
int chordNotes[12];
|
||||
int numChordNotes = 0;
|
||||
getChordNotes(chordNotes, numChordNotes);
|
||||
for(int i=0; i<NUM_TRACKS; i++) {
|
||||
if (random(100) < (trackIntensity[i] * 10)) {
|
||||
strategies[currentStrategyIndices[i]]->mutate(target, i, numSteps[i], scaleNotes, numScaleNotes, chordNotes, numChordNotes, trackIntensity[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SequenceGenerator::pickRandomScaleType(int themeType) {
|
||||
unsigned long seed = themeType * 9999;
|
||||
for(int i=0; i<NUM_TRACKS; i++) seed += melodySeeds[i];
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
#define SEQUENCE_GENERATOR_H
|
||||
|
||||
#include "TrackerTypes.h"
|
||||
#include "config.h"
|
||||
|
||||
class SequenceGenerator {
|
||||
public:
|
||||
@ -12,6 +11,7 @@ public:
|
||||
static void generateRandomScale();
|
||||
static void updateScale();
|
||||
static void pickRandomScaleType(int themeType);
|
||||
static void getChordNotes(int* chordNotes, int& numChordNotes);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@ -6,9 +6,13 @@
|
||||
|
||||
class WaveStrategy : public MelodyStrategy {
|
||||
public:
|
||||
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed, int intensity) override {
|
||||
void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int seed, int intensity) override {
|
||||
randomSeed(seed);
|
||||
if (numScaleNotes == 0) return;
|
||||
|
||||
int* sourceNotes = (numChordNotes > 0) ? chordNotes : scaleNotes;
|
||||
int sourceNoteCount = (numChordNotes > 0) ? numChordNotes : numScaleNotes;
|
||||
|
||||
if (sourceNoteCount == 0) return;
|
||||
|
||||
// Wave parameters
|
||||
// Frequency: How many cycles per sequence
|
||||
@ -19,8 +23,8 @@ public:
|
||||
int type = random(3);
|
||||
|
||||
// Center pitch (note index)
|
||||
int centerIdx = numScaleNotes * 2; // Middle of 4 octaves roughly
|
||||
int amp = numScaleNotes + (intensity); // Amplitude in scale degrees
|
||||
int centerIdx = sourceNoteCount * 2; // Middle of 4 octaves roughly
|
||||
int amp = sourceNoteCount + (intensity); // Amplitude in scale degrees
|
||||
|
||||
for (int i = 0; i < numSteps; i++) {
|
||||
float t = (float)i / (float)numSteps; // 0.0 to 1.0
|
||||
@ -43,17 +47,17 @@ public:
|
||||
int totalIdx = centerIdx + noteIdxOffset;
|
||||
|
||||
// Normalize totalIdx
|
||||
while(totalIdx < 0) totalIdx += numScaleNotes;
|
||||
while(totalIdx < 0) totalIdx += sourceNoteCount;
|
||||
|
||||
int octave = 2 + (totalIdx / numScaleNotes);
|
||||
int scaleIdx = totalIdx % numScaleNotes;
|
||||
int octave = 2 + (totalIdx / sourceNoteCount);
|
||||
int noteIdx = totalIdx % sourceNoteCount;
|
||||
|
||||
if (octave < 0) octave = 0;
|
||||
if (octave > 8) octave = 8;
|
||||
|
||||
// Rhythmic density based on intensity
|
||||
if (random(100) < (40 + intensity * 5)) {
|
||||
sequence[track][i].note = 12 * octave + scaleNotes[scaleIdx];
|
||||
sequence[track][i].note = 12 * octave + sourceNotes[noteIdx];
|
||||
sequence[track][i].accent = (val > 0.8f); // Accent peaks
|
||||
sequence[track][i].tie = false;
|
||||
} else {
|
||||
@ -77,7 +81,7 @@ public:
|
||||
sortArray(scaleNotes, numScaleNotes);
|
||||
}
|
||||
|
||||
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int intensity) override {
|
||||
void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int intensity) override {
|
||||
// Phase shift the sequence
|
||||
int shift = random(1, 4);
|
||||
Step temp[NUM_STEPS];
|
||||
|
||||
Loading…
Reference in New Issue
Block a user