From c5786b61fbac360e5af0552e49d61cf7ef61b67d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 15 May 2022 01:42:49 -0500 Subject: [PATCH] dev95 - multiple songs in a single file (READ) experimental feature! proceed with caution. if you experience song corruption or crashes, report issue immediately! files with multiple songs will be readable in older versions of Furnace, but only the first song will be read in those versions. issue #199 --- CMakeLists.txt | 1 + papers/format.md | 89 +++++-- src/engine/engine.cpp | 233 +++++++++++------- src/engine/engine.h | 28 ++- src/engine/fileOps.cpp | 501 +++++++++++++++++++++++++++------------ src/engine/pattern.cpp | 2 +- src/engine/playback.cpp | 50 ++-- src/engine/song.cpp | 19 +- src/engine/song.h | 54 ++--- src/engine/sysDef.cpp | 4 +- src/gui/actionUtil.h | 4 +- src/gui/channels.cpp | 6 +- src/gui/cursor.cpp | 54 ++--- src/gui/doAction.cpp | 32 +-- src/gui/editControls.cpp | 8 +- src/gui/editing.cpp | 157 ++++++------ src/gui/gui.cpp | 27 ++- src/gui/gui.h | 17 +- src/gui/guiConst.cpp | 1 + src/gui/orders.cpp | 22 +- src/gui/pattern.cpp | 80 +++---- src/gui/settings.cpp | 1 + src/gui/songInfo.cpp | 35 +-- src/gui/subSongs.cpp | 94 ++++++++ 24 files changed, 974 insertions(+), 545 deletions(-) create mode 100644 src/gui/subSongs.cpp 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