mirror of
https://github.com/fabiangreffrath/woof.git
synced 2025-09-22 11:22:18 -04:00
fix Thread Sanitizer warnings, music modules refactoring (#1531)
* Fix TSan warnings with Fluidsynth backend * Convert Fluidsynth and OPL modules to stream_module_t interface * Remove mutexes from OPL module * Remove I_OAL_HookMusic/callback * Move extern declarations to headers
This commit is contained in:
parent
b3147857e2
commit
7bc4f24c7c
@ -99,6 +99,17 @@ if(ENABLE_ASAN)
|
||||
_checked_add_link_option(-fsanitize=address)
|
||||
endif()
|
||||
|
||||
option(ENABLE_TSAN "Enable TSan" OFF)
|
||||
if(ENABLE_TSAN)
|
||||
# Set -Werror to catch "argument unused during compilation" warnings.
|
||||
# Also needs to be a link flag for test to pass.
|
||||
set(CMAKE_REQUIRED_FLAGS "-Werror -fsanitize=thread")
|
||||
_checked_add_compile_option(-g)
|
||||
_checked_add_compile_option(-fsanitize=thread)
|
||||
unset(CMAKE_REQUIRED_FLAGS)
|
||||
_checked_add_link_option(-fsanitize=thread)
|
||||
endif()
|
||||
|
||||
option(ENABLE_HARDENING "Enable hardening flags" OFF)
|
||||
if(ENABLE_HARDENING)
|
||||
_checked_add_compile_option(-fstack-protector-strong)
|
||||
|
@ -12,4 +12,4 @@ target_woof_settings(opl)
|
||||
target_include_directories(opl
|
||||
INTERFACE "."
|
||||
PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../" "../src/")
|
||||
target_link_libraries(opl SDL2::SDL2)
|
||||
target_link_libraries(opl)
|
||||
|
171
opl/opl.c
171
opl/opl.c
@ -15,9 +15,8 @@
|
||||
// OPL interface.
|
||||
//
|
||||
|
||||
#include "SDL.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "m_io.h"
|
||||
#include "opl.h"
|
||||
@ -34,7 +33,6 @@ static opl_driver_t *drivers[] =
|
||||
};
|
||||
|
||||
static opl_driver_t *driver = NULL;
|
||||
static int init_stage_reg_writes = 1;
|
||||
|
||||
unsigned int opl_sample_rate = 22050;
|
||||
|
||||
@ -48,8 +46,6 @@ unsigned int opl_sample_rate = 22050;
|
||||
static opl_init_result_t InitDriver(opl_driver_t *_driver,
|
||||
unsigned int port_base)
|
||||
{
|
||||
opl_init_result_t result1, result2;
|
||||
|
||||
// Initialize the driver.
|
||||
|
||||
if (!_driver->init_func(port_base))
|
||||
@ -63,23 +59,10 @@ static opl_init_result_t InitDriver(opl_driver_t *_driver,
|
||||
// (it's done twice, like how Doom does it).
|
||||
|
||||
driver = _driver;
|
||||
init_stage_reg_writes = 1;
|
||||
|
||||
result1 = OPL_Detect();
|
||||
result2 = OPL_Detect();
|
||||
if (result1 == OPL_INIT_NONE || result2 == OPL_INIT_NONE)
|
||||
{
|
||||
printf("OPL_Init: No OPL detected using '%s' driver.\n", _driver->name);
|
||||
_driver->shutdown_func();
|
||||
driver = NULL;
|
||||
return OPL_INIT_NONE;
|
||||
}
|
||||
|
||||
init_stage_reg_writes = 0;
|
||||
|
||||
printf("OPL_Init: Using driver '%s'.\n", driver->name);
|
||||
|
||||
return result2;
|
||||
return OPL_INIT_OPL3;
|
||||
}
|
||||
|
||||
// Find a driver automatically by trying each in the list.
|
||||
@ -231,20 +214,9 @@ void OPL_WriteRegister(int reg, int value)
|
||||
// register number to cause the appropriate delay
|
||||
|
||||
for (i=0; i<6; ++i)
|
||||
{
|
||||
// An oddity of the Doom OPL code: at startup initialization,
|
||||
// the spacing here is performed by reading from the register
|
||||
// port; after initialization, the data port is read, instead.
|
||||
|
||||
if (init_stage_reg_writes)
|
||||
{
|
||||
OPL_ReadPort(OPL_REGISTER_PORT);
|
||||
}
|
||||
else
|
||||
{
|
||||
OPL_ReadPort(OPL_DATA_PORT);
|
||||
}
|
||||
}
|
||||
|
||||
OPL_WritePort(OPL_DATA_PORT, value);
|
||||
|
||||
@ -257,66 +229,6 @@ void OPL_WriteRegister(int reg, int value)
|
||||
}
|
||||
}
|
||||
|
||||
// Detect the presence of an OPL chip
|
||||
|
||||
opl_init_result_t OPL_Detect(void)
|
||||
{
|
||||
int result1, result2;
|
||||
int i;
|
||||
|
||||
// Reset both timers:
|
||||
OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60);
|
||||
|
||||
// Enable interrupts:
|
||||
OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80);
|
||||
|
||||
// Read status
|
||||
result1 = OPL_ReadStatus();
|
||||
|
||||
// Set timer:
|
||||
OPL_WriteRegister(OPL_REG_TIMER1, 0xff);
|
||||
|
||||
// Start timer 1:
|
||||
OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x21);
|
||||
|
||||
// Wait for 80 microseconds
|
||||
// This is how Doom does it:
|
||||
|
||||
for (i=0; i<200; ++i)
|
||||
{
|
||||
OPL_ReadStatus();
|
||||
}
|
||||
|
||||
OPL_Delay(1 * OPL_MS);
|
||||
|
||||
// Read status
|
||||
result2 = OPL_ReadStatus();
|
||||
|
||||
// Reset both timers:
|
||||
OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x60);
|
||||
|
||||
// Enable interrupts:
|
||||
OPL_WriteRegister(OPL_REG_TIMER_CTRL, 0x80);
|
||||
|
||||
if ((result1 & 0xe0) == 0x00 && (result2 & 0xe0) == 0xc0)
|
||||
{
|
||||
result1 = OPL_ReadPort(OPL_REGISTER_PORT);
|
||||
result2 = OPL_ReadPort(OPL_REGISTER_PORT_OPL3);
|
||||
if (result1 == 0x00)
|
||||
{
|
||||
return OPL_INIT_OPL3;
|
||||
}
|
||||
else
|
||||
{
|
||||
return OPL_INIT_OPL2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return OPL_INIT_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize registers on startup
|
||||
|
||||
void OPL_InitRegisters(int opl3)
|
||||
@ -414,85 +326,6 @@ void OPL_ClearCallbacks(void)
|
||||
}
|
||||
}
|
||||
|
||||
void OPL_Lock(void)
|
||||
{
|
||||
if (driver != NULL)
|
||||
{
|
||||
driver->lock_func();
|
||||
}
|
||||
}
|
||||
|
||||
void OPL_Unlock(void)
|
||||
{
|
||||
if (driver != NULL)
|
||||
{
|
||||
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_CondSignal(delay_data->cond);
|
||||
|
||||
SDL_UnlockMutex(delay_data->mutex);
|
||||
}
|
||||
|
||||
void OPL_Delay(uint64_t us)
|
||||
{
|
||||
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(us, 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);
|
||||
}
|
||||
|
||||
void OPL_SetPaused(int paused)
|
||||
{
|
||||
if (driver != NULL)
|
||||
{
|
||||
driver->set_paused_func(paused);
|
||||
}
|
||||
}
|
||||
|
||||
void OPL_AdjustCallbacks(float value)
|
||||
{
|
||||
if (driver != NULL)
|
||||
|
20
opl/opl.h
20
opl/opl.h
@ -104,11 +104,6 @@ unsigned int OPL_ReadStatus(void);
|
||||
|
||||
void OPL_WriteRegister(int reg, int value);
|
||||
|
||||
// Perform a detection sequence to determine that an
|
||||
// OPL chip is present.
|
||||
|
||||
opl_init_result_t OPL_Detect(void);
|
||||
|
||||
// Initialize all registers, performed on startup.
|
||||
|
||||
void OPL_InitRegisters(int opl3);
|
||||
@ -131,22 +126,11 @@ void OPL_AdjustCallbacks(float factor);
|
||||
|
||||
void OPL_ClearCallbacks(void);
|
||||
|
||||
// Begin critical section, during which, OPL callbacks will not be
|
||||
// invoked.
|
||||
|
||||
void OPL_Lock(void);
|
||||
|
||||
// End critical section.
|
||||
|
||||
void OPL_Unlock(void);
|
||||
|
||||
// Block until the specified number of microseconds have elapsed.
|
||||
|
||||
void OPL_Delay(uint64_t us);
|
||||
|
||||
// Pause the OPL callbacks.
|
||||
|
||||
void OPL_SetPaused(int paused);
|
||||
|
||||
int OPL_FillBuffer(unsigned char *buffer, int buffer_samples);
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -29,9 +29,6 @@ typedef void (*opl_set_callback_func)(uint64_t us,
|
||||
opl_callback_t callback,
|
||||
void *data);
|
||||
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
|
||||
@ -44,9 +41,6 @@ typedef struct
|
||||
opl_write_port_func write_port_func;
|
||||
opl_set_callback_func set_callback_func;
|
||||
opl_clear_callbacks_func clear_callbacks_func;
|
||||
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;
|
||||
|
||||
|
@ -15,10 +15,7 @@
|
||||
// OPL SDL interface.
|
||||
//
|
||||
|
||||
#include "SDL.h"
|
||||
|
||||
#include "doomtype.h"
|
||||
#include "i_oalmusic.h"
|
||||
#include "opl.h"
|
||||
#include "opl3.h"
|
||||
#include "opl_internal.h"
|
||||
@ -34,19 +31,10 @@ typedef struct
|
||||
uint64_t expire_time; // Calculated time that timer will expire.
|
||||
} opl_timer_t;
|
||||
|
||||
// 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 us since startup:
|
||||
|
||||
static uint64_t current_time;
|
||||
@ -85,8 +73,6 @@ static void AdvanceTime(unsigned int nsamples)
|
||||
void *callback_data;
|
||||
uint64_t us;
|
||||
|
||||
SDL_LockMutex(callback_queue_mutex);
|
||||
|
||||
// Advance time.
|
||||
|
||||
us = ((uint64_t) nsamples * OPL_SECOND) / mixing_freq;
|
||||
@ -110,35 +96,13 @@ static void AdvanceTime(unsigned int nsamples)
|
||||
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(uint8_t *buffer, unsigned int nsamples)
|
||||
{
|
||||
OPL3_GenerateStream(&opl_chip, (Bit16s *) buffer, nsamples);
|
||||
}
|
||||
|
||||
// Callback function to fill a new sound buffer:
|
||||
|
||||
static int OPL_Callback(byte *buffer, int buffer_samples)
|
||||
int OPL_FillBuffer(byte *buffer, int buffer_samples)
|
||||
{
|
||||
unsigned int filled;
|
||||
|
||||
@ -151,8 +115,6 @@ static int OPL_Callback(byte *buffer, int buffer_samples)
|
||||
uint64_t next_callback_time;
|
||||
uint64_t 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.
|
||||
@ -174,11 +136,8 @@ static int OPL_Callback(byte *buffer, int buffer_samples)
|
||||
}
|
||||
}
|
||||
|
||||
SDL_UnlockMutex(callback_queue_mutex);
|
||||
|
||||
// Add emulator output to buffer.
|
||||
|
||||
FillBuffer(buffer + filled * 4, nsamples);
|
||||
OPL3_GenerateStream(&opl_chip, (Bit16s *)(buffer + filled * 4), nsamples);
|
||||
filled += nsamples;
|
||||
|
||||
// Invoke callbacks for this point in time.
|
||||
@ -191,8 +150,6 @@ static int OPL_Callback(byte *buffer, int buffer_samples)
|
||||
|
||||
static void OPL_SDL_Shutdown(void)
|
||||
{
|
||||
I_OAL_HookMusic(NULL);
|
||||
|
||||
OPL_Queue_Destroy(callback_queue);
|
||||
|
||||
/*
|
||||
@ -202,22 +159,8 @@ static void OPL_SDL_Shutdown(void)
|
||||
opl_chip = 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;
|
||||
}
|
||||
}
|
||||
|
||||
int opl_gain = 200;
|
||||
|
||||
static int OPL_SDL_Init(unsigned int port_base)
|
||||
{
|
||||
opl_sdl_paused = 0;
|
||||
@ -239,19 +182,6 @@ static int OPL_SDL_Init(unsigned int port_base)
|
||||
OPL3_Reset(&opl_chip, mixing_freq);
|
||||
opl_opl3mode = 0;
|
||||
|
||||
callback_mutex = SDL_CreateMutex();
|
||||
callback_queue_mutex = SDL_CreateMutex();
|
||||
|
||||
if (!I_OAL_HookMusic(OPL_Callback))
|
||||
{
|
||||
OPL_Queue_Destroy(callback_queue);
|
||||
SDL_DestroyMutex(callback_mutex);
|
||||
SDL_DestroyMutex(callback_queue_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
I_OAL_SetGain((float)opl_gain / 100.0f);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -359,39 +289,18 @@ static void OPL_SDL_PortWrite(opl_port_t port, unsigned int value)
|
||||
static void OPL_SDL_SetCallback(uint64_t us, opl_callback_t callback,
|
||||
void *data)
|
||||
{
|
||||
SDL_LockMutex(callback_queue_mutex);
|
||||
OPL_Queue_Push(callback_queue, callback, data,
|
||||
current_time - pause_offset + us);
|
||||
SDL_UnlockMutex(callback_queue_mutex);
|
||||
}
|
||||
|
||||
static void OPL_SDL_ClearCallbacks(void)
|
||||
{
|
||||
SDL_LockMutex(callback_queue_mutex);
|
||||
OPL_Queue_Clear(callback_queue);
|
||||
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);
|
||||
}
|
||||
|
||||
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 =
|
||||
@ -403,9 +312,6 @@ opl_driver_t opl_sdl_driver =
|
||||
OPL_SDL_PortWrite,
|
||||
OPL_SDL_SetCallback,
|
||||
OPL_SDL_ClearCallbacks,
|
||||
OPL_SDL_Lock,
|
||||
OPL_SDL_Unlock,
|
||||
OPL_SDL_SetPaused,
|
||||
OPL_SDL_AdjustCallbacks,
|
||||
};
|
||||
|
||||
|
@ -38,7 +38,7 @@ set(WOOF_SOURCES
|
||||
i_input.c i_input.h
|
||||
i_main.c
|
||||
i_mbfsound.c
|
||||
i_oalmusic.c i_oalmusic.h
|
||||
i_oalmusic.c
|
||||
i_oalsound.c i_oalsound.h
|
||||
i_oalstream.h
|
||||
i_oplmusic.c
|
||||
|
158
src/i_flmusic.c
158
src/i_flmusic.c
@ -21,6 +21,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "i_oalstream.h"
|
||||
|
||||
#if (FLUIDSYNTH_VERSION_MAJOR < 2 || (FLUIDSYNTH_VERSION_MAJOR == 2 && FLUIDSYNTH_VERSION_MINOR < 2))
|
||||
typedef int fluid_int_t;
|
||||
@ -32,7 +33,6 @@
|
||||
#include "d_iwad.h" // [FG] D_DoomExeDir()
|
||||
#include "doomtype.h"
|
||||
#include "i_glob.h"
|
||||
#include "i_oalmusic.h"
|
||||
#include "i_printf.h"
|
||||
#include "i_sound.h"
|
||||
#include "m_array.h"
|
||||
@ -46,7 +46,6 @@ const char *soundfont_path = "";
|
||||
char *soundfont_dir = "";
|
||||
boolean mus_chorus;
|
||||
boolean mus_reverb;
|
||||
int mus_gain = 100;
|
||||
|
||||
static fluid_synth_t *synth = NULL;
|
||||
static fluid_settings_t *settings = NULL;
|
||||
@ -54,20 +53,6 @@ static fluid_player_t *player = NULL;
|
||||
|
||||
static const char **soundfonts = NULL;
|
||||
|
||||
static int FL_Callback(byte *buffer, int buffer_samples)
|
||||
{
|
||||
int result;
|
||||
|
||||
result = fluid_synth_write_s16(synth, buffer_samples, buffer, 0, 2, buffer, 1, 2);
|
||||
|
||||
if (result != FLUID_OK)
|
||||
{
|
||||
I_Printf(VB_ERROR, "FL_Callback: Error generating FluidSynth audio");
|
||||
}
|
||||
|
||||
return buffer_samples;
|
||||
}
|
||||
|
||||
// Load SNDFONT lump
|
||||
|
||||
static byte *lump;
|
||||
@ -211,7 +196,7 @@ static void I_FL_Log_Debug(int level, const char *message, void *data)
|
||||
I_Printf(VB_DEBUG, "%s", message);
|
||||
}
|
||||
|
||||
static boolean I_FL_InitMusic(int device)
|
||||
static boolean I_FL_InitStream(int device)
|
||||
{
|
||||
int sf_id;
|
||||
int lumpnum;
|
||||
@ -308,56 +293,16 @@ static boolean I_FL_InitMusic(int device)
|
||||
return false;
|
||||
}
|
||||
|
||||
fluid_synth_set_gain(synth, 1.0f);
|
||||
|
||||
I_Printf(VB_INFO, "FluidSynth Init: Using '%s'.",
|
||||
lumpnum >= 0 ? "SNDFONT lump" : soundfont_path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void I_FL_SetMusicVolume(int volume)
|
||||
{
|
||||
if (synth)
|
||||
{
|
||||
// FluidSynth's default is 0.2. Make 1.0 the maximum.
|
||||
// 0 -- 0.2 -- 10.0
|
||||
fluid_synth_set_gain(synth, ((float)volume / 15) * ((float)mus_gain / 100));
|
||||
}
|
||||
}
|
||||
|
||||
static void I_FL_PauseSong(void *handle)
|
||||
{
|
||||
if (player)
|
||||
{
|
||||
I_OAL_HookMusic(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void I_FL_ResumeSong(void *handle)
|
||||
{
|
||||
if (player)
|
||||
{
|
||||
I_OAL_HookMusic(FL_Callback);
|
||||
}
|
||||
}
|
||||
|
||||
static void I_FL_PlaySong(void *handle, boolean looping)
|
||||
{
|
||||
if (player)
|
||||
{
|
||||
fluid_player_set_loop(player, looping ? -1 : 1);
|
||||
fluid_player_play(player);
|
||||
}
|
||||
}
|
||||
|
||||
static void I_FL_StopSong(void *handle)
|
||||
{
|
||||
if (player)
|
||||
{
|
||||
fluid_player_stop(player);
|
||||
}
|
||||
}
|
||||
|
||||
static void *I_FL_RegisterSong(void *data, int len)
|
||||
static boolean I_FL_OpenStream(void *data, ALsizei size, ALenum *format,
|
||||
ALsizei *freq, ALsizei *frame_size)
|
||||
{
|
||||
int result = FLUID_FAILED;
|
||||
|
||||
@ -367,12 +312,12 @@ static void *I_FL_RegisterSong(void *data, int len)
|
||||
{
|
||||
I_Printf(VB_ERROR,
|
||||
"I_FL_InitMusic: FluidSynth failed to initialize player.");
|
||||
return NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsMid(data, len))
|
||||
if (IsMid(data, size))
|
||||
{
|
||||
result = fluid_player_add_mem(player, data, len);
|
||||
result = fluid_player_add_mem(player, data, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -382,7 +327,7 @@ static void *I_FL_RegisterSong(void *data, int len)
|
||||
void *outbuf;
|
||||
size_t outbuf_len;
|
||||
|
||||
instream = mem_fopen_read(data, len);
|
||||
instream = mem_fopen_read(data, size);
|
||||
outstream = mem_fopen_write();
|
||||
|
||||
if (mus2mid(instream, outstream) == 0)
|
||||
@ -400,38 +345,56 @@ static void *I_FL_RegisterSong(void *data, int len)
|
||||
delete_fluid_player(player);
|
||||
player = NULL;
|
||||
I_Printf(VB_ERROR, "I_FL_RegisterSong: Failed to load in-memory song.");
|
||||
return NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!I_OAL_HookMusic(FL_Callback))
|
||||
{
|
||||
delete_fluid_player(player);
|
||||
player = NULL;
|
||||
return NULL;
|
||||
}
|
||||
*format = AL_FORMAT_STEREO16;
|
||||
*freq = SND_SAMPLERATE;
|
||||
*frame_size = 2 * sizeof(short);
|
||||
|
||||
return (void *)1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void I_FL_UnRegisterSong(void *handle)
|
||||
static int I_FL_FillStream(byte *buffer, int buffer_samples)
|
||||
{
|
||||
int result;
|
||||
|
||||
result = fluid_synth_write_s16(synth, buffer_samples, buffer, 0, 2, buffer, 1, 2);
|
||||
|
||||
if (result != FLUID_OK)
|
||||
{
|
||||
I_Printf(VB_ERROR, "FL_Callback: Error generating FluidSynth audio");
|
||||
}
|
||||
|
||||
return buffer_samples;
|
||||
}
|
||||
|
||||
|
||||
static void I_FL_PlayStream(boolean looping)
|
||||
{
|
||||
if (player)
|
||||
{
|
||||
fluid_player_set_loop(player, looping ? -1 : 1);
|
||||
fluid_player_play(player);
|
||||
}
|
||||
}
|
||||
|
||||
static void I_FL_CloseStream(void)
|
||||
{
|
||||
if (player)
|
||||
{
|
||||
fluid_player_stop(player);
|
||||
|
||||
fluid_synth_program_reset(synth);
|
||||
fluid_synth_system_reset(synth);
|
||||
|
||||
I_OAL_HookMusic(NULL);
|
||||
|
||||
delete_fluid_player(player);
|
||||
player = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void I_FL_ShutdownMusic(void)
|
||||
static void I_FL_ShutdownStream(void)
|
||||
{
|
||||
I_FL_StopSong(NULL);
|
||||
I_FL_UnRegisterSong(NULL);
|
||||
|
||||
FreeSynthAndSettings();
|
||||
}
|
||||
|
||||
@ -439,9 +402,17 @@ static void I_FL_ShutdownMusic(void)
|
||||
|
||||
static const char **I_FL_DeviceList(int *current_device)
|
||||
{
|
||||
const char **devices = NULL;
|
||||
static const char **devices = NULL;
|
||||
|
||||
if (devices)
|
||||
{
|
||||
return devices;
|
||||
}
|
||||
|
||||
if (current_device)
|
||||
{
|
||||
*current_device = 0;
|
||||
}
|
||||
|
||||
if (W_CheckNumForName("SNDFONT") >= 0)
|
||||
{
|
||||
@ -461,7 +432,7 @@ static const char **I_FL_DeviceList(int *current_device)
|
||||
|
||||
array_push(devices, M_StringJoin("FluidSynth (", name, ")", NULL));
|
||||
|
||||
if (!strcasecmp(soundfonts[i], soundfont_path))
|
||||
if (current_device && !strcasecmp(soundfonts[i], soundfont_path))
|
||||
{
|
||||
*current_device = i;
|
||||
}
|
||||
@ -470,22 +441,13 @@ static const char **I_FL_DeviceList(int *current_device)
|
||||
return devices;
|
||||
}
|
||||
|
||||
static void I_FL_UpdateMusic(void)
|
||||
stream_module_t stream_fl_module =
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
music_module_t music_fl_module =
|
||||
{
|
||||
I_FL_InitMusic,
|
||||
I_FL_ShutdownMusic,
|
||||
I_FL_SetMusicVolume,
|
||||
I_FL_PauseSong,
|
||||
I_FL_ResumeSong,
|
||||
I_FL_RegisterSong,
|
||||
I_FL_PlaySong,
|
||||
I_FL_UpdateMusic,
|
||||
I_FL_StopSong,
|
||||
I_FL_UnRegisterSong,
|
||||
I_FL_InitStream,
|
||||
I_FL_OpenStream,
|
||||
I_FL_FillStream,
|
||||
I_FL_PlayStream,
|
||||
I_FL_CloseStream,
|
||||
I_FL_ShutdownStream,
|
||||
I_FL_DeviceList,
|
||||
};
|
||||
|
145
src/i_oalmusic.c
145
src/i_oalmusic.c
@ -22,7 +22,6 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "doomtype.h"
|
||||
#include "i_oalmusic.h"
|
||||
#include "i_oalstream.h"
|
||||
#include "i_printf.h"
|
||||
#include "i_sound.h"
|
||||
@ -33,10 +32,7 @@
|
||||
#define NUM_BUFFERS 4
|
||||
#define BUFFER_SAMPLES 4096
|
||||
|
||||
extern stream_module_t stream_snd_module;
|
||||
extern stream_module_t stream_xmp_module;
|
||||
|
||||
stream_module_t *stream_modules[] =
|
||||
static stream_module_t *stream_modules[] =
|
||||
{
|
||||
&stream_snd_module,
|
||||
#if defined(HAVE_LIBXMP)
|
||||
@ -66,10 +62,10 @@ static stream_player_t player;
|
||||
static SDL_Thread *player_thread_handle;
|
||||
static int player_thread_running;
|
||||
|
||||
static callback_func_t callback;
|
||||
|
||||
static boolean music_initialized;
|
||||
|
||||
static SDL_mutex *music_lock;
|
||||
|
||||
static boolean UpdatePlayer(void)
|
||||
{
|
||||
ALint processed, state;
|
||||
@ -95,14 +91,7 @@ static boolean UpdatePlayer(void)
|
||||
|
||||
// Read the next chunk of data, refill the buffer, and queue it back on
|
||||
// the source.
|
||||
if (callback)
|
||||
{
|
||||
frames = callback(player.data, BUFFER_SAMPLES);
|
||||
}
|
||||
else
|
||||
{
|
||||
frames = active_module->I_FillStream(player.data, BUFFER_SAMPLES);
|
||||
}
|
||||
|
||||
if (frames > 0)
|
||||
{
|
||||
@ -150,10 +139,7 @@ static boolean StartPlayer(void)
|
||||
alSourceRewind(player.source);
|
||||
alSourcei(player.source, AL_BUFFER, 0);
|
||||
|
||||
if (!callback)
|
||||
{
|
||||
active_module->I_PlayStream(player.looping);
|
||||
}
|
||||
|
||||
// Fill the buffer queue
|
||||
for (i = 0; i < NUM_BUFFERS; i++)
|
||||
@ -162,14 +148,7 @@ static boolean StartPlayer(void)
|
||||
ALsizei size;
|
||||
|
||||
// Get some data to give it to the buffer
|
||||
if (callback)
|
||||
{
|
||||
frames = callback(player.data, BUFFER_SAMPLES);
|
||||
}
|
||||
else
|
||||
{
|
||||
frames = active_module->I_FillStream(player.data, BUFFER_SAMPLES);
|
||||
}
|
||||
|
||||
if (frames < 1)
|
||||
break;
|
||||
@ -196,13 +175,20 @@ static int PlayerThread(void *unused)
|
||||
|
||||
StartPlayer();
|
||||
|
||||
while (player_thread_running)
|
||||
while (true)
|
||||
{
|
||||
boolean result;
|
||||
boolean keep_going;
|
||||
|
||||
result = UpdatePlayer();
|
||||
if (!UpdatePlayer())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == false)
|
||||
SDL_LockMutex(music_lock);
|
||||
keep_going = player_thread_running;
|
||||
SDL_UnlockMutex(music_lock);
|
||||
|
||||
if (!keep_going)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@ -243,17 +229,36 @@ static boolean I_OAL_InitMusic(int device)
|
||||
alSourcei(player.source, AL_SOURCE_SPATIALIZE_SOFT, AL_FALSE);
|
||||
}
|
||||
|
||||
for (int i = 0; i < arrlen(stream_modules); ++i)
|
||||
{
|
||||
stream_modules[i]->I_InitStream(0);
|
||||
}
|
||||
|
||||
music_initialized = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int mus_gain = 100;
|
||||
int opl_gain = 200;
|
||||
|
||||
static void I_OAL_SetMusicVolume(int volume)
|
||||
{
|
||||
if (!music_initialized)
|
||||
return;
|
||||
|
||||
alSourcef(player.source, AL_GAIN, (ALfloat)volume / 15.0f);
|
||||
ALfloat gain = (ALfloat)volume / 15.0f;
|
||||
|
||||
if (active_module == &stream_opl_module)
|
||||
{
|
||||
gain *= (ALfloat)opl_gain / 100.0f;
|
||||
}
|
||||
else if (active_module == &stream_fl_module)
|
||||
{
|
||||
gain *= (ALfloat)mus_gain / 100.0f;
|
||||
}
|
||||
|
||||
alSourcef(player.source, AL_GAIN, gain);
|
||||
}
|
||||
|
||||
static void I_OAL_PauseSong(void *handle)
|
||||
@ -286,6 +291,8 @@ static void I_OAL_PlaySong(void *handle, boolean looping)
|
||||
return;
|
||||
}
|
||||
|
||||
music_lock = SDL_CreateMutex();
|
||||
|
||||
player_thread_running = true;
|
||||
player_thread_handle = SDL_CreateThread(PlayerThread, NULL, NULL);
|
||||
if (player_thread_handle == NULL)
|
||||
@ -297,34 +304,23 @@ static void I_OAL_PlaySong(void *handle, boolean looping)
|
||||
|
||||
static void I_OAL_StopSong(void *handle)
|
||||
{
|
||||
ALsizei processed;
|
||||
|
||||
if (!music_initialized)
|
||||
if (!music_initialized || !player_thread_running)
|
||||
return;
|
||||
|
||||
if (!player_thread_running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
alSourceStop(player.source);
|
||||
|
||||
SDL_LockMutex(music_lock);
|
||||
player_thread_running = false;
|
||||
SDL_WaitThread(player_thread_handle, NULL);
|
||||
SDL_UnlockMutex(music_lock);
|
||||
|
||||
alGetSourcei(player.source, AL_BUFFERS_PROCESSED, &processed);
|
||||
if (processed > 0)
|
||||
{
|
||||
ALuint* al_buf = malloc(processed * sizeof(*al_buf));
|
||||
alSourceUnqueueBuffers(player.source, processed, al_buf);
|
||||
free(al_buf);
|
||||
}
|
||||
alSourcei(player.source, AL_BUFFER, 0);
|
||||
SDL_WaitThread(player_thread_handle, NULL);
|
||||
|
||||
if (alGetError() != AL_NO_ERROR)
|
||||
{
|
||||
I_Printf(VB_ERROR, "I_OAL_StopSong: Error stopping playback.");
|
||||
}
|
||||
|
||||
SDL_DestroyMutex(music_lock);
|
||||
}
|
||||
|
||||
static void I_OAL_UnRegisterSong(void *handle)
|
||||
@ -332,10 +328,7 @@ static void I_OAL_UnRegisterSong(void *handle)
|
||||
if (!music_initialized)
|
||||
return;
|
||||
|
||||
if (!callback)
|
||||
{
|
||||
active_module->I_CloseStream();
|
||||
}
|
||||
|
||||
if (player.data)
|
||||
{
|
||||
@ -352,6 +345,12 @@ static void I_OAL_ShutdownMusic(void)
|
||||
I_OAL_StopSong(NULL);
|
||||
I_OAL_UnRegisterSong(NULL);
|
||||
|
||||
if (midi_stream_module)
|
||||
{
|
||||
midi_stream_module->I_ShutdownStream();
|
||||
}
|
||||
active_module->I_ShutdownStream();
|
||||
|
||||
alDeleteSources(1, &player.source);
|
||||
alDeleteBuffers(NUM_BUFFERS, player.buffers);
|
||||
if (alGetError() != AL_NO_ERROR)
|
||||
@ -373,6 +372,18 @@ static void *I_OAL_RegisterSong(void *data, int len)
|
||||
if (!music_initialized)
|
||||
return NULL;
|
||||
|
||||
if (IsMid(data, len) || IsMus(data, len))
|
||||
{
|
||||
if (midi_stream_module &&
|
||||
midi_stream_module->I_OpenStream(data, len, &player.format,
|
||||
&player.freq, &player.frame_size))
|
||||
{
|
||||
active_module = midi_stream_module;
|
||||
return (void *)1;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < arrlen(stream_modules); ++i)
|
||||
{
|
||||
if (stream_modules[i]->I_OpenStream(data, len, &player.format,
|
||||
@ -388,45 +399,9 @@ static void *I_OAL_RegisterSong(void *data, int len)
|
||||
|
||||
static const char **I_OAL_DeviceList(int *current_device)
|
||||
{
|
||||
*current_device = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void I_OAL_SetGain(float gain)
|
||||
{
|
||||
if (!music_initialized)
|
||||
return;
|
||||
|
||||
alSourcef(player.source, AL_GAIN, (ALfloat)gain);
|
||||
}
|
||||
|
||||
boolean I_OAL_HookMusic(callback_func_t callback_func)
|
||||
{
|
||||
if (!music_initialized)
|
||||
return false;
|
||||
|
||||
if (callback_func)
|
||||
{
|
||||
callback = callback_func;
|
||||
|
||||
player.format = AL_FORMAT_STEREO16;
|
||||
player.freq = SND_SAMPLERATE;
|
||||
player.frame_size = 2 * sizeof(short);
|
||||
|
||||
I_OAL_SetGain(1.0f);
|
||||
I_OAL_PlaySong(NULL, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
I_OAL_StopSong(NULL);
|
||||
I_OAL_UnRegisterSong(NULL);
|
||||
|
||||
callback = NULL;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void I_OAL_UpdateMusic(void)
|
||||
{
|
||||
;
|
||||
|
@ -1,27 +0,0 @@
|
||||
//
|
||||
// Copyright(C) 2023 Roman Fomin
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// DESCRIPTION:
|
||||
//
|
||||
|
||||
#ifndef __I_OALMUSIC__
|
||||
#define __I_OALMUSIC__
|
||||
|
||||
#include "doomtype.h"
|
||||
|
||||
typedef int (*callback_func_t)(byte *buffer, int buffer_samples);
|
||||
|
||||
boolean I_OAL_HookMusic(callback_func_t callback_func);
|
||||
void I_OAL_SetGain(float gain);
|
||||
|
||||
#endif
|
@ -21,13 +21,21 @@
|
||||
|
||||
#include "doomtype.h"
|
||||
|
||||
typedef struct
|
||||
typedef struct stream_module_s
|
||||
{
|
||||
boolean (*I_InitStream)(int device);
|
||||
boolean (*I_OpenStream)(void *data, ALsizei size, ALenum *format,
|
||||
ALsizei *freq, ALsizei *frame_size);
|
||||
int (*I_FillStream)(byte *data, int frames);
|
||||
void (*I_PlayStream)(boolean looping);
|
||||
void (*I_CloseStream)(void);
|
||||
void (*I_ShutdownStream)(void);
|
||||
const char **(*I_DeviceList)(int *current_device);
|
||||
} stream_module_t;
|
||||
|
||||
extern stream_module_t stream_opl_module;
|
||||
extern stream_module_t stream_fl_module;
|
||||
extern stream_module_t stream_snd_module;
|
||||
extern stream_module_t stream_xmp_module;
|
||||
|
||||
#endif
|
||||
|
420
src/i_oplmusic.c
420
src/i_oplmusic.c
@ -22,6 +22,7 @@
|
||||
#include "../opl/opl.h"
|
||||
|
||||
#include "doomtype.h"
|
||||
#include "i_oalstream.h"
|
||||
#include "i_printf.h"
|
||||
#include "i_sound.h"
|
||||
#include "m_array.h"
|
||||
@ -652,13 +653,6 @@ static void I_OPL_SetMusicVolume(int volume)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
volume = volume * 127 / 15; // [FG] adjust volume
|
||||
|
||||
if (current_music_volume == volume)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Internal state variable.
|
||||
|
||||
current_music_volume = volume;
|
||||
@ -1481,234 +1475,7 @@ static void StartTrack(midi_file_t *file, unsigned int track_num)
|
||||
ScheduleTrack(track);
|
||||
}
|
||||
|
||||
// Start playing a mid
|
||||
|
||||
static void I_OPL_PlaySong(void *handle, boolean looping)
|
||||
{
|
||||
midi_file_t *file;
|
||||
unsigned int i;
|
||||
|
||||
if (!music_initialized || handle == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
file = handle;
|
||||
|
||||
// Allocate track data.
|
||||
|
||||
tracks = malloc(MIDI_NumTracks(file) * sizeof(opl_track_data_t));
|
||||
|
||||
num_tracks = MIDI_NumTracks(file);
|
||||
running_tracks = num_tracks;
|
||||
song_looping = looping;
|
||||
|
||||
ticks_per_beat = MIDI_GetFileTimeDivision(file);
|
||||
|
||||
// Default is 120 bpm.
|
||||
// TODO: this is wrong
|
||||
|
||||
us_per_beat = 500 * 1000;
|
||||
|
||||
start_music_volume = current_music_volume;
|
||||
|
||||
for (i = 0; i < num_tracks; ++i)
|
||||
{
|
||||
StartTrack(file, i);
|
||||
}
|
||||
|
||||
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
|
||||
{
|
||||
InitChannel(&channels[i]);
|
||||
}
|
||||
|
||||
// If the music was previously paused, it needs to be unpaused; playing
|
||||
// a new song implies that we turn off pause. This matches vanilla
|
||||
// behavior of the DMX library, and some of the higher-level code in
|
||||
// s_sound.c relies on this.
|
||||
OPL_SetPaused(0);
|
||||
}
|
||||
|
||||
static void I_OPL_PauseSong(void *handle)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
if (!music_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Pause OPL callbacks.
|
||||
|
||||
OPL_SetPaused(1);
|
||||
|
||||
// Turn off all main instrument voices (not percussion).
|
||||
// This is what Vanilla does.
|
||||
|
||||
for (i = 0; i < num_opl_voices; ++i)
|
||||
{
|
||||
if (voices[i].channel != NULL
|
||||
&& voices[i].current_instr < percussion_instrs)
|
||||
{
|
||||
VoiceKeyOff(&voices[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void I_OPL_ResumeSong(void *handle)
|
||||
{
|
||||
if (!music_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OPL_SetPaused(0);
|
||||
}
|
||||
|
||||
static void I_OPL_StopSong(void *handle)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
if (!music_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OPL_Lock();
|
||||
|
||||
// Stop all playback.
|
||||
|
||||
OPL_ClearCallbacks();
|
||||
|
||||
// Free all voices.
|
||||
|
||||
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
|
||||
{
|
||||
AllNotesOff(&channels[i], 0);
|
||||
}
|
||||
|
||||
// Free all track data.
|
||||
|
||||
for (i = 0; i < num_tracks; ++i)
|
||||
{
|
||||
MIDI_FreeIterator(tracks[i].iter);
|
||||
}
|
||||
|
||||
free(tracks);
|
||||
|
||||
tracks = NULL;
|
||||
num_tracks = 0;
|
||||
|
||||
OPL_Unlock();
|
||||
}
|
||||
|
||||
static void I_OPL_UnRegisterSong(void *handle)
|
||||
{
|
||||
if (!music_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (handle != NULL)
|
||||
{
|
||||
MIDI_FreeFile(handle);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean OPL_InitMusic(void);
|
||||
|
||||
static void *I_OPL_RegisterSong(void *data, int len)
|
||||
{
|
||||
midi_file_t *result;
|
||||
|
||||
if (!music_initialized)
|
||||
{
|
||||
OPL_InitMusic();
|
||||
}
|
||||
|
||||
// MUS files begin with "MUS"
|
||||
// Reject anything which doesnt have this signature
|
||||
|
||||
// [crispy] remove MID file size limit
|
||||
if (IsMid(data, len) /* && len < MAXMIDLENGTH */)
|
||||
{
|
||||
result = MIDI_LoadFile(data, len);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Assume a MUS file and try to convert
|
||||
MEMFILE *instream;
|
||||
MEMFILE *outstream;
|
||||
void *outbuf;
|
||||
size_t outbuf_len;
|
||||
|
||||
instream = mem_fopen_read(data, len);
|
||||
outstream = mem_fopen_write();
|
||||
|
||||
if (mus2mid(instream, outstream) == 0)
|
||||
{
|
||||
mem_get_buf(outstream, &outbuf, &outbuf_len);
|
||||
result = MIDI_LoadFile(outbuf, outbuf_len);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = NULL;
|
||||
}
|
||||
|
||||
mem_fclose(instream);
|
||||
mem_fclose(outstream);
|
||||
}
|
||||
|
||||
if (result == NULL)
|
||||
{
|
||||
I_Printf(VB_ERROR, "I_OPL_RegisterSong: Failed to load MID.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// Is the song playing?
|
||||
|
||||
static boolean I_OPL_MusicIsPlaying(void)
|
||||
{
|
||||
if (!music_initialized)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return num_tracks > 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Shutdown music
|
||||
|
||||
static void I_OPL_ShutdownMusic(void)
|
||||
{
|
||||
if (music_initialized)
|
||||
{
|
||||
// Stop currently-playing track, if there is one:
|
||||
|
||||
I_OPL_StopSong(NULL);
|
||||
|
||||
OPL_Shutdown();
|
||||
|
||||
// Release GENMIDI lump
|
||||
|
||||
Z_ChangeTag(lump, PU_CACHE);
|
||||
|
||||
music_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize music subsystem
|
||||
|
||||
static boolean I_OPL_InitMusic(int device)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean OPL_InitMusic(void)
|
||||
static boolean I_OPL_InitStream(int device)
|
||||
{
|
||||
char *dmxoption;
|
||||
opl_init_result_t chip_type;
|
||||
@ -1766,30 +1533,179 @@ static boolean OPL_InitMusic(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
static midi_file_t *midifile;
|
||||
|
||||
static boolean I_OPL_OpenStream(void *data, ALsizei size, ALenum *format,
|
||||
ALsizei *freq, ALsizei *frame_size)
|
||||
{
|
||||
// [crispy] remove MID file size limit
|
||||
if (IsMid(data, size) /* && size < MAXMIDLENGTH */)
|
||||
{
|
||||
midifile = MIDI_LoadFile(data, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Assume a MUS file and try to convert
|
||||
MEMFILE *instream;
|
||||
MEMFILE *outstream;
|
||||
void *outbuf;
|
||||
size_t outbuf_len;
|
||||
|
||||
instream = mem_fopen_read(data, size);
|
||||
outstream = mem_fopen_write();
|
||||
|
||||
if (mus2mid(instream, outstream) == 0)
|
||||
{
|
||||
mem_get_buf(outstream, &outbuf, &outbuf_len);
|
||||
midifile = MIDI_LoadFile(outbuf, outbuf_len);
|
||||
}
|
||||
else
|
||||
{
|
||||
midifile = NULL;
|
||||
}
|
||||
|
||||
mem_fclose(instream);
|
||||
mem_fclose(outstream);
|
||||
}
|
||||
|
||||
if (midifile == NULL)
|
||||
{
|
||||
I_Printf(VB_ERROR, "I_OPL_RegisterSong: Failed to load MID.");
|
||||
return false;
|
||||
}
|
||||
|
||||
*format = AL_FORMAT_STEREO16;
|
||||
*freq = SND_SAMPLERATE;
|
||||
*frame_size = 2 * sizeof(short);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Start playing a mid
|
||||
|
||||
static void I_OPL_PlayStream(boolean looping)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
if (!music_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
I_OPL_SetMusicVolume(127);
|
||||
|
||||
// Allocate track data.
|
||||
|
||||
tracks = malloc(MIDI_NumTracks(midifile) * sizeof(opl_track_data_t));
|
||||
|
||||
num_tracks = MIDI_NumTracks(midifile);
|
||||
running_tracks = num_tracks;
|
||||
song_looping = looping;
|
||||
|
||||
ticks_per_beat = MIDI_GetFileTimeDivision(midifile);
|
||||
|
||||
// Default is 120 bpm.
|
||||
// TODO: this is wrong
|
||||
|
||||
us_per_beat = 500 * 1000;
|
||||
|
||||
start_music_volume = current_music_volume;
|
||||
|
||||
for (i = 0; i < num_tracks; ++i)
|
||||
{
|
||||
StartTrack(midifile, i);
|
||||
}
|
||||
|
||||
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
|
||||
{
|
||||
InitChannel(&channels[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static int I_OPL_FillStream(byte *buffer, int buffer_samples)
|
||||
{
|
||||
return OPL_FillBuffer(buffer, buffer_samples);
|
||||
}
|
||||
|
||||
static void I_OPL_CloseStream(void)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
if (!music_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop all playback.
|
||||
|
||||
OPL_ClearCallbacks();
|
||||
|
||||
// Free all voices.
|
||||
|
||||
for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
|
||||
{
|
||||
AllNotesOff(&channels[i], 0);
|
||||
}
|
||||
|
||||
// Free all track data.
|
||||
|
||||
for (i = 0; i < num_tracks; ++i)
|
||||
{
|
||||
MIDI_FreeIterator(tracks[i].iter);
|
||||
}
|
||||
|
||||
free(tracks);
|
||||
|
||||
tracks = NULL;
|
||||
num_tracks = 0;
|
||||
|
||||
if (midifile)
|
||||
{
|
||||
MIDI_FreeFile(midifile);
|
||||
midifile = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown music
|
||||
|
||||
static void I_OPL_ShutdownStream(void)
|
||||
{
|
||||
if (music_initialized)
|
||||
{
|
||||
OPL_Shutdown();
|
||||
|
||||
// Release GENMIDI lump
|
||||
|
||||
Z_ChangeTag(lump, PU_CACHE);
|
||||
|
||||
music_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
static const char **I_OPL_DeviceList(int *current_device)
|
||||
{
|
||||
const char **devices = NULL;
|
||||
static const char **devices = NULL;
|
||||
|
||||
if (devices)
|
||||
{
|
||||
return devices;
|
||||
}
|
||||
|
||||
if (current_device)
|
||||
{
|
||||
*current_device = 0;
|
||||
}
|
||||
array_push(devices, "OPL3 Emulation");
|
||||
return devices;
|
||||
}
|
||||
|
||||
static void I_OPL_UpdateMusic(void)
|
||||
stream_module_t stream_opl_module =
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
music_module_t music_opl_module =
|
||||
{
|
||||
I_OPL_InitMusic,
|
||||
I_OPL_ShutdownMusic,
|
||||
I_OPL_SetMusicVolume,
|
||||
I_OPL_PauseSong,
|
||||
I_OPL_ResumeSong,
|
||||
I_OPL_RegisterSong,
|
||||
I_OPL_PlaySong,
|
||||
I_OPL_UpdateMusic,
|
||||
I_OPL_StopSong,
|
||||
I_OPL_UnRegisterSong,
|
||||
I_OPL_InitStream,
|
||||
I_OPL_OpenStream,
|
||||
I_OPL_FillStream,
|
||||
I_OPL_PlayStream,
|
||||
I_OPL_CloseStream,
|
||||
I_OPL_ShutdownStream,
|
||||
I_OPL_DeviceList,
|
||||
};
|
||||
|
@ -577,6 +577,11 @@ boolean I_SND_LoadFile(void *data, ALenum *format, byte **wavdata,
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean I_SND_InitStream(int device)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static sndfile_t stream;
|
||||
|
||||
static loop_metadata_t loop;
|
||||
@ -661,10 +666,23 @@ static void I_SND_CloseStream(void)
|
||||
CloseFile(&stream);
|
||||
}
|
||||
|
||||
static void I_SND_ShutdownStream(void)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
static const char **I_SND_DeviceList(int *current_device)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
stream_module_t stream_snd_module =
|
||||
{
|
||||
I_SND_InitStream,
|
||||
I_SND_OpenStream,
|
||||
I_SND_FillStream,
|
||||
I_SND_PlayStream,
|
||||
I_SND_CloseStream,
|
||||
I_SND_ShutdownStream,
|
||||
I_SND_DeviceList,
|
||||
};
|
||||
|
202
src/i_sound.c
202
src/i_sound.c
@ -24,6 +24,8 @@
|
||||
#include "i_sound.h"
|
||||
|
||||
#include "doomstat.h"
|
||||
#include "doomtype.h"
|
||||
#include "i_oalstream.h"
|
||||
#include "i_printf.h"
|
||||
#include "i_system.h"
|
||||
#include "m_array.h"
|
||||
@ -44,33 +46,27 @@ static const sound_module_t *sound_modules[] =
|
||||
|
||||
static const sound_module_t *sound_module;
|
||||
|
||||
// Music modules
|
||||
extern music_module_t music_win_module;
|
||||
extern music_module_t music_mac_module;
|
||||
extern music_module_t music_fl_module;
|
||||
extern music_module_t music_oal_module;
|
||||
extern music_module_t music_opl_module;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
music_module_t *module;
|
||||
int num_devices;
|
||||
} music_modules_t;
|
||||
|
||||
static music_modules_t music_modules[] =
|
||||
{
|
||||
static music_module_t *native_midi_module =
|
||||
#if defined(_WIN32)
|
||||
{ &music_win_module, 1 },
|
||||
&music_win_module;
|
||||
#elif defined(__APPLE__)
|
||||
{ &music_mac_module, 1 },
|
||||
&music_mac_module;
|
||||
#else
|
||||
NULL;
|
||||
#endif
|
||||
|
||||
static boolean native_midi;
|
||||
|
||||
static stream_module_t *stream_modules[] =
|
||||
{
|
||||
#if defined(HAVE_FLUIDSYNTH)
|
||||
{ &music_fl_module, 1 },
|
||||
&stream_fl_module,
|
||||
#endif
|
||||
{ &music_opl_module, 1 },
|
||||
&stream_opl_module,
|
||||
};
|
||||
|
||||
static music_module_t *midi_player_module = NULL;
|
||||
stream_module_t *midi_stream_module = NULL;
|
||||
|
||||
static music_module_t *active_module = NULL;
|
||||
|
||||
// haleyjd: safety variables to keep changes to *_card from making
|
||||
@ -488,15 +484,27 @@ int midi_player; // current music module
|
||||
static void MidiPlayerFallback(void)
|
||||
{
|
||||
// Fall back the the first module that initializes, device 0.
|
||||
int i;
|
||||
|
||||
for (i = 0; i < arrlen(music_modules); i++)
|
||||
midi_player = 0;
|
||||
|
||||
if (native_midi_module)
|
||||
{
|
||||
if (music_modules[i].module->I_InitMusic(0))
|
||||
if (native_midi_module->I_InitMusic(0))
|
||||
{
|
||||
midi_player = i;
|
||||
midi_player_module = music_modules[midi_player].module;
|
||||
active_module = midi_player_module;
|
||||
native_midi = true;
|
||||
return;
|
||||
}
|
||||
midi_player = 1;
|
||||
}
|
||||
|
||||
native_midi = false;
|
||||
|
||||
for (int i = 0; i < arrlen(stream_modules); ++i)
|
||||
{
|
||||
if (stream_modules[i]->I_InitStream(0))
|
||||
{
|
||||
midi_player += i;
|
||||
midi_stream_module = stream_modules[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -506,36 +514,52 @@ static void MidiPlayerFallback(void)
|
||||
|
||||
void I_SetMidiPlayer(int device)
|
||||
{
|
||||
int i, accum;
|
||||
|
||||
if (nomusicparm)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
midi_player_module->I_ShutdownMusic();
|
||||
int num_devices = 0;
|
||||
|
||||
for (i = 0, accum = 0; i < arrlen(music_modules); ++i)
|
||||
midi_player = 0;
|
||||
|
||||
if (native_midi_module)
|
||||
{
|
||||
int num_devices = music_modules[i].num_devices;
|
||||
const char **strings = native_midi_module->I_DeviceList(NULL);
|
||||
num_devices = array_size(strings);
|
||||
|
||||
if (device < num_devices)
|
||||
{
|
||||
native_midi_module->I_ShutdownMusic();
|
||||
if (native_midi_module->I_InitMusic(device))
|
||||
{
|
||||
native_midi = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
midi_player = 1;
|
||||
}
|
||||
|
||||
native_midi = false;
|
||||
|
||||
for (int i = 0, accum = num_devices; i < arrlen(stream_modules); ++i)
|
||||
{
|
||||
const char **strings = stream_modules[i]->I_DeviceList(NULL);
|
||||
num_devices = array_size(strings);
|
||||
|
||||
if (device >= accum && device < accum + num_devices)
|
||||
{
|
||||
midi_player_module = music_modules[i].module;
|
||||
midi_player = i;
|
||||
device -= accum;
|
||||
break;
|
||||
midi_player += i;
|
||||
if (stream_modules[i]->I_InitStream(device - accum))
|
||||
{
|
||||
midi_stream_module = stream_modules[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
accum += num_devices;
|
||||
}
|
||||
|
||||
if (midi_player_module->I_InitMusic(device))
|
||||
{
|
||||
active_module = midi_player_module;
|
||||
return;
|
||||
}
|
||||
|
||||
MidiPlayerFallback();
|
||||
}
|
||||
|
||||
@ -551,14 +575,32 @@ boolean I_InitMusic(void)
|
||||
|
||||
music_oal_module.I_InitMusic(0);
|
||||
|
||||
active_module = &music_oal_module;
|
||||
|
||||
I_AtExit(I_ShutdownMusic, true);
|
||||
|
||||
if (midi_player < arrlen(music_modules))
|
||||
int module_index = 0;
|
||||
|
||||
if (native_midi_module)
|
||||
{
|
||||
midi_player_module = music_modules[midi_player].module;
|
||||
if (midi_player_module->I_InitMusic(DEFAULT_MIDI_DEVICE))
|
||||
if (midi_player == 0 &&
|
||||
native_midi_module->I_InitMusic(DEFAULT_MIDI_DEVICE))
|
||||
{
|
||||
active_module = midi_player_module;
|
||||
native_midi = true;
|
||||
return true;
|
||||
}
|
||||
module_index = 1;
|
||||
}
|
||||
|
||||
native_midi = false;
|
||||
|
||||
module_index = midi_player - module_index;
|
||||
|
||||
if (module_index < arrlen(stream_modules))
|
||||
{
|
||||
if (stream_modules[module_index]->I_InitStream(DEFAULT_MIDI_DEVICE))
|
||||
{
|
||||
midi_stream_module = stream_modules[module_index];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -571,7 +613,10 @@ boolean I_InitMusic(void)
|
||||
void I_ShutdownMusic(void)
|
||||
{
|
||||
music_oal_module.I_ShutdownMusic();
|
||||
midi_player_module->I_ShutdownMusic();
|
||||
if (native_midi)
|
||||
{
|
||||
native_midi_module->I_ShutdownMusic();
|
||||
}
|
||||
}
|
||||
|
||||
void I_SetMusicVolume(int volume)
|
||||
@ -601,27 +646,16 @@ boolean IsMus(byte *mem, int len)
|
||||
|
||||
void *I_RegisterSong(void *data, int size)
|
||||
{
|
||||
if (IsMus(data, size) || IsMid(data, size))
|
||||
{
|
||||
active_module = midi_player_module;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not a MIDI file. We have to shutdown the OPL module due to
|
||||
// implementation details.
|
||||
|
||||
if (midi_player_module == &music_opl_module)
|
||||
{
|
||||
midi_player_module->I_ShutdownMusic();
|
||||
}
|
||||
|
||||
// Try to open file with SndFile or XMP.
|
||||
|
||||
active_module = &music_oal_module;
|
||||
|
||||
if (native_midi && (IsMid(data, size) || IsMus(data, size)))
|
||||
{
|
||||
active_module = native_midi_module;
|
||||
}
|
||||
|
||||
void *result = active_module->I_RegisterSong(data, size);
|
||||
active_module->I_SetMusicVolume(snd_MusicVolume);
|
||||
return active_module->I_RegisterSong(data, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
void I_PlaySong(void *handle, boolean looping)
|
||||
@ -653,25 +687,41 @@ const char **I_DeviceList(int *current_device)
|
||||
|
||||
*current_device = 0;
|
||||
|
||||
for (int i = 0; i < arrlen(music_modules); ++i)
|
||||
{
|
||||
const char **module_devices = NULL;
|
||||
int module_device;
|
||||
int module_index = 0;
|
||||
|
||||
module_devices = music_modules[i].module->I_DeviceList(&module_device);
|
||||
|
||||
if (midi_player == i)
|
||||
if (native_midi_module)
|
||||
{
|
||||
*current_device = array_size(devices) + module_device;
|
||||
int device;
|
||||
const char **strings = native_midi_module->I_DeviceList(&device);
|
||||
|
||||
if (midi_player == module_index)
|
||||
{
|
||||
*current_device = device;
|
||||
}
|
||||
|
||||
music_modules[i].num_devices = array_size(module_devices);
|
||||
|
||||
for (int k = 0; k < array_size(module_devices); ++k)
|
||||
for (int i = 0; i < array_size(strings); ++i)
|
||||
{
|
||||
array_push(devices, module_devices[k]);
|
||||
array_push(devices, strings[i]);
|
||||
}
|
||||
array_free(module_devices);
|
||||
module_index = 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < arrlen(stream_modules); ++i)
|
||||
{
|
||||
int device;
|
||||
const char **strings = stream_modules[i]->I_DeviceList(&device);
|
||||
|
||||
if (midi_player == module_index)
|
||||
{
|
||||
*current_device = array_size(devices) + device;
|
||||
}
|
||||
|
||||
for (int k = 0; k < array_size(strings); ++k)
|
||||
{
|
||||
array_push(devices, strings[k]);
|
||||
}
|
||||
|
||||
module_index++;
|
||||
}
|
||||
|
||||
return devices;
|
||||
|
@ -27,6 +27,8 @@
|
||||
#include "doomtype.h"
|
||||
#include "m_fixed.h"
|
||||
|
||||
struct stream_module_s;
|
||||
|
||||
// when to clip out sounds
|
||||
// Does not fit the large outdoor areas.
|
||||
#define S_CLIPPING_DIST (1200 << FRACBITS)
|
||||
@ -170,8 +172,15 @@ typedef struct
|
||||
const char **(*I_DeviceList)(int *current_device);
|
||||
} music_module_t;
|
||||
|
||||
// Music modules
|
||||
extern music_module_t music_oal_module;
|
||||
extern music_module_t music_win_module;
|
||||
extern music_module_t music_mac_module;
|
||||
|
||||
extern int midi_player;
|
||||
|
||||
extern struct stream_module_s *midi_stream_module;
|
||||
|
||||
boolean I_InitMusic(void);
|
||||
void I_ShutdownMusic(void);
|
||||
|
||||
|
@ -51,7 +51,7 @@ enum
|
||||
RESET_TYPE_XG,
|
||||
};
|
||||
|
||||
char *winmm_device = "";
|
||||
const char *winmm_device = "";
|
||||
int winmm_complevel = COMP_STANDARD;
|
||||
int winmm_reset_type = RESET_TYPE_GM;
|
||||
int winmm_reset_delay = 0;
|
||||
@ -104,7 +104,7 @@ static CRITICAL_SECTION CriticalSection;
|
||||
|
||||
#define EMIDI_DEVICE (1U << EMIDI_DEVICE_GENERAL_MIDI)
|
||||
|
||||
static char **winmm_devices = NULL;
|
||||
static const char **winmm_devices = NULL;
|
||||
|
||||
// This is a reduced Windows MIDIEVENT structure for MEVT_F_SHORT
|
||||
// type of events.
|
||||
@ -1888,9 +1888,17 @@ static void I_WIN_ShutdownMusic(void)
|
||||
|
||||
static const char **I_WIN_DeviceList(int *current_device)
|
||||
{
|
||||
const char **devices = NULL;
|
||||
static const char **devices = NULL;
|
||||
|
||||
if (devices)
|
||||
{
|
||||
return devices;
|
||||
}
|
||||
|
||||
if (current_device)
|
||||
{
|
||||
*current_device = 0;
|
||||
}
|
||||
|
||||
GetDevices();
|
||||
|
||||
@ -1902,12 +1910,13 @@ static const char **I_WIN_DeviceList(int *current_device)
|
||||
|
||||
for (int i = 0; i < array_size(winmm_devices); ++i)
|
||||
{
|
||||
array_push(devices, winmm_devices[i]);
|
||||
if (!strncasecmp(winmm_devices[i], winmm_device, MAXPNAMELEN))
|
||||
if (current_device &&
|
||||
!strncasecmp(winmm_devices[i], winmm_device, MAXPNAMELEN))
|
||||
{
|
||||
*current_device = i;
|
||||
}
|
||||
}
|
||||
devices = winmm_devices;
|
||||
return devices;
|
||||
}
|
||||
|
||||
|
41
src/i_xmp.c
41
src/i_xmp.c
@ -59,18 +59,27 @@ static void PrintError(int e)
|
||||
I_Printf(VB_ERROR, "XMP: %s", msg);
|
||||
}
|
||||
|
||||
static boolean I_XMP_OpenStream(void *data, ALsizei size, ALenum *format,
|
||||
ALsizei *freq, ALsizei *frame_size)
|
||||
static boolean I_XMP_InitStream(int device)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
context = xmp_create_context();
|
||||
|
||||
if (!context)
|
||||
{
|
||||
I_Printf(VB_ERROR, "XMP: Failed to create context.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean I_XMP_OpenStream(void *data, ALsizei size, ALenum *format,
|
||||
ALsizei *freq, ALsizei *frame_size)
|
||||
{
|
||||
if (!context)
|
||||
return false;
|
||||
|
||||
int err = 0;
|
||||
|
||||
err = xmp_load_module_from_memory(context, data, (long)size);
|
||||
if (err < 0)
|
||||
{
|
||||
@ -101,26 +110,44 @@ static int I_XMP_FillStream(byte *buffer, int buffer_samples)
|
||||
|
||||
static void I_XMP_PlayStream(boolean looping)
|
||||
{
|
||||
if (!context)
|
||||
return;
|
||||
|
||||
stream_looping = looping;
|
||||
xmp_start_player(context, SND_SAMPLERATE, 0);
|
||||
}
|
||||
|
||||
static void I_XMP_CloseStream(void)
|
||||
{
|
||||
if (context)
|
||||
{
|
||||
if (!context)
|
||||
return;
|
||||
|
||||
xmp_stop_module(context);
|
||||
xmp_end_player(context);
|
||||
xmp_release_module(context);
|
||||
}
|
||||
|
||||
static void I_XMP_ShutdownStream(void)
|
||||
{
|
||||
if (!context)
|
||||
return;
|
||||
|
||||
xmp_free_context(context);
|
||||
context = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static const char **I_XMP_DeviceList(int *current_device)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
stream_module_t stream_xmp_module =
|
||||
{
|
||||
I_XMP_InitStream,
|
||||
I_XMP_OpenStream,
|
||||
I_XMP_FillStream,
|
||||
I_XMP_PlayStream,
|
||||
I_XMP_CloseStream,
|
||||
I_XMP_ShutdownStream,
|
||||
I_XMP_DeviceList,
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user