summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/sound/ym2610.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/sound/ym2610.cpp')
-rw-r--r--src/devices/sound/ym2610.cpp318
1 files changed, 318 insertions, 0 deletions
diff --git a/src/devices/sound/ym2610.cpp b/src/devices/sound/ym2610.cpp
new file mode 100644
index 00000000000..23d0c8fc1ee
--- /dev/null
+++ b/src/devices/sound/ym2610.cpp
@@ -0,0 +1,318 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+
+#include "emu.h"
+#include "ym2610.h"
+
+
+DEFINE_DEVICE_TYPE(YM2610, ym2610_device, "ym2610", "YM2610 OPNB")
+DEFINE_DEVICE_TYPE(YM2610B, ym2610b_device, "ym2610b", "YM2610 OPNB2")
+
+
+//*********************************************************
+// YM2610/YM2610B DEVICE
+//*********************************************************
+
+//-------------------------------------------------
+// ym2610_device - constructor
+//-------------------------------------------------
+
+ym2610_device::ym2610_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock, device_type type, u8 opn_mask) :
+ ay8910_device(mconfig, type, tag, owner, clock, PSG_TYPE_YM, 1, 0),
+ device_memory_interface(mconfig, *this),
+ m_adpcm_a_config("adpcm-a", ENDIANNESS_LITTLE, 8, 24, 0),
+ m_adpcm_b_config("adpcm-b", ENDIANNESS_LITTLE, 8, 24, 0),
+ m_adpcm_a_region(*this, "adpcma"),
+ m_adpcm_b_region(*this, "adpcmb"),
+ m_opn(*this),
+ m_adpcm_a(*this, read8sm_delegate(*this, FUNC(ym2610_device::adpcm_a_read)), 8),
+ m_adpcm_b(*this, read8sm_delegate(*this, FUNC(ym2610_device::adpcm_b_read)), write8sm_delegate(*this), 8),
+ m_stream(nullptr),
+ m_busy_duration(m_opn.compute_busy_duration()),
+ m_address(0),
+ m_opn_mask(opn_mask),
+ m_eos_status(0x00),
+ m_flag_mask(0xbf)
+{
+}
+
+
+//-------------------------------------------------
+// ym2610b_device - constructor
+//-------------------------------------------------
+
+ym2610b_device::ym2610b_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
+ ym2610_device(mconfig, tag, owner, clock, YM2610B, 0x3f)
+{
+}
+
+
+//-------------------------------------------------
+// read - handle a read from the device
+//-------------------------------------------------
+
+u8 ym2610_device::read(offs_t offset)
+{
+ u8 result = 0;
+ switch (offset & 3)
+ {
+ case 0: // status port, YM2203 compatible
+ result = m_opn.status() & (ymopna_engine::STATUS_TIMERA | ymopna_engine::STATUS_TIMERB | ymopna_engine::STATUS_BUSY);
+ break;
+
+ case 1: // data port (only SSG)
+ if (m_address < 0x10)
+ result = ay8910_read_ym();
+ else if (m_address == 0xff)
+ result = 1; // ID code
+ break;
+
+ case 2: // status port, extended
+ result = m_eos_status & m_flag_mask;
+ break;
+
+ case 3: // ADPCM-B data
+ break;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void ym2610_device::write(offs_t offset, u8 value)
+{
+ switch (offset & 3)
+ {
+ case 0: // address port
+ m_address = value;
+
+ // write register to SSG emulator
+ if (m_address < 0x10)
+ ay8910_write_ym(0, m_address);
+ break;
+
+ case 1: // data port
+
+ // ignore if paired with upper address
+ if (BIT(m_address, 8))
+ break;
+
+ if (m_address < 0x10)
+ {
+ // write to SSG
+ ay8910_write_ym(1, value);
+ }
+ else if (m_address < 0x1c)
+ {
+ // write to ADPCM-B
+ m_stream->update();
+ m_adpcm_b.write(m_address & 0x0f, value);
+ }
+ else if (m_address == 0x1c)
+ {
+ // EOS flag reset
+ m_stream->update();
+ m_flag_mask = ~value;
+ m_eos_status &= ~value;
+ }
+ else
+ {
+ // write to OPN
+ m_stream->update();
+ m_opn.write(m_address, value);
+ }
+
+ // mark busy for a bit
+ m_opn.set_busy_end(machine().time() + m_busy_duration);
+ break;
+
+ case 2: // upper address port
+ m_address = 0x100 | value;
+ break;
+
+ case 3: // upper data port
+
+ // ignore if paired with lower address
+ if (!BIT(m_address, 8))
+ break;
+
+ if (m_address < 0x130)
+ {
+ // write to ADPCM-B
+ m_stream->update();
+ m_adpcm_a.write(m_address & 0x3f, value);
+ }
+ else
+ {
+ // write to OPN
+ m_stream->update();
+ m_opn.write(m_address, value);
+ }
+
+ // mark busy for a bit
+ m_opn.set_busy_end(machine().time() + m_busy_duration);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// memory_space_config - return a description of
+// any address spaces owned by this device
+//-------------------------------------------------
+
+device_memory_interface::space_config_vector ym2610_device::memory_space_config() const
+{
+ return space_config_vector{
+ std::make_pair(0, &m_adpcm_a_config),
+ std::make_pair(1, &m_adpcm_b_config)
+ };
+}
+
+
+//-------------------------------------------------
+// device_start - start of emulation
+//-------------------------------------------------
+
+void ym2610_device::device_start()
+{
+ // call our parent
+ ay8910_device::device_start();
+
+ // create our stream
+ m_stream = stream_alloc(0, 2, clock() / (4 * 6 * 6));
+
+ // save our data
+ save_item(YMFM_NAME(m_address));
+ save_item(YMFM_NAME(m_eos_status));
+ save_item(YMFM_NAME(m_flag_mask));
+
+ // save the engines
+ m_opn.save(*this);
+ m_adpcm_a.save(*this);
+ m_adpcm_b.save(*this);
+
+ // configure ADPCM-B limit, since these registers are not
+ // directly accessible in the map
+ m_adpcm_b.write(0x0c, 0xff);
+ m_adpcm_b.write(0x0d, 0xff);
+
+ // automatically map memory regions if not configured externally
+ if (!has_configured_map(0) && !has_configured_map(1))
+ {
+ if (m_adpcm_a_region)
+ space(0).install_rom(0, m_adpcm_a_region->bytes() - 1, m_adpcm_a_region->base());
+
+ if (m_adpcm_b_region)
+ space(1).install_rom(0, m_adpcm_b_region->bytes() - 1, m_adpcm_b_region->base());
+ else if (m_adpcm_a_region)
+ space(1).install_rom(0, m_adpcm_a_region->bytes() - 1, m_adpcm_a_region->base());
+ }
+}
+
+
+//-------------------------------------------------
+// device_reset - start of emulation
+//-------------------------------------------------
+
+void ym2610_device::device_reset()
+{
+ // call our parent
+ ay8910_device::device_reset();
+
+ // reset the engines
+ m_opn.reset();
+ m_adpcm_a.reset();
+ m_adpcm_b.reset();
+
+ // initialize our special interrupt states
+ m_eos_status = 0x00;
+ m_flag_mask = 0xbf;
+}
+
+
+//-------------------------------------------------
+// device_clock_changed - update if clock changes
+//-------------------------------------------------
+
+void ym2610_device::device_clock_changed()
+{
+ m_stream->set_sample_rate(clock() / (4 * 6 * 6));
+ ay_set_clock(clock() / 4);
+
+ // recompute the busy duration
+ m_busy_duration = m_opn.compute_busy_duration();
+}
+
+
+//-------------------------------------------------
+// sound_stream_update - update the sound stream
+//-------------------------------------------------
+
+void ym2610_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
+{
+ // if this is not our stream, pass it on
+ if (&stream != m_stream)
+ {
+ ay8910_device::sound_stream_update(stream, inputs, outputs);
+ return;
+ }
+
+ // iterate over all target samples
+ for (int sampindex = 0; sampindex < outputs[0].samples(); sampindex++)
+ {
+ // clock the OPN
+ u32 env_counter = m_opn.clock(m_opn_mask);
+
+ // clock the ADPCM-A engine on every envelope cycle
+ if (BIT(env_counter, 0, 2) == 0)
+ m_eos_status |= m_adpcm_a.clock(0x3f);
+
+ // clock the ADPCM-B engine every cycle
+ m_adpcm_b.clock(0x01);
+ if ((m_adpcm_b.status(0) & ymadpcm_b_channel::STATUS_EOS) != 0)
+ m_eos_status |= 0x80;
+
+ // update the OPN content; OPNB is 13-bit with no intermediate clipping
+ s32 lsum = 0, rsum = 0;
+ m_opn.output(lsum, rsum, 1, 32767, m_opn_mask);
+ lsum <<= 1;
+ rsum <<= 1;
+
+ // mix in the ADPCM
+ m_adpcm_a.output(lsum, rsum, 0x3f);
+ m_adpcm_b.output(lsum, rsum, 2, 0x01);
+
+ // YM2608 is stereo
+ outputs[0].put_int_clamp(sampindex, lsum, 32768);
+ outputs[1].put_int_clamp(sampindex, rsum, 32768);
+ }
+}
+
+
+//-------------------------------------------------
+// adpcm_a_read - callback to read data for the
+// ADPCM-A engine; in this case, from address
+// space 0
+//-------------------------------------------------
+
+u8 ym2610_device::adpcm_a_read(offs_t offset)
+{
+ return space(0).read_byte(offset);
+}
+
+
+//-------------------------------------------------
+// adpcm_b_read - callback to read data for the
+// ADPCM-B engine; in this case, from address
+// space 1
+//-------------------------------------------------
+
+u8 ym2610_device::adpcm_b_read(offs_t offset)
+{
+ return space(1).read_byte(offset);
+}