diff options
author | Simon Howard | 2009-08-28 18:04:04 +0000 |
---|---|---|
committer | Simon Howard | 2009-08-28 18:04:04 +0000 |
commit | e33a4961331301b1e3a5c65d148050fa33c4c594 (patch) | |
tree | bf28e4e1af04818592e64af72320438b69245ed1 | |
parent | 480a31094b7621dd7d65ec05a6e36964dca99b66 (diff) | |
download | chocolate-doom-e33a4961331301b1e3a5c65d148050fa33c4c594.tar.gz chocolate-doom-e33a4961331301b1e3a5c65d148050fa33c4c594.tar.bz2 chocolate-doom-e33a4961331301b1e3a5c65d148050fa33c4c594.zip |
Working SDL OPL driver.
Subversion-branch: /branches/opl-branch
Subversion-revision: 1632
-rw-r--r-- | opl/examples/Makefile.am | 2 | ||||
-rw-r--r-- | opl/examples/droplay.c | 10 | ||||
-rw-r--r-- | opl/opl.c | 58 | ||||
-rw-r--r-- | opl/opl.h | 4 | ||||
-rw-r--r-- | opl/opl_sdl.c | 182 |
5 files changed, 249 insertions, 7 deletions
diff --git a/opl/examples/Makefile.am b/opl/examples/Makefile.am index 3dc07d46..7c2c7c8a 100644 --- a/opl/examples/Makefile.am +++ b/opl/examples/Makefile.am @@ -3,6 +3,6 @@ AM_CFLAGS = -I.. noinst_PROGRAMS=droplay -droplay_LDADD = ../libopl.a @LDFLAGS@ @SDL_LIBS@ +droplay_LDADD = ../libopl.a @LDFLAGS@ @SDL_LIBS@ @SDLMIXER_LIBS@ droplay_SOURCES = droplay.c diff --git a/opl/examples/droplay.c b/opl/examples/droplay.c index 89cf6862..5158fbcd 100644 --- a/opl/examples/droplay.c +++ b/opl/examples/droplay.c @@ -77,16 +77,20 @@ void ClearAllRegs(void) int DetectOPL(void) { + int val1, val2; + WriteReg(OPL_REG_TIMER_CTRL, 0x60); WriteReg(OPL_REG_TIMER_CTRL, 0x80); - int val1 = OPL_ReadPort(OPL_REGISTER_PORT) & 0xe0; + val1 = OPL_ReadPort(OPL_REGISTER_PORT) & 0xe0; WriteReg(OPL_REG_TIMER1, 0xff); WriteReg(OPL_REG_TIMER_CTRL, 0x21); - SDL_Delay(50); - int val2 = OPL_ReadPort(OPL_REGISTER_PORT) & 0xe0; + OPL_Delay(50); + val2 = OPL_ReadPort(OPL_REGISTER_PORT) & 0xe0; WriteReg(OPL_REG_TIMER_CTRL, 0x60); WriteReg(OPL_REG_TIMER_CTRL, 0x80); +// Temporary hack for SDL driver. +return 1; return val1 == 0 && val2 == 0xc0; } @@ -28,6 +28,8 @@ #include <stdio.h> #include <stdlib.h> +#include "SDL.h" + #include "opl.h" #include "opl_internal.h" @@ -36,12 +38,14 @@ #ifdef HAVE_IOPERM extern opl_driver_t opl_linux_driver; #endif +extern opl_driver_t opl_sdl_driver; static opl_driver_t *drivers[] = { #ifdef HAVE_IOPERM &opl_linux_driver, #endif + &opl_sdl_driver, NULL }; @@ -129,3 +133,57 @@ void OPL_Unlock(void) } } +typedef struct +{ + int finished; + + SDL_mutex *mutex; + SDL_cond *cond; +} delay_data_t; + +static void DelayCallback(void *_delay_data) +{ + delay_data_t *delay_data = _delay_data; + + SDL_LockMutex(delay_data->mutex); + delay_data->finished = 1; + SDL_UnlockMutex(delay_data->mutex); + + SDL_CondSignal(delay_data->cond); +} + +void OPL_Delay(unsigned int ms) +{ + delay_data_t delay_data; + + if (driver == NULL) + { + return; + } + + // Create a callback that will signal this thread after the + // specified time. + + delay_data.finished = 0; + delay_data.mutex = SDL_CreateMutex(); + delay_data.cond = SDL_CreateCond(); + + OPL_SetCallback(ms, DelayCallback, &delay_data); + + // Wait until the callback is invoked. + + SDL_LockMutex(delay_data.mutex); + + while (!delay_data.finished) + { + SDL_CondWait(delay_data.cond, delay_data.mutex); + } + + SDL_UnlockMutex(delay_data.mutex); + + // Clean up. + + SDL_DestroyMutex(delay_data.mutex); + SDL_DestroyCond(delay_data.cond); +} + @@ -88,5 +88,9 @@ void OPL_Lock(void); void OPL_Unlock(void); +// Block until the specified number of milliseconds have elapsed. + +void OPL_Delay(unsigned int ms); + #endif diff --git a/opl/opl_sdl.c b/opl/opl_sdl.c index 2b8f5174..849a10b0 100644 --- a/opl/opl_sdl.c +++ b/opl/opl_sdl.c @@ -28,6 +28,7 @@ #include <stdio.h> #include <string.h> #include <errno.h> +#include <assert.h> #include "SDL.h" #include "SDL_mixer.h" @@ -42,8 +43,33 @@ // 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; @@ -56,10 +82,131 @@ static int SDLIsInitialised(void) 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<nsamples; ++i) + { + buffer[i * 2] += mix_buffer[i] / 2; + buffer[i * 2 + 1] += mix_buffer[i] / 2; + } +} + // Callback function to fill a new sound buffer: -static void OPL_Mix_Callback(void *udata, Uint8 *stream, int len) +static void OPL_Mix_Callback(void *udata, + Uint8 *byte_buffer, + int buffer_bytes) { + int16_t *buffer; + unsigned int buffer_len; + unsigned int filled = 0; + + // Buffer length in samples (quadrupled, because of 16-bit and stereo) + + buffer = (int16_t *) byte_buffer; + buffer_len = buffer_bytes / 4; + + // Repeatedly call the FMOPL update function until the buffer is + // full. + + while (filled < buffer_len) + { + unsigned int next_callback_time; + unsigned int nsamples; + + SDL_LockMutex(callback_queue_mutex); + + // Work out the time until the next callback waiting in + // the callback queue must be invoked. We can then fill the + // buffer with this many samples. + + if (OPL_Queue_IsEmpty(callback_queue)) + { + nsamples = buffer_len - filled; + } + else + { + next_callback_time = OPL_Queue_Peek(callback_queue); + + nsamples = next_callback_time - current_time; + + if (nsamples > 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) @@ -69,6 +216,7 @@ static void OPL_SDL_Shutdown(void) Mix_CloseAudio(); SDL_QuitSubSystem(SDL_INIT_AUDIO); OPL_Queue_Destroy(callback_queue); + free(mix_buffer); sdl_was_initialised = 0; } @@ -77,6 +225,18 @@ static void OPL_SDL_Shutdown(void) 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; + } } static int OPL_SDL_Init(unsigned int port_base) @@ -86,8 +246,6 @@ static int OPL_SDL_Init(unsigned int port_base) if (!SDLIsInitialised()) { - callback_queue = OPL_Queue_Create(); - if (SDL_Init(SDL_INIT_AUDIO) < 0) { fprintf(stderr, "Unable to set up sound.\n"); @@ -114,6 +272,11 @@ static int OPL_SDL_Init(unsigned int port_base) 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); @@ -130,6 +293,10 @@ static int OPL_SDL_Init(unsigned int port_base) return 0; } + // Mix buffer: + + mix_buffer = malloc(mixing_freq * 2); + // Create the emulator structure: opl_emulator = makeAdlibOPL(mixing_freq); @@ -141,6 +308,9 @@ static int OPL_SDL_Init(unsigned int port_base) return 0; } + callback_mutex = SDL_CreateMutex(); + callback_queue_mutex = SDL_CreateMutex(); + // TODO: This should be music callback? or-? Mix_SetPostMix(OPL_Mix_Callback, NULL); @@ -171,14 +341,20 @@ 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 = |