--- /dev/null
+/*
+ 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) // FIXME: untested, please test a windows build
+ "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
+ // (even if built in; to prefer ModPlug if available)
+ 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"
+ "\tModule name : %s\n"
+ "\tModule type : %s\n"
+ "\tModule length: %i patterns\n"
+ "\tPatterns : %i\n"
+ "\tInstruments : %i\n"
+ "\tSamples : %i\n"
+ "\tChannels : %i\n"
+ "\tInitial Speed: %i\n"
+ "\tInitial BPM : %i\n"
+ "\tRestart Pos. : %i\n"
+ "\tGlobal 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
+ Con_Printf("Decoding module (libxmp) \"%s\" (%s)\n", mi.mod->name, mi.mod->type);
+
+ qxmp_free_context(xc);
+ return true;
+}