earliest VGM export!

only supports single-chip Genesis for now
the other systems will be added shortly
This commit is contained in:
tildearrow 2022-01-24 01:01:08 -05:00
parent 35ee06d6cf
commit 858d5343b8
6 changed files with 284 additions and 52 deletions

View File

@ -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.

View File

@ -22,6 +22,8 @@
#include <sndfile.h>
#include <fmt/printf.h>
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; i<song.systemLen; i++) {
bool willExport=false;
willExport[i]=false;
streamIDs[i]=0;
switch (song.system[i]) {
case DIV_SYSTEM_GENESIS:
case DIV_SYSTEM_GENESIS_EXT:
if (!hasOPN2) {
hasOPN2=7670454;
willExport=true;
willExport[i]=true;
}
if (!hasSN) {
hasSN=3579545;
willExport=true;
willExport[i]=true;
}
break;
case DIV_SYSTEM_SMS:
if (!hasSN) {
hasSN=3579545;
willExport=true;
willExport[i]=true;
}
break;
case DIV_SYSTEM_GB:
if (!hasGB) {
hasGB=4194304;
willExport=true;
willExport[i]=true;
}
break;
case DIV_SYSTEM_PCE:
if (!hasPCE) {
hasPCE=3579545;
willExport=true;
willExport[i]=true;
}
break;
case DIV_SYSTEM_NES:
if (!hasNES) {
hasNES=1789773;
willExport=true;
willExport[i]=true;
}
break;
case DIV_SYSTEM_ARCADE:
if (!hasOPM) {
hasOPM=3579545;
willExport=true;
willExport[i]=true;
}
if (!hasSegaPCM) {
hasSegaPCM=4000000;
willExport=true;
willExport[i]=true;
}
break;
case DIV_SYSTEM_YM2610:
case DIV_SYSTEM_YM2610_EXT:
if (!hasOPNB) {
hasOPNB=8000000;
willExport=true;
willExport[i]=true;
}
break;
case DIV_SYSTEM_AY8910:
case DIV_SYSTEM_AY8930:
if (!hasAY) {
hasAY=1789773;
willExport=true;
ayConfig=(song.system[i]==DIV_SYSTEM_AY8930)?3:0;
ayFlags=1;
willExport[i]=true;
}
break;
case DIV_SYSTEM_SAA1099:
if (!hasSAA) {
hasSAA=8000000;
willExport=true;
willExport[i]=true;
}
break;
case DIV_SYSTEM_YM2612:
if (!hasOPN2) {
hasOPN2=7670454;
willExport=true;
willExport[i]=true;
}
break;
case DIV_SYSTEM_YM2151:
if (!hasOPM) {
hasOPM=3579545;
willExport=true;
willExport[i]=true;
}
break;
default:
break;
}
if (willExport) {
if (willExport[i]) {
disCont[i].dispatch->toggleRegisterDump(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; i<song.sampleLen; i++) {
DivSample* sample=song.sample[i];
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0); // for now!
w->writeI(sample->rendLength);
if (sample->depth==8) {
for (unsigned int j=0; j<sample->rendLength; j++) {
w->writeC((unsigned char)sample->rendData[j]+0x80);
}
} else {
for (unsigned int j=0; j<sample->rendLength; j++) {
w->writeC(((unsigned short)sample->rendData[j]+0x8000)>>8);
}
}
}
// initialize streams
int streamID=0;
for (int i=0; i<song.systemLen; i++) {
if (!willExport[i]) continue;
streamIDs[i]=streamID;
switch (song.system[i]) {
case DIV_SYSTEM_GENESIS:
case DIV_SYSTEM_GENESIS_EXT:
w->writeC(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; i<song.systemLen; i++) {
for (DivRegWrite& j: disCont[i].dispatch->getRegisterWrites()) {
printf("- (%d) %.2x = %.2x\n",i,j.addr,j.val);
std::vector<DivRegWrite>& 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; i<song.systemLen; i++) {
disCont[i].dispatch->toggleRegisterDump(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;
}

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);