#include "snd_main.h"
#include "snd_modplug.h"
+#ifdef SND_MODPLUG_STATIC
+
+#include <libmodplug/modplug.h>
+qboolean ModPlug_OpenLibrary (void)
+{
+ return true; // statically linked
+}
+void ModPlug_CloseLibrary (void)
+{
+}
+#define modplug_dll 1
+#define qModPlug_Load ModPlug_Load
+#define qModPlug_Unload ModPlug_Unload
+#define qModPlug_Read ModPlug_Read
+#define qModPlug_Seek ModPlug_Seek
+#define qModPlug_GetSettings ModPlug_GetSettings
+#define qModPlug_SetSettings ModPlug_SetSettings
+#define qModPlug_SetMasterVolume ModPlug_SetMasterVolume
+
+#else
// BEGIN SECTION FROM modplug.h
/*
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 mStereoSeparation; /* Stereo separation, 1 - 256 */
+ int mMaxMixChannels; /* Maximum number of mixing channels (polyphony), 32 - 256 */
int mReverbDepth; /* Reverb level 0(quiet)-100(loud) */
int mReverbDelay; /* Reverb delay in ms, usually 40-200ms */
// 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 ModPlugFile* (*qModPlug_Load) (const void* data, int size);
+static void (*qModPlug_Unload) (ModPlugFile* file);
+static int (*qModPlug_Read) (ModPlugFile* file, void* buffer, int size);
+static void (*qModPlug_Seek) (ModPlugFile* file, int millisecond);
+static void (*qModPlug_GetSettings) (ModPlug_Settings* settings);
+static void (*qModPlug_SetSettings) (const ModPlug_Settings* settings);
+typedef void (ModPlug_SetMasterVolume_t) (ModPlugFile* file,unsigned int cvol) ;
+ModPlug_SetMasterVolume_t *qModPlug_SetMasterVolume;
+
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},
+ {"ModPlug_Load", (void **) &qModPlug_Load},
+ {"ModPlug_Unload", (void **) &qModPlug_Unload},
+ {"ModPlug_Read", (void **) &qModPlug_Read},
+ {"ModPlug_Seek", (void **) &qModPlug_Seek},
+ {"ModPlug_GetSettings", (void **) &qModPlug_GetSettings},
+ {"ModPlug_SetSettings", (void **) &qModPlug_SetSettings},
{NULL, NULL}
};
{
const char* dllnames_modplug [] =
{
-#if defined(WIN64)
- "libmodplug64.dll",
-#elif defined(WIN32)
- "libmodplug-0.dll",
+#if defined(WIN32)
+ "libmodplug-1.dll",
"modplug.dll",
#elif defined(MACOSX)
"libmodplug.dylib",
#else
- "libmodplug.so.0",
+ "libmodplug.so.1",
"libmodplug.so",
#endif
NULL
// 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))
+ if(Sys_LoadLibrary (dllnames_modplug, &modplug_dll, modplugfuncs))
{
- Sys_UnloadLibrary (&modplug_dll);
- Con_Printf ("ModPlug support disabled\n");
- return false;
+ qModPlug_SetMasterVolume = (ModPlug_SetMasterVolume_t *) Sys_GetProcAddress(modplug_dll, "ModPlug_SetMasterVolume");
+ if(!qModPlug_SetMasterVolume)
+ Con_Print("Warning: modplug volume control not supported. Try getting a newer version of libmodplug.\n");
+ return true;
}
-
- Con_Printf ("ModPlug support enabled\n");
- return true;
+ else
+ return false;
}
{
Sys_UnloadLibrary (&modplug_dll);
}
+#endif
/*
=================================================================
*/
-#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
+ int buffer_firstframe;
+ int buffer_numframes;
+ unsigned char buffer[STREAM_BUFFERSIZE*4];
} modplug_stream_perchannel_t;
/*
====================
-ModPlug_FetchSound
+ModPlug_GetSamplesFloat
====================
*/
-static const snd_buffer_t* ModPlug_FetchSound (void *sfxfetcher, void **chfetcherpointer, unsigned int *start, unsigned int nbsampleframes)
+static void ModPlug_GetSamplesFloat(channel_t *ch, sfx_t *sfx, int firstsampleframe, int numsampleframes, float *outsamplesfloat)
{
- 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;
+ modplug_stream_perchannel_t* per_ch = (modplug_stream_perchannel_t *)ch->fetcher_data;
+ modplug_stream_persfx_t* per_sfx = (modplug_stream_persfx_t *)sfx->fetcher_data;
+ int newlength, done, ret;
+ int f = sfx->format.width * sfx->format.channels; // bytes per frame
+ short *buf;
+ int i, len;
// 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);
+ per_ch = (modplug_stream_perchannel_t *)Mem_Alloc(snd_mempool, sizeof(*per_ch));
// Open it with the modplugFile API
- per_ch->mf = ModPlug_Load(per_sfx->file, per_sfx->filesize);
+ per_ch->mf = qModPlug_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;
+ // we can't call Con_Printf here, not thread safe
+// Con_Printf("error while reading ModPlug stream \"%s\"\n", per_sfx->name);
+ Mem_Free(per_ch);
+ return;
}
- 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;
- }
+#ifndef SND_MODPLUG_STATIC
+ if(qModPlug_SetMasterVolume)
+#endif
+ qModPlug_SetMasterVolume(per_ch->mf, 512); // max volume, DP scales down!
- real_start = *start;
+ per_ch->bs = 0;
- sb = &per_ch->sb;
- factor = per_sfx->format.width * per_sfx->format.channels;
+ per_ch->buffer_firstframe = 0;
+ per_ch->buffer_numframes = 0;
+ ch->fetcher_data = per_ch;
+ }
- // If the stream buffer can't contain that much samples anyway
- if (nbsampleframes > sb->maxframes)
+ // if the request is too large for our buffer, loop...
+ while (numsampleframes * f > (int)sizeof(per_ch->buffer))
{
- Con_Printf ("ModPlug_FetchSound: stream buffer too small (%u sample frames required)\n", nbsampleframes);
- return NULL;
+ done = sizeof(per_ch->buffer) / f;
+ ModPlug_GetSamplesFloat(ch, sfx, firstsampleframe, done, outsamplesfloat);
+ firstsampleframe += done;
+ numsampleframes -= done;
+ outsamplesfloat += done * sfx->format.channels;
}
- // 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)
+ // 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)
{
- *start = per_ch->sb_offset;
- return sb;
+ // 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;
+ // we don't actually seek - we don't care much about timing on silent mod music streams and looping never happens
+ //qModPlug_Seek(per_ch->mf, firstsampleframe * 1000.0 / sfx->format.speed);
}
- 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)
+ // decompress the file as needed
+ if (firstsampleframe + numsampleframes > per_ch->buffer_firstframe + per_ch->buffer_numframes)
{
- 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)
+ // 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;
+ // decompress as much as we can fit in the buffer
+ newlength = sizeof(per_ch->buffer) - per_ch->buffer_numframes * f;
+ done = 0;
+ while (newlength > done && (ret = qModPlug_Read(per_ch->mf, (void *)((unsigned char *)per_ch->buffer + done), (int)(newlength - done))) > 0)
+ done += ret;
+ // clear the missing space if any
+ if (done < newlength)
{
- 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;
+ memset(per_ch->buffer + done, 0, newlength - done);
+ // 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...
+ sfx->total_length = firstsampleframe + done / f;
+ sfx->loopstart = 0;
+ // can't Con_Printf from this thread
+ //if (newlength != done)
+ // Con_DPrintf("ModPlug_Fetch: wanted: %d, got: %d\n", newlength, done);
}
- */
-
- // 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;
+ // we now have more data in the buffer
+ per_ch->buffer_numframes += done / f;
}
- 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;
+ // convert the sample format for the caller
+ buf = (short *)(per_ch->buffer + (firstsampleframe - per_ch->buffer_firstframe) * f);
+ len = numsampleframes * sfx->format.channels;
+ for (i = 0;i < len;i++)
+ outsamplesfloat[i] = buf[i] * (1.0f / 32768.0f);
}
/*
====================
-ModPlug_FetchEnd
+ModPlug_StopChannel
====================
*/
-static void ModPlug_FetchEnd (void *chfetcherdata)
+static void ModPlug_StopChannel(channel_t *ch)
{
- modplug_stream_perchannel_t* per_ch = (modplug_stream_perchannel_t *)chfetcherdata;
+ modplug_stream_perchannel_t *per_ch = (modplug_stream_perchannel_t *)ch->fetcher_data;
if (per_ch != NULL)
{
// Free the modplug decoder
- ModPlug_Unload (per_ch->mf);
+ qModPlug_Unload(per_ch->mf);
- Mem_Free (per_ch);
+ Mem_Free(per_ch);
}
}
ModPlug_FreeSfx
====================
*/
-static void ModPlug_FreeSfx (void *sfxfetcherdata)
+static void ModPlug_FreeSfx (sfx_t *sfx)
{
- modplug_stream_persfx_t* per_sfx = (modplug_stream_persfx_t *)sfxfetcherdata;
+ modplug_stream_persfx_t* per_sfx = (modplug_stream_persfx_t *)sfx->fetcher_data;
// Free the modplug file
Mem_Free(per_sfx->file);
}
-/*
-====================
-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 };
+static const snd_fetcher_t modplug_fetcher = { ModPlug_GetSamplesFloat, ModPlug_StopChannel, ModPlug_FreeSfx };
/*
if (data == NULL)
return false;
- Con_DPrintf ("Loading ModPlug file \"%s\"\n", filename);
+ if (developer_loading.integer >= 2)
+ Con_Printf ("Loading ModPlug file \"%s\"\n", filename);
- ModPlug_GetSettings(&s);
+ qModPlug_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);
+ qModPlug_SetSettings(&s);
// Open it with the modplugFile API
- if (!(mf = ModPlug_Load (data, filesize)))
+ if (!(mf = qModPlug_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);
+#ifndef SND_MODPLUG_STATIC
+ if(qModPlug_SetMasterVolume)
+#endif
+ qModPlug_SetMasterVolume(mf, 512); // max volume, DP scales down!
+
+ if (developer_loading.integer >= 2)
+ Con_Printf ("\"%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 += sizeof(*per_sfx);
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->format.speed = 44100; // modplug always works at that rate
+ sfx->format.width = 2; // We always work with 16 bits samples
+ sfx->format.channels = 2; // stereo rulez ;) (MAYBE default to mono because Amiga MODs sound better then?)
sfx->fetcher_data = per_sfx;
sfx->fetcher = &modplug_fetcher;
sfx->flags |= SFXFLAG_STREAMED;
- sfx->total_length = 2147384647; // they always loop
+ 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
return true;