diff --git a/CMakeLists.txt b/CMakeLists.txt index b7b111f82..fe9118685 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -445,6 +445,7 @@ src/gui/settings.cpp src/gui/songInfo.cpp src/gui/songNotes.cpp src/gui/stats.cpp +src/gui/subSongs.cpp src/gui/sysConf.cpp src/gui/sysEx.cpp src/gui/util.cpp diff --git a/papers/format.md b/papers/format.md index bd4b57a02..c7672c29e 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,11 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 95: Furnace dev95 +- 94: Furnace dev94 +- 93: Furnace dev93 +- 92: Furnace dev92 +- 91: Furnace dev91 - 90: Furnace dev90 - 89: Furnace dev89 - 88: Furnace dev88 @@ -122,26 +127,26 @@ size | description -----|------------------------------------ 4 | "INFO" block ID 4 | reserved - 1 | time base - 1 | speed 1 - 1 | speed 2 - 1 | initial arpeggio time - 4f | ticks per second + 1 | time base (of first song) + 1 | speed 1 (of first song) + 1 | speed 2 (of first song) + 1 | initial arpeggio time (of first song) + 4f | ticks per second (of first song) | - 60 is NTSC | - 50 is PAL - 2 | pattern length + 2 | pattern length (of first song) | - the limit is 256. - 2 | orders length + 2 | orders length (of first song) | - the limit is 256 (>=80) or 127 (<80). - 1 | highlight A - 1 | highlight B + 1 | highlight A (of first song) + 1 | highlight B (of first song) 2 | instrument count | - the limit is 256. 2 | wavetable count | - the limit is 256. 2 | sample count | - the limit is 256. - 4 | pattern count + 4 | pattern count (global) 32 | list of sound chips | - possible soundchips: | - 0x00: end of list @@ -258,20 +263,20 @@ size | description 4?? | pointers to wavetables 4?? | pointers to samples 4?? | pointers to patterns - ??? | orders + ??? | orders (of first song) | - a table of bytes | - size=channels*ordLen | - read orders then channels | - the maximum value of a cell is FF (>=80) or 7F (<80). - ??? | effect columns + ??? | effect columns (of first song) | - size=channels - 1?? | channel hide status + 1?? | channel hide status (of first song) | - size=channels - 1?? | channel collapse status + 1?? | channel collapse status (of first song) | - size=channels - S?? | channel names + S?? | channel names (of first song) | - a list of channelCount C strings - S?? | channel short names + S?? | channel short names (of first song) | - same as above STR | song comment 4f | master volume, 1.0f=100% (>=59) @@ -292,6 +297,55 @@ size | description 1 | pitch macro is linear (>=90) or reserved 1 | pitch slide speed in full linear pitch mode (>=94) or reserved 18 | reserved + --- | **additional subsongs** (>=95) + STR | first subsong name + STR | first subsong comment + 1 | number of additional subsongs + 3 | reserved + 4?? | pointers to subsong data +``` + +# subsong + +from version 95 onwards, Furnace supports storing multiple songs on a single file. +the way it's currently done is really weird, but it provides for some backwards compatibility (previous versions will only load the first subsong which is already defined in the `INFO` block). + +``` +size | description +-----|------------------------------------ + 4 | "SONG" block ID + 4 | reserved + 1 | time base + 1 | speed 1 + 1 | speed 2 + 1 | initial arpeggio time + 4f | ticks per second + | - 60 is NTSC + | - 50 is PAL + 2 | pattern length + | - the limit is 256. + 2 | orders length + | - the limit is 256. + 1 | highlight A + 1 | highlight B + 4 | reserved + STR | subsong name + STR | subsong comment + ??? | orders + | - a table of bytes + | - size=channels*ordLen + | - read orders then channels + | - the maximum value of a cell is FF. + ??? | effect columns + | - size=channels + 1?? | channel hide status + | - size=channels + 1?? | channel collapse status + | - size=channels + S?? | channel names + | - a list of channelCount C strings + S?? | channel short names + | - same as above ``` # instrument @@ -772,7 +826,8 @@ size | description 4 | reserved 2 | channel 2 | pattern index - 4 | reserved + 2 | subsong (>=95) or reserved + 2 | reserved ??? | pattern data | - size: rows*(4+effectColumns*2)*2 | - read shorts in this order: diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index e96005405..01f1a3a2e 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -139,18 +139,18 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) { int nextRow=0; int effectVal=0; DivPattern* pat[DIV_MAX_CHANS]; - for (int i=0; iordersLen; i++) { for (int j=0; jord[j][i],false); } - for (int j=nextRow; jpatLen; j++) { nextRow=0; for (int k=0; kdata[j][5+(l<<1)]; if (effectVal<0) effectVal=0; if (pat[k]->data[j][4+(l<<1)]==0x0d) { - if (nextOrder==-1 && (iordersLen-1 || !song.ignoreJumpAtEnd)) { nextOrder=i+1; nextRow=effectVal; } @@ -695,6 +695,7 @@ void DivEngine::createNew(const int* description) { saveLock.lock(); song.unload(); song=DivSong(); + changeSong(0); if (description!=NULL) { initSongWithDesc(description); } @@ -716,45 +717,55 @@ void DivEngine::swapChannels(int src, int dest) { } for (int i=0; i<256; i++) { - song.orders.ord[dest][i]^=song.orders.ord[src][i]; - song.orders.ord[src][i]^=song.orders.ord[dest][i]; - song.orders.ord[dest][i]^=song.orders.ord[src][i]; + curOrders->ord[dest][i]^=curOrders->ord[src][i]; + curOrders->ord[src][i]^=curOrders->ord[dest][i]; + curOrders->ord[dest][i]^=curOrders->ord[src][i]; - DivPattern* prev=song.pat[src].data[i]; - song.pat[src].data[i]=song.pat[dest].data[i]; - song.pat[dest].data[i]=prev; + DivPattern* prev=curPat[src].data[i]; + curPat[src].data[i]=curPat[dest].data[i]; + curPat[dest].data[i]=prev; } - song.pat[src].effectCols^=song.pat[dest].effectCols; - song.pat[dest].effectCols^=song.pat[src].effectCols; - song.pat[src].effectCols^=song.pat[dest].effectCols; + curPat[src].effectCols^=curPat[dest].effectCols; + curPat[dest].effectCols^=curPat[src].effectCols; + curPat[src].effectCols^=curPat[dest].effectCols; - String prevChanName=song.chanName[src]; - String prevChanShortName=song.chanShortName[src]; - bool prevChanShow=song.chanShow[src]; - bool prevChanCollapse=song.chanCollapse[src]; + String prevChanName=curSubSong->chanName[src]; + String prevChanShortName=curSubSong->chanShortName[src]; + bool prevChanShow=curSubSong->chanShow[src]; + bool prevChanCollapse=curSubSong->chanCollapse[src]; - song.chanName[src]=song.chanName[dest]; - song.chanShortName[src]=song.chanShortName[dest]; - song.chanShow[src]=song.chanShow[dest]; - song.chanCollapse[src]=song.chanCollapse[dest]; - song.chanName[dest]=prevChanName; - song.chanShortName[dest]=prevChanShortName; - song.chanShow[dest]=prevChanShow; - song.chanCollapse[dest]=prevChanCollapse; + curSubSong->chanName[src]=curSubSong->chanName[dest]; + curSubSong->chanShortName[src]=curSubSong->chanShortName[dest]; + curSubSong->chanShow[src]=curSubSong->chanShow[dest]; + curSubSong->chanCollapse[src]=curSubSong->chanCollapse[dest]; + curSubSong->chanName[dest]=prevChanName; + curSubSong->chanShortName[dest]=prevChanShortName; + curSubSong->chanShow[dest]=prevChanShow; + curSubSong->chanCollapse[dest]=prevChanCollapse; } void DivEngine::stompChannel(int ch) { logV("stomping channel %d",ch); for (int i=0; i<256; i++) { - song.orders.ord[ch][i]=0; + curOrders->ord[ch][i]=0; } - song.pat[ch].wipePatterns(); - song.pat[ch].effectCols=1; - song.chanName[ch]=""; - song.chanShortName[ch]=""; - song.chanShow[ch]=true; - song.chanCollapse[ch]=false; + curPat[ch].wipePatterns(); + curPat[ch].effectCols=1; + curSubSong->chanName[ch]=""; + curSubSong->chanShortName[ch]=""; + curSubSong->chanShow[ch]=true; + curSubSong->chanCollapse[ch]=false; +} + +void DivEngine::changeSong(size_t songIndex) { + if (songIndex>=song.subsong.size()) return; + curSubSong=song.subsong[songIndex]; + curPat=song.subsong[songIndex]->pat; + curOrders=&song.subsong[songIndex]->orders; + curSubSongIndex=songIndex; + curOrder=0; + curRow=0; } void DivEngine::swapChannelsP(int src, int dest) { @@ -767,6 +778,41 @@ void DivEngine::swapChannelsP(int src, int dest) { BUSY_END; } +void DivEngine::changeSongP(size_t index) { + if (index>=song.subsong.size()) return; + if (index==curSubSongIndex) return; + stop(); + BUSY_BEGIN; + saveLock.lock(); + changeSong(index); + saveLock.unlock(); + BUSY_END; +} + +int DivEngine::addSubSong() { + if (song.subsong.size()>=127) return -1; + BUSY_BEGIN; + saveLock.lock(); + song.subsong.push_back(new DivSubSong); + saveLock.unlock(); + BUSY_END; + return song.subsong.size()-1; +} + +bool DivEngine::removeSubSong(int index) { + if (song.subsong.size()<=1) return false; + stop(); + BUSY_BEGIN; + saveLock.lock(); + song.subsong[index]->clearData(); + delete song.subsong[index]; + song.subsong.erase(song.subsong.begin()+index); + changeSong(0); + saveLock.unlock(); + BUSY_END; + return true; +} + void DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) { int chanCount=chans; quitDispatch(); @@ -1321,15 +1367,15 @@ void DivEngine::reset() { } extValue=0; extValuePresent=0; - speed1=song.speed1; - speed2=song.speed2; + speed1=curSubSong->speed1; + speed2=curSubSong->speed2; firstTick=false; nextSpeed=speed1; divider=60; - if (song.customTempo) { - divider=song.hz; + if (curSubSong->customTempo) { + divider=curSubSong->hz; } else { - if (song.pal) { + if (curSubSong->pal) { divider=60; } else { divider=50; @@ -1476,6 +1522,10 @@ int DivEngine::getRow() { return curRow; } +size_t DivEngine::getCurrentSubSong() { + return curSubSongIndex; +} + unsigned char DivEngine::getSpeed1() { return speed1; } @@ -1485,9 +1535,9 @@ unsigned char DivEngine::getSpeed2() { } float DivEngine::getHz() { - if (song.customTempo) { - return song.hz; - } else if (song.pal) { + if (curSubSong->customTempo) { + return curSubSong->hz; + } else if (curSubSong->pal) { return 60.0; } else { return 50.0; @@ -1659,10 +1709,10 @@ void DivEngine::delInstrument(int index) { song.insLen=song.ins.size(); for (int i=0; idata[k][2]>index) { - song.pat[i].data[j]->data[k][2]--; + if (curPat[i].data[j]==NULL) continue; + for (int k=0; kpatLen; k++) { + if (curPat[i].data[j]->data[k][2]>index) { + curPat[i].data[j]->data[k][2]--; } } } @@ -2048,18 +2098,18 @@ void DivEngine::delSample(int index) { void DivEngine::addOrder(bool duplicate, bool where) { unsigned char order[DIV_MAX_CHANS]; - if (song.ordersLen>=0xff) return; + if (curSubSong->ordersLen>=0xff) return; BUSY_BEGIN_SOFT; if (duplicate) { for (int i=0; iord[i][curOrder]; } } else { bool used[256]; for (int i=0; iordersLen; j++) { + used[curOrders->ord[i][j]]=true; } order[i]=0xff; for (int j=0; j<256; j++) { @@ -2073,19 +2123,19 @@ void DivEngine::addOrder(bool duplicate, bool where) { if (where) { // at the end saveLock.lock(); for (int i=0; iord[i][curSubSong->ordersLen]=order[i]; } - song.ordersLen++; + curSubSong->ordersLen++; saveLock.unlock(); } else { // after current order saveLock.lock(); for (int i=0; icurOrder; j--) { - song.orders.ord[i][j]=song.orders.ord[i][j-1]; + for (int j=curSubSong->ordersLen; j>curOrder; j--) { + curOrders->ord[i][j]=curOrders->ord[i][j-1]; } - song.orders.ord[i][curOrder+1]=order[i]; + curOrders->ord[i][curOrder+1]=order[i]; } - song.ordersLen++; + curSubSong->ordersLen++; saveLock.unlock(); curOrder++; if (playing && !freelance) { @@ -2097,21 +2147,21 @@ void DivEngine::addOrder(bool duplicate, bool where) { void DivEngine::deepCloneOrder(bool where) { unsigned char order[DIV_MAX_CHANS]; - if (song.ordersLen>=0xff) return; + if (curSubSong->ordersLen>=0xff) return; warnings=""; BUSY_BEGIN_SOFT; for (int i=0; iord[i][curOrder]; // find free slot for (int j=0; j<256; j++) { logD("finding free slot in %d...",j); - if (song.pat[i].data[j]==NULL) { + if (curPat[i].data[j]==NULL) { int origOrd=order[i]; order[i]=j; - DivPattern* oldPat=song.pat[i].getPattern(origOrd,false); - DivPattern* pat=song.pat[i].getPattern(j,true); + DivPattern* oldPat=curPat[i].getPattern(origOrd,false); + DivPattern* pat=curPat[i].getPattern(j,true); memcpy(pat->data,oldPat->data,256*32*sizeof(short)); logD("found at %d",j); didNotFind=false; @@ -2125,19 +2175,19 @@ void DivEngine::deepCloneOrder(bool where) { if (where) { // at the end saveLock.lock(); for (int i=0; iord[i][curSubSong->ordersLen]=order[i]; } - song.ordersLen++; + curSubSong->ordersLen++; saveLock.unlock(); } else { // after current order saveLock.lock(); for (int i=0; icurOrder; j--) { - song.orders.ord[i][j]=song.orders.ord[i][j-1]; + for (int j=curSubSong->ordersLen; j>curOrder; j--) { + curOrders->ord[i][j]=curOrders->ord[i][j-1]; } - song.orders.ord[i][curOrder+1]=order[i]; + curOrders->ord[i][curOrder+1]=order[i]; } - song.ordersLen++; + curSubSong->ordersLen++; saveLock.unlock(); curOrder++; if (playing && !freelance) { @@ -2148,17 +2198,17 @@ void DivEngine::deepCloneOrder(bool where) { } void DivEngine::deleteOrder() { - if (song.ordersLen<=1) return; + if (curSubSong->ordersLen<=1) return; BUSY_BEGIN_SOFT; saveLock.lock(); for (int i=0; iordersLen; j++) { + curOrders->ord[i][j]=curOrders->ord[i][j+1]; } } - song.ordersLen--; + curSubSong->ordersLen--; saveLock.unlock(); - if (curOrder>=song.ordersLen) curOrder=song.ordersLen-1; + if (curOrder>=curSubSong->ordersLen) curOrder=curSubSong->ordersLen-1; if (playing && !freelance) { playSub(false); } @@ -2173,9 +2223,9 @@ void DivEngine::moveOrderUp() { } saveLock.lock(); for (int i=0; iord[i][curOrder]^=curOrders->ord[i][curOrder-1]; + curOrders->ord[i][curOrder-1]^=curOrders->ord[i][curOrder]; + curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder-1]; } saveLock.unlock(); curOrder--; @@ -2187,15 +2237,15 @@ void DivEngine::moveOrderUp() { void DivEngine::moveOrderDown() { BUSY_BEGIN_SOFT; - if (curOrder>=song.ordersLen-1) { + if (curOrder>=curSubSong->ordersLen-1) { BUSY_END; return; } saveLock.lock(); for (int i=0; iord[i][curOrder]^=curOrders->ord[i][curOrder+1]; + curOrders->ord[i][curOrder+1]^=curOrders->ord[i][curOrder]; + curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder+1]; } saveLock.unlock(); curOrder++; @@ -2208,12 +2258,12 @@ void DivEngine::moveOrderDown() { void DivEngine::exchangeIns(int one, int two) { for (int i=0; idata[k][2]==one) { - song.pat[i].data[j]->data[k][2]=two; - } else if (song.pat[i].data[j]->data[k][2]==two) { - song.pat[i].data[j]->data[k][2]=one; + if (curPat[i].data[j]==NULL) continue; + for (int k=0; kpatLen; k++) { + if (curPat[i].data[j]->data[k][2]==one) { + curPat[i].data[j]->data[k][2]=two; + } else if (curPat[i].data[j]->data[k][2]==two) { + curPat[i].data[j]->data[k][2]=one; } } } @@ -2417,7 +2467,7 @@ void DivEngine::autoNoteOffAll() { void DivEngine::setOrder(unsigned char order) { BUSY_BEGIN_SOFT; curOrder=order; - if (order>=song.ordersLen) curOrder=0; + if (order>=curSubSong->ordersLen) curOrder=0; if (playing && !freelance) { playSub(false); } @@ -2440,15 +2490,15 @@ void DivEngine::setSysFlags(int system, unsigned int flags, bool restart) { void DivEngine::setSongRate(float hz, bool pal) { BUSY_BEGIN; saveLock.lock(); - song.pal=!pal; - song.hz=hz; + curSubSong->pal=!pal; + curSubSong->hz=hz; // what? - song.customTempo=true; + curSubSong->customTempo=true; divider=60; - if (song.customTempo) { - divider=song.hz; + if (curSubSong->customTempo) { + divider=curSubSong->hz; } else { - if (song.pal) { + if (curSubSong->pal) { divider=60; } else { divider=50; @@ -2891,5 +2941,6 @@ bool DivEngine::quit() { if (yrw801ROM!=NULL) delete[] yrw801ROM; if (tg100ROM!=NULL) delete[] tg100ROM; if (mu5ROM!=NULL) delete[] mu5ROM; + song.unload(); return true; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 4e446721f..5bb77c7e6 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -45,8 +45,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev94" -#define DIV_ENGINE_VERSION 94 +#define DIV_VERSION "dev95" +#define DIV_ENGINE_VERSION 95 // for imports #define DIV_VERSION_MOD 0xff01 @@ -304,6 +304,7 @@ class DivEngine { bool hasLoadedSomething; int softLockCount; int subticks, ticks, curRow, curOrder, remainingLoops, nextSpeed; + size_t curSubSongIndex; double divider; int cycles; double clockDrift; @@ -411,8 +412,14 @@ class DivEngine { void swapChannels(int src, int dest); void stompChannel(int ch); + // change song (UNSAFE) + void changeSong(size_t songIndex); + public: DivSong song; + DivOrders* curOrders; + DivChannelData* curPat; + DivSubSong* curSubSong; DivInstrument* tempIns; DivSystem sysOfChan[DIV_MAX_CHANS]; int dispatchOfChan[DIV_MAX_CHANS]; @@ -603,6 +610,9 @@ class DivEngine { // get current row int getRow(); + // get current subsong + size_t getCurrentSubSong(); + // get speed 1 unsigned char getSpeed1(); @@ -799,6 +809,15 @@ class DivEngine { // public swap channels void swapChannelsP(int src, int dest); + // public change song + void changeSongP(size_t index); + + // add subsong + int addSubSong(); + + // remove subsong + bool removeSubSong(int index); + // change system void changeSystem(int index, DivSystem which, bool preserveOrder=true); @@ -900,6 +919,7 @@ class DivEngine { curOrder(0), remainingLoops(-1), nextSpeed(3), + curSubSongIndex(0), divider(60), cycles(0), clockDrift(0), @@ -935,6 +955,8 @@ class DivEngine { metroAmp(0.0f), metroVol(1.0f), totalProcessed(0), + curOrders(NULL), + curPat(NULL), tempIns(NULL), oscBuf{NULL,NULL}, oscSize(1), @@ -959,6 +981,8 @@ class DivEngine { sysFileMapFur[i]=DIV_SYSTEM_NULL; sysFileMapDMF[i]=DIV_SYSTEM_NULL; } + + changeSong(0); } }; #endif diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 8899f7980..c63601a4f 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -184,57 +184,57 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { logI("reading module data..."); if (ds.version>0x0c) { - ds.hilightA=reader.readC(); - ds.hilightB=reader.readC(); + ds.subsong[0]->hilightA=reader.readC(); + ds.subsong[0]->hilightB=reader.readC(); } - ds.timeBase=reader.readC(); - ds.speed1=reader.readC(); + ds.subsong[0]->timeBase=reader.readC(); + ds.subsong[0]->speed1=reader.readC(); if (ds.version>0x07) { - ds.speed2=reader.readC(); - ds.pal=reader.readC(); - ds.hz=(ds.pal)?60:50; - ds.customTempo=reader.readC(); + ds.subsong[0]->speed2=reader.readC(); + ds.subsong[0]->pal=reader.readC(); + ds.subsong[0]->hz=(ds.subsong[0]->pal)?60:50; + ds.subsong[0]->customTempo=reader.readC(); } else { - ds.speed2=ds.speed1; + ds.subsong[0]->speed2=ds.subsong[0]->speed1; } if (ds.version>0x0a) { String hz=reader.readString(3); - if (ds.customTempo) { + if (ds.subsong[0]->customTempo) { try { - ds.hz=std::stoi(hz); + ds.subsong[0]->hz=std::stoi(hz); } catch (std::exception& e) { logW("invalid custom Hz!"); - ds.hz=60; + ds.subsong[0]->hz=60; } } } if (ds.version>0x17) { - ds.patLen=reader.readI(); + ds.subsong[0]->patLen=reader.readI(); } else { - ds.patLen=(unsigned char)reader.readC(); + ds.subsong[0]->patLen=(unsigned char)reader.readC(); } - ds.ordersLen=(unsigned char)reader.readC(); + ds.subsong[0]->ordersLen=(unsigned char)reader.readC(); - if (ds.patLen<0) { + if (ds.subsong[0]->patLen<0) { logE("pattern length is negative!"); lastError="pattern lengrh is negative!"; delete[] file; return false; } - if (ds.patLen>256) { + if (ds.subsong[0]->patLen>256) { logE("pattern length is too large!"); lastError="pattern length is too large!"; delete[] file; return false; } - if (ds.ordersLen<0) { + if (ds.subsong[0]->ordersLen<0) { logE("song length is negative!"); lastError="song length is negative!"; delete[] file; return false; } - if (ds.ordersLen>127) { + if (ds.subsong[0]->ordersLen>127) { logE("song is too long!"); lastError="song is too long!"; delete[] file; @@ -242,54 +242,54 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } if (ds.version<20 && ds.version>3) { - ds.arpLen=reader.readC(); + ds.subsong[0]->arpLen=reader.readC(); } else { - ds.arpLen=1; + ds.subsong[0]->arpLen=1; } if (ds.system[0]==DIV_SYSTEM_YMU759) { - switch (ds.timeBase) { + switch (ds.subsong[0]->timeBase) { case 0: - ds.hz=248; + ds.subsong[0]->hz=248; break; case 1: - ds.hz=200; + ds.subsong[0]->hz=200; break; case 2: - ds.hz=100; + ds.subsong[0]->hz=100; break; case 3: - ds.hz=50; + ds.subsong[0]->hz=50; break; case 4: - ds.hz=25; + ds.subsong[0]->hz=25; break; case 5: - ds.hz=20; + ds.subsong[0]->hz=20; break; default: - ds.hz=248; + ds.subsong[0]->hz=248; break; } - ds.customTempo=true; - ds.timeBase=0; + ds.subsong[0]->customTempo=true; + ds.subsong[0]->timeBase=0; addWarning("Yamaha YMU759 emulation is incomplete! please migrate your song to the OPL3 system."); } logV("%x",reader.tell()); - logI("reading pattern matrix (%d * %d = %d)...",ds.ordersLen,getChannelCount(ds.system[0]),ds.ordersLen*getChannelCount(ds.system[0])); + logI("reading pattern matrix (%d * %d = %d)...",ds.subsong[0]->ordersLen,getChannelCount(ds.system[0]),ds.subsong[0]->ordersLen*getChannelCount(ds.system[0])); for (int i=0; i0x7f) { - logE("order at %d, %d out of range! (%d)",i,j,ds.orders.ord[i][j]); - lastError=fmt::sprintf("order at %d, %d out of range! (%d)",i,j,ds.orders.ord[i][j]); + for (int j=0; jordersLen; j++) { + ds.subsong[0]->orders.ord[i][j]=reader.readC(); + if (ds.subsong[0]->orders.ord[i][j]>0x7f) { + logE("order at %d, %d out of range! (%d)",i,j,ds.subsong[0]->orders.ord[i][j]); + lastError=fmt::sprintf("order at %d, %d out of range! (%d)",i,j,ds.subsong[0]->orders.ord[i][j]); delete[] file; return false; } if (ds.version>0x18) { // 1.1 pattern names - ds.pat[i].getPattern(j,true)->name=reader.readString((unsigned char)reader.readC()); + ds.subsong[0]->pat[i].getPattern(j,true)->name=reader.readString((unsigned char)reader.readC()); } } if (ds.version>0x03 && ds.version<0x06 && i<16) { @@ -637,9 +637,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { logV("%x",reader.tell()); - logI("reading patterns (%d channels, %d orders)...",getChannelCount(ds.system[0]),ds.ordersLen); + logI("reading patterns (%d channels, %d orders)...",getChannelCount(ds.system[0]),ds.subsong[0]->ordersLen); for (int i=0; ipat[i]; if (ds.version<0x0a) { chan.effectCols=1; } else { @@ -652,10 +652,10 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { delete[] file; return false; } - for (int j=0; jordersLen; j++) { + DivPattern* pat=chan.getPattern(ds.subsong[0]->orders.ord[i][j],true); if (ds.version>0x08) { // current pattern format - for (int k=0; kpatLen; k++) { // note pat->data[k][0]=reader.readS(); // octave @@ -723,7 +723,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } } else { // historic pattern format if (i<16) pat->data[0][2]=historicColIns[i]; - for (int k=0; kpatLen; k++) { // note pat->data[k][0]=reader.readC(); // octave @@ -905,6 +905,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { saveLock.lock(); song.unload(); song=ds; + changeSong(0); recalcChans(); saveLock.unlock(); BUSY_END; @@ -929,13 +930,16 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { unsigned int insPtr[256]; unsigned int wavePtr[256]; unsigned int samplePtr[256]; + unsigned int subSongPtr[256]; std::vector patPtr; + int numberOfSubSongs=0; char magic[5]; memset(magic,0,5); SafeReader reader=SafeReader(file,len); warnings=""; try { DivSong ds; + DivSubSong* subSong=ds.subsong[0]; if (!reader.seek(16,SEEK_SET)) { logE("premature end of file!"); @@ -1045,44 +1049,44 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } reader.readI(); - ds.timeBase=reader.readC(); - ds.speed1=reader.readC(); - ds.speed2=reader.readC(); - ds.arpLen=reader.readC(); - ds.hz=reader.readF(); - ds.pal=(ds.hz>=53); - ds.customTempo=true; + subSong->timeBase=reader.readC(); + subSong->speed1=reader.readC(); + subSong->speed2=reader.readC(); + subSong->arpLen=reader.readC(); + subSong->hz=reader.readF(); + subSong->pal=(subSong->hz>=53); + subSong->customTempo=true; - ds.patLen=reader.readS(); - ds.ordersLen=reader.readS(); + subSong->patLen=reader.readS(); + subSong->ordersLen=reader.readS(); - ds.hilightA=reader.readC(); - ds.hilightB=reader.readC(); + subSong->hilightA=reader.readC(); + subSong->hilightB=reader.readC(); ds.insLen=reader.readS(); ds.waveLen=reader.readS(); ds.sampleLen=reader.readS(); int numberOfPats=reader.readI(); - if (ds.patLen<0) { + if (subSong->patLen<0) { logE("pattern length is negative!"); lastError="pattern lengrh is negative!"; delete[] file; return false; } - if (ds.patLen>256) { + if (subSong->patLen>256) { logE("pattern length is too large!"); lastError="pattern length is too large!"; delete[] file; return false; } - if (ds.ordersLen<0) { + if (subSong->ordersLen<0) { logE("song length is negative!"); lastError="song length is negative!"; delete[] file; return false; } - if (ds.ordersLen>256) { + if (subSong->ordersLen>256) { logE("song is too long!"); lastError="song is too long!"; delete[] file; @@ -1295,18 +1299,18 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { reader.read(samplePtr,ds.sampleLen*4); for (int i=0; iordersLen); for (int i=0; iordersLen; j++) { + subSong->orders.ord[i][j]=reader.readC(); } } for (int i=0; i8) { - logE("channel %d has zero or too many effect columns! (%d)",i,ds.pat[i].effectCols); - lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,ds.pat[i].effectCols); + subSong->pat[i].effectCols=reader.readC(); + if (subSong->pat[i].effectCols<1 || subSong->pat[i].effectCols>8) { + logE("channel %d has zero or too many effect columns! (%d)",i,subSong->pat[i].effectCols); + lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,subSong->pat[i].effectCols); delete[] file; return false; } @@ -1314,25 +1318,25 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version>=39) { for (int i=0; ichanShow[i]=reader.readC(); } for (int i=0; ichanCollapse[i]=reader.readC(); } if (ds.version<92) { for (int i=0; i0) ds.chanCollapse[i]=3; + if (subSong->chanCollapse[i]>0) subSong->chanCollapse[i]=3; } } for (int i=0; ichanName[i]=reader.readString(); } for (int i=0; ichanShortName[i]=reader.readString(); } ds.notes=reader.readString(); @@ -1405,6 +1409,88 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } } + // subsongs + if (ds.version>=95) { + subSong->name=reader.readString(); + subSong->notes=reader.readString(); + numberOfSubSongs=(unsigned char)reader.readC(); + reader.readC(); // reserved + reader.readC(); + reader.readC(); + // pointers + for (int i=0; itimeBase=reader.readC(); + subSong->speed1=reader.readC(); + subSong->speed2=reader.readC(); + subSong->arpLen=reader.readC(); + subSong->hz=reader.readF(); + subSong->pal=(subSong->hz>=53); + subSong->customTempo=true; + + subSong->patLen=reader.readS(); + subSong->ordersLen=reader.readS(); + + subSong->hilightA=reader.readC(); + subSong->hilightB=reader.readC(); + + reader.readI(); // reserved + + subSong->name=reader.readString(); + subSong->notes=reader.readString(); + + logD("reading orders of subsong %d (%d)...",i+1,subSong->ordersLen); + for (int j=0; jordersLen; k++) { + subSong->orders.ord[j][k]=reader.readC(); + } + } + + for (int i=0; ipat[i].effectCols=reader.readC(); + } + + for (int i=0; ichanShow[i]=reader.readC(); + } + + for (int i=0; ichanCollapse[i]=reader.readC(); + } + + for (int i=0; ichanName[i]=reader.readString(); + } + + for (int i=0; ichanShortName[i]=reader.readString(); + } + } + } + // read instruments for (int i=0; i=95) { + subs=reader.readS(); + } else { + reader.readS(); + } + reader.readS(); - logD("- %d, %d",chan,index); + logD("- %d, %d, %d",subs,chan,index); if (chan<0 || chan>=tchans) { logE("pattern channel out of range!",i); @@ -1586,14 +1678,21 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { delete[] file; return false; } + if (subs<0 || subs>=(int)ds.subsong.size()) { + logE("pattern subsong out of range!",i); + lastError="pattern subsong out of range!"; + ds.unload(); + delete[] file; + return false; + } - DivPattern* pat=ds.pat[chan].getPattern(index,true); - for (int j=0; jpat[chan].getPattern(index,true); + for (int j=0; jpatLen; j++) { pat->data[j][0]=reader.readS(); pat->data[j][1]=reader.readS(); pat->data[j][2]=reader.readS(); pat->data[j][3]=reader.readS(); - for (int k=0; kpat[chan].effectCols; k++) { pat->data[j][4+(k<<1)]=reader.readS(); pat->data[j][5+(k<<1)]=reader.readS(); } @@ -1615,6 +1714,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { saveLock.lock(); song.unload(); song=ds; + changeSong(0); recalcChans(); saveLock.unlock(); BUSY_END; @@ -1740,8 +1840,8 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { ds.sampleLen=ds.sample.size(); // orders - ds.ordersLen=ordCount=reader.readC(); - if (ds.ordersLen<1 || ds.ordersLen>127) { + ds.subsong[0]->ordersLen=ordCount=reader.readC(); + if (ds.subsong[0]->ordersLen<1 || ds.subsong[0]->ordersLen>127) { logD("invalid order count!"); throw EndOfFileException(&reader,reader.tell()); } @@ -1761,7 +1861,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { unsigned char pat=reader.readC(); if (pat>patMax) patMax=pat; for (int j=0; jorders.ord[j][i]=pat; } } @@ -1778,7 +1878,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { } // patterns - ds.patLen=64; + ds.subsong[0]->patLen=64; for (int ch=0; chpat[ch].getPattern(pat,true); } for (int row=0; row<64; row++) { for (int ch=0; chdata; + auto* data=ds.subsong[0]->pat[ch].getPattern(pat,true)->data; short lastPitchEffect=-1; short lastEffectState[5]={-1,-1,-1,-1,-1}; short setEffectState[5]={-1,-1,-1,-1,-1}; @@ -1988,25 +2088,25 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { } } } - ds.pat[ch].effectCols=fxCols; + ds.subsong[0]->pat[ch].effectCols=fxCols; } - ds.pal=false; - ds.hz=50; - ds.customTempo=false; + ds.subsong[0]->pal=false; + ds.subsong[0]->hz=50; + ds.subsong[0]->customTempo=false; ds.systemLen=(chCount+3)/4; for(int i=0; i1 || bypassLimits)?2:0); // PAL } for(int i=0; ichanShow[i]=true; + ds.subsong[0]->chanName[i]=fmt::sprintf("Channel %d",i+1); + ds.subsong[0]->chanShortName[i]=fmt::sprintf("C%d",i+1); } for(int i=chCount; ipat[i].effectCols=1; + ds.subsong[0]->chanShow[i]=false; } // instrument creation @@ -2024,6 +2124,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { saveLock.lock(); song.unload(); song=ds; + changeSong(0); recalcChans(); saveLock.unlock(); BUSY_END; @@ -2118,8 +2219,8 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { newVibrato=reader.readI(); } if (blockVersion>=4) { - ds.hilightA=reader.readI(); - ds.hilightB=reader.readI(); + ds.subsong[0]->hilightA=reader.readI(); + ds.subsong[0]->hilightB=reader.readI(); } if (expansions&8) if (blockVersion>=5) { // N163 channels n163Chans=reader.readI(); @@ -2135,12 +2236,12 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { logV("custom Hz: %d",customHz); logV("new vibrato: %d",newVibrato); logV("N163 channels: %d",n163Chans); - logV("highlight 1: %d",ds.hilightA); - logV("highlight 2: %d",ds.hilightB); + logV("highlight 1: %d",ds.subsong[0]->hilightA); + logV("highlight 2: %d",ds.subsong[0]->hilightB); logV("split point: %d",speedSplitPoint); if (customHz!=0) { - ds.hz=customHz; + ds.subsong[0]->hz=customHz; } // initialize channels @@ -2201,7 +2302,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) { for (int j=0; j<=totalSongs; j++) { unsigned char effectCols=reader.readC(); if (j==0) { - ds.pat[i].effectCols=effectCols+1; + ds.subsong[0]->pat[i].effectCols=effectCols+1; } logV("- song %d has %d effect columns",j,effectCols); } @@ -2532,26 +2633,39 @@ bool DivEngine::load(unsigned char* f, size_t slen) { return false; } +struct PatToWrite { + unsigned short subsong, chan, pat; + PatToWrite(unsigned short s, unsigned short c, unsigned short p): + subsong(s), + chan(c), + pat(p) {} +}; + SafeWriter* DivEngine::saveFur(bool notPrimary) { saveLock.lock(); + std::vector subSongPtr; std::vector insPtr; std::vector wavePtr; std::vector samplePtr; std::vector patPtr; - size_t ptrSeek; + size_t ptrSeek, subSongPtrSeek; + size_t subSongIndex=0; + DivSubSong* subSong=song.subsong[subSongIndex]; warnings=""; // fail if values are out of range - if (song.ordersLen>256) { + /* + if (subSong->ordersLen>256) { logE("maximum song length is 256!"); lastError="maximum song length is 256"; return NULL; } - if (song.patLen>256) { + if (subSong->patLen>256) { logE("maximum pattern length is 256!"); lastError="maximum pattern length is 256"; return NULL; } + */ if (song.ins.size()>256) { logE("maximum number of instruments is 256!"); lastError="maximum number of instruments is 256"; @@ -2594,14 +2708,17 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { // high short is channel // low short is pattern number - std::vector patsToWrite; + std::vector patsToWrite; bool alreadyAdded[256]; for (int i=0; iordersLen; k++) { + if (alreadyAdded[subs->orders.ord[i][k]]) continue; + patsToWrite.push_back(PatToWrite(j,i,subs->orders.ord[i][k])); + alreadyAdded[subs->orders.ord[i][k]]=true; + } } } @@ -2609,15 +2726,15 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->write("INFO",4); w->writeI(0); - w->writeC(song.timeBase); - w->writeC(song.speed1); - w->writeC(song.speed2); - w->writeC(song.arpLen); - w->writeF(song.hz); - w->writeS(song.patLen); - w->writeS(song.ordersLen); - w->writeC(song.hilightA); - w->writeC(song.hilightB); + w->writeC(subSong->timeBase); + w->writeC(subSong->speed1); + w->writeC(subSong->speed2); + w->writeC(subSong->arpLen); + w->writeF(subSong->hz); + w->writeS(subSong->patLen); + w->writeS(subSong->ordersLen); + w->writeC(subSong->hilightA); + w->writeC(subSong->hilightB); w->writeS(song.insLen); w->writeS(song.waveLen); w->writeS(song.sampleLen); @@ -2694,29 +2811,29 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { } for (int i=0; iwriteC(song.orders.ord[i][j]); + for (int j=0; jordersLen; j++) { + w->writeC(subSong->orders.ord[i][j]); } } for (int i=0; iwriteC(song.pat[i].effectCols); + w->writeC(subSong->pat[i].effectCols); } for (int i=0; iwriteC(song.chanShow[i]); + w->writeC(subSong->chanShow[i]); } for (int i=0; iwriteC(song.chanCollapse[i]); + w->writeC(subSong->chanCollapse[i]); } for (int i=0; iwriteString(song.chanName[i],false); + w->writeString(subSong->chanName[i],false); } for (int i=0; iwriteString(song.chanShortName[i],false); + w->writeString(subSong->chanShortName[i],false); } w->writeString(song.notes,false); @@ -2741,6 +2858,67 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { for (int i=0; i<18; i++) { w->writeC(0); } + + // subsong list + w->writeString(subSong->name,false); + w->writeString(subSong->notes,false); + w->writeC((unsigned char)(song.subsong.size()-1)); + w->writeC(0); // reserved + w->writeC(0); + w->writeC(0); + subSongPtrSeek=w->tell(); + // subsong pointers (we'll seek here later) + for (size_t i=0; i<(song.subsong.size()-1); i++) { + w->writeI(0); + } + + /// SUBSONGS + for (subSongIndex=1; subSongIndextell()); + w->write("SONG",4); + w->writeI(0); + + w->writeC(subSong->timeBase); + w->writeC(subSong->speed1); + w->writeC(subSong->speed2); + w->writeC(subSong->arpLen); + w->writeF(subSong->hz); + w->writeS(subSong->patLen); + w->writeS(subSong->ordersLen); + w->writeC(subSong->hilightA); + w->writeC(subSong->hilightB); + w->writeI(0); // reserved + + w->writeString(subSong->name,false); + w->writeString(subSong->notes,false); + + for (int i=0; iordersLen; j++) { + w->writeC(subSong->orders.ord[i][j]); + } + } + + for (int i=0; iwriteC(subSong->pat[i].effectCols); + } + + for (int i=0; iwriteC(subSong->chanShow[i]); + } + + for (int i=0; iwriteC(subSong->chanCollapse[i]); + } + + for (int i=0; iwriteString(subSong->chanName[i],false); + } + + for (int i=0; iwriteString(subSong->chanShortName[i],false); + } + } /// INSTRUMENT for (int i=0; i>16].getPattern(i&0xffff,false); + for (PatToWrite& i: patsToWrite) { + DivPattern* pat=song.subsong[i.subsong]->pat[i.chan].getPattern(i.pat,false); patPtr.push_back(w->tell()); w->write("PATR",4); w->writeI(0); - w->writeS(i>>16); - w->writeS(i&0xffff); + w->writeS(i.chan); + w->writeS(i.pat); + w->writeS(i.subsong); - w->writeI(0); // reserved + w->writeS(0); // reserved - for (int j=0; jpatLen; j++) { w->writeS(pat->data[j][0]); // note w->writeS(pat->data[j][1]); // octave w->writeS(pat->data[j][2]); // instrument w->writeS(pat->data[j][3]); // volume - w->write(&pat->data[j][4],2*song.pat[i>>16].effectCols*2); // effects + w->write(&pat->data[j][4],2*song.subsong[i.subsong]->pat[i.chan].effectCols*2); // effects } w->writeString(pat->name,false); @@ -2805,21 +2984,29 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeI(insPtr[i]); } - // wavetable pointers (we'll seek here later) + // wavetable pointers for (int i=0; iwriteI(wavePtr[i]); } - // sample pointers (we'll seek here later) + // sample pointers for (int i=0; iwriteI(samplePtr[i]); } - // pattern pointers (we'll seek here later) + // pattern pointers for (int i: patPtr) { w->writeI(i); } + /// SUBSONG POINTERS + w->seek(subSongPtrSeek,SEEK_SET); + + // subsong pointers + for (size_t i=0; i<(song.subsong.size()-1); i++) { + w->writeI(subSongPtr[i]); + } + saveLock.unlock(); return w; } @@ -2890,7 +3077,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { return NULL; } // fail if values are out of range - if (song.ordersLen>127) { + if (curSubSong->ordersLen>127) { logE("maximum .dmf song length is 127!"); lastError="maximum .dmf song length is 127"; return NULL; @@ -2906,10 +3093,10 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { return NULL; } for (int i=0; i0x7f) { - logE("order %d, %d is out of range (0-127)!",song.orders.ord[i][j]); - lastError=fmt::sprintf("order %d, %d is out of range (0-127)",song.orders.ord[i][j]); + for (int j=0; jordersLen; j++) { + if (curOrders->ord[i][j]>0x7f) { + logE("order %d, %d is out of range (0-127)!",curOrders->ord[i][j]); + lastError=fmt::sprintf("order %d, %d is out of range (0-127)",curOrders->ord[i][j]); return NULL; } } @@ -2952,31 +3139,35 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { // song info w->writeString(song.name,true); w->writeString(song.author,true); - w->writeC(song.hilightA); - w->writeC(song.hilightB); + w->writeC(curSubSong->hilightA); + w->writeC(curSubSong->hilightB); - w->writeC(song.timeBase); - w->writeC(song.speed1); - w->writeC(song.speed2); - w->writeC(song.pal); - w->writeC(song.customTempo); + w->writeC(curSubSong->timeBase); + w->writeC(curSubSong->speed1); + w->writeC(curSubSong->speed2); + w->writeC(curSubSong->pal); + w->writeC(curSubSong->customTempo); char customHz[4]; memset(customHz,0,4); - snprintf(customHz,4,"%d",(int)song.hz); + snprintf(customHz,4,"%d",(int)curSubSong->hz); w->write(customHz,3); - w->writeI(song.patLen); - w->writeC(song.ordersLen); + w->writeI(curSubSong->patLen); + w->writeC(curSubSong->ordersLen); for (int i=0; iwriteC(song.orders.ord[i][j]); + for (int j=0; jordersLen; j++) { + w->writeC(curOrders->ord[i][j]); if (version>=25) { - DivPattern* pat=song.pat[i].getPattern(j,false); + DivPattern* pat=curPat[i].getPattern(j,false); w->writeString(pat->name,true); } } } + if (song.subsong.size()>1) { + addWarning("only the currently selected subsong will be saved"); + } + if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) { addWarning("absolute duty/cutoff macro not available in .dmf!"); addWarning("duty precision will be lost"); @@ -3138,15 +3329,15 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { } for (int i=0; iwriteC(song.pat[i].effectCols); + w->writeC(curPat[i].effectCols); - for (int j=0; jordersLen; j++) { + DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][j],false); + for (int k=0; kpatLen; k++) { w->writeS(pat->data[k][0]); // note w->writeS(pat->data[k][1]); // octave w->writeS(pat->data[k][3]); // volume - w->write(&pat->data[k][4],2*song.pat[i].effectCols*2); // effects + w->write(&pat->data[k][4],2*curPat[i].effectCols*2); // effects w->writeS(pat->data[k][2]); // instrument } } diff --git a/src/engine/pattern.cpp b/src/engine/pattern.cpp index 4f4663cae..0a561376b 100644 --- a/src/engine/pattern.cpp +++ b/src/engine/pattern.cpp @@ -49,7 +49,7 @@ void DivChannelData::wipePatterns() { } } -void DivPattern::copyOn(DivPattern *dest) { +void DivPattern::copyOn(DivPattern* dest) { dest->name=name; memcpy(dest->data,data,sizeof(data)); } diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 6a27dc65e..7a2356a7e 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -30,7 +30,7 @@ constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0; void DivEngine::nextOrder() { curRow=0; if (repeatPattern) return; - if (++curOrder>=song.ordersLen) { + if (++curOrder>=curSubSong->ordersLen) { endOfSong=true; curOrder=0; } @@ -266,11 +266,11 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char void DivEngine::processRow(int i, bool afterDelay) { int whatOrder=afterDelay?chan[i].delayOrder:curOrder; int whatRow=afterDelay?chan[i].delayRow:curRow; - DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][whatOrder],false); + DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][whatOrder],false); // pre effects if (!afterDelay) { bool returnAfterPre=false; - for (int j=0; jdata[whatRow][4+(j<<1)]; short effectVal=pat->data[whatRow][5+(j<<1)]; @@ -290,7 +290,7 @@ void DivEngine::processRow(int i, bool afterDelay) { } break; case 0x0d: // next order - if (changeOrd<0 && (curOrder<(song.ordersLen-1) || !song.ignoreJumpAtEnd)) { + if (changeOrd<0 && (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) { changeOrd=-2; changePos=effectVal; } @@ -405,7 +405,7 @@ void DivEngine::processRow(int i, bool afterDelay) { bool panChanged=false; // effects - for (int j=0; jdata[whatRow][4+(j<<1)]; short effectVal=pat->data[whatRow][5+(j<<1)]; @@ -548,7 +548,7 @@ void DivEngine::processRow(int i, bool afterDelay) { break; case 0xe0: // arp speed if (effectVal>0) { - song.arpLen=effectVal; + curSubSong->arpLen=effectVal; } break; case 0xe1: // portamento up @@ -727,7 +727,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].noteOnInhibit=false; // post effects - for (int j=0; jdata[whatRow][4+(j<<1)]; short effectVal=pat->data[whatRow][5+(j<<1)]; @@ -745,10 +745,10 @@ void DivEngine::nextRow() { strcpy(pb1,""); strcpy(pb3,""); for (int i=0; iord[i][curOrder]); strcat(pb1,pb); - DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][curOrder],false); + DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][curOrder],false); snprintf(pb2,4095,"\x1b[37m %s", formatNote(pat->data[curRow][0],pat->data[curRow][1])); strcat(pb3,pb2); @@ -764,7 +764,7 @@ void DivEngine::nextRow() { snprintf(pb2,4095,"\x1b[0;36m%.2x",pat->data[curRow][2]); strcat(pb3,pb2); } - for (int j=0; jdata[curRow][4+(j<<1)]==-1) { strcat(pb3,"\x1b[m--"); } else { @@ -796,32 +796,32 @@ void DivEngine::nextRow() { if (changeOrd==-2) changeOrd=curOrder+1; if (changeOrd<=curOrder) endOfSong=true; curOrder=changeOrd; - if (curOrder>=song.ordersLen) { + if (curOrder>=curSubSong->ordersLen) { curOrder=0; endOfSong=true; } changeOrd=-1; } if (haltOn==DIV_HALT_PATTERN) halted=true; - } else if (playing) if (++curRow>=song.patLen) { + } else if (playing) if (++curRow>=curSubSong->patLen) { nextOrder(); if (haltOn==DIV_HALT_PATTERN) halted=true; } if (song.brokenSpeedSel) { - if ((song.patLen&1) && curOrder&1) { - ticks=((curRow&1)?speed2:speed1)*(song.timeBase+1); + if ((curSubSong->patLen&1) && curOrder&1) { + ticks=((curRow&1)?speed2:speed1)*(curSubSong->timeBase+1); nextSpeed=(curRow&1)?speed1:speed2; } else { - ticks=((curRow&1)?speed1:speed2)*(song.timeBase+1); + ticks=((curRow&1)?speed1:speed2)*(curSubSong->timeBase+1); nextSpeed=(curRow&1)?speed2:speed1; } } else { if (speedAB) { - ticks=speed2*(song.timeBase+1); + ticks=speed2*(curSubSong->timeBase+1); nextSpeed=speed1; } else { - ticks=speed1*(song.timeBase+1); + ticks=speed1*(curSubSong->timeBase+1); nextSpeed=speed2; } speedAB=!speedAB; @@ -829,7 +829,7 @@ void DivEngine::nextRow() { // post row details for (int i=0; iord[i][curOrder],false); if (!(pat->data[curRow][0]==0 && pat->data[curRow][1]==0)) { if (pat->data[curRow][0]!=100 && pat->data[curRow][0]!=101 && pat->data[curRow][0]!=102) { if (!chan[i].legato) { @@ -838,7 +838,7 @@ void DivEngine::nextRow() { if (song.oneTickCut) { bool doPrepareCut=true; - for (int j=0; jdata[curRow][4+(j<<1)]==0x03) { doPrepareCut=false; break; @@ -1007,7 +1007,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } if (chan[i].arp!=0 && !chan[i].arpYield && chan[i].portaSpeed<1) { if (--chan[i].arpTicks<1) { - chan[i].arpTicks=song.arpLen; + chan[i].arpTicks=curSubSong->arpLen; chan[i].arpStage++; if (chan[i].arpStage>2) chan[i].arpStage=0; switch (chan[i].arpStage) { @@ -1048,7 +1048,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } } - if (consoleMode && subticks<=1) fprintf(stderr,"\x1b[2K> %d:%.2d:%.2d.%.2d %.2x/%.2x:%.3d/%.3d %4dcmd/s\x1b[G",totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000,curOrder,song.ordersLen,curRow,song.patLen,cmdsPerSecond); + if (consoleMode && subticks<=1) fprintf(stderr,"\x1b[2K> %d:%.2d:%.2d.%.2d %.2x/%.2x:%.3d/%.3d %4dcmd/s\x1b[G",totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000,curOrder,curSubSong->ordersLen,curRow,curSubSong->patLen,cmdsPerSecond); } if (haltOn==DIV_HALT_TICK) halted=true; @@ -1240,11 +1240,11 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi if (!freelance && stepPlay!=-1 && subticks==1) { unsigned int realPos=size-(runLeftG>>MASTER_CLOCK_PREC); if (realPos>=size) realPos=size-1; - if (song.hilightA>0) { - if ((curRow%song.hilightA)==0 && ticks==1) metroTick[realPos]=1; + if (curSubSong->hilightA>0) { + if ((curRow%curSubSong->hilightA)==0 && ticks==1) metroTick[realPos]=1; } - if (song.hilightB>0) { - if ((curRow%song.hilightB)==0 && ticks==1) metroTick[realPos]=2; + if (curSubSong->hilightB>0) { + if ((curRow%curSubSong->hilightB)==0 && ticks==1) metroTick[realPos]=2; } } if (nextTick()) { diff --git a/src/engine/song.cpp b/src/engine/song.cpp index 4c142c5f3..216a1bc4d 100644 --- a/src/engine/song.cpp +++ b/src/engine/song.cpp @@ -19,9 +19,7 @@ #include "song.h" -DivOrders emptyOrders; - -void DivSong::clearSongData() { +void DivSubSong::clearData() { for (int i=0; iclearData(); + delete i; + } + subsong.clear(); + subsong.push_back(new DivSubSong); +} + void DivSong::clearInstruments() { for (DivInstrument* i: ins) { delete i; @@ -73,7 +80,9 @@ void DivSong::unload() { sample.clear(); sampleLen=0; - for (int i=0; iclearData(); + delete i; } + subsong.clear(); } diff --git a/src/engine/song.h b/src/engine/song.h index b89d56ad0..219225e8f 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -110,8 +110,6 @@ enum DivSystem { DIV_SYSTEM_DUMMY }; -extern DivOrders emptyOrders; - struct DivSubSong { String name, notes; unsigned char hilightA, hilightB; @@ -126,8 +124,23 @@ struct DivSubSong { bool chanShow[DIV_MAX_CHANS]; unsigned char chanCollapse[DIV_MAX_CHANS]; + String chanName[DIV_MAX_CHANS]; + String chanShortName[DIV_MAX_CHANS]; - DivSubSong() { + void clearData(); + + DivSubSong(): + hilightA(4), + hilightB(16), + timeBase(0), + speed1(6), + speed2(6), + arpLen(1), + pal(true), + customTempo(false), + hz(60.0), + patLen(64), + ordersLen(1) { for (int i=0; i ins; - DivChannelData* pat; std::vector wave; std::vector sample; - std::vector subsongs; - - bool* chanShow; - unsigned char* chanCollapse; + std::vector subsong; DivInstrument nullIns, nullInsOPLL, nullInsOPL, nullInsQSound; DivWavetable nullWave; @@ -435,17 +434,6 @@ struct DivSong { manInfo(""), createdDate(""), revisionDate(""), - hilightA(4), - hilightB(16), - timeBase(0), - speed1(6), - speed2(6), - arpLen(1), - pal(true), - customTempo(false), - hz(60.0), - patLen(64), - ordersLen(1), insLen(0), waveLen(0), sampleLen(0), @@ -491,11 +479,7 @@ struct DivSong { systemPan[i]=0; systemFlags[i]=0; } - subsongs.push_back(DivSubSong()); - chanShow=subsongs[0].chanShow; - chanCollapse=subsongs[0].chanCollapse; - orders=&subsongs[0].orders; - pat=subsongs[0].pat; + subsong.push_back(new DivSubSong); system[0]=DIV_SYSTEM_YM2612; system[1]=DIV_SYSTEM_SMS; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 2c90142e0..10f8907f4 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -283,7 +283,7 @@ bool DivEngine::isSTDSystem(DivSystem sys) { const char* DivEngine::getChannelName(int chan) { if (chan<0 || chan>chans) return "??"; - if (!song.chanName[chan].empty()) return song.chanName[chan].c_str(); + if (!curSubSong->chanName[chan].empty()) return curSubSong->chanName[chan].c_str(); if (sysDefs[sysOfChan[chan]]==NULL) return "??"; const char* ret=sysDefs[sysOfChan[chan]]->chanNames[dispatchChanOfChan[chan]]; @@ -293,7 +293,7 @@ const char* DivEngine::getChannelName(int chan) { const char* DivEngine::getChannelShortName(int chan) { if (chan<0 || chan>chans) return "??"; - if (!song.chanShortName[chan].empty()) return song.chanShortName[chan].c_str(); + if (!curSubSong->chanShortName[chan].empty()) return curSubSong->chanShortName[chan].c_str(); if (sysDefs[sysOfChan[chan]]==NULL) return "??"; const char* ret=sysDefs[sysOfChan[chan]]->chanShortNames[dispatchChanOfChan[chan]]; diff --git a/src/gui/actionUtil.h b/src/gui/actionUtil.h index 752a54423..81685af48 100644 --- a/src/gui/actionUtil.h +++ b/src/gui/actionUtil.h @@ -20,7 +20,7 @@ #define DETERMINE_FIRST \ int firstChannel=0; \ for (int i=0; igetTotalChannelCount(); i++) { \ - if (e->song.chanShow[i]) { \ + if (e->curSubSong->chanShow[i]) { \ firstChannel=i; \ break; \ } \ @@ -29,7 +29,7 @@ #define DETERMINE_LAST \ int lastChannel=0; \ for (int i=e->getTotalChannelCount()-1; i>=0; i--) { \ - if (e->song.chanShow[i]) { \ + if (e->curSubSong->chanShow[i]) { \ lastChannel=i+1; \ break; \ } \ diff --git a/src/gui/channels.cpp b/src/gui/channels.cpp index 397ac2f65..c88075611 100644 --- a/src/gui/channels.cpp +++ b/src/gui/channels.cpp @@ -38,7 +38,7 @@ void FurnaceGUI::drawChannels() { ImGui::PushID(i); ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::Checkbox("##Visible",&e->song.chanShow[i]); + ImGui::Checkbox("##Visible",&e->curSubSong->chanShow[i]); ImGui::SameLine(); ImGui::BeginDisabled(i==0); if (ImGui::Button(ICON_FA_CHEVRON_UP)) { @@ -53,10 +53,10 @@ void FurnaceGUI::drawChannels() { ImGui::EndDisabled(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputTextWithHint("##ChanName",e->getChannelName(i),&e->song.chanName[i]); + ImGui::InputTextWithHint("##ChanName",e->getChannelName(i),&e->curSubSong->chanName[i]); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputTextWithHint("##ChanShortName",e->getChannelShortName(i),&e->song.chanShortName[i]); + ImGui::InputTextWithHint("##ChanShortName",e->getChannelShortName(i),&e->curSubSong->chanShortName[i]); ImGui::PopID(); } ImGui::EndTable(); diff --git a/src/gui/cursor.cpp b/src/gui/cursor.cpp index 972beee02..244880735 100644 --- a/src/gui/cursor.cpp +++ b/src/gui/cursor.cpp @@ -32,7 +32,7 @@ void FurnaceGUI::startSelection(int xCoarse, int xFine, int y, bool fullRow) { selStart.xCoarse=firstChannel; selStart.xFine=0; selEnd.xCoarse=lastChannel-1; - selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2; + selEnd.xFine=2+e->curPat[selEnd.xCoarse].effectCols*2; selStart.y=y; selEnd.y=y; } else { @@ -56,7 +56,7 @@ void FurnaceGUI::updateSelection(int xCoarse, int xFine, int y, bool fullRow) { if (selectingFull) { DETERMINE_LAST; selEnd.xCoarse=lastChannel-1; - selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2; + selEnd.xFine=2+e->curPat[selEnd.xCoarse].effectCols*2; selEnd.y=y; } else { selEnd.xCoarse=xCoarse; @@ -94,21 +94,21 @@ void FurnaceGUI::finishSelection() { if (selStart.xCoarse<0) selStart.xCoarse=0; if (selStart.xCoarse>=chanCount) selStart.xCoarse=chanCount-1; if (selStart.y<0) selStart.y=0; - if (selStart.y>=e->song.patLen) selStart.y=e->song.patLen-1; + if (selStart.y>=e->curSubSong->patLen) selStart.y=e->curSubSong->patLen-1; if (selEnd.xCoarse<0) selEnd.xCoarse=0; if (selEnd.xCoarse>=chanCount) selEnd.xCoarse=chanCount-1; if (selEnd.y<0) selEnd.y=0; - if (selEnd.y>=e->song.patLen) selEnd.y=e->song.patLen-1; + if (selEnd.y>=e->curSubSong->patLen) selEnd.y=e->curSubSong->patLen-1; if (cursor.xCoarse<0) cursor.xCoarse=0; if (cursor.xCoarse>=chanCount) cursor.xCoarse=chanCount-1; if (cursor.y<0) cursor.y=0; - if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1; + if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1; - if (e->song.chanCollapse[selStart.xCoarse]==3) { + if (e->curSubSong->chanCollapse[selStart.xCoarse]==3) { selStart.xFine=0; } - if (e->song.chanCollapse[selEnd.xCoarse] && selEnd.xFine>=(3-e->song.chanCollapse[selEnd.xCoarse])) { - selEnd.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2; + if (e->curSubSong->chanCollapse[selEnd.xCoarse] && selEnd.xFine>=(3-e->curSubSong->chanCollapse[selEnd.xCoarse])) { + selEnd.xFine=2+e->curPat[cursor.xCoarse].effectCols*2; } e->setMidiBaseChan(cursor.xCoarse); @@ -126,7 +126,7 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { demandScrollX=true; if (x>0) { for (int i=0; i=(e->song.chanCollapse[cursor.xCoarse]?(4-e->song.chanCollapse[cursor.xCoarse]):(3+e->song.pat[cursor.xCoarse].effectCols*2))) { + if (++cursor.xFine>=(e->curSubSong->chanCollapse[cursor.xCoarse]?(4-e->curSubSong->chanCollapse[cursor.xCoarse]):(3+e->curPat[cursor.xCoarse].effectCols*2))) { cursor.xFine=0; if (++cursor.xCoarse>=lastChannel) { if (settings.wrapHorizontal!=0 && !select) { @@ -134,10 +134,10 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { if (settings.wrapHorizontal==2) y++; } else { cursor.xCoarse=lastChannel-1; - cursor.xFine=e->song.chanCollapse[cursor.xCoarse]?(3-e->song.chanCollapse[cursor.xCoarse]):(2+e->song.pat[cursor.xCoarse].effectCols*2); + cursor.xFine=e->curSubSong->chanCollapse[cursor.xCoarse]?(3-e->curSubSong->chanCollapse[cursor.xCoarse]):(2+e->curPat[cursor.xCoarse].effectCols*2); } } else { - while (!e->song.chanShow[cursor.xCoarse]) { + while (!e->curSubSong->chanShow[cursor.xCoarse]) { cursor.xCoarse++; if (cursor.xCoarse>=e->getTotalChannelCount()) break; } @@ -150,21 +150,21 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { if (--cursor.xCoarsesong.pat[cursor.xCoarse].effectCols*2; + cursor.xFine=2+e->curPat[cursor.xCoarse].effectCols*2; if (settings.wrapHorizontal==2) y--; } else { cursor.xCoarse=firstChannel; cursor.xFine=0; } } else { - while (!e->song.chanShow[cursor.xCoarse]) { + while (!e->curSubSong->chanShow[cursor.xCoarse]) { cursor.xCoarse--; if (cursor.xCoarse<0) break; } - if (e->song.chanCollapse[cursor.xCoarse]) { - cursor.xFine=3-e->song.chanCollapse[cursor.xCoarse]; + if (e->curSubSong->chanCollapse[cursor.xCoarse]) { + cursor.xFine=3-e->curSubSong->chanCollapse[cursor.xCoarse]; } else { - cursor.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2; + cursor.xFine=2+e->curPat[cursor.xCoarse].effectCols*2; } } } @@ -175,18 +175,18 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { if (y>0) { for (int i=0; i=e->song.patLen) { + if (cursor.y>=e->curSubSong->patLen) { if (settings.wrapVertical!=0 && !select) { cursor.y=0; if (settings.wrapVertical==2) { - if ((!e->isPlaying() || !followPattern) && curOrder<(e->song.ordersLen-1)) { + if ((!e->isPlaying() || !followPattern) && curOrder<(e->curSubSong->ordersLen-1)) { setOrder(curOrder+1); } else { - cursor.y=e->song.patLen-1; + cursor.y=e->curSubSong->patLen-1; } } } else { - cursor.y=e->song.patLen-1; + cursor.y=e->curSubSong->patLen-1; } } } @@ -195,7 +195,7 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { cursor.y--; if (cursor.y<0) { if (settings.wrapVertical!=0 && !select) { - cursor.y=e->song.patLen-1; + cursor.y=e->curSubSong->patLen-1; if (settings.wrapVertical==2) { if ((!e->isPlaying() || !followPattern) && curOrder>0) { setOrder(curOrder-1); @@ -229,7 +229,7 @@ void FurnaceGUI::moveCursorPrevChannel(bool overflow) { do { cursor.xCoarse--; if (cursor.xCoarse<0) break; - } while (!e->song.chanShow[cursor.xCoarse]); + } while (!e->curSubSong->chanShow[cursor.xCoarse]); if (cursor.xCoarse=e->getTotalChannelCount()) break; - } while (!e->song.chanShow[cursor.xCoarse]); + } while (!e->curSubSong->chanShow[cursor.xCoarse]); if (cursor.xCoarse>=lastChannel) { if (overflow) { cursor.xCoarse=firstChannel; @@ -290,14 +290,14 @@ void FurnaceGUI::moveCursorTop(bool select) { void FurnaceGUI::moveCursorBottom(bool select) { finishSelection(); curNibble=false; - if (cursor.y==e->song.patLen-1) { + if (cursor.y==e->curSubSong->patLen-1) { DETERMINE_LAST; cursor.xCoarse=lastChannel-1; if (cursor.xCoarse<0) cursor.xCoarse=0; - cursor.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2; + cursor.xFine=2+e->curPat[cursor.xCoarse].effectCols*2; demandScrollX=true; } else { - cursor.y=e->song.patLen-1; + cursor.y=e->curSubSong->patLen-1; } if (!select) { selStart=cursor; @@ -310,7 +310,7 @@ void FurnaceGUI::moveCursorBottom(bool select) { void FurnaceGUI::editAdvance() { finishSelection(); cursor.y+=editStep; - if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1; + if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1; selStart=cursor; selEnd=cursor; updateScroll(cursor.y); diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 8a71d9a38..9a364e233 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -470,7 +470,7 @@ void FurnaceGUI::doAction(int what) { e->unmuteAll(); break; case GUI_ACTION_PAT_NEXT_ORDER: - if (curOrdersong.ordersLen-1) { + if (curOrdercurSubSong->ordersLen-1) { setOrder(curOrder+1); } break; @@ -481,21 +481,21 @@ void FurnaceGUI::doAction(int what) { break; case GUI_ACTION_PAT_COLLAPSE: if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - if (e->song.chanCollapse[cursor.xCoarse]==0) { - e->song.chanCollapse[cursor.xCoarse]=3; - } else if (e->song.chanCollapse[cursor.xCoarse]>0) { - e->song.chanCollapse[cursor.xCoarse]--; + if (e->curSubSong->chanCollapse[cursor.xCoarse]==0) { + e->curSubSong->chanCollapse[cursor.xCoarse]=3; + } else if (e->curSubSong->chanCollapse[cursor.xCoarse]>0) { + e->curSubSong->chanCollapse[cursor.xCoarse]--; } break; case GUI_ACTION_PAT_INCREASE_COLUMNS: if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - e->song.pat[cursor.xCoarse].effectCols++; - if (e->song.pat[cursor.xCoarse].effectCols>8) e->song.pat[cursor.xCoarse].effectCols=8; + e->curPat[cursor.xCoarse].effectCols++; + if (e->curPat[cursor.xCoarse].effectCols>8) e->curPat[cursor.xCoarse].effectCols=8; break; case GUI_ACTION_PAT_DECREASE_COLUMNS: if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - e->song.pat[cursor.xCoarse].effectCols--; - if (e->song.pat[cursor.xCoarse].effectCols<1) e->song.pat[cursor.xCoarse].effectCols=1; + e->curPat[cursor.xCoarse].effectCols--; + if (e->curPat[cursor.xCoarse].effectCols<1) e->curPat[cursor.xCoarse].effectCols=1; break; case GUI_ACTION_PAT_INTERPOLATE: doInterpolate(); @@ -1196,7 +1196,7 @@ void FurnaceGUI::doAction(int what) { } break; case GUI_ACTION_ORDERS_DOWN: - if (curOrdersong.ordersLen-1) { + if (curOrdercurSubSong->ordersLen-1) { setOrder(curOrder+1); } break; @@ -1209,7 +1209,7 @@ void FurnaceGUI::doAction(int what) { orderCursor=firstChannel; break; } - } while (!e->song.chanShow[orderCursor]); + } while (!e->curSubSong->chanShow[orderCursor]); break; } case GUI_ACTION_ORDERS_RIGHT: { @@ -1221,20 +1221,20 @@ void FurnaceGUI::doAction(int what) { orderCursor=lastChannel-1; break; } - } while (!e->song.chanShow[orderCursor]); + } while (!e->curSubSong->chanShow[orderCursor]); break; } case GUI_ACTION_ORDERS_INCREASE: { if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break; - if (e->song.orders.ord[orderCursor][curOrder]<0x7f) { - e->song.orders.ord[orderCursor][curOrder]++; + if (e->curOrders->ord[orderCursor][curOrder]<0x7f) { + e->curOrders->ord[orderCursor][curOrder]++; } break; } case GUI_ACTION_ORDERS_DECREASE: { if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break; - if (e->song.orders.ord[orderCursor][curOrder]>0) { - e->song.orders.ord[orderCursor][curOrder]--; + if (e->curOrders->ord[orderCursor][curOrder]>0) { + e->curOrders->ord[orderCursor][curOrder]--; } break; } diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index 722c38f2a..199a7de00 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -45,7 +45,7 @@ void FurnaceGUI::drawEditControls() { ImGui::Text("Edit Step"); ImGui::SameLine(); if (ImGui::InputInt("##EditStep",&editStep,1,1)) { - if (editStep>=e->song.patLen) editStep=e->song.patLen-1; + if (editStep>=e->curSubSong->patLen) editStep=e->curSubSong->patLen-1; if (editStep<0) editStep=0; if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { @@ -148,7 +148,7 @@ void FurnaceGUI::drawEditControls() { ImGui::SameLine(); ImGui::SetNextItemWidth(96.0f*dpiScale); if (ImGui::InputInt("##EditStep",&editStep,1,1)) { - if (editStep>=e->song.patLen) editStep=e->song.patLen-1; + if (editStep>=e->curSubSong->patLen) editStep=e->curSubSong->patLen-1; if (editStep<0) editStep=0; if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { @@ -217,7 +217,7 @@ void FurnaceGUI::drawEditControls() { ImGui::Text("Step"); ImGui::SetNextItemWidth(avail); if (ImGui::InputInt("##EditStep",&editStep,0,0)) { - if (editStep>=e->song.patLen) editStep=e->song.patLen-1; + if (editStep>=e->curSubSong->patLen) editStep=e->curSubSong->patLen-1; if (editStep<0) editStep=0; if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { @@ -317,7 +317,7 @@ void FurnaceGUI::drawEditControls() { ImGui::SetCursorPosX(cursor); ImGui::SetNextItemWidth(avail); if (ImGui::InputInt("##EditStep",&editStep,1,1)) { - if (editStep>=e->song.patLen) editStep=e->song.patLen-1; + if (editStep>=e->curSubSong->patLen) editStep=e->curSubSong->patLen-1; if (editStep<0) editStep=0; if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index 8832b7fbb..ea12eb591 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -44,8 +44,8 @@ const char* noteNameNormal(short note, short octave) { void FurnaceGUI::prepareUndo(ActionType action) { switch (action) { case GUI_UNDO_CHANGE_ORDER: - oldOrders=e->song.orders; - oldOrdersLen=e->song.ordersLen; + memcpy(&oldOrders,e->curOrders,sizeof(DivOrders)); + oldOrdersLen=e->curSubSong->ordersLen; break; case GUI_UNDO_PATTERN_EDIT: case GUI_UNDO_PATTERN_DELETE: @@ -63,7 +63,7 @@ void FurnaceGUI::prepareUndo(ActionType action) { case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: for (int i=0; igetTotalChannelCount(); i++) { - e->song.pat[i].getPattern(e->song.orders.ord[i][curOrder],false)->copyOn(oldPat[i]); + e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false)->copyOn(oldPat[i]); } break; } @@ -78,18 +78,19 @@ void FurnaceGUI::makeUndo(ActionType action) { s.selEnd=selEnd; s.order=curOrder; s.nibble=curNibble; + size_t subSong=e->getCurrentSubSong(); switch (action) { case GUI_UNDO_CHANGE_ORDER: for (int i=0; isong.orders.ord[i][j]) { - s.ord.push_back(UndoOrderData(i,j,oldOrders.ord[i][j],e->song.orders.ord[i][j])); + if (oldOrders.ord[i][j]!=e->curOrders->ord[i][j]) { + s.ord.push_back(UndoOrderData(subSong,i,j,oldOrders.ord[i][j],e->curOrders->ord[i][j])); } } } s.oldOrdersLen=oldOrdersLen; - s.newOrdersLen=e->song.ordersLen; - if (oldOrdersLen!=e->song.ordersLen) { + s.newOrdersLen=e->curSubSong->ordersLen; + if (oldOrdersLen!=e->curSubSong->ordersLen) { doPush=true; } if (!s.ord.empty()) { @@ -112,11 +113,11 @@ void FurnaceGUI::makeUndo(ActionType action) { case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: for (int i=0; igetTotalChannelCount(); i++) { - DivPattern* p=e->song.pat[i].getPattern(e->song.orders.ord[i][curOrder],false); - for (int j=0; jsong.patLen; j++) { + DivPattern* p=e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false); + for (int j=0; jcurSubSong->patLen; j++) { for (int k=0; k<32; k++) { if (p->data[j][k]!=oldPat[i]->data[j][k]) { - s.pat.push_back(UndoPatternData(i,e->song.orders.ord[i][curOrder],j,k,oldPat[i]->data[j][k],p->data[j][k])); + s.pat.push_back(UndoPatternData(subSong,i,e->curOrders->ord[i][curOrder],j,k,oldPat[i]->data[j][k],p->data[j][k])); } } } @@ -137,15 +138,15 @@ void FurnaceGUI::makeUndo(ActionType action) { void FurnaceGUI::doSelectAll() { finishSelection(); curNibble=false; - if (selStart.xFine==0 && selEnd.xFine==2+e->song.pat[selEnd.xCoarse].effectCols*2) { - if (selStart.y==0 && selEnd.y==e->song.patLen-1) { // select entire pattern + if (selStart.xFine==0 && selEnd.xFine==2+e->curPat[selEnd.xCoarse].effectCols*2) { + if (selStart.y==0 && selEnd.y==e->curSubSong->patLen-1) { // select entire pattern selStart.xCoarse=0; selStart.xFine=0; selEnd.xCoarse=e->getTotalChannelCount()-1; - selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2; + selEnd.xFine=2+e->curPat[selEnd.xCoarse].effectCols*2; } else { // select entire column selStart.y=0; - selEnd.y=e->song.patLen-1; + selEnd.y=e->curSubSong->patLen-1; } } else { int selStartX=0; @@ -153,26 +154,26 @@ void FurnaceGUI::doSelectAll() { // find row position for (SelectionPoint i; i.xCoarse!=selStart.xCoarse || i.xFine!=selStart.xFine; selStartX++) { i.xFine++; - if (i.xFine>=3+e->song.pat[i.xCoarse].effectCols*2) { + if (i.xFine>=3+e->curPat[i.xCoarse].effectCols*2) { i.xFine=0; i.xCoarse++; } } for (SelectionPoint i; i.xCoarse!=selEnd.xCoarse || i.xFine!=selEnd.xFine; selEndX++) { i.xFine++; - if (i.xFine>=3+e->song.pat[i.xCoarse].effectCols*2) { + if (i.xFine>=3+e->curPat[i.xCoarse].effectCols*2) { i.xFine=0; i.xCoarse++; } } float aspect=float(selEndX-selStartX+1)/float(selEnd.y-selStart.y+1); - if (aspect<=1.0f && !(selStart.y==0 && selEnd.y==e->song.patLen-1)) { // up-down + if (aspect<=1.0f && !(selStart.y==0 && selEnd.y==e->curSubSong->patLen-1)) { // up-down selStart.y=0; - selEnd.y=e->song.patLen-1; + selEnd.y=e->curSubSong->patLen-1; } else { // left-right selStart.xFine=0; - selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2; + selEnd.xFine=2+e->curPat[selEnd.xCoarse].effectCols*2; } } } @@ -198,9 +199,9 @@ void FurnaceGUI::doDelete() { int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.patLen; j++) { - if (jsong.patLen-1) { + for (int j=selStart.y; jcurSubSong->patLen; j++) { + if (jcurSubSong->patLen-1) { if (iFine==0) { pat->data[j][iFine]=pat->data[j+1][iFine]; } @@ -267,11 +268,11 @@ void FurnaceGUI::doInsert() { int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.patLen-1; j>=selStart.y; j--) { + for (int j=e->curSubSong->patLen-1; j>=selStart.y; j--) { if (j==selStart.y) { if (iFine==0) { pat->data[j][iFine]=0; @@ -299,9 +300,9 @@ void FurnaceGUI::doTranspose(int amount, OperationMask& mask) { int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsedata[j][0],pat->data[j][1]); if (cut) { @@ -432,7 +433,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { int j=cursor.y; char note[4]; - for (size_t i=2; isong.patLen; i++) { + for (size_t i=2; icurSubSong->patLen; i++) { size_t charPos=0; int iCoarse=cursor.xCoarse; int iFine=(startOff>2 && cursor.xFine>2)?(((cursor.xFine-1)&(~1))|1):startOff; @@ -440,10 +441,10 @@ void FurnaceGUI::doPaste(PasteMode mode) { String& line=data[i]; while (charPossong.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); if (line[charPos]=='|') { iCoarse++; - if (iCoarsesong.chanShow[iCoarse]) { + if (iCoarsecurSubSong->chanShow[iCoarse]) { iCoarse++; if (iCoarse>=lastChannel) break; } @@ -530,7 +531,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { break; } if (mode!=GUI_PASTE_MODE_MIX_BG || pat->data[j][iFine+1]==-1) { - if (iFine<(3+e->song.pat[iCoarse].effectCols*2)) pat->data[j][iFine+1]=val; + if (iFine<(3+e->curPat[iCoarse].effectCols*2)) pat->data[j][iFine+1]=val; } } } @@ -543,7 +544,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { break; } j++; - if (mode==GUI_PASTE_MODE_OVERFLOW && j>=e->song.patLen && curOrdersong.ordersLen-1) { + if (mode==GUI_PASTE_MODE_OVERFLOW && j>=e->curSubSong->patLen && curOrdercurSubSong->ordersLen-1) { j=0; curOrder++; } @@ -554,7 +555,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { } if (settings.cursorPastePos) { cursor.y=j; - if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1; + if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1; updateScroll(cursor.y); } @@ -567,8 +568,8 @@ void FurnaceGUI::doChangeIns(int ins) { int iCoarse=selStart.xCoarse; for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); + if (!e->curSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); for (int j=selStart.y; j<=selEnd.y; j++) { if (pat->data[j][2]!=-1 || !(pat->data[j][0]==0 && pat->data[j][1]==0)) { pat->data[j][2]=ins; @@ -587,9 +588,9 @@ void FurnaceGUI::doInterpolate() { int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsesong.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); - for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarsecurSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsedata[j][iFine+1]; } for (int j=0; j<=(selEnd.y-selStart.y)*multiplier; j++) { - if ((j+selStart.y)>=e->song.patLen) break; + if ((j+selStart.y)>=e->curSubSong->patLen) break; if ((j%multiplier)!=0) { if (iFine==0) { pat->data[j+selStart.y][0]=0; @@ -921,9 +922,10 @@ void FurnaceGUI::doUndo() { switch (us.type) { case GUI_UNDO_CHANGE_ORDER: - e->song.ordersLen=us.oldOrdersLen; + e->curSubSong->ordersLen=us.oldOrdersLen; for (UndoOrderData& i: us.ord) { - e->song.orders.ord[i.chan][i.ord]=i.oldVal; + e->changeSongP(i.subSong); + e->curOrders->ord[i.chan][i.ord]=i.oldVal; } break; case GUI_UNDO_PATTERN_EDIT: @@ -942,7 +944,8 @@ void FurnaceGUI::doUndo() { case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: for (UndoPatternData& i: us.pat) { - DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true); + e->changeSongP(i.subSong); + DivPattern* p=e->curPat[i.chan].getPattern(i.pat,true); p->data[i.row][i.col]=i.oldVal; } if (!e->isPlaying() || !followPattern) { @@ -967,9 +970,10 @@ void FurnaceGUI::doRedo() { switch (us.type) { case GUI_UNDO_CHANGE_ORDER: - e->song.ordersLen=us.newOrdersLen; + e->curSubSong->ordersLen=us.newOrdersLen; for (UndoOrderData& i: us.ord) { - e->song.orders.ord[i.chan][i.ord]=i.newVal; + e->changeSongP(i.subSong); + e->curOrders->ord[i.chan][i.ord]=i.newVal; } break; case GUI_UNDO_PATTERN_EDIT: @@ -988,7 +992,8 @@ void FurnaceGUI::doRedo() { case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: for (UndoPatternData& i: us.pat) { - DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true); + e->changeSongP(i.subSong); + DivPattern* p=e->curPat[i.chan].getPattern(i.pat,true); p->data[i.row][i.col]=i.newVal; } if (!e->isPlaying()) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 00f0da2b3..634482e91 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -793,9 +793,9 @@ void FurnaceGUI::prepareLayout() { } float FurnaceGUI::calcBPM(int s1, int s2, float hz) { - float hl=e->song.hilightA; + float hl=e->curSubSong->hilightA; if (hl<=0.0f) hl=4.0f; - float timeBase=e->song.timeBase+1; + float timeBase=e->curSubSong->timeBase+1; float speedSum=s1+s2; if (timeBase<1.0f) timeBase=1.0f; if (speedSum<1.0f) speedSum=1.0f; @@ -857,7 +857,7 @@ void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode, bool autoNote) { } void FurnaceGUI::noteInput(int num, int key, int vol) { - DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],true); + DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true); prepareUndo(GUI_UNDO_PATTERN_EDIT); @@ -901,7 +901,7 @@ void FurnaceGUI::noteInput(int num, int key, int vol) { } void FurnaceGUI::valueInput(int num, bool direct, int target) { - DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],true); + DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true); prepareUndo(GUI_UNDO_PATTERN_EDIT); if (target==-1) target=cursor.xFine+1; if (direct) { @@ -965,7 +965,7 @@ void FurnaceGUI::valueInput(int num, bool direct, int target) { editAdvance(); } else { if (settings.effectCursorDir==2) { - if (++cursor.xFine>=(3+(e->song.pat[cursor.xCoarse].effectCols*2))) { + if (++cursor.xFine>=(3+(e->curPat[cursor.xCoarse].effectCols*2))) { cursor.xFine=3; } } else { @@ -1123,7 +1123,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { int num=valueKeys.at(ev.key.keysym.sym); if (orderCursor>=0 && orderCursorgetTotalChannelCount()) { e->lockSave([this,num]() { - e->song.orders.ord[orderCursor][curOrder]=((e->song.orders.ord[orderCursor][curOrder]<<4)|num); + e->curOrders->ord[orderCursor][curOrder]=((e->curOrders->ord[orderCursor][curOrder]<<4)|num); }); if (orderEditMode==2 || orderEditMode==3) { curNibble=!curNibble; @@ -1132,7 +1132,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { orderCursor++; if (orderCursor>=e->getTotalChannelCount()) orderCursor=0; } else if (orderEditMode==3) { - if (curOrdersong.ordersLen-1) { + if (curOrdercurSubSong->ordersLen-1) { setOrder(curOrder+1); } } @@ -2031,7 +2031,7 @@ void FurnaceGUI::editOptions(bool topMenu) { ImGui::PopFont(); ImGui::SameLine(); if (ImGui::Button("Set")) { - DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],true); + DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true); latchIns=pat->data[cursor.y][2]; latchVol=pat->data[cursor.y][3]; latchEffect=pat->data[cursor.y][4]; @@ -2817,12 +2817,12 @@ bool FurnaceGUI::loop() { if (e->isPlaying()) { int totalTicks=e->getTotalTicks(); int totalSeconds=e->getTotalSeconds(); - ImGui::Text("| Speed %d:%d @ %gHz (%g BPM) | Order %d/%d | Row %d/%d | %d:%.2d:%.2d.%.2d",e->getSpeed1(),e->getSpeed2(),e->getCurHz(),calcBPM(e->getSpeed1(),e->getSpeed2(),e->getCurHz()),e->getOrder(),e->song.ordersLen,e->getRow(),e->song.patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000); + ImGui::Text("| Speed %d:%d @ %gHz (%g BPM) | Order %d/%d | Row %d/%d | %d:%.2d:%.2d.%.2d",e->getSpeed1(),e->getSpeed2(),e->getCurHz(),calcBPM(e->getSpeed1(),e->getSpeed2(),e->getCurHz()),e->getOrder(),e->curSubSong->ordersLen,e->getRow(),e->curSubSong->patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000); } else { bool hasInfo=false; String info; if (cursor.xCoarse>=0 && cursor.xCoarsegetTotalChannelCount()) { - DivPattern* p=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],false); + DivPattern* p=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],false); if (cursor.xFine>=0) switch (cursor.xFine) { case 0: // note if (p->data[cursor.y][0]>0) { @@ -2883,6 +2883,7 @@ bool FurnaceGUI::loop() { ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0); + drawSubSongs(); drawPattern(); drawEditControls(); drawSongInfo(); @@ -3491,7 +3492,7 @@ bool FurnaceGUI::loop() { stop(); e->lockEngine([this]() { for (int i=0; igetTotalChannelCount(); i++) { - DivPattern* pat=e->song.pat[i].getPattern(e->song.orders.ord[i][curOrder],true); + DivPattern* pat=e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],true); memset(pat->data,-1,256*32*sizeof(short)); for (int j=0; j<256; j++) { pat->data[j][0]=0; @@ -3650,6 +3651,7 @@ bool FurnaceGUI::init() { regViewOpen=e->getConfBool("regViewOpen",false); logOpen=e->getConfBool("logOpen",false); effectListOpen=e->getConfBool("effectListOpen",false); + subSongsOpen=e->getConfBool("subSongsOpen",true); tempoView=e->getConfBool("tempoView",true); waveHex=e->getConfBool("waveHex",false); @@ -3841,6 +3843,7 @@ bool FurnaceGUI::finish() { e->setConf("regViewOpen",regViewOpen); e->setConf("logOpen",logOpen); e->setConf("effectListOpen",effectListOpen); + e->setConf("subSongsOpen",subSongsOpen); // commit last window size e->setConf("lastWindowWidth",scrW); @@ -3961,6 +3964,7 @@ FurnaceGUI::FurnaceGUI(): logOpen(false), effectListOpen(false), chanOscOpen(false), + subSongsOpen(true), /* editControlsDocked(false), ordersDocked(false), @@ -3988,6 +3992,7 @@ FurnaceGUI::FurnaceGUI(): logDocked(false), effectListDocked(false), chanOscDocked(false), + subSongsDocked(false), */ selecting(false), selectingFull(false), diff --git a/src/gui/gui.h b/src/gui/gui.h index 4a5045129..b976a7cb7 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -231,7 +231,8 @@ enum FurnaceGUIWindows { GUI_WINDOW_REGISTER_VIEW, GUI_WINDOW_LOG, GUI_WINDOW_EFFECT_LIST, - GUI_WINDOW_CHAN_OSC + GUI_WINDOW_CHAN_OSC, + GUI_WINDOW_SUBSONGS }; enum FurnaceGUIFileDialogs { @@ -337,6 +338,7 @@ enum FurnaceGUIActions { GUI_ACTION_WINDOW_LOG, GUI_ACTION_WINDOW_EFFECT_LIST, GUI_ACTION_WINDOW_CHAN_OSC, + GUI_ACTION_WINDOW_SUBSONGS, GUI_ACTION_COLLAPSE_WINDOW, GUI_ACTION_CLOSE_WINDOW, @@ -550,9 +552,10 @@ enum ActionType { }; struct UndoPatternData { - int chan, pat, row, col; + int subSong, chan, pat, row, col; short oldVal, newVal; - UndoPatternData(int c, int p, int r, int co, short v1, short v2): + UndoPatternData(int s, int c, int p, int r, int co, short v1, short v2): + subSong(s), chan(c), pat(p), row(r), @@ -562,9 +565,10 @@ struct UndoPatternData { }; struct UndoOrderData { - int chan, ord; + int subSong, chan, ord; unsigned char oldVal, newVal; - UndoOrderData(int c, int o, unsigned char v1, unsigned char v2): + UndoOrderData(int s, int c, int o, unsigned char v1, unsigned char v2): + subSong(s), chan(c), ord(o), oldVal(v1), @@ -1010,12 +1014,14 @@ class FurnaceGUI { bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen; bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen; bool pianoOpen, notesOpen, channelsOpen, regViewOpen, logOpen, effectListOpen, chanOscOpen; + bool subSongsOpen; /* there ought to be a better way... bool editControlsDocked, ordersDocked, insListDocked, songInfoDocked, patternDocked, insEditDocked; bool waveListDocked, waveEditDocked, sampleListDocked, sampleEditDocked, aboutDocked, settingsDocked; bool mixerDocked, debugDocked, inspectorDocked, oscDocked, volMeterDocked, statsDocked, compatFlagsDocked; bool pianoDocked, notesDocked, channelsDocked, regViewDocked, logDocked, effectListDocked, chanOscDocked; + bool subSongsDocked; */ SelectionPoint selStart, selEnd, cursor; @@ -1246,6 +1252,7 @@ class FurnaceGUI { void drawNewSong(); void drawLog(); void drawEffectList(); + void drawSubSongs(); void parseKeybinds(); void promptKey(int which); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 0afee14ca..31aad6432 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -485,6 +485,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("WINDOW_CHANNELS", "Channels", 0), D("WINDOW_REGISTER_VIEW", "Register View", 0), D("WINDOW_LOG", "Log Viewer", 0), + D("WINDOW_SUBSONGS", "Subsongs", 0), D("EFFECT_LIST", "Effect List", 0), D("WINDOW_CHAN_OSC", "Oscilloscope (per-channel)", 0), diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index 2311a3980..e9886a1ae 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -37,7 +37,7 @@ void FurnaceGUI::drawOrders() { ImGui::SetColumnWidth(-1,regionX-24.0f*dpiScale); int displayChans=0; for (int i=0; igetTotalChannelCount(); i++) { - if (e->song.chanShow[i]) displayChans++; + if (e->curSubSong->chanShow[i]) displayChans++; } ImGui::PushFont(patFont); bool tooSmall=((displayChans+1)>((ImGui::GetContentRegionAvail().x)/(ImGui::CalcTextSize("AA").x+2.0*ImGui::GetStyle().ItemInnerSpacing.x))); @@ -56,12 +56,12 @@ void FurnaceGUI::drawOrders() { ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]); for (int i=0; igetTotalChannelCount(); i++) { - if (!e->song.chanShow[i]) continue; + if (!e->curSubSong->chanShow[i]) continue; ImGui::TableNextColumn(); ImGui::Text("%s",e->getChannelShortName(i)); } ImGui::PopStyleColor(); - for (int i=0; isong.ordersLen; i++) { + for (int i=0; icurSubSong->ordersLen; i++) { ImGui::TableNextRow(0,lineHeight); if (oldOrder1==i) ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_ORDER_ACTIVE])); ImGui::TableNextColumn(); @@ -84,16 +84,16 @@ void FurnaceGUI::drawOrders() { } ImGui::PopStyleColor(); for (int j=0; jgetTotalChannelCount(); j++) { - if (!e->song.chanShow[j]) continue; + if (!e->curSubSong->chanShow[j]) continue; ImGui::TableNextColumn(); - DivPattern* pat=e->song.pat[j].getPattern(e->song.orders.ord[j][i],false); + DivPattern* pat=e->curPat[j].getPattern(e->curOrders->ord[j][i],false); /*if (!pat->name.empty()) { snprintf(selID,4096,"%s##O_%.2x_%.2x",pat->name.c_str(),j,i); } else {*/ - snprintf(selID,4096,"%.2X##O_%.2x_%.2x",e->song.orders.ord[j][i],j,i); + snprintf(selID,4096,"%.2X##O_%.2x_%.2x",e->curOrders->ord[j][i],j,i); //} - ImGui::PushStyleColor(ImGuiCol_Text,(curOrder==i || e->song.orders.ord[j][i]==e->song.orders.ord[j][curOrder])?uiColors[GUI_COLOR_ORDER_SIMILAR]:uiColors[GUI_COLOR_ORDER_INACTIVE]); + ImGui::PushStyleColor(ImGuiCol_Text,(curOrder==i || e->curOrders->ord[j][i]==e->curOrders->ord[j][curOrder])?uiColors[GUI_COLOR_ORDER_SIMILAR]:uiColors[GUI_COLOR_ORDER_INACTIVE]); if (ImGui::Selectable(selID,(orderEditMode!=0 && curOrder==i && orderCursor==j))) { if (curOrder==i) { if (orderEditMode==0) { @@ -101,10 +101,10 @@ void FurnaceGUI::drawOrders() { e->lockSave([this,i,j]() { if (changeAllOrders) { for (int k=0; kgetTotalChannelCount(); k++) { - if (e->song.orders.ord[k][i]<0xff) e->song.orders.ord[k][i]++; + if (e->curOrders->ord[k][i]<0xff) e->curOrders->ord[k][i]++; } } else { - if (e->song.orders.ord[j][i]<0xff) e->song.orders.ord[j][i]++; + if (e->curOrders->ord[j][i]<0xff) e->curOrders->ord[j][i]++; } }); e->walkSong(loopOrder,loopRow,loopEnd); @@ -137,10 +137,10 @@ void FurnaceGUI::drawOrders() { e->lockSave([this,i,j]() { if (changeAllOrders) { for (int k=0; kgetTotalChannelCount(); k++) { - if (e->song.orders.ord[k][i]>0) e->song.orders.ord[k][i]--; + if (e->curOrders->ord[k][i]>0) e->curOrders->ord[k][i]--; } } else { - if (e->song.orders.ord[j][i]>0) e->song.orders.ord[j][i]--; + if (e->curOrders->ord[j][i]>0) e->curOrders->ord[j][i]--; } }); e->walkSong(loopOrder,loopRow,loopEnd); diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index ae9bedb2c..b7ca4bed9 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -43,21 +43,21 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int return; } // check if we are in range - if (ord<0 || ord>=e->song.ordersLen) { + if (ord<0 || ord>=e->curSubSong->ordersLen) { return; } - if (i<0 || i>=e->song.patLen) { + if (i<0 || i>=e->curSubSong->patLen) { return; } bool isPushing=false; ImVec4 activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE]; ImVec4 inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE]; ImVec4 rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX]; - if (e->song.hilightB>0 && !(i%e->song.hilightB)) { + if (e->curSubSong->hilightB>0 && !(i%e->curSubSong->hilightB)) { activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE_HI2]; inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE_HI2]; rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX_HI2]; - } else if (e->song.hilightA>0 && !(i%e->song.hilightA)) { + } else if (e->curSubSong->hilightA>0 && !(i%e->curSubSong->hilightA)) { activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE_HI1]; inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE_HI1]; rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX_HI1]; @@ -68,9 +68,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_EDITING])); } else if (isPlaying && oldRow==i && ord==e->getOrder()) { ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PLAY_HEAD])); - } else if (e->song.hilightB>0 && !(i%e->song.hilightB)) { + } else if (e->curSubSong->hilightB>0 && !(i%e->curSubSong->hilightB)) { ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_2])); - } else if (e->song.hilightA>0 && !(i%e->song.hilightA)) { + } else if (e->curSubSong->hilightA>0 && !(i%e->curSubSong->hilightA)) { ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_1])); } } else { @@ -79,9 +79,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_EDITING])); } else if (isPlaying && oldRow==i && ord==e->getOrder()) { ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PLAY_HEAD])); - } else if (e->song.hilightB>0 && !(i%e->song.hilightB)) { + } else if (e->curSubSong->hilightB>0 && !(i%e->curSubSong->hilightB)) { ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_2])); - } else if (e->song.hilightA>0 && !(i%e->song.hilightA)) { + } else if (e->curSubSong->hilightA>0 && !(i%e->curSubSong->hilightA)) { ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_1])); } else { isPushing=false; @@ -106,7 +106,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int // for each column for (int j=0; j