shithub: choc

Download patch

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 =