summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/sound.cpp
diff options
context:
space:
mode:
author Olivier Galibert <galibert@pobox.com>2025-04-14 11:31:53 +0200
committer Olivier Galibert <galibert@pobox.com>2025-04-14 22:28:31 +0200
commitcef6157803320544651bfc96457d2f8a6df0abd6 (patch)
treef8a64867e3d654cdcad5f4c24824ea82e2c77194 /src/emu/sound.cpp
parent9473c027358e1a2d5c93d240a48052368f9d3b84 (diff)
New sound infrastructure.sound
Should be added soon: - mute - lua hookup (with documentation) - speaker/microphone resampling To be added a little later: - compression - reverb Needs to be added by someone else: - coreaudio - direct - portaudio - xaudio2 - js
Diffstat (limited to 'src/emu/sound.cpp')
-rw-r--r--src/emu/sound.cpp3268
1 files changed, 2040 insertions, 1228 deletions
diff --git a/src/emu/sound.cpp b/src/emu/sound.cpp
index c9626855197..2cad8289f80 100644
--- a/src/emu/sound.cpp
+++ b/src/emu/sound.cpp
@@ -10,6 +10,9 @@
#include "emu.h"
+#include "audio_effects/aeffect.h"
+#include "resampler.h"
+
#include "config.h"
#include "emuopts.h"
#include "main.h"
@@ -20,1632 +23,2441 @@
#include "osdepend.h"
+#include <algorithm>
//**************************************************************************
// DEBUGGING
//**************************************************************************
-//#define VERBOSE 1
-#define LOG_OUTPUT_FUNC osd_printf_debug
+#define LOG_OUTPUT_FUNC m_machine.logerror
-#include "logmacro.h"
+#define LOG_OSD_INFO (1U << 1)
+#define LOG_MAPPING (1U << 2)
+#define LOG_OSD_STREAMS (1U << 3)
+#define LOG_ORDER (1U << 4)
+
+#define VERBOSE -1
-#define LOG_OUTPUT_WAV (0)
+#include "logmacro.h"
-//**************************************************************************
-// GLOBAL VARIABLES
-//**************************************************************************
const attotime sound_manager::STREAMS_UPDATE_ATTOTIME = attotime::from_hz(STREAMS_UPDATE_FREQUENCY);
-//**************************************************************************
-// STREAM BUFFER
-//**************************************************************************
-//-------------------------------------------------
-// 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)
-{
-}
+//**// Output buffer management
+// Output buffers store samples produced every system-wide update.
+// They give access to a window of samples produced before the update,
+// and ensure that enough space is available to fit the update.
-//-------------------------------------------------
-// stream_buffer - destructor
-//-------------------------------------------------
-stream_buffer::~stream_buffer()
+template<typename S> emu::detail::output_buffer_interleaved<S>::output_buffer_interleaved(u32 buffer_size, u32 channels) :
+ m_buffer(channels*buffer_size, 0),
+ m_sync_sample(0),
+ m_write_position(0),
+ m_sync_position(0),
+ m_history(0),
+ m_channels(channels)
{
-#if (SOUND_DEBUG)
- if (m_wav_file)
- flush_wav();
-#endif
}
+template<typename S> void emu::detail::output_buffer_interleaved<S>::set_buffer_size(u32 buffer_size)
+{
+ m_buffer.resize(m_channels*buffer_size, 0);
+}
-//-------------------------------------------------
-// set_sample_rate - set a new sample rate for
-// this buffer
-//-------------------------------------------------
-
-void stream_buffer::set_sample_rate(u32 rate, bool resample)
+template<typename S> void emu::detail::output_buffer_interleaved<S>::prepare_space(u32 samples)
{
- // skip if nothing is actually changing
- if (rate == m_sample_rate)
+ if(!m_channels)
return;
- // force resampling off if coming to or from an invalid rate, or if we're at time 0 (startup)
- sound_assert(rate >= SAMPLE_RATE_MINIMUM - 1);
- if (rate < SAMPLE_RATE_MINIMUM || m_sample_rate < SAMPLE_RATE_MINIMUM || (m_end_second == 0 && m_end_sample == 0))
- 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(std::size(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)
- {
- 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);
-#if (SOUND_DEBUG)
- // multiple resamples can occur before clearing out old NaNs so
- // neuter them for this specific case
- if (std::isnan(m_buffer[end]))
- buffer[index] = 0;
- else
-#endif
- buffer[index] = get(end);
- }
- }
+ // Check if potential overflow, bring data back up front if needed
+ u32 buffer_size = m_buffer.size() / m_channels;
+ if(m_write_position + samples > buffer_size) {
+ u32 source_start = (m_sync_position - m_history) * m_channels;
+ u32 source_end = m_write_position * m_channels;
+ std::copy(m_buffer.begin() + source_start, m_buffer.begin() + source_end, m_buffer.begin());
+ m_write_position -= m_sync_position - m_history;
+ m_sync_position = m_history;
}
- // 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();
+ // Clear the destination range
+ u32 fill_start = m_write_position * m_channels;
+ u32 fill_end = (m_write_position + samples) * m_channels;
+ std::fill(m_buffer.begin() + fill_start, m_buffer.begin() + fill_end, 0.0);
+}
- // compute the new end sample index based on the buffer time
- m_end_sample = time_to_buffer_index(prevend, false, true);
+template<typename S> void emu::detail::output_buffer_interleaved<S>::commit(u32 samples)
+{
+ m_write_position += samples;
+}
- // if the new rate is higher, upsample from our temporary buffer;
- // otherwise just copy our previously-downsampled data
- if (resample)
- {
-#if (SOUND_DEBUG)
- // for aggressive debugging, fill the buffer with NANs to catch anyone
- // reading beyond what we resample below
- fill(NAN);
-#endif
-
- 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]);
- }
- }
- }
+template<typename S> void emu::detail::output_buffer_interleaved<S>::sync()
+{
+ m_sync_sample += m_write_position - m_sync_position;
+ m_sync_position = m_write_position;
+}
- // if not resampling, clear the buffer
- else
- fill(0);
+template<typename S> emu::detail::output_buffer_flat<S>::output_buffer_flat(u32 buffer_size, u32 channels) :
+ m_buffer(channels),
+ m_sync_sample(0),
+ m_write_position(0),
+ m_sync_position(0),
+ m_history(0),
+ m_channels(channels)
+{
+ for(auto &b : m_buffer)
+ b.resize(buffer_size, 0);
}
+template<typename S> void emu::detail::output_buffer_flat<S>::register_save_state(device_t &device, const char *id1, const char *id2)
+{
+ auto &save = device.machine().save();
-//-------------------------------------------------
-// open_wav - open a WAV file for logging purposes
-//-------------------------------------------------
+ for(unsigned int i=0; i != m_buffer.size(); i++)
+ save.save_item(&device, id1, id2, i, NAME(m_buffer[i]));
-#if (SOUND_DEBUG)
-void stream_buffer::open_wav(char const *filename)
-{
- // always open at 48k so that sound programs can handle it
- // re-sample as needed
- m_wav_file = util::wav_open(filename, 48000, 1);
-}
-#endif
+ save.save_item(&device, id1, id2, 0, NAME(m_sync_sample));
+ save.save_item(&device, id1, id2, 0, NAME(m_write_position));
+ save.save_item(&device, id1, id2, 0, NAME(m_sync_position));
+ save.save_item(&device, id1, id2, 0, NAME(m_history));
+}
-//-------------------------------------------------
-// flush_wav - flush data to the WAV file
-//-------------------------------------------------
+template<typename S> void emu::detail::output_buffer_flat<S>::set_buffer_size(u32 buffer_size)
+{
+ for(auto &b : m_buffer)
+ b.resize(buffer_size, 0);
+}
-#if (SOUND_DEBUG)
-void stream_buffer::flush_wav()
+template<typename S> void emu::detail::output_buffer_flat<S>::prepare_space(u32 samples)
{
- // skip if no file
- if (!m_wav_file)
+ if(!m_channels)
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;
+ // Check if potential overflow, bring data back up front if needed
+ u32 buffer_size = m_buffer[0].size();
+ if(m_write_position + samples > buffer_size) {
+ u32 source_start = m_sync_position - m_history;
+ u32 source_end = m_write_position;
+ for(u32 channel = 0; channel != m_channels; channel++)
+ std::copy(m_buffer[channel].begin() + source_start, m_buffer[channel].begin() + source_end, m_buffer[channel].begin());
+ m_write_position -= source_start;
+ m_sync_position = m_history;
+ }
- // iterate over chunks for conversion
- s16 buffer[1024];
- for (int samplebase = 0; samplebase < view.samples(); samplebase += std::size(buffer))
- {
- // clamp to the buffer size
- int cursamples = view.samples() - samplebase;
- if (cursamples > std::size(buffer))
- cursamples = std::size(buffer);
+ // Clear the destination range
+ u32 fill_start = m_write_position;
+ u32 fill_end = m_write_position + samples;
+ for(u32 channel = 0; channel != m_channels; channel++)
+ std::fill(m_buffer[channel].begin() + fill_start, m_buffer[channel].begin() + fill_end, 0.0);
+}
- // convert and fill
- for (int sampindex = 0; sampindex < cursamples; sampindex++)
- buffer[sampindex] = s16(view.get(samplebase + sampindex) * 32768.0);
+template<typename S> void emu::detail::output_buffer_flat<S>::commit(u32 samples)
+{
+ m_write_position += samples;
+}
- // write to the WAV
- util::wav_add_data_16(*m_wav_file, buffer, cursamples);
- }
+template<typename S> void emu::detail::output_buffer_flat<S>::sync()
+{
+ m_sync_sample += m_write_position - m_sync_position;
+ m_sync_position = m_write_position;
}
-#endif
+template<typename S> void emu::detail::output_buffer_flat<S>::set_history(u32 history)
+{
+ m_history = history;
+ if(m_sync_position < m_history) {
+ u32 delta = m_history - m_sync_position;
+ if(m_write_position)
+ for(u32 channel = 0; channel != m_channels; channel++) {
+ std::copy_backward(m_buffer[channel].begin(), m_buffer[channel].begin() + m_write_position, m_buffer[channel].begin() + m_write_position + delta);
+ std::fill(m_buffer[channel].begin() + 1, m_buffer[channel].begin() + delta, m_buffer[channel][0]);
+ }
+ else
+ for(u32 channel = 0; channel != m_channels; channel++)
+ std::fill(m_buffer[channel].begin(), m_buffer[channel].begin() + m_history, 0.0);
-//-------------------------------------------------
-// index_time - return the attotime of a given
-// index within the buffer
-//-------------------------------------------------
+ m_write_position += delta;
+ m_sync_position = m_history;
+ }
+}
-attotime stream_buffer::index_time(s32 index) const
+template<typename S> void emu::detail::output_buffer_flat<S>::resample(u32 previous_rate, u32 next_rate, attotime sync_time, attotime now)
{
- index = clamp_index(index);
- return attotime(m_end_second - ((index > m_end_sample) ? 1 : 0), index * m_sample_attos);
-}
+ if(!m_write_position)
+ return;
+ auto si = [](attotime time, u32 rate) -> s64 {
+ return time.m_seconds * rate + ((time.m_attoseconds / 100000000) * rate) / 10000000000;
+ };
+
+ auto cv = [](u32 source_rate, u32 dest_rate, s64 time) -> std::pair<s64, double> {
+ s64 sec = time / source_rate;
+ s64 prem = time % source_rate;
+ double nrem = double(prem * dest_rate) / double(source_rate);
+ s64 cyc = s64(nrem);
+ return std::make_pair(sec * dest_rate + cyc, nrem - cyc);
+ };
+
+ // Compute what will be the new start, sync and write positions (if it fits)
+ s64 nsync = si(sync_time, next_rate);
+ s64 nwrite = si(now, next_rate);
+ s64 pbase = m_sync_sample - m_sync_position; // Beware, pbase can be negative at startup due to history size
+ auto [nbase, nbase_dec] = cv(previous_rate, next_rate, pbase < 0 ? 0 : pbase);
+ nbase += 1;
+ if(nbase > nsync)
+ nbase = nsync;
+
+ u32 space = m_buffer[0].size();
+ if(nwrite - nbase > space) {
+ nbase = nwrite - space;
+ if(nbase > nsync)
+ fatalerror("Stream buffer too small, can't proceed, rate change %d -> %d, space=%d\n", previous_rate, next_rate, space);
+ }
-//-------------------------------------------------
-// time_to_buffer_index - given an attotime,
-// return the buffer index corresponding to it
-//-------------------------------------------------
+ auto [ppos, pdec] = cv(next_rate, previous_rate, nbase);
+ if(ppos < pbase || ppos >= pbase + m_write_position)
+ fatalerror("Something went very wrong, ppos=%d, pbase=%d, pbase+wp=%d\n", ppos, pbase, pbase + m_write_position);
-u32 stream_buffer::time_to_buffer_index(attotime time, bool round_up, bool allow_expansion)
-{
- // 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());
+ double step = double(previous_rate) / double(next_rate);
+ u32 pindex = ppos - pbase;
+ u32 nend = nwrite - nbase;
- // 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))
- {
- sound_assert(allow_expansion);
+ // Warning: don't try to be too clever, the m_buffer storage is
+ // registered in the save state system, so it must not move or
+ // change size
+
+ std::vector<S> copy(m_write_position);
+ for(u32 channel = 0; channel != m_channels; channel++) {
+ std::copy(m_buffer[channel].begin(), m_buffer[channel].begin() + m_write_position, copy.begin());
- m_end_sample = sample;
- m_end_second = time.m_seconds;
+ // Interpolate the buffer contents
- // due to round_up, we could tweak over the line into the next second
- if (sample >= size())
- {
- m_end_sample -= size();
- m_end_second++;
+ for(u32 nindex = 0; nindex != nend; nindex++) {
+ u32 pi0 = std::clamp(pindex, 0U, m_write_position - 1);
+ u32 pi1 = std::clamp(pindex + 1, 0U, m_write_position - 1);
+ m_buffer[channel][nindex] = copy[pi0] * (1-pdec) + copy[pi1] * pdec;
+
+ pdec += step;
+ if(pdec >= 1) {
+ int s = s32(pdec);
+ pindex += s;
+ pdec -= s;
+ }
}
}
- // 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");
+ m_sync_sample = nsync;
+ m_sync_position = m_sync_sample - nbase;
+ m_write_position = nend;
- return clamp_index(sample);
+ // history and the associated resizes are taken into account later
}
+template class emu::detail::output_buffer_flat<sound_stream::sample_t>;
+template class emu::detail::output_buffer_interleaved<s16>;
-//-------------------------------------------------
-// backfill_downsample - this is called BEFORE
-// the sample rate change to downsample from the
-// end of the current buffer into a temporary
-// holding location
-//-------------------------------------------------
-void stream_buffer::backfill_downsample(sample_t *dest, int samples, attotime newend, attotime newperiod)
-{
- // compute the time of the first sample to be backfilled; start one period before
- attotime time = newend - newperiod;
+// Not inline because with the unique_ptr it would require audio_effect in emu.h
- // 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;
+sound_manager::effect_step::effect_step(u32 buffer_size, u32 channels) : m_buffer(buffer_size, channels)
+{
}
-//-------------------------------------------------
-// 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
-//-------------------------------------------------
+//**// Streams and routes
-void stream_buffer::backfill_upsample(sample_t const *src, int samples, attotime prevend, attotime prevperiod)
+sound_stream::sound_stream(device_t &device, u32 inputs, u32 outputs, u32 sample_rate, stream_update_delegate callback, sound_stream_flags flags) :
+ m_device(device),
+ m_output_buffer(0, outputs),
+ m_sample_rate(sample_rate == SAMPLE_RATE_INPUT_ADAPTIVE || sample_rate == SAMPLE_RATE_OUTPUT_ADAPTIVE || sample_rate == SAMPLE_RATE_ADAPTIVE ? 0 : sample_rate),
+ m_input_count(inputs),
+ m_output_count(outputs),
+ m_input_adaptive(sample_rate == SAMPLE_RATE_INPUT_ADAPTIVE || sample_rate == SAMPLE_RATE_ADAPTIVE),
+ m_output_adaptive(sample_rate == SAMPLE_RATE_OUTPUT_ADAPTIVE || sample_rate == SAMPLE_RATE_ADAPTIVE),
+ m_synchronous((flags & STREAM_SYNCHRONOUS) != 0),
+ m_started(false),
+ m_sync_timer(nullptr),
+ m_callback(std::move(callback))
{
- // 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;
+ sound_assert(outputs > 0 || inputs > 0);
- // 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++;
- }
+ // create a name
+ m_name = m_device.name();
+ m_name += " '";
+ m_name += m_device.tag();
+ m_name += "'";
- // stop when we run out of source
- if (srcindex >= samples)
- break;
+ // create an update timer for synchronous streams
+ if(synchronous())
+ m_sync_timer = m_device.timer_alloc(FUNC(sound_stream::sync_update), this);
- // write this sample at the pevious position
- end = prev_index(end);
- put(end, src[srcindex]);
+ // create the gain vectors
+ m_input_channel_gain.resize(m_input_count, 1.0);
+ m_output_channel_gain.resize(m_output_count, 1.0);
+ m_user_output_channel_gain.resize(m_output_count, 1.0);
+ m_user_output_gain = 1.0;
- // back up to the next sample time
- time -= sample_period();
- }
}
+sound_stream::~sound_stream()
+{
+}
+void sound_stream::add_bw_route(sound_stream *source, int output, int input, float gain)
+{
+ m_bw_routes.emplace_back(route_bw(source, output, input, gain));
+}
-//**************************************************************************
-// SOUND STREAM OUTPUT
-//**************************************************************************
-
-//-------------------------------------------------
-// sound_stream_output - constructor
-//-------------------------------------------------
+void sound_stream::add_fw_route(sound_stream *target, int input, int output)
+{
+ m_fw_routes.emplace_back(route_fw(target, input, output));
+}
-sound_stream_output::sound_stream_output() :
- m_stream(nullptr),
- m_index(0),
- m_gain(1.0)
+bool sound_stream::set_route_gain(sound_stream *source, int source_channel, int target_channel, float gain)
{
+ for(auto &r : m_bw_routes)
+ if(r.m_source == source && r.m_output == source_channel && r.m_input == target_channel) {
+ r.m_gain = gain;
+ return true;
+ }
+ return false;
}
+std::vector<sound_stream *> sound_stream::sources() const
+{
+ std::vector<sound_stream *> streams;
+ for(const route_bw &route : m_bw_routes) {
+ sound_stream *stream = route.m_source;
+ for(const sound_stream *s : streams)
+ if(s == stream)
+ goto already;
+ streams.push_back(stream);
+ already:;
+ }
+ return streams;
+}
-//-------------------------------------------------
-// init - initialization
-//-------------------------------------------------
+std::vector<sound_stream *> sound_stream::targets() const
+{
+ std::vector<sound_stream *> streams;
+ for(const route_fw &route : m_fw_routes) {
+ sound_stream *stream = route.m_target;
+ for(const sound_stream *s : streams)
+ if(s == stream)
+ goto already;
+ streams.push_back(stream);
+ already:;
+ }
+ return streams;
+}
-void sound_stream_output::init(sound_stream &stream, u32 index, char const *tag)
+void sound_stream::register_state()
{
- // set the passed-in data
- m_stream = &stream;
- m_index = index;
+ // create a unique tag for saving
+ m_state_tag = string_format("%d", m_device.machine().sound().unique_id());
+ auto &save = m_device.machine().save();
+
+ save.save_item(&m_device, "stream.sound_stream", m_state_tag.c_str(), 0, NAME(m_sample_rate));
+ if(m_input_count)
+ save.save_item(&m_device, "stream.sound_stream", m_state_tag.c_str(), 0, NAME(m_input_channel_gain));
+ if(m_output_count)
+ save.save_item(&m_device, "stream.sound_stream", m_state_tag.c_str(), 0, NAME(m_output_channel_gain));
+ // user gains go to .cfg files, not state files
- // save our state
- auto &save = stream.device().machine().save();
- save.save_item(&stream.device(), "stream.output", tag, index, NAME(m_gain));
+ m_output_buffer.register_save_state(m_device, "stream.sound_stream.output_buffer", m_state_tag.c_str());
-#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
+ for(unsigned int i=0; i != m_bw_routes.size(); i++)
+ save.save_item(&m_device, "stream.sound_stream", m_state_tag.c_str(), i, m_bw_routes[i].m_gain, "route_gain");
}
-//-------------------------------------------------
-// name - return the friendly name of this output
-//-------------------------------------------------
+void sound_stream::compute_dependants()
+{
+ m_dependant_streams.clear();
+ for(const route_bw &r : m_bw_routes)
+ r.m_source->add_dependants(m_dependant_streams);
+}
-std::string sound_stream_output::name() const
+void sound_stream::add_dependants(std::vector<sound_stream *> &deps)
{
- // 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();
+ for(const route_bw &r : m_bw_routes)
+ r.m_source->add_dependants(deps);
+ for(sound_stream *dep : deps)
+ if(dep == this)
+ return;
+ deps.push_back(this);
}
-//-------------------------------------------------
-// optimize_resampler - optimize resamplers by
-// either returning the native rate or another
-// input's resampler if they can be reused
-//-------------------------------------------------
+//**// Stream sample rate
-sound_stream_output &sound_stream_output::optimize_resampler(sound_stream_output *input_resampler)
+void sound_stream::set_sample_rate(u32 new_rate)
{
- // 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;
+ m_input_adaptive = m_output_adaptive = false;
+ internal_set_sample_rate(new_rate);
}
+void sound_stream::internal_set_sample_rate(u32 new_rate)
+{
+ if(m_started) {
+ update();
+ m_output_buffer.resample(m_sample_rate, new_rate, m_sync_time, m_device.machine().time());
+ m_sample_rate = new_rate;
+ for(const route_fw &r : m_fw_routes)
+ r.m_target->create_resamplers();
+ create_resamplers();
+ lookup_history_sizes();
+ } else
+ m_sample_rate = new_rate;
+}
-//**************************************************************************
-// SOUND STREAM INPUT
-//**************************************************************************
+bool sound_stream::try_solving_frequency()
+{
+ if(frequency_is_solved())
+ return false;
-//-------------------------------------------------
-// sound_stream_input - constructor
-//-------------------------------------------------
+ if(input_adaptive() && !output_adaptive()) {
+ u32 freq = 0;
+ for(const route_bw &r : m_bw_routes) {
+ if(!r.m_source->frequency_is_solved())
+ return false;
+ if(freq < r.m_source->sample_rate())
+ freq = r.m_source->sample_rate();
+ }
+ m_sample_rate = freq;
+ return true;
+
+ } else if(output_adaptive() && !input_adaptive()) {
+ u32 freq = 0;
+ for(const route_fw &r : m_fw_routes) {
+ if(!r.m_target->frequency_is_solved())
+ return false;
+ if(freq < r.m_target->sample_rate())
+ freq = r.m_target->sample_rate();
+ }
+ m_sample_rate = freq;
+ return true;
+
+ } else {
+ u32 freqbw = 0;
+ for(const route_bw &r : m_bw_routes) {
+ if(!r.m_source->frequency_is_solved()) {
+ freqbw = 0;
+ break;
+ }
+ if(freqbw < r.m_source->sample_rate())
+ freqbw = r.m_source->sample_rate();
+ }
+ u32 freqfw = 0;
+ for(const route_fw &r : m_fw_routes) {
+ if(!r.m_target->frequency_is_solved()) {
+ freqfw = 0;
+ break;
+ }
+ if(freqfw < r.m_target->sample_rate())
+ freqfw = r.m_target->sample_rate();
+ }
+ if(!freqbw && !freqfw)
+ return false;
-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)
-{
+ m_sample_rate = freqfw > freqbw ? freqfw : freqbw;
+ return true;
+ }
}
-//-------------------------------------------------
-// init - initialization
-//-------------------------------------------------
+//**// Stream flow and updates
-void sound_stream_input::init(sound_stream &stream, u32 index, char const *tag, sound_stream_output *resampler)
+void sound_stream::init()
{
- // 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));
-}
+ // Ensure the buffer size is non-zero, since a stream can be started at any time
+ u32 bsize = m_sample_rate ? m_sample_rate : 48000;
+ m_input_buffer.resize(m_input_count);
+ for(auto &b : m_input_buffer)
+ b.resize(bsize);
+ m_output_buffer.set_buffer_size(bsize);
-//-------------------------------------------------
-// name - return the friendly name of this input
-//-------------------------------------------------
+ m_samples_to_update = 0;
+ m_started = true;
+ if(synchronous())
+ reprime_sync_timer();
+}
-std::string sound_stream_input::name() const
+u64 sound_stream::get_current_sample_index() const
{
- // start with our owning stream's name
- std::ostringstream str;
- util::stream_format(str, "%s", m_owner->name());
-
- // 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();
+ attotime now = m_device.machine().time();
+ return now.m_seconds * m_sample_rate + ((now.m_attoseconds / 1000000000) * m_sample_rate) / 1000000000;
}
+void sound_stream::update()
+{
+ if(!is_active())
+ return;
-//-------------------------------------------------
-// set_source - wire up the output source for
-// our consumption
-//-------------------------------------------------
+ // Find out where we are and how much we have to do
+ u64 idx = get_current_sample_index();
+ m_samples_to_update = idx - m_output_buffer.write_sample();
-void sound_stream_input::set_source(sound_stream_output *source)
-{
- m_native_source = source;
- if (m_resampler_source != nullptr)
- m_resampler_source->stream().set_input(0, &source->stream(), source->index());
-}
+ if(m_samples_to_update <= 0)
+ return;
+ // If there's anything to do, well, do it, starting with the dependencies
+ for(auto &stream : m_dependant_streams)
+ stream->update_nodeps();
-//-------------------------------------------------
-// update - update our source's stream to the
-// current end time and return a view to its
-// contents
-//-------------------------------------------------
+ do_update();
+}
-read_stream_view sound_stream_input::update(attotime start, attotime end)
+void sound_stream::update_nodeps()
{
- // 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(!is_active())
+ return;
- // 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);
+ // Find out where we are and how much we have to do
+ u64 idx = get_current_sample_index();
+ m_samples_to_update = idx - m_output_buffer.write_sample();
+ if(m_samples_to_update <= 0)
+ return;
- // 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 * source.gain());
+ // If there's anything to do, well, do it
+ do_update();
}
+void sound_stream::create_resamplers()
+{
+ if(!is_active()) {
+ for(auto &r : m_bw_routes)
+ r.m_resampler = nullptr;
+ return;
+ }
-//-------------------------------------------------
-// apply_sample_rate_changes - tell our sources
-// to apply any sample rate changes, informing
-// them of our current rate
-//-------------------------------------------------
+ for(auto &r : m_bw_routes)
+ if(r.m_source->is_active() && r.m_source->sample_rate() != m_sample_rate)
+ r.m_resampler = m_device.machine().sound().get_resampler(r.m_source->sample_rate(), m_sample_rate);
+ else
+ r.m_resampler = nullptr;
+}
-void sound_stream_input::apply_sample_rate_changes(u32 updatenum, u32 downstream_rate)
+void sound_stream::lookup_history_sizes()
{
- // shouldn't get here unless valid
- sound_assert(valid());
+ u32 history = 0;
+ for(auto &r : m_fw_routes) {
+ u32 h = r.m_target->get_history_for_bw_route(this, r.m_output);
+ if(h > history)
+ history = h;
+ }
- // 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);
+ m_output_buffer.set_history(history);
+}
- // otherwise, just tell the native source directly
- else
- m_native_source->stream().apply_sample_rate_changes(updatenum, downstream_rate);
+u32 sound_stream::get_history_for_bw_route(const sound_stream *source, u32 channel) const
+{
+ u32 history = 0;
+ for(auto &r : m_bw_routes)
+ if(r.m_source == source && r.m_output == channel && r.m_resampler) {
+ u32 h = r.m_resampler->history_size();
+ if(h > history)
+ history = h;
+ }
+ return history;
}
+void sound_stream::do_update()
+{
+ // Mix in all the inputs (if any)
+ if(m_input_count) {
+ for(auto &b : m_input_buffer)
+ std::fill(b.begin(), b.begin() + m_samples_to_update, 0.0);
+ for(const auto &r : m_bw_routes) {
+ if(!r.m_source->is_active())
+ continue;
+
+ float gain = r.m_source->m_user_output_gain * r.m_source->m_output_channel_gain[r.m_output] * r.m_source->m_user_output_channel_gain[r.m_output] * r.m_gain * m_input_channel_gain[r.m_input];
+ auto &db = m_input_buffer[r.m_input];
+ if(r.m_resampler)
+ r.m_resampler->apply(r.m_source->m_output_buffer, db, m_output_buffer.write_sample(), r.m_output, gain, m_samples_to_update);
+
+ else {
+ const sample_t *sb = r.m_source->m_output_buffer.ptrs(r.m_output, m_output_buffer.write_sample() - r.m_source->m_output_buffer.sync_sample());
+ for(u32 i = 0; i != m_samples_to_update; i++)
+ db[i] += sb[i] * gain;
+ }
+ }
+ }
+ // Prepare the output space (if any)
+ m_output_buffer.prepare_space(m_samples_to_update);
-//**************************************************************************
-// SOUND STREAM
-//**************************************************************************
+ // Call the callback
+ m_callback(*this);
-//-------------------------------------------------
-// sound_stream - private common constructor
-//-------------------------------------------------
+ // Update the indexes
+ m_output_buffer.commit(m_samples_to_update);
+}
-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_last_update_end_time(attotime::zero),
- m_input(inputs),
- m_input_view(inputs),
- m_empty_buffer(100),
- m_output_base(output_base),
- m_output(outputs),
- m_output_view(outputs)
+void sound_stream::sync(attotime now)
{
- sound_assert(outputs > 0);
+ m_sync_time = now;
+ m_output_buffer.sync();
+}
+
- // 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.save_item(&m_device, "stream.sound_stream", state_tag.c_str(), 0, NAME(m_sample_rate));
- save.save_item(&m_device, "stream.sound_stream", state_tag.c_str(), 0, NAME(m_last_update_end_time));
- save.register_postload(save_prepost_delegate(FUNC(sound_stream::postload), this));
- save.register_presave(save_prepost_delegate(FUNC(sound_stream::presave), 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.timer_alloc(FUNC(sound_stream::sync_update), this);
- // force an update to the sample rates
- sample_rate_changed();
-}
-//-------------------------------------------------
-// sound_stream - constructor
-//-------------------------------------------------
-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)
+attotime sound_stream::sample_to_time(u64 index) const
{
- m_callback_ex = std::move(callback);
+ attotime res = attotime::zero;
+ res.m_seconds = index / m_sample_rate;
+ u64 remain = index % m_sample_rate;
+ res.m_attoseconds = ((remain * 1000000000) / m_sample_rate) * 1000000000;
+ return res;
}
-//-------------------------------------------------
-// ~sound_stream - destructor
-//-------------------------------------------------
+//**// Synchronous stream updating
-sound_stream::~sound_stream()
+void sound_stream::reprime_sync_timer()
{
+ if(!is_active())
+ return;
+
+ u64 next_sample = m_output_buffer.write_sample() + 1;
+ attotime next_time = sample_to_time(next_sample);
+ next_time.m_attoseconds += 1000000000; // Go to the next nanosecond
+ m_sync_timer->adjust(next_time - m_device.machine().time());
}
-
-//-------------------------------------------------
-// set_sample_rate - set the sample rate on a
-// given stream
-//-------------------------------------------------
-
-void sound_stream::set_sample_rate(u32 new_rate)
+void sound_stream::sync_update(s32)
{
- // we will update this on the next global update
- if (new_rate != sample_rate())
- m_pending_sample_rate = new_rate;
+ update();
+ reprime_sync_timer();
}
-//-------------------------------------------------
-// set_input - configure a stream's input
-//-------------------------------------------------
-
-void sound_stream::set_input(int index, sound_stream *input_stream, int output_index, float gain)
+//**// Sound manager and stream allocation
+sound_manager::sound_manager(running_machine &machine) :
+ m_machine(machine),
+ m_update_timer(nullptr),
+ m_last_sync_time(attotime::zero),
+ m_effects_thread(nullptr),
+ m_effects_done(false),
+ m_master_gain(1.0),
+ m_muted(0),
+ m_nosound_mode(machine.osd().no_sound()),
+ m_unique_id(0),
+ m_wavfile()
{
- LOG("stream_set_input(%p, '%s', %d, %p, %d, %f)\n", (void *)this, m_device.tag(),
- index, (void *)input_stream, output_index, gain);
+ // register callbacks
+ machine.configuration().config_register(
+ "mixer",
+ configuration_manager::load_delegate(&sound_manager::config_load, this),
+ configuration_manager::save_delegate(&sound_manager::config_save, this));
+ machine.add_notifier(MACHINE_NOTIFY_PAUSE, machine_notify_delegate(&sound_manager::pause, this));
+ machine.add_notifier(MACHINE_NOTIFY_RESUME, machine_notify_delegate(&sound_manager::resume, this));
+ machine.add_notifier(MACHINE_NOTIFY_RESET, machine_notify_delegate(&sound_manager::reset, this));
+ machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(&sound_manager::stop_recording, this));
- // 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()));
+ // register global states
+ // machine.save().save_item(NAME(m_last_update));
- // 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()));
+ // start the periodic update flushing timer
+ m_update_timer = machine.scheduler().timer_alloc(timer_expired_delegate(FUNC(sound_manager::update), this));
+ m_update_timer->adjust(STREAMS_UPDATE_ATTOTIME, 0, STREAMS_UPDATE_ATTOTIME);
- // wire it up
- m_input[index].set_source((input_stream != nullptr) ? &input_stream->m_output[output_index] : nullptr);
- m_input[index].set_gain(gain);
+ // mark the generation as "just starting"
+ m_osd_info.m_generation = 0xffffffff;
+}
- // update sample rates now that we know the input
- sample_rate_changed();
+sound_manager::~sound_manager()
+{
+ if(m_effects_thread) {
+ m_effects_done = true;
+ m_effects_condition.notify_all();
+ m_effects_thread->join();
+ m_effects_thread = nullptr;
+ }
}
+sound_stream *sound_manager::stream_alloc(device_t &device, u32 inputs, u32 outputs, u32 sample_rate, stream_update_delegate callback, sound_stream_flags flags)
+{
+ m_stream_list.push_back(std::make_unique<sound_stream>(device, inputs, outputs, sample_rate, callback, flags));
+ return m_stream_list.back().get();
+}
-//-------------------------------------------------
-// update - force a stream to update to
-// the current emulated time
-//-------------------------------------------------
-void sound_stream::update()
+//**// Sound system initialization
+
+void sound_manager::before_devices_init()
{
- // 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;
+ // Inform the targets of the existence of the routes
+ for(device_sound_interface &sound : sound_interface_enumerator(machine().root_device()))
+ sound.sound_before_devices_init();
- // regular update then
- update_view(start, end);
+ m_machine.save().register_postload(save_prepost_delegate(FUNC(sound_manager::postload), this));
}
+void sound_manager::postload()
+{
+ std::unique_lock<std::mutex> lock(m_effects_mutex);
+ attotime now = machine().time();
+ for(osd_output_stream &stream : m_osd_output_streams) {
+ stream.m_last_sync = rate_and_time_to_index(now, stream.m_rate);
+ stream.m_samples = 0;
+ }
+}
-//-------------------------------------------------
-// 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
-//-------------------------------------------------
-
-read_stream_view sound_stream::update_view(attotime start, attotime end, u32 outputnum)
+void sound_manager::after_devices_init()
{
- sound_assert(start <= end);
- sound_assert(outputnum < m_output.size());
+ // Link all the streams together
+ for(device_sound_interface &sound : sound_interface_enumerator(machine().root_device()))
+ sound.sound_after_devices_init();
+
+ // Resolve the frequencies
+ int need_to_solve = 0;
+ for(auto &stream : m_stream_list)
+ if(!stream->frequency_is_solved())
+ need_to_solve ++;
+
+ while(need_to_solve) {
+ int prev_need_to_solve = need_to_solve;
+ for(auto &stream : m_stream_list)
+ if(!stream->frequency_is_solved() && stream->try_solving_frequency())
+ need_to_solve --;
+ if(need_to_solve == prev_need_to_solve)
+ break;
+ }
- // clean up parameters for when the asserts go away
- if (outputnum >= m_output.size())
- outputnum = 0;
- if (start > end)
- start = end;
+ if(need_to_solve) {
+ u32 def = machine().sample_rate();
+ for(auto &stream : m_stream_list)
+ if(!stream->frequency_is_solved())
+ stream->internal_set_sample_rate(def);
+ }
- auto profile = g_profiler.start(PROFILER_SOUND);
+ // Have all streams create their buffers and other initializations
+ for(auto &stream : m_stream_list)
+ stream->init();
+
+ // Detect loops and order streams for full update at the same time
+ // Check the number of sources for each stream
+ std::map<sound_stream *, int> depcounts;
+ for(auto &stream : m_stream_list)
+ depcounts[stream.get()] = stream->sources().size();
+
+ // Start from all the ones that don't depend on anything
+ std::vector<sound_stream *> ready_streams;
+ for(auto &dpc : depcounts)
+ if(dpc.second == 0)
+ ready_streams.push_back(dpc.first);
+
+ // Handle all the ready streams in a lifo matter (better for cache when generating sound)
+ while(!ready_streams.empty()) {
+ sound_stream *stream = ready_streams.back();
+ // add the stream to the update order
+ m_ordered_streams.push_back(stream);
+ ready_streams.resize(ready_streams.size() - 1);
+ // reduce the depcount for all the streams that depend on the updated stream
+ for(sound_stream *target : stream->targets())
+ if(!--depcounts[target])
+ // when the depcount is zero, a stream is ready to be updated
+ ready_streams.push_back(target);
+ }
+
+ // If not all streams ended up in the sorted list, we have a loop
+ if(m_ordered_streams.size() != m_stream_list.size()) {
+ // Apply the same algorithm from the other side to the
+ // remaining streams to only keep the ones in the loop
+
+ std::map<sound_stream *, int> inverted_depcounts;
+ for(auto &dpc : depcounts)
+ if(dpc.second)
+ inverted_depcounts[dpc.first] = dpc.first->targets().size();
+ for(auto &dpc : inverted_depcounts)
+ if(dpc.second == 0)
+ ready_streams.push_back(dpc.first);
+ while(!ready_streams.empty()) {
+ sound_stream *stream = ready_streams.back();
+ ready_streams.resize(ready_streams.size() - 1);
+ for(sound_stream *source : stream->sources())
+ if(!--inverted_depcounts[source])
+ ready_streams.push_back(source);
+ }
+ std::string stream_names;
+ for(auto &dpc : inverted_depcounts)
+ if(dpc.second)
+ stream_names += ' ' + dpc.first->name();
+ fatalerror("Loop detected in stream routes:%s", stream_names);
+ }
- // reposition our start to coincide with the current buffer end
- attotime update_start = m_output[outputnum].end_time();
- if (update_start <= end)
- {
- // 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)
- {
- 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_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_input_view[inputnum].samples() > 0);
- sound_assert(m_resampling_disabled || m_input_view[inputnum].sample_rate() == m_sample_rate);
- }
+ if(VERBOSE & LOG_ORDER) {
+ LOG_OUTPUT_FUNC("Order:\n");
+ for(sound_stream *s : m_ordered_streams)
+ LOG_OUTPUT_FUNC("- %s (%d)\n", s->name().c_str(), s->sample_rate());
+ }
+
+ // Registrations for state saving
+ for(auto &stream : m_stream_list)
+ stream->register_state();
+
+ // Compute all the per-stream orders for update()
+ for(auto &stream : m_stream_list)
+ stream->compute_dependants();
+
+ // Create the default effect chain
+ for(u32 effect = 0; effect != audio_effect::COUNT; effect++)
+ m_default_effects.emplace_back(audio_effect::create(effect, machine().sample_rate(), nullptr));
+
+ // Inventory speakers and microphones
+ m_outputs_count = 0;
+ for(speaker_device &dev : speaker_device_enumerator(machine().root_device())) {
+ dev.set_id(m_speakers.size());
+ m_speakers.emplace_back(speaker_info(dev, machine().sample_rate(), m_outputs_count));
+ for(u32 effect = 0; effect != audio_effect::COUNT; effect++)
+ m_speakers.back().m_effects[effect].m_effect.reset(audio_effect::create(effect, machine().sample_rate(), m_default_effects[effect].get()));
+ m_outputs_count += dev.inputs();
+ }
+
+ for(microphone_device &dev : microphone_device_enumerator(machine().root_device())) {
+ dev.set_id(m_microphones.size());
+ m_microphones.emplace_back(microphone_info(dev));
+ }
+
+ // Allocate the buffer to pass for recording
+ m_record_buffer.resize(m_outputs_count * machine().sample_rate(), 0);
+ m_record_samples = 0;
-#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
+ // Have all streams create their initial resamplers
+ for(auto &stream : m_stream_list)
+ stream->create_resamplers();
- // if we have an extended callback, that's all we need
- m_callback_ex(*this, m_input_view, m_output_view);
+ // Then get the initial history sizes
+ for(auto &stream : m_stream_list)
+ stream->lookup_history_sizes();
-#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);
+ m_effects_done = false;
- for (unsigned int outindex = 0; outindex < m_output.size(); outindex++)
- m_output[outindex].m_buffer.flush_wav();
-#endif
+ m_effects_thread = std::make_unique<std::thread>(
+ [this]{ run_effects(); });
+}
+
+
+//**// Effects, input and output management
+
+void sound_manager::input_get(int id, sound_stream &stream)
+{
+ u32 samples = stream.samples();
+ u64 end_pos = stream.sample_index();
+ u32 skip = stream.output_count();
+
+ for(const auto &step : m_microphones[id].m_input_mixing_steps) {
+ auto get_source = [&istream = m_osd_input_streams[step.m_osd_index], this](u32 samples, u64 end_pos, u32 channel) -> const s16 * {
+ if(istream.m_buffer.write_sample() < end_pos) {
+ u32 needed = end_pos - istream.m_buffer.write_sample();
+ istream.m_buffer.prepare_space(needed);
+ machine().osd().sound_stream_source_update(istream.m_id, istream.m_buffer.ptrw(0, 0), needed);
+ istream.m_buffer.commit(needed);
+ }
+ return istream.m_buffer.ptrs(channel, end_pos - samples - istream.m_buffer.sync_sample());
+ };
+
+ switch(step.m_mode) {
+ case mixing_step::CLEAR:
+ case mixing_step::COPY:
+ fatalerror("Impossible step encountered in input\n");
+
+ case mixing_step::ADD: {
+ const s16 *src = get_source(samples, end_pos, step.m_osd_channel);
+ float gain = step.m_linear_volume / 32768.0;
+ for(u32 sample = 0; sample != samples; sample++) {
+ stream.add(step.m_device_channel, sample, *src * gain);
+ src += skip;
+ }
+ break;
+ }
}
}
+}
- // return the requested view
- return read_stream_view(m_output_view[outputnum], start);
+void sound_manager::output_push(int id, sound_stream &stream)
+{
+ auto &spk = m_speakers[id];
+ auto &out = spk.m_buffer;
+ auto &inp = stream.m_input_buffer;
+ int samples = stream.samples();
+ int channels = stream.input_count();
+ out.prepare_space(samples);
+ for(int channel = 0; channel != channels; channel ++)
+ std::copy(inp[channel].begin(), inp[channel].begin() + samples, out.ptrw(channel, 0));
+ out.commit(samples);
+
+ m_record_samples = samples;
+ s16 *outb = m_record_buffer.data() + spk.m_first_output;
+ for(int channel = 0; channel != channels; channel ++) {
+ s16 *outb1 = outb;
+ const float *inb = inp[channel].data();
+ for(int sample = 0; sample != samples; sample++) {
+ *outb1 = std::clamp(int(*inb++ * 32768), -32768, 32767);
+ outb1 += m_outputs_count;
+ }
+ }
}
+void sound_manager::run_effects()
+{
+ std::unique_lock<std::mutex> lock(m_effects_mutex);
+ for(;;) {
+ m_effects_condition.wait(lock);
+ if(m_effects_done)
+ return;
+
+ // Apply the effects
+ for(auto &si : m_speakers)
+ for(u32 i=0; i != si.m_effects.size(); i++) {
+ auto &source = i ? si.m_effects[i-1].m_buffer : si.m_buffer;
+ si.m_effects[i].m_effect->apply(source, si.m_effects[i].m_buffer);
+ source.sync();
+ }
-//-------------------------------------------------
-// apply_sample_rate_changes - if there is a
-// pending sample rate change, apply it now
-//-------------------------------------------------
+ // Apply the mixing steps
+ for(const auto &step : m_output_mixing_steps) {
+ const sample_t *src = step.m_mode == mixing_step::CLEAR ? nullptr : m_speakers[step.m_device_index].m_effects.back().m_buffer.ptrs(step.m_device_channel, 0);
-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;
+ auto &ostream = m_osd_output_streams[step.m_osd_index];
+ u32 samples = ostream.m_samples;
+ s16 *dest = ostream.m_buffer.data() + step.m_osd_channel;
+ u32 skip = ostream.m_channels;
- // 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;
+ switch(step.m_mode) {
+ case mixing_step::CLEAR:
+ for(u32 sample = 0; sample != samples; sample++) {
+ *dest = 0;
+ dest += skip;
+ }
+ break;
- // 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();
+ case mixing_step::COPY: {
+ float gain = 32768 * step.m_linear_volume * m_master_gain;
+ for(u32 sample = 0; sample != samples; sample++) {
+ *dest = std::clamp(int(*src++ * gain), -32768, 32767);
+ dest += skip;
+ }
+ break;
+ }
- // if we're output adaptive, override with the rate of our output
- if (output_adaptive())
- {
- 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;
- }
+ case mixing_step::ADD: {
+ float gain = 32768 * step.m_linear_volume * m_master_gain;
+ for(u32 sample = 0; sample != samples; sample++) {
+ *dest = std::clamp(int(*src++ * gain) + *dest, -32768, 32767);
+ dest += skip;
+ }
+ break;
+ }
+ }
+ }
- // if something is different, process the change
- if (new_rate != SAMPLE_RATE_INVALID && new_rate != m_sample_rate)
- {
- // 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();
+ for(auto &si : m_speakers)
+ si.m_effects.back().m_buffer.sync();
+
+ // Send the result to the osd
+ for(auto &stream : m_osd_output_streams)
+ if(stream.m_samples)
+ machine().osd().sound_stream_sink_update(stream.m_id, stream.m_buffer.data(), stream.m_samples);
}
+}
- // now call through our inputs and apply the rate change there
- for (auto &input : m_input)
- if (input.valid())
- input.apply_sample_rate_changes(updatenum, m_sample_rate);
+std::string sound_manager::effect_chain_tag(s32 index) const
+{
+ return m_speakers[index].m_dev.tag();
}
+std::vector<audio_effect *> sound_manager::effect_chain(s32 index) const
+{
+ std::vector<audio_effect *> res;
+ for(const auto &e : m_speakers[index].m_effects)
+ res.push_back(e.m_effect.get());
+ return res;
+}
-//-------------------------------------------------
-// print_graph_recursive - helper for debugging;
-// prints info on this stream and then recursively
-// prints info on all inputs
-//-------------------------------------------------
+std::vector<audio_effect *> sound_manager::default_effect_chain() const
+{
+ std::vector<audio_effect *> res;
+ for(const auto &e : m_default_effects)
+ res.push_back(e.get());
+ return res;
+}
-#if (SOUND_DEBUG)
-void sound_stream::print_graph_recursive(int indent, int index)
-{
- osd_printf_info("%*s%s Ch.%d @ %d\n", indent, "", name(), index + m_output_base, sample_rate());
- for (int index = 0; index < m_input.size(); index++)
- if (m_input[index].valid())
- {
- 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());
- }
+void sound_manager::default_effect_changed(u32 entry)
+{
+ u32 type = m_default_effects[entry]->type();
+ for(const auto &s : m_speakers)
+ for(const auto &e : s.m_effects)
+ if(e.m_effect->type() == type)
+ e.m_effect->default_changed();
}
-#endif
-//-------------------------------------------------
-// sample_rate_changed - recompute sample
-// rate data, and all streams that are affected
-// by this stream
-//-------------------------------------------------
-void sound_stream::sample_rate_changed()
-{
- // if invalid, just punt
- if (m_sample_rate == SAMPLE_RATE_INVALID)
- return;
- // update all output buffers
- for (auto &output : m_output)
- output.sample_rate_changed(m_sample_rate);
- // if synchronous, prime the timer
- if (synchronous())
- reprime_sync_timer();
-}
-//-------------------------------------------------
-// postload - save/restore callback
-//-------------------------------------------------
-void sound_stream::postload()
-{
- // set the end time of all of our streams to the value saved in m_last_update_end_time
- for (auto &output : m_output)
- output.set_end_time(m_last_update_end_time);
- // recompute the sample rate information
- sample_rate_changed();
-}
//-------------------------------------------------
-// presave - save/restore callback
+// start_recording - begin audio recording
//-------------------------------------------------
-void sound_stream::presave()
+bool sound_manager::start_recording(std::string_view filename)
{
- // save the stream end time
- m_last_update_end_time = m_output[0].end_time();
+ if(m_wavfile)
+ return false;
+ m_wavfile = util::wav_open(filename, machine().sample_rate(), m_outputs_count);
+ return bool(m_wavfile);
+}
+
+bool sound_manager::start_recording()
+{
+ // open the output WAV file if specified
+ char const *const filename = machine().options().wav_write();
+ return *filename ? start_recording(filename) : false;
}
//-------------------------------------------------
-// reprime_sync_timer - set up the next sync
-// timer to go off just a hair after the end of
-// the current sample period
+// stop_recording - end audio recording
//-------------------------------------------------
-void sound_stream::reprime_sync_timer()
+void sound_manager::stop_recording()
{
- attotime curtime = m_device.machine().time();
- attotime target = m_output[0].end_time() + attotime(0, 1);
- m_sync_timer->adjust(target - curtime);
+ // close any open WAV file
+ m_wavfile.reset();
}
//-------------------------------------------------
-// sync_update - timer callback to handle a
-// synchronous stream
+// mute - mute sound output
//-------------------------------------------------
-void sound_stream::sync_update(s32)
+void sound_manager::mute(bool mute, u8 reason)
{
- update();
- reprime_sync_timer();
+ if(mute)
+ m_muted |= reason;
+ else
+ m_muted &= ~reason;
}
//-------------------------------------------------
-// empty_view - return an empty view covering the
-// given time period as a substitute for invalid
-// inputs
+// reset - reset all sound chips
//-------------------------------------------------
-read_stream_view sound_stream::empty_view(attotime start, attotime end)
+sound_manager::speaker_info::speaker_info(speaker_device &dev, u32 rate, u32 first_output) : m_dev(dev), m_first_output(first_output), m_buffer(rate, dev.inputs())
{
- // 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);
+ m_channels = dev.inputs();
+ m_stream = dev.stream();
+ for(u32 i=0; i != audio_effect::COUNT; i++)
+ m_effects.emplace_back(effect_step(rate, dev.inputs()));
}
+sound_manager::microphone_info::microphone_info(microphone_device &dev) : m_dev(dev)
+{
+ m_channels = dev.outputs();
+}
+void sound_manager::reset()
+{
+ LOG_OUTPUT_FUNC("Sound reset\n");
+}
-//**************************************************************************
-// RESAMPLER STREAM
-//**************************************************************************
//-------------------------------------------------
-// default_resampler_stream - derived sound_stream
-// class that handles resampling
+// pause - pause sound output
//-------------------------------------------------
-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)
+void sound_manager::pause()
{
- // create a name
- m_name = "Default Resampler '";
- m_name += device.tag();
- m_name += "'";
+ mute(true, MUTE_REASON_PAUSE);
}
//-------------------------------------------------
-// resampler_sound_update - stream callback
-// handler for resampling an input stream to the
-// target sample rate of the output
+// resume - resume sound output
//-------------------------------------------------
-void default_resampler_stream::resampler_sound_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
+void sound_manager::resume()
{
- sound_assert(inputs.size() == 1);
- sound_assert(outputs.size() == 1);
+ mute(false, MUTE_REASON_PAUSE);
+}
- auto &input = inputs[0];
- auto &output = outputs[0];
- // if the input has an invalid rate, just fill with zeros
- if (input.sample_rate() <= 1)
- {
- output.fill(0);
+//**// Configuration management
+
+void sound_manager::config_load(config_type cfg_type, config_level cfg_level, util::xml::data_node const *parentnode)
+{
+ // If no config file, ignore
+ if(!parentnode)
return;
- }
- // optimize_resampler ensures we should not have equal sample rates
- sound_assert(input.sample_rate() != output.sample_rate());
+ switch(cfg_type) {
+ case config_type::INIT:
+ break;
- // 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;
+ case config_type::CONTROLLER:
+ break;
- // 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)
- {
- output.put(dstindex++, 0);
- output_start += output.sample_period();
+ case config_type::DEFAULT: {
+ // In the global config, get the default effect chain configuration
+
+ util::xml::data_node const *efl_node = parentnode->get_child("default_audio_effects");
+ for(util::xml::data_node const *ef_node = efl_node->get_child("effect"); ef_node != nullptr; ef_node = ef_node->get_next_sibling("effect")) {
+ unsigned int id = ef_node->get_attribute_int("step", 0);
+ std::string type = ef_node->get_attribute_string("type", "");
+ if(id >= 1 && id <= m_default_effects.size() && audio_effect::effect_names[m_default_effects[id-1]->type()] == type)
+ m_default_effects[id-1]->config_load(ef_node);
+ }
+ break;
}
- 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);
+ case config_type::SYSTEM: {
+ // In the per-driver file, get the specific configuration for everything
+
+ // Effects configuration
+ for(util::xml::data_node const *efl_node = parentnode->get_child("audio_effects"); efl_node != nullptr; efl_node = efl_node->get_next_sibling("audio_effects")) {
+ std::string speaker_tag = efl_node->get_attribute_string("tag", "");
+ for(auto &speaker : m_speakers)
+ if(speaker.m_dev.tag() == speaker_tag) {
+ auto &eff = speaker.m_effects;
+ for(util::xml::data_node const *ef_node = efl_node->get_child("effect"); ef_node != nullptr; ef_node = ef_node->get_next_sibling("effect")) {
+ unsigned int id = ef_node->get_attribute_int("step", 0);
+ std::string type = ef_node->get_attribute_string("type", "");
+ if(id >= 1 && id <= m_default_effects.size() && audio_effect::effect_names[eff[id-1].m_effect->type()] == type)
+ eff[id-1].m_effect->config_load(ef_node);
+ }
+ break;
+ }
+ }
- // 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);
+ // All levels
+ const util::xml::data_node *lv_node = parentnode->get_child("master_volume");
+ if(lv_node)
+ m_master_gain = lv_node->get_attribute_float("gain", 1.0);
- // input is undersampled: point sample except where our sample period covers a boundary
- s32 srcindex = 0;
- if (step < 1.0)
- {
- stream_buffer::sample_t cursample = rebased.get(srcindex++);
- for ( ; dstindex < numsamples; dstindex++)
- {
- // if still within the current sample, just replicate
- srcpos += step;
- if (srcpos <= 1.0)
- output.put(dstindex, cursample);
-
- // if crossing a sample boundary, blend with the neighbor
- else
- {
- 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));
- }
+ for(lv_node = parentnode->get_child("device_volume"); lv_node != nullptr; lv_node = lv_node->get_next_sibling("device_volume")) {
+ std::string device_tag = lv_node->get_attribute_string("device", "");
+ device_sound_interface *intf = dynamic_cast<device_sound_interface *>(m_machine.root_device().subdevice(device_tag));
+ if(intf)
+ intf->set_user_output_gain(lv_node->get_attribute_float("gain", 1.0));
}
- sound_assert(srcindex <= rebased.samples());
- }
- // input is oversampled: sum the energy
- else
- {
- float cursample = rebased.get(srcindex++);
- for ( ; dstindex < numsamples; dstindex++)
- {
- // 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 += rebased.get(srcindex++);
- remaining -= 1.0;
- }
+ for(lv_node = parentnode->get_child("device_channel_volume"); lv_node != nullptr; lv_node = lv_node->get_next_sibling("device_channel_volume")) {
+ std::string device_tag = lv_node->get_attribute_string("device", "");
+ int channel = lv_node->get_attribute_int("channel", -1);
+ device_sound_interface *intf = dynamic_cast<device_sound_interface *>(m_machine.root_device().subdevice(device_tag));
+ if(intf && channel >= 0 && channel < intf->outputs())
+ intf->set_user_output_gain(channel, lv_node->get_attribute_float("gain", 1.0));
+ }
- // add in the final partial sample
- cursample = rebased.get(srcindex++);
- sample += cursample * remaining;
- output.put(dstindex, sample * stepinv);
- // our position is now the remainder
- srcpos = remaining;
- sound_assert(srcindex <= rebased.samples());
+ // Mapping configuration
+ m_configs.clear();
+ for(util::xml::data_node const *node = parentnode->get_child("sound_map"); node != nullptr; node = node->get_next_sibling("sound_map")) {
+ m_configs.emplace_back(config_mapping { node->get_attribute_string("tag", "") });
+ auto &config = m_configs.back();
+ for(util::xml::data_node const *nmap = node->get_child("node_mapping"); nmap != nullptr; nmap = nmap->get_next_sibling("node_mapping"))
+ config.m_node_mappings.emplace_back(std::pair<std::string, float>(nmap->get_attribute_string("node", ""), nmap->get_attribute_float("db", 0)));
+ for(util::xml::data_node const *cmap = node->get_child("channel_mapping"); cmap != nullptr; cmap = cmap->get_next_sibling("channel_mapping"))
+ config.m_channel_mappings.emplace_back(std::tuple<u32, std::string, u32, float>(cmap->get_attribute_int("guest_channel", 0),
+ cmap->get_attribute_string("node", ""),
+ cmap->get_attribute_int("node_channel", 0),
+ cmap->get_attribute_float("db", 0)));
}
+ break;
}
-}
-
+ case config_type::FINAL:
+ break;
+ }
+}
-//**************************************************************************
-// SOUND MANAGER
-//**************************************************************************
//-------------------------------------------------
-// sound_manager - constructor
+// config_save - save data to the configuration
+// file
//-------------------------------------------------
-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_compressor_enabled(machine.options().compressor()),
- m_muted(0),
- m_nosound_mode(machine.osd().no_sound()),
- m_attenuation(0),
- m_unique_id(0),
- m_wavfile(),
- m_first_reset(true)
+void sound_manager::config_save(config_type cfg_type, util::xml::data_node *parentnode)
{
- // count the mixers
-#if VERBOSE
- mixer_interface_enumerator iter(machine.root_device());
- LOG("total mixers = %d\n", iter.count());
-#endif
+ switch(cfg_type) {
+ case config_type::INIT:
+ break;
+
+ case config_type::CONTROLLER:
+ break;
+
+ case config_type::DEFAULT: {
+ // In the global config, save the default effect chain configuration
+ util::xml::data_node *const efl_node = parentnode->add_child("default_audio_effects", nullptr);
+ for(u32 ei = 0; ei != m_default_effects.size(); ei++) {
+ const audio_effect *e = m_default_effects[ei].get();
+ util::xml::data_node *const ef_node = efl_node->add_child("effect", nullptr);
+ ef_node->set_attribute_int("step", ei+1);
+ ef_node->set_attribute("type", audio_effect::effect_names[e->type()]);
+ e->config_save(ef_node);
+ }
+ break;
+ }
- // register callbacks
- machine.configuration().config_register(
- "mixer",
- configuration_manager::load_delegate(&sound_manager::config_load, this),
- configuration_manager::save_delegate(&sound_manager::config_save, this));
- machine.add_notifier(MACHINE_NOTIFY_PAUSE, machine_notify_delegate(&sound_manager::pause, this));
- machine.add_notifier(MACHINE_NOTIFY_RESUME, machine_notify_delegate(&sound_manager::resume, this));
- machine.add_notifier(MACHINE_NOTIFY_RESET, machine_notify_delegate(&sound_manager::reset, this));
- machine.add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(&sound_manager::stop_recording, this));
+ case config_type::SYSTEM: {
+ // In the per-driver file, save the specific configuration for everything
+
+ // Effects configuration
+ for(const auto &speaker : m_speakers) {
+ util::xml::data_node *const efl_node = parentnode->add_child("audio_effects", nullptr);
+ efl_node->set_attribute("tag", speaker.m_dev.tag());
+ for(u32 ei = 0; ei != speaker.m_effects.size(); ei++) {
+ const audio_effect *e = speaker.m_effects[ei].m_effect.get();
+ util::xml::data_node *const ef_node = efl_node->add_child("effect", nullptr);
+ ef_node->set_attribute_int("step", ei+1);
+ ef_node->set_attribute("type", audio_effect::effect_names[e->type()]);
+ e->config_save(ef_node);
+ }
+ }
- // register global states
- machine.save().save_item(NAME(m_last_update));
+ // All levels
+ if(m_master_gain != 1.0) {
+ util::xml::data_node *const lv_node = parentnode->add_child("master_volume", nullptr);
+ lv_node->set_attribute_float("gain", m_master_gain);
+ }
+ for(device_sound_interface &snd : sound_interface_enumerator(m_machine.root_device())) {
+ // Don't add microphones, speakers or devices without outputs
+ if(dynamic_cast<sound_io_device *>(&snd) || !snd.outputs())
+ continue;
+ if(snd.user_output_gain() != 1.0) {
+ util::xml::data_node *const lv_node = parentnode->add_child("device_volume", nullptr);
+ lv_node->set_attribute("device", snd.device().tag());
+ lv_node->set_attribute_float("gain", snd.user_output_gain());
+ }
+ for(int channel = 0; channel != snd.outputs(); channel ++)
+ if(snd.user_output_gain(channel) != 1.0) {
+ util::xml::data_node *const lv_node = parentnode->add_child("device_channel_volume", nullptr);
+ lv_node->set_attribute("device", snd.device().tag());
+ lv_node->set_attribute_int("channel", channel);
+ lv_node->set_attribute_float("gain", snd.user_output_gain(channel));
+ }
+ }
- // set the starting attenuation
- set_attenuation(machine.options().volume());
+ // Mapping configuration
+ auto output_one = [this, parentnode](sound_io_device &dev) {
+ for(const auto &config : m_configs)
+ if(config.m_name == dev.tag()) {
+ util::xml::data_node *const sp_node = parentnode->add_child("sound_map", nullptr);
+ sp_node->set_attribute("tag", dev.tag());
+ for(const auto &nmap : config.m_node_mappings) {
+ util::xml::data_node *const node = sp_node->add_child("node_mapping", nullptr);
+ node->set_attribute("node", nmap.first.c_str());
+ node->set_attribute_float("db", nmap.second);
+ }
+ for(const auto &cmap : config.m_channel_mappings) {
+ util::xml::data_node *const node = sp_node->add_child("channel_mapping", nullptr);
+ node->set_attribute_int("guest_channel", std::get<0>(cmap));
+ node->set_attribute("node", std::get<1>(cmap).c_str());
+ node->set_attribute_int("node_channel", std::get<2>(cmap));
+ node->set_attribute_float("db", std::get<3>(cmap));
+ }
+ return;
+ }
+ };
+
+ for(auto &spk : m_speakers)
+ output_one(spk.m_dev);
+ for(auto &mic : m_microphones)
+ output_one(mic.m_dev);
+ break;
+ }
- // start the periodic update flushing timer
- m_update_timer = machine.scheduler().timer_alloc(timer_expired_delegate(FUNC(sound_manager::update), this));
- m_update_timer->adjust(STREAMS_UPDATE_ATTOTIME, 0, STREAMS_UPDATE_ATTOTIME);
+ case config_type::FINAL:
+ break;
+ }
}
-//-------------------------------------------------
-// sound_manager - destructor
-//-------------------------------------------------
-sound_manager::~sound_manager()
+//**// Mapping between speakers/microphones and OSD endpoints
+
+sound_manager::config_mapping &sound_manager::config_get_sound_io(sound_io_device *dev)
{
+ for(auto &config : m_configs)
+ if(config.m_name == dev->tag())
+ return config;
+ m_configs.emplace_back(config_mapping { dev->tag() });
+ return m_configs.back();
}
+void sound_manager::config_add_sound_io_connection_node(sound_io_device *dev, std::string name, float db)
+{
+ internal_config_add_sound_io_connection_node(dev, name, db);
+ m_osd_info.m_generation --;
+}
-//-------------------------------------------------
-// stream_alloc - allocate a new stream with the
-// new-style callback and flags
-//-------------------------------------------------
+void sound_manager::internal_config_add_sound_io_connection_node(sound_io_device *dev, std::string name, float db)
+{
+ auto &config = config_get_sound_io(dev);
+ for(auto &nmap : config.m_node_mappings)
+ if(nmap.first == name)
+ return;
+ config.m_node_mappings.emplace_back(std::pair<std::string, float>(name, db));
+}
-sound_stream *sound_manager::stream_alloc(device_t &device, u32 inputs, u32 outputs, u32 sample_rate, stream_update_delegate callback, sound_stream_flags flags)
+void sound_manager::config_add_sound_io_connection_default(sound_io_device *dev, float db)
{
- // determine output base
- u32 output_base = 0;
- for (auto &stream : m_stream_list)
- if (&stream->device() == &device)
- output_base += stream->output_count();
+ internal_config_add_sound_io_connection_default(dev, db);
+ m_osd_info.m_generation --;
+}
- 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();
+void sound_manager::internal_config_add_sound_io_connection_default(sound_io_device *dev, float db)
+{
+ auto &config = config_get_sound_io(dev);
+ for(auto &nmap : config.m_node_mappings)
+ if(nmap.first == "")
+ return;
+ config.m_node_mappings.emplace_back(std::pair<std::string, float>("", db));
}
+void sound_manager::config_remove_sound_io_connection_node(sound_io_device *dev, std::string name)
+{
+ internal_config_remove_sound_io_connection_node(dev, name);
+ m_osd_info.m_generation --;
+}
-//-------------------------------------------------
-// start_recording - begin audio recording
-//-------------------------------------------------
+void sound_manager::internal_config_remove_sound_io_connection_node(sound_io_device *dev, std::string name)
+{
+ auto &config = config_get_sound_io(dev);
+ for(auto i = config.m_node_mappings.begin(); i != config.m_node_mappings.end(); i++)
+ if(i->first == name) {
+ config.m_node_mappings.erase(i);
+ return;
+ }
+}
-bool sound_manager::start_recording(std::string_view filename)
+void sound_manager::config_remove_sound_io_connection_default(sound_io_device *dev)
{
- if (m_wavfile)
- return false;
- m_wavfile = util::wav_open(filename, machine().sample_rate(), 2);
- return bool(m_wavfile);
+ internal_config_remove_sound_io_connection_default(dev);
+ m_osd_info.m_generation --;
}
-bool sound_manager::start_recording()
+void sound_manager::internal_config_remove_sound_io_connection_default(sound_io_device *dev)
{
- // open the output WAV file if specified
- char const *const filename = machine().options().wav_write();
- return *filename ? start_recording(filename) : false;
+ auto &config = config_get_sound_io(dev);
+ for(auto i = config.m_node_mappings.begin(); i != config.m_node_mappings.end(); i++)
+ if(i->first == "") {
+ config.m_node_mappings.erase(i);
+ return;
+ }
}
+void sound_manager::config_set_volume_sound_io_connection_node(sound_io_device *dev, std::string name, float db)
+{
+ internal_config_set_volume_sound_io_connection_node(dev, name, db);
+ m_osd_info.m_generation --;
+}
-//-------------------------------------------------
-// stop_recording - end audio recording
-//-------------------------------------------------
+void sound_manager::internal_config_set_volume_sound_io_connection_node(sound_io_device *dev, std::string name, float db)
+{
+ auto &config = config_get_sound_io(dev);
+ for(auto &nmap : config.m_node_mappings)
+ if(nmap.first == name) {
+ nmap.second = db;
+ return;
+ }
+}
-void sound_manager::stop_recording()
+void sound_manager::config_set_volume_sound_io_connection_default(sound_io_device *dev, float db)
{
- // close any open WAV file
- m_wavfile.reset();
+ internal_config_set_volume_sound_io_connection_default(dev, db);
+ m_osd_info.m_generation --;
}
+void sound_manager::internal_config_set_volume_sound_io_connection_default(sound_io_device *dev, float db)
+{
+ auto &config = config_get_sound_io(dev);
+ for(auto &nmap : config.m_node_mappings)
+ if(nmap.first == "") {
+ nmap.second = db;
+ return;
+ }
+}
-//-------------------------------------------------
-// set_attenuation - set the global volume
-//-------------------------------------------------
-void sound_manager::set_attenuation(float attenuation)
+void sound_manager::config_add_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel, float db)
{
- // currently OSD only supports integral attenuation
- m_attenuation = int(attenuation);
- machine().osd().set_mastervolume(m_muted ? -32 : m_attenuation);
+ internal_config_add_sound_io_channel_connection_node(dev, guest_channel, name, node_channel, db);
+ m_osd_info.m_generation --;
}
+void sound_manager::internal_config_add_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel, float db)
+{
+ auto &config = config_get_sound_io(dev);
+ for(auto &cmap : config.m_channel_mappings)
+ if(std::get<0>(cmap) == guest_channel && std::get<1>(cmap) == name && std::get<2>(cmap) == node_channel)
+ return;
+ config.m_channel_mappings.emplace_back(std::tuple<u32, std::string, u32, float>(guest_channel, name, node_channel, db));
+}
-//-------------------------------------------------
-// indexed_mixer_input - return the mixer
-// device and input index of the global mixer
-// input
-//-------------------------------------------------
+void sound_manager::config_add_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel, float db)
+{
+ internal_config_add_sound_io_channel_connection_default(dev, guest_channel, node_channel, db);
+ m_osd_info.m_generation --;
+}
-bool sound_manager::indexed_mixer_input(int index, mixer_input &info) const
+void sound_manager::internal_config_add_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel, float db)
{
- // scan through the mixers until we find the indexed input
- for (device_mixer_interface &mixer : mixer_interface_enumerator(machine().root_device()))
- {
- if (index < mixer.inputs())
- {
- info.mixer = &mixer;
- info.stream = mixer.input_to_stream_input(index, info.inputnum);
- sound_assert(info.stream != nullptr);
- return true;
- }
- index -= mixer.inputs();
- }
+ auto &config = config_get_sound_io(dev);
+ for(auto &cmap : config.m_channel_mappings)
+ if(std::get<0>(cmap) == guest_channel && std::get<1>(cmap) == "" && std::get<2>(cmap) == node_channel)
+ return;
+ config.m_channel_mappings.emplace_back(std::tuple<u32, std::string, u32, float>(guest_channel, "", node_channel, db));
+}
- // didn't locate
- info.mixer = nullptr;
- return false;
+void sound_manager::config_remove_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel)
+{
+ internal_config_remove_sound_io_channel_connection_node(dev, guest_channel, name, node_channel);
+ m_osd_info.m_generation --;
}
+void sound_manager::internal_config_remove_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel)
+{
+ auto &config = config_get_sound_io(dev);
+ for(auto i = config.m_channel_mappings.begin(); i != config.m_channel_mappings.end(); i++)
+ if(std::get<0>(*i) == guest_channel && std::get<1>(*i) == name && std::get<2>(*i) == node_channel) {
+ config.m_channel_mappings.erase(i);
+ return;
+ }
+}
-//-------------------------------------------------
-// samples - fills the specified buffer with
-// 16-bit stereo audio samples generated during
-// the current frame
-//-------------------------------------------------
+void sound_manager::config_remove_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel)
+{
+ internal_config_remove_sound_io_channel_connection_default(dev, guest_channel, node_channel);
+ m_osd_info.m_generation --;
+}
-void sound_manager::samples(s16 *buffer)
+void sound_manager::internal_config_remove_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel)
{
- for (int sample = 0; sample < m_samples_this_update * 2; sample++)
- *buffer++ = m_finalmix[sample];
+ auto &config = config_get_sound_io(dev);
+ for(auto i = config.m_channel_mappings.begin(); i != config.m_channel_mappings.end(); i++)
+ if(std::get<0>(*i) == guest_channel && std::get<1>(*i) == "" && std::get<2>(*i) == node_channel) {
+ config.m_channel_mappings.erase(i);
+ return;
+ }
}
+void sound_manager::config_set_volume_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel, float db)
+{
+ internal_config_set_volume_sound_io_channel_connection_node(dev, guest_channel, name, node_channel, db);
+ m_osd_info.m_generation --;
+}
-//-------------------------------------------------
-// mute - mute sound output
-//-------------------------------------------------
+void sound_manager::internal_config_set_volume_sound_io_channel_connection_node(sound_io_device *dev, u32 guest_channel, std::string name, u32 node_channel, float db)
+{
+ auto &config = config_get_sound_io(dev);
+ for(auto &cmap : config.m_channel_mappings)
+ if(std::get<0>(cmap) == guest_channel && std::get<1>(cmap) == name && std::get<2>(cmap) == node_channel) {
+ std::get<3>(cmap) = db;
+ return;
+ }
+}
-void sound_manager::mute(bool mute, u8 reason)
+void sound_manager::config_set_volume_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel, float db)
{
- bool old_muted = m_muted;
- if (mute)
- m_muted |= reason;
- else
- m_muted &= ~reason;
+ internal_config_set_volume_sound_io_channel_connection_default(dev, guest_channel, node_channel, db);
+ m_osd_info.m_generation --;
+}
- if(old_muted != (m_muted != 0))
- set_attenuation(m_attenuation);
+void sound_manager::internal_config_set_volume_sound_io_channel_connection_default(sound_io_device *dev, u32 guest_channel, u32 node_channel, float db)
+{
+ auto &config = config_get_sound_io(dev);
+ for(auto &cmap : config.m_channel_mappings)
+ if(std::get<0>(cmap) == guest_channel && std::get<1>(cmap) == "" && std::get<2>(cmap) == node_channel) {
+ std::get<3>(cmap) = db;
+ return;
+ }
}
+void sound_manager::startup_cleanups()
+{
+ auto osd_info = machine().osd().sound_get_information();
+
+ // for every sound_io device that does not have a configuration entry, add a
+ // mapping to default
+ auto default_one = [this](sound_io_device &dev) {
+ for(const auto &config : m_configs)
+ if(config.m_name == dev.tag())
+ return;
+ m_configs.emplace_back(config_mapping { dev.tag() });
+ m_configs.back().m_node_mappings.emplace_back(std::pair<std::string, float>("", 0.0));
+ };
+
+ for(sound_io_device &dev : speaker_device_enumerator(machine().root_device()))
+ default_one(dev);
+ for(sound_io_device &dev : microphone_device_enumerator(machine().root_device()))
+ default_one(dev);
+
+ // If there's no default sink replace all the default sink config
+ // entries into the first sink available
+ if(!osd_info.m_default_sink) {
+ std::string first_sink_name;
+ for(const auto &node : osd_info.m_nodes)
+ if(node.m_sinks) {
+ first_sink_name = node.name();
+ break;
+ }
-//-------------------------------------------------
-// recursive_remove_stream_from_orphan_list -
-// remove the given stream from the orphan list
-// and recursively remove all our inputs
-//-------------------------------------------------
+ if(first_sink_name != "")
+ for(auto &config : m_configs) {
+ for(auto &nmap : config.m_node_mappings)
+ if(nmap.first == "")
+ nmap.first = first_sink_name;
+ for(auto &cmap : config.m_channel_mappings)
+ if(std::get<1>(cmap) == "")
+ std::get<1>(cmap) = first_sink_name;
+ }
+ }
-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());
- }
-}
+ // If there's no default source replace all the default source config
+ // entries into the first source available
+ if(!osd_info.m_default_source) {
+ std::string first_source_name;
+ for(const auto &node : osd_info.m_nodes)
+ if(node.m_sources) {
+ first_source_name = node.name();
+ break;
+ }
-//-------------------------------------------------
-// apply_sample_rate_changes - recursively
-// update sample rates throughout the system
-//-------------------------------------------------
+ if(first_source_name != "")
+ for(auto &config : m_configs) {
+ for(auto &nmap : config.m_node_mappings)
+ if(nmap.first == "")
+ nmap.first = first_source_name;
+ for(auto &cmap : config.m_channel_mappings)
+ if(std::get<1>(cmap) == "")
+ std::get<1>(cmap) = first_source_name;
+ }
+ }
+}
-void sound_manager::apply_sample_rate_changes()
+template<bool is_output, typename S> void sound_manager::apply_osd_changes(std::vector<S> &streams)
{
- // update sample rates if they have changed
- for (speaker_device &speaker : speaker_device_enumerator(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());
+ // Apply host system volume and routing changes to the internal structures
+ for(S &stream : streams) {
+ u32 sidx;
+ for(sidx = 0; sidx != m_osd_info.m_streams.size() && m_osd_info.m_streams[sidx].m_id != stream.m_id; sidx++);
+ // If the stream has been lost, continue. It will be cleared in update_osd_streams.
+ if(sidx == m_osd_info.m_streams.size())
+ continue;
+
+ // Check if the target and/or the volumes changed
+ bool node_changed = stream.m_node != m_osd_info.m_streams[sidx].m_node;
+ bool volume_changed = !std::equal(stream.m_volumes.begin(), stream.m_volumes.end(), m_osd_info.m_streams[sidx].m_volumes.begin(), m_osd_info.m_streams[sidx].m_volumes.end());
+
+ if(node_changed || volume_changed) {
+ // Check if a node change is just tracking the system default
+ bool system_default_tracking = node_changed && stream.m_is_system_default && m_osd_info.m_streams[sidx].m_node == (is_output ? m_osd_info.m_default_sink : m_osd_info.m_default_source);
+
+ // Find the config entry for the sound_io
+ config_mapping *config = nullptr;
+ for(auto &conf : m_configs)
+ if(conf.m_name == stream.m_dev->tag()) {
+ config = &conf;
+ break;
+ }
+ if(!config)
+ continue;
+
+ // Retrieve the old node name, and, if it's different, the new node name
+ std::string old_node_name = stream.m_node_name;
+ std::string new_node_name;
+ if(node_changed) {
+ for(const auto &node : m_osd_info.m_nodes)
+ if(node.m_id == m_osd_info.m_streams[sidx].m_node) {
+ new_node_name = node.name();
+ break;
+ }
+ // That's really, really not supposed to happen
+ if(new_node_name.empty())
+ continue;
+ } else
+ new_node_name = old_node_name;
+
+ // Separate the cases on full mapping vs. channel mapping
+ if(!stream.m_is_channel_mapping) {
+ // Full mapping
+ // Find the index of the config mapping entry that generated the stream, if there's still one.
+ // Note that a default system stream has the empty string as a name
+ u32 index;
+ for(index = 0; index != config->m_node_mappings.size(); index++)
+ if(config->m_node_mappings[index].first == old_node_name)
+ break;
+ if(index == config->m_node_mappings.size())
+ continue;
+
+ // If the target node changed, write it down
+ if(node_changed) {
+ if(!system_default_tracking) {
+ config->m_node_mappings[index].first = new_node_name;
+ stream.m_node_name = new_node_name;
+ stream.m_is_system_default = false;
+ }
+ stream.m_node = m_osd_info.m_streams[sidx].m_node;
+ }
+
+ // If the volume changed, there are two
+ // possibilities: either the channels split, or
+ // they didn't.
+ if(volume_changed) {
+ // Check is all the channel volumes are the same
+ float new_volume = m_osd_info.m_streams[sidx].m_volumes[0];
+ bool same = true;
+ for(u32 i = 1; i != m_osd_info.m_streams[sidx].m_volumes.size(); i++)
+ if(m_osd_info.m_streams[sidx].m_volumes[i] != new_volume) {
+ same = false;
+ break;
+ }
+ if(same) {
+ // All the same volume, just note down the new volume
+ stream.m_volumes = m_osd_info.m_streams[sidx].m_volumes;
+ config->m_node_mappings[index].second = new_volume;
+
+ } else {
+ const osd::audio_info::node_info *node = nullptr;
+ for(const auto &n : m_osd_info.m_nodes)
+ if(n.m_id == stream.m_node) {
+ node = &n;
+ break;
+ }
+ for(u32 channel = 0; channel != stream.m_channels; channel++) {
+ std::vector<u32> targets = find_channel_mapping(stream.m_dev->get_position(channel), node);
+ for(u32 tchannel : targets)
+ if(stream.m_node_name == "")
+ internal_config_add_sound_io_channel_connection_default(stream.m_dev, channel, tchannel, m_osd_info.m_streams[sidx].m_volumes[tchannel]);
+ else
+ internal_config_add_sound_io_channel_connection_node(stream.m_dev, channel, stream.m_node_name, tchannel, m_osd_info.m_streams[sidx].m_volumes[tchannel]);
+ }
+ config->m_node_mappings.erase(config->m_node_mappings.begin() + index);
+ }
+ }
+ } else {
+ // Channel mapping
+ for(u32 channel = 0; channel != stream.m_channels; channel++) {
+ if(stream.m_unused_channels_mask & (1 << channel))
+ continue;
+
+ // Find the index of the config mapping entry that generated the stream channel, if there's still one.
+ // Note that a default system stream has the empty string as a name
+ u32 index;
+ for(index = 0; index != config->m_channel_mappings.size(); index++)
+ if(std::get<1>(config->m_channel_mappings[index]) == old_node_name &&
+ std::get<2>(config->m_channel_mappings[index]) == channel)
+ break;
+ if(index == config->m_channel_mappings.size())
+ continue;
+
+ // If the target node changed, write it down
+ if(node_changed) {
+ if(!system_default_tracking) {
+ std::get<1>(config->m_channel_mappings[index]) = new_node_name;
+ stream.m_node_name = new_node_name;
+ stream.m_is_system_default = false;
+ }
+ stream.m_node = m_osd_info.m_streams[sidx].m_node;
+ }
+
+ // If the volume changed, write in down too
+ if(volume_changed) {
+ std::get<3>(config->m_channel_mappings[index]) = m_osd_info.m_streams[sidx].m_volumes[channel];
+ stream.m_volumes[channel] = m_osd_info.m_streams[sidx].m_volumes[channel];
+ }
+ }
+ }
}
}
}
-
-//-------------------------------------------------
-// reset - reset all sound chips
-//-------------------------------------------------
-
-void sound_manager::reset()
+void sound_manager::osd_information_update()
{
- // reset all the sound chips
- for (device_sound_interface &sound : sound_interface_enumerator(machine().root_device()))
- sound.device().reset();
+ // Get a snapshot of the current information
+ m_osd_info = machine().osd().sound_get_information();
+
+ // Analyze the streams to see if anything changed, but only in the
+ // split stream case.
+ if(machine().osd().sound_split_streams_per_source()) {
+ apply_osd_changes<false, osd_input_stream >(m_osd_input_streams );
+ apply_osd_changes<false, osd_output_stream>(m_osd_output_streams);
+ }
- // 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;
+void sound_manager::generate_mapping()
+{
+ auto find_node = [this](std::string name) -> u32 {
+ for(const auto &node : m_osd_info.m_nodes)
+ if(node.name() == name)
+ return node.m_id;
+ return 0;
+ };
+
+ m_mappings.clear();
+ for(speaker_info &speaker : m_speakers) {
+ auto &config = config_get_sound_io(&speaker.m_dev);
+ m_mappings.emplace_back(mapping { &speaker.m_dev });
+ auto &omap = m_mappings.back();
+
+ std::vector<std::string> node_to_remove;
+ for(auto &nmap : config.m_node_mappings) {
+ if(nmap.first == "") {
+ if(m_osd_info.m_default_sink)
+ omap.m_node_mappings.emplace_back(mapping::node_mapping { m_osd_info.m_default_sink, nmap.second, true });
+ } else {
+ u32 node_id = find_node(nmap.first);
+ if(node_id != 0)
+ omap.m_node_mappings.emplace_back(mapping::node_mapping { node_id, nmap.second, false });
+ else
+ node_to_remove.push_back(nmap.first);
+ }
+ }
- // put all the streams on the orphan list to start
- for (auto &stream : m_stream_list)
- m_orphan_stream_list[stream.get()] = 0;
+ for(auto &nmap: node_to_remove)
+ internal_config_remove_sound_io_connection_node(&speaker.m_dev, nmap);
+
+ std::vector<std::tuple<u32, std::string, u32>> channel_map_to_remove;
+ for(auto &cmap : config.m_channel_mappings) {
+ if(std::get<1>(cmap) == "") {
+ if(m_osd_info.m_default_sink)
+ omap.m_channel_mappings.emplace_back(mapping::channel_mapping { std::get<0>(cmap), m_osd_info.m_default_sink, std::get<2>(cmap), std::get<3>(cmap), true });
+ } else {
+ u32 node_id = find_node(std::get<1>(cmap));
+ if(node_id != 0)
+ omap.m_channel_mappings.emplace_back(mapping::channel_mapping { std::get<0>(cmap), node_id, std::get<2>(cmap), std::get<3>(cmap), false });
+ else
+ channel_map_to_remove.push_back(std::tuple<u32, std::string, u32>(std::get<0>(cmap), std::get<1>(cmap), std::get<2>(cmap)));
+ }
+ }
- // then walk the graph like we do on update and remove any we touch
- for (speaker_device &speaker : speaker_device_enumerator(machine().root_device()))
- {
- int dummy;
- sound_stream *const output = speaker.output_to_stream_output(0, dummy);
- if (output)
- recursive_remove_stream_from_orphan_list(output);
+ for(auto &cmap : channel_map_to_remove)
+ internal_config_remove_sound_io_channel_connection_node(&speaker.m_dev, std::get<0>(cmap), std::get<1>(cmap), std::get<2>(cmap));
+ }
- m_speakers.emplace_back(speaker);
+ for(microphone_info &mic : m_microphones) {
+ auto &config = config_get_sound_io(&mic.m_dev);
+ m_mappings.emplace_back(mapping { &mic.m_dev });
+ auto &omap = m_mappings.back();
+
+ std::vector<std::string> node_to_remove;
+ for(auto &nmap : config.m_node_mappings) {
+ if(nmap.first == "") {
+ if(m_osd_info.m_default_source)
+ omap.m_node_mappings.emplace_back(mapping::node_mapping { m_osd_info.m_default_source, nmap.second, true });
+ } else {
+ u32 node_id = find_node(nmap.first);
+ if(node_id != 0)
+ omap.m_node_mappings.emplace_back(mapping::node_mapping { node_id, nmap.second, false });
+ else
+ node_to_remove.push_back(nmap.first);
+ }
}
-#if (SOUND_DEBUG)
- // dump the sound graph when we start up
- for (speaker_device &speaker : speaker_device_enumerator(machine().root_device()))
- {
- int index;
- sound_stream *output = speaker.output_to_stream_output(0, index);
- if (output != nullptr)
- output->print_graph_recursive(0, index);
+ for(auto &nmap: node_to_remove)
+ internal_config_remove_sound_io_connection_node(&mic.m_dev, nmap);
+
+ std::vector<std::tuple<u32, std::string, u32>> channel_map_to_remove;
+ for(auto &cmap : config.m_channel_mappings) {
+ if(std::get<1>(cmap) == "") {
+ if(m_osd_info.m_default_source)
+ omap.m_channel_mappings.emplace_back(mapping::channel_mapping { std::get<0>(cmap), m_osd_info.m_default_source, std::get<2>(cmap), std::get<3>(cmap), true });
+ } else {
+ u32 node_id = find_node(std::get<1>(cmap));
+ if(node_id != 0)
+ omap.m_channel_mappings.emplace_back(mapping::channel_mapping { std::get<0>(cmap), node_id, std::get<2>(cmap), std::get<3>(cmap), false });
+ else
+ channel_map_to_remove.push_back(std::tuple<u32, std::string, u32>(std::get<0>(cmap), std::get<1>(cmap), std::get<2>(cmap)));
+ }
}
- // 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
+ for(auto &cmap : channel_map_to_remove)
+ internal_config_remove_sound_io_channel_connection_node(&mic.m_dev, std::get<0>(cmap), std::get<1>(cmap), std::get<2>(cmap));
}
}
+// Find where to map a sound_io channel into a node's channels depending on their positions
-//-------------------------------------------------
-// pause - pause sound output
-//-------------------------------------------------
-
-void sound_manager::pause()
+std::vector<u32> sound_manager::find_channel_mapping(const std::array<double, 3> &position, const osd::audio_info::node_info *node)
{
- mute(true, MUTE_REASON_PAUSE);
+ std::vector<u32> result;
+ if(position[0] == 0 && position[1] == 0 && position[2] == 0)
+ return result;
+ double best_dist = -1;
+ for(u32 port = 0; port != node->m_port_positions.size(); port++)
+ if(node->m_port_positions[port][0] || node->m_port_positions[port][1] || node->m_port_positions[port][2]) {
+ double dx = position[0] - node->m_port_positions[port][0];
+ double dy = position[1] - node->m_port_positions[port][1];
+ double dz = position[2] - node->m_port_positions[port][2];
+ double dist = dx*dx + dy*dy + dz*dz;
+ if(best_dist == -1 || dist < best_dist) {
+ best_dist = dist;
+ result.clear();
+ result.push_back(port);
+ } else if(best_dist == dist)
+ result.push_back(port);
+ }
+ return result;
}
-//-------------------------------------------------
-// resume - resume sound output
-//-------------------------------------------------
-
-void sound_manager::resume()
+void sound_manager::update_osd_streams()
{
- mute(false, MUTE_REASON_PAUSE);
-}
+ std::unique_lock<std::mutex> lock(m_effects_mutex);
+ auto current_input_streams = std::move(m_osd_input_streams);
+ auto current_output_streams = std::move(m_osd_output_streams);
+ m_osd_input_streams.clear();
+ m_osd_output_streams.clear();
+
+ // Find the index of a sound_io_device in the speaker_info vector or the microphone_info vector
+
+ auto find_sound_io_index = [this](sound_io_device *dev) -> u32 {
+ for(u32 si = 0; si != m_speakers.size(); si++)
+ if(&m_speakers[si].m_dev == dev)
+ return si;
+ for(u32 si = 0; si != m_microphones.size(); si++)
+ if(&m_microphones[si].m_dev == dev)
+ return si;
+ return 0; // Can't happen
+ };
+
+
+ // Find a pointer to a node_info from the node id
+ auto find_node_info = [this](u32 node) -> const osd::audio_info::node_info * {
+ for(const auto &ni : m_osd_info.m_nodes) {
+ if(ni.m_id == node)
+ return &ni;
+ }
+ // Can't happen
+ return nullptr;
+ };
+
+ // Two possible mapping methods depending on the osd capabilities
+
+ for(auto &m : m_microphones)
+ m.m_input_mixing_steps.clear();
+ m_output_mixing_steps.clear();
+
+ auto &osd = machine().osd();
+ if(osd.sound_split_streams_per_source()) {
+ auto get_input_stream_for_node_and_device = [this, &current_input_streams] (const osd::audio_info::node_info *node, sound_io_device *dev, bool is_system_default, bool is_channel_mapping = false) -> u32 {
+ // Check if the osd stream already exists to pick it up in case.
+ // Clear the id in the current_streams structure to show it has been picked up, reset the unused mask.
+ // Clear the volumes
+ // m_dev will already be correct
+
+ for(auto &os : current_input_streams)
+ if(os.m_id && os.m_node == node->m_id && os.m_dev == dev) {
+ u32 sid = m_osd_input_streams.size();
+ m_osd_input_streams.emplace_back(std::move(os));
+ os.m_id = 0;
+ auto &nos = m_osd_input_streams[sid];
+ nos.m_is_channel_mapping = is_channel_mapping;
+ nos.m_unused_channels_mask = util::make_bitmask<u32>(node->m_sources);
+ nos.m_volumes.clear();
+ nos.m_is_system_default = is_system_default;
+ return sid;
+ }
+ // If none exists, create one
+ u32 sid = m_osd_input_streams.size();
+ u32 rate = machine().sample_rate();
+ m_osd_input_streams.emplace_back(osd_input_stream(node->m_id, is_system_default ? "" : node->m_name, node->m_sources, rate, is_system_default, dev));
+ osd_input_stream &nos = m_osd_input_streams.back();
+ nos.m_id = machine().osd().sound_stream_source_open(node->m_id, dev->tag(), rate);
+ nos.m_is_channel_mapping = is_channel_mapping;
+ nos.m_buffer.set_sync_sample(rate_and_last_sync_to_index(rate));
+ return sid;
+ };
+
+ auto get_output_stream_for_node_and_device = [this, &current_output_streams] (const osd::audio_info::node_info *node, sound_io_device *dev, bool is_system_default, bool is_channel_mapping = false) -> u32 {
+ // Check if the osd stream already exists to pick it up in case.
+ // Clear the id in the current_streams structure to show it has been picked up, reset the unused mask.
+ // Clear the volumes
+ // m_dev will already be correct
+
+ for(auto &os : current_output_streams)
+ if(os.m_id && os.m_node == node->m_id && os.m_dev == dev) {
+ u32 sid = m_osd_output_streams.size();
+ m_osd_output_streams.emplace_back(std::move(os));
+ os.m_id = 0;
+ auto &nos = m_osd_output_streams[sid];
+ nos.m_is_channel_mapping = is_channel_mapping;
+ nos.m_volumes.clear();
+ nos.m_unused_channels_mask = util::make_bitmask<u32>(node->m_sinks);
+ nos.m_is_system_default = is_system_default;
+ return sid;
+ }
-//-------------------------------------------------
-// config_load - read and apply data from the
-// configuration file
-//-------------------------------------------------
+ // If none exists, create one
+ u32 sid = m_osd_output_streams.size();
+ u32 rate = machine().sample_rate();
+ m_osd_output_streams.emplace_back(osd_output_stream(node->m_id, is_system_default ? "" : node->m_name, node->m_sinks, rate, is_system_default, dev));
+ osd_output_stream &nos = m_osd_output_streams.back();
+ nos.m_id = machine().osd().sound_stream_sink_open(node->m_id, dev->tag(), rate);
+ nos.m_is_channel_mapping = is_channel_mapping;
+ nos.m_last_sync = rate_and_last_sync_to_index(rate);
+ return sid;
+ };
+
+ auto get_input_stream_for_node_and_channel = [this, &get_input_stream_for_node_and_device] (const osd::audio_info::node_info *node, u32 node_channel, sound_io_device *dev, bool is_system_default) -> u32 {
+ // First check if there's an active stream
+ for(u32 sid = 0; sid != m_osd_input_streams.size(); sid++) {
+ auto &os = m_osd_input_streams[sid];
+ if(os.m_node == node->m_id && os.m_dev == dev && os.m_unused_channels_mask & (1 << node_channel) && os.m_is_channel_mapping)
+ return sid;
+ }
-void sound_manager::config_load(config_type cfg_type, config_level cfg_level, util::xml::data_node const *parentnode)
-{
- // we only care system-specific configuration
- if ((cfg_type != config_type::SYSTEM) || !parentnode)
- return;
+ // Otherwise use the default method
+ return get_input_stream_for_node_and_device(node, dev, is_system_default, true);
+ };
- // master volume attenuation
- if (util::xml::data_node const *node = parentnode->get_child("attenuation"))
- {
- // treat source INI files or more specific as higher priority than CFG
- // FIXME: leaky abstraction - this depends on a front-end implementation detail
- if ((OPTION_PRIORITY_NORMAL + 5) > machine().options().get_entry(OPTION_VOLUME)->priority())
- set_attenuation(std::clamp(int(node->get_attribute_int("value", 0)), -32, 0));
- }
- // iterate over channel nodes
- for (util::xml::data_node const *node = parentnode->get_child("channel"); node != nullptr; node = node->get_next_sibling("channel"))
- {
- mixer_input info;
- if (indexed_mixer_input(node->get_attribute_int("index", -1), info))
- {
- // note that this doesn't disallow out-of-range values
- float value = node->get_attribute_float("value", std::nanf(""));
-
- if (!std::isnan(value))
- info.stream->input(info.inputnum).set_user_gain(value);
- }
- }
+ auto get_output_stream_for_node_and_channel = [this, &get_output_stream_for_node_and_device] (const osd::audio_info::node_info *node, u32 node_channel, sound_io_device *dev, bool is_system_default) -> u32 {
+ // First check if there's an active stream with the correct channel not used yet
+ for(u32 sid = 0; sid != m_osd_output_streams.size(); sid++) {
+ auto &os = m_osd_output_streams[sid];
+ if(os.m_node == node->m_id && os.m_dev == dev && os.m_unused_channels_mask & (1 << node_channel) && os.m_is_channel_mapping)
+ return sid;
+ }
- // iterate over speaker panning nodes
- for (util::xml::data_node const *node = parentnode->get_child("panning"); node != nullptr; node = node->get_next_sibling("panning"))
- {
- char const *const tag = node->get_attribute_string("tag", nullptr);
- if (tag != nullptr)
- {
- for (speaker_device &speaker : speaker_device_enumerator(machine().root_device()))
- {
- if (!strcmp(tag, speaker.tag()))
- {
- float value = node->get_attribute_float("value", speaker.defpan());
- speaker.set_pan(value);
- break;
+ // Otherwise use the default method
+ return get_output_stream_for_node_and_device(node, dev, is_system_default, true);
+ };
+
+ // Create/retrieve streams to apply the decided mapping
+ for(const auto &omap : m_mappings) {
+ u32 dev_index = find_sound_io_index(omap.m_dev);
+ bool is_output = omap.m_dev->is_output();
+ if(is_output) {
+ std::vector<mixing_step> &mixing_steps = m_output_mixing_steps;
+ u32 dchannels = omap.m_dev->inputs();
+ for(const auto &nm : omap.m_node_mappings) {
+ const auto *node = find_node_info(nm.m_node);
+ u32 osd_index = get_output_stream_for_node_and_device(node, omap.m_dev, nm.m_is_system_default);
+ auto &stream = m_osd_output_streams[osd_index];
+ u32 umask = stream.m_unused_channels_mask;
+ float linear_volume = 1.0;
+
+ if(osd.sound_external_per_channel_volume()) {
+ stream.m_volumes.clear();
+ stream.m_volumes.resize(stream.m_channels, nm.m_db);
+
+ } else
+ linear_volume = osd::db_to_linear(nm.m_db);
+
+ for(u32 channel = 0; channel != dchannels; channel++) {
+ std::vector<u32> targets = find_channel_mapping(omap.m_dev->get_position(channel), node);
+ for(u32 tchannel : targets) {
+ // If the channel is output and in the to
+ // clear mask, use load, otherwise use add.
+ // Apply the volume too if needed
+ mixing_steps.emplace_back(mixing_step {
+ (umask & (1 << tchannel)) ? mixing_step::COPY : mixing_step::ADD,
+ osd_index,
+ tchannel,
+ dev_index,
+ channel,
+ linear_volume
+ });
+ umask &= ~(1 << tchannel);
+ }
+ }
+ stream.m_unused_channels_mask = umask;
+ }
+
+ for(const auto &cm : omap.m_channel_mappings) {
+ const auto *node = find_node_info(cm.m_node);
+ u32 osd_index = get_output_stream_for_node_and_channel(node, cm.m_node_channel, omap.m_dev, cm.m_is_system_default);
+ auto &stream = m_osd_output_streams[osd_index];
+ float linear_volume = 1.0;
+
+ if(osd.sound_external_per_channel_volume()) {
+ if(stream.m_volumes.empty())
+ stream.m_volumes.resize(stream.m_channels, -96);
+ stream.m_volumes[cm.m_node_channel] = cm.m_db;
+
+ } else
+ linear_volume = osd::db_to_linear(cm.m_db);
+
+ mixing_steps.emplace_back(mixing_step {
+ (stream.m_unused_channels_mask & (1 << cm.m_node_channel)) ?
+ mixing_step::COPY : mixing_step::ADD,
+ osd_index,
+ cm.m_node_channel,
+ dev_index,
+ cm.m_guest_channel,
+ linear_volume
+ });
+ stream.m_unused_channels_mask &= ~(1 << cm.m_node_channel);
+ }
+
+
+ } else {
+ std::vector<mixing_step> &mixing_steps = m_microphones[dev_index].m_input_mixing_steps;
+ u32 dchannels = omap.m_dev->outputs();
+ for(const auto &nm : omap.m_node_mappings) {
+ const auto *node = find_node_info(nm.m_node);
+ u32 osd_index = get_input_stream_for_node_and_device(node, omap.m_dev, nm.m_is_system_default);
+ auto &stream = m_osd_input_streams[osd_index];
+ u32 umask = stream.m_unused_channels_mask;
+ float linear_volume = 1.0;
+
+ if(osd.sound_external_per_channel_volume()) {
+ stream.m_volumes.clear();
+ stream.m_volumes.resize(stream.m_channels, nm.m_db);
+
+ } else
+ linear_volume = osd::db_to_linear(nm.m_db);
+
+ for(u32 channel = 0; channel != dchannels; channel++) {
+ std::vector<u32> targets = find_channel_mapping(omap.m_dev->get_position(channel), node);
+ for(u32 tchannel : targets) {
+ // If the channel is output and in the to
+ // clear mask, use load, otherwise use add.
+ // Apply the volume too if needed
+ mixing_steps.emplace_back(mixing_step {
+ mixing_step::ADD,
+ osd_index,
+ tchannel,
+ dev_index,
+ channel,
+ linear_volume
+ });
+ umask &= ~(1 << tchannel);
+ }
+ }
+ stream.m_unused_channels_mask = umask;
+ }
+
+ for(const auto &cm : omap.m_channel_mappings) {
+ const auto *node = find_node_info(cm.m_node);
+ u32 osd_index = get_input_stream_for_node_and_channel(node, cm.m_node_channel, omap.m_dev, cm.m_is_system_default);
+ auto &stream = m_osd_input_streams[osd_index];
+ float linear_volume = 1.0;
+
+ if(osd.sound_external_per_channel_volume()) {
+ if(stream.m_volumes.empty())
+ stream.m_volumes.resize(stream.m_channels, -96);
+ stream.m_volumes[cm.m_node_channel] = cm.m_db;
+
+ } else
+ linear_volume = osd::db_to_linear(cm.m_db);
+
+ mixing_steps.emplace_back(mixing_step {
+ mixing_step::ADD,
+ osd_index,
+ cm.m_node_channel,
+ dev_index,
+ cm.m_guest_channel,
+ linear_volume
+ });
+ stream.m_unused_channels_mask &= ~(1 << cm.m_node_channel);
}
}
}
- }
-}
+ } else {
+ // All sources need to be merged per-destination, max one stream per destination
+
+ std::map<u32, u32> stream_per_node;
+
+ // Retrieve or create the one osd stream for a given
+ // destination. First check if we already have it, then
+ // whether it was previously created, then otherwise create
+ // it.
+
+ auto get_input_stream_for_node = [this, &current_input_streams, &stream_per_node] (const osd::audio_info::node_info *node, bool is_system_default) -> u32 {
+ // Pick up the existing stream if there's one
+ auto si = stream_per_node.find(node->m_id);
+ if(si != stream_per_node.end())
+ return si->second;
+
+ // Create the default unused mask
+ u32 channels = node->m_sources;
+ u32 umask = util::make_bitmask<u32>(channels);
+
+ // Check if the osd stream already exists to pick it up in case.
+ // Clear the id in the current_streams structure to show it has been picked up, reset the unused mask.
+ // m_speaker will already be nullptr, m_source_channels and m_volumes empty.
+
+ for(auto &os : current_input_streams)
+ if(os.m_id && os.m_node == node->m_id) {
+ u32 sid = m_osd_input_streams.size();
+ m_osd_input_streams.emplace_back(std::move(os));
+ os.m_id = 0;
+ m_osd_input_streams.back().m_unused_channels_mask = umask;
+ m_osd_input_streams.back().m_is_system_default = is_system_default;
+ stream_per_node[node->m_id] = sid;
+ return sid;
+ }
-//-------------------------------------------------
-// config_save - save data to the configuration
-// file
-//-------------------------------------------------
+ // If none exists, create one
+ u32 sid = m_osd_input_streams.size();
+ u32 rate = machine().sample_rate();
+ m_osd_input_streams.emplace_back(osd_input_stream(node->m_id, is_system_default ? "" : node->m_name, channels, rate, is_system_default, nullptr));
+ osd_input_stream &stream = m_osd_input_streams.back();
+ stream.m_id = machine().osd().sound_stream_source_open(node->m_id, machine().system().name, rate);
+ stream.m_buffer.set_sync_sample(rate_and_last_sync_to_index(rate));
+ stream_per_node[node->m_id] = sid;
+ return sid;
+ };
+
+ auto get_output_stream_for_node = [this, &current_output_streams, &stream_per_node] (const osd::audio_info::node_info *node, bool is_system_default) -> u32 {
+ // Pick up the existing stream if there's one
+ auto si = stream_per_node.find(node->m_id);
+ if(si != stream_per_node.end())
+ return si->second;
+
+ // Create the default unused mask
+ u32 channels = node->m_sinks;
+ u32 umask = util::make_bitmask<u32>(channels);
+
+ // Check if the osd stream already exists to pick it up in case.
+ // Clear the id in the current_streams structure to show it has been picked up, reset the unused mask.
+ // m_speaker will already be nullptr, m_source_channels and m_volumes empty.
+
+ for(auto &os : current_output_streams)
+ if(os.m_id && os.m_node == node->m_id) {
+ u32 sid = m_osd_output_streams.size();
+ m_osd_output_streams.emplace_back(std::move(os));
+ os.m_id = 0;
+ m_osd_output_streams.back().m_unused_channels_mask = umask;
+ m_osd_output_streams.back().m_is_system_default = is_system_default;
+ stream_per_node[node->m_id] = sid;
+ return sid;
+ }
-void sound_manager::config_save(config_type cfg_type, util::xml::data_node *parentnode)
-{
- // we only save system-specific configuration
- if (cfg_type != config_type::SYSTEM)
- return;
+ // If none exists, create one
+ u32 sid = m_osd_output_streams.size();
+ u32 rate = machine().sample_rate();
+ m_osd_output_streams.emplace_back(osd_output_stream(node->m_id, is_system_default ? "" : node->m_name, channels, rate, is_system_default, nullptr));
+ osd_output_stream &stream = m_osd_output_streams.back();
+ stream.m_id = machine().osd().sound_stream_sink_open(node->m_id, machine().system().name, rate);
+ stream.m_last_sync = rate_and_last_sync_to_index(rate);
+ stream_per_node[node->m_id] = sid;
+ return sid;
+ };
+
+
+ // Create/retrieve streams to apply the decided mapping
+
+ for(const auto &omap : m_mappings) {
+ u32 dev_index = find_sound_io_index(omap.m_dev);
+ bool is_output = omap.m_dev->is_output();
+ if(is_output) {
+ u32 channels = m_speakers[dev_index].m_channels;
+ std::vector<mixing_step> &mixing_steps = m_output_mixing_steps;
+ for(const auto &nm : omap.m_node_mappings) {
+ const auto *node = find_node_info(nm.m_node);
+ u32 stream_index = get_output_stream_for_node(node, nm.m_is_system_default);
+ u32 umask = m_osd_output_streams[stream_index].m_unused_channels_mask;
+ float linear_volume = osd::db_to_linear(nm.m_db);
+
+ for(u32 channel = 0; channel != channels; channel++) {
+ std::vector<u32> targets = find_channel_mapping(omap.m_dev->get_position(channel), node);
+ for(u32 tchannel : targets) {
+ // If the channel is in the to clear mask, use load, otherwise use add
+ // Apply the volume too
+ mixing_steps.emplace_back(mixing_step {
+ (umask & (1 << tchannel)) ? mixing_step::COPY : mixing_step::ADD,
+ dev_index,
+ channel,
+ stream_index,
+ tchannel,
+ linear_volume
+ });
+ umask &= ~(1 << tchannel);
+ }
+ }
+ m_osd_output_streams[stream_index].m_unused_channels_mask = umask;
+ }
- // master volume attenuation
- if (m_attenuation != machine().options().volume())
- {
- if (util::xml::data_node *const node = parentnode->add_child("attenuation", nullptr))
- node->set_attribute_int("value", m_attenuation);
- }
+ for(const auto &cm : omap.m_channel_mappings) {
+ const auto *node = find_node_info(cm.m_node);
+ u32 stream_index = get_output_stream_for_node(node, false);
+ u32 umask = m_osd_output_streams[stream_index].m_unused_channels_mask;
+
+ // If the channel is in the to clear mask, use load, otherwise use add
+ // Apply the volume too
+ mixing_steps.emplace_back(mixing_step {
+ (umask & (1 << cm.m_node_channel)) ? mixing_step::COPY : mixing_step::ADD,
+ dev_index,
+ cm.m_guest_channel,
+ stream_index,
+ cm.m_node_channel,
+ osd::db_to_linear(cm.m_db)
+ });
+ m_osd_output_streams[stream_index].m_unused_channels_mask = umask & ~(1 << cm.m_node_channel);
+ }
- // iterate over mixer channels for per-channel volume
- for (int mixernum = 0; ; mixernum++)
- {
- mixer_input info;
- if (!indexed_mixer_input(mixernum, info))
- break;
+ } else {
+ u32 channels = m_microphones[dev_index].m_channels;
+ std::vector<mixing_step> &mixing_steps = m_microphones[dev_index].m_input_mixing_steps;
+ for(const auto &nm : omap.m_node_mappings) {
+ const auto *node = find_node_info(nm.m_node);
+ u32 stream_index = get_input_stream_for_node(node, nm.m_is_system_default);
+ float linear_volume = osd::db_to_linear(nm.m_db);
+
+ for(u32 channel = 0; channel != channels; channel++) {
+ std::vector<u32> targets = find_channel_mapping(omap.m_dev->get_position(channel), node);
+ for(u32 tchannel : targets) {
+ // If the channel is in the to clear mask, use load, otherwise use add
+ // Apply the volume too
+ mixing_steps.emplace_back(mixing_step {
+ mixing_step::ADD,
+ dev_index,
+ channel,
+ stream_index,
+ tchannel,
+ linear_volume
+ });
+ m_osd_input_streams[stream_index].m_unused_channels_mask &= ~(1 << tchannel);
+ }
+ }
+ }
- float const value = info.stream->input(info.inputnum).user_gain();
- if (value != 1.0f)
- {
- util::xml::data_node *const node = parentnode->add_child("channel", nullptr);
- if (node)
- {
- node->set_attribute_int("index", mixernum);
- node->set_attribute_float("value", value);
+ for(const auto &cm : omap.m_channel_mappings) {
+ const auto *node = find_node_info(cm.m_node);
+ u32 stream_index = get_input_stream_for_node(node, false);
+
+ // If the channel is in the to clear mask, use load, otherwise use add
+ // Apply the volume too
+ mixing_steps.emplace_back(mixing_step {
+ mixing_step::ADD,
+ dev_index,
+ cm.m_guest_channel,
+ stream_index,
+ cm.m_node_channel,
+ osd::db_to_linear(cm.m_db)
+ });
+ m_osd_input_streams[stream_index].m_unused_channels_mask &= ~(1 << cm.m_node_channel);
+ }
}
}
}
- // iterate over speakers for panning
- for (speaker_device &speaker : speaker_device_enumerator(machine().root_device()))
- {
- float const value = speaker.pan();
- if (value != speaker.defpan())
- {
- util::xml::data_node *const node = parentnode->add_child("panning", nullptr);
- if (node)
- {
- node->set_attribute("tag", speaker.tag());
- node->set_attribute_float("value", value);
- }
+ // Add a clear step for all output streams that need it
+ // Also set the volumes if supported
+ for(u32 stream_index = 0; stream_index != m_osd_output_streams.size(); stream_index++) {
+ auto &stream = m_osd_output_streams[stream_index];
+ if(stream.m_unused_channels_mask) {
+ for(u32 channel = 0; channel != stream.m_channels; channel ++)
+ if(stream.m_unused_channels_mask & (1 << channel))
+ m_output_mixing_steps.emplace_back(mixing_step { mixing_step::CLEAR, 0, 0, stream_index, channel, 0.0 });
}
+ if(!stream.m_volumes.empty())
+ osd.sound_stream_set_volumes(stream.m_id, stream.m_volumes);
}
-}
+ // If supported, set the volumes for the input streams
+ for(u32 stream_index = 0; stream_index != m_osd_input_streams.size(); stream_index++) {
+ auto &stream = m_osd_input_streams[stream_index];
+ if(!stream.m_volumes.empty())
+ osd.sound_stream_set_volumes(stream.m_id, stream.m_volumes);
+ }
-//-------------------------------------------------
-// adjust_toward_compressor_scale - adjust the
-// current scale factor toward the current goal,
-// in small increments
-//-------------------------------------------------
+ // Close all previous streams that haven't been picked up
+ for(const auto &stream : current_input_streams)
+ if(stream.m_id)
+ machine().osd().sound_stream_close(stream.m_id);
+ for(const auto &stream : current_output_streams)
+ if(stream.m_id)
+ machine().osd().sound_stream_close(stream.m_id);
+}
-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)
+void sound_manager::mapping_update()
{
- stream_buffer::sample_t proposed_scale = curscale;
+ auto &osd = machine().osd();
+ while(m_osd_info.m_generation != osd.sound_get_generation()) {
+ osd_information_update();
+
+ if(VERBOSE & LOG_OSD_INFO) {
+ LOG_OUTPUT_FUNC("OSD information:\n");
+ LOG_OUTPUT_FUNC("- generation %u\n", m_osd_info.m_generation);
+ LOG_OUTPUT_FUNC("- default sink %u\n", m_osd_info.m_default_sink);
+ LOG_OUTPUT_FUNC("- default source %u\n", m_osd_info.m_default_source);
+ LOG_OUTPUT_FUNC("- nodes:\n");
+ for(const auto &node : m_osd_info.m_nodes) {
+ LOG_OUTPUT_FUNC(" * %3u %s [%d %d-%d]\n", node.m_id, node.name().c_str(), node.m_rate.m_default_rate, node.m_rate.m_min_rate, node.m_rate.m_max_rate);
+ uint32_t port_count = node.m_sinks;
+ if(port_count < node.m_sources)
+ port_count = node.m_sources;
+ for(uint32_t port = 0; port != port_count; port++)
+ LOG_OUTPUT_FUNC(" %s %s [%g %g %g]\n",
+ port < node.m_sinks ? port < node.m_sources ? "<>" : ">" : "<",
+ node.m_port_names[port].c_str(),
+ node.m_port_positions[port][0],
+ node.m_port_positions[port][1],
+ node.m_port_positions[port][2]);
+ }
+ LOG_OUTPUT_FUNC("- streams:\n");
+ for(const auto &stream : m_osd_info.m_streams) {
+ LOG_OUTPUT_FUNC(" * %3u node %u", stream.m_id, stream.m_node);
+ if(!stream.m_volumes.empty()) {
+ LOG_OUTPUT_FUNC(" volumes");
+ for(float v : stream.m_volumes)
+ LOG_OUTPUT_FUNC(" %g", v);
+ }
+ LOG_OUTPUT_FUNC("\n");
+ }
+ }
- // 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;
- }
+ generate_mapping();
- // otherwise, decrement by 0.01
- else
- {
- proposed_scale -= 0.01f;
- if (proposed_scale < m_compressor_scale)
- proposed_scale = m_compressor_scale;
+ if(VERBOSE & LOG_MAPPING) {
+ LOG_OUTPUT_FUNC("MAPPING:\n");
+ for(const auto &omap : m_mappings) {
+ LOG_OUTPUT_FUNC("- sound_io %s\n", omap.m_dev->tag());
+ for(const auto &nm : omap.m_node_mappings)
+ LOG_OUTPUT_FUNC(" * node %u volume %g%s\n", nm.m_node, nm.m_db, nm.m_is_system_default ? " (default)" : "");
+ for(const auto &cm : omap.m_channel_mappings)
+ LOG_OUTPUT_FUNC(" * channel %u <-> node %u:%i volume %g\n", cm.m_guest_channel, cm.m_node, cm.m_node_channel, cm.m_db);
+ }
+ }
+
+ update_osd_streams();
+
+ if(VERBOSE & LOG_OSD_STREAMS) {
+ LOG_OUTPUT_FUNC("OSD input streams:\n");
+ for(const auto &os : m_osd_input_streams) {
+ if(machine().osd().sound_split_streams_per_source()) {
+ LOG_OUTPUT_FUNC("- %3u %s node %u", os.m_id, os.m_dev ? os.m_dev->tag() : "-", os.m_node);
+ if(!os.m_is_channel_mapping)
+ LOG_OUTPUT_FUNC(" channels");
+ if(machine().osd().sound_external_per_channel_volume()) {
+ LOG_OUTPUT_FUNC(" dB");
+ for(u32 i = 0; i != os.m_channels; i++)
+ LOG_OUTPUT_FUNC(" %g", os.m_volumes[i]);
+ }
+ LOG_OUTPUT_FUNC("\n");
+ } else
+ LOG_OUTPUT_FUNC("- %3u node %u\n", os.m_id, os.m_node);
+ }
+ LOG_OUTPUT_FUNC("Input mixing steps:\n");
+ for(const auto &m : m_microphones) {
+ LOG_OUTPUT_FUNC(" %s:\n", m.m_dev.tag());
+ for(const auto &ms : m.m_input_mixing_steps) {
+ static const char *const modes[5] = { "clear", "copy", "copy+vol", "add", "add+vol" };
+ LOG_OUTPUT_FUNC(" - %s osd %u:%u -> device %u:%u level %g\n", modes[ms.m_mode], ms.m_osd_index, ms.m_osd_channel, ms.m_device_index, ms.m_device_channel, ms.m_linear_volume);
+ }
+ }
+ LOG_OUTPUT_FUNC("OSD output streams:\n");
+ for(const auto &os : m_osd_output_streams) {
+ if(machine().osd().sound_split_streams_per_source()) {
+ LOG_OUTPUT_FUNC("- %3u %s node %u", os.m_id, os.m_dev ? os.m_dev->tag() : "-", os.m_node);
+ if(!os.m_is_channel_mapping)
+ LOG_OUTPUT_FUNC(" channels");
+ if(machine().osd().sound_external_per_channel_volume()) {
+ LOG_OUTPUT_FUNC(" dB");
+ for(u32 i = 0; i != os.m_channels; i++)
+ LOG_OUTPUT_FUNC(" %g", os.m_volumes[i]);
+ }
+ LOG_OUTPUT_FUNC("\n");
+ } else
+ LOG_OUTPUT_FUNC("- %3u node %u\n", os.m_id, os.m_node);
+ }
+ LOG_OUTPUT_FUNC("Output mixing steps:\n");
+ for(const auto &ms : m_output_mixing_steps) {
+ static const char *const modes[5] = { "clear", "copy", "copy+vol", "add", "add+vol" };
+ LOG_OUTPUT_FUNC("- %s device %u:%u -> osd %u:%u level %g\n", modes[ms.m_mode], ms.m_device_index, ms.m_device_channel, ms.m_osd_index, ms.m_osd_channel, ms.m_linear_volume);
+ }
+ }
}
+}
- // 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;
-}
+//**// Global sound system update
-//-------------------------------------------------
-// update - mix everything down to its final form
-// and send it to the OSD layer
-//-------------------------------------------------
-
-void sound_manager::update(s32 param)
+u64 sound_manager::rate_and_time_to_index(attotime time, u32 sample_rate) const
{
- LOG("sound_update\n");
+ return time.m_seconds * sample_rate + ((time.m_attoseconds / 100000000) * sample_rate) / 10000000000;
+}
+void sound_manager::update(s32)
+{
auto profile = 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);
+ if(m_osd_info.m_generation == 0xffffffff)
+ startup_cleanups();
- // 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;
+ mapping_update();
+ streams_update();
- // 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
- for (speaker_device &speaker : m_speakers)
- 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;
+ // notify that new samples have been generated
+ m_last_sync_time = machine().time();
+ emulator_info::sound_hook();
+}
- // if we're above what the compressor will handle, adjust the compression
- if (curmax * m_compressor_scale > 1.0)
+void sound_manager::streams_update()
+{
+ attotime now = machine().time();
{
- 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--;
+ std::unique_lock<std::mutex> lock(m_effects_mutex);
+ for(osd_output_stream &stream : m_osd_output_streams) {
+ u64 next_sync = rate_and_time_to_index(now, stream.m_rate);
+ stream.m_samples = next_sync - stream.m_last_sync;
+ stream.m_last_sync = next_sync;
+ }
- // 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;
+ for(sound_stream *stream : m_ordered_streams)
+ stream->update_nodeps();
}
-#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;
+ for(sound_stream *stream : m_ordered_streams)
+ if(stream->device().type() != SPEAKER)
+ stream->sync(now);
- // now downmix the final result
- u32 finalmix_step = machine().video().speed_factor();
- u32 finalmix_offset = 0;
- s16 *finalmix = &m_finalmix[0];
- int sample;
- for (sample = m_finalmix_leftover; sample < m_samples_this_update * 1000; sample += finalmix_step)
- {
- 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);
-
- lprev = lsamp * lscale;
- if (m_compressor_enabled)
- lsamp = lprev;
-
- // clamp the left side
- 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);
-
- rprev = rsamp * rscale;
- if (m_compressor_enabled)
- rsamp = rprev;
-
- // clamp the right side
- 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;
-
- // play the result
- if (finalmix_offset > 0)
- {
- if (!m_nosound_mode)
- machine().osd().update_audio_stream(finalmix, finalmix_offset / 2);
- machine().osd().add_audio_to_recording(finalmix, finalmix_offset / 2);
- machine().video().add_sound_to_recording(finalmix, finalmix_offset / 2);
- if (m_wavfile)
- util::wav_add_data_16(*m_wavfile, finalmix, finalmix_offset);
- }
+ for(osd_input_stream &stream : m_osd_input_streams)
+ stream.m_buffer.sync();
- // update any orphaned streams so they don't get too far behind
- for (auto &stream : m_orphan_stream_list)
- stream.first->update();
+ machine().osd().add_audio_to_recording(m_record_buffer.data(), m_record_samples);
+ machine().video().add_sound_to_recording(m_record_buffer.data(), m_record_samples);
+ if(m_wavfile)
+ util::wav_add_data_16(*m_wavfile, m_record_buffer.data(), m_record_samples);
- // remember the update time
- m_last_update = endtime;
- m_update_number++;
+ m_effects_condition.notify_all();
+
+}
- // apply sample rate changes
- apply_sample_rate_changes();
+//**// Resampler management
- // notify that new samples have been generated
- emulator_info::sound_hook();
+const audio_resampler *sound_manager::get_resampler(u32 fs, u32 ft)
+{
+ auto key = std::make_pair(fs, ft);
+ auto i = m_resamplers.find(key);
+ if(i != m_resamplers.end())
+ return i->second.get();
+ auto *res = new audio_resampler(fs, ft);
+ m_resamplers[key].reset(res);
+ return res;
}
+