opl: Add API to adjust tempo.

When the tempo is changed, the times on all active timers must be
adjusted to match the new timing values. Add an API to do this and
invoke it when a tempo change meta event is read.
This commit is contained in:
Simon Howard 2014-05-10 14:00:41 -04:00
parent 541267071a
commit 495694da29
12 changed files with 65 additions and 10 deletions

View file

@ -452,3 +452,11 @@ void OPL_SetPaused(int paused)
}
}
void OPL_AdjustCallbacks(float value)
{
if (driver != NULL)
{
driver->adjust_callbacks_func(value);
}
}

View file

@ -104,6 +104,11 @@ void OPL_InitRegisters(void);
void OPL_SetCallback(unsigned int ms, 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.
void OPL_AdjustCallbacks(float factor);
// Clear all OPL callbacks that have been set.
void OPL_ClearCallbacks(void);

View file

@ -32,6 +32,7 @@ typedef void (*opl_clear_callbacks_func)(void);
typedef void (*opl_lock_func)(void);
typedef void (*opl_unlock_func)(void);
typedef void (*opl_set_paused_func)(int paused);
typedef void (*opl_adjust_callbacks_func)(float value);
typedef struct
{
@ -46,6 +47,7 @@ typedef struct
opl_lock_func lock_func;
opl_unlock_func unlock_func;
opl_set_paused_func set_paused_func;
opl_adjust_callbacks_func adjust_callbacks_func;
} opl_driver_t;
// Sample rate to use when doing software emulation.

View file

@ -95,7 +95,8 @@ opl_driver_t opl_linux_driver =
OPL_Timer_ClearCallbacks,
OPL_Timer_Lock,
OPL_Timer_Unlock,
OPL_Timer_SetPaused
OPL_Timer_SetPaused,
OPL_Timer_AdjustCallbacks,
};
#endif /* #ifdef HAVE_IOPERM */

View file

@ -110,7 +110,8 @@ opl_driver_t opl_openbsd_driver =
OPL_Timer_ClearCallbacks,
OPL_Timer_Lock,
OPL_Timer_Unlock,
OPL_Timer_SetPaused
OPL_Timer_SetPaused,
OPL_Timer_AdjustCallbacks,
};
#endif /* #ifndef NO_OBSD_DRIVER */

View file

@ -201,6 +201,19 @@ unsigned int OPL_Queue_Peek(opl_callback_queue_t *queue)
}
}
void OPL_Queue_AdjustCallbacks(opl_callback_queue_t *queue,
unsigned int time, float factor)
{
int 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);
}
}
#ifdef TEST
#include <assert.h>

View file

@ -32,6 +32,8 @@ void OPL_Queue_Push(opl_callback_queue_t *queue,
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);
void OPL_Queue_AdjustCallbacks(opl_callback_queue_t *queue,
unsigned int time, float factor);
#endif /* #ifndef OPL_QUEUE_H */

View file

@ -486,6 +486,13 @@ static void OPL_SDL_SetPaused(int paused)
opl_sdl_paused = paused;
}
static void OPL_SDL_AdjustCallbacks(float factor)
{
SDL_LockMutex(callback_queue_mutex);
OPL_Queue_AdjustCallbacks(callback_queue, current_time, factor);
SDL_UnlockMutex(callback_queue_mutex);
}
opl_driver_t opl_sdl_driver =
{
"SDL",
@ -497,6 +504,7 @@ opl_driver_t opl_sdl_driver =
OPL_SDL_ClearCallbacks,
OPL_SDL_Lock,
OPL_SDL_Unlock,
OPL_SDL_SetPaused
OPL_SDL_SetPaused,
OPL_SDL_AdjustCallbacks,
};

View file

@ -224,6 +224,13 @@ void OPL_Timer_ClearCallbacks(void)
SDL_UnlockMutex(callback_queue_mutex);
}
void OPL_Timer_AdjustCallbacks(float factor)
{
SDL_LockMutex(callback_queue_mutex);
OPL_Queue_AdjustCallbacks(callback_queue, current_time, factor);
SDL_UnlockMutex(callback_queue_mutex);
}
void OPL_Timer_Lock(void)
{
SDL_LockMutex(timer_mutex);

View file

@ -29,6 +29,7 @@ void OPL_Timer_ClearCallbacks(void);
void OPL_Timer_Lock(void);
void OPL_Timer_Unlock(void);
void OPL_Timer_SetPaused(int paused);
void OPL_Timer_AdjustCallbacks(float factor);
#endif /* #ifndef OPL_TIMER_H */

View file

@ -184,7 +184,8 @@ opl_driver_t opl_win32_driver =
OPL_Timer_ClearCallbacks,
OPL_Timer_Lock,
OPL_Timer_Unlock,
OPL_Timer_SetPaused
OPL_Timer_SetPaused,
OPL_Timer_AdjustCallbacks,
};
#endif /* #ifdef _WIN32 */

View file

@ -1016,6 +1016,12 @@ static void PitchBendEvent(opl_track_data_t *track, midi_event_t *event)
}
}
static void MetaSetTempo(unsigned int tempo)
{
OPL_AdjustCallbacks((float) us_per_beat / tempo);
us_per_beat = tempo;
}
// Process a meta event.
static void MetaEvent(opl_track_data_t *track, midi_event_t *event)
@ -1041,7 +1047,7 @@ static void MetaEvent(opl_track_data_t *track, midi_event_t *event)
case MIDI_META_SET_TEMPO:
if (data_len == 3)
{
us_per_beat = (data[0] << 16) | (data[1] << 8) | data[2];
MetaSetTempo((data[0] << 16) | (data[1] << 8) | data[2]);
}
break;
@ -1168,19 +1174,19 @@ static void TrackTimerCallback(void *arg)
static void ScheduleTrack(opl_track_data_t *track)
{
unsigned int nticks;
unsigned int ms;
static int total = 0;
uint64_t ms;
// Get the number of milliseconds until the next event.
nticks = MIDI_GetDeltaTime(track->iter);
ms = (nticks * us_per_beat * TEMPO_FUDGE_FACTOR) / ticks_per_beat;
total += ms;
ms = nticks;
ms *= us_per_beat * TEMPO_FUDGE_FACTOR;
ms /= ticks_per_beat;
// Set a timer to be invoked when the next event is
// ready to play.
OPL_SetCallback(ms, TrackTimerCallback, track);
OPL_SetCallback((unsigned int) ms, TrackTimerCallback, track);
}
// Initialize a channel.