//============================================================ // // 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(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; }