ref: e33a4961331301b1e3a5c65d148050fa33c4c594
parent: 480a31094b7621dd7d65ec05a6e36964dca99b66
author: Simon Howard <fraggle@gmail.com>
date: Fri Aug 28 14:04:04 EDT 2009
Working SDL OPL driver. Subversion-branch: /branches/opl-branch Subversion-revision: 1632
--- a/opl/examples/Makefile.am
+++ b/opl/examples/Makefile.am
@@ -3,6 +3,6 @@
noinst_PROGRAMS=droplay
-droplay_LDADD = ../libopl.a @LDFLAGS@ @SDL_LIBS@
+droplay_LDADD = ../libopl.a @LDFLAGS@ @SDL_LIBS@ @SDLMIXER_LIBS@
droplay_SOURCES = droplay.c
--- a/opl/examples/droplay.c
+++ b/opl/examples/droplay.c
@@ -77,16 +77,20 @@
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;
}
--- a/opl/opl.c
+++ b/opl/opl.c
@@ -28,6 +28,8 @@
#include <stdio.h>
#include <stdlib.h>
+#include "SDL.h"
+
#include "opl.h"
#include "opl_internal.h"
@@ -36,6 +38,7 @@
#ifdef HAVE_IOPERM
extern opl_driver_t opl_linux_driver;
#endif
+extern opl_driver_t opl_sdl_driver;
static opl_driver_t *drivers[] =
{
@@ -42,6 +45,7 @@
#ifdef HAVE_IOPERM
&opl_linux_driver,
#endif
+ &opl_sdl_driver,
NULL
};
@@ -127,5 +131,59 @@
{
driver->unlock_func();
}
+}
+
+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);
}
--- a/opl/opl.h
+++ b/opl/opl.h
@@ -88,5 +88,9 @@
void OPL_Unlock(void);
+// Block until the specified number of milliseconds have elapsed.
+
+void OPL_Delay(unsigned int ms);
+
#endif
--- 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 @@
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 @@
Mix_CloseAudio();
SDL_QuitSubSystem(SDL_INIT_AUDIO);
OPL_Queue_Destroy(callback_queue);
+ free(mix_buffer);
sdl_was_initialised = 0;
}
@@ -77,6 +225,18 @@
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 @@
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 @@
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 @@
return 0;
}
+ // Mix buffer:
+
+ mix_buffer = malloc(mixing_freq * 2);
+
// Create the emulator structure:
opl_emulator = makeAdlibOPL(mixing_freq);
@@ -141,6 +308,9 @@
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 @@
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 =