From 319da2d3916100a51aaf21df442142a143ae8fce Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 30 Oct 2025 04:07:27 -0500 Subject: [PATCH] file player cue point and loop tracking no loop trail yet --- src/engine/engine.cpp | 10 +++++ src/engine/engine.h | 11 ++++- src/engine/filePlayer.cpp | 82 +++++++++++++++++++++++++++++------- src/engine/filePlayer.h | 14 +++++-- src/engine/playback.cpp | 16 +++++++ src/gui/gui.cpp | 22 ++++++++-- src/gui/refPlayer.cpp | 88 +++++++++++++++++++++++++++++++++++---- 7 files changed, 213 insertions(+), 30 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 9c1825f4e..d52aa877c 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -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; diff --git a/src/engine/engine.h b/src/engine/engine.h index da98f565d..9cda7433e 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -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), diff --git a/src/engine/filePlayer.cpp b/src/engine/filePlayer.cpp index 2b2b493ca..9c627ce84 100644 --- a/src/engine/filePlayer.cpp +++ b/src/engine/filePlayer.cpp @@ -208,6 +208,19 @@ void DivFilePlayer::mix(float** buf, int chans, unsigned int size) { } for (unsigned int i=0; i>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(); diff --git a/src/engine/filePlayer.h b/src/engine/filePlayer.h index 197d5cc2c..0ab310179 100644 --- a/src/engine/filePlayer.h +++ b/src/engine/filePlayer.h @@ -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); diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 70595e3b0..e7804fc83 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -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) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 34752df2b..bde08b038 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -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); } } diff --git a/src/gui/refPlayer.cpp b/src/gui/refPlayer.cpp index c2099f163..716a3cfc3 100644 --- a/src/gui/refPlayer.cpp +++ b/src/gui/refPlayer.cpp @@ -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);