Refactor and locking cleanup
This commit is contained in:
parent
07c30d20ef
commit
66c20de208
142
PlaybackThread.cpp
Normal file
142
PlaybackThread.cpp
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
#include "MidiDriver.h"
|
||||||
|
#include "TrackerTypes.h"
|
||||||
|
#include "UIThread.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "SharedState.h"
|
||||||
|
|
||||||
|
bool wasPlaying = false;
|
||||||
|
|
||||||
|
static void handlePlayback() {
|
||||||
|
bool nowPlaying = isPlaying;
|
||||||
|
int tracksToPlay = (playMode == MODE_POLY) ? NUM_TRACKS : 1;
|
||||||
|
if (!wasPlaying && !nowPlaying) {
|
||||||
|
midi.sendRealtime(0xFA); // MIDI Start
|
||||||
|
} else if (wasPlaying && !nowPlaying) {
|
||||||
|
midi.sendRealtime(0xFC); // MIDI Stop
|
||||||
|
for (int i=0; i<tracksToPlay; i++) midi.panic(midiChannels[i]);
|
||||||
|
}
|
||||||
|
wasPlaying = nowPlaying;
|
||||||
|
|
||||||
|
if (!nowPlaying) return;
|
||||||
|
|
||||||
|
unsigned long currentMicros = micros();
|
||||||
|
unsigned long clockInterval = 2500000 / tempo; // 60s * 1000000us / (tempo * 24ppqn)
|
||||||
|
|
||||||
|
if (currentMicros - lastClockTime >= clockInterval) {
|
||||||
|
lastClockTime += clockInterval;
|
||||||
|
|
||||||
|
midi.sendRealtime(0xF8); // MIDI Clock
|
||||||
|
|
||||||
|
clockCount++;
|
||||||
|
if (clockCount < 6) return; // 24 ppqn / 4 = 6 pulses per 16th note
|
||||||
|
clockCount = 0;
|
||||||
|
|
||||||
|
midi.lock();
|
||||||
|
Step local_sequence[NUM_TRACKS][NUM_STEPS];
|
||||||
|
memcpy(local_sequence, sequence, sizeof(local_sequence));
|
||||||
|
Step local_nextSequence[NUM_TRACKS][NUM_STEPS];
|
||||||
|
memcpy(local_nextSequence, nextSequence, sizeof(local_nextSequence));
|
||||||
|
midi.unlock();
|
||||||
|
|
||||||
|
for(int t=0; t<tracksToPlay; t++) {
|
||||||
|
int trackChannel = playMode == MODE_POLY ? midiChannels[t] : midiChannels[0];
|
||||||
|
int nextStep = playbackStep + 1;
|
||||||
|
if (nextStep >= NUM_STEPS) nextStep = 0;
|
||||||
|
|
||||||
|
// Determine if we are tying to the next note
|
||||||
|
bool isTied = local_sequence[t][playbackStep].tie && (local_sequence[t][nextStep].note != -1);
|
||||||
|
int prevNote = local_sequence[t][playbackStep].note;
|
||||||
|
|
||||||
|
// Note Off for previous step (if NOT tied)
|
||||||
|
if (!isTied && prevNote != -1) {
|
||||||
|
midi.sendNoteOff(prevNote, trackChannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
playbackStep++;
|
||||||
|
if (playbackStep >= NUM_STEPS) {
|
||||||
|
playbackStep = 0;
|
||||||
|
|
||||||
|
// Theme change
|
||||||
|
if (sequenceChangeScheduled && queuedTheme != -1) {
|
||||||
|
currentThemeIndex = queuedTheme;
|
||||||
|
queuedTheme = -1;
|
||||||
|
// nextSequence is already generated
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutation
|
||||||
|
if (mutationEnabled) {
|
||||||
|
if (!sequenceChangeScheduled) {
|
||||||
|
memcpy(local_nextSequence, local_sequence, sizeof(sequence));
|
||||||
|
}
|
||||||
|
mutateSequence(local_nextSequence);
|
||||||
|
midi.lock();
|
||||||
|
memcpy(nextSequence, local_nextSequence, sizeof(sequence));
|
||||||
|
sequenceChangeScheduled = true;
|
||||||
|
midi.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i=0; i<tracksToPlay; i++) midi.panic(midiChannels[i]);
|
||||||
|
|
||||||
|
if (sequenceChangeScheduled) {
|
||||||
|
memcpy(local_sequence, local_nextSequence, sizeof(local_sequence));
|
||||||
|
midi.lock();
|
||||||
|
memcpy(sequence, local_sequence, sizeof(local_sequence));
|
||||||
|
sequenceChangeScheduled = false;
|
||||||
|
midi.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Song Mode? Advance repeats
|
||||||
|
if (songModeEnabled) {
|
||||||
|
// we just used one repeat
|
||||||
|
if (songRepeatsRemaining <= 1) {
|
||||||
|
// let's start another round
|
||||||
|
songRepeatsRemaining = nextSongRepeats;
|
||||||
|
} else {
|
||||||
|
// next repeat
|
||||||
|
songRepeatsRemaining--;
|
||||||
|
}
|
||||||
|
// Trigger next song segment generation if we are on the last repeat
|
||||||
|
if (songRepeatsRemaining <= 1 && !sequenceChangeScheduled && !songModeNeedsNext) {
|
||||||
|
songModeNeedsNext = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note On for new step
|
||||||
|
for(int t=0; t<tracksToPlay; t++) {
|
||||||
|
int trackChannel = playMode == MODE_POLY ? midiChannels[t] : midiChannels[0];
|
||||||
|
if (!trackMute[t] && local_sequence[t][playbackStep].note != -1) {
|
||||||
|
uint8_t velocity = local_sequence[t][playbackStep].accent ? 127 : 100;
|
||||||
|
midi.sendNoteOn(local_sequence[t][playbackStep].note, velocity, trackChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
int prevStep = (playbackStep == 0) ? NUM_STEPS - 1 : playbackStep - 1;
|
||||||
|
bool wasTied = local_sequence[t][prevStep].tie && (local_sequence[t][playbackStep].note != -1);
|
||||||
|
int prevNote = local_sequence[t][prevStep].note;
|
||||||
|
// Note Off for previous step (if tied - delayed Note Off)
|
||||||
|
if (wasTied && prevNote != -1) {
|
||||||
|
midi.sendNoteOff(prevNote, trackChannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loopPlayback() {
|
||||||
|
unsigned long now = millis();
|
||||||
|
lastLoop1Time = now;
|
||||||
|
if (watchdogActive && (now - lastLoop0Time > 1000)) {
|
||||||
|
Serial.println("Core 0 Freeze detected");
|
||||||
|
rp2040.reboot();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsPanic) {
|
||||||
|
if (playMode == MODE_POLY) {
|
||||||
|
for (int i=0; i<NUM_TRACKS; i++) midi.panic(midiChannels[i]);
|
||||||
|
} else {
|
||||||
|
midi.panic(midiChannels[0]);
|
||||||
|
}
|
||||||
|
needsPanic = false;
|
||||||
|
}
|
||||||
|
handlePlayback();
|
||||||
|
}
|
||||||
6
PlaybackThread.h
Normal file
6
PlaybackThread.h
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#ifndef PLAYBACK_THREAD_H
|
||||||
|
#define PLAYBACK_THREAD_H
|
||||||
|
|
||||||
|
void loopPlayback();
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -2,85 +2,17 @@
|
|||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
#include <EEPROM.h>
|
#include <EEPROM.h>
|
||||||
#include "TrackerTypes.h"
|
#include "TrackerTypes.h"
|
||||||
#include "MelodyStrategy.h"
|
|
||||||
#include "LuckyStrategy.h"
|
|
||||||
#include "ArpStrategy.h"
|
|
||||||
#include "EuclideanStrategy.h"
|
|
||||||
#include "MarkovStrategy.h"
|
|
||||||
#include "CellularAutomataStrategy.h"
|
|
||||||
#include "LSystemStrategy.h"
|
|
||||||
#include "MidiDriver.h"
|
#include "MidiDriver.h"
|
||||||
#include "UIManager.h"
|
#include "UIManager.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "UIThread.h"
|
||||||
Step sequence[NUM_TRACKS][NUM_STEPS];
|
#include "PlaybackThread.h"
|
||||||
Step nextSequence[NUM_TRACKS][NUM_STEPS];
|
#include "SharedState.h"
|
||||||
volatile bool sequenceChangeScheduled = false;
|
|
||||||
volatile bool needsPanic = false;
|
|
||||||
|
|
||||||
UIState currentState = UI_MENU_RANDOMIZE; // Let's start in the Play menu
|
|
||||||
|
|
||||||
const char* mainMenu[] = { "Randomize", "Setup" };
|
|
||||||
const int mainMenuCount = sizeof(mainMenu) / sizeof(char*);
|
|
||||||
|
|
||||||
const char* randomizeMenuMono[] = { "Setup", "Melody", "Flavour", "Scale", "Tempo", "Mutation", "Song Mode", "Theme 1", "Theme 2", "Theme 3", "Theme 4", "Theme 5", "Theme 6", "Theme 7" };
|
|
||||||
const int randomizeMenuMonoCount = sizeof(randomizeMenuMono) / sizeof(char*);
|
|
||||||
const int THEME_1_INDEX_MONO = 7;
|
|
||||||
|
|
||||||
const char* randomizeMenuPoly[] = { "Setup", "Track", "Mute", "Melody", "Flavour", "Scale", "Tempo", "Mutation", "Song Mode", "Theme 1", "Theme 2", "Theme 3", "Theme 4", "Theme 5", "Theme 6", "Theme 7" };
|
|
||||||
const int randomizeMenuPolyCount = sizeof(randomizeMenuPoly) / sizeof(char*);
|
|
||||||
const int THEME_1_INDEX_POLY = 9;
|
|
||||||
|
|
||||||
const char* setupMenu[] = { "Back", "Play Mode", "Channel", "Factory Reset" };
|
|
||||||
const int setupMenuCount = sizeof(setupMenu) / sizeof(char*);
|
|
||||||
|
|
||||||
int menuSelection = 0;
|
|
||||||
volatile bool trackMute[NUM_TRACKS];
|
|
||||||
int randomizeTrack = 0;
|
|
||||||
volatile int playbackStep = 0;
|
|
||||||
volatile int midiChannels[NUM_TRACKS];
|
|
||||||
int scaleNotes[12];
|
|
||||||
int numScaleNotes = 0;
|
|
||||||
int melodySeeds[NUM_TRACKS];
|
|
||||||
volatile int queuedTheme = -1;
|
|
||||||
volatile int currentThemeIndex = 1;
|
|
||||||
const uint32_t EEPROM_MAGIC = 0x4242424B;
|
|
||||||
|
|
||||||
MelodyStrategy* strategies[] = {
|
|
||||||
new LuckyStrategy(), new ArpStrategy(), new EuclideanStrategy(),
|
|
||||||
new MarkovStrategy(), new CellularAutomataStrategy(), new LSystemStrategy()};
|
|
||||||
const int numStrategies = 6;
|
|
||||||
int currentStrategyIndices[NUM_TRACKS];
|
|
||||||
|
|
||||||
volatile PlayMode playMode = MODE_MONO;
|
|
||||||
volatile bool mutationEnabled = false;
|
|
||||||
volatile bool songModeEnabled = false;
|
|
||||||
volatile int songRepeatsRemaining = 0;
|
|
||||||
volatile int nextSongRepeats = 0;
|
|
||||||
volatile bool songModeNeedsNext = false;
|
|
||||||
volatile bool isPlaying = false;
|
|
||||||
volatile int tempo = 120; // BPM
|
|
||||||
volatile unsigned long lastClockTime = 0;
|
|
||||||
volatile int clockCount = 0;
|
|
||||||
|
|
||||||
|
|
||||||
// Watchdog
|
|
||||||
volatile unsigned long lastLoop0Time = 0;
|
|
||||||
volatile unsigned long lastLoop1Time = 0;
|
|
||||||
volatile bool watchdogActive = false;
|
|
||||||
|
|
||||||
// Encoder State
|
// Encoder State
|
||||||
volatile int encoderDelta = 0;
|
|
||||||
static uint8_t prevNextCode = 0;
|
static uint8_t prevNextCode = 0;
|
||||||
static uint16_t store = 0;
|
static uint16_t store = 0;
|
||||||
|
|
||||||
// Button State
|
|
||||||
bool lastButtonState = HIGH;
|
|
||||||
unsigned long lastDebounceTime = 0;
|
|
||||||
bool buttonActive = false;
|
|
||||||
bool buttonConsumed = false;
|
|
||||||
unsigned long buttonPressTime = 0;
|
|
||||||
|
|
||||||
// --- ENCODER INTERRUPT ---
|
// --- ENCODER INTERRUPT ---
|
||||||
// Robust Rotary Encoder reading
|
// Robust Rotary Encoder reading
|
||||||
void readEncoder() {
|
void readEncoder() {
|
||||||
@ -100,72 +32,6 @@ void readEncoder() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveSequence(bool quiet = false) {
|
|
||||||
int addr = 0;
|
|
||||||
EEPROM.put(addr, EEPROM_MAGIC); addr += sizeof(EEPROM_MAGIC);
|
|
||||||
int channels[NUM_TRACKS];
|
|
||||||
for(int i=0; i<NUM_TRACKS; i++) channels[i] = midiChannels[i];
|
|
||||||
EEPROM.put(addr, channels); addr += sizeof(channels);
|
|
||||||
EEPROM.put(addr, melodySeeds); addr += sizeof(melodySeeds);
|
|
||||||
EEPROM.put(addr, currentStrategyIndices); addr += sizeof(currentStrategyIndices);
|
|
||||||
bool mutes[NUM_TRACKS];
|
|
||||||
for(int i=0; i<NUM_TRACKS; i++) mutes[i] = trackMute[i];
|
|
||||||
EEPROM.put(addr, mutes); addr += sizeof(mutes);
|
|
||||||
EEPROM.put(addr, (int)tempo); addr += sizeof(int);
|
|
||||||
EEPROM.put(addr, (int)playMode); addr += sizeof(int);
|
|
||||||
|
|
||||||
EEPROM.put(addr, numScaleNotes); addr += sizeof(numScaleNotes);
|
|
||||||
for (int i=0; i<12; i++) {
|
|
||||||
EEPROM.put(addr, scaleNotes[i]); addr += sizeof(int);
|
|
||||||
}
|
|
||||||
|
|
||||||
midi.lock();
|
|
||||||
EEPROM.put(addr, sequence); addr += sizeof(sequence);
|
|
||||||
midi.unlock();
|
|
||||||
EEPROM.commit();
|
|
||||||
if (!quiet) ui.showMessage("SAVED!");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool loadSequence() {
|
|
||||||
int addr = 0;
|
|
||||||
uint32_t magic;
|
|
||||||
EEPROM.get(addr, magic); addr += sizeof(magic);
|
|
||||||
if (magic != EEPROM_MAGIC) return false;
|
|
||||||
|
|
||||||
int channels[NUM_TRACKS];
|
|
||||||
EEPROM.get(addr, channels); addr += sizeof(channels);
|
|
||||||
for(int i=0; i<NUM_TRACKS; i++) midiChannels[i] = channels[i];
|
|
||||||
EEPROM.get(addr, melodySeeds); addr += sizeof(melodySeeds);
|
|
||||||
EEPROM.get(addr, currentStrategyIndices); addr += sizeof(currentStrategyIndices);
|
|
||||||
bool mutes[NUM_TRACKS];
|
|
||||||
EEPROM.get(addr, mutes); addr += sizeof(mutes);
|
|
||||||
for(int i=0; i<NUM_TRACKS; i++) trackMute[i] = mutes[i];
|
|
||||||
int t;
|
|
||||||
EEPROM.get(addr, t); addr += sizeof(int);
|
|
||||||
tempo = t;
|
|
||||||
EEPROM.get(addr, t); addr += sizeof(int);
|
|
||||||
playMode = (PlayMode)t;
|
|
||||||
|
|
||||||
EEPROM.get(addr, numScaleNotes); addr += sizeof(numScaleNotes);
|
|
||||||
for (int i=0; i<12; i++) {
|
|
||||||
EEPROM.get(addr, scaleNotes[i]); addr += sizeof(int);
|
|
||||||
}
|
|
||||||
|
|
||||||
midi.lock();
|
|
||||||
EEPROM.get(addr, sequence); addr += sizeof(sequence);
|
|
||||||
midi.unlock();
|
|
||||||
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);
|
||||||
|
|
||||||
@ -208,438 +74,10 @@ void setup() {
|
|||||||
watchdogActive = true;
|
watchdogActive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void generateTrackData(int track, int themeType, Step (*target)[NUM_STEPS]) {
|
|
||||||
randomSeed(melodySeeds[track] + themeType * 12345);
|
|
||||||
strategies[currentStrategyIndices[track]]->generate(target, track, NUM_STEPS, scaleNotes, numScaleNotes, melodySeeds[track] + themeType * 12345);
|
|
||||||
}
|
|
||||||
|
|
||||||
void generateRandomScale() {
|
|
||||||
// All tracks share the same scale for now
|
|
||||||
strategies[currentStrategyIndices[0]]->generateScale(scaleNotes, numScaleNotes);
|
|
||||||
}
|
|
||||||
|
|
||||||
void generateSequenceData(int themeType, Step (*target)[NUM_STEPS]) {
|
|
||||||
for(int i=0; i<NUM_TRACKS; i++) {
|
|
||||||
generateTrackData(i, themeType, target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void generateTheme(int themeType) {
|
|
||||||
currentThemeIndex = themeType;
|
|
||||||
needsPanic = true;
|
|
||||||
midi.lock();
|
|
||||||
generateSequenceData(themeType, sequence);
|
|
||||||
midi.unlock();
|
|
||||||
|
|
||||||
clockCount = 0;
|
|
||||||
lastClockTime = micros();
|
|
||||||
playbackStep = 0;
|
|
||||||
midi.sendRealtime(0xFA); // MIDI Start
|
|
||||||
isPlaying = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void mutateSequence(Step (*target)[NUM_STEPS]) {
|
|
||||||
if (playMode == MODE_POLY) {
|
|
||||||
for(int i=0; i<NUM_TRACKS; i++) strategies[currentStrategyIndices[i]]->mutate(target, i, NUM_STEPS, scaleNotes, numScaleNotes);
|
|
||||||
} else {
|
|
||||||
strategies[currentStrategyIndices[0]]->mutate(target, 0, NUM_STEPS, scaleNotes, numScaleNotes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleInput() {
|
|
||||||
// Handle Encoder Rotation
|
|
||||||
int delta = 0;
|
|
||||||
noInterrupts();
|
|
||||||
delta = encoderDelta;
|
|
||||||
encoderDelta = 0;
|
|
||||||
interrupts();
|
|
||||||
|
|
||||||
if (delta != 0) {
|
|
||||||
switch(currentState) {
|
|
||||||
case UI_MENU_MAIN:
|
|
||||||
menuSelection += (delta > 0 ? 1 : -1);
|
|
||||||
if (menuSelection < 0) menuSelection = mainMenuCount - 1;
|
|
||||||
if (menuSelection >= mainMenuCount) menuSelection = 0;
|
|
||||||
break;
|
|
||||||
case UI_MENU_RANDOMIZE:
|
|
||||||
{
|
|
||||||
menuSelection += (delta > 0 ? 1 : -1);
|
|
||||||
int count = (playMode == MODE_POLY) ? randomizeMenuPolyCount : randomizeMenuMonoCount;
|
|
||||||
if (menuSelection < 0) menuSelection = count - 1;
|
|
||||||
if (menuSelection >= count) menuSelection = 0;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case UI_MENU_SETUP:
|
|
||||||
menuSelection += (delta > 0 ? 1 : -1);
|
|
||||||
if (menuSelection < 0) menuSelection = setupMenuCount - 1;
|
|
||||||
if (menuSelection >= setupMenuCount) menuSelection = 0;
|
|
||||||
break;
|
|
||||||
case UI_SETUP_CHANNEL_EDIT:
|
|
||||||
{
|
|
||||||
int trackToEdit = (playMode == MODE_POLY) ? randomizeTrack : 0;
|
|
||||||
midiChannels[trackToEdit] += (delta > 0 ? 1 : -1);
|
|
||||||
if (midiChannels[trackToEdit] < 1) midiChannels[trackToEdit] = 16;
|
|
||||||
if (midiChannels[trackToEdit] > 16) midiChannels[trackToEdit] = 1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case UI_EDIT_TEMPO:
|
|
||||||
tempo += delta;
|
|
||||||
if (tempo < 40) tempo = 40;
|
|
||||||
if (tempo > 240) tempo = 240;
|
|
||||||
break;
|
|
||||||
case UI_EDIT_FLAVOUR:
|
|
||||||
{
|
|
||||||
int trackToEdit = playMode == MODE_POLY ? randomizeTrack : 0;
|
|
||||||
currentStrategyIndices[trackToEdit] += (delta > 0 ? 1 : -1);
|
|
||||||
if (currentStrategyIndices[trackToEdit] < 0) currentStrategyIndices[trackToEdit] = numStrategies - 1;
|
|
||||||
if (currentStrategyIndices[trackToEdit] >= numStrategies) currentStrategyIndices[trackToEdit] = 0;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case UI_SETUP_PLAYMODE_EDIT:
|
|
||||||
playMode = (playMode == MODE_MONO) ? MODE_POLY : MODE_MONO;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (currentState == UI_RANDOMIZE_TRACK_EDIT) {
|
|
||||||
randomizeTrack += (delta > 0 ? 1 : -1);
|
|
||||||
if (randomizeTrack < 0) randomizeTrack = NUM_TRACKS - 1;
|
|
||||||
if (randomizeTrack >= NUM_TRACKS) randomizeTrack = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle Button
|
|
||||||
int reading = digitalRead(ENC_SW);
|
|
||||||
|
|
||||||
if (reading != lastButtonState) {
|
|
||||||
lastDebounceTime = millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((millis() - lastDebounceTime) > 50) {
|
|
||||||
if (reading == LOW && !buttonActive) {
|
|
||||||
// Button Pressed
|
|
||||||
buttonActive = true;
|
|
||||||
buttonPressTime = millis();
|
|
||||||
buttonConsumed = false;
|
|
||||||
Serial.println(F("Button Down"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reading == HIGH && buttonActive) {
|
|
||||||
// Button Released
|
|
||||||
buttonActive = false;
|
|
||||||
if (!buttonConsumed) { // Short press action
|
|
||||||
switch(currentState) {
|
|
||||||
case UI_MENU_MAIN:
|
|
||||||
if (menuSelection == 0) { currentState = UI_MENU_RANDOMIZE; menuSelection = 0; break; }
|
|
||||||
if (menuSelection == 1) { currentState = UI_MENU_SETUP; menuSelection = 0; break; }
|
|
||||||
break;
|
|
||||||
case UI_MENU_RANDOMIZE:
|
|
||||||
{
|
|
||||||
int track_offset = (playMode == MODE_POLY) ? 2 : 0;
|
|
||||||
int theme_1_index = (playMode == MODE_POLY) ? THEME_1_INDEX_POLY : THEME_1_INDEX_MONO;
|
|
||||||
if (menuSelection == 0) { currentState = UI_MENU_SETUP; menuSelection = 0; break; }
|
|
||||||
if (playMode == MODE_POLY) {
|
|
||||||
if (menuSelection == 1) { currentState = UI_RANDOMIZE_TRACK_EDIT; break; }
|
|
||||||
if (menuSelection == 2) { trackMute[randomizeTrack] = !trackMute[randomizeTrack]; break; }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (menuSelection == 1 + track_offset) { // Melody
|
|
||||||
int track = playMode == MODE_POLY ? randomizeTrack : 0;
|
|
||||||
midi.lock();
|
|
||||||
melodySeeds[track] = random(10000);
|
|
||||||
if (isPlaying) {
|
|
||||||
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
|
|
||||||
if (!sequenceChangeScheduled) {
|
|
||||||
memcpy(nextSequence, sequence, sizeof(sequence));
|
|
||||||
}
|
|
||||||
generateTrackData(track, theme, nextSequence);
|
|
||||||
sequenceChangeScheduled = true;
|
|
||||||
}
|
|
||||||
midi.unlock();
|
|
||||||
saveSequence(true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (menuSelection == 2 + track_offset) { currentState = UI_EDIT_FLAVOUR; break; } // Flavour
|
|
||||||
if (menuSelection == 3 + track_offset) { // Scale
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
if (menuSelection == 4 + track_offset) { currentState = UI_EDIT_TEMPO; break; }
|
|
||||||
if (menuSelection == 5 + track_offset) { mutationEnabled = !mutationEnabled; break; }
|
|
||||||
if (menuSelection == 6 + track_offset) {
|
|
||||||
songModeEnabled = !songModeEnabled;
|
|
||||||
if (songModeEnabled) {
|
|
||||||
songModeNeedsNext = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (menuSelection >= theme_1_index) { // Themes
|
|
||||||
const int selectedTheme = menuSelection - theme_1_index + 1;
|
|
||||||
if (isPlaying) {
|
|
||||||
queuedTheme = selectedTheme;
|
|
||||||
midi.lock();
|
|
||||||
generateSequenceData(queuedTheme, nextSequence);
|
|
||||||
sequenceChangeScheduled = true;
|
|
||||||
midi.unlock();
|
|
||||||
} else {
|
|
||||||
generateTheme(selectedTheme);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case UI_MENU_SETUP:
|
|
||||||
if (menuSelection == 0) { currentState = UI_MENU_RANDOMIZE; menuSelection = 0; break; }
|
|
||||||
if (menuSelection == 1) { currentState = UI_SETUP_PLAYMODE_EDIT; break; }
|
|
||||||
if (menuSelection == 2) { currentState = UI_SETUP_CHANNEL_EDIT; break; }
|
|
||||||
if (menuSelection == 3) { factoryReset(); break; }
|
|
||||||
break;
|
|
||||||
case UI_SETUP_CHANNEL_EDIT:
|
|
||||||
currentState = UI_MENU_SETUP;
|
|
||||||
saveSequence(true);
|
|
||||||
break;
|
|
||||||
case UI_EDIT_TEMPO:
|
|
||||||
currentState = UI_MENU_RANDOMIZE;
|
|
||||||
saveSequence(true);
|
|
||||||
break;
|
|
||||||
case UI_EDIT_FLAVOUR:
|
|
||||||
currentState = UI_MENU_RANDOMIZE;
|
|
||||||
if (isPlaying) {
|
|
||||||
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
|
|
||||||
int track = playMode == MODE_POLY ? randomizeTrack : 0;
|
|
||||||
midi.lock();
|
|
||||||
if (!sequenceChangeScheduled) {
|
|
||||||
memcpy(nextSequence, sequence, sizeof(sequence));
|
|
||||||
}
|
|
||||||
generateTrackData(track, theme, nextSequence);
|
|
||||||
sequenceChangeScheduled = true;
|
|
||||||
midi.unlock();
|
|
||||||
}
|
|
||||||
saveSequence(true);
|
|
||||||
break;
|
|
||||||
case UI_SETUP_PLAYMODE_EDIT:
|
|
||||||
currentState = UI_MENU_SETUP;
|
|
||||||
saveSequence(true);
|
|
||||||
break;
|
|
||||||
case UI_RANDOMIZE_TRACK_EDIT:
|
|
||||||
currentState = UI_MENU_RANDOMIZE;
|
|
||||||
saveSequence(true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for Long Press (Start/Stop Playback)
|
|
||||||
if (buttonActive && !buttonConsumed && (millis() - buttonPressTime > 600)) {
|
|
||||||
isPlaying = !isPlaying;
|
|
||||||
buttonConsumed = true; // Prevent short press action
|
|
||||||
Serial.print(F("Playback: ")); Serial.println(isPlaying ? F("ON") : F("OFF"));
|
|
||||||
if (isPlaying) {
|
|
||||||
playbackStep = 0;
|
|
||||||
clockCount = 0;
|
|
||||||
lastClockTime = micros();
|
|
||||||
midi.sendRealtime(0xFA); // MIDI Start
|
|
||||||
} else {
|
|
||||||
// Send All Notes Off on stop (CC 123)
|
|
||||||
needsPanic = true;
|
|
||||||
midi.sendRealtime(0xFC); // MIDI Stop
|
|
||||||
queuedTheme = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastButtonState = reading;
|
|
||||||
}
|
|
||||||
|
|
||||||
void handlePlayback() {
|
|
||||||
if (!isPlaying) return;
|
|
||||||
|
|
||||||
unsigned long currentMicros = micros();
|
|
||||||
unsigned long clockInterval = 2500000 / tempo; // 60s * 1000000us / (tempo * 24ppqn)
|
|
||||||
int tracksToPlay = (playMode == MODE_POLY) ? NUM_TRACKS : 1;
|
|
||||||
|
|
||||||
if (currentMicros - lastClockTime >= clockInterval) {
|
|
||||||
lastClockTime += clockInterval;
|
|
||||||
|
|
||||||
midi.sendRealtime(0xF8); // MIDI Clock
|
|
||||||
|
|
||||||
clockCount++;
|
|
||||||
if (clockCount < 6) return; // 24 ppqn / 4 = 6 pulses per 16th note
|
|
||||||
clockCount = 0;
|
|
||||||
|
|
||||||
midi.lock();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for(int t=0; t<tracksToPlay; t++) {
|
|
||||||
int trackChannel = playMode == MODE_POLY ? midiChannels[t] : midiChannels[0];
|
|
||||||
int nextStep = playbackStep + 1;
|
|
||||||
if (nextStep >= NUM_STEPS) nextStep = 0;
|
|
||||||
|
|
||||||
// Determine if we are tying to the next note
|
|
||||||
bool isTied = sequence[t][playbackStep].tie && (sequence[t][nextStep].note != -1);
|
|
||||||
int prevNote = sequence[t][playbackStep].note;
|
|
||||||
|
|
||||||
// Note Off for previous step (if NOT tied)
|
|
||||||
if (!isTied && prevNote != -1) {
|
|
||||||
midi.sendNoteOff(prevNote, trackChannel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
playbackStep++;
|
|
||||||
if (playbackStep >= NUM_STEPS) {
|
|
||||||
playbackStep = 0;
|
|
||||||
|
|
||||||
// Theme change
|
|
||||||
if (sequenceChangeScheduled && queuedTheme != -1) {
|
|
||||||
currentThemeIndex = queuedTheme;
|
|
||||||
queuedTheme = -1;
|
|
||||||
// nextSequence is already generated
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mutation
|
|
||||||
if (mutationEnabled) {
|
|
||||||
if (!sequenceChangeScheduled) {
|
|
||||||
memcpy(nextSequence, sequence, sizeof(sequence));
|
|
||||||
}
|
|
||||||
mutateSequence(nextSequence);
|
|
||||||
sequenceChangeScheduled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i=0; i<tracksToPlay; i++) midi.panic(midiChannels[i]);
|
|
||||||
|
|
||||||
if (sequenceChangeScheduled) {
|
|
||||||
memcpy(sequence, nextSequence, sizeof(sequence));
|
|
||||||
sequenceChangeScheduled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Song Mode? Advance repeats
|
|
||||||
if (songModeEnabled) {
|
|
||||||
// we just used one repeat
|
|
||||||
if (songRepeatsRemaining <= 1) {
|
|
||||||
// let's start another round
|
|
||||||
songRepeatsRemaining = nextSongRepeats;
|
|
||||||
} else {
|
|
||||||
// next repeat
|
|
||||||
songRepeatsRemaining--;
|
|
||||||
}
|
|
||||||
// Trigger next song segment generation if we are on the last repeat
|
|
||||||
if (songRepeatsRemaining <= 1 && !sequenceChangeScheduled && !songModeNeedsNext) {
|
|
||||||
songModeNeedsNext = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note On for new step
|
|
||||||
for(int t=0; t<tracksToPlay; t++) {
|
|
||||||
int trackChannel = playMode == MODE_POLY ? midiChannels[t] : midiChannels[0];
|
|
||||||
if (!trackMute[t] && sequence[t][playbackStep].note != -1) {
|
|
||||||
uint8_t velocity = sequence[t][playbackStep].accent ? 127 : 100;
|
|
||||||
midi.sendNoteOn(sequence[t][playbackStep].note, velocity, trackChannel);
|
|
||||||
}
|
|
||||||
|
|
||||||
int prevStep = (playbackStep == 0) ? NUM_STEPS - 1 : playbackStep - 1;
|
|
||||||
bool wasTied = sequence[t][prevStep].tie && (sequence[t][playbackStep].note != -1);
|
|
||||||
int prevNote = sequence[t][prevStep].note;
|
|
||||||
// Note Off for previous step (if tied - delayed Note Off)
|
|
||||||
if (wasTied && prevNote != -1) {
|
|
||||||
midi.sendNoteOff(prevNote, trackChannel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
midi.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void drawUI() {
|
|
||||||
midi.lock();
|
|
||||||
const char **randMenu;
|
|
||||||
int randMenuCount;
|
|
||||||
int themeIndex;
|
|
||||||
if (playMode == MODE_POLY) {
|
|
||||||
randMenu = randomizeMenuPoly;
|
|
||||||
randMenuCount = randomizeMenuPolyCount;
|
|
||||||
themeIndex = THEME_1_INDEX_POLY;
|
|
||||||
} else {
|
|
||||||
randMenu = randomizeMenuMono;
|
|
||||||
randMenuCount = randomizeMenuMonoCount;
|
|
||||||
themeIndex = THEME_1_INDEX_MONO;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ui_track = 0;
|
|
||||||
if (playMode == MODE_POLY) {
|
|
||||||
ui_track = randomizeTrack;
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.draw(currentState, menuSelection,
|
|
||||||
midiChannels[ui_track], tempo, strategies[currentStrategyIndices[ui_track]],
|
|
||||||
queuedTheme, currentThemeIndex, numScaleNotes, scaleNotes, melodySeeds[ui_track],
|
|
||||||
mutationEnabled, songModeEnabled, sequence, playbackStep, isPlaying,
|
|
||||||
mainMenu, mainMenuCount, randMenu, randMenuCount, setupMenu, setupMenuCount,
|
|
||||||
themeIndex, playMode, randomizeTrack, (const bool*)trackMute);
|
|
||||||
midi.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateLeds() {
|
|
||||||
midi.lock();
|
|
||||||
ui.updateLeds(sequence, playbackStep, isPlaying, currentState, songModeEnabled, songRepeatsRemaining, sequenceChangeScheduled, playMode, numScaleNotes, scaleNotes, (const bool*)trackMute);
|
|
||||||
midi.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop1() {
|
void loop1() {
|
||||||
unsigned long now = millis();
|
loopPlayback();
|
||||||
lastLoop1Time = now;
|
|
||||||
if (watchdogActive && (now - lastLoop0Time > 1000)) {
|
|
||||||
Serial.println("Core 0 Freeze detected");
|
|
||||||
rp2040.reboot();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needsPanic) {
|
|
||||||
midi.lock();
|
|
||||||
if (playMode == MODE_POLY) {
|
|
||||||
for (int i=0; i<NUM_TRACKS; i++) midi.panic(midiChannels[i]);
|
|
||||||
} else {
|
|
||||||
midi.panic(midiChannels[0]);
|
|
||||||
}
|
|
||||||
midi.unlock();
|
|
||||||
needsPanic = false;
|
|
||||||
}
|
|
||||||
handlePlayback();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
unsigned long now = millis();
|
loopUI();
|
||||||
lastLoop0Time = now;
|
|
||||||
if (watchdogActive && (now - lastLoop1Time > 1000)) {
|
|
||||||
Serial.println("Core 1 Freeze detected");
|
|
||||||
rp2040.reboot();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle Song Mode Generation in UI Thread
|
|
||||||
if (songModeNeedsNext) {
|
|
||||||
int nextTheme = random(1, 8); // Themes 1-7
|
|
||||||
int repeats = random(1, 9); // 1-8 repeats
|
|
||||||
|
|
||||||
midi.lock();
|
|
||||||
generateSequenceData(nextTheme, nextSequence);
|
|
||||||
queuedTheme = nextTheme;
|
|
||||||
nextSongRepeats = repeats;
|
|
||||||
sequenceChangeScheduled = true;
|
|
||||||
midi.unlock();
|
|
||||||
|
|
||||||
songModeNeedsNext = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleInput();
|
|
||||||
drawUI();
|
|
||||||
updateLeds();
|
|
||||||
delay(10); // Small delay to prevent screen tearing/excessive refresh
|
|
||||||
}
|
}
|
||||||
74
SharedState.cpp
Normal file
74
SharedState.cpp
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#include "SharedState.h"
|
||||||
|
#include "LuckyStrategy.h"
|
||||||
|
#include "ArpStrategy.h"
|
||||||
|
#include "EuclideanStrategy.h"
|
||||||
|
#include "MarkovStrategy.h"
|
||||||
|
#include "CellularAutomataStrategy.h"
|
||||||
|
#include "LSystemStrategy.h"
|
||||||
|
|
||||||
|
// Global state variables
|
||||||
|
Step sequence[NUM_TRACKS][NUM_STEPS];
|
||||||
|
Step nextSequence[NUM_TRACKS][NUM_STEPS];
|
||||||
|
volatile bool sequenceChangeScheduled = false;
|
||||||
|
volatile bool needsPanic = false;
|
||||||
|
|
||||||
|
UIState currentState = UI_MENU_RANDOMIZE;
|
||||||
|
|
||||||
|
// Menus
|
||||||
|
const char* mainMenu[] = { "Randomize", "Setup" };
|
||||||
|
extern const int mainMenuCount = sizeof(mainMenu) / sizeof(char*);
|
||||||
|
|
||||||
|
const char* randomizeMenuMono[] = { "Setup", "Melody", "Flavour", "Scale", "Tempo", "Mutation", "Song Mode", "Theme 1", "Theme 2", "Theme 3", "Theme 4", "Theme 5", "Theme 6", "Theme 7" };
|
||||||
|
extern const int randomizeMenuMonoCount = sizeof(randomizeMenuMono) / sizeof(char*);
|
||||||
|
extern const int THEME_1_INDEX_MONO = 7;
|
||||||
|
|
||||||
|
const char* randomizeMenuPoly[] = { "Setup", "Track", "Mute", "Melody", "Flavour", "Scale", "Tempo", "Mutation", "Song Mode", "Theme 1", "Theme 2", "Theme 3", "Theme 4", "Theme 5", "Theme 6", "Theme 7" };
|
||||||
|
extern const int randomizeMenuPolyCount = sizeof(randomizeMenuPoly) / sizeof(char*);
|
||||||
|
extern const int THEME_1_INDEX_POLY = 9;
|
||||||
|
|
||||||
|
const char* setupMenu[] = { "Back", "Play Mode", "Channel", "Factory Reset" };
|
||||||
|
extern const int setupMenuCount = sizeof(setupMenu) / sizeof(char*);
|
||||||
|
|
||||||
|
int menuSelection = 0;
|
||||||
|
volatile bool trackMute[NUM_TRACKS];
|
||||||
|
int randomizeTrack = 0;
|
||||||
|
volatile int playbackStep = 0;
|
||||||
|
volatile int midiChannels[NUM_TRACKS];
|
||||||
|
int scaleNotes[12];
|
||||||
|
int numScaleNotes = 0;
|
||||||
|
int melodySeeds[NUM_TRACKS];
|
||||||
|
volatile int queuedTheme = -1;
|
||||||
|
volatile int currentThemeIndex = 1;
|
||||||
|
extern const uint32_t EEPROM_MAGIC = 0x4242424B;
|
||||||
|
|
||||||
|
MelodyStrategy* strategies[] = {
|
||||||
|
new LuckyStrategy(), new ArpStrategy(), new EuclideanStrategy(),
|
||||||
|
new MarkovStrategy(), new CellularAutomataStrategy(), new LSystemStrategy()};
|
||||||
|
extern const int numStrategies = 6;
|
||||||
|
int currentStrategyIndices[NUM_TRACKS];
|
||||||
|
|
||||||
|
volatile PlayMode playMode = MODE_MONO;
|
||||||
|
volatile bool mutationEnabled = false;
|
||||||
|
volatile bool songModeEnabled = false;
|
||||||
|
volatile int songRepeatsRemaining = 0;
|
||||||
|
volatile int nextSongRepeats = 0;
|
||||||
|
volatile bool songModeNeedsNext = false;
|
||||||
|
volatile bool isPlaying = false;
|
||||||
|
volatile int tempo = 120; // BPM
|
||||||
|
volatile unsigned long lastClockTime = 0;
|
||||||
|
volatile int clockCount = 0;
|
||||||
|
|
||||||
|
// Watchdog
|
||||||
|
volatile unsigned long lastLoop0Time = 0;
|
||||||
|
volatile unsigned long lastLoop1Time = 0;
|
||||||
|
volatile bool watchdogActive = false;
|
||||||
|
|
||||||
|
// Encoder State
|
||||||
|
volatile int encoderDelta = 0;
|
||||||
|
|
||||||
|
// Button State
|
||||||
|
bool lastButtonState = HIGH;
|
||||||
|
unsigned long lastDebounceTime = 0;
|
||||||
|
bool buttonActive = false;
|
||||||
|
bool buttonConsumed = false;
|
||||||
|
unsigned long buttonPressTime = 0;
|
||||||
68
SharedState.h
Normal file
68
SharedState.h
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#ifndef SHARED_STATE_H
|
||||||
|
#define SHARED_STATE_H
|
||||||
|
|
||||||
|
#include "TrackerTypes.h"
|
||||||
|
#include "MelodyStrategy.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
// Global state variables defined in main .ino
|
||||||
|
extern Step sequence[NUM_TRACKS][NUM_STEPS];
|
||||||
|
extern Step nextSequence[NUM_TRACKS][NUM_STEPS];
|
||||||
|
extern volatile bool sequenceChangeScheduled;
|
||||||
|
extern volatile bool needsPanic;
|
||||||
|
|
||||||
|
extern UIState currentState;
|
||||||
|
|
||||||
|
// Menus
|
||||||
|
extern const char* mainMenu[];
|
||||||
|
extern const int mainMenuCount;
|
||||||
|
extern const char* randomizeMenuMono[];
|
||||||
|
extern const int randomizeMenuMonoCount;
|
||||||
|
extern const int THEME_1_INDEX_MONO;
|
||||||
|
extern const char* randomizeMenuPoly[];
|
||||||
|
extern const int randomizeMenuPolyCount;
|
||||||
|
extern const int THEME_1_INDEX_POLY;
|
||||||
|
extern const char* setupMenu[];
|
||||||
|
extern const int setupMenuCount;
|
||||||
|
|
||||||
|
extern int menuSelection;
|
||||||
|
extern volatile bool trackMute[NUM_TRACKS];
|
||||||
|
extern int randomizeTrack;
|
||||||
|
extern volatile int playbackStep;
|
||||||
|
extern volatile int midiChannels[NUM_TRACKS];
|
||||||
|
extern int scaleNotes[12];
|
||||||
|
extern int numScaleNotes;
|
||||||
|
extern int melodySeeds[NUM_TRACKS];
|
||||||
|
extern volatile int queuedTheme;
|
||||||
|
extern volatile int currentThemeIndex;
|
||||||
|
extern const uint32_t EEPROM_MAGIC;
|
||||||
|
|
||||||
|
extern MelodyStrategy* strategies[];
|
||||||
|
extern const int numStrategies;
|
||||||
|
extern int currentStrategyIndices[NUM_TRACKS];
|
||||||
|
|
||||||
|
extern volatile PlayMode playMode;
|
||||||
|
extern volatile bool mutationEnabled;
|
||||||
|
extern volatile bool songModeEnabled;
|
||||||
|
extern volatile int songRepeatsRemaining;
|
||||||
|
extern volatile int nextSongRepeats;
|
||||||
|
extern volatile bool songModeNeedsNext;
|
||||||
|
extern volatile bool isPlaying;
|
||||||
|
extern volatile int tempo;
|
||||||
|
extern volatile unsigned long lastClockTime;
|
||||||
|
extern volatile int clockCount;
|
||||||
|
|
||||||
|
// Watchdog & Loop timing
|
||||||
|
extern volatile unsigned long lastLoop0Time;
|
||||||
|
extern volatile unsigned long lastLoop1Time;
|
||||||
|
extern volatile bool watchdogActive;
|
||||||
|
|
||||||
|
// Input state
|
||||||
|
extern volatile int encoderDelta;
|
||||||
|
extern bool lastButtonState;
|
||||||
|
extern unsigned long lastDebounceTime;
|
||||||
|
extern bool buttonActive;
|
||||||
|
extern bool buttonConsumed;
|
||||||
|
extern unsigned long buttonPressTime;
|
||||||
|
|
||||||
|
#endif
|
||||||
460
UIThread.cpp
Normal file
460
UIThread.cpp
Normal file
@ -0,0 +1,460 @@
|
|||||||
|
#include <SPI.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <EEPROM.h>
|
||||||
|
#include "TrackerTypes.h"
|
||||||
|
#include "MelodyStrategy.h"
|
||||||
|
#include "LuckyStrategy.h"
|
||||||
|
#include "ArpStrategy.h"
|
||||||
|
#include "EuclideanStrategy.h"
|
||||||
|
#include "MarkovStrategy.h"
|
||||||
|
#include "CellularAutomataStrategy.h"
|
||||||
|
#include "LSystemStrategy.h"
|
||||||
|
#include "MidiDriver.h"
|
||||||
|
#include "UIManager.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "UIThread.h"
|
||||||
|
#include "SharedState.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static void handleInput();
|
||||||
|
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]);
|
||||||
|
|
||||||
|
void saveSequence(bool quiet) {
|
||||||
|
midi.lock();
|
||||||
|
int addr = 0;
|
||||||
|
EEPROM.put(addr, EEPROM_MAGIC); addr += sizeof(EEPROM_MAGIC);
|
||||||
|
int channels[NUM_TRACKS];
|
||||||
|
for(int i=0; i<NUM_TRACKS; i++) channels[i] = midiChannels[i];
|
||||||
|
EEPROM.put(addr, channels); addr += sizeof(channels);
|
||||||
|
EEPROM.put(addr, melodySeeds); addr += sizeof(melodySeeds);
|
||||||
|
EEPROM.put(addr, currentStrategyIndices); addr += sizeof(currentStrategyIndices);
|
||||||
|
bool mutes[NUM_TRACKS];
|
||||||
|
for(int i=0; i<NUM_TRACKS; i++) mutes[i] = trackMute[i];
|
||||||
|
EEPROM.put(addr, mutes); addr += sizeof(mutes);
|
||||||
|
EEPROM.put(addr, (int)tempo); addr += sizeof(int);
|
||||||
|
EEPROM.put(addr, (int)playMode); addr += sizeof(int);
|
||||||
|
|
||||||
|
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, sequence); addr += sizeof(sequence);
|
||||||
|
midi.unlock();
|
||||||
|
EEPROM.commit();
|
||||||
|
if (!quiet) ui.showMessage("SAVED!");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loadSequence() {
|
||||||
|
midi.lock();
|
||||||
|
int addr = 0;
|
||||||
|
uint32_t magic;
|
||||||
|
EEPROM.get(addr, magic); addr += sizeof(magic);
|
||||||
|
if (magic != EEPROM_MAGIC) return false;
|
||||||
|
|
||||||
|
int channels[NUM_TRACKS];
|
||||||
|
EEPROM.get(addr, channels); addr += sizeof(channels);
|
||||||
|
for(int i=0; i<NUM_TRACKS; i++) midiChannels[i] = channels[i];
|
||||||
|
EEPROM.get(addr, melodySeeds); addr += sizeof(melodySeeds);
|
||||||
|
EEPROM.get(addr, currentStrategyIndices); addr += sizeof(currentStrategyIndices);
|
||||||
|
bool mutes[NUM_TRACKS];
|
||||||
|
EEPROM.get(addr, mutes); addr += sizeof(mutes);
|
||||||
|
for(int i=0; i<NUM_TRACKS; i++) trackMute[i] = mutes[i];
|
||||||
|
int t;
|
||||||
|
EEPROM.get(addr, t); addr += sizeof(int);
|
||||||
|
tempo = t;
|
||||||
|
EEPROM.get(addr, t); addr += sizeof(int);
|
||||||
|
playMode = (PlayMode)t;
|
||||||
|
|
||||||
|
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, sequence); addr += sizeof(sequence);
|
||||||
|
midi.unlock();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void factoryReset() {
|
||||||
|
ui.showMessage("RESETTING...");
|
||||||
|
uint32_t magic = 0;
|
||||||
|
EEPROM.put(0, magic);
|
||||||
|
EEPROM.commit();
|
||||||
|
delay(500);
|
||||||
|
rp2040.reboot();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void generateTrackData(int track, int themeType, Step (*target)[NUM_STEPS]) {
|
||||||
|
randomSeed(melodySeeds[track] + themeType * 12345);
|
||||||
|
strategies[currentStrategyIndices[track]]->generate(target, track, NUM_STEPS, scaleNotes, numScaleNotes, melodySeeds[track] + themeType * 12345);
|
||||||
|
}
|
||||||
|
|
||||||
|
void generateRandomScale() {
|
||||||
|
// All tracks share the same scale for now
|
||||||
|
strategies[currentStrategyIndices[0]]->generateScale(scaleNotes, numScaleNotes);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void generateSequenceData(int themeType, Step (*target)[NUM_STEPS]) {
|
||||||
|
for(int i=0; i<NUM_TRACKS; i++) {
|
||||||
|
generateTrackData(i, themeType, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void generateTheme(int themeType) {
|
||||||
|
Step local_sequence[NUM_TRACKS][NUM_STEPS];
|
||||||
|
generateSequenceData(themeType, sequence);
|
||||||
|
|
||||||
|
midi.lock();
|
||||||
|
memcpy(sequence, local_sequence, sizeof(local_sequence));
|
||||||
|
needsPanic = true;
|
||||||
|
midi.unlock();
|
||||||
|
|
||||||
|
currentThemeIndex = themeType;
|
||||||
|
clockCount = 0;
|
||||||
|
lastClockTime = micros();
|
||||||
|
playbackStep = 0;
|
||||||
|
isPlaying = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mutateSequence(Step (*target)[NUM_STEPS]) {
|
||||||
|
if (playMode == MODE_POLY) {
|
||||||
|
for(int i=0; i<NUM_TRACKS; i++) strategies[currentStrategyIndices[i]]->mutate(target, i, NUM_STEPS, scaleNotes, numScaleNotes);
|
||||||
|
} else {
|
||||||
|
strategies[currentStrategyIndices[0]]->mutate(target, 0, NUM_STEPS, scaleNotes, numScaleNotes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handleInput() {
|
||||||
|
// Handle Encoder Rotation
|
||||||
|
int delta = 0;
|
||||||
|
noInterrupts();
|
||||||
|
delta = encoderDelta;
|
||||||
|
encoderDelta = 0;
|
||||||
|
interrupts();
|
||||||
|
|
||||||
|
if (delta != 0) {
|
||||||
|
switch(currentState) {
|
||||||
|
case UI_MENU_MAIN:
|
||||||
|
menuSelection += (delta > 0 ? 1 : -1);
|
||||||
|
if (menuSelection < 0) menuSelection = mainMenuCount - 1;
|
||||||
|
if (menuSelection >= mainMenuCount) menuSelection = 0;
|
||||||
|
break;
|
||||||
|
case UI_MENU_RANDOMIZE:
|
||||||
|
{
|
||||||
|
menuSelection += (delta > 0 ? 1 : -1);
|
||||||
|
int count = (playMode == MODE_POLY) ? randomizeMenuPolyCount : randomizeMenuMonoCount;
|
||||||
|
if (menuSelection < 0) menuSelection = count - 1;
|
||||||
|
if (menuSelection >= count) menuSelection = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UI_MENU_SETUP:
|
||||||
|
menuSelection += (delta > 0 ? 1 : -1);
|
||||||
|
if (menuSelection < 0) menuSelection = setupMenuCount - 1;
|
||||||
|
if (menuSelection >= setupMenuCount) menuSelection = 0;
|
||||||
|
break;
|
||||||
|
case UI_SETUP_CHANNEL_EDIT:
|
||||||
|
{
|
||||||
|
int trackToEdit = (playMode == MODE_POLY) ? randomizeTrack : 0;
|
||||||
|
midiChannels[trackToEdit] += (delta > 0 ? 1 : -1);
|
||||||
|
if (midiChannels[trackToEdit] < 1) midiChannels[trackToEdit] = 16;
|
||||||
|
if (midiChannels[trackToEdit] > 16) midiChannels[trackToEdit] = 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UI_EDIT_TEMPO:
|
||||||
|
tempo += delta;
|
||||||
|
if (tempo < 40) tempo = 40;
|
||||||
|
if (tempo > 240) tempo = 240;
|
||||||
|
break;
|
||||||
|
case UI_EDIT_FLAVOUR:
|
||||||
|
{
|
||||||
|
int trackToEdit = playMode == MODE_POLY ? randomizeTrack : 0;
|
||||||
|
currentStrategyIndices[trackToEdit] += (delta > 0 ? 1 : -1);
|
||||||
|
if (currentStrategyIndices[trackToEdit] < 0) currentStrategyIndices[trackToEdit] = numStrategies - 1;
|
||||||
|
if (currentStrategyIndices[trackToEdit] >= numStrategies) currentStrategyIndices[trackToEdit] = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UI_SETUP_PLAYMODE_EDIT:
|
||||||
|
playMode = (playMode == MODE_MONO) ? MODE_POLY : MODE_MONO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (currentState == UI_RANDOMIZE_TRACK_EDIT) {
|
||||||
|
randomizeTrack += (delta > 0 ? 1 : -1);
|
||||||
|
if (randomizeTrack < 0) randomizeTrack = NUM_TRACKS - 1;
|
||||||
|
if (randomizeTrack >= NUM_TRACKS) randomizeTrack = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Button
|
||||||
|
int reading = digitalRead(ENC_SW);
|
||||||
|
|
||||||
|
if (reading != lastButtonState) {
|
||||||
|
lastDebounceTime = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((millis() - lastDebounceTime) > 50) {
|
||||||
|
if (reading == LOW && !buttonActive) {
|
||||||
|
// Button Pressed
|
||||||
|
buttonActive = true;
|
||||||
|
buttonPressTime = millis();
|
||||||
|
buttonConsumed = false;
|
||||||
|
Serial.println(F("Button Down"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reading == HIGH && buttonActive) {
|
||||||
|
// Button Released
|
||||||
|
buttonActive = false;
|
||||||
|
if (!buttonConsumed) { // Short press action
|
||||||
|
switch(currentState) {
|
||||||
|
case UI_MENU_MAIN:
|
||||||
|
if (menuSelection == 0) { currentState = UI_MENU_RANDOMIZE; menuSelection = 0; break; }
|
||||||
|
if (menuSelection == 1) { currentState = UI_MENU_SETUP; menuSelection = 0; break; }
|
||||||
|
break;
|
||||||
|
case UI_MENU_RANDOMIZE:
|
||||||
|
{
|
||||||
|
int track_offset = (playMode == MODE_POLY) ? 2 : 0;
|
||||||
|
int theme_1_index = (playMode == MODE_POLY) ? THEME_1_INDEX_POLY : THEME_1_INDEX_MONO;
|
||||||
|
if (menuSelection == 0) { currentState = UI_MENU_SETUP; menuSelection = 0; break; }
|
||||||
|
if (playMode == MODE_POLY) {
|
||||||
|
if (menuSelection == 1) { currentState = UI_RANDOMIZE_TRACK_EDIT; break; }
|
||||||
|
if (menuSelection == 2) { trackMute[randomizeTrack] = !trackMute[randomizeTrack]; break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (menuSelection == 1 + track_offset) { // Melody
|
||||||
|
int track = playMode == MODE_POLY ? randomizeTrack : 0;
|
||||||
|
midi.lock();
|
||||||
|
melodySeeds[track] = random(10000);
|
||||||
|
if (isPlaying) {
|
||||||
|
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
|
||||||
|
if (!sequenceChangeScheduled) {
|
||||||
|
memcpy(nextSequence, sequence, sizeof(sequence));
|
||||||
|
}
|
||||||
|
generateTrackData(track, theme, nextSequence);
|
||||||
|
sequenceChangeScheduled = true;
|
||||||
|
}
|
||||||
|
midi.unlock();
|
||||||
|
saveSequence(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (menuSelection == 2 + track_offset) { currentState = UI_EDIT_FLAVOUR; break; } // Flavour
|
||||||
|
if (menuSelection == 3 + track_offset) { // Scale
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if (menuSelection == 4 + track_offset) { currentState = UI_EDIT_TEMPO; break; }
|
||||||
|
if (menuSelection == 5 + track_offset) { mutationEnabled = !mutationEnabled; break; }
|
||||||
|
if (menuSelection == 6 + track_offset) {
|
||||||
|
songModeEnabled = !songModeEnabled;
|
||||||
|
if (songModeEnabled) {
|
||||||
|
songModeNeedsNext = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (menuSelection >= theme_1_index) { // Themes
|
||||||
|
const int selectedTheme = menuSelection - theme_1_index + 1;
|
||||||
|
if (isPlaying) {
|
||||||
|
queuedTheme = selectedTheme;
|
||||||
|
midi.lock();
|
||||||
|
generateSequenceData(queuedTheme, nextSequence);
|
||||||
|
sequenceChangeScheduled = true;
|
||||||
|
midi.unlock();
|
||||||
|
} else {
|
||||||
|
generateTheme(selectedTheme);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UI_MENU_SETUP:
|
||||||
|
if (menuSelection == 0) { currentState = UI_MENU_RANDOMIZE; menuSelection = 0; break; }
|
||||||
|
if (menuSelection == 1) { currentState = UI_SETUP_PLAYMODE_EDIT; break; }
|
||||||
|
if (menuSelection == 2) { currentState = UI_SETUP_CHANNEL_EDIT; break; }
|
||||||
|
if (menuSelection == 3) { factoryReset(); break; }
|
||||||
|
break;
|
||||||
|
case UI_SETUP_CHANNEL_EDIT:
|
||||||
|
currentState = UI_MENU_SETUP;
|
||||||
|
saveSequence(true);
|
||||||
|
break;
|
||||||
|
case UI_EDIT_TEMPO:
|
||||||
|
currentState = UI_MENU_RANDOMIZE;
|
||||||
|
saveSequence(true);
|
||||||
|
break;
|
||||||
|
case UI_EDIT_FLAVOUR:
|
||||||
|
currentState = UI_MENU_RANDOMIZE;
|
||||||
|
if (isPlaying) {
|
||||||
|
int theme = (queuedTheme != -1) ? queuedTheme : currentThemeIndex;
|
||||||
|
int track = playMode == MODE_POLY ? randomizeTrack : 0;
|
||||||
|
midi.lock();
|
||||||
|
if (!sequenceChangeScheduled) {
|
||||||
|
memcpy(nextSequence, sequence, sizeof(sequence));
|
||||||
|
}
|
||||||
|
generateTrackData(track, theme, nextSequence);
|
||||||
|
sequenceChangeScheduled = true;
|
||||||
|
midi.unlock();
|
||||||
|
}
|
||||||
|
saveSequence(true);
|
||||||
|
break;
|
||||||
|
case UI_SETUP_PLAYMODE_EDIT:
|
||||||
|
currentState = UI_MENU_SETUP;
|
||||||
|
saveSequence(true);
|
||||||
|
break;
|
||||||
|
case UI_RANDOMIZE_TRACK_EDIT:
|
||||||
|
currentState = UI_MENU_RANDOMIZE;
|
||||||
|
saveSequence(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Long Press (Start/Stop Playback)
|
||||||
|
if (buttonActive && !buttonConsumed && (millis() - buttonPressTime > 600)) {
|
||||||
|
isPlaying = !isPlaying;
|
||||||
|
buttonConsumed = true; // Prevent short press action
|
||||||
|
Serial.print(F("Playback: ")); Serial.println(isPlaying ? F("ON") : F("OFF"));
|
||||||
|
if (isPlaying) {
|
||||||
|
playbackStep = 0;
|
||||||
|
clockCount = 0;
|
||||||
|
lastClockTime = micros();
|
||||||
|
} else {
|
||||||
|
queuedTheme = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastButtonState = reading;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawUI() {
|
||||||
|
const char **randMenu;
|
||||||
|
int randMenuCount;
|
||||||
|
int themeIndex;
|
||||||
|
|
||||||
|
// Make local copies of shared data inside a critical section
|
||||||
|
// to avoid holding the lock during slow display operations.
|
||||||
|
UIState local_currentState;
|
||||||
|
PlayMode local_playMode;
|
||||||
|
int local_menuSelection, local_randomizeTrack, local_tempo, local_currentThemeIndex, local_queuedTheme, local_numScaleNotes;
|
||||||
|
int local_melodySeed;
|
||||||
|
bool local_mutationEnabled, local_songModeEnabled, local_isPlaying;
|
||||||
|
bool local_trackMute[NUM_TRACKS];
|
||||||
|
int local_midiChannel;
|
||||||
|
MelodyStrategy* local_strategy;
|
||||||
|
Step local_sequence[NUM_TRACKS][NUM_STEPS];
|
||||||
|
int local_playbackStep;
|
||||||
|
int local_scaleNotes[12];
|
||||||
|
|
||||||
|
midi.lock();
|
||||||
|
local_playMode = playMode;
|
||||||
|
local_randomizeTrack = randomizeTrack;
|
||||||
|
int ui_track = (local_playMode == MODE_POLY) ? local_randomizeTrack : 0;
|
||||||
|
|
||||||
|
if (local_playMode == MODE_POLY) {
|
||||||
|
randMenu = randomizeMenuPoly;
|
||||||
|
randMenuCount = randomizeMenuPolyCount;
|
||||||
|
themeIndex = THEME_1_INDEX_POLY;
|
||||||
|
} else {
|
||||||
|
randMenu = randomizeMenuMono;
|
||||||
|
randMenuCount = randomizeMenuMonoCount;
|
||||||
|
themeIndex = THEME_1_INDEX_MONO;
|
||||||
|
}
|
||||||
|
|
||||||
|
local_currentState = currentState;
|
||||||
|
local_menuSelection = menuSelection;
|
||||||
|
local_midiChannel = midiChannels[ui_track];
|
||||||
|
local_tempo = tempo;
|
||||||
|
local_strategy = strategies[currentStrategyIndices[ui_track]];
|
||||||
|
local_queuedTheme = queuedTheme;
|
||||||
|
local_currentThemeIndex = currentThemeIndex;
|
||||||
|
local_numScaleNotes = numScaleNotes;
|
||||||
|
memcpy(local_scaleNotes, scaleNotes, sizeof(local_scaleNotes));
|
||||||
|
local_melodySeed = melodySeeds[ui_track];
|
||||||
|
local_mutationEnabled = mutationEnabled;
|
||||||
|
local_songModeEnabled = songModeEnabled;
|
||||||
|
memcpy(local_sequence, sequence, sizeof(local_sequence));
|
||||||
|
local_playbackStep = playbackStep;
|
||||||
|
local_isPlaying = isPlaying;
|
||||||
|
memcpy(local_trackMute, (const void*)trackMute, sizeof(local_trackMute));
|
||||||
|
midi.unlock();
|
||||||
|
|
||||||
|
ui.draw(local_currentState, local_menuSelection,
|
||||||
|
local_midiChannel, local_tempo, local_strategy,
|
||||||
|
local_queuedTheme, local_currentThemeIndex, local_numScaleNotes, local_scaleNotes, local_melodySeed,
|
||||||
|
local_mutationEnabled, local_songModeEnabled, (const Step (*)[NUM_STEPS])local_sequence, local_playbackStep, local_isPlaying,
|
||||||
|
mainMenu, mainMenuCount, randMenu, randMenuCount, setupMenu, setupMenuCount,
|
||||||
|
themeIndex, local_playMode, local_randomizeTrack, (const bool*)local_trackMute);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void updateLeds() {
|
||||||
|
// Make local copies of shared data inside a critical section
|
||||||
|
// to avoid holding the lock during slow LED update operations.
|
||||||
|
Step local_sequence[NUM_TRACKS][NUM_STEPS];
|
||||||
|
int local_playbackStep;
|
||||||
|
bool local_isPlaying;
|
||||||
|
UIState local_currentState;
|
||||||
|
bool local_songModeEnabled;
|
||||||
|
int local_songRepeatsRemaining;
|
||||||
|
bool local_sequenceChangeScheduled;
|
||||||
|
PlayMode local_playMode;
|
||||||
|
int local_numScaleNotes;
|
||||||
|
int local_scaleNotes[12];
|
||||||
|
bool local_trackMute[NUM_TRACKS];
|
||||||
|
|
||||||
|
midi.lock();
|
||||||
|
memcpy(local_sequence, sequence, sizeof(local_sequence));
|
||||||
|
local_playbackStep = playbackStep;
|
||||||
|
local_isPlaying = isPlaying;
|
||||||
|
local_currentState = currentState;
|
||||||
|
local_songModeEnabled = songModeEnabled;
|
||||||
|
local_songRepeatsRemaining = songRepeatsRemaining;
|
||||||
|
local_sequenceChangeScheduled = sequenceChangeScheduled;
|
||||||
|
local_playMode = playMode;
|
||||||
|
local_numScaleNotes = numScaleNotes;
|
||||||
|
memcpy(local_scaleNotes, scaleNotes, sizeof(local_scaleNotes));
|
||||||
|
memcpy(local_trackMute, (const void*)trackMute, sizeof(local_trackMute));
|
||||||
|
midi.unlock();
|
||||||
|
|
||||||
|
ui.updateLeds((const Step (*)[NUM_STEPS])local_sequence, local_playbackStep, local_isPlaying,
|
||||||
|
local_currentState, local_songModeEnabled, local_songRepeatsRemaining,
|
||||||
|
local_sequenceChangeScheduled, local_playMode, local_numScaleNotes,
|
||||||
|
local_scaleNotes, (const bool*)local_trackMute);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loopUI() {
|
||||||
|
unsigned long now = millis();
|
||||||
|
lastLoop0Time = now;
|
||||||
|
if (watchdogActive && (now - lastLoop1Time > 1000)) {
|
||||||
|
Serial.println("Core 1 Freeze detected");
|
||||||
|
rp2040.reboot();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Song Mode Generation in UI Thread
|
||||||
|
if (songModeNeedsNext) {
|
||||||
|
int nextTheme = random(1, 8); // Themes 1-7
|
||||||
|
int repeats = random(1, 9); // 1-8 repeats
|
||||||
|
|
||||||
|
generateSequenceData(nextTheme, nextSequence);
|
||||||
|
queuedTheme = nextTheme;
|
||||||
|
nextSongRepeats = repeats;
|
||||||
|
sequenceChangeScheduled = true;
|
||||||
|
songModeNeedsNext = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInput();
|
||||||
|
drawUI();
|
||||||
|
updateLeds();
|
||||||
|
delay(10); // Small delay to prevent screen tearing/excessive refresh
|
||||||
|
}
|
||||||
14
UIThread.h
Normal file
14
UIThread.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#ifndef UI_THREAD_H
|
||||||
|
#define UI_THREAD_H
|
||||||
|
|
||||||
|
#include "TrackerTypes.h"
|
||||||
|
|
||||||
|
void loopUI();
|
||||||
|
void mutateSequence(Step (*target)[NUM_STEPS]);
|
||||||
|
void generateRandomScale();
|
||||||
|
void generateTheme(int themeType);
|
||||||
|
bool loadSequence();
|
||||||
|
void factoryReset();
|
||||||
|
void saveSequence(bool quiet = false);
|
||||||
|
|
||||||
|
#endif
|
||||||
Loading…
Reference in New Issue
Block a user