diff options
Diffstat (limited to 'src/emu')
-rw-r--r-- | src/emu/debug/dvmemory.cpp | 2 | ||||
-rw-r--r-- | src/emu/devfind.h | 11 | ||||
-rw-r--r-- | src/emu/disound.cpp | 105 | ||||
-rw-r--r-- | src/emu/disound.h | 18 | ||||
-rw-r--r-- | src/emu/inpttype.ipp | 29 | ||||
-rw-r--r-- | src/emu/ioport.h | 19 | ||||
-rw-r--r-- | src/emu/layout/exorterm155.lay | 9 | ||||
-rw-r--r-- | src/emu/layout/ie15.lay | 2 | ||||
-rw-r--r-- | src/emu/render.cpp | 6 | ||||
-rw-r--r-- | src/emu/render.h | 46 | ||||
-rw-r--r-- | src/emu/rendlay.cpp | 723 | ||||
-rw-r--r-- | src/emu/rendutil.cpp | 3 | ||||
-rw-r--r-- | src/emu/save.cpp | 6 | ||||
-rw-r--r-- | src/emu/save.h | 185 | ||||
-rw-r--r-- | src/emu/sound.cpp | 1699 | ||||
-rw-r--r-- | src/emu/sound.h | 821 | ||||
-rw-r--r-- | src/emu/speaker.cpp | 76 | ||||
-rw-r--r-- | src/emu/speaker.h | 16 | ||||
-rw-r--r-- | src/emu/xtal.cpp | 2 |
19 files changed, 2617 insertions, 1161 deletions
diff --git a/src/emu/debug/dvmemory.cpp b/src/emu/debug/dvmemory.cpp index 7f07e48b6ed..fe1b9da419a 100644 --- a/src/emu/debug/dvmemory.cpp +++ b/src/emu/debug/dvmemory.cpp @@ -84,7 +84,7 @@ debug_view_memory_source::debug_view_memory_source(std::string &&name, void *bas , m_base(base) , m_blocklength(element_size * num_elements) , m_numblocks(num_blocks) - , m_blockstride(element_size * block_stride) + , m_blockstride(block_stride) , m_offsetxor(0) , m_endianness(ENDIANNESS_NATIVE) , m_prefsize(std::min(element_size, 8)) diff --git a/src/emu/devfind.h b/src/emu/devfind.h index 668defe4523..958ae16c3bc 100644 --- a/src/emu/devfind.h +++ b/src/emu/devfind.h @@ -15,6 +15,7 @@ #include <functional> #include <iterator> +#include <memory> #include <stdexcept> #include <string> #include <type_traits> @@ -1063,11 +1064,11 @@ public: // dynamic allocation of a shared pointer void allocate(u32 entries) { - assert(m_allocated.empty()); - m_allocated.resize(entries); - this->m_target = &m_allocated[0]; + assert(!m_allocated); + m_allocated = std::make_unique<PointerType []>(entries); + this->m_target = m_allocated.get(); m_bytes = entries * sizeof(PointerType); - this->m_base.get().save_item(m_allocated, this->m_tag); + this->m_base.get().save_pointer(m_allocated, this->m_tag, entries); } private: @@ -1086,7 +1087,7 @@ private: // internal state u8 const m_width; size_t m_bytes; - std::vector<PointerType> m_allocated; + std::unique_ptr<PointerType []> m_allocated; }; template <typename PointerType> using optional_shared_ptr = shared_ptr_finder<PointerType, false>; diff --git a/src/emu/disound.cpp b/src/emu/disound.cpp index 4d7d60265b6..d2c063ff205 100644 --- a/src/emu/disound.cpp +++ b/src/emu/disound.cpp @@ -66,13 +66,23 @@ device_sound_interface &device_sound_interface::add_route(u32 output, device_t & //------------------------------------------------- -// stream_alloc - allocate a stream implicitly +// stream_alloc_legacy - allocate a stream implicitly // associated with this device //------------------------------------------------- +sound_stream *device_sound_interface::stream_alloc_legacy(int inputs, int outputs, int sample_rate) +{ + return device().machine().sound().stream_alloc_legacy(*this, inputs, outputs, sample_rate, stream_update_legacy_delegate(&device_sound_interface::sound_stream_update_legacy, this)); +} + 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); + 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); } @@ -171,7 +181,7 @@ 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_gain(stream_inputnum) : 0.0f; + return (stream != nullptr) ? stream->input(stream_inputnum).gain() : 0.0f; } @@ -184,7 +194,7 @@ 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_gain(stream_outputnum) : 0.0f; + return (stream != nullptr) ? stream->output(stream_outputnum).gain() : 0.0f; } @@ -198,7 +208,7 @@ 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->set_input_gain(stream_inputnum, gain); + stream->input(stream_inputnum).set_gain(gain); } @@ -215,7 +225,7 @@ void device_sound_interface::set_output_gain(int outputnum, float gain) for (auto &stream : device().machine().sound().streams()) if (&stream->device() == &device()) for (int num = 0; num < stream->output_count(); num++) - stream->set_output_gain(num, gain); + stream->output(num).set_gain(gain); } // look up the stream and stream output index @@ -224,7 +234,7 @@ void device_sound_interface::set_output_gain(int outputnum, float gain) int stream_outputnum; sound_stream *stream = output_to_stream_output(outputnum, stream_outputnum); if (stream != nullptr) - stream->set_output_gain(stream_outputnum, gain); + stream->output(stream_outputnum).set_gain(gain); } } @@ -240,8 +250,11 @@ int device_sound_interface::inputnum_from_device(device_t &source_device, int ou for (auto &stream : device().machine().sound().streams()) if (&stream->device() == &device()) for (int inputnum = 0; inputnum < stream->input_count(); inputnum++, overall++) - if (stream->input_source_device(inputnum) == &source_device && stream->input_source_outputnum(inputnum) == outputnum) + { + auto &input = stream->input(inputnum); + if (input.valid() && &input.source().stream().device() == &source_device && input.source().index() == outputnum) return overall; + } return -1; } @@ -368,6 +381,28 @@ void device_sound_interface::interface_pre_reset() } +//------------------------------------------------- +// sound_stream_update_legacy - implementation +// that should be overridden by legacy devices +//------------------------------------------------- + +void device_sound_interface::sound_stream_update_legacy(sound_stream &stream, stream_sample_t const * const *inputs, stream_sample_t * const *outputs, int samples) +{ + throw emu_fatalerror("sound_stream_update_legacy called but not overridden by owning class"); +} + + +//------------------------------------------------- +// 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) +{ + throw emu_fatalerror("sound_stream_update called but not overridden by owning class"); +} + + //************************************************************************** // SIMPLE DERIVED MIXER INTERFACE @@ -430,8 +465,11 @@ void device_mixer_interface::interface_pre_start() } } + // 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()); + m_mixer_stream = stream_alloc(m_auto_allocated_inputs, m_outputs, device().machine().sample_rate(), STREAM_DEFAULT_FLAGS); } @@ -443,9 +481,8 @@ void device_mixer_interface::interface_pre_start() void device_mixer_interface::interface_post_load() { - // Beware that there's not going to be a mixer stream if there was - // no inputs - if (m_mixer_stream) + // 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 @@ -454,21 +491,43 @@ void device_mixer_interface::interface_post_load() //------------------------------------------------- -// mixer_update - mix all inputs to one output +// sound_stream_update - mix all inputs to one +// output //------------------------------------------------- -void device_mixer_interface::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) +void device_mixer_interface::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) { - // clear output buffers - for (int output = 0; output < m_outputs; output++) - std::fill_n(outputs[output], samples, 0); + // 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 samples - const u8 *outmap = &m_outputmap[0]; - for (int pos = 0; pos < samples; pos++) + // loop over inputs + for (int inputnum = 0; inputnum < m_auto_allocated_inputs; inputnum++) { - // for each input, add it to the appropriate output - for (int inp = 0; inp < m_auto_allocated_inputs; inp++) - outputs[outmap[inp]][pos] += inputs[inp][pos]; + // 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); } diff --git a/src/emu/disound.h b/src/emu/disound.h index 37a7df462be..d6135123ddb 100644 --- a/src/emu/disound.h +++ b/src/emu/disound.h @@ -34,6 +34,10 @@ constexpr int AUTO_ALLOC_INPUT = 65535; // TYPE DEFINITIONS //************************************************************************** +class read_stream_view; +class write_stream_view; +enum sound_stream_flags : u32; + // ======================> device_sound_interface @@ -73,10 +77,13 @@ public: device_sound_interface &reset_routes() { m_route_list.clear(); return *this; } // sound stream update overrides - virtual void sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) = 0; + virtual void sound_stream_update_legacy(sound_stream &stream, stream_sample_t const * const *inputs, stream_sample_t * const *outputs, int samples); + virtual void sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs); // stream creation + sound_stream *stream_alloc_legacy(int inputs, int outputs, int sample_rate); 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); // helpers int inputs() const; @@ -126,12 +133,13 @@ protected: virtual void interface_post_load() override; // sound interface overrides - virtual void sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) override; + 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 - sound_stream * m_mixer_stream; // mixing stream + 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 }; // iterator diff --git a/src/emu/inpttype.ipp b/src/emu/inpttype.ipp index cef6f36d1f4..f16677cf5c9 100644 --- a/src/emu/inpttype.ipp +++ b/src/emu/inpttype.ipp @@ -73,13 +73,13 @@ namespace { INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, MAHJONG_CHI, "P1 Mahjong Chi", input_seq(KEYCODE_SPACE) ) \ INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, MAHJONG_REACH, "P1 Mahjong Reach", input_seq(KEYCODE_LSHIFT) ) \ INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, MAHJONG_RON, "P1 Mahjong Ron", input_seq(KEYCODE_Z) ) \ + INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, MAHJONG_FLIP_FLOP, "P1 Mahjong Flip Flop", input_seq(KEYCODE_Y) ) \ INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, MAHJONG_BET, "P1 Mahjong Bet", input_seq(KEYCODE_3) ) \ - INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, MAHJONG_LAST_CHANCE, "P1 Mahjong Last Chance", input_seq(KEYCODE_RALT) ) \ - INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, MAHJONG_SCORE, "P1 Mahjong Score", input_seq(KEYCODE_RCONTROL) ) \ + INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, MAHJONG_SCORE, "P1 Mahjong Take Score", input_seq(KEYCODE_RCONTROL) ) \ INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, MAHJONG_DOUBLE_UP, "P1 Mahjong Double Up", input_seq(KEYCODE_RSHIFT) ) \ - INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, MAHJONG_FLIP_FLOP, "P1 Mahjong Flip Flop", input_seq(KEYCODE_Y) ) \ INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, MAHJONG_BIG, "P1 Mahjong Big", input_seq(KEYCODE_ENTER) ) \ INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, MAHJONG_SMALL, "P1 Mahjong Small", input_seq(KEYCODE_BACKSPACE) ) \ + INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, MAHJONG_LAST_CHANCE, "P1 Mahjong Last Chance", input_seq(KEYCODE_RALT) ) \ CORE_INPUT_TYPES_END() #define CORE_INPUT_TYPES_P1_HANAFUDA \ @@ -98,20 +98,20 @@ namespace { #define CORE_INPUT_TYPES_GAMBLE \ CORE_INPUT_TYPES_BEGIN(gamble) \ - INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_HIGH, "High", input_seq(KEYCODE_A) ) \ - INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_LOW, "Low", input_seq(KEYCODE_S) ) \ - INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_HALF, "Half Gamble", input_seq(KEYCODE_D) ) \ - INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_DEAL, "Deal", input_seq(KEYCODE_2) ) \ - INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_D_UP, "Double Up", input_seq(KEYCODE_3) ) \ - INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_TAKE, "Take", input_seq(KEYCODE_4) ) \ - INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_STAND, "Stand", input_seq(KEYCODE_L) ) \ - INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_BET, "Bet", input_seq(KEYCODE_M) ) \ INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_KEYIN, "Key In", input_seq(KEYCODE_Q) ) \ INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_KEYOUT, "Key Out", input_seq(KEYCODE_W) ) \ - INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_PAYOUT, "Payout", input_seq(KEYCODE_I) ) \ - INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_DOOR, "Door", input_seq(KEYCODE_O) ) \ INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_SERVICE, "Service", input_seq(KEYCODE_9) ) \ INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_BOOK, "Book-Keeping", input_seq(KEYCODE_0) ) \ + INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_DOOR, "Door", input_seq(KEYCODE_O) ) \ + INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_PAYOUT, "Payout", input_seq(KEYCODE_I) ) \ + INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_BET, "Bet", input_seq(KEYCODE_M) ) \ + INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_DEAL, "Deal", input_seq(KEYCODE_2) ) \ + INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_STAND, "Stand", input_seq(KEYCODE_L) ) \ + INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_TAKE, "Take Score", input_seq(KEYCODE_4) ) \ + INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_D_UP, "Double Up", input_seq(KEYCODE_3) ) \ + INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_HALF, "Half Gamble", input_seq(KEYCODE_D) ) \ + INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_HIGH, "High", input_seq(KEYCODE_A) ) \ + INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, GAMBLE_LOW, "Low", input_seq(KEYCODE_S) ) \ CORE_INPUT_TYPES_END() #define CORE_INPUT_TYPES_POKER \ @@ -122,7 +122,6 @@ namespace { INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, POKER_HOLD4, "Hold 4", input_seq(KEYCODE_V) ) \ INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, POKER_HOLD5, "Hold 5", input_seq(KEYCODE_B) ) \ INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, POKER_CANCEL, "Cancel", input_seq(KEYCODE_N) ) \ - INPUT_PORT_DIGITAL_TYPE( 1, PLAYER1, POKER_BET, "Bet", input_seq(KEYCODE_1) ) \ CORE_INPUT_TYPES_END() #define CORE_INPUT_TYPES_SLOT \ @@ -194,7 +193,7 @@ namespace { INPUT_PORT_DIGITAL_TYPE( 2, PLAYER2, MAHJONG_RON, "P2 Mahjong Ron", input_seq() ) \ INPUT_PORT_DIGITAL_TYPE( 2, PLAYER2, MAHJONG_BET, "P2 Mahjong Bet", input_seq() ) \ INPUT_PORT_DIGITAL_TYPE( 2, PLAYER2, MAHJONG_LAST_CHANCE, "P2 Mahjong Last Chance", input_seq() ) \ - INPUT_PORT_DIGITAL_TYPE( 2, PLAYER2, MAHJONG_SCORE, "P2 Mahjong Score", input_seq() ) \ + INPUT_PORT_DIGITAL_TYPE( 2, PLAYER2, MAHJONG_SCORE, "P2 Mahjong Take Score", input_seq() ) \ INPUT_PORT_DIGITAL_TYPE( 2, PLAYER2, MAHJONG_DOUBLE_UP, "P2 Mahjong Double Up", input_seq() ) \ INPUT_PORT_DIGITAL_TYPE( 2, PLAYER2, MAHJONG_FLIP_FLOP, "P2 Mahjong Flip Flop", input_seq() ) \ INPUT_PORT_DIGITAL_TYPE( 2, PLAYER2, MAHJONG_BIG, "P2 Mahjong Big", input_seq() ) \ diff --git a/src/emu/ioport.h b/src/emu/ioport.h index 9df5e8f533a..72add6abb41 100644 --- a/src/emu/ioport.h +++ b/src/emu/ioport.h @@ -217,13 +217,13 @@ enum ioport_type IPT_MAHJONG_CHI, IPT_MAHJONG_REACH, IPT_MAHJONG_RON, + IPT_MAHJONG_FLIP_FLOP, IPT_MAHJONG_BET, - IPT_MAHJONG_LAST_CHANCE, IPT_MAHJONG_SCORE, IPT_MAHJONG_DOUBLE_UP, - IPT_MAHJONG_FLIP_FLOP, IPT_MAHJONG_BIG, IPT_MAHJONG_SMALL, + IPT_MAHJONG_LAST_CHANCE, IPT_MAHJONG_LAST, @@ -256,15 +256,15 @@ enum ioport_type // IPT_GAMBLE_DOOR4, // IPT_GAMBLE_DOOR5, - IPT_GAMBLE_HIGH, // player - IPT_GAMBLE_LOW, // player - IPT_GAMBLE_HALF, // player + IPT_GAMBLE_PAYOUT, // player + IPT_GAMBLE_BET, // player IPT_GAMBLE_DEAL, // player - IPT_GAMBLE_D_UP, // player - IPT_GAMBLE_TAKE, // player IPT_GAMBLE_STAND, // player - IPT_GAMBLE_BET, // player - IPT_GAMBLE_PAYOUT, // player + IPT_GAMBLE_TAKE, // player + IPT_GAMBLE_D_UP, // player + IPT_GAMBLE_HALF, // player + IPT_GAMBLE_HIGH, // player + IPT_GAMBLE_LOW, // player // poker-specific inputs IPT_POKER_HOLD1, @@ -273,7 +273,6 @@ enum ioport_type IPT_POKER_HOLD4, IPT_POKER_HOLD5, IPT_POKER_CANCEL, - IPT_POKER_BET, // slot-specific inputs IPT_SLOT_STOP1, diff --git a/src/emu/layout/exorterm155.lay b/src/emu/layout/exorterm155.lay index 6a47f6869e7..9da2b5ed7ef 100644 --- a/src/emu/layout/exorterm155.lay +++ b/src/emu/layout/exorterm155.lay @@ -8,7 +8,7 @@ LEDs for the Motorola EXORterm 155 <mamelayout version="2"> <element name="red_led"> - <disk> + <disk state="0"> <color red="1.0" green="0.0" blue="0.0" /> </disk> </element> @@ -35,12 +35,13 @@ LEDs for the Motorola EXORterm 155 </element> <element name="background"> <rect> - <bounds left="0" top="0" right="1" bottom="1" /> <color red="0.0" green="0.0" blue="0.0" /> </rect> </element> <view name="Keyboard LEDs"> + <bounds left="0" top="0" right="720" bottom="544" /> + <screen index="0"> <bounds x="0" y="0" width="720" height="512" /> </screen> @@ -71,10 +72,6 @@ LEDs for the Motorola EXORterm 155 <element name="insert_char_led" ref="red_led"> <bounds left="312" right="320" top="532" bottom="540" /> </element> - - <element ref="background"> - <bounds left="0" top="540" right="720" bottom="544" /> - </element> </view> </mamelayout> diff --git a/src/emu/layout/ie15.lay b/src/emu/layout/ie15.lay index 90ed8816edb..255e2022ef8 100644 --- a/src/emu/layout/ie15.lay +++ b/src/emu/layout/ie15.lay @@ -5,7 +5,7 @@ license:CC0 <mamelayout version="2"> <element name="e_led"> - <disk> + <disk state="0"> <color red="1.0" green="0.0" blue="0.0" /> </disk> </element> diff --git a/src/emu/render.cpp b/src/emu/render.cpp index 221ce9fc929..46123a02b3f 100644 --- a/src/emu/render.cpp +++ b/src/emu/render.cpp @@ -2511,15 +2511,13 @@ void render_target::add_container_primitives(render_primitive_list &list, const void render_target::add_element_primitives(render_primitive_list &list, const object_transform &xform, layout_element &element, int state, int blendmode) { - // if we're out of range, bail - if (state > element.maxstate()) - return; + // limit state range to non-negative values if (state < 0) state = 0; // get a pointer to the relevant texture render_texture *texture = element.state_texture(state); - if (texture != nullptr) + if (texture) { render_primitive *prim = list.alloc(render_primitive::QUAD); diff --git a/src/emu/render.h b/src/emu/render.h index b0d0df82d3c..1545a3f041d 100644 --- a/src/emu/render.h +++ b/src/emu/render.h @@ -579,7 +579,6 @@ public: // getters running_machine &machine() const { return m_machine; } int default_state() const { return m_defstate; } - int maxstate() const { return m_maxstate; } render_texture *state_texture(int state); private: @@ -603,17 +602,22 @@ private: void normalize_bounds(float xoffs, float yoffs, float xscale, float yscale); // getters - int state() const { return m_state; } - virtual int maxstate() const { return m_state; } - const render_bounds &bounds() const { return m_bounds; } - const render_color &color() const { return m_color; } + int statemask() const { return m_statemask; } + int stateval() const { return m_stateval; } + std::pair<int, bool> statewrap() const; + render_bounds overall_bounds() const; + render_bounds bounds(int state) const; + render_color color(int state) const; // operations virtual void draw(running_machine &machine, bitmap_argb32 &dest, const rectangle &bounds, int state) = 0; protected: - // helpers - void draw_text(render_font &font, bitmap_argb32 &dest, const rectangle &bounds, const char *str, int align); + // helper + virtual int maxstate() const { return -1; } + + // drawing helpers + void draw_text(render_font &font, bitmap_argb32 &dest, const rectangle &bounds, const char *str, int align, const render_color &color); void draw_segment_horizontal_caps(bitmap_argb32 &dest, int minx, int maxx, int midy, int width, int caps, rgb_t color); void draw_segment_horizontal(bitmap_argb32 &dest, int minx, int maxx, int midy, int width, rgb_t color); void draw_segment_vertical_caps(bitmap_argb32 &dest, int miny, int maxy, int midx, int width, int caps, rgb_t color); @@ -625,10 +629,27 @@ private: void apply_skew(bitmap_argb32 &dest, int skewwidth); private: + struct bounds_step + { + int state; + render_bounds bounds; + render_bounds delta; + }; + using bounds_vector = std::vector<bounds_step>; + + struct color_step + { + int state; + render_color color; + render_color delta; + }; + using color_vector = std::vector<color_step>; + // internal state - int m_state; // state where this component is visible (-1 means all states) - render_bounds m_bounds; // bounds of the element - render_color m_color; // color of the element + int const m_statemask; // bits of state used to control visibility + int const m_stateval; // masked state value to make component visible + bounds_vector m_bounds; // bounds of the element + color_vector m_color; // color of the element }; // component implementations @@ -677,8 +698,9 @@ private: // internal state running_machine & m_machine; // reference to the owning machine std::vector<component::ptr> m_complist; // list of components - int m_defstate; // default state of this element - int m_maxstate; // maximum state value for all components + int const m_defstate; // default state of this element + int m_statemask; // mask to apply to state values + bool m_foldhigh; // whether we need to fold state values above the mask range std::vector<texture> m_elemtex; // array of element textures used for managing the scaled bitmaps }; diff --git a/src/emu/rendlay.cpp b/src/emu/rendlay.cpp index 72c50a3db3b..198fa03998a 100644 --- a/src/emu/rendlay.cpp +++ b/src/emu/rendlay.cpp @@ -9,12 +9,14 @@ ***************************************************************************/ #include "emu.h" +#include "render.h" +#include "rendlay.h" #include "emuopts.h" -#include "render.h" #include "rendfont.h" -#include "rendlay.h" #include "rendutil.h" +#include "video/rgbutil.h" + #include "vecstream.h" #include "xmlfile.h" @@ -938,12 +940,10 @@ layout_element::make_component_map const layout_element::s_make_component{ layout_element::layout_element(environment &env, util::xml::data_node const &elemnode, const char *dirname) : m_machine(env.machine()) - , m_defstate(0) - , m_maxstate(0) + , m_defstate(env.get_attribute_int(elemnode, "defstate", -1)) + , m_statemask(0) + , m_foldhigh(false) { - // get the default state - m_defstate = env.get_attribute_int(elemnode, "defstate", -1); - // parse components in order bool first = true; render_bounds bounds = { 0.0, 0.0, 0.0, 0.0 }; @@ -958,13 +958,15 @@ layout_element::layout_element(environment &env, util::xml::data_node const &ele // accumulate bounds if (first) - bounds = newcomp.bounds(); + bounds = newcomp.overall_bounds(); else - union_render_bounds(bounds, newcomp.bounds()); + union_render_bounds(bounds, newcomp.overall_bounds()); first = false; // determine the maximum state - m_maxstate = std::max(m_maxstate, newcomp.maxstate()); + std::pair<int, bool> const wrap(newcomp.statewrap()); + m_statemask |= wrap.first; + m_foldhigh = m_foldhigh || wrap.second; } if (!m_complist.empty()) @@ -972,8 +974,8 @@ layout_element::layout_element(environment &env, util::xml::data_node const &ele // determine the scale/offset for normalization float xoffs = bounds.x0; float yoffs = bounds.y0; - float xscale = 1.0f / (bounds.x1 - bounds.x0); - float yscale = 1.0f / (bounds.y1 - bounds.y0); + float xscale = 1.0F / (bounds.x1 - bounds.x0); + float yscale = 1.0F / (bounds.y1 - bounds.y0); // normalize all the component bounds for (component::ptr const &curcomp : m_complist) @@ -981,7 +983,7 @@ layout_element::layout_element(environment &env, util::xml::data_node const &ele } // allocate an array of element textures for the states - m_elemtex.resize(m_maxstate + 1); + m_elemtex.resize((m_statemask + 1) << (m_foldhigh ? 1 : 0)); } @@ -1005,7 +1007,7 @@ layout_element::~layout_element() layout_group::layout_group(util::xml::data_node const &groupnode) : m_groupnode(groupnode) - , m_bounds{ 0.0f, 0.0f, 0.0f, 0.0f } + , m_bounds{ 0.0F, 0.0F, 0.0F, 0.0F } , m_bounds_resolved(false) { } @@ -1284,8 +1286,12 @@ void layout_group::resolve_bounds( render_texture *layout_element::state_texture(int state) { - assert(state <= m_maxstate); - if (m_elemtex[state].m_texture == nullptr) + if (m_foldhigh && (state & ~m_statemask)) + state = (state & m_statemask) | (((m_statemask << 1) | 1) & ~m_statemask); + else + state &= m_statemask; + assert(m_elemtex.size() > state); + if (!m_elemtex[state].m_texture) { m_elemtex[state].m_element = this; m_elemtex[state].m_state = state; @@ -1303,23 +1309,26 @@ render_texture *layout_element::state_texture(int state) void layout_element::element_scale(bitmap_argb32 &dest, bitmap_argb32 &source, const rectangle &sbounds, void *param) { - texture *elemtex = (texture *)param; + texture const &elemtex(*reinterpret_cast<texture const *>(param)); // iterate over components that are part of the current state - for (auto &curcomp : elemtex->m_element->m_complist) - if (curcomp->state() == -1 || curcomp->state() == elemtex->m_state) + for (auto const &curcomp : elemtex.m_element->m_complist) + { + if ((elemtex.m_state & curcomp->statemask()) == curcomp->stateval()) { // get the local scaled bounds + render_bounds const compbounds(curcomp->bounds(elemtex.m_state)); rectangle bounds( - render_round_nearest(curcomp->bounds().x0 * dest.width()), - render_round_nearest(curcomp->bounds().x1 * dest.width()), - render_round_nearest(curcomp->bounds().y0 * dest.height()), - render_round_nearest(curcomp->bounds().y1 * dest.height())); + render_round_nearest(compbounds.x0 * dest.width()), + render_round_nearest(compbounds.x1 * dest.width()), + render_round_nearest(compbounds.y0 * dest.height()), + render_round_nearest(compbounds.y1 * dest.height())); bounds &= dest.cliprect(); // based on the component type, add to the texture - curcomp->draw(elemtex->m_element->machine(), dest, bounds, elemtex->m_state); + curcomp->draw(elemtex.m_element->machine(), dest, bounds, elemtex.m_state); } + } } @@ -1330,13 +1339,10 @@ public: // construction/destruction image_component(environment &env, util::xml::data_node const &compnode, const char *dirname) : component(env, compnode, dirname) - , m_hasalpha(false) + , m_dirname(dirname ? dirname : "") + , m_imagefile(env.get_attribute_string(compnode, "file", "")) + , m_alphafile(env.get_attribute_string(compnode, "alphafile", "")) { - if (dirname != nullptr) - m_dirname = dirname; - m_imagefile = env.get_attribute_string(compnode, "file", ""); - m_alphafile = env.get_attribute_string(compnode, "alphafile", ""); - m_file = std::make_unique<emu_file>(env.machine().options().art_path(), OPEN_FLAG_READ); } protected: @@ -1344,19 +1350,56 @@ protected: virtual void draw(running_machine &machine, bitmap_argb32 &dest, const rectangle &bounds, int state) override { if (!m_bitmap.valid()) - load_bitmap(); + load_bitmap(machine); - bitmap_argb32 destsub(dest, bounds); - render_resample_argb_bitmap_hq(destsub, m_bitmap, color()); + render_color const c(color(state)); + if (m_hasalpha || (1.0F > c.a)) + { + bitmap_argb32 tempbitmap(dest.width(), dest.height()); + render_resample_argb_bitmap_hq(tempbitmap, m_bitmap, c); + for (s32 y0 = 0, y1 = bounds.top(); bounds.bottom() >= y1; ++y0, ++y1) + { + u32 const *src(&tempbitmap.pix(y0, 0)); + u32 *dst(&dest.pix(y1, bounds.left())); + for (s32 x1 = bounds.left(); bounds.right() >= x1; ++x1, ++src, ++dst) + { + rgb_t const a(*src); + u32 const aa(a.a()); + if (aa) + { + rgb_t const b(*dst); + u32 const ba(b.a()); + if (ba) + { + u32 const ca((aa * 255) + (ba * (255 - aa))); + *dst = rgb_t( + u8(ca / 255), + u8(((a.r() * aa * 255) + (b.r() * ba * (255 - aa))) / ca), + u8(((a.g() * aa * 255) + (b.g() * ba * (255 - aa))) / ca), + u8(((a.b() * aa * 255) + (b.b() * ba * (255 - aa))) / ca)); + } + else + { + *dst = *src; + } + } + } + } + } + else + { + bitmap_argb32 destsub(dest, bounds); + render_resample_argb_bitmap_hq(destsub, m_bitmap, c); + } } private: // internal helpers - void load_bitmap() + void load_bitmap(running_machine &machine) { - assert(m_file != nullptr); + emu_file file(machine.options().art_path(), OPEN_FLAG_READ); - ru_imgformat const format = render_detect_image(*m_file, m_dirname.c_str(), m_imagefile.c_str()); + ru_imgformat const format = render_detect_image(file, m_dirname.c_str(), m_imagefile.c_str()); switch (format) { case RENDUTIL_IMGFORMAT_ERROR: @@ -1364,18 +1407,18 @@ private: case RENDUTIL_IMGFORMAT_PNG: // load the basic bitmap - m_hasalpha = render_load_png(m_bitmap, *m_file, m_dirname.c_str(), m_imagefile.c_str()); + m_hasalpha = render_load_png(m_bitmap, file, m_dirname.c_str(), m_imagefile.c_str()); break; default: // try JPG - render_load_jpeg(m_bitmap, *m_file, m_dirname.c_str(), m_imagefile.c_str()); + render_load_jpeg(m_bitmap, file, m_dirname.c_str(), m_imagefile.c_str()); break; } // load the alpha bitmap if specified if (m_bitmap.valid() && !m_alphafile.empty()) - render_load_png(m_bitmap, *m_file, m_dirname.c_str(), m_alphafile.c_str(), true); + render_load_png(m_bitmap, file, m_dirname.c_str(), m_alphafile.c_str(), true); // if we can't load the bitmap, allocate a dummy one and report an error if (!m_bitmap.valid()) @@ -1397,11 +1440,10 @@ private: // internal state bitmap_argb32 m_bitmap; // source bitmap for images - std::string m_dirname; // directory name of image file (for lazy loading) - std::unique_ptr<emu_file> m_file; // file object for reading image/alpha files - std::string m_imagefile; // name of the image file (for lazy loading) - std::string m_alphafile; // name of the alpha file (for lazy loading) - bool m_hasalpha; // is there any alpha component present? + std::string const m_dirname; // directory name of image file (for lazy loading) + std::string const m_imagefile; // name of the image file (for lazy loading) + std::string const m_alphafile; // name of the alpha file (for lazy loading) + bool m_hasalpha = false; // is there any alpha component present? }; @@ -1420,10 +1462,11 @@ protected: virtual void draw(running_machine &machine, bitmap_argb32 &dest, const rectangle &bounds, int state) override { // compute premultiplied colors - u32 const r = color().r * color().a * 255.0f; - u32 const g = color().g * color().a * 255.0f; - u32 const b = color().b * color().a * 255.0f; - u32 const inva = (1.0f - color().a) * 255.0f; + render_color const c = color(state); + u32 const r = c.r * c.a * 255.0f; + u32 const g = c.g * c.a * 255.0f; + u32 const b = c.b * c.a * 255.0f; + u32 const inva = (1.0f - c.a) * 255.0f; // iterate over X and Y for (u32 y = bounds.top(); y <= bounds.bottom(); y++) @@ -1466,10 +1509,11 @@ protected: virtual void draw(running_machine &machine, bitmap_argb32 &dest, const rectangle &bounds, int state) override { // compute premultiplied colors - u32 const r = color().r * color().a * 255.0f; - u32 const g = color().g * color().a * 255.0f; - u32 const b = color().b * color().a * 255.0f; - u32 const inva = (1.0f - color().a) * 255.0f; + render_color const c(color(state)); + u32 const r = c.r * c.a * 255.0f; + u32 const g = c.g * c.a * 255.0f; + u32 const b = c.b * c.a * 255.0f; + u32 const inva = (1.0f - c.a) * 255.0f; // find the center float const xcenter = float(bounds.xcenter()); @@ -1481,7 +1525,7 @@ protected: // iterate over y for (u32 y = bounds.top(); y <= bounds.bottom(); y++) { - float ycoord = ycenter - ((float)y + 0.5f); + float ycoord = ycenter - (float(y) + 0.5f); float xval = xradius * sqrtf(1.0f - (ycoord * ycoord) * ooyradius2); // compute left/right coordinates @@ -1529,7 +1573,7 @@ protected: virtual void draw(running_machine &machine, bitmap_argb32 &dest, const rectangle &bounds, int state) override { render_font *font = machine.render().font_alloc("default"); - draw_text(*font, dest, bounds, m_string.c_str(), m_textalign); + draw_text(*font, dest, bounds, m_string.c_str(), m_textalign, color(state)); machine.render().font_free(font); } @@ -1556,14 +1600,14 @@ protected: virtual void draw(running_machine &machine, bitmap_argb32 &dest, const rectangle &bounds, int state) override { - const rgb_t onpen = rgb_t(0xff,0xff,0xff,0xff); - const rgb_t offpen = rgb_t(0x20,0xff,0xff,0xff); + rgb_t const onpen = rgb_t(0xff, 0xff, 0xff, 0xff); + rgb_t const offpen = rgb_t(0x20, 0xff, 0xff, 0xff); // sizes for computation - int bmwidth = 250; - int bmheight = 400; - int segwidth = 40; - int skewwidth = 40; + int const bmwidth = 250; + int const bmheight = 400; + int const segwidth = 40; + int const skewwidth = 40; // allocate a temporary bitmap for drawing bitmap_argb32 tempbitmap(bmwidth + skewwidth, bmheight); @@ -1597,7 +1641,7 @@ protected: draw_segment_decimal(tempbitmap, bmwidth + segwidth/2, bmheight - segwidth/2, segwidth, BIT(state, 7) ? onpen : offpen); // resample to the target size - render_resample_argb_bitmap_hq(dest, tempbitmap, color()); + render_resample_argb_bitmap_hq(dest, tempbitmap, color(state)); } }; @@ -1618,15 +1662,15 @@ protected: virtual void draw(running_machine &machine, bitmap_argb32 &dest, const rectangle &bounds, int state) override { - const rgb_t onpen = rgb_t(0xff,0xff,0xff,0xff); - const rgb_t offpen = rgb_t(0x20,0xff,0xff,0xff); - const rgb_t backpen = rgb_t(0x00,0x00,0x00,0x00); + rgb_t const onpen = rgb_t(0xff, 0xff, 0xff, 0xff); + rgb_t const offpen = rgb_t(0x20, 0xff, 0xff, 0xff); + rgb_t const backpen = rgb_t(0x00, 0x00, 0x00, 0x00); // sizes for computation - int bmwidth = 250; - int bmheight = 400; - int segwidth = 40; - int skewwidth = 40; + int const bmwidth = 250; + int const bmheight = 400; + int const segwidth = 40; + int const skewwidth = 40; // allocate a temporary bitmap for drawing bitmap_argb32 tempbitmap(bmwidth + skewwidth, bmheight); @@ -1665,7 +1709,7 @@ protected: apply_skew(tempbitmap, 40); // resample to the target size - render_resample_argb_bitmap_hq(dest, tempbitmap, color()); + render_resample_argb_bitmap_hq(dest, tempbitmap, color(state)); } }; @@ -1686,14 +1730,14 @@ protected: virtual void draw(running_machine &machine, bitmap_argb32 &dest, const rectangle &bounds, int state) override { - const rgb_t onpen = rgb_t(0xff, 0xff, 0xff, 0xff); - const rgb_t offpen = rgb_t(0x20, 0xff, 0xff, 0xff); + rgb_t const onpen = rgb_t(0xff, 0xff, 0xff, 0xff); + rgb_t const offpen = rgb_t(0x20, 0xff, 0xff, 0xff); // sizes for computation - int bmwidth = 250; - int bmheight = 400; - int segwidth = 40; - int skewwidth = 40; + int const bmwidth = 250; + int const bmheight = 400; + int const segwidth = 40; + int const skewwidth = 40; // allocate a temporary bitmap for drawing bitmap_argb32 tempbitmap(bmwidth + skewwidth, bmheight); @@ -1701,83 +1745,83 @@ protected: // top bar draw_segment_horizontal(tempbitmap, - 0 + 2*segwidth/3, bmwidth - 2*segwidth/3, 0 + segwidth/2, - segwidth, (state & (1 << 0)) ? onpen : offpen); + 0 + 2*segwidth/3, bmwidth - 2*segwidth/3, 0 + segwidth/2, + segwidth, (state & (1 << 0)) ? onpen : offpen); // right-top bar draw_segment_vertical(tempbitmap, - 0 + 2*segwidth/3, bmheight/2 - segwidth/3, bmwidth - segwidth/2, - segwidth, (state & (1 << 1)) ? onpen : offpen); + 0 + 2*segwidth/3, bmheight/2 - segwidth/3, bmwidth - segwidth/2, + segwidth, (state & (1 << 1)) ? onpen : offpen); // right-bottom bar draw_segment_vertical(tempbitmap, - bmheight/2 + segwidth/3, bmheight - 2*segwidth/3, bmwidth - segwidth/2, - segwidth, (state & (1 << 2)) ? onpen : offpen); + bmheight/2 + segwidth/3, bmheight - 2*segwidth/3, bmwidth - segwidth/2, + segwidth, (state & (1 << 2)) ? onpen : offpen); // bottom bar draw_segment_horizontal(tempbitmap, - 0 + 2*segwidth/3, bmwidth - 2*segwidth/3, bmheight - segwidth/2, - segwidth, (state & (1 << 3)) ? onpen : offpen); + 0 + 2*segwidth/3, bmwidth - 2*segwidth/3, bmheight - segwidth/2, + segwidth, (state & (1 << 3)) ? onpen : offpen); // left-bottom bar draw_segment_vertical(tempbitmap, - bmheight/2 + segwidth/3, bmheight - 2*segwidth/3, 0 + segwidth/2, - segwidth, (state & (1 << 4)) ? onpen : offpen); + bmheight/2 + segwidth/3, bmheight - 2*segwidth/3, 0 + segwidth/2, + segwidth, (state & (1 << 4)) ? onpen : offpen); // left-top bar draw_segment_vertical(tempbitmap, - 0 + 2*segwidth/3, bmheight/2 - segwidth/3, 0 + segwidth/2, - segwidth, (state & (1 << 5)) ? onpen : offpen); + 0 + 2*segwidth/3, bmheight/2 - segwidth/3, 0 + segwidth/2, + segwidth, (state & (1 << 5)) ? onpen : offpen); // horizontal-middle-left bar draw_segment_horizontal_caps(tempbitmap, - 0 + 2*segwidth/3, bmwidth/2 - segwidth/10, bmheight/2, - segwidth, LINE_CAP_START, (state & (1 << 6)) ? onpen : offpen); + 0 + 2*segwidth/3, bmwidth/2 - segwidth/10, bmheight/2, + segwidth, LINE_CAP_START, (state & (1 << 6)) ? onpen : offpen); // horizontal-middle-right bar draw_segment_horizontal_caps(tempbitmap, - 0 + bmwidth/2 + segwidth/10, bmwidth - 2*segwidth/3, bmheight/2, - segwidth, LINE_CAP_END, (state & (1 << 7)) ? onpen : offpen); + 0 + bmwidth/2 + segwidth/10, bmwidth - 2*segwidth/3, bmheight/2, + segwidth, LINE_CAP_END, (state & (1 << 7)) ? onpen : offpen); // vertical-middle-top bar draw_segment_vertical_caps(tempbitmap, - 0 + segwidth + segwidth/3, bmheight/2 - segwidth/2 - segwidth/3, bmwidth/2, - segwidth, LINE_CAP_NONE, (state & (1 << 8)) ? onpen : offpen); + 0 + segwidth + segwidth/3, bmheight/2 - segwidth/2 - segwidth/3, bmwidth/2, + segwidth, LINE_CAP_NONE, (state & (1 << 8)) ? onpen : offpen); // vertical-middle-bottom bar draw_segment_vertical_caps(tempbitmap, - bmheight/2 + segwidth/2 + segwidth/3, bmheight - segwidth - segwidth/3, bmwidth/2, - segwidth, LINE_CAP_NONE, (state & (1 << 9)) ? onpen : offpen); + bmheight/2 + segwidth/2 + segwidth/3, bmheight - segwidth - segwidth/3, bmwidth/2, + segwidth, LINE_CAP_NONE, (state & (1 << 9)) ? onpen : offpen); // diagonal-left-bottom bar draw_segment_diagonal_1(tempbitmap, - 0 + segwidth + segwidth/5, bmwidth/2 - segwidth/2 - segwidth/5, - bmheight/2 + segwidth/2 + segwidth/3, bmheight - segwidth - segwidth/3, - segwidth, (state & (1 << 10)) ? onpen : offpen); + 0 + segwidth + segwidth/5, bmwidth/2 - segwidth/2 - segwidth/5, + bmheight/2 + segwidth/2 + segwidth/3, bmheight - segwidth - segwidth/3, + segwidth, (state & (1 << 10)) ? onpen : offpen); // diagonal-left-top bar draw_segment_diagonal_2(tempbitmap, - 0 + segwidth + segwidth/5, bmwidth/2 - segwidth/2 - segwidth/5, - 0 + segwidth + segwidth/3, bmheight/2 - segwidth/2 - segwidth/3, - segwidth, (state & (1 << 11)) ? onpen : offpen); + 0 + segwidth + segwidth/5, bmwidth/2 - segwidth/2 - segwidth/5, + 0 + segwidth + segwidth/3, bmheight/2 - segwidth/2 - segwidth/3, + segwidth, (state & (1 << 11)) ? onpen : offpen); // diagonal-right-top bar draw_segment_diagonal_1(tempbitmap, - bmwidth/2 + segwidth/2 + segwidth/5, bmwidth - segwidth - segwidth/5, - 0 + segwidth + segwidth/3, bmheight/2 - segwidth/2 - segwidth/3, - segwidth, (state & (1 << 12)) ? onpen : offpen); + bmwidth/2 + segwidth/2 + segwidth/5, bmwidth - segwidth - segwidth/5, + 0 + segwidth + segwidth/3, bmheight/2 - segwidth/2 - segwidth/3, + segwidth, (state & (1 << 12)) ? onpen : offpen); // diagonal-right-bottom bar draw_segment_diagonal_2(tempbitmap, - bmwidth/2 + segwidth/2 + segwidth/5, bmwidth - segwidth - segwidth/5, - bmheight/2 + segwidth/2 + segwidth/3, bmheight - segwidth - segwidth/3, - segwidth, (state & (1 << 13)) ? onpen : offpen); + bmwidth/2 + segwidth/2 + segwidth/5, bmwidth - segwidth - segwidth/5, + bmheight/2 + segwidth/2 + segwidth/3, bmheight - segwidth - segwidth/3, + segwidth, (state & (1 << 13)) ? onpen : offpen); // apply skew apply_skew(tempbitmap, 40); // resample to the target size - render_resample_argb_bitmap_hq(dest, tempbitmap, color()); + render_resample_argb_bitmap_hq(dest, tempbitmap, color(state)); } }; @@ -1899,7 +1943,7 @@ protected: apply_skew(tempbitmap, 40); // resample to the target size - render_resample_argb_bitmap_hq(dest, tempbitmap, color()); + render_resample_argb_bitmap_hq(dest, tempbitmap, color(state)); } }; @@ -1920,14 +1964,14 @@ protected: virtual void draw(running_machine &machine, bitmap_argb32 &dest, const rectangle &bounds, int state) override { - const rgb_t onpen = rgb_t(0xff, 0xff, 0xff, 0xff); - const rgb_t offpen = rgb_t(0x20, 0xff, 0xff, 0xff); + rgb_t const onpen = rgb_t(0xff, 0xff, 0xff, 0xff); + rgb_t const offpen = rgb_t(0x20, 0xff, 0xff, 0xff); // sizes for computation - int bmwidth = 250; - int bmheight = 400; - int segwidth = 40; - int skewwidth = 40; + int const bmwidth = 250; + int const bmheight = 400; + int const segwidth = 40; + int const skewwidth = 40; // allocate a temporary bitmap for drawing, adding some extra space for the tail bitmap_argb32 tempbitmap(bmwidth + skewwidth, bmheight + segwidth); @@ -1935,92 +1979,94 @@ protected: // top bar draw_segment_horizontal(tempbitmap, - 0 + 2*segwidth/3, bmwidth - 2*segwidth/3, 0 + segwidth/2, - segwidth, (state & (1 << 0)) ? onpen : offpen); + 0 + 2*segwidth/3, bmwidth - 2*segwidth/3, 0 + segwidth/2, + segwidth, (state & (1 << 0)) ? onpen : offpen); // right-top bar draw_segment_vertical(tempbitmap, - 0 + 2*segwidth/3, bmheight/2 - segwidth/3, bmwidth - segwidth/2, - segwidth, (state & (1 << 1)) ? onpen : offpen); + 0 + 2*segwidth/3, bmheight/2 - segwidth/3, bmwidth - segwidth/2, + segwidth, (state & (1 << 1)) ? onpen : offpen); // right-bottom bar draw_segment_vertical(tempbitmap, - bmheight/2 + segwidth/3, bmheight - 2*segwidth/3, bmwidth - segwidth/2, - segwidth, (state & (1 << 2)) ? onpen : offpen); + bmheight/2 + segwidth/3, bmheight - 2*segwidth/3, bmwidth - segwidth/2, + segwidth, (state & (1 << 2)) ? onpen : offpen); // bottom bar draw_segment_horizontal(tempbitmap, - 0 + 2*segwidth/3, bmwidth - 2*segwidth/3, bmheight - segwidth/2, - segwidth, (state & (1 << 3)) ? onpen : offpen); + 0 + 2*segwidth/3, bmwidth - 2*segwidth/3, bmheight - segwidth/2, + segwidth, (state & (1 << 3)) ? onpen : offpen); // left-bottom bar draw_segment_vertical(tempbitmap, - bmheight/2 + segwidth/3, bmheight - 2*segwidth/3, 0 + segwidth/2, - segwidth, (state & (1 << 4)) ? onpen : offpen); + bmheight/2 + segwidth/3, bmheight - 2*segwidth/3, 0 + segwidth/2, + segwidth, (state & (1 << 4)) ? onpen : offpen); // left-top bar draw_segment_vertical(tempbitmap, - 0 + 2*segwidth/3, bmheight/2 - segwidth/3, 0 + segwidth/2, - segwidth, (state & (1 << 5)) ? onpen : offpen); + 0 + 2*segwidth/3, bmheight/2 - segwidth/3, 0 + segwidth/2, + segwidth, (state & (1 << 5)) ? onpen : offpen); // horizontal-middle-left bar draw_segment_horizontal_caps(tempbitmap, - 0 + 2*segwidth/3, bmwidth/2 - segwidth/10, bmheight/2, - segwidth, LINE_CAP_START, (state & (1 << 6)) ? onpen : offpen); + 0 + 2*segwidth/3, bmwidth/2 - segwidth/10, bmheight/2, + segwidth, LINE_CAP_START, (state & (1 << 6)) ? onpen : offpen); // horizontal-middle-right bar draw_segment_horizontal_caps(tempbitmap, - 0 + bmwidth/2 + segwidth/10, bmwidth - 2*segwidth/3, bmheight/2, - segwidth, LINE_CAP_END, (state & (1 << 7)) ? onpen : offpen); + 0 + bmwidth/2 + segwidth/10, bmwidth - 2*segwidth/3, bmheight/2, + segwidth, LINE_CAP_END, (state & (1 << 7)) ? onpen : offpen); // vertical-middle-top bar draw_segment_vertical_caps(tempbitmap, - 0 + segwidth + segwidth/3, bmheight/2 - segwidth/2 - segwidth/3, bmwidth/2, - segwidth, LINE_CAP_NONE, (state & (1 << 8)) ? onpen : offpen); + 0 + segwidth + segwidth/3, bmheight/2 - segwidth/2 - segwidth/3, bmwidth/2, + segwidth, LINE_CAP_NONE, (state & (1 << 8)) ? onpen : offpen); // vertical-middle-bottom bar draw_segment_vertical_caps(tempbitmap, - bmheight/2 + segwidth/2 + segwidth/3, bmheight - segwidth - segwidth/3, bmwidth/2, - segwidth, LINE_CAP_NONE, (state & (1 << 9)) ? onpen : offpen); + bmheight/2 + segwidth/2 + segwidth/3, bmheight - segwidth - segwidth/3, bmwidth/2, + segwidth, LINE_CAP_NONE, (state & (1 << 9)) ? onpen : offpen); // diagonal-left-bottom bar draw_segment_diagonal_1(tempbitmap, - 0 + segwidth + segwidth/5, bmwidth/2 - segwidth/2 - segwidth/5, - bmheight/2 + segwidth/2 + segwidth/3, bmheight - segwidth - segwidth/3, - segwidth, (state & (1 << 10)) ? onpen : offpen); + 0 + segwidth + segwidth/5, bmwidth/2 - segwidth/2 - segwidth/5, + bmheight/2 + segwidth/2 + segwidth/3, bmheight - segwidth - segwidth/3, + segwidth, (state & (1 << 10)) ? onpen : offpen); // diagonal-left-top bar draw_segment_diagonal_2(tempbitmap, - 0 + segwidth + segwidth/5, bmwidth/2 - segwidth/2 - segwidth/5, - 0 + segwidth + segwidth/3, bmheight/2 - segwidth/2 - segwidth/3, - segwidth, (state & (1 << 11)) ? onpen : offpen); + 0 + segwidth + segwidth/5, bmwidth/2 - segwidth/2 - segwidth/5, + 0 + segwidth + segwidth/3, bmheight/2 - segwidth/2 - segwidth/3, + segwidth, (state & (1 << 11)) ? onpen : offpen); // diagonal-right-top bar draw_segment_diagonal_1(tempbitmap, - bmwidth/2 + segwidth/2 + segwidth/5, bmwidth - segwidth - segwidth/5, - 0 + segwidth + segwidth/3, bmheight/2 - segwidth/2 - segwidth/3, - segwidth, (state & (1 << 12)) ? onpen : offpen); + bmwidth/2 + segwidth/2 + segwidth/5, bmwidth - segwidth - segwidth/5, + 0 + segwidth + segwidth/3, bmheight/2 - segwidth/2 - segwidth/3, + segwidth, (state & (1 << 12)) ? onpen : offpen); // diagonal-right-bottom bar draw_segment_diagonal_2(tempbitmap, - bmwidth/2 + segwidth/2 + segwidth/5, bmwidth - segwidth - segwidth/5, - bmheight/2 + segwidth/2 + segwidth/3, bmheight - segwidth - segwidth/3, - segwidth, (state & (1 << 13)) ? onpen : offpen); + bmwidth/2 + segwidth/2 + segwidth/5, bmwidth - segwidth - segwidth/5, + bmheight/2 + segwidth/2 + segwidth/3, bmheight - segwidth - segwidth/3, + segwidth, (state & (1 << 13)) ? onpen : offpen); // apply skew apply_skew(tempbitmap, 40); // comma tail draw_segment_diagonal_1(tempbitmap, - bmwidth - (segwidth/2), bmwidth + segwidth, - bmheight - (segwidth), bmheight + segwidth*1.5, - segwidth/2, (state & (1 << 15)) ? onpen : offpen); + bmwidth - (segwidth/2), bmwidth + segwidth, + bmheight - (segwidth), bmheight + segwidth*1.5, + segwidth/2, (state & (1 << 15)) ? onpen : offpen); // decimal point - draw_segment_decimal(tempbitmap, bmwidth + segwidth/2, bmheight - segwidth/2, segwidth, (state & (1 << 14)) ? onpen : offpen); + draw_segment_decimal(tempbitmap, + bmwidth + segwidth/2, bmheight - segwidth/2, + segwidth, (state & (1 << 14)) ? onpen : offpen); // resample to the target size - render_resample_argb_bitmap_hq(dest, tempbitmap, color()); + render_resample_argb_bitmap_hq(dest, tempbitmap, color(state)); } }; @@ -2041,14 +2087,14 @@ protected: virtual void draw(running_machine &machine, bitmap_argb32 &dest, const rectangle &bounds, int state) override { - const rgb_t onpen = rgb_t(0xff, 0xff, 0xff, 0xff); - const rgb_t offpen = rgb_t(0x20, 0xff, 0xff, 0xff); + rgb_t const onpen = rgb_t(0xff, 0xff, 0xff, 0xff); + rgb_t const offpen = rgb_t(0x20, 0xff, 0xff, 0xff); // sizes for computation - int bmwidth = 250; - int bmheight = 400; - int segwidth = 40; - int skewwidth = 40; + int const bmwidth = 250; + int const bmheight = 400; + int const segwidth = 40; + int const skewwidth = 40; // allocate a temporary bitmap for drawing bitmap_argb32 tempbitmap(bmwidth + skewwidth, bmheight + segwidth); @@ -2056,102 +2102,103 @@ protected: // top-left bar draw_segment_horizontal_caps(tempbitmap, - 0 + 2*segwidth/3, bmwidth/2 - segwidth/10, 0 + segwidth/2, - segwidth, LINE_CAP_START, (state & (1 << 0)) ? onpen : offpen); + 0 + 2*segwidth/3, bmwidth/2 - segwidth/10, 0 + segwidth/2, + segwidth, LINE_CAP_START, (state & (1 << 0)) ? onpen : offpen); // top-right bar draw_segment_horizontal_caps(tempbitmap, - 0 + bmwidth/2 + segwidth/10, bmwidth - 2*segwidth/3, 0 + segwidth/2, - segwidth, LINE_CAP_END, (state & (1 << 1)) ? onpen : offpen); + 0 + bmwidth/2 + segwidth/10, bmwidth - 2*segwidth/3, 0 + segwidth/2, + segwidth, LINE_CAP_END, (state & (1 << 1)) ? onpen : offpen); // right-top bar draw_segment_vertical(tempbitmap, - 0 + 2*segwidth/3, bmheight/2 - segwidth/3, bmwidth - segwidth/2, - segwidth, (state & (1 << 2)) ? onpen : offpen); + 0 + 2*segwidth/3, bmheight/2 - segwidth/3, bmwidth - segwidth/2, + segwidth, (state & (1 << 2)) ? onpen : offpen); // right-bottom bar draw_segment_vertical(tempbitmap, - bmheight/2 + segwidth/3, bmheight - 2*segwidth/3, bmwidth - segwidth/2, - segwidth, (state & (1 << 3)) ? onpen : offpen); + bmheight/2 + segwidth/3, bmheight - 2*segwidth/3, bmwidth - segwidth/2, + segwidth, (state & (1 << 3)) ? onpen : offpen); // bottom-right bar draw_segment_horizontal_caps(tempbitmap, - 0 + bmwidth/2 + segwidth/10, bmwidth - 2*segwidth/3, bmheight - segwidth/2, - segwidth, LINE_CAP_END, (state & (1 << 4)) ? onpen : offpen); + 0 + bmwidth/2 + segwidth/10, bmwidth - 2*segwidth/3, bmheight - segwidth/2, + segwidth, LINE_CAP_END, (state & (1 << 4)) ? onpen : offpen); // bottom-left bar draw_segment_horizontal_caps(tempbitmap, - 0 + 2*segwidth/3, bmwidth/2 - segwidth/10, bmheight - segwidth/2, - segwidth, LINE_CAP_START, (state & (1 << 5)) ? onpen : offpen); + 0 + 2*segwidth/3, bmwidth/2 - segwidth/10, bmheight - segwidth/2, + segwidth, LINE_CAP_START, (state & (1 << 5)) ? onpen : offpen); // left-bottom bar draw_segment_vertical(tempbitmap, - bmheight/2 + segwidth/3, bmheight - 2*segwidth/3, 0 + segwidth/2, - segwidth, (state & (1 << 6)) ? onpen : offpen); + bmheight/2 + segwidth/3, bmheight - 2*segwidth/3, 0 + segwidth/2, + segwidth, (state & (1 << 6)) ? onpen : offpen); // left-top bar draw_segment_vertical(tempbitmap, - 0 + 2*segwidth/3, bmheight/2 - segwidth/3, 0 + segwidth/2, - segwidth, (state & (1 << 7)) ? onpen : offpen); + 0 + 2*segwidth/3, bmheight/2 - segwidth/3, 0 + segwidth/2, + segwidth, (state & (1 << 7)) ? onpen : offpen); // horizontal-middle-left bar draw_segment_horizontal_caps(tempbitmap, - 0 + 2*segwidth/3, bmwidth/2 - segwidth/10, bmheight/2, - segwidth, LINE_CAP_START, (state & (1 << 8)) ? onpen : offpen); + 0 + 2*segwidth/3, bmwidth/2 - segwidth/10, bmheight/2, + segwidth, LINE_CAP_START, (state & (1 << 8)) ? onpen : offpen); // horizontal-middle-right bar draw_segment_horizontal_caps(tempbitmap, - 0 + bmwidth/2 + segwidth/10, bmwidth - 2*segwidth/3, bmheight/2, - segwidth, LINE_CAP_END, (state & (1 << 9)) ? onpen : offpen); + 0 + bmwidth/2 + segwidth/10, bmwidth - 2*segwidth/3, bmheight/2, + segwidth, LINE_CAP_END, (state & (1 << 9)) ? onpen : offpen); // vertical-middle-top bar draw_segment_vertical_caps(tempbitmap, - 0 + segwidth + segwidth/3, bmheight/2 - segwidth/2 - segwidth/3, bmwidth/2, - segwidth, LINE_CAP_NONE, (state & (1 << 10)) ? onpen : offpen); + 0 + segwidth + segwidth/3, bmheight/2 - segwidth/2 - segwidth/3, bmwidth/2, + segwidth, LINE_CAP_NONE, (state & (1 << 10)) ? onpen : offpen); // vertical-middle-bottom bar draw_segment_vertical_caps(tempbitmap, - bmheight/2 + segwidth/2 + segwidth/3, bmheight - segwidth - segwidth/3, bmwidth/2, - segwidth, LINE_CAP_NONE, (state & (1 << 11)) ? onpen : offpen); + bmheight/2 + segwidth/2 + segwidth/3, bmheight - segwidth - segwidth/3, bmwidth/2, + segwidth, LINE_CAP_NONE, (state & (1 << 11)) ? onpen : offpen); // diagonal-left-bottom bar draw_segment_diagonal_1(tempbitmap, - 0 + segwidth + segwidth/5, bmwidth/2 - segwidth/2 - segwidth/5, - bmheight/2 + segwidth/2 + segwidth/3, bmheight - segwidth - segwidth/3, - segwidth, (state & (1 << 12)) ? onpen : offpen); + 0 + segwidth + segwidth/5, bmwidth/2 - segwidth/2 - segwidth/5, + bmheight/2 + segwidth/2 + segwidth/3, bmheight - segwidth - segwidth/3, + segwidth, (state & (1 << 12)) ? onpen : offpen); // diagonal-left-top bar draw_segment_diagonal_2(tempbitmap, - 0 + segwidth + segwidth/5, bmwidth/2 - segwidth/2 - segwidth/5, - 0 + segwidth + segwidth/3, bmheight/2 - segwidth/2 - segwidth/3, - segwidth, (state & (1 << 13)) ? onpen : offpen); + 0 + segwidth + segwidth/5, bmwidth/2 - segwidth/2 - segwidth/5, + 0 + segwidth + segwidth/3, bmheight/2 - segwidth/2 - segwidth/3, + segwidth, (state & (1 << 13)) ? onpen : offpen); // diagonal-right-top bar draw_segment_diagonal_1(tempbitmap, - bmwidth/2 + segwidth/2 + segwidth/5, bmwidth - segwidth - segwidth/5, - 0 + segwidth + segwidth/3, bmheight/2 - segwidth/2 - segwidth/3, - segwidth, (state & (1 << 14)) ? onpen : offpen); + bmwidth/2 + segwidth/2 + segwidth/5, bmwidth - segwidth - segwidth/5, + 0 + segwidth + segwidth/3, bmheight/2 - segwidth/2 - segwidth/3, + segwidth, (state & (1 << 14)) ? onpen : offpen); // diagonal-right-bottom bar draw_segment_diagonal_2(tempbitmap, - bmwidth/2 + segwidth/2 + segwidth/5, bmwidth - segwidth - segwidth/5, - bmheight/2 + segwidth/2 + segwidth/3, bmheight - segwidth - segwidth/3, - segwidth, (state & (1 << 15)) ? onpen : offpen); + bmwidth/2 + segwidth/2 + segwidth/5, bmwidth - segwidth - segwidth/5, + bmheight/2 + segwidth/2 + segwidth/3, bmheight - segwidth - segwidth/3, + segwidth, (state & (1 << 15)) ? onpen : offpen); + + // apply skew + apply_skew(tempbitmap, 40); // comma tail draw_segment_diagonal_1(tempbitmap, - bmwidth - (segwidth/2), bmwidth + segwidth, - bmheight - (segwidth), bmheight + segwidth*1.5, - segwidth/2, (state & (1 << 17)) ? onpen : offpen); + bmwidth - (segwidth/2), bmwidth + segwidth, bmheight - (segwidth), bmheight + segwidth*1.5, + segwidth/2, (state & (1 << 17)) ? onpen : offpen); // decimal point (draw last for priority) - draw_segment_decimal(tempbitmap, bmwidth + segwidth/2, bmheight - segwidth/2, segwidth, (state & (1 << 16)) ? onpen : offpen); - - // apply skew - apply_skew(tempbitmap, 40); + draw_segment_decimal(tempbitmap, + bmwidth + segwidth/2, bmheight - segwidth/2, + segwidth, (state & (1 << 16)) ? onpen : offpen); // resample to the target size - render_resample_argb_bitmap_hq(dest, tempbitmap, color()); + render_resample_argb_bitmap_hq(dest, tempbitmap, color(state)); } }; @@ -2188,7 +2235,7 @@ protected: draw_segment_decimal(tempbitmap, ((dotwidth / 2) + (i * dotwidth)), bmheight / 2, dotwidth, BIT(state, i) ? onpen : offpen); // resample to the target size - render_resample_argb_bitmap_hq(dest, tempbitmap, color()); + render_resample_argb_bitmap_hq(dest, tempbitmap, color(state)); } private: @@ -2216,9 +2263,8 @@ protected: virtual void draw(running_machine &machine, bitmap_argb32 &dest, const rectangle &bounds, int state) override { - render_font *font = machine.render().font_alloc("default"); - std::string temp = string_format("%0*d", m_digits, state); - draw_text(*font, dest, bounds, temp.c_str(), m_textalign); + render_font *const font = machine.render().font_alloc("default"); + draw_text(*font, dest, bounds, string_format("%0*d", m_digits, state).c_str(), m_textalign, color(state)); machine.render().font_free(font); } @@ -2307,10 +2353,11 @@ protected: int use_state = (state + m_stateoffset) % max_state_used; // compute premultiplied colors - u32 r = color().r * 255.0f; - u32 g = color().g * 255.0f; - u32 b = color().b * 255.0f; - u32 a = color().a * 255.0f; + render_color const c(color(state)); + u32 const r = c.r * 255.0f; + u32 const g = c.g * 255.0f; + u32 const b = c.b * 255.0f; + u32 const a = c.a * 255.0f; // get the width of the string render_font *font = machine.render().font_alloc("default"); @@ -2367,7 +2414,7 @@ protected: if (m_bitmap[fruit].valid()) { - render_resample_argb_bitmap_hq(tempbitmap2, m_bitmap[fruit], color()); + render_resample_argb_bitmap_hq(tempbitmap2, m_bitmap[fruit], c); for (int y = 0; y < ourheight/num_shown; y++) { @@ -2468,10 +2515,11 @@ private: int use_state = (state + m_stateoffset) % max_state_used; // compute premultiplied colors - u32 r = color().r * 255.0f; - u32 g = color().g * 255.0f; - u32 b = color().b * 255.0f; - u32 a = color().a * 255.0f; + render_color const c(color(state)); + u32 const r = c.r * 255.0f; + u32 const g = c.g * 255.0f; + u32 const b = c.b * 255.0f; + u32 const a = c.a * 255.0f; // get the width of the string render_font *font = machine.render().font_alloc("default"); @@ -2526,7 +2574,7 @@ private: if (m_bitmap[fruit].valid()) { - render_resample_argb_bitmap_hq(tempbitmap2, m_bitmap[fruit], color()); + render_resample_argb_bitmap_hq(tempbitmap2, m_bitmap[fruit], c); for (int y = 0; y < dest.height(); y++) { @@ -2736,10 +2784,206 @@ layout_element::texture &layout_element::texture::operator=(texture &&that) //------------------------------------------------- layout_element::component::component(environment &env, util::xml::data_node const &compnode, const char *dirname) - : m_state(env.get_attribute_int(compnode, "state", -1)) - , m_color(env.parse_color(compnode.get_child("color"))) + : m_statemask(env.get_attribute_int(compnode, "statemask", env.get_attribute_string(compnode, "state", "")[0] ? ~0 : 0)) + , m_stateval(env.get_attribute_int(compnode, "state", m_statemask) & m_statemask) +{ + for (util::xml::data_node const *child = compnode.get_first_child(); child; child = child->get_next_sibling()) + { + if (!strcmp(child->get_name(), "bounds")) + { + int const state(env.get_attribute_int(*child, "state", 0)); + auto const pos( + std::lower_bound( + m_bounds.begin(), + m_bounds.end(), + state, + [] (bounds_step const &lhs, int rhs) { return lhs.state < rhs; })); + if ((m_bounds.end() != pos) && (state == pos->state)) + { + throw layout_syntax_error( + util::string_format( + "%s component has duplicate bounds for state %d", + compnode.get_name(), + state)); + } + bounds_step &ins(*m_bounds.emplace(pos, bounds_step{ state, { 0.0F, 0.0F, 0.0F, 0.0F }, { 0.0F, 0.0F, 0.0F, 0.0F } })); + env.parse_bounds(child, ins.bounds); + } + else if (!strcmp(child->get_name(), "color")) + { + int const state(env.get_attribute_int(*child, "state", 0)); + auto const pos( + std::lower_bound( + m_color.begin(), + m_color.end(), + state, + [] (color_step const &lhs, int rhs) { return lhs.state < rhs; })); + if ((m_color.end() != pos) && (state == pos->state)) + { + throw layout_syntax_error( + util::string_format( + "%s component has duplicate color for state %d", + compnode.get_name(), + state)); + } + m_color.emplace(pos, color_step{ state, env.parse_color(child), { 0.0F, 0.0F, 0.0F, 0.0F } }); + } + } + if (m_bounds.empty()) + { + m_bounds.emplace_back(bounds_step{ 0, { 0.0F, 0.0F, 1.0F, 1.0F }, { 0.0F, 0.0F, 0.0F, 0.0F } }); + } + else + { + auto i(m_bounds.begin()); + auto j(i); + while (m_bounds.end() != ++j) + { + assert(j->state > i->state); + + i->delta.x0 = (j->bounds.x0 - i->bounds.x0) / (j->state - i->state); + i->delta.x1 = (j->bounds.x1 - i->bounds.x1) / (j->state - i->state); + i->delta.y0 = (j->bounds.y0 - i->bounds.y0) / (j->state - i->state); + i->delta.y1 = (j->bounds.y1 - i->bounds.y1) / (j->state - i->state); + + i = j; + } + } + if (m_color.empty()) + { + m_color.emplace_back(color_step{ 0, { 1.0F, 1.0F, 1.0F, 1.0F }, { 0.0F, 0.0F, 0.0F, 0.0F } }); + } + else + { + auto i(m_color.begin()); + auto j(i); + while (m_color.end() != ++j) + { + assert(j->state > i->state); + + i->delta.a = (j->color.a - i->color.a) / (j->state - i->state); + i->delta.r = (j->color.r - i->color.r) / (j->state - i->state); + i->delta.g = (j->color.g - i->color.g) / (j->state - i->state); + i->delta.b = (j->color.b - i->color.b) / (j->state - i->state); + + i = j; + } + } +} + + +//------------------------------------------------- +// statewrap - get state wraparound requirements +//------------------------------------------------- + +std::pair<int, bool> layout_element::component::statewrap() const +{ + int result(0); + bool fold; + auto const adjustmask = + [&result, &fold] (int val, int mask) + { + assert(!(val & ~mask)); + auto const splatright = + [] (int x) + { + for (unsigned shift = 1; (sizeof(x) * 4) >= shift; shift <<= 1) + x |= (x >> shift); + return x; + }; + int const unfolded(splatright(mask)); + int const folded(splatright(~mask | splatright(val))); + if (unsigned(folded) < unsigned(unfolded)) + { + result |= folded; + fold = true; + } + else + { + result |= unfolded; + } + }; + adjustmask(stateval(), statemask()); + int max(maxstate()); + if (m_bounds.size() > 1U) + max = (std::max)(max, m_bounds.back().state); + if (m_color.size() > 1U) + max = (std::max)(max, m_color.back().state); + if (0 <= max) + adjustmask(max, ~0); + return std::make_pair(result, fold); +} + + +//------------------------------------------------- +// overall_bounds - maximum bounds for all states +//------------------------------------------------- + +render_bounds layout_element::component::overall_bounds() const { - env.parse_bounds(compnode.get_child("bounds"), m_bounds); + auto i(m_bounds.begin()); + render_bounds result(i->bounds); + while (m_bounds.end() != ++i) + union_render_bounds(result, i->bounds); + return result; +} + + +//------------------------------------------------- +// bounds - bounds for a given state +//------------------------------------------------- + +render_bounds layout_element::component::bounds(int state) const +{ + auto pos( + std::lower_bound( + m_bounds.begin(), + m_bounds.end(), + state, + [] (bounds_step const &lhs, int rhs) { return lhs.state < rhs; })); + if (m_bounds.begin() == pos) + { + return pos->bounds; + } + else + { + --pos; + render_bounds result(pos->bounds); + result.x0 += pos->delta.x0 * (state - pos->state); + result.x1 += pos->delta.x1 * (state - pos->state); + result.y0 += pos->delta.y0 * (state - pos->state); + result.y1 += pos->delta.y1 * (state - pos->state); + return result; + } +} + + +//------------------------------------------------- +// color - color for a given state +//------------------------------------------------- + +render_color layout_element::component::color(int state) const +{ + auto pos( + std::lower_bound( + m_color.begin(), + m_color.end(), + state, + [] (color_step const &lhs, int rhs) { return lhs.state < rhs; })); + if (m_color.begin() == pos) + { + return pos->color; + } + else + { + --pos; + render_color result(pos->color); + result.a += pos->delta.a * (state - pos->state); + result.r += pos->delta.r * (state - pos->state); + result.g += pos->delta.g * (state - pos->state); + result.b += pos->delta.b * (state - pos->state); + return result; + } } @@ -2749,10 +2993,27 @@ layout_element::component::component(environment &env, util::xml::data_node cons void layout_element::component::normalize_bounds(float xoffs, float yoffs, float xscale, float yscale) { - m_bounds.x0 = (m_bounds.x0 - xoffs) * xscale; - m_bounds.x1 = (m_bounds.x1 - xoffs) * xscale; - m_bounds.y0 = (m_bounds.y0 - yoffs) * yscale; - m_bounds.y1 = (m_bounds.y1 - yoffs) * yscale; + auto i(m_bounds.begin()); + i->bounds.x0 = (i->bounds.x0 - xoffs) * xscale; + i->bounds.x1 = (i->bounds.x1 - xoffs) * xscale; + i->bounds.y0 = (i->bounds.y0 - yoffs) * yscale; + i->bounds.y1 = (i->bounds.y1 - yoffs) * yscale; + + auto j(i); + while (m_bounds.end() != ++j) + { + j->bounds.x0 = (j->bounds.x0 - xoffs) * xscale; + j->bounds.x1 = (j->bounds.x1 - xoffs) * xscale; + j->bounds.y0 = (j->bounds.y0 - yoffs) * yscale; + j->bounds.y1 = (j->bounds.y1 - yoffs) * yscale; + + i->delta.x0 = (j->bounds.x0 - i->bounds.x0) / (j->state - i->state); + i->delta.x1 = (j->bounds.x1 - i->bounds.x1) / (j->state - i->state); + i->delta.y0 = (j->bounds.y0 - i->bounds.y0) / (j->state - i->state); + i->delta.y1 = (j->bounds.y1 - i->bounds.y1) / (j->state - i->state); + + i = j; + } } @@ -2760,13 +3021,19 @@ void layout_element::component::normalize_bounds(float xoffs, float yoffs, float // draw_text - draw text in the specified color //------------------------------------------------- -void layout_element::component::draw_text(render_font &font, bitmap_argb32 &dest, const rectangle &bounds, const char *str, int align) +void layout_element::component::draw_text( + render_font &font, + bitmap_argb32 &dest, + const rectangle &bounds, + const char *str, + int align, + const render_color &color) { // compute premultiplied colors - u32 r = color().r * 255.0f; - u32 g = color().g * 255.0f; - u32 b = color().b * 255.0f; - u32 a = color().a * 255.0f; + u32 const r(color.r * 255.0f); + u32 const g(color.g * 255.0f); + u32 const b(color.b * 255.0f); + u32 const a(color.a * 255.0f); // get the width of the string float aspect = 1.0f; diff --git a/src/emu/rendutil.cpp b/src/emu/rendutil.cpp index 8baf312d224..f3e4950f598 100644 --- a/src/emu/rendutil.cpp +++ b/src/emu/rendutil.cpp @@ -9,12 +9,13 @@ ***************************************************************************/ #include "emu.h" -#include "render.h" #include "rendutil.h" + #include "png.h" #include "jpeglib.h" + /*************************************************************************** FUNCTION PROTOTYPES ***************************************************************************/ diff --git a/src/emu/save.cpp b/src/emu/save.cpp index b2456d17b6f..b95ff1796b4 100644 --- a/src/emu/save.cpp +++ b/src/emu/save.cpp @@ -416,7 +416,7 @@ inline save_error save_manager::do_write(T check_space, U write_block, V start_h { const u32 blocksize = entry->m_typesize * entry->m_typecount; const u8 *data = reinterpret_cast<const u8 *>(entry->m_data); - for (u32 b = 0; entry->m_blockcount > b; ++b, data += (entry->m_typesize * entry->m_stride)) + for (u32 b = 0; entry->m_blockcount > b; ++b, data += entry->m_stride) if (!write_block(data, blocksize)) return STATERR_WRITE_ERROR; } @@ -460,7 +460,7 @@ inline save_error save_manager::do_read(T check_length, U read_block, V start_he { const u32 blocksize = entry->m_typesize * entry->m_typecount; u8 *data = reinterpret_cast<u8 *>(entry->m_data); - for (u32 b = 0; entry->m_blockcount > b; ++b, data += (entry->m_typesize * entry->m_stride)) + for (u32 b = 0; entry->m_blockcount > b; ++b, data += entry->m_stride) if (!read_block(data, blocksize)) return STATERR_READ_ERROR; @@ -984,7 +984,7 @@ save_manager::state_entry::state_entry(void *data, const char *name, device_t *d void save_manager::state_entry::flip_data() { u8 *data = reinterpret_cast<u8 *>(m_data); - for (u32 b = 0; m_blockcount > b; ++b, data += (m_typesize * m_stride)) + for (u32 b = 0; m_blockcount > b; ++b, data += m_stride) { u16 *data16; u32 *data32; diff --git a/src/emu/save.h b/src/emu/save.h index f015534f02c..f31e3762d5b 100644 --- a/src/emu/save.h +++ b/src/emu/save.h @@ -55,12 +55,12 @@ typedef named_delegate<void ()> save_prepost_delegate; // saved; in general, this is intended only to be used for specific enum types // defined by your device #define ALLOW_SAVE_TYPE(TYPE) \ - template <> struct save_manager::type_checker<TYPE> { static constexpr bool is_atom = true; static constexpr bool is_pointer = false; } + template <> struct save_manager::is_atom<TYPE> { static constexpr bool value = true; }; // use this as above, but also to declare that std::vector<TYPE> is safe as well -#define ALLOW_SAVE_TYPE_AND_ARRAY(TYPE) \ - ALLOW_SAVE_TYPE(TYPE); \ - template <> inline void save_manager::save_item(device_t *device, const char *module, const char *tag, int index, std::vector<TYPE> &value, const char *name) { save_memory(device, module, tag, index, name, &value[0], sizeof(TYPE), value.size()); } +#define ALLOW_SAVE_TYPE_AND_VECTOR(TYPE) \ + ALLOW_SAVE_TYPE(TYPE) \ + template <> struct save_manager::is_vector_safe<TYPE> { static constexpr bool value = true; }; // use this for saving members of structures in arrays #define STRUCT_MEMBER(s, m) s, &save_manager::pointer_unwrap<decltype(s)>::underlying_type::m, #s "." #m @@ -98,9 +98,9 @@ class save_manager static underlying_type *ptr(std::array<T, N> &value) { return array_unwrap<T>::ptr(value[0]); } }; - // type_checker is a set of templates to identify valid save types - template <typename ItemType> struct type_checker { static constexpr bool is_atom = false; static constexpr bool is_pointer = false; }; - template <typename ItemType> struct type_checker<ItemType *> { static constexpr bool is_atom = false; static constexpr bool is_pointer = true; }; + // set of templates to identify valid save types + template <typename ItemType> struct is_atom { static constexpr bool value = false; }; + template <typename ItemType> struct is_vector_safe { static constexpr bool value = false; }; class state_entry { @@ -160,10 +160,9 @@ public: // templatized wrapper for general objects and arrays template <typename ItemType> - void save_item(device_t *device, const char *module, const char *tag, int index, ItemType &value, const char *valname) + std::enable_if_t<is_atom<typename array_unwrap<ItemType>::underlying_type>::value> save_item(device_t *device, const char *module, const char *tag, int index, ItemType &value, const char *valname) { - static_assert(!type_checker<ItemType>::is_pointer, "Called save_item on a pointer with no count!"); - static_assert(type_checker<typename array_unwrap<ItemType>::underlying_type>::is_atom, "Called save_item on a non-fundamental type!"); + static_assert(!std::is_pointer<ItemType>::value, "Called save_item on a pointer with no count!"); save_memory(device, module, tag, index, valname, array_unwrap<ItemType>::ptr(value), array_unwrap<ItemType>::SIZE, array_unwrap<ItemType>::SAVE_COUNT); } @@ -172,17 +171,15 @@ public: void save_item(device_t *device, const char *module, const char *tag, int index, ItemType &value, ElementType StructType::*element, const char *valname) { static_assert(std::is_base_of<StructType, typename array_unwrap<ItemType>::underlying_type>::value, "Called save_item on a non-matching struct member pointer!"); - static_assert(!(sizeof(typename array_unwrap<ItemType>::underlying_type) % sizeof(typename array_unwrap<ElementType>::underlying_type)), "Called save_item on an unaligned struct member!"); - static_assert(!type_checker<ElementType>::is_pointer, "Called save_item on a struct member pointer!"); - static_assert(type_checker<typename array_unwrap<ElementType>::underlying_type>::is_atom, "Called save_item on a non-fundamental type!"); - save_memory(device, module, tag, index, valname, array_unwrap<ElementType>::ptr(array_unwrap<ItemType>::ptr(value)->*element), array_unwrap<ElementType>::SIZE, array_unwrap<ElementType>::SAVE_COUNT, array_unwrap<ItemType>::SAVE_COUNT, sizeof(typename array_unwrap<ItemType>::underlying_type) / sizeof(typename array_unwrap<ElementType>::underlying_type)); + static_assert(!std::is_pointer<ElementType>::value, "Called save_item on a struct member pointer!"); + static_assert(is_atom<typename array_unwrap<ElementType>::underlying_type>::value, "Called save_item on a non-fundamental type!"); + save_memory(device, module, tag, index, valname, array_unwrap<ElementType>::ptr(array_unwrap<ItemType>::ptr(value)->*element), array_unwrap<ElementType>::SIZE, array_unwrap<ElementType>::SAVE_COUNT, array_unwrap<ItemType>::SAVE_COUNT, sizeof(typename array_unwrap<ItemType>::underlying_type)); } // templatized wrapper for pointers template <typename ItemType> - void save_pointer(device_t *device, const char *module, const char *tag, int index, ItemType *value, const char *valname, u32 count) + std::enable_if_t<is_atom<typename array_unwrap<ItemType>::underlying_type>::value> save_pointer(device_t *device, const char *module, const char *tag, int index, ItemType *value, const char *valname, u32 count) { - static_assert(type_checker<typename array_unwrap<ItemType>::underlying_type>::is_atom, "Called save_pointer on a non-fundamental type!"); save_memory(device, module, tag, index, valname, array_unwrap<ItemType>::ptr(value[0]), array_unwrap<ItemType>::SIZE, array_unwrap<ItemType>::SAVE_COUNT * count); } @@ -190,17 +187,15 @@ public: void save_pointer(device_t *device, const char *module, const char *tag, int index, ItemType *value, ElementType StructType::*element, const char *valname, u32 count) { static_assert(std::is_base_of<StructType, typename array_unwrap<ItemType>::underlying_type>::value, "Called save_pointer on a non-matching struct member pointer!"); - static_assert(!(sizeof(typename array_unwrap<ItemType>::underlying_type) % sizeof(typename array_unwrap<ElementType>::underlying_type)), "Called save_pointer on an unaligned struct member!"); - static_assert(!type_checker<ElementType>::is_pointer, "Called save_pointer on a struct member pointer!"); - static_assert(type_checker<typename array_unwrap<ElementType>::underlying_type>::is_atom, "Called save_pointer on a non-fundamental type!"); - save_memory(device, module, tag, index, valname, array_unwrap<ElementType>::ptr(array_unwrap<ItemType>::ptr(value[0])->*element), array_unwrap<ElementType>::SIZE, array_unwrap<ElementType>::SAVE_COUNT, array_unwrap<ItemType>::SAVE_COUNT * count, sizeof(typename array_unwrap<ItemType>::underlying_type) / sizeof(typename array_unwrap<ElementType>::underlying_type)); + static_assert(!std::is_pointer<ElementType>::value, "Called save_pointer on a struct member pointer!"); + static_assert(is_atom<typename array_unwrap<ElementType>::underlying_type>::value, "Called save_pointer on a non-fundamental type!"); + save_memory(device, module, tag, index, valname, array_unwrap<ElementType>::ptr(array_unwrap<ItemType>::ptr(value[0])->*element), array_unwrap<ElementType>::SIZE, array_unwrap<ElementType>::SAVE_COUNT, array_unwrap<ItemType>::SAVE_COUNT * count, sizeof(typename array_unwrap<ItemType>::underlying_type)); } // templatized wrapper for std::unique_ptr template <typename ItemType> - void save_pointer(device_t *device, const char *module, const char *tag, int index, const std::unique_ptr<ItemType []> &value, const char *valname, u32 count) + std::enable_if_t<is_atom<typename array_unwrap<ItemType>::underlying_type>::value> save_pointer(device_t *device, const char *module, const char *tag, int index, const std::unique_ptr<ItemType []> &value, const char *valname, u32 count) { - static_assert(type_checker<typename array_unwrap<ItemType>::underlying_type>::is_atom, "Called save_pointer on a non-fundamental type!"); save_memory(device, module, tag, index, valname, array_unwrap<ItemType>::ptr(value[0]), array_unwrap<ItemType>::SIZE, array_unwrap<ItemType>::SAVE_COUNT * count); } @@ -208,10 +203,68 @@ public: void save_pointer(device_t *device, const char *module, const char *tag, int index, const std::unique_ptr<ItemType []> &value, ElementType StructType::*element, const char *valname, u32 count) { static_assert(std::is_base_of<StructType, typename array_unwrap<ItemType>::underlying_type>::value, "Called save_pointer on a non-matching struct member pointer!"); - static_assert(!(sizeof(typename array_unwrap<ItemType>::underlying_type) % sizeof(typename array_unwrap<ElementType>::underlying_type)), "Called save_pointer on an unaligned struct member!"); - static_assert(!type_checker<ElementType>::is_pointer, "Called save_pointer on a struct member pointer!"); - static_assert(type_checker<typename array_unwrap<ElementType>::underlying_type>::is_atom, "Called save_pointer on a non-fundamental type!"); - save_memory(device, module, tag, index, valname, array_unwrap<ElementType>::ptr(array_unwrap<ItemType>::ptr(value[0])->*element), array_unwrap<ElementType>::SIZE, array_unwrap<ElementType>::SAVE_COUNT, array_unwrap<ItemType>::SAVE_COUNT * count, sizeof(typename array_unwrap<ItemType>::underlying_type) / sizeof(typename array_unwrap<ElementType>::underlying_type)); + static_assert(!std::is_pointer<ElementType>::value, "Called save_pointer on a struct member pointer!"); + static_assert(is_atom<typename array_unwrap<ElementType>::underlying_type>::value, "Called save_pointer on a non-fundamental type!"); + save_memory(device, module, tag, index, valname, array_unwrap<ElementType>::ptr(array_unwrap<ItemType>::ptr(value[0])->*element), array_unwrap<ElementType>::SIZE, array_unwrap<ElementType>::SAVE_COUNT, array_unwrap<ItemType>::SAVE_COUNT * count, sizeof(typename array_unwrap<ItemType>::underlying_type)); + } + + // templatized wrapper for std::vector + template <typename ItemType> + std::enable_if_t<is_vector_safe<typename array_unwrap<ItemType>::underlying_type>::value> save_item(device_t *device, const char *module, const char *tag, int index, std::vector<ItemType> &value, const char *valname) + { + save_pointer(device, module, tag, index, &value[0], valname, value.size()); + } + + // specializations for bitmaps + void save_item(device_t *device, const char *module, const char *tag, int index, bitmap_ind8 &value, const char *valname) + { + save_memory(device, module, tag, index, valname, &value.pix(0), value.bpp() / 8, value.rowpixels() * value.height()); + } + + void save_item(device_t *device, const char *module, const char *tag, int index, bitmap_ind16 &value, const char *valname) + { + save_memory(device, module, tag, index, valname, &value.pix(0), value.bpp() / 8, value.rowpixels() * value.height()); + } + + void save_item(device_t *device, const char *module, const char *tag, int index, bitmap_ind32 &value, const char *valname) + { + save_memory(device, module, tag, index, valname, &value.pix(0), value.bpp() / 8, value.rowpixels() * value.height()); + } + + void save_item(device_t *device, const char *module, const char *tag, int index, bitmap_rgb32 &value, const char *valname) + { + save_memory(device, module, tag, index, valname, &value.pix(0), value.bpp() / 8, value.rowpixels() * value.height()); + } + + // specializations for attotimes + template <typename ItemType> + std::enable_if_t<std::is_same<typename save_manager::array_unwrap<ItemType>::underlying_type, attotime>::value> save_item(device_t *device, const char *module, const char *tag, int index, ItemType &value, const char *valname) + { + std::string tempstr; + tempstr.assign(valname).append(".attoseconds"); + save_item(device, module, tag, index, value, &attotime::m_attoseconds, tempstr.c_str()); + tempstr.assign(valname).append(".seconds"); + save_item(device, module, tag, index, value, &attotime::m_seconds, tempstr.c_str()); + } + + template <typename ItemType> + std::enable_if_t<std::is_same<typename save_manager::array_unwrap<ItemType>::underlying_type, attotime>::value> save_pointer(device_t *device, const char *module, const char *tag, int index, ItemType *value, const char *valname, u32 count) + { + std::string tempstr; + tempstr.assign(valname).append(".attoseconds"); + save_item(device, module, tag, index, value, &attotime::m_attoseconds, tempstr.c_str(), count); + tempstr.assign(valname).append(".seconds"); + save_item(device, module, tag, index, value, &attotime::m_seconds, tempstr.c_str(), count); + } + + template <typename ItemType> + std::enable_if_t<std::is_same<typename save_manager::array_unwrap<ItemType>::underlying_type, attotime>::value> save_pointer(device_t *device, const char *module, const char *tag, int index, const std::unique_ptr<ItemType []> &value, const char *valname, u32 count) + { + std::string tempstr; + tempstr.assign(valname).append(".attoseconds"); + save_item(device, module, tag, index, value, &attotime::m_attoseconds, tempstr.c_str(), count); + tempstr.assign(valname).append(".seconds"); + save_item(device, module, tag, index, value, &attotime::m_seconds, tempstr.c_str(), count); } // global memory registration @@ -325,70 +378,22 @@ public: // template specializations to enumerate the fundamental atomic types you are allowed to save -ALLOW_SAVE_TYPE_AND_ARRAY(char) -ALLOW_SAVE_TYPE (bool); // std::vector<bool> may be packed internally -ALLOW_SAVE_TYPE_AND_ARRAY(osd::s8) -ALLOW_SAVE_TYPE_AND_ARRAY(osd::u8) -ALLOW_SAVE_TYPE_AND_ARRAY(osd::s16) -ALLOW_SAVE_TYPE_AND_ARRAY(osd::u16) -ALLOW_SAVE_TYPE_AND_ARRAY(osd::s32) -ALLOW_SAVE_TYPE_AND_ARRAY(osd::u32) -ALLOW_SAVE_TYPE_AND_ARRAY(osd::s64) -ALLOW_SAVE_TYPE_AND_ARRAY(osd::u64) -ALLOW_SAVE_TYPE_AND_ARRAY(PAIR) -ALLOW_SAVE_TYPE_AND_ARRAY(PAIR64) -ALLOW_SAVE_TYPE_AND_ARRAY(float) -ALLOW_SAVE_TYPE_AND_ARRAY(double) -ALLOW_SAVE_TYPE_AND_ARRAY(endianness_t) -ALLOW_SAVE_TYPE_AND_ARRAY(rgb_t) - - - -//************************************************************************** -// INLINE FUNCTIONS -//************************************************************************** - -//------------------------------------------------- -// save_item - specialized save_item for bitmaps -//------------------------------------------------- - -template <> -inline void save_manager::save_item(device_t *device, const char *module, const char *tag, int index, bitmap_ind8 &value, const char *name) -{ - save_memory(device, module, tag, index, name, &value.pix(0), value.bpp() / 8, value.rowpixels() * value.height()); -} - -template <> -inline void save_manager::save_item(device_t *device, const char *module, const char *tag, int index, bitmap_ind16 &value, const char *name) -{ - save_memory(device, module, tag, index, name, &value.pix(0), value.bpp() / 8, value.rowpixels() * value.height()); -} - -template <> -inline void save_manager::save_item(device_t *device, const char *module, const char *tag, int index, bitmap_ind32 &value, const char *name) -{ - save_memory(device, module, tag, index, name, &value.pix(0), value.bpp() / 8, value.rowpixels() * value.height()); -} - -template <> -inline void save_manager::save_item(device_t *device, const char *module, const char *tag, int index, bitmap_rgb32 &value, const char *name) -{ - save_memory(device, module, tag, index, name, &value.pix(0), value.bpp() / 8, value.rowpixels() * value.height()); -} - - -//------------------------------------------------- -// save_item - specialized save_item for attotimes -//------------------------------------------------- - -template <> -inline void save_manager::save_item(device_t *device, const char *module, const char *tag, int index, attotime &value, const char *name) -{ - std::string tempstr = std::string(name).append(".attoseconds"); - save_memory(device, module, tag, index, tempstr.c_str(), &value.m_attoseconds, sizeof(value.m_attoseconds)); - tempstr.assign(name).append(".seconds"); - save_memory(device, module, tag, index, tempstr.c_str(), &value.m_seconds, sizeof(value.m_seconds)); -} +ALLOW_SAVE_TYPE_AND_VECTOR(char) +ALLOW_SAVE_TYPE (bool) // std::vector<bool> may be packed internally +ALLOW_SAVE_TYPE_AND_VECTOR(osd::s8) +ALLOW_SAVE_TYPE_AND_VECTOR(osd::u8) +ALLOW_SAVE_TYPE_AND_VECTOR(osd::s16) +ALLOW_SAVE_TYPE_AND_VECTOR(osd::u16) +ALLOW_SAVE_TYPE_AND_VECTOR(osd::s32) +ALLOW_SAVE_TYPE_AND_VECTOR(osd::u32) +ALLOW_SAVE_TYPE_AND_VECTOR(osd::s64) +ALLOW_SAVE_TYPE_AND_VECTOR(osd::u64) +ALLOW_SAVE_TYPE_AND_VECTOR(PAIR) +ALLOW_SAVE_TYPE_AND_VECTOR(PAIR64) +ALLOW_SAVE_TYPE_AND_VECTOR(float) +ALLOW_SAVE_TYPE_AND_VECTOR(double) +ALLOW_SAVE_TYPE_AND_VECTOR(endianness_t) +ALLOW_SAVE_TYPE_AND_VECTOR(rgb_t) #endif // MAME_EMU_SAVE_H diff --git a/src/emu/sound.cpp b/src/emu/sound.cpp index b59b2304edd..6b54797de36 100644 --- a/src/emu/sound.cpp +++ b/src/emu/sound.cpp @@ -25,6 +25,8 @@ #define VPRINTF(x) do { if (VERBOSE) osd_printf_debug x; } while (0) +#define LOG_OUTPUT_WAV (0) + //************************************************************************** @@ -42,783 +44,1081 @@ const attotime sound_manager::STREAMS_UPDATE_ATTOTIME = attotime::from_hz(STREAM //************************************************************************** -// INITIALIZATION +// STREAM BUFFER //************************************************************************** //------------------------------------------------- -// sound_stream - constructor -//------------------------------------------------- - -sound_stream::sound_stream(device_t &device, int inputs, int outputs, int sample_rate, stream_update_delegate callback) - : m_device(device), - m_next(nullptr), - m_sample_rate(sample_rate), - m_new_sample_rate(0xffffffff), - m_attoseconds_per_sample(0), - m_max_samples_per_update(0), - m_input(inputs), - m_input_array(inputs), - m_resample_bufalloc(0), - m_output(outputs), - m_output_array(outputs), - m_output_bufalloc(0), - m_output_sampindex(0), - m_output_update_sampindex(0), - m_output_base_sampindex(0), - m_callback(std::move(callback)) +// stream_buffer - constructor +//------------------------------------------------- + +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) { - // get the device's sound interface - device_sound_interface *sound; - if (!device.interface(sound)) - throw emu_fatalerror("Attempted to create a sound_stream with a non-sound device"); +} - if(m_callback.isnull()) - m_callback = stream_update_delegate(&device_sound_interface::sound_stream_update,(device_sound_interface *)sound); - // create a unique tag for saving - std::string state_tag = string_format("%d", m_device.machine().sound().m_stream_list.size()); - m_device.machine().save().save_item(&m_device, "stream", state_tag.c_str(), 0, NAME(m_sample_rate)); - m_device.machine().save().register_postload(save_prepost_delegate(FUNC(sound_stream::postload), this)); +//------------------------------------------------- +// stream_buffer - destructor +//------------------------------------------------- - // save the gain of each input and output - for (unsigned int inputnum = 0; inputnum < m_input.size(); inputnum++) +stream_buffer::~stream_buffer() +{ +#if (SOUND_DEBUG) + if (m_wav_file != nullptr) { - m_device.machine().save().save_item(&m_device, "stream", state_tag.c_str(), inputnum, NAME(m_input[inputnum].m_gain)); - m_device.machine().save().save_item(&m_device, "stream", state_tag.c_str(), inputnum, NAME(m_input[inputnum].m_user_gain)); + flush_wav(); + close_wav(); } - for (unsigned int outputnum = 0; outputnum < m_output.size(); outputnum++) +#endif +} + + +//------------------------------------------------- +// set_sample_rate - set a new sample rate for +// this buffer +//------------------------------------------------- + +void stream_buffer::set_sample_rate(u32 rate, bool resample) +{ + // skip if nothing is actually changing + if (rate == m_sample_rate) + return; + + // force resampling off if coming to or from an invalid rate + sound_assert(rate >= SAMPLE_RATE_MINIMUM - 1); + if (rate < SAMPLE_RATE_MINIMUM || m_sample_rate < SAMPLE_RATE_MINIMUM) + 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(ARRAY_LENGTH(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) { - m_output[outputnum].m_stream = this; - m_device.machine().save().save_item(&m_device, "stream", state_tag.c_str(), outputnum, NAME(m_output[outputnum].m_gain)); + 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); + buffer[index] = get(end); + } + } } - // Mark synchronous streams as such - m_synchronous = m_sample_rate == STREAM_SYNC; - if (m_synchronous) + // 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) { - m_sample_rate = 0; - m_sync_timer = m_device.machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(sound_stream::sync_update), this)); - } - else - m_sync_timer = nullptr; +#if (SOUND_DEBUG) + // for aggressive debugging, fill the buffer with NANs to catch anyone + // reading beyond what we resample below + fill(NAN); +#endif - // force an update to the sample rates; this will cause everything to be recomputed - // and will generate the initial resample buffers for our inputs - recompute_sample_rate_data(); + 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]); + } + } + } - // set up the initial output buffer positions now that we have data - m_output_base_sampindex = -m_max_samples_per_update; + // if not resampling, clear the buffer + else + fill(0); } //------------------------------------------------- -// sample_time - return the emulation time of the -// next sample to be generated on the stream +// open_wav - open a WAV file for logging purposes //------------------------------------------------- -attotime sound_stream::sample_time() const +#if (SOUND_DEBUG) +void stream_buffer::open_wav(char const *filename) { - return attotime(m_device.machine().sound().last_update().seconds(), 0) + attotime(0, m_output_sampindex * m_attoseconds_per_sample); + // always open at 48k so that sound programs can handle it + // re-sample as needed + m_wav_file = wav_open(filename, 48000, 1); } +#endif //------------------------------------------------- -// user_gain - return the user-controllable gain -// on a given stream's input +// flush_wav - flush data to the WAV file //------------------------------------------------- -float sound_stream::user_gain(int inputnum) const +#if (SOUND_DEBUG) +void stream_buffer::flush_wav() { - assert(inputnum >= 0 && inputnum < m_input.size()); - return float(m_input[inputnum].m_user_gain) / 256.0f; + // skip if no file + if (m_wav_file == nullptr) + return; + + // 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; + + // iterate over chunks for conversion + s16 buffer[1024]; + for (int samplebase = 0; samplebase < view.samples(); samplebase += ARRAY_LENGTH(buffer)) + { + // clamp to the buffer size + int cursamples = view.samples() - samplebase; + if (cursamples > ARRAY_LENGTH(buffer)) + cursamples = ARRAY_LENGTH(buffer); + + // convert and fill + for (int sampindex = 0; sampindex < cursamples; sampindex++) + buffer[sampindex] = s16(view.get(samplebase + sampindex) * 32768.0); + + // write to the WAV + wav_add_data_16(m_wav_file, buffer, cursamples); + } } +#endif //------------------------------------------------- -// input_gain - return the input gain on a -// given stream's input +// close_wav - close the logging WAV file //------------------------------------------------- -float sound_stream::input_gain(int inputnum) const +#if (SOUND_DEBUG) +void stream_buffer::close_wav() { - assert(inputnum >= 0 && inputnum < m_input.size()); - return float(m_input[inputnum].m_gain) / 256.0f; + if (m_wav_file != nullptr) + wav_close(m_wav_file); + m_wav_file = nullptr; } +#endif //------------------------------------------------- -// output_gain - return the output gain on a -// given stream's output +// index_time - return the attotime of a given +// index within the buffer //------------------------------------------------- -float sound_stream::output_gain(int outputnum) const +attotime stream_buffer::index_time(s32 index) const { - assert(outputnum >= 0 && outputnum < m_output.size()); - return float(m_output[outputnum].m_gain) / 256.0f; + index = clamp_index(index); + return attotime(m_end_second - ((index > m_end_sample) ? 1 : 0), index * m_sample_attos); } //------------------------------------------------- -// input_name - return the original input gain -// on a given stream's input +// time_to_buffer_index - given an attotime, +// return the buffer index corresponding to it //------------------------------------------------- -std::string sound_stream::input_name(int inputnum) const +u32 stream_buffer::time_to_buffer_index(attotime time, bool round_up, bool allow_expansion) { - std::ostringstream str; - - // start with our device name and tag - assert(inputnum >= 0 && inputnum < m_input.size()); - util::stream_format(str, "%s '%s': ", m_device.name(), m_device.tag()); + // 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 we have a source, indicate where the sound comes from by device name and tag - if (m_input[inputnum].m_source != nullptr && m_input[inputnum].m_source->m_stream != nullptr) + // 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)) { - device_t &source = m_input[inputnum].m_source->m_stream->device(); - util::stream_format(str, "%s '%s'", source.name(), source.tag()); + sound_assert(allow_expansion); + + m_end_sample = sample; + m_end_second = time.m_seconds; - // get the sound interface; if there is more than 1 output we need to figure out which one - device_sound_interface *sound; - if (source.interface(sound) && sound->outputs() > 1) + // due to round_up, we could tweak over the line into the next second + if (sample >= size()) { - // iterate over outputs until we find the stream that matches our source - // then look for a match on the output number - sound_stream *outstream; - int streamoutputnum; - for (int outputnum = 0; (outstream = sound->output_to_stream_output(outputnum, streamoutputnum)) != nullptr; outputnum++) - if (outstream == m_input[inputnum].m_source->m_stream && m_input[inputnum].m_source == &outstream->m_output[streamoutputnum]) - { - util::stream_format(str, " Ch.%d", outputnum); - break; - } + m_end_sample -= size(); + m_end_second++; } } - return str.str(); + + // 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); } //------------------------------------------------- -// input_source_device - return the device -// attached as a given input's source +// backfill_downsample - this is called BEFORE +// the sample rate change to downsample from the +// end of the current buffer into a temporary +// holding location //------------------------------------------------- -device_t *sound_stream::input_source_device(int inputnum) const +void stream_buffer::backfill_downsample(sample_t *dest, int samples, attotime newend, attotime newperiod) { - assert(inputnum >= 0 && inputnum < m_input.size()); - return (m_input[inputnum].m_source != nullptr) ? &m_input[inputnum].m_source->m_stream->device() : nullptr; + // compute the time of the first sample to be backfilled; start one period before + attotime time = newend - newperiod; + + // 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; + else +#endif + dest[dstindex] = get(srcindex); + time -= newperiod; + } + for ( ; dstindex < samples; dstindex++) + dest[dstindex] = 0; } //------------------------------------------------- -// input_source_device - return the output number -// attached as a given input's source +// 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 //------------------------------------------------- -int sound_stream::input_source_outputnum(int inputnum) const +void stream_buffer::backfill_upsample(sample_t const *src, int samples, attotime prevend, attotime prevperiod) { - assert(inputnum >= 0 && inputnum < m_input.size()); - return (m_input[inputnum].m_source != nullptr) ? (m_input[inputnum].m_source - &m_input[inputnum].m_source->m_stream->m_output[0]) : -1; + // compute the time of the first sample to be backfilled; start one period before + attotime time = end_time() - sample_period(); + + // also adjust the buffered sample end time to point to the sample time of the + // final sample captured + prevend -= prevperiod; + + // 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++; + } + + // stop when we run out of source + if (srcindex >= samples) + break; + + // write this sample at the pevious position + end = prev_index(end); + put(end, src[srcindex]); + + // back up to the next sample time + time -= sample_period(); + } } + +//************************************************************************** +// SOUND STREAM OUTPUT +//************************************************************************** + //------------------------------------------------- -// set_input - configure a stream's input +// sound_stream_output - constructor //------------------------------------------------- -void sound_stream::set_input(int index, sound_stream *input_stream, int output_index, float gain) +sound_stream_output::sound_stream_output() : + m_stream(nullptr), + m_index(0), + m_gain(1.0) { - VPRINTF(("stream_set_input(%p, '%s', %d, %p, %d, %f)\n", (void *)this, m_device.tag(), - index, (void *)input_stream, output_index, (double) gain)); +} - // 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())); - // 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())); +//------------------------------------------------- +// init - initialization +//------------------------------------------------- - // if this input is already wired, update the dependent info - stream_input &input = m_input[index]; - if (input.m_source != nullptr) - input.m_source->m_dependents--; +void sound_stream_output::init(sound_stream &stream, u32 index, char const *tag) +{ + // set the passed-in data + m_stream = &stream; + m_index = index; + + // save our state + auto &save = stream.device().machine().save(); + save.save_item(&stream.device(), "stream.output", tag, index, NAME(m_gain)); + +#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 +} - // wire it up - input.m_source = (input_stream != nullptr) ? &input_stream->m_output[output_index] : nullptr; - input.m_gain = int(0x100 * gain); - input.m_user_gain = 0x100; - // update the dependent info - if (input.m_source != nullptr) - input.m_source->m_dependents++; +//------------------------------------------------- +// name - return the friendly name of this output +//------------------------------------------------- - // update sample rates now that we know the input - recompute_sample_rate_data(); +std::string sound_stream_output::name() const +{ + // 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(); } //------------------------------------------------- -// update - force a stream to update to -// the current emulated time +// optimize_resampler - optimize resamplers by +// either returning the native rate or another +// input's resampler if they can be reused //------------------------------------------------- -void sound_stream::update() +sound_stream_output &sound_stream_output::optimize_resampler(sound_stream_output *input_resampler) { - if (!m_attoseconds_per_sample) - return; + // 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; + + // 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; + + // add the input to our list and return the one we were given back + m_resampler_list.push_back(input_resampler); + return *input_resampler; +} - // determine the number of samples since the start of this second - attotime time = m_device.machine().time(); - s32 update_sampindex = s32(time.attoseconds() / m_attoseconds_per_sample); - // if we're ahead of the last update, then adjust upwards - attotime last_update = m_device.machine().sound().last_update(); - if (time.seconds() > last_update.seconds()) - { - assert(time.seconds() == last_update.seconds() + 1); - update_sampindex += m_sample_rate; - } - - // if we're behind the last update, then adjust downwards - if (time.seconds() < last_update.seconds()) - { - assert(time.seconds() == last_update.seconds() - 1); - update_sampindex -= m_sample_rate; - } - if (update_sampindex <= m_output_sampindex) - return; +//************************************************************************** +// SOUND STREAM INPUT +//************************************************************************** - // generate samples to get us up to the appropriate time - g_profiler.start(PROFILER_SOUND); - assert(m_output_sampindex - m_output_base_sampindex >= 0); - assert(update_sampindex - m_output_base_sampindex <= m_output_bufalloc); - generate_samples(update_sampindex - m_output_sampindex); - g_profiler.stop(); +//------------------------------------------------- +// sound_stream_input - constructor +//------------------------------------------------- - // remember this info for next time - m_output_sampindex = update_sampindex; +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::sync_update(void *, s32) +//------------------------------------------------- +// init - initialization +//------------------------------------------------- + +void sound_stream_input::init(sound_stream &stream, u32 index, char const *tag, sound_stream_output *resampler) { - update(); - attotime time = m_device.machine().time(); - attoseconds_t next_edge = m_attoseconds_per_sample - (time.attoseconds() % m_attoseconds_per_sample); - m_sync_timer->adjust(attotime(0, next_edge)); + // 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)); } //------------------------------------------------- -// output_since_last_update - return a pointer to -// the output buffer and the number of samples -// since the last global update +// name - return the friendly name of this input //------------------------------------------------- -const stream_sample_t *sound_stream::output_since_last_update(int outputnum, int &numsamples) +std::string sound_stream_input::name() const { - // force an update on the stream - update(); + // start with our owning stream's name + std::ostringstream str; + util::stream_format(str, "%s", m_owner->name()); - // compute the number of samples and a pointer to the output buffer - numsamples = m_output_sampindex - m_output_update_sampindex; - return &m_output[outputnum].m_buffer[m_output_update_sampindex - m_output_base_sampindex]; + // 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(); } //------------------------------------------------- -// set_sample_rate - set the sample rate on a -// given stream +// set_source - wire up the output source for +// our consumption //------------------------------------------------- -void sound_stream::set_sample_rate(int new_rate) +void sound_stream_input::set_source(sound_stream_output *source) { - // we will update this on the next global update - if (new_rate != sample_rate()) - m_new_sample_rate = new_rate; + m_native_source = source; + if (m_resampler_source != nullptr) + m_resampler_source->stream().set_input(0, &source->stream(), source->index()); } //------------------------------------------------- -// set_user_gain - set the user-controllable gain -// on a given stream's input +// update - update our source's stream to the +// current end time and return a view to its +// contents //------------------------------------------------- -void sound_stream::set_user_gain(int inputnum, float gain) +read_stream_view sound_stream_input::update(attotime start, attotime end) { - update(); - assert(inputnum >= 0 && inputnum < m_input.size()); - m_input[inputnum].m_user_gain = int(0x100 * gain); + // shouldn't get here unless valid + sound_assert(valid()); + + // pick an optimized resampler + sound_stream_output &source = m_native_source->optimize_resampler(m_resampler_source); + + // 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); + + // 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 * m_native_source->gain()); } //------------------------------------------------- -// set_input_gain - set the input gain on a -// given stream's input +// apply_sample_rate_changes - tell our sources +// to apply any sample rate changes, informing +// them of our current rate //------------------------------------------------- -void sound_stream::set_input_gain(int inputnum, float gain) +void sound_stream_input::apply_sample_rate_changes(u32 updatenum, u32 downstream_rate) { - update(); - assert(inputnum >= 0 && inputnum < m_input.size()); - m_input[inputnum].m_gain = int(0x100 * gain); + // shouldn't get here unless valid + sound_assert(valid()); + + // 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); + + // otherwise, just tell the native source directly + else + m_native_source->stream().apply_sample_rate_changes(updatenum, downstream_rate); } + +//************************************************************************** +// SOUND STREAM +//************************************************************************** + //------------------------------------------------- -// set_output_gain - set the output gain on a -// given stream's output +// sound_stream - private common constructor +//------------------------------------------------- + +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_input(inputs), + m_input_array(inputs), + m_input_view(inputs), + m_empty_buffer(100), + m_output_base(output_base), + m_output(outputs), + m_output_array(outputs), + m_output_view(outputs) +{ + sound_assert(outputs > 0); + + // create a name + m_name = m_device.name(); + m_name += " '"; + m_name += m_device.tag(); + m_name += "'"; + + // 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.register_postload(save_prepost_delegate(FUNC(sound_stream::postload), this)); + + // 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]; + } + + // add the new input + m_input[inputnum].init(*this, inputnum, state_tag.c_str(), resampler); + } + + // initialize all outputs + for (unsigned int outputnum = 0; outputnum < m_output.size(); outputnum++) + m_output[outputnum].init(*this, outputnum, state_tag.c_str()); + + // create an update timer for synchronous streams + if (synchronous()) + m_sync_timer = m_device.machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(sound_stream::sync_update), this)); + + // force an update to the sample rates + sample_rate_changed(); +} + + +//------------------------------------------------- +// sound_stream - constructor with old-style +// callback //------------------------------------------------- -void sound_stream::set_output_gain(int outputnum, float gain) +sound_stream::sound_stream(device_t &device, u32 inputs, u32 outputs, u32 output_base, u32 sample_rate, stream_update_legacy_delegate callback, sound_stream_flags flags) : + sound_stream(device, inputs, outputs, output_base, sample_rate, flags) { - update(); - assert(outputnum >= 0 && outputnum < m_output.size()); - m_output[outputnum].m_gain = int(0x100 * gain); + m_callback = std::move(callback); + m_callback_ex = stream_update_delegate(&sound_stream::stream_update_legacy, this); } //------------------------------------------------- -// update_with_accounting - do a regular update, -// but also do periodic accounting +// sound_stream - constructor with new-style +// callback //------------------------------------------------- -void sound_stream::update_with_accounting(bool second_tick) +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) { - // do the normal update - update(); + m_callback_ex = std::move(callback); +} - // if we've ticked over another second, adjust all the counters that are relative to - // the current second - s32 output_bufindex = m_output_sampindex - m_output_base_sampindex; - if (second_tick) - { - m_output_sampindex -= m_sample_rate; - m_output_base_sampindex -= m_sample_rate; - } - // note our current output sample - m_output_update_sampindex = m_output_sampindex; +//------------------------------------------------- +// ~sound_stream - destructor +//------------------------------------------------- + +sound_stream::~sound_stream() +{ +} - // if we don't have enough output buffer space to hold two updates' worth of samples, - // we need to shuffle things down - if (m_output_bufalloc - output_bufindex < 2 * m_max_samples_per_update) - { - s32 samples_to_lose = output_bufindex - m_max_samples_per_update; - if (samples_to_lose > 0) - { - // if we have samples to move, do so for each output - if (output_bufindex > 0) - for (auto &output : m_output) - { - memmove(&output.m_buffer[0], &output.m_buffer[samples_to_lose], sizeof(output.m_buffer[0]) * (output_bufindex - samples_to_lose)); - } - // update the base position - m_output_base_sampindex += samples_to_lose; - } - } +//------------------------------------------------- +// set_sample_rate - set the sample rate on a +// given stream +//------------------------------------------------- + +void sound_stream::set_sample_rate(u32 new_rate) +{ + // we will update this on the next global update + if (new_rate != sample_rate()) + m_pending_sample_rate = new_rate; } //------------------------------------------------- -// apply_sample_rate_changes - if there is a -// pending sample rate change, apply it now +// set_input - configure a stream's input //------------------------------------------------- -void sound_stream::apply_sample_rate_changes() +void sound_stream::set_input(int index, sound_stream *input_stream, int output_index, float gain) { - // skip if nothing to do - if (m_new_sample_rate == 0xffffffff) - return; + VPRINTF(("stream_set_input(%p, '%s', %d, %p, %d, %f)\n", (void *)this, m_device.tag(), + index, (void *)input_stream, output_index, (double) gain)); - // update to the new rate and remember the old rate - u32 old_rate = m_sample_rate; - m_sample_rate = m_new_sample_rate; - m_new_sample_rate = 0xffffffff; + // 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())); - // recompute all the data - recompute_sample_rate_data(); + // 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())); - // reset our sample indexes to the current time - if (old_rate) - { - m_output_sampindex = s64(m_output_sampindex) * s64(m_sample_rate) / old_rate; - m_output_update_sampindex = s64(m_output_update_sampindex) * s64(m_sample_rate) / old_rate; - } - else - { - m_output_sampindex = m_attoseconds_per_sample ? m_device.machine().sound().last_update().attoseconds() / m_attoseconds_per_sample : 0; - m_output_update_sampindex = m_output_sampindex; - } + // wire it up + m_input[index].set_source((input_stream != nullptr) ? &input_stream->m_output[output_index] : nullptr); + m_input[index].set_gain(gain); + + // update sample rates now that we know the input + sample_rate_changed(); +} - m_output_base_sampindex = m_output_sampindex - m_max_samples_per_update; - // clear out the buffer - if (m_max_samples_per_update) - for (auto &elem : m_output) - std::fill_n(&elem.m_buffer[0], m_max_samples_per_update, 0); +//------------------------------------------------- +// update - force a stream to update to +// the current emulated time +//------------------------------------------------- + +void sound_stream::update() +{ + // 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; + + // regular update then + update_view(start, end); } //------------------------------------------------- -// recompute_sample_rate_data - recompute sample -// rate data, and all streams that are affected -// by this stream +// 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 //------------------------------------------------- -void sound_stream::recompute_sample_rate_data() +read_stream_view sound_stream::update_view(attotime start, attotime end, u32 outputnum) { - if (m_synchronous) + sound_assert(start <= end); + sound_assert(outputnum < m_output.size()); + + // clean up parameters for when the asserts go away + if (outputnum >= m_output.size()) + outputnum = 0; + if (start > end) + start = end; + + g_profiler.start(PROFILER_SOUND); + + // reposition our start to coincide with the current buffer end + attotime update_start = m_output[outputnum].end_time(); + if (update_start <= end) { - m_sample_rate = 0; - // When synchronous, pick the sample rate for the inputs, if any - for (auto &input : m_input) + // 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) { - if (input.m_source != nullptr) + 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_sample_rate) - m_sample_rate = input.m_source->m_stream->m_sample_rate; - else if (m_sample_rate != input.m_source->m_stream->m_sample_rate) - throw emu_fatalerror("Incompatible sample rates as input of a synchronous stream: %d and %d\n", m_sample_rate, input.m_source->m_stream->m_sample_rate); + 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_resampling_disabled || m_input_view[inputnum].sample_rate() == m_sample_rate); } + +#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 we have an extended callback, that's all we need + m_callback_ex(*this, m_input_view, m_output_view); + +#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); + + for (unsigned int outindex = 0; outindex < m_output.size(); outindex++) + m_output[outindex].m_buffer.flush_wav(); +#endif } } + g_profiler.stop(); + + // return the requested view + return read_stream_view(m_output_view[outputnum], start); +} - // recompute the timing parameters - attoseconds_t update_attoseconds = m_device.machine().sound().update_attoseconds(); +//------------------------------------------------- +// apply_sample_rate_changes - if there is a +// pending sample rate change, apply it now +//------------------------------------------------- - if (m_sample_rate) +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; + + // 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(); + + // if we're output adaptive, override with the rate of our output + if (output_adaptive()) { - m_attoseconds_per_sample = ATTOSECONDS_PER_SECOND / m_sample_rate; - m_max_samples_per_update = (update_attoseconds + m_attoseconds_per_sample - 1) / m_attoseconds_per_sample; + 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; } - else + + // if something is different, process the change + if (new_rate != SAMPLE_RATE_INVALID && new_rate != m_sample_rate) { - m_attoseconds_per_sample = 0; - m_max_samples_per_update = 0; + // 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(); } - // update resample and output buffer sizes - allocate_resample_buffers(); - allocate_output_buffers(); - - // iterate over each input + // now call through our inputs and apply the rate change there for (auto &input : m_input) - { - // if we have a source, see if its sample rate changed + if (input.valid()) + input.apply_sample_rate_changes(updatenum, m_sample_rate); +} - if (input.m_source != nullptr && input.m_source->m_stream->m_sample_rate) - { - // okay, we have a new sample rate; recompute the latency to be the maximum - // sample period between us and our input - attoseconds_t new_attosecs_per_sample = ATTOSECONDS_PER_SECOND / input.m_source->m_stream->m_sample_rate; - attoseconds_t latency = std::max(new_attosecs_per_sample, m_attoseconds_per_sample); - - // if the input stream's sample rate is lower, we will use linear interpolation - // this requires an extra sample from the source - if (input.m_source->m_stream->m_sample_rate < m_sample_rate) - latency += new_attosecs_per_sample; - - // if our sample rates match exactly, we don't need any latency - else if (input.m_source->m_stream->m_sample_rate == m_sample_rate) - latency = 0; - - // we generally don't want to tweak the latency, so we just keep the greatest - // one we've computed thus far - input.m_latency_attoseconds = std::max(input.m_latency_attoseconds, latency); - assert(input.m_latency_attoseconds < update_attoseconds); - } - else - { - input.m_latency_attoseconds = 0; - } - } - // If synchronous, prime the timer - if (m_synchronous) - { - attotime time = m_device.machine().time(); - if (m_attoseconds_per_sample) +//------------------------------------------------- +// print_graph_recursive - helper for debugging; +// prints info on this stream and then recursively +// prints info on all inputs +//------------------------------------------------- + +#if (SOUND_DEBUG) +void sound_stream::print_graph_recursive(int indent, int index) +{ + osd_printf_info("%c %*s%s Ch.%d @ %d\n", m_callback.isnull() ? ' ' : '!', indent, "", name().c_str(), index + m_output_base, sample_rate()); + for (int index = 0; index < m_input.size(); index++) + if (m_input[index].valid()) { - attoseconds_t next_edge = m_attoseconds_per_sample - (time.attoseconds() % m_attoseconds_per_sample); - m_sync_timer->adjust(attotime(0, next_edge)); + 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()); } - else - m_sync_timer->adjust(attotime::never); - } } +#endif //------------------------------------------------- -// allocate_resample_buffers - recompute the -// resample buffer sizes and expand if necessary +// sample_rate_changed - recompute sample +// rate data, and all streams that are affected +// by this stream //------------------------------------------------- -void sound_stream::allocate_resample_buffers() +void sound_stream::sample_rate_changed() { - // compute the target number of samples - s32 bufsize = 2 * m_max_samples_per_update; + // if invalid, just punt + if (m_sample_rate == SAMPLE_RATE_INVALID) + return; - // if we don't have enough room, allocate more - if (m_resample_bufalloc < bufsize) - { - // this becomes the new allocation size - m_resample_bufalloc = bufsize; + // update all output buffers + for (auto &output : m_output) + output.sample_rate_changed(m_sample_rate); - // iterate over outputs and realloc their buffers - for (auto &elem : m_input) - elem.m_resample.resize(m_resample_bufalloc, 0); - } + // if synchronous, prime the timer + if (synchronous()) + reprime_sync_timer(); } //------------------------------------------------- -// allocate_output_buffers - recompute the -// output buffer sizes and expand if necessary +// postload - save/restore callback //------------------------------------------------- -void sound_stream::allocate_output_buffers() +void sound_stream::postload() { - // if we don't have enough room, allocate more - s32 bufsize = OUTPUT_BUFFER_UPDATES * m_max_samples_per_update; - if (m_output_bufalloc < bufsize) - { - // this becomes the new allocation size - m_output_bufalloc = bufsize; + // set the end time of all of our streams to now + for (auto &output : m_output) + output.set_end_time(m_device.machine().time()); - // iterate over outputs and realloc their buffers - for (auto &elem : m_output) - elem.m_buffer.resize(m_output_bufalloc, 0); - } + // recompute the sample rate information + sample_rate_changed(); } //------------------------------------------------- -// postload - save/restore callback +// reprime_sync_timer - set up the next sync +// timer to go off just a hair after the end of +// the current sample period //------------------------------------------------- -void sound_stream::postload() +void sound_stream::reprime_sync_timer() { - // recompute the same rate information - recompute_sample_rate_data(); + attotime curtime = m_device.machine().time(); + attotime target = m_output[0].end_time() + attotime(0, 1); + m_sync_timer->adjust(target - curtime); +} - // make sure our output buffers are fully cleared - for (auto &elem : m_output) - std::fill_n(&elem.m_buffer[0], m_output_bufalloc, 0); - // recompute the sample indexes to make sense - m_output_sampindex = m_attoseconds_per_sample ? m_device.machine().sound().last_update().attoseconds() / m_attoseconds_per_sample : 0; - m_output_update_sampindex = m_output_sampindex; - m_output_base_sampindex = m_output_sampindex - m_max_samples_per_update; +//------------------------------------------------- +// sync_update - timer callback to handle a +// synchronous stream +//------------------------------------------------- + +void sound_stream::sync_update(void *, s32) +{ + update(); + reprime_sync_timer(); } //------------------------------------------------- -// generate_samples - generate the requested -// number of samples for a stream, making sure -// all inputs have the appropriate number of -// samples generated +// stream_update_legacy - new-style callback which +// forwards on to the old-style traditional +// callback, converting to/from floats //------------------------------------------------- -void sound_stream::generate_samples(int samples) +void sound_stream::stream_update_legacy(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) { - stream_sample_t **inputs = nullptr; - stream_sample_t **outputs = nullptr; - - VPRINTF(("generate_samples(%p, %d)\n", (void *) this, samples)); - assert(samples > 0); + // temporary buffer to hold stream_sample_t inputs and outputs + stream_sample_t temp_buffer[1024]; + int chunksize = ARRAY_LENGTH(temp_buffer) / (inputs.size() + outputs.size()); + int chunknum = 0; + + // create the arrays to pass to the callback + stream_sample_t **inputptr = m_input.empty() ? nullptr : &m_input_array[0]; + stream_sample_t **outputptr = &m_output_array[0]; + for (unsigned int inputnum = 0; inputnum < inputs.size(); inputnum++) + inputptr[inputnum] = &temp_buffer[chunksize * chunknum++]; + for (unsigned int outputnum = 0; outputnum < m_output.size(); outputnum++) + outputptr[outputnum] = &temp_buffer[chunksize * chunknum++]; - // ensure all inputs are up to date and generate resampled data - for (unsigned int inputnum = 0; inputnum < m_input.size(); inputnum++) + // loop until all chunks done + for (int baseindex = 0; baseindex < outputs[0].samples(); baseindex += chunksize) { - // update the stream to the current time - stream_input &input = m_input[inputnum]; - if (input.m_source != nullptr) - input.m_source->m_stream->update(); + // determine the number of samples to process this time + int cursamples = outputs[0].samples() - baseindex; + if (cursamples > chunksize) + cursamples = chunksize; - // generate the resampled data - m_input_array[inputnum] = generate_resampled_data(input, samples); - } + // copy in the input data + for (unsigned int inputnum = 0; inputnum < inputs.size(); inputnum++) + { + stream_sample_t *dest = inputptr[inputnum]; + for (int index = 0; index < cursamples; index++) + dest[index] = stream_sample_t(inputs[inputnum].get(baseindex + index) * stream_buffer::sample_t(32768.0)); + } - if (!m_input.empty()) - { - inputs = &m_input_array[0]; - } + // run the callback + m_callback(*this, inputptr, outputptr, cursamples); - // loop over all outputs and compute the output pointer - for (unsigned int outputnum = 0; outputnum < m_output.size(); outputnum++) - { - stream_output &output = m_output[outputnum]; - m_output_array[outputnum] = &output.m_buffer[m_output_sampindex - m_output_base_sampindex]; + // copy out the output data + for (unsigned int outputnum = 0; outputnum < m_output.size(); outputnum++) + { + stream_sample_t *src = outputptr[outputnum]; + for (int index = 0; index < cursamples; index++) + outputs[outputnum].put(baseindex + index, stream_buffer::sample_t(src[index]) * stream_buffer::sample_t(1.0 / 32768.0)); + } } +} - if (!m_output.empty()) - { - outputs = &m_output_array[0]; - } - // run the callback - VPRINTF((" callback(%p, %d)\n", (void *)this, samples)); - m_callback(*this, inputs, outputs, samples); - VPRINTF((" callback done\n")); +//------------------------------------------------- +// empty_view - return an empty view covering the +// given time period as a substitute for invalid +// inputs +//------------------------------------------------- + +read_stream_view sound_stream::empty_view(attotime start, attotime end) +{ + // 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); } + +//************************************************************************** +// RESAMPLER STREAM +//************************************************************************** + //------------------------------------------------- -// generate_resampled_data - generate the -// resample buffer for a given input +// default_resampler_stream - derived sound_stream +// class that handles resampling //------------------------------------------------- -stream_sample_t *sound_stream::generate_resampled_data(stream_input &input, u32 numsamples) +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) { - // if we don't have an output to pull data from, generate silence - stream_sample_t *dest = &input.m_resample[0]; - if (input.m_source == nullptr || input.m_source->m_stream->m_attoseconds_per_sample == 0) - { - std::fill_n(dest, numsamples, 0); - return &input.m_resample[0]; - } + // create a name + m_name = "Default Resampler '"; + m_name += device.tag(); + m_name += "'"; +} - // grab data from the output - stream_output &output = *input.m_source; - sound_stream &input_stream = *output.m_stream; - s64 gain = (input.m_gain * input.m_user_gain * output.m_gain) >> 16; - // determine the time at which the current sample begins, accounting for the - // latency we calculated between the input and output streams - attoseconds_t basetime = m_output_sampindex * m_attoseconds_per_sample - input.m_latency_attoseconds; +//------------------------------------------------- +// resampler_sound_update - stream callback +// handler for resampling an input stream to the +// target sample rate of the output +//------------------------------------------------- - // now convert that time into a sample in the input stream - s32 basesample; - if (basetime >= 0) - basesample = basetime / input_stream.m_attoseconds_per_sample; - else - basesample = -(-basetime / input_stream.m_attoseconds_per_sample) - 1; +void default_resampler_stream::resampler_sound_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) +{ + sound_assert(inputs.size() == 1); + sound_assert(outputs.size() == 1); + + auto &input = inputs[0]; + auto &output = outputs[0]; - // compute a source pointer to the first sample - assert(basesample >= input_stream.m_output_base_sampindex); - stream_sample_t *source = &output.m_buffer[basesample - input_stream.m_output_base_sampindex]; + // if the input has an invalid rate, just fill with zeros + if (input.sample_rate() <= 1) + { + output.fill(0); + return; + } - // determine the current fraction of a sample, expressed as a fraction of FRAC_ONE - // (Note: this formula is valid as long as input_stream.m_attoseconds_per_sample significantly exceeds FRAC_ONE > attoseconds = 4.2E-12 s) - u32 basefrac = (basetime - basesample * input_stream.m_attoseconds_per_sample) / ((input_stream.m_attoseconds_per_sample + FRAC_ONE - 1) >> FRAC_BITS); - assert(basefrac < FRAC_ONE); + // optimize_resampler ensures we should not have equal sample rates + sound_assert(input.sample_rate() != output.sample_rate()); - // compute the stepping fraction - u32 step = (u64(input_stream.m_sample_rate) << FRAC_BITS) / m_sample_rate; + // 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; - // if we have equal sample rates, we just need to copy - if (step == FRAC_ONE) + // 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) { - while (numsamples--) - { - // compute the sample - s64 sample = *source++; - *dest++ = (sample * gain) >> 8; - } + output.put(dstindex++, 0); + output_start += output.sample_period(); } + if (dstindex >= numsamples) + return; + + // 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); + + // 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); // input is undersampled: point sample except where our sample period covers a boundary - else if (step < FRAC_ONE) + s32 srcindex = 0; + if (step < 1.0) { - while (numsamples != 0) + stream_buffer::sample_t cursample = rebased.get(srcindex++); + for ( ; dstindex < numsamples; dstindex++) { - // fill in with point samples until we hit a boundary - int nextfrac; - while ((nextfrac = basefrac + step) < FRAC_ONE && numsamples--) + // if still within the current sample, just replicate + if (srcpos <= 1.0) + output.put(dstindex, cursample); + + // if crossing a sample boundary, blend with the neighbor + else { - *dest++ = (source[0] * gain) >> 8; - basefrac = nextfrac; + 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)); } - - // if we're done, we're done - if (s32(numsamples--) < 0) - break; - - // compute starting and ending fractional positions - int startfrac = basefrac >> (FRAC_BITS - 12); - int endfrac = nextfrac >> (FRAC_BITS - 12); - - // blend between the two samples accordingly - s64 sample = (s64(source[0]) * (0x1000 - startfrac) + s64(source[1]) * (endfrac - 0x1000)) / (endfrac - startfrac); - *dest++ = (sample * gain) >> 8; - - // advance - basefrac = nextfrac & FRAC_MASK; - source++; + srcpos += step; } + sound_assert(srcindex <= rebased.samples()); } // input is oversampled: sum the energy else { - // use 8 bits to allow some extra headroom - int smallstep = step >> (FRAC_BITS - 8); - while (numsamples--) + float cursample = rebased.get(srcindex++); + for ( ; dstindex < numsamples; dstindex++) { - s64 remainder = smallstep; - int tpos = 0; - - // compute the sample - s64 scale = (FRAC_ONE - basefrac) >> (FRAC_BITS - 8); - s64 sample = s64(source[tpos++]) * scale; - remainder -= scale; - while (remainder > 0x100) + // 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 += s64(source[tpos++]) * s64(0x100); - remainder -= 0x100; + sample += rebased.get(srcindex++); + remaining -= 1.0; } - sample += s64(source[tpos]) * remainder; - sample /= smallstep; - *dest++ = (sample * gain) >> 8; + // add in the final partial sample + cursample = rebased.get(srcindex++); + sample += cursample * remaining; + output.put(dstindex, sample * stepinv); - // advance - basefrac += step; - source += basefrac >> FRAC_BITS; - basefrac &= FRAC_MASK; + // our position is now the remainder + srcpos = remaining; + sound_assert(srcindex <= rebased.samples()); } } - - return &input.m_resample[0]; -} - - - -//************************************************************************** -// STREAM INPUT -//************************************************************************** - -//------------------------------------------------- -// stream_input - constructor -//------------------------------------------------- - -sound_stream::stream_input::stream_input() - : m_source(nullptr), - m_latency_attoseconds(0), - m_gain(0x100), - m_user_gain(0x100) -{ -} - - - -//************************************************************************** -// STREAM OUTPUT -//************************************************************************** - -//------------------------------------------------- -// stream_output - constructor -//------------------------------------------------- - -sound_stream::stream_output::stream_output() - : m_stream(nullptr), - m_dependents(0), - m_gain(0x100) -{ } @@ -831,20 +1131,24 @@ sound_stream::stream_output::stream_output() // sound_manager - constructor //------------------------------------------------- -sound_manager::sound_manager(running_machine &machine) - : m_machine(machine), - m_update_timer(nullptr), - m_finalmix_leftover(0), - m_finalmix(machine.sample_rate()), - m_leftmix(machine.sample_rate()), - m_rightmix(machine.sample_rate()), - m_samples_this_update(0), - m_muted(0), - m_attenuation(0), - m_nosound_mode(machine.osd().no_sound()), - m_wavfile(nullptr), - m_update_attoseconds(STREAMS_UPDATE_ATTOTIME.attoseconds()), - m_last_update(attotime::zero) +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_muted(0), + m_nosound_mode(machine.osd().no_sound()), + m_attenuation(0), + m_unique_id(0), + m_wavfile(nullptr), + m_first_reset(true) { // get filename for WAV file or AVI file if specified const char *wavfile = machine.options().wav_write(); @@ -889,6 +1193,41 @@ sound_manager::~sound_manager() //------------------------------------------------- +// stream_alloc_legacy - allocate a new stream +//------------------------------------------------- + +sound_stream *sound_manager::stream_alloc_legacy(device_t &device, u32 inputs, u32 outputs, u32 sample_rate, stream_update_legacy_delegate callback) +{ + // determine output base + u32 output_base = 0; + for (auto &stream : m_stream_list) + if (&stream->device() == &device) + output_base += stream->output_count(); + + m_stream_list.push_back(std::make_unique<sound_stream>(device, inputs, outputs, output_base, sample_rate, callback)); + return m_stream_list.back().get(); +} + + +//------------------------------------------------- +// 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) +{ + // determine output base + u32 output_base = 0; + for (auto &stream : m_stream_list) + if (&stream->device() == &device) + output_base += stream->output_count(); + + 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(); +} + + +//------------------------------------------------- // start_recording - begin audio recording //------------------------------------------------- @@ -915,23 +1254,13 @@ void sound_manager::stop_recording() //------------------------------------------------- -// stream_alloc - allocate a new stream -//------------------------------------------------- - -sound_stream *sound_manager::stream_alloc(device_t &device, int inputs, int outputs, int sample_rate, stream_update_delegate callback) -{ - m_stream_list.push_back(std::make_unique<sound_stream>(device, inputs, outputs, sample_rate, callback)); - return m_stream_list.back().get(); -} - - -//------------------------------------------------- // set_attenuation - set the global volume //------------------------------------------------- -void sound_manager::set_attenuation(int attenuation) +void sound_manager::set_attenuation(float attenuation) { - m_attenuation = attenuation; + // currently OSD only supports integral attenuation + m_attenuation = int(attenuation); machine().osd().set_mastervolume(m_muted ? -32 : m_attenuation); } @@ -951,7 +1280,7 @@ bool sound_manager::indexed_mixer_input(int index, mixer_input &info) const { info.mixer = &mixer; info.stream = mixer.input_to_stream_input(index, info.inputnum); - assert(info.stream != nullptr); + sound_assert(info.stream != nullptr); return true; } index -= mixer.inputs(); @@ -964,6 +1293,19 @@ bool sound_manager::indexed_mixer_input(int index, mixer_input &info) const //------------------------------------------------- +// samples - fills the specified buffer with +// 16-bit stereo audio samples generated during +// the current frame +//------------------------------------------------- + +void sound_manager::samples(s16 *buffer) +{ + for (int sample = 0; sample < m_samples_this_update * 2; sample++) + *buffer++ = m_finalmix[sample]; +} + + +//------------------------------------------------- // mute - mute sound output //------------------------------------------------- @@ -978,6 +1320,47 @@ void sound_manager::mute(bool mute, u8 reason) //------------------------------------------------- +// recursive_remove_stream_from_orphan_list - +// remove the given stream from the orphan list +// and recursively remove all our inputs +//------------------------------------------------- + +void sound_manager::recursive_remove_stream_from_orphan_list(sound_stream *which) +{ + 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()); + } +} + + +//------------------------------------------------- +// apply_sample_rate_changes - recursively +// update sample rates throughout the system +//------------------------------------------------- + +void sound_manager::apply_sample_rate_changes() +{ + // update sample rates if they have changed + for (speaker_device &speaker : speaker_device_iterator(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()); + } + } +} + + +//------------------------------------------------- // reset - reset all sound chips //------------------------------------------------- @@ -986,6 +1369,47 @@ void sound_manager::reset() // reset all the sound chips for (device_sound_interface &sound : sound_interface_iterator(machine().root_device())) sound.device().reset(); + + // apply any sample rate changes now + apply_sample_rate_changes(); + + // on first reset, identify any orphaned streams + if (m_first_reset) + { + m_first_reset = false; + + // put all the streams on the orphan list to start + for (auto &stream : m_stream_list) + m_orphan_stream_list[stream.get()] = 0; + + // then walk the graph like we do on update and remove any we touch + for (speaker_device &speaker : speaker_device_iterator(machine().root_device())) + { + int dummy; + sound_stream *output = speaker.output_to_stream_output(0, dummy); + if (output != nullptr) + recursive_remove_stream_from_orphan_list(output); + } + +#if (SOUND_DEBUG) + // dump the sound graph when we start up + for (speaker_device &speaker : speaker_device_iterator(machine().root_device())) + { + int index; + sound_stream *output = speaker.output_to_stream_output(0, index); + if (output != nullptr) + output->print_graph_recursive(0, index); + } + + // 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 + } } @@ -1033,7 +1457,7 @@ void sound_manager::config_load(config_type cfg_type, util::xml::data_node const float defvol = channelnode->get_attribute_float("defvol", 1.0f); float newvol = channelnode->get_attribute_float("newvol", -1000.0f); if (newvol != -1000.0f) - info.stream->set_user_gain(info.inputnum, newvol / defvol); + info.stream->input(info.inputnum).set_user_gain(newvol / defvol); } } } @@ -1057,7 +1481,7 @@ void sound_manager::config_save(config_type cfg_type, util::xml::data_node *pare mixer_input info; if (!indexed_mixer_input(mixernum, info)) break; - float newvol = info.stream->user_gain(info.inputnum); + float newvol = info.stream->input(info.inputnum).user_gain(); if (newvol != 1.0f) { @@ -1073,6 +1497,45 @@ void sound_manager::config_save(config_type cfg_type, util::xml::data_node *pare //------------------------------------------------- +// adjust_toward_compressor_scale - adjust the +// current scale factor toward the current goal, +// in small increments +//------------------------------------------------- + +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) +{ + stream_buffer::sample_t proposed_scale = curscale; + + // 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; + } + + // otherwise, decrement by 0.01 + else + { + proposed_scale -= 0.01f; + if (proposed_scale < m_compressor_scale) + proposed_scale = m_compressor_scale; + } + + // 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; + + // 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 //------------------------------------------------- @@ -1083,10 +1546,72 @@ void sound_manager::update(void *ptr, int param) 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); + + // 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; + + // recompute the end time to an even sample boundary + attotime endtime = m_last_update + attotime(0, m_samples_this_update * sample_rate_attos); + + // 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); + // force all the speaker streams to generate the proper number of samples - m_samples_this_update = 0; for (speaker_device &speaker : speaker_device_iterator(machine().root_device())) - speaker.mix(&m_leftmix[0], &m_rightmix[0], m_samples_this_update, (m_muted & MUTE_REASON_SYSTEM)); + speaker.mix(&m_leftmix[0], &m_rightmix[0], m_last_update, endtime, m_samples_this_update, (m_muted & MUTE_REASON_SYSTEM)); + + // determine the maximum in this section + stream_buffer::sample_t curmax = 0; + for (int sampindex = 0; sampindex < m_samples_this_update; sampindex++) + { + 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; + } + + // pull in current compressor scale factor before modifying + stream_buffer::sample_t lscale = m_compressor_scale; + stream_buffer::sample_t rscale = m_compressor_scale; + + // 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; + } + + // 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--; + + // 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; + } + +#if (SOUND_DEBUG) + if (lscale != m_compressor_scale) + printf("scale=%.5f\n", m_compressor_scale); +#endif + + // track whether there are pending scale changes in left/right + stream_buffer::sample_t lprev = 0, rprev = 0; // now downmix the final result u32 finalmix_step = machine().video().speed_factor(); @@ -1097,21 +1622,31 @@ void sound_manager::update(void *ptr, int param) { 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); + // clamp the left side - s32 samp = m_leftmix[sampindex]; - if (samp < -32768) - samp = -32768; - else if (samp > 32767) - samp = 32767; - finalmix[finalmix_offset++] = samp; - - // clamp the right side - samp = m_rightmix[sampindex]; - if (samp < -32768) - samp = -32768; - else if (samp > 32767) - samp = 32767; - finalmix[finalmix_offset++] = samp; + lprev = lsamp *= lscale; + 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); + + // clamp the left side + rprev = rsamp *= rscale; + if (rsamp > 1.0) + rsamp = 1.0; + else if (rsamp < -1.0) + rsamp = -1.0; + finalmix[finalmix_offset++] = s16(rsamp * 32767.0); } m_finalmix_leftover = sample - m_samples_this_update * 1000; @@ -1126,43 +1661,19 @@ void sound_manager::update(void *ptr, int param) wav_add_data_16(m_wavfile, finalmix, finalmix_offset); } - // see if we ticked over to the next second - attotime curtime = machine().time(); - bool second_tick = false; - if (curtime.seconds() != m_last_update.seconds()) - { - assert(curtime.seconds() == m_last_update.seconds() + 1); - second_tick = true; - } - - // iterate over all the streams and update them - for (auto &stream : m_stream_list) - stream->update_with_accounting(second_tick); + // update any orphaned streams so they don't get too far behind + for (auto &stream : m_orphan_stream_list) + stream.first->update(); // remember the update time - m_last_update = curtime; + m_last_update = endtime; + m_update_number++; - // update sample rates if they have changed - for (auto &stream : m_stream_list) - stream->apply_sample_rate_changes(); + // apply sample rate changes + apply_sample_rate_changes(); // notify that new samples have been generated emulator_info::sound_hook(); g_profiler.stop(); } - - -//------------------------------------------------- -// samples - fills the specified buffer with -// 16-bit stereo audio samples generated during -// the current frame -//------------------------------------------------- - -void sound_manager::samples(s16 *buffer) -{ - for (int sample = 0; sample < m_samples_this_update * 2; sample++) - { - *buffer++ = m_finalmix[sample]; - } -} diff --git a/src/emu/sound.h b/src/emu/sound.h index 7301a4d69ba..c3027b28ba6 100644 --- a/src/emu/sound.h +++ b/src/emu/sound.h @@ -6,6 +6,50 @@ Core sound interface functions and definitions. +**************************************************************************** + + In MAME, sound is represented as a graph of sound "streams". Each + stream has a fixed number of inputs and outputs, and is responsible + for producing sound on demand. + + The graph is driven from the outputs, which are speaker devices. + These devices are updated on a regular basis (~50 times per second), + and when an update occurs, the graph is walked from the speaker + through each input, until all connected streams are up to date. + + Individual streams can also be updated manually. This is important + for sound chips and CPU-driven devices, who should force any + affected streams to update prior to making changes. + + Sound streams are *not* part of the device execution model. This is + very important to understand. If the process of producing the ouput + stream affects state that might be consumed by an executing device + (e.g., a CPU), then care must be taken to ensure that the stream is + updated frequently enough + + The model for timing sound samples is very important and explained + here. Each stream source has a clock (aka sample rate). Each clock + edge represents a sample that is held for the duration of one clock + period. This model has interesting effects: + + 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, + which is in the future, because it knows it will hold that last + sample until 1.0s. + + Sound generation callbacks are presented with a std::vector of inputs + and outputs. The vectors contain objects of read_stream_view and + write_stream_view respectively, which wrap access to a circular buffer + of samples. Sound generation callbacks are expected to fill all the + samples described by the outputs' write_stream_view objects. At the + moment, all outputs have the same sample rate, so the number of samples + that need to be generated will be consistent across all outputs. + + By default, the inputs will have been resampled to match the output + sample rate, unless otherwise specified. + ***************************************************************************/ #pragma once @@ -22,26 +66,503 @@ // CONSTANTS //************************************************************************** -constexpr int STREAM_SYNC = -1; // special rate value indicating a one-sample-at-a-time stream - // with actual rate defined by its input +// 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; + + //************************************************************************** -// MACROS +// DEBUGGING //************************************************************************** -typedef delegate<void (sound_stream &, stream_sample_t **inputs, stream_sample_t **outputs, int samples)> stream_update_delegate; +// turn this on to enable aggressive assertions and other checks +#define SOUND_DEBUG (1) + +// if SOUND_DEBUG is on, make assertions fire regardless of MAME_DEBUG +#if (SOUND_DEBUG) +#define sound_assert(x) do { if (!(x)) { osd_printf_error("sound_assert: " #x "\n"); osd_break_into_debugger("sound_assert: " #x "\n"); } } while (0) +#else +#define sound_assert assert +#endif + + //************************************************************************** // TYPE DEFINITIONS //************************************************************************** +// ======================> stream_buffer -// structure describing an indexed mixer -struct mixer_input +class stream_buffer { - device_mixer_interface *mixer; // owning device interface - sound_stream * stream; // stream within the device - int inputnum; // input on the stream + // 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(); + void close_wav(); + +private: + // internal debugging state + wav_file *m_wav_file = nullptr; // 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 gain-applied sample to the buffer + void put(s32 index, sample_t sample) + { + sound_assert(u32(index) < samples()); + index += m_start; + if (index >= m_buffer->size()) + index -= m_buffer->size(); + m_buffer->put(index, sample); + } + + // safely add a gain-applied sample to the buffer + void add(s32 index, sample_t sample) + { + sound_assert(u32(index) < samples()); + index += m_start; + if (index >= m_buffer->size()) + index -= m_buffer->size(); + m_buffer->put(index, m_buffer->get(index) + sample); + } + + // 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 = start + m_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 = start + m_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 = start + m_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()); } +}; + + +// ======================> 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_legacy_delegate/stream_update_delegate + +// old-style callback; eventually should be deprecated +using stream_update_legacy_delegate = delegate<void (sound_stream &stream, stream_sample_t const * const *inputs, stream_sample_t * const *outputs, int samples)>; + +// 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)>; + + +// ======================> sound_stream_flags + +enum sound_stream_flags : u32 +{ + // default is no special flags + STREAM_DEFAULT_FLAGS = 0x00, + + // 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 }; @@ -51,127 +572,143 @@ class sound_stream { friend class sound_manager; - typedef void (*stream_update_func)(device_t *device, sound_stream *stream, void *param, stream_sample_t **inputs, stream_sample_t **outputs, int samples); - - // stream output class - class stream_output - { - public: - // construction/destruction - stream_output(); - stream_output &operator=(const stream_output &rhs) { assert(false); return *this; } - - // internal state - sound_stream * m_stream; // owning stream - std::vector<stream_sample_t> m_buffer; // output buffer - int m_dependents; // number of dependents - s16 m_gain; // gain to apply to the output - }; - - // stream input class - class stream_input - { - public: - // construction/destruction - stream_input(); - stream_input &operator=(const stream_input &rhs) { assert(false); return *this; } - - // internal state - stream_output * m_source; // pointer to the sound_output for this source - std::vector<stream_sample_t> m_resample; // buffer for resampling to the stream's sample rate - attoseconds_t m_latency_attoseconds; // latency between this stream and the input stream - s16 m_gain; // gain to apply to this input - s16 m_user_gain; // user-controlled gain to apply to this input - }; - - // constants - static constexpr int OUTPUT_BUFFER_UPDATES = 5; - static constexpr u32 FRAC_BITS = 22; - static constexpr u32 FRAC_ONE = 1 << FRAC_BITS; - static constexpr u32 FRAC_MASK = FRAC_ONE - 1; + // 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, int inputs, int outputs, int sample_rate, stream_update_delegate callback); + sound_stream(device_t &device, u32 inputs, u32 outputs, u32 output_base, u32 sample_rate, stream_update_legacy_delegate callback, sound_stream_flags flags = STREAM_DEFAULT_FLAGS); + 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); + virtual ~sound_stream(); - // getters + // simple getters sound_stream *next() const { return m_next; } device_t &device() const { return m_device; } - int sample_rate() const { return (m_new_sample_rate != 0xffffffff) ? m_new_sample_rate : m_sample_rate; } - attotime sample_time() const; - attotime sample_period() const { return attotime(0, m_attoseconds_per_sample); } - int input_count() const { return m_input.size(); } - int output_count() const { return m_output.size(); } - std::string input_name(int inputnum) const; - device_t *input_source_device(int inputnum) const; - int input_source_outputnum(int inputnum) const; - float user_gain(int inputnum) const; - float input_gain(int inputnum) const; - float output_gain(int outputnum) const; - - // operations + std::string name() const { return m_name; } + bool input_adaptive() const { return m_input_adaptive || m_synchronous; } + bool output_adaptive() const { return m_output_adaptive; } + bool synchronous() const { return m_synchronous; } + bool resampling_disabled() const { return m_resampling_disabled; } + + // 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]; } + + // 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 + 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(); - const stream_sample_t *output_since_last_update(int outputnum, int &numsamples); - // timing - void set_sample_rate(int sample_rate); - void set_user_gain(int inputnum, float gain); - void set_input_gain(int inputnum, float gain); - void set_output_gain(int outputnum, float gain); + // 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); + + // apply any pending sample rate changes; should only be called by the sound manager + void apply_sample_rate_changes(u32 updatenum, u32 downstream_rate); + +#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 + +protected: + // protected state + std::string m_name; // name of this stream private: - // helpers called by our friends only - void update_with_accounting(bool second_tick); - void apply_sample_rate_changes(); + // perform most of the initialization here + void init_common(u32 inputs, u32 outputs, u32 sample_rate, sound_stream_flags flags); + + // if the sample rate has changed, this gets called to update internals + void sample_rate_changed(); - // internal helpers - void recompute_sample_rate_data(); - void allocate_resample_buffers(); - void allocate_output_buffers(); + // handle updates after a save state load void postload(); - void generate_samples(int samples); - stream_sample_t *generate_resampled_data(stream_input &input, u32 numsamples); + + // re-print the synchronization timer + void reprime_sync_timer(); + + // timer callback for synchronous streams void sync_update(void *, s32); + // new callback which wrapps calls through to the old-style callbacks + void stream_update_legacy(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs); + + // return a view of 0 data covering the given time period + read_stream_view empty_view(attotime start, attotime end); + // linking information - device_t & m_device; // owning device - sound_stream * m_next; // next stream in the chain + device_t &m_device; // owning device + sound_stream *m_next; // next stream in the chain // general information - u32 m_sample_rate; // sample rate of this stream - u32 m_new_sample_rate; // newly-set sample rate for the stream - bool m_synchronous; // synchronous stream that runs at the rate of its input - - // timing information - attoseconds_t m_attoseconds_per_sample; // number of attoseconds per sample - s32 m_max_samples_per_update; // maximum samples per update - emu_timer * m_sync_timer; // update timer for synchronous streams + 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 + 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? + emu_timer *m_sync_timer; // update timer for synchronous streams // input information - std::vector<stream_input> m_input; // list of streams we directly depend upon - std::vector<stream_sample_t *> m_input_array; // array of inputs for passing to the callback - - // resample buffer information - u32 m_resample_bufalloc; // allocated size of each resample buffer + std::vector<sound_stream_input> m_input; // list of streams we directly depend upon + std::vector<stream_sample_t *> m_input_array; // array of inputs for passing to the callback + 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 - std::vector<stream_output> m_output; // list of streams which directly depend upon us - std::vector<stream_sample_t *> m_output_array; // array of outputs for passing to the callback - - // output buffer information - u32 m_output_bufalloc; // allocated size of each output buffer - s32 m_output_sampindex; // current position within each output buffer - s32 m_output_update_sampindex; // position at time of last global update - s32 m_output_base_sampindex; // sample at base of buffer, relative to the current emulated second + 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<stream_sample_t *> m_output_array; // array of outputs for passing to the callback + std::vector<write_stream_view> m_output_view; // array of output views for passing to the callback // callback information - stream_update_delegate m_callback; // callback function + stream_update_legacy_delegate m_callback; // callback function + 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; }; // ======================> 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; @@ -197,56 +734,88 @@ public: 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; } - attoseconds_t update_attoseconds() const { return m_update_attoseconds; } int sample_count() const { return m_samples_this_update; } - void samples(s16 *buffer); + int unique_id() { return m_unique_id++; } + + // allocate a new stream with the old-style callback + sound_stream *stream_alloc_legacy(device_t &device, u32 inputs, u32 outputs, u32 sample_rate, stream_update_legacy_delegate callback); - // stream creation - sound_stream *stream_alloc(device_t &device, int inputs, int outputs, int sample_rate, stream_update_delegate callback = stream_update_delegate()); + // allocate a new stream with a new-style callback + sound_stream *stream_alloc(device_t &device, u32 inputs, u32 outputs, u32 sample_rate, stream_update_delegate callback, sound_stream_flags flags); - // global controls + // begin recording a WAV file if options has requested it void start_recording(); + + // stop recording the WAV file void stop_recording(); - void set_attenuation(int attenuation); + + // set the global OSD attenuation level + void set_attenuation(float attenuation); + + // mute sound for one of various independent reasons void ui_mute(bool turn_off = true) { mute(turn_off, MUTE_REASON_UI); } void debugger_mute(bool turn_off = true) { mute(turn_off, MUTE_REASON_DEBUGGER); } void system_mute(bool turn_off = true) { mute(turn_off, MUTE_REASON_SYSTEM); } void system_enable(bool turn_on = true) { mute(!turn_on, MUTE_REASON_SYSTEM); } - // user gain controls + // return information about the given mixer input, by index bool indexed_mixer_input(int index, mixer_input &info) const; + // fill the given buffer with 16-bit stereo audio samples + void samples(s16 *buffer); + private: - // internal helpers + // 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(); + + // pause/resume sound output void pause(); void resume(); + + // handle configuration load/save void config_load(config_type cfg_type, 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(void *ptr = nullptr, s32 param = 0); // internal state - running_machine & m_machine; // reference to our machine - emu_timer * m_update_timer; // timer to drive periodic updates - - u32 m_finalmix_leftover; - std::vector<s16> m_finalmix; - std::vector<s32> m_leftmix; - std::vector<s32> m_rightmix; - int m_samples_this_update; - - u8 m_muted; - int m_attenuation; - int m_nosound_mode; - - wav_file * m_wavfile; + running_machine &m_machine; // reference to the running machine + emu_timer *m_update_timer; // timer that runs the update function + + 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 + + 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 + wav_file *m_wavfile; // WAV file for streaming // streams data - std::vector<std::unique_ptr<sound_stream>> m_stream_list; // list of streams - attoseconds_t m_update_attoseconds; // attoseconds between global updates - attotime m_last_update; // last update time + 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? }; diff --git a/src/emu/speaker.cpp b/src/emu/speaker.cpp index f5f507d242b..3cbb14355a4 100644 --- a/src/emu/speaker.cpp +++ b/src/emu/speaker.cpp @@ -56,38 +56,31 @@ speaker_device::~speaker_device() // mix - mix in samples from the speaker's stream //------------------------------------------------- -void speaker_device::mix(s32 *leftmix, s32 *rightmix, int &samples_this_update, bool suppress) +void speaker_device::mix(stream_buffer::sample_t *leftmix, stream_buffer::sample_t *rightmix, attotime start, attotime end, int expected_samples, bool suppress) { // skip if no stream if (m_mixer_stream == nullptr) return; - // update the stream, getting the start/end pointers around the operation - int numsamples; - const stream_sample_t *stream_buf = m_mixer_stream->output_since_last_update(0, numsamples); - - // set or assert that all streams have the same count - if (samples_this_update == 0) - { - samples_this_update = numsamples; + // skip if invalid range + if (start > end) + return; - // reset the mixing streams - std::fill_n(leftmix, samples_this_update, 0); - std::fill_n(rightmix, samples_this_update, 0); - } - assert(samples_this_update == numsamples); + // 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 < samples_this_update; sample++) + for (int sample = 0; sample < expected_samples; sample++) { - m_current_max = std::max(m_current_max, abs(stream_buf[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; + m_current_max = 0.0f; m_samples_this_bucket = 0; } } @@ -98,21 +91,22 @@ void speaker_device::mix(s32 *leftmix, s32 *rightmix, int &samples_this_update, { // if the speaker is centered, send to both left and right if (m_x == 0) - for (int sample = 0; sample < samples_this_update; sample++) + for (int sample = 0; sample < expected_samples; sample++) { - leftmix[sample] += stream_buf[sample]; - rightmix[sample] += stream_buf[sample]; + stream_buffer::sample_t cursample = view.get(sample); + leftmix[sample] += cursample; + rightmix[sample] += cursample; } // if the speaker is to the left, send only to the left else if (m_x < 0) - for (int sample = 0; sample < samples_this_update; sample++) - leftmix[sample] += stream_buf[sample]; + for (int sample = 0; sample < expected_samples; sample++) + leftmix[sample] += view.get(sample); // if the speaker is to the right, send only to the right else - for (int sample = 0; sample < samples_this_update; sample++) - rightmix[sample] += stream_buf[sample]; + for (int sample = 0; sample < expected_samples; sample++) + rightmix[sample] += view.get(sample); } } @@ -143,27 +137,51 @@ void speaker_device::device_stop() m_max_sample.push_back(m_current_max); // determine overall maximum and number of clipped buckets - s32 overallmax = 0; + stream_buffer::sample_t overallmax = 0; u32 clipped = 0; for (auto &curmax : m_max_sample) { overallmax = std::max(overallmax, curmax); - if (curmax > 32767) + 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 = %d (gain *= %.3f) - clipped in %d/%d (%d%%) buckets\n", tag(), overallmax, 32767.0 / (overallmax ? overallmax : 1), clipped, m_max_sample.size(), clipped * 100 / m_max_sample.size()); + 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 > 32767 || report == 4) - osd_printf_info(" t=%5.1f max=%6d\n", t, curmax); + 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); } } diff --git a/src/emu/speaker.h b/src/emu/speaker.h index 7cf27d12471..fd20c4b94b8 100644 --- a/src/emu/speaker.h +++ b/src/emu/speaker.h @@ -69,7 +69,7 @@ public: speaker_device &backrest() { set_position( 0.0, -0.2, 0.1); return *this; } // internally for use by the sound system - void mix(s32 *leftmix, s32 *rightmix, int &samples_this_update, bool suppress); + void mix(stream_buffer::sample_t *leftmix, stream_buffer::sample_t *rightmix, attotime start, attotime end, int expected_samples, bool suppress); protected: // device-level overrides @@ -77,20 +77,20 @@ protected: virtual void device_stop() override ATTR_COLD; // inline configuration state - double m_x; - double m_y; - double m_z; + double m_x; + double m_y; + double m_z; // internal state static constexpr int BUCKETS_PER_SECOND = 10; - std::vector<s32> m_max_sample; - s32 m_current_max; - u32 m_samples_this_bucket; + std::vector<stream_buffer::sample_t> m_max_sample; + stream_buffer::sample_t m_current_max; + u32 m_samples_this_bucket; }; // speaker device iterator -typedef device_type_iterator<speaker_device> speaker_device_iterator; +using speaker_device_iterator = device_type_iterator<speaker_device>; #endif // MAME_EMU_SPEAKER_H diff --git a/src/emu/xtal.cpp b/src/emu/xtal.cpp index 5b8b763ab2f..ca69a8c54f9 100644 --- a/src/emu/xtal.cpp +++ b/src/emu/xtal.cpp @@ -336,6 +336,7 @@ const double XTAL::known_xtals[] = { 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 */ @@ -353,6 +354,7 @@ const double XTAL::known_xtals[] = { 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 - */ + 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 */ |