From ac8080688588aba1ab7934f7ba0c2ffea0c8eb91 Mon Sep 17 00:00:00 2001 From: cloudwalk Date: Tue, 16 Jun 2020 13:41:46 +0000 Subject: [PATCH] Implement XMP module sound support This implements support for libxmp in the engine. It will dlopen by default and is therefore not a strict dependency. Implementation by nyov: https://gitlab.com/xonotic/darkplaces/-/merge_requests/8 Updated for darkplaces trunk/master by nico: https://gitlab.com/xonotic/darkplaces/-/merge_requests/88 git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@12682 d7cf8633-e32d-0410-b094-e92efae38249 --- BSDmakefile | 12 + makefile | 18 ++ makefile.inc | 21 +- quakedef.h | 3 + snd_main.c | 7 + snd_mem.c | 17 ++ snd_xmp.c | 674 +++++++++++++++++++++++++++++++++++++++++++++++++++ snd_xmp.h | 29 +++ 8 files changed, 771 insertions(+), 10 deletions(-) create mode 100644 snd_xmp.c create mode 100644 snd_xmp.h diff --git a/BSDmakefile b/BSDmakefile index 0c5e55cc..338293fa 100644 --- a/BSDmakefile +++ b/BSDmakefile @@ -28,6 +28,7 @@ DP_LINK_JPEG?=shared DP_LINK_ODE?=dlopen DP_LINK_CRYPTO?=dlopen DP_LINK_CRYPTO_RIJNDAEL?=dlopen +DP_LINK_XMP?=dlopen ###### Optional features ##### DP_VIDEO_CAPTURE?=enabled @@ -112,6 +113,17 @@ LIB_CRYPTO_RIJNDAEL= CFLAGS_CRYPTO_RIJNDAEL= .endif +# xmp +.if $(DP_LINK_XMP) == "shared" +OBJ_SND_XMP=snd_xmp.o +LIB_SND_XMP=-lxmp +CFLAGS_SND_XMP=-DUSEXMP -DLINK_TO_LIBXMP +.else +OBJ_SND_XMP=snd_xmp.o +LIB_SND_XMP= +CFLAGS_SND_XMP=-DUSEXMP +.endif + .endif diff --git a/makefile b/makefile index 685fb63d..d3b6ce90 100644 --- a/makefile +++ b/makefile @@ -106,6 +106,7 @@ ifeq ($(DP_MAKE_TARGET), linux) DP_LINK_ODE?=dlopen DP_LINK_CRYPTO?=dlopen DP_LINK_CRYPTO_RIJNDAEL?=dlopen + DP_LINK_XMP?=dlopen endif # Mac OS X configuration @@ -134,6 +135,7 @@ ifeq ($(DP_MAKE_TARGET), macosx) DP_LINK_ODE?=dlopen DP_LINK_CRYPTO?=dlopen DP_LINK_CRYPTO_RIJNDAEL?=dlopen + DP_LINK_XMP?=dlopen # on OS X, we don't build the CL by default because it uses deprecated # and not-implemented-in-64bit Carbon @@ -168,6 +170,7 @@ ifeq ($(DP_MAKE_TARGET), sunos) DP_LINK_ODE?=dlopen DP_LINK_CRYPTO?=dlopen DP_LINK_CRYPTO_RIJNDAEL?=dlopen + DP_LINK_XMP?=dlopen endif # BSD configuration @@ -193,6 +196,7 @@ ifeq ($(DP_MAKE_TARGET), bsd) DP_LINK_ODE?=dlopen DP_LINK_CRYPTO?=dlopen DP_LINK_CRYPTO_RIJNDAEL?=dlopen + DP_LINK_XMP?=dlopen endif # Win32 configuration @@ -237,6 +241,7 @@ ifeq ($(DP_MAKE_TARGET), mingw) DP_LINK_ODE?=dlopen DP_LINK_CRYPTO?=dlopen DP_LINK_CRYPTO_RIJNDAEL?=dlopen + DP_LINK_XMP?=dlopen endif # set these to "" if you want to use dynamic loading instead @@ -289,6 +294,19 @@ ifeq ($(DP_LINK_CRYPTO_RIJNDAEL), dlopen) CFLAGS_CRYPTO_RIJNDAEL= endif +# xmp +ifeq ($(DP_LINK_XMP), shared) + OBJ_SND_XMP=snd_xmp.o + LIB_SND_XMP=-lxmp + CFLAGS_SND_XMP=-DUSEXMP -DLINK_TO_LIBXMP +endif +ifeq ($(DP_LINK_XMP), dlopen) + OBJ_SND_XMP=snd_xmp.o + LIB_SND_XMP= + CFLAGS_SND_XMP=-DUSEXMP +endif + + ##### Extra CFLAGS ##### CFLAGS_MAKEDEP?=-MMD diff --git a/makefile.inc b/makefile.inc index 3c4982cc..55d19698 100644 --- a/makefile.inc +++ b/makefile.inc @@ -53,23 +53,23 @@ LIB_SND_NULL= # Open Sound System (Linux, FreeBSD and Solaris) OBJ_SND_OSS=$(OBJ_SND_COMMON) snd_oss.o -LIB_SND_OSS= +LIB_SND_OSS=$(LIB_SND_XMP) # Advanced Linux Sound Architecture (Linux) OBJ_SND_ALSA=$(OBJ_SND_COMMON) snd_alsa.o -LIB_SND_ALSA=-lasound +LIB_SND_ALSA=-lasound $(LIB_SND_XMP) # Core Audio (Mac OS X) OBJ_SND_COREAUDIO=$(OBJ_SND_COMMON) snd_coreaudio.o -LIB_SND_COREAUDIO=-framework CoreAudio +LIB_SND_COREAUDIO=-framework CoreAudio $(LIB_SND_XMP) # BSD / Sun audio API (NetBSD and OpenBSD) OBJ_SND_BSD=$(OBJ_SND_COMMON) snd_bsd.o -LIB_SND_BSD= +LIB_SND_BSD=$(LIB_SND_XMP) # DirectX and Win32 WAVE output (Win32) OBJ_SND_WIN=$(OBJ_SND_COMMON) snd_win.o -LIB_SND_WIN= +LIB_SND_WIN=$(LIB_SND_XMP) ###### Common objects and flags ##### @@ -163,11 +163,11 @@ OBJ_MENU= \ # being linked, because it should be recompiled every time an executable is # built to give the executable a proper date string OBJ_SV= builddate.c sys_linux.o vid_null.o thread_null.o $(OBJ_SND_NULL) $(OBJ_COMMON) -OBJ_SDL= builddate.c sys_sdl.o vid_sdl.o thread_sdl.o $(OBJ_MENU) $(OBJ_SND_COMMON) snd_sdl.o $(OBJ_VIDEO_CAPTURE) $(OBJ_COMMON) +OBJ_SDL= builddate.c sys_sdl.o vid_sdl.o thread_sdl.o $(OBJ_MENU) $(OBJ_SND_COMMON) $(OBJ_SND_XMP) snd_sdl.o $(OBJ_VIDEO_CAPTURE) $(OBJ_COMMON) # Compilation -CFLAGS_COMMON=$(CFLAGS_MAKEDEP) $(CFLAGS_PRELOAD) $(CFLAGS_FS) $(CFLAGS_WARNINGS) $(CFLAGS_LIBZ) $(CFLAGS_LIBJPEG) $(CFLAGS_NET) $(CFLAGS_SDL) -D_FILE_OFFSET_BITS=64 -D__KERNEL_STRICT_NAMES -I../../../ +CFLAGS_COMMON=$(CFLAGS_MAKEDEP) $(CFLAGS_PRELOAD) $(CFLAGS_FS) $(CFLAGS_WARNINGS) $(CFLAGS_LIBZ) $(CFLAGS_LIBJPEG) $(CFLAGS_SND_XMP) $(CFLAGS_NET) $(CFLAGS_SDL) -D_FILE_OFFSET_BITS=64 -D__KERNEL_STRICT_NAMES -I../../../ CFLAGS_CLIENT=-DCONFIG_MENU $(CFLAGS_VIDEO_CAPTURE) CFLAGS_SERVER= CFLAGS_DEBUG=-ggdb @@ -204,9 +204,9 @@ LDFLAGS_RELEASE=$(OPTIM_RELEASE) -DSVNREVISION=`{ test -d .svn && svnversion; } ##### UNIX specific variables ##### -LDFLAGS_UNIXCOMMON=-lm $(LIB_ODE) $(LIB_Z) $(LIB_JPEG) $(LIB_CRYPTO) $(LIB_CRYPTO_RIJNDAEL) +LDFLAGS_UNIXCOMMON=-lm $(LIB_ODE) $(LIB_Z) $(LIB_JPEG) $(LIB_CRYPTO) $(LIB_CRYPTO_RIJNDAEL) $(LIB_SND_XMP) LDFLAGS_UNIXSV_PRELOAD=-lz -ljpeg -lpng -lcurl -LDFLAGS_UNIXSDL_PRELOAD=-lz -ljpeg -lpng -logg -ltheora -lvorbis -lvorbisenc -lvorbisfile -lcurl +LDFLAGS_UNIXSDL_PRELOAD=-lz -ljpeg -lpng -logg -ltheora -lvorbis -lvorbisenc -lvorbisfile -lcurl -lxmp CFLAGS_UNIX_PRELOAD=-DPREFER_PRELOAD LDFLAGS_UNIXSDL=$(SDLCONFIG_LIBS) @@ -257,7 +257,7 @@ WINDRES ?= windres # Link # see LDFLAGS_WINCOMMON in makefile LDFLAGS_WINSV=$(LDFLAGS_WINCOMMON) $(LIB_CRYPTO) $(LIB_CRYPTO_RIJNDAEL) -mconsole -lwinmm -lws2_32 $(LIB_Z) $(LIB_JPEG) -LDFLAGS_WINSDL=$(LDFLAGS_WINCOMMON) $(LIB_CRYPTO) $(LIB_CRYPTO_RIJNDAEL) $(SDLCONFIG_LIBS) -lwinmm -lws2_32 $(LIB_Z) $(LIB_JPEG) +LDFLAGS_WINSDL=$(LDFLAGS_WINCOMMON) $(LIB_CRYPTO) $(LIB_CRYPTO_RIJNDAEL) $(SDLCONFIG_LIBS) -lwinmm -lws2_32 $(LIB_Z) $(LIB_JPEG) $(LIB_SND_XMP) EXE_WINSV=darkplaces-dedicated.exe EXE_WINSDL=darkplaces-sdl.exe EXE_WINSVNEXUIZ=nexuiz-dedicated.exe @@ -414,6 +414,7 @@ prepare : $(CMD_CP) makefile.inc $(BUILD_DIR)/ $(CMD_CP) $(MAKEFILE) $(BUILD_DIR)/ + #this checks USEODE when compiling so it needs the ODE flags as well prvm_cmds.o: prvm_cmds.c $(CHECKLEVEL2) diff --git a/quakedef.h b/quakedef.h index 8b9ea4d8..b614bfab 100644 --- a/quakedef.h +++ b/quakedef.h @@ -428,6 +428,9 @@ extern cvar_t sessionid; # define USE_RWOPS 1 # define LINK_TO_ZLIB 1 # define LINK_TO_LIBVORBIS 1 +#ifdef USEXMP +# define LINK_TO_LIBXMP 1 // nyov: if someone can test with the android NDK compiled libxmp? +#endif # define DP_MOBILETOUCH 1 # define DP_FREETYPE_STATIC 1 #elif TARGET_OS_IPHONE /* must come first because it also defines MACOSX */ diff --git a/snd_main.c b/snd_main.c index 9f8b748e..4a71751b 100644 --- a/snd_main.c +++ b/snd_main.c @@ -23,6 +23,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "snd_main.h" #include "snd_ogg.h" +#include "snd_xmp.h" #include "csprogs.h" #include "cl_collision.h" #include "cdaudio.h" @@ -800,6 +801,9 @@ void S_Init(void) memset(channels, 0, MAX_CHANNELS * sizeof(channel_t)); OGG_OpenLibrary (); +#ifdef USEXMP + XMP_OpenLibrary (); +#endif } @@ -813,6 +817,9 @@ Shutdown and free all resources void S_Terminate (void) { S_Shutdown (); +#ifdef USEXMP + XMP_CloseLibrary (); +#endif OGG_CloseLibrary (); // Free all SFXs diff --git a/snd_mem.c b/snd_mem.c index 5588c642..482b7709 100644 --- a/snd_mem.c +++ b/snd_mem.c @@ -24,6 +24,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "snd_main.h" #include "snd_ogg.h" #include "snd_wav.h" +#ifdef USEXMP +#include "snd_xmp.h" +#endif /* @@ -120,6 +123,13 @@ qboolean S_LoadSound (sfx_t *sfx, qboolean complain) if (OGG_LoadVorbisFile (namebuffer, sfx)) goto loaded; } +#ifdef USEXMP + else if (len >= 1) + { + if (XMP_LoadModFile (namebuffer, sfx)) + goto loaded; + } +#endif } // LadyHavoc: then try without the added sound/ as wav and ogg @@ -139,6 +149,13 @@ qboolean S_LoadSound (sfx_t *sfx, qboolean complain) if (OGG_LoadVorbisFile (namebuffer, sfx)) goto loaded; } +#ifdef USEXMP + else if (len >= 1) + { + if (XMP_LoadModFile (namebuffer, sfx)) + goto loaded; + } +#endif // Can't load the sound! sfx->flags |= SFXFLAG_FILEMISSING; diff --git a/snd_xmp.c b/snd_xmp.c new file mode 100644 index 00000000..7fde6137 --- /dev/null +++ b/snd_xmp.c @@ -0,0 +1,674 @@ +/* + Copyright (C) 2014 nyov + + 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. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +/* + * libxmp is licensed under the terms of the Lesser General Public License 2.1 + */ + + +#include "quakedef.h" +#include "snd_main.h" +#include "snd_xmp.h" + +#ifdef LINK_TO_LIBXMP +#include + +/* libxmp API */ +// Version and player information +#define qxmp_version xmp_version // const char *xmp_version +#define qxmp_vercode xmp_vercode // const unsigned int xmp_vercode +//#define qxmp_get_format_list xmp_get_format_list // char **xmp_get_format_list() +// Context creation +#define qxmp_create_context xmp_create_context // xmp_context xmp_create_context() +#define qxmp_free_context xmp_free_context // void xmp_free_context(xmp_context c) +// Module loading +#define qxmp_test_module xmp_test_module // int xmp_test_module(char *path, struct xmp_test_info *test_info) +//#define qxmp_load_module xmp_load_module // int xmp_load_module(xmp_context c, char *path) +#define qxmp_load_module_from_memory xmp_load_module_from_memory // int xmp_load_module_from_memory(xmp_context c, void *mem, long size) +//#define qxmp_load_module_from_file xmp_load_module_from_file // int xmp_load_module_from_file(xmp_context c, FILE *f, long size) +#define qxmp_release_module xmp_release_module // void xmp_release_module(xmp_context c) +//#define qxmp_scan_module xmp_scan_module // void xmp_scan_module(xmp_context c) +#define qxmp_get_module_info xmp_get_module_info // void xmp_get_module_info(xmp_context c, struct xmp_module_info *info) +// Module playing +#define qxmp_start_player xmp_start_player // int xmp_start_player(xmp_context c, int rate, int format) +#define qxmp_play_frame xmp_play_frame // int xmp_play_frame(xmp_context c) +#define qxmp_play_buffer xmp_play_buffer // int xmp_play_buffer(xmp_context c, void *buffer, int size, int loop) +#define qxmp_get_frame_info xmp_get_frame_info // void xmp_get_frame_info(xmp_context c, struct xmp_frame_info *info) +#define qxmp_end_player xmp_end_player // void xmp_end_player(xmp_context c) +// Player control +//#define qxmp_next_position xmp_next_position // int xmp_next_position(xmp_context c) +//#define qxmp_prev_position xmp_prev_position// int xmp_prev_position(xmp_context c) +//#define qxmp_set_position xmp_set_position // int xmp_set_position(xmp_context c, int pos) +//#define qxmp_stop_module xmp_stop_module // void xmp_stop_module(xmp_context c) +//#define qxmp_restart_module xmp_restart_module // void xmp_restart_module(xmp_context c) +//#define qxmp_seek_time xmp_seek_time // int xmp_seek_time(xmp_context c, int time) +//#define qxmp_channel_mute xmp_channel_mute // int xmp_channel_mute(xmp_context c, int channel, int status) +//#define qxmp_channel_vol xmp_channel_vol // int xmp_channel_vol(xmp_context c, int channel, int vol) +//#define qxmp_inject_event xmp_inject_event // void xmp_inject_event(xmp_context c, int channel, struct xmp_event *event) +// Player parameter setting +//#define qxmp_set_instrument_path xmp_set_instrument_path // int xmp_set_instrument_path(xmp_context c, char *path) +#define qxmp_get_player xmp_get_player // int xmp_get_player(xmp_context c, int param) +#define qxmp_set_player xmp_set_player // int xmp_set_player(xmp_context c, int param, int val) + +#define xmp_dll 1 + +qboolean XMP_OpenLibrary (void) +{ + Con_Printf("Linked against libxmp version %s (0x0%x)\n", qxmp_version, qxmp_vercode); + return true; +} +void XMP_CloseLibrary (void) {} +#else + +/* libxmp ABI */ +/* +================================================================= + + definitions from xmp.h + +================================================================= +*/ + +// constants from libxmp +#define XMP_NAME_SIZE 64 /* Size of module name and type */ + +/* sample format flags */ +#define XMP_FORMAT_8BIT (1 << 0) /* Mix to 8-bit instead of 16 */ +#define XMP_FORMAT_UNSIGNED (1 << 1) /* Mix to unsigned samples */ +#define XMP_FORMAT_MONO (1 << 2) /* Mix to mono instead of stereo */ + +/* player parameters */ +#define XMP_PLAYER_AMP 0 /* Amplification factor */ +#define XMP_PLAYER_MIX 1 /* Stereo mixing */ +#define XMP_PLAYER_INTERP 2 /* Interpolation type */ +#define XMP_PLAYER_DSP 3 /* DSP effect flags */ +#define XMP_PLAYER_FLAGS 4 /* Player flags */ +#define XMP_PLAYER_CFLAGS 5 /* Player flags for current module */ +#define XMP_PLAYER_SMPCTL 6 /* Sample control flags */ +#define XMP_PLAYER_VOLUME 7 /* Player module volume */ +#define XMP_PLAYER_STATE 8 /* Internal player state */ +#define XMP_PLAYER_SMIX_VOLUME 9 /* SMIX volume */ +#define XMP_PLAYER_DEFPAN 10 /* Default pan setting */ + +/* interpolation types */ +#define XMP_INTERP_NEAREST 0 /* Nearest neighbor */ +#define XMP_INTERP_LINEAR 1 /* Linear (default) */ +#define XMP_INTERP_SPLINE 2 /* Cubic spline */ + +/* player state */ +#define XMP_STATE_UNLOADED 0 /* Context created */ +#define XMP_STATE_LOADED 1 /* Module loaded */ +#define XMP_STATE_PLAYING 2 /* Module playing */ + +/* sample flags */ +#define XMP_SMPCTL_SKIP (1 << 0) /* Don't load samples */ + +/* limits */ +//#define XMP_MAX_KEYS 121 /* Number of valid keys */ +//#define XMP_MAX_ENV_POINTS 32 /* Max number of envelope points */ +#define XMP_MAX_MOD_LENGTH 256 /* Max number of patterns in module */ +//#define XMP_MAX_CHANNELS 64 /* Max number of channels in module */ +//#define XMP_MAX_SRATE 49170 /* max sampling rate (Hz) */ +//#define XMP_MIN_SRATE 4000 /* min sampling rate (Hz) */ +//#define XMP_MIN_BPM 20 /* min BPM */ +#define XMP_MAX_FRAMESIZE (5 * XMP_MAX_SRATE * 2 / XMP_MIN_BPM) + +/* error codes */ +#define XMP_END 1 +#define XMP_ERROR_INTERNAL 2 /* Internal error */ +#define XMP_ERROR_FORMAT 3 /* Unsupported module format */ +#define XMP_ERROR_LOAD 4 /* Error loading file */ +#define XMP_ERROR_DEPACK 5 /* Error depacking file */ +#define XMP_ERROR_SYSTEM 6 /* System error */ +#define XMP_ERROR_INVALID 7 /* Invalid parameter */ +#define XMP_ERROR_STATE 8 /* Invalid player state */ + +// types from libxmp +typedef char *xmp_context; + +static const char **qxmp_version; +static const unsigned int *qxmp_vercode; + +struct xmp_channel { +// int pan; /* Channel pan (0x80 is center) */ +// int vol; /* Channel volume */ +//#define XMP_CHANNEL_SYNTH (1 << 0) /* Channel is synthesized */ +//#define XMP_CHANNEL_MUTE (1 << 1) /* Channel is muted */ +// int flg; /* Channel flags */ +}; + +//struct xmp_sequence { +// int entry_point; +// int duration; +//}; + +struct xmp_module { + char name[XMP_NAME_SIZE]; /* Module title */ + char type[XMP_NAME_SIZE]; /* Module format */ + int pat; /* Number of patterns */ + int trk; /* Number of tracks */ + int chn; /* Tracks per pattern */ + int ins; /* Number of instruments */ + int smp; /* Number of samples */ + int spd; /* Initial speed */ + int bpm; /* Initial BPM */ + int len; /* Module length in patterns */ + int rst; /* Restart position */ + int gvl; /* Global volume */ + + struct xmp_pattern **xxp; /* Patterns */ + struct xmp_track **xxt; /* Tracks */ + struct xmp_instrument *xxi; /* Instruments */ + struct xmp_sample *xxs; /* Samples */ + struct xmp_channel xxc[64]; /* Channel info */ + unsigned char xxo[XMP_MAX_MOD_LENGTH]; /* Orders */ +}; + +struct xmp_test_info { +// char name[XMP_NAME_SIZE]; /* Module title */ +// char type[XMP_NAME_SIZE]; /* Module format */ +}; + +struct xmp_module_info { + unsigned char md5[16]; /* MD5 message digest */ + int vol_base; /* Volume scale */ + struct xmp_module *mod; /* Pointer to module data */ + char *comment; /* Comment text, if any */ + int num_sequences; /* Number of valid sequences */ + struct xmp_sequence *seq_data; /* Pointer to sequence data */ +}; + +struct xmp_frame_info { /* Current frame information */ +// int pos; /* Current position */ +// int pattern; /* Current pattern */ +// int row; /* Current row in pattern */ +// int num_rows; /* Number of rows in current pattern */ +// int frame; /* Current frame */ +// int speed; /* Current replay speed */ +// int bpm; /* Current bpm */ +// int time; /* Current module time in ms */ +// int total_time; /* Estimated replay time in ms*/ +// int frame_time; /* Frame replay time in us */ +// void *buffer; /* Pointer to sound buffer */ +// int buffer_size; /* Used buffer size */ +// int total_size; /* Total buffer size */ +// int volume; /* Current master volume */ +// int loop_count; /* Loop counter */ +// int virt_channels; /* Number of virtual channels */ +// int virt_used; /* Used virtual channels */ +// int sequence; /* Current sequence */ +// +// struct xmp_channel_info { /* Current channel information */ +// unsigned int period; /* Sample period */ +// unsigned int position; /* Sample position */ +// short pitchbend; /* Linear bend from base note*/ +// unsigned char note; /* Current base note number */ +// unsigned char instrument; /* Current instrument number */ +// unsigned char sample; /* Current sample number */ +// unsigned char volume; /* Current volume */ +// unsigned char pan; /* Current stereo pan */ +// unsigned char reserved; /* Reserved */ +// struct xmp_event event; /* Current track event */ +// } channel_info[XMP_MAX_CHANNELS]; +}; + +// Functions exported from libxmp +static xmp_context (*qxmp_create_context) (void); +static void (*qxmp_free_context) (xmp_context); +static int (*qxmp_test_module) (char *, struct xmp_test_info *); +//static int (*qxmp_load_module) (xmp_context, char *); +//static void (*qxmp_scan_module) (xmp_context); +static void (*qxmp_release_module) (xmp_context); +static int (*qxmp_start_player) (xmp_context, int, int); +static int (*qxmp_play_frame) (xmp_context); +static int (*qxmp_play_buffer) (xmp_context, void *, int, int); +static void (*qxmp_get_frame_info) (xmp_context, struct xmp_frame_info *); +static void (*qxmp_end_player) (xmp_context); +//static void (*qxmp_inject_event) (xmp_context, int, struct xmp_event *); +static void (*qxmp_get_module_info) (xmp_context, struct xmp_module_info *); +//static char **(*qxmp_get_format_list) (void); // FIXME: did I do this right? +//static int (*qxmp_next_position) (xmp_context); +//static int (*qxmp_prev_position) (xmp_context); +//static int (*qxmp_set_position) (xmp_context, int); +//static void (*qxmp_stop_module) (xmp_context); +//static void (*qxmp_restart_module) (xmp_context); +//static int (*qxmp_seek_time) (xmp_context, int); +//static int (*qxmp_channel_mute) (xmp_context, int, int); +//static int (*qxmp_channel_vol) (xmp_context, int, int); +static int (*qxmp_set_player) (xmp_context, int, int); +static int (*qxmp_get_player) (xmp_context, int); +//static int (*qxmp_set_instrument_path) (xmp_context, char *); +static int (*qxmp_load_module_from_memory) (xmp_context, void *, long); +//static int (*qxmp_load_module_from_file) (xmp_context, void *, long); +//static int (XMP_EXPORT *qxmp_load_module_from_file) (xmp_context, void *, long); + +/* External sample mixer API */ +/* +static int (*qxmp_start_smix) (xmp_context, int, int); +static void (*qxmp_end_smix) (xmp_context); +static int (*qxmp_smix_play_instrument)(xmp_context, int, int, int, int); +static int (*qxmp_smix_play_sample) (xmp_context, int, int, int, int); +static int (*qxmp_smix_channel_pan) (xmp_context, int, int); +static int (*qxmp_smix_load_sample) (xmp_context, int, char *); +static int (*qxmp_smix_release_sample) (xmp_context, int); +// end Functions exported from libxmp +*/ + +/* +================================================================= + + DarkPlaces definitions + +================================================================= +*/ + +static dllfunction_t xmpfuncs[] = +{ + /* libxmp ABI */ + // Version and player information + {"xmp_version", (void **) &qxmp_version}, + {"xmp_vercode", (void **) &qxmp_vercode}, +// {"xmp_get_format_list", (void **) &qxmp_get_format_list}, + // Context creation + {"xmp_create_context", (void **) &qxmp_create_context}, + {"xmp_free_context", (void **) &qxmp_free_context}, + // Module loading + {"xmp_test_module", (void **) &qxmp_test_module}, +// {"xmp_load_module", (void **) &qxmp_load_module}, + {"xmp_load_module_from_memory", (void **) &qxmp_load_module_from_memory}, // since libxmp 4.2.0 +// {"xmp_load_module_from_file", (void **) &qxmp_load_module_from_file}, // since libxmp 4.3.0 + {"xmp_release_module", (void **) &qxmp_release_module}, +// {"xmp_scan_module", (void **) &qxmp_scan_module}, + {"xmp_get_module_info", (void **) &qxmp_get_module_info}, + // Module playing + {"xmp_start_player", (void **) &qxmp_start_player}, + {"xmp_play_frame", (void **) &qxmp_play_frame}, + {"xmp_play_buffer", (void **) &qxmp_play_buffer}, + {"xmp_get_frame_info", (void **) &qxmp_get_frame_info}, + {"xmp_end_player", (void **) &qxmp_end_player}, + // Player control +// {"xmp_next_position", (void **) &qxmp_next_position}, +// {"xmp_prev_position", (void **) &qxmp_prev_position}, +// {"xmp_set_position", (void **) &qxmp_set_position}, +// {"xmp_stop_module", (void **) &qxmp_stop_module}, +// {"xmp_restart_module", (void **) &qxmp_restart_module}, +// {"xmp_seek_time", (void **) &qxmp_seek_time}, +// {"xmp_channel_mute", (void **) &qxmp_channel_mute}, +// {"xmp_channel_vol", (void **) &qxmp_channel_vol}, +// {"xmp_inject_event", (void **) &qxmp_inject_event}, + // Player parameter setting +// {"xmp_set_instrument_path", (void **) &qxmp_set_instrument_path}, + {"xmp_get_player", (void **) &qxmp_get_player}, + {"xmp_set_player", (void **) &qxmp_set_player}, + /* smix */ // for completeness sake only, right now +// {"xmp_start_smix", (void **) &qxmp_start_smix}, +// {"xmp_end_smix", (void **) &qxmp_end_smix}, +// {"xmp_smix_play_instrument", (void **) &qxmp_smix_play_instrument}, +// {"xmp_smix_play_sample", (void **) &qxmp_smix_play_sample}, +// {"xmp_smix_channel_pan", (void **) &qxmp_smix_channel_pan}, +// {"xmp_smix_load_sample", (void **) &qxmp_smix_load_sample}, +// {"xmp_smix_release_sample", (void **) &qxmp_smix_release_sample}, + {NULL, NULL} +}; + +// libxmp DLL handle +static dllhandle_t xmp_dll = NULL; + + +/* +================================================================= + + DLL load & unload + +================================================================= +*/ + +/* +==================== +XMP_OpenLibrary + +Try to load the libxmp DLL +==================== +*/ +qboolean XMP_OpenLibrary (void) +{ + const char* dllnames_xmp [] = + { +#if defined(WIN32) + "libxmp.dll", +#elif defined(MACOSX) // FIXME: untested, please test a mac os build + "libxmp.4.dylib", + "libxmp.dylib", +#else + "libxmp.so.4", + "libxmp.so", +#endif + NULL + }; + + if (xmp_dll) // Already loaded? + return true; + +// COMMANDLINEOPTION: Sound: -noxmp disables xmp module sound support + if (COM_CheckParm("-noxmp")) + return false; + + // Load the DLL + if (Sys_LoadLibrary (dllnames_xmp, &xmp_dll, xmpfuncs)) + { + if (*qxmp_vercode < 0x040200) + { + Con_Printf("Found incompatible XMP library version %s, not loading. (4.2.0 or higher required)\n", *qxmp_version); + Sys_UnloadLibrary (&xmp_dll); + return false; + } + Con_Printf("XMP library loaded, version %s (0x0%x)\n", *qxmp_version, *qxmp_vercode); + return true; + } + else + return false; +} + + +/* +==================== +XMP_CloseLibrary + +Unload the libxmp DLL +==================== +*/ +void XMP_CloseLibrary (void) +{ + Sys_UnloadLibrary (&xmp_dll); +} + +#endif + +/* +================================================================= + + Module file decoding + +================================================================= +*/ + +// Per-sfx data structure +typedef struct +{ + unsigned char *file; + size_t filesize; +} xmp_stream_persfx_t; + +// Per-channel data structure +typedef struct +{ + xmp_context playercontext; + int bs; + int buffer_firstframe; + int buffer_numframes; + unsigned char buffer[STREAM_BUFFERSIZE*4]; +} xmp_stream_perchannel_t; + + +/* +==================== +XMP_GetSamplesFloat +==================== +*/ +static void XMP_GetSamplesFloat(channel_t *ch, sfx_t *sfx, int firstsampleframe, int numsampleframes, float *outsamplesfloat) +{ + int i, len = numsampleframes * sfx->format.channels; + int f = sfx->format.width * sfx->format.channels; // bytes per frame in the buffer + xmp_stream_perchannel_t* per_ch = (xmp_stream_perchannel_t *)ch->fetcher_data; + xmp_stream_persfx_t* per_sfx = (xmp_stream_persfx_t *)sfx->fetcher_data; + const short *buf; + int newlength, done; + unsigned int format = 0; + + // if this channel does not yet have a channel fetcher, make one + if (per_ch == NULL) + { + // allocate a struct to keep track of our file position and buffer + per_ch = (xmp_stream_perchannel_t *)Mem_Alloc(snd_mempool, sizeof(*per_ch)); + + // create an xmp file context + if ((per_ch->playercontext = qxmp_create_context()) == NULL) + { + //Con_Printf("error getting a libxmp file context; while trying to load file \"%s\"\n", filename); + Mem_Free(per_ch); + return; + } + // copy file to xmp + if (qxmp_load_module_from_memory(per_ch->playercontext, (void *)per_sfx->file, (long)per_sfx->filesize) < 0) + { + qxmp_free_context(per_ch->playercontext); + Mem_Free(per_ch); + return; + } + + // start playing the loaded file + if (sfx->format.width == 1) { format |= XMP_FORMAT_8BIT; } // else 16bit + if (sfx->format.channels == 1) { format |= XMP_FORMAT_MONO; } // else stereo + if (qxmp_start_player(per_ch->playercontext, sfx->format.speed, format) < 0) // FIXME: only if speed is in XMP acceptable range, else default to 48khz and let DP mix + { + Mem_Free(per_ch); + return; + } + + per_ch->bs = 0; + per_ch->buffer_firstframe = 0; + per_ch->buffer_numframes = 0; + // attach the struct to our channel + ch->fetcher_data = (void *)per_ch; + + // reset internal xmp state / syncs buffer start with frame start + qxmp_play_buffer(per_ch->playercontext, NULL, 0, 0); + } + + // if the request is too large for our buffer, loop... + while (numsampleframes * f > (int)sizeof(per_ch->buffer)) + { + done = sizeof(per_ch->buffer) / f; + XMP_GetSamplesFloat(ch, sfx, firstsampleframe, done, outsamplesfloat); + firstsampleframe += done; + numsampleframes -= done; + outsamplesfloat += done * sfx->format.channels; + } + + // seek if the request is before the current buffer (loop back) + // seek if the request starts beyond the current buffer by at least one frame (channel was zero volume for a while) + // do not seek if the request overlaps the buffer end at all (expected behavior) + if (per_ch->buffer_firstframe > firstsampleframe || per_ch->buffer_firstframe + per_ch->buffer_numframes < firstsampleframe) + { + // we expect to decode forward from here so this will be our new buffer start + per_ch->buffer_firstframe = firstsampleframe; + per_ch->buffer_numframes = 0; + // no seeking at this time + } + + // render the file to pcm as needed + if (firstsampleframe + numsampleframes > per_ch->buffer_firstframe + per_ch->buffer_numframes) + { + // first slide the buffer back, discarding any data preceding the range we care about + int offset = firstsampleframe - per_ch->buffer_firstframe; + int keeplength = per_ch->buffer_numframes - offset; + if (keeplength > 0) + memmove(per_ch->buffer, per_ch->buffer + offset * sfx->format.width * sfx->format.channels, keeplength * sfx->format.width * sfx->format.channels); + per_ch->buffer_firstframe = firstsampleframe; + per_ch->buffer_numframes -= offset; + // render as much as we can fit in the buffer + newlength = sizeof(per_ch->buffer) - per_ch->buffer_numframes * f; + done = 0; +// while (newlength > done && qxmp_play_buffer(per_ch->playercontext, (void *)((char *)per_ch->buffer + done), (int)(newlength - done), 1) == 0) // don't loop by default (TODO: fix pcm duration calculation first) + while (newlength > done && qxmp_play_buffer(per_ch->playercontext, (void *)((char *)per_ch->buffer + done), (int)(newlength - done), 0) == 0) // loop forever + { + done += (int)(newlength - done); + } + // clear the missing space if any + if (done < newlength) + { + memset(per_ch->buffer + done, 0, newlength - done); + } + // we now have more data in the buffer + per_ch->buffer_numframes += done / f; + } + + // convert the sample format for the caller + buf = (short *)((char *)per_ch->buffer + (firstsampleframe - per_ch->buffer_firstframe) * f); + for (i = 0;i < len;i++) + outsamplesfloat[i] = buf[i] * (1.0f / 32768.0f); +} + +/* +==================== +XMP_StopChannel +==================== +*/ +static void XMP_StopChannel(channel_t *ch) +{ + xmp_stream_perchannel_t *per_ch = (xmp_stream_perchannel_t *)ch->fetcher_data; + if (per_ch != NULL) + { + // stop the player + qxmp_end_player(per_ch->playercontext); + // free the module + qxmp_release_module(per_ch->playercontext); + // free the xmp playercontext + qxmp_free_context(per_ch->playercontext); + Mem_Free(per_ch); + } +} + +/* +==================== +XMP_FreeSfx +==================== +*/ +static void XMP_FreeSfx(sfx_t *sfx) +{ + xmp_stream_persfx_t* per_sfx = (xmp_stream_persfx_t *)sfx->fetcher_data; + // free the complete file we were keeping around + Mem_Free(per_sfx->file); + // free the file information structure + Mem_Free(per_sfx); +} + +static const snd_fetcher_t xmp_fetcher = { XMP_GetSamplesFloat, XMP_StopChannel, XMP_FreeSfx }; + +/* +=============== +XMP_LoadModFile + +Load an XMP module file into memory +=============== +*/ +qboolean XMP_LoadModFile(const char *filename, sfx_t *sfx) +{ + fs_offset_t filesize; + unsigned char *data; + xmp_context xc; + xmp_stream_persfx_t* per_sfx; + struct xmp_module_info mi; + +#ifndef LINK_TO_LIBXMP + if (!xmp_dll) + return false; +#endif + +// COMMANDLINEOPTION: Sound: -noxmp disables xmp module sound support + if (COM_CheckParm("-noxmp")) + return false; + + // Return if already loaded + if (sfx->fetcher != NULL) + return true; + + // Load the file + data = FS_LoadFile(filename, snd_mempool, false, &filesize); + if (data == NULL) + return false; + + // Create an xmp file context + if ((xc = qxmp_create_context()) == NULL) + { + Con_Printf("error creating a libxmp file context; while trying to load file \"%s\"\n", filename); + Mem_Free(data); + return false; + } + + if (developer_loading.integer >= 2) + Con_Printf("Loading Module file (libxmp) \"%s\"\n", filename); + + if (qxmp_load_module_from_memory(xc, (void *)data, (long)filesize) < 0) // Added in libxmp 4.2 + { + Con_Printf("error while trying to load xmp module \"%s\"\n", filename); + qxmp_free_context(xc); + Mem_Free(data); + return false; + } + + if (developer_loading.integer >= 2) + Con_Printf ("\"%s\" will be streamed\n", filename); + + // keep the file around + per_sfx = (xmp_stream_persfx_t *)Mem_Alloc (snd_mempool, sizeof (*per_sfx)); + per_sfx->file = data; + per_sfx->filesize = filesize; + // set dp sfx + sfx->memsize += sizeof(*per_sfx); + sfx->memsize += filesize; // total memory used (including sfx_t and fetcher data) +// sfx->format // format describing the audio data that fetcher->getsamplesfloat shall return + sfx->format.speed = 48000; // default to this sample rate + sfx->format.width = 2; // default to 16 bit samples +// sfx->format.width = 1; // 8-bit + sfx->format.channels = 2; // default to stereo +// sfx->format.channels = 1; // mono + sfx->flags |= SFXFLAG_STREAMED; // cf SFXFLAG_* defines +// sfx->total_length // in (pcm) sample frames + sfx->total_length = 1<<30; // 2147384647; // they always loop (FIXME this breaks after 6 hours, we need support for a real "infinite" value!) + sfx->loopstart = sfx->total_length; // (modplug does it) in sample frames. equals total_length if not looped + sfx->fetcher_data = per_sfx; + sfx->fetcher = &xmp_fetcher; +// sfx->volume_mult // for replay gain (multiplier to apply) +// sfx->volume_peak // for replay gain (highest peak); if set to 0, ReplayGain isn't supported + + qxmp_get_module_info(xc, &mi); + if (developer_loading.integer >= 1) + { + Con_Printf("Decoding module (libxmp):\n" + " Module name : %s\n" + " Module type : %s\n" + " Module length: %i patterns\n" + " Patterns : %i\n" + " Instruments : %i\n" + " Samples : %i\n" + " Channels : %i\n" + " Initial Speed: %i\n" + " Initial BPM : %i\n" + " Restart Pos. : %i\n" + " Global Volume: %i\n", + mi.mod->name, mi.mod->type, + mi.mod->len, mi.mod->pat, mi.mod->ins, mi.mod->smp, mi.mod->chn, + mi.mod->spd, mi.mod->bpm, mi.mod->rst, mi.mod->gvl + ); + } + else if (developer.integer >= 1) + Con_Printf("Decoding module (libxmp) \"%s\" (%s)\n", mi.mod->name, mi.mod->type); + + qxmp_free_context(xc); + return true; +} diff --git a/snd_xmp.h b/snd_xmp.h new file mode 100644 index 00000000..caa68323 --- /dev/null +++ b/snd_xmp.h @@ -0,0 +1,29 @@ +/* + Copyright (C) 2014 nyov + + 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. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#ifndef SND_XMP_H +#define SND_XMP_H + + +qboolean XMP_OpenLibrary (void); +void XMP_CloseLibrary (void); +qboolean XMP_LoadModFile (const char *filename, sfx_t *sfx); + + +#endif -- 2.39.2