//============================================================
//
// 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 "sdlinc.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 5
//============================================================
// 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 SDLCALL 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, machine_notify_delegate(FUNC(sdl_cleanup_audio), &machine));
// 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 += bytes_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 (SDLMAME_SDL2)
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;
}