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

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

View file

@ -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;
@ -1594,6 +1644,7 @@ void DivEngine::unmuteAll() {
}
int DivEngine::addInstrument(int refChan) {
if (song.ins.size()>=256) return -1;
BUSY_BEGIN;
DivInstrument* ins=new DivInstrument;
int insCount=(int)song.ins.size();
@ -1624,6 +1675,10 @@ int DivEngine::addInstrument(int refChan) {
}
int DivEngine::addInstrumentPtr(DivInstrument* which) {
if (song.ins.size()>=256) {
delete which;
return -1;
}
BUSY_BEGIN;
saveLock.lock();
song.ins.push_back(which);
@ -1654,10 +1709,10 @@ void DivEngine::delInstrument(int index) {
song.insLen=song.ins.size();
for (int i=0; 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]--;
}
}
}
@ -1668,6 +1723,7 @@ void DivEngine::delInstrument(int index) {
}
int DivEngine::addWave() {
if (song.wave.size()>=256) return -1;
BUSY_BEGIN;
saveLock.lock();
DivWavetable* wave=new DivWavetable;
@ -1680,37 +1736,48 @@ int DivEngine::addWave() {
}
bool DivEngine::addWaveFromFile(const char* path) {
if (song.wave.size()>=256) {
lastError="too many wavetables!";
return false;
}
FILE* f=ps_fopen(path,"rb");
if (f==NULL) {
lastError=fmt::sprintf("%s",strerror(errno));
return false;
}
unsigned char* buf;
ssize_t len;
if (fseek(f,0,SEEK_END)!=0) {
fclose(f);
lastError=fmt::sprintf("could not seek to end: %s",strerror(errno));
return false;
}
len=ftell(f);
if (len<0) {
fclose(f);
lastError=fmt::sprintf("could not determine file size: %s",strerror(errno));
return false;
}
if (len==(SIZE_MAX>>1)) {
fclose(f);
lastError="file size is invalid!";
return false;
}
if (len==0) {
fclose(f);
lastError="file is empty";
return false;
}
if (fseek(f,0,SEEK_SET)!=0) {
fclose(f);
lastError=fmt::sprintf("could not seek to beginning: %s",strerror(errno));
return false;
}
buf=new unsigned char[len];
if (fread(buf,1,len,f)!=(size_t)len) {
logW("did not read entire wavetable file buffer!");
delete[] buf;
lastError=fmt::sprintf("could not read entire file: %s",strerror(errno));
return false;
}
fclose(f);
@ -1790,6 +1857,7 @@ bool DivEngine::addWaveFromFile(const char* path) {
} catch (EndOfFileException& e) {
delete wave;
delete[] buf;
lastError="premature end of file";
return false;
}
@ -1816,6 +1884,7 @@ void DivEngine::delWave(int index) {
}
int DivEngine::addSample() {
if (song.sample.size()>=256) return -1;
BUSY_BEGIN;
saveLock.lock();
DivSample* sample=new DivSample;
@ -1830,6 +1899,10 @@ int DivEngine::addSample() {
}
int DivEngine::addSampleFromFile(const char* path) {
if (song.sample.size()>=256) {
lastError="too many samples!";
return -1;
}
BUSY_BEGIN;
warnings="";
@ -2025,18 +2098,18 @@ void DivEngine::delSample(int index) {
void DivEngine::addOrder(bool duplicate, bool where) {
unsigned char order[DIV_MAX_CHANS];
if (song.ordersLen>=0xff) return;
if (curSubSong->ordersLen>=0xff) return;
BUSY_BEGIN_SOFT;
if (duplicate) {
for (int i=0; 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++) {
@ -2050,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) {
@ -2074,21 +2147,21 @@ void DivEngine::addOrder(bool duplicate, bool where) {
void DivEngine::deepCloneOrder(bool where) {
unsigned char order[DIV_MAX_CHANS];
if (song.ordersLen>=0xff) return;
if (curSubSong->ordersLen>=0xff) return;
warnings="";
BUSY_BEGIN_SOFT;
for (int i=0; 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;
@ -2102,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) {
@ -2125,17 +2198,17 @@ void DivEngine::deepCloneOrder(bool where) {
}
void DivEngine::deleteOrder() {
if (song.ordersLen<=1) return;
if (curSubSong->ordersLen<=1) return;
BUSY_BEGIN_SOFT;
saveLock.lock();
for (int i=0; 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);
}
@ -2150,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--;
@ -2164,15 +2237,15 @@ void DivEngine::moveOrderUp() {
void DivEngine::moveOrderDown() {
BUSY_BEGIN_SOFT;
if (curOrder>=song.ordersLen-1) {
if (curOrder>=curSubSong->ordersLen-1) {
BUSY_END;
return;
}
saveLock.lock();
for (int i=0; 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++;
@ -2185,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;
}
}
}
@ -2394,7 +2467,7 @@ void DivEngine::autoNoteOffAll() {
void DivEngine::setOrder(unsigned char order) {
BUSY_BEGIN_SOFT;
curOrder=order;
if (order>=song.ordersLen) curOrder=0;
if (order>=curSubSong->ordersLen) curOrder=0;
if (playing && !freelance) {
playSub(false);
}
@ -2417,15 +2490,15 @@ void DivEngine::setSysFlags(int system, unsigned int flags, bool restart) {
void DivEngine::setSongRate(float hz, bool pal) {
BUSY_BEGIN;
saveLock.lock();
song.pal=!pal;
song.hz=hz;
curSubSong->pal=!pal;
curSubSong->hz=hz;
// what?
song.customTempo=true;
curSubSong->customTempo=true;
divider=60;
if (song.customTempo) {
divider=song.hz;
if (curSubSong->customTempo) {
divider=curSubSong->hz;
} else {
if (song.pal) {
if (curSubSong->pal) {
divider=60;
} else {
divider=50;
@ -2868,5 +2941,6 @@ bool DivEngine::quit() {
if (yrw801ROM!=NULL) delete[] yrw801ROM;
if (tg100ROM!=NULL) delete[] tg100ROM;
if (mu5ROM!=NULL) delete[] mu5ROM;
song.unload();
return true;
}

View file

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

View file

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

View file

@ -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));
}

View file

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

View file

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

View file

@ -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()) {

View file

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

View file

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

View file

@ -19,7 +19,7 @@
#include "song.h"
void DivSong::clearSongData() {
void DivSubSong::clearData() {
for (int i=0; i<DIV_MAX_CHANS; i++) {
pat[i].wipePatterns();
}
@ -28,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;
@ -71,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();
}

View file

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

View file

@ -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]];

View file

@ -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; \
} \

View file

@ -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();

View file

@ -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);

View file

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

View file

@ -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) {

View file

@ -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()) {

View file

@ -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);
}
}
@ -2047,7 +2047,7 @@ void FurnaceGUI::editOptions(bool topMenu) {
ImGui::PopFont();
ImGui::SameLine();
if (ImGui::Button("Set")) {
DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][curOrder],true);
DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],true);
latchIns=pat->data[cursor.y][2];
latchVol=pat->data[cursor.y][3];
latchEffect=pat->data[cursor.y][4];
@ -2833,12 +2833,12 @@ bool FurnaceGUI::loop() {
if (e->isPlaying()) {
int totalTicks=e->getTotalTicks();
int totalSeconds=e->getTotalSeconds();
ImGui::Text("| Speed %d:%d @ %gHz (%g BPM) | Order %d/%d | Row %d/%d | %d:%.2d:%.2d.%.2d",e->getSpeed1(),e->getSpeed2(),e->getCurHz(),calcBPM(e->getSpeed1(),e->getSpeed2(),e->getCurHz()),e->getOrder(),e->song.ordersLen,e->getRow(),e->song.patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000);
ImGui::Text("| Speed %d:%d @ %gHz (%g BPM) | Order %d/%d | Row %d/%d | %d:%.2d:%.2d.%.2d",e->getSpeed1(),e->getSpeed2(),e->getCurHz(),calcBPM(e->getSpeed1(),e->getSpeed2(),e->getCurHz()),e->getOrder(),e->curSubSong->ordersLen,e->getRow(),e->curSubSong->patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000);
} else {
bool hasInfo=false;
String info;
if (cursor.xCoarse>=0 && cursor.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) {
@ -2899,6 +2899,7 @@ bool FurnaceGUI::loop() {
ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0);
drawSubSongs();
drawPattern();
drawEditControls();
drawSongInfo();
@ -3177,8 +3178,11 @@ bool FurnaceGUI::loop() {
break;
}
case GUI_FILE_WAVE_OPEN:
e->addWaveFromFile(copyOfName.c_str());
MARK_MODIFIED;
if (!e->addWaveFromFile(copyOfName.c_str())) {
showError("cannot load wavetable! ("+e->getLastError()+")");
} else {
MARK_MODIFIED;
}
break;
case GUI_FILE_EXPORT_VGM: {
SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion);
@ -3504,7 +3508,7 @@ bool FurnaceGUI::loop() {
stop();
e->lockEngine([this]() {
for (int i=0; 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;
@ -3663,6 +3667,7 @@ bool FurnaceGUI::init() {
regViewOpen=e->getConfBool("regViewOpen",false);
logOpen=e->getConfBool("logOpen",false);
effectListOpen=e->getConfBool("effectListOpen",false);
subSongsOpen=e->getConfBool("subSongsOpen",true);
tempoView=e->getConfBool("tempoView",true);
waveHex=e->getConfBool("waveHex",false);
@ -3854,6 +3859,7 @@ bool FurnaceGUI::finish() {
e->setConf("regViewOpen",regViewOpen);
e->setConf("logOpen",logOpen);
e->setConf("effectListOpen",effectListOpen);
e->setConf("subSongsOpen",subSongsOpen);
// commit last window size
e->setConf("lastWindowWidth",scrW);
@ -3974,6 +3980,7 @@ FurnaceGUI::FurnaceGUI():
logOpen(false),
effectListOpen(false),
chanOscOpen(false),
subSongsOpen(true),
/*
editControlsDocked(false),
ordersDocked(false),
@ -4001,6 +4008,7 @@ FurnaceGUI::FurnaceGUI():
logDocked(false),
effectListDocked(false),
chanOscDocked(false),
subSongsDocked(false),
*/
selecting(false),
selectingFull(false),

View file

@ -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);

View file

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

View file

@ -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);

View file

@ -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];

View file

@ -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);

View file

@ -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
View file

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