MCUME/MCUME_teensy/teensynofrendo/nes_apu.c

1196 wiersze
31 KiB
C
Executable File

/*
** 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.2 2001/04/27 14:37:11 neil Exp $
*/
#include <string.h>
#include "noftypes.h"
#include "log.h"
#include "nes_apu.h"
#include "nes6502.h"
#define APU_OVERSAMPLE
#define APU_VOLUME_DECAY(x) ((x) -= ((x) >> 7))
/* the following seem to be the correct (empirically determined)
** relative volumes between the sound channels
*/
#define APU_RECTANGLE_OUTPUT(channel) (apu.rectangle[channel].output_vol)
#define APU_TRIANGLE_OUTPUT (apu.triangle.output_vol + (apu.triangle.output_vol >> 2))
#define APU_NOISE_OUTPUT ((apu.noise.output_vol + apu.noise.output_vol + apu.noise.output_vol) >> 2)
#define APU_DMC_OUTPUT ((apu.dmc.output_vol + apu.dmc.output_vol + apu.dmc.output_vol) >> 2)
/* 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_flip[4] = { 2, 4, 8, 12 };
void apu_setcontext(apu_t *src_apu)
{
apu = *src_apu;
}
void apu_getcontext(apu_t *dest_apu)
{
*dest_apu = apu;
}
void apu_setchan(int chan, bool enabled)
{
if (enabled)
apu.mix_enable |= (1 << chan);
else
apu.mix_enable &= ~(1 << chan);
}
/* 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
*/
#ifdef APU_OVERSAMPLE
#define APU_MAKE_RECTANGLE(ch) \
static int32 apu_rectangle_##ch(void) \
{ \
int32 output, total; \
int num_times; \
\
APU_VOLUME_DECAY(apu.rectangle[ch].output_vol); \
\
if (false == apu.rectangle[ch].enabled || 0 == apu.rectangle[ch].vbl_length) \
return APU_RECTANGLE_OUTPUT(ch); \
\
/* vbl length counter */ \
if (false == apu.rectangle[ch].holdnote) \
apu.rectangle[ch].vbl_length--; \
\
/* envelope decay at a rate of (env_delay + 1) / 240 secs */ \
apu.rectangle[ch].env_phase -= 4; /* 240/60 */ \
while (apu.rectangle[ch].env_phase < 0) \
{ \
apu.rectangle[ch].env_phase += apu.rectangle[ch].env_delay; \
\
if (apu.rectangle[ch].holdnote) \
apu.rectangle[ch].env_vol = (apu.rectangle[ch].env_vol + 1) & 0x0F; \
else if (apu.rectangle[ch].env_vol < 0x0F) \
apu.rectangle[ch].env_vol++; \
} \
\
/* TODO: find true relation of freq_limit to register values */ \
if (apu.rectangle[ch].freq < 8 \
|| (false == apu.rectangle[ch].sweep_inc \
&& apu.rectangle[ch].freq > apu.rectangle[ch].freq_limit)) \
return APU_RECTANGLE_OUTPUT(ch); \
\
/* frequency sweeping at a rate of (sweep_delay + 1) / 120 secs */ \
if (apu.rectangle[ch].sweep_on && apu.rectangle[ch].sweep_shifts) \
{ \
apu.rectangle[ch].sweep_phase -= 2; /* 120/60 */ \
while (apu.rectangle[ch].sweep_phase < 0) \
{ \
apu.rectangle[ch].sweep_phase += apu.rectangle[ch].sweep_delay; \
\
if (apu.rectangle[ch].sweep_inc) /* ramp up */ \
{ \
if (0 == ch) \
apu.rectangle[ch].freq += ~(apu.rectangle[ch].freq >> apu.rectangle[ch].sweep_shifts); \
else \
apu.rectangle[ch].freq -= (apu.rectangle[ch].freq >> apu.rectangle[ch].sweep_shifts); \
} \
else /* ramp down */ \
{ \
apu.rectangle[ch].freq += (apu.rectangle[ch].freq >> apu.rectangle[ch].sweep_shifts); \
} \
} \
} \
\
apu.rectangle[ch].accum -= apu.cycle_rate; \
if (apu.rectangle[ch].accum >= 0) \
return APU_RECTANGLE_OUTPUT(ch); \
\
if (apu.rectangle[ch].fixed_envelope) \
output = apu.rectangle[ch].volume << 8; /* fixed volume */ \
else \
output = (apu.rectangle[ch].env_vol ^ 0x0F) << 8; \
\
num_times = total = 0; \
\
while (apu.rectangle[ch].accum < 0) \
{ \
apu.rectangle[ch].accum += apu.rectangle[ch].freq + 1; \
apu.rectangle[ch].adder = (apu.rectangle[ch].adder + 1) & 0x0F; \
\
if (apu.rectangle[ch].adder < apu.rectangle[ch].duty_flip) \
total += output; \
else \
total -= output; \
\
num_times++; \
} \
\
apu.rectangle[ch].output_vol = total / num_times; \
return APU_RECTANGLE_OUTPUT(ch); \
}
#else /* !APU_OVERSAMPLE */
#define APU_MAKE_RECTANGLE(ch) \
static int32 apu_rectangle_##ch(void) \
{ \
int32 output; \
\
APU_VOLUME_DECAY(apu.rectangle[ch].output_vol); \
\
if (false == apu.rectangle[ch].enabled || 0 == apu.rectangle[ch].vbl_length) \
return APU_RECTANGLE_OUTPUT(ch); \
\
/* vbl length counter */ \
if (false == apu.rectangle[ch].holdnote) \
apu.rectangle[ch].vbl_length--; \
\
/* envelope decay at a rate of (env_delay + 1) / 240 secs */ \
apu.rectangle[ch].env_phase -= 4; /* 240/60 */ \
while (apu.rectangle[ch].env_phase < 0) \
{ \
apu.rectangle[ch].env_phase += apu.rectangle[ch].env_delay; \
\
if (apu.rectangle[ch].holdnote) \
apu.rectangle[ch].env_vol = (apu.rectangle[ch].env_vol + 1) & 0x0F; \
else if (apu.rectangle[ch].env_vol < 0x0F) \
apu.rectangle[ch].env_vol++; \
} \
\
/* TODO: find true relation of freq_limit to register values */ \
if (apu.rectangle[ch].freq < 8 || (false == apu.rectangle[ch].sweep_inc && apu.rectangle[ch].freq > apu.rectangle[ch].freq_limit)) \
return APU_RECTANGLE_OUTPUT(ch); \
\
/* frequency sweeping at a rate of (sweep_delay + 1) / 120 secs */ \
if (apu.rectangle[ch].sweep_on && apu.rectangle[ch].sweep_shifts) \
{ \
apu.rectangle[ch].sweep_phase -= 2; /* 120/60 */ \
while (apu.rectangle[ch].sweep_phase < 0) \
{ \
apu.rectangle[ch].sweep_phase += apu.rectangle[ch].sweep_delay; \
\
if (apu.rectangle[ch].sweep_inc) /* ramp up */ \
{ \
if (0 == ch) \
apu.rectangle[ch].freq += ~(apu.rectangle[ch].freq >> apu.rectangle[ch].sweep_shifts); \
else \
apu.rectangle[ch].freq -= (apu.rectangle[ch].freq >> apu.rectangle[ch].sweep_shifts); \
} \
else /* ramp down */ \
{ \
apu.rectangle[ch].freq += (apu.rectangle[ch].freq >> apu.rectangle[ch].sweep_shifts); \
} \
} \
} \
\
apu.rectangle[ch].accum -= apu.cycle_rate; \
if (apu.rectangle[ch].accum >= 0) \
return APU_RECTANGLE_OUTPUT(ch); \
\
while (apu.rectangle[ch].accum < 0) \
{ \
apu.rectangle[ch].accum += (apu.rectangle[ch].freq + 1); \
apu.rectangle[ch].adder = (apu.rectangle[ch].adder + 1) & 0x0F; \
} \
\
if (apu.rectangle[ch].fixed_envelope) \
output = apu.rectangle[ch].volume << 8; /* fixed volume */ \
else \
output = (apu.rectangle[ch].env_vol ^ 0x0F) << 8; \
\
if (0 == apu.rectangle[ch].adder) \
apu.rectangle[ch].output_vol = output; \
else if (apu.rectangle[ch].adder == apu.rectangle[ch].duty_flip) \
apu.rectangle[ch].output_vol = -output; \
\
return APU_RECTANGLE_OUTPUT(ch); \
}
#endif /* !APU_OVERSAMPLE */
/* generate the functions */
APU_MAKE_RECTANGLE(0)
APU_MAKE_RECTANGLE(1)
/* 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
*/
static int32 apu_triangle(void)
{
APU_VOLUME_DECAY(apu.triangle.output_vol);
if (false == apu.triangle.enabled || 0 == apu.triangle.vbl_length)
return APU_TRIANGLE_OUTPUT;
if (apu.triangle.counter_started)
{
if (apu.triangle.linear_length > 0)
apu.triangle.linear_length--;
if (apu.triangle.vbl_length && false == apu.triangle.holdnote)
apu.triangle.vbl_length--;
}
else if (false == apu.triangle.holdnote && apu.triangle.write_latency)
{
if (--apu.triangle.write_latency == 0)
apu.triangle.counter_started = true;
}
if (0 == apu.triangle.linear_length || apu.triangle.freq < 4) /* inaudible */
return APU_TRIANGLE_OUTPUT;
apu.triangle.accum -= apu.cycle_rate; \
while (apu.triangle.accum < 0)
{
apu.triangle.accum += apu.triangle.freq;
apu.triangle.adder = (apu.triangle.adder + 1) & 0x1F;
if (apu.triangle.adder & 0x10)
apu.triangle.output_vol -= (2 << 8);
else
apu.triangle.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
*/
/* TODO: AAAAAAAAAAAAAAAAAAAAAAAA! #ifdef MADNESS! */
static int32 apu_noise(void)
{
int32 outvol;
#if defined(APU_OVERSAMPLE) && defined(REALTIME_NOISE)
#else /* !(APU_OVERSAMPLE && REALTIME_NOISE) */
int32 noise_bit;
#endif /* !(APU_OVERSAMPLE && REALTIME_NOISE) */
#ifdef APU_OVERSAMPLE
int num_times;
int32 total;
#endif /* APU_OVERSAMPLE */
APU_VOLUME_DECAY(apu.noise.output_vol);
if (false == apu.noise.enabled || 0 == apu.noise.vbl_length)
return APU_NOISE_OUTPUT;
/* vbl length counter */
if (false == apu.noise.holdnote)
apu.noise.vbl_length--;
/* envelope decay at a rate of (env_delay + 1) / 240 secs */
apu.noise.env_phase -= 4; /* 240/60 */
while (apu.noise.env_phase < 0)
{
apu.noise.env_phase += apu.noise.env_delay;
if (apu.noise.holdnote)
apu.noise.env_vol = (apu.noise.env_vol + 1) & 0x0F;
else if (apu.noise.env_vol < 0x0F)
apu.noise.env_vol++;
}
apu.noise.accum -= apu.cycle_rate;
if (apu.noise.accum >= 0)
return APU_NOISE_OUTPUT;
#ifdef APU_OVERSAMPLE
if (apu.noise.fixed_envelope)
outvol = apu.noise.volume << 8; /* fixed volume */
else
outvol = (apu.noise.env_vol ^ 0x0F) << 8;
num_times = total = 0;
#endif /* APU_OVERSAMPLE */
while (apu.noise.accum < 0)
{
apu.noise.accum += apu.noise.freq;
#ifdef REALTIME_NOISE
#ifdef APU_OVERSAMPLE
if (shift_register15(apu.noise.xor_tap))
total += outvol;
else
total -= outvol;
num_times++;
#else /* !APU_OVERSAMPLE */
noise_bit = shift_register15(apu.noise.xor_tap);
#endif /* !APU_OVERSAMPLE */
#else /* !REALTIME_NOISE */
apu.noise.cur_pos++;
if (apu.noise.short_sample)
{
if (APU_NOISE_93 == apu.noise.cur_pos)
apu.noise.cur_pos = 0;
}
else
{
if (APU_NOISE_32K == apu.noise.cur_pos)
apu.noise.cur_pos = 0;
}
#ifdef APU_OVERSAMPLE
if (apu.noise.short_sample)
noise_bit = noise_short_lut[apu.noise.cur_pos];
else
noise_bit = noise_long_lut[apu.noise.cur_pos];
if (noise_bit)
total += outvol;
else
total -= outvol;
num_times++;
#endif /* APU_OVERSAMPLE */
#endif /* !REALTIME_NOISE */
}
#ifdef APU_OVERSAMPLE
apu.noise.output_vol = total / num_times;
#else /* !APU_OVERSAMPLE */
if (apu.noise.fixed_envelope)
outvol = apu.noise.volume << 8; /* fixed volume */
else
outvol = (apu.noise.env_vol ^ 0x0F) << 8;
#ifndef REALTIME_NOISE
if (apu.noise.short_sample)
noise_bit = noise_short_lut[apu.noise.cur_pos];
else
noise_bit = noise_long_lut[apu.noise.cur_pos];
#endif /* !REALTIME_NOISE */
if (noise_bit)
apu.noise.output_vol = outvol;
else
apu.noise.output_vol = -outvol;
#endif /* !APU_OVERSAMPLE */
return APU_NOISE_OUTPUT;
}
INLINE void apu_dmcreload(void)
{
apu.dmc.address = apu.dmc.cached_addr;
apu.dmc.dma_length = apu.dmc.cached_dmalength;
apu.dmc.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
*/
static int32 apu_dmc(void)
{
int delta_bit;
APU_VOLUME_DECAY(apu.dmc.output_vol);
/* only process when channel is alive */
if (apu.dmc.dma_length)
{
apu.dmc.accum -= apu.cycle_rate;
while (apu.dmc.accum < 0)
{
apu.dmc.accum += apu.dmc.freq;
delta_bit = (apu.dmc.dma_length & 7) ^ 7;
if (7 == delta_bit)
{
apu.dmc.cur_byte = nes6502_getbyte(apu.dmc.address);
/* steal a cycle from CPU*/
nes6502_burn(1);
/* prevent wraparound */
if (0xFFFF == apu.dmc.address)
apu.dmc.address = 0x8000;
else
apu.dmc.address++;
}
if (--apu.dmc.dma_length == 0)
{
/* if loop bit set, we're cool to retrigger sample */
if (apu.dmc.looping)
{
apu_dmcreload();
}
else
{
/* check to see if we should generate an irq */
if (apu.dmc.irq_gen)
{
apu.dmc.irq_occurred = true;
if (apu.irq_callback)
apu.irq_callback();
}
/* bodge for timestamp queue */
apu.dmc.enabled = false;
break;
}
}
/* positive delta */
if (apu.dmc.cur_byte & (1 << delta_bit))
{
if (apu.dmc.regs[1] < 0x7D)
{
apu.dmc.regs[1] += 2;
apu.dmc.output_vol += (2 << 8);
}
}
/* negative delta */
else
{
if (apu.dmc.regs[1] > 1)
{
apu.dmc.regs[1] -= 2;
apu.dmc.output_vol -= (2 << 8);
}
}
}
}
return APU_DMC_OUTPUT;
}
void apu_write(uint32 address, uint8 value)
{
int chan;
switch (address)
{
/* rectangles */
case APU_WRA0:
case APU_WRB0:
chan = (address & 4) >> 2;
apu.rectangle[chan].regs[0] = value;
apu.rectangle[chan].volume = value & 0x0F;
apu.rectangle[chan].env_delay = 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_flip[value >> 6];
break;
case APU_WRA1:
case APU_WRB1:
chan = (address & 4) >> 2;
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 = decay_lut[(value >> 4) & 7];
apu.rectangle[chan].sweep_inc = (value & 0x08) ? true : false;
apu.rectangle[chan].freq_limit = freq_limit[value & 7];
break;
case APU_WRA2:
case APU_WRB2:
chan = (address & 4) >> 2;
apu.rectangle[chan].regs[2] = value;
apu.rectangle[chan].freq = (apu.rectangle[chan].freq & ~0xFF) | value;
break;
case APU_WRA3:
case APU_WRB3:
chan = (address & 4) >> 2;
apu.rectangle[chan].regs[3] = value;
apu.rectangle[chan].vbl_length = 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;
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 = trilength_lut[value & 0x7F];
break;
case APU_WRC2:
apu.triangle.regs[1] = value;
apu.triangle.freq = (((apu.triangle.regs[2] & 7) << 8) + value) + 1;
break;
case APU_WRC3:
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
*/
apu.triangle.write_latency = (int) (228 / apu.cycle_rate);
apu.triangle.freq = (((value & 7) << 8) + apu.triangle.regs[1]) + 1;
apu.triangle.vbl_length = vbl_lut[value >> 3];
apu.triangle.counter_started = false;
apu.triangle.linear_length = trilength_lut[apu.triangle.regs[0] & 0x7F];
break;
/* noise */
case APU_WRD0:
apu.noise.regs[0] = value;
apu.noise.env_delay = decay_lut[value & 0x0F];
apu.noise.holdnote = (value & 0x20) ? true : false;
apu.noise.fixed_envelope = (value & 0x10) ? true : false;
apu.noise.volume = value & 0x0F;
break;
case APU_WRD2:
apu.noise.regs[1] = value;
apu.noise.freq = 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 */
break;
case APU_WRD3:
apu.noise.regs[2] = value;
apu.noise.vbl_length = vbl_lut[value >> 3];
apu.noise.env_vol = 0; /* reset envelope */
break;
/* DMC */
case APU_WRE0:
apu.dmc.regs[0] = value;
apu.dmc.freq = 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;
}
break;
case APU_WRE1: /* 7-bit DAC */
/* 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;
break;
case APU_WRE2:
apu.dmc.regs[2] = value;
apu.dmc.cached_addr = 0xC000 + (uint16) (value << 6);
break;
case APU_WRE3:
apu.dmc.regs[3] = value;
apu.dmc.cached_dmalength = ((value << 4) + 1) << 3;
break;
case APU_SMASK:
/* 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();
}
else
{
apu.dmc.dma_length = 0;
}
apu.dmc.irq_occurred = false;
break;
/* unused, but they get hit in some mem-clear loops */
case 0x4009:
case 0x400D:
break;
default:
break;
}
}
/* Read from $4000-$4017 */
uint8 apu_read(uint32 address)
{
uint8 value;
switch (address)
{
case APU_SMASK:
value = 0;
/* 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;
if (apu.irqclear_callback)
value |= apu.irqclear_callback();
break;
default:
value = (address >> 8); /* heavy capacitance on data bus */
break;
}
return value;
}
#define CLIP_OUTPUT16(out) \
{ \
/*out <<= 1;*/ \
if (out > 0x7FFF) \
out = 0x7FFF; \
else if (out < -0x8000) \
out = -0x8000; \
}
void apu_process(void *buffer, int num_samples)
{
static int32 prev_sample = 0;
int16 *buf16;
uint8 *buf8;
if (NULL != buffer)
{
/* bleh */
apu.buffer = buffer;
buf16 = (int16 *) buffer;
buf8 = (uint8 *) buffer;
while (num_samples--)
{
int32 next_sample, accum = 0;
if (apu.mix_enable & 0x01)
accum += apu_rectangle_0();
if (apu.mix_enable & 0x02)
accum += apu_rectangle_1();
if (apu.mix_enable & 0x04)
accum += apu_triangle();
if (apu.mix_enable & 0x08)
accum += apu_noise();
if (apu.mix_enable & 0x10)
accum += apu_dmc();
if (apu.ext && (apu.mix_enable & 0x20))
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;
}
/* do clipping */
CLIP_OUTPUT16(accum);
/* signed 16-bit output, unsigned 8-bit */
if (16 == apu.sample_bits)
*buf16++ = (int16) accum;
else
*buf8++ = (accum >> 8) ^ 0x80;
}
}
}
/* set the filter type */
void apu_setfilter(int filter_type)
{
apu.filter_type = filter_type;
}
void apu_reset(void)
{
uint32 address;
/* initialize all channel members */
for (address = 0x4000; address <= 0x4013; address++)
apu_write(address, 0);
apu_write(0x4015, 0);
if (apu.ext && NULL != apu.ext->reset)
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++)
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++)
vbl_lut[i] = vbl_length[i] * num_samples;
/* triangle wave channel's linear length table */
for (i = 0; i < 128; i++)
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(double base_freq, int sample_rate, int refresh_rate, 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;
if (0 == base_freq)
apu.base_freq = APU_BASEFREQ;
else
apu.base_freq = base_freq;
apu.cycle_rate = (float) (apu.base_freq / sample_rate);
/* build various lookup tables for apu */
apu_build_luts(apu.num_samples);
apu_reset();
}
/* Initializes emulated sound hardware, creates waveforms/voices */
apu_t *apu_create(double base_freq, int sample_rate, int refresh_rate, int sample_bits)
{
apu_t *temp_apu;
int channel;
temp_apu = malloc(sizeof(apu_t));
if (NULL == temp_apu)
return NULL;
memset(temp_apu, 0, sizeof(apu_t));
/* set the update routine */
temp_apu->process = apu_process;
temp_apu->ext = NULL;
/* clear the callbacks */
temp_apu->irq_callback = NULL;
temp_apu->irqclear_callback = NULL;
apu_setcontext(temp_apu);
apu_setparams(base_freq, sample_rate, refresh_rate, sample_bits);
for (channel = 0; channel < 6; channel++)
apu_setchan(channel, true);
apu_setfilter(APU_FILTER_WEIGHTED);
apu_getcontext(temp_apu);
return temp_apu;
}
void apu_destroy(apu_t **src_apu)
{
if (*src_apu)
{
if ((*src_apu)->ext && NULL != (*src_apu)->ext->shutdown)
(*src_apu)->ext->shutdown();
free(*src_apu);
*src_apu = NULL;
}
}
void apu_setext(apu_t *src_apu, apuext_t *ext)
{
ASSERT(src_apu);
src_apu->ext = ext;
/* initialize it */
if (src_apu->ext && NULL != src_apu->ext->init)
src_apu->ext->init();
}
/*
** $Log: nes_apu.c,v $
** Revision 1.2 2001/04/27 14:37:11 neil
** wheeee
**
** Revision 1.1 2001/04/27 12:54:40 neil
** blah
**
** Revision 1.1.1.1 2001/04/27 07:03:54 neil
** initial
**
** Revision 1.6 2000/12/08 02:36:14 matt
** bye bye apu queue (for now)
**
** Revision 1.5 2000/11/27 19:33:53 matt
** no special treatment for nsf
**
** Revision 1.4 2000/11/25 20:29:17 matt
** weighted filter is now default
**
** Revision 1.3 2000/11/21 13:28:19 matt
** take care to zero allocated mem
**
** Revision 1.2 2000/10/28 15:20:59 matt
** irq callbacks in nes_apu
**
** Revision 1.1 2000/10/24 12:19:59 matt
** changed directory structure
**
** Revision 1.44 2000/10/23 17:53:06 matt
** set ptr to NULL after freeing
**
** Revision 1.43 2000/10/17 11:56:42 matt
** selectable apu base frequency
**
** Revision 1.42 2000/10/13 12:16:01 matt
** macro-ized the stuff that should be removed
**
** Revision 1.41 2000/10/10 13:58:18 matt
** stroustrup squeezing his way in the door
**
** Revision 1.40 2000/10/03 11:56:20 matt
** better support for optional sound ext routines
**
** Revision 1.39 2000/09/27 12:26:03 matt
** changed sound accumulators back to floats
**
** Revision 1.38 2000/09/18 02:12:55 matt
** more optimizations
**
** Revision 1.37 2000/09/15 13:38:40 matt
** changes for optimized apu core
**
** Revision 1.36 2000/09/15 04:58:07 matt
** simplifying and optimizing APU core
**
** Revision 1.35 2000/09/07 21:57:14 matt
** api change
**
** Revision 1.34 2000/08/16 05:01:01 matt
** small buglet fixed
**
** Revision 1.33 2000/08/15 12:38:04 matt
** removed debug output
**
** Revision 1.32 2000/08/15 12:36:51 matt
** calling apu_process with buffer=NULL causes silent emulation of APU
**
** Revision 1.31 2000/08/11 02:27:21 matt
** general cleanups, plus apu_setparams routine
**
** Revision 1.30 2000/07/31 04:32:52 matt
** fragsize problem fixed, perhaps
**
** Revision 1.29 2000/07/30 04:32:59 matt
** no more apu_getcyclerate hack
**
** Revision 1.28 2000/07/28 03:15:46 matt
** accuracy changes for rectangle frequency sweeps
**
** Revision 1.27 2000/07/27 02:49:50 matt
** eccentricity in sweeping hardware now emulated correctly
**
** Revision 1.26 2000/07/25 02:25:14 matt
** safer apu_destroy
**
** Revision 1.25 2000/07/23 15:10:54 matt
** hacks for win32
**
** Revision 1.24 2000/07/17 01:52:31 matt
** made sure last line of all source files is a newline
**
** Revision 1.23 2000/07/10 19:24:55 matt
** irqs are not supported in NSF playing mode
**
** Revision 1.22 2000/07/10 13:54:32 matt
** using generic nes_irq() now
**
** Revision 1.21 2000/07/10 05:29:34 matt
** moved joypad/oam dma from apu to ppu
**
** Revision 1.20 2000/07/09 03:49:31 matt
** apu irqs now draw an irq line (bleh)
**
** Revision 1.19 2000/07/04 04:53:26 matt
** minor changes, sound amplification
**
** Revision 1.18 2000/07/03 02:18:53 matt
** much better external module exporting
**
** Revision 1.17 2000/06/26 11:01:55 matt
** made triangle a tad quieter
**
** Revision 1.16 2000/06/26 05:10:33 matt
** fixed cycle rate generation accuracy
**
** Revision 1.15 2000/06/26 05:00:37 matt
** cleanups
**
** Revision 1.14 2000/06/23 11:06:24 matt
** more faithful mixing of channels
**
** Revision 1.13 2000/06/23 03:29:27 matt
** cleaned up external sound inteface
**
** Revision 1.12 2000/06/20 00:08:39 matt
** bugfix to rectangle wave
**
** Revision 1.11 2000/06/13 13:48:58 matt
** fixed triangle write latency for fixed point apu cycle rate
**
** Revision 1.10 2000/06/12 01:14:36 matt
** minor change to clipping extents
**
** Revision 1.9 2000/06/09 20:00:56 matt
** fixed noise hiccup in NSF player mode
**
** Revision 1.8 2000/06/09 16:49:02 matt
** removed all floating point from sound generation
**
** Revision 1.7 2000/06/09 15:12:28 matt
** initial revision
**
*/