📄 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.4 2005/05/07 09:11:39 valtri Exp $*/#include <string.h>#include "types.h"#include "log.h"#include "nes_apu.h"#include "nes6502.h"#ifdef NSF_PLAYER#include "nsf.h"#else#include "nes.h"#include "nes_ppu.h"#include "nes_mmc.h"#include "nesinput.h"#endif /* !NSF_PLAYER */#define APU_OVERSAMPLE#define APU_VOLUME_DECAY(x) ((x) -= ((x) >> 7))/* 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_NOISEstatic 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 };#if 0 /* unused */static void apu_setcontext(apu_t *src_apu){ apu = src_apu;}#endif/*** Simple queue routines*/#define APU_QEMPTY() (apu->q_head == apu->q_tail)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];}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_NOISEINLINE 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);}#elsestatic 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/* 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_volstatic int32 apu_rectangle(rectangle_t *chan){ int32 output;#ifdef APU_OVERSAMPLE int num_times; int32 total;#endif 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 */ 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++; } if ((FALSE == chan->sweep_inc && chan->freq > chan->freq_limit) || chan->freq < APU_TO_FIXED(4)) 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 */ 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 num_times = total = 0; if (chan->fixed_envelope) output = chan->volume << 8; /* fixed volume */ else output = (chan->env_vol ^ 0x0F) << 8;#endif while (chan->phaseacc < 0) { chan->phaseacc += chan->freq; chan->adder = (chan->adder + 1) & 0x0F;#ifdef APU_OVERSAMPLE if (chan->adder < chan->duty_flip) total += output; else total -= output; num_times++;#endif }#ifdef APU_OVERSAMPLE chan->output_vol = total / num_times;#else 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 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))static int32 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 (chan->countmode == COUNTMODE_COUNT) { if (chan->linear_length > 0) chan->linear_length--; if (chan->vbl_length) chan->vbl_length--; }*/ 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)static int32 apu_noise(noise_t *chan){ int32 outvol;#if defined(APU_OVERSAMPLE) && defined(REALTIME_NOISE)#else int32 noise_bit;#endif#ifdef APU_OVERSAMPLE int num_times; int32 total;#endif 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 */ 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++; } 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 while (chan->phaseacc < 0) { chan->phaseacc += chan->freq;#ifdef REALTIME_NOISE#ifdef APU_OVERSAMPLE if (shift_register15(chan->xor_tap)) total += outvol; else total -= outvol; num_times++;#else noise_bit = shift_register15(chan->xor_tap);#endif#else 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#endif /* REALTIME_NOISE */ }#ifdef APU_OVERSAMPLE chan->output_vol = total / num_times;#else 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 return APU_NOISE_OUTPUT;}INLINE 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)static int32 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 = nes6502_getbyte(chan->address); /* steal a cycle from CPU*/ nes6502_setdma(1); 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; nes6502_irq(); } /* 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); }/* if (chan->regs[1] < 0x3F) chan->regs[1]++; chan->output_vol &= ~(0x7E << 8); chan->output_vol |= ((chan->regs[1] << 1) << 8);*/ } /* negative delta */ else { if (chan->regs[1] > 1) { chan->regs[1] -= 2; chan->output_vol -= (2 << 8); }/* if (chan->regs[1] > 0) chan->regs[1]--; chan->output_vol &= ~(0x7E << 8); chan->output_vol |= ((chan->regs[1] << 1) << 8);*/ } } } return APU_DMC_OUTPUT;}static void apu_regwrite(uint32 address, uint8 value){ int chan; ASSERT(apu); 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 = 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]; break; case APU_WRA1: case APU_WRB1:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -