diff --git a/CMakeLists.txt b/CMakeLists.txt index 52cef63c1..fe9118685 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -354,6 +354,7 @@ src/engine/platform/c64.cpp src/engine/platform/arcade.cpp src/engine/platform/tx81z.cpp src/engine/platform/ym2203.cpp +src/engine/platform/ym2203ext.cpp src/engine/platform/ym2608.cpp src/engine/platform/ym2610.cpp src/engine/platform/ym2610ext.cpp @@ -444,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 e6d35e832..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 @@ -215,6 +220,7 @@ size | description | - 0xb5: tildearrow Sound Unit - 8 channels | - 0xb6: OPN extended - 9 channels | - 0xb7: PC-98 extended - 19 channels + | - 0xb8: YMZ280B - 8 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - 0xfd: Dummy System - 8 channels @@ -257,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) @@ -291,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 @@ -743,6 +798,8 @@ size | description 1 | depth | - 0: ZX Spectrum overlay drum (1-bit) | - 1: 1-bit NES DPCM (1-bit) + | - 2: AICA ADPCM + | - 3: YMZ ADPCM | - 4: QSound ADPCM | - 5: ADPCM-A | - 6: ADPCM-B @@ -769,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 d9c23d845..0a6fc7e90 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; @@ -1594,6 +1644,7 @@ void DivEngine::unmuteAll() { } int DivEngine::addInstrument(int refChan) { + if (song.ins.size()>=256) return -1; BUSY_BEGIN; DivInstrument* ins=new DivInstrument; int insCount=(int)song.ins.size(); @@ -1624,6 +1675,10 @@ int DivEngine::addInstrument(int refChan) { } int DivEngine::addInstrumentPtr(DivInstrument* which) { + if (song.ins.size()>=256) { + delete which; + return -1; + } BUSY_BEGIN; saveLock.lock(); song.ins.push_back(which); @@ -1654,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]--; } } } @@ -1668,6 +1723,7 @@ void DivEngine::delInstrument(int index) { } int DivEngine::addWave() { + if (song.wave.size()>=256) return -1; BUSY_BEGIN; saveLock.lock(); DivWavetable* wave=new DivWavetable; @@ -1680,37 +1736,48 @@ int DivEngine::addWave() { } bool DivEngine::addWaveFromFile(const char* path) { + if (song.wave.size()>=256) { + lastError="too many wavetables!"; + return false; + } FILE* f=ps_fopen(path,"rb"); if (f==NULL) { + lastError=fmt::sprintf("%s",strerror(errno)); return false; } unsigned char* buf; ssize_t len; if (fseek(f,0,SEEK_END)!=0) { fclose(f); + lastError=fmt::sprintf("could not seek to end: %s",strerror(errno)); return false; } len=ftell(f); if (len<0) { fclose(f); + lastError=fmt::sprintf("could not determine file size: %s",strerror(errno)); return false; } if (len==(SIZE_MAX>>1)) { fclose(f); + lastError="file size is invalid!"; return false; } if (len==0) { fclose(f); + lastError="file is empty"; return false; } if (fseek(f,0,SEEK_SET)!=0) { fclose(f); + lastError=fmt::sprintf("could not seek to beginning: %s",strerror(errno)); return false; } buf=new unsigned char[len]; if (fread(buf,1,len,f)!=(size_t)len) { logW("did not read entire wavetable file buffer!"); delete[] buf; + lastError=fmt::sprintf("could not read entire file: %s",strerror(errno)); return false; } fclose(f); @@ -1790,6 +1857,7 @@ bool DivEngine::addWaveFromFile(const char* path) { } catch (EndOfFileException& e) { delete wave; delete[] buf; + lastError="premature end of file"; return false; } @@ -1816,6 +1884,7 @@ void DivEngine::delWave(int index) { } int DivEngine::addSample() { + if (song.sample.size()>=256) return -1; BUSY_BEGIN; saveLock.lock(); DivSample* sample=new DivSample; @@ -1830,6 +1899,10 @@ int DivEngine::addSample() { } int DivEngine::addSampleFromFile(const char* path) { + if (song.sample.size()>=256) { + lastError="too many samples!"; + return -1; + } BUSY_BEGIN; warnings=""; @@ -2025,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++) { @@ -2050,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) { @@ -2074,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; @@ -2102,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) { @@ -2125,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); } @@ -2150,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--; @@ -2164,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++; @@ -2185,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; } } } @@ -2394,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); } @@ -2417,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; @@ -2868,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 60f1c520b..a6e502953 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; @@ -414,8 +415,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]; @@ -606,6 +613,9 @@ class DivEngine { // get current row int getRow(); + // get current subsong + size_t getCurrentSubSong(); + // get speed 1 unsigned char getSpeed1(); @@ -802,6 +812,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); @@ -903,6 +922,7 @@ class DivEngine { curOrder(0), remainingLoops(-1), nextSpeed(3), + curSubSongIndex(0), divider(60), cycles(0), clockDrift(0), @@ -938,6 +958,8 @@ class DivEngine { metroAmp(0.0f), metroVol(1.0f), totalProcessed(0), + curOrders(NULL), + curPat(NULL), tempIns(NULL), oscBuf{NULL,NULL}, oscSize(1), @@ -962,6 +984,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 99b46319d..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(); - if (ds.version>0x05) { - ds.speed2=reader.readC(); - ds.pal=reader.readC(); - ds.hz=(ds.pal)?60:50; - ds.customTempo=reader.readC(); + ds.subsong[0]->timeBase=reader.readC(); + ds.subsong[0]->speed1=reader.readC(); + if (ds.version>0x07) { + 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 @@ -788,7 +788,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { pitch=reader.readC(); vol=reader.readC(); } - if (ds.version<=0x05) { + if (ds.version<=0x08) { sample->rate=ymuSampleRate*400; } if (ds.version>0x15) { @@ -798,15 +798,15 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { sample->depth=16; } } else { - if (ds.version>0x05) { + if (ds.version>0x08) { sample->depth=16; } else { // it appears samples were stored as ADPCM back then - sample->depth=6; + sample->depth=3; } } if (length>0) { - if (ds.version>0x05) { + if (ds.version>0x08) { if (ds.version<0x0b) { data=new short[1+(length/2)]; reader.read(data,length); @@ -842,8 +842,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { delete[] data; } else { - // ADPCM? - // it appears to be a slightly modified version of ADPCM-B! + // YMZ ADPCM adpcmData=new unsigned char[length]; logV("%x",reader.tell()); reader.read(adpcmData,length); @@ -854,7 +853,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { logE("%d: error while initializing sample!",i); } - memcpy(sample->dataB,adpcmData,length); + memcpy(sample->dataZ,adpcmData,length); delete[] adpcmData; } } @@ -906,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; @@ -930,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!"); @@ -1046,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; @@ -1296,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; } @@ -1315,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(); @@ -1406,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); @@ -1587,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(); } @@ -1616,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; @@ -1741,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()); } @@ -1762,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; } } @@ -1779,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}; @@ -1989,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 @@ -2025,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; @@ -2119,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(); @@ -2136,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 @@ -2202,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); } @@ -2533,15 +2633,55 @@ 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(); - int insPtr[256]; - int wavePtr[256]; - int samplePtr[256]; + 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 (subSong->ordersLen>256) { + logE("maximum song length is 256!"); + lastError="maximum song length is 256"; + return NULL; + } + 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"; + return NULL; + } + if (song.wave.size()>256) { + logE("maximum number of wavetables is 256!"); + lastError="maximum number of wavetables is 256"; + return NULL; + } + if (song.sample.size()>256) { + logE("maximum number of samples is 256!"); + lastError="maximum number of samples is 256"; + return NULL; + } + if (!notPrimary) { song.isDMF=false; song.version=DIV_ENGINE_VERSION; @@ -2568,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; + } } } @@ -2583,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); @@ -2668,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); @@ -2715,25 +2858,86 @@ 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; itell(); + insPtr.push_back(w->tell()); ins->putInsData(w); } /// WAVETABLE for (int i=0; itell(); + wavePtr.push_back(w->tell()); wave->putWaveData(w); } /// SAMPLE for (int i=0; itell(); + samplePtr.push_back(w->tell()); w->write("SMPL",4); w->writeI(0); @@ -2750,23 +2954,24 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { } /// PATTERN - for (int i: patsToWrite) { - DivPattern* pat=song.pat[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); @@ -2779,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; } @@ -2864,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; @@ -2880,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; } } @@ -2926,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"); @@ -3112,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/platform/ym2203ext.cpp b/src/engine/platform/ym2203ext.cpp index 440dce6a0..7948d51e7 100644 --- a/src/engine/platform/ym2203ext.cpp +++ b/src/engine/platform/ym2203ext.cpp @@ -17,20 +17,20 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "ym2610bext.h" +#include "ym2203ext.h" #include "../engine.h" #include #include "ym2610shared.h" #include "fmshared_OPN.h" -int DivPlatformYM2610BExt::dispatch(DivCommand c) { +int DivPlatformYM2203Ext::dispatch(DivCommand c) { if (c.chan<2) { - return DivPlatformYM2610B::dispatch(c); + return DivPlatformYM2203::dispatch(c); } if (c.chan>5) { c.chan-=3; - return DivPlatformYM2610B::dispatch(c); + return DivPlatformYM2203::dispatch(c); } int ch=c.chan-2; int ordch=orderedOps[ch]; @@ -384,7 +384,7 @@ static int opChanOffsH[4]={ 0xad, 0xae, 0xac, 0xa6 }; -void DivPlatformYM2610BExt::tick(bool sysTick) { +void DivPlatformYM2203Ext::tick(bool sysTick) { if (extMode) { bool writeSomething=false; unsigned char writeMask=2; @@ -401,7 +401,7 @@ void DivPlatformYM2610BExt::tick(bool sysTick) { } } - DivPlatformYM2610B::tick(sysTick); + DivPlatformYM2203::tick(sysTick); bool writeNoteOn=false; unsigned char writeMask=2; @@ -438,13 +438,13 @@ void DivPlatformYM2610BExt::tick(bool sysTick) { } } -void DivPlatformYM2610BExt::muteChannel(int ch, bool mute) { +void DivPlatformYM2203Ext::muteChannel(int ch, bool mute) { if (ch<2) { - DivPlatformYM2610B::muteChannel(ch,mute); + DivPlatformYM2203::muteChannel(ch,mute); return; } if (ch>5) { - DivPlatformYM2610B::muteChannel(ch-3,mute); + DivPlatformYM2203::muteChannel(ch-3,mute); return; } isOpMuted[ch-2]=mute; @@ -462,7 +462,7 @@ void DivPlatformYM2610BExt::muteChannel(int ch, bool mute) { } } -void DivPlatformYM2610BExt::forceIns() { +void DivPlatformYM2203Ext::forceIns() { for (int i=0; i<6; i++) { for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; @@ -494,7 +494,6 @@ void DivPlatformYM2610BExt::forceIns() { rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); - rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); if (chan[i].active) { chan[i].keyOn=true; chan[i].freqChanged=true; @@ -518,23 +517,23 @@ void DivPlatformYM2610BExt::forceIns() { } } -void* DivPlatformYM2610BExt::getChanState(int ch) { +void* DivPlatformYM2203Ext::getChanState(int ch) { if (ch>=6) return &chan[ch-3]; if (ch>=2) return &opChan[ch-2]; return &chan[ch]; } -DivDispatchOscBuffer* DivPlatformYM2610BExt::getOscBuffer(int ch) { +DivDispatchOscBuffer* DivPlatformYM2203Ext::getOscBuffer(int ch) { if (ch>=6) return oscBuf[ch-3]; if (ch<3) return oscBuf[ch]; return NULL; } -void DivPlatformYM2610BExt::reset() { - DivPlatformYM2610B::reset(); +void DivPlatformYM2203Ext::reset() { + DivPlatformYM2203::reset(); for (int i=0; i<4; i++) { - opChan[i]=DivPlatformYM2610BExt::OpChannel(); + opChan[i]=DivPlatformYM2203Ext::OpChannel(); opChan[i].vol=127; } @@ -543,12 +542,12 @@ void DivPlatformYM2610BExt::reset() { extMode=true; } -bool DivPlatformYM2610BExt::keyOffAffectsArp(int ch) { +bool DivPlatformYM2203Ext::keyOffAffectsArp(int ch) { return (ch>8); } -void DivPlatformYM2610BExt::notifyInsChange(int ins) { - DivPlatformYM2610B::notifyInsChange(ins); +void DivPlatformYM2203Ext::notifyInsChange(int ins) { + DivPlatformYM2203::notifyInsChange(ins); for (int i=0; i<4; i++) { if (opChan[i].ins==ins) { opChan[i].insChanged=true; @@ -556,8 +555,8 @@ void DivPlatformYM2610BExt::notifyInsChange(int ins) { } } -int DivPlatformYM2610BExt::init(DivEngine* parent, int channels, int sugRate, unsigned int flags) { - DivPlatformYM2610B::init(parent,channels,sugRate,flags); +int DivPlatformYM2203Ext::init(DivEngine* parent, int channels, int sugRate, unsigned int flags) { + DivPlatformYM2203::init(parent,channels,sugRate,flags); for (int i=0; i<4; i++) { isOpMuted[i]=false; } @@ -566,9 +565,9 @@ int DivPlatformYM2610BExt::init(DivEngine* parent, int channels, int sugRate, un return 19; } -void DivPlatformYM2610BExt::quit() { - DivPlatformYM2610B::quit(); +void DivPlatformYM2203Ext::quit() { + DivPlatformYM2203::quit(); } -DivPlatformYM2610BExt::~DivPlatformYM2610BExt() { +DivPlatformYM2203Ext::~DivPlatformYM2203Ext() { } diff --git a/src/engine/platform/ym2203ext.h b/src/engine/platform/ym2203ext.h index 4e3f7c5ff..7dde3abb4 100644 --- a/src/engine/platform/ym2203ext.h +++ b/src/engine/platform/ym2203ext.h @@ -19,9 +19,9 @@ #include "../dispatch.h" -#include "ym2610b.h" +#include "ym2203.h" -class DivPlatformYM2610BExt: public DivPlatformYM2610B { +class DivPlatformYM2203Ext: public DivPlatformYM2203 { struct OpChannel { DivMacroInt std; unsigned char freqH, freqL; @@ -47,5 +47,5 @@ class DivPlatformYM2610BExt: public DivPlatformYM2610B { void notifyInsChange(int ins); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); - ~DivPlatformYM2610BExt(); + ~DivPlatformYM2203Ext(); }; 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/sample.cpp b/src/engine/sample.cpp index e46c267d1..e8bc0f303 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -93,6 +93,18 @@ bool DivSample::initInternal(unsigned char d, int count) { dataDPCM=new unsigned char[lengthDPCM]; memset(dataDPCM,0,lengthDPCM); break; + case 2: // AICA ADPCM + if (dataAICA!=NULL) delete[] dataAICA; + lengthAICA=(count+1)/2; + dataAICA=new unsigned char[(lengthAICA+255)&(~0xff)]; + memset(dataAICA,0,(lengthAICA+255)&(~0xff)); + break; + case 3: // YMZ ADPCM + if (dataZ!=NULL) delete[] dataZ; + lengthZ=(count+1)/2; + dataZ=new unsigned char[(lengthZ+255)&(~0xff)]; + memset(dataZ,0,(lengthZ+255)&(~0xff)); + break; case 4: // QSound ADPCM if (dataQSoundA!=NULL) delete[] dataQSoundA; lengthQSoundA=(count+1)/2; @@ -657,6 +669,12 @@ void DivSample::render() { } break; } + case 2: // AICA ADPCM + aica_decode(dataAICA,data16,samples); + break; + case 3: // YMZ ADPCM + ymz_decode(dataZ,data16,samples); + break; case 4: // QSound ADPCM bs_decode(dataQSoundA,data16,samples); break; @@ -709,6 +727,14 @@ void DivSample::render() { if (accum>127) accum=127; } } + if (depth!=2) { // AICA ADPCM + if (!initInternal(2,samples)) return; + aica_encode(data16,dataAICA,(samples+511)&(~0x1ff)); + } + if (depth!=3) { // YMZ ADPCM + if (!initInternal(3,samples)) return; + ymz_encode(data16,dataZ,(samples+511)&(~0x1ff)); + } if (depth!=4) { // QSound ADPCM if (!initInternal(4,samples)) return; bs_encode(data16,dataQSoundA,samples); @@ -745,6 +771,10 @@ void* DivSample::getCurBuf() { return data1; case 1: return dataDPCM; + case 2: + return dataAICA; + case 3: + return dataZ; case 4: return dataQSoundA; case 5: @@ -771,6 +801,10 @@ unsigned int DivSample::getCurBufLen() { return length1; case 1: return lengthDPCM; + case 2: + return lengthAICA; + case 3: + return lengthZ; case 4: return lengthQSoundA; case 5: @@ -881,6 +915,8 @@ DivSample::~DivSample() { if (data16) delete[] data16; if (data1) delete[] data1; if (dataDPCM) delete[] dataDPCM; + if (dataAICA) delete[] dataAICA; + if (dataZ) delete[] dataZ; if (dataQSoundA) delete[] dataQSoundA; if (dataA) delete[] dataA; if (dataB) delete[] dataB; diff --git a/src/engine/sample.h b/src/engine/sample.h index 6e4de0941..624139797 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -62,6 +62,8 @@ struct DivSample { // valid values are: // - 0: ZX Spectrum overlay drum (1-bit) // - 1: 1-bit NES DPCM (1-bit) + // - 2: AICA ADPCM + // - 3: YMZ ADPCM // - 4: QSound ADPCM // - 5: ADPCM-A // - 6: ADPCM-B @@ -77,6 +79,8 @@ struct DivSample { short* data16; // 16 unsigned char* data1; // 0 unsigned char* dataDPCM; // 1 + unsigned char* dataAICA; // 2 + unsigned char* dataZ; // 3 unsigned char* dataQSoundA; // 4 unsigned char* dataA; // 5 unsigned char* dataB; // 6 @@ -84,8 +88,8 @@ struct DivSample { unsigned char* dataBRR; // 9 unsigned char* dataVOX; // 10 - unsigned int length8, length16, length1, lengthDPCM, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX; - unsigned int off8, off16, off1, offDPCM, offQSoundA, offA, offB, offX68, offBRR, offVOX; + unsigned int length8, length16, length1, lengthDPCM, lengthAICA, lengthZ, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX; + unsigned int off8, off16, off1, offDPCM, offAICA, offZ, offQSoundA, offA, offB, offX68, offBRR, offVOX; unsigned int offSegaPCM, offQSound, offX1_010, offSU; unsigned int samples; @@ -218,6 +222,8 @@ struct DivSample { data16(NULL), data1(NULL), dataDPCM(NULL), + dataAICA(NULL), + dataZ(NULL), dataQSoundA(NULL), dataA(NULL), dataB(NULL), @@ -228,6 +234,8 @@ struct DivSample { length16(0), length1(0), lengthDPCM(0), + lengthAICA(0), + lengthZ(0), lengthQSoundA(0), lengthA(0), lengthB(0), @@ -238,6 +246,8 @@ struct DivSample { off16(0), off1(0), offDPCM(0), + offAICA(0), + offZ(0), offQSoundA(0), offA(0), offB(0), diff --git a/src/engine/song.cpp b/src/engine/song.cpp index 7a815eb16..216a1bc4d 100644 --- a/src/engine/song.cpp +++ b/src/engine/song.cpp @@ -19,7 +19,7 @@ #include "song.h" -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; @@ -71,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 e4f003542..219225e8f 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -110,6 +110,44 @@ enum DivSystem { DIV_SYSTEM_DUMMY }; +struct DivSubSong { + String name, notes; + unsigned char hilightA, hilightB; + unsigned char timeBase, speed1, speed2, arpLen; + bool pal; + bool customTempo; + float hz; + int patLen, ordersLen; + + DivOrders orders; + DivChannelData pat[DIV_MAX_CHANS]; + + bool chanShow[DIV_MAX_CHANS]; + unsigned char chanCollapse[DIV_MAX_CHANS]; + String chanName[DIV_MAX_CHANS]; + String chanShortName[DIV_MAX_CHANS]; + + 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[DIV_MAX_CHANS]; std::vector wave; std::vector sample; - bool chanShow[DIV_MAX_CHANS]; - unsigned char chanCollapse[DIV_MAX_CHANS]; + std::vector subsong; DivInstrument nullIns, nullInsOPLL, nullInsOPL, nullInsQSound; DivWavetable nullWave; @@ -401,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), @@ -457,10 +479,7 @@ struct DivSong { systemPan[i]=0; systemFlags[i]=0; } - for (int i=0; ichans) 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 2e1dd3779..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(); @@ -525,14 +525,22 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_INS_LIST_ADD: curIns=e->addInstrument(cursor.xCoarse); - MARK_MODIFIED; + if (curIns==-1) { + showError("too many instruments!"); + } else { + MARK_MODIFIED; + } break; case GUI_ACTION_INS_LIST_DUPLICATE: if (curIns>=0 && curIns<(int)e->song.ins.size()) { int prevIns=curIns; curIns=e->addInstrument(cursor.xCoarse); - (*e->song.ins[curIns])=(*e->song.ins[prevIns]); - MARK_MODIFIED; + if (curIns==-1) { + showError("too many instruments!"); + } else { + (*e->song.ins[curIns])=(*e->song.ins[prevIns]); + MARK_MODIFIED; + } } break; case GUI_ACTION_INS_LIST_OPEN: @@ -571,14 +579,22 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_WAVE_LIST_ADD: curWave=e->addWave(); - MARK_MODIFIED; + if (curWave==-1) { + showError("too many wavetables!"); + } else { + MARK_MODIFIED; + } break; case GUI_ACTION_WAVE_LIST_DUPLICATE: if (curWave>=0 && curWave<(int)e->song.wave.size()) { int prevWave=curWave; curWave=e->addWave(); - (*e->song.wave[curWave])=(*e->song.wave[prevWave]); - MARK_MODIFIED; + if (curWave==-1) { + showError("too many wavetables!"); + } else { + (*e->song.wave[curWave])=(*e->song.wave[prevWave]); + MARK_MODIFIED; + } } break; case GUI_ACTION_WAVE_LIST_OPEN: @@ -614,31 +630,39 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_LIST_ADD: curSample=e->addSample(); + if (curSample==-1) { + showError("too many samples!"); + } else { + MARK_MODIFIED; + } updateSampleTex=true; - MARK_MODIFIED; break; case GUI_ACTION_SAMPLE_LIST_DUPLICATE: if (curSample>=0 && curSample<(int)e->song.sample.size()) { DivSample* prevSample=e->getSample(curSample); curSample=e->addSample(); - updateSampleTex=true; - e->lockEngine([this,prevSample]() { - DivSample* sample=e->getSample(curSample); - if (sample!=NULL) { - sample->rate=prevSample->rate; - sample->centerRate=prevSample->centerRate; - sample->name=prevSample->name; - sample->loopStart=prevSample->loopStart; - sample->depth=prevSample->depth; - if (sample->init(prevSample->samples)) { - if (prevSample->getCurBuf()!=NULL) { - memcpy(sample->getCurBuf(),prevSample->getCurBuf(),prevSample->getCurBufLen()); + if (curSample==-1) { + showError("too many samples!"); + } else { + e->lockEngine([this,prevSample]() { + DivSample* sample=e->getSample(curSample); + if (sample!=NULL) { + sample->rate=prevSample->rate; + sample->centerRate=prevSample->centerRate; + sample->name=prevSample->name; + sample->loopStart=prevSample->loopStart; + sample->depth=prevSample->depth; + if (sample->init(prevSample->samples)) { + if (prevSample->getCurBuf()!=NULL) { + memcpy(sample->getCurBuf(),prevSample->getCurBuf(),prevSample->getCurBufLen()); + } } } - } - e->renderSamples(); - }); - MARK_MODIFIED; + e->renderSamples(); + }); + MARK_MODIFIED; + } + updateSampleTex=true; } break; case GUI_ACTION_SAMPLE_LIST_OPEN: @@ -1154,11 +1178,15 @@ void FurnaceGUI::doAction(int what) { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; curIns=e->addInstrument(cursor.xCoarse); - e->song.ins[curIns]->type=DIV_INS_AMIGA; - e->song.ins[curIns]->name=sample->name; - e->song.ins[curIns]->amiga.initSample=curSample; - nextWindow=GUI_WINDOW_INS_EDIT; - MARK_MODIFIED; + if (curIns==-1) { + showError("too many instruments!"); + } else { + e->song.ins[curIns]->type=DIV_INS_AMIGA; + e->song.ins[curIns]->name=sample->name; + e->song.ins[curIns]->amiga.initSample=curSample; + nextWindow=GUI_WINDOW_INS_EDIT; + MARK_MODIFIED; + } break; } @@ -1168,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; @@ -1181,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: { @@ -1193,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 1c52f12bb..ec38c86c2 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); } } @@ -2047,7 +2047,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]; @@ -2833,12 +2833,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) { @@ -2899,6 +2899,7 @@ bool FurnaceGUI::loop() { ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0); + drawSubSongs(); drawPattern(); drawEditControls(); drawSongInfo(); @@ -3177,8 +3178,11 @@ bool FurnaceGUI::loop() { break; } case GUI_FILE_WAVE_OPEN: - e->addWaveFromFile(copyOfName.c_str()); - MARK_MODIFIED; + if (!e->addWaveFromFile(copyOfName.c_str())) { + showError("cannot load wavetable! ("+e->getLastError()+")"); + } else { + MARK_MODIFIED; + } break; case GUI_FILE_EXPORT_VGM: { SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion); @@ -3504,7 +3508,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; @@ -3663,6 +3667,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); @@ -3854,6 +3859,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); @@ -3974,6 +3980,7 @@ FurnaceGUI::FurnaceGUI(): logOpen(false), effectListOpen(false), chanOscOpen(false), + subSongsOpen(true), /* editControlsDocked(false), ordersDocked(false), @@ -4001,6 +4008,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 a02543aa5..31aad6432 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -116,8 +116,8 @@ const char* insTypes[DIV_INS_MAX]={ const char* sampleDepths[17]={ "1-bit PCM", "1-bit DPCM", - NULL, - NULL, + "Yamaha AICA", + "YMZ/YMU ADPCM", "QSound ADPCM", "ADPCM-A", "ADPCM-B", @@ -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