--- /dev/null
+/*
+ Copyright (C) 2003-2005 Mathieu Olivier
+
+ 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, write to:
+
+ Free Software Foundation, Inc.
+ 59 Temple Place - Suite 330
+ Boston, MA 02111-1307, USA
+
+*/
+
+
+#include "quakedef.h"
+#include "snd_main.h"
+#include "snd_modplug.h"
+
+// BEGIN SECTION FROM modplug.h
+
+ /*
+ * This source code is public domain.
+ *
+ * Authors: Kenton Varda <temporal@gauge3d.org> (C interface wrapper)
+ */
+
+ enum _ModPlug_Flags
+ {
+ MODPLUG_ENABLE_OVERSAMPLING = 1 << 0, /* Enable oversampling (*highly* recommended) */
+ MODPLUG_ENABLE_NOISE_REDUCTION = 1 << 1, /* Enable noise reduction */
+ MODPLUG_ENABLE_REVERB = 1 << 2, /* Enable reverb */
+ MODPLUG_ENABLE_MEGABASS = 1 << 3, /* Enable megabass */
+ MODPLUG_ENABLE_SURROUND = 1 << 4 /* Enable surround sound. */
+ };
+
+ enum _ModPlug_ResamplingMode
+ {
+ MODPLUG_RESAMPLE_NEAREST = 0, /* No interpolation (very fast, extremely bad sound quality) */
+ MODPLUG_RESAMPLE_LINEAR = 1, /* Linear interpolation (fast, good quality) */
+ MODPLUG_RESAMPLE_SPLINE = 2, /* Cubic spline interpolation (high quality) */
+ MODPLUG_RESAMPLE_FIR = 3 /* 8-tap fir filter (extremely high quality) */
+ };
+
+ typedef struct _ModPlug_Settings
+ {
+ int mFlags; /* One or more of the MODPLUG_ENABLE_* flags above, bitwise-OR'ed */
+
+ /* Note that ModPlug always decodes sound at 44100kHz, 32 bit, stereo and then
+ * down-mixes to the settings you choose. */
+ int mChannels; /* Number of channels - 1 for mono or 2 for stereo */
+ int mBits; /* Bits per sample - 8, 16, or 32 */
+ int mFrequency; /* Sampling rate - 11025, 22050, or 44100 */
+ int mResamplingMode; /* One of MODPLUG_RESAMPLE_*, above */
+
+ int mReverbDepth; /* Reverb level 0(quiet)-100(loud) */
+ int mReverbDelay; /* Reverb delay in ms, usually 40-200ms */
+ int mBassAmount; /* XBass level 0(quiet)-100(loud) */
+ int mBassRange; /* XBass cutoff in Hz 10-100 */
+ int mSurroundDepth; /* Surround level 0(quiet)-100(heavy) */
+ int mSurroundDelay; /* Surround delay in ms, usually 5-40ms */
+ int mLoopCount; /* Number of times to loop. Zero prevents looping.
+ -1 loops forever. */
+ } ModPlug_Settings;
+
+ struct _ModPlugFile;
+ typedef struct _ModPlugFile ModPlugFile;
+
+// END SECTION FROM modplug.h
+
+static ModPlugFile* (*ModPlug_Load) (const void* data, int size);
+static void (*ModPlug_Unload) (ModPlugFile* file);
+static int (*ModPlug_Read) (ModPlugFile* file, void* buffer, int size);
+static void (*ModPlug_Seek) (ModPlugFile* file, int millisecond);
+static void (*ModPlug_GetSettings) (ModPlug_Settings* settings);
+static void (*ModPlug_SetSettings) (const ModPlug_Settings* settings);
+
+static dllfunction_t modplugfuncs[] =
+{
+ {"ModPlug_Load", (void **) &ModPlug_Load},
+ {"ModPlug_Unload", (void **) &ModPlug_Unload},
+ {"ModPlug_Read", (void **) &ModPlug_Read},
+ {"ModPlug_Seek", (void **) &ModPlug_Seek},
+ {"ModPlug_GetSettings", (void **) &ModPlug_GetSettings},
+ {"ModPlug_SetSettings", (void **) &ModPlug_SetSettings},
+ {NULL, NULL}
+};
+
+// Handles for the modplug and modplugfile DLLs
+static dllhandle_t modplug_dll = NULL;
+
+/*
+=================================================================
+
+ DLL load & unload
+
+=================================================================
+*/
+
+/*
+====================
+ModPlug_OpenLibrary
+
+Try to load the modplugFile DLL
+====================
+*/
+qboolean ModPlug_OpenLibrary (void)
+{
+ const char* dllnames_modplug [] =
+ {
+#if defined(WIN64)
+ "libmodplug64.dll",
+#elif defined(WIN32)
+ "libmodplug-0.dll",
+ "modplug.dll",
+#elif defined(MACOSX)
+ "libmodplug.dylib",
+#else
+ "libmodplug.so.0",
+ "libmodplug.so",
+#endif
+ NULL
+ };
+
+ // Already loaded?
+ if (modplug_dll)
+ return true;
+
+// COMMANDLINEOPTION: Sound: -nomodplug disables modplug sound support
+ if (COM_CheckParm("-nomodplug"))
+ return false;
+
+ // Load the DLLs
+ // We need to load both by hand because some OSes seem to not load
+ // the modplug DLL automatically when loading the modplugFile DLL
+ if (! Sys_LoadLibrary (dllnames_modplug, &modplug_dll, modplugfuncs))
+ {
+ Sys_UnloadLibrary (&modplug_dll);
+ Con_Printf ("ModPlug support disabled\n");
+ return false;
+ }
+
+ Con_Printf ("ModPlug support enabled\n");
+ return true;
+}
+
+
+/*
+====================
+ModPlug_CloseLibrary
+
+Unload the modplugFile DLL
+====================
+*/
+void ModPlug_CloseLibrary (void)
+{
+ Sys_UnloadLibrary (&modplug_dll);
+}
+
+
+/*
+=================================================================
+
+ modplug decoding
+
+=================================================================
+*/
+
+#define STREAM_BUFFER_DURATION 1.5f // 1.5 sec
+#define STREAM_BUFFER_SIZE(format_ptr) ((int)(ceil (STREAM_BUFFER_DURATION * ((format_ptr)->speed * (format_ptr)->width * (format_ptr)->channels))))
+// We work with 1 sec sequences, so this buffer must be able to contain
+// 1 sec of sound of the highest quality (48 KHz, 16 bit samples, stereo)
+static unsigned char resampling_buffer [48000 * 2 * 2];
+
+
+// Per-sfx data structure
+typedef struct
+{
+ unsigned char *file;
+ size_t filesize;
+ snd_format_t format;
+ unsigned int total_length;
+ char name[128];
+ sfx_t *sfx;
+} modplug_stream_persfx_t;
+
+// Per-channel data structure
+typedef struct
+{
+ ModPlugFile *mf;
+ unsigned int sb_offset;
+ int bs;
+ snd_buffer_t sb; // must be at the end due to its dynamically allocated size
+} modplug_stream_perchannel_t;
+
+
+/*
+====================
+ModPlug_FetchSound
+====================
+*/
+static const snd_buffer_t* ModPlug_FetchSound (void *sfxfetcher, void **chfetcherpointer, unsigned int *start, unsigned int nbsampleframes)
+{
+ modplug_stream_perchannel_t* per_ch = (modplug_stream_perchannel_t *)*chfetcherpointer;
+ modplug_stream_persfx_t* per_sfx = (modplug_stream_persfx_t *)sfxfetcher;
+ snd_buffer_t* sb;
+ int newlength, done, ret, bigendian;
+ unsigned int real_start;
+ unsigned int factor;
+
+ // If there's no fetcher structure attached to the channel yet
+ if (per_ch == NULL)
+ {
+ size_t buff_len, memsize;
+ snd_format_t sb_format;
+
+ sb_format.speed = snd_renderbuffer->format.speed;
+ sb_format.width = per_sfx->format.width;
+ sb_format.channels = per_sfx->format.channels;
+
+ buff_len = STREAM_BUFFER_SIZE(&sb_format);
+ memsize = sizeof (*per_ch) - sizeof (per_ch->sb.samples) + buff_len;
+ per_ch = (modplug_stream_perchannel_t *)Mem_Alloc (snd_mempool, memsize);
+
+ // Open it with the modplugFile API
+ per_ch->mf = ModPlug_Load(per_sfx->file, per_sfx->filesize);
+ if (!per_ch->mf)
+ {
+ Con_Printf("error while reading ModPlug stream \"%s\"\n", per_sfx->name);
+ Mem_Free (per_ch);
+ return NULL;
+ }
+ per_ch->bs = 0;
+
+ per_ch->sb_offset = 0;
+ per_ch->sb.format = sb_format;
+ per_ch->sb.nbframes = 0;
+ per_ch->sb.maxframes = buff_len / (per_ch->sb.format.channels * per_ch->sb.format.width);
+
+ *chfetcherpointer = per_ch;
+ }
+
+ real_start = *start;
+
+ sb = &per_ch->sb;
+ factor = per_sfx->format.width * per_sfx->format.channels;
+
+ // If the stream buffer can't contain that much samples anyway
+ if (nbsampleframes > sb->maxframes)
+ {
+ Con_Printf ("ModPlug_FetchSound: stream buffer too small (%u sample frames required)\n", nbsampleframes);
+ return NULL;
+ }
+
+ // If the data we need has already been decompressed in the sfxbuffer, just return it
+ if (per_ch->sb_offset <= real_start && per_ch->sb_offset + sb->nbframes >= real_start + nbsampleframes)
+ {
+ *start = per_ch->sb_offset;
+ return sb;
+ }
+
+ newlength = (int)(per_ch->sb_offset + sb->nbframes) - real_start;
+
+ // If we need to skip some data before decompressing the rest, or if the stream has looped
+ if (newlength < 0 || per_ch->sb_offset > real_start)
+ {
+ unsigned int time_start;
+ unsigned int modplug_start;
+
+ /*
+ MODs loop on their own, so any position is valid!
+ if (real_start > (unsigned int)per_sfx->total_length)
+ {
+ Con_Printf ("ModPlug_FetchSound: asked for a start position after the end of the sfx! (%u > %u)\n",
+ real_start, per_sfx->total_length);
+ return NULL;
+ }
+ */
+
+ // We work with 200ms (1/5 sec) steps to avoid rounding errors
+ time_start = real_start * 5 / snd_renderbuffer->format.speed;
+ modplug_start = time_start * (1000 / 5);
+
+ Con_DPrintf("warning: mod file needed to seek (to %d)\n", modplug_start);
+
+ ModPlug_Seek(per_ch->mf, modplug_start);
+ sb->nbframes = 0;
+
+ real_start = (float)modplug_start / 1000 * snd_renderbuffer->format.speed;
+ if (*start - real_start + nbsampleframes > sb->maxframes)
+ {
+ Con_Printf ("ModPlug_FetchSound: stream buffer too small after seek (%u sample frames required)\n",
+ *start - real_start + nbsampleframes);
+ per_ch->sb_offset = real_start;
+ return NULL;
+ }
+ }
+ // Else, move forward the samples we need to keep in the sound buffer
+ else
+ {
+ memmove (sb->samples, sb->samples + (real_start - per_ch->sb_offset) * factor, newlength * factor);
+ sb->nbframes = newlength;
+ }
+
+ per_ch->sb_offset = real_start;
+
+ // We add exactly 1 sec of sound to the buffer:
+ // 1- to ensure we won't lose any sample during the resampling process
+ // 2- to force one call to ModPlug_FetchSound per second to regulate the workload
+ if (sb->format.speed + sb->nbframes > sb->maxframes)
+ {
+ Con_Printf ("ModPlug_FetchSound: stream buffer overflow (%u sample frames / %u)\n",
+ sb->format.speed + sb->nbframes, sb->maxframes);
+ return NULL;
+ }
+ newlength = per_sfx->format.speed * factor; // -> 1 sec of sound before resampling
+ if(newlength > (int)sizeof(resampling_buffer))
+ newlength = sizeof(resampling_buffer);
+
+ // Decompress in the resampling_buffer
+#if BYTE_ORDER == BIG_ENDIAN
+ bigendian = 1;
+#else
+ bigendian = 0;
+#endif
+ done = 0;
+ while ((ret = ModPlug_Read (per_ch->mf, (char *)&resampling_buffer[done], (int)(newlength - done))) > 0)
+ done += ret;
+ if(done < newlength)
+ {
+ // Argh. We didn't get as many samples as we wanted. Probably
+ // libmodplug forgot what mLoopCount==-1 means... basically, this means
+ // we can't loop like this. Try to let DP fix it later...
+ per_sfx->sfx->total_length = (real_start + ((size_t)done / (size_t)factor));
+ per_sfx->sfx->loopstart = 0;
+
+ if(newlength != done)
+ Con_DPrintf("ModPlug_Fetch: wanted: %d, got: %d\n", newlength, done);
+ }
+
+ Snd_AppendToSndBuffer (sb, resampling_buffer, (size_t)done / (size_t)factor, &per_sfx->format);
+
+ *start = per_ch->sb_offset;
+ return sb;
+}
+
+
+/*
+====================
+ModPlug_FetchEnd
+====================
+*/
+static void ModPlug_FetchEnd (void *chfetcherdata)
+{
+ modplug_stream_perchannel_t* per_ch = (modplug_stream_perchannel_t *)chfetcherdata;
+
+ if (per_ch != NULL)
+ {
+ // Free the modplug decoder
+ ModPlug_Unload (per_ch->mf);
+
+ Mem_Free (per_ch);
+ }
+}
+
+
+/*
+====================
+ModPlug_FreeSfx
+====================
+*/
+static void ModPlug_FreeSfx (void *sfxfetcherdata)
+{
+ modplug_stream_persfx_t* per_sfx = (modplug_stream_persfx_t *)sfxfetcherdata;
+
+ // Free the modplug file
+ Mem_Free(per_sfx->file);
+
+ // Free the stream structure
+ Mem_Free(per_sfx);
+}
+
+
+/*
+====================
+ModPlug_GetFormat
+====================
+*/
+static const snd_format_t* ModPlug_GetFormat (sfx_t* sfx)
+{
+ modplug_stream_persfx_t* per_sfx = (modplug_stream_persfx_t *)sfx->fetcher_data;
+ return &per_sfx->format;
+}
+
+static const snd_fetcher_t modplug_fetcher = { ModPlug_FetchSound, ModPlug_FetchEnd, ModPlug_FreeSfx, ModPlug_GetFormat };
+
+
+/*
+====================
+ModPlug_LoadmodplugFile
+
+Load an modplug file into memory
+====================
+*/
+qboolean ModPlug_LoadModPlugFile (const char *filename, sfx_t *sfx)
+{
+ unsigned char *data;
+ fs_offset_t filesize;
+ ModPlugFile *mf;
+ modplug_stream_persfx_t* per_sfx;
+ ModPlug_Settings s;
+
+ if (!modplug_dll)
+ return false;
+
+ // Already loaded?
+ if (sfx->fetcher != NULL)
+ return true;
+
+ // Load the file
+ data = FS_LoadFile (filename, snd_mempool, false, &filesize);
+ if (data == NULL)
+ return false;
+
+ Con_DPrintf ("Loading ModPlug file \"%s\"\n", filename);
+
+ ModPlug_GetSettings(&s);
+ s.mFlags = MODPLUG_ENABLE_OVERSAMPLING | MODPLUG_ENABLE_NOISE_REDUCTION | MODPLUG_ENABLE_REVERB;
+ s.mChannels = 2;
+ s.mBits = 16;
+ s.mFrequency = 44100;
+ s.mResamplingMode = MODPLUG_RESAMPLE_SPLINE;
+ s.mLoopCount = -1;
+ ModPlug_SetSettings(&s);
+
+ // Open it with the modplugFile API
+ if (!(mf = ModPlug_Load (data, filesize)))
+ {
+ Con_Printf ("error while opening ModPlug file \"%s\"\n", filename);
+ Mem_Free(data);
+ return false;
+ }
+
+ Con_DPrintf ("\"%s\" will be streamed\n", filename);
+ per_sfx = (modplug_stream_persfx_t *)Mem_Alloc (snd_mempool, sizeof (*per_sfx));
+ strlcpy(per_sfx->name, sfx->name, sizeof(per_sfx->name));
+ sfx->memsize += sizeof (*per_sfx);
+ per_sfx->file = data;
+ per_sfx->filesize = filesize;
+ sfx->memsize += filesize;
+
+ per_sfx->format.speed = 44100; // modplug always works at that rate
+ per_sfx->format.width = 2; // We always work with 16 bits samples
+ per_sfx->format.channels = 2; // stereo rulez ;) (MAYBE default to mono because Amiga MODs sound better then?)
+ per_sfx->sfx = sfx;
+
+ sfx->fetcher_data = per_sfx;
+ sfx->fetcher = &modplug_fetcher;
+ sfx->flags |= SFXFLAG_STREAMED;
+ sfx->total_length = 2147384647; // they always loop
+ sfx->loopstart = sfx->total_length; // modplug does it
+
+ return true;
+}