preliminary Future Composer module loading
only loads patterns and doesn't deduplicate conversion required to fit in the Furnace format (no transpose ins/note) even the pattern loader itself isn't complete due to how different the format is
This commit is contained in:
parent
84c955058b
commit
588f3f737c
|
@ -50,6 +50,7 @@
|
||||||
|
|
||||||
// for imports
|
// for imports
|
||||||
#define DIV_VERSION_MOD 0xff01
|
#define DIV_VERSION_MOD 0xff01
|
||||||
|
#define DIV_VERSION_FC 0xff02
|
||||||
|
|
||||||
enum DivStatusView {
|
enum DivStatusView {
|
||||||
DIV_STATUS_NOTHING=0,
|
DIV_STATUS_NOTHING=0,
|
||||||
|
@ -397,6 +398,7 @@ class DivEngine {
|
||||||
bool loadFur(unsigned char* file, size_t len);
|
bool loadFur(unsigned char* file, size_t len);
|
||||||
bool loadMod(unsigned char* file, size_t len);
|
bool loadMod(unsigned char* file, size_t len);
|
||||||
bool loadFTM(unsigned char* file, size_t len);
|
bool loadFTM(unsigned char* file, size_t len);
|
||||||
|
bool loadFC(unsigned char* file, size_t len);
|
||||||
|
|
||||||
void loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
void loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||||
void loadTFI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
void loadTFI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
#define DIV_DMF_MAGIC ".DelekDefleMask."
|
#define DIV_DMF_MAGIC ".DelekDefleMask."
|
||||||
#define DIV_FUR_MAGIC "-Furnace module-"
|
#define DIV_FUR_MAGIC "-Furnace module-"
|
||||||
#define DIV_FTM_MAGIC "FamiTracker Module"
|
#define DIV_FTM_MAGIC "FamiTracker Module"
|
||||||
|
#define DIV_FC13_MAGIC "SMOD"
|
||||||
|
#define DIV_FC14_MAGIC "FC14"
|
||||||
|
|
||||||
struct InflateBlock {
|
struct InflateBlock {
|
||||||
unsigned char* buf;
|
unsigned char* buf;
|
||||||
|
@ -2259,6 +2261,232 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DivEngine::loadFC(unsigned char* file, size_t len) {
|
||||||
|
struct InvalidHeaderException {};
|
||||||
|
bool success=false;
|
||||||
|
char magic[4]={0,0,0,0};
|
||||||
|
SafeReader reader=SafeReader(file,len);
|
||||||
|
warnings="";
|
||||||
|
bool isFC14=false;
|
||||||
|
unsigned int patPtr, freqMacroPtr, volMacroPtr, samplePtr;
|
||||||
|
unsigned int seqLen, patLen, freqMacroLen, volMacroLen, sampleLen;
|
||||||
|
|
||||||
|
struct FCSequence {
|
||||||
|
unsigned char pat[4];
|
||||||
|
signed char transpose[4];
|
||||||
|
signed char offsetIns[4];
|
||||||
|
unsigned char speed;
|
||||||
|
};
|
||||||
|
std::vector<FCSequence> seq;
|
||||||
|
struct FCPattern {
|
||||||
|
unsigned char note[32];
|
||||||
|
unsigned char val[32];
|
||||||
|
};
|
||||||
|
std::vector<FCPattern> pat;
|
||||||
|
|
||||||
|
struct FCSample {
|
||||||
|
unsigned short loopLen, len, loopStart;
|
||||||
|
} sample[10];
|
||||||
|
|
||||||
|
try {
|
||||||
|
DivSong ds;
|
||||||
|
ds.tuning=436.0;
|
||||||
|
ds.version=DIV_VERSION_FC;
|
||||||
|
ds.linearPitch=0;
|
||||||
|
ds.noSlidesOnFirstTick=true;
|
||||||
|
ds.rowResetsArpPos=true;
|
||||||
|
ds.ignoreJumpAtEnd=false;
|
||||||
|
|
||||||
|
// load here
|
||||||
|
if (!reader.seek(0,SEEK_SET)) {
|
||||||
|
logE("premature end of file!");
|
||||||
|
lastError="incomplete file";
|
||||||
|
delete[] file;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
reader.read(magic,4);
|
||||||
|
|
||||||
|
if (memcmp(magic,DIV_FC13_MAGIC,4)==0) {
|
||||||
|
isFC14=false;
|
||||||
|
} else if (memcmp(magic,DIV_FC14_MAGIC,4)==0) {
|
||||||
|
isFC14=true;
|
||||||
|
} else {
|
||||||
|
logW("the magic isn't complete");
|
||||||
|
throw EndOfFileException(&reader,reader.tell());
|
||||||
|
}
|
||||||
|
|
||||||
|
ds.systemLen=1;
|
||||||
|
ds.system[0]=DIV_SYSTEM_DUMMY;
|
||||||
|
ds.systemVol[0]=64;
|
||||||
|
ds.systemPan[0]=0;
|
||||||
|
ds.systemFlags[0]=1|(80<<8); // PAL
|
||||||
|
ds.systemName="Amiga";
|
||||||
|
|
||||||
|
seqLen=reader.readI_BE();
|
||||||
|
if (seqLen%13) {
|
||||||
|
logW("sequence length is not multiple of 13 (%d)",seqLen);
|
||||||
|
//throw EndOfFileException(&reader,reader.tell());
|
||||||
|
}
|
||||||
|
patPtr=reader.readI_BE();
|
||||||
|
patLen=reader.readI_BE();
|
||||||
|
if (patLen%64) {
|
||||||
|
logW("pattern length is not multiple of 64 (%d)",patLen);
|
||||||
|
throw EndOfFileException(&reader,reader.tell());
|
||||||
|
}
|
||||||
|
freqMacroPtr=reader.readI_BE();
|
||||||
|
freqMacroLen=reader.readI_BE();
|
||||||
|
volMacroPtr=reader.readI_BE();
|
||||||
|
volMacroLen=reader.readI_BE();
|
||||||
|
samplePtr=reader.readI_BE();
|
||||||
|
if (isFC14) {
|
||||||
|
reader.readI_BE(); // wave len
|
||||||
|
} else {
|
||||||
|
sampleLen=reader.readI_BE();
|
||||||
|
}
|
||||||
|
|
||||||
|
logD("patPtr: %d",patPtr);
|
||||||
|
logD("patLen: %d",patLen);
|
||||||
|
logD("freqMacroPtr: %d",freqMacroPtr);
|
||||||
|
logD("freqMacroLen: %d",freqMacroLen);
|
||||||
|
logD("volMacroPtr: %d",volMacroPtr);
|
||||||
|
logD("volMacroLen: %d",volMacroLen);
|
||||||
|
logD("samplePtr: %d",samplePtr);
|
||||||
|
logD("sampleLen: %d",sampleLen);
|
||||||
|
|
||||||
|
// sample info
|
||||||
|
logD("samples:");
|
||||||
|
for (int i=0; i<10; i++) {
|
||||||
|
sample[i].loopLen=reader.readS_BE();
|
||||||
|
sample[i].len=reader.readS_BE();
|
||||||
|
sample[i].loopStart=reader.readS_BE();
|
||||||
|
|
||||||
|
logD("- %d: %d (%d, %d)",i,sample[i].len,sample[i].loopStart,sample[i].loopLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wavetable lengths
|
||||||
|
if (isFC14) for (int i=0; i<20; i++) {
|
||||||
|
reader.readS_BE();
|
||||||
|
reader.readS_BE();
|
||||||
|
}
|
||||||
|
|
||||||
|
// sequences
|
||||||
|
seqLen/=13;
|
||||||
|
logD("reading sequences... (%d)",seqLen);
|
||||||
|
for (unsigned int i=0; i<seqLen; i++) {
|
||||||
|
FCSequence s;
|
||||||
|
for (int j=0; j<4; j++) {
|
||||||
|
s.pat[j]=reader.readC();
|
||||||
|
s.transpose[j]=reader.readC();
|
||||||
|
s.offsetIns[j]=reader.readC();
|
||||||
|
}
|
||||||
|
s.speed=reader.readC();
|
||||||
|
seq.push_back(s);
|
||||||
|
logV(
|
||||||
|
"%.2x | %.2x%.2x%.2x %.2x%.2x%.2x %.2x%.2x%.2x %.2x%.2x%.2x | %.2x",
|
||||||
|
i,
|
||||||
|
s.pat[0],s.transpose[0],s.offsetIns[0],
|
||||||
|
s.pat[1],s.transpose[1],s.offsetIns[1],
|
||||||
|
s.pat[2],s.transpose[2],s.offsetIns[2],
|
||||||
|
s.pat[3],s.transpose[3],s.offsetIns[3],
|
||||||
|
s.speed
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// patterns
|
||||||
|
if (!reader.seek(patPtr,SEEK_SET)) {
|
||||||
|
logE("premature end of file!");
|
||||||
|
lastError="incomplete file";
|
||||||
|
delete[] file;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
patLen/=64;
|
||||||
|
logD("reading patterns... (%d)",patLen);
|
||||||
|
for (unsigned int i=0; i<patLen; i++) {
|
||||||
|
FCPattern p;
|
||||||
|
logV("- pattern %d",i);
|
||||||
|
for (int j=0; j<32; j++) {
|
||||||
|
p.note[j]=reader.readC();
|
||||||
|
p.val[j]=reader.readC();
|
||||||
|
//logV("%.2x | %.2x %.2x",j,p.note[j],p.val[j]);
|
||||||
|
}
|
||||||
|
pat.push_back(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: read the rest
|
||||||
|
|
||||||
|
// convert
|
||||||
|
ds.subsong[0]->ordersLen=seqLen;
|
||||||
|
ds.subsong[0]->patLen=32;
|
||||||
|
ds.subsong[0]->hz=50;
|
||||||
|
ds.subsong[0]->pal=true;
|
||||||
|
ds.subsong[0]->customTempo=true;
|
||||||
|
ds.subsong[0]->pat[3].effectCols=3;
|
||||||
|
ds.subsong[0]->speed1=3;
|
||||||
|
ds.subsong[0]->speed2=3;
|
||||||
|
|
||||||
|
for (unsigned int i=0; i<seqLen; i++) {
|
||||||
|
for (int j=0; j<4; j++) {
|
||||||
|
ds.subsong[0]->orders.ord[j][i]=i;
|
||||||
|
DivPattern* p=ds.subsong[0]->pat[j].getPattern(i,true);
|
||||||
|
if (j==3 && seq[i].speed) {
|
||||||
|
p->data[0][6]=0x09;
|
||||||
|
p->data[0][7]=seq[i].speed;
|
||||||
|
p->data[0][8]=0x0f;
|
||||||
|
p->data[0][9]=seq[i].speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int k=0; k<32; k++) {
|
||||||
|
FCPattern& fp=pat[seq[i].pat[j]];
|
||||||
|
if (fp.note[k]>0 && fp.note[k]<0x49) {
|
||||||
|
short note=(fp.note[k]+seq[i].transpose[j])%12;
|
||||||
|
short octave=2+((fp.note[k]+seq[i].transpose[j])/12);
|
||||||
|
if (fp.note[k]>=0x3d) octave-=6;
|
||||||
|
if (note==0) {
|
||||||
|
note=12;
|
||||||
|
octave--;
|
||||||
|
}
|
||||||
|
octave&=0xff;
|
||||||
|
p->data[k][0]=note;
|
||||||
|
p->data[k][1]=octave;
|
||||||
|
if (fp.val[k]) {
|
||||||
|
if (fp.val[k]&0xe0) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
p->data[k][2]=fp.val[k]-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
success=true;
|
||||||
|
} catch (EndOfFileException& e) {
|
||||||
|
//logE("premature end of file!");
|
||||||
|
lastError="incomplete file";
|
||||||
|
} catch (InvalidHeaderException& e) {
|
||||||
|
//logE("invalid header!");
|
||||||
|
lastError="invalid header!";
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
#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); \
|
logE("incompatible block version %d for %s!",blockVersion,blockName); \
|
||||||
|
@ -2731,6 +2959,8 @@ bool DivEngine::load(unsigned char* f, size_t slen) {
|
||||||
return loadFTM(file,len);
|
return loadFTM(file,len);
|
||||||
} else if (memcmp(file,DIV_FUR_MAGIC,16)==0) {
|
} else if (memcmp(file,DIV_FUR_MAGIC,16)==0) {
|
||||||
return loadFur(file,len);
|
return loadFur(file,len);
|
||||||
|
} else if (memcmp(file,DIV_FC13_MAGIC,4)==0 || memcmp(file,DIV_FC14_MAGIC,4)==0) {
|
||||||
|
return loadFC(file,len);
|
||||||
}
|
}
|
||||||
|
|
||||||
// step 3: try loading as .mod
|
// step 3: try loading as .mod
|
||||||
|
|
|
@ -1226,9 +1226,9 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
|
||||||
if (!dirExists(workingDirSong)) workingDirSong=getHomeDir();
|
if (!dirExists(workingDirSong)) workingDirSong=getHomeDir();
|
||||||
hasOpened=fileDialog->openLoad(
|
hasOpened=fileDialog->openLoad(
|
||||||
"Open File",
|
"Open File",
|
||||||
{"compatible files", "*.fur *.dmf *.mod",
|
{"compatible files", "*.fur *.dmf *.mod *.fc13 *.fc14 *.smod",
|
||||||
"all files", ".*"},
|
"all files", ".*"},
|
||||||
"compatible files{.fur,.dmf,.mod},.*",
|
"compatible files{.fur,.dmf,.mod,.fc13,.fc14,.smod},.*",
|
||||||
workingDirSong,
|
workingDirSong,
|
||||||
dpiScale
|
dpiScale
|
||||||
);
|
);
|
||||||
|
|
|
@ -91,7 +91,6 @@ void FurnaceGUI::drawSongInfo() {
|
||||||
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0);
|
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0);
|
||||||
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.0);
|
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.0);
|
||||||
|
|
||||||
/*
|
|
||||||
ImGui::TableNextRow();
|
ImGui::TableNextRow();
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
ImGui::Text("TimeBase");
|
ImGui::Text("TimeBase");
|
||||||
|
@ -213,14 +212,12 @@ void FurnaceGUI::drawSongInfo() {
|
||||||
ImGui::Text("NTSC");
|
ImGui::Text("NTSC");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
ImGui::TableNextRow();
|
ImGui::TableNextRow();
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
ImGui::Text("Tuning (A-4)");
|
ImGui::Text("Tuning (A-4)");
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
float tune=e->song.tuning;
|
float tune=e->song.tuning;
|
||||||
float avail=ImGui::GetContentRegionAvail().x;
|
|
||||||
ImGui::SetNextItemWidth(avail);
|
ImGui::SetNextItemWidth(avail);
|
||||||
if (ImGui::InputFloat("##Tuning",&tune,1.0f,3.0f,"%g")) { MARK_MODIFIED
|
if (ImGui::InputFloat("##Tuning",&tune,1.0f,3.0f,"%g")) { MARK_MODIFIED
|
||||||
if (tune<220.0f) tune=220.0f;
|
if (tune<220.0f) tune=220.0f;
|
||||||
|
|
Loading…
Reference in a new issue