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:
Roman Fomin 2024-03-01 18:22:04 +07:00 committed by GitHub
parent b3147857e2
commit 7bc4f24c7c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 529 additions and 854 deletions

View File

@ -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)

View File

@ -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)

173
opl/opl.c
View File

@ -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.
@ -232,18 +215,7 @@ void OPL_WriteRegister(int reg, int value)
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_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)

View File

@ -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

View File

@ -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;

View File

@ -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,
};

View File

@ -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

View File

@ -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;
*current_device = 0;
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,
};

View File

@ -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);
}
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);
}
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);
}
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();
}
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)
{
;

View File

@ -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

View File

@ -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

View File

@ -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;
*current_device = 0;
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,
};

View File

@ -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,
};

View File

@ -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 = &music_oal_module;
if (native_midi && (IsMid(data, size) || IsMus(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;
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)
int module_index = 0;
if (native_midi_module)
{
const char **module_devices = NULL;
int module_device;
int device;
const char **strings = native_midi_module->I_DeviceList(&device);
module_devices = music_modules[i].module->I_DeviceList(&module_device);
if (midi_player == i)
if (midi_player == module_index)
{
*current_device = array_size(devices) + module_device;
*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;

View File

@ -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);

View File

@ -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;
*current_device = 0;
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;
}

View File

@ -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)
{
xmp_stop_module(context);
xmp_end_player(context);
xmp_release_module(context);
xmp_free_context(context);
context = NULL;
}
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,
};