// license:GPL-2.0+ // copyright-holders:Matthew Conte /***************************************************************************** MAME/MESS NES APU CORE Based on the Nofrendo/Nosefart NES RP2A03 sound emulation core written by Matthew Conte (matt@conte.com) and redesigned for use in MAME/MESS by Who Wants to Know? (wwtk@mail.com) This core is written with the advise and consent of Matthew Conte and is released under the GNU Public License. timing notes: master = 21477270 2A03 clock = master/12 sequencer = master/89490 or CPU/7457 ***************************************************************************** NES_APU.CPP Actual NES APU interface. LAST MODIFIED 02/29/2004 - Based on Matthew Conte's Nofrendo/Nosefart core and redesigned to use MAME system calls and to enable multiple APUs. Sound at this point should be just about 100% accurate, though I cannot tell for certain as yet. ***************************************************************************** BUGFIXES: - Various bugs concerning the DPCM channel fixed. (Oliver Achten) - Fixed $4015 read behaviour. (Oliver Achten) *****************************************************************************/ #include "emu.h" #include "nes_apu.h" DEFINE_DEVICE_TYPE(NES_APU, nesapu_device, "nesapu", "RP2A0X APU") DEFINE_DEVICE_TYPE(APU_2A03, apu2a03_device, "apu2a03", "RP2A03 APU") nesapu_device::nesapu_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock) : device_t(mconfig, type, tag, owner, clock) , device_sound_interface(mconfig, *this) , m_is_pal(0) , m_samps_per_sync(0) , m_stream(nullptr) , m_irq_handler(*this) , m_mem_read_cb(*this, 0x00) , m_frame_timer(nullptr) { } nesapu_device::nesapu_device(const machine_config& mconfig, const char* tag, device_t* owner, u32 clock) : nesapu_device(mconfig, NES_APU, tag, owner, clock) { } apu2a03_device::apu2a03_device(const machine_config& mconfig, const char* tag, device_t* owner, u32 clock) : nesapu_device(mconfig, APU_2A03, tag, owner, clock) { } void nesapu_device::device_reset() { write(0x15, 0x00); m_APU.tri.adder = 0; if (m_APU.frame_irq_enabled) m_frame_timer->adjust(m_frame_period, 0, m_frame_period); } void nesapu_device::device_clock_changed() { calculate_rates(); m_is_pal = m_clock == PAL_APU_CLOCK; } void nesapu_device::calculate_rates() { m_samps_per_sync = m_frame_clocks / 4; // FIXME: tables are 4-step mode ONLY // initialize sample times in terms of vsyncs for (int i = 0; i < SYNCS_MAX1; i++) { m_vbl_times[i] = vbl_length[i] * m_samps_per_sync / 2; m_sync_times1[i] = m_samps_per_sync * (i + 1); } for (int i = 0; i < SYNCS_MAX2; i++) m_sync_times2[i] = (m_samps_per_sync * i) >> 2; int rate = clock() / 4; if (m_stream != nullptr) m_stream->set_sample_rate(rate); else m_stream = stream_alloc(0, 1, rate); } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void nesapu_device::device_start() { m_frame_timer = timer_alloc(FUNC(nesapu_device::frame_timer_cb), this); m_frame_clocks = m_is_pal ? 33254 : 29830; m_frame_period = clocks_to_attotime(m_frame_clocks); m_APU.step_mode = 4; m_APU.frame_irq_enabled = true; calculate_rates(); // calculate mixer output /* pulse channel output: 95.88 ----------------------- 8128 ----------------- + 100 pulse 1 + pulse 2 */ for (int i = 0; i < 31; i++) { sound_stream::sample_t pulse_out = (i == 0) ? 0.0 : 95.88 / ((8128.0 / i) + 100.0); m_square_lut[i] = pulse_out; } /* triangle, noise, DMC channel output: 159.79 ------------------------------- 1 ------------------------- + 100 triangle noise dmc -------- + ----- + ----- 8227 12241 22638 */ for (int t = 0; t < 16; t++) { for (int n = 0; n < 16; n++) { for (int d = 0; d < 128; d++) { sound_stream::sample_t tnd_out = (t / 8227.0) + (n / 12241.0) + (d / 22638.0); tnd_out = (tnd_out == 0.0) ? 0.0 : 159.79 / ((1.0 / tnd_out) + 100.0); m_tnd_lut[t][n][d] = tnd_out; } } } /* register for save */ for (int i = 0; i < 2; i++) { save_item(NAME(m_APU.squ[i].regs), i); save_item(NAME(m_APU.squ[i].vbl_length), i); save_item(NAME(m_APU.squ[i].freq), i); save_item(NAME(m_APU.squ[i].phaseacc), i); save_item(NAME(m_APU.squ[i].env_phase), i); save_item(NAME(m_APU.squ[i].sweep_phase), i); save_item(NAME(m_APU.squ[i].adder), i); save_item(NAME(m_APU.squ[i].env_vol), i); save_item(NAME(m_APU.squ[i].enabled), i); save_item(NAME(m_APU.squ[i].output), i); } save_item(NAME(m_APU.tri.regs)); save_item(NAME(m_APU.tri.linear_length)); save_item(NAME(m_APU.tri.linear_reload)); save_item(NAME(m_APU.tri.vbl_length)); save_item(NAME(m_APU.tri.write_latency)); save_item(NAME(m_APU.tri.phaseacc)); save_item(NAME(m_APU.tri.adder)); save_item(NAME(m_APU.tri.counter_started)); save_item(NAME(m_APU.tri.enabled)); save_item(NAME(m_APU.tri.output)); save_item(NAME(m_APU.noi.regs)); save_item(NAME(m_APU.noi.lfsr)); save_item(NAME(m_APU.noi.vbl_length)); save_item(NAME(m_APU.noi.phaseacc)); save_item(NAME(m_APU.noi.env_phase)); save_item(NAME(m_APU.noi.env_vol)); save_item(NAME(m_APU.noi.enabled)); save_item(NAME(m_APU.noi.output)); save_item(NAME(m_APU.dpcm.regs)); save_item(NAME(m_APU.dpcm.address)); save_item(NAME(m_APU.dpcm.length)); save_item(NAME(m_APU.dpcm.bits_left)); save_item(NAME(m_APU.dpcm.phaseacc)); save_item(NAME(m_APU.dpcm.cur_byte)); save_item(NAME(m_APU.dpcm.enabled)); save_item(NAME(m_APU.dpcm.irq_occurred)); save_item(NAME(m_APU.dpcm.vol)); save_item(NAME(m_APU.dpcm.output)); save_item(NAME(m_APU.step_mode)); save_item(NAME(m_APU.frame_irq_enabled)); save_item(NAME(m_APU.frame_irq_occurred)); } TIMER_CALLBACK_MEMBER(nesapu_device::frame_timer_cb) { m_stream->update(); if (m_APU.step_mode == 4 && m_APU.frame_irq_enabled) { m_APU.frame_irq_occurred = true; m_irq_handler(true); } } /* TODO: sound channels should *ALL* have DC volume decay */ /* OUTPUT SQUARE WAVE SAMPLE (VALUES FROM 0 to +15) */ void nesapu_device::apu_square(apu_t::square_t *chan) { int env_delay; int sweep_delay; /* 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 */ if (!chan->enabled) { chan->output = 0; return; } /* enveloping */ env_delay = m_sync_times1[chan->regs[0] & 0x0f]; /* decay is at a rate of (env_regs + 1) / 240 secs */ chan->env_phase -= 4; while (chan->env_phase < 0) { chan->env_phase += env_delay; if (chan->regs[0] & 0x20) chan->env_vol = (chan->env_vol + 1) & 15; else if (chan->env_vol < 15) chan->env_vol++; } /* vbl length counter */ if (chan->vbl_length > 0 && !(chan->regs[0] & 0x20)) chan->vbl_length--; if (!chan->vbl_length) { chan->output = 0; return; } /* freqsweeps */ if ((chan->regs[1] & 0x80) && (chan->regs[1] & 7)) { sweep_delay = m_sync_times1[(chan->regs[1] >> 4) & 7]; chan->sweep_phase -= 2; while (chan->sweep_phase < 0) { chan->sweep_phase += sweep_delay; if (chan->regs[1] & 8) chan->freq -= chan->freq >> (chan->regs[1] & 7); else chan->freq += chan->freq >> (chan->regs[1] & 7); } } if ((!(chan->regs[1] & 8) && (chan->freq >> 16) > freq_limit[chan->regs[1] & 7]) || (chan->freq >> 16) < 4) { chan->output = 0; return; } chan->phaseacc -= 4; while (chan->phaseacc < 0) { chan->phaseacc += (chan->freq >> 16); chan->adder = (chan->adder + 1) & 0x0f; } if (chan->regs[0] & 0x10) /* fixed volume */ chan->output = chan->regs[0] & 0x0f; else chan->output = 0x0f - chan->env_vol; chan->output *= BIT(duty_lut[chan->regs[0] >> 6], 7 - BIT(chan->adder, 1, 3)); } /* OUTPUT TRIANGLE WAVE SAMPLE (VALUES FROM 0 to +15) */ void nesapu_device::apu_triangle(apu_t::triangle_t *chan) { /* 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 */ if (!chan->enabled) return; bool not_held = !BIT(chan->regs[0], 7); if (!chan->counter_started && not_held) { if (chan->write_latency) chan->write_latency--; if (!chan->write_latency) chan->counter_started = true; } if (chan->counter_started) { if (chan->linear_reload) chan->linear_length = m_sync_times2[chan->regs[0] & 0x7f]; else if (chan->linear_length > 0) chan->linear_length--; if (not_held) chan->linear_reload = false; if (chan->vbl_length && not_held) chan->vbl_length--; } if (!(chan->linear_length && chan->vbl_length)) return; int freq = ((chan->regs[3] & 7) << 8) + chan->regs[2] + 1; // FIXME: This halts ultrasonic frequencies. On hardware there should be some popping noise? Crash Man's stage in Mega Man 2 is an example. This can probably be removed if hardware filters are implemented (they vary by machine, NES, FC, VS, etc). if (freq < 2) return; chan->phaseacc -= 4; while (chan->phaseacc < 0) { chan->phaseacc += freq; chan->adder++; chan->output = chan->adder & 0xf; if (!BIT(chan->adder, 4)) chan->output ^= 0xf; } } /* OUTPUT NOISE WAVE SAMPLE (VALUES FROM 0 to +15) */ void nesapu_device::apu_noise(apu_t::noise_t *chan) { int freq, env_delay; /* reg0: 0-3=volume, 4=envelope, 5=hold ** reg2: 7=small(93 byte) sample,3-0=freq lookup ** reg3: 7-4=vbl length counter */ if (!chan->enabled) { chan->output = 0; return; } /* enveloping */ env_delay = m_sync_times1[chan->regs[0] & 0x0f]; /* decay is at a rate of (env_regs + 1) / 240 secs */ chan->env_phase -= 4; while (chan->env_phase < 0) { chan->env_phase += env_delay; if (chan->regs[0] & 0x20) chan->env_vol = (chan->env_vol + 1) & 15; else if (chan->env_vol < 15) chan->env_vol++; } /* length counter */ if (!(chan->regs[0] & 0x20)) { if (chan->vbl_length > 0) chan->vbl_length--; } if (!chan->vbl_length) { chan->output = 0; return; } freq = noise_freq[m_is_pal][chan->regs[2] & 0x0f]; chan->phaseacc -= 4; while (chan->phaseacc < 0) { chan->phaseacc += freq; update_lfsr(*chan); } if (BIT(chan->lfsr, 0)) /* silence channel */ { chan->output = 0; return; } if (chan->regs[0] & 0x10) /* fixed volume */ chan->output = chan->regs[0] & 0x0f; else chan->output = 0x0f - chan->env_vol; } void nesapu_device::update_lfsr(apu_t::noise_t &chan) { chan.lfsr |= (BIT(chan.lfsr, 0) ^ BIT(chan.lfsr, (chan.regs[2] & 0x80) ? 6 : 1)) << 15; chan.lfsr >>= 1; } void apu2a03_device::update_lfsr(apu_t::noise_t &chan) { chan.lfsr |= (BIT(chan.lfsr, 0) ^ BIT(chan.lfsr, 1)) << 15; chan.lfsr >>= 1; } /* RESET DPCM PARAMETERS */ static inline void apu_dpcmreset(apu_t::dpcm_t *chan) { chan->address = 0xc000 + u16(chan->regs[2] << 6); chan->length = u16(chan->regs[3] << 4) + 1; chan->bits_left = chan->length << 3; chan->enabled = true; /* Fixed * Proper DPCM channel ENABLE/DISABLE flag behaviour*/ } /* OUTPUT DPCM WAVE SAMPLE (VALUES FROM 0 to +127) */ /* TODO: centerline naughtiness */ void nesapu_device::apu_dpcm(apu_t::dpcm_t *chan) { int freq, bit_pos; /* reg0: 7=irq gen, 6=looping, 3-0=pointer to clock table ** reg1: output dc level, 7 bits unsigned ** reg2: 8 bits of 64-byte aligned address offset : $C000 + (value * 64) ** reg3: length, (value * 16) + 1 */ if (chan->enabled) { freq = dpcm_clocks[m_is_pal][chan->regs[0] & 0x0f]; chan->phaseacc -= 4; while (chan->phaseacc < 0) { chan->phaseacc += freq; if (!chan->length) { chan->enabled = false; /* Fixed * Proper DPCM channel ENABLE/DISABLE flag behaviour*/ if (chan->regs[0] & 0x40) apu_dpcmreset(chan); else { if (chan->regs[0] & 0x80) /* IRQ Generator */ { chan->irq_occurred = true; m_irq_handler(true); } break; } } chan->bits_left--; bit_pos = 7 - (chan->bits_left & 7); if (7 == bit_pos) { chan->cur_byte = m_mem_read_cb(chan->address); chan->address++; chan->length--; } if ((chan->cur_byte & (1 << bit_pos)) && (chan->vol <= 125)) // chan->regs[1] += 2; chan->vol += 2; /* FIXED * DPCM channel only uses the upper 6 bits of the DAC */ else if (chan->vol >= 2) // chan->regs[1] -= 2; chan->vol -= 2; } } chan->output = (u8)(chan->vol); } /* WRITE REGISTER VALUE */ void nesapu_device::write(offs_t offset, u8 value) { m_stream->update(); int chan = BIT(offset, 2); switch (offset) { /* squares */ case apu_t::WRA0: case apu_t::WRB0: m_APU.squ[chan].regs[0] = value; break; case apu_t::WRA1: case apu_t::WRB1: m_APU.squ[chan].regs[1] = value; break; case apu_t::WRA2: case apu_t::WRB2: m_APU.squ[chan].regs[2] = value; if (m_APU.squ[chan].enabled) m_APU.squ[chan].freq = ((((m_APU.squ[chan].regs[3] & 7) << 8) + value) + 1) << 16; break; case apu_t::WRA3: case apu_t::WRB3: m_APU.squ[chan].regs[3] = value; if (m_APU.squ[chan].enabled) { m_APU.squ[chan].vbl_length = m_vbl_times[value >> 3]; m_APU.squ[chan].env_vol = 0; m_APU.squ[chan].freq = ((((value & 7) << 8) + m_APU.squ[chan].regs[2]) + 1) << 16; } break; /* triangle */ case apu_t::WRC0: m_APU.tri.regs[0] = value; if (m_APU.tri.enabled) { /* ??? */ if (!m_APU.tri.counter_started) m_APU.tri.linear_length = m_sync_times2[value & 0x7f]; } break; case 0x4009: /* unused */ m_APU.tri.regs[1] = value; break; case apu_t::WRC2: m_APU.tri.regs[2] = value; break; case apu_t::WRC3: m_APU.tri.regs[3] = value; /* this is somewhat of a hack. there is 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 to a few NES sample -- should be sufficient ** ** 3 * (1789772.727 / 44100) = ~122 cycles, just around one scanline ** ** should be plenty of time for the 6502 code to do a couple of table ** dereferences and load up the other triregs */ /* used to be 3, but now we run the clock faster, so base it on samples/sync */ m_APU.tri.write_latency = (m_samps_per_sync + 239) / 240; if (m_APU.tri.enabled) { m_APU.tri.counter_started = false; m_APU.tri.vbl_length = m_vbl_times[value >> 3]; m_APU.tri.linear_length = m_sync_times2[m_APU.tri.regs[0] & 0x7f]; m_APU.tri.linear_reload = true; } break; /* noise */ case apu_t::WRD0: m_APU.noi.regs[0] = value; break; case 0x400D: /* unused */ m_APU.noi.regs[1] = value; break; case apu_t::WRD2: m_APU.noi.regs[2] = value; break; case apu_t::WRD3: m_APU.noi.regs[3] = value; if (m_APU.noi.enabled) { m_APU.noi.vbl_length = m_vbl_times[value >> 3]; m_APU.noi.env_vol = 0; /* reset envelope */ } break; /* DMC */ case apu_t::WRE0: m_APU.dpcm.regs[0] = value; if (!(value & 0x80)) { m_APU.dpcm.irq_occurred = false; if (!m_APU.frame_irq_occurred) m_irq_handler(false); } break; case apu_t::WRE1: /* 7-bit DAC */ m_APU.dpcm.regs[1] = value & 0x7f; m_APU.dpcm.vol = m_APU.dpcm.regs[1]; break; case apu_t::WRE2: m_APU.dpcm.regs[2] = value; //apu_dpcmreset(m_APU.dpcm); break; case apu_t::WRE3: m_APU.dpcm.regs[3] = value; break; case apu_t::IRQCTRL: if (value & 0x80) m_APU.step_mode = 5; else m_APU.step_mode = 4; m_APU.frame_irq_enabled = !BIT(value, 6); if (m_APU.frame_irq_enabled) { m_frame_timer->adjust(m_frame_period, 0, m_frame_period); } else { m_APU.frame_irq_occurred = false; if (!m_APU.dpcm.irq_occurred) m_irq_handler(false); m_frame_timer->reset(); } break; case apu_t::SMASK: if (value & 0x01) m_APU.squ[0].enabled = true; else { m_APU.squ[0].enabled = false; m_APU.squ[0].vbl_length = 0; } if (value & 0x02) m_APU.squ[1].enabled = true; else { m_APU.squ[1].enabled = false; m_APU.squ[1].vbl_length = 0; } if (value & 0x04) m_APU.tri.enabled = true; else { m_APU.tri.enabled = false; m_APU.tri.vbl_length = 0; m_APU.tri.linear_length = 0; m_APU.tri.counter_started = false; m_APU.tri.write_latency = 0; } if (value & 0x08) m_APU.noi.enabled = true; else { m_APU.noi.enabled = false; m_APU.noi.vbl_length = 0; } if (value & 0x10) { /* only reset dpcm values if DMA is finished */ if (!m_APU.dpcm.enabled) { m_APU.dpcm.enabled = true; apu_dpcmreset(&m_APU.dpcm); } } else m_APU.dpcm.enabled = false; m_APU.dpcm.irq_occurred = false; if (!m_APU.frame_irq_occurred) m_irq_handler(false); break; default: #ifdef MAME_DEBUG logerror("invalid apu write: $%02X at $%04X\n", value, offset); #endif break; } } // Read status register at $4015 u8 nesapu_device::status_r() { m_stream->update(); u8 readval = 0; if (m_APU.squ[0].vbl_length > 0) readval |= 0x01; if (m_APU.squ[1].vbl_length > 0) readval |= 0x02; if (m_APU.tri.vbl_length > 0) readval |= 0x04; if (m_APU.noi.vbl_length > 0) readval |= 0x08; if (m_APU.dpcm.enabled) readval |= 0x10; if (m_APU.frame_irq_occurred) readval |= 0x40; if (m_APU.dpcm.irq_occurred) readval |= 0x80; m_APU.frame_irq_occurred = false; if (!m_APU.dpcm.irq_occurred) m_irq_handler(false); return readval; } //------------------------------------------------- // sound_stream_update - handle a stream update //------------------------------------------------- void nesapu_device::sound_stream_update(sound_stream &stream) { sound_stream::sample_t accum = 0.0; for (int sampindex = 0; sampindex < stream.samples(); sampindex++) { apu_square(&m_APU.squ[0]); apu_square(&m_APU.squ[1]); apu_triangle(&m_APU.tri); apu_noise(&m_APU.noi); apu_dpcm(&m_APU.dpcm); accum = m_square_lut[m_APU.squ[0].output + m_APU.squ[1].output]; accum += m_tnd_lut[m_APU.tri.output][m_APU.noi.output][m_APU.dpcm.output]; stream.put(0, sampindex, accum); } }