From 72ce4242077f23b7de876b977397856bd072a6b1 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Sun, 11 May 2014 00:14:04 -0400 Subject: 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. --- opl/examples/droplay.c | 4 ++-- opl/opl.c | 10 +++++----- opl/opl.h | 16 ++++++++++++---- opl/opl_internal.h | 2 +- opl/opl_queue.c | 12 ++++++------ opl/opl_queue.h | 6 +++--- opl/opl_sdl.c | 31 +++++++++++++++++-------------- opl/opl_timer.c | 30 +++++++++++++++--------------- opl/opl_timer.h | 4 +--- src/i_oplmusic.c | 15 +++++++-------- 10 files changed, 69 insertions(+), 61 deletions(-) diff --git a/opl/examples/droplay.c b/opl/examples/droplay.c index 99456050..7b20e03d 100644 --- a/opl/examples/droplay.c +++ b/opl/examples/droplay.c @@ -139,7 +139,7 @@ void TimerCallback(void *data) // 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 @@ void PlayFile(char *filename) running = timer_data.running; OPL_Unlock(); - SDL_Delay(100); + SDL_Delay(100 * OPL_MS); } while (running); fclose(timer_data.fstream); diff --git a/opl/opl.c b/opl/opl.c index cc5b861d..982fb814 100644 --- a/opl/opl.c +++ b/opl/opl.c @@ -295,7 +295,7 @@ int OPL_Detect(void) OPL_ReadStatus(); } - OPL_Delay(1); + OPL_Delay(1 * OPL_MS); // Read status result2 = OPL_ReadStatus(); @@ -357,11 +357,11 @@ void OPL_InitRegisters(void) // 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 @@ static void DelayCallback(void *_delay_data) 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 @@ void OPL_Delay(unsigned int ms) 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. diff --git a/opl/opl.h b/opl/opl.h index d6a295b0..0af8bc17 100644 --- a/opl/opl.h +++ b/opl/opl.h @@ -19,6 +19,8 @@ #ifndef OPL_OPL_H #define OPL_OPL_H +#include + typedef void (*opl_callback_t)(void *data); typedef enum @@ -50,6 +52,12 @@ typedef enum #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 @@ void OPL_InitRegisters(void); // 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_Lock(void); 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. diff --git a/opl/opl_internal.h b/opl/opl_internal.h index 24e79644..2d027ad8 100644 --- a/opl/opl_internal.h +++ b/opl/opl_internal.h @@ -25,7 +25,7 @@ typedef int (*opl_init_func)(unsigned int port_base); 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); diff --git a/opl/opl_queue.c b/opl/opl_queue.c index ee87a19b..4b4e4e38 100644 --- a/opl/opl_queue.c +++ b/opl/opl_queue.c @@ -28,7 +28,7 @@ typedef struct { 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_Clear(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 entry_id; int parent_id; @@ -189,7 +189,7 @@ int OPL_Queue_Pop(opl_callback_queue_t *queue, 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 @@ unsigned int 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) { - 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); } } diff --git a/opl/opl_queue.h b/opl/opl_queue.h index 20ddeda9..8b4f0dbe 100644 --- a/opl/opl_queue.h +++ b/opl/opl_queue.h @@ -28,12 +28,12 @@ void OPL_Queue_Clear(opl_callback_queue_t *queue); 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 */ diff --git a/opl/opl_sdl.c b/opl/opl_sdl.c index 3ed7f784..fa9f047a 100644 --- a/opl/opl_sdl.c +++ b/opl/opl_sdl.c @@ -39,7 +39,7 @@ typedef struct 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 opl_callback_queue_t *callback_queue; 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 @@ static void AdvanceTime(unsigned int nsamples) { 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 @@ static void OPL_Mix_Callback(void *udata, 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 @@ static void OPL_Mix_Callback(void *udata, { 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 @@ static void OPLTimer_CalculateEndTime(opl_timer_t *timer) { 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_PortWrite(opl_port_t port, unsigned int value) } } -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); } diff --git a/opl/opl_timer.c b/opl/opl_timer.c index bab15687..312fe7fa 100644 --- a/opl/opl_timer.c +++ b/opl/opl_timer.c @@ -31,16 +31,16 @@ typedef enum 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 @@ static SDL_mutex *timer_mutex; // 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 @@ static int CallbackWaiting(unsigned int *next_time) 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 @@ static int CallbackWaiting(unsigned int *next_time) if (OPL_Queue_IsEmpty(callback_queue)) { - *next_time = current_time + 50; + *next_time = current_time + 50 * OPL_MS; return 0; } @@ -89,11 +89,11 @@ static int CallbackWaiting(unsigned int *next_time) 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 unsigned int GetNextTime(void) 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 @@ static int ThreadFunction(void *unused) // 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 @@ void OPL_Timer_StopThread(void) 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); } diff --git a/opl/opl_timer.h b/opl/opl_timer.h index 73544013..adf81503 100644 --- 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); diff --git a/src/i_oplmusic.c b/src/i_oplmusic.c index 489b6d55..5f9ab462 100644 --- 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 @@ static void TrackTimerCallback(void *arg) if (running_tracks <= 0 && song_looping) { - OPL_SetCallback(5, RestartSong, NULL); + OPL_SetCallback(5000, RestartSong, NULL); } return; @@ -1174,19 +1174,18 @@ static void TrackTimerCallback(void *arg) 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. -- cgit v1.2.3