summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/sound.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/emu/sound.h')
-rw-r--r--src/emu/sound.h814
1 files changed, 688 insertions, 126 deletions
diff --git a/src/emu/sound.h b/src/emu/sound.h
index 7301a4d69ba..f7d83093ea6 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,498 @@
// 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 &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; }
+
+ // 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); }
+
+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
+};
+
+
+// ======================> 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 +567,141 @@ 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 synchronous() const { return m_synchronous; }
+ bool input_adaptive() const { return m_input_adaptive || m_synchronous; }
+ bool output_adaptive() const { return m_output_adaptive; }
+
+ // 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);
+#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
+ 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 +727,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?
};