PicoWaveTracker/PlaybackThread.cpp
2026-03-07 21:31:17 +01:00

161 lines
5.1 KiB
C++

#include "MidiDriver.h"
#include "TrackerTypes.h"
#include "UIThread.h"
#include "config.h"
#include "SharedState.h"
static int local_numSteps[NUM_TRACKS];
static Step local_sequence[NUM_TRACKS][NUM_STEPS];
static Step local_nextSequence[NUM_TRACKS][NUM_STEPS];
bool wasPlaying = false;
static void handlePlayback() {
bool nowPlaying = isPlaying;
if (!wasPlaying && nowPlaying) {
midi.sendRealtime(0xFA); // MIDI Start
} else if (wasPlaying && !nowPlaying) {
midi.sendRealtime(0xFC); // MIDI Stop
for (int i=0; i<NUM_TRACKS; i++) midi.panic(midiChannels[i]);
}
wasPlaying = nowPlaying;
if (!nowPlaying) {
delay(1); // yield
return;
}
unsigned long currentMicros = micros();
unsigned long clockInterval = 2500000 / tempo; // 60s * 1000000us / (tempo * 24ppqn)
if (currentMicros - lastClockTime < clockInterval) {
delay(1); // yield
return;
} else {
lastClockTime += clockInterval;
midi.sendRealtime(0xF8); // MIDI Clock
clockCount++;
if (clockCount < 6) return; // 24 ppqn / 4 = 6 pulses per 16th note
clockCount = 0;
midi.lock();
memcpy(local_sequence, sequence, sizeof(local_sequence));
memcpy(local_nextSequence, nextSequence, sizeof(local_nextSequence));
for (int i = 0; i < NUM_TRACKS; i++) local_numSteps[i] = numSteps[i];
midi.unlock();
int master_len = 0;
for(int i=0; i<NUM_TRACKS; i++) {
if(local_numSteps[i] > master_len) master_len = local_numSteps[i];
}
if (master_len == 0) master_len = NUM_STEPS;
for(int t=0; t<NUM_TRACKS; t++) {
int trackChannel = midiChannels[t];
int track_len = local_numSteps[t];
int current_step = playbackStep % track_len;
int next_step = (playbackStep + 1) % track_len;
// Determine if we are tying to the next note
bool isTied = local_sequence[t][current_step].tie && (local_sequence[t][next_step].note != -1);
int prevNote = local_sequence[t][current_step].note;
// Note Off for previous step (if NOT tied)
if (!isTied && prevNote != -1) {
midi.sendNoteOff(prevNote, trackChannel);
}
}
playbackStep++;
bool justPanicked = false;
// When the global step counter completes a full cycle of the longest track
if ((playbackStep > 0) && ((playbackStep % master_len) == 0)) {
for (int i=0; i<NUM_TRACKS; i++) midi.panic(midiChannels[i]);
justPanicked = true;
// 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();
}
if (sequenceChangeScheduled) {
memcpy(local_sequence, local_nextSequence, sizeof(local_sequence));
midi.lock();
visualProgressionStep = queuedProgressionStep;
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<NUM_TRACKS; t++) {
int trackChannel = midiChannels[t];
int track_len = local_numSteps[t];
int current_step = playbackStep % track_len;
int prev_step = (playbackStep == 0) ? track_len - 1 : (playbackStep - 1) % track_len;
bool wasTied = local_sequence[t][prev_step].tie && (local_sequence[t][current_step].note != -1);
int prevNote = local_sequence[t][prev_step].note;
int currentNote = local_sequence[t][current_step].note;
// If tied to the SAME note, do not retrigger (sustain)
bool isContinuing = wasTied && (prevNote == currentNote) && !justPanicked;
// Check chance
bool shouldPlay = (random(100) < trackChance[t]);
if (!trackMute[t] && currentNote != -1 && !isContinuing && shouldPlay) {
uint8_t velocity = local_sequence[t][current_step].accent ? 127 : 100;
midi.sendNoteOn(currentNote, velocity, trackChannel);
}
// Note Off for previous step (if tied - delayed Note Off)
if (wasTied && prevNote != -1 && !isContinuing && !justPanicked) {
midi.sendNoteOff(prevNote, trackChannel);
}
}
}
}
void loopPlayback() {
midi.update();
if (needsPanic) {
for (int i=0; i<NUM_TRACKS; i++) {
midi.panic(midiChannels[i]);
}
needsPanic = false;
}
handlePlayback();
}