Complete disaster-recovery snapshot: engine/game source, game data assets, VC6 toolchain + DX SDKs, build outputs, deployed game, and _UNUSED archive. Large binaries in Git LFS; text preserved byte-for-byte (core.autocrlf=false, no eol attributes). See RECOVERY.md for the one-clone rebuild procedure.
443 lines
13 KiB
C++
443 lines
13 KiB
C++
//===========================================================================//
|
|
// File: AudioRenderer.cpp //
|
|
// Project: MUNGA Brick: Video Renderer //
|
|
// Contents: Abstracted Base Video Renderer //
|
|
//---------------------------------------------------------------------------//
|
|
// Date Who Modification //
|
|
// -------- --- ---------------------------------------------------------- //
|
|
// 03/04/97 JTR Initial coding. //
|
|
//---------------------------------------------------------------------------//
|
|
// Copyright (C) 1997, Virtual World Entertainment, Inc. //
|
|
// All Rights reserved worldwide //
|
|
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
|
|
//===========================================================================//
|
|
|
|
#include "AdeptHeaders.hpp"
|
|
|
|
#include "AudioCommand.hpp"
|
|
#include "AudioRenderer.hpp"
|
|
#include "AudioChannel.hpp"
|
|
#include "AudioSample.hpp"
|
|
|
|
//##########################################################################
|
|
//############################ AudioCommand ##########################
|
|
//##########################################################################
|
|
|
|
AudioCommand::ClassData*
|
|
AudioCommand::DefaultData = NULL;
|
|
|
|
MemoryBlock*
|
|
AudioCommand::s_AllocatedMemory = NULL;
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void AudioCommand::InitializeClass(
|
|
size_t block_count,
|
|
size_t block_delta
|
|
)
|
|
{
|
|
Verify(!s_AllocatedMemory);
|
|
s_AllocatedMemory =
|
|
new MemoryBlock(
|
|
sizeof(AudioCommand),
|
|
block_count,
|
|
block_delta,
|
|
"AudioCommand",
|
|
g_LibraryHeap
|
|
);
|
|
Register_Object(s_AllocatedMemory);
|
|
|
|
Verify(!DefaultData);
|
|
DefaultData =
|
|
new ClassData(
|
|
AudioCommandClassID,
|
|
"Adept::AudioCommand",
|
|
BaseClass::DefaultData
|
|
);
|
|
Register_Object(DefaultData);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void AudioCommand::TerminateClass()
|
|
{
|
|
Unregister_Object(DefaultData);
|
|
delete DefaultData;
|
|
DefaultData = NULL;
|
|
|
|
Unregister_Object(s_AllocatedMemory);
|
|
delete s_AllocatedMemory;
|
|
s_AllocatedMemory = NULL;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void AudioCommand::TestInstance() const
|
|
{
|
|
Verify(IsDerivedFrom(DefaultData));
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
AudioCommand* AudioCommand::Create(
|
|
int type,
|
|
const ResourceID &hint_ID,
|
|
Stuff::Scalar priority,
|
|
Stuff::Scalar volume,
|
|
Stuff::Scalar min_restart,
|
|
Stuff::Scalar min_replace
|
|
)
|
|
{
|
|
return new AudioCommand(
|
|
DefaultData,
|
|
type,
|
|
hint_ID,
|
|
priority,
|
|
volume,
|
|
min_restart,
|
|
min_replace
|
|
);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
AudioCommand* AudioCommand::Create(
|
|
int type,
|
|
const char *name,
|
|
Stuff::Scalar priority,
|
|
Stuff::Scalar volume,
|
|
Stuff::Scalar min_restart,
|
|
Stuff::Scalar min_replace
|
|
)
|
|
{
|
|
Resource hint(MString("Audio\\")+name);
|
|
if (!hint.DoesResourceExist())
|
|
return NULL;
|
|
return new AudioCommand(
|
|
DefaultData,
|
|
type,
|
|
hint.GetResourceID(),
|
|
priority,
|
|
volume,
|
|
min_restart,
|
|
min_replace
|
|
);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
AudioCommand::AudioCommand(
|
|
ClassData *class_data,
|
|
int type,
|
|
const ResourceID &sample_ID,
|
|
Stuff::Scalar priority,
|
|
Stuff::Scalar volume,
|
|
Stuff::Scalar min_restart,
|
|
Stuff::Scalar min_replace
|
|
):
|
|
Plug(class_data),
|
|
m_type(type),
|
|
m_handleResourceID(sample_ID),
|
|
m_priority(priority),
|
|
m_volume(volume),
|
|
m_minRestartDelay(min_restart),
|
|
m_minReplaceDelay(min_replace),
|
|
m_channel(NULL),
|
|
m_playMode(gosAudio_Stop)
|
|
{
|
|
Check_Pointer(this);
|
|
Verify((unsigned)type < AudioRenderer::Instance->m_channelTypes.GetLength());
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
AudioCommand::~AudioCommand()
|
|
{
|
|
Check_Object(this);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void AudioCommand::Play(gosAudio_PlayMode play_mode)
|
|
{
|
|
Check_Object(this);
|
|
Check_Object(AudioRenderer::Instance);
|
|
|
|
//
|
|
//---------------------------------------------------------------------
|
|
// Grab the sample (loading it if necessary), and figure out the delays
|
|
//---------------------------------------------------------------------
|
|
//
|
|
m_playMode = play_mode;
|
|
AudioSample *sample = AudioRenderer::Instance->UseSample(m_handleResourceID);
|
|
Check_Object(sample);
|
|
sample->m_commands.Add(this);
|
|
|
|
//
|
|
//------------------------------------------------------------------------------------
|
|
// If this is going to be a loop, add it to the loop list. If nothing is available to
|
|
// play it now, just return
|
|
//------------------------------------------------------------------------------------
|
|
//
|
|
if (play_mode == gosAudio_Loop)
|
|
{
|
|
if (!AddLoop())
|
|
return;
|
|
}
|
|
|
|
//
|
|
//---------------------------------------------
|
|
// Put the loop command in the audio buffer now
|
|
//---------------------------------------------
|
|
//
|
|
AudioRenderer::Instance->AddNewCommand(this);
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
void AudioCommand::Stop()
|
|
{
|
|
Check_Object(this);
|
|
|
|
//
|
|
//--------------------------------------------------------------------------------------------
|
|
// If we still have a channel, check the play mode. If we are play once, stopping the channel
|
|
// will delete the command
|
|
//--------------------------------------------------------------------------------------------
|
|
//
|
|
if (m_channel)
|
|
{
|
|
if (m_playMode == gosAudio_PlayOnce)
|
|
{
|
|
Check_Object(m_channel);
|
|
m_channel->Stop();
|
|
}
|
|
|
|
//
|
|
//---------------------------------------------------------------------------------------------
|
|
// We are a loop, so the stop command just put us on the loop queue. We need to kill ourselves
|
|
// to truly stop the sound
|
|
//---------------------------------------------------------------------------------------------
|
|
//
|
|
else
|
|
{
|
|
Verify(m_playMode == gosAudio_Loop);
|
|
Check_Object(m_channel);
|
|
m_channel->Stop();
|
|
Check_Object(this);
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
//
|
|
//-----------------------------------------------------------
|
|
// We don't have a channel, so we need to destroy the command
|
|
//-----------------------------------------------------------
|
|
//
|
|
else
|
|
delete this;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
bool AudioCommand::ConnectToChannel(AudioSample *sample)
|
|
{
|
|
Check_Object(this);
|
|
Check_Object(sample);
|
|
AUDIO_RENDER("Execute::NewCommands::AudioCommand");
|
|
|
|
//
|
|
//---------------------------------------------------------------------------------
|
|
// If this is a queued type, go ahead and queue it immediately if there are no free
|
|
// channels
|
|
//---------------------------------------------------------------------------------
|
|
//
|
|
Check_Object(AudioRenderer::Instance);
|
|
ChannelType &type = AudioRenderer::Instance->m_channelTypes[m_type];
|
|
if (type.IsQueued() && type.m_inactiveChannels.IsEmpty())
|
|
{
|
|
type.m_queuedCommands.Add(this);
|
|
return false;
|
|
}
|
|
|
|
//
|
|
//---------------------------
|
|
// Compute the command energy
|
|
//---------------------------
|
|
//
|
|
Time now = gos_GetElapsedTime();
|
|
AudioChannel *candidate = NULL;
|
|
Scalar energy = m_energy = ComputeEnergy(NULL);
|
|
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
// First look through the active channels to see if we can ignore this command
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
AudioChannel *channel;
|
|
if (m_playMode == gosAudio_PlayOnce)
|
|
{
|
|
ChainIteratorOf<AudioChannel*> active_channels(&sample->m_activeChannels);
|
|
while ((channel = active_channels.ReadAndNext()) != NULL)
|
|
{
|
|
Check_Object(channel);
|
|
AudioCommand *existing_command = channel->m_command;
|
|
Check_Object(existing_command);
|
|
|
|
//
|
|
//---------------------------------------------
|
|
// Can we merge the sound with an existing one?
|
|
//---------------------------------------------
|
|
//
|
|
if (
|
|
existing_command->m_volume >= m_volume
|
|
&& existing_command->m_priority <= m_priority
|
|
&& channel->m_startTime + existing_command->m_minRestartDelay >= now
|
|
)
|
|
{
|
|
delete this;
|
|
return false;
|
|
}
|
|
|
|
//
|
|
//------------------------------------------------------------
|
|
// Check to see if this channel is a candidate for replacement
|
|
//------------------------------------------------------------
|
|
//
|
|
if (
|
|
existing_command->m_volume <= m_volume
|
|
&& existing_command->m_priority >= m_priority
|
|
&& channel->m_startTime + existing_command->m_minReplaceDelay <= now
|
|
)
|
|
{
|
|
Scalar temp = channel->m_energy;
|
|
if (temp <= energy)
|
|
{
|
|
candidate = channel;
|
|
energy = temp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
//------------------------------------------------------
|
|
// Look for an inactive channel hooked up to this sample
|
|
//------------------------------------------------------
|
|
//
|
|
ChainIteratorOf<AudioChannel*> inactive_channels(&sample->m_inactiveChannels);
|
|
if ((channel = inactive_channels.GetCurrent()) != NULL)
|
|
{
|
|
channel->Activate(this);
|
|
Verify(channel->m_startTime == now);
|
|
return true;
|
|
}
|
|
|
|
//
|
|
//-------------------------------------------
|
|
// Replace the best candidate if there is one
|
|
//-------------------------------------------
|
|
//
|
|
if (candidate)
|
|
{
|
|
Check_Object(candidate);
|
|
candidate->Stop();
|
|
candidate->Activate(this);
|
|
Verify(candidate->m_startTime == now);
|
|
return true;
|
|
}
|
|
|
|
//
|
|
//------------------------------------------------------------------------------------
|
|
// Now we search the inactive channel list for our sound type and use the earliest one
|
|
//------------------------------------------------------------------------------------
|
|
//
|
|
ChainIteratorOf<AudioChannel*> free_channels(&type.m_inactiveChannels);
|
|
if ((channel = free_channels.GetCurrent()) != NULL)
|
|
{
|
|
Check_Object(channel);
|
|
channel->SetSample(sample);
|
|
channel->Activate(this);
|
|
Verify(channel->m_startTime == now);
|
|
return true;
|
|
}
|
|
|
|
//
|
|
//----------------------------------------------------------------------------------
|
|
// Now we search the active channel list for our sound type and use the earliest one
|
|
// that also matches the playmode - loops steal loops and one-shots steal one-shots
|
|
//----------------------------------------------------------------------------------
|
|
//
|
|
ChainIteratorOf<AudioChannel*> used_channels(&type.m_activeChannels);
|
|
while ((channel = used_channels.ReadAndNext()) != NULL)
|
|
{
|
|
Check_Object(channel);
|
|
if (channel->m_playMode == m_playMode)
|
|
{
|
|
channel->Stop();
|
|
if (channel->m_sample != sample)
|
|
channel->SetSample(sample);
|
|
channel->Activate(this);
|
|
Verify(channel->m_startTime == now);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//
|
|
//----------------------------------------------------------------------------------
|
|
// Now we search the active channel list for our sound type and use the earliest one
|
|
//----------------------------------------------------------------------------------
|
|
//
|
|
used_channels.First();
|
|
channel = used_channels.GetCurrent();
|
|
Check_Object(channel);
|
|
channel->Stop();
|
|
if (channel->m_sample != sample)
|
|
channel->SetSample(sample);
|
|
channel->Activate(this);
|
|
Verify(channel->m_startTime == now);
|
|
return true;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
bool AudioCommand::AddLoop()
|
|
{
|
|
Check_Object(this);
|
|
|
|
//
|
|
//-------------------------------------------------
|
|
// Hook us up to the loop chain of our channel type
|
|
//-------------------------------------------------
|
|
//
|
|
Check_Object(AudioRenderer::Instance);
|
|
ChannelType &type = AudioRenderer::Instance->m_channelTypes[m_type];
|
|
type.m_loopCommands.Add(this);
|
|
return true;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
//
|
|
Stuff::Scalar AudioCommand::ComputeEnergy(AudioChannel *channel)
|
|
{
|
|
Check_Object(this);
|
|
|
|
//
|
|
//--------------------------------------------------------------
|
|
// If we haven't been assigned a channel yet, our energy is full
|
|
//--------------------------------------------------------------
|
|
//
|
|
if (!channel)
|
|
return 1.0f;
|
|
|
|
//
|
|
//----------------------------------------------------------------
|
|
// Otherwise, our energy decreases over the lifetime of the sample
|
|
//----------------------------------------------------------------
|
|
//
|
|
Check_Object(channel);
|
|
AudioSample *sample = channel->m_sample;
|
|
Check_Object(sample);
|
|
return 1.0f - (static_cast<Scalar>(gos_GetElapsedTime() - channel->m_startTime)/sample->m_sampleInfo.fDuration);
|
|
}
|