summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu
diff options
context:
space:
mode:
Diffstat (limited to 'src/emu')
-rw-r--r--src/emu/debug/dvmemory.cpp2
-rw-r--r--src/emu/devfind.h11
-rw-r--r--src/emu/disound.cpp105
-rw-r--r--src/emu/disound.h18
-rw-r--r--src/emu/inpttype.ipp29
-rw-r--r--src/emu/ioport.h19
-rw-r--r--src/emu/layout/exorterm155.lay9
-rw-r--r--src/emu/layout/ie15.lay2
-rw-r--r--src/emu/render.cpp6
-rw-r--r--src/emu/render.h46
-rw-r--r--src/emu/rendlay.cpp723
-rw-r--r--src/emu/rendutil.cpp3
-rw-r--r--src/emu/save.cpp6
-rw-r--r--src/emu/save.h185
-rw-r--r--src/emu/sound.cpp1699
-rw-r--r--src/emu/sound.h821
-rw-r--r--src/emu/speaker.cpp76
-rw-r--r--src/emu/speaker.h16
-rw-r--r--src/emu/xtal.cpp2
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 */