diff --git a/CMakeLists.txt b/CMakeLists.txt index d19a2d3ab..b851ce593 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -552,6 +552,7 @@ src/engine/platform/sound/nes/fds.c src/engine/platform/sound/nes/mmc5.c src/engine/platform/sound/vera_psg.c src/engine/platform/sound/vera_pcm.c +src/engine/platform/sound/ymf278b/ymf278.cpp src/engine/platform/sound/atomicssg/ssg.c diff --git a/papers/format.md b/papers/format.md index 5acb926c6..e9853cd83 100644 --- a/papers/format.md +++ b/papers/format.md @@ -590,6 +590,7 @@ size | description | - 11: 8-bit μ-law PCM | - 12: C219 PCM | - 13: IMA ADPCM + | - 14: 12-bit PCM (MultiPCM) | - 16: 16-bit PCM 1 | loop direction (>=123) or reserved | - 0: forward diff --git a/papers/newIns.md b/papers/newIns.md index 7ca9c349e..96cb02755 100644 --- a/papers/newIns.md +++ b/papers/newIns.md @@ -582,6 +582,11 @@ size | description 1 | LFO rate 1 | vibrato depth 1 | AM depth + 1 | flags (>=221) + | - bit 0: damp + | - bit 1: pseudo-reverb + | - bit 2: LFO reset + | - bit 3: level direct ``` # Sound Unit data (SU) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index f207ae359..e0dd441c6 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -268,6 +268,22 @@ enum DivDispatchCmds { DIV_CMD_FM_OPMASK, // (mask) + DIV_CMD_MULTIPCM_MIX_FM, // (value) + DIV_CMD_MULTIPCM_MIX_PCM, // (value) + DIV_CMD_MULTIPCM_LFO, // (value) + DIV_CMD_MULTIPCM_VIB, // (value) + DIV_CMD_MULTIPCM_AM, // (value) + DIV_CMD_MULTIPCM_AR, // (value) + DIV_CMD_MULTIPCM_D1R, // (value) + DIV_CMD_MULTIPCM_DL, // (value) + DIV_CMD_MULTIPCM_D2R, // (value) + DIV_CMD_MULTIPCM_RC, // (value) + DIV_CMD_MULTIPCM_RR, // (value) + DIV_CMD_MULTIPCM_DAMP, // (value) + DIV_CMD_MULTIPCM_PSEUDO_REVERB, // (value) + DIV_CMD_MULTIPCM_LFO_RESET, // (value) + DIV_CMD_MULTIPCM_LEVEL_DIRECT, // (value) + DIV_CMD_MAX }; diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 5353c77d1..8c2784f2b 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -768,6 +768,24 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_SID2: dispatch=new DivPlatformSID2; break; + case DIV_SYSTEM_OPL4: + dispatch=new DivPlatformOPL; + ((DivPlatformOPL*)dispatch)->setOPLType(4,false); + if (isRender) { + ((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl4CoreRender",0)); + } else { + ((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl4Core",0)); + } + break; + case DIV_SYSTEM_OPL4_DRUMS: + dispatch=new DivPlatformOPL; + ((DivPlatformOPL*)dispatch)->setOPLType(4,true); + if (isRender) { + ((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl4CoreRender",0)); + } else { + ((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl4Core",0)); + } + break; case DIV_SYSTEM_DUMMY: dispatch=new DivPlatformDummy; break; diff --git a/src/engine/engine.h b/src/engine/engine.h index 3dacd0730..8d83c272e 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -54,8 +54,8 @@ class DivWorkPool; #define DIV_UNSTABLE -#define DIV_VERSION "dev220" -#define DIV_ENGINE_VERSION 220 +#define DIV_VERSION "dev221" +#define DIV_ENGINE_VERSION 221 // for imports #define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_FC 0xff02 diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 5caaa7f56..6774202f1 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -174,7 +174,11 @@ bool DivInstrumentMultiPCM::operator==(const DivInstrumentMultiPCM& other) { _C(rc) && _C(lfo) && _C(vib) && - _C(am) + _C(am) && + _C(damp) && + _C(pseudoReverb) && + _C(lfoReset) && + _C(levelDirect) ); } @@ -892,6 +896,14 @@ void DivInstrument::writeFeatureMP(SafeWriter* w) { w->writeC(multipcm.vib); w->writeC(multipcm.am); + unsigned char next=( + (multipcm.damp?1:0)& + (multipcm.pseudoReverb?2:0)& + (multipcm.lfoReset?4:0)& + (multipcm.levelDirect?8:0) + ); + w->writeC(next); + FEATURE_END; } @@ -2218,6 +2230,14 @@ void DivInstrument::readFeatureMP(SafeReader& reader, short version) { multipcm.vib=reader.readC(); multipcm.am=reader.readC(); + if (version>=221) { + unsigned char next=reader.readC(); + multipcm.damp=next&1; + multipcm.pseudoReverb=next&2; + multipcm.lfoReset=next&4; + multipcm.levelDirect=next&8; + } + READ_FEAT_END; } @@ -3492,4 +3512,4 @@ DivInstrument& DivInstrument::operator=( const DivInstrument& ins ) { *(DivInstrumentPOD*)this=ins; name=ins.name; return *this; -} \ No newline at end of file +} diff --git a/src/engine/instrument.h b/src/engine/instrument.h index f19f0e456..8115d81ab 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -612,6 +612,7 @@ struct DivInstrumentFDS { struct DivInstrumentMultiPCM { unsigned char ar, d1r, dl, d2r, rr, rc; unsigned char lfo, vib, am; + bool damp, pseudoReverb, lfoReset, levelDirect; bool operator==(const DivInstrumentMultiPCM& other); bool operator!=(const DivInstrumentMultiPCM& other) { @@ -620,7 +621,11 @@ struct DivInstrumentMultiPCM { DivInstrumentMultiPCM(): ar(15), d1r(15), dl(0), d2r(0), rr(15), rc(15), - lfo(0), vib(0), am(0) { + lfo(0), vib(0), am(0), + damp(false), + pseudoReverb(false), + lfoReset(false), + levelDirect(true) { } }; diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index b3d47a280..9facff76c 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -29,6 +29,12 @@ #define KVSL(x,y) ((chan[x].state.op[orderedOpsL1[ops==4][y]].kvs==2 && isOutputL[ops==4][chan[x].state.alg][y]) || chan[x].state.op[orderedOpsL1[ops==4][y]].kvs==1) #define CHIP_FREQBASE chipFreqBase +#define PCM_FREQBASE (402653184) + +#define NOTE_PCM(x) parent->calcBaseFreq(chipClock,PCM_FREQBASE,x,false) + +#define PCM_CHECK(ch) ((chipType==4) && (ch>=pcmChanOffs)) +#define PCM_REG(ch) (ch-pcmChanOffs) // N = invalid #define N 255 @@ -159,37 +165,59 @@ const int orderedOpsL[4]={ #define ADDR_FREQH 0xb0 #define ADDR_LR_FB_ALG 0xc0 + +#define PCM_ADDR_WAVE_L 0x208 // Wavetable number LSB +#define PCM_ADDR_WAVE_H_FN_L 0x220 // Wavetable number MSB, F-number LSB +#define PCM_ADDR_FN_H_PR_OCT 0x238 // F-number MSB, Pseudo-reverb, Octave +#define PCM_ADDR_TL 0x250 // Total level, Level direct +#define PCM_ADDR_KEY_DAMP_LFORST_CH_PAN 0x268 // Key, Damp, LFO Reset, Channel select, Panpot + +#define PCM_ADDR_LFO_VIB 0x280 +#define PCM_ADDR_AR_D1R 0x298 +#define PCM_ADDR_DL_D2R 0x2b0 +#define PCM_ADDR_RC_RR 0x2c8 +#define PCM_ADDR_AM 0x2e0 + +#define PCM_ADDR_MIX_FM 0x2f8 +#define PCM_ADDR_MIX_PCM 0x2f9 + void DivPlatformOPL::acquire_nuked(short** buf, size_t len) { - thread_local short o[4]; - thread_local int os[4]; + thread_local short o[8]; + thread_local int os[6]; thread_local ymfm::ymfm_output<2> aOut; + thread_local short pcmBuf[24]; for (size_t h=0; h=0) { - adpcmB->write(w.addr-7,(w.val&15)|0x80); - OPL3_WriteReg(&fm,w.addr,w.val&0xc0); - } else { + if (w.addr>=0x200) { + pcm.writeReg(w.addr&0xff,w.val); + regPool[0x200|(w.addr&0xff)]=w.val; + } else { + switch (w.addr) { + case 8: + if (adpcmChan>=0) { + adpcmB->write(w.addr-7,(w.val&15)|0x80); + OPL3_WriteReg(&fm,w.addr,w.val&0xc0); + } else { + OPL3_WriteReg(&fm,w.addr,w.val); + } + break; + case 7: case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 21: case 22: case 23: + if (adpcmChan>=0) { + adpcmB->write(w.addr-7,w.val); + } else { + OPL3_WriteReg(&fm,w.addr,w.val); + } + break; + default: OPL3_WriteReg(&fm,w.addr,w.val); - } - break; - case 7: case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 21: case 22: case 23: - if (adpcmChan>=0) { - adpcmB->write(w.addr-7,w.val); - } else { - OPL3_WriteReg(&fm,w.addr,w.val); - } - break; - default: - OPL3_WriteReg(&fm,w.addr,w.val); - break; + break; + } + regPool[w.addr&511]=w.val; } - regPool[w.addr&511]=w.val; writes.pop(); } @@ -198,10 +226,20 @@ void DivPlatformOPL::acquire_nuked(short** buf, size_t len) { } else { OPL3_Generate4Ch(&fm,o); } - os[0]+=o[0]; - os[1]+=o[1]; - os[2]+=o[2]; - os[3]+=o[3]; + if (chipType==4) { + pcm.generateMix(o[0],o[1],o[4],o[5],o[6],o[7],pcmBuf); + os[0]+=o[4]; // FM + PCM left + os[1]+=o[5]; // FM + PCM right + os[2]+=o[2]; // FM left + os[3]+=o[3]; // FM right + os[4]+=o[6]; // PCM left + os[5]+=o[7]; // PCM right + } else { + os[0]+=o[0]; + os[1]+=o[1]; + os[2]+=o[2]; + os[3]+=o[3]; + } if (adpcmChan>=0) { adpcmB->clock(); @@ -261,6 +299,12 @@ void DivPlatformOPL::acquire_nuked(short** buf, size_t len) { oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(chOut<<2,-32768,32767); } } + + if (chipType==4) { + for (int i=pcmChanOffs; idata[oscBuf[i]->needle++]=CLAMP(pcmBuf[i-pcmChanOffs],-32768,32767); + } + } if (os[0]<-32768) os[0]=-32768; if (os[0]>32767) os[0]=32767; @@ -274,6 +318,12 @@ void DivPlatformOPL::acquire_nuked(short** buf, size_t len) { if (os[3]<-32768) os[3]=-32768; if (os[3]>32767) os[3]=32767; + if (os[4]<-32768) os[4]=-32768; + if (os[4]>32767) os[4]=32767; + + if (os[5]<-32768) os[5]=-32768; + if (os[5]>32767) os[5]=32767; + buf[0][h]=os[0]; if (totalOutputs>1) { buf[1][h]=os[1]; @@ -285,9 +335,8 @@ void DivPlatformOPL::acquire_nuked(short** buf, size_t len) { buf[3][h]=os[3]; } if (totalOutputs==6) { - // placeholder for OPL4 - buf[4][h]=0; - buf[5][h]=0; + buf[4][h]=os[4]; + buf[5][h]=os[5]; } } } @@ -505,6 +554,103 @@ void DivPlatformOPL::acquire_ymfm3(short** buf, size_t len) { } } +void DivPlatformOPL::acquire_ymfm4(short** buf, size_t len) { + ymfm::ymfm_output<6> out; + + ymfm::ymf278b::fm_engine* fme=fm_ymfm4->debug_fm_engine(); + ymfm::pcm_engine* pcme=fm_ymfm4->debug_pcm_engine(); + ymfm::fm_channel>* fmChan[18]; + ymfm::pcm_channel* pcmChan[24]; + + for (int i=0; i<18; i++) { + fmChan[i]=fme->debug_channel(i); + } + + for (int i=0; i<24; i++) { + pcmChan[i]=pcme->debug_channel(i); + } + + for (size_t h=0; hwrite((w.addr&0x200)?4:(w.addr&0x100)?2:0,w.addr); + fm_ymfm4->write((w.addr&0x200)?5:1,w.val); + + regPool[(w.addr&0x200)?(0x200+(w.addr&255)):(w.addr&511)]=w.val; + writes.pop(); + } + + fm_ymfm4->generate(&out,1); + + buf[0][h]=out.data[4]>>1; // FM + PCM left + if (totalOutputs>1) { + buf[1][h]=out.data[5]>>1; // FM + PCM right + } + if (totalOutputs>2) { + buf[2][h]=out.data[0]>>1; // FM left + } + if (totalOutputs>3) { + buf[3][h]=out.data[1]>>1; // FM right + } + if (totalOutputs==6) { + buf[4][h]=out.data[2]>>1; // PCM left + buf[5][h]=out.data[3]>>1; // PCM right + } + + if (properDrums) { + for (int i=0; i<16; i++) { + unsigned char ch=(i<12 && chan[i&(~1)].fourOp)?outChanMap[i^1]:outChanMap[i]; + if (ch==255) continue; + int chOut=fmChan[ch]->debug_output(0); + if (chOut==0) { + chOut=fmChan[ch]->debug_output(1); + } + if (chOut==0) { + chOut=fmChan[ch]->debug_output(2); + } + if (chOut==0) { + chOut=fmChan[ch]->debug_output(3); + } + if (i==15) { + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(chOut,-32768,32767); + } else { + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(chOut<<1,-32768,32767); + } + } + oscBuf[16]->data[oscBuf[16]->needle++]=CLAMP(fmChan[7]->debug_special2()<<1,-32768,32767); + oscBuf[17]->data[oscBuf[17]->needle++]=CLAMP(fmChan[8]->debug_special1()<<1,-32768,32767); + oscBuf[18]->data[oscBuf[18]->needle++]=CLAMP(fmChan[8]->debug_special2()<<1,-32768,32767); + oscBuf[19]->data[oscBuf[19]->needle++]=CLAMP(fmChan[7]->debug_special1()<<1,-32768,32767); + } else { + for (int i=0; i<18; i++) { + unsigned char ch=outChanMap[i]; + if (ch==255) continue; + int chOut=fmChan[ch]->debug_output(0); + if (chOut==0) { + chOut=fmChan[ch]->debug_output(1); + } + if (chOut==0) { + chOut=fmChan[ch]->debug_output(2); + } + if (chOut==0) { + chOut=fmChan[ch]->debug_output(3); + } + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(chOut<<1,-32768,32767); + } + } + for (int i=0; i<24; i++) { + unsigned char oscOffs=i+pcmChanOffs; + int chOut=pcmChan[i]->debug_output(0); + chOut+=pcmChan[i]->debug_output(1); + chOut+=pcmChan[i]->debug_output(2); + chOut+=pcmChan[i]->debug_output(3); + oscBuf[oscOffs]->data[oscBuf[oscOffs]->needle++]=CLAMP(chOut<<1,-32768,32767); + } + } +} + static const int cycleMap[18]={ 6, 7, 8, 6, 7, 8, 0, 1, 2, 0, 1, 2, 3, 4, 5, 3, 4, 5, @@ -816,6 +962,9 @@ void DivPlatformOPL::acquire(short** buf, size_t len) { case 3: case 759: acquire_ymfm3(buf,len); break; + case 4: + acquire_ymfm4(buf,len); + break; } } else { // OPL3 acquire_nuked(buf,len); @@ -833,144 +982,230 @@ double DivPlatformOPL::NOTE_ADPCMB(int note) { void DivPlatformOPL::tick(bool sysTick) { for (int i=0; icalcArp(chan[i].note,chan[i].std.arp.val)); + } + chan[i].freqChanged=true; + } + + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-131071,131071); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1 && chan[i].active) { + chan[i].keyOn=true; + chan[i].writeCtrl=true; + } + } + + if (chan[i].std.panL.had) { // panning + chan[i].pan=chan[i].std.panL.val&0xf; + chan[i].freqChanged=true; + chan[i].writeCtrl=true; + } + + 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(); + + if (chan[i].std.vol.had) { + chan[i].outVol=VOL_SCALE_LOG_BROKEN(chan[i].vol,MIN(63,chan[i].std.vol.val),63); + for (int j=0; jmelodicChans) { + rWrite(baseAddr+ADDR_KSL_TL,(63-VOL_SCALE_LOG_BROKEN(63-op.tl,chan[i].outVol&0x3f,63))|(op.ksl<<6)); + } else { + rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6)); + } + } + } + } + + if (NEW_ARP_STRAT) { + chan[i].handleArp(); + } else if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val)); + } + chan[i].freqChanged=true; + } + + if (oplType==3 && chan[i].std.panL.had) { + chan[i].pan=((chan[i].std.panL.val&1)<<1)|((chan[i].std.panL.val&2)>>1)|((chan[i].std.panL.val&4)<<1)|((chan[i].std.panL.val&8)>>1); + } + + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-131071,131071); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + + if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.val==1 && chan[i].active) { + chan[i].keyOn=true; + } + } + + if (chan[i].std.alg.had) { + chan[i].state.alg=chan[i].std.alg.val; + } + if (chan[i].std.fb.had) { + chan[i].state.fb=chan[i].std.fb.val; + } + + if (chan[i].std.alg.had || chan[i].std.fb.had || (oplType==3 && chan[i].std.panL.had)) { + if (isMuted[i] && i<=melodicChans) { + rWrite(chanMap[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&1)|(chan[i].state.fb<<1)); + if (ops==4) { + rWrite(chanMap[i+1]+ADDR_LR_FB_ALG,((chan[i].state.alg>>1)&1)|(chan[i].state.fb<<1)); + } + } else { + rWrite(chanMap[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&1)|(chan[i].state.fb<<1)|((chan[i].pan&15)<<4)); + if (ops==4) { + rWrite(chanMap[i+1]+ADDR_LR_FB_ALG,((chan[i].state.alg>>1)&1)|(chan[i].state.fb<<1)|((chan[i].pan&15)<<4)); + } + } + } - if (chan[i].std.vol.had) { - chan[i].outVol=VOL_SCALE_LOG_BROKEN(chan[i].vol,MIN(63,chan[i].std.vol.val),63); for (int j=0; jmelodicChans) { - rWrite(baseAddr+ADDR_KSL_TL,(63-VOL_SCALE_LOG_BROKEN(63-op.tl,chan[i].outVol&0x3f,63))|(op.ksl<<6)); - } else { - rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6)); + if (m.ar.had) { + op.ar=m.ar.val; + rWrite(baseAddr+ADDR_AR_DR,(op.ar<<4)|op.dr); + } + if (m.dr.had) { + op.dr=m.dr.val; + rWrite(baseAddr+ADDR_AR_DR,(op.ar<<4)|op.dr); + } + if (m.sl.had) { + op.sl=m.sl.val; + rWrite(baseAddr+ADDR_SL_RR,(op.sl<<4)|op.rr); + } + if (m.rr.had) { + op.rr=m.rr.val; + rWrite(baseAddr+ADDR_SL_RR,(op.sl<<4)|op.rr); + } + + if (oplType>1) { + if (m.ws.had) { + op.ws=m.ws.val; + rWrite(baseAddr+ADDR_WS,op.ws&((oplType==3)?7:3)); } } - } - } - if (NEW_ARP_STRAT) { - chan[i].handleArp(); - } else if (chan[i].std.arp.had) { - if (!chan[i].inPorta) { - chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val)); - } - chan[i].freqChanged=true; - } - - if (oplType==3 && chan[i].std.panL.had) { - chan[i].pan=((chan[i].std.panL.val&1)<<1)|((chan[i].std.panL.val&2)>>1)|((chan[i].std.panL.val&4)<<1)|((chan[i].std.panL.val&8)>>1); - } - - if (chan[i].std.pitch.had) { - if (chan[i].std.pitch.mode) { - chan[i].pitch2+=chan[i].std.pitch.val; - CLAMP_VAR(chan[i].pitch2,-131071,131071); - } else { - chan[i].pitch2=chan[i].std.pitch.val; - } - chan[i].freqChanged=true; - } - - if (chan[i].std.phaseReset.had) { - if (chan[i].std.phaseReset.val==1 && chan[i].active) { - chan[i].keyOn=true; - } - } - - if (chan[i].std.alg.had) { - chan[i].state.alg=chan[i].std.alg.val; - } - if (chan[i].std.fb.had) { - chan[i].state.fb=chan[i].std.fb.val; - } - - if (chan[i].std.alg.had || chan[i].std.fb.had || (oplType==3 && chan[i].std.panL.had)) { - if (isMuted[i] && i<=melodicChans) { - rWrite(chanMap[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&1)|(chan[i].state.fb<<1)); - if (ops==4) { - rWrite(chanMap[i+1]+ADDR_LR_FB_ALG,((chan[i].state.alg>>1)&1)|(chan[i].state.fb<<1)); + if (m.tl.had) { + op.tl=m.tl.val&63; } - } else { - rWrite(chanMap[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&1)|(chan[i].state.fb<<1)|((chan[i].pan&15)<<4)); - if (ops==4) { - rWrite(chanMap[i+1]+ADDR_LR_FB_ALG,((chan[i].state.alg>>1)&1)|(chan[i].state.fb<<1)|((chan[i].pan&15)<<4)); + if (m.ksl.had) { + op.ksl=m.ksl.val; } - } - } - - for (int j=0; j1) { - if (m.ws.had) { - op.ws=m.ws.val; - rWrite(baseAddr+ADDR_WS,op.ws&((oplType==3)?7:3)); - } - } - - if (m.tl.had) { - op.tl=m.tl.val&63; - } - if (m.ksl.had) { - op.ksl=m.ksl.val; - } - if (m.tl.had || m.ksl.had) { - if (isMuted[i] && i<=melodicChans) { - rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); - } else { - if (KVSL(i,j) || i>melodicChans) { - rWrite(baseAddr+ADDR_KSL_TL,(63-VOL_SCALE_LOG_BROKEN(63-op.tl,chan[i].outVol&0x3f,63))|(op.ksl<<6)); + if (m.tl.had || m.ksl.had) { + if (isMuted[i] && i<=melodicChans) { + rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { - rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6)); + if (KVSL(i,j) || i>melodicChans) { + rWrite(baseAddr+ADDR_KSL_TL,(63-VOL_SCALE_LOG_BROKEN(63-op.tl,chan[i].outVol&0x3f,63))|(op.ksl<<6)); + } else { + rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6)); + } } } } @@ -1090,7 +1325,7 @@ void DivPlatformOPL::tick(bool sysTick) { } } - for (int i=0; i<512; i++) { + for (int i=0; i<768; i++) { if (pendingWrites[i]!=oldWrites[i]) { if ((i>=0x80 && i<0xa0)) { if (weWillWriteRRLater[i-0x80]) continue; @@ -1104,38 +1339,118 @@ void DivPlatformOPL::tick(bool sysTick) { bool updateDrums=false; for (int i=0; icalcFreq(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); - if (chan[i].fixedFreq>0) chan[i].freq=chan[i].fixedFreq; - if (chan[i].freq<0) chan[i].freq=0; - if (chan[i].freq>131071) chan[i].freq=131071; - int freqt=toFreq(chan[i].freq); - chan[i].freqH=freqt>>8; - chan[i].freqL=freqt&0xff; - immWrite(chanMap[i]+ADDR_FREQ,chan[i].freqL); - } - if (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 { - immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(chan[i].active<<5)); + 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) { + immWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+PCM_REG(i),ctrl&~0x80); // force keyoff first + immWrite(PCM_ADDR_WAVE_H_FN_L+PCM_REG(i),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1)); + immWrite(PCM_ADDR_WAVE_L+PCM_REG(i),waveNum&0xff); + immWrite(PCM_ADDR_LFO_VIB+PCM_REG(i),(chan[i].lfo<<3)|(chan[i].vib)); + immWrite(PCM_ADDR_AR_D1R+PCM_REG(i),(chan[i].ar<<4)|(chan[i].d1r)); + immWrite(PCM_ADDR_DL_D2R+PCM_REG(i),(chan[i].dl<<4)|(chan[i].d2r)); + immWrite(PCM_ADDR_RC_RR+PCM_REG(i),(chan[i].rc<<4)|(chan[i].rr)); + immWrite(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) { + immWrite(PCM_ADDR_WAVE_H_FN_L+PCM_REG(i),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1)); + immWrite(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) { + immWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+PCM_REG(i),ctrl); + chan[i].writeCtrl=false; } } } else { - if (chan[i].keyOn) { - immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH); - if (!isMuted[i]) drumState|=(1<<(totalChans-i-1)); - updateDrums=true; - chan[i].keyOn=false; - } else if (chan[i].freqChanged) { - immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH); + 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); + if (chan[i].fixedFreq>0) chan[i].freq=chan[i].fixedFreq; + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].freq>131071) chan[i].freq=131071; + int freqt=toFreq(chan[i].freq); + chan[i].freqH=freqt>>8; + chan[i].freqL=freqt&0xff; + immWrite(chanMap[i]+ADDR_FREQ,chan[i].freqL); } + if (imelodicChans && ins->type==DIV_INS_OPL_DRUMS) { for (int i=0; i<4; i++) { @@ -1369,7 +1692,62 @@ int DivPlatformOPL::dispatch(DivCommand c) { } switch (c.cmd) { case DIV_CMD_NOTE_ON: { - if (c.chan==adpcmChan) { // ADPCM + if (PCM_CHECK(c.chan)) { // OPL4 PCM + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_MULTIPCM); + chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:127; + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].sample=ins->amiga.getSample(c.value); + chan[c.chan].sampleNote=c.value; + c.value=ins->amiga.getFreq(c.value); + chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote; + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PCM(c.value); + } + if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { + chan[c.chan].sample=-1; + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + if (ins->type==DIV_INS_MULTIPCM) { + 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->multipcm.damp; + chan[c.chan].pseudoReverb=ins->multipcm.pseudoReverb; + chan[c.chan].levelDirect=ins->multipcm.levelDirect; + chan[c.chan].lfoReset=ins->multipcm.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); + if (!chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + break; + } else if (c.chan==adpcmChan) { // ADPCM DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:255; if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_ADPCMB) { @@ -1493,6 +1871,10 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; + if (PCM_CHECK(c.chan)) { + chan[c.chan].sample=-1; + chan[c.chan].macroInit(NULL); + } break; case DIV_CMD_NOTE_OFF_ENV: chan[c.chan].keyOff=true; @@ -1513,6 +1895,10 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } + if (PCM_CHECK(c.chan)) { // OPL4 PCM + immWrite(PCM_ADDR_TL+PCM_REG(c.chan),((0x7f-chan[c.chan].outVol)<<1)|(chan[c.chan].levelDirect?1:0)); + break; + } if (c.chan==adpcmChan) { // ADPCM-B immWrite(18,(isMuted[adpcmChan]?0:chan[adpcmChan].outVol)); break; @@ -1550,10 +1936,17 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].ins=c.value; 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; + break; + } if (oplType!=3) break; if (c.chan==adpcmChan) break; chan[c.chan].pan&=~3; - if (c.value==0 && c.value2==0 && compatPan) { + if (c.value==0 && c.value2==0 && ((chipType!=4) && compatPan)) { chan[c.chan].pan|=3; } else { chan[c.chan].pan|=(c.value>0)|((c.value2>0)<<1); @@ -1573,6 +1966,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; @@ -1608,6 +2007,29 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { + if (PCM_CHECK(c.chan)) { + int destFreq=NOTE_PCM(c.value2+chan[c.chan].sampleNoteDelta); + 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=(c.chan==adpcmChan)?(NOTE_ADPCMB(c.value2)):(NOTE_FREQUENCY(c.value2)); int newFreq; bool return2=false; @@ -1648,17 +2070,20 @@ int DivPlatformOPL::dispatch(DivCommand c) { iface.sampleBank=sampleBank; break; case DIV_CMD_LEGATO: { + // TODO: OPL4 PCM if (chan[c.chan].insChanged) { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_FM); commitState(c.chan,ins); chan[c.chan].insChanged=false; } - chan[c.chan].baseFreq=(c.chan==adpcmChan)?(NOTE_ADPCMB(c.value)):(NOTE_FREQUENCY(c.value)); + chan[c.chan].baseFreq=(PCM_CHECK(c.chan))?NOTE_PCM(c.value+chan[c.chan].sampleNoteDelta+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val-12):(0))): + (c.chan==adpcmChan)?(NOTE_ADPCMB(c.value)):(NOTE_FREQUENCY(c.value)); chan[c.chan].note=c.value; chan[c.chan].freqChanged=true; break; } case DIV_CMD_FM_LFO: { + if (PCM_CHECK(c.chan)) break; if (c.chan==adpcmChan) break; if (c.value&2) { dvb=c.value&1; @@ -1669,6 +2094,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_FB: { + if (PCM_CHECK(c.chan)) break; if (c.chan==adpcmChan) break; chan[c.chan].state.fb=c.value&7; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; @@ -1686,6 +2112,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_MULT: { + if (PCM_CHECK(c.chan)) break; if (c.chan==adpcmChan) break; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; if (c.value>=ops) break; @@ -1698,6 +2125,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_TL: { + if (PCM_CHECK(c.chan)) break; if (c.chan==adpcmChan) break; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; if (c.value>=ops) break; @@ -1718,6 +2146,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_AR: { + if (PCM_CHECK(c.chan)) break; if (c.chan==adpcmChan) break; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; if (c.value<0) { @@ -1741,6 +2170,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_DR: { + if (PCM_CHECK(c.chan)) break; if (c.chan==adpcmChan) break; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; if (c.value<0) { @@ -1764,6 +2194,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_SL: { + if (PCM_CHECK(c.chan)) break; if (c.chan==adpcmChan) break; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; if (c.value<0) { @@ -1787,6 +2218,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_RR: { + if (PCM_CHECK(c.chan)) break; if (c.chan==adpcmChan) break; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; if (c.value<0) { @@ -1810,6 +2242,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_AM: { + if (PCM_CHECK(c.chan)) break; if (c.chan==adpcmChan) break; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; if (c.value<0) { @@ -1833,6 +2266,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_VIB: { + if (PCM_CHECK(c.chan)) break; if (c.chan==adpcmChan) break; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; if (c.value<0) { @@ -1856,6 +2290,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_SUS: { + if (PCM_CHECK(c.chan)) break; if (c.chan==adpcmChan) break; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; if (c.value<0) { @@ -1879,6 +2314,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_KSR: { + if (PCM_CHECK(c.chan)) break; if (c.chan==adpcmChan) break; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; if (c.value<0) { @@ -1902,6 +2338,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_WS: { + if (PCM_CHECK(c.chan)) break; if (c.chan==adpcmChan) break; if (oplType<2) break; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; @@ -1926,6 +2363,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_RS: { + if (PCM_CHECK(c.chan)) break; if (c.chan==adpcmChan) break; if (oplType<2) break; int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; @@ -1974,6 +2412,10 @@ int DivPlatformOPL::dispatch(DivCommand c) { chanMap=properDrums?chanMapOPL3Drums:chanMapOPL3; melodicChans=properDrums?15:18; totalChans=properDrums?20:18; + if (chipType==4) { + pcmChanOffs=totalChans; + totalChans+=24; + } } else { chanMap=properDrums?chanMapOPL2Drums:chanMapOPL2; melodicChans=properDrums?6:9; @@ -1982,9 +2424,103 @@ 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_MULTIPCM_MIX_FM: + if (chipType==4) { + fmMixL=CLAMP((c.value&0x70)>>4,0,7); + fmMixR=CLAMP((c.value&0x7),0,7); + immWrite(PCM_ADDR_MIX_FM,((7-fmMixR)<<3)|(7-fmMixL)); + } + break; + case DIV_CMD_MULTIPCM_MIX_PCM: + if (chipType==4) { + pcmMixL=CLAMP((c.value&0x70)>>4,0,7); + pcmMixR=CLAMP((c.value&0x7),0,7); + immWrite(PCM_ADDR_MIX_PCM,((7-pcmMixR)<<3)|(7-pcmMixL)); + } + break; + case DIV_CMD_MULTIPCM_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_MULTIPCM_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_MULTIPCM_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_MULTIPCM_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_MULTIPCM_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_MULTIPCM_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_MULTIPCM_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_MULTIPCM_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_MULTIPCM_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_MULTIPCM_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_MULTIPCM_PSEUDO_REVERB: + if (PCM_CHECK(c.chan)) { + chan[c.chan].pseudoReverb=c.value&1; + chan[c.chan].freqChanged=true; + } + break; + case DIV_CMD_MULTIPCM_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_MULTIPCM_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; @@ -1995,13 +2531,18 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].std.restart(c.value); break; case DIV_CMD_GET_VOLMAX: + if (PCM_CHECK(c.chan)) return 127; if (c.chan==adpcmChan) return 255; if (pretendYMU) return 127; return 63; break; case DIV_CMD_PRE_PORTA: + if (PCM_CHECK(c.chan) && chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_MULTIPCM)); + } if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) { - chan[c.chan].baseFreq=(c.chan==adpcmChan)?(NOTE_ADPCMB(chan[c.chan].note)):(NOTE_FREQUENCY(chan[c.chan].note)); + chan[c.chan].baseFreq=(PCM_CHECK(c.chan))?NOTE_PCM(chan[c.chan].note): + ((c.chan==adpcmChan)?(NOTE_ADPCMB(chan[c.chan].note)):(NOTE_FREQUENCY(chan[c.chan].note))); } chan[c.chan].inPorta=c.value; break; @@ -2019,6 +2560,10 @@ void DivPlatformOPL::forceIns() { chanMap=properDrums?chanMapOPL3Drums:chanMapOPL3; melodicChans=properDrums?15:18; totalChans=properDrums?20:18; + if (chipType==4) { + pcmChanOffs=totalChans; + totalChans+=24; + } } else { chanMap=properDrums?chanMapOPL2Drums:chanMapOPL2; melodicChans=properDrums?6:9; @@ -2067,7 +2612,7 @@ void DivPlatformOPL::forceIns() { } */ } - for (int i=0; i<512; i++) { + for (int i=0; i<768; i++) { oldWrites[i]=-1; } immWrite(0xbd,(dam<<7)|(dvb<<6)|(properDrums<<5)|drumState); @@ -2088,6 +2633,9 @@ DivMacroInt* DivPlatformOPL::getChanMacroInt(int ch) { unsigned short DivPlatformOPL::getPan(int ch) { if (totalOutputs<=1) return 0; + if (PCM_CHECK(ch)) { + return parent->convertPanLinearToSplit(8^chan[ch].pan,8,15); + } /*if (chan[ch&(~1)].fourOp) { if (ch&1) { return ((chan[ch-1].pan&2)<<7)|(chan[ch-1].pan&1); @@ -2126,6 +2674,19 @@ DivDispatchOscBuffer* DivPlatformOPL::getOscBuffer(int ch) { } int DivPlatformOPL::mapVelocity(int ch, float vel) { + if (PCM_CHECK(ch)) { // TODO: correct? + // -0.375dB per step + // -6: 64: 16 + // -12: 32: 32 + // -18: 16: 48 + // -24: 8: 64 + // -30: 4: 80 + // -36: 2: 96 + // -42: 1: 112 + if (vel==0) return 0; + if (vel>=1.0) return 127; + return CLAMP(round(128.0-(112.0-log2(vel*127.0)*16.0)),0,127); + } if (ch==adpcmChan) return vel*255.0; // -0.75dB per step // -6: 64: 8 @@ -2142,6 +2703,8 @@ int DivPlatformOPL::mapVelocity(int ch, float vel) { float DivPlatformOPL::getGain(int ch, int vol) { if (vol==0) return 0; + if (PCM_CHECK(ch)) return 1.0/pow(10.0,(float)(127-vol)*0.375/20.0); + if (ch==adpcmChan) return (float)vol/255.0; return 1.0/pow(10.0,(float)(63-vol)*0.75/20.0); } @@ -2150,12 +2713,12 @@ unsigned char* DivPlatformOPL::getRegisterPool() { } int DivPlatformOPL::getRegisterPoolSize() { - return (oplType<3)?256:512; + return (chipType==4)?768:((oplType<3)?256:512); } void DivPlatformOPL::reset() { while (!writes.empty()) writes.pop(); - memset(regPool,0,512); + memset(regPool,0,768); dacVal=0; dacVal2=0; @@ -2209,6 +2772,9 @@ void DivPlatformOPL::reset() { case 3: case 759: fm_ymfm3->reset(); break; + case 4: + fm_ymfm4->reset(); + break; } } else { if (downsample) { @@ -2217,6 +2783,7 @@ void DivPlatformOPL::reset() { OPL3_Reset(&fm,rate); } } + pcm.reset(); if (dumpWrites) { addWrite(0xffffffff,0); @@ -2228,6 +2795,10 @@ void DivPlatformOPL::reset() { outChanMap=outChanMapOPL3; melodicChans=properDrums?15:18; totalChans=properDrums?20:18; + if (chipType==4) { + pcmChanOffs=totalChans; + totalChans+=24; + } } else { chanMap=properDrums?chanMapOPL2Drums:chanMapOPL2; outChanMap=outChanMapOPL2; @@ -2238,8 +2809,9 @@ void DivPlatformOPL::reset() { for (int i=0; i=0) { @@ -2261,7 +2833,7 @@ void DivPlatformOPL::reset() { fm.channel[outChanMap[i]].muted=isMuted[i]; } - for (int i=0; i<512; i++) { + for (int i=0; i<768; i++) { oldWrites[i]=-1; pendingWrites[i]=-1; } @@ -2282,7 +2854,20 @@ void DivPlatformOPL::reset() { } if (oplType==3) { // enable OPL3 features - immWrite(0x105,1); + if (chipType==4) { + immWrite(0x105,3); + // Reset wavetable header + immWrite(0x202,(ramSize<=0x200000)?0x10:0x00); + // initialize mixer volume + fmMixL=7; + fmMixR=7; + pcmMixL=7; + pcmMixR=7; + immWrite(PCM_ADDR_MIX_FM,((7-fmMixR)<<3)|(7-fmMixL)); + immWrite(PCM_ADDR_MIX_PCM,((7-pcmMixR)<<3)|(7-pcmMixL)); + } else { + immWrite(0x105,1); + } } update4OpMask=true; @@ -2373,6 +2958,8 @@ void DivPlatformOPL::setOPLType(int type, bool drums) { pretendYMU=true; adpcmChan=16; } else if (type==4) { + pcmChanOffs=totalChans; + totalChans+=24; chipFreqBase=32768*684; downsample=true; } @@ -2498,18 +3085,43 @@ void DivPlatformOPL::setFlags(const DivConfig& flags) { case 4: switch (flags.getInt("clockSel",0)) { case 0x01: - chipClock=COLOR_PAL*32.0/5.0; + chipClock=COLOR_NTSC*8.0; break; case 0x02: - chipClock=33868800.0; + chipClock=COLOR_PAL*32.0/5.0; break; default: - chipClock=COLOR_NTSC*8.0; + chipClock=33868800.0; + break; + } + switch (flags.getInt("ramSize",0)) { + case 0x01: // 2MB (512KB 512KB 512KB 512KB) + ramSize=0x200000; + break; + case 0x02: // 1MB (512KB 512KB) + ramSize=0x100000; + break; + case 0x03: // 640KB (512KB 128KB) + ramSize=0xa0000; + break; + case 0x04: // 512KB + ramSize=0x80000; + break; + case 0x05: // 256KB (128KB 128KB) + ramSize=0x40000; + break; + case 0x06: // 128KB + ramSize=0x20000; + break; + default: + ramSize=0x400000; break; } CHECK_CUSTOM_CLOCK; + pcm.setClockFrequency(chipClock); rate=chipClock/768; chipRateBase=chipClock/684; + immWrite(0x202,(ramSize<=0x200000)?0x10:0x00); break; case 759: rate=48000; @@ -2519,21 +3131,25 @@ void DivPlatformOPL::setFlags(const DivConfig& flags) { } compatPan=flags.getBool("compatPan",false); - for (int i=0; i<20; i++) { + for (int i=0; i<44; i++) { oscBuf[i]->rate=rate; } } const void* DivPlatformOPL::getSampleMem(int index) { - return (index==0 && adpcmChan>=0) ? adpcmBMem : NULL; + return (index==0 && pcmChanOffs>=0)?pcmMem: + (index==0 && adpcmChan>=0)?adpcmBMem:NULL; } size_t DivPlatformOPL::getSampleMemCapacity(int index) { - return (index==0 && adpcmChan>=0) ? 262144 : 0; + return (index==0 && pcmChanOffs>=0)? + ((ramSize<=0x200000)?0x200000+ramSize:ramSize): + ((index==0 && adpcmChan>=0)?262144:0); } size_t DivPlatformOPL::getSampleMemUsage(int index) { - return (index==0 && adpcmChan>=0) ? adpcmBMemLen : 0; + return (index==0 && pcmChanOffs>=0)?pcmMemLen: + (index==0 && adpcmChan>=0)?adpcmBMemLen:0; } bool DivPlatformOPL::isSampleLoaded(int index, int sample) { @@ -2543,50 +3159,147 @@ bool DivPlatformOPL::isSampleLoaded(int index, int sample) { } const DivMemoryComposition* DivPlatformOPL::getMemCompo(int index) { - if (adpcmChan<0) return NULL; + if ((adpcmChan<0) && (pcmChanOffs<0)) return NULL; if (index!=0) return NULL; return &memCompo; } void DivPlatformOPL::renderSamples(int sysID) { - if (adpcmChan<0) return; - memset(adpcmBMem,0,getSampleMemCapacity(0)); + if (adpcmChan<0 && pcmChanOffs<0) return; + if (adpcmChan>=0 && adpcmBMem!=NULL) { + memset(adpcmBMem,0,262144); + } + if (pcmChanOffs>=0 && pcmMem!=NULL) { + memset(pcmMem,0,4194304); + } + memset(sampleOffPCM,0,256*sizeof(unsigned int)); memset(sampleOffB,0,256*sizeof(unsigned int)); memset(sampleLoaded,0,256*sizeof(bool)); memCompo=DivMemoryComposition(); memCompo.name="Sample Memory"; - size_t memPos=0; - for (int i=0; isong.sampleLen; i++) { - DivSample* s=parent->song.sample[i]; - if (!s->renderOn[0][sysID]) { - sampleOffB[i]=0; - continue; - } + if (pcmChanOffs>=0) { // OPL4 PCM + size_t memPos=((ramSize<=0x200000)?0x200600:0x1800); + const int maxSample=(ramSize<=0x200000)?127:511; + int sampleCount=parent->song.sampleLen; + if (sampleCount>maxSample) sampleCount=maxSample; + for (int i=0; isong.sample[i]; + if (!s->renderOn[0][sysID]) { + sampleOffPCM[i]=0; + continue; + } - int paddedLen=(s->lengthB+255)&(~0xff); - if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) { - memPos=(memPos+0xfffff)&0xf00000; - } - if (memPos>=getSampleMemCapacity(0)) { - logW("out of ADPCM memory for sample %d!",i); - break; - } - if (memPos+paddedLen>=getSampleMemCapacity(0)) { - memcpy(adpcmBMem+memPos,s->dataB,getSampleMemCapacity(0)-memPos); - logW("out of ADPCM memory for sample %d!",i); - } else { - memcpy(adpcmBMem+memPos,s->dataB,paddedLen); + int length; + switch (s->depth) { + default: + case DIV_SAMPLE_DEPTH_8BIT: + length=MIN(65535,s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)); + break; + case DIV_SAMPLE_DEPTH_12BIT: + length=MIN(98303,s->getLoopEndPosition(DIV_SAMPLE_DEPTH_12BIT)); + break; + case DIV_SAMPLE_DEPTH_16BIT: + length=MIN(131070,s->getLoopEndPosition(DIV_SAMPLE_DEPTH_16BIT)); + break; + } + unsigned char* src=(unsigned char*)s->getCurBuf(); + int actualLength=MIN((int)(getSampleMemCapacity(0)-memPos),length); + if (actualLength>0) { + #ifdef TA_BIG_ENDIAN + memcpy(&pcmMem[memPos],src,actualLength); + #else + if (s->depth==DIV_SAMPLE_DEPTH_16BIT) { + for (int i=0; isong.sample[i]; + unsigned int insAddr=(i*12)+((ramSize<=0x200000)?0x200000:0); + unsigned char bitDepth; + int endPos=CLAMP(s->loopEnd,1,0x10000); + int loop=s->isLoopable()?CLAMP(s->loopStart,0,endPos-1):(endPos-1); + switch (s->depth) { + default: + case DIV_SAMPLE_DEPTH_8BIT: + bitDepth=0; + break; + case DIV_SAMPLE_DEPTH_12BIT: + bitDepth=1; + break; + case DIV_SAMPLE_DEPTH_16BIT: + bitDepth=2; + break; + } + pcmMem[insAddr]=(bitDepth<<6)|((sampleOffPCM[i]>>16)&0x3f); + pcmMem[1+insAddr]=(sampleOffPCM[i]>>8)&0xff; + pcmMem[2+insAddr]=(sampleOffPCM[i])&0xff; + pcmMem[3+insAddr]=(loop>>8)&0xff; + pcmMem[4+insAddr]=(loop)&0xff; + pcmMem[5+insAddr]=((~(endPos-1))>>8)&0xff; + pcmMem[6+insAddr]=(~(endPos-1))&0xff; + // TODO: how to fill in rest of instrument table? + pcmMem[7+insAddr]=0; // LFO, VIB + pcmMem[8+insAddr]=(0xf << 4) | (0xf << 0); // AR, D1R + pcmMem[9+insAddr]=0; // DL, D2R + pcmMem[10+insAddr]=(0xf << 4) | (0xf << 0); // RC, RR + pcmMem[11+insAddr]=0; // AM + } + if (ramSize<=0x200000) { + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_RESERVED,"ROM data",0,0,0x200000)); + } + + memCompo.used=pcmMemLen; + } else if (adpcmChan>=0) { // ADPCM + size_t memPos=0; + for (int i=0; isong.sampleLen; i++) { + DivSample* s=parent->song.sample[i]; + if (!s->renderOn[0][sysID]) { + sampleOffB[i]=0; + continue; + } + + int paddedLen=(s->lengthB+255)&(~0xff); + if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) { + memPos=(memPos+0xfffff)&0xf00000; + } + if (memPos>=getSampleMemCapacity(0)) { + logW("out of ADPCM memory for sample %d!",i); + break; + } + if (memPos+paddedLen>=getSampleMemCapacity(0)) { + memcpy(adpcmBMem+memPos,s->dataB,getSampleMemCapacity(0)-memPos); + logW("out of ADPCM memory for sample %d!",i); + } else { + memcpy(adpcmBMem+memPos,s->dataB,paddedLen); + sampleLoaded[i]=true; + } + sampleOffB[i]=memPos; + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_SAMPLE,"Sample",i,memPos,memPos+paddedLen)); + memPos+=paddedLen; + } + adpcmBMemLen=memPos+256; + + memCompo.used=adpcmBMemLen; + } memCompo.capacity=getSampleMemCapacity(0); } @@ -2594,10 +3307,10 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, const DivConfi parent=p; dumpWrites=false; skipRegisterWrites=false; - for (int i=0; i<20; i++) { + for (int i=0; i<44; i++) { isMuted[i]=false; } - for (int i=0; i<20; i++) { + for (int i=0; i<44; i++) { oscBuf[i]=new DivDispatchOscBuffer; } @@ -2605,6 +3318,7 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, const DivConfi fm_ymfm2=NULL; fm_ymfm8950=NULL; fm_ymfm3=NULL; + fm_ymfm4=NULL; if (emuCore==1) { switch (chipType) { @@ -2620,31 +3334,45 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, const DivConfi case 3: case 759: fm_ymfm3=new ymfm::ymf262(iface); break; + case 4: + fm_ymfm4=new ymfm::ymf278b(iface); + break; } } setFlags(flags); if (adpcmChan>=0) { - adpcmBMem=new unsigned char[getSampleMemCapacity(0)]; + adpcmBMem=new unsigned char[262144]; adpcmBMemLen=0; iface.adpcmBMem=adpcmBMem; iface.sampleBank=0; adpcmB=new ymfm::adpcm_b_engine(iface,2); } + if (pcmChanOffs>=0) { + pcmMem=new unsigned char[4194304]; + pcmMemLen=0; + iface.pcmMem=pcmMem; + iface.sampleBank=0; + pcmMemory.memory=pcmMem; + } + reset(); return totalChans; } void DivPlatformOPL::quit() { - for (int i=0; i<20; i++) { + for (int i=0; i<44; i++) { delete oscBuf[i]; } if (adpcmChan>=0) { delete adpcmB; delete[] adpcmBMem; } + if (pcmChanOffs>=0) { + delete[] pcmMem; + } if (fm_ymfm1!=NULL) { delete fm_ymfm1; fm_ymfm1=NULL; @@ -2661,6 +3389,10 @@ void DivPlatformOPL::quit() { delete fm_ymfm3; fm_ymfm3=NULL; } + if (fm_ymfm4!=NULL) { + delete fm_ymfm4; + fm_ymfm4=NULL; + } } DivPlatformOPL::~DivPlatformOPL() { diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index 327389c1f..449cb2948 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -29,24 +29,41 @@ extern "C" { } #include "sound/ymfm/ymfm_adpcm.h" #include "sound/ymfm/ymfm_opl.h" +#include "sound/ymfm/ymfm_pcm.h" +#include "sound/ymf278b/ymf278.h" class DivOPLAInterface: public ymfm::ymfm_interface { public: unsigned char* adpcmBMem; + unsigned char* pcmMem; int sampleBank; uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address); void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data); - DivOPLAInterface(): adpcmBMem(NULL), sampleBank(0) {} + DivOPLAInterface(): adpcmBMem(NULL), pcmMem(NULL), sampleBank(0) {} +}; + +class DivYMF278MemoryInterface: public MemoryInterface { + public: + unsigned char* memory; + DivYMF278MemoryInterface(unsigned size_) : memory(NULL), size(size_) {}; + byte operator[](unsigned address) const override; + unsigned getSize() const override { return size; }; + void write(unsigned address, byte value) override {}; + void clear(byte value) override {}; + private: + unsigned size; }; class DivPlatformOPL: public DivDispatch { protected: struct Channel: public SharedChannel { DivInstrumentFM state; - unsigned char freqH, freqL; + unsigned int freqH, freqL; int sample, fixedFreq; - bool furnacePCM, fourOp, hardReset; - unsigned char pan; + bool furnacePCM, fourOp, hardReset, writeCtrl; + bool levelDirect, damp, pseudoReverb, lfoReset, ch; + int lfo, vib, am, ar, d1r, d2r, dl, rc, rr; + int pan; int macroVolMul; Channel(): SharedChannel(0), @@ -57,14 +74,29 @@ class DivPlatformOPL: public DivDispatch { furnacePCM(false), fourOp(false), hardReset(false), + writeCtrl(false), + 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; } }; - Channel chan[20]; - DivDispatchOscBuffer* oscBuf[20]; - bool isMuted[20]; + Channel chan[44]; + DivDispatchOscBuffer* oscBuf[44]; + bool isMuted[44]; struct QueuedWrite { unsigned short addr; unsigned char val; @@ -72,7 +104,7 @@ class DivPlatformOPL: public DivDispatch { QueuedWrite(): addr(0), val(0), addrOrVal(false) {} QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} }; - FixedQueue writes; + FixedQueue writes; unsigned int dacVal; unsigned int dacVal2; @@ -86,8 +118,12 @@ class DivPlatformOPL: public DivDispatch { unsigned char* adpcmBMem; size_t adpcmBMemLen; + unsigned char* pcmMem; + size_t pcmMemLen; DivOPLAInterface iface; + DivYMF278MemoryInterface pcmMemory; unsigned int sampleOffB[256]; + unsigned int sampleOffPCM[256]; bool sampleLoaded[256]; ymfm::adpcm_b_engine* adpcmB; @@ -97,12 +133,13 @@ class DivPlatformOPL: public DivDispatch { const unsigned short* chanMap; const unsigned char* outChanMap; int chipFreqBase, chipRateBase; - int delay, chipType, oplType, chans, melodicChans, totalChans, adpcmChan, sampleBank, totalOutputs; + int delay, chipType, oplType, chans, melodicChans, totalChans, adpcmChan=-1, pcmChanOffs=-1, sampleBank, totalOutputs, ramSize; + int fmMixL=7, fmMixR=7, pcmMixL=7, pcmMixR=7; unsigned char lastBusy; unsigned char drumState; unsigned char drumVol[5]; - unsigned char regPool[512]; + unsigned char regPool[768]; bool properDrums, properDrumsSys, dam, dvb; @@ -115,15 +152,17 @@ class DivPlatformOPL: public DivDispatch { bool update4OpMask, pretendYMU, downsample, compatPan; - short oldWrites[512]; - short pendingWrites[512]; + short oldWrites[768]; + short pendingWrites[768]; // chips opl3_chip fm; + YMF278 pcm; ymfm::ym3526* fm_ymfm1; ymfm::ym3812* fm_ymfm2; ymfm::y8950* fm_ymfm8950; ymfm::ymf262* fm_ymfm3; + ymfm::ymf278b* fm_ymfm4; fmopl2_t fm_lle2; fmopl3_t fm_lle3; @@ -141,6 +180,7 @@ class DivPlatformOPL: public DivDispatch { void acquire_nukedLLE3(short** buf, size_t len); void acquire_nuked(short** buf, size_t len); void acquire_ymfm3(short** buf, size_t len); + void acquire_ymfm4(short** buf, size_t len); void acquire_ymfm8950(short** buf, size_t len); void acquire_ymfm2(short** buf, size_t len); void acquire_ymfm1(short** buf, size_t len); @@ -182,6 +222,9 @@ class DivPlatformOPL: public DivDispatch { void renderSamples(int chipID); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); + DivPlatformOPL(): + pcmMemory(0x400000), + pcm(pcmMemory) {} ~DivPlatformOPL(); }; #endif diff --git a/src/engine/platform/oplAInterface.cpp b/src/engine/platform/oplAInterface.cpp index 9643b80c0..dcd5c8e85 100644 --- a/src/engine/platform/oplAInterface.cpp +++ b/src/engine/platform/oplAInterface.cpp @@ -28,6 +28,11 @@ uint8_t DivOPLAInterface::ymfm_external_read(ymfm::access_class type, uint32_t a return 0; } return adpcmBMem[address&0x3ffff]; + case ymfm::ACCESS_PCM: + if (pcmMem==NULL) { + return 0; + } + return pcmMem[address&0x3fffff]; default: return 0; } @@ -36,3 +41,10 @@ uint8_t DivOPLAInterface::ymfm_external_read(ymfm::access_class type, uint32_t a void DivOPLAInterface::ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data) { } + +byte DivYMF278MemoryInterface::operator[](unsigned address) const { + if (memory && address + Copyright (C) + + 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. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/src/engine/platform/sound/ymf278b/ymf278.cpp b/src/engine/platform/sound/ymf278b/ymf278.cpp new file mode 100644 index 000000000..9eec6c85b --- /dev/null +++ b/src/engine/platform/sound/ymf278b/ymf278.cpp @@ -0,0 +1,1094 @@ +// Based on ymf278b.c written by R. Belmont and O. Galibert + +// Improved by Valley Bell, 2018 +// Thanks to niekniek and l_oliveira for providing recordings from OPL4 hardware. +// Thanks to superctr and wouterv for discussing changes. +// +// Improvements: +// - added TL interpolation, recordings show that internal TL levels are 0x00..0xff +// - fixed ADSR speeds, attack rate 15 is now instant +// - correct clamping of intermediate Rate Correction values +// - emulation of "loop glitch" (going out-of-bounds by playing a sample faster than it the loop is long) +// - made calculation of sample position cleaner and closer to how the HW works +// - increased output resolution from TL (0.375dB) to envelope (0.09375dB) +// - fixed volume table -6dB steps are done using bit shifts, steps in between are multiplicators +// - made octave -8 freeze the sample +// - verified that TL and envelope levels are applied separately, both go silent at -60dB +// - implemented pseudo-reverb and damping according to manual +// - made pseudo-reverb ignore Rate Correction (real hardware ignores it) +// - reimplemented LFO, speed exactly matches the formulas that were probably used when creating the manual +// - fixed LFO (tremolo) amplitude modulation +// - made LFO vibrato and tremolo accurate to hardware +// +// Known issues: +// - Octave -8 was only tested with fnum 0. Other fnum values might behave differently. + +// This class doesn't model a full YMF278b chip. Instead it only models the +// wave part. The FM part in modeled in YMF262 (it's almost 100% compatible, +// the small differences are handled in YMF262). The status register and +// interaction with the FM registers (e.g. the NEW2 bit) is currently handled +// in the MSXMoonSound class. + +// MODIFIED: +// Add YMW258 support by Grauw +// Add DO1 output support by cam900 + +#include "ymf278.h" +#include +#include + +// envelope output entries +// fixed to match recordings from actual OPL4 -Valley Bell +constexpr int MAX_ATT_INDEX = 0x280; // makes attack phase right and also goes well with "envelope stops at -60dB" +constexpr int MIN_ATT_INDEX = 0; +constexpr int TL_SHIFT = 2; // envelope values are 4x as fine as TL levels + +constexpr unsigned LFO_SHIFT = 18; // LFO period of up to 0x40000 sample +constexpr unsigned LFO_PERIOD = 1 << LFO_SHIFT; + +// Envelope Generator phases +constexpr int EG_ATT = 4; +constexpr int EG_DEC = 3; +constexpr int EG_SUS = 2; +constexpr int EG_REL = 1; +constexpr int EG_OFF = 0; + +// Pan values, units are -3dB, i.e. 8. +constexpr uint8_t pan_left[16] = { + 0, 8, 16, 24, 32, 40, 48, 255, 255, 0, 0, 0, 0, 0, 0, 0 +}; +constexpr uint8_t pan_right[16] = { + 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 48, 40, 32, 24, 16, 8 +}; + +// decay level table (3dB per step) +// 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB) +static constexpr int16_t SC(int dB) { return int16_t(dB / 3 * 0x20); } +constexpr int16_t dl_tab[16] = { + SC( 0), SC( 3), SC( 6), SC( 9), SC(12), SC(15), SC(18), SC(21), + SC(24), SC(27), SC(30), SC(33), SC(36), SC(39), SC(42), SC(93) +}; + +constexpr byte RATE_STEPS = 8; +constexpr byte eg_inc[15 * RATE_STEPS] = { +//cycle:0 1 2 3 4 5 6 7 + 0, 1, 0, 1, 0, 1, 0, 1, // 0 rates 00..12 0 (increment by 0 or 1) + 0, 1, 0, 1, 1, 1, 0, 1, // 1 rates 00..12 1 + 0, 1, 1, 1, 0, 1, 1, 1, // 2 rates 00..12 2 + 0, 1, 1, 1, 1, 1, 1, 1, // 3 rates 00..12 3 + + 1, 1, 1, 1, 1, 1, 1, 1, // 4 rate 13 0 (increment by 1) + 1, 1, 1, 2, 1, 1, 1, 2, // 5 rate 13 1 + 1, 2, 1, 2, 1, 2, 1, 2, // 6 rate 13 2 + 1, 2, 2, 2, 1, 2, 2, 2, // 7 rate 13 3 + + 2, 2, 2, 2, 2, 2, 2, 2, // 8 rate 14 0 (increment by 2) + 2, 2, 2, 4, 2, 2, 2, 4, // 9 rate 14 1 + 2, 4, 2, 4, 2, 4, 2, 4, // 10 rate 14 2 + 2, 4, 4, 4, 2, 4, 4, 4, // 11 rate 14 3 + + 4, 4, 4, 4, 4, 4, 4, 4, // 12 rates 15 0, 15 1, 15 2, 15 3 for decay + 8, 8, 8, 8, 8, 8, 8, 8, // 13 rates 15 0, 15 1, 15 2, 15 3 for attack (zero time) + 0, 0, 0, 0, 0, 0, 0, 0, // 14 infinity rates for attack and decay(s) +}; + +static constexpr byte O(int a) { return a * RATE_STEPS; } +constexpr byte eg_rate_select[64] = { + O(14),O(14),O(14),O(14), // inf rate + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 0),O( 1),O( 2),O( 3), + O( 4),O( 5),O( 6),O( 7), + O( 8),O( 9),O(10),O(11), + O(12),O(12),O(12),O(12), +}; + +// rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 +// shift 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0 +// mask 4095, 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0, 0 +constexpr byte eg_rate_shift[64] = { + 12, 12, 12, 12, + 11, 11, 11, 11, + 10, 10, 10, 10, + 9, 9, 9, 9, + 8, 8, 8, 8, + 7, 7, 7, 7, + 6, 6, 6, 6, + 5, 5, 5, 5, + 4, 4, 4, 4, + 3, 3, 3, 3, + 2, 2, 2, 2, + 1, 1, 1, 1, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, +}; + + +// number of steps the LFO counter advances per sample +// LFO frequency (Hz) -> LFO counter steps per sample +static constexpr int L(double a) { return int((LFO_PERIOD * a) / 44100.0 + 0.5); } +constexpr int lfo_period[8] = { + L(0.168), // step: 1, period: 262144 samples + L(2.019), // step: 12, period: 21845 samples + L(3.196), // step: 19, period: 13797 samples + L(4.206), // step: 25, period: 10486 samples + L(5.215), // step: 31, period: 8456 samples + L(5.888), // step: 35, period: 7490 samples + L(6.224), // step: 37, period: 7085 samples + L(7.066), // step: 42, period: 6242 samples +}; + + +// formula used by Yamaha docs: +// vib_depth_cents(x) = (log2(0x400 + x) - 10) * 1200 +constexpr int16_t vib_depth[8] = { + 0, // 0.000 cents + 2, // 3.378 cents + 3, // 5.065 cents + 4, // 6.750 cents + 6, // 10.114 cents + 12, // 20.170 cents + 24, // 40.106 cents + 48, // 79.307 cents +}; + + +// formula used by Yamaha docs: +// am_depth_db(x) = (x-1) / 0x40 * 6.0 +// They use (x-1), because the depth is multiplied with the AM counter, which has a range of 0..0x7F. +// Thus the maximum attenuation with x=0x80 is (0x7F * 0x80) >> 7 = 0x7F. +// reversed formula: +// am_depth(dB) = round(dB / 6.0 * 0x40) + 1 +constexpr uint8_t am_depth[8] = { + 0x00, // 0.000 dB + 0x14, // 1.781 dB + 0x20, // 2.906 dB + 0x28, // 3.656 dB + 0x30, // 4.406 dB + 0x40, // 5.906 dB + 0x50, // 7.406 dB + 0x80, // 11.910 dB +}; + +// divisions of 16 +constexpr int mix_level[8] = { + 16, // 0dB + 12, // -3dB (approx) + 8, // -6dB + 6, // -9dB (approx) + 4, // -12dB + 3, // -15dB (approx) + 2, // -18dB + 0, // -inf dB +}; + +YMF278::Slot::Slot() +{ + reset(); +} + +// Sign extend a 4-bit value to int (32-bit) +// require: x in range [0..15] +static constexpr int sign_extend_4(int x) +{ + return (x ^ 8) - 8; +} + +// Params: oct in [-8 .. +7] +// fn in [ 0 .. 1023] +// We want to interpret oct as a signed 4-bit number and calculate +// ((fn | 1024) + vib) << (5 + sign_extend_4(oct)) +// Though in this formula the shift can go over a negative distance (in that +// case we should shift in the other direction). +static constexpr unsigned calcStep(int8_t oct, uint16_t fn, int16_t vib = 0) +{ + if (oct == -8) return 0; + unsigned t = (fn + 1024 + vib) << (8 + oct); // use '+' iso '|' (generates slightly better code) + return t >> 3; // was shifted 3 positions too far +} + +void YMF278::Slot::reset() +{ + wave = FN = OCT = TLdest = TL = pan = vib = AM = 0; + DL = AR = D1R = D2R = RC = RR = 0; + PRVB = keyon = DAMP = false; + stepptr = 0; + step = calcStep(OCT, FN); + bits = startaddr = loopaddr = endaddr = 0; + env_vol = MAX_ATT_INDEX; + + lfo_active = false; + lfo_cnt = 0; + lfo = 0; + + state = EG_OFF; + + // not strictly needed, but avoid UMR on savestate + pos = 0; +} + +int YMF278::Slot::compute_rate(int val) const +{ + if (val == 0) { + return 0; + } else if (val == 15) { + return 63; + } + int res = val * 4; + if (RC != 15) { + // clamping verified with HW tests -Valley Bell + res += 2 * YMF_clamp(OCT + RC, 0, 15); + res += (FN & 0x200) ? 1 : 0; + } + return YMF_clamp(res, 0, 63); +} + +int YMF278::Slot::compute_decay_rate(int val) const +{ + if (DAMP) { + // damping + // The manual lists these values for time and attenuation: (44100 samples/second) + // -12dB at 5.8ms, sample 256 + // -48dB at 8.0ms, sample 352 + // -72dB at 9.4ms, sample 416 + // -96dB at 10.9ms, sample 480 + // This results in these durations and rate values for the respective phases: + // 0dB .. -12dB: 256 samples (5.80ms) -> 128 samples per -6dB = rate 48 + // -12dB .. -48dB: 96 samples (2.18ms) -> 16 samples per -6dB = rate 63 + // -48dB .. -72dB: 64 samples (1.45ms) -> 16 samples per -6dB = rate 63 + // -72dB .. -96dB: 64 samples (1.45ms) -> 16 samples per -6dB = rate 63 + // Damping was verified to ignore rate correction. + if (env_vol < dl_tab[4]) { + return 48; // 0dB .. -12dB + } else { + return 63; // -12dB .. -96dB + } + } + if (PRVB) { + // pseudo reverb + // activated when reaching -18dB, overrides D1R/D2R/RR with reverb rate 5 + // + // The manual is actually a bit unclear and just says "RATE=5", + // referring to the D1R/D2R/RR register value. However, later + // pages use "RATE" to refer to the "internal" rate, which is + // (register * 4) + rate correction. HW recordings prove that + // Rate Correction is ignored, so pseudo reverb just sets the + // "internal" rate to a value of 4*5 = 20. + if (env_vol >= dl_tab[6]) { + return 20; + } + } + return compute_rate(val); +} + +int16_t YMF278::Slot::compute_vib() const +{ + // verified via hardware recording: + // With LFO speed 0 (period 262144 samples), each vibrato step takes + // 4096 samples. + // -> 64 steps total + // Also, with vibrato depth 7 (80 cents) and an F-Num of 0x400, the + // final F-Nums are: 0x400 .. 0x43C, 0x43C .. 0x400, 0x400 .. 0x3C4, + // 0x3C4 .. 0x400 + int16_t lfo_fm = lfo_cnt / (LFO_PERIOD / 0x40); + // results in +0x00..+0x0F, +0x0F..+0x00, -0x00..-0x0F, -0x0F..-0x00 + if (lfo_fm & 0x10) lfo_fm ^= 0x1F; + if (lfo_fm & 0x20) lfo_fm = -(lfo_fm & 0x0F); + + return (lfo_fm * vib_depth[vib]) / 12; +} + +uint16_t YMF278::Slot::compute_am() const +{ + // verified via hardware recording: + // With LFO speed 0 (period 262144 samples), each tremolo step takes + // 1024 samples. + // -> 256 steps total + uint16_t lfo_am = lfo_cnt / (LFO_PERIOD / 0x100); + // results in 0x00..0x7F, 0x7F..0x00 + if (lfo_am >= 0x80) lfo_am ^= 0xFF; + + return (lfo_am * am_depth[AM]) >> 7; +} + + +void YMF278Base::advance() +{ + eg_cnt++; + + // modulo counters for volume interpolation + int tl_int_cnt = eg_cnt % 9; // 0 .. 8 + int tl_int_step = (eg_cnt / 9) % 3; // 0 .. 2 + + for (auto& op : slots) { + // volume interpolation + if (tl_int_cnt == 0) { + if (tl_int_step == 0) { + // decrease volume by one step every 27 samples + if (op.TL < op.TLdest) ++op.TL; + } else { + // increase volume by one step every 13.5 samples + if (op.TL > op.TLdest) --op.TL; + } + } + + if (op.lfo_active) { + op.lfo_cnt = (op.lfo_cnt + lfo_period[op.lfo]) & (LFO_PERIOD - 1); + } + + // Envelope Generator + switch (op.state) { + case EG_ATT: { // attack phase + uint8_t rate = op.compute_rate(op.AR); + // Verified by HW recording (and matches Nemesis' tests of the YM2612): + // AR = 0xF during KeyOn results in instant switch to EG_DEC. (see keyOnHelper) + // Setting AR = 0xF while the attack phase is in progress freezes the envelope. + if (rate >= 63) { + break; + } + uint8_t shift = eg_rate_shift[rate]; + if (!(eg_cnt & ((1 << shift) - 1))) { + uint8_t select = eg_rate_select[rate]; + // >>4 makes the attack phase's shape match the actual chip -Valley Bell + op.env_vol += (~op.env_vol * eg_inc[select + ((eg_cnt >> shift) & 7)]) >> 4; + if (op.env_vol <= MIN_ATT_INDEX) { + op.env_vol = MIN_ATT_INDEX; + // TODO does the real HW skip EG_DEC completely, + // or is it active for 1 sample? + op.state = op.DL ? EG_DEC : EG_SUS; + } + } + break; + } + case EG_DEC: { // decay phase + uint8_t rate = op.compute_decay_rate(op.D1R); + uint8_t shift = eg_rate_shift[rate]; + if (!(eg_cnt & ((1 << shift) - 1))) { + uint8_t select = eg_rate_select[rate]; + op.env_vol += eg_inc[select + ((eg_cnt >> shift) & 7)]; + if (op.env_vol >= op.DL) { + op.state = (op.env_vol < MAX_ATT_INDEX) ? EG_SUS : EG_OFF; + } + } + break; + } + case EG_SUS: { // sustain phase + uint8_t rate = op.compute_decay_rate(op.D2R); + uint8_t shift = eg_rate_shift[rate]; + if (!(eg_cnt & ((1 << shift) - 1))) { + uint8_t select = eg_rate_select[rate]; + op.env_vol += eg_inc[select + ((eg_cnt >> shift) & 7)]; + if (op.env_vol >= MAX_ATT_INDEX) { + op.env_vol = MAX_ATT_INDEX; + op.state = EG_OFF; + } + } + break; + } + case EG_REL: { // release phase + uint8_t rate = op.compute_decay_rate(op.RR); + uint8_t shift = eg_rate_shift[rate]; + if (!(eg_cnt & ((1 << shift) - 1))) { + uint8_t select = eg_rate_select[rate]; + op.env_vol += eg_inc[select + ((eg_cnt >> shift) & 7)]; + if (op.env_vol >= MAX_ATT_INDEX) { + op.env_vol = MAX_ATT_INDEX; + op.state = EG_OFF; + } + } + break; + } + case EG_OFF: + // nothing + break; + + default: + UNREACHABLE; + } + } +} + +int16_t YMF278Base::getSample(Slot& slot, uint16_t pos) const +{ + // TODO How does this behave when R#2 bit 0 = 1? + // As-if read returns 0xff? (Like for CPU memory reads.) Or is + // sound generation blocked at some higher level? + switch (slot.bits) { + case 0: { + // 8 bit + return memory[slot.startaddr + pos] << 8; + } + case 1: { + // 12 bit + unsigned addr = slot.startaddr + ((pos / 2) * 3); + if (pos & 1) { + return (memory[addr + 2] << 8) | + (memory[addr + 1] & 0xF0); + } else { + return (memory[addr + 0] << 8) | + ((memory[addr + 1] << 4) & 0xF0); + } + } + case 2: { + // 16 bit + unsigned addr = slot.startaddr + (pos * 2); + return (memory[addr + 0] << 8) | + (memory[addr + 1]); + } + default: + // TODO unspecified + return 0; + } +} + +uint16_t YMF278Base::nextPos(Slot& slot, uint16_t pos, uint16_t increment) +{ + // If there is a 4-sample loop and you advance 12 samples per step, + // it may exceed the end offset. + // This is abused by the "Lizard Star" song to generate noise at 0:52. -Valley Bell + pos += increment; + if ((uint32_t(pos) + slot.endaddr) >= 0x10000) // check position >= (negated) end address + pos += slot.endaddr + slot.loopaddr; // This is how the actual chip does it. + return pos; +} + +bool YMF278Base::anyActive() +{ + return std::any_of(std::begin(slots), std::end(slots), [](auto& op) { return op.state != EG_OFF; }); +} + +// In: 'envVol', 0=max volume, others -> -3/32 = -0.09375 dB/step +// Out: 'x' attenuated by the corresponding factor. +// Note: microbenchmarks have shown that re-doing this calculation is about the +// same speed as using a 4kB lookup table. +static constexpr int vol_factor(int x, unsigned envVol) +{ + if (envVol >= MAX_ATT_INDEX) return 0; // hardware clips to silence below -60dB + int vol_mul = 0x80 - (envVol & 0x3F); // 0x40 values per 6dB + int vol_shift = 7 + (envVol >> 6); + return (x * ((0x8000 * vol_mul) >> vol_shift)) >> 15; +} + +void YMF278Base::generate(short& fleft, short& fright, short& rleft, short& rright, short* channelBufs) +{ + int sampleFLeft = 0; + int sampleFRight = 0; + int sampleRLeft = 0; + int sampleRRight = 0; + for (size_t i = 0, count = slots.size(); i < count; i++) { + Slot& sl = slots[i]; + if (sl.state == EG_OFF) { + //sampleLeft += 0; + //sampleRight += 0; + if (channelBufs != nullptr) { + channelBufs[i] = 0; + } + continue; + } + + int16_t sample = (getSample(sl, sl.pos) * (0x10000 - sl.stepptr) + + getSample(sl, nextPos(sl, sl.pos, 1)) * sl.stepptr) >> 16; + // TL levels are 00..FF internally (TL register value 7F is mapped to TL level FF) + // Envelope levels have 4x the resolution (000..3FF) + // Volume levels are approximate logarithmic. -6dB result in half volume. Steps in between use linear interpolation. + // A volume of -60dB or lower results in silence. (value 0x280..0x3FF). + // Recordings from actual hardware indicate that TL level and envelope level are applied separarely. + // Each of them is clipped to silence below -60dB, but TL+envelope might result in a lower volume. -Valley Bell + uint16_t envVol = std::min(sl.env_vol + ((sl.lfo_active && sl.AM) ? sl.compute_am() : 0), + MAX_ATT_INDEX); + int smplOut = vol_factor(vol_factor(sample, envVol), sl.TL << TL_SHIFT); + + // Panning is also done separately. (low-volume TL + low-volume panning goes below -60dB) + // I'll be taking wild guess and assume that -3dB is approximated with 75%. (same as with TL and envelope levels) + // The same applies to the PCM mix level. + int32_t volLeft = pan_left [sl.pan]; // note: register 0xF9 is handled externally + int32_t volRight = pan_right[sl.pan]; + // 0 -> 0x20, 8 -> 0x18, 16 -> 0x10, 24 -> 0x0C, etc. (not using vol_factor here saves array boundary checks) + volLeft = (0x20 - (volLeft & 0x0f)) >> (volLeft >> 4); + volRight = (0x20 - (volRight & 0x0f)) >> (volRight >> 4); + + if (sl.ch) + { + sampleRLeft += (smplOut * volLeft ) >> 5; + sampleRRight += (smplOut * volRight) >> 5; + } + else + { + sampleFLeft += (smplOut * volLeft ) >> 5; + sampleFRight += (smplOut * volRight) >> 5; + } + + unsigned step = (sl.lfo_active && sl.vib) + ? calcStep(sl.OCT, sl.FN, sl.compute_vib()) + : sl.step; + sl.stepptr += step; + + if (sl.stepptr >= 0x10000) { + sl.pos = nextPos(sl, sl.pos, sl.stepptr >> 16); + sl.stepptr &= 0xffff; + } + + if (channelBufs != nullptr) { + channelBufs[i] = sl.pan != 8 ? smplOut : 0; + } + } + advance(); + + fleft = sampleFLeft >> 4; + fright = sampleFRight >> 4; + rleft = sampleRLeft >> 4; + rright = sampleRRight >> 4; +} + +void YMF278Base::keyOnHelper(Slot& slot) +{ + // Unlike FM, the envelope level is reset. (And it makes sense, because you restart the sample.) + slot.env_vol = MAX_ATT_INDEX; + if (slot.compute_rate(slot.AR) < 63) { + slot.state = EG_ATT; + } else { + // Nuke.YKT verified that the FM part does it exactly this way, + // and the OPL4 manual says it's instant as well. + slot.env_vol = MIN_ATT_INDEX; + // see comment in 'case EG_ATT' in YMF278::advance() + slot.state = slot.DL ? EG_DEC : EG_SUS; + } + slot.stepptr = 0; + slot.pos = 0; +} + +YMF278Base::YMF278Base(MemoryInterface& memory, int channelCount, int clockDivider, double clockFrequency) + : memory(memory) + , slots(channelCount) + , channelCount(channelCount) + , clockDivider(clockDivider) + , clockFrequency(clockFrequency) +{ + reset(); +} + +YMF278Base::~YMF278Base() +{ +} + +int YMF278Base::getChannelCount() +{ + return channelCount; +} + +int YMF278Base::getClockDivider() +{ + return clockDivider; +} + +double YMF278Base::getClockFrequency() +{ + return clockFrequency; +} + +void YMF278Base::setClockFrequency(double clockFrequency_) +{ + clockFrequency = clockFrequency_; +} + +double YMF278Base::getSampleRate() +{ + return clockFrequency / (channelCount * clockDivider); +} + +void YMF278Base::reset() +{ + eg_cnt = 0; + for (auto& op : slots) { + op.reset(); + } + memory.setMemoryType(false); +} + +YMF278::YMF278(MemoryInterface& memory) + : YMF278Base(memory, 24, 32, 33868800) + , fmMixL(0), fmMixR(0), pcmMixL(0), pcmMixR(0) +{ + memAdr = 0; // avoid UMR + std::fill(std::begin(regs), std::end(regs), 0); +} + +void YMF278::reset() +{ + YMF278Base::reset(); + + regs[2] = 0; // avoid UMR + for (int i = 0xf7; i >= 0; --i) { // reverse order to avoid UMR + writeReg(i, 0); + } + writeReg(0xf8, 0x1b); + writeReg(0xf9, 0x00); + memAdr = 0; +} + +void YMF278::writeReg(byte reg, byte data) +{ + // Handle slot registers specifically + if (reg >= 0x08 && reg <= 0xF7) { + int sNum = (reg - 8) % 24; + auto& slot = slots[sNum]; + switch ((reg - 8) / 24) { + case 0: { + slot.wave = (slot.wave & 0x100) | data; + int waveTblHdr = (regs[2] >> 2) & 0x7; + int base = (slot.wave < 384 || !waveTblHdr) ? + (slot.wave * 12) : + (waveTblHdr * 0x80000 + ((slot.wave - 384) * 12)); + byte buf[12]; + for (unsigned i = 0; i < 12; ++i) { + // TODO What if R#2 bit 0 = 1? + // See also getSample() + buf[i] = memory[base + i]; + } + slot.bits = (buf[0] & 0xC0) >> 6; + slot.startaddr = buf[2] | (buf[1] << 8) | ((buf[0] & 0x3F) << 16); + slot.loopaddr = buf[4] | (buf[3] << 8); + slot.endaddr = buf[6] | (buf[5] << 8); + for (unsigned i = 7; i < 12; ++i) { + // Verified on real YMF278: + // After tone loading, if you read these + // registers, their value actually has changed. + writeReg(8 + sNum + (i - 2) * 24, buf[i]); + } + if (slot.keyon) { + keyOnHelper(slot); + } else { + slot.stepptr = 0; + slot.pos = 0; + } + break; + } + case 1: { + slot.wave = (slot.wave & 0xFF) | ((data & 0x1) << 8); + slot.FN = (slot.FN & 0x380) | (data >> 1); + slot.step = calcStep(slot.OCT, slot.FN); + break; + } + case 2: { + slot.FN = (slot.FN & 0x07F) | ((data & 0x07) << 7); + slot.PRVB = (data & 0x08) != 0; + slot.OCT = sign_extend_4((data & 0xF0) >> 4); + slot.step = calcStep(slot.OCT, slot.FN); + break; + } + case 3: { + uint8_t t = data >> 1; + slot.TLdest = (t != 0x7f) ? t : 0xff; // verified on HW via volume interpolation + if (data & 1) { + // directly change volume + slot.TL = slot.TLdest; + } else { + // interpolate volume + } + break; + } + case 4: + slot.ch = data & 0x10; + slot.pan = data & 0x0F; + + if (data & 0x20) { + // LFO reset + slot.lfo_active = false; + slot.lfo_cnt = 0; + } else { + // LFO activate + slot.lfo_active = true; + } + + slot.DAMP = (data & 0x40) != 0; + + if (data & 0x80) { + if (!slot.keyon) { + slot.keyon = true; + keyOnHelper(slot); + } + } else { + if (slot.keyon) { + slot.keyon = false; + slot.state = EG_REL; + } + } + break; + case 5: + slot.lfo = (data >> 3) & 0x7; + slot.vib = data & 0x7; + break; + case 6: + slot.AR = data >> 4; + slot.D1R = data & 0xF; + break; + case 7: + slot.DL = dl_tab[data >> 4]; + slot.D2R = data & 0xF; + break; + case 8: + slot.RC = data >> 4; + slot.RR = data & 0xF; + break; + case 9: + slot.AM = data & 0x7; + break; + } + } else { + // All non-slot registers + switch (reg) { + case 0x00: // TEST + case 0x01: + break; + + case 0x02: + // wave-table-header / memory-type / memory-access-mode + // Simply store in regs[2] + memory.setMemoryType(regs[2] & 2); + break; + + case 0x03: + // Verified on real YMF278: + // * Don't update the 'memAdr' variable on writes to + // reg 3 and 4. Only store the value in the 'regs' + // array for later use. + // * The upper 2 bits are not used to address the + // external memories (so from a HW pov they don't + // matter). But if you read back this register, the + // upper 2 bits always read as '0' (even if you wrote + // '1'). So we mask the bits here already. + data &= 0x3F; + break; + + case 0x04: + // See reg 3. + break; + + case 0x05: + // Verified on real YMF278: (see above) + // Only writes to reg 5 change the (full) 'memAdr'. + memAdr = (regs[3] << 16) | (regs[4] << 8) | data; + break; + + case 0x06: // memory data + if (regs[2] & 1) { + memory.write(memAdr, data); + ++memAdr; // no need to mask (again) here + } else { + // Verified on real YMF278: + // - writes are ignored + // - memAdr is NOT increased + } + break; + + case 0xf8: + fmMixL = mix_level[data & 0x7]; + fmMixR = mix_level[data >> 3 & 0x7]; + break; + case 0xf9: + pcmMixL = mix_level[data & 0x7]; + pcmMixR = mix_level[data >> 3 & 0x7]; + break; + } + } + + regs[reg] = data; +} + +byte YMF278::readReg(byte reg) +{ + // no need to call updateStream(time) + byte result = peekReg(reg); + if (reg == 6) { + // Memory Data Register + if (regs[2] & 1) { + // Verified on real YMF278: + // memAdr is only increased when 'regs[2] & 1' + ++memAdr; // no need to mask (again) here + } + } + return result; +} + +byte YMF278::peekReg(byte reg) const +{ + switch (reg) { + case 2: // 3 upper bits are device ID + return (regs[2] & 0x1F) | 0x20; + + case 6: // Memory Data Register + if (regs[2] & 1) { + return memory[memAdr]; + } else { + // Verified on real YMF278 + return 0xff; + } + + default: + return regs[reg]; + } +} + +YMW258::YMW258(MemoryInterface& memory) + : YMF278Base(memory, 28, 8, 9878400) +{ +} + +void YMW258::writeReg(byte channel, byte reg, byte data) +{ + if ((channel & 0x7) == 0x7 || channel >= 0x20 || reg >= 0x8) + return; + int sNum = (channel >> 3) * 7 + (channel & 0x7); + auto& slot = slots[sNum]; + + switch (reg) { + case 0: { + slot.pan = data >> 4; + break; + } + case 1: { + slot.wave = (slot.wave & 0x100) | data; + int base = slot.wave * 12; + byte buf[12]; + for (unsigned i = 0; i < 12; ++i) { + buf[i] = memory[base + i]; + } + slot.bits = (buf[0] >> 6) == 0x3 ? 1 : 0; // 00 / 10: 8 bit, 11: 12 bit, 01: unknown + slot.startaddr = buf[2] | (buf[1] << 8) | ((buf[0] & 0x1F) << 16); + slot.loopaddr = buf[4] | (buf[3] << 8); + slot.endaddr = buf[6] | (buf[5] << 8); + slot.lfo = (buf[7] >> 3) & 0x7; + slot.vib = buf[7] & 0x7; + slot.AR = buf[8] >> 4; + slot.D1R = buf[8] & 0xF; + slot.DL = dl_tab[buf[9] >> 4]; + slot.D2R = buf[9] & 0xF; + slot.RC = buf[10] >> 4; + slot.RR = buf[10] & 0xF; + slot.AM = buf[11] & 0x7; + if (slot.keyon) { + keyOnHelper(slot); + } else { + slot.stepptr = 0; + slot.pos = 0; + } + break; + } + case 2: { + slot.wave = (slot.wave & 0xFF) | ((data & 0x1) << 8); + slot.FN = (slot.FN & 0x3C0) | (data >> 2); + slot.step = calcStep(slot.OCT, slot.FN); + break; + } + case 3: { + slot.FN = (slot.FN & 0x03F) | ((data & 0x0F) << 6); + slot.OCT = sign_extend_4((data & 0xF0) >> 4); + slot.step = calcStep(slot.OCT, slot.FN); + break; + } + case 4: { + slot.lfo_active = true; + if (data & 0x80) { + if (!slot.keyon) { + slot.keyon = true; + keyOnHelper(slot); + } + } else { + if (slot.keyon) { + slot.keyon = false; + slot.state = EG_REL; + } + } + break; + } + case 5: { + uint8_t t = data >> 1; + slot.TLdest = (t != 0x7f) ? t : 0xff; // verified on YMF278 via volume interpolation + if (data & 1) { + // directly change volume + slot.TL = slot.TLdest; + } else { + // interpolate volume + } + break; + } + case 6: { + slot.lfo = (data >> 3) & 0x7; + slot.vib = data & 0x7; + break; + } + case 7: { + slot.AM = data & 0x7; + break; + } + } +} + +MemoryMoonSound::MemoryMoonSound(MemoryInterface& rom, MemoryInterface& ram) + : rom(rom) + , ram(ram) + , memoryType(false) +{ + if (rom.getSize() != 0x200000) { // 2MB + assert(false); + } + assert((ram.getSize() & (1024 - 1)) == 0); + int ramSize_ = ram.getSize() / 1024; + if ((ramSize_ != 0) && // - - + (ramSize_ != 128) && // 128kB - + (ramSize_ != 256) && // 128kB 128kB + (ramSize_ != 512) && // 512kB - + (ramSize_ != 640) && // 512kB 128kB + (ramSize_ != 1024) && // 512kB 512kB + (ramSize_ != 2048)) { // 512kB 512kB 512kB 512kB + assert(false); + } +} + +// This routine translates an address from the (upper) MoonSound address space +// to an address inside the (linearized) SRAM address space. +// +// The following info is based on measurements on a real MoonSound (v2.0) +// PCB. This PCB can have several possible SRAM configurations: +// 128kB: +// 1 SRAM chip of 128kB, chip enable (/CE) of this SRAM chip is connected to +// the 1Y0 output of a 74LS139 (2-to-4 decoder). The enable input of the +// 74LS139 is connected to YMF278 pin /MCS6 and the 74LS139 1B:1A inputs are +// connected to YMF278 pins MA18:MA17. So the SRAM is selected when /MC6 is +// active and MA18:MA17 == 0:0. +// 256kB: +// 2 SRAM chips of 128kB. First one connected as above. Second one has /CE +// connected to 74LS139 pin 1Y1. So SRAM2 is selected when /MSC6 is active +// and MA18:MA17 == 0:1. +// 512kB: +// 1 SRAM chip of 512kB, /CE connected to /MCS6 +// 640kB: +// 1 SRAM chip of 512kB, /CE connected to /MCS6 +// 1 SRAM chip of 128kB, /CE connected to /MCS7. +// (This means SRAM2 is potentially mirrored over a 512kB region) +// 1024kB: +// 1 SRAM chip of 512kB, /CE connected to /MCS6 +// 1 SRAM chip of 512kB, /CE connected to /MCS7 +// 2048kB: +// 1 SRAM chip of 512kB, /CE connected to /MCS6 +// 1 SRAM chip of 512kB, /CE connected to /MCS7 +// 1 SRAM chip of 512kB, /CE connected to /MCS8 +// 1 SRAM chip of 512kB, /CE connected to /MCS9 +// This configuration is not so easy to create on the v2.0 PCB. So it's +// very rare. +// +// So the /MCS6 and /MCS7 (and /MCS8 and /MCS9 in case of 2048kB) signals are +// used to select the different SRAM chips. The meaning of these signals +// depends on the 'memory access mode'. This mode can be changed at run-time +// via bit 1 in register 2. The following table indicates for which regions +// these signals are active (normally MoonSound should be used with mode=0): +// mode=0 mode=1 +// /MCS6 0x200000-0x27FFFF 0x380000-0x39FFFF +// /MCS7 0x280000-0x2FFFFF 0x3A0000-0x3BFFFF +// /MCS8 0x300000-0x37FFFF 0x3C0000-0x3DFFFF +// /MCS9 0x380000-0x3FFFFF 0x3E0000-0x3FFFFF +// +// (For completeness) MoonSound also has 2MB ROM (YRW801), /CE of this ROM is +// connected to YMF278 /MCS0. In both mode=0 and mode=1 this signal is active +// for the region 0x000000-0x1FFFFF. (But this routine does not handle ROM). +unsigned MemoryMoonSound::getRamAddress(unsigned addr) const +{ + addr -= 0x200000; // RAM starts at 0x200000 + if (memoryType) { + // Normally MoonSound is used in 'memory access mode = 0'. But + // in the rare case that mode=1 we adjust the address. + if ((0x180000 <= addr) && (addr <= 0x1FFFFF)) { + addr -= 0x180000; + switch (addr & 0x060000) { + case 0x000000: // [0x380000-0x39FFFF] + // 1st 128kB of SRAM1 + break; + case 0x020000: // [0x3A0000-0x3BFFFF] + if (ram.getSize() == 256 * 1024) { + // 2nd 128kB SRAM chip + } else { + // 2nd block of 128kB in SRAM2 + // In case of 512+128, we use mirroring + addr += 0x080000; + } + break; + case 0x040000: // [0x3C0000-0x3DFFFF] + // 3rd 128kB block in SRAM3 + addr += 0x100000; + break; + case 0x060000: // [0x3EFFFF-0x3FFFFF] + // 4th 128kB block in SRAM4 + addr += 0x180000; + break; + } + } else { + addr = unsigned(-1); // unmapped + } + } + if (ram.getSize() == 640 * 1024) { + // Verified on real MoonSound cartridge (v2.0): In case of + // 640kB (1x512kB + 1x128kB), the 128kB SRAM chip is 4 times + // visible. None of the other SRAM configurations show similar + // mirroring (because the others are powers of two). + if (addr > 0x080000) { + addr &= ~0x060000; + } + } + return addr; +} + +byte MemoryMoonSound::operator[](unsigned address) const +{ + // Verified on real YMF278: address space wraps at 4MB. + address &= 0x3FFFFF; + if (address < 0x200000) { + // ROM connected to /MCS0 + return rom[address]; + } else { + unsigned ramAddr = getRamAddress(address); + if (ramAddr < ram.getSize()) { + return ram[ramAddr]; + } else { + // unmapped region + return 255; // TODO check + } + } +} + +unsigned MemoryMoonSound::getSize() const { + return 0x400000; +} + +void MemoryMoonSound::write(unsigned address, byte value) +{ + address &= 0x3FFFFF; + if (address < 0x200000) { + // can't write to ROM + } else { + unsigned ramAddr = getRamAddress(address); + if (ramAddr < ram.getSize()) { + ram.write(ramAddr, value); + } else { + // can't write to unmapped memory + } + } +} + +void MemoryMoonSound::clear(byte value) { + ram.clear(value); +} + +void MemoryMoonSound::setMemoryType(bool memoryType_) { + memoryType = memoryType_; +} diff --git a/src/engine/platform/sound/ymf278b/ymf278.h b/src/engine/platform/sound/ymf278b/ymf278.h new file mode 100644 index 000000000..b0622adb2 --- /dev/null +++ b/src/engine/platform/sound/ymf278b/ymf278.h @@ -0,0 +1,155 @@ +#ifndef YMF278_HH +#define YMF278_HH + +#include +#include +#include + +#define UNREACHABLE while (1) assert(false) + +using byte = uint8_t; + +template +const T& YMF_clamp(const T& value, const T& min, const T& max) { + return std::min(std::max(value, min), max); +} + +class MemoryInterface { +public: + virtual byte operator[](unsigned address) const = 0; + virtual unsigned getSize() const = 0; + virtual void write(unsigned address, byte value) = 0; + virtual void clear(byte value) = 0; + virtual void setMemoryType(bool memoryType) {}; +}; + +class YMF278Base +{ +public: + YMF278Base(MemoryInterface& memory, int channelCount, int clockDivider, double clockFrequency); + ~YMF278Base(); + int getChannelCount(); + int getClockDivider(); + double getClockFrequency(); + void setClockFrequency(double clockFrequency); + double getSampleRate(); + virtual void reset(); + + void generate(short& fleft, short& fright, short& rleft, short& rright, short* channelBufs = nullptr); + + class Slot final { + public: + Slot(); + void reset(); + int compute_rate(int val) const; + int compute_decay_rate(int val) const; + unsigned decay_rate(int num, int sample_rate); + void envelope_next(int sample_rate); + int16_t compute_vib() const; + uint16_t compute_am() const; + + template + void serialize(Archive& ar, unsigned version); + + uint32_t startaddr; + uint16_t loopaddr; + uint16_t endaddr; // Note: stored in 2s complement (0x0000 = 0, 0x0001 = -65536, 0xffff = -1) + uint32_t step; // fixed-point frequency step + // invariant: step == calcStep(OCT, FN) + uint32_t stepptr; // fixed-point pointer into the sample + uint16_t pos; + + int16_t env_vol; + + uint32_t lfo_cnt; + + int16_t DL; + uint16_t wave; // wavetable number + uint16_t FN; // f-number TODO store 'FN | 1024'? + int8_t OCT; // octave [-8..+7] + bool PRVB; // pseudo-reverb + uint8_t TLdest; // destination total level + uint8_t TL; // total level (goes towards TLdest) + uint8_t pan; // panpot 0..15 + bool ch; // channel select + bool keyon; // slot keyed on + bool DAMP; + uint8_t lfo; // LFO speed 0..7 + uint8_t vib; // vibrato 0..7 + uint8_t AM; // AM level 0..7 + uint8_t AR; // 0..15 + uint8_t D1R; // 0..15 + uint8_t D2R; // 0..15 + uint8_t RC; // rate correction 0..15 + uint8_t RR; // 0..15 + + uint8_t bits; // width of the samples + + uint8_t state; // envelope generator state + bool lfo_active; + }; + +protected: + void keyOnHelper(Slot& slot); + + MemoryInterface& memory; + std::vector slots; + +private: + int16_t getSample(Slot& slot, uint16_t pos) const; + static uint16_t nextPos(Slot& slot, uint16_t pos, uint16_t increment); + void advance(); + bool anyActive(); + + /** Global envelope generator counter. */ + unsigned eg_cnt; + + unsigned channelCount, clockDivider; + double clockFrequency; +}; + +class YMF278 final : public YMF278Base { +public: + YMF278(MemoryInterface& memory); + void reset() override; + void writeReg(byte reg, byte data); + byte readReg(byte reg); + byte peekReg(byte reg) const; + + void generateMix(short fmL, short fmR, short& bufFL, short& bufFR, short& bufRL, short& bufRR, short* channelBufs = nullptr) { + generate(bufFL, bufFR, bufRL, bufRR, channelBufs); + bufFL = std::min(std::max((pcmMixL * bufFL + fmMixL * fmL) >> 4, -0x8000), 0x7fff); + bufFR = std::min(std::max((pcmMixR * bufFR + fmMixR * fmR) >> 4, -0x8000), 0x7fff);; + } + +private: + int fmMixL, fmMixR, pcmMixL, pcmMixR; + int memAdr; + byte regs[256]; +}; + +class YMW258 final : public YMF278Base { +public: + YMW258(MemoryInterface& memory); + void writeReg(byte channel, byte reg, byte data); +}; + +class MemoryMoonSound : MemoryInterface { +public: + MemoryMoonSound(MemoryInterface& rom, MemoryInterface& ram); + byte operator[](unsigned address) const override; + unsigned getSize() const override; + void write(unsigned address, byte value) override; + void clear(byte value) override; + void setMemoryType(bool memoryType) override; + +private: + unsigned getRamAddress(unsigned addr) const; + + MemoryInterface& rom; + MemoryInterface& ram; + + bool memoryType; +}; + +#endif diff --git a/src/engine/platform/sound/ymfm/ymfm_fm.h b/src/engine/platform/sound/ymfm/ymfm_fm.h index f77e89434..0c4342f0b 100644 --- a/src/engine/platform/sound/ymfm/ymfm_fm.h +++ b/src/engine/platform/sound/ymfm/ymfm_fm.h @@ -304,9 +304,9 @@ public: // simple getters for debugging fm_operator *debug_operator(uint32_t index) const { return m_op[index]; } - int32_t debug_output(uint32_t index) const { return m_output[index]; } - int32_t debug_special1() const { return m_special1; } - int32_t debug_special2() const { return m_special2; } + int32_t debug_output(uint32_t index) const { return m_output[index]; } + int32_t debug_special1() const { return m_special1; } + int32_t debug_special2() const { return m_special2; } private: // helper to add values to the outputs based on channel enables @@ -320,21 +320,21 @@ private: constexpr int out3_index = 3 % RegisterType::OUTPUTS; if (RegisterType::OUTPUTS == 1 || m_regs.ch_output_0(choffs)) { - m_output[out0_index]=value; + m_output[out0_index]=value; output.data[out0_index] += value; - } + } if (RegisterType::OUTPUTS >= 2 && m_regs.ch_output_1(choffs)) { - m_output[out1_index]=value; + m_output[out1_index]=value; output.data[out1_index] += value; - } + } if (RegisterType::OUTPUTS >= 3 && m_regs.ch_output_2(choffs)) { - m_output[out2_index]=value; + m_output[out2_index]=value; output.data[out2_index] += value; - } + } if (RegisterType::OUTPUTS >= 4 && m_regs.ch_output_3(choffs)) { - m_output[out3_index]=value; + m_output[out3_index]=value; output.data[out3_index] += value; - } + } } // internal state @@ -344,9 +344,9 @@ private: fm_operator *m_op[4]; // up to 4 operators RegisterType &m_regs; // direct reference to registers fm_engine_base &m_owner; // reference to the owning engine - mutable int32_t m_output[4]; - mutable int32_t m_special1; - mutable int32_t m_special2; + mutable int32_t m_output[4]; + mutable int32_t m_special1; + mutable int32_t m_special2; }; diff --git a/src/engine/platform/sound/ymfm/ymfm_opl.h b/src/engine/platform/sound/ymfm/ymfm_opl.h index 8a2dd5147..c23a5f0b9 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opl.h +++ b/src/engine/platform/sound/ymfm/ymfm_opl.h @@ -529,7 +529,7 @@ public: // generate samples of sound void generate(output_data *output, uint32_t numsamples = 1); - fm_engine* debug_fm_engine() { return &m_fm; } + fm_engine* debug_fm_engine() { return &m_fm; } protected: // internal state uint8_t m_address; // address register @@ -577,8 +577,8 @@ public: // generate samples of sound void generate(output_data *output, uint32_t numsamples = 1); - fm_engine* debug_fm_engine() { return &m_fm; } - adpcm_b_engine* debug_adpcm_b_engine() { return &m_adpcm_b; } + fm_engine* debug_fm_engine() { return &m_fm; } + adpcm_b_engine* debug_adpcm_b_engine() { return &m_adpcm_b; } protected: // internal state @@ -628,7 +628,7 @@ public: // generate samples of sound void generate(output_data *output, uint32_t numsamples = 1); - fm_engine* debug_fm_engine() { return &m_fm; } + fm_engine* debug_fm_engine() { return &m_fm; } protected: // internal state @@ -677,7 +677,7 @@ public: // generate samples of sound void generate(output_data *output, uint32_t numsamples = 1); - fm_engine* debug_fm_engine() { return &m_fm; } + fm_engine* debug_fm_engine() { return &m_fm; } protected: // internal state @@ -791,6 +791,8 @@ public: // generate samples of sound void generate(output_data *output, uint32_t numsamples = 1); + fm_engine* debug_fm_engine() { return &m_fm; } + pcm_engine* debug_pcm_engine() { return &m_pcm; } protected: // internal state uint16_t m_address; // address register diff --git a/src/engine/platform/sound/ymfm/ymfm_pcm.cpp b/src/engine/platform/sound/ymfm/ymfm_pcm.cpp index 34417490c..30fbe2396 100644 --- a/src/engine/platform/sound/ymfm/ymfm_pcm.cpp +++ b/src/engine/platform/sound/ymfm/ymfm_pcm.cpp @@ -309,6 +309,7 @@ void pcm_channel::clock(uint32_t env_counter) void pcm_channel::output(output_data &output) const { + m_output[0] = m_output[1] = m_output[2] = m_output[3] = 0; // early out if the envelope is effectively off uint32_t envelope = m_env_attenuation; if (envelope > EG_QUIET) @@ -340,6 +341,8 @@ void pcm_channel::output(output_data &output) const uint32_t outnum = m_regs.ch_output_channel(m_choffs) * 2; output.data[outnum + 0] += (lvol * sample) >> 15; output.data[outnum + 1] += (rvol * sample) >> 15; + m_output[outnum + 0] = output.data[outnum + 0]; + m_output[outnum + 1] = output.data[outnum + 1]; } diff --git a/src/engine/platform/sound/ymfm/ymfm_pcm.h b/src/engine/platform/sound/ymfm/ymfm_pcm.h index b471fa611..ad15a80de 100644 --- a/src/engine/platform/sound/ymfm/ymfm_pcm.h +++ b/src/engine/platform/sound/ymfm/ymfm_pcm.h @@ -267,6 +267,8 @@ public: // load a new wavetable entry void load_wavetable(); + int32_t debug_output(uint32_t index) const { return m_output[index]; } + private: // internal helpers void start_attack(); @@ -291,6 +293,7 @@ private: pcm_cache m_cache; // cached data pcm_registers &m_regs; // reference to registers pcm_engine &m_owner; // reference to our owner + mutable int32_t m_output[4]; }; @@ -331,6 +334,8 @@ public: // return a reference to our registers pcm_registers ®s() { return m_regs; } + // simple getters for debugging + pcm_channel *debug_channel(uint32_t index) const { return m_channel[index].get(); } private: // internal state ymfm_interface &m_intf; // reference to the interface diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index c886c9e87..c1e14109e 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -266,7 +266,23 @@ const char* cmdName[]={ "FDS_MOD_AUTO", - "FM_OPMASK" + "FM_OPMASK", + + "MULTIPCM_MIX_FM", + "MULTIPCM_MIX_PCM", + "MULTIPCM_LFO", + "MULTIPCM_VIB", + "MULTIPCM_AM", + "MULTIPCM_AR", + "MULTIPCM_D1R", + "MULTIPCM_DL", + "MULTIPCM_D2R", + "MULTIPCM_RR", + "MULTIPCM_RC", + "MULTIPCM_DAMP", + "MULTIPCM_PSEUDO_REVERB", + "MULTIPCM_LFO_RESET", + "MULTIPCM_LEVEL_DIRECT" }; static_assert((sizeof(cmdName)/sizeof(void*))==DIV_CMD_MAX,"update cmdName!"); diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index 020e0dec7..670e869af 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -285,6 +285,9 @@ int DivSample::getSampleOffset(int offset, int length, DivSampleDepth depth) { case DIV_SAMPLE_DEPTH_IMA_ADPCM: off=(offset+1)/2; break; + case DIV_SAMPLE_DEPTH_12BIT: + off=((offset*3)+1)/2; + break; case DIV_SAMPLE_DEPTH_16BIT: off=offset*2; break; @@ -348,6 +351,10 @@ int DivSample::getSampleOffset(int offset, int length, DivSampleDepth depth) { off=(offset+1)/2; len=(length+1)/2; break; + case DIV_SAMPLE_DEPTH_12BIT: + off=((offset*3)+1)/2; + len=((length*3)+1)/2; + break; case DIV_SAMPLE_DEPTH_16BIT: off=offset*2; len=length*2; @@ -409,6 +416,9 @@ int DivSample::getEndPosition(DivSampleDepth depth) { case DIV_SAMPLE_DEPTH_IMA_ADPCM: off=lengthIMA; break; + case DIV_SAMPLE_DEPTH_12BIT: + off=length12; + break; case DIV_SAMPLE_DEPTH_16BIT: off=length16; break; @@ -606,6 +616,12 @@ bool DivSample::initInternal(DivSampleDepth d, int count) { dataIMA=new unsigned char[lengthIMA]; memset(dataIMA,0,lengthIMA); break; + case DIV_SAMPLE_DEPTH_12BIT: // 12-bit PCM (MultiPCM) + if (data12!=NULL) delete[] data12; + length12=((count*3)+1)/2; + data12=new unsigned char[length12]; + memset(data12,0,length12); + break; case DIV_SAMPLE_DEPTH_16BIT: // 16-bit if (data16!=NULL) delete[] data16; length16=count*2; @@ -1293,6 +1309,14 @@ void DivSample::render(unsigned int formatMask) { case DIV_SAMPLE_DEPTH_IMA_ADPCM: // IMA ADPCM if (adpcm_decode_block(data16,dataIMA,lengthIMA,samples)==0) logE("oh crap!"); break; + case DIV_SAMPLE_DEPTH_12BIT: // 12-bit PCM (MultiPCM) + for (unsigned int i=0,j=0; i>8; + data12[j+1]=((data16[i+0]>>4)&0xf)|(i+1>4)&0xf:0); + if (i+1>8; + } + } } void* DivSample::getCurBuf() { @@ -1512,6 +1545,8 @@ void* DivSample::getCurBuf() { return dataC219; case DIV_SAMPLE_DEPTH_IMA_ADPCM: return dataIMA; + case DIV_SAMPLE_DEPTH_12BIT: + return data12; case DIV_SAMPLE_DEPTH_16BIT: return data16; default: @@ -1548,6 +1583,8 @@ unsigned int DivSample::getCurBufLen() { return lengthC219; case DIV_SAMPLE_DEPTH_IMA_ADPCM: return lengthIMA; + case DIV_SAMPLE_DEPTH_12BIT: + return length12; case DIV_SAMPLE_DEPTH_16BIT: return length16; default: @@ -1662,4 +1699,5 @@ DivSample::~DivSample() { if (dataMuLaw) delete[] dataMuLaw; if (dataC219) delete[] dataC219; if (dataIMA) delete[] dataIMA; + if (data12) delete[] data12; } diff --git a/src/engine/sample.h b/src/engine/sample.h index 5fc593d23..c585c7e32 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -47,6 +47,7 @@ enum DivSampleDepth: unsigned char { DIV_SAMPLE_DEPTH_MULAW=11, DIV_SAMPLE_DEPTH_C219=12, DIV_SAMPLE_DEPTH_IMA_ADPCM=13, + DIV_SAMPLE_DEPTH_12BIT=14, DIV_SAMPLE_DEPTH_16BIT=16, DIV_SAMPLE_DEPTH_MAX // boundary for sample depth }; @@ -118,6 +119,7 @@ struct DivSample { // - 11: 8-bit µ-law PCM // - 12: C219 "µ-law" PCM // - 13: IMA ADPCM + // - 14: 12-bit PCM (MultiPCM) // - 16: 16-bit PCM DivSampleDepth depth; bool loop, brrEmphasis, brrNoFilter, dither; @@ -144,8 +146,9 @@ struct DivSample { unsigned char* dataMuLaw; // 11 unsigned char* dataC219; // 12 unsigned char* dataIMA; // 13 + unsigned char* data12; // 14 - unsigned int length8, length16, length1, lengthDPCM, lengthZ, lengthQSoundA, lengthA, lengthB, lengthK, lengthBRR, lengthVOX, lengthMuLaw, lengthC219, lengthIMA; + unsigned int length8, length16, length1, lengthDPCM, lengthZ, lengthQSoundA, lengthA, lengthB, lengthK, lengthBRR, lengthVOX, lengthMuLaw, lengthC219, lengthIMA, length12; unsigned int samples; @@ -356,6 +359,7 @@ struct DivSample { dataMuLaw(NULL), dataC219(NULL), dataIMA(NULL), + data12(NULL), length8(0), length16(0), length1(0), @@ -370,6 +374,7 @@ struct DivSample { lengthMuLaw(0), lengthC219(0), lengthIMA(0), + length12(0), samples(0) { for (int i=0; i, effectValAnd<1>}}, }; + EffectHandlerMap fmOPL4PostEffectHandlerMap(fmOPLPostEffectHandlerMap); + fmOPL4PostEffectHandlerMap.insert({ + {0x1e, {DIV_CMD_MULTIPCM_MIX_FM, _("1Exy: FM global level (x: left, y: right; 0 to 7)"), effectVal}}, + {0x1f, {DIV_CMD_MULTIPCM_MIX_PCM, _("1Fxy: PCM global level (x: left, y: right; 0 to 7)"), effectVal}}, + {0x20, {DIV_CMD_MULTIPCM_LFO, _("20xx: PCM LFO Rate (0 to 7)"), effectValAnd<7>}}, + {0x21, {DIV_CMD_MULTIPCM_VIB, _("21xx: PCM LFO PM Depth (0 to 7)"), effectValAnd<7>}}, + {0x22, {DIV_CMD_MULTIPCM_AM, _("22xx: PCM LFO AM Depth (0 to 7)"), effectValAnd<7>}}, + {0x23, {DIV_CMD_MULTIPCM_AR, _("23xx: PCM Attack Rate (0 to 15)"), effectValAnd<15>}}, + {0x24, {DIV_CMD_MULTIPCM_D1R, _("24xx: PCM Decay 1 Rate (0 to 15)"), effectValAnd<15>}}, + {0x25, {DIV_CMD_MULTIPCM_DL, _("25xx: PCM Decay Level (0 to 15)"), effectValAnd<15>}}, + {0x26, {DIV_CMD_MULTIPCM_D2R, _("26xx: PCM Decay 2 Rate (0 to 15)"), effectValAnd<15>}}, + {0x27, {DIV_CMD_MULTIPCM_RR, _("27xx: PCM Release Rate (0 to 15)"), effectValAnd<15>}}, + {0x28, {DIV_CMD_MULTIPCM_RC, _("28xx: PCM Rate Correction (0 to 15)"), effectValAnd<15>}}, + {0x2c, {DIV_CMD_MULTIPCM_DAMP, _("2Cxx: PCM Damp"), effectValAnd<1>}}, + {0x2d, {DIV_CMD_MULTIPCM_PSEUDO_REVERB, _("2Dxx: PCM Pseudo Reverb"), effectValAnd<1>}}, + {0x2e, {DIV_CMD_MULTIPCM_LFO_RESET, _("2Exx: PCM LFO Reset"), effectValAnd<1>}}, + {0x2f, {DIV_CMD_MULTIPCM_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)")}}, @@ -1618,24 +1637,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<writeC(0); } break; + case DIV_SYSTEM_OPL4: + case DIV_SYSTEM_OPL4_DRUMS: + // disable envelope + for (int i=0; i<6; i++) { + w->writeC(0xd0); + w->writeC(0x00|baseAddr2); + w->writeC(0x80+i); + w->writeC(0x0f); + w->writeC(0xd0); + w->writeC(0x00|baseAddr2); + w->writeC(0x88+i); + w->writeC(0x0f); + w->writeC(0xd0); + w->writeC(0x00|baseAddr2); + w->writeC(0x90+i); + w->writeC(0x0f); + w->writeC(0xd0); + w->writeC(0x01|baseAddr2); + w->writeC(0x80+i); + w->writeC(0x0f); + w->writeC(0xd0); + w->writeC(0x01|baseAddr2); + w->writeC(0x88+i); + w->writeC(0x0f); + w->writeC(0xd0); + w->writeC(0x01|baseAddr2); + w->writeC(0x90+i); + w->writeC(0x0f); + } + for (int i=0; i<24; i++) { + w->writeC(0xd0); + w->writeC(0x02|baseAddr2); + w->writeC(0x80+i); + w->writeC(0x00); + w->writeC(0xd0); + w->writeC(0x02|baseAddr2); + w->writeC(0x98+i); + w->writeC(0x00); + w->writeC(0xd0); + w->writeC(0x02|baseAddr2); + w->writeC(0xb0+i); + w->writeC(0x00); + w->writeC(0xd0); + w->writeC(0x02|baseAddr2); + w->writeC(0xc8+i); + w->writeC(0x00); + w->writeC(0xd0); + w->writeC(0x02|baseAddr2); + w->writeC(0xe0+i); + w->writeC(0x00); + } + // key off + freq reset + for (int i=0; i<9; i++) { + w->writeC(0xd0); + w->writeC(0x00|baseAddr2); + w->writeC(0xa0+i); + w->writeC(0); + w->writeC(0xd0); + w->writeC(0x00|baseAddr2); + w->writeC(0xb0+i); + w->writeC(0); + w->writeC(0xd0); + w->writeC(0x01|baseAddr2); + w->writeC(0xa0+i); + w->writeC(0); + w->writeC(0xd0); + w->writeC(0x01|baseAddr2); + w->writeC(0xb0+i); + w->writeC(0); + } + for (int i=0; i<24; i++) { + w->writeC(0xd0); + w->writeC(0x02|baseAddr2); + w->writeC(0x20+i); + w->writeC(0); + w->writeC(0xd0); + w->writeC(0x02|baseAddr2); + w->writeC(0x38+i); + w->writeC(0); + w->writeC(0xd0); + w->writeC(0x02|baseAddr2); + w->writeC(0x68+i); + w->writeC(8); + } + // reset 4-op + w->writeC(0xd0); + w->writeC(0x01|baseAddr2); + w->writeC(0x04); + w->writeC(0x00); + break; default: break; } @@ -1096,6 +1186,13 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeS_BE(baseAddr2S|(write.addr&0x1ff)); w->writeC(write.val&0xff); break; + case DIV_SYSTEM_OPL4: + case DIV_SYSTEM_OPL4_DRUMS: + w->writeC(0xd0); + w->writeC(((write.addr>>8)&0x7f)|baseAddr2); + w->writeC(write.addr&0xff); + w->writeC(write.val); + break; default: logW("write not handled!"); break; @@ -1276,6 +1373,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p DivDispatch* writeC140[2]={NULL,NULL}; DivDispatch* writeC219[2]={NULL,NULL}; DivDispatch* writeNES[2]={NULL,NULL}; + DivDispatch* writePCM_OPL4[2]={NULL,NULL}; int writeNESIndex[2]={0,0}; @@ -1874,6 +1972,22 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p howManyChips++; } break; + case DIV_SYSTEM_OPL4: + case DIV_SYSTEM_OPL4_DRUMS: + if (!hasOPL4) { + hasOPL4=disCont[i].dispatch->chipClock; + CHIP_VOL(13,1.0); + willExport[i]=true; + writePCM_OPL4[0]=disCont[i].dispatch; + } else if (!(hasOPL4&0x40000000)) { + isSecond[i]=true; + CHIP_VOL_SECOND(13,1.0); + willExport[i]=true; + writePCM_OPL4[1]=disCont[i].dispatch; + hasOPL4|=0x40000000; + howManyChips++; + } + break; default: break; } @@ -2209,6 +2323,16 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p w->write(sampleMem,sampleMemLen); delete[] sampleMem; } + // PCM (OPL4) + if (writePCM_OPL4[i]!=NULL && writePCM_OPL4[i]->getSampleMemUsage(0)>0) { + w->writeC(0x67); + w->writeC(0x66); + w->writeC(0x84); + w->writeI((writePCM_OPL4[i]->getSampleMemUsage(0)+8)|(i*0x80000000)); + w->writeI(writePCM_OPL4[i]->getSampleMemCapacity(0)); + w->writeI(0); + w->write(writePCM_OPL4[i]->getSampleMem(0),writePCM_OPL4[i]->getSampleMemUsage(0)); + } } for (int i=0; i<2; i++) { diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 66d229006..e2664c890 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -300,6 +300,7 @@ const char* aboutLine[]={ _N("PowerNoise emulator by scratchminer"), _N("ep128emu by Istvan Varga"), _N("NDS sound emulator by cam900"), + _N("openMSX YMF278 emulator (modified version) by the openMSX developers"), "", _N("greetings to:"), "floxy!", diff --git a/src/gui/gui.h b/src/gui/gui.h index 250c01ea1..c48d3615e 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1772,6 +1772,7 @@ class FurnaceGUI { int opnbCore; int opl2Core; int opl3Core; + int opl4Core; int esfmCore; int opllCore; int ayCore; @@ -1798,6 +1799,7 @@ class FurnaceGUI { int opnbCoreRender; int opl2CoreRender; int opl3CoreRender; + int opl4CoreRender; int esfmCoreRender; int opllCoreRender; int ayCoreRender; @@ -2032,6 +2034,7 @@ class FurnaceGUI { opnbCore(1), opl2Core(0), opl3Core(0), + opl4Core(0), esfmCore(0), opllCore(0), ayCore(0), @@ -2058,6 +2061,7 @@ class FurnaceGUI { opnbCoreRender(1), opl2CoreRender(0), opl3CoreRender(0), + opl4CoreRender(0), esfmCoreRender(0), opllCoreRender(0), ayCoreRender(0), diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 06740d927..c09e79edc 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -210,7 +210,7 @@ const char* sampleDepths[DIV_SAMPLE_DEPTH_MAX]={ "8-bit µ-law PCM", "C219 PCM", "IMA ADPCM", - NULL, + "12-bit PCM", NULL, "16-bit PCM" }; @@ -1270,6 +1270,8 @@ const int availableSystems[]={ DIV_SYSTEM_5E01, DIV_SYSTEM_BIFURCATOR, DIV_SYSTEM_SID2, + DIV_SYSTEM_OPL4, + DIV_SYSTEM_OPL4_DRUMS, DIV_SYSTEM_SUPERVISION, DIV_SYSTEM_UPD1771C, 0 // don't remove this last one! @@ -1307,6 +1309,8 @@ const int chipsFM[]={ DIV_SYSTEM_OPL3_DRUMS, DIV_SYSTEM_OPZ, DIV_SYSTEM_ESFM, + DIV_SYSTEM_OPL4, + DIV_SYSTEM_OPL4_DRUMS, 0 // don't remove this last one! }; @@ -1394,6 +1398,8 @@ const int chipsSample[]={ DIV_SYSTEM_NDS, DIV_SYSTEM_GBA_DMA, DIV_SYSTEM_GBA_MINMOD, + DIV_SYSTEM_OPL4, + DIV_SYSTEM_OPL4_DRUMS, 0 // don't remove this last one! }; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 9269ad06b..fd30812df 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -6569,6 +6569,19 @@ void FurnaceGUI::drawInsEdit() { P(CWSliderScalar(_("AM Depth"),ImGuiDataType_U8,&ins->multipcm.am,&_ZERO,&_SEVEN)); rightClickable ImGui::EndTable(); } + P(ImGui::Checkbox(_("Damp"),&ins->multipcm.damp)); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip(_("Only for OPL4 PCM.")); + } + P(ImGui::Checkbox(_("Pseudo Reverb"),&ins->multipcm.pseudoReverb)); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip(_("Only for OPL4 PCM.")); + } + P(ImGui::Checkbox(_("LFO Reset"),&ins->multipcm.lfoReset)); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip(_("Only for OPL4 PCM.")); + } + P(ImGui::Checkbox(_("Level Direct"),&ins->multipcm.levelDirect)); ImGui::EndTabItem(); } } @@ -7262,6 +7275,9 @@ void FurnaceGUI::drawInsEdit() { 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 Vib 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])); break; case DIV_INS_SNES: macroList.push_back(FurnaceGUIMacroDesc(_("Volume"),&ins->std.volMacro,0,127,160,uiColors[GUI_COLOR_MACRO_VOLUME])); diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 85cf9ba58..ae2ae7672 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -524,6 +524,18 @@ void FurnaceGUI::initSystemPresets() { ) // variable rate, Mono DAC } ); + SUB_ENTRY( + "MSX + MoonSound", { + CH(DIV_SYSTEM_AY8910, 1.0f, 0, "chipType=1"), + CH(DIV_SYSTEM_OPL4, 1.0f, 0, "") + } + ); + SUB_ENTRY( + "MSX + MoonSound (drums mode)", { + CH(DIV_SYSTEM_AY8910, 1.0f, 0, "chipType=1"), + CH(DIV_SYSTEM_OPL4_DRUMS, 1.0f, 0, "") + } + ); ENTRY( "NEC PC-6001", { CH(DIV_SYSTEM_AY8910, 1.0f, 0, "customClock=3993600") @@ -1905,6 +1917,30 @@ void FurnaceGUI::initSystemPresets() { } ); + ENTRY( + "Psikyo", {} + ); + SUB_ENTRY( + "Psikyo 68EC020 hardware with OPL4", { + CH(DIV_SYSTEM_OPL4, 1.0f, 0, "") + } + ); + SUB_ENTRY( + "Psikyo 68EC020 hardware with OPL4 (drums mode)", { + CH(DIV_SYSTEM_OPL4_DRUMS, 1.0f, 0, "") + } + ); + SUB_ENTRY( + "Psikyo SH-2 hardware", { + CH(DIV_SYSTEM_OPL4, 1.0f, 0, "clockSel=1") + } + ); + SUB_ENTRY( + "Psikyo SH-2 hardware (drums mode)", { + CH(DIV_SYSTEM_OPL4_DRUMS, 1.0f, 0, "clockSel=1") + } + ); + ENTRY( "Sega", {} ); @@ -2729,6 +2765,16 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_ESFM, 1.0f, 0, "") } ); + ENTRY( + "Yamaha YMF278B (OPL4)", { + CH(DIV_SYSTEM_OPL4, 1.0f, 0, "") + } + ); + SUB_ENTRY( + "Yamaha YMF278B (drums mode)", { + CH(DIV_SYSTEM_OPL4_DRUMS, 1.0f, 0, "") + } + ); if (settings.hiddenSystems) { ENTRY( "Yamaha YMU759 (MA-2)", { @@ -2940,6 +2986,16 @@ void FurnaceGUI::initSystemPresets() { CH(DIV_SYSTEM_NDS, 1.0f, 0, "") } ); + ENTRY( + "Yamaha YMF278B (OPL4)", { + CH(DIV_SYSTEM_OPL4, 1.0f, 0, "") + } + ); + SUB_ENTRY( + "Yamaha YMF278B (drums mode)", { + CH(DIV_SYSTEM_OPL4_DRUMS, 1.0f, 0, "") + } + ); CATEGORY_END; CATEGORY_BEGIN("Wavetable","chips which use user-specified waveforms to generate sound."); diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index d5975c55a..9a6000820 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -413,6 +413,12 @@ void FurnaceGUI::drawSampleEdit() { SAMPLE_WARN(warnLength,_("GBA DMA: sample length will be padded to multiple of 16")); } break; + case DIV_SYSTEM_OPL4: + case DIV_SYSTEM_OPL4_DRUMS: + if (sample->samples>65535) { + SAMPLE_WARN(warnLength,_("OPL4: maximum sample length is 65535")); + } + break; case DIV_SYSTEM_SUPERVISION: if (sample->loop) { if (sample->loopStart!=0 || sample->loopEnd!=(int)(sample->samples)) { diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 5bc3008d2..1b168b72d 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -189,6 +189,11 @@ const char* opl3Cores[]={ "YMF262-LLE" }; +const char* opl4Cores[]={ + "Nuked-OPL3 (FM) + openMSX (PCM)", + "ymfm" +}; + const char* esfmCores[]={ "ESFMu", _N("ESFMu (fast)") @@ -2058,6 +2063,17 @@ void FurnaceGUI::drawSettings() { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::Combo("##OPL3CoreRender",&settings.opl3CoreRender,opl3Cores,3)) settingsChanged=true; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::Text("OPL4"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::Combo("##OPL4Core",&settings.opl4Core,opl4Cores,2)) settingsChanged=true; + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::Combo("##OPL4CoreRender",&settings.opl4CoreRender,opl4Cores,2)) settingsChanged=true; + ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::AlignTextToFramePadding(); @@ -5002,6 +5018,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { settings.opnbCore=conf.getInt("opnbCore",1); settings.opl2Core=conf.getInt("opl2Core",0); settings.opl3Core=conf.getInt("opl3Core",0); + settings.opl4Core=conf.getInt("opl4Core",0); settings.esfmCore=conf.getInt("esfmCore",0); settings.opllCore=conf.getInt("opllCore",0); settings.ayCore=conf.getInt("ayCore",0); @@ -5030,6 +5047,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { settings.opnbCoreRender=conf.getInt("opnbCoreRender",1); settings.opl2CoreRender=conf.getInt("opl2CoreRender",0); settings.opl3CoreRender=conf.getInt("opl3CoreRender",0); + settings.opl4CoreRender=conf.getInt("opl4CoreRender",0); settings.esfmCoreRender=conf.getInt("esfmCoreRender",0); settings.opllCoreRender=conf.getInt("opllCoreRender",0); settings.ayCoreRender=conf.getInt("ayCoreRender",0); @@ -5075,6 +5093,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { clampSetting(settings.opnbCore,0,2); clampSetting(settings.opl2Core,0,2); clampSetting(settings.opl3Core,0,2); + clampSetting(settings.opl4Core,0,1); clampSetting(settings.esfmCore,0,1); clampSetting(settings.opllCore,0,1); clampSetting(settings.ayCore,0,1); @@ -5101,6 +5120,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { clampSetting(settings.opnbCoreRender,0,2); clampSetting(settings.opl2CoreRender,0,2); clampSetting(settings.opl3CoreRender,0,2); + clampSetting(settings.opl4CoreRender,0,1); clampSetting(settings.esfmCoreRender,0,1); clampSetting(settings.opllCoreRender,0,1); clampSetting(settings.ayCoreRender,0,1); @@ -5591,6 +5611,7 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { conf.set("opnbCore",settings.opnbCore); conf.set("opl2Core",settings.opl2Core); conf.set("opl3Core",settings.opl3Core); + conf.set("opl4Core",settings.opl4Core); conf.set("esfmCore",settings.esfmCore); conf.set("opllCore",settings.opllCore); conf.set("ayCore",settings.ayCore); @@ -5619,6 +5640,7 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { conf.set("opnbCoreRender",settings.opnbCoreRender); conf.set("opl2CoreRender",settings.opl2CoreRender); conf.set("opl3CoreRender",settings.opl3CoreRender); + conf.set("opl4CoreRender",settings.opl4CoreRender); conf.set("esfmCoreRender",settings.esfmCoreRender); conf.set("opllCoreRender",settings.opllCoreRender); conf.set("ayCoreRender",settings.ayCoreRender); @@ -5682,6 +5704,7 @@ void FurnaceGUI::commitSettings() { settings.opnbCore!=e->getConfInt("opnbCore",1) || settings.opl2Core!=e->getConfInt("opl2Core",0) || settings.opl3Core!=e->getConfInt("opl3Core",0) || + settings.opl4Core!=e->getConfInt("opl4Core",0) || settings.esfmCore!=e->getConfInt("esfmCore",0) || settings.opllCore!=e->getConfInt("opllCore",0) || settings.ayCore!=e->getConfInt("ayCore",0) || diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index a22ea0323..997c3674e 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -2543,6 +2543,67 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl } break; } + case DIV_SYSTEM_OPL4: + case DIV_SYSTEM_OPL4_DRUMS: { + int clockSel=flags.getInt("clockSel",0); + int ramSize=flags.getInt("ramSize",0); + + ImGui::Text(_("Clock rate:")); + ImGui::Indent(); + if (ImGui::RadioButton(_("33.8688MHz"),clockSel==0)) { + clockSel=0; + altered=true; + } + if (ImGui::RadioButton(_("28.64MHz (NTSC)"),clockSel==1)) { + clockSel=1; + altered=true; + } + if (ImGui::RadioButton(_("28.38MHz (PAL)"),clockSel==2)) { + clockSel=2; + altered=true; + } + ImGui::Unindent(); + + ImGui::Text(_("RAM size:")); + ImGui::Indent(); + if (ImGui::RadioButton(_("4MB"),ramSize==0)) { + ramSize=0; + altered=true; + } + if (ImGui::RadioButton(_("2MB"),ramSize==1)) { + ramSize=1; + altered=true; + } + if (ImGui::RadioButton(_("1MB"),ramSize==2)) { + ramSize=2; + altered=true; + } + if (ImGui::RadioButton(_("640KB"),ramSize==3)) { + ramSize=3; + altered=true; + } + if (ImGui::RadioButton(_("512KB"),ramSize==4)) { + ramSize=4; + altered=true; + } + if (ImGui::RadioButton(_("256KB"),ramSize==5)) { + ramSize=5; + altered=true; + } + if (ImGui::RadioButton(_("128KB"),ramSize==6)) { + ramSize=6; + altered=true; + } + ImGui::Unindent(); + + if (altered) { + e->lockSave([&]() { + flags.set("clockSel",clockSel); + flags.set("ramSize",ramSize); + }); + } + break; + } case DIV_SYSTEM_SWAN: case DIV_SYSTEM_BUBSYS_WSG: case DIV_SYSTEM_PET: diff --git a/src/main.cpp b/src/main.cpp index 30b551a83..37f74ef2a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -339,6 +339,7 @@ TAParamResult pVersion(String) { printf("- PowerNoise emulator by scratchminer (MIT)\n"); printf("- ep128emu by Istvan Varga (GPLv2)\n"); printf("- NDS sound emulator by cam900 (zlib license)\n"); + printf("- openMSX YMF278 emulator (modified version) by the openMSX developers (GPLv2)\n"); return TA_PARAM_QUIT; }