333 lines
9 KiB
C++
333 lines
9 KiB
C++
#include <cstring>
|
|
#include "nes_n106.h"
|
|
#include "common.h"
|
|
|
|
namespace xgm {
|
|
|
|
NES_N106::NES_N106 ()
|
|
{
|
|
option[OPT_SERIAL] = 0;
|
|
option[OPT_PHASE_READ_ONLY] = 0;
|
|
option[OPT_LIMIT_WAVELENGTH] = 0;
|
|
SetClock (DEFAULT_CLOCK);
|
|
SetRate (DEFAULT_RATE);
|
|
for (int i=0; i < 8; ++i)
|
|
{
|
|
sm[0][i] = 128;
|
|
sm[1][i] = 128;
|
|
}
|
|
Reset();
|
|
}
|
|
|
|
NES_N106::~NES_N106 ()
|
|
{
|
|
}
|
|
|
|
void NES_N106::SetStereoMix (int trk, short mixl, short mixr)
|
|
{
|
|
if (trk < 0 || trk >= 8) return;
|
|
trk = 7-trk; // displayed channels are inverted
|
|
sm[0][trk] = mixl;
|
|
sm[1][trk] = mixr;
|
|
}
|
|
|
|
|
|
void NES_N106::SetClock (double c)
|
|
{
|
|
clock = c;
|
|
}
|
|
|
|
void NES_N106::SetRate (double r)
|
|
{
|
|
rate = r;
|
|
}
|
|
|
|
void NES_N106::SetMask (int m)
|
|
{
|
|
// bit reverse the mask,
|
|
// N163 waves are displayed in reverse order
|
|
mask = 0
|
|
| ((m & (1<<0)) ? (1<<7) : 0)
|
|
| ((m & (1<<1)) ? (1<<6) : 0)
|
|
| ((m & (1<<2)) ? (1<<5) : 0)
|
|
| ((m & (1<<3)) ? (1<<4) : 0)
|
|
| ((m & (1<<4)) ? (1<<3) : 0)
|
|
| ((m & (1<<5)) ? (1<<2) : 0)
|
|
| ((m & (1<<6)) ? (1<<1) : 0)
|
|
| ((m & (1<<7)) ? (1<<0) : 0);
|
|
}
|
|
|
|
void NES_N106::SetOption (int id, int val)
|
|
{
|
|
if (id<OPT_END) option[id] = val;
|
|
}
|
|
|
|
void NES_N106::Reset ()
|
|
{
|
|
master_disable = false;
|
|
::memset(reg, 0, sizeof(reg));
|
|
reg_select = 0;
|
|
reg_advance = false;
|
|
tick_channel = 0;
|
|
tick_clock = 0;
|
|
render_channel = 0;
|
|
render_clock = 0;
|
|
render_subclock = 0;
|
|
|
|
for (int i=0; i<8; ++i) fout[i] = 0;
|
|
|
|
Write(0xE000, 0x00); // master disable off
|
|
Write(0xF800, 0x80); // select $00 with auto-increment
|
|
for (unsigned int i=0; i<0x80; ++i) // set all regs to 0
|
|
{
|
|
Write(0x4800, 0x00);
|
|
}
|
|
Write(0xF800, 0x00); // select $00 without auto-increment
|
|
}
|
|
|
|
void NES_N106::Tick (unsigned int clocks)
|
|
{
|
|
if (master_disable) return;
|
|
|
|
int channels = get_channels();
|
|
|
|
tick_clock += clocks;
|
|
render_clock += clocks; // keep render in sync
|
|
while (tick_clock > 0)
|
|
{
|
|
int channel = 7-tick_channel;
|
|
|
|
unsigned int phase = get_phase(channel);
|
|
unsigned int freq = get_freq(channel);
|
|
unsigned int len = get_len(channel);
|
|
unsigned int off = get_off(channel);
|
|
int vol = get_vol(channel);
|
|
|
|
// accumulate 24-bit phase
|
|
phase = (phase + freq) & 0x00FFFFFF;
|
|
|
|
// wrap phase if wavelength exceeded
|
|
unsigned int hilen = len << 16;
|
|
while (phase >= hilen) phase -= hilen;
|
|
|
|
// write back phase
|
|
set_phase(phase, channel);
|
|
|
|
// fetch sample (note: N163 output is centred at 8, and inverted w.r.t 2A03)
|
|
int sample = 8 - get_sample(((phase >> 16) + off) & 0xFF);
|
|
fout[channel] = sample * vol;
|
|
|
|
// cycle to next channel every 15 clocks
|
|
tick_clock -= 15;
|
|
++tick_channel;
|
|
if (tick_channel >= channels)
|
|
tick_channel = 0;
|
|
}
|
|
}
|
|
|
|
unsigned int NES_N106::Render (int b[2])
|
|
{
|
|
b[0] = 0;
|
|
b[1] = 0;
|
|
if (master_disable) return 2;
|
|
|
|
int channels = get_channels();
|
|
|
|
if (option[OPT_SERIAL]) // hardware accurate serial multiplexing
|
|
{
|
|
// this could be made more efficient than going clock-by-clock
|
|
// but this way is simpler
|
|
int clocks = render_clock;
|
|
while (clocks > 0)
|
|
{
|
|
int c = 7-render_channel;
|
|
if (0 == ((mask >> c) & 1))
|
|
{
|
|
b[0] += fout[c] * sm[0][c];
|
|
b[1] += fout[c] * sm[1][c];
|
|
}
|
|
|
|
++render_subclock;
|
|
if (render_subclock >= 15) // each channel gets a 15-cycle slice
|
|
{
|
|
render_subclock = 0;
|
|
++render_channel;
|
|
if (render_channel >= channels)
|
|
render_channel = 0;
|
|
}
|
|
--clocks;
|
|
}
|
|
|
|
// increase output level by 1 bits (7 bits already added from sm)
|
|
b[0] <<= 1;
|
|
b[1] <<= 1;
|
|
|
|
// average the output
|
|
if (render_clock > 0)
|
|
{
|
|
b[0] /= render_clock;
|
|
b[1] /= render_clock;
|
|
}
|
|
render_clock = 0;
|
|
}
|
|
else // just mix all channels
|
|
{
|
|
for (int i = (8-channels); i<8; ++i)
|
|
{
|
|
if (0 == ((mask >> i) & 1))
|
|
{
|
|
b[0] += fout[i] * sm[0][i];
|
|
b[1] += fout[i] * sm[1][i];
|
|
}
|
|
}
|
|
|
|
// mix together, increase output level by 8 bits, roll off 7 bits from sm
|
|
int MIX[9] = { 256/1, 256/1, 256/2, 256/3, 256/4, 256/5, 256/6, 256/6, 256/6 };
|
|
b[0] = (b[0] * MIX[channels]) >> 7;
|
|
b[1] = (b[1] * MIX[channels]) >> 7;
|
|
// when approximating the serial multiplex as a straight mix, once the
|
|
// multiplex frequency gets below the nyquist frequency an average mix
|
|
// begins to sound too quiet. To approximate this effect, I don't attenuate
|
|
// any further after 6 channels are active.
|
|
}
|
|
|
|
// 8 bit approximation of master volume
|
|
// max N163 vol vs max APU square
|
|
// unfortunately, games have been measured as low as 3.4x and as high as 8.5x
|
|
// with higher volumes on Erika, King of Kings, and Rolling Thunder
|
|
// and lower volumes on others. Using 6.0x as a rough "one size fits all".
|
|
const double MASTER_VOL = 6.0 * 1223.0;
|
|
const double MAX_OUT = 15.0 * 15.0 * 256.0; // max digital value
|
|
const int GAIN = int((MASTER_VOL / MAX_OUT) * 256.0f);
|
|
b[0] = (b[0] * GAIN) >> 8;
|
|
b[1] = (b[1] * GAIN) >> 8;
|
|
|
|
return 2;
|
|
}
|
|
|
|
bool NES_N106::Write (unsigned int adr, unsigned int val, unsigned int id)
|
|
{
|
|
if (adr == 0xE000) // master disable
|
|
{
|
|
master_disable = ((val & 0x40) != 0);
|
|
return true;
|
|
}
|
|
else if (adr == 0xF800) // register select
|
|
{
|
|
reg_select = (val & 0x7F);
|
|
reg_advance = (val & 0x80) != 0;
|
|
return true;
|
|
}
|
|
else if (adr == 0x4800) // register write
|
|
{
|
|
if (option[OPT_PHASE_READ_ONLY]) // old emulators didn't know phase was stored here
|
|
{
|
|
int c = 15 - (reg_select/8);
|
|
int r = reg_select & 7;
|
|
if (c < get_channels() &&
|
|
(r == 1 ||
|
|
r == 3 ||
|
|
r == 5))
|
|
{
|
|
if (reg_advance)
|
|
reg_select = (reg_select + 1) & 0x7F;
|
|
return true;
|
|
}
|
|
}
|
|
if (option[OPT_LIMIT_WAVELENGTH]) // old emulators ignored top 3 bits of length
|
|
{
|
|
int c = 15 - (reg_select/8);
|
|
int r = reg_select & 7;
|
|
if (c < get_channels() && r == 4)
|
|
{
|
|
val |= 0xE0;
|
|
}
|
|
}
|
|
reg[reg_select] = val;
|
|
if (reg_advance)
|
|
reg_select = (reg_select + 1) & 0x7F;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool NES_N106::Read (unsigned int adr, unsigned int & val, unsigned int id)
|
|
{
|
|
if (adr == 0x4800) // register read
|
|
{
|
|
val = reg[reg_select];
|
|
if (reg_advance)
|
|
reg_select = (reg_select + 1) & 0x7F;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// register decoding/encoding functions
|
|
//
|
|
|
|
inline unsigned int NES_N106::get_phase (int channel)
|
|
{
|
|
// 24-bit phase stored in channel regs 1/3/5
|
|
channel = channel << 3;
|
|
return (reg[0x41 + channel] )
|
|
+ (reg[0x43 + channel] << 8 )
|
|
+ (reg[0x45 + channel] << 16);
|
|
}
|
|
|
|
inline unsigned int NES_N106::get_freq (int channel)
|
|
{
|
|
// 19-bit frequency stored in channel regs 0/2/4
|
|
channel = channel << 3;
|
|
return ( reg[0x40 + channel] )
|
|
+ ( reg[0x42 + channel] << 8 )
|
|
+ ((reg[0x44 + channel] & 0x03) << 16);
|
|
}
|
|
|
|
inline unsigned int NES_N106::get_off (int channel)
|
|
{
|
|
// 8-bit offset stored in channel reg 6
|
|
channel = channel << 3;
|
|
return reg[0x46 + channel];
|
|
}
|
|
|
|
inline unsigned int NES_N106::get_len (int channel)
|
|
{
|
|
// 6-bit<<3 length stored obscurely in channel reg 4
|
|
channel = channel << 3;
|
|
return 256 - (reg[0x44 + channel] & 0xFC);
|
|
}
|
|
|
|
inline int NES_N106::get_vol (int channel)
|
|
{
|
|
// 4-bit volume stored in channel reg 7
|
|
channel = channel << 3;
|
|
return reg[0x47 + channel] & 0x0F;
|
|
}
|
|
|
|
inline int NES_N106::get_sample (unsigned int index)
|
|
{
|
|
// every sample becomes 2 samples in regs
|
|
return (index&1) ?
|
|
((reg[index>>1] >> 4) & 0x0F) :
|
|
( reg[index>>1] & 0x0F) ;
|
|
}
|
|
|
|
inline int NES_N106::get_channels ()
|
|
{
|
|
// 3-bit channel count stored in reg 0x7F
|
|
return ((reg[0x7F] >> 4) & 0x07) + 1;
|
|
}
|
|
|
|
inline void NES_N106::set_phase (unsigned int phase, int channel)
|
|
{
|
|
// 24-bit phase stored in channel regs 1/3/5
|
|
channel = channel << 3;
|
|
reg[0x41 + channel] = phase & 0xFF;
|
|
reg[0x43 + channel] = (phase >> 8 ) & 0xFF;
|
|
reg[0x45 + channel] = (phase >> 16) & 0xFF;
|
|
}
|
|
|
|
} //namespace
|