/* Copyright (C) 1997-2001 Id Software, Inc. 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 the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // snd_mem.c: sound caching #include "cl_local.h" #include "snd_local.h" wavinfo_t s_info; #if USE_SNDDMA /* ================ ResampleSfx ================ */ static sfxcache_t *ResampleSfx( sfx_t *sfx ) { int outcount; int srcsample; float stepscale; int i; int samplefrac, fracstep; sfxcache_t *sc; stepscale = ( float )s_info.rate / dma.speed; // this is usually 0.5, 1, or 2 outcount = s_info.samples / stepscale; if( !outcount ) { Com_DPrintf( "%s resampled to zero length\n", s_info.name ); return NULL; } sc = sfx->cache = S_Malloc( outcount * s_info.width + sizeof( sfxcache_t ) - 1 ); sc->length = outcount; sc->loopstart = s_info.loopstart == -1 ? -1 : s_info.loopstart / stepscale; sc->width = s_info.width; // resample / decimate to the current source rate //Com_Printf("%s: %f, %d\n",sfx->name,stepscale,sc->width); if (stepscale == 1) { // fast special case if( sc->width == 1 ) { memcpy( sc->data, s_info.data, outcount ); } else { #if __BYTE_ORDER == __LITTLE_ENDIAN memcpy( sc->data, s_info.data, outcount << 1 ); #else for(i = 0; i < outcount; i++) { ((uint16_t *)sc->data)[i] = LittleShort( (( uint16_t * )s_info.data)[i] ); } #endif } } else { // general case samplefrac = 0; fracstep = stepscale*256; if( sc->width == 1 ) { for (i = 0; i < outcount; i++) { srcsample = samplefrac >> 8; samplefrac += fracstep; sc->data[i] = s_info.data[srcsample]; } } else { for (i = 0; i < outcount; i++) { srcsample = samplefrac >> 8; samplefrac += fracstep; ((uint16_t *)sc->data)[i] = LittleShort( (( uint16_t * )s_info.data)[srcsample] ); } } } return sc; } #endif /* =============================================================================== WAV loading =============================================================================== */ static byte *data_p; static byte *iff_end; static byte *iff_data; static uint32_t iff_chunk_len; static int GetLittleShort( void ) { int val; if( data_p + 2 > iff_end ) { return -1; } val = LittleShortMem( data_p ); data_p += 2; return val; } static int GetLittleLong( void ) { int val; if( data_p + 4 > iff_end ) { return -1; } val = LittleLongMem( data_p ); data_p += 4; return val; } static int GetRawLong( void ) { int val; if( data_p + 4 > iff_end ) { return -1; } val = RawLongMem( data_p ); data_p += 4; return val; } static void FindNextChunk( uint32_t search ) { uint32_t chunk, length; int i; for( i = 0; i < 1000; i++ ) { if( data_p + 8 >= iff_end ) { data_p = NULL; return; // didn't find the chunk } chunk = GetRawLong(); iff_chunk_len = GetLittleLong(); if( iff_chunk_len > iff_end - data_p ) { Com_DPrintf( "%s: oversize chunk %#x in %s\n", __func__, LittleLong( chunk ), s_info.name ); data_p = NULL; return; } if( chunk == search ) { return; } length = ( iff_chunk_len + 1 ) & ~1; data_p += length; } Com_DPrintf( "%s: too many iterations for chunk %#x in %s\n", __func__, search, s_info.name ); data_p = NULL; } static void FindChunk( uint32_t search ) { data_p = iff_data; FindNextChunk( search ); } #define TAG_RIFF MakeRawLong( 'R', 'I', 'F', 'F' ) #define TAG_WAVE MakeRawLong( 'W', 'A', 'V', 'E' ) #define TAG_fmt MakeRawLong( 'f', 'm', 't', ' ' ) #define TAG_cue MakeRawLong( 'c', 'u', 'e', ' ' ) #define TAG_LIST MakeRawLong( 'L', 'I', 'S', 'T' ) #define TAG_MARK MakeRawLong( 'M', 'A', 'R', 'K' ) #define TAG_data MakeRawLong( 'd', 'a', 't', 'a' ) static qboolean GetWavinfo( void ) { int format; int samples, width; uint32_t chunk; // find "RIFF" chunk FindChunk( TAG_RIFF ); if( !data_p ) { Com_DPrintf( "%s has missing/invalid RIFF chunk\n", s_info.name ); return qfalse; } chunk = GetLittleLong(); if( chunk != TAG_WAVE ) { Com_DPrintf( "%s has missing/invalid WAVE chunk\n", s_info.name ); return qfalse; } iff_data = data_p; // get "fmt " chunk FindChunk( TAG_fmt ); if( !data_p ) { Com_DPrintf("%s has missing/invalid fmt chunk\n", s_info.name ); return qfalse; } format = GetLittleShort(); if( format != 1 ) { Com_DPrintf( "%s has non-Microsoft PCM format\n", s_info.name ); return qfalse; } format = GetLittleShort(); if( format != 1 ) { Com_DPrintf( "%s has bad number of channels\n", s_info.name ); return qfalse; } s_info.rate = GetLittleLong(); if( s_info.rate < 8000 || s_info.rate > 48000 ) { Com_DPrintf( "%s has bad rate\n", s_info.name ); return qfalse; } data_p += 4+2; width = GetLittleShort(); switch( width ) { case 8: s_info.width = 1; break; case 16: s_info.width = 2; break; default: Com_DPrintf( "%s has bad width\n", s_info.name ); return qfalse; } // get cue chunk FindChunk( TAG_cue ); if( data_p ) { data_p += 24; s_info.loopstart = GetLittleLong(); if( s_info.loopstart < 0 || s_info.loopstart > INT_MAX ) { Com_DPrintf( "%s has bad loop start\n", s_info.name ); return qfalse; } FindNextChunk( TAG_LIST ); if( data_p ) { data_p += 20; chunk = GetLittleLong(); if( chunk == TAG_MARK ) { // this is not a proper parse, but it works with cooledit... data_p += 16; samples = GetLittleLong(); // samples in loop if( samples < 0 || samples > INT_MAX - s_info.loopstart ) { Com_DPrintf( "%s has bad loop length\n", s_info.name ); return qfalse; } s_info.samples = s_info.loopstart + samples; } } } else { s_info.loopstart = -1; } // find data chunk FindChunk( TAG_data ); if( !data_p ) { Com_DPrintf( "%s has missing/invalid data chunk\n", s_info.name ); return qfalse; } samples = iff_chunk_len / s_info.width; #if 0 if( !samples ) { Com_DPrintf( "%s has zero length\n", s_info.name ); return qfalse; } #endif if( s_info.samples ) { if( samples < s_info.samples ) { Com_DPrintf( "%s has bad loop length\n", s_info.name ); return qfalse; } } else { s_info.samples = samples; } s_info.data = data_p; return qtrue; } /* ============== S_LoadSound ============== */ sfxcache_t *S_LoadSound (sfx_t *s) { char namebuffer[MAX_QPATH]; byte *data; sfxcache_t *sc; size_t len; char *name; qerror_t ret; if (s->name[0] == '*') return NULL; // see if still in memory sc = s->cache; if (sc) return sc; // load it in if (s->truename) name = s->truename; else name = s->name; if (name[0] == '#') len = Q_strlcpy( namebuffer, name + 1, sizeof( namebuffer ) ); else len = Q_concat( namebuffer, sizeof( namebuffer ), "sound/", name, NULL ); if( len >= sizeof( namebuffer ) ) { ret = Q_ERR_NAMETOOLONG; goto fail1; } len = FS_LoadFile (namebuffer, (void **)&data); if (!data) { ret = len; goto fail1; } memset( &s_info, 0, sizeof( s_info ) ); s_info.name = namebuffer; iff_data = data; iff_end = data + len; if( !GetWavinfo() ) { ret = Q_ERR_INVALID_FORMAT; goto fail2; } #if USE_OPENAL if( s_started == SS_OAL ) sc = AL_UploadSfx( s ); else #endif #if USE_SNDDMA sc = ResampleSfx( s ) #endif ; ret = Q_ERR_SUCCESS; fail2: FS_FreeFile( data ); fail1: if( ret && ret != Q_ERR_NOENT ) { Com_EPrintf( "Couldn't load %s: %s\n", namebuffer, Q_ErrorString( ret ) ); } return sc; }