summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/sound.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/emu/sound.c')
-rw-r--r--src/emu/sound.c955
1 files changed, 955 insertions, 0 deletions
diff --git a/src/emu/sound.c b/src/emu/sound.c
new file mode 100644
index 00000000000..df7c9277cff
--- /dev/null
+++ b/src/emu/sound.c
@@ -0,0 +1,955 @@
+/***************************************************************************
+
+ sound.c
+
+ Core sound functions and definitions.
+
+ Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team.
+ Visit http://mamedev.org for licensing and usage restrictions.
+
+***************************************************************************/
+
+#include "driver.h"
+#include "osdepend.h"
+#include "streams.h"
+#include "config.h"
+#include "profiler.h"
+#include "sound/wavwrite.h"
+
+
+
+/***************************************************************************
+ DEBUGGING
+***************************************************************************/
+
+#define VERBOSE (0)
+
+#if VERBOSE
+#define VPRINTF(x) mame_printf_debug x
+#else
+#define VPRINTF(x)
+#endif
+
+
+
+/***************************************************************************
+ CONSTANTS
+***************************************************************************/
+
+#define MAX_MIXER_CHANNELS 100
+#define SOUND_UPDATE_FREQUENCY ATTOTIME_IN_HZ(50)
+
+
+
+/***************************************************************************
+ TYPE DEFINITIONS
+***************************************************************************/
+
+typedef struct _sound_output sound_output;
+struct _sound_output
+{
+ sound_stream * stream; /* associated stream */
+ int output; /* output number */
+};
+
+
+typedef struct _sound_info sound_info;
+struct _sound_info
+{
+ const sound_config *sound; /* pointer to the sound info */
+ int outputs; /* number of outputs from this instance */
+ sound_output * output; /* array of output information */
+};
+
+
+typedef struct _speaker_input speaker_input;
+struct _speaker_input
+{
+ float gain; /* current gain */
+ float default_gain; /* default gain */
+ char * name; /* name of this input */
+};
+
+
+typedef struct _speaker_info speaker_info;
+struct _speaker_info
+{
+ const speaker_config *speaker; /* pointer to the speaker info */
+ sound_stream * mixer_stream; /* mixing stream */
+ int inputs; /* number of input streams */
+ speaker_input * input; /* array of input information */
+#ifdef MAME_DEBUG
+ INT32 max_sample; /* largest sample value we've seen */
+ INT32 clipped_samples; /* total number of clipped samples */
+ INT32 total_samples; /* total number of samples */
+#endif
+};
+
+
+
+/***************************************************************************
+ GLOBAL VARIABLES
+***************************************************************************/
+
+static emu_timer *sound_update_timer;
+
+static int totalsnd;
+static sound_info sound[MAX_SOUND];
+
+static int totalspeakers;
+static speaker_info speaker[MAX_SPEAKER];
+
+static INT16 *finalmix;
+static UINT32 finalmix_leftover;
+static INT32 *leftmix, *rightmix;
+
+static int sound_muted;
+static int sound_attenuation;
+static int global_sound_enabled;
+static int nosound_mode;
+
+static wav_file *wavfile;
+
+
+
+/***************************************************************************
+ FUNCTION PROTOTYPES
+***************************************************************************/
+
+static void sound_reset(running_machine *machine);
+static void sound_exit(running_machine *machine);
+static void sound_pause(running_machine *machine, int pause);
+static void sound_load(int config_type, xml_data_node *parentnode);
+static void sound_save(int config_type, xml_data_node *parentnode);
+static TIMER_CALLBACK( sound_update );
+static void start_sound_chips(void);
+static void start_speakers(void);
+static void route_sound(void);
+static void mixer_update(void *param, stream_sample_t **inputs, stream_sample_t **buffer, int length);
+
+
+
+/***************************************************************************
+ INLINE FUNCTIONS
+***************************************************************************/
+
+/*-------------------------------------------------
+ find_speaker_by_tag - find a tagged speaker
+-------------------------------------------------*/
+
+INLINE speaker_info *find_speaker_by_tag(const char *tag)
+{
+ int spknum;
+
+ /* attempt to find the speaker in our list */
+ for (spknum = 0; spknum < totalspeakers; spknum++)
+ if (strcmp(speaker[spknum].speaker->tag, tag) == 0)
+ return &speaker[spknum];
+ return NULL;
+}
+
+
+/*-------------------------------------------------
+ find_sound_by_tag - find a tagged sound chip
+-------------------------------------------------*/
+
+INLINE sound_info *find_sound_by_tag(const char *tag)
+{
+ int sndnum;
+
+ /* attempt to find the speaker in our list */
+ for (sndnum = 0; sndnum < totalsnd; sndnum++)
+ if (sound[sndnum].sound->tag && !strcmp(sound[sndnum].sound->tag, tag))
+ return &sound[sndnum];
+ return NULL;
+}
+
+
+
+/***************************************************************************
+ INITIALIZATION
+***************************************************************************/
+
+/*-------------------------------------------------
+ sound_init - start up the sound system
+-------------------------------------------------*/
+
+void sound_init(running_machine *machine)
+{
+ attotime update_frequency = SOUND_UPDATE_FREQUENCY;
+ const char *filename;
+
+ /* handle -nosound */
+ nosound_mode = !options_get_bool(mame_options(), OPTION_SOUND);
+ if (nosound_mode)
+ Machine->sample_rate = 11025;
+
+ /* count the speakers */
+ for (totalspeakers = 0; Machine->drv->speaker[totalspeakers].tag; totalspeakers++) ;
+ VPRINTF(("total speakers = %d\n", totalspeakers));
+
+ /* allocate memory for mix buffers */
+ leftmix = auto_malloc(Machine->sample_rate * sizeof(*leftmix));
+ rightmix = auto_malloc(Machine->sample_rate * sizeof(*rightmix));
+ finalmix = auto_malloc(Machine->sample_rate * sizeof(*finalmix));
+
+ /* allocate a global timer for sound timing */
+ sound_update_timer = timer_alloc(sound_update);
+ timer_adjust(sound_update_timer, update_frequency, 0, update_frequency);
+
+ /* initialize the streams engine */
+ VPRINTF(("streams_init\n"));
+ streams_init(machine, update_frequency.attoseconds);
+
+ /* now start up the sound chips and tag their streams */
+ VPRINTF(("start_sound_chips\n"));
+ start_sound_chips();
+
+ /* then create all the speakers */
+ VPRINTF(("start_speakers\n"));
+ start_speakers();
+
+ /* finally, do all the routing */
+ VPRINTF(("route_sound\n"));
+ route_sound();
+
+ /* open the output WAV file if specified */
+ filename = options_get_string(mame_options(), OPTION_WAVWRITE);
+ if (filename[0] != 0)
+ wavfile = wav_open(filename, machine->sample_rate, 2);
+
+ /* enable sound by default */
+ global_sound_enabled = TRUE;
+ sound_muted = FALSE;
+ sound_set_attenuation(options_get_int(mame_options(), OPTION_VOLUME));
+
+ /* register callbacks */
+ config_register("mixer", sound_load, sound_save);
+ add_pause_callback(machine, sound_pause);
+ add_reset_callback(machine, sound_reset);
+ add_exit_callback(machine, sound_exit);
+}
+
+
+/*-------------------------------------------------
+ sound_exit - clean up after ourselves
+-------------------------------------------------*/
+
+static void sound_exit(running_machine *machine)
+{
+ int sndnum;
+
+ /* close any open WAV file */
+ if (wavfile != NULL)
+ wav_close(wavfile);
+
+#ifdef MAME_DEBUG
+{
+ int spknum;
+
+ /* log the maximum sample values for all speakers */
+ for (spknum = 0; spknum < totalspeakers; spknum++)
+ if (speaker[spknum].max_sample > 0)
+ {
+ speaker_info *spk = &speaker[spknum];
+ mame_printf_debug("Speaker \"%s\" - max = %d (gain *= %f) - %d%% samples clipped\n", spk->speaker->tag, spk->max_sample, 32767.0 / (spk->max_sample ? spk->max_sample : 1), (int)((double)spk->clipped_samples * 100.0 / spk->total_samples));
+ }
+}
+#endif /* MAME_DEBUG */
+
+ /* stop all the sound chips */
+ for (sndnum = 0; sndnum < MAX_SOUND; sndnum++)
+ if (Machine->drv->sound[sndnum].type != SOUND_DUMMY)
+ sndintrf_exit_sound(sndnum);
+
+ /* reset variables */
+ totalspeakers = 0;
+ totalsnd = 0;
+ memset(&speaker, 0, sizeof(speaker));
+ memset(&sound, 0, sizeof(sound));
+}
+
+
+
+/***************************************************************************
+ INITIALIZATION HELPERS
+***************************************************************************/
+
+/*-------------------------------------------------
+ start_sound_chips - loop over all sound chips
+ and initialize them
+-------------------------------------------------*/
+
+static void start_sound_chips(void)
+{
+ int sndnum;
+
+ /* reset the sound array */
+ memset(sound, 0, sizeof(sound));
+
+ /* start up all the sound chips */
+ for (sndnum = 0; sndnum < MAX_SOUND; sndnum++)
+ {
+ const sound_config *msound = &Machine->drv->sound[sndnum];
+ sound_info *info;
+ int num_regs;
+ int index;
+
+ /* stop when we hit an empty entry */
+ if (msound->type == SOUND_DUMMY)
+ break;
+ totalsnd++;
+
+ /* zap all the info */
+ info = &sound[sndnum];
+ memset(info, 0, sizeof(*info));
+
+ /* copy in all the relevant info */
+ info->sound = msound;
+
+ /* start the chip, tagging all its streams */
+ VPRINTF(("sndnum = %d -- sound_type = %d\n", sndnum, msound->type));
+ num_regs = state_save_get_reg_count();
+ streams_set_tag(Machine, info);
+ if (sndintrf_init_sound(sndnum, msound->type, msound->clock, msound->config) != 0)
+ fatalerror("Sound chip #%d (%s) failed to initialize!", sndnum, sndnum_name(sndnum));
+
+ /* if no state registered for saving, we can't save */
+ num_regs = state_save_get_reg_count() - num_regs;
+ if (num_regs == 0)
+ {
+ logerror("Sound chip #%d (%s) did not register any state to save!\n", sndnum, sndnum_name(sndnum));
+ if (Machine->gamedrv->flags & GAME_SUPPORTS_SAVE)
+ fatalerror("Sound chip #%d (%s) did not register any state to save!", sndnum, sndnum_name(sndnum));
+ }
+
+ /* now count the outputs */
+ VPRINTF(("Counting outputs\n"));
+ for (index = 0; ; index++)
+ {
+ sound_stream *stream = stream_find_by_tag(info, index);
+ if (!stream)
+ break;
+ info->outputs += stream_get_outputs(stream);
+ VPRINTF((" stream %p, %d outputs\n", stream, stream_get_outputs(stream)));
+ }
+
+ /* if we have outputs, examine them */
+ if (info->outputs)
+ {
+ /* allocate an array to hold them */
+ info->output = auto_malloc(info->outputs * sizeof(*info->output));
+ VPRINTF((" %d outputs total\n", info->outputs));
+
+ /* now fill the array */
+ info->outputs = 0;
+ for (index = 0; ; index++)
+ {
+ sound_stream *stream = stream_find_by_tag(info, index);
+ int outputs, outputnum;
+
+ if (!stream)
+ break;
+ outputs = stream_get_outputs(stream);
+
+ /* fill in an entry for each output */
+ for (outputnum = 0; outputnum < outputs; outputnum++)
+ {
+ info->output[info->outputs].stream = stream;
+ info->output[info->outputs].output = outputnum;
+ info->outputs++;
+ }
+ }
+ }
+ }
+}
+
+
+/*-------------------------------------------------
+ start_speakers - loop over all speakers and
+ initialize them
+-------------------------------------------------*/
+
+static void start_speakers(void)
+{
+ /* reset the speaker array */
+ memset(speaker, 0, sizeof(speaker));
+
+ /* start up all the speakers */
+ for (totalspeakers = 0; totalspeakers < MAX_SPEAKER; totalspeakers++)
+ {
+ const speaker_config *mspeaker = &Machine->drv->speaker[totalspeakers];
+ speaker_info *info;
+
+ /* stop when we hit an empty entry */
+ if (mspeaker->tag == NULL)
+ break;
+
+ /* zap all the info */
+ info = &speaker[totalspeakers];
+ memset(info, 0, sizeof(*info));
+
+ /* copy in all the relevant info */
+ info->speaker = mspeaker;
+ info->mixer_stream = NULL;
+ info->inputs = 0;
+ }
+}
+
+
+/*-------------------------------------------------
+ route_sound - route sound outputs to target
+ inputs
+-------------------------------------------------*/
+
+static void route_sound(void)
+{
+ int sndnum, spknum, routenum, outputnum;
+
+ /* iterate over all the sound chips */
+ for (sndnum = 0; sndnum < totalsnd; sndnum++)
+ {
+ sound_info *info = &sound[sndnum];
+
+ /* iterate over all routes */
+ for (routenum = 0; routenum < info->sound->routes; routenum++)
+ {
+ const sound_route *mroute = &info->sound->route[routenum];
+ speaker_info *speaker;
+ sound_info *sound;
+
+ /* find the target */
+ speaker = find_speaker_by_tag(mroute->target);
+ sound = find_sound_by_tag(mroute->target);
+
+ /* if neither found, it's fatal */
+ if (speaker == NULL && sound == NULL)
+ fatalerror("Sound route \"%s\" not found!\n", mroute->target);
+
+ /* if we got a speaker, bump its input count */
+ if (speaker != NULL)
+ {
+ if (mroute->output >= 0 && mroute->output < info->outputs)
+ speaker->inputs++;
+ else if (mroute->output == ALL_OUTPUTS)
+ speaker->inputs += info->outputs;
+ }
+ }
+ }
+
+ /* now allocate the mixers and input data */
+ streams_set_tag(Machine, NULL);
+ for (spknum = 0; spknum < totalspeakers; spknum++)
+ {
+ speaker_info *info = &speaker[spknum];
+ if (info->inputs != 0)
+ {
+ info->mixer_stream = stream_create(info->inputs, 1, Machine->sample_rate, info, mixer_update);
+ info->input = auto_malloc(info->inputs * sizeof(*info->input));
+ info->inputs = 0;
+ }
+ else
+ logerror("Warning: speaker \"%s\" has no inputs\n", info->speaker->tag);
+ }
+
+ /* iterate again over all the sound chips */
+ for (sndnum = 0; sndnum < totalsnd; sndnum++)
+ {
+ sound_info *info = &sound[sndnum];
+
+ /* iterate over all routes */
+ for (routenum = 0; routenum < info->sound->routes; routenum++)
+ {
+ const sound_route *mroute = &info->sound->route[routenum];
+ speaker_info *speaker;
+ sound_info *sound;
+
+ /* find the target */
+ speaker = find_speaker_by_tag(mroute->target);
+ sound = find_sound_by_tag(mroute->target);
+
+ /* if it's a speaker, set the input */
+ if (speaker != NULL)
+ {
+ for (outputnum = 0; outputnum < info->outputs; outputnum++)
+ if (mroute->output == outputnum || mroute->output == ALL_OUTPUTS)
+ {
+ char namebuf[256];
+ int index;
+
+ sndnum_to_sndti(sndnum, &index);
+
+ /* fill in the input data on this speaker */
+ speaker->input[speaker->inputs].gain = mroute->gain;
+ speaker->input[speaker->inputs].default_gain = mroute->gain;
+ sprintf(namebuf, "%s:%s #%d.%d", speaker->speaker->tag, sndnum_name(sndnum), index, outputnum);
+ speaker->input[speaker->inputs].name = auto_strdup(namebuf);
+
+ /* connect the output to the input */
+ stream_set_input(speaker->mixer_stream, speaker->inputs++, info->output[outputnum].stream, info->output[outputnum].output, mroute->gain);
+ }
+ }
+
+ /* if it's a sound chip, set the input */
+ else
+ {
+ for (outputnum = 0; outputnum < info->outputs; outputnum++)
+ if (mroute->output == outputnum || mroute->output == ALL_OUTPUTS)
+ stream_set_input(sound->output[0].stream, 0, info->output[outputnum].stream, info->output[outputnum].output, mroute->gain);
+ }
+ }
+ }
+}
+
+
+
+/***************************************************************************
+ GLOBAL STATE MANAGEMENT
+***************************************************************************/
+
+/*-------------------------------------------------
+ sound_reset - reset all sound chips
+-------------------------------------------------*/
+
+static void sound_reset(running_machine *machine)
+{
+ int sndnum;
+
+ /* reset all the sound chips */
+ for (sndnum = 0; sndnum < MAX_SOUND; sndnum++)
+ if (Machine->drv->sound[sndnum].type != SOUND_DUMMY)
+ sndnum_reset(sndnum);
+}
+
+
+/*-------------------------------------------------
+ sound_pause - pause sound output
+-------------------------------------------------*/
+
+static void sound_pause(running_machine *machine, int pause)
+{
+ if (pause)
+ sound_muted |= 0x02;
+ else
+ sound_muted &= ~0x02;
+ osd_set_mastervolume(sound_muted ? -32 : sound_attenuation);
+}
+
+
+/*-------------------------------------------------
+ sound_pause - pause sound output
+-------------------------------------------------*/
+
+void sound_mute(int mute)
+{
+ if (mute)
+ sound_muted |= 0x01;
+ else
+ sound_muted &= ~0x01;
+ osd_set_mastervolume(sound_muted ? -32 : sound_attenuation);
+}
+
+
+/*-------------------------------------------------
+ sound_set_attenuation - set the global volume
+-------------------------------------------------*/
+
+void sound_set_attenuation(int attenuation)
+{
+ sound_attenuation = attenuation;
+ osd_set_mastervolume(sound_muted ? -32 : sound_attenuation);
+}
+
+
+/*-------------------------------------------------
+ sound_get_attenuation - return the global
+ volume
+-------------------------------------------------*/
+
+int sound_get_attenuation(void)
+{
+ return sound_attenuation;
+}
+
+
+/*-------------------------------------------------
+ sound_global_enable - enable/disable sound
+ globally
+-------------------------------------------------*/
+
+void sound_global_enable(int enable)
+{
+ global_sound_enabled = enable;
+}
+
+
+
+/***************************************************************************
+ SOUND SAVE/LOAD
+***************************************************************************/
+
+/*-------------------------------------------------
+ sound_load - read and apply data from the
+ configuration file
+-------------------------------------------------*/
+
+static void sound_load(int config_type, xml_data_node *parentnode)
+{
+ xml_data_node *channelnode;
+ int mixernum;
+
+ /* we only care about game files */
+ if (config_type != CONFIG_TYPE_GAME)
+ return;
+
+ /* might not have any data */
+ if (parentnode == NULL)
+ return;
+
+ /* iterate over channel nodes */
+ for (channelnode = xml_get_sibling(parentnode->child, "channel"); channelnode; channelnode = xml_get_sibling(channelnode->next, "channel"))
+ {
+ mixernum = xml_get_attribute_int(channelnode, "index", -1);
+ if (mixernum >= 0 && mixernum < MAX_MIXER_CHANNELS)
+ {
+ float defvol = xml_get_attribute_float(channelnode, "defvol", -1000.0);
+ float newvol = xml_get_attribute_float(channelnode, "newvol", -1000.0);
+ if (defvol == sound_get_default_gain(mixernum) && newvol != -1000.0)
+ sound_set_user_gain(mixernum, newvol);
+ }
+ }
+}
+
+
+/*-------------------------------------------------
+ sound_save - save data to the configuration
+ file
+-------------------------------------------------*/
+
+static void sound_save(int config_type, xml_data_node *parentnode)
+{
+ int mixernum;
+
+ /* we only care about game files */
+ if (config_type != CONFIG_TYPE_GAME)
+ return;
+
+ /* iterate over mixer channels */
+ if (parentnode != NULL)
+ for (mixernum = 0; mixernum < MAX_MIXER_CHANNELS; mixernum++)
+ {
+ float defvol = sound_get_default_gain(mixernum);
+ float newvol = sound_get_user_gain(mixernum);
+
+ if (defvol != newvol)
+ {
+ xml_data_node *channelnode = xml_add_child(parentnode, "channel", NULL);
+ if (channelnode != NULL)
+ {
+ xml_set_attribute_int(channelnode, "index", mixernum);
+ xml_set_attribute_float(channelnode, "defvol", defvol);
+ xml_set_attribute_float(channelnode, "newvol", newvol);
+ }
+ }
+ }
+}
+
+
+
+/***************************************************************************
+ MIXING STAGE
+***************************************************************************/
+
+/*-------------------------------------------------
+ sound_update - mix everything down to
+ its final form and send it to the OSD layer
+-------------------------------------------------*/
+
+static TIMER_CALLBACK( sound_update )
+{
+ UINT32 finalmix_step, finalmix_offset;
+ int samples_this_update = 0;
+ int sample, spknum;
+
+ VPRINTF(("sound_update\n"));
+
+ profiler_mark(PROFILER_SOUND);
+
+ /* force all the speaker streams to generate the proper number of samples */
+ for (spknum = 0; spknum < totalspeakers; spknum++)
+ {
+ speaker_info *spk = &speaker[spknum];
+ const stream_sample_t *stream_buf;
+
+ /* get the output buffer */
+ if (spk->mixer_stream != NULL)
+ {
+ int numsamples;
+
+ /* update the stream, getting the start/end pointers around the operation */
+ stream_buf = stream_get_output_since_last_update(spk->mixer_stream, 0, &numsamples);
+
+ /* set or assert that all streams have the same count */
+ if (samples_this_update == 0)
+ {
+ samples_this_update = numsamples;
+
+ /* reset the mixing streams */
+ memset(leftmix, 0, samples_this_update * sizeof(*leftmix));
+ memset(rightmix, 0, samples_this_update * sizeof(*rightmix));
+ }
+ assert(samples_this_update == numsamples);
+
+#ifdef MAME_DEBUG
+ /* debug version: keep track of the maximum sample */
+ for (sample = 0; sample < samples_this_update; sample++)
+ {
+ if (stream_buf[sample] > spk->max_sample)
+ spk->max_sample = stream_buf[sample];
+ else if (-stream_buf[sample] > spk->max_sample)
+ spk->max_sample = -stream_buf[sample];
+ if (stream_buf[sample] > 32767 || stream_buf[sample] < -32768)
+ spk->clipped_samples++;
+ spk->total_samples++;
+ }
+#endif
+
+ /* mix if sound is enabled */
+ if (global_sound_enabled && !nosound_mode)
+ {
+ /* if the speaker is centered, send to both left and right */
+ if (spk->speaker->x == 0)
+ for (sample = 0; sample < samples_this_update; sample++)
+ {
+ leftmix[sample] += stream_buf[sample];
+ rightmix[sample] += stream_buf[sample];
+ }
+
+ /* if the speaker is to the left, send only to the left */
+ else if (spk->speaker->x < 0)
+ for (sample = 0; sample < samples_this_update; sample++)
+ leftmix[sample] += stream_buf[sample];
+
+ /* if the speaker is to the right, send only to the right */
+ else
+ for (sample = 0; sample < samples_this_update; sample++)
+ rightmix[sample] += stream_buf[sample];
+ }
+ }
+ }
+
+ /* now downmix the final result */
+ finalmix_step = video_get_speed_factor();
+ finalmix_offset = 0;
+ for (sample = finalmix_leftover; sample < samples_this_update * 100; sample += finalmix_step)
+ {
+ int sampindex = sample / 100;
+ INT32 samp;
+
+ /* clamp the left side */
+ samp = leftmix[sampindex];
+ if (samp < -32768)
+ samp = -32768;
+ else if (samp > 32767)
+ samp = 32767;
+ finalmix[finalmix_offset++] = samp;
+
+ /* clamp the right side */
+ samp = rightmix[sampindex];
+ if (samp < -32768)
+ samp = -32768;
+ else if (samp > 32767)
+ samp = 32767;
+ finalmix[finalmix_offset++] = samp;
+ }
+ finalmix_leftover = sample - samples_this_update * 100;
+
+ /* play the result */
+ if (finalmix_offset > 0)
+ {
+ osd_update_audio_stream(finalmix, finalmix_offset / 2);
+ if (wavfile != NULL)
+ wav_add_data_16(wavfile, finalmix, finalmix_offset);
+ }
+
+ /* update the streamer */
+ streams_update(machine);
+
+ profiler_mark(PROFILER_END);
+}
+
+
+/*-------------------------------------------------
+ mixer_update - mix all inputs to one output
+-------------------------------------------------*/
+
+static void mixer_update(void *param, stream_sample_t **inputs, stream_sample_t **buffer, int length)
+{
+ speaker_info *speaker = param;
+ int numinputs = speaker->inputs;
+ int pos;
+
+ VPRINTF(("Mixer_update(%d)\n", length));
+
+ /* loop over samples */
+ for (pos = 0; pos < length; pos++)
+ {
+ INT32 sample = inputs[0][pos];
+ int inp;
+
+ /* add up all the inputs */
+ for (inp = 1; inp < numinputs; inp++)
+ sample += inputs[inp][pos];
+ buffer[0][pos] = sample;
+ }
+}
+
+
+
+/***************************************************************************
+ MISCELLANEOUS HELPERS
+***************************************************************************/
+
+/*-------------------------------------------------
+ sndti_set_output_gain - set the gain of a
+ particular output
+-------------------------------------------------*/
+
+void sndti_set_output_gain(sound_type type, int index, int output, float gain)
+{
+ int sndnum = sndti_to_sndnum(type, index);
+
+ if (sndnum < 0)
+ {
+ logerror("sndti_set_output_gain called for invalid sound type %d, index %d\n", type, index);
+ return;
+ }
+ if (output >= sound[sndnum].outputs)
+ {
+ logerror("sndti_set_output_gain called for invalid sound output %d (type %d, index %d)\n", output, type, index);
+ return;
+ }
+ stream_set_output_gain(sound[sndnum].output[output].stream, sound[sndnum].output[output].output, gain);
+}
+
+
+
+/***************************************************************************
+ USER GAIN CONTROLS
+***************************************************************************/
+
+/*-------------------------------------------------
+ index_to_input - map an absolute index to
+ a particular input
+-------------------------------------------------*/
+
+INLINE speaker_info *index_to_input(int index, int *input)
+{
+ int count = 0, speakernum;
+
+ /* scan through the speakers until we find the indexed input */
+ for (speakernum = 0; speakernum < totalspeakers; speakernum++)
+ {
+ if (index < count + speaker[speakernum].inputs)
+ {
+ *input = index - count;
+ return &speaker[speakernum];
+ }
+ count += speaker[speakernum].inputs;
+ }
+
+ /* index out of range */
+ return NULL;
+}
+
+
+/*-------------------------------------------------
+ sound_get_user_gain_count - return the number
+ of user-controllable gain parameters
+-------------------------------------------------*/
+
+int sound_get_user_gain_count(void)
+{
+ int count = 0, speakernum;
+
+ /* count up the number of speaker inputs */
+ for (speakernum = 0; speakernum < totalspeakers; speakernum++)
+ count += speaker[speakernum].inputs;
+ return count;
+}
+
+
+/*-------------------------------------------------
+ sound_set_user_gain - set the nth user gain
+ value
+-------------------------------------------------*/
+
+void sound_set_user_gain(int index, float gain)
+{
+ int inputnum;
+ speaker_info *spk = index_to_input(index, &inputnum);
+
+ if (spk != NULL)
+ {
+ spk->input[inputnum].gain = gain;
+ stream_set_input_gain(spk->mixer_stream, inputnum, gain);
+ }
+}
+
+
+/*-------------------------------------------------
+ sound_get_user_gain - get the nth user gain
+ value
+-------------------------------------------------*/
+
+float sound_get_user_gain(int index)
+{
+ int inputnum;
+ speaker_info *spk = index_to_input(index, &inputnum);
+ return (spk != NULL) ? spk->input[inputnum].gain : 0;
+}
+
+
+/*-------------------------------------------------
+ sound_get_default_gain - return the default
+ gain of the nth user value
+-------------------------------------------------*/
+
+float sound_get_default_gain(int index)
+{
+ int inputnum;
+ speaker_info *spk = index_to_input(index, &inputnum);
+ return (spk != NULL) ? spk->input[inputnum].default_gain : 0;
+}
+
+
+/*-------------------------------------------------
+ sound_get_user_gain_name - return the name
+ of the nth user value
+-------------------------------------------------*/
+
+const char *sound_get_user_gain_name(int index)
+{
+ int inputnum;
+ speaker_info *spk = index_to_input(index, &inputnum);
+ return (spk != NULL) ? spk->input[inputnum].name : NULL;
+}
+
+
+/*-------------------------------------------------
+ sound_find_sndnum_by_tag - return a sndnum
+ by finding the appropriate tag
+-------------------------------------------------*/
+
+int sound_find_sndnum_by_tag(const char *tag)
+{
+ int index;
+
+ /* find a match */
+ for (index = 0; index < MAX_SOUND; index++)
+ if (sound[index].sound != NULL && sound[index].sound->tag != NULL)
+ if (strcmp(tag, sound[index].sound->tag) == 0)
+ return index;
+
+ return -1;
+}