// license:BSD-3-Clause // copyright-holders:Aaron Giles /*************************************************************************** disound.cpp Device sound interfaces. ***************************************************************************/ #include "emu.h" #include "speaker.h" //************************************************************************** // DEVICE CONFIG SOUND INTERFACE //************************************************************************** //------------------------------------------------- // device_sound_interface - constructor //------------------------------------------------- device_sound_interface::device_sound_interface(const machine_config &mconfig, device_t &device) : device_interface(device, "sound"), m_outputs(0), m_auto_allocated_inputs(0), m_specified_inputs_mask(0) { } //------------------------------------------------- // ~device_sound_interface - destructor //------------------------------------------------- device_sound_interface::~device_sound_interface() { } //------------------------------------------------- // add_route - send sound output to a consumer //------------------------------------------------- device_sound_interface &device_sound_interface::add_route(u32 output, const char *target, double gain, u32 input, u32 mixoutput) { return add_route(output, device().mconfig().current_device(), target, gain, input, mixoutput); } device_sound_interface &device_sound_interface::add_route(u32 output, device_sound_interface &target, double gain, u32 input, u32 mixoutput) { return add_route(output, target.device(), DEVICE_SELF, gain, input, mixoutput); } device_sound_interface &device_sound_interface::add_route(u32 output, speaker_device &target, double gain, u32 input, u32 mixoutput) { return add_route(output, target, DEVICE_SELF, gain, input, mixoutput); } device_sound_interface &device_sound_interface::add_route(u32 output, device_t &base, const char *target, double gain, u32 input, u32 mixoutput) { assert(!device().started()); m_route_list.emplace_back(sound_route{ output, input, mixoutput, float(gain), base, target }); return *this; } //------------------------------------------------- // stream_alloc - allocate a stream implicitly // associated with this device //------------------------------------------------- sound_stream *device_sound_interface::stream_alloc(int inputs, int outputs, int sample_rate) { return device().machine().sound().stream_alloc(*this, inputs, outputs, sample_rate, stream_update_delegate(&device_sound_interface::sound_stream_update, this), STREAM_DEFAULT_FLAGS); } sound_stream *device_sound_interface::stream_alloc(int inputs, int outputs, int sample_rate, sound_stream_flags flags) { return device().machine().sound().stream_alloc(*this, inputs, outputs, sample_rate, stream_update_delegate(&device_sound_interface::sound_stream_update, this), flags); } //------------------------------------------------- // inputs - return the total number of inputs // for the given device //------------------------------------------------- int device_sound_interface::inputs() const { // scan the list counting streams we own and summing their inputs int inputs = 0; for (auto &stream : device().machine().sound().streams()) if (&stream->device() == &device()) inputs += stream->input_count(); return inputs; } //------------------------------------------------- // outputs - return the total number of outputs // for the given device //------------------------------------------------- int device_sound_interface::outputs() const { // scan the list counting streams we own and summing their outputs int outputs = 0; for (auto &stream : device().machine().sound().streams()) if (&stream->device() == &device()) outputs += stream->output_count(); return outputs; } //------------------------------------------------- // input_to_stream_input - convert a device's // input index to a stream and the input index // on that stream //------------------------------------------------- sound_stream *device_sound_interface::input_to_stream_input(int inputnum, int &stream_inputnum) const { assert(inputnum >= 0); // scan the list looking for streams owned by this device for (auto &stream : device().machine().sound().streams()) if (&stream->device() == &device()) { if (inputnum < stream->input_count()) { stream_inputnum = inputnum; return stream.get(); } inputnum -= stream->input_count(); } // not found return nullptr; } //------------------------------------------------- // output_to_stream_output - convert a device's // output index to a stream and the output index // on that stream //------------------------------------------------- sound_stream *device_sound_interface::output_to_stream_output(int outputnum, int &stream_outputnum) const { assert(outputnum >= 0); // scan the list looking for streams owned by this device for (auto &stream : device().machine().sound().streams()) if (&stream->device() == &device()) { if (outputnum < stream->output_count()) { stream_outputnum = outputnum; return stream.get(); } outputnum -= stream->output_count(); } // not found return nullptr; } //------------------------------------------------- // input_gain - return the gain on the given // input index of the device //------------------------------------------------- float device_sound_interface::input_gain(int inputnum) const { int stream_inputnum; sound_stream *stream = input_to_stream_input(inputnum, stream_inputnum); return (stream != nullptr) ? stream->input(stream_inputnum).gain() : 0.0f; } //------------------------------------------------- // output_gain - return the gain on the given // output index of the device //------------------------------------------------- float device_sound_interface::output_gain(int outputnum) const { int stream_outputnum; sound_stream *stream = output_to_stream_output(outputnum, stream_outputnum); return (stream != nullptr) ? stream->output(stream_outputnum).gain() : 0.0f; } //------------------------------------------------- // set_input_gain - set the gain on the given // input index of the device //------------------------------------------------- void device_sound_interface::set_input_gain(int inputnum, float gain) { int stream_inputnum; sound_stream *stream = input_to_stream_input(inputnum, stream_inputnum); if (stream != nullptr) stream->input(stream_inputnum).set_gain(gain); } //------------------------------------------------- // set_output_gain - set the gain on the given // output index of the device //------------------------------------------------- void device_sound_interface::set_output_gain(int outputnum, float gain) { // handle ALL_OUTPUTS as a special case if (outputnum == ALL_OUTPUTS) { for (auto &stream : device().machine().sound().streams()) if (&stream->device() == &device()) for (int num = 0; num < stream->output_count(); num++) stream->output(num).set_gain(gain); } // look up the stream and stream output index else { int stream_outputnum; sound_stream *stream = output_to_stream_output(outputnum, stream_outputnum); if (stream != nullptr) stream->output(stream_outputnum).set_gain(gain); } } //------------------------------------------------- // inputnum_from_device - return the input number // that is connected to the given device's output //------------------------------------------------- int device_sound_interface::inputnum_from_device(device_t &source_device, int outputnum) const { int overall = 0; for (auto &stream : device().machine().sound().streams()) if (&stream->device() == &device()) for (int inputnum = 0; inputnum < stream->input_count(); inputnum++, overall++) { auto &input = stream->input(inputnum); if (input.valid() && &input.source().stream().device() == &source_device && input.source().index() == outputnum) return overall; } return -1; } //------------------------------------------------- // interface_validity_check - validation for a // device after the configuration has been // constructed //------------------------------------------------- void device_sound_interface::interface_validity_check(validity_checker &valid) const { // loop over all the routes for (sound_route const &route : routes()) { // find a device with the requested tag device_t const *const target = route.m_base.get().subdevice(route.m_target); if (!target) osd_printf_error("Attempting to route sound to non-existent device '%s'\n", route.m_base.get().subtag(route.m_target)); // if it's not a speaker or a sound device, error device_sound_interface const *sound; if (target && (target->type() != SPEAKER) && !target->interface(sound)) osd_printf_error("Attempting to route sound to a non-sound device '%s' (%s)\n", target->tag(), target->name()); } } //------------------------------------------------- // interface_pre_start - make sure all our input // devices are started //------------------------------------------------- void device_sound_interface::interface_pre_start() { // scan all the sound devices sound_interface_enumerator iter(device().machine().root_device()); for (device_sound_interface const &sound : iter) { // scan each route on the device for (sound_route const &route : sound.routes()) { device_t *const target_device = route.m_base.get().subdevice(route.m_target); if (target_device == &device()) { // see if we are the target of this route; if we are, make sure the source device is started if (!sound.device().started()) throw device_missing_dependencies(); if (route.m_input != AUTO_ALLOC_INPUT) m_specified_inputs_mask |= 1 << route.m_input; } } } // now iterate through devices again and assign any auto-allocated inputs m_auto_allocated_inputs = 0; for (device_sound_interface &sound : iter) { // scan each route on the device for (sound_route &route : sound.routes()) { // see if we are the target of this route device_t *const target_device = route.m_base.get().subdevice(route.m_target); if (target_device == &device() && route.m_input == AUTO_ALLOC_INPUT) { route.m_input = m_auto_allocated_inputs; m_auto_allocated_inputs += (route.m_output == ALL_OUTPUTS) ? sound.outputs() : 1; } } } } //------------------------------------------------- // interface_post_start - verify that state was // properly set up //------------------------------------------------- void device_sound_interface::interface_post_start() { // iterate over all the sound devices for (device_sound_interface &sound : sound_interface_enumerator(device().machine().root_device())) { // scan each route on the device for (sound_route const &route : sound.routes()) { // if we are the target of this route, hook it up device_t *const target_device = route.m_base.get().subdevice(route.m_target); if (target_device == &device()) { // iterate over all outputs, matching any that apply int inputnum = route.m_input; int const numoutputs = sound.outputs(); for (int outputnum = 0; outputnum < numoutputs; outputnum++) if (route.m_output == outputnum || route.m_output == ALL_OUTPUTS) { // find the output stream to connect from int streamoutputnum; sound_stream *const outputstream = sound.output_to_stream_output(outputnum, streamoutputnum); if (!outputstream) fatalerror("Sound device '%s' specifies route for nonexistent output #%d\n", sound.device().tag(), outputnum); // find the input stream to connect to int streaminputnum; sound_stream *const inputstream = input_to_stream_input(inputnum++, streaminputnum); if (!inputstream) fatalerror("Sound device '%s' targeted output #%d to nonexistent device '%s' input %d\n", sound.device().tag(), outputnum, device().tag(), inputnum - 1); // set the input inputstream->set_input(streaminputnum, outputstream, streamoutputnum, route.m_gain); } } } } } //------------------------------------------------- // interface_pre_reset - called prior to // resetting the device //------------------------------------------------- void device_sound_interface::interface_pre_reset() { // update all streams on this device prior to reset for (auto &stream : device().machine().sound().streams()) if (&stream->device() == &device()) stream->update(); } //------------------------------------------------- // sound_stream_update - default implementation // that should be overridden //------------------------------------------------- void device_sound_interface::sound_stream_update(sound_stream &stream, std::vector const &inputs, std::vector &outputs) { throw emu_fatalerror("sound_stream_update called but not overridden by owning class"); } //************************************************************************** // SIMPLE DERIVED MIXER INTERFACE //************************************************************************** //------------------------------------------------- // device_mixer_interface - constructor //------------------------------------------------- device_mixer_interface::device_mixer_interface(const machine_config &mconfig, device_t &device, int outputs) : device_sound_interface(mconfig, device), m_outputs(outputs), m_mixer_stream(nullptr) { } //------------------------------------------------- // ~device_mixer_interface - destructor //------------------------------------------------- device_mixer_interface::~device_mixer_interface() { } //------------------------------------------------- // interface_pre_start - perform startup prior // to the device startup //------------------------------------------------- void device_mixer_interface::interface_pre_start() { // call our parent device_sound_interface::interface_pre_start(); // no inputs? that's weird if (m_auto_allocated_inputs == 0) { device().logerror("Warning: mixer \"%s\" has no inputs\n", device().tag()); return; } // generate the output map m_outputmap.resize(m_auto_allocated_inputs); // iterate through all routes that point to us and note their mixer output for (device_sound_interface const &sound : sound_interface_enumerator(device().machine().root_device())) { for (sound_route const &route : sound.routes()) { // see if we are the target of this route device_t *const target_device = route.m_base.get().subdevice(route.m_target); if (target_device == &device() && route.m_input < m_auto_allocated_inputs) { int const count = (route.m_output == ALL_OUTPUTS) ? sound.outputs() : 1; for (int output = 0; output < count; output++) m_outputmap[route.m_input + output] = route.m_mixoutput; } } } // keep a small buffer handy for tracking cleared buffers m_output_clear.resize(m_outputs); // allocate the mixer stream m_mixer_stream = stream_alloc(m_auto_allocated_inputs, m_outputs, device().machine().sample_rate(), STREAM_DEFAULT_FLAGS); } //------------------------------------------------- // interface_post_load - after we load a save // state be sure to update the mixer stream's // output sample rate //------------------------------------------------- void device_mixer_interface::interface_post_load() { // mixer stream could be null if no inputs were specified if (m_mixer_stream != nullptr) m_mixer_stream->set_sample_rate(device().machine().sample_rate()); // call our parent device_sound_interface::interface_post_load(); } //------------------------------------------------- // sound_stream_update - mix all inputs to one // output //------------------------------------------------- void device_mixer_interface::sound_stream_update(sound_stream &stream, std::vector const &inputs, std::vector &outputs) { // special case: single input, single output, same rate if (inputs.size() == 1 && outputs.size() == 1 && inputs[0].sample_rate() == outputs[0].sample_rate()) { outputs[0] = inputs[0]; return; } // reset the clear flags std::fill(std::begin(m_output_clear), std::end(m_output_clear), false); // loop over inputs for (int inputnum = 0; inputnum < m_auto_allocated_inputs; inputnum++) { // skip if the gain is 0 auto &input = inputs[inputnum]; if (input.gain() == 0) continue; // either store or accumulate int outputnum = m_outputmap[inputnum]; auto &output = outputs[outputnum]; if (!m_output_clear[outputnum]) output.copy(input); else output.add(input); m_output_clear[outputnum] = true; } // clear anything unused for (int outputnum = 0; outputnum < m_outputs; outputnum++) if (!m_output_clear[outputnum]) outputs[outputnum].fill(0); }