//----------------------------------------------------------------------------- // 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& 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" );