Merge branch 'tildearrow:master' into SID3

This commit is contained in:
LTVA1 2024-08-22 14:17:34 +03:00 committed by GitHub
commit 093b1fe4dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 451 additions and 23 deletions

View file

@ -1142,13 +1142,6 @@ dirent_mbstowcs_s(
{ {
int error; 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 */ /* Older Visual Studio or non-Microsoft compiler */
size_t n; size_t n;
@ -1179,7 +1172,6 @@ dirent_mbstowcs_s(
} }
#endif
return error; return error;
} }
@ -1230,13 +1222,6 @@ dirent_wcstombs_s(
{ {
int error; 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 */ /* Older Visual Studio or non-Microsoft compiler */
size_t n; size_t n;
@ -1268,7 +1253,6 @@ dirent_wcstombs_s(
} }
#endif
return error; return error;
} }

View file

@ -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<int>& 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 #define EXPORT_BUFSIZE 2048
double DivEngine::benchmarkPlayback() { double DivEngine::benchmarkPlayback() {

View file

@ -474,7 +474,7 @@ class DivEngine {
int midiOutTimeRate; int midiOutTimeRate;
float midiVolExp; float midiVolExp;
int softLockCount; 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 curSubSongIndex;
size_t bufferPos; size_t bufferPos;
double divider; double divider;
@ -498,6 +498,7 @@ class DivEngine {
DivAudioExportModes exportMode; DivAudioExportModes exportMode;
DivAudioExportFormats exportFormat; DivAudioExportFormats exportFormat;
double exportFadeOut; double exportFadeOut;
bool isFadingOut;
int exportOutputs; int exportOutputs;
bool exportChannelMask[DIV_MAX_CHANS]; bool exportChannelMask[DIV_MAX_CHANS];
DivConfig conf; DivConfig conf;
@ -816,6 +817,9 @@ class DivEngine {
// find song loop position // find song loop position
void walkSong(int& loopOrder, int& loopRow, int& loopEnd); 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<int>& orders, int& length);
// play (returns whether successful) // play (returns whether successful)
bool play(); bool play();
@ -1007,6 +1011,24 @@ class DivEngine {
// is exporting // is exporting
bool isExporting(); 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 // add instrument
int addInstrument(int refChan=0, DivInstrumentType fallbackType=DIV_INS_STD); int addInstrument(int refChan=0, DivInstrumentType fallbackType=DIV_INS_STD);
@ -1400,6 +1422,7 @@ class DivEngine {
totalLoops(0), totalLoops(0),
lastLoopPos(0), lastLoopPos(0),
exportLoopCount(0), exportLoopCount(0),
curExportChan(0),
nextSpeed(3), nextSpeed(3),
elapsedBars(0), elapsedBars(0),
elapsedBeats(0), elapsedBeats(0),
@ -1438,6 +1461,7 @@ class DivEngine {
exportMode(DIV_EXPORT_MODE_ONE), exportMode(DIV_EXPORT_MODE_ONE),
exportFormat(DIV_EXPORT_FORMAT_S16), exportFormat(DIV_EXPORT_FORMAT_S16),
exportFadeOut(0.0), exportFadeOut(0.0),
isFadingOut(false),
exportOutputs(2), exportOutputs(2),
cmdStreamInt(NULL), cmdStreamInt(NULL),
midiBaseChan(0), midiBaseChan(0),

View file

@ -102,6 +102,211 @@ bool DivSubSong::walk(int& loopOrder, int& loopRow, int& loopEnd, int chans, int
return false; 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<MIN(16,speeds.len); i++) {
speedSum+=speeds.val[i];
}
speedSum/=MAX(1,speeds.len);
if (timeBase<1.0) timeBase=1.0;
if (speedSum<1.0) speedSum=1.0;
if (vD<1) vD=1;
//return (60.0 * hz / (timeBase * hl * speedSum)) * (double)vN / (double)vD;
return 1.0 / ((60.0*hz/(timeBase*hl*speedSum))*(double)vN/(double)vD / 60.0);
}
void DivSubSong::findLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector<int>& orders_vec, std::vector<DivGroovePattern>& 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; i<ordersLen; i++)
{
bool jumped = false;
for (int j=0; j<chans; j++)
{
subPat[j]=pat[j].getPattern(orders.ord[j][i],false);
}
if (i>lastSuspectedLoopEnd)
{
lastSuspectedLoopEnd=i;
}
for (int j=nextRow; j<patLen; j++)
{
nextRow=0;
bool changingOrder=false;
bool jumpingOrder=false;
if (wsWalked[((i<<5)+(j>>3))&8191]&(1<<(j&7)))
{
return;
}
for (int k=0; k<chans; k++)
{
for (int l=0; l<pat[k].effectCols; l++)
{
effectVal=subPat[k]->data[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 ((i<ordersLen-1 || !ignoreJumpAtEnd))
{
nextOrder=i+1;
nextRow=effectVal;
jumpingOrder=true;
}
}
else if (jumpTreatment==1)
{
if (nextOrder==-1 && (i<ordersLen-1 || !ignoreJumpAtEnd))
{
nextOrder=i+1;
nextRow=effectVal;
jumpingOrder=true;
}
}
else
{
if ((i<ordersLen-1 || !ignoreJumpAtEnd))
{
if (!changingOrder)
{
nextOrder=i+1;
}
jumpingOrder=true;
nextRow=effectVal;
}
}
}
else if (subPat[k]->data[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() { void DivSubSong::clearData() {
for (int i=0; i<DIV_MAX_CHANS; i++) { for (int i=0; i<DIV_MAX_CHANS; i++) {
pat[i].wipePatterns(); pat[i].wipePatterns();

View file

@ -186,6 +186,11 @@ struct DivSubSong {
*/ */
bool walk(int& loopOrder, int& loopRow, int& loopEnd, int chans, int jumpTreatment, int ignoreJumpAtEnd, int firstPat=0); bool walk(int& loopOrder, int& loopRow, int& loopEnd, int chans, int jumpTreatment, int ignoreJumpAtEnd, int firstPat=0);
/**
* find song length in rows (up to specified loop point). Also find length of every row
*/
void findLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector<int>& orders, std::vector<DivGroovePattern>& grooves, int& length, int chans, int jumpTreatment, int ignoreJumpAtEnd, int firstPat=0);
void clearData(); void clearData();
void optimizePatterns(); void optimizePatterns();
void rearrangePatterns(); void rearrangePatterns();

View file

@ -33,11 +33,87 @@ bool DivEngine::isExporting() {
return exporting; 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 #ifdef HAVE_SNDFILE
void DivEngine::runExportThread() { void DivEngine::runExportThread() {
size_t fadeOutSamples=got.rate*exportFadeOut; size_t fadeOutSamples=got.rate*exportFadeOut;
size_t curFadeOutSample=0; size_t curFadeOutSample=0;
bool isFadingOut=false; isFadingOut=false;
switch (exportMode) { switch (exportMode) {
case DIV_EXPORT_MODE_ONE: { case DIV_EXPORT_MODE_ONE: {
@ -140,7 +216,11 @@ void DivEngine::runExportThread() {
sf[i]=NULL; sf[i]=NULL;
si[i].samplerate=got.rate; si[i].samplerate=got.rate;
si[i].channels=disCont[i].dispatch->getOutputCount(); si[i].channels=disCont[i].dispatch->getOutputCount();
if (exportFormat==DIV_EXPORT_FORMAT_S16) {
si[i].format=SF_FORMAT_WAV|SF_FORMAT_PCM_16; si[i].format=SF_FORMAT_WAV|SF_FORMAT_PCM_16;
} else {
si[i].format=SF_FORMAT_WAV|SF_FORMAT_FLOAT;
}
} }
for (int i=0; i<song.systemLen; i++) { for (int i=0; i<song.systemLen; i++) {
@ -247,6 +327,8 @@ void DivEngine::runExportThread() {
// take control of audio output // take control of audio output
deinitAudioBackend(); deinitAudioBackend();
curExportChan = 0;
float* outBuf[DIV_MAX_OUTPUTS]; float* outBuf[DIV_MAX_OUTPUTS];
float* outBufFinal; float* outBufFinal;
for (int i=0; i<exportOutputs; i++) { for (int i=0; i<exportOutputs; i++) {
@ -258,6 +340,7 @@ void DivEngine::runExportThread() {
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
if (!exportChannelMask[i]) continue; if (!exportChannelMask[i]) continue;
SNDFILE* sf; SNDFILE* sf;
SF_INFO si; SF_INFO si;
SFWrapper sfWrap; SFWrapper sfWrap;
@ -338,6 +421,8 @@ void DivEngine::runExportThread() {
} }
} }
curExportChan++;
if (sfWrap.doClose()!=0) { if (sfWrap.doClose()!=0) {
logE("could not close audio file!"); logE("could not close audio file!");
} }
@ -378,6 +463,7 @@ void DivEngine::runExportThread() {
} }
logI("done!"); logI("done!");
exporting=false; exporting=false;
curExportChan = 0;
break; break;
} }
} }

View file

@ -995,7 +995,7 @@ Collapsed=0\n\
\n\ \n\
[Window][Rendering...]\n\ [Window][Rendering...]\n\
Pos=585,342\n\ Pos=585,342\n\
Size=114,71\n\ Size=600,100\n\
Collapsed=0\n\ Collapsed=0\n\
\n\ \n\
[Window][Export VGM##FileDialog]\n\ [Window][Export VGM##FileDialog]\n\
@ -2582,7 +2582,42 @@ int FurnaceGUI::loadStream(String path) {
void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) { void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) {
songOrdersLengths.clear();
int loopOrder=0;
int loopRow=0;
int loopEnd=0;
e->walkSong(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); 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; 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]); info=fmt::sprintf(_("Set volume: %d (%.2X, INVALID!)"),p->data[cursor.y][3],p->data[cursor.y][3]);
} else { } else {
float realVol=e->getGain(cursor.xCoarse,p->data[cursor.y][3]); 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; hasInfo=true;
} }
@ -5820,8 +5855,69 @@ bool FurnaceGUI::loop() {
MEASURE_BEGIN(popup); MEASURE_BEGIN(popup);
centerNextWindow(_("Rendering..."),canvasW,canvasH); centerNextWindow(_("Rendering..."),canvasW,canvasH);
if (ImGui::BeginPopupModal(_("Rendering..."),NULL,ImGuiWindowFlags_AlwaysAutoResize)) { if (ImGui::BeginPopupModal(_("Rendering..."),NULL,ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) {
if(audioExportOptions.mode != DIV_EXPORT_MODE_MANY_CHAN)
{
ImGui::Text(_("Please wait...")); 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 (ImGui::Button(_("Abort"))) {
if (e->haltAudioFile()) { if (e->haltAudioFile()) {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
@ -8305,6 +8401,14 @@ FurnaceGUI::FurnaceGUI():
bigFont(NULL), bigFont(NULL),
headFont(NULL), headFont(NULL),
fontRange(NULL), fontRange(NULL),
songLength(0),
songLoopedSectionLength(0),
songFadeoutSectionLength(0),
songHasSongEndCommand(false),
lengthOfOneFile(0),
totalLength(0),
curProgress(0.0f),
totalFiles(0),
localeRequiresJapanese(false), localeRequiresJapanese(false),
localeRequiresChinese(false), localeRequiresChinese(false),
localeRequiresChineseTrad(false), localeRequiresChineseTrad(false),
@ -8825,6 +8929,8 @@ FurnaceGUI::FurnaceGUI():
memset(romExportAvail,0,sizeof(bool)*DIV_ROM_MAX); memset(romExportAvail,0,sizeof(bool)*DIV_ROM_MAX);
songOrdersLengths.clear();
strncpy(noteOffLabel,"OFF",32); strncpy(noteOffLabel,"OFF",32);
strncpy(noteRelLabel,"===",32); strncpy(noteRelLabel,"===",32);
strncpy(macroRelLabel,"REL",32); strncpy(macroRelLabel,"REL",32);

View file

@ -1724,6 +1724,16 @@ class FurnaceGUI {
char emptyLabel[32]; char emptyLabel[32];
char emptyLabel2[32]; char emptyLabel2[32];
std::vector<int> 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 { struct Settings {
bool settingsChanged; bool settingsChanged;
int mainFontSize, patFontSize, headFontSize, iconSize; int mainFontSize, patFontSize, headFontSize, iconSize;