]> git.xonotic.org Git - xonotic/darkplaces.git/commitdiff
Implement XMP module sound support
authorcloudwalk <cloudwalk@d7cf8633-e32d-0410-b094-e92efae38249>
Tue, 16 Jun 2020 13:41:46 +0000 (13:41 +0000)
committercloudwalk <cloudwalk@d7cf8633-e32d-0410-b094-e92efae38249>
Tue, 16 Jun 2020 13:41:46 +0000 (13:41 +0000)
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
makefile
makefile.inc
quakedef.h
snd_main.c
snd_mem.c
snd_xmp.c [new file with mode: 0644]
snd_xmp.h [new file with mode: 0644]

index 0c5e55cc47dff9c249cdcd3e5eb1d73c2b05c336..338293fa73b83f1f8c12b5589fde3aeb46d2c331 100644 (file)
@@ -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
 
 
index 685fb63d29fc8deacdbff8ffbd7dd03b1a5f26dc..d3b6ce908ee98a38bb23e3ac78239cbad8d10535 100644 (file)
--- 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
index 3c4982cc85f4046992b8c54f8dc24584cf227e65..55d19698ca36588a168b43cbc0c53b545f1c2f7a 100644 (file)
@@ -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)
index 8b9ea4d8aaf0242c64011bcf0fcbfdfa89541589..b614bfab989e31e63b94d5b0487f0f328a1cc4b7 100644 (file)
@@ -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 */
index 9f8b748ea7b6cf9d93ab52e124fc2be551d5c5cc..4a71751bb2532a949c2d1976d865b2fd9afde132 100644 (file)
@@ -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
index 5588c64295b94f8cc263e6fd12651d55aa67ce0b..482b7709493552d0e6c1f0fdeff86a17479df878 100644 (file)
--- 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 (file)
index 0000000..7fde613
--- /dev/null
+++ b/snd_xmp.c
@@ -0,0 +1,674 @@
+/*
+       Copyright (C) 2014 nyov <nyov@nexnode.net>
+
+       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 <http://www.gnu.org/licenses/>.
+*/
+/*
+ * 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 <xmp.h>
+
+/* 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 (file)
index 0000000..caa6832
--- /dev/null
+++ b/snd_xmp.h
@@ -0,0 +1,29 @@
+/*
+       Copyright (C) 2014 nyov <nyov@nexnode.net>
+
+       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 <http://www.gnu.org/licenses/>.
+*/
+
+
+#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