Add support for libmodplug (when found). Sound files with an extension are now all...
authordivverent <divverent@d7cf8633-e32d-0410-b094-e92efae38249>
Wed, 12 Dec 2007 19:55:17 +0000 (19:55 +0000)
committerdivverent <divverent@d7cf8633-e32d-0410-b094-e92efae38249>
Wed, 12 Dec 2007 19:55:17 +0000 (19:55 +0000)
git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@7788 d7cf8633-e32d-0410-b094-e92efae38249

cd_shared.c
makefile.inc
snd_main.c
snd_mem.c
snd_modplug.c [new file with mode: 0644]

index 03596e7..e6c27a8 100644 (file)
@@ -133,6 +133,8 @@ void CDAudio_Play_byName (const char *trackname, qboolean looping)
        else
        {
                sfx = S_PrecacheSound (va("cdtracks/%s.wav", trackname), false, false);
+               if (sfx == NULL || !S_IsSoundPrecached (sfx))
+                       sfx = S_PrecacheSound (va("cdtracks/%s", trackname), false, false);
        }
        if (sfx != NULL)
        {
index 1b6a950..8e604f5 100644 (file)
@@ -28,7 +28,7 @@ STRIP?=strip
 
 ###### Sound and audio CD #####
 
-OBJ_SND_COMMON=snd_main.o snd_mem.o snd_mix.o snd_ogg.o snd_wav.o
+OBJ_SND_COMMON=snd_main.o snd_mem.o snd_mix.o snd_ogg.o snd_wav.o snd_modplug.o
 
 # No sound
 OBJ_SND_NULL=snd_null.o
index d0b9139..cd9700c 100644 (file)
@@ -759,6 +759,7 @@ void S_Init(void)
        memset(channels, 0, MAX_CHANNELS * sizeof(channel_t));
 
        OGG_OpenLibrary ();
+       ModPlug_OpenLibrary ();
 }
 
 
@@ -772,6 +773,7 @@ Shutdown and free all resources
 void S_Terminate (void)
 {
        S_Shutdown ();
+       ModPlug_CloseLibrary ();
        OGG_CloseLibrary ();
 
        // Free all SFXs
index adb296f..44e33d1 100644 (file)
--- a/snd_mem.c
+++ b/snd_mem.c
@@ -24,6 +24,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 #include "snd_main.h"
 #include "snd_ogg.h"
 #include "snd_wav.h"
+#include "snd_modplug.h"
 
 
 /*
@@ -336,7 +337,10 @@ qboolean S_LoadSound (sfx_t *sfx, qboolean complain)
                        return true;
                if (len >= 4 && !strcasecmp (namebuffer + len - 4, ".wav"))
                        memcpy (namebuffer + len - 3, "ogg", 4);
-               if (OGG_LoadVorbisFile (namebuffer, sfx))
+               if (len >= 4 && !strcasecmp (namebuffer + len - 4, ".ogg"))
+                       if (OGG_LoadVorbisFile (namebuffer, sfx))
+                               return true;
+               if (ModPlug_LoadModPlugFile (namebuffer, sfx))
                        return true;
        }
 
@@ -352,7 +356,10 @@ qboolean S_LoadSound (sfx_t *sfx, qboolean complain)
                return true;
        if (len >= 4 && !strcasecmp (namebuffer + len - 4, ".wav"))
                memcpy (namebuffer + len - 3, "ogg", 4);
-       if (OGG_LoadVorbisFile (namebuffer, sfx))
+       if (len >= 4 && !strcasecmp (namebuffer + len - 4, ".ogg"))
+               if (OGG_LoadVorbisFile (namebuffer, sfx))
+                       return true;
+       if (ModPlug_LoadModPlugFile (namebuffer, sfx))
                return true;
 
        // Can't load the sound!
diff --git a/snd_modplug.c b/snd_modplug.c
new file mode 100644 (file)
index 0000000..81f00b6
--- /dev/null
@@ -0,0 +1,473 @@
+/*
+       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;
+}