Torque3D/Engine/source/sfx/sfxProfile.cpp
Areloch 5525f8ecdd Converts all game, gui editor, and system classes to utilize assets
Processed core, tools and default modules to utilize assets
Converted all console types that were string based, such as TypeImageFilename to utilize const char*/the string table, which avoids a lot of type swapping shenanigans and avoids string corruption
Removed unneeded MainEditor mockup module
Removed some unused/duplicate image assets from the tools
2021-07-19 01:07:08 -05:00

494 lines
15 KiB
C++

//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
// Copyright (C) 2015 Faust Logic, Inc.
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
#include "platform/platform.h"
#include "sfx/sfxProfile.h"
#include "sfx/sfxDescription.h"
#include "sfx/sfxSystem.h"
#include "sfx/sfxStream.h"
#include "sim/netConnection.h"
#include "core/stream/bitStream.h"
#include "core/resourceManager.h"
#include "console/engineAPI.h"
using namespace Torque;
IMPLEMENT_CO_DATABLOCK_V1( SFXProfile );
ConsoleDocClass( SFXProfile,
"@brief Encapsulates a single sound file for playback by the sound system.\n\n"
"SFXProfile combines a sound description (SFXDescription) with a sound file such that it can be played "
"by the sound system. To be able to play a sound file, the sound system will always require a profile "
"for it to be created. However, several of the SFX functions (sfxPlayOnce(), sfxCreateSource()) perform "
"this creation internally for convenience using temporary profile objects.\n\n"
"Sound files can be in either OGG or WAV format. However, extended format support is available when using FMOD. "
"See @ref SFX_formats.\n\n"
"@section SFXProfile_loading Profile Loading\n\n"
"By default, the sound data referenced by a profile will be loaded when the profile is first played and the "
"data then kept until either the profile is deleted or until the sound device on which the sound data is held "
"is deleted.\n\n"
"This initial loading my incur a small delay when the sound is first played. To avoid this, a profile may be "
"expicitly set to load its sound data immediately when the profile is added to the system. This is done by "
"setting the #preload property to true.\n\n"
"@note Sounds using streamed playback (SFXDescription::isStreaming) cannot be preloaded and will thus "
"ignore the #preload flag.\n\n"
"@tsexample\n"
"datablock SFXProfile( Shore01Snd )\n"
"{\n"
" fileName = \"art/sound/Lakeshore_mono_01\";\n"
" description = Shore01Looping3d;\n"
" preload = true;\n"
"};\n"
"@endtsexample\n\n"
"@ingroup SFX\n"
"@ingroup Datablocks\n"
);
//-----------------------------------------------------------------------------
SFXProfile::SFXProfile()
: mPreload( false )
{
}
//-----------------------------------------------------------------------------
SFXProfile::SFXProfile( SFXDescription* desc, const String& filename, bool preload )
: Parent( desc ),
mFilename( filename ),
mPreload( preload )
{
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void SFXProfile::initPersistFields()
{
addGroup( "Sound" );
addField( "filename", TypeStringFilename, Offset( mFilename, SFXProfile ),
"%Path to the sound file.\n"
"If the extension is left out, it will be inferred by the sound system. This allows to "
"easily switch the sound format without having to go through the profiles and change the "
"filenames there, too.\n" );
addField( "preload", TypeBool, Offset( mPreload, SFXProfile ),
"Whether to preload sound data when the profile is added to system.\n"
"@note This flag is ignored by streamed sounds.\n\n"
"@ref SFXProfile_loading" );
endGroup( "Sound" );
// disallow some field substitutions
disableFieldSubstitutions("description");
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
bool SFXProfile::onAdd()
{
if( !Parent::onAdd() )
return false;
// If we're a streaming profile we don't preload
// or need device events.
if( SFX && !mDescription->mIsStreaming )
{
// If preload is enabled we load the resource
// and device buffer now to avoid a delay on
// first playback.
if( mPreload && !_preloadBuffer() )
Con::errorf( "SFXProfile(%s)::onAdd: The preload failed!", getName() );
}
_registerSignals();
return true;
}
//-----------------------------------------------------------------------------
void SFXProfile::onRemove()
{
_unregisterSignals();
Parent::onRemove();
}
//-----------------------------------------------------------------------------
bool SFXProfile::preload( bool server, String &errorStr )
{
if ( !Parent::preload( server, errorStr ) )
return false;
// TODO: Investigate how NetConnection::filesWereDownloaded()
// effects the system.
// Validate the datablock... has nothing to do with mPreload.
if( !server &&
NetConnection::filesWereDownloaded() &&
( mFilename == StringTable->EmptyString() || !SFXResource::exists( mFilename ) ) )
return false;
return true;
}
//-----------------------------------------------------------------------------
void SFXProfile::packData(BitStream* stream)
{
Parent::packData( stream );
char buffer[256];
if ( mFilename == StringTable->EmptyString())
buffer[0] = 0;
else
dStrncpy( buffer, mFilename, 256 );
stream->writeString( buffer );
stream->writeFlag( mPreload );
}
//-----------------------------------------------------------------------------
void SFXProfile::unpackData(BitStream* stream)
{
Parent::unpackData( stream );
char buffer[256];
stream->readString( buffer );
mFilename = buffer;
mPreload = stream->readFlag();
}
//-----------------------------------------------------------------------------
bool SFXProfile::isLooping() const
{
return getDescription()->mIsLooping;
}
//-----------------------------------------------------------------------------
void SFXProfile::_registerSignals()
{
SFX->getEventSignal().notify( this, &SFXProfile::_onDeviceEvent );
ResourceManager::get().getChangedSignal().notify( this, &SFXProfile::_onResourceChanged );
}
//-----------------------------------------------------------------------------
void SFXProfile::_unregisterSignals()
{
ResourceManager::get().getChangedSignal().remove( this, &SFXProfile::_onResourceChanged );
if( SFX )
SFX->getEventSignal().remove( this, &SFXProfile::_onDeviceEvent );
}
//-----------------------------------------------------------------------------
void SFXProfile::_onDeviceEvent( SFXSystemEventType evt )
{
switch( evt )
{
case SFXSystemEvent_CreateDevice:
{
if( mPreload && !mDescription->mIsStreaming && !_preloadBuffer() )
Con::errorf( "SFXProfile::_onDeviceEvent: The preload failed! %s", getName() );
break;
}
default:
break;
}
}
//-----------------------------------------------------------------------------
void SFXProfile::_onResourceChanged( const Torque::Path& path )
{
if( path != Path( mFilename ) )
return;
// Let go of the old resource and buffer.
mResource = NULL;
mBuffer = NULL;
// Load the new resource.
getResource();
if( mPreload && !mDescription->mIsStreaming )
{
if( !_preloadBuffer() )
Con::errorf( "SFXProfile::_onResourceChanged() - failed to preload '%s'", mFilename );
}
mChangedSignal.trigger( this );
}
//-----------------------------------------------------------------------------
bool SFXProfile::_preloadBuffer()
{
AssertFatal( !mDescription->mIsStreaming, "SFXProfile::_preloadBuffer() - must not be called for streaming profiles" );
mBuffer = _createBuffer();
return ( !mBuffer.isNull() );
}
//-----------------------------------------------------------------------------
Resource<SFXResource>& SFXProfile::getResource()
{
if( !mResource && mFilename != StringTable->EmptyString())
mResource = SFXResource::load( mFilename );
return mResource;
}
//-----------------------------------------------------------------------------
SFXBuffer* SFXProfile::getBuffer()
{
if ( mDescription->mIsStreaming )
{
// Streaming requires unique buffers per
// source, so this creates a new buffer.
if ( SFX )
return _createBuffer();
return NULL;
}
if ( mBuffer.isNull() )
_preloadBuffer();
return mBuffer;
}
//-----------------------------------------------------------------------------
SFXBuffer* SFXProfile::_createBuffer()
{
SFXBuffer* buffer = 0;
// Try to create through SFXDevie.
if( mFilename != StringTable->EmptyString() && SFX )
{
buffer = SFX->_createBuffer( mFilename, mDescription );
if( buffer )
{
#ifdef TORQUE_DEBUG
const SFXFormat& format = buffer->getFormat();
Con::printf( "%s SFX: %s (%i channels, %i kHz, %.02f sec, %i kb)",
mDescription->mIsStreaming ? "Streaming" : "Loaded", mFilename,
format.getChannels(),
format.getSamplesPerSecond() / 1000,
F32( buffer->getDuration() ) / 1000.0f,
format.getDataLength( buffer->getDuration() ) / 1024 );
#endif
}
}
// If that failed, load through SFXResource.
if( !buffer )
{
Resource< SFXResource >& resource = getResource();
if( resource != NULL && SFX )
{
#ifdef TORQUE_DEBUG
const SFXFormat& format = resource->getFormat();
Con::printf( "%s SFX: %s (%i channels, %i kHz, %.02f sec, %i kb)",
mDescription->mIsStreaming ? "Streaming" : "Loading", resource->getFileName().c_str(),
format.getChannels(),
format.getSamplesPerSecond() / 1000,
F32( resource->getDuration() ) / 1000.0f,
format.getDataLength( resource->getDuration() ) / 1024 );
#endif
ThreadSafeRef< SFXStream > sfxStream = resource->openStream();
buffer = SFX->_createBuffer( sfxStream, mDescription );
}
}
return buffer;
}
//-----------------------------------------------------------------------------
U32 SFXProfile::getSoundDuration()
{
Resource< SFXResource >& resource = getResource();
if( resource != NULL )
return mResource->getDuration();
else
return 0;
}
//-----------------------------------------------------------------------------
DefineEngineMethod( SFXProfile, getSoundDuration, F32, (),,
"Return the length of the sound data in seconds.\n\n"
"@return The length of the sound data in seconds or 0 if the sound referenced by the profile could not be found." )
{
return ( F32 ) object->getSoundDuration() * 0.001f;
}
// enable this to help verify that temp-clones of AudioProfile are being deleted
//#define TRACK_AUDIO_PROFILE_CLONES
#ifdef TRACK_AUDIO_PROFILE_CLONES
static int audio_prof_clones = 0;
#endif
SFXProfile::SFXProfile(const SFXProfile& other, bool temp_clone) : SFXTrack(other, temp_clone)
{
#ifdef TRACK_AUDIO_PROFILE_CLONES
audio_prof_clones++;
if (audio_prof_clones == 1)
Con::errorf("SFXProfile -- Clones are on the loose!");
#endif
mResource = other.mResource;
mFilename = other.mFilename;
mPreload = other.mPreload;
mBuffer = other.mBuffer; // -- AudioBuffer loaded using mFilename
mChangedSignal = other.mChangedSignal;
}
SFXProfile::~SFXProfile()
{
if (!isTempClone())
return;
// cleanup after a temp-clone
if (mDescription && mDescription->isTempClone())
{
delete mDescription;
mDescription = 0;
}
#ifdef TRACK_AUDIO_PROFILE_CLONES
if (audio_prof_clones > 0)
{
audio_prof_clones--;
if (audio_prof_clones == 0)
Con::errorf("SFXProfile -- Clones eliminated!");
}
else
Con::errorf("SFXProfile -- Too many clones deleted!");
#endif
}
// Clone and perform substitutions on the SFXProfile and on any SFXDescription
// it references.
SFXProfile* SFXProfile::cloneAndPerformSubstitutions(const SimObject* owner, S32 index)
{
if (!owner)
return this;
SFXProfile* sub_profile_db = this;
// look for mDescriptionObject subs
SFXDescription* desc_db;
if (mDescription && mDescription->getSubstitutionCount() > 0)
{
SFXDescription* orig_db = mDescription;
desc_db = new SFXDescription(*orig_db, true);
orig_db->performSubstitutions(desc_db, owner, index);
}
else
desc_db = 0;
if (this->getSubstitutionCount() > 0 || desc_db)
{
sub_profile_db = new SFXProfile(*this, true);
performSubstitutions(sub_profile_db, owner, index);
if (desc_db)
sub_profile_db->mDescription = desc_db;
}
return sub_profile_db;
}
void SFXProfile::onPerformSubstitutions()
{
if ( SFX )
{
// If preload is enabled we load the resource
// and device buffer now to avoid a delay on
// first playback.
if ( mPreload && !_preloadBuffer() )
Con::errorf( "SFXProfile(%s)::onPerformSubstitutions: The preload failed!", getName() );
// We need to get device change notifications.
SFX->getEventSignal().notify( this, &SFXProfile::_onDeviceEvent );
}
}
// This allows legacy AudioProfile datablocks to be recognized as an alias
// for SFXProfile. It is intended to ease the transition from older scripts
// especially those that still need to support pre-1.7 applications.
// (This maybe removed in future releases so treat as deprecated.)
class AudioProfile : public SFXProfile
{
typedef SFXProfile Parent;
public:
DECLARE_CONOBJECT(AudioProfile);
};
IMPLEMENT_CO_DATABLOCK_V1(AudioProfile);
ConsoleDocClass( AudioProfile,
"@brief Allows legacy AudioProfile datablocks to be treated as SFXProfile datablocks.\n\n"
"@ingroup afxMisc\n"
"@ingroup AFX\n"
"@ingroup Datablocks\n"
);