summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/sound/mmc5.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/sound/mmc5.cpp')
-rw-r--r--src/devices/sound/mmc5.cpp314
1 files changed, 314 insertions, 0 deletions
diff --git a/src/devices/sound/mmc5.cpp b/src/devices/sound/mmc5.cpp
new file mode 100644
index 00000000000..48b976e588d
--- /dev/null
+++ b/src/devices/sound/mmc5.cpp
@@ -0,0 +1,314 @@
+// 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
+
+ *****************************************************************************
+
+ mmc5.cpp
+
+ MMC5 sound emulation core, heavily based from sound/nes_apu.cpp.
+
+ *****************************************************************************/
+
+#include "emu.h"
+#include "mmc5.h"
+
+DEFINE_DEVICE_TYPE(MMC5_SOUND, mmc5_sound_device, "mmc5_sound", "Nintendo MMC5 (sound)")
+
+mmc5_sound_device::mmc5_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
+ : device_t(mconfig, MMC5_SOUND, tag, owner, clock)
+ , device_sound_interface(mconfig, *this)
+ , m_samps_per_sync(0)
+ , m_stream(nullptr)
+ , m_irq_handler(*this)
+{
+}
+
+
+void mmc5_sound_device::device_reset()
+{
+ write(0x15, 0x00);
+}
+
+void mmc5_sound_device::device_clock_changed()
+{
+ calculate_rates();
+}
+
+void mmc5_sound_device::calculate_rates()
+{
+ m_samps_per_sync = m_frame_clocks / 4;
+
+ // 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 / 4; // twice as fast as NES APU
+ m_sync_times1[i] = m_samps_per_sync * (i + 1);
+ }
+
+ int const 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 mmc5_sound_device::device_start()
+{
+ m_frame_clocks = 29830;
+
+ calculate_rates();
+
+ // calculate mixer output
+ /*
+ pulse channel output (same as NES APU?):
+
+ 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;
+ }
+
+ /* register for save */
+ for (int i = 0; i < 2; i++)
+ {
+ save_item(NAME(m_core.squ[i].regs), i);
+ save_item(NAME(m_core.squ[i].vbl_length), i);
+ save_item(NAME(m_core.squ[i].freq), i);
+ save_item(NAME(m_core.squ[i].phaseacc), i);
+ save_item(NAME(m_core.squ[i].env_phase), i);
+ save_item(NAME(m_core.squ[i].adder), i);
+ save_item(NAME(m_core.squ[i].env_vol), i);
+ save_item(NAME(m_core.squ[i].enabled), i);
+ save_item(NAME(m_core.squ[i].output), i);
+ }
+
+ save_item(NAME(m_core.pcm.regs));
+ save_item(NAME(m_core.pcm.irq_enabled));
+ save_item(NAME(m_core.pcm.irq_line));
+ save_item(NAME(m_core.pcm.output));
+}
+
+
+/* TODO: sound channels should *ALL* have DC volume decay */
+
+/* OUTPUT SQUARE WAVE SAMPLE (VALUES FROM 0 to +15) */
+void mmc5_sound_device::tick_square(mmc5_sound_t::square_t &chan)
+{
+ /* reg0: 0-3=volume, 4=envelope, 5=hold, 6-7=duty cycle
+ ** reg2: 8 bits of freq
+ ** reg3: 0-2=high freq, 7-4=vbl length counter
+ */
+
+ if (!chan.enabled)
+ {
+ chan.output = 0;
+ return;
+ }
+
+ /* enveloping */
+ int const 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 (BIT(chan.regs[0], 5))
+ 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 && BIT(~chan.regs[0], 5))
+ chan.vbl_length--;
+
+ if (!chan.vbl_length)
+ {
+ chan.output = 0;
+ return;
+ }
+
+ chan.phaseacc -= 4;
+
+ while (chan.phaseacc < 0)
+ {
+ chan.phaseacc += (chan.freq >> 16);
+ chan.adder = (chan.adder + 1) & 0x0f;
+ }
+
+ if (BIT(chan.regs[0], 4)) /* 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));
+}
+
+/* WRITE REGISTER VALUE */
+void mmc5_sound_device::write(offs_t offset, u8 data)
+{
+ m_stream->update();
+
+ int chan = BIT(offset, 2);
+
+ switch (offset)
+ {
+ /* squares */
+ case mmc5_sound_t::WRA0:
+ case mmc5_sound_t::WRB0:
+ m_core.squ[chan].regs[0] = data;
+ break;
+
+ case mmc5_sound_t::WRA2:
+ case mmc5_sound_t::WRB2:
+ m_core.squ[chan].regs[2] = data;
+ if (m_core.squ[chan].enabled)
+ m_core.squ[chan].freq = ((((m_core.squ[chan].regs[3] & 7) << 8) + data) + 1) << 16;
+ break;
+
+ case mmc5_sound_t::WRA3:
+ case mmc5_sound_t::WRB3:
+ m_core.squ[chan].regs[3] = data;
+
+ if (m_core.squ[chan].enabled)
+ {
+ m_core.squ[chan].vbl_length = m_vbl_times[data >> 3];
+ m_core.squ[chan].env_vol = 0;
+ m_core.squ[chan].freq = ((((data & 7) << 8) + m_core.squ[chan].regs[2]) + 1) << 16;
+ }
+
+ break;
+
+ /* PCM */
+ case mmc5_sound_t::WRE0:
+ m_core.pcm.regs[0] = data;
+ m_core.pcm.irq_enabled = BIT(data, 7);
+ m_irq_handler(m_core.pcm.irq_line & m_core.pcm.irq_enabled);
+ break;
+
+ case mmc5_sound_t::WRE1: /* PCM output */
+ m_core.pcm.regs[1] = data;
+ if (BIT(~m_core.pcm.regs[0], 0))
+ {
+ if (data == 0x00)
+ {
+ m_core.pcm.irq_line = true;
+ m_irq_handler(m_core.pcm.irq_line & m_core.pcm.irq_enabled);
+ }
+ else
+ m_core.pcm.output = data;
+ }
+ break;
+
+ case mmc5_sound_t::SMASK:
+ if (BIT(data, 0))
+ m_core.squ[0].enabled = true;
+ else
+ {
+ m_core.squ[0].enabled = false;
+ m_core.squ[0].vbl_length = 0;
+ }
+
+ if (BIT(data, 1))
+ m_core.squ[1].enabled = true;
+ else
+ {
+ m_core.squ[1].enabled = false;
+ m_core.squ[1].vbl_length = 0;
+ }
+ break;
+ default:
+#ifdef MAME_DEBUG
+logerror("invalid mmc5 sound write: $%02X at $%04X\n", data, offset);
+#endif
+ break;
+ }
+}
+
+// Read registers
+u8 mmc5_sound_device::read(offs_t offset)
+{
+ m_stream->update();
+
+ u8 readval = 0;
+ switch (offset)
+ {
+ case mmc5_sound_t::WRE0:
+ readval |= (m_core.pcm.irq_line & m_core.pcm.irq_enabled) ? 0x80 : 0;
+ if (!machine().side_effects_disabled())
+ {
+ m_core.pcm.irq_line = false;
+ m_irq_handler(m_core.pcm.irq_line & m_core.pcm.irq_enabled);
+ }
+ break;
+ case mmc5_sound_t::SMASK:
+ if (m_core.squ[0].vbl_length > 0)
+ readval |= 0x01;
+
+ if (m_core.squ[1].vbl_length > 0)
+ readval |= 0x02;
+ break;
+ default:
+ if (!machine().side_effects_disabled())
+ logerror("%s: Invalid mmc5 sound read at $%02x\n", offset);
+ break;
+ }
+
+ return readval;
+}
+
+// Write PCM data
+void mmc5_sound_device::pcm_w(u8 data)
+{
+ if (BIT(m_core.pcm.regs[0], 0))
+ m_core.pcm.output = data;
+}
+
+
+//-------------------------------------------------
+// sound_stream_update - handle a stream update
+//-------------------------------------------------
+
+void mmc5_sound_device::sound_stream_update(sound_stream &stream)
+{
+ sound_stream::sample_t accum = 0.0;
+
+ for (int sampindex = 0; sampindex < stream.samples(); sampindex++)
+ {
+ tick_square(m_core.squ[0]);
+ tick_square(m_core.squ[1]);
+
+ accum = m_square_lut[m_core.squ[0].output + m_core.squ[1].output];
+ accum += sound_stream::sample_t(m_core.pcm.output) / 255.0f;
+
+ stream.put(0, sampindex, -accum);
+ }
+}