VGM export: write resets and GD3 tag

may be non-standard compliant (yet)
also it crashes foobar2000 for some reason but this will be fixed
This commit is contained in:
tildearrow 2022-01-25 18:46:27 -05:00
parent c7ee0ce642
commit 8bcab6e139
17 changed files with 388 additions and 6 deletions

View File

@ -80,6 +80,7 @@ endif()
set(ENGINE_SOURCES
src/log.cpp
src/fileutils.cpp
src/utfutils.cpp
extern/Nuked-OPN2/ym3438.c
extern/opm/opm.c
@ -176,7 +177,6 @@ if (NOT WIN32 AND NOT APPLE)
endif()
if (WIN32)
list(APPEND ENGINE_SOURCES src/utfutils.cpp)
list(APPEND ENGINE_SOURCES src/engine/winStuff.cpp)
list(APPEND ENGINE_SOURCES res/furnace.rc)
endif()

View File

@ -6,6 +6,7 @@
#include "safeReader.h"
#include "../ta-log.h"
#include "../fileutils.h"
#include "../utfutils.h"
#include "../audio/sdl.h"
#include <cstddef>
#include <stdexcept>
@ -233,6 +234,53 @@ const char* DivEngine::getSystemName(DivSystem sys) {
return "Unknown";
}
const char* DivEngine::getSystemNameJ(DivSystem sys) {
switch (sys) {
case DIV_SYSTEM_NULL:
return "不明";
case DIV_SYSTEM_YMU759:
return "";
case DIV_SYSTEM_GENESIS:
return "メガドライブ";
case DIV_SYSTEM_SMS:
return "マスターシステム";
case DIV_SYSTEM_GB:
return "ゲームボーイ";
case DIV_SYSTEM_PCE:
return "PCエンジン";
case DIV_SYSTEM_NES:
return "ファミリーコンピュータ";
case DIV_SYSTEM_C64_6581:
return "コモドール64 (6581)";
case DIV_SYSTEM_C64_8580:
return "コモドール64 (8580)";
case DIV_SYSTEM_ARCADE:
return "Arcade";
case DIV_SYSTEM_GENESIS_EXT:
return "";
case DIV_SYSTEM_YM2610:
return "業務用ネオジオ";
case DIV_SYSTEM_YM2610_EXT:
return "";
// Furnace-specific systems
case DIV_SYSTEM_AY8910:
return "";
case DIV_SYSTEM_AMIGA:
return "";
case DIV_SYSTEM_YM2151:
return "";
case DIV_SYSTEM_YM2612:
return "";
case DIV_SYSTEM_TIA:
return "";
case DIV_SYSTEM_SAA1099:
return "";
case DIV_SYSTEM_AY8930:
return "";
}
return "不明";
}
bool DivEngine::isFMSystem(DivSystem sys) {
return (sys==DIV_SYSTEM_GENESIS ||
sys==DIV_SYSTEM_GENESIS_EXT ||
@ -1910,6 +1958,244 @@ SafeWriter* DivEngine::saveDMF() {
}
void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample) {
if (write.addr==0xffffffff) { // Furnace fake reset
switch (sys) {
case DIV_SYSTEM_GENESIS:
case DIV_SYSTEM_GENESIS_EXT:
case DIV_SYSTEM_YM2612:
for (int i=0; i<3; i++) { // set SL and RR to highest
w->writeC(0x52);
w->writeC(0x80+i);
w->writeC(0xff);
w->writeC(0x52);
w->writeC(0x84+i);
w->writeC(0xff);
w->writeC(0x52);
w->writeC(0x88+i);
w->writeC(0xff);
w->writeC(0x52);
w->writeC(0x8c+i);
w->writeC(0xff);
w->writeC(0x53);
w->writeC(0x80+i);
w->writeC(0xff);
w->writeC(0x53);
w->writeC(0x84+i);
w->writeC(0xff);
w->writeC(0x53);
w->writeC(0x88+i);
w->writeC(0xff);
w->writeC(0x53);
w->writeC(0x8c+i);
w->writeC(0xff);
}
for (int i=0; i<3; i++) { // note off
w->writeC(0x52);
w->writeC(0x28);
w->writeC(i);
w->writeC(0x52);
w->writeC(0x28);
w->writeC(4+i);
}
w->writeC(0x52); // disable DAC
w->writeC(0x2b);
w->writeC(0);
if (sys!=DIV_SYSTEM_YM2612) {
for (int i=0; i<4; i++) {
w->writeC(0x50);
w->writeC(0x90|(i<<5)|15);
}
}
break;
case DIV_SYSTEM_SMS:
for (int i=0; i<4; i++) {
w->writeC(0x50);
w->writeC(0x90|(i<<5)|15);
}
break;
case DIV_SYSTEM_GB:
// square 1
w->writeC(0xb3);
w->writeC(2);
w->writeC(0);
w->writeC(0xb3);
w->writeC(4);
w->writeC(0x80);
// square 2
w->writeC(0xb3);
w->writeC(7);
w->writeC(0);
w->writeC(0xb3);
w->writeC(9);
w->writeC(0x80);
// wave
w->writeC(0xb3);
w->writeC(0x0c);
w->writeC(0);
w->writeC(0xb3);
w->writeC(0x0e);
w->writeC(0x80);
// noise
w->writeC(0xb3);
w->writeC(0x11);
w->writeC(0);
w->writeC(0xb3);
w->writeC(0x13);
w->writeC(0x80);
break;
case DIV_SYSTEM_PCE:
for (int i=0; i<6; i++) {
w->writeC(0xb9);
w->writeC(0);
w->writeC(i);
w->writeC(0xb9);
w->writeC(4);
w->writeC(0);
}
break;
case DIV_SYSTEM_NES:
w->writeC(0xb4);
w->writeC(0x15);
w->writeC(0);
break;
case DIV_SYSTEM_ARCADE:
case DIV_SYSTEM_YM2151:
for (int i=0; i<8; i++) {
w->writeC(0x54);
w->writeC(0xe0+i);
w->writeC(0xff);
w->writeC(0x54);
w->writeC(0xe8+i);
w->writeC(0xff);
w->writeC(0x54);
w->writeC(0xf0+i);
w->writeC(0xff);
w->writeC(0x54);
w->writeC(0xf8+i);
w->writeC(0xff);
w->writeC(0x54);
w->writeC(0x08);
w->writeC(i);
}
if (sys==DIV_SYSTEM_ARCADE) {
for (int i=0; i<5; i++) {
w->writeC(0xc0);
w->writeS(0x86+(i<<3));
w->writeC(3);
}
}
break;
case DIV_SYSTEM_YM2610:
case DIV_SYSTEM_YM2610_EXT:
for (int i=0; i<2; i++) { // set SL and RR to highest
w->writeC(0x58);
w->writeC(0x81+i);
w->writeC(0xff);
w->writeC(0x58);
w->writeC(0x85+i);
w->writeC(0xff);
w->writeC(0x58);
w->writeC(0x89+i);
w->writeC(0xff);
w->writeC(0x58);
w->writeC(0x8d+i);
w->writeC(0xff);
w->writeC(0x59);
w->writeC(0x81+i);
w->writeC(0xff);
w->writeC(0x59);
w->writeC(0x85+i);
w->writeC(0xff);
w->writeC(0x59);
w->writeC(0x89+i);
w->writeC(0xff);
w->writeC(0x59);
w->writeC(0x8d+i);
w->writeC(0xff);
}
for (int i=0; i<2; i++) { // note off
w->writeC(0x58);
w->writeC(0x28);
w->writeC(1+i);
w->writeC(0x58);
w->writeC(0x28);
w->writeC(5+i);
}
// reset AY
w->writeC(0x58);
w->writeC(7);
w->writeC(0x3f);
w->writeC(0x58);
w->writeC(8);
w->writeC(0);
w->writeC(0x58);
w->writeC(9);
w->writeC(0);
w->writeC(0x58);
w->writeC(10);
w->writeC(0);
// reset sample
w->writeC(0x59);
w->writeC(0);
w->writeC(0xbf);
break;
case DIV_SYSTEM_AY8910:
w->writeC(0xa0);
w->writeC(7);
w->writeC(0x3f);
w->writeC(0xa0);
w->writeC(8);
w->writeC(0);
w->writeC(0xa0);
w->writeC(9);
w->writeC(0);
w->writeC(0xa0);
w->writeC(10);
w->writeC(0);
break;
case DIV_SYSTEM_AY8930:
w->writeC(0xa0);
w->writeC(0x0d);
w->writeC(0);
w->writeC(0xa0);
w->writeC(0x0d);
w->writeC(0xa0);
break;
case DIV_SYSTEM_SAA1099:
w->writeC(0xbd);
w->writeC(0x1c);
w->writeC(0x02);
w->writeC(0xbd);
w->writeC(0x14);
w->writeC(0);
w->writeC(0xbd);
w->writeC(0x15);
w->writeC(0);
for (int i=0; i<6; i++) {
w->writeC(0xbd);
w->writeC(i);
w->writeC(0);
}
break;
default:
break;
}
}
if (write.addr>=0xffff0000) { // Furnace special command
unsigned char streamID=streamOff+((write.addr&0xff00)>>8);
switch (write.addr&0xff) {
@ -1996,6 +2282,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
}
break;
case DIV_SYSTEM_YM2610:
case DIV_SYSTEM_YM2610_EXT:
switch (write.addr>>8) {
case 0: // port 0
w->writeC(0x58);
@ -2086,6 +2373,8 @@ SafeWriter* DivEngine::saveVGM() {
bool done=false;
int writeCount=0;
int gd3Off=0;
int hasSN=0;
int snNoiseConfig=9;
int snNoiseSize=16;
@ -2542,7 +2831,15 @@ SafeWriter* DivEngine::saveVGM() {
writeLoop=true;
}
}
if (nextTick()) done=true;
if (nextTick()) {
done=true;
// stop all streams
for (int i=0; i<streamID; i++) {
w->writeC(0x94);
w->writeC(i);
loopSample[i]=-1;
}
}
// get register dumps
for (int i=0; i<song.systemLen; i++) {
std::vector<DivRegWrite>& writes=disCont[i].dispatch->getRegisterWrites();
@ -2625,11 +2922,48 @@ SafeWriter* DivEngine::saveVGM() {
disCont[i].dispatch->toggleRegisterDump(false);
}
// write GD3 tag
gd3Off=w->tell();
w->write("Gd3 ",4);
w->writeI(0x100);
w->writeI(0); // length. will be written later
WString ws;
ws=utf8To16(song.name.c_str());
w->writeWString(ws,false); // name
w->writeS(0); // japanese name
w->writeS(0); // game name
w->writeS(0); // japanese game name
if (song.systemLen>1) {
ws=L"Multiple Systems";
} else {
ws=utf8To16(getSystemName(song.system[0]));
}
w->writeWString(ws,false); // system name
if (song.systemLen>1) {
ws=L"複数システム";
} else {
ws=utf8To16(getSystemNameJ(song.system[0]));
}
w->writeWString(ws,false); // japanese system name
ws=utf8To16(song.author.c_str());
w->writeWString(ws,false); // author name
w->writeS(0); // japanese author name
w->writeS(0); // date
w->writeWString(L"Furnace Tracker",false); // ripper
w->writeS(0); // notes
int gd3Len=w->tell()-gd3Off-12;
w->seek(gd3Off+8,SEEK_SET);
w->writeI(gd3Len);
// finish file
size_t len=w->size()-4;
w->seek(4,SEEK_SET);
w->writeI(len);
w->seek(0x18,SEEK_SET);
w->seek(0x14,SEEK_SET);
w->writeI(gd3Off-0x14);
w->writeI(tickCount);
// loop not handled for now
printf("writing loop pos: %d\n",loopPos-0x1c);

View File

@ -303,6 +303,9 @@ class DivEngine {
// get sys name
const char* getSystemName(DivSystem sys);
// get japanese system name
const char* getSystemNameJ(DivSystem sys);
// convert sample rate format
int fileToDivRate(int frate);

View File

@ -556,6 +556,9 @@ void DivPlatformArcade::reset() {
memset(&fm,0,sizeof(opm_t));
OPM_Reset(&fm);
}
if (dumpWrites) {
addWrite(0xffffffff,0);
}
for (int i=0; i<13; i++) {
chan[i]=DivPlatformArcade::Channel();
chan[i].vol=0x7f;

View File

@ -297,6 +297,9 @@ void DivPlatformAY8910::reset() {
chan[i]=DivPlatformAY8910::Channel();
chan[i].vol=0x0f;
}
if (dumpWrites) {
addWrite(0xffffffff,0);
}
for (int i=0; i<16; i++) {
oldWrites[i]=-1;

View File

@ -339,6 +339,9 @@ void DivPlatformAY8930::reset() {
ayEnvSlide[i]=0;
ayEnvSlideLow[i]=0;
}
if (dumpWrites) {
addWrite(0xffffffff,0);
}
for (int i=0; i<32; i++) {
oldWrites[i]=-1;

View File

@ -305,6 +305,9 @@ void DivPlatformGB::reset() {
for (int i=0; i<4; i++) {
chan[i]=DivPlatformGB::Channel();
}
if (dumpWrites) {
addWrite(0xffffffff,0);
}
memset(gb,0,sizeof(GB_gameboy_t));
gb->model=GB_MODEL_DMG_B;
GB_apu_init(gb);

View File

@ -538,6 +538,7 @@ void DivPlatformGenesis::forceIns() {
rWrite(0x2b,0x80);
}
immWrite(0x22,lfoValue);
psg.forceIns();
}
void DivPlatformGenesis::toggleRegisterDump(bool enable) {
@ -548,6 +549,9 @@ void DivPlatformGenesis::toggleRegisterDump(bool enable) {
void DivPlatformGenesis::reset() {
while (!writes.empty()) writes.pop();
OPN2_Reset(&fm);
if (dumpWrites) {
addWrite(0xffffffff,0);
}
for (int i=0; i<10; i++) {
chan[i]=DivPlatformGenesis::Channel();
chan[i].vol=0x7f;
@ -576,6 +580,7 @@ void DivPlatformGenesis::reset() {
// PSG
psg.reset();
psg.getRegisterWrites().clear();
psgClocks=0;
psgOut=0;
}

View File

@ -348,6 +348,9 @@ void DivPlatformNES::reset() {
for (int i=0; i<5; i++) {
chan[i]=DivPlatformNES::Channel();
}
if (dumpWrites) {
addWrite(0xffffffff,0);
}
dacPeriod=0;
dacPos=0;

View File

@ -345,6 +345,9 @@ void DivPlatformPCE::reset() {
for (int i=0; i<6; i++) {
chan[i]=DivPlatformPCE::Channel();
}
if (dumpWrites) {
addWrite(0xffffffff,0);
}
pce->Power(0);
lastPan=0xff;
memset(tempL,0,32*sizeof(int));

View File

@ -262,6 +262,9 @@ void DivPlatformSAA1099::reset() {
chan[i]=DivPlatformSAA1099::Channel();
chan[i].vol=0x0f;
}
if (dumpWrites) {
addWrite(0xffffffff,0);
}
lastBusy=60;
dacMode=0;

View File

@ -191,6 +191,9 @@ void DivPlatformSMS::reset() {
for (int i=0; i<4; i++) {
chan[i]=DivPlatformSMS::Channel();
}
if (dumpWrites) {
addWrite(0xffffffff,0);
}
sn->device_start();
snNoiseMode=3;
updateSNMode=false;

View File

@ -572,6 +572,9 @@ void DivPlatformYM2610::forceIns() {
void DivPlatformYM2610::reset() {
while (!writes.empty()) writes.pop();
if (dumpWrites) {
addWrite(0xffffffff,0);
}
fm->reset();
for (int i=0; i<13; i++) {
chan[i]=DivPlatformYM2610::Channel();

View File

@ -82,6 +82,21 @@ int SafeWriter::writeString(String val, bool pascal) {
return write(val.c_str(),val.size()+1);
}
}
int SafeWriter::writeWString(WString val, bool pascal) {
if (pascal) {
writeS((unsigned short)val.size());
for (wchar_t& i: val) {
writeS(i);
}
return 2+val.size()*2;
} else {
for (wchar_t& i: val) {
writeS(i);
}
writeS(0);
return 2+val.size()*2;
}
}
void SafeWriter::init() {
if (operative) return;

View File

@ -36,6 +36,7 @@ class SafeWriter {
int writeF_BE(float val);
int writeD(double val);
int writeD_BE(double val);
int writeWString(WString val, bool pascal);
int writeString(String val, bool pascal);
void init();

View File

@ -14,9 +14,7 @@ typedef std::string String;
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
#ifdef _WIN32
typedef std::wstring WString;
#endif
struct TAParam {
String shortName;

View File

@ -87,7 +87,6 @@ String utf16To8(const wchar_t* s) {
ret+=(0xe0+((s[i]>>12)&15));
ret+=(0x80+((s[i]>>6)&63));
ret+=(0x80+((s[i])&63));
}
}
return ret;