2025-06-27 00:32:57 +08:00
|
|
|
|
#include "main.h"
|
|
|
|
|
#include "nes_apu.h"
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
2025-07-05 19:47:28 +08:00
|
|
|
|
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֲ<EFBFBD><D6B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ye781205<30><35>NESģ<53><C4A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
|
|
|
//ALIENTEK STM32F407<30><37><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
|
|
|
//NES APU <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
|
|
|
//<2F><><EFBFBD><EFBFBD>ԭ<EFBFBD><D4AD>@ALIENTEK
|
|
|
|
|
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>̳:www.openedv.com
|
|
|
|
|
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>:2014/7/1
|
|
|
|
|
//<2F>汾<EFBFBD><E6B1BE>V1.0
|
2025-06-27 00:32:57 +08:00
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* look up table madness */
|
|
|
|
|
#define APU_OVERSAMPLE
|
|
|
|
|
#define APU_VOLUME_DECAY(x) ((x) -= ((x) >> 7))
|
2025-07-05 19:47:28 +08:00
|
|
|
|
//<2F><>Ҫ<EFBFBD>õ<EFBFBD><C3B5>Ļ<EFBFBD><C4BB><EFBFBD><EFBFBD>Ĵ<EFBFBD><C4B4>뼰<EFBFBD><EBBCB0><EFBFBD><EFBFBD>
|
2025-06-27 00:32:57 +08:00
|
|
|
|
u8 K6502_Read( u16 wAddr ); //6502.s
|
|
|
|
|
extern u32 clocks; //6502.s
|
|
|
|
|
//noise lookups for both modes */
|
2025-07-05 19:47:28 +08:00
|
|
|
|
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ */
|
2025-06-27 00:32:57 +08:00
|
|
|
|
#ifndef REALTIME_NOISE //1
|
|
|
|
|
static char noise_long_lut[APU_NOISE_32K];//APU_NOISE_32K 0x7FFF
|
|
|
|
|
static char noise_short_lut[APU_NOISE_93];//APU_NOISE_93 93
|
|
|
|
|
#endif /* !REALTIME_NOISE */
|
|
|
|
|
|
2025-07-05 19:47:28 +08:00
|
|
|
|
/* vblank<6E><6B><EFBFBD>ȱ<EFBFBD><C8B1><EFBFBD><EFBFBD>ھ<EFBFBD><DABE>Ρ<EFBFBD><CEA1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ρ<EFBFBD><CEA1><EFBFBD><EFBFBD><EFBFBD> */
|
2025-06-27 00:32:57 +08:00
|
|
|
|
static const u8 vbl_length[32] =
|
|
|
|
|
{
|
|
|
|
|
5, 127, 10, 1, 19, 2, 40, 3, 80, 4, 30, 5, 7, 6, 13, 7,
|
|
|
|
|
6, 8, 12, 9, 24, 10, 48, 11, 96, 12, 36, 13, 8, 14, 16, 15
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-05 19:47:28 +08:00
|
|
|
|
/* <20><><EFBFBD><EFBFBD>ͨ<EFBFBD><CDA8><EFBFBD><EFBFBD>Ƶ<EFBFBD><C6B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
|
2025-06-27 00:32:57 +08:00
|
|
|
|
static const int freq_limit[8] =
|
|
|
|
|
{
|
|
|
|
|
0x3FF, 0x555, 0x666, 0x71C, 0x787, 0x7C1, 0x7E0, 0x7F0
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-05 19:47:28 +08:00
|
|
|
|
/* <20><><EFBFBD><EFBFBD>Ƶ<EFBFBD>ʲ<EFBFBD><CAB2>ұ<EFBFBD> */
|
2025-06-27 00:32:57 +08:00
|
|
|
|
static const int noise_freq[16] =
|
|
|
|
|
{
|
|
|
|
|
4, 8, 16, 32, 64, 96, 128, 160,
|
|
|
|
|
202, 254, 380, 508, 762, 1016, 2034, 4068
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* DMC transfer freqs */
|
|
|
|
|
const int dmc_clocks[16] =
|
|
|
|
|
{
|
|
|
|
|
428, 380, 340, 320, 286, 254, 226, 214,
|
|
|
|
|
190, 160, 142, 128, 106, 85, 72, 54
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-05 19:47:28 +08:00
|
|
|
|
/* pos /<2F><>Ƭ<EFBFBD>ı<EFBFBD><C4B1><EFBFBD>Ϊ<EFBFBD><CEAA><EFBFBD>β<EFBFBD><CEB2><EFBFBD><EFBFBD><EFBFBD> */
|
2025-06-27 00:32:57 +08:00
|
|
|
|
static const int duty_lut[4] = { 2, 4, 8, 12 };
|
|
|
|
|
|
|
|
|
|
|
2025-07-05 19:47:28 +08:00
|
|
|
|
/*** Simple queue routines<65><EFBFBD><F2B5A5B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>*/
|
2025-06-27 00:32:57 +08:00
|
|
|
|
#define APU_QEMPTY() (apu->q_head == apu->q_tail)
|
|
|
|
|
|
|
|
|
|
void apu_enqueue(apudata_t *d)
|
|
|
|
|
{
|
|
|
|
|
apu->queue[apu->q_head] = *d;
|
|
|
|
|
|
|
|
|
|
apu->q_head = (apu->q_head + 1) & APUQUEUE_MASK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
apudata_t *apu_dequeue(void)
|
|
|
|
|
{
|
|
|
|
|
int loc;
|
|
|
|
|
|
|
|
|
|
loc = apu->q_tail;
|
|
|
|
|
apu->q_tail = (apu->q_tail + 1) & APUQUEUE_MASK;
|
|
|
|
|
|
|
|
|
|
return &apu->queue[loc];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void apu_setchan(int chan, u8 enabled)
|
|
|
|
|
{
|
|
|
|
|
apu->mix_enable[chan] = enabled;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* emulation of the 15-bit shift register the
|
|
|
|
|
** NES uses to generate pseudo-random series
|
|
|
|
|
** for the white noise channel
|
2025-07-05 19:47:28 +08:00
|
|
|
|
ģ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>15λ<EFBFBD><EFBFBD>λ<EFBFBD>Ĵ<EFBFBD><EFBFBD><EFBFBD>
|
|
|
|
|
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>NES<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>α<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
|
|
|
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ŵ<EFBFBD>
|
2025-06-27 00:32:57 +08:00
|
|
|
|
*/
|
|
|
|
|
#ifdef REALTIME_NOISE //1
|
|
|
|
|
char shift_register15(u8 xor_tap)
|
|
|
|
|
{
|
|
|
|
|
static int sreg = 0x4000;
|
|
|
|
|
int bit0, tap, bit14;
|
|
|
|
|
|
|
|
|
|
bit0 = sreg & 1;
|
|
|
|
|
tap = (sreg & xor_tap) ? 1 : 0;
|
|
|
|
|
bit14 = (bit0 ^ tap);
|
|
|
|
|
sreg >>= 1;
|
|
|
|
|
sreg |= (bit14 << 14);
|
|
|
|
|
return (bit0 ^ 1);
|
|
|
|
|
}
|
|
|
|
|
#else /* !REALTIME_NOISE */
|
|
|
|
|
void shift_register15(char *buf, int count)
|
|
|
|
|
{
|
|
|
|
|
static int sreg = 0x4000;
|
|
|
|
|
int bit0, bit1, bit6, bit14;
|
|
|
|
|
|
|
|
|
|
if (count == APU_NOISE_93)
|
|
|
|
|
{
|
|
|
|
|
while (count--)
|
|
|
|
|
{
|
|
|
|
|
bit0 = sreg & 1;
|
|
|
|
|
bit6 = (sreg & 0x40) >> 6;
|
|
|
|
|
bit14 = (bit0 ^ bit6);
|
|
|
|
|
sreg >>= 1;
|
|
|
|
|
sreg |= (bit14 << 14);
|
|
|
|
|
*buf++ = bit0 ^ 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else /* 32K noise */
|
|
|
|
|
{
|
|
|
|
|
while (count--)
|
|
|
|
|
{
|
|
|
|
|
bit0 = sreg & 1;
|
|
|
|
|
bit1 = (sreg & 2) >> 1;
|
|
|
|
|
bit14 = (bit0 ^ bit1);
|
|
|
|
|
sreg >>= 1;
|
|
|
|
|
sreg |= (bit14 << 14);
|
|
|
|
|
*buf++ = bit0 ^ 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif /* !REALTIME_NOISE */
|
|
|
|
|
|
|
|
|
|
/* RECTANGLE WAVE
|
|
|
|
|
** ==============
|
|
|
|
|
** reg0: 0-3=volume, 4=envelope, 5=hold, 6-7=duty cycle
|
|
|
|
|
** reg1: 0-2=sweep shifts, 3=sweep inc/dec, 4-6=sweep length, 7=sweep on
|
|
|
|
|
** reg2: 8 bits of freq
|
|
|
|
|
** reg3: 0-2=high freq, 7-4=vbl length counter
|
|
|
|
|
*/
|
|
|
|
|
#define APU_RECTANGLE_OUTPUT chan->output_vol
|
|
|
|
|
int apu_rectangle(rectangle_t *chan)
|
|
|
|
|
{
|
|
|
|
|
int output;
|
|
|
|
|
|
|
|
|
|
#ifdef APU_OVERSAMPLE //1
|
|
|
|
|
int num_times;
|
|
|
|
|
int total;
|
|
|
|
|
#endif /* APU_OVERSAMPLE */
|
|
|
|
|
|
|
|
|
|
APU_VOLUME_DECAY(chan->output_vol);
|
|
|
|
|
|
|
|
|
|
if (FALSE == chan->enabled || 0 == chan->vbl_length)
|
|
|
|
|
return APU_RECTANGLE_OUTPUT;
|
|
|
|
|
|
|
|
|
|
/* vbl length counter */
|
|
|
|
|
if (FALSE == chan->holdnote)
|
|
|
|
|
chan->vbl_length--;
|
|
|
|
|
|
|
|
|
|
/* envelope decay at a rate of (env_delay + 1) / 240 secs */
|
|
|
|
|
#if 0
|
|
|
|
|
chan->env_phase -= 4; /* 240/60 */
|
|
|
|
|
while (chan->env_phase < 0)
|
|
|
|
|
{
|
|
|
|
|
chan->env_phase += chan->env_delay;
|
|
|
|
|
|
|
|
|
|
if (chan->holdnote)
|
|
|
|
|
chan->env_vol = (chan->env_vol + 1) & 0x0F;
|
|
|
|
|
else if (chan->env_vol < 0x0F)
|
|
|
|
|
chan->env_vol++;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
int env_phase = chan->env_phase;
|
|
|
|
|
int env_delay = chan->env_delay;
|
|
|
|
|
int holdnote = chan->holdnote;
|
|
|
|
|
int env_vol = chan->env_vol;
|
|
|
|
|
env_phase -= 4; /* 240/60 */
|
|
|
|
|
while (env_phase < 0)
|
|
|
|
|
{
|
2025-07-05 19:47:28 +08:00
|
|
|
|
if(env_delay==0)break;//*******<2A>Ҽӵ<D2BC>*****************1943<34><33><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϸ<EFBFBD><CFB7>0
|
2025-06-27 00:32:57 +08:00
|
|
|
|
env_phase += env_delay;
|
|
|
|
|
|
|
|
|
|
if (holdnote)
|
|
|
|
|
env_vol = (env_vol + 1) & 0x0F;
|
|
|
|
|
else if (env_vol < 0x0F)
|
|
|
|
|
env_vol++;
|
|
|
|
|
}
|
|
|
|
|
chan->env_phase = env_phase;
|
|
|
|
|
chan->env_delay = env_delay;
|
|
|
|
|
chan->holdnote = holdnote;
|
|
|
|
|
chan->env_vol = env_vol;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* TODO: using a table of max frequencies is not technically
|
|
|
|
|
** clean, but it is fast and (or should be) accurate
|
|
|
|
|
*/
|
|
|
|
|
if (chan->freq < 8 || (FALSE == chan->sweep_inc && chan->freq > chan->freq_limit))
|
|
|
|
|
return APU_RECTANGLE_OUTPUT;
|
|
|
|
|
|
|
|
|
|
/* frequency sweeping at a rate of (sweep_delay + 1) / 120 secs */
|
|
|
|
|
if (chan->sweep_on && chan->sweep_shifts)
|
|
|
|
|
{
|
|
|
|
|
chan->sweep_phase -= 2; /* 120/60 */
|
|
|
|
|
while (chan->sweep_phase < 0)
|
|
|
|
|
{
|
|
|
|
|
chan->sweep_phase += chan->sweep_delay;
|
|
|
|
|
|
|
|
|
|
if (chan->sweep_inc) /* ramp up */
|
|
|
|
|
{
|
|
|
|
|
if (TRUE == chan->sweep_complement)
|
|
|
|
|
chan->freq += ~(chan->freq >> chan->sweep_shifts);
|
|
|
|
|
else
|
|
|
|
|
chan->freq -= (chan->freq >> chan->sweep_shifts);
|
|
|
|
|
}
|
|
|
|
|
else /* ramp down */
|
|
|
|
|
{
|
|
|
|
|
chan->freq += (chan->freq >> chan->sweep_shifts);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
chan->phaseacc -= apu->cycle_rate; /* # of cycles per sample */
|
|
|
|
|
if (chan->phaseacc >= 0)
|
|
|
|
|
return APU_RECTANGLE_OUTPUT;
|
|
|
|
|
|
|
|
|
|
#ifdef APU_OVERSAMPLE //1
|
|
|
|
|
num_times = total = 0;
|
|
|
|
|
|
|
|
|
|
if (chan->fixed_envelope)
|
|
|
|
|
output = chan->volume << 8; /* fixed volume */
|
|
|
|
|
else
|
|
|
|
|
output = (chan->env_vol ^ 0x0F) << 8;
|
|
|
|
|
#endif /* APU_OVERSAMPLE */
|
|
|
|
|
|
|
|
|
|
while (chan->phaseacc < 0)
|
|
|
|
|
{
|
|
|
|
|
chan->phaseacc += APU_TO_FIXED(chan->freq + 1);
|
|
|
|
|
chan->adder = (chan->adder + 1) & 0x0F;
|
|
|
|
|
|
|
|
|
|
#ifdef APU_OVERSAMPLE //1
|
|
|
|
|
if (chan->adder < chan->duty_flip)
|
|
|
|
|
total += output;
|
|
|
|
|
else
|
|
|
|
|
total -= output;
|
|
|
|
|
|
|
|
|
|
num_times++;
|
|
|
|
|
#endif /* APU_OVERSAMPLE */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef APU_OVERSAMPLE //1
|
|
|
|
|
chan->output_vol = total / num_times;
|
|
|
|
|
#else /* !APU_OVERSAMPLE */
|
|
|
|
|
if (chan->fixed_envelope)
|
|
|
|
|
output = chan->volume << 8; /* fixed volume */
|
|
|
|
|
else
|
|
|
|
|
output = (chan->env_vol ^ 0x0F) << 8;
|
|
|
|
|
|
|
|
|
|
if (0 == chan->adder)
|
|
|
|
|
chan->output_vol = output;
|
|
|
|
|
else if (chan->adder == chan->duty_flip)
|
|
|
|
|
chan->output_vol = -output;
|
|
|
|
|
#endif /* !APU_OVERSAMPLE */
|
|
|
|
|
|
|
|
|
|
return APU_RECTANGLE_OUTPUT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* TRIANGLE WAVE
|
|
|
|
|
** =============
|
|
|
|
|
** reg0: 7=holdnote, 6-0=linear length counter
|
|
|
|
|
** reg2: low 8 bits of frequency
|
|
|
|
|
** reg3: 7-3=length counter, 2-0=high 3 bits of frequency
|
|
|
|
|
*/
|
|
|
|
|
#define APU_TRIANGLE_OUTPUT (chan->output_vol + (chan->output_vol >> 2))
|
|
|
|
|
int apu_triangle(triangle_t *chan)
|
|
|
|
|
{
|
|
|
|
|
APU_VOLUME_DECAY(chan->output_vol);
|
|
|
|
|
|
|
|
|
|
if (FALSE == chan->enabled || 0 == chan->vbl_length)
|
|
|
|
|
return APU_TRIANGLE_OUTPUT;
|
|
|
|
|
|
|
|
|
|
if (chan->counter_started)
|
|
|
|
|
{
|
|
|
|
|
if (chan->linear_length > 0)
|
|
|
|
|
chan->linear_length--;
|
|
|
|
|
if (chan->vbl_length && FALSE == chan->holdnote)
|
|
|
|
|
chan->vbl_length--;
|
|
|
|
|
}
|
|
|
|
|
else if (FALSE == chan->holdnote && chan->write_latency)
|
|
|
|
|
{
|
|
|
|
|
if (--chan->write_latency == 0)
|
|
|
|
|
chan->counter_started = TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (0 == chan->linear_length || chan->freq < APU_TO_FIXED(4)) /* inaudible */
|
|
|
|
|
return APU_TRIANGLE_OUTPUT;
|
|
|
|
|
|
|
|
|
|
chan->phaseacc -= apu->cycle_rate; /* # of cycles per sample */
|
|
|
|
|
while (chan->phaseacc < 0)
|
|
|
|
|
{
|
|
|
|
|
chan->phaseacc += chan->freq;
|
|
|
|
|
chan->adder = (chan->adder + 1) & 0x1F;
|
|
|
|
|
|
|
|
|
|
if (chan->adder & 0x10)
|
|
|
|
|
chan->output_vol -= (2 << 8);
|
|
|
|
|
else
|
|
|
|
|
chan->output_vol += (2 << 8);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return APU_TRIANGLE_OUTPUT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* WHITE NOISE CHANNEL
|
|
|
|
|
** ===================
|
|
|
|
|
** reg0: 0-3=volume, 4=envelope, 5=hold
|
|
|
|
|
** reg2: 7=small(93 byte) sample,3-0=freq lookup
|
|
|
|
|
** reg3: 7-4=vbl length counter
|
|
|
|
|
*/
|
|
|
|
|
#define APU_NOISE_OUTPUT ((chan->output_vol + chan->output_vol + chan->output_vol) >> 2)
|
|
|
|
|
|
|
|
|
|
int apu_noise(noise_t *chan)
|
|
|
|
|
{
|
|
|
|
|
int outvol;
|
|
|
|
|
|
|
|
|
|
#if defined(APU_OVERSAMPLE) && defined(REALTIME_NOISE)
|
|
|
|
|
#else /* !(APU_OVERSAMPLE && REALTIME_NOISE) */
|
|
|
|
|
int noise_bit;
|
|
|
|
|
#endif /* !(APU_OVERSAMPLE && REALTIME_NOISE) */
|
|
|
|
|
#ifdef APU_OVERSAMPLE
|
|
|
|
|
int num_times;
|
|
|
|
|
int total;
|
|
|
|
|
#endif /* APU_OVERSAMPLE */
|
|
|
|
|
|
|
|
|
|
APU_VOLUME_DECAY(chan->output_vol);
|
|
|
|
|
|
|
|
|
|
if (FALSE == chan->enabled || 0 == chan->vbl_length)
|
|
|
|
|
return APU_NOISE_OUTPUT;
|
|
|
|
|
|
|
|
|
|
/* vbl length counter */
|
|
|
|
|
if (FALSE == chan->holdnote)
|
|
|
|
|
chan->vbl_length--;
|
|
|
|
|
|
|
|
|
|
/* envelope decay at a rate of (env_delay + 1) / 240 secs */
|
|
|
|
|
#if 0
|
|
|
|
|
chan->env_phase -= 4; /* 240/60 */
|
|
|
|
|
while (chan->env_phase < 0)
|
|
|
|
|
{
|
|
|
|
|
chan->env_phase += chan->env_delay;
|
|
|
|
|
|
|
|
|
|
if (chan->holdnote)
|
|
|
|
|
chan->env_vol = (chan->env_vol + 1) & 0x0F;
|
|
|
|
|
else if (chan->env_vol < 0x0F)
|
|
|
|
|
chan->env_vol++;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
int env_phase = chan->env_phase;
|
|
|
|
|
int env_delay = chan->env_delay;
|
|
|
|
|
int holdnote = chan->holdnote;
|
|
|
|
|
int env_vol = chan->env_vol;
|
|
|
|
|
env_phase -= 4; /* 240/60 */
|
|
|
|
|
while (env_phase < 0)
|
|
|
|
|
{
|
|
|
|
|
env_phase += env_delay;
|
|
|
|
|
|
|
|
|
|
if (holdnote)
|
|
|
|
|
env_vol = (env_vol + 1) & 0x0F;
|
|
|
|
|
else if (env_vol < 0x0F)
|
|
|
|
|
env_vol++;
|
|
|
|
|
}
|
|
|
|
|
chan->env_phase = env_phase;
|
|
|
|
|
chan->env_delay = env_delay;
|
|
|
|
|
chan->holdnote = holdnote;
|
|
|
|
|
chan->env_vol = env_vol;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
chan->phaseacc -= apu->cycle_rate; /* # of cycles per sample */
|
|
|
|
|
if (chan->phaseacc >= 0)
|
|
|
|
|
return APU_NOISE_OUTPUT;
|
|
|
|
|
|
|
|
|
|
#ifdef APU_OVERSAMPLE
|
|
|
|
|
num_times = total = 0;
|
|
|
|
|
|
|
|
|
|
if (chan->fixed_envelope)
|
|
|
|
|
outvol = chan->volume << 8; /* fixed volume */
|
|
|
|
|
else
|
|
|
|
|
outvol = (chan->env_vol ^ 0x0F) << 8;
|
|
|
|
|
#endif /* APU_OVERSAMPLE */
|
|
|
|
|
|
|
|
|
|
while (chan->phaseacc < 0)
|
|
|
|
|
{
|
|
|
|
|
|
2025-07-05 19:47:28 +08:00
|
|
|
|
if(chan->freq==0)break;//*******<2A>Ҽӵ<D2BC>*****************<2A><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϸ<EFBFBD><CFB7>0/////////////////////////
|
2025-06-27 00:32:57 +08:00
|
|
|
|
chan->phaseacc += chan->freq;
|
|
|
|
|
|
|
|
|
|
#ifdef REALTIME_NOISE
|
|
|
|
|
|
|
|
|
|
#ifdef APU_OVERSAMPLE
|
|
|
|
|
if (shift_register15(chan->xor_tap))
|
|
|
|
|
total += outvol;
|
|
|
|
|
else
|
|
|
|
|
total -= outvol;
|
|
|
|
|
|
|
|
|
|
num_times++;
|
|
|
|
|
#else /* !APU_OVERSAMPLE */
|
|
|
|
|
noise_bit = shift_register15(chan->xor_tap);
|
|
|
|
|
#endif /* !APU_OVERSAMPLE */
|
|
|
|
|
|
|
|
|
|
#else /* !REALTIME_NOISE */
|
|
|
|
|
chan->cur_pos++;
|
|
|
|
|
|
|
|
|
|
if (chan->short_sample)
|
|
|
|
|
{
|
|
|
|
|
if (APU_NOISE_93 == chan->cur_pos)
|
|
|
|
|
chan->cur_pos = 0;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (APU_NOISE_32K == chan->cur_pos)
|
|
|
|
|
chan->cur_pos = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef APU_OVERSAMPLE
|
|
|
|
|
if (chan->short_sample)
|
|
|
|
|
noise_bit = noise_short_lut[chan->cur_pos];
|
|
|
|
|
else
|
|
|
|
|
noise_bit = noise_long_lut[chan->cur_pos];
|
|
|
|
|
|
|
|
|
|
if (noise_bit)
|
|
|
|
|
total += outvol;
|
|
|
|
|
else
|
|
|
|
|
total -= outvol;
|
|
|
|
|
|
|
|
|
|
num_times++;
|
|
|
|
|
#endif /* APU_OVERSAMPLE */
|
|
|
|
|
#endif /* !REALTIME_NOISE */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef APU_OVERSAMPLE
|
|
|
|
|
chan->output_vol = total / num_times;
|
|
|
|
|
#else /* !APU_OVERSAMPLE */
|
|
|
|
|
if (chan->fixed_envelope)
|
|
|
|
|
outvol = chan->volume << 8; /* fixed volume */
|
|
|
|
|
else
|
|
|
|
|
outvol = (chan->env_vol ^ 0x0F) << 8;
|
|
|
|
|
|
|
|
|
|
#ifndef REALTIME_NOISE
|
|
|
|
|
if (chan->short_sample)
|
|
|
|
|
noise_bit = noise_short_lut[chan->cur_pos];
|
|
|
|
|
else
|
|
|
|
|
noise_bit = noise_long_lut[chan->cur_pos];
|
|
|
|
|
#endif /* !REALTIME_NOISE */
|
|
|
|
|
|
|
|
|
|
if (noise_bit)
|
|
|
|
|
chan->output_vol = outvol;
|
|
|
|
|
else
|
|
|
|
|
chan->output_vol = -outvol;
|
|
|
|
|
#endif /* !APU_OVERSAMPLE */
|
|
|
|
|
|
|
|
|
|
return APU_NOISE_OUTPUT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void apu_dmcreload(dmc_t *chan)
|
|
|
|
|
{
|
|
|
|
|
chan->address = chan->cached_addr;
|
|
|
|
|
chan->dma_length = chan->cached_dmalength;
|
|
|
|
|
chan->irq_occurred = FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* DELTA MODULATION CHANNEL
|
|
|
|
|
** =========================
|
|
|
|
|
** reg0: 7=irq gen, 6=looping, 3-0=pointer to clock table
|
|
|
|
|
** reg1: output dc level, 6 bits unsigned
|
|
|
|
|
** reg2: 8 bits of 64-byte aligned address offset : $C000 + (value * 64)
|
|
|
|
|
** reg3: length, (value * 16) + 1
|
|
|
|
|
*/
|
|
|
|
|
#define APU_DMC_OUTPUT ((chan->output_vol + chan->output_vol + chan->output_vol) >> 2)
|
|
|
|
|
int apu_dmc(dmc_t *chan)
|
|
|
|
|
{
|
|
|
|
|
int delta_bit;
|
|
|
|
|
|
|
|
|
|
APU_VOLUME_DECAY(chan->output_vol);
|
|
|
|
|
|
|
|
|
|
/* only process when channel is alive */
|
|
|
|
|
if (chan->dma_length)
|
|
|
|
|
{
|
|
|
|
|
chan->phaseacc -= apu->cycle_rate; /* # of cycles per sample */
|
|
|
|
|
|
|
|
|
|
while (chan->phaseacc < 0)
|
|
|
|
|
{
|
|
|
|
|
chan->phaseacc += chan->freq;
|
|
|
|
|
|
|
|
|
|
delta_bit = (chan->dma_length & 7) ^ 7;
|
|
|
|
|
|
|
|
|
|
if (7 == delta_bit)
|
|
|
|
|
{
|
|
|
|
|
chan->cur_byte =K6502_Read(chan->address);//chan->cur_byte = nes6502_getbyte(chan->address);*********************
|
2025-07-05 19:47:28 +08:00
|
|
|
|
/* steal a cycle from CPU͵<55><CDB5>CPU<50><55><EFBFBD><EFBFBD>*/
|
|
|
|
|
clocks++; // nes6502_burn(1);//ҪCPUʱ<55><CAB1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>1**********************************************************************
|
2025-06-27 00:32:57 +08:00
|
|
|
|
|
|
|
|
|
if (0xFFFF == chan->address)
|
|
|
|
|
chan->address = 0x8000;
|
|
|
|
|
else
|
|
|
|
|
chan->address++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (--chan->dma_length == 0)
|
|
|
|
|
{
|
|
|
|
|
/* if loop bit set, we're cool to retrigger sample */
|
|
|
|
|
if (chan->looping)
|
|
|
|
|
apu_dmcreload(chan);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* check to see if we should generate an irq */
|
|
|
|
|
if (chan->irq_gen)
|
|
|
|
|
{
|
|
|
|
|
chan->irq_occurred = TRUE;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* bodge for timestamp queue */
|
|
|
|
|
chan->enabled = FALSE;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* positive delta */
|
|
|
|
|
if (chan->cur_byte & (1 << delta_bit))
|
|
|
|
|
{
|
|
|
|
|
if (chan->regs[1] < 0x7D)
|
|
|
|
|
{
|
|
|
|
|
chan->regs[1] += 2;
|
|
|
|
|
chan->output_vol += (2 << 8);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* negative delta */
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (chan->regs[1] > 1)
|
|
|
|
|
{
|
|
|
|
|
chan->regs[1] -= 2;
|
|
|
|
|
chan->output_vol -= (2 << 8);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return APU_DMC_OUTPUT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void apu_regwrite(u32 address, u8 value)
|
|
|
|
|
{
|
|
|
|
|
int chan = (address & 4) ? 1 : 0;
|
|
|
|
|
rectangle_t * rect = chan ? &(apu->rectangle[1]) : &(apu->rectangle[0]);
|
|
|
|
|
|
|
|
|
|
switch (address)
|
|
|
|
|
{
|
|
|
|
|
/* rectangles */
|
|
|
|
|
case APU_WRA0:
|
|
|
|
|
case APU_WRB0:
|
|
|
|
|
/*
|
|
|
|
|
chan = (address & 4) ? 1 : 0;
|
|
|
|
|
apu->rectangle[chan].regs[0] = value;
|
|
|
|
|
|
|
|
|
|
apu->rectangle[chan].volume = value & 0x0F;
|
|
|
|
|
apu->rectangle[chan].env_delay = apu->decay_lut[value & 0x0F];
|
|
|
|
|
apu->rectangle[chan].holdnote = (value & 0x20) ? TRUE : FALSE;
|
|
|
|
|
apu->rectangle[chan].fixed_envelope = (value & 0x10) ? TRUE : FALSE;
|
|
|
|
|
apu->rectangle[chan].duty_flip = duty_lut[value >> 6];
|
|
|
|
|
*/
|
|
|
|
|
rect->regs[0] = value;
|
|
|
|
|
rect->volume = value & 0x0F;
|
|
|
|
|
rect->env_delay = apu->decay_lut[value & 0x0F];
|
|
|
|
|
rect->holdnote = (value & 0x20) ? TRUE : FALSE;
|
|
|
|
|
rect->fixed_envelope = (value & 0x10) ? TRUE : FALSE;
|
|
|
|
|
rect->duty_flip = duty_lut[value >> 6];
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case APU_WRA1:
|
|
|
|
|
case APU_WRB1:
|
|
|
|
|
/*
|
|
|
|
|
chan = (address & 4) ? 1 : 0;
|
|
|
|
|
apu->rectangle[chan].regs[1] = value;
|
|
|
|
|
apu->rectangle[chan].sweep_on = (value & 0x80) ? TRUE : FALSE;
|
|
|
|
|
apu->rectangle[chan].sweep_shifts = value & 7;
|
|
|
|
|
apu->rectangle[chan].sweep_delay = apu->decay_lut[(value >> 4) & 7];
|
|
|
|
|
|
|
|
|
|
apu->rectangle[chan].sweep_inc = (value & 0x08) ? TRUE : FALSE;
|
|
|
|
|
apu->rectangle[chan].freq_limit = freq_limit[value & 7];
|
|
|
|
|
*/
|
|
|
|
|
rect->regs[1] = value;
|
|
|
|
|
rect->sweep_on = (value & 0x80) ? TRUE : FALSE;
|
|
|
|
|
rect->sweep_shifts = value & 7;
|
|
|
|
|
rect->sweep_delay = apu->decay_lut[(value >> 4) & 7];
|
|
|
|
|
|
|
|
|
|
rect->sweep_inc = (value & 0x08) ? TRUE : FALSE;
|
|
|
|
|
rect->freq_limit = freq_limit[value & 7];
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case APU_WRA2:
|
|
|
|
|
case APU_WRB2:
|
|
|
|
|
/*
|
|
|
|
|
chan = (address & 4) ? 1 : 0;
|
|
|
|
|
apu->rectangle[chan].regs[2] = value;
|
|
|
|
|
// if (apu->rectangle[chan].enabled)
|
|
|
|
|
apu->rectangle[chan].freq = (apu->rectangle[chan].freq & ~0xFF) | value;
|
|
|
|
|
*/
|
|
|
|
|
rect->regs[2] = value;
|
|
|
|
|
rect->freq = (apu->rectangle[chan].freq & ~0xFF) | value;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case APU_WRA3:
|
|
|
|
|
case APU_WRB3:
|
|
|
|
|
/*
|
|
|
|
|
chan = (address & 4) ? 1 : 0;
|
|
|
|
|
apu->rectangle[chan].regs[3] = value;
|
|
|
|
|
|
|
|
|
|
apu->rectangle[chan].vbl_length = apu->vbl_lut[value >> 3];
|
|
|
|
|
apu->rectangle[chan].env_vol = 0;
|
|
|
|
|
apu->rectangle[chan].freq = ((value & 7) << 8) | (apu->rectangle[chan].freq & 0xFF);
|
|
|
|
|
apu->rectangle[chan].adder = 0;
|
|
|
|
|
*/
|
|
|
|
|
rect->regs[3] = value;
|
|
|
|
|
|
|
|
|
|
rect->vbl_length = apu->vbl_lut[value >> 3];
|
|
|
|
|
rect->env_vol = 0;
|
|
|
|
|
rect->freq = ((value & 7) << 8) | (rect->freq & 0xFF);
|
|
|
|
|
rect->adder = 0;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
/* triangle */
|
|
|
|
|
case APU_WRC0:
|
|
|
|
|
/*
|
|
|
|
|
apu->triangle.regs[0] = value;
|
|
|
|
|
apu->triangle.holdnote = (value & 0x80) ? TRUE : FALSE;
|
|
|
|
|
|
|
|
|
|
if (FALSE == apu->triangle.counter_started && apu->triangle.vbl_length)
|
|
|
|
|
apu->triangle.linear_length = apu->trilength_lut[value & 0x7F];
|
|
|
|
|
*/
|
|
|
|
|
{
|
|
|
|
|
triangle_t * tri = &(apu->triangle);
|
|
|
|
|
tri->regs[0] = value;
|
|
|
|
|
tri->holdnote = (value & 0x80) ? TRUE : FALSE;
|
|
|
|
|
|
|
|
|
|
if (FALSE == tri->counter_started && tri->vbl_length)
|
|
|
|
|
tri->linear_length = apu->trilength_lut[value & 0x7F];
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case APU_WRC2:
|
|
|
|
|
/*
|
|
|
|
|
apu->triangle.regs[1] = value;
|
|
|
|
|
apu->triangle.freq = APU_TO_FIXED((((apu->triangle.regs[2] & 7) << 8) + value) + 1);
|
|
|
|
|
*/
|
|
|
|
|
{
|
|
|
|
|
triangle_t * tri = &(apu->triangle);
|
|
|
|
|
tri->regs[1] = value;
|
|
|
|
|
tri->freq = APU_TO_FIXED((((tri->regs[2] & 7) << 8) + value) + 1);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case APU_WRC3:
|
|
|
|
|
#if 0
|
|
|
|
|
apu->triangle.regs[2] = value;
|
|
|
|
|
|
|
|
|
|
/* this is somewhat of a hack. there appears to be some latency on
|
|
|
|
|
** the Real Thing between when trireg0 is written to and when the
|
|
|
|
|
** linear length counter actually begins its countdown. we want to
|
|
|
|
|
** prevent the case where the program writes to the freq regs first,
|
|
|
|
|
** then to reg 0, and the counter accidentally starts running because
|
|
|
|
|
** of the sound queue's timestamp processing.
|
|
|
|
|
**
|
|
|
|
|
** set latency to a couple hundred cycles -- should be plenty of time
|
|
|
|
|
** for the 6502 code to do a couple of table dereferences and load up
|
|
|
|
|
** the other triregs
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* 06/13/00 MPC -- seems to work OK */
|
|
|
|
|
apu->triangle.write_latency = (int) (228 / APU_FROM_FIXED(apu->cycle_rate));
|
|
|
|
|
|
|
|
|
|
apu->triangle.freq = APU_TO_FIXED((((value & 7) << 8) + apu->triangle.regs[1]) + 1);
|
|
|
|
|
apu->triangle.vbl_length = apu->vbl_lut[value >> 3];
|
|
|
|
|
apu->triangle.counter_started = FALSE;
|
|
|
|
|
apu->triangle.linear_length = apu->trilength_lut[apu->triangle.regs[0] & 0x7F];
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
triangle_t * tri = &(apu->triangle);
|
|
|
|
|
tri->regs[2] = value;
|
|
|
|
|
tri->write_latency = (int) (228 / APU_FROM_FIXED(apu->cycle_rate));
|
|
|
|
|
|
|
|
|
|
tri->freq = APU_TO_FIXED((((value & 7) << 8) + tri->regs[1]) + 1);
|
|
|
|
|
tri->vbl_length = apu->vbl_lut[value >> 3];
|
|
|
|
|
tri->counter_started = FALSE;
|
|
|
|
|
tri->linear_length = apu->trilength_lut[tri->regs[0] & 0x7F];
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
/* noise */
|
|
|
|
|
case APU_WRD0:
|
|
|
|
|
/*
|
|
|
|
|
apu->noise.regs[0] = value;
|
|
|
|
|
apu->noise.env_delay = apu->decay_lut[value & 0x0F];
|
|
|
|
|
apu->noise.holdnote = (value & 0x20) ? TRUE : FALSE;
|
|
|
|
|
apu->noise.fixed_envelope = (value & 0x10) ? TRUE : FALSE;
|
|
|
|
|
apu->noise.volume = value & 0x0F;
|
|
|
|
|
*/
|
|
|
|
|
{
|
|
|
|
|
noise_t * noise = &(apu->noise);
|
|
|
|
|
noise->regs[0] = value;
|
|
|
|
|
noise->env_delay = apu->decay_lut[value & 0x0F];
|
|
|
|
|
noise->holdnote = (value & 0x20) ? TRUE : FALSE;
|
|
|
|
|
noise->fixed_envelope = (value & 0x10) ? TRUE : FALSE;
|
|
|
|
|
noise->volume = value & 0x0F;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case APU_WRD2:
|
|
|
|
|
#if 0
|
|
|
|
|
apu->noise.regs[1] = value;
|
|
|
|
|
apu->noise.freq = APU_TO_FIXED(noise_freq[value & 0x0F]);
|
|
|
|
|
|
|
|
|
|
#ifdef REALTIME_NOISE
|
|
|
|
|
apu->noise.xor_tap = (value & 0x80) ? 0x40: 0x02;
|
|
|
|
|
#else /* !REALTIME_NOISE */
|
|
|
|
|
/* detect transition from long->short sample */
|
|
|
|
|
if ((value & 0x80) && FALSE == apu->noise.short_sample)
|
|
|
|
|
{
|
|
|
|
|
/* recalculate short noise buffer */
|
|
|
|
|
shift_register15(noise_short_lut, APU_NOISE_93);
|
|
|
|
|
apu->noise.cur_pos = 0;
|
|
|
|
|
}
|
|
|
|
|
apu->noise.short_sample = (value & 0x80) ? TRUE : FALSE;
|
|
|
|
|
#endif /* !REALTIME_NOISE */
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
noise_t * noise = &(apu->noise);
|
|
|
|
|
noise->regs[1] = value;
|
|
|
|
|
noise->freq = APU_TO_FIXED(noise_freq[value & 0x0F]);
|
|
|
|
|
|
|
|
|
|
#ifdef REALTIME_NOISE
|
|
|
|
|
noise->xor_tap = (value & 0x80) ? 0x40: 0x02;
|
|
|
|
|
#else /* !REALTIME_NOISE */
|
|
|
|
|
/* detect transition from long->short sample */
|
|
|
|
|
if ((value & 0x80) && FALSE == noise->short_sample)
|
|
|
|
|
{
|
|
|
|
|
/* recalculate short noise buffer */
|
|
|
|
|
shift_register15(noise_short_lut, APU_NOISE_93);
|
|
|
|
|
noise->cur_pos = 0;
|
|
|
|
|
}
|
|
|
|
|
noise->short_sample = (value & 0x80) ? TRUE : FALSE;
|
|
|
|
|
#endif /* !REALTIME_NOISE */
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case APU_WRD3:
|
|
|
|
|
#if 0
|
|
|
|
|
apu->noise.regs[2] = value;
|
|
|
|
|
|
|
|
|
|
apu->noise.vbl_length = apu->vbl_lut[value >> 3];
|
|
|
|
|
apu->noise.env_vol = 0; /* reset envelope */
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
noise_t * noise = &(apu->noise);
|
|
|
|
|
noise->regs[2] = value;
|
|
|
|
|
|
|
|
|
|
noise->vbl_length = apu->vbl_lut[value >> 3];
|
|
|
|
|
noise->env_vol = 0; /* reset envelope */
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
/* DMC */
|
|
|
|
|
case APU_WRE0:
|
|
|
|
|
/*
|
|
|
|
|
apu->dmc.regs[0] = value;
|
|
|
|
|
|
|
|
|
|
apu->dmc.freq = APU_TO_FIXED(dmc_clocks[value & 0x0F]);
|
|
|
|
|
apu->dmc.looping = (value & 0x40) ? TRUE : FALSE;
|
|
|
|
|
|
|
|
|
|
if (value & 0x80)
|
|
|
|
|
apu->dmc.irq_gen = TRUE;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
apu->dmc.irq_gen = FALSE;
|
|
|
|
|
apu->dmc.irq_occurred = FALSE;
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
{
|
|
|
|
|
dmc_t * dmc = &(apu->dmc);
|
|
|
|
|
|
|
|
|
|
dmc->regs[0] = value;
|
|
|
|
|
|
|
|
|
|
dmc->freq = APU_TO_FIXED(dmc_clocks[value & 0x0F]);
|
|
|
|
|
dmc->looping = (value & 0x40) ? TRUE : FALSE;
|
|
|
|
|
|
|
|
|
|
if (value & 0x80)
|
|
|
|
|
dmc->irq_gen = TRUE;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
dmc->irq_gen = FALSE;
|
|
|
|
|
dmc->irq_occurred = FALSE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case APU_WRE1: /* 7-bit DAC */
|
|
|
|
|
#if 0
|
|
|
|
|
/* add the _delta_ between written value and
|
|
|
|
|
** current output level of the volume reg
|
|
|
|
|
*/
|
|
|
|
|
value &= 0x7F; /* bit 7 ignored */
|
|
|
|
|
apu->dmc.output_vol += ((value - apu->dmc.regs[1]) << 8);
|
|
|
|
|
apu->dmc.regs[1] = value;
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
dmc_t * dmc = &(apu->dmc);
|
|
|
|
|
value &= 0x7F; /* bit 7 ignored */
|
|
|
|
|
dmc->output_vol += ((value - apu->dmc.regs[1]) << 8);
|
|
|
|
|
dmc->regs[1] = value;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case APU_WRE2:
|
|
|
|
|
/*
|
|
|
|
|
apu->dmc.regs[2] = value;
|
|
|
|
|
apu->dmc.cached_addr = 0xC000 + (u16) (value << 6);
|
|
|
|
|
*/
|
|
|
|
|
{
|
|
|
|
|
dmc_t * dmc = &(apu->dmc);
|
|
|
|
|
dmc->regs[2] = value;
|
|
|
|
|
dmc->cached_addr = 0xC000 + (u16) (value << 6);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case APU_WRE3:
|
|
|
|
|
/*
|
|
|
|
|
apu->dmc.regs[3] = value;
|
|
|
|
|
apu->dmc.cached_dmalength = ((value << 4) + 1) << 3;
|
|
|
|
|
*/
|
|
|
|
|
{
|
|
|
|
|
dmc_t * dmc = &(apu->dmc);
|
|
|
|
|
dmc->regs[3] = value;
|
|
|
|
|
dmc->cached_dmalength = ((value << 4) + 1) << 3;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case APU_SMASK:
|
|
|
|
|
#if 0
|
|
|
|
|
/* bodge for timestamp queue */
|
|
|
|
|
apu->dmc.enabled = (value & 0x10) ? TRUE : FALSE;
|
|
|
|
|
|
|
|
|
|
apu->enable_reg = value;
|
|
|
|
|
|
|
|
|
|
for (chan = 0; chan < 2; chan++)
|
|
|
|
|
{
|
|
|
|
|
if (value & (1 << chan))
|
|
|
|
|
apu->rectangle[chan].enabled = TRUE;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
apu->rectangle[chan].enabled = FALSE;
|
|
|
|
|
apu->rectangle[chan].vbl_length = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (value & 0x04)
|
|
|
|
|
apu->triangle.enabled = TRUE;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
apu->triangle.enabled = FALSE;
|
|
|
|
|
apu->triangle.vbl_length = 0;
|
|
|
|
|
apu->triangle.linear_length = 0;
|
|
|
|
|
apu->triangle.counter_started = FALSE;
|
|
|
|
|
apu->triangle.write_latency = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (value & 0x08)
|
|
|
|
|
apu->noise.enabled = TRUE;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
apu->noise.enabled = FALSE;
|
|
|
|
|
apu->noise.vbl_length = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (value & 0x10)
|
|
|
|
|
{
|
|
|
|
|
if (0 == apu->dmc.dma_length)
|
|
|
|
|
apu_dmcreload(&apu->dmc);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
apu->dmc.dma_length = 0;
|
|
|
|
|
|
|
|
|
|
apu->dmc.irq_occurred = FALSE;
|
|
|
|
|
#endif
|
|
|
|
|
{
|
|
|
|
|
dmc_t * dmc = &(apu->dmc);
|
|
|
|
|
triangle_t * tri = &(apu->triangle);
|
|
|
|
|
noise_t * noise = &(apu->noise);
|
|
|
|
|
|
|
|
|
|
dmc->enabled = (value & 0x10) ? TRUE : FALSE;
|
|
|
|
|
|
|
|
|
|
apu->enable_reg = value;
|
|
|
|
|
|
|
|
|
|
for (chan = 0; chan < 2; chan++)
|
|
|
|
|
{
|
|
|
|
|
if (value & (1 << chan))
|
|
|
|
|
apu->rectangle[chan].enabled = TRUE;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
apu->rectangle[chan].enabled = FALSE;
|
|
|
|
|
apu->rectangle[chan].vbl_length = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (value & 0x04)
|
|
|
|
|
tri->enabled = TRUE;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
tri->enabled = FALSE;
|
|
|
|
|
tri->vbl_length = 0;
|
|
|
|
|
tri->linear_length = 0;
|
|
|
|
|
tri->counter_started = FALSE;
|
|
|
|
|
tri->write_latency = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (value & 0x08)
|
|
|
|
|
noise->enabled = TRUE;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
noise->enabled = FALSE;
|
|
|
|
|
noise->vbl_length = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (value & 0x10)
|
|
|
|
|
{
|
|
|
|
|
if (0 == dmc->dma_length)
|
|
|
|
|
apu_dmcreload(&apu->dmc);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
dmc->dma_length = 0;
|
|
|
|
|
|
|
|
|
|
dmc->irq_occurred = FALSE;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
/* unused, but they get hit in some mem-clear loops */
|
|
|
|
|
case 0x4009:
|
|
|
|
|
case 0x400D:
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
//DCR
|
|
|
|
|
if(apu->ext)
|
|
|
|
|
{
|
|
|
|
|
apu_memwrite* mw = apu->ext->mem_write;
|
|
|
|
|
|
2025-07-05 19:47:28 +08:00
|
|
|
|
while(mw->max_range != -1)//<2F><><EFBFBD><EFBFBD>ת<EFBFBD><D7AA><EFBFBD><EFBFBD><EFBFBD>¸ı<C2B8><C4B1>ļ<EFBFBD><C4BC><EFBFBD>
|
2025-06-27 00:32:57 +08:00
|
|
|
|
{
|
|
|
|
|
if((mw->min_range <= address) && ((mw->max_range >= address)))
|
|
|
|
|
{
|
|
|
|
|
mw->write_func(address, value);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
mw++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Read from $4000-$4017 */
|
|
|
|
|
u8 Apu_Read4015(u32 address)//***********************************************************************
|
|
|
|
|
{
|
|
|
|
|
u8 value=0;
|
|
|
|
|
|
|
|
|
|
// if(!(frame_irq_enabled & 0xC0))
|
|
|
|
|
{
|
|
|
|
|
/* Return 1 in 0-5 bit pos if a channel is playing */
|
|
|
|
|
if (apu->rectangle[0].enabled && apu->rectangle[0].vbl_length)
|
|
|
|
|
value |= 0x01;
|
|
|
|
|
if (apu->rectangle[1].enabled && apu->rectangle[1].vbl_length)
|
|
|
|
|
value |= 0x02;
|
|
|
|
|
if (apu->triangle.enabled && apu->triangle.vbl_length)
|
|
|
|
|
value |= 0x04;
|
|
|
|
|
if (apu->noise.enabled && apu->noise.vbl_length)
|
|
|
|
|
value |= 0x08;
|
|
|
|
|
|
|
|
|
|
/* bodge for timestamp queue */
|
|
|
|
|
if (apu->dmc.enabled)
|
|
|
|
|
value |= 0x10;
|
|
|
|
|
|
|
|
|
|
if (apu->dmc.irq_occurred)
|
|
|
|
|
value |= 0x80;
|
|
|
|
|
|
|
|
|
|
return value | 0x40;
|
|
|
|
|
}
|
|
|
|
|
// return value;
|
|
|
|
|
}
|
|
|
|
|
void Apu_Write4015(u8 value,u32 address )
|
|
|
|
|
{
|
|
|
|
|
apudata_t d;
|
|
|
|
|
apu->dmc.enabled = (value & 0x10) ? TRUE : FALSE;
|
|
|
|
|
d.timestamp = clocks; //d.timestamp = nes6502_getcycles(0);*********************
|
|
|
|
|
d.address = address|0x4000;
|
|
|
|
|
d.value = value;
|
|
|
|
|
apu_enqueue(&d);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Apu_Write(u8 value,u32 address )
|
|
|
|
|
{
|
|
|
|
|
apudata_t d;
|
|
|
|
|
d.timestamp = clocks; //d.timestamp = nes6502_getcycles(0);*********************
|
|
|
|
|
d.address = address|0x4000;
|
|
|
|
|
d.value = value;
|
|
|
|
|
apu_enqueue(&d);
|
|
|
|
|
}
|
|
|
|
|
void apu_getpcmdata(void **data, int *num_samples, int *sample_bits)
|
|
|
|
|
{
|
|
|
|
|
*data = apu->buffer;
|
|
|
|
|
*num_samples = apu->num_samples;
|
|
|
|
|
*sample_bits = apu->sample_bits;
|
|
|
|
|
}
|
|
|
|
|
// _local_sample_size = 8
|
|
|
|
|
// apu_process(buf, buf_len/(_local_sample_size/8));
|
|
|
|
|
void apu_process(u16 *buffer, int num_samples)
|
|
|
|
|
{
|
|
|
|
|
apudata_t *d;
|
|
|
|
|
u32 elapsed_cycles;
|
|
|
|
|
static int prev_sample = 0;
|
|
|
|
|
int next_sample, accum;
|
|
|
|
|
|
|
|
|
|
/* grab it, keep it local for speed */
|
|
|
|
|
elapsed_cycles = (u32) apu->elapsed_cycles;
|
|
|
|
|
|
|
|
|
|
if (NULL == buffer)
|
|
|
|
|
{
|
|
|
|
|
/* just go through the motions... */
|
|
|
|
|
while (num_samples--)
|
|
|
|
|
{
|
|
|
|
|
while ((FALSE == APU_QEMPTY()) && (apu->queue[apu->q_tail].timestamp <= elapsed_cycles))
|
|
|
|
|
{
|
|
|
|
|
d = apu_dequeue();
|
|
|
|
|
apu_regwrite(d->address, d->value);
|
|
|
|
|
}
|
|
|
|
|
elapsed_cycles += APU_FROM_FIXED(apu->cycle_rate);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Rick
|
|
|
|
|
u8 * mix_enable = apu->mix_enable;
|
|
|
|
|
|
|
|
|
|
/* bleh */
|
|
|
|
|
apu->buffer = buffer;
|
|
|
|
|
|
|
|
|
|
while (num_samples--)
|
|
|
|
|
{
|
|
|
|
|
while ((FALSE == APU_QEMPTY()) && (apu->queue[apu->q_tail].timestamp <= elapsed_cycles))
|
|
|
|
|
{
|
|
|
|
|
d = apu_dequeue();
|
|
|
|
|
apu_regwrite(d->address, d->value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elapsed_cycles += APU_FROM_FIXED(apu->cycle_rate);
|
|
|
|
|
|
|
|
|
|
accum = 0;
|
|
|
|
|
if (mix_enable[0]) accum += apu_rectangle(&apu->rectangle[0]);
|
|
|
|
|
if (mix_enable[1]) accum += apu_rectangle(&apu->rectangle[1]);
|
|
|
|
|
if (mix_enable[2]) accum += apu_triangle(&apu->triangle);
|
|
|
|
|
if (mix_enable[3]) accum += apu_noise(&apu->noise);
|
|
|
|
|
if (mix_enable[4]) accum += apu_dmc(&apu->dmc);
|
|
|
|
|
|
|
|
|
|
if (apu->ext && mix_enable[5]) accum += apu->ext->process();
|
|
|
|
|
|
|
|
|
|
/* do any filtering */
|
|
|
|
|
if (APU_FILTER_NONE != apu->filter_type)
|
|
|
|
|
{
|
|
|
|
|
next_sample = accum;
|
|
|
|
|
|
|
|
|
|
if (APU_FILTER_LOWPASS == apu->filter_type)
|
|
|
|
|
{
|
|
|
|
|
accum += prev_sample;
|
|
|
|
|
accum >>= 1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
accum = (accum + accum + accum + prev_sample) >> 2;
|
|
|
|
|
|
|
|
|
|
prev_sample = next_sample;
|
|
|
|
|
}
|
|
|
|
|
/* little extra kick for the kids */
|
|
|
|
|
accum <<= 1;
|
|
|
|
|
|
|
|
|
|
/* prevent clipping */
|
|
|
|
|
if (accum > 0x7FFF)accum = 0x7FFF;
|
|
|
|
|
else if (accum < -0x8000)accum = -0x8000;
|
|
|
|
|
|
2025-07-05 19:47:28 +08:00
|
|
|
|
*buffer++=(u16)accum;//<2F><>Ƶ<EFBFBD><C6B5><EFBFBD>ݴ<EFBFBD><DDB4>뻺<EFBFBD><EBBBBA>
|
2025-06-27 00:32:57 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-05 19:47:28 +08:00
|
|
|
|
/* resync cycle counter <20><><EFBFBD><EFBFBD>ͬ<EFBFBD><CDAC>ѭ<EFBFBD><D1AD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>*/
|
2025-06-27 00:32:57 +08:00
|
|
|
|
apu->elapsed_cycles =clocks; // apu->elapsed_cycles = nes6502_getcycles(0);//*****************************************************************
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void apu_reset(void)
|
|
|
|
|
{
|
|
|
|
|
u32 address;
|
|
|
|
|
|
|
|
|
|
apu->elapsed_cycles = 0;
|
|
|
|
|
mymemset(&apu->queue, 0, APUQUEUE_SIZE * sizeof(apudata_t));
|
|
|
|
|
apu->q_head = apu->q_tail = 0;
|
|
|
|
|
|
|
|
|
|
/* use to avoid bugs =) */
|
|
|
|
|
for (address = 0x00; address <= 0x13; address++)apu_regwrite(address, 0);
|
|
|
|
|
apu_regwrite(0x15, 0x00);
|
|
|
|
|
if (apu->ext)apu->ext->reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void apu_build_luts(int num_samples)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
/* lut used for enveloping and frequency sweeps */
|
|
|
|
|
for (i = 0; i < 16; i++)
|
|
|
|
|
apu->decay_lut[i] = num_samples * (i + 1);
|
|
|
|
|
|
|
|
|
|
/* used for note length, based on vblanks and size of audio buffer */
|
|
|
|
|
for (i = 0; i < 32; i++)
|
|
|
|
|
apu->vbl_lut[i] = vbl_length[i] * num_samples;
|
|
|
|
|
|
|
|
|
|
/* triangle wave channel's linear length table */
|
|
|
|
|
for (i = 0; i < 128; i++)
|
|
|
|
|
apu->trilength_lut[i] = (int) (0.25 * (i * num_samples));
|
|
|
|
|
|
|
|
|
|
#ifndef REALTIME_NOISE
|
|
|
|
|
/* generate noise samples */
|
|
|
|
|
shift_register15(noise_long_lut, APU_NOISE_32K);
|
|
|
|
|
shift_register15(noise_short_lut, APU_NOISE_93);
|
|
|
|
|
#endif /* !REALTIME_NOISE */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void apu_setparams(int sample_rate, int refresh_rate, int frag_size, int sample_bits)
|
|
|
|
|
{
|
|
|
|
|
apu->sample_rate = sample_rate;
|
|
|
|
|
apu->refresh_rate = refresh_rate;
|
|
|
|
|
apu->sample_bits = sample_bits;
|
|
|
|
|
|
|
|
|
|
apu->num_samples = sample_rate / refresh_rate;
|
2025-07-05 19:47:28 +08:00
|
|
|
|
frag_size = frag_size; /* quell warnings ƽϢ<C6BD><CFA2><EFBFBD><EFBFBD>*/
|
2025-06-27 00:32:57 +08:00
|
|
|
|
|
|
|
|
|
/* turn into fixed point! */
|
|
|
|
|
apu->cycle_rate = (int) (APU_BASEFREQ * 65536.0 / (float) sample_rate);
|
|
|
|
|
|
|
|
|
|
/* build various lookup tables for apu */
|
|
|
|
|
apu_build_luts(apu->num_samples);
|
|
|
|
|
|
|
|
|
|
if (apu->ext)
|
|
|
|
|
apu->ext->paramschanged();
|
|
|
|
|
}
|
2025-07-05 19:47:28 +08:00
|
|
|
|
/*Ӳ<><D3B2><EFBFBD><EFBFBD>ʼ<EFBFBD><CABC>ģ<EFBFBD><C4A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,<2C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>/<2F><><EFBFBD><EFBFBD> */
|
2025-06-27 00:32:57 +08:00
|
|
|
|
// _local_sample_rate = 11025; _local_sample_size = 8;
|
|
|
|
|
// apu_create(_local_sample_rate, 60, 0, _local_sample_size);
|
|
|
|
|
//void apu_create(int sample_rate, int refresh_rate, int frag_size, int sample_bits)
|
|
|
|
|
void apu_init(void)
|
|
|
|
|
{
|
|
|
|
|
int channel;
|
|
|
|
|
mymemset(apu, 0, sizeof(apu_t));
|
|
|
|
|
/* set the stupid flag to tell difference between two rectangles */
|
|
|
|
|
apu->rectangle[0].sweep_complement = TRUE;
|
|
|
|
|
apu->rectangle[1].sweep_complement = FALSE;
|
|
|
|
|
apu->ext = NULL;
|
|
|
|
|
//apu_setparams(sample_rate, refresh_rate, frag_size, sample_bits);
|
2025-07-05 19:47:28 +08:00
|
|
|
|
apu_setparams(APU_SAMPLE_RATE,60,0,16);//APU_SAMPLE_RATEΪ22050Hz //<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
2025-06-27 00:32:57 +08:00
|
|
|
|
apu_reset();
|
|
|
|
|
for(channel=0;channel<6;channel++)apu_setchan(channel,TRUE);
|
2025-07-05 19:47:28 +08:00
|
|
|
|
apu->filter_type=APU_FILTER_LOWPASS; //<2F><><EFBFBD><EFBFBD>ɸѡ<C9B8><D1A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
2025-06-27 00:32:57 +08:00
|
|
|
|
}
|
2025-07-05 19:47:28 +08:00
|
|
|
|
//apu<70><75><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
2025-06-27 00:32:57 +08:00
|
|
|
|
void apu_soundoutput(void)
|
|
|
|
|
{
|
|
|
|
|
u16 i;
|
|
|
|
|
apu_process(wave_buffers,APU_PCMBUF_SIZE);
|
2025-07-05 19:47:28 +08:00
|
|
|
|
for(i=0;i<30;i++)if(wave_buffers[i]!=wave_buffers[i+1])break;//<2F>ж<EFBFBD>ǰ30<33><30><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,<2C>Dz<EFBFBD><C7B2>Ƕ<EFBFBD><C7B6><EFBFBD><EFBFBD><EFBFBD>?
|
|
|
|
|
if(i==30&&wave_buffers[i])//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,<2C>Ҳ<EFBFBD><D2B2><EFBFBD><EFBFBD><EFBFBD>0
|
2025-06-27 00:32:57 +08:00
|
|
|
|
{
|
2025-07-05 19:47:28 +08:00
|
|
|
|
for(i=0;i<APU_PCMBUF_SIZE;i++)wave_buffers[i]=0;//<2F><><EFBFBD><EFBFBD>ͣ״̬<D7B4><CCAC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ظ<EFBFBD><D8B8><EFBFBD>Ч<EFBFBD><D0A7><EFBFBD><EFBFBD>,ֱ<><D6B1><EFBFBD><EFBFBD>Ϊ0.<2E>Ӷ<EFBFBD><D3B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
|
2025-06-27 00:32:57 +08:00
|
|
|
|
}
|
|
|
|
|
clocks=0;
|
|
|
|
|
nes_apu_fill_buffer(0,wave_buffers);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|