From 35e459d9e5f685d0bf6b15577637a11420f49992 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 25 Feb 2022 00:11:27 -0500 Subject: [PATCH] Neo Geo: implement ADPCM-B --- src/engine/engine.cpp | 29 +++++++- src/engine/platform/ym2610.cpp | 120 +++++++++++++++++++++++++++++++-- src/engine/platform/ym2610.h | 26 ++++++- src/engine/playback.cpp | 20 +++--- 4 files changed, 177 insertions(+), 18 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 62c01656..436385bd 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -460,12 +460,12 @@ void DivEngine::renderSamples() { memPos=(memPos+0xfffff)&0xf00000; } if (memPos>=16777216) { - logW("out of ADPCM memory for sample %d!\n",i); + logW("out of ADPCM-A memory for sample %d!\n",i); break; } if (memPos+paddedLen>=16777216) { memcpy(adpcmAMem+memPos,s->dataA,16777216-memPos); - logW("out of ADPCM memory for sample %d!\n",i); + logW("out of ADPCM-A memory for sample %d!\n",i); } else { memcpy(adpcmAMem+memPos,s->dataA,paddedLen); } @@ -474,6 +474,31 @@ void DivEngine::renderSamples() { } adpcmAMemLen=memPos+256; + // step 2: allocate ADPCM-B samples + if (adpcmBMem==NULL) adpcmBMem=new unsigned char[16777216]; + + memPos=0; + for (int i=0; ilengthB+255)&(~0xff); + if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) { + memPos=(memPos+0xfffff)&0xf00000; + } + if (memPos>=16777216) { + logW("out of ADPCM-B memory for sample %d!\n",i); + break; + } + if (memPos+paddedLen>=16777216) { + memcpy(adpcmBMem+memPos,s->dataB,16777216-memPos); + logW("out of ADPCM-B memory for sample %d!\n",i); + } else { + memcpy(adpcmBMem+memPos,s->dataB,paddedLen); + } + s->offB=memPos; + memPos+=paddedLen; + } + adpcmBMemLen=memPos+256; + // step 4: allocate qsound pcm samples if (qsoundMem==NULL) qsoundMem=new unsigned char[16777216]; diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 7347f3e2..8b9501e2 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -101,6 +101,13 @@ const char* DivPlatformYM2610::getEffectName(unsigned char effect) { return NULL; } +double DivPlatformYM2610::NOTE_ADPCMB(int note) { + DivInstrument* ins=parent->getIns(chan[13].ins); + if (ins->type!=DIV_INS_AMIGA) return 0; + double off=(double)(parent->getSample(ins->amiga.initSample)->centerRate)/8363.0; + return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false); +} + void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t len) { static int os[2]; @@ -358,6 +365,38 @@ void DivPlatformYM2610::tick() { } } + // ADPCM-B + if (chan[13].furnacePCM) { + chan[13].std.next(); + + if (chan[13].std.hadVol) { + chan[13].outVol=(chan[13].vol*MIN(64,chan[13].std.vol))/64; + immWrite(0x1b,chan[13].outVol); + } + + if (chan[13].std.hadArp) { + if (!chan[13].inPorta) { + if (chan[13].std.arpMode) { + chan[13].baseFreq=NOTE_ADPCMB(chan[13].std.arp); + } else { + chan[13].baseFreq=NOTE_ADPCMB(chan[13].note+(signed char)chan[13].std.arp); + } + } + chan[13].freqChanged=true; + } else { + if (chan[13].std.arpMode && chan[13].std.finishedArp) { + chan[13].baseFreq=NOTE_ADPCMB(chan[13].note); + chan[13].freqChanged=true; + } + } + } + if (chan[13].freqChanged) { + chan[13].freq=parent->calcFreq(chan[13].baseFreq,chan[13].pitch,false,4); + immWrite(0x19,chan[13].freq&0xff); + immWrite(0x1a,(chan[13].freq>>8)&0xff); + chan[13].freqChanged=false; + } + for (int i=0; i<512; i++) { if (pendingWrites[i]!=oldWrites[i]) { immWrite(i,pendingWrites[i]&0xff); @@ -426,7 +465,59 @@ int DivPlatformYM2610::toFreq(int freq) { int DivPlatformYM2610::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { - if (c.chan>6) { // ADPCM + if (c.chan>12) { // ADPCM-B + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + if (ins->type==DIV_INS_AMIGA) { + chan[c.chan].furnacePCM=true; + } else { + chan[c.chan].furnacePCM=false; + } + if (skipRegisterWrites) break; + if (chan[c.chan].furnacePCM) { + chan[c.chan].std.init(ins); + if (!chan[c.chan].std.willVol) { + chan[c.chan].outVol=chan[c.chan].vol; + } + DivSample* s=parent->getSample(ins->amiga.initSample); + immWrite(0x12,(s->offB>>8)&0xff); + immWrite(0x13,s->offB>>16); + int end=s->offB+s->lengthB-1; + immWrite(0x14,(end>>8)&0xff); + immWrite(0x15,end>>16); + immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + immWrite(0x10,0x80); // start + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); + chan[c.chan].freqChanged=true; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + } else { + chan[c.chan].std.init(NULL); + chan[c.chan].outVol=chan[c.chan].vol; + if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { + immWrite(0x10,0x01); // reset + immWrite(0x12,0); + immWrite(0x13,0); + immWrite(0x14,0); + immWrite(0x15,0); + break; + } + DivSample* s=parent->getSample(12*sampleBank+c.value%12); + immWrite(0x12,(s->offB>>8)&0xff); + immWrite(0x13,s->offB>>16); + int end=s->offB+s->lengthB-1; + immWrite(0x14,(end>>8)&0xff); + immWrite(0x15,end>>16); + immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + immWrite(0x10,0x80); // start + chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144); + chan[c.chan].freqChanged=true; + } + break; + } + if (c.chan>6) { // ADPCM-A if (skipRegisterWrites) break; if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { immWrite(0x100,0x80|(1<<(c.chan-7))); @@ -511,6 +602,10 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_OFF: + if (c.chan>12) { + immWrite(0x10,0x01); // reset + break; + } if (c.chan>6) { immWrite(0x100,0x80|(1<<(c.chan-7))); break; @@ -521,6 +616,10 @@ int DivPlatformYM2610::dispatch(DivCommand c) { chan[c.chan].std.init(NULL); break; case DIV_CMD_NOTE_OFF_ENV: + if (c.chan>12) { + immWrite(0x10,0x01); // reset + break; + } if (c.chan>6) { immWrite(0x100,0x80|(1<<(c.chan-7))); break; @@ -542,7 +641,11 @@ int DivPlatformYM2610::dispatch(DivCommand c) { if (!chan[c.chan].std.hasVol) { chan[c.chan].outVol=c.value; } - if (c.chan>6) { // ADPCM + if (c.chan>12) { // ADPCM-B + immWrite(0x1b,chan[c.chan].outVol); + break; + } + if (c.chan>6) { // ADPCM-A immWrite(0x108+(c.chan-7),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); break; } @@ -587,6 +690,10 @@ int DivPlatformYM2610::dispatch(DivCommand c) { chan[c.chan].pan=3; break; } + if (c.chan>12) { + immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + break; + } if (c.chan>6) { immWrite(0x108+(c.chan-7),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); break; @@ -773,7 +880,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { return 0; break; case DIV_CMD_GET_VOLMAX: - if (c.chan>12) return 127; + if (c.chan>12) return 255; if (c.chan>6) return 31; if (c.chan>3) return 15; return 127; @@ -797,7 +904,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { void DivPlatformYM2610::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - if (ch>6) { // ADPCM + if (ch>6) { // ADPCM-A immWrite(0x108+(ch-7),isMuted[ch]?0:((chan[ch].pan<<6)|chan[ch].vol)); return; } @@ -885,7 +992,7 @@ void DivPlatformYM2610::reset() { for (int i=7; i<13; i++) { chan[i].vol=0x1f; } - chan[13].vol=0x7f; + chan[13].vol=0xff; for (int i=0; i<512; i++) { oldWrites[i]=-1; @@ -916,7 +1023,8 @@ void DivPlatformYM2610::reset() { immWrite(0x22,0x08); // PCM volume - immWrite(0x101,0x3f); + immWrite(0x101,0x3f); // A + immWrite(0x1b,0xff); // B } bool DivPlatformYM2610::isStereo() { diff --git a/src/engine/platform/ym2610.h b/src/engine/platform/ym2610.h index 593e5668..317870f6 100644 --- a/src/engine/platform/ym2610.h +++ b/src/engine/platform/ym2610.h @@ -41,11 +41,32 @@ class DivPlatformYM2610: public DivDispatch { int freq, baseFreq, pitch, note; unsigned char ins, psgMode, autoEnvNum, autoEnvDen; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM; int vol, outVol; unsigned char pan; DivMacroInt std; - Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), note(0), ins(-1), psgMode(1), autoEnvNum(0), autoEnvDen(0), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(15), pan(3) {} + Channel(): + freqH(0), + freqL(0), + freq(0), + baseFreq(0), + pitch(0), + note(0), + ins(-1), + psgMode(1), + autoEnvNum(0), + autoEnvDen(0), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + portaPause(false), + inPorta(false), + furnacePCM(false), + vol(0), + outVol(15), + pan(3) {} }; Channel chan[14]; bool isMuted[14]; @@ -83,6 +104,7 @@ class DivPlatformYM2610: public DivDispatch { int octave(int freq); int toFreq(int freq); + double NOTE_ADPCMB(int note); friend void putDispatchChan(void*,int,int); public: diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 5fb0c4e8..e4ed6066 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -255,6 +255,8 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe return true; } +#define IS_YM2610 (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT) + bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char effectVal) { switch (sysOfChan[ch]) { case DIV_SYSTEM_YM2612: @@ -262,6 +264,8 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char case DIV_SYSTEM_YM2151: case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: + case DIV_SYSTEM_YM2610_FULL: + case DIV_SYSTEM_YM2610_FULL_EXT: switch (effect) { case 0x10: // LFO or noise mode if (sysOfChan[ch]==DIV_SYSTEM_YM2151) { @@ -324,42 +328,42 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char dispatchCmd(DivCommand(DIV_CMD_FM_PM_DEPTH,ch,effectVal&127)); break; case 0x20: // Neo Geo PSG mode - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (IS_YM2610) { dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); } break; case 0x21: // Neo Geo PSG noise freq - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (IS_YM2610) { dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_FREQ,ch,effectVal)); } break; case 0x22: // UNOFFICIAL: Neo Geo PSG envelope enable - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (IS_YM2610) { dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SET,ch,effectVal)); } break; case 0x23: // UNOFFICIAL: Neo Geo PSG envelope period low - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (IS_YM2610) { dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_LOW,ch,effectVal)); } break; case 0x24: // UNOFFICIAL: Neo Geo PSG envelope period high - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (IS_YM2610) { dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_HIGH,ch,effectVal)); } break; case 0x25: // UNOFFICIAL: Neo Geo PSG envelope slide up - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (IS_YM2610) { dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SLIDE,ch,-effectVal)); } break; case 0x26: // UNOFFICIAL: Neo Geo PSG envelope slide down - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (IS_YM2610) { dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SLIDE,ch,effectVal)); } break; case 0x29: // auto-envelope - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (IS_YM2610) { dispatchCmd(DivCommand(DIV_CMD_AY_AUTO_ENVELOPE,ch,effectVal)); } break;