Compare commits

..

6 Commits

Author SHA1 Message Date
Dejvino
84ef40a555 Melody randomizes tracks length 2026-03-05 00:11:35 +01:00
Dejvino
27fd42442c Dual mono view 2026-03-04 23:51:07 +01:00
Dejvino
6b27ef6d0b Mutation has a lower chance with low intensity 2026-03-04 23:35:24 +01:00
Dejvino
1014e371ee Steps are set in Track section 2026-03-04 23:32:57 +01:00
Dejvino
e9f9682663 Polyrhythm tracks 2026-03-04 23:20:27 +01:00
Dejvino
288a363618 Adjustments for 2x1 Neopixel display 2026-03-04 22:38:11 +01:00
8 changed files with 158 additions and 83 deletions

View File

@ -4,6 +4,7 @@
#include "config.h" #include "config.h"
#include "SharedState.h" #include "SharedState.h"
static int local_numSteps[NUM_TRACKS];
static Step local_sequence[NUM_TRACKS][NUM_STEPS]; static Step local_sequence[NUM_TRACKS][NUM_STEPS];
static Step local_nextSequence[NUM_TRACKS][NUM_STEPS]; static Step local_nextSequence[NUM_TRACKS][NUM_STEPS];
@ -42,16 +43,24 @@ static void handlePlayback() {
midi.lock(); midi.lock();
memcpy(local_sequence, sequence, sizeof(local_sequence)); memcpy(local_sequence, sequence, sizeof(local_sequence));
memcpy(local_nextSequence, nextSequence, sizeof(local_nextSequence)); memcpy(local_nextSequence, nextSequence, sizeof(local_nextSequence));
for (int i = 0; i < NUM_TRACKS; i++) local_numSteps[i] = numSteps[i];
midi.unlock(); midi.unlock();
int master_len = 0;
for(int i=0; i<NUM_TRACKS; i++) {
if(local_numSteps[i] > master_len) master_len = local_numSteps[i];
}
if (master_len == 0) master_len = NUM_STEPS;
for(int t=0; t<NUM_TRACKS; t++) { for(int t=0; t<NUM_TRACKS; t++) {
int trackChannel = midiChannels[t]; int trackChannel = midiChannels[t];
int nextStep = playbackStep + 1; int track_len = local_numSteps[t];
if (nextStep >= numSteps) nextStep = 0; int current_step = playbackStep % track_len;
int next_step = (playbackStep + 1) % track_len;
// Determine if we are tying to the next note // Determine if we are tying to the next note
bool isTied = local_sequence[t][playbackStep].tie && (local_sequence[t][nextStep].note != -1); bool isTied = local_sequence[t][current_step].tie && (local_sequence[t][next_step].note != -1);
int prevNote = local_sequence[t][playbackStep].note; int prevNote = local_sequence[t][current_step].note;
// Note Off for previous step (if NOT tied) // Note Off for previous step (if NOT tied)
if (!isTied && prevNote != -1) { if (!isTied && prevNote != -1) {
@ -61,9 +70,8 @@ static void handlePlayback() {
playbackStep++; playbackStep++;
bool justPanicked = false; bool justPanicked = false;
if (playbackStep >= numSteps) { // When the global step counter completes a full cycle of the longest track
playbackStep = 0; if ((playbackStep > 0) && ((playbackStep % master_len) == 0)) {
for (int i=0; i<NUM_TRACKS; i++) midi.panic(midiChannels[i]); for (int i=0; i<NUM_TRACKS; i++) midi.panic(midiChannels[i]);
justPanicked = true; justPanicked = true;
@ -114,16 +122,18 @@ 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];
int prevStep = (playbackStep == 0) ? numSteps - 1 : playbackStep - 1; int track_len = local_numSteps[t];
bool wasTied = local_sequence[t][prevStep].tie && (local_sequence[t][playbackStep].note != -1); int current_step = playbackStep % track_len;
int prevNote = local_sequence[t][prevStep].note; int prev_step = (playbackStep == 0) ? track_len - 1 : (playbackStep - 1) % track_len;
int currentNote = local_sequence[t][playbackStep].note; bool wasTied = local_sequence[t][prev_step].tie && (local_sequence[t][current_step].note != -1);
int prevNote = local_sequence[t][prev_step].note;
int currentNote = local_sequence[t][current_step].note;
// If tied to the SAME note, do not retrigger (sustain) // If tied to the SAME note, do not retrigger (sustain)
bool isContinuing = wasTied && (prevNote == currentNote) && !justPanicked; bool isContinuing = wasTied && (prevNote == currentNote) && !justPanicked;
if (!trackMute[t] && currentNote != -1 && !isContinuing) { if (!trackMute[t] && currentNote != -1 && !isContinuing) {
uint8_t velocity = local_sequence[t][playbackStep].accent ? 127 : 100; uint8_t velocity = local_sequence[t][current_step].accent ? 127 : 100;
midi.sendNoteOn(currentNote, velocity, trackChannel); midi.sendNoteOn(currentNote, velocity, trackChannel);
} }

View File

@ -62,11 +62,12 @@ void setup() {
EEPROM.begin(4096); EEPROM.begin(4096);
if (!loadSequence()) { if (!loadSequence()) {
Serial.println(F("Starting fresh instead.")); Serial.println(F("Starting fresh instead."));
numSteps = NUM_STEPS;
for(int i=0; i<NUM_TRACKS; i++) { for(int i=0; i<NUM_TRACKS; i++) {
midiChannels[i] = i + 1; midiChannels[i] = i + 1;
melodySeeds[i] = random(10000); melodySeeds[i] = random(10000);
randomSeed(melodySeeds[i]);
numSteps[i] = random(1, NUM_STEPS + 1);
currentStrategyIndices[i] = 0; currentStrategyIndices[i] = 0;
trackMute[i] = false; trackMute[i] = false;
} }

View File

@ -23,12 +23,12 @@ MenuItem menuItems[] = {
{ "Playback", MENU_ID_PLAYBACK, false, false, 1 }, { "Playback", MENU_ID_PLAYBACK, false, false, 1 },
{ "Melody", MENU_ID_MELODY, false, false, 1 }, { "Melody", MENU_ID_MELODY, false, false, 1 },
{ "Scale", MENU_ID_SCALE, false, false, 1 }, { "Scale", MENU_ID_SCALE, false, false, 1 },
{ "Steps", MENU_ID_STEPS, false, false, 1 },
{ "Tempo", MENU_ID_TEMPO, false, false, 1 }, { "Tempo", MENU_ID_TEMPO, false, false, 1 },
{ "Song Mode", MENU_ID_SONG_MODE, false, false, 1 }, { "Song Mode", MENU_ID_SONG_MODE, false, false, 1 },
{ "Track", MENU_ID_GROUP_TRACK, true, true, 0 }, { "Track", MENU_ID_GROUP_TRACK, true, true, 0 },
{ "Track", MENU_ID_TRACK_SELECT, false, false, 1 }, { "Track", MENU_ID_TRACK_SELECT, false, false, 1 },
{ "Mute", MENU_ID_MUTE, false, false, 1 }, { "Mute", MENU_ID_MUTE, false, false, 1 },
{ "Steps", MENU_ID_STEPS, false, false, 1 },
{ "Flavour", MENU_ID_FLAVOUR, false, false, 1 }, { "Flavour", MENU_ID_FLAVOUR, false, false, 1 },
{ "Intensity", MENU_ID_INTENSITY, false, false, 1 }, { "Intensity", MENU_ID_INTENSITY, false, false, 1 },
{ "Mutation", MENU_ID_MUTATION, false, false, 1 }, { "Mutation", MENU_ID_MUTATION, false, false, 1 },
@ -104,7 +104,7 @@ bool isItemVisible(int index) {
int menuSelection = 0; int menuSelection = 0;
volatile bool trackMute[NUM_TRACKS]; volatile bool trackMute[NUM_TRACKS];
int randomizeTrack = 0; int randomizeTrack = 0;
volatile int numSteps = NUM_STEPS; volatile int numSteps[NUM_TRACKS] = {NUM_STEPS, NUM_STEPS, NUM_STEPS, NUM_STEPS};
volatile int playbackStep = 0; volatile int playbackStep = 0;
volatile int midiChannels[NUM_TRACKS]; volatile int midiChannels[NUM_TRACKS];
int scaleNotes[12]; int scaleNotes[12];

View File

@ -19,13 +19,13 @@ enum MenuItemID {
MENU_ID_PLAYBACK, MENU_ID_PLAYBACK,
MENU_ID_MELODY, MENU_ID_MELODY,
MENU_ID_SCALE, MENU_ID_SCALE,
MENU_ID_STEPS,
MENU_ID_TEMPO, MENU_ID_TEMPO,
MENU_ID_SONG_MODE, MENU_ID_SONG_MODE,
MENU_ID_GROUP_TRACK, MENU_ID_GROUP_TRACK,
MENU_ID_TRACK_SELECT, MENU_ID_TRACK_SELECT,
MENU_ID_MUTE, MENU_ID_MUTE,
MENU_ID_STEPS,
MENU_ID_FLAVOUR, MENU_ID_FLAVOUR,
MENU_ID_INTENSITY, MENU_ID_INTENSITY,
MENU_ID_MUTATION, MENU_ID_MUTATION,
@ -65,7 +65,7 @@ bool isItemVisible(int index);
extern int menuSelection; extern int menuSelection;
extern volatile bool trackMute[NUM_TRACKS]; extern volatile bool trackMute[NUM_TRACKS];
extern int randomizeTrack; extern int randomizeTrack;
extern volatile int numSteps; extern volatile int numSteps[NUM_TRACKS];
extern volatile int playbackStep; extern volatile int playbackStep;
extern volatile int midiChannels[NUM_TRACKS]; extern volatile int midiChannels[NUM_TRACKS];
extern int scaleNotes[12]; extern int scaleNotes[12];

View File

@ -7,8 +7,10 @@
#define SCREEN_HEIGHT 64 #define SCREEN_HEIGHT 64
#define OLED_RESET -1 #define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C #define SCREEN_ADDRESS 0x3C
#define PIN_NEOPIXEL 2 #define PIN_NEOPIXEL 6
#define NUM_PIXELS 64 #define NEOPIXELS_X 16
#define NEOPIXELS_Y 8
#define NUM_PIXELS 64*2 // 8x8 LEDs in 2 panels
UIManager ui; UIManager ui;
@ -49,8 +51,8 @@ void UIManager::showMessage(const char* msg) {
void UIManager::draw(UIState currentState, int menuSelection, void UIManager::draw(UIState currentState, int menuSelection,
int midiChannel, int tempo, MelodyStrategy* currentStrategy, int midiChannel, int tempo, MelodyStrategy* currentStrategy,
int queuedTheme, int currentThemeIndex, int queuedTheme, int currentThemeIndex,
int numScaleNotes, const int* scaleNotes, int melodySeed, int numSteps, int numScaleNotes, const int* scaleNotes, int melodySeed, int currentTrackNumSteps,
bool mutationEnabled, bool songModeEnabled, bool mutationEnabled, bool songModeEnabled,
const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying, const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying,
int randomizeTrack, const bool* trackMute, const int* trackIntensities) { int randomizeTrack, const bool* trackMute, const int* trackIntensities) {
@ -62,7 +64,7 @@ void UIManager::draw(UIState currentState, int menuSelection,
switch(currentState) { switch(currentState) {
case UI_MENU_MAIN: case UI_MENU_MAIN:
drawMenu(menuSelection, currentState, midiChannel, tempo, currentStrategy->getName(), queuedTheme, currentThemeIndex, numScaleNotes, scaleNotes, melodySeed, numSteps, mutationEnabled, songModeEnabled, isPlaying, randomizeTrack, trackMute, trackIntensities); drawMenu(menuSelection, currentState, midiChannel, tempo, currentStrategy->getName(), queuedTheme, currentThemeIndex, numScaleNotes, scaleNotes, melodySeed, currentTrackNumSteps, mutationEnabled, songModeEnabled, isPlaying, randomizeTrack, trackMute, trackIntensities);
break; break;
case UI_SETUP_CHANNEL_EDIT: case UI_SETUP_CHANNEL_EDIT:
display.println(F("SET MIDI CHANNEL")); display.println(F("SET MIDI CHANNEL"));
@ -93,7 +95,7 @@ void UIManager::draw(UIState currentState, int menuSelection,
display.setCursor(20, 25); display.setCursor(20, 25);
display.setTextSize(2); display.setTextSize(2);
display.print(F("LEN: ")); display.print(F("LEN: "));
display.print(numSteps); display.print(currentTrackNumSteps);
display.setTextSize(1); display.setTextSize(1);
display.setCursor(0, 50); display.setCursor(0, 50);
display.println(F(" (Press to confirm)")); display.println(F(" (Press to confirm)"));
@ -231,8 +233,8 @@ void UIManager::drawNumberEditor(const char* title, int value, int minVal, int m
} }
void UIManager::drawMenu(int selection, UIState currentState, int midiChannel, int tempo, const char* flavourName, void UIManager::drawMenu(int selection, UIState currentState, int midiChannel, int tempo, const char* flavourName,
int queuedTheme, int currentThemeIndex, int numScaleNotes, int queuedTheme, int currentThemeIndex, int numScaleNotes,
const int* scaleNotes, int melodySeed, int numSteps, bool mutationEnabled, const int* scaleNotes, int melodySeed, int currentTrackNumSteps, bool mutationEnabled,
bool songModeEnabled, bool isPlaying, int randomizeTrack, const bool* trackMute, const int* trackIntensities) { bool songModeEnabled, bool isPlaying, int randomizeTrack, const bool* trackMute, const int* trackIntensities) {
// Calculate visual cursor position and scroll offset // Calculate visual cursor position and scroll offset
@ -292,7 +294,7 @@ void UIManager::drawMenu(int selection, UIState currentState, int midiChannel, i
} }
} }
} else if (id == MENU_ID_TEMPO) { display.print(F(": ")); display.print(tempo); } } else if (id == MENU_ID_TEMPO) { display.print(F(": ")); display.print(tempo); }
else if (id == MENU_ID_STEPS) { display.print(F(": ")); display.print(numSteps); } else if (id == MENU_ID_STEPS) { display.print(F(": ")); display.print(currentTrackNumSteps); }
else if (id == MENU_ID_SONG_MODE) { display.print(F(": ")); display.print(songModeEnabled ? F("ON") : F("OFF")); } else if (id == MENU_ID_SONG_MODE) { display.print(F(": ")); display.print(songModeEnabled ? F("ON") : F("OFF")); }
else if (id == MENU_ID_TRACK_SELECT) { display.print(F(": ")); display.print(randomizeTrack + 1); } else if (id == MENU_ID_TRACK_SELECT) { display.print(F(": ")); display.print(randomizeTrack + 1); }
else if (id == MENU_ID_MUTE) { display.print(F(": ")); display.print(trackMute[randomizeTrack] ? F("YES") : F("NO")); } else if (id == MENU_ID_MUTE) { display.print(F(": ")); display.print(trackMute[randomizeTrack] ? F("YES") : F("NO")); }
@ -332,57 +334,66 @@ uint32_t UIManager::getNoteColor(int note, bool dim) {
} }
int UIManager::getPixelIndex(int x, int y) { int UIManager::getPixelIndex(int x, int y) {
return y * 8 + x; // [i] Here you can adjust the mapping from "logical" pixel coordinates
// to your physical NeoPixel layout. It depends on how you connected it.
return NEOPIXELS_Y * x + (NEOPIXELS_Y - 1 - y);
} }
void UIManager::updateLeds(const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying, void UIManager::updateLeds(const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying,
UIState currentState, bool songModeEnabled, UIState currentState, bool songModeEnabled,
int songRepeatsRemaining, bool sequenceChangeScheduled, PlayMode playMode, int songRepeatsRemaining, bool sequenceChangeScheduled, PlayMode playMode,
int selectedTrack, int numSteps, int numScaleNotes, const int* scaleNotes, const bool* trackMute) { int selectedTrack, const int* numSteps, int numScaleNotes, const int* scaleNotes, const bool* trackMute) {
pixels.clear(); pixels.clear();
const uint32_t COLOR_PLAYHEAD = pixels.Color(0, 255, 0); const uint32_t COLOR_PLAYHEAD = pixels.Color(0, 255, 0);
const uint32_t COLOR_PLAYHEAD_DIM = pixels.Color(0, 32, 0); const uint32_t COLOR_PLAYHEAD_DIM = pixels.Color(0, 32, 0);
const uint32_t COLOR_MUTED_PLAYHEAD = pixels.Color(0, 0, 255); const uint32_t COLOR_MUTED_PLAYHEAD = pixels.Color(0, 0, 255);
const uint32_t COLOR_CURSOR = pixels.Color(255, 255, 255); const uint32_t COLOR_CURSOR = pixels.Color(255, 100, 100);
const uint32_t COLOR_CURSOR_DIM = pixels.Color(32, 0, 0); const uint32_t COLOR_CURSOR_DIM = pixels.Color(32, 0, 0);
if(playMode == MODE_POLY) { if(playMode == MODE_POLY) {
for(int t=0; t<NUM_TRACKS; t++) { for(int t=0; t<NUM_TRACKS; t++) {
int currentTrackSteps = numSteps[t];
for(int s=0; s<NUM_STEPS; s++) { for(int s=0; s<NUM_STEPS; s++) {
if (s >= numSteps) continue; int col = s; // Each step is a column
int row = t * 2 + (s / 8); // --- First row of track pair: Notes ---
int col = s % 8; int note_row = t * 2;
uint32_t note_color = 0;
uint32_t color = 0; if (s < currentTrackSteps) {
int note = sequence[t][s].note; int note = sequence[t][s].note;
if (note != -1) {
if (note != -1) { note_color = getNoteColor(note, !sequence[t][s].accent);
color = getNoteColor(note, !sequence[t][s].accent);
}
if (isPlaying && s == playbackStep) {
if (trackMute[t]) {
color = COLOR_MUTED_PLAYHEAD;
} else {
color = (note != -1) ? COLOR_PLAYHEAD : COLOR_PLAYHEAD_DIM;
} }
} }
pixels.setPixelColor(getPixelIndex(col, note_row), note_color);
pixels.setPixelColor(getPixelIndex(col, row), color);
// --- Second row of track pair: Steps & Playhead ---
int step_row = t * 2 + 1;
uint32_t step_color = 0; // Off by default for steps > currentTrackSteps
if (s < currentTrackSteps) {
step_color = COLOR_PLAYHEAD_DIM; // It's a valid step
if (isPlaying && (s == (playbackStep % currentTrackSteps))) {
if (trackMute[t]) {
step_color = COLOR_MUTED_PLAYHEAD;
} else {
step_color = COLOR_PLAYHEAD;
}
}
}
pixels.setPixelColor(getPixelIndex(col, step_row), step_color);
} }
} }
} else { } else {
// --- Mono Mode (original) --- // --- Mono Mode (original) ---
const Step* trackSequence = sequence[selectedTrack]; const Step* trackSequence = sequence[selectedTrack];
for (int s = 0; s < NUM_STEPS; s++) { for (int s = 0; s < NUM_STEPS; s++) {
if (s >= numSteps) continue; if (s >= numSteps[selectedTrack]) continue;
int x = s % 8; int x = s % NUM_STEPS;
int yBase = (s / 8) * 4; int yBase = (s / NUM_STEPS) * 2;
uint32_t color = 0, dimColor = 0; uint32_t color = 0, dimColor = 0;
bool isCursorHere = (isPlaying && s == playbackStep); bool isCursorHere = (isPlaying && s == (playbackStep % numSteps[selectedTrack]));
if (trackSequence[s].note != -1) { if (trackSequence[s].note != -1) {
color = getNoteColor(trackSequence[s].note, trackSequence[s].tie); color = getNoteColor(trackSequence[s].note, trackSequence[s].tie);
dimColor = getNoteColor(trackSequence[s].note, true); dimColor = getNoteColor(trackSequence[s].note, true);
@ -397,9 +408,9 @@ void UIManager::updateLeds(const Step sequence[][NUM_STEPS], int playbackStep, b
uint32_t cursorColor = pixels.Color(0, 0, 50); uint32_t cursorColor = pixels.Color(0, 0, 50);
if (isPlaying) { if (isPlaying) {
cursorColor = pixels.Color(0, 50, 0); cursorColor = pixels.Color(0, 50, 0);
if (songModeEnabled && s >= 8) { if (songModeEnabled && s >= NUM_STEPS) {
int repeats = min(songRepeatsRemaining, 8); int repeats = min(songRepeatsRemaining, NUM_STEPS);
if (x >= (8 - repeats)) cursorColor = (songRepeatsRemaining == 1 && x == 7 && (millis()/250)%2) ? pixels.Color(255, 200, 0) : pixels.Color(100, 220, 40); if (x >= (NUM_STEPS - repeats)) cursorColor = (songRepeatsRemaining == 1 && x == 7 && (millis()/250)%2) ? pixels.Color(255, 200, 0) : pixels.Color(100, 220, 40);
} }
} }
@ -415,6 +426,39 @@ void UIManager::updateLeds(const Step sequence[][NUM_STEPS], int playbackStep, b
} }
for(int i=0; i<4; i++) pixels.setPixelColor(getPixelIndex(x, yBase + i), c[i]); for(int i=0; i<4; i++) pixels.setPixelColor(getPixelIndex(x, yBase + i), c[i]);
} }
// --- Overview of all tracks on bottom 4 rows ---
for(int t=0; t<NUM_TRACKS; t++) {
int row = 4 + t;
int currentTrackSteps = numSteps[t];
for(int s=0; s<NUM_STEPS; s++) {
if (s >= currentTrackSteps) continue;
uint32_t pixelColor = 0;
// Background for active track
if (t == selectedTrack) {
pixelColor = COLOR_CURSOR_DIM;
}
else if (sequence[t][s].note != -1) // Note
{
pixelColor = getNoteColor(sequence[t][s].note, !sequence[t][s].accent);
}
// Playhead
if (isPlaying && (s == (playbackStep % currentTrackSteps))) {
if (t == selectedTrack) {
pixelColor = COLOR_CURSOR;
} else if (trackMute[t]) {
pixelColor = COLOR_MUTED_PLAYHEAD;
} else {
pixelColor = COLOR_PLAYHEAD;
}
}
pixels.setPixelColor(getPixelIndex(s, row), pixelColor);
}
}
} }
if (sequenceChangeScheduled && (millis() / 125) % 2) pixels.setPixelColor(NUM_PIXELS - 1, pixels.Color(127, 50, 0)); if (sequenceChangeScheduled && (millis() / 125) % 2) pixels.setPixelColor(NUM_PIXELS - 1, pixels.Color(127, 50, 0));
pixels.show(); pixels.show();

View File

@ -15,9 +15,9 @@ public:
void showMessage(const char* msg); void showMessage(const char* msg);
void draw(UIState currentState, int menuSelection, void draw(UIState currentState, int menuSelection,
int midiChannel, int tempo, MelodyStrategy* currentStrategy, int midiChannel, int tempo, MelodyStrategy* currentStrategy,
int queuedTheme, int currentThemeIndex, int queuedTheme, int currentThemeIndex,
int numScaleNotes, const int* scaleNotes, int melodySeed, int numSteps, int numScaleNotes, const int* scaleNotes, int melodySeed, int currentTrackNumSteps,
bool mutationEnabled, bool songModeEnabled, bool mutationEnabled, bool songModeEnabled,
const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying, const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying,
int randomizeTrack, const bool* trackMute, const int* trackIntensities); int randomizeTrack, const bool* trackMute, const int* trackIntensities);
@ -25,16 +25,15 @@ public:
void updateLeds(const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying, void updateLeds(const Step sequence[][NUM_STEPS], int playbackStep, bool isPlaying,
UIState currentState, bool songModeEnabled, UIState currentState, bool songModeEnabled,
int songRepeatsRemaining, bool sequenceChangeScheduled, PlayMode playMode, int songRepeatsRemaining, bool sequenceChangeScheduled, PlayMode playMode,
int selectedTrack, int numSteps, int numScaleNotes, const int* scaleNotes, const bool* trackMute); int selectedTrack, const int* numSteps, int numScaleNotes, const int* scaleNotes, const bool* trackMute);
private: private:
Adafruit_SSD1306 display; Adafruit_SSD1306 display;
Adafruit_NeoPixel pixels; Adafruit_NeoPixel pixels;
uint32_t leds_buffer[8][8]; // For piano roll
void drawMenu(int selection, UIState currentState, int midiChannel, int tempo, const char* flavourName, void drawMenu(int selection, UIState currentState, int midiChannel, int tempo, const char* flavourName,
int queuedTheme, int currentThemeIndex, int queuedTheme, int currentThemeIndex,
int numScaleNotes, const int* scaleNotes, int melodySeed, int numSteps, int numScaleNotes, const int* scaleNotes, int melodySeed, int currentTrackNumSteps,
bool mutationEnabled, bool songModeEnabled, bool isPlaying, int randomizeTrack, const bool* trackMute, const int* trackIntensities); bool mutationEnabled, bool songModeEnabled, bool isPlaying, int randomizeTrack, const bool* trackMute, const int* trackIntensities);
void drawNumberEditor(const char* title, int value, int minVal, int maxVal); void drawNumberEditor(const char* title, int value, int minVal, int maxVal);

View File

@ -44,9 +44,11 @@ void saveSequence(bool quiet) {
EEPROM.put(addr, mutes); addr += sizeof(mutes); EEPROM.put(addr, mutes); addr += sizeof(mutes);
EEPROM.put(addr, (int)tempo); addr += sizeof(int); EEPROM.put(addr, (int)tempo); addr += sizeof(int);
int intensities[NUM_TRACKS]; int intensities[NUM_TRACKS];
for(int i=0; i<NUM_TRACKS; i++) intensities[i] = trackIntensity[i]; for(int i=0; i<NUM_TRACKS; i++) intensities[i] = (int)trackIntensity[i];
EEPROM.put(addr, intensities); addr += sizeof(intensities); EEPROM.put(addr, intensities); addr += sizeof(intensities);
EEPROM.put(addr, (int)numSteps); addr += sizeof(int); int steps[NUM_TRACKS];
for(int i=0; i<NUM_TRACKS; i++) steps[i] = numSteps[i];
EEPROM.put(addr, steps); addr += sizeof(steps);
EEPROM.put(addr, numScaleNotes); addr += sizeof(numScaleNotes); EEPROM.put(addr, numScaleNotes); addr += sizeof(numScaleNotes);
for (int i = 0; i<12; i++) { for (int i = 0; i<12; i++) {
@ -96,9 +98,14 @@ bool loadSequence() {
if (trackIntensity[i] < 1) trackIntensity[i] = 1; if (trackIntensity[i] < 1) trackIntensity[i] = 1;
if (trackIntensity[i] > 10) trackIntensity[i] = 10; if (trackIntensity[i] > 10) trackIntensity[i] = 10;
} }
EEPROM.get(addr, t); addr += sizeof(int); int steps[NUM_TRACKS];
numSteps = t; EEPROM.get(addr, steps); addr += sizeof(steps);
if (numSteps <= 0 || numSteps >= NUM_STEPS) numSteps = NUM_STEPS; for(int i=0; i<NUM_TRACKS; i++) {
numSteps[i] = steps[i];
if (numSteps[i] <= 0 || numSteps[i] > NUM_STEPS) {
numSteps[i] = NUM_STEPS;
}
}
EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes); EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes);
if (numScaleNotes < 0 || numScaleNotes > 12) numScaleNotes = 0; if (numScaleNotes < 0 || numScaleNotes > 12) numScaleNotes = 0;
@ -124,7 +131,9 @@ static void savePatch(int bank, int slot) {
} }
EEPROM.put(addr, currentStrategyIndices); addr += sizeof(currentStrategyIndices); EEPROM.put(addr, currentStrategyIndices); addr += sizeof(currentStrategyIndices);
EEPROM.put(addr, melodySeeds); addr += sizeof(melodySeeds); EEPROM.put(addr, melodySeeds); addr += sizeof(melodySeeds);
EEPROM.put(addr, (int)numSteps); addr += sizeof(int); int steps[NUM_TRACKS];
for(int i=0; i<NUM_TRACKS; i++) steps[i] = numSteps[i];
EEPROM.put(addr, steps); addr += sizeof(steps);
bool mutes[NUM_TRACKS]; bool mutes[NUM_TRACKS];
for(int i=0; i<NUM_TRACKS; i++) mutes[i] = trackMute[i]; for(int i=0; i<NUM_TRACKS; i++) mutes[i] = trackMute[i];
EEPROM.put(addr, mutes); addr += sizeof(mutes); EEPROM.put(addr, mutes); addr += sizeof(mutes);
@ -153,10 +162,14 @@ static void loadPatch(int bank, int slot) {
if (currentStrategyIndices[i] < 0 || currentStrategyIndices[i] >= numStrategies) currentStrategyIndices[i] = 0; if (currentStrategyIndices[i] < 0 || currentStrategyIndices[i] >= numStrategies) currentStrategyIndices[i] = 0;
} }
EEPROM.get(addr, melodySeeds); addr += sizeof(melodySeeds); EEPROM.get(addr, melodySeeds); addr += sizeof(melodySeeds);
int t; int steps[NUM_TRACKS];
EEPROM.get(addr, t); addr += sizeof(int); EEPROM.get(addr, steps); addr += sizeof(steps);
numSteps = t; for(int i=0; i<NUM_TRACKS; i++) {
if (numSteps <= 0 || numSteps >= NUM_STEPS) numSteps = NUM_STEPS; numSteps[i] = steps[i];
if (numSteps[i] <= 0 || numSteps[i] > NUM_STEPS) {
numSteps[i] = NUM_STEPS;
}
}
bool mutes[NUM_TRACKS]; bool mutes[NUM_TRACKS];
EEPROM.get(addr, mutes); addr += sizeof(mutes); EEPROM.get(addr, mutes); addr += sizeof(mutes);
@ -195,7 +208,7 @@ void factoryReset() {
static void generateTrackData(int track, int themeType, Step (*target)[NUM_STEPS]) { static void 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, scaleNotes, numScaleNotes, melodySeeds[track] + themeType * 12345, trackIntensity[track]); strategies[currentStrategyIndices[track]]->generate(target, track, numSteps[track], scaleNotes, numScaleNotes, melodySeeds[track] + themeType * 12345, trackIntensity[track]);
} }
void generateRandomScale() { void generateRandomScale() {
@ -229,7 +242,11 @@ void generateTheme(int themeType) {
} }
void mutateSequence(Step (*target)[NUM_STEPS]) { void mutateSequence(Step (*target)[NUM_STEPS]) {
for(int i=0; i<NUM_TRACKS; i++) strategies[currentStrategyIndices[i]]->mutate(target, i, numSteps, scaleNotes, numScaleNotes, trackIntensity[i]); 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]);
}
}
} }
static void handleInput() { static void handleInput() {
@ -268,9 +285,9 @@ static void handleInput() {
if (tempo > 240) tempo = 240; if (tempo > 240) tempo = 240;
break; break;
case UI_EDIT_STEPS: case UI_EDIT_STEPS:
numSteps += delta; numSteps[randomizeTrack] += delta;
if (numSteps < 1) numSteps = 1; if (numSteps[randomizeTrack] < 1) numSteps[randomizeTrack] = 1;
if (numSteps > NUM_STEPS) numSteps = NUM_STEPS; if (numSteps[randomizeTrack] > NUM_STEPS) numSteps[randomizeTrack] = NUM_STEPS;
break; break;
case UI_EDIT_FLAVOUR: case UI_EDIT_FLAVOUR:
{ {
@ -370,6 +387,10 @@ static void handleInput() {
case MENU_ID_MELODY: case MENU_ID_MELODY:
midi.lock(); midi.lock();
melodySeeds[randomizeTrack] = random(10000); melodySeeds[randomizeTrack] = random(10000);
randomSeed(melodySeeds[randomizeTrack]);
for (int i = 0; i < NUM_TRACKS; i++) {
numSteps[i] = random(1, NUM_STEPS + 1);
}
if (isPlaying) { if (isPlaying) {
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex; int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
if (!sequenceChangeScheduled) { if (!sequenceChangeScheduled) {
@ -570,7 +591,7 @@ static void drawUI() {
// to avoid holding the lock during slow display operations. // to avoid holding the lock during slow display operations.
UIState local_currentState; UIState local_currentState;
int local_menuSelection, local_randomizeTrack, local_tempo, local_currentThemeIndex, local_queuedTheme, local_numScaleNotes; int local_menuSelection, local_randomizeTrack, local_tempo, local_currentThemeIndex, local_queuedTheme, local_numScaleNotes;
int local_melodySeed, local_numSteps; int local_melodySeed, local_currentTrackNumSteps;
bool local_mutationEnabled, local_songModeEnabled, local_isPlaying; bool local_mutationEnabled, local_songModeEnabled, local_isPlaying;
bool local_trackMute[NUM_TRACKS]; bool local_trackMute[NUM_TRACKS];
int local_midiChannel; int local_midiChannel;
@ -590,7 +611,7 @@ static void drawUI() {
} }
local_midiChannel = midiChannels[local_randomizeTrack]; local_midiChannel = midiChannels[local_randomizeTrack];
local_tempo = tempo; local_tempo = tempo;
local_numSteps = numSteps; local_currentTrackNumSteps = numSteps[local_randomizeTrack];
local_strategy = strategies[currentStrategyIndices[local_randomizeTrack]]; local_strategy = strategies[currentStrategyIndices[local_randomizeTrack]];
local_queuedTheme = queuedTheme; local_queuedTheme = queuedTheme;
local_currentThemeIndex = currentThemeIndex; local_currentThemeIndex = currentThemeIndex;
@ -608,7 +629,7 @@ static void drawUI() {
ui.draw(local_currentState, local_menuSelection, ui.draw(local_currentState, local_menuSelection,
local_midiChannel, local_tempo, local_strategy, local_midiChannel, local_tempo, local_strategy,
local_queuedTheme, local_currentThemeIndex, local_numScaleNotes, local_scaleNotes, local_melodySeed, local_numSteps, local_queuedTheme, local_currentThemeIndex, local_numScaleNotes, local_scaleNotes, local_melodySeed, local_currentTrackNumSteps,
local_mutationEnabled, local_songModeEnabled, (const Step (*)[NUM_STEPS])local_sequence, local_playbackStep, local_isPlaying, local_randomizeTrack, (const bool*)local_trackMute, (const int*)local_trackIntensities); local_mutationEnabled, local_songModeEnabled, (const Step (*)[NUM_STEPS])local_sequence, local_playbackStep, local_isPlaying, local_randomizeTrack, (const bool*)local_trackMute, (const int*)local_trackIntensities);
} }
@ -623,8 +644,8 @@ static void updateLeds() {
int local_songRepeatsRemaining; int local_songRepeatsRemaining;
bool local_sequenceChangeScheduled; bool local_sequenceChangeScheduled;
PlayMode local_playMode; PlayMode local_playMode;
int local_numScaleNotes, local_numSteps; int local_numScaleNotes;
int local_scaleNotes[12]; int local_scaleNotes[12], local_numSteps[NUM_TRACKS];
bool local_trackMute[NUM_TRACKS]; bool local_trackMute[NUM_TRACKS];
int local_randomizeTrack; int local_randomizeTrack;
@ -639,7 +660,7 @@ static void updateLeds() {
local_sequenceChangeScheduled = sequenceChangeScheduled; local_sequenceChangeScheduled = sequenceChangeScheduled;
local_playMode = playMode; local_playMode = playMode;
local_numScaleNotes = numScaleNotes; local_numScaleNotes = numScaleNotes;
local_numSteps = numSteps; for (int i = 0; i < NUM_TRACKS; i++) local_numSteps[i] = numSteps[i];
local_randomizeTrack = randomizeTrack; local_randomizeTrack = randomizeTrack;
memcpy(local_scaleNotes, scaleNotes, sizeof(local_scaleNotes)); memcpy(local_scaleNotes, scaleNotes, sizeof(local_scaleNotes));
memcpy(local_trackMute, (const void*)trackMute, sizeof(local_trackMute)); memcpy(local_trackMute, (const void*)trackMute, sizeof(local_trackMute));
@ -654,14 +675,14 @@ static void updateLeds() {
// It's a TRACK section item (Track, Mute, Flavour, Mutation, Themes) // It's a TRACK section item (Track, Mute, Flavour, Mutation, Themes)
ledDisplayMode = MODE_MONO; ledDisplayMode = MODE_MONO;
} }
} else if (local_currentState == UI_EDIT_FLAVOUR || local_currentState == UI_RANDOMIZE_TRACK_EDIT || local_currentState == UI_SCALE_EDIT || local_currentState == UI_SCALE_NOTE_EDIT || local_currentState == UI_SCALE_TRANSPOSE) { } else if (local_currentState == UI_EDIT_STEPS || local_currentState == UI_EDIT_FLAVOUR || local_currentState == UI_RANDOMIZE_TRACK_EDIT || local_currentState == UI_SCALE_EDIT || local_currentState == UI_SCALE_NOTE_EDIT || local_currentState == UI_SCALE_TRANSPOSE) {
// These are entered from TRACK section items // These are entered from TRACK section items
ledDisplayMode = MODE_MONO; ledDisplayMode = MODE_MONO;
} }
ui.updateLeds((const Step (*)[NUM_STEPS])local_sequence, local_playbackStep, local_isPlaying, ui.updateLeds((const Step (*)[NUM_STEPS])local_sequence, local_playbackStep, local_isPlaying,
local_currentState, local_songModeEnabled, local_songRepeatsRemaining, local_currentState, local_songModeEnabled, local_songRepeatsRemaining,
local_sequenceChangeScheduled, ledDisplayMode, local_randomizeTrack, local_numSteps, local_numScaleNotes, local_sequenceChangeScheduled, ledDisplayMode, local_randomizeTrack, local_numSteps, local_numScaleNotes,
local_scaleNotes, (const bool*)local_trackMute); local_scaleNotes, (const bool*)local_trackMute);
} }

View File

@ -16,7 +16,7 @@
#define ENC_SW 14 #define ENC_SW 14
// --- TRACKER DATA --- // --- TRACKER DATA ---
#define NUM_STEPS 8 #define NUM_STEPS 16
#define NUM_TRACKS 4 #define NUM_TRACKS 4
#define MIDI_DEBUG #define MIDI_DEBUG