From 95e3a098d0ad138c14c5daf717199ce793425782 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 14 Dec 2021 14:31:57 -0500 Subject: [PATCH] add Neo Geo extended channel 2 support --- CMakeLists.txt | 1 + src/engine/engine.cpp | 4 + src/engine/platform/ym2610.cpp | 4 +- src/engine/platform/ym2610ext.cpp | 258 +++++++++++++++++++++++++++++ src/engine/platform/ym2610ext.h | 23 +++ src/engine/platform/ym2610shared.h | 3 + 6 files changed, 290 insertions(+), 3 deletions(-) create mode 100644 src/engine/platform/ym2610ext.cpp create mode 100644 src/engine/platform/ym2610ext.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d86f5533..6242bc720 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,6 +102,7 @@ src/engine/platform/nes.cpp src/engine/platform/c64.cpp src/engine/platform/arcade.cpp src/engine/platform/ym2610.cpp +src/engine/platform/ym2610ext.cpp src/engine/platform/dummy.cpp) set(GUI_SOURCES diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 5e18057c8..d7d316ead 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -15,6 +15,7 @@ #include "platform/c64.h" #include "platform/arcade.h" #include "platform/ym2610.h" +#include "platform/ym2610ext.h" #include "platform/dummy.h" #include #include @@ -1239,6 +1240,9 @@ void DivEngine::initDispatch() { case DIV_SYSTEM_YM2610: dispatch=new DivPlatformYM2610; break; + case DIV_SYSTEM_YM2610_EXT: + dispatch=new DivPlatformYM2610Ext; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 6e434811d..6096d73ef 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -5,9 +5,6 @@ #include "ym2610shared.h" -#define FM_FREQ_BASE 622.0f -#define PSG_FREQ_BASE 7640.0f - static unsigned char konOffs[4]={ 1, 2, 5, 6 }; @@ -22,6 +19,7 @@ void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t l QueuedWrite& w=writes.front(); fm->write(0x0+((w.addr>>8)<<1),w.addr); fm->write(0x1+((w.addr>>8)<<1),w.val); + printf("%.2x = %.2x\n",w.addr,w.val); writes.pop(); delay=4; } diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp new file mode 100644 index 000000000..772e4cc98 --- /dev/null +++ b/src/engine/platform/ym2610ext.cpp @@ -0,0 +1,258 @@ +#include "ym2610ext.h" +#include "../engine.h" +#include + +#include "ym2610shared.h" + +int DivPlatformYM2610Ext::dispatch(DivCommand c) { + if (c.chan<1) { + return DivPlatformYM2610::dispatch(c); + } + if (c.chan>4) { + c.chan-=3; + return DivPlatformYM2610::dispatch(c); + } + int ch=c.chan-1; + int ordch=orderedOps[ch]; + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(opChan[ch].ins); + + unsigned short baseAddr=chanOffs[1]|opOffs[ordch]; + DivInstrumentFM::Operator op=ins->fm.op[ordch]; + if (isOutput[ins->fm.alg][ordch]) { + if (!opChan[ch].active || opChan[ch].insChanged) { + rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127)); + } + } else { + if (opChan[ch].insChanged) { + rWrite(baseAddr+0x40,op.tl); + } + } + if (opChan[ch].insChanged) { + rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+0x60,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+0x70,op.d2r&31); + rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+0x90,op.ssgEnv&15); + } + if (opChan[ch].insChanged) { // TODO how does this work? + rWrite(chanOffs[1]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); + rWrite(chanOffs[1]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + } + opChan[ch].insChanged=false; + + opChan[ch].baseFreq=FM_FREQ_BASE*pow(2.0f,((float)c.value/12.0f)); + opChan[ch].freqChanged=true; + opChan[ch].keyOn=true; + opChan[ch].active=true; + break; + } + case DIV_CMD_NOTE_OFF: + opChan[ch].keyOff=true; + opChan[ch].active=false; + break; + case DIV_CMD_VOLUME: { + opChan[ch].vol=c.value; + DivInstrument* ins=parent->getIns(opChan[ch].ins); + unsigned short baseAddr=chanOffs[1]|opOffs[ordch]; + DivInstrumentFM::Operator op=ins->fm.op[ordch]; + if (isOutput[ins->fm.alg][ordch]) { + rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127)); + } else { + rWrite(baseAddr+0x40,op.tl); + } + break; + } + case DIV_CMD_GET_VOLUME: { + return opChan[ch].vol; + break; + } + case DIV_CMD_INSTRUMENT: + if (opChan[ch].ins!=c.value) { + opChan[ch].insChanged=true; + } + opChan[ch].ins=c.value; + break; + case DIV_CMD_PANNING: { + switch (c.value) { + case 0x01: + opChan[ch].pan=1; + break; + case 0x10: + opChan[ch].pan=2; + break; + default: + opChan[ch].pan=3; + break; + } + DivInstrument* ins=parent->getIns(opChan[ch].ins); + // TODO: ??? + rWrite(chanOffs[1]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + break; + } + case DIV_CMD_PITCH: { + opChan[ch].pitch=c.value; + opChan[ch].freqChanged=true; + break; + } + case DIV_CMD_NOTE_PORTA: { + int destFreq=FM_FREQ_BASE*pow(2.0f,((float)c.value2/12.0f)); + int newFreq; + bool return2=false; + if (destFreq>opChan[ch].baseFreq) { + newFreq=opChan[ch].baseFreq+c.value*octave(opChan[ch].baseFreq); + if (newFreq>=destFreq) { + newFreq=destFreq; + return2=true; + } + } else { + newFreq=opChan[ch].baseFreq-c.value*octave(opChan[ch].baseFreq); + if (newFreq<=destFreq) { + newFreq=destFreq; + return2=true; + } + } + if (!opChan[ch].portaPause) { + if (octave(opChan[ch].baseFreq)!=octave(newFreq)) { + opChan[ch].portaPause=true; + break; + } + } + opChan[ch].baseFreq=newFreq; + opChan[ch].portaPause=false; + opChan[ch].freqChanged=true; + if (return2) return 2; + break; + } + case DIV_CMD_SAMPLE_MODE: { + // ignored on extended channel 2 mode. + break; + } + case DIV_CMD_LEGATO: { + opChan[ch].baseFreq=FM_FREQ_BASE*pow(2.0f,((float)c.value/12.0f)); + opChan[ch].freqChanged=true; + break; + } + case DIV_CMD_FM_MULT: { // TODO + unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + DivInstrument* ins=parent->getIns(opChan[ch].ins); + DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; + rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4)); + break; + } + case DIV_CMD_FM_TL: { // TODO + unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + DivInstrument* ins=parent->getIns(opChan[ch].ins); + if (isOutput[ins->fm.alg][c.value]) { + rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127)); + } else { + rWrite(baseAddr+0x40,c.value2); + } + break; + } + case DIV_CMD_FM_AR: { + DivInstrument* ins=parent->getIns(opChan[ch].ins); + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator op=ins->fm.op[i]; + unsigned short baseAddr=chanOffs[1]|opOffs[i]; + rWrite(baseAddr+0x50,(c.value2&31)|(op.rs<<6)); + } + } else { + DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+0x50,(c.value2&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_GET_VOLMAX: + return 127; + break; + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + case DIV_CMD_PRE_PORTA: + break; + default: + //printf("WARNING: unimplemented command %d\n",c.cmd); + break; + } + return 1; +} + +static int opChanOffsL[4]={ + 0xa9, 0xaa, 0xa8, 0xa2 +}; + +static int opChanOffsH[4]={ + 0xad, 0xae, 0xac, 0xa6 +}; + +void DivPlatformYM2610Ext::tick() { + if (extMode) { + bool writeSomething=false; + unsigned char writeMask=2; + for (int i=0; i<4; i++) { + writeMask|=opChan[i].active<<(4+i); + if (opChan[i].keyOn || opChan[i].keyOff) { + writeSomething=true; + writeMask&=~(1<<(4+i)); + opChan[i].keyOff=false; + } + } + if (writeSomething) { + writes.emplace(0x28,writeMask); + } + } + + DivPlatformYM2610::tick(); + + bool writeNoteOn=false; + unsigned char writeMask=2; + if (extMode) for (int i=0; i<4; i++) { + if (opChan[i].freqChanged) { + opChan[i].freq=(opChan[i].baseFreq*(ONE_SEMITONE+opChan[i].pitch))/ONE_SEMITONE; + int freqt=toFreq(opChan[i].freq); + opChan[i].freqH=freqt>>8; + opChan[i].freqL=freqt&0xff; + writes.emplace(opChanOffsH[i],opChan[i].freqH); + writes.emplace(opChanOffsL[i],opChan[i].freqL); + opChan[i].freqChanged=false; + } + writeMask|=opChan[i].active<<(4+i); + if (opChan[i].keyOn) { + writeNoteOn=true; + writeMask|=1<<(4+i); + opChan[i].keyOn=false; + } + } + if (writeNoteOn) { + writes.emplace(0x28,writeMask); + } +} + +void DivPlatformYM2610Ext::reset() { + DivPlatformYM2610::reset(); + + for (int i=0; i<4; i++) { + opChan[i]=DivPlatformYM2610Ext::OpChannel(); + opChan[i].vol=127; + } + + // channel 2 mode + writes.emplace(0x27,0x40); + extMode=true; +} + +bool DivPlatformYM2610Ext::keyOffAffectsArp(int ch) { + return (ch>7); +} + +int DivPlatformYM2610Ext::init(DivEngine* parent, int channels, int sugRate, bool pal) { + DivPlatformYM2610::init(parent,channels,sugRate,pal); + + reset(); + return 16; +} diff --git a/src/engine/platform/ym2610ext.h b/src/engine/platform/ym2610ext.h new file mode 100644 index 000000000..98fe152a9 --- /dev/null +++ b/src/engine/platform/ym2610ext.h @@ -0,0 +1,23 @@ +#include "../dispatch.h" + +#include "ym2610.h" + +class DivPlatformYM2610Ext: public DivPlatformYM2610 { + struct OpChannel { + unsigned char freqH, freqL; + int freq, baseFreq, pitch; + unsigned char ins; + signed char konCycles; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause; + int vol; + unsigned char pan; + OpChannel(): 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) {} + }; + OpChannel opChan[4]; + public: + int dispatch(DivCommand c); + void reset(); + void tick(); + bool keyOffAffectsArp(int ch); + int init(DivEngine* parent, int channels, int sugRate, bool pal); +}; diff --git a/src/engine/platform/ym2610shared.h b/src/engine/platform/ym2610shared.h index baada39a8..e02785248 100644 --- a/src/engine/platform/ym2610shared.h +++ b/src/engine/platform/ym2610shared.h @@ -24,3 +24,6 @@ static int orderedOps[4]={ }; #define rWrite(a,v) pendingWrites[a]=v; + +#define FM_FREQ_BASE 622.0f +#define PSG_FREQ_BASE 7640.0f