diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 6757fe1e8..a3ed1400c 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -263,6 +263,22 @@ enum DivDispatchCmds { DIV_CMD_BIFURCATOR_STATE_LOAD, DIV_CMD_BIFURCATOR_PARAMETER, + DIV_CMD_OPL4_PCM_MIX_FM, // (value) + DIV_CMD_OPL4_PCM_MIX_PCM, // (value) + DIV_CMD_OPL4_PCM_LFO, // (value) + DIV_CMD_OPL4_PCM_VIB, // (value) + DIV_CMD_OPL4_PCM_AM, // (value) + DIV_CMD_OPL4_PCM_AR, // (value) + DIV_CMD_OPL4_PCM_D1R, // (value) + DIV_CMD_OPL4_PCM_DL, // (value) + DIV_CMD_OPL4_PCM_D2R, // (value) + DIV_CMD_OPL4_PCM_RC, // (value) + DIV_CMD_OPL4_PCM_RR, // (value) + DIV_CMD_OPL4_PCM_DAMP, // (value) + DIV_CMD_OPL4_PCM_PSEUDO_REVERB, // (value) + DIV_CMD_OPL4_PCM_LFO_RESET, // (value) + DIV_CMD_OPL4_PCM_LEVEL_DIRECT, // (value) + DIV_CMD_MAX }; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 7ca58611b..b024257e4 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -938,7 +938,8 @@ void DivEngine::delUnusedSamples() { i->type==DIV_INS_C219 || i->type==DIV_INS_NDS || i->type==DIV_INS_GBA_DMA || - i->type==DIV_INS_GBA_MINMOD) { + i->type==DIV_INS_GBA_MINMOD || + i->type==DIV_INS_OPL4PCM) { if (i->amiga.initSample>=0 && i->amiga.initSampleamiga.initSample]=true; } diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index d6407ec6c..039721445 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -178,6 +178,15 @@ bool DivInstrumentMultiPCM::operator==(const DivInstrumentMultiPCM& other) { ); } +bool DivInstrumentOPL4PCM::operator==(const DivInstrumentOPL4PCM& other) { + return ( + _C(damp) && + _C(pseudoReverb) && + _C(lfoReset) && + _C(levelDirect) + ); +} + bool DivInstrumentWaveSynth::operator==(const DivInstrumentWaveSynth& other) { return ( _C(wave1) && @@ -848,6 +857,17 @@ void DivInstrument::writeFeatureS2(SafeWriter* w) { FEATURE_END; } +void DivInstrument::writeFeatureO4(SafeWriter* w) { + FEATURE_BEGIN("O4"); + + w->writeC(opl4pcm.damp); + w->writeC(opl4pcm.pseudoReverb); + w->writeC(opl4pcm.lfoReset); + w->writeC(opl4pcm.levelDirect); + + FEATURE_END; +} + void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bool insName) { size_t blockStartSeek=0; size_t blockEndSeek=0; @@ -894,6 +914,7 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo bool featureEF=false; bool featurePN=false; bool featureS2=false; + bool featureO4=false; bool checkForWL=false; @@ -1137,6 +1158,12 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo feature64=true; featureS2=true; break; + case DIV_INS_OPL4PCM: + featureSM=true; + featureSL=true; + featureMP=true; + featureO4=true; + break; case DIV_INS_MAX: break; case DIV_INS_NULL: @@ -1193,6 +1220,9 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo if (sid2!=defaultIns.sid2) { featureS2=true; } + if (opl4pcm!=defaultIns.opl4pcm) { + featureO4=true; + } } // check ins name @@ -1344,6 +1374,9 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo if (featureS2) { writeFeatureS2(w); } + if (featureO4) { + writeFeatureO4(w); + } if (fui && (featureSL || featureWL)) { w->write("EN",2); @@ -2172,6 +2205,17 @@ void DivInstrument::readFeatureS2(SafeReader& reader, short version) { READ_FEAT_END; } +void DivInstrument::readFeatureO4(SafeReader& reader, short version) { + READ_FEAT_BEGIN; + + opl4pcm.damp=reader.readC(); + opl4pcm.pseudoReverb=reader.readC(); + opl4pcm.lfoReset=reader.readC(); + opl4pcm.levelDirect=reader.readC(); + + READ_FEAT_END; +} + DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, bool fui, DivSong* song) { unsigned char featCode[2]; bool volIsCutoff=false; @@ -2246,6 +2290,8 @@ DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, b readFeaturePN(reader,version); } else if (memcmp(featCode,"S2",2)==0) { // SID2 readFeatureS2(reader,version); + } else if (memcmp(featCode,"O4",2)==0) { // OPL4 PCM + readFeatureO4(reader,version); } else { if (song==NULL && (memcmp(featCode,"SL",2)==0 || (memcmp(featCode,"WL",2)==0))) { // nothing diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 49b2991d4..f37a61c0a 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -94,6 +94,7 @@ enum DivInstrumentType: unsigned short { DIV_INS_GBA_MINMOD=61, DIV_INS_BIFURCATOR=62, DIV_INS_SID2=63, // coincidence! + DIV_INS_OPL4PCM=64, DIV_INS_MAX, DIV_INS_NULL }; @@ -620,6 +621,22 @@ struct DivInstrumentMultiPCM { } }; +struct DivInstrumentOPL4PCM { + bool damp, pseudoReverb, lfoReset, levelDirect; + + bool operator==(const DivInstrumentOPL4PCM& other); + bool operator!=(const DivInstrumentOPL4PCM& other) { + return !(*this==other); + } + + DivInstrumentOPL4PCM(): + damp(false), + pseudoReverb(false), + lfoReset(false), + levelDirect(true) { + } +}; + enum DivWaveSynthEffects { DIV_WS_NONE=0, // one waveform effects @@ -879,6 +896,7 @@ struct DivInstrument { DivInstrumentESFM esfm; DivInstrumentPowerNoise powernoise; DivInstrumentSID2 sid2; + DivInstrumentOPL4PCM opl4pcm; /** * these are internal functions. @@ -906,6 +924,7 @@ struct DivInstrument { void writeFeatureEF(SafeWriter* w); void writeFeaturePN(SafeWriter* w); void writeFeatureS2(SafeWriter* w); + void writeFeatureO4(SafeWriter* w); void readFeatureNA(SafeReader& reader, short version); void readFeatureFM(SafeReader& reader, short version); @@ -929,6 +948,7 @@ struct DivInstrument { void readFeatureEF(SafeReader& reader, short version); void readFeaturePN(SafeReader& reader, short version); void readFeatureS2(SafeReader& reader, short version); + void readFeatureO4(SafeReader& reader, short version); DivDataErrors readInsDataOld(SafeReader& reader, short version); DivDataErrors readInsDataNew(SafeReader& reader, short version, bool fui, DivSong* song); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 57ed459a4..d836379c0 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -1002,9 +1002,10 @@ void DivPlatformOPL::tick(bool sysTick) { chan[i].freqChanged=true; } - if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.had) { // TODO: not working if (chan[i].std.phaseReset.val==1 && chan[i].active) { chan[i].keyOn=true; + chan[i].writeCtrl=true; } } @@ -1014,78 +1015,51 @@ void DivPlatformOPL::tick(bool sysTick) { chan[i].writeCtrl=true; } - if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - DivSample* s=parent->getSample(chan[i].sample); - unsigned char ctrl=0; - double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0; - chan[i].freq=(int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,(524288*768))); - if (chan[i].freq<0x400) chan[i].freq=0x400; - if (chan[i].freq>0x4000000) chan[i].freq=0x4000000; - if (chan[i].freq>=0x2000000) { - chan[i].freqH=15; - } else if (chan[i].freq>=0x1000000) { - chan[i].freqH=14; - } else if (chan[i].freq>=0x800000) { - chan[i].freqH=13; - } else if (chan[i].freq>=0x400000) { - chan[i].freqH=12; - } else if (chan[i].freq>=0x200000) { - chan[i].freqH=11; - } else if (chan[i].freq>=0x100000) { - chan[i].freqH=10; - } else if (chan[i].freq>=0x80000) { - chan[i].freqH=9; - } else if (chan[i].freq>=0x40000) { - chan[i].freqH=8; - } else if (chan[i].freq>=0x20000) { - chan[i].freqH=7; - } else if (chan[i].freq>=0x10000) { - chan[i].freqH=6; - } else if (chan[i].freq>=0x8000) { - chan[i].freqH=5; - } else if (chan[i].freq>=0x4000) { - chan[i].freqH=4; - } else if (chan[i].freq>=0x2000) { - chan[i].freqH=3; - } else if (chan[i].freq>=0x1000) { - chan[i].freqH=2; - } else if (chan[i].freq>=0x800) { - chan[i].freqH=1; - } else { - chan[i].freqH=0; - } - chan[i].freqL=(chan[i].freq>>chan[i].freqH)&0x3ff; - chan[i].freqH=8^chan[i].freqH; - ctrl|=(chan[i].active?0x80:0)|(chan[i].damp?0x40:0)|(isMuted[i]?8:(chan[i].pan&0xf)); - unsigned int waveNum=chan[i].sample; - if (ramSize<=0x200000) { - waveNum=MIN(waveNum,0x7f)|0x180; - } - if (chan[i].keyOn) { - rWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+PCM_REG(i),ctrl&~0x80); // force keyoff first - rWrite(PCM_ADDR_WAVE_H_FN_L+PCM_REG(i),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1)); - rWrite(PCM_ADDR_WAVE_L+PCM_REG(i),waveNum&0xff); - if (!chan[i].std.vol.had) { - chan[i].outVol=chan[i].vol; - immWrite(PCM_ADDR_TL+(PCM_REG(i)),((0x7f-chan[i].outVol)<<1)|(chan[i].levelDirect?1:0)); - } - chan[i].writeCtrl=true; - chan[i].keyOn=false; - } - if (chan[i].keyOff) { - chan[i].writeCtrl=true; - chan[i].keyOff=false; - } - if (chan[i].freqChanged) { - rWrite(PCM_ADDR_WAVE_H_FN_L+PCM_REG(i),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1)); - rWrite(PCM_ADDR_FN_H_PR_OCT+PCM_REG(i),((chan[i].freqH&0xf)<<4)|(chan[i].pseudoReverb?0x08:0x00)|((chan[i].freqL>>7)&0x7)); - chan[i].freqChanged=false; - } - if (chan[i].writeCtrl) { - rWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+PCM_REG(i),ctrl); - chan[i].writeCtrl=false; - } + if (chan[i].std.ex1.had) { + chan[i].lfo=chan[i].std.ex1.val&0x7; + rWrite(PCM_ADDR_LFO_VIB+PCM_REG(i),(chan[i].lfo<<3)|(chan[i].vib)); } + + if (chan[i].std.fms.had) { + chan[i].vib=chan[i].std.fms.val&0x7; + rWrite(PCM_ADDR_LFO_VIB+PCM_REG(i),(chan[i].lfo<<3)|(chan[i].vib)); + } + + if (chan[i].std.ams.had) { + chan[i].am=chan[i].std.ams.val&0x7; + rWrite(PCM_ADDR_AM+PCM_REG(i),chan[i].am); + } + + if (chan[i].std.ex2.had) { + chan[i].ar=chan[i].std.ex2.val&0xf; + rWrite(PCM_ADDR_AR_D1R+PCM_REG(i),(chan[i].ar<<4)|(chan[i].d1r)); + } + + if (chan[i].std.ex3.had) { + chan[i].d1r=chan[i].std.ex3.val&0xf; + rWrite(PCM_ADDR_AR_D1R+PCM_REG(i),(chan[i].ar<<4)|(chan[i].d1r)); + } + + if (chan[i].std.ex4.had) { + chan[i].dl=chan[i].std.ex4.val&0xf; + rWrite(PCM_ADDR_DL_D2R+PCM_REG(i),(chan[i].dl<<4)|(chan[i].d2r)); + } + + if (chan[i].std.ex5.had) { + chan[i].d2r=chan[i].std.ex5.val&0xf; + rWrite(PCM_ADDR_DL_D2R+PCM_REG(i),(chan[i].dl<<4)|(chan[i].d2r)); + } + + if (chan[i].std.ex6.had) { + chan[i].rc=chan[i].std.ex6.val&0xf; + rWrite(PCM_ADDR_RC_RR+PCM_REG(i),(chan[i].rc<<4)|(chan[i].rr)); + } + + if (chan[i].std.ex7.had) { + chan[i].rr=chan[i].std.ex7.val&0xf; + rWrite(PCM_ADDR_RC_RR+PCM_REG(i),(chan[i].rc<<4)|(chan[i].rr)); + } + } else { int ops=(slots[3][i]!=255 && chan[i].state.ops==4 && oplType==3)?4:2; chan[i].std.next(); @@ -1360,7 +1334,83 @@ void DivPlatformOPL::tick(bool sysTick) { bool updateDrums=false; for (int i=0; igetSample(chan[i].sample); + unsigned char ctrl=0; + double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0; + chan[i].freq=(int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,(524288*768))); + if (chan[i].freq<0x400) chan[i].freq=0x400; + if (chan[i].freq>0x4000000) chan[i].freq=0x4000000; + if (chan[i].freq>=0x2000000) { + chan[i].freqH=15; + } else if (chan[i].freq>=0x1000000) { + chan[i].freqH=14; + } else if (chan[i].freq>=0x800000) { + chan[i].freqH=13; + } else if (chan[i].freq>=0x400000) { + chan[i].freqH=12; + } else if (chan[i].freq>=0x200000) { + chan[i].freqH=11; + } else if (chan[i].freq>=0x100000) { + chan[i].freqH=10; + } else if (chan[i].freq>=0x80000) { + chan[i].freqH=9; + } else if (chan[i].freq>=0x40000) { + chan[i].freqH=8; + } else if (chan[i].freq>=0x20000) { + chan[i].freqH=7; + } else if (chan[i].freq>=0x10000) { + chan[i].freqH=6; + } else if (chan[i].freq>=0x8000) { + chan[i].freqH=5; + } else if (chan[i].freq>=0x4000) { + chan[i].freqH=4; + } else if (chan[i].freq>=0x2000) { + chan[i].freqH=3; + } else if (chan[i].freq>=0x1000) { + chan[i].freqH=2; + } else if (chan[i].freq>=0x800) { + chan[i].freqH=1; + } else { + chan[i].freqH=0; + } + chan[i].freqL=(chan[i].freq>>chan[i].freqH)&0x3ff; + chan[i].freqH=8^chan[i].freqH; + ctrl|=(chan[i].active?0x80:0)|(chan[i].damp?0x40:0)|(chan[i].lfoReset?0x20:0)|(chan[i].ch?0x10:0)|(isMuted[i]?8:(chan[i].pan&0xf)); + unsigned int waveNum=chan[i].sample; + if (ramSize<=0x200000) { + waveNum=CLAMP(waveNum,0,0x7f)|0x180; + } + if (chan[i].keyOn) { + rWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+PCM_REG(i),ctrl&~0x80); // force keyoff first + rWrite(PCM_ADDR_WAVE_H_FN_L+PCM_REG(i),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1)); + rWrite(PCM_ADDR_WAVE_L+PCM_REG(i),waveNum&0xff); + rWrite(PCM_ADDR_LFO_VIB+PCM_REG(i),(chan[i].lfo<<3)|(chan[i].vib)); + rWrite(PCM_ADDR_AR_D1R+PCM_REG(i),(chan[i].ar<<4)|(chan[i].d1r)); + rWrite(PCM_ADDR_DL_D2R+PCM_REG(i),(chan[i].dl<<4)|(chan[i].d2r)); + rWrite(PCM_ADDR_RC_RR+PCM_REG(i),(chan[i].rc<<4)|(chan[i].rr)); + rWrite(PCM_ADDR_AM+PCM_REG(i),chan[i].am); + if (!chan[i].std.vol.had) { + chan[i].outVol=chan[i].vol; + immWrite(PCM_ADDR_TL+(PCM_REG(i)),((0x7f-chan[i].outVol)<<1)|(chan[i].levelDirect?1:0)); + } + chan[i].writeCtrl=true; + chan[i].keyOn=false; + } + if (chan[i].keyOff) { + chan[i].writeCtrl=true; + chan[i].keyOff=false; + } + if (chan[i].freqChanged) { + rWrite(PCM_ADDR_WAVE_H_FN_L+PCM_REG(i),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1)); + rWrite(PCM_ADDR_FN_H_PR_OCT+PCM_REG(i),((chan[i].freqH&0xf)<<4)|(chan[i].pseudoReverb?0x08:0x00)|((chan[i].freqL>>7)&0x7)); + chan[i].freqChanged=false; + } + if (chan[i].writeCtrl) { + rWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+PCM_REG(i),ctrl); + chan[i].writeCtrl=false; + } + } } else { if (chan[i].freqChanged) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,octave(chan[i].baseFreq)*2,chan[i].pitch2,chipClock,CHIP_FREQBASE); @@ -1655,6 +1705,35 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; } + if (ins->type==DIV_INS_OPL4PCM) { + chan[c.chan].lfo=ins->multipcm.lfo; + chan[c.chan].vib=ins->multipcm.vib; + chan[c.chan].am=ins->multipcm.am; + chan[c.chan].ar=ins->multipcm.ar; + chan[c.chan].d1r=ins->multipcm.d1r; + chan[c.chan].dl=ins->multipcm.dl; + chan[c.chan].d2r=ins->multipcm.d2r; + chan[c.chan].rc=ins->multipcm.rc; + chan[c.chan].rr=ins->multipcm.rr; + chan[c.chan].damp=ins->opl4pcm.damp; + chan[c.chan].pseudoReverb=ins->opl4pcm.pseudoReverb; + chan[c.chan].levelDirect=ins->opl4pcm.levelDirect; + chan[c.chan].lfoReset=ins->opl4pcm.lfoReset; + } else { + chan[c.chan].lfo=0; + chan[c.chan].vib=0; + chan[c.chan].am=0; + chan[c.chan].ar=15; + chan[c.chan].d1r=15; + chan[c.chan].dl=0; + chan[c.chan].d2r=0; + chan[c.chan].rc=15; + chan[c.chan].rr=15; + chan[c.chan].damp=false; + chan[c.chan].pseudoReverb=false; + chan[c.chan].levelDirect=true; + chan[c.chan].lfoReset=false; + } chan[c.chan].active=true; chan[c.chan].keyOn=true; chan[c.chan].macroInit(ins); @@ -1852,6 +1931,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; case DIV_CMD_PANNING: { if (PCM_CHECK(c.chan)) { + chan[c.chan].ch=false; chan[c.chan].pan=8^MIN(parent->convertPanSplitToLinearLR(c.value,c.value2,15)+1,15); chan[c.chan].freqChanged=true; chan[c.chan].writeCtrl=true; @@ -1880,6 +1960,12 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_SURROUND_PANNING: { + if (PCM_CHECK(c.chan)) { + chan[c.chan].ch=true; + chan[c.chan].freqChanged=true; + chan[c.chan].writeCtrl=true; + break; + } if (oplType!=3) break; if (c.chan==adpcmChan) break; @@ -2331,9 +2417,99 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_HARD_RESET: + if (PCM_CHECK(c.chan)) break; if (c.chan==adpcmChan) break; chan[c.chan].hardReset=c.value; break; + case DIV_CMD_OPL4_PCM_MIX_FM: + if (chipType==4) { + rWrite(PCM_ADDR_MIX_FM,(CLAMP((0x70-(c.value&0x70)),0,0x70)>>1)|(CLAMP((7-(c.value&7)),0,7))); + } + break; + case DIV_CMD_OPL4_PCM_MIX_PCM: + if (chipType==4) { + rWrite(PCM_ADDR_MIX_PCM,(CLAMP((0x70-(c.value&0x70)),0,0x70)>>1)|(CLAMP((7-(c.value&7)),0,7))); + } + break; + case DIV_CMD_OPL4_PCM_LFO: + if (PCM_CHECK(c.chan)) { + chan[c.chan].lfo=c.value&7; + rWrite(PCM_ADDR_LFO_VIB+PCM_REG(c.chan),(chan[c.chan].lfo<<3)|(chan[c.chan].vib)); + } + break; + case DIV_CMD_OPL4_PCM_VIB: + if (PCM_CHECK(c.chan)) { + chan[c.chan].vib=c.value&7; + rWrite(PCM_ADDR_LFO_VIB+PCM_REG(c.chan),(chan[c.chan].lfo<<3)|(chan[c.chan].vib)); + } + break; + case DIV_CMD_OPL4_PCM_AM: + if (PCM_CHECK(c.chan)) { + chan[c.chan].am=c.value&7; + rWrite(PCM_ADDR_AM+PCM_REG(c.chan),chan[c.chan].am); + } + break; + case DIV_CMD_OPL4_PCM_AR: + if (PCM_CHECK(c.chan)) { + chan[c.chan].ar=c.value&0xf; + rWrite(PCM_ADDR_AR_D1R+PCM_REG(c.chan),(chan[c.chan].ar<<4)|(chan[c.chan].d1r)); + } + break; + case DIV_CMD_OPL4_PCM_D1R: + if (PCM_CHECK(c.chan)) { + chan[c.chan].d1r=c.value&0xf; + rWrite(PCM_ADDR_AR_D1R+PCM_REG(c.chan),(chan[c.chan].ar<<4)|(chan[c.chan].d1r)); + } + break; + case DIV_CMD_OPL4_PCM_DL: + if (PCM_CHECK(c.chan)) { + chan[c.chan].dl=c.value&0xf; + rWrite(PCM_ADDR_DL_D2R+PCM_REG(c.chan),(chan[c.chan].dl<<4)|(chan[c.chan].d2r)); + } + break; + case DIV_CMD_OPL4_PCM_D2R: + if (PCM_CHECK(c.chan)) { + chan[c.chan].d2r=c.value&0xf; + rWrite(PCM_ADDR_DL_D2R+PCM_REG(c.chan),(chan[c.chan].dl<<4)|(chan[c.chan].d2r)); + } + break; + case DIV_CMD_OPL4_PCM_RC: + if (PCM_CHECK(c.chan)) { + chan[c.chan].rc=c.value&0xf; + rWrite(PCM_ADDR_RC_RR+PCM_REG(c.chan),(chan[c.chan].rc<<4)|(chan[c.chan].rr)); + } + break; + case DIV_CMD_OPL4_PCM_RR: + if (PCM_CHECK(c.chan)) { + chan[c.chan].rr=c.value&0xf; + rWrite(PCM_ADDR_RC_RR+PCM_REG(c.chan),(chan[c.chan].rc<<4)|(chan[c.chan].rr)); + } + break; + case DIV_CMD_OPL4_PCM_DAMP: + if (PCM_CHECK(c.chan)) { + chan[c.chan].damp=c.value&1; + chan[c.chan].freqChanged=true; + chan[c.chan].writeCtrl=true; + } + break; + case DIV_CMD_OPL4_PCM_PSEUDO_REVERB: + if (PCM_CHECK(c.chan)) { + chan[c.chan].pseudoReverb=c.value&1; + chan[c.chan].freqChanged=true; + } + break; + case DIV_CMD_OPL4_PCM_LFO_RESET: + if (PCM_CHECK(c.chan)) { + chan[c.chan].lfoReset=c.value&1; + chan[c.chan].freqChanged=true; + chan[c.chan].writeCtrl=true; + } + break; + case DIV_CMD_OPL4_PCM_LEVEL_DIRECT: + if (PCM_CHECK(c.chan)) { + immWrite(PCM_ADDR_TL+PCM_REG(c.chan),((0x7f-chan[c.chan].outVol)<<1)|(chan[c.chan].levelDirect?1:0)); + } + break; case DIV_CMD_MACRO_OFF: chan[c.chan].std.mask(c.value,true); break; @@ -3019,6 +3195,7 @@ void DivPlatformOPL::renderSamples(int sysID) { case DIV_SAMPLE_DEPTH_8BIT: bitDepth=0; break; + // TODO: 12 bit PCM case DIV_SAMPLE_DEPTH_16BIT: bitDepth=2; break; diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index 708d30b51..e62f5ec84 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -61,7 +61,8 @@ class DivPlatformOPL: public DivDispatch { unsigned int freqH, freqL; int sample, fixedFreq; bool furnacePCM, fourOp, hardReset, writeCtrl; - bool levelDirect, damp, pseudoReverb; + bool levelDirect, damp, pseudoReverb, lfoReset, ch; + int lfo, vib, am, ar, d1r, d2r, dl, rc, rr; int pan; int macroVolMul; Channel(): @@ -77,6 +78,17 @@ class DivPlatformOPL: public DivDispatch { levelDirect(true), damp(false), pseudoReverb(false), + lfoReset(false), + ch(false), + lfo(0), + vib(0), + am(0), + ar(15), + d1r(15), + d2r(0), + dl(0), + rc(15), + rr(15), pan(3), macroVolMul(64) { state.ops=2; @@ -208,7 +220,6 @@ class DivPlatformOPL: public DivDispatch { void renderSamples(int chipID); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); - YMF278& getChip(); DivPlatformOPL(): pcmMemory(0x400000), pcm(pcmMemory) {} diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 293ad4f7f..9ee939029 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -261,7 +261,23 @@ const char* cmdName[]={ "MINMOD_ECHO", "BIFURCATOR_STATE_LOAD", - "BIFURCATOR_PARAMETER" + "BIFURCATOR_PARAMETER", + + "OPL4_MIX_FM", + "OPL4_MIX_PCM", + "OPL4_LFO", + "OPL4_VIB", + "OPL4_AM", + "OPL4_AR", + "OPL4_D1R", + "OPL4_DL", + "OPL4_D2R", + "OPL4_RR", + "OPL4_RC", + "OPL4_DAMP", + "OPL4_PSEUDO_REVERB", + "OPL4_LFO_RESET", + "OPL4_LEVEL_DIRECT" }; static_assert((sizeof(cmdName)/sizeof(void*))==DIV_CMD_MAX,"update cmdName!"); diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 41ecbb050..384b0726a 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -599,6 +599,25 @@ void DivEngine::registerSystems() { {0x5b, {DIV_CMD_FM_KSR, _("5Bxy: Set whether key will scale envelope (x: operator from 1 to 4 (0 for all ops); y: enabled)"), effectOpVal<4>, effectValAnd<1>}}, }; + EffectHandlerMap fmOPL4PostEffectHandlerMap(fmOPLPostEffectHandlerMap); + fmOPL4PostEffectHandlerMap.insert({ + {0x1e, {DIV_CMD_OPL4_PCM_MIX_FM, _("1Exy: FM global level (left, right; 0 to 7)"), effectVal}}, + {0x1f, {DIV_CMD_OPL4_PCM_MIX_PCM, _("1Fxy: PCM global level (left, right; 0 to 7)"), effectVal}}, + {0x20, {DIV_CMD_OPL4_PCM_LFO, _("20xx: PCM LFO Rate (0 to 7)"), effectValAnd<7>}}, + {0x21, {DIV_CMD_OPL4_PCM_VIB, _("21xx: PCM LFO PM Depth (0 to 7)"), effectValAnd<7>}}, + {0x22, {DIV_CMD_OPL4_PCM_AM, _("22xx: PCM LFO AM Depth (0 to 7)"), effectValAnd<7>}}, + {0x23, {DIV_CMD_OPL4_PCM_AR, _("23xx: PCM Attack Rate (0 to 15)"), effectValAnd<15>}}, + {0x24, {DIV_CMD_OPL4_PCM_D1R, _("24xx: PCM Decay 1 Rate (0 to 15)"), effectValAnd<15>}}, + {0x25, {DIV_CMD_OPL4_PCM_DL, _("25xx: PCM Decay Level (0 to 15)"), effectValAnd<15>}}, + {0x26, {DIV_CMD_OPL4_PCM_D2R, _("26xx: PCM Decay 2 Rate (0 to 15)"), effectValAnd<15>}}, + {0x27, {DIV_CMD_OPL4_PCM_RC, _("27xx: PCM Release Rate (0 to 15)"), effectValAnd<15>}}, + {0x28, {DIV_CMD_OPL4_PCM_RR, _("28xx: PCM Rate Correction (0 to 15)"), effectValAnd<15>}}, + {0x2c, {DIV_CMD_OPL4_PCM_DAMP, _("2Cxx: PCM Damp"), effectValAnd<1>}}, + {0x2d, {DIV_CMD_OPL4_PCM_PSEUDO_REVERB, _("2Dxx: PCM Pseudo Reverb"), effectValAnd<1>}}, + {0x2e, {DIV_CMD_OPL4_PCM_LFO_RESET, _("2Exx: PCM LFO Reset"), effectValAnd<1>}}, + {0x2f, {DIV_CMD_OPL4_PCM_LEVEL_DIRECT, _("2Fxx: PCM Level Direct"), effectValAnd<1>}}, + }); + EffectHandlerMap c64PostEffectHandlerMap={ {0x10, {DIV_CMD_WAVE, _("10xx: Set waveform (bit 0: triangle; bit 1: saw; bit 2: pulse; bit 3: noise)")}}, {0x11, {DIV_CMD_C64_CUTOFF, _("11xx: Set coarse cutoff (not recommended; use 4xxx instead)")}}, @@ -1616,28 +1635,28 @@ void DivEngine::registerSystems() { // to Grauw: feel free to change this to 24 during development of OPL4's PCM part. // TODO: add 12-bit and 16-bit big-endian sample formats sysDefs[DIV_SYSTEM_OPL4]=new DivSysDef( - _("Yamaha YMF278B (OPL4)"), NULL, 0xae, 0, 42, true, true, 0, false, (1U<type==DIV_INS_C219 || ins->type==DIV_INS_NDS || ins->type==DIV_INS_GBA_DMA || - ins->type==DIV_INS_GBA_MINMOD) { + ins->type==DIV_INS_GBA_MINMOD || + ins->type==DIV_INS_OPL4PCM) { insTabSample(ins); } if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("Namco 163")) { @@ -6437,8 +6438,8 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndTabItem(); } - if (ins->type==DIV_INS_MULTIPCM) { - if (ImGui::BeginTabItem("MultiPCM")) { + if (ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_OPL4PCM) { + if (ImGui::BeginTabItem(ins->type==DIV_INS_OPL4PCM?"OPL4 PCM":"MultiPCM")) { ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale); if (ImGui::BeginTable("MultiPCMADSRParams",7,ImGuiTableFlags_NoHostExtendX)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); @@ -6519,6 +6520,12 @@ void FurnaceGUI::drawInsEdit() { P(CWSliderScalar(_("AM Depth"),ImGuiDataType_U8,&ins->multipcm.am,&_ZERO,&_SEVEN)); rightClickable ImGui::EndTable(); } + if (ins->type==DIV_INS_OPL4PCM) { + P(ImGui::Checkbox(_("Damp"),&ins->opl4pcm.damp)); + P(ImGui::Checkbox(_("Pseudo Reverb"),&ins->opl4pcm.pseudoReverb)); + P(ImGui::Checkbox(_("Level Direct"),&ins->opl4pcm.levelDirect)); + P(ImGui::Checkbox(_("LFO Reset"),&ins->opl4pcm.lfoReset)); + } ImGui::EndTabItem(); } } @@ -7503,6 +7510,22 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc(_("Noise Mode"),&ins->std.fmsMacro,0,3,64,uiColors[GUI_COLOR_MACRO_NOISE])); macroList.push_back(FurnaceGUIMacroDesc(_("Wave Mix"),&ins->std.amsMacro,0,3,64,uiColors[GUI_COLOR_MACRO_OTHER])); break; + case DIV_INS_OPL4PCM: + macroList.push_back(FurnaceGUIMacroDesc(_("Volume"),&ins->std.volMacro,0,127,160,uiColors[GUI_COLOR_MACRO_VOLUME])); + macroList.push_back(FurnaceGUIMacroDesc(_("Arpeggio"),&ins->std.arpMacro,-120,120,160,uiColors[GUI_COLOR_MACRO_PITCH],true,NULL,macroHoverNote,false,NULL,true,ins->std.arpMacro.val)); + macroList.push_back(FurnaceGUIMacroDesc(_("Panning"),&ins->std.panLMacro,-7,7,45,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL)); + macroList.push_back(FurnaceGUIMacroDesc(_("Pitch"),&ins->std.pitchMacro,-2048,2047,160,uiColors[GUI_COLOR_MACRO_PITCH],true,macroRelativeMode)); + macroList.push_back(FurnaceGUIMacroDesc(_("Phase Reset"),&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); + macroList.push_back(FurnaceGUIMacroDesc(_("LFO speed"),&ins->std.ex1Macro,0,7,160,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(_("LFO vibrato depth"),&ins->std.fmsMacro,0,7,160,uiColors[GUI_COLOR_MACRO_PITCH])); + macroList.push_back(FurnaceGUIMacroDesc(_("LFO AM depth"),&ins->std.amsMacro,0,7,160,uiColors[GUI_COLOR_MACRO_VOLUME])); + macroList.push_back(FurnaceGUIMacroDesc(_("AR"),&ins->std.ex2Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("D1R"),&ins->std.ex3Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("DL"),&ins->std.ex4Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("D2R"),&ins->std.ex5Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("Rate correction"),&ins->std.ex6Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("RR"),&ins->std.ex7Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + break; case DIV_INS_MAX: case DIV_INS_NULL: diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 63815cd02..23b1666e7 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -4107,6 +4107,7 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_INSTR_GBA_MINMOD,_("GBA MinMod")); UI_COLOR_CONFIG(GUI_COLOR_INSTR_BIFURCATOR,_("Bifurcator")); UI_COLOR_CONFIG(GUI_COLOR_INSTR_SID2,_("SID2")); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_OPL4PCM,_("OPL4 PCM")); UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,_("Other/Unknown")); ImGui::TreePop(); }