2022-04-19 08:14:12 -04:00
|
|
|
#include "nes_mmc5.h"
|
|
|
|
|
|
|
|
|
|
namespace xgm
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
NES_MMC5::NES_MMC5 ()
|
|
|
|
|
{
|
|
|
|
|
cpu = NULL;
|
|
|
|
|
SetClock (DEFAULT_CLOCK);
|
|
|
|
|
SetRate (DEFAULT_RATE);
|
|
|
|
|
option[OPT_NONLINEAR_MIXER] = true;
|
|
|
|
|
option[OPT_PHASE_REFRESH] = true;
|
|
|
|
|
frame_sequence_count = 0;
|
|
|
|
|
|
|
|
|
|
// square nonlinear mix, same as 2A03
|
|
|
|
|
square_table[0] = 0;
|
|
|
|
|
for(int i=1;i<32;i++)
|
|
|
|
|
square_table[i]=(INT32)((8192.0*95.88)/(8128.0/i+100));
|
|
|
|
|
|
|
|
|
|
// 2A03 style nonlinear pcm mix with double the bits
|
|
|
|
|
//pcm_table[0] = 0;
|
|
|
|
|
//INT32 wd = 22638;
|
|
|
|
|
//for(int d=1;d<256; ++d)
|
|
|
|
|
// pcm_table[d] = (INT32)((8192.0*159.79)/(100.0+1.0/((double)d/wd)));
|
|
|
|
|
|
|
|
|
|
// linear pcm mix (actual hardware seems closer to this)
|
|
|
|
|
pcm_table[0] = 0;
|
|
|
|
|
double pcm_scale = 32.0;
|
|
|
|
|
for (int d=1; d<256; ++d)
|
|
|
|
|
pcm_table[d] = (INT32)(double(d) * pcm_scale);
|
|
|
|
|
|
|
|
|
|
// stereo mix
|
|
|
|
|
for(int c=0;c<2;++c)
|
|
|
|
|
for(int t=0;t<3;++t)
|
|
|
|
|
sm[c][t] = 128;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-01 23:13:11 -04:00
|
|
|
NES_MMC5::‾NES_MMC5 ()
|
2022-04-19 08:14:12 -04:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NES_MMC5::Reset ()
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
scounter[0] = 0;
|
|
|
|
|
scounter[1] = 0;
|
|
|
|
|
sphase[0] = 0;
|
|
|
|
|
sphase[1] = 0;
|
|
|
|
|
|
|
|
|
|
envelope_div[0] = 0;
|
|
|
|
|
envelope_div[1] = 0;
|
|
|
|
|
length_counter[0] = 0;
|
|
|
|
|
length_counter[1] = 0;
|
|
|
|
|
envelope_counter[0] = 0;
|
|
|
|
|
envelope_counter[1] = 0;
|
|
|
|
|
frame_sequence_count = 0;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < 8; i++)
|
|
|
|
|
Write (0x5000 + i, 0);
|
|
|
|
|
|
|
|
|
|
Write(0x5015, 0);
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < 3; ++i)
|
|
|
|
|
out[i] = 0;
|
|
|
|
|
|
|
|
|
|
mask = 0;
|
|
|
|
|
pcm = 0; // PCM channel
|
|
|
|
|
pcm_mode = false; // write mode
|
|
|
|
|
|
|
|
|
|
SetRate(rate);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NES_MMC5::SetOption (int id, int val)
|
|
|
|
|
{
|
|
|
|
|
if(id<OPT_END) option[id] = val;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NES_MMC5::SetClock (double c)
|
|
|
|
|
{
|
|
|
|
|
this->clock = c;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NES_MMC5::SetRate (double r)
|
|
|
|
|
{
|
|
|
|
|
rate = r ? r : DEFAULT_RATE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NES_MMC5::FrameSequence ()
|
|
|
|
|
{
|
|
|
|
|
// 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];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MMC5 length counter is clocked at 240hz, unlike 2A03
|
|
|
|
|
for (int i=0; i < 2; ++i)
|
|
|
|
|
{
|
|
|
|
|
if (!envelope_loop[i] && (length_counter[i] > 0))
|
|
|
|
|
--length_counter[i];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
INT32 NES_MMC5::calc_sqr (int i, UINT32 clocks)
|
|
|
|
|
{
|
|
|
|
|
static const INT16 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, 1, 1, 0, 0, 0, 0, 0, 0},
|
|
|
|
|
{1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
scounter[i] += clocks;
|
|
|
|
|
while (scounter[i] > freq[i])
|
|
|
|
|
{
|
|
|
|
|
sphase[i] = (sphase[i] + 1) & 15;
|
|
|
|
|
scounter[i] -= (freq[i] + 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
INT32 ret = 0;
|
|
|
|
|
if (length_counter[i] > 0)
|
|
|
|
|
{
|
|
|
|
|
// note MMC5 does not silence the highest 8 frequencies like APU,
|
|
|
|
|
// because this is done by the sweep unit.
|
|
|
|
|
|
|
|
|
|
int v = envelope_disable[i] ? volume[i] : envelope_counter[i];
|
|
|
|
|
ret = sqrtbl[duty[i]][sphase[i]] ? v : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NES_MMC5::TickFrameSequence (UINT32 clocks)
|
|
|
|
|
{
|
|
|
|
|
frame_sequence_count += clocks;
|
|
|
|
|
while (frame_sequence_count > 7458)
|
|
|
|
|
{
|
|
|
|
|
FrameSequence();
|
|
|
|
|
frame_sequence_count -= 7458;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NES_MMC5::Tick (UINT32 clocks)
|
|
|
|
|
{
|
|
|
|
|
out[0] = calc_sqr(0, clocks);
|
|
|
|
|
out[1] = calc_sqr(1, clocks);
|
|
|
|
|
out[2] = pcm;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UINT32 NES_MMC5::Render (INT32 b[2])
|
|
|
|
|
{
|
|
|
|
|
out[0] = (mask & 1) ? 0 : out[0];
|
|
|
|
|
out[1] = (mask & 2) ? 0 : out[1];
|
|
|
|
|
out[2] = (mask & 4) ? 0 : out[2];
|
|
|
|
|
|
|
|
|
|
INT32 m[3];
|
|
|
|
|
|
|
|
|
|
if(option[OPT_NONLINEAR_MIXER])
|
|
|
|
|
{
|
|
|
|
|
// squares nonlinear
|
|
|
|
|
INT32 voltage = square_table[out[0] + out[1]];
|
|
|
|
|
m[0] = out[0] << 6;
|
|
|
|
|
m[1] = out[1] << 6;
|
|
|
|
|
INT32 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// pcm nonlinear
|
|
|
|
|
m[2] = pcm_table[out[2]];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// squares
|
|
|
|
|
m[0] = out[0] << 6;
|
|
|
|
|
m[1] = out[1] << 6;
|
|
|
|
|
|
|
|
|
|
// pcm channel
|
|
|
|
|
m[2] = out[2] << 5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// note polarity is flipped on output
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool NES_MMC5::Write (UINT32 adr, UINT32 val, UINT32 id)
|
|
|
|
|
{
|
|
|
|
|
int ch;
|
|
|
|
|
|
|
|
|
|
static const UINT8 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 ((0x5c00 <= adr) && (adr < 0x5ff0))
|
|
|
|
|
{
|
|
|
|
|
ram[adr & 0x3ff] = val;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else if ((0x5000 <= adr) && (adr < 0x5008))
|
|
|
|
|
{
|
|
|
|
|
reg[adr & 0x7] = val;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (adr)
|
|
|
|
|
{
|
|
|
|
|
case 0x5000:
|
|
|
|
|
case 0x5004:
|
|
|
|
|
ch = (adr >> 2) & 1;
|
|
|
|
|
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;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x5002:
|
|
|
|
|
case 0x5006:
|
|
|
|
|
ch = (adr >> 2) & 1;
|
|
|
|
|
freq[ch] = val + (freq[ch] & 0x700);
|
|
|
|
|
if (scounter[ch] > freq[ch]) scounter[ch] = freq[ch];
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x5003:
|
|
|
|
|
case 0x5007:
|
|
|
|
|
ch = (adr >> 2) & 1;
|
|
|
|
|
freq[ch] = (freq[ch] & 0xff) + ((val & 7) << 8);
|
|
|
|
|
if (scounter[ch] > freq[ch]) scounter[ch] = freq[ch];
|
|
|
|
|
// phase reset
|
|
|
|
|
if (option[OPT_PHASE_REFRESH])
|
|
|
|
|
sphase[ch] = 0;
|
|
|
|
|
envelope_write[ch] = true;
|
|
|
|
|
if (enable[ch])
|
|
|
|
|
{
|
|
|
|
|
length_counter[ch] = length_table[(val >> 3) & 0x1f];
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// PCM channel control
|
|
|
|
|
case 0x5010:
|
|
|
|
|
pcm_mode = ((val & 1) != 0); // 0 = write, 1 = read
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// PCM channel control
|
|
|
|
|
case 0x5011:
|
|
|
|
|
if (!pcm_mode)
|
|
|
|
|
{
|
|
|
|
|
val &= 0xFF;
|
|
|
|
|
if (val != 0) pcm = val;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x5015:
|
|
|
|
|
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;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x5205:
|
|
|
|
|
mreg[0] = val;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x5206:
|
|
|
|
|
mreg[1] = val;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool NES_MMC5::Read (UINT32 adr, UINT32 & val, UINT32 id)
|
|
|
|
|
{
|
|
|
|
|
// in PCM read mode, reads from $8000-$C000 automatically load the PCM output
|
|
|
|
|
if (pcm_mode && (0x8000 <= adr) && (adr < 0xC000) && cpu)
|
|
|
|
|
{
|
|
|
|
|
pcm_mode = false; // prevent recursive entry
|
|
|
|
|
UINT32 pcm_read;
|
|
|
|
|
cpu->Read(adr, pcm_read);
|
|
|
|
|
pcm_read &= 0xFF;
|
|
|
|
|
if (pcm_read != 0)
|
|
|
|
|
pcm = pcm_read;
|
|
|
|
|
pcm_mode = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((0x5000 <= adr) && (adr < 0x5008))
|
|
|
|
|
{
|
|
|
|
|
val = reg[adr&0x7];
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else if(adr == 0x5015)
|
|
|
|
|
{
|
|
|
|
|
val = (enable[1]?2:0)|(enable[0]?1:0);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((0x5c00 <= adr) && (adr < 0x5ff0))
|
|
|
|
|
{
|
|
|
|
|
val = ram[adr & 0x3ff];
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else if (adr == 0x5205)
|
|
|
|
|
{
|
|
|
|
|
val = (mreg[0] * mreg[1]) & 0xff;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else if (adr == 0x5206)
|
|
|
|
|
{
|
|
|
|
|
val = (mreg[0] * mreg[1]) >> 8;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NES_MMC5::SetStereoMix(int trk, xgm::INT16 mixl, xgm::INT16 mixr)
|
|
|
|
|
{
|
|
|
|
|
if (trk < 0) return;
|
|
|
|
|
if (trk > 2) return;
|
|
|
|
|
sm[0][trk] = mixl;
|
|
|
|
|
sm[1][trk] = mixr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ITrackInfo *NES_MMC5::GetTrackInfo(int trk)
|
|
|
|
|
{
|
|
|
|
|
assert(trk<3);
|
|
|
|
|
|
|
|
|
|
if (trk < 2) // square
|
|
|
|
|
{
|
|
|
|
|
trkinfo[trk]._freq = freq[trk];
|
|
|
|
|
if(freq[trk])
|
|
|
|
|
trkinfo[trk].freq = clock/16/(freq[trk] + 1);
|
|
|
|
|
else
|
|
|
|
|
trkinfo[trk].freq = 0;
|
|
|
|
|
|
|
|
|
|
trkinfo[trk].output = out[trk];
|
|
|
|
|
trkinfo[trk].max_volume = 15;
|
|
|
|
|
trkinfo[trk].volume = volume[trk]+(envelope_disable[trk]?0:0x10);
|
|
|
|
|
trkinfo[trk].key = (envelope_disable[trk]?(volume[trk]>0): (envelope_counter[trk]>0));
|
|
|
|
|
trkinfo[trk].tone = duty[trk];
|
|
|
|
|
}
|
|
|
|
|
else // pcm
|
|
|
|
|
{
|
|
|
|
|
trkinfo[trk]._freq = 0;
|
|
|
|
|
trkinfo[trk].freq = 0;
|
|
|
|
|
trkinfo[trk].output = out[2];
|
|
|
|
|
trkinfo[trk].max_volume = 255;
|
|
|
|
|
trkinfo[trk].volume = pcm;
|
|
|
|
|
trkinfo[trk].key = 0;
|
|
|
|
|
trkinfo[trk].tone = pcm_mode ? 1 : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &trkinfo[trk];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// pcm read mode requires CPU read access
|
|
|
|
|
void NES_MMC5::SetCPU(NES_CPU* cpu_)
|
|
|
|
|
{
|
|
|
|
|
cpu = cpu_;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}// namespace
|