dev95 - multiple songs in a single file (READ)
experimental feature! proceed with caution. if you experience song corruption or crashes, report issue immediately! files with multiple songs will be readable in older versions of Furnace, but only the first song will be read in those versions. issue #199
This commit is contained in:
parent
14053f70cb
commit
c5786b61fb
24 changed files with 974 additions and 545 deletions
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue