diff --git a/DroneStrategy.h b/DroneStrategy.h new file mode 100644 index 0000000..0328870 --- /dev/null +++ b/DroneStrategy.h @@ -0,0 +1,99 @@ +#ifndef DRONE_STRATEGY_H +#define DRONE_STRATEGY_H + +#include "MelodyStrategy.h" +#include + +class DroneStrategy : public MelodyStrategy { +public: + void generate(Step (*sequence)[NUM_STEPS], int track, int numSteps, int* scaleNotes, int numScaleNotes, int seed, int intensity) override { + randomSeed(seed); + if (numScaleNotes == 0) return; + + // Intensity 1: Very long notes (1-2 per sequence) + // Intensity 10: Short notes (active) + + // Map intensity to duration range + // Low intensity (1) -> Max duration is full sequence + // High intensity (10) -> Max duration is short (e.g. 4 steps) + int maxDur = map(intensity, 1, 10, numSteps, 4); + int minDur = map(intensity, 1, 10, numSteps, 1); + + if (minDur < 1) minDur = 1; + if (maxDur < minDur) maxDur = minDur; + + int currentStep = 0; + + while (currentStep < numSteps) { + int duration = random(minDur, maxDur + 1); + + // Pick a note from the scale + // Prefer lower octaves for drones (3 and 4) + int octave = 3 + random(2); + int noteIndex = random(numScaleNotes); + int note = 12 * octave + scaleNotes[noteIndex]; + + for (int k = 0; k < duration; k++) { + int stepIndex = currentStep + k; + if (stepIndex >= numSteps) break; + + sequence[track][stepIndex].note = note; + // Tie notes to create continuous sound (legato) + sequence[track][stepIndex].tie = true; + // Accent only the start of the note + sequence[track][stepIndex].accent = (k == 0); + } + + currentStep += duration; + } + randomSeed(micros()); + } + + void generateScale(int* scaleNotes, int& numScaleNotes) override { + // Drones work well with fewer notes (Pentatonic or Triad) + numScaleNotes = random(3, 6); + for (int i = 0; i < 12; i++) { + scaleNotes[i] = i; + } + // Shuffle + 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 { + // Mutate by shifting the pitch of a random segment + int s = random(numSteps); + if (sequence[track][s].note != -1) { + int originalNote = sequence[track][s].note; + + // Find the bounds of this note + int start = s; + while (start > 0 && sequence[track][start-1].note == originalNote && sequence[track][start-1].tie) { + start--; + } + int end = s; + while (end < numSteps - 1 && sequence[track][end].tie && sequence[track][end+1].note == originalNote) { + end++; + } + + // Pick a new note + int octave = 3 + random(2); + int newNote = 12 * octave + scaleNotes[random(numScaleNotes)]; + + for (int i = start; i <= end; i++) { + sequence[track][i].note = newNote; + } + } + } + + const char* getName() override { + return "Drone"; + } +}; + +#endif \ No newline at end of file diff --git a/MidiDriver.cpp b/MidiDriver.cpp index d614f9e..bc719b5 100644 --- a/MidiDriver.cpp +++ b/MidiDriver.cpp @@ -1,4 +1,5 @@ #include "MidiDriver.h" +#include "config.h" // MIDI UART Pins (GP0/GP1) #define PIN_MIDI_TX 0 @@ -7,6 +8,7 @@ MidiDriver midi; MidiDriver::MidiDriver() : lastInputNote(-1), lastInputVelocity(0), _runningStatus(0), _byteIndex(0), _data1(0), _data2(0) { + memset(activeNotes, 0, sizeof(activeNotes)); } void MidiDriver::begin() { @@ -88,6 +90,20 @@ void MidiDriver::unlock() { } void MidiDriver::sendNoteOn(uint8_t note, uint8_t velocity, uint8_t channel) { +#ifdef MIDI_DEBUG + Serial.print(F("[")); + Serial.print(millis()); + Serial.print(F("] ")); + Serial.print(F("MIDI OUT: Note On CH:")); + Serial.print(channel); + Serial.print(F(" Note:")); + Serial.print(note); + Serial.print(F(" Vel:")); + Serial.println(velocity); +#endif + if (channel >= 1 && channel <= 16 && note < 128) { + activeNotes[channel - 1][note] = (velocity > 0); + } uint8_t status = 0x90 | (channel - 1); Serial1.write(status); Serial1.write(note); @@ -95,6 +111,18 @@ void MidiDriver::sendNoteOn(uint8_t note, uint8_t velocity, uint8_t channel) { } void MidiDriver::sendNoteOff(uint8_t note, uint8_t channel) { +#ifdef MIDI_DEBUG + Serial.print(F("[")); + Serial.print(millis()); + Serial.print(F("] ")); + Serial.print(F("MIDI OUT: Note Off CH:")); + Serial.print(channel); + Serial.print(F(" Note:")); + Serial.println(note); +#endif + if (channel >= 1 && channel <= 16 && note < 128) { + activeNotes[channel - 1][note] = false; + } uint8_t status = 0x80 | (channel - 1); Serial1.write(status); Serial1.write(note); @@ -102,10 +130,33 @@ void MidiDriver::sendNoteOff(uint8_t note, uint8_t channel) { } void MidiDriver::sendRealtime(uint8_t status) { +#ifdef MIDI_DEBUG + if (status != 0xF8) { + Serial.print(F("[")); + Serial.print(millis()); + Serial.print(F("] ")); + Serial.print(F("MIDI OUT: Realtime 0x")); + Serial.println(status, HEX); + } +#endif Serial1.write(status); } void MidiDriver::panic(uint8_t channel) { +#ifdef MIDI_DEBUG + Serial.print(F("[")); + Serial.print(millis()); + Serial.print(F("] ")); + Serial.print(F("MIDI OUT: Panic CH:")); + Serial.println(channel); +#endif + if (channel >= 1 && channel <= 16) { + for (int i = 0; i < 128; i++) { + if (activeNotes[channel - 1][i]) { + sendNoteOff(i, channel); + } + } + } uint8_t status = 0xB0 | (channel - 1); Serial1.write(status); Serial1.write((uint8_t)123); // All Notes Off diff --git a/MidiDriver.h b/MidiDriver.h index afa1c9f..88a40a9 100644 --- a/MidiDriver.h +++ b/MidiDriver.h @@ -27,6 +27,7 @@ private: uint8_t _byteIndex; uint8_t _data1; uint8_t _data2; + bool activeNotes[16][128]; }; extern MidiDriver midi; diff --git a/PlaybackThread.cpp b/PlaybackThread.cpp index b1150ef..e4f987f 100644 --- a/PlaybackThread.cpp +++ b/PlaybackThread.cpp @@ -60,9 +60,13 @@ static void handlePlayback() { } playbackStep++; + bool justPanicked = false; if (playbackStep >= numSteps) { playbackStep = 0; + for (int i=0; i