summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/sound.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/emu/sound.cpp')
-rw-r--r--src/emu/sound.cpp1699
1 files changed, 1105 insertions, 594 deletions
diff --git a/src/emu/sound.cpp b/src/emu/sound.cpp
index b59b2304edd..6b54797de36 100644
--- a/src/emu/sound.cpp
+++ b/src/emu/sound.cpp
@@ -25,6 +25,8 @@
#define VPRINTF(x) do { if (VERBOSE) osd_printf_debug x; } while (0)
+#define LOG_OUTPUT_WAV (0)
+
//**************************************************************************
@@ -42,783 +44,1081 @@ const attotime sound_manager::STREAMS_UPDATE_ATTOTIME = attotime::from_hz(STREAM
//**************************************************************************
-// INITIALIZATION
+// STREAM BUFFER
//**************************************************************************
//-------------------------------------------------
-// sound_stream - constructor
-//-------------------------------------------------
-
-sound_stream::sound_stream(device_t &device, int inputs, int outputs, int sample_rate, stream_update_delegate callback)
- : m_device(device),
- m_next(nullptr),
- m_sample_rate(sample_rate),
- m_new_sample_rate(0xffffffff),
- m_attoseconds_per_sample(0),
- m_max_samples_per_update(0),
- m_input(inputs),
- m_input_array(inputs),
- m_resample_bufalloc(0),
- m_output(outputs),
- m_output_array(outputs),
- m_output_bufalloc(0),
- m_output_sampindex(0),
- m_output_update_sampindex(0),
- m_output_base_sampindex(0),
- m_callback(std::move(callback))
+// stream_buffer - constructor
+//-------------------------------------------------
+
+stream_buffer::stream_buffer(u32 sample_rate) :
+ m_end_second(0),
+ m_end_sample(0),
+ m_sample_rate(sample_rate),
+ m_sample_attos((sample_rate == 0) ? ATTOSECONDS_PER_SECOND : ((ATTOSECONDS_PER_SECOND + sample_rate - 1) / sample_rate)),
+ m_buffer(sample_rate)
{
- // get the device's sound interface
- device_sound_interface *sound;
- if (!device.interface(sound))
- throw emu_fatalerror("Attempted to create a sound_stream with a non-sound device");
+}
- if(m_callback.isnull())
- m_callback = stream_update_delegate(&device_sound_interface::sound_stream_update,(device_sound_interface *)sound);
- // create a unique tag for saving
- std::string state_tag = string_format("%d", m_device.machine().sound().m_stream_list.size());
- m_device.machine().save().save_item(&m_device, "stream", state_tag.c_str(), 0, NAME(m_sample_rate));
- m_device.machine().save().register_postload(save_prepost_delegate(FUNC(sound_stream::postload), this));
+//-------------------------------------------------
+// stream_buffer - destructor
+//-------------------------------------------------
- // save the gain of each input and output
- for (unsigned int inputnum = 0; inputnum < m_input.size(); inputnum++)
+stream_buffer::~stream_buffer()
+{
+#if (SOUND_DEBUG)
+ if (m_wav_file != nullptr)
{
- m_device.machine().save().save_item(&m_device, "stream", state_tag.c_str(), inputnum, NAME(m_input[inputnum].m_gain));
- m_device.machine().save().save_item(&m_device, "stream", state_tag.c_str(), inputnum, NAME(m_input[inputnum].m_user_gain));
+ flush_wav();
+ close_wav();
}
- for (unsigned int outputnum = 0; outputnum < m_output.size(); outputnum++)
+#endif
+}
+
+
+//-------------------------------------------------
+// set_sample_rate - set a new sample rate for
+// this buffer
+//-------------------------------------------------
+
+void stream_buffer::set_sample_rate(u32 rate, bool resample)
+{
+ // skip if nothing is actually changing
+ if (rate == m_sample_rate)
+ return;
+
+ // force resampling off if coming to or from an invalid rate
+ sound_assert(rate >= SAMPLE_RATE_MINIMUM - 1);
+ if (rate < SAMPLE_RATE_MINIMUM || m_sample_rate < SAMPLE_RATE_MINIMUM)
+ resample = false;
+
+ // note the time and period of the current buffer (end_time is AFTER the final sample)
+ attotime prevperiod = sample_period();
+ attotime prevend = end_time();
+
+ // compute the time and period of the new buffer
+ attotime newperiod = attotime(0, (ATTOSECONDS_PER_SECOND + rate - 1) / rate);
+ attotime newend = attotime(prevend.seconds(), (prevend.attoseconds() / newperiod.attoseconds()) * newperiod.attoseconds());
+
+ // buffer a short runway of previous samples; in order to support smooth
+ // sample rate changes (needed by, e.g., Q*Bert's Votrax), we buffer a few
+ // samples at the previous rate, and then reconstitute them resampled
+ // (via simple point sampling) at the new rate. The litmus test is the
+ // voice when jumping off the edge in Q*Bert; without this extra effort
+ // it is crackly and/or glitchy at times
+ sample_t buffer[64];
+ int buffered_samples = std::min(m_sample_rate, std::min(rate, u32(ARRAY_LENGTH(buffer))));
+
+ // if the new rate is lower, downsample into our holding buffer;
+ // otherwise just copy into our holding buffer for later upsampling
+ bool new_rate_higher = (rate > m_sample_rate);
+ if (resample)
{
- m_output[outputnum].m_stream = this;
- m_device.machine().save().save_item(&m_device, "stream", state_tag.c_str(), outputnum, NAME(m_output[outputnum].m_gain));
+ if (!new_rate_higher)
+ backfill_downsample(&buffer[0], buffered_samples, newend, newperiod);
+ else
+ {
+ u32 end = m_end_sample;
+ for (int index = 0; index < buffered_samples; index++)
+ {
+ end = prev_index(end);
+ buffer[index] = get(end);
+ }
+ }
}
- // Mark synchronous streams as such
- m_synchronous = m_sample_rate == STREAM_SYNC;
- if (m_synchronous)
+ // ensure our buffer is large enough to hold a full second at the new rate
+ if (m_buffer.size() < rate)
+ m_buffer.resize(rate);
+
+ // set the new rate
+ m_sample_rate = rate;
+ m_sample_attos = newperiod.attoseconds();
+
+ // compute the new end sample index based on the buffer time
+ m_end_sample = time_to_buffer_index(prevend, false, true);
+
+ // if the new rate is higher, upsample from our temporary buffer;
+ // otherwise just copy our previously-downsampled data
+ if (resample)
{
- m_sample_rate = 0;
- m_sync_timer = m_device.machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(sound_stream::sync_update), this));
- }
- else
- m_sync_timer = nullptr;
+#if (SOUND_DEBUG)
+ // for aggressive debugging, fill the buffer with NANs to catch anyone
+ // reading beyond what we resample below
+ fill(NAN);
+#endif
- // force an update to the sample rates; this will cause everything to be recomputed
- // and will generate the initial resample buffers for our inputs
- recompute_sample_rate_data();
+ if (new_rate_higher)
+ backfill_upsample(&buffer[0], buffered_samples, prevend, prevperiod);
+ else
+ {
+ u32 end = m_end_sample;
+ for (int index = 0; index < buffered_samples; index++)
+ {
+ end = prev_index(end);
+ put(end, buffer[index]);
+ }
+ }
+ }
- // set up the initial output buffer positions now that we have data
- m_output_base_sampindex = -m_max_samples_per_update;
+ // if not resampling, clear the buffer
+ else
+ fill(0);
}
//-------------------------------------------------
-// sample_time - return the emulation time of the
-// next sample to be generated on the stream
+// open_wav - open a WAV file for logging purposes
//-------------------------------------------------
-attotime sound_stream::sample_time() const
+#if (SOUND_DEBUG)
+void stream_buffer::open_wav(char const *filename)
{
- return attotime(m_device.machine().sound().last_update().seconds(), 0) + attotime(0, m_output_sampindex * m_attoseconds_per_sample);
+ // always open at 48k so that sound programs can handle it
+ // re-sample as needed
+ m_wav_file = wav_open(filename, 48000, 1);
}
+#endif
//-------------------------------------------------
-// user_gain - return the user-controllable gain
-// on a given stream's input
+// flush_wav - flush data to the WAV file
//-------------------------------------------------
-float sound_stream::user_gain(int inputnum) const
+#if (SOUND_DEBUG)
+void stream_buffer::flush_wav()
{
- assert(inputnum >= 0 && inputnum < m_input.size());
- return float(m_input[inputnum].m_user_gain) / 256.0f;
+ // skip if no file
+ if (m_wav_file == nullptr)
+ return;
+
+ // grab a view of the data from the last-written point
+ read_stream_view view(this, m_last_written, m_end_sample, 1.0f);
+ m_last_written = m_end_sample;
+
+ // iterate over chunks for conversion
+ s16 buffer[1024];
+ for (int samplebase = 0; samplebase < view.samples(); samplebase += ARRAY_LENGTH(buffer))
+ {
+ // clamp to the buffer size
+ int cursamples = view.samples() - samplebase;
+ if (cursamples > ARRAY_LENGTH(buffer))
+ cursamples = ARRAY_LENGTH(buffer);
+
+ // convert and fill
+ for (int sampindex = 0; sampindex < cursamples; sampindex++)
+ buffer[sampindex] = s16(view.get(samplebase + sampindex) * 32768.0);
+
+ // write to the WAV
+ wav_add_data_16(m_wav_file, buffer, cursamples);
+ }
}
+#endif
//-------------------------------------------------
-// input_gain - return the input gain on a
-// given stream's input
+// close_wav - close the logging WAV file
//-------------------------------------------------
-float sound_stream::input_gain(int inputnum) const
+#if (SOUND_DEBUG)
+void stream_buffer::close_wav()
{
- assert(inputnum >= 0 && inputnum < m_input.size());
- return float(m_input[inputnum].m_gain) / 256.0f;
+ if (m_wav_file != nullptr)
+ wav_close(m_wav_file);
+ m_wav_file = nullptr;
}
+#endif
//-------------------------------------------------
-// output_gain - return the output gain on a
-// given stream's output
+// index_time - return the attotime of a given
+// index within the buffer
//-------------------------------------------------
-float sound_stream::output_gain(int outputnum) const
+attotime stream_buffer::index_time(s32 index) const
{
- assert(outputnum >= 0 && outputnum < m_output.size());
- return float(m_output[outputnum].m_gain) / 256.0f;
+ index = clamp_index(index);
+ return attotime(m_end_second - ((index > m_end_sample) ? 1 : 0), index * m_sample_attos);
}
//-------------------------------------------------
-// input_name - return the original input gain
-// on a given stream's input
+// time_to_buffer_index - given an attotime,
+// return the buffer index corresponding to it
//-------------------------------------------------
-std::string sound_stream::input_name(int inputnum) const
+u32 stream_buffer::time_to_buffer_index(attotime time, bool round_up, bool allow_expansion)
{
- std::ostringstream str;
-
- // start with our device name and tag
- assert(inputnum >= 0 && inputnum < m_input.size());
- util::stream_format(str, "%s '%s': ", m_device.name(), m_device.tag());
+ // compute the sample index within the second
+ int sample = (time.attoseconds() + (round_up ? (m_sample_attos - 1) : 0)) / m_sample_attos;
+ sound_assert(sample >= 0 && sample <= size());
- // if we have a source, indicate where the sound comes from by device name and tag
- if (m_input[inputnum].m_source != nullptr && m_input[inputnum].m_source->m_stream != nullptr)
+ // if the time is past the current end, make it the end
+ if (time.seconds() > m_end_second || (time.seconds() == m_end_second && sample > m_end_sample))
{
- device_t &source = m_input[inputnum].m_source->m_stream->device();
- util::stream_format(str, "%s '%s'", source.name(), source.tag());
+ sound_assert(allow_expansion);
+
+ m_end_sample = sample;
+ m_end_second = time.m_seconds;
- // get the sound interface; if there is more than 1 output we need to figure out which one
- device_sound_interface *sound;
- if (source.interface(sound) && sound->outputs() > 1)
+ // due to round_up, we could tweak over the line into the next second
+ if (sample >= size())
{
- // iterate over outputs until we find the stream that matches our source
- // then look for a match on the output number
- sound_stream *outstream;
- int streamoutputnum;
- for (int outputnum = 0; (outstream = sound->output_to_stream_output(outputnum, streamoutputnum)) != nullptr; outputnum++)
- if (outstream == m_input[inputnum].m_source->m_stream && m_input[inputnum].m_source == &outstream->m_output[streamoutputnum])
- {
- util::stream_format(str, " Ch.%d", outputnum);
- break;
- }
+ m_end_sample -= size();
+ m_end_second++;
}
}
- return str.str();
+
+ // if the time is before the start, fail
+ if (time.seconds() + 1 < m_end_second || (time.seconds() + 1 == m_end_second && sample < m_end_sample))
+ throw emu_fatalerror("Attempt to create an out-of-bounds view");
+
+ return clamp_index(sample);
}
//-------------------------------------------------
-// input_source_device - return the device
-// attached as a given input's source
+// backfill_downsample - this is called BEFORE
+// the sample rate change to downsample from the
+// end of the current buffer into a temporary
+// holding location
//-------------------------------------------------
-device_t *sound_stream::input_source_device(int inputnum) const
+void stream_buffer::backfill_downsample(sample_t *dest, int samples, attotime newend, attotime newperiod)
{
- assert(inputnum >= 0 && inputnum < m_input.size());
- return (m_input[inputnum].m_source != nullptr) ? &m_input[inputnum].m_source->m_stream->device() : nullptr;
+ // compute the time of the first sample to be backfilled; start one period before
+ attotime time = newend - newperiod;
+
+ // loop until we run out of buffered data
+ int dstindex;
+ for (dstindex = 0; dstindex < samples && time.seconds() >= 0; dstindex++)
+ {
+ u32 srcindex = time_to_buffer_index(time, false);
+#if (SOUND_DEBUG)
+ // multiple resamples can occur before clearing out old NaNs so
+ // neuter them for this specific case
+ if (std::isnan(m_buffer[srcindex]))
+ dest[dstindex] = 0;
+ else
+#endif
+ dest[dstindex] = get(srcindex);
+ time -= newperiod;
+ }
+ for ( ; dstindex < samples; dstindex++)
+ dest[dstindex] = 0;
}
//-------------------------------------------------
-// input_source_device - return the output number
-// attached as a given input's source
+// backfill_upsample - this is called AFTER the
+// sample rate change to take a copied buffer
+// of samples at the old rate and upsample them
+// to the new (current) rate
//-------------------------------------------------
-int sound_stream::input_source_outputnum(int inputnum) const
+void stream_buffer::backfill_upsample(sample_t const *src, int samples, attotime prevend, attotime prevperiod)
{
- assert(inputnum >= 0 && inputnum < m_input.size());
- return (m_input[inputnum].m_source != nullptr) ? (m_input[inputnum].m_source - &m_input[inputnum].m_source->m_stream->m_output[0]) : -1;
+ // compute the time of the first sample to be backfilled; start one period before
+ attotime time = end_time() - sample_period();
+
+ // also adjust the buffered sample end time to point to the sample time of the
+ // final sample captured
+ prevend -= prevperiod;
+
+ // loop until we run out of buffered data
+ u32 end = m_end_sample;
+ int srcindex = 0;
+ while (1)
+ {
+ // if our backfill time is before the current buffered sample time,
+ // back up until we have a sample that covers this time
+ while (time < prevend && srcindex < samples)
+ {
+ prevend -= prevperiod;
+ srcindex++;
+ }
+
+ // stop when we run out of source
+ if (srcindex >= samples)
+ break;
+
+ // write this sample at the pevious position
+ end = prev_index(end);
+ put(end, src[srcindex]);
+
+ // back up to the next sample time
+ time -= sample_period();
+ }
}
+
+//**************************************************************************
+// SOUND STREAM OUTPUT
+//**************************************************************************
+
//-------------------------------------------------
-// set_input - configure a stream's input
+// sound_stream_output - constructor
//-------------------------------------------------
-void sound_stream::set_input(int index, sound_stream *input_stream, int output_index, float gain)
+sound_stream_output::sound_stream_output() :
+ m_stream(nullptr),
+ m_index(0),
+ m_gain(1.0)
{
- VPRINTF(("stream_set_input(%p, '%s', %d, %p, %d, %f)\n", (void *)this, m_device.tag(),
- index, (void *)input_stream, output_index, (double) gain));
+}
- // make sure it's a valid input
- if (index >= m_input.size())
- fatalerror("stream_set_input attempted to configure nonexistent input %d (%d max)\n", index, int(m_input.size()));
- // make sure it's a valid output
- if (input_stream != nullptr && output_index >= input_stream->m_output.size())
- fatalerror("stream_set_input attempted to use a nonexistent output %d (%d max)\n", output_index, int(m_output.size()));
+//-------------------------------------------------
+// init - initialization
+//-------------------------------------------------
- // if this input is already wired, update the dependent info
- stream_input &input = m_input[index];
- if (input.m_source != nullptr)
- input.m_source->m_dependents--;
+void sound_stream_output::init(sound_stream &stream, u32 index, char const *tag)
+{
+ // set the passed-in data
+ m_stream = &stream;
+ m_index = index;
+
+ // save our state
+ auto &save = stream.device().machine().save();
+ save.save_item(&stream.device(), "stream.output", tag, index, NAME(m_gain));
+
+#if (LOG_OUTPUT_WAV)
+ std::string filename = stream.device().machine().basename();
+ filename += stream.device().tag();
+ for (int index = 0; index < filename.size(); index++)
+ if (filename[index] == ':')
+ filename[index] = '_';
+ if (dynamic_cast<default_resampler_stream *>(&stream) != nullptr)
+ filename += "_resampler";
+ filename += "_OUT_";
+ char buf[10];
+ sprintf(buf, "%d", index);
+ filename += buf;
+ filename += ".wav";
+ m_buffer.open_wav(filename.c_str());
+#endif
+}
- // wire it up
- input.m_source = (input_stream != nullptr) ? &input_stream->m_output[output_index] : nullptr;
- input.m_gain = int(0x100 * gain);
- input.m_user_gain = 0x100;
- // update the dependent info
- if (input.m_source != nullptr)
- input.m_source->m_dependents++;
+//-------------------------------------------------
+// name - return the friendly name of this output
+//-------------------------------------------------
- // update sample rates now that we know the input
- recompute_sample_rate_data();
+std::string sound_stream_output::name() const
+{
+ // start with our owning stream's name
+ std::ostringstream str;
+ util::stream_format(str, "%s Ch.%d", m_stream->name(), m_stream->output_base() + m_index);
+ return str.str();
}
//-------------------------------------------------
-// update - force a stream to update to
-// the current emulated time
+// optimize_resampler - optimize resamplers by
+// either returning the native rate or another
+// input's resampler if they can be reused
//-------------------------------------------------
-void sound_stream::update()
+sound_stream_output &sound_stream_output::optimize_resampler(sound_stream_output *input_resampler)
{
- if (!m_attoseconds_per_sample)
- return;
+ // if no resampler, or if the resampler rate matches our rate, return ourself
+ if (input_resampler == nullptr || buffer_sample_rate() == input_resampler->buffer_sample_rate())
+ return *this;
+
+ // scan our list of resamplers to see if there's another match
+ for (auto &resampler : m_resampler_list)
+ if (resampler->buffer_sample_rate() == input_resampler->buffer_sample_rate())
+ return *resampler;
+
+ // add the input to our list and return the one we were given back
+ m_resampler_list.push_back(input_resampler);
+ return *input_resampler;
+}
- // determine the number of samples since the start of this second
- attotime time = m_device.machine().time();
- s32 update_sampindex = s32(time.attoseconds() / m_attoseconds_per_sample);
- // if we're ahead of the last update, then adjust upwards
- attotime last_update = m_device.machine().sound().last_update();
- if (time.seconds() > last_update.seconds())
- {
- assert(time.seconds() == last_update.seconds() + 1);
- update_sampindex += m_sample_rate;
- }
-
- // if we're behind the last update, then adjust downwards
- if (time.seconds() < last_update.seconds())
- {
- assert(time.seconds() == last_update.seconds() - 1);
- update_sampindex -= m_sample_rate;
- }
- if (update_sampindex <= m_output_sampindex)
- return;
+//**************************************************************************
+// SOUND STREAM INPUT
+//**************************************************************************
- // generate samples to get us up to the appropriate time
- g_profiler.start(PROFILER_SOUND);
- assert(m_output_sampindex - m_output_base_sampindex >= 0);
- assert(update_sampindex - m_output_base_sampindex <= m_output_bufalloc);
- generate_samples(update_sampindex - m_output_sampindex);
- g_profiler.stop();
+//-------------------------------------------------
+// sound_stream_input - constructor
+//-------------------------------------------------
- // remember this info for next time
- m_output_sampindex = update_sampindex;
+sound_stream_input::sound_stream_input() :
+ m_owner(nullptr),
+ m_native_source(nullptr),
+ m_resampler_source(nullptr),
+ m_index(0),
+ m_gain(1.0),
+ m_user_gain(1.0)
+{
}
-void sound_stream::sync_update(void *, s32)
+//-------------------------------------------------
+// init - initialization
+//-------------------------------------------------
+
+void sound_stream_input::init(sound_stream &stream, u32 index, char const *tag, sound_stream_output *resampler)
{
- update();
- attotime time = m_device.machine().time();
- attoseconds_t next_edge = m_attoseconds_per_sample - (time.attoseconds() % m_attoseconds_per_sample);
- m_sync_timer->adjust(attotime(0, next_edge));
+ // set the passed-in values
+ m_owner = &stream;
+ m_index = index;
+ m_resampler_source = resampler;
+
+ // save our state
+ auto &save = stream.device().machine().save();
+ save.save_item(&stream.device(), "stream.input", tag, index, NAME(m_gain));
+ save.save_item(&stream.device(), "stream.input", tag, index, NAME(m_user_gain));
}
//-------------------------------------------------
-// output_since_last_update - return a pointer to
-// the output buffer and the number of samples
-// since the last global update
+// name - return the friendly name of this input
//-------------------------------------------------
-const stream_sample_t *sound_stream::output_since_last_update(int outputnum, int &numsamples)
+std::string sound_stream_input::name() const
{
- // force an update on the stream
- update();
+ // start with our owning stream's name
+ std::ostringstream str;
+ util::stream_format(str, "%s", m_owner->name());
- // compute the number of samples and a pointer to the output buffer
- numsamples = m_output_sampindex - m_output_update_sampindex;
- return &m_output[outputnum].m_buffer[m_output_update_sampindex - m_output_base_sampindex];
+ // if we have a source, indicate where the sound comes from by device name and tag
+ if (valid())
+ util::stream_format(str, " <- %s", m_native_source->name());
+ return str.str();
}
//-------------------------------------------------
-// set_sample_rate - set the sample rate on a
-// given stream
+// set_source - wire up the output source for
+// our consumption
//-------------------------------------------------
-void sound_stream::set_sample_rate(int new_rate)
+void sound_stream_input::set_source(sound_stream_output *source)
{
- // we will update this on the next global update
- if (new_rate != sample_rate())
- m_new_sample_rate = new_rate;
+ m_native_source = source;
+ if (m_resampler_source != nullptr)
+ m_resampler_source->stream().set_input(0, &source->stream(), source->index());
}
//-------------------------------------------------
-// set_user_gain - set the user-controllable gain
-// on a given stream's input
+// update - update our source's stream to the
+// current end time and return a view to its
+// contents
//-------------------------------------------------
-void sound_stream::set_user_gain(int inputnum, float gain)
+read_stream_view sound_stream_input::update(attotime start, attotime end)
{
- update();
- assert(inputnum >= 0 && inputnum < m_input.size());
- m_input[inputnum].m_user_gain = int(0x100 * gain);
+ // shouldn't get here unless valid
+ sound_assert(valid());
+
+ // pick an optimized resampler
+ sound_stream_output &source = m_native_source->optimize_resampler(m_resampler_source);
+
+ // if not using our own resampler, keep it up to date in case we need to invoke it later
+ if (m_resampler_source != nullptr && &source != m_resampler_source)
+ m_resampler_source->set_end_time(end);
+
+ // update the source, returning a view of the needed output over the start and end times
+ return source.stream().update_view(start, end, source.index()).apply_gain(m_gain * m_user_gain * m_native_source->gain());
}
//-------------------------------------------------
-// set_input_gain - set the input gain on a
-// given stream's input
+// apply_sample_rate_changes - tell our sources
+// to apply any sample rate changes, informing
+// them of our current rate
//-------------------------------------------------
-void sound_stream::set_input_gain(int inputnum, float gain)
+void sound_stream_input::apply_sample_rate_changes(u32 updatenum, u32 downstream_rate)
{
- update();
- assert(inputnum >= 0 && inputnum < m_input.size());
- m_input[inputnum].m_gain = int(0x100 * gain);
+ // shouldn't get here unless valid
+ sound_assert(valid());
+
+ // if we have a resampler, tell it (and it will tell the native source)
+ if (m_resampler_source != nullptr)
+ m_resampler_source->stream().apply_sample_rate_changes(updatenum, downstream_rate);
+
+ // otherwise, just tell the native source directly
+ else
+ m_native_source->stream().apply_sample_rate_changes(updatenum, downstream_rate);
}
+
+//**************************************************************************
+// SOUND STREAM
+//**************************************************************************
+
//-------------------------------------------------
-// set_output_gain - set the output gain on a
-// given stream's output
+// sound_stream - private common constructor
+//-------------------------------------------------
+
+sound_stream::sound_stream(device_t &device, u32 inputs, u32 outputs, u32 output_base, u32 sample_rate, sound_stream_flags flags) :
+ m_device(device),
+ m_next(nullptr),
+ m_sample_rate((sample_rate < SAMPLE_RATE_MINIMUM) ? (SAMPLE_RATE_MINIMUM - 1) : (sample_rate < SAMPLE_RATE_OUTPUT_ADAPTIVE) ? sample_rate : 48000),
+ m_pending_sample_rate(SAMPLE_RATE_INVALID),
+ m_last_sample_rate_update(0),
+ m_input_adaptive(sample_rate == SAMPLE_RATE_INPUT_ADAPTIVE),
+ m_output_adaptive(sample_rate == SAMPLE_RATE_OUTPUT_ADAPTIVE),
+ m_synchronous((flags & STREAM_SYNCHRONOUS) != 0),
+ m_resampling_disabled((flags & STREAM_DISABLE_INPUT_RESAMPLING) != 0),
+ m_sync_timer(nullptr),
+ m_input(inputs),
+ m_input_array(inputs),
+ m_input_view(inputs),
+ m_empty_buffer(100),
+ m_output_base(output_base),
+ m_output(outputs),
+ m_output_array(outputs),
+ m_output_view(outputs)
+{
+ sound_assert(outputs > 0);
+
+ // create a name
+ m_name = m_device.name();
+ m_name += " '";
+ m_name += m_device.tag();
+ m_name += "'";
+
+ // create a unique tag for saving
+ std::string state_tag = string_format("%d", m_device.machine().sound().unique_id());
+ auto &save = m_device.machine().save();
+ save.register_postload(save_prepost_delegate(FUNC(sound_stream::postload), this));
+
+ // initialize all inputs
+ for (unsigned int inputnum = 0; inputnum < m_input.size(); inputnum++)
+ {
+ // allocate a resampler stream if needed, and get a pointer to its output
+ sound_stream_output *resampler = nullptr;
+ if (!m_resampling_disabled)
+ {
+ m_resampler_list.push_back(std::make_unique<default_resampler_stream>(m_device));
+ resampler = &m_resampler_list.back()->m_output[0];
+ }
+
+ // add the new input
+ m_input[inputnum].init(*this, inputnum, state_tag.c_str(), resampler);
+ }
+
+ // initialize all outputs
+ for (unsigned int outputnum = 0; outputnum < m_output.size(); outputnum++)
+ m_output[outputnum].init(*this, outputnum, state_tag.c_str());
+
+ // create an update timer for synchronous streams
+ if (synchronous())
+ m_sync_timer = m_device.machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(sound_stream::sync_update), this));
+
+ // force an update to the sample rates
+ sample_rate_changed();
+}
+
+
+//-------------------------------------------------
+// sound_stream - constructor with old-style
+// callback
//-------------------------------------------------
-void sound_stream::set_output_gain(int outputnum, float gain)
+sound_stream::sound_stream(device_t &device, u32 inputs, u32 outputs, u32 output_base, u32 sample_rate, stream_update_legacy_delegate callback, sound_stream_flags flags) :
+ sound_stream(device, inputs, outputs, output_base, sample_rate, flags)
{
- update();
- assert(outputnum >= 0 && outputnum < m_output.size());
- m_output[outputnum].m_gain = int(0x100 * gain);
+ m_callback = std::move(callback);
+ m_callback_ex = stream_update_delegate(&sound_stream::stream_update_legacy, this);
}
//-------------------------------------------------
-// update_with_accounting - do a regular update,
-// but also do periodic accounting
+// sound_stream - constructor with new-style
+// callback
//-------------------------------------------------
-void sound_stream::update_with_accounting(bool second_tick)
+sound_stream::sound_stream(device_t &device, u32 inputs, u32 outputs, u32 output_base, u32 sample_rate, stream_update_delegate callback, sound_stream_flags flags) :
+ sound_stream(device, inputs, outputs, output_base, sample_rate, flags)
{
- // do the normal update
- update();
+ m_callback_ex = std::move(callback);
+}
- // if we've ticked over another second, adjust all the counters that are relative to
- // the current second
- s32 output_bufindex = m_output_sampindex - m_output_base_sampindex;
- if (second_tick)
- {
- m_output_sampindex -= m_sample_rate;
- m_output_base_sampindex -= m_sample_rate;
- }
- // note our current output sample
- m_output_update_sampindex = m_output_sampindex;
+//-------------------------------------------------
+// ~sound_stream - destructor
+//-------------------------------------------------
+
+sound_stream::~sound_stream()
+{
+}
- // if we don't have enough output buffer space to hold two updates' worth of samples,
- // we need to shuffle things down
- if (m_output_bufalloc - output_bufindex < 2 * m_max_samples_per_update)
- {
- s32 samples_to_lose = output_bufindex - m_max_samples_per_update;
- if (samples_to_lose > 0)
- {
- // if we have samples to move, do so for each output
- if (output_bufindex > 0)
- for (auto &output : m_output)
- {
- memmove(&output.m_buffer[0], &output.m_buffer[samples_to_lose], sizeof(output.m_buffer[0]) * (output_bufindex - samples_to_lose));
- }
- // update the base position
- m_output_base_sampindex += samples_to_lose;
- }
- }
+//-------------------------------------------------
+// set_sample_rate - set the sample rate on a
+// given stream
+//-------------------------------------------------
+
+void sound_stream::set_sample_rate(u32 new_rate)
+{
+ // we will update this on the next global update
+ if (new_rate != sample_rate())
+ m_pending_sample_rate = new_rate;
}
//-------------------------------------------------
-// apply_sample_rate_changes - if there is a
-// pending sample rate change, apply it now
+// set_input - configure a stream's input
//-------------------------------------------------
-void sound_stream::apply_sample_rate_changes()
+void sound_stream::set_input(int index, sound_stream *input_stream, int output_index, float gain)
{
- // skip if nothing to do
- if (m_new_sample_rate == 0xffffffff)
- return;
+ VPRINTF(("stream_set_input(%p, '%s', %d, %p, %d, %f)\n", (void *)this, m_device.tag(),
+ index, (void *)input_stream, output_index, (double) gain));
- // update to the new rate and remember the old rate
- u32 old_rate = m_sample_rate;
- m_sample_rate = m_new_sample_rate;
- m_new_sample_rate = 0xffffffff;
+ // make sure it's a valid input
+ if (index >= m_input.size())
+ fatalerror("stream_set_input attempted to configure nonexistent input %d (%d max)\n", index, int(m_input.size()));
- // recompute all the data
- recompute_sample_rate_data();
+ // make sure it's a valid output
+ if (input_stream != nullptr && output_index >= input_stream->m_output.size())
+ fatalerror("stream_set_input attempted to use a nonexistent output %d (%d max)\n", output_index, int(m_output.size()));
- // reset our sample indexes to the current time
- if (old_rate)
- {
- m_output_sampindex = s64(m_output_sampindex) * s64(m_sample_rate) / old_rate;
- m_output_update_sampindex = s64(m_output_update_sampindex) * s64(m_sample_rate) / old_rate;
- }
- else
- {
- m_output_sampindex = m_attoseconds_per_sample ? m_device.machine().sound().last_update().attoseconds() / m_attoseconds_per_sample : 0;
- m_output_update_sampindex = m_output_sampindex;
- }
+ // wire it up
+ m_input[index].set_source((input_stream != nullptr) ? &input_stream->m_output[output_index] : nullptr);
+ m_input[index].set_gain(gain);
+
+ // update sample rates now that we know the input
+ sample_rate_changed();
+}
- m_output_base_sampindex = m_output_sampindex - m_max_samples_per_update;
- // clear out the buffer
- if (m_max_samples_per_update)
- for (auto &elem : m_output)
- std::fill_n(&elem.m_buffer[0], m_max_samples_per_update, 0);
+//-------------------------------------------------
+// update - force a stream to update to
+// the current emulated time
+//-------------------------------------------------
+
+void sound_stream::update()
+{
+ // ignore any update requests if we're already up to date
+ attotime start = m_output[0].end_time();
+ attotime end = m_device.machine().time();
+ if (start >= end)
+ return;
+
+ // regular update then
+ update_view(start, end);
}
//-------------------------------------------------
-// recompute_sample_rate_data - recompute sample
-// rate data, and all streams that are affected
-// by this stream
+// update_view - force a stream to update to
+// the current emulated time and return a view
+// to the generated samples from the given
+// output number
//-------------------------------------------------
-void sound_stream::recompute_sample_rate_data()
+read_stream_view sound_stream::update_view(attotime start, attotime end, u32 outputnum)
{
- if (m_synchronous)
+ sound_assert(start <= end);
+ sound_assert(outputnum < m_output.size());
+
+ // clean up parameters for when the asserts go away
+ if (outputnum >= m_output.size())
+ outputnum = 0;
+ if (start > end)
+ start = end;
+
+ g_profiler.start(PROFILER_SOUND);
+
+ // reposition our start to coincide with the current buffer end
+ attotime update_start = m_output[outputnum].end_time();
+ if (update_start <= end)
{
- m_sample_rate = 0;
- // When synchronous, pick the sample rate for the inputs, if any
- for (auto &input : m_input)
+ // create views for all the outputs
+ for (unsigned int outindex = 0; outindex < m_output.size(); outindex++)
+ m_output_view[outindex] = m_output[outindex].view(update_start, end);
+
+ // skip if nothing to do
+ u32 samples = m_output_view[0].samples();
+ sound_assert(samples >= 0);
+ if (samples != 0 && m_sample_rate >= SAMPLE_RATE_MINIMUM)
{
- if (input.m_source != nullptr)
+ sound_assert(!synchronous() || samples == 1);
+
+ // ensure all input streams are up to date, and create views for them as well
+ for (unsigned int inputnum = 0; inputnum < m_input.size(); inputnum++)
{
- if (!m_sample_rate)
- m_sample_rate = input.m_source->m_stream->m_sample_rate;
- else if (m_sample_rate != input.m_source->m_stream->m_sample_rate)
- throw emu_fatalerror("Incompatible sample rates as input of a synchronous stream: %d and %d\n", m_sample_rate, input.m_source->m_stream->m_sample_rate);
+ if (m_input[inputnum].valid())
+ m_input_view[inputnum] = m_input[inputnum].update(update_start, end);
+ else
+ m_input_view[inputnum] = empty_view(update_start, end);
+ sound_assert(m_resampling_disabled || m_input_view[inputnum].sample_rate() == m_sample_rate);
}
+
+#if (SOUND_DEBUG)
+ // clear each output view to NANs before we call the callback
+ for (unsigned int outindex = 0; outindex < m_output.size(); outindex++)
+ m_output_view[outindex].fill(NAN);
+#endif
+
+ // if we have an extended callback, that's all we need
+ m_callback_ex(*this, m_input_view, m_output_view);
+
+#if (SOUND_DEBUG)
+ // make sure everything was overwritten
+ for (unsigned int outindex = 0; outindex < m_output.size(); outindex++)
+ for (int sampindex = 0; sampindex < m_output_view[outindex].samples(); sampindex++)
+ m_output_view[outindex].get(sampindex);
+
+ for (unsigned int outindex = 0; outindex < m_output.size(); outindex++)
+ m_output[outindex].m_buffer.flush_wav();
+#endif
}
}
+ g_profiler.stop();
+
+ // return the requested view
+ return read_stream_view(m_output_view[outputnum], start);
+}
- // recompute the timing parameters
- attoseconds_t update_attoseconds = m_device.machine().sound().update_attoseconds();
+//-------------------------------------------------
+// apply_sample_rate_changes - if there is a
+// pending sample rate change, apply it now
+//-------------------------------------------------
- if (m_sample_rate)
+void sound_stream::apply_sample_rate_changes(u32 updatenum, u32 downstream_rate)
+{
+ // grab the new rate and invalidate
+ u32 new_rate = (m_pending_sample_rate != SAMPLE_RATE_INVALID) ? m_pending_sample_rate : m_sample_rate;
+ m_pending_sample_rate = SAMPLE_RATE_INVALID;
+
+ // clamp to the minimum - 1 (anything below minimum means "off" and
+ // will not call the sound callback at all)
+ if (new_rate < SAMPLE_RATE_MINIMUM)
+ new_rate = SAMPLE_RATE_MINIMUM - 1;
+
+ // if we're input adaptive, override with the rate of our input
+ if (input_adaptive() && m_input.size() > 0 && m_input[0].valid())
+ new_rate = m_input[0].source().stream().sample_rate();
+
+ // if we're output adaptive, override with the rate of our output
+ if (output_adaptive())
{
- m_attoseconds_per_sample = ATTOSECONDS_PER_SECOND / m_sample_rate;
- m_max_samples_per_update = (update_attoseconds + m_attoseconds_per_sample - 1) / m_attoseconds_per_sample;
+ if (m_last_sample_rate_update == updatenum)
+ sound_assert(new_rate == m_sample_rate);
+ else
+ m_last_sample_rate_update = updatenum;
+ new_rate = downstream_rate;
}
- else
+
+ // if something is different, process the change
+ if (new_rate != SAMPLE_RATE_INVALID && new_rate != m_sample_rate)
{
- m_attoseconds_per_sample = 0;
- m_max_samples_per_update = 0;
+ // update to the new rate and notify everyone
+#if (SOUND_DEBUG)
+ printf("stream %s changing rates %d -> %d\n", name().c_str(), m_sample_rate, new_rate);
+#endif
+ m_sample_rate = new_rate;
+ sample_rate_changed();
}
- // update resample and output buffer sizes
- allocate_resample_buffers();
- allocate_output_buffers();
-
- // iterate over each input
+ // now call through our inputs and apply the rate change there
for (auto &input : m_input)
- {
- // if we have a source, see if its sample rate changed
+ if (input.valid())
+ input.apply_sample_rate_changes(updatenum, m_sample_rate);
+}
- if (input.m_source != nullptr && input.m_source->m_stream->m_sample_rate)
- {
- // okay, we have a new sample rate; recompute the latency to be the maximum
- // sample period between us and our input
- attoseconds_t new_attosecs_per_sample = ATTOSECONDS_PER_SECOND / input.m_source->m_stream->m_sample_rate;
- attoseconds_t latency = std::max(new_attosecs_per_sample, m_attoseconds_per_sample);
-
- // if the input stream's sample rate is lower, we will use linear interpolation
- // this requires an extra sample from the source
- if (input.m_source->m_stream->m_sample_rate < m_sample_rate)
- latency += new_attosecs_per_sample;
-
- // if our sample rates match exactly, we don't need any latency
- else if (input.m_source->m_stream->m_sample_rate == m_sample_rate)
- latency = 0;
-
- // we generally don't want to tweak the latency, so we just keep the greatest
- // one we've computed thus far
- input.m_latency_attoseconds = std::max(input.m_latency_attoseconds, latency);
- assert(input.m_latency_attoseconds < update_attoseconds);
- }
- else
- {
- input.m_latency_attoseconds = 0;
- }
- }
- // If synchronous, prime the timer
- if (m_synchronous)
- {
- attotime time = m_device.machine().time();
- if (m_attoseconds_per_sample)
+//-------------------------------------------------
+// print_graph_recursive - helper for debugging;
+// prints info on this stream and then recursively
+// prints info on all inputs
+//-------------------------------------------------
+
+#if (SOUND_DEBUG)
+void sound_stream::print_graph_recursive(int indent, int index)
+{
+ osd_printf_info("%c %*s%s Ch.%d @ %d\n", m_callback.isnull() ? ' ' : '!', indent, "", name().c_str(), index + m_output_base, sample_rate());
+ for (int index = 0; index < m_input.size(); index++)
+ if (m_input[index].valid())
{
- attoseconds_t next_edge = m_attoseconds_per_sample - (time.attoseconds() % m_attoseconds_per_sample);
- m_sync_timer->adjust(attotime(0, next_edge));
+ if (m_input[index].m_resampler_source != nullptr)
+ m_input[index].m_resampler_source->stream().print_graph_recursive(indent + 2, m_input[index].m_resampler_source->index());
+ else
+ m_input[index].m_native_source->stream().print_graph_recursive(indent + 2, m_input[index].m_native_source->index());
}
- else
- m_sync_timer->adjust(attotime::never);
- }
}
+#endif
//-------------------------------------------------
-// allocate_resample_buffers - recompute the
-// resample buffer sizes and expand if necessary
+// sample_rate_changed - recompute sample
+// rate data, and all streams that are affected
+// by this stream
//-------------------------------------------------
-void sound_stream::allocate_resample_buffers()
+void sound_stream::sample_rate_changed()
{
- // compute the target number of samples
- s32 bufsize = 2 * m_max_samples_per_update;
+ // if invalid, just punt
+ if (m_sample_rate == SAMPLE_RATE_INVALID)
+ return;
- // if we don't have enough room, allocate more
- if (m_resample_bufalloc < bufsize)
- {
- // this becomes the new allocation size
- m_resample_bufalloc = bufsize;
+ // update all output buffers
+ for (auto &output : m_output)
+ output.sample_rate_changed(m_sample_rate);
- // iterate over outputs and realloc their buffers
- for (auto &elem : m_input)
- elem.m_resample.resize(m_resample_bufalloc, 0);
- }
+ // if synchronous, prime the timer
+ if (synchronous())
+ reprime_sync_timer();
}
//-------------------------------------------------
-// allocate_output_buffers - recompute the
-// output buffer sizes and expand if necessary
+// postload - save/restore callback
//-------------------------------------------------
-void sound_stream::allocate_output_buffers()
+void sound_stream::postload()
{
- // if we don't have enough room, allocate more
- s32 bufsize = OUTPUT_BUFFER_UPDATES * m_max_samples_per_update;
- if (m_output_bufalloc < bufsize)
- {
- // this becomes the new allocation size
- m_output_bufalloc = bufsize;
+ // set the end time of all of our streams to now
+ for (auto &output : m_output)
+ output.set_end_time(m_device.machine().time());
- // iterate over outputs and realloc their buffers
- for (auto &elem : m_output)
- elem.m_buffer.resize(m_output_bufalloc, 0);
- }
+ // recompute the sample rate information
+ sample_rate_changed();
}
//-------------------------------------------------
-// postload - save/restore callback
+// reprime_sync_timer - set up the next sync
+// timer to go off just a hair after the end of
+// the current sample period
//-------------------------------------------------
-void sound_stream::postload()
+void sound_stream::reprime_sync_timer()
{
- // recompute the same rate information
- recompute_sample_rate_data();
+ attotime curtime = m_device.machine().time();
+ attotime target = m_output[0].end_time() + attotime(0, 1);
+ m_sync_timer->adjust(target - curtime);
+}
- // make sure our output buffers are fully cleared
- for (auto &elem : m_output)
- std::fill_n(&elem.m_buffer[0], m_output_bufalloc, 0);
- // recompute the sample indexes to make sense
- m_output_sampindex = m_attoseconds_per_sample ? m_device.machine().sound().last_update().attoseconds() / m_attoseconds_per_sample : 0;
- m_output_update_sampindex = m_output_sampindex;
- m_output_base_sampindex = m_output_sampindex - m_max_samples_per_update;
+//-------------------------------------------------
+// sync_update - timer callback to handle a
+// synchronous stream
+//-------------------------------------------------
+
+void sound_stream::sync_update(void *, s32)
+{
+ update();
+ reprime_sync_timer();
}
//-------------------------------------------------
-// generate_samples - generate the requested
-// number of samples for a stream, making sure
-// all inputs have the appropriate number of
-// samples generated
+// stream_update_legacy - new-style callback which
+// forwards on to the old-style traditional
+// callback, converting to/from floats
//-------------------------------------------------
-void sound_stream::generate_samples(int samples)
+void sound_stream::stream_update_legacy(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
{
- stream_sample_t **inputs = nullptr;
- stream_sample_t **outputs = nullptr;
-
- VPRINTF(("generate_samples(%p, %d)\n", (void *) this, samples));
- assert(samples > 0);
+ // temporary buffer to hold stream_sample_t inputs and outputs
+ stream_sample_t temp_buffer[1024];
+ int chunksize = ARRAY_LENGTH(temp_buffer) / (inputs.size() + outputs.size());
+ int chunknum = 0;
+
+ // create the arrays to pass to the callback
+ stream_sample_t **inputptr = m_input.empty() ? nullptr : &m_input_array[0];
+ stream_sample_t **outputptr = &m_output_array[0];
+ for (unsigned int inputnum = 0; inputnum < inputs.size(); inputnum++)
+ inputptr[inputnum] = &temp_buffer[chunksize * chunknum++];
+ for (unsigned int outputnum = 0; outputnum < m_output.size(); outputnum++)
+ outputptr[outputnum] = &temp_buffer[chunksize * chunknum++];
- // ensure all inputs are up to date and generate resampled data
- for (unsigned int inputnum = 0; inputnum < m_input.size(); inputnum++)
+ // loop until all chunks done
+ for (int baseindex = 0; baseindex < outputs[0].samples(); baseindex += chunksize)
{
- // update the stream to the current time
- stream_input &input = m_input[inputnum];
- if (input.m_source != nullptr)
- input.m_source->m_stream->update();
+ // determine the number of samples to process this time
+ int cursamples = outputs[0].samples() - baseindex;
+ if (cursamples > chunksize)
+ cursamples = chunksize;
- // generate the resampled data
- m_input_array[inputnum] = generate_resampled_data(input, samples);
- }
+ // copy in the input data
+ for (unsigned int inputnum = 0; inputnum < inputs.size(); inputnum++)
+ {
+ stream_sample_t *dest = inputptr[inputnum];
+ for (int index = 0; index < cursamples; index++)
+ dest[index] = stream_sample_t(inputs[inputnum].get(baseindex + index) * stream_buffer::sample_t(32768.0));
+ }
- if (!m_input.empty())
- {
- inputs = &m_input_array[0];
- }
+ // run the callback
+ m_callback(*this, inputptr, outputptr, cursamples);
- // loop over all outputs and compute the output pointer
- for (unsigned int outputnum = 0; outputnum < m_output.size(); outputnum++)
- {
- stream_output &output = m_output[outputnum];
- m_output_array[outputnum] = &output.m_buffer[m_output_sampindex - m_output_base_sampindex];
+ // copy out the output data
+ for (unsigned int outputnum = 0; outputnum < m_output.size(); outputnum++)
+ {
+ stream_sample_t *src = outputptr[outputnum];
+ for (int index = 0; index < cursamples; index++)
+ outputs[outputnum].put(baseindex + index, stream_buffer::sample_t(src[index]) * stream_buffer::sample_t(1.0 / 32768.0));
+ }
}
+}
- if (!m_output.empty())
- {
- outputs = &m_output_array[0];
- }
- // run the callback
- VPRINTF((" callback(%p, %d)\n", (void *)this, samples));
- m_callback(*this, inputs, outputs, samples);
- VPRINTF((" callback done\n"));
+//-------------------------------------------------
+// empty_view - return an empty view covering the
+// given time period as a substitute for invalid
+// inputs
+//-------------------------------------------------
+
+read_stream_view sound_stream::empty_view(attotime start, attotime end)
+{
+ // if our dummy buffer doesn't match our sample rate, update and clear it
+ if (m_empty_buffer.sample_rate() != m_sample_rate)
+ m_empty_buffer.set_sample_rate(m_sample_rate, false);
+
+ // allocate a write view so that it can expand, and convert back to a read view
+ // on the return
+ return write_stream_view(m_empty_buffer, start, end);
}
+
+//**************************************************************************
+// RESAMPLER STREAM
+//**************************************************************************
+
//-------------------------------------------------
-// generate_resampled_data - generate the
-// resample buffer for a given input
+// default_resampler_stream - derived sound_stream
+// class that handles resampling
//-------------------------------------------------
-stream_sample_t *sound_stream::generate_resampled_data(stream_input &input, u32 numsamples)
+default_resampler_stream::default_resampler_stream(device_t &device) :
+ sound_stream(device, 1, 1, 0, SAMPLE_RATE_OUTPUT_ADAPTIVE, stream_update_delegate(&default_resampler_stream::resampler_sound_update, this), STREAM_DISABLE_INPUT_RESAMPLING),
+ m_max_latency(0)
{
- // if we don't have an output to pull data from, generate silence
- stream_sample_t *dest = &input.m_resample[0];
- if (input.m_source == nullptr || input.m_source->m_stream->m_attoseconds_per_sample == 0)
- {
- std::fill_n(dest, numsamples, 0);
- return &input.m_resample[0];
- }
+ // create a name
+ m_name = "Default Resampler '";
+ m_name += device.tag();
+ m_name += "'";
+}
- // grab data from the output
- stream_output &output = *input.m_source;
- sound_stream &input_stream = *output.m_stream;
- s64 gain = (input.m_gain * input.m_user_gain * output.m_gain) >> 16;
- // determine the time at which the current sample begins, accounting for the
- // latency we calculated between the input and output streams
- attoseconds_t basetime = m_output_sampindex * m_attoseconds_per_sample - input.m_latency_attoseconds;
+//-------------------------------------------------
+// resampler_sound_update - stream callback
+// handler for resampling an input stream to the
+// target sample rate of the output
+//-------------------------------------------------
- // now convert that time into a sample in the input stream
- s32 basesample;
- if (basetime >= 0)
- basesample = basetime / input_stream.m_attoseconds_per_sample;
- else
- basesample = -(-basetime / input_stream.m_attoseconds_per_sample) - 1;
+void default_resampler_stream::resampler_sound_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
+{
+ sound_assert(inputs.size() == 1);
+ sound_assert(outputs.size() == 1);
+
+ auto &input = inputs[0];
+ auto &output = outputs[0];
- // compute a source pointer to the first sample
- assert(basesample >= input_stream.m_output_base_sampindex);
- stream_sample_t *source = &output.m_buffer[basesample - input_stream.m_output_base_sampindex];
+ // if the input has an invalid rate, just fill with zeros
+ if (input.sample_rate() <= 1)
+ {
+ output.fill(0);
+ return;
+ }
- // determine the current fraction of a sample, expressed as a fraction of FRAC_ONE
- // (Note: this formula is valid as long as input_stream.m_attoseconds_per_sample significantly exceeds FRAC_ONE > attoseconds = 4.2E-12 s)
- u32 basefrac = (basetime - basesample * input_stream.m_attoseconds_per_sample) / ((input_stream.m_attoseconds_per_sample + FRAC_ONE - 1) >> FRAC_BITS);
- assert(basefrac < FRAC_ONE);
+ // optimize_resampler ensures we should not have equal sample rates
+ sound_assert(input.sample_rate() != output.sample_rate());
- // compute the stepping fraction
- u32 step = (u64(input_stream.m_sample_rate) << FRAC_BITS) / m_sample_rate;
+ // compute the stepping value and the inverse
+ stream_buffer::sample_t step = stream_buffer::sample_t(input.sample_rate()) / stream_buffer::sample_t(output.sample_rate());
+ stream_buffer::sample_t stepinv = 1.0 / step;
- // if we have equal sample rates, we just need to copy
- if (step == FRAC_ONE)
+ // determine the latency we need to introduce, in input samples:
+ // 1 input sample for undersampled inputs
+ // 1 + step input samples for oversampled inputs
+ s64 latency_samples = 1 + ((step < 1.0) ? 0 : s32(step));
+ if (latency_samples <= m_max_latency)
+ latency_samples = m_max_latency;
+ else
+ m_max_latency = latency_samples;
+ attotime latency = latency_samples * input.sample_period();
+
+ // clamp the latency to the start (only relevant at the beginning)
+ s32 dstindex = 0;
+ attotime output_start = output.start_time();
+ auto numsamples = output.samples();
+ while (latency > output_start && dstindex < numsamples)
{
- while (numsamples--)
- {
- // compute the sample
- s64 sample = *source++;
- *dest++ = (sample * gain) >> 8;
- }
+ output.put(dstindex++, 0);
+ output_start += output.sample_period();
}
+ if (dstindex >= numsamples)
+ return;
+
+ // create a rebased input buffer around the adjusted start time
+ read_stream_view rebased(input, output_start - latency);
+ sound_assert(rebased.start_time() + latency <= output_start);
+
+ // compute the fractional input start position
+ attotime delta = output_start - (rebased.start_time() + latency);
+ sound_assert(delta.seconds() == 0);
+ stream_buffer::sample_t srcpos = stream_buffer::sample_t(double(delta.attoseconds()) / double(rebased.sample_period_attoseconds()));
+ sound_assert(srcpos <= 1.0f);
// input is undersampled: point sample except where our sample period covers a boundary
- else if (step < FRAC_ONE)
+ s32 srcindex = 0;
+ if (step < 1.0)
{
- while (numsamples != 0)
+ stream_buffer::sample_t cursample = rebased.get(srcindex++);
+ for ( ; dstindex < numsamples; dstindex++)
{
- // fill in with point samples until we hit a boundary
- int nextfrac;
- while ((nextfrac = basefrac + step) < FRAC_ONE && numsamples--)
+ // if still within the current sample, just replicate
+ if (srcpos <= 1.0)
+ output.put(dstindex, cursample);
+
+ // if crossing a sample boundary, blend with the neighbor
+ else
{
- *dest++ = (source[0] * gain) >> 8;
- basefrac = nextfrac;
+ srcpos -= 1.0;
+ sound_assert(srcpos <= step + 1e-5);
+ stream_buffer::sample_t prevsample = cursample;
+ cursample = rebased.get(srcindex++);
+ output.put(dstindex, stepinv * (prevsample * (step - srcpos) + srcpos * cursample));
}
-
- // if we're done, we're done
- if (s32(numsamples--) < 0)
- break;
-
- // compute starting and ending fractional positions
- int startfrac = basefrac >> (FRAC_BITS - 12);
- int endfrac = nextfrac >> (FRAC_BITS - 12);
-
- // blend between the two samples accordingly
- s64 sample = (s64(source[0]) * (0x1000 - startfrac) + s64(source[1]) * (endfrac - 0x1000)) / (endfrac - startfrac);
- *dest++ = (sample * gain) >> 8;
-
- // advance
- basefrac = nextfrac & FRAC_MASK;
- source++;
+ srcpos += step;
}
+ sound_assert(srcindex <= rebased.samples());
}
// input is oversampled: sum the energy
else
{
- // use 8 bits to allow some extra headroom
- int smallstep = step >> (FRAC_BITS - 8);
- while (numsamples--)
+ float cursample = rebased.get(srcindex++);
+ for ( ; dstindex < numsamples; dstindex++)
{
- s64 remainder = smallstep;
- int tpos = 0;
-
- // compute the sample
- s64 scale = (FRAC_ONE - basefrac) >> (FRAC_BITS - 8);
- s64 sample = s64(source[tpos++]) * scale;
- remainder -= scale;
- while (remainder > 0x100)
+ // compute the partial first sample and advance
+ stream_buffer::sample_t scale = 1.0 - srcpos;
+ stream_buffer::sample_t sample = cursample * scale;
+
+ // add in complete samples until we only have a fraction left
+ stream_buffer::sample_t remaining = step - scale;
+ while (remaining >= 1.0)
{
- sample += s64(source[tpos++]) * s64(0x100);
- remainder -= 0x100;
+ sample += rebased.get(srcindex++);
+ remaining -= 1.0;
}
- sample += s64(source[tpos]) * remainder;
- sample /= smallstep;
- *dest++ = (sample * gain) >> 8;
+ // add in the final partial sample
+ cursample = rebased.get(srcindex++);
+ sample += cursample * remaining;
+ output.put(dstindex, sample * stepinv);
- // advance
- basefrac += step;
- source += basefrac >> FRAC_BITS;
- basefrac &= FRAC_MASK;
+ // our position is now the remainder
+ srcpos = remaining;
+ sound_assert(srcindex <= rebased.samples());
}
}
-
- return &input.m_resample[0];
-}
-
-
-
-//**************************************************************************
-// STREAM INPUT
-//**************************************************************************
-
-//-------------------------------------------------
-// stream_input - constructor
-//-------------------------------------------------
-
-sound_stream::stream_input::stream_input()
- : m_source(nullptr),
- m_latency_attoseconds(0),
- m_gain(0x100),
- m_user_gain(0x100)
-{
-}
-
-
-
-//**************************************************************************
-// STREAM OUTPUT
-//**************************************************************************
-
-//-------------------------------------------------
-// stream_output - constructor
-//-------------------------------------------------
-
-sound_stream::stream_output::stream_output()
- : m_stream(nullptr),
- m_dependents(0),
- m_gain(0x100)
-{
}
@@ -831,20 +1131,24 @@ sound_stream::stream_output::stream_output()
// sound_manager - constructor
//-------------------------------------------------
-sound_manager::sound_manager(running_machine &machine)
- : m_machine(machine),
- m_update_timer(nullptr),
- m_finalmix_leftover(0),
- m_finalmix(machine.sample_rate()),
- m_leftmix(machine.sample_rate()),
- m_rightmix(machine.sample_rate()),
- m_samples_this_update(0),
- m_muted(0),
- m_attenuation(0),
- m_nosound_mode(machine.osd().no_sound()),
- m_wavfile(nullptr),
- m_update_attoseconds(STREAMS_UPDATE_ATTOTIME.attoseconds()),
- m_last_update(attotime::zero)
+sound_manager::sound_manager(running_machine &machine) :
+ m_machine(machine),
+ m_update_timer(nullptr),
+ m_update_number(0),
+ m_last_update(attotime::zero),
+ m_finalmix_leftover(0),
+ m_samples_this_update(0),
+ m_finalmix(machine.sample_rate()),
+ m_leftmix(machine.sample_rate()),
+ m_rightmix(machine.sample_rate()),
+ m_compressor_scale(1.0),
+ m_compressor_counter(0),
+ m_muted(0),
+ m_nosound_mode(machine.osd().no_sound()),
+ m_attenuation(0),
+ m_unique_id(0),
+ m_wavfile(nullptr),
+ m_first_reset(true)
{
// get filename for WAV file or AVI file if specified
const char *wavfile = machine.options().wav_write();
@@ -889,6 +1193,41 @@ sound_manager::~sound_manager()
//-------------------------------------------------
+// stream_alloc_legacy - allocate a new stream
+//-------------------------------------------------
+
+sound_stream *sound_manager::stream_alloc_legacy(device_t &device, u32 inputs, u32 outputs, u32 sample_rate, stream_update_legacy_delegate callback)
+{
+ // determine output base
+ u32 output_base = 0;
+ for (auto &stream : m_stream_list)
+ if (&stream->device() == &device)
+ output_base += stream->output_count();
+
+ m_stream_list.push_back(std::make_unique<sound_stream>(device, inputs, outputs, output_base, sample_rate, callback));
+ return m_stream_list.back().get();
+}
+
+
+//-------------------------------------------------
+// stream_alloc - allocate a new stream with the
+// new-style callback and flags
+//-------------------------------------------------
+
+sound_stream *sound_manager::stream_alloc(device_t &device, u32 inputs, u32 outputs, u32 sample_rate, stream_update_delegate callback, sound_stream_flags flags)
+{
+ // determine output base
+ u32 output_base = 0;
+ for (auto &stream : m_stream_list)
+ if (&stream->device() == &device)
+ output_base += stream->output_count();
+
+ m_stream_list.push_back(std::make_unique<sound_stream>(device, inputs, outputs, output_base, sample_rate, callback, flags));
+ return m_stream_list.back().get();
+}
+
+
+//-------------------------------------------------
// start_recording - begin audio recording
//-------------------------------------------------
@@ -915,23 +1254,13 @@ void sound_manager::stop_recording()
//-------------------------------------------------
-// stream_alloc - allocate a new stream
-//-------------------------------------------------
-
-sound_stream *sound_manager::stream_alloc(device_t &device, int inputs, int outputs, int sample_rate, stream_update_delegate callback)
-{
- m_stream_list.push_back(std::make_unique<sound_stream>(device, inputs, outputs, sample_rate, callback));
- return m_stream_list.back().get();
-}
-
-
-//-------------------------------------------------
// set_attenuation - set the global volume
//-------------------------------------------------
-void sound_manager::set_attenuation(int attenuation)
+void sound_manager::set_attenuation(float attenuation)
{
- m_attenuation = attenuation;
+ // currently OSD only supports integral attenuation
+ m_attenuation = int(attenuation);
machine().osd().set_mastervolume(m_muted ? -32 : m_attenuation);
}
@@ -951,7 +1280,7 @@ bool sound_manager::indexed_mixer_input(int index, mixer_input &info) const
{
info.mixer = &mixer;
info.stream = mixer.input_to_stream_input(index, info.inputnum);
- assert(info.stream != nullptr);
+ sound_assert(info.stream != nullptr);
return true;
}
index -= mixer.inputs();
@@ -964,6 +1293,19 @@ bool sound_manager::indexed_mixer_input(int index, mixer_input &info) const
//-------------------------------------------------
+// samples - fills the specified buffer with
+// 16-bit stereo audio samples generated during
+// the current frame
+//-------------------------------------------------
+
+void sound_manager::samples(s16 *buffer)
+{
+ for (int sample = 0; sample < m_samples_this_update * 2; sample++)
+ *buffer++ = m_finalmix[sample];
+}
+
+
+//-------------------------------------------------
// mute - mute sound output
//-------------------------------------------------
@@ -978,6 +1320,47 @@ void sound_manager::mute(bool mute, u8 reason)
//-------------------------------------------------
+// recursive_remove_stream_from_orphan_list -
+// remove the given stream from the orphan list
+// and recursively remove all our inputs
+//-------------------------------------------------
+
+void sound_manager::recursive_remove_stream_from_orphan_list(sound_stream *which)
+{
+ m_orphan_stream_list.erase(which);
+ for (int inputnum = 0; inputnum < which->input_count(); inputnum++)
+ {
+ auto &input = which->input(inputnum);
+ if (input.valid())
+ recursive_remove_stream_from_orphan_list(&input.source().stream());
+ }
+}
+
+
+//-------------------------------------------------
+// apply_sample_rate_changes - recursively
+// update sample rates throughout the system
+//-------------------------------------------------
+
+void sound_manager::apply_sample_rate_changes()
+{
+ // update sample rates if they have changed
+ for (speaker_device &speaker : speaker_device_iterator(machine().root_device()))
+ {
+ int stream_out;
+ sound_stream *stream = speaker.output_to_stream_output(0, stream_out);
+
+ // due to device removal, some speakers may end up with no outputs; just skip those
+ if (stream != nullptr)
+ {
+ sound_assert(speaker.outputs() == 1);
+ stream->apply_sample_rate_changes(m_update_number, machine().sample_rate());
+ }
+ }
+}
+
+
+//-------------------------------------------------
// reset - reset all sound chips
//-------------------------------------------------
@@ -986,6 +1369,47 @@ void sound_manager::reset()
// reset all the sound chips
for (device_sound_interface &sound : sound_interface_iterator(machine().root_device()))
sound.device().reset();
+
+ // apply any sample rate changes now
+ apply_sample_rate_changes();
+
+ // on first reset, identify any orphaned streams
+ if (m_first_reset)
+ {
+ m_first_reset = false;
+
+ // put all the streams on the orphan list to start
+ for (auto &stream : m_stream_list)
+ m_orphan_stream_list[stream.get()] = 0;
+
+ // then walk the graph like we do on update and remove any we touch
+ for (speaker_device &speaker : speaker_device_iterator(machine().root_device()))
+ {
+ int dummy;
+ sound_stream *output = speaker.output_to_stream_output(0, dummy);
+ if (output != nullptr)
+ recursive_remove_stream_from_orphan_list(output);
+ }
+
+#if (SOUND_DEBUG)
+ // dump the sound graph when we start up
+ for (speaker_device &speaker : speaker_device_iterator(machine().root_device()))
+ {
+ int index;
+ sound_stream *output = speaker.output_to_stream_output(0, index);
+ if (output != nullptr)
+ output->print_graph_recursive(0, index);
+ }
+
+ // dump the orphan list as well
+ if (m_orphan_stream_list.size() != 0)
+ {
+ osd_printf_info("\nOrphaned streams:\n");
+ for (auto &stream : m_orphan_stream_list)
+ osd_printf_info(" %s\n", stream.first->name());
+ }
+#endif
+ }
}
@@ -1033,7 +1457,7 @@ void sound_manager::config_load(config_type cfg_type, util::xml::data_node const
float defvol = channelnode->get_attribute_float("defvol", 1.0f);
float newvol = channelnode->get_attribute_float("newvol", -1000.0f);
if (newvol != -1000.0f)
- info.stream->set_user_gain(info.inputnum, newvol / defvol);
+ info.stream->input(info.inputnum).set_user_gain(newvol / defvol);
}
}
}
@@ -1057,7 +1481,7 @@ void sound_manager::config_save(config_type cfg_type, util::xml::data_node *pare
mixer_input info;
if (!indexed_mixer_input(mixernum, info))
break;
- float newvol = info.stream->user_gain(info.inputnum);
+ float newvol = info.stream->input(info.inputnum).user_gain();
if (newvol != 1.0f)
{
@@ -1073,6 +1497,45 @@ void sound_manager::config_save(config_type cfg_type, util::xml::data_node *pare
//-------------------------------------------------
+// adjust_toward_compressor_scale - adjust the
+// current scale factor toward the current goal,
+// in small increments
+//-------------------------------------------------
+
+stream_buffer::sample_t sound_manager::adjust_toward_compressor_scale(stream_buffer::sample_t curscale, stream_buffer::sample_t prevsample, stream_buffer::sample_t rawsample)
+{
+ stream_buffer::sample_t proposed_scale = curscale;
+
+ // if we want to get larger, increment by 0.01
+ if (curscale < m_compressor_scale)
+ {
+ proposed_scale += 0.01f;
+ if (proposed_scale > m_compressor_scale)
+ proposed_scale = m_compressor_scale;
+ }
+
+ // otherwise, decrement by 0.01
+ else
+ {
+ proposed_scale -= 0.01f;
+ if (proposed_scale < m_compressor_scale)
+ proposed_scale = m_compressor_scale;
+ }
+
+ // compute the sample at the current scale and at the proposed scale
+ stream_buffer::sample_t cursample = rawsample * curscale;
+ stream_buffer::sample_t proposed_sample = rawsample * proposed_scale;
+
+ // if they trend in the same direction, it's ok to take the step
+ if ((cursample < prevsample && proposed_sample < prevsample) || (cursample > prevsample && proposed_sample > prevsample))
+ curscale = proposed_scale;
+
+ // return the current scale
+ return curscale;
+}
+
+
+//-------------------------------------------------
// update - mix everything down to its final form
// and send it to the OSD layer
//-------------------------------------------------
@@ -1083,10 +1546,72 @@ void sound_manager::update(void *ptr, int param)
g_profiler.start(PROFILER_SOUND);
+ // determine the duration of this update
+ attotime update_period = machine().time() - m_last_update;
+ sound_assert(update_period.seconds() == 0);
+
+ // use that to compute the number of samples we need from the speakers
+ attoseconds_t sample_rate_attos = HZ_TO_ATTOSECONDS(machine().sample_rate());
+ m_samples_this_update = update_period.attoseconds() / sample_rate_attos;
+
+ // recompute the end time to an even sample boundary
+ attotime endtime = m_last_update + attotime(0, m_samples_this_update * sample_rate_attos);
+
+ // clear out the mix bufers
+ std::fill_n(&m_leftmix[0], m_samples_this_update, 0);
+ std::fill_n(&m_rightmix[0], m_samples_this_update, 0);
+
// force all the speaker streams to generate the proper number of samples
- m_samples_this_update = 0;
for (speaker_device &speaker : speaker_device_iterator(machine().root_device()))
- speaker.mix(&m_leftmix[0], &m_rightmix[0], m_samples_this_update, (m_muted & MUTE_REASON_SYSTEM));
+ speaker.mix(&m_leftmix[0], &m_rightmix[0], m_last_update, endtime, m_samples_this_update, (m_muted & MUTE_REASON_SYSTEM));
+
+ // determine the maximum in this section
+ stream_buffer::sample_t curmax = 0;
+ for (int sampindex = 0; sampindex < m_samples_this_update; sampindex++)
+ {
+ auto sample = m_leftmix[sampindex];
+ if (sample < 0)
+ sample = -sample;
+ if (sample > curmax)
+ curmax = sample;
+
+ sample = m_rightmix[sampindex];
+ if (sample < 0)
+ sample = -sample;
+ if (sample > curmax)
+ curmax = sample;
+ }
+
+ // pull in current compressor scale factor before modifying
+ stream_buffer::sample_t lscale = m_compressor_scale;
+ stream_buffer::sample_t rscale = m_compressor_scale;
+
+ // if we're above what the compressor will handle, adjust the compression
+ if (curmax * m_compressor_scale > 1.0)
+ {
+ m_compressor_scale = 1.0 / curmax;
+ m_compressor_counter = STREAMS_UPDATE_FREQUENCY / 5;
+ }
+
+ // if we're currently scaled, wait a bit to see if we can trend back toward 1.0
+ else if (m_compressor_counter != 0)
+ m_compressor_counter--;
+
+ // try to migrate toward 0 unless we're going to introduce clipping
+ else if (m_compressor_scale < 1.0 && curmax * 1.01 * m_compressor_scale < 1.0)
+ {
+ m_compressor_scale *= 1.01f;
+ if (m_compressor_scale > 1.0)
+ m_compressor_scale = 1.0;
+ }
+
+#if (SOUND_DEBUG)
+ if (lscale != m_compressor_scale)
+ printf("scale=%.5f\n", m_compressor_scale);
+#endif
+
+ // track whether there are pending scale changes in left/right
+ stream_buffer::sample_t lprev = 0, rprev = 0;
// now downmix the final result
u32 finalmix_step = machine().video().speed_factor();
@@ -1097,21 +1622,31 @@ void sound_manager::update(void *ptr, int param)
{
int sampindex = sample / 1000;
+ // ensure that changing the compression won't reverse direction to reduce "pops"
+ stream_buffer::sample_t lsamp = m_leftmix[sampindex];
+ if (lscale != m_compressor_scale && sample != m_finalmix_leftover)
+ lscale = adjust_toward_compressor_scale(lscale, lprev, lsamp);
+
// clamp the left side
- s32 samp = m_leftmix[sampindex];
- if (samp < -32768)
- samp = -32768;
- else if (samp > 32767)
- samp = 32767;
- finalmix[finalmix_offset++] = samp;
-
- // clamp the right side
- samp = m_rightmix[sampindex];
- if (samp < -32768)
- samp = -32768;
- else if (samp > 32767)
- samp = 32767;
- finalmix[finalmix_offset++] = samp;
+ lprev = lsamp *= lscale;
+ if (lsamp > 1.0)
+ lsamp = 1.0;
+ else if (lsamp < -1.0)
+ lsamp = -1.0;
+ finalmix[finalmix_offset++] = s16(lsamp * 32767.0);
+
+ // ensure that changing the compression won't reverse direction to reduce "pops"
+ stream_buffer::sample_t rsamp = m_rightmix[sampindex];
+ if (rscale != m_compressor_scale && sample != m_finalmix_leftover)
+ rscale = adjust_toward_compressor_scale(rscale, rprev, rsamp);
+
+ // clamp the left side
+ rprev = rsamp *= rscale;
+ if (rsamp > 1.0)
+ rsamp = 1.0;
+ else if (rsamp < -1.0)
+ rsamp = -1.0;
+ finalmix[finalmix_offset++] = s16(rsamp * 32767.0);
}
m_finalmix_leftover = sample - m_samples_this_update * 1000;
@@ -1126,43 +1661,19 @@ void sound_manager::update(void *ptr, int param)
wav_add_data_16(m_wavfile, finalmix, finalmix_offset);
}
- // see if we ticked over to the next second
- attotime curtime = machine().time();
- bool second_tick = false;
- if (curtime.seconds() != m_last_update.seconds())
- {
- assert(curtime.seconds() == m_last_update.seconds() + 1);
- second_tick = true;
- }
-
- // iterate over all the streams and update them
- for (auto &stream : m_stream_list)
- stream->update_with_accounting(second_tick);
+ // update any orphaned streams so they don't get too far behind
+ for (auto &stream : m_orphan_stream_list)
+ stream.first->update();
// remember the update time
- m_last_update = curtime;
+ m_last_update = endtime;
+ m_update_number++;
- // update sample rates if they have changed
- for (auto &stream : m_stream_list)
- stream->apply_sample_rate_changes();
+ // apply sample rate changes
+ apply_sample_rate_changes();
// notify that new samples have been generated
emulator_info::sound_hook();
g_profiler.stop();
}
-
-
-//-------------------------------------------------
-// samples - fills the specified buffer with
-// 16-bit stereo audio samples generated during
-// the current frame
-//-------------------------------------------------
-
-void sound_manager::samples(s16 *buffer)
-{
- for (int sample = 0; sample < m_samples_this_update * 2; sample++)
- {
- *buffer++ = m_finalmix[sample];
- }
-}