jedioutcast/CODE-mp/client/snd_mem.cpp
2013-04-04 16:05:53 -05:00

542 lines
13 KiB
C++

/*****************************************************************************
* name: snd_mem.c
*
* desc: sound caching
*
* $Archive: /MissionPack/code/client/snd_mem.c $
* $Author: Raduffy $
* $Revision: 39 $
* $Modtime: 12/04/00 10:58a $
* $Date: 12/04/00 11:06a $
*
*****************************************************************************/
#include "snd_local.h"
#include "snd_mp3.h"
// Open AL
extern int s_UseOpenAL;
/*
===============================================================================
WAV loading
===============================================================================
*/
static byte *data_p;
static byte *iff_end;
static byte *last_chunk;
static byte *iff_data;
static int iff_chunk_len;
static short GetLittleShort(void)
{
short val = 0;
val = *data_p;
val = val + (*(data_p+1)<<8);
data_p += 2;
return val;
}
static int GetLittleLong(void)
{
int val = 0;
val = *data_p;
val = val + (*(data_p+1)<<8);
val = val + (*(data_p+2)<<16);
val = val + (*(data_p+3)<<24);
data_p += 4;
return val;
}
static void FindNextChunk(char *name)
{
while (1)
{
data_p=last_chunk;
if (data_p >= iff_end)
{ // didn't find the chunk
data_p = NULL;
return;
}
data_p += 4;
iff_chunk_len = GetLittleLong();
if (iff_chunk_len < 0)
{
data_p = NULL;
return;
}
data_p -= 8;
last_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 );
if (!strncmp((char *)data_p, name, 4))
return;
}
}
static void FindChunk(char *name)
{
last_chunk = iff_data;
FindNextChunk (name);
}
/*
============
GetWavinfo
============
*/
static wavinfo_t GetWavinfo (char *name, byte *wav, int wavlength)
{
wavinfo_t info;
Com_Memset (&info, 0, sizeof(info));
if (!wav)
return info;
iff_data = wav;
iff_end = wav + wavlength;
// find "RIFF" chunk
FindChunk("RIFF");
if (!(data_p && !strncmp((char *)data_p+8, "WAVE", 4)))
{
Com_Printf("Missing RIFF/WAVE chunks\n");
return info;
}
// get "fmt " chunk
iff_data = data_p + 12;
// DumpChunks ();
FindChunk("fmt ");
if (!data_p)
{
Com_Printf("Missing fmt chunk\n");
return info;
}
data_p += 8;
info.format = GetLittleShort();
info.channels = GetLittleShort();
info.rate = GetLittleLong();
data_p += 4+2;
info.width = GetLittleShort() / 8;
if (info.format != 1)
{
Com_Printf("Microsoft PCM format only\n");
return info;
}
// find data chunk
FindChunk("data");
if (!data_p)
{
Com_Printf("Missing data chunk\n");
return info;
}
data_p += 4;
info.samples = GetLittleLong () / info.width;
info.dataofs = data_p - wav;
return info;
}
/*
================
ResampleSfx
resample / decimate to the current source rate
================
*/
static
void ResampleSfx (sfx_t *sfx, int iInRate, int iInWidth, byte *pData)
{
int iOutCount;
int iSrcSample;
float fStepScale;
int i;
int iSample;
unsigned int uiSampleFrac, uiFracStep; // uiSampleFrac MUST be unsigned, or large samples (eg music tracks) crash
fStepScale = (float)iInRate / dma.speed; // this is usually 0.5, 1, or 2
// When stepscale is > 1 (we're downsampling), we really ought to run a low pass filter on the samples
iOutCount = (int)(sfx->iSoundLengthInSamples / fStepScale);
sfx->iSoundLengthInSamples = iOutCount;
sfx->pSoundData = (short *) SND_malloc( sfx->iSoundLengthInSamples*2 ,sfx );
sfx->fVolRange = 0;
uiSampleFrac = 0;
uiFracStep = (int)(fStepScale*256);
for (i=0 ; i<sfx->iSoundLengthInSamples ; i++)
{
iSrcSample = uiSampleFrac >> 8;
uiSampleFrac += uiFracStep;
if (iInWidth == 2) {
iSample = LittleShort ( ((short *)pData)[iSrcSample] );
} else {
iSample = (int)( (unsigned char)(pData[iSrcSample]) - 128) << 8;
}
sfx->pSoundData[i] = (short)iSample;
// work out max vol for this sample...
//
if (iSample < 0)
iSample = -iSample;
if (sfx->fVolRange < (iSample >> 8) )
{
sfx->fVolRange = iSample >> 8;
}
}
}
// (MP3 helper func)
//
void S_LoadSound_Finalize(wavinfo_t *info, sfx_t *sfx, byte *data)
{
float stepscale = (float)info->rate / dma.speed;
int len = (int)(info->samples / stepscale);
len *= info->width;
sfx->eSoundCompressionMethod = ct_16;
sfx->iSoundLengthInSamples = info->samples;
ResampleSfx( sfx, info->rate, info->width, data + info->dataofs );
}
// adjust filename for foreign languages and WAV/MP3 issues.
//
// returns qfalse if failed to load, else fills in *pData
//
extern cvar_t *com_buildScript;
static qboolean S_LoadSound_FileLoadAndNameAdjuster(char *psFilename, byte **pData, int *piSize, int iNameStrlen)
{
char *psVoice = strstr(psFilename,"chars");
if (psVoice)
{
// cache foreign voices...
//
if (com_buildScript->integer)
{
fileHandle_t hFile;
//German
strncpy(psVoice,"chr_d",5); // same number of letters as "chars"
FS_FOpenFileRead(psFilename, &hFile, qfalse); //cache the wav
if (!hFile)
{
strcpy(&psFilename[iNameStrlen-3],"mp3"); //not there try mp3
FS_FOpenFileRead(psFilename, &hFile, qfalse); //cache the mp3
}
if (hFile)
{
FS_FCloseFile(hFile);
}
strcpy(&psFilename[iNameStrlen-3],"wav"); //put it back to wav
//French
strncpy(psVoice,"chr_f",5); // same number of letters as "chars"
FS_FOpenFileRead(psFilename, &hFile, qfalse); //cache the wav
if (!hFile)
{
strcpy(&psFilename[iNameStrlen-3],"mp3"); //not there try mp3
FS_FOpenFileRead(psFilename, &hFile, qfalse); //cache the mp3
}
if (hFile)
{
FS_FCloseFile(hFile);
}
strcpy(&psFilename[iNameStrlen-3],"wav"); //put it back to wav
//Spanish
strncpy(psVoice,"chr_e",5); // same number of letters as "chars"
FS_FOpenFileRead(psFilename, &hFile, qfalse); //cache the wav
if (!hFile)
{
strcpy(&psFilename[iNameStrlen-3],"mp3"); //not there try mp3
FS_FOpenFileRead(psFilename, &hFile, qfalse); //cache the mp3
}
if (hFile)
{
FS_FCloseFile(hFile);
}
strcpy(&psFilename[iNameStrlen-3],"wav"); //put it back to wav
strncpy(psVoice,"chars",5); //put it back to chars
}
// account for foreign voices...
//
extern cvar_t* s_language;
if (s_language && stricmp("DEUTSCH",s_language->string)==0)
{
strncpy(psVoice,"chr_d",5); // same number of letters as "chars"
}
else if (s_language && stricmp("FRANCAIS",s_language->string)==0)
{
strncpy(psVoice,"chr_f",5); // same number of letters as "chars"
}
else if (s_language && stricmp("ESPANOL",s_language->string)==0)
{
strncpy(psVoice,"chr_e",5); // same number of letters as "chars"
}
else
{
psVoice = NULL; // use this ptr as a flag as to whether or not we substituted with a foreign version
}
}
*piSize = FS_ReadFile( psFilename, (void **)pData ); // try WAV
if ( !*pData ) {
psFilename[iNameStrlen-3] = 'm';
psFilename[iNameStrlen-2] = 'p';
psFilename[iNameStrlen-1] = '3';
*piSize = FS_ReadFile( psFilename, (void **)pData ); // try MP3
if ( !*pData )
{
//hmmm, not found, ok, maybe we were trying a foreign noise ("arghhhhh.mp3" that doesn't matter?) but it
// was missing? Can't tell really, since both types are now in sound/chars. Oh well, fall back to English for now...
if (psVoice) // were we trying to load foreign?
{
// yep, so fallback to re-try the english...
//
#ifndef FINAL_BUILD
Com_Printf(S_COLOR_YELLOW "Foreign file missing: \"%s\"! (using English...)\n",psFilename);
#endif
strncpy(psVoice,"chars",5);
psFilename[iNameStrlen-3] = 'w';
psFilename[iNameStrlen-2] = 'a';
psFilename[iNameStrlen-1] = 'v';
*piSize = FS_ReadFile( psFilename, (void **)pData ); // try English WAV
if ( !*pData )
{
psFilename[iNameStrlen-3] = 'm';
psFilename[iNameStrlen-2] = 'p';
psFilename[iNameStrlen-1] = '3';
*piSize = FS_ReadFile( psFilename, (void **)pData ); // try English MP3
}
}
if (!*pData)
{
return qfalse; // sod it, give up...
}
}
}
return qtrue;
}
//=============================================================================
/*
==============
S_LoadSound
The filename may be different than sfx->name in the case
of a forced fallback of a player specific sound
==============
*/
qboolean gbInsideLoadSound = qfalse; // important to default to this!!!
static qboolean S_LoadSound_Actual( sfx_t *sfx )
{
byte *data;
short *samples;
wavinfo_t info;
int size;
ALuint Buffer;
// player specific sounds are never directly loaded...
//
if ( sfx->sSoundName[0] == '*') {
return qfalse;
}
// make up a local filename to try wav/mp3 substitutes...
//
char sRootName[MAX_QPATH];
char sLoadName[MAX_QPATH];
COM_StripExtension(sfx->sSoundName, sRootName);
Com_sprintf(sLoadName, MAX_QPATH, "%s.wav", sRootName);
const char *psExt = &sLoadName[strlen(sLoadName)-4];
if (!S_LoadSound_FileLoadAndNameAdjuster(sLoadName, &data, &size, strlen(sLoadName)))
{
return qfalse;
}
SND_TouchSFX(sfx);
sfx->iLastTimeUsed = Com_Milliseconds()+1; // why +1? Hmmm, leave it for now I guess
//=========
if (strnicmp(psExt,".mp3",4)==0)
{
// load MP3 file instead...
//
if (MP3_IsValid(sfx->sSoundName,data, size))
{
int iRawPCMDataSize = MP3_GetUnpackedSize(sfx->sSoundName,data,size);
if (MP3Stream_InitFromFile(sfx, data, size, sfx->sSoundName, iRawPCMDataSize + 2304 /* + 1 MP3 frame size, jic */)
)
{
// Com_DPrintf("(Keeping file \"%s\" as MP3)\n",altname);
}
else
{
// small file, not worth keeping as MP3 since it would increase in size (with MP3 header etc)...
//
Com_DPrintf("S_LoadSound: Unpacking MP3 file \"%s\" to wav.\n",sfx->sSoundName);
//
// unpack and convert into WAV...
//
byte *pbUnpackBuffer = (byte *) Z_Malloc ( iRawPCMDataSize+10 +2304 /* <g> */, TAG_TEMP_WORKSPACE ); // won't return if fails
int iResultBytes = MP3_UnpackRawPCM( sfx->sSoundName, data, size, pbUnpackBuffer );
if (iResultBytes!= iRawPCMDataSize){
Com_Printf("**** MP3 final unpack size %d different to previous value %d\n",iResultBytes,iRawPCMDataSize);
//assert (iResultBytes == iRawPCMDataSize);
}
// fake up a WAV structure so I can use the other post-load sound code such as volume calc for lip-synching
//
// (this is a bit crap really, but it lets me drop through into existing code)...
//
MP3_FakeUpWAVInfo( sfx->sSoundName, data, size, iResultBytes,
// these params are all references...
info.format, info.rate, info.width, info.channels, info.samples, info.dataofs
);
S_LoadSound_Finalize(&info,sfx,pbUnpackBuffer);
// Open AL
if (s_UseOpenAL)
{
// Clear Open AL Error state
alGetError();
// Generate AL Buffer
alGenBuffers(1, &Buffer);
if (alGetError() == AL_NO_ERROR)
{
// Copy audio data to AL Buffer
alBufferData(Buffer, AL_FORMAT_MONO16, sfx->pSoundData, sfx->iSoundLengthInSamples*2, 22050);
if (alGetError() == AL_NO_ERROR)
{
sfx->Buffer = Buffer;
Z_Free(sfx->pSoundData);
sfx->pSoundData = NULL;
}
}
}
Z_Free(pbUnpackBuffer);
}
}
else
{
// MP3_IsValid() will already have printed any errors via Com_Printf at this point...
//
FS_FreeFile (data);
return qfalse;
}
}
else
{
// loading a WAV, presumably...
//=========
info = GetWavinfo( sfx->sSoundName, data, size );
if ( info.channels != 1 ) {
Com_Printf ("%s is a stereo wav file\n", sfx->sSoundName);
FS_FreeFile (data);
return qfalse;
}
#ifdef _DEBUG
if ( info.width == 1 ) {
Com_Printf(S_COLOR_YELLOW "WARNING: %s is an 8 bit wav file\n", sfx->sSoundName);
}
if ( info.rate != 22050 ) {
Com_Printf(S_COLOR_YELLOW "WARNING: %s is not a 22kHz wav file\n", sfx->sSoundName);
}
#endif
samples = (short *)Z_Malloc(info.samples * sizeof(short) * 2, TAG_TEMP_WORKSPACE);
// each of these compression schemes works just fine
// but the 16bit quality is much nicer and with a local
// install assured we can rely upon the sound memory
// manager to do the right thing for us and page
// sound in as needed
//
sfx->eSoundCompressionMethod= ct_16;
sfx->iSoundLengthInSamples = info.samples;
sfx->pSoundData = NULL;
ResampleSfx( sfx, info.rate, info.width, data + info.dataofs );
// Open AL
if (s_UseOpenAL)
{
// Clear Open AL Error State
alGetError();
// Generate AL Buffer
alGenBuffers(1, &Buffer);
if (alGetError() == AL_NO_ERROR)
{
// Copy audio data to AL Buffer
alBufferData(Buffer, AL_FORMAT_MONO16, sfx->pSoundData, sfx->iSoundLengthInSamples*2, 22050);
if (alGetError() == AL_NO_ERROR)
{
// Store AL Buffer in sfx struct, and release sample data
sfx->Buffer = Buffer;
Z_Free(sfx->pSoundData);
sfx->pSoundData = NULL;
}
}
}
Z_Free(samples);
}
FS_FreeFile( data );
return qtrue;
}
qboolean S_LoadSound( sfx_t *sfx )
{
gbInsideLoadSound = qtrue; // !!!!!!!!!!!!!!
qboolean bReturn = S_LoadSound_Actual( sfx );
gbInsideLoadSound = qfalse; // !!!!!!!!!!!!!!
return bReturn;
}