dev95 - multiple songs in a single file (READ)

experimental feature! proceed with caution.
if you experience song corruption or crashes, report issue immediately!

files with multiple songs will be readable in older versions of Furnace,
but only the first song will be read in those versions.

issue #199
This commit is contained in:
tildearrow 2022-05-15 01:42:49 -05:00
parent 14053f70cb
commit c5786b61fb
24 changed files with 974 additions and 545 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;
@ -1659,10 +1709,10 @@ void DivEngine::delInstrument(int index) {
song.insLen=song.ins.size();
for (int i=0; i<chans; i++) {
for (int j=0; j<256; j++) {
if (song.pat[i].data[j]==NULL) continue;
for (int k=0; k<song.patLen; k++) {
if (song.pat[i].data[j]->data[k][2]>index) {
song.pat[i].data[j]->data[k][2]--;
if (curPat[i].data[j]==NULL) continue;
for (int k=0; k<curSubSong->patLen; k++) {
if (curPat[i].data[j]->data[k][2]>index) {
curPat[i].data[j]->data[k][2]--;
}
}
}
@ -2048,18 +2098,18 @@ void DivEngine::delSample(int index) {
void DivEngine::addOrder(bool duplicate, bool where) {
unsigned char order[DIV_MAX_CHANS];
if (song.ordersLen>=0xff) return;
if (curSubSong->ordersLen>=0xff) return;
BUSY_BEGIN_SOFT;
if (duplicate) {
for (int i=0; i<DIV_MAX_CHANS; i++) {
order[i]=song.orders.ord[i][curOrder];
order[i]=curOrders->ord[i][curOrder];
}
} else {
bool used[256];
for (int i=0; i<chans; i++) {
memset(used,0,sizeof(bool)*256);
for (int j=0; j<song.ordersLen; j++) {
used[song.orders.ord[i][j]]=true;
for (int j=0; j<curSubSong->ordersLen; j++) {
used[curOrders->ord[i][j]]=true;
}
order[i]=0xff;
for (int j=0; j<256; j++) {
@ -2073,19 +2123,19 @@ void DivEngine::addOrder(bool duplicate, bool where) {
if (where) { // at the end
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
song.orders.ord[i][song.ordersLen]=order[i];
curOrders->ord[i][curSubSong->ordersLen]=order[i];
}
song.ordersLen++;
curSubSong->ordersLen++;
saveLock.unlock();
} else { // after current order
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
for (int j=song.ordersLen; j>curOrder; j--) {
song.orders.ord[i][j]=song.orders.ord[i][j-1];
for (int j=curSubSong->ordersLen; j>curOrder; j--) {
curOrders->ord[i][j]=curOrders->ord[i][j-1];
}
song.orders.ord[i][curOrder+1]=order[i];
curOrders->ord[i][curOrder+1]=order[i];
}
song.ordersLen++;
curSubSong->ordersLen++;
saveLock.unlock();
curOrder++;
if (playing && !freelance) {
@ -2097,21 +2147,21 @@ void DivEngine::addOrder(bool duplicate, bool where) {
void DivEngine::deepCloneOrder(bool where) {
unsigned char order[DIV_MAX_CHANS];
if (song.ordersLen>=0xff) return;
if (curSubSong->ordersLen>=0xff) return;
warnings="";
BUSY_BEGIN_SOFT;
for (int i=0; i<chans; i++) {
bool didNotFind=true;
logD("channel %d",i);
order[i]=song.orders.ord[i][curOrder];
order[i]=curOrders->ord[i][curOrder];
// find free slot
for (int j=0; j<256; j++) {
logD("finding free slot in %d...",j);
if (song.pat[i].data[j]==NULL) {
if (curPat[i].data[j]==NULL) {
int origOrd=order[i];
order[i]=j;
DivPattern* oldPat=song.pat[i].getPattern(origOrd,false);
DivPattern* pat=song.pat[i].getPattern(j,true);
DivPattern* oldPat=curPat[i].getPattern(origOrd,false);
DivPattern* pat=curPat[i].getPattern(j,true);
memcpy(pat->data,oldPat->data,256*32*sizeof(short));
logD("found at %d",j);
didNotFind=false;
@ -2125,19 +2175,19 @@ void DivEngine::deepCloneOrder(bool where) {
if (where) { // at the end
saveLock.lock();
for (int i=0; i<chans; i++) {
song.orders.ord[i][song.ordersLen]=order[i];
curOrders->ord[i][curSubSong->ordersLen]=order[i];
}
song.ordersLen++;
curSubSong->ordersLen++;
saveLock.unlock();
} else { // after current order
saveLock.lock();
for (int i=0; i<chans; i++) {
for (int j=song.ordersLen; j>curOrder; j--) {
song.orders.ord[i][j]=song.orders.ord[i][j-1];
for (int j=curSubSong->ordersLen; j>curOrder; j--) {
curOrders->ord[i][j]=curOrders->ord[i][j-1];
}
song.orders.ord[i][curOrder+1]=order[i];
curOrders->ord[i][curOrder+1]=order[i];
}
song.ordersLen++;
curSubSong->ordersLen++;
saveLock.unlock();
curOrder++;
if (playing && !freelance) {
@ -2148,17 +2198,17 @@ void DivEngine::deepCloneOrder(bool where) {
}
void DivEngine::deleteOrder() {
if (song.ordersLen<=1) return;
if (curSubSong->ordersLen<=1) return;
BUSY_BEGIN_SOFT;
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
for (int j=curOrder; j<song.ordersLen; j++) {
song.orders.ord[i][j]=song.orders.ord[i][j+1];
for (int j=curOrder; j<curSubSong->ordersLen; j++) {
curOrders->ord[i][j]=curOrders->ord[i][j+1];
}
}
song.ordersLen--;
curSubSong->ordersLen--;
saveLock.unlock();
if (curOrder>=song.ordersLen) curOrder=song.ordersLen-1;
if (curOrder>=curSubSong->ordersLen) curOrder=curSubSong->ordersLen-1;
if (playing && !freelance) {
playSub(false);
}
@ -2173,9 +2223,9 @@ void DivEngine::moveOrderUp() {
}
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder-1];
song.orders.ord[i][curOrder-1]^=song.orders.ord[i][curOrder];
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder-1];
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder-1];
curOrders->ord[i][curOrder-1]^=curOrders->ord[i][curOrder];
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder-1];
}
saveLock.unlock();
curOrder--;
@ -2187,15 +2237,15 @@ void DivEngine::moveOrderUp() {
void DivEngine::moveOrderDown() {
BUSY_BEGIN_SOFT;
if (curOrder>=song.ordersLen-1) {
if (curOrder>=curSubSong->ordersLen-1) {
BUSY_END;
return;
}
saveLock.lock();
for (int i=0; i<DIV_MAX_CHANS; i++) {
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder+1];
song.orders.ord[i][curOrder+1]^=song.orders.ord[i][curOrder];
song.orders.ord[i][curOrder]^=song.orders.ord[i][curOrder+1];
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder+1];
curOrders->ord[i][curOrder+1]^=curOrders->ord[i][curOrder];
curOrders->ord[i][curOrder]^=curOrders->ord[i][curOrder+1];
}
saveLock.unlock();
curOrder++;
@ -2208,12 +2258,12 @@ void DivEngine::moveOrderDown() {
void DivEngine::exchangeIns(int one, int two) {
for (int i=0; i<chans; i++) {
for (int j=0; j<256; j++) {
if (song.pat[i].data[j]==NULL) continue;
for (int k=0; k<song.patLen; k++) {
if (song.pat[i].data[j]->data[k][2]==one) {
song.pat[i].data[j]->data[k][2]=two;
} else if (song.pat[i].data[j]->data[k][2]==two) {
song.pat[i].data[j]->data[k][2]=one;
if (curPat[i].data[j]==NULL) continue;
for (int k=0; k<curSubSong->patLen; k++) {
if (curPat[i].data[j]->data[k][2]==one) {
curPat[i].data[j]->data[k][2]=two;
} else if (curPat[i].data[j]->data[k][2]==two) {
curPat[i].data[j]->data[k][2]=one;
}
}
}
@ -2417,7 +2467,7 @@ void DivEngine::autoNoteOffAll() {
void DivEngine::setOrder(unsigned char order) {
BUSY_BEGIN_SOFT;
curOrder=order;
if (order>=song.ordersLen) curOrder=0;
if (order>=curSubSong->ordersLen) curOrder=0;
if (playing && !freelance) {
playSub(false);
}
@ -2440,15 +2490,15 @@ void DivEngine::setSysFlags(int system, unsigned int flags, bool restart) {
void DivEngine::setSongRate(float hz, bool pal) {
BUSY_BEGIN;
saveLock.lock();
song.pal=!pal;
song.hz=hz;
curSubSong->pal=!pal;
curSubSong->hz=hz;
// what?
song.customTempo=true;
curSubSong->customTempo=true;
divider=60;
if (song.customTempo) {
divider=song.hz;
if (curSubSong->customTempo) {
divider=curSubSong->hz;
} else {
if (song.pal) {
if (curSubSong->pal) {
divider=60;
} else {
divider=50;
@ -2891,5 +2941,6 @@ bool DivEngine::quit() {
if (yrw801ROM!=NULL) delete[] yrw801ROM;
if (tg100ROM!=NULL) delete[] tg100ROM;
if (mu5ROM!=NULL) delete[] mu5ROM;
song.unload();
return true;
}

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;
@ -411,8 +412,14 @@ class DivEngine {
void swapChannels(int src, int dest);
void stompChannel(int ch);
// change song (UNSAFE)
void changeSong(size_t songIndex);
public:
DivSong song;
DivOrders* curOrders;
DivChannelData* curPat;
DivSubSong* curSubSong;
DivInstrument* tempIns;
DivSystem sysOfChan[DIV_MAX_CHANS];
int dispatchOfChan[DIV_MAX_CHANS];
@ -603,6 +610,9 @@ class DivEngine {
// get current row
int getRow();
// get current subsong
size_t getCurrentSubSong();
// get speed 1
unsigned char getSpeed1();
@ -799,6 +809,15 @@ class DivEngine {
// public swap channels
void swapChannelsP(int src, int dest);
// public change song
void changeSongP(size_t index);
// add subsong
int addSubSong();
// remove subsong
bool removeSubSong(int index);
// change system
void changeSystem(int index, DivSystem which, bool preserveOrder=true);
@ -900,6 +919,7 @@ class DivEngine {
curOrder(0),
remainingLoops(-1),
nextSpeed(3),
curSubSongIndex(0),
divider(60),
cycles(0),
clockDrift(0),
@ -935,6 +955,8 @@ class DivEngine {
metroAmp(0.0f),
metroVol(1.0f),
totalProcessed(0),
curOrders(NULL),
curPat(NULL),
tempIns(NULL),
oscBuf{NULL,NULL},
oscSize(1),
@ -959,6 +981,8 @@ class DivEngine {
sysFileMapFur[i]=DIV_SYSTEM_NULL;
sysFileMapDMF[i]=DIV_SYSTEM_NULL;
}
changeSong(0);
}
};
#endif

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();
ds.subsong[0]->timeBase=reader.readC();
ds.subsong[0]->speed1=reader.readC();
if (ds.version>0x07) {
ds.speed2=reader.readC();
ds.pal=reader.readC();
ds.hz=(ds.pal)?60:50;
ds.customTempo=reader.readC();
ds.subsong[0]->speed2=reader.readC();
ds.subsong[0]->pal=reader.readC();
ds.subsong[0]->hz=(ds.subsong[0]->pal)?60:50;
ds.subsong[0]->customTempo=reader.readC();
} else {
ds.speed2=ds.speed1;
ds.subsong[0]->speed2=ds.subsong[0]->speed1;
}
if (ds.version>0x0a) {
String hz=reader.readString(3);
if (ds.customTempo) {
if (ds.subsong[0]->customTempo) {
try {
ds.hz=std::stoi(hz);
ds.subsong[0]->hz=std::stoi(hz);
} catch (std::exception& e) {
logW("invalid custom Hz!");
ds.hz=60;
ds.subsong[0]->hz=60;
}
}
}
if (ds.version>0x17) {
ds.patLen=reader.readI();
ds.subsong[0]->patLen=reader.readI();
} else {
ds.patLen=(unsigned char)reader.readC();
ds.subsong[0]->patLen=(unsigned char)reader.readC();
}
ds.ordersLen=(unsigned char)reader.readC();
ds.subsong[0]->ordersLen=(unsigned char)reader.readC();
if (ds.patLen<0) {
if (ds.subsong[0]->patLen<0) {
logE("pattern length is negative!");
lastError="pattern lengrh is negative!";
delete[] file;
return false;
}
if (ds.patLen>256) {
if (ds.subsong[0]->patLen>256) {
logE("pattern length is too large!");
lastError="pattern length is too large!";
delete[] file;
return false;
}
if (ds.ordersLen<0) {
if (ds.subsong[0]->ordersLen<0) {
logE("song length is negative!");
lastError="song length is negative!";
delete[] file;
return false;
}
if (ds.ordersLen>127) {
if (ds.subsong[0]->ordersLen>127) {
logE("song is too long!");
lastError="song is too long!";
delete[] file;
@ -242,54 +242,54 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
}
if (ds.version<20 && ds.version>3) {
ds.arpLen=reader.readC();
ds.subsong[0]->arpLen=reader.readC();
} else {
ds.arpLen=1;
ds.subsong[0]->arpLen=1;
}
if (ds.system[0]==DIV_SYSTEM_YMU759) {
switch (ds.timeBase) {
switch (ds.subsong[0]->timeBase) {
case 0:
ds.hz=248;
ds.subsong[0]->hz=248;
break;
case 1:
ds.hz=200;
ds.subsong[0]->hz=200;
break;
case 2:
ds.hz=100;
ds.subsong[0]->hz=100;
break;
case 3:
ds.hz=50;
ds.subsong[0]->hz=50;
break;
case 4:
ds.hz=25;
ds.subsong[0]->hz=25;
break;
case 5:
ds.hz=20;
ds.subsong[0]->hz=20;
break;
default:
ds.hz=248;
ds.subsong[0]->hz=248;
break;
}
ds.customTempo=true;
ds.timeBase=0;
ds.subsong[0]->customTempo=true;
ds.subsong[0]->timeBase=0;
addWarning("Yamaha YMU759 emulation is incomplete! please migrate your song to the OPL3 system.");
}
logV("%x",reader.tell());
logI("reading pattern matrix (%d * %d = %d)...",ds.ordersLen,getChannelCount(ds.system[0]),ds.ordersLen*getChannelCount(ds.system[0]));
logI("reading pattern matrix (%d * %d = %d)...",ds.subsong[0]->ordersLen,getChannelCount(ds.system[0]),ds.subsong[0]->ordersLen*getChannelCount(ds.system[0]));
for (int i=0; i<getChannelCount(ds.system[0]); i++) {
for (int j=0; j<ds.ordersLen; j++) {
ds.orders.ord[i][j]=reader.readC();
if (ds.orders.ord[i][j]>0x7f) {
logE("order at %d, %d out of range! (%d)",i,j,ds.orders.ord[i][j]);
lastError=fmt::sprintf("order at %d, %d out of range! (%d)",i,j,ds.orders.ord[i][j]);
for (int j=0; j<ds.subsong[0]->ordersLen; j++) {
ds.subsong[0]->orders.ord[i][j]=reader.readC();
if (ds.subsong[0]->orders.ord[i][j]>0x7f) {
logE("order at %d, %d out of range! (%d)",i,j,ds.subsong[0]->orders.ord[i][j]);
lastError=fmt::sprintf("order at %d, %d out of range! (%d)",i,j,ds.subsong[0]->orders.ord[i][j]);
delete[] file;
return false;
}
if (ds.version>0x18) { // 1.1 pattern names
ds.pat[i].getPattern(j,true)->name=reader.readString((unsigned char)reader.readC());
ds.subsong[0]->pat[i].getPattern(j,true)->name=reader.readString((unsigned char)reader.readC());
}
}
if (ds.version>0x03 && ds.version<0x06 && i<16) {
@ -637,9 +637,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
logV("%x",reader.tell());
logI("reading patterns (%d channels, %d orders)...",getChannelCount(ds.system[0]),ds.ordersLen);
logI("reading patterns (%d channels, %d orders)...",getChannelCount(ds.system[0]),ds.subsong[0]->ordersLen);
for (int i=0; i<getChannelCount(ds.system[0]); i++) {
DivChannelData& chan=ds.pat[i];
DivChannelData& chan=ds.subsong[0]->pat[i];
if (ds.version<0x0a) {
chan.effectCols=1;
} else {
@ -652,10 +652,10 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
delete[] file;
return false;
}
for (int j=0; j<ds.ordersLen; j++) {
DivPattern* pat=chan.getPattern(ds.orders.ord[i][j],true);
for (int j=0; j<ds.subsong[0]->ordersLen; j++) {
DivPattern* pat=chan.getPattern(ds.subsong[0]->orders.ord[i][j],true);
if (ds.version>0x08) { // current pattern format
for (int k=0; k<ds.patLen; k++) {
for (int k=0; k<ds.subsong[0]->patLen; k++) {
// note
pat->data[k][0]=reader.readS();
// octave
@ -723,7 +723,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
}
} else { // historic pattern format
if (i<16) pat->data[0][2]=historicColIns[i];
for (int k=0; k<ds.patLen; k++) {
for (int k=0; k<ds.subsong[0]->patLen; k++) {
// note
pat->data[k][0]=reader.readC();
// octave
@ -905,6 +905,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
saveLock.lock();
song.unload();
song=ds;
changeSong(0);
recalcChans();
saveLock.unlock();
BUSY_END;
@ -929,13 +930,16 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
unsigned int insPtr[256];
unsigned int wavePtr[256];
unsigned int samplePtr[256];
unsigned int subSongPtr[256];
std::vector<int> patPtr;
int numberOfSubSongs=0;
char magic[5];
memset(magic,0,5);
SafeReader reader=SafeReader(file,len);
warnings="";
try {
DivSong ds;
DivSubSong* subSong=ds.subsong[0];
if (!reader.seek(16,SEEK_SET)) {
logE("premature end of file!");
@ -1045,44 +1049,44 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
}
reader.readI();
ds.timeBase=reader.readC();
ds.speed1=reader.readC();
ds.speed2=reader.readC();
ds.arpLen=reader.readC();
ds.hz=reader.readF();
ds.pal=(ds.hz>=53);
ds.customTempo=true;
subSong->timeBase=reader.readC();
subSong->speed1=reader.readC();
subSong->speed2=reader.readC();
subSong->arpLen=reader.readC();
subSong->hz=reader.readF();
subSong->pal=(subSong->hz>=53);
subSong->customTempo=true;
ds.patLen=reader.readS();
ds.ordersLen=reader.readS();
subSong->patLen=reader.readS();
subSong->ordersLen=reader.readS();
ds.hilightA=reader.readC();
ds.hilightB=reader.readC();
subSong->hilightA=reader.readC();
subSong->hilightB=reader.readC();
ds.insLen=reader.readS();
ds.waveLen=reader.readS();
ds.sampleLen=reader.readS();
int numberOfPats=reader.readI();
if (ds.patLen<0) {
if (subSong->patLen<0) {
logE("pattern length is negative!");
lastError="pattern lengrh is negative!";
delete[] file;
return false;
}
if (ds.patLen>256) {
if (subSong->patLen>256) {
logE("pattern length is too large!");
lastError="pattern length is too large!";
delete[] file;
return false;
}
if (ds.ordersLen<0) {
if (subSong->ordersLen<0) {
logE("song length is negative!");
lastError="song length is negative!";
delete[] file;
return false;
}
if (ds.ordersLen>256) {
if (subSong->ordersLen>256) {
logE("song is too long!");
lastError="song is too long!";
delete[] file;
@ -1295,18 +1299,18 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
reader.read(samplePtr,ds.sampleLen*4);
for (int i=0; i<numberOfPats; i++) patPtr.push_back(reader.readI());
logD("reading orders (%d)...",ds.ordersLen);
logD("reading orders (%d)...",subSong->ordersLen);
for (int i=0; i<tchans; i++) {
for (int j=0; j<ds.ordersLen; j++) {
ds.orders.ord[i][j]=reader.readC();
for (int j=0; j<subSong->ordersLen; j++) {
subSong->orders.ord[i][j]=reader.readC();
}
}
for (int i=0; i<tchans; i++) {
ds.pat[i].effectCols=reader.readC();
if (ds.pat[i].effectCols<1 || ds.pat[i].effectCols>8) {
logE("channel %d has zero or too many effect columns! (%d)",i,ds.pat[i].effectCols);
lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,ds.pat[i].effectCols);
subSong->pat[i].effectCols=reader.readC();
if (subSong->pat[i].effectCols<1 || subSong->pat[i].effectCols>8) {
logE("channel %d has zero or too many effect columns! (%d)",i,subSong->pat[i].effectCols);
lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,subSong->pat[i].effectCols);
delete[] file;
return false;
}
@ -1314,25 +1318,25 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (ds.version>=39) {
for (int i=0; i<tchans; i++) {
ds.chanShow[i]=reader.readC();
subSong->chanShow[i]=reader.readC();
}
for (int i=0; i<tchans; i++) {
ds.chanCollapse[i]=reader.readC();
subSong->chanCollapse[i]=reader.readC();
}
if (ds.version<92) {
for (int i=0; i<tchans; i++) {
if (ds.chanCollapse[i]>0) ds.chanCollapse[i]=3;
if (subSong->chanCollapse[i]>0) subSong->chanCollapse[i]=3;
}
}
for (int i=0; i<tchans; i++) {
ds.chanName[i]=reader.readString();
subSong->chanName[i]=reader.readString();
}
for (int i=0; i<tchans; i++) {
ds.chanShortName[i]=reader.readString();
subSong->chanShortName[i]=reader.readString();
}
ds.notes=reader.readString();
@ -1405,6 +1409,88 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
}
}
// subsongs
if (ds.version>=95) {
subSong->name=reader.readString();
subSong->notes=reader.readString();
numberOfSubSongs=(unsigned char)reader.readC();
reader.readC(); // reserved
reader.readC();
reader.readC();
// pointers
for (int i=0; i<numberOfSubSongs; i++) {
subSongPtr[i]=reader.readI();
}
for (int i=0; i<numberOfSubSongs; i++) {
ds.subsong.push_back(new DivSubSong);
if (!reader.seek(subSongPtr[i],SEEK_SET)) {
logE("couldn't seek to subsong %d!",i+1);
lastError=fmt::sprintf("couldn't seek to subsong %d!",i+1);
ds.unload();
delete[] file;
return false;
}
reader.read(magic,4);
if (strcmp(magic,"SONG")!=0) {
logE("%d: invalid subsong header!",i);
lastError="invalid subsong header!";
ds.unload();
delete[] file;
return false;
}
reader.readI();
subSong=ds.subsong[i+1];
subSong->timeBase=reader.readC();
subSong->speed1=reader.readC();
subSong->speed2=reader.readC();
subSong->arpLen=reader.readC();
subSong->hz=reader.readF();
subSong->pal=(subSong->hz>=53);
subSong->customTempo=true;
subSong->patLen=reader.readS();
subSong->ordersLen=reader.readS();
subSong->hilightA=reader.readC();
subSong->hilightB=reader.readC();
reader.readI(); // reserved
subSong->name=reader.readString();
subSong->notes=reader.readString();
logD("reading orders of subsong %d (%d)...",i+1,subSong->ordersLen);
for (int j=0; j<tchans; j++) {
for (int k=0; k<subSong->ordersLen; k++) {
subSong->orders.ord[j][k]=reader.readC();
}
}
for (int i=0; i<tchans; i++) {
subSong->pat[i].effectCols=reader.readC();
}
for (int i=0; i<tchans; i++) {
subSong->chanShow[i]=reader.readC();
}
for (int i=0; i<tchans; i++) {
subSong->chanCollapse[i]=reader.readC();
}
for (int i=0; i<tchans; i++) {
subSong->chanName[i]=reader.readString();
}
for (int i=0; i<tchans; i++) {
subSong->chanShortName[i]=reader.readString();
}
}
}
// read instruments
for (int i=0; i<ds.insLen; i++) {
DivInstrument* ins=new DivInstrument;
@ -1568,9 +1654,15 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
int chan=reader.readS();
int index=reader.readS();
reader.readI();
int subs=0;
if (ds.version>=95) {
subs=reader.readS();
} else {
reader.readS();
}
reader.readS();
logD("- %d, %d",chan,index);
logD("- %d, %d, %d",subs,chan,index);
if (chan<0 || chan>=tchans) {
logE("pattern channel out of range!",i);
@ -1586,14 +1678,21 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
delete[] file;
return false;
}
if (subs<0 || subs>=(int)ds.subsong.size()) {
logE("pattern subsong out of range!",i);
lastError="pattern subsong out of range!";
ds.unload();
delete[] file;
return false;
}
DivPattern* pat=ds.pat[chan].getPattern(index,true);
for (int j=0; j<ds.patLen; j++) {
DivPattern* pat=ds.subsong[subs]->pat[chan].getPattern(index,true);
for (int j=0; j<ds.subsong[subs]->patLen; j++) {
pat->data[j][0]=reader.readS();
pat->data[j][1]=reader.readS();
pat->data[j][2]=reader.readS();
pat->data[j][3]=reader.readS();
for (int k=0; k<ds.pat[chan].effectCols; k++) {
for (int k=0; k<ds.subsong[subs]->pat[chan].effectCols; k++) {
pat->data[j][4+(k<<1)]=reader.readS();
pat->data[j][5+(k<<1)]=reader.readS();
}
@ -1615,6 +1714,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
saveLock.lock();
song.unload();
song=ds;
changeSong(0);
recalcChans();
saveLock.unlock();
BUSY_END;
@ -1740,8 +1840,8 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
ds.sampleLen=ds.sample.size();
// orders
ds.ordersLen=ordCount=reader.readC();
if (ds.ordersLen<1 || ds.ordersLen>127) {
ds.subsong[0]->ordersLen=ordCount=reader.readC();
if (ds.subsong[0]->ordersLen<1 || ds.subsong[0]->ordersLen>127) {
logD("invalid order count!");
throw EndOfFileException(&reader,reader.tell());
}
@ -1761,7 +1861,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
unsigned char pat=reader.readC();
if (pat>patMax) patMax=pat;
for (int j=0; j<chCount; j++) {
ds.orders.ord[j][i]=pat;
ds.subsong[0]->orders.ord[j][i]=pat;
}
}
@ -1778,7 +1878,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
}
// patterns
ds.patLen=64;
ds.subsong[0]->patLen=64;
for (int ch=0; ch<chCount; ch++) {
for (int i=0; i<5; i++) {
fxUsage[ch][i]=false;
@ -1787,7 +1887,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
for (int pat=0; pat<=patMax; pat++) {
DivPattern* chpats[DIV_MAX_CHANS];
for (int ch=0; ch<chCount; ch++) {
chpats[ch]=ds.pat[ch].getPattern(pat,true);
chpats[ch]=ds.subsong[0]->pat[ch].getPattern(pat,true);
}
for (int row=0; row<64; row++) {
for (int ch=0; ch<chCount; ch++) {
@ -1860,7 +1960,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
for (int ch=0; ch<=chCount; ch++) {
unsigned char fxCols=1;
for (int pat=0; pat<=patMax; pat++) {
auto* data=ds.pat[ch].getPattern(pat,true)->data;
auto* data=ds.subsong[0]->pat[ch].getPattern(pat,true)->data;
short lastPitchEffect=-1;
short lastEffectState[5]={-1,-1,-1,-1,-1};
short setEffectState[5]={-1,-1,-1,-1,-1};
@ -1988,25 +2088,25 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
}
}
}
ds.pat[ch].effectCols=fxCols;
ds.subsong[0]->pat[ch].effectCols=fxCols;
}
ds.pal=false;
ds.hz=50;
ds.customTempo=false;
ds.subsong[0]->pal=false;
ds.subsong[0]->hz=50;
ds.subsong[0]->customTempo=false;
ds.systemLen=(chCount+3)/4;
for(int i=0; i<ds.systemLen; i++) {
ds.system[i]=DIV_SYSTEM_AMIGA;
ds.systemFlags[i]=1|(80<<8)|(bypassLimits?4:0)|((ds.systemLen>1 || bypassLimits)?2:0); // PAL
}
for(int i=0; i<chCount; i++) {
ds.chanShow[i]=true;
ds.chanName[i]=fmt::sprintf("Channel %d",i+1);
ds.chanShortName[i]=fmt::sprintf("C%d",i+1);
ds.subsong[0]->chanShow[i]=true;
ds.subsong[0]->chanName[i]=fmt::sprintf("Channel %d",i+1);
ds.subsong[0]->chanShortName[i]=fmt::sprintf("C%d",i+1);
}
for(int i=chCount; i<ds.systemLen*4; i++) {
ds.pat[i].effectCols=1;
ds.chanShow[i]=false;
ds.subsong[0]->pat[i].effectCols=1;
ds.subsong[0]->chanShow[i]=false;
}
// instrument creation
@ -2024,6 +2124,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
saveLock.lock();
song.unload();
song=ds;
changeSong(0);
recalcChans();
saveLock.unlock();
BUSY_END;
@ -2118,8 +2219,8 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
newVibrato=reader.readI();
}
if (blockVersion>=4) {
ds.hilightA=reader.readI();
ds.hilightB=reader.readI();
ds.subsong[0]->hilightA=reader.readI();
ds.subsong[0]->hilightB=reader.readI();
}
if (expansions&8) if (blockVersion>=5) { // N163 channels
n163Chans=reader.readI();
@ -2135,12 +2236,12 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
logV("custom Hz: %d",customHz);
logV("new vibrato: %d",newVibrato);
logV("N163 channels: %d",n163Chans);
logV("highlight 1: %d",ds.hilightA);
logV("highlight 2: %d",ds.hilightB);
logV("highlight 1: %d",ds.subsong[0]->hilightA);
logV("highlight 2: %d",ds.subsong[0]->hilightB);
logV("split point: %d",speedSplitPoint);
if (customHz!=0) {
ds.hz=customHz;
ds.subsong[0]->hz=customHz;
}
// initialize channels
@ -2201,7 +2302,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
for (int j=0; j<=totalSongs; j++) {
unsigned char effectCols=reader.readC();
if (j==0) {
ds.pat[i].effectCols=effectCols+1;
ds.subsong[0]->pat[i].effectCols=effectCols+1;
}
logV("- song %d has %d effect columns",j,effectCols);
}
@ -2532,26 +2633,39 @@ bool DivEngine::load(unsigned char* f, size_t slen) {
return false;
}
struct PatToWrite {
unsigned short subsong, chan, pat;
PatToWrite(unsigned short s, unsigned short c, unsigned short p):
subsong(s),
chan(c),
pat(p) {}
};
SafeWriter* DivEngine::saveFur(bool notPrimary) {
saveLock.lock();
std::vector<int> subSongPtr;
std::vector<int> insPtr;
std::vector<int> wavePtr;
std::vector<int> samplePtr;
std::vector<int> patPtr;
size_t ptrSeek;
size_t ptrSeek, subSongPtrSeek;
size_t subSongIndex=0;
DivSubSong* subSong=song.subsong[subSongIndex];
warnings="";
// fail if values are out of range
if (song.ordersLen>256) {
/*
if (subSong->ordersLen>256) {
logE("maximum song length is 256!");
lastError="maximum song length is 256";
return NULL;
}
if (song.patLen>256) {
if (subSong->patLen>256) {
logE("maximum pattern length is 256!");
lastError="maximum pattern length is 256";
return NULL;
}
*/
if (song.ins.size()>256) {
logE("maximum number of instruments is 256!");
lastError="maximum number of instruments is 256";
@ -2594,14 +2708,17 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
// high short is channel
// low short is pattern number
std::vector<int> patsToWrite;
std::vector<PatToWrite> patsToWrite;
bool alreadyAdded[256];
for (int i=0; i<chans; i++) {
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;
}
}
}
@ -2609,15 +2726,15 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->write("INFO",4);
w->writeI(0);
w->writeC(song.timeBase);
w->writeC(song.speed1);
w->writeC(song.speed2);
w->writeC(song.arpLen);
w->writeF(song.hz);
w->writeS(song.patLen);
w->writeS(song.ordersLen);
w->writeC(song.hilightA);
w->writeC(song.hilightB);
w->writeC(subSong->timeBase);
w->writeC(subSong->speed1);
w->writeC(subSong->speed2);
w->writeC(subSong->arpLen);
w->writeF(subSong->hz);
w->writeS(subSong->patLen);
w->writeS(subSong->ordersLen);
w->writeC(subSong->hilightA);
w->writeC(subSong->hilightB);
w->writeS(song.insLen);
w->writeS(song.waveLen);
w->writeS(song.sampleLen);
@ -2694,29 +2811,29 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
}
for (int i=0; i<chans; i++) {
for (int j=0; j<song.ordersLen; j++) {
w->writeC(song.orders.ord[i][j]);
for (int j=0; j<subSong->ordersLen; j++) {
w->writeC(subSong->orders.ord[i][j]);
}
}
for (int i=0; i<chans; i++) {
w->writeC(song.pat[i].effectCols);
w->writeC(subSong->pat[i].effectCols);
}
for (int i=0; i<chans; i++) {
w->writeC(song.chanShow[i]);
w->writeC(subSong->chanShow[i]);
}
for (int i=0; i<chans; i++) {
w->writeC(song.chanCollapse[i]);
w->writeC(subSong->chanCollapse[i]);
}
for (int i=0; i<chans; i++) {
w->writeString(song.chanName[i],false);
w->writeString(subSong->chanName[i],false);
}
for (int i=0; i<chans; i++) {
w->writeString(song.chanShortName[i],false);
w->writeString(subSong->chanShortName[i],false);
}
w->writeString(song.notes,false);
@ -2741,6 +2858,67 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
for (int i=0; i<18; i++) {
w->writeC(0);
}
// subsong list
w->writeString(subSong->name,false);
w->writeString(subSong->notes,false);
w->writeC((unsigned char)(song.subsong.size()-1));
w->writeC(0); // reserved
w->writeC(0);
w->writeC(0);
subSongPtrSeek=w->tell();
// subsong pointers (we'll seek here later)
for (size_t i=0; i<(song.subsong.size()-1); i++) {
w->writeI(0);
}
/// SUBSONGS
for (subSongIndex=1; 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++) {
@ -2776,23 +2954,24 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
}
/// PATTERN
for (int i: patsToWrite) {
DivPattern* pat=song.pat[i>>16].getPattern(i&0xffff,false);
for (PatToWrite& i: patsToWrite) {
DivPattern* pat=song.subsong[i.subsong]->pat[i.chan].getPattern(i.pat,false);
patPtr.push_back(w->tell());
w->write("PATR",4);
w->writeI(0);
w->writeS(i>>16);
w->writeS(i&0xffff);
w->writeS(i.chan);
w->writeS(i.pat);
w->writeS(i.subsong);
w->writeI(0); // reserved
w->writeS(0); // reserved
for (int j=0; j<song.patLen; j++) {
for (int j=0; j<song.subsong[i.subsong]->patLen; j++) {
w->writeS(pat->data[j][0]); // note
w->writeS(pat->data[j][1]); // octave
w->writeS(pat->data[j][2]); // instrument
w->writeS(pat->data[j][3]); // volume
w->write(&pat->data[j][4],2*song.pat[i>>16].effectCols*2); // effects
w->write(&pat->data[j][4],2*song.subsong[i.subsong]->pat[i.chan].effectCols*2); // effects
}
w->writeString(pat->name,false);
@ -2805,21 +2984,29 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeI(insPtr[i]);
}
// wavetable pointers (we'll seek here later)
// wavetable pointers
for (int i=0; i<song.waveLen; i++) {
w->writeI(wavePtr[i]);
}
// sample pointers (we'll seek here later)
// sample pointers
for (int i=0; i<song.sampleLen; i++) {
w->writeI(samplePtr[i]);
}
// pattern pointers (we'll seek here later)
// pattern pointers
for (int i: patPtr) {
w->writeI(i);
}
/// SUBSONG POINTERS
w->seek(subSongPtrSeek,SEEK_SET);
// subsong pointers
for (size_t i=0; i<(song.subsong.size()-1); i++) {
w->writeI(subSongPtr[i]);
}
saveLock.unlock();
return w;
}
@ -2890,7 +3077,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
return NULL;
}
// fail if values are out of range
if (song.ordersLen>127) {
if (curSubSong->ordersLen>127) {
logE("maximum .dmf song length is 127!");
lastError="maximum .dmf song length is 127";
return NULL;
@ -2906,10 +3093,10 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
return NULL;
}
for (int i=0; i<chans; i++) {
for (int j=0; j<song.ordersLen; j++) {
if (song.orders.ord[i][j]>0x7f) {
logE("order %d, %d is out of range (0-127)!",song.orders.ord[i][j]);
lastError=fmt::sprintf("order %d, %d is out of range (0-127)",song.orders.ord[i][j]);
for (int j=0; j<curSubSong->ordersLen; j++) {
if (curOrders->ord[i][j]>0x7f) {
logE("order %d, %d is out of range (0-127)!",curOrders->ord[i][j]);
lastError=fmt::sprintf("order %d, %d is out of range (0-127)",curOrders->ord[i][j]);
return NULL;
}
}
@ -2952,31 +3139,35 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
// song info
w->writeString(song.name,true);
w->writeString(song.author,true);
w->writeC(song.hilightA);
w->writeC(song.hilightB);
w->writeC(curSubSong->hilightA);
w->writeC(curSubSong->hilightB);
w->writeC(song.timeBase);
w->writeC(song.speed1);
w->writeC(song.speed2);
w->writeC(song.pal);
w->writeC(song.customTempo);
w->writeC(curSubSong->timeBase);
w->writeC(curSubSong->speed1);
w->writeC(curSubSong->speed2);
w->writeC(curSubSong->pal);
w->writeC(curSubSong->customTempo);
char customHz[4];
memset(customHz,0,4);
snprintf(customHz,4,"%d",(int)song.hz);
snprintf(customHz,4,"%d",(int)curSubSong->hz);
w->write(customHz,3);
w->writeI(song.patLen);
w->writeC(song.ordersLen);
w->writeI(curSubSong->patLen);
w->writeC(curSubSong->ordersLen);
for (int i=0; i<chans; i++) {
for (int j=0; j<song.ordersLen; j++) {
w->writeC(song.orders.ord[i][j]);
for (int j=0; j<curSubSong->ordersLen; j++) {
w->writeC(curOrders->ord[i][j]);
if (version>=25) {
DivPattern* pat=song.pat[i].getPattern(j,false);
DivPattern* pat=curPat[i].getPattern(j,false);
w->writeString(pat->name,true);
}
}
}
if (song.subsong.size()>1) {
addWarning("only the currently selected subsong will be saved");
}
if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) {
addWarning("absolute duty/cutoff macro not available in .dmf!");
addWarning("duty precision will be lost");
@ -3138,15 +3329,15 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
}
for (int i=0; i<getChannelCount(sys); i++) {
w->writeC(song.pat[i].effectCols);
w->writeC(curPat[i].effectCols);
for (int j=0; j<song.ordersLen; j++) {
DivPattern* pat=song.pat[i].getPattern(song.orders.ord[i][j],false);
for (int k=0; k<song.patLen; k++) {
for (int j=0; j<curSubSong->ordersLen; j++) {
DivPattern* pat=curPat[i].getPattern(curOrders->ord[i][j],false);
for (int k=0; k<curSubSong->patLen; k++) {
w->writeS(pat->data[k][0]); // note
w->writeS(pat->data[k][1]); // octave
w->writeS(pat->data[k][3]); // volume
w->write(&pat->data[k][4],2*song.pat[i].effectCols*2); // effects
w->write(&pat->data[k][4],2*curPat[i].effectCols*2); // effects
w->writeS(pat->data[k][2]); // instrument
}
}

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

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

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

View file

@ -110,8 +110,6 @@ enum DivSystem {
DIV_SYSTEM_DUMMY
};
extern DivOrders emptyOrders;
struct DivSubSong {
String name, notes;
unsigned char hilightA, hilightB;
@ -126,8 +124,23 @@ struct DivSubSong {
bool chanShow[DIV_MAX_CHANS];
unsigned char chanCollapse[DIV_MAX_CHANS];
String chanName[DIV_MAX_CHANS];
String chanShortName[DIV_MAX_CHANS];
DivSubSong() {
void clearData();
DivSubSong():
hilightA(4),
hilightB(16),
timeBase(0),
speed1(6),
speed2(6),
arpLen(1),
pal(true),
customTempo(false),
hz(60.0),
patLen(64),
ordersLen(1) {
for (int i=0; i<DIV_MAX_CHANS; i++) {
chanShow[i]=true;
chanCollapse[i]=0;
@ -317,19 +330,10 @@ struct DivSong {
String nameJ, authorJ, categoryJ;
// other things
String chanName[DIV_MAX_CHANS];
String chanShortName[DIV_MAX_CHANS];
String notes;
// highlight
unsigned char hilightA, hilightB;
// module details
unsigned char timeBase, speed1, speed2, arpLen;
bool pal;
bool customTempo;
float hz;
int patLen, ordersLen, insLen, waveLen, sampleLen;
int insLen, waveLen, sampleLen;
float masterVol;
float tuning;
@ -377,16 +381,11 @@ struct DivSong {
bool snDutyReset;
bool pitchMacroIsLinear;
DivOrders* orders;
std::vector<DivInstrument*> ins;
DivChannelData* pat;
std::vector<DivWavetable*> wave;
std::vector<DivSample*> sample;
std::vector<DivSubSong> subsongs;
bool* chanShow;
unsigned char* chanCollapse;
std::vector<DivSubSong*> subsong;
DivInstrument nullIns, nullInsOPLL, nullInsOPL, nullInsQSound;
DivWavetable nullWave;
@ -435,17 +434,6 @@ struct DivSong {
manInfo(""),
createdDate(""),
revisionDate(""),
hilightA(4),
hilightB(16),
timeBase(0),
speed1(6),
speed2(6),
arpLen(1),
pal(true),
customTempo(false),
hz(60.0),
patLen(64),
ordersLen(1),
insLen(0),
waveLen(0),
sampleLen(0),
@ -491,11 +479,7 @@ struct DivSong {
systemPan[i]=0;
systemFlags[i]=0;
}
subsongs.push_back(DivSubSong());
chanShow=subsongs[0].chanShow;
chanCollapse=subsongs[0].chanCollapse;
orders=&subsongs[0].orders;
pat=subsongs[0].pat;
subsong.push_back(new DivSubSong);
system[0]=DIV_SYSTEM_YM2612;
system[1]=DIV_SYSTEM_SMS;

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