From 89bf9ac7f13b2308dd89291f15efc8a299c3e783 Mon Sep 17 00:00:00 2001 From: divverent Date: Wed, 12 Dec 2007 19:55:17 +0000 Subject: [PATCH] Add support for libmodplug (when found). Sound files with an extension are now all eligible for modplug, if neither vorbis nor wav recognizes them. CD tracks now try to append the .wav and .ogg extension first, and if that fails, the file name is used as is - but tracker extensions need to be explicitly stated. Example: "cd loop Ending.umx" git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@7788 d7cf8633-e32d-0410-b094-e92efae38249 --- cd_shared.c | 2 + makefile.inc | 2 +- snd_main.c | 2 + snd_mem.c | 11 +- snd_modplug.c | 473 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 487 insertions(+), 3 deletions(-) create mode 100644 snd_modplug.c diff --git a/cd_shared.c b/cd_shared.c index 03596e78..e6c27a88 100644 --- a/cd_shared.c +++ b/cd_shared.c @@ -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) { diff --git a/makefile.inc b/makefile.inc index 1b6a9506..8e604f55 100644 --- a/makefile.inc +++ b/makefile.inc @@ -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 diff --git a/snd_main.c b/snd_main.c index d0b91390..cd9700ce 100644 --- a/snd_main.c +++ b/snd_main.c @@ -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 diff --git a/snd_mem.c b/snd_mem.c index adb296ff..44e33d16 100644 --- 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 index 00000000..81f00b62 --- /dev/null +++ b/snd_modplug.c @@ -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 (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; +} -- 2.39.2