/***************************************************************************

    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	MAME_TIME_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 mame_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)
{
	mame_time 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 = mame_timer_alloc(sound_update);
	mame_timer_adjust(sound_update_timer, update_frequency, 0, update_frequency);

	/* initialize the streams engine */
	VPRINTF(("streams_init\n"));
	streams_init(machine, update_frequency.subseconds);

	/* 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;
}
