now move these files

This commit is contained in:
tildearrow 2022-05-01 22:22:02 -05:00
parent 867f96ff01
commit e873070d84
13 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,400 @@
//
// NES 2A03
//
#include <assert.h>
#include "nes_apu.h"
namespace xgm
{
void NES_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 NES_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;
}
}
}
}
INT32 NES_APU::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] < 0)
{
sphase[i] = (sphase[i] + 1) & 15;
scounter[i] += freq[i] + 1;
}
INT32 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 NES_APU::Read (UINT32 adr, UINT32 & val, UINT32 id)
{
if (0x4000 <= adr && adr < 0x4008)
{
val |= reg[adr&0x7];
return true;
}
else if(adr==0x4015)
{
val |= (length_counter[1]?2:0)|(length_counter[0]?1:0);
return true;
}
else
return false;
}
void NES_APU::Tick (UINT32 clocks)
{
out[0] = calc_sqr(0, clocks);
out[1] = calc_sqr(1, clocks);
}
// ツ青カツ青ャツつウツづェツづゥツ波ツ形ツづ個振ツ閉敖づ0-8191
UINT32 NES_APU::Render (INT32 b[2])
{
out[0] = (mask & 1) ? 0 : out[0];
out[1] = (mask & 2) ? 0 : out[1];
INT32 m[2];
if(option[OPT_NONLINEAR_MIXER])
{
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;
}
}
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;
}
NES_APU::NES_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]=(INT32)((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;
}
NES_APU::NES_APU ()
{
}
void NES_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 = 0x4000; i < 0x4008; i++)
Write (i, 0);
Write (0x4015, 0);
if (option[OPT_UNMUTE_ON_RESET])
Write (0x4015, 0x0f);
if (option[OPT_NEGATE_SWEEP_INIT])
{
Write (0x4001, 0x08);
Write (0x4005, 0x08);
}
for (i = 0; i < 2; i++)
out[i] = 0;
SetRate(rate);
}
void NES_APU::SetOption (int id, int val)
{
if(id<OPT_END) option[id] = val;
}
void NES_APU::SetClock (double c)
{
clock = c;
}
void NES_APU::SetRate (double r)
{
rate = r ? r : DEFAULT_RATE;
}
void NES_APU::SetStereoMix(int trk, xgm::INT16 mixl, xgm::INT16 mixr)
{
if (trk < 0) return;
if (trk > 1) return;
sm[0][trk] = mixl;
sm[1][trk] = mixr;
}
ITrackInfo *NES_APU::GetTrackInfo(int trk)
{
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].volume = volume[trk]+(envelope_disable[trk]?0:0x10)+(envelope_loop[trk]?0x20:0);
trkinfo[trk].key =
enable[trk] &&
length_counter[trk] > 0 &&
freq[trk] >= 8 &&
sfreq[trk] < 0x800 &&
(envelope_disable[trk] ? volume[trk] : (envelope_counter[trk] > 0));
trkinfo[trk].tone = duty[trk];
trkinfo[trk].max_volume = 15;
return &trkinfo[trk];
}
bool NES_APU::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 (0x4000 <= adr && adr < 0x4008)
{
//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 == 0x4015)
{
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-0x4000] = val;
return true;
}
// 4017 is handled in nes_dmc.cpp
//else if (adr == 0x4017)
//{
//}
return false;
}
} // namespace xgm;

View file

@ -0,0 +1,88 @@
#ifndef _NES_APU_H_
#define _NES_APU_H_
#include "../device.h"
#include "nes_dmc.h"
namespace xgm
{
/** Upper half of APU **/
class NES_APU : public ISoundChip
{
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, };
protected:
int option[OPT_END]; // 各種オプション
int mask;
INT32 sm[2][2];
UINT32 gclock;
UINT8 reg[0x20];
INT32 out[2];
double rate, clock;
INT32 square_table[32]; // nonlinear mixer
INT32 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
INT32 calc_sqr (int ch, UINT32 clocks);
TrackInfoBasic trkinfo[2];
public:
NES_APU ();
NES_APU ();
void FrameSequence(int s);
virtual void Reset ();
virtual void Tick (UINT32 clocks);
virtual UINT32 Render (INT32 b[2]);
virtual bool Read (UINT32 adr, UINT32 & val, UINT32 id=0);
virtual bool Write (UINT32 adr, UINT32 val, UINT32 id=0);
virtual void SetRate (double rate);
virtual void SetClock (double clock);
virtual void SetOption (int id, int b);
virtual void SetMask(int m){ mask = m; }
virtual void SetStereoMix (int trk, xgm::INT16 mixl, xgm::INT16 mixr);
virtual ITrackInfo *GetTrackInfo(int trk);
};
} // namespace
#endif

View file

@ -0,0 +1,771 @@
#include "nes_dmc.h"
#include "nes_apu.h"
#include <cstdlib>
namespace xgm
{
const UINT32 NES_DMC::wavlen_table[2][16] = {
{ // NTSC
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068
},
{ // PAL
4, 8, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778
}};
const UINT32 NES_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 UINT32 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,
};
NES_DMC::NES_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;
}
NES_DMC::NES_DMC ()
{
}
void NES_DMC::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_DMC::GetTrackInfo(int trk)
{
switch(trk)
{
case 0:
trkinfo[trk].max_volume = 255;
trkinfo[0].key = (linear_counter>0 && length_counter[0]>0 && enable[0]);
trkinfo[0].volume = 0;
trkinfo[0]._freq = tri_freq;
if(trkinfo[0]._freq)
trkinfo[0].freq = clock/32/(trkinfo[0]._freq + 1);
else
trkinfo[0].freq = 0;
trkinfo[0].tone = -1;
trkinfo[0].output = out[0];
break;
case 1:
trkinfo[1].max_volume = 15;
trkinfo[1].volume = noise_volume+(envelope_disable?0:0x10)+(envelope_loop?0x20:0);
trkinfo[1].key = length_counter[1]>0 && enable[1] &&
(envelope_disable ? (noise_volume>0) : (envelope_counter>0));
trkinfo[1]._freq = reg[0x400e - 0x4008]&0xF;
trkinfo[1].freq = clock/double(wavlen_table[pal][trkinfo[1]._freq] * ((noise_tap&(1<<6)) ? 93 : 1));
trkinfo[1].tone = noise_tap & (1<<6);
trkinfo[1].output = out[1];
break;
case 2:
trkinfo[2].max_volume = 127;
trkinfo[2].volume = reg[0x4011 - 0x4008]&0x7F;
trkinfo[2].key = dlength > 0;
trkinfo[2]._freq = reg[0x4010 - 0x4008]&0xF;
trkinfo[2].freq = clock/double(freq_table[pal][trkinfo[2]._freq]);
trkinfo[2].tone = (0xc000|(adr_reg<<6));
trkinfo[2].output = (damp<<1)|dac_lsb;
break;
default:
return NULL;
}
return &trkinfo[trk];
}
void NES_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;
cpu->UpdateIRQ(NES_CPU::IRQD_FRAME, frame_irq & frame_irq_enable);
}
// 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
UINT32 NES_DMC::calc_tri (UINT32 clocks)
{
static UINT32 tritbl[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,
};
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);
}
}
UINT32 ret = tritbl[tphase];
return ret;
}
// ノイズチャンネルの計算 戻り値は0-127
// 低サンプリングレートで合成するとエイリアスノイズが激しいので
// ノイズだけはこの関数内で高クロック合成し、簡易なサンプリングレート
// 変換を行っている。
UINT32 NES_DMC::calc_noise(UINT32 clocks)
{
UINT32 env = envelope_disable ? noise_volume : envelope_counter;
if (length_counter[1] < 1) env = 0;
UINT32 last = (noise & 0x4000) ? 0 : env;
if (clocks < 1) return last;
// simple anti-aliasing (noise requires it, even when oversampling is off)
UINT32 count = 0;
UINT32 accum = counter[1] * last; // samples pending from previous calc
UINT32 accum_clocks = counter[1];
#ifdef _DEBUG
INT32 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
UINT32 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
UINT32 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;
UINT32 NES_DMC::calc_dmc (UINT32 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->Read (daddress, data);
cpu->StealCycles(4); // DMC read takes 3 or 4 CPU cycles, usually 4
// (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;
cpu->UpdateIRQ(NES_CPU::IRQD_DMC, true);
}
}
}
else
{
data = 0x10000; // DMC will do nothing
empty = true;
}
}
}
return (damp<<1) + dac_lsb;
}
void NES_DMC::TickFrameSequence (UINT32 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 NES_DMC::Tick (UINT32 clocks)
{
out[0] = calc_tri(clocks);
out[1] = calc_noise(clocks);
out[2] = calc_dmc(clocks);
}
UINT32 NES_DMC::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];
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])
{
INT32 ref = m[0] + m[1] + m[2];
INT32 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 INT32 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 NES_DMC::SetClock (double c)
{
clock = c;
}
void NES_DMC::SetRate (double r)
{
rate = (UINT32)(r?r:DEFAULT_RATE);
}
void NES_DMC::SetPal (bool is_pal)
{
pal = (is_pal ? 1 : 0);
// set CPU cycles in frame_sequence
frame_sequence_length = is_pal ? 8314 : 7458;
}
void NES_DMC::SetAPU (NES_APU* apu_)
{
apu = apu_;
}
// Initializing TRI, NOISE, DPCM mixing table
void NES_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] = (UINT32)(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] = (UINT32)((MASTER*159.79)/(100.0+1.0/((double)t/wt+(double)n/wn+(double)d/wd)));
}
}
}
}
}
void NES_DMC::Reset ()
{
int i;
mask = 0;
InitializeTNDTable(8227,12241,22638);
counter[0] = 0;
counter[1] = 0;
counter[2] = 0;
tphase = 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;
cpu->UpdateIRQ(NES_CPU::IRQD_FRAME, false);
for (i = 0; i < 0x0F; i++)
Write (0x4008 + i, 0);
Write (0x4017, 0x40);
irq = false;
Write (0x4015, 0x00);
if (option[OPT_UNMUTE_ON_RESET])
Write (0x4015, 0x0f);
cpu->UpdateIRQ(NES_CPU::IRQD_DMC, false);
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);
if (option[OPT_RANDOMIZE_NOISE])
{
noise |= ::rand();
counter[1] = -(rand() & 511);
}
if (option[OPT_RANDOMIZE_TRI])
{
tphase = ::rand() & 31;
counter[0] = -(rand() & 2047);
}
SetRate(rate);
}
void NES_DMC::SetMemory (IDevice * r)
{
memory = r;
}
void NES_DMC::SetOption (int id, int val)
{
if(id<OPT_END)
{
option[id] = val;
if(id==OPT_NONLINEAR_MIXER)
InitializeTNDTable(8227,12241,22638);
}
}
bool NES_DMC::Write (UINT32 adr, UINT32 val, UINT32 id)
{
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 (adr == 0x4015)
{
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;
cpu->UpdateIRQ(NES_CPU::IRQD_DMC, false);
reg[adr-0x4008] = val;
return true;
}
if (adr == 0x4017)
{
//DEBUG_OUT("4017 = %02X¥n", val);
frame_irq_enable = ((val & 0x40) != 0x40);
if (frame_irq_enable) frame_irq = false;
cpu->UpdateIRQ(NES_CPU::IRQD_FRAME, 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<0x4008||0x4013<adr)
return false;
reg[adr-0x4008] = val&0xff;
//DEBUG_OUT("$%04X %02X¥n", adr, val);
switch (adr)
{
// tri
case 0x4008:
linear_counter_control = (val >> 7) & 1;
linear_counter_reload = val & 0x7F;
break;
case 0x4009:
break;
case 0x400a:
tri_freq = val | (tri_freq & 0x700) ;
break;
case 0x400b:
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 0x400c:
noise_volume = val & 15;
envelope_div_period = val & 15;
envelope_disable = (val >> 4) & 1;
envelope_loop = (val >> 5) & 1;
break;
case 0x400d:
break;
case 0x400e:
if (option[OPT_ENABLE_PNOISE])
noise_tap = (val & 0x80) ? (1<<6) : (1<<1);
else
noise_tap = (1<<1);
nfreq = wavlen_table[pal][val&15];
break;
case 0x400f:
if (enable[1])
{
length_counter[1] = length_table[(val >> 3) & 0x1f];
}
envelope_write = true;
break;
// dmc
case 0x4010:
mode = (val >> 6) & 3;
if (!(mode & 2))
{
irq = false;
cpu->UpdateIRQ(NES_CPU::IRQD_DMC, false);
}
dfreq = freq_table[pal][val&15];
break;
case 0x4011:
if (option[OPT_ENABLE_4011])
{
damp = (val >> 1) & 0x3f;
dac_lsb = val & 1;
dmc_pop = true;
}
break;
case 0x4012:
adr_reg = val&0xff;
// ここでdaddressは更新されない
break;
case 0x4013:
len_reg = val&0xff;
// ここでlengthは更新されない
break;
default:
return false;
}
return true;
}
bool NES_DMC::Read (UINT32 adr, UINT32 & val, UINT32 id)
{
if (adr == 0x4015)
{
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;
cpu->UpdateIRQ(NES_CPU::IRQD_FRAME, false);
return true;
}
else if (0x4008<=adr&&adr<=0x4014)
{
val |= reg[adr-0x4008];
return true;
}
else
return false;
}
// IRQ support requires CPU read access
void NES_DMC::SetCPU(NES_CPU* cpu_)
{
cpu = cpu_;
}
} // namespace

View file

@ -0,0 +1,129 @@
#ifndef _NES_DMC_H_
#define _NES_DMC_H_
#include "../device.h"
#include "../Audio/MedianFilter.h"
#include "../CPU/nes_cpu.h"
namespace xgm
{
class NES_APU; // forward declaration
/** Bottom Half of APU **/
class NES_DMC:public ISoundChip
{
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;
static const UINT32 freq_table[2][16];
static const UINT32 wavlen_table[2][16];
UINT32 tnd_table[2][16][16][128];
int option[OPT_END];
int mask;
INT32 sm[2][3];
UINT8 reg[0x10];
UINT32 len_reg;
UINT32 adr_reg;
IDevice *memory;
UINT32 out[3];
UINT32 daddress;
UINT32 dlength;
UINT32 data;
bool empty;
INT16 damp;
int dac_lsb;
bool dmc_pop;
INT32 dmc_pop_offset;
INT32 dmc_pop_follow;
double clock;
UINT32 rate;
int pal;
int mode;
bool irq;
INT32 counter[3]; // frequency dividers
int tphase; // triangle phase
UINT32 nfreq; // noise frequency
UINT32 dfreq; // DPCM frequency
UINT32 tri_freq;
int linear_counter;
int linear_counter_reload;
bool linear_counter_halt;
bool linear_counter_control;
int noise_volume;
UINT32 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
TrackInfoBasic trkinfo[3];
// frame sequencer
NES_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;
NES_CPU* cpu; // IRQ needs CPU access
inline UINT32 calc_tri (UINT32 clocks);
inline UINT32 calc_dmc (UINT32 clocks);
inline UINT32 calc_noise (UINT32 clocks);
public:
NES_DMC ();
NES_DMC ();
void InitializeTNDTable(double wt, double wn, double wd);
void SetPal (bool is_pal);
void SetAPU (NES_APU* apu_);
void SetMemory (IDevice * r);
void FrameSequence(int s);
int GetDamp(){ return (damp<<1)|dac_lsb ; }
void TickFrameSequence (UINT32 clocks);
virtual void Reset ();
virtual void Tick (UINT32 clocks);
virtual UINT32 Render (INT32 b[2]);
virtual bool Write (UINT32 adr, UINT32 val, UINT32 id=0);
virtual bool Read (UINT32 adr, UINT32 & val, UINT32 id=0);
virtual void SetRate (double rate);
virtual void SetClock (double rate);
virtual void SetOption (int, int);
virtual void SetMask(int m){ mask = m; }
virtual void SetStereoMix (int trk, xgm::INT16 mixl, xgm::INT16 mixr);
virtual ITrackInfo *GetTrackInfo(int trk);
void SetCPU(NES_CPU* cpu_);
};
}
#endif

View file

@ -0,0 +1,397 @@
#include <cstring>
#include "nes_fds.h"
namespace xgm {
const int RC_BITS = 12;
NES_FDS::NES_FDS ()
{
option[OPT_CUTOFF] = 2000;
option[OPT_4085_RESET] = 0;
option[OPT_WRITE_PROTECT] = 0; // not used here, see nsfplay.cpp
rc_k = 0;
rc_l = (1<<RC_BITS);
SetClock (DEFAULT_CLOCK);
SetRate (DEFAULT_RATE);
sm[0] = 128;
sm[1] = 128;
Reset();
}
NES_FDS::NES_FDS ()
{
}
void NES_FDS::SetStereoMix(int trk, INT16 mixl, INT16 mixr)
{
if (trk < 0) return;
if (trk > 1) return;
sm[0] = mixl;
sm[1] = mixr;
}
ITrackInfo *NES_FDS::GetTrackInfo(int trk)
{
trkinfo.max_volume = 32;
trkinfo.volume = last_vol;
trkinfo.key = last_vol > 0;
trkinfo._freq = last_freq;
trkinfo.freq = (double(last_freq) * clock) / (65536.0 * 64.0);
trkinfo.tone = env_out[EMOD];
for(int i=0;i<64;i++)
trkinfo.wave[i] = wave[TWAV][i];
return &trkinfo;
}
void NES_FDS::SetClock (double c)
{
clock = c;
}
void NES_FDS::SetRate (double r)
{
rate = r;
// configure lowpass filter
double cutoff = double(option[OPT_CUTOFF]);
double leak = 0.0;
if (cutoff > 0)
leak = ::exp(-2.0 * 3.14159 * cutoff / rate);
rc_k = INT32(leak * double(1<<RC_BITS));
rc_l = (1<<RC_BITS) - rc_k;
}
void NES_FDS::SetOption (int id, int val)
{
if(id<OPT_END) option[id] = val;
// update cutoff immediately
if (id == OPT_CUTOFF) SetRate(rate);
}
void NES_FDS::Reset ()
{
master_io = true;
master_vol = 0;
last_freq = 0;
last_vol = 0;
rc_accum = 0;
for (int i=0; i<2; ++i)
{
::memset(wave[i], 0, sizeof(wave[i]));
freq[i] = 0;
phase[i] = 0;
}
wav_write = false;
wav_halt = true;
env_halt = true;
mod_halt = true;
mod_pos = 0;
mod_write_pos = 0;
for (int i=0; i<2; ++i)
{
env_mode[i] = false;
env_disable[i] = true;
env_timer[i] = 0;
env_speed[i] = 0;
env_out[i] = 0;
}
master_env_speed = 0xFF;
// NOTE: the FDS BIOS reset only does the following related to audio:
// $4023 = $00
// $4023 = $83 enables master_io
// $4080 = $80 output volume = 0, envelope disabled
// $408A = $E8 master envelope speed
Write(0x4023, 0x00);
Write(0x4023, 0x83);
Write(0x4080, 0x80);
Write(0x408A, 0xE8);
// reset other stuff
Write(0x4082, 0x00); // wav freq 0
Write(0x4083, 0x80); // wav disable
Write(0x4084, 0x80); // mod strength 0
Write(0x4085, 0x00); // mod position 0
Write(0x4086, 0x00); // mod freq 0
Write(0x4087, 0x80); // mod disable
Write(0x4089, 0x00); // wav write disable, max global volume}
}
void NES_FDS::Tick (UINT32 clocks)
{
// clock envelopes
if (!env_halt && !wav_halt && (master_env_speed != 0))
{
for (int i=0; i<2; ++i)
{
if (!env_disable[i])
{
env_timer[i] += clocks;
UINT32 period = ((env_speed[i]+1) * master_env_speed) << 3;
while (env_timer[i] >= period)
{
// clock the envelope
if (env_mode[i])
{
if (env_out[i] < 32) ++env_out[i];
}
else
{
if (env_out[i] > 0 ) --env_out[i];
}
env_timer[i] -= period;
}
}
}
}
// clock the mod table
if (!mod_halt)
{
// advance phase, adjust for modulator
UINT32 start_pos = phase[TMOD] >> 16;
phase[TMOD] += (clocks * freq[TMOD]);
UINT32 end_pos = phase[TMOD] >> 16;
// wrap the phase to the 64-step table (+ 16 bit accumulator)
phase[TMOD] = phase[TMOD] & 0x3FFFFF;
// execute all clocked steps
for (UINT32 p = start_pos; p < end_pos; ++p)
{
INT32 wv = wave[TMOD][p & 0x3F];
if (wv == 4) // 4 resets mod position
mod_pos = 0;
else
{
const INT32 BIAS[8] = { 0, 1, 2, 4, 0, -4, -2, -1 };
mod_pos += BIAS[wv];
mod_pos &= 0x7F; // 7-bit clamp
}
}
}
// clock the wav table
if (!wav_halt)
{
// complex mod calculation
INT32 mod = 0;
if (env_out[EMOD] != 0) // skip if modulator off
{
// convert mod_pos to 7-bit signed
INT32 pos = (mod_pos < 64) ? mod_pos : (mod_pos-128);
// multiply pos by gain,
// shift off 4 bits but with odd "rounding" behaviour
INT32 temp = pos * env_out[EMOD];
INT32 rem = temp & 0x0F;
temp >>= 4;
if ((rem > 0) && ((temp & 0x80) == 0))
{
if (pos < 0) temp -= 1;
else temp += 2;
}
// wrap if range is exceeded
while (temp >= 192) temp -= 256;
while (temp < -64) temp += 256;
// multiply result by pitch,
// shift off 6 bits, round to nearest
temp = freq[TWAV] * temp;
rem = temp & 0x3F;
temp >>= 6;
if (rem >= 32) temp += 1;
mod = temp;
}
// advance wavetable position
INT32 f = freq[TWAV] + mod;
phase[TWAV] = phase[TWAV] + (clocks * f);
phase[TWAV] = phase[TWAV] & 0x3FFFFF; // wrap
// store for trackinfo
last_freq = f;
}
// output volume caps at 32
INT32 vol_out = env_out[EVOL];
if (vol_out > 32) vol_out = 32;
// final output
if (!wav_write)
fout = wave[TWAV][(phase[TWAV]>>16)&0x3F] * vol_out;
// NOTE: during wav_halt, the unit still outputs (at phase 0)
// and volume can affect it if the first sample is nonzero.
// haven't worked out 100% of the conditions for volume to
// effect (vol envelope does not seem to run, but am unsure)
// but this implementation is very close to correct
// store for trackinfo
last_vol = vol_out;
}
UINT32 NES_FDS::Render (INT32 b[2])
{
// 8 bit approximation of master volume
const double MASTER_VOL = 2.4 * 1223.0; // max FDS vol vs max APU square (arbitrarily 1223)
const double MAX_OUT = 32.0f * 63.0f; // value that should map to master vol
const INT32 MASTER[4] = {
int((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 2.0f),
int((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 3.0f),
int((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 4.0f),
int((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 5.0f) };
INT32 v = fout * MASTER[master_vol] >> 8;
// lowpass RC filter
INT32 rc_out = ((rc_accum * rc_k) + (v * rc_l)) >> RC_BITS;
rc_accum = rc_out;
v = rc_out;
// output mix
INT32 m = mask ? 0 : v;
b[0] = (m * sm[0]) >> 7;
b[1] = (m * sm[1]) >> 7;
return 2;
}
bool NES_FDS::Write (UINT32 adr, UINT32 val, UINT32 id)
{
// $4023 master I/O enable/disable
if (adr == 0x4023)
{
master_io = ((val & 2) != 0);
return true;
}
if (!master_io)
return false;
if (adr < 0x4040 || adr > 0x408A)
return false;
if (adr < 0x4080) // $4040-407F wave table write
{
if (wav_write)
wave[TWAV][adr - 0x4040] = val & 0x3F;
return true;
}
switch (adr & 0x00FF)
{
case 0x80: // $4080 volume envelope
env_disable[EVOL] = ((val & 0x80) != 0);
env_mode[EVOL] = ((val & 0x40) != 0);
env_timer[EVOL] = 0;
env_speed[EVOL] = val & 0x3F;
if (env_disable[EVOL])
env_out[EVOL] = env_speed[EVOL];
return true;
case 0x81: // $4081 ---
return false;
case 0x82: // $4082 wave frequency low
freq[TWAV] = (freq[TWAV] & 0xF00) | val;
return true;
case 0x83: // $4083 wave frequency high / enables
freq[TWAV] = (freq[TWAV] & 0x0FF) | ((val & 0x0F) << 8);
wav_halt = ((val & 0x80) != 0);
env_halt = ((val & 0x40) != 0);
if (wav_halt)
phase[TWAV] = 0;
if (env_halt)
{
env_timer[EMOD] = 0;
env_timer[EVOL] = 0;
}
return true;
case 0x84: // $4084 mod envelope
env_disable[EMOD] = ((val & 0x80) != 0);
env_mode[EMOD] = ((val & 0x40) != 0);
env_timer[EMOD] = 0;
env_speed[EMOD] = val & 0x3F;
if (env_disable[EMOD])
env_out[EMOD] = env_speed[EMOD];
return true;
case 0x85: // $4085 mod position
mod_pos = val & 0x7F;
// not hardware accurate., but prevents detune due to cycle inaccuracies
// (notably in Bio Miracle Bokutte Upa)
if (option[OPT_4085_RESET])
phase[TMOD] = mod_write_pos << 16;
return true;
case 0x86: // $4086 mod frequency low
freq[TMOD] = (freq[TMOD] & 0xF00) | val;
return true;
case 0x87: // $4087 mod frequency high / enable
freq[TMOD] = (freq[TMOD] & 0x0FF) | ((val & 0x0F) << 8);
mod_halt = ((val & 0x80) != 0);
if (mod_halt)
phase[TMOD] = phase[TMOD] & 0x3F0000; // reset accumulator phase
return true;
case 0x88: // $4088 mod table write
if (mod_halt)
{
// writes to current playback position (there is no direct way to set phase)
wave[TMOD][(phase[TMOD] >> 16) & 0x3F] = val & 0x07;
phase[TMOD] = (phase[TMOD] + 0x010000) & 0x3FFFFF;
wave[TMOD][(phase[TMOD] >> 16) & 0x3F] = val & 0x07;
phase[TMOD] = (phase[TMOD] + 0x010000) & 0x3FFFFF;
mod_write_pos = phase[TMOD] >> 16; // used by OPT_4085_RESET
}
return true;
case 0x89: // $4089 wave write enable, master volume
wav_write = ((val & 0x80) != 0);
master_vol = val & 0x03;
return true;
case 0x8A: // $408A envelope speed
master_env_speed = val;
// haven't tested whether this register resets phase on hardware,
// but this ensures my inplementation won't spam envelope clocks
// if this value suddenly goes low.
env_timer[EMOD] = 0;
env_timer[EVOL] = 0;
return true;
default:
return false;
}
return false;
}
bool NES_FDS::Read (UINT32 adr, UINT32 & val, UINT32 id)
{
if (adr >= 0x4040 && adr <= 0x407F)
{
// TODO: if wav_write is not enabled, the
// read address may not be reliable? need
// to test this on hardware.
val = wave[TWAV][adr - 0x4040];
return true;
}
if (adr == 0x4090) // $4090 read volume envelope
{
val = env_out[EVOL] | 0x40;
return true;
}
if (adr == 0x4092) // $4092 read mod envelope
{
val = env_out[EMOD] | 0x40;
return true;
}
return false;
}
} // namespace

View file

@ -0,0 +1,83 @@
#ifndef _NES_FDS_H_
#define _NES_FDS_H_
#include "../device.h"
namespace xgm {
class TrackInfoFDS : public TrackInfoBasic
{
public:
INT16 wave[64];
virtual IDeviceInfo *Clone(){ return new TrackInfoFDS(*this); }
};
class NES_FDS : public ISoundChip
{
public:
enum
{
OPT_CUTOFF=0,
OPT_4085_RESET,
OPT_WRITE_PROTECT,
OPT_END
};
protected:
double rate, clock;
int mask;
INT32 sm[2]; // stereo mix
INT32 fout; // current output
TrackInfoFDS trkinfo;
int option[OPT_END];
bool master_io;
UINT32 master_vol;
UINT32 last_freq; // for trackinfo
UINT32 last_vol; // for trackinfo
// two wavetables
enum { TMOD=0, TWAV=1 };
INT32 wave[2][64];
UINT32 freq[2];
UINT32 phase[2];
bool wav_write;
bool wav_halt;
bool env_halt;
bool mod_halt;
UINT32 mod_pos;
UINT32 mod_write_pos;
// two ramp envelopes
enum { EMOD=0, EVOL=1 };
bool env_mode[2];
bool env_disable[2];
UINT32 env_timer[2];
UINT32 env_speed[2];
UINT32 env_out[2];
UINT32 master_env_speed;
// 1-pole RC lowpass filter
INT32 rc_accum;
INT32 rc_k;
INT32 rc_l;
public:
NES_FDS ();
virtual NES_FDS ();
virtual void Reset ();
virtual void Tick (UINT32 clocks);
virtual UINT32 Render (INT32 b[2]);
virtual bool Write (UINT32 adr, UINT32 val, UINT32 id=0);
virtual bool Read (UINT32 adr, UINT32 & val, UINT32 id=0);
virtual void SetRate (double);
virtual void SetClock (double);
virtual void SetOption (int, int);
virtual void SetMask(int m){ mask = m&1; }
virtual void SetStereoMix (int trk, INT16 mixl, INT16 mixr);
virtual ITrackInfo *GetTrackInfo(int trk);
};
} // namespace xgm
#endif

View file

@ -0,0 +1,422 @@
#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;
}
NES_MMC5::NES_MMC5 ()
{
}
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

View file

@ -0,0 +1,74 @@
#ifndef _NES_MMC5_H_
#define _NES_MMC5_H_
#include "../device.h"
#include "../CPU/nes_cpu.h"
namespace xgm
{
class NES_MMC5:public ISoundChip
{
public:
enum
{ OPT_NONLINEAR_MIXER=0, OPT_PHASE_REFRESH, OPT_END };
protected:
int option[OPT_END];
int mask;
INT32 sm[2][3]; // stereo panning
UINT8 ram[0x6000 - 0x5c00];
UINT8 reg[8];
UINT8 mreg[2];
UINT8 pcm; // PCM channel
bool pcm_mode; // PCM channel
NES_CPU* cpu; // PCM channel reads need CPU access
UINT32 scounter[2]; // frequency divider
UINT32 sphase[2]; // phase counter
UINT32 duty[2];
UINT32 volume[2];
UINT32 freq[2];
INT32 out[3];
bool enable[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];
int frame_sequence_count;
double clock, rate;
INT32 calc_sqr (int i, UINT32 clocks);
INT32 square_table[32];
INT32 pcm_table[256];
TrackInfoBasic trkinfo[3];
public:
NES_MMC5 ();
NES_MMC5 ();
void FrameSequence ();
void TickFrameSequence (UINT32 clocks);
virtual void Reset ();
virtual void Tick (UINT32 clocks);
virtual UINT32 Render (INT32 b[2]);
virtual bool Write (UINT32 adr, UINT32 val, UINT32 id=0);
virtual bool Read (UINT32 adr, UINT32 & val, UINT32 id=0);
virtual void SetOption (int id, int b);
virtual void SetClock (double);
virtual void SetRate (double);
virtual void SetMask (int m){ mask = m; }
virtual void SetStereoMix (int trk, xgm::INT16 mixl, xgm::INT16 mixr);
virtual ITrackInfo *GetTrackInfo(int trk);
void SetCPU(NES_CPU* cpu_);
};
}
#endif

View file

@ -0,0 +1,367 @@
#include <cstring>
#include "nes_n106.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, INT16 mixl, INT16 mixr)
{
if (trk < 0 || trk >= 8) return;
trk = 7-trk; // displayed channels are inverted
sm[0][trk] = mixl;
sm[1][trk] = mixr;
}
ITrackInfo *NES_N106::GetTrackInfo (int trk)
{
int channels = get_channels();
int channel = 7-trk; // invert the track display
TrackInfoN106* t = &trkinfo[channel];
if (trk >= channels)
{
t->max_volume = 15;
t->volume = 0;
t->_freq = 0;
t->wavelen = 0;
t->tone = -1;
t->output = 0;
t->key = false;
t->freq = 0;
}
else
{
t->max_volume = 15;
t->volume = get_vol(channel);
t->_freq = get_freq(channel);
t->wavelen = get_len(channel);
t->tone = get_off(channel);
t->output = fout[channel];
t->key = (t->volume > 0) && (t->_freq > 0);
t->freq = (double(t->_freq) * clock) / double(15 * 65536 * channels * t->wavelen);
for (int i=0; i < t->wavelen; ++i)
t->wave[i] = get_sample((i+t->tone)&0xFF);
}
return t;
}
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 (UINT32 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;
UINT32 phase = get_phase(channel);
UINT32 freq = get_freq(channel);
UINT32 len = get_len(channel);
UINT32 off = get_off(channel);
INT32 vol = get_vol(channel);
// accumulate 24-bit phase
phase = (phase + freq) & 0x00FFFFFF;
// wrap phase if wavelength exceeded
UINT32 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)
INT32 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;
}
}
UINT32 NES_N106::Render (INT32 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
INT32 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 INT32 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 (UINT32 adr, UINT32 val, UINT32 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 (UINT32 adr, UINT32 & val, UINT32 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 UINT32 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 UINT32 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 UINT32 NES_N106::get_off (int channel)
{
// 8-bit offset stored in channel reg 6
channel = channel << 3;
return reg[0x46 + channel];
}
inline UINT32 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 INT32 NES_N106::get_vol (int channel)
{
// 4-bit volume stored in channel reg 7
channel = channel << 3;
return reg[0x47 + channel] & 0x0F;
}
inline INT32 NES_N106::get_sample (UINT32 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 (UINT32 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

View file

@ -0,0 +1,74 @@
#ifndef _NES_N106_H_
#define _NES_N106_H_
#include "../device.h"
namespace xgm {
class TrackInfoN106 : public TrackInfoBasic
{
public:
int wavelen;
INT16 wave[256];
virtual IDeviceInfo *Clone(){ return new TrackInfoN106(*this); }
};
class NES_N106:public ISoundChip
{
public:
enum
{
OPT_SERIAL = 0,
OPT_PHASE_READ_ONLY = 1,
OPT_LIMIT_WAVELENGTH = 2,
OPT_END
};
protected:
double rate, clock;
int mask;
INT32 sm[2][8]; // stereo mix
INT32 fout[8]; // current output
TrackInfoN106 trkinfo[8];
int option[OPT_END];
bool master_disable;
UINT32 reg[0x80]; // all state is contained here
unsigned int reg_select;
bool reg_advance;
int tick_channel;
int tick_clock;
int render_channel;
int render_clock;
int render_subclock;
// convenience functions to interact with regs
inline UINT32 get_phase (int channel);
inline UINT32 get_freq (int channel);
inline UINT32 get_off (int channel);
inline UINT32 get_len (int channel);
inline INT32 get_vol (int channel);
inline INT32 get_sample (UINT32 index);
inline int get_channels ();
// for storing back the phase after modifying
inline void set_phase (UINT32 phase, int channel);
public:
NES_N106 ();
NES_N106 ();
virtual void Reset ();
virtual void Tick (UINT32 clocks);
virtual UINT32 Render (INT32 b[2]);
virtual bool Write (UINT32 adr, UINT32 val, UINT32 id=0);
virtual bool Read (UINT32 adr, UINT32 & val, UINT32 id=0);
virtual void SetRate (double);
virtual void SetClock (double);
virtual void SetOption (int, int);
virtual void SetMask (int m);
virtual void SetStereoMix (int trk, INT16 mixl, INT16 mixr);
virtual ITrackInfo *GetTrackInfo(int trk);
};
} // namespace xgm
#endif

View file

@ -0,0 +1,264 @@
#include "nes_vrc6.h"
namespace xgm
{
NES_VRC6::NES_VRC6 ()
{
SetClock (DEFAULT_CLOCK);
SetRate (DEFAULT_RATE);
halt = false;
freq_shift = 0;
for(int c=0;c<2;++c)
for(int t=0;t<3;++t)
sm[c][t] = 128;
}
NES_VRC6::NES_VRC6 ()
{
}
void NES_VRC6::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_VRC6::GetTrackInfo(int trk)
{
if(trk<2)
{
trkinfo[trk].max_volume = 15;
trkinfo[trk].volume = volume[trk];
trkinfo[trk]._freq = freq2[trk];
trkinfo[trk].freq = freq2[trk]?clock/16/(freq2[trk]+1):0;
trkinfo[trk].tone = duty[trk];
trkinfo[trk].key = (volume[trk]>0)&&enable[trk]&&!gate[trk];
return &trkinfo[trk];
}
else if(trk==2)
{
trkinfo[2].max_volume = 255;
trkinfo[2].volume = volume[2];
trkinfo[2]._freq = freq2[2];
trkinfo[2].freq = freq2[2]?clock/14/(freq2[2]+1):0;
trkinfo[2].tone = -1;
trkinfo[2].key = (enable[2]>0);
return &trkinfo[2];
}
else
return NULL;
}
void NES_VRC6::SetClock (double c)
{
clock = c;
}
void NES_VRC6::SetRate (double r)
{
rate = r ? r : DEFAULT_RATE;
}
void NES_VRC6::SetOption (int id, int val)
{
if(id<OPT_END)
{
//option[id] = val;
}
}
void NES_VRC6::Reset ()
{
Write (0x9003, 0);
for (int i = 0; i < 3; i++)
{
Write (0x9000 + i, 0);
Write (0xa000 + i, 0);
Write (0xb000 + i, 0);
}
count14 = 0;
mask = 0;
counter[0] = 0;
counter[1] = 0;
counter[2] = 0;
phase[0] = 0;
phase[0] = 1;
phase[0] = 2;
}
INT16 NES_VRC6::calc_sqr (int i, UINT32 clocks)
{
static const INT16 sqrtbl[8][16] = {
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1},
{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, 1, 1, 1, 1, 1},
{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, 1, 1, 1, 1, 1, 1, 1},
{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}
};
if (!enable[i])
return 0;
if (!halt)
{
counter[i] += clocks;
while(counter[i] > freq2[i])
{
phase[i] = (phase[i] + 1) & 15;
counter[i] -= (freq2[i] + 1);
}
}
return (gate[i]
|| sqrtbl[duty[i]][phase[i]])? volume[i] : 0;
}
INT16 NES_VRC6::calc_saw (UINT32 clocks)
{
if (!enable[2])
return 0;
if (!halt)
{
counter[2] += clocks;
while(counter[2] > freq2[2])
{
counter[2] -= (freq2[2] + 1);
// accumulate saw
++count14;
if (count14 >= 14)
{
count14 = 0;
phase[2] = 0;
}
else if (0 == (count14 & 1)) // only accumulate on even ticks
{
phase[2] = (phase[2] + volume[2]) & 0xFF; // note 8-bit wrapping behaviour
}
}
}
// only top 5 bits of saw are output
return phase[2] >> 3;
}
void NES_VRC6::Tick (UINT32 clocks)
{
out[0] = calc_sqr(0,clocks);
out[1] = calc_sqr(1,clocks);
out[2] = calc_saw(clocks);
}
UINT32 NES_VRC6::Render (INT32 b[2])
{
INT32 m[3];
m[0] = out[0];
m[1] = out[1];
m[2] = out[2];
// note: signal is inverted compared to 2A03
m[0] = (mask & 1) ? 0 : -m[0];
m[1] = (mask & 2) ? 0 : -m[1];
m[2] = (mask & 4) ? 0 : -m[2];
b[0] = m[0] * sm[0][0];
b[0] += m[1] * sm[0][1];
b[0] += m[2] * sm[0][2];
//b[0] >>= (7 - 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 - 7);
// master volume adjustment
const INT32 MASTER = INT32(256.0 * 1223.0 / 1920.0);
b[0] = (b[0] * MASTER) >> 8;
b[1] = (b[1] * MASTER) >> 8;
return 2;
}
bool NES_VRC6::Write (UINT32 adr, UINT32 val, UINT32 id)
{
int ch, cmap[4] = { 0, 0, 1, 2 };
switch (adr)
{
case 0x9000:
case 0xa000:
ch = cmap[(adr >> 12) & 3];
volume[ch] = val & 15;
duty[ch] = (val >> 4) & 7;
gate[ch] = (val >> 7) & 1;
break;
case 0xb000:
volume[2] = val & 63;
break;
case 0x9001:
case 0xa001:
case 0xb001:
ch = cmap[(adr >> 12) & 3];
freq[ch] = (freq[ch] & 0xf00) | val;
freq2[ch] = (freq[ch] >> freq_shift);
if (counter[ch] > freq2[ch]) counter[ch] = freq2[ch];
break;
case 0x9002:
case 0xa002:
case 0xb002:
ch = cmap[(adr >> 12) & 3];
freq[ch] = ((val & 0xf) << 8) + (freq[ch] & 0xff);
freq2[ch] = (freq[ch] >> freq_shift);
if (counter[ch] > freq2[ch]) counter[ch] = freq2[ch];
if (!enable[ch]) // if enable is being turned on, phase should be reset
{
if (ch == 2)
{
count14 = 0; // reset saw
}
phase[ch] = 0;
}
enable[ch] = (val >> 7) & 1;
break;
case 0x9003:
halt = val & 1;
freq_shift =
(val & 4) ? 8 :
(val & 2) ? 4 :
0;
freq2[0] = (freq[0] >> freq_shift);
freq2[1] = (freq[1] >> freq_shift);
freq2[2] = (freq[2] >> freq_shift);
if (counter[0] > freq2[0]) counter[0] = freq2[0];
if (counter[1] > freq2[1]) counter[1] = freq2[1];
if (counter[2] > freq2[2]) counter[2] = freq2[2];
break;
default:
return false;
}
return true;
}
bool NES_VRC6::Read (UINT32 adr, UINT32 & val, UINT32 id)
{
return false;
}
} // namespace

View file

@ -0,0 +1,56 @@
#ifndef _NES_VRC6_H_
#define _NES_VRC6_H_
#include "../device.h"
namespace xgm
{
class NES_VRC6:public ISoundChip
{
public:
enum
{
OPT_END
};
protected:
UINT32 counter[3]; // frequency divider
UINT32 phase[3]; // phase counter
UINT32 freq2[3]; // adjusted frequency
int count14; // saw 14-stage counter
//int option[OPT_END];
int mask;
INT32 sm[2][3]; // stereo mix
int duty[2];
int volume[3];
int enable[3];
int gate[3];
UINT32 freq[3];
INT16 calc_sqr (int i, UINT32 clocks);
INT16 calc_saw (UINT32 clocks);
bool halt;
int freq_shift;
double clock, rate;
INT32 out[3];
TrackInfoBasic trkinfo[3];
public:
NES_VRC6 ();
NES_VRC6 ();
virtual void Reset ();
virtual void Tick (UINT32 clocks);
virtual UINT32 Render (INT32 b[2]);
virtual bool Read (UINT32 adr, UINT32 & val, UINT32 id=0);
virtual bool Write (UINT32 adr, UINT32 val, UINT32 id=0);
virtual void SetClock (double);
virtual void SetRate (double);
virtual void SetOption (int, int);
virtual void SetMask (int m){ mask = m; }
virtual void SetStereoMix (int trk, xgm::INT16 mixl, xgm::INT16 mixr);
virtual ITrackInfo *GetTrackInfo(int trk);
};
} // namespace
#endif

View file

@ -0,0 +1,60 @@
MODIFIED
this is a modified version of the NES audio emulation core.
it converts the files to UTF-8 and Unix line endings.
XGM SOURCE ARCHIVE
This source archive is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY. You can reuse these source code freely. However,
we possibly change the structure and interface of the program code without
any advance notice.
HOW TO COMPILE
Open the workspace file top.sln with Visual C++ 7.0 or later
version. We can compile both KbMediaPlayer and Winamp version of
NSFplug on this workspace.
To make a KbMediaPlayer version of NSFplug, that is, in_nsf.kpi,
Please choose 'kbnsf' project as an active project. Then, you can
build in_nsf.kpi by build menu.
On the other hand, to make a Winamp version of NSFplug, activate
'wa2nsf' project and build it.
Note that after the build process, VC++ copies the plugin files to:
C:\Program Files\KbMediaPlayer\Plugins\OK\in_nsf\in_nsf.kpi
C:\Program Files\Windamp\Plugins\in_nsf.dll
If you don't need to have these copies, please remove or modify the
custom build settings.
ACKNOWLEDGEMENT
I thank Mamiya and Kobarin and Nullsoft for their great source code.
I thank Norix and Izumi for the fruitful discussions and the NSFplug
users for their comments and bug reports.
COPYRIGHTS
NSFplug is built on KM6502, KbMediaPlayer plugin SDK and Winamp2
plugin SDK.
NSFplug uses KM6502 in emulating a 6502 cpu. KM6502 code is stored in
devices\CPU\km6502 folder of this source archive. KM6502 is a public
domain software. See the document of KM6502 stored in the folder.
KbMediaPlayer Plugin SDK is provided by Kobarin. The SDK code is also
packed in the kbmedia\sdk folder of this archive. The copyright of
the source code remains with Kobarin.
The files in winamp/sdk folder of this archive are the header files
from Winamp2 Plugin SDK provided by Nullsoft. The copyright of these
header files remains with Nullsoft.
CONTACT
Digital Sound Antiques
http://dsa.sakura.ne.jp/