From 858d5343b8d88fef7e4e3b8c2892e0507a56a1c1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 24 Jan 2022 01:01:08 -0500 Subject: [PATCH] earliest VGM export! only supports single-chip Genesis for now the other systems will be added shortly --- src/engine/dispatch.h | 17 +- src/engine/engine.cpp | 296 +++++++++++++++++++++++++++----- src/engine/engine.h | 1 + src/engine/platform/genesis.cpp | 19 ++ src/engine/platform/genesis.h | 1 + src/engine/platform/sms.cpp | 2 +- 6 files changed, 284 insertions(+), 52 deletions(-) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index dc76fe61..bb5e596b 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -110,14 +110,21 @@ struct DivDelayedCommand { struct DivRegWrite { /** - * an address of 0xffffff00 indicates a Furnace specific command. + * an address of 0xffffxx00 indicates a Furnace specific command. * the following addresses are available: - * - 0xffffff00: start sample playback + * - 0xffffxx00: start sample playback + * - xx is the instance ID * - data is the sample number + * - 0xffffxx01: set sample rate + * - xx is the instance ID + * - data is the sample rate + * - 0xffffxx02: stop sample playback + * - xx is the instance ID + * - 0xffffffff: reset */ unsigned int addr; - unsigned char val; - DivRegWrite(unsigned int a, unsigned char v): + unsigned short val; + DivRegWrite(unsigned int a, unsigned short v): addr(a), val(v) {} }; @@ -252,7 +259,7 @@ class DivDispatch { /** * enable register dumping. */ - void toggleRegisterDump(bool enable); + virtual void toggleRegisterDump(bool enable); /** * get register writes. diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 1e44fca4..c012392a 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -22,6 +22,8 @@ #include #include +constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0; + void process(void* u, float** in, float** out, int inChans, int outChans, unsigned int size) { ((DivEngine*)u)->nextBuf(in,out,inChans,outChans,size); } @@ -1907,6 +1909,54 @@ SafeWriter* DivEngine::saveDMF() { return w; } +void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff) { + if (write.addr>=0xffff0000) { // Furnace special command + unsigned char streamID=streamOff+((write.addr&0xff00)>>8); + switch (write.addr&0xff) { + case 0: // play sample + w->writeC(0x95); + w->writeC(streamID); + w->writeS(write.val); // sample number + w->writeC(0); // flags + break; + case 1: // set sample freq + w->writeC(0x92); + w->writeC(streamID); + w->writeI(write.val); + break; + case 2: // stop sample + w->writeC(0x94); + w->writeC(streamID); + break; + } + return; + } + switch (sys) { + case DIV_SYSTEM_GENESIS: + case DIV_SYSTEM_GENESIS_EXT: + switch (write.addr>>8) { + case 0: // port 0 + w->writeC(0x52); + w->writeC(write.addr&0xff); + w->writeC(write.val); + break; + case 1: // port 1 + w->writeC(0x53); + w->writeC(write.addr&0xff); + w->writeC(write.val); + break; + case 2: // PSG + w->writeC(0x50); + w->writeC(write.val); + break; + } + break; + default: + logW("write not handled!\n"); + break; + } +} + SafeWriter* DivEngine::saveVGM() { stop(); setOrder(0); @@ -1916,44 +1966,52 @@ SafeWriter* DivEngine::saveVGM() { bool done=false; int hasSN=0; - //int hasOPLL=0; + int snNoiseConfig=9; + int snNoiseSize=16; + int snFlags=0; + int hasOPLL=0; int hasOPN2=0; int hasOPM=0; int hasSegaPCM=0; - //int hasRFC=0; - //int hasOPN=0; - //int hasOPNA=0; + int segaPCMOffset=0x2000; + int hasRFC=0; + int hasOPN=0; + int hasOPNA=0; int hasOPNB=0; - //int hasOPL2=0; - //int hasOPL=0; - //int hasY8950=0; - //int hasOPL3=0; - //int hasZ280=0; - //int hasRFC1=0; - //int hasPWM=0; + int hasOPL2=0; + int hasOPL=0; + int hasY8950=0; + int hasOPL3=0; + int hasOPL4=0; + int hasOPX=0; + int hasZ280=0; + int hasRFC1=0; + int hasPWM=0; int hasAY=0; + int ayConfig=0; + int ayFlags=0; int hasGB=0; int hasNES=0; - //int hasMultiPCM=0; - //int hasuPD7759=0; - //int hasOKIM6258=0; - //int hasK054539=0; - //int hasOKIM6295=0; - //int hasK051649=0; - //int hasK054539=0; + int hasMultiPCM=0; + int hasuPD7759=0; + int hasOKIM6258=0; + int hasK054539=0; + int hasOKIM6295=0; + int hasK051649=0; int hasPCE=0; - //int hasNamco=0; - //int hasK053260=0; - //int hasPOKEY=0; - //int hasQSound=0; - //int hasSCSP=0; - //int hasSwan=0; - //int hasVSU=0; + int hasNamco=0; + int hasK053260=0; + int hasPOKEY=0; + int hasQSound=0; + int hasSCSP=0; + int hasSwan=0; + int hasVSU=0; int hasSAA=0; - //int hasES5503=0; - //int hasX1=0; - //int hasC352=0; - //int hasGA20=0; + int hasES5503=0; + int hasES5505=0; + int hasX1=0; + int hasC352=0; + int hasGA20=0; SafeWriter* w=new SafeWriter; w->init(); @@ -1963,108 +2021,254 @@ SafeWriter* DivEngine::saveVGM() { w->writeI(0); // will be written later w->writeI(0x171); // VGM 1.71 + bool willExport[32]; + int streamIDs[32]; + for (int i=0; itoggleRegisterDump(true); } } + // write chips and stuff + w->writeI(hasSN); + w->writeI(hasOPLL); + w->writeI(0); + w->writeI(0); // length. will be written later + w->writeI(0); // loop. will be written later + w->writeI(0); // loop length. why is this necessary? + w->writeI(0); // tick rate + w->writeS(snNoiseConfig); + w->writeC(snNoiseSize); + w->writeC(snFlags); + w->writeI(hasOPN2); + w->writeI(hasOPM); + w->writeI(0x100-w->tell()); // data pointer + w->writeI(hasSegaPCM); + w->writeI(segaPCMOffset); + w->writeI(hasRFC); + w->writeI(hasOPN); + w->writeI(hasOPNA); + w->writeI(hasOPNB); + w->writeI(hasOPL2); + w->writeI(hasOPL); + w->writeI(hasY8950); + w->writeI(hasOPL3); + w->writeI(hasOPL4); + w->writeI(hasOPX); + w->writeI(hasZ280); + w->writeI(hasRFC1); + w->writeI(hasPWM); + w->writeI(hasAY); + w->writeC(ayConfig); + w->writeC(ayFlags); + w->writeC(ayFlags); // OPN + w->writeC(ayFlags); // OPNA + w->writeC(0); // volume + w->writeC(0); // reserved + w->writeC(0); // loop count + w->writeC(0); // loop modifier + w->writeI(hasGB); + w->writeI(hasNES); + w->writeI(hasMultiPCM); + w->writeI(hasuPD7759); + w->writeI(hasOKIM6258); + w->writeC(0); // flags + w->writeC(0); // K flags + w->writeC(0); // C140 chip type + w->writeC(0); // reserved + w->writeI(hasOKIM6295); + w->writeI(hasK051649); + w->writeI(hasK054539); + w->writeI(hasPCE); + w->writeI(hasNamco); + w->writeI(hasK053260); + w->writeI(hasPOKEY); + w->writeI(hasQSound); + w->writeI(hasSCSP); + w->writeI(0); // extra header. currently not written to + w->writeI(hasSwan); + w->writeI(hasVSU); + w->writeI(hasSAA); + w->writeI(hasES5503); + w->writeI(hasES5505); + w->writeC(0); // 5503 chans + w->writeC(0); // 5505 chans + w->writeC(0); // C352 clock divider + w->writeC(0); // reserved + w->writeI(hasX1); + w->writeI(hasC352); + w->writeI(hasGA20); + for (int i=0; i<7; i++) { // reserved + w->writeI(0); + } + + // write samples + for (int i=0; iwriteC(0x67); + w->writeC(0x66); + w->writeC(0); // for now! + w->writeI(sample->rendLength); + if (sample->depth==8) { + for (unsigned int j=0; jrendLength; j++) { + w->writeC((unsigned char)sample->rendData[j]+0x80); + } + } else { + for (unsigned int j=0; jrendLength; j++) { + w->writeC(((unsigned short)sample->rendData[j]+0x8000)>>8); + } + } + } + + // initialize streams + int streamID=0; + for (int i=0; iwriteC(0x90); + w->writeC(streamID); + w->writeC(0x02); + w->writeC(0); // port + w->writeC(0x2a); // DAC + + w->writeC(0x91); + w->writeC(streamID); + w->writeC(0); + w->writeC(1); + w->writeC(0); + + w->writeC(0x92); + w->writeC(streamID); + w->writeI(32000); // default + streamID++; + break; + default: + logE("what? trying to play sample on unsupported system\n"); + break; + } + } + + // write song data + size_t tickCount=0; while (!done) { if (nextTick()) done=true; // get register dumps for (int i=0; igetRegisterWrites()) { - printf("- (%d) %.2x = %.2x\n",i,j.addr,j.val); + std::vector& writes=disCont[i].dispatch->getRegisterWrites(); + for (DivRegWrite& j: writes) { + performVGMWrite(w,song.system[i],j,streamIDs[i]); } + writes.clear(); } + // write wait + w->writeC(0x61); + w->writeS(cycles>>MASTER_CLOCK_PREC); + tickCount+=cycles>>MASTER_CLOCK_PREC; } for (int i=0; itoggleRegisterDump(false); } + // finish file + size_t len=w->size()-4; + w->seek(4,SEEK_SET); + w->writeI(len); + w->seek(0x18,SEEK_SET); + w->writeI(tickCount); + // loop not handled for now + w->writeI(0); + w->writeI(0); + isBusy.unlock(); return w; } diff --git a/src/engine/engine.h b/src/engine/engine.h index e733a7b2..e8c25f32 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -188,6 +188,7 @@ class DivEngine { void processRow(int i, bool afterDelay); void nextOrder(); void nextRow(); + void performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff); // returns true if end of song. bool nextTick(bool noAccum=false); bool perSystemEffect(int ch, unsigned char effect, unsigned char effectVal); diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index eeadd81b..47079ce2 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -192,6 +192,7 @@ void DivPlatformGenesis::tick() { immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff); if (chan[i].furnaceDac) { dacRate=(1280000*1.25)/chan[i].baseFreq; + if (dumpWrites) addWrite(0xffff0001,chan[i].baseFreq); } chan[i].freqChanged=false; } @@ -202,6 +203,11 @@ void DivPlatformGenesis::tick() { } psg.tick(); + + for (DivRegWrite& i: psg.getRegisterWrites()) { + if (dumpWrites) addWrite(i.addr,i.val); + } + psg.getRegisterWrites().clear(); } int DivPlatformGenesis::octave(int freq) { @@ -277,7 +283,10 @@ int DivPlatformGenesis::dispatch(DivCommand c) { dacSample=ins->amiga.initSample; if (dacSample<0 || dacSample>=parent->song.sampleLen) { dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); break; + } else { + if (dumpWrites) addWrite(0xffff0000,dacSample); } dacPos=0; dacPeriod=0; @@ -288,11 +297,15 @@ int DivPlatformGenesis::dispatch(DivCommand c) { dacSample=12*sampleBank+c.value%12; if (dacSample>=parent->song.sampleLen) { dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); break; + } else { + if (dumpWrites) addWrite(0xffff0000,dacSample); } dacPos=0; dacPeriod=0; dacRate=1280000/parent->song.sample[dacSample]->rate; + if (dumpWrites) addWrite(0xffff0001,parent->song.sample[dacSample]->rate); chan[c.chan].furnaceDac=false; } break; @@ -342,6 +355,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { case DIV_CMD_NOTE_OFF: if (c.chan==5) { dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); } chan[c.chan].keyOff=true; chan[c.chan].active=false; @@ -522,6 +536,11 @@ void DivPlatformGenesis::forceIns() { immWrite(0x22,lfoValue); } +void DivPlatformGenesis::toggleRegisterDump(bool enable) { + DivDispatch::toggleRegisterDump(enable); + psg.toggleRegisterDump(enable); +} + void DivPlatformGenesis::reset() { while (!writes.empty()) writes.pop(); OPN2_Reset(&fm); diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index e09ca389..157e1221 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -78,6 +78,7 @@ class DivPlatformGenesis: public DivDispatch { bool isStereo(); bool keyOffAffectsArp(int ch); bool keyOffAffectsPorta(int ch); + void toggleRegisterDump(bool enable); void setPAL(bool pal); void notifyInsChange(int ins); void notifyInsDeletion(void* ins); diff --git a/src/engine/platform/sms.cpp b/src/engine/platform/sms.cpp index 9d3f32ce..5dc5aeac 100644 --- a/src/engine/platform/sms.cpp +++ b/src/engine/platform/sms.cpp @@ -4,7 +4,7 @@ #define FREQ_BASE 1712.0f -#define rWrite(v) {sn->write(v); if (dumpWrites) {addWrite(0,v);} } +#define rWrite(v) {sn->write(v); if (dumpWrites) {addWrite(0x200,v);} } void DivPlatformSMS::acquire(short* bufL, short* bufR, size_t start, size_t len) { sn->sound_stream_update(bufL+start,len);