diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index bce4cd321..835bf055b 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -55,6 +55,10 @@ enum DivDispatchCmds { DIV_CMD_C64_DUTY_RESET, DIV_CMD_C64_EXTENDED, + DIV_CMD_AY_ENVELOPE_SET, + DIV_CMD_AY_ENVELOPE_LOW, + DIV_CMD_AY_ENVELOPE_HIGH, + DIV_ALWAYS_SET_VOLUME, DIV_CMD_MAX diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index b0a2c6913..b1426c0fa 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -5,6 +5,9 @@ #include "ym2610shared.h" +#define FM_FREQ_BASE 624.0f +#define PSG_FREQ_BASE 7576.0f + static unsigned char konOffs[4]={ 1, 2, 5, 6 }; @@ -15,24 +18,22 @@ void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t l for (size_t h=start; hwrite(0x1+((w.addr>>8)<<1),w.val); - //printf("write: %x = %.2x\n",w.addr,w.val); - writes.pop(); - } else { + if (--delay<1) { + QueuedWrite& w=writes.front(); fm->write(0x0+((w.addr>>8)<<1),w.addr); - w.addrOrVal=true; + fm->write(0x1+((w.addr>>8)<<1),w.val); + writes.pop(); + delay=4; } } fm->generate(&fmout); - os[0]=fmout.data[0]; + os[0]=fmout.data[0]+(fmout.data[2]>>1); if (os[0]<-32768) os[0]=-32768; if (os[0]>32767) os[0]=32767; - os[1]=fmout.data[1]; + os[1]=fmout.data[1]+(fmout.data[2]>>1); if (os[1]<-32768) os[1]=-32768; if (os[1]>32767) os[1]=32767; @@ -42,6 +43,60 @@ void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t l } void DivPlatformYM2610::tick() { + // PSG + for (int i=4; i<7; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + chan[i].outVol=chan[i].std.vol-(15-chan[i].vol); + if (chan[i].outVol<0) chan[i].outVol=0; + rWrite(0x04+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); + } + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=round(PSG_FREQ_BASE/pow(2.0f,((float)(chan[i].std.arp)/12.0f))); + } else { + chan[i].baseFreq=round(PSG_FREQ_BASE/pow(2.0f,((float)(chan[i].note+chan[i].std.arp-12)/12.0f))); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=round(PSG_FREQ_BASE/pow(2.0f,((float)(chan[i].note)/12.0f))); + chan[i].freqChanged=true; + } + } + if (chan[i].std.hadWave) { + chan[i].psgMode&=4; + chan[i].psgMode|=(chan[i].std.wave+1)&3; + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + DivInstrument* ins=parent->getIns(chan[i].ins); + chan[i].freq=(chan[i].baseFreq*(ONE_SEMITONE-chan[i].pitch))/ONE_SEMITONE; + if (chan[i].freq>4095) chan[i].freq=4095; + if (chan[i].keyOn) { + //rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); + //rWrite(16+i*5+2,((chan[i].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3)); + } + if (chan[i].keyOff) { + rWrite(0x04+i,0); + } + rWrite((i-4)<<1,chan[i].freq&0xff); + rWrite(1+((i-4)<<1),chan[i].freq>>8); + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } + + rWrite(0x07, + ~((chan[4].psgMode&1)| + ((chan[5].psgMode&1)<<1)| + ((chan[6].psgMode&1)<<2)| + ((chan[4].psgMode&2)<<2)| + ((chan[5].psgMode&2)<<3)| + ((chan[6].psgMode&2)<<4))); + // FM for (int i=0; i<4; i++) { if (i==1 && extMode) continue; @@ -65,14 +120,13 @@ void DivPlatformYM2610::tick() { int freqt=toFreq(chan[i].freq); writes.emplace(chanOffs[i]+0xa4,freqt>>8); writes.emplace(chanOffs[i]+0xa0,freqt&0xff); + chan[i].freqChanged=false; } if (chan[i].keyOn) { writes.emplace(0x28,0xf0|konOffs[i]); chan[i].keyOn=false; } } - - // PSG } int DivPlatformYM2610::octave(int freq) { @@ -120,6 +174,17 @@ int DivPlatformYM2610::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins); + + if (c.chan>3) { // PSG + chan[c.chan].baseFreq=round(PSG_FREQ_BASE/pow(2.0f,((float)c.value/12.0f))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].std.init(ins); + rWrite(0x04+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + break; + } for (int i=0; i<4; i++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; @@ -148,22 +213,24 @@ int DivPlatformYM2610::dispatch(DivCommand c) { } chan[c.chan].insChanged=false; - chan[c.chan].baseFreq=644.0f*pow(2.0f,((float)c.value/12.0f)); + chan[c.chan].baseFreq=FM_FREQ_BASE*pow(2.0f,((float)c.value/12.0f)); chan[c.chan].freqChanged=true; chan[c.chan].keyOn=true; chan[c.chan].active=true; break; } case DIV_CMD_NOTE_OFF: - if (c.chan==5) { + if (c.chan>6) { dacSample=-1; } chan[c.chan].keyOff=true; chan[c.chan].active=false; + chan[c.chan].std.init(NULL); break; case DIV_CMD_VOLUME: { chan[c.chan].vol=c.value; DivInstrument* ins=parent->getIns(chan[c.chan].ins); + if (c.chan>3) break; for (int i=0; i<4; i++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator op=ins->fm.op[i]; @@ -186,6 +253,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { + if (c.chan>3) break; switch (c.value) { case 0x01: chan[c.chan].pan=1; @@ -207,7 +275,31 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - int destFreq=644.0f*pow(2.0f,((float)c.value2/12.0f)); + if (c.chan>3) { // PSG + int destFreq=round(PSG_FREQ_BASE/pow(2.0f,((float)c.value2/12.0f))); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + + int destFreq=FM_FREQ_BASE*pow(2.0f,((float)c.value2/12.0f)); int newFreq; bool return2=false; if (destFreq>chan[c.chan].baseFreq) { @@ -235,13 +327,6 @@ int DivPlatformYM2610::dispatch(DivCommand c) { if (return2) return 2; break; } - case DIV_CMD_SAMPLE_MODE: { - if (c.chan==5) { - dacMode=c.value; - rWrite(0x2b,c.value<<7); - } - break; - } case DIV_CMD_SAMPLE_BANK: sampleBank=c.value; if (sampleBank>(parent->song.sample.size()/12)) { @@ -249,7 +334,11 @@ int DivPlatformYM2610::dispatch(DivCommand c) { } break; case DIV_CMD_LEGATO: { - chan[c.chan].baseFreq=644.0f*pow(2.0f,((float)c.value/12.0f)); + if (c.chan>3) { // PSG + chan[c.chan].baseFreq=round(PSG_FREQ_BASE/pow(2.0f,((float)c.value/12.0f))); + } else { + chan[c.chan].baseFreq=FM_FREQ_BASE*pow(2.0f,((float)c.value/12.0f)); + } chan[c.chan].freqChanged=true; break; } @@ -258,6 +347,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; } case DIV_CMD_FM_MULT: { + if (c.chan>3) break; unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; DivInstrument* ins=parent->getIns(chan[c.chan].ins); DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; @@ -265,6 +355,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; } case DIV_CMD_FM_TL: { + if (c.chan>3) break; unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; DivInstrument* ins=parent->getIns(chan[c.chan].ins); if (isOutput[ins->fm.alg][c.value]) { @@ -275,6 +366,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; } case DIV_CMD_FM_AR: { + if (c.chan>3) break; DivInstrument* ins=parent->getIns(chan[c.chan].ins); if (c.value<0) { for (int i=0; i<4; i++) { @@ -290,6 +382,24 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; } + case DIV_CMD_AY_ENVELOPE_SET: + if (c.chan<4 || c.chan>6) break; + rWrite(0x0d,c.value>>4); + if (c.value&15) { + chan[c.chan].psgMode|=4; + } else { + chan[c.chan].psgMode&=~4; + } + break; + case DIV_CMD_AY_ENVELOPE_LOW: + if (c.chan<4 || c.chan>6) break; + writes.emplace(0x0b,c.value); + writes.emplace(0x0c,0); + break; + case DIV_CMD_AY_ENVELOPE_HIGH: + if (c.chan<4 || c.chan>6) break; + writes.emplace(0x0c,c.value); + break; case DIV_ALWAYS_SET_VOLUME: return 0; break; @@ -299,6 +409,10 @@ int DivPlatformYM2610::dispatch(DivCommand c) { return 127; break; case DIV_CMD_PRE_PORTA: + if (c.chan>3) { + chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + chan[c.chan].inPorta=c.value; + } break; case DIV_CMD_PRE_NOTE: break; @@ -326,9 +440,15 @@ int DivPlatformYM2610::init(DivEngine* p, int channels, int sugRate, bool pal) { } fm=new ymfm::ym2610(iface); fm->reset(); - for (int i=0; i<10; i++) { + for (int i=0; i<4; i++) { chan[i].vol=0x7f; } + for (int i=4; i<7; i++) { + chan[i].vol=0x0f; + } + for (int i=7; i<13; i++) { + chan[i].vol=0x1f; + } for (int i=0; i<512; i++) { oldWrites[i]=-1; @@ -343,6 +463,8 @@ int DivPlatformYM2610::init(DivEngine* p, int channels, int sugRate, bool pal) { dacSample=-1; sampleBank=0; + delay=0; + extMode=false; // LFO diff --git a/src/engine/platform/ym2610.h b/src/engine/platform/ym2610.h index 6353ba35c..6bf1c00f9 100644 --- a/src/engine/platform/ym2610.h +++ b/src/engine/platform/ym2610.h @@ -1,6 +1,7 @@ #ifndef _YM2610_H #define _YM2610_H #include "../dispatch.h" +#include "../macroInt.h" #include #include "sound/ym2610/ymfm_opn.h" @@ -15,12 +16,13 @@ class DivPlatformYM2610: public DivDispatch { struct Channel { unsigned char freqH, freqL; int freq, baseFreq, pitch; - unsigned char ins; + unsigned char ins, note, psgMode; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause; - int vol; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; + int vol, outVol; unsigned char pan; - Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {} + DivMacroInt std; + Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), ins(-1), note(0), psgMode(1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(15), pan(3) {} }; Channel chan[13]; struct QueuedWrite { @@ -42,6 +44,8 @@ class DivPlatformYM2610: public DivDispatch { int dacSample; int sampleBank; + int delay; + bool extMode; short oldWrites[512]; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index ed2374e38..287b70c09 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -65,6 +65,10 @@ const char* cmdName[DIV_CMD_MAX]={ "C64_DUTY_RESET", "C64_EXTENDED", + "AY_ENVELOPE_SET", + "AY_ENVELOPE_LOW", + "AY_ENVELOPE_HIGH", + "ALWAYS_SET_VOLUME" }; @@ -170,6 +174,8 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char case DIV_SYSTEM_GENESIS: case DIV_SYSTEM_GENESIS_EXT: case DIV_SYSTEM_ARCADE: + case DIV_SYSTEM_YM2610: + case DIV_SYSTEM_YM2610_EXT: switch (effect) { case 0x10: // LFO or noise mode if (song.system==DIV_SYSTEM_ARCADE) { @@ -225,9 +231,31 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char case 0x1d: // AR op4 dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,3,effectVal&31)); break; - case 0x20: // PCM frequency + case 0x20: // PCM frequency or Neo Geo PSG mode if (song.system==DIV_SYSTEM_ARCADE) { dispatchCmd(DivCommand(DIV_CMD_SAMPLE_FREQ,ch,effectVal)); + } else if (song.system==DIV_SYSTEM_YM2610 || song.system==DIV_SYSTEM_YM2610_EXT) { + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); + } + break; + case 0x21: // Neo Geo PSG noise freq + if (song.system==DIV_SYSTEM_YM2610 || song.system==DIV_SYSTEM_YM2610_EXT) { + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_FREQ,ch,effectVal)); + } + break; + case 0x22: // UNOFFICIAL: Neo Geo PSG envelope enable + if (song.system==DIV_SYSTEM_YM2610 || song.system==DIV_SYSTEM_YM2610_EXT) { + dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SET,ch,effectVal)); + } + break; + case 0x23: // UNOFFICIAL: Neo Geo PSG envelope period low + if (song.system==DIV_SYSTEM_YM2610 || song.system==DIV_SYSTEM_YM2610_EXT) { + dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_LOW,ch,effectVal)); + } + break; + case 0x24: // UNOFFICIAL: Neo Geo PSG envelope period high + if (song.system==DIV_SYSTEM_YM2610 || song.system==DIV_SYSTEM_YM2610_EXT) { + dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_HIGH,ch,effectVal)); } break; default: