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.h1060
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;
};