diff --git a/CMakeLists.txt b/CMakeLists.txt index 6171029b8..ea612efbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -694,6 +694,7 @@ src/engine/configEngine.cpp src/engine/dispatchContainer.cpp src/engine/engine.cpp src/engine/export.cpp +src/engine/exportDef.cpp src/engine/fileOpsIns.cpp src/engine/fileOpsSample.cpp src/engine/filter.cpp @@ -711,7 +712,6 @@ src/engine/wavOps.cpp src/engine/vgmOps.cpp src/engine/zsmOps.cpp src/engine/zsm.cpp -src/engine/tiunaOps.cpp src/engine/platform/abstract.cpp src/engine/platform/genesis.cpp @@ -789,6 +789,7 @@ src/engine/platform/dummy.cpp src/engine/export/abstract.cpp src/engine/export/amigaValidation.cpp +src/engine/export/tiuna.cpp src/engine/effect/abstract.cpp src/engine/effect/dummy.cpp diff --git a/demos/a2600/TIADeepIntoCode.fur b/demos/a2600/TIADeepIntoCode.fur new file mode 100644 index 000000000..0a952a9c9 Binary files /dev/null and b/demos/a2600/TIADeepIntoCode.fur differ diff --git a/demos/a2600/Watch My Tone.fur b/demos/a2600/Watch My Tone.fur new file mode 100644 index 000000000..8bfe4df17 Binary files /dev/null and b/demos/a2600/Watch My Tone.fur differ diff --git a/demos/esfm/WindFjordsESFM.fur b/demos/esfm/WindFjordsESFM.fur new file mode 100644 index 000000000..fdd32fe24 Binary files /dev/null and b/demos/esfm/WindFjordsESFM.fur differ diff --git a/demos/genesis/imaginarium.fur b/demos/genesis/imaginarium.fur new file mode 100644 index 000000000..842ec8575 Binary files /dev/null and b/demos/genesis/imaginarium.fur differ diff --git a/demos/msx/Juice_Stand.fur b/demos/msx/Juice_Stand.fur new file mode 100644 index 000000000..73e550b83 Binary files /dev/null and b/demos/msx/Juice_Stand.fur differ diff --git a/demos/multichip/TheOnlyWayIsForward.fur b/demos/multichip/TheOnlyWayIsForward.fur new file mode 100644 index 000000000..faca66ad2 Binary files /dev/null and b/demos/multichip/TheOnlyWayIsForward.fur differ diff --git a/demos/nes/Byte-Sized Bop.fur b/demos/nes/Byte-Sized Bop.fur new file mode 100644 index 000000000..2adcac8e6 Binary files /dev/null and b/demos/nes/Byte-Sized Bop.fur differ diff --git a/demos/nes/Super cool and awesome samurai.fur b/demos/nes/Super cool and awesome samurai.fur new file mode 100644 index 000000000..58c0d73ff Binary files /dev/null and b/demos/nes/Super cool and awesome samurai.fur differ diff --git a/demos/nes/The East.fur b/demos/nes/The East.fur new file mode 100644 index 000000000..89f8027a1 Binary files /dev/null and b/demos/nes/The East.fur differ diff --git a/demos/opl/I_won_t_be_there_again.fur b/demos/opl/I_won_t_be_there_again.fur new file mode 100644 index 000000000..81e6621db Binary files /dev/null and b/demos/opl/I_won_t_be_there_again.fur differ diff --git a/demos/snes/Dear Blue SNES.fur b/demos/snes/Dear Blue SNES.fur new file mode 100644 index 000000000..50b016ed7 Binary files /dev/null and b/demos/snes/Dear Blue SNES.fur differ diff --git a/demos/snes/Sunken Lights.fur b/demos/snes/Sunken Lights.fur new file mode 100644 index 000000000..5de7e687b Binary files /dev/null and b/demos/snes/Sunken Lights.fur differ diff --git a/demos/snes/analog.fur b/demos/snes/analog.fur new file mode 100644 index 000000000..7b30bc4ef Binary files /dev/null and b/demos/snes/analog.fur differ diff --git a/demos/snes/aurora.fur b/demos/snes/aurora.fur new file mode 100644 index 000000000..61742fcf2 Binary files /dev/null and b/demos/snes/aurora.fur differ diff --git a/demos/snes/encjslt.fur b/demos/snes/encjslt.fur new file mode 100644 index 000000000..a01c7f5b7 Binary files /dev/null and b/demos/snes/encjslt.fur differ diff --git a/demos/snes/sound processing unit.fur b/demos/snes/sound processing unit.fur new file mode 100644 index 000000000..a2749d224 Binary files /dev/null and b/demos/snes/sound processing unit.fur differ diff --git a/demos/snes/spd10.fur b/demos/snes/spd10.fur new file mode 100644 index 000000000..3cb0ab0c9 Binary files /dev/null and b/demos/snes/spd10.fur differ diff --git a/demos/snes/spd12.fur b/demos/snes/spd12.fur new file mode 100644 index 000000000..12b2cd2c4 Binary files /dev/null and b/demos/snes/spd12.fur differ diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 1a382b85e..e9b3255c3 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -3589,6 +3589,12 @@ void DivEngine::synchronized(const std::function& what) { BUSY_END; } +void DivEngine::synchronizedSoft(const std::function& what) { + BUSY_BEGIN_SOFT; + what(); + BUSY_END; +} + void DivEngine::lockSave(const std::function& what) { saveLock.lock(); what(); @@ -3916,6 +3922,9 @@ bool DivEngine::preInit(bool noSafeMode) { // register systems if (!systemsRegistered) registerSystems(); + // register ROM exports + if (!romExportsRegistered) registerROMExports(); + // TODO: re-enable with a better approach // see issue #1581 /* diff --git a/src/engine/engine.h b/src/engine/engine.h index 2a7718c1d..f010d7001 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -54,8 +54,8 @@ class DivWorkPool; #define DIV_UNSTABLE -#define DIV_VERSION "Import Test" -#define DIV_ENGINE_VERSION 216 +#define DIV_VERSION "dev217" +#define DIV_ENGINE_VERSION 217 // for imports #define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_FC 0xff02 @@ -465,6 +465,7 @@ class DivEngine { bool midiIsDirectProgram; bool lowLatency; bool systemsRegistered; + bool romExportsRegistered; bool hasLoadedSomething; bool midiOutClock; bool midiOutTime; @@ -518,6 +519,7 @@ class DivEngine { static DivSysDef* sysDefs[DIV_MAX_CHIP_DEFS]; static DivSystem sysFileMapFur[DIV_MAX_CHIP_DEFS]; static DivSystem sysFileMapDMF[DIV_MAX_CHIP_DEFS]; + static DivROMExportDef* romExportDefs[DIV_ROM_MAX]; DivCSPlayer* cmdStreamInt; @@ -624,6 +626,7 @@ class DivEngine { bool deinitAudioBackend(bool dueToSwitchMaster=false); void registerSystems(); + void registerROMExports(); void initSongWithDesc(const char* description, bool inBase64=true, bool oldVol=false); void exchangeIns(int one, int two); @@ -654,6 +657,7 @@ class DivEngine { // add every export method here friend class DivROMExport; friend class DivExportAmigaValidation; + friend class DivExportTiuna; public: DivSong song; @@ -697,9 +701,8 @@ class DivEngine { // save as .fur. // if notPrimary is true then the song will not be altered SafeWriter* saveFur(bool notPrimary=false, bool newPatternFormat=true); - // build a ROM file (TODO). - // specify system to build ROM for. - std::vector buildROM(DivROMExportOptions sys); + // return a ROM exporter. + DivROMExport* buildROM(DivROMExportOptions sys); // dump to VGM. // set trailingTicks to: // - 0 to add one tick of trailing @@ -885,6 +888,9 @@ class DivEngine { // get sys definition const DivSysDef* getSystemDef(DivSystem sys); + // get ROM export definition + const DivROMExportDef* getROMExportDef(DivROMExportOptions opt); + // convert sample rate format int fileToDivRate(int frate); int divToFileRate(int drate); @@ -1294,6 +1300,9 @@ class DivEngine { // perform secure/sync operation void synchronized(const std::function& what); + // perform secure/sync operation (soft) + void synchronizedSoft(const std::function& what); + // perform secure/sync song operation void lockSave(const std::function& what); @@ -1361,6 +1370,7 @@ class DivEngine { midiIsDirectProgram(false), lowLatency(false), systemsRegistered(false), + romExportsRegistered(false), hasLoadedSomething(false), midiOutClock(false), midiOutTime(false), @@ -1467,6 +1477,7 @@ class DivEngine { memset(pitchTable,0,4096*sizeof(int)); memset(effectSlotMap,-1,4096*sizeof(short)); memset(sysDefs,0,DIV_MAX_CHIP_DEFS*sizeof(void*)); + memset(romExportDefs,0,DIV_ROM_MAX*sizeof(void*)); memset(walked,0,8192); memset(oscBuf,0,DIV_MAX_OUTPUTS*(sizeof(float*))); memset(exportChannelMask,1,DIV_MAX_CHANS*sizeof(bool)); diff --git a/src/engine/export.cpp b/src/engine/export.cpp index 2ea35327f..33544e252 100644 --- a/src/engine/export.cpp +++ b/src/engine/export.cpp @@ -20,18 +20,20 @@ #include "engine.h" #include "export/amigaValidation.h" +#include "export/tiuna.h" -std::vector DivEngine::buildROM(DivROMExportOptions sys) { +DivROMExport* DivEngine::buildROM(DivROMExportOptions sys) { DivROMExport* exporter=NULL; switch (sys) { case DIV_ROM_AMIGA_VALIDATION: exporter=new DivExportAmigaValidation; break; + case DIV_ROM_TIUNA: + exporter=new DivExportTiuna; + break; default: exporter=new DivROMExport; break; } - std::vector ret=exporter->go(this); - delete exporter; - return ret; + return exporter; } diff --git a/src/engine/export.h b/src/engine/export.h index d8e1878f1..f9439fc95 100644 --- a/src/engine/export.h +++ b/src/engine/export.h @@ -29,6 +29,8 @@ class DivEngine; enum DivROMExportOptions { DIV_ROM_ABSTRACT=0, DIV_ROM_AMIGA_VALIDATION, + DIV_ROM_ZSM, + DIV_ROM_TIUNA, DIV_ROM_MAX }; @@ -45,30 +47,61 @@ struct DivROMExportOutput { data(NULL) {} }; +struct DivROMExportProgress { + String name; + float amount; +}; + class DivROMExport { + protected: + DivConfig conf; + std::vector output; + void logAppend(String what); public: - virtual std::vector go(DivEngine* e); + std::vector exportLog; + std::mutex logLock; + + void setConf(DivConfig& c); + virtual bool go(DivEngine* eng); + virtual void abort(); + virtual void wait(); + std::vector& getResult(); + virtual bool hasFailed(); + virtual bool isRunning(); + virtual DivROMExportProgress getProgress(int index=0); virtual ~DivROMExport() {} }; +#define logAppendf(...) logAppend(fmt::sprintf(__VA_ARGS__)) + +enum DivROMExportReqPolicy { + // exactly these chips. + DIV_REQPOL_EXACT=0, + // specified chips must be present but any amount of them is acceptable. + DIV_REQPOL_ANY, + // at least one of the specified chips. + DIV_REQPOL_LAX +}; + struct DivROMExportDef { const char* name; const char* author; const char* description; - DivSystem requisites[32]; - int requisitesLen; + const char* fileType; + const char* fileExt; + std::vector requisites; bool multiOutput; + DivROMExportReqPolicy requisitePolicy; - DivROMExportDef(const char* n, const char* a, const char* d, std::initializer_list req, bool multiOut): + DivROMExportDef(const char* n, const char* a, const char* d, const char* ft, const char* fe, std::initializer_list req, bool multiOut, DivROMExportReqPolicy reqPolicy): name(n), author(a), description(d), - multiOutput(multiOut) { - requisitesLen=0; - memset(requisites,0,32*sizeof(DivSystem)); - for (DivSystem i: req) { - requisites[requisitesLen++]=i; - } + fileType(ft), + fileExt(fe), + multiOutput(multiOut), + requisitePolicy(reqPolicy) { + requisites=req; } }; diff --git a/src/engine/export/abstract.cpp b/src/engine/export/abstract.cpp index bd39e3aa0..c6d0ec799 100644 --- a/src/engine/export/abstract.cpp +++ b/src/engine/export/abstract.cpp @@ -20,7 +20,42 @@ #include "../export.h" #include "../../ta-log.h" -std::vector DivROMExport::go(DivEngine* e) { +bool DivROMExport::go(DivEngine* eng) { logW("what's this? the null ROM export?"); - return std::vector(); + return false; +} + +void DivROMExport::abort() { +} + +std::vector& DivROMExport::getResult() { + return output; +} + +bool DivROMExport::hasFailed() { + return true; +} + +DivROMExportProgress DivROMExport::getProgress(int index) { + DivROMExportProgress ret; + ret.name=""; + ret.amount=0.0f; + return ret; +} + +void DivROMExport::logAppend(String what) { + logLock.lock(); + exportLog.push_back(what); + logLock.unlock(); +} + +void DivROMExport::wait() { +} + +bool DivROMExport::isRunning() { + return false; +} + +void DivROMExport::setConf(DivConfig& c) { + conf=c; } diff --git a/src/engine/export/amigaValidation.cpp b/src/engine/export/amigaValidation.cpp index a613ee422..5fce7a920 100644 --- a/src/engine/export/amigaValidation.cpp +++ b/src/engine/export/amigaValidation.cpp @@ -40,8 +40,7 @@ struct SampleBookEntry { len(0) {} }; -std::vector DivExportAmigaValidation::go(DivEngine* e) { - std::vector ret; +void DivExportAmigaValidation::run() { std::vector waves; std::vector sampleBook; unsigned int wavesDataPtr=0; @@ -71,6 +70,7 @@ std::vector DivExportAmigaValidation::go(DivEngine* e) { bool done=false; // sample.bin + logAppend("writing samples..."); SafeWriter* sample=new SafeWriter; sample->init(); for (int i=0; i<256; i++) { @@ -80,6 +80,7 @@ std::vector DivExportAmigaValidation::go(DivEngine* e) { if (sample->tell()&1) sample->writeC(0); // seq.bin + logAppend("making sequence..."); SafeWriter* seq=new SafeWriter; seq->init(); @@ -240,6 +241,7 @@ std::vector DivExportAmigaValidation::go(DivEngine* e) { EXTERN_BUSY_END; // wave.bin + logAppend("writing wavetables..."); SafeWriter* wave=new SafeWriter; wave->init(); for (WaveEntry& i: waves) { @@ -247,6 +249,7 @@ std::vector DivExportAmigaValidation::go(DivEngine* e) { } // sbook.bin + logAppend("writing sample book..."); SafeWriter* sbook=new SafeWriter; sbook->init(); for (SampleBookEntry& i: sampleBook) { @@ -256,6 +259,7 @@ std::vector DivExportAmigaValidation::go(DivEngine* e) { } // wbook.bin + logAppend("writing wavetable book..."); SafeWriter* wbook=new SafeWriter; wbook->init(); for (WaveEntry& i: waves) { @@ -266,12 +270,40 @@ std::vector DivExportAmigaValidation::go(DivEngine* e) { } // finish - ret.reserve(5); - ret.push_back(DivROMExportOutput("sbook.bin",sbook)); - ret.push_back(DivROMExportOutput("wbook.bin",wbook)); - ret.push_back(DivROMExportOutput("sample.bin",sample)); - ret.push_back(DivROMExportOutput("wave.bin",wave)); - ret.push_back(DivROMExportOutput("seq.bin",seq)); + output.reserve(5); + output.push_back(DivROMExportOutput("sbook.bin",sbook)); + output.push_back(DivROMExportOutput("wbook.bin",wbook)); + output.push_back(DivROMExportOutput("sample.bin",sample)); + output.push_back(DivROMExportOutput("wave.bin",wave)); + output.push_back(DivROMExportOutput("seq.bin",seq)); - return ret; + logAppend("finished!"); + + running=false; +} + +bool DivExportAmigaValidation::go(DivEngine* eng) { + e=eng; + running=true; + exportThread=new std::thread(&DivExportAmigaValidation::run,this); + return true; +} + +void DivExportAmigaValidation::wait() { + if (exportThread!=NULL) { + exportThread->join(); + delete exportThread; + } +} + +void DivExportAmigaValidation::abort() { + wait(); +} + +bool DivExportAmigaValidation::isRunning() { + return running; +} + +bool DivExportAmigaValidation::hasFailed() { + return false; } diff --git a/src/engine/export/amigaValidation.h b/src/engine/export/amigaValidation.h index 11a4ecd48..df8131652 100644 --- a/src/engine/export/amigaValidation.h +++ b/src/engine/export/amigaValidation.h @@ -19,8 +19,18 @@ #include "../export.h" +#include + class DivExportAmigaValidation: public DivROMExport { + DivEngine* e; + std::thread* exportThread; + bool running; + void run(); public: - std::vector go(DivEngine* e); + bool go(DivEngine* e); + bool isRunning(); + bool hasFailed(); + void abort(); + void wait(); ~DivExportAmigaValidation() {} }; diff --git a/src/engine/tiunaOps.cpp b/src/engine/export/tiuna.cpp similarity index 55% rename from src/engine/tiunaOps.cpp rename to src/engine/export/tiuna.cpp index 942822cdb..397cee34f 100644 --- a/src/engine/tiunaOps.cpp +++ b/src/engine/export/tiuna.cpp @@ -17,62 +17,117 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "tiuna.h" +#include "../engine.h" +#include "../ta-log.h" +#include #include #include #include #include -#include "engine.h" -#include "../fileutils.h" -#include "../ta-log.h" struct TiunaNew { - short pitch=-1; - signed char ins=-1; - signed char vol=-1; - short sync=-1; + short pitch; + signed char ins; + signed char vol; + short sync; + TiunaNew(): + pitch(-1), + ins(-1), + vol(-1), + sync(-1) {} }; + struct TiunaLast { - short pitch=0; - signed char ins=0; - signed char vol=0; - int tick=1; - bool forcePitch=true; + short pitch; + signed char ins; + signed char vol; + int tick; + bool forcePitch; + TiunaLast(): + pitch(0), + ins(0), + vol(0), + tick(1), + forcePitch(true) {} }; + struct TiunaCmd { - signed char pitchChange=-1; - short pitchSet=-1; - signed char ins=-1; - signed char vol=-1; - short sync=-1; - short wait=-1; + signed char pitchChange; + short pitchSet; + signed char ins; + signed char vol; + short sync; + short wait; + TiunaCmd(): + pitchChange(-1), + pitchSet(-1), + ins(-1), + vol(-1), + sync(-1), + wait(-1) {} }; + struct TiunaBytes { - unsigned char ch=0; - int ticks=0; - unsigned char size=0; + unsigned char ch; + int ticks; + unsigned char size; unsigned char buf[16]; friend bool operator==(const TiunaBytes& l, const TiunaBytes& r) { if (l.size!=r.size) return false; if (l.ticks!=r.ticks) return false; return memcmp(l.buf,r.buf,l.size)==0; } + TiunaBytes(unsigned char c, int t, unsigned char s, std::initializer_list b): + ch(c), + ticks(t), + size(s) { + // because C++14 does not support data() on initializer_list + unsigned char p=0; + for (unsigned char i: b) { + buf[p++]=i; + } + } + TiunaBytes(): + ch(0), + ticks(0), + size(0) { + memset(buf,0,16); + } }; + struct TiunaMatch { int pos; int endPos; int size; int id; + TiunaMatch(int p, int ep, int s, int _i): + pos(p), + endPos(ep), + size(s), + id(_i) {} + TiunaMatch(): + pos(0), + endPos(0), + size(0), + id(0) {} }; + struct TiunaMatches { - int bytesSaved=INT32_MIN; - int length=0; - int ticks=0; + int bytesSaved; + int length; + int ticks; std::vector pos; + TiunaMatches(): + bytesSaved(INT32_MIN), + length(0), + ticks(0) {} }; static void writeCmd(std::vector& cmds, TiunaCmd& cmd, unsigned char ch, int& lastWait, int fromTick, int toTick) { while (fromTick0); if (lastWait!=val) { cmd.wait=val; lastWait=val; @@ -126,140 +181,161 @@ static void writeCmd(std::vector& cmds, TiunaCmd& cmd, unsigned char } } -SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel, int firstBankSize, int otherBankSize) { - stop(); - repeatPattern=false; - shallStop=false; - setOrder(0); - BUSY_BEGIN_SOFT; - // determine loop point - // bool stopped=false; - int loopOrder=0; - int loopOrderRow=0; - int loopEnd=0; - walkSong(loopOrder,loopOrderRow,loopEnd); - logI("loop point: %d %d",loopOrder,loopOrderRow); - - SafeWriter* w=new SafeWriter; - w->init(); - - int tiaIdx=-1; - - for (int i=0; itoggleRegisterDump(true); - break; - } - } - if (tiaIdx<0) { - lastError="selected TIA system not found"; - return NULL; - } - - // write patterns - // bool writeLoop=false; - bool done=false; - playSub(false); - +void DivExportTiuna::run() { + int loopOrder, loopOrderRow, loopEnd; int tick=0; - // int loopTick=-1; - TiunaLast last[2]; - TiunaNew news[2]; + SafeWriter* w; std::map allCmds[2]; - while (!done) { - // TODO implement loop - // if (loopTick<0 && loopOrder==curOrder && loopOrderRow==curRow - // && (ticks-((tempoAccum+virtualTempoN)/virtualTempoD))<=0 - // ) { - // writeLoop=true; - // loopTick=tick; - // // invalidate last register state so it always force an absolute write after loop - // for (int i=0; i<2; i++) { - // last[i]=TiunaLast(); - // last[i].pitch=-1; - // last[i].ins=-1; - // last[i].vol=-1; - // } - // } - if (nextTick(false,true) || !playing) { - // stopped=!playing; - done=true; - break; - } - for (int i=0; i<2; i++) { - news[i]=TiunaNew(); - } - // get register dumps - std::vector& writes=disCont[tiaIdx].dispatch->getRegisterWrites(); - for (const DivRegWrite& i: writes) { - switch (i.addr) { - case 0xfffe0000: - case 0xfffe0001: - news[i.addr&1].pitch=i.val; - break; - case 0xfffe0002: - news[0].sync=i.val; - break; - case 0x15: - case 0x16: - news[i.addr-0x15].ins=i.val; - break; - case 0x19: - case 0x1a: - news[i.addr-0x19].vol=i.val; - break; - default: break; - } - } - writes.clear(); - // collect changes - for (int i=0; i<2; i++) { - TiunaCmd cmds; - bool hasCmd=false; - if (news[i].pitch>=0 && (last[i].forcePitch || news[i].pitch!=last[i].pitch)) { - int dt=news[i].pitch-last[i].pitch; - if (!last[i].forcePitch && abs(dt)<=16) { - if (dt<0) cmds.pitchChange=15-dt; - else cmds.pitchChange=dt-1; - } - else cmds.pitchSet=news[i].pitch; - last[i].pitch=news[i].pitch; - last[i].forcePitch=false; - hasCmd=true; - } - if (news[i].ins>=0 && news[i].ins!=last[i].ins) { - cmds.ins=news[i].ins; - last[i].ins=news[i].ins; - hasCmd=true; - } - if (news[i].vol>=0 && news[i].vol!=last[i].vol) { - cmds.vol=(news[i].vol-last[i].vol)&0xf; - last[i].vol=news[i].vol; - hasCmd=true; - } - if (news[i].sync>=0) { - cmds.sync=news[i].sync; - hasCmd=true; - } - if (hasCmd) allCmds[i][tick]=cmds; - } - cmdStream.clear(); - tick++; - } - for (int i=0; igetRegisterWrites().clear(); - disCont[i].dispatch->toggleRegisterDump(false); - } - remainingLoops=-1; - playing=false; - freelance=false; - extValuePresent=false; - BUSY_END; + // config + String baseLabel=conf.getString("baseLabel","song"); + int firstBankSize=conf.getInt("firstBankSize",3072); + int otherBankSize=conf.getInt("otherBankSize",4096-48); + int tiaIdx=conf.getInt("sysToExport",-1); + + e->stop(); + e->repeatPattern=false; + e->shallStop=false; + e->setOrder(0); + e->synchronizedSoft([&]() { + // determine loop point + // bool stopped=false; + loopOrder=0; + loopOrderRow=0; + loopEnd=0; + e->walkSong(loopOrder,loopOrderRow,loopEnd); + logAppendf("loop point: %d %d",loopOrder,loopOrderRow); + + w=new SafeWriter; + w->init(); + + if (tiaIdx<0 || tiaIdx>=e->song.systemLen) { + tiaIdx=-1; + for (int i=0; isong.systemLen; i++) { + if (e->song.system[i]==DIV_SYSTEM_TIA) { + tiaIdx=i; + break; + } + } + if (tiaIdx<0) { + logAppend("ERROR: selected TIA system not found"); + failed=true; + running=false; + return; + } + } else if (e->song.system[tiaIdx]!=DIV_SYSTEM_TIA) { + logAppend("ERROR: selected chip is not a TIA!"); + failed=true; + running=false; + return; + } + + e->disCont[tiaIdx].dispatch->toggleRegisterDump(true); + + // write patterns + // bool writeLoop=false; + logAppend("recording sequence..."); + bool done=false; + e->playSub(false); + + // int loopTick=-1; + TiunaLast last[2]; + TiunaNew news[2]; + while (!done) { + // TODO implement loop + // if (loopTick<0 && loopOrder==curOrder && loopOrderRow==curRow + // && (ticks-((tempoAccum+virtualTempoN)/virtualTempoD))<=0 + // ) { + // writeLoop=true; + // loopTick=tick; + // // invalidate last register state so it always force an absolute write after loop + // for (int i=0; i<2; i++) { + // last[i]=TiunaLast(); + // last[i].pitch=-1; + // last[i].ins=-1; + // last[i].vol=-1; + // } + // } + if (e->nextTick(false,true) || !e->playing) { + // stopped=!playing; + done=true; + break; + } + for (int i=0; i<2; i++) { + news[i]=TiunaNew(); + } + // get register dumps + std::vector& writes=e->disCont[tiaIdx].dispatch->getRegisterWrites(); + for (const DivRegWrite& i: writes) { + switch (i.addr) { + case 0xfffe0000: + case 0xfffe0001: + news[i.addr&1].pitch=i.val; + break; + case 0xfffe0002: + news[0].sync=i.val; + break; + case 0x15: + case 0x16: + news[i.addr-0x15].ins=i.val; + break; + case 0x19: + case 0x1a: + news[i.addr-0x19].vol=i.val; + break; + default: break; + } + } + writes.clear(); + // collect changes + for (int i=0; i<2; i++) { + TiunaCmd cmds; + bool hasCmd=false; + if (news[i].pitch>=0 && (last[i].forcePitch || news[i].pitch!=last[i].pitch)) { + int dt=news[i].pitch-last[i].pitch; + if (!last[i].forcePitch && abs(dt)<=16) { + if (dt<0) cmds.pitchChange=15-dt; + else cmds.pitchChange=dt-1; + } + else cmds.pitchSet=news[i].pitch; + last[i].pitch=news[i].pitch; + last[i].forcePitch=false; + hasCmd=true; + } + if (news[i].ins>=0 && news[i].ins!=last[i].ins) { + cmds.ins=news[i].ins; + last[i].ins=news[i].ins; + hasCmd=true; + } + if (news[i].vol>=0 && news[i].vol!=last[i].vol) { + cmds.vol=(news[i].vol-last[i].vol)&0xf; + last[i].vol=news[i].vol; + hasCmd=true; + } + if (news[i].sync>=0) { + cmds.sync=news[i].sync; + hasCmd=true; + } + if (hasCmd) allCmds[i][tick]=cmds; + } + e->cmdStream.clear(); + tick++; + } + for (int i=0; isong.systemLen; i++) { + e->disCont[i].dispatch->getRegisterWrites().clear(); + e->disCont[i].dispatch->toggleRegisterDump(false); + } + + e->remainingLoops=-1; + e->playing=false; + e->freelance=false; + e->extValuePresent=false; + }); + + if (failed) return; // render commands + logAppend("rendering commands..."); std::vector renderedCmds; w->writeText(fmt::format( "; Generated by Furnace " DIV_VERSION "\n" @@ -267,7 +343,7 @@ SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel, "; Author: {}\n" "; Album: {}\n" "; Subsong #{}: {}\n\n", - song.name,song.author,song.category,curSubSongIndex+1,curSubSong->name + e->song.name,e->song.author,e->song.category,e->curSubSongIndex+1,e->curSubSong->name )); for (int i=0; i<2; i++) { TiunaCmd lastCmd; @@ -293,17 +369,37 @@ SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel, std::vector callTicks; int cmId=0; int cmdSize=renderedCmds.size(); - std::vector processed=std::vector(cmdSize,false); - while (firstBankSize>768 && cmId<(MAX(firstBankSize/1024,1))*256) { + bool* processed=new bool[cmdSize]; + memset(processed,0,cmdSize*sizeof(bool)); + logAppend("compressing! this may take a while."); + int maxCmId=(MAX(firstBankSize/1024,1))*256; + int lastMaxPMVal=100000; + logAppendf("max cmId: %d",maxCmId); + logAppendf("commands: %d",cmdSize); + while (firstBankSize>768 && cmId potentialMatches; for (int i=0; i=cmdSize-1) break; + progress[1].amount=(float)i/(float)(cmdSize-1); std::vector match; int ch=renderedCmds[i].ch; for (int j=i+1; j=cmdSize) break; int k=0; int ticks=0; @@ -322,7 +418,7 @@ SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel, size+=renderedCmds[i+k].size; k++; } - if (size>2) match.push_back({j,j+k,size,0}); + if (size>2) match.push_back(TiunaMatch(j,j+k,size,0)); if (k==0) k++; j+=k; } @@ -367,7 +463,10 @@ SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel, } i++; } - if (potentialMatches.empty()) break; + if (potentialMatches.empty()) { + logAppend("potentialMatches is empty"); + break; + } int maxPMIdx=0; int maxPMVal=0; for (const auto& i: potentialMatches) { @@ -379,12 +478,18 @@ SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel, int maxPMLen=potentialMatches[maxPMIdx].length; for (const int i: potentialMatches[maxPMIdx].pos) { confirmedMatches.push_back({i,i+maxPMLen,0,cmId}); - std::fill(processed.begin()+i,processed.begin()+(i+maxPMLen),true); + memset(processed+i,1,maxPMLen); + //std::fill(processed.begin()+i,processed.begin()+(i+maxPMLen),true); } callTicks.push_back(potentialMatches[maxPMIdx].ticks); - logI("CM %04x added: pos=%d,len=%d,matches=%d,saved=%d",cmId,maxPMIdx,maxPMLen,potentialMatches[maxPMIdx].pos.size(),maxPMVal); + logAppendf("CM %04x added: pos=%d,len=%d,matches=%d,saved=%d",cmId,maxPMIdx,maxPMLen,potentialMatches[maxPMIdx].pos.size(),maxPMVal); + lastMaxPMVal=maxPMVal; cmId++; } + progress[0].amount=1.0f; + progress[1].amount=1.0f; + logAppend("generating data..."); + delete[] processed; std::sort(confirmedMatches.begin(),confirmedMatches.end(),[](const TiunaMatch& l, const TiunaMatch& r){ return l.poswriteC('\n'); if (totalSize>firstBankSize) { - lastError="first bank is not large enough to contain call table"; - return NULL; + logAppend("ERROR: first bank is not large enough to contain call table"); + failed=true; + running=false; + return; } int curBank=0; @@ -462,7 +571,7 @@ SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel, if (callVisited[cmIter->id]) { unsigned char idLo=cmIter->id&0xff; unsigned char idHi=cmIter->id>>8; - cmd={cmd.ch,0,2,{idHi,idLo}}; + cmd=TiunaBytes(cmd.ch,0,2,{idHi,idLo}); i=cmIter->endPos-1; } else { writeCall=cmIter->id; @@ -506,13 +615,50 @@ SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel, } w->writeText(" .text x\"e0\"\n .endsection\n"); totalSize++; - logI("total size: %d bytes (%d banks)",totalSize,curBank+1); - - FILE* f=ps_fopen("confirmedMatches.txt","wb"); - if (f!=NULL) { - fwrite(dbg.getFinalBuf(),1,dbg.size(),f); - fclose(f); - } + logAppendf("total size: %d bytes (%d banks)",totalSize,curBank+1); - return w; + output.push_back(DivROMExportOutput("export.asm",w)); + + logAppend("finished!"); + + running=false; +} + +bool DivExportTiuna::go(DivEngine* eng) { + progress[0].name="Compression"; + progress[0].amount=0.0f; + progress[1].name="Confirmed Matches"; + progress[1].amount=0.0f; + + e=eng; + running=true; + failed=false; + mustAbort=false; + exportThread=new std::thread(&DivExportTiuna::run,this); + return true; +} + +void DivExportTiuna::wait() { + if (exportThread!=NULL) { + exportThread->join(); + delete exportThread; + } +} + +void DivExportTiuna::abort() { + mustAbort=true; + wait(); +} + +bool DivExportTiuna::isRunning() { + return running; +} + +bool DivExportTiuna::hasFailed() { + return failed; +} + +DivROMExportProgress DivExportTiuna::getProgress(int index) { + if (index<0 || index>2) return progress[2]; + return progress[index]; } diff --git a/src/engine/export/tiuna.h b/src/engine/export/tiuna.h new file mode 100644 index 000000000..ae1a661a9 --- /dev/null +++ b/src/engine/export/tiuna.h @@ -0,0 +1,38 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "../export.h" + +#include + +class DivExportTiuna: public DivROMExport { + DivEngine* e; + std::thread* exportThread; + DivROMExportProgress progress[3]; + bool running, failed, mustAbort; + void run(); + public: + bool go(DivEngine* e); + bool isRunning(); + bool hasFailed(); + void abort(); + void wait(); + DivROMExportProgress getProgress(int index=0); + ~DivExportTiuna() {} +}; diff --git a/src/engine/exportDef.cpp b/src/engine/exportDef.cpp new file mode 100644 index 000000000..00ee94a36 --- /dev/null +++ b/src/engine/exportDef.cpp @@ -0,0 +1,63 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2024 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "engine.h" + +DivROMExportDef* DivEngine::romExportDefs[DIV_ROM_MAX]; + +const DivROMExportDef* DivEngine::getROMExportDef(DivROMExportOptions opt) { + return romExportDefs[opt]; +} + +void DivEngine::registerROMExports() { + logD("registering ROM exports..."); + + romExportDefs[DIV_ROM_AMIGA_VALIDATION]=new DivROMExportDef( + "Amiga Validation", "tildearrow", + "a test export for ensuring Amiga emulation is accurate. do not use!", + NULL, NULL, + {DIV_SYSTEM_AMIGA}, + true, DIV_REQPOL_EXACT + ); + + romExportDefs[DIV_ROM_ZSM]=new DivROMExportDef( + "Commander X16 ZSM", "ZeroByteOrg and MooingLemur", + "Commander X16 Zsound Music File.\n" + "for use with Melodius, Calliope and/or ZSMKit:\n" + "- https://github.com/mooinglemur/zsmkit (development)\n" + "- https://github.com/mooinglemur/melodius (player)\n" + "- https://github.com/ZeroByteOrg/calliope (player)\n", + "ZSM file", ".zsm", + { + DIV_SYSTEM_YM2151, DIV_SYSTEM_VERA + }, + false, DIV_REQPOL_LAX + ); + + romExportDefs[DIV_ROM_TIUNA]=new DivROMExportDef( + "Atari 2600 (TIunA)", "Natt Akuma", + "advanced driver with software tuning support.\n" + "see https://github.com/AYCEdemo/twin-tiuna for code.", + "assembly files", ".asm", + { + DIV_SYSTEM_TIA + }, + false, DIV_REQPOL_ANY + ); +} diff --git a/src/engine/fileOps/fur.cpp b/src/engine/fileOps/fur.cpp index 0251c410e..5483d8fb9 100644 --- a/src/engine/fileOps/fur.cpp +++ b/src/engine/fileOps/fur.cpp @@ -2094,6 +2094,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) { ds.systemFlags[i].set("oldPitch",true); } } + } else if (ds.version<217) { + for (int i=0; i=getSampleMemCapacity(0)) { + if (adpcmMem==NULL) { return 0; } if (isBanked) { if (address<0x400) { - return adpcmMem[(bank[(address>>8)&0x3]<<16)|(address&0x3ff)]; + unsigned int bankedAddress=(bank[(address>>8)&0x3]<<16)|(address&0x3ff); + if (bankedAddress>=getSampleMemCapacity(0)) { + return 0; + } + return adpcmMem[bankedAddress&0xffffff]; } - return adpcmMem[(bank[(address>>16)&0x3]<<16)|(address&0xffff)]; + unsigned int bankedAddress=(bank[(address>>16)&0x3]<<16)|(address&0xffff); + if (bankedAddress>=getSampleMemCapacity(0)) { + return 0; + } + return adpcmMem[bankedAddress&0xffffff]; + } + if (address>=getSampleMemCapacity(0)) { + return 0; } return adpcmMem[address&0x3ffff]; } diff --git a/src/engine/platform/sound/vera_psg.c b/src/engine/platform/sound/vera_psg.c index be080ace9..597aa5974 100644 --- a/src/engine/platform/sound/vera_psg.c +++ b/src/engine/platform/sound/vera_psg.c @@ -5,6 +5,7 @@ // Chip revisions // 0: V 0.3.0 // 1: V 47.0.0 (9-bit volume, phase reset on mute) +// 2: V 47.0.2 (Pulse Width XOR on Saw and Triangle) #include "vera_psg.h" @@ -88,8 +89,8 @@ render(struct VERA_PSG* psg, int16_t *left, int16_t *right) uint8_t v = 0; switch (ch->waveform) { case WF_PULSE: v = (ch->phase >> 10) > ch->pw ? 0 : 63; break; - case WF_SAWTOOTH: v = ch->phase >> 11; break; - case WF_TRIANGLE: v = (ch->phase & 0x10000) ? (~(ch->phase >> 10) & 0x3F) : ((ch->phase >> 10) & 0x3F); break; + case WF_SAWTOOTH: v = (ch->phase >> 11) ^ (psg->chipType < 2 ? 0 : (ch->pw ^ 0x3f) & 0x3f); break; + case WF_TRIANGLE: v = ((ch->phase & 0x10000) ? (~(ch->phase >> 10) & 0x3F) : ((ch->phase >> 10) & 0x3F)) ^ (psg->chipType < 2 ? 0 : (ch->pw ^ 0x3f) & 0x3f); break; case WF_NOISE: v = ch->noiseval; break; } int8_t sv = (v ^ 0x20); diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index e4e78933e..b6b7cf831 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -532,7 +532,7 @@ void DivPlatformVERA::poke(std::vector& wlist) { } void DivPlatformVERA::setFlags(const DivConfig& flags) { - psg->chipType=flags.getInt("chipType",1); + psg->chipType=flags.getInt("chipType",2); chipClock=25000000; CHECK_CUSTOM_CLOCK; rate=chipClock/512; diff --git a/src/engine/zsm.cpp b/src/engine/zsm.cpp index 87dbde8d2..75cc3d9da 100644 --- a/src/engine/zsm.cpp +++ b/src/engine/zsm.cpp @@ -133,14 +133,6 @@ void DivZSM::writePSG(unsigned char a, unsigned char v) { } else if (a>=64) { return writePCM(a-64,v); } - if (optimize) { - if ((a&3)==3 && v>64) { - // Pulse width on non-pulse waves is nonsense and wasteful - // No need to preserve state here because the next write that - // selects pulse will also set the pulse width in this register - v&=0xc0; - } - } if (psgState[psg_PREV][a]==v) { if (psgState[psg_NEW][a]!=v) { // NEW value is being reset to the same as PREV value diff --git a/src/gui/about.cpp b/src/gui/about.cpp index f3a31038b..eb4b06753 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -94,6 +94,7 @@ const char* aboutLine[]={ "battybeats", "bbqzzd", "Bernie", + "billimanmcjonnson", "BlastBrothers", "Blaze Weednix", "BlueElectric05", @@ -147,6 +148,7 @@ const char* aboutLine[]={ "MelonadeM", "Miker", "Molkirill", + "MrCoolDude", "NeoWar", "Nerreave", "niffuM", @@ -158,6 +160,7 @@ const char* aboutLine[]={ "Pale Moon", "PeyPey", "PichuMario", + "pixelated", "Poltvick", "PotaJoe", "potatoTeto", @@ -177,6 +180,7 @@ const char* aboutLine[]={ "Slightly Large NC", "smaybius", "SnugglyBun", + "Someone64", "Spinning Square Waves", "src3453", "SuperJet Spade", diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index 5527f5598..3eba0298c 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -619,41 +619,6 @@ void FurnaceGUI::drawMobileControls() { if (ImGui::Button(_("Switch to Desktop Mode"))) { toggleMobileUI(!mobileUI); } - - int numAmiga=0; - for (int i=0; isong.systemLen; i++) { - if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++; - } - - if (numAmiga) { - ImGui::Text(_( - "this is NOT ROM export! only use for making sure the\n" - "Furnace Amiga emulator is working properly by\n" - "comparing it with real Amiga output." - )); - ImGui::AlignTextToFramePadding(); - ImGui::Text(_("Directory")); - ImGui::SameLine(); - ImGui::InputText("##AVDPath",&workingDirROMExport); - if (ImGui::Button(_("Bake Data"))) { - std::vector out=e->buildROM(DIV_ROM_AMIGA_VALIDATION); - if (workingDirROMExport.size()>0) { - if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR; - } - for (DivROMExportOutput& i: out) { - String path=workingDirROMExport+i.name; - FILE* outFile=ps_fopen(path.c_str(),"wb"); - if (outFile!=NULL) { - fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile); - fclose(outFile); - } - i.data->finish(); - delete i.data; - } - showError(fmt::sprintf(_("Done! Baked %d files."),(int)out.size())); - } - } - break; } } diff --git a/src/gui/exportOptions.cpp b/src/gui/exportOptions.cpp index 357ef8002..aca5523f0 100644 --- a/src/gui/exportOptions.cpp +++ b/src/gui/exportOptions.cpp @@ -87,6 +87,18 @@ void FurnaceGUI::drawExportAudio(bool onWindow) { } } ImGui::SameLine(); + if (ImGui::SmallButton(_("Shown in pattern"))) { + for (int i=0; icurSubSong->chanShow[i]; + } + } + ImGui::SameLine(); + if (ImGui::SmallButton(_("Shown in oscilloscope"))) { + for (int i=0; icurSubSong->chanShowChanOsc[i]; + } + } + ImGui::SameLine(); if (ImGui::SmallButton(_("Invert"))) { for (int i=0; igetROMExportDef(romTarget); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("##ROMTarget",def==NULL?"