Merge branch 'master' of https://github.com/tildearrow/furnace into es5506_alt

This commit is contained in:
cam900 2022-10-22 10:17:00 +09:00
commit 487607b6ae
110 changed files with 1707 additions and 854 deletions

View file

@ -275,6 +275,14 @@ struct DivRegWrite {
addr(a), val(v) {}
};
struct DivDelayedWrite {
int time;
DivRegWrite write;
DivDelayedWrite(int t, unsigned int a, unsigned short v):
time(t),
write(a,v) {}
};
struct DivDispatchOscBuffer {
bool follow;
unsigned int rate;
@ -327,6 +335,14 @@ class DivDispatch {
*/
virtual void acquire(short* bufL, short* bufR, size_t start, size_t len);
/**
* fill a write stream with data (e.g. for software-mixed PCM).
* @param stream the write stream.
* @param rate stream rate (e.g. 44100 for VGM).
* @param len number of samples.
*/
virtual void fillStream(std::vector<DivDelayedWrite>& stream, int sRate, size_t len);
/**
* send a command to this dispatch.
* @param c a DivCommand.

View file

@ -47,8 +47,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev121"
#define DIV_ENGINE_VERSION 121
#define DIV_VERSION "dev122"
#define DIV_ENGINE_VERSION 122
// for imports
#define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02
@ -431,7 +431,7 @@ class DivEngine {
void processRow(int i, bool afterDelay);
void nextOrder();
void nextRow();
void performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond);
void performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, bool directStream);
// returns true if end of song.
bool nextTick(bool noAccum=false, bool inhibitLowLat=false);
bool perSystemEffect(int ch, unsigned char effect, unsigned char effectVal);
@ -515,7 +515,7 @@ class DivEngine {
// specify system to build ROM for.
SafeWriter* buildROM(int sys);
// dump to VGM.
SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171, bool patternHints=false);
SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171, bool patternHints=false, bool directStream=false);
// dump to ZSM.
SafeWriter* saveZSM(unsigned int zsmrate=60, bool loop=true);
// dump command stream.

View file

@ -937,13 +937,13 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ds.systemLen=2;
ds.system[0]=DIV_SYSTEM_YM2612;
ds.system[1]=DIV_SYSTEM_SMS;
ds.systemVol[1]=24;
ds.systemVol[1]=32;
}
if (ds.system[0]==DIV_SYSTEM_GENESIS_EXT) {
ds.systemLen=2;
ds.system[0]=DIV_SYSTEM_YM2612_EXT;
ds.system[1]=DIV_SYSTEM_SMS;
ds.systemVol[1]=24;
ds.systemVol[1]=32;
}
if (ds.system[0]==DIV_SYSTEM_ARCADE) {
ds.systemLen=2;

View file

@ -22,6 +22,9 @@
void DivDispatch::acquire(short* bufL, short* bufR, size_t start, size_t len) {
}
void DivDispatch::fillStream(std::vector<DivDelayedWrite>& stream, int sRate, size_t len) {
}
void DivDispatch::tick(bool sysTick) {
}

View file

@ -27,11 +27,11 @@
#define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6])))
void DivPlatformGenesis::processDAC() {
void DivPlatformGenesis::processDAC(int iRate) {
if (softPCM) {
softPCMTimer+=chipClock/576;
if (softPCMTimer>rate) {
softPCMTimer-=rate;
if (softPCMTimer>iRate) {
softPCMTimer-=iRate;
int sample=0;
for (int i=5; i<7; i++) {
@ -75,14 +75,14 @@ void DivPlatformGenesis::processDAC() {
} else {
if (!chan[5].dacReady) {
chan[5].dacDelay+=32000;
if (chan[5].dacDelay>=rate) {
chan[5].dacDelay-=rate;
if (chan[5].dacDelay>=iRate) {
chan[5].dacDelay-=iRate;
chan[5].dacReady=true;
}
}
if (chan[5].dacMode && chan[5].dacSample!=-1) {
chan[5].dacPeriod+=chan[5].dacRate;
if (chan[5].dacPeriod>=rate) {
if (chan[5].dacPeriod>=iRate) {
DivSample* s=parent->getSample(chan[5].dacSample);
if (s->samples>0) {
if (!isMuted[5]) {
@ -106,7 +106,7 @@ void DivPlatformGenesis::processDAC() {
rWrite(0x2b,0);
}
}
while (chan[5].dacPeriod>=rate) chan[5].dacPeriod-=rate;
while (chan[5].dacPeriod>=iRate) chan[5].dacPeriod-=iRate;
} else {
chan[5].dacSample=-1;
}
@ -120,7 +120,7 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s
static int os[2];
for (size_t h=start; h<start+len; h++) {
processDAC();
processDAC(rate);
os[0]=0; os[1]=0;
for (int i=0; i<6; i++) {
@ -180,7 +180,7 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si
ymfm::ym2612::fm_engine* fme=fm_ymfm->debug_engine();
for (size_t h=start; h<start+len; h++) {
processDAC();
processDAC(rate);
os[0]=0; os[1]=0;
if (!writes.empty()) {
@ -237,6 +237,20 @@ void DivPlatformGenesis::acquire(short* bufL, short* bufR, size_t start, size_t
}
}
void DivPlatformGenesis::fillStream(std::vector<DivDelayedWrite>& stream, int sRate, size_t len) {
while (!writes.empty()) writes.pop_front();
for (size_t i=0; i<len; i++) {
processDAC(sRate);
while (!writes.empty()) {
QueuedWrite& w=writes.front();
stream.push_back(DivDelayedWrite(i,w.addr,w.val));
writes.pop_front();
}
}
regWrites.clear();
}
void DivPlatformGenesis::tick(bool sysTick) {
for (int i=0; i<(softPCM?7:6); i++) {
if (i==2 && extMode) continue;

View file

@ -124,12 +124,13 @@ class DivPlatformGenesis: public DivPlatformOPN {
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
inline void processDAC();
inline void processDAC(int iRate);
void acquire_nuked(short* bufL, short* bufR, size_t start, size_t len);
void acquire_ymfm(short* bufL, short* bufR, size_t start, size_t len);
public:
void acquire(short* bufL, short* bufR, size_t start, size_t len);
void fillStream(std::vector<DivDelayedWrite>& stream, int sRate, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);

View file

@ -416,12 +416,10 @@ int DivPlatformNES::dispatch(DivCommand c) {
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
}
if (!parent->song.brokenOutVol2) {
if (c.chan==2) {
rWrite(0x4000+c.chan*4,0xff);
} else {
rWrite(0x4000+c.chan*4,0x30|chan[c.chan].vol|((chan[c.chan].duty&3)<<6));
}
if (c.chan==2) {
rWrite(0x4000+c.chan*4,0xff);
} else if (!parent->song.brokenOutVol2) {
rWrite(0x4000+c.chan*4,0x30|chan[c.chan].vol|((chan[c.chan].duty&3)<<6));
}
break;
case DIV_CMD_NOTE_OFF:

View file

@ -25,8 +25,6 @@
//#define rWrite(a,v) pendingWrites[a]=v;
#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
#define CHIP_DIVIDER 16
const char* regCheatSheetT6W28[]={
"Data0", "0",
"Data1", "1",
@ -72,14 +70,21 @@ void DivPlatformT6W28::acquire(short* bufL, short* bufR, size_t start, size_t le
}
void DivPlatformT6W28::writeOutVol(int ch) {
int left=15-CLAMP(chan[ch].outVol+chan[ch].panL-15,0,15);
int right=15-CLAMP(chan[ch].outVol+chan[ch].panR-15,0,15);
rWrite(0,0x90|(ch<<5)|(isMuted[ch]?15:left));
rWrite(1,0x90|(ch<<5)|(isMuted[ch]?15:right));
if (chan[ch].active) {
int left=15-CLAMP(chan[ch].outVol+chan[ch].panL-15,0,15);
int right=15-CLAMP(chan[ch].outVol+chan[ch].panR-15,0,15);
rWrite(0,0x90|(ch<<5)|(isMuted[ch]?15:left));
rWrite(1,0x90|(ch<<5)|(isMuted[ch]?15:right));
} else {
rWrite(0,0x9f|(ch<<5));
rWrite(1,0x9f|(ch<<5));
}
}
void DivPlatformT6W28::tick(bool sysTick) {
for (int i=0; i<4; i++) {
double CHIP_DIVIDER=16;
if (i==3) CHIP_DIVIDER=15;
chan[i].std.next();
if (chan[i].std.vol.had) {
chan[i].outVol=VOL_SCALE_LOG(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15);
@ -91,6 +96,12 @@ void DivPlatformT6W28::tick(bool sysTick) {
}
chan[i].freqChanged=true;
}
if (i==3 && chan[i].std.duty.had) {
if (chan[i].duty!=chan[i].std.duty.val) {
chan[i].duty=((chan[i].std.duty.val==1)?4:0)|3;
rWrite(1,0xe0+chan[i].duty);
}
}
if (chan[i].std.panL.had) {
chan[i].panL=chan[i].std.panL.val&15;
}
@ -109,12 +120,13 @@ void DivPlatformT6W28::tick(bool sysTick) {
}
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had) {
rWrite(1,0xe0+chan[i].duty);
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (chan[i].freq>1023) chan[i].freq=1023;
if (i==3) {
rWrite(1,0xe7);
rWrite(1,0x80|(2<<5)|(chan[3].freq&15));
rWrite(1,chan[3].freq>>4);
} else {
@ -129,6 +141,8 @@ void DivPlatformT6W28::tick(bool sysTick) {
}
int DivPlatformT6W28::dispatch(DivCommand c) {
double CHIP_DIVIDER=16;
if (c.chan==3) CHIP_DIVIDER=15;
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_PCE);
@ -142,6 +156,7 @@ int DivPlatformT6W28::dispatch(DivCommand c) {
chan[c.chan].macroInit(ins);
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
writeOutVol(c.chan);
}
chan[c.chan].insChanged=false;
break;
@ -150,6 +165,7 @@ int DivPlatformT6W28::dispatch(DivCommand c) {
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].macroInit(NULL);
writeOutVol(c.chan);
break;
case DIV_CMD_NOTE_OFF_ENV:
case DIV_CMD_ENV_RELEASE:
@ -166,8 +182,7 @@ int DivPlatformT6W28::dispatch(DivCommand c) {
chan[c.chan].vol=c.value;
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=c.value;
if (chan[c.chan].active) {
}
writeOutVol(c.chan);
}
}
break;
@ -205,7 +220,9 @@ int DivPlatformT6W28::dispatch(DivCommand c) {
break;
}
case DIV_CMD_STD_NOISE_MODE:
chan[c.chan].noise=c.value;
if (c.chan!=3) break;
chan[c.chan].duty=(((c.value&15)==1)?4:0)|3;
rWrite(1,0xe0+chan[c.chan].duty);
break;
case DIV_CMD_PANNING: {
chan[c.chan].panL=c.value>>4;
@ -226,7 +243,7 @@ int DivPlatformT6W28::dispatch(DivCommand c) {
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_GET_VOLMAX:
return 31;
return 15;
break;
case DIV_ALWAYS_SET_VOLUME:
return 1;
@ -289,6 +306,8 @@ void DivPlatformT6W28::reset() {
cycles=0;
curChan=-1;
delay=0;
// default noise mode
rWrite(1,0xe7);
}
bool DivPlatformT6W28::isStereo() {

View file

@ -29,8 +29,8 @@ class DivPlatformT6W28: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, pitch2, note;
int ins;
unsigned char panL, panR;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise;
unsigned char panL, panR, duty;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
signed char vol, outVol;
DivMacroInt std;
void macroInit(DivInstrument* which) {
@ -46,13 +46,13 @@ class DivPlatformT6W28: public DivDispatch {
ins(-1),
panL(15),
panR(15),
duty(7),
active(false),
insChanged(true),
freqChanged(false),
keyOn(false),
keyOff(false),
inPorta(false),
noise(false),
vol(15),
outVol(15) {}
};

View file

@ -84,6 +84,8 @@ const char* regCheatSheetVB[]={
"S6EV0", "550",
"S6EV1", "554",
"S6RAM", "558",
"RESET", "580",
NULL
};
@ -148,6 +150,13 @@ void DivPlatformVB::tick(bool sysTick) {
}
chan[i].freqChanged=true;
}
if (i==5 && chan[i].std.duty.had) {
if ((chan[i].std.duty.val&7)!=((chan[i].envHigh>>4)&7)) {
chan[i].envHigh&=~0x70;
chan[i].envHigh|=(chan[i].std.duty.val&7)<<4;
writeEnv(i,true);
}
}
if (chan[i].std.wave.had) {
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
chan[i].wave=chan[i].std.wave.val;
@ -176,7 +185,7 @@ void DivPlatformVB::tick(bool sysTick) {
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) {
// ???
chWrite(i,0x00,0x80);
}
if (chan[i].active) {
if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1)) {
@ -310,12 +319,12 @@ int DivPlatformVB::dispatch(DivCommand c) {
break;
case DIV_CMD_FDS_MOD_DEPTH: // set modulation
if (c.chan!=4) break;
modulation=(c.value<<4)&15;
modulation=(c.value&15)<<4;
modType=true;
chWrite(4,0x07,modulation);
if (modulation!=0) {
chan[c.chan].envHigh&=~0x70;
chan[c.chan].envHigh|=0x40|((c.value&15)<<4);
chan[c.chan].envHigh|=0x40|(c.value&0xf0);
} else {
chan[c.chan].envHigh&=~0x70;
}
@ -328,7 +337,7 @@ int DivPlatformVB::dispatch(DivCommand c) {
chWrite(4,0x07,modulation);
if (modulation!=0) {
chan[c.chan].envHigh&=~0x70;
chan[c.chan].envHigh|=0x10;
chan[c.chan].envHigh|=0x40;
} else {
chan[c.chan].envHigh&=~0x70;
}

View file

@ -174,6 +174,7 @@ void DivPlatformVERA::tick(bool sysTick) {
if (i<16) {
if (chan[i].std.panL.had) {
chan[i].pan=chan[i].std.panL.val&3;
chan[i].pan=((chan[i].pan&1)<<1)|((chan[i].pan&2)>>1);
rWriteHi(i,2,isMuted[i]?0:chan[i].pan);
}
}
@ -329,7 +330,7 @@ int DivPlatformVERA::dispatch(DivCommand c) {
if (chan[c.chan].active && c.value2) {
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_VERA));
}
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=calcNoteFreq(c.chan,chan[c.chan].note);
if (!chan[c.chan].inPorta && c.value && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=calcNoteFreq(c.chan,chan[c.chan].note);
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_STD_NOISE_MODE:

View file

@ -796,6 +796,7 @@ void DivEngine::registerSystems() {
{0x10, {DIV_CMD_AMIGA_FILTER, "10xx: Toggle filter (0 disables; 1 enables)"}},
{0x11, {DIV_CMD_AMIGA_AM, "11xx: Toggle AM with next channel"}},
{0x12, {DIV_CMD_AMIGA_PM, "12xx: Toggle period modulation with next channel"}},
{0x13, {DIV_CMD_WAVE, "13xx: Set waveform"}}
}
);
@ -1594,7 +1595,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_MSM6258]=new DivSysDef(
"OKI MSM6258", NULL, 0xab, 0, 1, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_VOX,
"OKI MSM6258", NULL, 0xab, 0, 1, false, true, 0x150, false, 1U<<DIV_SAMPLE_DEPTH_VOX,
"an ADPCM sound chip manufactured by OKI and used in the Sharp X68000.",
{"Sample"},
{"PCM"},
@ -1673,7 +1674,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2612_FRAC]=new DivSysDef(
"Yamaha YM2612 (OPN2) with DualPCM", NULL, 0xbe, 0, 7, true, false, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Yamaha YM2612 (OPN2) with DualPCM", NULL, 0xbe, 0, 7, true, false, 0x150, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis system uses software mixing to provide two sample channels.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6/PCM 1", "PCM 2"},
{"F1", "F2", "F3", "F4", "F5", "P1", "P2"},
@ -1685,7 +1686,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2612_FRAC_EXT]=new DivSysDef(
"Yamaha YM2612 (OPN2) Extended Channel 3 with DualPCM and CSM", NULL, 0xbd, 0, 11, true, false, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Yamaha YM2612 (OPN2) Extended Channel 3 with DualPCM and CSM", NULL, 0xbd, 0, 11, true, false, 0x150, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis system uses software mixing to provide two sample channels.\nthis one is in Extended Channel mode, which turns the third FM channel into four operators with independent notes/frequencies.",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6/PCM 1", "PCM 2", "CSM Timer"},
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "P1", "P2", "CSM"},
@ -1697,7 +1698,8 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_T6W28]=new DivSysDef(
"T6W28", NULL, 0xbf, 0, 4, false, true, 0, false, 0,
// 0x0a = wild guess. it may as well be 0x83
"T6W28", NULL, 0xbf, 0x0a, 4, false, true, 0x160, false, 0,
"an SN76489 derivative used in Neo Geo Pocket, has independent stereo volume and noise channel frequency.",
{"Square 1", "Square 2", "Square 3", "Noise"},
{"S1", "S2", "S3", "NO"},
@ -1705,7 +1707,7 @@ void DivEngine::registerSystems() {
{DIV_INS_T6W28, DIV_INS_T6W28, DIV_INS_T6W28, DIV_INS_T6W28},
{},
{
{0x20, {DIV_CMD_STD_NOISE_MODE, "20xy: Set noise mode (x: preset/variable; y: thin pulse/noise)"}}
{0x20, {DIV_CMD_STD_NOISE_MODE, "20xx: Set noise length (0: short, 1: long)"}}
}
);

View file

@ -24,7 +24,7 @@
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* sampleDir, bool isSecond) {
void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, bool directStream) {
unsigned char baseAddr1=isSecond?0xa0:0x50;
unsigned char baseAddr2=isSecond?0x80:0;
unsigned short baseAddr2S=isSecond?0x8000:0;
@ -34,6 +34,8 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
switch (sys) {
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_FRAC:
case DIV_SYSTEM_YM2612_FRAC_EXT:
for (int i=0; i<3; i++) { // set SL and RR to highest
w->writeC(2|baseAddr1);
w->writeC(0x80+i);
@ -79,6 +81,14 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(0x90|(i<<5)|15);
}
break;
case DIV_SYSTEM_T6W28:
for (int i=0; i<4; i++) {
w->writeC(0x30);
w->writeC(0x90|(i<<5)|15);
w->writeC(0x50);
w->writeC(0x90|(i<<5)|15);
}
break;
case DIV_SYSTEM_GB:
// square 1
w->writeC(0xb3);
@ -531,47 +541,57 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(baseAddr2|12);
w->writeC(1);
break;
case DIV_SYSTEM_VBOY:
// isn't it amazing when a chip has a built-in reset command?
w->writeC(0xc7);
w->writeS_BE(baseAddr2S|(0x580>>2));
w->writeC(0xff);
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->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0)|(sampleDir[streamID]?0x10:0)); // flags
if (sample->isLoopable() && !sampleDir[streamID]) {
loopTimer[streamID]=sample->length8;
loopSample[streamID]=write.val;
if (!directStream) {
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->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0)|(sampleDir[streamID]?0x10:0)); // flags
if (sample->isLoopable() && !sampleDir[streamID]) {
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;
case 3: // set sample direction
sampleDir[streamID]=write.val;
break;
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;
case 3: // set sample direction
sampleDir[streamID]=write.val;
break;
}
}
return;
}
switch (sys) {
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_FRAC:
case DIV_SYSTEM_YM2612_FRAC_EXT:
switch (write.addr>>8) {
case 0: // port 0
w->writeC(2|baseAddr1);
@ -593,6 +613,14 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(smsAddr);
w->writeC(write.val);
break;
case DIV_SYSTEM_T6W28:
if (write.addr) {
w->writeC(0x30);
} else {
w->writeC(0x50);
}
w->writeC(write.val);
break;
case DIV_SYSTEM_GB:
w->writeC(0xb3);
w->writeC(baseAddr2|((write.addr-16)&0xff));
@ -847,20 +875,20 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
}
#define CHIP_VOL(_id,_mult) { \
double _vol=fabs(song.systemVol[i])*4.0*_mult; \
double _vol=fabs((float)song.systemVol[i])*4.0*_mult; \
if (_vol<0.0) _vol=0.0; \
if (_vol>32767.0) _vol=32767.0; \
chipVol.push_back((_id)|(0x80000000)|(((unsigned int)_vol)<<16)); \
}
#define CHIP_VOL_SECOND(_id,_mult) { \
double _vol=fabs(song.systemVol[i])*4.0*_mult; \
double _vol=fabs((float)song.systemVol[i])*4.0*_mult; \
if (_vol<0.0) _vol=0.0; \
if (_vol>32767.0) _vol=32767.0; \
chipVol.push_back((_id)|(0x80000100)|(((unsigned int)_vol)<<16)); \
}
SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool patternHints) {
SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool patternHints, bool directStream) {
if (version<0x150) {
lastError="VGM version is too low";
return NULL;
@ -964,6 +992,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
int loopSample[DIV_MAX_CHANS];
bool sampleDir[DIV_MAX_CHANS];
std::vector<unsigned int> chipVol;
std::vector<DivDelayedWrite> delayedWrites[32];
std::vector<std::pair<int,DivDelayedWrite>> sortedWrites;
for (int i=0; i<DIV_MAX_CHANS; i++) {
loopTimer[i]=0;
@ -1162,6 +1192,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
break;
case DIV_SYSTEM_YM2612:
case DIV_SYSTEM_YM2612_EXT:
case DIV_SYSTEM_YM2612_FRAC:
case DIV_SYSTEM_YM2612_FRAC_EXT:
if (!hasOPN2) {
hasOPN2=disCont[i].dispatch->chipClock;
willExport[i]=true;
@ -1416,6 +1448,15 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
howManyChips++;
}
break;
case DIV_SYSTEM_T6W28:
if (!hasSN) {
hasSN=0xc0000000|disCont[i].dispatch->chipClock;
CHIP_VOL(0,1.0);
snNoiseConfig=3;
snNoiseSize=15;
willExport[i]=true;
}
break;
default:
break;
}
@ -1580,7 +1621,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
unsigned int exHeaderOff=w->tell();
if (version>=0x170) {
logD("writing extended header...");
w->writeI(8);
w->writeI(12);
w->writeI(0);
w->writeI(4);
@ -1608,7 +1649,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
sampleSeek+=sample->length8;
}
if (writeDACSamples) for (int i=0; i<song.sampleLen; i++) {
if (writeDACSamples && !directStream) for (int i=0; i<song.sampleLen; i++) {
DivSample* sample=song.sample[i];
w->writeC(0x67);
w->writeC(0x66);
@ -1619,7 +1660,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
}
}
if (writeNESSamples) for (int i=0; i<song.sampleLen; i++) {
if (writeNESSamples && !directStream) for (int i=0; i<song.sampleLen; i++) {
DivSample* sample=song.sample[i];
w->writeC(0x67);
w->writeC(0x66);
@ -1630,7 +1671,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
}
}
if (writePCESamples) for (int i=0; i<song.sampleLen; i++) {
if (writePCESamples && !directStream) for (int i=0; i<song.sampleLen; i++) {
DivSample* sample=song.sample[i];
w->writeC(0x67);
w->writeC(0x66);
@ -1821,87 +1862,91 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
// 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++) {
if (!directStream) {
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:
case DIV_SYSTEM_YM2612_FRAC:
case DIV_SYSTEM_YM2612_FRAC_EXT:
w->writeC(0x90);
w->writeC(streamID);
w->writeC(27);
w->writeC(j); // port
w->writeC(0x06); // select+DAC
w->writeC(0x02);
w->writeC(0); // port
w->writeC(0x2a); // DAC
w->writeC(0x91);
w->writeC(streamID);
w->writeC(5);
w->writeC(0);
w->writeC(1);
w->writeC(0);
w->writeC(0x92);
w->writeC(streamID);
w->writeI(16000); // default
w->writeI(32000); // 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
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(0);
w->writeC(1);
w->writeC(0);
w->writeC(0x91);
w->writeC(streamID);
w->writeC(7);
w->writeC(1);
w->writeC(0);
w->writeC(0x92);
w->writeC(streamID);
w->writeI(24000); // default
streamID++;
break;
default:
break;
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;
}
}
}
@ -1930,10 +1975,12 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
break;
}
// stop all streams
for (int i=0; i<streamID; i++) {
w->writeC(0x94);
w->writeC(i);
loopSample[i]=-1;
if (!directStream) {
for (int i=0; i<streamID; i++) {
w->writeC(0x94);
w->writeC(i);
loopSample[i]=-1;
}
}
if (!playing) {
@ -1965,63 +2012,103 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
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,sampleDir,isSecond[i]);
performVGMWrite(w,song.system[i],j,streamIDs[i],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i],directStream);
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;
if (directStream) {
// render stream of all chips
for (int i=0; i<song.systemLen; i++) {
disCont[i].dispatch->fillStream(delayedWrites[i],44100,totalWait);
for (DivDelayedWrite& j: delayedWrites[i]) {
sortedWrites.push_back(std::pair<int,DivDelayedWrite>(i,j));
}
delayedWrites[i].clear();
}
}
bool haveNegatives=false;
for (int i=0; i<streamID; i++) {
if (loopSample[i]>=0) {
if (loopTimer[i]<0) {
haveNegatives=true;
if (!sortedWrites.empty()) {
// sort if more than one chip
if (song.systemLen>1) {
std::sort(sortedWrites.begin(),sortedWrites.end(),[](const std::pair<int,DivDelayedWrite>& a, const std::pair<int,DivDelayedWrite>& b) -> bool {
return a.second.time<b.second.time;
});
}
// write it out
int lastOne=0;
for (std::pair<int,DivDelayedWrite>& i: sortedWrites) {
if (i.second.time>lastOne) {
// write delay
int delay=i.second.time-lastOne;
if (delay>16) {
w->writeC(0x61);
w->writeS(totalWait);
} else if (delay>0) {
w->writeC(0x70+delay-1);
}
lastOne=i.second.time;
}
// write write
performVGMWrite(w,song.system[i.first],i.second.write,streamIDs[i.first],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i.first],directStream);
}
sortedWrites.clear();
totalWait-=lastOne;
}
} else {
for (int i=0; i<streamID; i++) {
if (loopSample[i]>=0) {
loopTimer[i]-=(loopFreq[i]/44100.0)*(double)totalWait;
}
}
}
while (haveNegatives) {
// finish all negatives
int nextToTouch=-1;
bool haveNegatives=false;
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;
}
haveNegatives=true;
}
}
}
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->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)<sample->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)) {
w->writeC(0x93);
w->writeC(nextToTouch);
w->writeI(sampleOff8[loopSample[nextToTouch]]+sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT));
w->writeC(0x81);
w->writeI(sample->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)-sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT));
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;
}
}
}
}
loopSample[nextToTouch]=-1;
} else {
haveNegatives=false;
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->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)<sample->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)) {
w->writeC(0x93);
w->writeC(nextToTouch);
w->writeI(sampleOff8[loopSample[nextToTouch]]+sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT));
w->writeC(0x81);
w->writeI(sample->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)-sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT));
}
}
loopSample[nextToTouch]=-1;
} else {
haveNegatives=false;
}
}
}
// write wait