mirror of
https://github.com/holub/mame
synced 2025-05-28 08:33:05 +03:00

osd_free(). They take the same parameters as malloc() and free(). Renamed mamecore.h -> emucore.h. New C++-aware memory manager, implemented in emualloc.*. This is a simple manager that allows you to add any type of object to a resource pool. Most commonly, allocated objects are added, and so a set of allocation macros is provided to allow you to manage objects in a particular pool: pool_alloc(p, t) = allocate object of type 't' and add to pool 'p' pool_alloc_clear(p, t) = same as above, but clear the memory first pool_alloc_array(p, t, c) = allocate an array of 'c' objects of type 't' and add to pool 'p' pool_alloc_array_clear(p, t, c) = same, but with clearing pool_free(p, v) = free object 'v' and remove it from the pool Note that pool_alloc[_clear] is roughly equivalent to "new t" and pool_alloc_array[_clear] is roughly equivalent to "new t[c]". Also note that pool_free works for single objects and arrays. There is a single global_resource_pool defined which should be used for any global allocations. It has equivalent macros to the pool_* macros above that automatically target the global pool. In addition, the memory module defines global new/delete overrides that access file and line number parameters so that allocations can be tracked. Currently this tracking is only done if MAME_DEBUG is enabled. In debug builds, any unfreed memory will be printed at the end of the session. emualloc.h also has #defines to disable malloc/free/realloc/calloc. Since emualloc.h is included by emucore.h, this means pretty much all code within the emulator is forced to use the new allocators. Although straight new/delete do work, their use is discouraged, as any allocations made with them will not be tracked. Changed the familar auto_alloc_* macros to map to the resource pool model described above. The running_machine is now a class and contains a resource pool which is automatically destructed upon deletion. If you are a driver writer, all your allocations should be done with auto_alloc_*. Changed all drivers and files in the core using malloc/realloc or the old alloc_*_or_die macros to use (preferably) the auto_alloc_* macros instead, or the global_alloc_* macros if necessary. Added simple C++ wrappers for astring and bitmap_t, as these need proper constructors/destructors to be used for auto_alloc_astring and auto_alloc_bitmap. Removed references to the winalloc prefix file. Most of its functionality has moved into the core, save for the guard page allocations, which are now implemented in osd_alloc and osd_free.
1020 lines
30 KiB
C
1020 lines
30 KiB
C
/***************************************************************************
|
|
|
|
sound.c
|
|
|
|
Core sound functions and definitions.
|
|
|
|
Copyright 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"
|
|
#include "sound.h"
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
DEBUGGING
|
|
***************************************************************************/
|
|
|
|
#define VERBOSE (0)
|
|
|
|
#define VPRINTF(x) do { if (VERBOSE) mame_printf_debug x; } while (0)
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
CONSTANTS
|
|
***************************************************************************/
|
|
|
|
#define MAX_MIXER_CHANNELS 100
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
TYPE DEFINITIONS
|
|
***************************************************************************/
|
|
|
|
typedef struct _sound_output sound_output;
|
|
struct _sound_output
|
|
{
|
|
sound_stream * stream; /* associated stream */
|
|
int output; /* output number */
|
|
};
|
|
|
|
|
|
typedef struct _sound_class_data sound_class_data;
|
|
struct _sound_class_data
|
|
{
|
|
int outputs; /* number of outputs from this instance */
|
|
sound_output output[MAX_OUTPUTS]; /* 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 */
|
|
const char * tag; /* speaker tag */
|
|
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
|
|
};
|
|
|
|
struct _sound_private
|
|
{
|
|
emu_timer *update_timer;
|
|
|
|
int totalsnd;
|
|
|
|
UINT32 finalmix_leftover;
|
|
INT16 *finalmix;
|
|
INT32 *leftmix;
|
|
INT32 *rightmix;
|
|
|
|
int muted;
|
|
int attenuation;
|
|
int enabled;
|
|
int nosound_mode;
|
|
|
|
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(running_machine *machine, int config_type, xml_data_node *parentnode);
|
|
static void sound_save(running_machine *machine, int config_type, xml_data_node *parentnode);
|
|
static TIMER_CALLBACK( sound_update );
|
|
static void route_sound(running_machine *machine);
|
|
static STREAM_UPDATE( mixer_update );
|
|
static STATE_POSTLOAD( mixer_postload );
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
INLINE FUNCTIONS
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
get_class_data - return a pointer to the
|
|
class data
|
|
-------------------------------------------------*/
|
|
|
|
INLINE sound_class_data *get_class_data(const device_config *device)
|
|
{
|
|
assert(device != NULL);
|
|
assert(device->type == SOUND);
|
|
assert(device->devclass == DEVICE_CLASS_SOUND_CHIP);
|
|
assert(device->token != NULL);
|
|
return (sound_class_data *)((UINT8 *)device->token + device->tokenbytes) - 1;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
get_safe_token - makes sure that the passed
|
|
in device is, in fact, a timer
|
|
-------------------------------------------------*/
|
|
|
|
INLINE speaker_info *get_safe_token(const device_config *device)
|
|
{
|
|
assert(device != NULL);
|
|
assert(device->token != NULL);
|
|
assert(device->type == SPEAKER_OUTPUT);
|
|
|
|
return (speaker_info *)device->token;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
index_to_input - map an absolute index to
|
|
a particular input
|
|
-------------------------------------------------*/
|
|
|
|
INLINE speaker_info *index_to_input(running_machine *machine, int index, int *input)
|
|
{
|
|
const device_config *curspeak;
|
|
int count = 0;
|
|
|
|
/* scan through the speakers until we find the indexed input */
|
|
for (curspeak = speaker_output_first(machine->config); curspeak != NULL; curspeak = speaker_output_next(curspeak))
|
|
{
|
|
speaker_info *info = (speaker_info *)curspeak->token;
|
|
if (index < count + info->inputs)
|
|
{
|
|
*input = index - count;
|
|
return info;
|
|
}
|
|
count += info->inputs;
|
|
}
|
|
|
|
/* index out of range */
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
INITIALIZATION
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
sound_init - start up the sound system
|
|
-------------------------------------------------*/
|
|
|
|
void sound_init(running_machine *machine)
|
|
{
|
|
sound_private *global;
|
|
const char *filename;
|
|
|
|
machine->sound_data = global = auto_alloc_clear(machine, sound_private);
|
|
|
|
/* handle -nosound */
|
|
global->nosound_mode = !options_get_bool(mame_options(), OPTION_SOUND);
|
|
if (global->nosound_mode)
|
|
machine->sample_rate = 11025;
|
|
|
|
/* count the speakers */
|
|
VPRINTF(("total speakers = %d\n", speaker_output_count(machine->config)));
|
|
|
|
/* allocate memory for mix buffers */
|
|
global->leftmix = auto_alloc_array(machine, INT32, machine->sample_rate);
|
|
global->rightmix = auto_alloc_array(machine, INT32, machine->sample_rate);
|
|
global->finalmix = auto_alloc_array(machine, INT16, machine->sample_rate);
|
|
|
|
/* allocate a global timer for sound timing */
|
|
global->update_timer = timer_alloc(machine, sound_update, NULL);
|
|
timer_adjust_periodic(global->update_timer, STREAMS_UPDATE_ATTOTIME, 0, STREAMS_UPDATE_ATTOTIME);
|
|
|
|
/* finally, do all the routing */
|
|
VPRINTF(("route_sound\n"));
|
|
route_sound(machine);
|
|
|
|
/* open the output WAV file if specified */
|
|
filename = options_get_string(mame_options(), OPTION_WAVWRITE);
|
|
if (filename[0] != 0)
|
|
global->wavfile = wav_open(filename, machine->sample_rate, 2);
|
|
|
|
/* enable sound by default */
|
|
global->enabled = TRUE;
|
|
global->muted = FALSE;
|
|
sound_set_attenuation(machine, options_get_int(mame_options(), OPTION_VOLUME));
|
|
|
|
/* register callbacks */
|
|
config_register(machine, "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)
|
|
{
|
|
sound_private *global = machine->sound_data;
|
|
|
|
/* close any open WAV file */
|
|
if (global->wavfile != NULL)
|
|
wav_close(global->wavfile);
|
|
global->wavfile = NULL;
|
|
|
|
/* reset variables */
|
|
global->totalsnd = 0;
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
SOUND DEVICE INTERFACE
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
device_start_sound - device start callback
|
|
-------------------------------------------------*/
|
|
|
|
static DEVICE_START( sound )
|
|
{
|
|
sound_class_data *classdata;
|
|
const sound_config *config;
|
|
deviceinfo devinfo;
|
|
int num_regs, outputnum;
|
|
|
|
/* validate some basic stuff */
|
|
assert(device != NULL);
|
|
assert(device->inline_config != NULL);
|
|
assert(device->machine != NULL);
|
|
assert(device->machine->config != NULL);
|
|
|
|
/* get pointers to our data */
|
|
config = (const sound_config *)device->inline_config;
|
|
classdata = get_class_data(device);
|
|
|
|
/* get the chip's start function */
|
|
devinfo.start = NULL;
|
|
(*config->type)(device, DEVINFO_FCT_START, &devinfo);
|
|
assert(devinfo.start != NULL);
|
|
|
|
/* initialize this sound chip */
|
|
num_regs = state_save_get_reg_count(device->machine);
|
|
(*devinfo.start)(device);
|
|
num_regs = state_save_get_reg_count(device->machine) - num_regs;
|
|
|
|
/* now count the outputs */
|
|
VPRINTF(("Counting outputs\n"));
|
|
for (outputnum = 0; outputnum < MAX_OUTPUTS; outputnum++)
|
|
{
|
|
sound_stream *stream = stream_find_by_device(device, outputnum);
|
|
int curoutput, numoutputs;
|
|
|
|
/* stop when we run out of streams */
|
|
if (stream == NULL)
|
|
break;
|
|
|
|
/* accumulate the number of outputs from this stream */
|
|
numoutputs = stream_get_outputs(stream);
|
|
assert(classdata->outputs < MAX_OUTPUTS);
|
|
|
|
/* fill in the array */
|
|
for (curoutput = 0; curoutput < numoutputs; curoutput++)
|
|
{
|
|
sound_output *output = &classdata->output[classdata->outputs++];
|
|
output->stream = stream;
|
|
output->output = curoutput;
|
|
}
|
|
}
|
|
|
|
/* if no state registered for saving, we can't save */
|
|
if (num_regs == 0)
|
|
{
|
|
logerror("Sound chip '%s' did not register any state to save!\n", device->tag);
|
|
if (device->machine->gamedrv->flags & GAME_SUPPORTS_SAVE)
|
|
fatalerror("Sound chip '%s' did not register any state to save!", device->tag);
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
device_custom_config_sound - custom inline
|
|
config callback for populating sound routes
|
|
-------------------------------------------------*/
|
|
|
|
static DEVICE_CUSTOM_CONFIG( sound )
|
|
{
|
|
sound_config *config = (sound_config *)device->inline_config;
|
|
|
|
switch (entrytype)
|
|
{
|
|
/* custom config 1 is a new route */
|
|
case MCONFIG_TOKEN_DEVICE_CONFIG_CUSTOM_1:
|
|
{
|
|
sound_route **routeptr;
|
|
int output, input;
|
|
UINT32 gain;
|
|
|
|
/* put back the token that was originally fetched so we can grab a packed 64-bit token */
|
|
TOKEN_UNGET_UINT32(tokens);
|
|
TOKEN_GET_UINT64_UNPACK4(tokens, entrytype, 8, output, 12, input, 12, gain, 32);
|
|
|
|
/* allocate a new route */
|
|
for (routeptr = &config->routelist; *routeptr != NULL; routeptr = &(*routeptr)->next) ;
|
|
*routeptr = global_alloc(sound_route);
|
|
(*routeptr)->next = NULL;
|
|
(*routeptr)->output = output;
|
|
(*routeptr)->input = input;
|
|
(*routeptr)->gain = (float)gain * (1.0f / (float)(1 << 24));
|
|
(*routeptr)->target = TOKEN_GET_STRING(tokens);
|
|
break;
|
|
}
|
|
|
|
/* custom config free is also used as a reset of sound routes */
|
|
case MCONFIG_TOKEN_DEVICE_CONFIG_CUSTOM_FREE:
|
|
while (config->routelist != NULL)
|
|
{
|
|
sound_route *temp = config->routelist;
|
|
config->routelist = temp->next;
|
|
global_free(temp);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return tokens;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
device_get_info_sound - device get info
|
|
callback
|
|
-------------------------------------------------*/
|
|
|
|
DEVICE_GET_INFO( sound )
|
|
{
|
|
const sound_config *config = (device != NULL) ? (const sound_config *)device->inline_config : NULL;
|
|
|
|
switch (state)
|
|
{
|
|
/* --- the following bits of info are returned as 64-bit signed integers --- */
|
|
case DEVINFO_INT_TOKEN_BYTES:
|
|
(*config->type)(device, DEVINFO_INT_TOKEN_BYTES, info);
|
|
info->i += sizeof(sound_class_data);
|
|
break;
|
|
|
|
case DEVINFO_INT_INLINE_CONFIG_BYTES: info->i = sizeof(sound_config); break;
|
|
case DEVINFO_INT_CLASS: info->i = DEVICE_CLASS_SOUND_CHIP; break;
|
|
|
|
/* --- the following bits of info are returned as pointers to data or functions --- */
|
|
case DEVINFO_FCT_START: info->start = DEVICE_START_NAME(sound); break;
|
|
case DEVINFO_FCT_CUSTOM_CONFIG: info->custom_config = DEVICE_CUSTOM_CONFIG_NAME(sound); break;
|
|
|
|
/* --- the following bits of info are returned as NULL-terminated strings --- */
|
|
case DEVINFO_STR_NAME:
|
|
if (config != NULL)
|
|
(*config->type)(device, state, info);
|
|
else
|
|
strcpy(info->s, "sound");
|
|
break;
|
|
|
|
default:
|
|
(*config->type)(device, state, info);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
INITIALIZATION HELPERS
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
route_sound - route sound outputs to target
|
|
inputs
|
|
-------------------------------------------------*/
|
|
|
|
static void route_sound(running_machine *machine)
|
|
{
|
|
astring *tempstring = astring_alloc();
|
|
const device_config *curspeak;
|
|
const device_config *sound;
|
|
int outputnum;
|
|
|
|
/* first count up the inputs for each speaker */
|
|
for (sound = sound_first(machine->config); sound != NULL; sound = sound_next(sound))
|
|
{
|
|
const sound_config *config = (const sound_config *)sound->inline_config;
|
|
int numoutputs = stream_get_device_outputs(sound);
|
|
const sound_route *route;
|
|
|
|
/* iterate over all routes */
|
|
for (route = config->routelist; route != NULL; route = route->next)
|
|
{
|
|
const device_config *target_device = devtag_get_device(machine, route->target);
|
|
|
|
/* if neither found, it's fatal */
|
|
if (target_device == NULL)
|
|
fatalerror("Sound route \"%s\" not found!\n", route->target);
|
|
|
|
/* if we got a speaker, bump its input count */
|
|
if (target_device->type == SPEAKER_OUTPUT)
|
|
get_safe_token(target_device)->inputs += (route->output == ALL_OUTPUTS) ? numoutputs : 1;
|
|
}
|
|
}
|
|
|
|
/* now allocate the mixers and input data */
|
|
for (curspeak = speaker_output_first(machine->config); curspeak != NULL; curspeak = speaker_output_next(curspeak))
|
|
{
|
|
speaker_info *info = get_safe_token(curspeak);
|
|
if (info->inputs != 0)
|
|
{
|
|
info->mixer_stream = stream_create(curspeak, info->inputs, 1, machine->sample_rate, info, mixer_update);
|
|
state_save_register_postload(machine, mixer_postload, info->mixer_stream);
|
|
info->input = auto_alloc_array(machine, speaker_input, info->inputs);
|
|
info->inputs = 0;
|
|
}
|
|
else
|
|
logerror("Warning: speaker \"%s\" has no inputs\n", info->tag);
|
|
}
|
|
|
|
/* iterate again over all the sound chips */
|
|
for (sound = sound_first(machine->config); sound != NULL; sound = sound_next(sound))
|
|
{
|
|
const sound_config *config = (const sound_config *)sound->inline_config;
|
|
int numoutputs = stream_get_device_outputs(sound);
|
|
const sound_route *route;
|
|
|
|
/* iterate over all routes */
|
|
for (route = config->routelist; route != NULL; route = route->next)
|
|
{
|
|
const device_config *target_device = devtag_get_device(machine, route->target);
|
|
int inputnum = route->input;
|
|
sound_stream *stream;
|
|
int streamoutput;
|
|
|
|
/* iterate over all outputs, matching any that apply */
|
|
for (outputnum = 0; outputnum < numoutputs; outputnum++)
|
|
if (route->output == outputnum || route->output == ALL_OUTPUTS)
|
|
{
|
|
/* if it's a speaker, set the input */
|
|
if (target_device->type == SPEAKER_OUTPUT)
|
|
{
|
|
speaker_info *speakerinfo = get_safe_token(target_device);
|
|
|
|
/* generate text for the UI */
|
|
astring_printf(tempstring, "Speaker '%s': %s '%s'", target_device->tag, device_get_name(sound), sound->tag);
|
|
if (numoutputs > 1)
|
|
astring_catprintf(tempstring, " Ch.%d", outputnum);
|
|
|
|
/* fill in the input data on this speaker */
|
|
speakerinfo->input[speakerinfo->inputs].gain = route->gain;
|
|
speakerinfo->input[speakerinfo->inputs].default_gain = route->gain;
|
|
speakerinfo->input[speakerinfo->inputs].name = auto_strdup(machine, astring_c(tempstring));
|
|
|
|
/* connect the output to the input */
|
|
if (stream_device_output_to_stream_output(sound, outputnum, &stream, &streamoutput))
|
|
stream_set_input(speakerinfo->mixer_stream, speakerinfo->inputs++, stream, streamoutput, route->gain);
|
|
}
|
|
|
|
/* otherwise, it's a sound chip */
|
|
else
|
|
{
|
|
sound_stream *inputstream;
|
|
int streaminput;
|
|
|
|
if (stream_device_input_to_stream_input(target_device, inputnum++, &inputstream, &streaminput))
|
|
if (stream_device_output_to_stream_output(sound, outputnum, &stream, &streamoutput))
|
|
stream_set_input(inputstream, streaminput, stream, streamoutput, route->gain);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* free up our temporary string */
|
|
astring_free(tempstring);
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
GLOBAL STATE MANAGEMENT
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
sound_reset - reset all sound chips
|
|
-------------------------------------------------*/
|
|
|
|
static void sound_reset(running_machine *machine)
|
|
{
|
|
const device_config *sound;
|
|
|
|
/* reset all the sound chips */
|
|
for (sound = sound_first(machine->config); sound != NULL; sound = sound_next(sound))
|
|
device_reset(sound);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
sound_pause - pause sound output
|
|
-------------------------------------------------*/
|
|
|
|
static void sound_pause(running_machine *machine, int pause)
|
|
{
|
|
sound_private *global = machine->sound_data;
|
|
|
|
if (pause)
|
|
global->muted |= 0x02;
|
|
else
|
|
global->muted &= ~0x02;
|
|
osd_set_mastervolume(global->muted ? -32 : global->attenuation);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
sound_mute - mute sound output
|
|
-------------------------------------------------*/
|
|
|
|
void sound_mute(running_machine *machine, int mute)
|
|
{
|
|
sound_private *global = machine->sound_data;
|
|
|
|
if (mute)
|
|
global->muted |= 0x01;
|
|
else
|
|
global->muted &= ~0x01;
|
|
osd_set_mastervolume(global->muted ? -32 : global->attenuation);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
sound_set_attenuation - set the global volume
|
|
-------------------------------------------------*/
|
|
|
|
void sound_set_attenuation(running_machine *machine, int attenuation)
|
|
{
|
|
sound_private *global = machine->sound_data;
|
|
global->attenuation = attenuation;
|
|
osd_set_mastervolume(global->muted ? -32 : global->attenuation);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
sound_get_attenuation - return the global
|
|
volume
|
|
-------------------------------------------------*/
|
|
|
|
int sound_get_attenuation(running_machine *machine)
|
|
{
|
|
sound_private *global = machine->sound_data;
|
|
return global->attenuation;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
sound_global_enable - enable/disable sound
|
|
globally
|
|
-------------------------------------------------*/
|
|
|
|
void sound_global_enable(running_machine *machine, int enable)
|
|
{
|
|
sound_private *global = machine->sound_data;
|
|
global->enabled = enable;
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
SOUND SAVE/LOAD
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
sound_load - read and apply data from the
|
|
configuration file
|
|
-------------------------------------------------*/
|
|
|
|
static void sound_load(running_machine *machine, 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 (fabs(defvol - sound_get_default_gain(machine, mixernum)) < 1e-6 && newvol != -1000.0)
|
|
sound_set_user_gain(machine, mixernum, newvol);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
sound_save - save data to the configuration
|
|
file
|
|
-------------------------------------------------*/
|
|
|
|
static void sound_save(running_machine *machine, 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(machine, mixernum);
|
|
float newvol = sound_get_user_gain(machine, 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;
|
|
const device_config *curspeak;
|
|
int samples_this_update = 0;
|
|
int sample;
|
|
sound_private *global = machine->sound_data;
|
|
INT16 *finalmix;
|
|
INT32 *leftmix, *rightmix;
|
|
|
|
VPRINTF(("sound_update\n"));
|
|
|
|
profiler_mark_start(PROFILER_SOUND);
|
|
|
|
leftmix = global->leftmix;
|
|
rightmix = global->rightmix;
|
|
finalmix = global->finalmix;
|
|
|
|
/* force all the speaker streams to generate the proper number of samples */
|
|
for (curspeak = speaker_output_first(machine->config); curspeak != NULL; curspeak = speaker_output_next(curspeak))
|
|
{
|
|
speaker_info *spk = (speaker_info *)curspeak->token;
|
|
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->enabled && !global->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 = global->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;
|
|
}
|
|
global->finalmix_leftover = sample - samples_this_update * 100;
|
|
|
|
/* play the result */
|
|
if (finalmix_offset > 0)
|
|
{
|
|
osd_update_audio_stream(machine, finalmix, finalmix_offset / 2);
|
|
video_avi_add_sound(machine, finalmix, finalmix_offset / 2);
|
|
if (global->wavfile != NULL)
|
|
wav_add_data_16(global->wavfile, finalmix, finalmix_offset);
|
|
}
|
|
|
|
/* update the streamer */
|
|
streams_update(machine);
|
|
|
|
profiler_mark_end();
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
mixer_update - mix all inputs to one output
|
|
-------------------------------------------------*/
|
|
|
|
static STREAM_UPDATE( mixer_update )
|
|
{
|
|
speaker_info *speaker = (speaker_info *)param;
|
|
int numinputs = speaker->inputs;
|
|
int pos;
|
|
|
|
VPRINTF(("Mixer_update(%d)\n", samples));
|
|
|
|
/* loop over samples */
|
|
for (pos = 0; pos < samples; pos++)
|
|
{
|
|
INT32 sample = inputs[0][pos];
|
|
int inp;
|
|
|
|
/* add up all the inputs */
|
|
for (inp = 1; inp < numinputs; inp++)
|
|
sample += inputs[inp][pos];
|
|
outputs[0][pos] = sample;
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
mixer_postload - postload function to reset
|
|
the mixer stream to the proper sample rate
|
|
-------------------------------------------------*/
|
|
|
|
static STATE_POSTLOAD( mixer_postload )
|
|
{
|
|
sound_stream *stream = (sound_stream *)param;
|
|
stream_set_sample_rate(stream, machine->sample_rate);
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
SPEAKER OUTPUT DEVICE INTERFACE
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
speaker_output_start - device start callback
|
|
for a speaker
|
|
-------------------------------------------------*/
|
|
|
|
static DEVICE_START( speaker_output )
|
|
{
|
|
speaker_info *info = (speaker_info *)device->token;
|
|
|
|
/* copy in all the relevant info */
|
|
info->speaker = (const speaker_config *)device->inline_config;
|
|
info->tag = device->tag;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
speaker_output_stop - device stop callback
|
|
for a speaker
|
|
-------------------------------------------------*/
|
|
|
|
static DEVICE_STOP( speaker_output )
|
|
{
|
|
#ifdef MAME_DEBUG
|
|
speaker_info *info = (speaker_info *)device->token;
|
|
|
|
/* log the maximum sample values for all speakers */
|
|
if (info->max_sample > 0)
|
|
mame_printf_debug("Speaker \"%s\" - max = %d (gain *= %f) - %d%% samples clipped\n", info->tag, info->max_sample, 32767.0 / (info->max_sample ? info->max_sample : 1), (int)((double)info->clipped_samples * 100.0 / info->total_samples));
|
|
#endif /* MAME_DEBUG */
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
speaker_output_get_info - device get info
|
|
callback
|
|
-------------------------------------------------*/
|
|
|
|
DEVICE_GET_INFO( speaker_output )
|
|
{
|
|
switch (state)
|
|
{
|
|
/* --- the following bits of info are returned as 64-bit signed integers --- */
|
|
case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(speaker_info); break;
|
|
case DEVINFO_INT_INLINE_CONFIG_BYTES: info->i = sizeof(speaker_config); break;
|
|
case DEVINFO_INT_CLASS: info->i = DEVICE_CLASS_AUDIO; break;
|
|
|
|
/* --- the following bits of info are returned as pointers to data or functions --- */
|
|
case DEVINFO_FCT_START: info->start = DEVICE_START_NAME(speaker_output); break;
|
|
case DEVINFO_FCT_STOP: info->stop = DEVICE_STOP_NAME(speaker_output); break;
|
|
case DEVINFO_FCT_RESET: /* Nothing */ break;
|
|
|
|
/* --- the following bits of info are returned as NULL-terminated strings --- */
|
|
case DEVINFO_STR_NAME: strcpy(info->s, "Speaker"); break;
|
|
case DEVINFO_STR_FAMILY: strcpy(info->s, "Sound"); break;
|
|
case DEVINFO_STR_VERSION: strcpy(info->s, "1.0"); break;
|
|
case DEVINFO_STR_SOURCE_FILE: strcpy(info->s, __FILE__); break;
|
|
case DEVINFO_STR_CREDITS: strcpy(info->s, "Copyright Nicola Salmoria and the MAME Team"); break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
MISCELLANEOUS HELPERS
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
sound_set_output_gain - set the gain of a
|
|
particular output
|
|
-------------------------------------------------*/
|
|
|
|
void sound_set_output_gain(const device_config *device, int output, float gain)
|
|
{
|
|
sound_stream *stream;
|
|
int outputnum;
|
|
|
|
if (stream_device_output_to_stream_output(device, output, &stream, &outputnum))
|
|
stream_set_output_gain(stream, outputnum, gain);
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
USER GAIN CONTROLS
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
sound_get_user_gain_count - return the number
|
|
of user-controllable gain parameters
|
|
-------------------------------------------------*/
|
|
|
|
int sound_get_user_gain_count(running_machine *machine)
|
|
{
|
|
const device_config *curspeak;
|
|
int count = 0;
|
|
|
|
/* count up the number of speaker inputs */
|
|
for (curspeak = speaker_output_first(machine->config); curspeak != NULL; curspeak = speaker_output_next(curspeak))
|
|
{
|
|
speaker_info *info = (speaker_info *)curspeak->token;
|
|
count += info->inputs;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
sound_set_user_gain - set the nth user gain
|
|
value
|
|
-------------------------------------------------*/
|
|
|
|
void sound_set_user_gain(running_machine *machine, int index, float gain)
|
|
{
|
|
int inputnum;
|
|
speaker_info *spk = index_to_input(machine, 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(running_machine *machine, int index)
|
|
{
|
|
int inputnum;
|
|
speaker_info *spk = index_to_input(machine, 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(running_machine *machine, int index)
|
|
{
|
|
int inputnum;
|
|
speaker_info *spk = index_to_input(machine, 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(running_machine *machine, int index)
|
|
{
|
|
int inputnum;
|
|
speaker_info *spk = index_to_input(machine, index, &inputnum);
|
|
return (spk != NULL) ? spk->input[inputnum].name : NULL;
|
|
}
|