diff options
Diffstat (limited to 'hammer/soundsystem.cpp')
| -rw-r--r-- | hammer/soundsystem.cpp | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/hammer/soundsystem.cpp b/hammer/soundsystem.cpp new file mode 100644 index 0000000..000aaef --- /dev/null +++ b/hammer/soundsystem.cpp @@ -0,0 +1,548 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Sound management functions. Exposes a list of available sounds. +// +// $NoKeywords: $ +//=============================================================================// + +#include "stdafx.h" +#include "soundsystem.h" +#include "mmsystem.h" +#include "filesystem.h" +#include "KeyValues.h" +#include "hammer.h" +#include "HammerScene.h" +#include "ScenePreviewDlg.h" +#include "soundchars.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + + +// FIXME: Put gamesounds parsing into shared code somewhere +#define MANIFEST_FILE "scripts/game_sounds_manifest.txt" +#define SOUNDGENDER_MACRO "$gender" +#define SOUNDGENDER_MACRO_LENGTH 7 // Length of above including $ + + +// Sounds we're playing are loaded into here for Windows to access while playing them. +CUtlVector<char> g_SoundPlayData; + + +//----------------------------------------------------------------------------- +// Singleton sound system +//----------------------------------------------------------------------------- +CSoundSystem g_Sounds; + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +CSoundSystem::CSoundSystem() +{ +} + +CSoundSystem::~CSoundSystem() +{ + ShutDown(); +} + + +//----------------------------------------------------------------------------- +// Initialization, shutdown +//----------------------------------------------------------------------------- +bool CSoundSystem::Initialize( ) +{ + for ( int i = 0; i < SOUND_TYPE_COUNT; ++i ) + { + m_SoundList[i].m_Sounds.EnsureCapacity( 1024 ); + m_SoundList[i].m_pStrings = NULL; + + if (!BuildSoundList( (SoundType_t)i ) ) + return false; + } + + return true; +} + +void CSoundSystem::ShutDown(void) +{ + for ( int i = 0; i < SOUND_TYPE_COUNT; ++i ) + { + CleanupSoundList( (SoundType_t)i ); + } +} + + +//----------------------------------------------------------------------------- +// Build the list of sounds +//----------------------------------------------------------------------------- +bool CSoundSystem::BuildSoundList( SoundType_t type ) +{ + CleanupSoundList( type ); + + switch( type ) + { + case SOUND_TYPE_RAW: + return RecurseIntoDirectories( "sound", &CSoundSystem::ProcessDirectory_RawFileList ); + + case SOUND_TYPE_GAMESOUND: + return BuildGameSoundList(); + + case SOUND_TYPE_SCENE: + return RecurseIntoDirectories( "scenes", &CSoundSystem::ProcessDirectory_SceneFileList ); + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Cleans up the sound list +//----------------------------------------------------------------------------- +void CSoundSystem::CleanupSoundList( SoundType_t type ) +{ + m_SoundList[type].m_Sounds.RemoveAll(); + DestroyStringCache( m_SoundList[type].m_pStrings ); + m_SoundList[type].m_pStrings = NULL; +} + + +//----------------------------------------------------------------------------- +// Allocate, deallocate a string cache +//----------------------------------------------------------------------------- +CSoundSystem::StringCache_t *CSoundSystem::CreateStringCache( CSoundSystem::StringCache_t* pPrevious ) +{ + StringCache_t *pCache = new StringCache_t; + pCache->m_nTailIndex = 0; + pCache->m_pNext = pPrevious; + return pCache; +} + +void CSoundSystem::DestroyStringCache( CSoundSystem::StringCache_t *pCache ) +{ + if ( pCache ) + { + DestroyStringCache( pCache->m_pNext ); + delete pCache; + } +} + + +//----------------------------------------------------------------------------- +// Adds a string to the string cache +//----------------------------------------------------------------------------- +char *CSoundSystem::AddStringToCache( SoundType_t type, const char *pString ) +{ + int copyLen = V_strlen( pString ) + 1; + + StringCache_t *pCache = m_SoundList[type].m_pStrings; + if ( (!pCache) || ( copyLen + pCache->m_nTailIndex > StringCache_t::STRING_CACHE_SIZE ) ) + { + m_SoundList[type].m_pStrings = CreateStringCache( pCache ); + pCache = m_SoundList[type].m_pStrings; + } + + char fixedString[MAX_PATH]; + V_strncpy( fixedString, pString, sizeof( fixedString ) ); + V_FixSlashes( fixedString ); + copyLen = V_strlen( fixedString ) + 1; + + char *pDest = &pCache->m_pBuf[ pCache->m_nTailIndex ]; + memcpy( pDest, fixedString, copyLen ); + pCache->m_nTailIndex += copyLen; + + return pDest; +} + + +//----------------------------------------------------------------------------- +// Adds a sound to a sound list +//----------------------------------------------------------------------------- +void CSoundSystem::AddSoundToList( SoundType_t type, const char *pSoundName, const char *pActualFile, const char *pSourceFile ) +{ + // FIXME: Optimize the allocation pattern? + int i = m_SoundList[type].m_Sounds.AddToTail(); + SoundInfo_t &info = m_SoundList[type].m_Sounds[i]; + + info.m_pSoundName = AddStringToCache( type, pSoundName ); + + if ( type == SOUND_TYPE_RAW ) + { + info.m_pSoundFile = info.m_pSoundName; + info.m_pSourceFile = info.m_pSoundName; + } + else + { + info.m_pSoundFile = AddStringToCache( type, pActualFile ); + info.m_pSourceFile = pSourceFile; + } +} + + +//----------------------------------------------------------------------------- +// Add all sounds that lie within a single directory +//----------------------------------------------------------------------------- +void CSoundSystem::BuildFileListInDirectory( char const* pDirectoryName, const char *pExt, SoundType_t soundType ) +{ + Assert( Q_strlen( pExt ) <= 3 ); + + int nDirectoryNameLen = V_strlen( pDirectoryName ); + char *pWildCard = ( char * )stackalloc( nDirectoryNameLen + 7 ); + Q_snprintf( pWildCard, nDirectoryNameLen + 7, "%s/*.%s", pDirectoryName, pExt ); + + FileFindHandle_t findHandle; + const char *pFileName = g_pFullFileSystem->FindFirst( pWildCard, &findHandle ); + for ( ; pFileName; pFileName = g_pFullFileSystem->FindNext( findHandle ) ) + { + if( g_pFullFileSystem->FindIsDirectory( findHandle ) ) + continue; + + // Strip off the 'sound/' part of the sound name. + int nAllocSize = nDirectoryNameLen + Q_strlen(pFileName) + 2; + char *pFileNameWithPath = (char *)stackalloc( nAllocSize ); + + const char *pStartPos = max( strchr( pDirectoryName, '/' ), strchr( pDirectoryName, '\\' ) ); + if ( pStartPos ) + Q_snprintf( pFileNameWithPath, nAllocSize, "%s%c%s", pStartPos+1, CORRECT_PATH_SEPARATOR, pFileName ); + else + V_strncpy( pFileNameWithPath, pFileName, nAllocSize ); + + Q_strnlwr( pFileNameWithPath, nAllocSize ); + AddSoundToList( soundType, pFileNameWithPath, pFileNameWithPath, NULL ); + } + g_pFullFileSystem->FindClose( findHandle ); +} + + +//----------------------------------------------------------------------------- +// Populate the list of .WAV files +//----------------------------------------------------------------------------- +bool CSoundSystem::RecurseIntoDirectories( char const* pDirectoryName, pDirCallbackFn fn ) +{ + // Have the callback process the directory. + if ( !(this->*fn)( pDirectoryName ) ) + return false; + + int nDirectoryNameLen = Q_strlen( pDirectoryName ); + + char *pWildCard = ( char * )stackalloc( nDirectoryNameLen + 5 ); + strcpy(pWildCard, pDirectoryName); + strcat(pWildCard, "/*.*"); + int nPathStrLen = nDirectoryNameLen + 1; + + FileFindHandle_t findHandle; + const char *pFileName = g_pFullFileSystem->FindFirst( pWildCard, &findHandle ); + for ( ; pFileName; pFileName = g_pFullFileSystem->FindNext( findHandle ) ) + { + if ((pFileName[0] != '.') || (pFileName[1] != '.' && pFileName[1] != 0)) + { + if( !g_pFullFileSystem->FindIsDirectory( findHandle ) ) + continue; + + int fileNameStrLen = Q_strlen( pFileName ); + char *pFileNameWithPath = ( char * )stackalloc( nPathStrLen + fileNameStrLen + 1 ); + memcpy( pFileNameWithPath, pWildCard, nPathStrLen ); + pFileNameWithPath[nPathStrLen] = '\0'; + Q_strncat( pFileNameWithPath, pFileName, nPathStrLen + fileNameStrLen + 1 ); + + if (!RecurseIntoDirectories( pFileNameWithPath, fn )) + return false; + } + } + return true; +} + + +//----------------------------------------------------------------------------- +// Populate the list of .WAV files +//----------------------------------------------------------------------------- +bool CSoundSystem::ProcessDirectory_RawFileList( char const* pDirectoryName ) +{ + if ( !g_pFileSystem ) + return false; + + Assert( Q_strnicmp( pDirectoryName, "sound", 5 ) == 0 ); + + // Get all sound files out of this directory + BuildFileListInDirectory( pDirectoryName, "wav", SOUND_TYPE_RAW ); + BuildFileListInDirectory( pDirectoryName, "mp3", SOUND_TYPE_RAW ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Populate the list of .VCD files +//----------------------------------------------------------------------------- +bool CSoundSystem::ProcessDirectory_SceneFileList( char const* pDirectoryName ) +{ + if ( !g_pFileSystem ) + return false; + + // Get all sound files out of this directory + BuildFileListInDirectory( pDirectoryName, "vcd", SOUND_TYPE_SCENE ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Splits a name into 2 +//----------------------------------------------------------------------------- +static void SplitName( char const *input, int splitchar, int splitlen, char *before, int beforelen, char *after, int afterlen ) +{ + char const *in = input; + char *out = before; + + int c = 0; + int l = 0; + int maxl = beforelen; + while ( *in ) + { + if ( c == splitchar ) + { + while ( --splitlen >= 0 ) + { + in++; + } + + *out = 0; + out = after; + maxl = afterlen; + c++; + continue; + } + + if ( l >= maxl ) + { + in++; + c++; + continue; + } + + *out++ = *in++; + l++; + c++; + } + + *out = 0; +} + + +//----------------------------------------------------------------------------- +// Gamesounds may have macros embedded in them +//----------------------------------------------------------------------------- +void CSoundSystem::AddGameSoundToList( const char *pGameSound, char const *pFileName, const char *pSourceFile ) +{ + char const *p = Q_stristr( pFileName, SOUNDGENDER_MACRO ); + if ( !p ) + { + AddSoundToList( SOUND_TYPE_GAMESOUND, pGameSound, pFileName, pSourceFile ); + return; + } + + int offset = p - pFileName; + Assert( offset >= 0 ); + int duration = SOUNDGENDER_MACRO_LENGTH; + + // Create a "male" version of the sound only for browsing + char before[ 256 ], after[ 256 ]; + Q_memset( before, 0, sizeof( before ) ); + Q_memset( after, 0, sizeof( after ) ); + + SplitName( pFileName, offset, duration, before, sizeof( before ), after, sizeof( after ) ); + + char temp[ 256 ]; + Q_snprintf( temp, sizeof( temp ), "%s%s%s", before, "male", after ); + AddSoundToList( SOUND_TYPE_GAMESOUND, pGameSound, temp, pSourceFile ); +} + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgoff.h> + +//----------------------------------------------------------------------------- +// Load all game sounds from a particular file +//----------------------------------------------------------------------------- +void CSoundSystem::AddGameSoundsFromFile( const char *pFileName ) +{ + KeyValues *kv = new KeyValues( pFileName ); + if ( !kv->LoadFromFile( g_pFileSystem, pFileName, "GAME" ) ) + { + kv->deleteThis(); + return; + } + + const char *pSourceFile = AddStringToCache( SOUND_TYPE_GAMESOUND, pFileName ); + + // parse out all of the top level sections and save their names + for ( KeyValues *pKeys = kv; pKeys; pKeys = pKeys->GetNextKey() ) + { + if ( !pKeys->GetFirstSubKey() ) + continue; + + const char *pRawFile = pKeys->GetString( "wave", NULL ); + if ( pRawFile ) + { + AddGameSoundToList( pKeys->GetName(), pRawFile, pSourceFile ); + } + else + { + KeyValues *pRndWave = pKeys->FindKey( "rndwave" ); + if ( pRndWave ) + { + KeyValues *pFirstFile = pRndWave->GetFirstSubKey(); + if ( pFirstFile ) + { + AddGameSoundToList( pKeys->GetName(), pFirstFile->GetString(), pSourceFile ); + } + } + } + } + + if ( kv ) + { + kv->deleteThis(); + } +} + + +//----------------------------------------------------------------------------- +// Populate the list of game sounds +//----------------------------------------------------------------------------- +bool CSoundSystem::BuildGameSoundList() +{ + KeyValues *manifest = new KeyValues( MANIFEST_FILE ); + if ( !manifest->LoadFromFile( g_pFileSystem, MANIFEST_FILE, "GAME" ) ) + { + manifest->deleteThis(); + return false; + } + + for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() ) + { + if ( !Q_stricmp( sub->GetName(), "precache_file" ) || + !Q_stricmp( sub->GetName(), "declare_file" ) || + !Q_stricmp( sub->GetName(), "preload_file" ) ) + { + // Add and always precache + AddGameSoundsFromFile( sub->GetString() ); + } + } + manifest->deleteThis(); + return true; +} + + +//----------------------------------------------------------------------------- +// Plays a sound +//----------------------------------------------------------------------------- + +bool CSoundSystem::FindSoundByName( const char *pFilename, SoundType_t *type, int *nIndex ) +{ + char searchStr[MAX_PATH]; + V_strncpy( searchStr, pFilename, sizeof( searchStr ) ); + V_FixSlashes( searchStr ); + + for ( int i = SOUND_TYPE_COUNT; --i >= 0; ) + { + for ( int j = SoundCount( (SoundType_t)i ); --j >= 0; ) + { + if ( Q_stristr( searchStr, SoundName( (SoundType_t)i, j ) ) ) + { + *type = (SoundType_t)i; + *nIndex = j; + return true; + } + } + } + + return false; +} + + +bool CSoundSystem::PlayScene( const char *pFileName ) +{ + char fullFilename[MAX_PATH]; + V_snprintf( fullFilename, sizeof( fullFilename ), "scenes%c%s", CORRECT_PATH_SEPARATOR, pFileName ); + CChoreoScene *pScene = HammerLoadScene( fullFilename ); + if ( !pScene ) + return false; + + CScenePreviewDlg dlg( pScene, pFileName ); + dlg.DoModal(); + return true; +} + + +//----------------------------------------------------------------------------- +// Plays a sound +//----------------------------------------------------------------------------- + +bool CSoundSystem::Play( SoundType_t type, int nIndex ) +{ + const char *pFileName = SoundFile( type, nIndex ); + if ( !pFileName ) + return false; + + // If it's a scene, get the first sound in the scene. + if ( type == SOUND_TYPE_SCENE ) + { + return PlayScene( pFileName ); + } + + // Voiceover files have this. + pFileName = PSkipSoundChars( pFileName ); + + char pRelativePath[MAX_PATH]; + Q_snprintf( pRelativePath, MAX_PATH, "sound/%s", pFileName ); + + // Stop any previously-playing sound. + StopSound(); + + // We used to use GetLocalPath, but that doesn't work under Steam. + FileHandle_t fp = g_pFileSystem->Open( pRelativePath, "rb" ); + if ( fp ) + { + g_SoundPlayData.SetSize( g_pFileSystem->Size( fp ) ); + if ( g_pFileSystem->Read( g_SoundPlayData.Base(), g_SoundPlayData.Count(), fp ) == g_SoundPlayData.Count() ) + { + return (PlaySound( g_SoundPlayData.Base(), NULL, SND_ASYNC | SND_MEMORY ) != FALSE); + } + g_pFileSystem->Close( fp ); + } + return false; +} + + +//----------------------------------------------------------------------------- +// Stops any playing sound. +//----------------------------------------------------------------------------- +void CSoundSystem::StopSound() +{ + PlaySound( NULL, NULL, SND_ASYNC | SND_MEMORY ); +} + + +//----------------------------------------------------------------------------- +// Opens the source file associated with a sound +//----------------------------------------------------------------------------- +void CSoundSystem::OpenSource( SoundType_t type, int nIndex ) +{ + if ( type == SOUND_TYPE_RAW ) + return; + + const char *pFileName = SoundSourceFile( type, nIndex ); + if ( pFileName ) + { + char pRelativePath[MAX_PATH]; + Q_snprintf( pRelativePath, MAX_PATH, "%s", pFileName ); + + char pFullPath[MAX_PATH]; + if ( g_pFullFileSystem->GetLocalPath( pRelativePath, pFullPath, MAX_PATH ) ) + { + ShellExecute( NULL, "open", pFullPath, NULL, NULL, SW_SHOWNORMAL ); + } + } +} + |