From 5a724e49494d5e6a7da3267c316b77a3151b21c7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 2 May 2022 03:42:40 -0500 Subject: [PATCH] NES: DPCM work! --- papers/doc/7-systems/nes.md | 6 +++- src/engine/platform/nes.cpp | 57 ++++++++++++++++++++++++++++++++----- src/engine/platform/nes.h | 1 + src/gui/debugWindow.cpp | 45 +++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 8 deletions(-) diff --git a/papers/doc/7-systems/nes.md b/papers/doc/7-systems/nes.md index 45ce95bc..3ccca264 100644 --- a/papers/doc/7-systems/nes.md +++ b/papers/doc/7-systems/nes.md @@ -18,4 +18,8 @@ also known as Famicom. It is a five-channel PSG: first two channels play pulse w - `14xy`: setup sweep down. - `x` is the time. - `y` is the shift. - - set to 0 to disable it. \ No newline at end of file + - set to 0 to disable it. +- `18xx`: set PCM channel mode. + - `00`: PCM (software). + - `01`: DPCM (hardware). + - when in DPCM mode, samples will sound muffled (due to its nature), availables pitches are limited and loop point is ignored. \ No newline at end of file diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 9e87e2a5..0e336df0 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -206,6 +206,25 @@ static unsigned char noiseTable[253]={ 15 }; +unsigned char DivPlatformNES::calcDPCMRate(int inRate) { + if (inRate<4450) return 0; + if (inRate<5000) return 1; + if (inRate<5400) return 2; + if (inRate<5900) return 3; + if (inRate<6650) return 4; + if (inRate<7450) return 5; + if (inRate<8100) return 6; + if (inRate<8800) return 7; + if (inRate<10200) return 8; + if (inRate<11700) return 9; + if (inRate<13300) return 10; + if (inRate<15900) return 11; + if (inRate<18900) return 12; + if (inRate<23500) return 13; + if (inRate<29000) return 14; + return 15; +} + void DivPlatformNES::tick(bool sysTick) { for (int i=0; i<4; i++) { chan[i].std.next(); @@ -333,6 +352,9 @@ void DivPlatformNES::tick(bool sysTick) { off=(double)s->centerRate/8363.0; } dacRate=MIN(chan[4].freq*off,32000); + if (dpcmMode && !skipRegisterWrites) { + rWrite(0x4010,calcDPCMRate(dacRate)); + } if (dumpWrites) addWrite(0xffff0001,dacRate); } chan[4].freqChanged=false; @@ -363,6 +385,18 @@ int DivPlatformNES::dispatch(DivCommand c) { chan[c.chan].active=true; chan[c.chan].keyOn=true; chan[c.chan].furnaceDac=true; + if (dpcmMode && !skipRegisterWrites) { + unsigned int dpcmAddr=parent->getSample(dacSample)->offDPCM; + unsigned int dpcmLen=(parent->getSample(dacSample)->lengthDPCM+15)>>4; + if (dpcmLen>255) dpcmLen=255; + // write DPCM + rWrite(0x4015,15); + rWrite(0x4010,calcDPCMRate(chan[c.chan].baseFreq)); + rWrite(0x4012,(dpcmAddr>>6)&0xff); + rWrite(0x4013,dpcmLen&0xff); + rWrite(0x4015,31); + dpcmBank=dpcmAddr>>14; + } } else { if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; @@ -386,12 +420,11 @@ int DivPlatformNES::dispatch(DivCommand c) { if (dpcmLen>255) dpcmLen=255; // write DPCM rWrite(0x4015,15); - rWrite(0x4010,15); + rWrite(0x4010,calcDPCMRate(dacRate)); rWrite(0x4012,(dpcmAddr>>6)&0xff); rWrite(0x4013,dpcmLen&0xff); rWrite(0x4015,31); dpcmBank=dpcmAddr>>14; - logV("writing DPCM: %x %x",dpcmAddr,dpcmLen); } } break; @@ -421,6 +454,7 @@ int DivPlatformNES::dispatch(DivCommand c) { if (c.chan==4) { dacSample=-1; if (dumpWrites) addWrite(0xffff0002,0); + if (dpcmMode && !skipRegisterWrites) rWrite(0x4015,15); } chan[c.chan].active=false; chan[c.chan].keyOff=true; @@ -501,10 +535,16 @@ int DivPlatformNES::dispatch(DivCommand c) { break; case DIV_CMD_NES_DMC: rWrite(0x4011,c.value&0x7f); - if (dumpWrites && dpcmMode) addWrite(0xffff0002,0); break; case DIV_CMD_SAMPLE_MODE: dpcmMode=c.value; + if (dumpWrites && dpcmMode) addWrite(0xffff0002,0); + dacSample=-1; + rWrite(0x4015,15); + rWrite(0x4010,0); + rWrite(0x4012,0); + rWrite(0x4013,0); + rWrite(0x4015,31); break; case DIV_CMD_SAMPLE_BANK: sampleBank=c.value; @@ -682,10 +722,13 @@ void DivPlatformNES::renderSamples() { size_t memPos=0; for (int i=0; isong.sampleLen; i++) { DivSample* s=parent->song.sample[i]; - int paddedLen=(s->lengthDPCM+63)&(~0xff); + unsigned int paddedLen=(s->lengthDPCM+63)&(~0x3f); logV("%d padded length: %d",i,paddedLen); - if ((memPos&0x4000)!=((memPos+paddedLen)&0x4000)) { - memPos=(memPos+0x3fff)&0x4000; + if ((memPos&(~0x3fff))!=((memPos+paddedLen)&(~0x3fff))) { + memPos=(memPos+0x3fff)&(~0x3fff); + } + if (paddedLen>4081) { + paddedLen=4096; } if (memPos>=getSampleMemCapacity(0)) { logW("out of DPCM memory for sample %d!",i); @@ -695,7 +738,7 @@ void DivPlatformNES::renderSamples() { memcpy(dpcmMem+memPos,s->dataDPCM,getSampleMemCapacity(0)-memPos); logW("out of DPCM memory for sample %d!",i); } else { - memcpy(dpcmMem+memPos,s->dataDPCM,paddedLen); + memcpy(dpcmMem+memPos,s->dataDPCM,MIN(s->lengthDPCM,paddedLen)); } s->offDPCM=memPos; memPos+=paddedLen; diff --git a/src/engine/platform/nes.h b/src/engine/platform/nes.h index 75205548..a03efc7a 100644 --- a/src/engine/platform/nes.h +++ b/src/engine/platform/nes.h @@ -81,6 +81,7 @@ class DivPlatformNES: public DivDispatch { friend void putDispatchChan(void*,int,int); void doWrite(unsigned short addr, unsigned char data); + unsigned char calcDPCMRate(int inRate); void acquire_puNES(short* bufL, short* bufR, size_t start, size_t len); void acquire_NSFPlay(short* bufL, short* bufR, size_t start, size_t len); diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 06184c0b..7b232a9c 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -21,6 +21,7 @@ #include "debug.h" #include "IconsFontAwesome4.h" #include +#include void FurnaceGUI::drawDebug() { static int bpOrder; @@ -141,6 +142,50 @@ void FurnaceGUI::drawDebug() { ImGui::Columns(); ImGui::TreePop(); } + if (ImGui::TreeNode("Sample Debug")) { + for (int i=0; isong.sampleLen; i++) { + DivSample* sample=e->getSample(i); + if (sample==NULL) { + ImGui::Text("%d: ",i); + continue; + } + ImGui::Text("%d: %s",i,sample->name.c_str()); + ImGui::Indent(); + ImGui::Text("rate: %d",sample->rate); + ImGui::Text("centerRate: %d",sample->centerRate); + ImGui::Text("loopStart: %d",sample->loopStart); + ImGui::Text("loopOffP: %d",sample->loopOffP); + ImGui::Text("depth: %d",sample->depth); + ImGui::Text("length8: %d",sample->length8); + ImGui::Text("length16: %d",sample->length16); + ImGui::Text("length1: %d",sample->length1); + ImGui::Text("lengthDPCM: %d",sample->lengthDPCM); + ImGui::Text("lengthQSoundA: %d",sample->lengthQSoundA); + ImGui::Text("lengthA: %d",sample->lengthA); + ImGui::Text("lengthB: %d",sample->lengthB); + ImGui::Text("lengthX68: %d",sample->lengthX68); + ImGui::Text("lengthBRR: %d",sample->lengthBRR); + ImGui::Text("lengthVOX: %d",sample->lengthVOX); + + ImGui::Text("off8: %x",sample->off8); + ImGui::Text("off16: %x",sample->off16); + ImGui::Text("off1: %x",sample->off1); + ImGui::Text("offDPCM: %x",sample->offDPCM); + ImGui::Text("offQSoundA: %x",sample->offQSoundA); + ImGui::Text("offA: %x",sample->offA); + ImGui::Text("offB: %x",sample->offB); + ImGui::Text("offX68: %x",sample->offX68); + ImGui::Text("offBRR: %x",sample->offBRR); + ImGui::Text("offVOX: %x",sample->offVOX); + ImGui::Text("offSegaPCM: %x",sample->offSegaPCM); + ImGui::Text("offQSound: %x",sample->offQSound); + ImGui::Text("offX1_010: %x",sample->offX1_010); + + ImGui::Text("samples: %d",sample->samples); + ImGui::Unindent(); + } + ImGui::TreePop(); + } if (ImGui::TreeNode("Playground")) { if (pgSys<0 || pgSys>=e->song.systemLen) pgSys=0; if (ImGui::BeginCombo("System",fmt::sprintf("%d. %s",pgSys+1,e->getSystemName(e->song.system[pgSys])).c_str())) {