NES: DPCM work!

This commit is contained in:
tildearrow 2022-05-02 03:42:40 -05:00
parent dbe9bf25e7
commit 5a724e4949
4 changed files with 101 additions and 8 deletions

View File

@ -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.
- 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.

View File

@ -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; i<parent->song.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;

View File

@ -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);

View File

@ -21,6 +21,7 @@
#include "debug.h"
#include "IconsFontAwesome4.h"
#include <fmt/printf.h>
#include <imgui.h>
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; i<e->song.sampleLen; i++) {
DivSample* sample=e->getSample(i);
if (sample==NULL) {
ImGui::Text("%d: <NULL!>",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())) {