// license:BSD-3-Clause // copyright-holders:Aaron Giles /*************************************************************************** sound.c Core sound functions and definitions. ***************************************************************************/ #include "emu.h" #include "emuopts.h" #include "osdepend.h" #include "config.h" #include "sound/wavwrite.h" //************************************************************************** // DEBUGGING //************************************************************************** #define VERBOSE (0) #define VPRINTF(x) do { if (VERBOSE) osd_printf_debug x; } while (0) //************************************************************************** // CONSTANTS //************************************************************************** //************************************************************************** // GLOBAL VARIABLES //************************************************************************** const attotime sound_manager::STREAMS_UPDATE_ATTOTIME = attotime::from_hz(STREAMS_UPDATE_FREQUENCY); //************************************************************************** // INITIALIZATION //************************************************************************** //------------------------------------------------- // sound_stream - constructor //------------------------------------------------- sound_stream::sound_stream(device_t &device, int inputs, int outputs, int sample_rate, stream_update_delegate callback) : m_device(device), m_next(nullptr), m_sample_rate(sample_rate), m_new_sample_rate(0), m_attoseconds_per_sample(0), m_max_samples_per_update(0), m_input(inputs), m_input_array(inputs), m_resample_bufalloc(0), m_output(outputs), m_output_array(outputs), m_output_bufalloc(0), m_output_sampindex(0), m_output_update_sampindex(0), m_output_base_sampindex(0), m_callback(std::move(callback)) { // get the device's sound interface device_sound_interface *sound; if (!device.interface(sound)) throw emu_fatalerror("Attempted to create a sound_stream with a non-sound device"); if(m_callback.isnull()) m_callback = stream_update_delegate(FUNC(device_sound_interface::sound_stream_update),(device_sound_interface *)sound); // create a unique tag for saving std::string state_tag = string_format("%d", m_device.machine().sound().m_stream_list.count()); m_device.machine().save().save_item(&m_device, "stream", state_tag.c_str(), 0, NAME(m_sample_rate)); m_device.machine().save().register_postload(save_prepost_delegate(FUNC(sound_stream::postload), this)); // save the gain of each input and output for (unsigned int inputnum = 0; inputnum < m_input.size(); inputnum++) { m_device.machine().save().save_item(&m_device, "stream", state_tag.c_str(), inputnum, NAME(m_input[inputnum].m_gain)); m_device.machine().save().save_item(&m_device, "stream", state_tag.c_str(), inputnum, NAME(m_input[inputnum].m_user_gain)); } for (unsigned int outputnum = 0; outputnum < m_output.size(); outputnum++) { m_output[outputnum].m_stream = this; m_device.machine().save().save_item(&m_device, "stream", state_tag.c_str(), outputnum, NAME(m_output[outputnum].m_gain)); } // Mark synchronous streams as such m_synchronous = m_sample_rate == STREAM_SYNC; if (m_synchronous) { m_sample_rate = 0; m_sync_timer = m_device.machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(sound_stream::sync_update), this)); } else m_sync_timer = nullptr; // force an update to the sample rates; this will cause everything to be recomputed // and will generate the initial resample buffers for our inputs recompute_sample_rate_data(); // set up the initial output buffer positions now that we have data m_output_base_sampindex = -m_max_samples_per_update; } //------------------------------------------------- // sample_time - return the emulation time of the // next sample to be generated on the stream //------------------------------------------------- attotime sound_stream::sample_time() const { return attotime(m_device.machine().sound().last_update().seconds(), 0) + attotime(0, m_output_sampindex * m_attoseconds_per_sample); } //------------------------------------------------- // user_gain - return the user-controllable gain // on a given stream's input //------------------------------------------------- float sound_stream::user_gain(int inputnum) const { assert(inputnum >= 0 && inputnum < m_input.size()); return float(m_input[inputnum].m_user_gain) / 256.0f; } //------------------------------------------------- // input_gain - return the input gain on a // given stream's input //------------------------------------------------- float sound_stream::input_gain(int inputnum) const { assert(inputnum >= 0 && inputnum < m_input.size()); return float(m_input[inputnum].m_gain) / 256.0f; } //------------------------------------------------- // output_gain - return the output gain on a // given stream's output //------------------------------------------------- float sound_stream::output_gain(int outputnum) const { assert(outputnum >= 0 && outputnum < m_output.size()); return float(m_output[outputnum].m_gain) / 256.0f; } //------------------------------------------------- // input_name - return the original input gain // on a given stream's input //------------------------------------------------- std::string sound_stream::input_name(int inputnum) const { std::ostringstream str; // start with our device name and tag assert(inputnum >= 0 && inputnum < m_input.size()); util::stream_format(str, "%s '%s': ", m_device.name(), m_device.tag()); // if we have a source, indicate where the sound comes from by device name and tag if (m_input[inputnum].m_source != nullptr && m_input[inputnum].m_source->m_stream != nullptr) { device_t &source = m_input[inputnum].m_source->m_stream->device(); util::stream_format(str, "%s '%s'", source.name(), source.tag()); // get the sound interface; if there is more than 1 output we need to figure out which one device_sound_interface *sound; if (source.interface(sound) && sound->outputs() > 1) { // iterate over outputs until we find the stream that matches our source // then look for a match on the output number sound_stream *outstream; int streamoutputnum; for (int outputnum = 0; (outstream = sound->output_to_stream_output(outputnum, streamoutputnum)) != nullptr; outputnum++) if (outstream == m_input[inputnum].m_source->m_stream && m_input[inputnum].m_source == &outstream->m_output[streamoutputnum]) { util::stream_format(str, " Ch.%d", outputnum); break; } } } return str.str(); } //------------------------------------------------- // input_source_device - return the device // attached as a given input's source //------------------------------------------------- device_t *sound_stream::input_source_device(int inputnum) const { assert(inputnum >= 0 && inputnum < m_input.size()); return (m_input[inputnum].m_source != nullptr) ? &m_input[inputnum].m_source->m_stream->device() : nullptr; } //------------------------------------------------- // input_source_device - return the output number // attached as a given input's source //------------------------------------------------- int sound_stream::input_source_outputnum(int inputnum) const { assert(inputnum >= 0 && inputnum < m_input.size()); return (m_input[inputnum].m_source != nullptr) ? (m_input[inputnum].m_source - &m_input[inputnum].m_source->m_stream->m_output[0]) : -1; } //------------------------------------------------- // set_input - configure a stream's input //------------------------------------------------- void sound_stream::set_input(int index, sound_stream *input_stream, int output_index, float gain) { VPRINTF(("stream_set_input(%p, '%s', %d, %p, %d, %f)\n", (void *)this, m_device.tag(), index, (void *)input_stream, output_index, (double) gain)); // make sure it's a valid input if (index >= m_input.size()) fatalerror("stream_set_input attempted to configure non-existant input %d (%d max)\n", index, int(m_input.size())); // make sure it's a valid output if (input_stream != nullptr && output_index >= input_stream->m_output.size()) fatalerror("stream_set_input attempted to use a non-existant output %d (%d max)\n", output_index, int(m_output.size())); // if this input is already wired, update the dependent info stream_input &input = m_input[index]; if (input.m_source != nullptr) input.m_source->m_dependents--; // wire it up input.m_source = (input_stream != nullptr) ? &input_stream->m_output[output_index] : nullptr; input.m_gain = int(0x100 * gain); input.m_user_gain = 0x100; // update the dependent info if (input.m_source != nullptr) input.m_source->m_dependents++; // update sample rates now that we know the input recompute_sample_rate_data(); } //------------------------------------------------- // update - force a stream to update to // the current emulated time //------------------------------------------------- void sound_stream::update() { // determine the number of samples since the start of this second attotime time = m_device.machine().time(); INT32 update_sampindex = INT32(time.attoseconds() / m_attoseconds_per_sample); // if we're ahead of the last update, then adjust upwards attotime last_update = m_device.machine().sound().last_update(); if (time.seconds() > last_update.seconds()) { assert(time.seconds() == last_update.seconds() + 1); update_sampindex += m_sample_rate; } // if we're behind the last update, then adjust downwards if (time.seconds() < last_update.seconds()) { assert(time.seconds() == last_update.seconds() - 1); update_sampindex -= m_sample_rate; } // generate samples to get us up to the appropriate time g_profiler.start(PROFILER_SOUND); assert(m_output_sampindex - m_output_base_sampindex >= 0); assert(update_sampindex - m_output_base_sampindex <= m_output_bufalloc); generate_samples(update_sampindex - m_output_sampindex); g_profiler.stop(); // remember this info for next time m_output_sampindex = update_sampindex; } void sound_stream::sync_update(void *, INT32) { update(); attotime time = m_device.machine().time(); attoseconds_t next_edge = m_attoseconds_per_sample - (time.attoseconds() % m_attoseconds_per_sample); m_sync_timer->adjust(attotime(0, next_edge)); } //------------------------------------------------- // output_since_last_update - return a pointer to // the output buffer and the number of samples // since the last global update //------------------------------------------------- const stream_sample_t *sound_stream::output_since_last_update(int outputnum, int &numsamples) { // force an update on the stream update(); // compute the number of samples and a pointer to the output buffer numsamples = m_output_sampindex - m_output_update_sampindex; return &m_output[outputnum].m_buffer[m_output_update_sampindex - m_output_base_sampindex]; } //------------------------------------------------- // set_sample_rate - set the sample rate on a // given stream //------------------------------------------------- void sound_stream::set_sample_rate(int new_rate) { // we will update this on the next global update if (new_rate != sample_rate()) m_new_sample_rate = new_rate; } //------------------------------------------------- // set_user_gain - set the user-controllable gain // on a given stream's input //------------------------------------------------- void sound_stream::set_user_gain(int inputnum, float gain) { update(); assert(inputnum >= 0 && inputnum < m_input.size()); m_input[inputnum].m_user_gain = int(0x100 * gain); } //------------------------------------------------- // set_input_gain - set the input gain on a // given stream's input //------------------------------------------------- void sound_stream::set_input_gain(int inputnum, float gain) { update(); assert(inputnum >= 0 && inputnum < m_input.size()); m_input[inputnum].m_gain = int(0x100 * gain); } //------------------------------------------------- // set_output_gain - set the output gain on a // given stream's output //------------------------------------------------- void sound_stream::set_output_gain(int outputnum, float gain) { update(); assert(outputnum >= 0 && outputnum < m_output.size()); m_output[outputnum].m_gain = int(0x100 * gain); } //------------------------------------------------- // update_with_accounting - do a regular update, // but also do periodic accounting //------------------------------------------------- void sound_stream::update_with_accounting(bool second_tick) { // do the normal update update(); // if we've ticked over another second, adjust all the counters that are relative to // the current second INT32 output_bufindex = m_output_sampindex - m_output_base_sampindex; if (second_tick) { m_output_sampindex -= m_sample_rate; m_output_base_sampindex -= m_sample_rate; } // note our current output sample m_output_update_sampindex = m_output_sampindex; // if we don't have enough output buffer space to hold two updates' worth of samples, // we need to shuffle things down if (m_output_bufalloc - output_bufindex < 2 * m_max_samples_per_update) { INT32 samples_to_lose = output_bufindex - m_max_samples_per_update; if (samples_to_lose > 0) { // if we have samples to move, do so for each output if (output_bufindex > 0) for (auto & output : m_output) { memmove(&output.m_buffer[0], &output.m_buffer[samples_to_lose], sizeof(output.m_buffer[0]) * (output_bufindex - samples_to_lose)); } // update the base position m_output_base_sampindex += samples_to_lose; } } } //------------------------------------------------- // apply_sample_rate_changes - if there is a // pending sample rate change, apply it now //------------------------------------------------- void sound_stream::apply_sample_rate_changes() { // skip if nothing to do if (m_new_sample_rate == 0) return; // update to the new rate and remember the old rate UINT32 old_rate = m_sample_rate; m_sample_rate = m_new_sample_rate; m_new_sample_rate = 0; // recompute all the data recompute_sample_rate_data(); // reset our sample indexes to the current time m_output_sampindex = (INT64)m_output_sampindex * (INT64)m_sample_rate / old_rate; m_output_update_sampindex = (INT64)m_output_update_sampindex * (INT64)m_sample_rate / old_rate; m_output_base_sampindex = m_output_sampindex - m_max_samples_per_update; // clear out the buffer for (auto & elem : m_output) memset(&elem.m_buffer[0], 0, m_max_samples_per_update * sizeof(elem.m_buffer[0])); } //------------------------------------------------- // recompute_sample_rate_data - recompute sample // rate data, and all streams that are affected // by this stream //------------------------------------------------- void sound_stream::recompute_sample_rate_data() { if (m_synchronous) { m_sample_rate = 0; // When synchronous, pick the sample rate for the inputs, if any for (auto & input : m_input) { if (input.m_source != nullptr) { if (!m_sample_rate) m_sample_rate = input.m_source->m_stream->m_sample_rate; else if (m_sample_rate != input.m_source->m_stream->m_sample_rate) throw emu_fatalerror("Incompatible sample rates as input of a synchronous stream: %d and %d\n", m_sample_rate, input.m_source->m_stream->m_sample_rate); } } if (!m_sample_rate) m_sample_rate = 1000; } // recompute the timing parameters attoseconds_t update_attoseconds = m_device.machine().sound().update_attoseconds(); m_attoseconds_per_sample = ATTOSECONDS_PER_SECOND / m_sample_rate; m_max_samples_per_update = (update_attoseconds + m_attoseconds_per_sample - 1) / m_attoseconds_per_sample; // update resample and output buffer sizes allocate_resample_buffers(); allocate_output_buffers(); // iterate over each input for (auto & input : m_input) { // if we have a source, see if its sample rate changed if (input.m_source != nullptr) { // okay, we have a new sample rate; recompute the latency to be the maximum // sample period between us and our input attoseconds_t new_attosecs_per_sample = ATTOSECONDS_PER_SECOND / input.m_source->m_stream->m_sample_rate; attoseconds_t latency = MAX(new_attosecs_per_sample, m_attoseconds_per_sample); // if the input stream's sample rate is lower, we will use linear interpolation // this requires an extra sample from the source if (input.m_source->m_stream->m_sample_rate < m_sample_rate) latency += new_attosecs_per_sample; // if our sample rates match exactly, we don't need any latency else if (input.m_source->m_stream->m_sample_rate == m_sample_rate) latency = 0; // we generally don't want to tweak the latency, so we just keep the greatest // one we've computed thus far input.m_latency_attoseconds = MAX(input.m_latency_attoseconds, latency); assert(input.m_latency_attoseconds < update_attoseconds); } } // If synchronous, prime the timer if (m_synchronous) { attotime time = m_device.machine().time(); attoseconds_t next_edge = m_attoseconds_per_sample - (time.attoseconds() % m_attoseconds_per_sample); m_sync_timer->adjust(attotime(0, next_edge)); } } //------------------------------------------------- // allocate_resample_buffers - recompute the // resample buffer sizes and expand if necessary //------------------------------------------------- void sound_stream::allocate_resample_buffers() { // compute the target number of samples INT32 bufsize = 2 * m_max_samples_per_update; // if we don't have enough room, allocate more if (m_resample_bufalloc < bufsize) { // this becomes the new allocation size m_resample_bufalloc = bufsize; // iterate over outputs and realloc their buffers for (auto & elem : m_input) { unsigned int old_size = elem.m_resample.size(); elem.m_resample.resize(m_resample_bufalloc); memset(&elem.m_resample[old_size], 0, (m_resample_bufalloc - old_size)*sizeof(elem.m_resample[0])); } } } //------------------------------------------------- // allocate_output_buffers - recompute the // output buffer sizes and expand if necessary //------------------------------------------------- void sound_stream::allocate_output_buffers() { // if we don't have enough room, allocate more INT32 bufsize = OUTPUT_BUFFER_UPDATES * m_max_samples_per_update; if (m_output_bufalloc < bufsize) { // this becomes the new allocation size m_output_bufalloc = bufsize; // iterate over outputs and realloc their buffers for (auto & elem : m_output) { unsigned int old_size = elem.m_buffer.size(); elem.m_buffer.resize(m_output_bufalloc); memset(&elem.m_buffer[old_size], 0, (m_output_bufalloc - old_size)*sizeof(elem.m_buffer[0])); } } } //------------------------------------------------- // postload - save/restore callback //------------------------------------------------- void sound_stream::postload() { // recompute the same rate information recompute_sample_rate_data(); // make sure our output buffers are fully cleared for (auto & elem : m_output) memset(&elem.m_buffer[0], 0, m_output_bufalloc * sizeof(elem.m_buffer[0])); // recompute the sample indexes to make sense m_output_sampindex = m_device.machine().sound().last_update().attoseconds() / m_attoseconds_per_sample; m_output_update_sampindex = m_output_sampindex; m_output_base_sampindex = m_output_sampindex - m_max_samples_per_update; } //------------------------------------------------- // generate_samples - generate the requested // number of samples for a stream, making sure // all inputs have the appropriate number of // samples generated //------------------------------------------------- void sound_stream::generate_samples(int samples) { stream_sample_t **inputs = nullptr; stream_sample_t **outputs = nullptr; // if we're already there, skip it if (samples <= 0) return; VPRINTF(("generate_samples(%p, %d)\n", (void *) this, samples)); // ensure all inputs are up to date and generate resampled data for (unsigned int inputnum = 0; inputnum < m_input.size(); inputnum++) { // update the stream to the current time stream_input &input = m_input[inputnum]; if (input.m_source != nullptr) input.m_source->m_stream->update(); // generate the resampled data m_input_array[inputnum] = generate_resampled_data(input, samples); } if (!m_input.empty()) { inputs = &m_input_array[0]; } // loop over all outputs and compute the output pointer for (unsigned int outputnum = 0; outputnum < m_output.size(); outputnum++) { stream_output &output = m_output[outputnum]; m_output_array[outputnum] = &output.m_buffer[m_output_sampindex - m_output_base_sampindex]; } if (!m_output.empty()) { outputs = &m_output_array[0]; } // run the callback VPRINTF((" callback(%p, %d)\n", (void *)this, samples)); m_callback(*this, inputs, outputs, samples); VPRINTF((" callback done\n")); } //------------------------------------------------- // generate_resampled_data - generate the // resample buffer for a given input //------------------------------------------------- stream_sample_t *sound_stream::generate_resampled_data(stream_input &input, UINT32 numsamples) { // if we don't have an output to pull data from, generate silence stream_sample_t *dest = &input.m_resample[0]; if (input.m_source == nullptr) { memset(dest, 0, numsamples * sizeof(*dest)); return &input.m_resample[0]; } // grab data from the output stream_output &output = *input.m_source; sound_stream &input_stream = *output.m_stream; INT64 gain = (input.m_gain * input.m_user_gain * output.m_gain) >> 16; // determine the time at which the current sample begins, accounting for the // latency we calculated between the input and output streams attoseconds_t basetime = m_output_sampindex * m_attoseconds_per_sample - input.m_latency_attoseconds; // now convert that time into a sample in the input stream INT32 basesample; if (basetime >= 0) basesample = basetime / input_stream.m_attoseconds_per_sample; else basesample = -(-basetime / input_stream.m_attoseconds_per_sample) - 1; // compute a source pointer to the first sample assert(basesample >= input_stream.m_output_base_sampindex); stream_sample_t *source = &output.m_buffer[basesample - input_stream.m_output_base_sampindex]; // determine the current fraction of a sample, expressed as a fraction of FRAC_ONE // (Note: this formula is valid as long as input_stream.m_attoseconds_per_sample signficantly exceeds FRAC_ONE > attoseconds = 4.2E-12 s) UINT32 basefrac = (basetime - basesample * input_stream.m_attoseconds_per_sample) / ((input_stream.m_attoseconds_per_sample + FRAC_ONE - 1) >> FRAC_BITS); assert(basefrac < FRAC_ONE); // compute the stepping fraction UINT32 step = (UINT64(input_stream.m_sample_rate) << FRAC_BITS) / m_sample_rate; // if we have equal sample rates, we just need to copy if (step == FRAC_ONE) { while (numsamples--) { // compute the sample INT64 sample = *source++; *dest++ = (sample * gain) >> 8; } } // input is undersampled: point sample except where our sample period covers a boundary else if (step < FRAC_ONE) { while (numsamples != 0) { // fill in with point samples until we hit a boundary int nextfrac; while ((nextfrac = basefrac + step) < FRAC_ONE && numsamples--) { *dest++ = (source[0] * gain) >> 8; basefrac = nextfrac; } // if we're done, we're done if (INT32(numsamples--) < 0) break; // compute starting and ending fractional positions int startfrac = basefrac >> (FRAC_BITS - 12); int endfrac = nextfrac >> (FRAC_BITS - 12); // blend between the two samples accordingly INT64 sample = ((INT64) source[0] * (0x1000 - startfrac) + (INT64) source[1] * (endfrac - 0x1000)) / (endfrac - startfrac); *dest++ = (sample * gain) >> 8; // advance basefrac = nextfrac & FRAC_MASK; source++; } } // input is oversampled: sum the energy else { // use 8 bits to allow some extra headroom int smallstep = step >> (FRAC_BITS - 8); while (numsamples--) { INT64 remainder = smallstep; int tpos = 0; // compute the sample INT64 scale = (FRAC_ONE - basefrac) >> (FRAC_BITS - 8); INT64 sample = (INT64) source[tpos++] * scale; remainder -= scale; while (remainder > 0x100) { sample += (INT64) source[tpos++] * (INT64) 0x100; remainder -= 0x100; } sample += (INT64) source[tpos] * remainder; sample /= smallstep; *dest++ = (sample * gain) >> 8; // advance basefrac += step; source += basefrac >> FRAC_BITS; basefrac &= FRAC_MASK; } } return &input.m_resample[0]; } //************************************************************************** // STREAM INPUT //************************************************************************** //------------------------------------------------- // stream_input - constructor //------------------------------------------------- sound_stream::stream_input::stream_input() : m_source(nullptr), m_latency_attoseconds(0), m_gain(0x100), m_user_gain(0x100) { } //************************************************************************** // STREAM OUTPUT //************************************************************************** //------------------------------------------------- // stream_output - constructor //------------------------------------------------- sound_stream::stream_output::stream_output() : m_stream(nullptr), m_dependents(0), m_gain(0x100) { } //************************************************************************** // SOUND MANAGER //************************************************************************** //------------------------------------------------- // sound_manager - constructor //------------------------------------------------- sound_manager::sound_manager(running_machine &machine) : m_machine(machine), m_update_timer(nullptr), m_finalmix_leftover(0), m_finalmix(machine.sample_rate()), m_leftmix(machine.sample_rate()), m_rightmix(machine.sample_rate()), m_muted(0), m_attenuation(0), m_nosound_mode(machine.osd().no_sound()), m_wavfile(nullptr), m_update_attoseconds(STREAMS_UPDATE_ATTOTIME.attoseconds()), m_last_update(attotime::zero) { // get filename for WAV file or AVI file if specified const char *wavfile = machine.options().wav_write(); const char *avifile = machine.options().avi_write(); // handle -nosound and lower sample rate if not recording WAV or AVI if (m_nosound_mode && wavfile[0] == 0 && avifile[0] == 0) machine.m_sample_rate = 11025; // count the mixers #if VERBOSE mixer_interface_iterator iter(machine.root_device()); VPRINTF(("total mixers = %d\n", iter.count())); #endif // open the output WAV file if specified if (wavfile[0] != 0 && &machine.system() != &GAME_NAME(___empty)) m_wavfile = wav_open(wavfile, machine.sample_rate(), 2); // register callbacks machine.configuration().config_register("mixer", config_saveload_delegate(FUNC(sound_manager::config_load), this), config_saveload_delegate(FUNC(sound_manager::config_save), this)); machine.add_notifier(MACHINE_NOTIFY_PAUSE, machine_notify_delegate(FUNC(sound_manager::pause), this)); machine.add_notifier(MACHINE_NOTIFY_RESUME, machine_notify_delegate(FUNC(sound_manager::resume), this)); machine.add_notifier(MACHINE_NOTIFY_RESET, machine_notify_delegate(FUNC(sound_manager::reset), this)); // register global states machine.save().save_item(NAME(m_last_update)); // set the starting attenuation set_attenuation(machine.options().volume()); // 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); } //------------------------------------------------- // sound_manager - destructor //------------------------------------------------- sound_manager::~sound_manager() { // close any open WAV file if (m_wavfile != nullptr) wav_close(m_wavfile); m_wavfile = nullptr; } //------------------------------------------------- // stream_alloc - allocate a new stream //------------------------------------------------- sound_stream *sound_manager::stream_alloc(device_t &device, int inputs, int outputs, int sample_rate, stream_update_delegate callback) { return &m_stream_list.append(*global_alloc(sound_stream(device, inputs, outputs, sample_rate, callback))); } //------------------------------------------------- // set_attenuation - set the global volume //------------------------------------------------- void sound_manager::set_attenuation(int attenuation) { m_attenuation = attenuation; machine().osd().set_mastervolume(m_muted ? -32 : m_attenuation); } //------------------------------------------------- // indexed_mixer_input - return the mixer // device and input index of the global mixer // input //------------------------------------------------- bool sound_manager::indexed_mixer_input(int index, mixer_input &info) const { // scan through the mixers until we find the indexed input mixer_interface_iterator iter(machine().root_device()); for (info.mixer = iter.first(); info.mixer != nullptr; info.mixer = iter.next()) { if (index < info.mixer->inputs()) { info.stream = info.mixer->input_to_stream_input(index, info.inputnum); assert(info.stream != nullptr); return true; } index -= info.mixer->inputs(); } // didn't locate return false; } //------------------------------------------------- // mute - mute sound output //------------------------------------------------- void sound_manager::mute(bool mute, UINT8 reason) { if (mute) m_muted |= reason; else m_muted &= ~reason; set_attenuation(m_attenuation); } //------------------------------------------------- // reset - reset all sound chips //------------------------------------------------- void sound_manager::reset() { // reset all the sound chips sound_interface_iterator iter(machine().root_device()); for (device_sound_interface *sound = iter.first(); sound != nullptr; sound = iter.next()) sound->device().reset(); } //------------------------------------------------- // pause - pause sound output //------------------------------------------------- void sound_manager::pause() { mute(true, MUTE_REASON_PAUSE); } //------------------------------------------------- // resume - resume sound output //------------------------------------------------- void sound_manager::resume() { mute(false, MUTE_REASON_PAUSE); } //------------------------------------------------- // config_load - read and apply data from the // configuration file //------------------------------------------------- void sound_manager::config_load(config_type cfg_type, xml_data_node *parentnode) { // we only care about game files if (cfg_type != config_type::CONFIG_TYPE_GAME) return; // might not have any data if (parentnode == nullptr) return; // iterate over channel nodes for (xml_data_node *channelnode = xml_get_sibling(parentnode->child, "channel"); channelnode != nullptr; channelnode = xml_get_sibling(channelnode->next, "channel")) { mixer_input info; if (indexed_mixer_input(xml_get_attribute_int(channelnode, "index", -1), info)) { float defvol = xml_get_attribute_float(channelnode, "defvol", 1.0f); float newvol = xml_get_attribute_float(channelnode, "newvol", -1000.0f); if (newvol != -1000.0f) info.stream->set_user_gain(info.inputnum, newvol / defvol); } } } //------------------------------------------------- // config_save - save data to the configuration // file //------------------------------------------------- void sound_manager::config_save(config_type cfg_type, xml_data_node *parentnode) { // we only care about game files if (cfg_type != config_type::CONFIG_TYPE_GAME) return; // iterate over mixer channels if (parentnode != nullptr) for (int mixernum = 0; ; mixernum++) { mixer_input info; if (!indexed_mixer_input(mixernum, info)) break; float newvol = info.stream->user_gain(info.inputnum); if (newvol != 1.0f) { xml_data_node *channelnode = xml_add_child(parentnode, "channel", nullptr); if (channelnode != nullptr) { xml_set_attribute_int(channelnode, "index", mixernum); xml_set_attribute_float(channelnode, "newvol", newvol); } } } } //------------------------------------------------- // update - mix everything down to its final form // and send it to the OSD layer //------------------------------------------------- void sound_manager::update(void *ptr, int param) { VPRINTF(("sound_update\n")); g_profiler.start(PROFILER_SOUND); // force all the speaker streams to generate the proper number of samples int samples_this_update = 0; speaker_device_iterator iter(machine().root_device()); for (speaker_device *speaker = iter.first(); speaker != nullptr; speaker = iter.next()) speaker->mix(&m_leftmix[0], &m_rightmix[0], samples_this_update, (m_muted & MUTE_REASON_SYSTEM)); // now downmix the final result UINT32 finalmix_step = machine().video().speed_factor(); UINT32 finalmix_offset = 0; INT16 *finalmix = &m_finalmix[0]; int sample; for (sample = m_finalmix_leftover; sample < samples_this_update * 1000; sample += finalmix_step) { int sampindex = sample / 1000; // clamp the left side INT32 samp = m_leftmix[sampindex]; if (samp < -32768) samp = -32768; else if (samp > 32767) samp = 32767; finalmix[finalmix_offset++] = samp; // clamp the right side samp = m_rightmix[sampindex]; if (samp < -32768) samp = -32768; else if (samp > 32767) samp = 32767; finalmix[finalmix_offset++] = samp; } m_finalmix_leftover = sample - 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().video().add_sound_to_recording(finalmix, finalmix_offset / 2); if (m_wavfile != nullptr) wav_add_data_16(m_wavfile, finalmix, finalmix_offset); } // see if we ticked over to the next second attotime curtime = machine().time(); bool second_tick = false; if (curtime.seconds() != m_last_update.seconds()) { assert(curtime.seconds() == m_last_update.seconds() + 1); second_tick = true; } // iterate over all the streams and update them for (sound_stream *stream = m_stream_list.first(); stream != nullptr; stream = stream->next()) stream->update_with_accounting(second_tick); // remember the update time m_last_update = curtime; // update sample rates if they have changed for (sound_stream *stream = m_stream_list.first(); stream != nullptr; stream = stream->next()) stream->apply_sample_rate_changes(); g_profiler.stop(); }