More harmony through chord notes

This commit is contained in:
Dejvino 2026-03-10 21:58:03 +01:00
parent f34f960358
commit aa51e1e95a
13 changed files with 269 additions and 242 deletions

View File

@ -6,31 +6,35 @@
class ArpStrategy : public MelodyStrategy { class ArpStrategy : public MelodyStrategy {
public: 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); randomSeed(seed);
if (numScaleNotes == 0) return; if (numScaleNotes == 0) return;
// 1. Select Subset
int subsetSize = random(2, numScaleNotes + 1);
if (subsetSize > 12) subsetSize = 12;
int subset[12]; int subset[12];
int subsetSize = 0;
// Create indices to shuffle // 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]; int indices[12];
for(int i=0; i<numScaleNotes; i++) indices[i] = i; for(int i=0; i<numScaleNotes; i++) indices[i] = i;
// Shuffle indices
for(int i=0; i<numScaleNotes; i++) { for(int i=0; i<numScaleNotes; i++) {
int r = random(numScaleNotes); int r = random(numScaleNotes);
int temp = indices[i]; int temp = indices[i];
indices[i] = indices[r]; indices[i] = indices[r];
indices[r] = temp; indices[r] = temp;
} }
// Pick subset
for(int i=0; i<subsetSize; i++) { for(int i=0; i<subsetSize; i++) {
subset[i] = scaleNotes[indices[i]]; subset[i] = scaleNotes[indices[i]];
} }
}
sortArray(subset, subsetSize); sortArray(subset, subsetSize);
// 2. Pick Arp Length & Pattern // 2. Pick Arp Length & Pattern
@ -111,7 +115,7 @@ public:
sortArray(scaleNotes, numScaleNotes); 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 // Swap two notes
int s1 = random(numSteps); int s1 = random(numSteps);
int s2 = random(numSteps); int s2 = random(numSteps);

View File

@ -1,90 +1,63 @@
#include "MelodyStrategy.h"
#include <Arduino.h>
#ifndef CALL_AND_RESPONSE_STRATEGY_H #ifndef CALL_AND_RESPONSE_STRATEGY_H
#define CALL_AND_RESPONSE_STRATEGY_H #define CALL_AND_RESPONSE_STRATEGY_H
#include "MelodyStrategy.h"
#include <Arduino.h>
class CallAndResponseStrategy : public MelodyStrategy { class CallAndResponseStrategy : public MelodyStrategy {
public: 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); 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; // Clear sequence first
if (halfSteps < 1) halfSteps = 1; for (int i = 0; i < numSteps; i++) {
// 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 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].note = -1;
sequence[track][i].accent = false; sequence[track][i].accent = false;
sequence[track][i].tie = false; sequence[track][i].tie = false;
} }
}
// Generate Response (Second Half) int callLength = random(2, numSteps / 2);
for (int i = halfSteps; i < numSteps; i++) { int responseLength = callLength;
int srcIndex = i - halfSteps; int callStart = 0;
Step srcStep = sequence[track][srcIndex]; int responseStart = numSteps / 2;
// Default: Copy // Generate Call
sequence[track][i] = srcStep; for (int i = 0; i < callLength; i++) {
if (random(100) < intensity * 9) {
// 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); int octave = 3 + random(3);
sequence[track][i].note = 12 * octave + scaleNotes[random(numScaleNotes)]; sequence[track][callStart + i].note = 12 * octave + sourceNotes[random(sourceNoteCount)];
sequence[track][callStart + i].accent = (i == 0);
} }
} }
// 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()); randomSeed(micros());
} }
void generateScale(int* scaleNotes, int& numScaleNotes) override { void generateScale(int* scaleNotes, int& numScaleNotes) override {
numScaleNotes = random(5, 8); // This strategy doesn't generate its own scales, but the method must be implemented.
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, 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 call and response halves // For simplicity, just regenerate with a new seed.
int half = numSteps / 2; generate(sequence, track, numSteps, scaleNotes, numScaleNotes, chordNotes, numChordNotes, random(65536), intensity);
for(int i=0; i<half; i++) {
Step temp = sequence[track][i];
sequence[track][i] = sequence[track][i+half];
sequence[track][i+half] = temp;
}
} }
const char* getName() override { const char* getName() override {
return "CallResp"; return "Call/Resp";
} }
}; };

View File

@ -6,51 +6,37 @@
class CellularAutomataStrategy : public MelodyStrategy { class CellularAutomataStrategy : public MelodyStrategy {
public: 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); randomSeed(seed);
if (numScaleNotes == 0) return; int* sourceNotes = (numChordNotes > 0) ? chordNotes : scaleNotes;
int sourceNoteCount = (numChordNotes > 0) ? numChordNotes : numScaleNotes;
// 1. Setup CA if (sourceNoteCount == 0) return;
// 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 cells[NUM_STEPS];
bool next_cells[NUM_STEPS]; for (int i = 0; i < numSteps; i++) {
cells[i] = (random(100) < (intensity * 8)); // Initial density
// 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);
} }
// Evolve for some generations to let patterns emerge int rule = random(256); // Wolfram rule
int generations = numSteps + random(16);
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++) { for (int i = 0; i < numSteps; i++) {
bool left = cells[(i - 1 + numSteps) % numSteps]; bool left = cells[(i - 1 + numSteps) % numSteps];
bool center = cells[i]; bool middle = cells[i];
bool right = cells[(i + 1) % numSteps]; bool right = cells[(i + 1) % numSteps];
int index = (left << 2) | (middle << 1) | right;
uint8_t pattern = (left ? 4 : 0) | (center ? 2 : 0) | (right ? 1 : 0); next_cells[i] = (rule >> index) & 1;
next_cells[i] = (rule >> pattern) & 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++) { for (int i = 0; i < numSteps; i++) {
// Intensity also affects survival of active cells to notes if (cells[i]) {
// Intensity 1 = 50% survival, Intensity 10 = 100% survival int octave = 3 + random(3);
if (cells[i] && random(100) < (50 + intensity * 5)) { sequence[track][i].note = 12 * octave + sourceNotes[random(sourceNoteCount)];
int octave = 3 + random(3); // 3, 4, 5 sequence[track][i].accent = (random(100) < 20);
sequence[track][i].note = 12 * octave + scaleNotes[random(numScaleNotes)]; sequence[track][i].tie = false;
sequence[track][i].accent = (random(100) < 30);
sequence[track][i].tie = (random(100) < 15);
} else { } else {
sequence[track][i].note = -1; sequence[track][i].note = -1;
sequence[track][i].accent = false; sequence[track][i].accent = false;
@ -61,54 +47,12 @@ public:
} }
void generateScale(int* scaleNotes, int& numScaleNotes) override { void generateScale(int* scaleNotes, int& numScaleNotes) override {
numScaleNotes = random(5, 8); // This strategy doesn't generate its own scales, but the method must be implemented.
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, int intensity) override { void mutate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int* chordNotes, int numChordNotes, int intensity) override {
// Evolve the current sequence by one generation // For simplicity, just regenerate with a new seed.
// Use a random rule for mutation to keep it dynamic generate(sequence, track, numSteps, scaleNotes, numScaleNotes, chordNotes, numChordNotes, random(65536), intensity);
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 { const char* getName() override {

View File

@ -6,7 +6,7 @@
class DroneStrategy : public MelodyStrategy { class DroneStrategy : public MelodyStrategy {
public: 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); randomSeed(seed);
if (numScaleNotes == 0) return; if (numScaleNotes == 0) return;
@ -30,8 +30,12 @@ public:
// Pick a note from the scale // Pick a note from the scale
// Prefer lower octaves for drones (3 and 4) // Prefer lower octaves for drones (3 and 4)
int octave = 3 + random(2); int octave = 3 + random(2);
int noteIndex = random(numScaleNotes); int note;
int note = 12 * octave + scaleNotes[noteIndex]; if (numChordNotes > 0) {
note = 12 * octave + chordNotes[random(numChordNotes)];
} else {
note = 12 * octave + scaleNotes[random(numScaleNotes)];
}
for (int k = 0; k < duration; k++) { for (int k = 0; k < duration; k++) {
int stepIndex = currentStep + k; int stepIndex = currentStep + k;
@ -65,7 +69,7 @@ public:
sortArray(scaleNotes, numScaleNotes); 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 // Mutate by shifting the pitch of a random segment
int s = random(numSteps); int s = random(numSteps);
if (sequence[track][s].note != -1) { if (sequence[track][s].note != -1) {
@ -83,7 +87,12 @@ public:
// Pick a new note // Pick a new note
int octave = 3 + random(2); 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++) { for (int i = start; i <= end; i++) {
sequence[track][i].note = newNote; sequence[track][i].note = newNote;

View File

@ -6,7 +6,7 @@
class EuclideanStrategy : public MelodyStrategy { class EuclideanStrategy : public MelodyStrategy {
public: 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); randomSeed(seed);
if (numScaleNotes == 0) return; if (numScaleNotes == 0) return;
@ -42,7 +42,13 @@ public:
if (pattern[i]) { if (pattern[i]) {
int octave = random(3) + 3; // 3, 4, 5 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].accent = (random(100) < 30);
sequence[track][stepIndex].tie = (random(100) < 10); sequence[track][stepIndex].tie = (random(100) < 10);
} else { } else {
@ -69,7 +75,7 @@ public:
sortArray(scaleNotes, numScaleNotes); 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 // Rotate sequence
if (random(2) == 0) { if (random(2) == 0) {
Step last = sequence[track][numSteps - 1]; Step last = sequence[track][numSteps - 1];
@ -82,7 +88,13 @@ public:
int s = random(numSteps); int s = random(numSteps);
if (sequence[track][s].note != -1) { if (sequence[track][s].note != -1) {
int octave = random(3) + 3; 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;
} }
} }
} }

View File

@ -6,28 +6,31 @@
class IsorhythmStrategy : public MelodyStrategy { class IsorhythmStrategy : public MelodyStrategy {
public: 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); 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) // 1. Create the Color (pitch pattern)
// Intensity affects color length. Higher intensity = longer, more complex color. // Intensity affects color length. Higher intensity = longer, more complex color.
int colorLength = 2 + random(intensity + 2); // 2 to intensity+3 notes 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 if (colorLength > 8) colorLength = 8; // Keep it reasonable
int color[8]; int color[8];
// Pick unique notes from the scale for the color // Pick unique notes from the scale for the color
int scaleIndices[12]; int indices[12];
for(int i=0; i<numScaleNotes; i++) scaleIndices[i] = i; for(int i=0; i<sourceNoteCount; i++) indices[i] = i;
for(int i=0; i<numScaleNotes; i++) { // shuffle for(int i=0; i<sourceNoteCount; i++) { // shuffle
int r = random(numScaleNotes); int r = random(sourceNoteCount);
int temp = scaleIndices[i]; int temp = indices[i];
scaleIndices[i] = scaleIndices[r]; indices[i] = indices[r];
scaleIndices[r] = temp; indices[r] = temp;
} }
for(int i=0; i<colorLength; i++) { for(int i=0; i<colorLength; i++) {
color[i] = scaleIndices[i]; color[i] = sourceNotes[indices[i]];
} }
// 2. Create the Talea (rhythmic pattern) // 2. Create the Talea (rhythmic pattern)
@ -56,8 +59,8 @@ public:
int taleaIdx = i % taleaLength; int taleaIdx = i % taleaLength;
if (talea[taleaIdx]) { if (talea[taleaIdx]) {
int octave = 3 + random(3); int octave = 3 + random(3);
int noteScaleIndex = color[colorIdx % colorLength]; int noteValue = color[colorIdx % colorLength];
sequence[track][i].note = 12 * octave + scaleNotes[noteScaleIndex]; sequence[track][i].note = 12 * octave + noteValue;
sequence[track][i].accent = (i % 4 == 0); // Accent on downbeats sequence[track][i].accent = (i % 4 == 0); // Accent on downbeats
sequence[track][i].tie = false; sequence[track][i].tie = false;
colorIdx++; colorIdx++;
@ -82,7 +85,7 @@ public:
sortArray(scaleNotes, numScaleNotes); 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) // Mutation: rotate the talea (rhythmic displacement)
int rotation = random(1, numSteps); int rotation = random(1, numSteps);
Step temp[NUM_STEPS]; Step temp[NUM_STEPS];

View File

@ -29,9 +29,13 @@ const uint8_t numRuleSets = sizeof(ruleSets) / sizeof(RuleSet);
class LSystemStrategy : public MelodyStrategy { class LSystemStrategy : public MelodyStrategy {
public: 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); 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 // Fill with silence if no scale
for (int i = 0; i < numSteps; i++) { for (int i = 0; i < numSteps; i++) {
sequence[track][i].note = -1; sequence[track][i].note = -1;
@ -68,7 +72,7 @@ public:
// 2. Interpret the string to create the sequence // 2. Interpret the string to create the sequence
int stepIndex = 0; int stepIndex = 0;
int noteIndex = random(numScaleNotes); int noteIndex = random(sourceNoteCount);
int octave = 4; int octave = 4;
// Stack for branching rules // Stack for branching rules
@ -86,16 +90,16 @@ public:
switch(c) { switch(c) {
case 'F': case 'G': case 'A': case 'B': // Characters that draw notes 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].accent = (random(100) < accentChance);
sequence[track][stepIndex].tie = (random(100) < tieChance); sequence[track][stepIndex].tie = (random(100) < tieChance);
stepIndex++; stepIndex++;
break; break;
case '+': // Go up in scale case '+': // Go up in scale
noteIndex = (noteIndex + 1) % numScaleNotes; noteIndex = (noteIndex + 1) % sourceNoteCount;
break; break;
case '-': // Go down in scale case '-': // Go down in scale
noteIndex = (noteIndex - 1 + numScaleNotes) % numScaleNotes; noteIndex = (noteIndex - 1 + sourceNoteCount) % sourceNoteCount;
break; break;
case '[': // Push current state case '[': // Push current state
if(stack_ptr < 8) { if(stack_ptr < 8) {
@ -140,7 +144,7 @@ public:
sortArray(scaleNotes, numScaleNotes); 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 // Swap two non-rest steps to create a variation
int s1 = random(numSteps); int s1 = random(numSteps);
int s2 = random(numSteps); int s2 = random(numSteps);

View File

@ -6,16 +6,23 @@
class LuckyStrategy : public MelodyStrategy { class LuckyStrategy : public MelodyStrategy {
public: 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); randomSeed(seed);
if (numScaleNotes == 0) return;
int noteChance = intensity * 9; // 10% to 90% int noteChance = intensity * 9; // 10% to 90%
int accentChance = intensity * 6; // 6% to 60% int accentChance = intensity * 6; // 6% to 60%
for (int i = 0; i < numSteps; i++) { for (int i = 0; i < numSteps; i++) {
int octave = random(3) + 3; // 3, 4, 5 (Base is 4) 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].accent = (random(100) < accentChance);
sequence[track][i].tie = (random(100) < 20); sequence[track][i].tie = (random(100) < 20);
} }
@ -37,7 +44,7 @@ public:
sortArray(scaleNotes, numScaleNotes); 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 // Mutate 1 or 2 steps
int count = random(1, 3); int count = random(1, 3);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {

View File

@ -6,17 +6,21 @@
class MarkovStrategy : public MelodyStrategy { class MarkovStrategy : public MelodyStrategy {
public: 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); 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 // Transition matrix: weights for moving from index i to index j
// Max scale size is 12 // Max scale size is 12
uint8_t matrix[12][12]; uint8_t matrix[12][12];
// Initialize matrix with weighted probabilities // Initialize matrix with weighted probabilities
for (int i = 0; i < numScaleNotes; i++) { for (int i = 0; i < sourceNoteCount; i++) {
for (int j = 0; j < numScaleNotes; j++) { for (int j = 0; j < sourceNoteCount; j++) {
// Base random weight // Base random weight
matrix[i][j] = random(1, 8); 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++) { for (int i = 0; i < numSteps; i++) {
// Intensity 1 = 60% rest, Intensity 10 = 0% rest // Intensity 1 = 60% rest, Intensity 10 = 0% rest
@ -44,13 +48,13 @@ public:
// Select next note based on matrix // Select next note based on matrix
int totalWeight = 0; int totalWeight = 0;
for (int j = 0; j < numScaleNotes; j++) { for (int j = 0; j < sourceNoteCount; j++) {
totalWeight += matrix[currentIdx][j]; totalWeight += matrix[currentIdx][j];
} }
int r = random(totalWeight); int r = random(totalWeight);
int nextIdx = 0; int nextIdx = 0;
for (int j = 0; j < numScaleNotes; j++) { for (int j = 0; j < sourceNoteCount; j++) {
r -= matrix[currentIdx][j]; r -= matrix[currentIdx][j];
if (r < 0) { if (r < 0) {
nextIdx = j; nextIdx = j;
@ -65,7 +69,7 @@ public:
if (rOct < 20) octave = 3; if (rOct < 20) octave = 3;
else if (rOct > 80) octave = 5; 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].accent = (random(100) < 25);
sequence[track][i].tie = (random(100) < 15); sequence[track][i].tie = (random(100) < 15);
} }
@ -87,15 +91,19 @@ public:
sortArray(scaleNotes, numScaleNotes); 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 // Drift mutation: pick a note and move it stepwise in the scale
int s = random(numSteps); int s = random(numSteps);
if (sequence[track][s].note != -1) { if (sequence[track][s].note != -1) {
int currentNoteVal = sequence[track][s].note % 12; int currentNoteVal = sequence[track][s].note % 12;
int idx = -1; int idx = -1;
// Find index in scale // Find index in scale
for(int i=0; i<numScaleNotes; i++) { for(int i=0; i<sourceNoteCount; i++) {
if (scaleNotes[i] == currentNoteVal) { if (sourceNotes[i] == currentNoteVal) {
idx = i; idx = i;
break; break;
} }
@ -103,17 +111,17 @@ public:
if (idx != -1) { if (idx != -1) {
// Move up or down 1 step in scale // Move up or down 1 step in scale
if (random(2) == 0) idx = (idx + 1) % numScaleNotes; if (random(2) == 0) idx = (idx + 1) % sourceNoteCount;
else idx = (idx - 1 + numScaleNotes) % numScaleNotes; else idx = (idx - 1 + sourceNoteCount) % sourceNoteCount;
int octave = sequence[track][s].note / 12; int octave = sequence[track][s].note / 12;
sequence[track][s].note = 12 * octave + scaleNotes[idx]; sequence[track][s].note = 12 * octave + sourceNotes[idx];
} }
} else { } else {
// Chance to fill a rest // Chance to fill a rest
if (random(100) < 25) { if (random(100) < 25) {
int octave = 3 + random(3); 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].accent = false;
sequence[track][s].tie = false; sequence[track][s].tie = false;
} }

View File

@ -6,9 +6,9 @@
class MelodyStrategy { class MelodyStrategy {
public: 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 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 const char* getName() = 0;
virtual ~MelodyStrategy() {} virtual ~MelodyStrategy() {}
}; };

View File

@ -3,24 +3,65 @@
#include "MidiDriver.h" #include "MidiDriver.h"
#include <Arduino.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]) { void SequenceGenerator::generateTrackData(int track, int themeType, Step (*target)[NUM_STEPS]) {
randomSeed(melodySeeds[track] + themeType * 12345); randomSeed(melodySeeds[track] + themeType * 12345);
strategies[currentStrategyIndices[track]]->generate(target, track, numSteps[track], scaleNotes, numScaleNotes, melodySeeds[track] + themeType * 12345, trackIntensity[track]); int chordNotes[12];
} int numChordNotes = 0;
getChordNotes(chordNotes, numChordNotes);
void SequenceGenerator::generateSequenceData(int themeType, Step (*target)[NUM_STEPS]) { strategies[currentStrategyIndices[track]]->generate(target, track, numSteps[track], scaleNotes, numScaleNotes, chordNotes, numChordNotes, melodySeeds[track] + themeType * 12345, trackIntensity[track]);
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]);
}
}
} }
void SequenceGenerator::generateRandomScale() { void SequenceGenerator::generateRandomScale() {
@ -75,6 +116,24 @@ void SequenceGenerator::updateScale() {
midi.unlock(); 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) { void SequenceGenerator::pickRandomScaleType(int themeType) {
unsigned long seed = themeType * 9999; unsigned long seed = themeType * 9999;
for(int i=0; i<NUM_TRACKS; i++) seed += melodySeeds[i]; for(int i=0; i<NUM_TRACKS; i++) seed += melodySeeds[i];

View File

@ -2,7 +2,6 @@
#define SEQUENCE_GENERATOR_H #define SEQUENCE_GENERATOR_H
#include "TrackerTypes.h" #include "TrackerTypes.h"
#include "config.h"
class SequenceGenerator { class SequenceGenerator {
public: public:
@ -12,6 +11,7 @@ public:
static void generateRandomScale(); static void generateRandomScale();
static void updateScale(); static void updateScale();
static void pickRandomScaleType(int themeType); static void pickRandomScaleType(int themeType);
static void getChordNotes(int* chordNotes, int& numChordNotes);
}; };
#endif #endif

View File

@ -6,9 +6,13 @@
class WaveStrategy : public MelodyStrategy { class WaveStrategy : public MelodyStrategy {
public: 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); randomSeed(seed);
if (numScaleNotes == 0) return;
int* sourceNotes = (numChordNotes > 0) ? chordNotes : scaleNotes;
int sourceNoteCount = (numChordNotes > 0) ? numChordNotes : numScaleNotes;
if (sourceNoteCount == 0) return;
// Wave parameters // Wave parameters
// Frequency: How many cycles per sequence // Frequency: How many cycles per sequence
@ -19,8 +23,8 @@ public:
int type = random(3); int type = random(3);
// Center pitch (note index) // Center pitch (note index)
int centerIdx = numScaleNotes * 2; // Middle of 4 octaves roughly int centerIdx = sourceNoteCount * 2; // Middle of 4 octaves roughly
int amp = numScaleNotes + (intensity); // Amplitude in scale degrees int amp = sourceNoteCount + (intensity); // Amplitude in scale degrees
for (int i = 0; i < numSteps; i++) { for (int i = 0; i < numSteps; i++) {
float t = (float)i / (float)numSteps; // 0.0 to 1.0 float t = (float)i / (float)numSteps; // 0.0 to 1.0
@ -43,17 +47,17 @@ public:
int totalIdx = centerIdx + noteIdxOffset; int totalIdx = centerIdx + noteIdxOffset;
// Normalize totalIdx // Normalize totalIdx
while(totalIdx < 0) totalIdx += numScaleNotes; while(totalIdx < 0) totalIdx += sourceNoteCount;
int octave = 2 + (totalIdx / numScaleNotes); int octave = 2 + (totalIdx / sourceNoteCount);
int scaleIdx = totalIdx % numScaleNotes; int noteIdx = totalIdx % sourceNoteCount;
if (octave < 0) octave = 0; if (octave < 0) octave = 0;
if (octave > 8) octave = 8; if (octave > 8) octave = 8;
// Rhythmic density based on intensity // Rhythmic density based on intensity
if (random(100) < (40 + intensity * 5)) { 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].accent = (val > 0.8f); // Accent peaks
sequence[track][i].tie = false; sequence[track][i].tie = false;
} else { } else {
@ -77,7 +81,7 @@ public:
sortArray(scaleNotes, numScaleNotes); 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 // Phase shift the sequence
int shift = random(1, 4); int shift = random(1, 4);
Step temp[NUM_STEPS]; Step temp[NUM_STEPS];