summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/sound/ymfm_mame.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/sound/ymfm_mame.h')
-rw-r--r--src/devices/sound/ymfm_mame.h498
1 files changed, 498 insertions, 0 deletions
diff --git a/src/devices/sound/ymfm_mame.h b/src/devices/sound/ymfm_mame.h
new file mode 100644
index 00000000000..639d8fd9961
--- /dev/null
+++ b/src/devices/sound/ymfm_mame.h
@@ -0,0 +1,498 @@
+// license:BSD-3-Clause
+// copyright-holders:Aaron Giles
+
+#ifndef MAME_SOUND_YMFM_MAME_H
+#define MAME_SOUND_YMFM_MAME_H
+
+#pragma once
+
+#include "ymfm/src/ymfm.h"
+#include "ymfm/src/ymfm_ssg.h"
+#include "ay8910.h"
+
+
+// set this to 1 to use ymfm's built-in SSG implementation
+// set it to 0 to use MAME's ay8910 as the SSG implementation
+#define USE_BUILTIN_SSG (0)
+
+// set this to control the output sample rate for SSG-based chips
+#define SSG_FIDELITY (ymfm::OPN_FIDELITY_MED)
+
+
+
+//*********************************************************
+// MAME INTERFACES
+//*********************************************************
+
+// ======================> ym_generic_device
+
+// generic base class for a standalone FM device; this class contains the shared
+// configuration helpers, timers, and ymfm interface implementation; it also
+// specifies pure virtual functions for read/write access, which means it
+// can be used as a generic proxy for systems that have multiple FM types that are
+// swappable
+class ym_generic_device : public device_t, public device_sound_interface, public ymfm::ymfm_interface
+{
+public:
+ // constructor
+ ym_generic_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock, device_type type) :
+ device_t(mconfig, type, tag, owner, clock),
+ device_sound_interface(mconfig, *this),
+ m_timer{ nullptr, nullptr },
+ m_update_irq(*this),
+ m_io_read{ *this, *this },
+ m_io_write{ *this, *this }
+ {
+ }
+
+ // configuration helpers
+ auto irq_handler() { return m_update_irq.bind(); }
+ auto io_read_handler(int index = 0) { return m_io_read[index & 1].bind(); }
+ auto io_write_handler(int index = 0) { return m_io_write[index & 1].bind(); }
+
+ // read access interface, implemented by the derived chip-specific class
+ virtual u8 read(offs_t offset) = 0;
+ virtual u8 status_r() = 0;
+
+ // write access interface, implemented by the derived chip-specific class
+ virtual void write(offs_t offset, u8 data) = 0;
+ virtual void address_w(u8 data) = 0;
+ virtual void data_w(u8 data) = 0;
+
+protected:
+ // the chip implementation calls this when a write happens to the mode
+ // register, which could affect timers and interrupts; our responsibility
+ // is to ensure the system is up to date before calling the engine's
+ // engine_mode_write() method
+ virtual void ymfm_sync_mode_write(uint8_t data) override
+ {
+ machine().scheduler().synchronize(timer_expired_delegate(FUNC(ym_generic_device::fm_mode_write), this), data);
+ }
+
+ // the chip implementation calls this when the chip's status has changed,
+ // which may affect the interrupt state; our responsibility is to ensure
+ // the system is up to date before calling the engine's
+ // engine_check_interrupts() method
+ virtual void ymfm_sync_check_interrupts() override
+ {
+ // if we're currently executing a CPU, schedule the interrupt check;
+ // otherwise, do it directly
+ auto &scheduler = machine().scheduler();
+ if (scheduler.currently_executing())
+ scheduler.synchronize(timer_expired_delegate(FUNC(ym_generic_device::fm_check_interrupts), this));
+ else
+ m_engine->engine_check_interrupts();
+ }
+
+ // the chip implementation calls this when one of the two internal timers
+ // has changed state; our responsibility is to arrange to call the engine's
+ // engine_timer_expired() method after the provided number of clocks; if
+ // duration_in_clocks is negative, we should cancel any outstanding timers
+ virtual void ymfm_set_timer(uint32_t tnum, int32_t duration_in_clocks) override
+ {
+ if (duration_in_clocks >= 0)
+ m_timer[tnum]->adjust(attotime::from_ticks(duration_in_clocks, device_t::clock()), tnum);
+ else
+ m_timer[tnum]->enable(false);
+ }
+
+ // the chip implementation calls this when the state of the IRQ signal has
+ // changed due to a status change; our responsibility is to respons as
+ // needed to the change in IRQ state, signaling any consumers
+ virtual void ymfm_update_irq(bool asserted) override
+ {
+ if (!m_update_irq.isnull())
+ m_update_irq(asserted ? ASSERT_LINE : CLEAR_LINE);
+ }
+
+ // the chip implementation calls this to indicate that the chip should be
+ // considered in a busy state until the given number of clocks has passed;
+ // our responsibility is to compute and remember the ending time based on
+ // the chip's clock for later checking
+ virtual void ymfm_set_busy_end(uint32_t clocks) override
+ {
+ m_busy_end = machine().time() + attotime::from_ticks(clocks, device_t::clock());
+ }
+
+ // the chip implementation calls this to see if the chip is still currently
+ // is a busy state, as specified by a previous call to ymfm_set_busy_end();
+ // our responsibility is to compare the current time against the previously
+ // noted busy end time and return true if we haven't yet passed it
+ virtual bool ymfm_is_busy() override
+ {
+ return (machine().time() < m_busy_end);
+ }
+
+ // the chip implementation calls this whenever a new value is written to
+ // one of the chip's output ports (only applies to some chip types); our
+ // responsibility is to pass the written data on to any consumers
+ virtual void ymfm_io_write(uint8_t port, uint8_t data) override
+ {
+ if (!m_io_write[port & 1].isnull())
+ m_io_write[port & 1](data);
+ }
+
+ // the chip implementation calls this whenever an on-chip register is read
+ // which returns data from one of the chip's input ports; our responsibility
+ // is to produce the current input value so that it can be reflected by the
+ // read operation
+ virtual uint8_t ymfm_io_read(uint8_t port) override
+ {
+ return m_io_read[port & 1].isnull() ? 0 : m_io_read[port & 1]();
+ }
+
+ // handle device start
+ virtual void device_start() override
+ {
+ // allocate our timers
+ for (int tnum = 0; tnum < 2; tnum++)
+ m_timer[tnum] = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(ym_generic_device::fm_timer_handler), this));
+
+ // resolve the handlers
+ m_update_irq.resolve();
+ m_io_read[0].resolve();
+ m_io_read[1].resolve();
+ m_io_write[0].resolve();
+ m_io_write[1].resolve();
+ }
+
+ // timer callbacks
+ void fm_mode_write(void *ptr, int param) { m_engine->engine_mode_write(param); }
+ void fm_check_interrupts(void *ptr, int param) { m_engine->engine_check_interrupts(); }
+ void fm_timer_handler(void *ptr, int param) { m_engine->engine_timer_expired(param); }
+
+ // internal state
+ attotime m_busy_end; // busy end time
+ emu_timer *m_timer[2]; // two timers
+ devcb_write_line m_update_irq; // IRQ update callback
+ devcb_read8 m_io_read[2]; // up to 2 input port handlers
+ devcb_write8 m_io_write[2]; // up to 2 output port handlers
+};
+
+
+// ======================> ymfm_device_base
+
+// this template provides most of the basics used by device objects in MAME
+// that wrap ymfm chips; it provides basic read/write functions; however, this
+// class is not intended to be used directly -- rather, devices should inherit
+// from either ymfm_device_base or ymfm_ssg_device_base, depending on whether
+// they include an SSG or not
+template<typename ChipClass, bool FMOnly = false>
+class ymfm_device_base : public ym_generic_device
+{
+protected:
+ // for SSG chips, we only create a subset of outputs here:
+ // YM2203 is 4 outputs: 1 mono FM + 3 SSG
+ // YM2608/2610 is 3 outputs: 2 stereo FM + 1 SSG
+ static constexpr int OUTPUTS = FMOnly ? ((ChipClass::OUTPUTS == 4) ? 1 : 2) : ChipClass::OUTPUTS;
+
+public:
+ // constructor
+ ymfm_device_base(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock, device_type type) :
+ ym_generic_device(mconfig, tag, owner, clock, type),
+ m_stream(nullptr),
+ m_chip(*this)
+ {
+ }
+
+ // read access: update the streams before performing the read
+ virtual u8 read(offs_t offset) override { return update_streams().read(offset); }
+ virtual u8 status_r() override { return update_streams().read_status(); }
+
+ // write access: update the strams before performing the write
+ virtual void write(offs_t offset, u8 data) override { update_streams().write(offset, data); }
+ virtual void address_w(u8 data) override { update_streams().write_address(data); }
+ virtual void data_w(u8 data) override { update_streams().write_data(data); }
+
+protected:
+ // handle device start
+ virtual void device_start() override
+ {
+ // let our parent do its startup
+ ym_generic_device::device_start();
+
+ // allocate our stream
+ m_stream = device_sound_interface::stream_alloc(0, OUTPUTS, m_chip.sample_rate(device_t::clock()));
+
+ // compute the size of the save buffer by doing an initial save
+ ymfm::ymfm_saved_state state(m_save_blob, true);
+ m_chip.save_restore(state);
+
+ // now register the blob for save, on the assumption the size won't change
+ save_item(NAME(m_save_blob));
+ }
+
+ // device reset
+ virtual void device_reset() override
+ {
+ m_chip.reset();
+ }
+
+ // handle clock changed
+ virtual void device_clock_changed() override
+ {
+ if (m_stream != nullptr)
+ m_stream->set_sample_rate(m_chip.sample_rate(device_t::clock()));
+ }
+
+ // handle pre-saving by filling the blob
+ virtual void device_pre_save() override
+ {
+ // remember the original blob size
+ auto orig_size = m_save_blob.size();
+
+ // save the state
+ ymfm::ymfm_saved_state state(m_save_blob, true);
+ m_chip.save_restore(state);
+
+ // ensure that the size didn't change since we first allocated
+ if (m_save_blob.size() != orig_size)
+ throw emu_fatalerror("State size changed for ymfm chip");
+ }
+
+ // handle post-loading by restoring from the blob
+ virtual void device_post_load() override
+ {
+ // populate the state from the blob
+ ymfm::ymfm_saved_state state(m_save_blob, false);
+ m_chip.save_restore(state);
+ }
+
+ // sound overrides
+ virtual void sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) override
+ {
+ update_internal(outputs);
+ }
+
+ // update streams
+ virtual ChipClass &update_streams()
+ {
+ m_stream->update();
+ return m_chip;
+ }
+
+ // internal update helper
+ void update_internal(std::vector<write_stream_view> &outputs, int output_shift = 0)
+ {
+ // local buffer to hold samples
+ constexpr int MAX_SAMPLES = 256;
+ typename ChipClass::output_data output[MAX_SAMPLES];
+
+ // parameters
+ int const outcount = std::min(outputs.size(), std::size(output[0].data));
+ int const numsamples = outputs[0].samples();
+
+ // generate the FM/ADPCM stream
+ for (int sampindex = 0; sampindex < numsamples; sampindex += MAX_SAMPLES)
+ {
+ int cursamples = std::min(numsamples - sampindex, MAX_SAMPLES);
+ m_chip.generate(output, cursamples);
+ for (int outnum = 0; outnum < outcount; outnum++)
+ {
+ int eff_outnum = (outnum + output_shift) % OUTPUTS;
+ for (int index = 0; index < cursamples; index++)
+ outputs[eff_outnum].put_int(sampindex + index, output[index].data[outnum], 32768);
+ }
+ }
+ }
+
+ // internal state
+ sound_stream *m_stream; // sound stream
+ ChipClass m_chip; // core chip implementation
+ std::vector<uint8_t> m_save_blob; // state saving buffer
+};
+
+
+// ======================> ymfm_ssg_internal_device_base
+
+// this template adds SSG support to the base template, using ymfm's internal
+// SSG implementation
+template<typename ChipClass>
+class ymfm_ssg_internal_device_base : public ymfm_device_base<ChipClass>
+{
+ using parent = ymfm_device_base<ChipClass>;
+
+public:
+ // constructor
+ ymfm_ssg_internal_device_base(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock, device_type type) :
+ ymfm_device_base<ChipClass>(mconfig, tag, owner, clock, type)
+ {
+ }
+
+ // configuration helpers
+ void set_flags(int flags) { /* not supported when using internal SSG */ }
+
+protected:
+ // sound overrides
+ virtual void sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) override
+ {
+ // shift the output streams so that the SSGs are first:
+ // YM2203 has 4 outputs: 1 FM + 3 SSG, so add 3
+ // YM2608/10 has 3 outputs: 2 FM + 1 SSG, so add 1
+ parent::update_internal(outputs, (ChipClass::OUTPUTS == 4) ? 3 : 1);
+ }
+};
+
+
+// ======================> ymfm_ssg_external_device_base
+
+// this template adds SSG support to the base template, using MAME's YM2149
+// implementation in ay8910.cpp; this is the "classic" way to do it in MAME
+// and is more flexible in terms of output handling
+template<typename ChipClass>
+class ymfm_ssg_external_device_base : public ymfm_device_base<ChipClass, true>, public ymfm::ssg_override
+{
+ using parent = ymfm_device_base<ChipClass, true>;
+
+ // deduce the number of SSG outputs from the total outputs:
+ // YM2203 = mono FM + 3 SSG outputs = 4 total
+ // YM2608/10 = stereo FM + 1 SSG output = 3 total
+ static constexpr int SSG_OUTPUTS = ChipClass::OUTPUTS - parent::OUTPUTS;
+
+public:
+ // constructor
+ ymfm_ssg_external_device_base(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock, device_type type) :
+ ymfm_device_base<ChipClass, true>(mconfig, tag, owner, clock, type),
+ m_ssg_stream(nullptr),
+ m_ssg(*this, "ssg"),
+ m_ssg_flags(((SSG_OUTPUTS == 1) ? AY8910_SINGLE_OUTPUT : 0) | AY8910_LEGACY_OUTPUT)
+ {
+ }
+
+ // configuration helpers
+ void set_flags(int flags)
+ {
+ // don't allow some flags to be changed: there is no pin26 in the embedded chip,
+ // and the number of outputs is configured by the chip itself
+ flags &= ~(AY8910_SINGLE_OUTPUT | YM2149_PIN26_LOW);
+ flags |= m_ssg_flags & AY8910_SINGLE_OUTPUT;
+ m_ssg_flags = flags;
+ if (m_ssg)
+ m_ssg->set_flags(m_ssg_flags);
+ }
+
+protected:
+ using parent::m_chip;
+ using parent::m_io_read;
+ using parent::m_io_write;
+
+ // SSG overrides
+ virtual void ssg_reset() override
+ {
+ m_ssg->reset();
+ }
+
+ virtual uint8_t ssg_read(uint32_t offset) override
+ {
+ m_ssg->address_w(offset);
+ return m_ssg->data_r();
+ }
+
+ virtual void ssg_write(uint32_t offset, uint8_t data) override
+ {
+ m_ssg->address_w(offset);
+ m_ssg->data_w(data);
+ }
+
+ virtual void ssg_prescale_changed() override
+ {
+ device_clock_changed();
+ }
+
+ // device-level overrides
+ virtual void device_add_mconfig(machine_config &config) override
+ {
+ YM2149(config, m_ssg, device_t::clock());
+ m_ssg->set_flags(m_ssg_flags);
+
+ // configure the callbacks to route through our callbacks
+ m_ssg->port_a_read_callback().set(FUNC(ymfm_ssg_external_device_base::io_reader<0>));
+ m_ssg->port_a_write_callback().set(FUNC(ymfm_ssg_external_device_base::io_writer<0>));
+ m_ssg->port_b_read_callback().set(FUNC(ymfm_ssg_external_device_base::io_reader<1>));
+ m_ssg->port_b_write_callback().set(FUNC(ymfm_ssg_external_device_base::io_writer<1>));
+
+ // route outputs through us
+ m_ssg->add_route(0, *this, 1.0, 0);
+ if (SSG_OUTPUTS > 1)
+ {
+ m_ssg->add_route(1, *this, 1.0, 1);
+ m_ssg->add_route(2, *this, 1.0, 2);
+ }
+ }
+
+ // handle device start
+ virtual void device_start() override
+ {
+ // to use the YM2149 in MAME, we allocate our stream with the same number of inputs
+ // and outputs; in our update handler we'll just forward each output from the
+ // embedded YM2149 device through our stream to make it look like it used to when
+ // we were inheriting from ay8910_device
+ m_ssg_stream = device_sound_interface::stream_alloc(SSG_OUTPUTS, SSG_OUTPUTS, SAMPLE_RATE_INPUT_ADAPTIVE);
+
+ // also tell the chip we want to override reads & writes
+ m_chip.ssg_override(*this);
+
+ // SSG streams are expected to be first, so call the parent afterwards
+ parent::device_start();
+ }
+
+ // handle clock changed
+ virtual void device_clock_changed() override
+ {
+ parent::device_clock_changed();
+
+ // derive the effective clock from the computed sample rate
+ m_ssg->set_unscaled_clock(m_chip.ssg_effective_clock(device_t::clock()));
+ }
+
+ // sound overrides
+ virtual void sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) override
+ {
+ // if not the SSG stream, pass it along to our parent
+ if (&stream != m_ssg_stream)
+ return parent::sound_stream_update(stream, inputs, outputs);
+
+ // just copy the streams from the SSG
+ for (int index = 0; index < SSG_OUTPUTS; index++)
+ outputs[index] = inputs[index];
+ }
+
+ // internal helper to update all our streams
+ virtual ChipClass &update_streams() override
+ {
+ m_ssg_stream->update();
+ return parent::update_streams();
+ }
+
+ // I/O reader trampoline
+ template<int Index>
+ uint8_t io_reader()
+ {
+ return m_io_read[Index].isnull() ? 0 : m_io_read[Index](0);
+ }
+
+ // I/O writer trampoline
+ template<int Index>
+ void io_writer(uint8_t data)
+ {
+ if (!m_io_write[Index].isnull())
+ m_io_write[Index](0, data);
+ }
+
+ // internal state
+ sound_stream *m_ssg_stream; // SSG sound stream
+ required_device<ay8910_device> m_ssg; // our embedded SSG device
+ int m_ssg_flags; // SSG flags
+};
+
+
+// now pick the right one
+#if USE_BUILTIN_SSG
+template<typename ChipClass>
+using ymfm_ssg_device_base = ymfm_ssg_internal_device_base<ChipClass>;
+#else
+template<typename ChipClass>
+using ymfm_ssg_device_base = ymfm_ssg_external_device_base<ChipClass>;
+#endif
+
+#endif // MAME_SOUND_YMFM_H