More harmony through chord notes
This commit is contained in:
parent
f34f960358
commit
aa51e1e95a
@ -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);
|
||||||
|
|||||||
@ -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";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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];
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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++) {
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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() {}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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];
|
||||||
|
|||||||
@ -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
|
||||||
@ -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];
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user