here we go!

pull request #1706
This commit is contained in:
tildearrow 2024-09-30 03:37:22 -05:00
parent 5a587a6af6
commit 54745bec3b
5 changed files with 136 additions and 409 deletions

View file

@ -671,6 +671,7 @@ class DivEngine {
// add every export method here // add every export method here
friend class DivROMExport; friend class DivROMExport;
friend class DivExportAmigaValidation; friend class DivExportAmigaValidation;
friend class DivExportSAPR;
friend class DivExportTiuna; friend class DivExportTiuna;
friend class DivExportZSM; friend class DivExportZSM;

View file

@ -47,6 +47,7 @@ void DivROMExport::logAppend(String what) {
logLock.lock(); logLock.lock();
exportLog.push_back(what); exportLog.push_back(what);
logLock.unlock(); logLock.unlock();
logD("export: %s",what);
} }
void DivROMExport::wait() { void DivROMExport::wait() {

View file

@ -17,159 +17,140 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
// thanks asiekierka!
// I have ported your code to this ROM export framework.
#include "sapr.h" #include "sapr.h"
#include "../engine.h" #include "../engine.h"
#include "../ta-log.h" #include "../ta-log.h"
#include <fmt/printf.h> #include <fmt/printf.h>
#include <algorithm> #include <array>
#include <map>
#include <tuple>
#include <vector> #include <vector>
void DivExportSAPR::run() { constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0;
int loopOrder, loopOrderRow, loopEnd; constexpr int MASTER_CLOCK_MASK=(sizeof(void*)==8)?0xff:0;
int tick=0;
SafeWriter* w;
std::map<int,SAPRCmd> allCmds[2];
// config static String ticksToTime(double rate, int ticks) {
String baseLabel=conf.getString("baseLabel","song"); double timing = ticks / rate;
int firstBankSize=conf.getInt("firstBankSize",3072);
int otherBankSize=conf.getInt("otherBankSize",4096-48); return fmt::sprintf("%02d:%02d.%03d",
int tiaIdx=conf.getInt("sysToExport",-1); (int) timing / 60,
(int) timing % 60,
(int) (timing * 1000) % 1000
);
}
void DivExportSAPR::run() {
int sapScanlines=0; // TODO: property!
int POKEY=-1;
int IGNORED=0;
bool palTiming=(e->song.systemFlags[POKEY].getInt("clockSel",0) != 0);
int scanlinesPerFrame = (palTiming?312:262);
size_t tickCount=0;
std::vector<std::array<uint8_t, 9>> regs;
if (sapScanlines <= 0) {
sapScanlines = scanlinesPerFrame;
}
//double sapRate = (palTiming?49.86:59.92) * scanlinesPerFrame / sapScanlines;
double sapRate = (palTiming?50:60) * (double)scanlinesPerFrame / (double)sapScanlines;
// Locate system index.
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i] == DIV_SYSTEM_POKEY) {
if (POKEY>=0) {
IGNORED++;
logAppendf("Ignoring duplicate POKEY id %d",i);
break;
}
POKEY=i;
logAppendf("POKEY detected as chip id %d",i);
break;
} else {
IGNORED++;
logAppendf("Ignoring chip id %d, system id %d",i,(int)e->song.system[i]);
break;
}
}
if (POKEY<0) {
logAppendf("ERROR: Could not find POKEY");
failed=true;
running=false;
return;
}
if (IGNORED>0) {
logAppendf("WARNING: SAP export ignoring %d unsupported system%c",IGNORED,IGNORED>1?'s':' ');
}
e->stop(); e->stop();
e->repeatPattern=false; e->repeatPattern=false;
e->shallStop=false;
e->setOrder(0); e->setOrder(0);
logAppend("playing and logging register writes...");
e->synchronizedSoft([&]() { e->synchronizedSoft([&]() {
// determine loop point double origRate = e->got.rate;
// bool stopped=false; e->got.rate=sapRate;
loopOrder=0;
loopOrderRow=0;
loopEnd=0;
e->walkSong(loopOrder,loopOrderRow,loopEnd);
logAppendf("loop point: %d %d",loopOrder,loopOrderRow);
w=new SafeWriter; // Determine loop point.
w->init(); int loopOrder=0;
int loopRow=0;
int loopEnd=0;
e->walkSong(loopOrder,loopRow,loopEnd);
logAppendf("loop point: %d %d",loopOrder,loopRow);
e->warnings="";
if (tiaIdx<0 || tiaIdx>=e->song.systemLen) { // Reset the playback state.
tiaIdx=-1; e->curOrder=0;
for (int i=0; i<e->song.systemLen; i++) { e->freelance=false;
if (e->song.system[i]==DIV_SYSTEM_TIA) { e->playing=false;
tiaIdx=i; e->extValuePresent=false;
break; e->remainingLoops=-1;
}
}
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); // Prepare to write song data.
// write patterns
// bool writeLoop=false;
logAppend("recording sequence...");
bool done=false;
e->playSub(false); e->playSub(false);
bool done=false;
// int loopTick=-1; int fracWait=0; // accumulates fractional ticks
SAPRLast last[2]; e->disCont[POKEY].dispatch->toggleRegisterDump(true);
SAPRNew news[2]; std::array<uint8_t, 9> currRegs;
while (!done) { while (!done) {
// TODO implement loop if (e->nextTick() || !e->playing) {
// 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]=SAPRLast();
// last[i].pitch=-1;
// last[i].ins=-1;
// last[i].vol=-1;
// }
// }
if (e->nextTick(false,true) || !e->playing) {
// stopped=!playing;
done=true; done=true;
for (int i=0; i<e->song.systemLen; i++) {
e->disCont[i].dispatch->getRegisterWrites().clear();
}
break; break;
} }
for (int i=0; i<2; i++) {
news[i]=SAPRNew();
}
// get register dumps // get register dumps
std::vector<DivRegWrite>& writes=e->disCont[tiaIdx].dispatch->getRegisterWrites(); std::vector<DivRegWrite>& writes=e->disCont[POKEY].dispatch->getRegisterWrites();
for (const DivRegWrite& i: writes) { if (writes.size() > 0) {
switch (i.addr) { logAppendf("saprOps: found %d messages",writes.size());
case 0xfffe0000: for (DivRegWrite& write: writes)
case 0xfffe0001: if ((write.addr & 0xF) < 9)
news[i.addr&1].pitch=i.val; currRegs[write.addr & 0xF] = write.val;
break; writes.clear();
case 0xfffe0002: }
news[0].sync=i.val;
break; // write wait
case 0x15: tickCount++;
case 0x16: int totalWait=e->cycles>>MASTER_CLOCK_PREC;
news[i.addr-0x15].ins=i.val; fracWait+=e->cycles&MASTER_CLOCK_MASK;
break; totalWait+=fracWait>>MASTER_CLOCK_PREC;
case 0x19: fracWait&=MASTER_CLOCK_MASK;
case 0x1a: if (totalWait>0 && !done) {
news[i.addr-0x19].vol=i.val; while (totalWait) {
break; regs.push_back(currRegs);
default: break; totalWait--;
tickCount++;
} }
} }
writes.clear();
// collect changes
for (int i=0; i<2; i++) {
SAPRCmd 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; i<e->song.systemLen; i++) {
e->disCont[i].dispatch->getRegisterWrites().clear();
e->disCont[i].dispatch->toggleRegisterDump(false);
} }
// end of song
// done - close out.
e->got.rate=origRate;
e->disCont[POKEY].dispatch->toggleRegisterDump(false);
e->remainingLoops=-1; e->remainingLoops=-1;
e->playing=false; e->playing=false;
@ -177,286 +158,30 @@ void DivExportSAPR::run() {
e->extValuePresent=false; e->extValuePresent=false;
}); });
if (failed) return; logAppend("writing data...");
progress[0].amount=0.95f;
// render commands auto w = new SafeWriter;
logAppend("rendering commands..."); w->init();
std::vector<SAPRBytes> renderedCmds; // Write SAP header: Author, name, timing, type.
w->writeText(fmt::format( w->writeText(fmt::sprintf("SAP\r\nAUTHOR \"%s\"\r\nNAME \"%s\"\r\n%s\r\nTYPE R\r\n",
"; Generated by Furnace " DIV_VERSION "\n" e->song.author, e->song.name, palTiming ? "PAL" : "NTSC"
"; Name: {}\n"
"; Author: {}\n"
"; Album: {}\n"
"; Subsong #{}: {}\n\n",
e->song.name,e->song.author,e->song.category,e->curSubSongIndex+1,e->curSubSong->name
)); ));
for (int i=0; i<2; i++) { if (sapScanlines != scanlinesPerFrame) {
SAPRCmd lastCmd; // Fastplay.
int lastTick=0; w->writeText(fmt::sprintf("FASTPLAY %d\r\n", sapScanlines));
int lastWait=0;
// bool looped=false;
for (auto& kv: allCmds[i]) {
// if (!looped && !stopped && loopTick>=0 && kv.first>=loopTick) {
// writeCmd(w,&lastCmd,&lastWait,loopTick-lastTick);
// w->writeText(".loop\n");
// lastTick=loopTick;
// looped=true;
// }
writeCmd(renderedCmds,lastCmd,i,lastWait,lastTick,kv.first);
lastTick=kv.first;
lastCmd=kv.second;
}
writeCmd(renderedCmds,lastCmd,i,lastWait,lastTick,tick);
// if (stopped || loopTick<0) w->writeText(".loop\n db 0\n");
} }
// compress commands // Time.
std::vector<SAPRMatch> confirmedMatches; w->writeText(fmt::sprintf("TIME %s\r\n", ticksToTime(sapRate, tickCount)));
std::vector<int> callTicks; w->writeText("\r\n");
int cmId=0; // Registers.
int cmdSize=renderedCmds.size(); for (auto atRegs : regs) {
bool* processed=new bool[cmdSize]; w->write(atRegs.data(), 9 * sizeof(uint8_t));
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<maxCmId) {
if (mustAbort) {
logAppend("aborted!");
failed=true;
running=false;
delete[] processed;
return;
}
float theOtherSide=pow(1.0/float(MAX(1,lastMaxPMVal)),0.2)*0.98;
progress[0].amount=theOtherSide+(1.0-theOtherSide)*((float)cmId/(float)maxCmId);
logAppendf("start CM %04x...",cmId);
std::map<int,SAPRMatches> potentialMatches;
for (int i=0; i<cmdSize-1;) {
// continue and skip if it's part of previous confirmed matches
while (i<cmdSize-1 && processed[i]) i++;
if (i>=cmdSize-1) break;
progress[1].amount=(float)i/(float)(cmdSize-1);
std::vector<SAPRMatch> match;
int ch=renderedCmds[i].ch;
for (int j=i+1; j<cmdSize;) {
if (processed[i]) break;
//while (j<cmdSize && processed[i]) j++;
if (j>=cmdSize) break;
int k=0;
int ticks=0;
int size=0;
while (
(i+k)<j && (i+k)<cmdSize && (j+k)<cmdSize &&
(ticks+renderedCmds[i+k].ticks)<=256 &&
// match runs can't cross channels
// as channel end command would be insterted there later
renderedCmds[i+k].ch==ch &&
renderedCmds[j+k].ch==ch &&
renderedCmds[i+k]==renderedCmds[j+k] &&
!processed[i+k] && !processed[j+k]
) {
ticks+=renderedCmds[i+k].ticks;
size+=renderedCmds[i+k].size;
k++;
}
if (size>2) match.push_back(SAPRMatch(j,j+k,size,0));
if (k==0) k++;
j+=k;
}
if (match.empty()) {
i++;
continue;
}
// find a length that results in most bytes saved
SAPRMatches matches;
int curSize=0;
int curLength=1;
int curTicks=0;
while (true) {
int bytesSaved=-4;
bool found=false;
for (const SAPRMatch& j: match) {
if ((j.endPos-j.pos)>=curLength) {
if (!found) {
found=true;
curSize+=renderedCmds[i+curLength-1].size;
curTicks+=renderedCmds[i+curLength-1].ticks;
}
bytesSaved+=curSize-2;
}
}
if (!found) break;
if (bytesSaved>matches.bytesSaved) {
matches.length=curLength;
matches.bytesSaved=bytesSaved;
matches.ticks=curTicks;
}
curLength++;
}
if (matches.bytesSaved>0) {
matches.pos.push_back(i);
for (const SAPRMatch& j: match) {
if ((j.endPos-j.pos)>=matches.length) {
matches.pos.push_back(j.pos);
}
}
potentialMatches[i]=matches;
}
i++;
}
if (potentialMatches.empty()) {
logAppend("potentialMatches is empty");
break;
}
int maxPMIdx=0;
int maxPMVal=0;
for (const auto& i: potentialMatches) {
if (i.second.bytesSaved>maxPMVal) {
maxPMVal=i.second.bytesSaved;
maxPMIdx=i.first;
}
}
int maxPMLen=potentialMatches[maxPMIdx].length;
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);
logAppendf("CM %04x added: pos=%d,len=%d,matches=%d,saved=%d",cmId,maxPMIdx,maxPMLen,potentialMatches[maxPMIdx].pos.size(),maxPMVal);
lastMaxPMVal=maxPMVal;
cmId++;
} }
output.push_back(DivROMExportOutput("export.sap",w));
progress[0].amount=1.0f; progress[0].amount=1.0f;
progress[1].amount=1.0f;
logAppend("generating data...");
delete[] processed;
std::sort(confirmedMatches.begin(),confirmedMatches.end(),[](const SAPRMatch& l, const SAPRMatch& r){
return l.pos<r.pos;
});
// ignore last call IDs >256 that don't fill up a page
// as they tends to increase the final size due to page alignment
int cmIdLen=cmId>256?(cmId&~255):cmId;
// overlap check
for (int i=1; i<(int)confirmedMatches.size(); i++) {
if (confirmedMatches[i-1].endPos<=confirmedMatches[i].pos) continue;
logAppend("ERROR: impossible overlap found in matches list, please report");
failed=true;
running=false;
return;
}
// write commands
int totalSize=0;
int cnt=cmIdLen;
w->writeText(fmt::format(" .section {0}_bank0\n .align $100\n{0}_calltable",baseLabel));
while (cnt>0) {
int cnt2=MIN(cnt,256);
w->writeText("\n .byte ");
for (int j=0; j<cnt2; j++) {
w->writeText(fmt::format("<{}_c{},",baseLabel,cmIdLen-cnt+j));
}
for (int j=cnt2; j<256; j++) {
w->writeText("0,");
}
w->seek(-1,SEEK_CUR);
w->writeText("\n .byte ");
for (int j=0; j<cnt2; j++) {
w->writeText(fmt::format(">{}_c{},",baseLabel,cmIdLen-cnt+j));
}
for (int j=cnt2; j<256; j++) {
w->writeText("0,");
}
w->seek(-1,SEEK_CUR);
w->writeText("\n .byte ");
for (int j=0; j<cnt2; j++) {
w->writeText(fmt::format("{}_c{}>>13,",baseLabel,cmIdLen-cnt+j));
}
for (int j=cnt2; j<256; j++) {
w->writeText("0,");
}
w->seek(-1,SEEK_CUR);
w->writeText("\n .byte ");
for (int j=0; j<cnt2; j++) {
w->writeText(fmt::format("{},",callTicks[cmIdLen-cnt+j]&0xff));
}
w->seek(-1,SEEK_CUR);
totalSize+=768+cnt2;
cnt-=cnt2;
}
w->writeC('\n');
if (totalSize>firstBankSize) {
logAppend("ERROR: first bank is not large enough to contain call table");
failed=true;
running=false;
return;
}
int curBank=0;
int bankSize=totalSize;
int maxBankSize=firstBankSize;
int curCh=-1;
std::vector<bool> callVisited=std::vector<bool>(cmIdLen,false);
auto cmIter=confirmedMatches.begin();
for (int i=0; i<(int)renderedCmds.size(); i++) {
int writeCall=-1;
SAPRBytes cmd=renderedCmds[i];
if (cmIter!=confirmedMatches.end() && i==cmIter->pos) {
if (cmIter->id<cmIdLen) {
if (callVisited[cmIter->id]) {
unsigned char idLo=cmIter->id&0xff;
unsigned char idHi=cmIter->id>>8;
cmd=SAPRBytes(cmd.ch,0,2,{idHi,idLo});
i=cmIter->endPos-1;
} else {
writeCall=cmIter->id;
callVisited[writeCall]=true;
}
}
cmIter++;
}
if (cmd.ch!=curCh) {
if (curCh>=0) {
w->writeText(" .text x\"e0\"\n");
totalSize++;
bankSize++;
}
if (bankSize+cmd.size>=maxBankSize) {
maxBankSize=otherBankSize;
curBank++;
w->writeText(fmt::format(" .endsection\n\n .section {}_bank{}",baseLabel,curBank));
bankSize=0;
}
w->writeText(fmt::format("\n{}_ch{}\n",baseLabel,cmd.ch));
curCh=cmd.ch;
}
if (bankSize+cmd.size+1>=maxBankSize) {
maxBankSize=otherBankSize;
curBank++;
w->writeText(fmt::format(" .text x\"c0\"\n .endsection\n\n .section {}_bank{}\n",baseLabel,curBank));
totalSize++;
bankSize=0;
}
if (writeCall>=0) {
w->writeText(fmt::format("{}_c{}\n",baseLabel,writeCall));
}
w->writeText(" .text x\"");
for (int j=0; j<cmd.size; j++) {
w->writeText(fmt::format("{:02x}",cmd.buf[j]));
}
w->writeText("\"\n");
totalSize+=cmd.size;
bankSize+=cmd.size;
}
w->writeText(" .text x\"e0\"\n .endsection\n");
totalSize++;
logAppendf("total size: %d bytes (%d banks)",totalSize,curBank+1);
output.push_back(DivROMExportOutput("export.asm",w));
logAppend("finished!"); logAppend("finished!");
@ -464,10 +189,8 @@ void DivExportSAPR::run() {
} }
bool DivExportSAPR::go(DivEngine* eng) { bool DivExportSAPR::go(DivEngine* eng) {
progress[0].name="Compression"; progress[0].name="Progress";
progress[0].amount=0.0f; progress[0].amount=0.0f;
progress[1].name="Confirmed Matches";
progress[1].amount=0.0f;
e=eng; e=eng;
running=true; running=true;
@ -479,6 +202,7 @@ bool DivExportSAPR::go(DivEngine* eng) {
void DivExportSAPR::wait() { void DivExportSAPR::wait() {
if (exportThread!=NULL) { if (exportThread!=NULL) {
logV("waiting for export thread...");
exportThread->join(); exportThread->join();
delete exportThread; delete exportThread;
} }
@ -498,6 +222,6 @@ bool DivExportSAPR::hasFailed() {
} }
DivROMExportProgress DivExportSAPR::getProgress(int index) { DivROMExportProgress DivExportSAPR::getProgress(int index) {
if (index<0 || index>2) return progress[2]; if (index<0 || index>1) return progress[1];
return progress[index]; return progress[index];
} }

View file

@ -24,7 +24,7 @@
class DivExportSAPR: public DivROMExport { class DivExportSAPR: public DivROMExport {
DivEngine* e; DivEngine* e;
std::thread* exportThread; std::thread* exportThread;
DivROMExportProgress progress[3]; DivROMExportProgress progress[2];
bool running, failed, mustAbort; bool running, failed, mustAbort;
void run(); void run();
public: public:

View file

@ -37,6 +37,7 @@ const char* aboutLine[]={
_N("A M 4 N (intro tune)"), _N("A M 4 N (intro tune)"),
"Adam Lederer", "Adam Lederer",
"akumanatt", "akumanatt",
"asiekierka",
"cam900", "cam900",
"djtuBIG-MaliceX", "djtuBIG-MaliceX",
"Eknous", "Eknous",