PicoWaveTracker/PlaybackThread.cpp
2026-02-18 23:51:43 +01:00

142 lines
4.5 KiB
C++

#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();
}