This commit is contained in:
tildearrow 2024-03-18 16:02:16 -05:00
parent 3423ac8774
commit a83df6e8fd
7 changed files with 1458 additions and 25 deletions

View file

@ -516,6 +516,8 @@ src/engine/platform/sound/nes/mmc5.c
src/engine/platform/sound/vera_psg.c src/engine/platform/sound/vera_psg.c
src/engine/platform/sound/vera_pcm.c src/engine/platform/sound/vera_pcm.c
src/engine/platform/sound/nes_nsfplay/5e01_apu.cpp
src/engine/platform/sound/nes_nsfplay/5e01_dmc.cpp
src/engine/platform/sound/nes_nsfplay/nes_apu.cpp src/engine/platform/sound/nes_nsfplay/nes_apu.cpp
src/engine/platform/sound/nes_nsfplay/nes_dmc.cpp src/engine/platform/sound/nes_nsfplay/nes_dmc.cpp
src/engine/platform/sound/nes_nsfplay/nes_fds.cpp src/engine/platform/sound/nes_nsfplay/nes_fds.cpp

View file

@ -64,8 +64,13 @@ const char** DivPlatformNES::getRegisterSheet() {
void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) { void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) {
if (useNP) { if (useNP) {
nes1_NP->Write(addr,data); if (isE) {
nes2_NP->Write(addr,data); e1_NP->Write(addr,data);
e2_NP->Write(addr,data);
} else {
nes1_NP->Write(addr,data);
nes2_NP->Write(addr,data);
}
} else { } else {
apu_wr_reg(nes,addr,data); apu_wr_reg(nes,addr,data);
} }
@ -151,9 +156,40 @@ void DivPlatformNES::acquire_NSFPlay(short** buf, size_t len) {
} }
} }
void DivPlatformNES::acquire_NSFPlayE(short** buf, size_t len) {
int out1[2];
int out2[2];
for (size_t i=0; i<len; i++) {
doPCM;
e1_NP->Tick(8);
e2_NP->TickFrameSequence(8);
e2_NP->Tick(8);
e1_NP->Render(out1);
e2_NP->Render(out2);
int sample=(out1[0]+out1[1]+out2[0]+out2[1])<<1;
if (sample>32767) sample=32767;
if (sample<-32768) sample=-32768;
buf[0][i]=sample;
if (++writeOscBuf>=4) {
writeOscBuf=0;
oscBuf[0]->data[oscBuf[0]->needle++]=e1_NP->out[0]<<11;
oscBuf[1]->data[oscBuf[1]->needle++]=e1_NP->out[1]<<11;
oscBuf[2]->data[oscBuf[2]->needle++]=e2_NP->out[0]<<11;
oscBuf[3]->data[oscBuf[3]->needle++]=e2_NP->out[1]<<11;
oscBuf[4]->data[oscBuf[4]->needle++]=e2_NP->out[2]<<8;
}
}
}
void DivPlatformNES::acquire(short** buf, size_t len) { void DivPlatformNES::acquire(short** buf, size_t len) {
if (useNP) { if (useNP) {
acquire_NSFPlay(buf,len); if (isE) {
acquire_NSFPlayE(buf,len);
} else {
acquire_NSFPlay(buf,len);
}
} else { } else {
acquire_puNES(buf,len); acquire_puNES(buf,len);
} }
@ -242,6 +278,8 @@ void DivPlatformNES::tick(bool sysTick) {
} }
if (i!=2) { if (i!=2) {
rWrite(0x4000+i*4,(chan[i].envMode<<4)|chan[i].outVol|((chan[i].duty&3)<<6)); rWrite(0x4000+i*4,(chan[i].envMode<<4)|chan[i].outVol|((chan[i].duty&3)<<6));
} else if (isE) {
rWrite(0x4000+9,chan[i].duty);
} }
if (i==3) { // noise if (i==3) { // noise
chan[i].freqChanged=true; chan[i].freqChanged=true;
@ -279,7 +317,9 @@ void DivPlatformNES::tick(bool sysTick) {
} }
} }
ntPos+=chan[i].pitch2; ntPos+=chan[i].pitch2;
if (parent->song.properNoiseLayout) { if (isE) {
chan[i].freq=31-(ntPos&31);
} else if (parent->song.properNoiseLayout) {
chan[i].freq=15-(ntPos&15); chan[i].freq=15-(ntPos&15);
} else { } else {
if (ntPos<0) ntPos=0; if (ntPos<0) ntPos=0;
@ -678,8 +718,13 @@ int DivPlatformNES::dispatch(DivCommand c) {
void DivPlatformNES::muteChannel(int ch, bool mute) { void DivPlatformNES::muteChannel(int ch, bool mute) {
isMuted[ch]=mute; isMuted[ch]=mute;
if (useNP) { if (useNP) {
nes1_NP->SetMask(((int)isMuted[0])|(isMuted[1]<<1)); if (isE) {
nes2_NP->SetMask(((int)isMuted[2])|(isMuted[3]<<1)|(isMuted[4]<<2)); e1_NP->SetMask(((int)isMuted[0])|(isMuted[1]<<1));
e2_NP->SetMask(((int)isMuted[2])|(isMuted[3]<<1)|(isMuted[4]<<2));
} else {
nes1_NP->SetMask(((int)isMuted[0])|(isMuted[1]<<1));
nes2_NP->SetMask(((int)isMuted[2])|(isMuted[3]<<1)|(isMuted[4]<<2));
}
} else { } else {
nes->muted[ch]=mute; nes->muted[ch]=mute;
} }
@ -743,10 +788,17 @@ void DivPlatformNES::reset() {
linearCount=255; linearCount=255;
if (useNP) { if (useNP) {
nes1_NP->Reset(); if (isE) {
nes2_NP->Reset(); e1_NP->Reset();
nes1_NP->SetMask(((int)isMuted[0])|(isMuted[1]<<1)); e2_NP->Reset();
nes2_NP->SetMask(((int)isMuted[2])|(isMuted[3]<<1)|(isMuted[4]<<2)); e1_NP->SetMask(((int)isMuted[0])|(isMuted[1]<<1));
e2_NP->SetMask(((int)isMuted[2])|(isMuted[3]<<1)|(isMuted[4]<<2));
} else {
nes1_NP->Reset();
nes2_NP->Reset();
nes1_NP->SetMask(((int)isMuted[0])|(isMuted[1]<<1));
nes2_NP->SetMask(((int)isMuted[2])|(isMuted[3]<<1)|(isMuted[4]<<2));
}
} else { } else {
apu_turn_on(nes,apuType); apu_turn_on(nes,apuType);
nes->apu.cpu_cycles=0; nes->apu.cpu_cycles=0;
@ -779,11 +831,19 @@ void DivPlatformNES::setFlags(const DivConfig& flags) {
apuType=0; apuType=0;
} }
if (useNP) { if (useNP) {
nes1_NP->SetClock(rate); if (isE) {
nes1_NP->SetRate(rate); e1_NP->SetClock(rate);
nes2_NP->SetClock(rate); e1_NP->SetRate(rate);
nes2_NP->SetRate(rate); e2_NP->SetClock(rate);
nes2_NP->SetPal(apuType==1); e2_NP->SetRate(rate);
e2_NP->SetPal(apuType==1);
} else {
nes1_NP->SetClock(rate);
nes1_NP->SetRate(rate);
nes2_NP->SetClock(rate);
nes2_NP->SetRate(rate);
nes2_NP->SetPal(apuType==1);
}
} else { } else {
nes->apu.type=apuType; nes->apu.type=apuType;
} }
@ -819,6 +879,8 @@ void DivPlatformNES::setNSFPlay(bool use) {
void DivPlatformNES::set5E01(bool use) { void DivPlatformNES::set5E01(bool use) {
isE=use; isE=use;
// for now
if (isE) useNP=true;
} }
unsigned char DivPlatformNES::readDMC(unsigned short addr) { unsigned char DivPlatformNES::readDMC(unsigned short addr) {
@ -897,14 +959,25 @@ int DivPlatformNES::init(DivEngine* p, int channels, int sugRate, const DivConfi
dumpWrites=false; dumpWrites=false;
skipRegisterWrites=false; skipRegisterWrites=false;
if (useNP) { if (useNP) {
nes1_NP=new xgm::NES_APU; if (isE) {
nes1_NP->SetOption(xgm::NES_APU::OPT_NONLINEAR_MIXER,1); e1_NP=new xgm::I5E01_APU;
nes2_NP=new xgm::NES_DMC; e1_NP->SetOption(xgm::I5E01_APU::OPT_NONLINEAR_MIXER,1);
nes2_NP->SetOption(xgm::NES_DMC::OPT_NONLINEAR_MIXER,1); e2_NP=new xgm::I5E01_DMC;
nes2_NP->SetMemory([this](unsigned short addr, unsigned int& data) { e2_NP->SetOption(xgm::I5E01_DMC::OPT_NONLINEAR_MIXER,1);
data=readDMC(addr); e2_NP->SetMemory([this](unsigned short addr, unsigned int& data) {
}); data=readDMC(addr);
nes2_NP->SetAPU(nes1_NP); });
e2_NP->SetAPU(e1_NP);
} else {
nes1_NP=new xgm::NES_APU;
nes1_NP->SetOption(xgm::NES_APU::OPT_NONLINEAR_MIXER,1);
nes2_NP=new xgm::NES_DMC;
nes2_NP->SetOption(xgm::NES_DMC::OPT_NONLINEAR_MIXER,1);
nes2_NP->SetMemory([this](unsigned short addr, unsigned int& data) {
data=readDMC(addr);
});
nes2_NP->SetAPU(nes1_NP);
}
} else { } else {
nes=new struct NESAPU; nes=new struct NESAPU;
nes->readDMC=_readDMC; nes->readDMC=_readDMC;
@ -933,8 +1006,13 @@ void DivPlatformNES::quit() {
delete oscBuf[i]; delete oscBuf[i];
} }
if (useNP) { if (useNP) {
delete nes1_NP; if (isE) {
delete nes2_NP; delete e1_NP;
delete e2_NP;
} else {
delete nes1_NP;
delete nes2_NP;
}
} else { } else {
delete nes; delete nes;
} }

View file

@ -23,6 +23,7 @@
#include "../dispatch.h" #include "../dispatch.h"
#include "sound/nes_nsfplay/nes_apu.h" #include "sound/nes_nsfplay/nes_apu.h"
#include "sound/nes_nsfplay/5e01_apu.h"
class DivPlatformNES: public DivDispatch { class DivPlatformNES: public DivDispatch {
struct Channel: public SharedChannel<signed char> { struct Channel: public SharedChannel<signed char> {
@ -66,6 +67,8 @@ class DivPlatformNES: public DivDispatch {
struct NESAPU* nes; struct NESAPU* nes;
xgm::NES_APU* nes1_NP; xgm::NES_APU* nes1_NP;
xgm::NES_DMC* nes2_NP; xgm::NES_DMC* nes2_NP;
xgm::I5E01_APU* e1_NP;
xgm::I5E01_DMC* e2_NP;
unsigned char regPool[128]; unsigned char regPool[128];
unsigned int sampleOffDPCM[256]; unsigned int sampleOffDPCM[256];
DivMemoryComposition memCompo; DivMemoryComposition memCompo;
@ -77,6 +80,7 @@ class DivPlatformNES: public DivDispatch {
unsigned char calcDPCMRate(int inRate); unsigned char calcDPCMRate(int inRate);
void acquire_puNES(short** buf, size_t len); void acquire_puNES(short** buf, size_t len);
void acquire_NSFPlay(short** buf, size_t len); void acquire_NSFPlay(short** buf, size_t len);
void acquire_NSFPlayE(short** buf, size_t len);
public: public:
void acquire(short** buf, size_t len); void acquire(short** buf, size_t len);

View file

@ -0,0 +1,401 @@
//
// I5E01 2A03
//
#include <assert.h>
#include "5e01_apu.h"
#include "common.h"
namespace xgm
{
void I5E01_APU::sweep_sqr(int i)
{
int shifted = freq[i] >> sweep_amount[i];
if (i == 0 && sweep_mode[i]) shifted += 1;
sfreq[i] = freq[i] + (sweep_mode[i] ? -shifted : shifted);
//DEBUG_OUT("shifted[%d] = %d (%d >> %d)\n",i,shifted,freq[i],sweep_amount[i]);
}
void I5E01_APU::FrameSequence(int s)
{
//DEBUG_OUT("FrameSequence(%d)\n",s);
if (s > 3) return; // no operation in step 4
// 240hz clock
for (int i = 0; i < 2; ++i)
{
bool divider = false;
if (envelope_write[i])
{
envelope_write[i] = false;
envelope_counter[i] = 15;
envelope_div[i] = 0;
}
else
{
++envelope_div[i];
if (envelope_div[i] > envelope_div_period[i])
{
divider = true;
envelope_div[i] = 0;
}
}
if (divider)
{
if (envelope_loop[i] && envelope_counter[i] == 0)
envelope_counter[i] = 15;
else if (envelope_counter[i] > 0)
--envelope_counter[i];
}
}
// 120hz clock
if ((s & 1) == 0)
for (int i = 0; i < 2; ++i)
{
if (!envelope_loop[i] && (length_counter[i] > 0))
--length_counter[i];
if (sweep_enable[i])
{
//DEBUG_OUT("Clock sweep: %d\n", i);
--sweep_div[i];
if (sweep_div[i] <= 0)
{
sweep_sqr(i); // calculate new sweep target
//DEBUG_OUT("sweep_div[%d] (0/%d)\n",i,sweep_div_period[i]);
//DEBUG_OUT("freq[%d]=%d > sfreq[%d]=%d\n",i,freq[i],i,sfreq[i]);
if (freq[i] >= 8 && sfreq[i] < 0x800 && sweep_amount[i] > 0) // update frequency if appropriate
{
freq[i] = sfreq[i] < 0 ? 0 : sfreq[i];
}
sweep_div[i] = sweep_div_period[i] + 1;
//DEBUG_OUT("freq[%d]=%d\n",i,freq[i]);
}
if (sweep_write[i])
{
sweep_div[i] = sweep_div_period[i] + 1;
sweep_write[i] = false;
}
}
}
}
int I5E01_APU::calc_sqr(int i, unsigned int clocks)
{
static const short sqrtbl[4][16] = {
{0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}
};
scounter[i] -= clocks;
while (scounter[i] < 0)
{
sphase[i] = (sphase[i] + 1) & 15;
scounter[i] += freq[i] + 1;
}
int ret = 0;
if (length_counter[i] > 0 &&
freq[i] >= 8 &&
sfreq[i] < 0x800
)
{
int v = envelope_disable[i] ? volume[i] : envelope_counter[i];
ret = sqrtbl[duty[i]][sphase[i]] ? v : 0;
}
return ret;
}
bool I5E01_APU::Read(unsigned int adr, unsigned int& val, unsigned int id)
{
if (0x4100 <= adr && adr < 0x4108)
{
val |= reg[adr & 0x7];
return true;
}
else if (adr == 0x4115)
{
val |= (length_counter[1] ? 2 : 0) | (length_counter[0] ? 1 : 0);
return true;
}
else
return false;
}
void I5E01_APU::Tick(unsigned int clocks)
{
out[0] = calc_sqr(0, clocks);
out[1] = calc_sqr(1, clocks);
}
// 生成される波形の振幅は0-8191
unsigned int I5E01_APU::Render(int b[2])
{
out[0] = (mask & 1) ? 0 : out[0];
out[1] = (mask & 2) ? 0 : out[1];
int m[2];
if (option[OPT_NONLINEAR_MIXER])
{
int voltage = square_table[out[0] + out[1]];
m[0] = out[0] << 6;
m[1] = out[1] << 6;
int ref = m[0] + m[1];
if (ref > 0)
{
m[0] = (m[0] * voltage) / ref;
m[1] = (m[1] * voltage) / ref;
}
else
{
m[0] = voltage;
m[1] = voltage;
}
}
else
{
m[0] = (out[0] * square_linear) / 15;
m[1] = (out[1] * square_linear) / 15;
}
b[0] = m[0] * sm[0][0];
b[0] += m[1] * sm[0][1];
b[0] >>= 7;
b[1] = m[0] * sm[1][0];
b[1] += m[1] * sm[1][1];
b[1] >>= 7;
return 2;
}
I5E01_APU::I5E01_APU()
{
SetClock(DEFAULT_CLOCK);
SetRate(DEFAULT_RATE);
option[OPT_UNMUTE_ON_RESET] = true;
option[OPT_PHASE_REFRESH] = true;
option[OPT_NONLINEAR_MIXER] = true;
option[OPT_DUTY_SWAP] = false;
option[OPT_NEGATE_SWEEP_INIT] = false;
square_table[0] = 0;
for (int i = 1;i < 32;i++)
square_table[i] = (int)((8192.0 * 95.88) / (8128.0 / i + 100));
square_linear = square_table[15]; // match linear scale to one full volume square of nonlinear
for (int c = 0;c < 2;++c)
for (int t = 0;t < 2;++t)
sm[c][t] = 128;
}
I5E01_APU::~I5E01_APU()
{
}
void I5E01_APU::Reset()
{
int i;
gclock = 0;
mask = 0;
for (int i = 0; i < 2; ++i)
{
scounter[i] = 0;
sphase[i] = 0;
duty[i] = 0;
volume[i] = 0;
freq[i] = 0;
sfreq[i] = 0;
sweep_enable[i] = 0;
sweep_mode[i] = 0;
sweep_write[i] = 0;
sweep_div_period[i] = 0;
sweep_div[i] = 1;
sweep_amount[i] = 0;
envelope_disable[i] = 0;
envelope_loop[i] = 0;
envelope_write[i] = 0;
envelope_div_period[i] = 0;
envelope_div[0] = 0;
envelope_counter[i] = 0;
length_counter[i] = 0;
enable[i] = 0;
}
for (i = 0x4100; i < 0x4108; i++)
Write(i, 0);
Write(0x4115, 0);
if (option[OPT_UNMUTE_ON_RESET])
Write(0x4115, 0x0f);
if (option[OPT_NEGATE_SWEEP_INIT])
{
Write(0x4101, 0x08);
Write(0x4105, 0x08);
}
for (i = 0; i < 2; i++)
out[i] = 0;
SetRate(rate);
}
void I5E01_APU::SetOption(int id, int val)
{
if (id < OPT_END) option[id] = val;
}
void I5E01_APU::SetClock(double c)
{
clock = c;
}
void I5E01_APU::SetRate(double r)
{
rate = r ? r : DEFAULT_RATE;
}
void I5E01_APU::SetStereoMix(int trk, short mixl, short mixr)
{
if (trk < 0) return;
if (trk > 1) return;
sm[0][trk] = mixl;
sm[1][trk] = mixr;
}
double I5E01_APU::GetFrequencyPulse1() const // // !!
{
if (!(length_counter[0] > 0 &&
freq[0] >= 8 &&
sfreq[0] < 0x800))
return 0.0;
return clock / 16 / (freq[0] + 1);
}
double I5E01_APU::GetFrequencyPulse2() const // // !!
{
if (!(length_counter[1] > 0 &&
freq[1] >= 8 &&
sfreq[1] < 0x800))
return 0.0;
return clock / 16 / (freq[1] + 1);
}
bool I5E01_APU::Write(unsigned int adr, unsigned int val, unsigned int id)
{
int ch;
// hack
adr+=0x100;
static const unsigned char length_table[32] = {
0x0A, 0xFE,
0x14, 0x02,
0x28, 0x04,
0x50, 0x06,
0xA0, 0x08,
0x3C, 0x0A,
0x0E, 0x0C,
0x1A, 0x0E,
0x0C, 0x10,
0x18, 0x12,
0x30, 0x14,
0x60, 0x16,
0xC0, 0x18,
0x48, 0x1A,
0x10, 0x1C,
0x20, 0x1E
};
if (0x4100 <= adr && adr < 0x4108)
{
//DEBUG_OUT("$%04X = %02X\n",adr,val);
adr &= 0xf;
ch = adr >> 2;
switch (adr)
{
case 0x0:
case 0x4:
volume[ch] = val & 15;
envelope_disable[ch] = (val >> 4) & 1;
envelope_loop[ch] = (val >> 5) & 1;
envelope_div_period[ch] = (val & 15);
duty[ch] = (val >> 6) & 3;
if (option[OPT_DUTY_SWAP])
{
if (duty[ch] == 1) duty[ch] = 2;
else if (duty[ch] == 2) duty[ch] = 1;
}
break;
case 0x1:
case 0x5:
sweep_enable[ch] = (val >> 7) & 1;
sweep_div_period[ch] = (((val >> 4) & 7));
sweep_mode[ch] = (val >> 3) & 1;
sweep_amount[ch] = val & 7;
sweep_write[ch] = true;
sweep_sqr(ch);
break;
case 0x2:
case 0x6:
freq[ch] = val | (freq[ch] & 0x700);
sweep_sqr(ch);
break;
case 0x3:
case 0x7:
freq[ch] = (freq[ch] & 0xFF) | ((val & 0x7) << 8);
if (option[OPT_PHASE_REFRESH])
sphase[ch] = 0;
envelope_write[ch] = true;
if (enable[ch])
{
length_counter[ch] = length_table[(val >> 3) & 0x1f];
}
sweep_sqr(ch);
break;
default:
return false;
}
reg[adr] = val;
return true;
}
else if (adr == 0x4115)
{
enable[0] = (val & 1) ? true : false;
enable[1] = (val & 2) ? true : false;
if (!enable[0])
length_counter[0] = 0;
if (!enable[1])
length_counter[1] = 0;
reg[adr - 0x4100] = val;
return true;
}
// 4017 is handled in nes_dmc.cpp
//else if (adr == 0x4017)
//{
//}
return false;
}
} // namespace xgm;

View file

@ -0,0 +1,89 @@
#ifndef _I5E01_APU_H_
#define _I5E01_APU_H_
#include "5e01_dmc.h"
namespace xgm
{
/** Upper half of APU **/
class I5E01_APU
{
public:
enum
{
OPT_UNMUTE_ON_RESET=0,
OPT_PHASE_REFRESH,
OPT_NONLINEAR_MIXER,
OPT_DUTY_SWAP,
OPT_NEGATE_SWEEP_INIT,
OPT_END };
enum
{ SQR0_MASK = 1, SQR1_MASK = 2, };
public:
int option[OPT_END]; // 各種オプション
int mask;
int sm[2][2];
unsigned int gclock;
unsigned char reg[0x20];
double rate, clock;
int square_table[32]; // nonlinear mixer
int square_linear; // linear mix approximation
int scounter[2]; // frequency divider
int sphase[2]; // phase counter
int duty[2];
int volume[2];
int freq[2];
int sfreq[2];
bool sweep_enable[2];
bool sweep_mode[2];
bool sweep_write[2];
int sweep_div_period[2];
int sweep_div[2];
int sweep_amount[2];
bool envelope_disable[2];
bool envelope_loop[2];
bool envelope_write[2];
int envelope_div_period[2];
int envelope_div[2];
int envelope_counter[2];
int length_counter[2];
bool enable[2];
void sweep_sqr (int ch); // calculates target sweep frequency
int calc_sqr (int ch, unsigned int clocks);
public:
int out[2];
I5E01_APU ();
~I5E01_APU ();
// // !! fetch frequency directly instead of through GetTrackInfo()
double GetFrequencyPulse1() const;
double GetFrequencyPulse2() const;
void FrameSequence(int s);
void Reset ();
void Tick (unsigned int clocks);
unsigned int Render (int b[2]);
bool Read (unsigned int adr, unsigned int & val, unsigned int id=0);
bool Write (unsigned int adr, unsigned int val, unsigned int id=0);
void SetRate (double rate);
void SetClock (double clock);
void SetOption (int id, int b);
void SetMask(int m){ mask = m; }
void SetStereoMix (int trk, short mixl, short mixr);
};
} // namespace
#endif

View file

@ -0,0 +1,732 @@
#include "5e01_dmc.h"
#include "5e01_apu.h"
#include "common.h"
#include <assert.h>
#include <cstdlib>
namespace xgm
{
const unsigned int I5E01_DMC::wavlen_table[2][32] = {
{ // NTSC
4,5,6,7,9,12,15,19,23,29,37,46,58,72,91,114,142,178,222,278,348,435,544,681,851,1064,1331,1664,2081,2602,3253,4068
},
{ // PAL
3,4,6,7,9,12,15,18,23,29,36,45,56,70,88,110,137,171,213,266,332,414,517,644,804,1003,1251,1560,1946,2428,3028,3778
} };
const unsigned int I5E01_DMC::freq_table[2][16] = {
{ // NTSC
428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54
},
{ // PAL
398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50
} };
const unsigned int BITREVERSE[256] = {
0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF,
};
I5E01_DMC::I5E01_DMC() : GETA_BITS(20)
{
SetClock(DEFAULT_CLOCK);
SetRate(DEFAULT_RATE);
SetPal(false);
option[OPT_ENABLE_4011] = 1;
option[OPT_ENABLE_PNOISE] = 1;
option[OPT_UNMUTE_ON_RESET] = 1;
option[OPT_DPCM_ANTI_CLICK] = 0;
option[OPT_NONLINEAR_MIXER] = 1;
option[OPT_RANDOMIZE_NOISE] = 1;
option[OPT_RANDOMIZE_TRI] = 1;
option[OPT_TRI_MUTE] = 1;
option[OPT_DPCM_REVERSE] = 0;
tnd_table[0][0][0][0] = 0;
tnd_table[1][0][0][0] = 0;
apu = NULL;
frame_sequence_count = 0;
frame_sequence_length = 7458;
frame_sequence_steps = 4;
for (int c = 0;c < 2;++c)
for (int t = 0;t < 3;++t)
sm[c][t] = 128;
}
I5E01_DMC::~I5E01_DMC()
{
}
void I5E01_DMC::SetStereoMix(int trk, short mixl, short mixr)
{
if (trk < 0) return;
if (trk > 2) return;
sm[0][trk] = mixl;
sm[1][trk] = mixr;
}
void I5E01_DMC::FrameSequence(int s)
{
//DEBUG_OUT("FrameSequence: %d\n",s);
if (s > 3) return; // no operation in step 4
if (apu)
{
apu->FrameSequence(s);
}
if (s == 0 && (frame_sequence_steps == 4))
{
if (frame_irq_enable) frame_irq = true;
}
// 240hz clock
{
// triangle linear counter
if (linear_counter_halt)
{
linear_counter = linear_counter_reload;
}
else
{
if (linear_counter > 0) --linear_counter;
}
if (!linear_counter_control)
{
linear_counter_halt = false;
}
// noise envelope
bool divider = false;
if (envelope_write)
{
envelope_write = false;
envelope_counter = 15;
envelope_div = 0;
}
else
{
++envelope_div;
if (envelope_div > envelope_div_period)
{
divider = true;
envelope_div = 0;
}
}
if (divider)
{
if (envelope_loop && envelope_counter == 0)
envelope_counter = 15;
else if (envelope_counter > 0)
--envelope_counter;
}
}
// 120hz clock
if ((s & 1) == 0)
{
// triangle length counter
if (!linear_counter_control && (length_counter[0] > 0))
--length_counter[0];
// noise length counter
if (!envelope_loop && (length_counter[1] > 0))
--length_counter[1];
}
}
// 三角波チャンネルの計算 戻り値は0-15
unsigned int I5E01_DMC::calc_tri(unsigned int clocks)
{
static unsigned int wavtbl[4][32] =
{
{
15,14,13,12,11,10, 9, 8,
7, 6, 5, 4, 3, 2, 1, 0,
0, 1, 2, 3, 4, 5, 6, 7,
8, 9,10,11,12,13,14,15,
},
{
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,
},
{
11,11,11,11,11,11,11,11,
11,11,11,11,11,11,11,11,
4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4,
},
{
8, 9,10,12,13,14,14,15,
15,15,14,14,13,12,10, 9,
8, 6, 5, 3, 2, 1, 1, 0,
0, 0, 1, 1, 2, 3, 5, 6,
},
};
if (linear_counter > 0 && length_counter[0] > 0
&& (!option[OPT_TRI_MUTE] || tri_freq > 0))
{
counter[0] -= clocks;
while (counter[0] < 0)
{
tphase = (tphase + 1) & 31;
counter[0] += (tri_freq + 1);
}
}
unsigned int ret = wavtbl[tduty][tphase];
return ret;
}
// ノイズチャンネルの計算 戻り値は0-127
// 低サンプリングレートで合成するとエイリアスノイズが激しいので
// ノイズだけはこの関数内で高クロック合成し、簡易なサンプリングレート
// 変換を行っている。
unsigned int I5E01_DMC::calc_noise(unsigned int clocks)
{
unsigned int env = envelope_disable ? noise_volume : envelope_counter;
if (length_counter[1] < 1) env = 0;
unsigned int last = (noise & 0x4000) ? 0 : env;
if (clocks < 1) return last;
// simple anti-aliasing (noise requires it, even when oversampling is off)
unsigned int count = 0;
unsigned int accum = counter[1] * last; // samples pending from previous calc
unsigned int accum_clocks = counter[1];
#ifdef _DEBUG
int start_clocks = counter[1];
#endif
if (counter[1] < 0) // only happens on startup when using the randomize noise option
{
accum = 0;
accum_clocks = 0;
}
counter[1] -= clocks;
assert(nfreq > 0); // prevent infinite loop
while (counter[1] < 0)
{
// tick the noise generator
unsigned int feedback = (noise & 1) ^ ((noise & noise_tap) ? 1 : 0);
noise = (noise >> 1) | (feedback << 14);
last = (noise & 0x4000) ? 0 : env;
accum += (last * nfreq);
counter[1] += nfreq;
++count;
accum_clocks += nfreq;
}
if (count < 1) // no change over interval, don't anti-alias
{
return last;
}
accum -= (last * counter[1]); // remove these samples which belong in the next calc
accum_clocks -= counter[1];
#ifdef _DEBUG
if (start_clocks >= 0) assert(accum_clocks == clocks); // these should be equal
#endif
unsigned int average = accum / accum_clocks;
assert(average <= 15); // above this would indicate overflow
return average;
}
// Tick the DMC for the number of clocks, and return output counter;
unsigned int I5E01_DMC::calc_dmc(unsigned int clocks)
{
counter[2] -= clocks;
assert(dfreq > 0); // prevent infinite loop
while (counter[2] < 0)
{
counter[2] += dfreq;
if (data > 0x100) // data = 0x100 when shift register is empty
{
if (!empty)
{
if ((data & 1) && (damp < 63))
damp++;
else if (!(data & 1) && (0 < damp))
damp--;
}
data >>= 1;
}
if (data <= 0x100) // shift register is empty
{
if (dlength > 0)
{
memory(daddress, data);
// (checking for the 3-cycle case would require sub-instruction emulation)
data &= 0xFF; // read 8 bits
if (option[OPT_DPCM_REVERSE]) data = BITREVERSE[data];
data |= 0x10000; // use an extra bit to signal end of data
empty = false;
daddress = ((daddress + 1) & 0xFFFF) | 0x8000;
--dlength;
if (dlength == 0)
{
if (mode & 1) // looped DPCM = auto-reload
{
daddress = ((adr_reg << 6) | 0xC000);
dlength = (len_reg << 4) + 1;
}
else if (mode & 2) // IRQ and not looped
{
irq = true;
}
}
}
else
{
data = 0x10000; // DMC will do nothing
empty = true;
}
}
}
return (damp << 1) + dac_lsb;
}
void I5E01_DMC::TickFrameSequence(unsigned int clocks)
{
frame_sequence_count += clocks;
while (frame_sequence_count > frame_sequence_length)
{
FrameSequence(frame_sequence_step);
frame_sequence_count -= frame_sequence_length;
++frame_sequence_step;
if (frame_sequence_step >= frame_sequence_steps)
frame_sequence_step = 0;
}
}
void I5E01_DMC::Tick(unsigned int clocks)
{
out[0] = calc_tri(clocks);
out[1] = calc_noise(clocks);
out[2] = calc_dmc(clocks);
}
unsigned int I5E01_DMC::Render(int b[2])
{
out[0] = (mask & 1) ? 0 : out[0];
out[1] = (mask & 2) ? 0 : out[1];
out[2] = (mask & 4) ? 0 : out[2];
int m[3];
m[0] = tnd_table[0][out[0]][0][0];
m[1] = tnd_table[0][0][out[1]][0];
m[2] = tnd_table[0][0][0][out[2]];
if (option[OPT_NONLINEAR_MIXER])
{
int ref = m[0] + m[1] + m[2];
int voltage = tnd_table[1][out[0]][out[1]][out[2]];
if (ref)
{
for (int i = 0; i < 3; ++i)
m[i] = (m[i] * voltage) / ref;
}
else
{
for (int i = 0; i < 3; ++i)
m[i] = voltage;
}
}
// anti-click nullifies any 4011 write but preserves nonlinearity
if (option[OPT_DPCM_ANTI_CLICK])
{
if (dmc_pop) // $4011 will cause pop this frame
{
// adjust offset to counteract pop
dmc_pop_offset += dmc_pop_follow - m[2];
dmc_pop = false;
// prevent overflow, keep headspace at edges
const int OFFSET_MAX = (1 << 30) - (4 << 16);
if (dmc_pop_offset > OFFSET_MAX) dmc_pop_offset = OFFSET_MAX;
if (dmc_pop_offset < -OFFSET_MAX) dmc_pop_offset = -OFFSET_MAX;
}
dmc_pop_follow = m[2]; // remember previous position
m[2] += dmc_pop_offset; // apply offset
// TODO implement this in a better way
// roll off offset (not ideal, but prevents overflow)
if (dmc_pop_offset > 0) --dmc_pop_offset;
else if (dmc_pop_offset < 0) ++dmc_pop_offset;
}
b[0] = m[0] * sm[0][0];
b[0] += m[1] * sm[0][1];
b[0] += m[2] * sm[0][2];
b[0] >>= 7;
b[1] = m[0] * sm[1][0];
b[1] += m[1] * sm[1][1];
b[1] += m[2] * sm[1][2];
b[1] >>= 7;
return 2;
}
void I5E01_DMC::SetClock(double c)
{
clock = c;
}
void I5E01_DMC::SetRate(double r)
{
rate = (unsigned int)(r ? r : DEFAULT_RATE);
}
void I5E01_DMC::SetPal(bool is_pal)
{
pal = (is_pal ? 1 : 0);
// set CPU cycles in frame_sequence
frame_sequence_length = is_pal ? 8314 : 7458;
}
void I5E01_DMC::SetAPU(I5E01_APU* apu_)
{
apu = apu_;
}
// Initializing TRI, NOISE, DPCM mixing table
void I5E01_DMC::InitializeTNDTable(double wt, double wn, double wd) {
// volume adjusted by 0.95 based on empirical measurements
const double MASTER = 8192.0 * 0.95;
// truthfully, the nonlinear curve does not appear to match well
// with my tests. Do more testing of the APU/DMC DAC later.
// this value keeps the triangle consistent with measured levels,
// but not necessarily the rest of this APU channel,
// because of the lack of a good DAC model, currently.
{ // Linear Mixer
for (int t = 0; t < 16; t++) {
for (int n = 0; n < 16; n++) {
for (int d = 0; d < 128; d++) {
tnd_table[0][t][n][d] = (unsigned int)(MASTER * (3.0 * t + 2.0 * n + d) / 208.0);
}
}
}
}
{ // Non-Linear Mixer
tnd_table[1][0][0][0] = 0;
for (int t = 0; t < 16; t++) {
for (int n = 0; n < 16; n++) {
for (int d = 0; d < 128; d++) {
if (t != 0 || n != 0 || d != 0)
tnd_table[1][t][n][d] = (unsigned int)((MASTER * 159.79) / (100.0 + 1.0 / ((double)t / wt + (double)n / wn + (double)d / wd)));
}
}
}
}
}
void I5E01_DMC::Reset()
{
int i;
mask = 0;
InitializeTNDTable(8227, 12241, 22638);
counter[0] = 0;
counter[1] = 0;
counter[2] = 0;
tphase = 0;
tduty = 0;
nfreq = wavlen_table[0][0];
dfreq = freq_table[0][0];
tri_freq = 0;
linear_counter = 0;
linear_counter_reload = 0;
linear_counter_halt = 0;
linear_counter_control = 0;
noise_volume = 0;
noise = 0;
noise_tap = 0;
envelope_loop = 0;
envelope_disable = 0;
envelope_write = 0;
envelope_div_period = 0;
envelope_div = 0;
envelope_counter = 0;
enable[0] = 0;
enable[1] = 0;
length_counter[0] = 0;
length_counter[1] = 0;
frame_irq = false;
frame_irq_enable = false;
frame_sequence_count = 0;
frame_sequence_steps = 4;
frame_sequence_step = 0;
for (i = 0; i < 0x0F; i++)
Write(0x4108 + i, 0);
Write(0x4117, 0x40);
irq = false;
Write(0x4115, 0x00);
if (option[OPT_UNMUTE_ON_RESET])
Write(0x4115, 0x0f);
out[0] = out[1] = out[2] = 0;
damp = 0;
dmc_pop = false;
dmc_pop_offset = 0;
dmc_pop_follow = 0;
dac_lsb = 0;
data = 0x100;
empty = true;
adr_reg = 0;
dlength = 0;
len_reg = 0;
daddress = 0;
noise = 1;
noise_tap = (1 << 1);
SetRate(rate);
}
void I5E01_DMC::SetMemory(std::function<void(unsigned short, unsigned int&)> r)
{
memory = r;
}
void I5E01_DMC::SetOption(int id, int val)
{
if (id < OPT_END)
{
option[id] = val;
if (id == OPT_NONLINEAR_MIXER)
InitializeTNDTable(8227, 12241, 22638);
}
}
bool I5E01_DMC::Write(unsigned int adr, unsigned int val, unsigned int id)
{
static const unsigned char length_table[32] = {
0x0A, 0xFE,
0x14, 0x02,
0x28, 0x04,
0x50, 0x06,
0xA0, 0x08,
0x3C, 0x0A,
0x0E, 0x0C,
0x1A, 0x0E,
0x0C, 0x10,
0x18, 0x12,
0x30, 0x14,
0x60, 0x16,
0xC0, 0x18,
0x48, 0x1A,
0x10, 0x1C,
0x20, 0x1E
};
// hack
adr+=0x100;
if (adr == 0x4115)
{
enable[0] = (val & 4) ? true : false;
enable[1] = (val & 8) ? true : false;
if (!enable[0])
{
length_counter[0] = 0;
}
if (!enable[1])
{
length_counter[1] = 0;
}
if ((val & 16) && dlength == 0)
{
daddress = (0xC000 | (adr_reg << 6));
dlength = (len_reg << 4) + 1;
}
else if (!(val & 16))
{
dlength = 0;
}
irq = false;
reg[adr - 0x4108] = val;
return true;
}
if (adr == 0x4117)
{
//DEBUG_OUT("4017 = %02X\n", val);
frame_irq_enable = ((val & 0x40) != 0x40);
if (frame_irq_enable) frame_irq = false;
frame_sequence_count = 0;
if (val & 0x80)
{
frame_sequence_steps = 5;
frame_sequence_step = 0;
FrameSequence(frame_sequence_step);
++frame_sequence_step;
}
else
{
frame_sequence_steps = 4;
frame_sequence_step = 1;
}
}
if (adr < 0x4108 || 0x4113 < adr)
return false;
reg[adr - 0x4108] = val & 0xff;
//DEBUG_OUT("$%04X %02X\n", adr, val);
switch (adr)
{
// tri
case 0x4108:
linear_counter_control = (val >> 7) & 1;
linear_counter_reload = val & 0x7F;
break;
case 0x4109:
tduty = val & 3;
break;
case 0x410a:
tri_freq = val | (tri_freq & 0x700);
break;
case 0x410b:
tri_freq = (tri_freq & 0xff) | ((val & 0x7) << 8);
linear_counter_halt = true;
if (enable[0])
{
length_counter[0] = length_table[(val >> 3) & 0x1f];
}
break;
// noise
case 0x410c:
noise_volume = val & 15;
envelope_div_period = val & 15;
envelope_disable = (val >> 4) & 1;
envelope_loop = (val >> 5) & 1;
break;
case 0x410d:
break;
case 0x410e:
if (option[OPT_ENABLE_PNOISE])
noise_tap = (val & 0x80) ? (1 << 6) : (1 << 1);
else
noise_tap = (1 << 1);
nfreq = wavlen_table[pal][val & 31];
break;
case 0x410f:
if (enable[1])
{
length_counter[1] = length_table[(val >> 3) & 0xf];
}
envelope_write = true;
break;
// dmc
case 0x4110:
mode = (val >> 6) & 3;
if (!(mode & 2))
{
irq = false;
}
dfreq = freq_table[pal][val & 15];
break;
case 0x4111:
if (option[OPT_ENABLE_4011])
{
damp = (val >> 1) & 0x3f;
dac_lsb = val & 1;
dmc_pop = true;
}
break;
case 0x4112:
adr_reg = val & 0xff;
// ここでdaddressは更新されない
break;
case 0x4113:
len_reg = val & 0xff;
// ここでlengthは更新されない
break;
default:
return false;
}
return true;
}
bool I5E01_DMC::Read(unsigned int adr, unsigned int& val, unsigned int id)
{
if (adr == 0x4115)
{
val |= (irq ? 0x80 : 0)
| (frame_irq ? 0x40 : 0)
| ((dlength > 0) ? 0x10 : 0)
| (length_counter[1] ? 0x08 : 0)
| (length_counter[0] ? 0x04 : 0)
;
frame_irq = false;
return true;
}
else if (0x4108 <= adr && adr <= 0x4114)
{
val |= reg[adr - 0x4108];
return true;
}
else
return false;
}
} // namespace

View file

@ -0,0 +1,127 @@
#ifndef _I5E01_DMC_H_
#define _I5E01_DMC_H_
#include <functional>
namespace xgm
{
class I5E01_APU; // forward declaration
/** Bottom Half of APU **/
class I5E01_DMC
{
public:
enum
{
OPT_ENABLE_4011 = 0,
OPT_ENABLE_PNOISE,
OPT_UNMUTE_ON_RESET,
OPT_DPCM_ANTI_CLICK,
OPT_NONLINEAR_MIXER,
OPT_RANDOMIZE_NOISE,
OPT_TRI_MUTE,
OPT_RANDOMIZE_TRI,
OPT_DPCM_REVERSE,
OPT_END
};
protected:
const int GETA_BITS;
// DPCM.
static const unsigned int freq_table[2][16];
// Noise.
static const unsigned int wavlen_table[2][32];
unsigned int tnd_table[2][16][16][128];
int option[OPT_END];
int mask;
int sm[2][3];
unsigned char reg[0x10];
unsigned int len_reg;
unsigned int adr_reg;
std::function<void(unsigned short, unsigned int&)> memory;
unsigned int daddress;
unsigned int dlength;
unsigned int data;
bool empty;
short damp;
int dac_lsb;
bool dmc_pop;
int dmc_pop_offset;
int dmc_pop_follow;
double clock;
unsigned int rate;
int pal;
int mode;
bool irq;
int counter[3]; // frequency dividers
int tphase; // triangle phase
int tduty; // wave shape
unsigned int nfreq; // noise frequency
unsigned int dfreq; // DPCM frequency
unsigned int tri_freq;
int linear_counter;
int linear_counter_reload;
bool linear_counter_halt;
bool linear_counter_control;
int noise_volume;
unsigned int noise, noise_tap;
// noise envelope
bool envelope_loop;
bool envelope_disable;
bool envelope_write;
int envelope_div_period;
int envelope_div;
int envelope_counter;
bool enable[2]; // tri/noise enable
int length_counter[2]; // 0=tri, 1=noise
// frame sequencer
I5E01_APU* apu; // apu is clocked by DMC's frame sequencer
int frame_sequence_count; // current cycle count
int frame_sequence_length; // CPU cycles per FrameSequence
int frame_sequence_step; // current step of frame sequence
int frame_sequence_steps; // 4/5 steps per frame
bool frame_irq;
bool frame_irq_enable;
inline unsigned int calc_tri(unsigned int clocks);
inline unsigned int calc_dmc(unsigned int clocks);
inline unsigned int calc_noise(unsigned int clocks);
public:
unsigned int out[3];
I5E01_DMC();
~I5E01_DMC();
void InitializeTNDTable(double wt, double wn, double wd);
void SetPal(bool is_pal);
void SetAPU(I5E01_APU* apu_);
void SetMemory(std::function<void(unsigned short, unsigned int&)> r);
void FrameSequence(int s);
int GetDamp() { return (damp << 1) | dac_lsb; }
void TickFrameSequence(unsigned int clocks);
void Reset();
void Tick(unsigned int clocks);
unsigned int Render(int b[2]);
bool Write(unsigned int adr, unsigned int val, unsigned int id = 0);
bool Read(unsigned int adr, unsigned int& val, unsigned int id = 0);
void SetRate(double rate);
void SetClock(double rate);
void SetOption(int, int);
void SetMask(int m) { mask = m; }
void SetStereoMix(int trk, short mixl, short mixr);
};
}
#endif