-----____ | -----____ | FFFFF -----____ | FFFFFFFFF TTTTTTT | | FF TTTTTTTT | | FF TT | | FF TT | | FFFFFFFFF TT | | FFFFFFFFF TT | | FF TT | | FF TT | | FF TT | | FF TT | | FF __________------| |---------
This commit is contained in:
parent
d8471ce937
commit
f20da6b202
|
@ -58,6 +58,7 @@
|
|||
#define DIV_VERSION_MOD 0xff01
|
||||
#define DIV_VERSION_FC 0xff02
|
||||
#define DIV_VERSION_S3M 0xff03
|
||||
#define DIV_VERSION_FTM 0xff04
|
||||
|
||||
// "Namco C163"
|
||||
#define DIV_C163_DEFAULT_NAME "Namco 163"
|
||||
|
|
|
@ -4061,12 +4061,58 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
|
|||
|
||||
#define CHECK_BLOCK_VERSION(x) \
|
||||
if (blockVersion>x) { \
|
||||
logE("incompatible block version %d for %s!",blockVersion,blockName); \
|
||||
lastError="incompatible block version"; \
|
||||
delete[] file; \
|
||||
return false; \
|
||||
logW("incompatible block version %d for %s!",blockVersion,blockName); \
|
||||
}
|
||||
|
||||
const int ftEffectMap[]={
|
||||
-1, // none
|
||||
0x0f,
|
||||
0x0b,
|
||||
0x0d,
|
||||
0xff,
|
||||
-1, // volume? not supported in Furnace yet
|
||||
0x03,
|
||||
0x03, // unused?
|
||||
0x13,
|
||||
0x14,
|
||||
0x00,
|
||||
0x04,
|
||||
0x07,
|
||||
0xe5,
|
||||
0xed,
|
||||
0x11,
|
||||
0x01, // porta up
|
||||
0x02, // porta down
|
||||
0x12,
|
||||
0x90, // sample offset - not supported yet
|
||||
0xe1,
|
||||
0xe2,
|
||||
0x0a,
|
||||
0xec,
|
||||
0x0c,
|
||||
-1, // delayed volume - not supported yet
|
||||
0x11, // FDS
|
||||
0x12,
|
||||
0x13,
|
||||
0x20, // DPCM pitch
|
||||
0x22, // 5B
|
||||
0x24,
|
||||
0x23,
|
||||
0x21,
|
||||
-1, // VRC7 "custom patch port" - not supported?
|
||||
-1, // VRC7 "custom patch write"
|
||||
-1, // release - not supported yet
|
||||
0x09, // select groove
|
||||
-1, // transpose - not supported
|
||||
0x10, // Namco 163
|
||||
-1, // FDS vol env - not supported
|
||||
-1, // FDS auto FM - not supported yet
|
||||
-1, // phase reset - not supported
|
||||
-1, // harmonic - not supported
|
||||
};
|
||||
|
||||
constexpr int ftEffectMapSize=sizeof(ftEffectMap)/sizeof(int);
|
||||
|
||||
bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
||||
SafeReader reader=SafeReader(file,len);
|
||||
warnings="";
|
||||
|
@ -4078,6 +4124,9 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
unsigned int n163Chans=0;
|
||||
bool hasSequence[256][8];
|
||||
unsigned char sequenceIndex[256][8];
|
||||
unsigned int hilightA=4;
|
||||
unsigned int hilightB=16;
|
||||
double customHz=60;
|
||||
|
||||
memset(hasSequence,0,256*8*sizeof(bool));
|
||||
memset(sequenceIndex,0,256*8);
|
||||
|
@ -4098,6 +4147,14 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
return false;
|
||||
}
|
||||
|
||||
for (DivSubSong* i: ds.subsong) {
|
||||
i->clearData();
|
||||
delete i;
|
||||
}
|
||||
ds.subsong.clear();
|
||||
|
||||
ds.linearPitch=0;
|
||||
|
||||
while (true) {
|
||||
blockName=reader.readString(3);
|
||||
if (blockName=="END") {
|
||||
|
@ -4115,7 +4172,8 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
|
||||
logD("reading block %s (version %d, %d bytes)",blockName,blockVersion,blockSize);
|
||||
if (blockName=="PARAMS") {
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
// versions 7-9 don't change anything?
|
||||
CHECK_BLOCK_VERSION(9);
|
||||
unsigned int oldSpeedTempo=0;
|
||||
if (blockVersion<=1) {
|
||||
oldSpeedTempo=reader.readI();
|
||||
|
@ -4125,15 +4183,32 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
}
|
||||
tchans=reader.readI();
|
||||
unsigned int pal=reader.readI();
|
||||
unsigned int customHz=reader.readI();
|
||||
if (blockVersion>=7) {
|
||||
// advanced Hz control
|
||||
int controlType=reader.readI();
|
||||
switch (controlType) {
|
||||
case 1:
|
||||
customHz=1000000.0/(double)reader.readI();
|
||||
break;
|
||||
default:
|
||||
reader.readI();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
customHz=reader.readI();
|
||||
}
|
||||
unsigned int newVibrato=0;
|
||||
bool sweepReset=false;
|
||||
unsigned int speedSplitPoint=0;
|
||||
if (blockVersion>=3) {
|
||||
newVibrato=reader.readI();
|
||||
}
|
||||
if (blockVersion>=4) {
|
||||
ds.subsong[0]->hilightA=reader.readI();
|
||||
ds.subsong[0]->hilightB=reader.readI();
|
||||
if (blockVersion>=9) {
|
||||
sweepReset=reader.readI();
|
||||
}
|
||||
if (blockVersion>=4 && blockVersion<7) {
|
||||
hilightA=reader.readI();
|
||||
hilightB=reader.readI();
|
||||
}
|
||||
if (expansions&8) if (blockVersion>=5) { // N163 channels
|
||||
n163Chans=reader.readI();
|
||||
|
@ -4142,20 +4217,24 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
speedSplitPoint=reader.readI();
|
||||
}
|
||||
|
||||
if (blockVersion>=8) {
|
||||
int fineTuneCents=reader.readC()*100;
|
||||
fineTuneCents+=reader.readC();
|
||||
|
||||
ds.tuning=440.0*pow(2.0,(double)fineTuneCents/1200.0);
|
||||
}
|
||||
|
||||
logV("old speed/tempo: %d",oldSpeedTempo);
|
||||
logV("expansions: %x",expansions);
|
||||
logV("channels: %d",tchans);
|
||||
logV("PAL: %d",pal);
|
||||
logV("custom Hz: %d",customHz);
|
||||
logV("custom Hz: %f",customHz);
|
||||
logV("new vibrato: %d",newVibrato);
|
||||
logV("N163 channels: %d",n163Chans);
|
||||
logV("highlight 1: %d",ds.subsong[0]->hilightA);
|
||||
logV("highlight 2: %d",ds.subsong[0]->hilightB);
|
||||
logV("highlight 1: %d",hilightA);
|
||||
logV("highlight 2: %d",hilightB);
|
||||
logV("split point: %d",speedSplitPoint);
|
||||
|
||||
if (customHz!=0) {
|
||||
ds.subsong[0]->hz=customHz;
|
||||
}
|
||||
logV("sweep reset: %d",sweepReset);
|
||||
|
||||
// initialize channels
|
||||
int systemID=0;
|
||||
|
@ -4200,28 +4279,46 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
CHECK_BLOCK_VERSION(1);
|
||||
ds.name=reader.readString(32);
|
||||
ds.author=reader.readString(32);
|
||||
ds.copyright=reader.readString(32);
|
||||
ds.category=reader.readString(32);
|
||||
ds.systemName="NES";
|
||||
} else if (blockName=="HEADER") {
|
||||
CHECK_BLOCK_VERSION(3);
|
||||
CHECK_BLOCK_VERSION(4);
|
||||
unsigned char totalSongs=reader.readC();
|
||||
logV("%d songs:",totalSongs+1);
|
||||
for (int i=0; i<=totalSongs; i++) {
|
||||
String subSongName=reader.readString();
|
||||
ds.subsong.push_back(new DivSubSong);
|
||||
ds.subsong[i]->name=subSongName;
|
||||
ds.subsong[i]->hilightA=hilightA;
|
||||
ds.subsong[i]->hilightB=hilightB;
|
||||
if (customHz!=0) {
|
||||
ds.subsong[i]->hz=customHz;
|
||||
}
|
||||
logV("- %s",subSongName);
|
||||
}
|
||||
for (unsigned int i=0; i<tchans; i++) {
|
||||
// TODO: obey channel ID
|
||||
unsigned char chID=reader.readC();
|
||||
logV("for channel ID %d",chID);
|
||||
for (int j=0; j<=totalSongs; j++) {
|
||||
unsigned char effectCols=reader.readC();
|
||||
if (j==0) {
|
||||
ds.subsong[0]->pat[i].effectCols=effectCols+1;
|
||||
}
|
||||
ds.subsong[j]->pat[i].effectCols=effectCols+1;
|
||||
logV("- song %d has %d effect columns",j,effectCols);
|
||||
}
|
||||
}
|
||||
|
||||
if (blockVersion>=4) {
|
||||
for (int i=0; i<=totalSongs; i++) {
|
||||
ds.subsong[i]->hilightA=(unsigned char)reader.readC();
|
||||
ds.subsong[i]->hilightB=(unsigned char)reader.readC();
|
||||
}
|
||||
}
|
||||
} else if (blockName=="INSTRUMENTS") {
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
|
||||
reader.seek(blockSize,SEEK_CUR);
|
||||
|
||||
/*
|
||||
ds.insLen=reader.readI();
|
||||
if (ds.insLen<0 || ds.insLen>256) {
|
||||
logE("too many instruments/out of range!");
|
||||
|
@ -4381,21 +4478,131 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
ins->name=reader.readString((unsigned int)reader.readI());
|
||||
logV("- %d: %s",insIndex,ins->name);
|
||||
}
|
||||
*/
|
||||
} else if (blockName=="SEQUENCES") {
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
reader.seek(blockSize,SEEK_CUR);
|
||||
} else if (blockName=="FRAMES") {
|
||||
CHECK_BLOCK_VERSION(3);
|
||||
|
||||
for (size_t i=0; i<ds.subsong.size(); i++) {
|
||||
DivSubSong* s=ds.subsong[i];
|
||||
|
||||
s->ordersLen=reader.readI();
|
||||
if (blockVersion>=3) {
|
||||
s->speeds.val[0]=reader.readI();
|
||||
}
|
||||
if (blockVersion>=2) {
|
||||
s->virtualTempoN=reader.readI();
|
||||
s->patLen=reader.readI();
|
||||
}
|
||||
int why=tchans;
|
||||
if (blockVersion==1) {
|
||||
why=reader.readI();
|
||||
}
|
||||
logV("reading %d and %d orders",tchans,s->ordersLen);
|
||||
|
||||
for (int j=0; j<s->ordersLen; j++) {
|
||||
for (int k=0; k<why; k++) {
|
||||
unsigned char o=reader.readC();
|
||||
logV("%.2x",o);
|
||||
s->orders.ord[k][j]=o;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (blockName=="PATTERNS") {
|
||||
CHECK_BLOCK_VERSION(5);
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
|
||||
size_t blockEnd=reader.tell()+blockSize;
|
||||
|
||||
if (blockVersion==1) {
|
||||
int patLenOld=reader.readI();
|
||||
for (DivSubSong* i: ds.subsong) {
|
||||
i->patLen=patLenOld;
|
||||
}
|
||||
}
|
||||
|
||||
// so it appears .ftm doesn't keep track of how many patterns are stored in the file....
|
||||
while (reader.tell()<blockEnd) {
|
||||
int subs=0;
|
||||
if (blockVersion>=2) subs=reader.readI();
|
||||
int ch=reader.readI();
|
||||
int patNum=reader.readI();
|
||||
int numRows=reader.readI();
|
||||
|
||||
DivPattern* pat=ds.subsong[subs]->pat[ch].getPattern(patNum,true);
|
||||
for (int i=0; i<numRows; i++) {
|
||||
unsigned int row=0;
|
||||
if (blockVersion>=2 && blockVersion<6) { // row index
|
||||
row=reader.readI();
|
||||
} else {
|
||||
row=reader.readC();
|
||||
}
|
||||
|
||||
unsigned char nextNote=reader.readC();
|
||||
unsigned char nextOctave=reader.readC();
|
||||
if (nextNote==0x0d) {
|
||||
pat->data[row][0]=100;
|
||||
} else if (nextNote==0x0e) {
|
||||
pat->data[row][0]=101;
|
||||
} else if (nextNote==0x01) {
|
||||
pat->data[row][0]=12;
|
||||
pat->data[row][1]=nextOctave-1;
|
||||
} else if (nextNote==0) {
|
||||
pat->data[row][0]=0;
|
||||
} else if (nextNote<0x0d) {
|
||||
pat->data[row][0]=nextNote-1;
|
||||
pat->data[row][1]=nextOctave;
|
||||
}
|
||||
|
||||
unsigned char nextIns=reader.readC();
|
||||
if (nextIns<0x40) {
|
||||
pat->data[row][2]=nextIns;
|
||||
} else {
|
||||
pat->data[row][2]=-1;
|
||||
}
|
||||
|
||||
unsigned char nextVol=reader.readC();
|
||||
if (nextVol<0x10) {
|
||||
pat->data[row][3]=nextVol;
|
||||
} else {
|
||||
pat->data[row][3]=-1;
|
||||
}
|
||||
|
||||
int effectCols=ds.subsong[subs]->pat[ch].effectCols;
|
||||
if (blockVersion>=6) effectCols=4;
|
||||
|
||||
for (int j=0; j<effectCols; j++) {
|
||||
unsigned char nextEffect=reader.readC();
|
||||
unsigned char nextEffectVal=0;
|
||||
if (nextEffect!=0 || blockVersion<6) nextEffectVal=reader.readC();
|
||||
if (nextEffect==0 && nextEffectVal==0) {
|
||||
pat->data[row][4+(j*2)]=-1;
|
||||
pat->data[row][5+(j*2)]=-1;
|
||||
} else {
|
||||
if (nextEffect<ftEffectMapSize) {
|
||||
pat->data[row][4+(j*2)]=ftEffectMap[nextEffect];
|
||||
} else {
|
||||
pat->data[row][4+(j*2)]=-1;
|
||||
}
|
||||
pat->data[row][5+(j*2)]=nextEffectVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (blockName=="DPCM SAMPLES") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
reader.seek(blockSize,SEEK_CUR);
|
||||
} else if (blockName=="SEQUENCES_VRC6") {
|
||||
// where are the 5B and FDS sequences?
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
reader.seek(blockSize,SEEK_CUR);
|
||||
} else if (blockName=="SEQUENCES_N163") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
reader.seek(blockSize,SEEK_CUR);
|
||||
} else if (blockName=="COMMENTS") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
reader.seek(blockSize,SEEK_CUR);
|
||||
} else {
|
||||
logE("block %s is unknown!",blockName);
|
||||
lastError="unknown block "+blockName;
|
||||
|
@ -4410,6 +4617,27 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
addWarning("FamiTracker import is experimental!");
|
||||
|
||||
ds.version=DIV_VERSION_FTM;
|
||||
|
||||
if (active) quitDispatch();
|
||||
BUSY_BEGIN_SOFT;
|
||||
saveLock.lock();
|
||||
song.unload();
|
||||
song=ds;
|
||||
changeSong(0);
|
||||
recalcChans();
|
||||
saveLock.unlock();
|
||||
BUSY_END;
|
||||
if (active) {
|
||||
initDispatch();
|
||||
BUSY_BEGIN;
|
||||
renderSamples();
|
||||
reset();
|
||||
BUSY_END;
|
||||
}
|
||||
} catch (EndOfFileException& e) {
|
||||
logE("premature end of file!");
|
||||
lastError="incomplete file";
|
||||
|
|
|
@ -236,10 +236,14 @@ String SafeReader::readString(size_t stlen) {
|
|||
#endif
|
||||
size_t curPos=0;
|
||||
if (isEOF()) throw EndOfFileException(this, len);
|
||||
bool zero=false;
|
||||
|
||||
while (!isEOF() && curPos<stlen) {
|
||||
unsigned char c=readC();
|
||||
if (c!=0) ret.push_back(c);
|
||||
if (c==0) {
|
||||
zero=true;
|
||||
}
|
||||
if (!zero) ret.push_back(c);
|
||||
curPos++;
|
||||
}
|
||||
return ret;
|
||||
|
|
Loading…
Reference in New Issue