Files
GTASource/game/audio/waveslotmanager.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

479 lines
15 KiB
C++

//
// audio/waveslotmanager.h
//
// Copyright (C) 1999-2006 Rockstar Games. All Rights Reserved.
//
#include "waveslotmanager.h"
#include "audio_channel.h"
#include "grcore/debugdraw.h"
#include "fwsys/timer.h"
#include "audiohardware/waveslot.h"
#include "audioengine/engine.h"
#include "audioengine/soundfactory.h"
AUDIO_OPTIMISATIONS()
// ----------------------------------------------------------------
// Check if the bank has finished loading
// ----------------------------------------------------------------
bool audDynamicWaveSlot::IsLoaded() const
{
if(waveSlot)
{
audWaveSlot::audWaveSlotLoadStatus status = waveSlot->GetBankLoadingStatus(loadedBankId);
if(status == audWaveSlot::LOADED)
{
return true;
}
}
return false;
}
// ----------------------------------------------------------------
// Check if the bank is loading
// ----------------------------------------------------------------
bool audDynamicWaveSlot::IsLoading() const
{
if(waveSlot)
{
audWaveSlot::audWaveSlotLoadStatus status = waveSlot->GetBankLoadingStatus(loadedBankId);
if(status == audWaveSlot::LOADING)
{
return true;
}
}
return false;
}
// ----------------------------------------------------------------
// audWaveSlotManager constructor
// ----------------------------------------------------------------
audWaveSlotManager::audWaveSlotManager()
{
}
// ----------------------------------------------------------------
// Add a new wave slot
// ----------------------------------------------------------------
bool audWaveSlotManager::AddWaveSlot(const char* slotName, const char* slotType, const u32 slotPriority)
{
u32 slotNameHash = atStringHash(slotName);
u32 slotTypeHash = atStringHash(slotType);
return AddWaveSlot(slotNameHash, slotTypeHash, slotPriority);
}
// ----------------------------------------------------------------
// Add a new wave slot
// ----------------------------------------------------------------
bool audWaveSlotManager::AddWaveSlot(const u32 slotNameHash, const u32 slotTypeHash, const u32 slotPriority)
{
for(int slotLoop = 0; slotLoop < m_dynamicWaveSlots.GetCount(); slotLoop++)
{
if(m_dynamicWaveSlots[slotLoop].slotName == slotNameHash)
{
for(int typeLoop = 0; typeLoop < m_dynamicWaveSlots[slotLoop].slotTypes.GetCount(); typeLoop++)
{
// This slot/type combo is already registered!
if(m_dynamicWaveSlots[slotLoop].slotTypes[typeLoop] == slotTypeHash)
{
return true;
}
}
// Slot exists, but not type - so add it now
m_dynamicWaveSlots[slotLoop].slotTypes.PushAndGrow(slotTypeHash);
return true;
}
}
audWaveSlot* waveSlot = audWaveSlot::FindWaveSlot(slotNameHash);
if(waveSlot)
{
audDynamicWaveSlot& dynamicWaveSlot = m_dynamicWaveSlots.Grow();
dynamicWaveSlot.slotName = slotNameHash;
dynamicWaveSlot.slotPriority = slotPriority;
dynamicWaveSlot.slotTypes.PushAndGrow(slotTypeHash);
dynamicWaveSlot.waveSlot = waveSlot;
dynamicWaveSlot.refCount = 0;
return true;
}
return false;
}
// ----------------------------------------------------------------
// Query how many slots exist in total
// ----------------------------------------------------------------
u32 audWaveSlotManager::GetNumSlots() const
{
return m_dynamicWaveSlots.GetCount();
}
// ----------------------------------------------------------------
// Query how many slots have been used
// ----------------------------------------------------------------
u32 audWaveSlotManager::GetNumUsedSlots() const
{
u32 numSlotsUsed = 0;
for(u32 i = 0; i < m_dynamicWaveSlots.GetCount(); i++)
{
if(m_dynamicWaveSlots[i].refCount > 0)
{
numSlotsUsed++;
}
}
return numSlotsUsed;
}
// ----------------------------------------------------------------
// Query how many slots are currently loading data
// ----------------------------------------------------------------
u32 audWaveSlotManager::GetNumLoadsInProgress() const
{
u32 numSlotsLoading = 0;
for(u32 i = 0; i < m_dynamicWaveSlots.GetCount(); i++)
{
if(m_dynamicWaveSlots[i].IsLoading())
{
numSlotsLoading++;
}
}
return numSlotsLoading;
}
#if __BANK
// ----------------------------------------------------------------
// Render the current status of the slot manager
// ----------------------------------------------------------------
void audWaveSlotManager::DebugDraw(f32 xOffset, f32 yOffset)
{
for(int loop = 0; loop < m_dynamicWaveSlots.GetCount(); loop++)
{
float currentXOffset = xOffset;
char tempString[128];
grcDebugDraw::Text(Vector2(currentXOffset, yOffset), Color32(255,255,255), m_dynamicWaveSlots[loop].waveSlot->GetSlotName());
currentXOffset += 0.17f;
const char* bankName = NULL;
if(m_dynamicWaveSlots[loop].waveSlot->GetLoadedBankId() < AUD_INVALID_BANK_ID &&
m_dynamicWaveSlots[loop].refCount > 0)
{
audWaveSlot::audWaveSlotLoadStatus bankStatus = m_dynamicWaveSlots[loop].waveSlot->GetBankLoadingStatus(m_dynamicWaveSlots[loop].waveSlot->GetLoadedBankId());
if(bankStatus == audWaveSlot::LOADING)
{
bankName = "Loading";
}
else if(bankStatus == audWaveSlot::FAILED)
{
bankName = "Failed";
}
else if(bankStatus == audWaveSlot::LOADED)
{
bankName = m_dynamicWaveSlots[loop].waveSlot->GetLoadedBankName();
}
else
{
bankName = "Buh?";
}
}
else
{
bankName = "Empty";
}
u32 waveSlotRefCounts = 0;
if(m_dynamicWaveSlots[loop].waveSlot)
{
waveSlotRefCounts = m_dynamicWaveSlots[loop].waveSlot->GetReferenceCount();
}
sprintf(tempString, "%s (%d ref / %d wave ref)", bankName, m_dynamicWaveSlots[loop].refCount, waveSlotRefCounts);
grcDebugDraw::Text(Vector2(currentXOffset, yOffset), Color32(255,255,255), tempString);
yOffset += 0.02f;
}
}
#endif
// ----------------------------------------------------------------
// Load a bank into a free slot of the given type. Pass in
// AUD_WAVE_SLOT_ANY if you don't care which slot type is used
// ----------------------------------------------------------------
audDynamicWaveSlot* audWaveSlotManager::LoadBank(const u32 bankId, const u32 slotTypeHash, bool allowBankShuffling, audEntity* entity, naWaveLoadPriority::Priority loadPriority, f32 delayTime)
{
naAssertf(bankId!=0xffff, "Invalid bankId passed through as a parameter");
s32 bestMatchSlotIndex = -1;
u32 bestFreeSlotTime = 0;
u32 bestPriority = 0;
for(u32 i = 0; i < GetNumSlots(); i++)
{
audWaveSlot::audWaveSlotLoadStatus status = m_dynamicWaveSlots[i].waveSlot->GetBankLoadingStatus(bankId);
// Always prefer a loaded slot, whether it has refs or not
if(m_dynamicWaveSlots[i].loadedBankId == bankId &&
(status == audWaveSlot::LOADED || status == audWaveSlot::LOADING || m_dynamicWaveSlots[i].delayTime > 0.0f))
{
RegisterEntity(entity, i);
return &m_dynamicWaveSlots[i];
}
else
{
bool validSlot = (slotTypeHash == AUD_WAVE_SLOT_ANY);
// First check that this slot can have banks of the given type loaded into it
if(!validSlot)
{
for(int typeLoop = 0; typeLoop < m_dynamicWaveSlots[i].slotTypes.GetCount(); typeLoop++)
{
if(m_dynamicWaveSlots[i].slotTypes[typeLoop] == slotTypeHash)
{
validSlot = true;
break;
}
}
}
// If this slot can load banks of the required type
if(validSlot)
{
// Check both our high level reference count and the waveslots voice ref count to be sure its free
if(m_dynamicWaveSlots[i].refCount == 0 && m_dynamicWaveSlots[i].waveSlot->GetReferenceCount() == 0)
{
// So we have a free slot - need to work out if its preferable to any free slots we've already found
bool useThisSlot = false;
// First slot we've found
if(bestMatchSlotIndex == -1)
{
useThisSlot = true;
}
// Better priority than the best slot so far
else if(m_dynamicWaveSlots[i].slotPriority < bestPriority)
{
useThisSlot = true;
}
// Equal priority, but slot has gone unused for longer
else if(m_dynamicWaveSlots[i].slotPriority == bestPriority &&
m_dynamicWaveSlots[i].timeLastUsed < bestFreeSlotTime)
{
useThisSlot = true;
}
if(useThisSlot)
{
bestMatchSlotIndex = i;
bestPriority = m_dynamicWaveSlots[i].slotPriority;
bestFreeSlotTime = m_dynamicWaveSlots[i].timeLastUsed;
}
}
// This slot is valid, has a better priority, is currently in use by a rule, but is not actually being played from
else if(m_dynamicWaveSlots[i].refCount > 0 &&
m_dynamicWaveSlots[i].waveSlot->GetReferenceCount() == 0 &&
(m_dynamicWaveSlots[i].slotPriority < bestPriority || bestMatchSlotIndex == -1))
{
if(allowBankShuffling)
{
// Check all the other slots and see if theres one that this bank would be better suited to (ie. better priority, currently empty)
for(u32 comparisonIndex = 0; comparisonIndex < GetNumSlots(); comparisonIndex++)
{
if(comparisonIndex != i &&
m_dynamicWaveSlots[comparisonIndex].slotPriority < m_dynamicWaveSlots[i].slotPriority &&
m_dynamicWaveSlots[comparisonIndex].waveSlot->GetReferenceCount() == 0 &&
m_dynamicWaveSlots[comparisonIndex].refCount == 0)
{
for(int typeLoop = 0; typeLoop < m_dynamicWaveSlots[comparisonIndex].slotTypes.GetCount(); typeLoop++)
{
if(m_dynamicWaveSlots[comparisonIndex].slotTypes[typeLoop] == m_dynamicWaveSlots[i].loadedBankType)
{
// Job done! Mark this as a suitable slot. Once we load the incoming bank into here, the calling
// system needs to recognise that the slot has been invalidated (by seeing that the loadedBankId
// value has changed) and request a re-load of the evicted bank into the more optimal slot.
bestMatchSlotIndex = i;
bestPriority = m_dynamicWaveSlots[i].slotPriority;
bestFreeSlotTime = 0; // Always want to bank shuffle if poss, so set the time to min
break;
}
}
}
}
}
}
}
}
}
if(bestMatchSlotIndex != -1)
{
// Mark this slot as used and request the load
if(delayTime > 0.0f || m_dynamicWaveSlots[bestMatchSlotIndex].waveSlot->LoadBank(bankId, loadPriority))
{
RegisterEntity(entity, bestMatchSlotIndex);
m_dynamicWaveSlots[bestMatchSlotIndex].delayTime = delayTime;
m_dynamicWaveSlots[bestMatchSlotIndex].loadPriority = loadPriority;
m_dynamicWaveSlots[bestMatchSlotIndex].loadedBankId = bankId;
m_dynamicWaveSlots[bestMatchSlotIndex].loadedBankType = slotTypeHash;
return &m_dynamicWaveSlots[bestMatchSlotIndex];
}
}
return NULL;
}
// ----------------------------------------------------------------
// Register an entity as being associated with this wave slot
// ----------------------------------------------------------------
void audWaveSlotManager::RegisterEntity(audEntity* entity, u32 waveSlotIndex)
{
if(entity)
{
m_dynamicWaveSlots[waveSlotIndex].registeredEntities.PushAndGrow(entity);
m_dynamicWaveSlots[waveSlotIndex].refCount++;
}
else
{
m_dynamicWaveSlots[waveSlotIndex].refCount++;
}
}
// ----------------------------------------------------------------
// Load a bank required by a particular sound
// ----------------------------------------------------------------
audDynamicWaveSlot* audWaveSlotManager::LoadBankForSound(const u32 soundNameHash, const u32 slotType, bool allowBankShuffling, audEntity* entity, naWaveLoadPriority::Priority loadPriority, f32 delayTime)
{
// This helper class is passed down to the sound factory's ProcessHierarchy function, and gets called
// on every sound that we encounter in the hierarchy
class RequiredBankBuilderFn : public audSoundFactory::audSoundProcessHierarchyFn
{
public:
atArray<u32> bankList;
void operator()(u32 classID, const void* soundData)
{
u32 bankID = AUD_INVALID_BANK_ID;
if(classID == SimpleSound::TYPE_ID)
{
const SimpleSound * sound = reinterpret_cast<const SimpleSound*>(soundData);
bankID = g_AudioEngine.GetSoundManager().GetFactory().GetBankIndexFromMetadataRef(sound->WaveRef.BankName);
}
else if(classID == GranularSound::TYPE_ID)
{
const GranularSound * sound = reinterpret_cast<const GranularSound*>(soundData);
bankID = g_AudioEngine.GetSoundManager().GetFactory().GetBankIndexFromMetadataRef(sound->Channel0.BankName);
}
if(bankID < AUD_INVALID_BANK_ID)
{
// Bail out if we've already encountered this bank
for(u32 existingBankLoop = 0; existingBankLoop < bankList.GetCount(); existingBankLoop++)
{
if(bankList[existingBankLoop] == bankID)
{
return;
}
}
bankList.PushAndGrow(bankID);
}
}
};
RequiredBankBuilderFn bankListBuilder;
SOUNDFACTORY.ProcessHierarchy(soundNameHash, bankListBuilder);
for(s32 loop = 0; loop < bankListBuilder.bankList.GetCount(); )
{
audWaveSlot* bankSlot = audWaveSlot::FindLoadedBankWaveSlot(bankListBuilder.bankList[loop]);
if(bankSlot && bankSlot->IsStatic())
{
bankListBuilder.bankList.Delete(loop);
}
else
{
loop++;
}
}
if(bankListBuilder.bankList.GetCount() == 1)
{
return LoadBank(bankListBuilder.bankList[0], slotType, allowBankShuffling, entity, loadPriority, delayTime);
}
#if __DEV
else if(bankListBuilder.bankList.GetCount() == 0)
{
audWarningf("Bank load failed: No banks identified as being required to play sound %d", soundNameHash);
}
else
{
audWarningf("Bank load failed: Multiple (%d) banks identified as being required to play sound %d", bankListBuilder.bankList.GetCount(), soundNameHash);
}
#endif
return NULL;
}
// ----------------------------------------------------------------
// Free up a wave slot
// ----------------------------------------------------------------
void audWaveSlotManager::FreeWaveSlot(audDynamicWaveSlot*& slot, audEntity* entity)
{
if(slot &&
slot->refCount > 0)
{
slot->refCount--;
if(entity)
{
s32 entityIndex = slot->registeredEntities.Find(entity);
audAssertf(entityIndex >= 0, "Could not find registered entity in list!");
slot->registeredEntities.Delete(entityIndex);
}
if(slot->refCount == 0)
{
audAssertf(slot->registeredEntities.GetCount() == 0, "Expected registered entities to be clear!");
slot->timeLastUsed = fwTimer::GetTimeInMilliseconds();
slot->delayTime = 0.0f;
}
slot = NULL;
}
}
// ----------------------------------------------------------------
// Update the wave slot manager
// ----------------------------------------------------------------
void audWaveSlotManager::Update()
{
for(u32 i = 0; i < GetNumSlots(); i++)
{
if(m_dynamicWaveSlots[i].delayTime > 0.0f)
{
m_dynamicWaveSlots[i].delayTime -= fwTimer::GetTimeStep();
if(m_dynamicWaveSlots[i].delayTime <= 0.0f)
{
m_dynamicWaveSlots[i].waveSlot->LoadBank(m_dynamicWaveSlots[i].loadedBankId, m_dynamicWaveSlots[i].loadPriority);
}
}
}
}