mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 04:34:48 +00:00
Ogg file from libsndfile now working. we can pull more information from the format and specifics of the file from libsndfile should maybe look at updating all the parameters around mFormat so it feeds openal settings better. Next step is to remove the other stream classes.
498 lines
15 KiB
C++
498 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"
|
|
#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()
|
|
{
|
|
docsURL;
|
|
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(),
|
|
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"
|
|
);
|
|
|