add 5E01
This commit is contained in:
parent
3423ac8774
commit
a83df6e8fd
|
@ -516,6 +516,8 @@ 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/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_dmc.cpp
|
||||
src/engine/platform/sound/nes_nsfplay/nes_fds.cpp
|
||||
|
|
|
@ -64,8 +64,13 @@ const char** DivPlatformNES::getRegisterSheet() {
|
|||
|
||||
void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) {
|
||||
if (useNP) {
|
||||
nes1_NP->Write(addr,data);
|
||||
nes2_NP->Write(addr,data);
|
||||
if (isE) {
|
||||
e1_NP->Write(addr,data);
|
||||
e2_NP->Write(addr,data);
|
||||
} else {
|
||||
nes1_NP->Write(addr,data);
|
||||
nes2_NP->Write(addr,data);
|
||||
}
|
||||
} else {
|
||||
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) {
|
||||
if (useNP) {
|
||||
acquire_NSFPlay(buf,len);
|
||||
if (isE) {
|
||||
acquire_NSFPlayE(buf,len);
|
||||
} else {
|
||||
acquire_NSFPlay(buf,len);
|
||||
}
|
||||
} else {
|
||||
acquire_puNES(buf,len);
|
||||
}
|
||||
|
@ -242,6 +278,8 @@ void DivPlatformNES::tick(bool sysTick) {
|
|||
}
|
||||
if (i!=2) {
|
||||
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
|
||||
chan[i].freqChanged=true;
|
||||
|
@ -279,7 +317,9 @@ void DivPlatformNES::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
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);
|
||||
} else {
|
||||
if (ntPos<0) ntPos=0;
|
||||
|
@ -678,8 +718,13 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
|||
void DivPlatformNES::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
if (useNP) {
|
||||
nes1_NP->SetMask(((int)isMuted[0])|(isMuted[1]<<1));
|
||||
nes2_NP->SetMask(((int)isMuted[2])|(isMuted[3]<<1)|(isMuted[4]<<2));
|
||||
if (isE) {
|
||||
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 {
|
||||
nes->muted[ch]=mute;
|
||||
}
|
||||
|
@ -743,10 +788,17 @@ void DivPlatformNES::reset() {
|
|||
linearCount=255;
|
||||
|
||||
if (useNP) {
|
||||
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));
|
||||
if (isE) {
|
||||
e1_NP->Reset();
|
||||
e2_NP->Reset();
|
||||
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 {
|
||||
apu_turn_on(nes,apuType);
|
||||
nes->apu.cpu_cycles=0;
|
||||
|
@ -779,11 +831,19 @@ void DivPlatformNES::setFlags(const DivConfig& flags) {
|
|||
apuType=0;
|
||||
}
|
||||
if (useNP) {
|
||||
nes1_NP->SetClock(rate);
|
||||
nes1_NP->SetRate(rate);
|
||||
nes2_NP->SetClock(rate);
|
||||
nes2_NP->SetRate(rate);
|
||||
nes2_NP->SetPal(apuType==1);
|
||||
if (isE) {
|
||||
e1_NP->SetClock(rate);
|
||||
e1_NP->SetRate(rate);
|
||||
e2_NP->SetClock(rate);
|
||||
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 {
|
||||
nes->apu.type=apuType;
|
||||
}
|
||||
|
@ -819,6 +879,8 @@ void DivPlatformNES::setNSFPlay(bool use) {
|
|||
|
||||
void DivPlatformNES::set5E01(bool use) {
|
||||
isE=use;
|
||||
// for now
|
||||
if (isE) useNP=true;
|
||||
}
|
||||
|
||||
unsigned char DivPlatformNES::readDMC(unsigned short addr) {
|
||||
|
@ -897,14 +959,25 @@ int DivPlatformNES::init(DivEngine* p, int channels, int sugRate, const DivConfi
|
|||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
if (useNP) {
|
||||
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);
|
||||
if (isE) {
|
||||
e1_NP=new xgm::I5E01_APU;
|
||||
e1_NP->SetOption(xgm::I5E01_APU::OPT_NONLINEAR_MIXER,1);
|
||||
e2_NP=new xgm::I5E01_DMC;
|
||||
e2_NP->SetOption(xgm::I5E01_DMC::OPT_NONLINEAR_MIXER,1);
|
||||
e2_NP->SetMemory([this](unsigned short addr, unsigned int& data) {
|
||||
data=readDMC(addr);
|
||||
});
|
||||
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 {
|
||||
nes=new struct NESAPU;
|
||||
nes->readDMC=_readDMC;
|
||||
|
@ -933,8 +1006,13 @@ void DivPlatformNES::quit() {
|
|||
delete oscBuf[i];
|
||||
}
|
||||
if (useNP) {
|
||||
delete nes1_NP;
|
||||
delete nes2_NP;
|
||||
if (isE) {
|
||||
delete e1_NP;
|
||||
delete e2_NP;
|
||||
} else {
|
||||
delete nes1_NP;
|
||||
delete nes2_NP;
|
||||
}
|
||||
} else {
|
||||
delete nes;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "../dispatch.h"
|
||||
|
||||
#include "sound/nes_nsfplay/nes_apu.h"
|
||||
#include "sound/nes_nsfplay/5e01_apu.h"
|
||||
|
||||
class DivPlatformNES: public DivDispatch {
|
||||
struct Channel: public SharedChannel<signed char> {
|
||||
|
@ -66,6 +67,8 @@ class DivPlatformNES: public DivDispatch {
|
|||
struct NESAPU* nes;
|
||||
xgm::NES_APU* nes1_NP;
|
||||
xgm::NES_DMC* nes2_NP;
|
||||
xgm::I5E01_APU* e1_NP;
|
||||
xgm::I5E01_DMC* e2_NP;
|
||||
unsigned char regPool[128];
|
||||
unsigned int sampleOffDPCM[256];
|
||||
DivMemoryComposition memCompo;
|
||||
|
@ -77,6 +80,7 @@ class DivPlatformNES: public DivDispatch {
|
|||
unsigned char calcDPCMRate(int inRate);
|
||||
void acquire_puNES(short** buf, size_t len);
|
||||
void acquire_NSFPlay(short** buf, size_t len);
|
||||
void acquire_NSFPlayE(short** buf, size_t len);
|
||||
|
||||
public:
|
||||
void acquire(short** buf, size_t len);
|
||||
|
|
401
src/engine/platform/sound/nes_nsfplay/5e01_apu.cpp
Normal file
401
src/engine/platform/sound/nes_nsfplay/5e01_apu.cpp
Normal 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;
|
89
src/engine/platform/sound/nes_nsfplay/5e01_apu.h
Normal file
89
src/engine/platform/sound/nes_nsfplay/5e01_apu.h
Normal 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
|
732
src/engine/platform/sound/nes_nsfplay/5e01_dmc.cpp
Normal file
732
src/engine/platform/sound/nes_nsfplay/5e01_dmc.cpp
Normal 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
|
127
src/engine/platform/sound/nes_nsfplay/5e01_dmc.h
Normal file
127
src/engine/platform/sound/nes_nsfplay/5e01_dmc.h
Normal 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
|
Loading…
Reference in a new issue