diff --git a/papers/format.md b/papers/format.md index bd829516..1d0027c8 100644 --- a/papers/format.md +++ b/papers/format.md @@ -12,7 +12,7 @@ size | description -----|------------------------------------ 16 | "-Furnace module-" format magic 2 | format version - | - should be 1 for Furnace 0.2 + | - should be 15 for Furnace 0.3 2 | reserved 4 | song info pointer 8 | reserved @@ -22,7 +22,7 @@ size | description size | description -----|------------------------------------ 4 | "INFO" block ID - 4 | length of this block + 4 | reserved 1 | time base 1 | speed 1 1 | speed 2 @@ -38,10 +38,9 @@ size | description 2 | wavetable count 2 | sample count 4 | pattern count - 1 | sound chip count - 31 | list of sound chips + 32 | list of sound chips | - possible soundchips: - | - 0x00: invalid - 0 channels + | - 0x00: end of list | - 0x01: YMU759 - 17 channels | - 0x02: Genesis - 10 channels | - 0x03: SMS (SN76489) - 4 channels @@ -55,10 +54,13 @@ size | description | - 0x42: Genesis extended - 13 channels | - 0x47: C64 (6581) - 3 channels | - 0x49: Neo Geo extended - 16 channels - 4 | reserved - 124 | sound chip parameters (TODO) - 4 | pointer song name - 4 | pointer to song author + 32 | sound chip volumes + | - signed char, 64=1.0, 127=~2.0 + 32 | sound chip panning + | - signed char, -128=left, 127=right + 128 | sound chip parameters (TODO) + ??? | song name + ??? | song author 24 | reserved for compatibility flags 4?? | pointers to instruments 4?? | pointers to wavetables @@ -67,6 +69,7 @@ size | description ??? | orders | - a table of shorts | - size=channels*ordLen + | - read orders than channels ??? | effect columns | - size=channels @@ -75,7 +78,7 @@ size | description size | description -----|------------------------------------ 4 | "INST" block ID - 4 | length of this block + 4 | reserved 2 | format version (see header) 1 | instrument type | - 0: standard @@ -84,7 +87,7 @@ size | description | - 3: C64 | - 4: Amiga/sample 1 | reserved - 4 | pointer to instrument name + ??? | instrument name --- | **FM instrument data** 1 | alg 1 | feedback @@ -166,8 +169,8 @@ size | description size | description -----|------------------------------------ 4 | "WAVE" block ID - 4 | length of this block - 4 | pointer to wavetable name + 4 | reserved + ??? | wavetable name 4 | wavetable size 4 | wavetable min 4 | wavetable max @@ -178,8 +181,8 @@ size | description size | description -----|------------------------------------ 4 | "SMPL" block ID - 4 | length of this block - 4 | pointer to sample name + 4 | reserved + ??? | sample name 4 | length 4 | rate 2 | volume @@ -193,7 +196,7 @@ size | description size | description -----|------------------------------------ 4 | "PATR" block ID - 4 | length of this block + 4 | reserved 2 | channel 2 | pattern index 4 | reserved diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 0eb886fd..d5a28d6c 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -532,6 +532,13 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } else { ins->mode=reader.readC(); } + ins->type=ins->mode?DIV_INS_FM:DIV_INS_STD; + if (ds.system[0]==DIV_SYSTEM_GB) { + ins->type=DIV_INS_GB; + } + if (ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) { + ins->type=DIV_INS_C64; + } if (ins->mode) { // FM if (!isFMSystem(ds.system[0])) { @@ -1018,6 +1025,333 @@ bool DivEngine::load(unsigned char* f, size_t slen) { return false; } +SafeWriter* DivEngine::saveFur() { + int insPtr[256]; + int wavePtr[256]; + int samplePtr[256]; + std::vector patPtr; + size_t ptrSeek; + + // fail if this is an YMU759 song + if (song.system[0]==DIV_SYSTEM_YMU759) { + logE("cannot save YMU759 song!\n"); + lastError="YMU759 song saving is not supported"; + return NULL; + } + + SafeWriter* w=new SafeWriter; + w->init(); + /// HEADER + // write magic + w->write(DIV_FUR_MAGIC,16); + + // write version + w->writeS(DIV_ENGINE_VERSION); + + // reserved + w->writeS(0); + + // song info pointer + w->writeI(32); + + // reserved + w->writeI(0); + w->writeI(0); + + // high short is channel + // low short is pattern number + std::vector patsToWrite; + bool alreadyAdded[256]; + for (int i=0; iwrite("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->writeS(song.insLen); + w->writeS(song.waveLen); + w->writeS(song.sampleLen); + w->writeI(patsToWrite.size()); + + for (int i=0; i<32; i++) { + if (i>=song.systemLen) { + w->writeC(0); + } else { + w->writeC(systemToFile(song.system[i])); + } + } + + for (int i=0; i<32; i++) { + // for now + w->writeC(64); + } + + for (int i=0; i<32; i++) { + // for now + w->writeC(0); + } + + for (int i=0; i<32; i++) { + // for now + w->writeI(0); + } + + // song name + w->writeString(song.name,false); + // song author + w->writeString(song.author,false); + + // reserved + for (int i=0; i<24; i++) { + w->writeC(0); + } + + ptrSeek=w->tell(); + // instrument pointers (we'll seek here later) + for (int i=0; iwriteI(0); + } + + // wavetable pointers (we'll seek here later) + for (int i=0; iwriteI(0); + } + + // sample pointers (we'll seek here later) + for (int i=0; iwriteI(0); + } + + // pattern pointers (we'll seek here later) + for (size_t i=0; iwriteI(0); + } + + for (int i=0; iwriteC(song.orders.ord[i][j]); + } + } + + for (int i=0; iwriteC(song.pat[i].effectRows); + } + + /// INSTRUMENT + for (int i=0; itell(); + w->write("INST",4); + w->writeI(0); + + w->writeS(DIV_ENGINE_VERSION); + + w->writeC(ins->type); + w->writeC(0); + + w->writeString(ins->name,false); + + // FM + w->writeC(ins->fm.alg); + w->writeC(ins->fm.fb); + w->writeC(ins->fm.fms); + w->writeC(ins->fm.ams); + w->writeC(4); // operator count; always 4 + w->writeC(0); // reserved + w->writeC(0); + w->writeC(0); + + for (int j=0; j<4; j++) { + DivInstrumentFM::Operator& op=ins->fm.op[j]; + w->writeC(op.am); + w->writeC(op.ar); + w->writeC(op.dr); + w->writeC(op.mult); + w->writeC(op.rr); + w->writeC(op.sl); + w->writeC(op.tl); + w->writeC(op.dt2); + w->writeC(op.rs); + w->writeC(op.dt); + w->writeC(op.d2r); + w->writeC(op.ssgEnv); + + w->writeC(op.dam); + w->writeC(op.dvb); + w->writeC(op.egt); + w->writeC(op.ksl); + w->writeC(op.sus); + w->writeC(op.vib); + w->writeC(op.ws); + w->writeC(op.ksr); + + // reserved + for (int k=0; k<12; k++) { + w->writeC(0); + } + } + + // GB + w->writeC(ins->gb.envVol); + w->writeC(ins->gb.envDir); + w->writeC(ins->gb.envLen); + w->writeC(ins->gb.soundLen); + + // C64 + w->writeC(ins->c64.triOn); + w->writeC(ins->c64.sawOn); + w->writeC(ins->c64.pulseOn); + w->writeC(ins->c64.noiseOn); + w->writeC(ins->c64.a); + w->writeC(ins->c64.d); + w->writeC(ins->c64.s); + w->writeC(ins->c64.r); + w->writeS((ins->c64.duty*4096)/100); + w->writeC(ins->c64.ringMod); + w->writeC(ins->c64.oscSync); + w->writeC(ins->c64.toFilter); + w->writeC(ins->c64.initFilter); + w->writeC(ins->c64.volIsCutoff); + w->writeC(ins->c64.res); + w->writeC(ins->c64.lp); + w->writeC(ins->c64.bp); + w->writeC(ins->c64.hp); + w->writeC(ins->c64.ch3off); + w->writeS((ins->c64.cut*2047)/100); + w->writeC(ins->c64.dutyIsAbs); + w->writeC(ins->c64.filterIsAbs); + + // Amiga + w->writeS(ins->amiga.initSample); + for (int j=0; j<14; j++) { // reserved + w->writeC(0); + } + + // standard + w->writeI(ins->std.volMacroLen); + w->writeI(ins->std.arpMacroLen); + w->writeI(ins->std.dutyMacroLen); + w->writeI(ins->std.waveMacroLen); + w->writeI(ins->std.volMacroLoop); + w->writeI(ins->std.arpMacroLoop); + w->writeI(ins->std.dutyMacroLoop); + w->writeI(ins->std.waveMacroLoop); + w->writeC(ins->std.arpMacroMode); + w->writeC(0); // reserved + w->writeC(0); + w->writeC(0); + for (int j=0; jstd.volMacroLen; j++) { + w->writeI(ins->std.volMacro[j]); + } + for (int j=0; jstd.arpMacroLen; j++) { + w->writeI(ins->std.arpMacro[j]); + } + for (int j=0; jstd.dutyMacroLen; j++) { + w->writeI(ins->std.dutyMacro[j]); + } + for (int j=0; jstd.waveMacroLen; j++) { + w->writeI(ins->std.waveMacro[j]); + } + } + + /// WAVETABLE + for (int i=0; itell(); + w->write("WAVE",4); + w->writeI(0); + + w->writeC(0); // name + w->writeI(wave->len); + w->writeI(wave->min); + w->writeI(wave->max); + for (int j=0; jlen; j++) { + w->writeI(wave->data[j]); + } + } + + /// SAMPLE + for (int i=0; itell(); + w->write("SMPL",4); + w->writeI(0); + + w->writeString(sample->name,false); + w->writeI(sample->length); + w->writeI(sample->rate); + w->writeS(sample->vol); + w->writeS(sample->pitch); + w->writeC(sample->depth); + for (int j=0; j<7; j++) { // reserved + w->writeC(0); + } + + w->write(sample->data,sample->length*2); + } + + /// PATTERN + for (int i: patsToWrite) { + DivPattern* pat=song.pat[i>>16].getPattern(i&0xffff,false); + patPtr.push_back(w->tell()); + w->write("PATR",4); + w->writeI(0); + + w->writeS(i>>16); + w->writeS(i&0xffff); + + w->writeI(0); // reserved + + for (int j=0; jwriteS(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].effectRows*2); // effects + } + } + + /// POINTERS + w->seek(ptrSeek,SEEK_SET); + + for (int i=0; iwriteI(insPtr[i]); + } + + // wavetable pointers (we'll seek here later) + for (int i=0; iwriteI(wavePtr[i]); + } + + // sample pointers (we'll seek here later) + for (int i=0; iwriteI(samplePtr[i]); + } + + // pattern pointers (we'll seek here later) + for (int i: patPtr) { + w->writeI(i); + } + + return w; +} + SafeWriter* DivEngine::saveDMF() { // fail if more than one system if (song.systemLen!=1) { diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 0add362a..70b9b1ec 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -3,10 +3,11 @@ #include "../ta-utils.h" enum DivInstrumentType { - DIV_INS_FM, - DIV_INS_STD, - DIV_INS_GB, - DIV_INS_C64 + DIV_INS_STD=0, + DIV_INS_FM=1, + DIV_INS_GB=2, + DIV_INS_C64=3, + DIV_INS_AMIGA=4 }; struct DivInstrumentFM { @@ -117,7 +118,7 @@ struct DivInstrumentC64 { unsigned char a, d, s, r; unsigned char duty; unsigned char ringMod, oscSync; - bool toFilter, volIsCutoff, initFilter; + bool toFilter, volIsCutoff, initFilter, dutyIsAbs, filterIsAbs; unsigned char res, cut; bool hp, lp, bp, ch3off; @@ -136,6 +137,8 @@ struct DivInstrumentC64 { toFilter(false), volIsCutoff(false), initFilter(false), + dutyIsAbs(false), + filterIsAbs(false), res(0), cut(0), hp(false), @@ -144,6 +147,13 @@ struct DivInstrumentC64 { ch3off(false) {} }; +struct DivInstrumentAmiga { + short initSample; + + DivInstrumentAmiga(): + initSample(0) {} +}; + struct DivInstrument { String name; bool mode; @@ -152,6 +162,7 @@ struct DivInstrument { DivInstrumentSTD std; DivInstrumentGB gb; DivInstrumentC64 c64; + DivInstrumentAmiga amiga; DivInstrument(): name(""), mode(false), diff --git a/src/engine/wavetable.h b/src/engine/wavetable.h index eb2b70f5..bd115aa7 100644 --- a/src/engine/wavetable.h +++ b/src/engine/wavetable.h @@ -1,9 +1,11 @@ struct DivWavetable { - int len; + int len, min, max; int data[32]; DivWavetable(): - len(32) { + len(32), + min(0), + max(31) { for (int i=0; i<32; i++) { data[i]=i; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 295200ef..13691b09 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2343,10 +2343,10 @@ void FurnaceGUI::keyUp(SDL_Event& ev) { void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { switch (type) { case GUI_FILE_OPEN: - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Open File","DefleMask module{.dmf},.*",workingDir); + ImGuiFileDialog::Instance()->OpenModal("FileDialog","Open File","Furnace song{.fur},DefleMask module{.dmf},.*",workingDir); break; case GUI_FILE_SAVE: - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save File","DefleMask module{.dmf}",workingDir); + ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save File","Furnace song{.fur},DefleMask module{.dmf}",workingDir); break; case GUI_FILE_SAMPLE_OPEN: ImGuiFileDialog::Instance()->OpenModal("FileDialog","Load Sample","Wave file{.wav},.*",workingDir); @@ -2366,7 +2366,12 @@ int FurnaceGUI::save(String path) { lastError=strerror(errno); return 1; } - SafeWriter* w=e->saveDMF(); + SafeWriter* w; + if (path.rfind(".dmf")==path.size()-4) { + w=e->saveDMF(); + } else { + w=e->saveFur(); + } if (w==NULL) { lastError=e->getLastError(); fclose(outFile); @@ -2785,8 +2790,14 @@ bool FurnaceGUI::loop() { fileName=ImGuiFileDialog::Instance()->GetFilePathName(); if (fileName!="") { if (curFileDialog==GUI_FILE_SAVE) { - if (fileName.size()<4 || fileName.rfind(".dmf")!=fileName.size()-4) { - fileName+=".dmf"; + if (ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace song") { + if (fileName.size()<4 || fileName.rfind(".fur")!=fileName.size()-4) { + fileName+=".fur"; + } + } else { + if (fileName.size()<4 || fileName.rfind(".dmf")!=fileName.size()-4) { + fileName+=".dmf"; + } } } if (curFileDialog==GUI_FILE_SAMPLE_SAVE) { @@ -3013,6 +3024,7 @@ bool FurnaceGUI::init() { ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir,"",ImVec4(0.0f,1.0f,1.0f,1.0f),ICON_FA_FOLDER_O); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeFile,"",ImVec4(0.7f,0.7f,0.7f,1.0f),ICON_FA_FILE_O); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fur",ImVec4(0.5f,1.0f,0.5f,1.0f),ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmf",ImVec4(0.5f,1.0f,0.5f,1.0f),ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".wav",ImVec4(1.0f,1.0f,0.5f,1.0f),ICON_FA_FILE_AUDIO_O);