sfxProfile.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "platform/platform.h"
  23. #include "sfx/sfxProfile.h"
  24. #include "sfx/sfxDescription.h"
  25. #include "sfx/sfxSystem.h"
  26. #include "sfx/sfxStream.h"
  27. #include "sim/netConnection.h"
  28. #include "core/stream/bitStream.h"
  29. #include "core/resourceManager.h"
  30. #include "console/engineAPI.h"
  31. using namespace Torque;
  32. IMPLEMENT_CO_DATABLOCK_V1( SFXProfile );
  33. ConsoleDocClass( SFXProfile,
  34. "@brief Encapsulates a single sound file for playback by the sound system.\n\n"
  35. "SFXProfile combines a sound description (SFXDescription) with a sound file such that it can be played "
  36. "by the sound system. To be able to play a sound file, the sound system will always require a profile "
  37. "for it to be created. However, several of the SFX functions (sfxPlayOnce(), sfxCreateSource()) perform "
  38. "this creation internally for convenience using temporary profile objects.\n\n"
  39. "Sound files can be in either OGG or WAV format. However, extended format support is available when using FMOD. "
  40. "See @ref SFX_formats.\n\n"
  41. "@section SFXProfile_loading Profile Loading\n\n"
  42. "By default, the sound data referenced by a profile will be loaded when the profile is first played and the "
  43. "data then kept until either the profile is deleted or until the sound device on which the sound data is held "
  44. "is deleted.\n\n"
  45. "This initial loading my incur a small delay when the sound is first played. To avoid this, a profile may be "
  46. "expicitly set to load its sound data immediately when the profile is added to the system. This is done by "
  47. "setting the #preload property to true.\n\n"
  48. "@note Sounds using streamed playback (SFXDescription::isStreaming) cannot be preloaded and will thus "
  49. "ignore the #preload flag.\n\n"
  50. "@tsexample\n"
  51. "datablock SFXProfile( Shore01Snd )\n"
  52. "{\n"
  53. " fileName = \"art/sound/Lakeshore_mono_01\";\n"
  54. " description = Shore01Looping3d;\n"
  55. " preload = true;\n"
  56. "};\n"
  57. "@endtsexample\n\n"
  58. "@ingroup SFX\n"
  59. "@ingroup Datablocks\n"
  60. );
  61. //-----------------------------------------------------------------------------
  62. SFXProfile::SFXProfile()
  63. : mPreload( false )
  64. {
  65. }
  66. //-----------------------------------------------------------------------------
  67. SFXProfile::SFXProfile( SFXDescription* desc, const String& filename, bool preload )
  68. : Parent( desc ),
  69. mFilename( filename ),
  70. mPreload( preload )
  71. {
  72. }
  73. //-----------------------------------------------------------------------------
  74. SFXProfile::~SFXProfile()
  75. {
  76. }
  77. //-----------------------------------------------------------------------------
  78. void SFXProfile::initPersistFields()
  79. {
  80. addGroup( "Sound" );
  81. addField( "filename", TypeStringFilename, Offset( mFilename, SFXProfile ),
  82. "%Path to the sound file.\n"
  83. "If the extension is left out, it will be inferred by the sound system. This allows to "
  84. "easily switch the sound format without having to go through the profiles and change the "
  85. "filenames there, too.\n" );
  86. addField( "preload", TypeBool, Offset( mPreload, SFXProfile ),
  87. "Whether to preload sound data when the profile is added to system.\n"
  88. "@note This flag is ignored by streamed sounds.\n\n"
  89. "@ref SFXProfile_loading" );
  90. endGroup( "Sound" );
  91. Parent::initPersistFields();
  92. }
  93. //-----------------------------------------------------------------------------
  94. bool SFXProfile::onAdd()
  95. {
  96. if( !Parent::onAdd() )
  97. return false;
  98. // If we're a streaming profile we don't preload
  99. // or need device events.
  100. if( SFX && !mDescription->mIsStreaming )
  101. {
  102. // If preload is enabled we load the resource
  103. // and device buffer now to avoid a delay on
  104. // first playback.
  105. if( mPreload && !_preloadBuffer() )
  106. Con::errorf( "SFXProfile(%s)::onAdd: The preload failed!", getName() );
  107. }
  108. _registerSignals();
  109. return true;
  110. }
  111. //-----------------------------------------------------------------------------
  112. void SFXProfile::onRemove()
  113. {
  114. _unregisterSignals();
  115. Parent::onRemove();
  116. }
  117. //-----------------------------------------------------------------------------
  118. bool SFXProfile::preload( bool server, String &errorStr )
  119. {
  120. if ( !Parent::preload( server, errorStr ) )
  121. return false;
  122. // TODO: Investigate how NetConnection::filesWereDownloaded()
  123. // effects the system.
  124. // Validate the datablock... has nothing to do with mPreload.
  125. if( !server &&
  126. NetConnection::filesWereDownloaded() &&
  127. ( mFilename.isEmpty() || !SFXResource::exists( mFilename ) ) )
  128. return false;
  129. return true;
  130. }
  131. //-----------------------------------------------------------------------------
  132. void SFXProfile::packData(BitStream* stream)
  133. {
  134. Parent::packData( stream );
  135. char buffer[256];
  136. if ( mFilename.isEmpty() )
  137. buffer[0] = 0;
  138. else
  139. dStrncpy( buffer, mFilename.c_str(), 256 );
  140. stream->writeString( buffer );
  141. stream->writeFlag( mPreload );
  142. }
  143. //-----------------------------------------------------------------------------
  144. void SFXProfile::unpackData(BitStream* stream)
  145. {
  146. Parent::unpackData( stream );
  147. char buffer[256];
  148. stream->readString( buffer );
  149. mFilename = buffer;
  150. mPreload = stream->readFlag();
  151. }
  152. //-----------------------------------------------------------------------------
  153. bool SFXProfile::isLooping() const
  154. {
  155. return getDescription()->mIsLooping;
  156. }
  157. //-----------------------------------------------------------------------------
  158. void SFXProfile::_registerSignals()
  159. {
  160. SFX->getEventSignal().notify( this, &SFXProfile::_onDeviceEvent );
  161. ResourceManager::get().getChangedSignal().notify( this, &SFXProfile::_onResourceChanged );
  162. }
  163. //-----------------------------------------------------------------------------
  164. void SFXProfile::_unregisterSignals()
  165. {
  166. ResourceManager::get().getChangedSignal().remove( this, &SFXProfile::_onResourceChanged );
  167. if( SFX )
  168. SFX->getEventSignal().remove( this, &SFXProfile::_onDeviceEvent );
  169. }
  170. //-----------------------------------------------------------------------------
  171. void SFXProfile::_onDeviceEvent( SFXSystemEventType evt )
  172. {
  173. switch( evt )
  174. {
  175. case SFXSystemEvent_CreateDevice:
  176. {
  177. if( mPreload && !mDescription->mIsStreaming && !_preloadBuffer() )
  178. Con::errorf( "SFXProfile::_onDeviceEvent: The preload failed! %s", getName() );
  179. break;
  180. }
  181. default:
  182. break;
  183. }
  184. }
  185. //-----------------------------------------------------------------------------
  186. void SFXProfile::_onResourceChanged( const Torque::Path& path )
  187. {
  188. if( path != Path( mFilename ) )
  189. return;
  190. // Let go of the old resource and buffer.
  191. mResource = NULL;
  192. mBuffer = NULL;
  193. // Load the new resource.
  194. getResource();
  195. if( mPreload && !mDescription->mIsStreaming )
  196. {
  197. if( !_preloadBuffer() )
  198. Con::errorf( "SFXProfile::_onResourceChanged() - failed to preload '%s'", mFilename.c_str() );
  199. }
  200. mChangedSignal.trigger( this );
  201. }
  202. //-----------------------------------------------------------------------------
  203. bool SFXProfile::_preloadBuffer()
  204. {
  205. AssertFatal( !mDescription->mIsStreaming, "SFXProfile::_preloadBuffer() - must not be called for streaming profiles" );
  206. mBuffer = _createBuffer();
  207. return ( !mBuffer.isNull() );
  208. }
  209. //-----------------------------------------------------------------------------
  210. Resource<SFXResource>& SFXProfile::getResource()
  211. {
  212. if( !mResource && !mFilename.isEmpty() )
  213. mResource = SFXResource::load( mFilename );
  214. return mResource;
  215. }
  216. //-----------------------------------------------------------------------------
  217. SFXBuffer* SFXProfile::getBuffer()
  218. {
  219. if ( mDescription->mIsStreaming )
  220. {
  221. // Streaming requires unique buffers per
  222. // source, so this creates a new buffer.
  223. if ( SFX )
  224. return _createBuffer();
  225. return NULL;
  226. }
  227. if ( mBuffer.isNull() )
  228. _preloadBuffer();
  229. return mBuffer;
  230. }
  231. //-----------------------------------------------------------------------------
  232. SFXBuffer* SFXProfile::_createBuffer()
  233. {
  234. SFXBuffer* buffer = 0;
  235. // Try to create through SFXDevie.
  236. if( !mFilename.isEmpty() && SFX )
  237. {
  238. buffer = SFX->_createBuffer( mFilename, mDescription );
  239. if( buffer )
  240. {
  241. #ifdef TORQUE_DEBUG
  242. const SFXFormat& format = buffer->getFormat();
  243. Con::printf( "%s SFX: %s (%i channels, %i kHz, %.02f sec, %i kb)",
  244. mDescription->mIsStreaming ? "Streaming" : "Loaded", mFilename.c_str(),
  245. format.getChannels(),
  246. format.getSamplesPerSecond() / 1000,
  247. F32( buffer->getDuration() ) / 1000.0f,
  248. format.getDataLength( buffer->getDuration() ) / 1024 );
  249. #endif
  250. }
  251. }
  252. // If that failed, load through SFXResource.
  253. if( !buffer )
  254. {
  255. Resource< SFXResource >& resource = getResource();
  256. if( resource != NULL && SFX )
  257. {
  258. #ifdef TORQUE_DEBUG
  259. const SFXFormat& format = resource->getFormat();
  260. Con::printf( "%s SFX: %s (%i channels, %i kHz, %.02f sec, %i kb)",
  261. mDescription->mIsStreaming ? "Streaming" : "Loading", resource->getFileName().c_str(),
  262. format.getChannels(),
  263. format.getSamplesPerSecond() / 1000,
  264. F32( resource->getDuration() ) / 1000.0f,
  265. format.getDataLength( resource->getDuration() ) / 1024 );
  266. #endif
  267. ThreadSafeRef< SFXStream > sfxStream = resource->openStream();
  268. buffer = SFX->_createBuffer( sfxStream, mDescription );
  269. }
  270. }
  271. return buffer;
  272. }
  273. //-----------------------------------------------------------------------------
  274. U32 SFXProfile::getSoundDuration()
  275. {
  276. Resource< SFXResource >& resource = getResource();
  277. if( resource != NULL )
  278. return mResource->getDuration();
  279. else
  280. return 0;
  281. }
  282. //-----------------------------------------------------------------------------
  283. DefineEngineMethod( SFXProfile, getSoundDuration, F32, (),,
  284. "Return the length of the sound data in seconds.\n\n"
  285. "@return The length of the sound data in seconds or 0 if the sound referenced by the profile could not be found." )
  286. {
  287. return ( F32 ) object->getSoundDuration() * 0.001f;
  288. }