123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496 |
- //-----------------------------------------------------------------------------
- // 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"
- #include "core/stream/fileStream.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. "
- "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( StringTable->insert(filename.c_str()) ),
- 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 && SFXResource::exists(mFilename))
- mResource = SFXResource::load(mFilename);
- else
- mResource = NULL;
- 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"
- );
|