file player cue point and loop tracking

no loop trail yet
This commit is contained in:
tildearrow 2025-10-30 04:07:27 -05:00
parent 3c106f7861
commit 319da2d391
7 changed files with 213 additions and 30 deletions

View file

@ -1662,6 +1662,16 @@ void DivEngine::setFilePlayerSync(bool doSync) {
filePlayerSync=doSync;
}
void DivEngine::getFilePlayerCue(int& seconds, int& micros) {
seconds=filePlayerCueSeconds;
micros=filePlayerCueMicros;
}
void DivEngine::setFilePlayerCue(int seconds, int micros) {
filePlayerCueSeconds=seconds;
filePlayerCueMicros=micros;
}
void DivEngine::syncFilePlayer() {
if (curFilePlayer==NULL) return;
int finalSeconds=totalSeconds+filePlayerCueSeconds;

View file

@ -604,6 +604,8 @@ class DivEngine {
bool filePlayerSync;
ssize_t filePlayerCueSeconds;
unsigned int filePlayerCueMicros;
int filePlayerLoopTrail;
int curFilePlayerTrail;
size_t totalProcessed;
@ -630,8 +632,6 @@ class DivEngine {
void runMidiTime(int totalCycles=1);
bool shallSwitchCores();
void syncFilePlayer();
void testFunction();
bool loadDMF(unsigned char* file, size_t len);
@ -762,6 +762,11 @@ class DivEngine {
// get whether the player is synchronized with song playback.
bool getFilePlayerSync();
void setFilePlayerSync(bool doSync);
// get/set file player cue position.
void getFilePlayerCue(int& seconds, int& micros);
void setFilePlayerCue(int seconds, int micros);
// UNSAFE - sync file player to current playback position.
void syncFilePlayer();
// save as .dmf.
SafeWriter* saveDMF(unsigned char version);
@ -1573,6 +1578,8 @@ class DivEngine {
filePlayerSync(false),
filePlayerCueSeconds(0),
filePlayerCueMicros(0),
filePlayerLoopTrail(0),
curFilePlayerTrail(0),
totalProcessed(0),
renderPoolThreads(0),
renderPool(NULL),

View file

@ -208,6 +208,19 @@ void DivFilePlayer::mix(float** buf, int chans, unsigned int size) {
}
for (unsigned int i=0; i<size; i++) {
// acknowledge pending events
if (pendingPosOffset==i) {
pendingPosOffset=UINT_MAX;
playPos=pendingPos;
rateAccum=0;
}
if (pendingPlayOffset==i) {
playing=true;
}
if (pendingStopOffset==i) {
playing=false;
}
ssize_t blockIndex=playPos>>DIV_FPCACHE_BLOCK_SHIFT;
if (blockIndex!=lastWantBlock) {
wantBlock=playPos;
@ -285,22 +298,49 @@ ssize_t DivFilePlayer::getPos() {
return playPos;
}
void DivFilePlayer::getPosSeconds(ssize_t& seconds, unsigned int& micros) {
if (sf==NULL) {
seconds=0;
micros=0;
return;
}
double microsD=playPos%si.samplerate;
seconds=playPos/si.samplerate;
micros=(int)((1000000.0*microsD)/(double)si.samplerate);
}
ssize_t DivFilePlayer::setPos(ssize_t newPos, unsigned int offset) {
playPos=newPos;
rateAccum=0;
wantBlock=playPos;
logD("DivFilePlayer: setPos(%" PRIi64 ")",newPos);
return playPos;
if (offset==UINT_MAX) {
playPos=newPos;
rateAccum=0;
wantBlock=playPos;
logD("DivFilePlayer: setPos(%" PRIi64 ")",newPos);
return playPos;
} else {
pendingPosOffset=offset;
pendingPos=newPos;
wantBlock=playPos;
logD("DivFilePlayer: offset %u setPos(%" PRIi64 ")",offset,newPos);
return newPos;
}
}
ssize_t DivFilePlayer::setPosSeconds(ssize_t seconds, unsigned int micros, unsigned int offset) {
if (sf==NULL) return 0;
double microsD=(double)si.samplerate*((double)micros/1000000.0);
playPos=seconds*si.samplerate+(int)microsD;
rateAccum=0;
wantBlock=playPos;
logD("DivFilePlayer: setPosSeconds(%" PRIi64 ".%06d)",seconds,micros);
return playPos;
if (offset==UINT_MAX) {
playPos=seconds*si.samplerate+(int)microsD;
rateAccum=0;
wantBlock=playPos;
logD("DivFilePlayer: setPosSeconds(%" PRIi64 ".%06d)",seconds,micros);
return playPos;
} else {
pendingPosOffset=offset;
pendingPos=seconds*si.samplerate+(int)microsD;
wantBlock=pendingPos;
logD("DivFilePlayer: offset %u setPosSeconds(%" PRIi64 ".%06d)",offset,seconds,micros);
return pendingPos;
}
}
size_t DivFilePlayer::getMemUsage() {
@ -336,13 +376,23 @@ bool DivFilePlayer::isPlaying() {
}
void DivFilePlayer::play(unsigned int offset) {
logV("DivFilePlayer: playing");
playing=true;
if (offset!=UINT_MAX) {
pendingPlayOffset=offset;
logV("DivFilePlayer: playing (offset: %u)",offset);
} else {
playing=true;
logV("DivFilePlayer: playing");
}
}
void DivFilePlayer::stop(unsigned int offset) {
logV("DivFilePlayer: stopping");
playing=false;
if (offset!=UINT_MAX) {
pendingStopOffset=offset;
logV("DivFilePlayer: stopping (offset: %u)",offset);
} else {
playing=false;
logV("DivFilePlayer: stopping");
}
}
bool DivFilePlayer::closeFile() {
@ -492,6 +542,10 @@ DivFilePlayer::DivFilePlayer():
quitThread(false),
threadHasQuit(false),
isActive(false),
pendingPos(0),
pendingPosOffset(UINT_MAX),
pendingPlayOffset(UINT_MAX),
pendingStopOffset(UINT_MAX),
cacheThread(NULL) {
memset(&si,0,sizeof(SF_INFO));
sincTable=DivFilterTables::getSincTable8();

View file

@ -57,6 +57,11 @@ class DivFilePlayer {
bool threadHasQuit;
bool isActive;
ssize_t pendingPos;
unsigned int pendingPosOffset;
unsigned int pendingPlayOffset;
unsigned int pendingStopOffset;
std::thread* cacheThread;
std::mutex cacheMutex;
std::mutex cacheThreadLock;
@ -73,15 +78,16 @@ class DivFilePlayer {
void mix(float** buf, int chans, unsigned int size);
ssize_t getPos();
ssize_t setPos(ssize_t newPos, unsigned int offset=0);
ssize_t setPosSeconds(ssize_t seconds, unsigned int micros, unsigned int offset=0);
void getPosSeconds(ssize_t& seconds, unsigned int& micros);
ssize_t setPos(ssize_t newPos, unsigned int offset=UINT_MAX);
ssize_t setPosSeconds(ssize_t seconds, unsigned int micros, unsigned int offset=UINT_MAX);
bool isBlockPresent(ssize_t pos);
bool setBlockPriority(ssize_t pos, bool priority);
bool isLoaded();
bool isPlaying();
void play(unsigned int offset=0);
void stop(unsigned int offset=0);
void play(unsigned int offset=UINT_MAX);
void stop(unsigned int offset=UINT_MAX);
bool closeFile();
bool loadFile(const char* path);

View file

@ -3106,6 +3106,22 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
// used by audio export to determine how many samples to write (otherwise it'll add silence at the end)
lastLoopPos=size-runLeftG;
logD("last loop pos: %d for a size of %d and runLeftG of %d",lastLoopPos,size,runLeftG);
// if file player is synchronized then set its position to that of the loop row
if (curFilePlayer && filePlayerSync) {
if (curFilePlayer->isPlaying()) {
DivSongTimestamps::Timestamp rowTS=curSubSong->ts.loopStartTime;
int finalSeconds=rowTS.seconds+filePlayerCueSeconds;
int finalMicros=rowTS.micros+filePlayerCueMicros;
while (finalMicros>=1000000) {
finalMicros-=1000000;
finalSeconds++;
}
curFilePlayer->setPosSeconds(finalSeconds,finalMicros,lastLoopPos);
}
}
// increase total loop count
totalLoops++;
// stop playing once we hit a specific number of loops (set during audio export)
if (remainingLoops>0) {

View file

@ -5892,9 +5892,15 @@ bool FurnaceGUI::loop() {
}
break;
case GUI_FILE_MUSIC_OPEN:
if (!e->getFilePlayer()->loadFile(copyOfName.c_str())) {
showError(fmt::sprintf(_("Error while loading file!")));
}
e->synchronizedSoft([this,copyOfName]() {
bool wasPlaying=e->getFilePlayer()->isPlaying();
if (!e->getFilePlayer()->loadFile(copyOfName.c_str())) {
showError(fmt::sprintf(_("Error while loading file!")));
} else if (wasPlaying && filePlayerSync && refPlayerOpen && e->isPlaying()) {
e->syncFilePlayer();
e->getFilePlayer()->play();
}
});
break;
case GUI_FILE_TEST_OPEN:
showWarning(fmt::sprintf(_("You opened: %s"),copyOfName),GUI_WARN_GENERIC);
@ -7392,6 +7398,16 @@ bool FurnaceGUI::loop() {
if (!fp->isPlaying()) {
DivSongTimestamps::Timestamp rowTS=e->curSubSong->ts.getTimes(cursor.order,cursor.y);
if (rowTS.seconds!=-1) {
int cueSeconds=0;
int cueMicros=0;
e->getFilePlayerCue(cueSeconds,cueMicros);
rowTS.seconds+=cueSeconds;
rowTS.micros+=cueMicros;
while (rowTS.micros>=1000000) {
rowTS.micros-=1000000;
rowTS.seconds++;
}
fp->setPosSeconds(rowTS.seconds,rowTS.micros);
}
}

View file

@ -47,10 +47,14 @@ void FurnaceGUI::drawRefPlayer() {
int posMinutes=((playPos/fileRate)/60)%60;
int posSeconds=(playPos/fileRate)%60;
int posMillis=(1000*(playPos%fileRate))/fileRate;
if (playPosNegative) {
ImGui::Text("-%d:%02d:%02d.%03d",posHours,posMinutes,posSeconds,posMillis);
if (fp->isLoaded()) {
if (playPosNegative) {
ImGui::Text("-%d:%02d:%02d.%03d",posHours,posMinutes,posSeconds,posMillis);
} else {
ImGui::Text("%d:%02d:%02d.%03d",posHours,posMinutes,posSeconds,posMillis);
}
} else {
ImGui::Text("%d:%02d:%02d.%03d",posHours,posMinutes,posSeconds,posMillis);
ImGui::TextUnformatted(_("no file loaded"));
}
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
@ -58,13 +62,80 @@ void FurnaceGUI::drawRefPlayer() {
fp->setPos(playPos);
}
if (ImGui::Button("Open")) {
if (ImGui::Button(ICON_FA_FOLDER_OPEN "##Open")) {
openFileDialog(GUI_FILE_MUSIC_OPEN);
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
e->synchronizedSoft([this,fp]() {
if (!fp->closeFile()) {
showError(_("you haven't loaded a file!"));
}
});
}
ImGui::SetItemTooltip(_("open file\n(right click to unload current file)"));
ImGui::SameLine();
if (ImGui::Button(ICON_FA_FAST_BACKWARD)) {
fp->stop();
fp->setPos(0);
if (ImGui::Button(ICON_FA_STEP_BACKWARD)) {
// handled outside
}
if (fp->isPlaying()) {
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
int cueSeconds=0;
int cueMicros=0;
fp->stop();
e->getFilePlayerCue(cueSeconds,cueMicros);
fp->setPosSeconds(cueSeconds,cueMicros);
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) {
fp->stop();
fp->setPos(0);
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
int cueSeconds=0;
int cueMicros=0;
e->getFilePlayerCue(cueSeconds,cueMicros);
fp->setPosSeconds(cueSeconds,cueMicros);
}
ImGui::SetItemTooltip(
_("left click: go to cue position\n"
"middle click: go to beginning\n"
"right click: go to cue position (but don't stop)")
);
} else {
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
// try setting cue pos
ssize_t curSeconds=0;
unsigned int curMicros=0;
fp->getPosSeconds(curSeconds,curMicros);
DivSongTimestamps::Timestamp rowTS=e->curSubSong->ts.getTimes(curOrder,0);
if (rowTS.seconds==-1) {
showError("the first row of this order isn't going to play.");
} else {
// calculate difference and set cue pos
curSeconds-=rowTS.seconds;
int curMicrosI=curMicros-rowTS.micros;
while (curMicrosI<0) {
curMicrosI+=1000000;
curSeconds--;
}
e->setFilePlayerCue(curSeconds,curMicrosI);
}
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Middle)) {
fp->setPos(0);
}
if (ImGui::BeginPopupContextItem("Edit Cue Position",ImGuiPopupFlags_MouseButtonRight)) {
ImGui::Text("Edit me");
if (ImGui::Button("OK")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
ImGui::SetItemTooltip(
_("left click: set cue position here\n"
" - current playback time becomes position at first row of current order\n"
"middle click: go to beginning\n"
"right click: fine edit cue position")
);
}
ImGui::SameLine();
if (fp->isPlaying()) {
@ -72,11 +143,13 @@ void FurnaceGUI::drawRefPlayer() {
if (ImGui::Button(ICON_FA_PAUSE "##Pause")) {
fp->stop();
}
ImGui::SetItemTooltip(_("pause"));
popToggleColors();
} else {
if (ImGui::Button(ICON_FA_PLAY "##Play")) {
fp->play();
}
ImGui::SetItemTooltip(_("play"));
}
ImGui::SameLine();
@ -84,6 +157,7 @@ void FurnaceGUI::drawRefPlayer() {
if (ImGui::Button(_("Sync"))) {
filePlayerSync=!filePlayerSync;
}
ImGui::SetItemTooltip(_("synchronize playback with tracker playback"));
popToggleColors();
e->setFilePlayerSync(filePlayerSync);