2 ===========================================================================
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
22 In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
26 ===========================================================================
28 #include "../idlib/precompiled.h"
31 #include "snd_local.h"
33 #define USE_SOUND_CACHE_ALLOCATOR
35 #ifdef USE_SOUND_CACHE_ALLOCATOR
36 static idDynamicBlockAlloc<byte, 1<<20, 1<<10> soundCacheAllocator;
38 static idDynamicAlloc<byte, 1<<20, 1<<10> soundCacheAllocator;
44 idSoundCache::idSoundCache()
47 idSoundCache::idSoundCache() {
48 soundCacheAllocator.Init();
49 soundCacheAllocator.SetLockMemory( true );
50 listCache.AssureSize( 1024, NULL );
51 listCache.SetGranularity( 256 );
52 insideLevelLoad = false;
57 idSoundCache::~idSoundCache()
60 idSoundCache::~idSoundCache() {
61 listCache.DeleteContents( true );
62 soundCacheAllocator.Shutdown();
67 idSoundCache::::GetObject
69 returns a single cached object pointer
72 const idSoundSample* idSoundCache::GetObject( const int index ) const {
73 if (index<0 || index>listCache.Num()) {
76 return listCache[index];
81 idSoundCache::FindSound
83 Adds a sound object to the cache and returns a handle for it.
86 idSoundSample *idSoundCache::FindSound( const idStr& filename, bool loadOnDemandOnly ) {
90 fname.BackSlashesToSlashes();
93 declManager->MediaPrint( "%s\n", fname.c_str() );
95 // check to see if object is already in cache
96 for( int i = 0; i < listCache.Num(); i++ ) {
97 idSoundSample *def = listCache[i];
98 if ( def && def->name == fname ) {
99 def->levelLoadReferenced = true;
100 if ( def->purged && !loadOnDemandOnly ) {
107 // create a new entry
108 idSoundSample *def = new idSoundSample;
110 int shandle = listCache.FindNull();
111 if ( shandle != -1 ) {
112 listCache[shandle] = def;
114 shandle = listCache.Append( def );
118 def->levelLoadReferenced = true;
119 def->onDemand = loadOnDemandOnly;
122 if ( !loadOnDemandOnly ) {
123 // this may make it a default sound if it can't be loaded
132 idSoundCache::ReloadSounds
134 Completely nukes the current cache
137 void idSoundCache::ReloadSounds( bool force ) {
140 for( i = 0; i < listCache.Num(); i++ ) {
141 idSoundSample *def = listCache[i];
143 def->Reload( force );
152 Mark all file based images as currently unused,
153 but don't free anything. Calls to ImageFromFile() will
154 either mark the image as used, or create a new image without
155 loading the actual data.
158 void idSoundCache::BeginLevelLoad() {
159 insideLevelLoad = true;
161 for ( int i = 0 ; i < listCache.Num() ; i++ ) {
162 idSoundSample *sample = listCache[ i ];
167 if ( com_purgeAll.GetBool() ) {
168 sample->PurgeSoundSample();
171 sample->levelLoadReferenced = false;
174 soundCacheAllocator.FreeEmptyBaseBlocks();
181 Free all samples marked as unused
184 void idSoundCache::EndLevelLoad() {
185 int useCount, purgeCount;
186 common->Printf( "----- idSoundCache::EndLevelLoad -----\n" );
188 insideLevelLoad = false;
190 // purge the ones we don't need
193 for ( int i = 0 ; i < listCache.Num() ; i++ ) {
194 idSoundSample *sample = listCache[ i ];
198 if ( sample->purged ) {
201 if ( !sample->levelLoadReferenced ) {
202 // common->Printf( "Purging %s\n", sample->name.c_str() );
203 purgeCount += sample->objectMemSize;
204 sample->PurgeSoundSample();
206 useCount += sample->objectMemSize;
210 soundCacheAllocator.FreeEmptyBaseBlocks();
212 common->Printf( "%5ik referenced\n", useCount / 1024 );
213 common->Printf( "%5ik purged\n", purgeCount / 1024 );
214 common->Printf( "----------------------------------------\n" );
219 idSoundCache::PrintMemInfo
222 void idSoundCache::PrintMemInfo( MemInfo_t *mi ) {
223 int i, j, num = 0, total = 0;
227 f = fileSystem->OpenFileWrite( mi->filebase + "_sounds.txt" );
233 for ( i = 0; i < listCache.Num(); i++, num++ ) {
234 if ( !listCache[i] ) {
240 sortIndex = new int[num];
242 for ( i = 0; i < num; i++ ) {
246 for ( i = 0; i < num - 1; i++ ) {
247 for ( j = i + 1; j < num; j++ ) {
248 if ( listCache[sortIndex[i]]->objectMemSize < listCache[sortIndex[j]]->objectMemSize ) {
249 int temp = sortIndex[i];
250 sortIndex[i] = sortIndex[j];
257 for ( i = 0; i < num; i++ ) {
258 idSoundSample *sample = listCache[sortIndex[i]];
265 total += sample->objectMemSize;
266 f->Printf( "%s %s\n", idStr::FormatNumber( sample->objectMemSize ).c_str(), sample->name.c_str() );
269 mi->soundAssetsTotal = total;
271 f->Printf( "\nTotal sound bytes allocated: %s\n", idStr::FormatNumber( total ).c_str() );
272 fileSystem->CloseFile( f );
277 ==========================================================================
281 ==========================================================================
286 idSoundSample::idSoundSample
289 idSoundSample::idSoundSample() {
290 memset( &objectInfo, 0, sizeof(waveformatex_t) );
294 amplitudeData = NULL;
296 hardwareBuffer = false;
297 defaultSound = false;
300 levelLoadReferenced = false;
305 idSoundSample::~idSoundSample
308 idSoundSample::~idSoundSample() {
314 idSoundSample::LengthIn44kHzSamples
317 int idSoundSample::LengthIn44kHzSamples( void ) const {
318 // objectSize is samples
319 if ( objectInfo.nSamplesPerSec == 11025 ) {
320 return objectSize << 2;
321 } else if ( objectInfo.nSamplesPerSec == 22050 ) {
322 return objectSize << 1;
324 return objectSize << 0;
330 idSoundSample::MakeDefault
333 void idSoundSample::MakeDefault( void ) {
338 memset( &objectInfo, 0, sizeof( objectInfo ) );
340 objectInfo.nChannels = 1;
341 objectInfo.wBitsPerSample = 16;
342 objectInfo.nSamplesPerSec = 44100;
344 objectSize = MIXBUFFER_SAMPLES * 2;
345 objectMemSize = objectSize * sizeof( short );
347 nonCacheData = (byte *)soundCacheAllocator.Alloc( objectMemSize );
349 short *ncd = (short *)nonCacheData;
351 for ( i = 0; i < MIXBUFFER_SAMPLES; i ++ ) {
352 v = sin( idMath::PI * 2 * i / 64 );
358 if ( idSoundSystemLocal::useOpenAL ) {
360 alGenBuffers( 1, &openalBuffer );
361 if ( alGetError() != AL_NO_ERROR ) {
362 common->Error( "idSoundCache: error generating OpenAL hardware buffer" );
366 alBufferData( openalBuffer, objectInfo.nChannels==1?AL_FORMAT_MONO16:AL_FORMAT_STEREO16, nonCacheData, objectMemSize, objectInfo.nSamplesPerSec );
367 if ( alGetError() != AL_NO_ERROR ) {
368 common->Error( "idSoundCache: error loading data into OpenAL hardware buffer" );
370 hardwareBuffer = true;
379 idSoundSample::CheckForDownSample
382 void idSoundSample::CheckForDownSample( void ) {
383 if ( !idSoundSystemLocal::s_force22kHz.GetBool() ) {
386 if ( objectInfo.wFormatTag != WAVE_FORMAT_TAG_PCM || objectInfo.nSamplesPerSec != 44100 ) {
389 int shortSamples = objectSize >> 1;
390 short *converted = (short *)soundCacheAllocator.Alloc( shortSamples * sizeof( short ) );
392 if ( objectInfo.nChannels == 1 ) {
393 for ( int i = 0; i < shortSamples; i++ ) {
394 converted[i] = ((short *)nonCacheData)[i*2];
397 for ( int i = 0; i < shortSamples; i += 2 ) {
398 converted[i+0] = ((short *)nonCacheData)[i*2+0];
399 converted[i+1] = ((short *)nonCacheData)[i*2+1];
402 soundCacheAllocator.Free( nonCacheData );
403 nonCacheData = (byte *)converted;
406 objectInfo.nAvgBytesPerSec >>= 1;
407 objectInfo.nSamplesPerSec >>= 1;
412 idSoundSample::GetNewTimeStamp
415 ID_TIME_T idSoundSample::GetNewTimeStamp( void ) const {
418 fileSystem->ReadFile( name, NULL, ×tamp );
419 if ( timestamp == FILE_NOT_FOUND_TIMESTAMP ) {
420 idStr oggName = name;
421 oggName.SetFileExtension( ".ogg" );
422 fileSystem->ReadFile( oggName, NULL, ×tamp );
431 Loads based on name, possibly doing a MakeDefault if necessary
434 void idSoundSample::Load( void ) {
435 defaultSound = false;
437 hardwareBuffer = false;
439 timestamp = GetNewTimeStamp();
441 if ( timestamp == FILE_NOT_FOUND_TIMESTAMP ) {
442 common->Warning( "Couldn't load sound '%s' using default", name.c_str() );
451 if ( fh.Open( name, &info ) == -1 ) {
452 common->Warning( "Couldn't load sound '%s' using default", name.c_str() );
457 if ( info.nChannels != 1 && info.nChannels != 2 ) {
458 common->Warning( "idSoundSample: %s has %i channels, using default", name.c_str(), info.nChannels );
464 if ( info.wBitsPerSample != 16 ) {
465 common->Warning( "idSoundSample: %s is %dbits, expected 16bits using default", name.c_str(), info.wBitsPerSample );
471 if ( info.nSamplesPerSec != 44100 && info.nSamplesPerSec != 22050 && info.nSamplesPerSec != 11025 ) {
472 common->Warning( "idSoundCache: %s is %dHz, expected 11025, 22050 or 44100 Hz. Using default", name.c_str(), info.nSamplesPerSec );
479 objectSize = fh.GetOutputSize();
480 objectMemSize = fh.GetMemorySize();
482 nonCacheData = (byte *)soundCacheAllocator.Alloc( objectMemSize );
483 fh.Read( nonCacheData, objectMemSize, NULL );
485 // optionally convert it to 22kHz to save memory
486 CheckForDownSample();
488 // create hardware audio buffers
489 if ( idSoundSystemLocal::useOpenAL ) {
490 // PCM loads directly
491 if ( objectInfo.wFormatTag == WAVE_FORMAT_TAG_PCM ) {
493 alGenBuffers( 1, &openalBuffer );
494 if ( alGetError() != AL_NO_ERROR )
495 common->Error( "idSoundCache: error generating OpenAL hardware buffer" );
496 if ( alIsBuffer( openalBuffer ) ) {
498 alBufferData( openalBuffer, objectInfo.nChannels==1?AL_FORMAT_MONO16:AL_FORMAT_STEREO16, nonCacheData, objectMemSize, objectInfo.nSamplesPerSec );
499 if ( alGetError() != AL_NO_ERROR ) {
500 common->Error( "idSoundCache: error loading data into OpenAL hardware buffer" );
502 // Compute amplitude block size
503 int blockSize = 512 * objectInfo.nSamplesPerSec / 44100 ;
505 // Allocate amplitude data array
506 amplitudeData = (byte *)soundCacheAllocator.Alloc( ( objectSize / blockSize + 1 ) * 2 * sizeof( short) );
508 // Creating array of min/max amplitude pairs per blockSize samples
510 for ( i = 0; i < objectSize; i+=blockSize ) {
515 for ( j = 0; j < Min( objectSize - i, blockSize ); j++ ) {
516 min = ((short *)nonCacheData)[ i + j ] < min ? ((short *)nonCacheData)[ i + j ] : min;
517 max = ((short *)nonCacheData)[ i + j ] > max ? ((short *)nonCacheData)[ i + j ] : max;
520 ((short *)amplitudeData)[ ( i / blockSize ) * 2 ] = min;
521 ((short *)amplitudeData)[ ( i / blockSize ) * 2 + 1 ] = max;
524 hardwareBuffer = true;
529 // OGG decompressed at load time (when smaller than s_decompressionLimit seconds, 6 seconds by default)
530 if ( objectInfo.wFormatTag == WAVE_FORMAT_TAG_OGG ) {
532 if ( ( objectSize < ( ( int ) objectInfo.nSamplesPerSec * idSoundSystemLocal::s_decompressionLimit.GetInteger() ) ) ) {
534 if ( ( alIsExtensionPresent( ID_ALCHAR "EAX-RAM" ) == AL_TRUE ) && ( objectSize < ( ( int ) objectInfo.nSamplesPerSec * idSoundSystemLocal::s_decompressionLimit.GetInteger() ) ) ) {
537 alGenBuffers( 1, &openalBuffer );
538 if ( alGetError() != AL_NO_ERROR )
539 common->Error( "idSoundCache: error generating OpenAL hardware buffer" );
540 if ( alIsBuffer( openalBuffer ) ) {
541 idSampleDecoder *decoder = idSampleDecoder::Alloc();
542 float *destData = (float *)soundCacheAllocator.Alloc( ( LengthIn44kHzSamples() + 1 ) * sizeof( float ) );
544 // Decoder *always* outputs 44 kHz data
545 decoder->Decode( this, 0, LengthIn44kHzSamples(), destData );
547 // Downsample back to original frequency (save memory)
548 if ( objectInfo.nSamplesPerSec == 11025 ) {
549 for ( int i = 0; i < objectSize; i++ ) {
550 if ( destData[i*4] < -32768.0f )
551 ((short *)destData)[i] = -32768;
552 else if ( destData[i*4] > 32767.0f )
553 ((short *)destData)[i] = 32767;
555 ((short *)destData)[i] = idMath::FtoiFast( destData[i*4] );
557 } else if ( objectInfo.nSamplesPerSec == 22050 ) {
558 for ( int i = 0; i < objectSize; i++ ) {
559 if ( destData[i*2] < -32768.0f )
560 ((short *)destData)[i] = -32768;
561 else if ( destData[i*2] > 32767.0f )
562 ((short *)destData)[i] = 32767;
564 ((short *)destData)[i] = idMath::FtoiFast( destData[i*2] );
567 for ( int i = 0; i < objectSize; i++ ) {
568 if ( destData[i] < -32768.0f )
569 ((short *)destData)[i] = -32768;
570 else if ( destData[i] > 32767.0f )
571 ((short *)destData)[i] = 32767;
573 ((short *)destData)[i] = idMath::FtoiFast( destData[i] );
578 alBufferData( openalBuffer, objectInfo.nChannels==1?AL_FORMAT_MONO16:AL_FORMAT_STEREO16, destData, objectSize * sizeof( short ), objectInfo.nSamplesPerSec );
579 if ( alGetError() != AL_NO_ERROR )
580 common->Error( "idSoundCache: error loading data into OpenAL hardware buffer" );
582 // Compute amplitude block size
583 int blockSize = 512 * objectInfo.nSamplesPerSec / 44100 ;
585 // Allocate amplitude data array
586 amplitudeData = (byte *)soundCacheAllocator.Alloc( ( objectSize / blockSize + 1 ) * 2 * sizeof( short ) );
588 // Creating array of min/max amplitude pairs per blockSize samples
590 for ( i = 0; i < objectSize; i+=blockSize ) {
595 for ( j = 0; j < Min( objectSize - i, blockSize ); j++ ) {
596 min = ((short *)destData)[ i + j ] < min ? ((short *)destData)[ i + j ] : min;
597 max = ((short *)destData)[ i + j ] > max ? ((short *)destData)[ i + j ] : max;
600 ((short *)amplitudeData)[ ( i / blockSize ) * 2 ] = min;
601 ((short *)amplitudeData)[ ( i / blockSize ) * 2 + 1 ] = max;
604 hardwareBuffer = true;
607 soundCacheAllocator.Free( (byte *)destData );
608 idSampleDecoder::Free( decoder );
613 // Free memory if sample was loaded into hardware
614 if ( hardwareBuffer ) {
615 soundCacheAllocator.Free( nonCacheData );
625 idSoundSample::PurgeSoundSample
628 void idSoundSample::PurgeSoundSample() {
631 if ( hardwareBuffer && idSoundSystemLocal::useOpenAL ) {
633 alDeleteBuffers( 1, &openalBuffer );
634 if ( alGetError() != AL_NO_ERROR ) {
635 common->Error( "idSoundCache: error unloading data from OpenAL hardware buffer" );
638 hardwareBuffer = false;
642 if ( amplitudeData ) {
643 soundCacheAllocator.Free( amplitudeData );
644 amplitudeData = NULL;
647 if ( nonCacheData ) {
648 soundCacheAllocator.Free( nonCacheData );
655 idSoundSample::Reload
658 void idSoundSample::Reload( bool force ) {
660 ID_TIME_T newTimestamp;
662 // check the timestamp
663 newTimestamp = GetNewTimeStamp();
665 if ( newTimestamp == FILE_NOT_FOUND_TIMESTAMP ) {
666 if ( !defaultSound ) {
667 common->Warning( "Couldn't load sound '%s' using default", name.c_str() );
672 if ( newTimestamp == timestamp ) {
673 return; // don't need to reload it
677 common->Printf( "reloading %s\n", name.c_str() );
684 idSoundSample::FetchFromCache
686 Returns true on success.
689 bool idSoundSample::FetchFromCache( int offset, const byte **output, int *position, int *size, const bool allowIO ) {
690 offset &= 0xfffffffe;
692 if ( objectSize == 0 || offset < 0 || offset > objectSize * (int)sizeof( short ) || !nonCacheData ) {
697 *output = nonCacheData + offset;
703 *size = objectSize * sizeof( short ) - offset;
704 if ( *size > SCACHE_SIZE ) {