diff --git a/CMakeLists.txt b/CMakeLists.txt index 117265410..4e7e09a98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -695,6 +695,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 @@ -712,7 +713,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 @@ -791,6 +791,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/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/opl/I won't be there again (Extended Mix).fur b/demos/opl/I_won_t_be_there_again.fur similarity index 100% rename from demos/opl/I won't be there again (Extended Mix).fur rename to demos/opl/I_won_t_be_there_again.fur diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 17c0a1417..91cafeb86 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 daaf3852a..cfa958809 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -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; @@ -884,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); @@ -1293,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); @@ -1360,6 +1370,7 @@ class DivEngine { midiIsDirectProgram(false), lowLatency(false), systemsRegistered(false), + romExportsRegistered(false), hasLoadedSomething(false), midiOutClock(false), midiOutTime(false), @@ -1466,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 68794069e..33544e252 100644 --- a/src/engine/export.cpp +++ b/src/engine/export.cpp @@ -20,6 +20,7 @@ #include "engine.h" #include "export/amigaValidation.h" +#include "export/tiuna.h" DivROMExport* DivEngine::buildROM(DivROMExportOptions sys) { DivROMExport* exporter=NULL; @@ -27,6 +28,9 @@ DivROMExport* DivEngine::buildROM(DivROMExportOptions sys) { case DIV_ROM_AMIGA_VALIDATION: exporter=new DivExportAmigaValidation; break; + case DIV_ROM_TIUNA: + exporter=new DivExportTiuna; + break; default: exporter=new DivROMExport; break; diff --git a/src/engine/export.h b/src/engine/export.h index 9445ecfe9..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 }; @@ -52,39 +54,54 @@ struct DivROMExportProgress { class DivROMExport { protected: - std::vector exportLog; + DivConfig conf; std::vector output; - std::mutex logLock; void logAppend(String what); public: + 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(); + 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 c33c38502..c6d0ec799 100644 --- a/src/engine/export/abstract.cpp +++ b/src/engine/export/abstract.cpp @@ -36,9 +36,9 @@ bool DivROMExport::hasFailed() { return true; } -DivROMExportProgress DivROMExport::getProgress() { +DivROMExportProgress DivROMExport::getProgress(int index) { DivROMExportProgress ret; - ret.name="Test"; + ret.name=""; ret.amount=0.0f; return ret; } @@ -55,3 +55,7 @@ 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 e9a8a8b84..5fce7a920 100644 --- a/src/engine/export/amigaValidation.cpp +++ b/src/engine/export/amigaValidation.cpp @@ -70,6 +70,7 @@ void DivExportAmigaValidation::run() { bool done=false; // sample.bin + logAppend("writing samples..."); SafeWriter* sample=new SafeWriter; sample->init(); for (int i=0; i<256; i++) { @@ -79,6 +80,7 @@ void DivExportAmigaValidation::run() { if (sample->tell()&1) sample->writeC(0); // seq.bin + logAppend("making sequence..."); SafeWriter* seq=new SafeWriter; seq->init(); @@ -239,6 +241,7 @@ void DivExportAmigaValidation::run() { EXTERN_BUSY_END; // wave.bin + logAppend("writing wavetables..."); SafeWriter* wave=new SafeWriter; wave->init(); for (WaveEntry& i: waves) { @@ -246,6 +249,7 @@ void DivExportAmigaValidation::run() { } // sbook.bin + logAppend("writing sample book..."); SafeWriter* sbook=new SafeWriter; sbook->init(); for (SampleBookEntry& i: sampleBook) { @@ -255,6 +259,7 @@ void DivExportAmigaValidation::run() { } // wbook.bin + logAppend("writing wavetable book..."); SafeWriter* wbook=new SafeWriter; wbook->init(); for (WaveEntry& i: waves) { @@ -272,6 +277,8 @@ void DivExportAmigaValidation::run() { output.push_back(DivROMExportOutput("wave.bin",wave)); output.push_back(DivROMExportOutput("seq.bin",seq)); + logAppend("finished!"); + running=false; } @@ -296,3 +303,7 @@ void DivExportAmigaValidation::abort() { 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 42703a36e..df8131652 100644 --- a/src/engine/export/amigaValidation.h +++ b/src/engine/export/amigaValidation.h @@ -29,6 +29,7 @@ class DivExportAmigaValidation: public DivROMExport { public: 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 64% rename from src/engine/tiunaOps.cpp rename to src/engine/export/tiuna.cpp index 8a045cdf8..397cee34f 100644 --- a/src/engine/tiunaOps.cpp +++ b/src/engine/export/tiuna.cpp @@ -17,13 +17,14 @@ * 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; @@ -180,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" @@ -321,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; @@ -349,15 +371,30 @@ SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel, int cmdSize=renderedCmds.size(); bool* processed=new bool[cmdSize]; memset(processed,0,cmdSize*sizeof(bool)); - logI("max cmId: %d",(MAX(firstBankSize/1024,1))*256); - while (firstBankSize>768 && cmId<(MAX(firstBankSize/1024,1))*256) { - logI("start CM %04x...",cmId); + 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; - logD("scan %d size...",cmdSize-1); 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; jmaxPMVal) { maxPMVal=i.second.bytesSaved; @@ -440,16 +476,19 @@ SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel, } } int maxPMLen=potentialMatches[maxPMIdx].length; - logV("the other step..."); for (const int i: potentialMatches[maxPMIdx].pos) { confirmedMatches.push_back({i,i+maxPMLen,0,cmId}); 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; @@ -572,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 9a82577e6..18f8b4871 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -180,6 +180,7 @@ const char* aboutLine[]={ "Slightly Large NC", "smaybius", "SnugglyBun", + "Someone64", "Spinning Square Waves", "src3453", "SuperJet Spade", diff --git a/src/gui/exportOptions.cpp b/src/gui/exportOptions.cpp index 4b4360839..aca5523f0 100644 --- a/src/gui/exportOptions.cpp +++ b/src/gui/exportOptions.cpp @@ -239,6 +239,105 @@ void FurnaceGUI::drawExportVGM(bool onWindow) { } } +void FurnaceGUI::drawExportROM(bool onWindow) { + exitDisabledTimer=1; + + const DivROMExportDef* def=e->getROMExportDef(romTarget); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("##ROMTarget",def==NULL?"