diff --git a/extern/igfd/dirent/dirent.h b/extern/igfd/dirent/dirent.h index dccfe5e8c..e152dd510 100644 --- a/extern/igfd/dirent/dirent.h +++ b/extern/igfd/dirent/dirent.h @@ -1142,13 +1142,6 @@ dirent_mbstowcs_s( { int error; -#if defined(_MSC_VER) && _MSC_VER >= 1400 - - /* Microsoft Visual Studio 2005 or later */ - error = mbstowcs_s (pReturnValue, wcstr, sizeInWords, mbstr, count); - -#else - /* Older Visual Studio or non-Microsoft compiler */ size_t n; @@ -1179,7 +1172,6 @@ dirent_mbstowcs_s( } -#endif return error; } @@ -1230,13 +1222,6 @@ dirent_wcstombs_s( { int error; -#if defined(_MSC_VER) && _MSC_VER >= 1400 - - /* Microsoft Visual Studio 2005 or later */ - error = wcstombs_s (pReturnValue, mbstr, sizeInBytes, wcstr, count); - -#else - /* Older Visual Studio or non-Microsoft compiler */ size_t n; @@ -1268,7 +1253,6 @@ dirent_wcstombs_s( } -#endif return error; } diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 91cafeb86..92cc5ab87 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -199,6 +199,14 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) { } } +void DivEngine::findSongLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector& orders, int& length) +{ + if (curSubSong!=NULL) + { + curSubSong->findLength(loopOrder, loopRow, fadeoutLen, rowsForFadeout, hasFFxx, orders, song.grooves, length, chans, song.jumpTreatment, song.ignoreJumpAtEnd); + } +} + #define EXPORT_BUFSIZE 2048 double DivEngine::benchmarkPlayback() { diff --git a/src/engine/engine.h b/src/engine/engine.h index bea6879c9..e52e4d4b8 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -474,7 +474,7 @@ class DivEngine { int midiOutTimeRate; float midiVolExp; int softLockCount; - int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, nextSpeed, elapsedBars, elapsedBeats, curSpeed; + int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, curExportChan /*for per-channel export progress*/, nextSpeed, elapsedBars, elapsedBeats, curSpeed; size_t curSubSongIndex; size_t bufferPos; double divider; @@ -498,6 +498,7 @@ class DivEngine { DivAudioExportModes exportMode; DivAudioExportFormats exportFormat; double exportFadeOut; + bool isFadingOut; int exportOutputs; bool exportChannelMask[DIV_MAX_CHANS]; DivConfig conf; @@ -816,6 +817,9 @@ class DivEngine { // find song loop position void walkSong(int& loopOrder, int& loopRow, int& loopEnd); + // find song length in rows (up to specified loop point), and find length of every order + void findSongLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector& orders, int& length); + // play (returns whether successful) bool play(); @@ -1007,6 +1011,24 @@ class DivEngine { // is exporting bool isExporting(); + // get how many loops is left + void getLoopsLeft(int& loops); + + //get how many loops in total export needs to do + void getTotalLoops(int& loops); + + // get current position in song + void getCurSongPos(int& row, int& order); + + //get how many files export needs to create + void getTotalAudioFiles(int& files); + + //get which file is processed right now (progress for e.g. per-channel export) + void getCurFileIndex(int& file); + + //get fadeout state + bool getIsFadingOut(); + // add instrument int addInstrument(int refChan=0, DivInstrumentType fallbackType=DIV_INS_STD); @@ -1400,6 +1422,7 @@ class DivEngine { totalLoops(0), lastLoopPos(0), exportLoopCount(0), + curExportChan(0), nextSpeed(3), elapsedBars(0), elapsedBeats(0), @@ -1438,6 +1461,7 @@ class DivEngine { exportMode(DIV_EXPORT_MODE_ONE), exportFormat(DIV_EXPORT_FORMAT_S16), exportFadeOut(0.0), + isFadingOut(false), exportOutputs(2), cmdStreamInt(NULL), midiBaseChan(0), diff --git a/src/engine/song.cpp b/src/engine/song.cpp index 7d1215225..e4c516b6e 100644 --- a/src/engine/song.cpp +++ b/src/engine/song.cpp @@ -102,6 +102,211 @@ bool DivSubSong::walk(int& loopOrder, int& loopRow, int& loopEnd, int chans, int return false; } +double calcRowLenInSeconds(const DivGroovePattern& speeds, float hz, int vN, int vD, int timeBaseFromSong) +{ + double hl=1; //count for 1 row + if (hl<=0.0) hl=4.0; + double timeBase=timeBaseFromSong+1; + double speedSum=0; + for (int i=0; i& orders_vec, std::vector& grooves, int& length, int chans, int jumpTreatment, int ignoreJumpAtEnd, int firstPat) +{ + length = 0; + hasFFxx = false; + rowsForFadeout = 0; + + float secondsPerThisRow = 0.0f; + + DivGroovePattern curSpeeds = speeds; //simulate that we are playing the song, track all speed/BPM/tempo/engine rate changes + short curVirtualTempoN = virtualTempoN; + short curVirtualTempoD = virtualTempoD; + float curHz = hz; + double curDivider = (double)timeBase; + + double curLen = 0.0; //how many seconds passed since the start of song + + int nextOrder=-1; + int nextRow=0; + int effectVal=0; + int lastSuspectedLoopEnd=-1; + DivPattern* subPat[DIV_MAX_CHANS]; + unsigned char wsWalked[8192]; + memset(wsWalked,0,8192); + if (firstPat>0) { + memset(wsWalked,255,32*firstPat); + } + for (int i=firstPat; ilastSuspectedLoopEnd) + { + lastSuspectedLoopEnd=i; + } + for (int j=nextRow; j>3))&8191]&(1<<(j&7))) + { + return; + } + for (int k=0; kdata[j][5+(l<<1)]; + if (effectVal<0) effectVal=0; + + if (subPat[k]->data[j][4+(l<<1)]==0xff) + { + hasFFxx = true; + + //FFxx makes YOU SHALL NOT PASS!!! move + orders_vec.push_back(j + 1); //order len + length += j + 1; //add length of order to song length + + return; + } + + switch(subPat[k]->data[j][4+(l<<1)]) //track speed/BMP/Hz/tempo changes + { + case 0x09: // select groove pattern/speed 1 + { + if (grooves.empty()) { + if (effectVal>0) curSpeeds.val[0]=effectVal; + } else { + if (effectVal<(short)grooves.size()) { + curSpeeds=grooves[effectVal]; + //curSpeed=0; + } + } + break; + } + case 0x0f: // speed 1/speed 2 + { + if (curSpeeds.len==2 && grooves.empty()) { + if (effectVal>0) curSpeeds.val[1]=effectVal; + } else { + if (effectVal>0) curSpeeds.val[0]=effectVal; + } + break; + } + case 0xfd: // virtual tempo num + { + if (effectVal>0) curVirtualTempoN=effectVal; + break; + } + case 0xfe: // virtual tempo den + { + if (effectVal>0) curVirtualTempoD=effectVal; + break; + } + case 0xf0: // set Hz by tempo (set bpm) + { + curDivider=(double)effectVal*2.0/5.0; + if (curDivider<1) curDivider=1; + //cycles=got.rate*pow(2,MASTER_CLOCK_PREC)/divider; + //clockDrift=0; + //subticks=0; + break; + } + default: break; + } + + if (subPat[k]->data[j][4+(l<<1)]==0x0d) + { + if (jumpTreatment==2) + { + if ((idata[j][4+(l<<1)]==0x0b) + { + if (nextOrder==-1 || jumpTreatment==0) + { + nextOrder=effectVal; + if (jumpTreatment==1 || jumpTreatment==2 || !jumpingOrder) + { + nextRow=0; + } + changingOrder=true; + } + } + } + } + + if(i > loopOrder || (i == loopOrder && j > loopRow)) + { + if(curLen <= fadeoutLen && fadeoutLen > 0.0) //we count each row fadeout lasts. When our time is greater than fadeout length we successfully counted the number of fadeout rows + { + secondsPerThisRow = calcRowLenInSeconds(speeds, curHz, curVirtualTempoN, curVirtualTempoD, curDivider); + curLen += secondsPerThisRow; + rowsForFadeout++; + } + } + + wsWalked[((i<<5)+(j>>3))&8191]|=1<<(j&7); + + if (nextOrder!=-1) + { + i=nextOrder-1; + orders_vec.push_back(j + 1); //order len + length += j + 1; //add length of order to song length + jumped = true; + nextOrder=-1; + break; + } + } + if(!jumped) //if no jump occured we add full pattern length + { + orders_vec.push_back(patLen); //order len + length += patLen; //add length of order to song length + } + } +} + void DivSubSong::clearData() { for (int i=0; i& orders, std::vector& grooves, int& length, int chans, int jumpTreatment, int ignoreJumpAtEnd, int firstPat=0); + void clearData(); void optimizePatterns(); void rearrangePatterns(); diff --git a/src/engine/wavOps.cpp b/src/engine/wavOps.cpp index 001e3a3e7..f1e8de315 100644 --- a/src/engine/wavOps.cpp +++ b/src/engine/wavOps.cpp @@ -33,11 +33,87 @@ bool DivEngine::isExporting() { return exporting; } +void DivEngine::getLoopsLeft(int& loops) { + if(totalLoops < 0 || exportLoopCount == 0) + { + loops = 0; + return; + } + loops = exportLoopCount - 1 - totalLoops; +} + +void DivEngine::getTotalLoops(int& loops) { + loops = exportLoopCount - 1; +} + +void DivEngine::getCurSongPos(int& row, int& order) { + row = curRow; + order = curOrder; +} + +void DivEngine::getTotalAudioFiles(int& files) +{ + files = 0; + + switch(exportMode) + { + case DIV_EXPORT_MODE_ONE: + { + files = 1; + break; + } + case DIV_EXPORT_MODE_MANY_SYS: + { + files = 1; //there actually are several files but they are processed in the same loop, so to correctly draw progress we think of them as one file + break; + } + case DIV_EXPORT_MODE_MANY_CHAN: + { + for(int i = 0; i < chans; i++) + { + if (exportChannelMask[i]) files++; + } + break; + } + default: break; + } +} + +void DivEngine::getCurFileIndex(int& file) +{ + file = 0; + + switch(exportMode) + { + case DIV_EXPORT_MODE_ONE: + { + file = 0; + break; + } + case DIV_EXPORT_MODE_MANY_SYS: + { + file = 0; //there actually are several files but they are processed in the same loop, so to correctly draw progress we think of them as one file + break; + } + case DIV_EXPORT_MODE_MANY_CHAN: + { + file = curExportChan; + break; + } + default: break; + } +} + +bool DivEngine::getIsFadingOut() +{ + return isFadingOut; +} + #ifdef HAVE_SNDFILE void DivEngine::runExportThread() { size_t fadeOutSamples=got.rate*exportFadeOut; size_t curFadeOutSample=0; - bool isFadingOut=false; + isFadingOut=false; switch (exportMode) { case DIV_EXPORT_MODE_ONE: { @@ -140,7 +216,11 @@ void DivEngine::runExportThread() { sf[i]=NULL; si[i].samplerate=got.rate; si[i].channels=disCont[i].dispatch->getOutputCount(); - si[i].format=SF_FORMAT_WAV|SF_FORMAT_PCM_16; + if (exportFormat==DIV_EXPORT_FORMAT_S16) { + si[i].format=SF_FORMAT_WAV|SF_FORMAT_PCM_16; + } else { + si[i].format=SF_FORMAT_WAV|SF_FORMAT_FLOAT; + } } for (int i=0; iwalkSong(loopOrder,loopRow,loopEnd); + + e->findSongLength(loopOrder, loopRow, audioExportOptions.fadeOut, songFadeoutSectionLength, songHasSongEndCommand, songOrdersLengths, songLength); //for progress estimation + + songLoopedSectionLength = songLength; + for(int i = 0; i < loopOrder; i++) + { + songLoopedSectionLength -= songOrdersLengths[i]; + } + songLoopedSectionLength -= loopRow; + e->saveAudio(path.c_str(),audioExportOptions); + + totalFiles = 0; + e->getTotalAudioFiles(totalFiles); + int totalLoops = 0; + + lengthOfOneFile = songLength; + + if(!songHasSongEndCommand) + { + e->getTotalLoops(totalLoops); + + lengthOfOneFile += songLoopedSectionLength * totalLoops; + lengthOfOneFile += songFadeoutSectionLength; //account for fadeout + } + + totalLength = lengthOfOneFile * totalFiles; + + curProgress = 0.0f; + displayExporting=true; } @@ -4748,7 +4783,7 @@ bool FurnaceGUI::loop() { info=fmt::sprintf(_("Set volume: %d (%.2X, INVALID!)"),p->data[cursor.y][3],p->data[cursor.y][3]); } else { float realVol=e->getGain(cursor.xCoarse,p->data[cursor.y][3]); - info=fmt::sprintf(_("Set volume: %d (%.2X, %d%%)"),p->data[cursor.y][3],p->data[cursor.y][3],(int)(realVol*100.0f/(float)maxVol)); + info=fmt::sprintf(_("Set volume: %d (%.2X, %d%%)"),p->data[cursor.y][3],p->data[cursor.y][3],(int)(realVol*100.0f)); } hasInfo=true; } @@ -5820,8 +5855,69 @@ bool FurnaceGUI::loop() { MEASURE_BEGIN(popup); centerNextWindow(_("Rendering..."),canvasW,canvasH); - if (ImGui::BeginPopupModal(_("Rendering..."),NULL,ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text(_("Please wait...")); + if (ImGui::BeginPopupModal(_("Rendering..."),NULL,ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) { + if(audioExportOptions.mode != DIV_EXPORT_MODE_MANY_CHAN) + { + ImGui::Text(_("Please wait...")); + } + float* progressLambda = &curProgress; + int curPosInRows = 0; + int* curPosInRowsLambda = &curPosInRows; + int loopsLeft = 0; + int* loopsLeftLambda = &loopsLeft; + int totalLoops = 0; + int* totalLoopsLambda = &totalLoops; + int curFile = 0; + int* curFileLambda = &curFile; + if(e->isExporting()) + { + e->lockEngine([this, progressLambda, curPosInRowsLambda, curFileLambda, loopsLeftLambda, totalLoopsLambda]() + { + int curRow = 0; + int curOrder = 0; + e->getCurSongPos(curRow, curOrder); + *curFileLambda = 0; + e->getCurFileIndex(*curFileLambda); + + *curPosInRowsLambda = curRow; + + for(int i = 0; i < curOrder; i++) + { + *curPosInRowsLambda += songOrdersLengths[i]; + } + + if(!songHasSongEndCommand) + { + e->getLoopsLeft(*loopsLeftLambda); + e->getTotalLoops(*totalLoopsLambda); + + if((*totalLoopsLambda) != (*loopsLeftLambda)) //we are going 2nd, 3rd, etc. time through the song + { + *curPosInRowsLambda -= (songLength - songLoopedSectionLength); //a hack so progress bar does not jump? + } + if(e->getIsFadingOut()) //we are in fadeout??? why it works like that bruh + { + *curPosInRowsLambda -= (songLength - songLoopedSectionLength); //a hack so progress bar does not jump? + } + } + + *progressLambda = (float)((*curPosInRowsLambda) + + ((*totalLoopsLambda) - (*loopsLeftLambda)) * songLength + + lengthOfOneFile * (*curFileLambda)) + / (float)totalLength; + }); + } + + ImGui::Text(_("Row %d of %d"), curPosInRows + + ((totalLoops) - (loopsLeft)) * songLength, lengthOfOneFile); + + if(audioExportOptions.mode == DIV_EXPORT_MODE_MANY_CHAN) + { + ImGui::Text(_("Channel %d of %d"), curFile + 1, totalFiles); + } + + ImGui::ProgressBar(curProgress,ImVec2(-FLT_MIN,0), fmt::sprintf("%.2f%%", curProgress * 100.0f).c_str()); + if (ImGui::Button(_("Abort"))) { if (e->haltAudioFile()) { ImGui::CloseCurrentPopup(); @@ -8305,6 +8401,14 @@ FurnaceGUI::FurnaceGUI(): bigFont(NULL), headFont(NULL), fontRange(NULL), + songLength(0), + songLoopedSectionLength(0), + songFadeoutSectionLength(0), + songHasSongEndCommand(false), + lengthOfOneFile(0), + totalLength(0), + curProgress(0.0f), + totalFiles(0), localeRequiresJapanese(false), localeRequiresChinese(false), localeRequiresChineseTrad(false), @@ -8825,6 +8929,8 @@ FurnaceGUI::FurnaceGUI(): memset(romExportAvail,0,sizeof(bool)*DIV_ROM_MAX); + songOrdersLengths.clear(); + strncpy(noteOffLabel,"OFF",32); strncpy(noteRelLabel,"===",32); strncpy(macroRelLabel,"REL",32); diff --git a/src/gui/gui.h b/src/gui/gui.h index 50dc1025c..29eb4e1a5 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1724,6 +1724,16 @@ class FurnaceGUI { char emptyLabel[32]; char emptyLabel2[32]; + std::vector songOrdersLengths; //lengths of all orders (for drawing song export progress) + int songLength; //length of all the song in rows + int songLoopedSectionLength; //length of looped part of the song + int songFadeoutSectionLength; //length of fading part of the song + bool songHasSongEndCommand; //song has "Song end" command (FFxx) + int lengthOfOneFile; //length of one rendering pass. song length times num of loops + fadeout + int totalLength; //total length of render (lengthOfOneFile times num of files for per-channel export) + float curProgress; + int totalFiles; + struct Settings { bool settingsChanged; int mainFontSize, patFontSize, headFontSize, iconSize;