Compare commits

..

No commits in common. "e6a471186149aa91e50357c933d0f38d70fa6c4a" and "4fd9e1b3e9467670b9af9d76f47cdacf18a93951" have entirely different histories.

10 changed files with 37 additions and 425 deletions

View File

@ -2,83 +2,19 @@
// MIDI UART Pins (GP0/GP1)
#define PIN_MIDI_TX 0
#define PIN_MIDI_RX 1
MidiDriver midi;
MidiDriver::MidiDriver() : lastInputNote(-1), lastInputVelocity(0), _runningStatus(0), _byteIndex(0), _data1(0), _data2(0) {
MidiDriver::MidiDriver() {
}
void MidiDriver::begin() {
mutex_init(&_mutex);
Serial1.setTX(PIN_MIDI_TX);
Serial1.setRX(PIN_MIDI_RX);
Serial1.begin(31250);
Serial.println(F("MIDI Serial initialized on GP0/GP1"));
}
void MidiDriver::update() {
while (Serial1.available()) {
uint8_t b = Serial1.read();
Serial1.write(b); // Soft THRU: Merge input with output
// Realtime messages don't affect running status
if (b >= 0xF8) continue;
if (b >= 0x80) {
_runningStatus = b;
_byteIndex = 0;
} else if (_runningStatus) {
if (_byteIndex == 0) {
_data1 = b;
_byteIndex++;
// Handle 2-byte messages (Program Change 0xC0, Channel Pressure 0xD0)
uint8_t type = _runningStatus & 0xF0;
if (type == 0xC0 || type == 0xD0) {
_byteIndex = 0; // Message complete
}
} else if (_byteIndex == 1) {
_data2 = b;
_byteIndex = 0; // Message complete
uint8_t channel = (_runningStatus & 0x0F) + 1;
uint8_t type = _runningStatus & 0xF0;
if (type == 0x90) {
if (_data2 > 0) {
// Serial.print(F("Note On CH"));
// Serial.print(channel);
// Serial.print(F(": "));
// Serial.print(_data1);
// Serial.print(F(" Vel: "));
// Serial.println(_data2);
lastInputNote = _data1;
lastInputVelocity = _data2;
} else {
// Serial.print(F("Note Off CH"));
// Serial.print(channel);
// Serial.print(F(": "));
// Serial.println(_data1);
if (lastInputNote == _data1) {
lastInputNote = -1; // Note On vel 0 is Note Off
lastInputVelocity = 0;
}
}
} else if (type == 0x80) {
// Serial.print(F("Note Off CH"));
// Serial.print(channel);
// Serial.print(F(": "));
// Serial.println(_data1);
if (lastInputNote == _data1) {
lastInputNote = -1;
lastInputVelocity = 0;
}
}
}
}
}
}
void MidiDriver::lock() {
mutex_enter_blocking(&_mutex);
}

View File

@ -8,10 +8,6 @@ class MidiDriver {
public:
MidiDriver();
void begin();
void update();
volatile int lastInputNote;
volatile int lastInputVelocity;
void sendNoteOn(uint8_t note, uint8_t velocity, uint8_t channel);
void sendNoteOff(uint8_t note, uint8_t channel);
@ -23,10 +19,6 @@ public:
private:
mutex_t _mutex;
uint8_t _runningStatus;
uint8_t _byteIndex;
uint8_t _data1;
uint8_t _data2;
};
extern MidiDriver midi;

View File

@ -129,7 +129,6 @@ static void handlePlayback() {
}
void loopPlayback() {
midi.update();
if (needsPanic) {
for (int i=0; i<NUM_TRACKS; i++) {
midi.panic(midiChannels[i]);

View File

@ -22,52 +22,35 @@ The project is best powered in two parts:
1. **Raspberry Pi Pico W**: Power the Pico via its Micro-USB port from a computer or a USB wall adapter.
2. **NeoPixel 8x8 Matrix**: This component is power-hungry and **requires a separate, external 5V power supply**. A power supply capable of delivering at least 2A is recommended.
### Single 5V Supply with Diode
You can power the Pico from the same external 5V source used for the NeoPixels by connecting a diode (e.g., 1N4001 silicon or 1N5817 Schottky).
* **Anode (+)**: Connect to External 5V Supply (+).
* **Cathode (-/Striped)**: Connect to **VSYS (Pin 39)** on the Pico.
This drops the voltage (to ~4.3V with silicon, ~4.7V with Schottky) which is safe for the Pico, and prevents power back-feeding into your computer's USB port if both are connected.
> **WARNING**: Do NOT power the NeoPixel matrix from the Pico's 3.3V or VBUS pins. VBUS is connected directly to the USB port, which is typically limited to 500mA. The matrix can draw over 2A, which could overload and damage your host computer's USB port.
### Component Wiring
Make sure to establish a **common ground** by connecting the ground from your external 5V power supply to one of the GND pins on the Pico.
+----------------------+------------------------+-----------------------------+
| Component | Pico Pin | Description |
+----------------------+------------------------+-----------------------------+
| SSD1306 OLED | | |
| VCC | 3V3 (OUT) (Pin 36) | 3.3V Power |
| GND | GND (Pin 38) | Ground |
| SDA | GP4 (Pin 6) | I2C Data |
| SCL | GP5 (Pin 7) | I2C Clock |
+----------------------+------------------------+-----------------------------+
| Rotary Encoder | | |
| + (VCC) | 3V3 (OUT) (Pin 36) | 3.3V Power |
| GND | GND (Pin 33) | Ground |
| CLK | GP12 (Pin 16) | Encoder Clock |
| DT | GP13 (Pin 17) | Encoder Data |
| SW | GP14 (Pin 19) | Encoder Switch |
+----------------------+------------------------+-----------------------------+
| WS2812B 8x8 Matrix | | |
| DIN (Data In) | GP2 (Pin 4) | NeoPixel Data |
| 5V / VCC | External 5V Supply + | External 5V Power |
| GND | External 5V Supply - | External Power Ground |
| (Matrix GND) | GND (Pin 18) | Common Ground with Pico |
+----------------------+------------------------+-----------------------------+
| MIDI OUT (Serial) | | |
| TX (MIDI OUT) | GP0 (Pin 1) | To DIN Pin 5 (via 220 ohm) |
| MIDI IN (Serial) | | (optional!) |
| RX (MIDI IN) | GP1 (Pin 2) | To DIN Pin 3 (optoisolator) |
+----------------------+------------------------+-----------------------------+
| Component | Pico Pin | Description |
| :--- | :--- | :--- |
| **SSD1306 OLED** | | |
| VCC | 3V3 (OUT) (Pin 36) | 3.3V Power |
| GND | GND (Pin 38) | Ground |
| SDA | GP4 (Pin 6) | I2C Data |
| SCL | GP5 (Pin 7) | I2C Clock |
| **Rotary Encoder** | | |
| + (VCC) | 3V3 (OUT) (Pin 36) | 3.3V Power |
| GND | GND (Pin 33) | Ground |
| CLK | GP12 (Pin 16) | Encoder Clock |
| DT | GP13 (Pin 17) | Encoder Data |
| SW | GP14 (Pin 19) | Encoder Switch |
| **WS2812B 8x8 Matrix**| | |
| DIN (Data In) | GP2 (Pin 4) | NeoPixel Data |
| 5V / VCC | External 5V Supply `+` | **External 5V Power** |
| GND | External 5V Supply `-` | **External Power Ground** |
| | GND (Pin 18) | **Common Ground with Pico** |
| **MIDI DIN (Serial)** | | |
| TX (MIDI OUT) | GP0 (Pin 1) | To DIN Pin 5 (via 220Ω) |
**MIDI Hardware Note**:
* **MIDI OUT**: Connect **GP0** to DIN Pin 5 via a 220Ω resistor. Connect DIN Pin 4 to 3.3V (3V3) via a 220Ω resistor. Connect DIN Pin 2 to GND.
* **MIDI IN**: Optional! Uses a 6N137 optocoupler based on [this guide](https://www.kieranreck.co.uk/MIDI-6N137-vs-6N138-vs-6N139/). Connect the optocoupler output to **GP1**.
Once everything is wired up, you can upload the code and your tracker should be ready to go!

View File

@ -59,7 +59,7 @@ void setup() {
randomSeed(micros());
Serial.println(F("Loading sequence."));
EEPROM.begin(4096);
EEPROM.begin(512);
if (!loadSequence()) {
Serial.println(F("Starting fresh instead."));
numSteps = NUM_STEPS;

View File

@ -13,7 +13,6 @@ volatile bool sequenceChangeScheduled = false;
volatile bool needsPanic = false;
UIState currentState = UI_MENU_MAIN;
bool protectedMode = false;
// Menus
MenuItem menuItems[] = {
@ -38,57 +37,11 @@ MenuItem menuItems[] = {
{ "Theme 7", MENU_ID_THEME_7, false, false, 1 },
{ "Setup", MENU_ID_GROUP_SETUP, true, false, 0 },
{ "Channel", MENU_ID_CHANNEL, false, false, 1 },
{ "Protected", MENU_ID_PROTECTED_MODE, false, false, 1 },
{ "Reset", MENU_ID_RESET, false, false, 1 },
{ "Patches", MENU_ID_GROUP_PATCHES, true, false, 0 },
{ "Bank 1", MENU_ID_BANK_1, true, false, 1 },
{ "Load 1.1", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 0), false, false, 2 },
{ "Load 1.2", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 1), false, false, 2 },
{ "Load 1.3", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 2), false, false, 2 },
{ "Load 1.4", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 3), false, false, 2 },
{ "Save 1.1", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 4), false, false, 2 },
{ "Save 1.2", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 5), false, false, 2 },
{ "Save 1.3", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 6), false, false, 2 },
{ "Save 1.4", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 7), false, false, 2 },
{ "Bank 2", MENU_ID_BANK_2, true, false, 1 },
{ "Load 2.1", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 8), false, false, 2 },
{ "Load 2.2", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 9), false, false, 2 },
{ "Load 2.3", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 10), false, false, 2 },
{ "Load 2.4", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 11), false, false, 2 },
{ "Save 2.1", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 12), false, false, 2 },
{ "Save 2.2", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 13), false, false, 2 },
{ "Save 2.3", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 14), false, false, 2 },
{ "Save 2.4", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 15), false, false, 2 },
{ "Bank 3", MENU_ID_BANK_3, true, false, 1 },
{ "Load 3.1", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 16), false, false, 2 },
{ "Load 3.2", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 17), false, false, 2 },
{ "Load 3.3", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 18), false, false, 2 },
{ "Load 3.4", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 19), false, false, 2 },
{ "Save 3.1", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 20), false, false, 2 },
{ "Save 3.2", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 21), false, false, 2 },
{ "Save 3.3", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 22), false, false, 2 },
{ "Save 3.4", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 23), false, false, 2 },
{ "Bank 4", MENU_ID_BANK_4, true, false, 1 },
{ "Load 4.1", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 24), false, false, 2 },
{ "Load 4.2", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 25), false, false, 2 },
{ "Load 4.3", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 26), false, false, 2 },
{ "Load 4.4", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 27), false, false, 2 },
{ "Save 4.1", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 28), false, false, 2 },
{ "Save 4.2", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 29), false, false, 2 },
{ "Save 4.3", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 30), false, false, 2 },
{ "Save 4.4", (MenuItemID)(MENU_ID_PATCH_ACTIONS_START + 31), false, false, 2 }
{ "Reset", MENU_ID_RESET, false, false, 1 }
};
extern const int menuItemsCount = sizeof(menuItems) / sizeof(MenuItem);
bool isItemVisible(int index) {
if (protectedMode) {
MenuItemID id = menuItems[index].id;
if (id >= MENU_ID_PATCH_ACTIONS_START) {
int offset = id - MENU_ID_PATCH_ACTIONS_START;
if ((offset % 8) >= 4) return false; // Save options are the last 4 in the block of 8
}
}
if (menuItems[index].indentLevel == 0) return true;
for (int i = index - 1; i >= 0; i--) {
if (menuItems[i].indentLevel < menuItems[index].indentLevel) {

View File

@ -38,15 +38,7 @@ enum MenuItemID {
MENU_ID_GROUP_SETUP,
MENU_ID_CHANNEL,
MENU_ID_PROTECTED_MODE,
MENU_ID_RESET,
MENU_ID_GROUP_PATCHES,
MENU_ID_BANK_1,
MENU_ID_BANK_2,
MENU_ID_BANK_3,
MENU_ID_BANK_4,
MENU_ID_PATCH_ACTIONS_START
MENU_ID_RESET
};
struct MenuItem {
@ -85,7 +77,6 @@ extern volatile int songRepeatsRemaining;
extern volatile int nextSongRepeats;
extern volatile bool songModeNeedsNext;
extern volatile bool isPlaying;
extern bool protectedMode;
extern volatile int tempo;
extern volatile unsigned long lastClockTime;
extern volatile int clockCount;

View File

@ -22,10 +22,7 @@ enum UIState {
UI_EDIT_STEPS,
UI_EDIT_FLAVOUR,
UI_SETUP_PLAYMODE_EDIT,
UI_RANDOMIZE_TRACK_EDIT,
UI_SCALE_EDIT,
UI_SCALE_NOTE_EDIT,
UI_SCALE_TRANSPOSE
UI_RANDOMIZE_TRACK_EDIT
};
inline void sortArray(int arr[], int size) {

View File

@ -119,85 +119,6 @@ void UIManager::draw(UIState currentState, int menuSelection,
display.setCursor(0, 50);
display.println(F(" (Press to confirm)"));
break;
case UI_SCALE_EDIT:
case UI_SCALE_NOTE_EDIT:
case UI_SCALE_TRANSPOSE:
display.println(F("EDIT SCALE"));
display.drawLine(0, 8, 128, 8, SSD1306_WHITE);
int totalItems = numScaleNotes + 5; // Back + Randomize + Notes + Add + Remove + Transpose
int startIdx = 0;
if (menuSelection >= 4) startIdx = menuSelection - 3;
int y = 12;
for (int i = startIdx; i < totalItems; i++) {
if (y > 54) break;
if (i == menuSelection) {
display.fillRect(0, y, 75, 9, SSD1306_WHITE);
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
} else {
display.setTextColor(SSD1306_WHITE);
}
display.setCursor(2, y + 1);
if (i == 0) {
display.print(F("Back"));
} else if (i == 1) {
display.print(F("Randomize"));
} else if (i <= numScaleNotes + 1) {
int noteIdx = i - 2;
const char* noteNames[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
display.print(noteNames[scaleNotes[noteIdx]]);
if (currentState == UI_SCALE_NOTE_EDIT && i == menuSelection) {
display.print(F(" <"));
}
} else if (i == numScaleNotes + 2) {
display.print(F("Transpose"));
if (currentState == UI_SCALE_TRANSPOSE) {
display.print(F(" < >"));
}
} else if (i == numScaleNotes + 3) {
display.print(F("Add Note"));
} else if (i == numScaleNotes + 4) {
display.print(F("Remove Note"));
}
y += 9;
}
// Piano Roll Preview
int px = 82;
int py = 20;
int wk_w = 5;
int wk_h = 20;
int bk_w = 4;
int bk_h = 12;
// White keys: C, D, E, F, G, A, B
int whiteNotes[] = {0, 2, 4, 5, 7, 9, 11};
for (int k = 0; k < 7; k++) {
bool active = false;
for (int j = 0; j < numScaleNotes; j++) {
if (scaleNotes[j] == whiteNotes[k]) { active = true; break; }
}
if (active) display.fillRect(px + k*6, py, wk_w, wk_h, SSD1306_WHITE);
else display.drawRect(px + k*6, py, wk_w, wk_h, SSD1306_WHITE);
}
// Black keys: C#, D#, F#, G#, A#
int blackNotes[] = {1, 3, 6, 8, 10};
int blackOffsets[] = {3, 9, 21, 27, 33};
for (int k = 0; k < 5; k++) {
bool active = false;
for (int j = 0; j < numScaleNotes; j++) {
if (scaleNotes[j] == blackNotes[k]) { active = true; break; }
}
int bx = px + blackOffsets[k];
display.fillRect(bx - 1, py - 1, bk_w + 2, bk_h + 2, SSD1306_BLACK);
if (active) display.fillRect(bx, py, bk_w, bk_h, SSD1306_WHITE);
else display.drawRect(bx, py, bk_w, bk_h, SSD1306_WHITE);
}
break;
}
display.display();
}
@ -270,7 +191,6 @@ void UIManager::drawMenu(int selection, UIState currentState, int midiChannel, i
else if (id == MENU_ID_MUTE) { display.print(F(": ")); display.print(trackMute[randomizeTrack] ? F("YES") : F("NO")); }
else if (id == MENU_ID_FLAVOUR) { display.print(F(": ")); display.print(flavourName); }
else if (id == MENU_ID_MUTATION) { display.print(F(": ")); display.print(mutationEnabled ? F("ON") : F("OFF")); }
else if (id == MENU_ID_PROTECTED_MODE) { display.print(F(": ")); display.print(protectedMode ? F("ON") : F("OFF")); }
if (id >= MENU_ID_THEME_1 && id <= MENU_ID_THEME_7) {
int themeIdx = id - MENU_ID_THEME_1 + 1;

View File

@ -18,14 +18,10 @@
static Step local_sequence[NUM_TRACKS][NUM_STEPS];
static void handleInput();
static int scaleEditSelection = 0;
static int scaleEditNoteIndex = 0;
static void drawUI();
static void updateLeds();
static void generateTrackData(int track, int themeType, Step (*target)[NUM_STEPS]);
static void generateSequenceData(int themeType, Step (*target)[NUM_STEPS]);
static void savePatch(int bank, int slot);
static void loadPatch(int bank, int slot);
void saveSequence(bool quiet) {
midi.lock();
@ -88,57 +84,6 @@ bool loadSequence() {
return true;
}
static void savePatch(int bank, int slot) {
int patchIndex = bank * 4 + slot;
int addr = 512 + patchIndex * 128; // Start after main save, 128 bytes per patch
midi.lock();
EEPROM.put(addr, numScaleNotes); addr += sizeof(numScaleNotes);
for (int i = 0; i < 12; i++) {
EEPROM.put(addr, scaleNotes[i]); addr += sizeof(int);
}
EEPROM.put(addr, currentStrategyIndices); addr += sizeof(currentStrategyIndices);
EEPROM.put(addr, melodySeeds); addr += sizeof(melodySeeds);
EEPROM.put(addr, (int)numSteps); addr += sizeof(int);
bool mutes[NUM_TRACKS];
for(int i=0; i<NUM_TRACKS; i++) mutes[i] = trackMute[i];
EEPROM.put(addr, mutes); addr += sizeof(mutes);
midi.unlock();
EEPROM.commit();
ui.showMessage("SAVED!");
}
static void loadPatch(int bank, int slot) {
int patchIndex = bank * 4 + slot;
int addr = 512 + patchIndex * 128;
midi.lock();
EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes);
for (int i = 0; i < 12; i++) {
EEPROM.get(addr, scaleNotes[i]); addr += sizeof(int);
}
EEPROM.get(addr, currentStrategyIndices); addr += sizeof(currentStrategyIndices);
EEPROM.get(addr, melodySeeds); addr += sizeof(melodySeeds);
int t;
EEPROM.get(addr, t); addr += sizeof(int);
numSteps = t;
if (numSteps <= 0 || numSteps >= NUM_STEPS) numSteps = NUM_STEPS;
bool mutes[NUM_TRACKS];
EEPROM.get(addr, mutes); addr += sizeof(mutes);
for(int i=0; i<NUM_TRACKS; i++) trackMute[i] = mutes[i];
if (isPlaying) {
generateSequenceData(currentThemeIndex, nextSequence);
sequenceChangeScheduled = true;
} else {
generateSequenceData(currentThemeIndex, local_sequence);
memcpy(sequence, local_sequence, sizeof(local_sequence));
}
midi.unlock();
ui.showMessage("LOADED!");
}
void factoryReset() {
ui.showMessage("RESETTING...");
uint32_t magic = 0;
@ -234,38 +179,6 @@ static void handleInput() {
if (currentStrategyIndices[randomizeTrack] >= numStrategies) currentStrategyIndices[randomizeTrack] = 0;
}
break;
case UI_SCALE_EDIT:
scaleEditSelection += (delta > 0 ? 1 : -1);
if (scaleEditSelection < 0) scaleEditSelection = numScaleNotes + 4;
if (scaleEditSelection > numScaleNotes + 4) scaleEditSelection = 0;
break;
case UI_SCALE_NOTE_EDIT:
scaleNotes[scaleEditNoteIndex] += (delta > 0 ? 1 : -1);
if (scaleNotes[scaleEditNoteIndex] < 0) scaleNotes[scaleEditNoteIndex] = 11;
if (scaleNotes[scaleEditNoteIndex] > 11) scaleNotes[scaleEditNoteIndex] = 0;
midi.lock();
midi.sendNoteOn(60 + scaleNotes[scaleEditNoteIndex], 100, midiChannels[randomizeTrack]);
midi.unlock();
delay(50);
midi.lock();
midi.sendNoteOff(60 + scaleNotes[scaleEditNoteIndex], midiChannels[randomizeTrack]);
midi.unlock();
break;
case UI_SCALE_TRANSPOSE:
if (delta != 0) {
int shift = delta % 12;
if (shift < 0) shift += 12;
for(int i=0; i<numScaleNotes; i++) scaleNotes[i] = (scaleNotes[i] + shift) % 12;
sortArray(scaleNotes, numScaleNotes);
if (isPlaying) {
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
midi.lock();
generateSequenceData(theme, nextSequence);
sequenceChangeScheduled = true;
midi.unlock();
}
}
break;
}
if (currentState == UI_RANDOMIZE_TRACK_EDIT) {
randomizeTrack += (delta > 0 ? 1 : -1);
@ -329,8 +242,16 @@ static void handleInput() {
break;
case MENU_ID_SCALE:
currentState = UI_SCALE_EDIT;
scaleEditSelection = 0;
generateRandomScale();
if (isPlaying) {
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
midi.lock();
// Regenerate all tracks with new scale
generateSequenceData(theme, nextSequence);
sequenceChangeScheduled = true;
midi.unlock();
}
saveSequence(true);
break;
case MENU_ID_TEMPO: currentState = UI_EDIT_TEMPO; break;
@ -349,7 +270,6 @@ static void handleInput() {
case MENU_ID_MUTATION: mutationEnabled = !mutationEnabled; break;
case MENU_ID_CHANNEL: currentState = UI_SETUP_CHANNEL_EDIT; break;
case MENU_ID_PROTECTED_MODE: protectedMode = !protectedMode; break;
case MENU_ID_RESET: factoryReset(); break;
default:
@ -366,16 +286,6 @@ static void handleInput() {
}
break;
}
if (menuItems[menuSelection].id >= MENU_ID_PATCH_ACTIONS_START) {
int offset = menuItems[menuSelection].id - MENU_ID_PATCH_ACTIONS_START;
int bank = offset / 8;
int sub = offset % 8;
bool isSave = sub >= 4;
int slot = sub % 4;
if (isSave) savePatch(bank, slot);
else loadPatch(bank, slot);
break;
}
break;
}
break;
@ -409,71 +319,6 @@ static void handleInput() {
currentState = UI_MENU_MAIN;
saveSequence(true);
break;
case UI_SCALE_EDIT:
if (scaleEditSelection == 0) {
currentState = UI_MENU_MAIN;
saveSequence(true);
} else if (scaleEditSelection == 1) {
generateRandomScale();
if (isPlaying) {
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
midi.lock();
generateSequenceData(theme, nextSequence);
sequenceChangeScheduled = true;
midi.unlock();
}
saveSequence(true);
} else if (scaleEditSelection <= numScaleNotes + 1) {
scaleEditNoteIndex = scaleEditSelection - 2;
currentState = UI_SCALE_NOTE_EDIT;
} else if (scaleEditSelection == numScaleNotes + 2) {
currentState = UI_SCALE_TRANSPOSE;
} else if (scaleEditSelection == numScaleNotes + 3) {
if (numScaleNotes < 12) {
int next = (numScaleNotes > 0) ? (scaleNotes[numScaleNotes-1] + 1) % 12 : 0;
scaleNotes[numScaleNotes] = next;
numScaleNotes++;
scaleEditSelection--; // Move cursor to the new note
if (isPlaying) {
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
midi.lock();
generateSequenceData(theme, nextSequence);
sequenceChangeScheduled = true;
midi.unlock();
}
saveSequence(true);
}
} else {
if (numScaleNotes > 1) {
numScaleNotes--;
scaleEditSelection--;
if (isPlaying) {
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
midi.lock();
generateSequenceData(theme, nextSequence);
sequenceChangeScheduled = true;
midi.unlock();
}
saveSequence(true);
}
}
break;
case UI_SCALE_NOTE_EDIT:
sortArray(scaleNotes, numScaleNotes);
if (isPlaying) {
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
midi.lock();
generateSequenceData(theme, nextSequence);
sequenceChangeScheduled = true;
midi.unlock();
}
currentState = UI_SCALE_EDIT;
saveSequence(true);
break;
case UI_SCALE_TRANSPOSE:
currentState = UI_SCALE_EDIT;
saveSequence(true);
break;
}
}
}
@ -513,11 +358,7 @@ static void drawUI() {
local_randomizeTrack = randomizeTrack;
local_currentState = currentState;
if (local_currentState == UI_SCALE_EDIT || local_currentState == UI_SCALE_NOTE_EDIT || local_currentState == UI_SCALE_TRANSPOSE) {
local_menuSelection = scaleEditSelection;
} else {
local_menuSelection = menuSelection;
}
local_menuSelection = menuSelection;
local_midiChannel = midiChannels[local_randomizeTrack];
local_tempo = tempo;
local_numSteps = numSteps;
@ -583,7 +424,7 @@ static void updateLeds() {
// It's a TRACK section item (Track, Mute, Flavour, Mutation, Themes)
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_FLAVOUR || local_currentState == UI_RANDOMIZE_TRACK_EDIT) {
// These are entered from TRACK section items
ledDisplayMode = MODE_MONO;
}