diff options
Diffstat (limited to 'src/emu/sound.h')
-rw-r--r-- | src/emu/sound.h | 1060 |
1 files changed, 426 insertions, 634 deletions
diff --git a/src/emu/sound.h b/src/emu/sound.h index 15f6a5743a2..3b30a58dc66 100644 --- a/src/emu/sound.h +++ b/src/emu/sound.h @@ -62,21 +62,19 @@ #define MAME_EMU_SOUND_H #include "wavwrite.h" - +#include "interface/audio.h" +#include <mutex> +#include <thread> +#include <condition_variable> //************************************************************************** // CONSTANTS //************************************************************************** // special sample-rate values -constexpr u32 SAMPLE_RATE_INVALID = 0xffffffff; -constexpr u32 SAMPLE_RATE_INPUT_ADAPTIVE = 0xfffffffe; -constexpr u32 SAMPLE_RATE_OUTPUT_ADAPTIVE = 0xfffffffd; - -// anything below this sample rate is effectively treated as "off" -constexpr u32 SAMPLE_RATE_MINIMUM = 50; - - +constexpr u32 SAMPLE_RATE_INPUT_ADAPTIVE = 0xffffffff; +constexpr u32 SAMPLE_RATE_OUTPUT_ADAPTIVE = 0xfffffffe; +constexpr u32 SAMPLE_RATE_ADAPTIVE = 0xfffffffd; //************************************************************************** // DEBUGGING @@ -86,7 +84,7 @@ constexpr u32 SAMPLE_RATE_MINIMUM = 50; #ifdef MAME_DEBUG #define SOUND_DEBUG (1) #else -#define SOUND_DEBUG (0) +#define SOUND_DEBUG (1) #endif // if SOUND_DEBUG is on, make assertions fire regardless of MAME_DEBUG @@ -96,491 +94,9 @@ constexpr u32 SAMPLE_RATE_MINIMUM = 50; #define sound_assert assert #endif - - -//************************************************************************** -// TYPE DEFINITIONS -//************************************************************************** - -// ======================> stream_buffer - -class stream_buffer -{ - // stream_buffer is an internal class, not directly accessed - // outside of the classes below - friend class read_stream_view; - friend class write_stream_view; - friend class sound_stream; - friend class sound_stream_output; - -public: - // the one public bit is the sample type - using sample_t = float; - -private: - // constructor/destructor - stream_buffer(u32 sample_rate = 48000); - ~stream_buffer(); - - // disable copying of stream_buffers directly - stream_buffer(stream_buffer const &src) = delete; - stream_buffer &operator=(stream_buffer const &rhs) = delete; - - // return the current sample rate - u32 sample_rate() const { return m_sample_rate; } - - // set a new sample rate - void set_sample_rate(u32 rate, bool resample); - - // return the current sample period in attoseconds - attoseconds_t sample_period_attoseconds() const { return m_sample_attos; } - attotime sample_period() const { return attotime(0, m_sample_attos); } - - // return the attotime of the current end of buffer - attotime end_time() const { return index_time(m_end_sample); } - - // set the ending time (for forced resyncs; generally not used) - void set_end_time(attotime time) - { - m_end_second = time.seconds(); - m_end_sample = u32(time.attoseconds() / m_sample_attos); - } - - // return the effective buffer size; currently it is a full second of audio - // at the current sample rate, but this maybe change in the future - u32 size() const { return m_sample_rate; } - - // read the sample at the given index (clamped); should be valid in all cases - sample_t get(s32 index) const - { - sound_assert(u32(index) < size()); - sample_t value = m_buffer[index]; -#if (SOUND_DEBUG) - sound_assert(!std::isnan(value)); -#endif - return value; - } - - // write the sample at the given index (clamped) - void put(s32 index, sample_t data) - { - sound_assert(u32(index) < size()); - m_buffer[index] = data; - } - - // simple helpers to step indexes - u32 next_index(u32 index) { index++; return (index == size()) ? 0 : index; } - u32 prev_index(u32 index) { return (index == 0) ? (size() - 1) : (index - 1); } - - // clamp an index to the size of the buffer; allows for indexing +/- one - // buffers' worth of range - u32 clamp_index(s32 index) const - { - if (index < 0) - index += size(); - else if (index >= size()) - index -= size(); - sound_assert(index >= 0 && index < size()); - return index; - } - - // fill the buffer with the given value - void fill(sample_t value) { std::fill_n(&m_buffer[0], m_buffer.size(), value); } - - // return the attotime of a given index within the buffer - attotime index_time(s32 index) const; - - // given an attotime, return the buffer index corresponding to it - u32 time_to_buffer_index(attotime time, bool round_up, bool allow_expansion = false); - - // downsample from our buffer into a temporary buffer - void backfill_downsample(sample_t *dest, int samples, attotime newend, attotime newperiod); - - // upsample from a temporary buffer into our buffer - void backfill_upsample(sample_t const *src, int samples, attotime prevend, attotime prevperiod); - - // internal state - u32 m_end_second; // current full second of the buffer end - u32 m_end_sample; // current sample number within the final second - u32 m_sample_rate; // sample rate of the data in the buffer - attoseconds_t m_sample_attos; // pre-computed attoseconds per sample - std::vector<sample_t> m_buffer; // vector of actual buffer data - -#if (SOUND_DEBUG) -public: - // for debugging, provide an interface to write a WAV stream - void open_wav(char const *filename); - void flush_wav(); - -private: - // internal debugging state - util::wav_file_ptr m_wav_file; // pointer to the current WAV file - u32 m_last_written = 0; // last written sample index -#endif -}; - - -// ======================> read_stream_view - -class read_stream_view -{ -public: - using sample_t = stream_buffer::sample_t; - -protected: - // private constructor used by write_stream_view that allows for expansion - read_stream_view(stream_buffer &buffer, attotime start, attotime end) : - read_stream_view(&buffer, 0, buffer.time_to_buffer_index(end, true, true), 1.0) - { - // start has to be set after end, since end can expand the buffer and - // potentially invalidate start - m_start = buffer.time_to_buffer_index(start, false); - normalize_start_end(); - } - -public: - // base constructor to simplify some of the code - read_stream_view(stream_buffer *buffer, s32 start, s32 end, sample_t gain) : - m_buffer(buffer), - m_end(end), - m_start(start), - m_gain(gain) - { - normalize_start_end(); - } - - // empty constructor so we can live in an array or vector - read_stream_view() : - read_stream_view(nullptr, 0, 0, 1.0) - { - } - - // constructor that covers the given time period - read_stream_view(stream_buffer &buffer, attotime start, attotime end, sample_t gain) : - read_stream_view(&buffer, buffer.time_to_buffer_index(start, false), buffer.time_to_buffer_index(end, true), gain) - { - } - - // copy constructor - read_stream_view(read_stream_view const &src) : - read_stream_view(src.m_buffer, src.m_start, src.m_end, src.m_gain) - { - } - - // copy constructor that sets a different start time - read_stream_view(read_stream_view const &src, attotime start) : - read_stream_view(src.m_buffer, src.m_buffer->time_to_buffer_index(start, false), src.m_end, src.m_gain) - { - } - - // copy assignment - read_stream_view &operator=(read_stream_view const &rhs) - { - m_buffer = rhs.m_buffer; - m_start = rhs.m_start; - m_end = rhs.m_end; - m_gain = rhs.m_gain; - normalize_start_end(); - return *this; - } - - // return the local gain - sample_t gain() const { return m_gain; } - - // return the sample rate of the data - u32 sample_rate() const { return m_buffer->sample_rate(); } - - // return the sample period (in attoseconds) of the data - attoseconds_t sample_period_attoseconds() const { return m_buffer->sample_period_attoseconds(); } - attotime sample_period() const { return m_buffer->sample_period(); } - - // return the number of samples represented by the buffer - u32 samples() const { return m_end - m_start; } - - // return the starting or ending time of the buffer - attotime start_time() const { return m_buffer->index_time(m_start); } - attotime end_time() const { return m_buffer->index_time(m_end); } - - // set the gain - read_stream_view &set_gain(float gain) { m_gain = gain; return *this; } - - // apply an additional gain factor - read_stream_view &apply_gain(float gain) { m_gain *= gain; return *this; } - - // safely fetch a gain-scaled sample from the buffer - sample_t get(s32 index) const - { - sound_assert(u32(index) < samples()); - index += m_start; - if (index >= m_buffer->size()) - index -= m_buffer->size(); - return m_buffer->get(index) * m_gain; - } - - // safely fetch a raw sample from the buffer; if you use this, you need to - // apply the gain yourself for correctness - sample_t getraw(s32 index) const - { - sound_assert(u32(index) < samples()); - index += m_start; - if (index >= m_buffer->size()) - index -= m_buffer->size(); - return m_buffer->get(index); - } - -protected: - // normalize start/end - void normalize_start_end() - { - // ensure that end is always greater than start; we'll - // wrap to the buffer length as needed - if (m_end < m_start && m_buffer != nullptr) - m_end += m_buffer->size(); - sound_assert(m_end >= m_start); - } - - // internal state - stream_buffer *m_buffer; // pointer to the stream buffer we're viewing - s32 m_end; // ending sample index (always >= start) - s32 m_start; // starting sample index - sample_t m_gain; // overall gain factor -}; - - -// ======================> write_stream_view - -class write_stream_view : public read_stream_view -{ - -public: - // empty constructor so we can live in an array or vector - write_stream_view() - { - } - - // constructor that covers the given time period - write_stream_view(stream_buffer &buffer, attotime start, attotime end) : - read_stream_view(buffer, start, end) - { - } - - // constructor that converts from a read_stream_view - write_stream_view(read_stream_view const &src) : - read_stream_view(src) - { - } - - // safely write a sample to the buffer - void put(s32 start, sample_t sample) - { - sound_assert(u32(start) < samples()); - m_buffer->put(index_to_buffer_index(start), sample); - } - - // write a sample to the buffer, clamping to +/- the clamp value - void put_clamp(s32 index, sample_t sample, sample_t clamp = 1.0) - { - assert(clamp >= sample_t(0)); - put(index, std::clamp(sample, -clamp, clamp)); - } - - // write a sample to the buffer, converting from an integer with the given maximum - void put_int(s32 index, s32 sample, s32 max) - { - put(index, sample_t(sample) * (1.0f / sample_t(max))); - } - - // write a sample to the buffer, converting from an integer with the given maximum - void put_int_clamp(s32 index, s32 sample, s32 maxclamp) - { - assert(maxclamp >= 0); - put_int(index, std::clamp(sample, -maxclamp, maxclamp), maxclamp); - } - - // safely add a sample to the buffer - void add(s32 start, sample_t sample) - { - sound_assert(u32(start) < samples()); - u32 index = index_to_buffer_index(start); - m_buffer->put(index, m_buffer->get(index) + sample); - } - - // add a sample to the buffer, converting from an integer with the given maximum - void add_int(s32 index, s32 sample, s32 max) - { - add(index, sample_t(sample) * (1.0f / sample_t(max))); - } - - // fill part of the view with the given value - void fill(sample_t value, s32 start, s32 count) - { - if (start + count > samples()) - count = samples() - start; - u32 index = index_to_buffer_index(start); - for (s32 sampindex = 0; sampindex < count; sampindex++) - { - m_buffer->put(index, value); - index = m_buffer->next_index(index); - } - } - void fill(sample_t value, s32 start) { fill(value, start, samples() - start); } - void fill(sample_t value) { fill(value, 0, samples()); } - - // copy data from another view - void copy(read_stream_view const &src, s32 start, s32 count) - { - if (start + count > samples()) - count = samples() - start; - u32 index = index_to_buffer_index(start); - for (s32 sampindex = 0; sampindex < count; sampindex++) - { - m_buffer->put(index, src.get(start + sampindex)); - index = m_buffer->next_index(index); - } - } - void copy(read_stream_view const &src, s32 start) { copy(src, start, samples() - start); } - void copy(read_stream_view const &src) { copy(src, 0, samples()); } - - // add data from another view to our current values - void add(read_stream_view const &src, s32 start, s32 count) - { - if (start + count > samples()) - count = samples() - start; - u32 index = index_to_buffer_index(start); - for (s32 sampindex = 0; sampindex < count; sampindex++) - { - m_buffer->put(index, m_buffer->get(index) + src.get(start + sampindex)); - index = m_buffer->next_index(index); - } - } - void add(read_stream_view const &src, s32 start) { add(src, start, samples() - start); } - void add(read_stream_view const &src) { add(src, 0, samples()); } - -private: - // given a stream starting offset, return the buffer index - u32 index_to_buffer_index(s32 start) const - { - u32 index = start + m_start; - if (index >= m_buffer->size()) - index -= m_buffer->size(); - return index; - } -}; - - -// ======================> sound_stream_output - -class sound_stream_output -{ -#if (SOUND_DEBUG) - friend class sound_stream; -#endif - -public: - // construction/destruction - sound_stream_output(); - - // initialization - void init(sound_stream &stream, u32 index, char const *tag_base); - - // no copying allowed - sound_stream_output(sound_stream_output const &src) = delete; - sound_stream_output &operator=(sound_stream_output const &rhs) = delete; - - // simple getters - sound_stream &stream() const { sound_assert(m_stream != nullptr); return *m_stream; } - attotime end_time() const { return m_buffer.end_time(); } - u32 index() const { return m_index; } - stream_buffer::sample_t gain() const { return m_gain; } - u32 buffer_sample_rate() const { return m_buffer.sample_rate(); } - - // simple setters - void set_gain(float gain) { m_gain = gain; } - - // return a friendly name - std::string name() const; - - // handle a changing sample rate - void sample_rate_changed(u32 rate) { m_buffer.set_sample_rate(rate, true); } - - // return an output view covering a time period - write_stream_view view(attotime start, attotime end) { return write_stream_view(m_buffer, start, end); } - - // resync the buffer to the given end time - void set_end_time(attotime end) { m_buffer.set_end_time(end); } - - // attempt to optimize resamplers by reusing them where possible - sound_stream_output &optimize_resampler(sound_stream_output *input_resampler); - -private: - // internal state - sound_stream *m_stream; // owning stream - stream_buffer m_buffer; // output buffer - u32 m_index; // output index within the stream - stream_buffer::sample_t m_gain; // gain to apply to the output - std::vector<sound_stream_output *> m_resampler_list; // list of resamplers we're connected to -}; - - -// ======================> sound_stream_input - -class sound_stream_input -{ -#if (SOUND_DEBUG) - friend class sound_stream; -#endif - -public: - // construction/destruction - sound_stream_input(); - - // initialization - void init(sound_stream &stream, u32 index, char const *tag_base, sound_stream_output *resampler); - - // no copying allowed - sound_stream_input(sound_stream_input const &src) = delete; - sound_stream_input &operator=(sound_stream_input const &rhs) = delete; - - // simple getters - bool valid() const { return (m_native_source != nullptr); } - sound_stream &owner() const { sound_assert(valid()); return *m_owner; } - sound_stream_output &source() const { sound_assert(valid()); return *m_native_source; } - u32 index() const { return m_index; } - stream_buffer::sample_t gain() const { return m_gain; } - stream_buffer::sample_t user_gain() const { return m_user_gain; } - - // simple setters - void set_gain(float gain) { m_gain = gain; } - void set_user_gain(float gain) { m_user_gain = gain; } - - // return a friendly name - std::string name() const; - - // connect the source - void set_source(sound_stream_output *source); - - // update and return an reading view - read_stream_view update(attotime start, attotime end); - - // tell inputs to apply sample rate changes - void apply_sample_rate_changes(u32 updatenum, u32 downstream_rate); - -private: - // internal state - sound_stream *m_owner; // pointer to the owning stream - sound_stream_output *m_native_source; // pointer to the native sound_stream_output - sound_stream_output *m_resampler_source; // pointer to the resampler output - u32 m_index; // input index within the stream - stream_buffer::sample_t m_gain; // gain to apply to this input - stream_buffer::sample_t m_user_gain; // user-controlled gain to apply to this input -}; - - -// ======================> stream_update_delegate - -// new-style callback -using stream_update_delegate = delegate<void (sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)>; - +using stream_update_delegate = delegate<void (sound_stream &stream)>; +class audio_effect; +class audio_resampler; // ======================> sound_stream_flags @@ -592,86 +108,203 @@ enum sound_stream_flags : u32 // specify that updates should be forced to one sample at a time, in real time // this implicitly creates a timer that runs at the stream's output frequency // so only use when strictly necessary - STREAM_SYNCHRONOUS = 0x01, - - // specify that input streams should not be resampled; stream update handler - // must be able to accommodate multiple strams of differing input rates - STREAM_DISABLE_INPUT_RESAMPLING = 0x02 + STREAM_SYNCHRONOUS = 0x01 }; +namespace emu::detail { + template<typename S> class output_buffer_interleaved { + public: + output_buffer_interleaved(u32 buffer_size, u32 channels); + + void set_buffer_size(u32 buffer_size); + + u32 channels() const { return m_channels; } + u64 sync_sample() const { return m_sync_sample; } + void set_sync_sample(u64 sample) { m_sync_sample = sample; } + u64 write_sample() const { return m_sync_sample + m_write_position - m_sync_position; } + void prepare_space(u32 samples); + void commit(u32 samples); + void sync(); + + void ensure_size(u32 buffer_size); + void set_history(u32 history); + + u32 available_samples() const { return m_write_position - m_sync_position; } + S *ptrw(u32 channel, s32 index) { return &m_buffer[(m_write_position + index) * m_channels + channel]; } + const S *ptrw(u32 channel, s32 index) const { return &m_buffer[(m_write_position + index) * m_channels + channel]; } + const S *ptrs(u32 channel, s32 index) const { return &m_buffer[(m_sync_position + index) * m_channels + channel]; } + + private: + std::vector<S> m_buffer; + u64 m_sync_sample; + u32 m_write_position; + u32 m_sync_position; + u32 m_history; + u32 m_channels; + }; + + template<typename S> class output_buffer_flat { + friend class sound_stream; // To make state saving easier + public: + output_buffer_flat(u32 buffer_size, u32 channels); + + void set_buffer_size(u32 buffer_size); + + u32 channels() const { return m_channels; } + u64 sync_sample() const { return m_sync_sample; } + void set_sync_sample(u64 sample) { m_sync_sample = sample; } + u64 write_sample() const { return m_sync_sample + m_write_position - m_sync_position; } + + void prepare_space(u32 samples); + void commit(u32 samples); + void sync(); + + void ensure_size(u32 buffer_size); + void set_history(u32 history); + + void resample(u32 previous_rate, u32 next_rate, attotime sync_time, attotime now); + + void register_save_state(device_t &device, const char *id1, const char *id2); + + u32 available_samples() const { return m_write_position - m_sync_position; } + S *ptrw(u32 channel, s32 index) { return &m_buffer[channel][m_write_position + index]; } + const S *ptrw(u32 channel, s32 index) const { return &m_buffer[channel][m_write_position + index]; } + const S *ptrs(u32 channel, s32 index) const { return &m_buffer[channel][m_sync_position + index]; } + + private: + std::vector<std::vector<S>> m_buffer; + u64 m_sync_sample; + u32 m_write_position; + u32 m_sync_position; + u32 m_history; + u32 m_channels; + }; +} // ======================> sound_stream class sound_stream { +public: friend class sound_manager; + using sample_t = float; - // private common constructopr - sound_stream(device_t &device, u32 inputs, u32 outputs, u32 output_base, u32 sample_rate, sound_stream_flags flags); - -public: // construction/destruction - sound_stream(device_t &device, u32 inputs, u32 outputs, u32 output_base, u32 sample_rate, stream_update_delegate callback, sound_stream_flags flags = STREAM_DEFAULT_FLAGS); + sound_stream(device_t &device, u32 inputs, u32 outputs, u32 sample_rate, stream_update_delegate callback, sound_stream_flags flags = sound_stream_flags::STREAM_DEFAULT_FLAGS); virtual ~sound_stream(); // simple getters - sound_stream *next() const { return m_next; } device_t &device() const { return m_device; } std::string name() const { return m_name; } - bool input_adaptive() const { return m_input_adaptive || m_synchronous; } + bool input_adaptive() const { return m_input_adaptive; } bool output_adaptive() const { return m_output_adaptive; } bool synchronous() const { return m_synchronous; } - bool resampling_disabled() const { return m_resampling_disabled; } + bool is_active() const { return m_sample_rate != 0; } // input and output getters - u32 input_count() const { return m_input.size(); } - u32 output_count() const { return m_output.size(); } - u32 output_base() const { return m_output_base; } - sound_stream_input &input(int index) { sound_assert(index >= 0 && index < m_input.size()); return m_input[index]; } - sound_stream_output &output(int index) { sound_assert(index >= 0 && index < m_output.size()); return m_output[index]; } + u32 input_count() const { return m_input_count; } + u32 output_count() const { return m_output_count; } // sample rate and timing getters - u32 sample_rate() const { return (m_pending_sample_rate != SAMPLE_RATE_INVALID) ? m_pending_sample_rate : m_sample_rate; } - attotime sample_time() const { return m_output[0].end_time(); } - attotime sample_period() const { return attotime(0, sample_period_attoseconds()); } - attoseconds_t sample_period_attoseconds() const { return (m_sample_rate != SAMPLE_RATE_INVALID) ? HZ_TO_ATTOSECONDS(m_sample_rate) : ATTOSECONDS_PER_SECOND; } - - // set the sample rate of the stream; will kick in at the next global update + u32 sample_rate() const { return m_sample_rate; } + attotime sample_period() const { return attotime::from_hz(m_sample_rate); } + + // sample id and timing of the first and last sample of the current update block, and first of the next sample block + u64 start_index() const { return m_output_buffer.write_sample(); } + u64 end_index() const { return m_output_buffer.write_sample() + samples() - 1; } + u64 sample_index() const { return m_output_buffer.write_sample() + samples(); } + attotime start_time() const { return sample_to_time(start_index()); } + attotime end_time() const { return sample_to_time(end_index()); } + attotime sample_time() const { return sample_to_time(sample_index()); } + + // convert from absolute sample index to time + attotime sample_to_time(u64 index) const; + + // gain management + float user_output_gain() const { return m_user_output_gain; } + void set_user_output_gain(float gain) { update(); m_user_output_gain = gain; } + float user_output_gain(s32 output) const { return m_user_output_channel_gain[output]; } + void set_user_output_gain(s32 output, float gain) { update(); m_user_output_channel_gain[output] = gain; } + + float input_gain(s32 input) const { return m_input_channel_gain[input]; } + void set_input_gain(s32 input, float gain) { update(); m_input_channel_gain[input] = gain; } + void apply_input_gain(s32 input, float gain) { update(); m_input_channel_gain[input] *= gain; } + float output_gain(s32 output) const { return m_output_channel_gain[output]; } + void set_output_gain(s32 output, float gain) { update(); m_output_channel_gain[output] = gain; } + void apply_output_gain(s32 output, float gain) { update(); m_output_channel_gain[output] *= gain; } + + // set the sample rate of the stream void set_sample_rate(u32 sample_rate); - // connect the output 'outputnum' of given input_stream to this stream's input 'inputnum' - void set_input(int inputnum, sound_stream *input_stream, int outputnum = 0, float gain = 1.0f); - // force an update to the current time void update(); - // force an update to the current time, returning a view covering the given time period - read_stream_view update_view(attotime start, attotime end, u32 outputnum = 0); + // number of samples to handle + s32 samples() const { return m_samples_to_update; } - // apply any pending sample rate changes; should only be called by the sound manager - void apply_sample_rate_changes(u32 updatenum, u32 downstream_rate); + // write a sample to the buffer + void put(s32 output, s32 index, sample_t sample) { *m_output_buffer.ptrw(output, index) = sample; } -#if (SOUND_DEBUG) - // print one level of the sound graph and recursively tell our inputs to do the same - void print_graph_recursive(int indent, int index); -#endif + // write a sample to the buffer, clamping to +/- the clamp value + void put_clamp(s32 output, s32 index, sample_t sample, sample_t clamp = 1.0) { put(output, index, std::clamp(sample, -clamp, clamp)); } -protected: - // protected state - std::string m_name; // name of this stream + // write a sample to the buffer, converting from an integer with the given maximum + void put_int(s32 output, s32 index, s32 sample, s32 max) { put(output, index, double(sample)/max); } + + // write a sample to the buffer, converting from an integer with the given maximum + void put_int_clamp(s32 output, s32 index, s32 sample, s32 maxclamp) { put_int(output, index, std::clamp(sample, -maxclamp, maxclamp-1), maxclamp); } + + // safely add a sample to the buffer + void add(s32 output, s32 index, sample_t sample) { *m_output_buffer.ptrw(output, index) += sample; } + + // add a sample to the buffer, converting from an integer with the given maximum + void add_int(s32 output, s32 index, s32 sample, s32 max) { add(output, index, double(sample)/max); } + + // fill part of the view with the given value + void fill(s32 output, sample_t value, s32 start, s32 count) { std::fill(m_output_buffer.ptrw(output, start), m_output_buffer.ptrw(output, start+count), value); } + void fill(s32 output, sample_t value, s32 start) { std::fill(m_output_buffer.ptrw(output, start), m_output_buffer.ptrw(output, samples()), value); } + void fill(s32 output, sample_t value) { std::fill(m_output_buffer.ptrw(output, 0), m_output_buffer.ptrw(output, samples()), value); } + + // copy data from the input + void copy(s32 output, s32 input, s32 start, s32 count) { std::copy(m_input_buffer[input].begin() + start, m_input_buffer[input].begin() + start + count, m_output_buffer.ptrw(output, start)); } + void copy(s32 output, s32 input, s32 start) { std::copy(m_input_buffer[input].begin() + start, m_input_buffer[input].begin() + samples(), m_output_buffer.ptrw(output, start)); } + void copy(s32 output, s32 input) { std::copy(m_input_buffer[input].begin(), m_input_buffer[input].begin() + samples(), m_output_buffer.ptrw(output, 0)); } + + // fetch a sample from the input buffer + sample_t get(s32 input, s32 index) const { return m_input_buffer[input][index]; } + + // fetch a sample from the output buffer + sample_t get_output(s32 output, s32 index) const { return *m_output_buffer.ptrw(output, index); } + + void add_bw_route(sound_stream *source, int output, int input, float gain); + void add_fw_route(sound_stream *target, int input, int output); + std::vector<sound_stream *> sources() const; + std::vector<sound_stream *> targets() const; + + bool set_route_gain(sound_stream *source, int source_channel, int target_channel, float gain); private: - // perform most of the initialization here - void init_common(u32 inputs, u32 outputs, u32 sample_rate, sound_stream_flags flags); + struct route_bw { + sound_stream *m_source; + int m_output; + int m_input; + float m_gain; + const audio_resampler *m_resampler; - // if the sample rate has changed, this gets called to update internals - void sample_rate_changed(); + route_bw(sound_stream *source, int output, int input, float gain) : m_source(source), m_output(output), m_input(input), m_gain(gain), m_resampler(nullptr) {} + }; - // handle updates after a save state load - void postload(); + struct route_fw { + sound_stream *m_target; + int m_input; + int m_output; + + route_fw(sound_stream *target, int input, int output) : m_target(target), m_input(input), m_output(output) {} + }; - // handle updates before a save state load - void presave(); + + // perform most of the initialization here + void init(); // re-print the synchronization timer void reprime_sync_timer(); @@ -679,68 +312,58 @@ private: // timer callback for synchronous streams void sync_update(s32); - // return a view of 0 data covering the given time period - read_stream_view empty_view(attotime start, attotime end); + void update_nodeps(); + void sync(attotime now); + u64 get_current_sample_index() const; + void do_update(); + + bool frequency_is_solved() const { return (!(m_input_adaptive || m_output_adaptive)) || m_sample_rate != 0; } + bool try_solving_frequency(); + void register_state(); + void add_dependants(std::vector<sound_stream *> &deps); + void compute_dependants(); + void create_resamplers(); + void lookup_history_sizes(); + u32 get_history_for_bw_route(const sound_stream *source, u32 channel) const; + void internal_set_sample_rate(u32 sample_rate); + + std::string m_name; // name of this stream + std::string m_state_tag; // linking information device_t &m_device; // owning device - sound_stream *m_next; // next stream in the chain + std::vector<route_bw> m_bw_routes; + std::vector<route_fw> m_fw_routes; + std::vector<sound_stream *> m_dependant_streams; + + // buffers + std::vector<std::vector<sample_t>> m_input_buffer; + emu::detail::output_buffer_flat<sample_t> m_output_buffer; + attotime m_sync_time; + s32 m_samples_to_update; + + // gains + std::vector<float> m_input_channel_gain; + std::vector<float> m_output_channel_gain; + std::vector<float> m_user_output_channel_gain; + float m_user_output_gain; // general information - u32 m_sample_rate; // current live sample rate - u32 m_pending_sample_rate; // pending sample rate for dynamic changes - u32 m_last_sample_rate_update; // update number of last sample rate change + u32 m_sample_rate; // current sample rate + u32 m_input_count; + u32 m_output_count; bool m_input_adaptive; // adaptive stream that runs at the sample rate of its input bool m_output_adaptive; // adaptive stream that runs at the sample rate of its output bool m_synchronous; // synchronous stream that runs at the rate of its input - bool m_resampling_disabled; // is resampling of input streams disabled? + bool m_started; + bool m_in_update; emu_timer *m_sync_timer; // update timer for synchronous streams - attotime m_last_update_end_time; // last end_time() in update - - // input information - std::vector<sound_stream_input> m_input; // list of streams we directly depend upon - std::vector<read_stream_view> m_input_view; // array of output views for passing to the callback - std::vector<std::unique_ptr<sound_stream>> m_resampler_list; // internal list of resamplers - stream_buffer m_empty_buffer; // empty buffer for invalid inputs - - // output information - u32 m_output_base; // base index of our outputs, relative to our device - std::vector<sound_stream_output> m_output; // list of streams which directly depend upon us - std::vector<write_stream_view> m_output_view; // array of output views for passing to the callback - // callback information - stream_update_delegate m_callback_ex; // extended callback function + stream_update_delegate m_callback; // update 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; @@ -755,6 +378,27 @@ class sound_manager static const attotime STREAMS_UPDATE_ATTOTIME; public: + using sample_t = sound_stream::sample_t; + + struct mapping { + struct node_mapping { + u32 m_node; + float m_db; + bool m_is_system_default; + }; + + struct channel_mapping { + u32 m_guest_channel; + u32 m_node; + u32 m_node_channel; + float m_db; + bool m_is_system_default; + }; + sound_io_device *m_dev; + std::vector<node_mapping> m_node_mappings; + std::vector<channel_mapping> m_channel_mappings; + }; + static constexpr int STREAMS_UPDATE_FREQUENCY = 50; // construction/destruction @@ -763,14 +407,13 @@ public: // getters running_machine &machine() const { return m_machine; } - int attenuation() const { return m_attenuation; } const std::vector<std::unique_ptr<sound_stream>> &streams() const { return m_stream_list; } - attotime last_update() const { return m_last_update; } - int sample_count() const { return m_samples_this_update; } int unique_id() { return m_unique_id++; } - stream_buffer::sample_t compressor_scale() const { return m_compressor_scale; } - // allocate a new stream with a new-style callback + const typename osd::audio_info &get_osd_info() const { return m_osd_info; } + const std::vector<mapping> &get_mappings() const { return m_mappings; } + + // allocate a new stream sound_stream *stream_alloc(device_t &device, u32 inputs, u32 outputs, u32 sample_rate, stream_update_delegate callback, sound_stream_flags flags); // WAV recording @@ -778,9 +421,21 @@ public: bool start_recording(); bool start_recording(std::string_view filename); void stop_recording(); - - // set the global OSD attenuation level - void set_attenuation(float attenuation); + u32 outputs_count() const { return m_outputs_count; } + + // manage the sound_io mapping and volume configuration + void config_add_sound_io_connection_node(sound_io_device *dev, std::string name, float db); + void config_add_sound_io_connection_default(sound_io_device *dev, float db); + void config_remove_sound_io_connection_node(sound_io_device *dev, std::string name); + void config_remove_sound_io_connection_default(sound_io_device *dev); + void config_set_volume_sound_io_connection_node(sound_io_device *dev, std::string name, float db); + void config_set_volume_sound_io_connection_default(sound_io_device *dev, float db); + void config_add_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel, float db); + void config_add_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel, float db); + void config_remove_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel); + void config_remove_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel); + void config_set_volume_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel, float db); + void config_set_volume_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel, float db); // mute sound for one of various independent reasons bool muted() const { return bool(m_muted); } @@ -791,22 +446,120 @@ public: void debugger_mute(bool turn_off) { mute(turn_off, MUTE_REASON_DEBUGGER); } void system_mute(bool turn_off) { mute(turn_off, MUTE_REASON_SYSTEM); } - // return information about the given mixer input, by index - bool indexed_mixer_input(int index, mixer_input &info) const; + // master gain + float master_gain() const { return m_master_gain; } + void set_master_gain(float gain) { m_master_gain = gain; } + + void before_devices_init(); + void after_devices_init(); + void postload(); + + void input_get(int m_id, sound_stream &stream); + void output_push(int m_id, sound_stream &stream); + const audio_resampler *get_resampler(u32 fs, u32 ft); + + u32 effect_chains() const { return m_speakers.size(); } + std::string effect_chain_tag(s32 index) const; + std::vector<audio_effect *> effect_chain(s32 index) const; + std::vector<audio_effect *> default_effect_chain() const; + void default_effect_changed(u32 entry); - // fill the given buffer with 16-bit stereo audio samples - void samples(s16 *buffer); + void mapping_update(); private: + struct effect_step { + std::unique_ptr<audio_effect> m_effect; + emu::detail::output_buffer_flat<sample_t> m_buffer; + effect_step(u32 buffer_size, u32 channels); + }; + + struct mixing_step { + enum { CLEAR, COPY, ADD }; + u32 m_mode; + u32 m_osd_index; + u32 m_osd_channel; + u32 m_device_index; + u32 m_device_channel; + float m_linear_volume; + }; + + struct speaker_info { + speaker_device &m_dev; + sound_stream *m_stream; + u32 m_channels; + u32 m_first_output; + + emu::detail::output_buffer_flat<sample_t> m_buffer; + + std::vector<effect_step> m_effects; + + speaker_info(speaker_device &dev, u32 rate, u32 first_output); + }; + + struct microphone_info { + microphone_device &m_dev; + u32 m_channels; + + std::vector<mixing_step> m_input_mixing_steps; // actions to take to fill the buffer + std::vector<sample_t> m_buffer; + microphone_info(microphone_device &dev); + }; + + struct osd_stream { + u32 m_id; + u32 m_node; + std::string m_node_name; + u32 m_channels; + u32 m_rate; + u32 m_unused_channels_mask; + bool m_is_system_default; + bool m_is_channel_mapping; + sound_io_device *m_dev; + std::vector<float> m_volumes; + + osd_stream(u32 node, std::string node_name, u32 channels, u32 rate, bool is_system_default, sound_io_device *dev) : + m_id(0), + m_node(node), + m_node_name(node_name), + m_channels(channels), + m_rate(rate), + m_unused_channels_mask(util::make_bitmask<u32>(channels)), + m_is_system_default(is_system_default), + m_is_channel_mapping(false), + m_dev(dev) + { } + }; + + struct osd_input_stream : public osd_stream { + emu::detail::output_buffer_interleaved<s16> m_buffer; + osd_input_stream(u32 node, std::string node_name, u32 channels, u32 rate, bool is_system_default, sound_io_device *dev) : + osd_stream(node, node_name, channels, rate, is_system_default, dev), + m_buffer(rate, channels) + { } + }; + + struct osd_output_stream : public osd_stream { + u64 m_last_sync; + u32 m_samples; + std::vector<s16> m_buffer; + osd_output_stream(u32 node, std::string node_name, u32 channels, u32 rate, bool is_system_default, sound_io_device *dev) : + osd_stream(node, node_name, channels, rate, is_system_default, dev), + m_last_sync(0), + m_samples(0), + m_buffer(channels*rate, 0) + { } + }; + + struct config_mapping { + std::string m_name; + // "" to indicates default node + std::vector<std::pair<std::string, float>> m_node_mappings; + std::vector<std::tuple<u32, std::string, u32, float>> m_channel_mappings; + }; + // set/reset the mute state for the given reason void mute(bool mute, u8 reason); - // helper to remove items from the orphan list - void recursive_remove_stream_from_orphan_list(sound_stream *stream); - - // apply pending sample rate changes - void apply_sample_rate_changes(); - // reset all sound chips void reset(); @@ -818,39 +571,78 @@ private: void config_load(config_type cfg_type, config_level cfg_lvl, util::xml::data_node const *parentnode); void config_save(config_type cfg_type, util::xml::data_node *parentnode); - // helper to adjust scale factor toward a goal - stream_buffer::sample_t adjust_toward_compressor_scale(stream_buffer::sample_t curscale, stream_buffer::sample_t prevsample, stream_buffer::sample_t rawsample); - // periodic sound update, called STREAMS_UPDATE_FREQUENCY per second - void update(s32 param = 0); + void update(s32); + + // handle mixing mapping update if needed + static std::vector<u32> find_channel_mapping(const std::array<double, 3> &position, const osd::audio_info::node_info *node); + void startup_cleanups(); + void streams_update(); + template<bool is_output, typename S> void apply_osd_changes(std::vector<S> &streams); + void osd_information_update(); + void generate_mapping(); + void update_osd_streams(); + void update_osd_input(); + void speakers_update(attotime endtime); + + void run_effects(); + + u64 rate_and_time_to_index(attotime time, u32 sample_rate) const; + u64 rate_and_last_sync_to_index(u32 sample_rate) const { return rate_and_time_to_index(m_last_sync_time, sample_rate); } + + // manage the sound_io mapping and volume configuration, + // but don't change generation because we're in the update process + + config_mapping &config_get_sound_io(sound_io_device *dev); + void internal_config_add_sound_io_connection_node(sound_io_device *dev, std::string name, float db); + void internal_config_add_sound_io_connection_default(sound_io_device *dev, float db); + void internal_config_remove_sound_io_connection_node(sound_io_device *dev, std::string name); + void internal_config_remove_sound_io_connection_default(sound_io_device *dev); + void internal_config_set_volume_sound_io_connection_node(sound_io_device *dev, std::string name, float db); + void internal_config_set_volume_sound_io_connection_default(sound_io_device *dev, float db); + void internal_config_add_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel, float db); + void internal_config_add_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel, float db); + void internal_config_remove_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel); + void internal_config_remove_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel); + void internal_config_set_volume_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel, float db); + void internal_config_set_volume_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel, float db); + // internal state - running_machine &m_machine; // reference to the running machine - emu_timer *m_update_timer; // timer that runs the update function - std::vector<std::reference_wrapper<speaker_device> > m_speakers; - - u32 m_update_number; // current update index; used for sample rate updates - attotime m_last_update; // time of the last update - u32 m_finalmix_leftover; // leftover samples in the final mix - u32 m_samples_this_update; // number of samples this update - std::vector<s16> m_finalmix; // final mix, in 16-bit signed format - std::vector<stream_buffer::sample_t> m_leftmix; // left speaker mix, in native format - std::vector<stream_buffer::sample_t> m_rightmix; // right speaker mix, in native format - - stream_buffer::sample_t m_compressor_scale; // current compressor scale factor - int m_compressor_counter; // compressor update counter for backoff - bool m_compressor_enabled; // enable compressor (it will still be calculated for detecting overdrive) - - u8 m_muted; // bitmask of muting reasons - bool m_nosound_mode; // true if we're in "nosound" mode - int m_attenuation; // current attentuation level (at the OSD) - int m_unique_id; // unique ID used for stream identification - util::wav_file_ptr m_wavfile; // WAV file for streaming + running_machine &m_machine; // reference to the running machine + emu_timer *m_update_timer; // timer that runs the update function + attotime m_last_sync_time; + std::vector<speaker_info> m_speakers; + std::vector<microphone_info> m_microphones; + + std::vector<s16> m_record_buffer; // pre-effects speaker samples for recording + u32 m_record_samples; // how many samples for the next update + osd::audio_info m_osd_info; // current state of the osd information + std::vector<mapping> m_mappings; // current state of the mappings + std::vector<osd_input_stream> m_osd_input_streams; // currently active osd streams + std::vector<osd_output_stream> m_osd_output_streams; // currently active osd streams + std::vector<mixing_step> m_output_mixing_steps; // actions to take to fill the osd streams buffers + std::vector<config_mapping> m_configs; // mapping user configuration + + std::mutex m_effects_mutex; + std::condition_variable m_effects_condition; + std::unique_ptr<std::thread> m_effects_thread; + std::vector<std::unique_ptr<audio_effect>> m_default_effects; + bool m_effects_done; + + float m_master_gain; + + std::map<std::pair<u32, u32>, std::unique_ptr<audio_resampler>> m_resamplers; + + u8 m_muted; // bitmask of muting reasons + bool m_nosound_mode; // true if we're in "nosound" mode + int m_unique_id; // unique ID used for stream identification + util::wav_file_ptr m_wavfile; // WAV file for streaming // streams data std::vector<std::unique_ptr<sound_stream>> m_stream_list; // list of streams - std::map<sound_stream *, u8> m_orphan_stream_list; // list of orphaned streams - bool m_first_reset; // is this our first reset? + std::vector<sound_stream *> m_ordered_streams; // Streams in update order + u32 m_outputs_count; }; |