-----____
|        -----____
|  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:
tildearrow 2023-03-25 03:55:42 -05:00
parent d8471ce937
commit f20da6b202
3 changed files with 256 additions and 23 deletions

View file

@ -58,6 +58,7 @@
#define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02 #define DIV_VERSION_FC 0xff02
#define DIV_VERSION_S3M 0xff03 #define DIV_VERSION_S3M 0xff03
#define DIV_VERSION_FTM 0xff04
// "Namco C163" // "Namco C163"
#define DIV_C163_DEFAULT_NAME "Namco 163" #define DIV_C163_DEFAULT_NAME "Namco 163"

View file

@ -4061,12 +4061,58 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
#define CHECK_BLOCK_VERSION(x) \ #define CHECK_BLOCK_VERSION(x) \
if (blockVersion>x) { \ if (blockVersion>x) { \
logE("incompatible block version %d for %s!",blockVersion,blockName); \ logW("incompatible block version %d for %s!",blockVersion,blockName); \
lastError="incompatible block version"; \
delete[] file; \
return false; \
} }
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) { bool DivEngine::loadFTM(unsigned char* file, size_t len) {
SafeReader reader=SafeReader(file,len); SafeReader reader=SafeReader(file,len);
warnings=""; warnings="";
@ -4078,6 +4124,9 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
unsigned int n163Chans=0; unsigned int n163Chans=0;
bool hasSequence[256][8]; bool hasSequence[256][8];
unsigned char sequenceIndex[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(hasSequence,0,256*8*sizeof(bool));
memset(sequenceIndex,0,256*8); memset(sequenceIndex,0,256*8);
@ -4098,6 +4147,14 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
return false; return false;
} }
for (DivSubSong* i: ds.subsong) {
i->clearData();
delete i;
}
ds.subsong.clear();
ds.linearPitch=0;
while (true) { while (true) {
blockName=reader.readString(3); blockName=reader.readString(3);
if (blockName=="END") { 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); logD("reading block %s (version %d, %d bytes)",blockName,blockVersion,blockSize);
if (blockName=="PARAMS") { if (blockName=="PARAMS") {
CHECK_BLOCK_VERSION(6); // versions 7-9 don't change anything?
CHECK_BLOCK_VERSION(9);
unsigned int oldSpeedTempo=0; unsigned int oldSpeedTempo=0;
if (blockVersion<=1) { if (blockVersion<=1) {
oldSpeedTempo=reader.readI(); oldSpeedTempo=reader.readI();
@ -4125,15 +4183,32 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
} }
tchans=reader.readI(); tchans=reader.readI();
unsigned int pal=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; unsigned int newVibrato=0;
bool sweepReset=false;
unsigned int speedSplitPoint=0; unsigned int speedSplitPoint=0;
if (blockVersion>=3) { if (blockVersion>=3) {
newVibrato=reader.readI(); newVibrato=reader.readI();
} }
if (blockVersion>=4) { if (blockVersion>=9) {
ds.subsong[0]->hilightA=reader.readI(); sweepReset=reader.readI();
ds.subsong[0]->hilightB=reader.readI(); }
if (blockVersion>=4 && blockVersion<7) {
hilightA=reader.readI();
hilightB=reader.readI();
} }
if (expansions&8) if (blockVersion>=5) { // N163 channels if (expansions&8) if (blockVersion>=5) { // N163 channels
n163Chans=reader.readI(); n163Chans=reader.readI();
@ -4142,20 +4217,24 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
speedSplitPoint=reader.readI(); 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("old speed/tempo: %d",oldSpeedTempo);
logV("expansions: %x",expansions); logV("expansions: %x",expansions);
logV("channels: %d",tchans); logV("channels: %d",tchans);
logV("PAL: %d",pal); logV("PAL: %d",pal);
logV("custom Hz: %d",customHz); logV("custom Hz: %f",customHz);
logV("new vibrato: %d",newVibrato); logV("new vibrato: %d",newVibrato);
logV("N163 channels: %d",n163Chans); logV("N163 channels: %d",n163Chans);
logV("highlight 1: %d",ds.subsong[0]->hilightA); logV("highlight 1: %d",hilightA);
logV("highlight 2: %d",ds.subsong[0]->hilightB); logV("highlight 2: %d",hilightB);
logV("split point: %d",speedSplitPoint); logV("split point: %d",speedSplitPoint);
logV("sweep reset: %d",sweepReset);
if (customHz!=0) {
ds.subsong[0]->hz=customHz;
}
// initialize channels // initialize channels
int systemID=0; int systemID=0;
@ -4200,28 +4279,46 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
CHECK_BLOCK_VERSION(1); CHECK_BLOCK_VERSION(1);
ds.name=reader.readString(32); ds.name=reader.readString(32);
ds.author=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") { } else if (blockName=="HEADER") {
CHECK_BLOCK_VERSION(3); CHECK_BLOCK_VERSION(4);
unsigned char totalSongs=reader.readC(); unsigned char totalSongs=reader.readC();
logV("%d songs:",totalSongs+1); logV("%d songs:",totalSongs+1);
for (int i=0; i<=totalSongs; i++) { for (int i=0; i<=totalSongs; i++) {
String subSongName=reader.readString(); 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); logV("- %s",subSongName);
} }
for (unsigned int i=0; i<tchans; i++) { for (unsigned int i=0; i<tchans; i++) {
// TODO: obey channel ID
unsigned char chID=reader.readC(); unsigned char chID=reader.readC();
logV("for channel ID %d",chID); logV("for channel ID %d",chID);
for (int j=0; j<=totalSongs; j++) { for (int j=0; j<=totalSongs; j++) {
unsigned char effectCols=reader.readC(); unsigned char effectCols=reader.readC();
if (j==0) { ds.subsong[j]->pat[i].effectCols=effectCols+1;
ds.subsong[0]->pat[i].effectCols=effectCols+1;
}
logV("- song %d has %d effect columns",j,effectCols); 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") { } else if (blockName=="INSTRUMENTS") {
CHECK_BLOCK_VERSION(6); CHECK_BLOCK_VERSION(6);
reader.seek(blockSize,SEEK_CUR);
/*
ds.insLen=reader.readI(); ds.insLen=reader.readI();
if (ds.insLen<0 || ds.insLen>256) { if (ds.insLen<0 || ds.insLen>256) {
logE("too many instruments/out of range!"); 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()); ins->name=reader.readString((unsigned int)reader.readI());
logV("- %d: %s",insIndex,ins->name); logV("- %d: %s",insIndex,ins->name);
} }
*/
} else if (blockName=="SEQUENCES") { } else if (blockName=="SEQUENCES") {
CHECK_BLOCK_VERSION(6); CHECK_BLOCK_VERSION(6);
reader.seek(blockSize,SEEK_CUR);
} else if (blockName=="FRAMES") { } else if (blockName=="FRAMES") {
CHECK_BLOCK_VERSION(3); 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") { } 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") { } else if (blockName=="DPCM SAMPLES") {
CHECK_BLOCK_VERSION(1); CHECK_BLOCK_VERSION(1);
reader.seek(blockSize,SEEK_CUR);
} else if (blockName=="SEQUENCES_VRC6") { } else if (blockName=="SEQUENCES_VRC6") {
// where are the 5B and FDS sequences? // where are the 5B and FDS sequences?
CHECK_BLOCK_VERSION(6); CHECK_BLOCK_VERSION(6);
reader.seek(blockSize,SEEK_CUR);
} else if (blockName=="SEQUENCES_N163") { } else if (blockName=="SEQUENCES_N163") {
CHECK_BLOCK_VERSION(1); CHECK_BLOCK_VERSION(1);
reader.seek(blockSize,SEEK_CUR);
} else if (blockName=="COMMENTS") { } else if (blockName=="COMMENTS") {
CHECK_BLOCK_VERSION(1); CHECK_BLOCK_VERSION(1);
reader.seek(blockSize,SEEK_CUR);
} else { } else {
logE("block %s is unknown!",blockName); logE("block %s is unknown!",blockName);
lastError="unknown block "+blockName; lastError="unknown block "+blockName;
@ -4410,6 +4617,27 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
return false; 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) { } catch (EndOfFileException& e) {
logE("premature end of file!"); logE("premature end of file!");
lastError="incomplete file"; lastError="incomplete file";

View file

@ -236,10 +236,14 @@ String SafeReader::readString(size_t stlen) {
#endif #endif
size_t curPos=0; size_t curPos=0;
if (isEOF()) throw EndOfFileException(this, len); if (isEOF()) throw EndOfFileException(this, len);
bool zero=false;
while (!isEOF() && curPos<stlen) { while (!isEOF() && curPos<stlen) {
unsigned char c=readC(); unsigned char c=readC();
if (c!=0) ret.push_back(c); if (c==0) {
zero=true;
}
if (!zero) ret.push_back(c);
curPos++; curPos++;
} }
return ret; return ret;