📄 nes_apu.c
字号:
/*
** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com)
**
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of version 2 of the GNU Library General
** Public License as published by the Free Software Foundation.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** Library General Public License for more details. To obtain a
** copy of the GNU Library General Public License, write to the Free
** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**
** Any permitted reproduction of these routines, in whole or in part,
** must bear this legend.
**
**
** nes_apu.c
**
** NES APU emulation
** $Id: nes_apu.c,v 1.5 2003/06/22 08:30:13 Rick Exp $
*/
/*
** Changes for nester:
** changes are marked with DCR
**
** removed bad #includes
** nullified ASSERT and log_printf
** added custom log_printf()
** #included stlib.h for malloc()
**
** - Darren Ranalli
*/
/*
** Changes for nester:
** support ExSounds
**
** - TAKEDA, toshiya
*/
#include <string.h>
#include <stdlib.h> //DCR
#include "types.h"
//DCR#include "log.h"
#include "nes_apu.h"
#include "nes6502.h"
/* included for nes_irq() and nes_clearframeirq() */
#ifndef NSF_PLAYER
//DCR#include "nes.h"
#endif /* !NSF_PLAYER */
// DCR
#define ASSERT(CONDITION)
#define log_printf(STR)
#ifndef APU_YANO
#define APU_OVERSAMPLE
#define APU_VOLUME_DECAY(x) ((x) -= ((x) >> 7))
#endif /* !APU_YANO */
/* pointer to active APU */
static apu_t *apu;
/* look up table madness */
static int32 decay_lut[16];
static int vbl_lut[32];
static int trilength_lut[128];
/* noise lookups for both modes */
#ifndef REALTIME_NOISE
static int8 noise_long_lut[APU_NOISE_32K];
static int8 noise_short_lut[APU_NOISE_93];
#endif /* !REALTIME_NOISE */
/* vblank length table used for rectangles, triangle, noise */
static const uint8 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
};
/* frequency limit of rectangle channels */
static const int freq_limit[8] =
{
0x3FF, 0x555, 0x666, 0x71C, 0x787, 0x7C1, 0x7E0, 0x7F0
};
/* noise frequency lookup table */
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
};
/* ratios of pos/neg pulse for rectangle waves */
static const int duty_lut[4] = { 2, 4, 8, 12 };
// for $4017:bit7 by T.Yano
static int apu_cnt_rate = 5;
void apu_setcontext(apu_t *src_apu)
{
apu = src_apu;
}
apu_t *apu_getcontext(void)
{
return apu;
}
/*
** Simple queue routines
*/
//#define APU_QEMPTY() (apu->q_head == apu->q_tail)
//#define EX_QEMPTY() (apu->ex_q_head == apu->ex_q_tail)
#define APU_QEMPTY(q) ((q)->q_head == (q)->q_tail)
#include "nes_exsound.cpp"
/*
static void apu_enqueue(apudata_t *d)
{
ASSERT(apu);
apu->queue[apu->q_head] = *d;
apu->q_head = (apu->q_head + 1) & APUQUEUE_MASK;
if (APU_QEMPTY())
log_printf("apu: queue overflow\n");
}
static apudata_t *apu_dequeue(void)
{
int loc;
ASSERT(apu);
if (APU_QEMPTY())
log_printf("apu: queue empty\n");
loc = apu->q_tail;
apu->q_tail = (apu->q_tail + 1) & APUQUEUE_MASK;
return &apu->queue[loc];
}
*/
INLINE void apu_enqueue(apuqueue_t *q, apudata_t *d)
{
apudata_t *save = &q->queue[q->q_head];
//q->queue[q->q_head] = *d;
// M$ compiler generates better code this way
save->address = d->address;
save->timestamp = d->timestamp;
save->value = d->value;
q->q_head = (q->q_head + 1) & APUQUEUE_MASK;
//if (APU_QEMPTY(q))
// log_printf("apu: queue overflow\n");
}
INLINE apudata_t *apu_dequeue(apuqueue_t *q)
{
int loc;
//if (APU_QEMPTY(q))
// log_printf("apu: queue empty\n");
loc = q->q_tail;
q->q_tail = (q->q_tail + 1) & APUQUEUE_MASK;
return &q->queue[loc];
}
/*
static void ex_enqueue(apudata_t *d)
{
ASSERT(apu);
apu->ex_queue[apu->ex_q_head] = *d;
apu->ex_q_head = (apu->ex_q_head + 1) & APUQUEUE_MASK;
if (EX_QEMPTY())
log_printf("ex_apu: queue overflow\n");
}
static apudata_t *ex_dequeue(void)
{
int loc;
ASSERT(apu);
if (EX_QEMPTY())
log_printf("ex_apu: queue empty\n");
loc = apu->ex_q_tail;
apu->ex_q_tail = (apu->ex_q_tail + 1) & APUQUEUE_MASK;
return &apu->ex_queue[loc];
}
*/
void apu_setchan(int chan, boolean enabled)
{
ASSERT(apu);
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
*/
#ifdef REALTIME_NOISE
INLINE int8 shift_register15(uint8 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 */
static void shift_register15(int8 *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)
static int32 apu_rectangle(rectangle_t *chan)
{
int32 output;
#ifdef APU_YANO
double total;
double sample_weight;
#else /* !APU_YANO */
#ifdef APU_OVERSAMPLE
int num_times;
int32 total;
#endif /* APU_OVERSAMPLE */
APU_VOLUME_DECAY(chan->output_vol);
#endif /* !APU_YANO */
if (FALSE == chan->enabled || chan->vbl_length <= 0)
return APU_RECTANGLE_OUTPUT;
/* vbl length counter */
if (FALSE == chan->holdnote)
chan->vbl_length -= apu_cnt_rate;
/* envelope decay at a rate of (env_delay + 1) / 240 secs */
#if 0
chan->env_phase -= 4 * apu_cnt_rate; /* 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++;
}
#else
// speed hacks. Rick.
{
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 * apu_cnt_rate; /* 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;
}
#endif
/* 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 * apu_cnt_rate; /* 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);
}
}
}
#ifdef APU_YANO
if (chan->fixed_envelope)
output = chan->volume << 8; /* fixed volume */
else
output = (chan->env_vol ^ 0x0F) << 8;
sample_weight = chan->phaseacc;
if ( sample_weight > apu->cycle_rate) {
sample_weight = apu->cycle_rate;
}
total = (chan->adder < chan->duty_flip)?sample_weight:-sample_weight;
chan->phaseacc -= apu->cycle_rate; /* # of cycles per sample */
while ( chan->phaseacc < 0) {
chan->phaseacc += APU_TO_FIXED(chan->freq + 1);
chan->adder = (chan->adder + 1) & 0x0F;
sample_weight = APU_TO_FIXED(chan->freq + 1);
if (chan->phaseacc > 0) {
sample_weight -= chan->phaseacc;
}
total += (chan->adder < chan->duty_flip)?sample_weight:-sample_weight;
}
chan->output_vol = (int)floor( output*total/apu->cycle_rate + 0.5);
#else /* !APU_YANO */
chan->phaseacc -= apu->cycle_rate; /* # of cycles per sample */
if (chan->phaseacc >= 0)
return APU_RECTANGLE_OUTPUT;
#ifdef APU_OVERSAMPLE
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
if (chan->adder < chan->duty_flip)
total += output;
else
total -= output;
num_times++;
#endif /* APU_OVERSAMPLE */
}
#ifdef APU_OVERSAMPLE
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 */
#endif /* !APU_YANO */
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 >> 1))
#define APU_TRIANGLE_OUTPUT ((chan->output_vol*21)>>4)
static int32 apu_triangle(triangle_t *chan)
{
#ifdef APU_YANO
static double val;
double sample_weight, total, prev_val;
#else /* !APU_YANO */
APU_VOLUME_DECAY(chan->output_vol);
#endif /* !APU_YANO */
if (FALSE == chan->enabled || chan->vbl_length <= 0)
return APU_TRIANGLE_OUTPUT;
if (chan->counter_started)
{
if (chan->linear_length > 0)
chan->linear_length -= 4 * apu_cnt_rate; /* 240/60 */
if (chan->vbl_length > 0 && FALSE == chan->holdnote)
chan->vbl_length -= apu_cnt_rate;
}
else if (FALSE == chan->holdnote && chan->write_latency)
{
if (--chan->write_latency == 0)
chan->counter_started = TRUE;
}
if (chan->linear_length <= 0 || chan->freq < APU_TO_FIXED(4)) /* inaudible */
return APU_TRIANGLE_OUTPUT;
#ifdef APU_YANO
if ( chan->ideal_triangle) {
total = 0;
sample_weight = 0;
prev_val = val;
if ( chan->adder) {
val -= (double)apu->cycle_rate/chan->freq;
} else {
val += (double)apu->cycle_rate/chan->freq;
}
while ( (val < -8) || (val >= 8)) {
if (val <-8) {
total += (prev_val + (-8))*(prev_val-(-8));
sample_weight += prev_val-(-8);
prev_val = -8;
val = -16-val;
chan->adder = 0;
}
if (val >= 8) {
total += (prev_val + 8)*(8-prev_val);
sample_weight += 8-prev_val;
prev_val = 8;
val = 16-val;
chan->adder = 1;
}
}
if ( chan->adder) {
total += (prev_val + val)*(prev_val-val);
sample_weight += prev_val-val;
} else {
total += (prev_val + val)*(val-prev_val);
sample_weight += val-prev_val;
}
total /= sample_weight;
chan->output_vol = (int)floor(total*256+0.5);
} else { /* !ideal_triangle */
sample_weight = chan->phaseacc;
if ( sample_weight > apu->cycle_rate) {
sample_weight = apu->cycle_rate;
}
total = (((chan->adder & 0x10)?0x1f:0)^chan->adder)*sample_weight;
chan->phaseacc -= apu->cycle_rate; /* # of cycles per sample */
while ( chan->phaseacc < 0) {
chan->phaseacc += chan->freq;
chan->adder = (chan->adder + 1) & 0x1F;
sample_weight = chan->freq;
if (chan->phaseacc > 0) {
sample_weight -= chan->phaseacc;
}
total += (((chan->adder & 0x10)?0x1f:0)^chan->adder)*sample_weight;
}
chan->output_vol = (int)floor( total*512/apu->cycle_rate + 0.5);
}
#else /* !APU_YANO */
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);
}
#endif /* !APU_YANO */
return APU_TRIANGLE_OUTPUT;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -