furnace/src/engine/vgmOps.cpp
2022-04-16 22:54:01 +07:00

1582 lines
41 KiB
C++

/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 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"
#include "../ta-log.h"
#include "../utfutils.h"
#include "song.h"
constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0;
void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool isSecond) {
unsigned char baseAddr1=isSecond?0xa0:0x50;
unsigned char baseAddr2=isSecond?0x80:0;
unsigned short baseAddr2S=isSecond?0x8000:0;
unsigned char smsAddr=isSecond?0x30:0x50;
if (write.addr==0xffffffff) { // Furnace fake reset
switch (sys) {
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
for (int i=0; i<3; i++) { // set SL and RR to highest
w->writeC(2|baseAddr1);
w->writeC(0x80+i);
w->writeC(0xff);
w->writeC(2|baseAddr1);
w->writeC(0x84+i);
w->writeC(0xff);
w->writeC(2|baseAddr1);
w->writeC(0x88+i);
w->writeC(0xff);
w->writeC(2|baseAddr1);
w->writeC(0x8c+i);
w->writeC(0xff);
w->writeC(3|baseAddr1);
w->writeC(0x80+i);
w->writeC(0xff);
w->writeC(3|baseAddr1);
w->writeC(0x84+i);
w->writeC(0xff);
w->writeC(3|baseAddr1);
w->writeC(0x88+i);
w->writeC(0xff);
w->writeC(3|baseAddr1);
w->writeC(0x8c+i);
w->writeC(0xff);
}
for (int i=0; i<3; i++) { // note off
w->writeC(2|baseAddr1);
w->writeC(0x28);
w->writeC(i);
w->writeC(2|baseAddr1);
w->writeC(0x28);
w->writeC(4+i);
}
w->writeC(2|baseAddr1); // disable DAC
w->writeC(0x2b);
w->writeC(0);
break;
case DIV_SYSTEM_SMS:
for (int i=0; i<4; i++) {
w->writeC(smsAddr);
w->writeC(0x90|(i<<5)|15);
}
break;
case DIV_SYSTEM_GB:
// square 1
w->writeC(0xb3);
w->writeC(2|baseAddr2);
w->writeC(0);
w->writeC(0xb3);
w->writeC(4|baseAddr2);
w->writeC(0x80);
// square 2
w->writeC(0xb3);
w->writeC(7|baseAddr2);
w->writeC(0);
w->writeC(0xb3);
w->writeC(9|baseAddr2);
w->writeC(0x80);
// wave
w->writeC(0xb3);
w->writeC(0x0c|baseAddr2);
w->writeC(0);
w->writeC(0xb3);
w->writeC(0x0e|baseAddr2);
w->writeC(0x80);
// noise
w->writeC(0xb3);
w->writeC(0x11|baseAddr2);
w->writeC(0);
w->writeC(0xb3);
w->writeC(0x13|baseAddr2);
w->writeC(0x80);
break;
case DIV_SYSTEM_PCE:
for (int i=0; i<6; i++) {
w->writeC(0xb9);
w->writeC(0|baseAddr2);
w->writeC(i);
w->writeC(0xb9);
w->writeC(4|baseAddr2);
w->writeC(0);
}
break;
case DIV_SYSTEM_NES:
w->writeC(0xb4);
w->writeC(0x15|baseAddr2);
w->writeC(0);
break;
case DIV_SYSTEM_YM2151:
for (int i=0; i<8; i++) {
w->writeC(4|baseAddr1);
w->writeC(0xe0+i);
w->writeC(0xff);
w->writeC(4|baseAddr1);
w->writeC(0xe8+i);
w->writeC(0xff);
w->writeC(4|baseAddr1);
w->writeC(0xf0+i);
w->writeC(0xff);
w->writeC(4|baseAddr1);
w->writeC(0xf8+i);
w->writeC(0xff);
w->writeC(4|baseAddr1);
w->writeC(0x08);
w->writeC(i);
}
break;
case DIV_SYSTEM_SEGAPCM:
case DIV_SYSTEM_SEGAPCM_COMPAT:
for (int i=0; i<16; i++) {
w->writeC(0xc0);
w->writeS((0x86|baseAddr2S)+(i<<3));
w->writeC(3);
}
break;
case DIV_SYSTEM_X1_010:
for (int i=0; i<16; i++) {
w->writeC(0xc8);
w->writeS_BE(baseAddr2S+(i<<3));
w->writeC(0);
}
break;
case DIV_SYSTEM_YM2610:
case DIV_SYSTEM_YM2610_FULL:
case DIV_SYSTEM_YM2610B:
case DIV_SYSTEM_YM2610_EXT:
case DIV_SYSTEM_YM2610_FULL_EXT:
case DIV_SYSTEM_YM2610B_EXT:
for (int i=0; i<2; i++) { // set SL and RR to highest
w->writeC(8|baseAddr1);
w->writeC(0x81+i);
w->writeC(0xff);
w->writeC(8|baseAddr1);
w->writeC(0x85+i);
w->writeC(0xff);
w->writeC(8|baseAddr1);
w->writeC(0x89+i);
w->writeC(0xff);
w->writeC(8|baseAddr1);
w->writeC(0x8d+i);
w->writeC(0xff);
w->writeC(9|baseAddr1);
w->writeC(0x81+i);
w->writeC(0xff);
w->writeC(9|baseAddr1);
w->writeC(0x85+i);
w->writeC(0xff);
w->writeC(9|baseAddr1);
w->writeC(0x89+i);
w->writeC(0xff);
w->writeC(9|baseAddr1);
w->writeC(0x8d+i);
w->writeC(0xff);
}
for (int i=0; i<2; i++) { // note off
w->writeC(8|baseAddr1);
w->writeC(0x28);
w->writeC(1+i);
w->writeC(8|baseAddr1);
w->writeC(0x28);
w->writeC(5+i);
}
// reset AY
w->writeC(8|baseAddr1);
w->writeC(7);
w->writeC(0x3f);
w->writeC(8|baseAddr1);
w->writeC(8);
w->writeC(0);
w->writeC(8|baseAddr1);
w->writeC(9);
w->writeC(0);
w->writeC(8|baseAddr1);
w->writeC(10);
w->writeC(0);
// reset sample
w->writeC(9|baseAddr1);
w->writeC(0);
w->writeC(0xbf);
break;
case DIV_SYSTEM_OPLL:
case DIV_SYSTEM_OPLL_DRUMS:
case DIV_SYSTEM_VRC7:
for (int i=0; i<9; i++) {
w->writeC(1|baseAddr1);
w->writeC(0x20+i);
w->writeC(0);
w->writeC(1|baseAddr1);
w->writeC(0x30+i);
w->writeC(0);
w->writeC(1|baseAddr1);
w->writeC(0x10+i);
w->writeC(0);
}
break;
case DIV_SYSTEM_AY8910:
w->writeC(0xa0);
w->writeC(7|baseAddr2);
w->writeC(0x3f);
w->writeC(0xa0);
w->writeC(8|baseAddr2);
w->writeC(0);
w->writeC(0xa0);
w->writeC(9|baseAddr2);
w->writeC(0);
w->writeC(0xa0);
w->writeC(10|baseAddr2);
w->writeC(0);
break;
case DIV_SYSTEM_AY8930:
w->writeC(0xa0);
w->writeC(0x0d|baseAddr2);
w->writeC(0);
w->writeC(0xa0);
w->writeC(0x0d|baseAddr2);
w->writeC(0xa0);
break;
case DIV_SYSTEM_SAA1099:
w->writeC(0xbd);
w->writeC(0x1c|baseAddr2);
w->writeC(0x02);
w->writeC(0xbd);
w->writeC(0x14|baseAddr2);
w->writeC(0);
w->writeC(0xbd);
w->writeC(0x15|baseAddr2);
w->writeC(0);
for (int i=0; i<6; i++) {
w->writeC(0xbd);
w->writeC((0|baseAddr2)+i);
w->writeC(0);
}
break;
case DIV_SYSTEM_LYNX:
w->writeC(0x4e);
w->writeC(0x44);
w->writeC(0xff); //stereo attenuation select
w->writeC(0x4e);
w->writeC(0x50);
w->writeC(0x00); //stereo channel disable
for (int i=0; i<4; i++) { //stereo attenuation value
w->writeC(0x4e);
w->writeC(0x40+i);
w->writeC(0xff);
}
break;
case DIV_SYSTEM_QSOUND:
for (int i=0; i<16; i++) {
w->writeC(0xc4);
w->writeC(0);
w->writeC(0);
w->writeC(2+(i*8));
w->writeC(0xc4);
w->writeC(0);
w->writeC(0);
w->writeC(6+(i*8));
}
for (int i=0; i<3; i++) {
w->writeC(0xc4);
w->writeC(0);
w->writeC(0);
w->writeC(0xcd+(i*4));
w->writeC(0xc4);
w->writeC(0x00);
w->writeC(0x01);
w->writeC(0xd6+i);
}
break;
// TODO: it's 3:35am
case DIV_SYSTEM_OPL:
case DIV_SYSTEM_OPL_DRUMS:
// disable envelope
for (int i=0; i<6; i++) {
w->writeC(0x0b|baseAddr1);
w->writeC(0x80+i);
w->writeC(0x0f);
w->writeC(0x0b|baseAddr1);
w->writeC(0x88+i);
w->writeC(0x0f);
w->writeC(0x0b|baseAddr1);
w->writeC(0x90+i);
w->writeC(0x0f);
}
// key off + freq reset
for (int i=0; i<9; i++) {
w->writeC(0x0b|baseAddr1);
w->writeC(0xa0+i);
w->writeC(0);
w->writeC(0x0b|baseAddr1);
w->writeC(0xb0+i);
w->writeC(0);
}
break;
case DIV_SYSTEM_OPL2:
case DIV_SYSTEM_OPL2_DRUMS:
// disable envelope
for (int i=0; i<6; i++) {
w->writeC(0x0a|baseAddr1);
w->writeC(0x80+i);
w->writeC(0x0f);
w->writeC(0x0a|baseAddr1);
w->writeC(0x88+i);
w->writeC(0x0f);
w->writeC(0x0a|baseAddr1);
w->writeC(0x90+i);
w->writeC(0x0f);
}
// key off + freq reset
for (int i=0; i<9; i++) {
w->writeC(0x0a|baseAddr1);
w->writeC(0xa0+i);
w->writeC(0);
w->writeC(0x0a|baseAddr1);
w->writeC(0xb0+i);
w->writeC(0);
}
break;
case DIV_SYSTEM_OPL3:
case DIV_SYSTEM_OPL3_DRUMS:
// disable envelope
for (int i=0; i<6; i++) {
w->writeC(0x0e|baseAddr1);
w->writeC(0x80+i);
w->writeC(0x0f);
w->writeC(0x0e|baseAddr1);
w->writeC(0x88+i);
w->writeC(0x0f);
w->writeC(0x0e|baseAddr1);
w->writeC(0x90+i);
w->writeC(0x0f);
w->writeC(0x0f|baseAddr1);
w->writeC(0x80+i);
w->writeC(0x0f);
w->writeC(0x0f|baseAddr1);
w->writeC(0x88+i);
w->writeC(0x0f);
w->writeC(0x0f|baseAddr1);
w->writeC(0x90+i);
w->writeC(0x0f);
}
// key off + freq reset
for (int i=0; i<9; i++) {
w->writeC(0x0e|baseAddr1);
w->writeC(0xa0+i);
w->writeC(0);
w->writeC(0x0e|baseAddr1);
w->writeC(0xb0+i);
w->writeC(0);
w->writeC(0x0f|baseAddr1);
w->writeC(0xa0+i);
w->writeC(0);
w->writeC(0x0f|baseAddr1);
w->writeC(0xb0+i);
w->writeC(0);
}
// reset 4-op
w->writeC(0x0f|baseAddr1);
w->writeC(0x04);
w->writeC(0x00);
break;
default:
break;
}
}
if (write.addr>=0xffff0000) { // Furnace special command
unsigned char streamID=streamOff+((write.addr&0xff00)>>8);
logD("writing stream command %x:%x with stream ID %d",write.addr,write.val,streamID);
switch (write.addr&0xff) {
case 0: // play sample
if (write.val<song.sampleLen) {
DivSample* sample=song.sample[write.val];
w->writeC(0x95);
w->writeC(streamID);
w->writeS(write.val); // sample number
w->writeC((sample->loopStart==0)); // flags
if (sample->loopStart>0) {
loopTimer[streamID]=sample->length8;
loopSample[streamID]=write.val;
}
}
break;
case 1: // set sample freq
w->writeC(0x92);
w->writeC(streamID);
w->writeI(write.val);
loopFreq[streamID]=write.val;
break;
case 2: // stop sample
w->writeC(0x94);
w->writeC(streamID);
loopSample[streamID]=-1;
break;
}
return;
}
switch (sys) {
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
switch (write.addr>>8) {
case 0: // port 0
w->writeC(2|baseAddr1);
w->writeC(write.addr&0xff);
w->writeC(write.val);
break;
case 1: // port 1
w->writeC(3|baseAddr1);
w->writeC(write.addr&0xff);
w->writeC(write.val);
break;
case 2: // PSG
w->writeC(smsAddr);
w->writeC(write.val);
break;
}
break;
case DIV_SYSTEM_SMS:
w->writeC(smsAddr);
w->writeC(write.val);
break;
case DIV_SYSTEM_GB:
w->writeC(0xb3);
w->writeC(baseAddr2|((write.addr-16)&0xff));
w->writeC(write.val);
break;
case DIV_SYSTEM_PCE:
w->writeC(0xb9);
w->writeC(baseAddr2|(write.addr&0xff));
w->writeC(write.val);
break;
case DIV_SYSTEM_NES:
w->writeC(0xb4);
w->writeC(baseAddr2|(write.addr&0xff));
w->writeC(write.val);
break;
case DIV_SYSTEM_FDS: // yeah
w->writeC(0xb4);
if ((write.addr&0xff)==0x23) {
w->writeC(baseAddr2|0x3f);
} else if ((write.addr&0xff)>=0x80) {
w->writeC(baseAddr2|(0x20+(write.addr&0x7f)));
} else {
w->writeC(baseAddr2|(write.addr&0xff));
}
w->writeC(write.val);
break;
case DIV_SYSTEM_YM2151:
w->writeC(4|baseAddr1);
w->writeC(write.addr&0xff);
w->writeC(write.val);
break;
case DIV_SYSTEM_SEGAPCM:
case DIV_SYSTEM_SEGAPCM_COMPAT:
w->writeC(0xc0);
w->writeS(baseAddr2S|(write.addr&0xffff));
w->writeC(write.val);
break;
case DIV_SYSTEM_X1_010:
w->writeC(0xc8);
w->writeS_BE(baseAddr2S|(write.addr&0x1fff));
w->writeC(write.val);
break;
case DIV_SYSTEM_YM2610:
case DIV_SYSTEM_YM2610_FULL:
case DIV_SYSTEM_YM2610B:
case DIV_SYSTEM_YM2610_EXT:
case DIV_SYSTEM_YM2610_FULL_EXT:
case DIV_SYSTEM_YM2610B_EXT:
switch (write.addr>>8) {
case 0: // port 0
w->writeC(8|baseAddr1);
w->writeC(write.addr&0xff);
w->writeC(write.val);
break;
case 1: // port 1
w->writeC(9|baseAddr1);
w->writeC(write.addr&0xff);
w->writeC(write.val);
break;
}
break;
case DIV_SYSTEM_OPLL:
case DIV_SYSTEM_OPLL_DRUMS:
case DIV_SYSTEM_VRC7:
w->writeC(1|baseAddr1);
w->writeC(write.addr&0xff);
w->writeC(write.val);
break;
case DIV_SYSTEM_AY8910:
case DIV_SYSTEM_AY8930:
w->writeC(0xa0);
w->writeC(baseAddr2|(write.addr&0xff));
w->writeC(write.val);
break;
case DIV_SYSTEM_SAA1099:
w->writeC(0xbd);
w->writeC(baseAddr2|(write.addr&0xff));
w->writeC(write.val);
break;
case DIV_SYSTEM_LYNX:
w->writeC(0x4e);
w->writeC(write.addr&0xff);
w->writeC(write.val&0xff);
break;
case DIV_SYSTEM_QSOUND:
w->writeC(0xc4);
w->writeC((write.val>>8)&0xff);
w->writeC(write.val&0xff);
w->writeC(write.addr&0xff);
break;
case DIV_SYSTEM_SWAN:
if ((write.addr&0x7f)<0x40) {
w->writeC(0xbc);
w->writeC(baseAddr2|(write.addr&0x3f));
w->writeC(write.val&0xff);
} else {
// (Wave) RAM write
w->writeC(0xc6);
w->writeS_BE(baseAddr2S|(write.addr&0x3f));
w->writeC(write.val&0xff);
}
break;
case DIV_SYSTEM_OPL:
case DIV_SYSTEM_OPL_DRUMS:
w->writeC(0x0b|baseAddr1);
w->writeC(write.addr&0xff);
w->writeC(write.val);
break;
case DIV_SYSTEM_OPL2:
case DIV_SYSTEM_OPL2_DRUMS:
w->writeC(0x0a|baseAddr1);
w->writeC(write.addr&0xff);
w->writeC(write.val);
break;
case DIV_SYSTEM_OPL3:
case DIV_SYSTEM_OPL3_DRUMS:
switch (write.addr>>8) {
case 0: // port 0
w->writeC(0x0e|baseAddr1);
w->writeC(write.addr&0xff);
w->writeC(write.val);
break;
case 1: // port 1
w->writeC(0x0f|baseAddr1);
w->writeC(write.addr&0xff);
w->writeC(write.val);
break;
}
break;
default:
logW("write not handled!");
break;
}
}
SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
if (version<0x150) {
lastError="VGM version is too low";
return NULL;
}
stop();
repeatPattern=false;
setOrder(0);
BUSY_BEGIN_SOFT;
double origRate=got.rate;
got.rate=44100;
// determine loop point
int loopOrder=0;
int loopRow=0;
int loopEnd=0;
walkSong(loopOrder,loopRow,loopEnd);
logI("loop point: %d %d",loopOrder,loopRow);
warnings="";
curOrder=0;
freelance=false;
playing=false;
extValuePresent=false;
remainingLoops=-1;
// play the song ourselves
bool done=false;
int writeCount=0;
int gd3Off=0;
int hasSN=0;
int snNoiseConfig=9;
int snNoiseSize=16;
int snFlags=0;
int hasOPLL=0;
int hasOPN2=0;
int hasOPM=0;
int hasSegaPCM=0;
int segaPCMOffset=0xf8000d;
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 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 hasPCE=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 hasES5505=0;
int hasX1=0;
int hasC352=0;
int hasGA20=0;
int hasLynx=0;
int howManyChips=0;
int loopPos=-1;
int loopTick=-1;
SafeWriter* w=new SafeWriter;
w->init();
// write header
w->write("Vgm ",4);
w->writeI(0); // will be written later
w->writeI(version);
bool willExport[32];
bool isSecond[32];
int streamIDs[32];
double loopTimer[DIV_MAX_CHANS];
double loopFreq[DIV_MAX_CHANS];
int loopSample[DIV_MAX_CHANS];
for (int i=0; i<DIV_MAX_CHANS; i++) {
loopTimer[i]=0;
loopFreq[i]=0;
loopSample[i]=-1;
}
bool writeDACSamples=false;
bool writeNESSamples=false;
bool writePCESamples=false;
int writeADPCM=0;
int writeSegaPCM=0;
int writeX1010=0;
int writeQSound=0;
for (int i=0; i<song.systemLen; i++) {
willExport[i]=false;
isSecond[i]=false;
streamIDs[i]=0;
if (sysToExport!=NULL) {
if (!sysToExport[i]) continue;
}
if (minVGMVersion(song.system[i])>version) continue;
switch (song.system[i]) {
case DIV_SYSTEM_SMS:
if (!hasSN) {
hasSN=disCont[i].dispatch->chipClock;
willExport[i]=true;
switch ((song.systemFlags[i]>>2)&3) {
case 1: // real SN
snNoiseConfig=3;
snNoiseSize=15;
break;
case 2: // real SN atari bass (seemingly unsupported)
snNoiseConfig=3;
snNoiseSize=15;
break;
default: // Sega VDP
snNoiseConfig=9;
snNoiseSize=16;
break;
}
} else if (!(hasSN&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasSN|=0x40000000;
howManyChips++;
}
break;
case DIV_SYSTEM_GB:
if (!hasGB) {
hasGB=disCont[i].dispatch->chipClock;
willExport[i]=true;
} else if (!(hasGB&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasGB|=0x40000000;
howManyChips++;
}
break;
case DIV_SYSTEM_PCE:
if (!hasPCE) {
hasPCE=disCont[i].dispatch->chipClock;
willExport[i]=true;
writePCESamples=true;
} else if (!(hasPCE&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasPCE|=0x40000000;
howManyChips++;
}
break;
case DIV_SYSTEM_NES:
if (!hasNES) {
hasNES=disCont[i].dispatch->chipClock;
willExport[i]=true;
writeNESSamples=true;
} else if (!(hasNES&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasNES|=0x40000000;
howManyChips++;
}
break;
case DIV_SYSTEM_SEGAPCM:
case DIV_SYSTEM_SEGAPCM_COMPAT:
if (!hasSegaPCM) {
hasSegaPCM=4000000;
willExport[i]=true;
writeSegaPCM=1;
} else if (!(hasSegaPCM&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
writeSegaPCM=2;
hasSegaPCM|=0x40000000;
howManyChips++;
}
break;
case DIV_SYSTEM_X1_010:
if (!hasX1) {
hasX1=disCont[i].dispatch->chipClock;
willExport[i]=true;
writeX1010=1;
} else if (!(hasX1&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
writeX1010=2;
hasX1|=0x40000000;
howManyChips++;
}
break;
case DIV_SYSTEM_YM2610:
case DIV_SYSTEM_YM2610_FULL:
case DIV_SYSTEM_YM2610B:
case DIV_SYSTEM_YM2610_EXT:
case DIV_SYSTEM_YM2610_FULL_EXT:
case DIV_SYSTEM_YM2610B_EXT:
if (!hasOPNB) {
hasOPNB=disCont[i].dispatch->chipClock;
willExport[i]=true;
writeADPCM=1;
} else if (!(hasOPNB&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
writeADPCM=2;
hasOPNB|=0x40000000;
howManyChips++;
}
if (((song.system[i]==DIV_SYSTEM_YM2610B) || (song.system[i]==DIV_SYSTEM_YM2610B_EXT)) && (!(hasOPNB&0x80000000))) { // YM2610B flag
hasOPNB|=0x80000000;
}
break;
case DIV_SYSTEM_AY8910:
case DIV_SYSTEM_AY8930:
if (!hasAY) {
hasAY=disCont[i].dispatch->chipClock;
ayConfig=(song.system[i]==DIV_SYSTEM_AY8930)?3:0;
ayFlags=1;
willExport[i]=true;
} else if (!(hasAY&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasAY|=0x40000000;
howManyChips++;
}
break;
case DIV_SYSTEM_SAA1099:
if (!hasSAA) {
hasSAA=disCont[i].dispatch->chipClock;
willExport[i]=true;
} else if (!(hasSAA&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasSAA|=0x40000000;
howManyChips++;
}
break;
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
if (!hasOPN2) {
hasOPN2=disCont[i].dispatch->chipClock;
willExport[i]=true;
writeDACSamples=true;
} else if (!(hasOPN2&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasOPN2|=0x40000000;
howManyChips++;
}
break;
case DIV_SYSTEM_YM2151:
if (!hasOPM) {
hasOPM=disCont[i].dispatch->chipClock;
willExport[i]=true;
} else if (!(hasOPM&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasOPM|=0x40000000;
howManyChips++;
}
break;
case DIV_SYSTEM_OPLL:
case DIV_SYSTEM_OPLL_DRUMS:
case DIV_SYSTEM_VRC7:
if (!hasOPLL) {
hasOPLL=disCont[i].dispatch->chipClock;
willExport[i]=true;
} else if (!(hasOPLL&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasOPLL|=0x40000000;
howManyChips++;
}
break;
case DIV_SYSTEM_FDS:
if (!hasNES) {
hasNES=0x80000000|disCont[i].dispatch->chipClock;
willExport[i]=true;
} else if (!(hasNES&0x80000000)) {
hasNES|=0x80000000;
willExport[i]=true;
} else if (!(hasNES&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasNES|=0xc0000000;
howManyChips++;
}
break;
case DIV_SYSTEM_LYNX:
if (!hasLynx) {
hasLynx=disCont[i].dispatch->chipClock;
willExport[i]=true;
} else if (!(hasLynx&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasLynx|=0x40000000;
howManyChips++;
}
break;
case DIV_SYSTEM_QSOUND:
if (!hasQSound) {
// could set chipClock to 4000000 here for compatibility
// However I think it it not necessary because old VGM players will still
// not be able to handle the 64kb sample bank trick
hasQSound=disCont[i].dispatch->chipClock;
willExport[i]=true;
writeQSound=1;
} else if (!(hasQSound&0x40000000)) {
isSecond[i]=true;
willExport[i]=false;
writeQSound=2;
addWarning("dual QSound is not supported by the VGM format");
}
break;
case DIV_SYSTEM_SWAN:
if (!hasSwan) {
hasSwan=disCont[i].dispatch->chipClock;
willExport[i]=true;
// funny enough, VGM doesn't have support for WSC's sound DMA by design
// so DAC stream it goes
// since WS has the same PCM format as YM2612 DAC, I can just reuse this flag
writeDACSamples=true;
} else if (!(hasSwan&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasSwan|=0x40000000;
howManyChips++;
}
break;
case DIV_SYSTEM_OPL:
case DIV_SYSTEM_OPL_DRUMS:
if (!hasOPL) {
hasOPL=disCont[i].dispatch->chipClock;
willExport[i]=true;
} else if (!(hasOPL&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasOPL|=0x40000000;
howManyChips++;
}
break;
case DIV_SYSTEM_OPL2:
case DIV_SYSTEM_OPL2_DRUMS:
if (!hasOPL2) {
hasOPL2=disCont[i].dispatch->chipClock;
willExport[i]=true;
} else if (!(hasOPL2&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasOPL2|=0x40000000;
howManyChips++;
}
break;
case DIV_SYSTEM_OPL3:
case DIV_SYSTEM_OPL3_DRUMS:
if (!hasOPL3) {
hasOPL3=disCont[i].dispatch->chipClock;
willExport[i]=true;
} else if (!(hasOPL3&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
hasOPL3|=0x40000000;
howManyChips++;
}
break;
default:
break;
}
if (willExport[i]) {
disCont[i].dispatch->toggleRegisterDump(true);
}
}
//bool wantsExtraHeader=false;
/*for (int i=0; i<song.systemLen; i++) {
if (isSecond[i]) {
wantsExtraHeader=true;
break;
}
}*/
// 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);
if (version>=0x151) {
w->writeC(snFlags);
} else {
w->writeC(0);
}
w->writeI(hasOPN2);
w->writeI(hasOPM);
w->writeI(0); // data pointer. will be written later
if (version>=0x151) {
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
} else {
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeC(0);
w->writeC(0);
w->writeC(0); // OPN
w->writeC(0); // OPNA
}
// currently not used but is part of 1.60
w->writeC(0); // volume
w->writeC(0); // reserved
w->writeC(0); // loop count
// 1.51
w->writeC(0); // loop modifier
if (version>=0x161) {
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);
} else {
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeC(0); // flags
w->writeC(0); // K flags
w->writeC(0); // C140 chip type
w->writeC(0); // reserved
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
}
if (version>=0x171) {
w->writeI(hasSCSP);
} else {
w->writeI(0);
}
// 1.70
w->writeI(0); // extra header
// 1.71
if (version>=0x171) {
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);
w->writeI(hasLynx);
} else {
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeC(0); // 5503 chans
w->writeC(0); // 5505 chans
w->writeC(0); // C352 clock divider
w->writeC(0); // reserved
w->writeI(0);
w->writeI(0);
w->writeI(0);
w->writeI(0);
}
for (int i=0; i<6; i++) { // reserved
w->writeI(0);
}
/* TODO
unsigned int exHeaderOff=w->tell();
if (wantsExtraHeader) {
w->writeI(4);
w->writeI(4);
// write clocks
w->writeC(howManyChips);
}*/
unsigned int songOff=w->tell();
// write samples
unsigned int sampleSeek=0;
for (int i=0; i<song.sampleLen; i++) {
DivSample* sample=song.sample[i];
logI("setting seek to %d",sampleSeek);
sample->off8=sampleSeek;
sampleSeek+=sample->length8;
}
if (writeDACSamples) for (int i=0; i<song.sampleLen; i++) {
DivSample* sample=song.sample[i];
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0);
w->writeI(sample->length8);
for (unsigned int j=0; j<sample->length8; j++) {
w->writeC((unsigned char)sample->data8[j]+0x80);
}
}
if (writeNESSamples) for (int i=0; i<song.sampleLen; i++) {
DivSample* sample=song.sample[i];
w->writeC(0x67);
w->writeC(0x66);
w->writeC(7);
w->writeI(sample->length8);
for (unsigned int j=0; j<sample->length8; j++) {
w->writeC(((unsigned char)sample->data8[j]+0x80)>>1);
}
}
if (writePCESamples) for (int i=0; i<song.sampleLen; i++) {
DivSample* sample=song.sample[i];
w->writeC(0x67);
w->writeC(0x66);
w->writeC(5);
w->writeI(sample->length8);
for (unsigned int j=0; j<sample->length8; j++) {
w->writeC(((unsigned char)sample->data8[j]+0x80)>>3);
}
}
if (writeSegaPCM>0) {
unsigned char* pcmMem=new unsigned char[16777216];
size_t memPos=0;
for (int i=0; i<song.sampleLen; i++) {
DivSample* sample=song.sample[i];
if ((memPos&0xff0000)!=((memPos+sample->length8)&0xff0000)) {
memPos=(memPos+0xffff)&0xff0000;
}
if (memPos>=16777216) break;
sample->offSegaPCM=memPos;
unsigned int alignedSize=(sample->length8+0xff)&(~0xff);
unsigned int readPos=0;
if (alignedSize>65536) alignedSize=65536;
for (unsigned int j=0; j<alignedSize; j++) {
if (readPos>=sample->length8) {
if (sample->loopStart>=0 && sample->loopStart<(int)sample->length8) {
readPos=sample->loopStart;
pcmMem[memPos++]=((unsigned char)sample->data8[readPos]+0x80);
} else {
pcmMem[memPos++]=0x80;
}
} else {
pcmMem[memPos++]=((unsigned char)sample->data8[readPos]+0x80);
}
readPos++;
if (memPos>=16777216) break;
}
sample->loopOffP=readPos-sample->loopStart;
if (memPos>=16777216) break;
}
for (int i=0; i<writeSegaPCM; i++) {
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0x80);
w->writeI((memPos+8)|(i*0x80000000));
w->writeI(memPos);
w->writeI(0);
w->write(pcmMem,memPos);
}
delete[] pcmMem;
}
if (adpcmAMemLen>0) {
for (int i=0; i<writeADPCM; i++) {
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0x82);
w->writeI((adpcmAMemLen+8)|(i*0x80000000));
w->writeI(adpcmAMemLen);
w->writeI(0);
w->write(adpcmAMem,adpcmAMemLen);
}
}
if (adpcmBMemLen>0) {
for (int i=0; i<writeADPCM; i++) {
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0x83);
w->writeI((adpcmBMemLen+8)|(i*0x80000000));
w->writeI(adpcmBMemLen);
w->writeI(0);
w->write(adpcmBMem,adpcmBMemLen);
}
}
if (qsoundMemLen>0) {
// always write a whole bank
unsigned int blockSize=(qsoundMemLen+0xffff)&(~0xffff);
if (blockSize > 0x1000000) {
blockSize = 0x1000000;
}
for (int i=0; i<writeQSound; i++) {
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0x8F);
w->writeI((blockSize+8)|(i*0x80000000));
w->writeI(0x1000000);
w->writeI(0);
w->write(qsoundMem,blockSize);
}
}
if (x1_010MemLen>0) {
for (int i=0; i<writeX1010; i++) {
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0x91);
w->writeI((x1_010MemLen+8)|(i*0x80000000));
w->writeI(x1_010MemLen);
w->writeI(0);
w->write(x1_010Mem,x1_010MemLen);
}
}
// 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_YM2612:
case DIV_SYSTEM_YM2612_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;
case DIV_SYSTEM_NES:
w->writeC(0x90);
w->writeC(streamID);
w->writeC(20);
w->writeC(0); // port
w->writeC(0x11); // DAC
w->writeC(0x91);
w->writeC(streamID);
w->writeC(7);
w->writeC(1);
w->writeC(0);
w->writeC(0x92);
w->writeC(streamID);
w->writeI(32000); // default
streamID++;
break;
case DIV_SYSTEM_PCE:
for (int j=0; j<6; j++) {
w->writeC(0x90);
w->writeC(streamID);
w->writeC(27);
w->writeC(j); // port
w->writeC(0x06); // select+DAC
w->writeC(0x91);
w->writeC(streamID);
w->writeC(5);
w->writeC(1);
w->writeC(0);
w->writeC(0x92);
w->writeC(streamID);
w->writeI(16000); // default
streamID++;
}
break;
case DIV_SYSTEM_SWAN:
w->writeC(0x90);
w->writeC(streamID);
w->writeC(isSecond[i]?0xa1:0x21);
w->writeC(0); // port
w->writeC(0x09); // DAC
w->writeC(0x91);
w->writeC(streamID);
w->writeC(0);
w->writeC(1);
w->writeC(0);
w->writeC(0x92);
w->writeC(streamID);
w->writeI(24000); // default
streamID++;
break;
default:
break;
}
}
// write song data
playSub(false);
size_t tickCount=0;
bool writeLoop=false;
while (!done) {
if (loopPos==-1) {
if (loopOrder==curOrder && loopRow==curRow && ticks==1) {
writeLoop=true;
}
}
if (nextTick(false,true) || !playing) {
done=true;
if (!loop) {
for (int i=0; i<song.systemLen; i++) {
disCont[i].dispatch->getRegisterWrites().clear();
}
break;
}
// stop all streams
for (int i=0; i<streamID; i++) {
w->writeC(0x94);
w->writeC(i);
loopSample[i]=-1;
}
if (!playing) {
writeLoop=false;
loopPos=-1;
}
}
// get register dumps
for (int i=0; i<song.systemLen; i++) {
std::vector<DivRegWrite>& writes=disCont[i].dispatch->getRegisterWrites();
for (DivRegWrite& j: writes) {
performVGMWrite(w,song.system[i],j,streamIDs[i],loopTimer,loopFreq,loopSample,isSecond[i]);
writeCount++;
}
writes.clear();
}
// check whether we need to loop
int totalWait=cycles>>MASTER_CLOCK_PREC;
for (int i=0; i<streamID; i++) {
if (loopSample[i]>=0) {
loopTimer[i]-=(loopFreq[i]/44100.0)*(double)totalWait;
}
}
bool haveNegatives=false;
for (int i=0; i<streamID; i++) {
if (loopSample[i]>=0) {
if (loopTimer[i]<0) {
haveNegatives=true;
}
}
}
while (haveNegatives) {
// finish all negatives
int nextToTouch=-1;
for (int i=0; i<streamID; i++) {
if (loopSample[i]>=0) {
if (loopTimer[i]<0) {
if (nextToTouch>=0) {
if (loopTimer[nextToTouch]>loopTimer[i]) nextToTouch=i;
} else {
nextToTouch=i;
}
}
}
}
if (nextToTouch>=0) {
double waitTime=totalWait+(loopTimer[nextToTouch]*(44100.0/MAX(1,loopFreq[nextToTouch])));
if (waitTime>0) {
w->writeC(0x61);
w->writeS(waitTime);
logV("wait is: %f",waitTime);
totalWait-=waitTime;
tickCount+=waitTime;
}
if (loopSample[nextToTouch]<song.sampleLen) {
DivSample* sample=song.sample[loopSample[nextToTouch]];
// insert loop
if (sample->loopStart<(int)sample->length8) {
w->writeC(0x93);
w->writeC(nextToTouch);
w->writeI(sample->off8+sample->loopStart);
w->writeC(0x81);
w->writeI(sample->length8-sample->loopStart);
}
}
loopSample[nextToTouch]=-1;
} else {
haveNegatives=false;
}
}
// write wait
if (totalWait>0) {
if (totalWait==735) {
w->writeC(0x62);
} else if (totalWait==882) {
w->writeC(0x63);
} else {
w->writeC(0x61);
w->writeS(totalWait);
}
tickCount+=totalWait;
}
if (writeLoop) {
writeLoop=false;
loopPos=w->tell();
loopTick=tickCount;
}
}
// end of song
w->writeC(0x66);
got.rate=origRate;
for (int i=0; i<song.systemLen; i++) {
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(0x14,SEEK_SET);
w->writeI(gd3Off-0x14);
w->writeI(tickCount);
if (loop) {
if (loopPos==-1) {
w->writeI(0);
w->writeI(0);
} else {
w->writeI(loopPos-0x1c);
w->writeI(tickCount-loopTick-1);
}
} else {
w->writeI(0);
w->writeI(0);
}
w->seek(0x34,SEEK_SET);
w->writeI(songOff-0x34);
/*if (wantsExtraHeader) {
w->seek(0xbc,SEEK_SET);
w->writeI(exHeaderOff-0xbc);
}*/
remainingLoops=-1;
playing=false;
freelance=false;
extValuePresent=false;
logI("%d register writes total.",writeCount);
BUSY_END;
return w;
}