From aa51e1e95a4dc2c5c5369e2c5f3f3c9d0dde97e1 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Tue, 10 Mar 2026 21:58:03 +0100 Subject: [PATCH] More harmony through chord notes --- ArpStrategy.h | 46 +++++++++-------- CallAndResponseStrategy.h | 103 ++++++++++++++----------------------- CellularAutomataStrategy.h | 100 ++++++++--------------------------- DroneStrategy.h | 19 +++++-- EuclideanStrategy.h | 20 +++++-- IsorhythmStrategy.h | 31 ++++++----- LSystemStrategy.h | 18 ++++--- LuckyStrategy.h | 15 ++++-- MarkovStrategy.h | 38 ++++++++------ MelodyStrategy.h | 4 +- SequenceGenerator.cpp | 91 ++++++++++++++++++++++++++------ SequenceGenerator.h | 4 +- WaveStrategy.h | 22 ++++---- 13 files changed, 269 insertions(+), 242 deletions(-) diff --git a/ArpStrategy.h b/ArpStrategy.h index 0cfd751..21da0d9 100644 --- a/ArpStrategy.h +++ b/ArpStrategy.h @@ -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 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 - #ifndef CALL_AND_RESPONSE_STRATEGY_H #define CALL_AND_RESPONSE_STRATEGY_H +#include "MelodyStrategy.h" +#include + 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 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 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> pattern) & 1; + int index = (left << 2) | (middle << 1) | right; + next_cells[i] = (rule >> index) & 1; } - for(int i=0; i> 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 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; diff --git a/EuclideanStrategy.h b/EuclideanStrategy.h index 112d7a1..2162ba3 100644 --- a/EuclideanStrategy.h +++ b/EuclideanStrategy.h @@ -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; } } } diff --git a/IsorhythmStrategy.h b/IsorhythmStrategy.h index edda07a..c2ed5ce 100644 --- a/IsorhythmStrategy.h +++ b/IsorhythmStrategy.h @@ -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 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); diff --git a/LuckyStrategy.h b/LuckyStrategy.h index b97e3da..810abb1 100644 --- a/LuckyStrategy.h +++ b/LuckyStrategy.h @@ -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++) { diff --git a/MarkovStrategy.h b/MarkovStrategy.h index f6e32f5..7dd8bef 100644 --- a/MarkovStrategy.h +++ b/MarkovStrategy.h @@ -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 +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; imutate(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; imutate(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 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];