Merge branch 'master' into feature/Moar-patch-bank-support-part3

This commit is contained in:
James Alan Nguyen 2022-05-15 16:49:52 +10:00
commit 0e07b745c7
28 changed files with 1206 additions and 611 deletions

View file

@ -354,6 +354,7 @@ src/engine/platform/c64.cpp
src/engine/platform/arcade.cpp src/engine/platform/arcade.cpp
src/engine/platform/tx81z.cpp src/engine/platform/tx81z.cpp
src/engine/platform/ym2203.cpp src/engine/platform/ym2203.cpp
src/engine/platform/ym2203ext.cpp
src/engine/platform/ym2608.cpp src/engine/platform/ym2608.cpp
src/engine/platform/ym2610.cpp src/engine/platform/ym2610.cpp
src/engine/platform/ym2610ext.cpp src/engine/platform/ym2610ext.cpp
@ -444,6 +445,7 @@ src/gui/settings.cpp
src/gui/songInfo.cpp src/gui/songInfo.cpp
src/gui/songNotes.cpp src/gui/songNotes.cpp
src/gui/stats.cpp src/gui/stats.cpp
src/gui/subSongs.cpp
src/gui/sysConf.cpp src/gui/sysConf.cpp
src/gui/sysEx.cpp src/gui/sysEx.cpp
src/gui/util.cpp src/gui/util.cpp

View file

@ -29,6 +29,11 @@ furthermore, an `or reserved` indicates this field is always present, but is res
the format versions are: the format versions are:
- 95: Furnace dev95
- 94: Furnace dev94
- 93: Furnace dev93
- 92: Furnace dev92
- 91: Furnace dev91
- 90: Furnace dev90 - 90: Furnace dev90
- 89: Furnace dev89 - 89: Furnace dev89
- 88: Furnace dev88 - 88: Furnace dev88
@ -122,26 +127,26 @@ size | description
-----|------------------------------------ -----|------------------------------------
4 | "INFO" block ID 4 | "INFO" block ID
4 | reserved 4 | reserved
1 | time base 1 | time base (of first song)
1 | speed 1 1 | speed 1 (of first song)
1 | speed 2 1 | speed 2 (of first song)
1 | initial arpeggio time 1 | initial arpeggio time (of first song)
4f | ticks per second 4f | ticks per second (of first song)
| - 60 is NTSC | - 60 is NTSC
| - 50 is PAL | - 50 is PAL
2 | pattern length 2 | pattern length (of first song)
| - the limit is 256. | - the limit is 256.
2 | orders length 2 | orders length (of first song)
| - the limit is 256 (>=80) or 127 (<80). | - the limit is 256 (>=80) or 127 (<80).
1 | highlight A 1 | highlight A (of first song)
1 | highlight B 1 | highlight B (of first song)
2 | instrument count 2 | instrument count
| - the limit is 256. | - the limit is 256.
2 | wavetable count 2 | wavetable count
| - the limit is 256. | - the limit is 256.
2 | sample count 2 | sample count
| - the limit is 256. | - the limit is 256.
4 | pattern count 4 | pattern count (global)
32 | list of sound chips 32 | list of sound chips
| - possible soundchips: | - possible soundchips:
| - 0x00: end of list | - 0x00: end of list
@ -215,6 +220,7 @@ size | description
| - 0xb5: tildearrow Sound Unit - 8 channels | - 0xb5: tildearrow Sound Unit - 8 channels
| - 0xb6: OPN extended - 9 channels | - 0xb6: OPN extended - 9 channels
| - 0xb7: PC-98 extended - 19 channels | - 0xb7: PC-98 extended - 19 channels
| - 0xb8: YMZ280B - 8 channels
| - 0xde: YM2610B extended - 19 channels | - 0xde: YM2610B extended - 19 channels
| - 0xe0: QSound - 19 channels | - 0xe0: QSound - 19 channels
| - 0xfd: Dummy System - 8 channels | - 0xfd: Dummy System - 8 channels
@ -257,20 +263,20 @@ size | description
4?? | pointers to wavetables 4?? | pointers to wavetables
4?? | pointers to samples 4?? | pointers to samples
4?? | pointers to patterns 4?? | pointers to patterns
??? | orders ??? | orders (of first song)
| - a table of bytes | - a table of bytes
| - size=channels*ordLen | - size=channels*ordLen
| - read orders then channels | - read orders then channels
| - the maximum value of a cell is FF (>=80) or 7F (<80). | - the maximum value of a cell is FF (>=80) or 7F (<80).
??? | effect columns ??? | effect columns (of first song)
| - size=channels | - size=channels
1?? | channel hide status 1?? | channel hide status (of first song)
| - size=channels | - size=channels
1?? | channel collapse status 1?? | channel collapse status (of first song)
| - size=channels | - size=channels
S?? | channel names S?? | channel names (of first song)
| - a list of channelCount C strings | - a list of channelCount C strings
S?? | channel short names S?? | channel short names (of first song)
| - same as above | - same as above
STR | song comment STR | song comment
4f | master volume, 1.0f=100% (>=59) 4f | master volume, 1.0f=100% (>=59)
@ -291,6 +297,55 @@ size | description
1 | pitch macro is linear (>=90) or reserved 1 | pitch macro is linear (>=90) or reserved
1 | pitch slide speed in full linear pitch mode (>=94) or reserved 1 | pitch slide speed in full linear pitch mode (>=94) or reserved
18 | 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 # instrument
@ -743,6 +798,8 @@ size | description
1 | depth 1 | depth
| - 0: ZX Spectrum overlay drum (1-bit) | - 0: ZX Spectrum overlay drum (1-bit)
| - 1: 1-bit NES DPCM (1-bit) | - 1: 1-bit NES DPCM (1-bit)
| - 2: AICA ADPCM
| - 3: YMZ ADPCM
| - 4: QSound ADPCM | - 4: QSound ADPCM
| - 5: ADPCM-A | - 5: ADPCM-A
| - 6: ADPCM-B | - 6: ADPCM-B
@ -769,7 +826,8 @@ size | description
4 | reserved 4 | reserved
2 | channel 2 | channel
2 | pattern index 2 | pattern index
4 | reserved 2 | subsong (>=95) or reserved
2 | reserved
??? | pattern data ??? | pattern data
| - size: rows*(4+effectColumns*2)*2 | - size: rows*(4+effectColumns*2)*2
| - read shorts in this order: | - read shorts in this order:

View file

@ -139,18 +139,18 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) {
int nextRow=0; int nextRow=0;
int effectVal=0; int effectVal=0;
DivPattern* pat[DIV_MAX_CHANS]; DivPattern* pat[DIV_MAX_CHANS];
for (int i=0; i<song.ordersLen; i++) { for (int i=0; i<curSubSong->ordersLen; i++) {
for (int j=0; j<chans; j++) { for (int j=0; j<chans; j++) {
pat[j]=song.pat[j].getPattern(song.orders.ord[j][i],false); pat[j]=curPat[j].getPattern(curOrders->ord[j][i],false);
} }
for (int j=nextRow; j<song.patLen; j++) { for (int j=nextRow; j<curSubSong->patLen; j++) {
nextRow=0; nextRow=0;
for (int k=0; k<chans; k++) { for (int k=0; k<chans; k++) {
for (int l=0; l<song.pat[k].effectCols; l++) { for (int l=0; l<curPat[k].effectCols; l++) {
effectVal=pat[k]->data[j][5+(l<<1)]; effectVal=pat[k]->data[j][5+(l<<1)];
if (effectVal<0) effectVal=0; if (effectVal<0) effectVal=0;
if (pat[k]->data[j][4+(l<<1)]==0x0d) { if (pat[k]->data[j][4+(l<<1)]==0x0d) {
if (nextOrder==-1 && (i<song.ordersLen-1 || !song.ignoreJumpAtEnd)) { if (nextOrder==-1 && (i<curSubSong->ordersLen-1 || !song.ignoreJumpAtEnd)) {
nextOrder=i+1; nextOrder=i+1;
nextRow=effectVal; nextRow=effectVal;
} }
@ -695,6 +695,7 @@ void DivEngine::createNew(const int* description) {
saveLock.lock(); saveLock.lock();
song.unload(); song.unload();
song=DivSong(); song=DivSong();
changeSong(0);
if (description!=NULL) { if (description!=NULL) {
initSongWithDesc(description); initSongWithDesc(description);
} }
@ -716,45 +717,55 @@ void DivEngine::swapChannels(int src, int dest) {
} }
for (int i=0; i<256; i++) { for (int i=0; i<256; i++) {
song.orders.ord[dest][i]^=song.orders.ord[src][i]; curOrders->ord[dest][i]^=curOrders->ord[src][i];
song.orders.ord[src][i]^=song.orders.ord[dest][i]; curOrders->ord[src][i]^=curOrders->ord[dest][i];
song.orders.ord[dest][i]^=song.orders.ord[src][i]; curOrders->ord[dest][i]^=curOrders->ord[src][i];
DivPattern* prev=song.pat[src].data[i]; DivPattern* prev=curPat[src].data[i];
song.pat[src].data[i]=song.pat[dest].data[i]; curPat[src].data[i]=curPat[dest].data[i];
song.pat[dest].data[i]=prev; curPat[dest].data[i]=prev;
} }
song.pat[src].effectCols^=song.pat[dest].effectCols; curPat[src].effectCols^=curPat[dest].effectCols;
song.pat[dest].effectCols^=song.pat[src].effectCols; curPat[dest].effectCols^=curPat[src].effectCols;
song.pat[src].effectCols^=song.pat[dest].effectCols; curPat[src].effectCols^=curPat[dest].effectCols;
String prevChanName=song.chanName[src]; String prevChanName=curSubSong->chanName[src];
String prevChanShortName=song.chanShortName[src]; String prevChanShortName=curSubSong->chanShortName[src];
bool prevChanShow=song.chanShow[src]; bool prevChanShow=curSubSong->chanShow[src];
bool prevChanCollapse=song.chanCollapse[src]; bool prevChanCollapse=curSubSong->chanCollapse[src];
song.chanName[src]=song.chanName[dest]; curSubSong->chanName[src]=curSubSong->chanName[dest];
song.chanShortName[src]=song.chanShortName[dest]; curSubSong->chanShortName[src]=curSubSong->chanShortName[dest];
song.chanShow[src]=song.chanShow[dest]; curSubSong->chanShow[src]=curSubSong->chanShow[dest];
song.chanCollapse[src]=song.chanCollapse[dest]; curSubSong->chanCollapse[src]=curSubSong->chanCollapse[dest];
song.chanName[dest]=prevChanName; curSubSong->chanName[dest]=prevChanName;
song.chanShortName[dest]=prevChanShortName; curSubSong->chanShortName[dest]=prevChanShortName;
song.chanShow[dest]=prevChanShow; curSubSong->chanShow[dest]=prevChanShow;
song.chanCollapse[dest]=prevChanCollapse; curSubSong->chanCollapse[dest]=prevChanCollapse;
} }
void DivEngine::stompChannel(int ch) { void DivEngine::stompChannel(int ch) {
logV("stomping channel %d",ch); logV("stomping channel %d",ch);
for (int i=0; i<256; i++) { for (int i=0; i<256; i++) {
song.orders.ord[ch][i]=0; curOrders->ord[ch][i]=0;
} }
song.pat[ch].wipePatterns(); curPat[ch].wipePatterns();
song.pat[ch].effectCols=1; curPat[ch].effectCols=1;
song.chanName[ch]=""; curSubSong->chanName[ch]="";
song.chanShortName[ch]=""; curSubSong->chanShortName[ch]="";
song.chanShow[ch]=true; curSubSong->chanShow[ch]=true;
song.chanCollapse[ch]=false; 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) { void DivEngine::swapChannelsP(int src, int dest) {
@ -767,6 +778,41 @@ void DivEngine::swapChannelsP(int src, int dest) {
BUSY_END; 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) { void DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) {
int chanCount=chans; int chanCount=chans;
quitDispatch(); quitDispatch();
@ -1321,15 +1367,15 @@ void DivEngine::reset() {
} }
extValue=0; extValue=0;
extValuePresent=0; extValuePresent=0;
speed1=song.speed1; speed1=curSubSong->speed1;
speed2=song.speed2; speed2=curSubSong->speed2;
firstTick=false; firstTick=false;
nextSpeed=speed1; nextSpeed=speed1;
divider=60; divider=60;
if (song.customTempo) { if (curSubSong->customTempo) {
divider=song.hz; divider=curSubSong->hz;
} else { } else {
if (song.pal) { if (curSubSong->pal) {
divider=60; divider=60;
} else { } else {
divider=50; divider=50;
@ -1476,6 +1522,10 @@ int DivEngine::getRow() {
return curRow; return curRow;
} }
size_t DivEngine::getCurrentSubSong() {
return curSubSongIndex;
}
unsigned char DivEngine::getSpeed1() { unsigned char DivEngine::getSpeed1() {
return speed1; return speed1;
} }
@ -1485,9 +1535,9 @@ unsigned char DivEngine::getSpeed2() {
} }
float DivEngine::getHz() { float DivEngine::getHz() {
if (song.customTempo) { if (curSubSong->customTempo) {
return song.hz; return curSubSong->hz;
} else if (song.pal) { } else if (curSubSong->pal) {
return 60.0; return 60.0;
} else { } else {
return 50.0; return 50.0;
@ -1594,6 +1644,7 @@ void DivEngine::unmuteAll() {
} }
int DivEngine::addInstrument(int refChan) { int DivEngine::addInstrument(int refChan) {
if (song.ins.size()>=256) return -1;
BUSY_BEGIN; BUSY_BEGIN;
DivInstrument* ins=new DivInstrument; DivInstrument* ins=new DivInstrument;
int insCount=(int)song.ins.size(); int insCount=(int)song.ins.size();
@ -1624,6 +1675,10 @@ int DivEngine::addInstrument(int refChan) {
} }
int DivEngine::addInstrumentPtr(DivInstrument* which) { int DivEngine::addInstrumentPtr(DivInstrument* which) {
if (song.ins.size()>=256) {
delete which;
return -1;
}
BUSY_BEGIN; BUSY_BEGIN;
saveLock.lock(); saveLock.lock();
song.ins.push_back(which); song.ins.push_back(which);
@ -1654,10 +1709,10 @@ void DivEngine::delInstrument(int index) {
song.insLen=song.ins.size(); song.insLen=song.ins.size();
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
for (int j=0; j<256; j++) { for (int j=0; j<256; j++) {
if (song.pat[i].data[j]==NULL) continue; if (curPat[i].data[j]==NULL) continue;
for (int k=0; k<song.patLen; k++) { for (int k=0; k<curSubSong->patLen; k++) {
if (song.pat[i].data[j]->data[k][2]>index) { if (curPat[i].data[j]->data[k][2]>index) {
song.pat[i].data[j]->data[k][2]--; curPat[i].data[j]->data[k][2]--;
} }
} }
} }
@ -1668,6 +1723,7 @@ void DivEngine::delInstrument(int index) {
} }
int DivEngine::addWave() { int DivEngine::addWave() {
if (song.wave.size()>=256) return -1;
BUSY_BEGIN; BUSY_BEGIN;
saveLock.lock(); saveLock.lock();
DivWavetable* wave=new DivWavetable; DivWavetable* wave=new DivWavetable;
@ -1680,37 +1736,48 @@ int DivEngine::addWave() {
} }
bool DivEngine::addWaveFromFile(const char* path) { bool DivEngine::addWaveFromFile(const char* path) {
if (song.wave.size()>=256) {
lastError="too many wavetables!";
return false;
}
FILE* f=ps_fopen(path,"rb"); FILE* f=ps_fopen(path,"rb");
if (f==NULL) { if (f==NULL) {
lastError=fmt::sprintf("%s",strerror(errno));
return false; return false;
} }
unsigned char* buf; unsigned char* buf;
ssize_t len; ssize_t len;
if (fseek(f,0,SEEK_END)!=0) { if (fseek(f,0,SEEK_END)!=0) {
fclose(f); fclose(f);
lastError=fmt::sprintf("could not seek to end: %s",strerror(errno));
return false; return false;
} }
len=ftell(f); len=ftell(f);
if (len<0) { if (len<0) {
fclose(f); fclose(f);
lastError=fmt::sprintf("could not determine file size: %s",strerror(errno));
return false; return false;
} }
if (len==(SIZE_MAX>>1)) { if (len==(SIZE_MAX>>1)) {
fclose(f); fclose(f);
lastError="file size is invalid!";
return false; return false;
} }
if (len==0) { if (len==0) {
fclose(f); fclose(f);
lastError="file is empty";
return false; return false;
} }
if (fseek(f,0,SEEK_SET)!=0) { if (fseek(f,0,SEEK_SET)!=0) {
fclose(f); fclose(f);
lastError=fmt::sprintf("could not seek to beginning: %s",strerror(errno));
return false; return false;
} }
buf=new unsigned char[len]; buf=new unsigned char[len];
if (fread(buf,1,len,f)!=(size_t)len) { if (fread(buf,1,len,f)!=(size_t)len) {
logW("did not read entire wavetable file buffer!"); logW("did not read entire wavetable file buffer!");
delete[] buf; delete[] buf;
lastError=fmt::sprintf("could not read entire file: %s",strerror(errno));
return false; return false;
} }
fclose(f); fclose(f);
@ -1790,6 +1857,7 @@ bool DivEngine::addWaveFromFile(const char* path) {
} catch (EndOfFileException& e) { } catch (EndOfFileException& e) {
delete wave; delete wave;
delete[] buf; delete[] buf;
lastError="premature end of file";
return false; return false;
} }
@ -1816,6 +1884,7 @@ void DivEngine::delWave(int index) {
} }
int DivEngine::addSample() { int DivEngine::addSample() {
if (song.sample.size()>=256) return -1;
BUSY_BEGIN; BUSY_BEGIN;
saveLock.lock(); saveLock.lock();
DivSample* sample=new DivSample; DivSample* sample=new DivSample;
@ -1830,6 +1899,10 @@ int DivEngine::addSample() {
} }
int DivEngine::addSampleFromFile(const char* path) { int DivEngine::addSampleFromFile(const char* path) {
if (song.sample.size()>=256) {
lastError="too many samples!";
return -1;
}
BUSY_BEGIN; BUSY_BEGIN;
warnings=""; warnings="";
@ -2025,18 +2098,18 @@ void DivEngine::delSample(int index) {
void DivEngine::addOrder(bool duplicate, bool where) { void DivEngine::addOrder(bool duplicate, bool where) {
unsigned char order[DIV_MAX_CHANS]; unsigned char order[DIV_MAX_CHANS];
if (song.ordersLen>=0xff) return; if (curSubSong->ordersLen>=0xff) return;
BUSY_BEGIN_SOFT; BUSY_BEGIN_SOFT;
if (duplicate) { if (duplicate) {
for (int i=0; i<DIV_MAX_CHANS; i++) { for (int i=0; i<DIV_MAX_CHANS; i++) {
order[i]=song.orders.ord[i][curOrder]; order[i]=curOrders->ord[i][curOrder];
} }
} else { } else {
bool used[256]; bool used[256];
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
memset(used,0,sizeof(bool)*256); memset(used,0,sizeof(bool)*256);
for (int j=0; j<song.ordersLen; j++) { for (int j=0; j<curSubSong->ordersLen; j++) {
used[song.orders.ord[i][j]]=true; used[curOrders->ord[i][j]]=true;
} }
order[i]=0xff; order[i]=0xff;
for (int j=0; j<256; j++) { for (int j=0; j<256; j++) {
@ -2050,19 +2123,19 @@ void DivEngine::addOrder(bool duplicate, bool where) {
if (where) { // at the end if (where) { // at the end
saveLock.lock(); saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) { for (int i=0; i<DIV_MAX_CHANS; i++) {
song.orders.ord[i][song.ordersLen]=order[i]; curOrders->ord[i][curSubSong->ordersLen]=order[i];
} }
song.ordersLen++; curSubSong->ordersLen++;
saveLock.unlock(); saveLock.unlock();
} else { // after current order } else { // after current order
saveLock.lock(); saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) { for (int i=0; i<DIV_MAX_CHANS; i++) {
for (int j=song.ordersLen; j>curOrder; j--) { for (int j=curSubSong->ordersLen; j>curOrder; j--) {
song.orders.ord[i][j]=song.orders.ord[i][j-1]; 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(); saveLock.unlock();
curOrder++; curOrder++;
if (playing && !freelance) { if (playing && !freelance) {
@ -2074,21 +2147,21 @@ void DivEngine::addOrder(bool duplicate, bool where) {
void DivEngine::deepCloneOrder(bool where) { void DivEngine::deepCloneOrder(bool where) {
unsigned char order[DIV_MAX_CHANS]; unsigned char order[DIV_MAX_CHANS];
if (song.ordersLen>=0xff) return; if (curSubSong->ordersLen>=0xff) return;
warnings=""; warnings="";
BUSY_BEGIN_SOFT; BUSY_BEGIN_SOFT;
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
bool didNotFind=true; bool didNotFind=true;
logD("channel %d",i); logD("channel %d",i);
order[i]=song.orders.ord[i][curOrder]; order[i]=curOrders->ord[i][curOrder];
// find free slot // find free slot
for (int j=0; j<256; j++) { for (int j=0; j<256; j++) {
logD("finding free slot in %d...",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]; int origOrd=order[i];
order[i]=j; order[i]=j;
DivPattern* oldPat=song.pat[i].getPattern(origOrd,false); DivPattern* oldPat=curPat[i].getPattern(origOrd,false);
DivPattern* pat=song.pat[i].getPattern(j,true); DivPattern* pat=curPat[i].getPattern(j,true);
memcpy(pat->data,oldPat->data,256*32*sizeof(short)); memcpy(pat->data,oldPat->data,256*32*sizeof(short));
logD("found at %d",j); logD("found at %d",j);
didNotFind=false; didNotFind=false;
@ -2102,19 +2175,19 @@ void DivEngine::deepCloneOrder(bool where) {
if (where) { // at the end if (where) { // at the end
saveLock.lock(); saveLock.lock();
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
song.orders.ord[i][song.ordersLen]=order[i]; curOrders->ord[i][curSubSong->ordersLen]=order[i];
} }
song.ordersLen++; curSubSong->ordersLen++;
saveLock.unlock(); saveLock.unlock();
} else { // after current order } else { // after current order
saveLock.lock(); saveLock.lock();
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
for (int j=song.ordersLen; j>curOrder; j--) { for (int j=curSubSong->ordersLen; j>curOrder; j--) {
song.orders.ord[i][j]=song.orders.ord[i][j-1]; 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(); saveLock.unlock();
curOrder++; curOrder++;
if (playing && !freelance) { if (playing && !freelance) {
@ -2125,17 +2198,17 @@ void DivEngine::deepCloneOrder(bool where) {
} }
void DivEngine::deleteOrder() { void DivEngine::deleteOrder() {
if (song.ordersLen<=1) return; if (curSubSong->ordersLen<=1) return;
BUSY_BEGIN_SOFT; BUSY_BEGIN_SOFT;
saveLock.lock(); saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) { for (int i=0; i<DIV_MAX_CHANS; i++) {
for (int j=curOrder; j<song.ordersLen; j++) { for (int j=curOrder; j<curSubSong->ordersLen; j++) {
song.orders.ord[i][j]=song.orders.ord[i][j+1]; curOrders->ord[i][j]=curOrders->ord[i][j+1];
} }
} }
song.ordersLen--; curSubSong->ordersLen--;
saveLock.unlock(); saveLock.unlock();
if (curOrder>=song.ordersLen) curOrder=song.ordersLen-1; if (curOrder>=curSubSong->ordersLen) curOrder=curSubSong->ordersLen-1;
if (playing && !freelance) { if (playing && !freelance) {
playSub(false); playSub(false);
} }
@ -2150,9 +2223,9 @@ void DivEngine::moveOrderUp() {
} }
saveLock.lock(); saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) { for (int i=0; i<DIV_MAX_CHANS; i++) {
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder-1]; curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder-1];
song.orders.ord[i][curOrder-1]^=song.orders.ord[i][curOrder]; curOrders->ord[i][curOrder-1]^=curOrders->ord[i][curOrder];
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder-1]; curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder-1];
} }
saveLock.unlock(); saveLock.unlock();
curOrder--; curOrder--;
@ -2164,15 +2237,15 @@ void DivEngine::moveOrderUp() {
void DivEngine::moveOrderDown() { void DivEngine::moveOrderDown() {
BUSY_BEGIN_SOFT; BUSY_BEGIN_SOFT;
if (curOrder>=song.ordersLen-1) { if (curOrder>=curSubSong->ordersLen-1) {
BUSY_END; BUSY_END;
return; return;
} }
saveLock.lock(); saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) { for (int i=0; i<DIV_MAX_CHANS; i++) {
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder+1]; curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder+1];
song.orders.ord[i][curOrder+1]^=song.orders.ord[i][curOrder]; curOrders->ord[i][curOrder+1]^=curOrders->ord[i][curOrder];
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder+1]; curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder+1];
} }
saveLock.unlock(); saveLock.unlock();
curOrder++; curOrder++;
@ -2185,12 +2258,12 @@ void DivEngine::moveOrderDown() {
void DivEngine::exchangeIns(int one, int two) { void DivEngine::exchangeIns(int one, int two) {
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
for (int j=0; j<256; j++) { for (int j=0; j<256; j++) {
if (song.pat[i].data[j]==NULL) continue; if (curPat[i].data[j]==NULL) continue;
for (int k=0; k<song.patLen; k++) { for (int k=0; k<curSubSong->patLen; k++) {
if (song.pat[i].data[j]->data[k][2]==one) { if (curPat[i].data[j]->data[k][2]==one) {
song.pat[i].data[j]->data[k][2]=two; curPat[i].data[j]->data[k][2]=two;
} else if (song.pat[i].data[j]->data[k][2]==two) { } else if (curPat[i].data[j]->data[k][2]==two) {
song.pat[i].data[j]->data[k][2]=one; curPat[i].data[j]->data[k][2]=one;
} }
} }
} }
@ -2394,7 +2467,7 @@ void DivEngine::autoNoteOffAll() {
void DivEngine::setOrder(unsigned char order) { void DivEngine::setOrder(unsigned char order) {
BUSY_BEGIN_SOFT; BUSY_BEGIN_SOFT;
curOrder=order; curOrder=order;
if (order>=song.ordersLen) curOrder=0; if (order>=curSubSong->ordersLen) curOrder=0;
if (playing && !freelance) { if (playing && !freelance) {
playSub(false); playSub(false);
} }
@ -2417,15 +2490,15 @@ void DivEngine::setSysFlags(int system, unsigned int flags, bool restart) {
void DivEngine::setSongRate(float hz, bool pal) { void DivEngine::setSongRate(float hz, bool pal) {
BUSY_BEGIN; BUSY_BEGIN;
saveLock.lock(); saveLock.lock();
song.pal=!pal; curSubSong->pal=!pal;
song.hz=hz; curSubSong->hz=hz;
// what? // what?
song.customTempo=true; curSubSong->customTempo=true;
divider=60; divider=60;
if (song.customTempo) { if (curSubSong->customTempo) {
divider=song.hz; divider=curSubSong->hz;
} else { } else {
if (song.pal) { if (curSubSong->pal) {
divider=60; divider=60;
} else { } else {
divider=50; divider=50;
@ -2868,5 +2941,6 @@ bool DivEngine::quit() {
if (yrw801ROM!=NULL) delete[] yrw801ROM; if (yrw801ROM!=NULL) delete[] yrw801ROM;
if (tg100ROM!=NULL) delete[] tg100ROM; if (tg100ROM!=NULL) delete[] tg100ROM;
if (mu5ROM!=NULL) delete[] mu5ROM; if (mu5ROM!=NULL) delete[] mu5ROM;
song.unload();
return true; return true;
} }

View file

@ -45,8 +45,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false; #define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev94" #define DIV_VERSION "dev95"
#define DIV_ENGINE_VERSION 94 #define DIV_ENGINE_VERSION 95
// for imports // for imports
#define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_MOD 0xff01
@ -304,6 +304,7 @@ class DivEngine {
bool hasLoadedSomething; bool hasLoadedSomething;
int softLockCount; int softLockCount;
int subticks, ticks, curRow, curOrder, remainingLoops, nextSpeed; int subticks, ticks, curRow, curOrder, remainingLoops, nextSpeed;
size_t curSubSongIndex;
double divider; double divider;
int cycles; int cycles;
double clockDrift; double clockDrift;
@ -414,8 +415,14 @@ class DivEngine {
void swapChannels(int src, int dest); void swapChannels(int src, int dest);
void stompChannel(int ch); void stompChannel(int ch);
// change song (UNSAFE)
void changeSong(size_t songIndex);
public: public:
DivSong song; DivSong song;
DivOrders* curOrders;
DivChannelData* curPat;
DivSubSong* curSubSong;
DivInstrument* tempIns; DivInstrument* tempIns;
DivSystem sysOfChan[DIV_MAX_CHANS]; DivSystem sysOfChan[DIV_MAX_CHANS];
int dispatchOfChan[DIV_MAX_CHANS]; int dispatchOfChan[DIV_MAX_CHANS];
@ -606,6 +613,9 @@ class DivEngine {
// get current row // get current row
int getRow(); int getRow();
// get current subsong
size_t getCurrentSubSong();
// get speed 1 // get speed 1
unsigned char getSpeed1(); unsigned char getSpeed1();
@ -802,6 +812,15 @@ class DivEngine {
// public swap channels // public swap channels
void swapChannelsP(int src, int dest); 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 // change system
void changeSystem(int index, DivSystem which, bool preserveOrder=true); void changeSystem(int index, DivSystem which, bool preserveOrder=true);
@ -903,6 +922,7 @@ class DivEngine {
curOrder(0), curOrder(0),
remainingLoops(-1), remainingLoops(-1),
nextSpeed(3), nextSpeed(3),
curSubSongIndex(0),
divider(60), divider(60),
cycles(0), cycles(0),
clockDrift(0), clockDrift(0),
@ -938,6 +958,8 @@ class DivEngine {
metroAmp(0.0f), metroAmp(0.0f),
metroVol(1.0f), metroVol(1.0f),
totalProcessed(0), totalProcessed(0),
curOrders(NULL),
curPat(NULL),
tempIns(NULL), tempIns(NULL),
oscBuf{NULL,NULL}, oscBuf{NULL,NULL},
oscSize(1), oscSize(1),
@ -962,6 +984,8 @@ class DivEngine {
sysFileMapFur[i]=DIV_SYSTEM_NULL; sysFileMapFur[i]=DIV_SYSTEM_NULL;
sysFileMapDMF[i]=DIV_SYSTEM_NULL; sysFileMapDMF[i]=DIV_SYSTEM_NULL;
} }
changeSong(0);
} }
}; };
#endif #endif

View file

@ -184,57 +184,57 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
logI("reading module data..."); logI("reading module data...");
if (ds.version>0x0c) { if (ds.version>0x0c) {
ds.hilightA=reader.readC(); ds.subsong[0]->hilightA=reader.readC();
ds.hilightB=reader.readC(); ds.subsong[0]->hilightB=reader.readC();
} }
ds.timeBase=reader.readC(); ds.subsong[0]->timeBase=reader.readC();
ds.speed1=reader.readC(); ds.subsong[0]->speed1=reader.readC();
if (ds.version>0x05) { if (ds.version>0x07) {
ds.speed2=reader.readC(); ds.subsong[0]->speed2=reader.readC();
ds.pal=reader.readC(); ds.subsong[0]->pal=reader.readC();
ds.hz=(ds.pal)?60:50; ds.subsong[0]->hz=(ds.subsong[0]->pal)?60:50;
ds.customTempo=reader.readC(); ds.subsong[0]->customTempo=reader.readC();
} else { } else {
ds.speed2=ds.speed1; ds.subsong[0]->speed2=ds.subsong[0]->speed1;
} }
if (ds.version>0x0a) { if (ds.version>0x0a) {
String hz=reader.readString(3); String hz=reader.readString(3);
if (ds.customTempo) { if (ds.subsong[0]->customTempo) {
try { try {
ds.hz=std::stoi(hz); ds.subsong[0]->hz=std::stoi(hz);
} catch (std::exception& e) { } catch (std::exception& e) {
logW("invalid custom Hz!"); logW("invalid custom Hz!");
ds.hz=60; ds.subsong[0]->hz=60;
} }
} }
} }
if (ds.version>0x17) { if (ds.version>0x17) {
ds.patLen=reader.readI(); ds.subsong[0]->patLen=reader.readI();
} else { } 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!"); logE("pattern length is negative!");
lastError="pattern lengrh is negative!"; lastError="pattern lengrh is negative!";
delete[] file; delete[] file;
return false; return false;
} }
if (ds.patLen>256) { if (ds.subsong[0]->patLen>256) {
logE("pattern length is too large!"); logE("pattern length is too large!");
lastError="pattern length is too large!"; lastError="pattern length is too large!";
delete[] file; delete[] file;
return false; return false;
} }
if (ds.ordersLen<0) { if (ds.subsong[0]->ordersLen<0) {
logE("song length is negative!"); logE("song length is negative!");
lastError="song length is negative!"; lastError="song length is negative!";
delete[] file; delete[] file;
return false; return false;
} }
if (ds.ordersLen>127) { if (ds.subsong[0]->ordersLen>127) {
logE("song is too long!"); logE("song is too long!");
lastError="song is too long!"; lastError="song is too long!";
delete[] file; delete[] file;
@ -242,54 +242,54 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
} }
if (ds.version<20 && ds.version>3) { if (ds.version<20 && ds.version>3) {
ds.arpLen=reader.readC(); ds.subsong[0]->arpLen=reader.readC();
} else { } else {
ds.arpLen=1; ds.subsong[0]->arpLen=1;
} }
if (ds.system[0]==DIV_SYSTEM_YMU759) { if (ds.system[0]==DIV_SYSTEM_YMU759) {
switch (ds.timeBase) { switch (ds.subsong[0]->timeBase) {
case 0: case 0:
ds.hz=248; ds.subsong[0]->hz=248;
break; break;
case 1: case 1:
ds.hz=200; ds.subsong[0]->hz=200;
break; break;
case 2: case 2:
ds.hz=100; ds.subsong[0]->hz=100;
break; break;
case 3: case 3:
ds.hz=50; ds.subsong[0]->hz=50;
break; break;
case 4: case 4:
ds.hz=25; ds.subsong[0]->hz=25;
break; break;
case 5: case 5:
ds.hz=20; ds.subsong[0]->hz=20;
break; break;
default: default:
ds.hz=248; ds.subsong[0]->hz=248;
break; break;
} }
ds.customTempo=true; ds.subsong[0]->customTempo=true;
ds.timeBase=0; ds.subsong[0]->timeBase=0;
addWarning("Yamaha YMU759 emulation is incomplete! please migrate your song to the OPL3 system."); addWarning("Yamaha YMU759 emulation is incomplete! please migrate your song to the OPL3 system.");
} }
logV("%x",reader.tell()); 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; i<getChannelCount(ds.system[0]); i++) { for (int i=0; i<getChannelCount(ds.system[0]); i++) {
for (int j=0; j<ds.ordersLen; j++) { for (int j=0; j<ds.subsong[0]->ordersLen; j++) {
ds.orders.ord[i][j]=reader.readC(); ds.subsong[0]->orders.ord[i][j]=reader.readC();
if (ds.orders.ord[i][j]>0x7f) { if (ds.subsong[0]->orders.ord[i][j]>0x7f) {
logE("order at %d, %d out of range! (%d)",i,j,ds.orders.ord[i][j]); 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.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; delete[] file;
return false; return false;
} }
if (ds.version>0x18) { // 1.1 pattern names 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) { 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()); 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; i<getChannelCount(ds.system[0]); i++) { for (int i=0; i<getChannelCount(ds.system[0]); i++) {
DivChannelData& chan=ds.pat[i]; DivChannelData& chan=ds.subsong[0]->pat[i];
if (ds.version<0x0a) { if (ds.version<0x0a) {
chan.effectCols=1; chan.effectCols=1;
} else { } else {
@ -652,10 +652,10 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
delete[] file; delete[] file;
return false; return false;
} }
for (int j=0; j<ds.ordersLen; j++) { for (int j=0; j<ds.subsong[0]->ordersLen; j++) {
DivPattern* pat=chan.getPattern(ds.orders.ord[i][j],true); DivPattern* pat=chan.getPattern(ds.subsong[0]->orders.ord[i][j],true);
if (ds.version>0x08) { // current pattern format if (ds.version>0x08) { // current pattern format
for (int k=0; k<ds.patLen; k++) { for (int k=0; k<ds.subsong[0]->patLen; k++) {
// note // note
pat->data[k][0]=reader.readS(); pat->data[k][0]=reader.readS();
// octave // octave
@ -723,7 +723,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
} }
} else { // historic pattern format } else { // historic pattern format
if (i<16) pat->data[0][2]=historicColIns[i]; if (i<16) pat->data[0][2]=historicColIns[i];
for (int k=0; k<ds.patLen; k++) { for (int k=0; k<ds.subsong[0]->patLen; k++) {
// note // note
pat->data[k][0]=reader.readC(); pat->data[k][0]=reader.readC();
// octave // octave
@ -788,7 +788,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
pitch=reader.readC(); pitch=reader.readC();
vol=reader.readC(); vol=reader.readC();
} }
if (ds.version<=0x05) { if (ds.version<=0x08) {
sample->rate=ymuSampleRate*400; sample->rate=ymuSampleRate*400;
} }
if (ds.version>0x15) { if (ds.version>0x15) {
@ -798,15 +798,15 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
sample->depth=16; sample->depth=16;
} }
} else { } else {
if (ds.version>0x05) { if (ds.version>0x08) {
sample->depth=16; sample->depth=16;
} else { } else {
// it appears samples were stored as ADPCM back then // it appears samples were stored as ADPCM back then
sample->depth=6; sample->depth=3;
} }
} }
if (length>0) { if (length>0) {
if (ds.version>0x05) { if (ds.version>0x08) {
if (ds.version<0x0b) { if (ds.version<0x0b) {
data=new short[1+(length/2)]; data=new short[1+(length/2)];
reader.read(data,length); reader.read(data,length);
@ -842,8 +842,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
delete[] data; delete[] data;
} else { } else {
// ADPCM? // YMZ ADPCM
// it appears to be a slightly modified version of ADPCM-B!
adpcmData=new unsigned char[length]; adpcmData=new unsigned char[length];
logV("%x",reader.tell()); logV("%x",reader.tell());
reader.read(adpcmData,length); reader.read(adpcmData,length);
@ -854,7 +853,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
logE("%d: error while initializing sample!",i); logE("%d: error while initializing sample!",i);
} }
memcpy(sample->dataB,adpcmData,length); memcpy(sample->dataZ,adpcmData,length);
delete[] adpcmData; delete[] adpcmData;
} }
} }
@ -906,6 +905,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
saveLock.lock(); saveLock.lock();
song.unload(); song.unload();
song=ds; song=ds;
changeSong(0);
recalcChans(); recalcChans();
saveLock.unlock(); saveLock.unlock();
BUSY_END; BUSY_END;
@ -930,13 +930,16 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
unsigned int insPtr[256]; unsigned int insPtr[256];
unsigned int wavePtr[256]; unsigned int wavePtr[256];
unsigned int samplePtr[256]; unsigned int samplePtr[256];
unsigned int subSongPtr[256];
std::vector<int> patPtr; std::vector<int> patPtr;
int numberOfSubSongs=0;
char magic[5]; char magic[5];
memset(magic,0,5); memset(magic,0,5);
SafeReader reader=SafeReader(file,len); SafeReader reader=SafeReader(file,len);
warnings=""; warnings="";
try { try {
DivSong ds; DivSong ds;
DivSubSong* subSong=ds.subsong[0];
if (!reader.seek(16,SEEK_SET)) { if (!reader.seek(16,SEEK_SET)) {
logE("premature end of file!"); logE("premature end of file!");
@ -1046,44 +1049,44 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
} }
reader.readI(); reader.readI();
ds.timeBase=reader.readC(); subSong->timeBase=reader.readC();
ds.speed1=reader.readC(); subSong->speed1=reader.readC();
ds.speed2=reader.readC(); subSong->speed2=reader.readC();
ds.arpLen=reader.readC(); subSong->arpLen=reader.readC();
ds.hz=reader.readF(); subSong->hz=reader.readF();
ds.pal=(ds.hz>=53); subSong->pal=(subSong->hz>=53);
ds.customTempo=true; subSong->customTempo=true;
ds.patLen=reader.readS(); subSong->patLen=reader.readS();
ds.ordersLen=reader.readS(); subSong->ordersLen=reader.readS();
ds.hilightA=reader.readC(); subSong->hilightA=reader.readC();
ds.hilightB=reader.readC(); subSong->hilightB=reader.readC();
ds.insLen=reader.readS(); ds.insLen=reader.readS();
ds.waveLen=reader.readS(); ds.waveLen=reader.readS();
ds.sampleLen=reader.readS(); ds.sampleLen=reader.readS();
int numberOfPats=reader.readI(); int numberOfPats=reader.readI();
if (ds.patLen<0) { if (subSong->patLen<0) {
logE("pattern length is negative!"); logE("pattern length is negative!");
lastError="pattern lengrh is negative!"; lastError="pattern lengrh is negative!";
delete[] file; delete[] file;
return false; return false;
} }
if (ds.patLen>256) { if (subSong->patLen>256) {
logE("pattern length is too large!"); logE("pattern length is too large!");
lastError="pattern length is too large!"; lastError="pattern length is too large!";
delete[] file; delete[] file;
return false; return false;
} }
if (ds.ordersLen<0) { if (subSong->ordersLen<0) {
logE("song length is negative!"); logE("song length is negative!");
lastError="song length is negative!"; lastError="song length is negative!";
delete[] file; delete[] file;
return false; return false;
} }
if (ds.ordersLen>256) { if (subSong->ordersLen>256) {
logE("song is too long!"); logE("song is too long!");
lastError="song is too long!"; lastError="song is too long!";
delete[] file; delete[] file;
@ -1296,18 +1299,18 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
reader.read(samplePtr,ds.sampleLen*4); reader.read(samplePtr,ds.sampleLen*4);
for (int i=0; i<numberOfPats; i++) patPtr.push_back(reader.readI()); for (int i=0; i<numberOfPats; i++) patPtr.push_back(reader.readI());
logD("reading orders (%d)...",ds.ordersLen); logD("reading orders (%d)...",subSong->ordersLen);
for (int i=0; i<tchans; i++) { for (int i=0; i<tchans; i++) {
for (int j=0; j<ds.ordersLen; j++) { for (int j=0; j<subSong->ordersLen; j++) {
ds.orders.ord[i][j]=reader.readC(); subSong->orders.ord[i][j]=reader.readC();
} }
} }
for (int i=0; i<tchans; i++) { for (int i=0; i<tchans; i++) {
ds.pat[i].effectCols=reader.readC(); subSong->pat[i].effectCols=reader.readC();
if (ds.pat[i].effectCols<1 || ds.pat[i].effectCols>8) { if (subSong->pat[i].effectCols<1 || subSong->pat[i].effectCols>8) {
logE("channel %d has zero or too many effect columns! (%d)",i,ds.pat[i].effectCols); 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,ds.pat[i].effectCols); lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,subSong->pat[i].effectCols);
delete[] file; delete[] file;
return false; return false;
} }
@ -1315,25 +1318,25 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (ds.version>=39) { if (ds.version>=39) {
for (int i=0; i<tchans; i++) { for (int i=0; i<tchans; i++) {
ds.chanShow[i]=reader.readC(); subSong->chanShow[i]=reader.readC();
} }
for (int i=0; i<tchans; i++) { for (int i=0; i<tchans; i++) {
ds.chanCollapse[i]=reader.readC(); subSong->chanCollapse[i]=reader.readC();
} }
if (ds.version<92) { if (ds.version<92) {
for (int i=0; i<tchans; i++) { for (int i=0; i<tchans; i++) {
if (ds.chanCollapse[i]>0) ds.chanCollapse[i]=3; if (subSong->chanCollapse[i]>0) subSong->chanCollapse[i]=3;
} }
} }
for (int i=0; i<tchans; i++) { for (int i=0; i<tchans; i++) {
ds.chanName[i]=reader.readString(); subSong->chanName[i]=reader.readString();
} }
for (int i=0; i<tchans; i++) { for (int i=0; i<tchans; i++) {
ds.chanShortName[i]=reader.readString(); subSong->chanShortName[i]=reader.readString();
} }
ds.notes=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; i<numberOfSubSongs; i++) {
subSongPtr[i]=reader.readI();
}
for (int i=0; i<numberOfSubSongs; i++) {
ds.subsong.push_back(new DivSubSong);
if (!reader.seek(subSongPtr[i],SEEK_SET)) {
logE("couldn't seek to subsong %d!",i+1);
lastError=fmt::sprintf("couldn't seek to subsong %d!",i+1);
ds.unload();
delete[] file;
return false;
}
reader.read(magic,4);
if (strcmp(magic,"SONG")!=0) {
logE("%d: invalid subsong header!",i);
lastError="invalid subsong header!";
ds.unload();
delete[] file;
return false;
}
reader.readI();
subSong=ds.subsong[i+1];
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;
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; j<tchans; j++) {
for (int k=0; k<subSong->ordersLen; k++) {
subSong->orders.ord[j][k]=reader.readC();
}
}
for (int i=0; i<tchans; i++) {
subSong->pat[i].effectCols=reader.readC();
}
for (int i=0; i<tchans; i++) {
subSong->chanShow[i]=reader.readC();
}
for (int i=0; i<tchans; i++) {
subSong->chanCollapse[i]=reader.readC();
}
for (int i=0; i<tchans; i++) {
subSong->chanName[i]=reader.readString();
}
for (int i=0; i<tchans; i++) {
subSong->chanShortName[i]=reader.readString();
}
}
}
// read instruments // read instruments
for (int i=0; i<ds.insLen; i++) { for (int i=0; i<ds.insLen; i++) {
DivInstrument* ins=new DivInstrument; DivInstrument* ins=new DivInstrument;
@ -1569,9 +1654,15 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
int chan=reader.readS(); int chan=reader.readS();
int index=reader.readS(); int index=reader.readS();
reader.readI(); int subs=0;
if (ds.version>=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) { if (chan<0 || chan>=tchans) {
logE("pattern channel out of range!",i); logE("pattern channel out of range!",i);
@ -1587,14 +1678,21 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
delete[] file; delete[] file;
return false; 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); DivPattern* pat=ds.subsong[subs]->pat[chan].getPattern(index,true);
for (int j=0; j<ds.patLen; j++) { for (int j=0; j<ds.subsong[subs]->patLen; j++) {
pat->data[j][0]=reader.readS(); pat->data[j][0]=reader.readS();
pat->data[j][1]=reader.readS(); pat->data[j][1]=reader.readS();
pat->data[j][2]=reader.readS(); pat->data[j][2]=reader.readS();
pat->data[j][3]=reader.readS(); pat->data[j][3]=reader.readS();
for (int k=0; k<ds.pat[chan].effectCols; k++) { for (int k=0; k<ds.subsong[subs]->pat[chan].effectCols; k++) {
pat->data[j][4+(k<<1)]=reader.readS(); pat->data[j][4+(k<<1)]=reader.readS();
pat->data[j][5+(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(); saveLock.lock();
song.unload(); song.unload();
song=ds; song=ds;
changeSong(0);
recalcChans(); recalcChans();
saveLock.unlock(); saveLock.unlock();
BUSY_END; BUSY_END;
@ -1741,8 +1840,8 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
ds.sampleLen=ds.sample.size(); ds.sampleLen=ds.sample.size();
// orders // orders
ds.ordersLen=ordCount=reader.readC(); ds.subsong[0]->ordersLen=ordCount=reader.readC();
if (ds.ordersLen<1 || ds.ordersLen>127) { if (ds.subsong[0]->ordersLen<1 || ds.subsong[0]->ordersLen>127) {
logD("invalid order count!"); logD("invalid order count!");
throw EndOfFileException(&reader,reader.tell()); throw EndOfFileException(&reader,reader.tell());
} }
@ -1762,7 +1861,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
unsigned char pat=reader.readC(); unsigned char pat=reader.readC();
if (pat>patMax) patMax=pat; if (pat>patMax) patMax=pat;
for (int j=0; j<chCount; j++) { for (int j=0; j<chCount; j++) {
ds.orders.ord[j][i]=pat; ds.subsong[0]->orders.ord[j][i]=pat;
} }
} }
@ -1779,7 +1878,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
} }
// patterns // patterns
ds.patLen=64; ds.subsong[0]->patLen=64;
for (int ch=0; ch<chCount; ch++) { for (int ch=0; ch<chCount; ch++) {
for (int i=0; i<5; i++) { for (int i=0; i<5; i++) {
fxUsage[ch][i]=false; fxUsage[ch][i]=false;
@ -1788,7 +1887,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
for (int pat=0; pat<=patMax; pat++) { for (int pat=0; pat<=patMax; pat++) {
DivPattern* chpats[DIV_MAX_CHANS]; DivPattern* chpats[DIV_MAX_CHANS];
for (int ch=0; ch<chCount; ch++) { for (int ch=0; ch<chCount; ch++) {
chpats[ch]=ds.pat[ch].getPattern(pat,true); chpats[ch]=ds.subsong[0]->pat[ch].getPattern(pat,true);
} }
for (int row=0; row<64; row++) { for (int row=0; row<64; row++) {
for (int ch=0; ch<chCount; ch++) { for (int ch=0; ch<chCount; ch++) {
@ -1861,7 +1960,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
for (int ch=0; ch<=chCount; ch++) { for (int ch=0; ch<=chCount; ch++) {
unsigned char fxCols=1; unsigned char fxCols=1;
for (int pat=0; pat<=patMax; pat++) { for (int pat=0; pat<=patMax; pat++) {
auto* data=ds.pat[ch].getPattern(pat,true)->data; auto* data=ds.subsong[0]->pat[ch].getPattern(pat,true)->data;
short lastPitchEffect=-1; short lastPitchEffect=-1;
short lastEffectState[5]={-1,-1,-1,-1,-1}; short lastEffectState[5]={-1,-1,-1,-1,-1};
short setEffectState[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.subsong[0]->pal=false;
ds.hz=50; ds.subsong[0]->hz=50;
ds.customTempo=false; ds.subsong[0]->customTempo=false;
ds.systemLen=(chCount+3)/4; ds.systemLen=(chCount+3)/4;
for(int i=0; i<ds.systemLen; i++) { for(int i=0; i<ds.systemLen; i++) {
ds.system[i]=DIV_SYSTEM_AMIGA; ds.system[i]=DIV_SYSTEM_AMIGA;
ds.systemFlags[i]=1|(80<<8)|(bypassLimits?4:0)|((ds.systemLen>1 || bypassLimits)?2:0); // PAL ds.systemFlags[i]=1|(80<<8)|(bypassLimits?4:0)|((ds.systemLen>1 || bypassLimits)?2:0); // PAL
} }
for(int i=0; i<chCount; i++) { for(int i=0; i<chCount; i++) {
ds.chanShow[i]=true; ds.subsong[0]->chanShow[i]=true;
ds.chanName[i]=fmt::sprintf("Channel %d",i+1); ds.subsong[0]->chanName[i]=fmt::sprintf("Channel %d",i+1);
ds.chanShortName[i]=fmt::sprintf("C%d",i+1); ds.subsong[0]->chanShortName[i]=fmt::sprintf("C%d",i+1);
} }
for(int i=chCount; i<ds.systemLen*4; i++) { for(int i=chCount; i<ds.systemLen*4; i++) {
ds.pat[i].effectCols=1; ds.subsong[0]->pat[i].effectCols=1;
ds.chanShow[i]=false; ds.subsong[0]->chanShow[i]=false;
} }
// instrument creation // instrument creation
@ -2025,6 +2124,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
saveLock.lock(); saveLock.lock();
song.unload(); song.unload();
song=ds; song=ds;
changeSong(0);
recalcChans(); recalcChans();
saveLock.unlock(); saveLock.unlock();
BUSY_END; BUSY_END;
@ -2119,8 +2219,8 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
newVibrato=reader.readI(); newVibrato=reader.readI();
} }
if (blockVersion>=4) { if (blockVersion>=4) {
ds.hilightA=reader.readI(); ds.subsong[0]->hilightA=reader.readI();
ds.hilightB=reader.readI(); ds.subsong[0]->hilightB=reader.readI();
} }
if (expansions&8) if (blockVersion>=5) { // N163 channels if (expansions&8) if (blockVersion>=5) { // N163 channels
n163Chans=reader.readI(); n163Chans=reader.readI();
@ -2136,12 +2236,12 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
logV("custom Hz: %d",customHz); logV("custom Hz: %d",customHz);
logV("new vibrato: %d",newVibrato); logV("new vibrato: %d",newVibrato);
logV("N163 channels: %d",n163Chans); logV("N163 channels: %d",n163Chans);
logV("highlight 1: %d",ds.hilightA); logV("highlight 1: %d",ds.subsong[0]->hilightA);
logV("highlight 2: %d",ds.hilightB); logV("highlight 2: %d",ds.subsong[0]->hilightB);
logV("split point: %d",speedSplitPoint); logV("split point: %d",speedSplitPoint);
if (customHz!=0) { if (customHz!=0) {
ds.hz=customHz; ds.subsong[0]->hz=customHz;
} }
// initialize channels // initialize channels
@ -2202,7 +2302,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
for (int j=0; j<=totalSongs; j++) { for (int j=0; j<=totalSongs; j++) {
unsigned char effectCols=reader.readC(); unsigned char effectCols=reader.readC();
if (j==0) { 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); logV("- song %d has %d effect columns",j,effectCols);
} }
@ -2533,15 +2633,55 @@ bool DivEngine::load(unsigned char* f, size_t slen) {
return false; 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) { SafeWriter* DivEngine::saveFur(bool notPrimary) {
saveLock.lock(); saveLock.lock();
int insPtr[256]; std::vector<int> subSongPtr;
int wavePtr[256]; std::vector<int> insPtr;
int samplePtr[256]; std::vector<int> wavePtr;
std::vector<int> samplePtr;
std::vector<int> patPtr; std::vector<int> patPtr;
size_t ptrSeek; size_t ptrSeek, subSongPtrSeek;
size_t subSongIndex=0;
DivSubSong* subSong=song.subsong[subSongIndex];
warnings=""; 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) { if (!notPrimary) {
song.isDMF=false; song.isDMF=false;
song.version=DIV_ENGINE_VERSION; song.version=DIV_ENGINE_VERSION;
@ -2568,14 +2708,17 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
// high short is channel // high short is channel
// low short is pattern number // low short is pattern number
std::vector<int> patsToWrite; std::vector<PatToWrite> patsToWrite;
bool alreadyAdded[256]; bool alreadyAdded[256];
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
memset(alreadyAdded,0,256*sizeof(bool)); for (size_t j=0; j<song.subsong.size(); j++) {
for (int j=0; j<song.ordersLen; j++) { DivSubSong* subs=song.subsong[j];
if (alreadyAdded[song.orders.ord[i][j]]) continue; memset(alreadyAdded,0,256*sizeof(bool));
patsToWrite.push_back((i<<16)|song.orders.ord[i][j]); for (int k=0; k<subs->ordersLen; k++) {
alreadyAdded[song.orders.ord[i][j]]=true; 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->write("INFO",4);
w->writeI(0); w->writeI(0);
w->writeC(song.timeBase); w->writeC(subSong->timeBase);
w->writeC(song.speed1); w->writeC(subSong->speed1);
w->writeC(song.speed2); w->writeC(subSong->speed2);
w->writeC(song.arpLen); w->writeC(subSong->arpLen);
w->writeF(song.hz); w->writeF(subSong->hz);
w->writeS(song.patLen); w->writeS(subSong->patLen);
w->writeS(song.ordersLen); w->writeS(subSong->ordersLen);
w->writeC(song.hilightA); w->writeC(subSong->hilightA);
w->writeC(song.hilightB); w->writeC(subSong->hilightB);
w->writeS(song.insLen); w->writeS(song.insLen);
w->writeS(song.waveLen); w->writeS(song.waveLen);
w->writeS(song.sampleLen); w->writeS(song.sampleLen);
@ -2668,29 +2811,29 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
} }
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
for (int j=0; j<song.ordersLen; j++) { for (int j=0; j<subSong->ordersLen; j++) {
w->writeC(song.orders.ord[i][j]); w->writeC(subSong->orders.ord[i][j]);
} }
} }
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
w->writeC(song.pat[i].effectCols); w->writeC(subSong->pat[i].effectCols);
} }
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
w->writeC(song.chanShow[i]); w->writeC(subSong->chanShow[i]);
} }
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
w->writeC(song.chanCollapse[i]); w->writeC(subSong->chanCollapse[i]);
} }
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
w->writeString(song.chanName[i],false); w->writeString(subSong->chanName[i],false);
} }
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
w->writeString(song.chanShortName[i],false); w->writeString(subSong->chanShortName[i],false);
} }
w->writeString(song.notes,false); w->writeString(song.notes,false);
@ -2716,24 +2859,85 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeC(0); 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; subSongIndex<song.subsong.size(); subSongIndex++) {
subSong=song.subsong[subSongIndex];
subSongPtr.push_back(w->tell());
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; i<chans; i++) {
for (int j=0; j<subSong->ordersLen; j++) {
w->writeC(subSong->orders.ord[i][j]);
}
}
for (int i=0; i<chans; i++) {
w->writeC(subSong->pat[i].effectCols);
}
for (int i=0; i<chans; i++) {
w->writeC(subSong->chanShow[i]);
}
for (int i=0; i<chans; i++) {
w->writeC(subSong->chanCollapse[i]);
}
for (int i=0; i<chans; i++) {
w->writeString(subSong->chanName[i],false);
}
for (int i=0; i<chans; i++) {
w->writeString(subSong->chanShortName[i],false);
}
}
/// INSTRUMENT /// INSTRUMENT
for (int i=0; i<song.insLen; i++) { for (int i=0; i<song.insLen; i++) {
DivInstrument* ins=song.ins[i]; DivInstrument* ins=song.ins[i];
insPtr[i]=w->tell(); insPtr.push_back(w->tell());
ins->putInsData(w); ins->putInsData(w);
} }
/// WAVETABLE /// WAVETABLE
for (int i=0; i<song.waveLen; i++) { for (int i=0; i<song.waveLen; i++) {
DivWavetable* wave=song.wave[i]; DivWavetable* wave=song.wave[i];
wavePtr[i]=w->tell(); wavePtr.push_back(w->tell());
wave->putWaveData(w); wave->putWaveData(w);
} }
/// SAMPLE /// SAMPLE
for (int i=0; i<song.sampleLen; i++) { for (int i=0; i<song.sampleLen; i++) {
DivSample* sample=song.sample[i]; DivSample* sample=song.sample[i];
samplePtr[i]=w->tell(); samplePtr.push_back(w->tell());
w->write("SMPL",4); w->write("SMPL",4);
w->writeI(0); w->writeI(0);
@ -2750,23 +2954,24 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
} }
/// PATTERN /// PATTERN
for (int i: patsToWrite) { for (PatToWrite& i: patsToWrite) {
DivPattern* pat=song.pat[i>>16].getPattern(i&0xffff,false); DivPattern* pat=song.subsong[i.subsong]->pat[i.chan].getPattern(i.pat,false);
patPtr.push_back(w->tell()); patPtr.push_back(w->tell());
w->write("PATR",4); w->write("PATR",4);
w->writeI(0); w->writeI(0);
w->writeS(i>>16); w->writeS(i.chan);
w->writeS(i&0xffff); w->writeS(i.pat);
w->writeS(i.subsong);
w->writeI(0); // reserved w->writeS(0); // reserved
for (int j=0; j<song.patLen; j++) { for (int j=0; j<song.subsong[i.subsong]->patLen; j++) {
w->writeS(pat->data[j][0]); // note w->writeS(pat->data[j][0]); // note
w->writeS(pat->data[j][1]); // octave w->writeS(pat->data[j][1]); // octave
w->writeS(pat->data[j][2]); // instrument w->writeS(pat->data[j][2]); // instrument
w->writeS(pat->data[j][3]); // volume 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); w->writeString(pat->name,false);
@ -2779,21 +2984,29 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeI(insPtr[i]); w->writeI(insPtr[i]);
} }
// wavetable pointers (we'll seek here later) // wavetable pointers
for (int i=0; i<song.waveLen; i++) { for (int i=0; i<song.waveLen; i++) {
w->writeI(wavePtr[i]); w->writeI(wavePtr[i]);
} }
// sample pointers (we'll seek here later) // sample pointers
for (int i=0; i<song.sampleLen; i++) { for (int i=0; i<song.sampleLen; i++) {
w->writeI(samplePtr[i]); w->writeI(samplePtr[i]);
} }
// pattern pointers (we'll seek here later) // pattern pointers
for (int i: patPtr) { for (int i: patPtr) {
w->writeI(i); 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(); saveLock.unlock();
return w; return w;
} }
@ -2864,7 +3077,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
return NULL; return NULL;
} }
// fail if values are out of range // fail if values are out of range
if (song.ordersLen>127) { if (curSubSong->ordersLen>127) {
logE("maximum .dmf song length is 127!"); logE("maximum .dmf song length is 127!");
lastError="maximum .dmf song length is 127"; lastError="maximum .dmf song length is 127";
return NULL; return NULL;
@ -2880,10 +3093,10 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
return NULL; return NULL;
} }
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
for (int j=0; j<song.ordersLen; j++) { for (int j=0; j<curSubSong->ordersLen; j++) {
if (song.orders.ord[i][j]>0x7f) { if (curOrders->ord[i][j]>0x7f) {
logE("order %d, %d is out of range (0-127)!",song.orders.ord[i][j]); 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)",song.orders.ord[i][j]); lastError=fmt::sprintf("order %d, %d is out of range (0-127)",curOrders->ord[i][j]);
return NULL; return NULL;
} }
} }
@ -2926,31 +3139,35 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
// song info // song info
w->writeString(song.name,true); w->writeString(song.name,true);
w->writeString(song.author,true); w->writeString(song.author,true);
w->writeC(song.hilightA); w->writeC(curSubSong->hilightA);
w->writeC(song.hilightB); w->writeC(curSubSong->hilightB);
w->writeC(song.timeBase); w->writeC(curSubSong->timeBase);
w->writeC(song.speed1); w->writeC(curSubSong->speed1);
w->writeC(song.speed2); w->writeC(curSubSong->speed2);
w->writeC(song.pal); w->writeC(curSubSong->pal);
w->writeC(song.customTempo); w->writeC(curSubSong->customTempo);
char customHz[4]; char customHz[4];
memset(customHz,0,4); memset(customHz,0,4);
snprintf(customHz,4,"%d",(int)song.hz); snprintf(customHz,4,"%d",(int)curSubSong->hz);
w->write(customHz,3); w->write(customHz,3);
w->writeI(song.patLen); w->writeI(curSubSong->patLen);
w->writeC(song.ordersLen); w->writeC(curSubSong->ordersLen);
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
for (int j=0; j<song.ordersLen; j++) { for (int j=0; j<curSubSong->ordersLen; j++) {
w->writeC(song.orders.ord[i][j]); w->writeC(curOrders->ord[i][j]);
if (version>=25) { if (version>=25) {
DivPattern* pat=song.pat[i].getPattern(j,false); DivPattern* pat=curPat[i].getPattern(j,false);
w->writeString(pat->name,true); 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) { if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) {
addWarning("absolute duty/cutoff macro not available in .dmf!"); addWarning("absolute duty/cutoff macro not available in .dmf!");
addWarning("duty precision will be lost"); addWarning("duty precision will be lost");
@ -3112,15 +3329,15 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
} }
for (int i=0; i<getChannelCount(sys); i++) { for (int i=0; i<getChannelCount(sys); i++) {
w->writeC(song.pat[i].effectCols); w->writeC(curPat[i].effectCols);
for (int j=0; j<song.ordersLen; j++) { for (int j=0; j<curSubSong->ordersLen; j++) {
DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][j],false); DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][j],false);
for (int k=0; k<song.patLen; k++) { for (int k=0; k<curSubSong->patLen; k++) {
w->writeS(pat->data[k][0]); // note w->writeS(pat->data[k][0]); // note
w->writeS(pat->data[k][1]); // octave w->writeS(pat->data[k][1]); // octave
w->writeS(pat->data[k][3]); // volume 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 w->writeS(pat->data[k][2]); // instrument
} }
} }

View file

@ -49,7 +49,7 @@ void DivChannelData::wipePatterns() {
} }
} }
void DivPattern::copyOn(DivPattern *dest) { void DivPattern::copyOn(DivPattern* dest) {
dest->name=name; dest->name=name;
memcpy(dest->data,data,sizeof(data)); memcpy(dest->data,data,sizeof(data));
} }

View file

@ -17,20 +17,20 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#include "ym2610bext.h" #include "ym2203ext.h"
#include "../engine.h" #include "../engine.h"
#include <math.h> #include <math.h>
#include "ym2610shared.h" #include "ym2610shared.h"
#include "fmshared_OPN.h" #include "fmshared_OPN.h"
int DivPlatformYM2610BExt::dispatch(DivCommand c) { int DivPlatformYM2203Ext::dispatch(DivCommand c) {
if (c.chan<2) { if (c.chan<2) {
return DivPlatformYM2610B::dispatch(c); return DivPlatformYM2203::dispatch(c);
} }
if (c.chan>5) { if (c.chan>5) {
c.chan-=3; c.chan-=3;
return DivPlatformYM2610B::dispatch(c); return DivPlatformYM2203::dispatch(c);
} }
int ch=c.chan-2; int ch=c.chan-2;
int ordch=orderedOps[ch]; int ordch=orderedOps[ch];
@ -384,7 +384,7 @@ static int opChanOffsH[4]={
0xad, 0xae, 0xac, 0xa6 0xad, 0xae, 0xac, 0xa6
}; };
void DivPlatformYM2610BExt::tick(bool sysTick) { void DivPlatformYM2203Ext::tick(bool sysTick) {
if (extMode) { if (extMode) {
bool writeSomething=false; bool writeSomething=false;
unsigned char writeMask=2; unsigned char writeMask=2;
@ -401,7 +401,7 @@ void DivPlatformYM2610BExt::tick(bool sysTick) {
} }
} }
DivPlatformYM2610B::tick(sysTick); DivPlatformYM2203::tick(sysTick);
bool writeNoteOn=false; bool writeNoteOn=false;
unsigned char writeMask=2; 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) { if (ch<2) {
DivPlatformYM2610B::muteChannel(ch,mute); DivPlatformYM2203::muteChannel(ch,mute);
return; return;
} }
if (ch>5) { if (ch>5) {
DivPlatformYM2610B::muteChannel(ch-3,mute); DivPlatformYM2203::muteChannel(ch-3,mute);
return; return;
} }
isOpMuted[ch-2]=mute; 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 i=0; i<6; i++) {
for (int j=0; j<4; j++) { for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j]; unsigned short baseAddr=chanOffs[i]|opOffs[j];
@ -494,7 +494,6 @@ void DivPlatformYM2610BExt::forceIns() {
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); 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_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) { if (chan[i].active) {
chan[i].keyOn=true; chan[i].keyOn=true;
chan[i].freqChanged=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>=6) return &chan[ch-3];
if (ch>=2) return &opChan[ch-2]; if (ch>=2) return &opChan[ch-2];
return &chan[ch]; return &chan[ch];
} }
DivDispatchOscBuffer* DivPlatformYM2610BExt::getOscBuffer(int ch) { DivDispatchOscBuffer* DivPlatformYM2203Ext::getOscBuffer(int ch) {
if (ch>=6) return oscBuf[ch-3]; if (ch>=6) return oscBuf[ch-3];
if (ch<3) return oscBuf[ch]; if (ch<3) return oscBuf[ch];
return NULL; return NULL;
} }
void DivPlatformYM2610BExt::reset() { void DivPlatformYM2203Ext::reset() {
DivPlatformYM2610B::reset(); DivPlatformYM2203::reset();
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
opChan[i]=DivPlatformYM2610BExt::OpChannel(); opChan[i]=DivPlatformYM2203Ext::OpChannel();
opChan[i].vol=127; opChan[i].vol=127;
} }
@ -543,12 +542,12 @@ void DivPlatformYM2610BExt::reset() {
extMode=true; extMode=true;
} }
bool DivPlatformYM2610BExt::keyOffAffectsArp(int ch) { bool DivPlatformYM2203Ext::keyOffAffectsArp(int ch) {
return (ch>8); return (ch>8);
} }
void DivPlatformYM2610BExt::notifyInsChange(int ins) { void DivPlatformYM2203Ext::notifyInsChange(int ins) {
DivPlatformYM2610B::notifyInsChange(ins); DivPlatformYM2203::notifyInsChange(ins);
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
if (opChan[i].ins==ins) { if (opChan[i].ins==ins) {
opChan[i].insChanged=true; 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) { int DivPlatformYM2203Ext::init(DivEngine* parent, int channels, int sugRate, unsigned int flags) {
DivPlatformYM2610B::init(parent,channels,sugRate,flags); DivPlatformYM2203::init(parent,channels,sugRate,flags);
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
isOpMuted[i]=false; isOpMuted[i]=false;
} }
@ -566,9 +565,9 @@ int DivPlatformYM2610BExt::init(DivEngine* parent, int channels, int sugRate, un
return 19; return 19;
} }
void DivPlatformYM2610BExt::quit() { void DivPlatformYM2203Ext::quit() {
DivPlatformYM2610B::quit(); DivPlatformYM2203::quit();
} }
DivPlatformYM2610BExt::~DivPlatformYM2610BExt() { DivPlatformYM2203Ext::~DivPlatformYM2203Ext() {
} }

View file

@ -19,9 +19,9 @@
#include "../dispatch.h" #include "../dispatch.h"
#include "ym2610b.h" #include "ym2203.h"
class DivPlatformYM2610BExt: public DivPlatformYM2610B { class DivPlatformYM2203Ext: public DivPlatformYM2203 {
struct OpChannel { struct OpChannel {
DivMacroInt std; DivMacroInt std;
unsigned char freqH, freqL; unsigned char freqH, freqL;
@ -47,5 +47,5 @@ class DivPlatformYM2610BExt: public DivPlatformYM2610B {
void notifyInsChange(int ins); void notifyInsChange(int ins);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit(); void quit();
~DivPlatformYM2610BExt(); ~DivPlatformYM2203Ext();
}; };

View file

@ -30,7 +30,7 @@ constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0;
void DivEngine::nextOrder() { void DivEngine::nextOrder() {
curRow=0; curRow=0;
if (repeatPattern) return; if (repeatPattern) return;
if (++curOrder>=song.ordersLen) { if (++curOrder>=curSubSong->ordersLen) {
endOfSong=true; endOfSong=true;
curOrder=0; curOrder=0;
} }
@ -266,11 +266,11 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char
void DivEngine::processRow(int i, bool afterDelay) { void DivEngine::processRow(int i, bool afterDelay) {
int whatOrder=afterDelay?chan[i].delayOrder:curOrder; int whatOrder=afterDelay?chan[i].delayOrder:curOrder;
int whatRow=afterDelay?chan[i].delayRow:curRow; 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 // pre effects
if (!afterDelay) { if (!afterDelay) {
bool returnAfterPre=false; bool returnAfterPre=false;
for (int j=0; j<song.pat[i].effectCols; j++) { for (int j=0; j<curPat[i].effectCols; j++) {
short effect=pat->data[whatRow][4+(j<<1)]; short effect=pat->data[whatRow][4+(j<<1)];
short effectVal=pat->data[whatRow][5+(j<<1)]; short effectVal=pat->data[whatRow][5+(j<<1)];
@ -290,7 +290,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
} }
break; break;
case 0x0d: // next order case 0x0d: // next order
if (changeOrd<0 && (curOrder<(song.ordersLen-1) || !song.ignoreJumpAtEnd)) { if (changeOrd<0 && (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) {
changeOrd=-2; changeOrd=-2;
changePos=effectVal; changePos=effectVal;
} }
@ -405,7 +405,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
bool panChanged=false; bool panChanged=false;
// effects // effects
for (int j=0; j<song.pat[i].effectCols; j++) { for (int j=0; j<curPat[i].effectCols; j++) {
short effect=pat->data[whatRow][4+(j<<1)]; short effect=pat->data[whatRow][4+(j<<1)];
short effectVal=pat->data[whatRow][5+(j<<1)]; short effectVal=pat->data[whatRow][5+(j<<1)];
@ -548,7 +548,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
break; break;
case 0xe0: // arp speed case 0xe0: // arp speed
if (effectVal>0) { if (effectVal>0) {
song.arpLen=effectVal; curSubSong->arpLen=effectVal;
} }
break; break;
case 0xe1: // portamento up case 0xe1: // portamento up
@ -727,7 +727,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
chan[i].noteOnInhibit=false; chan[i].noteOnInhibit=false;
// post effects // post effects
for (int j=0; j<song.pat[i].effectCols; j++) { for (int j=0; j<curPat[i].effectCols; j++) {
short effect=pat->data[whatRow][4+(j<<1)]; short effect=pat->data[whatRow][4+(j<<1)];
short effectVal=pat->data[whatRow][5+(j<<1)]; short effectVal=pat->data[whatRow][5+(j<<1)];
@ -745,10 +745,10 @@ void DivEngine::nextRow() {
strcpy(pb1,""); strcpy(pb1,"");
strcpy(pb3,""); strcpy(pb3,"");
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
snprintf(pb,4095," %.2x",song.orders.ord[i][curOrder]); snprintf(pb,4095," %.2x",curOrders->ord[i][curOrder]);
strcat(pb1,pb); 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", snprintf(pb2,4095,"\x1b[37m %s",
formatNote(pat->data[curRow][0],pat->data[curRow][1])); formatNote(pat->data[curRow][0],pat->data[curRow][1]));
strcat(pb3,pb2); strcat(pb3,pb2);
@ -764,7 +764,7 @@ void DivEngine::nextRow() {
snprintf(pb2,4095,"\x1b[0;36m%.2x",pat->data[curRow][2]); snprintf(pb2,4095,"\x1b[0;36m%.2x",pat->data[curRow][2]);
strcat(pb3,pb2); strcat(pb3,pb2);
} }
for (int j=0; j<song.pat[i].effectCols; j++) { for (int j=0; j<curPat[i].effectCols; j++) {
if (pat->data[curRow][4+(j<<1)]==-1) { if (pat->data[curRow][4+(j<<1)]==-1) {
strcat(pb3,"\x1b[m--"); strcat(pb3,"\x1b[m--");
} else { } else {
@ -796,32 +796,32 @@ void DivEngine::nextRow() {
if (changeOrd==-2) changeOrd=curOrder+1; if (changeOrd==-2) changeOrd=curOrder+1;
if (changeOrd<=curOrder) endOfSong=true; if (changeOrd<=curOrder) endOfSong=true;
curOrder=changeOrd; curOrder=changeOrd;
if (curOrder>=song.ordersLen) { if (curOrder>=curSubSong->ordersLen) {
curOrder=0; curOrder=0;
endOfSong=true; endOfSong=true;
} }
changeOrd=-1; changeOrd=-1;
} }
if (haltOn==DIV_HALT_PATTERN) halted=true; if (haltOn==DIV_HALT_PATTERN) halted=true;
} else if (playing) if (++curRow>=song.patLen) { } else if (playing) if (++curRow>=curSubSong->patLen) {
nextOrder(); nextOrder();
if (haltOn==DIV_HALT_PATTERN) halted=true; if (haltOn==DIV_HALT_PATTERN) halted=true;
} }
if (song.brokenSpeedSel) { if (song.brokenSpeedSel) {
if ((song.patLen&1) && curOrder&1) { if ((curSubSong->patLen&1) && curOrder&1) {
ticks=((curRow&1)?speed2:speed1)*(song.timeBase+1); ticks=((curRow&1)?speed2:speed1)*(curSubSong->timeBase+1);
nextSpeed=(curRow&1)?speed1:speed2; nextSpeed=(curRow&1)?speed1:speed2;
} else { } else {
ticks=((curRow&1)?speed1:speed2)*(song.timeBase+1); ticks=((curRow&1)?speed1:speed2)*(curSubSong->timeBase+1);
nextSpeed=(curRow&1)?speed2:speed1; nextSpeed=(curRow&1)?speed2:speed1;
} }
} else { } else {
if (speedAB) { if (speedAB) {
ticks=speed2*(song.timeBase+1); ticks=speed2*(curSubSong->timeBase+1);
nextSpeed=speed1; nextSpeed=speed1;
} else { } else {
ticks=speed1*(song.timeBase+1); ticks=speed1*(curSubSong->timeBase+1);
nextSpeed=speed2; nextSpeed=speed2;
} }
speedAB=!speedAB; speedAB=!speedAB;
@ -829,7 +829,7 @@ void DivEngine::nextRow() {
// post row details // post row details
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][curOrder],false); DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][curOrder],false);
if (!(pat->data[curRow][0]==0 && pat->data[curRow][1]==0)) { 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 (pat->data[curRow][0]!=100 && pat->data[curRow][0]!=101 && pat->data[curRow][0]!=102) {
if (!chan[i].legato) { if (!chan[i].legato) {
@ -838,7 +838,7 @@ void DivEngine::nextRow() {
if (song.oneTickCut) { if (song.oneTickCut) {
bool doPrepareCut=true; bool doPrepareCut=true;
for (int j=0; j<song.pat[i].effectCols; j++) { for (int j=0; j<curPat[i].effectCols; j++) {
if (pat->data[curRow][4+(j<<1)]==0x03) { if (pat->data[curRow][4+(j<<1)]==0x03) {
doPrepareCut=false; doPrepareCut=false;
break; 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].arp!=0 && !chan[i].arpYield && chan[i].portaSpeed<1) {
if (--chan[i].arpTicks<1) { if (--chan[i].arpTicks<1) {
chan[i].arpTicks=song.arpLen; chan[i].arpTicks=curSubSong->arpLen;
chan[i].arpStage++; chan[i].arpStage++;
if (chan[i].arpStage>2) chan[i].arpStage=0; if (chan[i].arpStage>2) chan[i].arpStage=0;
switch (chan[i].arpStage) { 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; 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) { if (!freelance && stepPlay!=-1 && subticks==1) {
unsigned int realPos=size-(runLeftG>>MASTER_CLOCK_PREC); unsigned int realPos=size-(runLeftG>>MASTER_CLOCK_PREC);
if (realPos>=size) realPos=size-1; if (realPos>=size) realPos=size-1;
if (song.hilightA>0) { if (curSubSong->hilightA>0) {
if ((curRow%song.hilightA)==0 && ticks==1) metroTick[realPos]=1; if ((curRow%curSubSong->hilightA)==0 && ticks==1) metroTick[realPos]=1;
} }
if (song.hilightB>0) { if (curSubSong->hilightB>0) {
if ((curRow%song.hilightB)==0 && ticks==1) metroTick[realPos]=2; if ((curRow%curSubSong->hilightB)==0 && ticks==1) metroTick[realPos]=2;
} }
} }
if (nextTick()) { if (nextTick()) {

View file

@ -93,6 +93,18 @@ bool DivSample::initInternal(unsigned char d, int count) {
dataDPCM=new unsigned char[lengthDPCM]; dataDPCM=new unsigned char[lengthDPCM];
memset(dataDPCM,0,lengthDPCM); memset(dataDPCM,0,lengthDPCM);
break; 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 case 4: // QSound ADPCM
if (dataQSoundA!=NULL) delete[] dataQSoundA; if (dataQSoundA!=NULL) delete[] dataQSoundA;
lengthQSoundA=(count+1)/2; lengthQSoundA=(count+1)/2;
@ -657,6 +669,12 @@ void DivSample::render() {
} }
break; 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 case 4: // QSound ADPCM
bs_decode(dataQSoundA,data16,samples); bs_decode(dataQSoundA,data16,samples);
break; break;
@ -709,6 +727,14 @@ void DivSample::render() {
if (accum>127) accum=127; 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 (depth!=4) { // QSound ADPCM
if (!initInternal(4,samples)) return; if (!initInternal(4,samples)) return;
bs_encode(data16,dataQSoundA,samples); bs_encode(data16,dataQSoundA,samples);
@ -745,6 +771,10 @@ void* DivSample::getCurBuf() {
return data1; return data1;
case 1: case 1:
return dataDPCM; return dataDPCM;
case 2:
return dataAICA;
case 3:
return dataZ;
case 4: case 4:
return dataQSoundA; return dataQSoundA;
case 5: case 5:
@ -771,6 +801,10 @@ unsigned int DivSample::getCurBufLen() {
return length1; return length1;
case 1: case 1:
return lengthDPCM; return lengthDPCM;
case 2:
return lengthAICA;
case 3:
return lengthZ;
case 4: case 4:
return lengthQSoundA; return lengthQSoundA;
case 5: case 5:
@ -881,6 +915,8 @@ DivSample::~DivSample() {
if (data16) delete[] data16; if (data16) delete[] data16;
if (data1) delete[] data1; if (data1) delete[] data1;
if (dataDPCM) delete[] dataDPCM; if (dataDPCM) delete[] dataDPCM;
if (dataAICA) delete[] dataAICA;
if (dataZ) delete[] dataZ;
if (dataQSoundA) delete[] dataQSoundA; if (dataQSoundA) delete[] dataQSoundA;
if (dataA) delete[] dataA; if (dataA) delete[] dataA;
if (dataB) delete[] dataB; if (dataB) delete[] dataB;

View file

@ -62,6 +62,8 @@ struct DivSample {
// valid values are: // valid values are:
// - 0: ZX Spectrum overlay drum (1-bit) // - 0: ZX Spectrum overlay drum (1-bit)
// - 1: 1-bit NES DPCM (1-bit) // - 1: 1-bit NES DPCM (1-bit)
// - 2: AICA ADPCM
// - 3: YMZ ADPCM
// - 4: QSound ADPCM // - 4: QSound ADPCM
// - 5: ADPCM-A // - 5: ADPCM-A
// - 6: ADPCM-B // - 6: ADPCM-B
@ -77,6 +79,8 @@ struct DivSample {
short* data16; // 16 short* data16; // 16
unsigned char* data1; // 0 unsigned char* data1; // 0
unsigned char* dataDPCM; // 1 unsigned char* dataDPCM; // 1
unsigned char* dataAICA; // 2
unsigned char* dataZ; // 3
unsigned char* dataQSoundA; // 4 unsigned char* dataQSoundA; // 4
unsigned char* dataA; // 5 unsigned char* dataA; // 5
unsigned char* dataB; // 6 unsigned char* dataB; // 6
@ -84,8 +88,8 @@ struct DivSample {
unsigned char* dataBRR; // 9 unsigned char* dataBRR; // 9
unsigned char* dataVOX; // 10 unsigned char* dataVOX; // 10
unsigned int length8, length16, length1, lengthDPCM, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX; unsigned int length8, length16, length1, lengthDPCM, lengthAICA, lengthZ, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX;
unsigned int off8, off16, off1, offDPCM, offQSoundA, offA, offB, offX68, offBRR, offVOX; unsigned int off8, off16, off1, offDPCM, offAICA, offZ, offQSoundA, offA, offB, offX68, offBRR, offVOX;
unsigned int offSegaPCM, offQSound, offX1_010, offSU; unsigned int offSegaPCM, offQSound, offX1_010, offSU;
unsigned int samples; unsigned int samples;
@ -218,6 +222,8 @@ struct DivSample {
data16(NULL), data16(NULL),
data1(NULL), data1(NULL),
dataDPCM(NULL), dataDPCM(NULL),
dataAICA(NULL),
dataZ(NULL),
dataQSoundA(NULL), dataQSoundA(NULL),
dataA(NULL), dataA(NULL),
dataB(NULL), dataB(NULL),
@ -228,6 +234,8 @@ struct DivSample {
length16(0), length16(0),
length1(0), length1(0),
lengthDPCM(0), lengthDPCM(0),
lengthAICA(0),
lengthZ(0),
lengthQSoundA(0), lengthQSoundA(0),
lengthA(0), lengthA(0),
lengthB(0), lengthB(0),
@ -238,6 +246,8 @@ struct DivSample {
off16(0), off16(0),
off1(0), off1(0),
offDPCM(0), offDPCM(0),
offAICA(0),
offZ(0),
offQSoundA(0), offQSoundA(0),
offA(0), offA(0),
offB(0), offB(0),

View file

@ -19,7 +19,7 @@
#include "song.h" #include "song.h"
void DivSong::clearSongData() { void DivSubSong::clearData() {
for (int i=0; i<DIV_MAX_CHANS; i++) { for (int i=0; i<DIV_MAX_CHANS; i++) {
pat[i].wipePatterns(); pat[i].wipePatterns();
} }
@ -28,6 +28,15 @@ void DivSong::clearSongData() {
ordersLen=1; ordersLen=1;
} }
void DivSong::clearSongData() {
for (DivSubSong* i: subsong) {
i->clearData();
delete i;
}
subsong.clear();
subsong.push_back(new DivSubSong);
}
void DivSong::clearInstruments() { void DivSong::clearInstruments() {
for (DivInstrument* i: ins) { for (DivInstrument* i: ins) {
delete i; delete i;
@ -71,7 +80,9 @@ void DivSong::unload() {
sample.clear(); sample.clear();
sampleLen=0; sampleLen=0;
for (int i=0; i<DIV_MAX_CHANS; i++) { for (DivSubSong* i: subsong) {
pat[i].wipePatterns(); i->clearData();
delete i;
} }
subsong.clear();
} }

View file

@ -110,6 +110,44 @@ enum DivSystem {
DIV_SYSTEM_DUMMY 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<DIV_MAX_CHANS; i++) {
chanShow[i]=true;
chanCollapse[i]=0;
}
}
};
struct DivSong { struct DivSong {
// version number used for saving the song. // version number used for saving the song.
// Furnace will save using the latest possible version, // Furnace will save using the latest possible version,
@ -156,14 +194,21 @@ struct DivSong {
// - introduces Genesis system // - introduces Genesis system
// - introduces system number // - introduces system number
// - patterns now stored in current known format // - patterns now stored in current known format
// - 8: ???
// - only used in the Medivo YMU cover
// - 7: ??? // - 7: ???
// - only present in a later version of First.dmf
// - pattern format changes: empty field is 0xFF instead of 0x80
// - instrument now stored in pattern
// - 5: BETA 3 // - 5: BETA 3
// - adds arpeggio tick // - adds arpeggio tick
// - 4: BETA 2 // - 4: BETA 2
// - possibly adds instrument number (stored in channel)?
// - cannot confirm as I don't have any version 4 modules
// - 3: BETA 1 // - 3: BETA 1
// - possibly the first version that could save // - possibly the first version that could save
// - basic format, no system number, 16 instruments, one speed, YMU759-only // - basic format, no system number, 16 instruments, one speed, YMU759-only
// - patterns were stored in a different format (chars instead of shorts) // - patterns were stored in a different format (chars instead of shorts) and no instrument
// - if somebody manages to find a version 2 or even 1 module, please tell me as it will be worth more than a luxury vehicle // - if somebody manages to find a version 2 or even 1 module, please tell me as it will be worth more than a luxury vehicle
unsigned short version; unsigned short version;
bool isDMF; bool isDMF;
@ -285,19 +330,10 @@ struct DivSong {
String nameJ, authorJ, categoryJ; String nameJ, authorJ, categoryJ;
// other things // other things
String chanName[DIV_MAX_CHANS];
String chanShortName[DIV_MAX_CHANS];
String notes; String notes;
// highlight
unsigned char hilightA, hilightB;
// module details // module details
unsigned char timeBase, speed1, speed2, arpLen; int insLen, waveLen, sampleLen;
bool pal;
bool customTempo;
float hz;
int patLen, ordersLen, insLen, waveLen, sampleLen;
float masterVol; float masterVol;
float tuning; float tuning;
@ -345,14 +381,11 @@ struct DivSong {
bool snDutyReset; bool snDutyReset;
bool pitchMacroIsLinear; bool pitchMacroIsLinear;
DivOrders orders;
std::vector<DivInstrument*> ins; std::vector<DivInstrument*> ins;
DivChannelData pat[DIV_MAX_CHANS];
std::vector<DivWavetable*> wave; std::vector<DivWavetable*> wave;
std::vector<DivSample*> sample; std::vector<DivSample*> sample;
bool chanShow[DIV_MAX_CHANS]; std::vector<DivSubSong*> subsong;
unsigned char chanCollapse[DIV_MAX_CHANS];
DivInstrument nullIns, nullInsOPLL, nullInsOPL, nullInsQSound; DivInstrument nullIns, nullInsOPLL, nullInsOPL, nullInsQSound;
DivWavetable nullWave; DivWavetable nullWave;
@ -401,17 +434,6 @@ struct DivSong {
manInfo(""), manInfo(""),
createdDate(""), createdDate(""),
revisionDate(""), 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), insLen(0),
waveLen(0), waveLen(0),
sampleLen(0), sampleLen(0),
@ -457,10 +479,7 @@ struct DivSong {
systemPan[i]=0; systemPan[i]=0;
systemFlags[i]=0; systemFlags[i]=0;
} }
for (int i=0; i<DIV_MAX_CHANS; i++) { subsong.push_back(new DivSubSong);
chanShow[i]=true;
chanCollapse[i]=0;
}
system[0]=DIV_SYSTEM_YM2612; system[0]=DIV_SYSTEM_YM2612;
system[1]=DIV_SYSTEM_SMS; system[1]=DIV_SYSTEM_SMS;

View file

@ -283,7 +283,7 @@ bool DivEngine::isSTDSystem(DivSystem sys) {
const char* DivEngine::getChannelName(int chan) { const char* DivEngine::getChannelName(int chan) {
if (chan<0 || chan>chans) return "??"; if (chan<0 || chan>chans) return "??";
if (!song.chanName[chan].empty()) return song.chanName[chan].c_str(); if (!curSubSong->chanName[chan].empty()) return curSubSong->chanName[chan].c_str();
if (sysDefs[sysOfChan[chan]]==NULL) return "??"; if (sysDefs[sysOfChan[chan]]==NULL) return "??";
const char* ret=sysDefs[sysOfChan[chan]]->chanNames[dispatchChanOfChan[chan]]; 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) { const char* DivEngine::getChannelShortName(int chan) {
if (chan<0 || chan>chans) return "??"; 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 "??"; if (sysDefs[sysOfChan[chan]]==NULL) return "??";
const char* ret=sysDefs[sysOfChan[chan]]->chanShortNames[dispatchChanOfChan[chan]]; const char* ret=sysDefs[sysOfChan[chan]]->chanShortNames[dispatchChanOfChan[chan]];

View file

@ -20,7 +20,7 @@
#define DETERMINE_FIRST \ #define DETERMINE_FIRST \
int firstChannel=0; \ int firstChannel=0; \
for (int i=0; i<e->getTotalChannelCount(); i++) { \ for (int i=0; i<e->getTotalChannelCount(); i++) { \
if (e->song.chanShow[i]) { \ if (e->curSubSong->chanShow[i]) { \
firstChannel=i; \ firstChannel=i; \
break; \ break; \
} \ } \
@ -29,7 +29,7 @@
#define DETERMINE_LAST \ #define DETERMINE_LAST \
int lastChannel=0; \ int lastChannel=0; \
for (int i=e->getTotalChannelCount()-1; i>=0; i--) { \ for (int i=e->getTotalChannelCount()-1; i>=0; i--) { \
if (e->song.chanShow[i]) { \ if (e->curSubSong->chanShow[i]) { \
lastChannel=i+1; \ lastChannel=i+1; \
break; \ break; \
} \ } \

View file

@ -38,7 +38,7 @@ void FurnaceGUI::drawChannels() {
ImGui::PushID(i); ImGui::PushID(i);
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Checkbox("##Visible",&e->song.chanShow[i]); ImGui::Checkbox("##Visible",&e->curSubSong->chanShow[i]);
ImGui::SameLine(); ImGui::SameLine();
ImGui::BeginDisabled(i==0); ImGui::BeginDisabled(i==0);
if (ImGui::Button(ICON_FA_CHEVRON_UP)) { if (ImGui::Button(ICON_FA_CHEVRON_UP)) {
@ -53,10 +53,10 @@ void FurnaceGUI::drawChannels() {
ImGui::EndDisabled(); ImGui::EndDisabled();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); 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::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); 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::PopID();
} }
ImGui::EndTable(); ImGui::EndTable();

View file

@ -32,7 +32,7 @@ void FurnaceGUI::startSelection(int xCoarse, int xFine, int y, bool fullRow) {
selStart.xCoarse=firstChannel; selStart.xCoarse=firstChannel;
selStart.xFine=0; selStart.xFine=0;
selEnd.xCoarse=lastChannel-1; 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; selStart.y=y;
selEnd.y=y; selEnd.y=y;
} else { } else {
@ -56,7 +56,7 @@ void FurnaceGUI::updateSelection(int xCoarse, int xFine, int y, bool fullRow) {
if (selectingFull) { if (selectingFull) {
DETERMINE_LAST; DETERMINE_LAST;
selEnd.xCoarse=lastChannel-1; 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; selEnd.y=y;
} else { } else {
selEnd.xCoarse=xCoarse; selEnd.xCoarse=xCoarse;
@ -94,21 +94,21 @@ void FurnaceGUI::finishSelection() {
if (selStart.xCoarse<0) selStart.xCoarse=0; if (selStart.xCoarse<0) selStart.xCoarse=0;
if (selStart.xCoarse>=chanCount) selStart.xCoarse=chanCount-1; if (selStart.xCoarse>=chanCount) selStart.xCoarse=chanCount-1;
if (selStart.y<0) selStart.y=0; 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<0) selEnd.xCoarse=0;
if (selEnd.xCoarse>=chanCount) selEnd.xCoarse=chanCount-1; if (selEnd.xCoarse>=chanCount) selEnd.xCoarse=chanCount-1;
if (selEnd.y<0) selEnd.y=0; 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<0) cursor.xCoarse=0;
if (cursor.xCoarse>=chanCount) cursor.xCoarse=chanCount-1; if (cursor.xCoarse>=chanCount) cursor.xCoarse=chanCount-1;
if (cursor.y<0) cursor.y=0; 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; selStart.xFine=0;
} }
if (e->song.chanCollapse[selEnd.xCoarse] && selEnd.xFine>=(3-e->song.chanCollapse[selEnd.xCoarse])) { if (e->curSubSong->chanCollapse[selEnd.xCoarse] && selEnd.xFine>=(3-e->curSubSong->chanCollapse[selEnd.xCoarse])) {
selEnd.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2; selEnd.xFine=2+e->curPat[cursor.xCoarse].effectCols*2;
} }
e->setMidiBaseChan(cursor.xCoarse); e->setMidiBaseChan(cursor.xCoarse);
@ -126,7 +126,7 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) {
demandScrollX=true; demandScrollX=true;
if (x>0) { if (x>0) {
for (int i=0; i<x; i++) { for (int i=0; i<x; i++) {
if (++cursor.xFine>=(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; cursor.xFine=0;
if (++cursor.xCoarse>=lastChannel) { if (++cursor.xCoarse>=lastChannel) {
if (settings.wrapHorizontal!=0 && !select) { if (settings.wrapHorizontal!=0 && !select) {
@ -134,10 +134,10 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) {
if (settings.wrapHorizontal==2) y++; if (settings.wrapHorizontal==2) y++;
} else { } else {
cursor.xCoarse=lastChannel-1; 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 { } else {
while (!e->song.chanShow[cursor.xCoarse]) { while (!e->curSubSong->chanShow[cursor.xCoarse]) {
cursor.xCoarse++; cursor.xCoarse++;
if (cursor.xCoarse>=e->getTotalChannelCount()) break; if (cursor.xCoarse>=e->getTotalChannelCount()) break;
} }
@ -150,21 +150,21 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) {
if (--cursor.xCoarse<firstChannel) { if (--cursor.xCoarse<firstChannel) {
if (settings.wrapHorizontal!=0 && !select) { if (settings.wrapHorizontal!=0 && !select) {
cursor.xCoarse=lastChannel-1; cursor.xCoarse=lastChannel-1;
cursor.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2; cursor.xFine=2+e->curPat[cursor.xCoarse].effectCols*2;
if (settings.wrapHorizontal==2) y--; if (settings.wrapHorizontal==2) y--;
} else { } else {
cursor.xCoarse=firstChannel; cursor.xCoarse=firstChannel;
cursor.xFine=0; cursor.xFine=0;
} }
} else { } else {
while (!e->song.chanShow[cursor.xCoarse]) { while (!e->curSubSong->chanShow[cursor.xCoarse]) {
cursor.xCoarse--; cursor.xCoarse--;
if (cursor.xCoarse<0) break; if (cursor.xCoarse<0) break;
} }
if (e->song.chanCollapse[cursor.xCoarse]) { if (e->curSubSong->chanCollapse[cursor.xCoarse]) {
cursor.xFine=3-e->song.chanCollapse[cursor.xCoarse]; cursor.xFine=3-e->curSubSong->chanCollapse[cursor.xCoarse];
} else { } 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) { if (y>0) {
for (int i=0; i<y; i++) { for (int i=0; i<y; i++) {
cursor.y++; cursor.y++;
if (cursor.y>=e->song.patLen) { if (cursor.y>=e->curSubSong->patLen) {
if (settings.wrapVertical!=0 && !select) { if (settings.wrapVertical!=0 && !select) {
cursor.y=0; cursor.y=0;
if (settings.wrapVertical==2) { 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); setOrder(curOrder+1);
} else { } else {
cursor.y=e->song.patLen-1; cursor.y=e->curSubSong->patLen-1;
} }
} }
} else { } 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--; cursor.y--;
if (cursor.y<0) { if (cursor.y<0) {
if (settings.wrapVertical!=0 && !select) { if (settings.wrapVertical!=0 && !select) {
cursor.y=e->song.patLen-1; cursor.y=e->curSubSong->patLen-1;
if (settings.wrapVertical==2) { if (settings.wrapVertical==2) {
if ((!e->isPlaying() || !followPattern) && curOrder>0) { if ((!e->isPlaying() || !followPattern) && curOrder>0) {
setOrder(curOrder-1); setOrder(curOrder-1);
@ -229,7 +229,7 @@ void FurnaceGUI::moveCursorPrevChannel(bool overflow) {
do { do {
cursor.xCoarse--; cursor.xCoarse--;
if (cursor.xCoarse<0) break; if (cursor.xCoarse<0) break;
} while (!e->song.chanShow[cursor.xCoarse]); } while (!e->curSubSong->chanShow[cursor.xCoarse]);
if (cursor.xCoarse<firstChannel) { if (cursor.xCoarse<firstChannel) {
if (overflow) { if (overflow) {
cursor.xCoarse=lastChannel-1; cursor.xCoarse=lastChannel-1;
@ -253,7 +253,7 @@ void FurnaceGUI::moveCursorNextChannel(bool overflow) {
do { do {
cursor.xCoarse++; cursor.xCoarse++;
if (cursor.xCoarse>=e->getTotalChannelCount()) break; if (cursor.xCoarse>=e->getTotalChannelCount()) break;
} while (!e->song.chanShow[cursor.xCoarse]); } while (!e->curSubSong->chanShow[cursor.xCoarse]);
if (cursor.xCoarse>=lastChannel) { if (cursor.xCoarse>=lastChannel) {
if (overflow) { if (overflow) {
cursor.xCoarse=firstChannel; cursor.xCoarse=firstChannel;
@ -290,14 +290,14 @@ void FurnaceGUI::moveCursorTop(bool select) {
void FurnaceGUI::moveCursorBottom(bool select) { void FurnaceGUI::moveCursorBottom(bool select) {
finishSelection(); finishSelection();
curNibble=false; curNibble=false;
if (cursor.y==e->song.patLen-1) { if (cursor.y==e->curSubSong->patLen-1) {
DETERMINE_LAST; DETERMINE_LAST;
cursor.xCoarse=lastChannel-1; cursor.xCoarse=lastChannel-1;
if (cursor.xCoarse<0) cursor.xCoarse=0; 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; demandScrollX=true;
} else { } else {
cursor.y=e->song.patLen-1; cursor.y=e->curSubSong->patLen-1;
} }
if (!select) { if (!select) {
selStart=cursor; selStart=cursor;
@ -310,7 +310,7 @@ void FurnaceGUI::moveCursorBottom(bool select) {
void FurnaceGUI::editAdvance() { void FurnaceGUI::editAdvance() {
finishSelection(); finishSelection();
cursor.y+=editStep; 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; selStart=cursor;
selEnd=cursor; selEnd=cursor;
updateScroll(cursor.y); updateScroll(cursor.y);

View file

@ -470,7 +470,7 @@ void FurnaceGUI::doAction(int what) {
e->unmuteAll(); e->unmuteAll();
break; break;
case GUI_ACTION_PAT_NEXT_ORDER: case GUI_ACTION_PAT_NEXT_ORDER:
if (curOrder<e->song.ordersLen-1) { if (curOrder<e->curSubSong->ordersLen-1) {
setOrder(curOrder+1); setOrder(curOrder+1);
} }
break; break;
@ -481,21 +481,21 @@ void FurnaceGUI::doAction(int what) {
break; break;
case GUI_ACTION_PAT_COLLAPSE: case GUI_ACTION_PAT_COLLAPSE:
if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break;
if (e->song.chanCollapse[cursor.xCoarse]==0) { if (e->curSubSong->chanCollapse[cursor.xCoarse]==0) {
e->song.chanCollapse[cursor.xCoarse]=3; e->curSubSong->chanCollapse[cursor.xCoarse]=3;
} else if (e->song.chanCollapse[cursor.xCoarse]>0) { } else if (e->curSubSong->chanCollapse[cursor.xCoarse]>0) {
e->song.chanCollapse[cursor.xCoarse]--; e->curSubSong->chanCollapse[cursor.xCoarse]--;
} }
break; break;
case GUI_ACTION_PAT_INCREASE_COLUMNS: case GUI_ACTION_PAT_INCREASE_COLUMNS:
if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break;
e->song.pat[cursor.xCoarse].effectCols++; e->curPat[cursor.xCoarse].effectCols++;
if (e->song.pat[cursor.xCoarse].effectCols>8) e->song.pat[cursor.xCoarse].effectCols=8; if (e->curPat[cursor.xCoarse].effectCols>8) e->curPat[cursor.xCoarse].effectCols=8;
break; break;
case GUI_ACTION_PAT_DECREASE_COLUMNS: case GUI_ACTION_PAT_DECREASE_COLUMNS:
if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break;
e->song.pat[cursor.xCoarse].effectCols--; e->curPat[cursor.xCoarse].effectCols--;
if (e->song.pat[cursor.xCoarse].effectCols<1) e->song.pat[cursor.xCoarse].effectCols=1; if (e->curPat[cursor.xCoarse].effectCols<1) e->curPat[cursor.xCoarse].effectCols=1;
break; break;
case GUI_ACTION_PAT_INTERPOLATE: case GUI_ACTION_PAT_INTERPOLATE:
doInterpolate(); doInterpolate();
@ -525,14 +525,22 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_INS_LIST_ADD: case GUI_ACTION_INS_LIST_ADD:
curIns=e->addInstrument(cursor.xCoarse); curIns=e->addInstrument(cursor.xCoarse);
MARK_MODIFIED; if (curIns==-1) {
showError("too many instruments!");
} else {
MARK_MODIFIED;
}
break; break;
case GUI_ACTION_INS_LIST_DUPLICATE: case GUI_ACTION_INS_LIST_DUPLICATE:
if (curIns>=0 && curIns<(int)e->song.ins.size()) { if (curIns>=0 && curIns<(int)e->song.ins.size()) {
int prevIns=curIns; int prevIns=curIns;
curIns=e->addInstrument(cursor.xCoarse); curIns=e->addInstrument(cursor.xCoarse);
(*e->song.ins[curIns])=(*e->song.ins[prevIns]); if (curIns==-1) {
MARK_MODIFIED; showError("too many instruments!");
} else {
(*e->song.ins[curIns])=(*e->song.ins[prevIns]);
MARK_MODIFIED;
}
} }
break; break;
case GUI_ACTION_INS_LIST_OPEN: case GUI_ACTION_INS_LIST_OPEN:
@ -571,14 +579,22 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_WAVE_LIST_ADD: case GUI_ACTION_WAVE_LIST_ADD:
curWave=e->addWave(); curWave=e->addWave();
MARK_MODIFIED; if (curWave==-1) {
showError("too many wavetables!");
} else {
MARK_MODIFIED;
}
break; break;
case GUI_ACTION_WAVE_LIST_DUPLICATE: case GUI_ACTION_WAVE_LIST_DUPLICATE:
if (curWave>=0 && curWave<(int)e->song.wave.size()) { if (curWave>=0 && curWave<(int)e->song.wave.size()) {
int prevWave=curWave; int prevWave=curWave;
curWave=e->addWave(); curWave=e->addWave();
(*e->song.wave[curWave])=(*e->song.wave[prevWave]); if (curWave==-1) {
MARK_MODIFIED; showError("too many wavetables!");
} else {
(*e->song.wave[curWave])=(*e->song.wave[prevWave]);
MARK_MODIFIED;
}
} }
break; break;
case GUI_ACTION_WAVE_LIST_OPEN: case GUI_ACTION_WAVE_LIST_OPEN:
@ -614,31 +630,39 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_SAMPLE_LIST_ADD: case GUI_ACTION_SAMPLE_LIST_ADD:
curSample=e->addSample(); curSample=e->addSample();
if (curSample==-1) {
showError("too many samples!");
} else {
MARK_MODIFIED;
}
updateSampleTex=true; updateSampleTex=true;
MARK_MODIFIED;
break; break;
case GUI_ACTION_SAMPLE_LIST_DUPLICATE: case GUI_ACTION_SAMPLE_LIST_DUPLICATE:
if (curSample>=0 && curSample<(int)e->song.sample.size()) { if (curSample>=0 && curSample<(int)e->song.sample.size()) {
DivSample* prevSample=e->getSample(curSample); DivSample* prevSample=e->getSample(curSample);
curSample=e->addSample(); curSample=e->addSample();
updateSampleTex=true; if (curSample==-1) {
e->lockEngine([this,prevSample]() { showError("too many samples!");
DivSample* sample=e->getSample(curSample); } else {
if (sample!=NULL) { e->lockEngine([this,prevSample]() {
sample->rate=prevSample->rate; DivSample* sample=e->getSample(curSample);
sample->centerRate=prevSample->centerRate; if (sample!=NULL) {
sample->name=prevSample->name; sample->rate=prevSample->rate;
sample->loopStart=prevSample->loopStart; sample->centerRate=prevSample->centerRate;
sample->depth=prevSample->depth; sample->name=prevSample->name;
if (sample->init(prevSample->samples)) { sample->loopStart=prevSample->loopStart;
if (prevSample->getCurBuf()!=NULL) { sample->depth=prevSample->depth;
memcpy(sample->getCurBuf(),prevSample->getCurBuf(),prevSample->getCurBufLen()); if (sample->init(prevSample->samples)) {
if (prevSample->getCurBuf()!=NULL) {
memcpy(sample->getCurBuf(),prevSample->getCurBuf(),prevSample->getCurBufLen());
}
} }
} }
} e->renderSamples();
e->renderSamples(); });
}); MARK_MODIFIED;
MARK_MODIFIED; }
updateSampleTex=true;
} }
break; break;
case GUI_ACTION_SAMPLE_LIST_OPEN: 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; if (curSample<0 || curSample>=(int)e->song.sample.size()) break;
DivSample* sample=e->song.sample[curSample]; DivSample* sample=e->song.sample[curSample];
curIns=e->addInstrument(cursor.xCoarse); curIns=e->addInstrument(cursor.xCoarse);
e->song.ins[curIns]->type=DIV_INS_AMIGA; if (curIns==-1) {
e->song.ins[curIns]->name=sample->name; showError("too many instruments!");
e->song.ins[curIns]->amiga.initSample=curSample; } else {
nextWindow=GUI_WINDOW_INS_EDIT; e->song.ins[curIns]->type=DIV_INS_AMIGA;
MARK_MODIFIED; e->song.ins[curIns]->name=sample->name;
e->song.ins[curIns]->amiga.initSample=curSample;
nextWindow=GUI_WINDOW_INS_EDIT;
MARK_MODIFIED;
}
break; break;
} }
@ -1168,7 +1196,7 @@ void FurnaceGUI::doAction(int what) {
} }
break; break;
case GUI_ACTION_ORDERS_DOWN: case GUI_ACTION_ORDERS_DOWN:
if (curOrder<e->song.ordersLen-1) { if (curOrder<e->curSubSong->ordersLen-1) {
setOrder(curOrder+1); setOrder(curOrder+1);
} }
break; break;
@ -1181,7 +1209,7 @@ void FurnaceGUI::doAction(int what) {
orderCursor=firstChannel; orderCursor=firstChannel;
break; break;
} }
} while (!e->song.chanShow[orderCursor]); } while (!e->curSubSong->chanShow[orderCursor]);
break; break;
} }
case GUI_ACTION_ORDERS_RIGHT: { case GUI_ACTION_ORDERS_RIGHT: {
@ -1193,20 +1221,20 @@ void FurnaceGUI::doAction(int what) {
orderCursor=lastChannel-1; orderCursor=lastChannel-1;
break; break;
} }
} while (!e->song.chanShow[orderCursor]); } while (!e->curSubSong->chanShow[orderCursor]);
break; break;
} }
case GUI_ACTION_ORDERS_INCREASE: { case GUI_ACTION_ORDERS_INCREASE: {
if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break; if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break;
if (e->song.orders.ord[orderCursor][curOrder]<0x7f) { if (e->curOrders->ord[orderCursor][curOrder]<0x7f) {
e->song.orders.ord[orderCursor][curOrder]++; e->curOrders->ord[orderCursor][curOrder]++;
} }
break; break;
} }
case GUI_ACTION_ORDERS_DECREASE: { case GUI_ACTION_ORDERS_DECREASE: {
if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break; if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break;
if (e->song.orders.ord[orderCursor][curOrder]>0) { if (e->curOrders->ord[orderCursor][curOrder]>0) {
e->song.orders.ord[orderCursor][curOrder]--; e->curOrders->ord[orderCursor][curOrder]--;
} }
break; break;
} }

View file

@ -45,7 +45,7 @@ void FurnaceGUI::drawEditControls() {
ImGui::Text("Edit Step"); ImGui::Text("Edit Step");
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::InputInt("##EditStep",&editStep,1,1)) { 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 (editStep<0) editStep=0;
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
@ -148,7 +148,7 @@ void FurnaceGUI::drawEditControls() {
ImGui::SameLine(); ImGui::SameLine();
ImGui::SetNextItemWidth(96.0f*dpiScale); ImGui::SetNextItemWidth(96.0f*dpiScale);
if (ImGui::InputInt("##EditStep",&editStep,1,1)) { 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 (editStep<0) editStep=0;
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
@ -217,7 +217,7 @@ void FurnaceGUI::drawEditControls() {
ImGui::Text("Step"); ImGui::Text("Step");
ImGui::SetNextItemWidth(avail); ImGui::SetNextItemWidth(avail);
if (ImGui::InputInt("##EditStep",&editStep,0,0)) { 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 (editStep<0) editStep=0;
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
@ -317,7 +317,7 @@ void FurnaceGUI::drawEditControls() {
ImGui::SetCursorPosX(cursor); ImGui::SetCursorPosX(cursor);
ImGui::SetNextItemWidth(avail); ImGui::SetNextItemWidth(avail);
if (ImGui::InputInt("##EditStep",&editStep,1,1)) { 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 (editStep<0) editStep=0;
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {

View file

@ -44,8 +44,8 @@ const char* noteNameNormal(short note, short octave) {
void FurnaceGUI::prepareUndo(ActionType action) { void FurnaceGUI::prepareUndo(ActionType action) {
switch (action) { switch (action) {
case GUI_UNDO_CHANGE_ORDER: case GUI_UNDO_CHANGE_ORDER:
oldOrders=e->song.orders; memcpy(&oldOrders,e->curOrders,sizeof(DivOrders));
oldOrdersLen=e->song.ordersLen; oldOrdersLen=e->curSubSong->ordersLen;
break; break;
case GUI_UNDO_PATTERN_EDIT: case GUI_UNDO_PATTERN_EDIT:
case GUI_UNDO_PATTERN_DELETE: case GUI_UNDO_PATTERN_DELETE:
@ -63,7 +63,7 @@ void FurnaceGUI::prepareUndo(ActionType action) {
case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_COLLAPSE:
case GUI_UNDO_PATTERN_EXPAND: case GUI_UNDO_PATTERN_EXPAND:
for (int i=0; i<e->getTotalChannelCount(); i++) { for (int i=0; i<e->getTotalChannelCount(); 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; break;
} }
@ -78,18 +78,19 @@ void FurnaceGUI::makeUndo(ActionType action) {
s.selEnd=selEnd; s.selEnd=selEnd;
s.order=curOrder; s.order=curOrder;
s.nibble=curNibble; s.nibble=curNibble;
size_t subSong=e->getCurrentSubSong();
switch (action) { switch (action) {
case GUI_UNDO_CHANGE_ORDER: case GUI_UNDO_CHANGE_ORDER:
for (int i=0; i<DIV_MAX_CHANS; i++) { for (int i=0; i<DIV_MAX_CHANS; i++) {
for (int j=0; j<128; j++) { for (int j=0; j<128; j++) {
if (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(i,j,oldOrders.ord[i][j],e->song.orders.ord[i][j])); s.ord.push_back(UndoOrderData(subSong,i,j,oldOrders.ord[i][j],e->curOrders->ord[i][j]));
} }
} }
} }
s.oldOrdersLen=oldOrdersLen; s.oldOrdersLen=oldOrdersLen;
s.newOrdersLen=e->song.ordersLen; s.newOrdersLen=e->curSubSong->ordersLen;
if (oldOrdersLen!=e->song.ordersLen) { if (oldOrdersLen!=e->curSubSong->ordersLen) {
doPush=true; doPush=true;
} }
if (!s.ord.empty()) { if (!s.ord.empty()) {
@ -112,11 +113,11 @@ void FurnaceGUI::makeUndo(ActionType action) {
case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_COLLAPSE:
case GUI_UNDO_PATTERN_EXPAND: case GUI_UNDO_PATTERN_EXPAND:
for (int i=0; i<e->getTotalChannelCount(); i++) { for (int i=0; i<e->getTotalChannelCount(); i++) {
DivPattern* p=e->song.pat[i].getPattern(e->song.orders.ord[i][curOrder],false); DivPattern* p=e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false);
for (int j=0; j<e->song.patLen; j++) { for (int j=0; j<e->curSubSong->patLen; j++) {
for (int k=0; k<32; k++) { for (int k=0; k<32; k++) {
if (p->data[j][k]!=oldPat[i]->data[j][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() { void FurnaceGUI::doSelectAll() {
finishSelection(); finishSelection();
curNibble=false; curNibble=false;
if (selStart.xFine==0 && selEnd.xFine==2+e->song.pat[selEnd.xCoarse].effectCols*2) { if (selStart.xFine==0 && selEnd.xFine==2+e->curPat[selEnd.xCoarse].effectCols*2) {
if (selStart.y==0 && selEnd.y==e->song.patLen-1) { // select entire pattern if (selStart.y==0 && selEnd.y==e->curSubSong->patLen-1) { // select entire pattern
selStart.xCoarse=0; selStart.xCoarse=0;
selStart.xFine=0; selStart.xFine=0;
selEnd.xCoarse=e->getTotalChannelCount()-1; 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 } else { // select entire column
selStart.y=0; selStart.y=0;
selEnd.y=e->song.patLen-1; selEnd.y=e->curSubSong->patLen-1;
} }
} else { } else {
int selStartX=0; int selStartX=0;
@ -153,26 +154,26 @@ void FurnaceGUI::doSelectAll() {
// find row position // find row position
for (SelectionPoint i; i.xCoarse!=selStart.xCoarse || i.xFine!=selStart.xFine; selStartX++) { for (SelectionPoint i; i.xCoarse!=selStart.xCoarse || i.xFine!=selStart.xFine; selStartX++) {
i.xFine++; 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.xFine=0;
i.xCoarse++; i.xCoarse++;
} }
} }
for (SelectionPoint i; i.xCoarse!=selEnd.xCoarse || i.xFine!=selEnd.xFine; selEndX++) { for (SelectionPoint i; i.xCoarse!=selEnd.xCoarse || i.xFine!=selEnd.xFine; selEndX++) {
i.xFine++; 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.xFine=0;
i.xCoarse++; i.xCoarse++;
} }
} }
float aspect=float(selEndX-selStartX+1)/float(selEnd.y-selStart.y+1); 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; selStart.y=0;
selEnd.y=e->song.patLen-1; selEnd.y=e->curSubSong->patLen-1;
} else { // left-right } else { // left-right
selStart.xFine=0; 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 iCoarse=selStart.xCoarse;
int iFine=selStart.xFine; int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) { for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue; if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) { for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskDelete,iFine); maskOut(opMaskDelete,iFine);
for (int j=selStart.y; j<=selEnd.y; j++) { for (int j=selStart.y; j<=selEnd.y; j++) {
if (iFine==0) { if (iFine==0) {
@ -235,12 +236,12 @@ void FurnaceGUI::doPullDelete() {
int iCoarse=selStart.xCoarse; int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine; int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) { for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue; if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) { for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskPullDelete,iFine); maskOut(opMaskPullDelete,iFine);
for (int j=selStart.y; j<e->song.patLen; j++) { for (int j=selStart.y; j<e->curSubSong->patLen; j++) {
if (j<e->song.patLen-1) { if (j<e->curSubSong->patLen-1) {
if (iFine==0) { if (iFine==0) {
pat->data[j][iFine]=pat->data[j+1][iFine]; pat->data[j][iFine]=pat->data[j+1][iFine];
} }
@ -267,11 +268,11 @@ void FurnaceGUI::doInsert() {
int iCoarse=selStart.xCoarse; int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine; int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) { for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue; if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) { for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskInsert,iFine); maskOut(opMaskInsert,iFine);
for (int j=e->song.patLen-1; j>=selStart.y; j--) { for (int j=e->curSubSong->patLen-1; j>=selStart.y; j--) {
if (j==selStart.y) { if (j==selStart.y) {
if (iFine==0) { if (iFine==0) {
pat->data[j][iFine]=0; pat->data[j][iFine]=0;
@ -299,9 +300,9 @@ void FurnaceGUI::doTranspose(int amount, OperationMask& mask) {
int iCoarse=selStart.xCoarse; int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine; int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) { for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue; if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) { for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(mask,iFine); maskOut(mask,iFine);
for (int j=selStart.y; j<=selEnd.y; j++) { for (int j=selStart.y; j<=selEnd.y; j++) {
if (iFine==0) { if (iFine==0) {
@ -367,9 +368,9 @@ void FurnaceGUI::doCopy(bool cut) {
} }
clipboard+='\n'; clipboard+='\n';
for (; iCoarse<=selEnd.xCoarse; iCoarse++) { for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue; if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) { for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
if (iFine==0) { if (iFine==0) {
clipboard+=noteNameNormal(pat->data[j][0],pat->data[j][1]); clipboard+=noteNameNormal(pat->data[j][0],pat->data[j][1]);
if (cut) { if (cut) {
@ -432,7 +433,7 @@ void FurnaceGUI::doPaste(PasteMode mode) {
int j=cursor.y; int j=cursor.y;
char note[4]; char note[4];
for (size_t i=2; i<data.size() && j<e->song.patLen; i++) { for (size_t i=2; i<data.size() && j<e->curSubSong->patLen; i++) {
size_t charPos=0; size_t charPos=0;
int iCoarse=cursor.xCoarse; int iCoarse=cursor.xCoarse;
int iFine=(startOff>2 && cursor.xFine>2)?(((cursor.xFine-1)&(~1))|1):startOff; 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]; String& line=data[i];
while (charPos<line.size() && iCoarse<lastChannel) { while (charPos<line.size() && iCoarse<lastChannel) {
DivPattern* pat=e->song.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]=='|') { if (line[charPos]=='|') {
iCoarse++; iCoarse++;
if (iCoarse<lastChannel) while (!e->song.chanShow[iCoarse]) { if (iCoarse<lastChannel) while (!e->curSubSong->chanShow[iCoarse]) {
iCoarse++; iCoarse++;
if (iCoarse>=lastChannel) break; if (iCoarse>=lastChannel) break;
} }
@ -530,7 +531,7 @@ void FurnaceGUI::doPaste(PasteMode mode) {
break; break;
} }
if (mode!=GUI_PASTE_MODE_MIX_BG || pat->data[j][iFine+1]==-1) { 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; break;
} }
j++; j++;
if (mode==GUI_PASTE_MODE_OVERFLOW && j>=e->song.patLen && curOrder<e->song.ordersLen-1) { if (mode==GUI_PASTE_MODE_OVERFLOW && j>=e->curSubSong->patLen && curOrder<e->curSubSong->ordersLen-1) {
j=0; j=0;
curOrder++; curOrder++;
} }
@ -554,7 +555,7 @@ void FurnaceGUI::doPaste(PasteMode mode) {
} }
if (settings.cursorPastePos) { if (settings.cursorPastePos) {
cursor.y=j; 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); updateScroll(cursor.y);
} }
@ -567,8 +568,8 @@ void FurnaceGUI::doChangeIns(int ins) {
int iCoarse=selStart.xCoarse; int iCoarse=selStart.xCoarse;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) { for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue; if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (int j=selStart.y; j<=selEnd.y; j++) { 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)) { if (pat->data[j][2]!=-1 || !(pat->data[j][0]==0 && pat->data[j][1]==0)) {
pat->data[j][2]=ins; pat->data[j][2]=ins;
@ -587,9 +588,9 @@ void FurnaceGUI::doInterpolate() {
int iCoarse=selStart.xCoarse; int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine; int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) { for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue; if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) { for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskInterpolate,iFine); maskOut(opMaskInterpolate,iFine);
points.clear(); points.clear();
if (iFine!=0) { if (iFine!=0) {
@ -646,9 +647,9 @@ void FurnaceGUI::doFade(int p0, int p1, bool mode) {
int iCoarse=selStart.xCoarse; int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine; int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) { for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue; if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) { for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskFade,iFine); maskOut(opMaskFade,iFine);
if (iFine!=0) { if (iFine!=0) {
int absoluteTop=255; int absoluteTop=255;
@ -684,9 +685,9 @@ void FurnaceGUI::doInvertValues() {
int iCoarse=selStart.xCoarse; int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine; int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) { for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue; if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) { for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskInvertVal,iFine); maskOut(opMaskInvertVal,iFine);
if (iFine!=0) { if (iFine!=0) {
int top=255; int top=255;
@ -715,9 +716,9 @@ void FurnaceGUI::doScale(float top) {
int iCoarse=selStart.xCoarse; int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine; int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) { for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue; if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) { for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskScale,iFine); maskOut(opMaskScale,iFine);
if (iFine!=0) { if (iFine!=0) {
int absoluteTop=255; int absoluteTop=255;
@ -746,9 +747,9 @@ void FurnaceGUI::doRandomize(int bottom, int top, bool mode) {
int iCoarse=selStart.xCoarse; int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine; int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) { for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue; if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) { for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskRandomize,iFine); maskOut(opMaskRandomize,iFine);
if (iFine!=0) { if (iFine!=0) {
int absoluteTop=255; int absoluteTop=255;
@ -792,9 +793,9 @@ void FurnaceGUI::doFlip() {
int iCoarse=selStart.xCoarse; int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine; int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) { for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue; if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) { for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskFlip,iFine); maskOut(opMaskFlip,iFine);
for (int j=selStart.y; j<=selEnd.y; j++) { for (int j=selStart.y; j<=selEnd.y; j++) {
if (iFine==0) { if (iFine==0) {
@ -823,9 +824,9 @@ void FurnaceGUI::doCollapse(int divider) {
int iCoarse=selStart.xCoarse; int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine; int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) { for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue; if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) { for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskCollapseExpand,iFine); maskOut(opMaskCollapseExpand,iFine);
for (int j=selStart.y; j<=selEnd.y; j++) { for (int j=selStart.y; j<=selEnd.y; j++) {
if (iFine==0) { if (iFine==0) {
@ -880,9 +881,9 @@ void FurnaceGUI::doExpand(int multiplier) {
int iCoarse=selStart.xCoarse; int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine; int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) { for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
if (!e->song.chanShow[iCoarse]) continue; if (!e->curSubSong->chanShow[iCoarse]) continue;
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true); DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) { for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
maskOut(opMaskCollapseExpand,iFine); maskOut(opMaskCollapseExpand,iFine);
for (int j=selStart.y; j<=selEnd.y; j++) { for (int j=selStart.y; j<=selEnd.y; j++) {
if (iFine==0) { if (iFine==0) {
@ -891,7 +892,7 @@ void FurnaceGUI::doExpand(int multiplier) {
patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; patBuffer.data[j][iFine+1]=pat->data[j][iFine+1];
} }
for (int j=0; j<=(selEnd.y-selStart.y)*multiplier; j++) { 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 ((j%multiplier)!=0) {
if (iFine==0) { if (iFine==0) {
pat->data[j+selStart.y][0]=0; pat->data[j+selStart.y][0]=0;
@ -921,9 +922,10 @@ void FurnaceGUI::doUndo() {
switch (us.type) { switch (us.type) {
case GUI_UNDO_CHANGE_ORDER: case GUI_UNDO_CHANGE_ORDER:
e->song.ordersLen=us.oldOrdersLen; e->curSubSong->ordersLen=us.oldOrdersLen;
for (UndoOrderData& i: us.ord) { 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; break;
case GUI_UNDO_PATTERN_EDIT: case GUI_UNDO_PATTERN_EDIT:
@ -942,7 +944,8 @@ void FurnaceGUI::doUndo() {
case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_COLLAPSE:
case GUI_UNDO_PATTERN_EXPAND: case GUI_UNDO_PATTERN_EXPAND:
for (UndoPatternData& i: us.pat) { 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; p->data[i.row][i.col]=i.oldVal;
} }
if (!e->isPlaying() || !followPattern) { if (!e->isPlaying() || !followPattern) {
@ -967,9 +970,10 @@ void FurnaceGUI::doRedo() {
switch (us.type) { switch (us.type) {
case GUI_UNDO_CHANGE_ORDER: case GUI_UNDO_CHANGE_ORDER:
e->song.ordersLen=us.newOrdersLen; e->curSubSong->ordersLen=us.newOrdersLen;
for (UndoOrderData& i: us.ord) { 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; break;
case GUI_UNDO_PATTERN_EDIT: case GUI_UNDO_PATTERN_EDIT:
@ -988,7 +992,8 @@ void FurnaceGUI::doRedo() {
case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_COLLAPSE:
case GUI_UNDO_PATTERN_EXPAND: case GUI_UNDO_PATTERN_EXPAND:
for (UndoPatternData& i: us.pat) { 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; p->data[i.row][i.col]=i.newVal;
} }
if (!e->isPlaying()) { if (!e->isPlaying()) {

View file

@ -793,9 +793,9 @@ void FurnaceGUI::prepareLayout() {
} }
float FurnaceGUI::calcBPM(int s1, int s2, float hz) { 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; if (hl<=0.0f) hl=4.0f;
float timeBase=e->song.timeBase+1; float timeBase=e->curSubSong->timeBase+1;
float speedSum=s1+s2; float speedSum=s1+s2;
if (timeBase<1.0f) timeBase=1.0f; if (timeBase<1.0f) timeBase=1.0f;
if (speedSum<1.0f) speedSum=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) { 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); 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) { 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); prepareUndo(GUI_UNDO_PATTERN_EDIT);
if (target==-1) target=cursor.xFine+1; if (target==-1) target=cursor.xFine+1;
if (direct) { if (direct) {
@ -965,7 +965,7 @@ void FurnaceGUI::valueInput(int num, bool direct, int target) {
editAdvance(); editAdvance();
} else { } else {
if (settings.effectCursorDir==2) { 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; cursor.xFine=3;
} }
} else { } else {
@ -1123,7 +1123,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
int num=valueKeys.at(ev.key.keysym.sym); int num=valueKeys.at(ev.key.keysym.sym);
if (orderCursor>=0 && orderCursor<e->getTotalChannelCount()) { if (orderCursor>=0 && orderCursor<e->getTotalChannelCount()) {
e->lockSave([this,num]() { 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) { if (orderEditMode==2 || orderEditMode==3) {
curNibble=!curNibble; curNibble=!curNibble;
@ -1132,7 +1132,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
orderCursor++; orderCursor++;
if (orderCursor>=e->getTotalChannelCount()) orderCursor=0; if (orderCursor>=e->getTotalChannelCount()) orderCursor=0;
} else if (orderEditMode==3) { } else if (orderEditMode==3) {
if (curOrder<e->song.ordersLen-1) { if (curOrder<e->curSubSong->ordersLen-1) {
setOrder(curOrder+1); setOrder(curOrder+1);
} }
} }
@ -2047,7 +2047,7 @@ void FurnaceGUI::editOptions(bool topMenu) {
ImGui::PopFont(); ImGui::PopFont();
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Set")) { 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]; latchIns=pat->data[cursor.y][2];
latchVol=pat->data[cursor.y][3]; latchVol=pat->data[cursor.y][3];
latchEffect=pat->data[cursor.y][4]; latchEffect=pat->data[cursor.y][4];
@ -2833,12 +2833,12 @@ bool FurnaceGUI::loop() {
if (e->isPlaying()) { if (e->isPlaying()) {
int totalTicks=e->getTotalTicks(); int totalTicks=e->getTotalTicks();
int totalSeconds=e->getTotalSeconds(); 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 { } else {
bool hasInfo=false; bool hasInfo=false;
String info; String info;
if (cursor.xCoarse>=0 && cursor.xCoarse<e->getTotalChannelCount()) { if (cursor.xCoarse>=0 && cursor.xCoarse<e->getTotalChannelCount()) {
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) { if (cursor.xFine>=0) switch (cursor.xFine) {
case 0: // note case 0: // note
if (p->data[cursor.y][0]>0) { 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); ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0);
drawSubSongs();
drawPattern(); drawPattern();
drawEditControls(); drawEditControls();
drawSongInfo(); drawSongInfo();
@ -3177,8 +3178,11 @@ bool FurnaceGUI::loop() {
break; break;
} }
case GUI_FILE_WAVE_OPEN: case GUI_FILE_WAVE_OPEN:
e->addWaveFromFile(copyOfName.c_str()); if (!e->addWaveFromFile(copyOfName.c_str())) {
MARK_MODIFIED; showError("cannot load wavetable! ("+e->getLastError()+")");
} else {
MARK_MODIFIED;
}
break; break;
case GUI_FILE_EXPORT_VGM: { case GUI_FILE_EXPORT_VGM: {
SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion); SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion);
@ -3504,7 +3508,7 @@ bool FurnaceGUI::loop() {
stop(); stop();
e->lockEngine([this]() { e->lockEngine([this]() {
for (int i=0; i<e->getTotalChannelCount(); i++) { for (int i=0; i<e->getTotalChannelCount(); 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)); memset(pat->data,-1,256*32*sizeof(short));
for (int j=0; j<256; j++) { for (int j=0; j<256; j++) {
pat->data[j][0]=0; pat->data[j][0]=0;
@ -3663,6 +3667,7 @@ bool FurnaceGUI::init() {
regViewOpen=e->getConfBool("regViewOpen",false); regViewOpen=e->getConfBool("regViewOpen",false);
logOpen=e->getConfBool("logOpen",false); logOpen=e->getConfBool("logOpen",false);
effectListOpen=e->getConfBool("effectListOpen",false); effectListOpen=e->getConfBool("effectListOpen",false);
subSongsOpen=e->getConfBool("subSongsOpen",true);
tempoView=e->getConfBool("tempoView",true); tempoView=e->getConfBool("tempoView",true);
waveHex=e->getConfBool("waveHex",false); waveHex=e->getConfBool("waveHex",false);
@ -3854,6 +3859,7 @@ bool FurnaceGUI::finish() {
e->setConf("regViewOpen",regViewOpen); e->setConf("regViewOpen",regViewOpen);
e->setConf("logOpen",logOpen); e->setConf("logOpen",logOpen);
e->setConf("effectListOpen",effectListOpen); e->setConf("effectListOpen",effectListOpen);
e->setConf("subSongsOpen",subSongsOpen);
// commit last window size // commit last window size
e->setConf("lastWindowWidth",scrW); e->setConf("lastWindowWidth",scrW);
@ -3974,6 +3980,7 @@ FurnaceGUI::FurnaceGUI():
logOpen(false), logOpen(false),
effectListOpen(false), effectListOpen(false),
chanOscOpen(false), chanOscOpen(false),
subSongsOpen(true),
/* /*
editControlsDocked(false), editControlsDocked(false),
ordersDocked(false), ordersDocked(false),
@ -4001,6 +4008,7 @@ FurnaceGUI::FurnaceGUI():
logDocked(false), logDocked(false),
effectListDocked(false), effectListDocked(false),
chanOscDocked(false), chanOscDocked(false),
subSongsDocked(false),
*/ */
selecting(false), selecting(false),
selectingFull(false), selectingFull(false),

View file

@ -231,7 +231,8 @@ enum FurnaceGUIWindows {
GUI_WINDOW_REGISTER_VIEW, GUI_WINDOW_REGISTER_VIEW,
GUI_WINDOW_LOG, GUI_WINDOW_LOG,
GUI_WINDOW_EFFECT_LIST, GUI_WINDOW_EFFECT_LIST,
GUI_WINDOW_CHAN_OSC GUI_WINDOW_CHAN_OSC,
GUI_WINDOW_SUBSONGS
}; };
enum FurnaceGUIFileDialogs { enum FurnaceGUIFileDialogs {
@ -337,6 +338,7 @@ enum FurnaceGUIActions {
GUI_ACTION_WINDOW_LOG, GUI_ACTION_WINDOW_LOG,
GUI_ACTION_WINDOW_EFFECT_LIST, GUI_ACTION_WINDOW_EFFECT_LIST,
GUI_ACTION_WINDOW_CHAN_OSC, GUI_ACTION_WINDOW_CHAN_OSC,
GUI_ACTION_WINDOW_SUBSONGS,
GUI_ACTION_COLLAPSE_WINDOW, GUI_ACTION_COLLAPSE_WINDOW,
GUI_ACTION_CLOSE_WINDOW, GUI_ACTION_CLOSE_WINDOW,
@ -550,9 +552,10 @@ enum ActionType {
}; };
struct UndoPatternData { struct UndoPatternData {
int chan, pat, row, col; int subSong, chan, pat, row, col;
short oldVal, newVal; 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), chan(c),
pat(p), pat(p),
row(r), row(r),
@ -562,9 +565,10 @@ struct UndoPatternData {
}; };
struct UndoOrderData { struct UndoOrderData {
int chan, ord; int subSong, chan, ord;
unsigned char oldVal, newVal; 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), chan(c),
ord(o), ord(o),
oldVal(v1), oldVal(v1),
@ -1010,12 +1014,14 @@ class FurnaceGUI {
bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen; bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen;
bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen; bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen;
bool pianoOpen, notesOpen, channelsOpen, regViewOpen, logOpen, effectListOpen, chanOscOpen; bool pianoOpen, notesOpen, channelsOpen, regViewOpen, logOpen, effectListOpen, chanOscOpen;
bool subSongsOpen;
/* there ought to be a better way... /* there ought to be a better way...
bool editControlsDocked, ordersDocked, insListDocked, songInfoDocked, patternDocked, insEditDocked; bool editControlsDocked, ordersDocked, insListDocked, songInfoDocked, patternDocked, insEditDocked;
bool waveListDocked, waveEditDocked, sampleListDocked, sampleEditDocked, aboutDocked, settingsDocked; bool waveListDocked, waveEditDocked, sampleListDocked, sampleEditDocked, aboutDocked, settingsDocked;
bool mixerDocked, debugDocked, inspectorDocked, oscDocked, volMeterDocked, statsDocked, compatFlagsDocked; bool mixerDocked, debugDocked, inspectorDocked, oscDocked, volMeterDocked, statsDocked, compatFlagsDocked;
bool pianoDocked, notesDocked, channelsDocked, regViewDocked, logDocked, effectListDocked, chanOscDocked; bool pianoDocked, notesDocked, channelsDocked, regViewDocked, logDocked, effectListDocked, chanOscDocked;
bool subSongsDocked;
*/ */
SelectionPoint selStart, selEnd, cursor; SelectionPoint selStart, selEnd, cursor;
@ -1246,6 +1252,7 @@ class FurnaceGUI {
void drawNewSong(); void drawNewSong();
void drawLog(); void drawLog();
void drawEffectList(); void drawEffectList();
void drawSubSongs();
void parseKeybinds(); void parseKeybinds();
void promptKey(int which); void promptKey(int which);

View file

@ -116,8 +116,8 @@ const char* insTypes[DIV_INS_MAX]={
const char* sampleDepths[17]={ const char* sampleDepths[17]={
"1-bit PCM", "1-bit PCM",
"1-bit DPCM", "1-bit DPCM",
NULL, "Yamaha AICA",
NULL, "YMZ/YMU ADPCM",
"QSound ADPCM", "QSound ADPCM",
"ADPCM-A", "ADPCM-A",
"ADPCM-B", "ADPCM-B",
@ -485,6 +485,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("WINDOW_CHANNELS", "Channels", 0), D("WINDOW_CHANNELS", "Channels", 0),
D("WINDOW_REGISTER_VIEW", "Register View", 0), D("WINDOW_REGISTER_VIEW", "Register View", 0),
D("WINDOW_LOG", "Log Viewer", 0), D("WINDOW_LOG", "Log Viewer", 0),
D("WINDOW_SUBSONGS", "Subsongs", 0),
D("EFFECT_LIST", "Effect List", 0), D("EFFECT_LIST", "Effect List", 0),
D("WINDOW_CHAN_OSC", "Oscilloscope (per-channel)", 0), D("WINDOW_CHAN_OSC", "Oscilloscope (per-channel)", 0),

View file

@ -37,7 +37,7 @@ void FurnaceGUI::drawOrders() {
ImGui::SetColumnWidth(-1,regionX-24.0f*dpiScale); ImGui::SetColumnWidth(-1,regionX-24.0f*dpiScale);
int displayChans=0; int displayChans=0;
for (int i=0; i<e->getTotalChannelCount(); i++) { for (int i=0; i<e->getTotalChannelCount(); i++) {
if (e->song.chanShow[i]) displayChans++; if (e->curSubSong->chanShow[i]) displayChans++;
} }
ImGui::PushFont(patFont); ImGui::PushFont(patFont);
bool tooSmall=((displayChans+1)>((ImGui::GetContentRegionAvail().x)/(ImGui::CalcTextSize("AA").x+2.0*ImGui::GetStyle().ItemInnerSpacing.x))); 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::TableNextColumn();
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]);
for (int i=0; i<e->getTotalChannelCount(); i++) { for (int i=0; i<e->getTotalChannelCount(); i++) {
if (!e->song.chanShow[i]) continue; if (!e->curSubSong->chanShow[i]) continue;
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("%s",e->getChannelShortName(i)); ImGui::Text("%s",e->getChannelShortName(i));
} }
ImGui::PopStyleColor(); ImGui::PopStyleColor();
for (int i=0; i<e->song.ordersLen; i++) { for (int i=0; i<e->curSubSong->ordersLen; i++) {
ImGui::TableNextRow(0,lineHeight); ImGui::TableNextRow(0,lineHeight);
if (oldOrder1==i) ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_ORDER_ACTIVE])); if (oldOrder1==i) ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_ORDER_ACTIVE]));
ImGui::TableNextColumn(); ImGui::TableNextColumn();
@ -84,16 +84,16 @@ void FurnaceGUI::drawOrders() {
} }
ImGui::PopStyleColor(); ImGui::PopStyleColor();
for (int j=0; j<e->getTotalChannelCount(); j++) { for (int j=0; j<e->getTotalChannelCount(); j++) {
if (!e->song.chanShow[j]) continue; if (!e->curSubSong->chanShow[j]) continue;
ImGui::TableNextColumn(); 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()) { /*if (!pat->name.empty()) {
snprintf(selID,4096,"%s##O_%.2x_%.2x",pat->name.c_str(),j,i); snprintf(selID,4096,"%s##O_%.2x_%.2x",pat->name.c_str(),j,i);
} else {*/ } 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 (ImGui::Selectable(selID,(orderEditMode!=0 && curOrder==i && orderCursor==j))) {
if (curOrder==i) { if (curOrder==i) {
if (orderEditMode==0) { if (orderEditMode==0) {
@ -101,10 +101,10 @@ void FurnaceGUI::drawOrders() {
e->lockSave([this,i,j]() { e->lockSave([this,i,j]() {
if (changeAllOrders) { if (changeAllOrders) {
for (int k=0; k<e->getTotalChannelCount(); k++) { for (int k=0; k<e->getTotalChannelCount(); 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 { } 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); e->walkSong(loopOrder,loopRow,loopEnd);
@ -137,10 +137,10 @@ void FurnaceGUI::drawOrders() {
e->lockSave([this,i,j]() { e->lockSave([this,i,j]() {
if (changeAllOrders) { if (changeAllOrders) {
for (int k=0; k<e->getTotalChannelCount(); k++) { for (int k=0; k<e->getTotalChannelCount(); 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 { } 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); e->walkSong(loopOrder,loopRow,loopEnd);

View file

@ -43,21 +43,21 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
return; return;
} }
// check if we are in range // check if we are in range
if (ord<0 || ord>=e->song.ordersLen) { if (ord<0 || ord>=e->curSubSong->ordersLen) {
return; return;
} }
if (i<0 || i>=e->song.patLen) { if (i<0 || i>=e->curSubSong->patLen) {
return; return;
} }
bool isPushing=false; bool isPushing=false;
ImVec4 activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE]; ImVec4 activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE];
ImVec4 inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE]; ImVec4 inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE];
ImVec4 rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX]; 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]; activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE_HI2];
inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE_HI2]; inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE_HI2];
rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX_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]; activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE_HI1];
inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE_HI1]; inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE_HI1];
rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX_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])); ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_EDITING]));
} else if (isPlaying && oldRow==i && ord==e->getOrder()) { } else if (isPlaying && oldRow==i && ord==e->getOrder()) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PLAY_HEAD])); 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])); 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])); ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_1]));
} }
} else { } 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])); ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_EDITING]));
} else if (isPlaying && oldRow==i && ord==e->getOrder()) { } else if (isPlaying && oldRow==i && ord==e->getOrder()) {
ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PLAY_HEAD])); 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])); 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])); ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_1]));
} else { } else {
isPushing=false; isPushing=false;
@ -106,7 +106,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
// for each column // for each column
for (int j=0; j<chans; j++) { for (int j=0; j<chans; j++) {
// check if channel is not hidden // check if channel is not hidden
if (!e->song.chanShow[j]) { if (!e->curSubSong->chanShow[j]) {
patChanX[j]=ImGui::GetCursorPosX(); patChanX[j]=ImGui::GetCursorPosX();
continue; continue;
} }
@ -155,7 +155,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
ImGui::PopStyleColor(); ImGui::PopStyleColor();
// the following is only visible when the channel is not collapsed // the following is only visible when the channel is not collapsed
if (e->song.chanCollapse[j]<3) { if (e->curSubSong->chanCollapse[j]<3) {
// instrument // instrument
if (pat->data[i][2]==-1) { if (pat->data[i][2]==-1) {
ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor); ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor);
@ -195,7 +195,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
ImGui::PopStyleColor(); ImGui::PopStyleColor();
} }
if (e->song.chanCollapse[j]<2) { if (e->curSubSong->chanCollapse[j]<2) {
// volume // volume
if (pat->data[i][3]==-1) { if (pat->data[i][3]==-1) {
sprintf(id,"..##PV_%d_%d",i,j); sprintf(id,"..##PV_%d_%d",i,j);
@ -229,9 +229,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
ImGui::PopStyleColor(); ImGui::PopStyleColor();
} }
if (e->song.chanCollapse[j]<1) { if (e->curSubSong->chanCollapse[j]<1) {
// effects // effects
for (int k=0; k<e->song.pat[j].effectCols; k++) { for (int k=0; k<e->curPat[j].effectCols; k++) {
int index=4+(k<<1); int index=4+(k<<1);
bool selectedEffect=selectedRow && (j32+index-1>=sel1XSum && j32+index-1<=sel2XSum); bool selectedEffect=selectedRow && (j32+index-1>=sel1XSum && j32+index-1<=sel2XSum);
bool selectedEffectVal=selectedRow && (j32+index>=sel1XSum && j32+index<=sel2XSum); bool selectedEffectVal=selectedRow && (j32+index>=sel1XSum && j32+index<=sel2XSum);
@ -358,7 +358,7 @@ void FurnaceGUI::drawPattern() {
int displayChans=0; int displayChans=0;
const DivPattern* patCache[DIV_MAX_CHANS]; const DivPattern* patCache[DIV_MAX_CHANS];
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
if (e->song.chanShow[i]) displayChans++; if (e->curSubSong->chanShow[i]) displayChans++;
} }
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0.0f,0.0f)); ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0.0f,0.0f));
ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]); ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]);
@ -383,7 +383,7 @@ void FurnaceGUI::drawPattern() {
} }
ImGui::TableSetupScrollFreeze(1,1); ImGui::TableSetupScrollFreeze(1,1);
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
if (!e->song.chanShow[i]) continue; if (!e->curSubSong->chanShow[i]) continue;
ImGui::TableSetupColumn(fmt::sprintf("c%d",i).c_str(),ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn(fmt::sprintf("c%d",i).c_str(),ImGuiTableColumnFlags_WidthFixed);
} }
ImGui::TableNextRow(); ImGui::TableNextRow();
@ -408,10 +408,10 @@ void FurnaceGUI::drawPattern() {
cmdStream.clear(); cmdStream.clear();
} }
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
if (!e->song.chanShow[i]) continue; if (!e->curSubSong->chanShow[i]) continue;
ImGui::TableNextColumn(); ImGui::TableNextColumn();
bool displayTooltip=false; bool displayTooltip=false;
if (e->song.chanCollapse[i]) { if (e->curSubSong->chanCollapse[i]) {
const char* chName=e->getChannelShortName(i); const char* chName=e->getChannelShortName(i);
if (strlen(chName)>3) { if (strlen(chName)>3) {
snprintf(chanID,2048,"...##_CH%d",i); snprintf(chanID,2048,"...##_CH%d",i);
@ -421,7 +421,7 @@ void FurnaceGUI::drawPattern() {
displayTooltip=true; displayTooltip=true;
} else { } else {
const char* chName=e->getChannelName(i); const char* chName=e->getChannelName(i);
size_t chNameLimit=6+4*e->song.pat[i].effectCols; size_t chNameLimit=6+4*e->curPat[i].effectCols;
if (strlen(chName)>chNameLimit) { if (strlen(chName)>chNameLimit) {
String shortChName=chName; String shortChName=chName;
shortChName.resize(chNameLimit-3); shortChName.resize(chNameLimit-3);
@ -483,7 +483,7 @@ void FurnaceGUI::drawPattern() {
e->toggleSolo(i); e->toggleSolo(i);
} }
if (extraChannelButtons==2) { if (extraChannelButtons==2) {
DivPattern* pat=e->song.pat[i].getPattern(e->song.orders.ord[i][ord],true); DivPattern* pat=e->curPat[i].getPattern(e->curOrders->ord[i][ord],true);
ImGui::PushFont(mainFont); ImGui::PushFont(mainFont);
if (patNameTarget==i) { if (patNameTarget==i) {
snprintf(chanID,2048,"##PatNameI%d_%d",i,ord); snprintf(chanID,2048,"##PatNameI%d_%d",i,ord);
@ -510,30 +510,30 @@ void FurnaceGUI::drawPattern() {
} }
ImGui::PopFont(); ImGui::PopFont();
} else if (extraChannelButtons==1) { } else if (extraChannelButtons==1) {
snprintf(chanID,2048,"%c##_HCH%d",e->song.chanCollapse[i]?'+':'-',i); snprintf(chanID,2048,"%c##_HCH%d",e->curSubSong->chanCollapse[i]?'+':'-',i);
ImGui::SetCursorPosX(ImGui::GetCursorPosX()+4.0f*dpiScale); ImGui::SetCursorPosX(ImGui::GetCursorPosX()+4.0f*dpiScale);
if (ImGui::SmallButton(chanID)) { if (ImGui::SmallButton(chanID)) {
if (e->song.chanCollapse[i]==0) { if (e->curSubSong->chanCollapse[i]==0) {
e->song.chanCollapse[i]=3; e->curSubSong->chanCollapse[i]=3;
} else if (e->song.chanCollapse[i]>0) { } else if (e->curSubSong->chanCollapse[i]>0) {
e->song.chanCollapse[i]--; e->curSubSong->chanCollapse[i]--;
} }
} }
if (!e->song.chanCollapse[i]) { if (!e->curSubSong->chanCollapse[i]) {
ImGui::SameLine(); ImGui::SameLine();
snprintf(chanID,2048,"<##_LCH%d",i); snprintf(chanID,2048,"<##_LCH%d",i);
ImGui::BeginDisabled(e->song.pat[i].effectCols<=1); ImGui::BeginDisabled(e->curPat[i].effectCols<=1);
if (ImGui::SmallButton(chanID)) { if (ImGui::SmallButton(chanID)) {
e->song.pat[i].effectCols--; e->curPat[i].effectCols--;
if (e->song.pat[i].effectCols<1) e->song.pat[i].effectCols=1; if (e->curPat[i].effectCols<1) e->curPat[i].effectCols=1;
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
ImGui::SameLine(); ImGui::SameLine();
ImGui::BeginDisabled(e->song.pat[i].effectCols>=8); ImGui::BeginDisabled(e->curPat[i].effectCols>=8);
snprintf(chanID,2048,">##_RCH%d",i); snprintf(chanID,2048,">##_RCH%d",i);
if (ImGui::SmallButton(chanID)) { if (ImGui::SmallButton(chanID)) {
e->song.pat[i].effectCols++; e->curPat[i].effectCols++;
if (e->song.pat[i].effectCols>8) e->song.pat[i].effectCols=8; if (e->curPat[i].effectCols>8) e->curPat[i].effectCols=8;
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
} }
@ -555,10 +555,10 @@ void FurnaceGUI::drawPattern() {
ImGui::BeginDisabled(); ImGui::BeginDisabled();
if (settings.viewPrevPattern) { if (settings.viewPrevPattern) {
if ((ord-1)>=0) for (int i=0; i<chans; i++) { if ((ord-1)>=0) for (int i=0; i<chans; i++) {
patCache[i]=e->song.pat[i].getPattern(e->song.orders.ord[i][ord-1],true); patCache[i]=e->curPat[i].getPattern(e->curOrders->ord[i][ord-1],true);
} }
for (int i=0; i<dummyRows-1; i++) { for (int i=0; i<dummyRows-1; i++) {
patternRow(e->song.patLen+i-dummyRows+1,e->isPlaying(),lineHeight,chans,ord-1,patCache,true); patternRow(e->curSubSong->patLen+i-dummyRows+1,e->isPlaying(),lineHeight,chans,ord-1,patCache,true);
} }
} else { } else {
for (int i=0; i<dummyRows-1; i++) { for (int i=0; i<dummyRows-1; i++) {
@ -569,16 +569,16 @@ void FurnaceGUI::drawPattern() {
ImGui::EndDisabled(); ImGui::EndDisabled();
// active area // active area
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {
patCache[i]=e->song.pat[i].getPattern(e->song.orders.ord[i][ord],true); patCache[i]=e->curPat[i].getPattern(e->curOrders->ord[i][ord],true);
} }
for (int i=0; i<e->song.patLen; i++) { for (int i=0; i<e->curSubSong->patLen; i++) {
patternRow(i,e->isPlaying(),lineHeight,chans,ord,patCache,false); patternRow(i,e->isPlaying(),lineHeight,chans,ord,patCache,false);
} }
// next pattern // next pattern
ImGui::BeginDisabled(); ImGui::BeginDisabled();
if (settings.viewPrevPattern) { if (settings.viewPrevPattern) {
if ((ord+1)<e->song.ordersLen) for (int i=0; i<chans; i++) { if ((ord+1)<e->curSubSong->ordersLen) for (int i=0; i<chans; i++) {
patCache[i]=e->song.pat[i].getPattern(e->song.orders.ord[i][ord+1],true); patCache[i]=e->curPat[i].getPattern(e->curOrders->ord[i][ord+1],true);
} }
for (int i=0; i<=dummyRows; i++) { for (int i=0; i<=dummyRows; i++) {
patternRow(i,e->isPlaying(),lineHeight,chans,ord+1,patCache,true); patternRow(i,e->isPlaying(),lineHeight,chans,ord+1,patCache,true);
@ -612,7 +612,7 @@ void FurnaceGUI::drawPattern() {
if (curOrder>0) { if (curOrder>0) {
setOrder(curOrder-1); setOrder(curOrder-1);
ImGui::SetScrollY(ImGui::GetScrollMaxY()); ImGui::SetScrollY(ImGui::GetScrollMaxY());
updateScroll(e->song.patLen); updateScroll(e->curSubSong->patLen);
} }
haveHitBounds=false; haveHitBounds=false;
} else { } else {
@ -624,7 +624,7 @@ void FurnaceGUI::drawPattern() {
} else { } else {
if (ImGui::GetScrollY()>=ImGui::GetScrollMaxY()) { if (ImGui::GetScrollY()>=ImGui::GetScrollMaxY()) {
if (haveHitBounds) { if (haveHitBounds) {
if (curOrder<(e->song.ordersLen-1)) { if (curOrder<(e->curSubSong->ordersLen-1)) {
setOrder(curOrder+1); setOrder(curOrder+1);
ImGui::SetScrollY(0); ImGui::SetScrollY(0);
updateScroll(0); updateScroll(0);
@ -776,7 +776,7 @@ void FurnaceGUI::drawPattern() {
// note slides // note slides
ImVec2 arrowPoints[7]; ImVec2 arrowPoints[7];
if (e->isPlaying()) for (int i=0; i<chans; i++) { if (e->isPlaying()) for (int i=0; i<chans; i++) {
if (!e->song.chanShow[i]) continue; if (!e->curSubSong->chanShow[i]) continue;
DivChannelState* ch=e->getChanState(i); DivChannelState* ch=e->getChanState(i);
if (ch->portaSpeed>0) { if (ch->portaSpeed>0) {
ImVec4 col=uiColors[GUI_COLOR_PATTERN_EFFECT_PITCH]; ImVec4 col=uiColors[GUI_COLOR_PATTERN_EFFECT_PITCH];

View file

@ -1495,6 +1495,7 @@ void FurnaceGUI::drawSettings() {
UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_INS_LIST); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_INS_LIST);
UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_INS_EDIT); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_INS_EDIT);
UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_SONG_INFO); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_SONG_INFO);
UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_SUBSONGS);
UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_PATTERN); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_PATTERN);
UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_WAVE_LIST); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_WAVE_LIST);
UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_WAVE_EDIT); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_WAVE_EDIT);

View file

@ -18,6 +18,7 @@
*/ */
#include "gui.h" #include "gui.h"
#include "imgui.h"
#include "misc/cpp/imgui_stdlib.h" #include "misc/cpp/imgui_stdlib.h"
#include "intConst.h" #include "intConst.h"
@ -74,28 +75,28 @@ void FurnaceGUI::drawSongInfo() {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
float avail=ImGui::GetContentRegionAvail().x; float avail=ImGui::GetContentRegionAvail().x;
ImGui::SetNextItemWidth(avail); ImGui::SetNextItemWidth(avail);
unsigned char realTB=e->song.timeBase+1; unsigned char realTB=e->curSubSong->timeBase+1;
if (ImGui::InputScalar("##TimeBase",ImGuiDataType_U8,&realTB,&_ONE,&_THREE)) { MARK_MODIFIED if (ImGui::InputScalar("##TimeBase",ImGuiDataType_U8,&realTB,&_ONE,&_THREE)) { MARK_MODIFIED
if (realTB<1) realTB=1; if (realTB<1) realTB=1;
if (realTB>16) realTB=16; if (realTB>16) realTB=16;
e->song.timeBase=realTB-1; e->curSubSong->timeBase=realTB-1;
} }
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("%.2f BPM",calcBPM(e->song.speed1,e->song.speed2,e->song.hz)); ImGui::Text("%.2f BPM",calcBPM(e->curSubSong->speed1,e->curSubSong->speed2,e->curSubSong->hz));
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("Speed"); ImGui::Text("Speed");
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail); ImGui::SetNextItemWidth(avail);
if (ImGui::InputScalar("##Speed1",ImGuiDataType_U8,&e->song.speed1,&_ONE,&_THREE)) { MARK_MODIFIED if (ImGui::InputScalar("##Speed1",ImGuiDataType_U8,&e->curSubSong->speed1,&_ONE,&_THREE)) { MARK_MODIFIED
if (e->song.speed1<1) e->song.speed1=1; if (e->curSubSong->speed1<1) e->curSubSong->speed1=1;
if (e->isPlaying()) play(); if (e->isPlaying()) play();
} }
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail); ImGui::SetNextItemWidth(avail);
if (ImGui::InputScalar("##Speed2",ImGuiDataType_U8,&e->song.speed2,&_ONE,&_THREE)) { MARK_MODIFIED if (ImGui::InputScalar("##Speed2",ImGuiDataType_U8,&e->curSubSong->speed2,&_ONE,&_THREE)) { MARK_MODIFIED
if (e->song.speed2<1) e->song.speed2=1; if (e->curSubSong->speed2<1) e->curSubSong->speed2=1;
if (e->isPlaying()) play(); if (e->isPlaying()) play();
} }
@ -104,12 +105,12 @@ void FurnaceGUI::drawSongInfo() {
ImGui::Text("Highlight"); ImGui::Text("Highlight");
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail); ImGui::SetNextItemWidth(avail);
if (ImGui::InputScalar("##Highlight1",ImGuiDataType_U8,&e->song.hilightA,&_ONE,&_THREE)) { if (ImGui::InputScalar("##Highlight1",ImGuiDataType_U8,&e->curSubSong->hilightA,&_ONE,&_THREE)) {
MARK_MODIFIED; MARK_MODIFIED;
} }
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail); ImGui::SetNextItemWidth(avail);
if (ImGui::InputScalar("##Highlight2",ImGuiDataType_U8,&e->song.hilightB,&_ONE,&_THREE)) { if (ImGui::InputScalar("##Highlight2",ImGuiDataType_U8,&e->curSubSong->hilightB,&_ONE,&_THREE)) {
MARK_MODIFIED; MARK_MODIFIED;
} }
@ -118,11 +119,11 @@ void FurnaceGUI::drawSongInfo() {
ImGui::Text("Pattern Length"); ImGui::Text("Pattern Length");
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail); ImGui::SetNextItemWidth(avail);
int patLen=e->song.patLen; int patLen=e->curSubSong->patLen;
if (ImGui::InputInt("##PatLength",&patLen,1,3)) { MARK_MODIFIED if (ImGui::InputInt("##PatLength",&patLen,1,3)) { MARK_MODIFIED
if (patLen<1) patLen=1; if (patLen<1) patLen=1;
if (patLen>256) patLen=256; if (patLen>256) patLen=256;
e->song.patLen=patLen; e->curSubSong->patLen=patLen;
} }
ImGui::TableNextRow(); ImGui::TableNextRow();
@ -130,11 +131,11 @@ void FurnaceGUI::drawSongInfo() {
ImGui::Text("Song Length"); ImGui::Text("Song Length");
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail); ImGui::SetNextItemWidth(avail);
int ordLen=e->song.ordersLen; int ordLen=e->curSubSong->ordersLen;
if (ImGui::InputInt("##OrdLength",&ordLen,1,3)) { MARK_MODIFIED if (ImGui::InputInt("##OrdLength",&ordLen,1,3)) { MARK_MODIFIED
if (ordLen<1) ordLen=1; if (ordLen<1) ordLen=1;
if (ordLen>256) ordLen=256; if (ordLen>256) ordLen=256;
e->song.ordersLen=ordLen; e->curSubSong->ordersLen=ordLen;
if (curOrder>=ordLen) { if (curOrder>=ordLen) {
setOrder(ordLen-1); setOrder(ordLen-1);
} }
@ -147,7 +148,7 @@ void FurnaceGUI::drawSongInfo() {
} }
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail); ImGui::SetNextItemWidth(avail);
float setHz=tempoView?e->song.hz*2.5:e->song.hz; float setHz=tempoView?e->curSubSong->hz*2.5:e->curSubSong->hz;
if (ImGui::InputFloat("##Rate",&setHz,1.0f,1.0f,"%g")) { MARK_MODIFIED if (ImGui::InputFloat("##Rate",&setHz,1.0f,1.0f,"%g")) { MARK_MODIFIED
if (tempoView) setHz/=2.5; if (tempoView) setHz/=2.5;
if (setHz<10) setHz=10; if (setHz<10) setHz=10;
@ -156,13 +157,13 @@ void FurnaceGUI::drawSongInfo() {
} }
if (tempoView) { if (tempoView) {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("= %gHz",e->song.hz); ImGui::Text("= %gHz",e->curSubSong->hz);
} else { } else {
if (e->song.hz>=49.98 && e->song.hz<=50.02) { if (e->curSubSong->hz>=49.98 && e->curSubSong->hz<=50.02) {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("PAL"); ImGui::Text("PAL");
} }
if (e->song.hz>=59.9 && e->song.hz<=60.11) { if (e->curSubSong->hz>=59.9 && e->curSubSong->hz<=60.11) {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("NTSC"); ImGui::Text("NTSC");
} }

94
src/gui/subSongs.cpp Normal file
View file

@ -0,0 +1,94 @@
#include "gui.h"
#include "imgui.h"
#include "IconsFontAwesome4.h"
#include "misc/cpp/imgui_stdlib.h"
void FurnaceGUI::drawSubSongs() {
if (nextWindow==GUI_WINDOW_SUBSONGS) {
subSongsOpen=true;
ImGui::SetNextWindowFocus();
nextWindow=GUI_WINDOW_NOTHING;
}
if (!oscOpen) return;
ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
if (ImGui::Begin("Subsongs",&subSongsOpen,ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar)) {
char id[1024];
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-ImGui::GetFrameHeightWithSpacing()*2.0f-ImGui::GetStyle().ItemSpacing.x);
if (e->curSubSong->name.empty()) {
snprintf(id,1023,"%d. <no name>",(int)e->getCurrentSubSong()+1);
} else {
snprintf(id,1023,"%d. %s",(int)e->getCurrentSubSong()+1,e->curSubSong->name.c_str());
}
if (ImGui::BeginCombo("##SubSong",id)) {
for (size_t i=0; i<e->song.subsong.size(); i++) {
if (e->song.subsong[i]->name.empty()) {
snprintf(id,1023,"%d. <no name>",(int)i+1);
} else {
snprintf(id,1023,"%d. %s",(int)i+1,e->song.subsong[i]->name.c_str());
}
if (ImGui::Selectable(id,i==e->getCurrentSubSong())) {
e->changeSongP(i);
updateScroll(0);
oldOrder=0;
oldOrder1=0;
oldRow=0;
cursor.xCoarse=0;
cursor.xFine=0;
cursor.y=0;
selStart=cursor;
selEnd=cursor;
curOrder=0;
}
}
ImGui::EndCombo();
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_PLUS "##SubSongAdd")) {
if (!e->addSubSong()) {
showError("too many subsongs!");
} else {
e->changeSongP(e->song.subsong.size()-1);
updateScroll(0);
oldOrder=0;
oldOrder1=0;
oldRow=0;
cursor.xCoarse=0;
cursor.xFine=0;
cursor.y=0;
selStart=cursor;
selEnd=cursor;
curOrder=0;
MARK_MODIFIED;
}
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_MINUS "##SubSongDel")) {
if (!e->removeSubSong(e->getCurrentSubSong())) {
showError("this is the only subsong!");
} else {
undoHist.clear();
redoHist.clear();
updateScroll(0);
oldOrder=0;
oldOrder1=0;
oldRow=0;
cursor.xCoarse=0;
cursor.xFine=0;
cursor.y=0;
selStart=cursor;
selEnd=cursor;
curOrder=0;
MARK_MODIFIED;
}
}
ImGui::Text("Name");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::InputText("##SubSongName",&e->curSubSong->name)) {
MARK_MODIFIED;
}
}
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SUBSONGS;
ImGui::End();
}