// Emacs style mode select -*- C++ -*- //----------------------------------------------------------------------------- // // Copyright(C) 2009 Simon Howard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA // 02111-1307, USA. // // DESCRIPTION: // OPL SDL interface. // //----------------------------------------------------------------------------- #include "config.h" #include #include #include #include #include "SDL.h" #include "SDL_mixer.h" #include "fmopl.h" #include "opl.h" #include "opl_internal.h" #include "opl_queue.h" // TODO: #define opl_sample_rate 22050 // When the callback mutex is locked using OPL_Lock, callback functions // are not invoked. static SDL_mutex *callback_mutex = NULL; // Queue of callbacks waiting to be invoked. static opl_callback_queue_t *callback_queue; // Mutex used to control access to the callback queue. static SDL_mutex *callback_queue_mutex = NULL; // Current time, in number of samples since startup: static int current_time; // OPL software emulator structure. static FM_OPL *opl_emulator = NULL; // Temporary mixing buffer used by the mixing callback. static int16_t *mix_buffer = NULL; // SDL parameters. static int sdl_was_initialised = 0; static int mixing_freq, mixing_channels; static Uint16 mixing_format; static int SDLIsInitialised(void) { int freq, channels; Uint16 format; return Mix_QuerySpec(&freq, &format, &channels); } // Advance time by the specified number of samples, invoking any // callback functions as appropriate. static void AdvanceTime(unsigned int nsamples) { opl_callback_t callback; void *callback_data; SDL_LockMutex(callback_queue_mutex); // Advance time. current_time += nsamples; // Are there callbacks to invoke now? Keep invoking them // until there are none more left. while (!OPL_Queue_IsEmpty(callback_queue) && current_time >= OPL_Queue_Peek(callback_queue)) { // Pop the callback from the queue to invoke it. if (!OPL_Queue_Pop(callback_queue, &callback, &callback_data)) { break; } // The mutex stuff here is a bit complicated. We must // hold callback_mutex when we invoke the callback (so that // the control thread can use OPL_Lock() to prevent callbacks // from being invoked), but we must not be holding // callback_queue_mutex, as the callback must be able to // call OPL_SetCallback to schedule new callbacks. SDL_UnlockMutex(callback_queue_mutex); SDL_LockMutex(callback_mutex); callback(callback_data); SDL_UnlockMutex(callback_mutex); SDL_LockMutex(callback_queue_mutex); } SDL_UnlockMutex(callback_queue_mutex); } // Call the OPL emulator code to fill the specified buffer. static void FillBuffer(int16_t *buffer, unsigned int nsamples) { unsigned int i; // This seems like a reasonable assumption. mix_buffer is // 1 second long, which should always be much longer than the // SDL mix buffer. assert(nsamples < mixing_freq); YM3812UpdateOne(opl_emulator, mix_buffer, nsamples, 0); // Mix into the destination buffer, doubling up into stereo. for (i=0; i buffer_len - filled) { nsamples = buffer_len - filled; } } SDL_UnlockMutex(callback_queue_mutex); // Add emulator output to buffer. FillBuffer(buffer + filled * 2, nsamples); filled += nsamples; // Invoke callbacks for this point in time. AdvanceTime(nsamples); } } static void OPL_SDL_Shutdown(void) { Mix_SetPostMix(NULL, NULL); if (sdl_was_initialised) { Mix_CloseAudio(); SDL_QuitSubSystem(SDL_INIT_AUDIO); OPL_Queue_Destroy(callback_queue); free(mix_buffer); sdl_was_initialised = 0; } if (opl_emulator != NULL) { OPLDestroy(opl_emulator); opl_emulator = NULL; } if (callback_mutex != NULL) { SDL_DestroyMutex(callback_mutex); callback_mutex = NULL; } if (callback_queue_mutex != NULL) { SDL_DestroyMutex(callback_queue_mutex); callback_queue_mutex = NULL; } } // Callback when a timer expires. static void TimerOver(void *data) { int channel = (int) data; OPLTimerOver(opl_emulator, channel); } // Callback invoked when the emulator code wants to set a timer. static void TimerHandler(int channel, double interval_seconds) { unsigned int interval_samples; interval_samples = (int) (interval_seconds * mixing_freq); SDL_LockMutex(callback_queue_mutex); OPL_Queue_Push(callback_queue, TimerOver, (void *) channel, current_time + interval_samples); SDL_UnlockMutex(callback_queue_mutex); } static int OPL_SDL_Init(unsigned int port_base) { // Check if SDL_mixer has been opened already // If not, we must initialise it now if (!SDLIsInitialised()) { if (SDL_Init(SDL_INIT_AUDIO) < 0) { fprintf(stderr, "Unable to set up sound.\n"); return 0; } if (Mix_OpenAudio(opl_sample_rate, AUDIO_S16SYS, 2, 1024) < 0) { fprintf(stderr, "Error initialising SDL_mixer: %s\n", Mix_GetError()); SDL_QuitSubSystem(SDL_INIT_AUDIO); return 0; } SDL_PauseAudio(0); // When this module shuts down, it has the responsibility to // shut down SDL. sdl_was_initialised = 1; } else { sdl_was_initialised = 0; } // Queue structure of callbacks to invoke. callback_queue = OPL_Queue_Create(); current_time = 0; // Get the mixer frequency, format and number of channels. Mix_QuerySpec(&mixing_freq, &mixing_format, &mixing_channels); // Only supports AUDIO_S16SYS if (mixing_format != AUDIO_S16SYS || mixing_channels != 2) { fprintf(stderr, "OPL_SDL only supports native signed 16-bit LSB, " "stereo format!\n"); OPL_SDL_Shutdown(); return 0; } // Mix buffer: mix_buffer = malloc(mixing_freq * 2); // Create the emulator structure: opl_emulator = makeAdlibOPL(mixing_freq); if (opl_emulator == NULL) { fprintf(stderr, "Failed to initialise software OPL emulator!\n"); OPL_SDL_Shutdown(); return 0; } OPLSetTimerHandler(opl_emulator, TimerHandler, 0); callback_mutex = SDL_CreateMutex(); callback_queue_mutex = SDL_CreateMutex(); // TODO: This should be music callback? or-? Mix_SetPostMix(OPL_Mix_Callback, NULL); return 1; } static unsigned int OPL_SDL_PortRead(opl_port_t port) { if (opl_emulator != NULL) { return OPLRead(opl_emulator, port); } else { return 0; } } static void OPL_SDL_PortWrite(opl_port_t port, unsigned int value) { if (opl_emulator != NULL) { OPLWrite(opl_emulator, port, value); } } static void OPL_SDL_SetCallback(unsigned int ms, opl_callback_t callback, void *data) { SDL_LockMutex(callback_queue_mutex); OPL_Queue_Push(callback_queue, callback, data, current_time + (ms * mixing_freq) / 1000); SDL_UnlockMutex(callback_queue_mutex); } static void OPL_SDL_Lock(void) { SDL_LockMutex(callback_mutex); } static void OPL_SDL_Unlock(void) { SDL_UnlockMutex(callback_mutex); } opl_driver_t opl_sdl_driver = { "SDL", OPL_SDL_Init, OPL_SDL_Shutdown, OPL_SDL_PortRead, OPL_SDL_PortWrite, OPL_SDL_SetCallback, OPL_SDL_Lock, OPL_SDL_Unlock };