/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2025 Electronic Arts 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 3 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, see .
*/
/*****************************************************************************
** **
** Westwood Studios Pacific. **
** **
** Confidential Information **
** Copyright (C) 2000 - All Rights Reserved **
** **
******************************************************************************
** **
** Project: Dune Emperor **
** **
** Module: (_) **
** **
** Version: $ID$ **
** **
** File name: audcache.cpp **
** **
** Created by: 04/30/99 TR **
** **
** Description: **
** **
*****************************************************************************/
/*****************************************************************************
** Includes **
*****************************************************************************/
#include
#include
#include // always include this header first
#include
#include
#include
#include
#include
#include
// 'assignment within condition expression'.
#pragma warning(disable : 4706)
DBG_DECLARE_TYPE ( AudioCache );
DBG_DECLARE_TYPE ( AudioCacheItem );
/*****************************************************************************
** Externals **
*****************************************************************************/
/*****************************************************************************
** Defines **
*****************************************************************************/
#define DEBUG_CACHE 0
/*****************************************************************************
** Private Types **
*****************************************************************************/
/*****************************************************************************
** Private Data **
*****************************************************************************/
/*****************************************************************************
** Public Data **
*****************************************************************************/
/*****************************************************************************
** Private Prototypes **
*****************************************************************************/
/*****************************************************************************
** Private Functions **
*****************************************************************************/
/******************************************************************/
/* */
/* */
/******************************************************************/
static void audioCacheAssetClose ( AudioCache *cache )
{
if ( cache->assetFile )
{
cache->assetFile->close();
cache->assetFile = NULL;
}
cache->assetBytesLeft = 0;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
static int audioCacheAssetOpen ( AudioCache *cache, const char *name )
{
audioCacheAssetClose ( cache );
if ( name == NULL || cache->openAssetCB == NULL )
{
return FALSE;
}
cache->assetFile = cache->openAssetCB( name );
if ( !cache->assetFile )
{
return FALSE;
}
if ( !AudioFormatReadWaveFile ( cache->assetFile, &cache->assetFormat, &cache->assetBytesLeft ))
{
audioCacheAssetClose ( cache );
return FALSE;
}
return TRUE;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
int audioCacheAssetRead ( AudioCache *cache, void *data, int bytes )
{
if ( bytes > cache->assetBytesLeft )
{
bytes = cache->assetBytesLeft;
}
return cache->assetFile ? cache->assetFile->read ( data, bytes ) : 0 ;
}
/*****************************************************************************
** Public Functions **
*****************************************************************************/
/******************************************************************/
/* */
/* */
/******************************************************************/
AudioCache* AudioCacheCreate ( int cacheSize, int maxItems, int frameSize )
{
AudioCache *cache;
int frameBytes;
int pages;
ALLOC_STRUCT ( cache, AudioCache );
DBG_SET_TYPE ( cache, AudioCache );
cache->frameSize = frameSize;
frameBytes = sizeof ( AudioFrame ) + frameSize;
pages = cacheSize/frameBytes;
cache->framePool = MemoryPoolCreate ( pages, frameBytes );
cache->itemPool = MemoryPoolCreate ( maxItems, sizeof ( AudioCacheItem ) );
ListInit ( &cache->items );
cache->assetFile = NULL;
AudioFormatInit ( &cache->assetFormat );
ProfCacheInit ( &cache->profile, pages, frameSize );
ProfCacheUpdateInterval ( &cache->profile, 10 ); // every ten milliseconds
if ( !cache->framePool || !cache->itemPool )
{
AudioCacheDestroy ( cache );
cache = NULL;
}
return cache;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void AudioCacheDestroy ( AudioCache *cache )
{
AudioCacheItem *item;
DBG_ASSERT_TYPE ( cache, AudioCache );
while ( ( item = (AudioCacheItem *) ListNodeNext ( &cache->items )) )
{
AudioCacheItemFree ( item );
}
if ( cache->framePool )
{
MemoryPoolDestroy ( cache->framePool );
}
if ( cache->itemPool )
{
MemoryPoolDestroy ( cache->itemPool );
}
DBG_INVALIDATE_TYPE ( cache );
AudioMemFree ( cache );
}
/******************************************************************/
/* */
/* */
/******************************************************************/
AudioCacheItem* AudioCacheGetItem ( AudioCache *cache, const char *name )
{
AudioCacheItem *item, *head;
DBG_ASSERT_TYPE ( cache, AudioCache );
item = head = (AudioCacheItem *) &cache->items ;
while ( (item = (AudioCacheItem *) item->nd.next ) != head )
{
if ( item->valid && item->name == name )
{
return item;
}
}
return NULL;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void AudioCacheInvalidate ( AudioCache *cache )
{
AudioCacheItem *item, *head;
DBG_ASSERT_TYPE ( cache, AudioCache );
item = head = (AudioCacheItem *) &cache->items ;
while ( (item = (AudioCacheItem *) item->nd.next ) != head )
{
item->valid = FALSE;
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
AudioCacheOpenCB* AudioCacheSetOpenCB ( AudioCache *cache, AudioCacheOpenCB *cb )
{
DBG_ASSERT_TYPE ( cache, AudioCache );
AudioCacheOpenCB *old = cache->openAssetCB;
cache->openAssetCB = cb;
return old;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
AudioCacheItem* AudioCacheLoadItem ( AudioCache *cache, const char *name )
{
AudioCacheItem *item;
int error;
DBG_ASSERT_TYPE ( cache, AudioCache );
if ( (item = AudioCacheGetItem ( cache, name )))
{
#if DEBUG_CACHE
DBGPRINTF (("ACACHE: %10s - Cached\n", item->name ));
#endif
ListNodeRemove ( &item->nd );
ListNodeAppend ( &cache->items, &item->nd );
ProfCacheHit ( &cache->profile );
return item;
}
ProfCacheMiss ( &cache->profile );
// item is not in the cache so load it
// see first if the sample exists
#if DEBUG_CACHE
DBGPRINTF (("ACACHE: %10s - Loading\n", name ));
#endif
ProfCacheLoadStart ( &cache->profile, 0 );
if ( !audioCacheAssetOpen( cache, name ))
{
#if DEBUG_CACHE
DBGPRINTF (("does not exist\n"));
#endif
goto none;
}
item = (AudioCacheItem *) MemoryPoolGetItem ( cache->itemPool );
if ( !item )
{
// free the oldest item so that we can use it's item struct
AudioCacheFreeOldestItem ( cache );
item = (AudioCacheItem *) MemoryPoolGetItem ( cache->itemPool );
if ( !item )
{
// the oldest item could not be freed because it was still playing
DBGPRINTF (("Audio cache overflow\n"));
audioCacheAssetClose ( cache );
goto none;
}
}
// prepare item for use
item->name = name;
item->cache = cache;
ListNodeInit ( &item->nd );
LockInit ( &item->lock );
AudioSampleInit ( &item->sample );
AudioSampleSetName ( &item->sample, name );
AudioFormatInit ( &item->format );
item->sample.Format = &item->format;
DBG_SET_TYPE ( item, AudioCacheItem );
error = FALSE;
// ok load sample data in to cache
{
int bytesToTransfer;
int bytes;
int bytesTransfered;
bytesToTransfer = cache->assetBytesLeft;
while ( bytesToTransfer )
{
AudioFrame *frame;
void *data;
if ( (bytes = cache->frameSize ) > bytesToTransfer )
{
bytes = bytesToTransfer;
}
while ( ! (frame = (AudioFrame *) MemoryPoolGetItem ( cache->framePool )))
{
if ( !AudioCacheFreeOldestItem ( cache ) )
{
break;
}
}
if ( !frame )
{
error = TRUE;
break;
}
data = (void *) ( (uint) frame + sizeof ( AudioFrame ));
AudioFrameInit ( frame, data, bytes );
AudioSampleAddFrame ( &item->sample, frame );
bytesTransfered = audioCacheAssetRead ( cache, data, bytes );
ProfCacheAddLoadBytes ( &cache->profile, bytesTransfered );
ProfCacheAddPage ( &cache->profile );
ProfCacheFill ( &cache->profile, bytesTransfered );
if ( bytesTransfered != bytes )
{
error = TRUE;
break;
}
bytesToTransfer -= bytesTransfered;
}
}
if ( error )
{
#if DEBUG_CACHE
DBGPRINTF (("FAILED\n"));
#endif
AudioCacheItemFree ( item );
goto none;
}
#if DEBUG_CACHE
DBGPRINTF (("done\n"));
#endif
// update the format structure
memcpy ( &item->format, &cache->assetFormat, sizeof ( AudioFormat) );
ListNodeAppend ( &cache->items, &item->nd );
item->valid = TRUE;
audioCacheAssetClose ( cache );
ProfCacheLoadEnd ( &cache->profile );
return item;
none:
ProfCacheLoadEnd ( &cache->profile );
return NULL;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
// AudioCacheFreeOldestItem()
// Free the oldest UNUSED item in the list. These should tend to be
// towards the end of the list, as old events should expire and unlock.
// But not always (e.g. loops), so don't count on it.
int AudioCacheFreeOldestItem( AudioCache *cache )
{
AudioCacheItem *item;
DBG_ASSERT_TYPE( cache, AudioCache );
item = (AudioCacheItem*) ListNodePrev( &cache->items );
while ( item )
{
if ( !AudioCacheItemInUse( item ) )
{
AudioCacheItemFree( item );
return TRUE;
}
item = (AudioCacheItem*) ListNodePrev( (ListNode*) item );
}
return FALSE;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void AudioCacheItemLock ( AudioCacheItem *item )
{
DBG_ASSERT_TYPE ( item, AudioCacheItem );
LockAcquire ( &item->lock );
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void AudioCacheItemUnlock ( AudioCacheItem *item )
{
DBG_ASSERT_TYPE ( item, AudioCacheItem );
LockRelease ( &item->lock );
}
/******************************************************************/
/* */
/* */
/******************************************************************/
int AudioCacheItemInUse ( AudioCacheItem *item )
{
DBG_ASSERT_TYPE ( item, AudioCacheItem );
return Locked ( &item->lock );
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void AudioCacheItemFree ( AudioCacheItem *item )
{
DBG_ASSERT_TYPE ( item, AudioCacheItem );
if ( ListNodeInList ( &item->nd ))
{
ListNodeRemove ( &item->nd );
}
DBG_MSGASSERT ( !AudioCacheItemInUse ( item ), ("cache item is still in use"));
#if DEBUG_CACHE
DBGPRINTF (("ACACHE: %10s - Freeing\n", AudioBagGetItemName ( item->cache->bag, item->id )));
#endif
// return frames to frame pool
{
AudioFrame *frame;
while ( (frame = (AudioFrame *) ListNodeNext ( &item->sample.Frames )) )
{
ListNodeRemove ( &frame->nd );
ProfCacheRemove ( &item->cache->profile, frame->Bytes );
AudioFrameDeinit ( frame );
ProfCacheRemovePage ( &item->cache->profile );
MemoryPoolReturnItem ( item->cache->framePool, frame );
}
}
AudioSampleDeinit ( &item->sample );
DBG_INVALIDATE_TYPE ( item );
MemoryPoolReturnItem ( item->cache->itemPool, item );
}
/******************************************************************/
/* */
/* */
/******************************************************************/
AudioSample* AudioCacheItemSample ( AudioCacheItem *item )
{
DBG_ASSERT_TYPE ( item, AudioCacheItem );
return &item->sample;
}