summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/sound/ym2154.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/sound/ym2154.cpp')
-rw-r--r--src/devices/sound/ym2154.cpp311
1 files changed, 311 insertions, 0 deletions
diff --git a/src/devices/sound/ym2154.cpp b/src/devices/sound/ym2154.cpp
new file mode 100644
index 00000000000..2ad1bbac1fa
--- /dev/null
+++ b/src/devices/sound/ym2154.cpp
@@ -0,0 +1,311 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+#include "emu.h"
+#include "ym2154.h"
+
+
+//**************************************************************************
+// YM2154 DEVICE
+//**************************************************************************
+
+DEFINE_DEVICE_TYPE(YM2154, ym2154_device, "ym2154", "YM2154 (RYP4)")
+
+//-------------------------------------------------
+// ym2154_device - constructor
+//-------------------------------------------------
+
+ym2154_device::ym2154_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
+ device_t(mconfig, YM2154, tag, owner, clock),
+ device_sound_interface(mconfig, *this),
+ device_memory_interface(mconfig, *this),
+ m_stream(nullptr),
+ m_timer(nullptr),
+ m_update_irq(*this),
+ m_io_read(*this),
+ m_io_write(*this),
+ m_group0_config("group0", ENDIANNESS_LITTLE, 8, 18, 0),
+ m_group1_config("group1", ENDIANNESS_LITTLE, 8, 18, 0),
+ m_group0_region(*this, "group0"),
+ m_group1_region(*this, "group1")
+{
+}
+
+
+//-------------------------------------------------
+// read - handle a device read
+//-------------------------------------------------
+
+u8 ym2154_device::read(offs_t offset)
+{
+ u8 result = 0xff;
+ switch (offset & 0x7f)
+ {
+ case 0x01: // A/D converter registers
+ case 0x02: // A/D converter registers
+ case 0x03: // A/D converter registers
+ case 0x04: // A/D converter registers
+ case 0x05: // A/D converter registers
+ case 0x06: // A/D converter registers
+ case 0x07: // A/D converter registers
+ case 0x08: // A/D converter registers
+ case 0x09: // A/D converter registers
+ case 0x0a: // A/D converter registers
+ result = m_io_read.isnull() ? 0 : m_io_read(offset - 1);
+ break;
+
+ case 0x0e: // IRQ ack
+ update_irq_state(0);
+ result = m_irq_count;
+ break;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// write - handle a device write
+//-------------------------------------------------
+
+void ym2154_device::write(offs_t offset, u8 data)
+{
+ m_stream->update();
+
+ u8 chan = BIT(offset, 0, 3);
+ u8 old;
+ switch (offset & 0x7f)
+ {
+ // timer count
+ case 0x02:
+ if (BIT(data, 7) != 0)
+ m_timer_count = (m_timer_count & 0x0f) | (BIT(data, 0, 7) << 4);
+ else
+ m_timer_count = (m_timer_count & 0x7f0) | BIT(data, 0, 4);
+ break;
+
+ // timer enable/output
+ case 0x03:
+ old = m_timer_enable;
+ m_timer_enable = BIT(data, 2);
+ if (!m_timer_enable)
+ m_timer->enable(false);
+ else if (m_timer_enable && !old)
+ m_timer->adjust((2048 - m_timer_count) * attotime::from_hz(sample_rate()));
+ if (!m_io_write.isnull())
+ m_io_write(0, BIT(data, 4, 4) ^ 0x0f);
+ break;
+
+ // output level
+ case 0x04:
+ m_total_level = BIT(data, 0, 6);
+ break;
+
+ // group 1 trigger
+ case 0x05:
+ for (int chan = 0; chan < 6; chan++)
+ if (BIT(data, chan))
+ m_channel[6 + chan].start();
+ break;
+
+ // group 0 trigger
+ case 0x06:
+ for (int chan = 0; chan < 6; chan++)
+ if (BIT(data, chan))
+ m_channel[0 + chan].start();
+ break;
+
+ // DAC mode (not implemented)
+ case 0x07:
+ break;
+
+ // pan
+ case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d:
+ m_channel[0 + chan].m_panpot = BIT(data, 0, 4);
+ m_channel[6 + chan].m_panpot = BIT(data, 4, 4);
+ break;
+
+ // rate/level, group 0
+ case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15:
+ m_channel[0 + chan].m_output_level = BIT(data, 0, 5);
+ m_channel[0 + chan].m_rate = BIT(data, 5, 2);
+ break;
+
+ // rate/level, group 1
+ case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d:
+ m_channel[6 + chan].m_output_level = BIT(data, 0, 5);
+ m_channel[6 + chan].m_rate = BIT(data, 5, 2);
+ break;
+
+ // sample position A, group 0
+ case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25:
+ m_channel[0 + chan].m_start = (m_channel[0 + chan].m_start & 0x0f) | (data << 4);
+ break;
+
+ // sample position B, group 0
+ case 0x28: case 0x29: case 0x2a: case 0x2b: case 0x2c: case 0x2d:
+ m_channel[0 + chan].m_start = (m_channel[0 + chan].m_start & 0xff0) | (data >> 4);
+ m_channel[0 + chan].m_end = (m_channel[0 + chan].m_end & 0xff) | ((data & 0xf) << 8);
+ break;
+
+ // sample position C, group 0
+ case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35:
+ m_channel[0 + chan].m_end = (m_channel[0 + chan].m_end & 0xf00) | data;
+ break;
+
+ // sample position A, group 1
+ case 0x38: case 0x39: case 0x3a: case 0x3b: case 0x3c: case 0x3d:
+ m_channel[6 + chan].m_start = (m_channel[6 + chan].m_start & 0x0f) | (data << 4);
+ break;
+
+ // sample position B, group 1
+ case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45:
+ m_channel[6 + chan].m_start = (m_channel[6 + chan].m_start & 0xff0) | (data >> 4);
+ m_channel[6 + chan].m_end = (m_channel[6 + chan].m_end & 0xff) | ((data & 0xf) << 8);
+ break;
+
+ // sample position C, group 1
+ case 0x48: case 0x49: case 0x4a: case 0x4b: case 0x4c: case 0x4d:
+ m_channel[6 + chan].m_end = (m_channel[6 + chan].m_end & 0xf00) | data;
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// device_start - device-specific startup
+//-------------------------------------------------
+
+void ym2154_device::device_start()
+{
+ // allocate our timer
+ m_timer = timer_alloc(0);
+
+ // resolve the handlers
+ m_update_irq.resolve();
+ m_io_read.resolve();
+ m_io_write.resolve();
+
+ // allocate our stream
+ m_stream = stream_alloc(0, 2, sample_rate());
+
+ // now register the blob for save, on the assumption the size won't change
+ save_item(NAME(m_timer_count));
+ save_item(NAME(m_timer_enable));
+ save_item(NAME(m_irq_state));
+ save_item(NAME(m_irq_count));
+ save_item(NAME(m_total_level));
+ save_item(STRUCT_MEMBER(m_channel, m_pos));
+ save_item(STRUCT_MEMBER(m_channel, m_start));
+ save_item(STRUCT_MEMBER(m_channel, m_end));
+ save_item(STRUCT_MEMBER(m_channel, m_panpot));
+ save_item(STRUCT_MEMBER(m_channel, m_output_level));
+ save_item(STRUCT_MEMBER(m_channel, m_rate));
+
+ // automatically map memory regions if not configured externally
+ if (!has_configured_map(0) && !has_configured_map(1))
+ {
+ if (m_group0_region)
+ space(0).install_rom(0, m_group0_region->bytes() - 1, m_group0_region->base());
+
+ if (m_group1_region)
+ space(1).install_rom(0, m_group1_region->bytes() - 1, m_group1_region->base());
+ else if (m_group0_region)
+ space(1).install_rom(0, m_group0_region->bytes() - 1, m_group0_region->base());
+ }
+}
+
+
+//-------------------------------------------------
+// device_reset - device-specific reset
+//-------------------------------------------------
+
+void ym2154_device::device_reset()
+{
+ for (int chan = 0; chan < 12; chan++)
+ m_channel[chan].m_pos = 0xfffffff;
+}
+
+
+//-------------------------------------------------
+// device_clock_changed - clock changed signal
+//-------------------------------------------------
+
+void ym2154_device::device_clock_changed()
+{
+ if (m_stream != nullptr)
+ m_stream->set_sample_rate(sample_rate());
+}
+
+
+//-------------------------------------------------
+// sound_stream_update - generate sound data
+//-------------------------------------------------
+
+void ym2154_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
+{
+ update_irq_state(1);
+ m_timer->adjust((2048 - m_timer_count) * attotime::from_hz(sample_rate()));
+}
+
+
+//-------------------------------------------------
+// sound_stream_update - generate sound data
+//-------------------------------------------------
+
+void ym2154_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
+{
+ static const uint16_t voltable[8] = { 0x7fa,0x751,0x6b5,0x627,0x5a4,0x52c,0x4be,0x45a };
+
+ auto &outl = outputs[0];
+ auto &outr = outputs[1];
+
+ outl.fill(0);
+ outr.fill(0);
+
+ for (int chan = 0; chan < 12; chan++)
+ {
+ auto &channel = m_channel[chan];
+
+ // not sure how the "rate" really works but it's used to stop a sample from
+ // playing so just treat it as such
+ if (channel.m_rate == 3)
+ m_channel[chan].m_pos = 0xfffffff;
+
+ if ((channel.m_pos >> ADDR_SHIFT) <= channel.m_end && channel.m_panpot != 0)
+ {
+ uint32_t vol = (channel.m_output_level ^ 0x1f) + (m_total_level ^ 0x3f);
+
+ uint32_t lvol = vol;
+ if (channel.m_panpot > 8)
+ lvol += 4 * (channel.m_panpot - 8);
+ lvol = voltable[lvol & 7] >> (lvol >> 3);
+
+ uint32_t rvol = vol;
+ if (channel.m_panpot < 7)
+ rvol += 4 * (7 - channel.m_panpot);
+ rvol = voltable[rvol & 7] >> (rvol >> 3);
+
+ for (int sampindex = 0; sampindex < outl.samples() && (channel.m_pos >> ADDR_SHIFT) <= channel.m_end; sampindex++)
+ {
+ // unsure what the real data is; for now guessing 8-bit PCM but could
+ // be ADPCM-A or some other format
+ int8_t data = space(chan / 6).read_byte(channel.m_pos++);
+ outl.add_int(sampindex, data * lvol, 0x80 * 0x800);
+ outr.add_int(sampindex, data * rvol, 0x80 * 0x800);
+ }
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// memory_space_config - return a description of
+// any address spaces owned by this device
+//-------------------------------------------------
+
+device_memory_interface::space_config_vector ym2154_device::memory_space_config() const
+{
+ return space_config_vector{
+ std::make_pair(0, &m_group0_config),
+ std::make_pair(1, &m_group1_config)
+ };
+}