mame/src/osd/sdl/sound.c
Aaron Giles 06ee6804dd Converted core_options to a class. Removed a bunch of marginal
functionality in favor of alternate mechanisms. Errors are
now reported via an astring rather than via callbacks. Every
option must now specify a type (command, integer, float, string,
boolean, etc). Command behavior has changed so that only one
command is permitted. [Aaron Giles]

Changed fileio system to accept just a raw searchpath instead of
an options/option name combination. [Aaron Giles]

Created emu_options class dervied from core_options which wraps
core emulator options. Added mechanisms to cleanly change the
system name and add/remove system-specific options, versus the
old way using callbacks. Also added read accessors for all the
options, to ensure consistency in how parameters are handled.
Changed most core systems to access emu_options instead of
core_options. Also changed machine->options() to return emu_options.
[Aaron Giles]
 
Created cli_options class derived from emu_options which adds the
command-line specific options. Updated clifront code to leverage
the new class and the new core behaviors. cli_execute() now accepts
a cli_options object when called. [Aaron Giles]

Updated both SDL and Windows to have their own options classes,
derived from cli_options, which add the OSD-specific options on
top of everything else. Added accessors for all the options so
that queries are strongly typed and simplified. [Aaron Giles]

Out of whatsnew: I've surely screwed up some stuff, though I have
smoke tested a bunch of things. Let me know if you hit anything odd.
Also I know this change will impact the WINUI stuff, please let me 
know if there are issues. All the functionality necessary should 
still be present. If it's not obvious, please talk to me before 
adding stuff to the core_options class.
2011-03-03 17:05:24 +00:00

546 lines
14 KiB
C

//============================================================
//
// sound.c - SDL implementation of MAME sound routines
//
// Copyright (c) 1996-2010, Nicola Salmoria and the MAME Team.
// Visit http://mamedev.org for licensing and usage restrictions.
//
// SDLMAME by Olivier Galibert and R. Belmont
//
//============================================================
// standard sdl header
#include <SDL/SDL.h>
// MAME headers
#include "emu.h"
#include "emuopts.h"
#include "osdepend.h"
#include "osdsdl.h"
//============================================================
// DEBUGGING
//============================================================
#define LOG_SOUND 0
//============================================================
// PARAMETERS
//============================================================
// number of samples per SDL callback
#define SDL_XFER_SAMPLES (512)
static int sdl_xfer_samples = SDL_XFER_SAMPLES;
static int stream_in_initialized = 0;
static int stream_loop = 0;
// maximum audio latency
#define MAX_AUDIO_LATENCY 10
//============================================================
// LOCAL VARIABLES
//============================================================
static int attenuation = 0;
static int initialized_audio = 0;
static int buf_locked;
static INT8 *stream_buffer;
static volatile INT32 stream_playpos;
static UINT32 stream_buffer_size;
static UINT32 stream_buffer_in;
// buffer over/underflow counts
static int buffer_underflows;
static int buffer_overflows;
// debugging
static FILE *sound_log;
// sound enable
static int snd_enabled;
//============================================================
// PROTOTYPES
//============================================================
static int sdl_init(running_machine *machine);
static void sdl_kill(running_machine *machine);
static int sdl_create_buffers(void);
static void sdl_destroy_buffers(void);
static void sdl_cleanup_audio(running_machine &machine);
static void sdl_callback(void *userdata, Uint8 *stream, int len);
//============================================================
// osd_start_audio_stream
//============================================================
void sdlaudio_init(running_machine *machine)
{
if (LOG_SOUND)
sound_log = fopen(SDLMAME_SOUND_LOG, "w");
// skip if sound disabled
if (machine->sample_rate != 0)
{
// attempt to initialize SDL
if (sdl_init(machine))
return;
machine->add_notifier(MACHINE_NOTIFY_EXIT, sdl_cleanup_audio);
// set the startup volume
machine->osd().set_mastervolume(attenuation);
}
return;
}
//============================================================
// osd_stop_audio_stream
//============================================================
static void sdl_cleanup_audio(running_machine &machine)
{
// if nothing to do, don't do it
if (machine.sample_rate == 0)
return;
// kill the buffers and dsound
sdl_kill(&machine);
sdl_destroy_buffers();
// print out over/underflow stats
if (buffer_overflows || buffer_underflows)
mame_printf_verbose("Sound buffer: overflows=%d underflows=%d\n", buffer_overflows, buffer_underflows);
if (LOG_SOUND)
{
fprintf(sound_log, "Sound buffer: overflows=%d underflows=%d\n", buffer_overflows, buffer_underflows);
fclose(sound_log);
}
}
//============================================================
// lock_buffer
//============================================================
static int lock_buffer(running_machine &machine, long offset, long size, void **buffer1, long *length1, void **buffer2, long *length2)
{
volatile long pstart, pend, lstart, lend;
if (!buf_locked)
{
if (machine.video().throttled())
{
pstart = stream_playpos;
pend = (pstart + sdl_xfer_samples);
lstart = offset;
lend = lstart+size;
while (((pstart >= lstart) && (pstart <= lend)) ||
((pend >= lstart) && (pend <= lend)))
{
pstart = stream_playpos;
pend = pstart + sdl_xfer_samples;
}
}
SDL_LockAudio();
buf_locked++;
}
// init lengths
*length1 = *length2 = 0;
if ((offset + size) > stream_buffer_size)
{
// 2-piece case
*length1 = stream_buffer_size - offset;
*buffer1 = &stream_buffer[offset];
*length2 = size - *length1;
*buffer2 = stream_buffer;
}
else
{
// normal 1-piece case
*length1 = size;
*buffer1 = &stream_buffer[offset];
}
if (LOG_SOUND)
fprintf(sound_log, "locking %ld bytes at offset %ld (total %d, playpos %d): len1 %ld len2 %ld\n",
size, offset, stream_buffer_size, stream_playpos, *length1, *length2);
return 0;
}
//============================================================
// unlock_buffer
//============================================================
static void unlock_buffer(void)
{
buf_locked--;
if (!buf_locked)
SDL_UnlockAudio();
if (LOG_SOUND)
fprintf(sound_log, "unlocking\n");
}
//============================================================
// Apply attenuation
//============================================================
static void att_memcpy(void *dest, const INT16 *data, int bytes_to_copy)
{
int level= (int) (pow(10.0, (float) attenuation / 20.0) * 128.0);
INT16 *d = (INT16 *) dest;
int count = bytes_to_copy/2;
while (count>0)
{
*d++ = (*data++ * level) >> 7; /* / 128 */
count--;
}
}
//============================================================
// copy_sample_data
//============================================================
static void copy_sample_data(running_machine &machine, const INT16 *data, int bytes_to_copy)
{
void *buffer1, *buffer2 = (void *)NULL;
long length1, length2;
int cur_bytes;
// attempt to lock the stream buffer
if (lock_buffer(machine, stream_buffer_in, bytes_to_copy, &buffer1, &length1, &buffer2, &length2) < 0)
{
buffer_underflows++;
return;
}
// adjust the input pointer
stream_buffer_in += bytes_to_copy;
if (stream_buffer_in >= stream_buffer_size)
{
stream_buffer_in -= stream_buffer_size;
stream_loop = 1;
if (LOG_SOUND)
fprintf(sound_log, "stream_loop set to 1 (stream_buffer_in looped)\n");
}
// copy the first chunk
cur_bytes = (bytes_to_copy > length1) ? length1 : bytes_to_copy;
att_memcpy(buffer1, data, cur_bytes);
// adjust for the number of bytes
bytes_to_copy -= cur_bytes;
data = (INT16 *)((UINT8 *)data + cur_bytes);
// copy the second chunk
if (bytes_to_copy != 0)
{
cur_bytes = (bytes_to_copy > length2) ? length2 : bytes_to_copy;
att_memcpy(buffer2, data, cur_bytes);
}
// unlock
unlock_buffer();
}
//============================================================
// update_audio_stream
//============================================================
void sdl_osd_interface::update_audio_stream(const INT16 *buffer, int samples_this_frame)
{
// if nothing to do, don't do it
if (machine().sample_rate != 0 && stream_buffer)
{
int bytes_this_frame = samples_this_frame * sizeof(INT16) * 2;
int play_position, write_position, stream_in;
int orig_write; // used in LOG
play_position = stream_playpos;
write_position = stream_playpos + ((machine().sample_rate / 50) * sizeof(INT16) * 2);
orig_write = write_position;
if (!stream_in_initialized)
{
stream_in = stream_buffer_in = (write_position + stream_buffer_size) / 2;
if (LOG_SOUND)
{
fprintf(sound_log, "stream_in = %d\n", (int)stream_in);
fprintf(sound_log, "stream_playpos = %d\n", (int)stream_playpos);
fprintf(sound_log, "write_position = %d\n", (int)write_position);
}
// start playing
SDL_PauseAudio(0);
stream_in_initialized = 1;
}
else
{
// normalize the stream in position
stream_in = stream_buffer_in;
if (stream_in < write_position && stream_loop == 1)
stream_in += stream_buffer_size;
// now we should have, in order:
// <------pp---wp---si--------------->
// if we're between play and write positions, then bump forward, but only in full chunks
while (stream_in < write_position)
{
if (LOG_SOUND)
fprintf(sound_log, "Underflow: PP=%d WP=%d(%d) SI=%d(%d) BTF=%d\n", (int)play_position, (int)write_position, (int)orig_write, (int)stream_in, (int)stream_buffer_in, (int)bytes_this_frame);
buffer_underflows++;
stream_in += samples_this_frame;
}
// if we're going to overlap the play position, just skip this chunk
if (stream_in + bytes_this_frame > play_position + stream_buffer_size)
{
if (LOG_SOUND)
fprintf(sound_log, "Overflow: PP=%d WP=%d(%d) SI=%d(%d) BTF=%d\n", (int)play_position, (int)write_position, (int)orig_write, (int)stream_in, (int)stream_buffer_in, (int)bytes_this_frame);
buffer_overflows++;
return;
}
}
if (stream_in >= stream_buffer_size)
{
stream_in -= stream_buffer_size;
stream_loop = 1;
if (LOG_SOUND)
fprintf(sound_log, "stream_loop set to 1 (stream_in looped)\n");
}
// now we know where to copy; let's do it
stream_buffer_in = stream_in;
copy_sample_data(machine(), buffer, bytes_this_frame);
}
}
//============================================================
// set_mastervolume
//============================================================
void sdl_osd_interface::set_mastervolume(int _attenuation)
{
// clamp the attenuation to 0-32 range
if (_attenuation > 0)
_attenuation = 0;
if (_attenuation < -32)
_attenuation = -32;
attenuation = _attenuation;
}
//============================================================
// sdl_callback
//============================================================
static void sdl_callback(void *userdata, Uint8 *stream, int len)
{
int len1, len2, sb_in;
sb_in = stream_buffer_in;
if (stream_loop)
sb_in += stream_buffer_size;
if (sb_in < (stream_playpos+len))
{
if (LOG_SOUND)
fprintf(sound_log, "Underflow at sdl_callback: SPP=%d SBI=%d(%d) Len=%d\n", (int)stream_playpos, (int)sb_in, (int)stream_buffer_in, (int)len);
return;
}
else if ((stream_playpos+len) > stream_buffer_size)
{
len1 = stream_buffer_size - stream_playpos;
len2 = len - len1;
}
else
{
len1 = len;
len2 = 0;
}
if (snd_enabled)
{
memcpy(stream, stream_buffer + stream_playpos, len1);
memset(stream_buffer + stream_playpos, 0, len1); // no longer needed
if (len2)
{
memcpy(stream+len1, stream_buffer, len2);
memset(stream_buffer, 0, len2); // no longer needed
}
}
else
{
memset(stream, 0, len);
}
// move the play cursor
stream_playpos += len1 + len2;
if (stream_playpos >= stream_buffer_size)
{
stream_playpos -= stream_buffer_size;
stream_loop = 0;
if (LOG_SOUND)
fprintf(sound_log, "stream_loop set to 0 (stream_playpos looped)\n");
}
if (LOG_SOUND)
fprintf(sound_log, "callback: xfer len1 %d len2 %d, playpos %d\n",
len1, len2, stream_playpos);
}
//============================================================
// sdl_init
//============================================================
static int sdl_init(running_machine *machine)
{
int n_channels = 2;
int audio_latency;
SDL_AudioSpec aspec, obtained;
char audio_driver[16] = "";
if (initialized_audio)
{
sdl_cleanup_audio(*machine);
}
mame_printf_verbose("Audio: Start initialization\n");
#if (SDL_VERSION_ATLEAST(1,3,0))
strncpy(audio_driver, SDL_GetCurrentAudioDriver(), sizeof(audio_driver));
#else
SDL_AudioDriverName(audio_driver, sizeof(audio_driver));
#endif
mame_printf_verbose("Audio: Driver is %s\n", audio_driver);
initialized_audio = 0;
sdl_xfer_samples = SDL_XFER_SAMPLES;
stream_in_initialized = 0;
stream_loop = 0;
// set up the audio specs
aspec.freq = machine->sample_rate;
aspec.format = AUDIO_S16SYS; // keep endian independent
aspec.channels = n_channels;
aspec.samples = sdl_xfer_samples;
aspec.callback = sdl_callback;
aspec.userdata = 0;
if (SDL_OpenAudio(&aspec, &obtained) < 0)
goto cant_start_audio;
initialized_audio = 1;
snd_enabled = 1;
mame_printf_verbose("Audio: frequency: %d, channels: %d, samples: %d\n",
obtained.freq, obtained.channels, obtained.samples);
sdl_xfer_samples = obtained.samples;
audio_latency = downcast<sdl_options &>(machine->options()).audio_latency();
// pin audio latency
if (audio_latency > MAX_AUDIO_LATENCY)
{
audio_latency = MAX_AUDIO_LATENCY;
}
else if (audio_latency < 1)
{
audio_latency = 1;
}
// compute the buffer sizes
stream_buffer_size = machine->sample_rate * 2 * sizeof(INT16) * audio_latency / MAX_AUDIO_LATENCY;
stream_buffer_size = (stream_buffer_size / 1024) * 1024;
if (stream_buffer_size < 1024)
stream_buffer_size = 1024;
// create the buffers
if (sdl_create_buffers())
goto cant_create_buffers;
mame_printf_verbose("Audio: End initialization\n");
return 0;
// error handling
cant_create_buffers:
cant_start_audio:
mame_printf_verbose("Audio: Initialization failed. SDL error: %s\n", SDL_GetError());
return 0;
}
//============================================================
// sdl_kill
//============================================================
static void sdl_kill(running_machine *machine)
{
if (initialized_audio)
{
mame_printf_verbose("sdl_kill: closing audio\n");
SDL_CloseAudio();
}
}
//============================================================
// dsound_create_buffers
//============================================================
static int sdl_create_buffers(void)
{
mame_printf_verbose("sdl_create_buffers: creating stream buffer of %u bytes\n", stream_buffer_size);
stream_buffer = global_alloc_array_clear(INT8, stream_buffer_size);
stream_playpos = 0;
buf_locked = 0;
return 0;
}
//============================================================
// sdl_destroy_buffers
//============================================================
static void sdl_destroy_buffers(void)
{
// release the buffer
if (stream_buffer)
global_free(stream_buffer);
stream_buffer = NULL;
}