diff options
Diffstat (limited to 'src/emu/sound.h')
-rw-r--r-- | src/emu/sound.h | 821 |
1 files changed, 695 insertions, 126 deletions
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? }; |