📄 nes_apu.c
字号:
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 = decay_lut[(value >> 4) & 7]; apu->rectangle[chan].sweep_inc = (value & 0x08) ? TRUE : FALSE; apu->rectangle[chan].freq_limit = APU_TO_FIXED(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_TO_FIXED((((apu->rectangle[chan].regs[3] & 7) << 8) + value) + 1); break; case APU_WRA3: case APU_WRB3: chan = (address & 4) ? 1 : 0; apu->rectangle[chan].regs[3] = value;// if (apu->rectangle[chan].enabled) { apu->rectangle[chan].vbl_length = vbl_lut[value >> 3]; apu->rectangle[chan].env_vol = 0; apu->rectangle[chan].freq = APU_TO_FIXED((((value & 7) << 8) + apu->rectangle[chan].regs[2]) + 1); apu->rectangle[chan].adder = 0; } break; /* triangle */ case APU_WRC0:/* if (0 == (apu->triangle.regs[0] & 0x80)) apu->triangle.countmode = COUNTMODE_COUNT; else { if (apu->triangle.countmode == COUNTMODE_LOAD && apu->triangle.vbl_length) apu->triangle.linear_length = trilength_lut[value & 0x7F]; if (0 == (value & 0x80)) apu->triangle.countmode = COUNTMODE_COUNT; }*/ apu->triangle.regs[0] = value; apu->triangle.holdnote = (value & 0x80) ? TRUE : FALSE;// if (apu->triangle.enabled) { 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;// if (apu->triangle.enabled) apu->triangle.freq = APU_TO_FIXED((((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 scanlines -- 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) (2 * NES_SCANLINE_CYCLES / APU_FROM_FIXED(apu->cycle_rate));/* apu->triangle.linear_length = trilength_lut[apu->triangle.regs[0] & 0x7F]; if (0 == (apu->triangle.regs[0] & 0x80)) apu->triangle.countmode = COUNTMODE_COUNT; else apu->triangle.countmode = COUNTMODE_LOAD;*/// if (apu->triangle.enabled) { apu->triangle.freq = APU_TO_FIXED((((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 = APU_TO_FIXED(noise_freq[value & 0x0F]);#ifdef REALTIME_NOISE apu->noise.xor_tap = (value & 0x80) ? 0x40: 0x02;#else /* 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 break; case APU_WRD3: apu->noise.regs[2] = value;// if (apu->noise.enabled) { 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 = 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; } 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;/* apu->dmc.output_vol = (value & 0x7F) << 8; apu->dmc.regs[1] = (value & 0x7E) >> 1;*/ 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(&apu->dmc); } 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; ASSERT(apu); switch (address) { case APU_SMASK: /* seems that bit 6 denotes vblank -- return 1 for now */ value = 0x40; /* 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; //if (apu->dmc.dma_length) /* bodge for timestamp queue */ if (apu->dmc.enabled) value |= 0x10; if (apu->dmc.irq_occurred) value |= 0x80; break;#ifndef NSF_PLAYER case APU_JOY0: value = input_get(INP_JOYPAD0); break; case APU_JOY1: value = input_get(INP_ZAPPER | INP_JOYPAD1 /*| INP_ARKANOID*/ /*| INP_POWERPAD*/); break;#endif /* !NSF_PLAYER */ default: value = (address >> 8); /* heavy capacitance on data bus */ break; } return value;}void apu_write(uint32 address, uint8 value){#ifndef NSF_PLAYER static uint8 last_write;#endif /* !NSF_PLAYER */ apudata_t d; switch (address) { case 0x4015: /* bodge for timestamp queue */ apu->dmc.enabled = (value & 0x10) ? TRUE : FALSE; case 0x4000: case 0x4001: case 0x4002: case 0x4003: case 0x4004: case 0x4005: case 0x4006: case 0x4007: case 0x4008: case 0x4009: case 0x400A: case 0x400B: case 0x400C: case 0x400D: case 0x400E: case 0x400F: case 0x4010: case 0x4011: case 0x4012: case 0x4013: d.timestamp = nes6502_getcycles(FALSE); d.address = address; d.value = value; apu_enqueue(&d); break;#ifndef NSF_PLAYER case APU_OAMDMA: ppu_oamdma(address, value); break; case APU_JOY0: /* VS system VROM switching */ mmc_vsvrom(value & 4); /* see if we need to strobe them joypads */ value &= 1; if ((0 == value) && last_write) input_strobe(); last_write = value; break; case APU_JOY1: /* Some kind of IRQ control business */ break;#endif /* !NSF_PLAYER */ default: break; }}void apu_getpcmdata(void **data, int *num_samples, int *sample_bits){ ASSERT(apu); *data = apu->buffer; *num_samples = apu->num_samples; *sample_bits = apu->sample_bits;}void apu_process(void *buffer, int num_samples){ apudata_t *d; uint32 elapsed_cycles; static int32 prev_sample = 0; int32 next_sample, accum; ASSERT(apu); /* grab it, keep it local for speed */ elapsed_cycles = (uint32) apu->elapsed_cycles; /* 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 (apu->mix_enable[0]) accum += apu_rectangle(&apu->rectangle[0]); if (apu->mix_enable[1]) accum += apu_rectangle(&apu->rectangle[1]); if (apu->mix_enable[2]) accum += apu_triangle(&apu->triangle); if (apu->mix_enable[3]) accum += apu_noise(&apu->noise); if (apu->mix_enable[4]) accum += apu_dmc(&apu->dmc); if (apu->ext && apu->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; /* signed 16-bit output, unsigned 8-bit */ if (16 == apu->sample_bits) { *((int16 *) buffer) = (int16) accum; buffer = (int16 *) buffer + 1; } else { *((uint8 *) buffer) = (accum >> 8) ^ 0x80; buffer = (int8 *) buffer + 1; } } /* resync cycle counter */ apu->elapsed_cycles = nes6502_getcycles(FALSE);}/* set the filter type */void apu_setfilter(int filter_type){ ASSERT(apu); apu->filter_type = filter_type;}void apu_reset(void){ uint32 address; ASSERT(apu); apu->elapsed_cycles = 0; memset(&apu->queue, 0, APUQUEUE_SIZE * sizeof(apudata_t)); apu->q_head = 0; apu->q_tail = 0; /* use to avoid bugs =) */ for (address = 0x4000; address <= 0x4013; address++) apu_regwrite(address, 0);#ifdef NSF_PLAYER apu_regwrite(0x400C, 0x10); /* silence noise channel on NSF start */ apu_regwrite(0x4015, 0x0F);#else apu_regwrite(0x4015, 0);#endif /* NSF_PLAYER */ if (apu->ext) apu->ext->reset();}static 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] = (i * num_samples) / 4;#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 */}static void apu_setactive(apu_t *active){ ASSERT(active); apu = active;}/* Initializes emulated sound hardware, creates waveforms/voices */apu_t *apu_create(int sample_rate, int refresh_rate, int sample_bits, boolean stereo){ apu_t *temp_apu; int channel; temp_apu = malloc(sizeof(apu_t)); if (NULL == temp_apu) return NULL; temp_apu->sample_rate = sample_rate; temp_apu->refresh_rate = refresh_rate; temp_apu->sample_bits = sample_bits; temp_apu->num_samples = sample_rate / refresh_rate; /* turn into fixed point! */ temp_apu->cycle_rate = (int32) (APU_BASEFREQ * 65536.0 / (float) sample_rate); /* build various lookup tables for apu */ apu_build_luts(temp_apu->num_samples); /* set the update routine */ temp_apu->process = apu_process; temp_apu->ext = NULL; apu_setactive(temp_apu); apu_reset(); for (channel = 0; channel < 6; channel++) apu_setchan(channel, TRUE); apu_setfilter(APU_FILTER_LOWPASS); return temp_apu;}apu_t *apu_getcontext(void){ return apu;}void apu_destroy(apu_t *src_apu){ if (src_apu) { if (src_apu->ext) src_apu->ext->shutdown(); free(src_apu); }}void apu_setext(apu_t *src_apu, apuext_t *ext){ ASSERT(src_apu); src_apu->ext = ext; /* initialize it */ if (src_apu->ext) src_apu->ext->init();}/* this exists for external mixing routines */int32 apu_getcyclerate(void){ ASSERT(apu); return apu->cycle_rate;}/*** $Log: nes_apu.c,v $** Revision 1.4 2005/05/07 09:11:39 valtri** *BUGFIX*** gcc4 patches from Dams Nadé (livna.org) and Keenan Pepper.**** Revision 1.3 2004/12/12 06:55:59 athp** Code cleanups and elimination of some compiler warnings; patch courtesy of AL13N**** Revision 1.2 2003/08/25 21:51:43 f1rmb** Reduce GCC verbosity (various prototype declaration fixes). ffmpeg, wine and fft*post are untouched (fft: for now).**** Revision 1.1 2003/01/08 07:04:35 tmmm** initial import of Nosefart sources**** 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***/
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -