dev95 - multiple songs in a single file (READ)
experimental feature! proceed with caution. if you experience song corruption or crashes, report issue immediately! files with multiple songs will be readable in older versions of Furnace, but only the first song will be read in those versions. issue #199
This commit is contained in:
parent
14053f70cb
commit
c5786b61fb
|
@ -445,6 +445,7 @@ src/gui/settings.cpp
|
|||
src/gui/songInfo.cpp
|
||||
src/gui/songNotes.cpp
|
||||
src/gui/stats.cpp
|
||||
src/gui/subSongs.cpp
|
||||
src/gui/sysConf.cpp
|
||||
src/gui/sysEx.cpp
|
||||
src/gui/util.cpp
|
||||
|
|
|
@ -29,6 +29,11 @@ furthermore, an `or reserved` indicates this field is always present, but is res
|
|||
|
||||
the format versions are:
|
||||
|
||||
- 95: Furnace dev95
|
||||
- 94: Furnace dev94
|
||||
- 93: Furnace dev93
|
||||
- 92: Furnace dev92
|
||||
- 91: Furnace dev91
|
||||
- 90: Furnace dev90
|
||||
- 89: Furnace dev89
|
||||
- 88: Furnace dev88
|
||||
|
@ -122,26 +127,26 @@ size | description
|
|||
-----|------------------------------------
|
||||
4 | "INFO" block ID
|
||||
4 | reserved
|
||||
1 | time base
|
||||
1 | speed 1
|
||||
1 | speed 2
|
||||
1 | initial arpeggio time
|
||||
4f | ticks per second
|
||||
1 | time base (of first song)
|
||||
1 | speed 1 (of first song)
|
||||
1 | speed 2 (of first song)
|
||||
1 | initial arpeggio time (of first song)
|
||||
4f | ticks per second (of first song)
|
||||
| - 60 is NTSC
|
||||
| - 50 is PAL
|
||||
2 | pattern length
|
||||
2 | pattern length (of first song)
|
||||
| - the limit is 256.
|
||||
2 | orders length
|
||||
2 | orders length (of first song)
|
||||
| - the limit is 256 (>=80) or 127 (<80).
|
||||
1 | highlight A
|
||||
1 | highlight B
|
||||
1 | highlight A (of first song)
|
||||
1 | highlight B (of first song)
|
||||
2 | instrument count
|
||||
| - the limit is 256.
|
||||
2 | wavetable count
|
||||
| - the limit is 256.
|
||||
2 | sample count
|
||||
| - the limit is 256.
|
||||
4 | pattern count
|
||||
4 | pattern count (global)
|
||||
32 | list of sound chips
|
||||
| - possible soundchips:
|
||||
| - 0x00: end of list
|
||||
|
@ -258,20 +263,20 @@ size | description
|
|||
4?? | pointers to wavetables
|
||||
4?? | pointers to samples
|
||||
4?? | pointers to patterns
|
||||
??? | orders
|
||||
??? | orders (of first song)
|
||||
| - a table of bytes
|
||||
| - size=channels*ordLen
|
||||
| - read orders then channels
|
||||
| - the maximum value of a cell is FF (>=80) or 7F (<80).
|
||||
??? | effect columns
|
||||
??? | effect columns (of first song)
|
||||
| - size=channels
|
||||
1?? | channel hide status
|
||||
1?? | channel hide status (of first song)
|
||||
| - size=channels
|
||||
1?? | channel collapse status
|
||||
1?? | channel collapse status (of first song)
|
||||
| - size=channels
|
||||
S?? | channel names
|
||||
S?? | channel names (of first song)
|
||||
| - a list of channelCount C strings
|
||||
S?? | channel short names
|
||||
S?? | channel short names (of first song)
|
||||
| - same as above
|
||||
STR | song comment
|
||||
4f | master volume, 1.0f=100% (>=59)
|
||||
|
@ -292,6 +297,55 @@ size | description
|
|||
1 | pitch macro is linear (>=90) or reserved
|
||||
1 | pitch slide speed in full linear pitch mode (>=94) or reserved
|
||||
18 | reserved
|
||||
--- | **additional subsongs** (>=95)
|
||||
STR | first subsong name
|
||||
STR | first subsong comment
|
||||
1 | number of additional subsongs
|
||||
3 | reserved
|
||||
4?? | pointers to subsong data
|
||||
```
|
||||
|
||||
# subsong
|
||||
|
||||
from version 95 onwards, Furnace supports storing multiple songs on a single file.
|
||||
the way it's currently done is really weird, but it provides for some backwards compatibility (previous versions will only load the first subsong which is already defined in the `INFO` block).
|
||||
|
||||
```
|
||||
size | description
|
||||
-----|------------------------------------
|
||||
4 | "SONG" block ID
|
||||
4 | reserved
|
||||
1 | time base
|
||||
1 | speed 1
|
||||
1 | speed 2
|
||||
1 | initial arpeggio time
|
||||
4f | ticks per second
|
||||
| - 60 is NTSC
|
||||
| - 50 is PAL
|
||||
2 | pattern length
|
||||
| - the limit is 256.
|
||||
2 | orders length
|
||||
| - the limit is 256.
|
||||
1 | highlight A
|
||||
1 | highlight B
|
||||
4 | reserved
|
||||
STR | subsong name
|
||||
STR | subsong comment
|
||||
??? | orders
|
||||
| - a table of bytes
|
||||
| - size=channels*ordLen
|
||||
| - read orders then channels
|
||||
| - the maximum value of a cell is FF.
|
||||
??? | effect columns
|
||||
| - size=channels
|
||||
1?? | channel hide status
|
||||
| - size=channels
|
||||
1?? | channel collapse status
|
||||
| - size=channels
|
||||
S?? | channel names
|
||||
| - a list of channelCount C strings
|
||||
S?? | channel short names
|
||||
| - same as above
|
||||
```
|
||||
|
||||
# instrument
|
||||
|
@ -772,7 +826,8 @@ size | description
|
|||
4 | reserved
|
||||
2 | channel
|
||||
2 | pattern index
|
||||
4 | reserved
|
||||
2 | subsong (>=95) or reserved
|
||||
2 | reserved
|
||||
??? | pattern data
|
||||
| - size: rows*(4+effectColumns*2)*2
|
||||
| - read shorts in this order:
|
||||
|
|
|
@ -139,18 +139,18 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) {
|
|||
int nextRow=0;
|
||||
int effectVal=0;
|
||||
DivPattern* pat[DIV_MAX_CHANS];
|
||||
for (int i=0; i<song.ordersLen; i++) {
|
||||
for (int i=0; i<curSubSong->ordersLen; i++) {
|
||||
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;
|
||||
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)];
|
||||
if (effectVal<0) effectVal=0;
|
||||
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;
|
||||
nextRow=effectVal;
|
||||
}
|
||||
|
@ -695,6 +695,7 @@ void DivEngine::createNew(const int* description) {
|
|||
saveLock.lock();
|
||||
song.unload();
|
||||
song=DivSong();
|
||||
changeSong(0);
|
||||
if (description!=NULL) {
|
||||
initSongWithDesc(description);
|
||||
}
|
||||
|
@ -716,45 +717,55 @@ void DivEngine::swapChannels(int src, int dest) {
|
|||
}
|
||||
|
||||
for (int i=0; i<256; i++) {
|
||||
song.orders.ord[dest][i]^=song.orders.ord[src][i];
|
||||
song.orders.ord[src][i]^=song.orders.ord[dest][i];
|
||||
song.orders.ord[dest][i]^=song.orders.ord[src][i];
|
||||
curOrders->ord[dest][i]^=curOrders->ord[src][i];
|
||||
curOrders->ord[src][i]^=curOrders->ord[dest][i];
|
||||
curOrders->ord[dest][i]^=curOrders->ord[src][i];
|
||||
|
||||
DivPattern* prev=song.pat[src].data[i];
|
||||
song.pat[src].data[i]=song.pat[dest].data[i];
|
||||
song.pat[dest].data[i]=prev;
|
||||
DivPattern* prev=curPat[src].data[i];
|
||||
curPat[src].data[i]=curPat[dest].data[i];
|
||||
curPat[dest].data[i]=prev;
|
||||
}
|
||||
|
||||
song.pat[src].effectCols^=song.pat[dest].effectCols;
|
||||
song.pat[dest].effectCols^=song.pat[src].effectCols;
|
||||
song.pat[src].effectCols^=song.pat[dest].effectCols;
|
||||
curPat[src].effectCols^=curPat[dest].effectCols;
|
||||
curPat[dest].effectCols^=curPat[src].effectCols;
|
||||
curPat[src].effectCols^=curPat[dest].effectCols;
|
||||
|
||||
String prevChanName=song.chanName[src];
|
||||
String prevChanShortName=song.chanShortName[src];
|
||||
bool prevChanShow=song.chanShow[src];
|
||||
bool prevChanCollapse=song.chanCollapse[src];
|
||||
String prevChanName=curSubSong->chanName[src];
|
||||
String prevChanShortName=curSubSong->chanShortName[src];
|
||||
bool prevChanShow=curSubSong->chanShow[src];
|
||||
bool prevChanCollapse=curSubSong->chanCollapse[src];
|
||||
|
||||
song.chanName[src]=song.chanName[dest];
|
||||
song.chanShortName[src]=song.chanShortName[dest];
|
||||
song.chanShow[src]=song.chanShow[dest];
|
||||
song.chanCollapse[src]=song.chanCollapse[dest];
|
||||
song.chanName[dest]=prevChanName;
|
||||
song.chanShortName[dest]=prevChanShortName;
|
||||
song.chanShow[dest]=prevChanShow;
|
||||
song.chanCollapse[dest]=prevChanCollapse;
|
||||
curSubSong->chanName[src]=curSubSong->chanName[dest];
|
||||
curSubSong->chanShortName[src]=curSubSong->chanShortName[dest];
|
||||
curSubSong->chanShow[src]=curSubSong->chanShow[dest];
|
||||
curSubSong->chanCollapse[src]=curSubSong->chanCollapse[dest];
|
||||
curSubSong->chanName[dest]=prevChanName;
|
||||
curSubSong->chanShortName[dest]=prevChanShortName;
|
||||
curSubSong->chanShow[dest]=prevChanShow;
|
||||
curSubSong->chanCollapse[dest]=prevChanCollapse;
|
||||
}
|
||||
|
||||
void DivEngine::stompChannel(int ch) {
|
||||
logV("stomping channel %d",ch);
|
||||
for (int i=0; i<256; i++) {
|
||||
song.orders.ord[ch][i]=0;
|
||||
curOrders->ord[ch][i]=0;
|
||||
}
|
||||
song.pat[ch].wipePatterns();
|
||||
song.pat[ch].effectCols=1;
|
||||
song.chanName[ch]="";
|
||||
song.chanShortName[ch]="";
|
||||
song.chanShow[ch]=true;
|
||||
song.chanCollapse[ch]=false;
|
||||
curPat[ch].wipePatterns();
|
||||
curPat[ch].effectCols=1;
|
||||
curSubSong->chanName[ch]="";
|
||||
curSubSong->chanShortName[ch]="";
|
||||
curSubSong->chanShow[ch]=true;
|
||||
curSubSong->chanCollapse[ch]=false;
|
||||
}
|
||||
|
||||
void DivEngine::changeSong(size_t songIndex) {
|
||||
if (songIndex>=song.subsong.size()) return;
|
||||
curSubSong=song.subsong[songIndex];
|
||||
curPat=song.subsong[songIndex]->pat;
|
||||
curOrders=&song.subsong[songIndex]->orders;
|
||||
curSubSongIndex=songIndex;
|
||||
curOrder=0;
|
||||
curRow=0;
|
||||
}
|
||||
|
||||
void DivEngine::swapChannelsP(int src, int dest) {
|
||||
|
@ -767,6 +778,41 @@ void DivEngine::swapChannelsP(int src, int dest) {
|
|||
BUSY_END;
|
||||
}
|
||||
|
||||
void DivEngine::changeSongP(size_t index) {
|
||||
if (index>=song.subsong.size()) return;
|
||||
if (index==curSubSongIndex) return;
|
||||
stop();
|
||||
BUSY_BEGIN;
|
||||
saveLock.lock();
|
||||
changeSong(index);
|
||||
saveLock.unlock();
|
||||
BUSY_END;
|
||||
}
|
||||
|
||||
int DivEngine::addSubSong() {
|
||||
if (song.subsong.size()>=127) return -1;
|
||||
BUSY_BEGIN;
|
||||
saveLock.lock();
|
||||
song.subsong.push_back(new DivSubSong);
|
||||
saveLock.unlock();
|
||||
BUSY_END;
|
||||
return song.subsong.size()-1;
|
||||
}
|
||||
|
||||
bool DivEngine::removeSubSong(int index) {
|
||||
if (song.subsong.size()<=1) return false;
|
||||
stop();
|
||||
BUSY_BEGIN;
|
||||
saveLock.lock();
|
||||
song.subsong[index]->clearData();
|
||||
delete song.subsong[index];
|
||||
song.subsong.erase(song.subsong.begin()+index);
|
||||
changeSong(0);
|
||||
saveLock.unlock();
|
||||
BUSY_END;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) {
|
||||
int chanCount=chans;
|
||||
quitDispatch();
|
||||
|
@ -1321,15 +1367,15 @@ void DivEngine::reset() {
|
|||
}
|
||||
extValue=0;
|
||||
extValuePresent=0;
|
||||
speed1=song.speed1;
|
||||
speed2=song.speed2;
|
||||
speed1=curSubSong->speed1;
|
||||
speed2=curSubSong->speed2;
|
||||
firstTick=false;
|
||||
nextSpeed=speed1;
|
||||
divider=60;
|
||||
if (song.customTempo) {
|
||||
divider=song.hz;
|
||||
if (curSubSong->customTempo) {
|
||||
divider=curSubSong->hz;
|
||||
} else {
|
||||
if (song.pal) {
|
||||
if (curSubSong->pal) {
|
||||
divider=60;
|
||||
} else {
|
||||
divider=50;
|
||||
|
@ -1476,6 +1522,10 @@ int DivEngine::getRow() {
|
|||
return curRow;
|
||||
}
|
||||
|
||||
size_t DivEngine::getCurrentSubSong() {
|
||||
return curSubSongIndex;
|
||||
}
|
||||
|
||||
unsigned char DivEngine::getSpeed1() {
|
||||
return speed1;
|
||||
}
|
||||
|
@ -1485,9 +1535,9 @@ unsigned char DivEngine::getSpeed2() {
|
|||
}
|
||||
|
||||
float DivEngine::getHz() {
|
||||
if (song.customTempo) {
|
||||
return song.hz;
|
||||
} else if (song.pal) {
|
||||
if (curSubSong->customTempo) {
|
||||
return curSubSong->hz;
|
||||
} else if (curSubSong->pal) {
|
||||
return 60.0;
|
||||
} else {
|
||||
return 50.0;
|
||||
|
@ -1659,10 +1709,10 @@ void DivEngine::delInstrument(int index) {
|
|||
song.insLen=song.ins.size();
|
||||
for (int i=0; i<chans; i++) {
|
||||
for (int j=0; j<256; j++) {
|
||||
if (song.pat[i].data[j]==NULL) continue;
|
||||
for (int k=0; k<song.patLen; k++) {
|
||||
if (song.pat[i].data[j]->data[k][2]>index) {
|
||||
song.pat[i].data[j]->data[k][2]--;
|
||||
if (curPat[i].data[j]==NULL) continue;
|
||||
for (int k=0; k<curSubSong->patLen; k++) {
|
||||
if (curPat[i].data[j]->data[k][2]>index) {
|
||||
curPat[i].data[j]->data[k][2]--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2048,18 +2098,18 @@ void DivEngine::delSample(int index) {
|
|||
|
||||
void DivEngine::addOrder(bool duplicate, bool where) {
|
||||
unsigned char order[DIV_MAX_CHANS];
|
||||
if (song.ordersLen>=0xff) return;
|
||||
if (curSubSong->ordersLen>=0xff) return;
|
||||
BUSY_BEGIN_SOFT;
|
||||
if (duplicate) {
|
||||
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
||||
order[i]=song.orders.ord[i][curOrder];
|
||||
order[i]=curOrders->ord[i][curOrder];
|
||||
}
|
||||
} else {
|
||||
bool used[256];
|
||||
for (int i=0; i<chans; i++) {
|
||||
memset(used,0,sizeof(bool)*256);
|
||||
for (int j=0; j<song.ordersLen; j++) {
|
||||
used[song.orders.ord[i][j]]=true;
|
||||
for (int j=0; j<curSubSong->ordersLen; j++) {
|
||||
used[curOrders->ord[i][j]]=true;
|
||||
}
|
||||
order[i]=0xff;
|
||||
for (int j=0; j<256; j++) {
|
||||
|
@ -2073,19 +2123,19 @@ void DivEngine::addOrder(bool duplicate, bool where) {
|
|||
if (where) { // at the end
|
||||
saveLock.lock();
|
||||
for (int i=0; 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();
|
||||
} else { // after current order
|
||||
saveLock.lock();
|
||||
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
||||
for (int j=song.ordersLen; j>curOrder; j--) {
|
||||
song.orders.ord[i][j]=song.orders.ord[i][j-1];
|
||||
for (int j=curSubSong->ordersLen; j>curOrder; j--) {
|
||||
curOrders->ord[i][j]=curOrders->ord[i][j-1];
|
||||
}
|
||||
song.orders.ord[i][curOrder+1]=order[i];
|
||||
curOrders->ord[i][curOrder+1]=order[i];
|
||||
}
|
||||
song.ordersLen++;
|
||||
curSubSong->ordersLen++;
|
||||
saveLock.unlock();
|
||||
curOrder++;
|
||||
if (playing && !freelance) {
|
||||
|
@ -2097,21 +2147,21 @@ void DivEngine::addOrder(bool duplicate, bool where) {
|
|||
|
||||
void DivEngine::deepCloneOrder(bool where) {
|
||||
unsigned char order[DIV_MAX_CHANS];
|
||||
if (song.ordersLen>=0xff) return;
|
||||
if (curSubSong->ordersLen>=0xff) return;
|
||||
warnings="";
|
||||
BUSY_BEGIN_SOFT;
|
||||
for (int i=0; i<chans; i++) {
|
||||
bool didNotFind=true;
|
||||
logD("channel %d",i);
|
||||
order[i]=song.orders.ord[i][curOrder];
|
||||
order[i]=curOrders->ord[i][curOrder];
|
||||
// find free slot
|
||||
for (int j=0; j<256; j++) {
|
||||
logD("finding free slot in %d...",j);
|
||||
if (song.pat[i].data[j]==NULL) {
|
||||
if (curPat[i].data[j]==NULL) {
|
||||
int origOrd=order[i];
|
||||
order[i]=j;
|
||||
DivPattern* oldPat=song.pat[i].getPattern(origOrd,false);
|
||||
DivPattern* pat=song.pat[i].getPattern(j,true);
|
||||
DivPattern* oldPat=curPat[i].getPattern(origOrd,false);
|
||||
DivPattern* pat=curPat[i].getPattern(j,true);
|
||||
memcpy(pat->data,oldPat->data,256*32*sizeof(short));
|
||||
logD("found at %d",j);
|
||||
didNotFind=false;
|
||||
|
@ -2125,19 +2175,19 @@ void DivEngine::deepCloneOrder(bool where) {
|
|||
if (where) { // at the end
|
||||
saveLock.lock();
|
||||
for (int i=0; i<chans; i++) {
|
||||
song.orders.ord[i][song.ordersLen]=order[i];
|
||||
curOrders->ord[i][curSubSong->ordersLen]=order[i];
|
||||
}
|
||||
song.ordersLen++;
|
||||
curSubSong->ordersLen++;
|
||||
saveLock.unlock();
|
||||
} else { // after current order
|
||||
saveLock.lock();
|
||||
for (int i=0; i<chans; i++) {
|
||||
for (int j=song.ordersLen; j>curOrder; j--) {
|
||||
song.orders.ord[i][j]=song.orders.ord[i][j-1];
|
||||
for (int j=curSubSong->ordersLen; j>curOrder; j--) {
|
||||
curOrders->ord[i][j]=curOrders->ord[i][j-1];
|
||||
}
|
||||
song.orders.ord[i][curOrder+1]=order[i];
|
||||
curOrders->ord[i][curOrder+1]=order[i];
|
||||
}
|
||||
song.ordersLen++;
|
||||
curSubSong->ordersLen++;
|
||||
saveLock.unlock();
|
||||
curOrder++;
|
||||
if (playing && !freelance) {
|
||||
|
@ -2148,17 +2198,17 @@ void DivEngine::deepCloneOrder(bool where) {
|
|||
}
|
||||
|
||||
void DivEngine::deleteOrder() {
|
||||
if (song.ordersLen<=1) return;
|
||||
if (curSubSong->ordersLen<=1) return;
|
||||
BUSY_BEGIN_SOFT;
|
||||
saveLock.lock();
|
||||
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
||||
for (int j=curOrder; j<song.ordersLen; j++) {
|
||||
song.orders.ord[i][j]=song.orders.ord[i][j+1];
|
||||
for (int j=curOrder; j<curSubSong->ordersLen; j++) {
|
||||
curOrders->ord[i][j]=curOrders->ord[i][j+1];
|
||||
}
|
||||
}
|
||||
song.ordersLen--;
|
||||
curSubSong->ordersLen--;
|
||||
saveLock.unlock();
|
||||
if (curOrder>=song.ordersLen) curOrder=song.ordersLen-1;
|
||||
if (curOrder>=curSubSong->ordersLen) curOrder=curSubSong->ordersLen-1;
|
||||
if (playing && !freelance) {
|
||||
playSub(false);
|
||||
}
|
||||
|
@ -2173,9 +2223,9 @@ void DivEngine::moveOrderUp() {
|
|||
}
|
||||
saveLock.lock();
|
||||
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
||||
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder-1];
|
||||
song.orders.ord[i][curOrder-1]^=song.orders.ord[i][curOrder];
|
||||
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder-1];
|
||||
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder-1];
|
||||
curOrders->ord[i][curOrder-1]^=curOrders->ord[i][curOrder];
|
||||
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder-1];
|
||||
}
|
||||
saveLock.unlock();
|
||||
curOrder--;
|
||||
|
@ -2187,15 +2237,15 @@ void DivEngine::moveOrderUp() {
|
|||
|
||||
void DivEngine::moveOrderDown() {
|
||||
BUSY_BEGIN_SOFT;
|
||||
if (curOrder>=song.ordersLen-1) {
|
||||
if (curOrder>=curSubSong->ordersLen-1) {
|
||||
BUSY_END;
|
||||
return;
|
||||
}
|
||||
saveLock.lock();
|
||||
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
||||
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder+1];
|
||||
song.orders.ord[i][curOrder+1]^=song.orders.ord[i][curOrder];
|
||||
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder+1];
|
||||
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder+1];
|
||||
curOrders->ord[i][curOrder+1]^=curOrders->ord[i][curOrder];
|
||||
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder+1];
|
||||
}
|
||||
saveLock.unlock();
|
||||
curOrder++;
|
||||
|
@ -2208,12 +2258,12 @@ void DivEngine::moveOrderDown() {
|
|||
void DivEngine::exchangeIns(int one, int two) {
|
||||
for (int i=0; i<chans; i++) {
|
||||
for (int j=0; j<256; j++) {
|
||||
if (song.pat[i].data[j]==NULL) continue;
|
||||
for (int k=0; k<song.patLen; k++) {
|
||||
if (song.pat[i].data[j]->data[k][2]==one) {
|
||||
song.pat[i].data[j]->data[k][2]=two;
|
||||
} else if (song.pat[i].data[j]->data[k][2]==two) {
|
||||
song.pat[i].data[j]->data[k][2]=one;
|
||||
if (curPat[i].data[j]==NULL) continue;
|
||||
for (int k=0; k<curSubSong->patLen; k++) {
|
||||
if (curPat[i].data[j]->data[k][2]==one) {
|
||||
curPat[i].data[j]->data[k][2]=two;
|
||||
} else if (curPat[i].data[j]->data[k][2]==two) {
|
||||
curPat[i].data[j]->data[k][2]=one;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2417,7 +2467,7 @@ void DivEngine::autoNoteOffAll() {
|
|||
void DivEngine::setOrder(unsigned char order) {
|
||||
BUSY_BEGIN_SOFT;
|
||||
curOrder=order;
|
||||
if (order>=song.ordersLen) curOrder=0;
|
||||
if (order>=curSubSong->ordersLen) curOrder=0;
|
||||
if (playing && !freelance) {
|
||||
playSub(false);
|
||||
}
|
||||
|
@ -2440,15 +2490,15 @@ void DivEngine::setSysFlags(int system, unsigned int flags, bool restart) {
|
|||
void DivEngine::setSongRate(float hz, bool pal) {
|
||||
BUSY_BEGIN;
|
||||
saveLock.lock();
|
||||
song.pal=!pal;
|
||||
song.hz=hz;
|
||||
curSubSong->pal=!pal;
|
||||
curSubSong->hz=hz;
|
||||
// what?
|
||||
song.customTempo=true;
|
||||
curSubSong->customTempo=true;
|
||||
divider=60;
|
||||
if (song.customTempo) {
|
||||
divider=song.hz;
|
||||
if (curSubSong->customTempo) {
|
||||
divider=curSubSong->hz;
|
||||
} else {
|
||||
if (song.pal) {
|
||||
if (curSubSong->pal) {
|
||||
divider=60;
|
||||
} else {
|
||||
divider=50;
|
||||
|
@ -2891,5 +2941,6 @@ bool DivEngine::quit() {
|
|||
if (yrw801ROM!=NULL) delete[] yrw801ROM;
|
||||
if (tg100ROM!=NULL) delete[] tg100ROM;
|
||||
if (mu5ROM!=NULL) delete[] mu5ROM;
|
||||
song.unload();
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -45,8 +45,8 @@
|
|||
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
||||
#define BUSY_END isBusy.unlock(); softLocked=false;
|
||||
|
||||
#define DIV_VERSION "dev94"
|
||||
#define DIV_ENGINE_VERSION 94
|
||||
#define DIV_VERSION "dev95"
|
||||
#define DIV_ENGINE_VERSION 95
|
||||
|
||||
// for imports
|
||||
#define DIV_VERSION_MOD 0xff01
|
||||
|
@ -304,6 +304,7 @@ class DivEngine {
|
|||
bool hasLoadedSomething;
|
||||
int softLockCount;
|
||||
int subticks, ticks, curRow, curOrder, remainingLoops, nextSpeed;
|
||||
size_t curSubSongIndex;
|
||||
double divider;
|
||||
int cycles;
|
||||
double clockDrift;
|
||||
|
@ -411,8 +412,14 @@ class DivEngine {
|
|||
void swapChannels(int src, int dest);
|
||||
void stompChannel(int ch);
|
||||
|
||||
// change song (UNSAFE)
|
||||
void changeSong(size_t songIndex);
|
||||
|
||||
public:
|
||||
DivSong song;
|
||||
DivOrders* curOrders;
|
||||
DivChannelData* curPat;
|
||||
DivSubSong* curSubSong;
|
||||
DivInstrument* tempIns;
|
||||
DivSystem sysOfChan[DIV_MAX_CHANS];
|
||||
int dispatchOfChan[DIV_MAX_CHANS];
|
||||
|
@ -603,6 +610,9 @@ class DivEngine {
|
|||
// get current row
|
||||
int getRow();
|
||||
|
||||
// get current subsong
|
||||
size_t getCurrentSubSong();
|
||||
|
||||
// get speed 1
|
||||
unsigned char getSpeed1();
|
||||
|
||||
|
@ -799,6 +809,15 @@ class DivEngine {
|
|||
// public swap channels
|
||||
void swapChannelsP(int src, int dest);
|
||||
|
||||
// public change song
|
||||
void changeSongP(size_t index);
|
||||
|
||||
// add subsong
|
||||
int addSubSong();
|
||||
|
||||
// remove subsong
|
||||
bool removeSubSong(int index);
|
||||
|
||||
// change system
|
||||
void changeSystem(int index, DivSystem which, bool preserveOrder=true);
|
||||
|
||||
|
@ -900,6 +919,7 @@ class DivEngine {
|
|||
curOrder(0),
|
||||
remainingLoops(-1),
|
||||
nextSpeed(3),
|
||||
curSubSongIndex(0),
|
||||
divider(60),
|
||||
cycles(0),
|
||||
clockDrift(0),
|
||||
|
@ -935,6 +955,8 @@ class DivEngine {
|
|||
metroAmp(0.0f),
|
||||
metroVol(1.0f),
|
||||
totalProcessed(0),
|
||||
curOrders(NULL),
|
||||
curPat(NULL),
|
||||
tempIns(NULL),
|
||||
oscBuf{NULL,NULL},
|
||||
oscSize(1),
|
||||
|
@ -959,6 +981,8 @@ class DivEngine {
|
|||
sysFileMapFur[i]=DIV_SYSTEM_NULL;
|
||||
sysFileMapDMF[i]=DIV_SYSTEM_NULL;
|
||||
}
|
||||
|
||||
changeSong(0);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
|
|
@ -184,57 +184,57 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
|||
|
||||
logI("reading module data...");
|
||||
if (ds.version>0x0c) {
|
||||
ds.hilightA=reader.readC();
|
||||
ds.hilightB=reader.readC();
|
||||
ds.subsong[0]->hilightA=reader.readC();
|
||||
ds.subsong[0]->hilightB=reader.readC();
|
||||
}
|
||||
|
||||
ds.timeBase=reader.readC();
|
||||
ds.speed1=reader.readC();
|
||||
ds.subsong[0]->timeBase=reader.readC();
|
||||
ds.subsong[0]->speed1=reader.readC();
|
||||
if (ds.version>0x07) {
|
||||
ds.speed2=reader.readC();
|
||||
ds.pal=reader.readC();
|
||||
ds.hz=(ds.pal)?60:50;
|
||||
ds.customTempo=reader.readC();
|
||||
ds.subsong[0]->speed2=reader.readC();
|
||||
ds.subsong[0]->pal=reader.readC();
|
||||
ds.subsong[0]->hz=(ds.subsong[0]->pal)?60:50;
|
||||
ds.subsong[0]->customTempo=reader.readC();
|
||||
} else {
|
||||
ds.speed2=ds.speed1;
|
||||
ds.subsong[0]->speed2=ds.subsong[0]->speed1;
|
||||
}
|
||||
if (ds.version>0x0a) {
|
||||
String hz=reader.readString(3);
|
||||
if (ds.customTempo) {
|
||||
if (ds.subsong[0]->customTempo) {
|
||||
try {
|
||||
ds.hz=std::stoi(hz);
|
||||
ds.subsong[0]->hz=std::stoi(hz);
|
||||
} catch (std::exception& e) {
|
||||
logW("invalid custom Hz!");
|
||||
ds.hz=60;
|
||||
ds.subsong[0]->hz=60;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ds.version>0x17) {
|
||||
ds.patLen=reader.readI();
|
||||
ds.subsong[0]->patLen=reader.readI();
|
||||
} else {
|
||||
ds.patLen=(unsigned char)reader.readC();
|
||||
ds.subsong[0]->patLen=(unsigned char)reader.readC();
|
||||
}
|
||||
ds.ordersLen=(unsigned char)reader.readC();
|
||||
ds.subsong[0]->ordersLen=(unsigned char)reader.readC();
|
||||
|
||||
if (ds.patLen<0) {
|
||||
if (ds.subsong[0]->patLen<0) {
|
||||
logE("pattern length is negative!");
|
||||
lastError="pattern lengrh is negative!";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
if (ds.patLen>256) {
|
||||
if (ds.subsong[0]->patLen>256) {
|
||||
logE("pattern length is too large!");
|
||||
lastError="pattern length is too large!";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
if (ds.ordersLen<0) {
|
||||
if (ds.subsong[0]->ordersLen<0) {
|
||||
logE("song length is negative!");
|
||||
lastError="song length is negative!";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
if (ds.ordersLen>127) {
|
||||
if (ds.subsong[0]->ordersLen>127) {
|
||||
logE("song is too long!");
|
||||
lastError="song is too long!";
|
||||
delete[] file;
|
||||
|
@ -242,54 +242,54 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
|||
}
|
||||
|
||||
if (ds.version<20 && ds.version>3) {
|
||||
ds.arpLen=reader.readC();
|
||||
ds.subsong[0]->arpLen=reader.readC();
|
||||
} else {
|
||||
ds.arpLen=1;
|
||||
ds.subsong[0]->arpLen=1;
|
||||
}
|
||||
|
||||
if (ds.system[0]==DIV_SYSTEM_YMU759) {
|
||||
switch (ds.timeBase) {
|
||||
switch (ds.subsong[0]->timeBase) {
|
||||
case 0:
|
||||
ds.hz=248;
|
||||
ds.subsong[0]->hz=248;
|
||||
break;
|
||||
case 1:
|
||||
ds.hz=200;
|
||||
ds.subsong[0]->hz=200;
|
||||
break;
|
||||
case 2:
|
||||
ds.hz=100;
|
||||
ds.subsong[0]->hz=100;
|
||||
break;
|
||||
case 3:
|
||||
ds.hz=50;
|
||||
ds.subsong[0]->hz=50;
|
||||
break;
|
||||
case 4:
|
||||
ds.hz=25;
|
||||
ds.subsong[0]->hz=25;
|
||||
break;
|
||||
case 5:
|
||||
ds.hz=20;
|
||||
ds.subsong[0]->hz=20;
|
||||
break;
|
||||
default:
|
||||
ds.hz=248;
|
||||
ds.subsong[0]->hz=248;
|
||||
break;
|
||||
}
|
||||
ds.customTempo=true;
|
||||
ds.timeBase=0;
|
||||
ds.subsong[0]->customTempo=true;
|
||||
ds.subsong[0]->timeBase=0;
|
||||
addWarning("Yamaha YMU759 emulation is incomplete! please migrate your song to the OPL3 system.");
|
||||
}
|
||||
|
||||
logV("%x",reader.tell());
|
||||
|
||||
logI("reading pattern matrix (%d * %d = %d)...",ds.ordersLen,getChannelCount(ds.system[0]),ds.ordersLen*getChannelCount(ds.system[0]));
|
||||
logI("reading pattern matrix (%d * %d = %d)...",ds.subsong[0]->ordersLen,getChannelCount(ds.system[0]),ds.subsong[0]->ordersLen*getChannelCount(ds.system[0]));
|
||||
for (int i=0; i<getChannelCount(ds.system[0]); i++) {
|
||||
for (int j=0; j<ds.ordersLen; j++) {
|
||||
ds.orders.ord[i][j]=reader.readC();
|
||||
if (ds.orders.ord[i][j]>0x7f) {
|
||||
logE("order at %d, %d out of range! (%d)",i,j,ds.orders.ord[i][j]);
|
||||
lastError=fmt::sprintf("order at %d, %d out of range! (%d)",i,j,ds.orders.ord[i][j]);
|
||||
for (int j=0; j<ds.subsong[0]->ordersLen; j++) {
|
||||
ds.subsong[0]->orders.ord[i][j]=reader.readC();
|
||||
if (ds.subsong[0]->orders.ord[i][j]>0x7f) {
|
||||
logE("order at %d, %d out of range! (%d)",i,j,ds.subsong[0]->orders.ord[i][j]);
|
||||
lastError=fmt::sprintf("order at %d, %d out of range! (%d)",i,j,ds.subsong[0]->orders.ord[i][j]);
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
if (ds.version>0x18) { // 1.1 pattern names
|
||||
ds.pat[i].getPattern(j,true)->name=reader.readString((unsigned char)reader.readC());
|
||||
ds.subsong[0]->pat[i].getPattern(j,true)->name=reader.readString((unsigned char)reader.readC());
|
||||
}
|
||||
}
|
||||
if (ds.version>0x03 && ds.version<0x06 && i<16) {
|
||||
|
@ -637,9 +637,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
|||
|
||||
logV("%x",reader.tell());
|
||||
|
||||
logI("reading patterns (%d channels, %d orders)...",getChannelCount(ds.system[0]),ds.ordersLen);
|
||||
logI("reading patterns (%d channels, %d orders)...",getChannelCount(ds.system[0]),ds.subsong[0]->ordersLen);
|
||||
for (int i=0; i<getChannelCount(ds.system[0]); i++) {
|
||||
DivChannelData& chan=ds.pat[i];
|
||||
DivChannelData& chan=ds.subsong[0]->pat[i];
|
||||
if (ds.version<0x0a) {
|
||||
chan.effectCols=1;
|
||||
} else {
|
||||
|
@ -652,10 +652,10 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
|||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
for (int j=0; j<ds.ordersLen; j++) {
|
||||
DivPattern* pat=chan.getPattern(ds.orders.ord[i][j],true);
|
||||
for (int j=0; j<ds.subsong[0]->ordersLen; j++) {
|
||||
DivPattern* pat=chan.getPattern(ds.subsong[0]->orders.ord[i][j],true);
|
||||
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
|
||||
pat->data[k][0]=reader.readS();
|
||||
// octave
|
||||
|
@ -723,7 +723,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
|||
}
|
||||
} else { // historic pattern format
|
||||
if (i<16) pat->data[0][2]=historicColIns[i];
|
||||
for (int k=0; k<ds.patLen; k++) {
|
||||
for (int k=0; k<ds.subsong[0]->patLen; k++) {
|
||||
// note
|
||||
pat->data[k][0]=reader.readC();
|
||||
// octave
|
||||
|
@ -905,6 +905,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
|
|||
saveLock.lock();
|
||||
song.unload();
|
||||
song=ds;
|
||||
changeSong(0);
|
||||
recalcChans();
|
||||
saveLock.unlock();
|
||||
BUSY_END;
|
||||
|
@ -929,13 +930,16 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
unsigned int insPtr[256];
|
||||
unsigned int wavePtr[256];
|
||||
unsigned int samplePtr[256];
|
||||
unsigned int subSongPtr[256];
|
||||
std::vector<int> patPtr;
|
||||
int numberOfSubSongs=0;
|
||||
char magic[5];
|
||||
memset(magic,0,5);
|
||||
SafeReader reader=SafeReader(file,len);
|
||||
warnings="";
|
||||
try {
|
||||
DivSong ds;
|
||||
DivSubSong* subSong=ds.subsong[0];
|
||||
|
||||
if (!reader.seek(16,SEEK_SET)) {
|
||||
logE("premature end of file!");
|
||||
|
@ -1045,44 +1049,44 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
}
|
||||
reader.readI();
|
||||
|
||||
ds.timeBase=reader.readC();
|
||||
ds.speed1=reader.readC();
|
||||
ds.speed2=reader.readC();
|
||||
ds.arpLen=reader.readC();
|
||||
ds.hz=reader.readF();
|
||||
ds.pal=(ds.hz>=53);
|
||||
ds.customTempo=true;
|
||||
subSong->timeBase=reader.readC();
|
||||
subSong->speed1=reader.readC();
|
||||
subSong->speed2=reader.readC();
|
||||
subSong->arpLen=reader.readC();
|
||||
subSong->hz=reader.readF();
|
||||
subSong->pal=(subSong->hz>=53);
|
||||
subSong->customTempo=true;
|
||||
|
||||
ds.patLen=reader.readS();
|
||||
ds.ordersLen=reader.readS();
|
||||
subSong->patLen=reader.readS();
|
||||
subSong->ordersLen=reader.readS();
|
||||
|
||||
ds.hilightA=reader.readC();
|
||||
ds.hilightB=reader.readC();
|
||||
subSong->hilightA=reader.readC();
|
||||
subSong->hilightB=reader.readC();
|
||||
|
||||
ds.insLen=reader.readS();
|
||||
ds.waveLen=reader.readS();
|
||||
ds.sampleLen=reader.readS();
|
||||
int numberOfPats=reader.readI();
|
||||
|
||||
if (ds.patLen<0) {
|
||||
if (subSong->patLen<0) {
|
||||
logE("pattern length is negative!");
|
||||
lastError="pattern lengrh is negative!";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
if (ds.patLen>256) {
|
||||
if (subSong->patLen>256) {
|
||||
logE("pattern length is too large!");
|
||||
lastError="pattern length is too large!";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
if (ds.ordersLen<0) {
|
||||
if (subSong->ordersLen<0) {
|
||||
logE("song length is negative!");
|
||||
lastError="song length is negative!";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
if (ds.ordersLen>256) {
|
||||
if (subSong->ordersLen>256) {
|
||||
logE("song is too long!");
|
||||
lastError="song is too long!";
|
||||
delete[] file;
|
||||
|
@ -1295,18 +1299,18 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
reader.read(samplePtr,ds.sampleLen*4);
|
||||
for (int i=0; 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 j=0; j<ds.ordersLen; j++) {
|
||||
ds.orders.ord[i][j]=reader.readC();
|
||||
for (int j=0; j<subSong->ordersLen; j++) {
|
||||
subSong->orders.ord[i][j]=reader.readC();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<tchans; i++) {
|
||||
ds.pat[i].effectCols=reader.readC();
|
||||
if (ds.pat[i].effectCols<1 || ds.pat[i].effectCols>8) {
|
||||
logE("channel %d has zero or too many effect columns! (%d)",i,ds.pat[i].effectCols);
|
||||
lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,ds.pat[i].effectCols);
|
||||
subSong->pat[i].effectCols=reader.readC();
|
||||
if (subSong->pat[i].effectCols<1 || subSong->pat[i].effectCols>8) {
|
||||
logE("channel %d has zero or too many effect columns! (%d)",i,subSong->pat[i].effectCols);
|
||||
lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,subSong->pat[i].effectCols);
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
@ -1314,25 +1318,25 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
|
||||
if (ds.version>=39) {
|
||||
for (int i=0; i<tchans; i++) {
|
||||
ds.chanShow[i]=reader.readC();
|
||||
subSong->chanShow[i]=reader.readC();
|
||||
}
|
||||
|
||||
for (int i=0; i<tchans; i++) {
|
||||
ds.chanCollapse[i]=reader.readC();
|
||||
subSong->chanCollapse[i]=reader.readC();
|
||||
}
|
||||
|
||||
if (ds.version<92) {
|
||||
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++) {
|
||||
ds.chanName[i]=reader.readString();
|
||||
subSong->chanName[i]=reader.readString();
|
||||
}
|
||||
|
||||
for (int i=0; i<tchans; i++) {
|
||||
ds.chanShortName[i]=reader.readString();
|
||||
subSong->chanShortName[i]=reader.readString();
|
||||
}
|
||||
|
||||
ds.notes=reader.readString();
|
||||
|
@ -1405,6 +1409,88 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
}
|
||||
}
|
||||
|
||||
// subsongs
|
||||
if (ds.version>=95) {
|
||||
subSong->name=reader.readString();
|
||||
subSong->notes=reader.readString();
|
||||
numberOfSubSongs=(unsigned char)reader.readC();
|
||||
reader.readC(); // reserved
|
||||
reader.readC();
|
||||
reader.readC();
|
||||
// pointers
|
||||
for (int i=0; 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
|
||||
for (int i=0; i<ds.insLen; i++) {
|
||||
DivInstrument* ins=new DivInstrument;
|
||||
|
@ -1568,9 +1654,15 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
|
||||
int chan=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) {
|
||||
logE("pattern channel out of range!",i);
|
||||
|
@ -1586,14 +1678,21 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
if (subs<0 || subs>=(int)ds.subsong.size()) {
|
||||
logE("pattern subsong out of range!",i);
|
||||
lastError="pattern subsong out of range!";
|
||||
ds.unload();
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
DivPattern* pat=ds.pat[chan].getPattern(index,true);
|
||||
for (int j=0; j<ds.patLen; j++) {
|
||||
DivPattern* pat=ds.subsong[subs]->pat[chan].getPattern(index,true);
|
||||
for (int j=0; j<ds.subsong[subs]->patLen; j++) {
|
||||
pat->data[j][0]=reader.readS();
|
||||
pat->data[j][1]=reader.readS();
|
||||
pat->data[j][2]=reader.readS();
|
||||
pat->data[j][3]=reader.readS();
|
||||
for (int k=0; 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][5+(k<<1)]=reader.readS();
|
||||
}
|
||||
|
@ -1615,6 +1714,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
|
|||
saveLock.lock();
|
||||
song.unload();
|
||||
song=ds;
|
||||
changeSong(0);
|
||||
recalcChans();
|
||||
saveLock.unlock();
|
||||
BUSY_END;
|
||||
|
@ -1740,8 +1840,8 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
ds.sampleLen=ds.sample.size();
|
||||
|
||||
// orders
|
||||
ds.ordersLen=ordCount=reader.readC();
|
||||
if (ds.ordersLen<1 || ds.ordersLen>127) {
|
||||
ds.subsong[0]->ordersLen=ordCount=reader.readC();
|
||||
if (ds.subsong[0]->ordersLen<1 || ds.subsong[0]->ordersLen>127) {
|
||||
logD("invalid order count!");
|
||||
throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
|
@ -1761,7 +1861,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
unsigned char pat=reader.readC();
|
||||
if (pat>patMax) patMax=pat;
|
||||
for (int j=0; j<chCount; j++) {
|
||||
ds.orders.ord[j][i]=pat;
|
||||
ds.subsong[0]->orders.ord[j][i]=pat;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1778,7 +1878,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
}
|
||||
|
||||
// patterns
|
||||
ds.patLen=64;
|
||||
ds.subsong[0]->patLen=64;
|
||||
for (int ch=0; ch<chCount; ch++) {
|
||||
for (int i=0; i<5; i++) {
|
||||
fxUsage[ch][i]=false;
|
||||
|
@ -1787,7 +1887,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
for (int pat=0; pat<=patMax; pat++) {
|
||||
DivPattern* chpats[DIV_MAX_CHANS];
|
||||
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 ch=0; ch<chCount; ch++) {
|
||||
|
@ -1860,7 +1960,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
for (int ch=0; ch<=chCount; ch++) {
|
||||
unsigned char fxCols=1;
|
||||
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 lastEffectState[5]={-1,-1,-1,-1,-1};
|
||||
short setEffectState[5]={-1,-1,-1,-1,-1};
|
||||
|
@ -1988,25 +2088,25 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
}
|
||||
}
|
||||
}
|
||||
ds.pat[ch].effectCols=fxCols;
|
||||
ds.subsong[0]->pat[ch].effectCols=fxCols;
|
||||
}
|
||||
|
||||
ds.pal=false;
|
||||
ds.hz=50;
|
||||
ds.customTempo=false;
|
||||
ds.subsong[0]->pal=false;
|
||||
ds.subsong[0]->hz=50;
|
||||
ds.subsong[0]->customTempo=false;
|
||||
ds.systemLen=(chCount+3)/4;
|
||||
for(int i=0; i<ds.systemLen; i++) {
|
||||
ds.system[i]=DIV_SYSTEM_AMIGA;
|
||||
ds.systemFlags[i]=1|(80<<8)|(bypassLimits?4:0)|((ds.systemLen>1 || bypassLimits)?2:0); // PAL
|
||||
}
|
||||
for(int i=0; i<chCount; i++) {
|
||||
ds.chanShow[i]=true;
|
||||
ds.chanName[i]=fmt::sprintf("Channel %d",i+1);
|
||||
ds.chanShortName[i]=fmt::sprintf("C%d",i+1);
|
||||
ds.subsong[0]->chanShow[i]=true;
|
||||
ds.subsong[0]->chanName[i]=fmt::sprintf("Channel %d",i+1);
|
||||
ds.subsong[0]->chanShortName[i]=fmt::sprintf("C%d",i+1);
|
||||
}
|
||||
for(int i=chCount; i<ds.systemLen*4; i++) {
|
||||
ds.pat[i].effectCols=1;
|
||||
ds.chanShow[i]=false;
|
||||
ds.subsong[0]->pat[i].effectCols=1;
|
||||
ds.subsong[0]->chanShow[i]=false;
|
||||
}
|
||||
|
||||
// instrument creation
|
||||
|
@ -2024,6 +2124,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
saveLock.lock();
|
||||
song.unload();
|
||||
song=ds;
|
||||
changeSong(0);
|
||||
recalcChans();
|
||||
saveLock.unlock();
|
||||
BUSY_END;
|
||||
|
@ -2118,8 +2219,8 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
newVibrato=reader.readI();
|
||||
}
|
||||
if (blockVersion>=4) {
|
||||
ds.hilightA=reader.readI();
|
||||
ds.hilightB=reader.readI();
|
||||
ds.subsong[0]->hilightA=reader.readI();
|
||||
ds.subsong[0]->hilightB=reader.readI();
|
||||
}
|
||||
if (expansions&8) if (blockVersion>=5) { // N163 channels
|
||||
n163Chans=reader.readI();
|
||||
|
@ -2135,12 +2236,12 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
logV("custom Hz: %d",customHz);
|
||||
logV("new vibrato: %d",newVibrato);
|
||||
logV("N163 channels: %d",n163Chans);
|
||||
logV("highlight 1: %d",ds.hilightA);
|
||||
logV("highlight 2: %d",ds.hilightB);
|
||||
logV("highlight 1: %d",ds.subsong[0]->hilightA);
|
||||
logV("highlight 2: %d",ds.subsong[0]->hilightB);
|
||||
logV("split point: %d",speedSplitPoint);
|
||||
|
||||
if (customHz!=0) {
|
||||
ds.hz=customHz;
|
||||
ds.subsong[0]->hz=customHz;
|
||||
}
|
||||
|
||||
// initialize channels
|
||||
|
@ -2201,7 +2302,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
for (int j=0; j<=totalSongs; j++) {
|
||||
unsigned char effectCols=reader.readC();
|
||||
if (j==0) {
|
||||
ds.pat[i].effectCols=effectCols+1;
|
||||
ds.subsong[0]->pat[i].effectCols=effectCols+1;
|
||||
}
|
||||
logV("- song %d has %d effect columns",j,effectCols);
|
||||
}
|
||||
|
@ -2532,26 +2633,39 @@ bool DivEngine::load(unsigned char* f, size_t slen) {
|
|||
return false;
|
||||
}
|
||||
|
||||
struct PatToWrite {
|
||||
unsigned short subsong, chan, pat;
|
||||
PatToWrite(unsigned short s, unsigned short c, unsigned short p):
|
||||
subsong(s),
|
||||
chan(c),
|
||||
pat(p) {}
|
||||
};
|
||||
|
||||
SafeWriter* DivEngine::saveFur(bool notPrimary) {
|
||||
saveLock.lock();
|
||||
std::vector<int> subSongPtr;
|
||||
std::vector<int> insPtr;
|
||||
std::vector<int> wavePtr;
|
||||
std::vector<int> samplePtr;
|
||||
std::vector<int> patPtr;
|
||||
size_t ptrSeek;
|
||||
size_t ptrSeek, subSongPtrSeek;
|
||||
size_t subSongIndex=0;
|
||||
DivSubSong* subSong=song.subsong[subSongIndex];
|
||||
warnings="";
|
||||
|
||||
// fail if values are out of range
|
||||
if (song.ordersLen>256) {
|
||||
/*
|
||||
if (subSong->ordersLen>256) {
|
||||
logE("maximum song length is 256!");
|
||||
lastError="maximum song length is 256";
|
||||
return NULL;
|
||||
}
|
||||
if (song.patLen>256) {
|
||||
if (subSong->patLen>256) {
|
||||
logE("maximum pattern length is 256!");
|
||||
lastError="maximum pattern length is 256";
|
||||
return NULL;
|
||||
}
|
||||
*/
|
||||
if (song.ins.size()>256) {
|
||||
logE("maximum number of instruments is 256!");
|
||||
lastError="maximum number of instruments is 256";
|
||||
|
@ -2594,14 +2708,17 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
|
|||
|
||||
// high short is channel
|
||||
// low short is pattern number
|
||||
std::vector<int> patsToWrite;
|
||||
std::vector<PatToWrite> patsToWrite;
|
||||
bool alreadyAdded[256];
|
||||
for (int i=0; i<chans; i++) {
|
||||
for (size_t j=0; j<song.subsong.size(); j++) {
|
||||
DivSubSong* subs=song.subsong[j];
|
||||
memset(alreadyAdded,0,256*sizeof(bool));
|
||||
for (int j=0; j<song.ordersLen; j++) {
|
||||
if (alreadyAdded[song.orders.ord[i][j]]) continue;
|
||||
patsToWrite.push_back((i<<16)|song.orders.ord[i][j]);
|
||||
alreadyAdded[song.orders.ord[i][j]]=true;
|
||||
for (int k=0; k<subs->ordersLen; k++) {
|
||||
if (alreadyAdded[subs->orders.ord[i][k]]) continue;
|
||||
patsToWrite.push_back(PatToWrite(j,i,subs->orders.ord[i][k]));
|
||||
alreadyAdded[subs->orders.ord[i][k]]=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2609,15 +2726,15 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
|
|||
w->write("INFO",4);
|
||||
w->writeI(0);
|
||||
|
||||
w->writeC(song.timeBase);
|
||||
w->writeC(song.speed1);
|
||||
w->writeC(song.speed2);
|
||||
w->writeC(song.arpLen);
|
||||
w->writeF(song.hz);
|
||||
w->writeS(song.patLen);
|
||||
w->writeS(song.ordersLen);
|
||||
w->writeC(song.hilightA);
|
||||
w->writeC(song.hilightB);
|
||||
w->writeC(subSong->timeBase);
|
||||
w->writeC(subSong->speed1);
|
||||
w->writeC(subSong->speed2);
|
||||
w->writeC(subSong->arpLen);
|
||||
w->writeF(subSong->hz);
|
||||
w->writeS(subSong->patLen);
|
||||
w->writeS(subSong->ordersLen);
|
||||
w->writeC(subSong->hilightA);
|
||||
w->writeC(subSong->hilightB);
|
||||
w->writeS(song.insLen);
|
||||
w->writeS(song.waveLen);
|
||||
w->writeS(song.sampleLen);
|
||||
|
@ -2694,29 +2811,29 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
|
|||
}
|
||||
|
||||
for (int i=0; i<chans; i++) {
|
||||
for (int j=0; j<song.ordersLen; j++) {
|
||||
w->writeC(song.orders.ord[i][j]);
|
||||
for (int j=0; j<subSong->ordersLen; j++) {
|
||||
w->writeC(subSong->orders.ord[i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
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++) {
|
||||
w->writeC(song.chanShow[i]);
|
||||
w->writeC(subSong->chanShow[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++) {
|
||||
w->writeString(song.chanName[i],false);
|
||||
w->writeString(subSong->chanName[i],false);
|
||||
}
|
||||
|
||||
for (int i=0; i<chans; i++) {
|
||||
w->writeString(song.chanShortName[i],false);
|
||||
w->writeString(subSong->chanShortName[i],false);
|
||||
}
|
||||
|
||||
w->writeString(song.notes,false);
|
||||
|
@ -2742,6 +2859,67 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
|
|||
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
|
||||
for (int i=0; i<song.insLen; i++) {
|
||||
DivInstrument* ins=song.ins[i];
|
||||
|
@ -2776,23 +2954,24 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
|
|||
}
|
||||
|
||||
/// PATTERN
|
||||
for (int i: patsToWrite) {
|
||||
DivPattern* pat=song.pat[i>>16].getPattern(i&0xffff,false);
|
||||
for (PatToWrite& i: patsToWrite) {
|
||||
DivPattern* pat=song.subsong[i.subsong]->pat[i.chan].getPattern(i.pat,false);
|
||||
patPtr.push_back(w->tell());
|
||||
w->write("PATR",4);
|
||||
w->writeI(0);
|
||||
|
||||
w->writeS(i>>16);
|
||||
w->writeS(i&0xffff);
|
||||
w->writeS(i.chan);
|
||||
w->writeS(i.pat);
|
||||
w->writeS(i.subsong);
|
||||
|
||||
w->writeI(0); // reserved
|
||||
w->writeS(0); // reserved
|
||||
|
||||
for (int j=0; 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][1]); // octave
|
||||
w->writeS(pat->data[j][2]); // instrument
|
||||
w->writeS(pat->data[j][3]); // volume
|
||||
w->write(&pat->data[j][4],2*song.pat[i>>16].effectCols*2); // effects
|
||||
w->write(&pat->data[j][4],2*song.subsong[i.subsong]->pat[i.chan].effectCols*2); // effects
|
||||
}
|
||||
|
||||
w->writeString(pat->name,false);
|
||||
|
@ -2805,21 +2984,29 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
|
|||
w->writeI(insPtr[i]);
|
||||
}
|
||||
|
||||
// wavetable pointers (we'll seek here later)
|
||||
// wavetable pointers
|
||||
for (int i=0; i<song.waveLen; i++) {
|
||||
w->writeI(wavePtr[i]);
|
||||
}
|
||||
|
||||
// sample pointers (we'll seek here later)
|
||||
// sample pointers
|
||||
for (int i=0; i<song.sampleLen; i++) {
|
||||
w->writeI(samplePtr[i]);
|
||||
}
|
||||
|
||||
// pattern pointers (we'll seek here later)
|
||||
// pattern pointers
|
||||
for (int i: patPtr) {
|
||||
w->writeI(i);
|
||||
}
|
||||
|
||||
/// SUBSONG POINTERS
|
||||
w->seek(subSongPtrSeek,SEEK_SET);
|
||||
|
||||
// subsong pointers
|
||||
for (size_t i=0; i<(song.subsong.size()-1); i++) {
|
||||
w->writeI(subSongPtr[i]);
|
||||
}
|
||||
|
||||
saveLock.unlock();
|
||||
return w;
|
||||
}
|
||||
|
@ -2890,7 +3077,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
|
|||
return NULL;
|
||||
}
|
||||
// fail if values are out of range
|
||||
if (song.ordersLen>127) {
|
||||
if (curSubSong->ordersLen>127) {
|
||||
logE("maximum .dmf song length is 127!");
|
||||
lastError="maximum .dmf song length is 127";
|
||||
return NULL;
|
||||
|
@ -2906,10 +3093,10 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
|
|||
return NULL;
|
||||
}
|
||||
for (int i=0; i<chans; i++) {
|
||||
for (int j=0; j<song.ordersLen; j++) {
|
||||
if (song.orders.ord[i][j]>0x7f) {
|
||||
logE("order %d, %d is out of range (0-127)!",song.orders.ord[i][j]);
|
||||
lastError=fmt::sprintf("order %d, %d is out of range (0-127)",song.orders.ord[i][j]);
|
||||
for (int j=0; j<curSubSong->ordersLen; j++) {
|
||||
if (curOrders->ord[i][j]>0x7f) {
|
||||
logE("order %d, %d is out of range (0-127)!",curOrders->ord[i][j]);
|
||||
lastError=fmt::sprintf("order %d, %d is out of range (0-127)",curOrders->ord[i][j]);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
@ -2952,31 +3139,35 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
|
|||
// song info
|
||||
w->writeString(song.name,true);
|
||||
w->writeString(song.author,true);
|
||||
w->writeC(song.hilightA);
|
||||
w->writeC(song.hilightB);
|
||||
w->writeC(curSubSong->hilightA);
|
||||
w->writeC(curSubSong->hilightB);
|
||||
|
||||
w->writeC(song.timeBase);
|
||||
w->writeC(song.speed1);
|
||||
w->writeC(song.speed2);
|
||||
w->writeC(song.pal);
|
||||
w->writeC(song.customTempo);
|
||||
w->writeC(curSubSong->timeBase);
|
||||
w->writeC(curSubSong->speed1);
|
||||
w->writeC(curSubSong->speed2);
|
||||
w->writeC(curSubSong->pal);
|
||||
w->writeC(curSubSong->customTempo);
|
||||
char customHz[4];
|
||||
memset(customHz,0,4);
|
||||
snprintf(customHz,4,"%d",(int)song.hz);
|
||||
snprintf(customHz,4,"%d",(int)curSubSong->hz);
|
||||
w->write(customHz,3);
|
||||
w->writeI(song.patLen);
|
||||
w->writeC(song.ordersLen);
|
||||
w->writeI(curSubSong->patLen);
|
||||
w->writeC(curSubSong->ordersLen);
|
||||
|
||||
for (int i=0; i<chans; i++) {
|
||||
for (int j=0; j<song.ordersLen; j++) {
|
||||
w->writeC(song.orders.ord[i][j]);
|
||||
for (int j=0; j<curSubSong->ordersLen; j++) {
|
||||
w->writeC(curOrders->ord[i][j]);
|
||||
if (version>=25) {
|
||||
DivPattern* pat=song.pat[i].getPattern(j,false);
|
||||
DivPattern* pat=curPat[i].getPattern(j,false);
|
||||
w->writeString(pat->name,true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (song.subsong.size()>1) {
|
||||
addWarning("only the currently selected subsong will be saved");
|
||||
}
|
||||
|
||||
if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) {
|
||||
addWarning("absolute duty/cutoff macro not available in .dmf!");
|
||||
addWarning("duty precision will be lost");
|
||||
|
@ -3138,15 +3329,15 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
|
|||
}
|
||||
|
||||
for (int i=0; i<getChannelCount(sys); i++) {
|
||||
w->writeC(song.pat[i].effectCols);
|
||||
w->writeC(curPat[i].effectCols);
|
||||
|
||||
for (int j=0; j<song.ordersLen; j++) {
|
||||
DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][j],false);
|
||||
for (int k=0; k<song.patLen; k++) {
|
||||
for (int j=0; j<curSubSong->ordersLen; j++) {
|
||||
DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][j],false);
|
||||
for (int k=0; k<curSubSong->patLen; k++) {
|
||||
w->writeS(pat->data[k][0]); // note
|
||||
w->writeS(pat->data[k][1]); // octave
|
||||
w->writeS(pat->data[k][3]); // volume
|
||||
w->write(&pat->data[k][4],2*song.pat[i].effectCols*2); // effects
|
||||
w->write(&pat->data[k][4],2*curPat[i].effectCols*2); // effects
|
||||
w->writeS(pat->data[k][2]); // instrument
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ void DivChannelData::wipePatterns() {
|
|||
}
|
||||
}
|
||||
|
||||
void DivPattern::copyOn(DivPattern *dest) {
|
||||
void DivPattern::copyOn(DivPattern* dest) {
|
||||
dest->name=name;
|
||||
memcpy(dest->data,data,sizeof(data));
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0;
|
|||
void DivEngine::nextOrder() {
|
||||
curRow=0;
|
||||
if (repeatPattern) return;
|
||||
if (++curOrder>=song.ordersLen) {
|
||||
if (++curOrder>=curSubSong->ordersLen) {
|
||||
endOfSong=true;
|
||||
curOrder=0;
|
||||
}
|
||||
|
@ -266,11 +266,11 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char
|
|||
void DivEngine::processRow(int i, bool afterDelay) {
|
||||
int whatOrder=afterDelay?chan[i].delayOrder:curOrder;
|
||||
int whatRow=afterDelay?chan[i].delayRow:curRow;
|
||||
DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][whatOrder],false);
|
||||
DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][whatOrder],false);
|
||||
// pre effects
|
||||
if (!afterDelay) {
|
||||
bool returnAfterPre=false;
|
||||
for (int j=0; j<song.pat[i].effectCols; j++) {
|
||||
for (int j=0; j<curPat[i].effectCols; j++) {
|
||||
short effect=pat->data[whatRow][4+(j<<1)];
|
||||
short effectVal=pat->data[whatRow][5+(j<<1)];
|
||||
|
||||
|
@ -290,7 +290,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
}
|
||||
break;
|
||||
case 0x0d: // next order
|
||||
if (changeOrd<0 && (curOrder<(song.ordersLen-1) || !song.ignoreJumpAtEnd)) {
|
||||
if (changeOrd<0 && (curOrder<(curSubSong->ordersLen-1) || !song.ignoreJumpAtEnd)) {
|
||||
changeOrd=-2;
|
||||
changePos=effectVal;
|
||||
}
|
||||
|
@ -405,7 +405,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
bool panChanged=false;
|
||||
|
||||
// effects
|
||||
for (int j=0; j<song.pat[i].effectCols; j++) {
|
||||
for (int j=0; j<curPat[i].effectCols; j++) {
|
||||
short effect=pat->data[whatRow][4+(j<<1)];
|
||||
short effectVal=pat->data[whatRow][5+(j<<1)];
|
||||
|
||||
|
@ -548,7 +548,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
break;
|
||||
case 0xe0: // arp speed
|
||||
if (effectVal>0) {
|
||||
song.arpLen=effectVal;
|
||||
curSubSong->arpLen=effectVal;
|
||||
}
|
||||
break;
|
||||
case 0xe1: // portamento up
|
||||
|
@ -727,7 +727,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
chan[i].noteOnInhibit=false;
|
||||
|
||||
// post effects
|
||||
for (int j=0; j<song.pat[i].effectCols; j++) {
|
||||
for (int j=0; j<curPat[i].effectCols; j++) {
|
||||
short effect=pat->data[whatRow][4+(j<<1)];
|
||||
short effectVal=pat->data[whatRow][5+(j<<1)];
|
||||
|
||||
|
@ -745,10 +745,10 @@ void DivEngine::nextRow() {
|
|||
strcpy(pb1,"");
|
||||
strcpy(pb3,"");
|
||||
for (int i=0; i<chans; i++) {
|
||||
snprintf(pb,4095," %.2x",song.orders.ord[i][curOrder]);
|
||||
snprintf(pb,4095," %.2x",curOrders->ord[i][curOrder]);
|
||||
strcat(pb1,pb);
|
||||
|
||||
DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][curOrder],false);
|
||||
DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][curOrder],false);
|
||||
snprintf(pb2,4095,"\x1b[37m %s",
|
||||
formatNote(pat->data[curRow][0],pat->data[curRow][1]));
|
||||
strcat(pb3,pb2);
|
||||
|
@ -764,7 +764,7 @@ void DivEngine::nextRow() {
|
|||
snprintf(pb2,4095,"\x1b[0;36m%.2x",pat->data[curRow][2]);
|
||||
strcat(pb3,pb2);
|
||||
}
|
||||
for (int j=0; j<song.pat[i].effectCols; j++) {
|
||||
for (int j=0; j<curPat[i].effectCols; j++) {
|
||||
if (pat->data[curRow][4+(j<<1)]==-1) {
|
||||
strcat(pb3,"\x1b[m--");
|
||||
} else {
|
||||
|
@ -796,32 +796,32 @@ void DivEngine::nextRow() {
|
|||
if (changeOrd==-2) changeOrd=curOrder+1;
|
||||
if (changeOrd<=curOrder) endOfSong=true;
|
||||
curOrder=changeOrd;
|
||||
if (curOrder>=song.ordersLen) {
|
||||
if (curOrder>=curSubSong->ordersLen) {
|
||||
curOrder=0;
|
||||
endOfSong=true;
|
||||
}
|
||||
changeOrd=-1;
|
||||
}
|
||||
if (haltOn==DIV_HALT_PATTERN) halted=true;
|
||||
} else if (playing) if (++curRow>=song.patLen) {
|
||||
} else if (playing) if (++curRow>=curSubSong->patLen) {
|
||||
nextOrder();
|
||||
if (haltOn==DIV_HALT_PATTERN) halted=true;
|
||||
}
|
||||
|
||||
if (song.brokenSpeedSel) {
|
||||
if ((song.patLen&1) && curOrder&1) {
|
||||
ticks=((curRow&1)?speed2:speed1)*(song.timeBase+1);
|
||||
if ((curSubSong->patLen&1) && curOrder&1) {
|
||||
ticks=((curRow&1)?speed2:speed1)*(curSubSong->timeBase+1);
|
||||
nextSpeed=(curRow&1)?speed1:speed2;
|
||||
} else {
|
||||
ticks=((curRow&1)?speed1:speed2)*(song.timeBase+1);
|
||||
ticks=((curRow&1)?speed1:speed2)*(curSubSong->timeBase+1);
|
||||
nextSpeed=(curRow&1)?speed2:speed1;
|
||||
}
|
||||
} else {
|
||||
if (speedAB) {
|
||||
ticks=speed2*(song.timeBase+1);
|
||||
ticks=speed2*(curSubSong->timeBase+1);
|
||||
nextSpeed=speed1;
|
||||
} else {
|
||||
ticks=speed1*(song.timeBase+1);
|
||||
ticks=speed1*(curSubSong->timeBase+1);
|
||||
nextSpeed=speed2;
|
||||
}
|
||||
speedAB=!speedAB;
|
||||
|
@ -829,7 +829,7 @@ void DivEngine::nextRow() {
|
|||
|
||||
// post row details
|
||||
for (int i=0; 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]!=100 && pat->data[curRow][0]!=101 && pat->data[curRow][0]!=102) {
|
||||
if (!chan[i].legato) {
|
||||
|
@ -838,7 +838,7 @@ void DivEngine::nextRow() {
|
|||
if (song.oneTickCut) {
|
||||
bool doPrepareCut=true;
|
||||
|
||||
for (int j=0; j<song.pat[i].effectCols; j++) {
|
||||
for (int j=0; j<curPat[i].effectCols; j++) {
|
||||
if (pat->data[curRow][4+(j<<1)]==0x03) {
|
||||
doPrepareCut=false;
|
||||
break;
|
||||
|
@ -1007,7 +1007,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
}
|
||||
if (chan[i].arp!=0 && !chan[i].arpYield && chan[i].portaSpeed<1) {
|
||||
if (--chan[i].arpTicks<1) {
|
||||
chan[i].arpTicks=song.arpLen;
|
||||
chan[i].arpTicks=curSubSong->arpLen;
|
||||
chan[i].arpStage++;
|
||||
if (chan[i].arpStage>2) chan[i].arpStage=0;
|
||||
switch (chan[i].arpStage) {
|
||||
|
@ -1048,7 +1048,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
}
|
||||
}
|
||||
|
||||
if (consoleMode && subticks<=1) fprintf(stderr,"\x1b[2K> %d:%.2d:%.2d.%.2d %.2x/%.2x:%.3d/%.3d %4dcmd/s\x1b[G",totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000,curOrder,song.ordersLen,curRow,song.patLen,cmdsPerSecond);
|
||||
if (consoleMode && subticks<=1) fprintf(stderr,"\x1b[2K> %d:%.2d:%.2d.%.2d %.2x/%.2x:%.3d/%.3d %4dcmd/s\x1b[G",totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000,curOrder,curSubSong->ordersLen,curRow,curSubSong->patLen,cmdsPerSecond);
|
||||
}
|
||||
|
||||
if (haltOn==DIV_HALT_TICK) halted=true;
|
||||
|
@ -1240,11 +1240,11 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
|
|||
if (!freelance && stepPlay!=-1 && subticks==1) {
|
||||
unsigned int realPos=size-(runLeftG>>MASTER_CLOCK_PREC);
|
||||
if (realPos>=size) realPos=size-1;
|
||||
if (song.hilightA>0) {
|
||||
if ((curRow%song.hilightA)==0 && ticks==1) metroTick[realPos]=1;
|
||||
if (curSubSong->hilightA>0) {
|
||||
if ((curRow%curSubSong->hilightA)==0 && ticks==1) metroTick[realPos]=1;
|
||||
}
|
||||
if (song.hilightB>0) {
|
||||
if ((curRow%song.hilightB)==0 && ticks==1) metroTick[realPos]=2;
|
||||
if (curSubSong->hilightB>0) {
|
||||
if ((curRow%curSubSong->hilightB)==0 && ticks==1) metroTick[realPos]=2;
|
||||
}
|
||||
}
|
||||
if (nextTick()) {
|
||||
|
|
|
@ -19,9 +19,7 @@
|
|||
|
||||
#include "song.h"
|
||||
|
||||
DivOrders emptyOrders;
|
||||
|
||||
void DivSong::clearSongData() {
|
||||
void DivSubSong::clearData() {
|
||||
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
||||
pat[i].wipePatterns();
|
||||
}
|
||||
|
@ -30,6 +28,15 @@ void DivSong::clearSongData() {
|
|||
ordersLen=1;
|
||||
}
|
||||
|
||||
void DivSong::clearSongData() {
|
||||
for (DivSubSong* i: subsong) {
|
||||
i->clearData();
|
||||
delete i;
|
||||
}
|
||||
subsong.clear();
|
||||
subsong.push_back(new DivSubSong);
|
||||
}
|
||||
|
||||
void DivSong::clearInstruments() {
|
||||
for (DivInstrument* i: ins) {
|
||||
delete i;
|
||||
|
@ -73,7 +80,9 @@ void DivSong::unload() {
|
|||
sample.clear();
|
||||
sampleLen=0;
|
||||
|
||||
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
||||
pat[i].wipePatterns();
|
||||
for (DivSubSong* i: subsong) {
|
||||
i->clearData();
|
||||
delete i;
|
||||
}
|
||||
subsong.clear();
|
||||
}
|
||||
|
|
|
@ -110,8 +110,6 @@ enum DivSystem {
|
|||
DIV_SYSTEM_DUMMY
|
||||
};
|
||||
|
||||
extern DivOrders emptyOrders;
|
||||
|
||||
struct DivSubSong {
|
||||
String name, notes;
|
||||
unsigned char hilightA, hilightB;
|
||||
|
@ -126,8 +124,23 @@ struct DivSubSong {
|
|||
|
||||
bool chanShow[DIV_MAX_CHANS];
|
||||
unsigned char chanCollapse[DIV_MAX_CHANS];
|
||||
String chanName[DIV_MAX_CHANS];
|
||||
String chanShortName[DIV_MAX_CHANS];
|
||||
|
||||
DivSubSong() {
|
||||
void clearData();
|
||||
|
||||
DivSubSong():
|
||||
hilightA(4),
|
||||
hilightB(16),
|
||||
timeBase(0),
|
||||
speed1(6),
|
||||
speed2(6),
|
||||
arpLen(1),
|
||||
pal(true),
|
||||
customTempo(false),
|
||||
hz(60.0),
|
||||
patLen(64),
|
||||
ordersLen(1) {
|
||||
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
||||
chanShow[i]=true;
|
||||
chanCollapse[i]=0;
|
||||
|
@ -317,19 +330,10 @@ struct DivSong {
|
|||
String nameJ, authorJ, categoryJ;
|
||||
|
||||
// other things
|
||||
String chanName[DIV_MAX_CHANS];
|
||||
String chanShortName[DIV_MAX_CHANS];
|
||||
String notes;
|
||||
|
||||
// highlight
|
||||
unsigned char hilightA, hilightB;
|
||||
|
||||
// module details
|
||||
unsigned char timeBase, speed1, speed2, arpLen;
|
||||
bool pal;
|
||||
bool customTempo;
|
||||
float hz;
|
||||
int patLen, ordersLen, insLen, waveLen, sampleLen;
|
||||
int insLen, waveLen, sampleLen;
|
||||
float masterVol;
|
||||
float tuning;
|
||||
|
||||
|
@ -377,16 +381,11 @@ struct DivSong {
|
|||
bool snDutyReset;
|
||||
bool pitchMacroIsLinear;
|
||||
|
||||
DivOrders* orders;
|
||||
std::vector<DivInstrument*> ins;
|
||||
DivChannelData* pat;
|
||||
std::vector<DivWavetable*> wave;
|
||||
std::vector<DivSample*> sample;
|
||||
|
||||
std::vector<DivSubSong> subsongs;
|
||||
|
||||
bool* chanShow;
|
||||
unsigned char* chanCollapse;
|
||||
std::vector<DivSubSong*> subsong;
|
||||
|
||||
DivInstrument nullIns, nullInsOPLL, nullInsOPL, nullInsQSound;
|
||||
DivWavetable nullWave;
|
||||
|
@ -435,17 +434,6 @@ struct DivSong {
|
|||
manInfo(""),
|
||||
createdDate(""),
|
||||
revisionDate(""),
|
||||
hilightA(4),
|
||||
hilightB(16),
|
||||
timeBase(0),
|
||||
speed1(6),
|
||||
speed2(6),
|
||||
arpLen(1),
|
||||
pal(true),
|
||||
customTempo(false),
|
||||
hz(60.0),
|
||||
patLen(64),
|
||||
ordersLen(1),
|
||||
insLen(0),
|
||||
waveLen(0),
|
||||
sampleLen(0),
|
||||
|
@ -491,11 +479,7 @@ struct DivSong {
|
|||
systemPan[i]=0;
|
||||
systemFlags[i]=0;
|
||||
}
|
||||
subsongs.push_back(DivSubSong());
|
||||
chanShow=subsongs[0].chanShow;
|
||||
chanCollapse=subsongs[0].chanCollapse;
|
||||
orders=&subsongs[0].orders;
|
||||
pat=subsongs[0].pat;
|
||||
subsong.push_back(new DivSubSong);
|
||||
system[0]=DIV_SYSTEM_YM2612;
|
||||
system[1]=DIV_SYSTEM_SMS;
|
||||
|
||||
|
|
|
@ -283,7 +283,7 @@ bool DivEngine::isSTDSystem(DivSystem sys) {
|
|||
|
||||
const char* DivEngine::getChannelName(int chan) {
|
||||
if (chan<0 || chan>chans) return "??";
|
||||
if (!song.chanName[chan].empty()) return song.chanName[chan].c_str();
|
||||
if (!curSubSong->chanName[chan].empty()) return curSubSong->chanName[chan].c_str();
|
||||
if (sysDefs[sysOfChan[chan]]==NULL) return "??";
|
||||
|
||||
const char* ret=sysDefs[sysOfChan[chan]]->chanNames[dispatchChanOfChan[chan]];
|
||||
|
@ -293,7 +293,7 @@ const char* DivEngine::getChannelName(int chan) {
|
|||
|
||||
const char* DivEngine::getChannelShortName(int chan) {
|
||||
if (chan<0 || chan>chans) return "??";
|
||||
if (!song.chanShortName[chan].empty()) return song.chanShortName[chan].c_str();
|
||||
if (!curSubSong->chanShortName[chan].empty()) return curSubSong->chanShortName[chan].c_str();
|
||||
if (sysDefs[sysOfChan[chan]]==NULL) return "??";
|
||||
|
||||
const char* ret=sysDefs[sysOfChan[chan]]->chanShortNames[dispatchChanOfChan[chan]];
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
#define DETERMINE_FIRST \
|
||||
int firstChannel=0; \
|
||||
for (int i=0; i<e->getTotalChannelCount(); i++) { \
|
||||
if (e->song.chanShow[i]) { \
|
||||
if (e->curSubSong->chanShow[i]) { \
|
||||
firstChannel=i; \
|
||||
break; \
|
||||
} \
|
||||
|
@ -29,7 +29,7 @@
|
|||
#define DETERMINE_LAST \
|
||||
int lastChannel=0; \
|
||||
for (int i=e->getTotalChannelCount()-1; i>=0; i--) { \
|
||||
if (e->song.chanShow[i]) { \
|
||||
if (e->curSubSong->chanShow[i]) { \
|
||||
lastChannel=i+1; \
|
||||
break; \
|
||||
} \
|
||||
|
|
|
@ -38,7 +38,7 @@ void FurnaceGUI::drawChannels() {
|
|||
ImGui::PushID(i);
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Checkbox("##Visible",&e->song.chanShow[i]);
|
||||
ImGui::Checkbox("##Visible",&e->curSubSong->chanShow[i]);
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginDisabled(i==0);
|
||||
if (ImGui::Button(ICON_FA_CHEVRON_UP)) {
|
||||
|
@ -53,10 +53,10 @@ void FurnaceGUI::drawChannels() {
|
|||
ImGui::EndDisabled();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
ImGui::InputTextWithHint("##ChanName",e->getChannelName(i),&e->song.chanName[i]);
|
||||
ImGui::InputTextWithHint("##ChanName",e->getChannelName(i),&e->curSubSong->chanName[i]);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
ImGui::InputTextWithHint("##ChanShortName",e->getChannelShortName(i),&e->song.chanShortName[i]);
|
||||
ImGui::InputTextWithHint("##ChanShortName",e->getChannelShortName(i),&e->curSubSong->chanShortName[i]);
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndTable();
|
||||
|
|
|
@ -32,7 +32,7 @@ void FurnaceGUI::startSelection(int xCoarse, int xFine, int y, bool fullRow) {
|
|||
selStart.xCoarse=firstChannel;
|
||||
selStart.xFine=0;
|
||||
selEnd.xCoarse=lastChannel-1;
|
||||
selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2;
|
||||
selEnd.xFine=2+e->curPat[selEnd.xCoarse].effectCols*2;
|
||||
selStart.y=y;
|
||||
selEnd.y=y;
|
||||
} else {
|
||||
|
@ -56,7 +56,7 @@ void FurnaceGUI::updateSelection(int xCoarse, int xFine, int y, bool fullRow) {
|
|||
if (selectingFull) {
|
||||
DETERMINE_LAST;
|
||||
selEnd.xCoarse=lastChannel-1;
|
||||
selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2;
|
||||
selEnd.xFine=2+e->curPat[selEnd.xCoarse].effectCols*2;
|
||||
selEnd.y=y;
|
||||
} else {
|
||||
selEnd.xCoarse=xCoarse;
|
||||
|
@ -94,21 +94,21 @@ void FurnaceGUI::finishSelection() {
|
|||
if (selStart.xCoarse<0) selStart.xCoarse=0;
|
||||
if (selStart.xCoarse>=chanCount) selStart.xCoarse=chanCount-1;
|
||||
if (selStart.y<0) selStart.y=0;
|
||||
if (selStart.y>=e->song.patLen) selStart.y=e->song.patLen-1;
|
||||
if (selStart.y>=e->curSubSong->patLen) selStart.y=e->curSubSong->patLen-1;
|
||||
if (selEnd.xCoarse<0) selEnd.xCoarse=0;
|
||||
if (selEnd.xCoarse>=chanCount) selEnd.xCoarse=chanCount-1;
|
||||
if (selEnd.y<0) selEnd.y=0;
|
||||
if (selEnd.y>=e->song.patLen) selEnd.y=e->song.patLen-1;
|
||||
if (selEnd.y>=e->curSubSong->patLen) selEnd.y=e->curSubSong->patLen-1;
|
||||
if (cursor.xCoarse<0) cursor.xCoarse=0;
|
||||
if (cursor.xCoarse>=chanCount) cursor.xCoarse=chanCount-1;
|
||||
if (cursor.y<0) cursor.y=0;
|
||||
if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1;
|
||||
if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1;
|
||||
|
||||
if (e->song.chanCollapse[selStart.xCoarse]==3) {
|
||||
if (e->curSubSong->chanCollapse[selStart.xCoarse]==3) {
|
||||
selStart.xFine=0;
|
||||
}
|
||||
if (e->song.chanCollapse[selEnd.xCoarse] && selEnd.xFine>=(3-e->song.chanCollapse[selEnd.xCoarse])) {
|
||||
selEnd.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2;
|
||||
if (e->curSubSong->chanCollapse[selEnd.xCoarse] && selEnd.xFine>=(3-e->curSubSong->chanCollapse[selEnd.xCoarse])) {
|
||||
selEnd.xFine=2+e->curPat[cursor.xCoarse].effectCols*2;
|
||||
}
|
||||
|
||||
e->setMidiBaseChan(cursor.xCoarse);
|
||||
|
@ -126,7 +126,7 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) {
|
|||
demandScrollX=true;
|
||||
if (x>0) {
|
||||
for (int i=0; i<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;
|
||||
if (++cursor.xCoarse>=lastChannel) {
|
||||
if (settings.wrapHorizontal!=0 && !select) {
|
||||
|
@ -134,10 +134,10 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) {
|
|||
if (settings.wrapHorizontal==2) y++;
|
||||
} else {
|
||||
cursor.xCoarse=lastChannel-1;
|
||||
cursor.xFine=e->song.chanCollapse[cursor.xCoarse]?(3-e->song.chanCollapse[cursor.xCoarse]):(2+e->song.pat[cursor.xCoarse].effectCols*2);
|
||||
cursor.xFine=e->curSubSong->chanCollapse[cursor.xCoarse]?(3-e->curSubSong->chanCollapse[cursor.xCoarse]):(2+e->curPat[cursor.xCoarse].effectCols*2);
|
||||
}
|
||||
} else {
|
||||
while (!e->song.chanShow[cursor.xCoarse]) {
|
||||
while (!e->curSubSong->chanShow[cursor.xCoarse]) {
|
||||
cursor.xCoarse++;
|
||||
if (cursor.xCoarse>=e->getTotalChannelCount()) break;
|
||||
}
|
||||
|
@ -150,21 +150,21 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) {
|
|||
if (--cursor.xCoarse<firstChannel) {
|
||||
if (settings.wrapHorizontal!=0 && !select) {
|
||||
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--;
|
||||
} else {
|
||||
cursor.xCoarse=firstChannel;
|
||||
cursor.xFine=0;
|
||||
}
|
||||
} else {
|
||||
while (!e->song.chanShow[cursor.xCoarse]) {
|
||||
while (!e->curSubSong->chanShow[cursor.xCoarse]) {
|
||||
cursor.xCoarse--;
|
||||
if (cursor.xCoarse<0) break;
|
||||
}
|
||||
if (e->song.chanCollapse[cursor.xCoarse]) {
|
||||
cursor.xFine=3-e->song.chanCollapse[cursor.xCoarse];
|
||||
if (e->curSubSong->chanCollapse[cursor.xCoarse]) {
|
||||
cursor.xFine=3-e->curSubSong->chanCollapse[cursor.xCoarse];
|
||||
} else {
|
||||
cursor.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2;
|
||||
cursor.xFine=2+e->curPat[cursor.xCoarse].effectCols*2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -175,18 +175,18 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) {
|
|||
if (y>0) {
|
||||
for (int i=0; i<y; i++) {
|
||||
cursor.y++;
|
||||
if (cursor.y>=e->song.patLen) {
|
||||
if (cursor.y>=e->curSubSong->patLen) {
|
||||
if (settings.wrapVertical!=0 && !select) {
|
||||
cursor.y=0;
|
||||
if (settings.wrapVertical==2) {
|
||||
if ((!e->isPlaying() || !followPattern) && curOrder<(e->song.ordersLen-1)) {
|
||||
if ((!e->isPlaying() || !followPattern) && curOrder<(e->curSubSong->ordersLen-1)) {
|
||||
setOrder(curOrder+1);
|
||||
} else {
|
||||
cursor.y=e->song.patLen-1;
|
||||
cursor.y=e->curSubSong->patLen-1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cursor.y=e->song.patLen-1;
|
||||
cursor.y=e->curSubSong->patLen-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -195,7 +195,7 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) {
|
|||
cursor.y--;
|
||||
if (cursor.y<0) {
|
||||
if (settings.wrapVertical!=0 && !select) {
|
||||
cursor.y=e->song.patLen-1;
|
||||
cursor.y=e->curSubSong->patLen-1;
|
||||
if (settings.wrapVertical==2) {
|
||||
if ((!e->isPlaying() || !followPattern) && curOrder>0) {
|
||||
setOrder(curOrder-1);
|
||||
|
@ -229,7 +229,7 @@ void FurnaceGUI::moveCursorPrevChannel(bool overflow) {
|
|||
do {
|
||||
cursor.xCoarse--;
|
||||
if (cursor.xCoarse<0) break;
|
||||
} while (!e->song.chanShow[cursor.xCoarse]);
|
||||
} while (!e->curSubSong->chanShow[cursor.xCoarse]);
|
||||
if (cursor.xCoarse<firstChannel) {
|
||||
if (overflow) {
|
||||
cursor.xCoarse=lastChannel-1;
|
||||
|
@ -253,7 +253,7 @@ void FurnaceGUI::moveCursorNextChannel(bool overflow) {
|
|||
do {
|
||||
cursor.xCoarse++;
|
||||
if (cursor.xCoarse>=e->getTotalChannelCount()) break;
|
||||
} while (!e->song.chanShow[cursor.xCoarse]);
|
||||
} while (!e->curSubSong->chanShow[cursor.xCoarse]);
|
||||
if (cursor.xCoarse>=lastChannel) {
|
||||
if (overflow) {
|
||||
cursor.xCoarse=firstChannel;
|
||||
|
@ -290,14 +290,14 @@ void FurnaceGUI::moveCursorTop(bool select) {
|
|||
void FurnaceGUI::moveCursorBottom(bool select) {
|
||||
finishSelection();
|
||||
curNibble=false;
|
||||
if (cursor.y==e->song.patLen-1) {
|
||||
if (cursor.y==e->curSubSong->patLen-1) {
|
||||
DETERMINE_LAST;
|
||||
cursor.xCoarse=lastChannel-1;
|
||||
if (cursor.xCoarse<0) cursor.xCoarse=0;
|
||||
cursor.xFine=2+e->song.pat[cursor.xCoarse].effectCols*2;
|
||||
cursor.xFine=2+e->curPat[cursor.xCoarse].effectCols*2;
|
||||
demandScrollX=true;
|
||||
} else {
|
||||
cursor.y=e->song.patLen-1;
|
||||
cursor.y=e->curSubSong->patLen-1;
|
||||
}
|
||||
if (!select) {
|
||||
selStart=cursor;
|
||||
|
@ -310,7 +310,7 @@ void FurnaceGUI::moveCursorBottom(bool select) {
|
|||
void FurnaceGUI::editAdvance() {
|
||||
finishSelection();
|
||||
cursor.y+=editStep;
|
||||
if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1;
|
||||
if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1;
|
||||
selStart=cursor;
|
||||
selEnd=cursor;
|
||||
updateScroll(cursor.y);
|
||||
|
|
|
@ -470,7 +470,7 @@ void FurnaceGUI::doAction(int what) {
|
|||
e->unmuteAll();
|
||||
break;
|
||||
case GUI_ACTION_PAT_NEXT_ORDER:
|
||||
if (curOrder<e->song.ordersLen-1) {
|
||||
if (curOrder<e->curSubSong->ordersLen-1) {
|
||||
setOrder(curOrder+1);
|
||||
}
|
||||
break;
|
||||
|
@ -481,21 +481,21 @@ void FurnaceGUI::doAction(int what) {
|
|||
break;
|
||||
case GUI_ACTION_PAT_COLLAPSE:
|
||||
if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break;
|
||||
if (e->song.chanCollapse[cursor.xCoarse]==0) {
|
||||
e->song.chanCollapse[cursor.xCoarse]=3;
|
||||
} else if (e->song.chanCollapse[cursor.xCoarse]>0) {
|
||||
e->song.chanCollapse[cursor.xCoarse]--;
|
||||
if (e->curSubSong->chanCollapse[cursor.xCoarse]==0) {
|
||||
e->curSubSong->chanCollapse[cursor.xCoarse]=3;
|
||||
} else if (e->curSubSong->chanCollapse[cursor.xCoarse]>0) {
|
||||
e->curSubSong->chanCollapse[cursor.xCoarse]--;
|
||||
}
|
||||
break;
|
||||
case GUI_ACTION_PAT_INCREASE_COLUMNS:
|
||||
if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break;
|
||||
e->song.pat[cursor.xCoarse].effectCols++;
|
||||
if (e->song.pat[cursor.xCoarse].effectCols>8) e->song.pat[cursor.xCoarse].effectCols=8;
|
||||
e->curPat[cursor.xCoarse].effectCols++;
|
||||
if (e->curPat[cursor.xCoarse].effectCols>8) e->curPat[cursor.xCoarse].effectCols=8;
|
||||
break;
|
||||
case GUI_ACTION_PAT_DECREASE_COLUMNS:
|
||||
if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break;
|
||||
e->song.pat[cursor.xCoarse].effectCols--;
|
||||
if (e->song.pat[cursor.xCoarse].effectCols<1) e->song.pat[cursor.xCoarse].effectCols=1;
|
||||
e->curPat[cursor.xCoarse].effectCols--;
|
||||
if (e->curPat[cursor.xCoarse].effectCols<1) e->curPat[cursor.xCoarse].effectCols=1;
|
||||
break;
|
||||
case GUI_ACTION_PAT_INTERPOLATE:
|
||||
doInterpolate();
|
||||
|
@ -1196,7 +1196,7 @@ void FurnaceGUI::doAction(int what) {
|
|||
}
|
||||
break;
|
||||
case GUI_ACTION_ORDERS_DOWN:
|
||||
if (curOrder<e->song.ordersLen-1) {
|
||||
if (curOrder<e->curSubSong->ordersLen-1) {
|
||||
setOrder(curOrder+1);
|
||||
}
|
||||
break;
|
||||
|
@ -1209,7 +1209,7 @@ void FurnaceGUI::doAction(int what) {
|
|||
orderCursor=firstChannel;
|
||||
break;
|
||||
}
|
||||
} while (!e->song.chanShow[orderCursor]);
|
||||
} while (!e->curSubSong->chanShow[orderCursor]);
|
||||
break;
|
||||
}
|
||||
case GUI_ACTION_ORDERS_RIGHT: {
|
||||
|
@ -1221,20 +1221,20 @@ void FurnaceGUI::doAction(int what) {
|
|||
orderCursor=lastChannel-1;
|
||||
break;
|
||||
}
|
||||
} while (!e->song.chanShow[orderCursor]);
|
||||
} while (!e->curSubSong->chanShow[orderCursor]);
|
||||
break;
|
||||
}
|
||||
case GUI_ACTION_ORDERS_INCREASE: {
|
||||
if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break;
|
||||
if (e->song.orders.ord[orderCursor][curOrder]<0x7f) {
|
||||
e->song.orders.ord[orderCursor][curOrder]++;
|
||||
if (e->curOrders->ord[orderCursor][curOrder]<0x7f) {
|
||||
e->curOrders->ord[orderCursor][curOrder]++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GUI_ACTION_ORDERS_DECREASE: {
|
||||
if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break;
|
||||
if (e->song.orders.ord[orderCursor][curOrder]>0) {
|
||||
e->song.orders.ord[orderCursor][curOrder]--;
|
||||
if (e->curOrders->ord[orderCursor][curOrder]>0) {
|
||||
e->curOrders->ord[orderCursor][curOrder]--;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ void FurnaceGUI::drawEditControls() {
|
|||
ImGui::Text("Edit Step");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::InputInt("##EditStep",&editStep,1,1)) {
|
||||
if (editStep>=e->song.patLen) editStep=e->song.patLen-1;
|
||||
if (editStep>=e->curSubSong->patLen) editStep=e->curSubSong->patLen-1;
|
||||
if (editStep<0) editStep=0;
|
||||
|
||||
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
|
||||
|
@ -148,7 +148,7 @@ void FurnaceGUI::drawEditControls() {
|
|||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(96.0f*dpiScale);
|
||||
if (ImGui::InputInt("##EditStep",&editStep,1,1)) {
|
||||
if (editStep>=e->song.patLen) editStep=e->song.patLen-1;
|
||||
if (editStep>=e->curSubSong->patLen) editStep=e->curSubSong->patLen-1;
|
||||
if (editStep<0) editStep=0;
|
||||
|
||||
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
|
||||
|
@ -217,7 +217,7 @@ void FurnaceGUI::drawEditControls() {
|
|||
ImGui::Text("Step");
|
||||
ImGui::SetNextItemWidth(avail);
|
||||
if (ImGui::InputInt("##EditStep",&editStep,0,0)) {
|
||||
if (editStep>=e->song.patLen) editStep=e->song.patLen-1;
|
||||
if (editStep>=e->curSubSong->patLen) editStep=e->curSubSong->patLen-1;
|
||||
if (editStep<0) editStep=0;
|
||||
|
||||
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
|
||||
|
@ -317,7 +317,7 @@ void FurnaceGUI::drawEditControls() {
|
|||
ImGui::SetCursorPosX(cursor);
|
||||
ImGui::SetNextItemWidth(avail);
|
||||
if (ImGui::InputInt("##EditStep",&editStep,1,1)) {
|
||||
if (editStep>=e->song.patLen) editStep=e->song.patLen-1;
|
||||
if (editStep>=e->curSubSong->patLen) editStep=e->curSubSong->patLen-1;
|
||||
if (editStep<0) editStep=0;
|
||||
|
||||
if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) {
|
||||
|
|
|
@ -44,8 +44,8 @@ const char* noteNameNormal(short note, short octave) {
|
|||
void FurnaceGUI::prepareUndo(ActionType action) {
|
||||
switch (action) {
|
||||
case GUI_UNDO_CHANGE_ORDER:
|
||||
oldOrders=e->song.orders;
|
||||
oldOrdersLen=e->song.ordersLen;
|
||||
memcpy(&oldOrders,e->curOrders,sizeof(DivOrders));
|
||||
oldOrdersLen=e->curSubSong->ordersLen;
|
||||
break;
|
||||
case GUI_UNDO_PATTERN_EDIT:
|
||||
case GUI_UNDO_PATTERN_DELETE:
|
||||
|
@ -63,7 +63,7 @@ void FurnaceGUI::prepareUndo(ActionType action) {
|
|||
case GUI_UNDO_PATTERN_COLLAPSE:
|
||||
case GUI_UNDO_PATTERN_EXPAND:
|
||||
for (int i=0; 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;
|
||||
}
|
||||
|
@ -78,18 +78,19 @@ void FurnaceGUI::makeUndo(ActionType action) {
|
|||
s.selEnd=selEnd;
|
||||
s.order=curOrder;
|
||||
s.nibble=curNibble;
|
||||
size_t subSong=e->getCurrentSubSong();
|
||||
switch (action) {
|
||||
case GUI_UNDO_CHANGE_ORDER:
|
||||
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
||||
for (int j=0; j<128; j++) {
|
||||
if (oldOrders.ord[i][j]!=e->song.orders.ord[i][j]) {
|
||||
s.ord.push_back(UndoOrderData(i,j,oldOrders.ord[i][j],e->song.orders.ord[i][j]));
|
||||
if (oldOrders.ord[i][j]!=e->curOrders->ord[i][j]) {
|
||||
s.ord.push_back(UndoOrderData(subSong,i,j,oldOrders.ord[i][j],e->curOrders->ord[i][j]));
|
||||
}
|
||||
}
|
||||
}
|
||||
s.oldOrdersLen=oldOrdersLen;
|
||||
s.newOrdersLen=e->song.ordersLen;
|
||||
if (oldOrdersLen!=e->song.ordersLen) {
|
||||
s.newOrdersLen=e->curSubSong->ordersLen;
|
||||
if (oldOrdersLen!=e->curSubSong->ordersLen) {
|
||||
doPush=true;
|
||||
}
|
||||
if (!s.ord.empty()) {
|
||||
|
@ -112,11 +113,11 @@ void FurnaceGUI::makeUndo(ActionType action) {
|
|||
case GUI_UNDO_PATTERN_COLLAPSE:
|
||||
case GUI_UNDO_PATTERN_EXPAND:
|
||||
for (int i=0; i<e->getTotalChannelCount(); i++) {
|
||||
DivPattern* p=e->song.pat[i].getPattern(e->song.orders.ord[i][curOrder],false);
|
||||
for (int j=0; j<e->song.patLen; j++) {
|
||||
DivPattern* p=e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false);
|
||||
for (int j=0; j<e->curSubSong->patLen; j++) {
|
||||
for (int k=0; k<32; k++) {
|
||||
if (p->data[j][k]!=oldPat[i]->data[j][k]) {
|
||||
s.pat.push_back(UndoPatternData(i,e->song.orders.ord[i][curOrder],j,k,oldPat[i]->data[j][k],p->data[j][k]));
|
||||
s.pat.push_back(UndoPatternData(subSong,i,e->curOrders->ord[i][curOrder],j,k,oldPat[i]->data[j][k],p->data[j][k]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -137,15 +138,15 @@ void FurnaceGUI::makeUndo(ActionType action) {
|
|||
void FurnaceGUI::doSelectAll() {
|
||||
finishSelection();
|
||||
curNibble=false;
|
||||
if (selStart.xFine==0 && selEnd.xFine==2+e->song.pat[selEnd.xCoarse].effectCols*2) {
|
||||
if (selStart.y==0 && selEnd.y==e->song.patLen-1) { // select entire pattern
|
||||
if (selStart.xFine==0 && selEnd.xFine==2+e->curPat[selEnd.xCoarse].effectCols*2) {
|
||||
if (selStart.y==0 && selEnd.y==e->curSubSong->patLen-1) { // select entire pattern
|
||||
selStart.xCoarse=0;
|
||||
selStart.xFine=0;
|
||||
selEnd.xCoarse=e->getTotalChannelCount()-1;
|
||||
selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2;
|
||||
selEnd.xFine=2+e->curPat[selEnd.xCoarse].effectCols*2;
|
||||
} else { // select entire column
|
||||
selStart.y=0;
|
||||
selEnd.y=e->song.patLen-1;
|
||||
selEnd.y=e->curSubSong->patLen-1;
|
||||
}
|
||||
} else {
|
||||
int selStartX=0;
|
||||
|
@ -153,26 +154,26 @@ void FurnaceGUI::doSelectAll() {
|
|||
// find row position
|
||||
for (SelectionPoint i; i.xCoarse!=selStart.xCoarse || i.xFine!=selStart.xFine; selStartX++) {
|
||||
i.xFine++;
|
||||
if (i.xFine>=3+e->song.pat[i.xCoarse].effectCols*2) {
|
||||
if (i.xFine>=3+e->curPat[i.xCoarse].effectCols*2) {
|
||||
i.xFine=0;
|
||||
i.xCoarse++;
|
||||
}
|
||||
}
|
||||
for (SelectionPoint i; i.xCoarse!=selEnd.xCoarse || i.xFine!=selEnd.xFine; selEndX++) {
|
||||
i.xFine++;
|
||||
if (i.xFine>=3+e->song.pat[i.xCoarse].effectCols*2) {
|
||||
if (i.xFine>=3+e->curPat[i.xCoarse].effectCols*2) {
|
||||
i.xFine=0;
|
||||
i.xCoarse++;
|
||||
}
|
||||
}
|
||||
|
||||
float aspect=float(selEndX-selStartX+1)/float(selEnd.y-selStart.y+1);
|
||||
if (aspect<=1.0f && !(selStart.y==0 && selEnd.y==e->song.patLen-1)) { // up-down
|
||||
if (aspect<=1.0f && !(selStart.y==0 && selEnd.y==e->curSubSong->patLen-1)) { // up-down
|
||||
selStart.y=0;
|
||||
selEnd.y=e->song.patLen-1;
|
||||
selEnd.y=e->curSubSong->patLen-1;
|
||||
} else { // left-right
|
||||
selStart.xFine=0;
|
||||
selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectCols*2;
|
||||
selEnd.xFine=2+e->curPat[selEnd.xCoarse].effectCols*2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -198,9 +199,9 @@ void FurnaceGUI::doDelete() {
|
|||
int iCoarse=selStart.xCoarse;
|
||||
int iFine=selStart.xFine;
|
||||
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
|
||||
if (!e->song.chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
if (!e->curSubSong->chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
maskOut(opMaskDelete,iFine);
|
||||
for (int j=selStart.y; j<=selEnd.y; j++) {
|
||||
if (iFine==0) {
|
||||
|
@ -235,12 +236,12 @@ void FurnaceGUI::doPullDelete() {
|
|||
int iCoarse=selStart.xCoarse;
|
||||
int iFine=selStart.xFine;
|
||||
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
|
||||
if (!e->song.chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
if (!e->curSubSong->chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
maskOut(opMaskPullDelete,iFine);
|
||||
for (int j=selStart.y; j<e->song.patLen; j++) {
|
||||
if (j<e->song.patLen-1) {
|
||||
for (int j=selStart.y; j<e->curSubSong->patLen; j++) {
|
||||
if (j<e->curSubSong->patLen-1) {
|
||||
if (iFine==0) {
|
||||
pat->data[j][iFine]=pat->data[j+1][iFine];
|
||||
}
|
||||
|
@ -267,11 +268,11 @@ void FurnaceGUI::doInsert() {
|
|||
int iCoarse=selStart.xCoarse;
|
||||
int iFine=selStart.xFine;
|
||||
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
|
||||
if (!e->song.chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
if (!e->curSubSong->chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); 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 (iFine==0) {
|
||||
pat->data[j][iFine]=0;
|
||||
|
@ -299,9 +300,9 @@ void FurnaceGUI::doTranspose(int amount, OperationMask& mask) {
|
|||
int iCoarse=selStart.xCoarse;
|
||||
int iFine=selStart.xFine;
|
||||
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
|
||||
if (!e->song.chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
if (!e->curSubSong->chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
maskOut(mask,iFine);
|
||||
for (int j=selStart.y; j<=selEnd.y; j++) {
|
||||
if (iFine==0) {
|
||||
|
@ -367,9 +368,9 @@ void FurnaceGUI::doCopy(bool cut) {
|
|||
}
|
||||
clipboard+='\n';
|
||||
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
|
||||
if (!e->song.chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
if (!e->curSubSong->chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
if (iFine==0) {
|
||||
clipboard+=noteNameNormal(pat->data[j][0],pat->data[j][1]);
|
||||
if (cut) {
|
||||
|
@ -432,7 +433,7 @@ void FurnaceGUI::doPaste(PasteMode mode) {
|
|||
|
||||
int j=cursor.y;
|
||||
char note[4];
|
||||
for (size_t i=2; 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;
|
||||
int iCoarse=cursor.xCoarse;
|
||||
int iFine=(startOff>2 && cursor.xFine>2)?(((cursor.xFine-1)&(~1))|1):startOff;
|
||||
|
@ -440,10 +441,10 @@ void FurnaceGUI::doPaste(PasteMode mode) {
|
|||
String& line=data[i];
|
||||
|
||||
while (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]=='|') {
|
||||
iCoarse++;
|
||||
if (iCoarse<lastChannel) while (!e->song.chanShow[iCoarse]) {
|
||||
if (iCoarse<lastChannel) while (!e->curSubSong->chanShow[iCoarse]) {
|
||||
iCoarse++;
|
||||
if (iCoarse>=lastChannel) break;
|
||||
}
|
||||
|
@ -530,7 +531,7 @@ void FurnaceGUI::doPaste(PasteMode mode) {
|
|||
break;
|
||||
}
|
||||
if (mode!=GUI_PASTE_MODE_MIX_BG || pat->data[j][iFine+1]==-1) {
|
||||
if (iFine<(3+e->song.pat[iCoarse].effectCols*2)) pat->data[j][iFine+1]=val;
|
||||
if (iFine<(3+e->curPat[iCoarse].effectCols*2)) pat->data[j][iFine+1]=val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -543,7 +544,7 @@ void FurnaceGUI::doPaste(PasteMode mode) {
|
|||
break;
|
||||
}
|
||||
j++;
|
||||
if (mode==GUI_PASTE_MODE_OVERFLOW && j>=e->song.patLen && curOrder<e->song.ordersLen-1) {
|
||||
if (mode==GUI_PASTE_MODE_OVERFLOW && j>=e->curSubSong->patLen && curOrder<e->curSubSong->ordersLen-1) {
|
||||
j=0;
|
||||
curOrder++;
|
||||
}
|
||||
|
@ -554,7 +555,7 @@ void FurnaceGUI::doPaste(PasteMode mode) {
|
|||
}
|
||||
if (settings.cursorPastePos) {
|
||||
cursor.y=j;
|
||||
if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1;
|
||||
if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1;
|
||||
updateScroll(cursor.y);
|
||||
}
|
||||
|
||||
|
@ -567,8 +568,8 @@ void FurnaceGUI::doChangeIns(int ins) {
|
|||
|
||||
int iCoarse=selStart.xCoarse;
|
||||
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
|
||||
if (!e->song.chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
|
||||
if (!e->curSubSong->chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
|
||||
for (int j=selStart.y; j<=selEnd.y; j++) {
|
||||
if (pat->data[j][2]!=-1 || !(pat->data[j][0]==0 && pat->data[j][1]==0)) {
|
||||
pat->data[j][2]=ins;
|
||||
|
@ -587,9 +588,9 @@ void FurnaceGUI::doInterpolate() {
|
|||
int iCoarse=selStart.xCoarse;
|
||||
int iFine=selStart.xFine;
|
||||
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
|
||||
if (!e->song.chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
if (!e->curSubSong->chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
maskOut(opMaskInterpolate,iFine);
|
||||
points.clear();
|
||||
if (iFine!=0) {
|
||||
|
@ -646,9 +647,9 @@ void FurnaceGUI::doFade(int p0, int p1, bool mode) {
|
|||
int iCoarse=selStart.xCoarse;
|
||||
int iFine=selStart.xFine;
|
||||
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
|
||||
if (!e->song.chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
if (!e->curSubSong->chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
maskOut(opMaskFade,iFine);
|
||||
if (iFine!=0) {
|
||||
int absoluteTop=255;
|
||||
|
@ -684,9 +685,9 @@ void FurnaceGUI::doInvertValues() {
|
|||
int iCoarse=selStart.xCoarse;
|
||||
int iFine=selStart.xFine;
|
||||
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
|
||||
if (!e->song.chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
if (!e->curSubSong->chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
maskOut(opMaskInvertVal,iFine);
|
||||
if (iFine!=0) {
|
||||
int top=255;
|
||||
|
@ -715,9 +716,9 @@ void FurnaceGUI::doScale(float top) {
|
|||
int iCoarse=selStart.xCoarse;
|
||||
int iFine=selStart.xFine;
|
||||
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
|
||||
if (!e->song.chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
if (!e->curSubSong->chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
maskOut(opMaskScale,iFine);
|
||||
if (iFine!=0) {
|
||||
int absoluteTop=255;
|
||||
|
@ -746,9 +747,9 @@ void FurnaceGUI::doRandomize(int bottom, int top, bool mode) {
|
|||
int iCoarse=selStart.xCoarse;
|
||||
int iFine=selStart.xFine;
|
||||
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
|
||||
if (!e->song.chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
if (!e->curSubSong->chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
maskOut(opMaskRandomize,iFine);
|
||||
if (iFine!=0) {
|
||||
int absoluteTop=255;
|
||||
|
@ -792,9 +793,9 @@ void FurnaceGUI::doFlip() {
|
|||
int iCoarse=selStart.xCoarse;
|
||||
int iFine=selStart.xFine;
|
||||
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
|
||||
if (!e->song.chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
if (!e->curSubSong->chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
maskOut(opMaskFlip,iFine);
|
||||
for (int j=selStart.y; j<=selEnd.y; j++) {
|
||||
if (iFine==0) {
|
||||
|
@ -823,9 +824,9 @@ void FurnaceGUI::doCollapse(int divider) {
|
|||
int iCoarse=selStart.xCoarse;
|
||||
int iFine=selStart.xFine;
|
||||
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
|
||||
if (!e->song.chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
if (!e->curSubSong->chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
maskOut(opMaskCollapseExpand,iFine);
|
||||
for (int j=selStart.y; j<=selEnd.y; j++) {
|
||||
if (iFine==0) {
|
||||
|
@ -880,9 +881,9 @@ void FurnaceGUI::doExpand(int multiplier) {
|
|||
int iCoarse=selStart.xCoarse;
|
||||
int iFine=selStart.xFine;
|
||||
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
|
||||
if (!e->song.chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->song.pat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
if (!e->curSubSong->chanShow[iCoarse]) continue;
|
||||
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
|
||||
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
||||
maskOut(opMaskCollapseExpand,iFine);
|
||||
for (int j=selStart.y; j<=selEnd.y; j++) {
|
||||
if (iFine==0) {
|
||||
|
@ -891,7 +892,7 @@ void FurnaceGUI::doExpand(int multiplier) {
|
|||
patBuffer.data[j][iFine+1]=pat->data[j][iFine+1];
|
||||
}
|
||||
for (int j=0; j<=(selEnd.y-selStart.y)*multiplier; j++) {
|
||||
if ((j+selStart.y)>=e->song.patLen) break;
|
||||
if ((j+selStart.y)>=e->curSubSong->patLen) break;
|
||||
if ((j%multiplier)!=0) {
|
||||
if (iFine==0) {
|
||||
pat->data[j+selStart.y][0]=0;
|
||||
|
@ -921,9 +922,10 @@ void FurnaceGUI::doUndo() {
|
|||
|
||||
switch (us.type) {
|
||||
case GUI_UNDO_CHANGE_ORDER:
|
||||
e->song.ordersLen=us.oldOrdersLen;
|
||||
e->curSubSong->ordersLen=us.oldOrdersLen;
|
||||
for (UndoOrderData& i: us.ord) {
|
||||
e->song.orders.ord[i.chan][i.ord]=i.oldVal;
|
||||
e->changeSongP(i.subSong);
|
||||
e->curOrders->ord[i.chan][i.ord]=i.oldVal;
|
||||
}
|
||||
break;
|
||||
case GUI_UNDO_PATTERN_EDIT:
|
||||
|
@ -942,7 +944,8 @@ void FurnaceGUI::doUndo() {
|
|||
case GUI_UNDO_PATTERN_COLLAPSE:
|
||||
case GUI_UNDO_PATTERN_EXPAND:
|
||||
for (UndoPatternData& i: us.pat) {
|
||||
DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true);
|
||||
e->changeSongP(i.subSong);
|
||||
DivPattern* p=e->curPat[i.chan].getPattern(i.pat,true);
|
||||
p->data[i.row][i.col]=i.oldVal;
|
||||
}
|
||||
if (!e->isPlaying() || !followPattern) {
|
||||
|
@ -967,9 +970,10 @@ void FurnaceGUI::doRedo() {
|
|||
|
||||
switch (us.type) {
|
||||
case GUI_UNDO_CHANGE_ORDER:
|
||||
e->song.ordersLen=us.newOrdersLen;
|
||||
e->curSubSong->ordersLen=us.newOrdersLen;
|
||||
for (UndoOrderData& i: us.ord) {
|
||||
e->song.orders.ord[i.chan][i.ord]=i.newVal;
|
||||
e->changeSongP(i.subSong);
|
||||
e->curOrders->ord[i.chan][i.ord]=i.newVal;
|
||||
}
|
||||
break;
|
||||
case GUI_UNDO_PATTERN_EDIT:
|
||||
|
@ -988,7 +992,8 @@ void FurnaceGUI::doRedo() {
|
|||
case GUI_UNDO_PATTERN_COLLAPSE:
|
||||
case GUI_UNDO_PATTERN_EXPAND:
|
||||
for (UndoPatternData& i: us.pat) {
|
||||
DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true);
|
||||
e->changeSongP(i.subSong);
|
||||
DivPattern* p=e->curPat[i.chan].getPattern(i.pat,true);
|
||||
p->data[i.row][i.col]=i.newVal;
|
||||
}
|
||||
if (!e->isPlaying()) {
|
||||
|
|
|
@ -793,9 +793,9 @@ void FurnaceGUI::prepareLayout() {
|
|||
}
|
||||
|
||||
float FurnaceGUI::calcBPM(int s1, int s2, float hz) {
|
||||
float hl=e->song.hilightA;
|
||||
float hl=e->curSubSong->hilightA;
|
||||
if (hl<=0.0f) hl=4.0f;
|
||||
float timeBase=e->song.timeBase+1;
|
||||
float timeBase=e->curSubSong->timeBase+1;
|
||||
float speedSum=s1+s2;
|
||||
if (timeBase<1.0f) timeBase=1.0f;
|
||||
if (speedSum<1.0f) speedSum=1.0f;
|
||||
|
@ -857,7 +857,7 @@ void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode, bool autoNote) {
|
|||
}
|
||||
|
||||
void FurnaceGUI::noteInput(int num, int key, int vol) {
|
||||
DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],true);
|
||||
DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true);
|
||||
|
||||
prepareUndo(GUI_UNDO_PATTERN_EDIT);
|
||||
|
||||
|
@ -901,7 +901,7 @@ void FurnaceGUI::noteInput(int num, int key, int vol) {
|
|||
}
|
||||
|
||||
void FurnaceGUI::valueInput(int num, bool direct, int target) {
|
||||
DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],true);
|
||||
DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true);
|
||||
prepareUndo(GUI_UNDO_PATTERN_EDIT);
|
||||
if (target==-1) target=cursor.xFine+1;
|
||||
if (direct) {
|
||||
|
@ -965,7 +965,7 @@ void FurnaceGUI::valueInput(int num, bool direct, int target) {
|
|||
editAdvance();
|
||||
} else {
|
||||
if (settings.effectCursorDir==2) {
|
||||
if (++cursor.xFine>=(3+(e->song.pat[cursor.xCoarse].effectCols*2))) {
|
||||
if (++cursor.xFine>=(3+(e->curPat[cursor.xCoarse].effectCols*2))) {
|
||||
cursor.xFine=3;
|
||||
}
|
||||
} else {
|
||||
|
@ -1123,7 +1123,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
|
|||
int num=valueKeys.at(ev.key.keysym.sym);
|
||||
if (orderCursor>=0 && orderCursor<e->getTotalChannelCount()) {
|
||||
e->lockSave([this,num]() {
|
||||
e->song.orders.ord[orderCursor][curOrder]=((e->song.orders.ord[orderCursor][curOrder]<<4)|num);
|
||||
e->curOrders->ord[orderCursor][curOrder]=((e->curOrders->ord[orderCursor][curOrder]<<4)|num);
|
||||
});
|
||||
if (orderEditMode==2 || orderEditMode==3) {
|
||||
curNibble=!curNibble;
|
||||
|
@ -1132,7 +1132,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
|
|||
orderCursor++;
|
||||
if (orderCursor>=e->getTotalChannelCount()) orderCursor=0;
|
||||
} else if (orderEditMode==3) {
|
||||
if (curOrder<e->song.ordersLen-1) {
|
||||
if (curOrder<e->curSubSong->ordersLen-1) {
|
||||
setOrder(curOrder+1);
|
||||
}
|
||||
}
|
||||
|
@ -2031,7 +2031,7 @@ void FurnaceGUI::editOptions(bool topMenu) {
|
|||
ImGui::PopFont();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Set")) {
|
||||
DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],true);
|
||||
DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true);
|
||||
latchIns=pat->data[cursor.y][2];
|
||||
latchVol=pat->data[cursor.y][3];
|
||||
latchEffect=pat->data[cursor.y][4];
|
||||
|
@ -2817,12 +2817,12 @@ bool FurnaceGUI::loop() {
|
|||
if (e->isPlaying()) {
|
||||
int totalTicks=e->getTotalTicks();
|
||||
int totalSeconds=e->getTotalSeconds();
|
||||
ImGui::Text("| Speed %d:%d @ %gHz (%g BPM) | Order %d/%d | Row %d/%d | %d:%.2d:%.2d.%.2d",e->getSpeed1(),e->getSpeed2(),e->getCurHz(),calcBPM(e->getSpeed1(),e->getSpeed2(),e->getCurHz()),e->getOrder(),e->song.ordersLen,e->getRow(),e->song.patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000);
|
||||
ImGui::Text("| Speed %d:%d @ %gHz (%g BPM) | Order %d/%d | Row %d/%d | %d:%.2d:%.2d.%.2d",e->getSpeed1(),e->getSpeed2(),e->getCurHz(),calcBPM(e->getSpeed1(),e->getSpeed2(),e->getCurHz()),e->getOrder(),e->curSubSong->ordersLen,e->getRow(),e->curSubSong->patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000);
|
||||
} else {
|
||||
bool hasInfo=false;
|
||||
String info;
|
||||
if (cursor.xCoarse>=0 && cursor.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) {
|
||||
case 0: // note
|
||||
if (p->data[cursor.y][0]>0) {
|
||||
|
@ -2883,6 +2883,7 @@ bool FurnaceGUI::loop() {
|
|||
|
||||
ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0);
|
||||
|
||||
drawSubSongs();
|
||||
drawPattern();
|
||||
drawEditControls();
|
||||
drawSongInfo();
|
||||
|
@ -3491,7 +3492,7 @@ bool FurnaceGUI::loop() {
|
|||
stop();
|
||||
e->lockEngine([this]() {
|
||||
for (int i=0; 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));
|
||||
for (int j=0; j<256; j++) {
|
||||
pat->data[j][0]=0;
|
||||
|
@ -3650,6 +3651,7 @@ bool FurnaceGUI::init() {
|
|||
regViewOpen=e->getConfBool("regViewOpen",false);
|
||||
logOpen=e->getConfBool("logOpen",false);
|
||||
effectListOpen=e->getConfBool("effectListOpen",false);
|
||||
subSongsOpen=e->getConfBool("subSongsOpen",true);
|
||||
|
||||
tempoView=e->getConfBool("tempoView",true);
|
||||
waveHex=e->getConfBool("waveHex",false);
|
||||
|
@ -3841,6 +3843,7 @@ bool FurnaceGUI::finish() {
|
|||
e->setConf("regViewOpen",regViewOpen);
|
||||
e->setConf("logOpen",logOpen);
|
||||
e->setConf("effectListOpen",effectListOpen);
|
||||
e->setConf("subSongsOpen",subSongsOpen);
|
||||
|
||||
// commit last window size
|
||||
e->setConf("lastWindowWidth",scrW);
|
||||
|
@ -3961,6 +3964,7 @@ FurnaceGUI::FurnaceGUI():
|
|||
logOpen(false),
|
||||
effectListOpen(false),
|
||||
chanOscOpen(false),
|
||||
subSongsOpen(true),
|
||||
/*
|
||||
editControlsDocked(false),
|
||||
ordersDocked(false),
|
||||
|
@ -3988,6 +3992,7 @@ FurnaceGUI::FurnaceGUI():
|
|||
logDocked(false),
|
||||
effectListDocked(false),
|
||||
chanOscDocked(false),
|
||||
subSongsDocked(false),
|
||||
*/
|
||||
selecting(false),
|
||||
selectingFull(false),
|
||||
|
|
|
@ -231,7 +231,8 @@ enum FurnaceGUIWindows {
|
|||
GUI_WINDOW_REGISTER_VIEW,
|
||||
GUI_WINDOW_LOG,
|
||||
GUI_WINDOW_EFFECT_LIST,
|
||||
GUI_WINDOW_CHAN_OSC
|
||||
GUI_WINDOW_CHAN_OSC,
|
||||
GUI_WINDOW_SUBSONGS
|
||||
};
|
||||
|
||||
enum FurnaceGUIFileDialogs {
|
||||
|
@ -337,6 +338,7 @@ enum FurnaceGUIActions {
|
|||
GUI_ACTION_WINDOW_LOG,
|
||||
GUI_ACTION_WINDOW_EFFECT_LIST,
|
||||
GUI_ACTION_WINDOW_CHAN_OSC,
|
||||
GUI_ACTION_WINDOW_SUBSONGS,
|
||||
|
||||
GUI_ACTION_COLLAPSE_WINDOW,
|
||||
GUI_ACTION_CLOSE_WINDOW,
|
||||
|
@ -550,9 +552,10 @@ enum ActionType {
|
|||
};
|
||||
|
||||
struct UndoPatternData {
|
||||
int chan, pat, row, col;
|
||||
int subSong, chan, pat, row, col;
|
||||
short oldVal, newVal;
|
||||
UndoPatternData(int c, int p, int r, int co, short v1, short v2):
|
||||
UndoPatternData(int s, int c, int p, int r, int co, short v1, short v2):
|
||||
subSong(s),
|
||||
chan(c),
|
||||
pat(p),
|
||||
row(r),
|
||||
|
@ -562,9 +565,10 @@ struct UndoPatternData {
|
|||
};
|
||||
|
||||
struct UndoOrderData {
|
||||
int chan, ord;
|
||||
int subSong, chan, ord;
|
||||
unsigned char oldVal, newVal;
|
||||
UndoOrderData(int c, int o, unsigned char v1, unsigned char v2):
|
||||
UndoOrderData(int s, int c, int o, unsigned char v1, unsigned char v2):
|
||||
subSong(s),
|
||||
chan(c),
|
||||
ord(o),
|
||||
oldVal(v1),
|
||||
|
@ -1010,12 +1014,14 @@ class FurnaceGUI {
|
|||
bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen;
|
||||
bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen;
|
||||
bool pianoOpen, notesOpen, channelsOpen, regViewOpen, logOpen, effectListOpen, chanOscOpen;
|
||||
bool subSongsOpen;
|
||||
|
||||
/* there ought to be a better way...
|
||||
bool editControlsDocked, ordersDocked, insListDocked, songInfoDocked, patternDocked, insEditDocked;
|
||||
bool waveListDocked, waveEditDocked, sampleListDocked, sampleEditDocked, aboutDocked, settingsDocked;
|
||||
bool mixerDocked, debugDocked, inspectorDocked, oscDocked, volMeterDocked, statsDocked, compatFlagsDocked;
|
||||
bool pianoDocked, notesDocked, channelsDocked, regViewDocked, logDocked, effectListDocked, chanOscDocked;
|
||||
bool subSongsDocked;
|
||||
*/
|
||||
|
||||
SelectionPoint selStart, selEnd, cursor;
|
||||
|
@ -1246,6 +1252,7 @@ class FurnaceGUI {
|
|||
void drawNewSong();
|
||||
void drawLog();
|
||||
void drawEffectList();
|
||||
void drawSubSongs();
|
||||
|
||||
void parseKeybinds();
|
||||
void promptKey(int which);
|
||||
|
|
|
@ -485,6 +485,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
|
|||
D("WINDOW_CHANNELS", "Channels", 0),
|
||||
D("WINDOW_REGISTER_VIEW", "Register View", 0),
|
||||
D("WINDOW_LOG", "Log Viewer", 0),
|
||||
D("WINDOW_SUBSONGS", "Subsongs", 0),
|
||||
D("EFFECT_LIST", "Effect List", 0),
|
||||
D("WINDOW_CHAN_OSC", "Oscilloscope (per-channel)", 0),
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ void FurnaceGUI::drawOrders() {
|
|||
ImGui::SetColumnWidth(-1,regionX-24.0f*dpiScale);
|
||||
int displayChans=0;
|
||||
for (int i=0; i<e->getTotalChannelCount(); i++) {
|
||||
if (e->song.chanShow[i]) displayChans++;
|
||||
if (e->curSubSong->chanShow[i]) displayChans++;
|
||||
}
|
||||
ImGui::PushFont(patFont);
|
||||
bool tooSmall=((displayChans+1)>((ImGui::GetContentRegionAvail().x)/(ImGui::CalcTextSize("AA").x+2.0*ImGui::GetStyle().ItemInnerSpacing.x)));
|
||||
|
@ -56,12 +56,12 @@ void FurnaceGUI::drawOrders() {
|
|||
ImGui::TableNextColumn();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]);
|
||||
for (int i=0; i<e->getTotalChannelCount(); i++) {
|
||||
if (!e->song.chanShow[i]) continue;
|
||||
if (!e->curSubSong->chanShow[i]) continue;
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s",e->getChannelShortName(i));
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
for (int i=0; i<e->song.ordersLen; i++) {
|
||||
for (int i=0; i<e->curSubSong->ordersLen; i++) {
|
||||
ImGui::TableNextRow(0,lineHeight);
|
||||
if (oldOrder1==i) ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_ORDER_ACTIVE]));
|
||||
ImGui::TableNextColumn();
|
||||
|
@ -84,16 +84,16 @@ void FurnaceGUI::drawOrders() {
|
|||
}
|
||||
ImGui::PopStyleColor();
|
||||
for (int j=0; j<e->getTotalChannelCount(); j++) {
|
||||
if (!e->song.chanShow[j]) continue;
|
||||
if (!e->curSubSong->chanShow[j]) continue;
|
||||
ImGui::TableNextColumn();
|
||||
DivPattern* pat=e->song.pat[j].getPattern(e->song.orders.ord[j][i],false);
|
||||
DivPattern* pat=e->curPat[j].getPattern(e->curOrders->ord[j][i],false);
|
||||
/*if (!pat->name.empty()) {
|
||||
snprintf(selID,4096,"%s##O_%.2x_%.2x",pat->name.c_str(),j,i);
|
||||
} else {*/
|
||||
snprintf(selID,4096,"%.2X##O_%.2x_%.2x",e->song.orders.ord[j][i],j,i);
|
||||
snprintf(selID,4096,"%.2X##O_%.2x_%.2x",e->curOrders->ord[j][i],j,i);
|
||||
//}
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text,(curOrder==i || e->song.orders.ord[j][i]==e->song.orders.ord[j][curOrder])?uiColors[GUI_COLOR_ORDER_SIMILAR]:uiColors[GUI_COLOR_ORDER_INACTIVE]);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text,(curOrder==i || e->curOrders->ord[j][i]==e->curOrders->ord[j][curOrder])?uiColors[GUI_COLOR_ORDER_SIMILAR]:uiColors[GUI_COLOR_ORDER_INACTIVE]);
|
||||
if (ImGui::Selectable(selID,(orderEditMode!=0 && curOrder==i && orderCursor==j))) {
|
||||
if (curOrder==i) {
|
||||
if (orderEditMode==0) {
|
||||
|
@ -101,10 +101,10 @@ void FurnaceGUI::drawOrders() {
|
|||
e->lockSave([this,i,j]() {
|
||||
if (changeAllOrders) {
|
||||
for (int k=0; 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 {
|
||||
if (e->song.orders.ord[j][i]<0xff) e->song.orders.ord[j][i]++;
|
||||
if (e->curOrders->ord[j][i]<0xff) e->curOrders->ord[j][i]++;
|
||||
}
|
||||
});
|
||||
e->walkSong(loopOrder,loopRow,loopEnd);
|
||||
|
@ -137,10 +137,10 @@ void FurnaceGUI::drawOrders() {
|
|||
e->lockSave([this,i,j]() {
|
||||
if (changeAllOrders) {
|
||||
for (int k=0; 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 {
|
||||
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);
|
||||
|
|
|
@ -43,21 +43,21 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
|
|||
return;
|
||||
}
|
||||
// check if we are in range
|
||||
if (ord<0 || ord>=e->song.ordersLen) {
|
||||
if (ord<0 || ord>=e->curSubSong->ordersLen) {
|
||||
return;
|
||||
}
|
||||
if (i<0 || i>=e->song.patLen) {
|
||||
if (i<0 || i>=e->curSubSong->patLen) {
|
||||
return;
|
||||
}
|
||||
bool isPushing=false;
|
||||
ImVec4 activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE];
|
||||
ImVec4 inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE];
|
||||
ImVec4 rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX];
|
||||
if (e->song.hilightB>0 && !(i%e->song.hilightB)) {
|
||||
if (e->curSubSong->hilightB>0 && !(i%e->curSubSong->hilightB)) {
|
||||
activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE_HI2];
|
||||
inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE_HI2];
|
||||
rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX_HI2];
|
||||
} else if (e->song.hilightA>0 && !(i%e->song.hilightA)) {
|
||||
} else if (e->curSubSong->hilightA>0 && !(i%e->curSubSong->hilightA)) {
|
||||
activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE_HI1];
|
||||
inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE_HI1];
|
||||
rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX_HI1];
|
||||
|
@ -68,9 +68,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
|
|||
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_EDITING]));
|
||||
} else if (isPlaying && oldRow==i && ord==e->getOrder()) {
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PLAY_HEAD]));
|
||||
} else if (e->song.hilightB>0 && !(i%e->song.hilightB)) {
|
||||
} else if (e->curSubSong->hilightB>0 && !(i%e->curSubSong->hilightB)) {
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_2]));
|
||||
} else if (e->song.hilightA>0 && !(i%e->song.hilightA)) {
|
||||
} else if (e->curSubSong->hilightA>0 && !(i%e->curSubSong->hilightA)) {
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_1]));
|
||||
}
|
||||
} else {
|
||||
|
@ -79,9 +79,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
|
|||
ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_EDITING]));
|
||||
} else if (isPlaying && oldRow==i && ord==e->getOrder()) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PLAY_HEAD]));
|
||||
} else if (e->song.hilightB>0 && !(i%e->song.hilightB)) {
|
||||
} else if (e->curSubSong->hilightB>0 && !(i%e->curSubSong->hilightB)) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_2]));
|
||||
} else if (e->song.hilightA>0 && !(i%e->song.hilightA)) {
|
||||
} else if (e->curSubSong->hilightA>0 && !(i%e->curSubSong->hilightA)) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_1]));
|
||||
} else {
|
||||
isPushing=false;
|
||||
|
@ -106,7 +106,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
|
|||
// for each column
|
||||
for (int j=0; j<chans; j++) {
|
||||
// check if channel is not hidden
|
||||
if (!e->song.chanShow[j]) {
|
||||
if (!e->curSubSong->chanShow[j]) {
|
||||
patChanX[j]=ImGui::GetCursorPosX();
|
||||
continue;
|
||||
}
|
||||
|
@ -155,7 +155,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
|
|||
ImGui::PopStyleColor();
|
||||
|
||||
// the following is only visible when the channel is not collapsed
|
||||
if (e->song.chanCollapse[j]<3) {
|
||||
if (e->curSubSong->chanCollapse[j]<3) {
|
||||
// instrument
|
||||
if (pat->data[i][2]==-1) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor);
|
||||
|
@ -195,7 +195,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
|
|||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
if (e->song.chanCollapse[j]<2) {
|
||||
if (e->curSubSong->chanCollapse[j]<2) {
|
||||
// volume
|
||||
if (pat->data[i][3]==-1) {
|
||||
sprintf(id,"..##PV_%d_%d",i,j);
|
||||
|
@ -229,9 +229,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
|
|||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
if (e->song.chanCollapse[j]<1) {
|
||||
if (e->curSubSong->chanCollapse[j]<1) {
|
||||
// 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);
|
||||
bool selectedEffect=selectedRow && (j32+index-1>=sel1XSum && j32+index-1<=sel2XSum);
|
||||
bool selectedEffectVal=selectedRow && (j32+index>=sel1XSum && j32+index<=sel2XSum);
|
||||
|
@ -358,7 +358,7 @@ void FurnaceGUI::drawPattern() {
|
|||
int displayChans=0;
|
||||
const DivPattern* patCache[DIV_MAX_CHANS];
|
||||
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::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]);
|
||||
|
@ -383,7 +383,7 @@ void FurnaceGUI::drawPattern() {
|
|||
}
|
||||
ImGui::TableSetupScrollFreeze(1,1);
|
||||
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::TableNextRow();
|
||||
|
@ -408,10 +408,10 @@ void FurnaceGUI::drawPattern() {
|
|||
cmdStream.clear();
|
||||
}
|
||||
for (int i=0; i<chans; i++) {
|
||||
if (!e->song.chanShow[i]) continue;
|
||||
if (!e->curSubSong->chanShow[i]) continue;
|
||||
ImGui::TableNextColumn();
|
||||
bool displayTooltip=false;
|
||||
if (e->song.chanCollapse[i]) {
|
||||
if (e->curSubSong->chanCollapse[i]) {
|
||||
const char* chName=e->getChannelShortName(i);
|
||||
if (strlen(chName)>3) {
|
||||
snprintf(chanID,2048,"...##_CH%d",i);
|
||||
|
@ -421,7 +421,7 @@ void FurnaceGUI::drawPattern() {
|
|||
displayTooltip=true;
|
||||
} else {
|
||||
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) {
|
||||
String shortChName=chName;
|
||||
shortChName.resize(chNameLimit-3);
|
||||
|
@ -483,7 +483,7 @@ void FurnaceGUI::drawPattern() {
|
|||
e->toggleSolo(i);
|
||||
}
|
||||
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);
|
||||
if (patNameTarget==i) {
|
||||
snprintf(chanID,2048,"##PatNameI%d_%d",i,ord);
|
||||
|
@ -510,30 +510,30 @@ void FurnaceGUI::drawPattern() {
|
|||
}
|
||||
ImGui::PopFont();
|
||||
} 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);
|
||||
if (ImGui::SmallButton(chanID)) {
|
||||
if (e->song.chanCollapse[i]==0) {
|
||||
e->song.chanCollapse[i]=3;
|
||||
} else if (e->song.chanCollapse[i]>0) {
|
||||
e->song.chanCollapse[i]--;
|
||||
if (e->curSubSong->chanCollapse[i]==0) {
|
||||
e->curSubSong->chanCollapse[i]=3;
|
||||
} else if (e->curSubSong->chanCollapse[i]>0) {
|
||||
e->curSubSong->chanCollapse[i]--;
|
||||
}
|
||||
}
|
||||
if (!e->song.chanCollapse[i]) {
|
||||
if (!e->curSubSong->chanCollapse[i]) {
|
||||
ImGui::SameLine();
|
||||
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)) {
|
||||
e->song.pat[i].effectCols--;
|
||||
if (e->song.pat[i].effectCols<1) e->song.pat[i].effectCols=1;
|
||||
e->curPat[i].effectCols--;
|
||||
if (e->curPat[i].effectCols<1) e->curPat[i].effectCols=1;
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginDisabled(e->song.pat[i].effectCols>=8);
|
||||
ImGui::BeginDisabled(e->curPat[i].effectCols>=8);
|
||||
snprintf(chanID,2048,">##_RCH%d",i);
|
||||
if (ImGui::SmallButton(chanID)) {
|
||||
e->song.pat[i].effectCols++;
|
||||
if (e->song.pat[i].effectCols>8) e->song.pat[i].effectCols=8;
|
||||
e->curPat[i].effectCols++;
|
||||
if (e->curPat[i].effectCols>8) e->curPat[i].effectCols=8;
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
@ -555,10 +555,10 @@ void FurnaceGUI::drawPattern() {
|
|||
ImGui::BeginDisabled();
|
||||
if (settings.viewPrevPattern) {
|
||||
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++) {
|
||||
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 {
|
||||
for (int i=0; i<dummyRows-1; i++) {
|
||||
|
@ -569,16 +569,16 @@ void FurnaceGUI::drawPattern() {
|
|||
ImGui::EndDisabled();
|
||||
// active area
|
||||
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);
|
||||
}
|
||||
// next pattern
|
||||
ImGui::BeginDisabled();
|
||||
if (settings.viewPrevPattern) {
|
||||
if ((ord+1)<e->song.ordersLen) for (int i=0; i<chans; i++) {
|
||||
patCache[i]=e->song.pat[i].getPattern(e->song.orders.ord[i][ord+1],true);
|
||||
if ((ord+1)<e->curSubSong->ordersLen) for (int i=0; i<chans; i++) {
|
||||
patCache[i]=e->curPat[i].getPattern(e->curOrders->ord[i][ord+1],true);
|
||||
}
|
||||
for (int i=0; i<=dummyRows; i++) {
|
||||
patternRow(i,e->isPlaying(),lineHeight,chans,ord+1,patCache,true);
|
||||
|
@ -612,7 +612,7 @@ void FurnaceGUI::drawPattern() {
|
|||
if (curOrder>0) {
|
||||
setOrder(curOrder-1);
|
||||
ImGui::SetScrollY(ImGui::GetScrollMaxY());
|
||||
updateScroll(e->song.patLen);
|
||||
updateScroll(e->curSubSong->patLen);
|
||||
}
|
||||
haveHitBounds=false;
|
||||
} else {
|
||||
|
@ -624,7 +624,7 @@ void FurnaceGUI::drawPattern() {
|
|||
} else {
|
||||
if (ImGui::GetScrollY()>=ImGui::GetScrollMaxY()) {
|
||||
if (haveHitBounds) {
|
||||
if (curOrder<(e->song.ordersLen-1)) {
|
||||
if (curOrder<(e->curSubSong->ordersLen-1)) {
|
||||
setOrder(curOrder+1);
|
||||
ImGui::SetScrollY(0);
|
||||
updateScroll(0);
|
||||
|
@ -776,7 +776,7 @@ void FurnaceGUI::drawPattern() {
|
|||
// note slides
|
||||
ImVec2 arrowPoints[7];
|
||||
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);
|
||||
if (ch->portaSpeed>0) {
|
||||
ImVec4 col=uiColors[GUI_COLOR_PATTERN_EFFECT_PITCH];
|
||||
|
|
|
@ -1495,6 +1495,7 @@ void FurnaceGUI::drawSettings() {
|
|||
UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_INS_LIST);
|
||||
UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_INS_EDIT);
|
||||
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_WAVE_LIST);
|
||||
UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_WAVE_EDIT);
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
#include "gui.h"
|
||||
#include "imgui.h"
|
||||
#include "misc/cpp/imgui_stdlib.h"
|
||||
#include "intConst.h"
|
||||
|
||||
|
@ -74,28 +75,28 @@ void FurnaceGUI::drawSongInfo() {
|
|||
ImGui::TableNextColumn();
|
||||
float avail=ImGui::GetContentRegionAvail().x;
|
||||
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 (realTB<1) realTB=1;
|
||||
if (realTB>16) realTB=16;
|
||||
e->song.timeBase=realTB-1;
|
||||
e->curSubSong->timeBase=realTB-1;
|
||||
}
|
||||
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::TableNextColumn();
|
||||
ImGui::Text("Speed");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::SetNextItemWidth(avail);
|
||||
if (ImGui::InputScalar("##Speed1",ImGuiDataType_U8,&e->song.speed1,&_ONE,&_THREE)) { MARK_MODIFIED
|
||||
if (e->song.speed1<1) e->song.speed1=1;
|
||||
if (ImGui::InputScalar("##Speed1",ImGuiDataType_U8,&e->curSubSong->speed1,&_ONE,&_THREE)) { MARK_MODIFIED
|
||||
if (e->curSubSong->speed1<1) e->curSubSong->speed1=1;
|
||||
if (e->isPlaying()) play();
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::SetNextItemWidth(avail);
|
||||
if (ImGui::InputScalar("##Speed2",ImGuiDataType_U8,&e->song.speed2,&_ONE,&_THREE)) { MARK_MODIFIED
|
||||
if (e->song.speed2<1) e->song.speed2=1;
|
||||
if (ImGui::InputScalar("##Speed2",ImGuiDataType_U8,&e->curSubSong->speed2,&_ONE,&_THREE)) { MARK_MODIFIED
|
||||
if (e->curSubSong->speed2<1) e->curSubSong->speed2=1;
|
||||
if (e->isPlaying()) play();
|
||||
}
|
||||
|
||||
|
@ -104,12 +105,12 @@ void FurnaceGUI::drawSongInfo() {
|
|||
ImGui::Text("Highlight");
|
||||
ImGui::TableNextColumn();
|
||||
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;
|
||||
}
|
||||
ImGui::TableNextColumn();
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -118,11 +119,11 @@ void FurnaceGUI::drawSongInfo() {
|
|||
ImGui::Text("Pattern Length");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::SetNextItemWidth(avail);
|
||||
int patLen=e->song.patLen;
|
||||
int patLen=e->curSubSong->patLen;
|
||||
if (ImGui::InputInt("##PatLength",&patLen,1,3)) { MARK_MODIFIED
|
||||
if (patLen<1) patLen=1;
|
||||
if (patLen>256) patLen=256;
|
||||
e->song.patLen=patLen;
|
||||
e->curSubSong->patLen=patLen;
|
||||
}
|
||||
|
||||
ImGui::TableNextRow();
|
||||
|
@ -130,11 +131,11 @@ void FurnaceGUI::drawSongInfo() {
|
|||
ImGui::Text("Song Length");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::SetNextItemWidth(avail);
|
||||
int ordLen=e->song.ordersLen;
|
||||
int ordLen=e->curSubSong->ordersLen;
|
||||
if (ImGui::InputInt("##OrdLength",&ordLen,1,3)) { MARK_MODIFIED
|
||||
if (ordLen<1) ordLen=1;
|
||||
if (ordLen>256) ordLen=256;
|
||||
e->song.ordersLen=ordLen;
|
||||
e->curSubSong->ordersLen=ordLen;
|
||||
if (curOrder>=ordLen) {
|
||||
setOrder(ordLen-1);
|
||||
}
|
||||
|
@ -147,7 +148,7 @@ void FurnaceGUI::drawSongInfo() {
|
|||
}
|
||||
ImGui::TableNextColumn();
|
||||
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 (tempoView) setHz/=2.5;
|
||||
if (setHz<10) setHz=10;
|
||||
|
@ -156,13 +157,13 @@ void FurnaceGUI::drawSongInfo() {
|
|||
}
|
||||
if (tempoView) {
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("= %gHz",e->song.hz);
|
||||
ImGui::Text("= %gHz",e->curSubSong->hz);
|
||||
} 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::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::Text("NTSC");
|
||||
}
|
||||
|
|
94
src/gui/subSongs.cpp
Normal file
94
src/gui/subSongs.cpp
Normal 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();
|
||||
}
|
Loading…
Reference in a new issue