Drone strategy and fixed MIDI panic

This commit is contained in:
Dejvino 2026-02-22 23:03:03 +01:00
parent 33c6329f0b
commit 4e4ba0d0e4
7 changed files with 172 additions and 10 deletions

99
DroneStrategy.h Normal file
View File

@ -0,0 +1,99 @@
#ifndef DRONE_STRATEGY_H
#define DRONE_STRATEGY_H
#include "MelodyStrategy.h"
#include <Arduino.h>
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

View File

@ -1,4 +1,5 @@
#include "MidiDriver.h" #include "MidiDriver.h"
#include "config.h"
// MIDI UART Pins (GP0/GP1) // MIDI UART Pins (GP0/GP1)
#define PIN_MIDI_TX 0 #define PIN_MIDI_TX 0
@ -7,6 +8,7 @@
MidiDriver midi; MidiDriver midi;
MidiDriver::MidiDriver() : lastInputNote(-1), lastInputVelocity(0), _runningStatus(0), _byteIndex(0), _data1(0), _data2(0) { MidiDriver::MidiDriver() : lastInputNote(-1), lastInputVelocity(0), _runningStatus(0), _byteIndex(0), _data1(0), _data2(0) {
memset(activeNotes, 0, sizeof(activeNotes));
} }
void MidiDriver::begin() { void MidiDriver::begin() {
@ -88,6 +90,20 @@ void MidiDriver::unlock() {
} }
void MidiDriver::sendNoteOn(uint8_t note, uint8_t velocity, uint8_t channel) { 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); uint8_t status = 0x90 | (channel - 1);
Serial1.write(status); Serial1.write(status);
Serial1.write(note); 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) { 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); uint8_t status = 0x80 | (channel - 1);
Serial1.write(status); Serial1.write(status);
Serial1.write(note); Serial1.write(note);
@ -102,10 +130,33 @@ void MidiDriver::sendNoteOff(uint8_t note, uint8_t channel) {
} }
void MidiDriver::sendRealtime(uint8_t status) { 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); Serial1.write(status);
} }
void MidiDriver::panic(uint8_t channel) { 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); uint8_t status = 0xB0 | (channel - 1);
Serial1.write(status); Serial1.write(status);
Serial1.write((uint8_t)123); // All Notes Off Serial1.write((uint8_t)123); // All Notes Off

View File

@ -27,6 +27,7 @@ private:
uint8_t _byteIndex; uint8_t _byteIndex;
uint8_t _data1; uint8_t _data1;
uint8_t _data2; uint8_t _data2;
bool activeNotes[16][128];
}; };
extern MidiDriver midi; extern MidiDriver midi;

View File

@ -60,9 +60,13 @@ static void handlePlayback() {
} }
playbackStep++; playbackStep++;
bool justPanicked = false;
if (playbackStep >= numSteps) { if (playbackStep >= numSteps) {
playbackStep = 0; playbackStep = 0;
for (int i=0; i<NUM_TRACKS; i++) midi.panic(midiChannels[i]);
justPanicked = true;
// Theme change // Theme change
if (sequenceChangeScheduled && queuedTheme != -1) { if (sequenceChangeScheduled && queuedTheme != -1) {
currentThemeIndex = queuedTheme; currentThemeIndex = queuedTheme;
@ -82,8 +86,6 @@ static void handlePlayback() {
midi.unlock(); midi.unlock();
} }
for (int i=0; i<NUM_TRACKS; i++) midi.panic(midiChannels[i]);
if (sequenceChangeScheduled) { if (sequenceChangeScheduled) {
memcpy(local_sequence, local_nextSequence, sizeof(local_sequence)); memcpy(local_sequence, local_nextSequence, sizeof(local_sequence));
midi.lock(); midi.lock();
@ -112,16 +114,21 @@ static void handlePlayback() {
// Note On for new step // Note On for new step
for(int t=0; t<NUM_TRACKS; t++) { for(int t=0; t<NUM_TRACKS; t++) {
int trackChannel = midiChannels[t]; int trackChannel = midiChannels[t];
if (!trackMute[t] && local_sequence[t][playbackStep].note != -1) {
uint8_t velocity = local_sequence[t][playbackStep].accent ? 127 : 100;
midi.sendNoteOn(local_sequence[t][playbackStep].note, velocity, trackChannel);
}
int prevStep = (playbackStep == 0) ? numSteps - 1 : playbackStep - 1; int prevStep = (playbackStep == 0) ? numSteps - 1 : playbackStep - 1;
bool wasTied = local_sequence[t][prevStep].tie && (local_sequence[t][playbackStep].note != -1); bool wasTied = local_sequence[t][prevStep].tie && (local_sequence[t][playbackStep].note != -1);
int prevNote = local_sequence[t][prevStep].note; int prevNote = local_sequence[t][prevStep].note;
int currentNote = local_sequence[t][playbackStep].note;
// If tied to the SAME note, do not retrigger (sustain)
bool isContinuing = wasTied && (prevNote == currentNote) && !justPanicked;
if (!trackMute[t] && currentNote != -1 && !isContinuing) {
uint8_t velocity = local_sequence[t][playbackStep].accent ? 127 : 100;
midi.sendNoteOn(currentNote, velocity, trackChannel);
}
// Note Off for previous step (if tied - delayed Note Off) // Note Off for previous step (if tied - delayed Note Off)
if (wasTied && prevNote != -1) { if (wasTied && prevNote != -1 && !isContinuing && !justPanicked) {
midi.sendNoteOff(prevNote, trackChannel); midi.sendNoteOff(prevNote, trackChannel);
} }
} }

View File

@ -5,6 +5,7 @@
#include "MarkovStrategy.h" #include "MarkovStrategy.h"
#include "CellularAutomataStrategy.h" #include "CellularAutomataStrategy.h"
#include "LSystemStrategy.h" #include "LSystemStrategy.h"
#include "DroneStrategy.h"
// Global state variables // Global state variables
Step sequence[NUM_TRACKS][NUM_STEPS]; Step sequence[NUM_TRACKS][NUM_STEPS];
@ -115,8 +116,8 @@ extern const uint32_t EEPROM_MAGIC = 0x4242424E;
MelodyStrategy* strategies[] = { MelodyStrategy* strategies[] = {
new LuckyStrategy(), new ArpStrategy(), new EuclideanStrategy(), new LuckyStrategy(), new ArpStrategy(), new EuclideanStrategy(),
new MarkovStrategy(), new CellularAutomataStrategy(), new LSystemStrategy()}; new MarkovStrategy(), new CellularAutomataStrategy(), new LSystemStrategy(), new DroneStrategy()};
extern const int numStrategies = 6; extern const int numStrategies = 7;
int currentStrategyIndices[NUM_TRACKS]; int currentStrategyIndices[NUM_TRACKS];
volatile PlayMode playMode = MODE_POLY; volatile PlayMode playMode = MODE_POLY;

View File

@ -9,6 +9,7 @@
#include "MarkovStrategy.h" #include "MarkovStrategy.h"
#include "CellularAutomataStrategy.h" #include "CellularAutomataStrategy.h"
#include "LSystemStrategy.h" #include "LSystemStrategy.h"
#include "DroneStrategy.h"
#include "MidiDriver.h" #include "MidiDriver.h"
#include "UIManager.h" #include "UIManager.h"
#include "config.h" #include "config.h"

View File

@ -19,4 +19,6 @@
#define NUM_STEPS 8 #define NUM_STEPS 8
#define NUM_TRACKS 4 #define NUM_TRACKS 4
#define MIDI_DEBUG
#endif #endif