Compare commits
3 Commits
1e475eeaa5
...
c6e2248090
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6e2248090 | ||
|
|
225d04a53c | ||
|
|
6734572a67 |
100
ArpStrategy.h
100
ArpStrategy.h
@ -10,24 +10,102 @@ public:
|
|||||||
randomSeed(seed);
|
randomSeed(seed);
|
||||||
if (numScaleNotes == 0) return;
|
if (numScaleNotes == 0) return;
|
||||||
|
|
||||||
int currentNoteIndex = 0;
|
// 1. Select Subset
|
||||||
int octave = 4;
|
int subsetSize = random(2, numScaleNotes + 1);
|
||||||
|
if (subsetSize > 12) subsetSize = 12;
|
||||||
|
int subset[12];
|
||||||
|
|
||||||
for (int i = 0; i < numSteps; i++) {
|
// Create indices to shuffle
|
||||||
sequence[i].note = 12 * octave + scaleNotes[currentNoteIndex];
|
int indices[12];
|
||||||
sequence[i].accent = (i % 4 == 0); // Accent on beat
|
for(int i=0; i<numScaleNotes; i++) indices[i] = i;
|
||||||
sequence[i].tie = false;
|
|
||||||
|
|
||||||
currentNoteIndex++;
|
// Shuffle indices
|
||||||
if (currentNoteIndex >= numScaleNotes) {
|
for(int i=0; i<numScaleNotes; i++) {
|
||||||
currentNoteIndex = 0;
|
int r = random(numScaleNotes);
|
||||||
octave++;
|
int temp = indices[i];
|
||||||
if (octave > 5) octave = 3;
|
indices[i] = indices[r];
|
||||||
|
indices[r] = temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pick subset
|
||||||
|
for(int i=0; i<subsetSize; i++) {
|
||||||
|
subset[i] = scaleNotes[indices[i]];
|
||||||
|
}
|
||||||
|
sortArray(subset, subsetSize);
|
||||||
|
|
||||||
|
// 2. Pick Arp Length & Pattern
|
||||||
|
int arpLength = random(2, 17); // 2 to 16
|
||||||
|
Step arpPattern[16];
|
||||||
|
|
||||||
|
int mode = random(3); // 0: Up, 1: Down, 2: Up/Down
|
||||||
|
int numOctaves = 3;
|
||||||
|
int baseOctave = 3;
|
||||||
|
int totalNotes = subsetSize * numOctaves;
|
||||||
|
|
||||||
|
int currentIndex = 0;
|
||||||
|
int direction = 1;
|
||||||
|
|
||||||
|
if (mode == 1) { // Down
|
||||||
|
currentIndex = totalNotes - 1;
|
||||||
|
direction = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < arpLength; i++) {
|
||||||
|
// Chance of rest (15%)
|
||||||
|
if (random(100) < 15) {
|
||||||
|
arpPattern[i].note = -1;
|
||||||
|
arpPattern[i].accent = false;
|
||||||
|
arpPattern[i].tie = false;
|
||||||
|
} else {
|
||||||
|
int octaveOffset = currentIndex / subsetSize;
|
||||||
|
int noteIndex = currentIndex % subsetSize;
|
||||||
|
int octave = baseOctave + octaveOffset;
|
||||||
|
|
||||||
|
arpPattern[i].note = 12 * octave + subset[noteIndex];
|
||||||
|
arpPattern[i].accent = (i % 4 == 0); // Accent on beat
|
||||||
|
arpPattern[i].tie = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == 0) { // Up
|
||||||
|
currentIndex++;
|
||||||
|
if (currentIndex >= totalNotes) currentIndex = 0;
|
||||||
|
} else if (mode == 1) { // Down
|
||||||
|
currentIndex--;
|
||||||
|
if (currentIndex < 0) currentIndex = totalNotes - 1;
|
||||||
|
} else { // Up/Down
|
||||||
|
currentIndex += direction;
|
||||||
|
if (currentIndex >= totalNotes) {
|
||||||
|
currentIndex = max(0, totalNotes - 2);
|
||||||
|
direction = -1;
|
||||||
|
} else if (currentIndex < 0) {
|
||||||
|
currentIndex = min(totalNotes - 1, 1);
|
||||||
|
direction = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Fill Sequence
|
||||||
|
for (int i = 0; i < numSteps; i++) {
|
||||||
|
sequence[i] = arpPattern[i % arpLength];
|
||||||
}
|
}
|
||||||
randomSeed(micros());
|
randomSeed(micros());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void generateScale(int* scaleNotes, int& numScaleNotes) override {
|
||||||
|
numScaleNotes = random(3, 13); // 3 to 12 notes
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
scaleNotes[i] = i; // Fill with all notes
|
||||||
|
}
|
||||||
|
// 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, int numSteps, int* scaleNotes, int numScaleNotes) override {
|
void mutate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes) override {
|
||||||
// Swap two notes
|
// Swap two notes
|
||||||
int s1 = random(numSteps);
|
int s1 = random(numSteps);
|
||||||
|
|||||||
@ -19,6 +19,21 @@ public:
|
|||||||
randomSeed(micros());
|
randomSeed(micros());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void generateScale(int* scaleNotes, int& numScaleNotes) override {
|
||||||
|
numScaleNotes = random(3, 13); // 3 to 12 notes
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
scaleNotes[i] = i; // Fill with all notes
|
||||||
|
}
|
||||||
|
// 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, int numSteps, int* scaleNotes, int numScaleNotes) override {
|
void mutate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes) override {
|
||||||
// Mutate 1 or 2 steps
|
// Mutate 1 or 2 steps
|
||||||
int count = random(1, 3);
|
int count = random(1, 3);
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
class MelodyStrategy {
|
class MelodyStrategy {
|
||||||
public:
|
public:
|
||||||
virtual void generate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes, int seed) = 0;
|
virtual void generate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes, int seed) = 0;
|
||||||
|
virtual void generateScale(int* scaleNotes, int& numScaleNotes) = 0;
|
||||||
virtual void mutate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes) = 0;
|
virtual void mutate(Step* sequence, int numSteps, int* scaleNotes, int numScaleNotes) = 0;
|
||||||
virtual const char* getName() = 0;
|
virtual const char* getName() = 0;
|
||||||
virtual ~MelodyStrategy() {}
|
virtual ~MelodyStrategy() {}
|
||||||
|
|||||||
49
MidiDriver.cpp
Normal file
49
MidiDriver.cpp
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#include "MidiDriver.h"
|
||||||
|
|
||||||
|
// MIDI UART Pins (GP0/GP1)
|
||||||
|
#define PIN_MIDI_TX 0
|
||||||
|
|
||||||
|
MidiDriver midi;
|
||||||
|
|
||||||
|
MidiDriver::MidiDriver() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiDriver::begin() {
|
||||||
|
mutex_init(&_mutex);
|
||||||
|
Serial1.setTX(PIN_MIDI_TX);
|
||||||
|
Serial1.begin(31250);
|
||||||
|
Serial.println(F("MIDI Serial initialized on GP0/GP1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiDriver::lock() {
|
||||||
|
mutex_enter_blocking(&_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiDriver::unlock() {
|
||||||
|
mutex_exit(&_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiDriver::sendNoteOn(uint8_t note, uint8_t velocity, uint8_t channel) {
|
||||||
|
uint8_t status = 0x90 | (channel - 1);
|
||||||
|
Serial1.write(status);
|
||||||
|
Serial1.write(note);
|
||||||
|
Serial1.write(velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiDriver::sendNoteOff(uint8_t note, uint8_t channel) {
|
||||||
|
uint8_t status = 0x80 | (channel - 1);
|
||||||
|
Serial1.write(status);
|
||||||
|
Serial1.write(note);
|
||||||
|
Serial1.write((uint8_t)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiDriver::sendRealtime(uint8_t status) {
|
||||||
|
Serial1.write(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MidiDriver::panic(uint8_t channel) {
|
||||||
|
uint8_t status = 0xB0 | (channel - 1);
|
||||||
|
Serial1.write(status);
|
||||||
|
Serial1.write(123); // All Notes Off
|
||||||
|
Serial1.write((uint8_t)0);
|
||||||
|
}
|
||||||
26
MidiDriver.h
Normal file
26
MidiDriver.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#ifndef MIDI_DRIVER_H
|
||||||
|
#define MIDI_DRIVER_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <pico/mutex.h>
|
||||||
|
|
||||||
|
class MidiDriver {
|
||||||
|
public:
|
||||||
|
MidiDriver();
|
||||||
|
void begin();
|
||||||
|
|
||||||
|
void sendNoteOn(uint8_t note, uint8_t velocity, uint8_t channel);
|
||||||
|
void sendNoteOff(uint8_t note, uint8_t channel);
|
||||||
|
void sendRealtime(uint8_t status);
|
||||||
|
void panic(uint8_t channel);
|
||||||
|
|
||||||
|
void lock();
|
||||||
|
void unlock();
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutex_t _mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern MidiDriver midi;
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -1,14 +1,12 @@
|
|||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
#include <Adafruit_GFX.h>
|
|
||||||
#include <Adafruit_SSD1306.h>
|
|
||||||
#include <Adafruit_NeoPixel.h>
|
|
||||||
#include <EEPROM.h>
|
#include <EEPROM.h>
|
||||||
#include <pico/mutex.h>
|
|
||||||
#include "TrackerTypes.h"
|
#include "TrackerTypes.h"
|
||||||
#include "MelodyStrategy.h"
|
#include "MelodyStrategy.h"
|
||||||
#include "LuckyStrategy.h"
|
#include "LuckyStrategy.h"
|
||||||
#include "ArpStrategy.h"
|
#include "ArpStrategy.h"
|
||||||
|
#include "MidiDriver.h"
|
||||||
|
#include "UIManager.h"
|
||||||
|
|
||||||
// --- HARDWARE CONFIGURATION ---
|
// --- HARDWARE CONFIGURATION ---
|
||||||
#define SCREEN_WIDTH 128
|
#define SCREEN_WIDTH 128
|
||||||
@ -24,13 +22,6 @@
|
|||||||
#define ENC_DT 13
|
#define ENC_DT 13
|
||||||
#define ENC_SW 14
|
#define ENC_SW 14
|
||||||
|
|
||||||
// MIDI UART Pins (GP0/GP1) -- OUT only so far
|
|
||||||
#define PIN_MIDI_TX 0
|
|
||||||
|
|
||||||
// NeoPixel Pin (any GPIO is fine, I've chosen 16)
|
|
||||||
#define PIN_NEOPIXEL 16
|
|
||||||
#define NUM_PIXELS 64 // For 8x8 WS2812B matrix
|
|
||||||
|
|
||||||
// --- TRACKER DATA ---
|
// --- TRACKER DATA ---
|
||||||
#define NUM_STEPS 16
|
#define NUM_STEPS 16
|
||||||
|
|
||||||
@ -39,30 +30,14 @@ Step nextSequence[NUM_STEPS];
|
|||||||
volatile bool sequenceChangeScheduled = false;
|
volatile bool sequenceChangeScheduled = false;
|
||||||
volatile bool needsPanic = false;
|
volatile bool needsPanic = false;
|
||||||
|
|
||||||
mutex_t midiMutex;
|
|
||||||
|
|
||||||
// --- STATE ---
|
|
||||||
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
|
|
||||||
Adafruit_NeoPixel pixels(NUM_PIXELS, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800);
|
|
||||||
|
|
||||||
enum UIState {
|
|
||||||
UI_TRACKER,
|
|
||||||
UI_MENU_MAIN,
|
|
||||||
UI_MENU_RANDOMIZE,
|
|
||||||
UI_MENU_SETUP,
|
|
||||||
UI_SETUP_CHANNEL_EDIT,
|
|
||||||
UI_EDIT_TEMPO,
|
|
||||||
UI_EDIT_FLAVOUR
|
|
||||||
};
|
|
||||||
|
|
||||||
UIState currentState = UI_MENU_MAIN;
|
UIState currentState = UI_MENU_MAIN;
|
||||||
|
|
||||||
const char* mainMenu[] = { "Tracker", "Randomize", "Setup" };
|
const char* mainMenu[] = { "Tracker", "Randomize", "Setup" };
|
||||||
const int mainMenuCount = sizeof(mainMenu) / sizeof(char*);
|
const int mainMenuCount = sizeof(mainMenu) / sizeof(char*);
|
||||||
const char* randomizeMenu[] = { "Back", "Scale", "Melody", "Flavour", "Tempo", "Mutation", "Song Mode", "Theme 1", "Theme 2", "Theme 3", "Theme 4", "Theme 5", "Theme 6", "Theme 7" };
|
const char* randomizeMenu[] = { "Back", "Melody", "Flavour", "Scale", "Tempo", "Mutation", "Song Mode", "Theme 1", "Theme 2", "Theme 3", "Theme 4", "Theme 5", "Theme 6", "Theme 7" };
|
||||||
const int THEME_1_INDEX = 7;
|
const int THEME_1_INDEX = 7;
|
||||||
const int randomizeMenuCount = sizeof(randomizeMenu) / sizeof(char*);
|
const int randomizeMenuCount = sizeof(randomizeMenu) / sizeof(char*);
|
||||||
const char* setupMenu[] = { "Back", "Channel", "Save", "Load" };
|
const char* setupMenu[] = { "Back", "Channel", "Factory Reset" };
|
||||||
const int setupMenuCount = sizeof(setupMenu) / sizeof(char*);
|
const int setupMenuCount = sizeof(setupMenu) / sizeof(char*);
|
||||||
|
|
||||||
int menuSelection = 0;
|
int menuSelection = 0;
|
||||||
@ -125,29 +100,6 @@ void readEncoder() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sortArray(int arr[], int size) {
|
|
||||||
for (int i = 0; i < size - 1; i++) {
|
|
||||||
for (int j = 0; j < size - i - 1; j++) {
|
|
||||||
if (arr[j] > arr[j + 1]) {
|
|
||||||
int temp = arr[j];
|
|
||||||
arr[j] = arr[j + 1];
|
|
||||||
arr[j + 1] = temp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void showMessage(const char* msg) {
|
|
||||||
display.clearDisplay();
|
|
||||||
display.setCursor(10, 25);
|
|
||||||
display.setTextColor(SSD1306_WHITE);
|
|
||||||
display.setTextSize(2);
|
|
||||||
display.print(msg);
|
|
||||||
display.display();
|
|
||||||
delay(500);
|
|
||||||
display.setTextSize(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void saveSequence(bool quiet = false) {
|
void saveSequence(bool quiet = false) {
|
||||||
int addr = 0;
|
int addr = 0;
|
||||||
EEPROM.put(addr, EEPROM_MAGIC); addr += sizeof(EEPROM_MAGIC);
|
EEPROM.put(addr, EEPROM_MAGIC); addr += sizeof(EEPROM_MAGIC);
|
||||||
@ -161,13 +113,13 @@ void saveSequence(bool quiet = false) {
|
|||||||
EEPROM.put(addr, scaleNotes[i]); addr += sizeof(int);
|
EEPROM.put(addr, scaleNotes[i]); addr += sizeof(int);
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_enter_blocking(&midiMutex);
|
midi.lock();
|
||||||
for (int i=0; i<NUM_STEPS; i++) {
|
for (int i=0; i<NUM_STEPS; i++) {
|
||||||
EEPROM.put(addr, sequence[i]); addr += sizeof(Step);
|
EEPROM.put(addr, sequence[i]); addr += sizeof(Step);
|
||||||
}
|
}
|
||||||
mutex_exit(&midiMutex);
|
midi.unlock();
|
||||||
EEPROM.commit();
|
EEPROM.commit();
|
||||||
if (!quiet) showMessage("SAVED!");
|
if (!quiet) ui.showMessage("SAVED!");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool loadSequence() {
|
bool loadSequence() {
|
||||||
@ -189,17 +141,25 @@ bool loadSequence() {
|
|||||||
EEPROM.get(addr, scaleNotes[i]); addr += sizeof(int);
|
EEPROM.get(addr, scaleNotes[i]); addr += sizeof(int);
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_enter_blocking(&midiMutex);
|
midi.lock();
|
||||||
for (int i=0; i<NUM_STEPS; i++) {
|
for (int i=0; i<NUM_STEPS; i++) {
|
||||||
EEPROM.get(addr, sequence[i]); addr += sizeof(Step);
|
EEPROM.get(addr, sequence[i]); addr += sizeof(Step);
|
||||||
}
|
}
|
||||||
mutex_exit(&midiMutex);
|
midi.unlock();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void factoryReset() {
|
||||||
|
ui.showMessage("RESETTING...");
|
||||||
|
uint32_t magic = 0;
|
||||||
|
EEPROM.put(0, magic);
|
||||||
|
EEPROM.commit();
|
||||||
|
delay(500);
|
||||||
|
rp2040.reboot();
|
||||||
|
}
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
mutex_init(&midiMutex);
|
|
||||||
|
|
||||||
// Use random ADC noise for seed
|
// Use random ADC noise for seed
|
||||||
delay(5000);
|
delay(5000);
|
||||||
@ -213,26 +173,8 @@ void setup() {
|
|||||||
attachInterrupt(digitalPinToInterrupt(ENC_CLK), readEncoder, CHANGE);
|
attachInterrupt(digitalPinToInterrupt(ENC_CLK), readEncoder, CHANGE);
|
||||||
attachInterrupt(digitalPinToInterrupt(ENC_DT), readEncoder, CHANGE);
|
attachInterrupt(digitalPinToInterrupt(ENC_DT), readEncoder, CHANGE);
|
||||||
|
|
||||||
// 2. Setup Display
|
ui.begin();
|
||||||
// Note: Using default I2C pins (SDA=GP4, SCL=GP5) which works on both cores.
|
midi.begin();
|
||||||
Wire.begin();
|
|
||||||
|
|
||||||
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
|
|
||||||
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
|
|
||||||
Serial.println(F("SSD1306 allocation failed"));
|
|
||||||
for(;;); // Don't proceed, loop forever
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Setup NeoPixel Matrix
|
|
||||||
pixels.begin();
|
|
||||||
pixels.setBrightness(40); // Set brightness to a medium-low value (0-255)
|
|
||||||
pixels.clear();
|
|
||||||
pixels.show();
|
|
||||||
|
|
||||||
// 4. Setup MIDI Serial
|
|
||||||
Serial1.setTX(PIN_MIDI_TX);
|
|
||||||
Serial1.begin(31250);
|
|
||||||
Serial.println(F("MIDI Serial initialized on GP0/GP1"));
|
|
||||||
|
|
||||||
// 5. Init Sequence
|
// 5. Init Sequence
|
||||||
randomSeed(micros());
|
randomSeed(micros());
|
||||||
@ -245,38 +187,11 @@ void setup() {
|
|||||||
}
|
}
|
||||||
isPlaying = false; // Don't start playing on boot
|
isPlaying = false; // Don't start playing on boot
|
||||||
|
|
||||||
display.clearDisplay();
|
|
||||||
display.display();
|
|
||||||
|
|
||||||
Serial.println(F("Started."));
|
Serial.println(F("Started."));
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendMidi(uint8_t status, uint8_t note, uint8_t velocity) {
|
|
||||||
uint8_t channelStatus = status | (shMidiChannel - 1);
|
|
||||||
Serial1.write(channelStatus);
|
|
||||||
Serial1.write(note);
|
|
||||||
Serial1.write(velocity);
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendMidiRealtime(uint8_t status) {
|
|
||||||
mutex_enter_blocking(&midiMutex);
|
|
||||||
Serial1.write(status);
|
|
||||||
mutex_exit(&midiMutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void generateRandomScale() {
|
void generateRandomScale() {
|
||||||
numScaleNotes = random(3, 13); // 3 to 12 notes
|
strategies[currentStrategyIndex]->generateScale(scaleNotes, numScaleNotes);
|
||||||
for (int i = 0; i < 12; i++) {
|
|
||||||
scaleNotes[i] = i; // Fill with all notes
|
|
||||||
}
|
|
||||||
// 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 generateSequenceData(int themeType, Step* target) {
|
void generateSequenceData(int themeType, Step* target) {
|
||||||
@ -287,14 +202,14 @@ void generateSequenceData(int themeType, Step* target) {
|
|||||||
void generateTheme(int themeType) {
|
void generateTheme(int themeType) {
|
||||||
currentThemeIndex = themeType;
|
currentThemeIndex = themeType;
|
||||||
needsPanic = true;
|
needsPanic = true;
|
||||||
mutex_enter_blocking(&midiMutex);
|
midi.lock();
|
||||||
generateSequenceData(themeType, sequence);
|
generateSequenceData(themeType, sequence);
|
||||||
mutex_exit(&midiMutex);
|
midi.unlock();
|
||||||
|
|
||||||
clockCount = 0;
|
clockCount = 0;
|
||||||
lastClockTime = micros();
|
lastClockTime = micros();
|
||||||
playbackStep = 0;
|
playbackStep = 0;
|
||||||
sendMidiRealtime(0xFA); // MIDI Start
|
midi.sendRealtime(0xFA); // MIDI Start
|
||||||
isPlaying = true;
|
isPlaying = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,9 +234,9 @@ void handleInput() {
|
|||||||
int newNote = sequence[stepIndex].note + delta;
|
int newNote = sequence[stepIndex].note + delta;
|
||||||
if (newNote < -1) newNote = -1;
|
if (newNote < -1) newNote = -1;
|
||||||
if (newNote > 127) newNote = 127;
|
if (newNote > 127) newNote = 127;
|
||||||
mutex_enter_blocking(&midiMutex);
|
midi.lock();
|
||||||
sequence[stepIndex].note = newNote;
|
sequence[stepIndex].note = newNote;
|
||||||
mutex_exit(&midiMutex);
|
midi.unlock();
|
||||||
} else {
|
} else {
|
||||||
// Move Cursor
|
// Move Cursor
|
||||||
navigationSelection += (delta > 0 ? 1 : -1);
|
navigationSelection += (delta > 0 ? 1 : -1);
|
||||||
@ -404,28 +319,28 @@ void handleInput() {
|
|||||||
case UI_MENU_RANDOMIZE:
|
case UI_MENU_RANDOMIZE:
|
||||||
if (menuSelection == 0) { currentState = UI_MENU_MAIN; menuSelection = 1; }
|
if (menuSelection == 0) { currentState = UI_MENU_MAIN; menuSelection = 1; }
|
||||||
if (menuSelection == 1) {
|
if (menuSelection == 1) {
|
||||||
generateRandomScale(); // Scale
|
|
||||||
if (isPlaying) {
|
|
||||||
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
|
|
||||||
mutex_enter_blocking(&midiMutex);
|
|
||||||
generateSequenceData(theme, nextSequence);
|
|
||||||
sequenceChangeScheduled = true;
|
|
||||||
mutex_exit(&midiMutex);
|
|
||||||
}
|
|
||||||
saveSequence(true);
|
|
||||||
}
|
|
||||||
if (menuSelection == 2) {
|
|
||||||
melodySeed = random(10000); // Melody
|
melodySeed = random(10000); // Melody
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
|
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
|
||||||
mutex_enter_blocking(&midiMutex);
|
midi.lock();
|
||||||
generateSequenceData(theme, nextSequence);
|
generateSequenceData(theme, nextSequence);
|
||||||
sequenceChangeScheduled = true;
|
sequenceChangeScheduled = true;
|
||||||
mutex_exit(&midiMutex);
|
midi.unlock();
|
||||||
|
}
|
||||||
|
saveSequence(true);
|
||||||
|
}
|
||||||
|
if (menuSelection == 2) currentState = UI_EDIT_FLAVOUR; // Flavour
|
||||||
|
if (menuSelection == 3) { // Scale
|
||||||
|
generateRandomScale();
|
||||||
|
if (isPlaying) {
|
||||||
|
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
|
||||||
|
midi.lock();
|
||||||
|
generateSequenceData(theme, nextSequence);
|
||||||
|
sequenceChangeScheduled = true;
|
||||||
|
midi.unlock();
|
||||||
}
|
}
|
||||||
saveSequence(true);
|
saveSequence(true);
|
||||||
}
|
}
|
||||||
if (menuSelection == 3) currentState = UI_EDIT_FLAVOUR;
|
|
||||||
if (menuSelection == 4) currentState = UI_EDIT_TEMPO;
|
if (menuSelection == 4) currentState = UI_EDIT_TEMPO;
|
||||||
if (menuSelection == 5) mutationEnabled = !mutationEnabled;
|
if (menuSelection == 5) mutationEnabled = !mutationEnabled;
|
||||||
if (menuSelection == 6) {
|
if (menuSelection == 6) {
|
||||||
@ -438,10 +353,10 @@ void handleInput() {
|
|||||||
const int selectedTheme = menuSelection - THEME_1_INDEX + 1;
|
const int selectedTheme = menuSelection - THEME_1_INDEX + 1;
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
queuedTheme = selectedTheme;
|
queuedTheme = selectedTheme;
|
||||||
mutex_enter_blocking(&midiMutex);
|
midi.lock();
|
||||||
generateSequenceData(queuedTheme, nextSequence);
|
generateSequenceData(queuedTheme, nextSequence);
|
||||||
sequenceChangeScheduled = true;
|
sequenceChangeScheduled = true;
|
||||||
mutex_exit(&midiMutex);
|
midi.unlock();
|
||||||
} else {
|
} else {
|
||||||
generateTheme(selectedTheme);
|
generateTheme(selectedTheme);
|
||||||
}
|
}
|
||||||
@ -450,14 +365,7 @@ void handleInput() {
|
|||||||
case UI_MENU_SETUP:
|
case UI_MENU_SETUP:
|
||||||
if (menuSelection == 0) { currentState = UI_MENU_MAIN; menuSelection = 2; }
|
if (menuSelection == 0) { currentState = UI_MENU_MAIN; menuSelection = 2; }
|
||||||
if (menuSelection == 1) currentState = UI_SETUP_CHANNEL_EDIT;
|
if (menuSelection == 1) currentState = UI_SETUP_CHANNEL_EDIT;
|
||||||
if (menuSelection == 2) {
|
if (menuSelection == 2) factoryReset();
|
||||||
saveSequence();
|
|
||||||
currentState = UI_MENU_MAIN;
|
|
||||||
}
|
|
||||||
if (menuSelection == 3) {
|
|
||||||
if (loadSequence()) showMessage("LOADED!");
|
|
||||||
currentState = UI_MENU_MAIN;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case UI_SETUP_CHANNEL_EDIT:
|
case UI_SETUP_CHANNEL_EDIT:
|
||||||
currentState = UI_MENU_SETUP;
|
currentState = UI_MENU_SETUP;
|
||||||
@ -469,6 +377,13 @@ void handleInput() {
|
|||||||
break;
|
break;
|
||||||
case UI_EDIT_FLAVOUR:
|
case UI_EDIT_FLAVOUR:
|
||||||
currentState = UI_MENU_RANDOMIZE;
|
currentState = UI_MENU_RANDOMIZE;
|
||||||
|
if (isPlaying) {
|
||||||
|
queuedTheme = currentThemeIndex;
|
||||||
|
midi.lock();
|
||||||
|
generateSequenceData(currentThemeIndex, nextSequence);
|
||||||
|
sequenceChangeScheduled = true;
|
||||||
|
midi.unlock();
|
||||||
|
}
|
||||||
saveSequence(true);
|
saveSequence(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -487,11 +402,11 @@ void handleInput() {
|
|||||||
playbackStep = 0;
|
playbackStep = 0;
|
||||||
clockCount = 0;
|
clockCount = 0;
|
||||||
lastClockTime = micros();
|
lastClockTime = micros();
|
||||||
sendMidiRealtime(0xFA); // MIDI Start
|
midi.sendRealtime(0xFA); // MIDI Start
|
||||||
} else {
|
} else {
|
||||||
// Send All Notes Off on stop (CC 123)
|
// Send All Notes Off on stop (CC 123)
|
||||||
needsPanic = true;
|
needsPanic = true;
|
||||||
sendMidiRealtime(0xFC); // MIDI Stop
|
midi.sendRealtime(0xFC); // MIDI Stop
|
||||||
queuedTheme = -1;
|
queuedTheme = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -509,13 +424,13 @@ void handlePlayback() {
|
|||||||
if (currentMicros - lastClockTime >= clockInterval) {
|
if (currentMicros - lastClockTime >= clockInterval) {
|
||||||
lastClockTime += clockInterval;
|
lastClockTime += clockInterval;
|
||||||
|
|
||||||
sendMidiRealtime(0xF8); // MIDI Clock
|
midi.sendRealtime(0xF8); // MIDI Clock
|
||||||
|
|
||||||
clockCount++;
|
clockCount++;
|
||||||
if (clockCount < 6) return; // 24 ppqn / 4 = 6 pulses per 16th note
|
if (clockCount < 6) return; // 24 ppqn / 4 = 6 pulses per 16th note
|
||||||
clockCount = 0;
|
clockCount = 0;
|
||||||
|
|
||||||
mutex_enter_blocking(&midiMutex);
|
midi.lock();
|
||||||
|
|
||||||
// Determine if we are tying to the next note
|
// Determine if we are tying to the next note
|
||||||
int nextStep = playbackStep + 1;
|
int nextStep = playbackStep + 1;
|
||||||
@ -526,7 +441,7 @@ void handlePlayback() {
|
|||||||
|
|
||||||
// Note Off for previous step (if NOT tied)
|
// Note Off for previous step (if NOT tied)
|
||||||
if (!isTied && prevNote != -1) {
|
if (!isTied && prevNote != -1) {
|
||||||
sendMidi(0x80, prevNote, 0);
|
midi.sendNoteOff(prevNote, shMidiChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
playbackStep++;
|
playbackStep++;
|
||||||
@ -549,7 +464,7 @@ void handlePlayback() {
|
|||||||
sequenceChangeScheduled = true;
|
sequenceChangeScheduled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMidi(0xB0, 123, 0); // Panic / All Notes Off
|
midi.panic(shMidiChannel); // Panic / All Notes Off
|
||||||
|
|
||||||
if (sequenceChangeScheduled) {
|
if (sequenceChangeScheduled) {
|
||||||
memcpy(sequence, nextSequence, sizeof(sequence));
|
memcpy(sequence, nextSequence, sizeof(sequence));
|
||||||
@ -576,336 +491,35 @@ void handlePlayback() {
|
|||||||
// Note On for new step
|
// Note On for new step
|
||||||
if (sequence[playbackStep].note != -1) {
|
if (sequence[playbackStep].note != -1) {
|
||||||
uint8_t velocity = sequence[playbackStep].accent ? 127 : 100;
|
uint8_t velocity = sequence[playbackStep].accent ? 127 : 100;
|
||||||
sendMidi(0x90, sequence[playbackStep].note, velocity);
|
midi.sendNoteOn(sequence[playbackStep].note, velocity, shMidiChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note Off for previous step (if tied - delayed Note Off)
|
// Note Off for previous step (if tied - delayed Note Off)
|
||||||
if (isTied && prevNote != -1) {
|
if (isTied && prevNote != -1) {
|
||||||
sendMidi(0x80, prevNote, 0);
|
midi.sendNoteOff(prevNote, shMidiChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_exit(&midiMutex);
|
midi.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawMenu(const char* title, const char* items[], int count, int selection) {
|
|
||||||
display.println(title);
|
|
||||||
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
|
|
||||||
|
|
||||||
// Simple scrolling: keep selection visible
|
|
||||||
int start = 0;
|
|
||||||
if (selection >= 5) {
|
|
||||||
start = selection - 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
int y = 10;
|
|
||||||
for (int i = start; i < count; i++) {
|
|
||||||
if (y > 55) break; // Stop if we run out of screen space
|
|
||||||
|
|
||||||
if (i == selection) {
|
|
||||||
display.fillRect(0, y, 128, 8, SSD1306_WHITE);
|
|
||||||
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
|
|
||||||
} else {
|
|
||||||
display.setTextColor(SSD1306_WHITE);
|
|
||||||
}
|
|
||||||
display.setCursor(2, y);
|
|
||||||
display.print(items[i]);
|
|
||||||
|
|
||||||
// Special case for channel display
|
|
||||||
if (currentState == UI_MENU_SETUP && i == 1) {
|
|
||||||
display.print(F(": "));
|
|
||||||
display.print(midiChannel);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special case for queued theme
|
|
||||||
if (currentState == UI_MENU_RANDOMIZE && i >= THEME_1_INDEX && queuedTheme == (i - THEME_1_INDEX + 1)) {
|
|
||||||
display.print(F(" [NEXT]"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special case for active theme
|
|
||||||
if (currentState == UI_MENU_RANDOMIZE && i >= THEME_1_INDEX && currentThemeIndex == (i - THEME_1_INDEX + 1)) {
|
|
||||||
display.print(F(" *"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special cases for Randomize Menu values
|
|
||||||
if (currentState == UI_MENU_RANDOMIZE) {
|
|
||||||
if (i == 1) { // Scale
|
|
||||||
display.print(F(": "));
|
|
||||||
if (numScaleNotes > 0) {
|
|
||||||
const char* noteNames[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
|
|
||||||
for (int j = 0; j < min(numScaleNotes, 6); j++) {
|
|
||||||
display.print(noteNames[scaleNotes[j]]);
|
|
||||||
if (j < min(numScaleNotes, 6) - 1) display.print(F(" "));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (i == 2) { // Melody
|
|
||||||
display.print(F(": "));
|
|
||||||
display.print(melodySeed);
|
|
||||||
} else if (i == 3) { // Flavour
|
|
||||||
display.print(F(": "));
|
|
||||||
display.print(strategies[currentStrategyIndex]->getName());
|
|
||||||
} else if (i == 4) { // Tempo
|
|
||||||
display.print(F(": "));
|
|
||||||
display.print(tempo);
|
|
||||||
} else if (i == 5) { // Mutation
|
|
||||||
display.print(F(": "));
|
|
||||||
display.print(mutationEnabled ? F("ON") : F("OFF"));
|
|
||||||
} else if (i == 6) { // Song Mode
|
|
||||||
display.print(F(": "));
|
|
||||||
display.print(songModeEnabled ? F("ON") : F("OFF"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
y += 9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void drawTracker() {
|
|
||||||
display.clearDisplay();
|
|
||||||
display.setTextSize(1);
|
|
||||||
display.setTextColor(SSD1306_WHITE);
|
|
||||||
display.setCursor(0, 0);
|
|
||||||
|
|
||||||
// Header
|
|
||||||
display.print(F("SEQ "));
|
|
||||||
if (navigationSelection > 0 && isEditing) {
|
|
||||||
display.print(F("[EDIT]"));
|
|
||||||
} else {
|
|
||||||
display.print(F("[NAV] "));
|
|
||||||
}
|
|
||||||
display.print(F(" CH:"));
|
|
||||||
display.print(midiChannel);
|
|
||||||
|
|
||||||
display.println();
|
|
||||||
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
|
|
||||||
|
|
||||||
// Steps
|
|
||||||
int y = 10;
|
|
||||||
mutex_enter_blocking(&midiMutex);
|
|
||||||
for (int i = 0; i < 6; i++) {
|
|
||||||
int itemIndex = i + scrollOffset;
|
|
||||||
if (itemIndex > NUM_STEPS) break;
|
|
||||||
|
|
||||||
// Draw Cursor
|
|
||||||
if (itemIndex == navigationSelection) {
|
|
||||||
display.fillRect(0, y, 128, 8, SSD1306_WHITE);
|
|
||||||
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Invert text
|
|
||||||
} else {
|
|
||||||
display.setTextColor(SSD1306_WHITE);
|
|
||||||
}
|
|
||||||
display.setCursor(2, y);
|
|
||||||
|
|
||||||
if (itemIndex == 0) {
|
|
||||||
display.print(F(">> MENU"));
|
|
||||||
} else {
|
|
||||||
int stepIndex = itemIndex - 1;
|
|
||||||
bool isPlayback = isPlaying && (stepIndex == playbackStep);
|
|
||||||
|
|
||||||
if (isPlayback) {
|
|
||||||
if (itemIndex == navigationSelection) display.setTextColor(SSD1306_WHITE, SSD1306_BLACK);
|
|
||||||
else display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step Number
|
|
||||||
if (stepIndex < 10) display.print(F("0"));
|
|
||||||
display.print(stepIndex);
|
|
||||||
|
|
||||||
if (isPlayback) {
|
|
||||||
if (itemIndex == navigationSelection) display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
|
|
||||||
else display.setTextColor(SSD1306_WHITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
display.print(F(" | "));
|
|
||||||
|
|
||||||
// Note Value
|
|
||||||
int n = sequence[stepIndex].note;
|
|
||||||
if (n == -1) {
|
|
||||||
display.print(F("---"));
|
|
||||||
} else {
|
|
||||||
// Basic Note to String conversion
|
|
||||||
const char* noteNames[] = {"C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-"};
|
|
||||||
display.print(noteNames[n % 12]);
|
|
||||||
display.print(n / 12 - 1); // Octave
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
y += 9;
|
|
||||||
}
|
|
||||||
mutex_exit(&midiMutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void drawUI() {
|
void drawUI() {
|
||||||
display.clearDisplay();
|
midi.lock();
|
||||||
display.setTextSize(1);
|
ui.draw(currentState, menuSelection, navigationSelection, isEditing, midiChannel, tempo, strategies[currentStrategyIndex], queuedTheme, currentThemeIndex, numScaleNotes, scaleNotes, melodySeed, mutationEnabled, songModeEnabled, sequence, scrollOffset, playbackStep, isPlaying, mainMenu, mainMenuCount, randomizeMenu, randomizeMenuCount, setupMenu, setupMenuCount, THEME_1_INDEX);
|
||||||
display.setTextColor(SSD1306_WHITE);
|
midi.unlock();
|
||||||
display.setCursor(0, 0);
|
|
||||||
|
|
||||||
switch(currentState) {
|
|
||||||
case UI_TRACKER:
|
|
||||||
drawTracker();
|
|
||||||
break;
|
|
||||||
case UI_MENU_MAIN:
|
|
||||||
drawMenu("MAIN MENU", mainMenu, mainMenuCount, menuSelection);
|
|
||||||
break;
|
|
||||||
case UI_MENU_RANDOMIZE:
|
|
||||||
drawMenu("RANDOMIZE", randomizeMenu, randomizeMenuCount, menuSelection);
|
|
||||||
break;
|
|
||||||
case UI_MENU_SETUP:
|
|
||||||
drawMenu("SETUP", setupMenu, setupMenuCount, menuSelection);
|
|
||||||
break;
|
|
||||||
case UI_SETUP_CHANNEL_EDIT:
|
|
||||||
display.println(F("SET MIDI CHANNEL"));
|
|
||||||
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
|
|
||||||
display.setCursor(20, 25);
|
|
||||||
display.setTextSize(2);
|
|
||||||
display.print(F("CH: "));
|
|
||||||
if (midiChannel < 10) display.print(F(" "));
|
|
||||||
display.print(midiChannel);
|
|
||||||
display.setTextSize(1);
|
|
||||||
display.setCursor(0, 50);
|
|
||||||
display.println(F(" (Press to confirm)"));
|
|
||||||
break;
|
|
||||||
case UI_EDIT_TEMPO:
|
|
||||||
display.println(F("SET TEMPO"));
|
|
||||||
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
|
|
||||||
display.setCursor(20, 25);
|
|
||||||
display.setTextSize(2);
|
|
||||||
display.print(F("BPM: "));
|
|
||||||
display.print(tempo);
|
|
||||||
display.setTextSize(1);
|
|
||||||
display.setCursor(0, 50);
|
|
||||||
display.println(F(" (Press to confirm)"));
|
|
||||||
break;
|
|
||||||
case UI_EDIT_FLAVOUR:
|
|
||||||
display.println(F("SET FLAVOUR"));
|
|
||||||
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
|
|
||||||
display.setCursor(20, 25);
|
|
||||||
display.setTextSize(2);
|
|
||||||
display.print(strategies[currentStrategyIndex]->getName());
|
|
||||||
display.setTextSize(1);
|
|
||||||
display.setCursor(0, 50);
|
|
||||||
display.println(F(" (Press to confirm)"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
display.display();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to convert X,Y to pixel index for an 8x8 matrix.
|
|
||||||
// Assumes row-major wiring (NOT serpentine).
|
|
||||||
// If your matrix is wired differently, you'll need to change this function.
|
|
||||||
int getPixelIndex(int x, int y) {
|
|
||||||
return y * 8 + x;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t getNoteColor(int note, bool dim) {
|
|
||||||
if (note == -1) return 0;
|
|
||||||
// Map note to hue, avoiding Green (approx 21845) which is used for playback cursor.
|
|
||||||
// We start from Cyan (~30000) and go up to Orange (~8000 wrapped).
|
|
||||||
// Range: 30000 to 65536+8000 = 73536. Width = 43536.
|
|
||||||
// Step per semitone: 43536 / 12 = 3628.
|
|
||||||
// This ensures notes are distinct colors but never pure Green.
|
|
||||||
uint16_t hue = 30000 + (note % 12) * 3628;
|
|
||||||
return Adafruit_NeoPixel::ColorHSV(hue, 255, dim ? 10 : 50);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateLeds() {
|
void updateLeds() {
|
||||||
pixels.clear(); // Clear buffer
|
midi.lock();
|
||||||
|
ui.updateLeds(sequence, navigationSelection, playbackStep, isPlaying, currentState, isEditing, songModeEnabled, songRepeatsRemaining, sequenceChangeScheduled);
|
||||||
mutex_enter_blocking(&midiMutex);
|
midi.unlock();
|
||||||
for (int s = 0; s < NUM_STEPS; s++) {
|
|
||||||
int x = s % 8;
|
|
||||||
int yBase = (s / 8) * 4;
|
|
||||||
|
|
||||||
uint32_t color = 0;
|
|
||||||
uint32_t dimColor = 0;
|
|
||||||
if (sequence[s].note != -1) {
|
|
||||||
color = getNoteColor(sequence[s].note, sequence[s].tie);
|
|
||||||
dimColor = getNoteColor(sequence[s].note, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t colorP0 = 0;
|
|
||||||
uint32_t colorP1 = 0;
|
|
||||||
uint32_t colorP2 = 0;
|
|
||||||
uint32_t colorP3 = 0;
|
|
||||||
|
|
||||||
if (sequence[s].note != -1) {
|
|
||||||
int octave = sequence[s].note / 12;
|
|
||||||
|
|
||||||
if (octave > 4) {
|
|
||||||
// Octave Up -> Top Row (P0)
|
|
||||||
colorP0 = color;
|
|
||||||
if (sequence[s].accent) colorP1 = dimColor;
|
|
||||||
} else if (octave < 4) {
|
|
||||||
// Octave Down -> Bottom Row (P2)
|
|
||||||
colorP2 = color;
|
|
||||||
if (sequence[s].accent) colorP1 = dimColor;
|
|
||||||
} else {
|
|
||||||
// Normal -> Middle Row (P1)
|
|
||||||
colorP1 = color;
|
|
||||||
if (sequence[s].accent) {
|
|
||||||
colorP0 = dimColor;
|
|
||||||
colorP2 = dimColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int stepNavIndex = navigationSelection - 1;
|
|
||||||
uint32_t cursorColor = pixels.Color(0 + (s%2) * 20, 0 + (s%2) * 20, 50 + (s%2) * 20); // Blue for paused
|
|
||||||
if (isPlaying) {
|
|
||||||
cursorColor = pixels.Color(0, 50, 0); // Green for playback
|
|
||||||
if (songModeEnabled) {
|
|
||||||
// Song Mode: Show repeats on bottom row
|
|
||||||
// Right aligned.
|
|
||||||
// If repeats = 1 (last one), blink yellow.
|
|
||||||
if (s >= NUM_STEPS/2) { // second half = bottom row
|
|
||||||
int col = x;
|
|
||||||
int repeatsToDraw = min(songRepeatsRemaining, 8);
|
|
||||||
if (col >= (8 - repeatsToDraw)) {
|
|
||||||
if (songRepeatsRemaining == 1 && col == 7 && (millis() / 250) % 2) {
|
|
||||||
cursorColor = pixels.Color(0, 250, 0); // Max green, change is about to happen
|
|
||||||
} else {
|
|
||||||
cursorColor = pixels.Color(80, 100, 0); // Yellow-green, remaining repeat
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (currentState == UI_TRACKER) {
|
|
||||||
cursorColor = isEditing ? pixels.Color(50, 0, 0) : pixels.Color(40, 40, 40);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cursorColor != 0) {
|
|
||||||
bool isCursorHere = (isPlaying && s == playbackStep) ||
|
|
||||||
(!isPlaying && currentState == UI_TRACKER && s == stepNavIndex);
|
|
||||||
if (isCursorHere) {
|
|
||||||
colorP3 = cursorColor;
|
|
||||||
} else {
|
|
||||||
// Lightly colored background for cursor row
|
|
||||||
uint8_t r = (uint8_t)(cursorColor >> 16);
|
|
||||||
uint8_t g = (uint8_t)(cursorColor >> 8);
|
|
||||||
uint8_t b = (uint8_t)cursorColor;
|
|
||||||
colorP3 = pixels.Color(r/5, g/5, b/5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pixels.setPixelColor(getPixelIndex(x, yBase), colorP0);
|
|
||||||
pixels.setPixelColor(getPixelIndex(x, yBase + 1), colorP1);
|
|
||||||
pixels.setPixelColor(getPixelIndex(x, yBase + 2), colorP2);
|
|
||||||
pixels.setPixelColor(getPixelIndex(x, yBase + 3), colorP3);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sequenceChangeScheduled && (millis() / 125) % 2) {
|
|
||||||
pixels.setPixelColor(NUM_PIXELS - 1, pixels.Color(127, 50, 0));
|
|
||||||
}
|
|
||||||
mutex_exit(&midiMutex);
|
|
||||||
|
|
||||||
pixels.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop1() {
|
void loop1() {
|
||||||
if (needsPanic) {
|
if (needsPanic) {
|
||||||
mutex_enter_blocking(&midiMutex);
|
midi.lock();
|
||||||
sendMidi(0xB0, 123, 0);
|
midi.panic(shMidiChannel);
|
||||||
mutex_exit(&midiMutex);
|
midi.unlock();
|
||||||
needsPanic = false;
|
needsPanic = false;
|
||||||
}
|
}
|
||||||
handlePlayback();
|
handlePlayback();
|
||||||
@ -917,12 +531,12 @@ void loop() {
|
|||||||
int nextTheme = random(1, 8); // Themes 1-7
|
int nextTheme = random(1, 8); // Themes 1-7
|
||||||
int repeats = random(1, 9); // 1-8 repeats
|
int repeats = random(1, 9); // 1-8 repeats
|
||||||
|
|
||||||
mutex_enter_blocking(&midiMutex);
|
midi.lock();
|
||||||
generateSequenceData(nextTheme, nextSequence);
|
generateSequenceData(nextTheme, nextSequence);
|
||||||
queuedTheme = nextTheme;
|
queuedTheme = nextTheme;
|
||||||
nextSongRepeats = repeats;
|
nextSongRepeats = repeats;
|
||||||
sequenceChangeScheduled = true;
|
sequenceChangeScheduled = true;
|
||||||
mutex_exit(&midiMutex);
|
midi.unlock();
|
||||||
|
|
||||||
songModeNeedsNext = false;
|
songModeNeedsNext = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,4 +9,26 @@ struct Step {
|
|||||||
bool tie;
|
bool tie;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum UIState {
|
||||||
|
UI_TRACKER,
|
||||||
|
UI_MENU_MAIN,
|
||||||
|
UI_MENU_RANDOMIZE,
|
||||||
|
UI_MENU_SETUP,
|
||||||
|
UI_SETUP_CHANNEL_EDIT,
|
||||||
|
UI_EDIT_TEMPO,
|
||||||
|
UI_EDIT_FLAVOUR
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void sortArray(int arr[], int size) {
|
||||||
|
for (int i = 0; i < size - 1; i++) {
|
||||||
|
for (int j = 0; j < size - i - 1; j++) {
|
||||||
|
if (arr[j] > arr[j + 1]) {
|
||||||
|
int temp = arr[j];
|
||||||
|
arr[j] = arr[j + 1];
|
||||||
|
arr[j + 1] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
275
UIManager.cpp
Normal file
275
UIManager.cpp
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
#include "UIManager.h"
|
||||||
|
|
||||||
|
// --- HARDWARE CONFIGURATION ---
|
||||||
|
#define SCREEN_WIDTH 128
|
||||||
|
#define SCREEN_HEIGHT 64
|
||||||
|
#define OLED_RESET -1
|
||||||
|
#define SCREEN_ADDRESS 0x3C
|
||||||
|
|
||||||
|
#define PIN_NEOPIXEL 16
|
||||||
|
#define NUM_PIXELS 64
|
||||||
|
#define NUM_STEPS 16
|
||||||
|
|
||||||
|
UIManager ui;
|
||||||
|
|
||||||
|
UIManager::UIManager()
|
||||||
|
: display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET),
|
||||||
|
pixels(NUM_PIXELS, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIManager::begin() {
|
||||||
|
// Setup Display
|
||||||
|
Wire.begin();
|
||||||
|
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
|
||||||
|
Serial.println(F("SSD1306 allocation failed"));
|
||||||
|
for(;;);
|
||||||
|
}
|
||||||
|
display.clearDisplay();
|
||||||
|
display.display();
|
||||||
|
|
||||||
|
// Setup NeoPixel Matrix
|
||||||
|
pixels.begin();
|
||||||
|
pixels.setBrightness(40);
|
||||||
|
pixels.clear();
|
||||||
|
pixels.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIManager::showMessage(const char* msg) {
|
||||||
|
display.clearDisplay();
|
||||||
|
display.setCursor(10, 25);
|
||||||
|
display.setTextColor(SSD1306_WHITE);
|
||||||
|
display.setTextSize(2);
|
||||||
|
display.print(msg);
|
||||||
|
display.display();
|
||||||
|
delay(500);
|
||||||
|
display.setTextSize(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIManager::draw(UIState currentState, int menuSelection, int navSelection, bool isEditing,
|
||||||
|
int midiChannel, int tempo, MelodyStrategy* currentStrategy,
|
||||||
|
int queuedTheme, int currentThemeIndex,
|
||||||
|
int numScaleNotes, const int* scaleNotes, int melodySeed,
|
||||||
|
bool mutationEnabled, bool songModeEnabled,
|
||||||
|
const Step* sequence, int scrollOffset, int playbackStep, bool isPlaying,
|
||||||
|
const char* mainMenu[], int mainMenuCount,
|
||||||
|
const char* randomizeMenu[], int randomizeMenuCount,
|
||||||
|
const char* setupMenu[], int setupMenuCount,
|
||||||
|
int theme1Index) {
|
||||||
|
|
||||||
|
display.clearDisplay();
|
||||||
|
display.setTextSize(1);
|
||||||
|
display.setTextColor(SSD1306_WHITE);
|
||||||
|
display.setCursor(0, 0);
|
||||||
|
|
||||||
|
switch(currentState) {
|
||||||
|
case UI_TRACKER:
|
||||||
|
drawTracker(navSelection, isEditing, midiChannel, sequence, scrollOffset, playbackStep, isPlaying);
|
||||||
|
break;
|
||||||
|
case UI_MENU_MAIN:
|
||||||
|
drawMenu("MAIN MENU", mainMenu, mainMenuCount, menuSelection, currentState, midiChannel, tempo, currentStrategy->getName(), queuedTheme, currentThemeIndex, numScaleNotes, scaleNotes, melodySeed, mutationEnabled, songModeEnabled, theme1Index);
|
||||||
|
break;
|
||||||
|
case UI_MENU_RANDOMIZE:
|
||||||
|
drawMenu("RANDOMIZE", randomizeMenu, randomizeMenuCount, menuSelection, currentState, midiChannel, tempo, currentStrategy->getName(), queuedTheme, currentThemeIndex, numScaleNotes, scaleNotes, melodySeed, mutationEnabled, songModeEnabled, theme1Index);
|
||||||
|
break;
|
||||||
|
case UI_MENU_SETUP:
|
||||||
|
drawMenu("SETUP", setupMenu, setupMenuCount, menuSelection, currentState, midiChannel, tempo, currentStrategy->getName(), queuedTheme, currentThemeIndex, numScaleNotes, scaleNotes, melodySeed, mutationEnabled, songModeEnabled, theme1Index);
|
||||||
|
break;
|
||||||
|
case UI_SETUP_CHANNEL_EDIT:
|
||||||
|
display.println(F("SET MIDI CHANNEL"));
|
||||||
|
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
|
||||||
|
display.setCursor(20, 25);
|
||||||
|
display.setTextSize(2);
|
||||||
|
display.print(F("CH: "));
|
||||||
|
if (midiChannel < 10) display.print(F(" "));
|
||||||
|
display.print(midiChannel);
|
||||||
|
display.setTextSize(1);
|
||||||
|
display.setCursor(0, 50);
|
||||||
|
display.println(F(" (Press to confirm)"));
|
||||||
|
break;
|
||||||
|
case UI_EDIT_TEMPO:
|
||||||
|
display.println(F("SET TEMPO"));
|
||||||
|
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
|
||||||
|
display.setCursor(20, 25);
|
||||||
|
display.setTextSize(2);
|
||||||
|
display.print(F("BPM: "));
|
||||||
|
display.print(tempo);
|
||||||
|
display.setTextSize(1);
|
||||||
|
display.setCursor(0, 50);
|
||||||
|
display.println(F(" (Press to confirm)"));
|
||||||
|
break;
|
||||||
|
case UI_EDIT_FLAVOUR:
|
||||||
|
display.println(F("SET FLAVOUR"));
|
||||||
|
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
|
||||||
|
display.setCursor(20, 25);
|
||||||
|
display.setTextSize(2);
|
||||||
|
display.print(currentStrategy->getName());
|
||||||
|
display.setTextSize(1);
|
||||||
|
display.setCursor(0, 50);
|
||||||
|
display.println(F(" (Press to confirm)"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
display.display();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIManager::drawMenu(const char* title, const char* items[], int count, int selection,
|
||||||
|
UIState currentState, int midiChannel, int tempo, const char* flavourName,
|
||||||
|
int queuedTheme, int currentThemeIndex,
|
||||||
|
int numScaleNotes, const int* scaleNotes, int melodySeed,
|
||||||
|
bool mutationEnabled, bool songModeEnabled, int theme1Index) {
|
||||||
|
display.println(title);
|
||||||
|
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
|
||||||
|
|
||||||
|
int start = 0;
|
||||||
|
if (selection >= 5) start = selection - 4;
|
||||||
|
|
||||||
|
int y = 10;
|
||||||
|
for (int i = start; i < count; i++) {
|
||||||
|
if (y > 55) break;
|
||||||
|
|
||||||
|
if (i == selection) {
|
||||||
|
display.fillRect(0, y, 128, 8, SSD1306_WHITE);
|
||||||
|
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
|
||||||
|
} else {
|
||||||
|
display.setTextColor(SSD1306_WHITE);
|
||||||
|
}
|
||||||
|
display.setCursor(2, y);
|
||||||
|
display.print(items[i]);
|
||||||
|
|
||||||
|
if (currentState == UI_MENU_SETUP && i == 1) {
|
||||||
|
display.print(F(": ")); display.print(midiChannel);
|
||||||
|
}
|
||||||
|
if (currentState == UI_MENU_RANDOMIZE && i >= theme1Index && queuedTheme == (i - theme1Index + 1)) {
|
||||||
|
display.print(F(" [NEXT]"));
|
||||||
|
}
|
||||||
|
if (currentState == UI_MENU_RANDOMIZE && i >= theme1Index && currentThemeIndex == (i - theme1Index + 1)) {
|
||||||
|
display.print(F(" *"));
|
||||||
|
}
|
||||||
|
if (currentState == UI_MENU_RANDOMIZE) {
|
||||||
|
if (i == 1) { // Melody
|
||||||
|
display.print(F(": ")); display.print(melodySeed);
|
||||||
|
} else if (i == 2) { // Flavour
|
||||||
|
display.print(F(": ")); display.print(flavourName);
|
||||||
|
} else if (i == 3) { // Scale
|
||||||
|
display.print(F(": "));
|
||||||
|
if (numScaleNotes > 0) {
|
||||||
|
const char* noteNames[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
|
||||||
|
for (int j = 0; j < min(numScaleNotes, 6); j++) {
|
||||||
|
display.print(noteNames[scaleNotes[j]]);
|
||||||
|
if (j < min(numScaleNotes, 6) - 1) display.print(F(" "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (i == 4) { display.print(F(": ")); display.print(tempo); }
|
||||||
|
else if (i == 5) { display.print(F(": ")); display.print(mutationEnabled ? F("ON") : F("OFF")); }
|
||||||
|
else if (i == 6) { display.print(F(": ")); display.print(songModeEnabled ? F("ON") : F("OFF")); }
|
||||||
|
}
|
||||||
|
y += 9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIManager::drawTracker(int navSelection, bool isEditing, int midiChannel,
|
||||||
|
const Step* sequence, int scrollOffset, int playbackStep, bool isPlaying) {
|
||||||
|
display.print(F("SEQ "));
|
||||||
|
if (navSelection > 0 && isEditing) display.print(F("[EDIT]"));
|
||||||
|
else display.print(F("[NAV] "));
|
||||||
|
display.print(F(" CH:")); display.print(midiChannel);
|
||||||
|
display.println();
|
||||||
|
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
|
||||||
|
|
||||||
|
int y = 10;
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
int itemIndex = i + scrollOffset;
|
||||||
|
if (itemIndex > NUM_STEPS) break;
|
||||||
|
|
||||||
|
if (itemIndex == navSelection) {
|
||||||
|
display.fillRect(0, y, 128, 8, SSD1306_WHITE);
|
||||||
|
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
|
||||||
|
} else {
|
||||||
|
display.setTextColor(SSD1306_WHITE);
|
||||||
|
}
|
||||||
|
display.setCursor(2, y);
|
||||||
|
|
||||||
|
if (itemIndex == 0) {
|
||||||
|
display.print(F(">> MENU"));
|
||||||
|
} else {
|
||||||
|
int stepIndex = itemIndex - 1;
|
||||||
|
bool isPlayback = isPlaying && (stepIndex == playbackStep);
|
||||||
|
if (isPlayback) {
|
||||||
|
if (itemIndex == navSelection) display.setTextColor(SSD1306_WHITE, SSD1306_BLACK);
|
||||||
|
else display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
|
||||||
|
}
|
||||||
|
if (stepIndex < 10) display.print(F("0"));
|
||||||
|
display.print(stepIndex);
|
||||||
|
if (isPlayback) {
|
||||||
|
if (itemIndex == navSelection) display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
|
||||||
|
else display.setTextColor(SSD1306_WHITE);
|
||||||
|
}
|
||||||
|
display.print(F(" | "));
|
||||||
|
int n = sequence[stepIndex].note;
|
||||||
|
if (n == -1) display.print(F("---"));
|
||||||
|
else {
|
||||||
|
const char* noteNames[] = {"C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-"};
|
||||||
|
display.print(noteNames[n % 12]);
|
||||||
|
display.print(n / 12 - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
y += 9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t UIManager::getNoteColor(int note, bool dim) {
|
||||||
|
if (note == -1) return 0;
|
||||||
|
uint16_t hue = 30000 + (note % 12) * 3628;
|
||||||
|
return Adafruit_NeoPixel::ColorHSV(hue, 255, dim ? 10 : 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
int UIManager::getPixelIndex(int x, int y) {
|
||||||
|
return y * 8 + x;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIManager::updateLeds(const Step* sequence, int navSelection, int playbackStep, bool isPlaying,
|
||||||
|
UIState currentState, bool isEditing, bool songModeEnabled,
|
||||||
|
int songRepeatsRemaining, bool sequenceChangeScheduled) {
|
||||||
|
pixels.clear();
|
||||||
|
for (int s = 0; s < NUM_STEPS; s++) {
|
||||||
|
int x = s % 8;
|
||||||
|
int yBase = (s / 8) * 4;
|
||||||
|
uint32_t color = 0, dimColor = 0;
|
||||||
|
if (sequence[s].note != -1) {
|
||||||
|
color = getNoteColor(sequence[s].note, sequence[s].tie);
|
||||||
|
dimColor = getNoteColor(sequence[s].note, true);
|
||||||
|
}
|
||||||
|
uint32_t c[4] = {0};
|
||||||
|
if (sequence[s].note != -1) {
|
||||||
|
int octave = sequence[s].note / 12;
|
||||||
|
if (octave > 4) { c[0] = color; if (sequence[s].accent) c[1] = dimColor; }
|
||||||
|
else if (octave < 4) { c[2] = color; if (sequence[s].accent) c[1] = dimColor; }
|
||||||
|
else { c[1] = color; if (sequence[s].accent) { c[0] = dimColor; c[2] = dimColor; } }
|
||||||
|
}
|
||||||
|
int stepNavIndex = navSelection - 1;
|
||||||
|
// Apply cursor color logic from original code more strictly
|
||||||
|
uint32_t cursorColor = 0;
|
||||||
|
if (isPlaying) {
|
||||||
|
cursorColor = pixels.Color(0, 50, 0);
|
||||||
|
if (songModeEnabled && s >= 8) {
|
||||||
|
int repeats = min(songRepeatsRemaining, 8);
|
||||||
|
if (x >= (8 - repeats)) cursorColor = (songRepeatsRemaining == 1 && x == 7 && (millis()/250)%2) ? pixels.Color(255, 200, 0) : pixels.Color(100, 220, 40);
|
||||||
|
}
|
||||||
|
} else if (currentState == UI_TRACKER) {
|
||||||
|
cursorColor = isEditing ? pixels.Color(50, 0, 0) : pixels.Color(40, 40, 40);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isCursorHere = (isPlaying && s == playbackStep) || (!isPlaying && currentState == UI_TRACKER && s == stepNavIndex);
|
||||||
|
if (cursorColor != 0) {
|
||||||
|
if (isCursorHere) c[3] = cursorColor;
|
||||||
|
else {
|
||||||
|
uint8_t r = (uint8_t)(cursorColor >> 16), g = (uint8_t)(cursorColor >> 8), b = (uint8_t)cursorColor;
|
||||||
|
c[3] = pixels.Color(r/5, g/5, b/5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i=0; i<4; i++) pixels.setPixelColor(getPixelIndex(x, yBase + i), c[i]);
|
||||||
|
}
|
||||||
|
if (sequenceChangeScheduled && (millis() / 125) % 2) pixels.setPixelColor(NUM_PIXELS - 1, pixels.Color(127, 50, 0));
|
||||||
|
pixels.show();
|
||||||
|
}
|
||||||
51
UIManager.h
Normal file
51
UIManager.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#ifndef UI_MANAGER_H
|
||||||
|
#define UI_MANAGER_H
|
||||||
|
|
||||||
|
#include <Adafruit_GFX.h>
|
||||||
|
#include <Adafruit_SSD1306.h>
|
||||||
|
#include <Adafruit_NeoPixel.h>
|
||||||
|
#include "TrackerTypes.h"
|
||||||
|
#include "MelodyStrategy.h"
|
||||||
|
|
||||||
|
class UIManager {
|
||||||
|
public:
|
||||||
|
UIManager();
|
||||||
|
void begin();
|
||||||
|
|
||||||
|
void showMessage(const char* msg);
|
||||||
|
|
||||||
|
void draw(UIState currentState, int menuSelection, int navSelection, bool isEditing,
|
||||||
|
int midiChannel, int tempo, MelodyStrategy* currentStrategy,
|
||||||
|
int queuedTheme, int currentThemeIndex,
|
||||||
|
int numScaleNotes, const int* scaleNotes, int melodySeed,
|
||||||
|
bool mutationEnabled, bool songModeEnabled,
|
||||||
|
const Step* sequence, int scrollOffset, int playbackStep, bool isPlaying,
|
||||||
|
const char* mainMenu[], int mainMenuCount,
|
||||||
|
const char* randomizeMenu[], int randomizeMenuCount,
|
||||||
|
const char* setupMenu[], int setupMenuCount,
|
||||||
|
int theme1Index);
|
||||||
|
|
||||||
|
void updateLeds(const Step* sequence, int navSelection, int playbackStep, bool isPlaying,
|
||||||
|
UIState currentState, bool isEditing, bool songModeEnabled,
|
||||||
|
int songRepeatsRemaining, bool sequenceChangeScheduled);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Adafruit_SSD1306 display;
|
||||||
|
Adafruit_NeoPixel pixels;
|
||||||
|
|
||||||
|
void drawMenu(const char* title, const char* items[], int count, int selection,
|
||||||
|
UIState currentState, int midiChannel, int tempo, const char* flavourName,
|
||||||
|
int queuedTheme, int currentThemeIndex,
|
||||||
|
int numScaleNotes, const int* scaleNotes, int melodySeed,
|
||||||
|
bool mutationEnabled, bool songModeEnabled, int theme1Index);
|
||||||
|
|
||||||
|
void drawTracker(int navSelection, bool isEditing, int midiChannel,
|
||||||
|
const Step* sequence, int scrollOffset, int playbackStep, bool isPlaying);
|
||||||
|
|
||||||
|
uint32_t getNoteColor(int note, bool dim);
|
||||||
|
int getPixelIndex(int x, int y);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern UIManager ui;
|
||||||
|
|
||||||
|
#endif
|
||||||
Loading…
Reference in New Issue
Block a user