shithub: choc

Download patch

ref: 72ce4242077f23b7de876b977397856bd072a6b1
parent: 495694da29ff736fba2fdc696553ee7197247174
author: Simon Howard <fraggle@gmail.com>
date: Sat May 10 20:14:04 EDT 2014

opl: Change library to use us instead of ms.

Multi-track MIDI files are played back using separate callback chains
for each track, and this introduces the possibility of one track
becoming out of sync with the others. This was noticeable in WADs
that use multi-track MIDIs, such as Alien Vendetta. Increase the
timing resolution to microsecond precision to fix this.

--- a/opl/examples/droplay.c
+++ b/opl/examples/droplay.c
@@ -139,7 +139,7 @@
 
     // Schedule the next timer callback.
 
-    OPL_SetCallback(delay, TimerCallback, timer_data);
+    OPL_SetCallback(delay * OPL_MS, TimerCallback, timer_data);
 }
 
 void PlayFile(char *filename)
@@ -183,7 +183,7 @@
         running = timer_data.running;
         OPL_Unlock();
 
-        SDL_Delay(100);
+        SDL_Delay(100 * OPL_MS);
     } while (running);
 
     fclose(timer_data.fstream);
--- a/opl/opl.c
+++ b/opl/opl.c
@@ -295,7 +295,7 @@
         OPL_ReadStatus();
     }
 
-    OPL_Delay(1);
+    OPL_Delay(1 * OPL_MS);
 
     // Read status
     result2 = OPL_ReadStatus();
@@ -357,11 +357,11 @@
 // Timer functions.
 //
 
-void OPL_SetCallback(unsigned int ms, opl_callback_t callback, void *data)
+void OPL_SetCallback(unsigned int us, opl_callback_t callback, void *data)
 {
     if (driver != NULL)
     {
-        driver->set_callback_func(ms, callback, data);
+        driver->set_callback_func(us, callback, data);
     }
 }
 
@@ -409,7 +409,7 @@
     SDL_UnlockMutex(delay_data->mutex);
 }
 
-void OPL_Delay(unsigned int ms)
+void OPL_Delay(unsigned int us)
 {
     delay_data_t delay_data;
 
@@ -425,7 +425,7 @@
     delay_data.mutex = SDL_CreateMutex();
     delay_data.cond = SDL_CreateCond();
 
-    OPL_SetCallback(ms, DelayCallback, &delay_data);
+    OPL_SetCallback(us, DelayCallback, &delay_data);
 
     // Wait until the callback is invoked.
 
--- a/opl/opl.h
+++ b/opl/opl.h
@@ -19,6 +19,8 @@
 #ifndef OPL_OPL_H
 #define OPL_OPL_H
 
+#include <inttypes.h>
+
 typedef void (*opl_callback_t)(void *data);
 
 typedef enum
@@ -50,6 +52,12 @@
 #define OPL_REGS_FREQ_2           0xB0
 #define OPL_REGS_FEEDBACK         0xC0
 
+// Times
+
+#define OPL_SECOND ((uint64_t) 1000 * 1000)
+#define OPL_MS     ((uint64_t) 1000)
+#define OPL_US     ((uint64_t) 1)
+
 //
 // Low-level functions.
 //
@@ -99,10 +107,10 @@
 // Timer callback functions.
 //
 
-// Set a timer callback.  After the specified number of milliseconds
+// Set a timer callback.  After the specified number of microseconds
 // have elapsed, the callback will be invoked.
 
-void OPL_SetCallback(unsigned int ms, opl_callback_t callback, void *data);
+void OPL_SetCallback(unsigned int us, opl_callback_t callback, void *data);
 
 // Adjust callback times by the specified factor. For example, a value of
 // 0.5 will halve all remaining times.
@@ -122,9 +130,9 @@
 
 void OPL_Unlock(void);
 
-// Block until the specified number of milliseconds have elapsed.
+// Block until the specified number of microseconds have elapsed.
 
-void OPL_Delay(unsigned int ms);
+void OPL_Delay(unsigned int us);
 
 // Pause the OPL callbacks.
 
--- a/opl/opl_internal.h
+++ b/opl/opl_internal.h
@@ -25,7 +25,7 @@
 typedef void (*opl_shutdown_func)(void);
 typedef unsigned int (*opl_read_port_func)(opl_port_t port);
 typedef void (*opl_write_port_func)(opl_port_t port, unsigned int value);
-typedef void (*opl_set_callback_func)(unsigned int ms,
+typedef void (*opl_set_callback_func)(unsigned int us,
                                       opl_callback_t callback,
                                       void *data);
 typedef void (*opl_clear_callbacks_func)(void);
--- a/opl/opl_queue.c
+++ b/opl/opl_queue.c
@@ -28,7 +28,7 @@
 {
     opl_callback_t callback;
     void *data;
-    unsigned int time;
+    uint64_t time;
 } opl_queue_entry_t;
 
 struct opl_callback_queue_s
@@ -64,7 +64,7 @@
 
 void OPL_Queue_Push(opl_callback_queue_t *queue,
                     opl_callback_t callback, void *data,
-                    unsigned int time)
+                    uint64_t time)
 {
     int entry_id;
     int parent_id;
@@ -189,7 +189,7 @@
     return 1;
 }
 
-unsigned int OPL_Queue_Peek(opl_callback_queue_t *queue)
+uint64_t OPL_Queue_Peek(opl_callback_queue_t *queue)
 {
     if (queue->num_entries > 0)
     {
@@ -202,15 +202,15 @@
 }
 
 void OPL_Queue_AdjustCallbacks(opl_callback_queue_t *queue,
-                               unsigned int time, float factor)
+                               uint64_t time, float factor)
 {
-    int offset;
+    int64_t offset;
     int i;
 
     for (i = 0; i < queue->num_entries; ++i)
     {
         offset = queue->entries[i].time - time;
-        queue->entries[i].time = time + (int) (offset * factor);
+        queue->entries[i].time = time + (uint64_t) (offset * factor);
     }
 }
 
--- a/opl/opl_queue.h
+++ b/opl/opl_queue.h
@@ -28,12 +28,12 @@
 void OPL_Queue_Destroy(opl_callback_queue_t *queue);
 void OPL_Queue_Push(opl_callback_queue_t *queue,
                     opl_callback_t callback, void *data,
-                    unsigned int time);
+                    uint64_t time);
 int OPL_Queue_Pop(opl_callback_queue_t *queue,
                   opl_callback_t *callback, void **data);
-unsigned int OPL_Queue_Peek(opl_callback_queue_t *queue);
+uint64_t OPL_Queue_Peek(opl_callback_queue_t *queue);
 void OPL_Queue_AdjustCallbacks(opl_callback_queue_t *queue,
-                               unsigned int time, float factor);
+                               uint64_t time, float factor);
 
 #endif /* #ifndef OPL_QUEUE_H */
 
--- a/opl/opl_sdl.c
+++ b/opl/opl_sdl.c
@@ -39,7 +39,7 @@
     unsigned int rate;        // Number of times the timer is advanced per sec.
     unsigned int enabled;     // Non-zero if timer is enabled.
     unsigned int value;       // Last value that was set.
-    unsigned int expire_time; // Calculated time that timer will expire.
+    uint64_t expire_time;     // Calculated time that timer will expire.
 } opl_timer_t;
 
 // When the callback mutex is locked using OPL_Lock, callback functions
@@ -55,18 +55,18 @@
 
 static SDL_mutex *callback_queue_mutex = NULL;
 
-// Current time, in number of samples since startup:
+// Current time, in us since startup:
 
-static int current_time;
+static uint64_t current_time;
 
 // If non-zero, playback is currently paused.
 
 static int opl_sdl_paused;
 
-// Time offset (in samples) due to the fact that callbacks
+// Time offset (in us) due to the fact that callbacks
 // were previously paused.
 
-static unsigned int pause_offset;
+static uint64_t pause_offset;
 
 // OPL software emulator structure.
 
@@ -106,20 +106,22 @@
 {
     opl_callback_t callback;
     void *callback_data;
+    uint64_t us;
 
     SDL_LockMutex(callback_queue_mutex);
 
     // Advance time.
 
-    current_time += nsamples;
+    us = ((uint64_t) nsamples * OPL_SECOND) / mixing_freq;
+    current_time += us;
 
     if (opl_sdl_paused)
     {
-        pause_offset += nsamples;
+        pause_offset += us;
     }
 
     // Are there callbacks to invoke now?  Keep invoking them
-    // until there are none more left.
+    // until there are no more left.
 
     while (!OPL_Queue_IsEmpty(callback_queue)
         && current_time >= OPL_Queue_Peek(callback_queue) + pause_offset)
@@ -193,8 +195,8 @@
 
     while (filled < buffer_len)
     {
-        unsigned int next_callback_time;
-        unsigned int nsamples;
+        uint64_t next_callback_time;
+        uint64_t nsamples;
 
         SDL_LockMutex(callback_queue_mutex);
 
@@ -210,7 +212,8 @@
         {
             next_callback_time = OPL_Queue_Peek(callback_queue) + pause_offset;
 
-            nsamples = next_callback_time - current_time;
+            nsamples = (next_callback_time - current_time) * mixing_freq;
+            nsamples = (nsamples + OPL_SECOND - 1) / OPL_SECOND;
 
             if (nsamples > buffer_len - filled)
             {
@@ -395,7 +398,7 @@
     {
         tics = 0x100 - timer->value;
         timer->expire_time = current_time
-                           + (tics * opl_sample_rate) / timer->rate;
+                           + ((uint64_t) tics * OPL_SECOND) / timer->rate;
     }
 }
 
@@ -454,13 +457,13 @@
     }
 }
 
-static void OPL_SDL_SetCallback(unsigned int ms,
+static void OPL_SDL_SetCallback(unsigned int us,
                                 opl_callback_t callback,
                                 void *data)
 {
     SDL_LockMutex(callback_queue_mutex);
     OPL_Queue_Push(callback_queue, callback, data,
-                   current_time - pause_offset + (ms * mixing_freq) / 1000);
+                   current_time - pause_offset + us);
     SDL_UnlockMutex(callback_queue_mutex);
 }
 
--- a/opl/opl_timer.c
+++ b/opl/opl_timer.c
@@ -31,16 +31,16 @@
 
 static SDL_Thread *timer_thread = NULL;
 static thread_state_t timer_thread_state;
-static int current_time;
+static uint64_t current_time;
 
 // If non-zero, callbacks are currently paused.
 
 static int opl_timer_paused;
 
-// Offset in milliseconds to adjust time due to the fact that playback
+// Offset in microseconds to adjust time due to the fact that playback
 // was paused.
 
-static unsigned int pause_offset = 0;
+static uint64_t pause_offset = 0;
 
 // Queue of callbacks waiting to be invoked.
 // The callback queue mutex is held while the callback queue structure
@@ -58,7 +58,7 @@
 // to be invoked.  Otherwise, next_time is set to the time when the
 // timer thread must wake up again to check.
 
-static int CallbackWaiting(unsigned int *next_time)
+static int CallbackWaiting(uint64_t *next_time)
 {
     // If paused, just wait in 50ms increments until unpaused.
     // Update pause_offset so after we unpause, the callback 
@@ -66,8 +66,8 @@
 
     if (opl_timer_paused)
     {
-        *next_time = current_time + 50;
-        pause_offset += 50;
+        *next_time = current_time + 50 * OPL_MS;
+        pause_offset += 50 * OPL_MS;
         return 0;
     }
 
@@ -76,7 +76,7 @@
 
     if (OPL_Queue_IsEmpty(callback_queue))
     {
-        *next_time = current_time + 50;
+        *next_time = current_time + 50 * OPL_MS;
         return 0;
     }
 
@@ -89,11 +89,11 @@
     return *next_time <= current_time;
 }
 
-static unsigned int GetNextTime(void)
+static uint64_t GetNextTime(void)
 {
     opl_callback_t callback;
     void *callback_data;
-    unsigned int next_time;
+    uint64_t next_time;
     int have_callback;
 
     // Keep running through callbacks until there are none ready to
@@ -131,8 +131,8 @@
 
 static int ThreadFunction(void *unused)
 {
-    unsigned int next_time;
-    unsigned int now;
+    uint64_t next_time;
+    uint64_t now;
 
     // Keep running until OPL_Timer_StopThread is called.
 
@@ -142,11 +142,11 @@
         // wait until that time.
 
         next_time = GetNextTime();
-        now = SDL_GetTicks();
+        now = SDL_GetTicks() * OPL_MS;
 
         if (next_time > now)
         {
-            SDL_Delay(next_time - now);
+            SDL_Delay((next_time - now) / OPL_MS);
         }
 
         // Update the current time.
@@ -209,11 +209,11 @@
     FreeResources();
 }
 
-void OPL_Timer_SetCallback(unsigned int ms, opl_callback_t callback, void *data)
+void OPL_Timer_SetCallback(uint64_t us, opl_callback_t callback, void *data)
 {
     SDL_LockMutex(callback_queue_mutex);
     OPL_Queue_Push(callback_queue, callback, data,
-                   current_time + ms - pause_offset);
+                   current_time + us - pause_offset);
     SDL_UnlockMutex(callback_queue_mutex);
 }
 
--- a/opl/opl_timer.h
+++ b/opl/opl_timer.h
@@ -22,9 +22,7 @@
 
 int OPL_Timer_StartThread(void);
 void OPL_Timer_StopThread(void);
-void OPL_Timer_SetCallback(unsigned int ms,
-                           opl_callback_t callback,
-                           void *data);
+void OPL_Timer_SetCallback(uint64_t us, opl_callback_t callback, void *data);
 void OPL_Timer_ClearCallbacks(void);
 void OPL_Timer_Lock(void);
 void OPL_Timer_Unlock(void);
--- a/src/i_oplmusic.c
+++ b/src/i_oplmusic.c
@@ -47,7 +47,7 @@
 #define PERCUSSION_LOG_LEN 16
 
 // TODO: Figure out why this is needed.
-#define TEMPO_FUDGE_FACTOR 0.26
+#define TEMPO_FUDGE_FACTOR 260
 
 typedef struct
 {
@@ -1160,7 +1160,7 @@
 
         if (running_tracks <= 0 && song_looping)
         {
-            OPL_SetCallback(5, RestartSong, NULL);
+            OPL_SetCallback(5000, RestartSong, NULL);
         }
 
         return;
@@ -1174,19 +1174,18 @@
 static void ScheduleTrack(opl_track_data_t *track)
 {
     unsigned int nticks;
-    uint64_t ms;
+    uint64_t us;
 
-    // Get the number of milliseconds until the next event.
+    // Get the number of microseconds until the next event.
 
     nticks = MIDI_GetDeltaTime(track->iter);
-    ms = nticks;
-    ms *= us_per_beat * TEMPO_FUDGE_FACTOR;
-    ms /= ticks_per_beat;
+    us = ((uint64_t) nticks * us_per_beat * TEMPO_FUDGE_FACTOR)
+       / ticks_per_beat;
 
     // Set a timer to be invoked when the next event is
     // ready to play.
 
-    OPL_SetCallback((unsigned int) ms, TrackTimerCallback, track);
+    OPL_SetCallback(us, TrackTimerCallback, track);
 }
 
 // Initialize a channel.