From c08edb125476f313fd9b76260e2f571af2507576 Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 11 Jul 2024 15:13:02 +0900 Subject: [PATCH 01/39] Prepare to (very) partially OPL4 support --- extern/adpcm-xq | 1 + src/engine/dispatchContainer.cpp | 12 + src/engine/platform/opl.cpp | 700 ++++++++++++++------ src/engine/platform/opl.h | 25 +- src/engine/platform/oplAInterface.cpp | 5 + src/engine/platform/sound/ymfm/ymfm_fm.h | 28 +- src/engine/platform/sound/ymfm/ymfm_opl.h | 12 +- src/engine/platform/sound/ymfm/ymfm_pcm.cpp | 3 + src/engine/platform/sound/ymfm/ymfm_pcm.h | 5 + src/engine/sysDef.cpp | 10 +- src/engine/vgmOps.cpp | 32 + src/gui/guiConst.cpp | 6 + src/gui/presets.cpp | 32 + src/gui/sysConf.cpp | 53 ++ 14 files changed, 691 insertions(+), 233 deletions(-) create mode 160000 extern/adpcm-xq diff --git a/extern/adpcm-xq b/extern/adpcm-xq new file mode 160000 index 000000000..6220fed76 --- /dev/null +++ b/extern/adpcm-xq @@ -0,0 +1 @@ +Subproject commit 6220fed7655e86a29702b45dbc641a028ed5a4bf diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 65ca2383d..9bb0abc75 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -760,6 +760,18 @@ 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); + // YMFM for now + ((DivPlatformOPL*)dispatch)->setCore(1); + break; + case DIV_SYSTEM_OPL4_DRUMS: + dispatch=new DivPlatformOPL; + ((DivPlatformOPL*)dispatch)->setOPLType(4,true); + // YMFM for now + ((DivPlatformOPL*)dispatch)->setCore(1); + break; case DIV_SYSTEM_DUMMY: dispatch=new DivPlatformDummy; break; diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 77eabbf1e..b9188d1db 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -159,6 +159,22 @@ 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]; @@ -505,6 +521,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>>3,-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 +929,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 +949,148 @@ double DivPlatformOPL::NOTE_ADPCMB(int note) { void DivPlatformOPL::tick(bool sysTick) { for (int i=0; i=pcmChanOffs) { // OPL4 PCM + chan[i].std.next(); + } 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 +1210,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 +1224,42 @@ 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 (i=pcmChanOffs) { // OPL4 PCM + + } else { + if (chan[i].freqChanged) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,octave(chan[i].baseFreq)*2,chan[i].pitch2,chipClock,CHIP_FREQBASE); + 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 (i=pcmChanOffs) { // OPL4 PCM + + } 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) { @@ -1553,7 +1679,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { 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); @@ -1659,6 +1785,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_LFO: { + if (c.chan>=pcmChanOffs) break; if (c.chan==adpcmChan) break; if (c.value&2) { dvb=c.value&1; @@ -1669,6 +1796,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_FB: { + if (c.chan>=pcmChanOffs) 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 +1814,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_MULT: { + if (c.chan>=pcmChanOffs) 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 +1827,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_TL: { + if (c.chan>=pcmChanOffs) 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 +1848,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_AR: { + if (c.chan>=pcmChanOffs) 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 +1872,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_DR: { + if (c.chan>=pcmChanOffs) 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 +1896,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_SL: { + if (c.chan>=pcmChanOffs) 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 +1920,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_RR: { + if (c.chan>=pcmChanOffs) 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 +1944,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_AM: { + if (c.chan>=pcmChanOffs) 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 +1968,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_VIB: { + if (c.chan>=pcmChanOffs) 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 +1992,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_SUS: { + if (c.chan>=pcmChanOffs) 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 +2016,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_KSR: { + if (c.chan>=pcmChanOffs) 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 +2040,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_WS: { + if (c.chan>=pcmChanOffs) 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 +2065,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_RS: { + if (c.chan>=pcmChanOffs) 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 +2114,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; @@ -1995,6 +2139,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].std.restart(c.value); break; case DIV_CMD_GET_VOLMAX: + if (c.chan>=pcmChanOffs) return 127; if (c.chan==adpcmChan) return 255; if (pretendYMU) return 127; return 63; @@ -2019,6 +2164,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 +2216,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); @@ -2145,12 +2294,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; @@ -2204,6 +2353,9 @@ void DivPlatformOPL::reset() { case 3: case 759: fm_ymfm3->reset(); break; + case 4: + fm_ymfm4->reset(); + break; } } else { if (downsample) { @@ -2223,6 +2375,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; @@ -2256,7 +2412,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; } @@ -2277,7 +2433,13 @@ 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); + } else { + immWrite(0x105,1); + } } update4OpMask=true; @@ -2368,6 +2530,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; } @@ -2493,18 +2657,36 @@ 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: + ramSize=0x200000; + break; + case 0x02: + ramSize=0x100000; + break; + case 0x03: + ramSize=0x80000; + break; + case 0x04: + ramSize=0x20000; + break; + default: + ramSize=0x400000; break; } CHECK_CUSTOM_CLOCK; rate=chipClock/768; chipRateBase=chipClock/684; + immWrite(0x202,(ramSize<=0x200000)?0x10:0x00); break; case 759: rate=48000; @@ -2514,21 +2696,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) { @@ -2538,61 +2724,151 @@ 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 (adpcmBMem!=NULL) { + memset(adpcmBMem,0,262144); + } + if (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); + int sampleCount=parent->song.sampleLen; + if (sampleCount>511) sampleCount=511; + for (int i=0; isong.sample[i]; + if (!s->renderOn[0][sysID]) { + sampleOffPCM[i]=0; + continue; + } + + 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_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 loop=CLAMP(s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT),0,0xffff); + int endPos=CLAMP(s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT),1,0x10000); + switch (s->depth) { + default: + case DIV_SAMPLE_DEPTH_8BIT: + bitDepth=0; + 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>>8)&0xff; + pcmMem[6+insAddr]=(endPos)&0xff; + // TODO: how to fill in rest of instrument table? + pcmMem[7+insAddr]=0; // LFO, VIB + pcmMem[8+insAddr]=0; // AR, D1R + pcmMem[9+insAddr]=0; // DL, D2R + pcmMem[10+insAddr]=0; // RC, RR + pcmMem[11+insAddr]=0; // AM + } + if (ramSize<=0x200000) { + memCompo.entries.push_back(DivMemoryEntry(DIV_MEMORY_RESERVED,"ROM data",0,0,0x200000)); } - int paddedLen=(s->lengthB+255)&(~0xff); - if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) { - memPos=(memPos+0xfffff)&0xf00000; + memCompo.used=pcmMemLen; + memCompo.capacity=getSampleMemCapacity(0); + } 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; } - 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); } - adpcmBMemLen=memPos+256; - - memCompo.used=adpcmBMemLen; - memCompo.capacity=getSampleMemCapacity(0); } int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { 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; } @@ -2600,6 +2876,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) { @@ -2615,31 +2892,44 @@ 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; + } + 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; @@ -2656,6 +2946,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 e76aae834..90951b824 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -29,14 +29,16 @@ extern "C" { } #include "sound/ymfm/ymfm_adpcm.h" #include "sound/ymfm/ymfm_opl.h" +#include "sound/ymfm/ymfm_pcm.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 DivPlatformOPL: public DivDispatch { @@ -62,9 +64,9 @@ class DivPlatformOPL: public DivDispatch { 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 +74,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 +88,11 @@ class DivPlatformOPL: public DivDispatch { unsigned char* adpcmBMem; size_t adpcmBMemLen; + unsigned char* pcmMem; + size_t pcmMemLen; DivOPLAInterface iface; unsigned int sampleOffB[256]; + unsigned int sampleOffPCM[256]; bool sampleLoaded[256]; ymfm::adpcm_b_engine* adpcmB; @@ -97,12 +102,12 @@ 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; unsigned char lastBusy; unsigned char drumState; unsigned char drumVol[5]; - unsigned char regPool[512]; + unsigned char regPool[768]; bool properDrums, properDrumsSys, dam, dvb; @@ -115,8 +120,8 @@ 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; @@ -124,6 +129,7 @@ class DivPlatformOPL: public DivDispatch { 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 +147,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); diff --git a/src/engine/platform/oplAInterface.cpp b/src/engine/platform/oplAInterface.cpp index 37ebda7f0..286385806 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&0xffffff]; + case ymfm::ACCESS_PCM: + if (pcmMem==NULL) { + return 0; + } + return pcmMem[address&0x3fffff]; default: return 0; } 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/sysDef.cpp b/src/engine/sysDef.cpp index 20e8461d5..41ecbb050 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1621,7 +1621,10 @@ void DivEngine::registerSystems() { {_("4OP 1"), _("FM 2"), _("4OP 3"), _("FM 4"), _("4OP 5"), _("FM 6"), _("4OP 7"), _("FM 8"), _("4OP 9"), _("FM 10"), _("4OP 11"), _("FM 12"), _("FM 13"), _("FM 14"), _("FM 15"), _("FM 16"), _("FM 17"), _("FM 18"), _("PCM 1"), _("PCM 2"), _("PCM 3"), _("PCM 4"), _("PCM 5"), _("PCM 6"), _("PCM 7"), _("PCM 8"), _("PCM 9"), _("PCM 10"), _("PCM 11"), _("PCM 12"), _("PCM 13"), _("PCM 14"), _("PCM 15"), _("PCM 16"), _("PCM 17"), _("PCM 18"), _("PCM 19"), _("PCM 20"), _("PCM 21"), _("PCM 22"), _("PCM 23"), _("PCM 24")}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P8", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"}, {DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, - {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM} + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM}, + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, + fmEffectHandlerMap, + fmOPLPostEffectHandlerMap ); // TODO: same here @@ -1631,7 +1634,10 @@ void DivEngine::registerSystems() { {_("4OP 1"), _("FM 2"), _("4OP 3"), _("FM 4"), _("4OP 5"), _("FM 6"), _("4OP 7"), _("FM 8"), _("4OP 9"), _("FM 10"), _("4OP 11"), _("FM 12"), _("FM 13"), _("FM 14"), _("FM 15"), _("Kick/FM 16"), _("Snare"), _("Tom"), _("Top"), _("HiHat"), _("PCM 1"), _("PCM 2"), _("PCM 3"), _("PCM 4"), _("PCM 5"), _("PCM 6"), _("PCM 7"), _("PCM 8"), _("PCM 9"), _("PCM 10"), _("PCM 11"), _("PCM 12"), _("PCM 13"), _("PCM 14"), _("PCM 15"), _("PCM 16"), _("PCM 17"), _("PCM 18"), _("PCM 19"), _("PCM 20"), _("PCM 21"), _("PCM 22"), _("PCM 23"), _("PCM 24")}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "BD", "SD", "TM", "TP", "HH", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P8", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"}, {DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, - {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM} + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM}, + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, + fmOPLDrumsEffectHandlerMap, + fmOPLPostEffectHandlerMap ); EffectHandlerMap es5506PreEffectHandlerMap={ diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index a8a1fab74..e0391fe6e 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -987,6 +987,13 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write break; } break; + case DIV_SYSTEM_OPL4: + case DIV_SYSTEM_OPL4_DRUMS: + w->writeC(0xd0|baseAddr2); + w->writeC(write.addr>>8); + w->writeC(write.addr&0xff); + w->writeC(write.val); + break; case DIV_SYSTEM_SCC: if (write.addr<0x80) { w->writeC(0xd2); @@ -1254,6 +1261,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p bool writeVOXSamples=false; DivDispatch* writeADPCM_OPNA[2]={NULL,NULL}; DivDispatch* writeADPCM_OPNB[2]={NULL,NULL}; + DivDispatch* writePCM_OPL4[2]={NULL,NULL}; DivDispatch* writeADPCM_Y8950[2]={NULL,NULL}; DivDispatch* writeSegaPCM[2]={NULL,NULL}; DivDispatch* writeX1010[2]={NULL,NULL}; @@ -1706,6 +1714,20 @@ 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(12,1.0); + willExport[i]=true; + } else if (!(hasOPL4&0x40000000)) { + isSecond[i]=true; + CHIP_VOL_SECOND(12,1.0); + willExport[i]=true; + hasOPL4|=0x40000000; + howManyChips++; + } + break; case DIV_SYSTEM_SCC: case DIV_SYSTEM_SCC_PLUS: if (!hasK051649) { @@ -2150,6 +2172,16 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p w->writeI(0); w->write(writeADPCM_OPNB[i]->getSampleMem(1),writeADPCM_OPNB[i]->getSampleMemUsage(1)); } + // 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)); + } // ADPCM (Y8950) if (writeADPCM_Y8950[i]!=NULL && writeADPCM_Y8950[i]->getSampleMemUsage(0)>0) { w->writeC(0x67); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index ca7a26b44..b9d9ae85d 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -1260,6 +1260,8 @@ const int availableSystems[]={ DIV_SYSTEM_5E01, DIV_SYSTEM_BIFURCATOR, DIV_SYSTEM_SID2, + DIV_SYSTEM_OPL4, + DIV_SYSTEM_OPL4_DRUMS, 0 // don't remove this last one! }; @@ -1295,6 +1297,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! }; @@ -1380,6 +1384,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/presets.cpp b/src/gui/presets.cpp index 382bdd1e9..19a35c6bc 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -519,6 +519,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-88", {} ); @@ -2659,6 +2671,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)", { @@ -2870,6 +2892,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/sysConf.cpp b/src/gui/sysConf.cpp index 0875f25c1..0c9848681 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -2503,6 +2503,59 @@ 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(_("512KB"),ramSize==3)) { + ramSize=3; + altered=true; + } + if (ImGui::RadioButton(_("128KB"),ramSize==4)) { + ramSize=4; + 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: From c2bbe0a98acdefa57a03803c8296e08c5b23d4fe Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 11 Jul 2024 15:17:26 +0900 Subject: [PATCH 02/39] Oopsie --- extern/adpcm-xq | 1 - 1 file changed, 1 deletion(-) delete mode 160000 extern/adpcm-xq diff --git a/extern/adpcm-xq b/extern/adpcm-xq deleted file mode 160000 index 6220fed76..000000000 --- a/extern/adpcm-xq +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6220fed7655e86a29702b45dbc641a028ed5a4bf From 6a5a58ad134b02df5b8c8fd3b8b160fc79d1a643 Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 11 Jul 2024 20:16:41 +0900 Subject: [PATCH 03/39] Sample playable, but still incomplete --- src/engine/platform/opl.cpp | 148 +++++++++++++++++++++++++++++++++--- src/engine/platform/opl.h | 12 ++- 2 files changed, 145 insertions(+), 15 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index b9188d1db..f72479ee3 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -947,10 +947,95 @@ double DivPlatformOPL::NOTE_ADPCMB(int note) { return 0; } +double DivPlatformOPL::NOTE_OPL4(int ch, int note) { // TODO + if (pcmChanOffs<0) return 0; + if (chan[ch].sample>=0 && chan[ch].samplesong.sampleLen) { + double off=65535.0*(double)(parent->getSample(chan[ch].sample)->centerRate)/8363.0; + return parent->calcBaseFreq((double)chipClock/768,off,note,false); + } + return 0; +} + void DivPlatformOPL::tick(bool sysTick) { for (int i=0; i=pcmChanOffs) { // OPL4 PCM chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].outVol=VOL_SCALE_LOG((chan[i].vol&0x7f),(0x7f*chan[i].std.vol.val)/chan[i].macroVolMul,0x7f); + immWrite(PCM_ADDR_TL+(i-pcmChanOffs),((0x7f-chan[i].outVol)<<1)|(chan[i].levelDirect?1:0)); + } + + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + DivSample* s=parent->getSample(chan[i].sample); + unsigned char ctrl=0; + double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0; + chan[i].freq=(int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,(524288*768))); + if (chan[i].freq<0x400) chan[i].freq=0x400; + if (chan[i].freq>0x4000000) chan[i].freq=0x4000000; + if (chan[i].freq>=0x2000000) { + chan[i].freqH=15; + } else if (chan[i].freq>=0x1000000) { + chan[i].freqH=14; + } else if (chan[i].freq>=0x800000) { + chan[i].freqH=13; + } else if (chan[i].freq>=0x400000) { + chan[i].freqH=12; + } else if (chan[i].freq>=0x200000) { + chan[i].freqH=11; + } else if (chan[i].freq>=0x100000) { + chan[i].freqH=10; + } else if (chan[i].freq>=0x80000) { + chan[i].freqH=9; + } else if (chan[i].freq>=0x40000) { + chan[i].freqH=8; + } else if (chan[i].freq>=0x20000) { + chan[i].freqH=7; + } else if (chan[i].freq>=0x10000) { + chan[i].freqH=6; + } else if (chan[i].freq>=0x8000) { + chan[i].freqH=5; + } else if (chan[i].freq>=0x4000) { + chan[i].freqH=4; + } else if (chan[i].freq>=0x2000) { + chan[i].freqH=3; + } else if (chan[i].freq>=0x1000) { + chan[i].freqH=2; + } else if (chan[i].freq>=0x800) { + chan[i].freqH=1; + } else { + chan[i].freqH=0; + } + chan[i].freqL=(chan[i].freq>>chan[i].freqH)&0x3ff; + chan[i].freqH=8^chan[i].freqH; + ctrl|=(chan[i].active?0x80:0)|(chan[i].damp?0x40:0)|(isMuted[i]?8:(chan[i].pan&0xf)); + unsigned int waveNum=chan[i].sample; + if (ramSize<=0x200000) { + waveNum=MIN(waveNum,0x7f)|0x180; + } + if (chan[i].keyOn) { + rWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+(i-pcmChanOffs),ctrl&~0x80); // force keyoff first + rWrite(PCM_ADDR_WAVE_H_FN_L+(i-pcmChanOffs),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1)); + rWrite(PCM_ADDR_WAVE_L+(i-pcmChanOffs),waveNum&0xff); + if (!chan[i].std.vol.had) { + chan[i].outVol=chan[i].vol; + } + chan[i].writeCtrl=true; + chan[i].keyOn=false; + } + if (chan[i].keyOff) { + chan[i].writeCtrl=true; + chan[i].keyOff=false; + } + if (chan[i].freqChanged) { + rWrite(PCM_ADDR_WAVE_H_FN_L+(i-pcmChanOffs),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1)); + rWrite(PCM_ADDR_FN_H_PR_OCT+(i-pcmChanOffs),((chan[i].freqH&0xf)<<4)|(chan[i].pseudoReverb?0x08:0x00)|((chan[i].freqL>>7)&0x7)); + chan[i].freqChanged=false; + } + if (chan[i].writeCtrl) { + rWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+(i-pcmChanOffs),ctrl); + chan[i].writeCtrl=false; + } + } } else { int ops=(slots[3][i]!=255 && chan[i].state.ops==4 && oplType==3)?4:2; chan[i].std.next(); @@ -1341,6 +1426,11 @@ int DivPlatformOPL::toFreq(int freq) { void DivPlatformOPL::muteChannel(int ch, bool mute) { isMuted[ch]=mute; + if (ch>=pcmChanOffs) { + chan[ch].freqChanged=true; + chan[ch].writeCtrl=true; + return; + } if (ch==adpcmChan) { immWrite(18,(isMuted[adpcmChan]?0:chan[adpcmChan].outVol)); return; @@ -1494,7 +1584,31 @@ int DivPlatformOPL::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { if (c.chan>=pcmChanOffs) { // OPL4 PCM - + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); + 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_FREQUENCY(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; + } + 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; @@ -1619,6 +1733,10 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; + if (c.chan>=pcmChanOffs) { + chan[c.chan].sample=-1; + chan[c.chan].macroInit(NULL); + } break; case DIV_CMD_NOTE_OFF_ENV: chan[c.chan].keyOff=true; @@ -1639,6 +1757,10 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } + if (c.chan>=pcmChanOffs) { // OPL4 PCM + immWrite(PCM_ADDR_TL+(c.chan-pcmChanOffs),((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; @@ -2275,6 +2397,7 @@ DivDispatchOscBuffer* DivPlatformOPL::getOscBuffer(int ch) { } int DivPlatformOPL::mapVelocity(int ch, float vel) { + if (ch>=pcmChanOffs) return vel*127.0; if (ch==adpcmChan) return vel*255.0; // -0.75dB per step // -6: 64: 8 @@ -2389,8 +2512,9 @@ void DivPlatformOPL::reset() { for (int i=0; i=pcmChanOffs)?0x7f:0x3f; + chan[i].outVol=(i>=pcmChanOffs)?0x7f:0x3f; + chan[i].pan=(i>=pcmChanOffs)?0:3; } if (adpcmChan>=0) { @@ -2746,8 +2870,9 @@ void DivPlatformOPL::renderSamples(int sysID) { 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>511) sampleCount=511; + if (sampleCount>maxSample) sampleCount=maxSample; for (int i=0; isong.sample[i]; if (!s->renderOn[0][sysID]) { @@ -2795,8 +2920,8 @@ void DivPlatformOPL::renderSamples(int sysID) { DivSample* s=parent->song.sample[i]; unsigned int insAddr=(i*12)+((ramSize<=0x200000)?0x200000:0); unsigned char bitDepth; - int loop=CLAMP(s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT),0,0xffff); - int endPos=CLAMP(s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT),1,0x10000); + 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: @@ -2811,13 +2936,13 @@ void DivPlatformOPL::renderSamples(int sysID) { pcmMem[2+insAddr]=(sampleOffPCM[i])&0xff; pcmMem[3+insAddr]=(loop>>8)&0xff; pcmMem[4+insAddr]=(loop)&0xff; - pcmMem[5+insAddr]=(endPos>>8)&0xff; - pcmMem[6+insAddr]=(endPos)&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]=0; // AR, D1R + pcmMem[8+insAddr]=(0xf << 4) | (0xf << 0); // AR, D1R pcmMem[9+insAddr]=0; // DL, D2R - pcmMem[10+insAddr]=0; // RC, RR + pcmMem[10+insAddr]=(0xf << 4) | (0xf << 0); // RC, RR pcmMem[11+insAddr]=0; // AM } if (ramSize<=0x200000) { @@ -2825,7 +2950,6 @@ void DivPlatformOPL::renderSamples(int sysID) { } memCompo.used=pcmMemLen; - memCompo.capacity=getSampleMemCapacity(0); } else if (adpcmChan>=0) { // ADPCM size_t memPos=0; for (int i=0; isong.sampleLen; i++) { @@ -2857,8 +2981,8 @@ void DivPlatformOPL::renderSamples(int sysID) { adpcmBMemLen=memPos+256; memCompo.used=adpcmBMemLen; - memCompo.capacity=getSampleMemCapacity(0); } + memCompo.capacity=getSampleMemCapacity(0); } int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index 90951b824..1394764e7 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -45,10 +45,11 @@ 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; + int pan; int macroVolMul; Channel(): SharedChannel(0), @@ -59,6 +60,10 @@ class DivPlatformOPL: public DivDispatch { furnacePCM(false), fourOp(false), hardReset(false), + writeCtrl(false), + levelDirect(true), + damp(false), + pseudoReverb(false), pan(3), macroVolMul(64) { state.ops=2; @@ -138,6 +143,7 @@ class DivPlatformOPL: public DivDispatch { int octave(int freq); int toFreq(int freq); double NOTE_ADPCMB(int note); + double NOTE_OPL4(int ch, int note); void commitState(int ch, DivInstrument* ins); friend void putDispatchChip(void*,int); From 876234d749cba834aa010265ec994b6f4f88a75b Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 11 Jul 2024 20:26:40 +0900 Subject: [PATCH 04/39] Fix warning --- src/engine/platform/opl.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index f72479ee3..ae978c4f8 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -2912,6 +2912,7 @@ void DivPlatformOPL::renderSamples(int sysID) { logW("out of OPL4 PCM memory for sample %d!",i); break; } + sampleLoaded[i]=true; } pcmMemLen=memPos+256; From 23447488be59471210e0edc1c7fd59636b41c1e6 Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 11 Jul 2024 20:30:05 +0900 Subject: [PATCH 05/39] Fix preset name --- src/gui/presets.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 19a35c6bc..274e2d7c5 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -520,13 +520,13 @@ void FurnaceGUI::initSystemPresets() { } ); SUB_ENTRY( - "MSX + Moonsound", { + "MSX + MoonSound", { CH(DIV_SYSTEM_AY8910, 1.0f, 0, "chipType=1"), CH(DIV_SYSTEM_OPL4, 1.0f, 0, "") } ); SUB_ENTRY( - "MSX + Moonsound (drums mode)", { + "MSX + MoonSound (drums mode)", { CH(DIV_SYSTEM_AY8910, 1.0f, 0, "chipType=1"), CH(DIV_SYSTEM_OPL4_DRUMS, 1.0f, 0, "") } From a8ec76699b710e7ca39a2780c251f3d9d5e21429 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 11 Jul 2024 17:36:22 -0500 Subject: [PATCH 06/39] a gift from tildearrow --- CMakeLists.txt | 1 + src/engine/platform/sound/ymf278b/License.txt | 339 ++++++ src/engine/platform/sound/ymf278b/ymf278.cpp | 1084 +++++++++++++++++ src/engine/platform/sound/ymf278b/ymf278.h | 154 +++ 4 files changed, 1578 insertions(+) create mode 100644 src/engine/platform/sound/ymf278b/License.txt create mode 100644 src/engine/platform/sound/ymf278b/ymf278.cpp create mode 100644 src/engine/platform/sound/ymf278b/ymf278.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a08f9d9f5..3c2d8853f 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/src/engine/platform/sound/ymf278b/License.txt b/src/engine/platform/sound/ymf278b/License.txt new file mode 100644 index 000000000..d159169d1 --- /dev/null +++ b/src/engine/platform/sound/ymf278b/License.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + 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..ae4529985 --- /dev/null +++ b/src/engine/platform/sound/ymf278b/ymf278.cpp @@ -0,0 +1,1084 @@ +// 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. + +#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& left, short& right, short* channelBufs) +{ + int sampleLeft = 0; + int sampleRight = 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); + + sampleLeft += (smplOut * volLeft ) >> 5; + sampleRight += (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(); + + left = sampleLeft >> 4; + right = sampleRight >> 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: + if (data & 0x10) { + // output to DO1 pin: + // this pin is not used in moonsound + // we emulate this by muting the sound + slot.pan = 8; // both left/right -inf dB + } else { + 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..f02b179aa --- /dev/null +++ b/src/engine/platform/sound/ymf278b/ymf278.h @@ -0,0 +1,154 @@ +#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& left, short& right, 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 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& bufL, short& bufR, short* channelBufs = nullptr) { + generate(bufL, bufR, channelBufs); + bufL = std::min(std::max((pcmMixL * bufL + fmMixL * fmL) >> 4, -0x8000), 0x7fff); + bufR = std::min(std::max((pcmMixR * bufR + 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 From 221fa5aa421babb9fe7e18f38d5e6deb5b911621 Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 12 Jul 2024 12:16:24 +0900 Subject: [PATCH 07/39] Some fleshing out YMF278B Add OpenMSX YMF278B core option, Expand RAM size option --- src/engine/dispatchContainer.cpp | 14 +- src/engine/platform/opl.cpp | 246 +++++++++++++------ src/engine/platform/opl.h | 20 +- src/engine/platform/oplAInterface.cpp | 7 + src/engine/platform/sound/ymf278b/ymf278.cpp | 40 +-- src/engine/platform/sound/ymf278b/ymf278.h | 11 +- src/gui/about.cpp | 1 + src/gui/gui.h | 4 + src/gui/settings.cpp | 23 ++ src/gui/sysConf.cpp | 12 +- src/main.cpp | 1 + 11 files changed, 275 insertions(+), 104 deletions(-) diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 9bb0abc75..d1efa70ef 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -763,14 +763,20 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_OPL4: dispatch=new DivPlatformOPL; ((DivPlatformOPL*)dispatch)->setOPLType(4,false); - // YMFM for now - ((DivPlatformOPL*)dispatch)->setCore(1); + 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); - // YMFM for now - ((DivPlatformOPL*)dispatch)->setCore(1); + 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; diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index ae978c4f8..57ed459a4 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 @@ -176,36 +182,42 @@ const int orderedOpsL[4]={ #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; 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(); } @@ -214,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]; + os[1]+=o[5]; + os[2]+=o[2]; + os[3]+=o[3]; + os[4]+=o[6]; + os[5]+=o[7]; + } else { + os[0]+=o[0]; + os[1]+=o[1]; + os[2]+=o[2]; + os[3]+=o[3]; + } if (adpcmChan>=0) { adpcmB->clock(); @@ -290,6 +312,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]; @@ -301,9 +329,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]; } } } @@ -947,22 +974,44 @@ double DivPlatformOPL::NOTE_ADPCMB(int note) { return 0; } -double DivPlatformOPL::NOTE_OPL4(int ch, int note) { // TODO - if (pcmChanOffs<0) return 0; - if (chan[ch].sample>=0 && chan[ch].samplesong.sampleLen) { - double off=65535.0*(double)(parent->getSample(chan[ch].sample)->centerRate)/8363.0; - return parent->calcBaseFreq((double)chipClock/768,off,note,false); - } - return 0; -} - void DivPlatformOPL::tick(bool sysTick) { for (int i=0; i=pcmChanOffs) { // OPL4 PCM + if (PCM_CHECK(i)) { // OPL4 PCM chan[i].std.next(); if (chan[i].std.vol.had) { chan[i].outVol=VOL_SCALE_LOG((chan[i].vol&0x7f),(0x7f*chan[i].std.vol.val)/chan[i].macroVolMul,0x7f); - immWrite(PCM_ADDR_TL+(i-pcmChanOffs),((0x7f-chan[i].outVol)<<1)|(chan[i].levelDirect?1:0)); + immWrite(PCM_ADDR_TL+(PCM_REG(i)),((0x7f-chan[i].outVol)<<1)|(chan[i].levelDirect?1:0)); + } + + if (NEW_ARP_STRAT) { + chan[i].handleArp(); + } else if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + chan[i].baseFreq=NOTE_PCM(parent->calcArp(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; + } + } + + 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].freqChanged || chan[i].keyOn || chan[i].keyOff) { @@ -1013,11 +1062,12 @@ void DivPlatformOPL::tick(bool sysTick) { waveNum=MIN(waveNum,0x7f)|0x180; } if (chan[i].keyOn) { - rWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+(i-pcmChanOffs),ctrl&~0x80); // force keyoff first - rWrite(PCM_ADDR_WAVE_H_FN_L+(i-pcmChanOffs),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1)); - rWrite(PCM_ADDR_WAVE_L+(i-pcmChanOffs),waveNum&0xff); + rWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+PCM_REG(i),ctrl&~0x80); // force keyoff first + rWrite(PCM_ADDR_WAVE_H_FN_L+PCM_REG(i),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1)); + rWrite(PCM_ADDR_WAVE_L+PCM_REG(i),waveNum&0xff); if (!chan[i].std.vol.had) { chan[i].outVol=chan[i].vol; + immWrite(PCM_ADDR_TL+(PCM_REG(i)),((0x7f-chan[i].outVol)<<1)|(chan[i].levelDirect?1:0)); } chan[i].writeCtrl=true; chan[i].keyOn=false; @@ -1027,12 +1077,12 @@ void DivPlatformOPL::tick(bool sysTick) { chan[i].keyOff=false; } if (chan[i].freqChanged) { - rWrite(PCM_ADDR_WAVE_H_FN_L+(i-pcmChanOffs),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1)); - rWrite(PCM_ADDR_FN_H_PR_OCT+(i-pcmChanOffs),((chan[i].freqH&0xf)<<4)|(chan[i].pseudoReverb?0x08:0x00)|((chan[i].freqL>>7)&0x7)); + rWrite(PCM_ADDR_WAVE_H_FN_L+PCM_REG(i),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1)); + rWrite(PCM_ADDR_FN_H_PR_OCT+PCM_REG(i),((chan[i].freqH&0xf)<<4)|(chan[i].pseudoReverb?0x08:0x00)|((chan[i].freqL>>7)&0x7)); chan[i].freqChanged=false; } if (chan[i].writeCtrl) { - rWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+(i-pcmChanOffs),ctrl); + rWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+PCM_REG(i),ctrl); chan[i].writeCtrl=false; } } @@ -1309,7 +1359,7 @@ void DivPlatformOPL::tick(bool sysTick) { bool updateDrums=false; for (int i=0; i=pcmChanOffs) { // OPL4 PCM + if (PCM_CHECK(i)) { // OPL4 PCM } else { if (chan[i].freqChanged) { @@ -1426,7 +1476,7 @@ int DivPlatformOPL::toFreq(int freq) { void DivPlatformOPL::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - if (ch>=pcmChanOffs) { + if (PCM_CHECK(ch)) { chan[ch].freqChanged=true; chan[ch].writeCtrl=true; return; @@ -1479,6 +1529,9 @@ void DivPlatformOPL::muteChannel(int ch, bool mute) { } void DivPlatformOPL::commitState(int ch, DivInstrument* ins) { + if (PCM_CHECK(ch)) { + return; + } if (chan[ch].insChanged) { if (ch>melodicChans && ins->type==DIV_INS_OPL_DRUMS) { for (int i=0; i<4; i++) { @@ -1583,7 +1636,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { } switch (c.cmd) { case DIV_CMD_NOTE_ON: { - if (c.chan>=pcmChanOffs) { // OPL4 PCM + if (PCM_CHECK(c.chan)) { // OPL4 PCM DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:127; if (c.value!=DIV_NOTE_NULL) { @@ -1593,7 +1646,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote; } if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + 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; @@ -1733,7 +1786,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; - if (c.chan>=pcmChanOffs) { + if (PCM_CHECK(c.chan)) { chan[c.chan].sample=-1; chan[c.chan].macroInit(NULL); } @@ -1757,8 +1810,8 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } - if (c.chan>=pcmChanOffs) { // OPL4 PCM - immWrite(PCM_ADDR_TL+(c.chan-pcmChanOffs),((0x7f-chan[c.chan].outVol)<<1)|(chan[c.chan].levelDirect?1:0)); + 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 @@ -1798,6 +1851,12 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { + if (PCM_CHECK(c.chan)) { + 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; @@ -1856,6 +1915,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; @@ -1901,13 +1983,14 @@ int DivPlatformOPL::dispatch(DivCommand c) { 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): + (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 (c.chan>=pcmChanOffs) break; + if (PCM_CHECK(c.chan)) break; if (c.chan==adpcmChan) break; if (c.value&2) { dvb=c.value&1; @@ -1918,7 +2001,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_FB: { - if (c.chan>=pcmChanOffs) break; + 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; @@ -1936,7 +2019,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_MULT: { - if (c.chan>=pcmChanOffs) break; + 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; @@ -1949,7 +2032,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_TL: { - if (c.chan>=pcmChanOffs) break; + 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; @@ -1970,7 +2053,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_AR: { - if (c.chan>=pcmChanOffs) break; + 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) { @@ -1994,7 +2077,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_DR: { - if (c.chan>=pcmChanOffs) break; + 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) { @@ -2018,7 +2101,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_SL: { - if (c.chan>=pcmChanOffs) break; + 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) { @@ -2042,7 +2125,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_RR: { - if (c.chan>=pcmChanOffs) break; + 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) { @@ -2066,7 +2149,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_AM: { - if (c.chan>=pcmChanOffs) break; + 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) { @@ -2090,7 +2173,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_VIB: { - if (c.chan>=pcmChanOffs) break; + 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) { @@ -2114,7 +2197,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_SUS: { - if (c.chan>=pcmChanOffs) break; + 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) { @@ -2138,7 +2221,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_KSR: { - if (c.chan>=pcmChanOffs) break; + 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) { @@ -2162,7 +2245,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_WS: { - if (c.chan>=pcmChanOffs) break; + 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; @@ -2187,7 +2270,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_RS: { - if (c.chan>=pcmChanOffs) break; + 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; @@ -2261,7 +2344,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].std.restart(c.value); break; case DIV_CMD_GET_VOLMAX: - if (c.chan>=pcmChanOffs) return 127; + if (PCM_CHECK(c.chan)) return 127; if (c.chan==adpcmChan) return 255; if (pretendYMU) return 127; return 63; @@ -2397,7 +2480,7 @@ DivDispatchOscBuffer* DivPlatformOPL::getOscBuffer(int ch) { } int DivPlatformOPL::mapVelocity(int ch, float vel) { - if (ch>=pcmChanOffs) return vel*127.0; + if (PCM_CHECK(ch)) return vel*127.0; if (ch==adpcmChan) return vel*255.0; // -0.75dB per step // -6: 64: 8 @@ -2487,6 +2570,7 @@ void DivPlatformOPL::reset() { OPL3_Reset(&fm,rate); } } + pcm.reset(); if (dumpWrites) { addWrite(0xffffffff,0); @@ -2512,9 +2596,9 @@ void DivPlatformOPL::reset() { for (int i=0; i=pcmChanOffs)?0x7f:0x3f; - chan[i].outVol=(i>=pcmChanOffs)?0x7f:0x3f; - chan[i].pan=(i>=pcmChanOffs)?0:3; + chan[i].vol=(PCM_CHECK(i))?0x7f:0x3f; + chan[i].outVol=(PCM_CHECK(i))?0x7f:0x3f; + chan[i].pan=(PCM_CHECK(i))?0:3; } if (adpcmChan>=0) { @@ -2791,16 +2875,22 @@ void DivPlatformOPL::setFlags(const DivConfig& flags) { break; } switch (flags.getInt("ramSize",0)) { - case 0x01: + case 0x01: // 2MB (512KB 512KB 512KB 512KB) ramSize=0x200000; break; - case 0x02: + case 0x02: // 1MB (512KB 512KB) ramSize=0x100000; break; - case 0x03: + case 0x03: // 640KB (512KB 128KB) + ramSize=0xa0000; + break; + case 0x04: // 512KB ramSize=0x80000; break; - case 0x04: + case 0x05: // 256KB (128KB 128KB) + ramSize=0x40000; + break; + case 0x06: // 128KB ramSize=0x20000; break; default: @@ -2808,6 +2898,7 @@ void DivPlatformOPL::setFlags(const DivConfig& flags) { break; } CHECK_CUSTOM_CLOCK; + pcm.setClockFrequency(chipClock); rate=chipClock/768; chipRateBase=chipClock/684; immWrite(0x202,(ramSize<=0x200000)?0x10:0x00); @@ -3038,6 +3129,7 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, const DivConfi pcmMemLen=0; iface.pcmMem=pcmMem; iface.sampleBank=0; + pcmMemory.memory=pcmMem; } reset(); diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index 1394764e7..708d30b51 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -30,6 +30,7 @@ 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: @@ -41,6 +42,18 @@ class DivOPLAInterface: public ymfm::ymfm_interface { 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 { @@ -96,6 +109,7 @@ class DivPlatformOPL: public DivDispatch { unsigned char* pcmMem; size_t pcmMemLen; DivOPLAInterface iface; + DivYMF278MemoryInterface pcmMemory; unsigned int sampleOffB[256]; unsigned int sampleOffPCM[256]; bool sampleLoaded[256]; @@ -130,6 +144,7 @@ class DivPlatformOPL: public DivDispatch { // chips opl3_chip fm; + YMF278 pcm; ymfm::ym3526* fm_ymfm1; ymfm::ym3812* fm_ymfm2; ymfm::y8950* fm_ymfm8950; @@ -143,7 +158,6 @@ class DivPlatformOPL: public DivDispatch { int octave(int freq); int toFreq(int freq); double NOTE_ADPCMB(int note); - double NOTE_OPL4(int ch, int note); void commitState(int ch, DivInstrument* ins); friend void putDispatchChip(void*,int); @@ -194,6 +208,10 @@ class DivPlatformOPL: public DivDispatch { void renderSamples(int chipID); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); + YMF278& getChip(); + DivPlatformOPL(): + pcmMemory(0x400000), + pcm(pcmMemory) {} ~DivPlatformOPL(); }; #endif diff --git a/src/engine/platform/oplAInterface.cpp b/src/engine/platform/oplAInterface.cpp index 286385806..46d39b869 100644 --- a/src/engine/platform/oplAInterface.cpp +++ b/src/engine/platform/oplAInterface.cpp @@ -41,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 #include @@ -476,10 +480,12 @@ static constexpr int vol_factor(int x, unsigned envVol) return (x * ((0x8000 * vol_mul) >> vol_shift)) >> 15; } -void YMF278Base::generate(short& left, short& right, short* channelBufs) +void YMF278Base::generate(short& fleft, short& fright, short& rleft, short& rright, short* channelBufs) { - int sampleLeft = 0; - int sampleRight = 0; + 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) { @@ -512,8 +518,16 @@ void YMF278Base::generate(short& left, short& right, short* channelBufs) volLeft = (0x20 - (volLeft & 0x0f)) >> (volLeft >> 4); volRight = (0x20 - (volRight & 0x0f)) >> (volRight >> 4); - sampleLeft += (smplOut * volLeft ) >> 5; - sampleRight += (smplOut * volRight) >> 5; + 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()) @@ -531,8 +545,10 @@ void YMF278Base::generate(short& left, short& right, short* channelBufs) } advance(); - left = sampleLeft >> 4; - right = sampleRight >> 4; + fleft = sampleFLeft >> 4; + fright = sampleFRight >> 4; + rleft = sampleRLeft >> 4; + rright = sampleRRight >> 4; } void YMF278Base::keyOnHelper(Slot& slot) @@ -683,14 +699,8 @@ void YMF278::writeReg(byte reg, byte data) break; } case 4: - if (data & 0x10) { - // output to DO1 pin: - // this pin is not used in moonsound - // we emulate this by muting the sound - slot.pan = 8; // both left/right -inf dB - } else { - slot.pan = data & 0x0F; - } + slot.ch = data & 0x10; + slot.pan = data & 0x0F; if (data & 0x20) { // LFO reset diff --git a/src/engine/platform/sound/ymf278b/ymf278.h b/src/engine/platform/sound/ymf278b/ymf278.h index f02b179aa..b0622adb2 100644 --- a/src/engine/platform/sound/ymf278b/ymf278.h +++ b/src/engine/platform/sound/ymf278b/ymf278.h @@ -35,7 +35,7 @@ public: double getSampleRate(); virtual void reset(); - void generate(short& left, short& right, short* channelBufs = nullptr); + void generate(short& fleft, short& fright, short& rleft, short& rright, short* channelBufs = nullptr); class Slot final { public: @@ -71,6 +71,7 @@ public: 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 @@ -115,10 +116,10 @@ public: byte readReg(byte reg); byte peekReg(byte reg) const; - void generateMix(short fmL, short fmR, short& bufL, short& bufR, short* channelBufs = nullptr) { - generate(bufL, bufR, channelBufs); - bufL = std::min(std::max((pcmMixL * bufL + fmMixL * fmL) >> 4, -0x8000), 0x7fff); - bufR = std::min(std::max((pcmMixR * bufR + fmMixR * fmR) >> 4, -0x8000), 0x7fff);; + 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: diff --git a/src/gui/about.cpp b/src/gui/about.cpp index fc9775368..18be5f5d0 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -294,6 +294,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:"), "NEOART Costa Rica", diff --git a/src/gui/gui.h b/src/gui/gui.h index d168a8a0c..51161c224 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1737,6 +1737,7 @@ class FurnaceGUI { int opnbCore; int opl2Core; int opl3Core; + int opl4Core; int esfmCore; int opllCore; int ayCore; @@ -1763,6 +1764,7 @@ class FurnaceGUI { int opnbCoreRender; int opl2CoreRender; int opl3CoreRender; + int opl4CoreRender; int esfmCoreRender; int opllCoreRender; int ayCoreRender; @@ -1995,6 +1997,7 @@ class FurnaceGUI { opnbCore(1), opl2Core(0), opl3Core(0), + opl4Core(0), esfmCore(0), opllCore(0), ayCore(0), @@ -2021,6 +2024,7 @@ class FurnaceGUI { opnbCoreRender(1), opl2CoreRender(0), opl3CoreRender(0), + opl4CoreRender(0), esfmCoreRender(0), opllCoreRender(0), ayCoreRender(0), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 0baee2725..63815cd02 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 + OpenMSX", + "ymfm" +}; + const char* esfmCores[]={ "ESFMu", _N("ESFMu (fast)") @@ -2046,6 +2051,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(); @@ -4959,6 +4975,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); @@ -4987,6 +5004,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); @@ -5032,6 +5050,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); @@ -5058,6 +5077,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); @@ -5543,6 +5563,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); @@ -5571,6 +5592,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); @@ -5634,6 +5656,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 0c9848681..085368b95 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -2538,14 +2538,22 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl ramSize=2; altered=true; } - if (ImGui::RadioButton(_("512KB"),ramSize==3)) { + if (ImGui::RadioButton(_("640KB"),ramSize==3)) { ramSize=3; altered=true; } - if (ImGui::RadioButton(_("128KB"),ramSize==4)) { + 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) { diff --git a/src/main.cpp b/src/main.cpp index 9c3dc6a9e..d6ba74bc2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -333,6 +333,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; } From 65f48cc5748c7875129fb974c698617bc5672488 Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 12 Jul 2024 17:37:27 +0900 Subject: [PATCH 08/39] Add instrument for OPL4 PCM, Macros and Effects TODO: Phase reset, Keyon/off --- src/engine/dispatch.h | 16 ++ src/engine/engine.cpp | 3 +- src/engine/instrument.cpp | 46 +++++ src/engine/instrument.h | 20 +++ src/engine/platform/opl.cpp | 323 ++++++++++++++++++++++++++++-------- src/engine/platform/opl.h | 15 +- src/engine/playback.cpp | 18 +- src/engine/sysDef.cpp | 31 +++- src/gui/doAction.cpp | 6 +- src/gui/gui.h | 1 + src/gui/guiConst.cpp | 2 + src/gui/insEdit.cpp | 29 +++- src/gui/settings.cpp | 1 + 13 files changed, 423 insertions(+), 88 deletions(-) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 6757fe1e8..a3ed1400c 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -263,6 +263,22 @@ enum DivDispatchCmds { DIV_CMD_BIFURCATOR_STATE_LOAD, DIV_CMD_BIFURCATOR_PARAMETER, + DIV_CMD_OPL4_PCM_MIX_FM, // (value) + DIV_CMD_OPL4_PCM_MIX_PCM, // (value) + DIV_CMD_OPL4_PCM_LFO, // (value) + DIV_CMD_OPL4_PCM_VIB, // (value) + DIV_CMD_OPL4_PCM_AM, // (value) + DIV_CMD_OPL4_PCM_AR, // (value) + DIV_CMD_OPL4_PCM_D1R, // (value) + DIV_CMD_OPL4_PCM_DL, // (value) + DIV_CMD_OPL4_PCM_D2R, // (value) + DIV_CMD_OPL4_PCM_RC, // (value) + DIV_CMD_OPL4_PCM_RR, // (value) + DIV_CMD_OPL4_PCM_DAMP, // (value) + DIV_CMD_OPL4_PCM_PSEUDO_REVERB, // (value) + DIV_CMD_OPL4_PCM_LFO_RESET, // (value) + DIV_CMD_OPL4_PCM_LEVEL_DIRECT, // (value) + DIV_CMD_MAX }; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 7ca58611b..b024257e4 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -938,7 +938,8 @@ void DivEngine::delUnusedSamples() { i->type==DIV_INS_C219 || i->type==DIV_INS_NDS || i->type==DIV_INS_GBA_DMA || - i->type==DIV_INS_GBA_MINMOD) { + i->type==DIV_INS_GBA_MINMOD || + i->type==DIV_INS_OPL4PCM) { if (i->amiga.initSample>=0 && i->amiga.initSampleamiga.initSample]=true; } diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index d6407ec6c..039721445 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -178,6 +178,15 @@ bool DivInstrumentMultiPCM::operator==(const DivInstrumentMultiPCM& other) { ); } +bool DivInstrumentOPL4PCM::operator==(const DivInstrumentOPL4PCM& other) { + return ( + _C(damp) && + _C(pseudoReverb) && + _C(lfoReset) && + _C(levelDirect) + ); +} + bool DivInstrumentWaveSynth::operator==(const DivInstrumentWaveSynth& other) { return ( _C(wave1) && @@ -848,6 +857,17 @@ void DivInstrument::writeFeatureS2(SafeWriter* w) { FEATURE_END; } +void DivInstrument::writeFeatureO4(SafeWriter* w) { + FEATURE_BEGIN("O4"); + + w->writeC(opl4pcm.damp); + w->writeC(opl4pcm.pseudoReverb); + w->writeC(opl4pcm.lfoReset); + w->writeC(opl4pcm.levelDirect); + + FEATURE_END; +} + void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bool insName) { size_t blockStartSeek=0; size_t blockEndSeek=0; @@ -894,6 +914,7 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo bool featureEF=false; bool featurePN=false; bool featureS2=false; + bool featureO4=false; bool checkForWL=false; @@ -1137,6 +1158,12 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo feature64=true; featureS2=true; break; + case DIV_INS_OPL4PCM: + featureSM=true; + featureSL=true; + featureMP=true; + featureO4=true; + break; case DIV_INS_MAX: break; case DIV_INS_NULL: @@ -1193,6 +1220,9 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo if (sid2!=defaultIns.sid2) { featureS2=true; } + if (opl4pcm!=defaultIns.opl4pcm) { + featureO4=true; + } } // check ins name @@ -1344,6 +1374,9 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo if (featureS2) { writeFeatureS2(w); } + if (featureO4) { + writeFeatureO4(w); + } if (fui && (featureSL || featureWL)) { w->write("EN",2); @@ -2172,6 +2205,17 @@ void DivInstrument::readFeatureS2(SafeReader& reader, short version) { READ_FEAT_END; } +void DivInstrument::readFeatureO4(SafeReader& reader, short version) { + READ_FEAT_BEGIN; + + opl4pcm.damp=reader.readC(); + opl4pcm.pseudoReverb=reader.readC(); + opl4pcm.lfoReset=reader.readC(); + opl4pcm.levelDirect=reader.readC(); + + READ_FEAT_END; +} + DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, bool fui, DivSong* song) { unsigned char featCode[2]; bool volIsCutoff=false; @@ -2246,6 +2290,8 @@ DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, b readFeaturePN(reader,version); } else if (memcmp(featCode,"S2",2)==0) { // SID2 readFeatureS2(reader,version); + } else if (memcmp(featCode,"O4",2)==0) { // OPL4 PCM + readFeatureO4(reader,version); } else { if (song==NULL && (memcmp(featCode,"SL",2)==0 || (memcmp(featCode,"WL",2)==0))) { // nothing diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 49b2991d4..f37a61c0a 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -94,6 +94,7 @@ enum DivInstrumentType: unsigned short { DIV_INS_GBA_MINMOD=61, DIV_INS_BIFURCATOR=62, DIV_INS_SID2=63, // coincidence! + DIV_INS_OPL4PCM=64, DIV_INS_MAX, DIV_INS_NULL }; @@ -620,6 +621,22 @@ struct DivInstrumentMultiPCM { } }; +struct DivInstrumentOPL4PCM { + bool damp, pseudoReverb, lfoReset, levelDirect; + + bool operator==(const DivInstrumentOPL4PCM& other); + bool operator!=(const DivInstrumentOPL4PCM& other) { + return !(*this==other); + } + + DivInstrumentOPL4PCM(): + damp(false), + pseudoReverb(false), + lfoReset(false), + levelDirect(true) { + } +}; + enum DivWaveSynthEffects { DIV_WS_NONE=0, // one waveform effects @@ -879,6 +896,7 @@ struct DivInstrument { DivInstrumentESFM esfm; DivInstrumentPowerNoise powernoise; DivInstrumentSID2 sid2; + DivInstrumentOPL4PCM opl4pcm; /** * these are internal functions. @@ -906,6 +924,7 @@ struct DivInstrument { void writeFeatureEF(SafeWriter* w); void writeFeaturePN(SafeWriter* w); void writeFeatureS2(SafeWriter* w); + void writeFeatureO4(SafeWriter* w); void readFeatureNA(SafeReader& reader, short version); void readFeatureFM(SafeReader& reader, short version); @@ -929,6 +948,7 @@ struct DivInstrument { void readFeatureEF(SafeReader& reader, short version); void readFeaturePN(SafeReader& reader, short version); void readFeatureS2(SafeReader& reader, short version); + void readFeatureO4(SafeReader& reader, short version); DivDataErrors readInsDataOld(SafeReader& reader, short version); DivDataErrors readInsDataNew(SafeReader& reader, short version, bool fui, DivSong* song); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 57ed459a4..d836379c0 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -1002,9 +1002,10 @@ void DivPlatformOPL::tick(bool sysTick) { chan[i].freqChanged=true; } - if (chan[i].std.phaseReset.had) { + if (chan[i].std.phaseReset.had) { // TODO: not working if (chan[i].std.phaseReset.val==1 && chan[i].active) { chan[i].keyOn=true; + chan[i].writeCtrl=true; } } @@ -1014,78 +1015,51 @@ void DivPlatformOPL::tick(bool sysTick) { chan[i].writeCtrl=true; } - if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - DivSample* s=parent->getSample(chan[i].sample); - unsigned char ctrl=0; - double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0; - chan[i].freq=(int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,(524288*768))); - if (chan[i].freq<0x400) chan[i].freq=0x400; - if (chan[i].freq>0x4000000) chan[i].freq=0x4000000; - if (chan[i].freq>=0x2000000) { - chan[i].freqH=15; - } else if (chan[i].freq>=0x1000000) { - chan[i].freqH=14; - } else if (chan[i].freq>=0x800000) { - chan[i].freqH=13; - } else if (chan[i].freq>=0x400000) { - chan[i].freqH=12; - } else if (chan[i].freq>=0x200000) { - chan[i].freqH=11; - } else if (chan[i].freq>=0x100000) { - chan[i].freqH=10; - } else if (chan[i].freq>=0x80000) { - chan[i].freqH=9; - } else if (chan[i].freq>=0x40000) { - chan[i].freqH=8; - } else if (chan[i].freq>=0x20000) { - chan[i].freqH=7; - } else if (chan[i].freq>=0x10000) { - chan[i].freqH=6; - } else if (chan[i].freq>=0x8000) { - chan[i].freqH=5; - } else if (chan[i].freq>=0x4000) { - chan[i].freqH=4; - } else if (chan[i].freq>=0x2000) { - chan[i].freqH=3; - } else if (chan[i].freq>=0x1000) { - chan[i].freqH=2; - } else if (chan[i].freq>=0x800) { - chan[i].freqH=1; - } else { - chan[i].freqH=0; - } - chan[i].freqL=(chan[i].freq>>chan[i].freqH)&0x3ff; - chan[i].freqH=8^chan[i].freqH; - ctrl|=(chan[i].active?0x80:0)|(chan[i].damp?0x40:0)|(isMuted[i]?8:(chan[i].pan&0xf)); - unsigned int waveNum=chan[i].sample; - if (ramSize<=0x200000) { - waveNum=MIN(waveNum,0x7f)|0x180; - } - if (chan[i].keyOn) { - rWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+PCM_REG(i),ctrl&~0x80); // force keyoff first - rWrite(PCM_ADDR_WAVE_H_FN_L+PCM_REG(i),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1)); - rWrite(PCM_ADDR_WAVE_L+PCM_REG(i),waveNum&0xff); - if (!chan[i].std.vol.had) { - chan[i].outVol=chan[i].vol; - immWrite(PCM_ADDR_TL+(PCM_REG(i)),((0x7f-chan[i].outVol)<<1)|(chan[i].levelDirect?1:0)); - } - chan[i].writeCtrl=true; - chan[i].keyOn=false; - } - if (chan[i].keyOff) { - chan[i].writeCtrl=true; - chan[i].keyOff=false; - } - if (chan[i].freqChanged) { - rWrite(PCM_ADDR_WAVE_H_FN_L+PCM_REG(i),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1)); - rWrite(PCM_ADDR_FN_H_PR_OCT+PCM_REG(i),((chan[i].freqH&0xf)<<4)|(chan[i].pseudoReverb?0x08:0x00)|((chan[i].freqL>>7)&0x7)); - chan[i].freqChanged=false; - } - if (chan[i].writeCtrl) { - rWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+PCM_REG(i),ctrl); - chan[i].writeCtrl=false; - } + if (chan[i].std.ex1.had) { + chan[i].lfo=chan[i].std.ex1.val&0x7; + rWrite(PCM_ADDR_LFO_VIB+PCM_REG(i),(chan[i].lfo<<3)|(chan[i].vib)); } + + if (chan[i].std.fms.had) { + chan[i].vib=chan[i].std.fms.val&0x7; + rWrite(PCM_ADDR_LFO_VIB+PCM_REG(i),(chan[i].lfo<<3)|(chan[i].vib)); + } + + if (chan[i].std.ams.had) { + chan[i].am=chan[i].std.ams.val&0x7; + rWrite(PCM_ADDR_AM+PCM_REG(i),chan[i].am); + } + + if (chan[i].std.ex2.had) { + chan[i].ar=chan[i].std.ex2.val&0xf; + rWrite(PCM_ADDR_AR_D1R+PCM_REG(i),(chan[i].ar<<4)|(chan[i].d1r)); + } + + if (chan[i].std.ex3.had) { + chan[i].d1r=chan[i].std.ex3.val&0xf; + rWrite(PCM_ADDR_AR_D1R+PCM_REG(i),(chan[i].ar<<4)|(chan[i].d1r)); + } + + if (chan[i].std.ex4.had) { + chan[i].dl=chan[i].std.ex4.val&0xf; + rWrite(PCM_ADDR_DL_D2R+PCM_REG(i),(chan[i].dl<<4)|(chan[i].d2r)); + } + + if (chan[i].std.ex5.had) { + chan[i].d2r=chan[i].std.ex5.val&0xf; + rWrite(PCM_ADDR_DL_D2R+PCM_REG(i),(chan[i].dl<<4)|(chan[i].d2r)); + } + + if (chan[i].std.ex6.had) { + chan[i].rc=chan[i].std.ex6.val&0xf; + rWrite(PCM_ADDR_RC_RR+PCM_REG(i),(chan[i].rc<<4)|(chan[i].rr)); + } + + if (chan[i].std.ex7.had) { + chan[i].rr=chan[i].std.ex7.val&0xf; + rWrite(PCM_ADDR_RC_RR+PCM_REG(i),(chan[i].rc<<4)|(chan[i].rr)); + } + } else { int ops=(slots[3][i]!=255 && chan[i].state.ops==4 && oplType==3)?4:2; chan[i].std.next(); @@ -1360,7 +1334,83 @@ void DivPlatformOPL::tick(bool sysTick) { bool updateDrums=false; for (int i=0; igetSample(chan[i].sample); + unsigned char ctrl=0; + double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0; + chan[i].freq=(int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,(524288*768))); + if (chan[i].freq<0x400) chan[i].freq=0x400; + if (chan[i].freq>0x4000000) chan[i].freq=0x4000000; + if (chan[i].freq>=0x2000000) { + chan[i].freqH=15; + } else if (chan[i].freq>=0x1000000) { + chan[i].freqH=14; + } else if (chan[i].freq>=0x800000) { + chan[i].freqH=13; + } else if (chan[i].freq>=0x400000) { + chan[i].freqH=12; + } else if (chan[i].freq>=0x200000) { + chan[i].freqH=11; + } else if (chan[i].freq>=0x100000) { + chan[i].freqH=10; + } else if (chan[i].freq>=0x80000) { + chan[i].freqH=9; + } else if (chan[i].freq>=0x40000) { + chan[i].freqH=8; + } else if (chan[i].freq>=0x20000) { + chan[i].freqH=7; + } else if (chan[i].freq>=0x10000) { + chan[i].freqH=6; + } else if (chan[i].freq>=0x8000) { + chan[i].freqH=5; + } else if (chan[i].freq>=0x4000) { + chan[i].freqH=4; + } else if (chan[i].freq>=0x2000) { + chan[i].freqH=3; + } else if (chan[i].freq>=0x1000) { + chan[i].freqH=2; + } else if (chan[i].freq>=0x800) { + chan[i].freqH=1; + } else { + chan[i].freqH=0; + } + chan[i].freqL=(chan[i].freq>>chan[i].freqH)&0x3ff; + chan[i].freqH=8^chan[i].freqH; + ctrl|=(chan[i].active?0x80:0)|(chan[i].damp?0x40:0)|(chan[i].lfoReset?0x20:0)|(chan[i].ch?0x10:0)|(isMuted[i]?8:(chan[i].pan&0xf)); + unsigned int waveNum=chan[i].sample; + if (ramSize<=0x200000) { + waveNum=CLAMP(waveNum,0,0x7f)|0x180; + } + if (chan[i].keyOn) { + rWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+PCM_REG(i),ctrl&~0x80); // force keyoff first + rWrite(PCM_ADDR_WAVE_H_FN_L+PCM_REG(i),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1)); + rWrite(PCM_ADDR_WAVE_L+PCM_REG(i),waveNum&0xff); + rWrite(PCM_ADDR_LFO_VIB+PCM_REG(i),(chan[i].lfo<<3)|(chan[i].vib)); + rWrite(PCM_ADDR_AR_D1R+PCM_REG(i),(chan[i].ar<<4)|(chan[i].d1r)); + rWrite(PCM_ADDR_DL_D2R+PCM_REG(i),(chan[i].dl<<4)|(chan[i].d2r)); + rWrite(PCM_ADDR_RC_RR+PCM_REG(i),(chan[i].rc<<4)|(chan[i].rr)); + rWrite(PCM_ADDR_AM+PCM_REG(i),chan[i].am); + if (!chan[i].std.vol.had) { + chan[i].outVol=chan[i].vol; + immWrite(PCM_ADDR_TL+(PCM_REG(i)),((0x7f-chan[i].outVol)<<1)|(chan[i].levelDirect?1:0)); + } + chan[i].writeCtrl=true; + chan[i].keyOn=false; + } + if (chan[i].keyOff) { + chan[i].writeCtrl=true; + chan[i].keyOff=false; + } + if (chan[i].freqChanged) { + rWrite(PCM_ADDR_WAVE_H_FN_L+PCM_REG(i),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1)); + rWrite(PCM_ADDR_FN_H_PR_OCT+PCM_REG(i),((chan[i].freqH&0xf)<<4)|(chan[i].pseudoReverb?0x08:0x00)|((chan[i].freqL>>7)&0x7)); + chan[i].freqChanged=false; + } + if (chan[i].writeCtrl) { + rWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+PCM_REG(i),ctrl); + chan[i].writeCtrl=false; + } + } } else { if (chan[i].freqChanged) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,octave(chan[i].baseFreq)*2,chan[i].pitch2,chipClock,CHIP_FREQBASE); @@ -1655,6 +1705,35 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; } + if (ins->type==DIV_INS_OPL4PCM) { + chan[c.chan].lfo=ins->multipcm.lfo; + chan[c.chan].vib=ins->multipcm.vib; + chan[c.chan].am=ins->multipcm.am; + chan[c.chan].ar=ins->multipcm.ar; + chan[c.chan].d1r=ins->multipcm.d1r; + chan[c.chan].dl=ins->multipcm.dl; + chan[c.chan].d2r=ins->multipcm.d2r; + chan[c.chan].rc=ins->multipcm.rc; + chan[c.chan].rr=ins->multipcm.rr; + chan[c.chan].damp=ins->opl4pcm.damp; + chan[c.chan].pseudoReverb=ins->opl4pcm.pseudoReverb; + chan[c.chan].levelDirect=ins->opl4pcm.levelDirect; + chan[c.chan].lfoReset=ins->opl4pcm.lfoReset; + } else { + chan[c.chan].lfo=0; + chan[c.chan].vib=0; + chan[c.chan].am=0; + chan[c.chan].ar=15; + chan[c.chan].d1r=15; + chan[c.chan].dl=0; + chan[c.chan].d2r=0; + chan[c.chan].rc=15; + chan[c.chan].rr=15; + chan[c.chan].damp=false; + chan[c.chan].pseudoReverb=false; + chan[c.chan].levelDirect=true; + chan[c.chan].lfoReset=false; + } chan[c.chan].active=true; chan[c.chan].keyOn=true; chan[c.chan].macroInit(ins); @@ -1852,6 +1931,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; case DIV_CMD_PANNING: { if (PCM_CHECK(c.chan)) { + chan[c.chan].ch=false; chan[c.chan].pan=8^MIN(parent->convertPanSplitToLinearLR(c.value,c.value2,15)+1,15); chan[c.chan].freqChanged=true; chan[c.chan].writeCtrl=true; @@ -1880,6 +1960,12 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_SURROUND_PANNING: { + if (PCM_CHECK(c.chan)) { + chan[c.chan].ch=true; + chan[c.chan].freqChanged=true; + chan[c.chan].writeCtrl=true; + break; + } if (oplType!=3) break; if (c.chan==adpcmChan) break; @@ -2331,9 +2417,99 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_HARD_RESET: + if (PCM_CHECK(c.chan)) break; if (c.chan==adpcmChan) break; chan[c.chan].hardReset=c.value; break; + case DIV_CMD_OPL4_PCM_MIX_FM: + if (chipType==4) { + rWrite(PCM_ADDR_MIX_FM,(CLAMP((0x70-(c.value&0x70)),0,0x70)>>1)|(CLAMP((7-(c.value&7)),0,7))); + } + break; + case DIV_CMD_OPL4_PCM_MIX_PCM: + if (chipType==4) { + rWrite(PCM_ADDR_MIX_PCM,(CLAMP((0x70-(c.value&0x70)),0,0x70)>>1)|(CLAMP((7-(c.value&7)),0,7))); + } + break; + case DIV_CMD_OPL4_PCM_LFO: + if (PCM_CHECK(c.chan)) { + chan[c.chan].lfo=c.value&7; + rWrite(PCM_ADDR_LFO_VIB+PCM_REG(c.chan),(chan[c.chan].lfo<<3)|(chan[c.chan].vib)); + } + break; + case DIV_CMD_OPL4_PCM_VIB: + if (PCM_CHECK(c.chan)) { + chan[c.chan].vib=c.value&7; + rWrite(PCM_ADDR_LFO_VIB+PCM_REG(c.chan),(chan[c.chan].lfo<<3)|(chan[c.chan].vib)); + } + break; + case DIV_CMD_OPL4_PCM_AM: + if (PCM_CHECK(c.chan)) { + chan[c.chan].am=c.value&7; + rWrite(PCM_ADDR_AM+PCM_REG(c.chan),chan[c.chan].am); + } + break; + case DIV_CMD_OPL4_PCM_AR: + if (PCM_CHECK(c.chan)) { + chan[c.chan].ar=c.value&0xf; + rWrite(PCM_ADDR_AR_D1R+PCM_REG(c.chan),(chan[c.chan].ar<<4)|(chan[c.chan].d1r)); + } + break; + case DIV_CMD_OPL4_PCM_D1R: + if (PCM_CHECK(c.chan)) { + chan[c.chan].d1r=c.value&0xf; + rWrite(PCM_ADDR_AR_D1R+PCM_REG(c.chan),(chan[c.chan].ar<<4)|(chan[c.chan].d1r)); + } + break; + case DIV_CMD_OPL4_PCM_DL: + if (PCM_CHECK(c.chan)) { + chan[c.chan].dl=c.value&0xf; + rWrite(PCM_ADDR_DL_D2R+PCM_REG(c.chan),(chan[c.chan].dl<<4)|(chan[c.chan].d2r)); + } + break; + case DIV_CMD_OPL4_PCM_D2R: + if (PCM_CHECK(c.chan)) { + chan[c.chan].d2r=c.value&0xf; + rWrite(PCM_ADDR_DL_D2R+PCM_REG(c.chan),(chan[c.chan].dl<<4)|(chan[c.chan].d2r)); + } + break; + case DIV_CMD_OPL4_PCM_RC: + if (PCM_CHECK(c.chan)) { + chan[c.chan].rc=c.value&0xf; + rWrite(PCM_ADDR_RC_RR+PCM_REG(c.chan),(chan[c.chan].rc<<4)|(chan[c.chan].rr)); + } + break; + case DIV_CMD_OPL4_PCM_RR: + if (PCM_CHECK(c.chan)) { + chan[c.chan].rr=c.value&0xf; + rWrite(PCM_ADDR_RC_RR+PCM_REG(c.chan),(chan[c.chan].rc<<4)|(chan[c.chan].rr)); + } + break; + case DIV_CMD_OPL4_PCM_DAMP: + if (PCM_CHECK(c.chan)) { + chan[c.chan].damp=c.value&1; + chan[c.chan].freqChanged=true; + chan[c.chan].writeCtrl=true; + } + break; + case DIV_CMD_OPL4_PCM_PSEUDO_REVERB: + if (PCM_CHECK(c.chan)) { + chan[c.chan].pseudoReverb=c.value&1; + chan[c.chan].freqChanged=true; + } + break; + case DIV_CMD_OPL4_PCM_LFO_RESET: + if (PCM_CHECK(c.chan)) { + chan[c.chan].lfoReset=c.value&1; + chan[c.chan].freqChanged=true; + chan[c.chan].writeCtrl=true; + } + break; + case DIV_CMD_OPL4_PCM_LEVEL_DIRECT: + if (PCM_CHECK(c.chan)) { + immWrite(PCM_ADDR_TL+PCM_REG(c.chan),((0x7f-chan[c.chan].outVol)<<1)|(chan[c.chan].levelDirect?1:0)); + } + break; case DIV_CMD_MACRO_OFF: chan[c.chan].std.mask(c.value,true); break; @@ -3019,6 +3195,7 @@ void DivPlatformOPL::renderSamples(int sysID) { case DIV_SAMPLE_DEPTH_8BIT: bitDepth=0; break; + // TODO: 12 bit PCM case DIV_SAMPLE_DEPTH_16BIT: bitDepth=2; break; diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index 708d30b51..e62f5ec84 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -61,7 +61,8 @@ class DivPlatformOPL: public DivDispatch { unsigned int freqH, freqL; int sample, fixedFreq; bool furnacePCM, fourOp, hardReset, writeCtrl; - bool levelDirect, damp, pseudoReverb; + bool levelDirect, damp, pseudoReverb, lfoReset, ch; + int lfo, vib, am, ar, d1r, d2r, dl, rc, rr; int pan; int macroVolMul; Channel(): @@ -77,6 +78,17 @@ class DivPlatformOPL: public DivDispatch { levelDirect(true), damp(false), pseudoReverb(false), + lfoReset(false), + ch(false), + lfo(0), + vib(0), + am(0), + ar(15), + d1r(15), + d2r(0), + dl(0), + rc(15), + rr(15), pan(3), macroVolMul(64) { state.ops=2; @@ -208,7 +220,6 @@ class DivPlatformOPL: public DivDispatch { void renderSamples(int chipID); int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); void quit(); - YMF278& getChip(); DivPlatformOPL(): pcmMemory(0x400000), pcm(pcmMemory) {} diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 293ad4f7f..9ee939029 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -261,7 +261,23 @@ const char* cmdName[]={ "MINMOD_ECHO", "BIFURCATOR_STATE_LOAD", - "BIFURCATOR_PARAMETER" + "BIFURCATOR_PARAMETER", + + "OPL4_MIX_FM", + "OPL4_MIX_PCM", + "OPL4_LFO", + "OPL4_VIB", + "OPL4_AM", + "OPL4_AR", + "OPL4_D1R", + "OPL4_DL", + "OPL4_D2R", + "OPL4_RR", + "OPL4_RC", + "OPL4_DAMP", + "OPL4_PSEUDO_REVERB", + "OPL4_LFO_RESET", + "OPL4_LEVEL_DIRECT" }; static_assert((sizeof(cmdName)/sizeof(void*))==DIV_CMD_MAX,"update cmdName!"); diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 41ecbb050..384b0726a 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -599,6 +599,25 @@ void DivEngine::registerSystems() { {0x5b, {DIV_CMD_FM_KSR, _("5Bxy: Set whether key will scale envelope (x: operator from 1 to 4 (0 for all ops); y: enabled)"), effectOpVal<4>, effectValAnd<1>}}, }; + EffectHandlerMap fmOPL4PostEffectHandlerMap(fmOPLPostEffectHandlerMap); + fmOPL4PostEffectHandlerMap.insert({ + {0x1e, {DIV_CMD_OPL4_PCM_MIX_FM, _("1Exy: FM global level (left, right; 0 to 7)"), effectVal}}, + {0x1f, {DIV_CMD_OPL4_PCM_MIX_PCM, _("1Fxy: PCM global level (left, right; 0 to 7)"), effectVal}}, + {0x20, {DIV_CMD_OPL4_PCM_LFO, _("20xx: PCM LFO Rate (0 to 7)"), effectValAnd<7>}}, + {0x21, {DIV_CMD_OPL4_PCM_VIB, _("21xx: PCM LFO PM Depth (0 to 7)"), effectValAnd<7>}}, + {0x22, {DIV_CMD_OPL4_PCM_AM, _("22xx: PCM LFO AM Depth (0 to 7)"), effectValAnd<7>}}, + {0x23, {DIV_CMD_OPL4_PCM_AR, _("23xx: PCM Attack Rate (0 to 15)"), effectValAnd<15>}}, + {0x24, {DIV_CMD_OPL4_PCM_D1R, _("24xx: PCM Decay 1 Rate (0 to 15)"), effectValAnd<15>}}, + {0x25, {DIV_CMD_OPL4_PCM_DL, _("25xx: PCM Decay Level (0 to 15)"), effectValAnd<15>}}, + {0x26, {DIV_CMD_OPL4_PCM_D2R, _("26xx: PCM Decay 2 Rate (0 to 15)"), effectValAnd<15>}}, + {0x27, {DIV_CMD_OPL4_PCM_RC, _("27xx: PCM Release Rate (0 to 15)"), effectValAnd<15>}}, + {0x28, {DIV_CMD_OPL4_PCM_RR, _("28xx: PCM Rate Correction (0 to 15)"), effectValAnd<15>}}, + {0x2c, {DIV_CMD_OPL4_PCM_DAMP, _("2Cxx: PCM Damp"), effectValAnd<1>}}, + {0x2d, {DIV_CMD_OPL4_PCM_PSEUDO_REVERB, _("2Dxx: PCM Pseudo Reverb"), effectValAnd<1>}}, + {0x2e, {DIV_CMD_OPL4_PCM_LFO_RESET, _("2Exx: PCM LFO Reset"), effectValAnd<1>}}, + {0x2f, {DIV_CMD_OPL4_PCM_LEVEL_DIRECT, _("2Fxx: PCM Level Direct"), effectValAnd<1>}}, + }); + EffectHandlerMap c64PostEffectHandlerMap={ {0x10, {DIV_CMD_WAVE, _("10xx: Set waveform (bit 0: triangle; bit 1: saw; bit 2: pulse; bit 3: noise)")}}, {0x11, {DIV_CMD_C64_CUTOFF, _("11xx: Set coarse cutoff (not recommended; use 4xxx instead)")}}, @@ -1616,28 +1635,28 @@ void DivEngine::registerSystems() { // to Grauw: feel free to change this to 24 during development of OPL4's PCM part. // TODO: add 12-bit and 16-bit big-endian sample formats sysDefs[DIV_SYSTEM_OPL4]=new DivSysDef( - _("Yamaha YMF278B (OPL4)"), NULL, 0xae, 0, 42, true, true, 0, false, (1U<type==DIV_INS_C219 || ins->type==DIV_INS_NDS || ins->type==DIV_INS_GBA_DMA || - ins->type==DIV_INS_GBA_MINMOD) { + ins->type==DIV_INS_GBA_MINMOD || + ins->type==DIV_INS_OPL4PCM) { insTabSample(ins); } if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("Namco 163")) { @@ -6437,8 +6438,8 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndTabItem(); } - if (ins->type==DIV_INS_MULTIPCM) { - if (ImGui::BeginTabItem("MultiPCM")) { + if (ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_OPL4PCM) { + if (ImGui::BeginTabItem(ins->type==DIV_INS_OPL4PCM?"OPL4 PCM":"MultiPCM")) { ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale); if (ImGui::BeginTable("MultiPCMADSRParams",7,ImGuiTableFlags_NoHostExtendX)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); @@ -6519,6 +6520,12 @@ void FurnaceGUI::drawInsEdit() { P(CWSliderScalar(_("AM Depth"),ImGuiDataType_U8,&ins->multipcm.am,&_ZERO,&_SEVEN)); rightClickable ImGui::EndTable(); } + if (ins->type==DIV_INS_OPL4PCM) { + P(ImGui::Checkbox(_("Damp"),&ins->opl4pcm.damp)); + P(ImGui::Checkbox(_("Pseudo Reverb"),&ins->opl4pcm.pseudoReverb)); + P(ImGui::Checkbox(_("Level Direct"),&ins->opl4pcm.levelDirect)); + P(ImGui::Checkbox(_("LFO Reset"),&ins->opl4pcm.lfoReset)); + } ImGui::EndTabItem(); } } @@ -7503,6 +7510,22 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc(_("Noise Mode"),&ins->std.fmsMacro,0,3,64,uiColors[GUI_COLOR_MACRO_NOISE])); macroList.push_back(FurnaceGUIMacroDesc(_("Wave Mix"),&ins->std.amsMacro,0,3,64,uiColors[GUI_COLOR_MACRO_OTHER])); break; + case DIV_INS_OPL4PCM: + macroList.push_back(FurnaceGUIMacroDesc(_("Volume"),&ins->std.volMacro,0,127,160,uiColors[GUI_COLOR_MACRO_VOLUME])); + macroList.push_back(FurnaceGUIMacroDesc(_("Arpeggio"),&ins->std.arpMacro,-120,120,160,uiColors[GUI_COLOR_MACRO_PITCH],true,NULL,macroHoverNote,false,NULL,true,ins->std.arpMacro.val)); + macroList.push_back(FurnaceGUIMacroDesc(_("Panning"),&ins->std.panLMacro,-7,7,45,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL)); + macroList.push_back(FurnaceGUIMacroDesc(_("Pitch"),&ins->std.pitchMacro,-2048,2047,160,uiColors[GUI_COLOR_MACRO_PITCH],true,macroRelativeMode)); + macroList.push_back(FurnaceGUIMacroDesc(_("Phase Reset"),&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); + macroList.push_back(FurnaceGUIMacroDesc(_("LFO speed"),&ins->std.ex1Macro,0,7,160,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(_("LFO vibrato depth"),&ins->std.fmsMacro,0,7,160,uiColors[GUI_COLOR_MACRO_PITCH])); + macroList.push_back(FurnaceGUIMacroDesc(_("LFO AM depth"),&ins->std.amsMacro,0,7,160,uiColors[GUI_COLOR_MACRO_VOLUME])); + macroList.push_back(FurnaceGUIMacroDesc(_("AR"),&ins->std.ex2Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("D1R"),&ins->std.ex3Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("DL"),&ins->std.ex4Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("D2R"),&ins->std.ex5Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("Rate correction"),&ins->std.ex6Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("RR"),&ins->std.ex7Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + break; case DIV_INS_MAX: case DIV_INS_NULL: diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 63815cd02..23b1666e7 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -4107,6 +4107,7 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_INSTR_GBA_MINMOD,_("GBA MinMod")); UI_COLOR_CONFIG(GUI_COLOR_INSTR_BIFURCATOR,_("Bifurcator")); UI_COLOR_CONFIG(GUI_COLOR_INSTR_SID2,_("SID2")); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_OPL4PCM,_("OPL4 PCM")); UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,_("Other/Unknown")); ImGui::TreePop(); } From 62af456840102cf808fd64cc59e73ce8d489e82c Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 12 Jul 2024 18:00:36 +0900 Subject: [PATCH 09/39] Fix segmentation fault --- src/engine/platform/opl.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index d836379c0..4e618f411 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -3122,10 +3122,10 @@ const DivMemoryComposition* DivPlatformOPL::getMemCompo(int index) { void DivPlatformOPL::renderSamples(int sysID) { if (adpcmChan<0 && pcmChanOffs<0) return; - if (adpcmBMem!=NULL) { + if (adpcmChan>=0 && adpcmBMem!=NULL) { memset(adpcmBMem,0,262144); } - if (pcmMem!=NULL) { + if (pcmChanOffs>=0 && pcmMem!=NULL) { memset(pcmMem,0,4194304); } memset(sampleOffPCM,0,256*sizeof(unsigned int)); From 0d14789ac66c8d8800c1d17435938b9dbf8e8a05 Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 12 Jul 2024 18:19:41 +0900 Subject: [PATCH 10/39] Fix Keyon/Off --- src/engine/platform/opl.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 4e618f411..c6b06edaf 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -1382,14 +1382,14 @@ void DivPlatformOPL::tick(bool sysTick) { waveNum=CLAMP(waveNum,0,0x7f)|0x180; } if (chan[i].keyOn) { - rWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+PCM_REG(i),ctrl&~0x80); // force keyoff first - rWrite(PCM_ADDR_WAVE_H_FN_L+PCM_REG(i),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1)); - rWrite(PCM_ADDR_WAVE_L+PCM_REG(i),waveNum&0xff); - rWrite(PCM_ADDR_LFO_VIB+PCM_REG(i),(chan[i].lfo<<3)|(chan[i].vib)); - rWrite(PCM_ADDR_AR_D1R+PCM_REG(i),(chan[i].ar<<4)|(chan[i].d1r)); - rWrite(PCM_ADDR_DL_D2R+PCM_REG(i),(chan[i].dl<<4)|(chan[i].d2r)); - rWrite(PCM_ADDR_RC_RR+PCM_REG(i),(chan[i].rc<<4)|(chan[i].rr)); - rWrite(PCM_ADDR_AM+PCM_REG(i),chan[i].am); + 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)); @@ -1402,12 +1402,12 @@ void DivPlatformOPL::tick(bool sysTick) { chan[i].keyOff=false; } if (chan[i].freqChanged) { - rWrite(PCM_ADDR_WAVE_H_FN_L+PCM_REG(i),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1)); - rWrite(PCM_ADDR_FN_H_PR_OCT+PCM_REG(i),((chan[i].freqH&0xf)<<4)|(chan[i].pseudoReverb?0x08:0x00)|((chan[i].freqL>>7)&0x7)); + 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) { - rWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+PCM_REG(i),ctrl); + immWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+PCM_REG(i),ctrl); chan[i].writeCtrl=false; } } From 339cc6a5f0868d891709497bfd27808260b28564 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 13 Jul 2024 07:14:46 +0900 Subject: [PATCH 11/39] Fix typo --- src/engine/sysDef.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 384b0726a..d2ab92490 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1638,7 +1638,7 @@ void DivEngine::registerSystems() { _("Yamaha YMF278B (OPL4)"), NULL, 0xae, 0, 42, true, true, 0x151, false, (1U< Date: Sat, 13 Jul 2024 07:34:53 +0900 Subject: [PATCH 12/39] Revert and Re-add instrument parameter into multipcm instruemnt Fix per-channel OSC --- src/engine/engine.cpp | 3 +-- src/engine/instrument.cpp | 53 ++++++++----------------------------- src/engine/instrument.h | 19 ++----------- src/engine/platform/opl.cpp | 18 ++++++++----- src/engine/sysDef.cpp | 4 +-- src/gui/doAction.cpp | 6 ++--- src/gui/gui.h | 1 - src/gui/guiConst.cpp | 2 -- src/gui/insEdit.cpp | 49 +++++++++++++++++----------------- src/gui/settings.cpp | 1 - 10 files changed, 54 insertions(+), 102 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index b024257e4..7ca58611b 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -938,8 +938,7 @@ void DivEngine::delUnusedSamples() { i->type==DIV_INS_C219 || i->type==DIV_INS_NDS || i->type==DIV_INS_GBA_DMA || - i->type==DIV_INS_GBA_MINMOD || - i->type==DIV_INS_OPL4PCM) { + i->type==DIV_INS_GBA_MINMOD) { if (i->amiga.initSample>=0 && i->amiga.initSampleamiga.initSample]=true; } diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 039721445..7df890731 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -174,12 +174,7 @@ bool DivInstrumentMultiPCM::operator==(const DivInstrumentMultiPCM& other) { _C(rc) && _C(lfo) && _C(vib) && - _C(am) - ); -} - -bool DivInstrumentOPL4PCM::operator==(const DivInstrumentOPL4PCM& other) { - return ( + _C(am) && _C(damp) && _C(pseudoReverb) && _C(lfoReset) && @@ -766,6 +761,11 @@ void DivInstrument::writeFeatureMP(SafeWriter* w) { w->writeC(multipcm.vib); w->writeC(multipcm.am); + w->writeC(multipcm.damp); + w->writeC(multipcm.pseudoReverb); + w->writeC(multipcm.lfoReset); + w->writeC(multipcm.levelDirect); + FEATURE_END; } @@ -857,17 +857,6 @@ void DivInstrument::writeFeatureS2(SafeWriter* w) { FEATURE_END; } -void DivInstrument::writeFeatureO4(SafeWriter* w) { - FEATURE_BEGIN("O4"); - - w->writeC(opl4pcm.damp); - w->writeC(opl4pcm.pseudoReverb); - w->writeC(opl4pcm.lfoReset); - w->writeC(opl4pcm.levelDirect); - - FEATURE_END; -} - void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bool insName) { size_t blockStartSeek=0; size_t blockEndSeek=0; @@ -914,7 +903,6 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo bool featureEF=false; bool featurePN=false; bool featureS2=false; - bool featureO4=false; bool checkForWL=false; @@ -1158,12 +1146,6 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo feature64=true; featureS2=true; break; - case DIV_INS_OPL4PCM: - featureSM=true; - featureSL=true; - featureMP=true; - featureO4=true; - break; case DIV_INS_MAX: break; case DIV_INS_NULL: @@ -1220,9 +1202,6 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo if (sid2!=defaultIns.sid2) { featureS2=true; } - if (opl4pcm!=defaultIns.opl4pcm) { - featureO4=true; - } } // check ins name @@ -1374,9 +1353,6 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo if (featureS2) { writeFeatureS2(w); } - if (featureO4) { - writeFeatureO4(w); - } if (fui && (featureSL || featureWL)) { w->write("EN",2); @@ -2098,6 +2074,11 @@ void DivInstrument::readFeatureMP(SafeReader& reader, short version) { multipcm.vib=reader.readC(); multipcm.am=reader.readC(); + multipcm.damp=reader.readC(); + multipcm.pseudoReverb=reader.readC(); + multipcm.lfoReset=reader.readC(); + multipcm.levelDirect=reader.readC(); + READ_FEAT_END; } @@ -2205,16 +2186,6 @@ void DivInstrument::readFeatureS2(SafeReader& reader, short version) { READ_FEAT_END; } -void DivInstrument::readFeatureO4(SafeReader& reader, short version) { - READ_FEAT_BEGIN; - - opl4pcm.damp=reader.readC(); - opl4pcm.pseudoReverb=reader.readC(); - opl4pcm.lfoReset=reader.readC(); - opl4pcm.levelDirect=reader.readC(); - - READ_FEAT_END; -} DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, bool fui, DivSong* song) { unsigned char featCode[2]; @@ -2290,8 +2261,6 @@ DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, b readFeaturePN(reader,version); } else if (memcmp(featCode,"S2",2)==0) { // SID2 readFeatureS2(reader,version); - } else if (memcmp(featCode,"O4",2)==0) { // OPL4 PCM - readFeatureO4(reader,version); } else { if (song==NULL && (memcmp(featCode,"SL",2)==0 || (memcmp(featCode,"WL",2)==0))) { // nothing diff --git a/src/engine/instrument.h b/src/engine/instrument.h index f37a61c0a..95a86755a 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -94,7 +94,6 @@ enum DivInstrumentType: unsigned short { DIV_INS_GBA_MINMOD=61, DIV_INS_BIFURCATOR=62, DIV_INS_SID2=63, // coincidence! - DIV_INS_OPL4PCM=64, DIV_INS_MAX, DIV_INS_NULL }; @@ -609,6 +608,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) { @@ -617,19 +617,7 @@ struct DivInstrumentMultiPCM { DivInstrumentMultiPCM(): ar(15), d1r(15), dl(0), d2r(0), rr(15), rc(15), - lfo(0), vib(0), am(0) { - } -}; - -struct DivInstrumentOPL4PCM { - bool damp, pseudoReverb, lfoReset, levelDirect; - - bool operator==(const DivInstrumentOPL4PCM& other); - bool operator!=(const DivInstrumentOPL4PCM& other) { - return !(*this==other); - } - - DivInstrumentOPL4PCM(): + lfo(0), vib(0), am(0), damp(false), pseudoReverb(false), lfoReset(false), @@ -896,7 +884,6 @@ struct DivInstrument { DivInstrumentESFM esfm; DivInstrumentPowerNoise powernoise; DivInstrumentSID2 sid2; - DivInstrumentOPL4PCM opl4pcm; /** * these are internal functions. @@ -924,7 +911,6 @@ struct DivInstrument { void writeFeatureEF(SafeWriter* w); void writeFeaturePN(SafeWriter* w); void writeFeatureS2(SafeWriter* w); - void writeFeatureO4(SafeWriter* w); void readFeatureNA(SafeReader& reader, short version); void readFeatureFM(SafeReader& reader, short version); @@ -948,7 +934,6 @@ struct DivInstrument { void readFeatureEF(SafeReader& reader, short version); void readFeaturePN(SafeReader& reader, short version); void readFeatureS2(SafeReader& reader, short version); - void readFeatureO4(SafeReader& reader, short version); DivDataErrors readInsDataOld(SafeReader& reader, short version); DivDataErrors readInsDataNew(SafeReader& reader, short version, bool fui, DivSong* song); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index c6b06edaf..8dbce53a2 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -299,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; @@ -640,7 +646,7 @@ void DivPlatformOPL::acquire_ymfm4(short** buf, size_t len) { 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>>3,-32768,32767); + oscBuf[oscOffs]->data[oscBuf[oscOffs]->needle++]=CLAMP(chOut,-32768,32767); } } } @@ -1705,7 +1711,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; } - if (ins->type==DIV_INS_OPL4PCM) { + 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; @@ -1715,10 +1721,10 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].d2r=ins->multipcm.d2r; chan[c.chan].rc=ins->multipcm.rc; chan[c.chan].rr=ins->multipcm.rr; - chan[c.chan].damp=ins->opl4pcm.damp; - chan[c.chan].pseudoReverb=ins->opl4pcm.pseudoReverb; - chan[c.chan].levelDirect=ins->opl4pcm.levelDirect; - chan[c.chan].lfoReset=ins->opl4pcm.lfoReset; + 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; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index d2ab92490..94a699754 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1640,7 +1640,7 @@ void DivEngine::registerSystems() { {_("4OP 1"), _("FM 2"), _("4OP 3"), _("FM 4"), _("4OP 5"), _("FM 6"), _("4OP 7"), _("FM 8"), _("4OP 9"), _("FM 10"), _("4OP 11"), _("FM 12"), _("FM 13"), _("FM 14"), _("FM 15"), _("FM 16"), _("FM 17"), _("FM 18"), _("PCM 1"), _("PCM 2"), _("PCM 3"), _("PCM 4"), _("PCM 5"), _("PCM 6"), _("PCM 7"), _("PCM 8"), _("PCM 9"), _("PCM 10"), _("PCM 11"), _("PCM 12"), _("PCM 13"), _("PCM 14"), _("PCM 15"), _("PCM 16"), _("PCM 17"), _("PCM 18"), _("PCM 19"), _("PCM 20"), _("PCM 21"), _("PCM 22"), _("PCM 23"), _("PCM 24")}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P9", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"}, {DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, - {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM}, + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM}, {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, fmEffectHandlerMap, fmOPL4PostEffectHandlerMap @@ -1653,7 +1653,7 @@ void DivEngine::registerSystems() { {_("4OP 1"), _("FM 2"), _("4OP 3"), _("FM 4"), _("4OP 5"), _("FM 6"), _("4OP 7"), _("FM 8"), _("4OP 9"), _("FM 10"), _("4OP 11"), _("FM 12"), _("FM 13"), _("FM 14"), _("FM 15"), _("Kick/FM 16"), _("Snare"), _("Tom"), _("Top"), _("HiHat"), _("PCM 1"), _("PCM 2"), _("PCM 3"), _("PCM 4"), _("PCM 5"), _("PCM 6"), _("PCM 7"), _("PCM 8"), _("PCM 9"), _("PCM 10"), _("PCM 11"), _("PCM 12"), _("PCM 13"), _("PCM 14"), _("PCM 15"), _("PCM 16"), _("PCM 17"), _("PCM 18"), _("PCM 19"), _("PCM 20"), _("PCM 21"), _("PCM 22"), _("PCM 23"), _("PCM 24")}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "BD", "SD", "TM", "TP", "HH", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P9", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"}, {DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, - {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM, DIV_INS_OPL4PCM}, + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM}, {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, fmOPLDrumsEffectHandlerMap, fmOPL4PostEffectHandlerMap diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 86018242c..d6d8ea434 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -1043,8 +1043,7 @@ void FurnaceGUI::doAction(int what) { i==DIV_INS_K053260 || i==DIV_INS_C140 || i==DIV_INS_C219 || - i==DIV_INS_NDS || - i==DIV_INS_OPL4PCM) { + i==DIV_INS_NDS) { makeInsTypeList.push_back(i); } } @@ -1573,8 +1572,7 @@ void FurnaceGUI::doAction(int what) { i==DIV_INS_C219 || i==DIV_INS_NDS || i==DIV_INS_GBA_DMA || - i==DIV_INS_GBA_MINMOD || - i==DIV_INS_OPL4PCM) { + i==DIV_INS_GBA_MINMOD) { makeInsTypeList.push_back(i); } } diff --git a/src/gui/gui.h b/src/gui/gui.h index b54f9263c..51161c224 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -351,7 +351,6 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_GBA_MINMOD, GUI_COLOR_INSTR_BIFURCATOR, GUI_COLOR_INSTR_SID2, - GUI_COLOR_INSTR_OPL4PCM, GUI_COLOR_INSTR_UNKNOWN, GUI_COLOR_CHANNEL_BG, diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 7c0b43c3c..b9d9ae85d 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -184,7 +184,6 @@ const char* insTypes[DIV_INS_MAX+1][3]={ {"GBA MinMod",ICON_FA_VOLUME_UP,ICON_FUR_INS_GBA_MINMOD}, {"Bifurcator",ICON_FA_LINE_CHART,ICON_FUR_INS_BIFURCATOR}, {"SID2",ICON_FA_KEYBOARD_O,ICON_FUR_INS_SID2}, - {"OPL4 PCM",ICON_FA_KEYBOARD_O,ICON_FUR_INS_SID2}, {NULL,ICON_FA_QUESTION,ICON_FA_QUESTION} }; @@ -1013,7 +1012,6 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_INSTR_GBA_MINMOD,"",ImVec4(0.5f,0.45f,0.7f,1.0f)), D(GUI_COLOR_INSTR_BIFURCATOR,"",ImVec4(0.8925f,0.8925f,0.8925f,1.0f)), D(GUI_COLOR_INSTR_SID2,"",ImVec4(0.6f,0.75f,1.0f,1.0f)), - D(GUI_COLOR_INSTR_OPL4PCM,"",ImVec4(1.0f,0.8f,0.1f,1.0f)), D(GUI_COLOR_INSTR_UNKNOWN,"",ImVec4(0.3f,0.3f,0.3f,1.0f)), D(GUI_COLOR_CHANNEL_BG,"",ImVec4(0.4f,0.6f,0.8f,1.0f)), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 08fcfe33a..974188e96 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -6205,8 +6205,7 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_C219 || ins->type==DIV_INS_NDS || ins->type==DIV_INS_GBA_DMA || - ins->type==DIV_INS_GBA_MINMOD || - ins->type==DIV_INS_OPL4PCM) { + ins->type==DIV_INS_GBA_MINMOD) { insTabSample(ins); } if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("Namco 163")) { @@ -6438,8 +6437,8 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndTabItem(); } - if (ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_OPL4PCM) { - if (ImGui::BeginTabItem(ins->type==DIV_INS_OPL4PCM?"OPL4 PCM":"MultiPCM")) { + if (ins->type==DIV_INS_MULTIPCM) { + if (ImGui::BeginTabItem("MultiPCM")) { ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale); if (ImGui::BeginTable("MultiPCMADSRParams",7,ImGuiTableFlags_NoHostExtendX)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); @@ -6520,12 +6519,19 @@ void FurnaceGUI::drawInsEdit() { P(CWSliderScalar(_("AM Depth"),ImGuiDataType_U8,&ins->multipcm.am,&_ZERO,&_SEVEN)); rightClickable ImGui::EndTable(); } - if (ins->type==DIV_INS_OPL4PCM) { - P(ImGui::Checkbox(_("Damp"),&ins->opl4pcm.damp)); - P(ImGui::Checkbox(_("Pseudo Reverb"),&ins->opl4pcm.pseudoReverb)); - P(ImGui::Checkbox(_("Level Direct"),&ins->opl4pcm.levelDirect)); - P(ImGui::Checkbox(_("LFO Reset"),&ins->opl4pcm.lfoReset)); + 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(); } } @@ -7217,6 +7223,15 @@ 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 vibrato depth"),&ins->std.fmsMacro,0,7,160,uiColors[GUI_COLOR_MACRO_PITCH])); + macroList.push_back(FurnaceGUIMacroDesc(_("LFO AM depth"),&ins->std.amsMacro,0,7,160,uiColors[GUI_COLOR_MACRO_VOLUME])); + macroList.push_back(FurnaceGUIMacroDesc(_("AR"),&ins->std.ex2Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("D1R"),&ins->std.ex3Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("DL"),&ins->std.ex4Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("D2R"),&ins->std.ex5Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("Rate correction"),&ins->std.ex6Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("RR"),&ins->std.ex7Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); break; case DIV_INS_SNES: macroList.push_back(FurnaceGUIMacroDesc(_("Volume"),&ins->std.volMacro,0,127,160,uiColors[GUI_COLOR_MACRO_VOLUME])); @@ -7510,22 +7525,6 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc(_("Noise Mode"),&ins->std.fmsMacro,0,3,64,uiColors[GUI_COLOR_MACRO_NOISE])); macroList.push_back(FurnaceGUIMacroDesc(_("Wave Mix"),&ins->std.amsMacro,0,3,64,uiColors[GUI_COLOR_MACRO_OTHER])); break; - case DIV_INS_OPL4PCM: - macroList.push_back(FurnaceGUIMacroDesc(_("Volume"),&ins->std.volMacro,0,127,160,uiColors[GUI_COLOR_MACRO_VOLUME])); - macroList.push_back(FurnaceGUIMacroDesc(_("Arpeggio"),&ins->std.arpMacro,-120,120,160,uiColors[GUI_COLOR_MACRO_PITCH],true,NULL,macroHoverNote,false,NULL,true,ins->std.arpMacro.val)); - macroList.push_back(FurnaceGUIMacroDesc(_("Panning"),&ins->std.panLMacro,-7,7,45,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL)); - macroList.push_back(FurnaceGUIMacroDesc(_("Pitch"),&ins->std.pitchMacro,-2048,2047,160,uiColors[GUI_COLOR_MACRO_PITCH],true,macroRelativeMode)); - macroList.push_back(FurnaceGUIMacroDesc(_("Phase Reset"),&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); - macroList.push_back(FurnaceGUIMacroDesc(_("LFO speed"),&ins->std.ex1Macro,0,7,160,uiColors[GUI_COLOR_MACRO_OTHER])); - macroList.push_back(FurnaceGUIMacroDesc(_("LFO vibrato depth"),&ins->std.fmsMacro,0,7,160,uiColors[GUI_COLOR_MACRO_PITCH])); - macroList.push_back(FurnaceGUIMacroDesc(_("LFO AM depth"),&ins->std.amsMacro,0,7,160,uiColors[GUI_COLOR_MACRO_VOLUME])); - macroList.push_back(FurnaceGUIMacroDesc(_("AR"),&ins->std.ex2Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - macroList.push_back(FurnaceGUIMacroDesc(_("D1R"),&ins->std.ex3Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - macroList.push_back(FurnaceGUIMacroDesc(_("DL"),&ins->std.ex4Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - macroList.push_back(FurnaceGUIMacroDesc(_("D2R"),&ins->std.ex5Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - macroList.push_back(FurnaceGUIMacroDesc(_("Rate correction"),&ins->std.ex6Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - macroList.push_back(FurnaceGUIMacroDesc(_("RR"),&ins->std.ex7Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - break; case DIV_INS_MAX: case DIV_INS_NULL: diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 23b1666e7..63815cd02 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -4107,7 +4107,6 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_INSTR_GBA_MINMOD,_("GBA MinMod")); UI_COLOR_CONFIG(GUI_COLOR_INSTR_BIFURCATOR,_("Bifurcator")); UI_COLOR_CONFIG(GUI_COLOR_INSTR_SID2,_("SID2")); - UI_COLOR_CONFIG(GUI_COLOR_INSTR_OPL4PCM,_("OPL4 PCM")); UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,_("Other/Unknown")); ImGui::TreePop(); } From 96571a5e0d4e7c4da002872aace767fe2e1b25dc Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 13 Jul 2024 07:36:24 +0900 Subject: [PATCH 13/39] Minor fix error --- src/engine/instrument.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 7df890731..a44572ab8 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -761,10 +761,12 @@ void DivInstrument::writeFeatureMP(SafeWriter* w) { w->writeC(multipcm.vib); w->writeC(multipcm.am); + /* w->writeC(multipcm.damp); w->writeC(multipcm.pseudoReverb); w->writeC(multipcm.lfoReset); w->writeC(multipcm.levelDirect); + */ FEATURE_END; } @@ -2074,10 +2076,12 @@ void DivInstrument::readFeatureMP(SafeReader& reader, short version) { multipcm.vib=reader.readC(); multipcm.am=reader.readC(); + /* multipcm.damp=reader.readC(); multipcm.pseudoReverb=reader.readC(); multipcm.lfoReset=reader.readC(); multipcm.levelDirect=reader.readC(); + */ READ_FEAT_END; } From 4d3bbc57b3de59f12d847037f45709c9240189d8 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 13 Jul 2024 07:56:23 +0900 Subject: [PATCH 14/39] Fix getPan --- src/engine/platform/opl.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 8dbce53a2..fdaa7ad2b 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -2624,6 +2624,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); From ec5879f10b3403f6974d75e6f12d3cf4dc4d2cfe Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 13 Jul 2024 11:41:28 +0900 Subject: [PATCH 15/39] Fix pre-porta command --- src/engine/platform/opl.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index fdaa7ad2b..f048af2c0 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -2533,7 +2533,8 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; case DIV_CMD_PRE_PORTA: 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; From 87ac12e14319c3e40483988eabf62dc4e826a0a2 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 13 Jul 2024 13:09:10 +0900 Subject: [PATCH 16/39] Re-split OPL4 PCM instruments due to hardware differences - MultiPCM also has level direct, so it's remained in DivInstrumentMultiPCM. everything else don't support at MultiPCM is moved into DivInstrumentOPL4PCM. - MultiPCM can't modify ADSR envelope unless reading instrument from ROM, so I decided to remove ADSR envelope macros. these are moved into OPL4 PCM instrument. - Save/Load of MultiPCM level direct parameter is still disabled until main version update (for disallow crash). - Also, this PR fixes OPL4 VGM output. --- src/engine/engine.cpp | 3 +- src/engine/instrument.cpp | 52 ++++++++++++++---- src/engine/instrument.h | 24 +++++++-- src/engine/platform/opl.cpp | 8 +-- src/engine/sysDef.cpp | 4 +- src/engine/vgmOps.cpp | 104 +++++++++++++++++++++++++++++++++--- src/gui/doAction.cpp | 6 ++- src/gui/gui.h | 1 + src/gui/guiConst.cpp | 2 + src/gui/insEdit.cpp | 46 ++++++++-------- src/gui/settings.cpp | 1 + 11 files changed, 201 insertions(+), 50 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 7ca58611b..8f631bc6c 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -938,7 +938,8 @@ void DivEngine::delUnusedSamples() { i->type==DIV_INS_C219 || i->type==DIV_INS_NDS || i->type==DIV_INS_GBA_DMA || - i->type==DIV_INS_GBA_MINMOD) { + i->type==DIV_INS_GBA_MINMOD || + i->type==DIV_INS_OPL4_PCM) { if (i->amiga.initSample>=0 && i->amiga.initSampleamiga.initSample]=true; } diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index a44572ab8..e568754f5 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -175,9 +175,6 @@ bool DivInstrumentMultiPCM::operator==(const DivInstrumentMultiPCM& other) { _C(lfo) && _C(vib) && _C(am) && - _C(damp) && - _C(pseudoReverb) && - _C(lfoReset) && _C(levelDirect) ); } @@ -270,6 +267,14 @@ bool DivInstrumentSID2::operator==(const DivInstrumentSID2& other) { ); } +bool DivInstrumentOPL4PCM::operator==(const DivInstrumentOPL4PCM& other) { + return ( + _C(damp) && + _C(pseudoReverb) && + _C(lfoReset) + ); +} + #undef _C #define CONSIDER(x,t) \ @@ -762,9 +767,6 @@ void DivInstrument::writeFeatureMP(SafeWriter* w) { w->writeC(multipcm.am); /* - w->writeC(multipcm.damp); - w->writeC(multipcm.pseudoReverb); - w->writeC(multipcm.lfoReset); w->writeC(multipcm.levelDirect); */ @@ -859,6 +861,16 @@ void DivInstrument::writeFeatureS2(SafeWriter* w) { FEATURE_END; } +void DivInstrument::writeFeatureOP(SafeWriter* w) { + FEATURE_BEGIN("OP"); + + w->writeC(opl4pcm.damp); + w->writeC(opl4pcm.pseudoReverb); + w->writeC(opl4pcm.lfoReset); + + FEATURE_END; +} + void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bool insName) { size_t blockStartSeek=0; size_t blockEndSeek=0; @@ -905,6 +917,7 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo bool featureEF=false; bool featurePN=false; bool featureS2=false; + bool featureOP=false; bool checkForWL=false; @@ -1148,6 +1161,12 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo feature64=true; featureS2=true; break; + case DIV_INS_OPL4_PCM: + featureSM=true; + featureSL=true; + featureMP=true; + featureOP=true; + break; case DIV_INS_MAX: break; case DIV_INS_NULL: @@ -1204,6 +1223,9 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo if (sid2!=defaultIns.sid2) { featureS2=true; } + if (opl4pcm!=defaultIns.opl4pcm) { + featureOP=true; + } } // check ins name @@ -1355,6 +1377,9 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo if (featureS2) { writeFeatureS2(w); } + if (featureOP) { + writeFeatureOP(w); + } if (fui && (featureSL || featureWL)) { w->write("EN",2); @@ -2077,9 +2102,6 @@ void DivInstrument::readFeatureMP(SafeReader& reader, short version) { multipcm.am=reader.readC(); /* - multipcm.damp=reader.readC(); - multipcm.pseudoReverb=reader.readC(); - multipcm.lfoReset=reader.readC(); multipcm.levelDirect=reader.readC(); */ @@ -2190,6 +2212,16 @@ void DivInstrument::readFeatureS2(SafeReader& reader, short version) { READ_FEAT_END; } +void DivInstrument::readFeatureOP(SafeReader& reader, short version) { + READ_FEAT_BEGIN; + + opl4pcm.damp=reader.readC(); + opl4pcm.pseudoReverb=reader.readC(); + opl4pcm.lfoReset=reader.readC(); + + READ_FEAT_END; +} + DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, bool fui, DivSong* song) { unsigned char featCode[2]; @@ -2265,6 +2297,8 @@ DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, b readFeaturePN(reader,version); } else if (memcmp(featCode,"S2",2)==0) { // SID2 readFeatureS2(reader,version); + } else if (memcmp(featCode,"OP",2)==0) { // OPL4 PCM + readFeatureOP(reader,version); } else { if (song==NULL && (memcmp(featCode,"SL",2)==0 || (memcmp(featCode,"WL",2)==0))) { // nothing diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 95a86755a..16f690a35 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -94,6 +94,7 @@ enum DivInstrumentType: unsigned short { DIV_INS_GBA_MINMOD=61, DIV_INS_BIFURCATOR=62, DIV_INS_SID2=63, // coincidence! + DIV_INS_OPL4_PCM=64, DIV_INS_MAX, DIV_INS_NULL }; @@ -608,7 +609,7 @@ struct DivInstrumentFDS { struct DivInstrumentMultiPCM { unsigned char ar, d1r, dl, d2r, rr, rc; unsigned char lfo, vib, am; - bool damp, pseudoReverb, lfoReset, levelDirect; + bool levelDirect; bool operator==(const DivInstrumentMultiPCM& other); bool operator!=(const DivInstrumentMultiPCM& other) { @@ -618,9 +619,6 @@ struct DivInstrumentMultiPCM { DivInstrumentMultiPCM(): ar(15), d1r(15), dl(0), d2r(0), rr(15), rc(15), lfo(0), vib(0), am(0), - damp(false), - pseudoReverb(false), - lfoReset(false), levelDirect(true) { } }; @@ -865,6 +863,21 @@ struct DivInstrumentSID2 { noiseMode(0) {} }; +struct DivInstrumentOPL4PCM { + bool damp, pseudoReverb, lfoReset; + + bool operator==(const DivInstrumentOPL4PCM& other); + bool operator!=(const DivInstrumentOPL4PCM& other) { + return !(*this==other); + } + + DivInstrumentOPL4PCM(): + damp(false), + pseudoReverb(false), + lfoReset(false) { + } +}; + struct DivInstrument { String name; DivInstrumentType type; @@ -884,6 +897,7 @@ struct DivInstrument { DivInstrumentESFM esfm; DivInstrumentPowerNoise powernoise; DivInstrumentSID2 sid2; + DivInstrumentOPL4PCM opl4pcm; /** * these are internal functions. @@ -911,6 +925,7 @@ struct DivInstrument { void writeFeatureEF(SafeWriter* w); void writeFeaturePN(SafeWriter* w); void writeFeatureS2(SafeWriter* w); + void writeFeatureOP(SafeWriter* w); void readFeatureNA(SafeReader& reader, short version); void readFeatureFM(SafeReader& reader, short version); @@ -934,6 +949,7 @@ struct DivInstrument { void readFeatureEF(SafeReader& reader, short version); void readFeaturePN(SafeReader& reader, short version); void readFeatureS2(SafeReader& reader, short version); + void readFeatureOP(SafeReader& reader, short version); DivDataErrors readInsDataOld(SafeReader& reader, short version); DivDataErrors readInsDataNew(SafeReader& reader, short version, bool fui, DivSong* song); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index f048af2c0..8a4c1916c 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -1711,7 +1711,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; } - if (ins->type==DIV_INS_MULTIPCM) { + if (ins->type==DIV_INS_OPL4_PCM) { chan[c.chan].lfo=ins->multipcm.lfo; chan[c.chan].vib=ins->multipcm.vib; chan[c.chan].am=ins->multipcm.am; @@ -1721,10 +1721,10 @@ int DivPlatformOPL::dispatch(DivCommand c) { 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; + chan[c.chan].damp=ins->opl4pcm.damp; + chan[c.chan].pseudoReverb=ins->opl4pcm.pseudoReverb; + chan[c.chan].lfoReset=ins->opl4pcm.lfoReset; } else { chan[c.chan].lfo=0; chan[c.chan].vib=0; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 94a699754..b7562277b 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1640,7 +1640,7 @@ void DivEngine::registerSystems() { {_("4OP 1"), _("FM 2"), _("4OP 3"), _("FM 4"), _("4OP 5"), _("FM 6"), _("4OP 7"), _("FM 8"), _("4OP 9"), _("FM 10"), _("4OP 11"), _("FM 12"), _("FM 13"), _("FM 14"), _("FM 15"), _("FM 16"), _("FM 17"), _("FM 18"), _("PCM 1"), _("PCM 2"), _("PCM 3"), _("PCM 4"), _("PCM 5"), _("PCM 6"), _("PCM 7"), _("PCM 8"), _("PCM 9"), _("PCM 10"), _("PCM 11"), _("PCM 12"), _("PCM 13"), _("PCM 14"), _("PCM 15"), _("PCM 16"), _("PCM 17"), _("PCM 18"), _("PCM 19"), _("PCM 20"), _("PCM 21"), _("PCM 22"), _("PCM 23"), _("PCM 24")}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P9", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"}, {DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, - {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM}, + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM}, {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, fmEffectHandlerMap, fmOPL4PostEffectHandlerMap @@ -1653,7 +1653,7 @@ void DivEngine::registerSystems() { {_("4OP 1"), _("FM 2"), _("4OP 3"), _("FM 4"), _("4OP 5"), _("FM 6"), _("4OP 7"), _("FM 8"), _("4OP 9"), _("FM 10"), _("4OP 11"), _("FM 12"), _("FM 13"), _("FM 14"), _("FM 15"), _("Kick/FM 16"), _("Snare"), _("Tom"), _("Top"), _("HiHat"), _("PCM 1"), _("PCM 2"), _("PCM 3"), _("PCM 4"), _("PCM 5"), _("PCM 6"), _("PCM 7"), _("PCM 8"), _("PCM 9"), _("PCM 10"), _("PCM 11"), _("PCM 12"), _("PCM 13"), _("PCM 14"), _("PCM 15"), _("PCM 16"), _("PCM 17"), _("PCM 18"), _("PCM 19"), _("PCM 20"), _("PCM 21"), _("PCM 22"), _("PCM 23"), _("PCM 24")}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "BD", "SD", "TM", "TP", "HH", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P9", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"}, {DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, - {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM}, + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM}, {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, fmOPLDrumsEffectHandlerMap, fmOPL4PostEffectHandlerMap diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index e0391fe6e..b3729dffe 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -615,6 +615,96 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->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; } @@ -987,13 +1077,6 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write break; } break; - case DIV_SYSTEM_OPL4: - case DIV_SYSTEM_OPL4_DRUMS: - w->writeC(0xd0|baseAddr2); - w->writeC(write.addr>>8); - w->writeC(write.addr&0xff); - w->writeC(write.val); - break; case DIV_SYSTEM_SCC: if (write.addr<0x80) { w->writeC(0xd2); @@ -1096,6 +1179,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; diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index d6d8ea434..ffcbcce5a 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -1043,7 +1043,8 @@ void FurnaceGUI::doAction(int what) { i==DIV_INS_K053260 || i==DIV_INS_C140 || i==DIV_INS_C219 || - i==DIV_INS_NDS) { + i==DIV_INS_NDS || + i==DIV_INS_OPL4_PCM) { makeInsTypeList.push_back(i); } } @@ -1572,7 +1573,8 @@ void FurnaceGUI::doAction(int what) { i==DIV_INS_C219 || i==DIV_INS_NDS || i==DIV_INS_GBA_DMA || - i==DIV_INS_GBA_MINMOD) { + i==DIV_INS_GBA_MINMOD || + i==DIV_INS_OPL4_PCM) { makeInsTypeList.push_back(i); } } diff --git a/src/gui/gui.h b/src/gui/gui.h index 51161c224..4ad5f6649 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -351,6 +351,7 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_GBA_MINMOD, GUI_COLOR_INSTR_BIFURCATOR, GUI_COLOR_INSTR_SID2, + GUI_COLOR_INSTR_OPL4_PCM, GUI_COLOR_INSTR_UNKNOWN, GUI_COLOR_CHANNEL_BG, diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index b9d9ae85d..d05e84e7d 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -184,6 +184,7 @@ const char* insTypes[DIV_INS_MAX+1][3]={ {"GBA MinMod",ICON_FA_VOLUME_UP,ICON_FUR_INS_GBA_MINMOD}, {"Bifurcator",ICON_FA_LINE_CHART,ICON_FUR_INS_BIFURCATOR}, {"SID2",ICON_FA_KEYBOARD_O,ICON_FUR_INS_SID2}, + {"OPL4 PCM",ICON_FA_VOLUME_UP,ICON_FUR_INS_MULTIPCM}, {NULL,ICON_FA_QUESTION,ICON_FA_QUESTION} }; @@ -1012,6 +1013,7 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_INSTR_GBA_MINMOD,"",ImVec4(0.5f,0.45f,0.7f,1.0f)), D(GUI_COLOR_INSTR_BIFURCATOR,"",ImVec4(0.8925f,0.8925f,0.8925f,1.0f)), D(GUI_COLOR_INSTR_SID2,"",ImVec4(0.6f,0.75f,1.0f,1.0f)), + D(GUI_COLOR_INSTR_OPL4_PCM,"",ImVec4(1.0f,0.8f,0.1f,1.0f)), D(GUI_COLOR_INSTR_UNKNOWN,"",ImVec4(0.3f,0.3f,0.3f,1.0f)), D(GUI_COLOR_CHANNEL_BG,"",ImVec4(0.4f,0.6f,0.8f,1.0f)), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 974188e96..4c0bed32a 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -6205,7 +6205,8 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_C219 || ins->type==DIV_INS_NDS || ins->type==DIV_INS_GBA_DMA || - ins->type==DIV_INS_GBA_MINMOD) { + ins->type==DIV_INS_GBA_MINMOD || + ins->type==DIV_INS_OPL4_PCM) { insTabSample(ins); } if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("Namco 163")) { @@ -6437,8 +6438,8 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndTabItem(); } - if (ins->type==DIV_INS_MULTIPCM) { - if (ImGui::BeginTabItem("MultiPCM")) { + if (ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_OPL4_PCM) { + if (ImGui::BeginTabItem(ins->type==DIV_INS_OPL4_PCM?"OPL4 PCM":"MultiPCM")) { ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale); if (ImGui::BeginTable("MultiPCMADSRParams",7,ImGuiTableFlags_NoHostExtendX)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); @@ -6519,19 +6520,12 @@ 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)); + if (ins->type==DIV_INS_OPL4_PCM) { + P(ImGui::Checkbox(_("Damp"),&ins->opl4pcm.damp)); + P(ImGui::Checkbox(_("Pseudo Reverb"),&ins->opl4pcm.pseudoReverb)); + P(ImGui::Checkbox(_("LFO Reset"),&ins->opl4pcm.lfoReset)); + } ImGui::EndTabItem(); } } @@ -7226,12 +7220,6 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc(_("LFO speed"),&ins->std.ex1Macro,0,7,160,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc(_("LFO vibrato depth"),&ins->std.fmsMacro,0,7,160,uiColors[GUI_COLOR_MACRO_PITCH])); macroList.push_back(FurnaceGUIMacroDesc(_("LFO AM depth"),&ins->std.amsMacro,0,7,160,uiColors[GUI_COLOR_MACRO_VOLUME])); - macroList.push_back(FurnaceGUIMacroDesc(_("AR"),&ins->std.ex2Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - macroList.push_back(FurnaceGUIMacroDesc(_("D1R"),&ins->std.ex3Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - macroList.push_back(FurnaceGUIMacroDesc(_("DL"),&ins->std.ex4Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - macroList.push_back(FurnaceGUIMacroDesc(_("D2R"),&ins->std.ex5Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - macroList.push_back(FurnaceGUIMacroDesc(_("Rate correction"),&ins->std.ex6Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - macroList.push_back(FurnaceGUIMacroDesc(_("RR"),&ins->std.ex7Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); break; case DIV_INS_SNES: macroList.push_back(FurnaceGUIMacroDesc(_("Volume"),&ins->std.volMacro,0,127,160,uiColors[GUI_COLOR_MACRO_VOLUME])); @@ -7525,6 +7513,22 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc(_("Noise Mode"),&ins->std.fmsMacro,0,3,64,uiColors[GUI_COLOR_MACRO_NOISE])); macroList.push_back(FurnaceGUIMacroDesc(_("Wave Mix"),&ins->std.amsMacro,0,3,64,uiColors[GUI_COLOR_MACRO_OTHER])); break; + case DIV_INS_OPL4_PCM: + macroList.push_back(FurnaceGUIMacroDesc(_("Volume"),&ins->std.volMacro,0,127,160,uiColors[GUI_COLOR_MACRO_VOLUME])); + macroList.push_back(FurnaceGUIMacroDesc(_("Arpeggio"),&ins->std.arpMacro,-120,120,160,uiColors[GUI_COLOR_MACRO_PITCH],true,NULL,macroHoverNote,false,NULL,true,ins->std.arpMacro.val)); + macroList.push_back(FurnaceGUIMacroDesc(_("Panning"),&ins->std.panLMacro,-7,7,45,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL)); + macroList.push_back(FurnaceGUIMacroDesc(_("Pitch"),&ins->std.pitchMacro,-2048,2047,160,uiColors[GUI_COLOR_MACRO_PITCH],true,macroRelativeMode)); + macroList.push_back(FurnaceGUIMacroDesc(_("Phase Reset"),&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); + macroList.push_back(FurnaceGUIMacroDesc(_("LFO speed"),&ins->std.ex1Macro,0,7,160,uiColors[GUI_COLOR_MACRO_OTHER])); + macroList.push_back(FurnaceGUIMacroDesc(_("LFO vibrato depth"),&ins->std.fmsMacro,0,7,160,uiColors[GUI_COLOR_MACRO_PITCH])); + macroList.push_back(FurnaceGUIMacroDesc(_("LFO AM depth"),&ins->std.amsMacro,0,7,160,uiColors[GUI_COLOR_MACRO_VOLUME])); + macroList.push_back(FurnaceGUIMacroDesc(_("AR"),&ins->std.ex2Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("D1R"),&ins->std.ex3Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("DL"),&ins->std.ex4Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("D2R"),&ins->std.ex5Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("Rate correction"),&ins->std.ex6Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("RR"),&ins->std.ex7Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + break; case DIV_INS_MAX: case DIV_INS_NULL: diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 63815cd02..541960ed3 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -4107,6 +4107,7 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_INSTR_GBA_MINMOD,_("GBA MinMod")); UI_COLOR_CONFIG(GUI_COLOR_INSTR_BIFURCATOR,_("Bifurcator")); UI_COLOR_CONFIG(GUI_COLOR_INSTR_SID2,_("SID2")); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_OPL4_PCM,_("OPL4 PCM")); UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,_("Other/Unknown")); ImGui::TreePop(); } From 26cf5bd097b38e7b441e0504b57bea0e7061e732 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 13 Jul 2024 13:15:40 +0900 Subject: [PATCH 17/39] Fix VGM output --- src/engine/vgmOps.cpp | 52 ++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index b3729dffe..60a22ff86 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -1351,7 +1351,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p bool writeVOXSamples=false; DivDispatch* writeADPCM_OPNA[2]={NULL,NULL}; DivDispatch* writeADPCM_OPNB[2]={NULL,NULL}; - DivDispatch* writePCM_OPL4[2]={NULL,NULL}; DivDispatch* writeADPCM_Y8950[2]={NULL,NULL}; DivDispatch* writeSegaPCM[2]={NULL,NULL}; DivDispatch* writeX1010[2]={NULL,NULL}; @@ -1365,6 +1364,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}; @@ -1804,20 +1804,6 @@ 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(12,1.0); - willExport[i]=true; - } else if (!(hasOPL4&0x40000000)) { - isSecond[i]=true; - CHIP_VOL_SECOND(12,1.0); - willExport[i]=true; - hasOPL4|=0x40000000; - howManyChips++; - } - break; case DIV_SYSTEM_SCC: case DIV_SYSTEM_SCC_PLUS: if (!hasK051649) { @@ -1977,6 +1963,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(12,1.0); + willExport[i]=true; + writePCM_OPL4[0]=disCont[i].dispatch; + } else if (!(hasOPL4&0x40000000)) { + isSecond[i]=true; + CHIP_VOL_SECOND(12,1.0); + willExport[i]=true; + writePCM_OPL4[1]=disCont[i].dispatch; + hasOPL4|=0x40000000; + howManyChips++; + } + break; default: break; } @@ -2262,16 +2264,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p w->writeI(0); w->write(writeADPCM_OPNB[i]->getSampleMem(1),writeADPCM_OPNB[i]->getSampleMemUsage(1)); } - // 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)); - } // ADPCM (Y8950) if (writeADPCM_Y8950[i]!=NULL && writeADPCM_Y8950[i]->getSampleMemUsage(0)>0) { w->writeC(0x67); @@ -2322,6 +2314,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++) { From 8ff4baa770ec2c6b63381ad41219a798c3eec29c Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 13 Jul 2024 13:18:17 +0900 Subject: [PATCH 18/39] Fix naming --- src/gui/about.cpp | 2 +- src/gui/settings.cpp | 2 +- src/main.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 18be5f5d0..f3a31038b 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -294,7 +294,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("openMSX YMF278 emulator (modified version) by the openMSX developers"), "", _N("greetings to:"), "NEOART Costa Rica", diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 541960ed3..fc02feb0f 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -190,7 +190,7 @@ const char* opl3Cores[]={ }; const char* opl4Cores[]={ - "Nuked-OPL3 + OpenMSX", + "Nuked-OPL3 + openMSX", "ymfm" }; diff --git a/src/main.cpp b/src/main.cpp index d6ba74bc2..bd0028ebc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -333,7 +333,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"); + printf("- openMSX YMF278 emulator (modified version) by the openMSX developers (GPLv2)\n"); return TA_PARAM_QUIT; } From 4ad160d6ca29a75c87211bb6e3608c2120e325c9 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 13 Jul 2024 13:30:59 +0900 Subject: [PATCH 19/39] Fix description --- src/gui/settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index fc02feb0f..30b3c5212 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -190,7 +190,7 @@ const char* opl3Cores[]={ }; const char* opl4Cores[]={ - "Nuked-OPL3 + openMSX", + "Nuked-OPL3 (FM) + openMSX (PCM)", "ymfm" }; From 00e9ca29fa2aed6b608717391d77c6626cf7c2f0 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 13 Jul 2024 16:28:20 +0900 Subject: [PATCH 20/39] Revert "Re-split OPL4 PCM instruments due to hardware differences" This reverts commit 87ac12e14319c3e40483988eabf62dc4e826a0a2. --- src/engine/engine.cpp | 3 +-- src/engine/instrument.cpp | 52 +++++++------------------------------ src/engine/instrument.h | 24 +++-------------- src/engine/platform/opl.cpp | 8 +++--- src/engine/sysDef.cpp | 4 +-- src/engine/vgmOps.cpp | 4 +-- src/gui/doAction.cpp | 6 ++--- src/gui/gui.h | 1 - src/gui/guiConst.cpp | 2 -- src/gui/insEdit.cpp | 46 +++++++++++++++----------------- src/gui/settings.cpp | 1 - 11 files changed, 45 insertions(+), 106 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 8f631bc6c..7ca58611b 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -938,8 +938,7 @@ void DivEngine::delUnusedSamples() { i->type==DIV_INS_C219 || i->type==DIV_INS_NDS || i->type==DIV_INS_GBA_DMA || - i->type==DIV_INS_GBA_MINMOD || - i->type==DIV_INS_OPL4_PCM) { + i->type==DIV_INS_GBA_MINMOD) { if (i->amiga.initSample>=0 && i->amiga.initSampleamiga.initSample]=true; } diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index e568754f5..a44572ab8 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -175,6 +175,9 @@ bool DivInstrumentMultiPCM::operator==(const DivInstrumentMultiPCM& other) { _C(lfo) && _C(vib) && _C(am) && + _C(damp) && + _C(pseudoReverb) && + _C(lfoReset) && _C(levelDirect) ); } @@ -267,14 +270,6 @@ bool DivInstrumentSID2::operator==(const DivInstrumentSID2& other) { ); } -bool DivInstrumentOPL4PCM::operator==(const DivInstrumentOPL4PCM& other) { - return ( - _C(damp) && - _C(pseudoReverb) && - _C(lfoReset) - ); -} - #undef _C #define CONSIDER(x,t) \ @@ -767,6 +762,9 @@ void DivInstrument::writeFeatureMP(SafeWriter* w) { w->writeC(multipcm.am); /* + w->writeC(multipcm.damp); + w->writeC(multipcm.pseudoReverb); + w->writeC(multipcm.lfoReset); w->writeC(multipcm.levelDirect); */ @@ -861,16 +859,6 @@ void DivInstrument::writeFeatureS2(SafeWriter* w) { FEATURE_END; } -void DivInstrument::writeFeatureOP(SafeWriter* w) { - FEATURE_BEGIN("OP"); - - w->writeC(opl4pcm.damp); - w->writeC(opl4pcm.pseudoReverb); - w->writeC(opl4pcm.lfoReset); - - FEATURE_END; -} - void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bool insName) { size_t blockStartSeek=0; size_t blockEndSeek=0; @@ -917,7 +905,6 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo bool featureEF=false; bool featurePN=false; bool featureS2=false; - bool featureOP=false; bool checkForWL=false; @@ -1161,12 +1148,6 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo feature64=true; featureS2=true; break; - case DIV_INS_OPL4_PCM: - featureSM=true; - featureSL=true; - featureMP=true; - featureOP=true; - break; case DIV_INS_MAX: break; case DIV_INS_NULL: @@ -1223,9 +1204,6 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo if (sid2!=defaultIns.sid2) { featureS2=true; } - if (opl4pcm!=defaultIns.opl4pcm) { - featureOP=true; - } } // check ins name @@ -1377,9 +1355,6 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo if (featureS2) { writeFeatureS2(w); } - if (featureOP) { - writeFeatureOP(w); - } if (fui && (featureSL || featureWL)) { w->write("EN",2); @@ -2102,6 +2077,9 @@ void DivInstrument::readFeatureMP(SafeReader& reader, short version) { multipcm.am=reader.readC(); /* + multipcm.damp=reader.readC(); + multipcm.pseudoReverb=reader.readC(); + multipcm.lfoReset=reader.readC(); multipcm.levelDirect=reader.readC(); */ @@ -2212,16 +2190,6 @@ void DivInstrument::readFeatureS2(SafeReader& reader, short version) { READ_FEAT_END; } -void DivInstrument::readFeatureOP(SafeReader& reader, short version) { - READ_FEAT_BEGIN; - - opl4pcm.damp=reader.readC(); - opl4pcm.pseudoReverb=reader.readC(); - opl4pcm.lfoReset=reader.readC(); - - READ_FEAT_END; -} - DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, bool fui, DivSong* song) { unsigned char featCode[2]; @@ -2297,8 +2265,6 @@ DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, b readFeaturePN(reader,version); } else if (memcmp(featCode,"S2",2)==0) { // SID2 readFeatureS2(reader,version); - } else if (memcmp(featCode,"OP",2)==0) { // OPL4 PCM - readFeatureOP(reader,version); } else { if (song==NULL && (memcmp(featCode,"SL",2)==0 || (memcmp(featCode,"WL",2)==0))) { // nothing diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 16f690a35..95a86755a 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -94,7 +94,6 @@ enum DivInstrumentType: unsigned short { DIV_INS_GBA_MINMOD=61, DIV_INS_BIFURCATOR=62, DIV_INS_SID2=63, // coincidence! - DIV_INS_OPL4_PCM=64, DIV_INS_MAX, DIV_INS_NULL }; @@ -609,7 +608,7 @@ struct DivInstrumentFDS { struct DivInstrumentMultiPCM { unsigned char ar, d1r, dl, d2r, rr, rc; unsigned char lfo, vib, am; - bool levelDirect; + bool damp, pseudoReverb, lfoReset, levelDirect; bool operator==(const DivInstrumentMultiPCM& other); bool operator!=(const DivInstrumentMultiPCM& other) { @@ -619,6 +618,9 @@ struct DivInstrumentMultiPCM { DivInstrumentMultiPCM(): ar(15), d1r(15), dl(0), d2r(0), rr(15), rc(15), lfo(0), vib(0), am(0), + damp(false), + pseudoReverb(false), + lfoReset(false), levelDirect(true) { } }; @@ -863,21 +865,6 @@ struct DivInstrumentSID2 { noiseMode(0) {} }; -struct DivInstrumentOPL4PCM { - bool damp, pseudoReverb, lfoReset; - - bool operator==(const DivInstrumentOPL4PCM& other); - bool operator!=(const DivInstrumentOPL4PCM& other) { - return !(*this==other); - } - - DivInstrumentOPL4PCM(): - damp(false), - pseudoReverb(false), - lfoReset(false) { - } -}; - struct DivInstrument { String name; DivInstrumentType type; @@ -897,7 +884,6 @@ struct DivInstrument { DivInstrumentESFM esfm; DivInstrumentPowerNoise powernoise; DivInstrumentSID2 sid2; - DivInstrumentOPL4PCM opl4pcm; /** * these are internal functions. @@ -925,7 +911,6 @@ struct DivInstrument { void writeFeatureEF(SafeWriter* w); void writeFeaturePN(SafeWriter* w); void writeFeatureS2(SafeWriter* w); - void writeFeatureOP(SafeWriter* w); void readFeatureNA(SafeReader& reader, short version); void readFeatureFM(SafeReader& reader, short version); @@ -949,7 +934,6 @@ struct DivInstrument { void readFeatureEF(SafeReader& reader, short version); void readFeaturePN(SafeReader& reader, short version); void readFeatureS2(SafeReader& reader, short version); - void readFeatureOP(SafeReader& reader, short version); DivDataErrors readInsDataOld(SafeReader& reader, short version); DivDataErrors readInsDataNew(SafeReader& reader, short version, bool fui, DivSong* song); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 8a4c1916c..f048af2c0 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -1711,7 +1711,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; } - if (ins->type==DIV_INS_OPL4_PCM) { + 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; @@ -1721,10 +1721,10 @@ int DivPlatformOPL::dispatch(DivCommand c) { 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].damp=ins->opl4pcm.damp; - chan[c.chan].pseudoReverb=ins->opl4pcm.pseudoReverb; - chan[c.chan].lfoReset=ins->opl4pcm.lfoReset; + chan[c.chan].lfoReset=ins->multipcm.lfoReset; } else { chan[c.chan].lfo=0; chan[c.chan].vib=0; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index b7562277b..94a699754 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1640,7 +1640,7 @@ void DivEngine::registerSystems() { {_("4OP 1"), _("FM 2"), _("4OP 3"), _("FM 4"), _("4OP 5"), _("FM 6"), _("4OP 7"), _("FM 8"), _("4OP 9"), _("FM 10"), _("4OP 11"), _("FM 12"), _("FM 13"), _("FM 14"), _("FM 15"), _("FM 16"), _("FM 17"), _("FM 18"), _("PCM 1"), _("PCM 2"), _("PCM 3"), _("PCM 4"), _("PCM 5"), _("PCM 6"), _("PCM 7"), _("PCM 8"), _("PCM 9"), _("PCM 10"), _("PCM 11"), _("PCM 12"), _("PCM 13"), _("PCM 14"), _("PCM 15"), _("PCM 16"), _("PCM 17"), _("PCM 18"), _("PCM 19"), _("PCM 20"), _("PCM 21"), _("PCM 22"), _("PCM 23"), _("PCM 24")}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P9", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"}, {DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, - {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM}, + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM}, {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, fmEffectHandlerMap, fmOPL4PostEffectHandlerMap @@ -1653,7 +1653,7 @@ void DivEngine::registerSystems() { {_("4OP 1"), _("FM 2"), _("4OP 3"), _("FM 4"), _("4OP 5"), _("FM 6"), _("4OP 7"), _("FM 8"), _("4OP 9"), _("FM 10"), _("4OP 11"), _("FM 12"), _("FM 13"), _("FM 14"), _("FM 15"), _("Kick/FM 16"), _("Snare"), _("Tom"), _("Top"), _("HiHat"), _("PCM 1"), _("PCM 2"), _("PCM 3"), _("PCM 4"), _("PCM 5"), _("PCM 6"), _("PCM 7"), _("PCM 8"), _("PCM 9"), _("PCM 10"), _("PCM 11"), _("PCM 12"), _("PCM 13"), _("PCM 14"), _("PCM 15"), _("PCM 16"), _("PCM 17"), _("PCM 18"), _("PCM 19"), _("PCM 20"), _("PCM 21"), _("PCM 22"), _("PCM 23"), _("PCM 24")}, {"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "BD", "SD", "TM", "TP", "HH", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P9", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"}, {DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_OP, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_NOISE, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM}, - {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM, DIV_INS_OPL4_PCM}, + {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM, DIV_INS_MULTIPCM}, {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, fmOPLDrumsEffectHandlerMap, fmOPL4PostEffectHandlerMap diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 60a22ff86..d4f8a8495 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -1967,12 +1967,12 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p case DIV_SYSTEM_OPL4_DRUMS: if (!hasOPL4) { hasOPL4=disCont[i].dispatch->chipClock; - CHIP_VOL(12,1.0); + CHIP_VOL(13,1.0); willExport[i]=true; writePCM_OPL4[0]=disCont[i].dispatch; } else if (!(hasOPL4&0x40000000)) { isSecond[i]=true; - CHIP_VOL_SECOND(12,1.0); + CHIP_VOL_SECOND(13,1.0); willExport[i]=true; writePCM_OPL4[1]=disCont[i].dispatch; hasOPL4|=0x40000000; diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index ffcbcce5a..d6d8ea434 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -1043,8 +1043,7 @@ void FurnaceGUI::doAction(int what) { i==DIV_INS_K053260 || i==DIV_INS_C140 || i==DIV_INS_C219 || - i==DIV_INS_NDS || - i==DIV_INS_OPL4_PCM) { + i==DIV_INS_NDS) { makeInsTypeList.push_back(i); } } @@ -1573,8 +1572,7 @@ void FurnaceGUI::doAction(int what) { i==DIV_INS_C219 || i==DIV_INS_NDS || i==DIV_INS_GBA_DMA || - i==DIV_INS_GBA_MINMOD || - i==DIV_INS_OPL4_PCM) { + i==DIV_INS_GBA_MINMOD) { makeInsTypeList.push_back(i); } } diff --git a/src/gui/gui.h b/src/gui/gui.h index 4ad5f6649..51161c224 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -351,7 +351,6 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_GBA_MINMOD, GUI_COLOR_INSTR_BIFURCATOR, GUI_COLOR_INSTR_SID2, - GUI_COLOR_INSTR_OPL4_PCM, GUI_COLOR_INSTR_UNKNOWN, GUI_COLOR_CHANNEL_BG, diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index d05e84e7d..b9d9ae85d 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -184,7 +184,6 @@ const char* insTypes[DIV_INS_MAX+1][3]={ {"GBA MinMod",ICON_FA_VOLUME_UP,ICON_FUR_INS_GBA_MINMOD}, {"Bifurcator",ICON_FA_LINE_CHART,ICON_FUR_INS_BIFURCATOR}, {"SID2",ICON_FA_KEYBOARD_O,ICON_FUR_INS_SID2}, - {"OPL4 PCM",ICON_FA_VOLUME_UP,ICON_FUR_INS_MULTIPCM}, {NULL,ICON_FA_QUESTION,ICON_FA_QUESTION} }; @@ -1013,7 +1012,6 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_INSTR_GBA_MINMOD,"",ImVec4(0.5f,0.45f,0.7f,1.0f)), D(GUI_COLOR_INSTR_BIFURCATOR,"",ImVec4(0.8925f,0.8925f,0.8925f,1.0f)), D(GUI_COLOR_INSTR_SID2,"",ImVec4(0.6f,0.75f,1.0f,1.0f)), - D(GUI_COLOR_INSTR_OPL4_PCM,"",ImVec4(1.0f,0.8f,0.1f,1.0f)), D(GUI_COLOR_INSTR_UNKNOWN,"",ImVec4(0.3f,0.3f,0.3f,1.0f)), D(GUI_COLOR_CHANNEL_BG,"",ImVec4(0.4f,0.6f,0.8f,1.0f)), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 4c0bed32a..974188e96 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -6205,8 +6205,7 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_C219 || ins->type==DIV_INS_NDS || ins->type==DIV_INS_GBA_DMA || - ins->type==DIV_INS_GBA_MINMOD || - ins->type==DIV_INS_OPL4_PCM) { + ins->type==DIV_INS_GBA_MINMOD) { insTabSample(ins); } if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("Namco 163")) { @@ -6438,8 +6437,8 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndTabItem(); } - if (ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_OPL4_PCM) { - if (ImGui::BeginTabItem(ins->type==DIV_INS_OPL4_PCM?"OPL4 PCM":"MultiPCM")) { + if (ins->type==DIV_INS_MULTIPCM) { + if (ImGui::BeginTabItem("MultiPCM")) { ImVec2 sliderSize=ImVec2(20.0f*dpiScale,128.0*dpiScale); if (ImGui::BeginTable("MultiPCMADSRParams",7,ImGuiTableFlags_NoHostExtendX)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,sliderSize.x); @@ -6520,12 +6519,19 @@ void FurnaceGUI::drawInsEdit() { P(CWSliderScalar(_("AM Depth"),ImGuiDataType_U8,&ins->multipcm.am,&_ZERO,&_SEVEN)); rightClickable ImGui::EndTable(); } - P(ImGui::Checkbox(_("Level Direct"),&ins->multipcm.levelDirect)); - if (ins->type==DIV_INS_OPL4_PCM) { - P(ImGui::Checkbox(_("Damp"),&ins->opl4pcm.damp)); - P(ImGui::Checkbox(_("Pseudo Reverb"),&ins->opl4pcm.pseudoReverb)); - P(ImGui::Checkbox(_("LFO Reset"),&ins->opl4pcm.lfoReset)); + 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(); } } @@ -7220,6 +7226,12 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc(_("LFO speed"),&ins->std.ex1Macro,0,7,160,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc(_("LFO vibrato depth"),&ins->std.fmsMacro,0,7,160,uiColors[GUI_COLOR_MACRO_PITCH])); macroList.push_back(FurnaceGUIMacroDesc(_("LFO AM depth"),&ins->std.amsMacro,0,7,160,uiColors[GUI_COLOR_MACRO_VOLUME])); + macroList.push_back(FurnaceGUIMacroDesc(_("AR"),&ins->std.ex2Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("D1R"),&ins->std.ex3Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("DL"),&ins->std.ex4Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("D2R"),&ins->std.ex5Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("Rate correction"),&ins->std.ex6Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + macroList.push_back(FurnaceGUIMacroDesc(_("RR"),&ins->std.ex7Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); break; case DIV_INS_SNES: macroList.push_back(FurnaceGUIMacroDesc(_("Volume"),&ins->std.volMacro,0,127,160,uiColors[GUI_COLOR_MACRO_VOLUME])); @@ -7513,22 +7525,6 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc(_("Noise Mode"),&ins->std.fmsMacro,0,3,64,uiColors[GUI_COLOR_MACRO_NOISE])); macroList.push_back(FurnaceGUIMacroDesc(_("Wave Mix"),&ins->std.amsMacro,0,3,64,uiColors[GUI_COLOR_MACRO_OTHER])); break; - case DIV_INS_OPL4_PCM: - macroList.push_back(FurnaceGUIMacroDesc(_("Volume"),&ins->std.volMacro,0,127,160,uiColors[GUI_COLOR_MACRO_VOLUME])); - macroList.push_back(FurnaceGUIMacroDesc(_("Arpeggio"),&ins->std.arpMacro,-120,120,160,uiColors[GUI_COLOR_MACRO_PITCH],true,NULL,macroHoverNote,false,NULL,true,ins->std.arpMacro.val)); - macroList.push_back(FurnaceGUIMacroDesc(_("Panning"),&ins->std.panLMacro,-7,7,45,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL)); - macroList.push_back(FurnaceGUIMacroDesc(_("Pitch"),&ins->std.pitchMacro,-2048,2047,160,uiColors[GUI_COLOR_MACRO_PITCH],true,macroRelativeMode)); - macroList.push_back(FurnaceGUIMacroDesc(_("Phase Reset"),&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); - macroList.push_back(FurnaceGUIMacroDesc(_("LFO speed"),&ins->std.ex1Macro,0,7,160,uiColors[GUI_COLOR_MACRO_OTHER])); - macroList.push_back(FurnaceGUIMacroDesc(_("LFO vibrato depth"),&ins->std.fmsMacro,0,7,160,uiColors[GUI_COLOR_MACRO_PITCH])); - macroList.push_back(FurnaceGUIMacroDesc(_("LFO AM depth"),&ins->std.amsMacro,0,7,160,uiColors[GUI_COLOR_MACRO_VOLUME])); - macroList.push_back(FurnaceGUIMacroDesc(_("AR"),&ins->std.ex2Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - macroList.push_back(FurnaceGUIMacroDesc(_("D1R"),&ins->std.ex3Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - macroList.push_back(FurnaceGUIMacroDesc(_("DL"),&ins->std.ex4Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - macroList.push_back(FurnaceGUIMacroDesc(_("D2R"),&ins->std.ex5Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - macroList.push_back(FurnaceGUIMacroDesc(_("Rate correction"),&ins->std.ex6Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - macroList.push_back(FurnaceGUIMacroDesc(_("RR"),&ins->std.ex7Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - break; case DIV_INS_MAX: case DIV_INS_NULL: diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 30b3c5212..239d550bb 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -4107,7 +4107,6 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_INSTR_GBA_MINMOD,_("GBA MinMod")); UI_COLOR_CONFIG(GUI_COLOR_INSTR_BIFURCATOR,_("Bifurcator")); UI_COLOR_CONFIG(GUI_COLOR_INSTR_SID2,_("SID2")); - UI_COLOR_CONFIG(GUI_COLOR_INSTR_OPL4_PCM,_("OPL4 PCM")); UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,_("Other/Unknown")); ImGui::TreePop(); } From 0e3b1f0bcad52d7ceb52dfb17da72c48f7a17b17 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 13 Jul 2024 21:44:40 +0900 Subject: [PATCH 21/39] Reduce unnecessary change --- src/engine/instrument.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index a44572ab8..e1a4b57c9 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -2190,7 +2190,6 @@ void DivInstrument::readFeatureS2(SafeReader& reader, short version) { READ_FEAT_END; } - DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, bool fui, DivSong* song) { unsigned char featCode[2]; bool volIsCutoff=false; From 55c343bd39d74b5227323400163b5a0619ebeadf Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 13 Jul 2024 21:47:05 +0900 Subject: [PATCH 22/39] Remove outdated note --- src/engine/platform/opl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index f048af2c0..b8e02bd01 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -1008,7 +1008,7 @@ void DivPlatformOPL::tick(bool sysTick) { chan[i].freqChanged=true; } - if (chan[i].std.phaseReset.had) { // TODO: not working + if (chan[i].std.phaseReset.had) { if (chan[i].std.phaseReset.val==1 && chan[i].active) { chan[i].keyOn=true; chan[i].writeCtrl=true; From e3daca9c83508c303dad57807afc5614f0f84879 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 13 Jul 2024 18:31:28 -0500 Subject: [PATCH 23/39] get rid of these macros --- src/gui/insEdit.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 974188e96..a375b2e21 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -7223,15 +7223,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 vibrato depth"),&ins->std.fmsMacro,0,7,160,uiColors[GUI_COLOR_MACRO_PITCH])); - macroList.push_back(FurnaceGUIMacroDesc(_("LFO AM depth"),&ins->std.amsMacro,0,7,160,uiColors[GUI_COLOR_MACRO_VOLUME])); - macroList.push_back(FurnaceGUIMacroDesc(_("AR"),&ins->std.ex2Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - macroList.push_back(FurnaceGUIMacroDesc(_("D1R"),&ins->std.ex3Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - macroList.push_back(FurnaceGUIMacroDesc(_("DL"),&ins->std.ex4Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - macroList.push_back(FurnaceGUIMacroDesc(_("D2R"),&ins->std.ex5Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - macroList.push_back(FurnaceGUIMacroDesc(_("Rate correction"),&ins->std.ex6Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); - macroList.push_back(FurnaceGUIMacroDesc(_("RR"),&ins->std.ex7Macro,0,15,160,uiColors[GUI_COLOR_MACRO_ENVELOPE])); + 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])); From c90c05fde307b4bff08f60f34f1fc20dad573ac7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 13 Jul 2024 19:15:23 -0500 Subject: [PATCH 24/39] rename a couple things --- src/engine/dispatch.h | 30 +++++++++++++++--------------- src/engine/platform/opl.cpp | 30 +++++++++++++++--------------- src/engine/playback.cpp | 30 +++++++++++++++--------------- src/engine/sysDef.cpp | 30 +++++++++++++++--------------- 4 files changed, 60 insertions(+), 60 deletions(-) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index a3ed1400c..ec4d11b2f 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -263,21 +263,21 @@ enum DivDispatchCmds { DIV_CMD_BIFURCATOR_STATE_LOAD, DIV_CMD_BIFURCATOR_PARAMETER, - DIV_CMD_OPL4_PCM_MIX_FM, // (value) - DIV_CMD_OPL4_PCM_MIX_PCM, // (value) - DIV_CMD_OPL4_PCM_LFO, // (value) - DIV_CMD_OPL4_PCM_VIB, // (value) - DIV_CMD_OPL4_PCM_AM, // (value) - DIV_CMD_OPL4_PCM_AR, // (value) - DIV_CMD_OPL4_PCM_D1R, // (value) - DIV_CMD_OPL4_PCM_DL, // (value) - DIV_CMD_OPL4_PCM_D2R, // (value) - DIV_CMD_OPL4_PCM_RC, // (value) - DIV_CMD_OPL4_PCM_RR, // (value) - DIV_CMD_OPL4_PCM_DAMP, // (value) - DIV_CMD_OPL4_PCM_PSEUDO_REVERB, // (value) - DIV_CMD_OPL4_PCM_LFO_RESET, // (value) - DIV_CMD_OPL4_PCM_LEVEL_DIRECT, // (value) + DIV_CMD_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/platform/opl.cpp b/src/engine/platform/opl.cpp index b8e02bd01..4a034bef4 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -2427,91 +2427,91 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (c.chan==adpcmChan) break; chan[c.chan].hardReset=c.value; break; - case DIV_CMD_OPL4_PCM_MIX_FM: + case DIV_CMD_MULTIPCM_MIX_FM: if (chipType==4) { rWrite(PCM_ADDR_MIX_FM,(CLAMP((0x70-(c.value&0x70)),0,0x70)>>1)|(CLAMP((7-(c.value&7)),0,7))); } break; - case DIV_CMD_OPL4_PCM_MIX_PCM: + case DIV_CMD_MULTIPCM_MIX_PCM: if (chipType==4) { rWrite(PCM_ADDR_MIX_PCM,(CLAMP((0x70-(c.value&0x70)),0,0x70)>>1)|(CLAMP((7-(c.value&7)),0,7))); } break; - case DIV_CMD_OPL4_PCM_LFO: + 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_OPL4_PCM_VIB: + 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_OPL4_PCM_AM: + 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_OPL4_PCM_AR: + 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_OPL4_PCM_D1R: + 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_OPL4_PCM_DL: + 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_OPL4_PCM_D2R: + 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_OPL4_PCM_RC: + 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_OPL4_PCM_RR: + 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_OPL4_PCM_DAMP: + 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_OPL4_PCM_PSEUDO_REVERB: + 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_OPL4_PCM_LFO_RESET: + 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_OPL4_PCM_LEVEL_DIRECT: + 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)); } diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 9ee939029..5443fa25b 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -263,21 +263,21 @@ const char* cmdName[]={ "BIFURCATOR_STATE_LOAD", "BIFURCATOR_PARAMETER", - "OPL4_MIX_FM", - "OPL4_MIX_PCM", - "OPL4_LFO", - "OPL4_VIB", - "OPL4_AM", - "OPL4_AR", - "OPL4_D1R", - "OPL4_DL", - "OPL4_D2R", - "OPL4_RR", - "OPL4_RC", - "OPL4_DAMP", - "OPL4_PSEUDO_REVERB", - "OPL4_LFO_RESET", - "OPL4_LEVEL_DIRECT" + "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/sysDef.cpp b/src/engine/sysDef.cpp index 94a699754..bdadfa11f 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -601,21 +601,21 @@ void DivEngine::registerSystems() { EffectHandlerMap fmOPL4PostEffectHandlerMap(fmOPLPostEffectHandlerMap); fmOPL4PostEffectHandlerMap.insert({ - {0x1e, {DIV_CMD_OPL4_PCM_MIX_FM, _("1Exy: FM global level (left, right; 0 to 7)"), effectVal}}, - {0x1f, {DIV_CMD_OPL4_PCM_MIX_PCM, _("1Fxy: PCM global level (left, right; 0 to 7)"), effectVal}}, - {0x20, {DIV_CMD_OPL4_PCM_LFO, _("20xx: PCM LFO Rate (0 to 7)"), effectValAnd<7>}}, - {0x21, {DIV_CMD_OPL4_PCM_VIB, _("21xx: PCM LFO PM Depth (0 to 7)"), effectValAnd<7>}}, - {0x22, {DIV_CMD_OPL4_PCM_AM, _("22xx: PCM LFO AM Depth (0 to 7)"), effectValAnd<7>}}, - {0x23, {DIV_CMD_OPL4_PCM_AR, _("23xx: PCM Attack Rate (0 to 15)"), effectValAnd<15>}}, - {0x24, {DIV_CMD_OPL4_PCM_D1R, _("24xx: PCM Decay 1 Rate (0 to 15)"), effectValAnd<15>}}, - {0x25, {DIV_CMD_OPL4_PCM_DL, _("25xx: PCM Decay Level (0 to 15)"), effectValAnd<15>}}, - {0x26, {DIV_CMD_OPL4_PCM_D2R, _("26xx: PCM Decay 2 Rate (0 to 15)"), effectValAnd<15>}}, - {0x27, {DIV_CMD_OPL4_PCM_RC, _("27xx: PCM Release Rate (0 to 15)"), effectValAnd<15>}}, - {0x28, {DIV_CMD_OPL4_PCM_RR, _("28xx: PCM Rate Correction (0 to 15)"), effectValAnd<15>}}, - {0x2c, {DIV_CMD_OPL4_PCM_DAMP, _("2Cxx: PCM Damp"), effectValAnd<1>}}, - {0x2d, {DIV_CMD_OPL4_PCM_PSEUDO_REVERB, _("2Dxx: PCM Pseudo Reverb"), effectValAnd<1>}}, - {0x2e, {DIV_CMD_OPL4_PCM_LFO_RESET, _("2Exx: PCM LFO Reset"), effectValAnd<1>}}, - {0x2f, {DIV_CMD_OPL4_PCM_LEVEL_DIRECT, _("2Fxx: PCM Level Direct"), effectValAnd<1>}}, + {0x1e, {DIV_CMD_MULTIPCM_MIX_FM, _("1Exy: FM global level (left, right; 0 to 7)"), effectVal}}, + {0x1f, {DIV_CMD_MULTIPCM_MIX_PCM, _("1Fxy: PCM global level (left, 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_RC, _("27xx: PCM Release Rate (0 to 15)"), effectValAnd<15>}}, + {0x28, {DIV_CMD_MULTIPCM_RR, _("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={ From 22e02ece242ef59cb401d5dce2f860fd7f134e1c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 13 Jul 2024 19:40:47 -0500 Subject: [PATCH 25/39] DIV_INS_MULTIPCM on note on --- src/engine/platform/opl.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 4a034bef4..24b758003 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -1693,8 +1693,8 @@ int DivPlatformOPL::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { if (PCM_CHECK(c.chan)) { // OPL4 PCM - DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); - chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:127; + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_MULTIPCM); + chan[c.chan].macroVolMul=ins->type==DIV_INS_MULTIPCM?64:127; if (c.value!=DIV_NOTE_NULL) { chan[c.chan].sample=ins->amiga.getSample(c.value); chan[c.chan].sampleNote=c.value; @@ -2070,6 +2070,7 @@ 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); From cf341b39a3ebcce80f06780d96b84ab2dc8d92fe Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 14 Jul 2024 11:57:01 +0900 Subject: [PATCH 26/39] Fix macro volume multiplier (127 for MultiPCM, 64 for Amiga) --- src/engine/platform/opl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 24b758003..f831d3887 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -1694,7 +1694,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { case DIV_CMD_NOTE_ON: { 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_MULTIPCM?64:127; + 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; From 5977e653fd7969647a3baf11a0b69774fa4e628f Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 14 Jul 2024 15:26:06 +0900 Subject: [PATCH 27/39] Add sample length warning for OPL4 PCM --- src/gui/sampleEdit.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 88337e39c..573ba85b9 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -402,6 +402,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; default: break; } From c58a232cd5e5f089d7a15ad9edd4393c40802ebe Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 15 Jul 2024 18:55:59 +0900 Subject: [PATCH 28/39] Add notes --- src/engine/platform/opl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index f831d3887..9d00d5289 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -2667,7 +2667,7 @@ DivDispatchOscBuffer* DivPlatformOPL::getOscBuffer(int ch) { } int DivPlatformOPL::mapVelocity(int ch, float vel) { - if (PCM_CHECK(ch)) return vel*127.0; + if (PCM_CHECK(ch)) return vel*127.0; // TODO: Covert to log, -0.375dB per step if (ch==adpcmChan) return vel*255.0; // -0.75dB per step // -6: 64: 8 From 49c214f72eba474b4e2b78a71b5a98542908dd48 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 15 Jul 2024 19:33:32 +0900 Subject: [PATCH 29/39] Legato and mapVelocity, TODO: correct? --- src/engine/platform/opl.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 9d00d5289..e6912cefb 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -2076,7 +2076,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { commitState(c.chan,ins); chan[c.chan].insChanged=false; } - chan[c.chan].baseFreq=(PCM_CHECK(c.chan))?NOTE_PCM(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; @@ -2667,7 +2667,19 @@ DivDispatchOscBuffer* DivPlatformOPL::getOscBuffer(int ch) { } int DivPlatformOPL::mapVelocity(int ch, float vel) { - if (PCM_CHECK(ch)) return vel*127.0; // TODO: Covert to log, -0.375dB per step + 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 From 4174be22c15f2af75060360aaa67d5cc9ba144c2 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 17 Jul 2024 18:15:23 +0900 Subject: [PATCH 30/39] Synchronize the OPL4 PCM per-channel OSC volume between the openMSX core and the ymfm core. Also add psikyo arcade presets. --- src/engine/platform/opl.cpp | 2 +- src/gui/presets.cpp | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index e6912cefb..55086c71d 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -646,7 +646,7 @@ void DivPlatformOPL::acquire_ymfm4(short** buf, size_t len) { 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,-32768,32767); + oscBuf[oscOffs]->data[oscBuf[oscOffs]->needle++]=CLAMP(chOut<<1,-32768,32767); } } } diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 274e2d7c5..e135ad847 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -1847,6 +1847,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", {} ); From 346d34c6e7a377dc15e717a7e9c894aac685abf6 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 17 Jul 2024 18:26:40 +0900 Subject: [PATCH 31/39] Fix OPL4 mixer volume, mixer effect description (x: Left, y: Right) --- src/engine/platform/opl.cpp | 15 +++++++++++++-- src/engine/platform/opl.h | 1 + src/engine/sysDef.cpp | 4 ++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 55086c71d..6405c6b72 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -2430,12 +2430,16 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; case DIV_CMD_MULTIPCM_MIX_FM: if (chipType==4) { - rWrite(PCM_ADDR_MIX_FM,(CLAMP((0x70-(c.value&0x70)),0,0x70)>>1)|(CLAMP((7-(c.value&7)),0,7))); + 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) { - rWrite(PCM_ADDR_MIX_PCM,(CLAMP((0x70-(c.value&0x70)),0,0x70)>>1)|(CLAMP((7-(c.value&7)),0,7))); + 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: @@ -2844,6 +2848,13 @@ void DivPlatformOPL::reset() { 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); } diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index e62f5ec84..453fa6786 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -134,6 +134,7 @@ class DivPlatformOPL: public DivDispatch { const unsigned char* outChanMap; int chipFreqBase, chipRateBase; 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]; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index bdadfa11f..6ba055993 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -601,8 +601,8 @@ void DivEngine::registerSystems() { EffectHandlerMap fmOPL4PostEffectHandlerMap(fmOPLPostEffectHandlerMap); fmOPL4PostEffectHandlerMap.insert({ - {0x1e, {DIV_CMD_MULTIPCM_MIX_FM, _("1Exy: FM global level (left, right; 0 to 7)"), effectVal}}, - {0x1f, {DIV_CMD_MULTIPCM_MIX_PCM, _("1Fxy: PCM global level (left, right; 0 to 7)"), effectVal}}, + {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>}}, From fed00609f8ebec55038cc0ec1bbeb3a095ad633b Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 17 Jul 2024 18:38:25 +0900 Subject: [PATCH 32/39] Add output description --- src/engine/platform/opl.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 6405c6b72..63e65817a 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -228,12 +228,12 @@ void DivPlatformOPL::acquire_nuked(short** buf, size_t len) { } if (chipType==4) { pcm.generateMix(o[0],o[1],o[4],o[5],o[6],o[7],pcmBuf); - os[0]+=o[4]; - os[1]+=o[5]; - os[2]+=o[2]; - os[3]+=o[3]; - os[4]+=o[6]; - os[5]+=o[7]; + 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]; From 9d1ad2642ccbcfb779b62dbb96a9f234d0d2507d Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 19 Jul 2024 11:12:11 +0900 Subject: [PATCH 33/39] Fix command (RR is release, RC is rate correction) --- src/engine/sysDef.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 6ba055993..3414b8608 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -610,8 +610,8 @@ void DivEngine::registerSystems() { {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_RC, _("27xx: PCM Release Rate (0 to 15)"), effectValAnd<15>}}, - {0x28, {DIV_CMD_MULTIPCM_RR, _("28xx: PCM Rate Correction (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>}}, From 58a596ecb09ac80bddbde909029f3fb8eedba843 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 21 Jul 2024 17:32:23 +0900 Subject: [PATCH 34/39] Implement resetMacroOnPorta in OPL4 PCM --- src/engine/platform/opl.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 63e65817a..ca017a004 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -2537,6 +2537,9 @@ int DivPlatformOPL::dispatch(DivCommand c) { 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=(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))); From db3b102e1d253e88570b359531b9bee6396545f6 Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 23 Jul 2024 18:04:43 +0900 Subject: [PATCH 35/39] Correct getGain for OPL4 PCM --- src/engine/platform/opl.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 12ba6f991..33e610f56 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -2703,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); } From 57eaa4eab39bf17ad0edb7490f16e304b2829a70 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 27 Jul 2024 17:19:47 +0900 Subject: [PATCH 36/39] Remove outdated comment for OPL4 --- src/engine/sysDef.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 3414b8608..d94dbe46e 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1633,7 +1633,7 @@ 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 + // TODO: add 12-bit sample formats sysDefs[DIV_SYSTEM_OPL4]=new DivSysDef( _("Yamaha YMF278B (OPL4)"), NULL, 0xae, 0, 42, true, true, 0x151, false, (1U< Date: Fri, 16 Aug 2024 20:45:40 +0900 Subject: [PATCH 37/39] Add OPL4/MultiPCM 12-bit PCM support --- papers/format.md | 1 + src/engine/platform/opl.cpp | 7 ++++++- src/engine/sample.cpp | 38 +++++++++++++++++++++++++++++++++++++ src/engine/sample.h | 7 ++++++- src/engine/sysDef.cpp | 6 ++---- src/gui/guiConst.cpp | 2 +- 6 files changed, 54 insertions(+), 7 deletions(-) diff --git a/papers/format.md b/papers/format.md index d8e12b7bd..d5ebddbab 100644 --- a/papers/format.md +++ b/papers/format.md @@ -587,6 +587,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/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index ef0955386..5fe422dbf 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -3197,6 +3197,9 @@ void DivPlatformOPL::renderSamples(int sysID) { 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; @@ -3239,7 +3242,9 @@ void DivPlatformOPL::renderSamples(int sysID) { case DIV_SAMPLE_DEPTH_8BIT: bitDepth=0; break; - // TODO: 12 bit PCM + case DIV_SAMPLE_DEPTH_12BIT: + bitDepth=1; + break; case DIV_SAMPLE_DEPTH_16BIT: bitDepth=2; break; 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 Date: Sun, 8 Sep 2024 18:11:22 -0500 Subject: [PATCH 38/39] MultiPCM: store flags dev221 --- papers/newIns.md | 5 +++++ src/engine/engine.h | 4 ++-- src/engine/instrument.cpp | 28 +++++++++++++++------------- 3 files changed, 22 insertions(+), 15 deletions(-) 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/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 ed5894d65..6774202f1 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -896,12 +896,13 @@ void DivInstrument::writeFeatureMP(SafeWriter* w) { w->writeC(multipcm.vib); w->writeC(multipcm.am); - /* - w->writeC(multipcm.damp); - w->writeC(multipcm.pseudoReverb); - w->writeC(multipcm.lfoReset); - w->writeC(multipcm.levelDirect); - */ + unsigned char next=( + (multipcm.damp?1:0)& + (multipcm.pseudoReverb?2:0)& + (multipcm.lfoReset?4:0)& + (multipcm.levelDirect?8:0) + ); + w->writeC(next); FEATURE_END; } @@ -2229,12 +2230,13 @@ void DivInstrument::readFeatureMP(SafeReader& reader, short version) { multipcm.vib=reader.readC(); multipcm.am=reader.readC(); - /* - multipcm.damp=reader.readC(); - multipcm.pseudoReverb=reader.readC(); - multipcm.lfoReset=reader.readC(); - multipcm.levelDirect=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; } @@ -3510,4 +3512,4 @@ DivInstrument& DivInstrument::operator=( const DivInstrument& ins ) { *(DivInstrumentPOD*)this=ins; name=ins.name; return *this; -} \ No newline at end of file +} From 89fb8842b0a0e21ffa9d56bbc7ce9e7038093b2c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 8 Sep 2024 18:34:32 -0500 Subject: [PATCH 39/39] OPL: put this variable outside --- src/engine/platform/opl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 5fe422dbf..9facff76c 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -185,10 +185,10 @@ void DivPlatformOPL::acquire_nuked(short** buf, size_t len) { 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