diff options
Diffstat (limited to 'src/emu')
121 files changed, 8057 insertions, 5330 deletions
diff --git a/src/emu/addrmap.h b/src/emu/addrmap.h index 7d350c14afd..25ff7d8cec5 100644 --- a/src/emu/addrmap.h +++ b/src/emu/addrmap.h @@ -157,6 +157,30 @@ public: address_map_entry &portw(const char *tag) { m_write.m_type = AMH_PORT; m_write.m_tag = tag; return *this; } address_map_entry &portrw(const char *tag) { portr(tag); portw(tag); return *this; } + template<bool req> address_map_entry &portr(ioport_finder<req> &finder) { + const std::pair<device_t &, const char *> target(finder.finder_target()); + assert(&target.first == &m_devbase); + m_read.m_type = AMH_PORT; + m_read.m_tag = target.second; + return *this; + } + + template<bool req> address_map_entry &portw(ioport_finder<req> &finder) { + const std::pair<device_t &, const char *> target(finder.finder_target()); + assert(&target.first == &m_devbase); + m_write.m_type = AMH_PORT; + m_write.m_tag = target.second; + return *this; + } + + template<bool req> address_map_entry &portrw(ioport_finder<req> &finder) { + const std::pair<device_t &, const char *> target(finder.finder_target()); + assert(&target.first == &m_devbase); + m_write.m_type = m_read.m_type = AMH_PORT; + m_write.m_tag = m_read.m_tag = target.second; + return *this; + } + // memory bank configuration address_map_entry &bankr(const char *tag) { m_read.m_type = AMH_BANK; m_read.m_tag = tag; return *this; } address_map_entry &bankw(const char *tag) { m_write.m_type = AMH_BANK; m_write.m_tag = tag; return *this; } diff --git a/src/emu/audio_effects/aeffect.cpp b/src/emu/audio_effects/aeffect.cpp new file mode 100644 index 00000000000..dcff278099f --- /dev/null +++ b/src/emu/audio_effects/aeffect.cpp @@ -0,0 +1,46 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#include "emu.h" +#include "aeffect.h" +#include "filter.h" +#include "compressor.h" +#include "reverb.h" +#include "eq.h" + +const char *const audio_effect::effect_names[COUNT] = { + "Filters", + "Compressor", + "Reverb", + "Equalizer" +}; + +audio_effect *audio_effect::create(int type, u32 sample_rate, audio_effect *def) +{ + switch(type) { + case FILTER: return new audio_effect_filter (sample_rate, def); + case COMPRESSOR: return new audio_effect_compressor(sample_rate, def); + case REVERB: return new audio_effect_reverb (sample_rate, def); + case EQ: return new audio_effect_eq (sample_rate, def); + } + return nullptr; +} + + +void audio_effect::copy(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) const +{ + u32 samples = src.available_samples(); + dest.prepare_space(samples); + u32 channels = src.channels(); + for(u32 channel = 0; channel != channels; channel++) { + const sample_t *srcd = src.ptrs(channel, 0); + sample_t *destd = dest.ptrw(channel, 0); + std::copy(srcd, srcd + samples, destd); + } + dest.commit(samples); +} + +u32 audio_effect::history_size() const +{ + return 0; +} diff --git a/src/emu/audio_effects/aeffect.h b/src/emu/audio_effects/aeffect.h new file mode 100644 index 00000000000..72e6279f1e6 --- /dev/null +++ b/src/emu/audio_effects/aeffect.h @@ -0,0 +1,43 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#pragma once + +#ifndef MAME_EMU_AUDIO_EFFECTS_AEFFECT_H +#define MAME_EMU_AUDIO_EFFECTS_AEFFECT_H + +class audio_effect +{ +public: + using sample_t = sound_stream::sample_t; + + enum { + FILTER, + COMPRESSOR, + REVERB, + EQ, + COUNT + }; + + static const char *const effect_names[COUNT]; + + static audio_effect *create(int type, u32 sample_rate, audio_effect *def = nullptr); + + audio_effect(u32 sample_rate, audio_effect *def) : m_default(def), m_sample_rate(sample_rate) {} + virtual ~audio_effect() = default; + + void copy(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) const; + + virtual int type() const = 0; + virtual u32 history_size() const; + virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) = 0; + virtual void config_load(util::xml::data_node const *ef_node) = 0; + virtual void config_save(util::xml::data_node *ef_node) const = 0; + virtual void default_changed() = 0; + +protected: + audio_effect *m_default; + u32 m_sample_rate; +}; + +#endif diff --git a/src/emu/audio_effects/compressor.cpp b/src/emu/audio_effects/compressor.cpp new file mode 100644 index 00000000000..06c054a4837 --- /dev/null +++ b/src/emu/audio_effects/compressor.cpp @@ -0,0 +1,28 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#include "emu.h" +#include "compressor.h" +#include "xmlfile.h" + +audio_effect_compressor::audio_effect_compressor(u32 sample_rate, audio_effect *def) : audio_effect(sample_rate, def) +{ +} + + +void audio_effect_compressor::config_load(util::xml::data_node const *ef_node) +{ +} + +void audio_effect_compressor::config_save(util::xml::data_node *ef_node) const +{ +} + +void audio_effect_compressor::default_changed() +{ +} + +void audio_effect_compressor::apply(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) +{ + copy(src, dest); +} diff --git a/src/emu/audio_effects/compressor.h b/src/emu/audio_effects/compressor.h new file mode 100644 index 00000000000..551212f34e5 --- /dev/null +++ b/src/emu/audio_effects/compressor.h @@ -0,0 +1,24 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#pragma once + +#ifndef MAME_EMU_AUDIO_EFFECTS_COMPRESSOR_H +#define MAME_EMU_AUDIO_EFFECTS_COMPRESSOR_H + +#include "aeffect.h" + +class audio_effect_compressor : public audio_effect +{ +public: + audio_effect_compressor(u32 sample_rate, audio_effect *def); + virtual ~audio_effect_compressor() = default; + + virtual int type() const override { return COMPRESSOR; } + virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) override; + virtual void config_load(util::xml::data_node const *ef_node) override; + virtual void config_save(util::xml::data_node *ef_node) const override; + virtual void default_changed() override; +}; + +#endif diff --git a/src/emu/audio_effects/eq.cpp b/src/emu/audio_effects/eq.cpp new file mode 100644 index 00000000000..799b2f8c9b5 --- /dev/null +++ b/src/emu/audio_effects/eq.cpp @@ -0,0 +1,331 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#include "emu.h" +#include "eq.h" +#include "xmlfile.h" + +// This effect implements a parametric EQ using peak and shelf filters + +// Formulas taken from (with some fixes): + +// [Zölzer 2011] "DAFX: Digital Audio Effects", Udo Zölzer, Second Edition, Wiley publishing, 2011 (Tables 2.3 and 2.4) +// [Zölzer 2008] "Digital Audio Signal Processing", Udo Zölzer, Second Edition, Wiley publishing, 2008 (Tables 5.3, 5.4 and 5.5) + +audio_effect_eq::audio_effect_eq(u32 sample_rate, audio_effect *def) : audio_effect(sample_rate, def) +{ + // Minimal init to avoid using uninitialized values when reset_* + // recomputes filters + + for(u32 band = 0; band != BANDS; band++) { + m_q[band] = 0.7; + m_f[band] = 1000; + m_db[band] = 0; + } + + reset_mode(); + reset_low_shelf(); + reset_high_shelf(); + for(u32 band = 0; band != BANDS; band++) { + reset_q(band); + reset_f(band); + reset_db(band); + } +} + +void audio_effect_eq::reset_mode() +{ + audio_effect_eq *d = static_cast<audio_effect_eq *>(m_default); + m_isset_mode = false; + m_mode = d ? d->mode() : 1; +} + +void audio_effect_eq::reset_q(u32 band) +{ + audio_effect_eq *d = static_cast<audio_effect_eq *>(m_default); + m_isset_q[band] = false; + m_q[band] = d ? d->q(band) : 0.7; + build_filter(band); +} + +void audio_effect_eq::reset_f(u32 band) +{ + static const u32 defs[BANDS] = { 80, 200, 500, 3200, 8000 }; + audio_effect_eq *d = static_cast<audio_effect_eq *>(m_default); + m_isset_f[band] = false; + m_f[band] = d ? d->f(band) : defs[band]; + build_filter(band); +} + +void audio_effect_eq::reset_db(u32 band) +{ + audio_effect_eq *d = static_cast<audio_effect_eq *>(m_default); + m_isset_db[band] = false; + m_db[band] = d ? d->db(band) : 0; + build_filter(band); +} + +void audio_effect_eq::reset_low_shelf() +{ + audio_effect_eq *d = static_cast<audio_effect_eq *>(m_default); + m_isset_low_shelf = false; + m_low_shelf = d ? d->low_shelf() : true; + build_filter(0); +} + +void audio_effect_eq::reset_high_shelf() +{ + audio_effect_eq *d = static_cast<audio_effect_eq *>(m_default); + m_isset_high_shelf = false; + m_high_shelf = d ? d->high_shelf() : true; + build_filter(BANDS-1); +} + +void audio_effect_eq::config_load(util::xml::data_node const *ef_node) +{ + if(ef_node->has_attribute("mode")) { + m_mode = ef_node->get_attribute_int("mode", 0); + m_isset_mode = true; + } else + reset_mode(); + + if(ef_node->has_attribute("low_shelf")) { + m_low_shelf = ef_node->get_attribute_int("low_shelf", 0); + m_isset_low_shelf = true; + } else + reset_low_shelf(); + + if(ef_node->has_attribute("high_shelf")) { + m_high_shelf = ef_node->get_attribute_int("high_shelf", 0); + m_isset_high_shelf = true; + } else + reset_high_shelf(); + + for(u32 band = 0; band != BANDS; band++) { + if(ef_node->has_attribute(util::string_format("q%d", band+1).c_str())) { + m_q[band] = ef_node->get_attribute_float(util::string_format("q%d", band+1).c_str(), 0); + m_isset_q[band] = true; + } else + reset_q(band); + + if(ef_node->has_attribute(util::string_format("f%d", band+1).c_str())) { + m_f[band] = ef_node->get_attribute_float(util::string_format("f%d", band+1).c_str(), 0); + m_isset_f[band] = true; + } else + reset_f(band); + + if(ef_node->has_attribute(util::string_format("db%d", band+1).c_str())) { + m_db[band] = ef_node->get_attribute_float(util::string_format("db%d", band+1).c_str(), 0); + m_isset_db[band] = true; + } else + reset_db(band); + } +} + +void audio_effect_eq::config_save(util::xml::data_node *ef_node) const +{ + if(m_isset_mode) + ef_node->set_attribute_int("mode", m_mode); + if(m_isset_low_shelf) + ef_node->set_attribute_int("low_shelf", m_low_shelf); + if(m_isset_high_shelf) + ef_node->set_attribute_int("high_shelf", m_high_shelf); + for(u32 band = 0; band != BANDS; band++) { + if(m_isset_q[band]) + ef_node->set_attribute_float(util::string_format("q%d", band+1).c_str(), m_q[band]); + if(m_isset_f[band]) + ef_node->set_attribute_float(util::string_format("f%d", band+1).c_str(), m_f[band]); + if(m_isset_db[band]) + ef_node->set_attribute_float(util::string_format("db%d", band+1).c_str(), m_db[band]); + } +} + +void audio_effect_eq::default_changed() +{ + if(!m_default) + return; + if(!m_isset_mode) + reset_mode(); + if(!m_isset_low_shelf) + reset_low_shelf(); + if(!m_isset_high_shelf) + reset_high_shelf(); + for(u32 band = 0; band != BANDS; band++) { + if(!m_isset_q[band]) + reset_q(band); + if(!m_isset_f[band]) + reset_f(band); + if(!m_isset_db[band]) + reset_db(band); + } +} + +void audio_effect_eq::set_mode(u32 mode) +{ + m_isset_mode = true; + m_mode = mode; +} + +void audio_effect_eq::set_q(u32 band, float q) +{ + m_isset_q[band] = true; + m_q[band] = q; + build_filter(band); +} + +void audio_effect_eq::set_f(u32 band, float f) +{ + m_isset_f[band] = true; + m_f[band] = f; + build_filter(band); +} + +void audio_effect_eq::set_db(u32 band, float db) +{ + m_isset_db[band] = true; + m_db[band] = db; + build_filter(band); +} + +void audio_effect_eq::set_low_shelf(bool active) +{ + m_isset_low_shelf = true; + m_low_shelf = active; + build_filter(0); +} + +void audio_effect_eq::set_high_shelf(bool active) +{ + m_isset_high_shelf = true; + m_high_shelf = active; + build_filter(BANDS-1); +} + +void audio_effect_eq::build_filter(u32 band) +{ + if(band == 0 && m_low_shelf) { + build_low_shelf(band); + return; + } + if(band == BANDS-1 && m_high_shelf) { + build_high_shelf(band); + return; + } + build_peak(band); +} + +void audio_effect_eq::build_low_shelf(u32 band) +{ + auto &fi = m_filter[band]; + if(m_db[band] == 0) { + fi.clear(); + return; + } + + float V = pow(10, abs(m_db[band])/20); + float K = tan(M_PI*m_f[band]/m_sample_rate); + float K2 = K*K; + + if(m_db[band] > 0) { + float d = 1 + sqrt(2)*K + K2; + fi.m_b0 = (1 + sqrt(2*V)*K + V*K2)/d; + fi.m_b1 = 2*(V*K2-1)/d; + fi.m_b2 = (1 - sqrt(2*V)*K + V*K2)/d; + fi.m_a1 = 2*(K2-1)/d; + fi.m_a2 = (1 - sqrt(2)*K + K2)/d; + } else { + float d = 1 + sqrt(2*V)*K + V*K2; + fi.m_b0 = (1 + sqrt(2)*K + K2)/d; + fi.m_b1 = 2*(K2-1)/d; + fi.m_b2 = (1 - sqrt(2)*K + K2)/d; + fi.m_a1 = 2*(V*K2-1)/d; + fi.m_a2 = (1 - sqrt(2*V)*K + V*K2)/d; + } +} + +void audio_effect_eq::build_high_shelf(u32 band) +{ + auto &fi = m_filter[band]; + if(m_db[band] == 0) { + fi.clear(); + return; + } + + float V = pow(10, m_db[band]/20); + float K = tan(M_PI*m_f[band]/m_sample_rate); + float K2 = K*K; + + if(m_db[band] > 0) { + float d = 1 + sqrt(2)*K + K2; + fi.m_b0 = (V + sqrt(2*V)*K + K2)/d; + fi.m_b1 = 2*(K2-V)/d; + fi.m_b2 = (V - sqrt(2*V)*K + K2)/d; + fi.m_a1 = 2*(K2-1)/d; + fi.m_a2 = (1 - sqrt(2)*K + K2)/d; + } else { + float d = 1 + sqrt(2*V)*K + V*K2; + fi.m_b0 = V*(1 + sqrt(2)*K + K2)/d; + fi.m_b1 = 2*V*(K2-1)/d; + fi.m_b2 = V*(1 - sqrt(2)*K + K2)/d; + fi.m_a1 = 2*(V*K2-1)/d; + fi.m_a2 = (1 - sqrt(2*V)*K + V*K2)/d; + } +} + +void audio_effect_eq::build_peak(u32 band) +{ + auto &fi = m_filter[band]; + if(m_db[band] == 0) { + fi.clear(); + return; + } + + float V = pow(10, m_db[band]/20); + float K = tan(M_PI*m_f[band]/m_sample_rate); + float K2 = K*K; + float Q = m_q[band]; + + if(m_db[band] > 0) { + float d = 1 + K/Q + K2; + fi.m_b0 = (1 + V*K/Q + K2)/d; + fi.m_b1 = 2*(K2-1)/d; + fi.m_b2 = (1 - V*K/Q + K2)/d; + fi.m_a1 = fi.m_b1; + fi.m_a2 = (1 - K/Q + K2)/d; + } else { + float d = 1 + K/(V*Q) + K2; + fi.m_b0 = (1 + K/Q + K2)/d; + fi.m_b1 = 2*(K2-1)/d; + fi.m_b2 = (1 - K/Q + K2)/d; + fi.m_a1 = fi.m_b1; + fi.m_a2 = (1 - K/(V*Q) + K2)/d; + } +} + + +void audio_effect_eq::apply(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) +{ + if(m_mode == 0) { + copy(src, dest); + return; + } + + u32 samples = src.available_samples(); + dest.prepare_space(samples); + u32 channels = src.channels(); + if(m_history.empty()) + m_history.resize(channels); + + for(u32 channel = 0; channel != channels; channel++) { + const sample_t *srcd = src.ptrs(channel, 0); + sample_t *destd = dest.ptrw(channel, 0); + for(u32 sample = 0; sample != samples; sample++) { + m_history[channel][0].push(*srcd++); + for(u32 band = 0; band != BANDS; band++) + m_filter[band].apply(m_history[channel][band], m_history[channel][band+1]); + *destd++ = m_history[channel][BANDS].m_v0; + } + } + + dest.commit(samples); +} diff --git a/src/emu/audio_effects/eq.h b/src/emu/audio_effects/eq.h new file mode 100644 index 00000000000..919d5292837 --- /dev/null +++ b/src/emu/audio_effects/eq.h @@ -0,0 +1,84 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#pragma once + +#ifndef MAME_EMU_AUDIO_EFFECTS_EQ_H +#define MAME_EMU_AUDIO_EFFECTS_EQ_H + +#include "aeffect.h" + +class audio_effect_eq : public audio_effect +{ +public: + enum { BANDS = 5 }; + + audio_effect_eq(u32 sample_rate, audio_effect *def); + virtual ~audio_effect_eq() = default; + + virtual int type() const override { return EQ; } + virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) override; + virtual void config_load(util::xml::data_node const *ef_node) override; + virtual void config_save(util::xml::data_node *ef_node) const override; + virtual void default_changed() override; + + void set_mode(u32 mode); + void set_q(u32 band, float q); + void set_f(u32 band, float f); + void set_db(u32 band, float db); + void set_low_shelf(bool active); + void set_high_shelf(bool active); + + u32 mode() const { return m_mode; } + float q(u32 band) const { return m_q[band]; } + float f(u32 band) const { return m_f[band]; } + float db(u32 band) const { return m_db[band]; } + bool low_shelf() const { return m_low_shelf; } + bool high_shelf() const { return m_high_shelf; } + + bool isset_mode() const { return m_isset_mode; } + bool isset_q(u32 band) const { return m_isset_q[band]; } + bool isset_f(u32 band) const { return m_isset_f[band]; } + bool isset_db(u32 band) const { return m_isset_db[band]; } + bool isset_low_shelf() const { return m_isset_low_shelf; } + bool isset_high_shelf() const { return m_isset_high_shelf; } + + void reset_mode(); + void reset_q(u32 band); + void reset_f(u32 band); + void reset_db(u32 band); + void reset_low_shelf(); + void reset_high_shelf(); + +private: + struct history { + float m_v0, m_v1, m_v2; + history() { m_v0 = m_v1 = m_v2 = 0; } + void push(float v) { m_v2 = m_v1; m_v1 = m_v0; m_v0 = v; } + }; + + struct filter { + float m_a1, m_a2, m_b0, m_b1, m_b2; + void clear() { m_a1 = 0; m_a2 = 0; m_b0 = 1; m_b1 = 0; m_b2 = 0; } + void apply(history &x, history &y) const { + y.push(m_b0 * x.m_v0 + m_b1 * x.m_v1 + m_b2 * x.m_v2 - m_a1 * y.m_v0 - m_a2 * y.m_v1); + } + }; + + u32 m_mode; + float m_q[BANDS], m_f[BANDS], m_db[BANDS]; + bool m_low_shelf, m_high_shelf; + std::array<filter, BANDS> m_filter; + std::vector<std::array<history, BANDS+1>> m_history; + + bool m_isset_mode, m_isset_low_shelf, m_isset_high_shelf; + bool m_isset_q[BANDS], m_isset_f[BANDS], m_isset_db[BANDS]; + + void build_filter(u32 band); + + void build_low_shelf(u32 band); + void build_high_shelf(u32 band); + void build_peak(u32 band); +}; + +#endif diff --git a/src/emu/audio_effects/filter.cpp b/src/emu/audio_effects/filter.cpp new file mode 100644 index 00000000000..70cf7ddc967 --- /dev/null +++ b/src/emu/audio_effects/filter.cpp @@ -0,0 +1,259 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#include "emu.h" +#include "filter.h" +#include "xmlfile.h" + +// This effect implements a couple of very standard biquad filters, +// one lowpass and one highpass. + +// Formulas taken from: + +// [Zölzer 2011] "DAFX: Digital Audio Effects", Udo Zölzer, Second Edition, Wiley publishing, 2011 (Table 2.2) + + +audio_effect_filter::audio_effect_filter(u32 sample_rate, audio_effect *def) : audio_effect(sample_rate, def) +{ + // Minimal init to avoid using uninitialized values when reset_* + // recomputes filters + m_fl = m_fh = 1000; + m_ql = m_qh = 0.7; + + reset_lowpass_active(); + reset_highpass_active(); + reset_fl(); + reset_fh(); + reset_ql(); + reset_qh(); +} + +void audio_effect_filter::reset_lowpass_active() +{ + audio_effect_filter *d = static_cast<audio_effect_filter *>(m_default); + m_isset_lowpass_active = false; + m_lowpass_active = d ? d->lowpass_active() : false; + build_lowpass(); +} + +void audio_effect_filter::reset_highpass_active() +{ + audio_effect_filter *d = static_cast<audio_effect_filter *>(m_default); + m_isset_highpass_active = false; + m_highpass_active = d ? d->highpass_active() : true; + build_highpass(); +} + +void audio_effect_filter::reset_fl() +{ + audio_effect_filter *d = static_cast<audio_effect_filter *>(m_default); + m_isset_fl = false; + m_fl = d ? d->fl() : 8000; + build_lowpass(); +} + +void audio_effect_filter::reset_ql() +{ + audio_effect_filter *d = static_cast<audio_effect_filter *>(m_default); + m_isset_ql = false; + m_ql = d ? d->ql() : 0.7; + build_lowpass(); +} + +void audio_effect_filter::reset_fh() +{ + audio_effect_filter *d = static_cast<audio_effect_filter *>(m_default); + m_isset_fh = false; + m_fh = d ? d->fh() : 40; + build_highpass(); +} + +void audio_effect_filter::reset_qh() +{ + audio_effect_filter *d = static_cast<audio_effect_filter *>(m_default); + m_isset_qh = false; + m_qh = d ? d->qh() : 0.7; + build_highpass(); +} + +void audio_effect_filter::config_load(util::xml::data_node const *ef_node) +{ + if(ef_node->has_attribute("lowpass_active")) { + m_lowpass_active = ef_node->get_attribute_int("lowpass_active", 0); + m_isset_lowpass_active = true; + } else + reset_lowpass_active(); + + if(ef_node->has_attribute("fl")) { + m_fl = ef_node->get_attribute_float("fl", 0); + m_isset_fl = true; + } else + reset_fl(); + + if(ef_node->has_attribute("ql")) { + m_ql = ef_node->get_attribute_float("ql", 0); + m_isset_ql = true; + } else + reset_ql(); + + if(ef_node->has_attribute("highpass_active")) { + m_highpass_active = ef_node->get_attribute_int("highpass_active", 0); + m_isset_highpass_active = true; + } else + reset_highpass_active(); + + if(ef_node->has_attribute("fh")) { + m_fh = ef_node->get_attribute_float("fh", 0); + m_isset_fh = true; + } else + reset_fh(); + + if(ef_node->has_attribute("qh")) { + m_qh = ef_node->get_attribute_float("qh", 0); + m_isset_qh = true; + } else + reset_qh(); +} + +void audio_effect_filter::config_save(util::xml::data_node *ef_node) const +{ + if(m_isset_lowpass_active) + ef_node->set_attribute_int("lowpass_active", m_lowpass_active); + if(m_isset_fl) + ef_node->set_attribute_float("fl", m_fl); + if(m_isset_ql) + ef_node->set_attribute_float("ql", m_ql); + if(m_isset_highpass_active) + ef_node->set_attribute_int("highpass_active", m_highpass_active); + if(m_isset_fh) + ef_node->set_attribute_float("fh", m_fh); + if(m_isset_qh) + ef_node->set_attribute_float("qh", m_qh); +} + +void audio_effect_filter::default_changed() +{ + if(!m_isset_lowpass_active) + reset_lowpass_active(); + if(!m_isset_highpass_active) + reset_highpass_active(); + if(!m_isset_fl) + reset_fl(); + if(!m_isset_fh) + reset_fh(); + if(!m_isset_ql) + reset_ql(); + if(!m_isset_qh) + reset_qh(); +} + +void audio_effect_filter::apply(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) +{ + if(!m_lowpass_active && !m_highpass_active) { + copy(src, dest); + return; + } + + u32 samples = src.available_samples(); + dest.prepare_space(samples); + u32 channels = src.channels(); + if(m_history.empty()) + m_history.resize(channels); + + for(u32 channel = 0; channel != channels; channel++) { + const sample_t *srcd = src.ptrs(channel, 0); + sample_t *destd = dest.ptrw(channel, 0); + for(u32 sample = 0; sample != samples; sample++) { + m_history[channel][0].push(*srcd++); + m_filter[0].apply(m_history[channel][0], m_history[channel][1]); + m_filter[1].apply(m_history[channel][1], m_history[channel][2]); + *destd++ = m_history[channel][2].m_v0; + } + } + + dest.commit(samples); + +} + +void audio_effect_filter::set_lowpass_active(bool active) +{ + m_isset_lowpass_active = true; + m_lowpass_active = active; + build_lowpass(); +} + +void audio_effect_filter::set_highpass_active(bool active) +{ + m_isset_highpass_active = true; + m_highpass_active = active; + build_highpass(); +} + +void audio_effect_filter::set_fl(float f) +{ + m_isset_fl = true; + m_fl = f; + build_lowpass(); +} + +void audio_effect_filter::set_fh(float f) +{ + m_isset_fh = true; + m_fh = f; + build_highpass(); +} + +void audio_effect_filter::set_ql(float q) +{ + m_isset_ql = true; + m_ql = q; + build_lowpass(); +} + +void audio_effect_filter::set_qh(float q) +{ + m_isset_qh = true; + m_qh = q; + build_highpass(); +} + +void audio_effect_filter::build_highpass() +{ + auto &fi = m_filter[0]; + if(!m_highpass_active) { + fi.clear(); + return; + } + + float K = tan(M_PI*m_fh/m_sample_rate); + float K2 = K*K; + float Q = m_qh; + + float d = K2*Q + K + Q; + fi.m_b0 = Q/d; + fi.m_b1 = -2*Q/d; + fi.m_b2 = fi.m_b0; + fi.m_a1 = 2*Q*(K2-1)/d; + fi.m_a2 = (K2*Q - K + Q)/d; +} + +void audio_effect_filter::build_lowpass() +{ + auto &fi = m_filter[1]; + if(!m_lowpass_active) { + fi.clear(); + return; + } + + float K = tan(M_PI*m_fl/m_sample_rate); + float K2 = K*K; + float Q = m_ql; + + float d = K2*Q + K + Q; + fi.m_b0 = K2*Q/d; + fi.m_b1 = 2*K2*Q /d; + fi.m_b2 = fi.m_b0; + fi.m_a1 = 2*Q*(K2-1)/d; + fi.m_a2 = (K2*Q - K + Q)/d; +} + diff --git a/src/emu/audio_effects/filter.h b/src/emu/audio_effects/filter.h new file mode 100644 index 00000000000..13463bd6b4b --- /dev/null +++ b/src/emu/audio_effects/filter.h @@ -0,0 +1,78 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#pragma once + +#ifndef MAME_EMU_AUDIO_EFFECTS_FILTER_H +#define MAME_EMU_AUDIO_EFFECTS_FILTER_H + +#include "aeffect.h" + +class audio_effect_filter : public audio_effect +{ +public: + audio_effect_filter(u32 sample_rate, audio_effect *def); + virtual ~audio_effect_filter() = default; + + virtual int type() const override { return FILTER; } + virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) override; + virtual void config_load(util::xml::data_node const *ef_node) override; + virtual void config_save(util::xml::data_node *ef_node) const override; + virtual void default_changed() override; + + void set_lowpass_active(bool active); + void set_highpass_active(bool active); + void set_fl(float f); + void set_fh(float f); + void set_ql(float q); + void set_qh(float q); + + bool lowpass_active() const { return m_lowpass_active; } + bool highpass_active() const { return m_highpass_active; } + float fl() const { return m_fl; } + float fh() const { return m_fh; } + float ql() const { return m_ql; } + float qh() const { return m_qh; } + + bool isset_lowpass_active() const { return m_isset_lowpass_active; } + bool isset_highpass_active() const { return m_isset_highpass_active; } + bool isset_fl() const { return m_isset_fl; } + bool isset_fh() const { return m_isset_fh; } + bool isset_ql() const { return m_isset_ql; } + bool isset_qh() const { return m_isset_qh; } + + void reset_lowpass_active(); + void reset_highpass_active(); + void reset_fl(); + void reset_fh(); + void reset_ql(); + void reset_qh(); + +private: + struct history { + float m_v0, m_v1, m_v2; + history() { m_v0 = m_v1 = m_v2 = 0; } + void push(float v) { m_v2 = m_v1; m_v1 = m_v0; m_v0 = v; } + }; + + struct filter { + float m_a1, m_a2, m_b0, m_b1, m_b2; + void clear() { m_a1 = 0; m_a2 = 0; m_b0 = 1; m_b1 = 0; m_b2 = 0; } + void apply(history &x, history &y) const { + y.push(m_b0 * x.m_v0 + m_b1 * x.m_v1 + m_b2 * x.m_v2 - m_a1 * y.m_v0 - m_a2 * y.m_v1); + } + }; + + bool m_isset_lowpass_active, m_isset_highpass_active; + bool m_isset_fl, m_isset_fh, m_isset_ql, m_isset_qh; + + bool m_lowpass_active, m_highpass_active; + float m_fl, m_fh, m_ql, m_qh; + std::array<filter, 2> m_filter; + std::vector<std::array<history, 3>> m_history; + + void build_lowpass(); + void build_highpass(); +}; + +#endif diff --git a/src/emu/audio_effects/reverb.cpp b/src/emu/audio_effects/reverb.cpp new file mode 100644 index 00000000000..c7f231a9d92 --- /dev/null +++ b/src/emu/audio_effects/reverb.cpp @@ -0,0 +1,28 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#include "emu.h" +#include "reverb.h" +#include "xmlfile.h" + +audio_effect_reverb::audio_effect_reverb(u32 sample_rate, audio_effect *def) : audio_effect(sample_rate, def) +{ +} + + +void audio_effect_reverb::config_load(util::xml::data_node const *ef_node) +{ +} + +void audio_effect_reverb::config_save(util::xml::data_node *ef_node) const +{ +} + +void audio_effect_reverb::default_changed() +{ +} + +void audio_effect_reverb::apply(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) +{ + copy(src, dest); +} diff --git a/src/emu/audio_effects/reverb.h b/src/emu/audio_effects/reverb.h new file mode 100644 index 00000000000..36aaabb697b --- /dev/null +++ b/src/emu/audio_effects/reverb.h @@ -0,0 +1,24 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#pragma once + +#ifndef MAME_EMU_AUDIO_EFFECTS_REVERB_H +#define MAME_EMU_AUDIO_EFFECTS_REVERB_H + +#include "aeffect.h" + +class audio_effect_reverb : public audio_effect +{ +public: + audio_effect_reverb(u32 sample_rate, audio_effect *def); + virtual ~audio_effect_reverb() = default; + + virtual int type() const override { return REVERB; } + virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) override; + virtual void config_load(util::xml::data_node const *ef_node) override; + virtual void config_save(util::xml::data_node *ef_node) const override; + virtual void default_changed() override; +}; + +#endif diff --git a/src/emu/bookkeeping.cpp b/src/emu/bookkeeping.cpp index 9f3e414e03f..47e4b2a195a 100644 --- a/src/emu/bookkeeping.cpp +++ b/src/emu/bookkeeping.cpp @@ -22,11 +22,11 @@ // bookkeeping_manager - constructor //------------------------------------------------- -bookkeeping_manager::bookkeeping_manager(running_machine &machine) - : m_machine(machine), - m_dispensed_tickets(0) +bookkeeping_manager::bookkeeping_manager(running_machine &machine) : + m_machine(machine), + m_dispensed_tickets(0) { - /* reset coin counters */ + // reset coin counters for (int counternum = 0; counternum < COIN_COUNTERS; counternum++) { m_lastcoin[counternum] = 0; @@ -53,6 +53,16 @@ bookkeeping_manager::bookkeeping_manager(running_machine &machine) ***************************************************************************/ /*------------------------------------------------- + increment_dispensed_tickets - increment the + number of dispensed tickets +-------------------------------------------------*/ + +void bookkeeping_manager::increment_dispensed_tickets(int delta) +{ + m_dispensed_tickets += delta; +} + +/*------------------------------------------------- get_dispensed_tickets - return the number of tickets dispensed -------------------------------------------------*/ @@ -64,13 +74,13 @@ int bookkeeping_manager::get_dispensed_tickets() const /*------------------------------------------------- - increment_dispensed_tickets - increment the - number of dispensed tickets + reset_dispensed_tickets - reset the number of + tickets dispensed -------------------------------------------------*/ -void bookkeeping_manager::increment_dispensed_tickets(int delta) +void bookkeeping_manager::reset_dispensed_tickets() { - m_dispensed_tickets += delta; + m_dispensed_tickets = 0; } @@ -156,8 +166,8 @@ void bookkeeping_manager::coin_counter_w(int num, int on) if (num >= std::size(m_coin_count)) return; - /* Count it only if the data has changed from 0 to non-zero */ - if (on && (m_lastcoin[num] == 0)) + // count it only if the data has changed from 0 to non-zero + if (machine().time() > attotime::zero && on && (m_lastcoin[num] == 0)) m_coin_count[num]++; m_lastcoin[num] = on; } @@ -177,10 +187,23 @@ int bookkeeping_manager::coin_counter_get_count(int num) /*------------------------------------------------- + coin_counter_reset_count - reset the coin count + for a given coin +-------------------------------------------------*/ + +void bookkeeping_manager::coin_counter_reset_count(int num) +{ + if (num >= std::size(m_coin_count)) + return; + m_coin_count[num] = 0; +} + + +/*------------------------------------------------- coin_lockout_w - locks out one coin input -------------------------------------------------*/ -void bookkeeping_manager::coin_lockout_w(int num,int on) +void bookkeeping_manager::coin_lockout_w(int num, int on) { if (num >= std::size(m_coinlockedout)) return; diff --git a/src/emu/bookkeeping.h b/src/emu/bookkeeping.h index 0d09c497b36..038d924f12f 100644 --- a/src/emu/bookkeeping.h +++ b/src/emu/bookkeeping.h @@ -32,11 +32,14 @@ public: bookkeeping_manager(running_machine &machine); // ----- tickets ----- + // increment the number of dispensed tickets + void increment_dispensed_tickets(int delta); + // return the number of tickets dispensed int get_dispensed_tickets() const; - // increment the number of dispensed tickets - void increment_dispensed_tickets(int delta); + // reset the number of dispensed tickets + void reset_dispensed_tickets(); // ----- coin counters ----- // write to a particular coin counter (clocks on active high edge) @@ -45,6 +48,9 @@ public: // return the coin count for a given coin int coin_counter_get_count(int num); + // reset the coin count for a given coin + void coin_counter_reset_count(int num); + // enable/disable coin lockout for a particular coin void coin_lockout_w(int num, int on); @@ -56,6 +62,7 @@ public: // getters running_machine &machine() const { return m_machine; } + private: void config_load(config_type cfg_type, config_level cfg_level, util::xml::data_node const *parentnode); void config_save(config_type cfg_type, util::xml::data_node *parentnode); diff --git a/src/emu/debug/debugcmd.cpp b/src/emu/debug/debugcmd.cpp index 730565494a4..0b7efa44a93 100644 --- a/src/emu/debug/debugcmd.cpp +++ b/src/emu/debug/debugcmd.cpp @@ -469,10 +469,14 @@ void debugger_commands::execute_print(const std::vector<std::string_view> ¶m mini_printf - safe printf to a buffer -------------------------------------------------*/ -bool debugger_commands::mini_printf(std::ostream &stream, std::string_view format, int params, u64 *param) +bool debugger_commands::mini_printf(std::ostream &stream, const std::vector<std::string_view> ¶ms) { + std::string_view const format(params[0]); auto f = format.begin(); + int param = 1; + u64 number; + // parse the string looking for % signs while (f != format.end()) { @@ -495,21 +499,48 @@ bool debugger_commands::mini_printf(std::ostream &stream, std::string_view forma // formatting else if (c == '%') { + bool left_justify = false; + bool zero_fill = false; int width = 0; - int zerofill = 0; + int precision = 0; - // parse out the width - while (f != format.end() && *f >= '0' && *f <= '9') + // parse optional left justification flag + if (f != format.end() && *f == '-') { - c = *f++; - if (c == '0' && width == 0) - zerofill = 1; - width = width * 10 + (c - '0'); + left_justify = true; + f++; + } + + // parse optional zero fill flag + if (f != format.end() && *f == '0') + { + zero_fill = true; + f++; + } + + // parse optional width + while (f != format.end() && isdigit(*f)) + width = width * 10 + (*f++ - '0'); + if (f == format.end()) + break; + + // apply left justification + if (left_justify) + width = -width; + + if ((c = *f++) == '.') + { + // parse optional precision + while (f != format.end() && isdigit(*f)) + precision = precision * 10 + (*f++ - '0'); + + // get the format + if (f != format.end()) + c = *f++; + else + break; } - if (f == format.end()) break; - // get the format - c = *f++; switch (c) { case '%': @@ -517,70 +548,89 @@ bool debugger_commands::mini_printf(std::ostream &stream, std::string_view forma break; case 'X': + if (param < params.size() && m_console.validate_number_parameter(params[param++], number)) + util::stream_format(stream, zero_fill ? "%0*X" : "%*X", width, number); + else + { + m_console.printf("Not enough parameters for format!\n"); + return false; + } + break; case 'x': - if (params == 0) + if (param < params.size() && m_console.validate_number_parameter(params[param++], number)) + util::stream_format(stream, zero_fill ? "%0*x" : "%*x", width, number); + else { m_console.printf("Not enough parameters for format!\n"); return false; } - if (u32(*param >> 32) != 0) - util::stream_format(stream, zerofill ? "%0*X" : "%*X", (width <= 8) ? 1 : width - 8, u32(*param >> 32)); - else if (width > 8) - util::stream_format(stream, zerofill ? "%0*X" : "%*X", width - 8, 0); - util::stream_format(stream, zerofill ? "%0*X" : "%*X", (width < 8) ? width : 8, u32(*param)); - param++; - params--; break; case 'O': case 'o': - if (params == 0) + if (param < params.size() && m_console.validate_number_parameter(params[param++], number)) + util::stream_format(stream, zero_fill ? "%0*o" : "%*o", width, number); + else { m_console.printf("Not enough parameters for format!\n"); return false; } - if (u32(*param >> 60) != 0) - { - util::stream_format(stream, zerofill ? "%0*o" : "%*o", (width <= 20) ? 1 : width - 20, u32(*param >> 60)); - util::stream_format(stream, "%0*o", 10, u32(BIT(*param, 30, 30))); - } - else - { - if (width > 20) - util::stream_format(stream, zerofill ? "%0*o" : "%*o", width - 20, 0); - if (u32(BIT(*param, 30, 30)) != 0) - util::stream_format(stream, zerofill ? "%0*o" : "%*o", (width <= 10) ? 1 : width - 10, u32(BIT(*param, 30, 30))); - else if (width > 10) - util::stream_format(stream, zerofill ? "%0*o" : "%*o", width - 10, 0); - } - util::stream_format(stream, zerofill ? "%0*o" : "%*o", (width < 10) ? width : 10, u32(BIT(*param, 0, 30))); - param++; - params--; break; case 'D': case 'd': - if (params == 0) + if (param < params.size() && m_console.validate_number_parameter(params[param++], number)) + util::stream_format(stream, zero_fill ? "%0*d" : "%*d", width, number); + else { m_console.printf("Not enough parameters for format!\n"); return false; } - util::stream_format(stream, zerofill ? "%0*d" : "%*d", width, u32(*param)); - param++; - params--; break; + case 'C': case 'c': - if (params == 0) + if (param < params.size() && m_console.validate_number_parameter(params[param++], number)) + stream << char(number); + else { m_console.printf("Not enough parameters for format!\n"); return false; } - stream << char(*param); - param++; - params--; break; + case 's': + { + address_space *space; + if (param < params.size() && m_console.validate_target_address_parameter(params[param++], -1, space, number)) + { + address_space *tspace; + std::string s; + + for (u32 address = u32(number), taddress; space->device().memory().translate(space->spacenum(), device_memory_interface::TR_READ, taddress = address, tspace); address++) + { + u8 const data = tspace->read_byte(taddress); + + if (!data) + break; + + s += data; + + if (precision == 1) + break; + else if (precision) + precision--; + } + + util::stream_format(stream, "%*s", width, s); + } + else + { + m_console.printf("Not enough parameters for format!\n"); + return false; + } + } + break; } } @@ -630,15 +680,9 @@ void debugger_commands::execute_index_command(std::vector<std::string_view> cons void debugger_commands::execute_printf(const std::vector<std::string_view> ¶ms) { - /* validate the other parameters */ - u64 values[MAX_COMMAND_PARAMS]; - for (int i = 1; i < params.size(); i++) - if (!m_console.validate_number_parameter(params[i], values[i])) - return; - /* then do a printf */ std::ostringstream buffer; - if (mini_printf(buffer, params[0], params.size() - 1, &values[1])) + if (mini_printf(buffer, params)) m_console.printf("%s\n", std::move(buffer).str()); } @@ -649,15 +693,9 @@ void debugger_commands::execute_printf(const std::vector<std::string_view> ¶ void debugger_commands::execute_logerror(const std::vector<std::string_view> ¶ms) { - /* validate the other parameters */ - u64 values[MAX_COMMAND_PARAMS]; - for (int i = 1; i < params.size(); i++) - if (!m_console.validate_number_parameter(params[i], values[i])) - return; - /* then do a printf */ std::ostringstream buffer; - if (mini_printf(buffer, params[0], params.size() - 1, &values[1])) + if (mini_printf(buffer, params)) m_machine.logerror("%s", std::move(buffer).str()); } @@ -668,15 +706,9 @@ void debugger_commands::execute_logerror(const std::vector<std::string_view> &pa void debugger_commands::execute_tracelog(const std::vector<std::string_view> ¶ms) { - /* validate the other parameters */ - u64 values[MAX_COMMAND_PARAMS]; - for (int i = 1; i < params.size(); i++) - if (!m_console.validate_number_parameter(params[i], values[i])) - return; - /* then do a printf */ std::ostringstream buffer; - if (mini_printf(buffer, params[0], params.size() - 1, &values[1])) + if (mini_printf(buffer, params)) m_console.get_visible_cpu()->debug()->trace_printf("%s", std::move(buffer).str()); } @@ -688,12 +720,11 @@ void debugger_commands::execute_tracelog(const std::vector<std::string_view> &pa void debugger_commands::execute_tracesym(const std::vector<std::string_view> ¶ms) { // build a format string appropriate for the parameters and validate them - std::stringstream format; - u64 values[MAX_COMMAND_PARAMS]; + std::ostringstream format; for (int i = 0; i < params.size(); i++) { // find this symbol - symbol_entry *sym = m_console.visible_symtable().find(strmakelower(params[i]).c_str()); + symbol_entry *const sym = m_console.visible_symtable().find(strmakelower(params[i]).c_str()); if (!sym) { m_console.printf("Unknown symbol: %s\n", params[i]); @@ -704,15 +735,18 @@ void debugger_commands::execute_tracesym(const std::vector<std::string_view> &pa util::stream_format(format, "%s=%s ", params[i], sym->format().empty() ? "%16X" : sym->format()); - - // validate the parameter - if (!m_console.validate_number_parameter(params[i], values[i])) - return; } + // build parameters for printf + auto const format_str = std::move(format).str(); // need this to stay put as long as the string_view exists + std::vector<std::string_view> printf_params; + printf_params.reserve(params.size() + 1); + printf_params.emplace_back(format_str); + std::copy(params.begin(), params.end(), std::back_inserter(printf_params)); + // then do a printf std::ostringstream buffer; - if (mini_printf(buffer, format.str(), params.size(), values)) + if (mini_printf(buffer, printf_params)) m_console.get_visible_cpu()->debug()->trace_printf("%s", std::move(buffer).str()); } @@ -3483,16 +3517,16 @@ void debugger_commands::execute_trace(const std::vector<std::string_view> ¶m using namespace std::literals; if (!util::streqlower(filename, "off"sv)) { - std::ios_base::openmode mode = std::ios_base::out; + std::ios_base::openmode mode; // opening for append? if ((filename[0] == '>') && (filename[1] == '>')) { - mode |= std::ios_base::ate; + mode = std::ios_base::in | std::ios_base::out | std::ios_base::ate; filename = filename.substr(2); } else - mode |= std::ios_base::trunc; + mode = std::ios_base::out | std::ios_base::trunc; f = std::make_unique<std::ofstream>(filename.c_str(), mode); if (f->fail()) diff --git a/src/emu/debug/debugcmd.h b/src/emu/debug/debugcmd.h index 97d1a628ab5..cf38c6d903a 100644 --- a/src/emu/debug/debugcmd.h +++ b/src/emu/debug/debugcmd.h @@ -13,9 +13,6 @@ #pragma once -#include "debugcpu.h" -#include "debugcon.h" - #include <string_view> @@ -73,7 +70,7 @@ private: u64 global_get(global_entry *global); void global_set(global_entry *global, u64 value); - bool mini_printf(std::ostream &stream, std::string_view format, int params, u64 *param); + bool mini_printf(std::ostream &stream, const std::vector<std::string_view> ¶ms); template <typename T> void execute_index_command(std::vector<std::string_view> const ¶ms, T &&apply, char const *unused_message); diff --git a/src/emu/debug/debugcpu.cpp b/src/emu/debug/debugcpu.cpp index 5c5a186e2aa..968966b1b74 100644 --- a/src/emu/debug/debugcpu.cpp +++ b/src/emu/debug/debugcpu.cpp @@ -25,7 +25,6 @@ #include "uiinput.h" #include "corestr.h" -#include "coreutil.h" #include "osdepend.h" #include "xmlfile.h" @@ -410,6 +409,70 @@ void debugger_cpu::halt_on_next_instruction(device_t *device, util::format_argum } } + +//------------------------------------------------- +// wait_for_debugger - pause during execution to +// allow debugging +//------------------------------------------------- + +void debugger_cpu::wait_for_debugger(device_t &device) +{ + assert(is_stopped()); + assert(within_instruction_hook()); + + bool firststop = true; + + // load comments if we haven't yet + ensure_comments_loaded(); + + // reset any transient state + reset_transient_flags(); + set_break_cpu(nullptr); + + // remember the last visible CPU in the debugger + m_machine.debugger().console().set_visible_cpu(&device); + + // update all views + m_machine.debug_view().update_all(); + m_machine.debugger().refresh_display(); + + // wait for the debugger; during this time, disable sound output + m_machine.sound().debugger_mute(true); + while (is_stopped()) + { + // flush any pending updates before waiting again + m_machine.debug_view().flush_osd_updates(); + + emulator_info::periodic_check(); + + // clear the memory modified flag and wait + set_memory_modified(false); + if (m_machine.debug_flags & DEBUG_FLAG_OSD_ENABLED) + m_machine.osd().wait_for_debugger(device, firststop); + firststop = false; + + // if something modified memory, update the screen + if (memory_modified()) + { + m_machine.debug_view().update_all(DVT_DISASSEMBLY); + m_machine.debug_view().update_all(DVT_STATE); + m_machine.debugger().refresh_display(); + } + + // check for commands in the source file + m_machine.debugger().console().process_source_file(); + + // if an event got scheduled, resume + if (m_machine.scheduled_event_pending()) + set_execution_running(); + } + m_machine.sound().debugger_mute(false); + + // remember the last visible CPU in the debugger + m_machine.debugger().console().set_visible_cpu(&device); +} + + //************************************************************************** // DEVICE DEBUG //************************************************************************** @@ -436,6 +499,7 @@ device_debug::device_debug(device_t &device) , m_endexectime(attotime::zero) , m_total_cycles(0) , m_last_total_cycles(0) + , m_was_waiting(true) , m_pc_history_index(0) , m_pc_history_valid(0) , m_bplist() @@ -639,6 +703,13 @@ void device_debug::stop_hook() void device_debug::interrupt_hook(int irqline, offs_t pc) { + // CPU is presumably no longer waiting if it acknowledges an interrupt + if (m_was_waiting) + { + m_was_waiting = false; + compute_debug_flags(); + } + // see if this matches a pending interrupt request if ((m_flags & DEBUG_FLAG_STOP_INTERRUPT) != 0 && (m_stopirq == -1 || m_stopirq == irqline)) { @@ -773,6 +844,7 @@ void device_debug::privilege_hook() } } + //------------------------------------------------- // instruction_hook - called by the CPU cores // before executing each instruction @@ -781,7 +853,7 @@ void device_debug::privilege_hook() void device_debug::instruction_hook(offs_t curpc) { running_machine &machine = m_device.machine(); - debugger_cpu& debugcpu = machine.debugger().cpu(); + debugger_cpu &debugcpu = machine.debugger().cpu(); // note that we are in the debugger code debugcpu.set_within_instruction(true); @@ -795,6 +867,11 @@ void device_debug::instruction_hook(offs_t curpc) // update total cycles m_last_total_cycles = m_total_cycles; m_total_cycles = m_exec->total_cycles(); + if (m_was_waiting) + { + m_was_waiting = false; + compute_debug_flags(); + } // are we tracking our recent pc visits? if (m_track_pc) @@ -866,7 +943,7 @@ void device_debug::instruction_hook(offs_t curpc) } // handle breakpoints - if (!debugcpu.is_stopped() && (m_flags & (DEBUG_FLAG_STOP_TIME | DEBUG_FLAG_STOP_PC | DEBUG_FLAG_LIVE_BP)) != 0) + if (!debugcpu.is_stopped() && (m_flags & (DEBUG_FLAG_STOP_TIME | DEBUG_FLAG_STOP_PC | DEBUG_FLAG_LIVE_BP | DEBUG_FLAG_LIVE_RP)) != 0) { // see if we hit a target time if ((m_flags & DEBUG_FLAG_STOP_TIME) != 0 && machine.time() >= m_stoptime) @@ -886,68 +963,68 @@ void device_debug::instruction_hook(offs_t curpc) } // check for execution breakpoints - else if ((m_flags & DEBUG_FLAG_LIVE_BP) != 0) - breakpoint_check(curpc); + else + { + if ((m_flags & DEBUG_FLAG_LIVE_BP) != 0) + breakpoint_check(curpc); + if ((m_flags & DEBUG_FLAG_LIVE_RP) != 0) + registerpoint_check(); + } } // if we are supposed to halt, do it now if (debugcpu.is_stopped()) - { - bool firststop = true; - - // load comments if we haven't yet - debugcpu.ensure_comments_loaded(); + debugcpu.wait_for_debugger(m_device); - // reset any transient state - debugcpu.reset_transient_flags(); - debugcpu.set_break_cpu(nullptr); - - // remember the last visible CPU in the debugger - machine.debugger().console().set_visible_cpu(&m_device); + // handle step out/over on the instruction we are about to execute + if ((m_flags & (DEBUG_FLAG_STEPPING_OVER | DEBUG_FLAG_STEPPING_OUT | DEBUG_FLAG_STEPPING_BRANCH)) != 0 && (m_flags & (DEBUG_FLAG_CALL_IN_PROGRESS | DEBUG_FLAG_TEST_IN_PROGRESS)) == 0) + prepare_for_step_overout(m_state->pcbase()); - // update all views - machine.debug_view().update_all(); - machine.debugger().refresh_display(); + // no longer in debugger code + debugcpu.set_within_instruction(false); +} - // wait for the debugger; during this time, disable sound output - m_device.machine().sound().debugger_mute(true); - while (debugcpu.is_stopped()) - { - // flush any pending updates before waiting again - machine.debug_view().flush_osd_updates(); - emulator_info::periodic_check(); +//------------------------------------------------- +// wait_hook - called by the CPU cores while +// waiting indefinitely for some kind of event +//------------------------------------------------- - // clear the memory modified flag and wait - debugcpu.set_memory_modified(false); - if (machine.debug_flags & DEBUG_FLAG_OSD_ENABLED) - machine.osd().wait_for_debugger(m_device, firststop); - firststop = false; +void device_debug::wait_hook() +{ + running_machine &machine = m_device.machine(); + debugger_cpu &debugcpu = machine.debugger().cpu(); - // if something modified memory, update the screen - if (debugcpu.memory_modified()) - { - machine.debug_view().update_all(DVT_DISASSEMBLY); - machine.debug_view().update_all(DVT_STATE); - machine.debugger().refresh_display(); - } + // note that we are in the debugger code + debugcpu.set_within_instruction(true); - // check for commands in the source file - machine.debugger().console().process_source_file(); + // update total cycles + m_last_total_cycles = m_total_cycles; + m_total_cycles = m_exec->total_cycles(); - // if an event got scheduled, resume - if (machine.scheduled_event_pending()) - debugcpu.set_execution_running(); + // handle registerpoints (but not breakpoints) + if (!debugcpu.is_stopped() && (m_flags & (DEBUG_FLAG_STOP_TIME | DEBUG_FLAG_LIVE_RP)) != 0) + { + // see if we hit a target time + if ((m_flags & DEBUG_FLAG_STOP_TIME) != 0 && machine.time() >= m_stoptime) + { + machine.debugger().console().printf("Stopped at time interval %.1g\n", machine.time().as_double()); + debugcpu.set_execution_stopped(); } - machine.sound().debugger_mute(false); - - // remember the last visible CPU in the debugger - machine.debugger().console().set_visible_cpu(&m_device); + else if ((m_flags & DEBUG_FLAG_LIVE_RP) != 0) + registerpoint_check(); } - // handle step out/over on the instruction we are about to execute - if ((m_flags & (DEBUG_FLAG_STEPPING_OVER | DEBUG_FLAG_STEPPING_OUT | DEBUG_FLAG_STEPPING_BRANCH)) != 0 && (m_flags & (DEBUG_FLAG_CALL_IN_PROGRESS | DEBUG_FLAG_TEST_IN_PROGRESS)) == 0) - prepare_for_step_overout(m_state->pcbase()); + // if we are supposed to halt, do it now + if (debugcpu.is_stopped()) + { + if (!m_was_waiting) + { + machine.debugger().console().printf("CPU waiting after PC=%s\n", m_state->state_string(STATE_GENPCBASE)); + m_was_waiting = true; + } + debugcpu.wait_for_debugger(m_device); + } // no longer in debugger code debugcpu.set_within_instruction(false); @@ -1727,7 +1804,7 @@ u32 device_debug::compute_opcode_crc32(offs_t pc) const buffer.data_get(pc, dasmresult & util::disasm_interface::LENGTHMASK, true, opbuf); // return a CRC of the exact count of opcode bytes - return core_crc32(0, &opbuf[0], opbuf.size()); + return util::crc32_creator::simple(&opbuf[0], opbuf.size()); } @@ -1754,7 +1831,7 @@ void device_debug::trace(std::unique_ptr<std::ostream> &&file, bool trace_over, void device_debug::compute_debug_flags() { running_machine &machine = m_device.machine(); - debugger_cpu& debugcpu = machine.debugger().cpu(); + debugger_cpu &debugcpu = machine.debugger().cpu(); // clear out global flags by default, keep DEBUG_FLAG_OSD_ENABLED machine.debug_flags &= DEBUG_FLAG_OSD_ENABLED; @@ -1770,7 +1847,7 @@ void device_debug::compute_debug_flags() // if we're tracking history, or we're hooked, or stepping, or stopping at a breakpoint // make sure we call the hook - if ((m_flags & (DEBUG_FLAG_HISTORY | DEBUG_FLAG_STEPPING_ANY | DEBUG_FLAG_STOP_PC | DEBUG_FLAG_LIVE_BP)) != 0) + if ((m_flags & (DEBUG_FLAG_HISTORY | DEBUG_FLAG_STEPPING_ANY | DEBUG_FLAG_STOP_PC | DEBUG_FLAG_LIVE_BP | DEBUG_FLAG_LIVE_RP)) != 0) machine.debug_flags |= DEBUG_FLAG_CALL_HOOK; // also call if we are tracing @@ -1780,6 +1857,10 @@ void device_debug::compute_debug_flags() // if we are stopping at a particular time and that time is within the current timeslice, we need to be called if ((m_flags & DEBUG_FLAG_STOP_TIME) && m_endexectime <= m_stoptime) machine.debug_flags |= DEBUG_FLAG_CALL_HOOK; + + // if we were waiting, call if only to clear + if (m_was_waiting) + machine.debug_flags |= DEBUG_FLAG_CALL_HOOK; } @@ -1860,7 +1941,7 @@ void device_debug::prepare_for_step_overout(offs_t pc) void device_debug::breakpoint_update_flags() { // see if there are any enabled breakpoints - m_flags &= ~DEBUG_FLAG_LIVE_BP; + m_flags &= ~(DEBUG_FLAG_LIVE_BP | DEBUG_FLAG_LIVE_RP); for (auto &bpp : m_bplist) if (bpp.second->m_enabled) { @@ -1868,16 +1949,13 @@ void device_debug::breakpoint_update_flags() break; } - if (!(m_flags & DEBUG_FLAG_LIVE_BP)) + // see if there are any enabled registerpoints + for (debug_registerpoint &rp : m_rplist) { - // see if there are any enabled registerpoints - for (debug_registerpoint &rp : m_rplist) + if (rp.m_enabled) { - if (rp.m_enabled) - { - m_flags |= DEBUG_FLAG_LIVE_BP; - break; - } + m_flags |= DEBUG_FLAG_LIVE_RP; + break; } } @@ -1894,8 +1972,6 @@ void device_debug::breakpoint_update_flags() void device_debug::breakpoint_check(offs_t pc) { - debugger_cpu& debugcpu = m_device.machine().debugger().cpu(); - // see if we match auto bpitp = m_bplist.equal_range(pc); for (auto bpit = bpitp.first; bpit != bpitp.second; ++bpit) @@ -1903,6 +1979,8 @@ void device_debug::breakpoint_check(offs_t pc) debug_breakpoint &bp = *bpit->second; if (bp.hit(pc)) { + debugger_cpu &debugcpu = m_device.machine().debugger().cpu(); + // halt in the debugger by default debugcpu.set_execution_stopped(); @@ -1919,12 +1997,23 @@ void device_debug::breakpoint_check(offs_t pc) break; } } +} + +//------------------------------------------------- +// registerpoint_check - check the registerpoints +// for a given device +//------------------------------------------------- + +void device_debug::registerpoint_check() +{ // see if we have any matching registerpoints for (debug_registerpoint &rp : m_rplist) { if (rp.hit()) { + debugger_cpu &debugcpu = m_device.machine().debugger().cpu(); + // halt in the debugger by default debugcpu.set_execution_stopped(); diff --git a/src/emu/debug/debugcpu.h b/src/emu/debug/debugcpu.h index 9a9f6e69f70..631d4c788d9 100644 --- a/src/emu/debug/debugcpu.h +++ b/src/emu/debug/debugcpu.h @@ -55,6 +55,7 @@ public: void exception_hook(int exception); void privilege_hook(); void instruction_hook(offs_t curpc); + void wait_hook(); // debugger focus void ignore(bool ignore = true); @@ -174,6 +175,7 @@ private: // breakpoint and watchpoint helpers void breakpoint_update_flags(); void breakpoint_check(offs_t pc); + void registerpoint_check(); void reinstall_all(read_or_write mode); void reinstall(address_space &space, read_or_write mode); void write_tracking(address_space &space, offs_t address, u64 data); @@ -204,6 +206,7 @@ private: attotime m_endexectime; // ending time of the current execution u64 m_total_cycles; // current total cycles u64 m_last_total_cycles; // last total cycles + bool m_was_waiting; // true if no instruction executed since last wait // history offs_t m_pc_history[HISTORY_SIZE]; // history of recent PCs @@ -328,7 +331,8 @@ private: static constexpr u32 DEBUG_FLAG_STOP_VBLANK = 0x00001000; // there is a pending stop on the next VBLANK static constexpr u32 DEBUG_FLAG_STOP_TIME = 0x00002000; // there is a pending stop at cpu->stoptime static constexpr u32 DEBUG_FLAG_SUSPENDED = 0x00004000; // CPU currently suspended - static constexpr u32 DEBUG_FLAG_LIVE_BP = 0x00010000; // there are live breakpoints for this CPU + static constexpr u32 DEBUG_FLAG_LIVE_BP = 0x00008000; // there are live breakpoints for this CPU + static constexpr u32 DEBUG_FLAG_LIVE_RP = 0x00010000; // there are live registerpoints for this CPU static constexpr u32 DEBUG_FLAG_STOP_PRIVILEGE = 0x00020000; // run until execution level changes static constexpr u32 DEBUG_FLAG_STEPPING_BRANCH_TRUE = 0x0040000; // run until true branch static constexpr u32 DEBUG_FLAG_STEPPING_BRANCH_FALSE = 0x0080000; // run until false branch @@ -409,6 +413,7 @@ public: void halt_on_next_instruction(device_t *device, util::format_argument_pack<char> &&args); void ensure_comments_loaded(); void reset_transient_flags(); + void wait_for_debugger(device_t &device); private: static const size_t NUM_TEMP_VARIABLES; diff --git a/src/emu/debug/debughlp.cpp b/src/emu/debug/debughlp.cpp index ee455bd84f8..8e5fa1b9c64 100644 --- a/src/emu/debug/debughlp.cpp +++ b/src/emu/debug/debughlp.cpp @@ -46,7 +46,7 @@ const help_item f_static_help_list[] = " Breakpoints\n" " Watchpoints\n" " Registerpoints\n" - " Exception Points\n" + " Exceptionpoints\n" " Expressions\n" " Comments\n" " Cheats\n" @@ -195,14 +195,14 @@ const help_item f_static_help_list[] = { "exceptionpoints", "\n" - "Exception Point Commands\n" + "Exceptionpoint Commands\n" "Type help <command> for further details on each command\n" "\n" - " ep[set] <type>[,<condition>[,<action>]] -- sets exception point on <type>\n" - " epclear [<epnum>] -- clears a given exception point or all if no <epnum> specified\n" - " epdisable [<epnum>] -- disabled a given exception point or all if no <epnum> specified\n" - " epenable [<epnum>] -- enables a given exception point or all if no <epnum> specified\n" - " eplist -- lists all the exception points\n" + " ep[set] <type>[,<condition>[,<action>]] -- sets exceptionpoint on <type>\n" + " epclear [<epnum>] -- clears a given exceptionpoint or all if no <epnum> specified\n" + " epdisable [<epnum>] -- disabled a given exceptionpoint or all if no <epnum> specified\n" + " epenable [<epnum>] -- enables a given exceptionpoint or all if no <epnum> specified\n" + " eplist -- lists all the exceptionpoints\n" }, { "expressions", @@ -359,16 +359,20 @@ const help_item f_static_help_list[] = "The printf command performs a C-style printf to the debugger console. Only a very limited set of " "formatting options are available:\n" "\n" - " %[0][<n>]d -- prints <item> as a decimal value with optional digit count and zero-fill\n" - " %[0][<n>]x -- prints <item> as a hexadecimal value with optional digit count and zero-fill\n" + " %c -- 8-bit character\n" + " %[-][0][<n>]d -- decimal number with optional left justification, zero fill and minimum width\n" + " %[-][0][<n>]o -- octal number with optional left justification, zero fill and minimum width\n" + " %[-][0][<n>]x -- lowercase hexadecimal number with optional left justification, zero fill and minimum width\n" + " %[-][0][<n>]X -- uppercase hexadecimal number with optional left justification, zero fill and minimum width\n" + " %[-][<n>][.[<n>]]s -- null-terminated string of 8-bit characters with optional left justification, minimum and maximum width\n" "\n" - "All remaining formatting options are ignored. Use %% together to output a % character. Multiple " - "lines can be printed by embedding a \\n in the text.\n" + "All remaining formatting options are ignored. Use %% to output a % character. Multiple lines can be " + "printed by embedding a \\n in the text.\n" "\n" "Examples:\n" "\n" "printf \"PC=%04X\",pc\n" - " Prints PC=<pcval> where <pcval> is displayed in hexadecimal with 4 digits with zero-fill.\n" + " Prints PC=<pcval> where <pcval> is displayed in uppercase hexadecimal with 4 digits and zero fill.\n" "\n" "printf \"A=%d, B=%d\\nC=%d\",a,b,a+b\n" " Prints A=<aval>, B=<bval> on one line, and C=<a+bval> on a second line.\n" @@ -379,18 +383,12 @@ const help_item f_static_help_list[] = " logerror <format>[,<item>[,...]]\n" "\n" "The logerror command performs a C-style printf to the error log. Only a very limited set of " - "formatting options are available:\n" - "\n" - " %[0][<n>]d -- logs <item> as a decimal value with optional digit count and zero-fill\n" - " %[0][<n>]x -- logs <item> as a hexadecimal value with optional digit count and zero-fill\n" - "\n" - "All remaining formatting options are ignored. Use %% together to output a % character. Multiple " - "lines can be printed by embedding a \\n in the text.\n" + "formatting options are available. See the 'printf' help for details.\n" "\n" "Examples:\n" "\n" - "logerror \"PC=%04X\",pc\n" - " Logs PC=<pcval> where <pcval> is displayed in hexadecimal with 4 digits with zero-fill.\n" + "logerror \"PC=%04x\",pc\n" + " Logs PC=<pcval> where <pcval> is displayed in lowercase hexadecimal with 4 digits and zero fill.\n" "\n" "logerror \"A=%d, B=%d\\nC=%d\",a,b,a+b\n" " Logs A=<aval>, B=<bval> on one line, and C=<a+bval> on a second line.\n" @@ -1594,12 +1592,12 @@ const help_item f_static_help_list[] = "\n" " ep[set] <type>[,<condition>[,<action>]]\n" "\n" - "Sets a new exception point for exceptions of type <type> on the currently visible CPU. " + "Sets a new exceptionpoint for exceptions of type <type> on the currently visible CPU. " "The optional <condition> parameter lets you specify an expression that will be evaluated " - "each time the exception point is hit. If the result of the expression is true (non-zero), " - "the exception point will actually halt execution at the start of the exception handler; " + "each time the exceptionpoint is hit. If the result of the expression is true (non-zero), " + "the exceptionpoint will actually halt execution at the start of the exception handler; " "otherwise, execution will continue with no notification. The optional <action> parameter " - "provides a command that is executed whenever the exception point is hit and the " + "provides a command that is executed whenever the exceptionpoint is hit and the " "<condition> is true. Note that you may need to embed the action within braces { } in order " "to prevent commas and semicolons from being interpreted as applying to the epset command " "itself.\n" @@ -1608,8 +1606,8 @@ const help_item f_static_help_list[] = "internally or externally vectored interrupts, errors occurring within instructions and " "system calls.\n" "\n" - "Each exception point that is set is assigned an index which can be used in other " - "exception point commands to reference this exception point.\n" + "Each exceptionpoint that is set is assigned an index which can be used in other " + "exceptionpoint commands to reference this exceptionpoint.\n" "\n" "Examples:\n" "\n" @@ -1622,57 +1620,57 @@ const help_item f_static_help_list[] = "\n" " epclear [<epnum>[,...]]\n" "\n" - "The epclear command clears exception points. If <epnum> is specified, only the requested " - "exception points are cleared, otherwise all exception points are cleared.\n" + "The epclear command clears exceptionpoints. If <epnum> is specified, only the requested " + "exceptionpoints are cleared, otherwise all exceptionpoints are cleared.\n" "\n" "Examples:\n" "\n" "epclear 3\n" - " Clear exception point index 3.\n" + " Clear exceptionpoint index 3.\n" "\n" "epclear\n" - " Clear all exception points.\n" + " Clear all exceptionpoints.\n" }, { "epdisable", "\n" " epdisable [<epnum>[,...]]\n" "\n" - "The epdisable command disables exception points. If <epnum> is specified, only the requested " - "exception points are disabled, otherwise all exception points are disabled. Note that " - "disabling an exception point does not delete it, it just temporarily marks the exception " - "point as inactive.\n" + "The epdisable command disables exceptionpoints. If <epnum> is specified, only the requested " + "exceptionpoints are disabled, otherwise all exceptionpoints are disabled. Note that " + "disabling an exceptionpoint does not delete it, it just temporarily marks the " + "exceptionpoint as inactive.\n" "\n" "Examples:\n" "\n" "epdisable 3\n" - " Disable exception point index 3.\n" + " Disable exceptionpoint index 3.\n" "\n" "epdisable\n" - " Disable all exception points.\n" + " Disable all exceptionpoints.\n" }, { "epenable", "\n" " epenable [<epnum>[,...]]\n" "\n" - "The epenable command enables exception points. If <epnum> is specified, only the " - "requested exception points are enabled, otherwise all exception points are enabled.\n" + "The epenable command enables exceptionpoints. If <epnum> is specified, only the " + "requested exceptionpoints are enabled, otherwise all exceptionpoints are enabled.\n" "\n" "Examples:\n" "\n" "epenable 3\n" - " Enable exception point index 3.\n" + " Enable exceptionpoint index 3.\n" "\n" "epenable\n" - " Enable all exception points.\n" + " Enable all exceptionpoints.\n" }, { "eplist", "\n" " eplist\n" "\n" - "The eplist command lists all the current exception points, along with their index and " + "The eplist command lists all the current exceptionpoints, along with their index and " "any conditions or actions attached to them.\n" }, { diff --git a/src/emu/debug/debugvw.cpp b/src/emu/debug/debugvw.cpp index 54fe57ecf5a..7412ddd2f60 100644 --- a/src/emu/debug/debugvw.cpp +++ b/src/emu/debug/debugvw.cpp @@ -14,6 +14,7 @@ #include "debugcpu.h" #include "dvbpoints.h" #include "dvdisasm.h" +#include "dvepoints.h" #include "dvmemory.h" #include "dvrpoints.h" #include "dvstate.h" @@ -369,6 +370,9 @@ debug_view *debug_view_manager::alloc_view(debug_view_type type, debug_view_osd_ case DVT_REGISTER_POINTS: return append(new debug_view_registerpoints(machine(), osdupdate, osdprivate)); + case DVT_EXCEPTION_POINTS: + return append(new debug_view_exceptionpoints(machine(), osdupdate, osdprivate)); + default: fatalerror("Attempt to create invalid debug view type %d\n", type); } diff --git a/src/emu/debug/debugvw.h b/src/emu/debug/debugvw.h index 73f93d14f32..64bd5dac10a 100644 --- a/src/emu/debug/debugvw.h +++ b/src/emu/debug/debugvw.h @@ -35,7 +35,8 @@ enum debug_view_type DVT_LOG, DVT_BREAK_POINTS, DVT_WATCH_POINTS, - DVT_REGISTER_POINTS + DVT_REGISTER_POINTS, + DVT_EXCEPTION_POINTS }; diff --git a/src/emu/debug/dvbpoints.cpp b/src/emu/debug/dvbpoints.cpp index d7be62c1eaa..0290f8719dc 100644 --- a/src/emu/debug/dvbpoints.cpp +++ b/src/emu/debug/dvbpoints.cpp @@ -9,8 +9,9 @@ ***************************************************************************/ #include "emu.h" -#include "debugger.h" #include "dvbpoints.h" + +#include "debugcpu.h" #include "points.h" #include <algorithm> diff --git a/src/emu/debug/dvbpoints.h b/src/emu/debug/dvbpoints.h index 1bf365f521e..be291fa66d8 100644 --- a/src/emu/debug/dvbpoints.h +++ b/src/emu/debug/dvbpoints.h @@ -12,7 +12,6 @@ #pragma once -#include "debugcpu.h" #include "debugvw.h" #include <vector> diff --git a/src/emu/debug/dvdisasm.cpp b/src/emu/debug/dvdisasm.cpp index 0dcc0b98982..17cc766ef94 100644 --- a/src/emu/debug/dvdisasm.cpp +++ b/src/emu/debug/dvdisasm.cpp @@ -9,10 +9,11 @@ ***************************************************************************/ #include "emu.h" -#include "debugvw.h" #include "dvdisasm.h" + +#include "debugbuf.h" #include "debugcpu.h" -#include "debugger.h" + //************************************************************************** // DEBUG VIEW DISASM SOURCE @@ -394,25 +395,25 @@ void debug_view_disasm::view_update() // print - print a string in the disassembly view //------------------------------------------------- -void debug_view_disasm::print(int row, std::string text, int start, int end, u8 attrib) +void debug_view_disasm::print(u32 row, std::string text, s32 start, s32 end, u8 attrib) { - int view_end = end - m_topleft.x; + s32 view_end = end - m_topleft.x; if(view_end < 0) return; - int string_0 = start - m_topleft.x; + s32 string_0 = start - m_topleft.x; if(string_0 >= m_visible.x) return; - int view_start = string_0 > 0 ? string_0 : 0; + s32 view_start = string_0 > 0 ? string_0 : 0; debug_view_char *dest = &m_viewdata[row * m_visible.x + view_start]; if(view_end >= m_visible.x) view_end = m_visible.x; - for(int pos = view_start; pos < view_end; pos++) { - int spos = pos - string_0; - if(spos >= int(text.size())) + for(s32 pos = view_start; pos < view_end; pos++) { + s32 spos = pos - string_0; + if(spos >= s32(text.size())) *dest++ = { ' ', attrib }; else *dest++ = { u8(text[spos]), attrib }; @@ -427,13 +428,15 @@ void debug_view_disasm::print(int row, std::string text, int start, int end, u8 void debug_view_disasm::redraw() { // determine how many characters we need for an address and set the divider - int m_divider1 = 1 + m_dasm[0].m_tadr.size() + 1; + s32 divider1 = 1 + m_dasm[0].m_tadr.size() + 1; // assume a fixed number of characters for the disassembly - int m_divider2 = m_divider1 + 1 + m_dasm_width + 1; + s32 divider2 = divider1 + 1 + m_dasm_width + 1; // set the width of the third column to max comment length - m_total.x = m_divider2 + 1 + 50; // DEBUG_COMMENT_MAX_LINE_LENGTH + m_total.x = divider2 + 1 + 50; // DEBUG_COMMENT_MAX_LINE_LENGTH + + const s32 max_visible_col = m_topleft.x + m_visible.x; // loop over visible rows for(u32 row = 0; row < m_visible.y; row++) @@ -460,22 +463,22 @@ void debug_view_disasm::redraw() if(m_dasm[effrow].m_is_visited) attrib |= DCA_VISITED; - print(row, ' ' + m_dasm[effrow].m_tadr, 0, m_divider1, attrib | DCA_ANCILLARY); - print(row, ' ' + m_dasm[effrow].m_dasm, m_divider1, m_divider2, attrib); + print(row, ' ' + m_dasm[effrow].m_tadr, 0, divider1, attrib | DCA_ANCILLARY); + print(row, ' ' + m_dasm[effrow].m_dasm, divider1, divider2, attrib); if(m_right_column == DASM_RIGHTCOL_RAW || m_right_column == DASM_RIGHTCOL_ENCRYPTED) { std::string text = ' ' +(m_right_column == DASM_RIGHTCOL_RAW ? m_dasm[effrow].m_topcodes : m_dasm[effrow].m_tparams); - print(row, text, m_divider2, m_visible.x, attrib | DCA_ANCILLARY); - if(int(text.size()) > m_visible.x - m_divider2) { - int base = m_total.x - 3; - if(base < m_divider2) - base = m_divider2; - print(row, "...", base, m_visible.x, attrib | DCA_ANCILLARY); + print(row, text, divider2, max_visible_col, attrib | DCA_ANCILLARY); + if(s32(text.size()) > max_visible_col - divider2) { + s32 base = max_visible_col - 3; + if(base < divider2) + base = divider2; + print(row, "...", base, max_visible_col, attrib | DCA_ANCILLARY); } } else if(!m_dasm[effrow].m_comment.empty()) - print(row, " // " + m_dasm[effrow].m_comment, m_divider2, m_visible.x, attrib | DCA_COMMENT | DCA_ANCILLARY); + print(row, " // " + m_dasm[effrow].m_comment, divider2, max_visible_col, attrib | DCA_COMMENT | DCA_ANCILLARY); else - print(row, "", m_divider2, m_visible.x, attrib | DCA_COMMENT | DCA_ANCILLARY); + print(row, "", divider2, max_visible_col, attrib | DCA_COMMENT | DCA_ANCILLARY); } } } diff --git a/src/emu/debug/dvdisasm.h b/src/emu/debug/dvdisasm.h index 0a16f95d5fe..ef4c5e9cf87 100644 --- a/src/emu/debug/dvdisasm.h +++ b/src/emu/debug/dvdisasm.h @@ -14,9 +14,6 @@ #pragma once #include "debugvw.h" -#include "debugbuf.h" - -#include "vecstream.h" //************************************************************************** @@ -38,6 +35,9 @@ enum disasm_right_column // TYPE DEFINITIONS //************************************************************************** +// forward declaration +class debug_disasm_buffer; + // a disassembly view_source class debug_view_disasm_source : public debug_view_source { @@ -119,7 +119,7 @@ private: void complete_information(const debug_view_disasm_source &source, debug_disasm_buffer &buffer, offs_t pc); void enumerate_sources(); - void print(int row, std::string text, int start, int end, u8 attrib); + void print(u32 row, std::string text, s32 start, s32 end, u8 attrib); void redraw(); // internal state diff --git a/src/emu/debug/dvepoints.cpp b/src/emu/debug/dvepoints.cpp new file mode 100644 index 00000000000..2ebc53a6609 --- /dev/null +++ b/src/emu/debug/dvepoints.cpp @@ -0,0 +1,314 @@ +// license:BSD-3-Clause +// copyright-holders:Andrew Gardner, Vas Crabb +/********************************************************************* + + dvepoints.cpp + + Exceptionpoint debugger view. + +***************************************************************************/ + +#include "emu.h" +#include "dvepoints.h" + +#include "debugcpu.h" +#include "points.h" + +#include <algorithm> +#include <iomanip> + + + +// Sorting functors for the qsort function +static bool cIndexAscending(const debug_exceptionpoint *a, const debug_exceptionpoint *b) +{ + return a->index() < b->index(); +} + +static bool cIndexDescending(const debug_exceptionpoint *a, const debug_exceptionpoint *b) +{ + return cIndexAscending(b, a); +} + +static bool cEnabledAscending(const debug_exceptionpoint *a, const debug_exceptionpoint *b) +{ + return !a->enabled() && b->enabled(); +} + +static bool cEnabledDescending(const debug_exceptionpoint *a, const debug_exceptionpoint *b) +{ + return cEnabledAscending(b, a); +} + +static bool cCpuAscending(const debug_exceptionpoint *a, const debug_exceptionpoint *b) +{ + return strcmp(a->debugInterface()->device().tag(), b->debugInterface()->device().tag()) < 0; +} + +static bool cCpuDescending(const debug_exceptionpoint *a, const debug_exceptionpoint *b) +{ + return cCpuAscending(b, a); +} + +static bool cTypeAscending(const debug_exceptionpoint *a, const debug_exceptionpoint *b) +{ + return a->type() < b->type(); +} + +static bool cTypeDescending(const debug_exceptionpoint *a, const debug_exceptionpoint *b) +{ + return cTypeAscending(b, a); +} + +static bool cConditionAscending(const debug_exceptionpoint *a, const debug_exceptionpoint *b) +{ + return strcmp(a->condition(), b->condition()) < 0; +} + +static bool cConditionDescending(const debug_exceptionpoint *a, const debug_exceptionpoint *b) +{ + return cConditionAscending(b, a); +} + +static bool cActionAscending(const debug_exceptionpoint *a, const debug_exceptionpoint *b) +{ + return a->action() < b->action(); +} + +static bool cActionDescending(const debug_exceptionpoint *a, const debug_exceptionpoint *b) +{ + return cActionAscending(b, a); +} + + +//************************************************************************** +// DEBUG VIEW BREAK POINTS +//************************************************************************** + +static const int tableBreaks[] = { 5, 9, 31, 45, 63, 80 }; + + +//------------------------------------------------- +// debug_view_exceptionpoints - constructor +//------------------------------------------------- + +debug_view_exceptionpoints::debug_view_exceptionpoints(running_machine &machine, debug_view_osd_update_func osdupdate, void *osdprivate) + : debug_view(machine, DVT_EXCEPTION_POINTS, osdupdate, osdprivate) + , m_sortType(cIndexAscending) +{ + // fail if no available sources + enumerate_sources(); + if (m_source_list.empty()) + throw std::bad_alloc(); +} + + +//------------------------------------------------- +// ~debug_view_exceptionpoints - destructor +//------------------------------------------------- + +debug_view_exceptionpoints::~debug_view_exceptionpoints() +{ +} + + +//------------------------------------------------- +// enumerate_sources - enumerate all possible +// sources for a disassembly view +//------------------------------------------------- + +void debug_view_exceptionpoints::enumerate_sources() +{ + // start with an empty list + m_source_list.clear(); + + // iterate over devices with disassembly interfaces + for (device_disasm_interface &dasm : disasm_interface_enumerator(machine().root_device())) + { + m_source_list.emplace_back( + std::make_unique<debug_view_source>( + util::string_format("%s '%s'", dasm.device().name(), dasm.device().tag()), + &dasm.device())); + } + + // reset the source to a known good entry + if (!m_source_list.empty()) + set_source(*m_source_list[0]); +} + + +//------------------------------------------------- +// view_click - handle a mouse click within the +// current view +//------------------------------------------------- + +void debug_view_exceptionpoints::view_click(const int button, const debug_view_xy& pos) +{ + bool clickedTopRow = (m_topleft.y == pos.y); + + if (clickedTopRow) + { + if (pos.x < tableBreaks[0]) + m_sortType = (m_sortType == &cIndexAscending) ? &cIndexDescending : &cIndexAscending; + else if (pos.x < tableBreaks[1]) + m_sortType = (m_sortType == &cEnabledAscending) ? &cEnabledDescending : &cEnabledAscending; + else if (pos.x < tableBreaks[2]) + m_sortType = (m_sortType == &cCpuAscending) ? &cCpuDescending : &cCpuAscending; + else if (pos.x < tableBreaks[3]) + m_sortType = (m_sortType == &cTypeAscending) ? &cTypeDescending : &cTypeAscending; + else if (pos.x < tableBreaks[4]) + m_sortType = (m_sortType == &cConditionAscending) ? &cConditionDescending : &cConditionAscending; + else if (pos.x < tableBreaks[5]) + m_sortType = (m_sortType == &cActionAscending) ? &cActionDescending : &cActionAscending; + } + else + { + // Gather a sorted list of all the exceptionpoints for all the CPUs + gather_exceptionpoints(); + + int epIndex = pos.y - 1; + if ((epIndex >= m_buffer.size()) || (epIndex < 0)) + return; + + // Enable / disable + const_cast<debug_exceptionpoint &>(*m_buffer[epIndex]).setEnabled(!m_buffer[epIndex]->enabled()); + + machine().debug_view().update_all(DVT_DISASSEMBLY); + } + + begin_update(); + m_update_pending = true; + end_update(); +} + + +void debug_view_exceptionpoints::pad_ostream_to_length(std::ostream& str, int len) +{ + auto const current = str.tellp(); + if (current < decltype(current)(len)) + str << std::setw(decltype(current)(len) - current) << ""; +} + + +void debug_view_exceptionpoints::gather_exceptionpoints() +{ + m_buffer.resize(0); + for (auto &source : m_source_list) + { + // Collect + device_debug &debugInterface = *source->device()->debug(); + for (const auto &epp : debugInterface.exceptionpoint_list()) + m_buffer.push_back(epp.second.get()); + } + + // And now for the sort + if (!m_buffer.empty()) + std::stable_sort(m_buffer.begin(), m_buffer.end(), m_sortType); +} + + +//------------------------------------------------- +// view_update - update the contents of the +// exceptionpoints view +//------------------------------------------------- + +void debug_view_exceptionpoints::view_update() +{ + // Gather a list of all the exceptionpoints for all the CPUs + gather_exceptionpoints(); + + // Set the view region so the scroll bars update + m_total.x = tableBreaks[std::size(tableBreaks) - 1]; + m_total.y = m_buffer.size() + 1; + if (m_total.y < 10) + m_total.y = 10; + + // Draw + debug_view_char *dest = &m_viewdata[0]; + util::ovectorstream linebuf; + linebuf.reserve(std::size(tableBreaks) - 1); + + // Header + if (m_visible.y > 0) + { + linebuf.clear(); + linebuf.rdbuf()->clear(); + linebuf << "ID"; + if (m_sortType == &cIndexAscending) linebuf.put('\\'); + else if (m_sortType == &cIndexDescending) linebuf.put('/'); + pad_ostream_to_length(linebuf, tableBreaks[0]); + linebuf << "En"; + if (m_sortType == &cEnabledAscending) linebuf.put('\\'); + else if (m_sortType == &cEnabledDescending) linebuf.put('/'); + pad_ostream_to_length(linebuf, tableBreaks[1]); + linebuf << "CPU"; + if (m_sortType == &cCpuAscending) linebuf.put('\\'); + else if (m_sortType == &cCpuDescending) linebuf.put('/'); + pad_ostream_to_length(linebuf, tableBreaks[2]); + linebuf << "Type"; + if (m_sortType == &cTypeAscending) linebuf.put('\\'); + else if (m_sortType == &cTypeDescending) linebuf.put('/'); + pad_ostream_to_length(linebuf, tableBreaks[3]); + linebuf << "Condition"; + if (m_sortType == &cConditionAscending) linebuf.put('\\'); + else if (m_sortType == &cConditionDescending) linebuf.put('/'); + pad_ostream_to_length(linebuf, tableBreaks[4]); + linebuf << "Action"; + if (m_sortType == &cActionAscending) linebuf.put('\\'); + else if (m_sortType == &cActionDescending) linebuf.put('/'); + pad_ostream_to_length(linebuf, tableBreaks[5]); + + auto const &text(linebuf.vec()); + for (u32 i = m_topleft.x; i < (m_topleft.x + m_visible.x); i++, dest++) + { + dest->byte = (i < text.size()) ? text[i] : ' '; + dest->attrib = DCA_ANCILLARY; + } + } + + for (int row = 1; row < m_visible.y; row++) + { + // Breakpoints + int epi = row + m_topleft.y - 1; + if ((epi < m_buffer.size()) && (epi >= 0)) + { + const debug_exceptionpoint *const ep = m_buffer[epi]; + + linebuf.clear(); + linebuf.rdbuf()->clear(); + util::stream_format(linebuf, "%2X", ep->index()); + pad_ostream_to_length(linebuf, tableBreaks[0]); + linebuf.put(ep->enabled() ? 'X' : 'O'); + pad_ostream_to_length(linebuf, tableBreaks[1]); + linebuf << ep->debugInterface()->device().tag(); + pad_ostream_to_length(linebuf, tableBreaks[2]); + util::stream_format(linebuf, "%X", ep->type()); + pad_ostream_to_length(linebuf, tableBreaks[3]); + if (strcmp(ep->condition(), "1")) + linebuf << ep->condition(); + pad_ostream_to_length(linebuf, tableBreaks[4]); + linebuf << ep->action(); + pad_ostream_to_length(linebuf, tableBreaks[5]); + + auto const &text(linebuf.vec()); + for (u32 i = m_topleft.x; i < (m_topleft.x + m_visible.x); i++, dest++) + { + dest->byte = (i < text.size()) ? text[i] : ' '; + dest->attrib = DCA_NORMAL; + + // Color disabled exceptionpoints red + if ((i >= tableBreaks[0]) && (i < tableBreaks[1]) && !ep->enabled()) + dest->attrib |= DCA_CHANGED; + } + } + else + { + // Fill the remaining vertical space + for (u32 i = m_topleft.x; i < (m_topleft.x + m_visible.x); i++, dest++) + { + dest->byte = ' '; + dest->attrib = DCA_NORMAL; + } + } + } +} diff --git a/src/emu/debug/dvepoints.h b/src/emu/debug/dvepoints.h new file mode 100644 index 00000000000..3b042218e2a --- /dev/null +++ b/src/emu/debug/dvepoints.h @@ -0,0 +1,49 @@ +// license:BSD-3-Clause +// copyright-holders:Andrew Gardner, Vas Crabb +/********************************************************************* + + dvepoints.h + + Exceptionpoint debugger view. + +***************************************************************************/ +#ifndef MAME_EMU_DEBUG_DVEPOINTS_H +#define MAME_EMU_DEBUG_DVEPOINTS_H + +#pragma once + +#include "debugvw.h" + +#include <vector> + + +//************************************************************************** +// TYPE DEFINITIONS +//************************************************************************** + +// debug view for exceptionpoints +class debug_view_exceptionpoints : public debug_view +{ + friend class debug_view_manager; + + // construction/destruction + debug_view_exceptionpoints(running_machine &machine, debug_view_osd_update_func osdupdate, void *osdprivate); + virtual ~debug_view_exceptionpoints(); + +protected: + // view overrides + virtual void view_update() override; + virtual void view_click(const int button, const debug_view_xy& pos) override; + +private: + // internal helpers + void enumerate_sources(); + void pad_ostream_to_length(std::ostream& str, int len); + void gather_exceptionpoints(); + + // internal state + bool (*m_sortType)(const debug_exceptionpoint *, const debug_exceptionpoint *); + std::vector<const debug_exceptionpoint *> m_buffer; +}; + +#endif // MAME_EMU_DEBUG_DVEPOINTS_H diff --git a/src/emu/debug/dvmemory.cpp b/src/emu/debug/dvmemory.cpp index da792305c0d..7c10407822b 100644 --- a/src/emu/debug/dvmemory.cpp +++ b/src/emu/debug/dvmemory.cpp @@ -12,7 +12,6 @@ #include "dvmemory.h" #include "debugcpu.h" -#include "debugger.h" #include <algorithm> #include <cctype> diff --git a/src/emu/debug/dvrpoints.cpp b/src/emu/debug/dvrpoints.cpp index 7295a1b2170..a36f42bd1d0 100644 --- a/src/emu/debug/dvrpoints.cpp +++ b/src/emu/debug/dvrpoints.cpp @@ -11,7 +11,7 @@ #include "emu.h" #include "dvrpoints.h" -#include "debugger.h" +#include "debugcpu.h" #include "points.h" #include <algorithm> diff --git a/src/emu/debug/dvrpoints.h b/src/emu/debug/dvrpoints.h index 307ccbc212e..31b821d7fae 100644 --- a/src/emu/debug/dvrpoints.h +++ b/src/emu/debug/dvrpoints.h @@ -12,7 +12,6 @@ #pragma once -#include "debugcpu.h" #include "debugvw.h" #include <utility> diff --git a/src/emu/debug/dvwpoints.cpp b/src/emu/debug/dvwpoints.cpp index 21d3f30c5ca..116904232a1 100644 --- a/src/emu/debug/dvwpoints.cpp +++ b/src/emu/debug/dvwpoints.cpp @@ -11,6 +11,7 @@ #include "emu.h" #include "dvwpoints.h" +#include "debugcpu.h" #include "points.h" #include <algorithm> diff --git a/src/emu/debug/dvwpoints.h b/src/emu/debug/dvwpoints.h index 650a0aefad5..df1145742b6 100644 --- a/src/emu/debug/dvwpoints.h +++ b/src/emu/debug/dvwpoints.h @@ -12,7 +12,6 @@ #pragma once -#include "debugcpu.h" #include "debugvw.h" diff --git a/src/emu/debug/express.cpp b/src/emu/debug/express.cpp index 0d711d676d2..9ab75b7b655 100644 --- a/src/emu/debug/express.cpp +++ b/src/emu/debug/express.cpp @@ -1740,7 +1740,7 @@ void parsed_expression::infix_to_postfix() else if (token->is_operator()) { // normalize the operator based on neighbors - normalize_operator(*token, prev, next != m_tokenlist.end() ? &*next : nullptr, stack, was_rparen); + normalize_operator(*token, prev, next != origlist.end() ? &*next : nullptr, stack, was_rparen); was_rparen = false; // if the token is an opening parenthesis, push it onto the stack. diff --git a/src/emu/debug/points.cpp b/src/emu/debug/points.cpp index 961488eebb2..12e4b614843 100644 --- a/src/emu/debug/points.cpp +++ b/src/emu/debug/points.cpp @@ -10,8 +10,10 @@ #include "emu.h" #include "points.h" + #include "debugger.h" #include "debugcon.h" +#include "debugcpu.h" //************************************************************************** diff --git a/src/emu/debug/points.h b/src/emu/debug/points.h index 42abd46398f..cf9e5bfc44d 100644 --- a/src/emu/debug/points.h +++ b/src/emu/debug/points.h @@ -13,7 +13,6 @@ #pragma once -#include "debugcpu.h" #include "express.h" diff --git a/src/emu/devcb.h b/src/emu/devcb.h index be62d8b6365..d2ee406aa60 100644 --- a/src/emu/devcb.h +++ b/src/emu/devcb.h @@ -756,6 +756,12 @@ private: auto set_constant(Result val) { return set([val] () { return val; }); } auto append_constant(Result val) { return append([val] () { return val; }); } + void remove() + { + set_used(); + m_target.m_creators.clear(); + } + private: void set_used() { assert(!m_used); m_used = true; } @@ -2152,6 +2158,12 @@ private: m_target.m_creators.emplace_back(std::make_unique<nop_creator>()); } + void remove() + { + set_used(); + m_target.m_creators.clear(); + } + private: void set_used() { assert(!m_used); m_used = true; } diff --git a/src/emu/devcpu.cpp b/src/emu/devcpu.cpp index efd12d682a9..616a501f438 100644 --- a/src/emu/devcpu.cpp +++ b/src/emu/devcpu.cpp @@ -21,15 +21,15 @@ // cpu_device - constructor //------------------------------------------------- -cpu_device::cpu_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock) - : device_t(mconfig, type, tag, owner, clock), - device_execute_interface(mconfig, *this), - device_memory_interface(mconfig, *this), - device_state_interface(mconfig, *this), - device_disasm_interface(mconfig, *this), - m_force_no_drc(false), - m_access_to_be_redone(false), - m_access_before_delay_tag(nullptr) +cpu_device::cpu_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock) : + device_t(mconfig, type, tag, owner, clock), + device_execute_interface(mconfig, *this), + device_memory_interface(mconfig, *this), + device_state_interface(mconfig, *this), + device_disasm_interface(mconfig, *this), + m_force_no_drc(false), + m_access_to_be_redone(false), + m_access_before_delay_tag(nullptr) { } @@ -63,9 +63,7 @@ bool cpu_device::access_before_time(u64 access_time, u64 current_time) noexcept { s32 delta = access_time - current_time; if(*m_icountptr <= delta) { - if(*m_icountptr > 0) - *m_icountptr = 0; - m_access_to_be_redone = true; + defer_access(); return true; } @@ -100,6 +98,13 @@ void cpu_device::access_after_delay(u32 cycles) noexcept void cpu_device::defer_access() noexcept { + if(*m_icountptr > 0) + *m_icountptr = 0; + m_access_to_be_redone = true; +} + +void cpu_device::retry_access() noexcept +{ + abort_timeslice(); m_access_to_be_redone = true; - *m_icountptr = 0; } diff --git a/src/emu/devcpu.h b/src/emu/devcpu.h index 70f7b1667a5..e4a37575858 100644 --- a/src/emu/devcpu.h +++ b/src/emu/devcpu.h @@ -50,7 +50,9 @@ public: // The access has already happened, nothing to abort void access_after_delay(u32 cycles) noexcept; + void defer_access() noexcept; + void retry_access() noexcept; protected: // construction/destruction @@ -58,7 +60,7 @@ protected: private: // configured state - bool m_force_no_drc; // whether or not to force DRC off + bool m_force_no_drc; // whether or not to force DRC off bool m_access_to_be_redone; // whether an access needs to be redone const void *m_access_before_delay_tag; // if the tag matches on access_before_delay, consider the delay to have already happened diff --git a/src/emu/device.cpp b/src/emu/device.cpp index 1940c619455..272df837cd4 100644 --- a/src/emu/device.cpp +++ b/src/emu/device.cpp @@ -368,7 +368,7 @@ void device_t::set_unscaled_clock(u32 clock, bool sync_on_new_clock_domain) return; m_unscaled_clock = clock; - m_clock = m_unscaled_clock * m_clock_scale; + m_clock = m_unscaled_clock * m_clock_scale + 0.5; m_attoseconds_per_clock = (m_clock == 0) ? 0 : HZ_TO_ATTOSECONDS(m_clock); // recalculate all derived clocks @@ -393,7 +393,7 @@ void device_t::set_clock_scale(double clockscale) return; m_clock_scale = clockscale; - m_clock = m_unscaled_clock * m_clock_scale; + m_clock = m_unscaled_clock * m_clock_scale + 0.5; m_attoseconds_per_clock = (m_clock == 0) ? 0 : HZ_TO_ATTOSECONDS(m_clock); // recalculate all derived clocks @@ -544,11 +544,10 @@ void device_t::start() // complain if nothing was registered by the device state_registrations = machine().save().registration_count() - state_registrations; device_execute_interface *exec; - device_sound_interface *sound; - if (state_registrations == 0 && (interface(exec) || interface(sound)) && type() != SPEAKER) + if ((state_registrations == 0) && interface(exec)) { logerror("Device did not register any state to save!\n"); - if ((machine().system().flags & MACHINE_SUPPORTS_SAVE) != 0) + if (!(type().emulation_flags() & flags::SAVE_UNSUPPORTED)) fatalerror("Device '%s' did not register any state to save!\n", tag()); } @@ -646,7 +645,7 @@ void device_t::pre_save() void device_t::post_load() { // recompute clock-related parameters if something changed - u32 const scaled_clock = m_unscaled_clock * m_clock_scale; + u32 const scaled_clock = m_unscaled_clock * m_clock_scale + 0.5; if (m_clock != scaled_clock) { m_clock = scaled_clock; diff --git a/src/emu/device.h b/src/emu/device.h index 87736144292..4c2e1d8a180 100644 --- a/src/emu/device.h +++ b/src/emu/device.h @@ -63,6 +63,18 @@ template <typename T> using is_device_interface = std::bool_constant<std::is_base_of_v<device_interface, T> && !is_device_implementation<T>::value>; +struct device_flags +{ + enum type : u16 + { + NOT_WORKING = u16(1) << 0, + SAVE_UNSUPPORTED = u16(1) << 1, + + NONE = u16(0), + ALL = (u16(1) << 2) - 1U + }; +}; + struct device_feature { enum type : u32 @@ -107,6 +119,7 @@ struct device_feature }; }; +DECLARE_ENUM_BITWISE_OPERATORS(device_flags::type); DECLARE_ENUM_BITWISE_OPERATORS(device_feature::type); @@ -165,14 +178,9 @@ private: template <class DeviceClass, char const *ShortName, char const *FullName, char const *Source> struct device_tag_struct { typedef DeviceClass type; }; -template <class DriverClass, char const *ShortName, char const *FullName, char const *Source, device_feature::type Unemulated, device_feature::type Imperfect> +template <class DriverClass, char const *ShortName, char const *FullName, char const *Source, device_flags::type Flags, device_feature::type Unemulated, device_feature::type Imperfect> struct driver_tag_struct { typedef DriverClass type; }; -template <class DeviceClass, char const *ShortName, char const *FullName, char const *Source> -auto device_tag_func() { return device_tag_struct<DeviceClass, ShortName, FullName, Source>{ }; }; -template <class DriverClass, char const *ShortName, char const *FullName, char const *Source, device_feature::type Unemulated, device_feature::type Imperfect> -auto driver_tag_func() { return driver_tag_struct<DriverClass, ShortName, FullName, Source, Unemulated, Imperfect>{ }; }; - class device_type_impl_base { private: @@ -205,6 +213,7 @@ private: char const *const m_shortname; char const *const m_fullname; char const *const m_source; + device_flags::type const m_emulation_flags; device_feature::type const m_unemulated_features; device_feature::type const m_imperfect_features; device_type_impl_base const *const m_parent_rom; @@ -220,6 +229,7 @@ public: , m_shortname(nullptr) , m_fullname(nullptr) , m_source(nullptr) + , m_emulation_flags(device_flags::NONE) , m_unemulated_features(device_feature::NONE) , m_imperfect_features(device_feature::NONE) , m_parent_rom(nullptr) @@ -228,12 +238,13 @@ public: } template <class DeviceClass, char const *ShortName, char const *FullName, char const *Source> - device_type_impl_base(device_tag_struct<DeviceClass, ShortName, FullName, Source> (*)()) + device_type_impl_base(device_tag_struct<DeviceClass, ShortName, FullName, Source>) : m_creator(&create_device<DeviceClass>) , m_type(typeid(DeviceClass)) , m_shortname(ShortName) , m_fullname(FullName) , m_source(Source) + , m_emulation_flags(DeviceClass::emulation_flags()) , m_unemulated_features(DeviceClass::unemulated_features()) , m_imperfect_features(DeviceClass::imperfect_features()) , m_parent_rom(DeviceClass::parent_rom_device_type()) @@ -241,13 +252,14 @@ public: { } - template <class DriverClass, char const *ShortName, char const *FullName, char const *Source, device_feature::type Unemulated, device_feature::type Imperfect> - device_type_impl_base(driver_tag_struct<DriverClass, ShortName, FullName, Source, Unemulated, Imperfect> (*)()) + template <class DriverClass, char const *ShortName, char const *FullName, char const *Source, device_flags::type Flags, device_feature::type Unemulated, device_feature::type Imperfect> + device_type_impl_base(driver_tag_struct<DriverClass, ShortName, FullName, Source, Flags, Unemulated, Imperfect>) : m_creator(&create_driver<DriverClass>) , m_type(typeid(DriverClass)) , m_shortname(ShortName) , m_fullname(FullName) , m_source(Source) + , m_emulation_flags(DriverClass::emulation_flags() | Flags) , m_unemulated_features(DriverClass::unemulated_features() | Unemulated) , m_imperfect_features((DriverClass::imperfect_features() & ~Unemulated) | Imperfect) , m_parent_rom(DriverClass::parent_rom_device_type()) @@ -259,6 +271,7 @@ public: char const *shortname() const { return m_shortname; } char const *fullname() const { return m_fullname; } char const *source() const { return m_source; } + device_flags::type emulation_flags() const { return m_emulation_flags; } device_feature::type unemulated_features() const { return m_unemulated_features; } device_feature::type imperfect_features() const { return m_imperfect_features; } device_type_impl_base const *parent_rom_device_type() const { return m_parent_rom; } @@ -306,22 +319,6 @@ typedef emu::detail::device_type_impl_base const &device_type; typedef std::add_pointer_t<device_type> device_type_ptr; extern emu::detail::device_registrar const registered_device_types; -template < - typename DeviceClass, - char const *ShortName, - char const *FullName, - char const *Source> -constexpr auto device_creator = &emu::detail::device_tag_func<DeviceClass, ShortName, FullName, Source>; - -template < - typename DriverClass, - char const *ShortName, - char const *FullName, - char const *Source, - emu::detail::device_feature::type Unemulated, - emu::detail::device_feature::type Imperfect> -constexpr auto driver_device_creator = &emu::detail::driver_tag_func<DriverClass, ShortName, FullName, Source, Unemulated, Imperfect>; - /// \addtogroup machinedef /// \{ @@ -404,7 +401,7 @@ constexpr auto driver_device_creator = &emu::detail::driver_tag_func<DriverClass struct Type##_device_traits { static constexpr char const shortname[] = ShortName, fullname[] = FullName, source[] = __FILE__; }; \ constexpr char const Type##_device_traits::shortname[], Type##_device_traits::fullname[], Type##_device_traits::source[]; \ } \ - emu::detail::device_type_impl<Class> const Type = device_creator<Class, (Type##_device_traits::shortname), (Type##_device_traits::fullname), (Type##_device_traits::source)>; \ + emu::detail::device_type_impl<Class> const Type = emu::detail::device_tag_struct<Class, (Type##_device_traits::shortname), (Type##_device_traits::fullname), (Type##_device_traits::source)>{ }; \ template class device_finder<Class, false>; \ template class device_finder<Class, true>; @@ -440,7 +437,7 @@ constexpr auto driver_device_creator = &emu::detail::driver_tag_func<DriverClass struct Type##_device_traits { static constexpr char const shortname[] = ShortName, fullname[] = FullName, source[] = __FILE__; }; \ constexpr char const Type##_device_traits::shortname[], Type##_device_traits::fullname[], Type##_device_traits::source[]; \ } \ - emu::detail::device_type_impl<Base> const Type = device_creator<Class, (Type##_device_traits::shortname), (Type##_device_traits::fullname), (Type##_device_traits::source)>; + emu::detail::device_type_impl<Base> const Type = emu::detail::device_tag_struct<Class, (Type##_device_traits::shortname), (Type##_device_traits::fullname), (Type##_device_traits::source)>{ }; /// \} @@ -565,9 +562,22 @@ protected: public: // device flags + using flags = emu::detail::device_flags; + using flags_type = emu::detail::device_flags::type; using feature = emu::detail::device_feature; using feature_type = emu::detail::device_feature::type; + /// \brief Report emulation status flags + /// + /// Implement this member in a derived class to declare flags + /// pertaining to the overall emulation status of the device. Some + /// flags propagate to all other devices and systems that use the + /// device. + /// \return Bitwise or of the flag constants pertaining to the + /// device emulation. + /// \sa unemulated_features imperfect_features + static constexpr flags_type emulation_flags() { return flags::NONE; } + /// \brief Report unemulated features /// /// Implement this member in a derived class to declare features @@ -577,7 +587,7 @@ public: /// displayed on starting a system. /// \return Bitwise or of the feature constants for unemulated /// features of the device. - /// \sa imperfect_features + /// \sa emulation_flags imperfect_features static constexpr feature_type unemulated_features() { return feature::NONE; } /// \brief Report imperfectly emulated features @@ -594,7 +604,7 @@ public: /// in a red warning being displayed when starting a system. /// \return Bitwise or of the feature constants for imperfectly /// emulated features of the device. - /// \sa unemulated_features + /// \sa emulation_flags unemulated_features static constexpr feature_type imperfect_features() { return feature::NONE; } /// \brief Get parent device type for ROM search diff --git a/src/emu/diexec.cpp b/src/emu/diexec.cpp index 4b06edc36d5..1cd5266c518 100644 --- a/src/emu/diexec.cpp +++ b/src/emu/diexec.cpp @@ -70,8 +70,6 @@ device_execute_interface::device_execute_interface(const machine_config &mconfig , m_attoseconds_per_cycle(0) , m_spin_end_timer(nullptr) { - memset(&m_localtime, 0, sizeof(m_localtime)); - // configure the fast accessor assert(!device.interfaces().m_execute); device.interfaces().m_execute = this; @@ -100,12 +98,11 @@ void device_execute_interface::abort_timeslice() noexcept return; // swallow the remaining cycles - if (m_icountptr != nullptr) + if (m_icountptr != nullptr && *m_icountptr > 0) { - int delta = *m_icountptr; - m_cycles_stolen += delta; - m_cycles_running -= delta; - *m_icountptr -= delta; + m_cycles_stolen += *m_icountptr; + m_cycles_running -= *m_icountptr; + *m_icountptr = 0; } } @@ -130,7 +127,8 @@ void device_execute_interface::suspend_resume_changed() void device_execute_interface::suspend(u32 reason, bool eatcycles) { -if (TEMPLOG) printf("suspend %s (%X)\n", device().tag(), reason); + if (TEMPLOG) printf("suspend %s (%X)\n", device().tag(), reason); + // set the suspend reason and eat cycles flag m_nextsuspend |= reason; m_nexteatcycles = eatcycles; @@ -145,7 +143,8 @@ if (TEMPLOG) printf("suspend %s (%X)\n", device().tag(), reason); void device_execute_interface::resume(u32 reason) { -if (TEMPLOG) printf("resume %s (%X)\n", device().tag(), reason); + if (TEMPLOG) printf("resume %s (%X)\n", device().tag(), reason); + // clear the suspend reason and eat cycles flag m_nextsuspend &= ~reason; suspend_resume_changed(); @@ -285,17 +284,6 @@ u32 device_execute_interface::execute_max_cycles() const noexcept //------------------------------------------------- -// execute_input_lines - return the total number -// of input lines for the device -//------------------------------------------------- - -u32 device_execute_interface::execute_input_lines() const noexcept -{ - return 0; -} - - -//------------------------------------------------- // execute_default_irq_vector - return the default // IRQ vector when an acknowledge is processed //------------------------------------------------- @@ -318,18 +306,6 @@ bool device_execute_interface::execute_input_edge_triggered(int linenum) const n //------------------------------------------------- -// execute_burn - called after we consume a bunch -// of cycles for artifical reasons (such as -// spinning devices for performance optimization) -//------------------------------------------------- - -void device_execute_interface::execute_burn(s32 cycles) -{ - // by default, do nothing -} - - -//------------------------------------------------- // execute_set_input - called when a synchronized // input is changed //------------------------------------------------- @@ -462,7 +438,7 @@ void device_execute_interface::interface_post_reset() if (m_vblank_interrupt_screen != nullptr) { // get the screen that will trigger the VBLANK - screen_device * screen = device().siblingdevice<screen_device>(m_vblank_interrupt_screen); + screen_device *const screen = device().siblingdevice<screen_device>(m_vblank_interrupt_screen); assert(screen != nullptr); screen->register_vblank_callback(vblank_state_delegate(&device_execute_interface::on_vblank, this)); @@ -537,7 +513,7 @@ int device_execute_interface::standard_irq_callback(int irqline, offs_t pc) vector = m_driver_irq(device(), irqline); // notify the debugger - if (device().machine().debug_flags & DEBUG_FLAG_ENABLED) + if (debugger_enabled()) device().debug()->interrupt_hook(irqline, pc); return vector; @@ -608,21 +584,28 @@ TIMER_CALLBACK_MEMBER(device_execute_interface::trigger_periodic_interrupt) void device_execute_interface::pulse_input_line(int irqline, const attotime &duration) { - // treat instantaneous pulses as ASSERT+CLEAR + const attotime expiry = m_pulse_end_timers[irqline]->expire(); if (duration == attotime::zero) { - if (irqline != INPUT_LINE_RESET && !input_edge_triggered(irqline)) + // treat instantaneous pulses as ASSERT+CLEAR + if ((irqline != INPUT_LINE_RESET) && !input_edge_triggered(irqline)) throw emu_fatalerror("device '%s': zero-width pulse is not allowed for input line %d\n", device().tag(), irqline); - set_input_line(irqline, ASSERT_LINE); - set_input_line(irqline, CLEAR_LINE); + if (expiry.is_never() || (expiry <= m_scheduler->time())) + { + set_input_line(irqline, ASSERT_LINE); + set_input_line(irqline, CLEAR_LINE); + } } else { - set_input_line(irqline, ASSERT_LINE); + const attotime target_time = local_time() + duration; + if (expiry.is_never() || (target_time > expiry)) + { + set_input_line(irqline, ASSERT_LINE); - attotime target_time = local_time() + duration; - m_pulse_end_timers[irqline]->adjust(target_time - m_scheduler->time(), irqline); + m_pulse_end_timers[irqline]->adjust(target_time - m_scheduler->time(), irqline); + } } } @@ -681,7 +664,7 @@ void device_execute_interface::device_input::set_state_synced(int state, int vec { LOG(("set_state_synced('%s',%d,%d,%02x)\n", m_execute->device().tag(), m_linenum, state, vector)); -if (TEMPLOG) printf("setline(%s,%d,%d,%d)\n", m_execute->device().tag(), m_linenum, state, (vector == USE_STORED_VECTOR) ? 0 : vector); + if (TEMPLOG) printf("setline(%s,%d,%d,%d)\n", m_execute->device().tag(), m_linenum, state, (vector == USE_STORED_VECTOR) ? 0 : vector); assert(state == ASSERT_LINE || state == HOLD_LINE || state == CLEAR_LINE); // if we're full of events, flush the queue and log a message @@ -714,7 +697,8 @@ if (TEMPLOG) printf("setline(%s,%d,%d,%d)\n", m_execute->device().tag(), m_linen TIMER_CALLBACK_MEMBER(device_execute_interface::device_input::empty_event_queue) { -if (TEMPLOG) printf("empty_queue(%s,%d,%d)\n", m_execute->device().tag(), m_linenum, m_qindex); + if (TEMPLOG) printf("empty_queue(%s,%d,%d)\n", m_execute->device().tag(), m_linenum, m_qindex); + // loop over all events for (int curevent = 0; curevent < m_qindex; curevent++) { @@ -723,7 +707,7 @@ if (TEMPLOG) printf("empty_queue(%s,%d,%d)\n", m_execute->device().tag(), m_line // set the input line state and vector m_curstate = input_event & 0xff; m_curvector = input_event >> 8; -if (TEMPLOG) printf(" (%d,%d)\n", m_curstate, m_curvector); + if (TEMPLOG) printf(" (%d,%d)\n", m_curstate, m_curvector); assert(m_curstate == ASSERT_LINE || m_curstate == HOLD_LINE || m_curstate == CLEAR_LINE); diff --git a/src/emu/diexec.h b/src/emu/diexec.h index 51973ad19cc..1e8303287e8 100644 --- a/src/emu/diexec.h +++ b/src/emu/diexec.h @@ -109,7 +109,6 @@ public: u32 max_cycles() const { return execute_max_cycles(); } attotime cycles_to_attotime(u64 cycles) const { return device().clocks_to_attotime(cycles_to_clocks(cycles)); } u64 attotime_to_cycles(const attotime &duration) const { return clocks_to_cycles(device().attotime_to_clocks(duration)); } - u32 input_lines() const { return execute_input_lines(); } u32 default_irq_vector(int linenum) const { return execute_default_irq_vector(linenum); } bool input_edge_triggered(int linenum) const { return execute_input_edge_triggered(linenum); } @@ -164,7 +163,7 @@ public: void set_input_line(int linenum, int state) { assert(device().started()); m_input[linenum].set_state_synced(state); } void set_input_line_vector(int linenum, int vector) { assert(device().started()); m_input[linenum].set_vector(vector); } void set_input_line_and_vector(int linenum, int state, int vector) { assert(device().started()); m_input[linenum].set_state_synced(state, vector); } - int input_state(int linenum) const { assert(device().started()); return m_input[linenum].m_curstate; } + int input_line_state(int linenum) const { assert(device().started()); return m_input[linenum].m_curstate; } void pulse_input_line(int irqline, const attotime &duration); // suspend/resume @@ -189,8 +188,7 @@ public: // required operation overrides void run() { execute_run(); } - // deliberately ambiguous functions; if you have the execute interface - // just use it + // deliberately ambiguous functions; if you have the execute interface just use it device_execute_interface &execute() { return *this; } protected: @@ -201,13 +199,11 @@ protected: virtual u32 execute_max_cycles() const noexcept; // input line information getters - virtual u32 execute_input_lines() const noexcept; virtual u32 execute_default_irq_vector(int linenum) const noexcept; virtual bool execute_input_edge_triggered(int linenum) const noexcept; // optional operation overrides virtual void execute_run() = 0; - virtual void execute_burn(s32 cycles); virtual void execute_set_input(int linenum, int state); // interface-level overrides @@ -219,7 +215,6 @@ protected: virtual void interface_clock_changed(bool sync_on_new_clock_domain) override; // for use by devcpu for now... - int current_input_state(unsigned i) const { return m_input[i].m_curstate; } void set_icountptr(int &icount) { assert(!m_icountptr); m_icountptr = &icount; } int standard_irq_callback(int irqline, offs_t pc); @@ -242,6 +237,12 @@ protected: device().debug()->privilege_hook(); } + void debugger_wait_hook() + { + if (device().machine().debug_flags & DEBUG_FLAG_CALL_HOOK) + device().debug()->wait_hook(); + } + private: // internal information about the state of inputs class device_input diff --git a/src/emu/digfx.cpp b/src/emu/digfx.cpp index c0b25c69b22..31f87216775 100644 --- a/src/emu/digfx.cpp +++ b/src/emu/digfx.cpp @@ -99,6 +99,24 @@ void device_gfx_interface::interface_post_start() //------------------------------------------------- +// interface_post_load - mark RAM-based entries +// dirty after loading save state +//------------------------------------------------- + +void device_gfx_interface::interface_post_load() +{ + if (!m_gfxdecodeinfo) + return; + + for (int curgfx = 0; curgfx < MAX_GFX_ELEMENTS && m_gfxdecodeinfo[curgfx].gfxlayout != nullptr; curgfx++) + { + if (GFXENTRY_ISRAM(m_gfxdecodeinfo[curgfx].flags)) + m_gfx[curgfx]->mark_all_dirty(); + } +} + + +//------------------------------------------------- // decode_gfx - parse gfx decode info and // create gfx elements //------------------------------------------------- @@ -106,7 +124,7 @@ void device_gfx_interface::interface_post_start() void device_gfx_interface::decode_gfx(const gfx_decode_entry *gfxdecodeinfo) { // skip if nothing to do - if (gfxdecodeinfo == nullptr) + if (!gfxdecodeinfo) return; // local variables to hold mutable copies of gfx layout data @@ -298,9 +316,9 @@ void device_gfx_interface::interface_validity_check(validity_checker &valid) con return; // validate graphics decoding entries - for (int gfxnum = 0; gfxnum < MAX_GFX_ELEMENTS && m_gfxdecodeinfo[gfxnum].gfxlayout != nullptr; gfxnum++) + for (int curgfx = 0; curgfx < MAX_GFX_ELEMENTS && m_gfxdecodeinfo[curgfx].gfxlayout != nullptr; curgfx++) { - const gfx_decode_entry &gfx = m_gfxdecodeinfo[gfxnum]; + const gfx_decode_entry &gfx = m_gfxdecodeinfo[curgfx]; const gfx_layout &layout = *gfx.gfxlayout; // currently we are unable to validate RAM-based entries @@ -316,7 +334,7 @@ void device_gfx_interface::interface_validity_check(validity_checker &valid) con u32 region_length = valid.region_length(gfxregion.c_str()); if (region_length == 0) - osd_printf_error("gfx[%d] references nonexistent region '%s'\n", gfxnum, gfxregion); + osd_printf_error("gfx[%d] references nonexistent region '%s'\n", curgfx, gfxregion); // if we have a valid region, and we're not using auto-sizing, check the decode against the region length else if (!IS_FRAC(layout.total)) @@ -336,7 +354,7 @@ void device_gfx_interface::interface_validity_check(validity_checker &valid) con // if not, this is an error if ((start + len) / 8 > avail) - osd_printf_error("gfx[%d] extends past allocated memory of region '%s'\n", gfxnum, region); + osd_printf_error("gfx[%d] extends past allocated memory of region '%s'\n", curgfx, region); } } @@ -347,9 +365,9 @@ void device_gfx_interface::interface_validity_check(validity_checker &valid) con if (layout.planeoffset[0] == GFX_RAW) { if (layout.total != RGN_FRAC(1,1)) - osd_printf_error("gfx[%d] RAW layouts can only be RGN_FRAC(1,1)\n", gfxnum); + osd_printf_error("gfx[%d] RAW layouts can only be RGN_FRAC(1,1)\n", curgfx); if (xscale != 1 || yscale != 1) - osd_printf_error("gfx[%d] RAW layouts do not support xscale/yscale\n", gfxnum); + osd_printf_error("gfx[%d] RAW layouts do not support xscale/yscale\n", curgfx); } // verify traditional decode doesn't have too many planes, @@ -357,11 +375,11 @@ void device_gfx_interface::interface_validity_check(validity_checker &valid) con else { if (layout.planes > MAX_GFX_PLANES) - osd_printf_error("gfx[%d] planes > %d\n", gfxnum, MAX_GFX_PLANES); + osd_printf_error("gfx[%d] planes > %d\n", curgfx, MAX_GFX_PLANES); if (layout.width > MAX_GFX_SIZE && layout.extxoffs == nullptr) - osd_printf_error("gfx[%d] width > %d but missing extended xoffset info\n", gfxnum, MAX_GFX_SIZE); + osd_printf_error("gfx[%d] width > %d but missing extended xoffset info\n", curgfx, MAX_GFX_SIZE); if (layout.height > MAX_GFX_SIZE && layout.extyoffs == nullptr) - osd_printf_error("gfx[%d] height > %d but missing extended yoffset info\n", gfxnum, MAX_GFX_SIZE); + osd_printf_error("gfx[%d] height > %d but missing extended yoffset info\n", curgfx, MAX_GFX_SIZE); } } } diff --git a/src/emu/digfx.h b/src/emu/digfx.h index 6ba323baafc..8c18c1f37da 100644 --- a/src/emu/digfx.h +++ b/src/emu/digfx.h @@ -72,7 +72,7 @@ const gfx_layout name = { width, height, RGN_FRAC(1,1), 8, { GFX_RAW }, { 0 }, { #define STEP2048(START,STEP) STEP1024(START,STEP),STEP1024((START)+1024*(STEP),STEP) #define STEP2_INV(START,STEP) (START)+(STEP),(START) -#define STEP4_INV(START,STEP) STEP2_INV(START+2*STEP,STEP),STEP2_INV(START,STEP) +#define STEP4_INV(START,STEP) STEP2_INV(START+2*STEP,STEP),STEP2_INV(START,STEP) //************************************************************************** // GRAPHICS INFO MACROS @@ -118,6 +118,7 @@ const gfx_layout name = { width, height, RGN_FRAC(1,1), 8, { GFX_RAW }, { 0 }, { #define GFXDECODE_DEVICE_RAM(region,offset,layout,start,colors) { region, offset, &layout, start, colors, GFXENTRY_DEVICE | GFXENTRY_RAM }, #define GFXDECODE_SCALE(region,offset,layout,start,colors,x,y) { region, offset, &layout, start, colors, GFXENTRY_XSCALE(x) | GFXENTRY_YSCALE(y) }, #define GFXDECODE_REVERSEBITS(region,offset,layout,start,colors) { region, offset, &layout, start, colors, GFXENTRY_REVERSE }, +#define GFXDECODE_DEVICE_REVERSEBITS(region,offset,layout,start,colors) { region, offset, &layout, start, colors, GFXENTRY_DEVICE | GFXENTRY_REVERSE }, @@ -185,17 +186,18 @@ protected: virtual void interface_validity_check(validity_checker &valid) const override; virtual void interface_pre_start() override; virtual void interface_post_start() override; + virtual void interface_post_load() override; private: - optional_device<device_palette_interface> m_palette; // configured tag for palette device - std::unique_ptr<gfx_element> m_gfx[MAX_GFX_ELEMENTS]; // array of pointers to graphic sets + optional_device<device_palette_interface> m_palette; // configured tag for palette device + std::unique_ptr<gfx_element> m_gfx[MAX_GFX_ELEMENTS]; // array of pointers to graphic sets // configuration - const gfx_decode_entry * m_gfxdecodeinfo; // pointer to array of gfx decode information - bool m_palette_is_disabled; // no palette associated with this gfx decode + const gfx_decode_entry * m_gfxdecodeinfo; // pointer to array of gfx decode information + bool m_palette_is_disabled; // no palette associated with this gfx decode // internal state - bool m_decoded; // have we processed our decode info yet? + bool m_decoded; // have we processed our decode info yet? }; // iterator diff --git a/src/emu/diimage.h b/src/emu/diimage.h index 900a4410a05..aa9c991c955 100644 --- a/src/emu/diimage.h +++ b/src/emu/diimage.h @@ -148,15 +148,13 @@ public: u32 fread(void *buffer, u32 length) { check_for_file(); - size_t actual; - m_file->read(buffer, length, actual); + auto const [err, actual] = read(*m_file, buffer, length); return actual; } u32 fwrite(const void *buffer, u32 length) { check_for_file(); - size_t actual; - m_file->write(buffer, length, actual); + auto const [err, actual] = write(*m_file, buffer, length); return actual; } std::error_condition fseek(s64 offset, int whence) @@ -172,10 +170,6 @@ public: return result; } - // allocate and read into buffers - u32 fread(std::unique_ptr<u8 []> &ptr, u32 length) { ptr = std::make_unique<u8 []>(length); return fread(ptr.get(), length); } - u32 fread(std::unique_ptr<u8 []> &ptr, u32 length, offs_t offset) { ptr = std::make_unique<u8 []>(length); return fread(ptr.get() + offset, length - offset); } - // access to software list item information const software_info *software_entry() const noexcept; const software_part *part_entry() const noexcept { return m_software_part_ptr; } diff --git a/src/emu/dinetwork.cpp b/src/emu/dinetwork.cpp index 7333fd25de8..30e10361c97 100644 --- a/src/emu/dinetwork.cpp +++ b/src/emu/dinetwork.cpp @@ -3,16 +3,21 @@ #include "emu.h" #include "dinetwork.h" -#include "osdnet.h" + +#include "osdepend.h" + +#include <algorithm> + device_network_interface::device_network_interface(const machine_config &mconfig, device_t &device, u32 bandwidth, u32 mtu) : device_interface(device, "network") + , m_poll_timer(nullptr) + , m_send_timer(nullptr) + , m_recv_timer(nullptr) { - m_promisc = false; // Convert to Mibps to Bps m_bandwidth = bandwidth << (20 - 3); m_mtu = mtu; - memset(m_mac, 0, 6); m_intf = -1; m_loopback_control = false; } @@ -21,15 +26,23 @@ device_network_interface::~device_network_interface() { } -void device_network_interface::interface_pre_start() +void device_network_interface::interface_post_start() { + m_poll_timer = device().machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(device_network_interface::poll_device), this)); m_send_timer = device().machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(device_network_interface::send_complete), this)); m_recv_timer = device().machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(device_network_interface::recv_complete), this)); + + device().save_item(NAME(m_loopback_control)); } -void device_network_interface::interface_post_start() +void device_network_interface::interface_post_load() { - device().save_item(NAME(m_loopback_control)); + if (!m_dev) + m_poll_timer->reset(); + else if (!m_loopback_control && !m_recv_timer->enabled()) + start_net_device(); + else + stop_net_device(); } int device_network_interface::send(u8 *buf, int len, int fcs) @@ -65,6 +78,25 @@ int device_network_interface::send(u8 *buf, int len, int fcs) return result; } +TIMER_CALLBACK_MEMBER(device_network_interface::poll_device) +{ + m_dev->poll(); +} + +void device_network_interface::start_net_device() +{ + // Set device polling time to transfer time for one MTU + m_dev->start(); + const attotime interval = attotime::from_hz(m_bandwidth / m_mtu); + m_poll_timer->adjust(attotime::zero, 0, interval); +} + +void device_network_interface::stop_net_device() +{ + m_poll_timer->reset(); + m_dev->stop(); +} + TIMER_CALLBACK_MEMBER(device_network_interface::send_complete) { send_complete_cb(param); @@ -85,7 +117,7 @@ void device_network_interface::recv_cb(u8 *buf, int len) { // stop receiving more data from the network if (m_dev) - m_dev->stop(); + stop_net_device(); // schedule receive complete callback m_recv_timer->adjust(attotime::from_ticks(len, m_bandwidth), result); @@ -98,28 +130,27 @@ TIMER_CALLBACK_MEMBER(device_network_interface::recv_complete) // start receiving data from the network again if (m_dev && !m_loopback_control) - m_dev->start(); -} - -void device_network_interface::set_promisc(bool promisc) -{ - m_promisc = promisc; - if(m_dev) m_dev->set_promisc(promisc); + start_net_device(); } void device_network_interface::set_mac(const u8 *mac) { - memcpy(m_mac, mac, 6); - if(m_dev) m_dev->set_mac(m_mac); + std::copy_n(mac, std::size(m_mac), std::begin(m_mac)); } void device_network_interface::set_interface(int id) { - if(m_dev) - m_dev->stop(); - // Set device polling time to transfer time for one mtu - m_dev.reset(open_netdev(id, this, (int)(m_bandwidth / m_mtu))); - if(!m_dev) { + if (m_dev) + stop_net_device(); + + m_dev = device().machine().osd().open_network_device(id, *this); + if (m_dev) + { + if (!m_loopback_control) + start_net_device(); + } + else + { device().logerror("Network interface %d not found\n", id); id = -1; } @@ -136,8 +167,8 @@ void device_network_interface::set_loopback(bool loopback) if (m_dev) { if (loopback) - m_dev->stop(); + stop_net_device(); else if (!m_recv_timer->enabled()) - m_dev->start(); + start_net_device(); } } diff --git a/src/emu/dinetwork.h b/src/emu/dinetwork.h index 6dd062c16aa..99fc1cb3a88 100644 --- a/src/emu/dinetwork.h +++ b/src/emu/dinetwork.h @@ -3,30 +3,28 @@ #ifndef MAME_EMU_DINETWORK_H #define MAME_EMU_DINETWORK_H -class osd_netdev; +#include "interface/nethandler.h" -class device_network_interface : public device_interface + +class device_network_interface : public device_interface, public osd::network_handler { public: device_network_interface(const machine_config &mconfig, device_t &device, u32 bandwidth, u32 mtu = 1500); virtual ~device_network_interface(); - void interface_pre_start() override; - void interface_post_start() override; + void interface_post_start() override ATTR_COLD; + void interface_post_load() override ATTR_COLD; - void set_interface(int id); - void set_promisc(bool promisc); + void set_interface(int id) ATTR_COLD; void set_mac(const u8 *mac); void set_loopback(bool loopback); - const char *get_mac() const { return m_mac; } - bool get_promisc() const { return m_promisc; } int get_interface() const { return m_intf; } int send(u8 *buf, int len, int fcs = 0); // TODO: de-virtualise this when existing devices implement delayed receive - virtual void recv_cb(u8 *buf, int len); + virtual void recv_cb(u8 *buf, int len) override; // delayed transmit/receive handlers virtual void send_complete_cb(int result) {} @@ -34,19 +32,25 @@ public: virtual void recv_complete_cb(int result) {} protected: - TIMER_CALLBACK_MEMBER(send_complete); - TIMER_CALLBACK_MEMBER(recv_complete); + bool has_net_device() const noexcept { return bool(m_dev); } - bool m_promisc; - char m_mac[6]; // bandwidth in bytes per second u32 m_bandwidth; // maximum transmission unit, used for device polling time u32 m_mtu; - std::unique_ptr<osd_netdev> m_dev; int m_intf; bool m_loopback_control; +private: + TIMER_CALLBACK_MEMBER(poll_device); + TIMER_CALLBACK_MEMBER(send_complete); + TIMER_CALLBACK_MEMBER(recv_complete); + + void start_net_device(); + void stop_net_device(); + + std::unique_ptr<osd::network_device> m_dev; + emu_timer *m_poll_timer; emu_timer *m_send_timer; emu_timer *m_recv_timer; }; diff --git a/src/emu/dirom.h b/src/emu/dirom.h index 1173a1b5d35..72d844f8b0e 100644 --- a/src/emu/dirom.h +++ b/src/emu/dirom.h @@ -22,6 +22,7 @@ public: template <typename... T> void set_map(T &&... args) { set_addrmap(0, std::forward<T>(args)...); } template <typename T> void set_device_rom_tag(T &&tag) { m_rom_region.set_tag(std::forward<T>(tag)); } + template <typename T> void set_space(T &&tag, int spacenum) { m_rom_space.set_tag(tag, spacenum); } u8 read_byte(offs_t addr) { return m_rom_cache.read_byte(addr); } u16 read_word(offs_t addr) { return m_rom_cache.read_word(addr); } @@ -40,6 +41,7 @@ protected: private: optional_memory_region m_rom_region; + optional_address_space m_rom_space; address_space_config m_rom_config; typename memory_access<AddrWidth, DataWidth, AddrShift, Endian>::cache m_rom_cache; diff --git a/src/emu/dirom.ipp b/src/emu/dirom.ipp index 28951ae50aa..d41da70a618 100644 --- a/src/emu/dirom.ipp +++ b/src/emu/dirom.ipp @@ -9,6 +9,7 @@ template<int AddrWidth, int DataWidth, int AddrShift, endianness_t Endian> device_rom_interface<AddrWidth, DataWidth, AddrShift, Endian>::device_rom_interface(const machine_config &mconfig, device_t &device) : device_memory_interface(mconfig, device), m_rom_region(device, DEVICE_SELF), + m_rom_space(device, finder_base::DUMMY_TAG, -1), m_rom_config("rom", Endian, 8 << DataWidth, AddrWidth, AddrShift), m_bank(device, "bank"), m_cur_bank(-1) @@ -109,6 +110,11 @@ void device_rom_interface<AddrWidth, DataWidth, AddrShift, Endian>::interface_pr { device_memory_interface::interface_pre_start(); + if(m_rom_space.spacenum() != -1) { + m_rom_space->cache(m_rom_cache); + return; + } + if(!has_space(0)) return; diff --git a/src/emu/dirtc.h b/src/emu/dirtc.h index e741cfc2628..e76a8d3dba3 100644 --- a/src/emu/dirtc.h +++ b/src/emu/dirtc.h @@ -51,7 +51,7 @@ public: void set_use_utc(bool use_utc) { m_use_utc = use_utc; } void set_time(bool update, int year, int month, int day, int day_of_week, int hour, int minute, int second); - virtual void set_current_time(const system_time &systime); + void set_current_time(const system_time &systime); bool has_battery() const { return rtc_battery_backed(); } diff --git a/src/emu/diserial.cpp b/src/emu/diserial.cpp index 5f7c00b7e59..2d900f27459 100644 --- a/src/emu/diserial.cpp +++ b/src/emu/diserial.cpp @@ -25,6 +25,7 @@ device_serial_interface::device_serial_interface(const machine_config &mconfig, m_df_word_length(0), m_df_parity(PARITY_NONE), m_df_stop_bit_count(STOP_BITS_0), + m_df_min_rx_stop_bit_count(0), m_rcv_register_data(0x8000), m_rcv_flags(0), m_rcv_bit_count_received(0), @@ -44,21 +45,6 @@ device_serial_interface::device_serial_interface(const machine_config &mconfig, m_tra_clock_state(false), m_rcv_clock_state(false) { - /* if sum of all bits in the byte is even, then the data - has even parity, otherwise it has odd parity */ - for (int i=0; i<256; i++) - { - int sum = 0; - int data = i; - - for (int b=0; b<8; b++) - { - sum+=data & 0x01; - data = data>>1; - } - - m_serial_parity_table[i] = sum & 0x01; - } } device_serial_interface::~device_serial_interface() @@ -81,6 +67,7 @@ void device_serial_interface::interface_post_start() device().save_item(NAME(m_df_word_length)); device().save_item(NAME(m_df_parity)); device().save_item(NAME(m_df_stop_bit_count)); + device().save_item(NAME(m_df_min_rx_stop_bit_count)); device().save_item(NAME(m_rcv_register_data)); device().save_item(NAME(m_rcv_flags)); device().save_item(NAME(m_rcv_bit_count_received)); @@ -174,25 +161,30 @@ void device_serial_interface::set_data_frame(int start_bit_count, int data_bit_c case STOP_BITS_0: default: m_df_stop_bit_count = 0; + m_df_min_rx_stop_bit_count = 0; break; case STOP_BITS_1: m_df_stop_bit_count = 1; + m_df_min_rx_stop_bit_count = 1; break; case STOP_BITS_1_5: m_df_stop_bit_count = 2; // TODO: support 1.5 stop bits + m_df_min_rx_stop_bit_count = 1; break; case STOP_BITS_2: m_df_stop_bit_count = 2; + m_df_min_rx_stop_bit_count = 1; break; } m_df_parity = parity; m_df_start_bit_count = start_bit_count; - m_rcv_bit_count = m_df_word_length + m_df_stop_bit_count; + /* Require at least one stop bit in async RX mode, none in sync RX mode. */ + m_rcv_bit_count = m_df_word_length + m_df_min_rx_stop_bit_count; if (m_df_parity != PARITY_NONE) { @@ -226,8 +218,10 @@ void device_serial_interface::rx_w(int state) { LOGMASKED(LOG_RX, "Receiver is synchronized\n"); if (m_rcv_clock && !(m_rcv_rate.is_never())) - // make start delay just a bit longer to make sure we are called after the sender - m_rcv_clock->adjust(((m_rcv_rate*3)/2), 0, m_rcv_rate); + { + // make start delay half a cycle longer to make sure we are called after the sender + m_rcv_clock->adjust(m_rcv_rate*2, 0, m_rcv_rate); + } else if (m_start_bit_hack_for_external_clocks) m_rcv_bit_count_received--; } @@ -272,6 +266,10 @@ void device_serial_interface::receive_register_update_bit(int bit) m_rcv_framing_error = false; m_rcv_parity_error = false; } + else + { + LOGMASKED(LOG_RX, "Receiver saw stop bit (%s)\n", device().machine().time().to_string()); + } } } else if (m_rcv_flags & RECEIVE_REGISTER_SYNCHRONISED) @@ -279,7 +277,7 @@ void device_serial_interface::receive_register_update_bit(int bit) LOGMASKED(LOG_RX, "Received bit %d as %d (%s)\n", m_rcv_bit_count_received, bit, device().machine().time().to_string()); m_rcv_bit_count_received++; - if (!bit && (m_rcv_bit_count_received > (m_rcv_bit_count - m_df_stop_bit_count))) + if (!bit && (m_rcv_bit_count_received > (m_rcv_bit_count - m_df_min_rx_stop_bit_count))) { LOGMASKED(LOG_RX, "Framing error\n"); m_rcv_framing_error = true; @@ -304,13 +302,13 @@ void device_serial_interface::receive_register_extract() receive_register_reset(); /* strip off stop bits and parity */ - assert(m_rcv_bit_count >0 && m_rcv_bit_count <= 16); - data = m_rcv_register_data>>(16-m_rcv_bit_count); + assert(m_rcv_bit_count > 0 && m_rcv_bit_count <= 16); + data = m_rcv_register_data >> (16 - m_rcv_bit_count); /* mask off other bits so data byte has 0's in unused bits */ - data &= ~(0xff<<m_df_word_length); + data &= ~(0xff << m_df_word_length); - m_rcv_byte_received = data; + m_rcv_byte_received = data; LOGMASKED(LOG_RX, "Receive data 0x%02x\n", m_rcv_byte_received); if(m_df_parity == PARITY_NONE) @@ -323,12 +321,12 @@ void device_serial_interface::receive_register_extract() switch (m_df_parity) { case PARITY_ODD: - if (parity_received == serial_helper_get_parity(data)) + if (parity_received == BIT(population_count_32(data), 0)) m_rcv_parity_error = true; break; case PARITY_EVEN: - if (parity_received != serial_helper_get_parity(data)) + if (parity_received != BIT(population_count_32(data), 0)) m_rcv_parity_error = true; break; @@ -356,9 +354,8 @@ void device_serial_interface::transmit_register_reset() void device_serial_interface::transmit_register_add_bit(int bit) { /* combine bit */ - m_tra_register_data = m_tra_register_data<<1; - m_tra_register_data &=~1; - m_tra_register_data|=(bit & 0x01); + m_tra_register_data = m_tra_register_data << 1; + m_tra_register_data |= (bit & 0x01); m_tra_bit_count++; } @@ -366,10 +363,9 @@ void device_serial_interface::transmit_register_add_bit(int bit) /* generate data in stream format ready for transfer */ void device_serial_interface::transmit_register_setup(u8 data_byte) { - int i; u8 transmit_data; - if(m_tra_clock && !m_tra_rate.is_never()) + if (m_tra_clock && !m_tra_rate.is_never()) m_tra_clock->adjust(m_tra_rate, 0, m_tra_rate); m_tra_bit_count_transmitted = 0; @@ -377,40 +373,35 @@ void device_serial_interface::transmit_register_setup(u8 data_byte) m_tra_flags &=~TRANSMIT_REGISTER_EMPTY; /* start bit */ - for (i=0; i<m_df_start_bit_count; i++) + for (int i = 0; i < m_df_start_bit_count; i++) { transmit_register_add_bit(0); } /* data bits */ transmit_data = data_byte; - for (i=0; i<m_df_word_length; i++) + for (int i = 0; i < m_df_word_length; i++) { - int databit; - - /* get bit from data */ - databit = transmit_data & 0x01; /* add bit to formatted byte */ - transmit_register_add_bit(databit); - transmit_data = transmit_data>>1; + transmit_register_add_bit(BIT(transmit_data, 0)); + transmit_data >>= 1; } /* parity */ - if (m_df_parity!=PARITY_NONE) + if (m_df_parity != PARITY_NONE) { /* odd or even parity */ u8 parity = 0; switch (m_df_parity) { case PARITY_ODD: - - /* get parity */ - /* if parity = 0, data has even parity - i.e. there is an even number of one bits in the data */ - /* if parity = 1, data has odd parity - i.e. there is an odd number of one bits in the data */ - parity = serial_helper_get_parity(data_byte) ^ 1; + // get parity + // if parity[0] = 0, data has even parity - i.e. there is an even number of one bits in the data + // if parity[0] = 1, data has odd parity - i.e. there is an odd number of one bits in the data + parity = BIT(population_count_32(data_byte), 0) ^ 1; break; case PARITY_EVEN: - parity = serial_helper_get_parity(data_byte); + parity = BIT(population_count_32(data_byte), 0); break; case PARITY_MARK: parity = 1; @@ -422,10 +413,9 @@ void device_serial_interface::transmit_register_setup(u8 data_byte) transmit_register_add_bit(parity); } - /* stop bit(s) + 1 extra bit as delay between bytes, needed to get 1 stop bit to work. */ - if (m_df_stop_bit_count) // no stop bits for synchronous - for (i=0; i<=m_df_stop_bit_count; i++) // ToDo - see if the hack on this line is still needed (was added 2016-04-10) - transmit_register_add_bit(1); + /* TX stop bit(s) */ + for (int i = 0; i < m_df_stop_bit_count; i++) + transmit_register_add_bit(1); } diff --git a/src/emu/diserial.h b/src/emu/diserial.h index aab03940206..eac3599e68a 100644 --- a/src/emu/diserial.h +++ b/src/emu/diserial.h @@ -103,8 +103,6 @@ protected: void transmit_register_setup(u8 data_byte); u8 transmit_register_get_data_bit(); - u8 serial_helper_get_parity(u8 data) { return m_serial_parity_table[data]; } - bool is_receive_register_full() const { return m_rcv_flags & RECEIVE_REGISTER_FULL; } bool is_transmit_register_empty() const { return m_tra_flags & TRANSMIT_REGISTER_EMPTY; } bool is_receive_register_synchronized() const { return m_rcv_flags & RECEIVE_REGISTER_SYNCHRONISED; } @@ -132,8 +130,6 @@ private: TIMER_CALLBACK_MEMBER(rcv_clock) { rx_clock_w(!m_rcv_clock_state); } TIMER_CALLBACK_MEMBER(tra_clock) { tx_clock_w(!m_tra_clock_state); } - u8 m_serial_parity_table[256]; - // Data frame // number of start bits int m_df_start_bit_count; @@ -141,8 +137,10 @@ private: u8 m_df_word_length; // parity state u8 m_df_parity; - // number of stop bits + // number of TX stop bits u8 m_df_stop_bit_count; + // min. number of RX stop bits + u8 m_df_min_rx_stop_bit_count; // Receive register /* data */ diff --git a/src/emu/disound.cpp b/src/emu/disound.cpp index 83d2c2bf084..57fd6a9c030 100644 --- a/src/emu/disound.cpp +++ b/src/emu/disound.cpp @@ -23,9 +23,11 @@ device_sound_interface::device_sound_interface(const machine_config &mconfig, device_t &device) : device_interface(device, "sound"), - m_outputs(0), - m_auto_allocated_inputs(0), - m_specified_inputs_mask(0) + m_sound_requested_inputs_mask(0), + m_sound_requested_outputs_mask(0), + m_sound_requested_inputs(0), + m_sound_requested_outputs(0), + m_sound_hook(false) { } @@ -43,25 +45,20 @@ device_sound_interface::~device_sound_interface() // add_route - send sound output to a consumer //------------------------------------------------- -device_sound_interface &device_sound_interface::add_route(u32 output, const char *target, double gain, u32 input, u32 mixoutput) +device_sound_interface &device_sound_interface::add_route(u32 output, const char *target, double gain, u32 channel) { - return add_route(output, device().mconfig().current_device(), target, gain, input, mixoutput); + return add_route(output, device().mconfig().current_device(), target, gain, channel); } -device_sound_interface &device_sound_interface::add_route(u32 output, device_sound_interface &target, double gain, u32 input, u32 mixoutput) +device_sound_interface &device_sound_interface::add_route(u32 output, device_sound_interface &target, double gain, u32 channel) { - return add_route(output, target.device(), DEVICE_SELF, gain, input, mixoutput); + return add_route(output, target.device(), DEVICE_SELF, gain, channel); } -device_sound_interface &device_sound_interface::add_route(u32 output, speaker_device &target, double gain, u32 input, u32 mixoutput) -{ - return add_route(output, target, DEVICE_SELF, gain, input, mixoutput); -} - -device_sound_interface &device_sound_interface::add_route(u32 output, device_t &base, const char *target, double gain, u32 input, u32 mixoutput) +device_sound_interface &device_sound_interface::add_route(u32 output, device_t &base, const char *target, double gain, u32 channel) { assert(!device().started()); - m_route_list.emplace_back(sound_route{ output, input, mixoutput, float(gain), base, target }); + m_route_list.emplace_back(sound_route{ output, channel, float(gain), base, target, nullptr }); return *this; } @@ -71,45 +68,41 @@ device_sound_interface &device_sound_interface::add_route(u32 output, device_t & // associated with this device //------------------------------------------------- -sound_stream *device_sound_interface::stream_alloc(int inputs, int outputs, int sample_rate) -{ - return device().machine().sound().stream_alloc(*this, inputs, outputs, sample_rate, stream_update_delegate(&device_sound_interface::sound_stream_update, this), STREAM_DEFAULT_FLAGS); -} - sound_stream *device_sound_interface::stream_alloc(int inputs, int outputs, int sample_rate, sound_stream_flags flags) { - return device().machine().sound().stream_alloc(*this, inputs, outputs, sample_rate, stream_update_delegate(&device_sound_interface::sound_stream_update, this), flags); + sound_stream *stream = device().machine().sound().stream_alloc(*this, inputs, outputs, sample_rate, stream_update_delegate(&device_sound_interface::sound_stream_update, this), flags); + m_sound_streams.push_back(stream); + return stream; } + //------------------------------------------------- // inputs - return the total number of inputs -// for the given device +// forthe given device //------------------------------------------------- int device_sound_interface::inputs() const { // scan the list counting streams we own and summing their inputs int inputs = 0; - for (auto &stream : device().machine().sound().streams()) - if (&stream->device() == &device()) - inputs += stream->input_count(); + for(sound_stream *stream : m_sound_streams) + inputs += stream->input_count(); return inputs; } //------------------------------------------------- // outputs - return the total number of outputs -// for the given device +// forthe given device //------------------------------------------------- int device_sound_interface::outputs() const { // scan the list counting streams we own and summing their outputs int outputs = 0; - for (auto &stream : device().machine().sound().streams()) - if (&stream->device() == &device()) - outputs += stream->output_count(); + for(auto *stream : m_sound_streams) + outputs += stream->output_count(); return outputs; } @@ -120,24 +113,19 @@ int device_sound_interface::outputs() const // on that stream //------------------------------------------------- -sound_stream *device_sound_interface::input_to_stream_input(int inputnum, int &stream_inputnum) const +std::pair<sound_stream *, int> device_sound_interface::input_to_stream_input(int inputnum) const { assert(inputnum >= 0); + int orig_inputnum = inputnum; - // scan the list looking for streams owned by this device - for (auto &stream : device().machine().sound().streams()) - if (&stream->device() == &device()) - { - if (inputnum < stream->input_count()) - { - stream_inputnum = inputnum; - return stream.get(); - } - inputnum -= stream->input_count(); - } + // scan the list looking forstreams owned by this device + for(auto *stream : m_sound_streams) { + if(inputnum < stream->input_count()) + return std::make_pair(stream, inputnum); + inputnum -= stream->input_count(); + } - // not found - return nullptr; + fatalerror("Requested input %d on sound device %s which only has %d.", orig_inputnum, device().tag(), inputs()); } @@ -147,24 +135,19 @@ sound_stream *device_sound_interface::input_to_stream_input(int inputnum, int &s // on that stream //------------------------------------------------- -sound_stream *device_sound_interface::output_to_stream_output(int outputnum, int &stream_outputnum) const +std::pair<sound_stream *, int> device_sound_interface::output_to_stream_output(int outputnum) const { assert(outputnum >= 0); + int orig_outputnum = outputnum; - // scan the list looking for streams owned by this device - for (auto &stream : device().machine().sound().streams()) - if (&stream->device() == &device()) - { - if (outputnum < stream->output_count()) - { - stream_outputnum = outputnum; - return stream.get(); - } - outputnum -= stream->output_count(); - } + // scan the list looking forstreams owned by this device + for(auto *stream : m_sound_streams) { + if(outputnum < stream->output_count()) + return std::make_pair(stream, outputnum); + outputnum -= stream->output_count(); + } - // not found - return nullptr; + fatalerror("Requested output %d on sound device %s which only has %d.", orig_outputnum, device().tag(), outputs()); } @@ -175,9 +158,8 @@ sound_stream *device_sound_interface::output_to_stream_output(int outputnum, int float device_sound_interface::input_gain(int inputnum) const { - int stream_inputnum; - sound_stream *stream = input_to_stream_input(inputnum, stream_inputnum); - return (stream != nullptr) ? stream->input(stream_inputnum).gain() : 0.0f; + auto [stream, input] = input_to_stream_input(inputnum); + return stream->input_gain(input); } @@ -188,9 +170,32 @@ float device_sound_interface::input_gain(int inputnum) const float device_sound_interface::output_gain(int outputnum) const { - int stream_outputnum; - sound_stream *stream = output_to_stream_output(outputnum, stream_outputnum); - return (stream != nullptr) ? stream->output(stream_outputnum).gain() : 0.0f; + auto [stream, output] = output_to_stream_output(outputnum); + return stream->output_gain(output); +} + + +//------------------------------------------------- +// user_output_gain - return the user gain for the device +//------------------------------------------------- + +float device_sound_interface::user_output_gain() const +{ + if(!outputs()) + fatalerror("Requested user output gain on sound device %s which has no outputs.", device().tag()); + return m_sound_streams.front()->user_output_gain(); +} + + +//------------------------------------------------- +// user_output_gain - return the user gain on the given +// output index of the device +//------------------------------------------------- + +float device_sound_interface::user_output_gain(int outputnum) const +{ + auto [stream, output] = output_to_stream_output(outputnum); + return stream->user_output_gain(output); } @@ -201,10 +206,8 @@ float device_sound_interface::output_gain(int outputnum) const void device_sound_interface::set_input_gain(int inputnum, float gain) { - int stream_inputnum; - sound_stream *stream = input_to_stream_input(inputnum, stream_inputnum); - if (stream != nullptr) - stream->input(stream_inputnum).set_gain(gain); + auto [stream, input] = input_to_stream_input(inputnum); + stream->set_input_gain(input, gain); } @@ -216,45 +219,66 @@ void device_sound_interface::set_input_gain(int inputnum, float gain) void device_sound_interface::set_output_gain(int outputnum, float gain) { // handle ALL_OUTPUTS as a special case - if (outputnum == ALL_OUTPUTS) + if(outputnum == ALL_OUTPUTS) { - for (auto &stream : device().machine().sound().streams()) - if (&stream->device() == &device()) - for (int num = 0; num < stream->output_count(); num++) - stream->output(num).set_gain(gain); + if(!outputs()) + fatalerror("Requested setting output gain on sound device %s which has no outputs.", device().tag()); + for(auto *stream : m_sound_streams) + for(int num = 0; num < stream->output_count(); num++) + stream->set_output_gain(num, gain); } // look up the stream and stream output index else { - int stream_outputnum; - sound_stream *stream = output_to_stream_output(outputnum, stream_outputnum); - if (stream != nullptr) - stream->output(stream_outputnum).set_gain(gain); + auto [stream, output] = output_to_stream_output(outputnum); + stream->set_output_gain(output, gain); } } +//------------------------------------------------- +// user_set_output_gain - set the user gain on the device +//------------------------------------------------- + +void device_sound_interface::set_user_output_gain(float gain) +{ + if(!outputs()) + fatalerror("Requested setting user output gain on sound device %s which has no outputs.", device().tag()); + for(auto *stream : m_sound_streams) + stream->set_user_output_gain(gain); +} + + + +//------------------------------------------------- +// set_user_output_gain - set the user gain on the given +// output index of the device +//------------------------------------------------- + +void device_sound_interface::set_user_output_gain(int outputnum, float gain) +{ + auto [stream, output] = output_to_stream_output(outputnum); + stream->set_user_output_gain(output, gain); +} + //------------------------------------------------- -// inputnum_from_device - return the input number -// that is connected to the given device's output +// set_route_gain - set the gain on a route //------------------------------------------------- -int device_sound_interface::inputnum_from_device(device_t &source_device, int outputnum) const +void device_sound_interface::set_route_gain(int source_channel, device_sound_interface *target, int target_channel, float gain) { - int overall = 0; - for (auto &stream : device().machine().sound().streams()) - if (&stream->device() == &device()) - for (int inputnum = 0; inputnum < stream->input_count(); inputnum++, overall++) - { - auto &input = stream->input(inputnum); - if (input.valid() && &input.source().stream().device() == &source_device && input.source().index() == outputnum) - return overall; - } - return -1; + auto [sstream, schan] = output_to_stream_output(source_channel); + auto [tstream, tchan] = target->input_to_stream_input(target_channel); + tstream->update(); + if(tstream->set_route_gain(sstream, schan, tchan, gain)) + return; + + fatalerror("Trying to change the gain on a non-existant route between %s channel %d and %s channel %d\n", device().tag(), source_channel, target->device().tag(), target_channel); } + //------------------------------------------------- // interface_validity_check - validation for a // device after the configuration has been @@ -264,16 +288,16 @@ int device_sound_interface::inputnum_from_device(device_t &source_device, int ou void device_sound_interface::interface_validity_check(validity_checker &valid) const { // loop over all the routes - for (sound_route const &route : routes()) + for(sound_route const &route : routes()) { // find a device with the requested tag device_t const *const target = route.m_base.get().subdevice(route.m_target); - if (!target) + if(!target) osd_printf_error("Attempting to route sound to non-existent device '%s'\n", route.m_base.get().subtag(route.m_target)); - // if it's not a speaker or a sound device, error + // ifit's not a speaker or a sound device, error device_sound_interface const *sound; - if (target && (target->type() != SPEAKER) && !target->interface(sound)) + if(target && !target->interface(sound)) osd_printf_error("Attempting to route sound to a non-sound device '%s' (%s)\n", target->tag(), target->name()); } } @@ -284,240 +308,68 @@ void device_sound_interface::interface_validity_check(validity_checker &valid) c // devices are started //------------------------------------------------- -void device_sound_interface::interface_pre_start() +void device_sound_interface::sound_before_devices_init() { - // scan all the sound devices - sound_interface_enumerator iter(device().machine().root_device()); - for (device_sound_interface const &sound : iter) - { - // scan each route on the device - for (sound_route const &route : sound.routes()) - { - device_t *const target_device = route.m_base.get().subdevice(route.m_target); - if (target_device == &device()) - { - // see if we are the target of this route; if we are, make sure the source device is started - if (!sound.device().started()) - throw device_missing_dependencies(); - if (route.m_input != AUTO_ALLOC_INPUT) - m_specified_inputs_mask |= 1 << route.m_input; - } - } - } - - // now iterate through devices again and assign any auto-allocated inputs - m_auto_allocated_inputs = 0; - for (device_sound_interface &sound : iter) - { - // scan each route on the device - for (sound_route &route : sound.routes()) - { - // see if we are the target of this route - device_t *const target_device = route.m_base.get().subdevice(route.m_target); - if (target_device == &device() && route.m_input == AUTO_ALLOC_INPUT) - { - route.m_input = m_auto_allocated_inputs; - m_auto_allocated_inputs += (route.m_output == ALL_OUTPUTS) ? sound.outputs() : 1; - } + for(sound_route &route : routes()) { + device_t *dev = route.m_base.get().subdevice(route.m_target); + dev->interface(route.m_interface); + if(route.m_output != ALL_OUTPUTS && m_sound_requested_outputs <= route.m_output) { + m_sound_requested_outputs_mask |= u64(1) << route.m_output; + m_sound_requested_outputs = route.m_output + 1; } + route.m_interface->sound_request_input(route.m_input); } } - -//------------------------------------------------- -// interface_post_start - verify that state was -// properly set up -//------------------------------------------------- - -void device_sound_interface::interface_post_start() +void device_sound_interface::sound_after_devices_init() { - // iterate over all the sound devices - for (device_sound_interface &sound : sound_interface_enumerator(device().machine().root_device())) - { - // scan each route on the device - for (sound_route const &route : sound.routes()) - { - // if we are the target of this route, hook it up - device_t *const target_device = route.m_base.get().subdevice(route.m_target); - if (target_device == &device()) - { - // iterate over all outputs, matching any that apply - int inputnum = route.m_input; - int const numoutputs = sound.outputs(); - for (int outputnum = 0; outputnum < numoutputs; outputnum++) - if (route.m_output == outputnum || route.m_output == ALL_OUTPUTS) - { - // find the output stream to connect from - int streamoutputnum; - sound_stream *const outputstream = sound.output_to_stream_output(outputnum, streamoutputnum); - if (!outputstream) - fatalerror("Sound device '%s' specifies route for nonexistent output #%d\n", sound.device().tag(), outputnum); - - // find the input stream to connect to - int streaminputnum; - sound_stream *const inputstream = input_to_stream_input(inputnum++, streaminputnum); - if (!inputstream) - fatalerror("Sound device '%s' targeted output #%d to nonexistent device '%s' input %d\n", sound.device().tag(), outputnum, device().tag(), inputnum - 1); - - // set the input - inputstream->set_input(streaminputnum, outputstream, streamoutputnum, route.m_gain); - } - } + for(sound_route &route : routes()) { + auto [si, ii] = route.m_interface->input_to_stream_input(route.m_input); + if(!si) + fatalerror("Requesting sound route to device %s input %d which doesn't exist\n", route.m_interface->device().tag(), route.m_input); + if(route.m_output != ALL_OUTPUTS) { + auto [so, io] = output_to_stream_output(route.m_output); + if(!so) + fatalerror("Requesting sound route from device %s output %d which doesn't exist\n", device().tag(), route.m_output); + si->add_bw_route(so, io, ii, route.m_gain); + so->add_fw_route(si, ii, io); + + } else { + for(sound_stream *so : m_sound_streams) + for(int io = 0; io != so->output_count(); io ++) { + si->add_bw_route(so, io, ii, route.m_gain); + so->add_fw_route(si, ii, io); + } } } } - -//------------------------------------------------- -// interface_pre_reset - called prior to -// resetting the device -//------------------------------------------------- - -void device_sound_interface::interface_pre_reset() -{ - // update all streams on this device prior to reset - for (auto &stream : device().machine().sound().streams()) - if (&stream->device() == &device()) - stream->update(); -} - - -//------------------------------------------------- -// sound_stream_update - default implementation -// that should be overridden -//------------------------------------------------- - -void device_sound_interface::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) +void device_sound_interface::sound_request_input(u32 input) { - throw emu_fatalerror("sound_stream_update called but not overridden by owning class"); + m_sound_requested_inputs_mask |= u64(1) << input; + if(m_sound_requested_inputs <= input) + m_sound_requested_inputs = input + 1; } - - -//************************************************************************** -// SIMPLE DERIVED MIXER INTERFACE -//************************************************************************** - -//------------------------------------------------- -// device_mixer_interface - constructor -//------------------------------------------------- - -device_mixer_interface::device_mixer_interface(const machine_config &mconfig, device_t &device, int outputs) - : device_sound_interface(mconfig, device), - m_outputs(outputs), - m_mixer_stream(nullptr) +device_mixer_interface::device_mixer_interface(const machine_config &mconfig, device_t &device) : + device_sound_interface(mconfig, device) { } - -//------------------------------------------------- -// ~device_mixer_interface - destructor -//------------------------------------------------- - device_mixer_interface::~device_mixer_interface() { } - -//------------------------------------------------- -// interface_pre_start - perform startup prior -// to the device startup -//------------------------------------------------- - void device_mixer_interface::interface_pre_start() { - // call our parent - device_sound_interface::interface_pre_start(); - - // no inputs? that's weird - if (m_auto_allocated_inputs == 0) - { - device().logerror("Warning: mixer \"%s\" has no inputs\n", device().tag()); - return; - } - - // generate the output map - m_outputmap.resize(m_auto_allocated_inputs); - - // iterate through all routes that point to us and note their mixer output - for (device_sound_interface const &sound : sound_interface_enumerator(device().machine().root_device())) - { - for (sound_route const &route : sound.routes()) - { - // see if we are the target of this route - device_t *const target_device = route.m_base.get().subdevice(route.m_target); - if (target_device == &device() && route.m_input < m_auto_allocated_inputs) - { - int const count = (route.m_output == ALL_OUTPUTS) ? sound.outputs() : 1; - for (int output = 0; output < count; output++) - m_outputmap[route.m_input + output] = route.m_mixoutput; - } - } - } - - // keep a small buffer handy for tracking cleared buffers - m_output_clear.resize(m_outputs); - - // allocate the mixer stream - m_mixer_stream = stream_alloc(m_auto_allocated_inputs, m_outputs, device().machine().sample_rate(), STREAM_DEFAULT_FLAGS); -} - - -//------------------------------------------------- -// interface_post_load - after we load a save -// state be sure to update the mixer stream's -// output sample rate -//------------------------------------------------- - -void device_mixer_interface::interface_post_load() -{ - // mixer stream could be null if no inputs were specified - if (m_mixer_stream != nullptr) - m_mixer_stream->set_sample_rate(device().machine().sample_rate()); - - // call our parent - device_sound_interface::interface_post_load(); + u32 ni = get_sound_requested_inputs(); + u32 no = get_sound_requested_outputs(); + u32 nc = ni > no ? ni : no; + for(u32 i = 0; i != nc; i++) + stream_alloc(1, 1, SAMPLE_RATE_ADAPTIVE); } - -//------------------------------------------------- -// sound_stream_update - mix all inputs to one -// output -//------------------------------------------------- - -void device_mixer_interface::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) +void device_mixer_interface::sound_stream_update(sound_stream &stream) { - // special case: single input, single output, same rate - if (inputs.size() == 1 && outputs.size() == 1 && inputs[0].sample_rate() == outputs[0].sample_rate()) - { - outputs[0] = inputs[0]; - return; - } - - // reset the clear flags - std::fill(std::begin(m_output_clear), std::end(m_output_clear), false); - - // loop over inputs - for (int inputnum = 0; inputnum < m_auto_allocated_inputs; inputnum++) - { - // skip if the gain is 0 - auto &input = inputs[inputnum]; - if (input.gain() == 0) - continue; - - // either store or accumulate - int outputnum = m_outputmap[inputnum]; - auto &output = outputs[outputnum]; - if (!m_output_clear[outputnum]) - output.copy(input); - else - output.add(input); - - m_output_clear[outputnum] = true; - } - - // clear anything unused - for (int outputnum = 0; outputnum < m_outputs; outputnum++) - if (!m_output_clear[outputnum]) - outputs[outputnum].fill(0); + stream.copy(0, 0); } diff --git a/src/emu/disound.h b/src/emu/disound.h index e48ba290bc6..eefe1197784 100644 --- a/src/emu/disound.h +++ b/src/emu/disound.h @@ -26,7 +26,6 @@ //************************************************************************** constexpr int ALL_OUTPUTS = 65535; // special value indicating all outputs for the current chip -constexpr int AUTO_ALLOC_INPUT = 65535; @@ -34,8 +33,6 @@ constexpr int AUTO_ALLOC_INPUT = 65535; // TYPE DEFINITIONS //************************************************************************** -class read_stream_view; -class write_stream_view; enum sound_stream_flags : u32; @@ -43,73 +40,89 @@ enum sound_stream_flags : u32; class device_sound_interface : public device_interface { + friend class sound_manager; + public: class sound_route { public: u32 m_output; // output index, or ALL_OUTPUTS u32 m_input; // target input index - u32 m_mixoutput; // target mixer output float m_gain; // gain std::reference_wrapper<device_t> m_base; // target search base std::string m_target; // target tag + device_sound_interface *m_interface; // target device interface }; // construction/destruction device_sound_interface(const machine_config &mconfig, device_t &device); virtual ~device_sound_interface(); - virtual bool issound() { return true; } /// HACK: allow devices to hide from the ui - // configuration access std::vector<sound_route> const &routes() const { return m_route_list; } // configuration helpers template <typename T, bool R> - device_sound_interface &add_route(u32 output, const device_finder<T, R> &target, double gain, u32 input = AUTO_ALLOC_INPUT, u32 mixoutput = 0) + device_sound_interface &add_route(u32 output, const device_finder<T, R> &target, double gain, u32 channel = 0) { const std::pair<device_t &, const char *> ft(target.finder_target()); - return add_route(output, ft.first, ft.second, gain, input, mixoutput); + return add_route(output, ft.first, ft.second, gain, channel); } - device_sound_interface &add_route(u32 output, const char *target, double gain, u32 input = AUTO_ALLOC_INPUT, u32 mixoutput = 0); - device_sound_interface &add_route(u32 output, device_sound_interface &target, double gain, u32 input = AUTO_ALLOC_INPUT, u32 mixoutput = 0); - device_sound_interface &add_route(u32 output, speaker_device &target, double gain, u32 input = AUTO_ALLOC_INPUT, u32 mixoutput = 0); + device_sound_interface &add_route(u32 output, const char *target, double gain, u32 channel = 0); + device_sound_interface &add_route(u32 output, device_sound_interface &target, double gain, u32 channel = 0); device_sound_interface &reset_routes() { m_route_list.clear(); return *this; } // sound stream update overrides - virtual void sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs); + virtual void sound_stream_update(sound_stream &stream) = 0; // stream creation - sound_stream *stream_alloc(int inputs, int outputs, int sample_rate); - sound_stream *stream_alloc(int inputs, int outputs, int sample_rate, sound_stream_flags flags); + sound_stream *stream_alloc(int inputs, int outputs, int sample_rate, sound_stream_flags flags = sound_stream_flags(0)); // helpers int inputs() const; int outputs() const; - sound_stream *input_to_stream_input(int inputnum, int &stream_inputnum) const; - sound_stream *output_to_stream_output(int outputnum, int &stream_outputnum) const; + std::pair<sound_stream *, int> input_to_stream_input(int inputnum) const; + std::pair<sound_stream *, int> output_to_stream_output(int outputnum) const; float input_gain(int inputnum) const; float output_gain(int outputnum) const; + float user_output_gain() const; + float user_output_gain(int outputnum) const; void set_input_gain(int inputnum, float gain); void set_output_gain(int outputnum, float gain); - int inputnum_from_device(device_t &device, int outputnum = 0) const; + void set_user_output_gain(float gain); + void set_user_output_gain(int outputnum, float gain); + void set_route_gain(int source_channel, device_sound_interface *target, int target_channel, float gain); + void set_sound_hook(bool enable) { m_sound_hook = enable; } + bool get_sound_hook() const { return m_sound_hook; } + protected: // configuration access std::vector<sound_route> &routes() { return m_route_list; } - device_sound_interface &add_route(u32 output, device_t &base, const char *tag, double gain, u32 input, u32 mixoutput); + device_sound_interface &add_route(u32 output, device_t &base, const char *tag, double gain, u32 channel); // optional operation overrides virtual void interface_validity_check(validity_checker &valid) const override; - virtual void interface_pre_start() override; - virtual void interface_post_start() override; - virtual void interface_pre_reset() override; + + u32 get_sound_requested_inputs() const { return m_sound_requested_inputs; } + u32 get_sound_requested_outputs() const { return m_sound_requested_outputs; } + u64 get_sound_requested_inputs_mask() const { return m_sound_requested_inputs_mask; } + u64 get_sound_requested_outputs_mask() const { return m_sound_requested_outputs_mask; } + +private: + void sound_request_input(u32 input); // internal state std::vector<sound_route> m_route_list; // list of sound routes - int m_outputs; // number of outputs from this instance - int m_auto_allocated_inputs; // number of auto-allocated inputs targeting us - u32 m_specified_inputs_mask; // mask of inputs explicitly specified (not counting auto-allocated) + std::vector<sound_stream *> m_sound_streams; + u64 m_sound_requested_inputs_mask; + u64 m_sound_requested_outputs_mask; + u32 m_sound_requested_inputs; + u32 m_sound_requested_outputs; + bool m_sound_hook; + + void sound_before_devices_init(); + void sound_after_devices_init(); }; // iterator @@ -123,22 +136,15 @@ class device_mixer_interface : public device_sound_interface { public: // construction/destruction - device_mixer_interface(const machine_config &mconfig, device_t &device, int outputs = 1); + device_mixer_interface(const machine_config &mconfig, device_t &device); virtual ~device_mixer_interface(); protected: // optional operation overrides virtual void interface_pre_start() override; - virtual void interface_post_load() override; // sound interface overrides - virtual void sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) override; - - // internal state - u8 m_outputs; // number of outputs - std::vector<u8> m_outputmap; // map of inputs to outputs - std::vector<bool> m_output_clear; // flag for tracking cleared buffers - sound_stream *m_mixer_stream; // mixing stream + virtual void sound_stream_update(sound_stream &stream) override; }; // iterator diff --git a/src/emu/drawgfx.cpp b/src/emu/drawgfx.cpp index 8a63d71467c..d7648483f21 100644 --- a/src/emu/drawgfx.cpp +++ b/src/emu/drawgfx.cpp @@ -66,63 +66,61 @@ gfxdecode_device::gfxdecode_device(const machine_config &mconfig, const char *ta - /*************************************************************************** GRAPHICS ELEMENTS ***************************************************************************/ - //------------------------------------------------- // gfx_element - constructor //------------------------------------------------- -gfx_element::gfx_element(device_palette_interface *palette, u8 *base, u16 width, u16 height, u32 rowbytes, u32 total_colors, u32 color_base, u32 color_granularity) - : m_palette(palette), - m_width(width), - m_height(height), - m_startx(0), - m_starty(0), - m_origwidth(width), - m_origheight(height), - m_total_elements(1), - m_color_base(color_base), - m_color_depth(color_granularity), - m_color_granularity(color_granularity), - m_total_colors((total_colors - color_base) / color_granularity), - m_line_modulo(rowbytes), - m_char_modulo(0), - m_srcdata(base), - m_dirtyseq(1), - m_gfxdata(base), - m_layout_is_raw(true), - m_layout_planes(0), - m_layout_xormask(0), - m_layout_charincrement(0) -{ -} - -gfx_element::gfx_element(device_palette_interface *palette, const gfx_layout &gl, const u8 *srcdata, u32 xormask, u32 total_colors, u32 color_base) - : m_palette(palette), - m_width(0), - m_height(0), - m_startx(0), - m_starty(0), - m_origwidth(0), - m_origheight(0), - m_total_elements(0), - m_color_base(color_base), - m_color_depth(0), - m_color_granularity(0), - m_total_colors(total_colors), - m_line_modulo(0), - m_char_modulo(0), - m_srcdata(nullptr), - m_dirtyseq(1), - m_gfxdata(nullptr), - m_layout_is_raw(false), - m_layout_planes(0), - m_layout_xormask(xormask), - m_layout_charincrement(0) +gfx_element::gfx_element(device_palette_interface *palette, u8 *base, u16 width, u16 height, u32 rowbytes, u32 total_colors, u32 color_base, u32 color_granularity) : + m_palette(palette), + m_width(width), + m_height(height), + m_startx(0), + m_starty(0), + m_origwidth(width), + m_origheight(height), + m_total_elements(1), + m_color_base(color_base), + m_color_depth(color_granularity), + m_color_granularity(color_granularity), + m_total_colors((total_colors - color_base) / color_granularity), + m_line_modulo(rowbytes), + m_char_modulo(0), + m_srcdata(base), + m_dirtyseq(1), + m_gfxdata(base), + m_layout_is_raw(true), + m_layout_planes(0), + m_layout_xormask(0), + m_layout_charincrement(0) +{ +} + +gfx_element::gfx_element(device_palette_interface *palette, const gfx_layout &gl, const u8 *srcdata, u32 xormask, u32 total_colors, u32 color_base) : + m_palette(palette), + m_width(0), + m_height(0), + m_startx(0), + m_starty(0), + m_origwidth(0), + m_origheight(0), + m_total_elements(0), + m_color_base(color_base), + m_color_depth(0), + m_color_granularity(0), + m_total_colors(total_colors), + m_line_modulo(0), + m_char_modulo(0), + m_srcdata(nullptr), + m_dirtyseq(1), + m_gfxdata(nullptr), + m_layout_is_raw(false), + m_layout_planes(0), + m_layout_xormask(xormask), + m_layout_charincrement(0) { // set the layout set_layout(gl, srcdata); diff --git a/src/emu/drawgfx.h b/src/emu/drawgfx.h index 4b5be8a4fa3..d28024bb442 100644 --- a/src/emu/drawgfx.h +++ b/src/emu/drawgfx.h @@ -538,8 +538,8 @@ class gfxdecode_device : public device_t, public device_gfx_interface public: // construction/destruction template <typename T> - gfxdecode_device(const machine_config &mconfig, const char *tag, device_t *owner, T &&palette_tag, const gfx_decode_entry *gfxinfo) - : gfxdecode_device(mconfig, tag, owner, 0) + gfxdecode_device(const machine_config &mconfig, const char *tag, device_t *owner, T &&palette_tag, const gfx_decode_entry *gfxinfo) : + gfxdecode_device(mconfig, tag, owner, 0) { set_palette(std::forward<T>(palette_tag)); set_info(gfxinfo); @@ -547,7 +547,7 @@ public: gfxdecode_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock = 0); protected: - virtual void device_start() override {} + virtual void device_start() override { } }; #endif // MAME_EMU_DRAWGFX_H diff --git a/src/emu/drivenum.cpp b/src/emu/drivenum.cpp index 3db8a3334d5..84158749189 100644 --- a/src/emu/drivenum.cpp +++ b/src/emu/drivenum.cpp @@ -260,7 +260,7 @@ void driver_enumerator::find_approximate_matches(std::string const &string, std: { // allocate memory to track the penalty value std::vector<std::pair<double, int> > penalty; - penalty.reserve(count); + penalty.reserve(count + 1); std::u32string const search(ustr_from_utf8(normalize_unicode(string, unicode_normalization_form::D, true))); std::string composed; std::u32string candidate; @@ -303,9 +303,9 @@ void driver_enumerator::find_approximate_matches(std::string const &string, std: auto const it(std::upper_bound(penalty.begin(), penalty.end(), std::make_pair(curpenalty, index))); if (penalty.end() != it) { - if (penalty.size() >= count) - penalty.resize(count - 1); penalty.emplace(it, curpenalty, index); + if (penalty.size() > count) + penalty.pop_back(); } else if (penalty.size() < count) { diff --git a/src/emu/driver.h b/src/emu/driver.h index 75e035d46c9..ed7172d25ef 100644 --- a/src/emu/driver.h +++ b/src/emu/driver.h @@ -156,10 +156,10 @@ protected: virtual void video_reset(); // device-level overrides - virtual const tiny_rom_entry *device_rom_region() const override; - virtual void device_add_mconfig(machine_config &config) override; - virtual ioport_constructor device_input_ports() const override; - virtual void device_start() override; + virtual const tiny_rom_entry *device_rom_region() const override ATTR_COLD; + virtual void device_add_mconfig(machine_config &config) override ATTR_COLD; + virtual ioport_constructor device_input_ports() const override ATTR_COLD; + virtual void device_start() override ATTR_COLD; virtual void device_reset_after_children() override; // generic video diff --git a/src/emu/drivers/testcpu.cpp b/src/emu/drivers/testcpu.cpp index 95e3d5a1ec2..c6df4ff74af 100644 --- a/src/emu/drivers/testcpu.cpp +++ b/src/emu/drivers/testcpu.cpp @@ -12,6 +12,10 @@ #include "emu.h" #include "cpu/powerpc/ppc.h" +#include <sstream> + + +namespace { //************************************************************************** // CONSTANTS @@ -29,18 +33,37 @@ class testcpu_state : public driver_device { public: // constructor - testcpu_state(const machine_config &mconfig, device_type type, const char *tag) - : driver_device(mconfig, type, tag), - m_cpu(*this, "maincpu"), - m_ram(*this, "ram"), - m_space(nullptr) + testcpu_state(const machine_config &mconfig, device_type type, const char *tag) : + driver_device(mconfig, type, tag), + m_cpu(*this, "maincpu"), + m_ram(*this, "ram"), + m_space(nullptr) { } + void testcpu(machine_config &config); + +private: + class disasm_data_buffer : public util::disasm_interface::data_buffer + { + public: + disasm_data_buffer(address_space &space) : m_space(space) + { + } + + virtual u8 r8(offs_t pc) const override { return m_space.read_byte(pc); } + virtual u16 r16(offs_t pc) const override { return m_space.read_word(pc); } + virtual u32 r32(offs_t pc) const override { return m_space.read_dword(pc); } + virtual u64 r64(offs_t pc) const override { return m_space.read_qword(pc); } + + private: + address_space &m_space; + }; + // timer callback; used to wrest control of the system TIMER_CALLBACK_MEMBER(timer_tick) { - static const u32 sample_instructions[] = + static constexpr u32 sample_instructions[] = { 0x3d40f900, // li r10,0xf9000000 0x394af000, // addi r10,r10,-0x1000 @@ -52,7 +75,7 @@ public: }; // iterate over instructions - for (auto & sample_instruction : sample_instructions) + for (auto &sample_instruction : sample_instructions) { // write the instruction to execute, followed by a BLR which will terminate the // basic block in the DRC @@ -74,8 +97,8 @@ public: } // output initial state - printf("==================================================\n"); - printf("Initial state:\n"); + osd_printf_info("==================================================\n"); + osd_printf_info("Initial state:\n"); dump_state(true); // execute one instruction @@ -83,7 +106,7 @@ public: m_cpu->run(); // dump the final register state - printf("Final state:\n"); + osd_printf_info("Final state:\n"); dump_state(false); } @@ -101,49 +124,43 @@ public: m_cpu->ppcdrc_set_options(PPCDRC_COMPATIBLE_OPTIONS); // set a timer to go off right away - timer_alloc(FUNC(timer_tick), this)->adjust(attotime::zero); + timer_alloc(FUNC(testcpu_state::timer_tick), this)->adjust(attotime::zero); } // dump the current CPU state void dump_state(bool disassemble) { - char buffer[256]; - u8 instruction[32]; - buffer[0] = 0; + std::ostringstream buffer; int bytes = 0; if (disassemble) { - // fill in an array of bytes in the CPU's natural order - int maxbytes = m_cpu->max_opcode_bytes(); - for (int bytenum = 0; bytenum < maxbytes; bytenum++) - instruction[bytenum] = m_space->read_byte(RAM_BASE + bytenum); - // disassemble the current instruction - bytes = m_cpu->disassemble(buffer, RAM_BASE, instruction, instruction) & DASMFLAG_LENGTHMASK; + disasm_data_buffer databuf(*m_space); + bytes = m_cpu->get_disassembler().disassemble(buffer, RAM_BASE, databuf, databuf) & util::disasm_interface::LENGTHMASK; } // output the registers - printf("PC : %08X", u32(m_cpu->state_int(PPC_PC))); - if (disassemble && bytes > 0) + osd_printf_info("PC : %08X", u32(m_cpu->state_int(PPC_PC))); + if (bytes > 0) { - printf(" => "); + osd_printf_info(" => "); for (int bytenum = 0; bytenum < bytes; bytenum++) - printf("%02X", instruction[bytenum]); - printf(" %s", buffer); + osd_printf_info("%02X", m_space->read_byte(RAM_BASE + bytenum)); + osd_printf_info(" %s", buffer.str()); } - printf("\n"); + osd_printf_info("\n"); for (int regnum = 0; regnum < 32; regnum++) { - printf("R%-2d: %08X ", regnum, u32(m_cpu->state_int(PPC_R0 + regnum))); - if (regnum % 4 == 3) printf("\n"); + osd_printf_info("R%-2d: %08X ", regnum, u32(m_cpu->state_int(PPC_R0 + regnum))); + if (regnum % 4 == 3) osd_printf_info("\n"); } - printf("CR : %08X LR : %08X CTR: %08X XER: %08X\n", + osd_printf_info("CR : %08X LR : %08X CTR: %08X XER: %08X\n", u32(m_cpu->state_int(PPC_CR)), u32(m_cpu->state_int(PPC_LR)), u32(m_cpu->state_int(PPC_CTR)), u32(m_cpu->state_int(PPC_XER))); for (int regnum = 0; regnum < 32; regnum++) { - printf("F%-2d: %10g ", regnum, u2d(m_cpu->state_int(PPC_F0 + regnum))); - if (regnum % 4 == 3) printf("\n"); + osd_printf_info("F%-2d: %10g ", regnum, u2d(m_cpu->state_int(PPC_F0 + regnum))); + if (regnum % 4 == 3) osd_printf_info("\n"); } } @@ -152,20 +169,18 @@ public: { u64 fulloffs = offset; u64 result = fulloffs + (fulloffs << 8) + (fulloffs << 16) + (fulloffs << 24) + (fulloffs << 32); - printf("Read from %08X & %08X%08X = %08X%08X\n", offset * 8, (int)((mem_mask&0xffffffff00000000LL) >> 32) , (int)(mem_mask&0xffffffff), (int)((result&0xffffffff00000000LL) >> 32), (int)(result&0xffffffff)); + osd_printf_info("Read from %08X & %016X = %016X\n", offset * 8, mem_mask, result); return result; } // report writes to anywhere void general_w(offs_t offset, u64 data, u64 mem_mask = ~0) { - printf("Write to %08X & %08X%08X = %08X%08X\n", offset * 8, (int)((mem_mask&0xffffffff00000000LL) >> 32) , (int)(mem_mask&0xffffffff), (int)((data&0xffffffff00000000LL) >> 32), (int)(data&0xffffffff)); + osd_printf_info("Write to %08X & %016X = %016X\n", offset * 8, mem_mask, data); } - void testcpu(machine_config &config); + void ppc_mem(address_map &map) ATTR_COLD; - void ppc_mem(address_map &map); -private: // internal state required_device<ppc603e_device> m_cpu; required_shared_ptr<u64> m_ram; @@ -180,8 +195,8 @@ private: void testcpu_state::ppc_mem(address_map &map) { + map(0x00000000, 0xffffffff).rw(FUNC(testcpu_state::general_r), FUNC(testcpu_state::general_w)); map(RAM_BASE, RAM_BASE+7).ram().share("ram"); - map(0x00000000, 0xffffffff).rw(this, FUNC(testcpu_state::general_r), FUNC(testcpu_state::general_w)); } @@ -193,8 +208,8 @@ void testcpu_state::ppc_mem(address_map &map) void testcpu_state::testcpu(machine_config &config) { // CPUs - PPC603E(config, m_cpu, 66000000); - m_cpu->set_bus_frequency(66000000); // Multiplier 1, Bus = 66MHz, Core = 66MHz + PPC603E(config, m_cpu, 66'000'000); + m_cpu->set_bus_frequency(66'000'000); // Multiplier 1, Bus = 66MHz, Core = 66MHz m_cpu->set_addrmap(AS_PROGRAM, &testcpu_state::ppc_mem); } @@ -208,10 +223,11 @@ ROM_START( testcpu ) ROM_REGION( 0x10, "user1", ROMREGION_ERASEFF ) ROM_END +} // anonymous namespace //************************************************************************** // GAME DRIVERS //************************************************************************** -GAME( 2012, testcpu, 0, testcpu, 0, driver_device, 0, ROT0, "MAME", "CPU Tester", MACHINE_NO_SOUND ) +GAME( 2012, testcpu, 0, testcpu, 0, testcpu_state, empty_init, ROT0, "MAME", "CPU Tester", MACHINE_NO_SOUND_HW ) diff --git a/src/emu/emucore.h b/src/emu/emucore.h index ca54f5208cd..f6552461131 100644 --- a/src/emu/emucore.h +++ b/src/emu/emucore.h @@ -233,7 +233,7 @@ public: emu_fatalerror(util::format_argument_pack<char> const &args); emu_fatalerror(int _exitcode, util::format_argument_pack<char> const &args); - template <typename Format, typename... Params> + template <typename Format, typename... Params, typename = std::enable_if_t<!std::is_base_of_v<emu_fatalerror, std::remove_reference_t<Format> > > > emu_fatalerror(Format &&fmt, Params &&... args) : emu_fatalerror(static_cast<util::format_argument_pack<char> const &>(util::make_format_argument_pack(std::forward<Format>(fmt), std::forward<Params>(args)...))) { diff --git a/src/emu/emufwd.h b/src/emu/emufwd.h index fe6ae26cb2c..cf14dcb8163 100644 --- a/src/emu/emufwd.h +++ b/src/emu/emufwd.h @@ -38,7 +38,6 @@ class output_module; // declared in osdepend.h class osd_font; class osd_interface; -class osd_midi_device; @@ -236,7 +235,9 @@ class sound_manager; class sound_stream; // declared in speaker.h +class sound_io_device; class speaker_device; +class microphone_device; // declared in tilemap.h class tilemap_device; diff --git a/src/emu/emumem.h b/src/emu/emumem.h index 1b9b6363622..39470a45632 100644 --- a/src/emu/emumem.h +++ b/src/emu/emumem.h @@ -71,26 +71,6 @@ using offs_t = u32; // address map constructors are delegates that build up an address_map using address_map_constructor = named_delegate<void (address_map &)>; -// struct with function pointers for accessors; use is generally discouraged unless necessary -struct data_accessors -{ - u8 (*read_byte)(address_space &space, offs_t address); - u16 (*read_word)(address_space &space, offs_t address); - u16 (*read_word_masked)(address_space &space, offs_t address, u16 mask); - u32 (*read_dword)(address_space &space, offs_t address); - u32 (*read_dword_masked)(address_space &space, offs_t address, u32 mask); - u64 (*read_qword)(address_space &space, offs_t address); - u64 (*read_qword_masked)(address_space &space, offs_t address, u64 mask); - - void (*write_byte)(address_space &space, offs_t address, u8 data); - void (*write_word)(address_space &space, offs_t address, u16 data); - void (*write_word_masked)(address_space &space, offs_t address, u16 data, u16 mask); - void (*write_dword)(address_space &space, offs_t address, u32 data); - void (*write_dword_masked)(address_space &space, offs_t address, u32 data, u32 mask); - void (*write_qword)(address_space &space, offs_t address, u64 data); - void (*write_qword_masked)(address_space &space, offs_t address, u64 data, u64 mask); -}; - // a line in the memory structure dump struct memory_entry_context { memory_view *view; @@ -1641,85 +1621,85 @@ template<int Width, int AddrShift, endianness_t Endian, int TargetWidth, bool Al template<int Level, int Width, int AddrShift> emu::detail::handler_entry_size_t<Width> dispatch_read(offs_t mask, offs_t offset, emu::detail::handler_entry_size_t<Width> mem_mask, const handler_entry_read<Width, AddrShift> *const *dispatch) { - static constexpr u32 LowBits = emu::detail::handler_entry_dispatch_level_to_lowbits(Level, Width, AddrShift); - return dispatch[(offset & mask) >> LowBits]->read(offset, mem_mask); + constexpr u32 LOW_BITS = emu::detail::handler_entry_dispatch_level_to_lowbits(Level, Width, AddrShift); + return dispatch[(offset & mask) >> LOW_BITS]->read(offset, mem_mask); } template<int Level, int Width, int AddrShift> void dispatch_write(offs_t mask, offs_t offset, emu::detail::handler_entry_size_t<Width> data, emu::detail::handler_entry_size_t<Width> mem_mask, const handler_entry_write<Width, AddrShift> *const *dispatch) { - static constexpr u32 LowBits = emu::detail::handler_entry_dispatch_level_to_lowbits(Level, Width, AddrShift); - return dispatch[(offset & mask) >> LowBits]->write(offset, data, mem_mask); + constexpr u32 LOW_BITS = emu::detail::handler_entry_dispatch_level_to_lowbits(Level, Width, AddrShift); + return dispatch[(offset & mask) >> LOW_BITS]->write(offset, data, mem_mask); } template<int Level, int Width, int AddrShift> std::pair<emu::detail::handler_entry_size_t<Width>, u16> dispatch_read_flags(offs_t mask, offs_t offset, emu::detail::handler_entry_size_t<Width> mem_mask, const handler_entry_read<Width, AddrShift> *const *dispatch) { - static constexpr u32 LowBits = emu::detail::handler_entry_dispatch_level_to_lowbits(Level, Width, AddrShift); - return dispatch[(offset & mask) >> LowBits]->read_flags(offset, mem_mask); + constexpr u32 LOW_BITS = emu::detail::handler_entry_dispatch_level_to_lowbits(Level, Width, AddrShift); + return dispatch[(offset & mask) >> LOW_BITS]->read_flags(offset, mem_mask); } template<int Level, int Width, int AddrShift> u16 dispatch_write_flags(offs_t mask, offs_t offset, emu::detail::handler_entry_size_t<Width> data, emu::detail::handler_entry_size_t<Width> mem_mask, const handler_entry_write<Width, AddrShift> *const *dispatch) { - static constexpr u32 LowBits = emu::detail::handler_entry_dispatch_level_to_lowbits(Level, Width, AddrShift); - return dispatch[(offset & mask) >> LowBits]->write_flags(offset, data, mem_mask); + constexpr u32 LOW_BITS = emu::detail::handler_entry_dispatch_level_to_lowbits(Level, Width, AddrShift); + return dispatch[(offset & mask) >> LOW_BITS]->write_flags(offset, data, mem_mask); } template<int Level, int Width, int AddrShift> u16 dispatch_lookup_read_flags(offs_t mask, offs_t offset, emu::detail::handler_entry_size_t<Width> mem_mask, const handler_entry_read<Width, AddrShift> *const *dispatch) { - static constexpr u32 LowBits = emu::detail::handler_entry_dispatch_level_to_lowbits(Level, Width, AddrShift); - return dispatch[(offset & mask) >> LowBits]->lookup_flags(offset, mem_mask); + constexpr u32 LOW_BITS = emu::detail::handler_entry_dispatch_level_to_lowbits(Level, Width, AddrShift); + return dispatch[(offset & mask) >> LOW_BITS]->lookup_flags(offset, mem_mask); } template<int Level, int Width, int AddrShift> u16 dispatch_lookup_write_flags(offs_t mask, offs_t offset, emu::detail::handler_entry_size_t<Width> mem_mask, const handler_entry_write<Width, AddrShift> *const *dispatch) { - static constexpr u32 LowBits = emu::detail::handler_entry_dispatch_level_to_lowbits(Level, Width, AddrShift); - return dispatch[(offset & mask) >> LowBits]->lookup_flags(offset, mem_mask); + constexpr u32 LOW_BITS = emu::detail::handler_entry_dispatch_level_to_lowbits(Level, Width, AddrShift); + return dispatch[(offset & mask) >> LOW_BITS]->lookup_flags(offset, mem_mask); } template<int Level, int Width, int AddrShift> emu::detail::handler_entry_size_t<Width> dispatch_read_interruptible(offs_t mask, offs_t offset, emu::detail::handler_entry_size_t<Width> mem_mask, const handler_entry_read<Width, AddrShift> *const *dispatch) { - static constexpr u32 LowBits = emu::detail::handler_entry_dispatch_level_to_lowbits(Level, Width, AddrShift); - return dispatch[(offset & mask) >> LowBits]->read_interruptible(offset, mem_mask); + constexpr u32 LOW_BITS = emu::detail::handler_entry_dispatch_level_to_lowbits(Level, Width, AddrShift); + return dispatch[(offset & mask) >> LOW_BITS]->read_interruptible(offset, mem_mask); } template<int Level, int Width, int AddrShift> void dispatch_write_interruptible(offs_t mask, offs_t offset, emu::detail::handler_entry_size_t<Width> data, emu::detail::handler_entry_size_t<Width> mem_mask, const handler_entry_write<Width, AddrShift> *const *dispatch) { - static constexpr u32 LowBits = emu::detail::handler_entry_dispatch_level_to_lowbits(Level, Width, AddrShift); - return dispatch[(offset & mask) >> LowBits]->write_interruptible(offset, data, mem_mask); + constexpr u32 LOW_BITS = emu::detail::handler_entry_dispatch_level_to_lowbits(Level, Width, AddrShift); + return dispatch[(offset & mask) >> LOW_BITS]->write_interruptible(offset, data, mem_mask); } +namespace emu::detail { + // ======================> memory_access_specific // memory_access_specific does uncached but faster accesses by shortcutting the address_space virtual call -namespace emu::detail { - template<int Level, int Width, int AddrShift, endianness_t Endian> class memory_access_specific { friend class ::address_space; using NativeType = emu::detail::handler_entry_size_t<Width>; static constexpr u32 NATIVE_BYTES = 1 << Width; - static constexpr u32 NATIVE_MASK = Width + AddrShift >= 0 ? (1 << (Width + AddrShift)) - 1 : 0; + static constexpr u32 NATIVE_MASK = (Width + AddrShift >= 0) ? ((1 << (Width + AddrShift)) - 1) : 0; public: // construction/destruction - memory_access_specific() - : m_space(nullptr), - m_addrmask(0), - m_dispatch_read(nullptr), - m_dispatch_write(nullptr) + memory_access_specific() : + m_space(nullptr), + m_addrmask(0), + m_dispatch_read(nullptr), + m_dispatch_write(nullptr) { } - inline address_space &space() const { + address_space &space() const { return *m_space; } @@ -1731,6 +1711,7 @@ public: auto lwopf() { return [this](offs_t offset, NativeType mask) -> u16 { return lookup_write_native_flags(offset, mask); }; } u8 read_byte(offs_t address) { if constexpr(Width == 0) return read_native(address & ~NATIVE_MASK); else return memory_read_generic<Width, AddrShift, Endian, 0, true>(rop(), address, 0xff); } + u8 read_byte(offs_t address, u8 mask) { return memory_read_generic<Width, AddrShift, Endian, 0, true>(rop(), address, mask); } u16 read_word(offs_t address) { if constexpr(Width == 1) return read_native(address & ~NATIVE_MASK); else return memory_read_generic<Width, AddrShift, Endian, 1, true>(rop(), address, 0xffff); } u16 read_word(offs_t address, u16 mask) { return memory_read_generic<Width, AddrShift, Endian, 1, true>(rop(), address, mask); } u16 read_word_unaligned(offs_t address) { return memory_read_generic<Width, AddrShift, Endian, 1, false>(rop(), address, 0xffff); } @@ -1745,6 +1726,7 @@ public: u64 read_qword_unaligned(offs_t address, u64 mask) { return memory_read_generic<Width, AddrShift, Endian, 3, false>(rop(), address, mask); } void write_byte(offs_t address, u8 data) { if constexpr(Width == 0) write_native(address & ~NATIVE_MASK, data); else memory_write_generic<Width, AddrShift, Endian, 0, true>(wop(), address, data, 0xff); } + void write_byte(offs_t address, u8 data, u8 mask) { memory_write_generic<Width, AddrShift, Endian, 0, true>(wop(), address, data, mask); } void write_word(offs_t address, u16 data) { if constexpr(Width == 1) write_native(address & ~NATIVE_MASK, data); else memory_write_generic<Width, AddrShift, Endian, 1, true>(wop(), address, data, 0xffff); } void write_word(offs_t address, u16 data, u16 mask) { memory_write_generic<Width, AddrShift, Endian, 1, true>(wop(), address, data, mask); } void write_word_unaligned(offs_t address, u16 data) { memory_write_generic<Width, AddrShift, Endian, 1, false>(wop(), address, data, 0xffff); } @@ -1760,6 +1742,7 @@ public: std::pair<u8, u16> read_byte_flags(offs_t address) { if constexpr(Width == 0) return read_native_flags(address & ~NATIVE_MASK); else return memory_read_generic_flags<Width, AddrShift, Endian, 0, true>(ropf(), address, 0xff); } + std::pair<u8, u16> read_byte_flags(offs_t address, u8 mask) { return memory_read_generic_flags<Width, AddrShift, Endian, 0, true>(ropf(), address, mask); } std::pair<u16, u16> read_word_flags(offs_t address) { if constexpr(Width == 1) return read_native_flags(address & ~NATIVE_MASK); else return memory_read_generic_flags<Width, AddrShift, Endian, 1, true>(ropf(), address, 0xffff); } std::pair<u16, u16> read_word_flags(offs_t address, u16 mask) { return memory_read_generic_flags<Width, AddrShift, Endian, 1, true>(ropf(), address, mask); } std::pair<u16, u16> read_word_unaligned_flags(offs_t address) { return memory_read_generic_flags<Width, AddrShift, Endian, 1, false>(ropf(), address, 0xffff); } @@ -1774,6 +1757,7 @@ public: std::pair<u64, u16> read_qword_unaligned_flags(offs_t address, u64 mask) { return memory_read_generic_flags<Width, AddrShift, Endian, 3, false>(ropf(), address, mask); } u16 write_byte_flags(offs_t address, u8 data) { if constexpr(Width == 0) return write_native_flags(address & ~NATIVE_MASK, data); else return memory_write_generic_flags<Width, AddrShift, Endian, 0, true>(wopf(), address, data, 0xff); } + u16 write_byte_flags(offs_t address, u8 data, u8 mask) { return memory_write_generic_flags<Width, AddrShift, Endian, 0, true>(wopf(), address, data, mask); } u16 write_word_flags(offs_t address, u16 data) { if constexpr(Width == 1) return write_native_flags(address & ~NATIVE_MASK, data); else return memory_write_generic_flags<Width, AddrShift, Endian, 1, true>(wopf(), address, data, 0xffff); } u16 write_word_flags(offs_t address, u16 data, u16 mask) { return memory_write_generic_flags<Width, AddrShift, Endian, 1, true>(wopf(), address, data, mask); } u16 write_word_unaligned_flags(offs_t address, u16 data) { return memory_write_generic_flags<Width, AddrShift, Endian, 1, false>(wopf(), address, data, 0xffff); } @@ -1788,6 +1772,7 @@ public: u16 write_qword_unaligned_flags(offs_t address, u64 data, u64 mask) { return memory_write_generic_flags<Width, AddrShift, Endian, 3, false>(wopf(), address, data, mask); } u16 lookup_read_byte_flags(offs_t address) { if constexpr(Width == 0) return lookup_read_native_flags(address & ~NATIVE_MASK); else return lookup_memory_read_generic_flags<Width, AddrShift, Endian, 0, true>(lropf(), address, 0xff); } + u16 lookup_read_byte_flags(offs_t address, u8 mask) { return lookup_memory_read_generic_flags<Width, AddrShift, Endian, 0, true>(lropf(), address, mask); } u16 lookup_read_word_flags(offs_t address) { if constexpr(Width == 1) return lookup_read_native_flags(address & ~NATIVE_MASK); else return lookup_memory_read_generic_flags<Width, AddrShift, Endian, 1, true>(lropf(), address, 0xffff); } u16 lookup_read_word_flags(offs_t address, u16 mask) { return lookup_memory_read_generic_flags<Width, AddrShift, Endian, 1, true>(lropf(), address, mask); } u16 lookup_read_word_unaligned_flags(offs_t address) { return lookup_memory_read_generic_flags<Width, AddrShift, Endian, 1, false>(lropf(), address, 0xffff); } @@ -1802,6 +1787,7 @@ public: u16 lookup_read_qword_unaligned_flags(offs_t address, u64 mask) { return lookup_memory_read_generic_flags<Width, AddrShift, Endian, 3, false>(lropf(), address, mask); } u16 lookup_write_byte_flags(offs_t address) { if constexpr(Width == 0) return lookup_write_native_flags(address & ~NATIVE_MASK); else return lookup_memory_write_generic_flags<Width, AddrShift, Endian, 0, true>(lwopf(), address, 0xff); } + u16 lookup_write_byte_flags(offs_t address, u8 mask) { return lookup_memory_write_generic_flags<Width, AddrShift, Endian, 0, true>(lwopf(), address, mask); } u16 lookup_write_word_flags(offs_t address) { if constexpr(Width == 1) return lookup_write_native_flags(address & ~NATIVE_MASK); else return lookup_memory_write_generic_flags<Width, AddrShift, Endian, 1, true>(lwopf(), address, 0xffff); } u16 lookup_write_word_flags(offs_t address, u16 mask) { return lookup_memory_write_generic_flags<Width, AddrShift, Endian, 1, true>(lwopf(), address, mask); } u16 lookup_write_word_unaligned_flags(offs_t address) { return lookup_memory_write_generic_flags<Width, AddrShift, Endian, 1, false>(lwopf(), address, 0xffff); } @@ -1816,11 +1802,11 @@ public: u16 lookup_write_qword_unaligned_flags(offs_t address, u64 mask) { return lookup_memory_write_generic_flags<Width, AddrShift, Endian, 3, false>(lwopf(), address, mask); } NativeType read_interruptible(offs_t address, NativeType mask = ~NativeType(0)) { - return dispatch_read_interruptible<Level, Width, AddrShift>(offs_t(-1), address & m_addrmask, mask, m_dispatch_read); + return dispatch_read_interruptible<Level, Width, AddrShift>(~offs_t(0), address & m_addrmask, mask, m_dispatch_read); } void write_interruptible(offs_t address, NativeType data, NativeType mask = ~NativeType(0)) { - dispatch_write_interruptible<Level, Width, AddrShift>(offs_t(-1), address & m_addrmask, data, mask, m_dispatch_write); + dispatch_write_interruptible<Level, Width, AddrShift>(~offs_t(0), address & m_addrmask, data, mask, m_dispatch_write); } private: @@ -1832,27 +1818,27 @@ private: const handler_entry_write<Width, AddrShift> *const *m_dispatch_write; NativeType read_native(offs_t address, NativeType mask = ~NativeType(0)) { - return dispatch_read<Level, Width, AddrShift>(offs_t(-1), address & m_addrmask, mask, m_dispatch_read); + return dispatch_read<Level, Width, AddrShift>(~offs_t(0), address & m_addrmask, mask, m_dispatch_read); } void write_native(offs_t address, NativeType data, NativeType mask = ~NativeType(0)) { - dispatch_write<Level, Width, AddrShift>(offs_t(-1), address & m_addrmask, data, mask, m_dispatch_write); + dispatch_write<Level, Width, AddrShift>(~offs_t(0), address & m_addrmask, data, mask, m_dispatch_write); } std::pair<NativeType, u16> read_native_flags(offs_t address, NativeType mask = ~NativeType(0)) { - return dispatch_read_flags<Level, Width, AddrShift>(offs_t(-1), address & m_addrmask, mask, m_dispatch_read); + return dispatch_read_flags<Level, Width, AddrShift>(~offs_t(0), address & m_addrmask, mask, m_dispatch_read); } u16 write_native_flags(offs_t address, NativeType data, NativeType mask = ~NativeType(0)) { - return dispatch_write_flags<Level, Width, AddrShift>(offs_t(-1), address & m_addrmask, data, mask, m_dispatch_write); + return dispatch_write_flags<Level, Width, AddrShift>(~offs_t(0), address & m_addrmask, data, mask, m_dispatch_write); } u16 lookup_read_native_flags(offs_t address, NativeType mask = ~NativeType(0)) { - return dispatch_lookup_read_flags<Level, Width, AddrShift>(offs_t(-1), address & m_addrmask, mask, m_dispatch_read); + return dispatch_lookup_read_flags<Level, Width, AddrShift>(~offs_t(0), address & m_addrmask, mask, m_dispatch_read); } u16 lookup_write_native_flags(offs_t address, NativeType mask = ~NativeType(0)) { - return dispatch_lookup_write_flags<Level, Width, AddrShift>(offs_t(-1), address & m_addrmask, mask, m_dispatch_write); + return dispatch_lookup_write_flags<Level, Width, AddrShift>(~offs_t(0), address & m_addrmask, mask, m_dispatch_write); } void set(address_space *space, std::pair<const void *, const void *> rw); @@ -1891,13 +1877,13 @@ public: // see if an address is within bounds, update it if not void check_address_r(offs_t address) { - if(address >= m_addrstart_r && address <= m_addrend_r) + if(EXPECTED(address >= m_addrstart_r && address <= m_addrend_r)) return; m_root_read->lookup(address, m_addrstart_r, m_addrend_r, m_cache_r); } void check_address_w(offs_t address) { - if(address >= m_addrstart_w && address <= m_addrend_w) + if(EXPECTED(address >= m_addrstart_w && address <= m_addrend_w)) return; m_root_write->lookup(address, m_addrstart_w, m_addrend_w, m_cache_w); } @@ -1922,6 +1908,7 @@ public: auto lwopf() { return [this](offs_t offset, NativeType mask) -> u16 { return lookup_write_native_flags(offset, mask); }; } u8 read_byte(offs_t address) { if constexpr(Width == 0) return read_native(address & ~NATIVE_MASK); else return memory_read_generic<Width, AddrShift, Endian, 0, true>(rop(), address, 0xff); } + u8 read_byte(offs_t address, u8 mask) { return memory_read_generic<Width, AddrShift, Endian, 0, true>(rop(), address, mask); } u16 read_word(offs_t address) { if constexpr(Width == 1) return read_native(address & ~NATIVE_MASK); else return memory_read_generic<Width, AddrShift, Endian, 1, true>(rop(), address, 0xffff); } u16 read_word(offs_t address, u16 mask) { return memory_read_generic<Width, AddrShift, Endian, 1, true>(rop(), address, mask); } u16 read_word_unaligned(offs_t address) { return memory_read_generic<Width, AddrShift, Endian, 1, false>(rop(), address, 0xffff); } @@ -1936,6 +1923,7 @@ public: u64 read_qword_unaligned(offs_t address, u64 mask) { return memory_read_generic<Width, AddrShift, Endian, 3, false>(rop(), address, mask); } void write_byte(offs_t address, u8 data) { if constexpr(Width == 0) write_native(address & ~NATIVE_MASK, data); else memory_write_generic<Width, AddrShift, Endian, 0, true>(wop(), address, data, 0xff); } + void write_byte(offs_t address, u8 data, u8 mask) { memory_write_generic<Width, AddrShift, Endian, 0, true>(wop(), address, data, mask); } void write_word(offs_t address, u16 data) { if constexpr(Width == 1) write_native(address & ~NATIVE_MASK, data); else memory_write_generic<Width, AddrShift, Endian, 1, true>(wop(), address, data, 0xffff); } void write_word(offs_t address, u16 data, u16 mask) { memory_write_generic<Width, AddrShift, Endian, 1, true>(wop(), address, data, mask); } void write_word_unaligned(offs_t address, u16 data) { memory_write_generic<Width, AddrShift, Endian, 1, false>(wop(), address, data, 0xffff); } @@ -1951,6 +1939,7 @@ public: std::pair<u8, u16> read_byte_flags(offs_t address) { if constexpr(Width == 0) return read_native_flags(address & ~NATIVE_MASK); else return memory_read_generic_flags<Width, AddrShift, Endian, 0, true>(ropf(), address, 0xff); } + std::pair<u8, u16> read_byte_flags(offs_t address, u8 mask) { return memory_read_generic_flags<Width, AddrShift, Endian, 0, true>(ropf(), address, mask); } std::pair<u16, u16> read_word_flags(offs_t address) { if constexpr(Width == 1) return read_native_flags(address & ~NATIVE_MASK); else return memory_read_generic_flags<Width, AddrShift, Endian, 1, true>(ropf(), address, 0xffff); } std::pair<u16, u16> read_word_flags(offs_t address, u16 mask) { return memory_read_generic_flags<Width, AddrShift, Endian, 1, true>(ropf(), address, mask); } std::pair<u16, u16> read_word_unaligned_flags(offs_t address) { return memory_read_generic_flags<Width, AddrShift, Endian, 1, false>(ropf(), address, 0xffff); } @@ -1965,6 +1954,7 @@ public: std::pair<u64, u16> read_qword_unaligned_flags(offs_t address, u64 mask) { return memory_read_generic_flags<Width, AddrShift, Endian, 3, false>(ropf(), address, mask); } u16 write_byte_flags(offs_t address, u8 data) { if constexpr(Width == 0) return write_native_flags(address & ~NATIVE_MASK, data); else return memory_write_generic_flags<Width, AddrShift, Endian, 0, true>(wopf(), address, data, 0xff); } + u16 write_byte_flags(offs_t address, u8 data, u8 mask) { return memory_write_generic_flags<Width, AddrShift, Endian, 0, true>(wopf(), address, data, mask); } u16 write_word_flags(offs_t address, u16 data) { if constexpr(Width == 1) return write_native_flags(address & ~NATIVE_MASK, data); else return memory_write_generic_flags<Width, AddrShift, Endian, 1, true>(wopf(), address, data, 0xffff); } u16 write_word_flags(offs_t address, u16 data, u16 mask) { return memory_write_generic_flags<Width, AddrShift, Endian, 1, true>(wopf(), address, data, mask); } u16 write_word_unaligned_flags(offs_t address, u16 data) { return memory_write_generic_flags<Width, AddrShift, Endian, 1, false>(wopf(), address, data, 0xffff); } @@ -1980,6 +1970,7 @@ public: u16 lookup_read_byte_flags(offs_t address) { if constexpr(Width == 0) return lookup_read_native_flags(address & ~NATIVE_MASK); else return lookup_memory_read_generic_flags<Width, AddrShift, Endian, 0, true>(lropf(), address, 0xff); } + u16 lookup_read_byte_flags(offs_t address, u8 mask) { return lookup_memory_read_generic_flags<Width, AddrShift, Endian, 0, true>(lropf(), address, mask); } u16 lookup_read_word_flags(offs_t address) { if constexpr(Width == 1) return lookup_read_native_flags(address & ~NATIVE_MASK); else return lookup_memory_read_generic_flags<Width, AddrShift, Endian, 1, true>(lropf(), address, 0xffff); } u16 lookup_read_word_flags(offs_t address, u16 mask) { return lookup_memory_read_generic_flags<Width, AddrShift, Endian, 1, true>(lropf(), address, mask); } u16 lookup_read_word_unaligned_flags(offs_t address) { return lookup_memory_read_generic_flags<Width, AddrShift, Endian, 1, false>(lropf(), address, 0xffff); } @@ -1994,6 +1985,7 @@ public: u16 lookup_read_qword_unaligned_flags(offs_t address, u64 mask) { return lookup_memory_read_generic_flags<Width, AddrShift, Endian, 3, false>(lropf(), address, mask); } u16 lookup_write_byte_flags(offs_t address) { if constexpr(Width == 0) return lookup_write_native_flags(address & ~NATIVE_MASK); else return lookup_memory_write_generic_flags<Width, AddrShift, Endian, 0, true>(lwopf(), address, 0xff); } + u16 lookup_write_byte_flags(offs_t address, u8 mask) { return lookup_memory_write_generic_flags<Width, AddrShift, Endian, 0, true>(lwopf(), address, mask); } u16 lookup_write_word_flags(offs_t address) { if constexpr(Width == 1) return lookup_write_native_flags(address & ~NATIVE_MASK); else return lookup_memory_write_generic_flags<Width, AddrShift, Endian, 1, true>(lwopf(), address, 0xffff); } u16 lookup_write_word_flags(offs_t address, u16 mask) { return lookup_memory_write_generic_flags<Width, AddrShift, Endian, 1, true>(lwopf(), address, mask); } u16 lookup_write_word_unaligned_flags(offs_t address) { return lookup_memory_write_generic_flags<Width, AddrShift, Endian, 1, false>(lwopf(), address, 0xffff); } @@ -2313,13 +2305,13 @@ protected: void check_optimize_mirror(const char *function, offs_t addrstart, offs_t addrend, offs_t addrmirror, offs_t &nstart, offs_t &nend, offs_t &nmask, offs_t &nmirror); void check_address(const char *function, offs_t addrstart, offs_t addrend); - address_space_installer(const address_space_config &config, memory_manager &manager) - : m_config(config), - m_manager(manager), - m_addrmask(make_bitmask<offs_t>(m_config.addr_width())), - m_logaddrmask(make_bitmask<offs_t>(m_config.logaddr_width())), - m_addrchars((m_config.addr_width() + 3) / 4), - m_logaddrchars((m_config.logaddr_width() + 3) / 4) + address_space_installer(const address_space_config &config, memory_manager &manager) : + m_config(config), + m_manager(manager), + m_addrmask(make_bitmask<offs_t>(m_config.addr_width())), + m_logaddrmask(make_bitmask<offs_t>(m_config.logaddr_width())), + m_addrchars((m_config.addr_width() + 3) / 4), + m_logaddrchars((m_config.logaddr_width() + 3) / 4) {} const address_space_config &m_config; // configuration of this space @@ -2343,6 +2335,24 @@ protected: address_space(memory_manager &manager, device_memory_interface &memory, int spacenum); public: + struct specific_access_info + { + struct side + { + void const *const *dispatch; + uintptr_t function; + ptrdiff_t displacement; + bool is_virtual; + }; + + unsigned native_bytes; + unsigned native_mask_bits; + unsigned address_width; + unsigned low_bits; + side read; + side write; + }; + virtual ~address_space(); // getters @@ -2406,12 +2416,13 @@ public: void set_log_unmap(bool log) { m_log_unmap = log; } // general accessors - virtual void accessors(data_accessors &accessors) const = 0; + virtual specific_access_info specific_accessors() const = 0; virtual void *get_read_ptr(offs_t address) const = 0; virtual void *get_write_ptr(offs_t address) const = 0; // read accessors virtual u8 read_byte(offs_t address) = 0; + virtual u8 read_byte(offs_t address, u8 mask) = 0; virtual u16 read_word(offs_t address) = 0; virtual u16 read_word(offs_t address, u16 mask) = 0; virtual u16 read_word_unaligned(offs_t address) = 0; @@ -2427,6 +2438,7 @@ public: // write accessors virtual void write_byte(offs_t address, u8 data) = 0; + virtual void write_byte(offs_t address, u8 data, u8 mask) = 0; virtual void write_word(offs_t address, u16 data) = 0; virtual void write_word(offs_t address, u16 data, u16 mask) = 0; virtual void write_word_unaligned(offs_t address, u16 data) = 0; @@ -2442,7 +2454,7 @@ public: // setup void prepare_map(); - void prepare_device_map(address_map &map); + void prepare_device_map(address_map &map) ATTR_COLD; void populate_from_map(address_map *map = nullptr); template<int Width, int AddrShift> handler_entry_read_unmapped <Width, AddrShift> *get_unmap_r() const { return static_cast<handler_entry_read_unmapped <Width, AddrShift> *>(m_unmap_r); } @@ -2458,7 +2470,7 @@ protected: virtual std::pair<void *, void *> get_cache_info() = 0; virtual std::pair<const void *, const void *> get_specific_info() = 0; - void prepare_map_generic(address_map &map, bool allow_alloc); + void prepare_map_generic(address_map &map, bool allow_alloc) ATTR_COLD; // private state device_t & m_device; // reference to the owning device @@ -2630,8 +2642,8 @@ public: int m_id; memory_view_entry(const address_space_config &config, memory_manager &manager, memory_view &view, int id); - void prepare_map_generic(address_map &map, bool allow_alloc); - void prepare_device_map(address_map &map); + void prepare_map_generic(address_map &map, bool allow_alloc) ATTR_COLD; + void prepare_device_map(address_map &map) ATTR_COLD; void check_range_optimize_all(const char *function, int width, offs_t addrstart, offs_t addrend, offs_t addrmask, offs_t addrmirror, offs_t addrselect, u64 unitmask, int cswidth, offs_t &nstart, offs_t &nend, offs_t &nmask, offs_t &nmirror, u64 &nunitmask, int &ncswidth); void check_range_optimize_mirror(const char *function, offs_t addrstart, offs_t addrend, offs_t addrmirror, offs_t &nstart, offs_t &nend, offs_t &nmask, offs_t &nmirror); @@ -2645,13 +2657,13 @@ public: void select(int entry); void disable(); + bool exists() const { return m_config != nullptr; } std::optional<int> entry() const { return m_cur_id == -1 ? std::optional<int>() : m_cur_slot; } const std::string &name() const { return m_name; } private: - device_t & m_device; std::string m_name; std::map<int, int> m_entry_mapping; @@ -2671,6 +2683,7 @@ private: void make_subdispatch(std::string context); int id_to_slot(int id) const; void register_state(); + void refresh_id(); }; diff --git a/src/emu/emumem_aspace.cpp b/src/emu/emumem_aspace.cpp index b0d00d6fca8..f93472b110b 100644 --- a/src/emu/emumem_aspace.cpp +++ b/src/emu/emumem_aspace.cpp @@ -10,10 +10,6 @@ ***************************************************************************/ #include "emu.h" -#include <list> -#include <map> -#include "emuopts.h" -#include "debug/debugcpu.h" #include "emumem_mud.h" #include "emumem_hea.h" @@ -27,6 +23,14 @@ #include "emumem_het.h" #include "emumem_hws.h" +#include "emuopts.h" +#include "debug/debugcpu.h" + +#include "mfpresolve.h" + +#include <list> +#include <map> + //************************************************************************** // DEBUGGING @@ -310,23 +314,24 @@ public: m_root_write->detach(handlers); } - // generate accessor table - virtual void accessors(data_accessors &accessors) const override + // generate specific accessor info + virtual specific_access_info specific_accessors() const override { - accessors.read_byte = reinterpret_cast<u8 (*)(address_space &, offs_t)>(&read_byte_static); - accessors.read_word = reinterpret_cast<u16 (*)(address_space &, offs_t)>(&read_word_static); - accessors.read_word_masked = reinterpret_cast<u16 (*)(address_space &, offs_t, u16)>(&read_word_masked_static); - accessors.read_dword = reinterpret_cast<u32 (*)(address_space &, offs_t)>(&read_dword_static); - accessors.read_dword_masked = reinterpret_cast<u32 (*)(address_space &, offs_t, u32)>(&read_dword_masked_static); - accessors.read_qword = reinterpret_cast<u64 (*)(address_space &, offs_t)>(&read_qword_static); - accessors.read_qword_masked = reinterpret_cast<u64 (*)(address_space &, offs_t, u64)>(&read_qword_masked_static); - accessors.write_byte = reinterpret_cast<void (*)(address_space &, offs_t, u8)>(&write_byte_static); - accessors.write_word = reinterpret_cast<void (*)(address_space &, offs_t, u16)>(&write_word_static); - accessors.write_word_masked = reinterpret_cast<void (*)(address_space &, offs_t, u16, u16)>(&write_word_masked_static); - accessors.write_dword = reinterpret_cast<void (*)(address_space &, offs_t, u32)>(&write_dword_static); - accessors.write_dword_masked = reinterpret_cast<void (*)(address_space &, offs_t, u32, u32)>(&write_dword_masked_static); - accessors.write_qword = reinterpret_cast<void (*)(address_space &, offs_t, u64)>(&write_qword_static); - accessors.write_qword_masked = reinterpret_cast<void (*)(address_space &, offs_t, u64, u64)>(&write_qword_masked_static); + specific_access_info accessors; + + accessors.native_bytes = 1 << Width; + accessors.native_mask_bits = ((Width + AddrShift) >= 0) ? (Width + AddrShift) : 0; + accessors.address_width = addr_width(); + accessors.low_bits = emu::detail::handler_entry_dispatch_level_to_lowbits(Level, Width, AddrShift); + accessors.read.dispatch = reinterpret_cast<void const *const *>(m_dispatch_read); + accessors.write.dispatch = reinterpret_cast<void const *const *>(m_dispatch_write); + + auto readfunc = &handler_entry_read<Width, AddrShift>::read; + auto writefunc = &handler_entry_write<Width, AddrShift>::write; + std::tie(accessors.read.function, accessors.read.displacement, accessors.read.is_virtual) = util::resolve_member_function(readfunc); + std::tie(accessors.write.function, accessors.write.displacement, accessors.write.is_virtual) = util::resolve_member_function(writefunc); + + return accessors; } // return a pointer to the read bank, or nullptr if none @@ -370,6 +375,7 @@ public: // virtual access to these functions u8 read_byte(offs_t address) override { if constexpr(Width == 0) return read_native(address & ~NATIVE_MASK); else return memory_read_generic<Width, AddrShift, Endian, 0, true>(rop(), address, 0xff); } + u8 read_byte(offs_t address, u8 mask) override { return memory_read_generic<Width, AddrShift, Endian, 0, true>(rop(), address, mask); } u16 read_word(offs_t address) override { if constexpr(Width == 1) return read_native(address & ~NATIVE_MASK); else return memory_read_generic<Width, AddrShift, Endian, 1, true>(rop(), address, 0xffff); } u16 read_word(offs_t address, u16 mask) override { return memory_read_generic<Width, AddrShift, Endian, 1, true>(rop(), address, mask); } u16 read_word_unaligned(offs_t address) override { return memory_read_generic<Width, AddrShift, Endian, 1, false>(rop(), address, 0xffff); } @@ -384,6 +390,7 @@ public: u64 read_qword_unaligned(offs_t address, u64 mask) override { return memory_read_generic<Width, AddrShift, Endian, 3, false>(rop(), address, mask); } void write_byte(offs_t address, u8 data) override { if constexpr(Width == 0) write_native(address & ~NATIVE_MASK, data); else memory_write_generic<Width, AddrShift, Endian, 0, true>(wop(), address, data, 0xff); } + void write_byte(offs_t address, u8 data, u8 mask) override { memory_write_generic<Width, AddrShift, Endian, 0, true>(wop(), address, data, mask); } void write_word(offs_t address, u16 data) override { if constexpr(Width == 1) write_native(address & ~NATIVE_MASK, data); else memory_write_generic<Width, AddrShift, Endian, 1, true>(wop(), address, data, 0xffff); } void write_word(offs_t address, u16 data, u16 mask) override { memory_write_generic<Width, AddrShift, Endian, 1, true>(wop(), address, data, mask); } void write_word_unaligned(offs_t address, u16 data) override { memory_write_generic<Width, AddrShift, Endian, 1, false>(wop(), address, data, 0xffff); } @@ -397,23 +404,6 @@ public: void write_qword_unaligned(offs_t address, u64 data) override { memory_write_generic<Width, AddrShift, Endian, 3, false>(wop(), address, data, 0xffffffffffffffffU); } void write_qword_unaligned(offs_t address, u64 data, u64 mask) override { memory_write_generic<Width, AddrShift, Endian, 3, false>(wop(), address, data, mask); } - - // static access to these functions - static u8 read_byte_static(this_type &space, offs_t address) { return Width == 0 ? space.read_native(address & ~NATIVE_MASK) : memory_read_generic<Width, AddrShift, Endian, 0, true>([&space](offs_t offset, NativeType mask) -> NativeType { return space.read_native(offset, mask); }, address, 0xff); } - static u16 read_word_static(this_type &space, offs_t address) { return Width == 1 ? space.read_native(address & ~NATIVE_MASK) : memory_read_generic<Width, AddrShift, Endian, 1, true>([&space](offs_t offset, NativeType mask) -> NativeType { return space.read_native(offset, mask); }, address, 0xffff); } - static u16 read_word_masked_static(this_type &space, offs_t address, u16 mask) { return memory_read_generic<Width, AddrShift, Endian, 1, true>([&space](offs_t offset, NativeType mask) -> NativeType { return space.read_native(offset, mask); }, address, mask); } - static u32 read_dword_static(this_type &space, offs_t address) { return Width == 2 ? space.read_native(address & ~NATIVE_MASK) : memory_read_generic<Width, AddrShift, Endian, 2, true>([&space](offs_t offset, NativeType mask) -> NativeType { return space.read_native(offset, mask); }, address, 0xffffffff); } - static u32 read_dword_masked_static(this_type &space, offs_t address, u32 mask) { return memory_read_generic<Width, AddrShift, Endian, 2, true>([&space](offs_t offset, NativeType mask) -> NativeType { return space.read_native(offset, mask); }, address, mask); } - static u64 read_qword_static(this_type &space, offs_t address) { return Width == 3 ? space.read_native(address & ~NATIVE_MASK) : memory_read_generic<Width, AddrShift, Endian, 3, true>([&space](offs_t offset, NativeType mask) -> NativeType { return space.read_native(offset, mask); }, address, 0xffffffffffffffffU); } - static u64 read_qword_masked_static(this_type &space, offs_t address, u64 mask) { return memory_read_generic<Width, AddrShift, Endian, 3, true>([&space](offs_t offset, NativeType mask) -> NativeType { return space.read_native(offset, mask); }, address, mask); } - static void write_byte_static(this_type &space, offs_t address, u8 data) { if (Width == 0) space.write_native(address & ~NATIVE_MASK, data); else memory_write_generic<Width, AddrShift, Endian, 0, true>([&space](offs_t offset, NativeType data, NativeType mask) { space.write_native(offset, data, mask); }, address, data, 0xff); } - static void write_word_static(this_type &space, offs_t address, u16 data) { if (Width == 1) space.write_native(address & ~NATIVE_MASK, data); else memory_write_generic<Width, AddrShift, Endian, 1, true>([&space](offs_t offset, NativeType data, NativeType mask) { space.write_native(offset, data, mask); }, address, data, 0xffff); } - static void write_word_masked_static(this_type &space, offs_t address, u16 data, u16 mask) { memory_write_generic<Width, AddrShift, Endian, 1, true>([&space](offs_t offset, NativeType data, NativeType mask) { space.write_native(offset, data, mask); }, address, data, mask); } - static void write_dword_static(this_type &space, offs_t address, u32 data) { if (Width == 2) space.write_native(address & ~NATIVE_MASK, data); else memory_write_generic<Width, AddrShift, Endian, 2, true>([&space](offs_t offset, NativeType data, NativeType mask) { space.write_native(offset, data, mask); }, address, data, 0xffffffff); } - static void write_dword_masked_static(this_type &space, offs_t address, u32 data, u32 mask) { memory_write_generic<Width, AddrShift, Endian, 2, true>([&space](offs_t offset, NativeType data, NativeType mask) { space.write_native(offset, data, mask); }, address, data, mask); } - static void write_qword_static(this_type &space, offs_t address, u64 data) { if (Width == 3) space.write_native(address & ~NATIVE_MASK, data); else memory_write_generic<Width, AddrShift, Endian, 3, false>([&space](offs_t offset, NativeType data, NativeType mask) { space.write_native(offset, data, mask); }, address, data, 0xffffffffffffffffU); } - static void write_qword_masked_static(this_type &space, offs_t address, u64 data, u64 mask) { memory_write_generic<Width, AddrShift, Endian, 3, false>([&space](offs_t offset, NativeType data, NativeType mask) { space.write_native(offset, data, mask); }, address, data, mask); } - handler_entry_read <Width, AddrShift> *m_root_read; handler_entry_write<Width, AddrShift> *m_root_write; diff --git a/src/emu/emumem_hedr.h b/src/emu/emumem_hedr.h index e1e9aa98743..e48737f31c9 100644 --- a/src/emu/emumem_hedr.h +++ b/src/emu/emumem_hedr.h @@ -17,7 +17,7 @@ public: using mapping = typename handler_entry_read<Width, AddrShift>::mapping; handler_entry_read_dispatch(address_space *space, const handler_entry::range &init, handler_entry_read<Width, AddrShift> *handler); - handler_entry_read_dispatch(address_space *space, memory_view &view); + handler_entry_read_dispatch(address_space *space, memory_view &view, offs_t addrstart, offs_t addrend); handler_entry_read_dispatch(handler_entry_read_dispatch<HighBits, Width, AddrShift> *src); ~handler_entry_read_dispatch(); @@ -92,6 +92,8 @@ private: handler_entry_read<Width, AddrShift> **m_u_dispatch; handler_entry::range *m_u_ranges; + handler_entry::range m_global_range; + void populate_nomirror_subdispatch(offs_t entry, offs_t start, offs_t end, offs_t ostart, offs_t oend, handler_entry_read<Width, AddrShift> *handler); void populate_mirror_subdispatch(offs_t entry, offs_t start, offs_t end, offs_t ostart, offs_t oend, offs_t mirror, handler_entry_read<Width, AddrShift> *handler); diff --git a/src/emu/emumem_hedr.ipp b/src/emu/emumem_hedr.ipp index 9994f251897..c79691e5e89 100644 --- a/src/emu/emumem_hedr.ipp +++ b/src/emu/emumem_hedr.ipp @@ -25,6 +25,7 @@ template<int HighBits, int Width, int AddrShift> handler_entry_read_dispatch<Hig m_a_dispatch = m_dispatch_array[0].data(); m_u_ranges = m_ranges_array[0].data(); m_u_dispatch = m_dispatch_array[0].data(); + m_global_range = init; if (!handler) handler = space->get_unmap_r<Width, AddrShift>(); @@ -35,7 +36,7 @@ template<int HighBits, int Width, int AddrShift> handler_entry_read_dispatch<Hig } } -template<int HighBits, int Width, int AddrShift> handler_entry_read_dispatch<HighBits, Width, AddrShift>::handler_entry_read_dispatch(address_space *space, memory_view &view) : handler_entry_read<Width, AddrShift>(space, handler_entry::F_VIEW), m_view(&view), m_a_dispatch(nullptr), m_a_ranges(nullptr), m_u_dispatch(nullptr), m_u_ranges(nullptr) +template<int HighBits, int Width, int AddrShift> handler_entry_read_dispatch<HighBits, Width, AddrShift>::handler_entry_read_dispatch(address_space *space, memory_view &view, offs_t addrstart, offs_t addrend) : handler_entry_read<Width, AddrShift>(space, handler_entry::F_VIEW), m_view(&view), m_a_dispatch(nullptr), m_a_ranges(nullptr), m_u_dispatch(nullptr), m_u_ranges(nullptr) { m_ranges_array.resize(1); m_dispatch_array.resize(1); @@ -43,12 +44,15 @@ template<int HighBits, int Width, int AddrShift> handler_entry_read_dispatch<Hig m_a_dispatch = m_dispatch_array[0].data(); m_u_ranges = m_ranges_array[0].data(); m_u_dispatch = m_dispatch_array[0].data(); + m_global_range.start = addrstart; + m_global_range.end = addrend; auto handler = space->get_unmap_r<Width, AddrShift>(); handler->ref(COUNT); for(unsigned int i=0; i != COUNT; i++) { m_u_dispatch[i] = handler; - m_u_ranges[i].set(0, 0); + m_u_ranges[i].start = addrstart; + m_u_ranges[i].end = addrend; } } @@ -60,6 +64,7 @@ template<int HighBits, int Width, int AddrShift> handler_entry_read_dispatch<Hig m_a_dispatch = m_dispatch_array[0].data(); m_u_ranges = m_ranges_array[0].data(); m_u_dispatch = m_dispatch_array[0].data(); + m_global_range = src->m_global_range; for(unsigned int i=0; i != COUNT; i++) { m_u_dispatch[i] = src->m_u_dispatch[i]->dup(); @@ -92,10 +97,12 @@ template<int HighBits, int Width, int AddrShift> offs_t handler_entry_read_dispa template<int HighBits, int Width, int AddrShift> void handler_entry_read_dispatch<HighBits, Width, AddrShift>::dump_map(std::vector<memory_entry> &map) const { if(m_view) { + offs_t base_cur = map.empty() ? m_view->m_addrstart & HIGHMASK : map.back().end + 1; for(u32 i = 0; i != m_dispatch_array.size(); i++) { u32 j = map.size(); - offs_t cur = map.empty() ? m_view->m_addrstart & HIGHMASK : map.back().end + 1; - offs_t end = m_view->m_addrend + 1; + offs_t cur = base_cur; + offs_t end = m_global_range.end + 1; + do { offs_t entry = (cur >> LowBits) & BITMASK; if(m_dispatch_array[i][entry]->is_dispatch() || m_dispatch_array[i][entry]->is_view()) @@ -116,6 +123,7 @@ template<int HighBits, int Width, int AddrShift> void handler_entry_read_dispatc } else { offs_t cur = map.empty() ? 0 : map.back().end + 1; offs_t base = cur & UPMASK; + offs_t end = m_global_range.end + 1; do { offs_t entry = (cur >> LowBits) & BITMASK; if(m_a_dispatch[entry]->is_dispatch() || m_a_dispatch[entry]->is_view()) @@ -123,7 +131,7 @@ template<int HighBits, int Width, int AddrShift> void handler_entry_read_dispatc else map.emplace_back(memory_entry{ m_a_ranges[entry].start, m_a_ranges[entry].end, m_a_dispatch[entry] }); cur = map.back().end + 1; - } while(cur && !((cur ^ base) & UPMASK)); + } while(cur != end && !((cur ^ base) & UPMASK)); } } @@ -343,7 +351,7 @@ template<int HighBits, int Width, int AddrShift> void handler_entry_read_dispatc template<int HighBits, int Width, int AddrShift> void handler_entry_read_dispatch<HighBits, Width, AddrShift>::populate_mismatched_nomirror_subdispatch(offs_t entry, offs_t start, offs_t end, offs_t ostart, offs_t oend, const memory_units_descriptor<Width, AddrShift> &descriptor, u8 rkey, std::vector<mapping> &mappings) { auto cur = m_u_dispatch[entry]; - if(cur->is_dispatch()) + if(cur->is_dispatch() && !cur->is_view()) cur->populate_mismatched_nomirror(start, end, ostart, oend, descriptor, rkey, mappings); else { auto subdispatch = new handler_entry_read_dispatch<LowBits, Width, AddrShift>(this->m_space, m_u_ranges[entry], cur); @@ -654,57 +662,29 @@ template<int HighBits, int Width, int AddrShift> void handler_entry_read_dispatc u32 dt = lowbits - LowBits; u32 ne = 1 << dt; u32 ee = end_entry - start_entry; - if(m_view) { - auto filter = [s = m_view->m_addrstart, e = m_view->m_addrend] (handler_entry::range r) { r.intersect(s, e); return r; }; - - for(offs_t entry = 0; entry <= ee; entry++) { - dispatch[entry]->ref(ne); - u32 e0 = (entry << dt) & BITMASK; - for(offs_t e = 0; e != ne; e++) { - offs_t e1 = e0 | e; - if(!(m_u_dispatch[e1]->flags() & handler_entry::F_UNMAP)) - fatalerror("Collision on multiple init_handlers calls"); - m_u_dispatch[e1]->unref(); - m_u_dispatch[e1] = dispatch[entry]; - m_u_ranges[e1] = filter(ranges[entry]); - } - } - } else { - for(offs_t entry = 0; entry <= ee; entry++) { - dispatch[entry]->ref(ne); - u32 e0 = (entry << dt) & BITMASK; - for(offs_t e = 0; e != ne; e++) { - offs_t e1 = e0 | e; - if(!(m_u_dispatch[e1]->flags() & handler_entry::F_UNMAP)) - fatalerror("Collision on multiple init_handlers calls"); - m_u_dispatch[e1]->unref(); - m_u_dispatch[e1] = dispatch[entry]; - m_u_ranges[e1] = ranges[entry]; - } + auto filter = [s = m_global_range.start, e = m_global_range.end] (handler_entry::range r) { r.intersect(s, e); return r; }; + for(offs_t entry = 0; entry <= ee; entry++) { + dispatch[entry]->ref(ne); + u32 e0 = (entry << dt) & BITMASK; + for(offs_t e = 0; e != ne; e++) { + offs_t e1 = e0 | e; + if(!(m_u_dispatch[e1]->flags() & handler_entry::F_UNMAP)) + fatalerror("Collision on multiple init_handlers calls"); + m_u_dispatch[e1]->unref(); + m_u_dispatch[e1] = dispatch[entry]; + m_u_ranges[e1] = filter(ranges[entry]); } } } else { - if(m_view) { - auto filter = [s = m_view->m_addrstart, e = m_view->m_addrend] (handler_entry::range r) { r.intersect(s, e); return r; }; - - for(offs_t entry = start_entry & BITMASK; entry <= (end_entry & BITMASK); entry++) { - if(!(m_u_dispatch[entry]->flags() & handler_entry::F_UNMAP)) - fatalerror("Collision on multiple init_handlers calls"); - m_u_dispatch[entry]->unref(); - m_u_dispatch[entry] = dispatch[entry]; - m_u_ranges[entry] = filter(ranges[entry]); - dispatch[entry]->ref(); - } - } else { - for(offs_t entry = start_entry & BITMASK; entry <= (end_entry & BITMASK); entry++) { - if(!(m_u_dispatch[entry]->flags() & handler_entry::F_UNMAP)) - fatalerror("Collision on multiple init_handlers calls"); - m_u_dispatch[entry]->unref(); - m_u_dispatch[entry] = dispatch[entry]; - m_u_ranges[entry] = ranges[entry]; - dispatch[entry]->ref(); - } + auto filter = [s = m_global_range.start, e = m_global_range.end] (handler_entry::range r) { r.intersect(s, e); return r; }; + for(offs_t entry = start_entry & BITMASK; entry <= (end_entry & BITMASK); entry++) { + if(!(m_u_dispatch[entry]->flags() & handler_entry::F_UNMAP)) + fatalerror("Collision on multiple init_handlers calls"); + m_u_dispatch[entry]->unref(); + m_u_dispatch[entry] = dispatch[entry]; + m_u_ranges[entry] = filter(ranges[entry]); + dispatch[entry]->ref(); } } } diff --git a/src/emu/emumem_hedw.h b/src/emu/emumem_hedw.h index 130293a73a4..4bd3098a333 100644 --- a/src/emu/emumem_hedw.h +++ b/src/emu/emumem_hedw.h @@ -17,7 +17,7 @@ public: using mapping = typename handler_entry_write<Width, AddrShift>::mapping; handler_entry_write_dispatch(address_space *space, const handler_entry::range &init, handler_entry_write<Width, AddrShift> *handler); - handler_entry_write_dispatch(address_space *space, memory_view &view); + handler_entry_write_dispatch(address_space *space, memory_view &view, offs_t addrstart, offs_t addrend); handler_entry_write_dispatch(handler_entry_write_dispatch<HighBits, Width, AddrShift> *src); ~handler_entry_write_dispatch(); @@ -92,6 +92,8 @@ private: handler_entry_write<Width, AddrShift> **m_u_dispatch; handler_entry::range *m_u_ranges; + handler_entry::range m_global_range; + void populate_nomirror_subdispatch(offs_t entry, offs_t start, offs_t end, offs_t ostart, offs_t oend, handler_entry_write<Width, AddrShift> *handler); void populate_mirror_subdispatch(offs_t entry, offs_t start, offs_t end, offs_t ostart, offs_t oend, offs_t mirror, handler_entry_write<Width, AddrShift> *handler); diff --git a/src/emu/emumem_hedw.ipp b/src/emu/emumem_hedw.ipp index 37c09ccfe11..dd6001d4bb0 100644 --- a/src/emu/emumem_hedw.ipp +++ b/src/emu/emumem_hedw.ipp @@ -25,6 +25,7 @@ template<int HighBits, int Width, int AddrShift> handler_entry_write_dispatch<Hi m_a_dispatch = m_dispatch_array[0].data(); m_u_ranges = m_ranges_array[0].data(); m_u_dispatch = m_dispatch_array[0].data(); + m_global_range = init; if (!handler) handler = space->get_unmap_w<Width, AddrShift>(); @@ -35,7 +36,7 @@ template<int HighBits, int Width, int AddrShift> handler_entry_write_dispatch<Hi } } -template<int HighBits, int Width, int AddrShift> handler_entry_write_dispatch<HighBits, Width, AddrShift>::handler_entry_write_dispatch(address_space *space, memory_view &view) : handler_entry_write<Width, AddrShift>(space, handler_entry::F_VIEW), m_view(&view), m_a_dispatch(nullptr), m_a_ranges(nullptr), m_u_dispatch(nullptr), m_u_ranges(nullptr) +template<int HighBits, int Width, int AddrShift> handler_entry_write_dispatch<HighBits, Width, AddrShift>::handler_entry_write_dispatch(address_space *space, memory_view &view, offs_t addrstart, offs_t addrend) : handler_entry_write<Width, AddrShift>(space, handler_entry::F_VIEW), m_view(&view), m_a_dispatch(nullptr), m_a_ranges(nullptr), m_u_dispatch(nullptr), m_u_ranges(nullptr) { m_ranges_array.resize(1); m_dispatch_array.resize(1); @@ -43,11 +44,16 @@ template<int HighBits, int Width, int AddrShift> handler_entry_write_dispatch<Hi m_a_dispatch = m_dispatch_array[0].data(); m_u_ranges = m_ranges_array[0].data(); m_u_dispatch = m_dispatch_array[0].data(); + m_global_range.start = addrstart; + m_global_range.end = addrend; auto handler = space->get_unmap_w<Width, AddrShift>(); handler->ref(COUNT); - for(unsigned int i=0; i != COUNT; i++) + for(unsigned int i=0; i != COUNT; i++) { m_u_dispatch[i] = handler; + m_u_ranges[i].start = addrstart; + m_u_ranges[i].end = addrend; + } } template<int HighBits, int Width, int AddrShift> handler_entry_write_dispatch<HighBits, Width, AddrShift>::handler_entry_write_dispatch(handler_entry_write_dispatch<HighBits, Width, AddrShift> *src) : handler_entry_write<Width, AddrShift>(src->m_space, handler_entry::F_DISPATCH), m_view(nullptr) @@ -58,6 +64,7 @@ template<int HighBits, int Width, int AddrShift> handler_entry_write_dispatch<Hi m_a_dispatch = m_dispatch_array[0].data(); m_u_ranges = m_ranges_array[0].data(); m_u_dispatch = m_dispatch_array[0].data(); + m_global_range = src->m_global_range; for(unsigned int i=0; i != COUNT; i++) { m_u_dispatch[i] = src->m_u_dispatch[i]->dup(); @@ -91,10 +98,11 @@ template<int HighBits, int Width, int AddrShift> offs_t handler_entry_write_disp template<int HighBits, int Width, int AddrShift> void handler_entry_write_dispatch<HighBits, Width, AddrShift>::dump_map(std::vector<memory_entry> &map) const { if(m_view) { + offs_t base_cur = map.empty() ? m_view->m_addrstart & HIGHMASK : map.back().end + 1; for(u32 i = 0; i != m_dispatch_array.size(); i++) { u32 j = map.size(); - offs_t cur = map.empty() ? m_view->m_addrstart & HIGHMASK : map.back().end + 1; - offs_t end = m_view->m_addrend + 1; + offs_t cur = base_cur; + offs_t end = m_global_range.end + 1; do { offs_t entry = (cur >> LowBits) & BITMASK; if(m_dispatch_array[i][entry]->is_dispatch() || m_dispatch_array[i][entry]->is_view()) @@ -115,6 +123,7 @@ template<int HighBits, int Width, int AddrShift> void handler_entry_write_dispat } else { offs_t cur = map.empty() ? 0 : map.back().end + 1; offs_t base = cur & UPMASK; + offs_t end = m_global_range.end + 1; do { offs_t entry = (cur >> LowBits) & BITMASK; if(m_a_dispatch[entry]->is_dispatch() || m_a_dispatch[entry]->is_view()) @@ -122,7 +131,7 @@ template<int HighBits, int Width, int AddrShift> void handler_entry_write_dispat else map.emplace_back(memory_entry{ m_a_ranges[entry].start, m_a_ranges[entry].end, m_a_dispatch[entry] }); cur = map.back().end + 1; - } while(cur && !((cur ^ base) & UPMASK)); + } while(cur != end && !((cur ^ base) & UPMASK)); } } @@ -655,57 +664,29 @@ template<int HighBits, int Width, int AddrShift> void handler_entry_write_dispat u32 dt = lowbits - LowBits; u32 ne = 1 << dt; u32 ee = end_entry - start_entry; - if(m_view) { - auto filter = [s = m_view->m_addrstart, e = m_view->m_addrend] (handler_entry::range r) { r.intersect(s, e); return r; }; - - for(offs_t entry = 0; entry <= ee; entry++) { - dispatch[entry]->ref(ne); - u32 e0 = (entry << dt) & BITMASK; - for(offs_t e = 0; e != ne; e++) { - offs_t e1 = e0 | e; - if(!(m_u_dispatch[e1]->flags() & handler_entry::F_UNMAP)) - fatalerror("Collision on multiple init_handlers calls"); - m_u_dispatch[e1]->unref(); - m_u_dispatch[e1] = dispatch[entry]; - m_u_ranges[e1] = filter(ranges[entry]); - } - } - } else { - for(offs_t entry = 0; entry <= ee; entry++) { - dispatch[entry]->ref(ne); - u32 e0 = (entry << dt) & BITMASK; - for(offs_t e = 0; e != ne; e++) { - offs_t e1 = e0 | e; - if(!(m_u_dispatch[e1]->flags() & handler_entry::F_UNMAP)) - fatalerror("Collision on multiple init_handlers calls"); - m_u_dispatch[e1]->unref(); - m_u_dispatch[e1] = dispatch[entry]; - m_u_ranges[e1] = ranges[entry]; - } + auto filter = [s = m_global_range.start, e = m_global_range.end] (handler_entry::range r) { r.intersect(s, e); return r; }; + for(offs_t entry = 0; entry <= ee; entry++) { + dispatch[entry]->ref(ne); + u32 e0 = (entry << dt) & BITMASK; + for(offs_t e = 0; e != ne; e++) { + offs_t e1 = e0 | e; + if(!(m_u_dispatch[e1]->flags() & handler_entry::F_UNMAP)) + fatalerror("Collision on multiple init_handlers calls"); + m_u_dispatch[e1]->unref(); + m_u_dispatch[e1] = dispatch[entry]; + m_u_ranges[e1] = filter(ranges[entry]); } } } else { - if(m_view) { - auto filter = [s = m_view->m_addrstart, e = m_view->m_addrend] (handler_entry::range r) { r.intersect(s, e); return r; }; - - for(offs_t entry = start_entry & BITMASK; entry <= (end_entry & BITMASK); entry++) { - if(!(m_u_dispatch[entry]->flags() & handler_entry::F_UNMAP)) - fatalerror("Collision on multiple init_handlers calls"); - m_u_dispatch[entry]->unref(); - m_u_dispatch[entry] = dispatch[entry]; - m_u_ranges[entry] = filter(ranges[entry]); - dispatch[entry]->ref(); - } - } else { - for(offs_t entry = start_entry & BITMASK; entry <= (end_entry & BITMASK); entry++) { - if(!(m_u_dispatch[entry]->flags() & handler_entry::F_UNMAP)) - fatalerror("Collision on multiple init_handlers calls"); - m_u_dispatch[entry]->unref(); - m_u_dispatch[entry] = dispatch[entry]; - m_u_ranges[entry] = ranges[entry]; - dispatch[entry]->ref(); - } + auto filter = [s = m_global_range.start, e = m_global_range.end] (handler_entry::range r) { r.intersect(s, e); return r; }; + for(offs_t entry = start_entry & BITMASK; entry <= (end_entry & BITMASK); entry++) { + if(!(m_u_dispatch[entry]->flags() & handler_entry::F_UNMAP)) + fatalerror("Collision on multiple init_handlers calls"); + m_u_dispatch[entry]->unref(); + m_u_dispatch[entry] = dispatch[entry]; + m_u_ranges[entry] = filter(ranges[entry]); + dispatch[entry]->ref(); } } } diff --git a/src/emu/emumem_mview.cpp b/src/emu/emumem_mview.cpp index 2456cadc7f9..8d16f8f439a 100644 --- a/src/emu/emumem_mview.cpp +++ b/src/emu/emumem_mview.cpp @@ -505,18 +505,25 @@ void memory_view::register_state() { m_device.machine().save().save_item(&m_device, "view", m_device.subtag(m_name).c_str(), 0, NAME(m_cur_slot)); m_device.machine().save().save_item(&m_device, "view", m_device.subtag(m_name).c_str(), 0, NAME(m_cur_id)); - m_device.machine().save().register_postload(save_prepost_delegate(NAME([this]() { m_handler_read->select_a(m_cur_id); m_handler_write->select_a(m_cur_id); }))); + m_device.machine().save().register_postload(save_prepost_delegate(FUNC(memory_view::refresh_id), this)); +} + +void memory_view::refresh_id() +{ + if (m_handler_read) { + m_handler_read->select_a(m_cur_id); + m_handler_write->select_a(m_cur_id); + } + + if (m_space) + m_space->invalidate_caches(read_or_write::READWRITE); } void memory_view::disable() { m_cur_slot = -1; m_cur_id = -1; - m_handler_read->select_a(-1); - m_handler_write->select_a(-1); - - if(m_space) - m_space->invalidate_caches(read_or_write::READWRITE); + refresh_id(); } void memory_view::select(int slot) @@ -527,11 +534,7 @@ void memory_view::select(int slot) m_cur_slot = slot; m_cur_id = i->second; - m_handler_read->select_a(m_cur_id); - m_handler_write->select_a(m_cur_id); - - if(m_space) - m_space->invalidate_caches(read_or_write::READWRITE); + refresh_id(); } int memory_view::id_to_slot(int id) const @@ -553,60 +556,60 @@ void memory_view::initialize_from_address_map(offs_t addrstart, offs_t addrend, } namespace { - template<int Width, int AddrShift> void h_make_1(int HighBits, address_space &space, memory_view &view, handler_entry *&r, handler_entry *&w) { + template<int Width, int AddrShift> void h_make_1(int HighBits, address_space &space, memory_view &view, offs_t addrstart, offs_t addrend, handler_entry *&r, handler_entry *&w) { switch(HighBits) { - case 0: r = new handler_entry_read_dispatch<std::max(0, Width), Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<std::max(0, Width), Width, AddrShift>(&space, view); break; - case 1: r = new handler_entry_read_dispatch<std::max(1, Width), Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<std::max(1, Width), Width, AddrShift>(&space, view); break; - case 2: r = new handler_entry_read_dispatch<std::max(2, Width), Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<std::max(2, Width), Width, AddrShift>(&space, view); break; - case 3: r = new handler_entry_read_dispatch<std::max(3, Width), Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<std::max(3, Width), Width, AddrShift>(&space, view); break; - case 4: r = new handler_entry_read_dispatch< 4, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch< 4, Width, AddrShift>(&space, view); break; - case 5: r = new handler_entry_read_dispatch< 5, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch< 5, Width, AddrShift>(&space, view); break; - case 6: r = new handler_entry_read_dispatch< 6, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch< 6, Width, AddrShift>(&space, view); break; - case 7: r = new handler_entry_read_dispatch< 7, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch< 7, Width, AddrShift>(&space, view); break; - case 8: r = new handler_entry_read_dispatch< 8, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch< 8, Width, AddrShift>(&space, view); break; - case 9: r = new handler_entry_read_dispatch< 9, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch< 9, Width, AddrShift>(&space, view); break; - case 10: r = new handler_entry_read_dispatch<10, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<10, Width, AddrShift>(&space, view); break; - case 11: r = new handler_entry_read_dispatch<11, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<11, Width, AddrShift>(&space, view); break; - case 12: r = new handler_entry_read_dispatch<12, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<12, Width, AddrShift>(&space, view); break; - case 13: r = new handler_entry_read_dispatch<13, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<13, Width, AddrShift>(&space, view); break; - case 14: r = new handler_entry_read_dispatch<14, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<14, Width, AddrShift>(&space, view); break; - case 15: r = new handler_entry_read_dispatch<15, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<15, Width, AddrShift>(&space, view); break; - case 16: r = new handler_entry_read_dispatch<16, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<16, Width, AddrShift>(&space, view); break; - case 17: r = new handler_entry_read_dispatch<17, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<17, Width, AddrShift>(&space, view); break; - case 18: r = new handler_entry_read_dispatch<18, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<18, Width, AddrShift>(&space, view); break; - case 19: r = new handler_entry_read_dispatch<19, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<19, Width, AddrShift>(&space, view); break; - case 20: r = new handler_entry_read_dispatch<20, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<20, Width, AddrShift>(&space, view); break; - case 21: r = new handler_entry_read_dispatch<21, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<21, Width, AddrShift>(&space, view); break; - case 22: r = new handler_entry_read_dispatch<22, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<22, Width, AddrShift>(&space, view); break; - case 23: r = new handler_entry_read_dispatch<23, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<23, Width, AddrShift>(&space, view); break; - case 24: r = new handler_entry_read_dispatch<24, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<24, Width, AddrShift>(&space, view); break; - case 25: r = new handler_entry_read_dispatch<25, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<25, Width, AddrShift>(&space, view); break; - case 26: r = new handler_entry_read_dispatch<26, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<26, Width, AddrShift>(&space, view); break; - case 27: r = new handler_entry_read_dispatch<27, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<27, Width, AddrShift>(&space, view); break; - case 28: r = new handler_entry_read_dispatch<28, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<28, Width, AddrShift>(&space, view); break; - case 29: r = new handler_entry_read_dispatch<29, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<29, Width, AddrShift>(&space, view); break; - case 30: r = new handler_entry_read_dispatch<30, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<30, Width, AddrShift>(&space, view); break; - case 31: r = new handler_entry_read_dispatch<31, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<31, Width, AddrShift>(&space, view); break; - case 32: r = new handler_entry_read_dispatch<32, Width, AddrShift>(&space, view); w = new handler_entry_write_dispatch<32, Width, AddrShift>(&space, view); break; + case 0: r = new handler_entry_read_dispatch<std::max(0, Width), Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<std::max(0, Width), Width, AddrShift>(&space, view, addrstart, addrend); break; + case 1: r = new handler_entry_read_dispatch<std::max(1, Width), Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<std::max(1, Width), Width, AddrShift>(&space, view, addrstart, addrend); break; + case 2: r = new handler_entry_read_dispatch<std::max(2, Width), Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<std::max(2, Width), Width, AddrShift>(&space, view, addrstart, addrend); break; + case 3: r = new handler_entry_read_dispatch<std::max(3, Width), Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<std::max(3, Width), Width, AddrShift>(&space, view, addrstart, addrend); break; + case 4: r = new handler_entry_read_dispatch< 4, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch< 4, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 5: r = new handler_entry_read_dispatch< 5, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch< 5, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 6: r = new handler_entry_read_dispatch< 6, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch< 6, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 7: r = new handler_entry_read_dispatch< 7, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch< 7, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 8: r = new handler_entry_read_dispatch< 8, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch< 8, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 9: r = new handler_entry_read_dispatch< 9, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch< 9, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 10: r = new handler_entry_read_dispatch<10, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<10, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 11: r = new handler_entry_read_dispatch<11, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<11, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 12: r = new handler_entry_read_dispatch<12, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<12, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 13: r = new handler_entry_read_dispatch<13, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<13, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 14: r = new handler_entry_read_dispatch<14, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<14, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 15: r = new handler_entry_read_dispatch<15, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<15, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 16: r = new handler_entry_read_dispatch<16, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<16, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 17: r = new handler_entry_read_dispatch<17, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<17, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 18: r = new handler_entry_read_dispatch<18, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<18, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 19: r = new handler_entry_read_dispatch<19, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<19, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 20: r = new handler_entry_read_dispatch<20, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<20, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 21: r = new handler_entry_read_dispatch<21, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<21, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 22: r = new handler_entry_read_dispatch<22, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<22, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 23: r = new handler_entry_read_dispatch<23, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<23, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 24: r = new handler_entry_read_dispatch<24, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<24, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 25: r = new handler_entry_read_dispatch<25, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<25, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 26: r = new handler_entry_read_dispatch<26, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<26, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 27: r = new handler_entry_read_dispatch<27, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<27, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 28: r = new handler_entry_read_dispatch<28, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<28, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 29: r = new handler_entry_read_dispatch<29, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<29, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 30: r = new handler_entry_read_dispatch<30, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<30, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 31: r = new handler_entry_read_dispatch<31, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<31, Width, AddrShift>(&space, view, addrstart, addrend); break; + case 32: r = new handler_entry_read_dispatch<32, Width, AddrShift>(&space, view, addrstart, addrend); w = new handler_entry_write_dispatch<32, Width, AddrShift>(&space, view, addrstart, addrend); break; default: abort(); } } - void h_make(int HighBits, int Width, int AddrShift, address_space &space, memory_view &view, handler_entry *&r, handler_entry *&w) { + void h_make(int HighBits, int Width, int AddrShift, address_space &space, memory_view &view, offs_t addrstart, offs_t addrend, handler_entry *&r, handler_entry *&w) { switch (Width | (AddrShift + 4)) { - case 8|(4+1): h_make_1<0, 1>(HighBits, space, view, r, w); break; - case 8|(4-0): h_make_1<0, 0>(HighBits, space, view, r, w); break; - case 16|(4+3): h_make_1<1, 3>(HighBits, space, view, r, w); break; - case 16|(4-0): h_make_1<1, 0>(HighBits, space, view, r, w); break; - case 16|(4-1): h_make_1<1, -1>(HighBits, space, view, r, w); break; - case 32|(4+3): h_make_1<2, 3>(HighBits, space, view, r, w); break; - case 32|(4-0): h_make_1<2, 0>(HighBits, space, view, r, w); break; - case 32|(4-1): h_make_1<2, -1>(HighBits, space, view, r, w); break; - case 32|(4-2): h_make_1<2, -2>(HighBits, space, view, r, w); break; - case 64|(4-0): h_make_1<3, 0>(HighBits, space, view, r, w); break; - case 64|(4-1): h_make_1<3, -1>(HighBits, space, view, r, w); break; - case 64|(4-2): h_make_1<3, -2>(HighBits, space, view, r, w); break; - case 64|(4-3): h_make_1<3, -3>(HighBits, space, view, r, w); break; + case 8|(4+1): h_make_1<0, 1>(HighBits, space, view, addrstart, addrend, r, w); break; + case 8|(4-0): h_make_1<0, 0>(HighBits, space, view, addrstart, addrend, r, w); break; + case 16|(4+3): h_make_1<1, 3>(HighBits, space, view, addrstart, addrend, r, w); break; + case 16|(4-0): h_make_1<1, 0>(HighBits, space, view, addrstart, addrend, r, w); break; + case 16|(4-1): h_make_1<1, -1>(HighBits, space, view, addrstart, addrend, r, w); break; + case 32|(4+3): h_make_1<2, 3>(HighBits, space, view, addrstart, addrend, r, w); break; + case 32|(4-0): h_make_1<2, 0>(HighBits, space, view, addrstart, addrend, r, w); break; + case 32|(4-1): h_make_1<2, -1>(HighBits, space, view, addrstart, addrend, r, w); break; + case 32|(4-2): h_make_1<2, -2>(HighBits, space, view, addrstart, addrend, r, w); break; + case 64|(4-0): h_make_1<3, 0>(HighBits, space, view, addrstart, addrend, r, w); break; + case 64|(4-1): h_make_1<3, -1>(HighBits, space, view, addrstart, addrend, r, w); break; + case 64|(4-2): h_make_1<3, -2>(HighBits, space, view, addrstart, addrend, r, w); break; + case 64|(4-3): h_make_1<3, -3>(HighBits, space, view, addrstart, addrend, r, w); break; default: abort(); } } @@ -632,7 +635,7 @@ std::pair<handler_entry *, handler_entry *> memory_view::make_handlers(address_s offs_t span = addrstart ^ addrend; u32 awidth = 32 - count_leading_zeros_32(span); - h_make(awidth, m_config->data_width(), m_config->addr_shift(), space, *this, m_handler_read, m_handler_write); + h_make(awidth, m_config->data_width(), m_config->addr_shift(), space, *this, addrstart, addrend, m_handler_read, m_handler_write); m_handler_read->ref(); m_handler_write->ref(); } diff --git a/src/emu/emupal.cpp b/src/emu/emupal.cpp index 9e1370a24ed..d8eb5a3e774 100644 --- a/src/emu/emupal.cpp +++ b/src/emu/emupal.cpp @@ -168,6 +168,12 @@ palette_device &palette_device::set_format(xbgr_333_t, u32 entries) return *this; } +palette_device &palette_device::set_format(xbgr_333_nib_t, u32 entries) +{ + set_format(2, &raw_to_rgb_converter::standard_rgb_decoder<3,3,3, 0,4,8>, entries); + return *this; +} + palette_device &palette_device::set_format(xrgb_444_t, u32 entries) { set_format(2, &raw_to_rgb_converter::standard_rgb_decoder<4,4,4, 8,4,0>, entries); @@ -198,6 +204,12 @@ palette_device &palette_device::set_format(rgbx_444_t, u32 entries) return *this; } +palette_device &palette_device::set_format(grbx_444_t, u32 entries) +{ + set_format(2, &raw_to_rgb_converter::standard_rgb_decoder<4,4,4, 8,12,4>, entries); + return *this; +} + palette_device &palette_device::set_format(gbrx_444_t, u32 entries) { set_format(2, &raw_to_rgb_converter::standard_rgb_decoder<4,4,4, 4,12,8>, entries); @@ -447,6 +459,12 @@ void palette_device::write16_ext(offs_t offset, u16 data, u16 mem_mask) update_for_write(offset * 2, 2); } +void palette_device::write32_ext(offs_t offset, u32 data, u32 mem_mask) +{ + m_paletteram_ext.write32(offset, data, mem_mask); + update_for_write(offset * 4, 4); +} + u8 palette_device::read8_ext(offs_t offset) { return m_paletteram_ext.read8(offset); @@ -457,6 +475,10 @@ u16 palette_device::read16_ext(offs_t offset) return m_paletteram_ext.read16(offset); } +u32 palette_device::read32_ext(offs_t offset) +{ + return m_paletteram_ext.read32(offset); +} //------------------------------------------------- // write_indirect - write a byte to the base diff --git a/src/emu/emupal.h b/src/emu/emupal.h index cb4884a72fb..17deb2b0903 100644 --- a/src/emu/emupal.h +++ b/src/emu/emupal.h @@ -210,11 +210,13 @@ public: enum xrgb_333_t { xRGB_333, xxxxxxxRRRGGGBBB }; enum xrbg_333_t { xRBG_333, xxxxxxxRRRBBBGGG }; enum xbgr_333_t { xBGR_333, xxxxxxxBBBGGGRRR }; + enum xbgr_333_nib_t { xBGR_333_nibble, xxxxxBBBxGGGxRRR }; enum xrgb_444_t { xRGB_444, xxxxRRRRGGGGBBBB }; enum xrbg_444_t { xRBG_444, xxxxRRRRBBBBGGGG }; enum xbrg_444_t { xBRG_444, xxxxBBBBRRRRGGGG }; enum xbgr_444_t { xBGR_444, xxxxBBBBGGGGRRRR }; enum rgbx_444_t { RGBx_444, RRRRGGGGBBBBxxxx }; + enum grbx_444_t { GRBx_444, GGGGRRRRBBBBxxxx }; enum gbrx_444_t { GBRx_444, GGGGBBBBRRRRxxxx }; enum irgb_4444_t { IRGB_4444, IIIIRRRRGGGGBBBB }; enum rgbi_4444_t { RGBI_4444, RRRRGGGGBBBBIIII }; @@ -296,11 +298,13 @@ public: palette_device &set_format(xrgb_333_t, u32 entries); palette_device &set_format(xrbg_333_t, u32 entries); palette_device &set_format(xbgr_333_t, u32 entries); + palette_device &set_format(xbgr_333_nib_t, u32 entries); palette_device &set_format(xrgb_444_t, u32 entries); palette_device &set_format(xrbg_444_t, u32 entries); palette_device &set_format(xbrg_444_t, u32 entries); palette_device &set_format(xbgr_444_t, u32 entries); palette_device &set_format(rgbx_444_t, u32 entries); + palette_device &set_format(grbx_444_t, u32 entries); palette_device &set_format(gbrx_444_t, u32 entries); palette_device &set_format(irgb_4444_t, u32 entries); palette_device &set_format(rgbi_4444_t, u32 entries); @@ -363,14 +367,16 @@ public: void write16(offs_t offset, u16 data, u16 mem_mask = u16(~0)); void write16_ext(offs_t offset, u16 data, u16 mem_mask = u16(~0)); u32 read32(offs_t offset); + u32 read32_ext(offs_t offset); void write32(offs_t offset, u32 data, u32 mem_mask = u32(~0)); + void write32_ext(offs_t offset, u32 data, u32 mem_mask = u32(~0)); // helper to update palette when data changed void update() { if (!m_init.isnull()) m_init(*this); } protected: // device-level overrides - virtual void device_start() override; + virtual void device_start() override ATTR_COLD; // device_palette_interface overrides virtual u32 palette_entries() const noexcept override { return m_entries; } diff --git a/src/emu/fileio.cpp b/src/emu/fileio.cpp index 23426cb61fa..c0341b93368 100644 --- a/src/emu/fileio.cpp +++ b/src/emu/fileio.cpp @@ -14,6 +14,8 @@ #include "util/path.h" #include "util/unzip.h" +#include <tuple> + //#define VERBOSE 1 #define LOG_OUTPUT_FUNC osd_printf_verbose #include "logmacro.h" @@ -531,9 +533,10 @@ u32 emu_file::read(void *buffer, u32 length) return 0; // read the data if we can + std::error_condition err; size_t actual = 0; if (m_file) - m_file->read(buffer, length, actual); + std::tie(err, actual) = util::read(*m_file, buffer, length); return actual; } @@ -601,9 +604,10 @@ u32 emu_file::write(const void *buffer, u32 length) { // FIXME: need better interface to report errors // write the data if we can + std::error_condition err; size_t actual = 0; if (m_file) - m_file->write(buffer, length, actual); + std::tie(err, actual) = util::write(*m_file, buffer, length); return actual; } diff --git a/src/emu/gamedrv.h b/src/emu/gamedrv.h index 4f0050e2a19..b3a1087a66a 100644 --- a/src/emu/gamedrv.h +++ b/src/emu/gamedrv.h @@ -40,16 +40,13 @@ struct machine_flags ROT180 = FLIP_X | FLIP_Y, ROT270 = FLIP_Y | SWAP_XY, - NOT_WORKING = 0x0000'0040, - SUPPORTS_SAVE = 0x0000'0080, // system supports save states - NO_COCKTAIL = 0x0000'0100, // screen flip support is missing - IS_BIOS_ROOT = 0x0000'0200, // this driver entry is a BIOS root - REQUIRES_ARTWORK = 0x0000'0400, // requires external artwork for key game elements - CLICKABLE_ARTWORK = 0x0000'0800, // artwork is clickable and requires mouse cursor - UNOFFICIAL = 0x0000'1000, // unofficial hardware modification - NO_SOUND_HW = 0x0000'2000, // system has no sound output - MECHANICAL = 0x0000'4000, // contains mechanical parts (pinball, redemption games, ...) - IS_INCOMPLETE = 0x0000'8000 // official system with blatantly incomplete hardware/software + NO_COCKTAIL = 0x0000'0040, // screen flip support is missing + IS_BIOS_ROOT = 0x0000'0080, // this driver entry is a BIOS root + REQUIRES_ARTWORK = 0x0000'0100, // requires external artwork for key game elements + UNOFFICIAL = 0x0000'0200, // unofficial hardware modification + NO_SOUND_HW = 0x0000'0400, // system has no sound output + MECHANICAL = 0x0000'0800, // contains mechanical parts (pinball, redemption games, ...) + IS_INCOMPLETE = 0x0000'1000 // official system with blatantly incomplete hardware/software }; }; @@ -66,33 +63,28 @@ DECLARE_ENUM_BITWISE_OPERATORS(machine_flags::type); /// \{ // flags for machine drivers -constexpr u64 MACHINE_NOT_WORKING = machine_flags::NOT_WORKING; ///< Imperfect emulation prevents using the system as intended -constexpr u64 MACHINE_SUPPORTS_SAVE = machine_flags::SUPPORTS_SAVE; ///< All devices in the system supports save states (enables auto save feature, and won't show a warning on using save states) constexpr u64 MACHINE_NO_COCKTAIL = machine_flags::NO_COCKTAIL; ///< The system supports screen flipping for use in a cocktail cabinet, but this feature is not properly emulated constexpr u64 MACHINE_IS_BIOS_ROOT = machine_flags::IS_BIOS_ROOT; ///< The system represents an empty system board of some kind - clones are treated as separate systems rather than variants constexpr u64 MACHINE_REQUIRES_ARTWORK = machine_flags::REQUIRES_ARTWORK; ///< The system requires external artwork for key functionality -constexpr u64 MACHINE_CLICKABLE_ARTWORK = machine_flags::CLICKABLE_ARTWORK; ///< Enables pointer display for the system to facilitate using clickable artwork constexpr u64 MACHINE_UNOFFICIAL = machine_flags::UNOFFICIAL; ///< The system represents an after-market or end-user modification to a system constexpr u64 MACHINE_NO_SOUND_HW = machine_flags::NO_SOUND_HW; ///< The system has no sound output capability constexpr u64 MACHINE_MECHANICAL = machine_flags::MECHANICAL; ///< The system depends on mechanical features for key functionality constexpr u64 MACHINE_IS_INCOMPLETE = machine_flags::IS_INCOMPLETE; ///< The system represents an incomplete prototype -// flags that map to device feature flags -constexpr u64 MACHINE_UNEMULATED_PROTECTION = 0x00000001'00000000; ///< Some form of protection is imperfectly emulated (e.g. copy protection or anti-tampering) -constexpr u64 MACHINE_WRONG_COLORS = 0x00000002'00000000; ///< Colours are completely wrong -constexpr u64 MACHINE_IMPERFECT_COLORS = 0x00000004'00000000; ///< Colours are close but not completely accurate -constexpr u64 MACHINE_IMPERFECT_GRAPHICS = 0x00000008'00000000; ///< Graphics are emulated incorrectly for the system -constexpr u64 MACHINE_NO_SOUND = 0x00000010'00000000; ///< The system has sound output, but it is not emulated -constexpr u64 MACHINE_IMPERFECT_SOUND = 0x00000020'00000000; ///< Sound is known to be imperfectly emulated for the system -constexpr u64 MACHINE_IMPERFECT_CONTROLS = 0x00000040'00000000; ///< Controls or inputs are emulated imperfectly for the system -constexpr u64 MACHINE_NODEVICE_MICROPHONE = 0x00000080'00000000; ///< The system has unemulated audio capture functionality -constexpr u64 MACHINE_NODEVICE_PRINTER = 0x00000100'00000000; ///< The system has unemulated printer functionality -constexpr u64 MACHINE_NODEVICE_LAN = 0x00000200'00000000; ///< The system has unemulated local area networking -constexpr u64 MACHINE_IMPERFECT_TIMING = 0x00000400'00000000; ///< Timing is known to be imperfectly emulated for the system - -// useful combinations of flags -constexpr u64 MACHINE_IS_SKELETON = MACHINE_NO_SOUND | MACHINE_NOT_WORKING; ///< Useful combination of flags for preliminary systems -constexpr u64 MACHINE_IS_SKELETON_MECHANICAL = MACHINE_IS_SKELETON | MACHINE_MECHANICAL | MACHINE_REQUIRES_ARTWORK; // flag combination for skeleton mechanical machines +// flags that map to device emulation and feature flags +constexpr u64 MACHINE_NOT_WORKING = 0x00000001'00000000; ///< Imperfect emulation prevents using the system as intended +constexpr u64 MACHINE_SUPPORTS_SAVE = 0x00000002'00000000; ///< All devices in the system supports save states (enables auto save feature, and won't show a warning on using save states) +constexpr u64 MACHINE_UNEMULATED_PROTECTION = 0x00000004'00000000; ///< Some form of protection is imperfectly emulated (e.g. copy protection or anti-tampering) +constexpr u64 MACHINE_WRONG_COLORS = 0x00000008'00000000; ///< Colours are completely wrong +constexpr u64 MACHINE_IMPERFECT_COLORS = 0x00000010'00000000; ///< Colours are close but not completely accurate +constexpr u64 MACHINE_IMPERFECT_GRAPHICS = 0x00000020'00000000; ///< Graphics are emulated incorrectly for the system +constexpr u64 MACHINE_NO_SOUND = 0x00000040'00000000; ///< The system has sound output, but it is not emulated +constexpr u64 MACHINE_IMPERFECT_SOUND = 0x00000080'00000000; ///< Sound is known to be imperfectly emulated for the system +constexpr u64 MACHINE_IMPERFECT_CONTROLS = 0x00000100'00000000; ///< Controls or inputs are emulated imperfectly for the system +constexpr u64 MACHINE_NODEVICE_MICROPHONE = 0x00000200'00000000; ///< The system has unemulated audio capture functionality +constexpr u64 MACHINE_NODEVICE_PRINTER = 0x00000400'00000000; ///< The system has unemulated printer functionality +constexpr u64 MACHINE_NODEVICE_LAN = 0x00000800'00000000; ///< The system has unemulated local area networking +constexpr u64 MACHINE_IMPERFECT_TIMING = 0x00001000'00000000; ///< Timing is known to be imperfectly emulated for the system /// \} /// \} @@ -113,6 +105,20 @@ public: typedef void (*machine_creator_wrapper)(machine_config &, device_t &); typedef void (*driver_init_wrapper)(device_t &); + /// \brief Get emulation flags + /// + /// Converts system flags corresponding to device emulation flags to + /// a device flags type bit field. + /// \param [in] flags A system flags bit field. + /// \return A device flags type bit field corresponding to emulation + /// flags declared in the \p flags argument. + static constexpr device_t::flags_type emulation_flags(u64 flags) + { + return + ((flags & MACHINE_NOT_WORKING) ? device_t::flags::NOT_WORKING : device_t::flags::NONE) | + ((flags & MACHINE_SUPPORTS_SAVE) ? device_t::flags::NONE : device_t::flags::SAVE_UNSUPPORTED); + } + /// \brief Get unemulated system features /// /// Converts system flags corresponding to unemulated device @@ -175,18 +181,19 @@ public: // static game traits #define GAME_DRIVER_TRAITS(NAME, FULLNAME) \ -namespace { \ - struct GAME_TRAITS_NAME(NAME) { static constexpr char const shortname[] = #NAME, fullname[] = FULLNAME, source[] = __FILE__; }; \ - constexpr char const GAME_TRAITS_NAME(NAME)::shortname[], GAME_TRAITS_NAME(NAME)::fullname[], GAME_TRAITS_NAME(NAME)::source[]; \ -} + namespace { \ + struct GAME_TRAITS_NAME(NAME) { static constexpr char const shortname[] = #NAME, fullname[] = FULLNAME, source[] = __FILE__; }; \ + constexpr char const GAME_TRAITS_NAME(NAME)::shortname[], GAME_TRAITS_NAME(NAME)::fullname[], GAME_TRAITS_NAME(NAME)::source[]; \ + } #define GAME_DRIVER_TYPE(NAME, CLASS, FLAGS) \ -driver_device_creator< \ - CLASS, \ - (GAME_TRAITS_NAME(NAME)::shortname), \ - (GAME_TRAITS_NAME(NAME)::fullname), \ - (GAME_TRAITS_NAME(NAME)::source), \ - game_driver::unemulated_features(FLAGS), \ - game_driver::imperfect_features(FLAGS)> + emu::detail::driver_tag_struct< \ + CLASS, \ + (GAME_TRAITS_NAME(NAME)::shortname), \ + (GAME_TRAITS_NAME(NAME)::fullname), \ + (GAME_TRAITS_NAME(NAME)::source), \ + game_driver::emulation_flags(FLAGS), \ + game_driver::unemulated_features(FLAGS), \ + game_driver::imperfect_features(FLAGS)>{ } /// \addtogroup machinedef diff --git a/src/emu/http.cpp b/src/emu/http.cpp index 8c95e731552..2a30a13dba3 100644 --- a/src/emu/http.cpp +++ b/src/emu/http.cpp @@ -144,28 +144,28 @@ public: virtual ~http_request_impl() = default; /** Retrieves the requested resource. */ - virtual const std::string get_resource() { + virtual const std::string get_resource() override { // The entire resource: path, query and fragment. return m_request->path; } /** Returns the path part of the requested resource. */ - virtual const std::string get_path() { + virtual const std::string get_path() override { return m_request->path.substr(0, m_path_end); } /** Returns the query part of the requested resource. */ - virtual const std::string get_query() { + virtual const std::string get_query() override { return m_query == std::string::npos ? "" : m_request->path.substr(m_query, m_query_end); } /** Returns the fragment part of the requested resource. */ - virtual const std::string get_fragment() { + virtual const std::string get_fragment() override { return m_fragment == std::string::npos ? "" : m_request->path.substr(m_fragment); } /** Retrieves a header from the HTTP request. */ - virtual const std::string get_header(const std::string &header_name) { + virtual const std::string get_header(const std::string &header_name) override { auto i = m_request->header.find(header_name); if (i != m_request->header.end()) { return (*i).second; @@ -175,7 +175,7 @@ public: } /** Retrieves a header from the HTTP request. */ - virtual const std::list<std::string> get_headers(const std::string &header_name) { + virtual const std::list<std::string> get_headers(const std::string &header_name) override { std::list<std::string> result; auto range = m_request->header.equal_range(header_name); for (auto i = range.first; i != range.second; i++) { @@ -185,7 +185,7 @@ public: } /** Returns the body that was submitted with the HTTP request. */ - virtual const std::string get_body() { + virtual const std::string get_body() override { // TODO(cbrunschen): What to return here - http_server::Request has a 'content' feld that is never filled in! return ""; } @@ -204,23 +204,23 @@ struct http_response_impl : public http_manager::http_response { virtual ~http_response_impl() = default; /** Sets the HTTP status to be returned to the client. */ - virtual void set_status(int status) { + virtual void set_status(int status) override { m_status = status; } /** Sets the HTTP content type to be returned to the client. */ - virtual void set_content_type(const std::string &content_type) { + virtual void set_content_type(const std::string &content_type) override { m_content_type = content_type; } /** Sets the body to be sent to the client. */ - virtual void set_body(const std::string &body) { + virtual void set_body(const std::string &body) override { m_body.str(""); append_body(body); } /** Appends something to the body to be sent to the client. */ - virtual void append_body(const std::string &body) { + virtual void append_body(const std::string &body) override { m_body << body; } @@ -258,7 +258,7 @@ struct websocket_connection_impl : public http_manager::websocket_connection { : m_wsserver(server), m_connection(connection) { } /** Sends a message to the client that is connected on the other end of this Websocket connection. */ - virtual void send_message(const std::string &payload, int opcode) { + virtual void send_message(const std::string &payload, int opcode) override { if (auto connection = m_connection.lock()) { std::shared_ptr<webpp::ws_server::SendStream> message_stream = std::make_shared<webpp::ws_server::SendStream>(); (*message_stream) << payload; @@ -267,7 +267,7 @@ struct websocket_connection_impl : public http_manager::websocket_connection { } /** Closes this open Websocket connection. */ - virtual void close() { + virtual void close() override { if (auto connection = m_connection.lock()) { m_wsserver->send_close(connection, 1000 /* normal close */); } diff --git a/src/emu/image.cpp b/src/emu/image.cpp index 3839a57549b..52e553953a7 100644 --- a/src/emu/image.cpp +++ b/src/emu/image.cpp @@ -181,24 +181,22 @@ void image_manager::config_save(config_type cfg_type, util::xml::data_node *pare int image_manager::write_config(emu_options &options, const char *filename, const game_driver *gamedrv) { - char buffer[128]; - int retval = 1; - - if (gamedrv != nullptr) + std::string buffer; + if (gamedrv) { - sprintf(buffer, "%s.ini", gamedrv->name); - filename = buffer; + buffer.reserve(strlen(gamedrv->name) + 4); + buffer = gamedrv->name; + buffer += ".ini"; + filename = buffer.c_str(); } emu_file file(options.ini_path(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE); std::error_condition const filerr = file.open(filename); - if (!filerr) - { - std::string inistring = options.output_ini(); - file.puts(inistring); - retval = 0; - } - return retval; + if (filerr) + return 1; + + file.puts(options.output_ini()); + return 0; } /*------------------------------------------------- diff --git a/src/emu/inpttype.h b/src/emu/inpttype.h index de5b42d0a77..92d4cbd2be1 100644 --- a/src/emu/inpttype.h +++ b/src/emu/inpttype.h @@ -206,6 +206,7 @@ enum ioport_type : osd::u32 IPT_SLOT_STOP2, IPT_SLOT_STOP3, IPT_SLOT_STOP4, + IPT_SLOT_STOP5, IPT_SLOT_STOP_ALL, IPT_GAMBLING_LAST, @@ -268,7 +269,9 @@ enum ioport_type : osd::u32 IPT_UI_PAUSE_SINGLE, IPT_UI_REWIND_SINGLE, IPT_UI_SAVE_STATE, + IPT_UI_SAVE_STATE_QUICK, IPT_UI_LOAD_STATE, + IPT_UI_LOAD_STATE_QUICK, IPT_UI_RESET_MACHINE, IPT_UI_SOFT_RESET, IPT_UI_SHOW_GFX, @@ -297,6 +300,8 @@ enum ioport_type : osd::u32 IPT_UI_FAVORITES, IPT_UI_EXPORT, IPT_UI_AUDIT, + IPT_UI_MIXER_ADD_FULL, + IPT_UI_MIXER_ADD_CHANNEL, // additional OSD-specified UI port types (up to 16) IPT_OSD_1, diff --git a/src/emu/inpttype.ipp b/src/emu/inpttype.ipp index e5cb81c45fc..c51afa8ce31 100644 --- a/src/emu/inpttype.ipp +++ b/src/emu/inpttype.ipp @@ -132,6 +132,7 @@ namespace { INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, SLOT_STOP2, N_p("input-name", "Stop Reel 2"), input_seq(KEYCODE_C) ) \ INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, SLOT_STOP3, N_p("input-name", "Stop Reel 3"), input_seq(KEYCODE_V) ) \ INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, SLOT_STOP4, N_p("input-name", "Stop Reel 4"), input_seq(KEYCODE_B) ) \ + INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, SLOT_STOP5, N_p("input-name", "Stop Reel 5"), input_seq(KEYCODE_N) ) \ INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, SLOT_STOP_ALL, N_p("input-name", "Stop All Reels"), input_seq(KEYCODE_Z) ) \ CORE_INPUT_TYPES_END() @@ -887,18 +888,20 @@ namespace { INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_PAGE_DOWN, N_p("input-name", "UI Page Down"), input_seq(KEYCODE_PGDN) ) \ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_PREV_GROUP, N_p("input-name", "UI Previous Group"), input_seq(KEYCODE_OPENBRACE) ) \ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_NEXT_GROUP, N_p("input-name", "UI Next Group"), input_seq(KEYCODE_CLOSEBRACE) ) \ - INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_ON_SCREEN_DISPLAY, N_p("input-name", "On Screen Display"), input_seq(KEYCODE_TILDE, input_seq::not_code, KEYCODE_LSHIFT, input_seq::not_code, KEYCODE_RSHIFT) ) \ + INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_ON_SCREEN_DISPLAY, N_p("input-name", "On Screen Display"), input_seq(KEYCODE_TILDE) ) \ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_TOGGLE_UI, N_p("input-name", "Toggle UI Controls"), input_seq(KEYCODE_SCRLOCK, input_seq::not_code, KEYCODE_LSHIFT, input_seq::not_code, KEYCODE_RSHIFT) ) \ - INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_DEBUG_BREAK, N_p("input-name", "Break in Debugger"), input_seq(KEYCODE_TILDE, input_seq::not_code, KEYCODE_LSHIFT, input_seq::not_code, KEYCODE_RSHIFT) ) \ - INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_PAUSE, N_p("input-name", "Pause"), input_seq(KEYCODE_P, input_seq::not_code, KEYCODE_LSHIFT, input_seq::not_code, KEYCODE_RSHIFT) ) \ - INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_PAUSE_SINGLE, N_p("input-name", "Pause - Single Step"), input_seq(KEYCODE_P, KEYCODE_LSHIFT, input_seq::or_code, KEYCODE_P, KEYCODE_RSHIFT) ) \ - INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_REWIND_SINGLE, N_p("input-name", "Rewind - Single Step"), input_seq(KEYCODE_TILDE, KEYCODE_LSHIFT) ) \ - INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_SAVE_STATE, N_p("input-name", "Save State"), input_seq(KEYCODE_F7, KEYCODE_LSHIFT, input_seq::or_code, KEYCODE_F7, KEYCODE_RSHIFT) ) \ + INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_DEBUG_BREAK, N_p("input-name", "Break in Debugger"), input_seq(KEYCODE_TILDE) ) \ + INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_PAUSE, N_p("input-name", "Pause"), input_seq(KEYCODE_F5, input_seq::not_code, KEYCODE_LSHIFT, input_seq::not_code, KEYCODE_RSHIFT) ) \ + INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_PAUSE_SINGLE, N_p("input-name", "Pause - Single Step"), input_seq(KEYCODE_F5, KEYCODE_LSHIFT, input_seq::or_code, KEYCODE_F5, KEYCODE_RSHIFT) ) \ + INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_REWIND_SINGLE, N_p("input-name", "Rewind - Single Step"), input_seq(KEYCODE_F4, KEYCODE_LSHIFT, input_seq::or_code, KEYCODE_F4, KEYCODE_RSHIFT) ) \ + INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_SAVE_STATE, N_p("input-name", "Save State"), input_seq(KEYCODE_F6, input_seq::not_code, KEYCODE_LSHIFT, input_seq::not_code, KEYCODE_RSHIFT) ) \ + INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_SAVE_STATE_QUICK, N_p("input-name", "Quick Save State"), input_seq(KEYCODE_F6, KEYCODE_LSHIFT, input_seq::or_code, KEYCODE_F6, KEYCODE_RSHIFT) ) \ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_LOAD_STATE, N_p("input-name", "Load State"), input_seq(KEYCODE_F7, input_seq::not_code, KEYCODE_LSHIFT, input_seq::not_code, KEYCODE_RSHIFT) ) \ + INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_LOAD_STATE_QUICK, N_p("input-name", "Quick Load State"), input_seq(KEYCODE_F7, KEYCODE_LSHIFT, input_seq::or_code, KEYCODE_F7, KEYCODE_RSHIFT) ) \ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_RESET_MACHINE, N_p("input-name", "Reset Machine"), input_seq(KEYCODE_F3, KEYCODE_LSHIFT, input_seq::or_code, KEYCODE_F3, KEYCODE_RSHIFT) ) \ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_SOFT_RESET, N_p("input-name", "Soft Reset"), input_seq(KEYCODE_F3, input_seq::not_code, KEYCODE_LSHIFT, input_seq::not_code, KEYCODE_RSHIFT) ) \ - INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_SHOW_GFX, N_p("input-name", "Show Decoded Graphics"), input_seq(KEYCODE_F4) ) \ - INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_FRAMESKIP_DEC, N_p("input-name", "Frameskip Dec"), input_seq(KEYCODE_F8) ) \ + INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_SHOW_GFX, N_p("input-name", "Show Decoded Graphics"), input_seq(KEYCODE_F4, input_seq::not_code, KEYCODE_LSHIFT, input_seq::not_code, KEYCODE_RSHIFT) ) \ + INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_FRAMESKIP_DEC, N_p("input-name", "Frameskip Dec"), input_seq(KEYCODE_F8, input_seq::not_code, KEYCODE_LSHIFT, input_seq::not_code, KEYCODE_RSHIFT) ) \ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_FRAMESKIP_INC, N_p("input-name", "Frameskip Inc"), input_seq(KEYCODE_F9) ) \ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_THROTTLE, N_p("input-name", "Throttle"), input_seq(KEYCODE_F10) ) \ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_FAST_FORWARD, N_p("input-name", "Fast Forward"), input_seq(KEYCODE_INSERT) ) \ @@ -906,7 +909,7 @@ namespace { INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_SNAPSHOT, N_p("input-name", "Save Snapshot"), input_seq(KEYCODE_F12, input_seq::not_code, KEYCODE_LSHIFT) ) \ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_RECORD_MNG, N_p("input-name", "Record MNG"), input_seq(KEYCODE_F12, KEYCODE_LSHIFT, input_seq::not_code, KEYCODE_LCONTROL) ) \ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_RECORD_AVI, N_p("input-name", "Record AVI"), input_seq(KEYCODE_F12, KEYCODE_LSHIFT, KEYCODE_LCONTROL) ) \ - INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_TOGGLE_CHEAT, N_p("input-name", "Toggle Cheat"), input_seq(KEYCODE_F6) ) \ + INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_TOGGLE_CHEAT, N_p("input-name", "Toggle Cheat"), input_seq(KEYCODE_F8, KEYCODE_LSHIFT, input_seq::or_code, KEYCODE_F8, KEYCODE_RSHIFT) ) \ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_DISPLAY_COMMENT, N_p("input-name", "UI Display Comment"), input_seq(KEYCODE_SPACE) ) \ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_ZOOM_IN, N_p("input-name", "UI Zoom In"), input_seq(KEYCODE_EQUALS) ) \ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_ZOOM_OUT, N_p("input-name", "UI Zoom Out"), input_seq(KEYCODE_MINUS) ) \ @@ -923,6 +926,8 @@ namespace { INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_FAVORITES, N_p("input-name", "UI Add/Remove Favorite"), input_seq(KEYCODE_LALT, KEYCODE_F) ) \ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_EXPORT, N_p("input-name", "UI Export List"), input_seq(KEYCODE_LALT, KEYCODE_E) ) \ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_AUDIT, N_p("input-name", "UI Audit Media"), input_seq(KEYCODE_F1, input_seq::not_code, KEYCODE_LSHIFT, input_seq::not_code, KEYCODE_RSHIFT) ) \ + INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_MIXER_ADD_FULL, N_p("input-name", "UI Audio add full mapping"), input_seq(KEYCODE_F) ) \ + INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_MIXER_ADD_CHANNEL, N_p("input-name", "UI Audio add channel mapping"), input_seq(KEYCODE_C) ) \ CORE_INPUT_TYPES_END() #define CORE_INPUT_TYPES_OSD \ diff --git a/src/emu/input.cpp b/src/emu/input.cpp index 9e82f290bf7..5eeee806f9d 100644 --- a/src/emu/input.cpp +++ b/src/emu/input.cpp @@ -35,17 +35,19 @@ namespace { // simple class to match codes to strings struct code_string_table { + static inline constexpr u32 SENTINEL = ~u32(0); + u32 operator[](std::string_view string) const { - for (const code_string_table *current = this; current->m_code != ~0; current++) + for (const code_string_table *current = this; current->m_code != SENTINEL; current++) if (current->m_string == string) return current->m_code; - return ~0; + return SENTINEL; } const char *operator[](u32 code) const { - for (const code_string_table *current = this; current->m_code != ~0; current++) + for (const code_string_table *current = this; current->m_code != SENTINEL; current++) if (current->m_code == code) return current->m_string; return nullptr; @@ -64,56 +66,56 @@ struct code_string_table // token strings for device classes const code_string_table devclass_token_table[] = { - { DEVICE_CLASS_KEYBOARD, "KEYCODE" }, - { DEVICE_CLASS_MOUSE, "MOUSECODE" }, - { DEVICE_CLASS_LIGHTGUN, "GUNCODE" }, - { DEVICE_CLASS_JOYSTICK, "JOYCODE" }, - { ~0U, "UNKCODE" } + { DEVICE_CLASS_KEYBOARD, "KEYCODE" }, + { DEVICE_CLASS_MOUSE, "MOUSECODE" }, + { DEVICE_CLASS_LIGHTGUN, "GUNCODE" }, + { DEVICE_CLASS_JOYSTICK, "JOYCODE" }, + { code_string_table::SENTINEL, "UNKCODE" } }; // friendly strings for device classes const code_string_table devclass_string_table[] = { - { DEVICE_CLASS_KEYBOARD, "Kbd" }, - { DEVICE_CLASS_MOUSE, "Mouse" }, - { DEVICE_CLASS_LIGHTGUN, "Gun" }, - { DEVICE_CLASS_JOYSTICK, "Joy" }, - { ~0U, "Unk" } + { DEVICE_CLASS_KEYBOARD, "Kbd" }, + { DEVICE_CLASS_MOUSE, "Mouse" }, + { DEVICE_CLASS_LIGHTGUN, "Gun" }, + { DEVICE_CLASS_JOYSTICK, "Joy" }, + { code_string_table::SENTINEL, "Unk" } }; // token strings for item modifiers const code_string_table modifier_token_table[] = { - { ITEM_MODIFIER_REVERSE, "REVERSE" }, - { ITEM_MODIFIER_POS, "POS" }, - { ITEM_MODIFIER_NEG, "NEG" }, - { ITEM_MODIFIER_LEFT, "LEFT" }, - { ITEM_MODIFIER_RIGHT, "RIGHT" }, - { ITEM_MODIFIER_UP, "UP" }, - { ITEM_MODIFIER_DOWN, "DOWN" }, - { ~0U, "" } + { ITEM_MODIFIER_REVERSE, "REVERSE" }, + { ITEM_MODIFIER_POS, "POS" }, + { ITEM_MODIFIER_NEG, "NEG" }, + { ITEM_MODIFIER_LEFT, "LEFT" }, + { ITEM_MODIFIER_RIGHT, "RIGHT" }, + { ITEM_MODIFIER_UP, "UP" }, + { ITEM_MODIFIER_DOWN, "DOWN" }, + { code_string_table::SENTINEL, "" } }; // friendly strings for item modifiers const code_string_table modifier_string_table[] = { - { ITEM_MODIFIER_REVERSE, "Reverse" }, - { ITEM_MODIFIER_POS, "+" }, - { ITEM_MODIFIER_NEG, "-" }, - { ITEM_MODIFIER_LEFT, "Left" }, - { ITEM_MODIFIER_RIGHT, "Right" }, - { ITEM_MODIFIER_UP, "Up" }, - { ITEM_MODIFIER_DOWN, "Down" }, - { ~0U, "" } + { ITEM_MODIFIER_REVERSE, "Reverse" }, + { ITEM_MODIFIER_POS, "+" }, + { ITEM_MODIFIER_NEG, "-" }, + { ITEM_MODIFIER_LEFT, "Left" }, + { ITEM_MODIFIER_RIGHT, "Right" }, + { ITEM_MODIFIER_UP, "Up" }, + { ITEM_MODIFIER_DOWN, "Down" }, + { code_string_table::SENTINEL, "" } }; // token strings for item classes const code_string_table itemclass_token_table[] = { - { ITEM_CLASS_SWITCH, "SWITCH" }, - { ITEM_CLASS_ABSOLUTE, "ABSOLUTE" }, - { ITEM_CLASS_RELATIVE, "RELATIVE" }, - { ~0U, "" } + { ITEM_CLASS_SWITCH, "SWITCH" }, + { ITEM_CLASS_ABSOLUTE, "ABSOLUTE" }, + { ITEM_CLASS_RELATIVE, "RELATIVE" }, + { code_string_table::SENTINEL, "" } }; // token strings for standard item ids @@ -351,7 +353,7 @@ const code_string_table itemid_token_table[] = { ITEM_ID_ADD_RELATIVE15,"ADDREL15" }, { ITEM_ID_ADD_RELATIVE16,"ADDREL16" }, - { ~0U, nullptr } + { code_string_table::SENTINEL, nullptr } }; @@ -696,7 +698,7 @@ std::string input_manager::code_to_token(input_code code) const // determine the devclass part const char *devclass = (*devclass_token_table)[code.device_class()]; - if (devclass == nullptr) + if (!devclass) return "INVALID"; // determine the devindex part; keyboard 0 doesn't show an index @@ -754,8 +756,8 @@ input_code input_manager::code_from_token(std::string_view _token) // first token should be the devclass int curtok = 0; - input_device_class devclass = input_device_class((*devclass_token_table)[token[curtok++]]); - if (devclass == ~input_device_class(0)) + input_device_class const devclass = input_device_class((*devclass_token_table)[token[curtok++]]); + if (devclass == input_device_class(code_string_table::SENTINEL)) return INPUT_CODE_INVALID; // second token might be index; look for number @@ -770,26 +772,28 @@ input_code input_manager::code_from_token(std::string_view _token) // next token is the item ID input_item_id itemid = input_item_id((*itemid_token_table)[token[curtok]]); - bool standard = (itemid != ~input_item_id(0)); + bool const standard = (itemid != input_item_id(code_string_table::SENTINEL)); - // if we're a standard code, default the itemclass based on it input_item_class itemclass = ITEM_CLASS_INVALID; if (standard) + { + // if we're a standard code, default the itemclass based on it itemclass = m_class[devclass]->standard_item_class(itemid); - - // otherwise, keep parsing + } else { + // otherwise, keep parsing + // if this is an invalid device, we have nothing to look up input_device *device = m_class[devclass]->device(devindex); - if (device == nullptr) + if (!device) return INPUT_CODE_INVALID; // if not a standard code, look it up in the device specific codes for (itemid = ITEM_ID_FIRST_VALID; itemid <= device->maxitem(); ++itemid) { input_device_item *item = device->item(itemid); - if (item != nullptr && token[curtok].compare(item->token()) == 0) + if (item && !token[curtok].compare(item->token())) { // take the itemclass from the item itemclass = item->itemclass(); @@ -808,7 +812,7 @@ input_code input_manager::code_from_token(std::string_view _token) if (curtok < numtokens) { modifier = input_item_modifier((*modifier_token_table)[token[curtok]]); - if (modifier != ~input_item_modifier(0)) + if (modifier != input_item_modifier(code_string_table::SENTINEL)) curtok++; else modifier = ITEM_MODIFIER_NONE; @@ -817,8 +821,8 @@ input_code input_manager::code_from_token(std::string_view _token) // if we have another token, it is the item class if (curtok < numtokens) { - u32 temp = (*itemclass_token_table)[token[curtok]]; - if (temp != ~0) + u32 const temp = (*itemclass_token_table)[token[curtok]]; + if (temp != code_string_table::SENTINEL) { curtok++; itemclass = input_item_class(temp); @@ -841,7 +845,7 @@ input_code input_manager::code_from_token(std::string_view _token) const char *input_manager::standard_token(input_item_id itemid) const { - return itemid <= ITEM_ID_MAXIMUM ? (*itemid_token_table)[itemid] : nullptr; + return (itemid <= ITEM_ID_MAXIMUM) ? (*itemid_token_table)[itemid] : nullptr; } @@ -1209,8 +1213,8 @@ bool input_manager::map_device_to_controller(const devicemap_table &table) return false; // first token should be the devclass - input_device_class devclass = input_device_class((*devclass_token_table)[strmakeupper(token[0])]); - if (devclass == ~input_device_class(0)) + input_device_class const devclass = input_device_class((*devclass_token_table)[strmakeupper(token[0])]); + if (devclass == input_device_class(code_string_table::SENTINEL)) return false; // second token should be the devindex diff --git a/src/emu/ioport.cpp b/src/emu/ioport.cpp index 77e42c100c1..712df85db29 100644 --- a/src/emu/ioport.cpp +++ b/src/emu/ioport.cpp @@ -32,6 +32,7 @@ #include <algorithm> #include <cctype> #include <ctime> +#include <sstream> namespace { @@ -237,6 +238,19 @@ const struct { INPUT_STRING_None, "None" }, }; +const char *const input_gm_notes_names[128] = { + "C-1", "C-1#", "D-1", "D-1#", "E-1", "F-1", "F-1#", "G-1", "G-1#", "A-1", "A-1#", "B-1", + "C0", "C0#", "D0", "D0#", "E0", "F0", "F0#", "G0", "G0#", "A0", "A0#", "B0", + "C1", "C1#", "D1", "D1#", "E1", "F1", "F1#", "G1", "G1#", "A1", "A1#", "B1", + "C2", "C2#", "D2", "D2#", "E2", "F2", "F2#", "G2", "G2#", "A2", "A2#", "B2", + "C3", "C3#", "D3", "D3#", "E3", "F3", "F3#", "G3", "G3#", "A3", "A3#", "B3", + "C4", "C4#", "D4", "D4#", "E4", "F4", "F4#", "G4", "G4#", "A4", "A4#", "B4", + "C5", "C5#", "D5", "D5#", "E5", "F5", "F5#", "G5", "G5#", "A5", "A5#", "B5", + "C6", "C6#", "D6", "D6#", "E6", "F6", "F6#", "G6", "G6#", "A6", "A6#", "B6", + "C7", "C7#", "D7", "D7#", "E7", "F7", "F7#", "G7", "G7#", "A7", "A7#", "B7", + "C8", "C8#", "D8", "D8#", "E8", "F8", "F8#", "G8", "G8#", "A8", "A8#", "B8", + "C9", "C9#", "D9", "D9#", "E9", "F9", "F9#", "G9" +}; inline bool input_seq_good(running_machine &machine, input_seq const &seq) { @@ -407,16 +421,13 @@ u8 const inp_header::MAGIC[inp_header::OFFS_BASETIME - inp_header::OFFS_MAGIC] = // to the current list //------------------------------------------------- -void ioport_list::append(device_t &device, std::string &errorbuf) +void ioport_list::append(device_t &device, std::ostream &errorbuf) { // no constructor, no list ioport_constructor constructor = device.input_ports(); - if (constructor == nullptr) + if (!constructor) return; - // reset error buffer - errorbuf.clear(); - // detokenize into the list (*constructor)(device, *this, errorbuf); @@ -435,22 +446,22 @@ void ioport_list::append(device_t &device, std::string &errorbuf) // input_type_entry - constructors //------------------------------------------------- -input_type_entry::input_type_entry(ioport_type type, ioport_group group, int player, const char *token, const char *name, input_seq standard) noexcept - : m_type(type), - m_group(group), - m_player(player), - m_token(token), - m_name(name) +input_type_entry::input_type_entry(ioport_type type, ioport_group group, int player, const char *token, const char *name, input_seq standard) noexcept : + m_type(type), + m_group(group), + m_player(player), + m_token(token), + m_name(name) { m_defseq[SEQ_TYPE_STANDARD] = m_seq[SEQ_TYPE_STANDARD] = standard; } -input_type_entry::input_type_entry(ioport_type type, ioport_group group, int player, const char *token, const char *name, input_seq standard, input_seq decrement, input_seq increment) noexcept - : m_type(type), - m_group(group), - m_player(player), - m_token(token), - m_name(name) +input_type_entry::input_type_entry(ioport_type type, ioport_group group, int player, const char *token, const char *name, input_seq standard, input_seq decrement, input_seq increment) noexcept : + m_type(type), + m_group(group), + m_player(player), + m_token(token), + m_name(name) { m_defseq[SEQ_TYPE_STANDARD] = m_seq[SEQ_TYPE_STANDARD] = standard; m_defseq[SEQ_TYPE_INCREMENT] = m_seq[SEQ_TYPE_INCREMENT] = increment; @@ -520,12 +531,12 @@ void input_type_entry::restore_default_seq() noexcept // digital_joystick - constructor //------------------------------------------------- -digital_joystick::digital_joystick(int player, int number) - : m_player(player), - m_number(number), - m_current(0), - m_current4way(0), - m_previous(0) +digital_joystick::digital_joystick(int player, int number) : + m_player(player), + m_number(number), + m_current(0), + m_current4way(0), + m_previous(0) { } @@ -608,6 +619,8 @@ void digital_joystick::frame_update() m_current4way &= ~(UP_BIT | DOWN_BIT); } } + else + m_current4way &= m_current; } @@ -662,10 +675,10 @@ void ioport_condition::initialize(device_t &device) // ioport_setting - constructor //------------------------------------------------- -ioport_setting::ioport_setting(ioport_field &field, ioport_value _value, const char *_name) - : m_field(field), - m_value(_value), - m_name(_name) +ioport_setting::ioport_setting(ioport_field &field, ioport_value _value, const char *_name) : + m_field(field), + m_value(_value), + m_name(_name) { } @@ -679,10 +692,10 @@ ioport_setting::ioport_setting(ioport_field &field, ioport_value _value, const c // ioport_diplocation - constructor //------------------------------------------------- -ioport_diplocation::ioport_diplocation(const char *name, u8 swnum, bool invert) - : m_name(name), - m_number(swnum), - m_invert(invert) +ioport_diplocation::ioport_diplocation(std::string_view name, u8 swnum, bool invert) : + m_name(name), + m_number(swnum), + m_invert(invert) { } @@ -696,41 +709,41 @@ ioport_diplocation::ioport_diplocation(const char *name, u8 swnum, bool invert) // ioport_field - constructor //------------------------------------------------- -ioport_field::ioport_field(ioport_port &port, ioport_type type, ioport_value defvalue, ioport_value maskbits, const char *name) - : m_next(nullptr), - m_port(port), - m_modcount(port.modcount()), - m_mask(maskbits), - m_defvalue(defvalue & maskbits), - m_type(type), - m_player(0), - m_flags(0), - m_impulse(0), - m_name(name), - m_read(port.device()), - m_write(port.device()), - m_write_param(0), - m_digital_value(false), - m_min(0), - m_max(maskbits), - m_sensitivity(0), - m_delta(0), - m_centerdelta(0), - m_crosshair_axis(CROSSHAIR_AXIS_NONE), - m_crosshair_scale(1.0), - m_crosshair_offset(0), - m_crosshair_altaxis(0), - m_crosshair_mapper(port.device()), - m_full_turn_count(0), - m_remap_table(nullptr), - m_way(0) +ioport_field::ioport_field(ioport_port &port, ioport_type type, ioport_value defvalue, ioport_value maskbits, const char *name) : + m_next(nullptr), + m_port(port), + m_modcount(port.modcount()), + m_mask(maskbits), + m_defvalue(defvalue & maskbits), + m_type(type), + m_player(0), + m_flags(0), + m_impulse(0), + m_name(name), + m_read(port.device()), + m_write(port.device()), + m_write_param(0), + m_digital_value(false), + m_min(0), + m_max(maskbits), + m_sensitivity(0), + m_delta(0), + m_centerdelta(0), + m_crosshair_axis(CROSSHAIR_AXIS_NONE), + m_crosshair_scale(1.0), + m_crosshair_offset(0), + m_crosshair_altaxis(0), + m_crosshair_mapper(port.device()), + m_full_turn_count(0), + m_remap_table(nullptr), + m_way(0) { // reset sequences and chars for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; ++seqtype) m_seq[seqtype].set_default(); - for (int i = 0; i < std::size(m_chars); i++) - std::fill(std::begin(m_chars[i]), std::end(m_chars[i]), char32_t(0)); + for (auto &chars : m_chars) + std::fill(std::begin(chars), std::end(chars), UCHAR_INVALID); // for DIP switches and configs, look for a default value from the owner if (type == IPT_DIPSWITCH || type == IPT_CONFIG) @@ -898,11 +911,8 @@ std::vector<char32_t> ioport_field::keyboard_codes(int which) const if (which >= std::size(m_chars)) throw emu_fatalerror("Tried to access keyboard_code with out-of-range index %d\n", which); - std::vector<char32_t> result; - for (int i = 0; i < std::size(m_chars[which]) && m_chars[which][i] != 0; i++) - result.push_back(m_chars[which][i]); - - return result; + auto &chars = m_chars[which]; + return std::vector<char32_t>(std::begin(chars), std::find(std::begin(chars), std::end(chars), UCHAR_INVALID)); } @@ -1346,7 +1356,7 @@ float ioport_field::crosshair_read() const // descriptions //------------------------------------------------- -void ioport_field::expand_diplocation(const char *location, std::string &errorbuf) +void ioport_field::expand_diplocation(const char *location, std::ostream &errorbuf) { // if nothing present, bail if (!location) @@ -1355,71 +1365,76 @@ void ioport_field::expand_diplocation(const char *location, std::string &errorbu m_diploclist.clear(); // parse the string - std::string name; // Don't move this variable inside the loop, lastname's lifetime depends on it being outside - const char *lastname = nullptr; + std::string_view lastname; const char *curentry = location; int entries = 0; - while (*curentry != 0) + while (*curentry) { // find the end of this entry const char *comma = strchr(curentry, ','); - if (comma == nullptr) + if (!comma) comma = curentry + strlen(curentry); // extract it to tempbuf - std::string tempstr(curentry, comma - curentry); + std::string_view tempstr(curentry, comma - curentry); // first extract the switch name if present - const char *number = tempstr.c_str(); - const char *colon = strchr(tempstr.c_str(), ':'); + std::string_view::size_type number = 0; + std::string_view::size_type const colon = tempstr.find(':'); - if (colon != nullptr) + std::string_view name; + if (colon != std::string_view::npos) { // allocate and copy the name if it is present - lastname = name.assign(number, colon - number).c_str(); + lastname = tempstr.substr(0, colon); number = colon + 1; + if (lastname.empty()) + { + util::stream_format(errorbuf, "Switch location '%s' has empty switch name!\n", location); + lastname = "UNK"; + } + name = lastname; } else { // otherwise, just copy the last name - if (lastname == nullptr) + if (lastname.empty()) { - errorbuf.append(string_format("Switch location '%s' missing switch name!\n", location)); - lastname = (char *)"UNK"; + util::stream_format(errorbuf, "Switch location '%s' missing switch name!\n", location); + lastname = "UNK"; } - name.assign(lastname); + name = lastname; } // if the number is preceded by a '!' it's active high - bool invert = false; - if (*number == '!') - { - invert = true; - number++; - } + bool const invert = tempstr[number] == '!'; + if (invert) + ++number; // now scan the switch number int swnum = -1; - if (sscanf(number, "%d", &swnum) != 1) - errorbuf.append(string_format("Switch location '%s' has invalid format!\n", location)); + if (sscanf(&tempstr[number], "%d", &swnum) != 1) + util::stream_format(errorbuf, "Switch location '%s' has invalid format!\n", location); + else if (0 >= swnum) + util::stream_format(errorbuf, "Switch location '%s' has switch number that is not positive!\n", location); // allocate a new entry - m_diploclist.emplace_back(name.c_str(), swnum, invert); + if (0 < swnum) + m_diploclist.emplace_back(name, swnum, invert); entries++; // advance to the next item curentry = comma; - if (*curentry != 0) + if (*curentry) curentry++; } // then verify the number of bits in the mask matches - ioport_value temp; - int bits; - for (bits = 0, temp = m_mask; temp != 0 && bits < 32; bits++) - temp &= temp - 1; - if (bits != entries) - errorbuf.append(string_format("Switch location '%s' does not describe enough bits for mask %X\n", location, m_mask)); + int const bits = population_count_32(m_mask); + if (bits > entries) + util::stream_format(errorbuf, "Switch location '%s' does not describe enough bits for mask %X\n", location, m_mask); + else if (bits < entries) + util::stream_format(errorbuf, "Switch location '%s' describes too many bits for mask %X\n", location, m_mask); } @@ -1453,15 +1468,15 @@ void ioport_field::init_live_state(analog_field *analog) // ioport_field_live - constructor //------------------------------------------------- -ioport_field_live::ioport_field_live(ioport_field &field, analog_field *analog) - : analog(analog), - joystick(nullptr), - value(field.defvalue()), - impulse(0), - last(0), - toggle(field.toggle()), - joydir(digital_joystick::JOYDIR_COUNT), - lockout(false) +ioport_field_live::ioport_field_live(ioport_field &field, analog_field *analog) : + analog(analog), + joystick(nullptr), + value(field.defvalue()), + impulse(0), + last(0), + toggle(field.toggle()), + joydir(digital_joystick::JOYDIR_COUNT), + lockout(false) { // fill in the basic values for (input_seq_type seqtype = SEQ_TYPE_STANDARD; seqtype < SEQ_TYPE_TOTAL; ++seqtype) @@ -1509,12 +1524,12 @@ ioport_field_live::ioport_field_live(ioport_field &field, analog_field *analog) // ioport_port - constructor //------------------------------------------------- -ioport_port::ioport_port(device_t &owner, const char *tag) - : m_next(nullptr), - m_device(owner), - m_tag(tag), - m_modcount(0), - m_active(0) +ioport_port::ioport_port(device_t &owner, const char *tag) : + m_next(nullptr), + m_device(owner), + m_tag(tag), + m_modcount(0), + m_active(0) { } @@ -1626,7 +1641,7 @@ void ioport_port::frame_update() // wholly overlapped by other fields //------------------------------------------------- -void ioport_port::collapse_fields(std::string &errorbuf) +void ioport_port::collapse_fields(std::ostream &errorbuf) { ioport_value maskbits = 0; int lastmodcount = -1; @@ -1655,13 +1670,13 @@ void ioport_port::collapse_fields(std::string &errorbuf) // for errors //------------------------------------------------- -void ioport_port::insert_field(ioport_field &newfield, ioport_value &disallowedbits, std::string &errorbuf) +void ioport_port::insert_field(ioport_field &newfield, ioport_value &disallowedbits, std::ostream &errorbuf) { // verify against the disallowed bits, but only if we are condition-free if (newfield.condition().none()) { if ((newfield.mask() & disallowedbits) != 0) - errorbuf.append(string_format("INPUT_TOKEN_FIELD specifies duplicate port bits (port=%s mask=%X)\n", tag(), newfield.mask())); + util::stream_format(errorbuf, "INPUT_TOKEN_FIELD specifies duplicate port bits (port=%s mask=%X)\n", tag(), newfield.mask()); disallowedbits |= newfield.mask(); } @@ -1733,10 +1748,10 @@ void ioport_port::update_defvalue(bool flush_defaults) // ioport_port_live - constructor //------------------------------------------------- -ioport_port_live::ioport_port_live(ioport_port &port) - : defvalue(0), - digital(0), - outputvalue(0) +ioport_port_live::ioport_port_live(ioport_port &port) : + defvalue(0), + digital(0), + outputvalue(0) { // iterate over fields for (ioport_field &field : port.fields()) @@ -1769,15 +1784,15 @@ ioport_port_live::ioport_port_live(ioport_port &port) // ioport_manager - constructor //------------------------------------------------- -ioport_manager::ioport_manager(running_machine &machine) - : m_machine(machine) - , m_safe_to_read(false) - , m_last_frame_time(attotime::zero) - , m_last_delta_nsec(0) - , m_playback_accumulated_speed(0) - , m_playback_accumulated_frames(0) - , m_deselected_card_config() - , m_applied_device_defaults(false) +ioport_manager::ioport_manager(running_machine &machine) : + m_machine(machine), + m_safe_to_read(false), + m_last_frame_time(attotime::zero), + m_last_delta_nsec(0), + m_playback_accumulated_speed(0), + m_playback_accumulated_frames(0), + m_deselected_card_config(), + m_applied_device_defaults(false) { for (auto &entries : m_type_to_entry) std::fill(std::begin(entries), std::end(entries), nullptr); @@ -1800,12 +1815,17 @@ time_t ioport_manager::initialize() // if we have a token list, proceed device_enumerator iter(machine().root_device()); - for (device_t &device : iter) { - std::string errors; - m_portlist.append(device, errors); - if (!errors.empty()) - osd_printf_error("Input port errors:\n%s", errors); + std::ostringstream errors; + for (device_t &device : iter) + { + m_portlist.append(device, errors); + if (errors.tellp()) + { + osd_printf_error("Input port errors:\n%s", std::move(errors).str()); + errors.str(""); + } + } } // renumber player numbers for controller ports @@ -2946,9 +2966,13 @@ Type ioport_manager::playback_read(Type &result) return result = Type(0); // read the value; if we fail, end playback - size_t read; - m_playback_stream->read(&result, sizeof(result), read); - if (sizeof(result) != read) + auto const [err, actual] = read(*m_playback_stream, &result, sizeof(result)); + if (err) + { + playback_end("Read error"); + return result = Type(0); + } + else if (sizeof(result) != actual) { playback_end("End of file"); return result = Type(0); @@ -3132,9 +3156,8 @@ void ioport_manager::record_write(Type value) value = little_endianize_int16(value); // write the value; if we fail, end recording - size_t written; - if (m_record_stream->write(&value, sizeof(value), written) || (sizeof(value) != written)) - record_end("Out of space"); + if (write(*m_record_stream, &value, sizeof(value)).first) + record_end("Write error"); } template<> @@ -3259,16 +3282,28 @@ void ioport_manager::record_port(ioport_port &port) // ioport_configurer - constructor //------------------------------------------------- -ioport_configurer::ioport_configurer(device_t &owner, ioport_list &portlist, std::string &errorbuf) - : m_owner(owner), - m_portlist(portlist), - m_errorbuf(errorbuf), - m_curport(nullptr), - m_curfield(nullptr), - m_cursetting(nullptr) +ioport_configurer::ioport_configurer(device_t &owner, ioport_list &portlist, std::ostream &errorbuf) : + m_owner(owner), + m_portlist(portlist), + m_errorbuf(errorbuf), + m_curport(nullptr), + m_curfield(nullptr), + m_cursetting(nullptr), + m_curshift(0) { } +//------------------------------------------------- +// field_set_gm_note - set a ioport as a general +// midi-encoded note number. Only sets the name +// for now +//------------------------------------------------- + +ioport_configurer& ioport_configurer::field_set_gm_note(u8 note) +{ + field_set_name(input_gm_notes_names[note]); + return *this; +} //------------------------------------------------- // string_from_token - convert an @@ -3359,6 +3394,7 @@ ioport_configurer& ioport_configurer::field_alloc(ioport_type type, ioport_value // reset the current setting m_cursetting = nullptr; + m_curshift = 0; return *this; } @@ -3369,16 +3405,17 @@ ioport_configurer& ioport_configurer::field_alloc(ioport_type type, ioport_value ioport_configurer& ioport_configurer::field_add_char(std::initializer_list<char32_t> charlist) { - for (int index = 0; index < std::size(m_curfield->m_chars); index++) - if (m_curfield->m_chars[index][0] == 0) - { - const size_t char_count = std::size(m_curfield->m_chars[index]); - assert(charlist.size() > 0 && charlist.size() <= char_count); + if (m_curshift < std::size(m_curfield->m_chars)) + { + auto &chars = m_curfield->m_chars[m_curshift++]; + assert(chars[0] == UCHAR_INVALID); + assert(charlist.size() <= std::size(chars)); - for (size_t i = 0; i < char_count; i++) - m_curfield->m_chars[index][i] = i < charlist.size() ? *(charlist.begin() + i) : 0; - return *this; - } + std::copy(charlist.begin(), charlist.end(), std::begin(chars)); + std::fill(std::begin(chars) + charlist.size(), std::end(chars), UCHAR_INVALID); + + return *this; + } std::ostringstream s; bool is_first = true; @@ -3462,10 +3499,10 @@ ioport_configurer& ioport_configurer::onoff_alloc(const char *name, ioport_value // dynamic_field - constructor //------------------------------------------------- -dynamic_field::dynamic_field(ioport_field &field) - : m_field(field) - , m_shift(0) - , m_oldval(field.defvalue()) +dynamic_field::dynamic_field(ioport_field &field) : + m_field(field), + m_shift(0), + m_oldval(field.defvalue()) { // fill in the data for (ioport_value mask = field.mask(); !(mask & 1); mask >>= 1) @@ -3519,36 +3556,36 @@ void dynamic_field::write(ioport_value newval) // analog_field - constructor //------------------------------------------------- -analog_field::analog_field(ioport_field &field) - : m_field(field) - , m_shift(compute_shift(field.mask())) - , m_adjdefvalue((field.defvalue() & field.mask()) >> m_shift) - , m_adjmin((field.minval() & field.mask()) >> m_shift) - , m_adjmax((field.maxval() & field.mask()) >> m_shift) - , m_adjoverride((field.defvalue() & field.mask()) >> m_shift) - , m_sensitivity(field.sensitivity()) - , m_reverse(field.analog_reverse()) - , m_delta(field.delta()) - , m_centerdelta(field.centerdelta()) - , m_accum(0) - , m_previous(0) - , m_previousanalog(0) - , m_minimum(osd::input_device::ABSOLUTE_MIN) - , m_maximum(osd::input_device::ABSOLUTE_MAX) - , m_center(0) - , m_reverse_val(0) - , m_scalepos(0) - , m_scaleneg(0) - , m_keyscalepos(0) - , m_keyscaleneg(0) - , m_positionalscale(0) - , m_absolute(false) - , m_wraps(false) - , m_autocenter(false) - , m_single_scale(false) - , m_interpolate(false) - , m_lastdigital(false) - , m_use_adjoverride(false) +analog_field::analog_field(ioport_field &field) : + m_field(field), + m_shift(compute_shift(field.mask())), + m_adjdefvalue((field.defvalue() & field.mask()) >> m_shift), + m_adjmin((field.minval() & field.mask()) >> m_shift), + m_adjmax((field.maxval() & field.mask()) >> m_shift), + m_adjoverride((field.defvalue() & field.mask()) >> m_shift), + m_sensitivity(field.sensitivity()), + m_reverse(field.analog_reverse()), + m_delta(field.delta()), + m_centerdelta(field.centerdelta()), + m_accum(0), + m_previous(0), + m_previousanalog(0), + m_minimum(osd::input_device::ABSOLUTE_MIN), + m_maximum(osd::input_device::ABSOLUTE_MAX), + m_center(0), + m_reverse_val(0), + m_scalepos(0), + m_scaleneg(0), + m_keyscalepos(0), + m_keyscaleneg(0), + m_positionalscale(0), + m_absolute(false), + m_wraps(false), + m_autocenter(false), + m_single_scale(false), + m_interpolate(false), + m_lastdigital(false), + m_use_adjoverride(false) { // set basic parameters based on the configured type switch (field.type()) @@ -3760,10 +3797,11 @@ s32 analog_field::apply_settings(s32 value) const value -= osd::input_device::ABSOLUTE_MIN; // map differently for positive and negative values + const s32 adjust = m_field.analog_reset() ? 0 : (1 << 23); if (value >= 0) - value = apply_scale(value, m_scalepos); + value = ((s64(value) * m_scalepos) + adjust) / (1 << 24); else - value = apply_scale(value, m_scaleneg); + value = ((s64(value) * m_scaleneg) - adjust) / (1 << 24); value += m_adjdefvalue; // for relative devices, wrap around when we go past the edge diff --git a/src/emu/ioport.h b/src/emu/ioport.h index bbff70db21d..8e8009ffce6 100644 --- a/src/emu/ioport.h +++ b/src/emu/ioport.h @@ -25,9 +25,12 @@ #include <cstdint> #include <cstring> #include <ctime> +#include <functional> +#include <iosfwd> #include <initializer_list> #include <list> #include <memory> +#include <string_view> #include <vector> @@ -43,12 +46,13 @@ constexpr ioport_value IP_ACTIVE_LOW = 0xffffffff; constexpr int MAX_PLAYERS = 10; // unicode constants +constexpr char32_t UCHAR_INVALID = 0xffff; constexpr char32_t UCHAR_PRIVATE = 0x100000; constexpr char32_t UCHAR_SHIFT_1 = UCHAR_PRIVATE + 0; constexpr char32_t UCHAR_SHIFT_2 = UCHAR_PRIVATE + 1; constexpr char32_t UCHAR_SHIFT_BEGIN = UCHAR_SHIFT_1; constexpr char32_t UCHAR_SHIFT_END = UCHAR_SHIFT_2; -constexpr char32_t UCHAR_MAMEKEY_BEGIN = UCHAR_PRIVATE + 2; +constexpr char32_t UCHAR_MAMEKEY_BEGIN = UCHAR_SHIFT_END + 1; // crosshair types @@ -325,7 +329,7 @@ enum //************************************************************************** // constructor function pointer -typedef void(*ioport_constructor)(device_t &owner, ioport_list &portlist, std::string &errorbuf); +typedef void(*ioport_constructor)(device_t &owner, ioport_list &portlist, std::ostream &errorbuf); // I/O port callback function delegates typedef device_delegate<ioport_value ()> ioport_field_read_delegate; @@ -470,7 +474,7 @@ public: bool none() const { return (m_condition == ALWAYS); } // configuration - void reset() { set(ALWAYS, nullptr, 0, 0); } + void reset() { set(ALWAYS, "", 0, 0); } void set(condition_t condition, const char *tag, ioport_value mask, ioport_value value) { m_condition = condition; @@ -485,7 +489,7 @@ public: private: // internal state condition_t m_condition; // condition to use - const char * m_tag; // tag of port whose condition is to be tested + const char * m_tag; // tag of port whose condition is to be tested (must never be nullptr) ioport_port * m_port; // reference to the port to be tested ioport_value m_mask; // mask to apply to the port ioport_value m_value; // value to compare against @@ -529,7 +533,7 @@ class ioport_diplocation { public: // construction/destruction - ioport_diplocation(const char *name, u8 swnum, bool invert); + ioport_diplocation(std::string_view name, u8 swnum, bool invert); // getters const char *name() const { return m_name.c_str(); } @@ -664,7 +668,7 @@ public: void set_user_settings(const user_settings &settings); private: - void expand_diplocation(const char *location, std::string &errorbuf); + void expand_diplocation(const char *location, std::ostream &errorbuf); // internal state ioport_field * m_next; // pointer to next field in sequence @@ -744,7 +748,7 @@ class ioport_list : public std::map<std::string, std::unique_ptr<ioport_port>> public: ioport_list() { } - void append(device_t &device, std::string &errorbuf); + void append(device_t &device, std::ostream &errorbuf); }; @@ -779,13 +783,13 @@ public: // other operations ioport_field *field(ioport_value mask) const; - void collapse_fields(std::string &errorbuf); + void collapse_fields(std::ostream &errorbuf); void frame_update(); void init_live_state(); void update_defvalue(bool flush_defaults); private: - void insert_field(ioport_field &newfield, ioport_value &disallowedbits, std::string &errorbuf); + void insert_field(ioport_field &newfield, ioport_value &disallowedbits, std::ostream &errorbuf); // internal state ioport_port * m_next; // pointer to next port @@ -1033,7 +1037,7 @@ class ioport_configurer { public: // construction/destruction - ioport_configurer(device_t &owner, ioport_list &portlist, std::string &errorbuf); + ioport_configurer(device_t &owner, ioport_list &portlist, std::ostream &errorbuf); // static helpers static const char *string_from_token(const char *string); @@ -1070,6 +1074,7 @@ public: ioport_configurer& field_set_dynamic_read(ioport_field_read_delegate delegate) { m_curfield->m_read = delegate; return *this; } ioport_configurer& field_set_dynamic_write(ioport_field_write_delegate delegate, u32 param = 0) { m_curfield->m_write = delegate; m_curfield->m_write_param = param; return *this; } ioport_configurer& field_set_diplocation(const char *location) { m_curfield->expand_diplocation(location, m_errorbuf); return *this; } + ioport_configurer& field_set_gm_note(u8 note); // setting helpers ioport_configurer& setting_alloc(ioport_value value, const char *name); @@ -1082,11 +1087,12 @@ private: // internal state device_t & m_owner; ioport_list & m_portlist; - std::string & m_errorbuf; + std::ostream & m_errorbuf; ioport_port * m_curport; ioport_field * m_curfield; ioport_setting * m_cursetting; + int m_curshift; }; @@ -1097,10 +1103,6 @@ private: #define UCHAR_MAMEKEY(code) (UCHAR_MAMEKEY_BEGIN + ITEM_ID_##code) -// macro for a read callback function (PORT_CUSTOM) -#define CUSTOM_INPUT_MEMBER(name) ioport_value name() -#define DECLARE_CUSTOM_INPUT_MEMBER(name) ioport_value name() - // macro for port write callback functions (PORT_CHANGED) #define INPUT_CHANGED_MEMBER(name) void name(ioport_field &field, u32 param, ioport_value oldval, ioport_value newval) #define DECLARE_INPUT_CHANGED_MEMBER(name) void name(ioport_field &field, u32 param, ioport_value oldval, ioport_value newval) @@ -1126,7 +1128,7 @@ private: // start of table #define INPUT_PORTS_START(_name) \ -ATTR_COLD void INPUT_PORTS_NAME(_name)(device_t &owner, ioport_list &portlist, std::string &errorbuf) \ +ATTR_COLD void INPUT_PORTS_NAME(_name)(device_t &owner, ioport_list &portlist, std::ostream &errorbuf) \ { \ ioport_configurer configurer(owner, portlist, errorbuf); // end of table @@ -1135,7 +1137,7 @@ ATTR_COLD void INPUT_PORTS_NAME(_name)(device_t &owner, ioport_list &portlist, s // aliasing #define INPUT_PORTS_EXTERN(_name) \ - extern void INPUT_PORTS_NAME(_name)(device_t &owner, ioport_list &portlist, std::string &errorbuf) + extern void INPUT_PORTS_NAME(_name)(device_t &owner, ioport_list &portlist, std::ostream &errorbuf) // including #define PORT_INCLUDE(_name) \ @@ -1205,6 +1207,9 @@ ATTR_COLD void INPUT_PORTS_NAME(_name)(device_t &owner, ioport_list &portlist, s #define PORT_OPTIONAL \ configurer.field_set_optional(); +#define PORT_GM_NOTE(_id) \ + configurer.field_set_gm_note(_id); + // analog settings // if this macro is not used, the minimum defaults to 0 and maximum defaults to the mask value #define PORT_MINMAX(_min, _max) \ @@ -1222,11 +1227,10 @@ ATTR_COLD void INPUT_PORTS_NAME(_name)(device_t &owner, ioport_list &portlist, s #define PORT_CROSSHAIR(axis, scale, offset, altaxis) \ configurer.field_set_crosshair(CROSSHAIR_AXIS_##axis, altaxis, scale, offset); -#define PORT_CROSSHAIR_MAPPER(_callback) \ - configurer.field_set_crossmapper(ioport_field_crossmap_delegate(owner, DEVICE_SELF, _callback, #_callback)); - -#define PORT_CROSSHAIR_MAPPER_MEMBER(_device, _class, _member) \ - configurer.field_set_crossmapper(ioport_field_crossmap_delegate(owner, _device, &_class::_member, #_class "::" #_member)); +#define PORT_CROSSHAIR_MAPPER_MEMBER_IMPL(_device, _funcptr, _name) \ + configurer.field_set_crossmapper(ioport_field_crossmap_delegate(owner, _device, _funcptr, _name)); +#define PORT_CROSSHAIR_MAPPER_DEVICE_MEMBER(...) PORT_CROSSHAIR_MAPPER_MEMBER_IMPL(__VA_ARGS__) +#define PORT_CROSSHAIR_MAPPER_MEMBER(...) PORT_CROSSHAIR_MAPPER_MEMBER_IMPL(DEVICE_SELF, __VA_ARGS__) // how many optical counts for 1 full turn of the control #define PORT_FULL_TURN_COUNT(_count) \ @@ -1252,48 +1256,41 @@ ATTR_COLD void INPUT_PORTS_NAME(_name)(device_t &owner, ioport_list &portlist, s configurer.field_set_analog_invert(); // read callbacks -#define PORT_CUSTOM_MEMBER(_class, _member) \ - configurer.field_set_dynamic_read(ioport_field_read_delegate(owner, DEVICE_SELF, &_class::_member, #_class "::" #_member)); -#define PORT_CUSTOM_DEVICE_MEMBER(_device, _class, _member) \ - configurer.field_set_dynamic_read(ioport_field_read_delegate(owner, _device, &_class::_member, #_class "::" #_member)); +#define PORT_CUSTOM_MEMBER_IMPL(_device, _funcptr, _name) \ + configurer.field_set_dynamic_read(ioport_field_read_delegate(owner, _device, _funcptr, _name)); +#define PORT_CUSTOM_DEVICE_MEMBER(...) PORT_CUSTOM_MEMBER_IMPL(__VA_ARGS__) +#define PORT_CUSTOM_MEMBER(...) PORT_CUSTOM_MEMBER_IMPL(DEVICE_SELF, __VA_ARGS__); // write callbacks -#define PORT_CHANGED_MEMBER(_device, _class, _member, _param) \ - configurer.field_set_dynamic_write(ioport_field_write_delegate(owner, _device, &_class::_member, #_class "::" #_member), (_param)); +#define PORT_CHANGED_MEMBER_IMPL(_device, _funcptr, _name, _param) \ + configurer.field_set_dynamic_write(ioport_field_write_delegate(owner, _device, _funcptr, _name), (_param)); +#define PORT_CHANGED_MEMBER(...) PORT_CHANGED_MEMBER_IMPL(__VA_ARGS__) // input device handler -#define PORT_READ_LINE_MEMBER(_class, _member) \ - configurer.field_set_dynamic_read( \ - ioport_field_read_delegate( \ - owner, \ - DEVICE_SELF, \ - static_cast<ioport_value (*)(_class &)>([] (_class &device) -> ioport_value { return (device._member() & 1) ? ~ioport_value(0) : 0; }), \ - #_class "::" #_member)); -#define PORT_READ_LINE_DEVICE_MEMBER(_device, _class, _member) \ +#define PORT_READ_LINE_MEMBER_IMPL(_device, _funcptr, _name) \ configurer.field_set_dynamic_read( \ ioport_field_read_delegate( \ owner, \ _device, \ - static_cast<ioport_value (*)(_class &)>([] (_class &device) -> ioport_value { return (device._member() & 1) ? ~ioport_value(0) : 0; }), \ - #_class "::" #_member)); + static_cast<ioport_value (*)(emu::detail::rw_delegate_device_class_t<decltype(_funcptr)> &)>( \ + [] (auto &device) -> ioport_value { return (std::invoke(_funcptr, device) & 1) ? ~ioport_value(0) : 0; }), \ + _name)); +#define PORT_READ_LINE_DEVICE_MEMBER(...) PORT_READ_LINE_MEMBER_IMPL(__VA_ARGS__) +#define PORT_READ_LINE_MEMBER(...) PORT_READ_LINE_MEMBER_IMPL(DEVICE_SELF, __VA_ARGS__) // output device handler -#define PORT_WRITE_LINE_MEMBER(_class, _member) \ - configurer.field_set_dynamic_write( \ - ioport_field_write_delegate( \ - owner, \ - DEVICE_SELF, \ - static_cast<void (*)(_class &, ioport_field &, u32, ioport_value, ioport_value)>([] (_class &device, ioport_field &field, u32 param, ioport_value oldval, ioport_value newval) { device._member(newval); }), \ - #_class "::" #_member)); -#define PORT_WRITE_LINE_DEVICE_MEMBER(_device, _class, _member) \ +#define PORT_WRITE_LINE_MEMBER_IMPL(_device, _funcptr, _name) \ configurer.field_set_dynamic_write( \ ioport_field_write_delegate( \ owner, \ _device, \ - static_cast<void (*)(_class &, ioport_field &, u32, ioport_value, ioport_value)>([] (_class &device, ioport_field &field, u32 param, ioport_value oldval, ioport_value newval) { device._member(newval); }), \ - #_class "::" #_member)); + static_cast<void (*)(emu::detail::rw_delegate_device_class_t<decltype(_funcptr)> &, ioport_field &, u32, ioport_value, ioport_value)>( \ + [] (auto &device, ioport_field &field, u32 param, ioport_value oldval, ioport_value newval) { std::invoke(_funcptr, device, newval); }), \ + _name)); +#define PORT_WRITE_LINE_DEVICE_MEMBER(...) PORT_WRITE_LINE_MEMBER_IMPL(__VA_ARGS__) +#define PORT_WRITE_LINE_MEMBER(...) PORT_WRITE_LINE_MEMBER_IMPL(DEVICE_SELF, __VA_ARGS__) -// dip switch definition +// DIP switch definition #define PORT_DIPNAME(_mask, _default, _name) \ configurer.field_alloc(IPT_DIPSWITCH, (_default), (_mask), (_name)); #define PORT_DIPSETTING(_default, _name) \ @@ -1302,7 +1299,7 @@ ATTR_COLD void INPUT_PORTS_NAME(_name)(device_t &owner, ioport_list &portlist, s // note that these are specified LSB-first #define PORT_DIPLOCATION(_location) \ configurer.field_set_diplocation(_location); -// conditionals for dip switch settings +// conditionals for DIP switch settings #define PORT_CONDITION(_tag, _mask, _condition, _value) \ configurer.set_condition(ioport_condition::_condition, _tag, _mask, _value); // analog adjuster definition @@ -1319,6 +1316,103 @@ ATTR_COLD void INPUT_PORTS_NAME(_name)(device_t &owner, ioport_list &portlist, s #define PORT_CHAR(...) \ configurer.field_add_char({ __VA_ARGS__ }); +// General-midi derived piano notes +#define PORT_GM_A0 PORT_GM_NOTE( 21) // Start of 88-key keyboard +#define PORT_GM_AS0 PORT_GM_NOTE( 22) +#define PORT_GM_B0 PORT_GM_NOTE( 23) + +#define PORT_GM_C1 PORT_GM_NOTE( 24) +#define PORT_GM_CS1 PORT_GM_NOTE( 25) +#define PORT_GM_D1 PORT_GM_NOTE( 26) +#define PORT_GM_DS1 PORT_GM_NOTE( 27) +#define PORT_GM_E1 PORT_GM_NOTE( 28) // Start of 76-key keyboard +#define PORT_GM_F1 PORT_GM_NOTE( 29) +#define PORT_GM_FS1 PORT_GM_NOTE( 30) +#define PORT_GM_G1 PORT_GM_NOTE( 31) +#define PORT_GM_GS1 PORT_GM_NOTE( 32) +#define PORT_GM_A1 PORT_GM_NOTE( 33) +#define PORT_GM_AS1 PORT_GM_NOTE( 34) +#define PORT_GM_B1 PORT_GM_NOTE( 35) + +#define PORT_GM_C2 PORT_GM_NOTE( 36) // Start of 49 and 61-key keyboards +#define PORT_GM_CS2 PORT_GM_NOTE( 37) +#define PORT_GM_D2 PORT_GM_NOTE( 38) +#define PORT_GM_DS2 PORT_GM_NOTE( 39) +#define PORT_GM_E2 PORT_GM_NOTE( 40) +#define PORT_GM_F2 PORT_GM_NOTE( 41) +#define PORT_GM_FS2 PORT_GM_NOTE( 42) +#define PORT_GM_G2 PORT_GM_NOTE( 43) +#define PORT_GM_GS2 PORT_GM_NOTE( 44) +#define PORT_GM_A2 PORT_GM_NOTE( 45) +#define PORT_GM_AS2 PORT_GM_NOTE( 46) +#define PORT_GM_B2 PORT_GM_NOTE( 47) + +#define PORT_GM_C3 PORT_GM_NOTE( 48) +#define PORT_GM_CS3 PORT_GM_NOTE( 49) +#define PORT_GM_D3 PORT_GM_NOTE( 50) +#define PORT_GM_DS3 PORT_GM_NOTE( 51) +#define PORT_GM_E3 PORT_GM_NOTE( 52) +#define PORT_GM_F3 PORT_GM_NOTE( 53) +#define PORT_GM_FS3 PORT_GM_NOTE( 54) +#define PORT_GM_G3 PORT_GM_NOTE( 55) +#define PORT_GM_GS3 PORT_GM_NOTE( 56) +#define PORT_GM_A3 PORT_GM_NOTE( 57) +#define PORT_GM_AS3 PORT_GM_NOTE( 58) +#define PORT_GM_B3 PORT_GM_NOTE( 59) + +#define PORT_GM_C4 PORT_GM_NOTE( 60) // Middle C +#define PORT_GM_CS4 PORT_GM_NOTE( 61) +#define PORT_GM_D4 PORT_GM_NOTE( 62) +#define PORT_GM_DS4 PORT_GM_NOTE( 63) +#define PORT_GM_E4 PORT_GM_NOTE( 64) +#define PORT_GM_F4 PORT_GM_NOTE( 65) +#define PORT_GM_FS4 PORT_GM_NOTE( 66) +#define PORT_GM_G4 PORT_GM_NOTE( 67) +#define PORT_GM_GS4 PORT_GM_NOTE( 68) +#define PORT_GM_A4 PORT_GM_NOTE( 69) +#define PORT_GM_AS4 PORT_GM_NOTE( 70) +#define PORT_GM_B4 PORT_GM_NOTE( 71) + +#define PORT_GM_C5 PORT_GM_NOTE( 72) +#define PORT_GM_CS5 PORT_GM_NOTE( 73) +#define PORT_GM_D5 PORT_GM_NOTE( 74) +#define PORT_GM_DS5 PORT_GM_NOTE( 75) +#define PORT_GM_E5 PORT_GM_NOTE( 76) +#define PORT_GM_F5 PORT_GM_NOTE( 77) +#define PORT_GM_FS5 PORT_GM_NOTE( 78) +#define PORT_GM_G5 PORT_GM_NOTE( 79) +#define PORT_GM_GS5 PORT_GM_NOTE( 80) +#define PORT_GM_A5 PORT_GM_NOTE( 81) +#define PORT_GM_AS5 PORT_GM_NOTE( 82) +#define PORT_GM_B5 PORT_GM_NOTE( 83) + +#define PORT_GM_C6 PORT_GM_NOTE( 84) // End of 49-key keyboard +#define PORT_GM_CS6 PORT_GM_NOTE( 85) +#define PORT_GM_D6 PORT_GM_NOTE( 86) +#define PORT_GM_DS6 PORT_GM_NOTE( 87) +#define PORT_GM_E6 PORT_GM_NOTE( 88) +#define PORT_GM_F6 PORT_GM_NOTE( 89) +#define PORT_GM_FS6 PORT_GM_NOTE( 90) +#define PORT_GM_G6 PORT_GM_NOTE( 91) +#define PORT_GM_GS6 PORT_GM_NOTE( 92) +#define PORT_GM_A6 PORT_GM_NOTE( 93) +#define PORT_GM_AS6 PORT_GM_NOTE( 94) +#define PORT_GM_B6 PORT_GM_NOTE( 95) + +#define PORT_GM_C7 PORT_GM_NOTE( 96) // End of 61-key keyboard +#define PORT_GM_CS7 PORT_GM_NOTE( 97) +#define PORT_GM_D7 PORT_GM_NOTE( 98) +#define PORT_GM_DS7 PORT_GM_NOTE( 99) +#define PORT_GM_E7 PORT_GM_NOTE(100) +#define PORT_GM_F7 PORT_GM_NOTE(101) +#define PORT_GM_FS7 PORT_GM_NOTE(102) +#define PORT_GM_G7 PORT_GM_NOTE(103) // End of 76-key keyboard +#define PORT_GM_GS7 PORT_GM_NOTE(104) +#define PORT_GM_A7 PORT_GM_NOTE(105) +#define PORT_GM_AS7 PORT_GM_NOTE(106) +#define PORT_GM_B7 PORT_GM_NOTE(107) + +#define PORT_GM_C8 PORT_GM_NOTE(108) // End of 88-key keyboard // name of table #define DEVICE_INPUT_DEFAULTS_NAME(_name) device_iptdef_##_name @@ -1362,12 +1456,6 @@ ATTR_COLD void INPUT_PORTS_NAME(_name)(device_t &owner, ioport_list &portlist, s #define PORT_SERVICE_NO_TOGGLE(_mask, _default) \ PORT_BIT( _mask, _mask & _default, IPT_SERVICE ) PORT_NAME( DEF_STR( Service_Mode )) -#define PORT_VBLANK(_screen) \ - PORT_READ_LINE_DEVICE_MEMBER(_screen, screen_device, vblank) - -#define PORT_HBLANK(_screen) \ - PORT_READ_LINE_DEVICE_MEMBER(_screen, screen_device, hblank) - //************************************************************************** // INLINE FUNCTIONS //************************************************************************** diff --git a/src/emu/layout/cyberlead.lay b/src/emu/layout/cyberlead.lay new file mode 100644 index 00000000000..9eb4e87e472 --- /dev/null +++ b/src/emu/layout/cyberlead.lay @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +license:CC0-1.0 +--> +<mamelayout version="2"> + <view name="Default Layout"> + <screen index="1"> + <bounds x="0" y="0" width="768" height="128" /> + </screen> + <screen index="0"> + <bounds x="0" y="128" width="768" height="576" /> + </screen> + </view> +</mamelayout> diff --git a/src/emu/layout/nes_rob.lay b/src/emu/layout/nes_rob.lay index 285a6d6fe86..c5b10747151 100644 --- a/src/emu/layout/nes_rob.lay +++ b/src/emu/layout/nes_rob.lay @@ -1,6 +1,7 @@ <?xml version="1.0"?> <!-- license:CC0-1.0 +authors:hap --> <mamelayout version="2"> diff --git a/src/emu/layout/smartboard.lay b/src/emu/layout/smartboard.lay index eea851def47..1b028bc23fe 100644 --- a/src/emu/layout/smartboard.lay +++ b/src/emu/layout/smartboard.lay @@ -1,6 +1,7 @@ <?xml version="1.0"?> <!-- license:CC0-1.0 +authors:Sandro Ronco, hap --> <mamelayout version="2"> diff --git a/src/emu/layout/teletex800.lay b/src/emu/layout/teletex800.lay new file mode 100644 index 00000000000..381c48ef34f --- /dev/null +++ b/src/emu/layout/teletex800.lay @@ -0,0 +1,167 @@ +<?xml version="1.0"?> +<!-- +license:CC0-1.0 +--> +<mamelayout version="2"> + <element name="panel"> + <!--<image file="teletex800.png" />--> + </element> + + <element name="button"> + <rect> + <color red="0.25" green="0.25" blue="0.25" /> + </rect> + </element> + + <element name="red_led" defstate="0"> + <disk state="0"> + <color red="0.20" green="0.0" blue="0.0" /> + </disk> + <disk state="1"> + <color red="0.75" green="0.0" blue="0.0" /> + </disk> + </element> + + <element name="green_led" defstate="0"> + <disk state="0"> + <color red="0.0" green="0.20" blue="0.0" /> + </disk> + <disk state="1"> + <color red="0.0" green="0.75" blue="0.0" /> + </disk> + </element> + + <element name="digit" defstate="0"> + <led7seg> + <color red="0.0" green="1.0" blue="0.0" /> + </led7seg> + </element> + + <element name="time_led" defstate="0"> + <text state="0" string="TID"> + <color red="0.0" green="0.20" blue="0.0" /> + </text> + <text state="1" string="TID"> + <color red="0.0" green="0.75" blue="0.0" /> + </text> + </element> + + <element name="date_led" defstate="0"> + <text state="0" string="DAT"> + <color red="0.0" green="0.20" blue="0.0" /> + </text> + <text state="1" string="DAT"> + <color red="0.0" green="0.75" blue="0.0" /> + </text> + </element> + + <element name="year_led" defstate="0"> + <text state="0" string="ÅR"> + <color red="0.0" green="0.20" blue="0.0" /> + </text> + <text state="1" string="ÅR"> + <color red="0.0" green="0.75" blue="0.0" /> + </text> + </element> + + <view name="Front panel"> + <bounds x="0" y="0" width="810" height="473" /> + + <element ref="panel"> + <bounds x="0" y="0" width="810" height="473" /> + </element> + + <!-- BATTERI DRIFT --> + <element name="bat_led" ref="red_led"> + <bounds x="114" y="118" width="16" height="16" /> + </element> + + <!-- SKRIVAR FEL --> + <element name="pr_led" ref="red_led"> + <bounds x="205" y="118" width="16" height="16" /> + </element> + + <!-- OKVITT TELEX --> + <element name="telex_led" ref="green_led"> + <bounds x="298" y="118" width="16" height="16" /> + </element> + + <!-- MOTTAGNA --> + <element name="rx_digit0" ref="digit"> + <bounds x="384" y="107" width="38" height="49" /> + </element> + + <element name="rx_digit1" ref="digit"> + <bounds x="440" y="107" width="38" height="49" /> + </element> + + <!-- SEND KÖ --> + <element name="tx_digit0" ref="digit"> + <bounds x="522" y="107" width="38" height="49" /> + </element> + + <element name="tx_digit1" ref="digit"> + <bounds x="578" y="107" width="38" height="49" /> + </element> + + <!-- TID --> + <element name="time_led" ref="time_led"> + <bounds x="658" y="99" width="29" height="14" /> + </element> + + <!-- DAT --> + <element name="date_led" ref="date_led"> + <bounds x="659" y="123" width="29" height="14" /> + </element> + + <!-- ÅR --> + <element name="year_led" ref="year_led"> + <bounds x="659" y="147" width="22" height="14" /> + </element> + + <!-- MINNES VARNING --> + <element name="mem_led" ref="red_led"> + <bounds x="114" y="204" width="16" height="16" /> + </element> + + <!-- OBS --> + <element name="obs_led" ref="red_led"> + <bounds x="205" y="204" width="16" height="16" /> + </element> + + <!-- SKRIV --> + <element name="write_led" ref="green_led"> + <bounds x="298" y="204" width="16" height="16" /> + </element> + + <!-- LOG --> + <element name="log_led" ref="green_led"> + <bounds x="388" y="204" width="16" height="16" /> + </element> + + <!-- KÖ --> + <element name="queue_led" ref="green_led"> + <bounds x="479" y="204" width="16" height="16" /> + </element> + + <!-- ALLA --> + <element name="all_led" ref="green_led"> + <bounds x="571" y="204" width="16" height="16" /> + </element> + + <!-- SKRIV --> + <element ref="button" inputtag="BTN" inputmask="0x01"> + <bounds x="275" y="262" width="54" height="54" /> + </element> + + <!-- ALLA --> + <element ref="button" inputtag="BTN" inputmask="0x02"> + <bounds x="556" y="261" width="54" height="54" /> + </element> + + <!-- KLOCK --> + <element ref="button" inputtag="BTN" inputmask="0x04"> + <bounds x="648" y="263" width="54" height="54" /> + </element> + </view> +</mamelayout> diff --git a/src/emu/machine.cpp b/src/emu/machine.cpp index 669f5fa27a5..a77140dba16 100644 --- a/src/emu/machine.cpp +++ b/src/emu/machine.cpp @@ -212,7 +212,9 @@ void running_machine::start() add_notifier(MACHINE_NOTIFY_RESET, machine_notify_delegate(&running_machine::reset_all_devices, this)); add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(&running_machine::stop_all_devices, this)); save().register_presave(save_prepost_delegate(FUNC(running_machine::presave_all_devices), this)); + m_sound->before_devices_init(); start_all_devices(); + m_sound->after_devices_init(); save().register_postload(save_prepost_delegate(FUNC(running_machine::postload_all_devices), this)); // save outputs created before start time @@ -232,14 +234,28 @@ void running_machine::start() if (filename[0] != 0 && !m_video->is_recording()) m_video->begin_recording(filename, movie_recording::format::AVI); - // if we're coming in with a savegame request, process it now const char *savegame = options().state(); if (savegame[0] != 0) + { + // if we're coming in with a savegame request, process it now schedule_load(savegame); - - // if we're in autosave mode, schedule a load - else if (options().autosave() && (m_system.flags & MACHINE_SUPPORTS_SAVE) != 0) - schedule_load("auto"); + } + else if (options().autosave()) + { + // if we're in autosave mode, schedule a load + // m_save.supported() won't be set until save state registrations are finalised + bool supported = true; + for (device_t &device : device_enumerator(root_device())) + { + if (device.type().emulation_flags() & device_t::flags::SAVE_UNSUPPORTED) + { + supported = false; + break; + } + } + if (supported) + schedule_load("auto"); + } manager().update_machine(); } @@ -284,15 +300,13 @@ int running_machine::run(bool quiet) // then finish setting up our local machine start(); + // disallow save state registrations starting here + m_save.allow_registration(false); + // load the configuration settings manager().before_load_settings(*this); m_configuration->load_settings(); - // disallow save state registrations starting here. - // Don't do it earlier, config load can create network - // devices with timers. - m_save.allow_registration(false); - // load the NVRAM nvram_load(); @@ -331,9 +345,12 @@ int running_machine::run(bool quiet) // execute CPUs if not paused if (!m_paused) m_scheduler.timeslice(); - // otherwise, just pump video updates through + // otherwise, just pump video updates and sound mapping updates through else + { m_video->frame_update(); + sound().mapping_update(); + } // handle save/load if (m_saveload_schedule != saveload_schedule::NONE) @@ -408,7 +425,7 @@ void running_machine::schedule_exit() m_scheduler.eat_all_cycles(); // if we're autosaving on exit, schedule a save as well - if (options().autosave() && (m_system.flags & MACHINE_SUPPORTS_SAVE) && this->time() > attotime::zero) + if (options().autosave() && m_save.supported() && (this->time() > attotime::zero)) schedule_save("auto"); } @@ -863,6 +880,7 @@ void running_machine::handle_saveload() if (!m_saveload_pending_file.empty()) { const char *const opname = (m_saveload_schedule == saveload_schedule::LOAD) ? "load" : "save"; + const char *const preposname = (m_saveload_schedule == saveload_schedule::LOAD) ? "from" : "to"; // if there are anonymous timers, we can't save just yet, and we can't load yet either // because the timers might overwrite data we have loaded @@ -870,7 +888,7 @@ void running_machine::handle_saveload() { // if more than a second has passed, we're probably screwed if ((this->time() - m_saveload_schedule_time) > attotime::from_seconds(1)) - popmessage("Unable to %s due to pending anonymous timers. See error.log for details.", opname); + popmessage("Error: Unable to %s state %s %s due to pending anonymous timers. See error.log for details.", opname, preposname, m_saveload_pending_file); else return; // return without cancelling the operation } @@ -883,39 +901,36 @@ void running_machine::handle_saveload() auto const filerr = file.open(m_saveload_pending_file); if (!filerr) { - const char *const opnamed = (m_saveload_schedule == saveload_schedule::LOAD) ? "loaded" : "saved"; - // read/write the save state save_error saverr = (m_saveload_schedule == saveload_schedule::LOAD) ? m_save.read_file(file) : m_save.write_file(file); // handle the result switch (saverr) { - case STATERR_ILLEGAL_REGISTRATIONS: - popmessage("Error: Unable to %s state due to illegal registrations. See error.log for details.", opname); - break; - case STATERR_INVALID_HEADER: - popmessage("Error: Unable to %s state due to an invalid header. Make sure the save state is correct for this machine.", opname); + popmessage("Error: Unable to %s state %s %s due to an invalid header. Make sure the save state is correct for this system.", opname, preposname, m_saveload_pending_file); break; case STATERR_READ_ERROR: - popmessage("Error: Unable to %s state due to a read error (file is likely corrupt).", opname); + popmessage("Error: Unable to %s state %s %s due to a read error (file is likely corrupt).", opname, preposname, m_saveload_pending_file); break; case STATERR_WRITE_ERROR: - popmessage("Error: Unable to %s state due to a write error. Verify there is enough disk space.", opname); + popmessage("Error: Unable to %s state %s %s due to a write error. Verify there is enough disk space.", opname, preposname, m_saveload_pending_file); break; case STATERR_NONE: - if (!(m_system.flags & MACHINE_SUPPORTS_SAVE)) - popmessage("State successfully %s.\nWarning: Save states are not officially supported for this machine.", opnamed); + { + const char *const opnamed = (m_saveload_schedule == saveload_schedule::LOAD) ? "Loaded" : "Saved"; + if (!m_save.supported()) + popmessage("%s state %s %s.\nWarning: Save states are not officially supported for this system.", opnamed, preposname, m_saveload_pending_file); else - popmessage("State successfully %s.", opnamed); + popmessage("%s state %s %s.", opnamed, preposname, m_saveload_pending_file); break; + } default: - popmessage("Error: Unknown error during state %s.", opnamed); + popmessage("Error: Unknown error during %s state %s %s.", opname, preposname, m_saveload_pending_file); break; } @@ -926,11 +941,11 @@ void running_machine::handle_saveload() else if ((openflags == OPEN_FLAG_READ) && (std::errc::no_such_file_or_directory == filerr)) { // attempt to load a non-existent savestate, report empty slot - popmessage("Error: No savestate file to load.", opname); + popmessage("Error: Load state file %s not found.", m_saveload_pending_file); } else { - popmessage("Error: Failed to open file for %s operation.", opname); + popmessage("Error: Failed to open %s for %s state operation.", m_saveload_pending_file, opname); } } } @@ -1160,8 +1175,17 @@ void running_machine::nvram_save() emu_file file(options().nvram_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); if (!file.open(nvram_filename(nvram.device()))) { + bool error = false; + if (!nvram.nvram_save(file)) + { + error = true; osd_printf_error("Error writing NVRAM file %s\n", file.filename()); + } + + // close and perhaps delete the file + if (error || file.size() == 0) + file.remove_on_close(); file.close(); } } diff --git a/src/emu/main.h b/src/emu/main.h index 78553ca5236..a62ccb46211 100644 --- a/src/emu/main.h +++ b/src/emu/main.h @@ -13,6 +13,7 @@ #include "emufwd.h" +#include <map> #include <memory> #include <string> #include <vector> @@ -57,7 +58,7 @@ public: static bool draw_user_interface(running_machine& machine); static void periodic_check(); static bool frame_hook(); - static void sound_hook(); + static void sound_hook(const std::map<std::string, std::vector<std::pair<const float *, int>>> &sound); // Can't use sound_stream::sample_t sadly static void layout_script_cb(layout_file &file, const char *script); static bool standalone(); }; diff --git a/src/emu/mconfig.cpp b/src/emu/mconfig.cpp index 2c8c1aa011b..275a4af46ae 100644 --- a/src/emu/mconfig.cpp +++ b/src/emu/mconfig.cpp @@ -404,6 +404,6 @@ void machine_config::set_perfect_quantum(device_t &device, std::string tag) m_current_device->tag()); } - m_perfect_quantum_device.first = &device; + m_perfect_quantum_device.first = tag.empty() ? nullptr : &device; m_perfect_quantum_device.second = std::move(tag); } diff --git a/src/emu/memarray.cpp b/src/emu/memarray.cpp index 660e07ad9a1..eb03974ff1c 100644 --- a/src/emu/memarray.cpp +++ b/src/emu/memarray.cpp @@ -134,8 +134,8 @@ void memory_array::write8_to_32le(int index, u32 data) { reinterpret_cast<u8 *>( u32 memory_array::read8_from_32be(int index) const { return reinterpret_cast<u8 *>(m_base)[BYTE4_XOR_BE(index)]; } void memory_array::write8_to_32be(int index, u32 data) { reinterpret_cast<u8 *>(m_base)[BYTE4_XOR_BE(index)] = data; } -u32 memory_array::read8_from_64le(int index) const { return reinterpret_cast<u8 *>(m_base)[BYTE8_XOR_BE(index)]; } -void memory_array::write8_to_64le(int index, u32 data) { reinterpret_cast<u8 *>(m_base)[BYTE8_XOR_BE(index)] = data; } +u32 memory_array::read8_from_64le(int index) const { return reinterpret_cast<u8 *>(m_base)[BYTE8_XOR_LE(index)]; } +void memory_array::write8_to_64le(int index, u32 data) { reinterpret_cast<u8 *>(m_base)[BYTE8_XOR_LE(index)] = data; } u32 memory_array::read8_from_64be(int index) const { return reinterpret_cast<u8 *>(m_base)[BYTE8_XOR_BE(index)]; } void memory_array::write8_to_64be(int index, u32 data) { reinterpret_cast<u8 *>(m_base)[BYTE8_XOR_BE(index)] = data; } diff --git a/src/emu/natkeyboard.cpp b/src/emu/natkeyboard.cpp index 681f64cd263..1abe9a140ff 100644 --- a/src/emu/natkeyboard.cpp +++ b/src/emu/natkeyboard.cpp @@ -34,7 +34,8 @@ // CONSTANTS //************************************************************************** -const int KEY_BUFFER_SIZE = 4096; +const u32 KEY_BUFFER_CHUNK_SIZE = 0x1000; +const u32 KEY_BUFFER_MAX_SIZE = 0x800000; const char32_t INVALID_CHAR = '?'; @@ -47,7 +48,7 @@ const char32_t INVALID_CHAR = '?'; struct char_info { char32_t ch; - const char *alternate; // alternative string, in UTF-8 + const char *alternate; // alternative string, in UTF-8 static const char_info *find(char32_t target); }; @@ -344,7 +345,7 @@ natural_keyboard::natural_keyboard(running_machine &machine) build_codes(); if (!m_keyboards.empty()) { - m_buffer.resize(KEY_BUFFER_SIZE); + m_buffer.resize(KEY_BUFFER_CHUNK_SIZE); m_timer = machine.scheduler().timer_alloc(timer_expired_delegate(FUNC(natural_keyboard::timer), this)); } @@ -455,23 +456,14 @@ void natural_keyboard::post_char(char32_t ch, bool normalize_crlf) // post - post a unicode encoded string //------------------------------------------------- -void natural_keyboard::post(const char32_t *text, size_t length, const attotime &rate) +void natural_keyboard::post(std::u32string_view text, const attotime &rate) { // set the fixed rate m_current_rate = rate; - // 0 length means strlen - if (length == 0) - for (const char32_t *scan = text; *scan != 0; scan++) - length++; - - // iterate over characters or until the buffer is full up - while (length > 0 && !full()) - { - // fetch next character - post_char(*text++, true); - length--; - } + // iterate over characters + for (char32_t ch : text) + post_char(ch, true); } @@ -479,21 +471,17 @@ void natural_keyboard::post(const char32_t *text, size_t length, const attotime // post_utf8 - post a UTF-8 encoded string //------------------------------------------------- -void natural_keyboard::post_utf8(const char *text, size_t length, const attotime &rate) +void natural_keyboard::post_utf8(std::string_view text, const attotime &rate) { // set the fixed rate m_current_rate = rate; - // 0-length means strlen - if (length == 0) - length = strlen(text); - // iterate until out of characters - while (length > 0) + while (!text.empty()) { // decode the next character char32_t uc; - int count = uchar_from_utf8(&uc, text, length); + int count = uchar_from_utf8(&uc, text); if (count < 0) { count = 1; @@ -502,83 +490,74 @@ void natural_keyboard::post_utf8(const char *text, size_t length, const attotime // append to the buffer post_char(uc, true); - text += count; - length -= count; + text.remove_prefix(count); } } -void natural_keyboard::post_utf8(std::string_view text, const attotime &rate) -{ - if (!text.empty()) - post_utf8(text.data(), text.size(), rate); -} - - //------------------------------------------------- // post_coded - post a coded string //------------------------------------------------- -void natural_keyboard::post_coded(const char *text, size_t length, const attotime &rate) +void natural_keyboard::post_coded(std::string_view text, const attotime &rate) { + using namespace std::literals; static const struct { - const char *key; + std::string_view key; char32_t code; } codes[] = { - { "BACKSPACE", 8 }, - { "BS", 8 }, - { "BKSP", 8 }, - { "DEL", UCHAR_MAMEKEY(DEL) }, - { "DELETE", UCHAR_MAMEKEY(DEL) }, - { "END", UCHAR_MAMEKEY(END) }, - { "ENTER", 13 }, - { "ESC", '\033' }, - { "HOME", UCHAR_MAMEKEY(HOME) }, - { "INS", UCHAR_MAMEKEY(INSERT) }, - { "INSERT", UCHAR_MAMEKEY(INSERT) }, - { "PGDN", UCHAR_MAMEKEY(PGDN) }, - { "PGUP", UCHAR_MAMEKEY(PGUP) }, - { "SPACE", 32 }, - { "TAB", 9 }, - { "F1", UCHAR_MAMEKEY(F1) }, - { "F2", UCHAR_MAMEKEY(F2) }, - { "F3", UCHAR_MAMEKEY(F3) }, - { "F4", UCHAR_MAMEKEY(F4) }, - { "F5", UCHAR_MAMEKEY(F5) }, - { "F6", UCHAR_MAMEKEY(F6) }, - { "F7", UCHAR_MAMEKEY(F7) }, - { "F8", UCHAR_MAMEKEY(F8) }, - { "F9", UCHAR_MAMEKEY(F9) }, - { "F10", UCHAR_MAMEKEY(F10) }, - { "F11", UCHAR_MAMEKEY(F11) }, - { "F12", UCHAR_MAMEKEY(F12) }, - { "QUOTE", '\"' } + { "BACKSPACE"sv, 8 }, + { "BS"sv, 8 }, + { "BKSP"sv, 8 }, + { "CAPSLOCK"sv, UCHAR_MAMEKEY(CAPSLOCK) }, + { "CR"sv, 13 }, + { "DEL"sv, UCHAR_MAMEKEY(DEL) }, + { "DELETE"sv, UCHAR_MAMEKEY(DEL) }, + { "END"sv, UCHAR_MAMEKEY(END) }, + { "ENTER"sv, 13 }, + { "ESC"sv, '\033' }, + { "HOME"sv, UCHAR_MAMEKEY(HOME) }, + { "INS"sv, UCHAR_MAMEKEY(INSERT) }, + { "INSERT"sv, UCHAR_MAMEKEY(INSERT) }, + { "LF"sv, 10 }, + { "PGDN"sv, UCHAR_MAMEKEY(PGDN) }, + { "PGUP"sv, UCHAR_MAMEKEY(PGUP) }, + { "SPACE"sv, 32 }, + { "TAB"sv, 9 }, + { "F1"sv, UCHAR_MAMEKEY(F1) }, + { "F2"sv, UCHAR_MAMEKEY(F2) }, + { "F3"sv, UCHAR_MAMEKEY(F3) }, + { "F4"sv, UCHAR_MAMEKEY(F4) }, + { "F5"sv, UCHAR_MAMEKEY(F5) }, + { "F6"sv, UCHAR_MAMEKEY(F6) }, + { "F7"sv, UCHAR_MAMEKEY(F7) }, + { "F8"sv, UCHAR_MAMEKEY(F8) }, + { "F9"sv, UCHAR_MAMEKEY(F9) }, + { "F10"sv, UCHAR_MAMEKEY(F10) }, + { "F11"sv, UCHAR_MAMEKEY(F11) }, + { "F12"sv, UCHAR_MAMEKEY(F12) }, + { "QUOTE"sv, '\"' } }; // set the fixed rate m_current_rate = rate; - // 0-length means strlen - if (length == 0) - length = strlen(text); - // iterate through the source string - size_t curpos = 0; - while (curpos < length) + while (!text.empty()) { // extract next character - char32_t ch = text[curpos]; - size_t increment = 1; + char32_t ch = text.front(); + std::string_view::size_type increment = 1; // look for escape characters if (ch == '{') for (auto & code : codes) { - size_t keylen = strlen(code.key); - if (curpos + keylen + 2 <= length) - if (core_strnicmp(code.key, &text[curpos + 1], keylen) == 0 && text[curpos + keylen + 1] == '}') + std::string_view::size_type keylen = code.key.length(); + if (keylen + 2 <= text.length()) + if (util::strequpper(text.substr(1, keylen), code.key) && text[keylen + 1] == '}') { ch = code.code; increment = keylen + 2; @@ -588,18 +567,11 @@ void natural_keyboard::post_coded(const char *text, size_t length, const attotim // if we got a code, post it if (ch != 0) post_char(ch); - curpos += increment; + text.remove_prefix(increment); } } -void natural_keyboard::post_coded(std::string_view text, const attotime &rate) -{ - if (!text.empty()) - post_coded(text.data(), text.size(), rate); -} - - //------------------------------------------------- // paste - does a paste from the keyboard //------------------------------------------------- @@ -704,11 +676,11 @@ void natural_keyboard::build_codes() { if (!(curshift & ~mask)) { - // fetch the code, ignoring 0 and shifters + // fetch the code, ignoring shifters std::vector<char32_t> const codes = field.keyboard_codes(curshift); for (char32_t code : codes) { - if (((code < UCHAR_SHIFT_BEGIN) || (code > UCHAR_SHIFT_END)) && (code != 0)) + if ((code < UCHAR_SHIFT_BEGIN) || (code > UCHAR_SHIFT_END)) { m_have_charkeys = true; keycode_map::iterator const found(devinfo.codemap.find(code)); @@ -855,10 +827,19 @@ void natural_keyboard::internal_post(char32_t ch) } // add to the buffer, resizing if necessary - m_buffer[m_bufend++] = ch; - if ((m_bufend + 1) % m_buffer.size() == m_bufbegin) - m_buffer.resize(m_buffer.size() + KEY_BUFFER_SIZE); - m_bufend %= m_buffer.size(); + m_buffer[m_bufend] = ch; + size_t size = m_buffer.size(); + + if ((m_bufend + 1) % size == m_bufbegin) + { + if (size >= KEY_BUFFER_MAX_SIZE) + return; + + m_buffer.insert(m_buffer.begin() + m_bufbegin, KEY_BUFFER_CHUNK_SIZE, INVALID_CHAR); + m_bufbegin += KEY_BUFFER_CHUNK_SIZE; + } + + m_bufend = (m_bufend + 1) % size; } diff --git a/src/emu/natkeyboard.h b/src/emu/natkeyboard.h index c14de66fa66..57b58ffd173 100644 --- a/src/emu/natkeyboard.h +++ b/src/emu/natkeyboard.h @@ -45,7 +45,6 @@ public: // getters and queries running_machine &machine() const { return m_machine; } bool empty() const { return (m_bufbegin == m_bufend); } - bool full() const { return ((m_bufend + 1) % m_buffer.size()) == m_bufbegin; } bool can_post() const { return m_have_charkeys || !m_queue_chars.isnull(); } bool is_posting() const { return (!empty() || (!m_charqueue_empty.isnull() && !m_charqueue_empty())); } bool in_use() const { return m_in_use; } @@ -62,10 +61,8 @@ public: // posting void post_char(char32_t ch, bool normalize_crlf = false); - void post(const char32_t *text, size_t length = 0, const attotime &rate = attotime::zero); - void post_utf8(const char *text, size_t length = 0, const attotime &rate = attotime::zero); + void post(std::u32string_view text, const attotime &rate = attotime::zero); void post_utf8(std::string_view text, const attotime &rate = attotime::zero); - void post_coded(const char *text, size_t length = 0, const attotime &rate = attotime::zero); void post_coded(std::string_view text, const attotime &rate = attotime::zero); void paste(); diff --git a/src/emu/network.cpp b/src/emu/network.cpp index 4566298a994..5953031133e 100644 --- a/src/emu/network.cpp +++ b/src/emu/network.cpp @@ -87,10 +87,11 @@ void network_manager::config_save(config_type cfg_type, util::xml::data_node *pa { node->set_attribute("tag", network.device().tag()); node->set_attribute_int("interface", network.get_interface()); - const char *mac = network.get_mac(); - char mac_addr[6 * 3]; - sprintf(mac_addr, "%02x:%02x:%02x:%02x:%02x:%02x", u8(mac[0]), u8(mac[1]), u8(mac[2]), u8(mac[3]), u8(mac[4]), u8(mac[5])); - node->set_attribute("mac", mac_addr); + const std::array<u8, 6> &mac = network.get_mac(); + const std::string mac_addr = util::string_format( + "%02x:%02x:%02x:%02x:%02x:%02x", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + node->set_attribute("mac", mac_addr.c_str()); } } } diff --git a/src/emu/recording.cpp b/src/emu/recording.cpp index d9c25c57767..91bc0e58bf7 100644 --- a/src/emu/recording.cpp +++ b/src/emu/recording.cpp @@ -146,7 +146,10 @@ movie_recording::ptr movie_recording::create(running_machine &machine, screen_de // if we successfully create a recording, set the current time and return it if (result) + { result->set_next_frame_time(machine.time()); + result->set_channel_count(machine.sound().outputs_count()); + } return result; } @@ -190,10 +193,12 @@ bool avi_movie_recording::initialize(running_machine &machine, std::unique_ptr<e info.audio_timescale = machine.sample_rate(); info.audio_sampletime = 1; info.audio_numsamples = 0; - info.audio_channels = 2; + info.audio_channels = machine.sound().outputs_count(); info.audio_samplebits = 16; info.audio_samplerate = machine.sample_rate(); + m_channels = info.audio_channels; + // compute the frame time set_frame_period(attotime::from_ticks(info.video_sampletime, info.video_timescale)); @@ -225,9 +230,9 @@ bool avi_movie_recording::add_sound_to_recording(const s16 *sound, int numsample auto profile = g_profiler.start(PROFILER_MOVIE_REC); // write the next frame - avi_file::error avierr = m_avi_file->append_sound_samples(0, sound + 0, numsamples, 1); - if (avierr == avi_file::error::NONE) - avierr = m_avi_file->append_sound_samples(1, sound + 1, numsamples, 1); + avi_file::error avierr = avi_file::error::NONE; + for (int channel = 0; channel != m_channels && avierr == avi_file::error::NONE; channel ++) + avierr = m_avi_file->append_sound_samples(channel, sound + channel, numsamples, m_channels-1); return avierr == avi_file::error::NONE; } diff --git a/src/emu/recording.h b/src/emu/recording.h index 0a26c5c7db8..98798063a73 100644 --- a/src/emu/recording.h +++ b/src/emu/recording.h @@ -50,6 +50,7 @@ public: screen_device *screen() { return m_screen; } attotime frame_period() { return m_frame_period; } void set_next_frame_time(attotime time) { m_next_frame_time = time; } + void set_channel_count(int channels) { m_channels = channels; } attotime next_frame_time() const { return m_next_frame_time; } // methods @@ -63,6 +64,8 @@ public: static const char *format_file_extension(format fmt); protected: + int m_channels; // count of audio channels + // ctor movie_recording(screen_device *screen); movie_recording(const movie_recording &) = delete; diff --git a/src/emu/render.cpp b/src/emu/render.cpp index 8251eabb2ec..545827262bb 100644 --- a/src/emu/render.cpp +++ b/src/emu/render.cpp @@ -57,6 +57,7 @@ #include "util/xmlfile.h" #include <algorithm> +#include <limits> @@ -99,6 +100,40 @@ struct render_target::object_transform }; +struct render_target::pointer_info +{ + pointer_info() noexcept + : type(osd::ui_event_handler::pointer::UNKNOWN) + , oldpos(std::numeric_limits<float>::min(), std::numeric_limits<float>::min()) + , newpos(std::numeric_limits<float>::min(), std::numeric_limits<float>::min()) + , oldbuttons(0U) + , newbuttons(0U) + , edges(0U, 0U) + { + } + + osd::ui_event_handler::pointer type; + std::pair<float, float> oldpos; + std::pair<float, float> newpos; + u32 oldbuttons, newbuttons; + std::pair<size_t, size_t> edges; +}; + + +struct render_target::hit_test +{ + hit_test() noexcept + : inbounds(0U, 0U) + , hit(0U) + { + } + + std::pair<u64, u64> inbounds; + u64 hit; +}; + + + //************************************************************************** // GLOBAL VARIABLES @@ -301,7 +336,8 @@ render_texture::render_texture() m_curseq(0) { m_sbounds.set(0, -1, 0, -1); - memset(m_scaled, 0, sizeof(m_scaled)); + for (auto &elem : m_scaled) + elem.seqid = 0; } @@ -883,19 +919,21 @@ render_container::user_settings::user_settings() // render_target - constructor //------------------------------------------------- -render_target::render_target(render_manager &manager, const internal_layout *layoutfile, u32 flags) - : render_target(manager, layoutfile, flags, CONSTRUCTOR_IMPL) +render_target::render_target(render_manager &manager, render_container *ui, const internal_layout *layoutfile, u32 flags) + : render_target(manager, ui, layoutfile, flags, CONSTRUCTOR_IMPL) { } -render_target::render_target(render_manager &manager, util::xml::data_node const &layout, u32 flags) - : render_target(manager, layout, flags, CONSTRUCTOR_IMPL) +render_target::render_target(render_manager &manager, render_container *ui, util::xml::data_node const &layout, u32 flags) + : render_target(manager, ui, layout, flags, CONSTRUCTOR_IMPL) { } -template <typename T> render_target::render_target(render_manager &manager, T &&layout, u32 flags, constructor_impl_t) +template <typename T> +render_target::render_target(render_manager &manager, render_container *ui, T &&layout, u32 flags, constructor_impl_t) : m_next(nullptr) , m_manager(manager) + , m_ui_container(ui) , m_curview(0U) , m_flags(flags) , m_listindex(0) @@ -1030,9 +1068,12 @@ void render_target::set_view(unsigned viewindex) { if (m_views.size() > viewindex) { + forget_pointers(); m_curview = viewindex; current_view().recompute(visibility_mask(), m_layerconfig.zoom_to_screen()); current_view().preload(); + m_clickable_items.clear(); + m_clickable_items.resize(current_view().interactive_items().size()); } } @@ -1050,6 +1091,229 @@ void render_target::set_max_texture_size(int maxwidth, int maxheight) //------------------------------------------------- +// pointer_updated - pointer activity within +// target +//------------------------------------------------- + +void render_target::pointer_updated( + osd::ui_event_handler::pointer type, + u16 ptrid, + u16 device, + s32 x, + s32 y, + u32 buttons, + u32 pressed, + u32 released, + s16 clicks) +{ + auto const target_f(map_point_layout(x, y)); + current_view().pointer_updated(type, ptrid, device, target_f.first, target_f.second, buttons, pressed, released, clicks); + + // 64 pointers ought to be enough for anyone + if (64 <= ptrid) + return; + + // just store the updated pointer state + if (m_pointers.size() <= ptrid) + m_pointers.resize(ptrid + 1); + m_pointers[ptrid].type = type; + m_pointers[ptrid].newpos = target_f; + m_pointers[ptrid].newbuttons = buttons; +} + + +//------------------------------------------------- +// pointer_left - pointer left target normally +//------------------------------------------------- + +void render_target::pointer_left( + osd::ui_event_handler::pointer type, + u16 ptrid, + u16 device, + s32 x, + s32 y, + u32 released, + s16 clicks) +{ + auto const target_f(map_point_layout(x, y)); + current_view().pointer_left(type, ptrid, device, target_f.first, target_f.second, released, clicks); + + // store the updated state if relevant + if (m_pointers.size() > ptrid) + { + m_pointers[ptrid].newpos = std::make_pair(std::numeric_limits<float>::min(), std::numeric_limits<float>::min()); + m_pointers[ptrid].newbuttons = 0; + } +} + + +//------------------------------------------------- +// pointer_aborted - pointer left target +// abnormally +//------------------------------------------------- + +void render_target::pointer_aborted( + osd::ui_event_handler::pointer type, + u16 ptrid, + u16 device, + s32 x, + s32 y, + u32 released, + s16 clicks) +{ + // let layout scripts handle pointer input + auto const target_f(map_point_layout(x, y)); + current_view().pointer_aborted(type, ptrid, device, target_f.first, target_f.second, released, clicks); + + // store the updated state if relevant + if (m_pointers.size() > ptrid) + { + m_pointers[ptrid].newpos = std::make_pair(std::numeric_limits<float>::min(), std::numeric_limits<float>::min()); + m_pointers[ptrid].newbuttons = 0; + } +} + + +//------------------------------------------------- +// forget_pointers - stop processing pointer +// input +//------------------------------------------------- + +void render_target::forget_pointers() +{ + current_view().forget_pointers(); + m_pointers.clear(); + for (size_t i = 0; m_clickable_items.size() > i; ++i) + { + if (m_clickable_items[i].hit) + { + layout_view_item const &item(current_view().interactive_items()[i]); + auto const [port, mask] = item.input_tag_and_mask(); + ioport_field *const field(port ? port->field(mask) : nullptr); + if (field) + field->set_value(0); + } + m_clickable_items[i] = hit_test(); + } +} + + +//------------------------------------------------- +// update_pointer_fields - update inputs for new +// pointer state +//------------------------------------------------- + +void render_target::update_pointer_fields() +{ + auto const &x_edges(current_view().interactive_edges_x()); + auto const &y_edges(current_view().interactive_edges_y()); + + // update items bounds intersection checks for pointers + for (size_t ptr = 0; m_pointers.size() > ptr; ++ptr) + { + auto const [x, y] = m_pointers[ptr].newpos; + auto edges = m_pointers[ptr].edges; + + // check for moving across horizontal edges + if (x < m_pointers[ptr].oldpos.first) + { + while (edges.first && (x < x_edges[edges.first - 1].position())) + { + --edges.first; + auto const &edge(x_edges[edges.first]); + if (edge.trailing()) + m_clickable_items[edge.index()].inbounds.first |= u64(1) << ptr; + else + m_clickable_items[edge.index()].inbounds.first &= ~(u64(1) << ptr); + } + } + else if (x > m_pointers[ptr].oldpos.first) + { + while ((x_edges.size() > edges.first) && (x >= x_edges[edges.first].position())) + { + auto const &edge(x_edges[edges.first]); + if (edge.trailing()) + m_clickable_items[edge.index()].inbounds.first &= ~(u64(1) << ptr); + else + m_clickable_items[edge.index()].inbounds.first |= u64(1) << ptr; + ++edges.first; + } + } + + // check for moving across vertical edges + if (y < m_pointers[ptr].oldpos.second) + { + while (edges.second && (y < y_edges[edges.second - 1].position())) + { + --edges.second; + auto const &edge(y_edges[edges.second]); + if (edge.trailing()) + m_clickable_items[edge.index()].inbounds.second |= u64(1) << ptr; + else + m_clickable_items[edge.index()].inbounds.second &= ~(u64(1) << ptr); + } + } + else if (y > m_pointers[ptr].oldpos.second) + { + while ((y_edges.size() > edges.second) && (y >= y_edges[edges.second].position())) + { + auto const &edge(y_edges[edges.second]); + if (edge.trailing()) + m_clickable_items[edge.index()].inbounds.second &= ~(u64(1) << ptr); + else + m_clickable_items[edge.index()].inbounds.second |= u64(1) << ptr; + ++edges.second; + } + } + + // update the pointer's state + m_pointers[ptr].oldpos = m_pointers[ptr].newpos; + m_pointers[ptr].oldbuttons = m_pointers[ptr].newbuttons; + m_pointers[ptr].edges = edges; + } + + // update item hit states + u64 obscured(0U); + for (size_t i = 0; m_clickable_items.size() > i; ++i) + { + layout_view_item const &item(current_view().interactive_items()[i]); + u64 const inbounds(m_clickable_items[i].inbounds.first & m_clickable_items[i].inbounds.second); + u64 hit(m_clickable_items[i].hit); + for (unsigned ptr = 0; m_pointers.size() > ptr; ++ptr) + { + pointer_info const &pointer(m_pointers[ptr]); + bool const prefilter(BIT(~obscured & inbounds, ptr)); + if (!prefilter || !BIT(pointer.newbuttons, 0) || !item.bounds().includes(pointer.newpos.first, pointer.newpos.second)) + { + hit &= ~(u64(1) << ptr); + } + else + { + hit |= u64(1) << ptr; + if (!item.clickthrough()) + obscured |= u64(1) << ptr; + } + } + + // update field state + if (bool(hit) != bool(m_clickable_items[i].hit)) + { + auto const [port, mask] = item.input_tag_and_mask(); + ioport_field *const field(port ? port->field(mask) : nullptr); + if (field) + { + if (hit) + field->set_value(1); + else + field->clear_value(); + } + } + m_clickable_items[i].hit = hit; + } +} + + +//------------------------------------------------- // set_visibility_toggle - show or hide selected // parts of a view //------------------------------------------------- @@ -1061,7 +1325,7 @@ void render_target::set_visibility_toggle(unsigned index, bool enable) m_views[m_curview].second |= u32(1) << index; else m_views[m_curview].second &= ~(u32(1) << index); - current_view().recompute(visibility_mask(), m_layerconfig.zoom_to_screen()); + update_layer_config(); current_view().preload(); } @@ -1454,8 +1718,8 @@ render_primitive_list &render_target::get_primitives() } } - // process the UI if we are the UI target - if (is_ui_target()) + // process UI elements if applicable + if (m_ui_container) { // compute the transform for the UI object_transform ui_xform; @@ -1468,7 +1732,7 @@ render_primitive_list &render_target::get_primitives() ui_xform.no_center = false; // add UI elements - add_container_primitives(list, root_xform, ui_xform, m_manager.ui_container(), BLENDMODE_ALPHA); + add_container_primitives(list, root_xform, ui_xform, *m_ui_container, BLENDMODE_ALPHA); } // optimize the list before handing it off @@ -1489,16 +1753,12 @@ bool render_target::map_point_container(s32 target_x, s32 target_y, render_conta std::pair<float, float> target_f(map_point_internal(target_x, target_y)); // explicitly check for the UI container - if (&container == &m_manager.ui_container()) + if (&container == m_ui_container) { // this hit test went against the UI container - if ((target_f.first >= 0.0f) && (target_f.first < 1.0f) && (target_f.second >= 0.0f) && (target_f.second < 1.0f)) - { - // this point was successfully mapped - container_x = float(target_x) / m_width; - container_y = float(target_y) / m_height; - return true; - } + container_x = float(target_x) / m_width; + container_y = float(target_y) / m_height; + return (target_f.first >= 0.0f) && (target_f.first < 1.0f) && (target_f.second >= 0.0f) && (target_f.second < 1.0f); } else { @@ -1517,15 +1777,12 @@ bool render_target::map_point_container(s32 target_x, s32 target_y, render_conta [&container] (layout_view_item &item) { return &item.screen()->container() == &container; })); if (items.end() != found) { + // point successfully mapped layout_view_item &item(*found); render_bounds const bounds(item.bounds()); - if (bounds.includes(target_f.first, target_f.second)) - { - // point successfully mapped - container_x = (target_f.first - bounds.x0) / bounds.width(); - container_y = (target_f.second - bounds.y0) / bounds.height(); - return true; - } + container_x = (target_f.first - bounds.x0) / bounds.width(); + container_y = (target_f.second - bounds.y0) / bounds.height(); + return bounds.includes(target_f.first, target_f.second); } } @@ -1536,74 +1793,6 @@ bool render_target::map_point_container(s32 target_x, s32 target_y, render_conta //------------------------------------------------- -// map_point_input - attempts to map a point on -// the specified render_target to an input port -// field, if possible -//------------------------------------------------- - -bool render_target::map_point_input(s32 target_x, s32 target_y, ioport_port *&input_port, ioport_value &input_mask, float &input_x, float &input_y) -{ - std::pair<float, float> target_f(map_point_internal(target_x, target_y)); - if (m_orientation & ORIENTATION_FLIP_X) - target_f.first = 1.0f - target_f.first; - if (m_orientation & ORIENTATION_FLIP_Y) - target_f.second = 1.0f - target_f.second; - if (m_orientation & ORIENTATION_SWAP_XY) - std::swap(target_f.first, target_f.second); - - auto const &items(current_view().interactive_items()); - m_hit_test.resize(items.size() * 2); - std::fill(m_hit_test.begin(), m_hit_test.end(), false); - - for (auto const &edge : current_view().interactive_edges_x()) - { - if ((edge.position() > target_f.first) || ((edge.position() == target_f.first) && edge.trailing())) - break; - else - m_hit_test[edge.index()] = !edge.trailing(); - } - - for (auto const &edge : current_view().interactive_edges_y()) - { - if ((edge.position() > target_f.second) || ((edge.position() == target_f.second) && edge.trailing())) - break; - else - m_hit_test[items.size() + edge.index()] = !edge.trailing(); - } - - for (unsigned i = 0; items.size() > i; ++i) - { - if (m_hit_test[i] && m_hit_test[items.size() + i]) - { - layout_view_item &item(items[i]); - render_bounds const bounds(item.bounds()); - if (bounds.includes(target_f.first, target_f.second)) - { - if (item.has_input()) - { - // point successfully mapped - std::tie(input_port, input_mask) = item.input_tag_and_mask(); - input_x = (target_f.first - bounds.x0) / bounds.width(); - input_y = (target_f.second - bounds.y0) / bounds.height(); - return true; - } - else - { - break; - } - } - } - } - - // default to point not mapped - input_port = nullptr; - input_mask = 0; - input_x = input_y = -1.0f; - return false; -} - - -//------------------------------------------------- // invalidate_all - if any of our primitive lists // contain a reference to the given pointer, // clear them @@ -1632,7 +1821,7 @@ void render_target::resolve_tags() for (layout_file &file : m_filelist) file.resolve_tags(); - current_view().recompute(visibility_mask(), m_layerconfig.zoom_to_screen()); + update_layer_config(); current_view().preload(); } @@ -1644,7 +1833,10 @@ void render_target::resolve_tags() void render_target::update_layer_config() { + forget_pointers(); current_view().recompute(visibility_mask(), m_layerconfig.zoom_to_screen()); + m_clickable_items.clear(); + m_clickable_items.resize(current_view().interactive_items().size()); } @@ -2119,11 +2311,10 @@ bool render_target::load_layout_file(const char *dirname, const internal_layout size_t decompressed = 0; do { - size_t actual; - std::error_condition const err = inflater->read( + auto const [err, actual] = read( + *inflater, &tempout[decompressed], - layout_data.decompressed_size - decompressed, - actual); + layout_data.decompressed_size - decompressed); decompressed += actual; if (err) { @@ -2621,6 +2812,25 @@ std::pair<float, float> render_target::map_point_internal(s32 target_x, s32 targ //------------------------------------------------- +// map_point_layout - map point from screen +// coordinates to layout coordinates +//------------------------------------------------- + +std::pair<float, float> render_target::map_point_layout(s32 target_x, s32 target_y) +{ + using std::swap; + std::pair<float, float> result(map_point_internal(target_x, target_y)); + if (m_orientation & ORIENTATION_FLIP_X) + result.first = 1.0f - result.first; + if (m_orientation & ORIENTATION_FLIP_Y) + result.second = 1.0f - result.second; + if (m_orientation & ORIENTATION_SWAP_XY) + swap(result.first, result.second); + return result; +} + + +//------------------------------------------------- // view_name - return the name of the indexed // view, or nullptr if it doesn't exist //------------------------------------------------- @@ -2699,12 +2909,11 @@ void render_target::config_load(util::xml::data_node const *targetnode) set_orientation(orientation_add(rotate, orientation())); // apply the opposite orientation to the UI - if (is_ui_target()) + if (m_ui_container) { - render_container &ui_container = m_manager.ui_container(); - render_container::user_settings settings = ui_container.get_user_settings(); + render_container::user_settings settings = m_ui_container->get_user_settings(); settings.m_orientation = orientation_add(orientation_reverse(rotate), settings.m_orientation); - ui_container.set_user_settings(settings); + m_ui_container->set_user_settings(settings); } } @@ -2744,6 +2953,8 @@ void render_target::config_load(util::xml::data_node const *targetnode) { current_view().recompute(visibility_mask(), m_layerconfig.zoom_to_screen()); current_view().preload(); + m_clickable_items.clear(); + m_clickable_items.resize(current_view().interactive_items().size()); } } } @@ -3105,7 +3316,6 @@ render_manager::render_manager(running_machine &machine) , m_ui_target(nullptr) , m_live_textures(0) , m_texture_id(0) - , m_ui_container(std::make_unique<render_container>(*this)) { // register callbacks machine.configuration().config_register( @@ -3126,7 +3336,7 @@ render_manager::render_manager(running_machine &machine) render_manager::~render_manager() { // free all the containers since they may own textures - m_ui_container.reset(); + m_ui_containers.clear(); m_screen_container_list.clear(); // better not be any outstanding textures when we die @@ -3182,12 +3392,14 @@ float render_manager::max_update_rate() const render_target *render_manager::target_alloc(const internal_layout *layoutfile, u32 flags) { - return &m_targetlist.append(*new render_target(*this, layoutfile, flags)); + render_container *const ui = (flags & RENDER_CREATE_HIDDEN) ? nullptr : &m_ui_containers.emplace_back(*this); + return &m_targetlist.append(*new render_target(*this, ui, layoutfile, flags)); } render_target *render_manager::target_alloc(util::xml::data_node const &layout, u32 flags) { - return &m_targetlist.append(*new render_target(*this, layout, flags)); + render_container *const ui = (flags & RENDER_CREATE_HIDDEN) ? nullptr : &m_ui_containers.emplace_back(*this); + return &m_targetlist.append(*new render_target(*this, ui, layout, flags)); } @@ -3224,35 +3436,56 @@ render_target *render_manager::target_by_index(int index) const float render_manager::ui_aspect(render_container *rc) { - int orient; + // work out if this is a UI container + render_target *target = nullptr; + if (!rc) + { + target = &ui_target(); + rc = target->ui_container(); + assert(rc); + } + else + { + for (render_target &t : m_targetlist) + { + if (t.ui_container() == rc) + { + target = &t; + break; + } + } + } + float aspect; - if (rc == m_ui_container.get() || rc == nullptr) { - // ui container, aggregated multi-screen target + if (target) + { + // UI container, aggregated multi-screen target - orient = orientation_add(m_ui_target->orientation(), m_ui_container->orientation()); // based on the orientation of the target, compute height/width or width/height + int const orient = orientation_add(target->orientation(), rc->orientation()); if (!(orient & ORIENTATION_SWAP_XY)) - aspect = (float)m_ui_target->height() / (float)m_ui_target->width(); + aspect = float(target->height()) / float(target->width()); else - aspect = (float)m_ui_target->width() / (float)m_ui_target->height(); + aspect = float(target->width()) / float(target->height()); // if we have a valid pixel aspect, apply that and return - if (m_ui_target->pixel_aspect() != 0.0f) + if (target->pixel_aspect() != 0.0f) { - float pixel_aspect = m_ui_target->pixel_aspect(); + float pixel_aspect = target->pixel_aspect(); if (orient & ORIENTATION_SWAP_XY) pixel_aspect = 1.0f / pixel_aspect; return aspect /= pixel_aspect; } - - } else { + } + else + { // single screen container - orient = rc->orientation(); // based on the orientation of the target, compute height/width or width/height + int const orient = rc->orientation(); if (!(orient & ORIENTATION_SWAP_XY)) aspect = (float)rc->screen()->visible_area().height() / (float)rc->screen()->visible_area().width(); else diff --git a/src/emu/render.h b/src/emu/render.h index 2faa5f46394..c4c0f504edd 100644 --- a/src/emu/render.h +++ b/src/emu/render.h @@ -48,6 +48,8 @@ #include "rendertypes.h" +#include "interface/uievents.h" + #include <cmath> #include <list> #include <memory> @@ -492,14 +494,15 @@ class render_target friend class render_manager; // construction/destruction - render_target(render_manager &manager, const internal_layout *layoutfile = nullptr, u32 flags = 0); - render_target(render_manager &manager, util::xml::data_node const &layout, u32 flags = 0); + render_target(render_manager &manager, render_container *ui, const internal_layout *layoutfile, u32 flags); + render_target(render_manager &manager, render_container *ui, util::xml::data_node const &layout, u32 flags); ~render_target(); public: // getters render_target *next() const { return m_next; } render_manager &manager() const { return m_manager; } + render_container *ui_container() const { return m_ui_container; } u32 width() const { return m_width; } u32 height() const { return m_height; } float pixel_aspect() const { return m_pixel_aspect; } @@ -525,6 +528,13 @@ public: void set_keepaspect(bool keepaspect) { m_keepaspect = keepaspect; } void set_scale_mode(int scale_mode) { m_scale_mode = scale_mode; } + // pointer input handling + void pointer_updated(osd::ui_event_handler::pointer type, u16 ptrid, u16 device, s32 x, s32 y, u32 buttons, u32 pressed, u32 released, s16 clicks); + void pointer_left(osd::ui_event_handler::pointer type, u16 ptrid, u16 device, s32 x, s32 y, u32 released, s16 clicks); + void pointer_aborted(osd::ui_event_handler::pointer type, u16 ptrid, u16 device, s32 x, s32 y, u32 released, s16 clicks); + void forget_pointers(); + void update_pointer_fields(); + // layer config getters bool screen_overlay_enabled() const { return m_layerconfig.screen_overlay_enabled(); } bool zoom_to_screen() const { return m_layerconfig.zoom_to_screen(); } @@ -550,7 +560,6 @@ public: // hit testing bool map_point_container(s32 target_x, s32 target_y, render_container &container, float &container_x, float &container_y); - bool map_point_input(s32 target_x, s32 target_y, ioport_port *&input_port, ioport_value &input_mask, float &input_x, float &input_y); // reference tracking void invalidate_all(void *refptr); @@ -559,15 +568,24 @@ public: void resolve_tags(); private: + // constants + static inline constexpr int NUM_PRIMLISTS = 3; + static inline constexpr int MAX_CLEAR_EXTENTS = 1000; + using view_mask_pair = std::pair<layout_view &, u32>; using view_mask_vector = std::vector<view_mask_pair>; // private classes declared in render.cpp struct object_transform; + struct pointer_info; + struct hit_test; + + using pointer_info_vector = std::vector<pointer_info>; + using hit_test_vector = std::vector<hit_test>; // internal helpers enum constructor_impl_t { CONSTRUCTOR_IMPL }; - template <typename T> render_target(render_manager &manager, T&& layout, u32 flags, constructor_impl_t); + template <typename T> render_target(render_manager &manager, render_container *ui, T&& layout, u32 flags, constructor_impl_t); void update_layer_config(); void load_layout_files(const internal_layout *layoutfile, bool singlefile); void load_layout_files(util::xml::data_node const &rootnode, bool singlefile); @@ -578,6 +596,7 @@ private: void add_container_primitives(render_primitive_list &list, const object_transform &root_xform, const object_transform &xform, render_container &container, int blendmode); void add_element_primitives(render_primitive_list &list, const object_transform &xform, layout_view_item &item); std::pair<float, float> map_point_internal(s32 target_x, s32 target_y); + std::pair<float, float> map_point_layout(s32 target_x, s32 target_y); // config callbacks void config_load(util::xml::data_node const *targetnode); @@ -593,13 +612,10 @@ private: void add_clear_extents(render_primitive_list &list); void add_clear_and_optimize_primitive_list(render_primitive_list &list); - // constants - static constexpr int NUM_PRIMLISTS = 3; - static constexpr int MAX_CLEAR_EXTENTS = 1000; - // internal state render_target * m_next; // link to next target render_manager & m_manager; // reference to our owning manager + render_container *const m_ui_container; // container for drawing UI elements std::list<layout_file> m_filelist; // list of layout files view_mask_vector m_views; // views we consider unsigned m_curview; // current view index @@ -618,7 +634,8 @@ private: float m_max_refresh; // maximum refresh rate, 0 or if none int m_orientation; // orientation render_layer_config m_layerconfig; // layer configuration - std::vector<bool> m_hit_test; // used when mapping points to inputs + pointer_info_vector m_pointers; // state of pointers over this target + hit_test_vector m_clickable_items; // for tracking clicked elements layout_view * m_base_view; // the view at the time of first frame int m_base_orientation; // the orientation at the time of first frame render_layer_config m_base_layerconfig; // the layer configuration at the time of first frame @@ -665,7 +682,7 @@ public: float ui_aspect(render_container *rc = nullptr); // UI containers - render_container &ui_container() const { assert(m_ui_container != nullptr); return *m_ui_container; } + render_container &ui_container() const { assert(ui_target().ui_container()); return *ui_target().ui_container(); } // textures render_texture *texture_alloc(texture_scaler_func scaler = nullptr, void *param = nullptr); @@ -686,20 +703,20 @@ private: void config_save(config_type cfg_type, util::xml::data_node *parentnode); // internal state - running_machine & m_machine; // reference back to the machine + running_machine & m_machine; // reference back to the machine // array of live targets - simple_list<render_target> m_targetlist; // list of targets - render_target * m_ui_target; // current UI target + simple_list<render_target> m_targetlist; // list of targets + render_target * m_ui_target; // current UI target // texture lists - u32 m_live_textures; // number of live textures - u64 m_texture_id; // rolling texture ID counter - fixed_allocator<render_texture> m_texture_allocator;// texture allocator + u32 m_live_textures; // number of live textures + u64 m_texture_id; // rolling texture ID counter + fixed_allocator<render_texture> m_texture_allocator; // texture allocator - // containers for the UI and for screens - std::unique_ptr<render_container> m_ui_container; // UI container - std::list<render_container> m_screen_container_list; // list of containers for the screen + // containers for UI elements and for screens + std::list<render_container> m_ui_containers; // containers for drawing UI elements + std::list<render_container> m_screen_container_list; // list of containers for the screen }; #endif // MAME_EMU_RENDER_H diff --git a/src/emu/rendersw.hxx b/src/emu/rendersw.hxx index 5561928e7df..d4fde37566e 100644 --- a/src/emu/rendersw.hxx +++ b/src/emu/rendersw.hxx @@ -14,6 +14,7 @@ #include "video/rgbutil.h" #include "render.h" +#include <array> template <typename PixelType, int SrcShiftR, int SrcShiftG, int SrcShiftB, int DstShiftR, int DstShiftG, int DstShiftB, bool NoDestRead = false, bool BilinearFilter = false> class software_renderer @@ -29,10 +30,15 @@ private: }; // internal helpers + template <int... Values> + static auto make_cosine_table(std::integer_sequence<int, Values...>) + { + return std::array<u32, sizeof...(Values)>{ u32((1.0 / cos(atan(double(Values) / double(sizeof...(Values) - 1)))) * 0x10000000 + 0.5)... }; + } static constexpr bool is_opaque(float alpha) { return (alpha >= (NoDestRead ? 0.5f : 1.0f)); } static constexpr bool is_transparent(float alpha) { return (alpha < (NoDestRead ? 0.5f : 0.0001f)); } - static inline rgb_t apply_intensity(int intensity, rgb_t color) { return color.scale8(intensity); } - static inline float round_nearest(float f) { return floor(f + 0.5f); } + static rgb_t apply_intensity(int intensity, rgb_t color) { return color.scale8(intensity); } + static float round_nearest(float f) { return floor(f + 0.5f); } // destination pixels are written based on the values of the template parameters static constexpr PixelType dest_assemble_rgb(u32 r, u32 g, u32 b) { return (r << DstShiftR) | (g << DstShiftG) | (b << DstShiftB); } @@ -426,8 +432,8 @@ private: static void draw_line(render_primitive const &prim, PixelType *dstdata, s32 width, s32 height, u32 pitch) { - // internal tables - static u32 s_cosine_table[2049]; + // internal cosine table generated at compile time + static auto const s_cosine_table = make_cosine_table(std::make_integer_sequence<int, 2049>()); // compute the start/end coordinates int x1 = int(prim.bounds.x0 * 65536.0f); @@ -440,11 +446,6 @@ private: if (PRIMFLAG_GET_ANTIALIAS(prim.flags)) { - // build up the cosine table if we haven't yet - if (s_cosine_table[0] == 0) - for (int entry = 0; entry <= 2048; entry++) - s_cosine_table[entry] = int(double(1.0 / cos(atan(double(entry) / 2048.0))) * 0x10000000 + 0.5); - int beam = prim.width * 65536.0f; if (beam < 0x00010000) beam = 0x00010000; @@ -473,9 +474,9 @@ private: draw_aa_pixel(dstdata, pitch, x1, dy, apply_intensity(0xff & (~y1 >> 8), col)); dy++; dx -= 0x10000 - (0xffff & y1); // take off amount plotted - u8 a1 = (dx >> 8) & 0xff; // calc remainder pixel - dx >>= 16; // adjust to pixel (solid) count - while (dx--) // plot rest of pixels + u8 a1 = (dx >> 8) & 0xff; // calc remainder pixel + dx >>= 16; // adjust to pixel (solid) count + while (dx--) // plot rest of pixels { if (dy >= 0 && dy < height) draw_aa_pixel(dstdata, pitch, x1, dy, col); @@ -509,9 +510,9 @@ private: draw_aa_pixel(dstdata, pitch, dx, y1, apply_intensity(0xff & (~x1 >> 8), col)); dx++; dy -= 0x10000 - (0xffff & x1); // take off amount plotted - u8 a1 = (dy >> 8) & 0xff; // remainder pixel - dy >>= 16; // adjust to pixel (solid) count - while (dy--) // plot rest of pixels + u8 a1 = (dy >> 8) & 0xff; // remainder pixel + dy >>= 16; // adjust to pixel (solid) count + while (dy--) // plot rest of pixels { if (dx >= 0 && dx < width) draw_aa_pixel(dstdata, pitch, dx, y1, col); @@ -576,7 +577,6 @@ private: } - //************************************************************************** // RECT RASTERIZERS //************************************************************************** @@ -663,19 +663,17 @@ private: //************************************************************************** - // 16-BIT PALETTE RASTERIZERS + // 16-BIT RASTERIZERS //************************************************************************** //------------------------------------------------- - // draw_quad_palette16_none - perform - // rasterization of a 16bpp palettized texture + // draw_quad_convert_none - perform + // rasterization of a texture after conversion //------------------------------------------------- - static void draw_quad_palette16_none(render_primitive const &prim, PixelType *dstdata, u32 pitch, quad_setup_data const &setup) + template <typename T> + static void draw_quad_convert_none(render_primitive const &prim, PixelType *dstdata, u32 pitch, quad_setup_data const &setup, T &&gettexel) { - // ensure all parameters are valid - assert(prim.texture.palette != nullptr); - if (prim.color.r >= 1.0f && prim.color.g >= 1.0f && prim.color.b >= 1.0f && is_opaque(prim.color.a)) { // fast case: no coloring, no alpha @@ -690,7 +688,7 @@ private: // loop over cols for (s32 x = setup.startx; x < setup.endx; x++) { - u32 const pix = get_texel_palette16(prim.texture, curu, curv); + u32 const pix = gettexel(prim, curu, curv); *dest++ = source32_to_dest(pix); curu += setup.dudx; curv += setup.dvdx; @@ -716,7 +714,7 @@ private: // loop over cols for (s32 x = setup.startx; x < setup.endx; x++) { - u32 const pix = get_texel_palette16(prim.texture, curu, curv); + u32 const pix = gettexel(prim, curu, curv); u32 const r = (source32_r(pix) * sr) >> 8; u32 const g = (source32_g(pix) * sg) >> 8; u32 const b = (source32_b(pix) * sb) >> 8; @@ -747,7 +745,7 @@ private: // loop over cols for (s32 x = setup.startx; x < setup.endx; x++) { - u32 const pix = get_texel_palette16(prim.texture, curu, curv); + u32 const pix = gettexel(prim, curu, curv); u32 const dpix = NoDestRead ? 0 : *dest; u32 const r = (source32_r(pix) * sr + dest_r(dpix) * invsa) >> 8; u32 const g = (source32_g(pix) * sg + dest_g(dpix) * invsa) >> 8; @@ -763,11 +761,12 @@ private: //------------------------------------------------- - // draw_quad_palette16_add - perform - // rasterization of a 16bpp palettized texture + // draw_quad_convert_rop - perform rasterization + // by using RGB operation after conversion //------------------------------------------------- - static void draw_quad_palette16_add(render_primitive const &prim, PixelType *dstdata, u32 pitch, quad_setup_data const &setup) + template <typename T, typename U, typename V> + static void draw_quad_convert_rop(render_primitive const &prim, PixelType *dstdata, u32 pitch, quad_setup_data const &setup, T &&gettexel, U &¬int, V &&tinted) { // ensure all parameters are valid assert(prim.texture.palette != nullptr); @@ -786,18 +785,8 @@ private: // loop over cols for (s32 x = setup.startx; x < setup.endx; x++) { - const u32 pix = get_texel_palette16(prim.texture, curu, curv); - if ((pix & 0xffffff) != 0) - { - u32 const dpix = NoDestRead ? 0 : *dest; - u32 r = source32_r(pix) + dest_r(dpix); - u32 g = source32_g(pix) + dest_g(dpix); - u32 b = source32_b(pix) + dest_b(dpix); - r = (r | -(r >> (8 - SrcShiftR))) & (0xff >> SrcShiftR); - g = (g | -(g >> (8 - SrcShiftG))) & (0xff >> SrcShiftG); - b = (b | -(b >> (8 - SrcShiftB))) & (0xff >> SrcShiftB); - *dest = dest_assemble_rgb(r, g, b); - } + u32 const pix = gettexel(prim, curu, curv); + notint(*dest, pix); dest++; curu += setup.dudx; curv += setup.dvdx; @@ -823,193 +812,9 @@ private: // loop over cols for (s32 x = setup.startx; x < setup.endx; x++) { - u32 const pix = get_texel_palette16(prim.texture, curu, curv); - if ((pix & 0xffffff) != 0) - { - u32 const dpix = NoDestRead ? 0 : *dest; - u32 r = ((source32_r(pix) * sr) >> 8) + dest_r(dpix); - u32 g = ((source32_g(pix) * sg) >> 8) + dest_g(dpix); - u32 b = ((source32_b(pix) * sb) >> 8) + dest_b(dpix); - r = (r | -(r >> (8 - SrcShiftR))) & (0xff >> SrcShiftR); - g = (g | -(g >> (8 - SrcShiftG))) & (0xff >> SrcShiftG); - b = (b | -(b >> (8 - SrcShiftB))) & (0xff >> SrcShiftB); - *dest++ = dest_assemble_rgb(r, g, b); - curu += setup.dudx; - curv += setup.dvdx; - } - } - } - } - } - - - - //************************************************************************** - // 16-BIT YUY RASTERIZERS - //************************************************************************** - - //------------------------------------------------- - // draw_quad_yuy16_none - perform - // rasterization of a 16bpp YUY image - //------------------------------------------------- - - static void draw_quad_yuy16_none(render_primitive const &prim, PixelType *dstdata, u32 pitch, quad_setup_data const &setup) - { - if (prim.color.r >= 1.0f && prim.color.g >= 1.0f && prim.color.b >= 1.0f && is_opaque(prim.color.a)) - { - // fast case: no coloring, no alpha - - // loop over rows - for (s32 y = setup.starty; y < setup.endy; y++) - { - PixelType *dest = dstdata + y * pitch + setup.startx; - s32 curu = setup.startu + (y - setup.starty) * setup.dudy; - s32 curv = setup.startv + (y - setup.starty) * setup.dvdy; - - // loop over cols - for (s32 x = setup.startx; x < setup.endx; x++) - { - u32 const pix = ycc_to_rgb(get_texel_yuy16(prim.texture, curu, curv)); - *dest++ = source32_to_dest(pix); - curu += setup.dudx; - curv += setup.dvdx; - } - } - } - else if (is_opaque(prim.color.a)) - { - // coloring-only case - - // clamp R,G,B to 0-256 range - u32 const sr = u32(std::clamp(256.0f * prim.color.r, 0.0f, 256.0f)); - u32 const sg = u32(std::clamp(256.0f * prim.color.g, 0.0f, 256.0f)); - u32 const sb = u32(std::clamp(256.0f * prim.color.b, 0.0f, 256.0f)); - - // loop over rows - for (s32 y = setup.starty; y < setup.endy; y++) - { - PixelType *dest = dstdata + y * pitch + setup.startx; - s32 curu = setup.startu + (y - setup.starty) * setup.dudy; - s32 curv = setup.startv + (y - setup.starty) * setup.dvdy; - - // loop over cols - for (s32 x = setup.startx; x < setup.endx; x++) - { - u32 const pix = ycc_to_rgb(get_texel_yuy16(prim.texture, curu, curv)); - u32 const r = (source32_r(pix) * sr) >> 8; - u32 const g = (source32_g(pix) * sg) >> 8; - u32 const b = (source32_b(pix) * sb) >> 8; - - *dest++ = dest_assemble_rgb(r, g, b); - curu += setup.dudx; - curv += setup.dvdx; - } - } - } - else if (!is_transparent(prim.color.a)) - { - // alpha and/or coloring case - - // clamp R,G,B and inverse A to 0-256 range - u32 const sr = u32(std::clamp(256.0f * prim.color.r * prim.color.a, 0.0f, 256.0f)); - u32 const sg = u32(std::clamp(256.0f * prim.color.g * prim.color.a, 0.0f, 256.0f)); - u32 const sb = u32(std::clamp(256.0f * prim.color.b * prim.color.a, 0.0f, 256.0f)); - u32 const invsa = u32(std::clamp(256.0f * (1.0f - prim.color.a), 0.0f, 256.0f)); - - // loop over rows - for (s32 y = setup.starty; y < setup.endy; y++) - { - PixelType *dest = dstdata + y * pitch + setup.startx; - s32 curu = setup.startu + (y - setup.starty) * setup.dudy; - s32 curv = setup.startv + (y - setup.starty) * setup.dvdy; - - // loop over cols - for (s32 x = setup.startx; x < setup.endx; x++) - { - u32 const pix = ycc_to_rgb(get_texel_yuy16(prim.texture, curu, curv)); - u32 const dpix = NoDestRead ? 0 : *dest; - u32 const r = (source32_r(pix) * sr + dest_r(dpix) * invsa) >> 8; - u32 const g = (source32_g(pix) * sg + dest_g(dpix) * invsa) >> 8; - u32 const b = (source32_b(pix) * sb + dest_b(dpix) * invsa) >> 8; - - *dest++ = dest_assemble_rgb(r, g, b); - curu += setup.dudx; - curv += setup.dvdx; - } - } - } - } - - - //------------------------------------------------- - // draw_quad_yuy16_add - perform - // rasterization by using RGB add after YUY - // conversion - //------------------------------------------------- - - static void draw_quad_yuy16_add(render_primitive const &prim, PixelType *dstdata, u32 pitch, quad_setup_data const &setup) - { - // simply can't do this without reading from the dest - if constexpr (NoDestRead) - return; - - if (prim.color.r >= 1.0f && prim.color.g >= 1.0f && prim.color.b >= 1.0f && is_opaque(prim.color.a)) - { - // fast case: no coloring, no alpha - - // loop over rows - for (s32 y = setup.starty; y < setup.endy; y++) - { - PixelType *dest = dstdata + y * pitch + setup.startx; - s32 curu = setup.startu + (y - setup.starty) * setup.dudy; - s32 curv = setup.startv + (y - setup.starty) * setup.dvdy; - - // loop over cols - for (s32 x = setup.startx; x < setup.endx; x++) - { - u32 const pix = ycc_to_rgb(get_texel_yuy16(prim.texture, curu, curv)); - u32 const dpix = NoDestRead ? 0 : *dest; - u32 r = source32_r(pix) + dest_r(dpix); - u32 g = source32_g(pix) + dest_g(dpix); - u32 b = source32_b(pix) + dest_b(dpix); - r = (r | -(r >> (8 - SrcShiftR))) & (0xff >> SrcShiftR); - g = (g | -(g >> (8 - SrcShiftG))) & (0xff >> SrcShiftG); - b = (b | -(b >> (8 - SrcShiftB))) & (0xff >> SrcShiftB); - *dest++ = dest_assemble_rgb(r, g, b); - curu += setup.dudx; - curv += setup.dvdx; - } - } - } - else - { - // alpha and/or coloring case - - // clamp R,G,B and inverse A to 0-256 range - u32 const sr = u32(std::clamp(256.0f * prim.color.r, 0.0f, 256.0f)); - u32 const sg = u32(std::clamp(256.0f * prim.color.g, 0.0f, 256.0f)); - u32 const sb = u32(std::clamp(256.0f * prim.color.b, 0.0f, 256.0f)); - u32 const sa = u32(std::clamp(256.0f * prim.color.a, 0.0f, 256.0f)); - - // loop over rows - for (s32 y = setup.starty; y < setup.endy; y++) - { - PixelType *dest = dstdata + y * pitch + setup.startx; - s32 curu = setup.startu + (y - setup.starty) * setup.dudy; - s32 curv = setup.startv + (y - setup.starty) * setup.dvdy; - - // loop over cols - for (s32 x = setup.startx; x < setup.endx; x++) - { - const u32 pix = ycc_to_rgb(get_texel_yuy16(prim.texture, curu, curv)); - const u32 dpix = NoDestRead ? 0 : *dest; - u32 r = ((source32_r(pix) * sr * sa) >> 16) + dest_r(dpix); - u32 g = ((source32_g(pix) * sg * sa) >> 16) + dest_g(dpix); - u32 b = ((source32_b(pix) * sb * sa) >> 16) + dest_b(dpix); - r = (r | -(r >> (8 - SrcShiftR))) & (0xff >> SrcShiftR); - g = (g | -(g >> (8 - SrcShiftG))) & (0xff >> SrcShiftG); - b = (b | -(b >> (8 - SrcShiftB))) & (0xff >> SrcShiftB); - *dest++ = dest_assemble_rgb(r, g, b); + u32 const pix = gettexel(prim, curu, curv); + tinted(*dest, pix, sr, sg, sb); + dest++; curu += setup.dudx; curv += setup.dvdx; } @@ -1224,6 +1029,7 @@ private: r = (r | -(r >> (8 - SrcShiftR))) & (0xff >> SrcShiftR); g = (g | -(g >> (8 - SrcShiftG))) & (0xff >> SrcShiftG); b = (b | -(b >> (8 - SrcShiftB))) & (0xff >> SrcShiftB); + *dest++ = dest_assemble_rgb(r, g, b); curu += setup.dudx; curv += setup.dvdx; @@ -1244,6 +1050,7 @@ private: r = (r | -(r >> (8 - SrcShiftR))) & (0xff >> SrcShiftR); g = (g | -(g >> (8 - SrcShiftG))) & (0xff >> SrcShiftG); b = (b | -(b >> (8 - SrcShiftB))) & (0xff >> SrcShiftB); + *dest++ = dest_assemble_rgb(r, g, b); curu += setup.dudx; curv += setup.dvdx; @@ -1283,6 +1090,7 @@ private: r = (r | -(r >> (8 - SrcShiftR))) & (0xff >> SrcShiftR); g = (g | -(g >> (8 - SrcShiftG))) & (0xff >> SrcShiftG); b = (b | -(b >> (8 - SrcShiftB))) & (0xff >> SrcShiftB); + *dest++ = dest_assemble_rgb(r, g, b); curu += setup.dudx; curv += setup.dvdx; @@ -1303,6 +1111,7 @@ private: r = (r | -(r >> (8 - SrcShiftR))) & (0xff >> SrcShiftR); g = (g | -(g >> (8 - SrcShiftG))) & (0xff >> SrcShiftG); b = (b | -(b >> (8 - SrcShiftB))) & (0xff >> SrcShiftB); + *dest++ = dest_assemble_rgb(r, g, b); curu += setup.dudx; curv += setup.dvdx; @@ -1783,25 +1592,101 @@ private: setup.startv -= 0x8000; } + auto const gettexel_palette16 = + [] (render_primitive const &prim, s32 u, s32 v) -> u32 + { + return get_texel_palette16(prim.texture, u, v); + }; + auto const gettexel_yuy16 = + [] (render_primitive const &prim, s32 u, s32 v) -> u32 + { + return ycc_to_rgb(get_texel_yuy16(prim.texture, u, v)); + }; + auto const rop_mul_notint = + [] (PixelType &dest, u32 pix) + { + if ((pix & 0x00ff'ffff) != 0x00ff'ffff) + { + u32 const dpix = NoDestRead ? 0x00ff'ffff : dest; + u32 const r = (source32_r(pix) * dest_r(dpix)) >> (8 - SrcShiftR); + u32 const g = (source32_g(pix) * dest_g(dpix)) >> (8 - SrcShiftG); + u32 const b = (source32_b(pix) * dest_b(dpix)) >> (8 - SrcShiftB); + dest = dest_assemble_rgb(r, g, b); + } + }; + auto const rop_mul_tinted = + [] (PixelType &dest, u32 pix, u32 sr, u32 sg, u32 sb) + { + if ((pix & 0x00ff'ffff) != 0x00ff'ffff) + { + u32 const dpix = NoDestRead ? 0x00ff'ffff : dest; + u32 const r = (source32_r(pix) * sr * dest_r(dpix)) >> (16 - SrcShiftR); + u32 const g = (source32_g(pix) * sg * dest_g(dpix)) >> (16 - SrcShiftG); + u32 const b = (source32_b(pix) * sb * dest_b(dpix)) >> (16 - SrcShiftB); + dest = dest_assemble_rgb(r, g, b); + } + }; + auto const rop_add_notint = + [] (PixelType &dest, u32 pix) + { + if ((pix & 0x00ff'ffff) != 0) + { + u32 const dpix = NoDestRead ? 0 : dest; + u32 r = source32_r(pix) + dest_r(dpix); + u32 g = source32_g(pix) + dest_g(dpix); + u32 b = source32_b(pix) + dest_b(dpix); + r = (r | -(r >> (8 - SrcShiftR))) & (0xff >> SrcShiftR); + g = (g | -(g >> (8 - SrcShiftG))) & (0xff >> SrcShiftG); + b = (b | -(b >> (8 - SrcShiftB))) & (0xff >> SrcShiftB); + dest = dest_assemble_rgb(r, g, b); + } + }; + auto const rop_add_tinted = + [] (PixelType &dest, u32 pix, u32 sr, u32 sg, u32 sb) + { + if ((pix & 0x00ff'ffff) != 0) + { + u32 const dpix = NoDestRead ? 0 : dest; + u32 r = ((source32_r(pix) * sr) >> 8) + dest_r(dpix); + u32 g = ((source32_g(pix) * sg) >> 8) + dest_g(dpix); + u32 b = ((source32_b(pix) * sb) >> 8) + dest_b(dpix); + r = (r | -(r >> (8 - SrcShiftR))) & (0xff >> SrcShiftR); + g = (g | -(g >> (8 - SrcShiftG))) & (0xff >> SrcShiftG); + b = (b | -(b >> (8 - SrcShiftB))) & (0xff >> SrcShiftB); + dest = dest_assemble_rgb(r, g, b); + } + }; + // render based on the texture coordinates switch (prim.flags & (PRIMFLAG_TEXFORMAT_MASK | PRIMFLAG_BLENDMODE_MASK)) { case PRIMFLAG_TEXFORMAT(TEXFORMAT_PALETTE16) | PRIMFLAG_BLENDMODE(BLENDMODE_NONE): case PRIMFLAG_TEXFORMAT(TEXFORMAT_PALETTE16) | PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA): - draw_quad_palette16_none(prim, dstdata, pitch, setup); + assert(prim.texture.palette); + draw_quad_convert_none(prim, dstdata, pitch, setup, gettexel_palette16); + break; + + case PRIMFLAG_TEXFORMAT(TEXFORMAT_PALETTE16) | PRIMFLAG_BLENDMODE(BLENDMODE_RGB_MULTIPLY): + assert(prim.texture.palette); + draw_quad_convert_rop(prim, dstdata, pitch, setup, gettexel_palette16, rop_mul_notint, rop_mul_tinted); break; case PRIMFLAG_TEXFORMAT(TEXFORMAT_PALETTE16) | PRIMFLAG_BLENDMODE(BLENDMODE_ADD): - draw_quad_palette16_add(prim, dstdata, pitch, setup); + assert(prim.texture.palette); + draw_quad_convert_rop(prim, dstdata, pitch, setup, gettexel_palette16, rop_add_notint, rop_add_tinted); break; case PRIMFLAG_TEXFORMAT(TEXFORMAT_YUY16) | PRIMFLAG_BLENDMODE(BLENDMODE_NONE): case PRIMFLAG_TEXFORMAT(TEXFORMAT_YUY16) | PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA): - draw_quad_yuy16_none(prim, dstdata, pitch, setup); + draw_quad_convert_none(prim, dstdata, pitch, setup, gettexel_yuy16); + break; + + case PRIMFLAG_TEXFORMAT(TEXFORMAT_YUY16) | PRIMFLAG_BLENDMODE(BLENDMODE_RGB_MULTIPLY): + draw_quad_convert_rop(prim, dstdata, pitch, setup, gettexel_yuy16, rop_mul_notint, rop_mul_tinted); break; case PRIMFLAG_TEXFORMAT(TEXFORMAT_YUY16) | PRIMFLAG_BLENDMODE(BLENDMODE_ADD): - draw_quad_yuy16_add(prim, dstdata, pitch, setup); + draw_quad_convert_rop(prim, dstdata, pitch, setup, gettexel_yuy16, rop_add_notint, rop_add_tinted); break; case PRIMFLAG_TEXFORMAT(TEXFORMAT_RGB32) | PRIMFLAG_BLENDMODE(BLENDMODE_NONE): diff --git a/src/emu/rendfont.cpp b/src/emu/rendfont.cpp index 171a8f6d7f5..259d7731fe5 100644 --- a/src/emu/rendfont.cpp +++ b/src/emu/rendfont.cpp @@ -16,7 +16,6 @@ #include "render.h" #include "corestr.h" -#include "coreutil.h" #include "multibyte.h" #include "path.h" @@ -129,13 +128,12 @@ public: bool read(util::read_stream &f) { - std::size_t actual(0); - return !f.read(m_data, sizeof(m_data), actual) && actual == sizeof(m_data); + auto const [err, actual] = util::read(f, m_data, sizeof(m_data)); + return !err && (actual == sizeof(m_data)); } bool write(util::write_stream &f) { - std::size_t actual(0); - return !f.write(m_data, sizeof(m_data), actual) && actual == sizeof(m_data); + return !util::write(f, m_data, sizeof(m_data)).first; } bool check_magic() const @@ -760,7 +758,8 @@ void render_font::get_scaled_bitmap_and_bounds(bitmap_argb32 &dest, float height bounds.min_y = 0; // compute x1,y1 from there based on the bitmap size - bounds.set_width(float(gl.bmwidth) * scale * aspect); + float width = float(gl.bmwidth) * scale * aspect; + bounds.set_width(width < 0.5f ? 0 : std::max(int(width), 1)); bounds.set_height(float(m_height) * scale); // if the bitmap isn't big enough, bail @@ -884,7 +883,7 @@ bool render_font::load_cached_bdf(std::string_view filename) m_rawdata.clear(); return false; } - u32 const hash(core_crc32(0, reinterpret_cast<u8 const *>(&m_rawdata[0]), bytes)); + u32 const hash(util::crc32_creator::simple(&m_rawdata[0], bytes)); // create the cached filename, changing the 'F' to a 'C' on the extension std::string cachedname(filename, 0, filename.length() - ((4U < filename.length()) && core_filename_ends_with(filename, ".bdf") ? 4 : 0)); @@ -1346,6 +1345,11 @@ bool render_font::load_cached(util::random_read &file, u64 length, u32 hash) // now read the rest of the data u64 const remaining(filesize - filepos); + if (remaining > std::numeric_limits<std::size_t>::max()) + { + osd_printf_error("render_font::load_cached: BDC file is too large to read into memory\n"); + return false; + } try { m_rawdata.resize(std::size_t(remaining)); @@ -1353,18 +1357,14 @@ bool render_font::load_cached(util::random_read &file, u64 length, u32 hash) catch (...) { osd_printf_error("render_font::load_cached: allocation error\n"); + return false; } - for (u64 bytes_read = 0; remaining > bytes_read; ) + auto const [err, bytes] = read(file, &m_rawdata[0], remaining); + if (err || (bytes != remaining)) { - u32 const chunk((std::min)(u64(std::numeric_limits<u32>::max()), remaining)); - std::size_t bytes(0); - if (file.read(&m_rawdata[bytes_read], chunk, bytes) || bytes != chunk) - { - osd_printf_error("render_font::load_cached: error reading BDC data\n"); - m_rawdata.clear(); - return false; - } - bytes_read += chunk; + osd_printf_error("render_font::load_cached: error reading BDC data\n"); + m_rawdata.clear(); + return false; } // extract the data from the data @@ -1446,11 +1446,11 @@ bool render_font::save_cached(util::random_write &file, u64 length, u32 hash) hdr.set_y_offset(m_yoffs); hdr.set_default_character(m_defchar); if (!hdr.write(file)) - throw emu_fatalerror("Error writing cached file"); + throw emu_fatalerror("Error writing cached font file"); } u64 table_offs; if (file.tell(table_offs)) - throw emu_fatalerror("Error writing cached file"); + throw emu_fatalerror("Error writing cached font file"); // allocate an array to hold the character data std::vector<u8> chartable(std::size_t(numchars) * bdc_table_entry::size(), 0); @@ -1459,9 +1459,8 @@ bool render_font::save_cached(util::random_write &file, u64 length, u32 hash) std::vector<u8> tempbuffer(65536); // write the empty table to the beginning of the file - std::size_t bytes_written(0); - if (file.write(&chartable[0], chartable.size(), bytes_written) || bytes_written != chartable.size()) - throw emu_fatalerror("Error writing cached file"); + if (write(file, &chartable[0], chartable.size()).first) + throw emu_fatalerror("Error writing cached font file"); // loop over all characters bdc_table_entry table_entry(chartable.empty() ? nullptr : &chartable[0]); @@ -1503,8 +1502,8 @@ bool render_font::save_cached(util::random_write &file, u64 length, u32 hash) *dest++ = accum; // write the data - if (file.write(&tempbuffer[0], dest - &tempbuffer[0], bytes_written) || bytes_written != dest - &tempbuffer[0]) - throw emu_fatalerror("Error writing cached file"); + if (write(file, &tempbuffer[0], dest - &tempbuffer[0]).first) + throw emu_fatalerror("Error writing cached font file"); // free the bitmap and texture m_manager.texture_free(gl.texture); @@ -1529,15 +1528,8 @@ bool render_font::save_cached(util::random_write &file, u64 length, u32 hash) LOG("render_font::save_cached: writing character table\n"); if (file.seek(table_offs, SEEK_SET)) return false; - u8 const *bytes(&chartable[0]); - for (u64 remaining = chartable.size(); remaining; ) - { - u32 const chunk((std::min<u64>)(std::numeric_limits<u32>::max(), remaining)); - if (file.write(bytes, chunk, bytes_written) || chunk != bytes_written) - throw emu_fatalerror("Error writing cached file"); - bytes += chunk; - remaining -= chunk; - } + if (write(file, &chartable[0], chartable.size()).first) + throw emu_fatalerror("Error writing cached font file"); } // no trouble? @@ -1588,6 +1580,11 @@ void render_font::render_font_command_glyph() // now read the rest of the data u64 const remaining(filesize - filepos); + if (remaining > std::numeric_limits<std::size_t>::max()) + { + osd_printf_error("render_font::render_font_command_glyph: BDC file is too large to read into memory\n"); + return; + } try { m_rawdata_cmd.resize(std::size_t(remaining)); @@ -1595,18 +1592,14 @@ void render_font::render_font_command_glyph() catch (...) { osd_printf_error("render_font::render_font_command_glyph: allocation error\n"); + return; } - for (u64 bytes_read = 0; remaining > bytes_read; ) + auto const [err, bytes] = read(*file, &m_rawdata_cmd[0], remaining); + if (err || (bytes != remaining)) { - u32 const chunk((std::min)(u64(std::numeric_limits<u32>::max()), remaining)); - std::size_t bytes(0); - if (file->read(&m_rawdata_cmd[bytes_read], chunk, bytes) || bytes != chunk) - { - osd_printf_error("render_font::render_font_command_glyph: error reading BDC data\n"); - m_rawdata_cmd.clear(); - return; - } - bytes_read += chunk; + osd_printf_error("render_font::render_font_command_glyph: error reading BDC data\n"); + m_rawdata_cmd.clear(); + return; } // extract the data from the data diff --git a/src/emu/rendlay.cpp b/src/emu/rendlay.cpp index a447fffec64..489f232a1ad 100644 --- a/src/emu/rendlay.cpp +++ b/src/emu/rendlay.cpp @@ -1790,10 +1790,10 @@ private: { u8 const *const src(reinterpret_cast<u8 const *>(dst)); rgb_t const d( - u8((float(src[3]) * c.a) + 0.5), - u8((float(src[0]) * c.r) + 0.5), - u8((float(src[1]) * c.g) + 0.5), - u8((float(src[2]) * c.b) + 0.5)); + u8((float(src[3]) * c.a) + 0.5F), + u8((float(src[0]) * c.r) + 0.5F), + u8((float(src[1]) * c.g) + 0.5F), + u8((float(src[2]) * c.b) + 0.5F)); *dst = d; havealpha = havealpha || (d.a() < 255U); } @@ -2069,17 +2069,12 @@ private: return; } svgbuf[len] = '\0'; - for (char *ptr = svgbuf.get(); len; ) + size_t actual; + std::tie(filerr, actual) = read(file, svgbuf.get(), len); + if (filerr || (actual < len)) { - size_t read; - filerr = file.read(ptr, size_t(len), read); - if (filerr || !read) - { - osd_printf_warning("Error reading component image '%s'\n", m_imagefile); - return; - } - ptr += read; - len -= read; + osd_printf_warning("Error reading component image '%s'\n", m_imagefile); + return; } parse_svg(svgbuf.get()); } @@ -2152,7 +2147,7 @@ protected: for (u32 y = bounds.top(); y <= bounds.bottom(); ++y) std::fill_n(&dest.pix(y, bounds.left()), width, f); } - else if (c.a) + else { // compute premultiplied color u32 const a(c.a * 255.0F); @@ -2161,6 +2156,9 @@ protected: u32 const b(u32(c.b * (255.0F * 255.0F)) * a); u32 const inva(255 - a); + if (!a) + return; + // we're translucent, add in the destination pixel contribution for (u32 y = bounds.top(); y <= bounds.bottom(); ++y) { @@ -2194,6 +2192,7 @@ public: u32 const g(c.g * (255.0F * 255.0F) * a); u32 const b(c.b * (255.0F * 255.0F) * a); u32 const inva(255 - a); + if (!a) return; @@ -2303,17 +2302,13 @@ public: if ((x >= minfill) && (x <= maxfill)) { if (255 <= a) - { dst = std::fill_n(dst, maxfill - x + 1, f); - x = maxfill; - } else - { while (x++ <= maxfill) alpha_blend(*dst++, a, r, g, b, inva); - --x; - } + --dst; + x = maxfill; } else { @@ -2415,17 +2410,13 @@ public: if ((x >= minfill) && (x <= maxfill)) { if (255 <= a) - { dst = std::fill_n(dst, maxfill - x + 1, f); - x = maxfill; - } else - { while (x++ <= maxfill) alpha_blend(*dst++, a, r, g, b, inva); - --x; - } + --dst; + x = maxfill; } else { @@ -4013,7 +4004,17 @@ layout_view::layout_view( , m_elemmap(elemmap) , m_defvismask(0U) , m_has_art(false) + , m_show_ptr(false) + , m_ptr_time_out(true) // FIXME: add attribute for this + , m_exp_show_ptr(-1) { + // check for explicit pointer display setting + if (viewnode.get_attribute_string_ptr("showpointers")) + { + m_show_ptr = env.get_attribute_bool(viewnode, "showpointers", false); + m_exp_show_ptr = m_show_ptr ? 1 : 0; + } + // parse the layout m_expbounds.x0 = m_expbounds.y0 = m_expbounds.x1 = m_expbounds.y1 = 0; view_environment local(env, m_name.c_str()); @@ -4191,6 +4192,7 @@ void layout_view::recompute(u32 visibility_mask, bool zoom_to_screen) // loop over items and filter by visibility mask bool first = true; bool scrfirst = true; + bool haveinput = false; for (item &curitem : m_items) { if ((visibility_mask & curitem.visibility_mask()) == curitem.visibility_mask()) @@ -4222,9 +4224,15 @@ void layout_view::recompute(u32 visibility_mask, bool zoom_to_screen) // accumulate interactive elements if (!curitem.clickthrough() || curitem.has_input()) m_interactive_items.emplace_back(curitem); + if (curitem.has_input()) + haveinput = true; } } + // if show pointers isn't explicitly, update it based on visible items + if (0 > m_exp_show_ptr) + m_show_ptr = haveinput; + // if we have an explicit bounds, override it if (m_expbounds.x1 > m_expbounds.x0) m_bounds = m_expbounds; @@ -4264,6 +4272,7 @@ void layout_view::recompute(u32 visibility_mask, bool zoom_to_screen) // sort edges of interactive items LOGMASKED(LOG_INTERACTIVE_ITEMS, "Recalculated view '%s' with %u interactive items\n", name(), m_interactive_items.size()); + //std::reverse(m_interactive_items.begin(), m_interactive_items.end()); TODO: flip hit test order to match visual order m_interactive_edges_x.reserve(m_interactive_items.size() * 2); m_interactive_edges_y.reserve(m_interactive_items.size() * 2); for (unsigned i = 0; m_interactive_items.size() > i; ++i) @@ -4295,6 +4304,29 @@ void layout_view::recompute(u32 visibility_mask, bool zoom_to_screen) //------------------------------------------------- +// set_show_pointers - set whether pointers +// should be displayed +//------------------------------------------------- + +void layout_view::set_show_pointers(bool value) noexcept +{ + m_show_ptr = value; + m_exp_show_ptr = value ? 1 : 0; +} + + +//------------------------------------------------- +// set_pointers_time_out - set whether pointers +// should be hidden after inactivity +//------------------------------------------------- + +void layout_view::set_hide_inactive_pointers(bool value) noexcept +{ + m_ptr_time_out = value; +} + + +//------------------------------------------------- // set_prepare_items_callback - set handler called // before adding items to render target //------------------------------------------------- @@ -4328,6 +4360,50 @@ void layout_view::set_recomputed_callback(recomputed_delegate &&handler) //------------------------------------------------- +// set_pointer_updated_callback - set handler +// called for pointer input +//------------------------------------------------- + +void layout_view::set_pointer_updated_callback(pointer_updated_delegate &&handler) +{ + m_pointer_updated = std::move(handler); +} + + +//------------------------------------------------- +// set_pointer_left_callback - set handler for +// pointer leaving normally +//------------------------------------------------- + +void layout_view::set_pointer_left_callback(pointer_left_delegate &&handler) +{ + m_pointer_left = std::move(handler); +} + + +//------------------------------------------------- +// set_pointer_aborted_callback - set handler for +// pointer leaving abnormally +//------------------------------------------------- + +void layout_view::set_pointer_aborted_callback(pointer_left_delegate &&handler) +{ + m_pointer_aborted = std::move(handler); +} + + +//------------------------------------------------- +// set_forget_pointers_callback - set handler for +// abandoning pointer input +//------------------------------------------------- + +void layout_view::set_forget_pointers_callback(forget_pointers_delegate &&handler) +{ + m_forget_pointers = std::move(handler); +} + + +//------------------------------------------------- // preload - perform expensive loading upfront // for visible elements //------------------------------------------------- diff --git a/src/emu/rendlay.h b/src/emu/rendlay.h index 0391bc6e961..23431e7fd92 100644 --- a/src/emu/rendlay.h +++ b/src/emu/rendlay.h @@ -16,6 +16,8 @@ #include "rendertypes.h" #include "screen.h" +#include "interface/uievents.h" + #include <array> #include <functional> #include <map> @@ -436,6 +438,9 @@ public: using prepare_items_delegate = delegate<void ()>; using preload_delegate = delegate<void ()>; using recomputed_delegate = delegate<void ()>; + using pointer_updated_delegate = delegate<void (osd::ui_event_handler::pointer, u16, u16, float, float, u32, u32, u32, s16)>; + using pointer_left_delegate = delegate<void (osd::ui_event_handler::pointer, u16, u16, float, float, u32, s16)>; + using forget_pointers_delegate = delegate<void ()>; using item = layout_view_item; using item_list = std::list<item>; @@ -522,11 +527,21 @@ public: const visibility_toggle_vector &visibility_toggles() const { return m_vistoggles; } u32 default_visibility_mask() const { return m_defvismask; } bool has_art() const { return m_has_art; } + bool show_pointers() const { return m_show_ptr; } + bool hide_inactive_pointers() const { return m_ptr_time_out; } + + // setters + void set_show_pointers(bool value) noexcept; + void set_hide_inactive_pointers(bool value) noexcept ATTR_COLD; // set handlers - void set_prepare_items_callback(prepare_items_delegate &&handler); - void set_preload_callback(preload_delegate &&handler); - void set_recomputed_callback(recomputed_delegate &&handler); + void set_prepare_items_callback(prepare_items_delegate &&handler) ATTR_COLD; + void set_preload_callback(preload_delegate &&handler) ATTR_COLD; + void set_recomputed_callback(recomputed_delegate &&handler) ATTR_COLD; + void set_pointer_updated_callback(pointer_updated_delegate &&handler) ATTR_COLD; + void set_pointer_left_callback(pointer_left_delegate &&handler) ATTR_COLD; + void set_pointer_aborted_callback(pointer_left_delegate &&handler) ATTR_COLD; + void set_forget_pointers_callback(forget_pointers_delegate &&handler) ATTR_COLD; // operations void prepare_items(); @@ -536,6 +551,28 @@ public: // resolve tags, if any void resolve_tags(); + // pointer input + void pointer_updated(osd::ui_event_handler::pointer type, u16 ptrid, u16 device, float x, float y, u32 buttons, u32 pressed, u32 released, s16 clicks) + { + if (!m_pointer_updated.isnull()) + m_pointer_updated(type, ptrid, device, x, y, buttons, pressed, released, clicks); + } + void pointer_left(osd::ui_event_handler::pointer type, u16 ptrid, u16 device, float x, float y, u32 released, s16 clicks) + { + if (!m_pointer_left.isnull()) + m_pointer_left(type, ptrid, device, x, y, released, clicks); + } + void pointer_aborted(osd::ui_event_handler::pointer type, u16 ptrid, u16 device, float x, float y, u32 released, s16 clicks) + { + if (!m_pointer_aborted.isnull()) + m_pointer_aborted(type, ptrid, device, x, y, released, clicks); + } + void forget_pointers() + { + if (!m_forget_pointers.isnull()) + m_forget_pointers(); + } + private: struct layer_lists; @@ -576,6 +613,10 @@ private: prepare_items_delegate m_prepare_items; // prepare items for adding to render container preload_delegate m_preload; // additional actions when visible items change recomputed_delegate m_recomputed; // additional actions on resizing/visibility change + pointer_updated_delegate m_pointer_updated; // pointer state updated + pointer_left_delegate m_pointer_left; // pointer left normally + pointer_left_delegate m_pointer_aborted; // pointer left abnormally + forget_pointers_delegate m_forget_pointers; // stop processing pointer input // cold items std::string m_name; // display name for the view @@ -586,6 +627,9 @@ private: render_bounds m_expbounds; // explicit bounds of the view u32 m_defvismask; // default visibility mask bool m_has_art; // true if the layout contains non-screen elements + bool m_show_ptr; // whether pointers should be displayed + bool m_ptr_time_out; // whether pointers should be hidden after inactivity + s8 m_exp_show_ptr; // explicitly configured pointer visibility }; diff --git a/src/emu/rendutil.cpp b/src/emu/rendutil.cpp index db7220351b8..5d09061ab70 100644 --- a/src/emu/rendutil.cpp +++ b/src/emu/rendutil.cpp @@ -41,8 +41,7 @@ private: { jpeg_corefile_source &src = *static_cast<jpeg_corefile_source *>(cinfo->src); - size_t nbytes; - src.infile->read(src.buffer, INPUT_BUF_SIZE, nbytes); // TODO: check error return + auto [err, nbytes] = read(*src.infile, src.buffer, INPUT_BUF_SIZE); // TODO: check error return if (0 >= nbytes) { diff --git a/src/emu/resampler.cpp b/src/emu/resampler.cpp new file mode 100644 index 00000000000..18a5d9f4a21 --- /dev/null +++ b/src/emu/resampler.cpp @@ -0,0 +1,439 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +// Audio resampler + +#include "emu.h" +#include "resampler.h" + +// How an accurate resampler works ? + +// Resampling uses a number of well-known theorems we are not trying +// to prove here. + +// Samping theorem. A digital signal sampled at frequency fs is +// equivalent to an analog signal where all frequencies are between 0 +// and fs/2. Equivalent here means that the samples are unique given +// the analog signal, the analog sugnal is unique given the samples, +// and going analog -> digital -> analog is perfect. + +// That gives us point one: resampling from fs to ft is, semantically, +// reconstructing the analog signal from the fs sampling, removing all +// frequencies over ft/2, then sampling at ft. + + +// Up-sampling theorem. Take a digital signal at frequency fs, and k +// an integer > 1. Create a new digital signal at frequency fs*k by +// alternatively taking one sample from the original signal and adding +// k-1 zeroes. If one recreates the corresponding analog signal and +// removes all frequencies over fs/2, then it will be identical to the +// original analog signal, up to a constant multiplier on the +// amplitude. For the curious the frequencies over fs/2 get copies of +// the original spectrum with inversions, e.g. the frequency fs/2-a is +// copied at fs/2+a, then it's not inverted at fs..fs*1.5, inverted +// again between fs*1.5 and fs*2, etc. + +// A corollary is that if one starts for an analog signal with no +// frequencies over fs/2, samples it at fs, then up-samples to fs*k by +// adding zeroes, remove (filter) from the upsampled signal all +// frequencies over fs/2 then reconstruct the analog signal you get a +// result identical to the original signal. It's a perfect +// upsampling, assuming the filtering is perfect. + + +// Down-sampling theorem. Take a digital signal at frequency ft*k, +// with k and integer > 1. Create a new digital signal at frequency +// ft by alternatively taking one sample from the original signal and +// dropping k-1 samples. If the original signal had no frequency over +// ft/2, then the reconstructed analog signal is identical to the +// original one, up to a constant multiplier on the amplitude. So it +// is a perfect downsampling assuming the original signal has nothing +// over ft/2. For the curious if there are frequencies over ft/2, +// they end up added to the lower frequencies with inversions. The +// frequency ft/2+a is added to ft/2-a, etc (signal to upsampling, +// only the other way around). + +// The corollary there is that if one starts with a ft*k digital +// signal, filters out everything over ft/2, then keeps only one +// sample every k, then reconstruct the analog signal, you get the +// original analog signal with frequencies over ft/2 removed, which is +// reasonable given they are not representable at sampling frequency +// ft anyway. As such it is called perfect because it's the best +// possible result in any case. + +// Incidentally, the parasite audible frequencies added with the +// wrapping when the original is insufficiently filtered before +// dropping the samples are called aliasing, as in the high barely +// audible frequencies that was there but not noticed gets aliased to +// a very audible and annoying lower frequency. + + +// As a result, the recipe to go from frequency fs to ft for a digital +// signal is: + +// - find a frequency fm = ks*fs = kt*ft with ks and kt integers. +// When fs and ft are integers (our case), the easy solution is +// fm = fs * ft / gcd(fs, ft) + +// - up-sample the original signal x(t) into xm(t) with: +// xm(ks*t) = x(t) +// xm(other) = 0 + +// - filter the resulting fm Hz signal to remove all frequencies above +// fs/2. This is also called "lowpass at fs/2" + +// - lowpass at ft/2 + +// - down-sample the fm signal into the resulting y(t) signal by: +// y(t) = xm(kt*t) + +// And, assuming the filtering is perfect (it isn't, of course), the +// result is a perfect resampling. + +// Now to optimize all that. The first point is that an ideal lowpass +// at fs/2 followed by an ideal lowpass at ft/2 is strictly equivalent +// to an ideal lowpass at min(fs/2, ft/2). So only one filter is +// needed. + +// The second point depends on the type of filter used. In our case +// the filter type known as FIR has a big advantage. A FIR filter +// computes the output signal as a finite ponderated sum on the values +// of the input signal only (also called a convolution). E.g. +// y(t) = sum(k=0, n-1) a[k] * x[t-k] +// where a[0..n-1] are constants called the coefficients of the filter. + +// Why this type of filter is pertinent shows up when building the +// complete computation: + +// y(t) = filter(xm)[kt*t] +// = sum(k=0, n-1) a[k] * xm[kt*t - k] +// = sum(k=0, n-1) a[k] * | x[(kt*t-k)/ks] when kt*t-k is divisible by ks +// | 0 otherwise +// = sum(k=(kt*t) mod ks, n-1, step=ks) a[k] * x[(kt*t-k)/ks] + +// (noting p = (kt*t) mode ks, and a // b integer divide of a by b) +// = sum(k=0, (n-1 - p))//ks) a[k*ks + p] x[(kt*t) // ks) - k] + +// Splitting the filter coefficients in ks phases ap[0..ks-1] where +// ap[p][k] = a[p + ks*k], and noting t0 = (k*kt) // ks: + +// y(t) = sum(k=0, len(ap[p])-1) ap[p][k] * x[t0-k] + +// So we can take a big FIR filter and split it into ks interpolation +// filters and just apply the correct one at each sample. We can make +// things even easier by ensuring that the size of every interpolation +// filter is the same. + +// The art of creating the big FIR filter so that it doesn't change +// the signal too much is complicated enough that entire books have +// been written on the topic. We use here a simple solution which is +// to use a so-called zero-phase filter, which is a symmetrical filter +// which looks into the future to filter out the frequencies without +// changing the phases, and shift it in the past by half its length, +// making it causal (e.g. not looking into the future anymore). It is +// then called linear-phase, and has a latency of exactly half its +// length. The filter itself is made very traditionally, by +// multiplying a sinc by a Hann window. + +// The filter size is selected by maximizing the latency to 5ms and +// capping the length at 400, which experimentally seems to ensure a +// sharp rejection of more than 100dB in every case. + +// Finally, remember that up and downsampling steps multiply the +// amplitude by a constant (upsampling divides by k, downsamply +// multiply by k in fact). To compensate for that and numerical +// errors the easiest way to to normalize each phase-filter +// independently to ensure the sum of their coefficients is 1. It is +// easy to see why it works: a constant input signal must be +// transformed into a constant output signal at the exact same level. +// Having the sum of coefficients being 1 ensures that. + + +audio_resampler_hq::audio_resampler_hq(u32 fs, u32 ft, float latency, u32 max_order_per_lane, u32 max_lanes) +{ + m_ft = ft; + m_fs = fs; + + // Compute the multiplier for fs and ft to reach the common frequency + u32 gcd = compute_gcd(fs, ft); + m_ftm = fs / gcd; + m_fsm = ft / gcd; + + // Compute the per-phase filter length to limit the latency to 5ms and capping it + m_order_per_lane = u32(fs * latency * 2); + if(m_order_per_lane > max_order_per_lane) + m_order_per_lane = max_order_per_lane; + + // Reduce the number of phases to be less than max_lanes + m_phase_shift = 0; + while(((m_fsm - 1) >> m_phase_shift) >= max_lanes) + m_phase_shift ++; + + m_phases = ((m_fsm - 1) >> m_phase_shift) + 1; + + // Compute the global filter length + u32 filter_length = m_order_per_lane * m_phases; + if((filter_length & 1) == 0) + filter_length --; + u32 hlen = filter_length / 2; + + // Prepare the per-phase filters + m_coefficients.resize(m_phases); + for(u32 i = 0; i != m_phases; i++) + m_coefficients[i].resize(m_order_per_lane, 0.0); + + // Select the filter cutoff. Keep it in audible range. + double cutoff = std::min(fs/2.0, ft/2.0); + if(cutoff > 20000) + cutoff = 20000; + + // Compute the filter and send the coefficients to the appropriate phase + auto set_filter = [this](u32 i, float v) { m_coefficients[i % m_phases][i / m_phases] = v; }; + + double wc = 2 * M_PI * cutoff / (double(fs) * m_fsm / (1 << m_phase_shift)); + double a = wc / M_PI; + for(u32 i = 1; i != hlen; i++) { + double win = cos(i*M_PI/hlen/2); + win = win*win; + double s = a * sin(i*wc)/(i*wc) * win; + + set_filter(hlen-1+i, s); + set_filter(hlen-1-i, s); + } + set_filter(hlen-1, a); + + // Normalize the per-phase filters + for(u32 i = 0; i != m_phases; i++) { + float s = 0; + for(u32 j = 0; j != m_order_per_lane; j++) + s += m_coefficients[i][j]; + s = 1/s; + for(u32 j = 0; j != m_order_per_lane; j++) + m_coefficients[i][j] *= s; + } + + // Compute the phase shift from one sample to the next + m_delta = m_ftm % m_fsm; + m_skip = m_ftm / m_fsm; +} + +u32 audio_resampler_hq::compute_gcd(u32 fs, u32 ft) +{ + u32 v1 = fs > ft ? fs : ft; + u32 v2 = fs > ft ? ft : fs; + while(v2) { + u32 v3 = v1 % v2; + v1 = v2; + v2 = v3; + } + return v1; +} + +u32 audio_resampler_hq::history_size() const +{ + return m_order_per_lane + m_skip + 1; +} + +void audio_resampler_hq::apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const +{ + u32 seconds = dest_sample / m_ft; + u32 dsamp = dest_sample % m_ft; + u32 ssamp = (u64(dsamp) * m_fs) / m_ft; + u64 ssample = ssamp + u64(m_fs) * seconds; + u32 phase = (dsamp * m_ftm) % m_fsm; + + const sample_t *s = src.ptrs(srcc, ssample - src.sync_sample()); + sample_t *d = dest.data(); + for(u32 sample = 0; sample != samples; sample++) { + sample_t acc = 0; + const sample_t *s1 = s; + const float *filter = m_coefficients[phase >> m_phase_shift].data(); + for(u32 k = 0; k != m_order_per_lane; k++) + acc += *filter++ * *s1--; + *d++ += acc * gain; + phase += m_delta; + s += m_skip; + while(phase >= m_fsm) { + phase -= m_fsm; + s ++; + } + } +} + +void audio_resampler_hq::apply(const emu::detail::output_buffer_interleaved<s16> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const +{ + u32 seconds = dest_sample / m_ft; + u32 dsamp = dest_sample % m_ft; + u32 ssamp = (u64(dsamp) * m_fs) / m_ft; + u64 ssample = ssamp + u64(m_fs) * seconds; + u32 phase = (dsamp * m_ftm) % m_fsm; + + gain /= 32768; + + const s16 *s = src.ptrs(srcc, ssample - src.sync_sample()); + sample_t *d = dest.data(); + int step = src.channels(); + for(u32 sample = 0; sample != samples; sample++) { + sample_t acc = 0; + const s16 *s1 = s; + const float *filter = m_coefficients[phase >> m_phase_shift].data(); + for(u32 k = 0; k != m_order_per_lane; k++) { + acc += *filter++ * *s1; + s1 -= step; + } + *d++ += acc * gain; + phase += m_delta; + s += m_skip * step; + while(phase >= m_fsm) { + phase -= m_fsm; + s += step; + } + } +} + + +void audio_resampler_hq::apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<s16> &dest, u32 destc, int dchannels, u64 dest_sample, u32 srcc, float gain, u32 samples) const +{ + u32 seconds = dest_sample / m_ft; + u32 dsamp = dest_sample % m_ft; + u32 ssamp = (u64(dsamp) * m_fs) / m_ft; + u64 ssample = ssamp + u64(m_fs) * seconds; + u32 phase = (dsamp * m_ftm) % m_fsm; + + gain *= 32768; + + const sample_t *s = src.ptrs(srcc, ssample - src.sync_sample()); + s16 *d = dest.data() + destc; + for(u32 sample = 0; sample != samples; sample++) { + sample_t acc = 0; + const sample_t *s1 = s; + const float *filter = m_coefficients[phase >> m_phase_shift].data(); + for(u32 k = 0; k != m_order_per_lane; k++) + acc += *filter++ * *s1--; + *d += acc * gain; + d += dchannels; + phase += m_delta; + s += m_skip; + while(phase >= m_fsm) { + phase -= m_fsm; + s ++; + } + } +} + + +// Now for the lo-fi version +// +// We mostly forget about filtering, and just try to do a decent +// interpolation. There's a nice 4-point formula used in yamaha +// devices from around 2000: +// f0(t) = (t - t**3)/6 +// f1(t) = t + (t**2 - t**3)/2 +// +// The polynoms are used with the decimal part 'p' (as in phase) of +// the sample position. The computation from the four samples s0..s3 +// is: +// s = - s0 * f0(1-p) + s1 * f1(1-p) + s2 * f1(p) - s3 * f0(p) +// +// The target sample must be between s1 and s2. +// +// When upsampling, that's enough. When downsampling, it feels like a +// good idea to filter a little with a moving average, dividing the +// source frequency by an integer just big enough to make the final +// source frequency lower. + +// Sample interpolation functions f0 and f1 + +const std::array<std::array<float, 0x1001>, 2> audio_resampler_lofi::interpolation_table = []() { + std::array<std::array<float, 0x1001>, 2> result; + + // The exact way of doing the computations replicate the values + // actually used by the chip (which are very probably a rom, of + // course). + + for(u32 i=1; i != 4096; i++) { + float p = i / 4096.0; + result[0][i] = (p - p*p*p) / 6; + } + for(u32 i=1; i != 2049; i++) { + float p = i / 4096.0; + result[1][i] = p + (p*p - p*p*p) / 2; + } + for(u32 i=2049; i != 4096; i++) + // When interpolating, f1 is added and f0 is subtracted, and the total must be 1 + result[1][i] = 1.0 + result[0][i] + result[0][4096-i] - result[1][4096-i]; + + result[0][ 0] = 0.0; + result[0][0x1000] = 0.0; + result[1][ 0] = 0.0; + result[1][0x1000] = 1.0; + return result; +}(); + +audio_resampler_lofi::audio_resampler_lofi(u32 fs, u32 ft) +{ + m_fs = fs; + m_ft = ft; + + m_source_divide = fs <= ft ? 1 : 1+fs/ft; + m_step = u64(fs) * 0x1000 / ft / m_source_divide; +} + + +u32 audio_resampler_lofi::history_size() const +{ + return 5 * m_source_divide + m_fs / m_ft + 1; +} + +void audio_resampler_lofi::apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const +{ + u32 seconds = dest_sample / m_ft; + u32 dsamp = dest_sample % m_ft; + u64 ssamp = (u64(dsamp) * m_fs * 0x1000) / m_ft; + u64 ssample = (ssamp >> 12) + u64(m_fs) * seconds; + u32 phase = ssamp & 0xfff; + if(m_source_divide > 1) { + u32 delta = ssample % m_source_divide; + phase = (phase | (delta << 12)) / m_source_divide; + ssample -= delta; + } + + // We're getting 2 samples latency, which is small enough + + ssample -= 4*m_source_divide; + + const sample_t *s = src.ptrs(srcc, ssample - src.sync_sample()); + + std::function<sample_t()> reader; + if(m_source_divide == 1) + reader = [s]() mutable -> sample_t { return *s++; }; + else + reader = [s, count = m_source_divide]() mutable -> sample_t { sample_t sm = 0; for(u32 i=0; i != count; i++) { sm += *s++; } return sm / count; }; + + sample_t s0 = reader(); + sample_t s1 = reader(); + sample_t s2 = reader(); + sample_t s3 = reader(); + + sample_t *d = dest.data(); + for(u32 sample = 0; sample != samples; sample++) { + *d++ += gain * (- s0 * interpolation_table[0][0x1000-phase] + s1 * interpolation_table[1][0x1000-phase] + s2 * interpolation_table[1][phase] - s3 * interpolation_table[0][phase]); + + phase += m_step; + if(phase & 0x1000) { + phase &= 0xfff; + s0 = s1; + s1 = s2; + s2 = s3; + s3 = reader(); + } + } +} + +void audio_resampler_lofi::apply(const emu::detail::output_buffer_interleaved<s16> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const +{ +} + +void audio_resampler_lofi::apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<s16> &dest, u32 destc, int dchannels, u64 dest_sample, u32 srcc, float gain, u32 samples) const +{ +} diff --git a/src/emu/resampler.h b/src/emu/resampler.h new file mode 100644 index 00000000000..d0758b5ae7e --- /dev/null +++ b/src/emu/resampler.h @@ -0,0 +1,66 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +// Audio resampler + +#pragma once + +#ifndef MAME_EMU_RESAMPLER_H +#define MAME_EMU_RESAMPLER_H + +#include "sound.h" + +class audio_resampler +{ +public: + using sample_t = sound_stream::sample_t; + + virtual ~audio_resampler() = default; + + virtual u32 history_size() const = 0; + + virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const = 0; + virtual void apply(const emu::detail::output_buffer_interleaved<s16> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const = 0; + virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<s16> &dest, u32 destc, int dchannels, u64 dest_sample, u32 srcc, float gain, u32 samples) const = 0; +}; + +class audio_resampler_hq : public audio_resampler +{ +public: + audio_resampler_hq(u32 fs, u32 ft, float latency, u32 max_order_per_lane, u32 max_lanes); + virtual ~audio_resampler_hq() = default; + + virtual u32 history_size() const override; + + virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const override; + virtual void apply(const emu::detail::output_buffer_interleaved<s16> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const override; + virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<s16> &dest, u32 destc, int dchannels, u64 dest_sample, u32 srcc, float gain, u32 samples) const override; + +private: + u32 m_order_per_lane, m_ftm, m_fsm, m_ft, m_fs, m_delta, m_skip, m_phases, m_phase_shift; + + std::vector<std::vector<float>> m_coefficients; + + static u32 compute_gcd(u32 fs, u32 ft); +}; + +class audio_resampler_lofi : public audio_resampler +{ +public: + audio_resampler_lofi(u32 fs, u32 ft); + virtual ~audio_resampler_lofi() = default; + + virtual u32 history_size() const override; + + virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const override; + virtual void apply(const emu::detail::output_buffer_interleaved<s16> &src, std::vector<sample_t> &dest, u64 dest_sample, u32 srcc, float gain, u32 samples) const override; + virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, std::vector<s16> &dest, u32 destc, int dchannels, u64 dest_sample, u32 srcc, float gain, u32 samples) const override; + +private: + static const std::array<std::array<float, 0x1001>, 2> interpolation_table; + u32 m_source_divide, m_fs, m_ft; + u32 m_step; +}; + +#endif + diff --git a/src/emu/romload.cpp b/src/emu/romload.cpp index a429153be3f..33541aedce4 100644 --- a/src/emu/romload.cpp +++ b/src/emu/romload.cpp @@ -960,10 +960,10 @@ void rom_load_manager::fill_rom_data(memory_region ®ion, const rom_entry *rom copy_rom_data - copy a region of ROM space -------------------------------------------------*/ -void rom_load_manager::copy_rom_data(memory_region ®ion, const rom_entry *romp) +void rom_load_manager::copy_rom_data(device_t &device, memory_region ®ion, const rom_entry *romp) { u8 *base = region.base() + ROM_GETOFFSET(romp); - const std::string &srcrgntag = romp->name(); + const std::string srcrgntag = device.subtag(romp->name()); u32 numbytes = ROM_GETLENGTH(romp); u32 srcoffs = u32(strtol(romp->hashdata().c_str(), nullptr, 0)); /* srcoffset in place of hashdata */ @@ -995,6 +995,7 @@ void rom_load_manager::copy_rom_data(memory_region ®ion, const rom_entry *rom -------------------------------------------------*/ void rom_load_manager::process_rom_entries( + device_t &device, const std::vector<std::string> &searchpath, u8 bios, memory_region ®ion, @@ -1028,7 +1029,7 @@ void rom_load_manager::process_rom_entries( } else if (ROMENTRY_ISCOPY(romp)) { - copy_rom_data(region, romp++); + copy_rom_data(device, region, romp++); } else if (ROMENTRY_ISFILE(romp)) { @@ -1437,7 +1438,7 @@ void rom_load_manager::load_software_part_region(device_t &device, software_list // now process the entries in the region if (ROMREGION_ISROMDATA(region)) { - process_rom_entries(swsearch, 0U, *memregion, region, region + 1, true); + process_rom_entries(device, swsearch, 0U, *memregion, region, region + 1, true); } else if (ROMREGION_ISDISKDATA(region)) { @@ -1510,7 +1511,7 @@ void rom_load_manager::process_region_list() if (searchpath.empty()) searchpath = device.searchpath(); assert(!searchpath.empty()); - process_rom_entries(searchpath, device.system_bios(), *memregion, region, region + 1, false); + process_rom_entries(device, searchpath, device.system_bios(), *memregion, region, region + 1, false); } else if (ROMREGION_ISDISKDATA(region)) { diff --git a/src/emu/romload.h b/src/emu/romload.h index 4ad0303a9f6..f655b81bf78 100644 --- a/src/emu/romload.h +++ b/src/emu/romload.h @@ -461,8 +461,9 @@ private: int rom_fread(emu_file *file, u8 *buffer, int length, const rom_entry *parent_region); int read_rom_data(emu_file *file, memory_region ®ion, const rom_entry *parent_region, const rom_entry *romp); void fill_rom_data(memory_region ®ion, const rom_entry *romp); - void copy_rom_data(memory_region ®ion, const rom_entry *romp); + void copy_rom_data(device_t &device, memory_region ®ion, const rom_entry *romp); void process_rom_entries( + device_t &device, const std::vector<std::string> &searchpath, u8 bios, memory_region ®ion, diff --git a/src/emu/save.cpp b/src/emu/save.cpp index ab1fc5a854d..35599c2e679 100644 --- a/src/emu/save.cpp +++ b/src/emu/save.cpp @@ -27,7 +27,6 @@ #include "main.h" -#include "util/coreutil.h" #include "util/ioprocs.h" #include "util/ioprocsfilter.h" @@ -68,7 +67,7 @@ enum save_manager::save_manager(running_machine &machine) : m_machine(machine) , m_reg_allowed(true) - , m_illegal_regs(0) + , m_supported(false) { m_rewind = std::make_unique<rewinder>(*this); } @@ -102,6 +101,16 @@ void save_manager::allow_registration(bool allowed) if (dupes_found) fatalerror("%d duplicate save state entries found.\n", dupes_found); + m_supported = true; + for (device_t &device : device_enumerator(machine().root_device())) + { + if (device.type().emulation_flags() & device_t::flags::SAVE_UNSUPPORTED) + { + m_supported = false; + break; + } + } + dump_registry(); // everything is registered by now, evaluate the savestate size @@ -187,9 +196,7 @@ void save_manager::save_memory(device_t *device, const char *module, const char if (!m_reg_allowed) { machine().logerror("Attempt to register save state entry after state registration is closed!\nModule %s tag %s name %s\n", module, tag, name); - if (machine().system().flags & machine_flags::SUPPORTS_SAVE) - fatalerror("Attempt to register save state entry after state registration is closed!\nModule %s tag %s name %s\n", module, tag, name); - m_illegal_regs++; + fatalerror("Attempt to register save state entry after state registration is closed!\nModule %s tag %s name %s\n", module, tag, name); return; } @@ -219,8 +226,8 @@ save_error save_manager::check_file(running_machine &machine, util::core_file &f // seek to the beginning and read the header file.seek(0, SEEK_SET); u8 header[HEADER_SIZE]; - size_t actual(0); - if (file.read(header, sizeof(header), actual) || actual != sizeof(header)) + auto const [err, actual] = read(file, header, sizeof(header)); + if (err || (actual != sizeof(header))) { if (errormsg != nullptr) (*errormsg)("Could not read %s save file header", emulator_info::get_appname()); @@ -267,9 +274,8 @@ save_error save_manager::write_file(util::core_file &file) [] (size_t total_size) { return true; }, [&writer] (const void *data, size_t size) { - size_t written; - std::error_condition filerr = writer->write(data, size, written); - return !filerr && (size == written); + auto const [filerr, written] = write(*writer, data, size); + return !filerr; }, [&file, &writer] () { @@ -300,9 +306,8 @@ save_error save_manager::read_file(util::core_file &file) [] (size_t total_size) { return true; }, [&reader] (void *data, size_t size) { - std::size_t read; - std::error_condition filerr = reader->read(data, size, read); - return !filerr && (read == size); + auto const [filerr, actual] = read(*reader, data, size); + return !filerr && (actual == size); }, [&file, &reader] () { @@ -408,10 +413,6 @@ save_error save_manager::read_buffer(const void *buf, size_t size) template <typename T, typename U, typename V, typename W> inline save_error save_manager::do_write(T check_space, U write_block, V start_header, W start_data) { - // if we have illegal registrations, return an error - if (m_illegal_regs > 0) - return STATERR_ILLEGAL_REGISTRATIONS; - // check for sufficient space size_t total_size = HEADER_SIZE; for (const auto &entry : m_entry_list) @@ -455,10 +456,6 @@ inline save_error save_manager::do_write(T check_space, U write_block, V start_h template <typename T, typename U, typename V, typename W> inline save_error save_manager::do_read(T check_length, U read_block, V start_header, W start_data) { - // if we have illegal registrations, return an error - if (m_illegal_regs > 0) - return STATERR_ILLEGAL_REGISTRATIONS; - // check for sufficient space size_t total_size = HEADER_SIZE; for (const auto &entry : m_entry_list) @@ -508,21 +505,21 @@ inline save_error save_manager::do_read(T check_length, U read_block, V start_he u32 save_manager::signature() const { // iterate over entries - u32 crc = 0; + util::crc32_creator crc; for (auto &entry : m_entry_list) { // add the entry name to the CRC - crc = core_crc32(crc, (u8 *)entry->m_name.c_str(), entry->m_name.length()); + crc.append(entry->m_name.data(), entry->m_name.length()); // add the type and size to the CRC u32 temp[4]; temp[0] = little_endianize_int32(entry->m_typesize); temp[1] = little_endianize_int32(entry->m_typecount); temp[2] = little_endianize_int32(entry->m_blockcount); - temp[3] = little_endianize_int32(entry->m_stride); - crc = core_crc32(crc, (u8 *)&temp[0], sizeof(temp)); + temp[3] = 0; + crc.append(&temp[0], sizeof(temp)); } - return crc; + return crc.finish(); } @@ -663,10 +660,6 @@ save_error ram_state::load() // initialize m_data.seekg(0); - // if we have illegal registrations, return an error - if (m_save.m_illegal_regs > 0) - return STATERR_ILLEGAL_REGISTRATIONS; - // get the save manager to load state return m_save.read_stream(m_data); } @@ -916,11 +909,6 @@ void rewinder::report_error(save_error error, rewind_operation operation) switch (error) { // internal saveload failures - case STATERR_ILLEGAL_REGISTRATIONS: - m_save.machine().logerror("Rewind error: Unable to %s state due to illegal registrations.", opname); - m_save.machine().popmessage("Rewind error occured. See error.log for details."); - break; - case STATERR_INVALID_HEADER: m_save.machine().logerror("Rewind error: Unable to %s state due to an invalid header. " "Make sure the save state is correct for this machine.\n", opname); @@ -957,7 +945,7 @@ void rewinder::report_error(save_error error, rewind_operation operation) // success case STATERR_NONE: { - const u64 supported = m_save.machine().system().flags & MACHINE_SUPPORTS_SAVE; + const u64 supported = m_save.supported(); const char *const warning = supported || !m_first_time_warning ? "" : "Rewind warning: Save states are not officially supported for this machine.\n"; const char *const opnamed = (operation == rewind_operation::LOAD) ? "loaded" : "captured"; diff --git a/src/emu/save.h b/src/emu/save.h index 677f127853a..066f20f3330 100644 --- a/src/emu/save.h +++ b/src/emu/save.h @@ -34,7 +34,6 @@ enum save_error { STATERR_NONE, STATERR_NOT_FOUND, - STATERR_ILLEGAL_REGISTRATIONS, STATERR_INVALID_HEADER, STATERR_READ_ERROR, STATERR_WRITE_ERROR, @@ -160,6 +159,7 @@ public: rewinder *rewind() { return m_rewind.get(); } int registration_count() const { return m_entry_list.size(); } bool registration_allowed() const { return m_reg_allowed; } + bool supported() const { return m_supported; } // registration control void allow_registration(bool allowed = true); @@ -334,7 +334,7 @@ private: running_machine & m_machine; // reference to our machine std::unique_ptr<rewinder> m_rewind; // rewinder bool m_reg_allowed; // are registrations allowed? - s32 m_illegal_regs; // number of illegal registrations + bool m_supported; // are saved states supported? std::vector<std::unique_ptr<state_entry>> m_entry_list; // list of registered entries std::vector<std::unique_ptr<ram_state>> m_ramstate_list; // list of ram states diff --git a/src/emu/schedule.cpp b/src/emu/schedule.cpp index acc596d6798..a475fd9b515 100644 --- a/src/emu/schedule.cpp +++ b/src/emu/schedule.cpp @@ -230,6 +230,7 @@ void emu_timer::register_save(save_manager &manager) manager.save_item(nullptr, "timer", name.c_str(), index, NAME(m_period)); manager.save_item(nullptr, "timer", name.c_str(), index, NAME(m_start)); manager.save_item(nullptr, "timer", name.c_str(), index, NAME(m_expire)); + manager.save_item(nullptr, "timer", name.c_str(), index, NAME(m_index)); } @@ -473,7 +474,7 @@ void device_scheduler::timeslice() // update the local time for this CPU attotime deltatime; - if (ran < exec->m_cycles_per_second) + if (EXPECTED(ran < exec->m_cycles_per_second)) deltatime = attotime(0, exec->m_attoseconds_per_cycle * ran); else { @@ -672,6 +673,9 @@ void device_scheduler::presave() { // report the timer state after a log LOG("Prior to saving state:\n"); + u32 index = 0; + for (emu_timer *timer = m_timer_list; timer; timer = timer->m_next) + timer->m_index = index++; #if VERBOSE dump_timers(); #endif @@ -724,7 +728,7 @@ void device_scheduler::postload() { emu_timer &timer = *private_list; private_list = timer.m_next; - timer_list_insert(timer); + timer_list_insert<true>(timer); } m_suspend_changes_pending = true; @@ -839,6 +843,7 @@ void device_scheduler::rebuild_execute_list() // the list at the appropriate location //------------------------------------------------- +template <bool CheckIndex> inline emu_timer &device_scheduler::timer_list_insert(emu_timer &timer) { // disabled timers never expire @@ -849,7 +854,10 @@ inline emu_timer &device_scheduler::timer_list_insert(emu_timer &timer) for (emu_timer *curtimer = m_timer_list; curtimer; prevtimer = curtimer, curtimer = curtimer->m_next) { // if the current list entry expires after us, we should be inserted before it - if (curtimer->m_expire > timer.m_expire) + bool const here = + (curtimer->m_expire > timer.m_expire) || + (CheckIndex && !(curtimer->m_expire < timer.m_expire) && (curtimer->m_index > timer.m_index)); + if (here) { // link the new guy in before the current list entry timer.m_prev = prevtimer; diff --git a/src/emu/schedule.h b/src/emu/schedule.h index e13fc18d573..b8515c0a00d 100644 --- a/src/emu/schedule.h +++ b/src/emu/schedule.h @@ -85,6 +85,7 @@ private: attotime m_period; // the repeat frequency of the timer attotime m_start; // time when the timer was started attotime m_expire; // time when the timer will expire + u32 m_index; // needed to restore timers scheduled at the same time in correct order friend class device_scheduler; friend class fixed_allocator<emu_timer>; @@ -143,7 +144,7 @@ private: void apply_suspend_changes(); // timer helpers - emu_timer &timer_list_insert(emu_timer &timer); + template <bool CheckIndex = false> emu_timer &timer_list_insert(emu_timer &timer); emu_timer &timer_list_remove(emu_timer &timer); void execute_timers(); diff --git a/src/emu/screen.cpp b/src/emu/screen.cpp index cf311fb21eb..0d8e35d6e4c 100644 --- a/src/emu/screen.cpp +++ b/src/emu/screen.cpp @@ -555,6 +555,7 @@ screen_device::screen_device(const machine_config &mconfig, const char *tag, dev , m_curbitmap(0) , m_curtexture(0) , m_changed(true) + , m_last_partial_reset(attotime::zero) , m_last_partial_scan(0) , m_partial_scan_hpos(0) , m_color(rgb_t(0xff, 0xff, 0xff, 0xff)) @@ -877,6 +878,7 @@ void screen_device::device_start() save_item(NAME(m_visarea.min_y)); save_item(NAME(m_visarea.max_x)); save_item(NAME(m_visarea.max_y)); + save_item(NAME(m_last_partial_reset)); save_item(NAME(m_last_partial_scan)); save_item(NAME(m_frame_period)); save_item(NAME(m_brightness)); @@ -1039,19 +1041,19 @@ void screen_device::reset_origin(int beamy, int beamx) m_vblank_end_time = curtime - attotime(0, beamy * m_scantime + beamx * m_pixeltime); m_vblank_start_time = m_vblank_end_time - attotime(0, m_vblank_period); - // if we are resetting relative to (0,0) == VBLANK end, call the - // scanline 0 timer by hand now; otherwise, adjust it for the future - if (beamy == 0 && beamx == 0) - reset_partial_updates(); - else - m_scanline0_timer->adjust(time_until_pos(0)); - // if we are resetting relative to (visarea.bottom() + 1, 0) == VBLANK start, // call the VBLANK start timer now; otherwise, adjust it for the future if (beamy == ((m_visarea.bottom() + 1) % m_height) && beamx == 0) vblank_begin(0); else m_vblank_begin_timer->adjust(time_until_vblank_start()); + + // if we are resetting relative to (0,0) == VBLANK end, call the + // scanline 0 timer by hand now; otherwise, adjust it for the future + if (beamy == 0 && beamx == 0) + reset_partial_updates(); + else + m_scanline0_timer->adjust(time_until_pos(0)); } @@ -1168,6 +1170,14 @@ bool screen_device::update_partial(int scanline) return false; } + // skip if we already rendered this frame + // this can happen if a cpu timeslice that called update_partial is in the previous frame while scanline 0 already started + if (m_last_partial_scan == 0 && m_last_partial_reset > machine().time()) + { + LOG_PARTIAL_UPDATES(("skipped because frame was already rendered\n")); + return false; + } + // set the range of scanlines to render rectangle clip(m_visarea); clip.sety((std::max)(clip.top(), m_last_partial_scan), (std::min)(clip.bottom(), scanline)); @@ -1278,6 +1288,14 @@ void screen_device::update_now() return; } + // skip if we already rendered this frame + // this can happen if a cpu timeslice that called update_now is in the previous frame while scanline 0 already started + if (m_last_partial_scan == 0 && m_partial_scan_hpos == 0 && m_last_partial_reset > machine().time()) + { + LOG_PARTIAL_UPDATES(("skipped because frame was already rendered\n")); + return; + } + LOG_PARTIAL_UPDATES(("update_now(): Y=%d, X=%d, last partial %d, partial hpos %d (vis %d %d)\n", current_vpos, current_hpos, m_last_partial_scan, m_partial_scan_hpos, m_visarea.right(), m_visarea.bottom())); // start off by doing a partial update up to the line before us, in case that was necessary @@ -1393,6 +1411,7 @@ void screen_device::update_now() void screen_device::reset_partial_updates() { + m_last_partial_reset = machine().time(); m_last_partial_scan = 0; m_partial_scan_hpos = 0; m_partial_updates_this_frame = 0; @@ -1914,7 +1933,7 @@ void screen_device::finalize_burnin() //------------------------------------------------- -// finalize_burnin - finalize the burnin bitmap +// load_effect_overlay - //------------------------------------------------- void screen_device::load_effect_overlay(const char *filename) diff --git a/src/emu/screen.h b/src/emu/screen.h index a2d9e7f7f2e..9100ae5bacc 100644 --- a/src/emu/screen.h +++ b/src/emu/screen.h @@ -427,10 +427,10 @@ private: // device-level overrides virtual void device_validity_check(validity_checker &valid) const override; virtual void device_config_complete() override; - virtual void device_resolve_objects() override; - virtual void device_start() override; - virtual void device_reset() override; - virtual void device_stop() override; + virtual void device_resolve_objects() override ATTR_COLD; + virtual void device_start() override ATTR_COLD; + virtual void device_reset() override ATTR_COLD; + virtual void device_stop() override ATTR_COLD; virtual void device_post_load() override; // internal helpers @@ -461,7 +461,7 @@ private: screen_update_rgb32_delegate m_screen_update_rgb32; // screen update callback (32-bit RGB) devcb_write_line m_screen_vblank; // screen vblank line callback devcb_write32 m_scanline_cb; // screen scanline callback - optional_device<device_palette_interface> m_palette; // our palette + optional_device<device_palette_interface> m_palette; // our palette u32 m_video_attributes; // flags describing the video system optional_memory_region m_svg_region; // the region in which the svg data is in @@ -485,6 +485,7 @@ private: u8 m_curbitmap; // current bitmap index u8 m_curtexture; // current texture index bool m_changed; // has this bitmap changed? + attotime m_last_partial_reset; // last time partial updates were reset s32 m_last_partial_scan; // scanline of last partial update s32 m_partial_scan_hpos; // horizontal pixel last rendered on this partial scanline bitmap_argb32 m_screen_overlay_bitmap; // screen overlay bitmap @@ -504,7 +505,7 @@ private: emu_timer * m_scanline0_timer; // scanline 0 timer emu_timer * m_scanline_timer; // scanline timer u64 m_frame_number; // the current frame number - u32 m_partial_updates_this_frame;// partial update counter this frame + u32 m_partial_updates_this_frame; // partial update counter this frame bool m_is_primary_screen; @@ -517,7 +518,7 @@ private: vblank_state_delegate m_callback; }; - std::vector<std::unique_ptr<callback_item>> m_callback_list; // list of VBLANK callbacks + std::vector<std::unique_ptr<callback_item>> m_callback_list; // list of VBLANK callbacks // auto-sizing bitmaps class auto_bitmap_item diff --git a/src/emu/softlist.cpp b/src/emu/softlist.cpp index 4892a5ca5d8..c6bb7161be3 100644 --- a/src/emu/softlist.cpp +++ b/src/emu/softlist.cpp @@ -286,8 +286,7 @@ softlist_parser::softlist_parser( char buffer[1024]; for (bool done = false; !done; ) { - size_t length; - file.read(buffer, sizeof(buffer), length); // TODO: better error handling + auto const [err, length] = read(file, buffer, sizeof(buffer)); // TODO: better error handling if (!length) done = true; if (XML_Parse(m_parser, buffer, length, done) == XML_STATUS_ERROR) diff --git a/src/emu/softlist_dev.h b/src/emu/softlist_dev.h index 772bb970c99..41c92b4f3c0 100644 --- a/src/emu/softlist_dev.h +++ b/src/emu/softlist_dev.h @@ -134,7 +134,7 @@ public: protected: // device-level overrides - virtual void device_start() override; + virtual void device_start() override ATTR_COLD; virtual void device_validity_check(validity_checker &valid) const override ATTR_COLD; private: diff --git a/src/emu/sound.cpp b/src/emu/sound.cpp index c9626855197..a08ede3f471 100644 --- a/src/emu/sound.cpp +++ b/src/emu/sound.cpp @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Aaron Giles +// copyright-holders:O. Galibert, Aaron Giles /*************************************************************************** sound.cpp @@ -10,6 +10,9 @@ #include "emu.h" +#include "audio_effects/aeffect.h" +#include "resampler.h" + #include "config.h" #include "emuopts.h" #include "main.h" @@ -20,1632 +23,2531 @@ #include "osdepend.h" +#include "util/language.h" + +#include <algorithm> //************************************************************************** // DEBUGGING //************************************************************************** -//#define VERBOSE 1 -#define LOG_OUTPUT_FUNC osd_printf_debug +#define LOG_OUTPUT_FUNC m_machine.logerror -#include "logmacro.h" +#define LOG_OSD_INFO (1U << 1) +#define LOG_MAPPING (1U << 2) +#define LOG_OSD_STREAMS (1U << 3) +#define LOG_ORDER (1U << 4) -#define LOG_OUTPUT_WAV (0) +#define VERBOSE 0 + +#include "logmacro.h" -//************************************************************************** -// GLOBAL VARIABLES -//************************************************************************** const attotime sound_manager::STREAMS_UPDATE_ATTOTIME = attotime::from_hz(STREAMS_UPDATE_FREQUENCY); -//************************************************************************** -// STREAM BUFFER -//************************************************************************** +//**// Output buffer management -//------------------------------------------------- -// stream_buffer - constructor -//------------------------------------------------- +// Output buffers store samples produced every system-wide update. +// They give access to a window of samples produced before the update, +// and ensure that enough space is available to fit the update. -stream_buffer::stream_buffer(u32 sample_rate) : - m_end_second(0), - m_end_sample(0), - m_sample_rate(sample_rate), - m_sample_attos((sample_rate == 0) ? ATTOSECONDS_PER_SECOND : ((ATTOSECONDS_PER_SECOND + sample_rate - 1) / sample_rate)), - m_buffer(sample_rate) + +template<typename S> emu::detail::output_buffer_interleaved<S>::output_buffer_interleaved(u32 buffer_size, u32 channels) : + m_buffer(channels*buffer_size, 0), + m_sync_sample(0), + m_write_position(0), + m_sync_position(0), + m_history(0), + m_channels(channels) { } - -//------------------------------------------------- -// stream_buffer - destructor -//------------------------------------------------- - -stream_buffer::~stream_buffer() +template<typename S> void emu::detail::output_buffer_interleaved<S>::set_buffer_size(u32 buffer_size) { -#if (SOUND_DEBUG) - if (m_wav_file) - flush_wav(); -#endif + m_buffer.resize(m_channels*buffer_size, 0); } - -//------------------------------------------------- -// set_sample_rate - set a new sample rate for -// this buffer -//------------------------------------------------- - -void stream_buffer::set_sample_rate(u32 rate, bool resample) +template<typename S> void emu::detail::output_buffer_interleaved<S>::prepare_space(u32 samples) { - // skip if nothing is actually changing - if (rate == m_sample_rate) + if(!m_channels) return; - // force resampling off if coming to or from an invalid rate, or if we're at time 0 (startup) - sound_assert(rate >= SAMPLE_RATE_MINIMUM - 1); - if (rate < SAMPLE_RATE_MINIMUM || m_sample_rate < SAMPLE_RATE_MINIMUM || (m_end_second == 0 && m_end_sample == 0)) - resample = false; - - // note the time and period of the current buffer (end_time is AFTER the final sample) - attotime prevperiod = sample_period(); - attotime prevend = end_time(); - - // compute the time and period of the new buffer - attotime newperiod = attotime(0, (ATTOSECONDS_PER_SECOND + rate - 1) / rate); - attotime newend = attotime(prevend.seconds(), (prevend.attoseconds() / newperiod.attoseconds()) * newperiod.attoseconds()); - - // buffer a short runway of previous samples; in order to support smooth - // sample rate changes (needed by, e.g., Q*Bert's Votrax), we buffer a few - // samples at the previous rate, and then reconstitute them resampled - // (via simple point sampling) at the new rate. The litmus test is the - // voice when jumping off the edge in Q*Bert; without this extra effort - // it is crackly and/or glitchy at times - sample_t buffer[64]; - int buffered_samples = std::min(m_sample_rate, std::min(rate, u32(std::size(buffer)))); - - // if the new rate is lower, downsample into our holding buffer; - // otherwise just copy into our holding buffer for later upsampling - bool new_rate_higher = (rate > m_sample_rate); - if (resample) - { - if (!new_rate_higher) - backfill_downsample(&buffer[0], buffered_samples, newend, newperiod); - else - { - u32 end = m_end_sample; - for (int index = 0; index < buffered_samples; index++) - { - end = prev_index(end); -#if (SOUND_DEBUG) - // multiple resamples can occur before clearing out old NaNs so - // neuter them for this specific case - if (std::isnan(m_buffer[end])) - buffer[index] = 0; - else -#endif - buffer[index] = get(end); - } - } + // Check if potential overflow, bring data back up front if needed + u32 buffer_size = m_buffer.size() / m_channels; + if(m_write_position + samples > buffer_size) { + u32 source_start = (m_sync_position - m_history) * m_channels; + u32 source_end = m_write_position * m_channels; + std::copy(m_buffer.begin() + source_start, m_buffer.begin() + source_end, m_buffer.begin()); + m_write_position -= m_sync_position - m_history; + m_sync_position = m_history; } - // ensure our buffer is large enough to hold a full second at the new rate - if (m_buffer.size() < rate) - m_buffer.resize(rate); - - // set the new rate - m_sample_rate = rate; - m_sample_attos = newperiod.attoseconds(); - - // compute the new end sample index based on the buffer time - m_end_sample = time_to_buffer_index(prevend, false, true); - - // if the new rate is higher, upsample from our temporary buffer; - // otherwise just copy our previously-downsampled data - if (resample) - { -#if (SOUND_DEBUG) - // for aggressive debugging, fill the buffer with NANs to catch anyone - // reading beyond what we resample below - fill(NAN); -#endif - - if (new_rate_higher) - backfill_upsample(&buffer[0], buffered_samples, prevend, prevperiod); - else - { - u32 end = m_end_sample; - for (int index = 0; index < buffered_samples; index++) - { - end = prev_index(end); - put(end, buffer[index]); - } - } - } - - // if not resampling, clear the buffer - else - fill(0); + // Clear the destination range + u32 fill_start = m_write_position * m_channels; + u32 fill_end = (m_write_position + samples) * m_channels; + std::fill(m_buffer.begin() + fill_start, m_buffer.begin() + fill_end, 0.0); } - -//------------------------------------------------- -// open_wav - open a WAV file for logging purposes -//------------------------------------------------- - -#if (SOUND_DEBUG) -void stream_buffer::open_wav(char const *filename) +template<typename S> void emu::detail::output_buffer_interleaved<S>::commit(u32 samples) { - // always open at 48k so that sound programs can handle it - // re-sample as needed - m_wav_file = util::wav_open(filename, 48000, 1); + m_write_position += samples; } -#endif - - -//------------------------------------------------- -// flush_wav - flush data to the WAV file -//------------------------------------------------- -#if (SOUND_DEBUG) -void stream_buffer::flush_wav() +template<typename S> void emu::detail::output_buffer_interleaved<S>::sync() { - // skip if no file - if (!m_wav_file) - return; + m_sync_sample += m_write_position - m_sync_position; + m_sync_position = m_write_position; +} - // grab a view of the data from the last-written point - read_stream_view view(this, m_last_written, m_end_sample, 1.0f); - m_last_written = m_end_sample; +template<typename S> emu::detail::output_buffer_flat<S>::output_buffer_flat(u32 buffer_size, u32 channels) : + m_buffer(channels), + m_sync_sample(0), + m_write_position(0), + m_sync_position(0), + m_history(0), + m_channels(channels) +{ + for(auto &b : m_buffer) + b.resize(buffer_size, 0); +} - // iterate over chunks for conversion - s16 buffer[1024]; - for (int samplebase = 0; samplebase < view.samples(); samplebase += std::size(buffer)) - { - // clamp to the buffer size - int cursamples = view.samples() - samplebase; - if (cursamples > std::size(buffer)) - cursamples = std::size(buffer); +template<typename S> void emu::detail::output_buffer_flat<S>::register_save_state(device_t &device, const char *id1, const char *id2) +{ + auto &save = device.machine().save(); - // convert and fill - for (int sampindex = 0; sampindex < cursamples; sampindex++) - buffer[sampindex] = s16(view.get(samplebase + sampindex) * 32768.0); + for(unsigned int i=0; i != m_buffer.size(); i++) + save.save_item(&device, id1, id2, i, NAME(m_buffer[i])); - // write to the WAV - util::wav_add_data_16(*m_wav_file, buffer, cursamples); - } + save.save_item(&device, id1, id2, 0, NAME(m_sync_sample)); + save.save_item(&device, id1, id2, 0, NAME(m_write_position)); + save.save_item(&device, id1, id2, 0, NAME(m_sync_position)); + save.save_item(&device, id1, id2, 0, NAME(m_history)); } -#endif - - -//------------------------------------------------- -// index_time - return the attotime of a given -// index within the buffer -//------------------------------------------------- -attotime stream_buffer::index_time(s32 index) const +template<typename S> void emu::detail::output_buffer_flat<S>::set_buffer_size(u32 buffer_size) { - index = clamp_index(index); - return attotime(m_end_second - ((index > m_end_sample) ? 1 : 0), index * m_sample_attos); + for(auto &b : m_buffer) + b.resize(buffer_size, 0); } - -//------------------------------------------------- -// time_to_buffer_index - given an attotime, -// return the buffer index corresponding to it -//------------------------------------------------- - -u32 stream_buffer::time_to_buffer_index(attotime time, bool round_up, bool allow_expansion) +template<typename S> void emu::detail::output_buffer_flat<S>::prepare_space(u32 samples) { - // compute the sample index within the second - int sample = (time.attoseconds() + (round_up ? (m_sample_attos - 1) : 0)) / m_sample_attos; - sound_assert(sample >= 0 && sample <= size()); - - // if the time is past the current end, make it the end - if (time.seconds() > m_end_second || (time.seconds() == m_end_second && sample > m_end_sample)) - { - sound_assert(allow_expansion); - - m_end_sample = sample; - m_end_second = time.m_seconds; + if(!m_channels) + return; - // due to round_up, we could tweak over the line into the next second - if (sample >= size()) - { - m_end_sample -= size(); - m_end_second++; - } + // Check if potential overflow, bring data back up front if needed + u32 buffer_size = m_buffer[0].size(); + if(m_write_position + samples > buffer_size) { + u32 source_start = m_sync_position - m_history; + u32 source_end = m_write_position; + for(u32 channel = 0; channel != m_channels; channel++) + std::copy(m_buffer[channel].begin() + source_start, m_buffer[channel].begin() + source_end, m_buffer[channel].begin()); + m_write_position -= source_start; + m_sync_position = m_history; } - // if the time is before the start, fail - if (time.seconds() + 1 < m_end_second || (time.seconds() + 1 == m_end_second && sample < m_end_sample)) - throw emu_fatalerror("Attempt to create an out-of-bounds view"); - - return clamp_index(sample); + // Clear the destination range + u32 fill_start = m_write_position; + u32 fill_end = m_write_position + samples; + for(u32 channel = 0; channel != m_channels; channel++) + std::fill(m_buffer[channel].begin() + fill_start, m_buffer[channel].begin() + fill_end, 0.0); } +template<typename S> void emu::detail::output_buffer_flat<S>::commit(u32 samples) +{ + m_write_position += samples; +} -//------------------------------------------------- -// backfill_downsample - this is called BEFORE -// the sample rate change to downsample from the -// end of the current buffer into a temporary -// holding location -//------------------------------------------------- - -void stream_buffer::backfill_downsample(sample_t *dest, int samples, attotime newend, attotime newperiod) +template<typename S> void emu::detail::output_buffer_flat<S>::sync() { - // compute the time of the first sample to be backfilled; start one period before - attotime time = newend - newperiod; + m_sync_sample += m_write_position - m_sync_position; + m_sync_position = m_write_position; +} - // loop until we run out of buffered data - int dstindex; - for (dstindex = 0; dstindex < samples && time.seconds() >= 0; dstindex++) - { - u32 srcindex = time_to_buffer_index(time, false); -#if (SOUND_DEBUG) - // multiple resamples can occur before clearing out old NaNs so - // neuter them for this specific case - if (std::isnan(m_buffer[srcindex])) - dest[dstindex] = 0; +template<typename S> void emu::detail::output_buffer_flat<S>::set_history(u32 history) +{ + m_history = history; + if(m_sync_position < m_history) { + u32 delta = m_history - m_sync_position; + if(m_write_position) + for(u32 channel = 0; channel != m_channels; channel++) { + std::copy_backward(m_buffer[channel].begin(), m_buffer[channel].begin() + m_write_position, m_buffer[channel].begin() + m_write_position + delta); + std::fill(m_buffer[channel].begin() + 1, m_buffer[channel].begin() + delta, m_buffer[channel][0]); + } else -#endif - dest[dstindex] = get(srcindex); - time -= newperiod; + for(u32 channel = 0; channel != m_channels; channel++) + std::fill(m_buffer[channel].begin(), m_buffer[channel].begin() + m_history, 0.0); + + m_write_position += delta; + m_sync_position = m_history; } - for ( ; dstindex < samples; dstindex++) - dest[dstindex] = 0; } +template<typename S> void emu::detail::output_buffer_flat<S>::resample(u32 previous_rate, u32 next_rate, attotime sync_time, attotime now) +{ + if(!m_write_position) + return; -//------------------------------------------------- -// backfill_upsample - this is called AFTER the -// sample rate change to take a copied buffer -// of samples at the old rate and upsample them -// to the new (current) rate -//------------------------------------------------- + auto si = [](attotime time, u32 rate) -> s64 { + return time.m_seconds * rate + ((time.m_attoseconds / 100000000) * rate) / 10000000000LL; + }; + + auto cv = [](u32 source_rate, u32 dest_rate, s64 time) -> std::pair<s64, double> { + s64 sec = time / source_rate; + s64 prem = time % source_rate; + double nrem = double(prem * dest_rate) / double(source_rate); + s64 cyc = s64(nrem); + return std::make_pair(sec * dest_rate + cyc, nrem - cyc); + }; + + // Compute what will be the new start, sync and write positions (if it fits) + s64 nsync = si(sync_time, next_rate); + s64 nwrite = si(now, next_rate); + s64 pbase = m_sync_sample - m_sync_position; // Beware, pbase can be negative at startup due to history size + auto [nbase, nbase_dec] = cv(previous_rate, next_rate, pbase < 0 ? 0 : pbase); + nbase += 1; + if(nbase > nsync) + nbase = nsync; + + u32 space = m_buffer[0].size(); + if(nwrite - nbase > space) { + nbase = nwrite - space; + if(nbase > nsync) + fatalerror("Stream buffer too small, can't proceed, rate change %d -> %d, space=%d\n", previous_rate, next_rate, space); + } -void stream_buffer::backfill_upsample(sample_t const *src, int samples, attotime prevend, attotime prevperiod) -{ - // compute the time of the first sample to be backfilled; start one period before - attotime time = end_time() - sample_period(); + auto [ppos, pdec] = cv(next_rate, previous_rate, nbase); + if(ppos < pbase || ppos > pbase + m_write_position) + fatalerror("Something went very wrong, ppos=%d, pbase=%d, pbase+wp=%d\n", ppos, pbase, pbase + m_write_position); - // also adjust the buffered sample end time to point to the sample time of the - // final sample captured - prevend -= prevperiod; + double step = double(previous_rate) / double(next_rate); + u32 pindex = ppos - pbase; + u32 nend = nwrite - nbase; - // loop until we run out of buffered data - u32 end = m_end_sample; - int srcindex = 0; - while (1) - { - // if our backfill time is before the current buffered sample time, - // back up until we have a sample that covers this time - while (time < prevend && srcindex < samples) - { - prevend -= prevperiod; - srcindex++; - } + // Warning: don't try to be too clever, the m_buffer storage is + // registered in the save state system, so it must not move or + // change size - // stop when we run out of source - if (srcindex >= samples) - break; + std::vector<S> copy(m_write_position); + for(u32 channel = 0; channel != m_channels; channel++) { + std::copy(m_buffer[channel].begin(), m_buffer[channel].begin() + m_write_position, copy.begin()); + + // Interpolate the buffer contents - // write this sample at the pevious position - end = prev_index(end); - put(end, src[srcindex]); + for(u32 nindex = 0; nindex != nend; nindex++) { + u32 pi0 = std::clamp(pindex, 0U, m_write_position - 1); + u32 pi1 = std::clamp(pindex + 1, 0U, m_write_position - 1); + m_buffer[channel][nindex] = copy[pi0] * (1-pdec) + copy[pi1] * pdec; - // back up to the next sample time - time -= sample_period(); + pdec += step; + if(pdec >= 1) { + int s = s32(pdec); + pindex += s; + pdec -= s; + } + } } -} + m_sync_sample = nsync; + m_sync_position = m_sync_sample - nbase; + m_write_position = nend; + // history and the associated resizes are taken into account later +} -//************************************************************************** -// SOUND STREAM OUTPUT -//************************************************************************** +template class emu::detail::output_buffer_flat<sound_stream::sample_t>; +template class emu::detail::output_buffer_interleaved<s16>; -//------------------------------------------------- -// sound_stream_output - constructor -//------------------------------------------------- -sound_stream_output::sound_stream_output() : - m_stream(nullptr), - m_index(0), - m_gain(1.0) +// Not inline because with the unique_ptr it would require audio_effect in emu.h + +sound_manager::effect_step::effect_step(u32 buffer_size, u32 channels) : m_buffer(buffer_size, channels) { } -//------------------------------------------------- -// init - initialization -//------------------------------------------------- +//**// Streams and routes -void sound_stream_output::init(sound_stream &stream, u32 index, char const *tag) +sound_stream::sound_stream(device_t &device, u32 inputs, u32 outputs, u32 sample_rate, stream_update_delegate callback, sound_stream_flags flags) : + m_device(device), + m_output_buffer(0, outputs), + m_sample_rate(sample_rate == SAMPLE_RATE_INPUT_ADAPTIVE || sample_rate == SAMPLE_RATE_OUTPUT_ADAPTIVE || sample_rate == SAMPLE_RATE_ADAPTIVE ? 0 : sample_rate), + m_input_count(inputs), + m_output_count(outputs), + m_input_adaptive(sample_rate == SAMPLE_RATE_INPUT_ADAPTIVE || sample_rate == SAMPLE_RATE_ADAPTIVE), + m_output_adaptive(sample_rate == SAMPLE_RATE_OUTPUT_ADAPTIVE || sample_rate == SAMPLE_RATE_ADAPTIVE), + m_synchronous((flags & STREAM_SYNCHRONOUS) != 0), + m_started(false), + m_in_update(false), + m_sync_timer(nullptr), + m_callback(std::move(callback)) { - // set the passed-in data - m_stream = &stream; - m_index = index; + sound_assert(outputs > 0 || inputs > 0); - // save our state - auto &save = stream.device().machine().save(); - save.save_item(&stream.device(), "stream.output", tag, index, NAME(m_gain)); + // create a name + m_name = m_device.name(); + m_name += " '"; + m_name += m_device.tag(); + m_name += "'"; -#if (LOG_OUTPUT_WAV) - std::string filename = stream.device().machine().basename(); - filename += stream.device().tag(); - for (int index = 0; index < filename.size(); index++) - if (filename[index] == ':') - filename[index] = '_'; - if (dynamic_cast<default_resampler_stream *>(&stream) != nullptr) - filename += "_resampler"; - filename += "_OUT_"; - char buf[10]; - sprintf(buf, "%d", index); - filename += buf; - filename += ".wav"; - m_buffer.open_wav(filename.c_str()); -#endif + // create an update timer for synchronous streams + if(synchronous()) + m_sync_timer = m_device.timer_alloc(FUNC(sound_stream::sync_update), this); + + // create the gain vectors + m_input_channel_gain.resize(m_input_count, 1.0); + m_output_channel_gain.resize(m_output_count, 1.0); + m_user_output_channel_gain.resize(m_output_count, 1.0); + m_user_output_gain = 1.0; } +sound_stream::~sound_stream() +{ +} -//------------------------------------------------- -// name - return the friendly name of this output -//------------------------------------------------- +void sound_stream::add_bw_route(sound_stream *source, int output, int input, float gain) +{ + m_bw_routes.emplace_back(route_bw(source, output, input, gain)); +} -std::string sound_stream_output::name() const +void sound_stream::add_fw_route(sound_stream *target, int input, int output) { - // start with our owning stream's name - std::ostringstream str; - util::stream_format(str, "%s Ch.%d", m_stream->name(), m_stream->output_base() + m_index); - return str.str(); + m_fw_routes.emplace_back(route_fw(target, input, output)); } +bool sound_stream::set_route_gain(sound_stream *source, int source_channel, int target_channel, float gain) +{ + for(auto &r : m_bw_routes) + if(r.m_source == source && r.m_output == source_channel && r.m_input == target_channel) { + r.m_gain = gain; + return true; + } + return false; +} -//------------------------------------------------- -// optimize_resampler - optimize resamplers by -// either returning the native rate or another -// input's resampler if they can be reused -//------------------------------------------------- +std::vector<sound_stream *> sound_stream::sources() const +{ + std::vector<sound_stream *> streams; + for(const route_bw &route : m_bw_routes) { + sound_stream *stream = route.m_source; + for(const sound_stream *s : streams) + if(s == stream) + goto already; + streams.push_back(stream); + already:; + } + return streams; +} -sound_stream_output &sound_stream_output::optimize_resampler(sound_stream_output *input_resampler) +std::vector<sound_stream *> sound_stream::targets() const { - // if no resampler, or if the resampler rate matches our rate, return ourself - if (input_resampler == nullptr || buffer_sample_rate() == input_resampler->buffer_sample_rate()) - return *this; + std::vector<sound_stream *> streams; + for(const route_fw &route : m_fw_routes) { + sound_stream *stream = route.m_target; + for(const sound_stream *s : streams) + if(s == stream) + goto already; + streams.push_back(stream); + already:; + } + return streams; +} - // scan our list of resamplers to see if there's another match - for (auto &resampler : m_resampler_list) - if (resampler->buffer_sample_rate() == input_resampler->buffer_sample_rate()) - return *resampler; +void sound_stream::register_state() +{ + // create a unique tag for saving + m_state_tag = string_format("%d", m_device.machine().sound().unique_id()); + auto &save = m_device.machine().save(); - // add the input to our list and return the one we were given back - m_resampler_list.push_back(input_resampler); - return *input_resampler; -} + save.save_item(&m_device, "stream.sound_stream", m_state_tag.c_str(), 0, NAME(m_sync_time)); + save.save_item(&m_device, "stream.sound_stream", m_state_tag.c_str(), 0, NAME(m_sample_rate)); + if(m_input_count) + save.save_item(&m_device, "stream.sound_stream", m_state_tag.c_str(), 0, NAME(m_input_channel_gain)); + if(m_output_count) + save.save_item(&m_device, "stream.sound_stream", m_state_tag.c_str(), 0, NAME(m_output_channel_gain)); + // user gains go to .cfg files, not state files + m_output_buffer.register_save_state(m_device, "stream.sound_stream.output_buffer", m_state_tag.c_str()); + for(unsigned int i=0; i != m_bw_routes.size(); i++) + save.save_item(&m_device, "stream.sound_stream", m_state_tag.c_str(), i, m_bw_routes[i].m_gain, "route_gain"); +} -//************************************************************************** -// SOUND STREAM INPUT -//************************************************************************** -//------------------------------------------------- -// sound_stream_input - constructor -//------------------------------------------------- +void sound_stream::compute_dependants() +{ + m_dependant_streams.clear(); + for(const route_bw &r : m_bw_routes) + r.m_source->add_dependants(m_dependant_streams); +} -sound_stream_input::sound_stream_input() : - m_owner(nullptr), - m_native_source(nullptr), - m_resampler_source(nullptr), - m_index(0), - m_gain(1.0), - m_user_gain(1.0) +void sound_stream::add_dependants(std::vector<sound_stream *> &deps) { + for(const route_bw &r : m_bw_routes) + r.m_source->add_dependants(deps); + for(sound_stream *dep : deps) + if(dep == this) + return; + deps.push_back(this); } -//------------------------------------------------- -// init - initialization -//------------------------------------------------- +//**// Stream sample rate -void sound_stream_input::init(sound_stream &stream, u32 index, char const *tag, sound_stream_output *resampler) +void sound_stream::set_sample_rate(u32 new_rate) { - // set the passed-in values - m_owner = &stream; - m_index = index; - m_resampler_source = resampler; - - // save our state - auto &save = stream.device().machine().save(); - save.save_item(&stream.device(), "stream.input", tag, index, NAME(m_gain)); - save.save_item(&stream.device(), "stream.input", tag, index, NAME(m_user_gain)); + m_input_adaptive = m_output_adaptive = false; + internal_set_sample_rate(new_rate); } - -//------------------------------------------------- -// name - return the friendly name of this input -//------------------------------------------------- - -std::string sound_stream_input::name() const +void sound_stream::internal_set_sample_rate(u32 new_rate) { - // start with our owning stream's name - std::ostringstream str; - util::stream_format(str, "%s", m_owner->name()); + if(m_started) { + update(); + m_output_buffer.resample(m_sample_rate, new_rate, m_sync_time, m_device.machine().time()); + m_sample_rate = new_rate; + for(const route_fw &r : m_fw_routes) + r.m_target->create_resamplers(); + create_resamplers(); + lookup_history_sizes(); - // if we have a source, indicate where the sound comes from by device name and tag - if (valid()) - util::stream_format(str, " <- %s", m_native_source->name()); - return str.str(); + } else + m_sample_rate = new_rate; } +bool sound_stream::try_solving_frequency() +{ + if(frequency_is_solved()) + return false; -//------------------------------------------------- -// set_source - wire up the output source for -// our consumption -//------------------------------------------------- + if(input_adaptive() && !output_adaptive()) { + u32 freq = 0; + for(const route_bw &r : m_bw_routes) { + if(!r.m_source->frequency_is_solved()) + return false; + if(freq < r.m_source->sample_rate()) + freq = r.m_source->sample_rate(); + } + m_sample_rate = freq; + return true; + + } else if(output_adaptive() && !input_adaptive()) { + u32 freq = 0; + for(const route_fw &r : m_fw_routes) { + if(!r.m_target->frequency_is_solved()) + return false; + if(freq < r.m_target->sample_rate()) + freq = r.m_target->sample_rate(); + } + m_sample_rate = freq; + return true; + + } else { + u32 freqbw = 0; + for(const route_bw &r : m_bw_routes) { + if(!r.m_source->frequency_is_solved()) { + freqbw = 0; + break; + } + if(freqbw < r.m_source->sample_rate()) + freqbw = r.m_source->sample_rate(); + } + u32 freqfw = 0; + for(const route_fw &r : m_fw_routes) { + if(!r.m_target->frequency_is_solved()) { + freqfw = 0; + break; + } + if(freqfw < r.m_target->sample_rate()) + freqfw = r.m_target->sample_rate(); + } + if(!freqbw && !freqfw) + return false; -void sound_stream_input::set_source(sound_stream_output *source) -{ - m_native_source = source; - if (m_resampler_source != nullptr) - m_resampler_source->stream().set_input(0, &source->stream(), source->index()); + m_sample_rate = freqfw > freqbw ? freqfw : freqbw; + return true; + } } -//------------------------------------------------- -// update - update our source's stream to the -// current end time and return a view to its -// contents -//------------------------------------------------- +//**// Stream flow and updates -read_stream_view sound_stream_input::update(attotime start, attotime end) +void sound_stream::init() { - // shouldn't get here unless valid - sound_assert(valid()); + // Ensure the buffer size is non-zero, since a stream can be started at any time + u32 bsize = m_sample_rate ? m_sample_rate : 48000; + m_input_buffer.resize(m_input_count); + for(auto &b : m_input_buffer) + b.resize(bsize); - // pick an optimized resampler - sound_stream_output &source = m_native_source->optimize_resampler(m_resampler_source); + m_output_buffer.set_buffer_size(bsize); - // if not using our own resampler, keep it up to date in case we need to invoke it later - if (m_resampler_source != nullptr && &source != m_resampler_source) - m_resampler_source->set_end_time(end); + m_samples_to_update = 0; + m_started = true; + if(synchronous()) + reprime_sync_timer(); +} - // update the source, returning a view of the needed output over the start and end times - return source.stream().update_view(start, end, source.index()).apply_gain(m_gain * m_user_gain * source.gain()); +u64 sound_stream::get_current_sample_index() const +{ + attotime now = m_device.machine().time(); + return now.m_seconds * m_sample_rate + ((now.m_attoseconds / 1'000'000'000) * m_sample_rate) / 1'000'000'000; } +void sound_stream::update() +{ + if(!is_active() || m_in_update || m_device.machine().phase() <= machine_phase::RESET) + return; -//------------------------------------------------- -// apply_sample_rate_changes - tell our sources -// to apply any sample rate changes, informing -// them of our current rate -//------------------------------------------------- + // Find out where we are and how much we have to do + u64 idx = get_current_sample_index(); + m_samples_to_update = idx - m_output_buffer.write_sample() + 1; // We want to include the current sample, hence the +1 -void sound_stream_input::apply_sample_rate_changes(u32 updatenum, u32 downstream_rate) -{ - // shouldn't get here unless valid - sound_assert(valid()); + if(m_samples_to_update > 0) { + m_in_update = true; - // if we have a resampler, tell it (and it will tell the native source) - if (m_resampler_source != nullptr) - m_resampler_source->stream().apply_sample_rate_changes(updatenum, downstream_rate); + // If there's anything to do, well, do it, starting with the dependencies + for(auto &stream : m_dependant_streams) + stream->update_nodeps(); - // otherwise, just tell the native source directly - else - m_native_source->stream().apply_sample_rate_changes(updatenum, downstream_rate); + do_update(); + m_in_update = false; + } + m_samples_to_update = 0; } +void sound_stream::update_nodeps() +{ + if(!is_active() || m_in_update || m_device.machine().phase() <= machine_phase::RESET) + return; + // Find out where we are and how much we have to do + u64 idx = get_current_sample_index(); + m_samples_to_update = idx - m_output_buffer.write_sample() + 1; // We want to include the current sample, hence the +1 -//************************************************************************** -// SOUND STREAM -//************************************************************************** + if(m_samples_to_update > 0) { + m_in_update = true; -//------------------------------------------------- -// sound_stream - private common constructor -//------------------------------------------------- + // If there's anything to do, well, do it + do_update(); + m_in_update = false; + } + m_samples_to_update = 0; +} -sound_stream::sound_stream(device_t &device, u32 inputs, u32 outputs, u32 output_base, u32 sample_rate, sound_stream_flags flags) : - m_device(device), - m_next(nullptr), - m_sample_rate((sample_rate < SAMPLE_RATE_MINIMUM) ? (SAMPLE_RATE_MINIMUM - 1) : (sample_rate < SAMPLE_RATE_OUTPUT_ADAPTIVE) ? sample_rate : 48000), - m_pending_sample_rate(SAMPLE_RATE_INVALID), - m_last_sample_rate_update(0), - m_input_adaptive(sample_rate == SAMPLE_RATE_INPUT_ADAPTIVE), - m_output_adaptive(sample_rate == SAMPLE_RATE_OUTPUT_ADAPTIVE), - m_synchronous((flags & STREAM_SYNCHRONOUS) != 0), - m_resampling_disabled((flags & STREAM_DISABLE_INPUT_RESAMPLING) != 0), - m_sync_timer(nullptr), - m_last_update_end_time(attotime::zero), - m_input(inputs), - m_input_view(inputs), - m_empty_buffer(100), - m_output_base(output_base), - m_output(outputs), - m_output_view(outputs) +void sound_stream::create_resamplers() { - sound_assert(outputs > 0); + if(!is_active()) { + for(auto &r : m_bw_routes) + r.m_resampler = nullptr; + return; + } - // create a name - m_name = m_device.name(); - m_name += " '"; - m_name += m_device.tag(); - m_name += "'"; + for(auto &r : m_bw_routes) + if(r.m_source->is_active() && r.m_source->sample_rate() != m_sample_rate) + r.m_resampler = m_device.machine().sound().get_resampler(r.m_source->sample_rate(), m_sample_rate); + else + r.m_resampler = nullptr; +} - // create a unique tag for saving - std::string state_tag = string_format("%d", m_device.machine().sound().unique_id()); - auto &save = m_device.machine().save(); - save.save_item(&m_device, "stream.sound_stream", state_tag.c_str(), 0, NAME(m_sample_rate)); - save.save_item(&m_device, "stream.sound_stream", state_tag.c_str(), 0, NAME(m_last_update_end_time)); - save.register_postload(save_prepost_delegate(FUNC(sound_stream::postload), this)); - save.register_presave(save_prepost_delegate(FUNC(sound_stream::presave), this)); +void sound_stream::lookup_history_sizes() +{ + u32 history = 0; + for(auto &r : m_fw_routes) { + u32 h = r.m_target->get_history_for_bw_route(this, r.m_output); + if(h > history) + history = h; + } - // initialize all inputs - for (unsigned int inputnum = 0; inputnum < m_input.size(); inputnum++) - { - // allocate a resampler stream if needed, and get a pointer to its output - sound_stream_output *resampler = nullptr; - if (!m_resampling_disabled) - { - m_resampler_list.push_back(std::make_unique<default_resampler_stream>(m_device)); - resampler = &m_resampler_list.back()->m_output[0]; + m_output_buffer.set_history(history); +} + +u32 sound_stream::get_history_for_bw_route(const sound_stream *source, u32 channel) const +{ + u32 history = 0; + for(auto &r : m_bw_routes) + if(r.m_source == source && r.m_output == channel && r.m_resampler) { + u32 h = r.m_resampler->history_size(); + if(h > history) + history = h; } + return history; +} - // add the new input - m_input[inputnum].init(*this, inputnum, state_tag.c_str(), resampler); +void sound_stream::do_update() +{ + // Mix in all the inputs (if any) + if(m_input_count) { + for(auto &b : m_input_buffer) + std::fill(b.begin(), b.begin() + m_samples_to_update, 0.0); + for(const auto &r : m_bw_routes) { + if(!r.m_source->is_active()) + continue; + + float gain = r.m_source->m_user_output_gain * r.m_source->m_output_channel_gain[r.m_output] * r.m_source->m_user_output_channel_gain[r.m_output] * r.m_gain * m_input_channel_gain[r.m_input]; + auto &db = m_input_buffer[r.m_input]; + if(r.m_resampler) + r.m_resampler->apply(r.m_source->m_output_buffer, db, m_output_buffer.write_sample(), r.m_output, gain, m_samples_to_update); + + else { + const sample_t *sb = r.m_source->m_output_buffer.ptrs(r.m_output, m_output_buffer.write_sample() - r.m_source->m_output_buffer.sync_sample()); + for(u32 i = 0; i != m_samples_to_update; i++) + db[i] += sb[i] * gain; + } + } } - // initialize all outputs - for (unsigned int outputnum = 0; outputnum < m_output.size(); outputnum++) - m_output[outputnum].init(*this, outputnum, state_tag.c_str()); + // Prepare the output space (if any) + m_output_buffer.prepare_space(m_samples_to_update); - // create an update timer for synchronous streams - if (synchronous()) - m_sync_timer = m_device.timer_alloc(FUNC(sound_stream::sync_update), this); + // Call the callback + m_callback(*this); - // force an update to the sample rates - sample_rate_changed(); + // Update the indexes + m_output_buffer.commit(m_samples_to_update); } - -//------------------------------------------------- -// sound_stream - constructor -//------------------------------------------------- - -sound_stream::sound_stream(device_t &device, u32 inputs, u32 outputs, u32 output_base, u32 sample_rate, stream_update_delegate callback, sound_stream_flags flags) : - sound_stream(device, inputs, outputs, output_base, sample_rate, flags) +void sound_stream::sync(attotime now) { - m_callback_ex = std::move(callback); + m_sync_time = now; + m_output_buffer.sync(); } -//------------------------------------------------- -// ~sound_stream - destructor -//------------------------------------------------- -sound_stream::~sound_stream() + +attotime sound_stream::sample_to_time(u64 index) const { + attotime res = attotime::zero; + res.m_seconds = index / m_sample_rate; + u64 remain = index % m_sample_rate; + res.m_attoseconds = ((remain * 1'000'000'000) / m_sample_rate) * 1'000'000'000; + return res; } -//------------------------------------------------- -// set_sample_rate - set the sample rate on a -// given stream -//------------------------------------------------- +//**// Synchronous stream updating -void sound_stream::set_sample_rate(u32 new_rate) +void sound_stream::reprime_sync_timer() { - // we will update this on the next global update - if (new_rate != sample_rate()) - m_pending_sample_rate = new_rate; + if(!is_active()) + return; + + u64 next_sample = m_output_buffer.write_sample() + 1; + attotime next_time = sample_to_time(next_sample); + next_time.m_attoseconds += 1'000'000'000; // Go to the next nanosecond ' + m_sync_timer->adjust(next_time - m_device.machine().time()); } +void sound_stream::sync_update(s32) +{ + update(); + reprime_sync_timer(); +} -//------------------------------------------------- -// set_input - configure a stream's input -//------------------------------------------------- -void sound_stream::set_input(int index, sound_stream *input_stream, int output_index, float gain) +//**// Sound manager and stream allocation +sound_manager::sound_manager(running_machine &machine) : + m_machine(machine), + m_update_timer(nullptr), + m_last_sync_time(attotime::zero), + m_effects_thread(nullptr), + m_effects_done(false), + m_master_gain(1.0), + m_muted(0), + m_nosound_mode(machine.osd().no_sound()), + m_unique_id(0), + m_wavfile(), + m_resampler_type(RESAMPLER_LOFI), + m_resampler_hq_latency(0.005), + m_resampler_hq_length(400), + m_resampler_hq_phases(200) { - LOG("stream_set_input(%p, '%s', %d, %p, %d, %f)\n", (void *)this, m_device.tag(), - index, (void *)input_stream, output_index, gain); + // register callbacks + machine.configuration().config_register( + "mixer", + configuration_manager::load_delegate(&sound_manager::config_load, this), + configuration_manager::save_delegate(&sound_manager::config_save, this)); + machine.add_notifier(MACHINE_NOTIFY_PAUSE, machine_notify_delegate(&sound_manager::pause, this)); + machine.add_notifier(MACHINE_NOTIFY_RESUME, machine_notify_delegate(&sound_manager::resume, this)); + machine.add_notifier(MACHINE_NOTIFY_RESET, machine_notify_delegate(&sound_manager::reset, this)); + machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(&sound_manager::stop_recording, this)); - // make sure it's a valid input - if (index >= m_input.size()) - fatalerror("stream_set_input attempted to configure nonexistent input %d (%d max)\n", index, int(m_input.size())); + // register global states + machine.save().save_item(NAME(m_last_sync_time)); - // make sure it's a valid output - if (input_stream != nullptr && output_index >= input_stream->m_output.size()) - fatalerror("stream_set_input attempted to use a nonexistent output %d (%d max)\n", output_index, int(m_output.size())); + // start the periodic update flushing timer + m_update_timer = machine.scheduler().timer_alloc(timer_expired_delegate(FUNC(sound_manager::update), this)); + m_update_timer->adjust(STREAMS_UPDATE_ATTOTIME, 0, STREAMS_UPDATE_ATTOTIME); - // wire it up - m_input[index].set_source((input_stream != nullptr) ? &input_stream->m_output[output_index] : nullptr); - m_input[index].set_gain(gain); + // mark the generation as "just starting" + m_osd_info.m_generation = 0xffffffff; +} - // update sample rates now that we know the input - sample_rate_changed(); +sound_manager::~sound_manager() +{ + if(m_effects_thread) { + m_effects_done = true; + m_effects_condition.notify_all(); + m_effects_thread->join(); + m_effects_thread = nullptr; + } +} + +sound_stream *sound_manager::stream_alloc(device_t &device, u32 inputs, u32 outputs, u32 sample_rate, stream_update_delegate callback, sound_stream_flags flags) +{ + m_stream_list.push_back(std::make_unique<sound_stream>(device, inputs, outputs, sample_rate, callback, flags)); + return m_stream_list.back().get(); } -//------------------------------------------------- -// update - force a stream to update to -// the current emulated time -//------------------------------------------------- +//**// Sound system initialization -void sound_stream::update() +void sound_manager::before_devices_init() { - // ignore any update requests if we're already up to date - attotime start = m_output[0].end_time(); - attotime end = m_device.machine().time(); - if (start >= end) - return; + // Inform the targets of the existence of the routes + for(device_sound_interface &sound : sound_interface_enumerator(machine().root_device())) + sound.sound_before_devices_init(); - // regular update then - update_view(start, end); + m_machine.save().register_postload(save_prepost_delegate(FUNC(sound_manager::postload), this)); } - -//------------------------------------------------- -// update_view - force a stream to update to -// the current emulated time and return a view -// to the generated samples from the given -// output number -//------------------------------------------------- - -read_stream_view sound_stream::update_view(attotime start, attotime end, u32 outputnum) +void sound_manager::postload() { - sound_assert(start <= end); - sound_assert(outputnum < m_output.size()); + std::unique_lock<std::mutex> lock(m_effects_mutex); + attotime now = machine().time(); + for(osd_output_stream &stream : m_osd_output_streams) { + stream.m_last_sync = rate_and_time_to_index(now, stream.m_rate); + stream.m_samples = 0; + } +} - // clean up parameters for when the asserts go away - if (outputnum >= m_output.size()) - outputnum = 0; - if (start > end) - start = end; +void sound_manager::after_devices_init() +{ + // Link all the streams together + for(device_sound_interface &sound : sound_interface_enumerator(machine().root_device())) + sound.sound_after_devices_init(); + + // Resolve the frequencies + int need_to_solve = 0; + for(auto &stream : m_stream_list) + if(!stream->frequency_is_solved()) + need_to_solve ++; + + while(need_to_solve) { + int prev_need_to_solve = need_to_solve; + for(auto &stream : m_stream_list) + if(!stream->frequency_is_solved() && stream->try_solving_frequency()) + need_to_solve --; + if(need_to_solve == prev_need_to_solve) + break; + } - auto profile = g_profiler.start(PROFILER_SOUND); + if(need_to_solve) { + u32 def = machine().sample_rate(); + for(auto &stream : m_stream_list) + if(!stream->frequency_is_solved()) + stream->internal_set_sample_rate(def); + } - // reposition our start to coincide with the current buffer end - attotime update_start = m_output[outputnum].end_time(); - if (update_start <= end) - { - // create views for all the outputs - for (unsigned int outindex = 0; outindex < m_output.size(); outindex++) - m_output_view[outindex] = m_output[outindex].view(update_start, end); - - // skip if nothing to do - u32 samples = m_output_view[0].samples(); - sound_assert(samples >= 0); - if (samples != 0 && m_sample_rate >= SAMPLE_RATE_MINIMUM) - { - sound_assert(!synchronous() || samples == 1); - - // ensure all input streams are up to date, and create views for them as well - for (unsigned int inputnum = 0; inputnum < m_input.size(); inputnum++) - { - if (m_input[inputnum].valid()) - m_input_view[inputnum] = m_input[inputnum].update(update_start, end); - else - m_input_view[inputnum] = empty_view(update_start, end); - sound_assert(m_input_view[inputnum].samples() > 0); - sound_assert(m_resampling_disabled || m_input_view[inputnum].sample_rate() == m_sample_rate); - } + // Have all streams create their buffers and other initializations + for(auto &stream : m_stream_list) + stream->init(); + + // Detect loops and order streams for full update at the same time + // Check the number of sources for each stream + std::map<sound_stream *, int> depcounts; + for(auto &stream : m_stream_list) + depcounts[stream.get()] = stream->sources().size(); + + // Start from all the ones that don't depend on anything + std::vector<sound_stream *> ready_streams; + for(auto &dpc : depcounts) + if(dpc.second == 0) + ready_streams.push_back(dpc.first); + + // Handle all the ready streams in a lifo matter (better for cache when generating sound) + while(!ready_streams.empty()) { + sound_stream *stream = ready_streams.back(); + // add the stream to the update order + m_ordered_streams.push_back(stream); + ready_streams.resize(ready_streams.size() - 1); + // reduce the depcount for all the streams that depend on the updated stream + for(sound_stream *target : stream->targets()) + if(!--depcounts[target]) + // when the depcount is zero, a stream is ready to be updated + ready_streams.push_back(target); + } -#if (SOUND_DEBUG) - // clear each output view to NANs before we call the callback - for (unsigned int outindex = 0; outindex < m_output.size(); outindex++) - m_output_view[outindex].fill(NAN); -#endif + // If not all streams ended up in the sorted list, we have a loop + if(m_ordered_streams.size() != m_stream_list.size()) { + // Apply the same algorithm from the other side to the + // remaining streams to only keep the ones in the loop + + std::map<sound_stream *, int> inverted_depcounts; + for(auto &dpc : depcounts) + if(dpc.second) + inverted_depcounts[dpc.first] = dpc.first->targets().size(); + for(auto &dpc : inverted_depcounts) + if(dpc.second == 0) + ready_streams.push_back(dpc.first); + while(!ready_streams.empty()) { + sound_stream *stream = ready_streams.back(); + ready_streams.resize(ready_streams.size() - 1); + for(sound_stream *source : stream->sources()) + if(!--inverted_depcounts[source]) + ready_streams.push_back(source); + } + std::string stream_names; + for(auto &dpc : inverted_depcounts) + if(dpc.second) + stream_names += ' ' + dpc.first->name(); + fatalerror("Loop detected in stream routes:%s", stream_names); + } - // if we have an extended callback, that's all we need - m_callback_ex(*this, m_input_view, m_output_view); + if(VERBOSE & LOG_ORDER) { + LOG_OUTPUT_FUNC("Order:\n"); + for(sound_stream *s : m_ordered_streams) + LOG_OUTPUT_FUNC("- %s (%d)\n", s->name().c_str(), s->sample_rate()); + } -#if (SOUND_DEBUG) - // make sure everything was overwritten - for (unsigned int outindex = 0; outindex < m_output.size(); outindex++) - for (int sampindex = 0; sampindex < m_output_view[outindex].samples(); sampindex++) - m_output_view[outindex].get(sampindex); + // Registrations for state saving + for(auto &stream : m_stream_list) + stream->register_state(); + + // Compute all the per-stream orders for update() + for(auto &stream : m_stream_list) + stream->compute_dependants(); + + // Create the default effect chain + for(u32 effect = 0; effect != audio_effect::COUNT; effect++) + m_default_effects.emplace_back(audio_effect::create(effect, machine().sample_rate(), nullptr)); + + // Inventory speakers and microphones + m_outputs_count = 0; + for(speaker_device &dev : speaker_device_enumerator(machine().root_device())) { + dev.set_id(m_speakers.size()); + m_speakers.emplace_back(speaker_info(dev, machine().sample_rate(), m_outputs_count)); + for(u32 effect = 0; effect != audio_effect::COUNT; effect++) + m_speakers.back().m_effects[effect].m_effect.reset(audio_effect::create(effect, machine().sample_rate(), m_default_effects[effect].get())); + m_outputs_count += dev.inputs(); + } - for (unsigned int outindex = 0; outindex < m_output.size(); outindex++) - m_output[outindex].m_buffer.flush_wav(); -#endif - } + for(microphone_device &dev : microphone_device_enumerator(machine().root_device())) { + dev.set_id(m_microphones.size()); + m_microphones.emplace_back(microphone_info(dev)); } - // return the requested view - return read_stream_view(m_output_view[outputnum], start); -} + // Allocate the buffer to pass for recording + m_record_buffer.resize(m_outputs_count * machine().sample_rate(), 0); + m_record_samples = 0; + // Create resamplers and setup history + rebuild_all_resamplers(); -//------------------------------------------------- -// apply_sample_rate_changes - if there is a -// pending sample rate change, apply it now -//------------------------------------------------- + m_effects_done = false; -void sound_stream::apply_sample_rate_changes(u32 updatenum, u32 downstream_rate) -{ - // grab the new rate and invalidate - u32 new_rate = (m_pending_sample_rate != SAMPLE_RATE_INVALID) ? m_pending_sample_rate : m_sample_rate; - m_pending_sample_rate = SAMPLE_RATE_INVALID; + m_effects_thread = std::make_unique<std::thread>( + [this]{ run_effects(); }); +} - // clamp to the minimum - 1 (anything below minimum means "off" and - // will not call the sound callback at all) - if (new_rate < SAMPLE_RATE_MINIMUM) - new_rate = SAMPLE_RATE_MINIMUM - 1; - // if we're input adaptive, override with the rate of our input - if (input_adaptive() && m_input.size() > 0 && m_input[0].valid()) - new_rate = m_input[0].source().stream().sample_rate(); +//**// Effects, input and output management - // if we're output adaptive, override with the rate of our output - if (output_adaptive()) - { - if (m_last_sample_rate_update == updatenum) - sound_assert(new_rate == m_sample_rate); - else - m_last_sample_rate_update = updatenum; - new_rate = downstream_rate; +void sound_manager::input_get(int id, sound_stream &stream) +{ + u32 samples = stream.samples(); + u64 end_pos = stream.end_index(); + u32 skip = stream.output_count(); + + for(const auto &step : m_microphones[id].m_input_mixing_steps) { + auto get_source = [&istream = m_osd_input_streams[step.m_osd_index], this](u32 samples, u64 end_pos, u32 channel) -> const s16 * { + if(istream.m_buffer.write_sample() < end_pos) { + u32 needed = end_pos - istream.m_buffer.write_sample(); + istream.m_buffer.prepare_space(needed); + machine().osd().sound_stream_source_update(istream.m_id, istream.m_buffer.ptrw(0, 0), needed); + istream.m_buffer.commit(needed); + } + return istream.m_buffer.ptrs(channel, end_pos - samples - istream.m_buffer.sync_sample()); + }; + + switch(step.m_mode) { + case mixing_step::CLEAR: + case mixing_step::COPY: + fatalerror("Impossible step encountered in input\n"); + + case mixing_step::ADD: { + const s16 *src = get_source(samples, end_pos, step.m_osd_channel); + float gain = step.m_linear_volume / 32768.0; + for(u32 sample = 0; sample != samples; sample++) { + stream.add(step.m_device_channel, sample, *src * gain); + src += skip; + } + break; + } + } } +} - // if something is different, process the change - if (new_rate != SAMPLE_RATE_INVALID && new_rate != m_sample_rate) - { - // update to the new rate and notify everyone -#if (SOUND_DEBUG) - printf("stream %s changing rates %d -> %d\n", name().c_str(), m_sample_rate, new_rate); -#endif - m_sample_rate = new_rate; - sample_rate_changed(); +void sound_manager::output_push(int id, sound_stream &stream) +{ + auto &spk = m_speakers[id]; + auto &out = spk.m_buffer; + auto &inp = stream.m_input_buffer; + int samples = stream.samples(); + int channels = stream.input_count(); + out.prepare_space(samples); + for(int channel = 0; channel != channels; channel ++) + std::copy(inp[channel].begin(), inp[channel].begin() + samples, out.ptrw(channel, 0)); + out.commit(samples); + + m_record_samples = samples; + s16 *outb = m_record_buffer.data() + spk.m_first_output; + for(int channel = 0; channel != channels; channel ++) { + s16 *outb1 = outb; + const float *inb = inp[channel].data(); + for(int sample = 0; sample != samples; sample++) { + *outb1 = std::clamp(int(*inb++ * 32768), -32768, 32767); + outb1 += m_outputs_count; + } + outb++; } - - // now call through our inputs and apply the rate change there - for (auto &input : m_input) - if (input.valid()) - input.apply_sample_rate_changes(updatenum, m_sample_rate); } +void sound_manager::run_effects() +{ + std::unique_lock<std::mutex> lock(m_effects_mutex); + for(;;) { + m_effects_condition.wait(lock); + if(m_effects_done) + return; + + // Apply the effects + for(auto &si : m_speakers) + for(u32 i=0; i != si.m_effects.size(); i++) { + auto &source = i ? si.m_effects[i-1].m_buffer : si.m_buffer; + si.m_effects[i].m_effect->apply(source, si.m_effects[i].m_buffer); + source.sync(); + } -//------------------------------------------------- -// print_graph_recursive - helper for debugging; -// prints info on this stream and then recursively -// prints info on all inputs -//------------------------------------------------- + // Apply the mixing steps + for(const auto &step : m_output_mixing_steps) { + const sample_t *src = step.m_mode == mixing_step::CLEAR ? nullptr : m_speakers[step.m_device_index].m_effects.back().m_buffer.ptrs(step.m_device_channel, 0); -#if (SOUND_DEBUG) -void sound_stream::print_graph_recursive(int indent, int index) -{ - osd_printf_info("%*s%s Ch.%d @ %d\n", indent, "", name(), index + m_output_base, sample_rate()); - for (int index = 0; index < m_input.size(); index++) - if (m_input[index].valid()) - { - if (m_input[index].m_resampler_source != nullptr) - m_input[index].m_resampler_source->stream().print_graph_recursive(indent + 2, m_input[index].m_resampler_source->index()); - else - m_input[index].m_native_source->stream().print_graph_recursive(indent + 2, m_input[index].m_native_source->index()); - } -} -#endif + auto &ostream = m_osd_output_streams[step.m_osd_index]; + u32 samples = ostream.m_samples; + s16 *dest = ostream.m_buffer.data() + step.m_osd_channel; + u32 skip = ostream.m_channels; + switch(step.m_mode) { + case mixing_step::CLEAR: + for(u32 sample = 0; sample != samples; sample++) { + *dest = 0; + dest += skip; + } + break; -//------------------------------------------------- -// sample_rate_changed - recompute sample -// rate data, and all streams that are affected -// by this stream -//------------------------------------------------- + case mixing_step::COPY: { + float gain = 32768 * step.m_linear_volume * m_master_gain; + for(u32 sample = 0; sample != samples; sample++) { + *dest = std::clamp(int(*src++ * gain), -32768, 32767); + dest += skip; + } + break; + } -void sound_stream::sample_rate_changed() -{ - // if invalid, just punt - if (m_sample_rate == SAMPLE_RATE_INVALID) - return; + case mixing_step::ADD: { + float gain = 32768 * step.m_linear_volume * m_master_gain; + for(u32 sample = 0; sample != samples; sample++) { + *dest = std::clamp(int(*src++ * gain) + *dest, -32768, 32767); + dest += skip; + } + break; + } + } + } - // update all output buffers - for (auto &output : m_output) - output.sample_rate_changed(m_sample_rate); + for(auto &si : m_speakers) + si.m_effects.back().m_buffer.sync(); - // if synchronous, prime the timer - if (synchronous()) - reprime_sync_timer(); + // Send the result to the osd + for(auto &stream : m_osd_output_streams) + if(stream.m_samples) + machine().osd().sound_stream_sink_update(stream.m_id, stream.m_buffer.data(), stream.m_samples); + } } +std::string sound_manager::effect_chain_tag(s32 index) const +{ + return m_speakers[index].m_dev.tag(); +} -//------------------------------------------------- -// postload - save/restore callback -//------------------------------------------------- +std::vector<audio_effect *> sound_manager::effect_chain(s32 index) const +{ + std::vector<audio_effect *> res; + for(const auto &e : m_speakers[index].m_effects) + res.push_back(e.m_effect.get()); + return res; +} -void sound_stream::postload() +std::vector<audio_effect *> sound_manager::default_effect_chain() const { - // set the end time of all of our streams to the value saved in m_last_update_end_time - for (auto &output : m_output) - output.set_end_time(m_last_update_end_time); + std::vector<audio_effect *> res; + for(const auto &e : m_default_effects) + res.push_back(e.get()); + return res; +} - // recompute the sample rate information - sample_rate_changed(); +void sound_manager::default_effect_changed(u32 entry) +{ + u32 type = m_default_effects[entry]->type(); + for(const auto &s : m_speakers) + for(const auto &e : s.m_effects) + if(e.m_effect->type() == type) + e.m_effect->default_changed(); } + + + //------------------------------------------------- -// presave - save/restore callback +// start_recording - begin audio recording //------------------------------------------------- -void sound_stream::presave() +bool sound_manager::start_recording(std::string_view filename) { - // save the stream end time - m_last_update_end_time = m_output[0].end_time(); + if(m_wavfile) + return false; + m_wavfile = util::wav_open(filename, machine().sample_rate(), m_outputs_count); + return bool(m_wavfile); +} + +bool sound_manager::start_recording() +{ + // open the output WAV file if specified + char const *const filename = machine().options().wav_write(); + return *filename ? start_recording(filename) : false; } //------------------------------------------------- -// reprime_sync_timer - set up the next sync -// timer to go off just a hair after the end of -// the current sample period +// stop_recording - end audio recording //------------------------------------------------- -void sound_stream::reprime_sync_timer() +void sound_manager::stop_recording() { - attotime curtime = m_device.machine().time(); - attotime target = m_output[0].end_time() + attotime(0, 1); - m_sync_timer->adjust(target - curtime); + // close any open WAV file + m_wavfile.reset(); } //------------------------------------------------- -// sync_update - timer callback to handle a -// synchronous stream +// mute - mute sound output //------------------------------------------------- -void sound_stream::sync_update(s32) +void sound_manager::mute(bool mute, u8 reason) { - update(); - reprime_sync_timer(); + if(mute) + m_muted |= reason; + else + m_muted &= ~reason; } //------------------------------------------------- -// empty_view - return an empty view covering the -// given time period as a substitute for invalid -// inputs +// reset - reset all sound chips //------------------------------------------------- -read_stream_view sound_stream::empty_view(attotime start, attotime end) +sound_manager::speaker_info::speaker_info(speaker_device &dev, u32 rate, u32 first_output) : m_dev(dev), m_first_output(first_output), m_buffer(rate, dev.inputs()) { - // if our dummy buffer doesn't match our sample rate, update and clear it - if (m_empty_buffer.sample_rate() != m_sample_rate) - m_empty_buffer.set_sample_rate(m_sample_rate, false); - - // allocate a write view so that it can expand, and convert back to a read view - // on the return - return write_stream_view(m_empty_buffer, start, end); + m_channels = dev.inputs(); + m_stream = dev.stream(); + for(u32 i=0; i != audio_effect::COUNT; i++) + m_effects.emplace_back(effect_step(rate, dev.inputs())); } +sound_manager::microphone_info::microphone_info(microphone_device &dev) : m_dev(dev) +{ + m_channels = dev.outputs(); +} +void sound_manager::reset() +{ + LOG_OUTPUT_FUNC("Sound reset\n"); +} -//************************************************************************** -// RESAMPLER STREAM -//************************************************************************** //------------------------------------------------- -// default_resampler_stream - derived sound_stream -// class that handles resampling +// pause - pause sound output //------------------------------------------------- -default_resampler_stream::default_resampler_stream(device_t &device) : - sound_stream(device, 1, 1, 0, SAMPLE_RATE_OUTPUT_ADAPTIVE, stream_update_delegate(&default_resampler_stream::resampler_sound_update, this), STREAM_DISABLE_INPUT_RESAMPLING), - m_max_latency(0) +void sound_manager::pause() { - // create a name - m_name = "Default Resampler '"; - m_name += device.tag(); - m_name += "'"; + mute(true, MUTE_REASON_PAUSE); } //------------------------------------------------- -// resampler_sound_update - stream callback -// handler for resampling an input stream to the -// target sample rate of the output +// resume - resume sound output //------------------------------------------------- -void default_resampler_stream::resampler_sound_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) +void sound_manager::resume() { - sound_assert(inputs.size() == 1); - sound_assert(outputs.size() == 1); + mute(false, MUTE_REASON_PAUSE); +} - auto &input = inputs[0]; - auto &output = outputs[0]; - // if the input has an invalid rate, just fill with zeros - if (input.sample_rate() <= 1) - { - output.fill(0); +//**// Configuration management + +void sound_manager::config_load(config_type cfg_type, config_level cfg_level, util::xml::data_node const *parentnode) +{ + // If no config file, ignore + if(!parentnode) return; - } - // optimize_resampler ensures we should not have equal sample rates - sound_assert(input.sample_rate() != output.sample_rate()); + switch(cfg_type) { + case config_type::INIT: + break; - // compute the stepping value and the inverse - stream_buffer::sample_t step = stream_buffer::sample_t(input.sample_rate()) / stream_buffer::sample_t(output.sample_rate()); - stream_buffer::sample_t stepinv = 1.0 / step; + case config_type::CONTROLLER: + break; - // determine the latency we need to introduce, in input samples: - // 1 input sample for undersampled inputs - // 1 + step input samples for oversampled inputs - s64 latency_samples = 1 + ((step < 1.0) ? 0 : s32(step)); - if (latency_samples <= m_max_latency) - latency_samples = m_max_latency; - else - m_max_latency = latency_samples; - attotime latency = latency_samples * input.sample_period(); - - // clamp the latency to the start (only relevant at the beginning) - s32 dstindex = 0; - attotime output_start = output.start_time(); - auto numsamples = output.samples(); - while (latency > output_start && dstindex < numsamples) - { - output.put(dstindex++, 0); - output_start += output.sample_period(); - } - if (dstindex >= numsamples) - return; + case config_type::DEFAULT: { + // In the global config, get the default effect chain configuration - // create a rebased input buffer around the adjusted start time - read_stream_view rebased(input, output_start - latency); - sound_assert(rebased.start_time() + latency <= output_start); + util::xml::data_node const *efl_node = parentnode->get_child("default_audio_effects"); + if(efl_node) { + for(util::xml::data_node const *ef_node = efl_node->get_child("effect"); ef_node != nullptr; ef_node = ef_node->get_next_sibling("effect")) { + unsigned int id = ef_node->get_attribute_int("step", 0); + std::string type = ef_node->get_attribute_string("type", ""); + if(id >= 1 && id <= m_default_effects.size() && audio_effect::effect_names[m_default_effects[id-1]->type()] == type) + m_default_effects[id-1]->config_load(ef_node); + } + } - // compute the fractional input start position - attotime delta = output_start - (rebased.start_time() + latency); - sound_assert(delta.seconds() == 0); - stream_buffer::sample_t srcpos = stream_buffer::sample_t(double(delta.attoseconds()) / double(rebased.sample_period_attoseconds())); - sound_assert(srcpos <= 1.0f); + // and the resampler configuration + util::xml::data_node const *rs_node = parentnode->get_child("resampler"); + if(rs_node) { + m_resampler_hq_latency = rs_node->get_attribute_float("hq_latency", 0.0050); + m_resampler_hq_length = rs_node->get_attribute_int("hq_length", 400); + m_resampler_hq_phases = rs_node->get_attribute_int("hq_phases", 200); - // input is undersampled: point sample except where our sample period covers a boundary - s32 srcindex = 0; - if (step < 1.0) - { - stream_buffer::sample_t cursample = rebased.get(srcindex++); - for ( ; dstindex < numsamples; dstindex++) - { - // if still within the current sample, just replicate - srcpos += step; - if (srcpos <= 1.0) - output.put(dstindex, cursample); - - // if crossing a sample boundary, blend with the neighbor - else - { - srcpos -= 1.0; - sound_assert(srcpos <= step + 1e-5); - stream_buffer::sample_t prevsample = cursample; - cursample = rebased.get(srcindex++); - output.put(dstindex, stepinv * (prevsample * (step - srcpos) + srcpos * cursample)); - } + // this also applies the hq settings if resampler is hq + set_resampler_type(rs_node->get_attribute_int("type", RESAMPLER_LOFI)); } - sound_assert(srcindex <= rebased.samples()); + break; } - // input is oversampled: sum the energy - else - { - float cursample = rebased.get(srcindex++); - for ( ; dstindex < numsamples; dstindex++) - { - // compute the partial first sample and advance - stream_buffer::sample_t scale = 1.0 - srcpos; - stream_buffer::sample_t sample = cursample * scale; - - // add in complete samples until we only have a fraction left - stream_buffer::sample_t remaining = step - scale; - while (remaining >= 1.0) - { - sample += rebased.get(srcindex++); - remaining -= 1.0; - } + case config_type::SYSTEM: { + // In the per-driver file, get the specific configuration for everything + + // Effects configuration + for(util::xml::data_node const *efl_node = parentnode->get_child("audio_effects"); efl_node != nullptr; efl_node = efl_node->get_next_sibling("audio_effects")) { + std::string speaker_tag = efl_node->get_attribute_string("tag", ""); + for(auto &speaker : m_speakers) + if(speaker.m_dev.tag() == speaker_tag) { + auto &eff = speaker.m_effects; + for(util::xml::data_node const *ef_node = efl_node->get_child("effect"); ef_node != nullptr; ef_node = ef_node->get_next_sibling("effect")) { + unsigned int id = ef_node->get_attribute_int("step", 0); + std::string type = ef_node->get_attribute_string("type", ""); + if(id >= 1 && id <= m_default_effects.size() && audio_effect::effect_names[eff[id-1].m_effect->type()] == type) + eff[id-1].m_effect->config_load(ef_node); + } + break; + } + } - // add in the final partial sample - cursample = rebased.get(srcindex++); - sample += cursample * remaining; - output.put(dstindex, sample * stepinv); + // All levels + const util::xml::data_node *lv_node = parentnode->get_child("master_volume"); + if(lv_node) + m_master_gain = lv_node->get_attribute_float("gain", 1.0); - // our position is now the remainder - srcpos = remaining; - sound_assert(srcindex <= rebased.samples()); + for(lv_node = parentnode->get_child("device_volume"); lv_node != nullptr; lv_node = lv_node->get_next_sibling("device_volume")) { + std::string device_tag = lv_node->get_attribute_string("device", ""); + device_sound_interface *intf = dynamic_cast<device_sound_interface *>(m_machine.root_device().subdevice(device_tag)); + if(intf) + intf->set_user_output_gain(lv_node->get_attribute_float("gain", 1.0)); } - } -} + for(lv_node = parentnode->get_child("device_channel_volume"); lv_node != nullptr; lv_node = lv_node->get_next_sibling("device_channel_volume")) { + std::string device_tag = lv_node->get_attribute_string("device", ""); + int channel = lv_node->get_attribute_int("channel", -1); + device_sound_interface *intf = dynamic_cast<device_sound_interface *>(m_machine.root_device().subdevice(device_tag)); + if(intf && channel >= 0 && channel < intf->outputs()) + intf->set_user_output_gain(channel, lv_node->get_attribute_float("gain", 1.0)); + } -//************************************************************************** -// SOUND MANAGER -//************************************************************************** + // Mapping configuration + m_configs.clear(); + for(util::xml::data_node const *node = parentnode->get_child("sound_map"); node != nullptr; node = node->get_next_sibling("sound_map")) { + m_configs.emplace_back(config_mapping { node->get_attribute_string("tag", "") }); + auto &config = m_configs.back(); + for(util::xml::data_node const *nmap = node->get_child("node_mapping"); nmap != nullptr; nmap = nmap->get_next_sibling("node_mapping")) + config.m_node_mappings.emplace_back(std::pair<std::string, float>(nmap->get_attribute_string("node", ""), nmap->get_attribute_float("db", 0))); + for(util::xml::data_node const *cmap = node->get_child("channel_mapping"); cmap != nullptr; cmap = cmap->get_next_sibling("channel_mapping")) + config.m_channel_mappings.emplace_back(std::tuple<u32, std::string, u32, float>(cmap->get_attribute_int("guest_channel", 0), + cmap->get_attribute_string("node", ""), + cmap->get_attribute_int("node_channel", 0), + cmap->get_attribute_float("db", 0))); + } + break; + } + + case config_type::FINAL: + break; + } +} + //------------------------------------------------- -// sound_manager - constructor +// config_save - save data to the configuration +// file //------------------------------------------------- -sound_manager::sound_manager(running_machine &machine) : - m_machine(machine), - m_update_timer(nullptr), - m_update_number(0), - m_last_update(attotime::zero), - m_finalmix_leftover(0), - m_samples_this_update(0), - m_finalmix(machine.sample_rate()), - m_leftmix(machine.sample_rate()), - m_rightmix(machine.sample_rate()), - m_compressor_scale(1.0), - m_compressor_counter(0), - m_compressor_enabled(machine.options().compressor()), - m_muted(0), - m_nosound_mode(machine.osd().no_sound()), - m_attenuation(0), - m_unique_id(0), - m_wavfile(), - m_first_reset(true) +void sound_manager::config_save(config_type cfg_type, util::xml::data_node *parentnode) { - // count the mixers -#if VERBOSE - mixer_interface_enumerator iter(machine.root_device()); - LOG("total mixers = %d\n", iter.count()); -#endif + switch(cfg_type) { + case config_type::INIT: + break; + + case config_type::CONTROLLER: + break; + + case config_type::DEFAULT: { + // In the global config, save the default effect chain configuration + util::xml::data_node *const efl_node = parentnode->add_child("default_audio_effects", nullptr); + for(u32 ei = 0; ei != m_default_effects.size(); ei++) { + const audio_effect *e = m_default_effects[ei].get(); + util::xml::data_node *const ef_node = efl_node->add_child("effect", nullptr); + ef_node->set_attribute_int("step", ei+1); + ef_node->set_attribute("type", audio_effect::effect_names[e->type()]); + e->config_save(ef_node); + } - // register callbacks - machine.configuration().config_register( - "mixer", - configuration_manager::load_delegate(&sound_manager::config_load, this), - configuration_manager::save_delegate(&sound_manager::config_save, this)); - machine.add_notifier(MACHINE_NOTIFY_PAUSE, machine_notify_delegate(&sound_manager::pause, this)); - machine.add_notifier(MACHINE_NOTIFY_RESUME, machine_notify_delegate(&sound_manager::resume, this)); - machine.add_notifier(MACHINE_NOTIFY_RESET, machine_notify_delegate(&sound_manager::reset, this)); - machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(&sound_manager::stop_recording, this)); + util::xml::data_node *const rs_node = parentnode->add_child("resampler", nullptr); + rs_node->set_attribute_int("type", m_resampler_type); + rs_node->set_attribute_float("hq_latency", m_resampler_hq_latency); + rs_node->set_attribute_int("hq_length", m_resampler_hq_length); + rs_node->set_attribute_int("hq_phases", m_resampler_hq_phases); + break; + } - // register global states - machine.save().save_item(NAME(m_last_update)); + case config_type::SYSTEM: { + // In the per-driver file, save the specific configuration for everything + + // Effects configuration + for(const auto &speaker : m_speakers) { + util::xml::data_node *const efl_node = parentnode->add_child("audio_effects", nullptr); + efl_node->set_attribute("tag", speaker.m_dev.tag()); + for(u32 ei = 0; ei != speaker.m_effects.size(); ei++) { + const audio_effect *e = speaker.m_effects[ei].m_effect.get(); + util::xml::data_node *const ef_node = efl_node->add_child("effect", nullptr); + ef_node->set_attribute_int("step", ei+1); + ef_node->set_attribute("type", audio_effect::effect_names[e->type()]); + e->config_save(ef_node); + } + } - // set the starting attenuation - set_attenuation(machine.options().volume()); + // All levels + if(m_master_gain != 1.0) { + util::xml::data_node *const lv_node = parentnode->add_child("master_volume", nullptr); + lv_node->set_attribute_float("gain", m_master_gain); + } + for(device_sound_interface &snd : sound_interface_enumerator(m_machine.root_device())) { + // Don't add microphones, speakers or devices without outputs + if(dynamic_cast<sound_io_device *>(&snd) || !snd.outputs()) + continue; + if(snd.user_output_gain() != 1.0) { + util::xml::data_node *const lv_node = parentnode->add_child("device_volume", nullptr); + lv_node->set_attribute("device", snd.device().tag()); + lv_node->set_attribute_float("gain", snd.user_output_gain()); + } + for(int channel = 0; channel != snd.outputs(); channel ++) + if(snd.user_output_gain(channel) != 1.0) { + util::xml::data_node *const lv_node = parentnode->add_child("device_channel_volume", nullptr); + lv_node->set_attribute("device", snd.device().tag()); + lv_node->set_attribute_int("channel", channel); + lv_node->set_attribute_float("gain", snd.user_output_gain(channel)); + } + } - // start the periodic update flushing timer - m_update_timer = machine.scheduler().timer_alloc(timer_expired_delegate(FUNC(sound_manager::update), this)); - m_update_timer->adjust(STREAMS_UPDATE_ATTOTIME, 0, STREAMS_UPDATE_ATTOTIME); + // Mapping configuration + auto output_one = [this, parentnode](sound_io_device &dev) { + for(const auto &config : m_configs) + if(config.m_name == dev.tag()) { + util::xml::data_node *const sp_node = parentnode->add_child("sound_map", nullptr); + sp_node->set_attribute("tag", dev.tag()); + for(const auto &nmap : config.m_node_mappings) { + util::xml::data_node *const node = sp_node->add_child("node_mapping", nullptr); + node->set_attribute("node", nmap.first.c_str()); + node->set_attribute_float("db", nmap.second); + } + for(const auto &cmap : config.m_channel_mappings) { + util::xml::data_node *const node = sp_node->add_child("channel_mapping", nullptr); + node->set_attribute_int("guest_channel", std::get<0>(cmap)); + node->set_attribute("node", std::get<1>(cmap).c_str()); + node->set_attribute_int("node_channel", std::get<2>(cmap)); + node->set_attribute_float("db", std::get<3>(cmap)); + } + return; + } + }; + + for(auto &spk : m_speakers) + output_one(spk.m_dev); + for(auto &mic : m_microphones) + output_one(mic.m_dev); + break; + } + + case config_type::FINAL: + break; + } } -//------------------------------------------------- -// sound_manager - destructor -//------------------------------------------------- -sound_manager::~sound_manager() +//**// Mapping between speakers/microphones and OSD endpoints + +sound_manager::config_mapping &sound_manager::config_get_sound_io(sound_io_device *dev) { + for(auto &config : m_configs) + if(config.m_name == dev->tag()) + return config; + m_configs.emplace_back(config_mapping { dev->tag() }); + return m_configs.back(); } - -//------------------------------------------------- -// stream_alloc - allocate a new stream with the -// new-style callback and flags -//------------------------------------------------- - -sound_stream *sound_manager::stream_alloc(device_t &device, u32 inputs, u32 outputs, u32 sample_rate, stream_update_delegate callback, sound_stream_flags flags) +void sound_manager::config_add_sound_io_connection_node(sound_io_device *dev, std::string name, float db) { - // determine output base - u32 output_base = 0; - for (auto &stream : m_stream_list) - if (&stream->device() == &device) - output_base += stream->output_count(); + internal_config_add_sound_io_connection_node(dev, name, db); + m_osd_info.m_generation --; +} - m_stream_list.push_back(std::make_unique<sound_stream>(device, inputs, outputs, output_base, sample_rate, callback, flags)); - return m_stream_list.back().get(); +void sound_manager::internal_config_add_sound_io_connection_node(sound_io_device *dev, std::string name, float db) +{ + auto &config = config_get_sound_io(dev); + for(auto &nmap : config.m_node_mappings) + if(nmap.first == name) + return; + config.m_node_mappings.emplace_back(std::pair<std::string, float>(name, db)); } +void sound_manager::config_add_sound_io_connection_default(sound_io_device *dev, float db) +{ + internal_config_add_sound_io_connection_default(dev, db); + m_osd_info.m_generation --; +} -//------------------------------------------------- -// start_recording - begin audio recording -//------------------------------------------------- +void sound_manager::internal_config_add_sound_io_connection_default(sound_io_device *dev, float db) +{ + auto &config = config_get_sound_io(dev); + for(auto &nmap : config.m_node_mappings) + if(nmap.first == "") + return; + config.m_node_mappings.emplace_back(std::pair<std::string, float>("", db)); +} -bool sound_manager::start_recording(std::string_view filename) +void sound_manager::config_remove_sound_io_connection_node(sound_io_device *dev, std::string name) { - if (m_wavfile) - return false; - m_wavfile = util::wav_open(filename, machine().sample_rate(), 2); - return bool(m_wavfile); + internal_config_remove_sound_io_connection_node(dev, name); + m_osd_info.m_generation --; } -bool sound_manager::start_recording() +void sound_manager::internal_config_remove_sound_io_connection_node(sound_io_device *dev, std::string name) { - // open the output WAV file if specified - char const *const filename = machine().options().wav_write(); - return *filename ? start_recording(filename) : false; + auto &config = config_get_sound_io(dev); + for(auto i = config.m_node_mappings.begin(); i != config.m_node_mappings.end(); i++) + if(i->first == name) { + config.m_node_mappings.erase(i); + return; + } } +void sound_manager::config_remove_sound_io_connection_default(sound_io_device *dev) +{ + internal_config_remove_sound_io_connection_default(dev); + m_osd_info.m_generation --; +} -//------------------------------------------------- -// stop_recording - end audio recording -//------------------------------------------------- +void sound_manager::internal_config_remove_sound_io_connection_default(sound_io_device *dev) +{ + auto &config = config_get_sound_io(dev); + for(auto i = config.m_node_mappings.begin(); i != config.m_node_mappings.end(); i++) + if(i->first == "") { + config.m_node_mappings.erase(i); + return; + } +} -void sound_manager::stop_recording() +void sound_manager::config_set_volume_sound_io_connection_node(sound_io_device *dev, std::string name, float db) { - // close any open WAV file - m_wavfile.reset(); + internal_config_set_volume_sound_io_connection_node(dev, name, db); + m_osd_info.m_generation --; } +void sound_manager::internal_config_set_volume_sound_io_connection_node(sound_io_device *dev, std::string name, float db) +{ + auto &config = config_get_sound_io(dev); + for(auto &nmap : config.m_node_mappings) + if(nmap.first == name) { + nmap.second = db; + return; + } +} -//------------------------------------------------- -// set_attenuation - set the global volume -//------------------------------------------------- +void sound_manager::config_set_volume_sound_io_connection_default(sound_io_device *dev, float db) +{ + internal_config_set_volume_sound_io_connection_default(dev, db); + m_osd_info.m_generation --; +} -void sound_manager::set_attenuation(float attenuation) +void sound_manager::internal_config_set_volume_sound_io_connection_default(sound_io_device *dev, float db) { - // currently OSD only supports integral attenuation - m_attenuation = int(attenuation); - machine().osd().set_mastervolume(m_muted ? -32 : m_attenuation); + auto &config = config_get_sound_io(dev); + for(auto &nmap : config.m_node_mappings) + if(nmap.first == "") { + nmap.second = db; + return; + } } -//------------------------------------------------- -// indexed_mixer_input - return the mixer -// device and input index of the global mixer -// input -//------------------------------------------------- +void sound_manager::config_add_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel, float db) +{ + internal_config_add_sound_io_channel_connection_node(dev, guest_channel, name, node_channel, db); + m_osd_info.m_generation --; +} -bool sound_manager::indexed_mixer_input(int index, mixer_input &info) const +void sound_manager::internal_config_add_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel, float db) { - // scan through the mixers until we find the indexed input - for (device_mixer_interface &mixer : mixer_interface_enumerator(machine().root_device())) - { - if (index < mixer.inputs()) - { - info.mixer = &mixer; - info.stream = mixer.input_to_stream_input(index, info.inputnum); - sound_assert(info.stream != nullptr); - return true; - } - index -= mixer.inputs(); - } + auto &config = config_get_sound_io(dev); + for(auto &cmap : config.m_channel_mappings) + if(std::get<0>(cmap) == guest_channel && std::get<1>(cmap) == name && std::get<2>(cmap) == node_channel) + return; + config.m_channel_mappings.emplace_back(std::tuple<u32, std::string, u32, float>(guest_channel, name, node_channel, db)); +} - // didn't locate - info.mixer = nullptr; - return false; +void sound_manager::config_add_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel, float db) +{ + internal_config_add_sound_io_channel_connection_default(dev, guest_channel, node_channel, db); + m_osd_info.m_generation --; } +void sound_manager::internal_config_add_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel, float db) +{ + auto &config = config_get_sound_io(dev); + for(auto &cmap : config.m_channel_mappings) + if(std::get<0>(cmap) == guest_channel && std::get<1>(cmap) == "" && std::get<2>(cmap) == node_channel) + return; + config.m_channel_mappings.emplace_back(std::tuple<u32, std::string, u32, float>(guest_channel, "", node_channel, db)); +} -//------------------------------------------------- -// samples - fills the specified buffer with -// 16-bit stereo audio samples generated during -// the current frame -//------------------------------------------------- +void sound_manager::config_remove_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel) +{ + internal_config_remove_sound_io_channel_connection_node(dev, guest_channel, name, node_channel); + m_osd_info.m_generation --; +} -void sound_manager::samples(s16 *buffer) +void sound_manager::internal_config_remove_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel) { - for (int sample = 0; sample < m_samples_this_update * 2; sample++) - *buffer++ = m_finalmix[sample]; + auto &config = config_get_sound_io(dev); + for(auto i = config.m_channel_mappings.begin(); i != config.m_channel_mappings.end(); i++) + if(std::get<0>(*i) == guest_channel && std::get<1>(*i) == name && std::get<2>(*i) == node_channel) { + config.m_channel_mappings.erase(i); + return; + } } +void sound_manager::config_remove_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel) +{ + internal_config_remove_sound_io_channel_connection_default(dev, guest_channel, node_channel); + m_osd_info.m_generation --; +} -//------------------------------------------------- -// mute - mute sound output -//------------------------------------------------- +void sound_manager::internal_config_remove_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel) +{ + auto &config = config_get_sound_io(dev); + for(auto i = config.m_channel_mappings.begin(); i != config.m_channel_mappings.end(); i++) + if(std::get<0>(*i) == guest_channel && std::get<1>(*i) == "" && std::get<2>(*i) == node_channel) { + config.m_channel_mappings.erase(i); + return; + } +} -void sound_manager::mute(bool mute, u8 reason) +void sound_manager::config_set_volume_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel, float db) { - bool old_muted = m_muted; - if (mute) - m_muted |= reason; - else - m_muted &= ~reason; + internal_config_set_volume_sound_io_channel_connection_node(dev, guest_channel, name, node_channel, db); + m_osd_info.m_generation --; +} - if(old_muted != (m_muted != 0)) - set_attenuation(m_attenuation); +void sound_manager::internal_config_set_volume_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel, float db) +{ + auto &config = config_get_sound_io(dev); + for(auto &cmap : config.m_channel_mappings) + if(std::get<0>(cmap) == guest_channel && std::get<1>(cmap) == name && std::get<2>(cmap) == node_channel) { + std::get<3>(cmap) = db; + return; + } } +void sound_manager::config_set_volume_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel, float db) +{ + internal_config_set_volume_sound_io_channel_connection_default(dev, guest_channel, node_channel, db); + m_osd_info.m_generation --; +} -//------------------------------------------------- -// recursive_remove_stream_from_orphan_list - -// remove the given stream from the orphan list -// and recursively remove all our inputs -//------------------------------------------------- +void sound_manager::internal_config_set_volume_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel, float db) +{ + auto &config = config_get_sound_io(dev); + for(auto &cmap : config.m_channel_mappings) + if(std::get<0>(cmap) == guest_channel && std::get<1>(cmap) == "" && std::get<2>(cmap) == node_channel) { + std::get<3>(cmap) = db; + return; + } +} -void sound_manager::recursive_remove_stream_from_orphan_list(sound_stream *which) +void sound_manager::startup_cleanups() { - m_orphan_stream_list.erase(which); - for (int inputnum = 0; inputnum < which->input_count(); inputnum++) - { - auto &input = which->input(inputnum); - if (input.valid()) - recursive_remove_stream_from_orphan_list(&input.source().stream()); + auto osd_info = machine().osd().sound_get_information(); + + // for every sound_io device that does not have a configuration entry, add a + // mapping to default + auto default_one = [this](sound_io_device &dev) { + for(const auto &config : m_configs) + if(config.m_name == dev.tag()) + return; + m_configs.emplace_back(config_mapping { dev.tag() }); + m_configs.back().m_node_mappings.emplace_back(std::pair<std::string, float>("", 0.0)); + }; + + for(sound_io_device &dev : speaker_device_enumerator(machine().root_device())) + default_one(dev); + for(sound_io_device &dev : microphone_device_enumerator(machine().root_device())) + default_one(dev); + + // If there's no default sink replace all the default sink config + // entries into the first sink available + if(!osd_info.m_default_sink) { + std::string first_sink_name; + for(const auto &node : osd_info.m_nodes) + if(node.m_sinks) { + first_sink_name = node.name(); + break; + } + + if(first_sink_name != "") + for(auto &config : m_configs) { + for(auto &nmap : config.m_node_mappings) + if(nmap.first == "") + nmap.first = first_sink_name; + for(auto &cmap : config.m_channel_mappings) + if(std::get<1>(cmap) == "") + std::get<1>(cmap) = first_sink_name; + } } -} -//------------------------------------------------- -// apply_sample_rate_changes - recursively -// update sample rates throughout the system -//------------------------------------------------- + // If there's no default source replace all the default source config + // entries into the first source available + if(!osd_info.m_default_source) { + std::string first_source_name; + for(const auto &node : osd_info.m_nodes) + if(node.m_sources) { + first_source_name = node.name(); + break; + } -void sound_manager::apply_sample_rate_changes() -{ - // update sample rates if they have changed - for (speaker_device &speaker : speaker_device_enumerator(machine().root_device())) - { - int stream_out; - sound_stream *stream = speaker.output_to_stream_output(0, stream_out); - - // due to device removal, some speakers may end up with no outputs; just skip those - if (stream != nullptr) - { - sound_assert(speaker.outputs() == 1); - stream->apply_sample_rate_changes(m_update_number, machine().sample_rate()); - } + if(first_source_name != "") + for(auto &config : m_configs) { + for(auto &nmap : config.m_node_mappings) + if(nmap.first == "") + nmap.first = first_source_name; + for(auto &cmap : config.m_channel_mappings) + if(std::get<1>(cmap) == "") + std::get<1>(cmap) = first_source_name; + } } } +template<bool is_output, typename S> void sound_manager::apply_osd_changes(std::vector<S> &streams) +{ + // Apply host system volume and routing changes to the internal structures + for(S &stream : streams) { + u32 sidx; + for(sidx = 0; sidx != m_osd_info.m_streams.size() && m_osd_info.m_streams[sidx].m_id != stream.m_id; sidx++); + // If the stream has been lost, continue. It will be cleared in update_osd_streams. + if(sidx == m_osd_info.m_streams.size()) + continue; + + // Check if the target and/or the volumes changed + bool node_changed = stream.m_node != m_osd_info.m_streams[sidx].m_node; + bool volume_changed = !std::equal(stream.m_volumes.begin(), stream.m_volumes.end(), m_osd_info.m_streams[sidx].m_volumes.begin(), m_osd_info.m_streams[sidx].m_volumes.end()); + + if(node_changed || volume_changed) { + // Check if a node change is just tracking the system default + bool system_default_tracking = node_changed && stream.m_is_system_default && m_osd_info.m_streams[sidx].m_node == (is_output ? m_osd_info.m_default_sink : m_osd_info.m_default_source); + + // Find the config entry for the sound_io + config_mapping *config = nullptr; + for(auto &conf : m_configs) + if(conf.m_name == stream.m_dev->tag()) { + config = &conf; + break; + } + if(!config) + continue; + + // Retrieve the old node name, and, if it's different, the new node name + std::string old_node_name = stream.m_node_name; + std::string new_node_name; + if(node_changed) { + for(const auto &node : m_osd_info.m_nodes) + if(node.m_id == m_osd_info.m_streams[sidx].m_node) { + new_node_name = node.name(); + break; + } + // That's really, really not supposed to happen + if(new_node_name.empty()) + continue; + } else + new_node_name = old_node_name; + + // Separate the cases on full mapping vs. channel mapping + if(!stream.m_is_channel_mapping) { + // Full mapping + // Find the index of the config mapping entry that generated the stream, if there's still one. + // Note that a default system stream has the empty string as a name + u32 index; + for(index = 0; index != config->m_node_mappings.size(); index++) + if(config->m_node_mappings[index].first == old_node_name) + break; + if(index == config->m_node_mappings.size()) + continue; + + // If the target node changed, write it down + if(node_changed) { + if(!system_default_tracking) { + config->m_node_mappings[index].first = new_node_name; + stream.m_node_name = new_node_name; + stream.m_is_system_default = false; + } + stream.m_node = m_osd_info.m_streams[sidx].m_node; + } -//------------------------------------------------- -// reset - reset all sound chips -//------------------------------------------------- + // If the volume changed, there are two + // possibilities: either the channels split, or + // they didn't. + if(volume_changed) { + // Check is all the channel volumes are the same + float new_volume = m_osd_info.m_streams[sidx].m_volumes[0]; + bool same = true; + for(u32 i = 1; i != m_osd_info.m_streams[sidx].m_volumes.size(); i++) + if(m_osd_info.m_streams[sidx].m_volumes[i] != new_volume) { + same = false; + break; + } + if(same) { + // All the same volume, just note down the new volume + stream.m_volumes = m_osd_info.m_streams[sidx].m_volumes; + config->m_node_mappings[index].second = new_volume; + + } else { + const osd::audio_info::node_info *node = nullptr; + for(const auto &n : m_osd_info.m_nodes) + if(n.m_id == stream.m_node) { + node = &n; + break; + } + for(u32 channel = 0; channel != stream.m_channels; channel++) { + std::vector<u32> targets = find_channel_mapping(stream.m_dev->get_position(channel), node); + for(u32 tchannel : targets) + if(stream.m_node_name == "") + internal_config_add_sound_io_channel_connection_default(stream.m_dev, channel, tchannel, m_osd_info.m_streams[sidx].m_volumes[tchannel]); + else + internal_config_add_sound_io_channel_connection_node(stream.m_dev, channel, stream.m_node_name, tchannel, m_osd_info.m_streams[sidx].m_volumes[tchannel]); + } + config->m_node_mappings.erase(config->m_node_mappings.begin() + index); + } + } + } else { + // Channel mapping + for(u32 channel = 0; channel != stream.m_channels; channel++) { + if(stream.m_unused_channels_mask & (1 << channel)) + continue; + + // Find the index of the config mapping entry that generated the stream channel, if there's still one. + // Note that a default system stream has the empty string as a name + u32 index; + for(index = 0; index != config->m_channel_mappings.size(); index++) + if(std::get<1>(config->m_channel_mappings[index]) == old_node_name && + std::get<2>(config->m_channel_mappings[index]) == channel) + break; + if(index == config->m_channel_mappings.size()) + continue; + + // If the target node changed, write it down + if(node_changed) { + if(!system_default_tracking) { + std::get<1>(config->m_channel_mappings[index]) = new_node_name; + stream.m_node_name = new_node_name; + stream.m_is_system_default = false; + } + stream.m_node = m_osd_info.m_streams[sidx].m_node; + } + + // If the volume changed, write in down too + if(volume_changed) { + std::get<3>(config->m_channel_mappings[index]) = m_osd_info.m_streams[sidx].m_volumes[channel]; + stream.m_volumes[channel] = m_osd_info.m_streams[sidx].m_volumes[channel]; + } + } + } + } + } +} -void sound_manager::reset() +void sound_manager::osd_information_update() { - // reset all the sound chips - for (device_sound_interface &sound : sound_interface_enumerator(machine().root_device())) - sound.device().reset(); - - // apply any sample rate changes now - apply_sample_rate_changes(); + // Get a snapshot of the current information + m_osd_info = machine().osd().sound_get_information(); + + // Analyze the streams to see if anything changed, but only in the + // split stream case. + if(machine().osd().sound_split_streams_per_source()) { + apply_osd_changes<false, osd_input_stream >(m_osd_input_streams ); + apply_osd_changes<false, osd_output_stream>(m_osd_output_streams); + } +} - // on first reset, identify any orphaned streams - if (m_first_reset) - { - m_first_reset = false; +void sound_manager::generate_mapping() +{ + auto find_node = [this](std::string name) -> u32 { + for(const auto &node : m_osd_info.m_nodes) + if(node.name() == name) + return node.m_id; + return 0; + }; + + m_mappings.clear(); + for(speaker_info &speaker : m_speakers) { + auto &config = config_get_sound_io(&speaker.m_dev); + m_mappings.emplace_back(mapping { &speaker.m_dev }); + auto &omap = m_mappings.back(); + + std::vector<std::string> node_to_remove; + for(auto &nmap : config.m_node_mappings) { + if(nmap.first == "") { + if(m_osd_info.m_default_sink) + omap.m_node_mappings.emplace_back(mapping::node_mapping { m_osd_info.m_default_sink, nmap.second, true }); + } else { + u32 node_id = find_node(nmap.first); + if(node_id != 0) + omap.m_node_mappings.emplace_back(mapping::node_mapping { node_id, nmap.second, false }); + else + node_to_remove.push_back(nmap.first); + } + } - // put all the streams on the orphan list to start - for (auto &stream : m_stream_list) - m_orphan_stream_list[stream.get()] = 0; + for(auto &nmap: node_to_remove) + internal_config_remove_sound_io_connection_node(&speaker.m_dev, nmap); + + std::vector<std::tuple<u32, std::string, u32>> channel_map_to_remove; + for(auto &cmap : config.m_channel_mappings) { + if(std::get<1>(cmap) == "") { + if(m_osd_info.m_default_sink) + omap.m_channel_mappings.emplace_back(mapping::channel_mapping { std::get<0>(cmap), m_osd_info.m_default_sink, std::get<2>(cmap), std::get<3>(cmap), true }); + } else { + u32 node_id = find_node(std::get<1>(cmap)); + if(node_id != 0) + omap.m_channel_mappings.emplace_back(mapping::channel_mapping { std::get<0>(cmap), node_id, std::get<2>(cmap), std::get<3>(cmap), false }); + else + channel_map_to_remove.push_back(std::tuple<u32, std::string, u32>(std::get<0>(cmap), std::get<1>(cmap), std::get<2>(cmap))); + } + } - // then walk the graph like we do on update and remove any we touch - for (speaker_device &speaker : speaker_device_enumerator(machine().root_device())) - { - int dummy; - sound_stream *const output = speaker.output_to_stream_output(0, dummy); - if (output) - recursive_remove_stream_from_orphan_list(output); + for(auto &cmap : channel_map_to_remove) + internal_config_remove_sound_io_channel_connection_node(&speaker.m_dev, std::get<0>(cmap), std::get<1>(cmap), std::get<2>(cmap)); + } - m_speakers.emplace_back(speaker); + for(microphone_info &mic : m_microphones) { + auto &config = config_get_sound_io(&mic.m_dev); + m_mappings.emplace_back(mapping { &mic.m_dev }); + auto &omap = m_mappings.back(); + + std::vector<std::string> node_to_remove; + for(auto &nmap : config.m_node_mappings) { + if(nmap.first == "") { + if(m_osd_info.m_default_source) + omap.m_node_mappings.emplace_back(mapping::node_mapping { m_osd_info.m_default_source, nmap.second, true }); + } else { + u32 node_id = find_node(nmap.first); + if(node_id != 0) + omap.m_node_mappings.emplace_back(mapping::node_mapping { node_id, nmap.second, false }); + else + node_to_remove.push_back(nmap.first); + } } -#if (SOUND_DEBUG) - // dump the sound graph when we start up - for (speaker_device &speaker : speaker_device_enumerator(machine().root_device())) - { - int index; - sound_stream *output = speaker.output_to_stream_output(0, index); - if (output != nullptr) - output->print_graph_recursive(0, index); + for(auto &nmap: node_to_remove) + internal_config_remove_sound_io_connection_node(&mic.m_dev, nmap); + + std::vector<std::tuple<u32, std::string, u32>> channel_map_to_remove; + for(auto &cmap : config.m_channel_mappings) { + if(std::get<1>(cmap) == "") { + if(m_osd_info.m_default_source) + omap.m_channel_mappings.emplace_back(mapping::channel_mapping { std::get<0>(cmap), m_osd_info.m_default_source, std::get<2>(cmap), std::get<3>(cmap), true }); + } else { + u32 node_id = find_node(std::get<1>(cmap)); + if(node_id != 0) + omap.m_channel_mappings.emplace_back(mapping::channel_mapping { std::get<0>(cmap), node_id, std::get<2>(cmap), std::get<3>(cmap), false }); + else + channel_map_to_remove.push_back(std::tuple<u32, std::string, u32>(std::get<0>(cmap), std::get<1>(cmap), std::get<2>(cmap))); + } } - // dump the orphan list as well - if (m_orphan_stream_list.size() != 0) - { - osd_printf_info("\nOrphaned streams:\n"); - for (auto &stream : m_orphan_stream_list) - osd_printf_info(" %s\n", stream.first->name()); - } -#endif + for(auto &cmap : channel_map_to_remove) + internal_config_remove_sound_io_channel_connection_node(&mic.m_dev, std::get<0>(cmap), std::get<1>(cmap), std::get<2>(cmap)); } } +// Find where to map a sound_io channel into a node's channels depending on their positions -//------------------------------------------------- -// pause - pause sound output -//------------------------------------------------- - -void sound_manager::pause() +std::vector<u32> sound_manager::find_channel_mapping(const std::array<double, 3> &position, const osd::audio_info::node_info *node) { - mute(true, MUTE_REASON_PAUSE); + std::vector<u32> result; + if(position[0] == 0 && position[1] == 0 && position[2] == 0) + return result; + double best_dist = -1; + for(u32 port = 0; port != node->m_port_positions.size(); port++) + if(node->m_port_positions[port][0] || node->m_port_positions[port][1] || node->m_port_positions[port][2]) { + double dx = position[0] - node->m_port_positions[port][0]; + double dy = position[1] - node->m_port_positions[port][1]; + double dz = position[2] - node->m_port_positions[port][2]; + double dist = dx*dx + dy*dy + dz*dz; + if(best_dist == -1 || dist < best_dist) { + best_dist = dist; + result.clear(); + result.push_back(port); + } else if(best_dist == dist) + result.push_back(port); + } + return result; } -//------------------------------------------------- -// resume - resume sound output -//------------------------------------------------- - -void sound_manager::resume() +void sound_manager::update_osd_streams() { - mute(false, MUTE_REASON_PAUSE); -} + std::unique_lock<std::mutex> lock(m_effects_mutex); + auto current_input_streams = std::move(m_osd_input_streams); + auto current_output_streams = std::move(m_osd_output_streams); + m_osd_input_streams.clear(); + m_osd_output_streams.clear(); + + // Find the index of a sound_io_device in the speaker_info vector or the microphone_info vector + + auto find_sound_io_index = [this](sound_io_device *dev) -> u32 { + for(u32 si = 0; si != m_speakers.size(); si++) + if(&m_speakers[si].m_dev == dev) + return si; + for(u32 si = 0; si != m_microphones.size(); si++) + if(&m_microphones[si].m_dev == dev) + return si; + return 0; // Can't happen + }; + + + // Find a pointer to a node_info from the node id + auto find_node_info = [this](u32 node) -> const osd::audio_info::node_info * { + for(const auto &ni : m_osd_info.m_nodes) { + if(ni.m_id == node) + return ∋ + } + // Can't happen + return nullptr; + }; + + // Two possible mapping methods depending on the osd capabilities + + for(auto &m : m_microphones) + m.m_input_mixing_steps.clear(); + m_output_mixing_steps.clear(); + + auto &osd = machine().osd(); + if(osd.sound_split_streams_per_source()) { + auto get_input_stream_for_node_and_device = [this, ¤t_input_streams] (const osd::audio_info::node_info *node, sound_io_device *dev, bool is_system_default, bool is_channel_mapping = false) -> u32 { + // Check if the osd stream already exists to pick it up in case. + // Clear the id in the current_streams structure to show it has been picked up, reset the unused mask. + // Clear the volumes + // m_dev will already be correct + + for(auto &os : current_input_streams) + if(os.m_id && os.m_node == node->m_id && os.m_dev == dev) { + u32 sid = m_osd_input_streams.size(); + m_osd_input_streams.emplace_back(std::move(os)); + os.m_id = 0; + auto &nos = m_osd_input_streams[sid]; + nos.m_is_channel_mapping = is_channel_mapping; + nos.m_unused_channels_mask = util::make_bitmask<u32>(node->m_sources); + nos.m_volumes.clear(); + nos.m_is_system_default = is_system_default; + return sid; + } + // If none exists, create one + u32 sid = m_osd_input_streams.size(); + u32 rate = machine().sample_rate(); + m_osd_input_streams.emplace_back(osd_input_stream(node->m_id, is_system_default ? "" : node->m_name, node->m_sources, rate, is_system_default, dev)); + osd_input_stream &nos = m_osd_input_streams.back(); + nos.m_id = machine().osd().sound_stream_source_open(node->m_id, dev->tag(), rate); + nos.m_is_channel_mapping = is_channel_mapping; + nos.m_buffer.set_sync_sample(rate_and_last_sync_to_index(rate)); + return sid; + }; + + auto get_output_stream_for_node_and_device = [this, ¤t_output_streams] (const osd::audio_info::node_info *node, sound_io_device *dev, bool is_system_default, bool is_channel_mapping = false) -> u32 { + // Check if the osd stream already exists to pick it up in case. + // Clear the id in the current_streams structure to show it has been picked up, reset the unused mask. + // Clear the volumes + // m_dev will already be correct + + for(auto &os : current_output_streams) + if(os.m_id && os.m_node == node->m_id && os.m_dev == dev) { + u32 sid = m_osd_output_streams.size(); + m_osd_output_streams.emplace_back(std::move(os)); + os.m_id = 0; + auto &nos = m_osd_output_streams[sid]; + nos.m_is_channel_mapping = is_channel_mapping; + nos.m_volumes.clear(); + nos.m_unused_channels_mask = util::make_bitmask<u32>(node->m_sinks); + nos.m_is_system_default = is_system_default; + return sid; + } -//------------------------------------------------- -// config_load - read and apply data from the -// configuration file -//------------------------------------------------- + // If none exists, create one + u32 sid = m_osd_output_streams.size(); + u32 rate = machine().sample_rate(); + m_osd_output_streams.emplace_back(osd_output_stream(node->m_id, is_system_default ? "" : node->m_name, node->m_sinks, rate, is_system_default, dev)); + osd_output_stream &nos = m_osd_output_streams.back(); + nos.m_id = machine().osd().sound_stream_sink_open(node->m_id, dev->tag(), rate); + nos.m_is_channel_mapping = is_channel_mapping; + nos.m_last_sync = rate_and_last_sync_to_index(rate); + return sid; + }; + + auto get_input_stream_for_node_and_channel = [this, &get_input_stream_for_node_and_device] (const osd::audio_info::node_info *node, u32 node_channel, sound_io_device *dev, bool is_system_default) -> u32 { + // First check if there's an active stream + for(u32 sid = 0; sid != m_osd_input_streams.size(); sid++) { + auto &os = m_osd_input_streams[sid]; + if(os.m_node == node->m_id && os.m_dev == dev && os.m_unused_channels_mask & (1 << node_channel) && os.m_is_channel_mapping) + return sid; + } -void sound_manager::config_load(config_type cfg_type, config_level cfg_level, util::xml::data_node const *parentnode) -{ - // we only care system-specific configuration - if ((cfg_type != config_type::SYSTEM) || !parentnode) - return; + // Otherwise use the default method + return get_input_stream_for_node_and_device(node, dev, is_system_default, true); + }; - // master volume attenuation - if (util::xml::data_node const *node = parentnode->get_child("attenuation")) - { - // treat source INI files or more specific as higher priority than CFG - // FIXME: leaky abstraction - this depends on a front-end implementation detail - if ((OPTION_PRIORITY_NORMAL + 5) > machine().options().get_entry(OPTION_VOLUME)->priority()) - set_attenuation(std::clamp(int(node->get_attribute_int("value", 0)), -32, 0)); - } - // iterate over channel nodes - for (util::xml::data_node const *node = parentnode->get_child("channel"); node != nullptr; node = node->get_next_sibling("channel")) - { - mixer_input info; - if (indexed_mixer_input(node->get_attribute_int("index", -1), info)) - { - // note that this doesn't disallow out-of-range values - float value = node->get_attribute_float("value", std::nanf("")); - - if (!std::isnan(value)) - info.stream->input(info.inputnum).set_user_gain(value); - } - } + auto get_output_stream_for_node_and_channel = [this, &get_output_stream_for_node_and_device] (const osd::audio_info::node_info *node, u32 node_channel, sound_io_device *dev, bool is_system_default) -> u32 { + // First check if there's an active stream with the correct channel not used yet + for(u32 sid = 0; sid != m_osd_output_streams.size(); sid++) { + auto &os = m_osd_output_streams[sid]; + if(os.m_node == node->m_id && os.m_dev == dev && os.m_unused_channels_mask & (1 << node_channel) && os.m_is_channel_mapping) + return sid; + } - // iterate over speaker panning nodes - for (util::xml::data_node const *node = parentnode->get_child("panning"); node != nullptr; node = node->get_next_sibling("panning")) - { - char const *const tag = node->get_attribute_string("tag", nullptr); - if (tag != nullptr) - { - for (speaker_device &speaker : speaker_device_enumerator(machine().root_device())) - { - if (!strcmp(tag, speaker.tag())) - { - float value = node->get_attribute_float("value", speaker.defpan()); - speaker.set_pan(value); - break; + // Otherwise use the default method + return get_output_stream_for_node_and_device(node, dev, is_system_default, true); + }; + + // Create/retrieve streams to apply the decided mapping + for(const auto &omap : m_mappings) { + u32 dev_index = find_sound_io_index(omap.m_dev); + bool is_output = omap.m_dev->is_output(); + if(is_output) { + std::vector<mixing_step> &mixing_steps = m_output_mixing_steps; + u32 dchannels = omap.m_dev->inputs(); + for(const auto &nm : omap.m_node_mappings) { + const auto *node = find_node_info(nm.m_node); + u32 osd_index = get_output_stream_for_node_and_device(node, omap.m_dev, nm.m_is_system_default); + auto &stream = m_osd_output_streams[osd_index]; + u32 umask = stream.m_unused_channels_mask; + float linear_volume = 1.0; + + if(osd.sound_external_per_channel_volume()) { + stream.m_volumes.clear(); + stream.m_volumes.resize(stream.m_channels, nm.m_db); + + } else + linear_volume = osd::db_to_linear(nm.m_db); + + for(u32 channel = 0; channel != dchannels; channel++) { + std::vector<u32> targets = find_channel_mapping(omap.m_dev->get_position(channel), node); + for(u32 tchannel : targets) { + // If the channel is output and in the to + // clear mask, use load, otherwise use add. + // Apply the volume too if needed + mixing_steps.emplace_back(mixing_step { + (umask & (1 << tchannel)) ? mixing_step::COPY : mixing_step::ADD, + osd_index, + tchannel, + dev_index, + channel, + linear_volume + }); + umask &= ~(1 << tchannel); + } + } + stream.m_unused_channels_mask = umask; + } + + for(const auto &cm : omap.m_channel_mappings) { + const auto *node = find_node_info(cm.m_node); + u32 osd_index = get_output_stream_for_node_and_channel(node, cm.m_node_channel, omap.m_dev, cm.m_is_system_default); + auto &stream = m_osd_output_streams[osd_index]; + float linear_volume = 1.0; + + if(osd.sound_external_per_channel_volume()) { + if(stream.m_volumes.empty()) + stream.m_volumes.resize(stream.m_channels, -96); + stream.m_volumes[cm.m_node_channel] = cm.m_db; + + } else + linear_volume = osd::db_to_linear(cm.m_db); + + mixing_steps.emplace_back(mixing_step { + (stream.m_unused_channels_mask & (1 << cm.m_node_channel)) ? + mixing_step::COPY : mixing_step::ADD, + osd_index, + cm.m_node_channel, + dev_index, + cm.m_guest_channel, + linear_volume + }); + stream.m_unused_channels_mask &= ~(1 << cm.m_node_channel); + } + + + } else { + std::vector<mixing_step> &mixing_steps = m_microphones[dev_index].m_input_mixing_steps; + u32 dchannels = omap.m_dev->outputs(); + for(const auto &nm : omap.m_node_mappings) { + const auto *node = find_node_info(nm.m_node); + u32 osd_index = get_input_stream_for_node_and_device(node, omap.m_dev, nm.m_is_system_default); + auto &stream = m_osd_input_streams[osd_index]; + u32 umask = stream.m_unused_channels_mask; + float linear_volume = 1.0; + + if(osd.sound_external_per_channel_volume()) { + stream.m_volumes.clear(); + stream.m_volumes.resize(stream.m_channels, nm.m_db); + + } else + linear_volume = osd::db_to_linear(nm.m_db); + + for(u32 channel = 0; channel != dchannels; channel++) { + std::vector<u32> targets = find_channel_mapping(omap.m_dev->get_position(channel), node); + for(u32 tchannel : targets) { + // If the channel is output and in the to + // clear mask, use load, otherwise use add. + // Apply the volume too if needed + mixing_steps.emplace_back(mixing_step { + mixing_step::ADD, + osd_index, + tchannel, + dev_index, + channel, + linear_volume + }); + umask &= ~(1 << tchannel); + } + } + stream.m_unused_channels_mask = umask; + } + + for(const auto &cm : omap.m_channel_mappings) { + const auto *node = find_node_info(cm.m_node); + u32 osd_index = get_input_stream_for_node_and_channel(node, cm.m_node_channel, omap.m_dev, cm.m_is_system_default); + auto &stream = m_osd_input_streams[osd_index]; + float linear_volume = 1.0; + + if(osd.sound_external_per_channel_volume()) { + if(stream.m_volumes.empty()) + stream.m_volumes.resize(stream.m_channels, -96); + stream.m_volumes[cm.m_node_channel] = cm.m_db; + + } else + linear_volume = osd::db_to_linear(cm.m_db); + + mixing_steps.emplace_back(mixing_step { + mixing_step::ADD, + osd_index, + cm.m_node_channel, + dev_index, + cm.m_guest_channel, + linear_volume + }); + stream.m_unused_channels_mask &= ~(1 << cm.m_node_channel); } } } - } -} + } else { + // All sources need to be merged per-destination, max one stream per destination + + std::map<u32, u32> stream_per_node; + + // Retrieve or create the one osd stream for a given + // destination. First check if we already have it, then + // whether it was previously created, then otherwise create + // it. + + auto get_input_stream_for_node = [this, ¤t_input_streams, &stream_per_node] (const osd::audio_info::node_info *node, bool is_system_default) -> u32 { + // Pick up the existing stream if there's one + auto si = stream_per_node.find(node->m_id); + if(si != stream_per_node.end()) + return si->second; + + // Create the default unused mask + u32 channels = node->m_sources; + u32 umask = util::make_bitmask<u32>(channels); + + // Check if the osd stream already exists to pick it up in case. + // Clear the id in the current_streams structure to show it has been picked up, reset the unused mask. + // m_speaker will already be nullptr, m_source_channels and m_volumes empty. + + for(auto &os : current_input_streams) + if(os.m_id && os.m_node == node->m_id) { + u32 sid = m_osd_input_streams.size(); + m_osd_input_streams.emplace_back(std::move(os)); + os.m_id = 0; + m_osd_input_streams.back().m_unused_channels_mask = umask; + m_osd_input_streams.back().m_is_system_default = is_system_default; + stream_per_node[node->m_id] = sid; + return sid; + } -//------------------------------------------------- -// config_save - save data to the configuration -// file -//------------------------------------------------- + // If none exists, create one + u32 sid = m_osd_input_streams.size(); + u32 rate = machine().sample_rate(); + m_osd_input_streams.emplace_back(osd_input_stream(node->m_id, is_system_default ? "" : node->m_name, channels, rate, is_system_default, nullptr)); + osd_input_stream &stream = m_osd_input_streams.back(); + stream.m_id = machine().osd().sound_stream_source_open(node->m_id, machine().system().name, rate); + stream.m_buffer.set_sync_sample(rate_and_last_sync_to_index(rate)); + stream_per_node[node->m_id] = sid; + return sid; + }; + + auto get_output_stream_for_node = [this, ¤t_output_streams, &stream_per_node] (const osd::audio_info::node_info *node, bool is_system_default) -> u32 { + // Pick up the existing stream if there's one + auto si = stream_per_node.find(node->m_id); + if(si != stream_per_node.end()) + return si->second; + + // Create the default unused mask + u32 channels = node->m_sinks; + u32 umask = util::make_bitmask<u32>(channels); + + // Check if the osd stream already exists to pick it up in case. + // Clear the id in the current_streams structure to show it has been picked up, reset the unused mask. + // m_speaker will already be nullptr, m_source_channels and m_volumes empty. + + for(auto &os : current_output_streams) + if(os.m_id && os.m_node == node->m_id) { + u32 sid = m_osd_output_streams.size(); + m_osd_output_streams.emplace_back(std::move(os)); + os.m_id = 0; + m_osd_output_streams.back().m_unused_channels_mask = umask; + m_osd_output_streams.back().m_is_system_default = is_system_default; + stream_per_node[node->m_id] = sid; + return sid; + } -void sound_manager::config_save(config_type cfg_type, util::xml::data_node *parentnode) -{ - // we only save system-specific configuration - if (cfg_type != config_type::SYSTEM) - return; + // If none exists, create one + u32 sid = m_osd_output_streams.size(); + u32 rate = machine().sample_rate(); + m_osd_output_streams.emplace_back(osd_output_stream(node->m_id, is_system_default ? "" : node->m_name, channels, rate, is_system_default, nullptr)); + osd_output_stream &stream = m_osd_output_streams.back(); + stream.m_id = machine().osd().sound_stream_sink_open(node->m_id, machine().system().name, rate); + stream.m_last_sync = rate_and_last_sync_to_index(rate); + stream_per_node[node->m_id] = sid; + return sid; + }; + + + // Create/retrieve streams to apply the decided mapping + + for(const auto &omap : m_mappings) { + u32 dev_index = find_sound_io_index(omap.m_dev); + bool is_output = omap.m_dev->is_output(); + if(is_output) { + u32 channels = m_speakers[dev_index].m_channels; + std::vector<mixing_step> &mixing_steps = m_output_mixing_steps; + for(const auto &nm : omap.m_node_mappings) { + const auto *node = find_node_info(nm.m_node); + u32 osd_index = get_output_stream_for_node(node, nm.m_is_system_default); + u32 umask = m_osd_output_streams[osd_index].m_unused_channels_mask; + float linear_volume = osd::db_to_linear(nm.m_db); + + for(u32 channel = 0; channel != channels; channel++) { + std::vector<u32> targets = find_channel_mapping(omap.m_dev->get_position(channel), node); + for(u32 tchannel : targets) { + // If the channel is in the to clear mask, use load, otherwise use add + // Apply the volume too + mixing_steps.emplace_back(mixing_step { + (umask & (1 << tchannel)) ? mixing_step::COPY : mixing_step::ADD, + osd_index, + tchannel, + dev_index, + channel, + linear_volume + }); + umask &= ~(1 << tchannel); + } + } + m_osd_output_streams[osd_index].m_unused_channels_mask = umask; + } - // master volume attenuation - if (m_attenuation != machine().options().volume()) - { - if (util::xml::data_node *const node = parentnode->add_child("attenuation", nullptr)) - node->set_attribute_int("value", m_attenuation); - } + for(const auto &cm : omap.m_channel_mappings) { + const auto *node = find_node_info(cm.m_node); + u32 osd_index = get_output_stream_for_node(node, false); + u32 umask = m_osd_output_streams[osd_index].m_unused_channels_mask; + + // If the channel is in the to clear mask, use load, otherwise use add + // Apply the volume too + mixing_steps.emplace_back(mixing_step { + (umask & (1 << cm.m_node_channel)) ? mixing_step::COPY : mixing_step::ADD, + osd_index, + cm.m_node_channel, + dev_index, + cm.m_guest_channel, + osd::db_to_linear(cm.m_db) + }); + m_osd_output_streams[osd_index].m_unused_channels_mask = umask & ~(1 << cm.m_node_channel); + } - // iterate over mixer channels for per-channel volume - for (int mixernum = 0; ; mixernum++) - { - mixer_input info; - if (!indexed_mixer_input(mixernum, info)) - break; + } else { + u32 channels = m_microphones[dev_index].m_channels; + std::vector<mixing_step> &mixing_steps = m_microphones[dev_index].m_input_mixing_steps; + for(const auto &nm : omap.m_node_mappings) { + const auto *node = find_node_info(nm.m_node); + u32 osd_index = get_input_stream_for_node(node, nm.m_is_system_default); + float linear_volume = osd::db_to_linear(nm.m_db); + + for(u32 channel = 0; channel != channels; channel++) { + std::vector<u32> targets = find_channel_mapping(omap.m_dev->get_position(channel), node); + for(u32 tchannel : targets) { + // If the channel is in the to clear mask, use load, otherwise use add + // Apply the volume too + mixing_steps.emplace_back(mixing_step { + mixing_step::ADD, + osd_index, + tchannel, + dev_index, + channel, + linear_volume + }); + m_osd_input_streams[osd_index].m_unused_channels_mask &= ~(1 << tchannel); + } + } + } - float const value = info.stream->input(info.inputnum).user_gain(); - if (value != 1.0f) - { - util::xml::data_node *const node = parentnode->add_child("channel", nullptr); - if (node) - { - node->set_attribute_int("index", mixernum); - node->set_attribute_float("value", value); + for(const auto &cm : omap.m_channel_mappings) { + const auto *node = find_node_info(cm.m_node); + u32 osd_index = get_input_stream_for_node(node, false); + + // If the channel is in the to clear mask, use load, otherwise use add + // Apply the volume too + mixing_steps.emplace_back(mixing_step { + mixing_step::ADD, + osd_index, + cm.m_node_channel, + dev_index, + cm.m_guest_channel, + osd::db_to_linear(cm.m_db) + }); + m_osd_input_streams[osd_index].m_unused_channels_mask &= ~(1 << cm.m_node_channel); + } } } } - // iterate over speakers for panning - for (speaker_device &speaker : speaker_device_enumerator(machine().root_device())) - { - float const value = speaker.pan(); - if (value != speaker.defpan()) - { - util::xml::data_node *const node = parentnode->add_child("panning", nullptr); - if (node) - { - node->set_attribute("tag", speaker.tag()); - node->set_attribute_float("value", value); - } + // Add a clear step for all output streams that need it + // Also set the volumes if supported + for(u32 stream_index = 0; stream_index != m_osd_output_streams.size(); stream_index++) { + auto &stream = m_osd_output_streams[stream_index]; + if(stream.m_unused_channels_mask) { + for(u32 channel = 0; channel != stream.m_channels; channel ++) + if(stream.m_unused_channels_mask & (1 << channel)) + m_output_mixing_steps.emplace_back(mixing_step { mixing_step::CLEAR, 0, 0, stream_index, channel, 0.0 }); } + if(!stream.m_volumes.empty()) + osd.sound_stream_set_volumes(stream.m_id, stream.m_volumes); } -} + // If supported, set the volumes for the input streams + for(u32 stream_index = 0; stream_index != m_osd_input_streams.size(); stream_index++) { + auto &stream = m_osd_input_streams[stream_index]; + if(!stream.m_volumes.empty()) + osd.sound_stream_set_volumes(stream.m_id, stream.m_volumes); + } -//------------------------------------------------- -// adjust_toward_compressor_scale - adjust the -// current scale factor toward the current goal, -// in small increments -//------------------------------------------------- + // Close all previous streams that haven't been picked up + for(const auto &stream : current_input_streams) + if(stream.m_id) + machine().osd().sound_stream_close(stream.m_id); + for(const auto &stream : current_output_streams) + if(stream.m_id) + machine().osd().sound_stream_close(stream.m_id); +} -stream_buffer::sample_t sound_manager::adjust_toward_compressor_scale(stream_buffer::sample_t curscale, stream_buffer::sample_t prevsample, stream_buffer::sample_t rawsample) +void sound_manager::mapping_update() { - stream_buffer::sample_t proposed_scale = curscale; + auto &osd = machine().osd(); + while(m_osd_info.m_generation != osd.sound_get_generation()) { + osd_information_update(); + + if(VERBOSE & LOG_OSD_INFO) { + LOG_OUTPUT_FUNC("OSD information:\n"); + LOG_OUTPUT_FUNC("- generation %u\n", m_osd_info.m_generation); + LOG_OUTPUT_FUNC("- default sink %u\n", m_osd_info.m_default_sink); + LOG_OUTPUT_FUNC("- default source %u\n", m_osd_info.m_default_source); + LOG_OUTPUT_FUNC("- nodes:\n"); + for(const auto &node : m_osd_info.m_nodes) { + LOG_OUTPUT_FUNC(" * %3u %s [%d %d-%d]\n", node.m_id, node.name().c_str(), node.m_rate.m_default_rate, node.m_rate.m_min_rate, node.m_rate.m_max_rate); + uint32_t port_count = node.m_sinks; + if(port_count < node.m_sources) + port_count = node.m_sources; + for(uint32_t port = 0; port != port_count; port++) + LOG_OUTPUT_FUNC(" %s %s [%g %g %g]\n", + port < node.m_sinks ? port < node.m_sources ? "<>" : ">" : "<", + node.m_port_names[port].c_str(), + node.m_port_positions[port][0], + node.m_port_positions[port][1], + node.m_port_positions[port][2]); + } + LOG_OUTPUT_FUNC("- streams:\n"); + for(const auto &stream : m_osd_info.m_streams) { + LOG_OUTPUT_FUNC(" * %3u node %u", stream.m_id, stream.m_node); + if(!stream.m_volumes.empty()) { + LOG_OUTPUT_FUNC(" volumes"); + for(float v : stream.m_volumes) + LOG_OUTPUT_FUNC(" %g", v); + } + LOG_OUTPUT_FUNC("\n"); + } + } - // if we want to get larger, increment by 0.01 - if (curscale < m_compressor_scale) - { - proposed_scale += 0.01f; - if (proposed_scale > m_compressor_scale) - proposed_scale = m_compressor_scale; - } + generate_mapping(); - // otherwise, decrement by 0.01 - else - { - proposed_scale -= 0.01f; - if (proposed_scale < m_compressor_scale) - proposed_scale = m_compressor_scale; - } + if(VERBOSE & LOG_MAPPING) { + LOG_OUTPUT_FUNC("MAPPING:\n"); + for(const auto &omap : m_mappings) { + LOG_OUTPUT_FUNC("- sound_io %s\n", omap.m_dev->tag()); + for(const auto &nm : omap.m_node_mappings) + LOG_OUTPUT_FUNC(" * node %u volume %g%s\n", nm.m_node, nm.m_db, nm.m_is_system_default ? " (default)" : ""); + for(const auto &cm : omap.m_channel_mappings) + LOG_OUTPUT_FUNC(" * channel %u <-> node %u:%i volume %g\n", cm.m_guest_channel, cm.m_node, cm.m_node_channel, cm.m_db); + } + } - // compute the sample at the current scale and at the proposed scale - stream_buffer::sample_t cursample = rawsample * curscale; - stream_buffer::sample_t proposed_sample = rawsample * proposed_scale; + update_osd_streams(); + + if(VERBOSE & LOG_OSD_STREAMS) { + LOG_OUTPUT_FUNC("OSD input streams:\n"); + for(const auto &os : m_osd_input_streams) { + if(machine().osd().sound_split_streams_per_source()) { + LOG_OUTPUT_FUNC("- %3u %s node %u", os.m_id, os.m_dev ? os.m_dev->tag() : "-", os.m_node); + if(!os.m_is_channel_mapping) + LOG_OUTPUT_FUNC(" channels"); + if(machine().osd().sound_external_per_channel_volume()) { + LOG_OUTPUT_FUNC(" dB"); + for(u32 i = 0; i != os.m_channels; i++) + LOG_OUTPUT_FUNC(" %g", os.m_volumes[i]); + } + LOG_OUTPUT_FUNC("\n"); + } else + LOG_OUTPUT_FUNC("- %3u node %u\n", os.m_id, os.m_node); + } + LOG_OUTPUT_FUNC("Input mixing steps:\n"); + for(const auto &m : m_microphones) { + LOG_OUTPUT_FUNC(" %s:\n", m.m_dev.tag()); + for(const auto &ms : m.m_input_mixing_steps) { + static const char *const modes[5] = { "clear", "copy", "copy+vol", "add", "add+vol" }; + LOG_OUTPUT_FUNC(" - %s osd %u:%u -> device %u:%u level %g\n", modes[ms.m_mode], ms.m_osd_index, ms.m_osd_channel, ms.m_device_index, ms.m_device_channel, ms.m_linear_volume); + } + } + LOG_OUTPUT_FUNC("OSD output streams:\n"); + for(const auto &os : m_osd_output_streams) { + if(machine().osd().sound_split_streams_per_source()) { + LOG_OUTPUT_FUNC("- %3u %s node %u", os.m_id, os.m_dev ? os.m_dev->tag() : "-", os.m_node); + if(!os.m_is_channel_mapping) + LOG_OUTPUT_FUNC(" channels"); + if(machine().osd().sound_external_per_channel_volume()) { + LOG_OUTPUT_FUNC(" dB"); + for(u32 i = 0; i != os.m_channels; i++) + LOG_OUTPUT_FUNC(" %g", os.m_volumes[i]); + } + LOG_OUTPUT_FUNC("\n"); + } else + LOG_OUTPUT_FUNC("- %3u node %u\n", os.m_id, os.m_node); + } + LOG_OUTPUT_FUNC("Output mixing steps:\n"); + for(const auto &ms : m_output_mixing_steps) { + static const char *const modes[5] = { "clear", "copy", "copy+vol", "add", "add+vol" }; + LOG_OUTPUT_FUNC("- %s device %u:%u -> osd %u:%u level %g\n", modes[ms.m_mode], ms.m_device_index, ms.m_device_channel, ms.m_osd_index, ms.m_osd_channel, ms.m_linear_volume); + } + } + } +} - // if they trend in the same direction, it's ok to take the step - if ((cursample < prevsample && proposed_sample < prevsample) || (cursample > prevsample && proposed_sample > prevsample)) - curscale = proposed_scale; - // return the current scale - return curscale; -} -//------------------------------------------------- -// update - mix everything down to its final form -// and send it to the OSD layer -//------------------------------------------------- +//**// Global sound system update -void sound_manager::update(s32 param) +u64 sound_manager::rate_and_time_to_index(attotime time, u32 sample_rate) const { - LOG("sound_update\n"); + return time.m_seconds * sample_rate + ((time.m_attoseconds / 100'000'000) * sample_rate) / 10'000'000'000LL; //' +} +void sound_manager::update(s32) +{ auto profile = g_profiler.start(PROFILER_SOUND); - // determine the duration of this update - attotime update_period = machine().time() - m_last_update; - sound_assert(update_period.seconds() == 0); + if(m_osd_info.m_generation == 0xffffffff) + startup_cleanups(); - // use that to compute the number of samples we need from the speakers - attoseconds_t sample_rate_attos = HZ_TO_ATTOSECONDS(machine().sample_rate()); - m_samples_this_update = update_period.attoseconds() / sample_rate_attos; + mapping_update(); + streams_update(); - // recompute the end time to an even sample boundary - attotime endtime = m_last_update + attotime(0, m_samples_this_update * sample_rate_attos); + m_last_sync_time = machine().time(); +} - // clear out the mix bufers - std::fill_n(&m_leftmix[0], m_samples_this_update, 0); - std::fill_n(&m_rightmix[0], m_samples_this_update, 0); +void sound_manager::streams_update() +{ + attotime now = machine().time(); + { + std::unique_lock<std::mutex> lock(m_effects_mutex); + for(osd_output_stream &stream : m_osd_output_streams) { + u64 next_sync = rate_and_time_to_index(now, stream.m_rate); + stream.m_samples = next_sync - stream.m_last_sync; + stream.m_last_sync = next_sync; + } - // force all the speaker streams to generate the proper number of samples - for (speaker_device &speaker : m_speakers) - speaker.mix(&m_leftmix[0], &m_rightmix[0], m_last_update, endtime, m_samples_this_update, (m_muted & MUTE_REASON_SYSTEM)); + for(sound_stream *stream : m_ordered_streams) + stream->update_nodeps(); + } - // determine the maximum in this section - stream_buffer::sample_t curmax = 0; - for (int sampindex = 0; sampindex < m_samples_this_update; sampindex++) + // Send the hooked samples to lua { - auto sample = m_leftmix[sampindex]; - if (sample < 0) - sample = -sample; - if (sample > curmax) - curmax = sample; - - sample = m_rightmix[sampindex]; - if (sample < 0) - sample = -sample; - if (sample > curmax) - curmax = sample; + std::map<std::string, std::vector<std::pair<const float *, int>>> sound_data; + for(device_sound_interface &sound : sound_interface_enumerator(machine().root_device())) + if(sound.get_sound_hook()) { + std::vector<std::pair<const float *, int>> buffers; + if(sound.device().type() == SPEAKER) { + const emu::detail::output_buffer_flat<sample_t> &buffer = m_speakers[static_cast<speaker_device &>(sound.device()).get_id()].m_buffer; + int samples = buffer.available_samples(); + for(int channel = 0; channel != sound.inputs(); channel++) + buffers.emplace_back(std::make_pair(buffer.ptrs(channel, 0), samples)); + + } else { + for(int channel = 0; channel != sound.outputs(); channel++) { + std::pair<sound_stream *, int> info = sound.output_to_stream_output(channel); + const emu::detail::output_buffer_flat<sample_t> &buffer = info.first->m_output_buffer; + buffers.emplace_back(std::make_pair(buffer.ptrs(info.second, 0), buffer.available_samples())); + } + } + sound_data.emplace(sound.device().tag(), std::move(buffers)); + } + + emulator_info::sound_hook(sound_data); } - // pull in current compressor scale factor before modifying - stream_buffer::sample_t lscale = m_compressor_scale; - stream_buffer::sample_t rscale = m_compressor_scale; + for(sound_stream *stream : m_ordered_streams) + if(stream->device().type() != SPEAKER) + stream->sync(now); - // if we're above what the compressor will handle, adjust the compression - if (curmax * m_compressor_scale > 1.0) - { - m_compressor_scale = 1.0 / curmax; - m_compressor_counter = STREAMS_UPDATE_FREQUENCY / 5; - } + for(osd_input_stream &stream : m_osd_input_streams) + stream.m_buffer.sync(); - // if we're currently scaled, wait a bit to see if we can trend back toward 1.0 - else if (m_compressor_counter != 0) - m_compressor_counter--; + machine().osd().add_audio_to_recording(m_record_buffer.data(), m_record_samples); + machine().video().add_sound_to_recording(m_record_buffer.data(), m_record_samples); + if(m_wavfile) + util::wav_add_data_16(*m_wavfile, m_record_buffer.data(), m_record_samples * m_outputs_count); - // try to migrate toward 0 unless we're going to introduce clipping - else if (m_compressor_scale < 1.0 && curmax * 1.01 * m_compressor_scale < 1.0) - { - m_compressor_scale *= 1.01f; - if (m_compressor_scale > 1.0) - m_compressor_scale = 1.0; - } + m_effects_condition.notify_all(); +} -#if (SOUND_DEBUG) - if (lscale != m_compressor_scale) - printf("scale=%.5f\n", m_compressor_scale); -#endif +//**// Resampler management +const audio_resampler *sound_manager::get_resampler(u32 fs, u32 ft) +{ + auto key = std::make_pair(fs, ft); + auto i = m_resamplers.find(key); + if(i != m_resamplers.end()) + return i->second.get(); + + audio_resampler *res; + if(m_resampler_type == RESAMPLER_HQ) + res = new audio_resampler_hq(fs, ft, m_resampler_hq_latency, m_resampler_hq_length, m_resampler_hq_phases); + else + res = new audio_resampler_lofi(fs, ft); + m_resamplers[key].reset(res); + return res; +} - // track whether there are pending scale changes in left/right - stream_buffer::sample_t lprev = 0, rprev = 0; +void sound_manager::rebuild_all_resamplers() +{ + m_resamplers.clear(); - // now downmix the final result - u32 finalmix_step = machine().video().speed_factor(); - u32 finalmix_offset = 0; - s16 *finalmix = &m_finalmix[0]; - int sample; - for (sample = m_finalmix_leftover; sample < m_samples_this_update * 1000; sample += finalmix_step) - { - int sampindex = sample / 1000; - - // ensure that changing the compression won't reverse direction to reduce "pops" - stream_buffer::sample_t lsamp = m_leftmix[sampindex]; - if (lscale != m_compressor_scale && sample != m_finalmix_leftover) - lscale = adjust_toward_compressor_scale(lscale, lprev, lsamp); - - lprev = lsamp * lscale; - if (m_compressor_enabled) - lsamp = lprev; - - // clamp the left side - if (lsamp > 1.0) - lsamp = 1.0; - else if (lsamp < -1.0) - lsamp = -1.0; - finalmix[finalmix_offset++] = s16(lsamp * 32767.0); - - // ensure that changing the compression won't reverse direction to reduce "pops" - stream_buffer::sample_t rsamp = m_rightmix[sampindex]; - if (rscale != m_compressor_scale && sample != m_finalmix_leftover) - rscale = adjust_toward_compressor_scale(rscale, rprev, rsamp); - - rprev = rsamp * rscale; - if (m_compressor_enabled) - rsamp = rprev; - - // clamp the right side - if (rsamp > 1.0) - rsamp = 1.0; - else if (rsamp < -1.0) - rsamp = -1.0; - finalmix[finalmix_offset++] = s16(rsamp * 32767.0); + for(auto &stream : m_stream_list) + stream->create_resamplers(); + + for(auto &stream : m_stream_list) + stream->lookup_history_sizes(); +} + +void sound_manager::set_resampler_type(u32 type) +{ + if(type != m_resampler_type) { + m_resampler_type = type; + rebuild_all_resamplers(); } - m_finalmix_leftover = sample - m_samples_this_update * 1000; +} - // play the result - if (finalmix_offset > 0) - { - if (!m_nosound_mode) - machine().osd().update_audio_stream(finalmix, finalmix_offset / 2); - machine().osd().add_audio_to_recording(finalmix, finalmix_offset / 2); - machine().video().add_sound_to_recording(finalmix, finalmix_offset / 2); - if (m_wavfile) - util::wav_add_data_16(*m_wavfile, finalmix, finalmix_offset); +void sound_manager::set_resampler_hq_latency(double latency) +{ + if(latency != m_resampler_hq_latency) { + m_resampler_hq_latency = latency; + rebuild_all_resamplers(); } +} - // update any orphaned streams so they don't get too far behind - for (auto &stream : m_orphan_stream_list) - stream.first->update(); +void sound_manager::set_resampler_hq_length(u32 length) +{ + if(length != m_resampler_hq_length) { + m_resampler_hq_length = length; + rebuild_all_resamplers(); + } +} - // remember the update time - m_last_update = endtime; - m_update_number++; +void sound_manager::set_resampler_hq_phases(u32 phases) +{ + if(phases != m_resampler_hq_phases) { + m_resampler_hq_phases = phases; + rebuild_all_resamplers(); + } +} - // apply sample rate changes - apply_sample_rate_changes(); +const char *sound_manager::resampler_type_names(u32 type) const +{ + using util::lang_translate; - // notify that new samples have been generated - emulator_info::sound_hook(); + if(type == RESAMPLER_HQ) + return _("HQ"); + else + return _("LoFi"); } diff --git a/src/emu/sound.h b/src/emu/sound.h index 15f6a5743a2..57c96683f0a 100644 --- a/src/emu/sound.h +++ b/src/emu/sound.h @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Aaron Giles +// copyright-holders:O. Galibert, Aaron Giles /*************************************************************************** sound.h @@ -35,7 +35,7 @@ For example, if you have a 10Hz clock, and call stream.update() at t=0.91, it will compute 10 samples (for clock edges 0.0, 0.1, 0.2, ..., 0.7, 0.8, and 0.9). And then if you ask the stream what its - current end time is (via stream.sample_time()), it will say t=1.0, + current end time is (via stream.end_time()), it will say t=1.0, which is in the future, because it knows it will hold that last sample until 1.0s. @@ -62,21 +62,19 @@ #define MAME_EMU_SOUND_H #include "wavwrite.h" - +#include "interface/audio.h" +#include <mutex> +#include <thread> +#include <condition_variable> //************************************************************************** // CONSTANTS //************************************************************************** // special sample-rate values -constexpr u32 SAMPLE_RATE_INVALID = 0xffffffff; -constexpr u32 SAMPLE_RATE_INPUT_ADAPTIVE = 0xfffffffe; -constexpr u32 SAMPLE_RATE_OUTPUT_ADAPTIVE = 0xfffffffd; - -// anything below this sample rate is effectively treated as "off" -constexpr u32 SAMPLE_RATE_MINIMUM = 50; - - +constexpr u32 SAMPLE_RATE_INPUT_ADAPTIVE = 0xffffffff; +constexpr u32 SAMPLE_RATE_OUTPUT_ADAPTIVE = 0xfffffffe; +constexpr u32 SAMPLE_RATE_ADAPTIVE = 0xfffffffd; //************************************************************************** // DEBUGGING @@ -86,7 +84,7 @@ constexpr u32 SAMPLE_RATE_MINIMUM = 50; #ifdef MAME_DEBUG #define SOUND_DEBUG (1) #else -#define SOUND_DEBUG (0) +#define SOUND_DEBUG (1) #endif // if SOUND_DEBUG is on, make assertions fire regardless of MAME_DEBUG @@ -96,491 +94,9 @@ constexpr u32 SAMPLE_RATE_MINIMUM = 50; #define sound_assert assert #endif - - -//************************************************************************** -// TYPE DEFINITIONS -//************************************************************************** - -// ======================> stream_buffer - -class stream_buffer -{ - // stream_buffer is an internal class, not directly accessed - // outside of the classes below - friend class read_stream_view; - friend class write_stream_view; - friend class sound_stream; - friend class sound_stream_output; - -public: - // the one public bit is the sample type - using sample_t = float; - -private: - // constructor/destructor - stream_buffer(u32 sample_rate = 48000); - ~stream_buffer(); - - // disable copying of stream_buffers directly - stream_buffer(stream_buffer const &src) = delete; - stream_buffer &operator=(stream_buffer const &rhs) = delete; - - // return the current sample rate - u32 sample_rate() const { return m_sample_rate; } - - // set a new sample rate - void set_sample_rate(u32 rate, bool resample); - - // return the current sample period in attoseconds - attoseconds_t sample_period_attoseconds() const { return m_sample_attos; } - attotime sample_period() const { return attotime(0, m_sample_attos); } - - // return the attotime of the current end of buffer - attotime end_time() const { return index_time(m_end_sample); } - - // set the ending time (for forced resyncs; generally not used) - void set_end_time(attotime time) - { - m_end_second = time.seconds(); - m_end_sample = u32(time.attoseconds() / m_sample_attos); - } - - // return the effective buffer size; currently it is a full second of audio - // at the current sample rate, but this maybe change in the future - u32 size() const { return m_sample_rate; } - - // read the sample at the given index (clamped); should be valid in all cases - sample_t get(s32 index) const - { - sound_assert(u32(index) < size()); - sample_t value = m_buffer[index]; -#if (SOUND_DEBUG) - sound_assert(!std::isnan(value)); -#endif - return value; - } - - // write the sample at the given index (clamped) - void put(s32 index, sample_t data) - { - sound_assert(u32(index) < size()); - m_buffer[index] = data; - } - - // simple helpers to step indexes - u32 next_index(u32 index) { index++; return (index == size()) ? 0 : index; } - u32 prev_index(u32 index) { return (index == 0) ? (size() - 1) : (index - 1); } - - // clamp an index to the size of the buffer; allows for indexing +/- one - // buffers' worth of range - u32 clamp_index(s32 index) const - { - if (index < 0) - index += size(); - else if (index >= size()) - index -= size(); - sound_assert(index >= 0 && index < size()); - return index; - } - - // fill the buffer with the given value - void fill(sample_t value) { std::fill_n(&m_buffer[0], m_buffer.size(), value); } - - // return the attotime of a given index within the buffer - attotime index_time(s32 index) const; - - // given an attotime, return the buffer index corresponding to it - u32 time_to_buffer_index(attotime time, bool round_up, bool allow_expansion = false); - - // downsample from our buffer into a temporary buffer - void backfill_downsample(sample_t *dest, int samples, attotime newend, attotime newperiod); - - // upsample from a temporary buffer into our buffer - void backfill_upsample(sample_t const *src, int samples, attotime prevend, attotime prevperiod); - - // internal state - u32 m_end_second; // current full second of the buffer end - u32 m_end_sample; // current sample number within the final second - u32 m_sample_rate; // sample rate of the data in the buffer - attoseconds_t m_sample_attos; // pre-computed attoseconds per sample - std::vector<sample_t> m_buffer; // vector of actual buffer data - -#if (SOUND_DEBUG) -public: - // for debugging, provide an interface to write a WAV stream - void open_wav(char const *filename); - void flush_wav(); - -private: - // internal debugging state - util::wav_file_ptr m_wav_file; // pointer to the current WAV file - u32 m_last_written = 0; // last written sample index -#endif -}; - - -// ======================> read_stream_view - -class read_stream_view -{ -public: - using sample_t = stream_buffer::sample_t; - -protected: - // private constructor used by write_stream_view that allows for expansion - read_stream_view(stream_buffer &buffer, attotime start, attotime end) : - read_stream_view(&buffer, 0, buffer.time_to_buffer_index(end, true, true), 1.0) - { - // start has to be set after end, since end can expand the buffer and - // potentially invalidate start - m_start = buffer.time_to_buffer_index(start, false); - normalize_start_end(); - } - -public: - // base constructor to simplify some of the code - read_stream_view(stream_buffer *buffer, s32 start, s32 end, sample_t gain) : - m_buffer(buffer), - m_end(end), - m_start(start), - m_gain(gain) - { - normalize_start_end(); - } - - // empty constructor so we can live in an array or vector - read_stream_view() : - read_stream_view(nullptr, 0, 0, 1.0) - { - } - - // constructor that covers the given time period - read_stream_view(stream_buffer &buffer, attotime start, attotime end, sample_t gain) : - read_stream_view(&buffer, buffer.time_to_buffer_index(start, false), buffer.time_to_buffer_index(end, true), gain) - { - } - - // copy constructor - read_stream_view(read_stream_view const &src) : - read_stream_view(src.m_buffer, src.m_start, src.m_end, src.m_gain) - { - } - - // copy constructor that sets a different start time - read_stream_view(read_stream_view const &src, attotime start) : - read_stream_view(src.m_buffer, src.m_buffer->time_to_buffer_index(start, false), src.m_end, src.m_gain) - { - } - - // copy assignment - read_stream_view &operator=(read_stream_view const &rhs) - { - m_buffer = rhs.m_buffer; - m_start = rhs.m_start; - m_end = rhs.m_end; - m_gain = rhs.m_gain; - normalize_start_end(); - return *this; - } - - // return the local gain - sample_t gain() const { return m_gain; } - - // return the sample rate of the data - u32 sample_rate() const { return m_buffer->sample_rate(); } - - // return the sample period (in attoseconds) of the data - attoseconds_t sample_period_attoseconds() const { return m_buffer->sample_period_attoseconds(); } - attotime sample_period() const { return m_buffer->sample_period(); } - - // return the number of samples represented by the buffer - u32 samples() const { return m_end - m_start; } - - // return the starting or ending time of the buffer - attotime start_time() const { return m_buffer->index_time(m_start); } - attotime end_time() const { return m_buffer->index_time(m_end); } - - // set the gain - read_stream_view &set_gain(float gain) { m_gain = gain; return *this; } - - // apply an additional gain factor - read_stream_view &apply_gain(float gain) { m_gain *= gain; return *this; } - - // safely fetch a gain-scaled sample from the buffer - sample_t get(s32 index) const - { - sound_assert(u32(index) < samples()); - index += m_start; - if (index >= m_buffer->size()) - index -= m_buffer->size(); - return m_buffer->get(index) * m_gain; - } - - // safely fetch a raw sample from the buffer; if you use this, you need to - // apply the gain yourself for correctness - sample_t getraw(s32 index) const - { - sound_assert(u32(index) < samples()); - index += m_start; - if (index >= m_buffer->size()) - index -= m_buffer->size(); - return m_buffer->get(index); - } - -protected: - // normalize start/end - void normalize_start_end() - { - // ensure that end is always greater than start; we'll - // wrap to the buffer length as needed - if (m_end < m_start && m_buffer != nullptr) - m_end += m_buffer->size(); - sound_assert(m_end >= m_start); - } - - // internal state - stream_buffer *m_buffer; // pointer to the stream buffer we're viewing - s32 m_end; // ending sample index (always >= start) - s32 m_start; // starting sample index - sample_t m_gain; // overall gain factor -}; - - -// ======================> write_stream_view - -class write_stream_view : public read_stream_view -{ - -public: - // empty constructor so we can live in an array or vector - write_stream_view() - { - } - - // constructor that covers the given time period - write_stream_view(stream_buffer &buffer, attotime start, attotime end) : - read_stream_view(buffer, start, end) - { - } - - // constructor that converts from a read_stream_view - write_stream_view(read_stream_view const &src) : - read_stream_view(src) - { - } - - // safely write a sample to the buffer - void put(s32 start, sample_t sample) - { - sound_assert(u32(start) < samples()); - m_buffer->put(index_to_buffer_index(start), sample); - } - - // write a sample to the buffer, clamping to +/- the clamp value - void put_clamp(s32 index, sample_t sample, sample_t clamp = 1.0) - { - assert(clamp >= sample_t(0)); - put(index, std::clamp(sample, -clamp, clamp)); - } - - // write a sample to the buffer, converting from an integer with the given maximum - void put_int(s32 index, s32 sample, s32 max) - { - put(index, sample_t(sample) * (1.0f / sample_t(max))); - } - - // write a sample to the buffer, converting from an integer with the given maximum - void put_int_clamp(s32 index, s32 sample, s32 maxclamp) - { - assert(maxclamp >= 0); - put_int(index, std::clamp(sample, -maxclamp, maxclamp), maxclamp); - } - - // safely add a sample to the buffer - void add(s32 start, sample_t sample) - { - sound_assert(u32(start) < samples()); - u32 index = index_to_buffer_index(start); - m_buffer->put(index, m_buffer->get(index) + sample); - } - - // add a sample to the buffer, converting from an integer with the given maximum - void add_int(s32 index, s32 sample, s32 max) - { - add(index, sample_t(sample) * (1.0f / sample_t(max))); - } - - // fill part of the view with the given value - void fill(sample_t value, s32 start, s32 count) - { - if (start + count > samples()) - count = samples() - start; - u32 index = index_to_buffer_index(start); - for (s32 sampindex = 0; sampindex < count; sampindex++) - { - m_buffer->put(index, value); - index = m_buffer->next_index(index); - } - } - void fill(sample_t value, s32 start) { fill(value, start, samples() - start); } - void fill(sample_t value) { fill(value, 0, samples()); } - - // copy data from another view - void copy(read_stream_view const &src, s32 start, s32 count) - { - if (start + count > samples()) - count = samples() - start; - u32 index = index_to_buffer_index(start); - for (s32 sampindex = 0; sampindex < count; sampindex++) - { - m_buffer->put(index, src.get(start + sampindex)); - index = m_buffer->next_index(index); - } - } - void copy(read_stream_view const &src, s32 start) { copy(src, start, samples() - start); } - void copy(read_stream_view const &src) { copy(src, 0, samples()); } - - // add data from another view to our current values - void add(read_stream_view const &src, s32 start, s32 count) - { - if (start + count > samples()) - count = samples() - start; - u32 index = index_to_buffer_index(start); - for (s32 sampindex = 0; sampindex < count; sampindex++) - { - m_buffer->put(index, m_buffer->get(index) + src.get(start + sampindex)); - index = m_buffer->next_index(index); - } - } - void add(read_stream_view const &src, s32 start) { add(src, start, samples() - start); } - void add(read_stream_view const &src) { add(src, 0, samples()); } - -private: - // given a stream starting offset, return the buffer index - u32 index_to_buffer_index(s32 start) const - { - u32 index = start + m_start; - if (index >= m_buffer->size()) - index -= m_buffer->size(); - return index; - } -}; - - -// ======================> sound_stream_output - -class sound_stream_output -{ -#if (SOUND_DEBUG) - friend class sound_stream; -#endif - -public: - // construction/destruction - sound_stream_output(); - - // initialization - void init(sound_stream &stream, u32 index, char const *tag_base); - - // no copying allowed - sound_stream_output(sound_stream_output const &src) = delete; - sound_stream_output &operator=(sound_stream_output const &rhs) = delete; - - // simple getters - sound_stream &stream() const { sound_assert(m_stream != nullptr); return *m_stream; } - attotime end_time() const { return m_buffer.end_time(); } - u32 index() const { return m_index; } - stream_buffer::sample_t gain() const { return m_gain; } - u32 buffer_sample_rate() const { return m_buffer.sample_rate(); } - - // simple setters - void set_gain(float gain) { m_gain = gain; } - - // return a friendly name - std::string name() const; - - // handle a changing sample rate - void sample_rate_changed(u32 rate) { m_buffer.set_sample_rate(rate, true); } - - // return an output view covering a time period - write_stream_view view(attotime start, attotime end) { return write_stream_view(m_buffer, start, end); } - - // resync the buffer to the given end time - void set_end_time(attotime end) { m_buffer.set_end_time(end); } - - // attempt to optimize resamplers by reusing them where possible - sound_stream_output &optimize_resampler(sound_stream_output *input_resampler); - -private: - // internal state - sound_stream *m_stream; // owning stream - stream_buffer m_buffer; // output buffer - u32 m_index; // output index within the stream - stream_buffer::sample_t m_gain; // gain to apply to the output - std::vector<sound_stream_output *> m_resampler_list; // list of resamplers we're connected to -}; - - -// ======================> sound_stream_input - -class sound_stream_input -{ -#if (SOUND_DEBUG) - friend class sound_stream; -#endif - -public: - // construction/destruction - sound_stream_input(); - - // initialization - void init(sound_stream &stream, u32 index, char const *tag_base, sound_stream_output *resampler); - - // no copying allowed - sound_stream_input(sound_stream_input const &src) = delete; - sound_stream_input &operator=(sound_stream_input const &rhs) = delete; - - // simple getters - bool valid() const { return (m_native_source != nullptr); } - sound_stream &owner() const { sound_assert(valid()); return *m_owner; } - sound_stream_output &source() const { sound_assert(valid()); return *m_native_source; } - u32 index() const { return m_index; } - stream_buffer::sample_t gain() const { return m_gain; } - stream_buffer::sample_t user_gain() const { return m_user_gain; } - - // simple setters - void set_gain(float gain) { m_gain = gain; } - void set_user_gain(float gain) { m_user_gain = gain; } - - // return a friendly name - std::string name() const; - - // connect the source - void set_source(sound_stream_output *source); - - // update and return an reading view - read_stream_view update(attotime start, attotime end); - - // tell inputs to apply sample rate changes - void apply_sample_rate_changes(u32 updatenum, u32 downstream_rate); - -private: - // internal state - sound_stream *m_owner; // pointer to the owning stream - sound_stream_output *m_native_source; // pointer to the native sound_stream_output - sound_stream_output *m_resampler_source; // pointer to the resampler output - u32 m_index; // input index within the stream - stream_buffer::sample_t m_gain; // gain to apply to this input - stream_buffer::sample_t m_user_gain; // user-controlled gain to apply to this input -}; - - -// ======================> stream_update_delegate - -// new-style callback -using stream_update_delegate = delegate<void (sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)>; - +using stream_update_delegate = delegate<void (sound_stream &stream)>; +class audio_effect; +class audio_resampler; // ======================> sound_stream_flags @@ -592,86 +108,200 @@ enum sound_stream_flags : u32 // specify that updates should be forced to one sample at a time, in real time // this implicitly creates a timer that runs at the stream's output frequency // so only use when strictly necessary - STREAM_SYNCHRONOUS = 0x01, - - // specify that input streams should not be resampled; stream update handler - // must be able to accommodate multiple strams of differing input rates - STREAM_DISABLE_INPUT_RESAMPLING = 0x02 + STREAM_SYNCHRONOUS = 0x01 }; +namespace emu::detail { + template<typename S> class output_buffer_interleaved { + public: + output_buffer_interleaved(u32 buffer_size, u32 channels); + + void set_buffer_size(u32 buffer_size); + + u32 channels() const { return m_channels; } + u64 sync_sample() const { return m_sync_sample; } + void set_sync_sample(u64 sample) { m_sync_sample = sample; } + u64 write_sample() const { return m_sync_sample + m_write_position - m_sync_position; } + void prepare_space(u32 samples); + void commit(u32 samples); + void sync(); + + void ensure_size(u32 buffer_size); + void set_history(u32 history); + + u32 available_samples() const { return m_write_position - m_sync_position; } + S *ptrw(u32 channel, s32 index) { return &m_buffer[(m_write_position + index) * m_channels + channel]; } + const S *ptrw(u32 channel, s32 index) const { return &m_buffer[(m_write_position + index) * m_channels + channel]; } + const S *ptrs(u32 channel, s32 index) const { return &m_buffer[(m_sync_position + index) * m_channels + channel]; } + + private: + std::vector<S> m_buffer; + u64 m_sync_sample; + u32 m_write_position; + u32 m_sync_position; + u32 m_history; + u32 m_channels; + }; + + template<typename S> class output_buffer_flat { + public: + output_buffer_flat(u32 buffer_size, u32 channels); + + void set_buffer_size(u32 buffer_size); + + u32 channels() const { return m_channels; } + u64 sync_sample() const { return m_sync_sample; } + void set_sync_sample(u64 sample) { m_sync_sample = sample; } + u64 write_sample() const { return m_sync_sample + m_write_position - m_sync_position; } + + void prepare_space(u32 samples); + void commit(u32 samples); + void sync(); + + void ensure_size(u32 buffer_size); + void set_history(u32 history); + + void resample(u32 previous_rate, u32 next_rate, attotime sync_time, attotime now); + + void register_save_state(device_t &device, const char *id1, const char *id2); + + u32 available_samples() const { return m_write_position - m_sync_position; } + S *ptrw(u32 channel, s32 index) { return &m_buffer[channel][m_write_position + index]; } + const S *ptrw(u32 channel, s32 index) const { return &m_buffer[channel][m_write_position + index]; } + const S *ptrs(u32 channel, s32 index) const { return &m_buffer[channel][m_sync_position + index]; } + + private: + std::vector<std::vector<S>> m_buffer; + u64 m_sync_sample; + u32 m_write_position; + u32 m_sync_position; + u32 m_history; + u32 m_channels; + }; +} // ======================> sound_stream class sound_stream { +public: friend class sound_manager; + using sample_t = float; - // private common constructopr - sound_stream(device_t &device, u32 inputs, u32 outputs, u32 output_base, u32 sample_rate, sound_stream_flags flags); - -public: // construction/destruction - sound_stream(device_t &device, u32 inputs, u32 outputs, u32 output_base, u32 sample_rate, stream_update_delegate callback, sound_stream_flags flags = STREAM_DEFAULT_FLAGS); + sound_stream(device_t &device, u32 inputs, u32 outputs, u32 sample_rate, stream_update_delegate callback, sound_stream_flags flags = sound_stream_flags::STREAM_DEFAULT_FLAGS); virtual ~sound_stream(); // simple getters - sound_stream *next() const { return m_next; } device_t &device() const { return m_device; } std::string name() const { return m_name; } - bool input_adaptive() const { return m_input_adaptive || m_synchronous; } + bool input_adaptive() const { return m_input_adaptive; } bool output_adaptive() const { return m_output_adaptive; } bool synchronous() const { return m_synchronous; } - bool resampling_disabled() const { return m_resampling_disabled; } + bool is_active() const { return m_sample_rate != 0; } // input and output getters - u32 input_count() const { return m_input.size(); } - u32 output_count() const { return m_output.size(); } - u32 output_base() const { return m_output_base; } - sound_stream_input &input(int index) { sound_assert(index >= 0 && index < m_input.size()); return m_input[index]; } - sound_stream_output &output(int index) { sound_assert(index >= 0 && index < m_output.size()); return m_output[index]; } + u32 input_count() const { return m_input_count; } + u32 output_count() const { return m_output_count; } // sample rate and timing getters - u32 sample_rate() const { return (m_pending_sample_rate != SAMPLE_RATE_INVALID) ? m_pending_sample_rate : m_sample_rate; } - attotime sample_time() const { return m_output[0].end_time(); } - attotime sample_period() const { return attotime(0, sample_period_attoseconds()); } - attoseconds_t sample_period_attoseconds() const { return (m_sample_rate != SAMPLE_RATE_INVALID) ? HZ_TO_ATTOSECONDS(m_sample_rate) : ATTOSECONDS_PER_SECOND; } - - // set the sample rate of the stream; will kick in at the next global update + u32 sample_rate() const { return m_sample_rate; } + attotime sample_period() const { return attotime::from_hz(m_sample_rate); } + + // sample id and timing of the first and last sample of the current update block, and first of the next sample block + u64 start_index() const { return m_output_buffer.write_sample(); } + u64 end_index() const { return m_output_buffer.write_sample() + samples(); } + attotime start_time() const { return sample_to_time(start_index()); } + attotime end_time() const { return sample_to_time(end_index()); } + + // convert from absolute sample index to time + attotime sample_to_time(u64 index) const; + + // gain management + float user_output_gain() const { return m_user_output_gain; } + void set_user_output_gain(float gain) { update(); m_user_output_gain = gain; } + float user_output_gain(s32 output) const { return m_user_output_channel_gain[output]; } + void set_user_output_gain(s32 output, float gain) { update(); m_user_output_channel_gain[output] = gain; } + + float input_gain(s32 input) const { return m_input_channel_gain[input]; } + void set_input_gain(s32 input, float gain) { update(); m_input_channel_gain[input] = gain; } + void apply_input_gain(s32 input, float gain) { update(); m_input_channel_gain[input] *= gain; } + float output_gain(s32 output) const { return m_output_channel_gain[output]; } + void set_output_gain(s32 output, float gain) { update(); m_output_channel_gain[output] = gain; } + void apply_output_gain(s32 output, float gain) { update(); m_output_channel_gain[output] *= gain; } + + // set the sample rate of the stream void set_sample_rate(u32 sample_rate); - // connect the output 'outputnum' of given input_stream to this stream's input 'inputnum' - void set_input(int inputnum, sound_stream *input_stream, int outputnum = 0, float gain = 1.0f); - // force an update to the current time void update(); - // force an update to the current time, returning a view covering the given time period - read_stream_view update_view(attotime start, attotime end, u32 outputnum = 0); + // number of samples to handle + s32 samples() const { return m_samples_to_update; } - // apply any pending sample rate changes; should only be called by the sound manager - void apply_sample_rate_changes(u32 updatenum, u32 downstream_rate); + // write a sample to the buffer + void put(s32 output, s32 index, sample_t sample) { *m_output_buffer.ptrw(output, index) = sample; } -#if (SOUND_DEBUG) - // print one level of the sound graph and recursively tell our inputs to do the same - void print_graph_recursive(int indent, int index); -#endif + // write a sample to the buffer, clamping to +/- the clamp value + void put_clamp(s32 output, s32 index, sample_t sample, sample_t clamp = 1.0) { put(output, index, std::clamp(sample, -clamp, clamp)); } -protected: - // protected state - std::string m_name; // name of this stream + // write a sample to the buffer, converting from an integer with the given maximum + void put_int(s32 output, s32 index, s32 sample, s32 max) { put(output, index, double(sample)/max); } + + // write a sample to the buffer, converting from an integer with the given maximum + void put_int_clamp(s32 output, s32 index, s32 sample, s32 maxclamp) { put_int(output, index, std::clamp(sample, -maxclamp, maxclamp-1), maxclamp); } + + // safely add a sample to the buffer + void add(s32 output, s32 index, sample_t sample) { *m_output_buffer.ptrw(output, index) += sample; } + + // add a sample to the buffer, converting from an integer with the given maximum + void add_int(s32 output, s32 index, s32 sample, s32 max) { add(output, index, double(sample)/max); } + + // fill part of the view with the given value + void fill(s32 output, sample_t value, s32 start, s32 count) { std::fill(m_output_buffer.ptrw(output, start), m_output_buffer.ptrw(output, start+count), value); } + void fill(s32 output, sample_t value, s32 start) { std::fill(m_output_buffer.ptrw(output, start), m_output_buffer.ptrw(output, samples()), value); } + void fill(s32 output, sample_t value) { std::fill(m_output_buffer.ptrw(output, 0), m_output_buffer.ptrw(output, samples()), value); } + + // copy data from the input + void copy(s32 output, s32 input, s32 start, s32 count) { std::copy(m_input_buffer[input].begin() + start, m_input_buffer[input].begin() + start + count, m_output_buffer.ptrw(output, start)); } + void copy(s32 output, s32 input, s32 start) { std::copy(m_input_buffer[input].begin() + start, m_input_buffer[input].begin() + samples(), m_output_buffer.ptrw(output, start)); } + void copy(s32 output, s32 input) { std::copy(m_input_buffer[input].begin(), m_input_buffer[input].begin() + samples(), m_output_buffer.ptrw(output, 0)); } + + // fetch a sample from the input buffer + sample_t get(s32 input, s32 index) const { return m_input_buffer[input][index]; } + + // fetch a sample from the output buffer + sample_t get_output(s32 output, s32 index) const { return *m_output_buffer.ptrw(output, index); } + + void add_bw_route(sound_stream *source, int output, int input, float gain); + void add_fw_route(sound_stream *target, int input, int output); + std::vector<sound_stream *> sources() const; + std::vector<sound_stream *> targets() const; + + bool set_route_gain(sound_stream *source, int source_channel, int target_channel, float gain); private: - // perform most of the initialization here - void init_common(u32 inputs, u32 outputs, u32 sample_rate, sound_stream_flags flags); + struct route_bw { + sound_stream *m_source; + int m_output; + int m_input; + float m_gain; + const audio_resampler *m_resampler; - // if the sample rate has changed, this gets called to update internals - void sample_rate_changed(); + route_bw(sound_stream *source, int output, int input, float gain) : m_source(source), m_output(output), m_input(input), m_gain(gain), m_resampler(nullptr) {} + }; + + struct route_fw { + sound_stream *m_target; + int m_input; + int m_output; + + route_fw(sound_stream *target, int input, int output) : m_target(target), m_input(input), m_output(output) {} + }; - // handle updates after a save state load - void postload(); - // handle updates before a save state load - void presave(); + // perform most of the initialization here + void init(); // re-print the synchronization timer void reprime_sync_timer(); @@ -679,68 +309,58 @@ private: // timer callback for synchronous streams void sync_update(s32); - // return a view of 0 data covering the given time period - read_stream_view empty_view(attotime start, attotime end); + void update_nodeps(); + void sync(attotime now); + u64 get_current_sample_index() const; + void do_update(); + + bool frequency_is_solved() const { return (!(m_input_adaptive || m_output_adaptive)) || m_sample_rate != 0; } + bool try_solving_frequency(); + void register_state(); + void add_dependants(std::vector<sound_stream *> &deps); + void compute_dependants(); + void create_resamplers(); + void lookup_history_sizes(); + u32 get_history_for_bw_route(const sound_stream *source, u32 channel) const; + void internal_set_sample_rate(u32 sample_rate); + + std::string m_name; // name of this stream + std::string m_state_tag; // linking information device_t &m_device; // owning device - sound_stream *m_next; // next stream in the chain + std::vector<route_bw> m_bw_routes; + std::vector<route_fw> m_fw_routes; + std::vector<sound_stream *> m_dependant_streams; + + // buffers + std::vector<std::vector<sample_t>> m_input_buffer; + emu::detail::output_buffer_flat<sample_t> m_output_buffer; + attotime m_sync_time; + s32 m_samples_to_update; + + // gains + std::vector<float> m_input_channel_gain; + std::vector<float> m_output_channel_gain; + std::vector<float> m_user_output_channel_gain; + float m_user_output_gain; // general information - u32 m_sample_rate; // current live sample rate - u32 m_pending_sample_rate; // pending sample rate for dynamic changes - u32 m_last_sample_rate_update; // update number of last sample rate change + u32 m_sample_rate; // current sample rate + u32 m_input_count; + u32 m_output_count; bool m_input_adaptive; // adaptive stream that runs at the sample rate of its input bool m_output_adaptive; // adaptive stream that runs at the sample rate of its output bool m_synchronous; // synchronous stream that runs at the rate of its input - bool m_resampling_disabled; // is resampling of input streams disabled? + bool m_started; + bool m_in_update; emu_timer *m_sync_timer; // update timer for synchronous streams - attotime m_last_update_end_time; // last end_time() in update - - // input information - std::vector<sound_stream_input> m_input; // list of streams we directly depend upon - std::vector<read_stream_view> m_input_view; // array of output views for passing to the callback - std::vector<std::unique_ptr<sound_stream>> m_resampler_list; // internal list of resamplers - stream_buffer m_empty_buffer; // empty buffer for invalid inputs - - // output information - u32 m_output_base; // base index of our outputs, relative to our device - std::vector<sound_stream_output> m_output; // list of streams which directly depend upon us - std::vector<write_stream_view> m_output_view; // array of output views for passing to the callback - // callback information - stream_update_delegate m_callback_ex; // extended callback function -}; - - -// ======================> default_resampler_stream - -class default_resampler_stream : public sound_stream -{ -public: - // construction/destruction - default_resampler_stream(device_t &device); - - // update handler - void resampler_sound_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs); - -private: - // internal state - u32 m_max_latency; + stream_update_delegate m_callback; // update callback function }; -// ======================> sound_manager - -// structure describing an indexed mixer -struct mixer_input -{ - device_mixer_interface *mixer; // owning device interface - sound_stream * stream; // stream within the device - int inputnum; // input on the stream -}; - class sound_manager { friend class sound_stream; @@ -755,6 +375,32 @@ class sound_manager static const attotime STREAMS_UPDATE_ATTOTIME; public: + using sample_t = sound_stream::sample_t; + + enum { + RESAMPLER_LOFI, + RESAMPLER_HQ + }; + + struct mapping { + struct node_mapping { + u32 m_node; + float m_db; + bool m_is_system_default; + }; + + struct channel_mapping { + u32 m_guest_channel; + u32 m_node; + u32 m_node_channel; + float m_db; + bool m_is_system_default; + }; + sound_io_device *m_dev; + std::vector<node_mapping> m_node_mappings; + std::vector<channel_mapping> m_channel_mappings; + }; + static constexpr int STREAMS_UPDATE_FREQUENCY = 50; // construction/destruction @@ -763,14 +409,13 @@ public: // getters running_machine &machine() const { return m_machine; } - int attenuation() const { return m_attenuation; } const std::vector<std::unique_ptr<sound_stream>> &streams() const { return m_stream_list; } - attotime last_update() const { return m_last_update; } - int sample_count() const { return m_samples_this_update; } int unique_id() { return m_unique_id++; } - stream_buffer::sample_t compressor_scale() const { return m_compressor_scale; } - // allocate a new stream with a new-style callback + const typename osd::audio_info &get_osd_info() const { return m_osd_info; } + const std::vector<mapping> &get_mappings() const { return m_mappings; } + + // allocate a new stream sound_stream *stream_alloc(device_t &device, u32 inputs, u32 outputs, u32 sample_rate, stream_update_delegate callback, sound_stream_flags flags); // WAV recording @@ -778,9 +423,21 @@ public: bool start_recording(); bool start_recording(std::string_view filename); void stop_recording(); - - // set the global OSD attenuation level - void set_attenuation(float attenuation); + u32 outputs_count() const { return m_outputs_count; } + + // manage the sound_io mapping and volume configuration + void config_add_sound_io_connection_node(sound_io_device *dev, std::string name, float db); + void config_add_sound_io_connection_default(sound_io_device *dev, float db); + void config_remove_sound_io_connection_node(sound_io_device *dev, std::string name); + void config_remove_sound_io_connection_default(sound_io_device *dev); + void config_set_volume_sound_io_connection_node(sound_io_device *dev, std::string name, float db); + void config_set_volume_sound_io_connection_default(sound_io_device *dev, float db); + void config_add_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel, float db); + void config_add_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel, float db); + void config_remove_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel); + void config_remove_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel); + void config_set_volume_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel, float db); + void config_set_volume_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel, float db); // mute sound for one of various independent reasons bool muted() const { return bool(m_muted); } @@ -791,22 +448,132 @@ public: void debugger_mute(bool turn_off) { mute(turn_off, MUTE_REASON_DEBUGGER); } void system_mute(bool turn_off) { mute(turn_off, MUTE_REASON_SYSTEM); } - // return information about the given mixer input, by index - bool indexed_mixer_input(int index, mixer_input &info) const; + // master gain + float master_gain() const { return m_master_gain; } + void set_master_gain(float gain) { m_master_gain = gain; } + + void before_devices_init(); + void after_devices_init(); + void postload(); - // fill the given buffer with 16-bit stereo audio samples - void samples(s16 *buffer); + void input_get(int m_id, sound_stream &stream); + void output_push(int m_id, sound_stream &stream); + const audio_resampler *get_resampler(u32 fs, u32 ft); + + u32 effect_chains() const { return m_speakers.size(); } + std::string effect_chain_tag(s32 index) const; + std::vector<audio_effect *> effect_chain(s32 index) const; + std::vector<audio_effect *> default_effect_chain() const; + void default_effect_changed(u32 entry); + + void mapping_update(); + + const char *resampler_type_names(u32 type) const; + + u32 resampler_type() const { return m_resampler_type; } + double resampler_hq_latency() const { return m_resampler_hq_latency; } + u32 resampler_hq_length() const { return m_resampler_hq_length; } + u32 resampler_hq_phases() const { return m_resampler_hq_phases; } + + void set_resampler_type(u32 type); + void set_resampler_hq_latency(double latency); + void set_resampler_hq_length(u32 length); + void set_resampler_hq_phases(u32 phases); private: + struct effect_step { + std::unique_ptr<audio_effect> m_effect; + emu::detail::output_buffer_flat<sample_t> m_buffer; + effect_step(u32 buffer_size, u32 channels); + }; + + struct mixing_step { + enum : u32 { CLEAR, COPY, ADD }; + u32 m_mode; + u32 m_osd_index; + u32 m_osd_channel; + u32 m_device_index; + u32 m_device_channel; + float m_linear_volume; + }; + + struct speaker_info { + speaker_device &m_dev; + sound_stream *m_stream; + u32 m_channels; + u32 m_first_output; + + emu::detail::output_buffer_flat<sample_t> m_buffer; + + std::vector<effect_step> m_effects; + + speaker_info(speaker_device &dev, u32 rate, u32 first_output); + }; + + struct microphone_info { + microphone_device &m_dev; + u32 m_channels; + + std::vector<mixing_step> m_input_mixing_steps; // actions to take to fill the buffer + std::vector<sample_t> m_buffer; + microphone_info(microphone_device &dev); + }; + + struct osd_stream { + u32 m_id; + u32 m_node; + std::string m_node_name; + u32 m_channels; + u32 m_rate; + u32 m_unused_channels_mask; + bool m_is_system_default; + bool m_is_channel_mapping; + sound_io_device *m_dev; + std::vector<float> m_volumes; + + osd_stream(u32 node, std::string node_name, u32 channels, u32 rate, bool is_system_default, sound_io_device *dev) : + m_id(0), + m_node(node), + m_node_name(node_name), + m_channels(channels), + m_rate(rate), + m_unused_channels_mask(util::make_bitmask<u32>(channels)), + m_is_system_default(is_system_default), + m_is_channel_mapping(false), + m_dev(dev) + { } + }; + + struct osd_input_stream : public osd_stream { + emu::detail::output_buffer_interleaved<s16> m_buffer; + osd_input_stream(u32 node, std::string node_name, u32 channels, u32 rate, bool is_system_default, sound_io_device *dev) : + osd_stream(node, node_name, channels, rate, is_system_default, dev), + m_buffer(rate, channels) + { } + }; + + struct osd_output_stream : public osd_stream { + u64 m_last_sync; + u32 m_samples; + std::vector<s16> m_buffer; + osd_output_stream(u32 node, std::string node_name, u32 channels, u32 rate, bool is_system_default, sound_io_device *dev) : + osd_stream(node, node_name, channels, rate, is_system_default, dev), + m_last_sync(0), + m_samples(0), + m_buffer(channels*rate, 0) + { } + }; + + struct config_mapping { + std::string m_name; + // "" to indicates default node + std::vector<std::pair<std::string, float>> m_node_mappings; + std::vector<std::tuple<u32, std::string, u32, float>> m_channel_mappings; + }; + // set/reset the mute state for the given reason void mute(bool mute, u8 reason); - // helper to remove items from the orphan list - void recursive_remove_stream_from_orphan_list(sound_stream *stream); - - // apply pending sample rate changes - void apply_sample_rate_changes(); - // reset all sound chips void reset(); @@ -818,39 +585,83 @@ private: void config_load(config_type cfg_type, config_level cfg_lvl, util::xml::data_node const *parentnode); void config_save(config_type cfg_type, util::xml::data_node *parentnode); - // helper to adjust scale factor toward a goal - stream_buffer::sample_t adjust_toward_compressor_scale(stream_buffer::sample_t curscale, stream_buffer::sample_t prevsample, stream_buffer::sample_t rawsample); - // periodic sound update, called STREAMS_UPDATE_FREQUENCY per second - void update(s32 param = 0); + void update(s32); + + // handle mixing mapping update if needed + static std::vector<u32> find_channel_mapping(const std::array<double, 3> &position, const osd::audio_info::node_info *node); + void startup_cleanups(); + void streams_update(); + template<bool is_output, typename S> void apply_osd_changes(std::vector<S> &streams); + void osd_information_update(); + void generate_mapping(); + void update_osd_streams(); + void update_osd_input(); + void speakers_update(attotime endtime); + void rebuild_all_resamplers(); + void run_effects(); + + u64 rate_and_time_to_index(attotime time, u32 sample_rate) const; + u64 rate_and_last_sync_to_index(u32 sample_rate) const { return rate_and_time_to_index(m_last_sync_time, sample_rate); } + + // manage the sound_io mapping and volume configuration, + // but don't change generation because we're in the update process + + config_mapping &config_get_sound_io(sound_io_device *dev); + void internal_config_add_sound_io_connection_node(sound_io_device *dev, std::string name, float db); + void internal_config_add_sound_io_connection_default(sound_io_device *dev, float db); + void internal_config_remove_sound_io_connection_node(sound_io_device *dev, std::string name); + void internal_config_remove_sound_io_connection_default(sound_io_device *dev); + void internal_config_set_volume_sound_io_connection_node(sound_io_device *dev, std::string name, float db); + void internal_config_set_volume_sound_io_connection_default(sound_io_device *dev, float db); + void internal_config_add_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel, float db); + void internal_config_add_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel, float db); + void internal_config_remove_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel); + void internal_config_remove_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel); + void internal_config_set_volume_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel, float db); + void internal_config_set_volume_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel, float db); + // internal state - running_machine &m_machine; // reference to the running machine - emu_timer *m_update_timer; // timer that runs the update function - std::vector<std::reference_wrapper<speaker_device> > m_speakers; - - u32 m_update_number; // current update index; used for sample rate updates - attotime m_last_update; // time of the last update - u32 m_finalmix_leftover; // leftover samples in the final mix - u32 m_samples_this_update; // number of samples this update - std::vector<s16> m_finalmix; // final mix, in 16-bit signed format - std::vector<stream_buffer::sample_t> m_leftmix; // left speaker mix, in native format - std::vector<stream_buffer::sample_t> m_rightmix; // right speaker mix, in native format - - stream_buffer::sample_t m_compressor_scale; // current compressor scale factor - int m_compressor_counter; // compressor update counter for backoff - bool m_compressor_enabled; // enable compressor (it will still be calculated for detecting overdrive) - - u8 m_muted; // bitmask of muting reasons - bool m_nosound_mode; // true if we're in "nosound" mode - int m_attenuation; // current attentuation level (at the OSD) - int m_unique_id; // unique ID used for stream identification - util::wav_file_ptr m_wavfile; // WAV file for streaming + running_machine &m_machine; // reference to the running machine + emu_timer *m_update_timer; // timer that runs the update function + attotime m_last_sync_time; + std::vector<speaker_info> m_speakers; + std::vector<microphone_info> m_microphones; + + std::vector<s16> m_record_buffer; // pre-effects speaker samples for recording + u32 m_record_samples; // how many samples for the next update + osd::audio_info m_osd_info; // current state of the osd information + std::vector<mapping> m_mappings; // current state of the mappings + std::vector<osd_input_stream> m_osd_input_streams; // currently active osd streams + std::vector<osd_output_stream> m_osd_output_streams; // currently active osd streams + std::vector<mixing_step> m_output_mixing_steps; // actions to take to fill the osd streams buffers + std::vector<config_mapping> m_configs; // mapping user configuration + + std::mutex m_effects_mutex; + std::condition_variable m_effects_condition; + std::unique_ptr<std::thread> m_effects_thread; + std::vector<std::unique_ptr<audio_effect>> m_default_effects; + bool m_effects_done; + + float m_master_gain; + + std::map<std::pair<u32, u32>, std::unique_ptr<audio_resampler>> m_resamplers; + + u8 m_muted; // bitmask of muting reasons + bool m_nosound_mode; // true if we're in "nosound" mode + int m_unique_id; // unique ID used for stream identification + util::wav_file_ptr m_wavfile; // WAV file for streaming // streams data std::vector<std::unique_ptr<sound_stream>> m_stream_list; // list of streams - std::map<sound_stream *, u8> m_orphan_stream_list; // list of orphaned streams - bool m_first_reset; // is this our first reset? + std::vector<sound_stream *> m_ordered_streams; // Streams in update order + u32 m_outputs_count; + + // resampler data + u32 m_resampler_type; + double m_resampler_hq_latency; + u32 m_resampler_hq_length, m_resampler_hq_phases; }; diff --git a/src/emu/speaker.cpp b/src/emu/speaker.cpp index 33b1e8d329a..d57f9119c24 100644 --- a/src/emu/speaker.cpp +++ b/src/emu/speaker.cpp @@ -5,6 +5,7 @@ speaker.cpp Speaker output sound device. + Microphone input sound device. ***************************************************************************/ @@ -14,210 +15,80 @@ -//************************************************************************** -// GLOBAL VARIABLES -//************************************************************************** - -// device type definition DEFINE_DEVICE_TYPE(SPEAKER, speaker_device, "speaker", "Speaker") - - - -//************************************************************************** -// LIVE SPEAKER DEVICE -//************************************************************************** - -//------------------------------------------------- -// speaker_device - constructor -//------------------------------------------------- - -speaker_device::speaker_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) - : device_t(mconfig, SPEAKER, tag, owner, clock) - , device_mixer_interface(mconfig, *this) - , m_x(0.0) - , m_y(0.0) - , m_z(0.0) - , m_pan(0.0) - , m_defpan(0.0) - , m_current_max(0) - , m_samples_this_bucket(0) +DEFINE_DEVICE_TYPE(MICROPHONE, microphone_device, "microphone", "Microphone") + +const sound_io_device::position_name_mapping sound_io_device::position_name_mappings[] = { + { 0.0, 0.0, 1.0, "Front center" }, + { -0.2, 0.0, 1.0, "Front left" }, + { 0.0, -0.5, 1.0, "Front floor" }, + { 0.2, 0.0, 1.0, "Front right" }, + { 0.0, 0.0, -0.5, "Rear center" }, + { -0.2, 0.0, -0.5, "Rear left" }, + { 0.2, 0.0, -0.5, "Read right" }, + { 0.0, 0.0, -0.1, "Headrest center" }, + { -0.1, 0.0, -0.1, "Headrest left" }, + { 0.1, 0.0, -0.1, "Headrest right" }, + { 0.0, -0.5, 0.0, "Seat" }, + { 0.0, -0.2, 0.1, "Backrest" }, + { } +}; + +std::string sound_io_device::get_position_name(u32 channel) const { + for(unsigned int i = 0; position_name_mappings[i].m_name; i++) + if(m_positions[channel][0] == position_name_mappings[i].m_x && m_positions[channel][1] == position_name_mappings[i].m_y && m_positions[channel][2] == position_name_mappings[i].m_z) + return position_name_mappings[i].m_name; + return util::string_format("#%d", channel); } - -//------------------------------------------------- -// ~speaker_device - destructor -//------------------------------------------------- - -speaker_device::~speaker_device() +sound_io_device &sound_io_device::set_position(u32 channel, double x, double y, double z) { + if(channel >= m_positions.size()) + fatalerror("%s: Requested channel %d on %d channel device\n", tag(), channel, m_positions.size()); + m_positions[channel][0] = x; + m_positions[channel][1] = y; + m_positions[channel][2] = z; + return *this; } - -//------------------------------------------------- -// set_position - set speaker position -//------------------------------------------------- - -speaker_device &speaker_device::set_position(double x, double y, double z) +sound_io_device::sound_io_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 channels) : + device_t(mconfig, type, tag, owner, 0), + device_sound_interface(mconfig, *this), + m_positions(channels ? channels : 1) { - // as mentioned in the header file, y and z params currently have no effect - m_x = x; - m_y = y; - m_z = z; - - // hard pan to left - if (m_x < 0.0) - set_pan(-1.0f); - - // hard pan to right - else if (m_x > 0.0) - set_pan(1.0f); - - // center (mono) - else - set_pan(0.0f); - - m_defpan = m_pan; - return *this; } -//------------------------------------------------- -// mix - mix in samples from the speaker's stream -//------------------------------------------------- +sound_io_device::~sound_io_device() +{ +} -void speaker_device::mix(stream_buffer::sample_t *leftmix, stream_buffer::sample_t *rightmix, attotime start, attotime end, int expected_samples, bool suppress) +speaker_device::~speaker_device() { - // skip if no stream - if (m_mixer_stream == nullptr) - return; - - // skip if invalid range - if (start > end) - return; - - // get a view on the desired range - read_stream_view view = m_mixer_stream->update_view(start, end); - sound_assert(view.samples() >= expected_samples); - - // track maximum sample value for each 0.1s bucket - if (machine().options().speaker_report() != 0) - { - u32 samples_per_bucket = m_mixer_stream->sample_rate() / BUCKETS_PER_SECOND; - for (int sample = 0; sample < expected_samples; sample++) - { - m_current_max = std::max(m_current_max, fabsf(view.get(sample))); - if (++m_samples_this_bucket >= samples_per_bucket) - { - m_max_sample.push_back(m_current_max); - m_current_max = 0.0f; - m_samples_this_bucket = 0; - } - } - } - - // mix if sound is enabled - if (!suppress) - { - // if the speaker is hard panned to the left, send only to the left - if (m_pan == -1.0f) - for (int sample = 0; sample < expected_samples; sample++) - leftmix[sample] += view.get(sample); - - // if the speaker is hard panned to the right, send only to the right - else if (m_pan == 1.0f) - for (int sample = 0; sample < expected_samples; sample++) - rightmix[sample] += view.get(sample); - - // otherwise, send to both - else - { - const float leftpan = (m_pan <= 0.0f) ? 1.0f : 1.0f - m_pan; - const float rightpan = (m_pan >= 0.0f) ? 1.0f : 1.0f + m_pan; - - for (int sample = 0; sample < expected_samples; sample++) - { - stream_buffer::sample_t cursample = view.get(sample); - leftmix[sample] += cursample * leftpan; - rightmix[sample] += cursample * rightpan; - } - } - } } +microphone_device::~microphone_device() +{ +} -//------------------------------------------------- -// device_start - handle device startup -//------------------------------------------------- void speaker_device::device_start() { + m_stream = stream_alloc(m_positions.size(), 0, machine().sample_rate()); } +void microphone_device::device_start() +{ + m_stream = stream_alloc(0, m_positions.size(), machine().sample_rate()); +} -//------------------------------------------------- -// device_stop - cleanup and report -//------------------------------------------------- +void speaker_device::sound_stream_update(sound_stream &stream) +{ + machine().sound().output_push(m_id, stream); +} -void speaker_device::device_stop() +void microphone_device::sound_stream_update(sound_stream &stream) { - // level 1: just report if there was any clipping - // level 2: report the overall maximum, even if no clipping - // level 3: print a detailed list of all the times there was clipping - // level 4: print a detailed list of every bucket - int report = machine().options().speaker_report(); - if (report != 0) - { - m_max_sample.push_back(m_current_max); - - // determine overall maximum and number of clipped buckets - stream_buffer::sample_t overallmax = 0; - u32 clipped = 0; - for (auto &curmax : m_max_sample) - { - overallmax = std::max(overallmax, curmax); - if (curmax > stream_buffer::sample_t(1.0)) - clipped++; - } - - // levels 1 and 2 just get a summary - if (clipped != 0 || report == 2 || report == 4) - osd_printf_info("Speaker \"%s\" - max = %.5f (gain *= %.3f) - clipped in %d/%d (%d%%) buckets\n", tag(), overallmax, 1 / (overallmax ? overallmax : 1), clipped, m_max_sample.size(), clipped * 100 / m_max_sample.size()); - - // levels 3 and 4 get a full dump - if (report >= 3) - { - static char const * const s_stars = "************************************************************"; - static char const * const s_spaces = " "; - int totalstars = strlen(s_stars); - double t = 0; - if (overallmax < 1.0) - overallmax = 1.0; - int leftstars = totalstars / overallmax; - for (auto &curmax : m_max_sample) - { - if (curmax > stream_buffer::sample_t(1.0) || report == 4) - { - osd_printf_info("%6.1f: %9.5f |", t, curmax); - if (curmax == 0) - osd_printf_info("%.*s|\n", leftstars, s_spaces); - else if (curmax <= 1.0) - { - int stars = std::max(1, std::min(leftstars, int(curmax * totalstars / overallmax))); - osd_printf_info("%.*s", stars, s_stars); - int spaces = leftstars - stars; - if (spaces != 0) - osd_printf_info("%.*s", spaces, s_spaces); - osd_printf_info("|\n"); - } - else - { - int rightstars = std::max(1, std::min(totalstars, int(curmax * totalstars / overallmax)) - leftstars); - osd_printf_info("%.*s|%.*s\n", leftstars, s_stars, rightstars, s_stars); - } - } - t += 1.0 / double(BUCKETS_PER_SECOND); - } - } - } + machine().sound().input_get(m_id, stream); } diff --git a/src/emu/speaker.h b/src/emu/speaker.h index 3cb0794d998..4674c0bf08d 100644 --- a/src/emu/speaker.h +++ b/src/emu/speaker.h @@ -5,8 +5,9 @@ speaker.h Speaker output sound device. + Microphone input sound device. - Speakers have (x, y, z) coordinates in 3D space: + They have (x, y, z) coordinates in 3D space: * Observer is at position (0, 0, 0) * Positive x is to the right of the observer * Negative x is to the left of the observer @@ -15,9 +16,6 @@ * Positive z is in front of the observer * Negative z is behind the observer - Currently, MAME only considers the sign of the x coordinate (not its - magnitude), and completely ignores the y and z coordinates. - ***************************************************************************/ #ifndef MAME_EMU_SPEAKER_H @@ -32,6 +30,7 @@ // device type definition DECLARE_DEVICE_TYPE(SPEAKER, speaker_device) +DECLARE_DEVICE_TYPE(MICROPHONE, microphone_device) @@ -39,65 +38,103 @@ DECLARE_DEVICE_TYPE(SPEAKER, speaker_device) // TYPE DEFINITIONS //************************************************************************** -// ======================> speaker_device +class sound_io_device : public device_t, public device_sound_interface +{ +public: + virtual ~sound_io_device(); + + // configuration helpers + sound_io_device &set_position(u32 channel, double x, double y, double z); + sound_io_device &front_center(u32 channel = 0) { return set_position(channel, 0.0, 0.0, 1.0); } + sound_io_device &front_left(u32 channel = 0) { return set_position(channel, -0.2, 0.0, 1.0); } + sound_io_device &front_floor(u32 channel = 0) { return set_position(channel, 0.0, -0.5, 1.0); } + sound_io_device &front_right(u32 channel = 0) { return set_position(channel, 0.2, 0.0, 1.0); } + sound_io_device &rear_center(u32 channel = 0) { return set_position(channel, 0.0, 0.0, -0.5); } + sound_io_device &rear_left(u32 channel = 0) { return set_position(channel, -0.2, 0.0, -0.5); } + sound_io_device &rear_right(u32 channel = 0) { return set_position(channel, 0.2, 0.0, -0.5); } + sound_io_device &headrest_center(u32 channel = 0) { return set_position(channel, 0.0, 0.0, -0.1); } + sound_io_device &headrest_left(u32 channel = 0) { return set_position(channel, -0.1, 0.0, -0.1); } + sound_io_device &headrest_right(u32 channel = 0) { return set_position(channel, 0.1, 0.0, -0.1); } + sound_io_device &seat(u32 channel = 0) { return set_position(channel, 0.0, -0.5, 0.0); } + sound_io_device &backrest(u32 channel = 0) { return set_position(channel, 0.0, -0.2, 0.1); } + sound_io_device &front() { return front_left(0).front_right(1); } + sound_io_device &rear() { return rear_left(0).rear_right(1); } + sound_io_device &corners() { return front_left(0).front_right(1).rear_left(2).rear_right(3); } + int channels() const { return m_positions.size(); } + std::array<double, 3> get_position(u32 channel) const { return m_positions[channel]; } + std::string get_position_name(u32 channel) const; + + virtual bool is_output() const = 0; + void set_id(int id) { m_id = id; } + int get_id() const { return m_id; } + + sound_stream *stream() const { return m_stream; } + +protected: + struct position_name_mapping { + double m_x, m_y, m_z; + const char *m_name; + }; + + static const position_name_mapping position_name_mappings[]; -class speaker_device : public device_t, public device_mixer_interface + // configuration state + std::vector<std::array<double, 3>> m_positions; + sound_stream *m_stream; + int m_id; + + sound_io_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, double x, double y, double z) + : sound_io_device(mconfig, type, tag, owner, 1) + { + set_position(0, x, y, z); + } + sound_io_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 channels); // Collides with clock, but not important +}; + +class speaker_device : public sound_io_device { public: // construction/destruction speaker_device(const machine_config &mconfig, const char *tag, device_t *owner, double x, double y, double z) - : speaker_device(mconfig, tag, owner, 0) - { - set_position(x, y, z); - } - speaker_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock = 0); + : sound_io_device(mconfig, SPEAKER, tag, owner, x, y, z) {} + speaker_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 channels = 1) + : sound_io_device(mconfig, SPEAKER, tag, owner, channels) {} + virtual ~speaker_device(); - // configuration helpers - speaker_device &set_position(double x, double y, double z); - speaker_device &front_center() { set_position( 0.0, 0.0, 1.0); return *this; } - speaker_device &front_left() { set_position(-0.2, 0.0, 1.0); return *this; } - speaker_device &front_floor() { set_position( 0.0, -0.5, 1.0); return *this; } - speaker_device &front_right() { set_position( 0.2, 0.0, 1.0); return *this; } - speaker_device &rear_center() { set_position( 0.0, 0.0, -0.5); return *this; } - speaker_device &rear_left() { set_position(-0.2, 0.0, -0.5); return *this; } - speaker_device &rear_right() { set_position( 0.2, 0.0, -0.5); return *this; } - speaker_device &headrest_center() { set_position( 0.0, 0.0, -0.1); return *this; } - speaker_device &headrest_left() { set_position(-0.1, 0.0, -0.1); return *this; } - speaker_device &headrest_right() { set_position( 0.1, 0.0, -0.1); return *this; } - speaker_device &seat() { set_position( 0.0, -0.5, 0.0); return *this; } - speaker_device &backrest() { set_position( 0.0, -0.2, 0.1); return *this; } - - // internally for use by the sound system - void mix(stream_buffer::sample_t *leftmix, stream_buffer::sample_t *rightmix, attotime start, attotime end, int expected_samples, bool suppress); - - // user panning configuration - void set_pan(float pan) { m_pan = std::clamp(pan, -1.0f, 1.0f); } - float pan() { return m_pan; } - float defpan() { return m_defpan; } + virtual bool is_output() const override { return true; } protected: + // device-level overrides virtual void device_start() override ATTR_COLD; - virtual void device_stop() override ATTR_COLD; - // configuration state - double m_x; - double m_y; - double m_z; - float m_pan; - float m_defpan; - - // internal state - static constexpr int BUCKETS_PER_SECOND = 10; - std::vector<stream_buffer::sample_t> m_max_sample; - stream_buffer::sample_t m_current_max; - u32 m_samples_this_bucket; + virtual void sound_stream_update(sound_stream &stream) override; }; +class microphone_device : public sound_io_device +{ +public: + // construction/destruction + microphone_device(const machine_config &mconfig, const char *tag, device_t *owner, double x, double y, double z) + : sound_io_device(mconfig, MICROPHONE, tag, owner, x, y, z) {} + microphone_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 channels = 1) + : sound_io_device(mconfig, MICROPHONE, tag, owner, channels) {} + + virtual ~microphone_device(); + + virtual bool is_output() const override { return false; } + +protected: + + // device-level overrides + virtual void device_start() override ATTR_COLD; + + virtual void sound_stream_update(sound_stream &stream) override; +}; -// speaker device iterator using speaker_device_enumerator = device_type_enumerator<speaker_device>; +using microphone_device_enumerator = device_type_enumerator<microphone_device>; #endif // MAME_EMU_SPEAKER_H diff --git a/src/emu/tilemap.cpp b/src/emu/tilemap.cpp index 1aa18eef168..8c4e20e158f 100644 --- a/src/emu/tilemap.cpp +++ b/src/emu/tilemap.cpp @@ -1556,6 +1556,10 @@ void tilemap_t::draw_debug(screen_device &screen, bitmap_rgb32 &dest, u32 scroll void tilemap_t::get_info_debug(u32 col, u32 row, u8 &gfxnum, u32 &code, u32 &color) { // first map to the memory index + if (m_attributes & TILEMAP_FLIPX) + col = (m_cols - 1) - col; + if (m_attributes & TILEMAP_FLIPY) + row = (m_rows - 1) - row; tilemap_memory_index memindex = memory_index(col, row); // next invoke the get info callback diff --git a/src/emu/tilemap.h b/src/emu/tilemap.h index b6b2c566bfa..3fc1a353315 100644 --- a/src/emu/tilemap.h +++ b/src/emu/tilemap.h @@ -754,7 +754,7 @@ public: protected: // device-level overrides - virtual void device_start() override; + virtual void device_start() override ATTR_COLD; private: // devices diff --git a/src/emu/ui/uimain.h b/src/emu/ui/uimain.h index bf10957d743..e45c71f9468 100644 --- a/src/emu/ui/uimain.h +++ b/src/emu/ui/uimain.h @@ -13,6 +13,8 @@ #pragma once +#include <functional> + /*************************************************************************** TYPE DEFINITIONS @@ -35,6 +37,8 @@ public: virtual void menu_reset() { } + virtual bool set_ui_event_handler(std::function<bool ()> &&handler) { return false; } + template <typename Format, typename... Params> void popup_time(int seconds, Format &&fmt, Params &&... args); protected: diff --git a/src/emu/uiinput.cpp b/src/emu/uiinput.cpp index 138bf67055d..12ba8a08bb0 100644 --- a/src/emu/uiinput.cpp +++ b/src/emu/uiinput.cpp @@ -36,19 +36,11 @@ enum ui_input_manager::ui_input_manager(running_machine &machine) : m_machine(machine) , m_presses_enabled(true) - , m_current_mouse_target(nullptr) - , m_current_mouse_x(-1) - , m_current_mouse_y(-1) - , m_current_mouse_down(false) - , m_current_mouse_field(nullptr) , m_events_start(0) , m_events_end(0) { std::fill(std::begin(m_next_repeat), std::end(m_next_repeat), 0); std::fill(std::begin(m_seqpressed), std::end(m_seqpressed), 0); - - // add a frame callback to poll inputs - machine.add_notifier(MACHINE_NOTIFY_FRAME, machine_notify_delegate(&ui_input_manager::frame_update, this)); } @@ -58,12 +50,12 @@ ui_input_manager::ui_input_manager(running_machine &machine) ***************************************************************************/ /*------------------------------------------------- - frame_update - looks through pressed - input as per events pushed our way and posts + check_ui_inputs - looks through pressed input + as per events pushed our way and posts corresponding IPT_UI_* events -------------------------------------------------*/ -void ui_input_manager::frame_update() +void ui_input_manager::check_ui_inputs() { // update the state of all the UI keys for (ioport_type code = ioport_type(IPT_UI_FIRST + 1); code < IPT_UI_LAST; ++code) @@ -80,22 +72,6 @@ void ui_input_manager::frame_update() m_seqpressed[code] = false; } } - - // perform mouse hit testing - ioport_field *mouse_field = m_current_mouse_down ? find_mouse_field() : nullptr; - if (m_current_mouse_field != mouse_field) - { - // clear the old field if there was one - if (m_current_mouse_field != nullptr) - m_current_mouse_field->set_value(0); - - // set the new field if it exists and isn't already being pressed - if (mouse_field != nullptr && !mouse_field->digital_value()) - mouse_field->set_value(1); - - // update internal state - m_current_mouse_field = mouse_field; - } } @@ -106,37 +82,6 @@ void ui_input_manager::frame_update() bool ui_input_manager::push_event(ui_event evt) { - // some pre-processing (this is an icky place to do this stuff!) - switch (evt.event_type) - { - case ui_event::type::MOUSE_MOVE: - m_current_mouse_target = evt.target; - m_current_mouse_x = evt.mouse_x; - m_current_mouse_y = evt.mouse_y; - break; - - case ui_event::type::MOUSE_LEAVE: - if (m_current_mouse_target == evt.target) - { - m_current_mouse_target = nullptr; - m_current_mouse_x = -1; - m_current_mouse_y = -1; - } - break; - - case ui_event::type::MOUSE_DOWN: - m_current_mouse_down = true; - break; - - case ui_event::type::MOUSE_UP: - m_current_mouse_down = false; - break; - - default: - /* do nothing */ - break; - } - // is the queue filled up? if ((m_events_end + 1) % std::size(m_events) == m_events_start) return false; @@ -186,64 +131,12 @@ void ui_input_manager::reset() } -/*------------------------------------------------- - find_mouse - retrieves the current - location of the mouse --------------------------------------------------*/ - -render_target *ui_input_manager::find_mouse(s32 *x, s32 *y, bool *button) const -{ - if (x != nullptr) - *x = m_current_mouse_x; - if (y != nullptr) - *y = m_current_mouse_y; - if (button != nullptr) - *button = m_current_mouse_down; - return m_current_mouse_target; -} - - -/*------------------------------------------------- - find_mouse_field - retrieves the input field - the mouse is currently pointing at --------------------------------------------------*/ - -ioport_field *ui_input_manager::find_mouse_field() const -{ - // map the point and determine what was hit - if (m_current_mouse_target != nullptr) - { - ioport_port *port = nullptr; - ioport_value mask; - float x, y; - if (m_current_mouse_target->map_point_input(m_current_mouse_x, m_current_mouse_y, port, mask, x, y)) - { - if (port != nullptr) - return port->field(mask); - } - } - return nullptr; -} - - /*************************************************************************** USER INTERFACE SEQUENCE READING ***************************************************************************/ /*------------------------------------------------- - pressed - return true if a key down - for the given user interface sequence is - detected --------------------------------------------------*/ - -bool ui_input_manager::pressed(int code) -{ - return pressed_repeat(code, 0); -} - - -/*------------------------------------------------- pressed_repeat - return true if a key down for the given user interface sequence is detected, or if autorepeat at the given speed @@ -272,7 +165,7 @@ bool ui_input_manager::pressed_repeat(int code, int speed) /* if this is an autorepeat case, set a 1x delay and leave pressed = 1 */ else if (speed > 0 && (osd_ticks() + tps - m_next_repeat[code]) >= tps) { - // In the autorepeatcase, we need to double check the key is still pressed + // In the autorepeat case, we need to double-check the key is still pressed // as there can be a delay between the key polling and our processing of the event m_seqpressed[code] = machine().ioport().type_pressed(ioport_type(code)); pressed = (m_seqpressed[code] == SEQ_PRESSED_TRUE); @@ -319,105 +212,84 @@ void ui_input_manager::push_window_defocus_event(render_target *target) } /*------------------------------------------------- - push_mouse_move_event - pushes a mouse - move event to the specified render_target --------------------------------------------------*/ - -void ui_input_manager::push_mouse_move_event(render_target *target, s32 x, s32 y) -{ - ui_event event = { ui_event::type::NONE }; - event.event_type = ui_event::type::MOUSE_MOVE; - event.target = target; - event.mouse_x = x; - event.mouse_y = y; - push_event(event); -} - -/*------------------------------------------------- - push_mouse_leave_event - pushes a - mouse leave event to the specified render_target --------------------------------------------------*/ - -void ui_input_manager::push_mouse_leave_event(render_target *target) -{ - ui_event event = { ui_event::type::NONE }; - event.event_type = ui_event::type::MOUSE_LEAVE; - event.target = target; - push_event(event); -} - -/*------------------------------------------------- - push_mouse_down_event - pushes a mouse - down event to the specified render_target --------------------------------------------------*/ - -void ui_input_manager::push_mouse_down_event(render_target *target, s32 x, s32 y) -{ - ui_event event = { ui_event::type::NONE }; - event.event_type = ui_event::type::MOUSE_DOWN; - event.target = target; - event.mouse_x = x; - event.mouse_y = y; - push_event(event); -} - -/*------------------------------------------------- - push_mouse_down_event - pushes a mouse - down event to the specified render_target --------------------------------------------------*/ - -void ui_input_manager::push_mouse_up_event(render_target *target, s32 x, s32 y) -{ - ui_event event = { ui_event::type::NONE }; - event.event_type = ui_event::type::MOUSE_UP; - event.target = target; - event.mouse_x = x; - event.mouse_y = y; - push_event(event); -} - -/*------------------------------------------------- -push_mouse_down_event - pushes a mouse -down event to the specified render_target + push_pointer_update - pushes a pointer update + event to the specified render_target -------------------------------------------------*/ -void ui_input_manager::push_mouse_rdown_event(render_target *target, s32 x, s32 y) +void ui_input_manager::push_pointer_update( + render_target *target, + pointer type, + u16 ptrid, + u16 device, + s32 x, + s32 y, + u32 buttons, + u32 pressed, + u32 released, + s16 clicks) { ui_event event = { ui_event::type::NONE }; - event.event_type = ui_event::type::MOUSE_RDOWN; + event.event_type = ui_event::type::POINTER_UPDATE; event.target = target; - event.mouse_x = x; - event.mouse_y = y; + event.pointer_type = type; + event.pointer_id = ptrid; + event.pointer_device = device; + event.pointer_x = x; + event.pointer_y = y; + event.pointer_buttons = buttons; + event.pointer_pressed = pressed; + event.pointer_released = released; + event.pointer_clicks = clicks; push_event(event); } -/*------------------------------------------------- -push_mouse_down_event - pushes a mouse -down event to the specified render_target --------------------------------------------------*/ - -void ui_input_manager::push_mouse_rup_event(render_target *target, s32 x, s32 y) +void ui_input_manager::push_pointer_leave( + render_target *target, + pointer type, + u16 ptrid, + u16 device, + s32 x, + s32 y, + u32 released, + s16 clicks) { ui_event event = { ui_event::type::NONE }; - event.event_type = ui_event::type::MOUSE_RUP; + event.event_type = ui_event::type::POINTER_LEAVE; event.target = target; - event.mouse_x = x; - event.mouse_y = y; + event.pointer_type = type; + event.pointer_id = ptrid; + event.pointer_device = device; + event.pointer_x = x; + event.pointer_y = y; + event.pointer_buttons = 0U; + event.pointer_pressed = 0U; + event.pointer_released = released; + event.pointer_clicks = clicks; push_event(event); } -/*------------------------------------------------- - push_mouse_double_click_event - pushes - a mouse double-click event to the specified - render_target --------------------------------------------------*/ -void ui_input_manager::push_mouse_double_click_event(render_target *target, s32 x, s32 y) +void ui_input_manager::push_pointer_abort( + render_target *target, + pointer type, + u16 ptrid, + u16 device, + s32 x, + s32 y, + u32 released, + s16 clicks) { ui_event event = { ui_event::type::NONE }; - event.event_type = ui_event::type::MOUSE_DOUBLE_CLICK; + event.event_type = ui_event::type::POINTER_ABORT; event.target = target; - event.mouse_x = x; - event.mouse_y = y; + event.pointer_type = type; + event.pointer_id = ptrid; + event.pointer_device = device; + event.pointer_x = x; + event.pointer_y = y; + event.pointer_buttons = 0U; + event.pointer_pressed = 0U; + event.pointer_released = released; + event.pointer_clicks = clicks; push_event(event); } @@ -439,7 +311,7 @@ void ui_input_manager::push_char_event(render_target *target, char32_t ch) wheel event to the specified render_target -------------------------------------------------*/ -void ui_input_manager::push_mouse_wheel_event(render_target *target, s32 x, s32 y, short delta, int ucNumLines) +void ui_input_manager::push_mouse_wheel_event(render_target *target, s32 x, s32 y, short delta, int lines) { ui_event event = { ui_event::type::NONE }; event.event_type = ui_event::type::MOUSE_WHEEL; @@ -447,7 +319,7 @@ void ui_input_manager::push_mouse_wheel_event(render_target *target, s32 x, s32 event.mouse_x = x; event.mouse_y = y; event.zdelta = delta; - event.num_lines = ucNumLines; + event.num_lines = lines; push_event(event); } diff --git a/src/emu/uiinput.h b/src/emu/uiinput.h index 3fc89d7a4ff..54170bad7ec 100644 --- a/src/emu/uiinput.h +++ b/src/emu/uiinput.h @@ -13,6 +13,8 @@ #pragma once +#include "interface/uievents.h" + /*************************************************************************** TYPE DEFINITIONS @@ -25,17 +27,15 @@ struct ui_event NONE, WINDOW_FOCUS, WINDOW_DEFOCUS, - MOUSE_MOVE, - MOUSE_LEAVE, - MOUSE_DOWN, - MOUSE_UP, - MOUSE_RDOWN, - MOUSE_RUP, - MOUSE_DOUBLE_CLICK, MOUSE_WHEEL, + POINTER_UPDATE, + POINTER_LEAVE, + POINTER_ABORT, IME_CHAR }; + using pointer = osd::ui_event_handler::pointer; + type event_type; render_target * target; s32 mouse_x; @@ -44,17 +44,27 @@ struct ui_event char32_t ch; short zdelta; int num_lines; + + pointer pointer_type; // type of input controlling this pointer + u16 pointer_id; // pointer ID - will be recycled aggressively + u16 pointer_device; // for grouping pointers for multi-touch gesture recognition + s32 pointer_x; // pointer X coordinate + s32 pointer_y; // pointer Y coordinate + u32 pointer_buttons; // currently depressed buttons + u32 pointer_pressed; // buttons pressed since last update (primary action in LSB) + u32 pointer_released; // buttons released since last update (primary action in LSB) + s16 pointer_clicks; // positive for multi-click, negative on release if turned into hold or drag }; // ======================> ui_input_manager -class ui_input_manager +class ui_input_manager final : public osd::ui_event_handler { public: // construction/destruction ui_input_manager(running_machine &machine); - void frame_update(); + void check_ui_inputs(); // pushes a single event onto the queue bool push_event(ui_event event); @@ -68,17 +78,13 @@ public: // clears all outstanding events void reset(); - // retrieves the current location of the mouse - render_target *find_mouse(s32 *x, s32 *y, bool *button) const; - ioport_field *find_mouse_field() const; - - // return true if a key down for the given user interface sequence is detected - bool pressed(int code); - // enable/disable UI key presses bool presses_enabled() const { return m_presses_enabled; } void set_presses_enabled(bool enabled) { m_presses_enabled = enabled; } + // return true if a key down for the given user interface sequence is detected + bool pressed(int code) { return pressed_repeat(code, 0); } + // return true if a key down for the given user interface sequence is detected, or if autorepeat at the given speed is triggered bool pressed_repeat(int code, int speed); @@ -86,44 +92,32 @@ public: running_machine &machine() const { return m_machine; } // queueing events - void push_window_focus_event(render_target *target); - void push_window_defocus_event(render_target *target); - void push_mouse_move_event(render_target *target, s32 x, s32 y); - void push_mouse_leave_event(render_target *target); - void push_mouse_down_event(render_target *target, s32 x, s32 y); - void push_mouse_up_event(render_target *target, s32 x, s32 y); - void push_mouse_rdown_event(render_target *target, s32 x, s32 y); - void push_mouse_rup_event(render_target *target, s32 x, s32 y); - void push_mouse_double_click_event(render_target *target, s32 x, s32 y); - void push_char_event(render_target *target, char32_t ch); - void push_mouse_wheel_event(render_target *target, s32 x, s32 y, short delta, int ucNumLines); + virtual void push_window_focus_event(render_target *target) override; + virtual void push_window_defocus_event(render_target *target) override; + virtual void push_mouse_wheel_event(render_target *target, s32 x, s32 y, short delta, int lines) override; + virtual void push_pointer_update(render_target *target, pointer type, u16 ptrid, u16 device, s32 x, s32 y, u32 buttons, u32 pressed, u32 released, s16 clicks) override; + virtual void push_pointer_leave(render_target *target, pointer type, u16 ptrid, u16 device, s32 x, s32 y, u32 released, s16 clicks) override; + virtual void push_pointer_abort(render_target *target, pointer type, u16 ptrid, u16 device, s32 x, s32 y, u32 released, s16 clicks) override; + virtual void push_char_event(render_target *target, char32_t ch) override; void mark_all_as_pressed(); private: - // constants - static constexpr unsigned EVENT_QUEUE_SIZE = 128; + static constexpr unsigned EVENT_QUEUE_SIZE = 256; // internal state - running_machine & m_machine; // reference to our machine - - // pressed states; retrieved with ui_input_pressed() - bool m_presses_enabled; - osd_ticks_t m_next_repeat[IPT_COUNT]; - u8 m_seqpressed[IPT_COUNT]; - - // mouse position/info - render_target * m_current_mouse_target; - s32 m_current_mouse_x; - s32 m_current_mouse_y; - bool m_current_mouse_down; - ioport_field * m_current_mouse_field; - - // popped states; ring buffer of ui_events - ui_event m_events[EVENT_QUEUE_SIZE]; - int m_events_start; - int m_events_end; + running_machine & m_machine; + + // pressed states; retrieved with pressed() or pressed_repeat() + bool m_presses_enabled; + osd_ticks_t m_next_repeat[IPT_COUNT]; + u8 m_seqpressed[IPT_COUNT]; + + // ring buffer of ui_events + ui_event m_events[EVENT_QUEUE_SIZE]; + int m_events_start; + int m_events_end; }; #endif // MAME_EMU_UIINPUT_H diff --git a/src/emu/validity.cpp b/src/emu/validity.cpp index 4cb6202b4d9..92b141f269e 100644 --- a/src/emu/validity.cpp +++ b/src/emu/validity.cpp @@ -22,6 +22,7 @@ #include "unicode.h" #include <cctype> +#include <sstream> #include <type_traits> #include <typeinfo> @@ -174,13 +175,6 @@ void validate_integer_semantics() if (a32 >> 1 != -2) osd_printf_error("s32 right shift must be arithmetic\n"); if (a64 >> 1 != -2) osd_printf_error("s64 right shift must be arithmetic\n"); - // check pointer size -#ifdef PTR64 - static_assert(sizeof(void *) == 8, "PTR64 flag enabled, but was compiled for 32-bit target\n"); -#else - static_assert(sizeof(void *) == 4, "PTR64 flag not enabled, but was compiled for 64-bit target\n"); -#endif - // TODO: check if this is actually working // check endianness definition u16 lsbtest = 0; @@ -2150,6 +2144,49 @@ void validity_checker::validate_driver(device_t &root) osd_printf_error("Driver cannot have features that are both unemulated and imperfect (0x%08X)\n", util::underlying_value(unemulated & imperfect)); if ((m_current_driver->flags & machine_flags::NO_SOUND_HW) && ((unemulated | imperfect) & device_t::feature::SOUND)) osd_printf_error("Machine without sound hardware cannot have unemulated/imperfect sound\n"); + + // catch systems marked as supporting save states that contain devices that don't support save states + if (!(m_current_driver->type.emulation_flags() & device_t::flags::SAVE_UNSUPPORTED)) + { + std::set<std::add_pointer_t<device_type> > nosave; + device_enumerator iter(root); + std::string_view cardtag; + for (auto &device : iter) + { + // ignore any children of a slot card + if (!cardtag.empty()) + { + std::string_view tag(device.tag()); + if ((tag.length() > cardtag.length()) && (tag.substr(0, cardtag.length()) == cardtag) && tag[cardtag.length()] == ':') + continue; + else + cardtag = std::string_view(); + } + + // check to see if this is a slot card + device_t *const parent(device.owner()); + if (parent) + { + device_slot_interface *slot; + parent->interface(slot); + if (slot && (slot->get_card_device() == &device)) + { + cardtag = device.tag(); + continue; + } + } + + if (device.type().emulation_flags() & device_t::flags::SAVE_UNSUPPORTED) + nosave.emplace(&device.type()); + } + if (!nosave.empty()) + { + std::ostringstream buf; + for (auto const &devtype : nosave) + util::stream_format(buf, "%s(%s) %s\n", core_filename_extract_base(devtype->source()), devtype->shortname(), devtype->fullname()); + osd_printf_error("Machine is marked as supporting save states but uses devices that lack save state support:\n%s", std::move(buf).str()); + } + } } @@ -2495,12 +2532,13 @@ void validity_checker::validate_inputs(device_t &root) // allocate the input ports ioport_list portlist; - std::string errorbuf; - portlist.append(device, errorbuf); - - // report any errors during construction - if (!errorbuf.empty()) - osd_printf_error("I/O port error during construction:\n%s\n", errorbuf); + { + // report any errors during construction + std::ostringstream errorbuf; + portlist.append(device, errorbuf); + if (errorbuf.tellp()) + osd_printf_error("I/O port error during construction:\n%s\n", std::move(errorbuf).str()); + } // do a first pass over ports to add their names and find duplicates for (auto &port : portlist) diff --git a/src/emu/xtal.cpp b/src/emu/xtal.cpp index f5dc48a1f49..62d0370e661 100644 --- a/src/emu/xtal.cpp +++ b/src/emu/xtal.cpp @@ -57,450 +57,474 @@ const double XTAL::known_xtals[] = { /* Frequency Sugarvassed Examples ----------- ---------------------- ---------------------------------------- */ - 32'768, /* 32.768_kHz_XTAL Used to drive RTC chips */ - 38'400, /* 38.4_kHz_XTAL Resonator */ - 384'000, /* 384_kHz_XTAL Resonator - Commonly used for driving OKI MSM5205 */ - 400'000, /* 400_kHz_XTAL Resonator - OKI MSM5205 on Great Swordman h/w */ - 430'000, /* 430_kHz_XTAL Resonator */ - 455'000, /* 455_kHz_XTAL Resonator - OKI MSM5205 on Gladiator h/w */ - 500'000, /* 500_kHz_XTAL Resonator - MIDI clock on various synthesizers (31250 * 16) */ - 512'000, /* 512_kHz_XTAL Resonator - Toshiba TC8830F */ - 600'000, /* 600_kHz_XTAL - */ - 640'000, /* 640_kHz_XTAL Resonator - NEC UPD7759, Texas Instruments Speech Chips @ 8khz */ - 960'000, /* 960_kHz_XTAL Resonator - Xerox Notetaker Keyboard UART */ - 1'000'000, /* 1_MHz_XTAL Used to drive OKI M6295 chips */ - 1'008'000, /* 1.008_MHz_XTAL Acorn Microcomputer (System 1) */ - 1'056'000, /* 1.056_MHz_XTAL Resonator - OKI M6295 on Trio The Punch h/w */ - 1'294'400, /* 1.2944_MHz_XTAL BBN BitGraph PSG */ - 1'600'000, /* 1.6_MHz_XTAL Resonator - Roland TR-707 */ - 1'689'600, /* 1.6896_MHz_XTAL Diablo 1355WP Printer */ - 1'750'000, /* 1.75_MHz_XTAL RCA CDP1861 */ - 1'797'100, /* 1.7971_MHz_XTAL SWTPC 6800 (with MIKBUG) */ - 1'843'200, /* 1.8432_MHz_XTAL Bondwell 12/14 */ - 2'000'000, /* 2_MHz_XTAL - */ - 2'012'160, /* 2.01216_MHz_XTAL Cidelsa Draco sound board */ - 2'097'152, /* 2.097152_MHz_XTAL Icatel 1995 - Brazilian public payphone */ - 2'250'000, /* 2.25_MHz_XTAL Resonator - YM2154 on Yamaha PSR-60 & PSR-70 */ - 2'376'000, /* 2.376_MHz_XTAL CIT-101 keyboard */ - 2'457'600, /* 2.4576_MHz_XTAL Atari ST MFP */ - 2'500'000, /* 2.5_MHz_XTAL Janken Man units */ - 2'600'000, /* 2.6_MHz_XTAL Sharp PC-1500 */ - 2'700'000, /* 2.7_MHz_XTAL Resonator - YM2154 on Yamaha RX15 */ - 2'950'000, /* 2.95_MHz_XTAL Playmatic MPU-C, MPU-III & Sound-3 */ - 3'000'000, /* 3_MHz_XTAL Probably only used to drive 68705 or similar MCUs on 80's Taito PCBs */ - 3'072'000, /* 3.072_MHz_XTAL INS 8520 input clock rate */ - 3'120'000, /* 3.12_MHz_XTAL SP0250 clock on Gottlieb games */ - 3'276'800, /* 3.2768_MHz_XTAL SP0256 clock in Speech Synthesis for Dragon 32 */ - 3'300'000, /* 3.3_MHz_XTAL LC 80 (export) */ - 3'521'280, /* 3.52128_MHz_XTAL RCA COSMAC VIP */ - 3'546'800, /* 3.5468_MHz_XTAL Atari 400 PAL */ - 3'546'894, /* 3.546894_MHz_XTAL Atari 2600 PAL */ - 3'547'000, /* 3.547_MHz_XTAL Philips G7200, Philips C7240 */ - 3'562'500, /* 3.5625_MHz_XTAL Jopac JO7400 */ - 3'570'000, /* 3.57_MHz_XTAL Telmac TMC-600 */ - 3'578'640, /* 3.57864_MHz_XTAL Atari Portfolio PCD3311T */ - 3'579'000, /* 3.579_MHz_XTAL BeebOPL */ - 3'579'545, /* 3.579545_MHz_XTAL NTSC color subcarrier, extremely common, used on 100's of PCBs (Keytronic custom part #48-300-010 is equivalent) */ - 3'579'575, /* 3.579575_MHz_XTAL Atari 2600 NTSC */ - 3'680'000, /* 3.68_MHz_XTAL Resonator - Baud rate clock for the 6551 in the MTU-130 */ - 3'686'400, /* 3.6864_MHz_XTAL Baud rate clock for MC68681 and similar UARTs */ - 3'840'000, /* 3.84_MHz_XTAL Fairlight CMI Alphanumeric Keyboard */ - 3'900'000, /* 3.9_MHz_XTAL Resonator - Used on some Fidelity boards */ - 3'932'160, /* 3.93216_MHz_XTAL Apple Lisa COP421 (197-0016A) */ - 4'000'000, /* 4_MHz_XTAL - */ - 4'028'000, /* 4.028_MHz_XTAL Sony SMC-777 */ - 4'032'000, /* 4.032_MHz_XTAL GRiD Compass modem board */ - 4'096'000, /* 4.096_MHz_XTAL Used to drive OKI M9810 chips */ - 4'194'304, /* 4.194304_MHz_XTAL Used to drive MC146818 / Nintendo Game Boy */ - 4'224'000, /* 4.224_MHz_XTAL Used to drive OKI M6295 chips, usually with /4 divider */ - 4'410'000, /* 4.41_MHz_XTAL Pioneer PR-8210 ldplayer */ - 4'433'610, /* 4.43361_MHz_XTAL Cidelsa Draco */ - 4'433'619, /* 4.433619_MHz_XTAL PAL color subcarrier (technically 4.43361875mhz)*/ - 4'608'000, /* 4.608_MHz_XTAL Luxor ABC-77 keyboard (Keytronic custom part #48-300-107 is equivalent) */ - 4'915'200, /* 4.9152_MHz_XTAL - */ - 5'000'000, /* 5_MHz_XTAL Mutant Night */ - 5'068'800, /* 5.0688_MHz_XTAL Usually used as MC2661 or COM8116 baud rate clock */ - 5'185'000, /* 5.185_MHz_XTAL Intel INTELLEC® 4 */ - 5'370'000, /* 5.37_MHz_XTAL HP 95LX */ - 5'460'000, /* 5.46_MHz_XTAL ec1840 and ec1841 keyboard */ - 5'500'000, /* 5.5_MHz_XTAL Yamaha PSS-480 */ - 5'529'600, /* 5.5296_MHz_XTAL Kontron PSI98 keyboard */ - 5'626'000, /* 5.626_MHz_XTAL RCA CDP1869 PAL dot clock */ - 5'659'200, /* 5.6592_MHz_XTAL Digilog 320 dot clock */ - 5'670'000, /* 5.67_MHz_XTAL RCA CDP1869 NTSC dot clock */ - 5'714'300, /* 5.7143_MHz_XTAL Cidelsa Destroyer, TeleVideo serial keyboards */ - 5'856'000, /* 5.856_MHz_XTAL HP 3478A Multimeter */ - 5'911'000, /* 5.911_MHz_XTAL Philips Videopac Plus G7400 */ - 5'990'400, /* 5.9904_MHz_XTAL Luxor ABC 800 keyboard (Keytronic custom part #48-300-008 is equivalent) */ - 6'000'000, /* 6_MHz_XTAL American Poker II, Taito SJ System */ - 6'048'000, /* 6.048_MHz_XTAL Hektor II */ - 6'144'000, /* 6.144_MHz_XTAL Used on Alpha Denshi early 80's games sound board, Casio FP-200 and Namco Universal System 16 */ - 6'400'000, /* 6.4_MHz_XTAL Textel Compact */ - 6'500'000, /* 6.5_MHz_XTAL Jupiter Ace, Roland QDD interface */ - 6'880'000, /* 6.88_MHz_XTAL Barcrest MPU4 */ - 6'900'000, /* 6.9_MHz_XTAL BBN BitGraph CPU */ - 7'000'000, /* 7_MHz_XTAL Jaleco Mega System PCBs */ - 7'056'000, /* 7.056_MHz_XTAL Alesis QS FXCHIP (LCM of 44.1 kHz and 48 kHz) */ - 7'159'090, /* 7.15909_MHz_XTAL Blood Bros (2x NTSC subcarrier) */ - 7'200'000, /* 7.2_MHz_XTAL Novag Constellation (later models, with /2 divider), Kawai K1 keyscan IC */ - 7'372'800, /* 7.3728_MHz_XTAL - */ - 7'680'000, /* 7.68_MHz_XTAL Psion Series 3 */ - 7'864'300, /* 7.8643_MHz_XTAL Used on InterFlip games as video clock */ - 7'987'000, /* 7.987_MHz_XTAL PC9801-86 YM2608 clock */ - 7'995'500, /* 7.9955_MHz_XTAL Used on Electronic Devices Italy Galaxy Gunners sound board */ - 8'000'000, /* 8_MHz_XTAL Extremely common, used on 100's of PCBs */ - 8'200'000, /* 8.2_MHz_XTAL Universal Mr. Do - Model 8021 PCB */ - 8'388'000, /* 8.388_MHz_XTAL Nintendo Game Boy Color */ - 8'448'000, /* 8.448_MHz_XTAL Banpresto's Note Chance - Used to drive OKI M6295 chips, usually with /8 divider */ - 8'467'200, /* 8.4672_MHz_XTAL Subsino's Ying Hua Lian */ - 8'664'000, /* 8.664_MHz_XTAL Touchmaster */ - 8'700'000, /* 8.7_MHz_XTAL Tandberg TDV 2324 */ - 8'860'000, /* 8.86_MHz_XTAL AlphaTantel */ - 8'867'000, /* 8.867_MHz_XTAL Philips G7400 (~2x PAL subcarrier) */ - 8'867'236, /* 8.867236_MHz_XTAL RCA CDP1869 PAL color clock (~2x PAL subcarrier) */ - 8'867'238, /* 8.867238_MHz_XTAL ETI-660 (~2x PAL subcarrier) */ - 8'945'000, /* 8.945_MHz_XTAL Hit Me */ - 8'960'000, /* 8.96_MHz_XTAL Casio CZ-101 (divided by 2 for Music LSI) */ - 9'000'000, /* 9_MHz_XTAL Homedata PCBs */ - 9'216'000, /* 9.216_MHz_XTAL Univac UTS 20 */ - 9'400'000, /* 9.4_MHz_XTAL Yamaha MU-5 and TG-100 */ - 9'426'500, /* 9.4265_MHz_XTAL Yamaha DX7, and DX9 */ - 9'600'000, /* 9.6_MHz_XTAL WD37C65 second clock (for 300 KB/sec rate) */ - 9'732'000, /* 9.732_MHz_XTAL CTA Invader */ - 9'828'000, /* 9.828_MHz_XTAL Universal PCBs */ - 9'830'400, /* 9.8304_MHz_XTAL Epson PX-8 */ - 9'832'000, /* 9.832_MHz_XTAL Robotron A7150 */ - 9'877'680, /* 9.87768_MHz_XTAL Microterm 420 */ - 9'987'000, /* 9.987_MHz_XTAL Crazy Balloon */ - 10'000'000, /* 10_MHz_XTAL - */ - 10'137'600, /* 10.1376_MHz_XTAL Wyse WY-100 */ - 10'245'000, /* 10.245_MHz_XTAL PES Speech box */ - 10'380'000, /* 10.38_MHz_XTAL Fairlight Q219 Lightpen/Graphics Card */ - 10'480'000, /* 10.48_MHz_XTAL System-80 (50 Hz) */ - 10'500'000, /* 10.5_MHz_XTAL Agat-7 */ - 10'595'000, /* 10.595_MHz_XTAL Mad Alien */ - 10'644'000, /* 10.644_MHz_XTAL Northwest Digitial Systems GP-19 */ - 10'644'500, /* 10.6445_MHz_XTAL TRS-80 Model I */ - 10'687'500, /* 10.6875_MHz_XTAL BBC Bridge Companion */ - 10'694'250, /* 10.69425_MHz_XTAL Xerox 820 */ - 10'717'200, /* 10.7172_MHz_XTAL Eltec EurocomII */ - 10'730'000, /* 10.73_MHz_XTAL Ruleta RE-900 VDP Clock */ - 10'733'000, /* 10.733_MHz_XTAL The Fairyland Story */ - 10'738'000, /* 10.738_MHz_XTAL Pokerout (poker+breakout) TMS9129 VDP Clock */ - 10'738'635, /* 10.738635_MHz_XTAL TMS9918 family (3x NTSC subcarrier) */ - 10'816'000, /* 10.816_MHz_XTAL Universal 1979-1980 (Cosmic Alien, etc) */ - 10'886'400, /* 10.8864_MHz_XTAL Systel System 100 */ - 10'920'000, /* 10.92_MHz_XTAL ADDS Viewpoint 60, Viewpoint A2 */ - 11'000'000, /* 11_MHz_XTAL Mario I8039 sound */ - 11'004'000, /* 11.004_MHz_XTAL TI 911 VDT */ - 11'059'200, /* 11.0592_MHz_XTAL Used with MCS-51 to generate common baud rates */ - 11'200'000, /* 11.2_MHz_XTAL New York, New York */ - 11'289'000, /* 11.289_MHz_XTAL Vanguard */ - 11'289'600, /* 11.2896_MHz_XTAL Frantic Fred */ - 11'400'000, /* 11.4_MHz_XTAL HP 9845 */ - 11'668'800, /* 11.6688_MHz_XTAL Gameplan pixel clock */ - 11'730'000, /* 11.73_MHz_XTAL Irem M-11 */ - 11'800'000, /* 11.8_MHz_XTAL IBM PC Music Feature Card */ - 11'980'800, /* 11.9808_MHz_XTAL Luxor ABC 80 */ - 12'000'000, /* 12_MHz_XTAL Extremely common, used on 100's of PCBs */ - 12'057'600, /* 12.0576_MHz_XTAL Poly 1 (38400 * 314) */ - 12'096'000, /* 12.096_MHz_XTAL Some early 80's Atari games */ - 12'288'000, /* 12.288_MHz_XTAL Sega Model 3 digital audio board */ - 12'292'000, /* 12.292_MHz_XTAL Northwest Digitial Systems GP-19 */ - 12'324'000, /* 12.324_MHz_XTAL Otrona Attache */ - 12'335'600, /* 12.3356_MHz_XTAL RasterOps ColorBoard 264 (~784x NTSC line rate) */ - 12'432'000, /* 12.432_MHz_XTAL Kaneko Fly Boy/Fast Freddie Hardware */ - 12'472'500, /* 12.4725_MHz_XTAL Bonanza's Mini Boy 7 */ - 12'480'000, /* 12.48_MHz_XTAL TRS-80 Model II */ - 12'500'000, /* 12.5_MHz_XTAL Red Alert audio board */ - 12'638'000, /* 12.638_MHz_XTAL Exidy Sorcerer */ - 12'672'000, /* 12.672_MHz_XTAL TRS-80 Model 4 80*24 video */ - 12'800'000, /* 12.8_MHz_XTAL Cave CV1000 */ - 12'854'400, /* 12.8544_MHz_XTAL Alphatronic P3 */ - 12'936'000, /* 12.936_MHz_XTAL CDC 721 */ - 12'979'200, /* 12.9792_MHz_XTAL Exidy 440 */ - 13'000'000, /* 13_MHz_XTAL STM Pied Piper dot clock */ - 13'300'000, /* 13.3_MHz_XTAL BMC bowling */ - 13'330'560, /* 13.33056_MHz_XTAL Taito L */ - 13'333'000, /* 13.333_MHz_XTAL Ojanko High School */ - 13'400'000, /* 13.4_MHz_XTAL TNK3, Ikari Warriors h/w */ - 13'478'400, /* 13.4784_MHz_XTAL TeleVideo 970 80-column display clock */ - 13'495'200, /* 13.4952_MHz_XTAL Used on Shadow Force pcb and maybe other Technos pcbs? */ - 13'500'000, /* 13.5_MHz_XTAL Microbee */ - 13'516'800, /* 13.5168_MHz_XTAL Kontron KDT6 */ - 13'608'000, /* 13.608_MHz_XTAL TeleVideo 910 & 925 */ - 13'824'000, /* 13.824_MHz_XTAL Robotron PC-1715 display circuit */ - 13'977'600, /* 13.9776_MHz_XTAL Kaypro II dot clock */ - 14'000'000, /* 14_MHz_XTAL - */ - 14'112'000, /* 14.112_MHz_XTAL Timex/Sinclair TS2068 */ - 14'192'640, /* 14.19264_MHz_XTAL Central Data 2650 */ - 14'218'000, /* 14.218_MHz_XTAL Dragon */ - 14'250'450, /* 14.25045_MHz_XTAL Apple II Europlus */ - 14'300'000, /* 14.3_MHz_XTAL Agat-7 */ - 14'314'000, /* 14.314_MHz_XTAL Taito TTL Board */ - 14'318'181, /* 14.318181_MHz_XTAL Extremely common, used on 100's of PCBs (4x NTSC subcarrier) */ - 14'349'600, /* 14.3496_MHz_XTAL Roland S-50 VDP */ - 14'580'000, /* 14.58_MHz_XTAL Fortune 32:16 Video Controller */ - 14'705'882, /* 14.705882_MHz_XTAL Aleck64 */ - 14'728'000, /* 14.728_MHz_XTAL ADM 36 */ - 14'742'800, /* 14.7428_MHz_XTAL ADM 23 */ - 14'745'000, /* 14.745_MHz_XTAL Synertek KTM-3 */ - 14'745'600, /* 14.7456_MHz_XTAL Namco System 12 & System Super 22/23 for JVS */ - 14'746'000, /* 14.746_MHz_XTAL Namco System 10 MGEXIO */ - 14'784'000, /* 14.784_MHz_XTAL Zenith Z-29 */ - 14'916'000, /* 14.916_MHz_XTAL ADDS Viewpoint 122 */ - 14'976'000, /* 14.976_MHz_XTAL CIT-101 80-column display clock */ - 15'000'000, /* 15_MHz_XTAL Sinclair QL, Amusco Poker */ - 15'148'800, /* 15.1488_MHz_XTAL Zentec 9002/9003 */ - 15'206'400, /* 15.2064_MHz_XTAL Falco TS-1 */ - 15'288'000, /* 15.288_MHz_XTAL DEC VT220 80-column display clock */ - 15'300'720, /* 15.30072_MHz_XTAL Microterm 420 */ - 15'360'000, /* 15.36_MHz_XTAL Visual 1050 */ - 15'400'000, /* 15.4_MHz_XTAL DVK KSM */ - 15'468'480, /* 15.46848_MHz_XTAL Bank Panic h/w, Sega G80 */ - 15'582'000, /* 15.582_MHz_XTAL Zentec Zephyr */ - 15'625'000, /* 15.625_MHz_XTAL Zaccaria The Invaders */ - 15'667'200, /* 15.6672_MHz_XTAL Apple Macintosh */ - 15'700'000, /* 15.7_MHz_XTAL Motogonki */ - 15'741'000, /* 15.741_MHz_XTAL DECmate II 80-column display clock */ - 15'897'600, /* 15.8976_MHz_XTAL IAI Swyft */ - 15'920'000, /* 15.92_MHz_XTAL HP Integral PC */ - 15'930'000, /* 15.93_MHz_XTAL ADM 12 */ - 15'974'400, /* 15.9744_MHz_XTAL Osborne 1 (9600 * 52 * 32) */ - 16'000'000, /* 16_MHz_XTAL Extremely common, used on 100's of PCBs */ - 16'097'280, /* 16.09728_MHz_XTAL DEC VT240 (1024 * 262 * 60) */ - 16'128'000, /* 16.128_MHz_XTAL Fujitsu FM-7 */ - 16'200'000, /* 16.2_MHz_XTAL Debut */ - 16'257'000, /* 16.257_MHz_XTAL IBM PC MDA & EGA */ - 16'313'000, /* 16.313_MHz_XTAL Micro-Term ERGO 201 */ - 16'364'000, /* 16.364_MHz_XTAL Corvus Concept */ - 16'384'000, /* 16.384_MHz_XTAL - */ - 16'400'000, /* 16.4_MHz_XTAL MS 6102 */ - 16'537'000, /* 16.537_MHz_XTAL Falco terminals 80-column clock */ - 16'572'000, /* 16.572_MHz_XTAL Micro-Term ACT-5A */ - 16'588'800, /* 16.5888_MHz_XTAL SM 7238 */ - 16'666'600, /* 16.6666_MHz_XTAL Firebeat GCU */ - 16'669'800, /* 16.6698_MHz_XTAL Qume QVT-102 */ - 16'670'000, /* 16.67_MHz_XTAL - */ - 16'777'216, /* 16.777216_MHz_XTAL Nintendo Game Boy Advance */ - 16'934'400, /* 16.9344_MHz_XTAL Usually used to drive 90's Yamaha OPL/FM chips (44100 * 384) */ - 17'010'000, /* 17.01_MHz_XTAL Epic 14E */ - 17'064'000, /* 17.064_MHz_XTAL Memorex 1377 */ - 17'074'800, /* 17.0748_MHz_XTAL SWTPC 8212 */ - 17'350'000, /* 17.35_MHz_XTAL ITT Courier 1700 */ - 17'360'000, /* 17.36_MHz_XTAL OMTI Series 10 SCSI controller */ - 17'430'000, /* 17.43_MHz_XTAL Videx Videoterm */ - 17'550'000, /* 17.55_MHz_XTAL HP 264x display clock (50 Hz configuration) */ - 17'600'000, /* 17.6_MHz_XTAL LSI Octopus */ - 17'734'470, /* 17.73447_MHz_XTAL 4x PAL subcarrier */ - 17'734'472, /* 17.734472_MHz_XTAL 4x PAL subcarrier - All of these exist, exact 4x PAL is actually 17'734'475 */ - 17'734'475, /* 17.734475_MHz_XTAL 4x PAL subcarrier - " */ - 17'734'476, /* 17.734476_MHz_XTAL 4x PAL subcarrier - " */ - 17'812'000, /* 17.812_MHz_XTAL Videopac C52 */ - 17'971'200, /* 17.9712_MHz_XTAL Compucolor II, Hazeltine Esprit III */ - 18'000'000, /* 18_MHz_XTAL S.A.R, Ikari Warriors 3 */ - 18'414'000, /* 18.414_MHz_XTAL Ann Arbor Ambassador */ - 18'432'000, /* 18.432_MHz_XTAL Extremely common, used on 100's of PCBs (48000 * 384) */ - 18'480'000, /* 18.48_MHz_XTAL Wyse WY-100 video */ - 18'575'000, /* 18.575_MHz_XTAL Visual 102, Visual 220 */ - 18'600'000, /* 18.6_MHz_XTAL Teleray Model 10 */ - 18'720'000, /* 18.72_MHz_XTAL Nokia MikroMikko 1 */ - 18'867'000, /* 18.867_MHz_XTAL Decision Data IS-482 */ - 18'869'600, /* 18.8696_MHz_XTAL Memorex 2178 */ - 19'170'000, /* 19.17_MHz_XTAL Ericsson ISA8 Monochrome HR Graphics Board */ - 19'339'600, /* 19.3396_MHz_XTAL TeleVideo TVI-955 80-column display clock */ - 19'584'000, /* 19.584_MHz_XTAL ADM-42 */ - 19'600'000, /* 19.6_MHz_XTAL Universal Mr. Do - Model 8021 PCB */ - 19'602'000, /* 19.602_MHz_XTAL Ampex 210+ 80-column display clock */ - 19'660'800, /* 19.6608_MHz_XTAL Euro League (bootleg), labeled as "UKI 19.6608 20PF" */ - 19'661'400, /* 19.6614_MHz_XTAL Wyse WY-30 */ - 19'718'400, /* 19.7184_MHz_XTAL Informer 207/100 */ - 19'923'000, /* 19.923_MHz_XTAL Cinematronics vectors */ - 19'968'000, /* 19.968_MHz_XTAL Used mostly by some Taito games */ - 20'000'000, /* 20_MHz_XTAL - */ - 20'160'000, /* 20.16_MHz_XTAL Nintendo 8080 */ - 20'275'200, /* 20.2752_MHz_XTAL TRS-80 Model III */ - 20'282'000, /* 20.282_MHz_XTAL Northwest Digitial Systems GP-19 */ - 20'375'040, /* 20.37504_MHz_XTAL Apple Lisa dot clock (197-0019A) */ - 20'625'000, /* 20.625_MHz_XTAL SM 7238 */ - 20'790'000, /* 20.79_MHz_XTAL Blockade-hardware Gremlin games */ - 21'000'000, /* 21_MHz_XTAL Lock-On pixel clock */ - 21'052'600, /* 21.0526_MHz_XTAL NEC PC-98xx pixel clock */ - 21'060'000, /* 21.06_MHz_XTAL HP 264x display clock (60 Hz configuration) */ - 21'254'400, /* 21.2544_MHz_XTAL TeleVideo 970 132-column display clock */ - 21'281'370, /* 21.28137_MHz_XTAL Radica Tetris (PAL) */ - 21'300'000, /* 21.3_MHz_XTAL - */ - 21'328'100, /* 21.3281_MHz_XTAL Philips NMS8245 */ - 21'477'272, /* 21.477272_MHz_XTAL BMC bowling, some Data East 90's games, Vtech Socrates; (6x NTSC subcarrier) */ - 21'667'500, /* 21.6675_MHz_XTAL AT&T 610 80-column display clock */ - 21'756'600, /* 21.7566_MHz_XTAL Tab Products E-22 80-column display clock */ - 22'000'000, /* 22_MHz_XTAL - */ - 22'032'000, /* 22.032_MHz_XTAL Intellec Series II I/O controller */ - 22'096'000, /* 22.096_MHz_XTAL ADDS Viewpoint 122 */ - 22'118'400, /* 22.1184_MHz_XTAL Amusco Poker */ - 22'168'000, /* 22.168_MHz_XTAL Sony HB-10P VDP (5x PAL subcarrier) */ - 22'248'000, /* 22.248_MHz_XTAL Quantel DPB-7000 */ - 22'321'000, /* 22.321_MHz_XTAL Apple LaserWriter II NT */ - 22'464'000, /* 22.464_MHz_XTAL CIT-101 132-column display clock */ - 22'579'000, /* 22.579_MHz_XTAL Sega System H1 SCSP clock */ - 22'579'200, /* 22.5792_MHz_XTAL Enhanced Apple Digital Sound Chip clock (44100 * 512) */ - 22'656'000, /* 22.656_MHz_XTAL Super Pinball Action (~1440x NTSC line rate) */ - 22'680'000, /* 22.680_MHz_XTAL HDS200 80-columns display clock */ - 22'896'000, /* 22.896_MHz_XTAL DEC VT220 132-column display clock */ - 23'200'000, /* 23.2_MHz_XTAL Roland JV-80 & JV-880 PCM clock */ - 23'814'000, /* 23.814_MHz_XTAL TeleVideo TVI-912, 920 & 950 */ - 23'961'600, /* 23.9616_MHz_XTAL Osborne 4 (Vixen) */ - 24'000'000, /* 24_MHz_XTAL Mario, 80's Data East games, 80's Konami games */ - 24'073'400, /* 24.0734_MHz_XTAL DEC Rainbow 100 */ - 24'167'829, /* 24.167829_MHz_XTAL Neo Geo AES rev. 3-3 and later (~1536x NTSC line rate) */ - 24'270'000, /* 24.27_MHz_XTAL CIT-101XL */ - 24'300'000, /* 24.3_MHz_XTAL ADM 36 132-column display clock */ - 24'576'000, /* 24.576_MHz_XTAL Pole Position h/w, Model 3 CPU board */ - 24'883'200, /* 24.8832_MHz_XTAL DEC VT100 */ - 25'000'000, /* 25_MHz_XTAL Namco System 22, Taito GNET, Dogyuun h/w */ - 25'174'800, /* 25.1748_MHz_XTAL Sega System 16A/16B (1600x NTSC line rate) */ - 25'175'000, /* 25.175_MHz_XTAL IBM MCGA/VGA 320/640-pixel graphics */ - 25'200'000, /* 25.2_MHz_XTAL Tektronix 4404 video clock */ - 25'398'360, /* 25.39836_MHz_XTAL Tandberg TDV 2324 */ - 25'400'000, /* 25.4_MHz_XTAL PC9801-86 PCM base clock */ - 25'447'000, /* 25.447_MHz_XTAL Namco EVA3A (Funcube2) */ - 25'771'500, /* 25.7715_MHz_XTAL HP-2622A */ - 25'920'000, /* 25.92_MHz_XTAL ADDS Viewpoint 60 */ - 26'000'000, /* 26_MHz_XTAL Gaelco PCBs */ - 26'195'000, /* 26.195_MHz_XTAL Roland JD-800 */ - 26'366'000, /* 26.366_MHz_XTAL DEC VT320 */ - 26'580'000, /* 26.58_MHz_XTAL Wyse WY-60 80-column display clock */ - 26'590'906, /* 26.590906_MHz_XTAL Atari Jaguar NTSC */ - 26'593'900, /* 26.5939_MHz_XTAL Atari Jaguar PAL */ - 26'601'712, /* 26.601712_MHz_XTAL Astro Corp.'s Show Hand, PAL Vtech/Yeno Socrates (6x PAL subcarrier) */ - 26'666'000, /* 26.666_MHz_XTAL Imagetek I4220/I4300 */ - 26'666'666, /* 26.666666_MHz_XTAL Irem M92 but most use 27MHz */ - 26'686'000, /* 26.686_MHz_XTAL Typically used on 90's Taito PCBs to drive the custom chips */ - 26'824'000, /* 26.824_MHz_XTAL Astro Corp.'s Zoo */ - 26'880'000, /* 26.88_MHz_XTAL Roland RF5C36/SA-16 clock (30000 * 896) */ - 26'989'200, /* 26.9892_MHz_XTAL TeleVideo 965 */ - 27'000'000, /* 27_MHz_XTAL Some Banpresto games macrossp, Irem M92 and 90's Toaplan games */ - 27'164'000, /* 27.164_MHz_XTAL Typically used on 90's Taito PCBs to drive the custom chips */ - 27'210'900, /* 27.2109_MHz_XTAL LA Girl */ - 27'562'000, /* 27.562_MHz_XTAL Visual 220 */ - 27'720'000, /* 27.72_MHz_XTAL AT&T 610 132-column display clock */ - 27'956'000, /* 27.956_MHz_XTAL CIT-101e 132-column display clock */ - 28'000'000, /* 28_MHz_XTAL Sega System H1 SH2 clock, Kyukyoku Tiger / Twin Cobra */ - 28'224'000, /* 28.224_MHz_XTAL Roland JD-800 */ - 28'322'000, /* 28.322_MHz_XTAL Saitek RISC 2500, Mephisto Montreux */ - 28'375'160, /* 28.37516_MHz_XTAL Amiga PAL systems */ - 28'475'000, /* 28.475_MHz_XTAL CoCo 3 PAL */ - 28'480'000, /* 28.48_MHz_XTAL Chromatics CGC-7900 */ - 28'636'000, /* 28.636_MHz_XTAL Super Kaneko Nova System */ - 28'636'363, /* 28.636363_MHz_XTAL Later Leland games and Atari GT, Amiga NTSC, Raiden2 h/w (8x NTSC subcarrier), NEC PC-88xx */ - 28'640'000, /* 28.64_MHz_XTAL Fuuki FG-1c AI AM-2 PCB */ - 28'700'000, /* 28.7_MHz_XTAL - */ - 29'376'000, /* 29.376_MHz_XTAL Qume QVT-103 */ - 29'491'200, /* 29.4912_MHz_XTAL Xerox Alto-II system clock (tagged 29.4MHz in the schematics) */ - 30'000'000, /* 30_MHz_XTAL Impera Magic Card */ - 30'209'800, /* 30.2098_MHz_XTAL Philips CD-i NTSC (1920x NTSC line rate) */ - 30'240'000, /* 30.24_MHz_XTAL Macintosh IIci RBV, 12- or 13-inch display */ - 30'476'180, /* 30.47618_MHz_XTAL Taito F3, JC, Under Fire */ - 30'800'000, /* 30.8_MHz_XTAL 15IE-00-013 */ - 31'279'500, /* 31.2795_MHz_XTAL Wyse WY-30+ */ - 31'334'400, /* 31.3344_MHz_XTAL Macintosh II */ - 31'684'000, /* 31.684_MHz_XTAL TeleVideo TVI-955 132-column display clock */ - 31'948'800, /* 31.9488_MHz_XTAL NEC PC-88xx, PC-98xx */ - 32'000'000, /* 32_MHz_XTAL - */ - 32'147'000, /* 32.147_MHz_XTAL Ampex 210+ 132-column display clock */ - 32'220'000, /* 32.22_MHz_XTAL Typically used on 90's Data East PCBs (close to 9x NTSC subcarrier which is 32.215905Mhz */ - 32'256'000, /* 32.256_MHz_XTAL Hitachi MB-6890 */ - 32'317'400, /* 32.3174_MHz_XTAL DEC VT330, VT340 */ - 32'530'470, /* 32.53047_MHz_XTAL Seta 2 */ - 32'640'000, /* 32.64_MHz_XTAL Vector 4 */ - 32'768'000, /* 32.768_MHz_XTAL Roland D-50 audio clock */ - 33'000'000, /* 33_MHz_XTAL Sega Model 3 video board */ - 33'264'000, /* 33.264_MHz_XTAL Hazeltine 1500 terminal */ - 33'330'000, /* 33.33_MHz_XTAL Sharp X68000 XVI */ - 33'333'000, /* 33.333_MHz_XTAL Sega Model 3 CPU board, Vegas */ - 33'333'333, /* 33.333333_MHz_XTAL Super Kaneko Nova System Sound clock with /2 divider */ - 33'833'000, /* 33.833_MHz_XTAL - */ - 33'868'800, /* 33.8688_MHz_XTAL Usually used to drive 90's Yamaha OPL/FM chips with /2 divider */ - 34'000'000, /* 34_MHz_XTAL Gaelco PCBs */ - 34'291'712, /* 34.291712_MHz_XTAL Fairlight CMI master card */ - 34'846'000, /* 34.846_MHz_XTAL Visual 550 */ - 35'469'000, /* 35.469_MHz_XTAL ZX Spectrum +2/+3 (~8x PAL subcarrier) */ - 35'640'000, /* 35.640_MHz_XTAL HDS200 132-column display clock */ - 35'834'400, /* 35.8344_MHz_XTAL Tab Products E-22 132-column display clock */ - 35'840'000, /* 35.84_MHz_XTAL Akai MPC 60 voice PCB */ - 35'904'000, /* 35.904_MHz_XTAL Used on HP98543 graphics board */ - 36'000'000, /* 36_MHz_XTAL Sega Model 1 video board */ - 36'864'000, /* 36.864_MHz_XTAL Unidesa Cirsa Rock 'n' Roll */ - 37'980'000, /* 37.98_MHz_XTAL Falco 5220 */ - 38'769'220, /* 38.76922_MHz_XTAL Namco System 21 video board */ - 38'863'630, /* 38.86363_MHz_XTAL Sharp X68000 15.98kHz video */ - 39'321'600, /* 39.3216_MHz_XTAL Sun 2/120 */ - 39'710'000, /* 39.71_MHz_XTAL Wyse WY-60 132-column display clock */ - 40'000'000, /* 40_MHz_XTAL - */ - 40'210'000, /* 40.21_MHz_XTAL Fairlight CMI IIx */ - 41'539'000, /* 41.539_MHz_XTAL IBM PS/2 132-column text mode */ - 42'000'000, /* 42_MHz_XTAL BMC A-00211 - Popo Bear */ - 42'105'200, /* 42.1052_MHz_XTAL NEC PC-88xx */ - 42'954'545, /* 42.954545_MHz_XTAL CPS3 (12x NTSC subcarrier)*/ - 43'320'000, /* 43.32_MHz_XTAL DEC VT420 */ - 44'100'000, /* 44.1_MHz_XTAL Subsino's Bishou Jan */ - 44'236'800, /* 44.2368_MHz_XTAL ReCo6502, Fortune 32:16 */ - 44'452'800, /* 44.4528_MHz_XTAL TeleVideo 965 */ - 44'900'000, /* 44.9_MHz_XTAL IBM 8514 1024x768 43.5Hz graphics */ - 45'000'000, /* 45_MHz_XTAL Eolith with Hyperstone CPUs */ - 45'158'400, /* 45.1584_MHz_XTAL Philips CD-i CDIC, Sega Model 2A video, Sega Model 3 CPU */ - 45'619'200, /* 45.6192_MHz_XTAL DEC VK100 */ - 45'830'400, /* 45.8304_MHz_XTAL Microterm 5510 */ - 46'615'120, /* 46.61512_MHz_XTAL Soundblaster 16 PCM base clock */ - 47'736'000, /* 47.736_MHz_XTAL Visual 100 */ - 48'000'000, /* 48_MHz_XTAL Williams/Midway Y/Z-unit system / SSV board */ - 48'384'000, /* 48.384_MHz_XTAL Namco NB-1 */ - 48'556'800, /* 48.5568_MHz_XTAL Wyse WY-85 */ - 48'654'000, /* 48.654_MHz_XTAL Qume QVT-201 */ - 48'660'000, /* 48.66_MHz_XTAL Zaxxon */ - 49'152'000, /* 49.152_MHz_XTAL Used on some Namco PCBs, Baraduke h/w, System 21, Super System 22 */ - 49'423'500, /* 49.4235_MHz_XTAL Wyse WY-185 */ - 50'000'000, /* 50_MHz_XTAL Williams/Midway T/W/V-unit system */ - 50'113'000, /* 50.113_MHz_XTAL Namco NA-1 (14x NTSC subcarrier)*/ - 50'349'000, /* 50.349_MHz_XTAL Sega System 18 (~3200x NTSC line rate) */ - 50'350'000, /* 50.35_MHz_XTAL Sharp X68030 video */ - 51'200'000, /* 51.2_MHz_XTAL Namco Super System 22 video clock */ - 52'000'000, /* 52_MHz_XTAL Cojag */ - 52'832'000, /* 52.832_MHz_XTAL Wang PC TIG video controller */ - 53'203'424, /* 53.203424_MHz_XTAL Master System, Mega Drive PAL (12x PAL subcarrier) */ - 53'693'175, /* 53.693175_MHz_XTAL PSX-based h/w, Sony ZN1-2-based (15x NTSC subcarrier) */ - 54'000'000, /* 54_MHz_XTAL Taito JC */ - 55'000'000, /* 55_MHz_XTAL Eolith Vega */ - 57'272'727, /* 57.272727_MHz_XTAL Psikyo SH2 with /2 divider (16x NTSC subcarrier)*/ - 57'283'200, /* 57.2832_MHz_XTAL Macintosh IIci RBV, 15-inch portrait display */ - 58'000'000, /* 58_MHz_XTAL Magic Reel (Play System) */ - 58'982'400, /* 58.9824_MHz_XTAL Wyse WY-65 */ - 59'292'000, /* 59.292_MHz_XTAL Data General D461 */ - 60'000'000, /* 60_MHz_XTAL ARM610 */ - 61'440'000, /* 61.44_MHz_XTAL Donkey Kong */ - 64'000'000, /* 64_MHz_XTAL BattleToads */ - 64'108'800, /* 64.1088_MHz_XTAL HP Topcat high-res */ - 66'000'000, /* 66_MHz_XTAL - */ - 66'666'700, /* 66.6667_MHz_XTAL Later Midway games */ - 67'737'600, /* 67.7376_MHz_XTAL PSX-based h/w, Sony ZN1-2-based */ - 68'850'000, /* 68.85_MHz_XTAL Wyse WY-50 */ - 69'551'990, /* 69.55199_MHz_XTAL Sharp X68000 31.5kHz video */ - 72'000'000, /* 72_MHz_XTAL Aristocrat MKV */ - 72'576'000, /* 72.576_MHz_XTAL Centipede, Millipede, Missile Command, Let's Go Bowling "Multipede" */ - 73'728'000, /* 73.728_MHz_XTAL Ms. Pac-Man/Galaga 20th Anniversary */ - 75'000'000, /* 75_MHz_XTAL Sony NEWS NWS-5000X */ - 77'414'400, /* 77.4144_MHz_XTAL NCD17c */ - 80'000'000, /* 80_MHz_XTAL ARM710 */ - 87'183'360, /* 87.18336_MHz_XTAL AT&T 630 MTG */ - 92'940'500, /* 92.9405_MHz_XTAL Sun cgthree */ - 96'000'000, /* 96_MHz_XTAL Acorn A680 */ - 99'522'000, /* 99.522_MHz_XTAL Radius Two Page Display */ - 100'000'000, /* 100_MHz_XTAL PSX-based Namco System 12, Vegas, Sony ZN1-2-based */ - 101'491'200, /* 101.4912_MHz_XTAL PSX-based Namco System 10 */ - 105'561'000, /* 105.561_MHz_XTAL Sun cgsix */ - 108'108'000, /* 108.108_MHz_XTAL HP 98550 high-res color card */ - 120'000'000, /* 120_MHz_XTAL Astro Corp.'s Stone Age */ - 200'000'000 /* 200_MHz_XTAL Base SH4 CPU (Naomi, Hikaru etc.) */ + 32'768, // 32.768_kHz_XTAL Used to drive RTC chips + 38'400, // 38.4_kHz_XTAL Resonator + 384'000, // 384_kHz_XTAL Resonator - Commonly used for driving OKI MSM5205 + 400'000, // 400_kHz_XTAL Resonator - OKI MSM5205 on Great Swordman h/w + 430'000, // 430_kHz_XTAL Resonator + 455'000, // 455_kHz_XTAL Resonator - OKI MSM5205 on Gladiator h/w + 500'000, // 500_kHz_XTAL Resonator - MIDI clock on various synthesizers (31250 * 16) + 512'000, // 512_kHz_XTAL Resonator - Toshiba TC8830F + 600'000, // 600_kHz_XTAL - + 640'000, // 640_kHz_XTAL Resonator - NEC UPD7759, Texas Instruments Speech Chips @ 8khz + 960'000, // 960_kHz_XTAL Resonator - Xerox Notetaker Keyboard UART + 1'000'000, // 1_MHz_XTAL Used to drive OKI M6295 chips + 1'008'000, // 1.008_MHz_XTAL Acorn Microcomputer (System 1) + 1'056'000, // 1.056_MHz_XTAL Resonator - OKI M6295 on Trio The Punch h/w + 1'294'400, // 1.2944_MHz_XTAL BBN BitGraph PSG + 1'600'000, // 1.6_MHz_XTAL Resonator - Roland TR-707 + 1'689'600, // 1.6896_MHz_XTAL Diablo 1355WP Printer + 1'750'000, // 1.75_MHz_XTAL RCA CDP1861 + 1'797'100, // 1.7971_MHz_XTAL SWTPC 6800 (with MIKBUG) + 1'843'200, // 1.8432_MHz_XTAL Bondwell 12/14 + 2'000'000, // 2_MHz_XTAL - + 2'012'160, // 2.01216_MHz_XTAL Cidelsa Draco sound board + 2'097'152, // 2.097152_MHz_XTAL Icatel 1995 - Brazilian public payphone + 2'250'000, // 2.25_MHz_XTAL Resonator - YM2154 on Yamaha PSR-60 & PSR-70 + 2'376'000, // 2.376_MHz_XTAL CIT-101 keyboard + 2'457'600, // 2.4576_MHz_XTAL Atari ST MFP + 2'470'000, // 2.47_MHz_XTAL CSA2.47MG ceramic oscillator - Casio CZ-1 + 2'500'000, // 2.5_MHz_XTAL Janken Man units + 2'600'000, // 2.6_MHz_XTAL Sharp PC-1500 + 2'700'000, // 2.7_MHz_XTAL Resonator - YM2154 on Yamaha RX15 + 2'950'000, // 2.95_MHz_XTAL Playmatic MPU-C, MPU-III & Sound-3 + 3'000'000, // 3_MHz_XTAL Probably only used to drive 68705 or similar MCUs on 80's Taito PCBs + 3'072'000, // 3.072_MHz_XTAL INS 8520 input clock rate + 3'120'000, // 3.12_MHz_XTAL SP0250 clock on Gottlieb games + 3'276'800, // 3.2768_MHz_XTAL SP0256 clock in Speech Synthesis for Dragon 32 + 3'300'000, // 3.3_MHz_XTAL LC 80 (export) + 3'521'280, // 3.52128_MHz_XTAL RCA COSMAC VIP + 3'546'800, // 3.5468_MHz_XTAL Atari 400 PAL + 3'546'894, // 3.546894_MHz_XTAL Atari 2600 PAL + 3'547'000, // 3.547_MHz_XTAL Philips G7200, Philips C7240 + 3'562'500, // 3.5625_MHz_XTAL Jopac JO7400 + 3'570'000, // 3.57_MHz_XTAL Telmac TMC-600 + 3'578'640, // 3.57864_MHz_XTAL Atari Portfolio PCD3311T + 3'579'000, // 3.579_MHz_XTAL BeebOPL + 3'579'545, // 3.579545_MHz_XTAL NTSC color subcarrier, extremely common, used on 100's of PCBs (Keytronic custom part #48-300-010 is equivalent) + 3'579'575, // 3.579575_MHz_XTAL Atari 2600 NTSC + 3'580'000, // 3.58_MHz_XTAL Resonator - Ritam Monty + 3'680'000, // 3.68_MHz_XTAL Resonator - Baud rate clock for the 6551 in the MTU-130 + 3'686'400, // 3.6864_MHz_XTAL Baud rate clock for MC68681 and similar UARTs + 3'840'000, // 3.84_MHz_XTAL Fairlight CMI Alphanumeric Keyboard + 3'900'000, // 3.9_MHz_XTAL Resonator - Used on some Fidelity boards + 3'932'160, // 3.93216_MHz_XTAL Apple Lisa COP421 (197-0016A) + 4'000'000, // 4_MHz_XTAL - + 4'032'000, // 4.032_MHz_XTAL GRiD Compass modem board + 4'096'000, // 4.096_MHz_XTAL Used to drive OKI M9810 chips + 4'194'304, // 4.194304_MHz_XTAL Used to drive MC146818 / Nintendo Game Boy + 4'220'000, // 4.220_MHz_XTAL Used to drive OKI M6295 chips on some Namco boards, usually with /4 divider + 4'224'000, // 4.224_MHz_XTAL Used to drive OKI M6295 chips, usually with /4 divider + 4'410'000, // 4.41_MHz_XTAL Pioneer PR-8210 ldplayer + 4'433'610, // 4.43361_MHz_XTAL Cidelsa Draco + 4'433'619, // 4.433619_MHz_XTAL PAL color subcarrier (technically 4.43361875mhz) + 4'608'000, // 4.608_MHz_XTAL Luxor ABC-77 keyboard (Keytronic custom part #48-300-107 is equivalent) + 4'915'200, // 4.9152_MHz_XTAL - + 4'946'800, // 4.9468_MHz_XTAL Casio CPS-2000 + 4'946'864, // 4.946864_MHz_XTAL Casiotone 8000 + 4'952'000, // 4.952_MHz_XTAL IGS M036 based mahjong games, for TT5665 sound chip + 5'000'000, // 5_MHz_XTAL Mutant Night + 5'068'800, // 5.0688_MHz_XTAL Usually used as MC2661 or COM8116 baud rate clock + 5'185'000, // 5.185_MHz_XTAL Intel INTELLEC® 4 + 5'370'000, // 5.37_MHz_XTAL HP 95LX + 5'460'000, // 5.46_MHz_XTAL ec1840 and ec1841 keyboard + 5'500'000, // 5.5_MHz_XTAL Yamaha PSS-480 + 5'529'600, // 5.5296_MHz_XTAL Kontron PSI98 keyboard + 5'626'000, // 5.626_MHz_XTAL RCA CDP1869 PAL dot clock + 5'659'200, // 5.6592_MHz_XTAL Digilog 320 dot clock + 5'670'000, // 5.67_MHz_XTAL RCA CDP1869 NTSC dot clock + 5'714'300, // 5.7143_MHz_XTAL Cidelsa Destroyer, TeleVideo serial keyboards + 5'856'000, // 5.856_MHz_XTAL HP 3478A Multimeter + 5'911'000, // 5.911_MHz_XTAL Philips Videopac Plus G7400 + 5'990'400, // 5.9904_MHz_XTAL Luxor ABC 800 keyboard (Keytronic custom part #48-300-008 is equivalent) + 6'000'000, // 6_MHz_XTAL American Poker II, Taito SJ System + 6'048'000, // 6.048_MHz_XTAL Hektor II + 6'144'000, // 6.144_MHz_XTAL Used on Alpha Denshi early 80's games sound board, Casio FP-200 and Namco Universal System 16 + 6'400'000, // 6.4_MHz_XTAL Textel Compact + 6'500'000, // 6.5_MHz_XTAL Jupiter Ace, Roland QDD interface + 6'880'000, // 6.88_MHz_XTAL Barcrest MPU4 + 6'900'000, // 6.9_MHz_XTAL BBN BitGraph CPU + 7'000'000, // 7_MHz_XTAL Jaleco Mega System PCBs + 7'056'000, // 7.056_MHz_XTAL Alesis QS FXCHIP (LCM of 44.1 kHz and 48 kHz) + 7'159'090, // 7.15909_MHz_XTAL Blood Bros (2x NTSC subcarrier) + 7'200'000, // 7.2_MHz_XTAL Novag Constellation (later models, with /2 divider), Kawai K1 keyscan IC + 7'372'800, // 7.3728_MHz_XTAL - + 7'680'000, // 7.68_MHz_XTAL Psion Series 3 + 7'864'300, // 7.8643_MHz_XTAL Used on InterFlip games as video clock + 7'987'000, // 7.987_MHz_XTAL PC9801-86 YM2608 clock + 7'995'500, // 7.9955_MHz_XTAL Used on Electronic Devices Italy Galaxy Gunners sound board + 8'000'000, // 8_MHz_XTAL Extremely common, used on 100's of PCBs + 8'053'000, // 8.053_MHz_XTAL Mad Motor + 8'200'000, // 8.2_MHz_XTAL Universal Mr. Do - Model 8021 PCB + 8'388'000, // 8.388_MHz_XTAL Nintendo Game Boy Color + 8'448'000, // 8.448_MHz_XTAL Banpresto's Note Chance - Used to drive OKI M6295 chips, usually with /8 divider + 8'467'200, // 8.4672_MHz_XTAL Subsino's Ying Hua Lian + 8'664'000, // 8.664_MHz_XTAL Touchmaster + 8'700'000, // 8.7_MHz_XTAL Tandberg TDV 2324 + 8'860'000, // 8.86_MHz_XTAL AlphaTantel + 8'867'000, // 8.867_MHz_XTAL Philips G7400 (~2x PAL subcarrier) + 8'867'236, // 8.867236_MHz_XTAL RCA CDP1869 PAL color clock (~2x PAL subcarrier) + 8'867'238, // 8.867238_MHz_XTAL ETI-660 (~2x PAL subcarrier) + 8'945'000, // 8.945_MHz_XTAL Hit Me + 8'960'000, // 8.96_MHz_XTAL Casio CZ-101 (divided by 2 for Music LSI) + 9'000'000, // 9_MHz_XTAL Homedata PCBs + 9'216'000, // 9.216_MHz_XTAL Univac UTS 20 + 9'263'750, // 9.263750_MHz_XTAL Sai Yu Gou Ma Roku bootleg + 9'400'000, // 9.4_MHz_XTAL Yamaha MU-5 and TG-100 + 9'426'500, // 9.4265_MHz_XTAL Yamaha DX7, and DX9 + 9'600'000, // 9.6_MHz_XTAL WD37C65 second clock (for 300 KB/sec rate) + 9'732'000, // 9.732_MHz_XTAL CTA Invader + 9'828'000, // 9.828_MHz_XTAL Universal PCBs + 9'830'400, // 9.8304_MHz_XTAL Epson PX-8 + 9'832'000, // 9.832_MHz_XTAL Robotron A7150 + 9'877'680, // 9.87768_MHz_XTAL Microterm 420 + 9'987'000, // 9.987_MHz_XTAL Crazy Balloon + 10'000'000, // 10_MHz_XTAL - + 10'137'600, // 10.1376_MHz_XTAL Wyse WY-100 + 10'240'000, // 10.240_MHz_XTAL Stella 8085 based fruit machines + 10'245'000, // 10.245_MHz_XTAL PES Speech box + 10'380'000, // 10.38_MHz_XTAL Fairlight Q219 Lightpen/Graphics Card + 10'480'000, // 10.48_MHz_XTAL System-80 (50 Hz) + 10'500'000, // 10.5_MHz_XTAL Agat-7 + 10'595'000, // 10.595_MHz_XTAL Mad Alien + 10'644'000, // 10.644_MHz_XTAL Northwest Digitial Systems GP-19 + 10'644'500, // 10.6445_MHz_XTAL TRS-80 Model I + 10'687'500, // 10.6875_MHz_XTAL BBC Bridge Companion + 10'694'250, // 10.69425_MHz_XTAL Xerox 820 + 10'717'200, // 10.7172_MHz_XTAL Eltec EurocomII + 10'730'000, // 10.73_MHz_XTAL Ruleta RE-900 VDP Clock + 10'733'000, // 10.733_MHz_XTAL The Fairyland Story + 10'738'000, // 10.738_MHz_XTAL Pokerout (poker+breakout) TMS9129 VDP Clock + 10'738'635, // 10.738635_MHz_XTAL TMS9918 family (3x NTSC subcarrier) + 10'816'000, // 10.816_MHz_XTAL Universal 1979-1980 (Cosmic Alien, etc) + 10'886'400, // 10.8864_MHz_XTAL Systel System 100 + 10'920'000, // 10.92_MHz_XTAL ADDS Viewpoint 60, Viewpoint A2 + 11'000'000, // 11_MHz_XTAL Mario I8039 sound + 11'004'000, // 11.004_MHz_XTAL TI 911 VDT + 11'055'000, // 11.055_MHz_XTAL Atari Tank 8 + 11'059'200, // 11.0592_MHz_XTAL Used with MCS-51 to generate common baud rates + 11'200'000, // 11.2_MHz_XTAL New York, New York + 11'289'000, // 11.289_MHz_XTAL Vanguard + 11'289'600, // 11.2896_MHz_XTAL Frantic Fred + 11'400'000, // 11.4_MHz_XTAL HP 9845 + 11'668'800, // 11.6688_MHz_XTAL Gameplan pixel clock + 11'730'000, // 11.73_MHz_XTAL Irem M-11 + 11'800'000, // 11.8_MHz_XTAL IBM PC Music Feature Card + 11'980'800, // 11.9808_MHz_XTAL Luxor ABC 80 + 12'000'000, // 12_MHz_XTAL Extremely common, used on 100's of PCBs + 12'057'600, // 12.0576_MHz_XTAL Poly 1 (38400 * 314) + 12'096'000, // 12.096_MHz_XTAL Some early 80's Atari games + 12'288'000, // 12.288_MHz_XTAL Sega Model 3 digital audio board + 12'292'000, // 12.292_MHz_XTAL Northwest Digitial Systems GP-19 + 12'324'000, // 12.324_MHz_XTAL Otrona Attache + 12'335'600, // 12.3356_MHz_XTAL RasterOps ColorBoard 264 (~784x NTSC line rate) + 12'472'500, // 12.4725_MHz_XTAL Bonanza's Mini Boy 7 + 12'480'000, // 12.48_MHz_XTAL TRS-80 Model II + 12'500'000, // 12.5_MHz_XTAL Red Alert audio board + 12'638'000, // 12.638_MHz_XTAL Exidy Sorcerer + 12'672'000, // 12.672_MHz_XTAL TRS-80 Model 4 80*24 video + 12'800'000, // 12.8_MHz_XTAL Cave CV1000 + 12'854'400, // 12.8544_MHz_XTAL Alphatronic P3 + 12'936'000, // 12.936_MHz_XTAL CDC 721 + 12'979'200, // 12.9792_MHz_XTAL Exidy 440 + 13'000'000, // 13_MHz_XTAL STM Pied Piper dot clock + 13'300'000, // 13.3_MHz_XTAL BMC bowling + 13'330'560, // 13.33056_MHz_XTAL Taito L + 13'333'000, // 13.333_MHz_XTAL Ojanko High School + 13'400'000, // 13.4_MHz_XTAL TNK3, Ikari Warriors h/w + 13'478'400, // 13.4784_MHz_XTAL TeleVideo 970 80-column display clock + 13'495'200, // 13.4952_MHz_XTAL Used on Shadow Force pcb and maybe other Technos pcbs? + 13'500'000, // 13.5_MHz_XTAL Microbee + 13'516'800, // 13.5168_MHz_XTAL Kontron KDT6 + 13'560'000, // 13.560_MHz_XTAL Tong Zi Maque + 13'608'000, // 13.608_MHz_XTAL TeleVideo 910 & 925 + 13'824'000, // 13.824_MHz_XTAL Robotron PC-1715 display circuit + 13'977'600, // 13.9776_MHz_XTAL Kaypro II dot clock + 14'000'000, // 14_MHz_XTAL - + 14'112'000, // 14.112_MHz_XTAL Timex/Sinclair TS2068 + 14'192'640, // 14.19264_MHz_XTAL Central Data 2650 + 14'218'000, // 14.218_MHz_XTAL Dragon + 14'250'450, // 14.25045_MHz_XTAL Apple II Europlus + 14'300'000, // 14.3_MHz_XTAL Agat-7 + 14'314'000, // 14.314_MHz_XTAL Taito TTL Board + 14'318'181, // 14.318181_MHz_XTAL Extremely common, used on 100's of PCBs (4x NTSC subcarrier) + 14'349'600, // 14.3496_MHz_XTAL Roland S-50 VDP + 14'469'000, // 14.469_MHz_XTAL Esprit Systems Executive 10/102 + 14'580'000, // 14.58_MHz_XTAL Fortune 32:16 Video Controller + 14'705'882, // 14.705882_MHz_XTAL Aleck64 + 14'728'000, // 14.728_MHz_XTAL ADM 36 + 14'742'800, // 14.7428_MHz_XTAL ADM 23 + 14'745'000, // 14.745_MHz_XTAL Synertek KTM-3 + 14'745'600, // 14.7456_MHz_XTAL Namco System 12 & System Super 22/23 for JVS + 14'746'000, // 14.746_MHz_XTAL Namco System 10 MGEXIO + 14'784'000, // 14.784_MHz_XTAL Zenith Z-29 + 14'916'000, // 14.916_MHz_XTAL ADDS Viewpoint 122 + 14'976'000, // 14.976_MHz_XTAL CIT-101 80-column display clock + 15'000'000, // 15_MHz_XTAL Sinclair QL, Amusco Poker + 15'148'800, // 15.1488_MHz_XTAL Zentec 9002/9003 + 15'206'400, // 15.2064_MHz_XTAL Falco TS-1 + 15'288'000, // 15.288_MHz_XTAL DEC VT220 80-column display clock + 15'300'720, // 15.30072_MHz_XTAL Microterm 420 + 15'360'000, // 15.36_MHz_XTAL Visual 1050 + 15'400'000, // 15.4_MHz_XTAL DVK KSM + 15'468'480, // 15.46848_MHz_XTAL Bank Panic h/w, Sega G80 + 15'582'000, // 15.582_MHz_XTAL Zentec Zephyr + 15'625'000, // 15.625_MHz_XTAL Zaccaria The Invaders + 15'667'200, // 15.6672_MHz_XTAL Apple Macintosh + 15'700'000, // 15.7_MHz_XTAL Motogonki + 15'741'000, // 15.741_MHz_XTAL DECmate II 80-column display clock + 15'897'600, // 15.8976_MHz_XTAL IAI Swyft + 15'920'000, // 15.92_MHz_XTAL HP Integral PC + 15'930'000, // 15.93_MHz_XTAL ADM 12 + 15'974'400, // 15.9744_MHz_XTAL Osborne 1 (9600 * 52 * 32) + 16'000'000, // 16_MHz_XTAL Extremely common, used on 100's of PCBs + 16'097'280, // 16.09728_MHz_XTAL DEC VT240 (1024 * 262 * 60) + 16'128'000, // 16.128_MHz_XTAL Fujitsu FM-7 + 16'200'000, // 16.2_MHz_XTAL Debut + 16'257'000, // 16.257_MHz_XTAL IBM PC MDA & EGA + 16'313'000, // 16.313_MHz_XTAL Micro-Term ERGO 201 + 16'364'000, // 16.364_MHz_XTAL Corvus Concept + 16'384'000, // 16.384_MHz_XTAL - + 16'400'000, // 16.4_MHz_XTAL MS 6102 + 16'537'000, // 16.537_MHz_XTAL Falco terminals 80-column clock + 16'572'000, // 16.572_MHz_XTAL Micro-Term ACT-5A + 16'588'800, // 16.5888_MHz_XTAL SM 7238 + 16'666'600, // 16.6666_MHz_XTAL Firebeat GCU + 16'667'000, // 16.667_MHz_XTAL Visual XDS-19P + 16'669'800, // 16.6698_MHz_XTAL Qume QVT-102 + 16'670'000, // 16.67_MHz_XTAL - + 16'777'216, // 16.777216_MHz_XTAL Nintendo Game Boy Advance + 16'934'400, // 16.9344_MHz_XTAL Usually used to drive 90's Yamaha OPL/FM chips (44100 * 384) + 16'960'000, // 16.960_MHz_XTAL Esprit Systems Executive 10/102 + 17'010'000, // 17.01_MHz_XTAL Epic 14E + 17'064'000, // 17.064_MHz_XTAL Memorex 1377 + 17'074'800, // 17.0748_MHz_XTAL SWTPC 8212 + 17'320'000, // 17.320_MHz_XTAL Visual 50 + 17'350'000, // 17.35_MHz_XTAL ITT Courier 1700 + 17'360'000, // 17.36_MHz_XTAL OMTI Series 10 SCSI controller + 17'430'000, // 17.43_MHz_XTAL Videx Videoterm + 17'550'000, // 17.55_MHz_XTAL HP 264x display clock (50 Hz configuration) + 17'600'000, // 17.6_MHz_XTAL LSI Octopus + 17'734'470, // 17.73447_MHz_XTAL 4x PAL subcarrier + 17'734'472, // 17.734472_MHz_XTAL 4x PAL subcarrier - All of these exist, exact 4x PAL is actually 17'734'475 + 17'734'475, // 17.734475_MHz_XTAL 4x PAL subcarrier - " + 17'734'476, // 17.734476_MHz_XTAL 4x PAL subcarrier - " + 17'812'000, // 17.812_MHz_XTAL Videopac C52 + 17'971'200, // 17.9712_MHz_XTAL Compucolor II, Hazeltine Esprit III + 18'000'000, // 18_MHz_XTAL S.A.R, Ikari Warriors 3 + 18'414'000, // 18.414_MHz_XTAL Ann Arbor Ambassador + 18'432'000, // 18.432_MHz_XTAL Extremely common, used on 100's of PCBs (48000 * 384) + 18'480'000, // 18.48_MHz_XTAL Wyse WY-100 video + 18'575'000, // 18.575_MHz_XTAL Visual 102, Visual 220 + 18'600'000, // 18.6_MHz_XTAL Teleray Model 10 + 18'720'000, // 18.72_MHz_XTAL Nokia MikroMikko 1 + 18'867'000, // 18.867_MHz_XTAL Decision Data IS-482 + 18'869'600, // 18.8696_MHz_XTAL Memorex 2178 + 19'170'000, // 19.17_MHz_XTAL Ericsson ISA8 Monochrome HR Graphics Board + 19'339'600, // 19.3396_MHz_XTAL TeleVideo TVI-955 80-column display clock + 19'584'000, // 19.584_MHz_XTAL ADM-42 + 19'600'000, // 19.6_MHz_XTAL Universal Mr. Do - Model 8021 PCB + 19'602'000, // 19.602_MHz_XTAL Ampex 210+ 80-column display clock + 19'660'800, // 19.6608_MHz_XTAL Euro League (bootleg), labeled as "UKI 19.6608 20PF" + 19'661'400, // 19.6614_MHz_XTAL Wyse WY-30 + 19'718'400, // 19.7184_MHz_XTAL Informer 207/100 + 19'923'000, // 19.923_MHz_XTAL Cinematronics vectors + 19'968'000, // 19.968_MHz_XTAL Used mostly by some Taito games + 20'000'000, // 20_MHz_XTAL - + 20'160'000, // 20.16_MHz_XTAL Nintendo 8080 + 20'275'200, // 20.2752_MHz_XTAL TRS-80 Model III + 20'282'000, // 20.282_MHz_XTAL Northwest Digitial Systems GP-19 + 20'375'040, // 20.37504_MHz_XTAL Apple Lisa dot clock (197-0019A) + 20'625'000, // 20.625_MHz_XTAL SM 7238 + 20'790'000, // 20.79_MHz_XTAL Blockade-hardware Gremlin games + 21'000'000, // 21_MHz_XTAL Lock-On pixel clock + 21'052'600, // 21.0526_MHz_XTAL NEC PC-98xx pixel clock + 21'060'000, // 21.06_MHz_XTAL HP 264x display clock (60 Hz configuration) + 21'254'400, // 21.2544_MHz_XTAL TeleVideo 970 132-column display clock + 21'281'370, // 21.28137_MHz_XTAL Radica Tetris (PAL) + 21'300'000, // 21.3_MHz_XTAL - + 21'328'100, // 21.3281_MHz_XTAL Philips NMS8245 + 21'477'272, // 21.477272_MHz_XTAL BMC bowling, some Data East 90's games, Vtech Socrates; (6x NTSC subcarrier) + 21'667'500, // 21.6675_MHz_XTAL AT&T 610 80-column display clock + 21'756'600, // 21.7566_MHz_XTAL Tab Products E-22 80-column display clock + 22'000'000, // 22_MHz_XTAL - + 22'032'000, // 22.032_MHz_XTAL Intellec Series II I/O controller + 22'096'000, // 22.096_MHz_XTAL ADDS Viewpoint 122 + 22'118'400, // 22.1184_MHz_XTAL Amusco Poker + 22'168'000, // 22.168_MHz_XTAL Sony HB-10P VDP (5x PAL subcarrier) + 22'248'000, // 22.248_MHz_XTAL Quantel DPB-7000 + 22'321'000, // 22.321_MHz_XTAL Apple LaserWriter II NT + 22'464'000, // 22.464_MHz_XTAL CIT-101 132-column display clock + 22'579'000, // 22.579_MHz_XTAL Sega System H1 SCSP clock + 22'579'200, // 22.5792_MHz_XTAL Enhanced Apple Digital Sound Chip clock (44100 * 512) + 22'656'000, // 22.656_MHz_XTAL Super Pinball Action (~1440x NTSC line rate) + 22'680'000, // 22.680_MHz_XTAL HDS200 80-columns display clock + 22'896'000, // 22.896_MHz_XTAL DEC VT220 132-column display clock + 23'200'000, // 23.2_MHz_XTAL Roland JV-80 & JV-880 PCM clock + 23'814'000, // 23.814_MHz_XTAL TeleVideo TVI-912, 920 & 950 + 23'961'600, // 23.9616_MHz_XTAL Osborne 4 (Vixen) + 24'000'000, // 24_MHz_XTAL Mario, 80's Data East games, 80's Konami games + 24'073'400, // 24.0734_MHz_XTAL DEC Rainbow 100 + 24'167'829, // 24.167829_MHz_XTAL Neo Geo AES rev. 3-3 and later (~1536x NTSC line rate) + 24'270'000, // 24.27_MHz_XTAL CIT-101XL + 24'300'000, // 24.3_MHz_XTAL ADM 36 132-column display clock + 24'576'000, // 24.576_MHz_XTAL Pole Position h/w, Model 3 CPU board + 24'883'200, // 24.8832_MHz_XTAL DEC VT100 + 25'000'000, // 25_MHz_XTAL Namco System 22, Taito GNET, Dogyuun h/w + 25'174'800, // 25.1748_MHz_XTAL Sega System 16A/16B (1600x NTSC line rate) + 25'175'000, // 25.175_MHz_XTAL IBM MCGA/VGA 320/640-pixel graphics + 25'200'000, // 25.2_MHz_XTAL Tektronix 4404 video clock + 25'398'360, // 25.39836_MHz_XTAL Tandberg TDV 2324 + 25'400'000, // 25.4_MHz_XTAL PC9801-86 PCM base clock + 25'447'000, // 25.447_MHz_XTAL Namco EVA3A (Funcube2) + 25'771'500, // 25.7715_MHz_XTAL HP-2622A + 25'920'000, // 25.92_MHz_XTAL ADDS Viewpoint 60 + 26'000'000, // 26_MHz_XTAL Gaelco PCBs + 26'195'000, // 26.195_MHz_XTAL Roland JD-800 + 26'366'000, // 26.366_MHz_XTAL DEC VT320 + 26'580'000, // 26.58_MHz_XTAL Wyse WY-60 80-column display clock + 26'590'906, // 26.590906_MHz_XTAL Atari Jaguar NTSC + 26'593'900, // 26.5939_MHz_XTAL Atari Jaguar PAL + 26'601'712, // 26.601712_MHz_XTAL Astro Corp.'s Show Hand, PAL Vtech/Yeno Socrates (6x PAL subcarrier) + 26'666'000, // 26.666_MHz_XTAL Imagetek I4220/I4300 + 26'666'666, // 26.666666_MHz_XTAL Irem M92 but most use 27MHz + 26'670'000, // 26.670_MHz_XTAL Namco EVA + 26'686'000, // 26.686_MHz_XTAL Typically used on 90's Taito PCBs to drive the custom chips + 26'800'000, // 26.8_MHz_XTAL SAA7110 TV decoder + 26'824'000, // 26.824_MHz_XTAL Astro Corp.'s Zoo + 26'880'000, // 26.88_MHz_XTAL Roland RF5C36/SA-16 clock (30000 * 896) + 26'989'200, // 26.9892_MHz_XTAL TeleVideo 965 + 27'000'000, // 27_MHz_XTAL Some Banpresto games macrossp, Irem M92 and 90's Toaplan games, Pinnacle Zoran based PCI cards + 27'164'000, // 27.164_MHz_XTAL Typically used on 90's Taito PCBs to drive the custom chips + 27'210'900, // 27.2109_MHz_XTAL LA Girl + 27'562'000, // 27.562_MHz_XTAL Visual 220 + 27'720'000, // 27.72_MHz_XTAL AT&T 610 132-column display clock + 27'956'000, // 27.956_MHz_XTAL CIT-101e 132-column display clock + 28'000'000, // 28_MHz_XTAL Sega System H1 SH2 clock, Kyukyoku Tiger / Twin Cobra + 28'224'000, // 28.224_MHz_XTAL Roland JD-800 + 28'322'000, // 28.322_MHz_XTAL Saitek RISC 2500, Mephisto Montreux + 28'375'160, // 28.37516_MHz_XTAL Amiga PAL systems + 28'432'000, // 28.432_MHz_XTAL Fuuki FG-3J MAIN-J PCB + 28'475'000, // 28.475_MHz_XTAL CoCo 3 PAL + 28'480'000, // 28.48_MHz_XTAL Chromatics CGC-7900 + 28'636'000, // 28.636_MHz_XTAL Super Kaneko Nova System + 28'636'363, // 28.636363_MHz_XTAL Later Leland games and Atari GT, Amiga NTSC, Raiden2 h/w (8x NTSC subcarrier), NEC PC-88xx + 28'640'000, // 28.64_MHz_XTAL Fuuki FG-1c AI AM-2 PCB + 28'700'000, // 28.7_MHz_XTAL - + 29'376'000, // 29.376_MHz_XTAL Qume QVT-103 + 29'491'200, // 29.4912_MHz_XTAL Xerox Alto-II system clock (tagged 29.4MHz in the schematics) + 30'000'000, // 30_MHz_XTAL Impera Magic Card + 30'209'800, // 30.2098_MHz_XTAL Philips CD-i NTSC (1920x NTSC line rate) + 30'240'000, // 30.24_MHz_XTAL Macintosh IIci RBV, 12- or 13-inch display + 30'476'180, // 30.47618_MHz_XTAL Taito F3, JC, Under Fire + 30'800'000, // 30.8_MHz_XTAL 15IE-00-013 + 31'279'500, // 31.2795_MHz_XTAL Wyse WY-30+ + 31'334'400, // 31.3344_MHz_XTAL Macintosh II + 31'684'000, // 31.684_MHz_XTAL TeleVideo TVI-955 132-column display clock + 31'948'800, // 31.9488_MHz_XTAL NEC PC-88xx, PC-98xx + 32'000'000, // 32_MHz_XTAL - + 32'147'000, // 32.147_MHz_XTAL Ampex 210+ 132-column display clock + 32'215'900, // 32.2159_MHz_XTAL Sega System 32, Sega Game Gear (close to 9x NTSC subcarrier which is 32.215905Mhz + 32'220'000, // 32.22_MHz_XTAL Typically used on 90's Data East PCBs + 32'223'800, // 32.2238_MHz_XTAL Sony SMC-777 (~2048x NTSC line rate) + 32'256'000, // 32.256_MHz_XTAL Hitachi MB-6890 + 32'317'400, // 32.3174_MHz_XTAL DEC VT330, VT340 + 32'530'470, // 32.53047_MHz_XTAL Seta 2 + 32'640'000, // 32.64_MHz_XTAL Vector 4 + 32'768'000, // 32.768_MHz_XTAL Roland D-50 audio clock + 33'000'000, // 33_MHz_XTAL Sega Model 3 video board + 33'264'000, // 33.264_MHz_XTAL Hazeltine 1500 terminal + 33'330'000, // 33.33_MHz_XTAL Sharp X68000 XVI + 33'333'000, // 33.333_MHz_XTAL Sega Model 3 CPU board, Vegas + 33'333'333, // 33.333333_MHz_XTAL Super Kaneko Nova System Sound clock with /2 divider + 33'833'000, // 33.833_MHz_XTAL - + 33'868'800, // 33.8688_MHz_XTAL Usually used to drive 90's Yamaha OPL/FM chips with /2 divider + 34'000'000, // 34_MHz_XTAL Gaelco PCBs + 34'291'712, // 34.291712_MHz_XTAL Fairlight CMI master card + 34'846'000, // 34.846_MHz_XTAL Visual 550 + 35'452'500, // 35.4525_MHz_XTAL Nokia MikroMikko 2 + 35'469'000, // 35.469_MHz_XTAL ZX Spectrum +2/+3 (~8x PAL subcarrier) + 35'640'000, // 35.640_MHz_XTAL HDS200 132-column display clock + 35'834'400, // 35.8344_MHz_XTAL Tab Products E-22 132-column display clock + 35'840'000, // 35.84_MHz_XTAL Akai MPC 60 voice PCB + 35'904'000, // 35.904_MHz_XTAL Used on HP98543 graphics board + 36'000'000, // 36_MHz_XTAL Sega Model 1 video board + 36'864'000, // 36.864_MHz_XTAL Unidesa Cirsa Rock 'n' Roll + 37'980'000, // 37.98_MHz_XTAL Falco 5220 + 38'769'220, // 38.76922_MHz_XTAL Namco System 21 video board + 38'863'630, // 38.86363_MHz_XTAL Sharp X68000 15.98kHz video + 39'321'600, // 39.3216_MHz_XTAL Sun 2/120 + 39'710'000, // 39.71_MHz_XTAL Wyse WY-60 132-column display clock + 40'000'000, // 40_MHz_XTAL - + 40'210'000, // 40.21_MHz_XTAL Fairlight CMI IIx + 41'539'000, // 41.539_MHz_XTAL IBM PS/2 132-column text mode + 42'000'000, // 42_MHz_XTAL BMC A-00211 - Popo Bear + 42'105'200, // 42.1052_MHz_XTAL NEC PC-88xx + 42'954'545, // 42.954545_MHz_XTAL CPS3 (12x NTSC subcarrier) + 43'320'000, // 43.32_MHz_XTAL DEC VT420 + 44'000'000, // 44_MHz_XTAL VGame slots + 44'100'000, // 44.1_MHz_XTAL Subsino's Bishou Jan + 44'236'800, // 44.2368_MHz_XTAL ReCo6502, Fortune 32:16 + 44'452'800, // 44.4528_MHz_XTAL TeleVideo 965 + 44'900'000, // 44.9_MHz_XTAL IBM 8514 1024x768 43.5Hz graphics + 45'000'000, // 45_MHz_XTAL Eolith with Hyperstone CPUs + 45'158'400, // 45.1584_MHz_XTAL Philips CD-i CDIC, Sega Model 2A video, Sega Model 3 CPU + 45'619'200, // 45.6192_MHz_XTAL DEC VK100 + 45'830'400, // 45.8304_MHz_XTAL Microterm 5510 + 46'615'120, // 46.61512_MHz_XTAL Soundblaster 16 PCM base clock + 47'736'000, // 47.736_MHz_XTAL Visual 100 + 48'000'000, // 48_MHz_XTAL Williams/Midway Y/Z-unit system / SSV board + 48'384'000, // 48.384_MHz_XTAL Namco NB-1 + 48'556'800, // 48.5568_MHz_XTAL Wyse WY-85 + 48'654'000, // 48.654_MHz_XTAL Qume QVT-201 + 48'660'000, // 48.66_MHz_XTAL Zaxxon + 48'940'000, // 48.94_MHz_XTAL Queen Bee New + 49'152'000, // 49.152_MHz_XTAL Used on some Namco PCBs, Baraduke h/w, System 21, Super System 22 + 49'423'500, // 49.4235_MHz_XTAL Wyse WY-185 + 50'000'000, // 50_MHz_XTAL Williams/Midway T/W/V-unit system + 50'113'000, // 50.113_MHz_XTAL Namco NA-1 (14x NTSC subcarrier) + 50'349'000, // 50.349_MHz_XTAL Sega System 18 (~3200x NTSC line rate) + 50'350'000, // 50.35_MHz_XTAL Sharp X68030 video + 51'200'000, // 51.2_MHz_XTAL Namco Super System 22 video clock + 52'000'000, // 52_MHz_XTAL Cojag + 52'832'000, // 52.832_MHz_XTAL Wang PC TIG video controller + 52'867'000, // 52.867_MHz_XTAL Atlus Print Club (Sega C2 PCB) + 53'203'424, // 53.203424_MHz_XTAL Master System, Mega Drive PAL (12x PAL subcarrier) + 53'693'175, // 53.693175_MHz_XTAL PSX-based h/w, Sony ZN1-2-based (15x NTSC subcarrier) + 54'000'000, // 54_MHz_XTAL Taito JC + 55'000'000, // 55_MHz_XTAL Eolith Vega + 56'000'000, // 56_MHz_XTAL ARM7500 based Belatra slot machines + 57'272'727, // 57.272727_MHz_XTAL Psikyo SH2 with /2 divider (16x NTSC subcarrier) + 57'283'200, // 57.2832_MHz_XTAL Macintosh IIci RBV, 15-inch portrait display + 58'000'000, // 58_MHz_XTAL Magic Reel (Play System) + 58'982'400, // 58.9824_MHz_XTAL Wyse WY-65 + 59'292'000, // 59.292_MHz_XTAL Data General D461 + 60'000'000, // 60_MHz_XTAL ARM610 + 61'440'000, // 61.44_MHz_XTAL Donkey Kong + 64'000'000, // 64_MHz_XTAL BattleToads + 64'108'800, // 64.1088_MHz_XTAL HP Topcat high-res + 66'000'000, // 66_MHz_XTAL - + 66'666'700, // 66.6667_MHz_XTAL Later Midway games + 67'737'600, // 67.7376_MHz_XTAL PSX-based h/w, Sony ZN1-2-based + 68'850'000, // 68.85_MHz_XTAL Wyse WY-50 + 69'196'800, // 69.1968_MHz_XTAL DEC VCB0x/VAXstation dot clock + 69'551'990, // 69.55199_MHz_XTAL Sharp X68000 31.5kHz video + 72'000'000, // 72_MHz_XTAL Aristocrat MKV + 72'576'000, // 72.576_MHz_XTAL Centipede, Millipede, Missile Command, Let's Go Bowling "Multipede" + 73'728'000, // 73.728_MHz_XTAL Ms. Pac-Man/Galaga 20th Anniversary + 75'000'000, // 75_MHz_XTAL Sony NEWS NWS-5000X + 77'414'400, // 77.4144_MHz_XTAL NCD17c + 80'000'000, // 80_MHz_XTAL ARM710 + 87'183'360, // 87.18336_MHz_XTAL AT&T 630 MTG + 92'940'500, // 92.9405_MHz_XTAL Sun cgthree + 96'000'000, // 96_MHz_XTAL Acorn A680 + 99'522'000, // 99.522_MHz_XTAL Radius Two Page Display + 100'000'000, // 100_MHz_XTAL PSX-based Namco System 12, Vegas, Sony ZN1-2-based + 101'491'200, // 101.4912_MHz_XTAL PSX-based Namco System 10 + 105'561'000, // 105.561_MHz_XTAL Sun cgsix + 108'108'000, // 108.108_MHz_XTAL HP 98550 high-res color card + 120'000'000, // 120_MHz_XTAL Astro Corp.'s Stone Age + 200'000'000 // 200_MHz_XTAL Base SH4 CPU (Naomi, Hikaru etc.) }; double XTAL::last_correct_value = -1; @@ -583,369 +607,3 @@ void XTAL::fail(double base_clock, const std::string &message) full_message += util::string_format(" Context: %s\n", message); fatalerror("%s\n", full_message); } - -/* - -For further reference: - -A search at http://search.digikey.com/scripts/DkSearch/dksus.dll?Cat=852333;keywords=cry -reveals the following shipping frequencies as of 1/1/2008: - -20kHz -25.600kHz -26.667kHz -28kHz - -30kHz -30.720kHz -30.76kHz -31.2kHz -31.25kHz -31.5kHz -32.000kHz -32.56kHz -32.768kHz -32.919kHz -34kHz -36kHz -38kHz -38.4kHz -39.500kHz - -40kHz -44.100kHz -46.604kHz -46.6084kHz - -50kHz -59.787kHz - -60.000kHz -60.002kHz -60.005kHz -65.535kHz -65.536kHz -69kHz - -70kHz -71kHz -72kHz -73kHz -74kHz -74.3kHz -74.4kHz -75kHz -76kHz -76.79kHz -76.8kHz -76.81kHz -77kHz -77.204kHz -77.287kHz -77.500kHz -77.503kHz -77.504kHz -78kHz -79kHz - -83kHz - -96kHz -96.006kHz - -100kHz -111kHz -117.72kHz -120kHz -120.8475kHz -125kHz -131.072kHz -149.475kHz -153.600kHz - -200kHz - -307.2kHz - -1.000MHz -1.8432MHz - -2.000MHz -2.048MHz -2.097152MHz -2.4576MHz -2.5MHz -2.560MHz -2.949120MHz - -3.000MHz -3.276MHz -3.2768MHz -3.579MHz -3.579545MHz -3.640MHz -3.6864MHz -3.700MHz -3.859MHz -3.93216MHz - -4.000MHz -4.032MHz -4.096MHz -4.09625MHz -4.194MHz -4.194304MHz -4.332MHz -4.433MHz -4.433616MHz -4.433618MHz -4.433619MHz -4.74687MHz -4.800MHz -4.8970MHz -4.90625MHz -4.915MHz -4.9152MHz - -5.000MHz -5.0688MHz -5.120MHz -5.223438MHz -5.5MHz -5.5296MHz -5.9904MHz - -6.000MHz -6.14MHz -6.144MHz -6.1760MHz -6.400 MHz -6.49830MHz -6.5MHz -6.5536MHz -6.612813MHz -6.7458MHz -6.757MHz -6.76438MHz - -7.1505MHz -7.15909 MHz -7.2MHz -7.3728MHz -7.68MHz -7.94888MHz - -8.000MHz -8.000156MHz -8.192MHz -8.388608MHz -8.432MHz -8.5MHz -8.6432MHz - -9.000MHz -9.216MHz -9.509375MHz -9.545MHz -9.6MHz -9.7941MHz -9.830MHz -9.8304MHz -9.84375MHz -9.8438MHz - -10.000MHz -10.240MHz -10.245MHz -10.6244MHz -10.738635MHz -10.73865MHz - -11.000MHz -11.046MHz -11.0592MHz -11.228MHz -11.2896MHz -11.520MHz -11.981350MHz - -12.000MHz -12.000393MHz -12.096MHz -12.1875MHz -12.288MHz -12.352MHz -12.500MHz -12.688MHz -12.800MHz -12.96MHz - -13.000MHz -13.0625MHz -13.225MHz -13.2256MHz -13.500MHz -13.5168MHz -13.56MHz -13.605MHz -13.824MHz -13.94916MHz - -14.00MHz -14.318MHz -14.31818MHz -14.3359MHz -14.3594MHz -14.4MHz -14.5MHz -14.69MHz -14.7456MHz -14.850MHz - -15MHz -15.360MHz - -16.000MHz -16.000312MHz -16.128MHz -16.257MHz -16.3676MHz -16.368MHz -16.384MHz -16.576MHz -16.6660MHz -16.667MHz -16.670MHz -16.800MHz -16.934MHz -16.9344MHz - -17.734475MHz - -18.000MHz -18.432MHz -18.869MHz - -19.200MHz -19.440MHz -19.660MHz -19.6608MHz -19.68MHz -19.800MHz - -20.000MHz -20.35625MHz -20.3563MHz -20.480MHz - -21.47727MHz - -22.000MHz -22.118MHz -22.1184MHz -22.400MHz -22.5MHz -22.5792MHz -22.6278MHz - -23MHz -23.2643MHz -23.5MHz -23.5122MHz -23.592MHz - -24.000MHz -24.00014MHz -24.5MHz -24.545454 MHz -24.5535MHz -24.576MHz -24.704MHz -24.7456MHz - -25.000MHz -25MHz -25.175MHz -25.2235MHz -25.4563MHz -25.5MHz - -26.000MHz -26.45125MHz -26.4513MHz -26.5MHz -26.5971MHz -26.800MHz - -27.000MHz -27.1344MHz -27.3067MHz -27.4688MHz - -28.000MHz -28.224MHz -28.259375MHz -28.2594MHz -28.322MHz -28.375MHz -28.5938MHz -28.636MHz -28.6363MHz -28.63636MHz - -29.4912MHz -29.498928MHz -29.500MHz - -30.000MHz -32.000MHz -32.514MHz -32.768MHz -33.000MHz -33.333MHz -33.3333MHz -33.8688MHz -35.2512MHz -35.3280MHz -36.000MHz -38.000MHz -38.00053MHz -38.400MHz -38.880MHz -39MHz - -40.000MHz -40.320MHz -40.960 MHz -42.000MHz -44.000MHz -44.2368MHz -44.545MHz -44.736MHz -44.800MHz -44.900MHz -45.000MHz -46.000MHz -48.000MHz -49.152MHz -49.86MHz - -50.000MHz -53.125MHz -55.000MHz - -60.000MHz -64.000MHz -66.000MHz -66.666MHz -66.6666MHz - -73.66979MHz -75.957292MHz -76.121875MHz - -80.000MHz - -100.00MHz - -*/ |