This commit is contained in:
FluorescentCIAAfricanAmerican
2020-04-22 12:56:21 -04:00
commit 3bf9df6b27
15370 changed files with 5489726 additions and 0 deletions

149
engine/DevShotGenerator.cpp Normal file
View File

@ -0,0 +1,149 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "DevShotGenerator.h"
#include "cmd.h"
#include "fmtstr.h"
#include "host.h"
#include "tier0/icommandline.h"
#include <tier0/dbg.h>
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define PAUSE_FRAMES_BETWEEN_MAPS 300
#define PAUSE_TIME_BETWEEN_MAPS 2.0f
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void DevShotGenerator_Usage()
{
//
Msg( "-makedevshots usage:\n" );
Msg( " [ -usedevshotsfile filename ] -- get map list from specified file, default is to build for maps/*.bsp\n" );
Msg( " [ -startmap mapname ] -- restart generation at specified map (after crash, implies resume)\n" );
Msg( " [ -condebug ] -- prepend console.log entries with mapname or engine if not in a map\n" );
Msg( " [ +map mapname ] -- generate devshots for specified map and exit after that map\n" );
}
void DevShotGenerator_Init()
{
// check for devshot generation
if ( CommandLine()->FindParm("-makedevshots") )
{
bool usemaplistfile = false;
if ( CommandLine()->FindParm("-usedevshotsfile") )
{
usemaplistfile = true;
}
DevShotGenerator().EnableDevShotGeneration( usemaplistfile );
}
}
void DevShotGenerator_Shutdown()
{
DevShotGenerator().Shutdown();
}
void DevShotGenerator_BuildMapList()
{
DevShotGenerator().BuildMapList();
}
CDevShotGenerator g_DevShotGenerator;
CDevShotGenerator &DevShotGenerator()
{
return g_DevShotGenerator;
}
void CL_DevShots_NextMap()
{
DevShotGenerator().NextMap();
}
static ConCommand devshots_nextmap( "devshots_nextmap", CL_DevShots_NextMap, "Used by the devshots system to go to the next map in the devshots maplist." );
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CDevShotGenerator::CDevShotGenerator()
{
m_bUsingMapList = false;
m_bDevShotsEnabled = false;
m_iCurrentMap = 0;
}
void CDevShotGenerator::BuildMapList()
{
if ( !m_bDevShotsEnabled )
return;
DevShotGenerator_Usage();
// Get the maplist file, if any
const char *pMapFile = NULL;
CommandLine()->CheckParm( "-usedevshotsfile", &pMapFile );
// Build the map list
if ( !BuildGeneralMapList( &m_Maps, CommandLine()->FindParm("-usedevshotsfile") != 0, pMapFile, "devshots", &m_iCurrentMap ) )
{
m_bDevShotsEnabled = false;
}
}
void CDevShotGenerator::NextMap()
{
if ( !m_bDevShotsEnabled )
return;
//Msg("DEVSHOTS: Nextmap command received.\n");
if (m_Maps.IsValidIndex(m_iCurrentMap))
{
//Msg("DEVSHOTS: Switching to %s (%d).\n", m_Maps[m_iCurrentMap].name, m_iCurrentMap );
CFmtStr str("map %s\n", m_Maps[m_iCurrentMap].name);
Cbuf_AddText( str.Access() );
++m_iCurrentMap;
}
else
{
//Msg("DEVSHOTS: Finished on map %d.\n", m_iCurrentMap);
// no more levels, just quit
Cbuf_AddText( "quit\n" );
}
}
//-----------------------------------------------------------------------------
// Purpose: initializes the object to enable dev shot generation
//-----------------------------------------------------------------------------
void CDevShotGenerator::EnableDevShotGeneration( bool usemaplistfile )
{
m_bUsingMapList = usemaplistfile;
m_bDevShotsEnabled = true;
}
//-----------------------------------------------------------------------------
// Purpose: starts the first map
//-----------------------------------------------------------------------------
void CDevShotGenerator::StartDevShotGeneration()
{
BuildMapList();
CFmtStr str("map %s\n", m_Maps[m_iCurrentMap].name);
Cbuf_AddText( str.Access() );
++m_iCurrentMap;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CDevShotGenerator::Shutdown()
{
}

58
engine/DevShotGenerator.h Normal file
View File

@ -0,0 +1,58 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#ifndef DEVSHOTGENERATOR_H
#define DEVSHOTGENERATOR_H
#ifdef _WIN32
#pragma once
#endif
#include "filesystem.h"
#include "utlvector.h"
#include "utlsymbol.h"
#include "MapReslistGenerator.h"
// initialization
void DevShotGenerator_Init();
void DevShotGenerator_Shutdown();
void DevShotGenerator_BuildMapList();
//-----------------------------------------------------------------------------
// Purpose: Takes development screenshots at camera positions over a set of levels
//-----------------------------------------------------------------------------
class CDevShotGenerator
{
public:
CDevShotGenerator();
// initializes the object to enable devshot generation
void EnableDevShotGeneration( bool usemaplistfile );
// starts the dev shot generation (starts cycling maps)
void StartDevShotGeneration();
void BuildMapList();
void NextMap();
void Shutdown();
// returns true if reslist generation is enabled
bool IsEnabled() { return m_bDevShotsEnabled; }
private:
bool m_bDevShotsEnabled;
int m_iCurrentMap;
bool m_bUsingMapList;
// list of all maps to scan
CUtlVector<maplist_map_t> m_Maps;
};
// singleton accessor
CDevShotGenerator &DevShotGenerator();
#endif // DEVSHOTGENERATOR_H

View File

@ -0,0 +1,453 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "mathlib/vector.h"
#include "DownloadListGenerator.h"
#include "filesystem.h"
#include "filesystem_engine.h"
#include "sys.h"
#include "cmd.h"
#include "common.h"
#include "quakedef.h"
#include "vengineserver_impl.h"
#include "tier1/strtools.h"
#include "tier0/icommandline.h"
#include "checksum_engine.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <tier0/dbg.h>
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
CDownloadListGenerator g_DownloadListGenerator;
CDownloadListGenerator &DownloadListGenerator()
{
return g_DownloadListGenerator;
}
ConVar sv_logdownloadlist( "sv_logdownloadlist", IsX360() ? "0" : "1" );
extern int GetSvPureMode();
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CDownloadListGenerator::CDownloadListGenerator()
: m_AlreadyWrittenFileNames( 0, 0, true )
{
m_hReslistFile = FILESYSTEM_INVALID_HANDLE;
m_pStringTable = NULL;
m_mapName[0] = 0;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CDownloadListGenerator::SetStringTable( INetworkStringTable *pStringTable )
{
if ( IsX360() )
{
// not supporting
return;
}
m_pStringTable = pStringTable;
// reset the duplication list
m_AlreadyWrittenFileNames.RemoveAll();
// add in the bsp file to the list, and its node graph and nav mesh
char path[_MAX_PATH];
Q_snprintf(path, sizeof(path), "maps\\%s.bsp", m_mapName);
OnResourcePrecached(path);
bool useNodeGraph = true;
KeyValues *modinfo = new KeyValues("ModInfo");
if ( modinfo->LoadFromFile( g_pFileSystem, "gameinfo.txt" ) )
{
useNodeGraph = modinfo->GetInt( "nodegraph", 1 ) != 0;
}
modinfo->deleteThis();
if ( useNodeGraph )
{
Q_snprintf(path, sizeof(path), "maps\\graphs\\%s.ain", m_mapName);
OnResourcePrecached(path);
}
Q_snprintf(path, sizeof(path), "maps\\%s.nav", m_mapName);
OnResourcePrecached(path);
char resfilename[MAX_OSPATH];
KeyValues *resfilekeys = new KeyValues( "resourefiles" );
Q_snprintf( resfilename, sizeof( resfilename), "maps/%s.res", m_mapName );
if ( resfilekeys->LoadFromFile( g_pFileSystem, resfilename, "GAME" ) )
{
KeyValues *entry = resfilekeys->GetFirstSubKey();
while ( entry )
{
OnResourcePrecached( entry->GetName() );
entry = entry->GetNextKey();
}
}
resfilekeys->deleteThis();
}
//-----------------------------------------------------------------------------
// Purpose: call to mark level load/end
//-----------------------------------------------------------------------------
void CDownloadListGenerator::OnLevelLoadStart(const char *levelName)
{
if ( IsX360() )
{
// not supporting
return;
}
// close the previous level reslist, if any
if (m_hReslistFile != FILESYSTEM_INVALID_HANDLE)
{
g_pFileSystem->Close(m_hReslistFile);
m_hReslistFile = FILESYSTEM_INVALID_HANDLE;
}
// reset the duplication list
m_AlreadyWrittenFileNames.RemoveAll();
if ( sv_logdownloadlist.GetBool() )
{
// open the new level reslist
char path[MAX_OSPATH];
g_pFileSystem->CreateDirHierarchy( "DownloadLists", "MOD" );
Q_snprintf(path, sizeof(path), "DownloadLists/%s.lst", levelName);
m_hReslistFile = g_pFileSystem->Open(path, "wt", "GAME");
}
// add a slash to the end of com_gamedir, so we can only deal with files for this mod
Q_snprintf( m_gameDir, sizeof(m_gameDir), "%s/", com_gamedir );
Q_FixSlashes( m_gameDir );
// save off the map name
Q_snprintf( m_mapName, sizeof( m_mapName ), "%s", levelName );
}
//-----------------------------------------------------------------------------
// Purpose: call to mark level load/end
//-----------------------------------------------------------------------------
void CDownloadListGenerator::OnLevelLoadEnd()
{
if ( IsX360() )
{
// not supporting
return;
}
if ( m_hReslistFile != FILESYSTEM_INVALID_HANDLE )
{
g_pFileSystem->Close(m_hReslistFile);
m_hReslistFile = FILESYSTEM_INVALID_HANDLE;
}
m_pStringTable = NULL;
}
//-----------------------------------------------------------------------------
// Purpose: logs and handles mdl files being precaches
//-----------------------------------------------------------------------------
void CDownloadListGenerator::OnModelPrecached(const char *relativePathFileName)
{
if ( IsX360() )
{
// not supporting
return;
}
if (Q_strstr(relativePathFileName, ".vmt"))
{
// it's a materials file, make sure that it starts in the materials directory, and we get the .vtf
char file[_MAX_PATH];
if (!Q_strnicmp(relativePathFileName, "materials", strlen("materials")))
{
Q_strncpy(file, relativePathFileName, sizeof(file));
}
else
{
// prepend the materials directory
Q_snprintf(file, sizeof(file), "materials\\%s", relativePathFileName);
}
OnResourcePrecached(file);
// get the matching vtf file
char *ext = Q_strstr(file, ".vmt");
if (ext)
{
Q_strncpy(ext, ".vtf", 5);
OnResourcePrecached(file);
}
}
else
{
OnResourcePrecached(relativePathFileName);
}
}
//-----------------------------------------------------------------------------
// Purpose: logs sound file access
//-----------------------------------------------------------------------------
void CDownloadListGenerator::OnSoundPrecached(const char *relativePathFileName)
{
if ( IsX360() )
{
// not supporting
return;
}
// skip any special characters
if (!V_isalnum(relativePathFileName[0]))
{
++relativePathFileName;
}
// prepend the sound/ directory if necessary
char file[_MAX_PATH];
if (!Q_strnicmp(relativePathFileName, "sound", strlen("sound")))
{
Q_strncpy(file, relativePathFileName, sizeof(file));
}
else
{
// prepend the materials directory
Q_snprintf(file, sizeof(file), "sound\\%s", relativePathFileName);
}
OnResourcePrecached(file);
}
//-----------------------------------------------------------------------------
// Purpose: logs the precache as a file access
//-----------------------------------------------------------------------------
void CDownloadListGenerator::OnResourcePrecached(const char *relativePathFileName)
{
if ( IsX360() )
{
// not supporting
return;
}
// ignore empty string
if (relativePathFileName[0] == 0)
{
return;
}
// ignore files that start with '*' since they signify special models
if (relativePathFileName[0] == '*')
{
return;
}
char fullPath[_MAX_PATH];
if (g_pFileSystem->GetLocalPath(relativePathFileName, fullPath, sizeof(fullPath)))
{
OnResourcePrecachedFullPath( fullPath, relativePathFileName);
}
}
//-----------------------------------------------------------------------------
// Purpose: marks a precached file as needing a specific CRC on the client
//-----------------------------------------------------------------------------
void CDownloadListGenerator::ForceSimpleMaterial( const char *relativePathFileName )
{
if ( IsX360() )
{
// not supporting
return;
}
if ( !m_pStringTable )
return;
if ( !Q_stristr(relativePathFileName, ".vmt") && !Q_stristr(relativePathFileName, ".vtf"))
{
DevMsg( "Tried to enforce simple material on %s\n", relativePathFileName );
return;
}
// it's a materials file, make sure that it starts in the materials directory, and we get the .vtf
char szFixedFilename[_MAX_PATH];
if (!Q_strnicmp(relativePathFileName, "materials", strlen("materials")))
{
V_strcpy_safe( szFixedFilename, relativePathFileName );
}
else
{
// prepend the materials directory
V_sprintf_safe( szFixedFilename, "materials\\%s", relativePathFileName );
}
V_FixSlashes( szFixedFilename );
if ( !g_pFullFileSystem->FileExists( szFixedFilename, "game" ) )
{
DevMsg( "Cannot force simple material on %s; file not found\n", szFixedFilename );
return;
}
ExactFileUserData userData;
userData.consistencyType = CONSISTENCY_SIMPLE_MATERIAL;
userData.crc = 0;
// Only set consistency data if pure, otherwise just create entry in download list
if ( GetSvPureMode() < 0 )
{
m_pStringTable->AddString( true, szFixedFilename );
}
else
{
int index = m_pStringTable->FindStringIndex( szFixedFilename );
if ( index != INVALID_STRING_INDEX )
{
m_pStringTable->SetStringUserData( index, sizeof( ExactFileUserData ), &userData );
}
else
{
m_pStringTable->AddString( true, szFixedFilename, sizeof( ExactFileUserData ), &userData );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: marks a precached model as having a maximum size on the client
//-----------------------------------------------------------------------------
void CDownloadListGenerator::ForceModelBounds( const char *relativePathFileName, const Vector &mins, const Vector &maxs )
{
if ( IsX360() )
{
// not supporting
return;
}
if ( !m_pStringTable )
return;
if ( !relativePathFileName )
relativePathFileName = "";
if (!Q_stristr(relativePathFileName, ".mdl"))
{
DevMsg( "Warning - trying to enforce model bounds on %s\n", relativePathFileName );
return;
}
char relativeFileName[_MAX_PATH];
Q_strncpy( relativeFileName, relativePathFileName, sizeof( relativeFileName ) );
Q_FixSlashes( relativeFileName );
// Only set consistency data if pure, otherwise just create entry in download list
if ( GetSvPureMode() < 0 )
{
m_pStringTable->AddString( true, relativePathFileName );
}
else
{
ModelBoundsUserData userData;
userData.consistencyType = CONSISTENCY_BOUNDS;
userData.mins = mins;
userData.maxs = maxs;
int index = m_pStringTable->FindStringIndex( relativeFileName );
if ( index != INVALID_STRING_INDEX )
{
m_pStringTable->SetStringUserData( index, sizeof( ModelBoundsUserData ), &userData );
}
else
{
m_pStringTable->AddString( true, relativeFileName, sizeof( ModelBoundsUserData ), &userData );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Logs out file access to a file
//-----------------------------------------------------------------------------
void CDownloadListGenerator::OnResourcePrecachedFullPath( char *fullPathFileName, const char *relativeFileName )
{
if ( IsX360() )
{
// not supporting
return;
}
Q_FixSlashes( fullPathFileName );
if ( !g_pFileSystem->FileExists( fullPathFileName ) )
{
return; // don't allow files for download the server doesn't have
}
if ( Q_strncasecmp( m_gameDir, fullPathFileName, Q_strlen( m_gameDir ) ) )
{
return; // the game dir must be part of the full name
}
// make sure the filename hasn't already been written
UtlSymId_t filename = m_AlreadyWrittenFileNames.Find( fullPathFileName );
if ( filename != UTL_INVAL_SYMBOL )
{
return;
}
// record in list, so we don't write it again
m_AlreadyWrittenFileNames.AddString( fullPathFileName );
// add extras for mdl's
if (Q_strstr(relativeFileName, ".mdl"))
{
// it's a model, get it's other files as well
char file[_MAX_PATH];
Q_strncpy(file, relativeFileName, sizeof(file) - 10);
char *ext = Q_strstr(file, ".mdl");
Q_strncpy(ext, ".vvd", 10);
OnResourcePrecached(file);
Q_strncpy(ext, ".ani", 10);
OnResourcePrecached(file);
Q_strncpy(ext, ".dx80.vtx", 10);
OnResourcePrecached(file);
Q_strncpy(ext, ".dx90.vtx", 10);
OnResourcePrecached(file);
Q_strncpy(ext, ".sw.vtx", 10);
OnResourcePrecached(file);
Q_strncpy(ext, ".phy", 10);
OnResourcePrecached(file);
Q_strncpy(ext, ".jpg", 10);
OnResourcePrecached(file);
}
FileHandle_t handle = m_hReslistFile;
if ( handle != FILESYSTEM_INVALID_HANDLE )
{
g_pFileSystem->Write("\"", 1, handle);
g_pFileSystem->Write( relativeFileName, Q_strlen(relativeFileName), handle );
g_pFileSystem->Write("\"\n", 2, handle);
}
if ( m_pStringTable )
{
m_pStringTable->AddString( true, relativeFileName );
}
}

View File

@ -0,0 +1,78 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef DOWNLOADLISTGENERATOR_H
#define DOWNLOADLISTGENERATOR_H
#ifdef _WIN32
#pragma once
#endif
#include "filesystem.h"
#include "utlvector.h"
#include "utlsymbol.h"
#include "networkstringtable.h"
#define DOWNLOADABLE_FILE_TABLENAME "downloadables"
#define MAX_DOWNLOADABLE_FILES 8192
enum ConsistencyType
{
CONSISTENCY_NONE,
CONSISTENCY_EXACT,
CONSISTENCY_SIMPLE_MATERIAL, // uses ExactFileUserData
CONSISTENCY_BOUNDS,
};
struct ExactFileUserData
{
unsigned char consistencyType;
unsigned long crc;
};
struct ModelBoundsUserData
{
unsigned char consistencyType;
Vector mins;
Vector maxs;
};
//-----------------------------------------------------------------------------
// Purpose: Handles collating lists of resources on level load
// Used to generate reslists for in-game downloads
//-----------------------------------------------------------------------------
class CDownloadListGenerator
{
public:
CDownloadListGenerator();
void SetStringTable( INetworkStringTable *pStringTable );
// call to mark level load/end
void OnLevelLoadStart(const char *levelName);
void OnLevelLoadEnd();
// call to mark resources as being precached
void OnResourcePrecached(const char *relativePathFileName);
void OnModelPrecached(const char *relativePathFileName);
void OnSoundPrecached(const char *relativePathFileName);
void ForceModelBounds( const char *relativePathFileName, const Vector &mins, const Vector &maxs );
void ForceSimpleMaterial( const char *relativePathFileName );
private:
void OnResourcePrecachedFullPath(char *fullPathFileName, const char *relativeFileName );
char m_gameDir[256];
char m_mapName[64];
FileHandle_t m_hReslistFile;
CUtlSymbolTable m_AlreadyWrittenFileNames;
INetworkStringTable *m_pStringTable;
};
// singleton accessor
CDownloadListGenerator &DownloadListGenerator();
#endif // DOWNLOADLISTGENERATOR_H

View File

@ -0,0 +1,510 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "engine/IEngineSound.h"
#include "tier0/dbg.h"
#include "sound.h"
#include "client.h"
#include "vox.h"
#include "icliententity.h"
#include "icliententitylist.h"
#include "enginesingleuserfilter.h"
#include "snd_audio_source.h"
#if defined(_X360)
#include "xmp.h"
#endif
#include "tier0/vprof.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// HACK: expose in sound.h maybe?
void DSP_FastReset(int dsp);
//-----------------------------------------------------------------------------
//
// Client-side implementation of the engine sound interface
//
//-----------------------------------------------------------------------------
class CEngineSoundClient : public IEngineSound
{
public:
// constructor, destructor
CEngineSoundClient();
virtual ~CEngineSoundClient();
virtual bool PrecacheSound( const char *pSample, bool bPreload, bool bIsUISound );
virtual bool IsSoundPrecached( const char *pSample );
virtual void PrefetchSound( const char *pSample );
virtual float GetSoundDuration( const char *pSample );
virtual void EmitSound( IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample,
float flVolume, float flAttenuation, int iFlags, int iPitch, int iSpecialDSP,
const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime = 0.0f, int speakerentity = -1 );
virtual void EmitSound( IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample,
float flVolume, soundlevel_t iSoundLevel, int iFlags, int iPitch, int iSpecialDSP,
const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime = 0.0f, int speakerentity = -1 );
virtual void EmitSentenceByIndex( IRecipientFilter& filter, int iEntIndex, int iChannel, int iSentenceIndex,
float flVolume, soundlevel_t iSoundLevel, int iFlags, int iPitch, int iSpecialDSP,
const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime = 0.0f, int speakerentity = -1 );
virtual void StopSound( int iEntIndex, int iChannel, const char *pSample );
virtual void StopAllSounds(bool bClearBuffers);
virtual void SetRoomType( IRecipientFilter& filter, int roomType );
virtual void SetPlayerDSP( IRecipientFilter& filter, int dspType, bool fastReset );
virtual void EmitAmbientSound( const char *pSample, float flVolume,
int iPitch, int flags, float soundtime = 0.0f );
virtual float GetDistGainFromSoundLevel( soundlevel_t soundlevel, float dist );
// Client .dll only functions
virtual int GetGuidForLastSoundEmitted();
virtual bool IsSoundStillPlaying( int guid );
virtual void StopSoundByGuid( int guid );
// Set's master volume (0.0->1.0)
virtual void SetVolumeByGuid( int guid, float fvol );
// Retrieves list of all active sounds
virtual void GetActiveSounds( CUtlVector< SndInfo_t >& sndlist );
virtual void PrecacheSentenceGroup( const char *pGroupName );
virtual void NotifyBeginMoviePlayback();
virtual void NotifyEndMoviePlayback();
private:
void EmitSoundInternal( IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample,
float flVolume, soundlevel_t iSoundLevel, int iFlags, int iPitch, int iSpecialDSP,
const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime = 0.0f, int speakerentity = -1 );
};
//-----------------------------------------------------------------------------
// Client-server neutral sound interface accessor
//-----------------------------------------------------------------------------
static CEngineSoundClient s_EngineSoundClient;
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CEngineSoundClient, IEngineSound,
IENGINESOUND_CLIENT_INTERFACE_VERSION, s_EngineSoundClient );
IEngineSound *EngineSoundClient()
{
return &s_EngineSoundClient;
}
//-----------------------------------------------------------------------------
// constructor, destructor
//-----------------------------------------------------------------------------
CEngineSoundClient::CEngineSoundClient()
{
}
CEngineSoundClient::~CEngineSoundClient()
{
}
//-----------------------------------------------------------------------------
// Precache a particular sample
//-----------------------------------------------------------------------------
bool CEngineSoundClient::PrecacheSound( const char *pSample, bool bPreload, bool bIsUISound )
{
CSfxTable *pTable = S_PrecacheSound( pSample );
if ( pTable )
{
if ( bIsUISound )
{
S_MarkUISound( pTable );
}
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pSample -
//-----------------------------------------------------------------------------
void CEngineSoundClient::PrefetchSound( const char *pSample )
{
S_PrefetchSound( pSample, true );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pSample -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CEngineSoundClient::IsSoundPrecached( const char *pSample )
{
if ( pSample && TestSoundChar(pSample, CHAR_SENTENCE) )
{
return true;
}
int idx = cl.LookupSoundIndex( pSample );
if ( idx == -1 )
return false;
return true;
}
//-----------------------------------------------------------------------------
// Actually does the work of emitting a sound
//-----------------------------------------------------------------------------
void CEngineSoundClient::EmitSoundInternal( IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample,
float flVolume, soundlevel_t iSoundLevel, int iFlags, int iPitch, int iSpecialDSP,
const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime /*= 0.0f*/, int speakerentity /*= -1*/ )
{
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
if (flVolume < 0 || flVolume > 1)
{
Warning ("EmitSound: volume out of bounds = %f\n", flVolume);
return;
}
if ( ( iSoundLevel < soundlevel_t(MIN_SNDLVL_VALUE) ) || ( iSoundLevel > soundlevel_t(MAX_SNDLVL_VALUE) ) )
{
Warning ("EmitSound: soundlevel out of bounds = %d\n", iSoundLevel);
return;
}
if (iPitch < 0 || iPitch > 255)
{
Warning ("EmitSound: pitch out of bounds = %i\n", iPitch);
return;
}
int iSoundSource = iEntIndex;
// Handle client UI sounds
if ( iSoundSource != SOUND_FROM_UI_PANEL )
{
if (iSoundSource < 0)
iSoundSource = cl.m_nViewEntity;
// See if local player is a recipient
int i = 0;
int c = filter.GetRecipientCount();
for ( ; i < c ; i++ )
{
int index = filter.GetRecipientIndex( i );
if ( index == cl.m_nPlayerSlot + 1 )
break;
}
// Local player not receiving sound
if ( i >= c )
return;
}
CSfxTable *pSound = S_PrecacheSound(pSample);
if (!pSound)
return;
Vector vecDummyOrigin;
Vector vecDirection;
if ( iSoundSource == SOUND_FROM_UI_PANEL )
{
vecDummyOrigin.Init();
vecDirection.Init();
pOrigin = &vecDummyOrigin;
pDirection = &vecDirection;
}
else
{
// Point at origin if they didn't specify a sound source.
if (!pOrigin)
{
// Try to use the origin of the entity
IClientEntity *pEnt = entitylist->GetClientEntity( iEntIndex );
// don't update position if we stop this sound
if (pEnt && !(iFlags & SND_STOP) )
{
vecDummyOrigin = pEnt->GetRenderOrigin();
}
else
{
vecDummyOrigin.Init();
}
pOrigin = &vecDummyOrigin;
}
if (!pDirection)
{
IClientEntity *pEnt = entitylist->GetClientEntity( iEntIndex );
if (pEnt && !(iFlags & SND_STOP))
{
QAngle angles;
angles = pEnt->GetAbsAngles();
AngleVectors( angles, &vecDirection );
}
else
{
vecDirection.Init();
}
pDirection = &vecDirection;
}
}
if ( pUtlVecOrigins )
{
(*pUtlVecOrigins).AddToTail( *pOrigin );
}
float delay = 0.0f;
if ( soundtime != 0.0f )
{
// this sound was played directly on the client, use its clock sync
delay = S_ComputeDelayForSoundtime( soundtime, CLOCK_SYNC_CLIENT );
#if 0
static float lastSoundTime = 0;
Msg("[%.3f] Play %s at %.3f %.1fsms delay\n", soundtime - lastSoundTime, pSample, soundtime, delay * 1000.0f );
lastSoundTime = soundtime;
#endif
// anything over 250ms is assumed to be intentional skipping
if ( delay <= 0 && delay > -0.250f )
{
// leave a little delay to flag the channel in the low-level sound system
delay = 1e-6f;
}
}
StartSoundParams_t params;
params.staticsound = iChannel == CHAN_STATIC;
params.soundsource = iSoundSource;
params.entchannel = iChannel;
params.pSfx = pSound;
params.origin = *pOrigin;
params.direction = *pDirection;
params.bUpdatePositions = bUpdatePositions;
params.fvol = flVolume;
params.soundlevel = iSoundLevel;
params.flags = iFlags;
params.pitch = iPitch;
params.specialdsp = iSpecialDSP;
params.fromserver = false;
params.delay = delay;
params.speakerentity = speakerentity;
S_StartSound( params );
}
//-----------------------------------------------------------------------------
// Plays a sentence
//-----------------------------------------------------------------------------
void CEngineSoundClient::EmitSentenceByIndex( IRecipientFilter& filter, int iEntIndex, int iChannel,
int iSentenceIndex, float flVolume, soundlevel_t iSoundLevel, int iFlags, int iPitch, int iSpecialDSP,
const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePosition, float soundtime /*= 0.0f*/, int speakerentity /*= -1*/ )
{
if ( iSentenceIndex >= 0 )
{
char pName[8];
Q_snprintf( pName, sizeof(pName), "!%d", iSentenceIndex );
EmitSoundInternal( filter, iEntIndex, iChannel, pName, flVolume, iSoundLevel,
iFlags, iPitch, iSpecialDSP, pOrigin, pDirection, pUtlVecOrigins, bUpdatePosition, soundtime, speakerentity );
}
}
//-----------------------------------------------------------------------------
// Emits a sound
//-----------------------------------------------------------------------------
void CEngineSoundClient::EmitSound( IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample,
float flVolume, float flAttenuation, int iFlags, int iPitch, int iSpecialDSP,
const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime /*= 0.0f*/, int speakerentity /*= -1*/ )
{
VPROF( "CEngineSoundClient::EmitSound" );
EmitSound( filter, iEntIndex, iChannel, pSample, flVolume, ATTN_TO_SNDLVL( flAttenuation ), iFlags,
iPitch, iSpecialDSP, pOrigin, pDirection, pUtlVecOrigins, bUpdatePositions, soundtime, speakerentity );
}
void CEngineSoundClient::EmitSound( IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample,
float flVolume, soundlevel_t iSoundLevel, int iFlags, int iPitch, int iSpecialDSP,
const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime /*= 0.0f*/, int speakerentity /*= -1*/ )
{
VPROF( "CEngineSoundClient::EmitSound" );
if ( pSample && TestSoundChar(pSample, CHAR_SENTENCE) )
{
int iSentenceIndex = -1;
VOX_LookupString( PSkipSoundChars(pSample), &iSentenceIndex );
if (iSentenceIndex >= 0)
{
EmitSentenceByIndex( filter, iEntIndex, iChannel, iSentenceIndex, flVolume,
iSoundLevel, iFlags, iPitch, iSpecialDSP, pOrigin, pDirection, pUtlVecOrigins, bUpdatePositions, soundtime, speakerentity );
}
else
{
DevWarning( 2, "Unable to find %s in sentences.txt\n", PSkipSoundChars(pSample));
}
}
else
{
EmitSoundInternal( filter, iEntIndex, iChannel, pSample, flVolume, iSoundLevel,
iFlags, iPitch, iSpecialDSP, pOrigin, pDirection, pUtlVecOrigins, bUpdatePositions, soundtime, speakerentity );
}
}
//-----------------------------------------------------------------------------
// Stops a sound
//-----------------------------------------------------------------------------
void CEngineSoundClient::StopSound( int iEntIndex, int iChannel, const char *pSample )
{
CEngineSingleUserFilter filter( cl.m_nPlayerSlot + 1 );
EmitSound( filter, iEntIndex, iChannel, pSample, 0, SNDLVL_NONE, SND_STOP, PITCH_NORM, 0,
NULL, NULL, NULL, true );
}
void CEngineSoundClient::SetRoomType( IRecipientFilter& filter, int roomType )
{
extern ConVar dsp_room;
dsp_room.SetValue( roomType );
}
void CEngineSoundClient::SetPlayerDSP( IRecipientFilter& filter, int dspType, bool fastReset )
{
extern ConVar dsp_player;
dsp_player.SetValue( dspType );
if ( fastReset )
{
DSP_FastReset( dspType );
}
}
void CEngineSoundClient::EmitAmbientSound( const char *pSample, float flVolume,
int iPitch, int flags, float soundtime /*= 0.0f*/ )
{
float delay = 0.0f;
if ( soundtime != 0.0f )
{
delay = soundtime - cl.m_flLastServerTickTime;
}
CSfxTable *pSound = S_PrecacheSound(pSample);
StartSoundParams_t params;
params.staticsound = true;
params.soundsource = SOUND_FROM_LOCAL_PLAYER;
params.entchannel = CHAN_STATIC;
params.pSfx = pSound;
params.origin = vec3_origin;
params.fvol = flVolume;
params.soundlevel = SNDLVL_NONE;
params.flags = flags;
params.pitch = iPitch;
params.specialdsp = 0;
params.fromserver = false;
params.delay = delay;
S_StartSound( params );
}
void CEngineSoundClient::StopAllSounds(bool bClearBuffers)
{
S_StopAllSounds( bClearBuffers );
}
float CEngineSoundClient::GetDistGainFromSoundLevel( soundlevel_t soundlevel, float dist )
{
return S_GetGainFromSoundLevel( soundlevel, dist );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pSample -
// Output : float
//-----------------------------------------------------------------------------
float CEngineSoundClient::GetSoundDuration( const char *pSample )
{
return AudioSource_GetSoundDuration( pSample );
}
// Client .dll only functions
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
// Output : int
//-----------------------------------------------------------------------------
int CEngineSoundClient::GetGuidForLastSoundEmitted()
{
return S_GetGuidForLastSoundEmitted();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : guid -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CEngineSoundClient::IsSoundStillPlaying( int guid )
{
return S_IsSoundStillPlaying( guid );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : guid -
//-----------------------------------------------------------------------------
void CEngineSoundClient::StopSoundByGuid( int guid )
{
S_StopSoundByGuid( guid );
}
//-----------------------------------------------------------------------------
// Purpose: Retrieves list of all active sounds
// Input : sndlist -
//-----------------------------------------------------------------------------
void CEngineSoundClient::GetActiveSounds( CUtlVector< SndInfo_t >& sndlist )
{
S_GetActiveSounds( sndlist );
}
//-----------------------------------------------------------------------------
// Purpose: Set's master volume (0.0->1.0)
// Input : guid -
// fvol -
//-----------------------------------------------------------------------------
void CEngineSoundClient::SetVolumeByGuid( int guid, float fvol )
{
S_SetVolumeByGuid( guid, fvol );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CEngineSoundClient::PrecacheSentenceGroup( const char *pGroupName )
{
VOX_PrecacheSentenceGroup( this, pGroupName );
}
void CEngineSoundClient::NotifyBeginMoviePlayback()
{
StopAllSounds(true);
#if _X360
XMPOverrideBackgroundMusic();
#endif
}
void CEngineSoundClient::NotifyEndMoviePlayback()
{
#if _X360
XMPRestoreBackgroundMusic();
#endif
}

View File

@ -0,0 +1,23 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef ENGINESOUNDINTERNAL_H
#define ENGINESOUNDINTERNAL_H
#if defined( _WIN32 )
#pragma once
#endif
#include "engine/IEngineSound.h"
//-----------------------------------------------------------------------------
// Method to get at the singleton implementations of the engine sound interfaces
//-----------------------------------------------------------------------------
IEngineSound* EngineSoundClient();
IEngineSound* EngineSoundServer();
#endif // SOUNDENGINEINTERNAL_H

View File

@ -0,0 +1,455 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "engine/IEngineSound.h"
#include "tier0/dbg.h"
#include "quakedef.h"
#include "vox.h"
#include "server.h"
#include "sv_main.h"
#include "edict.h"
#include "sound.h"
#include "host.h"
#include "vengineserver_impl.h"
#include "enginesingleuserfilter.h"
#include "snd_audio_source.h"
#include "soundchars.h"
#include "tier0/vprof.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
//
// Server-side implementation of the engine sound interface
//
//-----------------------------------------------------------------------------
class CEngineSoundServer : public IEngineSound
{
public:
// constructor, destructor
CEngineSoundServer();
virtual ~CEngineSoundServer();
virtual bool PrecacheSound( const char *pSample, bool bPreload, bool bIsUISound );
virtual bool IsSoundPrecached( const char *pSample );
virtual void PrefetchSound( const char *pSample );
virtual float GetSoundDuration( const char *pSample );
virtual void EmitSound( IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample,
float flVolume, float flAttenuation, int iFlags, int iPitch, int iSpecialDSP,
const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime = 0.0f, int speakerentity = -1 );
virtual void EmitSound( IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample,
float flVolume, soundlevel_t iSoundLevel, int iFlags, int iPitch, int iSpecialDSP,
const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime = 0.0f, int speakerentity = -1 );
virtual void EmitSentenceByIndex( IRecipientFilter& filter, int iEntIndex, int iChannel, int iSentenceIndex,
float flVolume, soundlevel_t iSoundLevel, int iFlags, int iPitch, int iSpecialDSP,
const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime = 0.0f, int speakerentity = -1 );
virtual void StopSound( int iEntIndex, int iChannel, const char *pSample );
virtual void StopAllSounds( bool bClearBuffers );
// Set the room type for a player
virtual void SetRoomType( IRecipientFilter& filter, int roomType );
virtual void SetPlayerDSP( IRecipientFilter& filter, int dspType, bool fastReset );
// emit an "ambient" sound that isn't spatialized - specify left/right volume
// only available on the client, assert on server
virtual void EmitAmbientSound( const char *pSample, float flVolume, int iPitch, int flags, float soundtime = 0.0f );
virtual float GetDistGainFromSoundLevel( soundlevel_t soundlevel, float dist );
// Client .dll only functions
virtual int GetGuidForLastSoundEmitted()
{
Warning( "Can't call GetGuidForLastSoundEmitted from server\n" );
return 0;
}
virtual bool IsSoundStillPlaying( int guid )
{
Warning( "Can't call IsSoundStillPlaying from server\n" );
return false;
}
virtual void StopSoundByGuid( int guid )
{
Warning( "Can't call StopSoundByGuid from server\n" );
return;
}
// Retrieves list of all active sounds
virtual void GetActiveSounds( CUtlVector< SndInfo_t >& sndlist )
{
Warning( "Can't call GetActiveSounds from server\n" );
return;
}
// Set's master volume (0.0->1.0)
virtual void SetVolumeByGuid( int guid, float fvol )
{
Warning( "Can't call SetVolumeByGuid from server\n" );
return;
}
virtual void PrecacheSentenceGroup( const char *pGroupName )
{
VOX_PrecacheSentenceGroup( this, pGroupName );
}
virtual void NotifyBeginMoviePlayback()
{
AssertMsg( 0, "Not supported" );
}
virtual void NotifyEndMoviePlayback()
{
AssertMsg( 0, "Not supported" );
}
private:
void EmitSoundInternal( IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample,
float flVolume, soundlevel_t iSoundLevel, int iFlags, int iPitch, int iSpecialDSP,
const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime = 0.0f, int speakerentity = -1 );
};
//-----------------------------------------------------------------------------
// Client-server neutral sound interface accessor
//-----------------------------------------------------------------------------
static CEngineSoundServer s_EngineSoundServer;
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CEngineSoundServer, IEngineSound,
IENGINESOUND_SERVER_INTERFACE_VERSION, s_EngineSoundServer );
IEngineSound *EngineSoundServer()
{
return &s_EngineSoundServer;
}
//-----------------------------------------------------------------------------
// constructor, destructor
//-----------------------------------------------------------------------------
CEngineSoundServer::CEngineSoundServer()
{
}
CEngineSoundServer::~CEngineSoundServer()
{
}
//-----------------------------------------------------------------------------
// Precache a particular sample
//-----------------------------------------------------------------------------
bool CEngineSoundServer::PrecacheSound( const char *pSample, bool bPreload, bool bIsUISound )
{
int i;
if ( pSample && TestSoundChar( pSample, CHAR_SENTENCE ) )
{
return true;
}
if ( pSample[0] <= ' ' )
{
Host_Error( "CEngineSoundServer::PrecacheSound: Bad string: %s", pSample );
}
// add the sound to the precache list
// Start at 1, since 0 is used to indicate an error in the sound precache
i = SV_FindOrAddSound( pSample, bPreload );
if ( i >= 0 )
return true;
Host_Error( "CEngineSoundServer::PrecacheSound: '%s' overflow", pSample );
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pSample -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CEngineSoundServer::IsSoundPrecached( const char *pSample )
{
if ( pSample && TestSoundChar(pSample, CHAR_SENTENCE) )
{
return true;
}
int idx = SV_SoundIndex( pSample );
if ( idx == -1 )
{
return false;
}
return true;
}
void CEngineSoundServer::PrefetchSound( const char *pSample )
{
if ( pSample && TestSoundChar(pSample, CHAR_SENTENCE) )
{
return;
}
int idx = SV_SoundIndex( pSample );
if ( idx == -1 )
{
return;
}
// Tell clients to prefetch the sound
SVC_Prefetch msg;
msg.m_fType = SVC_Prefetch::SOUND;
msg.m_nSoundIndex = idx;
sv.BroadcastMessage( msg, true, false );
}
//-----------------------------------------------------------------------------
// Stops a sound
//-----------------------------------------------------------------------------
void CEngineSoundServer::EmitSoundInternal( IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample,
float flVolume, soundlevel_t iSoundLevel, int iFlags, int iPitch, int iSpecialDSP,
const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime /*= 0.0f*/, int speakerentity /*=-1*/ )
{
AssertMsg( pDirection == NULL, "Direction specification not currently supported on server sounds" );
AssertMsg( bUpdatePositions, "Non-updated positions not currently supported on server sounds" );
if (flVolume < 0 || flVolume > 1)
{
Warning ("EmitSound: volume out of bounds = %f\n", flVolume);
return;
}
if ( ( iSoundLevel < soundlevel_t(MIN_SNDLVL_VALUE) ) || ( iSoundLevel > soundlevel_t(MAX_SNDLVL_VALUE) ) )
{
Warning ("EmitSound: soundlevel out of bounds = %d\n", iSoundLevel);
return;
}
if (iPitch < 0 || iPitch > 255)
{
Warning ("EmitSound: pitch out of bounds = %i\n", iPitch);
return;
}
edict_t *pEdict = (iEntIndex >= 0) ? &sv.edicts[iEntIndex] : NULL;
SV_StartSound( filter, pEdict, iChannel, pSample, flVolume, iSoundLevel,
iFlags, iPitch, iSpecialDSP, pOrigin, soundtime, speakerentity, pUtlVecOrigins );
}
//-----------------------------------------------------------------------------
// Plays a sentence
//-----------------------------------------------------------------------------
void CEngineSoundServer::EmitSentenceByIndex( IRecipientFilter& filter, int iEntIndex, int iChannel,
int iSentenceIndex, float flVolume, soundlevel_t iSoundLevel, int iFlags, int iPitch, int iSpecialDSP,
const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime /*= 0.0f*/, int speakerentity /*= -1*/ )
{
if ( iSentenceIndex >= 0 )
{
char pName[8];
Q_snprintf( pName, sizeof(pName), "!%d", iSentenceIndex );
EmitSoundInternal( filter, iEntIndex, iChannel, pName, flVolume, iSoundLevel,
iFlags, iPitch, iSpecialDSP, pOrigin, pDirection, pUtlVecOrigins, bUpdatePositions, soundtime, speakerentity );
}
}
//-----------------------------------------------------------------------------
// Emits a sound
//-----------------------------------------------------------------------------
void CEngineSoundServer::EmitSound( IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample,
float flVolume, float flAttenuation, int iFlags, int iPitch, int iSpecialDSP,
const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime /*= 0.0f*/, int speakerentity /*= -1*/ )
{
VPROF( "CEngineSoundServer::EmitSound" );
EmitSound( filter, iEntIndex, iChannel, pSample, flVolume, ATTN_TO_SNDLVL( flAttenuation ), iFlags,
iPitch, iSpecialDSP, pOrigin, pDirection, pUtlVecOrigins, bUpdatePositions, soundtime, speakerentity );
}
void CEngineSoundServer::EmitSound( IRecipientFilter& filter, int iEntIndex, int iChannel, const char *pSample,
float flVolume, soundlevel_t iSoundLevel, int iFlags, int iPitch, int iSpecialDSP,
const Vector *pOrigin, const Vector *pDirection, CUtlVector< Vector >* pUtlVecOrigins, bool bUpdatePositions, float soundtime /*= 0.0f*/, int speakerentity /*= -1*/ )
{
VPROF( "CEngineSoundServer::EmitSound" );
if ( pSample && TestSoundChar(pSample, CHAR_SENTENCE) )
{
int iSentenceIndex = -1;
VOX_LookupString( PSkipSoundChars(pSample), &iSentenceIndex );
if (iSentenceIndex >= 0)
{
EmitSentenceByIndex( filter, iEntIndex, iChannel, iSentenceIndex, flVolume,
iSoundLevel, iFlags, iPitch, iSpecialDSP, pOrigin, pDirection, pUtlVecOrigins, bUpdatePositions, soundtime, speakerentity );
}
else
{
DevWarning( 2, "Unable to find %s in sentences.txt\n", PSkipSoundChars(pSample) );
}
}
else
{
EmitSoundInternal( filter, iEntIndex, iChannel, pSample, flVolume, iSoundLevel,
iFlags, iPitch, iSpecialDSP, pOrigin, pDirection, pUtlVecOrigins, bUpdatePositions, soundtime, speakerentity );
}
}
void BuildRecipientList( CUtlVector< edict_t * >& list, const IRecipientFilter& filter )
{
int c = filter.GetRecipientCount();
for ( int i = 0; i < c; i++ )
{
int playerindex = filter.GetRecipientIndex( i );
if ( playerindex < 1 || playerindex > sv.GetClientCount() )
continue;
CGameClient *cl = sv.Client( playerindex - 1 );
// Never output to bots
if ( cl->IsFakeClient() )
continue;
if ( !cl->IsSpawned() )
continue;
list.AddToTail( cl->edict );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : filter -
// roomType -
//-----------------------------------------------------------------------------
void CEngineSoundServer::SetRoomType( IRecipientFilter& filter, int roomType )
{
CUtlVector< edict_t * > players;
BuildRecipientList( players, filter );
for ( int i = 0 ; i < players.Count(); i++ )
{
g_pVEngineServer->ClientCommand( players[ i ], "room_type %i\n", roomType );
}
}
// Set the dsp preset for a player (client only)
//-----------------------------------------------------------------------------
// Purpose:
// Input : filter -
// dspType -
//-----------------------------------------------------------------------------
void CEngineSoundServer::SetPlayerDSP( IRecipientFilter& filter, int dspType, bool fastReset )
{
Assert( !fastReset );
if ( fastReset )
{
Warning( "SetPlayerDSP: fastReset only valid from client\n" );
}
CUtlVector< edict_t * > players;
BuildRecipientList( players, filter );
for ( int i = 0 ; i < players.Count(); i++ )
{
g_pVEngineServer->ClientCommand( players[ i ], "dsp_player %i\n", dspType );
}
}
void CEngineSoundServer::StopAllSounds(bool bClearBuffers)
{
AssertMsg( 0, "Not supported" );
}
void CEngineSoundServer::EmitAmbientSound( const char *pSample, float flVolume, int iPitch, int flags, float soundtime /*= 0.0f*/ )
{
AssertMsg( 0, "Not supported" );
}
//-----------------------------------------------------------------------------
// Stops a sound
//-----------------------------------------------------------------------------
void CEngineSoundServer::StopSound( int iEntIndex, int iChannel, const char *pSample )
{
CEngineRecipientFilter filter;
filter.AddAllPlayers();
filter.MakeReliable();
EmitSound( filter, iEntIndex, iChannel, pSample, 0, SNDLVL_NONE, SND_STOP, PITCH_NORM, 0,
NULL, NULL, NULL, true );
}
float SV_GetSoundDuration( const char *pSample )
{
#ifdef SWDS
return 0; // TODO: make this return a real value (i.e implement an OS independent version of the sound code)
#else
return AudioSource_GetSoundDuration( PSkipSoundChars( pSample ) );
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pSample -
// Output : float
//-----------------------------------------------------------------------------
float CEngineSoundServer::GetSoundDuration( const char *pSample )
{
return Host_GetSoundDuration( pSample );
}
float CEngineSoundServer::GetDistGainFromSoundLevel( soundlevel_t soundlevel, float dist )
{
return S_GetGainFromSoundLevel( soundlevel, dist );
}
/*
//-----------------------------------------------------------------------------
// FIXME: Move into the CEngineSoundServer class?
//-----------------------------------------------------------------------------
void Host_RestartAmbientSounds()
{
if (!sv.active)
{
return;
}
#ifndef SWDS
const int NUM_INFOS = 64;
SoundInfo_t soundInfo[NUM_INFOS];
int nSounds = S_GetCurrentStaticSounds( soundInfo, NUM_INFOS, CHAN_STATIC );
for ( int i = 0; i < nSounds; i++)
{
if (soundInfo[i].looping &&
soundInfo[i].entity != -1 )
{
Msg("Restarting sound %s...\n", soundInfo[i].name);
S_StopSound(soundInfo[i].entity, soundInfo[i].channel);
CEngineRecipientFilter filter;
filter.AddAllPlayers();
SV_StartSound( filter, EDICT_NUM(soundInfo[i].entity),
CHAN_STATIC,
soundInfo[i].name,
soundInfo[i].volume,
soundInfo[i].soundlevel,
0, // @Q (toml 05-09-02): Is this correct, or will I need to squirrel away the original flags?
soundInfo[i].pitch );
}
}
#endif
} */

921
engine/GameEventManager.cpp Normal file
View File

@ -0,0 +1,921 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// GameEventManager.cpp: implementation of the CGameEventManager class.
//
//////////////////////////////////////////////////////////////////////
#include "GameEventManager.h"
#include "filesystem_engine.h"
#include "server.h"
#include "client.h"
#include "tier0/vprof.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
static CGameEventManager s_GameEventManager;
CGameEventManager &g_GameEventManager = s_GameEventManager;
static const char *s_GameEnventTypeMap[] =
{ "local", // 0 : don't network this field
"string", // 1 : zero terminated ASCII string
"float", // 2 : float 32 bit
"long", // 3 : signed int 32 bit
"short", // 4 : signed int 16 bit
"byte", // 5 : unsigned int 8 bit
"bool", // 6 : unsigned int 1 bit
NULL };
static ConVar net_showevents( "net_showevents", "0", FCVAR_CHEAT, "Dump game events to console (1=client only, 2=all)." );
// Expose CVEngineServer to the engine.
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CGameEventManager, IGameEventManager2, INTERFACEVERSION_GAMEEVENTSMANAGER2, s_GameEventManager );
CGameEvent::CGameEvent( CGameEventDescriptor *descriptor )
{
Assert( descriptor );
m_pDescriptor = descriptor;
m_pDataKeys = new KeyValues( descriptor->name );
}
CGameEvent::~CGameEvent()
{
m_pDataKeys->deleteThis();
}
bool CGameEvent::GetBool( const char *keyName, bool defaultValue)
{
return m_pDataKeys->GetInt( keyName, defaultValue ) != 0;
}
int CGameEvent::GetInt( const char *keyName, int defaultValue)
{
return m_pDataKeys->GetInt( keyName, defaultValue );
}
float CGameEvent::GetFloat( const char *keyName, float defaultValue )
{
return m_pDataKeys->GetFloat( keyName, defaultValue );
}
const char *CGameEvent::GetString( const char *keyName, const char *defaultValue )
{
return m_pDataKeys->GetString( keyName, defaultValue );
}
void CGameEvent::SetBool( const char *keyName, bool value )
{
m_pDataKeys->SetInt( keyName, value?1:0 );
}
void CGameEvent::SetInt( const char *keyName, int value )
{
m_pDataKeys->SetInt( keyName, value );
}
void CGameEvent::SetFloat( const char *keyName, float value )
{
m_pDataKeys->SetFloat( keyName, value );
}
void CGameEvent::SetString( const char *keyName, const char *value )
{
m_pDataKeys->SetString( keyName, value );
}
bool CGameEvent::IsEmpty( const char *keyName )
{
return m_pDataKeys->IsEmpty( keyName );
}
const char *CGameEvent::GetName() const
{
return m_pDataKeys->GetName();
}
bool CGameEvent::IsLocal() const
{
return m_pDescriptor->local;
}
bool CGameEvent::IsReliable() const
{
return m_pDescriptor->reliable;
}
CGameEventManager::CGameEventManager()
{
Reset();
}
CGameEventManager::~CGameEventManager()
{
Reset();
}
bool CGameEventManager::Init()
{
Reset();
LoadEventsFromFile( "resource/serverevents.res" );
return true;
}
void CGameEventManager::Shutdown()
{
Reset();
}
void CGameEventManager::Reset()
{
int number = m_GameEvents.Count();
for (int i = 0; i<number; i++)
{
CGameEventDescriptor &e = m_GameEvents.Element( i );
if ( e.keys )
{
e.keys->deleteThis(); // free the value keys
e.keys = NULL;
}
e.listeners.Purge(); // remove listeners
}
m_GameEvents.Purge();
m_Listeners.PurgeAndDeleteElements();
m_EventFiles.RemoveAll();
m_EventFileNames.RemoveAll();
m_bClientListenersChanged = true;
Assert( m_GameEvents.Count() == 0 );
}
bool CGameEventManager::HasClientListenersChanged( bool bReset /* = true */)
{
if ( !m_bClientListenersChanged )
return false;
if ( bReset )
m_bClientListenersChanged = false;
return true;
}
void CGameEventManager::WriteEventList(SVC_GameEventList *msg)
{
// reset event ids to -1 first
msg->m_nNumEvents = 0;
for (int i=0; i < m_GameEvents.Count(); i++ )
{
CGameEventDescriptor &descriptor = m_GameEvents[i];
if ( descriptor.local )
continue;
Assert( descriptor.eventid >= 0 && descriptor.eventid < MAX_EVENT_NUMBER );
msg->m_DataOut.WriteUBitLong( descriptor.eventid, MAX_EVENT_BITS );
msg->m_DataOut.WriteString( descriptor.name );
KeyValues *key = descriptor.keys->GetFirstSubKey();
while ( key )
{
int type = key->GetInt();
if ( type != TYPE_LOCAL )
{
msg->m_DataOut.WriteUBitLong( type, 3 );
msg->m_DataOut.WriteString( key->GetName() );
}
key = key->GetNextKey();
}
msg->m_DataOut.WriteUBitLong( TYPE_LOCAL, 3 ); // end marker
msg->m_nNumEvents++;
}
}
bool CGameEventManager::ParseEventList(SVC_GameEventList *msg)
{
int i;
// reset eventids to -1 first
for ( i=0; i < m_GameEvents.Count(); i++ )
{
CGameEventDescriptor &descriptor = m_GameEvents[i];
descriptor.eventid = -1;
}
// map server event IDs
for (i = 0; i<msg->m_nNumEvents; i++)
{
int id = msg->m_DataIn.ReadUBitLong( MAX_EVENT_BITS );
char name[MAX_EVENT_NAME_LENGTH];
msg->m_DataIn.ReadString( name, sizeof(name) );
CGameEventDescriptor *descriptor = GetEventDescriptor( name );
if ( !descriptor )
{
// event unknown to client, skip data
while ( msg->m_DataIn.ReadUBitLong( 3 ) )
msg->m_DataIn.ReadString( name, sizeof(name) );
continue;
}
// remove old definition list
if ( descriptor->keys )
descriptor->keys->deleteThis();
descriptor->keys = new KeyValues("descriptor");
int datatype = msg->m_DataIn.ReadUBitLong( 3 );
while ( datatype != TYPE_LOCAL )
{
msg->m_DataIn.ReadString( name, sizeof(name) );
descriptor->keys->SetInt( name, datatype );
datatype = msg->m_DataIn.ReadUBitLong( 3 );
}
descriptor->eventid = id;
}
// force client to answer what events he listens to
m_bClientListenersChanged = true;
return true;
}
void CGameEventManager::WriteListenEventList(CLC_ListenEvents *msg)
{
msg->m_EventArray.ClearAll();
// and know tell the server what events we want to listen to
for (int i=0; i < m_GameEvents.Count(); i++ )
{
CGameEventDescriptor &descriptor = m_GameEvents[i];
bool bHasClientListener = false;
for ( int j=0; j<descriptor.listeners.Count(); j++ )
{
CGameEventCallback *listener = descriptor.listeners[j];
if ( listener->m_nListenerType == CGameEventManager::CLIENTSIDE ||
listener->m_nListenerType == CGameEventManager::CLIENTSIDE_OLD )
{
// if we have a client side listener and server knows this event, add it
bHasClientListener = true;
break;
}
}
if ( !bHasClientListener )
continue;
if ( descriptor.eventid == -1 )
{
DevMsg("Warning! Client listens to event '%s' unknown by server.\n", descriptor.name );
continue;
}
msg->m_EventArray.Set( descriptor.eventid );
}
}
IGameEvent *CGameEventManager::CreateEvent( CGameEventDescriptor *descriptor )
{
return new CGameEvent ( descriptor );
}
IGameEvent *CGameEventManager::CreateEvent( const char *name, bool bForce )
{
if ( !name || !name[0] )
return NULL;
CGameEventDescriptor *descriptor = GetEventDescriptor( name );
// check if this event name is known
if ( !descriptor )
{
DevMsg( "CreateEvent: event '%s' not registered.\n", name );
return NULL;
}
// event is known but no one listen to it
if ( descriptor->listeners.Count() == 0 && !bForce )
{
return NULL;
}
// create & return the new event
return new CGameEvent ( descriptor );
}
bool CGameEventManager::FireEvent( IGameEvent *event, bool bServerOnly )
{
return FireEventIntern( event, bServerOnly, false );
}
bool CGameEventManager::FireEventClientSide( IGameEvent *event )
{
return FireEventIntern( event, false, true );
}
IGameEvent *CGameEventManager::DuplicateEvent( IGameEvent *event )
{
CGameEvent *gameEvent = dynamic_cast<CGameEvent*>(event);
if ( !gameEvent )
return NULL;
// create new instance
CGameEvent *newEvent = new CGameEvent ( gameEvent->m_pDescriptor );
// free keys
newEvent->m_pDataKeys->deleteThis();
// and make copy
newEvent->m_pDataKeys = gameEvent->m_pDataKeys->MakeCopy();
return newEvent;
}
void CGameEventManager::ConPrintEvent( IGameEvent *event)
{
CGameEventDescriptor *descriptor = GetEventDescriptor( event );
if ( !descriptor )
return;
KeyValues *key = descriptor->keys->GetFirstSubKey();
while ( key )
{
const char * keyName = key->GetName();
int type = key->GetInt();
switch ( type )
{
case TYPE_LOCAL : ConMsg( "- \"%s\" = \"%s\" (local)\n", keyName, event->GetString(keyName) ); break;
case TYPE_STRING : ConMsg( "- \"%s\" = \"%s\"\n", keyName, event->GetString(keyName) ); break;
case TYPE_FLOAT : ConMsg( "- \"%s\" = \"%.2f\"\n", keyName, event->GetFloat(keyName) ); break;
default: ConMsg( "- \"%s\" = \"%i\"\n", keyName, event->GetInt(keyName) ); break;
}
key = key->GetNextKey();
}
}
bool CGameEventManager::FireEventIntern( IGameEvent *event, bool bServerOnly, bool bClientOnly )
{
if ( event == NULL )
return false;
Assert( !(bServerOnly && bClientOnly) ); // it can't be both
VPROF_("CGameEventManager::FireEvent", 1, VPROF_BUDGETGROUP_OTHER_UNACCOUNTED, false,
bClientOnly ? BUDGETFLAG_CLIENT : ( bServerOnly ? BUDGETFLAG_SERVER : BUDGETFLAG_OTHER ) );
CGameEventDescriptor *descriptor = GetEventDescriptor( event );
if ( descriptor == NULL )
{
DevMsg( "FireEvent: event '%s' not registered.\n", event->GetName() );
FreeEvent( event );
return false;
}
tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "%s (name: %s listeners: %d)", __FUNCTION__, tmDynamicString( TELEMETRY_LEVEL0, event->GetName() ), descriptor->listeners.Count() );
// show game events in console
if ( net_showevents.GetInt() > 0 )
{
if ( bClientOnly )
{
ConMsg( "Game event \"%s\", Tick %i:\n", descriptor->name, cl.GetClientTickCount() );
ConPrintEvent( event );
}
else if ( net_showevents.GetInt() > 1 )
{
ConMsg( "Server event \"%s\", Tick %i:\n", descriptor->name, sv.GetTick() );
ConPrintEvent( event );
}
}
for ( int i = 0; i < descriptor->listeners.Count(); i++ )
{
CGameEventCallback *listener = descriptor->listeners.Element( i );
Assert ( listener );
// don't trigger server listners for clientside only events
if ( ( listener->m_nListenerType == SERVERSIDE ||
listener->m_nListenerType == SERVERSIDE_OLD ) &&
bClientOnly )
continue;
// don't trigger clientside events, if not explicit a clientside event
if ( ( listener->m_nListenerType == CLIENTSIDE ||
listener->m_nListenerType == CLIENTSIDE_OLD ) &&
!bClientOnly )
continue;
// don't broadcast events if server side only
if ( listener->m_nListenerType == CLIENTSTUB && (bServerOnly || bClientOnly) )
continue;
// TODO optimized the serialize event for clients, call only once and not per client
// fire event in this listener module
if ( listener->m_nListenerType == CLIENTSIDE_OLD ||
listener->m_nListenerType == SERVERSIDE_OLD )
{
tmZone( TELEMETRY_LEVEL1, TMZF_NONE, "FireGameEvent (i: %d, listenertype: %d (old))", i, listener->m_nListenerType );
// legacy support for old system
IGameEventListener *pCallback = static_cast<IGameEventListener*>(listener->m_pCallback);
CGameEvent *pEvent = static_cast<CGameEvent*>(event);
pCallback->FireGameEvent( pEvent->m_pDataKeys );
}
else
{
tmZone( TELEMETRY_LEVEL1, TMZF_NONE, "FireGameEvent (i: %d, listenertype: %d (new))", i, listener->m_nListenerType );
// new system
IGameEventListener2 *pCallback = static_cast<IGameEventListener2*>(listener->m_pCallback);
pCallback->FireGameEvent( event );
}
}
// free event resources
FreeEvent( event );
return true;
}
bool CGameEventManager::SerializeEvent( IGameEvent *event, bf_write* buf )
{
CGameEventDescriptor *descriptor = GetEventDescriptor( event );
Assert( descriptor );
buf->WriteUBitLong( descriptor->eventid, MAX_EVENT_BITS );
// now iterate trough all fields described in gameevents.res and put them in the buffer
KeyValues * key = descriptor->keys->GetFirstSubKey();
if ( net_showevents.GetInt() > 2 )
{
DevMsg("Serializing event '%s' (%i):\n", descriptor->name, descriptor->eventid );
}
while ( key )
{
const char * keyName = key->GetName();
int type = key->GetInt();
if ( net_showevents.GetInt() > 2 )
{
DevMsg(" - %s (%i)\n", keyName, type );
}
//Make sure every key is used in the event
// Assert( event->FindKey(keyName) && "GameEvent field not found in passed KeyValues" );
// see s_GameEnventTypeMap for index
switch ( type )
{
case TYPE_LOCAL : break; // don't network this guy
case TYPE_STRING: buf->WriteString( event->GetString( keyName, "") ); break;
case TYPE_FLOAT : buf->WriteFloat( event->GetFloat( keyName, 0.0f) ); break;
case TYPE_LONG : buf->WriteLong( event->GetInt( keyName, 0) ); break;
case TYPE_SHORT : buf->WriteShort( event->GetInt( keyName, 0) ); break;
case TYPE_BYTE : buf->WriteByte( event->GetInt( keyName, 0) ); break;
case TYPE_BOOL : buf->WriteOneBit( event->GetInt( keyName, 0) ); break;
default: DevMsg(1, "CGameEventManager: unkown type %i for key '%s'.\n", type, key->GetName() ); break;
}
key = key->GetNextKey();
}
return !buf->IsOverflowed();
}
IGameEvent *CGameEventManager::UnserializeEvent( bf_read *buf)
{
char databuf[MAX_EVENT_BYTES];
// read event id
int eventid = buf->ReadUBitLong( MAX_EVENT_BITS );
// get event description
CGameEventDescriptor *descriptor = GetEventDescriptor( eventid );
if ( descriptor == NULL )
{
DevMsg( "CGameEventManager::UnserializeEvent:: unknown event id %i.\n", eventid );
return NULL;
}
// create new event
IGameEvent *event = CreateEvent( descriptor );
if ( !event )
{
DevMsg( "CGameEventManager::UnserializeEvent:: failed to create event %s.\n", descriptor->name );
return NULL;
}
KeyValues * key = descriptor->keys->GetFirstSubKey();
while ( key )
{
const char * keyName = key->GetName();
int type = key->GetInt();
switch ( type )
{
case TYPE_LOCAL : break; // ignore
case TYPE_STRING : if ( buf->ReadString( databuf, sizeof(databuf) ) )
event->SetString( keyName, databuf );
break;
case TYPE_FLOAT : event->SetFloat( keyName, buf->ReadFloat() ); break;
case TYPE_LONG : event->SetInt( keyName, buf->ReadLong() ); break;
case TYPE_SHORT : event->SetInt( keyName, buf->ReadShort() ); break;
case TYPE_BYTE : event->SetInt( keyName, buf->ReadByte() ); break;
case TYPE_BOOL : event->SetInt( keyName, buf->ReadOneBit() ); break;
default: DevMsg(1, "CGameEventManager: unknown type %i for key '%s'.\n", type, key->GetName() ); break;
}
key = key->GetNextKey();
}
return event;
}
// returns true if this listener is listens to given event
bool CGameEventManager::FindListener( IGameEventListener2 *listener, const char *name )
{
CGameEventDescriptor *pDescriptor = GetEventDescriptor( name );
if ( !pDescriptor )
return false; // event is unknown
CGameEventCallback *pCallback = FindEventListener( listener );
if ( !pCallback )
return false; // listener is unknown
// see if listener is in the list for this event
return pDescriptor->listeners.IsValidIndex( pDescriptor->listeners.Find( pCallback ) );
}
CGameEventCallback* CGameEventManager::FindEventListener( void* pCallback )
{
for (int i=0; i < m_Listeners.Count(); i++ )
{
CGameEventCallback *listener = m_Listeners.Element(i);
if ( listener->m_pCallback == pCallback )
{
return listener;
}
}
return NULL;
}
void CGameEventManager::RemoveListener(IGameEventListener2 *listener)
{
CGameEventCallback *pCallback = FindEventListener( listener );
if ( pCallback == NULL )
{
return;
}
// remove reference from events
for (int i=0; i < m_GameEvents.Count(); i++ )
{
CGameEventDescriptor &et = m_GameEvents.Element( i );
et.listeners.FindAndRemove( pCallback );
}
// and from global list
m_Listeners.FindAndRemove( pCallback );
if ( pCallback->m_nListenerType == CLIENTSIDE )
{
m_bClientListenersChanged = true;
}
delete pCallback;
}
int CGameEventManager::LoadEventsFromFile( const char * filename )
{
if ( UTL_INVAL_SYMBOL == m_EventFiles.Find( filename ) )
{
CUtlSymbol id = m_EventFiles.AddString( filename );
m_EventFileNames.AddToTail( id );
}
KeyValues * key = new KeyValues(filename);
KeyValues::AutoDelete autodelete_key( key );
if ( !key->LoadFromFile( g_pFileSystem, filename, "GAME" ) )
return false;
int count = 0; // number new events
KeyValues * subkey = key->GetFirstSubKey();
while ( subkey )
{
if ( subkey->GetDataType() == KeyValues::TYPE_NONE )
{
RegisterEvent( subkey );
count++;
}
subkey = subkey->GetNextKey();
}
if ( net_showevents.GetBool() )
DevMsg( "Event System loaded %i events from file %s.\n", m_GameEvents.Count(), filename );
return m_GameEvents.Count();
}
void CGameEventManager::ReloadEventDefinitions()
{
for ( int i=0; i< m_EventFileNames.Count(); i++ )
{
const char *filename = m_EventFiles.String( m_EventFileNames[i] );
LoadEventsFromFile( filename );
}
// we are the server, build string table now
int number = m_GameEvents.Count();
for (int j = 0; j<number; j++)
{
m_GameEvents[j].eventid = j;
}
}
bool CGameEventManager::AddListener( IGameEventListener2 *listener, const char *event, bool bServerSide )
{
if ( !event )
return false;
// look for the event descriptor
CGameEventDescriptor *descriptor = GetEventDescriptor( event );
if ( !descriptor )
{
DevMsg( "CGameEventManager::AddListener: event '%s' unknown.\n", event );
return false; // that should not happen
}
return AddListener( listener, descriptor, bServerSide ? SERVERSIDE : CLIENTSIDE );
}
bool CGameEventManager::AddListener( void *listener, CGameEventDescriptor *descriptor, int nListenerType )
{
if ( !listener || !descriptor )
return false; // bahh
// check if we already know this listener
CGameEventCallback *pCallback = FindEventListener( listener );
if ( pCallback == NULL )
{
// add new callback
pCallback = new CGameEventCallback;
m_Listeners.AddToTail( pCallback );
pCallback->m_nListenerType = nListenerType;
pCallback->m_pCallback = listener;
}
else
{
// make sure that it hasn't changed:
Assert( pCallback->m_nListenerType == nListenerType );
Assert( pCallback->m_pCallback == listener );
}
// add to event listeners list if not already in there
if ( descriptor->listeners.Find( pCallback ) == descriptor->listeners.InvalidIndex() )
{
descriptor->listeners.AddToTail( pCallback );
if ( nListenerType == CLIENTSIDE || nListenerType == CLIENTSIDE_OLD )
m_bClientListenersChanged = true;
}
return true;
}
bool CGameEventManager::RegisterEvent( KeyValues * event)
{
if ( event == NULL )
return false;
if ( m_GameEvents.Count() == MAX_EVENT_NUMBER )
{
DevMsg( "CGameEventManager: couldn't register event '%s', limit reached (%i).\n",
event->GetName(), MAX_EVENT_NUMBER );
return false;
}
CGameEventDescriptor *descriptor = GetEventDescriptor( event->GetName() );
if ( !descriptor )
{
// event not known yet, create new one
int index = m_GameEvents.AddToTail();
descriptor = &m_GameEvents.Element(index);
AssertMsg2( V_strlen( event->GetName() ) <= MAX_EVENT_NAME_LENGTH, "Event named '%s' exceeds maximum name length %d", event->GetName(), MAX_EVENT_NAME_LENGTH );
Q_strncpy( descriptor->name, event->GetName(), MAX_EVENT_NAME_LENGTH );
}
else
{
// descriptor already know, but delete old definitions
descriptor->keys->deleteThis();
}
// create new descriptor keys
descriptor->keys = new KeyValues("descriptor");
KeyValues *subkey = event->GetFirstSubKey();
// interate through subkeys
while ( subkey )
{
const char *keyName = subkey->GetName();
// ok, check it's data type
const char * type = subkey->GetString();
if ( !Q_strcmp( "local", keyName) )
{
descriptor->local = Q_atoi( type ) != 0;
}
else if ( !Q_strcmp( "reliable", keyName) )
{
descriptor->reliable = Q_atoi( type ) != 0;
}
else
{
int i;
for (i = TYPE_LOCAL; i <= TYPE_BOOL; i++ )
{
if ( !Q_strcmp( type, s_GameEnventTypeMap[i]) )
{
// set data type
descriptor->keys->SetInt( keyName, i ); // set data type
break;
}
}
if ( i > TYPE_BOOL )
{
descriptor->keys->SetInt( keyName, 0 ); // unknown
DevMsg( "CGameEventManager:: unknown type '%s' for key '%s'.\n", type, subkey->GetName() );
}
}
subkey = subkey->GetNextKey();
}
return true;
}
CGameEventDescriptor *CGameEventManager::GetEventDescriptor(IGameEvent *event)
{
CGameEvent *gameevent = dynamic_cast<CGameEvent*>(event);
if ( !gameevent )
return NULL;
return gameevent->m_pDescriptor;
}
CGameEventDescriptor *CGameEventManager::GetEventDescriptor(int eventid) // returns event name or NULL
{
if ( eventid < 0 )
return NULL;
for ( int i = 0; i < m_GameEvents.Count(); i++ )
{
CGameEventDescriptor *descriptor = &m_GameEvents[i];
if ( descriptor->eventid == eventid )
return descriptor;
}
return NULL;
}
void CGameEventManager::FreeEvent( IGameEvent *event )
{
if ( !event )
return;
delete event;
}
CGameEventDescriptor *CGameEventManager::GetEventDescriptor(const char * name)
{
if ( !name || !name[0] )
return NULL;
for (int i=0; i < m_GameEvents.Count(); i++ )
{
CGameEventDescriptor *descriptor = &m_GameEvents[i];
if ( Q_strcmp( descriptor->name, name ) == 0 )
return descriptor;
}
return NULL;
}
bool CGameEventManager::AddListenerAll( void *listener, int nListenerType )
{
if ( !listener )
return false;
for (int i=0; i < m_GameEvents.Count(); i++ )
{
CGameEventDescriptor *descriptor = &m_GameEvents[i];
AddListener( listener, descriptor, nListenerType );
}
DevMsg("Warning! Game event listener registerd for all events. Use newer game event interface.\n");
return true;
}
void CGameEventManager::RemoveListenerOld( void *listener)
{
CGameEventCallback *pCallback = FindEventListener( listener );
if ( pCallback == NULL )
{
DevMsg("RemoveListenerOld: couldn't find listener\n");
return;
}
// remove reference from events
for (int i=0; i < m_GameEvents.Count(); i++ )
{
CGameEventDescriptor &et = m_GameEvents.Element( i );
et.listeners.FindAndRemove( pCallback );
}
// and from global list
m_Listeners.FindAndRemove( pCallback );
if ( pCallback->m_nListenerType == CLIENTSIDE_OLD )
{
m_bClientListenersChanged = true;
}
delete pCallback;
}

164
engine/GameEventManager.h Normal file
View File

@ -0,0 +1,164 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: gameeventmanager.h: interface for the CGameEventManager class.
//
// $Workfile: $
// $NoKeywords: $
//=============================================================================//
#if !defined ( GAMEEVENTMANAGER_H )
#define GAMEEVENTMANAGER_H
#ifdef _WIN32
#pragma once
#endif
#include <igameevents.h>
#include <utlvector.h>
#include <KeyValues.h>
#include <networkstringtabledefs.h>
#include <utlsymbol.h>
class SVC_GameEventList;
class CLC_ListenEvents;
class CGameEventCallback
{
public:
void *m_pCallback; // callback pointer
int m_nListenerType; // client or server side ?
};
class CGameEventDescriptor
{
public:
CGameEventDescriptor()
{
name[0] = 0;
eventid = -1;
keys = NULL;
local = false;
reliable = true;
}
public:
char name[MAX_EVENT_NAME_LENGTH]; // name of this event
int eventid; // network index number, -1 = not networked
KeyValues *keys; // KeyValue describing data types, if NULL only name
bool local; // local event, never tell clients about that
bool reliable; // send this event as reliable message
CUtlVector<CGameEventCallback*> listeners; // registered listeners
};
class CGameEvent : public IGameEvent
{
public:
CGameEvent( CGameEventDescriptor *descriptor );
virtual ~CGameEvent();
const char *GetName() const;
bool IsEmpty(const char *keyName = NULL);
bool IsLocal() const;
bool IsReliable() const;
bool GetBool( const char *keyName = NULL, bool defaultValue = false );
int GetInt( const char *keyName = NULL, int defaultValue = 0 );
float GetFloat( const char *keyName = NULL, float defaultValue = 0.0f );
const char *GetString( const char *keyName = NULL, const char *defaultValue = "" );
void SetBool( const char *keyName, bool value );
void SetInt( const char *keyName, int value );
void SetFloat( const char *keyName, float value );
void SetString( const char *keyName, const char *value );
CGameEventDescriptor *m_pDescriptor;
KeyValues *m_pDataKeys;
};
class CGameEventManager : public IGameEventManager2
{
friend class CGameEventManagerOld;
public: // IGameEventManager functions
enum
{
SERVERSIDE = 0, // this is a server side listener, event logger etc
CLIENTSIDE, // this is a client side listenet, HUD element etc
CLIENTSTUB, // this is a serverside stub for a remote client listener (used by engine only)
SERVERSIDE_OLD, // legacy support for old server event listeners
CLIENTSIDE_OLD, // legecy support for old client event listeners
};
enum
{
TYPE_LOCAL = 0, // not networked
TYPE_STRING, // zero terminated ASCII string
TYPE_FLOAT, // float 32 bit
TYPE_LONG, // signed int 32 bit
TYPE_SHORT, // signed int 16 bit
TYPE_BYTE, // unsigned int 8 bit
TYPE_BOOL // unsigned int 1 bit
};
CGameEventManager();
virtual ~CGameEventManager();
int LoadEventsFromFile( const char * filename );
void Reset();
bool AddListener( IGameEventListener2 *listener, const char *name, bool bServerSide );
bool FindListener( IGameEventListener2 *listener, const char *name );
void RemoveListener( IGameEventListener2 *listener);
IGameEvent *CreateEvent( const char *name, bool bForce = false );
IGameEvent *DuplicateEvent( IGameEvent *event);
bool FireEvent( IGameEvent *event, bool bDontBroadcast = false );
bool FireEventClientSide( IGameEvent *event );
void FreeEvent( IGameEvent *event );
bool SerializeEvent( IGameEvent *event, bf_write *buf );
IGameEvent *UnserializeEvent( bf_read *buf );
public:
bool Init();
void Shutdown();
void ReloadEventDefinitions(); // called by server on new map
bool AddListener( void *listener, CGameEventDescriptor *descriptor, int nListenerType );
CGameEventDescriptor *GetEventDescriptor( const char *name );
CGameEventDescriptor *GetEventDescriptor( IGameEvent *event );
CGameEventDescriptor *GetEventDescriptor( int eventid );
void WriteEventList(SVC_GameEventList *msg);
bool ParseEventList(SVC_GameEventList *msg);
void WriteListenEventList(CLC_ListenEvents *msg);
bool HasClientListenersChanged( bool bReset = true );
void ConPrintEvent( IGameEvent *event);
// legacy support
bool AddListenerAll( void *listener, int nListenerType );
void RemoveListenerOld( void *listener);
protected:
IGameEvent *CreateEvent( CGameEventDescriptor *descriptor );
bool RegisterEvent( KeyValues * keys );
void UnregisterEvent(int index);
bool FireEventIntern( IGameEvent *event, bool bServerSide, bool bClientOnly );
CGameEventCallback* FindEventListener( void* listener );
CUtlVector<CGameEventDescriptor> m_GameEvents; // list of all known events
CUtlVector<CGameEventCallback*> m_Listeners; // list of all registered listeners
CUtlSymbolTable m_EventFiles; // list of all loaded event files
CUtlVector<CUtlSymbol> m_EventFileNames;
bool m_bClientListenersChanged; // true every time client changed listeners
};
extern CGameEventManager &g_GameEventManager;
#endif

View File

@ -0,0 +1,144 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: wrapper for GameEvent manager legacy support
//
// $NoKeywords: $
//
//=============================================================================//
// GameEventManager.cpp: implementation of the CGameEventManager class.
//
//////////////////////////////////////////////////////////////////////
#include "GameEventManager.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
class CGameEventManagerOld : public IGameEventManager
{
public: // IGameEventManager wrapper
int LoadEventsFromFile( const char * filename ) { return g_GameEventManager.LoadEventsFromFile( filename ); }
KeyValues *GetEvent(const char * name);
void Reset() { g_GameEventManager.Reset(); }
bool AddListener( IGameEventListener * listener, const char * event, bool bIsServerSide );
bool AddListener( IGameEventListener * listener, bool bIsServerSide );
void RemoveListener(IGameEventListener * listener);
bool FireEvent( KeyValues * event );
bool FireEventClientOnly( KeyValues * event );
bool FireEventServerOnly( KeyValues * event );
bool SerializeKeyValues( KeyValues *event, bf_write *buf, CGameEvent *eventtype = NULL );
KeyValues * UnserializeKeyValue( bf_read *msg ); // create new KeyValues, must be deleted
protected:
bool FireEventIntern( KeyValues * event, bool bServerSide, bool bClientSide );
};
static CGameEventManagerOld s_GameEventManagerOld;
// Expose CVEngineServer to the engine.
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CGameEventManagerOld, IGameEventManager, INTERFACEVERSION_GAMEEVENTSMANAGER, s_GameEventManagerOld );
bool CGameEventManagerOld::AddListener( IGameEventListener * listener, const char * event, bool bIsServerSide )
{
CGameEventDescriptor *descriptor = g_GameEventManager.GetEventDescriptor( event );
if ( !descriptor )
return false;
if ( bIsServerSide )
{
return g_GameEventManager.AddListener( listener, descriptor, CGameEventManager::SERVERSIDE_OLD );
}
else
{
return g_GameEventManager.AddListener( listener, descriptor, CGameEventManager::CLIENTSIDE_OLD );
}
}
bool CGameEventManagerOld::AddListener( IGameEventListener * listener, bool bIsServerSide )
{
if ( bIsServerSide )
{
return g_GameEventManager.AddListenerAll( listener, CGameEventManager::SERVERSIDE_OLD );
}
else
{
return g_GameEventManager.AddListenerAll( listener, CGameEventManager::CLIENTSIDE_OLD );
}
}
void CGameEventManagerOld::RemoveListener(IGameEventListener * listener)
{
g_GameEventManager.RemoveListenerOld( listener );
}
KeyValues *CGameEventManagerOld::GetEvent(const char * name)
{
CGameEventDescriptor *event = g_GameEventManager.GetEventDescriptor( name );
if ( !event )
return NULL;
return event->keys;
}
bool CGameEventManagerOld::FireEvent( KeyValues * event )
{
return FireEventIntern( event, false, false );
}
bool CGameEventManagerOld::FireEventClientOnly( KeyValues * event )
{
return FireEventIntern( event, false, true );
}
bool CGameEventManagerOld::FireEventServerOnly( KeyValues * event )
{
return FireEventIntern( event, true, false );
}
bool CGameEventManagerOld::FireEventIntern( KeyValues *keys, bool bServerSideOnly, bool bClientSideOnly )
{
if ( !keys )
return false;
CGameEvent *event = (CGameEvent*) g_GameEventManager.CreateEvent( keys->GetName() );
if ( !event )
return false;
event->m_pDataKeys->deleteThis();
event->m_pDataKeys = keys;
if ( bClientSideOnly )
{
return g_GameEventManager.FireEventClientSide( event );
}
else
{
return g_GameEventManager.FireEvent( event, bServerSideOnly );
}
}
bool CGameEventManagerOld::SerializeKeyValues( KeyValues* event, bf_write* buf, CGameEvent* eventtype )
{
DevMsg("SerializeKeyValues:: not supported\n");
return false;
}
KeyValues *CGameEventManagerOld::UnserializeKeyValue( bf_read *buf)
{
DevMsg("UnserializeKeyValue:: not supported\n");
return NULL;
}

55
engine/IOcclusionSystem.h Normal file
View File

@ -0,0 +1,55 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#ifndef IOCCLUSIONSYSTEM_H
#define IOCCLUSIONSYSTEM_H
#ifdef _WIN32
#pragma once
#endif
class Vector;
class VMatrix;
struct model_t;
class VPlane;
class CUtlBuffer;
//-----------------------------------------------------------------------------
// Occlusion system interface
//-----------------------------------------------------------------------------
class IOcclusionSystem
{
public:
// Activate/deactivate an occluder brush model
virtual void ActivateOccluder( int nOccluderIndex, bool bActive ) = 0;
// Sets the view transform
virtual void SetView( const Vector &vecCameraPos, float flFOV, const VMatrix &worldToCamera,
const VMatrix &cameraToProjection, const VPlane &nearClipPlane ) = 0;
// Test for occlusion (bounds specified in abs space)
virtual bool IsOccluded( const Vector &vecAbsMins, const Vector &vecAbsMaxs ) = 0;
// Sets global occlusion parameters
virtual void SetOcclusionParameters( float flMaxOccludeeArea, float flMinOccluderArea ) = 0;
virtual float MinOccluderArea() const = 0;
// Render debugging overlay
virtual void DrawDebugOverlays() = 0;
};
//-----------------------------------------------------------------------------
// Singleton accessor
//-----------------------------------------------------------------------------
IOcclusionSystem *OcclusionSystem();
#endif // IOCCLUSIONSYSTEM_H

239
engine/LoadScreenUpdate.cpp Normal file
View File

@ -0,0 +1,239 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: To accomplish X360 TCR 22, we need to call present ever 66msec
// at least during loading screens. This amazing hack will do it
// by overriding the allocator to tick it every so often.
//
// $NoKeywords: $
//===========================================================================//
#include "LoadScreenUpdate.h"
#include "tier0/memalloc.h"
#include "tier1/delegates.h"
#include "tier0/threadtools.h"
#include "tier2/tier2.h"
#include "materialsystem/imaterialsystem.h"
#include "tier0/dbg.h"
#ifdef _X360
#define LOADING_UPDATE_INTERVAL 0.015f
#define UNINITIALIZED_LAST_TIME -1000.0f
//-----------------------------------------------------------------------------
// Used to tick the loading screen every so often
//-----------------------------------------------------------------------------
class CLoaderMemAlloc : public IMemAlloc
{
// Methods of IMemAlloc
public:
virtual void *Alloc( size_t nSize );
virtual void *Realloc( void *pMem, size_t nSize );
virtual void Free( void *pMem );
DELEGATE_TO_OBJECT_2( void *, Expand_NoLongerSupported, void *, size_t, m_pMemAlloc );
virtual void *Alloc( size_t nSize, const char *pFileName, int nLine );
virtual void *Realloc( void *pMem, size_t nSize, const char *pFileName, int nLine );
virtual void Free( void *pMem, const char *pFileName, int nLine );
DELEGATE_TO_OBJECT_4( void*, Expand_NoLongerSupported, void *, size_t, const char *, int, m_pMemAlloc );
DELEGATE_TO_OBJECT_1( size_t, GetSize, void *, m_pMemAlloc );
DELEGATE_TO_OBJECT_2V( PushAllocDbgInfo, const char *, int, m_pMemAlloc );
DELEGATE_TO_OBJECT_0V( PopAllocDbgInfo, m_pMemAlloc );
DELEGATE_TO_OBJECT_1( long, CrtSetBreakAlloc, long, m_pMemAlloc );
DELEGATE_TO_OBJECT_2( int, CrtSetReportMode, int, int, m_pMemAlloc );
DELEGATE_TO_OBJECT_1( int, CrtIsValidHeapPointer, const void *, m_pMemAlloc );
DELEGATE_TO_OBJECT_3( int, CrtIsValidPointer, const void *, unsigned int, int, m_pMemAlloc );
DELEGATE_TO_OBJECT_0( int, CrtCheckMemory, m_pMemAlloc );
DELEGATE_TO_OBJECT_1( int, CrtSetDbgFlag, int, m_pMemAlloc );
DELEGATE_TO_OBJECT_1V( CrtMemCheckpoint, _CrtMemState *, m_pMemAlloc );
DELEGATE_TO_OBJECT_0V( DumpStats, m_pMemAlloc );
DELEGATE_TO_OBJECT_1V( DumpStatsFileBase, const char *, m_pMemAlloc );
DELEGATE_TO_OBJECT_2( void*, CrtSetReportFile, int, void*, m_pMemAlloc );
DELEGATE_TO_OBJECT_1( void*, CrtSetReportHook, void*, m_pMemAlloc );
DELEGATE_TO_OBJECT_5( int, CrtDbgReport, int, const char *, int, const char *, const char *, m_pMemAlloc );
DELEGATE_TO_OBJECT_0( int, heapchk, m_pMemAlloc );
DELEGATE_TO_OBJECT_0( bool, IsDebugHeap, m_pMemAlloc );
DELEGATE_TO_OBJECT_2V( GetActualDbgInfo, const char *&, int &, m_pMemAlloc );
DELEGATE_TO_OBJECT_5V( RegisterAllocation, const char *, int, int, int, unsigned, m_pMemAlloc );
DELEGATE_TO_OBJECT_5V( RegisterDeallocation, const char *, int, int, int, unsigned, m_pMemAlloc );
DELEGATE_TO_OBJECT_0( int, GetVersion, m_pMemAlloc );
DELEGATE_TO_OBJECT_0V( CompactHeap, m_pMemAlloc );
DELEGATE_TO_OBJECT_1( MemAllocFailHandler_t, SetAllocFailHandler, MemAllocFailHandler_t, m_pMemAlloc );
DELEGATE_TO_OBJECT_1V( DumpBlockStats, void *, m_pMemAlloc );
#if defined( _MEMTEST )
DELEGATE_TO_OBJECT_2V( SetStatsExtraInfo, const char *, const char *, m_pMemAlloc );
#endif
DELEGATE_TO_OBJECT_0(size_t, MemoryAllocFailed, m_pMemAlloc );
virtual uint32 GetDebugInfoSize() { return 0; }
virtual void SaveDebugInfo( void *pvDebugInfo ) { }
virtual void RestoreDebugInfo( const void *pvDebugInfo ) {}
virtual void InitDebugInfo( void *pvDebugInfo, const char *pchRootFileName, int nLine ) {}
// Other public methods
public:
CLoaderMemAlloc();
void Start( MaterialNonInteractiveMode_t mode );
void Stop();
// Check if we need to call swap. Do so if necessary.
void CheckSwap( );
private:
IMemAlloc *m_pMemAlloc;
float m_flLastUpdateTime;
bool m_bInSwap;
};
//-----------------------------------------------------------------------------
// Activate, deactivate loadermemalloc
//-----------------------------------------------------------------------------
static CLoaderMemAlloc s_LoaderMemAlloc;
void BeginLoadingUpdates( MaterialNonInteractiveMode_t mode )
{
if ( IsX360() )
{
s_LoaderMemAlloc.Start( mode );
}
}
void RefreshScreenIfNecessary()
{
if ( IsX360() )
{
s_LoaderMemAlloc.CheckSwap();
}
}
void EndLoadingUpdates()
{
if ( IsX360() )
{
s_LoaderMemAlloc.Stop();
}
}
static int LoadLibraryThreadFunc()
{
RefreshScreenIfNecessary();
return 15;
}
//-----------------------------------------------------------------------------
// Used to tick the loading screen every so often
//-----------------------------------------------------------------------------
CLoaderMemAlloc::CLoaderMemAlloc()
{
m_pMemAlloc = 0;
}
void CLoaderMemAlloc::Start( MaterialNonInteractiveMode_t mode )
{
if ( m_pMemAlloc || ( mode == MATERIAL_NON_INTERACTIVE_MODE_NONE ) )
return;
CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
pRenderContext->EnableNonInteractiveMode( mode );
if ( mode == MATERIAL_NON_INTERACTIVE_MODE_STARTUP )
{
SetThreadedLoadLibraryFunc( LoadLibraryThreadFunc );
}
// NOTE: This is necessary to avoid a one-frame black flash
// since Present is what copies the back buffer into the temp buffer
if ( mode == MATERIAL_NON_INTERACTIVE_MODE_LEVEL_LOAD )
{
extern void V_RenderVGuiOnly( void );
V_RenderVGuiOnly();
}
m_flLastUpdateTime = UNINITIALIZED_LAST_TIME;
m_bInSwap = false;
m_pMemAlloc = g_pMemAlloc;
g_pMemAlloc = this;
}
void CLoaderMemAlloc::Stop()
{
if ( !m_pMemAlloc )
return;
g_pMemAlloc = m_pMemAlloc;
m_pMemAlloc = 0;
CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
pRenderContext->EnableNonInteractiveMode( MATERIAL_NON_INTERACTIVE_MODE_NONE );
SetThreadedLoadLibraryFunc( NULL );
}
//-----------------------------------------------------------------------------
// Check if we need to call swap. Do so if necessary.
//-----------------------------------------------------------------------------
void CLoaderMemAlloc::CheckSwap( )
{
if ( !m_pMemAlloc )
return;
float t = Plat_FloatTime();
float dt = t - m_flLastUpdateTime;
if ( dt >= LOADING_UPDATE_INTERVAL )
{
if ( ThreadInMainThread() && !m_bInSwap && !g_pMaterialSystem->IsInFrame() )
{
m_bInSwap = true;
CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
pRenderContext->RefreshFrontBufferNonInteractive();
m_bInSwap = false;
// NOTE: It is necessary to re-read time, since Refresh
// may block, and if it does, it'll force a refresh every allocation
// if we don't resample time after the block
m_flLastUpdateTime = Plat_FloatTime();
}
}
}
//-----------------------------------------------------------------------------
// Hook allocations, render when appropriate
//-----------------------------------------------------------------------------
void *CLoaderMemAlloc::Alloc( size_t nSize )
{
CheckSwap();
return m_pMemAlloc->Alloc( nSize );
}
void *CLoaderMemAlloc::Realloc( void *pMem, size_t nSize )
{
CheckSwap();
return m_pMemAlloc->Realloc( pMem, nSize );
}
void CLoaderMemAlloc::Free( void *pMem )
{
CheckSwap();
m_pMemAlloc->Free( pMem );
}
void *CLoaderMemAlloc::Alloc( size_t nSize, const char *pFileName, int nLine )
{
CheckSwap();
return m_pMemAlloc->Alloc( nSize, pFileName, nLine );
}
void *CLoaderMemAlloc::Realloc( void *pMem, size_t nSize, const char *pFileName, int nLine )
{
CheckSwap();
return m_pMemAlloc->Realloc( pMem, nSize, pFileName, nLine );
}
void CLoaderMemAlloc::Free( void *pMem, const char *pFileName, int nLine )
{
CheckSwap();
m_pMemAlloc->Free( pMem, pFileName, nLine );
}
#endif // _X360

39
engine/LoadScreenUpdate.h Normal file
View File

@ -0,0 +1,39 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: To accomplish X360 TCR 22, we need to call present ever 66msec
// at least during loading screens. This amazing hack will do it
// by overriding the allocator to tick it every so often.
//
// $NoKeywords: $
//===========================================================================//
#ifndef LOAD_SCREEN_UPDATE_H
#define LOAD_SCREEN_UPDATE_H
#ifdef _WIN32
#pragma once
#endif
#include "materialsystem/imaterialsystem.h"
//-----------------------------------------------------------------------------
// Activate, deactivate loader updates
//-----------------------------------------------------------------------------
#ifdef _X360
void BeginLoadingUpdates( MaterialNonInteractiveMode_t mode );
void RefreshScreenIfNecessary();
void EndLoadingUpdates();
#else
inline void BeginLoadingUpdates( MaterialNonInteractiveMode_t mode ) {}
inline void RefreshScreenIfNecessary() {}
inline void EndLoadingUpdates() {}
#endif
#endif /* LOAD_SCREEN_UPDATE_H */

View File

@ -0,0 +1,420 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "LocalNetworkBackdoor.h"
#include "server_class.h"
#include "client_class.h"
#include "server.h"
#include "eiface.h"
#include "cdll_engine_int.h"
#include "dt_localtransfer.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
CLocalNetworkBackdoor *g_pLocalNetworkBackdoor = NULL;
#ifndef SWDS
// This is called
void CLocalNetworkBackdoor::InitFastCopy()
{
if ( !cl.m_NetChannel->IsLoopback() )
return;
const CStandardSendProxies *pSendProxies = NULL;
// If the game server is greater than v4, then it is using the new proxy format.
if ( g_iServerGameDLLVersion >= 5 ) // check server version
{
pSendProxies = serverGameDLL->GetStandardSendProxies();
}
else
{
// If the game server is older than v4, it is using the old proxy; we set the new proxy members to the
// engine's copy.
static CStandardSendProxies compatSendProxy = *serverGameDLL->GetStandardSendProxies();
compatSendProxy.m_DataTableToDataTable = g_StandardSendProxies.m_DataTableToDataTable;
compatSendProxy.m_SendLocalDataTable = g_StandardSendProxies.m_SendLocalDataTable;
compatSendProxy.m_ppNonModifiedPointerProxies = g_StandardSendProxies.m_ppNonModifiedPointerProxies;
pSendProxies = &compatSendProxy;
}
const CStandardRecvProxies *pRecvProxies = g_ClientDLL->GetStandardRecvProxies();
int nFastCopyProps = 0;
int nSlowCopyProps = 0;
for ( int iClass=0; iClass < cl.m_nServerClasses; iClass++ )
{
ClientClass *pClientClass = cl.GetClientClass(iClass);
if ( !pClientClass )
Error( "InitFastCopy - missing client class %d (Should be equivelent of server class: %s)", iClass, cl.m_pServerClasses[iClass].m_ClassName );
ServerClass *pServerClass = SV_FindServerClass( pClientClass->GetName() );
if ( !pServerClass )
Error( "InitFastCopy - missing server class %s", pClientClass->GetName() );
LocalTransfer_InitFastCopy(
pServerClass->m_pTable,
pSendProxies,
pClientClass->m_pRecvTable,
pRecvProxies,
nSlowCopyProps,
nFastCopyProps
);
}
int percentFast = (nFastCopyProps * 100 ) / (nSlowCopyProps + nFastCopyProps + 1);
if ( percentFast <= 55 )
{
// This may not be a real problem, but at the time this code was added, 67% of the
// properties were able to be copied without proxies. If percentFast goes to 0 or some
// really low number suddenly, then something probably got screwed up.
Assert( false );
Warning( "InitFastCopy: only %d%% fast props. Bug?\n", percentFast );
}
}
#endif
void CLocalNetworkBackdoor::StartEntityStateUpdate()
{
m_EntsAlive.ClearAll();
m_nEntsCreated = 0;
m_nEntsChanged = 0;
// signal client that we start updating entities
ClientDLL_FrameStageNotify( FRAME_NET_UPDATE_START );
}
void CLocalNetworkBackdoor::EndEntityStateUpdate()
{
ClientDLL_FrameStageNotify( FRAME_NET_UPDATE_POSTDATAUPDATE_START );
// Handle entities created.
int i;
for ( i=0; i < m_nEntsCreated; i++ )
{
MDLCACHE_CRITICAL_SECTION_( g_pMDLCache );
int iEdict = m_EntsCreatedIndices[i];
CCachedEntState *pCached = &m_CachedEntState[iEdict];
IClientNetworkable *pNet = pCached->m_pNetworkable;
pNet->PostDataUpdate( DATA_UPDATE_CREATED );
pNet->NotifyShouldTransmit( SHOULDTRANSMIT_START );
pCached->m_bDormant = false;
}
// Handle entities changed.
for ( i=0; i < m_nEntsChanged; i++ )
{
MDLCACHE_CRITICAL_SECTION_( g_pMDLCache );
int iEdict = m_EntsChangedIndices[i];
m_CachedEntState[iEdict].m_pNetworkable->PostDataUpdate( DATA_UPDATE_DATATABLE_CHANGED );
}
ClientDLL_FrameStageNotify( FRAME_NET_UPDATE_POSTDATAUPDATE_END );
// Handle entities removed (= SV_WriteDeletions() in normal mode)
int nDWords = m_PrevEntsAlive.GetNumDWords();
// Handle entities removed.
for ( i=0; i < nDWords; i++ )
{
unsigned long prevEntsAlive = m_PrevEntsAlive.GetDWord( i );
unsigned long entsAlive = m_EntsAlive.GetDWord( i );
unsigned long toDelete = (prevEntsAlive ^ entsAlive) & prevEntsAlive;
if ( toDelete )
{
for ( int iBit=0; iBit < 32; iBit++ )
{
if ( toDelete & (1 << iBit) )
{
int iEdict = (i<<5) + iBit;
if ( iEdict >= 0 && iEdict < MAX_EDICTS )
{
if ( m_CachedEntState[iEdict].m_pNetworkable )
{
m_CachedEntState[iEdict].m_pNetworkable->Release();
m_CachedEntState[iEdict].m_pNetworkable = NULL;
}
else
{
AssertOnce( !"EndEntityStateUpdate: Would have crashed with NULL m_pNetworkable\n" );
}
}
else
{
AssertOnce( !"EndEntityStateUpdate: Would have crashed with entity out of range\n" );
}
}
}
}
}
// Remember the previous state of which entities were around.
m_PrevEntsAlive = m_EntsAlive;
// end of all entity update activity
ClientDLL_FrameStageNotify( FRAME_NET_UPDATE_END );
/*
#ifdef _DEBUG
for ( i=0; i <= highest_index; i++ )
{
if ( !( m_EntsAlive[i>>5] & (1 << (i & 31)) ) )
Assert( !m_CachedEntState[i].m_pNetworkable );
if ( ( m_EntsAlive[i>>5] & (1 << (i & 31)) ) &&
( m_EntsCreated[i>>5] & (1 << (i & 31)) ) )
{
Assert( FindInList( m_EntsCreatedIndices, m_nEntsCreated, i ) );
}
if ( (m_EntsAlive[i>>5] & (1 << (i & 31))) &&
!(m_EntsCreated[i>>5] & (1 << (i & 31))) &&
(m_EntsChanged[i>>5] & (1 << (i & 31)))
)
{
Assert( FindInList( m_EntsChangedIndices, m_nEntsChanged, i ) );
}
}
#endif
*/
}
void CLocalNetworkBackdoor::EntityDormant( int iEnt, int iSerialNum )
{
CCachedEntState *pCached = &m_CachedEntState[iEnt];
IClientNetworkable *pNet = pCached->m_pNetworkable;
Assert( pNet == entitylist->GetClientNetworkable( iEnt ) );
if ( pNet )
{
Assert( pCached->m_iSerialNumber == pNet->GetIClientUnknown()->GetRefEHandle().GetSerialNumber() );
if ( pCached->m_iSerialNumber == iSerialNum )
{
m_EntsAlive.Set( iEnt );
// Tell the game code that this guy is now dormant.
Assert( pCached->m_bDormant == pNet->IsDormant() );
if ( !pCached->m_bDormant )
{
pNet->NotifyShouldTransmit( SHOULDTRANSMIT_END );
pCached->m_bDormant = true;
}
}
else
{
pNet->Release();
pCached->m_pNetworkable = NULL;
m_PrevEntsAlive.Clear( iEnt );
}
}
}
void CLocalNetworkBackdoor::AddToPendingDormantEntityList( unsigned short iEdict )
{
edict_t *e = &sv.edicts[iEdict];
if ( !( e->m_fStateFlags & FL_EDICT_PENDING_DORMANT_CHECK ) )
{
e->m_fStateFlags |= FL_EDICT_PENDING_DORMANT_CHECK;
m_PendingDormantEntities.AddToTail( iEdict );
}
}
void CLocalNetworkBackdoor::ProcessDormantEntities()
{
FOR_EACH_LL( m_PendingDormantEntities, i )
{
int iEdict = m_PendingDormantEntities[i];
edict_t *e = &sv.edicts[iEdict];
// Make sure the entity still exists and stil has the dontsend flag set.
if ( e->IsFree() || !(e->m_fStateFlags & FL_EDICT_DONTSEND) )
{
e->m_fStateFlags &= ~FL_EDICT_PENDING_DORMANT_CHECK;
continue;
}
EntityDormant( iEdict, e->m_NetworkSerialNumber );
e->m_fStateFlags &= ~FL_EDICT_PENDING_DORMANT_CHECK;
}
m_PendingDormantEntities.Purge();
}
void CLocalNetworkBackdoor::EntState(
int iEnt,
int iSerialNum,
int iClass,
const SendTable *pSendTable,
const void *pSourceEnt,
bool bChanged,
bool bShouldTransmit )
{
CCachedEntState *pCached = &m_CachedEntState[iEnt];
// Remember that this ent is alive.
m_EntsAlive.Set(iEnt);
ClientClass *pClientClass = cl.GetClientClass(iClass);
if ( !pClientClass )
Error( "CLocalNetworkBackdoor::EntState - missing client class %d", iClass );
IClientNetworkable *pNet = pCached->m_pNetworkable;
Assert( pNet == entitylist->GetClientNetworkable( iEnt ) );
if ( !bShouldTransmit )
{
if ( pNet )
{
Assert( pCached->m_iSerialNumber == pNet->GetIClientUnknown()->GetRefEHandle().GetSerialNumber() );
if ( pCached->m_iSerialNumber == iSerialNum )
{
// Tell the game code that this guy is now dormant.
Assert( pCached->m_bDormant == pNet->IsDormant() );
if ( !pCached->m_bDormant )
{
pNet->NotifyShouldTransmit( SHOULDTRANSMIT_END );
pCached->m_bDormant = true;
}
}
else
{
pNet->Release();
pNet = NULL;
pCached->m_pNetworkable = NULL;
// Since we set this above, need to clear it now to avoid assertion in EndEntityStateUpdate()
m_EntsAlive.Clear(iEnt);
m_PrevEntsAlive.Clear( iEnt );
}
}
else
{
m_EntsAlive.Clear( iEnt );
}
return;
}
// Do we have an entity here already?
bool bExistedAndWasDormant = false;
if ( pNet )
{
// If the serial numbers are different, make it recreate the ent.
Assert( pCached->m_iSerialNumber == pNet->GetIClientUnknown()->GetRefEHandle().GetSerialNumber() );
if ( iSerialNum == pCached->m_iSerialNumber )
{
bExistedAndWasDormant = pCached->m_bDormant;
}
else
{
pNet->Release();
pNet = NULL;
m_PrevEntsAlive.Clear(iEnt);
}
}
// Create the entity?
bool bCreated = false;
DataUpdateType_t updateType;
if ( pNet )
{
updateType = DATA_UPDATE_DATATABLE_CHANGED;
}
else
{
updateType = DATA_UPDATE_CREATED;
pNet = pClientClass->m_pCreateFn( iEnt, iSerialNum );
bCreated = true;
m_EntsCreatedIndices[m_nEntsCreated++] = iEnt;
pCached->m_iSerialNumber = iSerialNum;
pCached->m_pDataPointer = pNet->GetDataTableBasePtr();
pCached->m_pNetworkable = pNet;
// Tracker 73192: ywb 8/1/07: We used to get an assertion that the pCached->m_bDormant was not equal to pNet->IsDormant() in ProcessDormantEntities.
// This appears to be the case if when we get here, the entity is set for Transmit still, but is a dormant entity on the server.
// Seems safe to go ahead an fill in the cache with the correct data. Probably was just an oversight.
pCached->m_bDormant = pNet->IsDormant();
}
if ( bChanged || bCreated || bExistedAndWasDormant )
{
pNet->PreDataUpdate( updateType );
Assert( pCached->m_pDataPointer == pNet->GetDataTableBasePtr() );
LocalTransfer_TransferEntity(
&sv.edicts[iEnt],
pSendTable,
pSourceEnt,
pClientClass->m_pRecvTable,
pCached->m_pDataPointer,
bCreated,
bExistedAndWasDormant,
iEnt );
if ( bExistedAndWasDormant )
{
// Set this so we use DATA_UPDATE_CREATED logic
m_EntsCreatedIndices[m_nEntsCreated++] = iEnt;
}
else
{
if ( !bCreated )
{
m_EntsChangedIndices[m_nEntsChanged++] = iEnt;
}
}
}
}
void CLocalNetworkBackdoor::ClearState()
{
// Clear the cache for all the entities.
for ( int i=0; i < MAX_EDICTS; i++ )
{
CCachedEntState &ces = m_CachedEntState[i];
ces.m_pNetworkable = NULL;
ces.m_iSerialNumber = -1;
ces.m_bDormant = false;
ces.m_pDataPointer = NULL;
}
m_PrevEntsAlive.ClearAll();
}
void CLocalNetworkBackdoor::StartBackdoorMode()
{
ClearState();
for ( int i=0; i < MAX_EDICTS; i++ )
{
IClientNetworkable *pNet = entitylist->GetClientNetworkable( i );
CCachedEntState &ces = m_CachedEntState[i];
if ( pNet )
{
ces.m_pNetworkable = pNet;
ces.m_iSerialNumber = pNet->GetIClientUnknown()->GetRefEHandle().GetSerialNumber();
ces.m_bDormant = pNet->IsDormant();
ces.m_pDataPointer = pNet->GetDataTableBasePtr();
m_PrevEntsAlive.Set( i );
}
}
}
void CLocalNetworkBackdoor::StopBackdoorMode()
{
ClearState();
}

View File

@ -0,0 +1,111 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef LOCALNETWORKBACKDOOR_H
#define LOCALNETWORKBACKDOOR_H
#ifdef _WIN32
#pragma once
#endif
#include "quakedef.h"
#include "cl_localnetworkbackdoor.h"
#include "icliententitylist.h"
#include "LocalNetworkBackdoor.h"
#include "iclientnetworkable.h"
#include "basehandle.h"
#include "client_class.h"
#include "dt_localtransfer.h"
#include "client.h"
#include "cdll_engine_int.h"
#include "datacache/imdlcache.h"
#include "sys_dll.h"
#include "utllinkedlist.h"
#include "edict.h"
#include "server.h"
class SendTable;
class CLocalNetworkBackdoor
{
public:
void StartEntityStateUpdate();
void EndEntityStateUpdate();
void EntityDormant( int iEnt, int iSerialNum );
void AddToPendingDormantEntityList( unsigned short iEdict );
void ProcessDormantEntities();
void NotifyEdictFlagsChange( int iEdict )
{
// If they newly added the dontsend flag, then we need to run it through EntityDormant.
if ( sv.edicts[iEdict].m_fStateFlags & FL_EDICT_DONTSEND )
AddToPendingDormantEntityList( iEdict );
}
void EntState(
int iEnt,
int iSerialNum,
int iClass,
const SendTable *pSendTable,
const void *pSourceEnt,
bool bChanged,
bool bShouldTransmit );
void ClearState();
void StartBackdoorMode();
void StopBackdoorMode();
// This is called when the client DLL is loaded to precalculate data to let it copy data faster.
static void InitFastCopy();
private:
// Temporarily built up while processing a frame.
CBitVec<MAX_EDICTS> m_EntsAlive;
// This should correspond to which m_CachedEntState entries have a non-null m_pNetworkable pointer.
CBitVec<MAX_EDICTS> m_PrevEntsAlive;
// Entities that get created during a frame are remembered here.
unsigned short m_EntsCreatedIndices[MAX_EDICTS];
int m_nEntsCreated;
// Entities that changed but weren't created go here.
unsigned short m_EntsChangedIndices[MAX_EDICTS];
int m_nEntsChanged;
// Tell the client DLL about entities that need to be notified about being dormant.
// Anything that EntityDormant() would care about needs to get added to this list.
CUtlLinkedList<unsigned short,unsigned short> m_PendingDormantEntities;
// This data is cached in here so we don't have to call a lot of virtuals to get it from the client DLL.
class CCachedEntState
{
public:
CCachedEntState()
{
m_iSerialNumber = -1;
}
bool m_bDormant;
int m_iSerialNumber;
void *m_pDataPointer;
IClientNetworkable *m_pNetworkable;
};
CCachedEntState m_CachedEntState[MAX_EDICTS];
};
// The client will set this if it decides to use the fast path.
extern CLocalNetworkBackdoor *g_pLocalNetworkBackdoor;
#endif // LOCALNETWORKBACKDOOR_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,128 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef MAPRESLISTGENERATOR_H
#define MAPRESLISTGENERATOR_H
#ifdef _WIN32
#pragma once
#endif
#include "filesystem.h"
#include "utlvector.h"
#include "utlsymbol.h"
#include "utlstring.h"
// Map list creation
// Map entry
struct maplist_map_t
{
char name[64];
};
// General purpose maplist generator.
// aMaps: Utlvector you'd like filled with the maps.
// bUseMapListFile: true if you want to use a maplist file, vs parsing the maps directory or using +map
// pMapFile: If you're using a maplist file, this should be the filename of it
// pSystemMsg: Used to preface and debug messages
// iCurrentMap: The map in the list to begin at. Handles the -startmap parameter for you.
bool BuildGeneralMapList( CUtlVector<maplist_map_t> *aMaps, bool bUseMapListFile, const char *pMapFile, char *pSystemMsg, int *iCurrentMap );
// initialization
void MapReslistGenerator_Init();
void MapReslistGenerator_Shutdown();
void MapReslistGenerator_BuildMapList();
//-----------------------------------------------------------------------------
// Purpose: Handles collating lists of resources on level load
// Used to generate reslists for steam
//-----------------------------------------------------------------------------
class CMapReslistGenerator
{
public:
CMapReslistGenerator();
// initializes the object to enable reslist generation
void EnableReslistGeneration( bool usemaplistfile );
// starts the reslist generation (starts cycling maps)
void StartReslistGeneration();
void BuildMapList();
void Shutdown();
// call every frame if we're enabled, just so that the next map can be triggered at the right time
void RunFrame();
// returns true if reslist generation is enabled
bool IsEnabled() { return m_bLoggingEnabled; }
bool IsLoggingToMap() { return m_bLoggingEnabled && !m_bLogToEngineList; }
// call to mark level load/end
void OnLevelLoadStart(const char *levelName);
void OnLevelLoadEnd();
void OnPlayerSpawn();
// call to mark resources as being precached
void OnResourcePrecached(const char *relativePathFileName);
void OnModelPrecached(const char *relativePathFileName);
void OnSoundPrecached(const char *relativePathFileName);
char const *LogPrefix();
void EnableDeletionsTracking();
void TrackDeletions( const char *fullPathFileName );
bool ShouldRebuildCaches();
char const *GetResListDirectory() const;
void SetAutoQuit( bool bState );
private:
static void TrackDeletionsLoggingFunc(const char *fullPathFileName, const char *options);
static void FileSystemLoggingFunc(const char *fullPathFileName, const char *options);
void OnResourcePrecachedFullPath(const char *fullPathFileName);
void BuildEngineLogFromReslist();
void LogToEngineReslist( char const *pLine );
void WriteMapLog();
void SetPrefix( char const *mapname );
void SpewTrackedDeletionsLog();
void DoQuit();
bool m_bTrackingDeletions;
bool m_bLoggingEnabled;
bool m_bUsingMapList;
bool m_bRestartOnTransition;
// true for engine, false for map
bool m_bLogToEngineList;
bool m_bAutoQuit;
// list of all maps to scan
CUtlVector<maplist_map_t> m_Maps;
int m_iCurrentMap;
float m_flNextMapRunTime;
int m_iFrameCountdownToRunningNextMap;
CUtlSymbolTable m_AlreadyWrittenFileNames;
int m_iPauseTimeBetweenMaps;
int m_iPauseFramesBetweenMaps;
char m_szPrefix[64];
char m_szLevelName[64];
CUtlSymbolTable m_DeletionList;
CUtlRBTree< CUtlSymbol > m_DeletionListWarnings;
CUtlSymbolTable m_DeletionListWarningsSymbols;
CUtlRBTree< CUtlString, int > m_MapLog;
CUtlRBTree< CUtlString, int > m_EngineLog;
CUtlString m_sResListDir;
};
// singleton accessor
CMapReslistGenerator &MapReslistGenerator();
#endif // MAPRESLISTGENERATOR_H

153
engine/MaterialBuckets.h Normal file
View File

@ -0,0 +1,153 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: This is a helper class for the situation where you want to build lists of
// things that fall into buckets, and effeciently keep up with which buckets have
// been used since flush.
//
//=============================================================================//
#ifndef MATERIALBUCKETS_H
#define MATERIALBUCKETS_H
#ifdef _WIN32
#pragma once
#endif
// FLASHLIGHTFIXME: Make all of the buckets share the same m_Elements (ie. make m_Elements static)
template <class Element_t>
class CMaterialsBuckets
{
public:
typedef unsigned short SortIDHandle_t;
typedef unsigned short ElementHandle_t;
CMaterialsBuckets()
{
m_FlushCount = -1;
}
// Set the number of buckets that are needed. This should get called every time
// a level is loaded.
void SetNumMaterialSortIDs( int n )
{
m_MaterialSortInfoArray.Purge();
m_MaterialSortInfoArray.SetCount( n );
m_Elements.Purge();
m_UsedSortIDs.Purge();
}
// Clear out all buckets. This should get called once a frame.
void Flush( void )
{
m_FlushCount++;
m_Elements.RemoveAll();
m_UsedSortIDs.RemoveAll();
}
//
// These functions are used to get at the list of used buckets.
//
SortIDHandle_t GetFirstUsedSortID()
{
return m_UsedSortIDs.Head();
}
SortIDHandle_t GetNextUsedSortID( SortIDHandle_t prevSortID )
{
return m_UsedSortIDs.Next( prevSortID );
}
int GetSortID( SortIDHandle_t handle )
{
return m_UsedSortIDs[handle];
}
SortIDHandle_t InvalidSortIDHandle()
{
return m_UsedSortIDs.InvalidIndex();
}
//
// These functions are used to get at the list of elements for each sortID.
//
ElementHandle_t GetElementListHead( int sortID )
{
return m_MaterialSortInfoArray[sortID].m_Head;
}
ElementHandle_t GetElementListNext( ElementHandle_t h )
{
return m_Elements.Next( h );
}
Element_t GetElement( ElementHandle_t h )
{
return m_Elements[h];
}
ElementHandle_t InvalidElementHandle()
{
return m_Elements.InvalidIndex();
}
// Add an element to the the bucket specified by sortID
void AddElement( int sortID, Element_t elem )
{
// Allocate an element to stick this in.
unsigned short elemID = m_Elements.Alloc( true );
m_Elements[elemID] = elem;
if( m_MaterialSortInfoArray[sortID].m_FlushCount != m_FlushCount )
{
// This is the first element that has used this sort id since flush.
// FLASHLIGHTFIXME: need to sort these by vertex format when shoving
// them into this list!
// Mark this sortID as having been used since the last flush.
m_MaterialSortInfoArray[sortID].m_FlushCount = m_FlushCount;
// Add this sortID to the list of sortIDs used since flush.
m_UsedSortIDs.AddToTail( sortID );
// Set the head pointer for this sort id to this element.
m_MaterialSortInfoArray[sortID].m_Head = elemID;
}
else
{
// We already have an element in this sort id since flush, so chain
// into thelist of elements for this sort id.
m_Elements.LinkBefore( m_MaterialSortInfoArray[sortID].m_Head, elemID );
m_MaterialSortInfoArray[sortID].m_Head = elemID;
}
}
private:
struct MaterialSortInfo_t
{
MaterialSortInfo_t() :
m_FlushCount( -1 ),
m_Head( (unsigned short)-1 ) // i.e., InvalidIndex()
{
}
int m_FlushCount;
unsigned short m_Head;
};
// This is a list of material sort info ids that have been used since flush.
CUtlLinkedList<unsigned short> m_UsedSortIDs;
// This is m_NumMaterialSortIDs big.
CUtlVector<MaterialSortInfo_t> m_MaterialSortInfoArray;
// This is used in multilist mode to make elements that belong in the multiple lists of
CUtlLinkedList<Element_t, unsigned short, true> m_Elements;
int m_FlushCount;
};
#endif // MATERIALBUCKETS_H

1308
engine/ModelInfo.cpp Normal file

File diff suppressed because it is too large Load Diff

23
engine/ModelInfo.h Normal file
View File

@ -0,0 +1,23 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $Workfile: $
// $NoKeywords: $
//=============================================================================//
#ifndef MODELINFO_H
#define MODELINFO_H
#ifdef _WIN32
#pragma once
#endif
#include "engine/ivmodelinfo.h"
extern IVModelInfo *modelinfo; // server version
extern IVModelInfoClient *modelinfoclient; // client version
#endif // MODELINFO_H

View File

@ -0,0 +1,232 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "quakedef.h"
#include "networkstringtableitem.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CNetworkStringTableItem::CNetworkStringTableItem( void )
{
m_pUserData = NULL;
m_nUserDataLength = 0;
m_nTickChanged = 0;
#ifndef SHARED_NET_STRING_TABLES
m_nTickCreated = 0;
m_pChangeList = NULL;
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CNetworkStringTableItem::~CNetworkStringTableItem( void )
{
#ifndef SHARED_NET_STRING_TABLES
if ( m_pChangeList )
{
// free changelist and elements
for ( int i=0; i < m_pChangeList->Count(); i++ )
{
itemchange_s item = m_pChangeList->Element( i );
if ( item.data )
delete[] item.data;
}
delete m_pChangeList; // destructor calls Purge()
m_pUserData = NULL;
}
#endif
if ( m_pUserData )
{
delete[] m_pUserData;
}
}
#ifndef SHARED_NET_STRING_TABLES
void CNetworkStringTableItem::EnableChangeHistory( void )
{
if ( m_pChangeList )
return; // already enabled
m_pChangeList = new CUtlVector<itemchange_s>;
Assert ( m_pChangeList );
}
void CNetworkStringTableItem::UpdateChangeList( int tick, int length, const void *userData )
{
int count = m_pChangeList->Count();
itemchange_s item;
if ( count > 0 )
{
// check if different from last change in list
item = m_pChangeList->Element( count-1 );
if ( !item.data && !userData )
return; // both NULL data
if ( item.length == length )
{
if ( item.data && userData )
{
if ( Q_memcmp( (void*)userData, (void*)item.data, length ) == 0 )
{
return; // no data or size change
}
}
}
if ( item.tick == tick )
{
// two changes within same tick frame, remove last change from list
if ( item.data )
{
delete[] item.data;
}
m_pChangeList->Remove( count-1 );
}
}
item.tick = tick;
// add new user data and time stamp
if ( userData && length )
{
item.data = new unsigned char[length];
item.length = length;
Q_memcpy( item.data, userData, length );
}
else
{
item.data = NULL;
item.length = 0;
}
m_pChangeList->AddToTail( item );
}
int CNetworkStringTableItem::RestoreTick( int tick )
{
Assert( m_pChangeList->Count()>0 );
int index = 1;
itemchange_s *item = &m_pChangeList->Element( 0 );
while ( index < m_pChangeList->Count() )
{
itemchange_s *nextitem = &m_pChangeList->Element( index );
if ( nextitem->tick > tick )
{
break;
}
item = nextitem;
index++;
}
if ( item->tick > tick )
{
// this string was created after tick, so ignore it right now
m_pUserData = NULL;
m_nUserDataLength = 0;
m_nTickChanged = 0;
}
else
{
// restore user data for this string
m_pUserData = item->data;
m_nUserDataLength = item->length;
m_nTickChanged = item->tick;
}
return m_nTickChanged;
}
#endif
//-----------------------------------------------------------------------------
// Purpose:
// Input : *string -
//-----------------------------------------------------------------------------
bool CNetworkStringTableItem::SetUserData( int tick, int length, const void *userData )
{
#ifndef SHARED_NET_STRING_TABLES
if ( m_pChangeList )
{
UpdateChangeList( tick, length, userData );
return false;
}
Assert ( m_nTickCreated > 0 && m_nTickCreated <= tick );
#endif
Assert ( m_nTickChanged > 0 && m_nTickChanged <= tick );
Assert ( length < CNetworkStringTableItem::MAX_USERDATA_SIZE );
// no old or new data
if ( !userData && !m_pUserData )
return false;
if ( m_pUserData &&
length == m_nUserDataLength &&
!Q_memcmp( m_pUserData, (void*)userData, length ) )
{
return false; // old & new data are equal
}
if ( m_pUserData )
delete[] m_pUserData;
m_nUserDataLength = length;
if ( length > 0 )
{
m_pUserData = new unsigned char[ length ];
Q_memcpy( m_pUserData, userData, length );
}
else
{
m_pUserData = NULL;
}
m_nTickChanged = tick;
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : stringNumber -
// Output : const void
//-----------------------------------------------------------------------------
const void *CNetworkStringTableItem::GetUserData( int *length )
{
if ( length )
*length = m_nUserDataLength;
return ( const void * )m_pUserData;
}

2957
engine/OcclusionSystem.cpp Normal file

File diff suppressed because it is too large Load Diff

2358
engine/Overlay.cpp Normal file

File diff suppressed because it is too large Load Diff

65
engine/Overlay.h Normal file
View File

@ -0,0 +1,65 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Model loading / unloading interface
//
// $NoKeywords: $
//=============================================================================//
#ifndef OVERLAY_H
#define OVERLAY_H
#ifdef _WIN32
#pragma once
#endif
// This is a workaround for the fact that we get massive decal flicker
// when looking at a decal at a glancing angle while standing right next to it.
#define OVERLAY_AVOID_FLICKER_NORMAL_OFFSET 0.1f
//-----------------------------------------------------------------------------
// Overlay fragments
//-----------------------------------------------------------------------------
typedef unsigned short OverlayFragmentHandle_t;
enum
{
OVERLAY_FRAGMENT_INVALID = (OverlayFragmentHandle_t)~0
};
//=============================================================================
//
// Overlay Manager Interface
//
class IOverlayMgr
{
public:
// Memory allocation/de-allocation.
virtual bool LoadOverlays( ) = 0;
virtual void UnloadOverlays( ) = 0;
virtual void CreateFragments( void ) = 0;
virtual void ReSortMaterials( void ) = 0;
// Drawing
// clears all
virtual void ClearRenderLists() = 0;
// clears a particular sort group
virtual void ClearRenderLists( int nSortGroup ) = 0;
virtual void AddFragmentListToRenderList( int nSortGroup, OverlayFragmentHandle_t iFragment, bool bDisp ) = 0;
virtual void RenderOverlays( int nSortGroup ) = 0;
// Sets the client renderable for an overlay's material proxy to bind to
virtual void SetOverlayBindProxy( int iOverlayID, void *pBindProxy ) = 0;
};
//-----------------------------------------------------------------------------
// Overlay manager singleton
//-----------------------------------------------------------------------------
IOverlayMgr *OverlayMgr();
#endif // OVERLAY_H

View File

@ -0,0 +1,38 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef SVCONNECTIONLESSHANDLER_H
#define SVCONNECTIONLESSHANDLER_H
#ifdef _WIN32
#pragma once
#endif
#include "inetmsghandler.h"
class CServerConnectionlessHandler : public IConnectionlessPacketHandler
{
public:
CServerConnectionlessHandler();
virtual ~CServerConnectionlessHandler();
public:
bool ProcessConnectionlessPacket( netpacket_t * packet );
private :
bool ProcessLog(netpacket_t * packet);
bool ProcessGetChallenge(netpacket_t * packet);
bool ProcessConnect(netpacket_t * packet);
bool ProcessInfo(netpacket_t * packet);
bool ProcessDetails(netpacket_t * packet);
bool ProcessPlayers(netpacket_t * packet);
bool ProcessRules(netpacket_t * packet);
bool ProcessRcon(netpacket_t * packet);
};
extern CServerConnectionlessHandler g_SVConnectionlessHandler;
#endif // SVCONNECTIONLESSHANDLER_H

611
engine/Session.cpp Normal file
View File

@ -0,0 +1,611 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "Session.h"
#include "strtools.h"
#include "matchmaking.h"
#include "utllinkedlist.h"
#include "tslist.h"
#include "hl2orange.spa.h"
#include "dbg.h"
#ifdef IS_WINDOWS_PC
#include "winlite.h" // for CloseHandle()
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern IXboxSystem *g_pXboxSystem;
#define ASYNC_OK 0
#define ASYNC_FAIL 1
//-----------------------------------------------------------------------------
// Purpose: CSession class
//-----------------------------------------------------------------------------
CSession::CSession()
{
m_pParent = NULL;
m_hSession = INVALID_HANDLE_VALUE;
m_SessionState = SESSION_STATE_NONE;
m_pRegistrationResults = NULL;
ResetSession();
}
CSession::~CSession()
{
ResetSession();
}
//-----------------------------------------------------------------------------
// Purpose: Reset a session to it's initial state
//-----------------------------------------------------------------------------
void CSession::ResetSession()
{
// Cleanup first
switch( m_SessionState )
{
case SESSION_STATE_CREATING:
CancelCreateSession();
break;
case SESSION_STATE_MIGRATING:
// X360TBD:
break;
}
if ( m_hSession != INVALID_HANDLE_VALUE )
{
Msg( "ResetSession: Destroying current session.\n" );
DestroySession();
m_hSession = INVALID_HANDLE_VALUE;
}
SwitchToState( SESSION_STATE_NONE );
m_bIsHost = false;
m_bIsArbitrated = false;
m_bUsingQoS = false;
m_bIsSystemLink = false;
Q_memset( &m_nPlayerSlots, 0, sizeof( m_nPlayerSlots ) );
Q_memset( &m_SessionInfo, 0, sizeof( m_SessionInfo ) );
if ( m_pRegistrationResults )
{
delete m_pRegistrationResults;
}
m_nSessionFlags = 0;
}
//-----------------------------------------------------------------------------
// Purpose: Set session contexts and properties
//-----------------------------------------------------------------------------
void CSession::SetContext( const uint nContextId, const uint nContextValue, const bool bAsync )
{
g_pXboxSystem->UserSetContext( XBX_GetPrimaryUserId(), nContextId, nContextValue, bAsync );
}
void CSession::SetProperty( const uint nPropertyId, const uint cbValue, const void *pvValue, const bool bAsync )
{
g_pXboxSystem->UserSetProperty( XBX_GetPrimaryUserId(), nPropertyId, cbValue, pvValue, bAsync );
}
//-----------------------------------------------------------------------------
// Purpose: Send a notification to GameUI
//-----------------------------------------------------------------------------
void CSession::SendNotification( SESSION_NOTIFY notification )
{
Assert( m_pParent );
m_pParent->SessionNotification( notification );
}
//-----------------------------------------------------------------------------
// Purpose: Update the number of player slots filled
//-----------------------------------------------------------------------------
void CSession::UpdateSlots( const CClientInfo *pClient, bool bAddPlayers )
{
int cPlayers = pClient->m_cPlayers;
if ( bAddPlayers )
{
if ( pClient->m_bInvited )
{
// Fill private slots first, then overflow into public slots
m_nPlayerSlots[SLOTS_FILLEDPRIVATE] += cPlayers;
pClient->m_numPrivateSlotsUsed = cPlayers;
cPlayers = 0;
if ( m_nPlayerSlots[SLOTS_FILLEDPRIVATE] > m_nPlayerSlots[SLOTS_TOTALPRIVATE] )
{
cPlayers = m_nPlayerSlots[SLOTS_FILLEDPRIVATE] - m_nPlayerSlots[SLOTS_TOTALPRIVATE];
pClient->m_numPrivateSlotsUsed -= cPlayers;
m_nPlayerSlots[SLOTS_FILLEDPRIVATE] = m_nPlayerSlots[SLOTS_TOTALPRIVATE];
}
}
m_nPlayerSlots[SLOTS_FILLEDPUBLIC] += cPlayers;
if ( m_nPlayerSlots[SLOTS_FILLEDPUBLIC] > m_nPlayerSlots[SLOTS_TOTALPUBLIC] )
{
//Handle error
Warning( "Too many players!\n" );
}
}
else
{
// The cast to 'int' is needed since otherwise underflow will wrap around to very large
// numbers and the 'max' macro will do nothing.
m_nPlayerSlots[SLOTS_FILLEDPRIVATE] = max( 0, (int)( m_nPlayerSlots[SLOTS_FILLEDPRIVATE] - pClient->m_numPrivateSlotsUsed ) );
m_nPlayerSlots[SLOTS_FILLEDPUBLIC] = max( 0, (int)( m_nPlayerSlots[SLOTS_FILLEDPUBLIC] - ( pClient->m_cPlayers - pClient->m_numPrivateSlotsUsed ) ) );
}
}
//-----------------------------------------------------------------------------
// Purpose: Join players on the local client
//-----------------------------------------------------------------------------
void CSession::JoinLocal( const CClientInfo *pClient )
{
uint nUserIndex[MAX_PLAYERS_PER_CLIENT] = {0};
bool bPrivate[MAX_PLAYERS_PER_CLIENT] = {0};
for( int i = 0; i < pClient->m_cPlayers; ++i )
{
nUserIndex[i] = pClient->m_iControllers[i];
bPrivate[i] = pClient->m_bInvited;
}
// X360TBD: Make async?
uint ret = g_pXboxSystem->SessionJoinLocal( m_hSession, pClient->m_cPlayers, nUserIndex, bPrivate, false );
if ( ret != ERROR_SUCCESS )
{
// Handle error
Warning( "Failed to add local players\n" );
}
else
{
UpdateSlots( pClient, true );
}
}
//-----------------------------------------------------------------------------
// Purpose: Join players on a remote client
//-----------------------------------------------------------------------------
void CSession::JoinRemote( const CClientInfo *pClient )
{
XUID xuids[MAX_PLAYERS_PER_CLIENT] = {0};
bool bPrivate[MAX_PLAYERS_PER_CLIENT] = {0};
for( int i = 0; i < pClient->m_cPlayers; ++i )
{
xuids[i] = pClient->m_xuids[i];
bPrivate[i] = pClient->m_bInvited;
}
// X360TBD: Make async?
uint ret = g_pXboxSystem->SessionJoinRemote( m_hSession, pClient->m_cPlayers, xuids, bPrivate, false );
if ( ret != ERROR_SUCCESS )
{
// Handle error
Warning( "Join Remote Error\n" );
}
else
{
UpdateSlots( pClient, true );
}
}
//-----------------------------------------------------------------------------
// Purpose: Remove players on the local client
//-----------------------------------------------------------------------------
void CSession::RemoveLocal( const CClientInfo *pClient )
{
uint nUserIndex[MAX_PLAYERS_PER_CLIENT] = {0};
for( int i = 0; i < pClient->m_cPlayers; ++i )
{
nUserIndex[i] = pClient->m_iControllers[i];
}
// X360TBD: Make async?
uint ret = g_pXboxSystem->SessionLeaveLocal( m_hSession, pClient->m_cPlayers, nUserIndex, false );
if ( ret != ERROR_SUCCESS )
{
// Handle error
Warning( "Failed to remove local players\n" );
}
else
{
UpdateSlots( pClient, false );
}
}
//-----------------------------------------------------------------------------
// Purpose: Remove players on a remote client
//-----------------------------------------------------------------------------
void CSession::RemoveRemote( const CClientInfo *pClient )
{
XUID xuids[MAX_PLAYERS_PER_CLIENT] = {0};
for( int i = 0; i < pClient->m_cPlayers; ++i )
{
xuids[i] = pClient->m_xuids[i];
}
// X360TBD: Make async?
uint ret = g_pXboxSystem->SessionLeaveRemote( m_hSession, pClient->m_cPlayers, xuids, false );
if ( ret != ERROR_SUCCESS )
{
// Handle error
Warning( "Failed to remove remote players\n" );
}
else
{
UpdateSlots( pClient, false );
}
}
//-----------------------------------------------------------------------------
// Purpose: Create a new session
//-----------------------------------------------------------------------------
bool CSession::CreateSession()
{
if( INVALID_HANDLE_VALUE != m_hSession )
{
Warning( "CreateSession called on existing session!" );
DestroySession();
m_hSession = INVALID_HANDLE_VALUE;
}
uint flags = m_nSessionFlags;
if( m_bIsHost )
{
flags |= XSESSION_CREATE_HOST;
}
if ( flags & XSESSION_CREATE_USES_ARBITRATION )
{
m_bIsArbitrated = true;
}
m_hCreateHandle = g_pXboxSystem->CreateAsyncHandle();
// Create the session
uint ret = g_pXboxSystem->CreateSession( flags,
XBX_GetPrimaryUserId(),
m_nPlayerSlots[SLOTS_TOTALPUBLIC],
m_nPlayerSlots[SLOTS_TOTALPRIVATE],
&m_SessionNonce,
&m_SessionInfo,
&m_hSession,
true,
&m_hCreateHandle );
if( ret != ERROR_SUCCESS && ret != ERROR_IO_PENDING )
{
Warning( "XSessionCreate failed with error %d\n", ret );
return false;
}
SwitchToState( SESSION_STATE_CREATING );
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Check for completion while creating a new session
//-----------------------------------------------------------------------------
void CSession::UpdateCreating()
{
DWORD ret = g_pXboxSystem->GetOverlappedResult( m_hCreateHandle, NULL, false );
if ( ret == ERROR_IO_INCOMPLETE )
{
// Still waiting
return;
}
else
{
SESSION_STATE nextState = SESSION_STATE_IDLE;
// Operation completed
SESSION_NOTIFY notification = IsHost() ? SESSION_NOTIFY_CREATED_HOST : SESSION_NOTIFY_CREATED_CLIENT;
if ( ret != ERROR_SUCCESS )
{
Warning( "CSession: CreateSession failed. Error %d\n", ret );
nextState = SESSION_STATE_NONE;
notification = SESSION_NOTIFY_FAIL_CREATE;
}
g_pXboxSystem->ReleaseAsyncHandle( m_hCreateHandle );
SendNotification( notification );
SwitchToState( nextState );
}
}
//-----------------------------------------------------------------------------
// Purpose: Cancel async session creation
//-----------------------------------------------------------------------------
void CSession::CancelCreateSession()
{
if ( m_SessionState != SESSION_STATE_CREATING )
return;
g_pXboxSystem->CancelOverlappedOperation( &m_hCreateHandle );
g_pXboxSystem->ReleaseAsyncHandle( m_hCreateHandle );
#ifndef POSIX
if( INVALID_HANDLE_VALUE != m_hSession )
{
CloseHandle( m_hSession );
m_hSession = INVALID_HANDLE_VALUE;
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Close an existing session
//-----------------------------------------------------------------------------
void CSession::DestroySession()
{
// TODO: Make this async
uint ret = g_pXboxSystem->DeleteSession( m_hSession, false );
if ( ret != ERROR_SUCCESS )
{
Warning( "Failed to delete session with error %d.\n", ret );
}
}
//-----------------------------------------------------------------------------
// Purpose: Register for arbritation in a ranked match
//-----------------------------------------------------------------------------
void CSession::RegisterForArbitration()
{
uint bytes = 0;
m_pRegistrationResults = NULL;
// Call once to determine size of results buffer
int ret = g_pXboxSystem->SessionArbitrationRegister( m_hSession, 0, m_SessionNonce, &bytes, NULL, false );
if ( ret != ERROR_INSUFFICIENT_BUFFER )
{
Warning( "Failed registering for arbitration\n" );
return;
}
m_pRegistrationResults = (XSESSION_REGISTRATION_RESULTS*)new byte[ bytes ];
m_hRegisterHandle = g_pXboxSystem->CreateAsyncHandle();
ret = g_pXboxSystem->SessionArbitrationRegister( m_hSession, 0, m_SessionNonce, &bytes, m_pRegistrationResults, true, &m_hRegisterHandle );
if( ret != ERROR_IO_PENDING )
{
Warning( "Failed registering for arbitration\n" );
}
m_SessionState = SESSION_STATE_REGISTERING;
}
//-----------------------------------------------------------------------------
// Purpose: Check for completion of arbitration registration
//-----------------------------------------------------------------------------
void CSession::UpdateRegistering()
{
DWORD ret = g_pXboxSystem->GetOverlappedResult( m_hRegisterHandle, NULL, false );
if ( ret == ERROR_IO_INCOMPLETE )
{
// Still waiting
return;
}
else
{
SESSION_STATE nextState = SESSION_STATE_IDLE;
// Operation completed
SESSION_NOTIFY notification = SESSION_NOTIFY_REGISTER_COMPLETED;
if ( ret != ERROR_SUCCESS )
{
Warning( "CSession: Registration failed. Error %d\n", ret );
nextState = SESSION_STATE_NONE;
notification = SESSION_NOTIFY_FAIL_REGISTER;
}
g_pXboxSystem->ReleaseAsyncHandle( m_hRegisterHandle );
SendNotification( notification );
SwitchToState( nextState );
}
}
//-----------------------------------------------------------------------------
// Purpose: Migrate the session to a new host
//-----------------------------------------------------------------------------
bool CSession::MigrateHost()
{
if ( IsHost() )
{
// Migrate call will fill this in for us
Q_memcpy( &m_NewSessionInfo, &m_SessionInfo, sizeof( m_NewSessionInfo ) );
}
m_hMigrateHandle = g_pXboxSystem->CreateAsyncHandle();
int ret = g_pXboxSystem->SessionMigrate( m_hSession, m_nOwnerId, &m_NewSessionInfo, true, &m_hMigrateHandle );
if ( ret != ERROR_IO_PENDING )
{
return false;
}
SwitchToState( SESSION_STATE_MIGRATING );
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Check for completion while migrating a session
//-----------------------------------------------------------------------------
void CSession::UpdateMigrating()
{
DWORD ret = g_pXboxSystem->GetOverlappedResult( m_hMigrateHandle, NULL, false );
if ( ret == ERROR_IO_INCOMPLETE )
{
// Still waiting
return;
}
else
{
SESSION_STATE nextState = SESSION_STATE_IDLE;
// Operation completed
SESSION_NOTIFY notification = SESSION_NOTIFY_MIGRATION_COMPLETED;
if ( ret != ERROR_SUCCESS )
{
Warning( "CSession: MigrateSession failed. Error %d\n", ret );
nextState = SESSION_STATE_NONE;
notification = SESSION_NOTIFY_FAIL_MIGRATE;
}
g_pXboxSystem->ReleaseAsyncHandle( m_hMigrateHandle );
SendNotification( notification );
SwitchToState( nextState );
}
}
//-----------------------------------------------------------------------------
// Purpose: Change state
//-----------------------------------------------------------------------------
void CSession::SwitchToState( SESSION_STATE newState )
{
m_SessionState = newState;
}
//-----------------------------------------------------------------------------
// Purpose: Per-frame update
//-----------------------------------------------------------------------------
void CSession::RunFrame()
{
switch( m_SessionState )
{
case SESSION_STATE_CREATING:
UpdateCreating();
break;
case SESSION_STATE_REGISTERING:
UpdateRegistering();
break;
case SESSION_STATE_MIGRATING:
UpdateMigrating();
break;
default:
break;
}
}
//-----------------------------------------------------------------------------
// Accessors
//-----------------------------------------------------------------------------
HANDLE CSession::GetSessionHandle()
{
return m_hSession;
}
void CSession::SetSessionInfo( XSESSION_INFO *pInfo )
{
memcpy( &m_SessionInfo, pInfo, sizeof( XSESSION_INFO ) );
}
void CSession::SetNewSessionInfo( XSESSION_INFO *pInfo )
{
memcpy( &m_NewSessionInfo, pInfo, sizeof( XSESSION_INFO ) );
}
void CSession::GetSessionInfo( XSESSION_INFO *pInfo )
{
Assert( pInfo );
memcpy( pInfo, &m_SessionInfo, sizeof( XSESSION_INFO ) );
}
void CSession::GetNewSessionInfo( XSESSION_INFO *pInfo )
{
Assert( pInfo );
memcpy( pInfo, &m_NewSessionInfo, sizeof( XSESSION_INFO ) );
}
void CSession::SetSessionNonce( int64 nonce )
{
m_SessionNonce = nonce;
}
uint64 CSession::GetSessionNonce()
{
return m_SessionNonce;
}
XNKID CSession::GetSessionId()
{
return m_SessionInfo.sessionID;
}
void CSession::SetSessionSlots(unsigned int nSlot, unsigned int nPlayers)
{
Assert( nSlot < SLOTS_LAST );
m_nPlayerSlots[nSlot] = nPlayers;
}
unsigned int CSession::GetSessionSlots( unsigned int nSlot )
{
Assert( nSlot < SLOTS_LAST );
return m_nPlayerSlots[nSlot];
}
void CSession::SetSessionFlags( uint flags )
{
m_nSessionFlags = flags;
}
uint CSession::GetSessionFlags()
{
return m_nSessionFlags;
}
int CSession::GetPlayerCount()
{
return m_nPlayerSlots[SLOTS_FILLEDPRIVATE] + m_nPlayerSlots[SLOTS_FILLEDPUBLIC];
}
void CSession::SetFlag( uint nFlag )
{
m_nSessionFlags |= nFlag;
}
void CSession::SetIsHost( bool bHost )
{
m_bIsHost = bHost;
}
void CSession::SetIsSystemLink( bool bSystemLink )
{
m_bIsSystemLink = bSystemLink;
}
void CSession::SetOwnerId( uint id )
{
m_nOwnerId = id;
}
bool CSession::IsHost()
{
return m_bIsHost;
}
bool CSession::IsFull()
{
return ( GetSessionSlots( SLOTS_TOTALPRIVATE ) == GetSessionSlots( SLOTS_FILLEDPRIVATE ) &&
GetSessionSlots( SLOTS_TOTALPUBLIC ) == GetSessionSlots( SLOTS_FILLEDPUBLIC ) );
}
bool CSession::IsArbitrated()
{
return m_bIsArbitrated;
}
bool CSession::IsSystemLink()
{
return m_bIsSystemLink;
}
void CSession::SetParent( CMatchmaking *pParent )
{
m_pParent = pParent;
}
double CSession::GetTime()
{
return Plat_FloatTime();
}

159
engine/Session.h Normal file
View File

@ -0,0 +1,159 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef SESSION_H
#define SESSION_H
#ifdef _WIN32
#pragma once
#endif
#include "engine/imatchmaking.h"
#include "ixboxsystem.h"
#include "const.h"
#include "net.h"
// valid session states
enum SESSION_STATE
{
SESSION_STATE_NONE,
SESSION_STATE_CREATING,
SESSION_STATE_MIGRATING,
SESSION_STATE_IDLE,
SESSION_STATE_WAITING_FOR_REGISTRATION,
SESSION_STATE_REGISTERING,
SESSION_STATE_REGISTERED,
SESSION_STATE_STARTING,
SESSION_STATE_IN_GAME,
SESSION_STATE_ENDING,
SESSION_STATE_FINISHED,
SESSION_STATE_DELETING
};
// slot types for the session
enum SLOTS
{
SLOTS_TOTALPUBLIC,
SLOTS_TOTALPRIVATE,
SLOTS_FILLEDPUBLIC,
SLOTS_FILLEDPRIVATE,
SLOTS_LAST
};
class CClientInfo
{
public:
uint64 m_id; // machine id
netadr_t m_adr; // IP and Port
XNADDR m_xnaddr; // XNADDR
XUID m_xuids[MAX_PLAYERS_PER_CLIENT]; // XUIDs
bool m_bInvited; // use private slots
bool m_bRegistered; // registered for arbitration
bool m_bMigrated; // successfully completed migration
bool m_bModified; // completed session modification
bool m_bReportedStats; // reported session stats to Live
bool m_bLoaded; // map load is complete
byte m_cVoiceState[MAX_PLAYERS_PER_CLIENT]; // has voice permission
char m_cPlayers; // number of players on this client
char m_iControllers[MAX_PLAYERS_PER_CLIENT]; // the controller (user index) for each player
char m_iTeam[MAX_PLAYERS_PER_CLIENT]; // each player's team
char m_szGamertags[MAX_PLAYERS_PER_CLIENT][MAX_PLAYER_NAME_LENGTH];
char mutable m_numPrivateSlotsUsed; // number of private slots used by this client if invited
CClientInfo()
{
Clear();
}
void Clear()
{
Q_memset( this, 0, sizeof( CClientInfo ) );
Q_memset( &m_iTeam, -1, sizeof( m_iTeam ) );
}
};
class CMatchmaking;
class CSession
{
public:
CSession();
~CSession();
void ResetSession();
void SetParent( CMatchmaking *pParent );
void RunFrame();
bool CreateSession();
void CancelCreateSession();
void DestroySession();
void RegisterForArbitration();
bool MigrateHost();
void JoinLocal( const CClientInfo *pClient );
void JoinRemote( const CClientInfo *pClient );
void RemoveLocal( const CClientInfo *pClient );
void RemoveRemote( const CClientInfo *pClient );
// Accessors
HANDLE GetSessionHandle();
void SetSessionInfo( XSESSION_INFO *pInfo );
void SetNewSessionInfo( XSESSION_INFO *pInfo );
void GetSessionInfo( XSESSION_INFO *pInfo );
void GetNewSessionInfo( XSESSION_INFO *pInfo );
void SetSessionNonce( int64 nonce );
uint64 GetSessionNonce();
XNKID GetSessionId();
void SetSessionSlots( unsigned int nSlot, unsigned int nPlayers );
uint GetSessionSlots( unsigned int nSlot );
void SetSessionFlags( uint flags );
uint GetSessionFlags();
int GetPlayerCount();
void SetFlag( uint dwFlag );
void SetIsHost( bool bHost );
void SetIsSystemLink( bool bSystemLink );
void SetOwnerId( uint id );
bool IsHost();
bool IsFull();
bool IsArbitrated();
bool IsSystemLink();
void SwitchToState( SESSION_STATE newState );
void SetContext( const uint nContextId, const uint nContextValue, const bool bAsync = true );
void SetProperty( const uint nPropertyId, const uint cbValue, const void *pvValue, const bool bAsync = true );
XSESSION_REGISTRATION_RESULTS *GetRegistrationResults() { return m_pRegistrationResults; }
private:
// Update functions
void UpdateCreating();
void UpdateMigrating();
void UpdateRegistering();
void SendNotification( SESSION_NOTIFY notification );
void UpdateSlots( const CClientInfo *pClient, bool bAddPlayers );
double GetTime();
HANDLE m_hSession; // Session handle
bool m_bIsHost; // Is hosting
bool m_bIsArbitrated; // Is Arbitrated
bool m_bUsingQoS; // Is the QoS listener enabled
bool m_bIsSystemLink; // Is this a system link session
XSESSION_INFO m_SessionInfo; // Session ID, key, and host address
XSESSION_INFO m_NewSessionInfo; // Session ID, key, and host address
uint64 m_SessionNonce; // Nonce of the session
uint m_nSessionFlags; // Session creation flags
uint m_nOwnerId; // Which player created the session
uint m_SessionState;
double m_fOperationStartTime;
CMatchmaking *m_pParent;
AsyncHandle_t m_hCreateHandle;
AsyncHandle_t m_hMigrateHandle;
AsyncHandle_t m_hRegisterHandle;
XSESSION_REGISTRATION_RESULTS *m_pRegistrationResults;
// public/private slots for the session
uint m_nPlayerSlots[SLOTS_LAST];
};
#endif // SESSION_H

View File

@ -0,0 +1,10 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $Workfile: $
// $Date: $
// $NoKeywords: $
//=============================================================================//
#include "audio_pch.h"

66
engine/audio/audio_pch.h Normal file
View File

@ -0,0 +1,66 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $Workfile: $
// $Date: $
// $NoKeywords: $
//===========================================================================//
#include "platform.h"
#if !defined( _X360 ) && defined( WIN32 )
#define WIN32_LEAN_AND_MEAN
#pragma warning(push, 1)
#pragma warning(disable: 4005)
#include <windows.h>
#include <mmsystem.h>
#pragma warning(pop)
#include <mmreg.h>
#endif
#include "basetypes.h"
#include "commonmacros.h"
#include "mathlib/mathlib.h"
#include "tier0/dbg.h"
#include "tier0/vprof.h"
#include "tier0/icommandline.h"
#include "tier1/strtools.h"
#include "tier2/riff.h"
#include "sound.h"
#include "Color.h"
#include "convar.h"
#include "soundservice.h"
#include "voice_sound_engine_interface.h"
#include "soundflags.h"
#include "filesystem.h"
#include "../filesystem_engine.h"
#include "snd_device.h"
#include "sound_private.h"
#include "snd_mix_buf.h"
#include "snd_env_fx.h"
#include "snd_channels.h"
#include "snd_audio_source.h"
#include "snd_convars.h"
#include "snd_dev_common.h"
#include "snd_dev_direct.h"
#include "snd_dev_wave.h"
#include "snd_dev_xaudio.h"
#include "snd_sfx.h"
#include "snd_audio_source.h"
#include "snd_wave_source.h"
#include "snd_wave_temp.h"
#include "snd_wave_data.h"
#include "snd_wave_mixer_private.h"
#include "snd_wave_mixer_adpcm.h"
#include "snd_io.h"
#include "snd_wave_mixer_xma.h"
#if defined( _X360 )
#include "xbox/xbox_win32stubs.h"
#include <xhv2.h>
#elif POSIX
#include "audio/private/posix_stubs.h"
#endif

View File

@ -0,0 +1,414 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#if defined( WIN32) && !defined( _X360 )
#include "winlite.h"
#endif
#include "tier0/platform.h"
#include "MPAFile.h"
#include "soundchars.h"
#include "tier1/utlrbtree.h"
#include "memdbgon.h"
extern IFileSystem *g_pFullFileSystem;
// exception class
CMPAException::CMPAException(ErrorIDs ErrorID, const char *szFile, const char *szFunction, bool bGetLastError ) :
m_ErrorID( ErrorID ), m_bGetLastError( bGetLastError )
{
m_szFile = szFile ? strdup(szFile) : NULL;
m_szFunction = szFunction ? strdup(szFunction) : NULL;
}
// copy constructor (necessary for exception throwing without pointers)
CMPAException::CMPAException(const CMPAException& Source)
{
m_ErrorID = Source.m_ErrorID;
m_bGetLastError = Source.m_bGetLastError;
m_szFile = Source.m_szFile ? strdup(Source.m_szFile) : NULL;
m_szFunction = Source.m_szFunction ? strdup(Source.m_szFunction) : NULL;
}
// destructor
CMPAException::~CMPAException()
{
if( m_szFile )
free( (void*)m_szFile );
if( m_szFunction )
free( (void*)m_szFunction );
}
// should be in resource file for multi language applications
const char *m_szErrors[] =
{
"Can't open the file.",
"Can't set file position.",
"Can't read from file.",
"Reached end of buffer.",
"No VBR Header found.",
"Incomplete VBR Header.",
"No subsequent frame found within tolerance range.",
"No frame found."
};
#define MAX_ERR_LENGTH 256
void CMPAException::ShowError()
{
char szErrorMsg[MAX_ERR_LENGTH] = {0};
char szHelp[MAX_ERR_LENGTH];
// this is not buffer-overflow-proof!
if( m_szFunction )
{
sprintf( szHelp, _T("%s: "), m_szFunction );
strcat( szErrorMsg, szHelp );
}
if( m_szFile )
{
sprintf( szHelp, _T("'%s'\n"), m_szFile );
strcat( szErrorMsg, szHelp );
}
strcat( szErrorMsg, m_szErrors[m_ErrorID] );
#if defined(WIN32) && !defined(_X360)
if( m_bGetLastError )
{
// get error message of last system error id
LPVOID pMsgBuf;
if ( FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &pMsgBuf,
0,
NULL ))
{
strcat( szErrorMsg, "\n" );
strcat( szErrorMsg, (const char *)pMsgBuf );
LocalFree( pMsgBuf );
}
}
#endif
// show error message
Warning( "%s\n", szErrorMsg );
}
// 1KB is inital buffersize, each time the buffer needs to be increased it is doubled
const uint32 CMPAFile::m_dwInitBufferSize = 1024;
CMPAFile::CMPAFile( const char * szFile, uint32 dwFileOffset, FileHandle_t hFile ) :
m_pBuffer(NULL), m_dwBufferSize(0), m_dwBegin( dwFileOffset ), m_dwEnd(0),
m_dwNumTimesRead(0), m_bVBRFile( false ), m_pVBRHeader(NULL), m_bMustReleaseFile( false ),
m_pMPAHeader(NULL), m_hFile( hFile ), m_szFile(NULL), m_dwFrameNo(1)
{
// open file, if not already done
if( m_hFile == FILESYSTEM_INVALID_HANDLE )
{
Open( szFile );
m_bMustReleaseFile = true;
}
// save filename
m_szFile = strdup( szFile );
// set end of MPEG data (assume file end)
if( m_dwEnd <= 0 )
{
// get file size
m_dwEnd = g_pFullFileSystem->Size( m_hFile );
}
// find first valid MPEG frame
m_pMPAHeader = new CMPAHeader( this );
// is VBR header available?
CVBRHeader::VBRHeaderType HeaderType = CVBRHeader::NoHeader;
uint32 dwOffset = m_pMPAHeader->m_dwSyncOffset;
if( CVBRHeader::IsVBRHeaderAvailable( this, HeaderType, dwOffset ) )
{
try
{
// read out VBR header
m_pVBRHeader = new CVBRHeader( this, HeaderType, dwOffset );
m_bVBRFile = true;
m_dwBytesPerSec = m_pVBRHeader->m_dwBytesPerSec;
if( m_pVBRHeader->m_dwBytes > 0 )
m_dwEnd = m_dwBegin + m_pVBRHeader->m_dwBytes;
}
catch(CMPAException& Exc)
{
Exc.ShowError();
}
}
if( !m_pVBRHeader )
{
// always skip empty (32kBit) frames
m_bVBRFile = m_pMPAHeader->SkipEmptyFrames();
m_dwBytesPerSec = m_pMPAHeader->GetBytesPerSecond();
}
}
bool CMPAFile::GetNextFrame()
{
uint32 dwOffset = m_pMPAHeader->m_dwSyncOffset + m_pMPAHeader->m_dwRealFrameSize;
try
{
CMPAHeader* pFrame = new CMPAHeader( this, dwOffset, false );
delete m_pMPAHeader;
m_pMPAHeader = pFrame;
if( m_dwFrameNo > 0 )
m_dwFrameNo++;
}
catch(...)
{
return false;
}
return true;
}
bool CMPAFile::GetPrevFrame()
{
uint32 dwOffset = m_pMPAHeader->m_dwSyncOffset-MPA_HEADER_SIZE;
try
{
// look backward from dwOffset on
CMPAHeader* pFrame = new CMPAHeader( this, dwOffset, false, true );
delete m_pMPAHeader;
m_pMPAHeader = pFrame;
if( m_dwFrameNo > 0 )
m_dwFrameNo --;
}
catch(...)
{
return false;
}
return true;
}
bool CMPAFile::GetFirstFrame()
{
uint32 dwOffset = 0;
try
{
CMPAHeader* pFrame = new CMPAHeader( this, dwOffset, false );
delete m_pMPAHeader;
m_pMPAHeader = pFrame;
m_dwFrameNo = 1;
}
catch(...)
{
return false;
}
return true;
}
bool CMPAFile::GetLastFrame()
{
uint32 dwOffset = m_dwEnd - m_dwBegin - MPA_HEADER_SIZE;
try
{
// look backward from dwOffset on
CMPAHeader* pFrame = new CMPAHeader( this, dwOffset, false, true );
delete m_pMPAHeader;
m_pMPAHeader = pFrame;
m_dwFrameNo = 0;
}
catch(...)
{
return false;
}
return true;
}
// destructor
CMPAFile::~CMPAFile(void)
{
delete m_pMPAHeader;
if( m_pVBRHeader )
delete m_pVBRHeader;
if( m_pBuffer )
delete[] m_pBuffer;
// close file
if( m_bMustReleaseFile )
g_pFullFileSystem->Close( m_hFile );
if( m_szFile )
free( (void*)m_szFile );
}
// open file
void CMPAFile::Open( const char * szFilename )
{
// open with CreateFile (no limitation of 128byte filename length, like in mmioOpen)
m_hFile = g_pFullFileSystem->Open( szFilename, "rb", "GAME" );//::CreateFile( szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
if( m_hFile == FILESYSTEM_INVALID_HANDLE )
{
// throw error
throw CMPAException( CMPAException::ErrOpenFile, szFilename, _T("CreateFile"), true );
}
}
// set file position
void CMPAFile::SetPosition( int offset )
{
/*
LARGE_INTEGER liOff;
liOff.QuadPart = lOffset;
liOff.LowPart = ::SetFilePointer(m_hFile, liOff.LowPart, &liOff.HighPart, dwMoveMethod );
if (liOff.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR )
{
// throw error
throw CMPAException( CMPAException::ErrSetPosition, m_szFile, _T("SetFilePointer"), true );
}
*/
g_pFullFileSystem->Seek( m_hFile, offset, FILESYSTEM_SEEK_HEAD );
}
// read from file, return number of bytes read
uint32 CMPAFile::Read( void *pData, uint32 dwSize, uint32 dwOffset )
{
uint32 dwBytesRead = 0;
// set position first
SetPosition( m_dwBegin+dwOffset );
//if( !::ReadFile( m_hFile, pData, dwSize, &dwBytesRead, NULL ) )
// throw CMPAException( CMPAException::ErrReadFile, m_szFile, _T("ReadFile"), true );
dwBytesRead = g_pFullFileSystem->Read( pData, dwSize, m_hFile );
return dwBytesRead;
}
// convert from big endian to native format (Intel=little endian) and return as uint32 (32bit)
uint32 CMPAFile::ExtractBytes( uint32& dwOffset, uint32 dwNumBytes, bool bMoveOffset )
{
Assert( dwNumBytes > 0 );
Assert( dwNumBytes <= 4 ); // max 4 byte
// enough bytes in buffer, otherwise read from file
if( !m_pBuffer || ( ((int)(m_dwBufferSize - dwOffset)) < (int)dwNumBytes) )
FillBuffer( dwOffset + dwNumBytes );
uint32 dwResult = 0;
// big endian extract (most significant byte first) (will work on little and big-endian computers)
uint32 dwNumByteShifts = dwNumBytes - 1;
for( uint32 n=dwOffset; n < dwOffset+dwNumBytes; n++ )
{
dwResult |= ((byte)m_pBuffer[n]) << (8*dwNumByteShifts); // the bit shift will do the correct byte order for you
dwNumByteShifts--;
}
if( bMoveOffset )
dwOffset += dwNumBytes;
return dwResult;
}
// throws exception if not possible
void CMPAFile::FillBuffer( uint32 dwOffsetToRead )
{
uint32 dwNewBufferSize;
// calc new buffer size
if( m_dwBufferSize == 0 )
dwNewBufferSize = m_dwInitBufferSize;
else
dwNewBufferSize = m_dwBufferSize*2;
// is it big enough?
if( dwNewBufferSize < dwOffsetToRead )
dwNewBufferSize = dwOffsetToRead;
// reserve new buffer
BYTE* pNewBuffer = new BYTE[dwNewBufferSize];
// take over data from old buffer
if( m_pBuffer )
{
memcpy( pNewBuffer, m_pBuffer, m_dwBufferSize );
// release old buffer
delete[] m_pBuffer;
}
m_pBuffer = (char*)pNewBuffer;
// read <dwNewBufferSize-m_dwBufferSize> bytes from offset <m_dwBufferSize>
uint32 dwBytesRead = Read( m_pBuffer+m_dwBufferSize, dwNewBufferSize-m_dwBufferSize, m_dwBufferSize );
// no more bytes in buffer than read out from file
m_dwBufferSize += dwBytesRead;
}
// Uses mp3 code from: http://www.codeproject.com/audio/MPEGAudioInfo.asp
struct MP3Duration_t
{
FileNameHandle_t h;
float duration;
static bool LessFunc( const MP3Duration_t& lhs, const MP3Duration_t& rhs )
{
return lhs.h < rhs.h;
}
};
CUtlRBTree< MP3Duration_t, int > g_MP3Durations( 0, 0, MP3Duration_t::LessFunc );
float GetMP3Duration_Helper( char const *filename )
{
float duration = 60.0f;
// See if it's in the RB tree already...
char fn[ 512 ];
Q_snprintf( fn, sizeof( fn ), "sound/%s", PSkipSoundChars( filename ) );
FileNameHandle_t h = g_pFullFileSystem->FindOrAddFileName( fn );
MP3Duration_t search;
search.h = h;
int idx = g_MP3Durations.Find( search );
if ( idx != g_MP3Durations.InvalidIndex() )
{
return g_MP3Durations[ idx ].duration;
}
try
{
CMPAFile MPAFile( fn, 0 );
if ( MPAFile.m_dwBytesPerSec != 0 )
{
duration = (float)(MPAFile.m_dwEnd - MPAFile.m_dwBegin) / (float)MPAFile.m_dwBytesPerSec;
}
}
catch ( ... )
{
}
search.duration = duration;
g_MP3Durations.Insert( search );
return duration;
}

View File

@ -0,0 +1,125 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Uses mp3 code from: http://www.codeproject.com/audio/MPEGAudioInfo.asp
//
// There don't appear to be any licensing restrictions for using this code:
//
/*
- Readme - MPEG Audio Info Tool V2.0 - 2004-11-01
Description:
This tool can display information about MPEG audio files. It supports
MPEG1, MPEG2, MPEG2.5 in all three layers. You can get all the fields
from the MPEG audio frame in each frame of the file. Additionally you
can check the whole file for inconsistencies.
This tool was written as an example on how to use the classes:
CMPAFile, CMPAHeader, CVBRHeader and CMPAException.
The article MPEG Audio Frame Header on Sourceproject
[http://www.codeproject.com/audio/MPEGAudioInfo.asp]
provides additional information about these classes and the frame header
in general.
This tool was written with MS Visual C++ 7.1. The MFC library is
statically linked.
*/
//=============================================================================
#ifndef MPAFILE_H
#define MPAFILE_H
#ifdef _WIN32
#pragma once
#endif
#pragma once
#include "VBRHeader.h"
#include "MPAHeader.h"
#include "filesystem.h"
// exception class
class CMPAException
{
public:
enum ErrorIDs
{
ErrOpenFile,
ErrSetPosition,
ErrReadFile,
EndOfBuffer,
NoVBRHeader,
IncompleteVBRHeader,
NoFrameInTolerance,
NoFrame
};
CMPAException( ErrorIDs ErrorID, const char *szFile, const char *szFunction = NULL, bool bGetLastError=false );
// copy constructor (necessary because of LPSTR members)
CMPAException(const CMPAException& Source);
~CMPAException(void);
ErrorIDs GetErrorID() { return m_ErrorID; }
void ShowError();
private:
ErrorIDs m_ErrorID;
bool m_bGetLastError;
const char *m_szFunction;
const char *m_szFile;
};
class CMPAFile
{
public:
CMPAFile( const char *szFile, uint32 dwFileOffset, FileHandle_t hFile = FILESYSTEM_INVALID_HANDLE );
~CMPAFile(void);
uint32 ExtractBytes( uint32 &dwOffset, uint32 dwNumBytes, bool bMoveOffset = true );
const char *GetFilename() const { return m_szFile; };
bool GetNextFrame();
bool GetPrevFrame();
bool GetFirstFrame();
bool GetLastFrame();
private:
static const uint32 m_dwInitBufferSize;
// methods for file access
void Open( const char *szFilename );
void SetPosition( int offset );
uint32 Read( void *pData, uint32 dwSize, uint32 dwOffset );
void FillBuffer( uint32 dwOffsetToRead );
static uint32 m_dwBufferSizes[MAXTIMESREAD];
// concerning file itself
FileHandle_t m_hFile;
const char *m_szFile;
bool m_bMustReleaseFile;
public:
uint32 m_dwBegin; // offset of first MPEG Audio frame
uint32 m_dwEnd; // offset of last MPEG Audio frame (estimated)
bool m_bVBRFile;
uint32 m_dwBytesPerSec;
CMPAHeader* m_pMPAHeader;
uint32 m_dwFrameNo;
CVBRHeader* m_pVBRHeader; // XING or VBRI
// concerning read-buffer
uint32 m_dwNumTimesRead;
char *m_pBuffer;
uint32 m_dwBufferSize;
};
#endif // MPAFILE_H

View File

@ -0,0 +1,332 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#if defined( WIN32) && !defined( _X360 )
#include "winlite.h"
#endif
#include "tier0/platform.h"
#include "MPAFile.h"
// static variables
const char *CMPAHeader::m_szLayers[] = { "Layer I", "Layer II", "Layer III" };
const char *CMPAHeader::m_szMPEGVersions[] = {"MPEG 2.5", "", "MPEG 2", "MPEG 1" };
const char *CMPAHeader::m_szChannelModes[] = { "Stereo", "Joint Stereo", "Dual Channel", "Single Channel" };
const char *CMPAHeader::m_szEmphasis[] = { "None", "50/15ms", "", "CCIT J.17" };
// tolerance range, look at expected offset +/- m_dwTolerance for subsequent frames
const uint32 CMPAHeader::m_dwTolerance = 3; // 3 bytes
// max. range where to look for frame sync
const uint32 CMPAHeader::m_dwMaxRange = ( 256 * 1024 );
// sampling rates in hertz: 1. index = MPEG Version ID, 2. index = sampling rate index
const uint32 CMPAHeader::m_dwSamplingRates[4][3] =
{
{11025, 12000, 8000, }, // MPEG 2.5
{0, 0, 0, }, // reserved
{22050, 24000, 16000, }, // MPEG 2
{44100, 48000, 32000 } // MPEG 1
};
// padding sizes in bytes for different layers: 1. index = layer
const uint32 CMPAHeader::m_dwPaddingSizes[3] =
{
4, // Layer1
1, // Layer2
1 // Layer3
};
// bitrates: 1. index = LSF, 2. index = Layer, 3. index = bitrate index
const uint32 CMPAHeader::m_dwBitrates[2][3][15] =
{
{ // MPEG 1
{0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,}, // Layer1
{0,32,48,56, 64, 80, 96,112,128,160,192,224,256,320,384,}, // Layer2
{0,32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320,} // Layer3
},
{ // MPEG 2, 2.5
{0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,}, // Layer1
{0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,}, // Layer2
{0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,} // Layer3
}
};
// Samples per Frame: 1. index = LSF, 2. index = Layer
const uint32 CMPAHeader::m_dwSamplesPerFrames[2][3] =
{
{ // MPEG 1
384, // Layer1
1152, // Layer2
1152 // Layer3
},
{ // MPEG 2, 2.5
384, // Layer1
1152, // Layer2
576 // Layer3
}
};
// Samples per Frame / 8
const uint32 CMPAHeader::m_dwCoefficients[2][3] =
{
{ // MPEG 1
48, // Layer1
144, // Layer2
144 // Layer3
},
{ // MPEG 2, 2.5
48, // Layer1
144, // Layer2
72 // Layer3
}
};
// needed later for CRC check
// sideinformation size: 1.index = lsf, 2. index = layer, 3. index = mono
const uint32 CMPAHeader::m_dwSideinfoSizes[2][3][2] =
{
{ // MPEG 1 (not mono, mono
{0,0}, // Layer1
{0,0}, // Layer2
{9,17} // Layer3
},
{ // MPEG 2, 2.5
{0,0}, // Layer1
{0,0}, // Layer2
{17,32} // Layer3
}
};
// constructor (throws exception if no frame found)
CMPAHeader::CMPAHeader( CMPAFile* pMPAFile, uint32 dwExpectedOffset, bool bSubsequentFrame, bool bReverse ) :
m_pMPAFile( pMPAFile ), m_dwSyncOffset( dwExpectedOffset ), m_dwRealFrameSize( 0 )
{
// first check at expected offset (extended for not subsequent frames)
HeaderError error = IsSync( m_dwSyncOffset, !bSubsequentFrame );
int nStep=1;
int nSyncOffset;
while( error != noError )
{
// either look in tolerance range
if( bSubsequentFrame )
{
if( nStep > m_dwTolerance )
{
// out of tolerance range
throw CMPAException( CMPAException::NoFrameInTolerance, pMPAFile->GetFilename() ? pMPAFile->GetFilename() : "??" );
}
// look around dwExpectedOffset with increasing steps (+1,-1,+2,-2,...)
if( m_dwSyncOffset <= dwExpectedOffset )
{
nSyncOffset = dwExpectedOffset + nStep;
}
else
{
nSyncOffset = dwExpectedOffset - nStep++;
}
}
// just go forward/backward to find sync
else
{
nSyncOffset = ((int)m_dwSyncOffset) + (bReverse?-1:+1);
}
// is new offset within valid range?
if( nSyncOffset < 0 || nSyncOffset > (int)((pMPAFile->m_dwEnd - pMPAFile->m_dwBegin) - MPA_HEADER_SIZE) || abs( (long)(nSyncOffset-dwExpectedOffset) ) > m_dwMaxRange )
{
// out of tolerance range
throw CMPAException( CMPAException::NoFrame, pMPAFile->GetFilename() ? pMPAFile->GetFilename() : "??" );
}
m_dwSyncOffset = nSyncOffset;
// found sync?
error = IsSync( m_dwSyncOffset, !bSubsequentFrame );
}
}
// destructor
CMPAHeader::~CMPAHeader()
{
}
// skips first 32kbit/s or lower bitrate frames to estimate bitrate (returns true if bitrate is variable)
bool CMPAHeader::SkipEmptyFrames()
{
if( m_dwBitrate > 32 )
return false;
uint32 dwHeader;
try
{
while( m_dwBitrate <= 32 )
{
m_dwSyncOffset += m_dwComputedFrameSize + MPA_HEADER_SIZE;
dwHeader = m_pMPAFile->ExtractBytes( m_dwSyncOffset, MPA_HEADER_SIZE, false );
if( IsSync( dwHeader, false ) != noError )
return false;
}
}
catch(CMPAException& /*Exc*/) // just catch the exception and return false
{
return false;
}
return true;
}
// in dwHeader stands 32bit header in big-endian format: frame sync at the end!
// because shifts do only work for integral types!!!
CMPAHeader::HeaderError CMPAHeader::DecodeHeader( uint32 dwHeader, bool bSimpleDecode )
{
// Check SYNC bits (last eleven bits set)
if( (dwHeader >> 24 != 0xff) || ((((dwHeader >> 16))&0xe0) != 0xe0) )
return noSync;
// get MPEG version
m_Version = (MPAVersion)((dwHeader >> 19) & 0x03); // mask only the rightmost 2 bits
if( m_Version == MPEGReserved )
return headerCorrupt;
if( m_Version == MPEG1 )
m_bLSF = false;
else
m_bLSF = true;
// get layer (0 = layer1, 2 = layer2, ...)
m_Layer = (MPALayer)(3 - ((dwHeader >> 17) & 0x03));
if( m_Layer == LayerReserved )
return headerCorrupt;
// protection bit (inverted)
m_bCRC = !((dwHeader >> 16) & 0x01);
// bitrate
BYTE bIndex = (BYTE)((dwHeader >> 12) & 0x0F);
if( bIndex == 0x0F ) // all bits set is reserved
return headerCorrupt;
m_dwBitrate = m_dwBitrates[m_bLSF][m_Layer][bIndex] * 1000; // convert from kbit to bit
if( m_dwBitrate == 0 ) // means free bitrate (is unsupported yet)
return freeBitrate;
// sampling rate
bIndex = (BYTE)((dwHeader >> 10) & 0x03);
if( bIndex == 0x03 ) // all bits set is reserved
return headerCorrupt;
m_dwSamplesPerSec = m_dwSamplingRates[m_Version][bIndex];
// padding bit
m_dwPaddingSize = m_dwPaddingSizes[m_Layer] * ((dwHeader >> 9) & 0x01);
// calculate frame size
m_dwComputedFrameSize = (m_dwCoefficients[m_bLSF][m_Layer] * m_dwBitrate / m_dwSamplesPerSec) + m_dwPaddingSize;
m_dwSamplesPerFrame = m_dwSamplesPerFrames[m_bLSF][m_Layer];
if( !bSimpleDecode )
{
// private bit
m_bPrivate = (dwHeader >> 8) & 0x01;
// channel mode
m_ChannelMode = (ChannelMode)((dwHeader >> 6) & 0x03);
// mode extension (currently not used)
m_ModeExt = (BYTE)((dwHeader >> 4) & 0x03);
// copyright bit
m_bCopyright = (dwHeader >> 3) & 0x01;
// original bit
m_bCopyright = (dwHeader >> 2) & 0x01;
// emphasis
m_Emphasis = (Emphasis)(dwHeader & 0x03);
if( m_Emphasis == EmphReserved )
return headerCorrupt;
}
return noError;
}
CMPAHeader::HeaderError CMPAHeader::IsSync( uint32 dwOffset, bool bExtended )
{
HeaderError error = noSync;
uint32 dwHeader = m_pMPAFile->ExtractBytes( dwOffset, MPA_HEADER_SIZE, false );
// sync bytes found?
if( (dwHeader & 0xFFE00000) == 0xFFE00000 )
{
error = DecodeHeader( dwHeader );
if( error == noError )
{
// enough buffer to do extended check?
if( bExtended )
{
// recursive call (offset for next frame header)
dwOffset = m_dwSyncOffset+m_dwComputedFrameSize;
try
{
CMPAHeader m_SubsequentFrame( m_pMPAFile, dwOffset, true );
m_dwRealFrameSize = m_SubsequentFrame.m_dwSyncOffset - m_dwSyncOffset;
}
catch( CMPAException& Exc )
{
// could not find any subsequent frame, assume it is the last frame
if( Exc.GetErrorID() == CMPAException::NoFrame )
{
if( dwOffset + m_pMPAFile->m_dwBegin > m_pMPAFile->m_dwEnd )
m_dwRealFrameSize = m_pMPAFile->m_dwEnd - m_pMPAFile->m_dwBegin - m_dwSyncOffset;
else
m_dwRealFrameSize = m_dwComputedFrameSize;
error = noError;
}
else
error = noSync;
}
}
}
}
return error;
}
// CRC-16 lookup table
const uint16 CMPAHeader::wCRC16Table[256] =
{
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
};

View File

@ -0,0 +1,116 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#ifndef MPAHEADER_H
#define MPAHEADER_H
#ifdef _WIN32
#pragma once
#endif
#pragma once
#define MPA_HEADER_SIZE 4 // MPEG-Audio Header Size 32bit
#define MAXTIMESREAD 5
class CMPAFile;
class CMPAHeader
{
public:
CMPAHeader( CMPAFile* pMPAFile, uint32 dwExpectedOffset = 0, bool bSubsequentFrame = false, bool bReverse = false );
~CMPAHeader();
bool SkipEmptyFrames();
// bitrate is in bit per second, to calculate in bytes => (/ 8)
uint32 GetBytesPerSecond() const { return m_dwBitrate / 8; };
// calc number of seconds from number of frames
uint32 GetLengthSecond(uint32 dwNumFrames) const { return dwNumFrames * m_dwSamplesPerFrame / m_dwSamplesPerSec; };
uint32 GetBytesPerSecond( uint32 dwNumFrames, uint32 dwNumBytes ) const { return dwNumBytes / GetLengthSecond( dwNumFrames ); };
bool IsMono() const { return (m_ChannelMode == SingleChannel)?true:false; };
// true if MPEG2/2.5 otherwise false
bool IsLSF() const { return m_bLSF; };
private:
static const uint32 m_dwMaxRange;
static const uint32 m_dwTolerance;
static const uint32 m_dwSamplingRates[4][3];
static const uint32 m_dwPaddingSizes[3];
static const uint32 m_dwBitrates[2][3][15];
static const uint32 m_dwSamplesPerFrames[2][3];
static const uint32 m_dwCoefficients[2][3];
// necessary for CRC check (not yet implemented)
static const uint32 m_dwSideinfoSizes[2][3][2];
static const uint16 wCRC16Table[256];
bool m_bLSF; // true means lower sampling frequencies (=MPEG2/MPEG2.5)
CMPAFile* m_pMPAFile;
public:
static const char * m_szLayers[];
static const char * m_szMPEGVersions[];
static const char * m_szChannelModes[];
static const char * m_szEmphasis[];
enum MPAVersion
{
MPEG25 = 0,
MPEGReserved,
MPEG2,
MPEG1
}m_Version;
enum MPALayer
{
Layer1 = 0,
Layer2,
Layer3,
LayerReserved
}m_Layer;
enum Emphasis
{
EmphNone = 0,
Emph5015,
EmphReserved,
EmphCCITJ17
}m_Emphasis;
enum ChannelMode
{
Stereo,
JointStereo,
DualChannel,
SingleChannel
}m_ChannelMode;
uint32 m_dwSamplesPerSec;
uint32 m_dwSamplesPerFrame;
uint32 m_dwBitrate; // in bit per second (1 kb = 1000 bit, not 1024)
uint32 m_dwSyncOffset;
uint32 m_dwComputedFrameSize, m_dwRealFrameSize;
uint32 m_dwPaddingSize;
// flags
bool m_bCopyright, m_bPrivate, m_bOriginal;
bool m_bCRC;
uint8 m_ModeExt;
private:
enum HeaderError
{
noError,
noSync,
freeBitrate,
headerCorrupt
};
HeaderError DecodeHeader( uint32 dwHeader, bool bSimpleDecode = false );
inline HeaderError IsSync( uint32 dwOffset, bool bExtended );
};
#endif // MPAHEADER_H

View File

@ -0,0 +1,304 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#include "audio_pch.h"
#include "tier0/platform.h"
#include "MPAFile.h" // also includes vbrheader.h
#include "tier0/dbg.h"
#ifndef MAKEFOURCC
#define MAKEFOURCC(ch0, ch1, ch2, ch3) \
((uint32)(BYTE)(ch0) | ((uint32)(BYTE)(ch1) << 8) | \
((uint32)(BYTE)(ch2) << 16) | ((uint32)(BYTE)(ch3) << 24 ))
#endif //defined(MAKEFOURCC)
// XING Header offset: 1. index = lsf, 2. index = mono
uint32 CVBRHeader::m_dwXINGOffsets[2][2] =
{
// MPEG 1 (not mono, mono)
{ 32 + MPA_HEADER_SIZE, 17 + MPA_HEADER_SIZE },
// MPEG 2/2.5
{ 17 + MPA_HEADER_SIZE, 9 + MPA_HEADER_SIZE }
};
// first test with this static method, if it does exist
bool CVBRHeader::IsVBRHeaderAvailable( CMPAFile* pMPAFile, VBRHeaderType& HeaderType, uint32& dwOffset )
{
Assert(pMPAFile);
// where does VBR header begin (XING)
uint32 dwNewOffset = dwOffset + m_dwXINGOffsets[pMPAFile->m_pMPAHeader->IsLSF()][pMPAFile->m_pMPAHeader->IsMono()];
// check for XING header first
if( CheckXING( pMPAFile, dwNewOffset ) )
{
HeaderType = XINGHeader;
// seek offset back to header begin
dwOffset = dwNewOffset - 4;
return true;
}
// VBRI header always at fixed offset
dwNewOffset = dwOffset + 32 + MPA_HEADER_SIZE;
if( CheckVBRI( pMPAFile, dwNewOffset ) )
{
HeaderType = VBRIHeader;
// seek offset back to header begin
dwOffset = dwNewOffset - 4;
return true;
}
HeaderType = NoHeader;
return false;
}
CVBRHeader::CVBRHeader( CMPAFile* pMPAFile, VBRHeaderType HeaderType, uint32 dwOffset ) :
m_pMPAFile( pMPAFile ), m_pnToc(NULL), m_HeaderType( HeaderType ), m_dwOffset(dwOffset), m_dwFrames(0), m_dwBytes(0)
{
switch( m_HeaderType )
{
case NoHeader:
// no Header found
throw CMPAException( CMPAException::NoVBRHeader, pMPAFile->GetFilename(), NULL, false );
break;
case XINGHeader:
if( !ExtractXINGHeader( m_dwOffset ) )
throw CMPAException( CMPAException::NoVBRHeader, pMPAFile->GetFilename(), NULL, false );
break;
case VBRIHeader:
if( !ExtractVBRIHeader( m_dwOffset ) )
throw CMPAException( CMPAException::NoVBRHeader, pMPAFile->GetFilename(), NULL, false );
break;
}
// calc bitrate
if( m_dwBytes > 0 && m_dwFrames > 0 )
{
// calc number of seconds
m_dwBytesPerSec = m_pMPAFile->m_pMPAHeader->GetBytesPerSecond( m_dwFrames, m_dwBytes );
}
else // incomplete header found
{
throw CMPAException( CMPAException::IncompleteVBRHeader, pMPAFile->GetFilename(), NULL, false );
}
}
bool CVBRHeader::CheckID( CMPAFile* pMPAFile, char ch0, char ch1, char ch2, char ch3, uint32& dwOffset )
{
return ( pMPAFile->ExtractBytes( dwOffset, 4 ) == MAKEFOURCC( ch3, ch2, ch1, ch0 ) );
}
bool CVBRHeader::CheckXING( CMPAFile* pMPAFile, uint32& dwOffset )
{
// XING ID found?
if( !CheckID( pMPAFile, 'X', 'i', 'n', 'g', dwOffset) && !CheckID( pMPAFile, 'I', 'n', 'f', 'o', dwOffset) )
return false;
return true;
}
bool CVBRHeader::CheckVBRI( CMPAFile* pMPAFile, uint32& dwOffset )
{
// VBRI ID found?
if( !CheckID( pMPAFile, 'V', 'B', 'R', 'I', dwOffset ) )
return false;
return true;
}
// currently not used
bool CVBRHeader::ExtractLAMETag( uint32 dwOffset )
{
// LAME ID found?
if( !CheckID( m_pMPAFile, 'L', 'A', 'M', 'E', dwOffset ) && !CheckID( m_pMPAFile, 'G', 'O', 'G', 'O', dwOffset ) )
return false;
return true;
}
bool CVBRHeader::ExtractXINGHeader( uint32 dwOffset )
{
/* XING VBR-Header
size description
4 'Xing' or 'Info'
4 flags (indicates which fields are used)
4 frames (optional)
4 bytes (optional)
100 toc (optional)
4 a VBR quality indicator: 0=best 100=worst (optional)
*/
if( !CheckXING( m_pMPAFile, dwOffset ) )
return false;
uint32 dwFlags;
// get flags (mandatory in XING header)
dwFlags = m_pMPAFile->ExtractBytes( dwOffset, 4 );
// extract total number of frames in file
if(dwFlags & FRAMES_FLAG)
m_dwFrames = m_pMPAFile->ExtractBytes(dwOffset,4);
// extract total number of bytes in file
if(dwFlags & BYTES_FLAG)
m_dwBytes = m_pMPAFile->ExtractBytes(dwOffset,4);
// extract TOC (for more accurate seeking)
if (dwFlags & TOC_FLAG)
{
m_dwTableSize = 100;
m_pnToc = new int[m_dwTableSize];
if( m_pnToc )
{
for(uint32 i=0;i<m_dwTableSize;i++)
m_pnToc[i] = m_pMPAFile->ExtractBytes( dwOffset, 1 );
}
}
m_dwQuality = (uint32)-1;
if(dwFlags & VBR_SCALE_FLAG )
m_dwQuality = m_pMPAFile->ExtractBytes(dwOffset, 4);
return true;
}
bool CVBRHeader::ExtractVBRIHeader( uint32 dwOffset )
{
/* FhG VBRI Header
size description
4 'VBRI' (ID)
2 version
2 delay
2 quality
4 # bytes
4 # frames
2 table size (for TOC)
2 table scale (for TOC)
2 size of table entry (max. size = 4 byte (must be stored in an integer))
2 frames per table entry
?? dynamic table consisting out of frames with size 1-4
whole length in table size! (for TOC)
*/
if( !CheckVBRI( m_pMPAFile, dwOffset ) )
return false;
// extract all fields from header (all mandatory)
m_dwVersion = m_pMPAFile->ExtractBytes(dwOffset, 2 );
m_fDelay = (float)m_pMPAFile->ExtractBytes(dwOffset, 2 );
m_dwQuality = m_pMPAFile->ExtractBytes(dwOffset, 2 );
m_dwBytes = m_pMPAFile->ExtractBytes(dwOffset, 4 );
m_dwFrames = m_pMPAFile->ExtractBytes(dwOffset, 4 );
m_dwTableSize = m_pMPAFile->ExtractBytes(dwOffset, 2 ) + 1; //!!!
m_dwTableScale = m_pMPAFile->ExtractBytes(dwOffset, 2 );
m_dwBytesPerEntry = m_pMPAFile->ExtractBytes(dwOffset, 2 );
m_dwFramesPerEntry = m_pMPAFile->ExtractBytes(dwOffset, 2 );
// extract TOC (for more accurate seeking)
m_pnToc = new int[m_dwTableSize];
if( m_pnToc )
{
for ( unsigned int i = 0 ; i < m_dwTableSize ; i++)
{
m_pnToc[i] = m_pMPAFile->ExtractBytes(dwOffset, m_dwBytesPerEntry );
}
}
return true;
}
CVBRHeader::~CVBRHeader(void)
{
if( m_pnToc )
delete[] m_pnToc;
}
// get byte position for percentage value (fPercent) of file
bool CVBRHeader::SeekPoint(float fPercent, uint32& dwSeekPoint)
{
if( !m_pnToc || m_dwBytes == 0 )
return false;
if( fPercent < 0.0f )
fPercent = 0.0f;
if( fPercent > 100.0f )
fPercent = 100.0f;
switch( m_HeaderType )
{
case XINGHeader:
dwSeekPoint = SeekPointXING( fPercent );
break;
case VBRIHeader:
dwSeekPoint = SeekPointVBRI( fPercent );
break;
}
return true;
}
uint32 CVBRHeader::SeekPointXING(float fPercent) const
{
// interpolate in TOC to get file seek point in bytes
int a;
float fa, fb, fx;
a = (int)fPercent;
if( a > 99 ) a = 99;
fa = (float)m_pnToc[a];
if( a < 99 )
{
fb = (float)m_pnToc[a+1];
}
else
{
fb = 256.0f;
}
fx = fa + (fb-fa)*(fPercent-a);
uint32 dwSeekpoint = (int)((1.0f/256.0f)*fx*m_dwBytes);
return dwSeekpoint;
}
uint32 CVBRHeader::SeekPointVBRI(float fPercent) const
{
return SeekPointByTimeVBRI( (fPercent/100.0f) * m_pMPAFile->m_pMPAHeader->GetLengthSecond( m_dwFrames ) * 1000.0f );
}
uint32 CVBRHeader::SeekPointByTimeVBRI(float fEntryTimeMS) const
{
unsigned int i=0, fraction = 0;
uint32 dwSeekPoint = 0;
float fLengthMS;
float fLengthMSPerTOCEntry;
float fAccumulatedTimeMS = 0.0f ;
fLengthMS = (float)m_pMPAFile->m_pMPAHeader->GetLengthSecond( m_dwFrames ) * 1000.0f ;
fLengthMSPerTOCEntry = fLengthMS / (float)m_dwTableSize;
if ( fEntryTimeMS > fLengthMS )
fEntryTimeMS = fLengthMS;
while ( fAccumulatedTimeMS <= fEntryTimeMS )
{
dwSeekPoint += m_pnToc[i++];
fAccumulatedTimeMS += fLengthMSPerTOCEntry;
}
// Searched too far; correct result
fraction = ( (int)(((( fAccumulatedTimeMS - fEntryTimeMS ) / fLengthMSPerTOCEntry )
+ (1.0f/(2.0f*(float)m_dwFramesPerEntry))) * (float)m_dwFramesPerEntry));
dwSeekPoint -= (uint32)((float)m_pnToc[i-1] * (float)(fraction)
/ (float)m_dwFramesPerEntry);
return dwSeekPoint;
}

View File

@ -0,0 +1,72 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#ifndef VBRHEADER_H
#define VBRHEADER_H
#ifdef _WIN32
#pragma once
#endif
// for XING VBR Header flags
#define FRAMES_FLAG 0x0001
#define BYTES_FLAG 0x0002
#define TOC_FLAG 0x0004
#define VBR_SCALE_FLAG 0x0008
class CMPAFile;
class CVBRHeader
{
public:
enum VBRHeaderType
{
NoHeader,
XINGHeader,
VBRIHeader
};
CVBRHeader( CMPAFile* pMPAFile, VBRHeaderType HeaderType, uint32 dwOffset );
~CVBRHeader(void);
static bool IsVBRHeaderAvailable( CMPAFile* pMPAFile, VBRHeaderType& HeaderType, uint32& dwOffset );
bool SeekPoint(float fPercent, uint32& dwSeekPoint);
uint32 m_dwBytesPerSec;
uint32 m_dwBytes; // total number of bytes
uint32 m_dwFrames; // total number of frames
private:
static uint32 m_dwXINGOffsets[2][2];
static bool CheckID( CMPAFile* pMPAFile, char ch0, char ch1, char ch2, char ch3, uint32& dwOffset );
static bool CheckXING( CMPAFile* pMPAFile, uint32& dwOffset );
static bool CheckVBRI( CMPAFile* pMPAFile, uint32& dwOffset );
bool ExtractLAMETag( uint32 dwOffset );
bool ExtractXINGHeader( uint32 dwOffset );
bool ExtractVBRIHeader( uint32 dwOffset );
uint32 SeekPointXING(float fPercent)const ;
uint32 SeekPointVBRI(float fPercent) const;
uint32 SeekPointByTimeVBRI(float fEntryTimeMS) const;
CMPAFile* m_pMPAFile;
public:
VBRHeaderType m_HeaderType;
uint32 m_dwOffset;
uint32 m_dwQuality; // quality (0..100)
int* m_pnToc; // TOC points for seeking (must be freed)
uint32 m_dwTableSize; // size of table (number of entries)
// only VBRI
float m_fDelay;
uint32 m_dwTableScale; // for seeking
uint32 m_dwBytesPerEntry;
uint32 m_dwFramesPerEntry;
uint32 m_dwVersion;
};
#endif // VBRHEADER_H

View File

@ -0,0 +1,271 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Circular Buffer
//
//=============================================================================//
#include "tier0/dbg.h"
#include "circularbuffer.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
CCircularBuffer::CCircularBuffer()
{
SetSize( 0 );
}
CCircularBuffer::CCircularBuffer(int size)
{
SetSize(size);
}
//------------------ Copyright (c) 1999 Valve, LLC. ----------------------------
//Purpose : Sets the maximum size for a circular buffer. This does not do any
// memory allocation, it simply informs the buffer of its size.
//Author : DSpeyrer
//------------------------------------------------------------------------------
void CCircularBuffer::SetSize(int size)
{
Assert( this );
m_nSize = size;
m_nRead = 0;
m_nWrite = 0;
m_nCount = 0;
}
//------------------ Copyright (c) 1999 Valve, LLC. ----------------------------
//Purpose : Empties a circular buffer.
//Author : DSpeyrer
//------------------------------------------------------------------------------
void CCircularBuffer::Flush()
{
AssertValid();
m_nRead = 0;
m_nWrite = 0;
m_nCount = 0;
}
//------------------ Copyright (c) 1999 Valve, LLC. ----------------------------
//Purpose : Returns the available space in a circular buffer.
//Author : DSpeyrer
//------------------------------------------------------------------------------
int CCircularBuffer::GetWriteAvailable()
{
AssertValid();
return(m_nSize - m_nCount);
}
//------------------ Copyright (c) 1999 Valve, LLC. ----------------------------
//Purpose : Returns the size of a circular buffer.
//Author : DSpeyrer
//------------------------------------------------------------------------------
int CCircularBuffer::GetSize()
{
AssertValid();
return(m_nSize);
}
//------------------ Copyright (c) 1999 Valve, LLC. ----------------------------
//Purpose : Returns the number of bytes in a circular buffer.
//Author : DSpeyrer
//------------------------------------------------------------------------------
int CCircularBuffer::GetReadAvailable()
{
AssertValid();
return(m_nCount);
}
//------------------ Copyright (c) 1999 Valve, LLC. ----------------------------
//Purpose : Reads a specified number of bytes from a circular buffer without
// consuming them. They will still be available for future calls to
// Read or Peek.
//Input : pchDest - destination buffer.
// m_nCount - number of bytes to place in destination buffer.
//Output : Returns the number of bytes placed in the destination buffer.
//Author : DSpeyrer
//------------------------------------------------------------------------------
int CCircularBuffer::Peek(char *pchDest, int nCount)
{
// If no data available, just return.
if(m_nCount == 0)
{
return(0);
}
//
// Requested amount should not exceed the available amount.
//
nCount = MIN(m_nCount, nCount);
//
// Copy as many of the requested bytes as possible.
// If buffer wrap occurs split the data into two chunks.
//
if (m_nRead + nCount > m_nSize)
{
int nCount1 = m_nSize - m_nRead;
memcpy(pchDest, &m_chData[m_nRead], nCount1);
pchDest += nCount1;
int nCount2 = nCount - nCount1;
memcpy(pchDest, m_chData, nCount2);
}
// Otherwise copy it in one go.
else
{
memcpy(pchDest, &m_chData[m_nRead], nCount);
}
AssertValid();
return nCount;
}
//------------------ Copyright (c) 1999 Valve, LLC. ----------------------------
//Purpose : Advances the read index, consuming a specified number of bytes from
// the circular buffer.
//Input : m_nCount - number of bytes to consume.
//Output : Returns the actual number of bytes consumed.
//Author : DSpeyrer
//------------------------------------------------------------------------------
int CCircularBuffer::Advance(int nCount)
{
// If no data available, just return.
if (m_nCount == 0)
{
return(0);
}
//
// Requested amount should not exceed the available amount.
//
nCount = MIN(m_nCount, nCount);
// Advance the read pointer, checking for buffer
//wrap.
//
m_nRead = (m_nRead + nCount) % m_nSize;
m_nCount -= nCount;
//
// If we have emptied the buffer, reset the read and write indices
// to minimize buffer wrap.
//
if (m_nCount == 0)
{
m_nRead = 0;
m_nWrite = 0;
}
AssertValid();
return nCount;
}
//------------------ Copyright (c) 1999 Valve, LLC. ----------------------------
//Purpose : Reads a specified number of bytes from a circular buffer. The bytes
// will be consumed by the read process.
//Input : pchDest - destination buffer.
// m_nCount - number of bytes to place in destination buffer.
//Output : Returns the number of bytes placed in the destination buffer.
//Author : DSpeyrer
//------------------------------------------------------------------------------
int CCircularBuffer::Read(void *pchDestIn, int nCount)
{
int nPeeked;
int nRead;
char *pchDest = (char*)pchDestIn;
nPeeked = Peek(pchDest, nCount);
if (nPeeked != 0)
{
nRead = Advance(nPeeked);
assert( nRead == nPeeked);
}
else
{
nRead = 0;
}
AssertValid();
return(nRead);
}
//------------------ Copyright (c) 1999 Valve, LLC. ----------------------------
//Purpose : Writes a specified number of bytes to the buffer.
//Input : pm_chData - buffer containing bytes to bw written.
// m_nCount - the number of bytes to write.
//Output : Returns the number of bytes written. If there wa insufficient space
// to write all requested bytes, the value returned will be less than
// the requested amount.
//Author : DSpeyrer
//------------------------------------------------------------------------------
int CCircularBuffer::Write(void *pData, int nBytesRequested)
{
// Write all the data.
int nBytesToWrite = nBytesRequested;
char *pDataToWrite = (char*)pData;
while(nBytesToWrite)
{
int from = m_nWrite;
int to = m_nWrite + nBytesToWrite;
if(to >= m_nSize)
{
to = m_nSize;
}
memcpy(&m_chData[from], pDataToWrite, to - from);
pDataToWrite += to - from;
m_nWrite = to % m_nSize;
nBytesToWrite -= to - from;
}
// Did it cross the read pointer? Then slide the read pointer up.
// This way, we will discard the old data.
if(nBytesRequested > (m_nSize - m_nCount))
{
m_nCount = m_nSize;
m_nRead = m_nWrite;
}
else
{
m_nCount += nBytesRequested;
}
AssertValid();
return nBytesRequested;
}
CCircularBuffer *AllocateCircularBuffer( int nSize )
{
char *pBuff = (char *)malloc( sizeof( CCircularBuffer ) + nSize - 1 );
CCircularBuffer *pCCircularBuffer = (CCircularBuffer *)pBuff;
pCCircularBuffer->SetSize( nSize );
return pCCircularBuffer;
}
void FreeCircularBuffer( CCircularBuffer *pCircularBuffer )
{
free( (char*)pCircularBuffer );
}

View File

@ -0,0 +1,99 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Defines an interface for circular buffers. Data can be written to
// and read from these buffers as though from a file. When it is
// write-overflowed (you write more data in than the buffer can hold),
// the read pointer is advanced to allow the new data to be written.
// This means old data will be discarded if you write too much data
// into the buffer.
//
// MikeD: Moved all the functions into a class.
// Changed it so when the buffer overflows, the old data
// is discarded rather than the new.
//
//=====================================================================================//
#ifndef CIRCULARBUFFER_H
#define CIRCULARBUFFER_H
#pragma once
class CCircularBuffer
{
public:
CCircularBuffer();
CCircularBuffer( int size );
void SetSize( int nSize );
protected:
inline void AssertValid()
{
#ifdef _DEBUG
Assert( this );
Assert( m_nSize > 0 );
Assert( m_nCount >= 0 );
Assert( m_nCount <= m_nSize );
Assert( m_nWrite < m_nSize );
// Verify that m_nCount is correct.
if( m_nRead == m_nWrite )
{
Assert( m_nCount == 0 || m_nCount == m_nSize );
}
else
{
int testCount=0;
if ( m_nRead < m_nWrite )
testCount = m_nWrite - m_nRead;
else
testCount = (m_nSize - m_nRead) + m_nWrite;
Assert( testCount == m_nCount );
}
#endif
}
public:
void Flush();
int GetSize(); // Get the size of the buffer (how much can you write without reading
// before losing data.
int GetWriteAvailable(); // Get the amount available to write without overflowing.
// Note: you can write however much you want, but it may overflow,
// in which case the newest data is kept and the oldest is discarded.
int GetReadAvailable(); // Get the amount available to read.
int GetMaxUsed();
int Peek(char *pchDest, int nCount);
int Advance(int nCount);
int Read(void *pchDest, int nCount);
int Write(void *pchData, int nCount);
public:
int m_nCount; // Space between the read and write pointers (how much data we can read).
int m_nRead; // Read index into circular buffer
int m_nWrite; // Write index into circular buffer
int m_nSize; // Size of circular buffer in bytes (how much data it can hold).
char m_chData[1]; // Circular buffer holding data
};
// Use this to instantiate a CircularBuffer.
template< int size >
class CSizedCircularBuffer : public CCircularBuffer
{
public:
CSizedCircularBuffer() : CCircularBuffer(size) {}
private:
char myData[size-1];
};
CCircularBuffer *AllocateCircularBuffer( int nSize );
void FreeCircularBuffer( CCircularBuffer *pCircularBuffer );
#endif // CIRCULARBUFFER_H

124
engine/audio/private/eax.h Normal file
View File

@ -0,0 +1,124 @@
// EAX.H -- DirectSound Environmental Audio Extensions
#ifndef EAX_H
#define EAX_H
#pragma once
// EAX (listener) reverb property set {4a4e6fc1-c341-11d1-b73a-444553540000}
DEFINE_GUID(DSPROPSETID_EAX_ReverbProperties,
0x4a4e6fc1,
0xc341,
0x11d1,
0xb7, 0x3a, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00);
typedef enum
{
DSPROPERTY_EAX_ALL, // all reverb properties
DSPROPERTY_EAX_ENVIRONMENT, // standard environment no.
DSPROPERTY_EAX_VOLUME, // loudness of the reverb
DSPROPERTY_EAX_DECAYTIME, // how long the reverb lasts
DSPROPERTY_EAX_DAMPING // the high frequencies decay faster
} DSPROPERTY_EAX_REVERBPROPERTY;
#define EAX_NUM_STANDARD_PROPERTIES (DSPROPERTY_EAX_DAMPING + 1)
// use this structure for get/set all properties...
typedef struct
{
unsigned long environment; // 0 to EAX_ENVIRONMENT_COUNT-1
float fVolume; // 0 to 1
float fDecayTime_sec; // seconds, 0.1 to 100
float fDamping; // 0 to 1
} EAX_REVERBPROPERTIES;
enum
{
EAX_ENVIRONMENT_GENERIC, // factory default
EAX_ENVIRONMENT_PADDEDCELL,
EAX_ENVIRONMENT_ROOM, // standard environments
EAX_ENVIRONMENT_BATHROOM,
EAX_ENVIRONMENT_LIVINGROOM,
EAX_ENVIRONMENT_STONEROOM,
EAX_ENVIRONMENT_AUDITORIUM,
EAX_ENVIRONMENT_CONCERTHALL,
EAX_ENVIRONMENT_CAVE,
EAX_ENVIRONMENT_ARENA,
EAX_ENVIRONMENT_HANGAR,
EAX_ENVIRONMENT_CARPETEDHALLWAY,
EAX_ENVIRONMENT_HALLWAY,
EAX_ENVIRONMENT_STONECORRIDOR,
EAX_ENVIRONMENT_ALLEY,
EAX_ENVIRONMENT_FOREST,
EAX_ENVIRONMENT_CITY,
EAX_ENVIRONMENT_MOUNTAINS,
EAX_ENVIRONMENT_QUARRY,
EAX_ENVIRONMENT_PLAIN,
EAX_ENVIRONMENT_PARKINGLOT,
EAX_ENVIRONMENT_SEWERPIPE,
EAX_ENVIRONMENT_UNDERWATER,
EAX_ENVIRONMENT_DRUGGED,
EAX_ENVIRONMENT_DIZZY,
EAX_ENVIRONMENT_PSYCHOTIC,
EAX_ENVIRONMENT_COUNT // total number of environments
};
#define EAX_MAX_ENVIRONMENT (EAX_ENVIRONMENT_COUNT - 1)
// presets
#define EAX_PRESET_GENERIC EAX_ENVIRONMENT_GENERIC,0.5F,1.493F,0.5F
#define EAX_PRESET_PADDEDCELL EAX_ENVIRONMENT_PADDEDCELL,0.25F,0.1F,0.0F
#define EAX_PRESET_ROOM EAX_ENVIRONMENT_ROOM,0.417F,0.4F,0.666F
#define EAX_PRESET_BATHROOM EAX_ENVIRONMENT_BATHROOM,0.653F,1.499F,0.166F
#define EAX_PRESET_LIVINGROOM EAX_ENVIRONMENT_LIVINGROOM,0.208F,0.478F,0.0F
#define EAX_PRESET_STONEROOM EAX_ENVIRONMENT_STONEROOM,0.5F,2.309F,0.888F
#define EAX_PRESET_AUDITORIUM EAX_ENVIRONMENT_AUDITORIUM,0.403F,4.279F,0.5F
#define EAX_PRESET_CONCERTHALL EAX_ENVIRONMENT_CONCERTHALL,0.5F,3.961F,0.5F
#define EAX_PRESET_CAVE EAX_ENVIRONMENT_CAVE,0.5F,2.886F,1.304F
#define EAX_PRESET_ARENA EAX_ENVIRONMENT_ARENA,0.361F,7.284F,0.332F
#define EAX_PRESET_HANGAR EAX_ENVIRONMENT_HANGAR,0.5F,10.0F,0.3F
#define EAX_PRESET_CARPETEDHALLWAY EAX_ENVIRONMENT_CARPETEDHALLWAY,0.153F,0.259F,2.0F
#define EAX_PRESET_HALLWAY EAX_ENVIRONMENT_HALLWAY,0.361F,1.493F,0.0F
#define EAX_PRESET_STONECORRIDOR EAX_ENVIRONMENT_STONECORRIDOR,0.444F,2.697F,0.638F
#define EAX_PRESET_ALLEY EAX_ENVIRONMENT_ALLEY,0.25F,1.752F,0.776F
#define EAX_PRESET_FOREST EAX_ENVIRONMENT_FOREST,0.111F,3.145F,0.472F
#define EAX_PRESET_CITY EAX_ENVIRONMENT_CITY,0.111F,2.767F,0.224F
#define EAX_PRESET_MOUNTAINS EAX_ENVIRONMENT_MOUNTAINS,0.194F,7.841F,0.472F
#define EAX_PRESET_QUARRY EAX_ENVIRONMENT_QUARRY,1.0F,1.499F,0.5F
#define EAX_PRESET_PLAIN EAX_ENVIRONMENT_PLAIN,0.097F,2.767F,0.224F
#define EAX_PRESET_PARKINGLOT EAX_ENVIRONMENT_PARKINGLOT,0.208F,1.652F,1.5F
#define EAX_PRESET_SEWERPIPE EAX_ENVIRONMENT_SEWERPIPE,0.652F,2.886F,0.25F
#define EAX_PRESET_UNDERWATER EAX_ENVIRONMENT_UNDERWATER,1.0F,1.499F,0.0F
#define EAX_PRESET_DRUGGED EAX_ENVIRONMENT_DRUGGED,0.875F,8.392F,1.388F
#define EAX_PRESET_DIZZY EAX_ENVIRONMENT_DIZZY,0.139F,17.234F,0.666F
#define EAX_PRESET_PSYCHOTIC EAX_ENVIRONMENT_PSYCHOTIC,0.486F,7.563F,0.806F
// EAX buffer reverb property set {4a4e6fc0-c341-11d1-b73a-444553540000}
DEFINE_GUID(DSPROPSETID_EAXBUFFER_ReverbProperties,
0x4a4e6fc0,
0xc341,
0x11d1,
0xb7, 0x3a, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00);
typedef enum
{
DSPROPERTY_EAXBUFFER_ALL, // all reverb buffer properties
DSPROPERTY_EAXBUFFER_REVERBMIX // the wet source amount
} DSPROPERTY_EAXBUFFER_REVERBPROPERTY;
// use this structure for get/set all properties...
typedef struct
{
float fMix; // linear factor, 0.0F to 1.0F
} EAXBUFFER_REVERBPROPERTIES;
#define EAX_REVERBMIX_USEDISTANCE -1.0F // out of normal range
// signifies the reverb engine should
// calculate it's own reverb mix value
// based on distance
#endif // EAX_H

View File

@ -0,0 +1,234 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Posix win32 replacements - Mocks trivial windows flow
//
//=============================================================================
#ifndef POSIX_AUDIO_STUBS_H
#define POSIX_AUDIO_STUBS_H
#define DSBCAPS_LOCSOFTWARE 0
#define DSERR_BUFFERLOST 0
#define DSBSTATUS_BUFFERLOST 0x02
#define DSSPEAKER_GEOMETRY(x) (((x)>>16) & 0xFFFF)
#define DSSPEAKER_CONFIG(x) ((x) & 0xFFFF)
#define DSSPEAKER_HEADPHONE -1
#define DSSPEAKER_QUAD -2
#define DSSPEAKER_5POINT1 -3
#define DSSPEAKER_7POINT1 -4
#define DISP_CHANGE_SUCCESSFUL 0
#define HKEY_CURRENT_USER NULL
#define HKEY_LOCAL_MACHINE NULL
#define KEY_QUERY_VALUE 0
#define KEY_READ 0
#define KEY_WRITE 1
#define KEY_ALL_ACCESS ((ULONG)-1)
#define SMTO_ABORTIFHUNG 0
#define JOY_RETURNX 0x01
#define JOY_RETURNY 0x02
#define JOY_RETURNZ 0x04
#define JOY_RETURNR 0x08
#define JOY_RETURNU 0x10
#define JOY_RETURNV 0x20
#define JOYCAPS_HASPOV 0x01
#define JOYCAPS_HASU 0x01
#define JOYCAPS_HASV 0x01
#define JOYCAPS_HASR 0x01
#define JOYCAPS_HASZ 0x01
#define MMSYSERR_NODRIVER 1
#define JOYERR_NOERROR 0
#define JOY_RETURNCENTERED 0
#define JOY_RETURNBUTTONS 0
#define JOY_RETURNPOV 0
#define JOY_POVCENTERED 0
#define JOY_POVFORWARD 0
#define JOY_POVRIGHT 0
#define JOY_POVBACKWARD 0
#define JOY_POVLEFT 0
#define CCHDEVICENAME 32
#define CCHFORMNAME 32
typedef wchar_t BCHAR;
typedef uint MMRESULT;
typedef uint32 *DWORD_PTR;
typedef char *LPCSTR;
typedef uint POINTL;
#define IDLE_PRIORITY_CLASS 1
#define HIGH_PRIORITY_CLASS 2
typedef struct _devicemode {
BCHAR dmDeviceName[CCHDEVICENAME];
WORD dmSpecVersion;
WORD dmDriverVersion;
WORD dmSize;
WORD dmDriverExtra;
DWORD dmFields;
union u1 {
struct s {
short dmOrientation;
short dmPaperSize;
short dmPaperLength;
short dmPaperWidth;
short dmScale;
short dmCopies;
short dmDefaultSource;
short dmPrintQuality;
};
POINTL dmPosition;
DWORD dmDisplayOrientation;
DWORD dmDisplayFixedOutput;
};
short dmColor;
short dmDuplex;
short dmYResolution;
short dmTTOption;
short dmCollate;
BYTE dmFormName[CCHFORMNAME];
WORD dmLogPixels;
DWORD dmBitsPerPel;
DWORD dmPelsWidth;
DWORD dmPelsHeight;
union u2 {
DWORD dmDisplayFlags;
DWORD dmNup;
};
DWORD dmDisplayFrequency;
DWORD dmICMMethod;
DWORD dmICMIntent;
DWORD dmMediaType;
DWORD dmDitherType;
DWORD dmReserved1;
DWORD dmReserved2;
DWORD dmPanningWidth;
DWORD dmPanningHeight;
} DEVMODE, *LPDEVMODE;
typedef uint32 MCIERROR;
typedef uint MCIDEVICEID;
typedef struct {
DWORD_PTR dwCallback;
} MCI_GENERIC_PARMS;
typedef struct {
DWORD_PTR dwCallback;
DWORD dwReturn;
DWORD dwItem;
DWORD dwTrack;
} MCI_STATUS_PARMS;
typedef struct {
DWORD_PTR dwCallback;
DWORD dwFrom;
DWORD dwTo;
} MCI_PLAY_PARMS;
typedef struct {
DWORD_PTR dwCallback;
MCIDEVICEID wDeviceID;
LPCSTR lpstrDeviceType;
LPCSTR lpstrElementName;
LPCSTR lpstrAlias;
} MCI_OPEN_PARMS;
typedef struct {
DWORD_PTR dwCallback;
DWORD dwTimeFormat;
DWORD dwAudio;
} MCI_SET_PARMS;
#define MCI_MAKE_TMSF(t, m, s, f) ((DWORD)(((BYTE)(t) | ((WORD)(m) << 8)) | ((DWORD)(BYTE)(s) | ((WORD)(f)<<8)) << 16))
#define MCI_MSF_MINUTE(msf) ((BYTE)(msf))
#define MCI_MSF_SECOND(msf) ((BYTE)(((WORD)(msf)) >> 8))
#define MCI_OPEN 0
#define MCI_OPEN_TYPE 0
#define MCI_OPEN_SHAREABLE 0
#define MCI_FORMAT_TMSF 0
#define MCI_SET_TIME_FORMAT 0
#define MCI_CLOSE 0
#define MCI_STOP 0
#define MCI_PAUSE 0
#define MCI_PLAY 0
#define MCI_SET 0
#define MCI_SET_DOOR_OPEN 0
#define MCI_SET_DOOR_CLOSED 0
#define MCI_STATUS_READY 0
#define MCI_STATUS 0
#define MCI_STATUS_ITEM 0
#define MCI_STATUS_WAIT 0
#define MCI_STATUS_NUMBER_OF_TRACKS 0
#define MCI_CDA_STATUS_TYPE_TRACK 0
#define MCI_TRACK 0
#define MCI_WAIT 0
#define MCI_CDA_TRACK_AUDIO 0
#define MCI_STATUS_LENGTH 0
#define MCI_NOTIFY 0
#define MCI_FROM 0
#define MCI_TO 0
#define MCIERR_DRIVER -1
#define DSERR_ALLOCATED 0
#pragma pack(push, 1)
typedef struct tWAVEFORMATEX
{
WORD wFormatTag;
WORD nChannels;
DWORD nSamplesPerSec;
DWORD nAvgBytesPerSec;
WORD nBlockAlign;
WORD wBitsPerSample;
WORD cbSize;
} WAVEFORMATEX, *PWAVEFORMATEX, *NPWAVEFORMATEX, *LPWAVEFORMATEX;
typedef const WAVEFORMATEX *LPCWAVEFORMATEX;
typedef struct waveformat_tag
{
WORD wFormatTag;
WORD nChannels;
DWORD nSamplesPerSec;
DWORD nAvgBytesPerSec;
WORD nBlockAlign;
} WAVEFORMAT, *PWAVEFORMAT, *NPWAVEFORMAT, *LPWAVEFORMAT;
typedef const WAVEFORMAT *LPCWAVEFORMAT;
typedef struct pcmwaveformat_tag
{
WAVEFORMAT wf;
WORD wBitsPerSample;
} PCMWAVEFORMAT, *PPCMWAVEFORMAT, *NPPCMWAVEFORMAT, *LPPCMWAVEFORMAT;
typedef const PCMWAVEFORMAT *LPCPCMWAVEFORMAT;
typedef struct adpcmcoef_tag {
short iCoef1;
short iCoef2;
} ADPCMCOEFSET;
typedef struct adpcmwaveformat_tag {
WAVEFORMATEX wfx;
WORD wSamplesPerBlock;
WORD wNumCoef;
ADPCMCOEFSET aCoef[1];
} ADPCMWAVEFORMAT;
#pragma pack(pop)
#endif

View File

@ -0,0 +1,213 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef SND_CHANNELS_H
#define SND_CHANNELS_H
#include "mathlib/vector.h"
#if defined( _WIN32 )
#pragma once
#endif
class CSfxTable;
class CAudioMixer;
typedef int SoundSource;
// DO NOT REORDER: indices to fvolume arrays in channel_t
#define IFRONT_LEFT 0 // NOTE: must correspond to order of fvolume array below!
#define IFRONT_RIGHT 1
#define IREAR_LEFT 2
#define IREAR_RIGHT 3
#define IFRONT_CENTER 4
#define IFRONT_CENTER0 5 // dummy slot - center channel is mono, but mixers reference volume[1] slot
#define IFRONT_LEFTD 6 // start of doppler right array
#define IFRONT_RIGHTD 7
#define IREAR_LEFTD 8
#define IREAR_RIGHTD 9
#define IFRONT_CENTERD 10
#define IFRONT_CENTERD0 11 // dummy slot - center channel is mono, but mixers reference volume[1] slot
#define CCHANVOLUMES 12
//-----------------------------------------------------------------------------
// Purpose: Each currently playing wave is stored in a channel
//-----------------------------------------------------------------------------
// NOTE: 128bytes. These are memset to zero at some points. Do not add virtuals without changing that pattern.
// UNDONE: now 300 bytes...
struct channel_t
{
int guid; // incremented each time a channel is allocated (to match with channel free in tools, etc.)
int userdata; // user specified data for syncing to tools
CSfxTable *sfx; // the actual sound
CAudioMixer *pMixer; // The sound's instance data for this channel
// speaker channel volumes, indexed using IFRONT_LEFT to IFRONT_CENTER.
// NOTE: never access these fvolume[] elements directly! Use channel helpers in snd_dma.cpp.
float fvolume[CCHANVOLUMES]; // 0.0-255.0 current output volumes
float fvolume_target[CCHANVOLUMES]; // 0.0-255.0 target output volumes
float fvolume_inc[CCHANVOLUMES]; // volume increment, per frame, moves volume[i] to vol_target[i] (per spatialization)
uint nFreeChannelAtSampleTime;
SoundSource soundsource; // see iclientsound.h for description.
int entchannel; // sound channel (CHAN_STREAM, CHAN_VOICE, etc.)
int speakerentity; // if a sound is being played through a speaker entity (e.g., on a monitor,), this is the
// entity upon which to show the lips moving, if the sound has sentence data
short master_vol; // 0-255 master volume
short basePitch; // base pitch percent (100% is normal pitch playback)
float pitch; // real-time pitch after any modulation or shift by dynamic data
int mixgroups[8]; // sound belongs to these mixgroups: world, actor, player weapon, explosion etc.
int last_mixgroupid;// last mixgroupid selected
float last_vol; // last volume after spatialization
Vector origin; // origin of sound effect
Vector direction; // direction of the sound
float dist_mult; // distance multiplier (attenuation/clipK)
float dspmix; // 0 - 1.0 proportion of dsp to mix with original sound, based on distance
float dspface; // -1.0 - 1.0 (1.0 = facing listener)
float distmix; // 0 - 1.0 proportion based on distance from listner (1.0 - 100% wav right - far)
float dsp_mix_min; // for dspmix calculation - set by current preset in SND_GetDspMix
float dsp_mix_max; // for dspmix calculation - set by current preset in SND_GetDspMix
float radius; // Radius of this sound effect (spatialization is different within the radius)
float ob_gain; // gain drop if sound source obscured from listener
float ob_gain_target; // target gain while crossfading between ob_gain & ob_gain_target
float ob_gain_inc; // crossfade increment
short activeIndex;
char wavtype; // 0 default, CHAR_DOPPLER, CHAR_DIRECTIONAL, CHAR_DISTVARIANT
char pad;
char sample_prev[8]; // last sample(s) in previous input data buffer - space for 2, 16 bit, stereo samples
int initialStreamPosition;
int special_dsp;
union
{
unsigned int flagsword;
struct
{
bool bUpdatePositions : 1; // if true, assume sound source can move and update according to entity
bool isSentence : 1; // true if playing linked sentence
bool bdry : 1; // if true, bypass all dsp processing for this sound (ie: music)
bool bSpeaker : 1; // true if sound is playing through in-game speaker entity.
bool bstereowav : 1; // if true, a stereo .wav file is the sample data source
bool delayed_start : 1; // If true, sound had a delay and so same sound on same channel won't channel steal from it
bool fromserver : 1; // for snd_show, networked sounds get colored differently than local sounds
bool bfirstpass : 1; // true if this is first time sound is spatialized
bool bTraced : 1; // true if channel was already checked this frame for obscuring
bool bfast_pitch : 1; // true if using low quality pitch (fast, but no interpolation)
bool m_bIsFreeingChannel : 1; // true when inside S_FreeChannel - prevents reentrance
bool m_bCompatibilityAttenuation : 1; // True when we want to use goldsrc compatibility mode for the attenuation
// In that case, dist_mul is set to a relatively meaningful value in StartDynamic/StartStaticSound,
// but we interpret it totally differently in SND_GetGain.
bool m_bShouldPause : 1; // if true, sound should pause when the game is paused
bool m_bIgnorePhonemes : 1; // if true, we don't want to drive animation w/ phoneme data
} flags;
};
};
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#define MAX_CHANNELS 128
#define MAX_DYNAMIC_CHANNELS 64
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
extern channel_t channels[MAX_CHANNELS];
// 0 to MAX_DYNAMIC_CHANNELS-1 = normal entity sounds
// MAX_DYNAMIC_CHANNELS to total_channels = static sounds
extern int total_channels;
class CChannelList
{
public:
int Count();
int GetChannelIndex( int listIndex );
channel_t *GetChannel( int listIndex );
void RemoveChannelFromList( int listIndex );
bool IsQuashed( int listIndex );
int m_count;
short m_list[MAX_CHANNELS];
bool m_quashed[MAX_CHANNELS]; // if true, the channel should be advanced, but not mixed, because it's been heuristically suppressed
CUtlVector< int > m_nSpecialDSPs;
bool m_hasSpeakerChannels : 1;
bool m_hasDryChannels : 1;
bool m_has11kChannels : 1;
bool m_has22kChannels : 1;
bool m_has44kChannels : 1;
};
inline int CChannelList::Count()
{
return m_count;
}
inline int CChannelList::GetChannelIndex( int listIndex )
{
return m_list[listIndex];
}
inline channel_t *CChannelList::GetChannel( int listIndex )
{
return &channels[GetChannelIndex(listIndex)];
}
inline bool CChannelList::IsQuashed( int listIndex )
{
return m_quashed[listIndex];
}
inline void CChannelList::RemoveChannelFromList( int listIndex )
{
// decrease the count by one, and swap the deleted channel with
// the last one.
m_count--;
if ( m_count > 0 && listIndex != m_count )
{
m_list[listIndex] = m_list[m_count];
m_quashed[listIndex] = m_quashed[m_count];
}
}
class CActiveChannels
{
public:
void Add( channel_t *pChannel );
void Remove( channel_t *pChannel );
void GetActiveChannels( CChannelList &list );
void Init();
int GetActiveCount() { return m_count; }
private:
int m_count;
short m_list[MAX_CHANNELS];
};
extern CActiveChannels g_ActiveChannels;
//=============================================================================
#endif // SND_CHANNELS_H

View File

@ -0,0 +1,21 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef SND_CONVARS_H
#define SND_CONVARS_H
#if defined( _WIN32 )
#pragma once
#endif
#include "convar.h"
extern ConVar snd_legacy_surround;
extern ConVar snd_surround;
extern ConVar snd_mix_async;
#endif // SND_CONVARS_H

View File

@ -0,0 +1,623 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Device Common Base Class.
//
//=====================================================================================//
#include "audio_pch.h"
#define ISPEAKER_RIGHT_FRONT 0
#define ISPEAKER_LEFT_FRONT 1
#define ISPEAKER_RIGHT_REAR 2
#define ISPEAKER_LEFT_REAR 3
#define ISPEAKER_CENTER_FRONT 4
extern Vector listener_right;
extern void DEBUG_StartSoundMeasure(int type, int samplecount );
extern void DEBUG_StopSoundMeasure(int type, int samplecount );
extern bool MIX_ScaleChannelVolume( paintbuffer_t *pPaint, channel_t *pChannel, int volume[CCHANVOLUMES], int mixchans );
inline bool FVolumeFrontNonZero( int *pvol )
{
return (pvol[IFRONT_RIGHT] || pvol[IFRONT_LEFT]);
}
inline bool FVolumeRearNonZero( int *pvol )
{
return (pvol[IREAR_RIGHT] || pvol[IREAR_LEFT]);
}
inline bool FVolumeCenterNonZero( int *pvol )
{
return (pvol[IFRONT_CENTER] != 0);
}
// fade speaker volumes to mono, based on xfade value.
// ie: xfade 1.0 is full mono.
// ispeaker is speaker index, cspeaker is total # of speakers
// fmix2channels causes mono mix for 4 channel mix to mix down to 2 channels
// this is used for the 2 speaker outpu case, which uses recombined 4 channel front/rear mixing
static float XfadeSpeakerVolToMono( float scale, float xfade, float ispeaker, float cspeaker, bool fmix2channels )
{
float scale_out;
float scale_target;
if (cspeaker == 4 )
{
// mono sound distribution:
float scale_targets[] = {0.9, 0.9, 0.9, 0.9}; // RF, LF, RR, LR
float scale_targets2ch[] = {0.9, 0.9, 0.0, 0.0}; // RF, LF, RR, LR
if ( fmix2channels )
scale_target = scale_targets2ch[clamp(FastFloatToSmallInt(ispeaker), 0, 3)];
else
scale_target = scale_targets[clamp(FastFloatToSmallInt(ispeaker), 0, 3)];
goto XfadeExit;
}
if (cspeaker == 5 )
{
// mono sound distribution:
float scale_targets[] = {0.9, 0.9, 0.5, 0.5, 0.9}; // RF, LF, RR, LR, FC
scale_target = scale_targets[(int)clamp(FastFloatToSmallInt(ispeaker), 0, 4)];
goto XfadeExit;
}
// if (cspeaker == 2 )
scale_target = 0.9; // front 2 speakers in stereo each get 50% of total volume in mono case
XfadeExit:
scale_out = scale + (scale_target - scale) * xfade;
return scale_out;
}
// given:
// 2d yaw angle to sound source (0-360), where 0 is listener_right
// pitch angle to source
// angle to speaker position (0-360), where 0 is listener_right
// speaker index
// speaker total count,
// return: scale from 0-1.0 for speaker volume.
// NOTE: as pitch angle goes to +/- 90, sound goes to mono, all speakers.
#define PITCH_ANGLE_THRESHOLD 45.0
#define REAR_VOL_DROP 0.5
#define VOLCURVEPOWER 1.5 // 1.0 is a linear crossfade of volume between speakers.
// 1.5 provides a smoother, nonlinear volume transition - this is done
// because a volume of 255 played in a single speaker is
// percieved as louder than 128 + 128 in two speakers
// separated by at least 45 degrees. The nonlinear curve
// gives the volume boost needed.
static float GetSpeakerVol( float yaw_source, float pitch_source, float mono, float yaw_speaker, int ispeaker, int cspeaker, bool fmix2channels )
{
float adif = fabs(yaw_source - yaw_speaker);
float pitch_angle = pitch_source;
float scale = 0.0;
float xfade = 0.0;
if ( adif > 180 )
adif = 360 - adif;
// mono goes from 0.0 to 1.0 as listener moves into 'mono' radius of sound source.
// Also, as pitch_angle to sound source approaches 90 (sound above/below listener), sounds become mono.
// convert pitch angle to 0-90 absolute pitch
if ( pitch_angle < 0)
pitch_angle += 360;
if ( pitch_angle > 180)
pitch_angle = 360 - pitch_angle;
if ( pitch_angle > 90)
pitch_angle = 90 - (pitch_angle - 90);
// calculate additional mono crossfade due to pitch angle
if ( pitch_angle > PITCH_ANGLE_THRESHOLD )
{
xfade = ( pitch_angle - PITCH_ANGLE_THRESHOLD ) / ( 90.0 - PITCH_ANGLE_THRESHOLD ); // 0.0 -> 1.0 as angle 45->90
mono += xfade;
mono = clamp(mono, 0.0f, 1.0f);
}
if ( cspeaker == 2 )
{
// 2 speaker (headphone) mix: speakers opposing, at 0 & 180 degrees
scale = (1.0 - powf(adif/180.0, VOLCURVEPOWER));
goto GetVolExit;
}
if ( adif >= 90.0 )
goto GetVolExit; // 0.0 scale
if ( cspeaker == 4 )
{
// 4 ch surround: all speakers on 90 degree angles,
// scale ranges from 0.0 (at 90 degree difference between source and speaker)
// to 1.0 (0 degree difference between source and speaker)
scale = (1.0 - powf(adif/90.0, VOLCURVEPOWER));
goto GetVolExit;
}
// 5 ch surround:
// rear speakers are on 90 degree angles and return 0.0->1.0 range over +/- 90 degrees each
// center speaker is on 45 degree angle to left/right front speaker
// center speaker has 0.0->1.0 range over 45 degrees
switch (ispeaker)
{
default:
case ISPEAKER_RIGHT_REAR:
case ISPEAKER_LEFT_REAR:
{
// rear speakers get +/- 90 degrees of linear scaling...
scale = (1.0 - powf(adif/90.0, VOLCURVEPOWER));
break;
}
case ISPEAKER_CENTER_FRONT:
{
// center speaker gets +/- 45 degrees of linear scaling...
if (adif > 45.0)
goto GetVolExit; // 0.0 scale
scale = (1.0 - powf(adif/45.0, VOLCURVEPOWER));
break;
}
case ISPEAKER_RIGHT_FRONT:
{
if (yaw_source > yaw_speaker)
{
// if sound source is between right front speaker and center speaker,
// apply scaling over 75 degrees...
if (adif > 75.0)
goto GetVolExit; // 0.0 scale
scale = (1.0 - powf(adif/75.0, VOLCURVEPOWER));
}
/*
if (yaw_source > yaw_speaker && yaw_source < (yaw_speaker + 90.0))
{
// if sound source is between right front speaker and center speaker,
// apply scaling over 45 degrees...
if (adif > 45.0)
goto GetVolExit; // 0.0 scale
scale = (1.0 - powf(adif/45.0, VOLCURVEPOWER));
}
*/
else
{
// sound source is CW from right speaker, apply scaling over 90 degrees...
scale = (1.0 - powf(adif/90.0, VOLCURVEPOWER));
}
break;
}
case ISPEAKER_LEFT_FRONT:
{
if (yaw_source < yaw_speaker )
{
// if sound source is between left front speaker and center speaker,
// apply scaling over 75 degrees...
if (adif > 75.0)
goto GetVolExit; // 0.0 scale
scale = (1.0 - powf(adif/75.0, VOLCURVEPOWER));
}
/*
if (yaw_source < yaw_speaker && yaw_source > (yaw_speaker - 90.0))
{
// if sound source is between left front speaker and center speaker,
// apply scaling over 45 degrees...
if (adif > 45.0)
goto GetVolExit; // 0.0 scale
scale = (1.0 - powf(adif/45.0, VOLCURVEPOWER));
}
*/
else
{
// sound source is CW from right speaker, apply scaling over 90 degrees...
scale = (1.0 - powf(adif/90.0, VOLCURVEPOWER));
}
break;
}
}
GetVolExit:
Assert(mono <= 1.0 && mono >= 0.0);
Assert(scale <= 1.0 && scale >= 0.0);
// crossfade speaker volumes towards mono with increased pitch angle of sound source
scale = XfadeSpeakerVolToMono( scale, mono, ispeaker, cspeaker, fmix2channels );
Assert(scale <= 1.0 && scale >= 0.0);
return scale;
}
// given unit vector from listener to sound source,
// determine proportion of volume for sound in FL, FC, FR, RL, RR quadrants
// Scale this proportion by the distance scalar 'gain'
// If sound has 'mono' radius, blend sound to mono over 50% of radius.
void CAudioDeviceBase::SpatializeChannel( int volume[CCHANVOLUMES/2], int master_vol, const Vector& sourceDir, float gain, float mono )
{
VPROF("CAudioDeviceBase::SpatializeChannel");
float rfscale, rrscale, lfscale, lrscale, fcscale;
fcscale = rfscale = lfscale = rrscale = lrscale = 0.0;
// clear volumes
for (int i = 0; i < CCHANVOLUMES/2; i++)
volume[i] = 0;
// linear crossfader for 2, 4 or 5 speakers, using polar coord. separation angle as linear basis
// get pitch & yaw angle from listener origin to sound source
QAngle angles;
float pitch;
float source_yaw;
float yaw;
VectorAngles(sourceDir, angles);
pitch = angles[PITCH];
source_yaw = angles[YAW];
// get 2d listener yaw angle from listener right
QAngle angles2d;
Vector source2d;
float listener_yaw;
source2d.x = listener_right.x;
source2d.y = listener_right.y;
source2d.z = 0.0;
VectorNormalize(source2d);
// convert right vector to euler angles (yaw & pitch)
VectorAngles(source2d, angles2d);
listener_yaw = angles2d[YAW];
// get yaw of sound source, with listener_yaw as reference 0.
yaw = source_yaw - listener_yaw;
if (yaw < 0)
yaw += 360;
if ( !m_bSurround )
{
// 2 ch stereo mixing
if ( m_bHeadphone )
{
// headphone mix: (NO HRTF)
rfscale = GetSpeakerVol( yaw, pitch, mono, 0.0, ISPEAKER_RIGHT_FRONT, 2, false);
lfscale = GetSpeakerVol( yaw, pitch, mono, 180.0, ISPEAKER_LEFT_FRONT, 2, false );
}
else
{
// stereo speakers at 45 & 135 degrees: (mono sounds mix down to 2 channels)
rfscale = GetSpeakerVol( yaw, pitch, mono, 45.0, ISPEAKER_RIGHT_FRONT, 4, true );
lfscale = GetSpeakerVol( yaw, pitch, mono, 135.0, ISPEAKER_LEFT_FRONT, 4, true );
rrscale = GetSpeakerVol( yaw, pitch, mono, 315.0, ISPEAKER_RIGHT_REAR, 4, true );
lrscale = GetSpeakerVol( yaw, pitch, mono, 225.0, ISPEAKER_LEFT_REAR, 4, true );
// add sounds coming from rear (quieter)
rfscale = clamp((rfscale + rrscale * 0.75), 0.0, 1.0);
lfscale = clamp((lfscale + lrscale * 0.75), 0.0, 1.0);
rrscale = 0;
lrscale = 0;
//DevMsg("lfscale=%f rfscale=%f lrscale=%f rrscale=%f\n",lfscale,rfscale,lrscale,rrscale);
//DevMsg("pitch=%f yaw=%f \n",pitch, yaw);
}
goto SpatialExit;
}
if ( m_bSurround && !m_bSurroundCenter )
{
// 4 ch surround
// linearly scale with radial distance from asource to FR, FL, RR, RL
// where FR = 45 degrees, FL = 135, RR = 315 (-45), RL = 225 (-135)
rfscale = GetSpeakerVol( yaw, pitch, mono, 45.0, ISPEAKER_RIGHT_FRONT, 4, false );
lfscale = GetSpeakerVol( yaw, pitch, mono, 135.0, ISPEAKER_LEFT_FRONT, 4, false );
rrscale = GetSpeakerVol( yaw, pitch, mono, 315.0, ISPEAKER_RIGHT_REAR, 4, false );
lrscale = GetSpeakerVol( yaw, pitch, mono, 225.0, ISPEAKER_LEFT_REAR, 4, false );
// DevMsg("lfscale=%f rfscale=%f lrscale=%f rrscale=%f\n",lfscale,rfscale,lrscale,rrscale);
// DevMsg("pitch=%f yaw=%f \n",pitch, yaw);
goto SpatialExit;
}
if ( m_bSurround && m_bSurroundCenter )
{
// 5 ch surround
// linearly scale with radial distance from asource to FR, FC, FL, RR, RL
// where FR = 45 degrees, FC = 90, FL = 135, RR = 315 (-45), RL = 225 (-135)
rfscale = GetSpeakerVol( yaw, pitch, mono, 45.0, ISPEAKER_RIGHT_FRONT, 5, false );
fcscale = GetSpeakerVol( yaw, pitch, mono, 90.0, ISPEAKER_CENTER_FRONT, 5, false );
lfscale = GetSpeakerVol( yaw, pitch, mono, 135.0, ISPEAKER_LEFT_FRONT, 5, false );
rrscale = GetSpeakerVol( yaw, pitch, mono, 315.0, ISPEAKER_RIGHT_REAR, 5, false );
lrscale = GetSpeakerVol( yaw, pitch, mono, 225.0, ISPEAKER_LEFT_REAR, 5, false );
//DevMsg("lfscale=%f center= %f rfscale=%f lrscale=%f rrscale=%f\n",lfscale,fcscale, rfscale,lrscale,rrscale);
//DevMsg("pitch=%f yaw=%f \n",pitch, yaw);
goto SpatialExit;
}
SpatialExit:
// scale volumes in each quadrant by distance attenuation.
// volumes are 0-255:
// gain is 0.0->1.0, rscale is 0.0->1.0, so scale is 0.0->1.0
// master_vol is 0->255, so rightvol is 0->255
volume[IFRONT_RIGHT] = (int) (master_vol * gain * rfscale);
volume[IFRONT_LEFT] = (int) (master_vol * gain * lfscale);
volume[IFRONT_RIGHT] = clamp( volume[IFRONT_RIGHT], 0, 255 );
volume[IFRONT_LEFT] = clamp( volume[IFRONT_LEFT], 0, 255 );
if ( m_bSurround )
{
volume[IREAR_RIGHT] = (int) (master_vol * gain * rrscale);
volume[IREAR_LEFT] = (int) (master_vol * gain * lrscale);
volume[IREAR_RIGHT] = clamp( volume[IREAR_RIGHT], 0, 255 );
volume[IREAR_LEFT] = clamp( volume[IREAR_LEFT], 0, 255 );
if ( m_bSurroundCenter )
{
volume[IFRONT_CENTER] = (int) (master_vol * gain * fcscale);
volume[IFRONT_CENTER0] = 0.0;
volume[IFRONT_CENTER] = clamp( volume[IFRONT_CENTER], 0, 255);
}
}
}
void CAudioDeviceBase::ApplyDSPEffects( int idsp, portable_samplepair_t *pbuffront, portable_samplepair_t *pbufrear, portable_samplepair_t *pbufcenter, int samplecount)
{
VPROF("CAudioDeviceBase::ApplyDSPEffects");
DEBUG_StartSoundMeasure( 1, samplecount );
DSP_Process( idsp, pbuffront, pbufrear, pbufcenter, samplecount );
DEBUG_StopSoundMeasure( 1, samplecount );
}
void CAudioDeviceBase::MixBegin( int sampleCount )
{
MIX_ClearAllPaintBuffers( sampleCount, false );
}
void CAudioDeviceBase::MixUpsample( int sampleCount, int filtertype )
{
paintbuffer_t *pPaint = MIX_GetCurrentPaintbufferPtr();
int ifilter = pPaint->ifilter;
Assert (ifilter < CPAINTFILTERS);
S_MixBufferUpsample2x( sampleCount, pPaint->pbuf, &(pPaint->fltmem[ifilter][0]), CPAINTFILTERMEM, filtertype );
if ( pPaint->fsurround )
{
Assert( pPaint->pbufrear );
S_MixBufferUpsample2x( sampleCount, pPaint->pbufrear, &(pPaint->fltmemrear[ifilter][0]), CPAINTFILTERMEM, filtertype );
if ( pPaint->fsurround_center )
{
Assert( pPaint->pbufcenter );
S_MixBufferUpsample2x( sampleCount, pPaint->pbufcenter, &(pPaint->fltmemcenter[ifilter][0]), CPAINTFILTERMEM, filtertype );
}
}
// make sure on next upsample pass for this paintbuffer, new filter memory is used
pPaint->ifilter++;
}
void CAudioDeviceBase::Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
{
int volume[CCHANVOLUMES];
paintbuffer_t *pPaint = MIX_GetCurrentPaintbufferPtr();
if ( !MIX_ScaleChannelVolume( pPaint, pChannel, volume, 1) )
return;
if ( FVolumeFrontNonZero(volume) )
{
Mix8MonoWavtype( pChannel, pPaint->pbuf + outputOffset, volume, (byte *)pData, inputOffset, rateScaleFix, outCount);
}
if ( pPaint->fsurround )
{
if ( FVolumeRearNonZero(volume) )
{
Assert( pPaint->pbufrear );
Mix8MonoWavtype( pChannel, pPaint->pbufrear + outputOffset, &volume[IREAR_LEFT], (byte *)pData, inputOffset, rateScaleFix, outCount );
}
if ( pPaint->fsurround_center && FVolumeCenterNonZero(volume) )
{
Assert( pPaint->pbufcenter );
Mix8MonoWavtype( pChannel, pPaint->pbufcenter + outputOffset, &volume[IFRONT_CENTER], (byte *)pData, inputOffset, rateScaleFix, outCount );
}
}
}
void CAudioDeviceBase::Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
{
int volume[CCHANVOLUMES];
paintbuffer_t *pPaint = MIX_GetCurrentPaintbufferPtr();
if ( !MIX_ScaleChannelVolume( pPaint, pChannel, volume, 2 ) )
return;
if ( FVolumeFrontNonZero(volume) )
{
Mix8StereoWavtype( pChannel, pPaint->pbuf + outputOffset, volume, (byte *)pData, inputOffset, rateScaleFix, outCount );
}
if ( pPaint->fsurround )
{
if ( FVolumeRearNonZero(volume) )
{
Assert( pPaint->pbufrear );
Mix8StereoWavtype( pChannel, pPaint->pbufrear + outputOffset, &volume[IREAR_LEFT], (byte *)pData, inputOffset, rateScaleFix, outCount );
}
if ( pPaint->fsurround_center && FVolumeCenterNonZero(volume) )
{
Assert( pPaint->pbufcenter );
Mix8StereoWavtype( pChannel, pPaint->pbufcenter + outputOffset, &volume[IFRONT_CENTER], (byte *)pData, inputOffset, rateScaleFix, outCount );
}
}
}
void CAudioDeviceBase::Mix16Mono( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
{
int volume[CCHANVOLUMES];
paintbuffer_t *pPaint = MIX_GetCurrentPaintbufferPtr();
if ( !MIX_ScaleChannelVolume( pPaint, pChannel, volume, 1 ) )
return;
if ( FVolumeFrontNonZero(volume) )
{
Mix16MonoWavtype( pChannel, pPaint->pbuf + outputOffset, volume, pData, inputOffset, rateScaleFix, outCount );
}
if ( pPaint->fsurround )
{
if ( FVolumeRearNonZero(volume) )
{
Assert( pPaint->pbufrear );
Mix16MonoWavtype( pChannel, pPaint->pbufrear + outputOffset, &volume[IREAR_LEFT], pData, inputOffset, rateScaleFix, outCount );
}
if ( pPaint->fsurround_center && FVolumeCenterNonZero(volume) )
{
Assert( pPaint->pbufcenter );
Mix16MonoWavtype( pChannel, pPaint->pbufcenter + outputOffset, &volume[IFRONT_CENTER], pData, inputOffset, rateScaleFix, outCount );
}
}
}
void CAudioDeviceBase::Mix16Stereo( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
{
int volume[CCHANVOLUMES];
paintbuffer_t *pPaint = MIX_GetCurrentPaintbufferPtr();
if ( !MIX_ScaleChannelVolume( pPaint, pChannel, volume, 2 ) )
return;
if ( FVolumeFrontNonZero(volume) )
{
Mix16StereoWavtype( pChannel, pPaint->pbuf + outputOffset, volume, pData, inputOffset, rateScaleFix, outCount );
}
if ( pPaint->fsurround )
{
if ( FVolumeRearNonZero(volume) )
{
Assert( pPaint->pbufrear );
Mix16StereoWavtype( pChannel, pPaint->pbufrear + outputOffset, &volume[IREAR_LEFT], pData, inputOffset, rateScaleFix, outCount );
}
if ( pPaint->fsurround_center && FVolumeCenterNonZero(volume) )
{
Assert( pPaint->pbufcenter );
Mix16StereoWavtype( pChannel, pPaint->pbufcenter + outputOffset, &volume[IFRONT_CENTER], pData, inputOffset, rateScaleFix, outCount );
}
}
}
// Null Audio Device
class CAudioDeviceNull : public CAudioDeviceBase
{
public:
bool IsActive( void ) { return false; }
bool Init( void ) { return true; }
void Shutdown( void ) {}
void Pause( void ) {}
void UnPause( void ) {}
float MixDryVolume( void ) { return 0; }
bool Should3DMix( void ) { return false; }
void StopAllSounds( void ) {}
int PaintBegin( float, int, int ) { return 0; }
void PaintEnd( void ) {}
void SpatializeChannel( int volume[CCHANVOLUMES/2], int master_vol, const Vector& sourceDir, float gain, float mono ) {}
void ApplyDSPEffects( int idsp, portable_samplepair_t *pbuffront, portable_samplepair_t *pbufrear, portable_samplepair_t *pbufcenter, int samplecount ) {}
int GetOutputPosition( void ) { return 0; }
void ClearBuffer( void ) {}
void UpdateListener( const Vector&, const Vector&, const Vector&, const Vector& ) {}
void MixBegin( int ) {}
void MixUpsample( int sampleCount, int filtertype ) {}
void Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress ) {}
void Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress ) {}
void Mix16Mono( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress ) {}
void Mix16Stereo( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress ) {}
void ChannelReset( int, int, float ) {}
void TransferSamples( int end ) {}
const char *DeviceName( void ) { return "Audio Disabled"; }
int DeviceChannels( void ) { return 2; }
int DeviceSampleBits( void ) { return 16; }
int DeviceSampleBytes( void ) { return 2; }
int DeviceDmaSpeed( void ) { return SOUND_DMA_SPEED; }
int DeviceSampleCount( void ) { return 0; }
bool IsSurround( void ) { return false; }
bool IsSurroundCenter( void ) { return false; }
bool IsHeadphone( void ) { return false; }
};
IAudioDevice *Audio_GetNullDevice( void )
{
// singeton device here
static CAudioDeviceNull nullDevice;
return &nullDevice;
}

View File

@ -0,0 +1,59 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Device Common Routines
//
//=====================================================================================//
#ifndef SND_DEV_COMMON_H
#define SND_DEV_COMMON_H
#pragma once
class CAudioDeviceBase : public IAudioDevice
{
public:
virtual bool IsActive( void ) { return false; }
virtual bool Init( void ) { return false; }
virtual void Shutdown( void ) {}
virtual void Pause( void ) {}
virtual void UnPause( void ) {}
virtual float MixDryVolume( void ) { return 0; }
virtual bool Should3DMix( void ) { return m_bSurround; }
virtual void StopAllSounds( void ) {}
virtual int PaintBegin( float, int soundtime, int paintedtime ) { return 0; }
virtual void PaintEnd( void ) {}
virtual void SpatializeChannel( int volume[CCHANVOLUMES/2], int master_vol, const Vector& sourceDir, float gain, float mono );
virtual void ApplyDSPEffects( int idsp, portable_samplepair_t *pbuffront, portable_samplepair_t *pbufrear, portable_samplepair_t *pbufcenter, int samplecount );
virtual int GetOutputPosition( void ) { return 0; }
virtual void ClearBuffer( void ) {}
virtual void UpdateListener( const Vector& position, const Vector& forward, const Vector& right, const Vector& up ) {}
virtual void MixBegin( int sampleCount );
virtual void MixUpsample( int sampleCount, int filtertype );
virtual void Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress );
virtual void Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress );
virtual void Mix16Mono( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress );
virtual void Mix16Stereo( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress );
virtual void ChannelReset( int entnum, int channelIndex, float distanceMod ) {}
virtual void TransferSamples( int end ) {}
virtual const char *DeviceName( void ) { return NULL; }
virtual int DeviceChannels( void ) { return 0; }
virtual int DeviceSampleBits( void ) { return 0; }
virtual int DeviceSampleBytes( void ) { return 0; }
virtual int DeviceDmaSpeed( void ) { return 1; }
virtual int DeviceSampleCount( void ) { return 0; }
virtual bool IsSurround( void ) { return m_bSurround; }
virtual bool IsSurroundCenter( void ) { return m_bSurroundCenter; }
virtual bool IsHeadphone( void ) { return m_bHeadphone; }
bool m_bSurround;
bool m_bSurroundCenter;
bool m_bHeadphone;
};
#endif // SND_DEV_COMMON_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,14 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#ifndef SND_DEV_DIRECT_H
#define SND_DEV_DIRECT_H
#pragma once
class IAudioDevice;
IAudioDevice *Audio_CreateDirectSoundDevice( void );
#endif // SND_DEV_DIRECT_H

View File

@ -0,0 +1,599 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//===========================================================================//
#include "audio_pch.h"
#include <AudioToolbox/AudioQueue.h>
#include <AudioToolbox/AudioFile.h>
#include <AudioToolbox/AudioFormat.h>
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern bool snd_firsttime;
extern bool MIX_ScaleChannelVolume( paintbuffer_t *ppaint, channel_t *pChannel, int volume[CCHANVOLUMES], int mixchans );
extern void S_SpatializeChannel( int volume[6], int master_vol, const Vector *psourceDir, float gain, float mono );
#define NUM_BUFFERS_SOURCES 128
#define BUFF_MASK (NUM_BUFFERS_SOURCES - 1 )
#define BUFFER_SIZE 0x0400
//-----------------------------------------------------------------------------
//
// NOTE: This only allows 16-bit, stereo wave out
//
//-----------------------------------------------------------------------------
class CAudioDeviceAudioQueue : public CAudioDeviceBase
{
public:
bool IsActive( void );
bool Init( void );
void Shutdown( void );
void PaintEnd( void );
int GetOutputPosition( void );
void ChannelReset( int entnum, int channelIndex, float distanceMod );
void Pause( void );
void UnPause( void );
float MixDryVolume( void );
bool Should3DMix( void );
void StopAllSounds( void );
int PaintBegin( float mixAheadTime, int soundtime, int paintedtime );
void ClearBuffer( void );
void UpdateListener( const Vector& position, const Vector& forward, const Vector& right, const Vector& up );
void MixBegin( int sampleCount );
void MixUpsample( int sampleCount, int filtertype );
void Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress );
void Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress );
void Mix16Mono( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress );
void Mix16Stereo( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress );
void TransferSamples( int end );
void SpatializeChannel( int volume[CCHANVOLUMES/2], int master_vol, const Vector& sourceDir, float gain, float mono);
void ApplyDSPEffects( int idsp, portable_samplepair_t *pbuffront, portable_samplepair_t *pbufrear, portable_samplepair_t *pbufcenter, int samplecount );
const char *DeviceName( void ) { return "AudioQueue"; }
int DeviceChannels( void ) { return 2; }
int DeviceSampleBits( void ) { return 16; }
int DeviceSampleBytes( void ) { return 2; }
int DeviceDmaSpeed( void ) { return SOUND_DMA_SPEED; }
int DeviceSampleCount( void ) { return m_deviceSampleCount; }
void BufferCompleted() { m_buffersCompleted++; }
void SetRunning( bool bState ) { m_bRunning = bState; }
private:
void OpenWaveOut( void );
void CloseWaveOut( void );
bool ValidWaveOut( void ) const;
bool BIsPlaying();
AudioStreamBasicDescription m_DataFormat;
AudioQueueRef m_Queue;
AudioQueueBufferRef m_Buffers[NUM_BUFFERS_SOURCES];
int m_SndBufSize;
void *m_sndBuffers;
CInterlockedInt m_deviceSampleCount;
int m_buffersSent;
int m_buffersCompleted;
int m_pauseCount;
bool m_bSoundsShutdown;
bool m_bFailed;
bool m_bRunning;
};
CAudioDeviceAudioQueue *wave = NULL;
static void AudioCallback(void *pContext, AudioQueueRef pQueue, AudioQueueBufferRef pBuffer)
{
if ( wave )
wave->BufferCompleted();
}
IAudioDevice *Audio_CreateMacAudioQueueDevice( void )
{
wave = new CAudioDeviceAudioQueue;
if ( wave->Init() )
return wave;
delete wave;
wave = NULL;
return NULL;
}
void OnSndSurroundCvarChanged2( IConVar *pVar, const char *pOldString, float flOldValue );
void OnSndSurroundLegacyChanged2( IConVar *pVar, const char *pOldString, float flOldValue );
//-----------------------------------------------------------------------------
// Init, shutdown
//-----------------------------------------------------------------------------
bool CAudioDeviceAudioQueue::Init( void )
{
m_SndBufSize = 0;
m_sndBuffers = NULL;
m_pauseCount = 0;
m_bSurround = false;
m_bSurroundCenter = false;
m_bHeadphone = false;
m_buffersSent = 0;
m_buffersCompleted = 0;
m_pauseCount = 0;
m_bSoundsShutdown = false;
m_bFailed = false;
m_bRunning = false;
m_Queue = NULL;
static bool first = true;
if ( first )
{
snd_surround.SetValue( 2 );
snd_surround.InstallChangeCallback( &OnSndSurroundCvarChanged2 );
snd_legacy_surround.InstallChangeCallback( &OnSndSurroundLegacyChanged2 );
first = false;
}
OpenWaveOut();
if ( snd_firsttime )
{
DevMsg( "Wave sound initialized\n" );
}
return ValidWaveOut() && !m_bFailed;
}
void CAudioDeviceAudioQueue::Shutdown( void )
{
CloseWaveOut();
}
//-----------------------------------------------------------------------------
// WAV out device
//-----------------------------------------------------------------------------
inline bool CAudioDeviceAudioQueue::ValidWaveOut( void ) const
{
return m_sndBuffers != 0 && m_Queue;
}
//-----------------------------------------------------------------------------
// called by the mac audioqueue code when we run out of playback buffers
//-----------------------------------------------------------------------------
void AudioQueueIsRunningCallback( void* inClientData, AudioQueueRef inAQ, AudioQueuePropertyID inID)
{
CAudioDeviceAudioQueue* audioqueue = (CAudioDeviceAudioQueue*)inClientData;
UInt32 running = 0;
UInt32 size;
OSStatus err = AudioQueueGetProperty(inAQ, kAudioQueueProperty_IsRunning, &running, &size);
audioqueue->SetRunning( running != 0 );
//DevWarning( "AudioQueueStart %d\n", running );
}
//-----------------------------------------------------------------------------
// Opens the windows wave out device
//-----------------------------------------------------------------------------
void CAudioDeviceAudioQueue::OpenWaveOut( void )
{
if ( m_Queue )
return;
m_buffersSent = 0;
m_buffersCompleted = 0;
m_DataFormat.mSampleRate = 44100;
m_DataFormat.mFormatID = kAudioFormatLinearPCM;
m_DataFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger|kAudioFormatFlagIsPacked;
m_DataFormat.mBytesPerPacket = 4; // 16-bit samples * 2 channels
m_DataFormat.mFramesPerPacket = 1;
m_DataFormat.mBytesPerFrame = 4; // 16-bit samples * 2 channels
m_DataFormat.mChannelsPerFrame = 2;
m_DataFormat.mBitsPerChannel = 16;
m_DataFormat.mReserved = 0;
// Create the audio queue that will be used to manage the array of audio
// buffers used to queue samples.
OSStatus err = AudioQueueNewOutput(&m_DataFormat, AudioCallback, this, NULL, NULL, 0, &m_Queue);
if ( err != noErr)
{
DevMsg( "Failed to create AudioQueue output %d\n", (int)err );
m_bFailed = true;
return;
}
for ( int i = 0; i < NUM_BUFFERS_SOURCES; ++i)
{
err = AudioQueueAllocateBuffer( m_Queue, BUFFER_SIZE,&(m_Buffers[i]));
if ( err != noErr)
{
DevMsg( "Failed to AudioQueueAllocateBuffer output %d (%i)\n",(int)err,i );
m_bFailed = true;
}
m_Buffers[i]->mAudioDataByteSize = BUFFER_SIZE;
Q_memset( m_Buffers[i]->mAudioData, 0, BUFFER_SIZE );
}
err = AudioQueuePrime( m_Queue, 0, NULL);
if ( err != noErr)
{
DevMsg( "Failed to create AudioQueue output %d\n", (int)err );
m_bFailed = true;
return;
}
AudioQueueSetParameter( m_Queue, kAudioQueueParam_Volume, 1.0);
err = AudioQueueAddPropertyListener( m_Queue, kAudioQueueProperty_IsRunning, AudioQueueIsRunningCallback, this );
if ( err != noErr)
{
DevMsg( "Failed to create AudioQueue output %d\n", (int)err );
m_bFailed = true;
return;
}
m_SndBufSize = NUM_BUFFERS_SOURCES*BUFFER_SIZE;
m_deviceSampleCount = m_SndBufSize / DeviceSampleBytes();
if ( !m_sndBuffers )
{
m_sndBuffers = malloc( m_SndBufSize );
memset( m_sndBuffers, 0x0, m_SndBufSize );
}
}
//-----------------------------------------------------------------------------
// Closes the windows wave out device
//-----------------------------------------------------------------------------
void CAudioDeviceAudioQueue::CloseWaveOut( void )
{
if ( ValidWaveOut() )
{
AudioQueueStop(m_Queue, true);
m_bRunning = false;
AudioQueueRemovePropertyListener( m_Queue, kAudioQueueProperty_IsRunning, AudioQueueIsRunningCallback, this );
for ( int i = 0; i < NUM_BUFFERS_SOURCES; i++ )
AudioQueueFreeBuffer( m_Queue, m_Buffers[i]);
AudioQueueDispose( m_Queue, true);
m_Queue = NULL;
}
if ( m_sndBuffers )
{
free( m_sndBuffers );
m_sndBuffers = NULL;
}
}
//-----------------------------------------------------------------------------
// Mixing setup
//-----------------------------------------------------------------------------
int CAudioDeviceAudioQueue::PaintBegin( float mixAheadTime, int soundtime, int paintedtime )
{
// soundtime - total samples that have been played out to hardware at dmaspeed
// paintedtime - total samples that have been mixed at speed
// endtime - target for samples in mixahead buffer at speed
unsigned int endtime = soundtime + mixAheadTime * DeviceDmaSpeed();
int samps = DeviceSampleCount() >> (DeviceChannels()-1);
if ((int)(endtime - soundtime) > samps)
endtime = soundtime + samps;
if ((endtime - paintedtime) & 0x3)
{
// The difference between endtime and painted time should align on
// boundaries of 4 samples. This is important when upsampling from 11khz -> 44khz.
endtime -= (endtime - paintedtime) & 0x3;
}
return endtime;
}
//-----------------------------------------------------------------------------
// Actually performs the mixing
//-----------------------------------------------------------------------------
void CAudioDeviceAudioQueue::PaintEnd( void )
{
int cblocks = 4 << 1;
if ( m_bRunning && m_buffersSent == m_buffersCompleted )
{
// We are running the audio queue but have become starved of buffers.
// Stop the audio queue so we force a restart of it.
AudioQueueStop( m_Queue, true );
}
//
// submit a few new sound blocks
//
// 44K sound support
while (((m_buffersSent - m_buffersCompleted) >> SAMPLE_16BIT_SHIFT) < cblocks)
{
int iBuf = m_buffersSent&BUFF_MASK;
m_Buffers[iBuf]->mAudioDataByteSize = BUFFER_SIZE;
Q_memcpy( m_Buffers[iBuf]->mAudioData, (char *)m_sndBuffers + iBuf*BUFFER_SIZE, BUFFER_SIZE);
// Queue the buffer for playback.
OSStatus err = AudioQueueEnqueueBuffer( m_Queue, m_Buffers[iBuf], 0, NULL);
if ( err != noErr)
{
DevMsg( "Failed to AudioQueueEnqueueBuffer output %d\n", (int)err );
}
m_buffersSent++;
}
if ( !m_bRunning )
{
DevMsg( "Restarting sound playback\n" );
m_bRunning = true;
AudioQueueStart( m_Queue, NULL);
}
}
int CAudioDeviceAudioQueue::GetOutputPosition( void )
{
int s = m_buffersSent * BUFFER_SIZE;
s >>= SAMPLE_16BIT_SHIFT;
s &= (DeviceSampleCount()-1);
return s / DeviceChannels();
}
//-----------------------------------------------------------------------------
// Pausing
//-----------------------------------------------------------------------------
void CAudioDeviceAudioQueue::Pause( void )
{
m_pauseCount++;
if (m_pauseCount == 1)
{
m_bRunning = false;
AudioQueueStop(m_Queue, true);
}
}
void CAudioDeviceAudioQueue::UnPause( void )
{
if ( m_pauseCount > 0 )
{
m_pauseCount--;
}
if ( m_pauseCount == 0 )
{
m_bRunning = true;
AudioQueueStart( m_Queue, NULL);
}
}
bool CAudioDeviceAudioQueue::IsActive( void )
{
return ( m_pauseCount == 0 );
}
float CAudioDeviceAudioQueue::MixDryVolume( void )
{
return 0;
}
bool CAudioDeviceAudioQueue::Should3DMix( void )
{
return false;
}
void CAudioDeviceAudioQueue::ClearBuffer( void )
{
if ( !m_sndBuffers )
return;
Q_memset( m_sndBuffers, 0x0, DeviceSampleCount() * DeviceSampleBytes() );
}
void CAudioDeviceAudioQueue::UpdateListener( const Vector& position, const Vector& forward, const Vector& right, const Vector& up )
{
}
bool CAudioDeviceAudioQueue::BIsPlaying()
{
UInt32 isRunning;
UInt32 propSize = sizeof(isRunning);
OSStatus result = AudioQueueGetProperty( m_Queue, kAudioQueueProperty_IsRunning, &isRunning, &propSize);
return isRunning != 0;
}
void CAudioDeviceAudioQueue::MixBegin( int sampleCount )
{
MIX_ClearAllPaintBuffers( sampleCount, false );
}
void CAudioDeviceAudioQueue::MixUpsample( int sampleCount, int filtertype )
{
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
int ifilter = ppaint->ifilter;
Assert (ifilter < CPAINTFILTERS);
S_MixBufferUpsample2x( sampleCount, ppaint->pbuf, &(ppaint->fltmem[ifilter][0]), CPAINTFILTERMEM, filtertype );
ppaint->ifilter++;
}
void CAudioDeviceAudioQueue::Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
{
int volume[CCHANVOLUMES];
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 1))
return;
Mix8MonoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, (byte *)pData, inputOffset, rateScaleFix, outCount );
}
void CAudioDeviceAudioQueue::Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
{
int volume[CCHANVOLUMES];
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 2 ))
return;
Mix8StereoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, (byte *)pData, inputOffset, rateScaleFix, outCount );
}
void CAudioDeviceAudioQueue::Mix16Mono( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
{
int volume[CCHANVOLUMES];
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 1 ))
return;
Mix16MonoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, pData, inputOffset, rateScaleFix, outCount );
}
void CAudioDeviceAudioQueue::Mix16Stereo( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
{
int volume[CCHANVOLUMES];
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 2 ))
return;
Mix16StereoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, pData, inputOffset, rateScaleFix, outCount );
}
void CAudioDeviceAudioQueue::ChannelReset( int entnum, int channelIndex, float distanceMod )
{
}
void CAudioDeviceAudioQueue::TransferSamples( int end )
{
int lpaintedtime = g_paintedtime;
int endtime = end;
// resumes playback...
if ( m_sndBuffers )
{
S_TransferStereo16( m_sndBuffers, PAINTBUFFER, lpaintedtime, endtime );
}
}
void CAudioDeviceAudioQueue::SpatializeChannel( int volume[CCHANVOLUMES/2], int master_vol, const Vector& sourceDir, float gain, float mono )
{
VPROF("CAudioDeviceAudioQueue::SpatializeChannel");
S_SpatializeChannel( volume, master_vol, &sourceDir, gain, mono );
}
void CAudioDeviceAudioQueue::StopAllSounds( void )
{
m_bSoundsShutdown = true;
m_bRunning = false;
AudioQueueStop(m_Queue, true);
}
void CAudioDeviceAudioQueue::ApplyDSPEffects( int idsp, portable_samplepair_t *pbuffront, portable_samplepair_t *pbufrear, portable_samplepair_t *pbufcenter, int samplecount )
{
//SX_RoomFX( endtime, filter, timefx );
DSP_Process( idsp, pbuffront, pbufrear, pbufcenter, samplecount );
}
static uint32 GetOSXSpeakerConfig()
{
return 2;
}
static uint32 GetSpeakerConfigForSurroundMode( int surroundMode, const char **pConfigDesc )
{
uint32 newSpeakerConfig = 2;
*pConfigDesc = "stereo speaker";
return newSpeakerConfig;
}
void OnSndSurroundCvarChanged2( IConVar *pVar, const char *pOldString, float flOldValue )
{
// if the old value is -1, we're setting this from the detect routine for the first time
// no need to reset the device
if ( flOldValue == -1 )
return;
// get the user's previous speaker config
uint32 speaker_config = GetOSXSpeakerConfig();
// get the new config
uint32 newSpeakerConfig = 0;
const char *speakerConfigDesc = "";
ConVarRef var( pVar );
newSpeakerConfig = GetSpeakerConfigForSurroundMode( var.GetInt(), &speakerConfigDesc );
// make sure the config has changed
if (newSpeakerConfig == speaker_config)
return;
// set new configuration
//SetWindowsSpeakerConfig(newSpeakerConfig);
Msg("Speaker configuration has been changed to %s.\n", speakerConfigDesc);
// restart sound system so it takes effect
//g_pSoundServices->RestartSoundSystem();
}
void OnSndSurroundLegacyChanged2( IConVar *pVar, const char *pOldString, float flOldValue )
{
}

View File

@ -0,0 +1,14 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#ifndef SND_DEV_MAC_AUDIOQUEUE_H
#define SND_DEV_MAC_AUDIOQUEUE_H
#pragma once
class IAudioDevice;
IAudioDevice *Audio_CreateMacAudioQueueDevice( void );
#endif // SND_DEV_MAC_AUDIOQUEUE_H

View File

@ -0,0 +1,611 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//===========================================================================//
#include "audio_pch.h"
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#ifdef OSX
#include <OpenAL/MacOSX_OALExtensions.h>
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#ifndef DEDICATED // have to test this because VPC is forcing us to compile this file.
extern bool snd_firsttime;
extern bool MIX_ScaleChannelVolume( paintbuffer_t *ppaint, channel_t *pChannel, int volume[CCHANVOLUMES], int mixchans );
extern void S_SpatializeChannel( int volume[6], int master_vol, const Vector *psourceDir, float gain, float mono );
#define NUM_BUFFERS_SOURCES 128
#define BUFF_MASK (NUM_BUFFERS_SOURCES - 1 )
#define BUFFER_SIZE 0x0400
//-----------------------------------------------------------------------------
//
// NOTE: This only allows 16-bit, stereo wave out
//
//-----------------------------------------------------------------------------
class CAudioDeviceOpenAL : public CAudioDeviceBase
{
public:
bool IsActive( void );
bool Init( void );
void Shutdown( void );
void PaintEnd( void );
int GetOutputPosition( void );
void ChannelReset( int entnum, int channelIndex, float distanceMod );
void Pause( void );
void UnPause( void );
float MixDryVolume( void );
bool Should3DMix( void );
void StopAllSounds( void );
int PaintBegin( float mixAheadTime, int soundtime, int paintedtime );
void ClearBuffer( void );
void UpdateListener( const Vector& position, const Vector& forward, const Vector& right, const Vector& up );
void MixBegin( int sampleCount );
void MixUpsample( int sampleCount, int filtertype );
void Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress );
void Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress );
void Mix16Mono( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress );
void Mix16Stereo( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress );
void TransferSamples( int end );
void SpatializeChannel( int volume[CCHANVOLUMES/2], int master_vol, const Vector& sourceDir, float gain, float mono);
void ApplyDSPEffects( int idsp, portable_samplepair_t *pbuffront, portable_samplepair_t *pbufrear, portable_samplepair_t *pbufcenter, int samplecount );
const char *DeviceName( void ) { return "OpenAL"; }
int DeviceChannels( void ) { return 2; }
int DeviceSampleBits( void ) { return 16; }
int DeviceSampleBytes( void ) { return 2; }
int DeviceDmaSpeed( void ) { return SOUND_DMA_SPEED; }
int DeviceSampleCount( void ) { return m_deviceSampleCount; }
private:
void OpenWaveOut( void );
void CloseWaveOut( void );
bool ValidWaveOut( void ) const;
ALuint m_Buffer[NUM_BUFFERS_SOURCES];
ALuint m_Source[1];
int m_SndBufSize;
void *m_sndBuffers;
int m_deviceSampleCount;
int m_buffersSent;
int m_buffersCompleted;
int m_pauseCount;
bool m_bSoundsShutdown;
};
IAudioDevice *Audio_CreateOpenALDevice( void )
{
CAudioDeviceOpenAL *wave = NULL;
if ( !wave )
{
wave = new CAudioDeviceOpenAL;
}
if ( wave->Init() )
return wave;
delete wave;
wave = NULL;
return NULL;
}
void OnSndSurroundCvarChanged( IConVar *pVar, const char *pOldString, float flOldValue );
void OnSndSurroundLegacyChanged( IConVar *pVar, const char *pOldString, float flOldValue );
//-----------------------------------------------------------------------------
// Init, shutdown
//-----------------------------------------------------------------------------
bool CAudioDeviceOpenAL::Init( void )
{
m_SndBufSize = 0;
m_sndBuffers = NULL;
m_pauseCount = 0;
m_bSurround = false;
m_bSurroundCenter = false;
m_bHeadphone = false;
m_buffersSent = 0;
m_buffersCompleted = 0;
m_pauseCount = 0;
m_bSoundsShutdown = false;
static bool first = true;
if ( first )
{
snd_surround.SetValue( 2 );
snd_surround.InstallChangeCallback( &OnSndSurroundCvarChanged );
snd_legacy_surround.InstallChangeCallback( &OnSndSurroundLegacyChanged );
first = false;
}
OpenWaveOut();
if ( snd_firsttime )
{
DevMsg( "Wave sound initialized\n" );
}
return ValidWaveOut();
}
void CAudioDeviceOpenAL::Shutdown( void )
{
CloseWaveOut();
}
//-----------------------------------------------------------------------------
// WAV out device
//-----------------------------------------------------------------------------
inline bool CAudioDeviceOpenAL::ValidWaveOut( void ) const
{
return m_sndBuffers != 0;
}
//-----------------------------------------------------------------------------
// Opens the windows wave out device
//-----------------------------------------------------------------------------
void CAudioDeviceOpenAL::OpenWaveOut( void )
{
m_buffersSent = 0;
m_buffersCompleted = 0;
ALenum error;
ALCcontext *newContext = NULL;
ALCdevice *newDevice = NULL;
// Create a new OpenAL Device
// Pass NULL to specify the systemuse default output device
const ALCchar *initStr = (const ALCchar *)"\'( (sampling-rate 44100 ))";
newDevice = alcOpenDevice(initStr);
if (newDevice != NULL)
{
// Create a new OpenAL Context
// The new context will render to the OpenAL Device just created
ALCint attr[] = { ALC_FREQUENCY, DeviceDmaSpeed(), ALC_SYNC, AL_FALSE, 0 };
newContext = alcCreateContext(newDevice, attr );
if (newContext != NULL)
{
// Make the new context the Current OpenAL Context
alcMakeContextCurrent(newContext);
// Create some OpenAL Buffer Objects
alGenBuffers( NUM_BUFFERS_SOURCES, m_Buffer);
if((error = alGetError()) != AL_NO_ERROR)
{
DevMsg("Error Generating Buffers: ");
return;
}
// Create some OpenAL Source Objects
alGenSources(1, m_Source);
if(alGetError() != AL_NO_ERROR)
{
DevMsg("Error generating sources! \n");
return;
}
alListener3f( AL_POSITION,0.0f,0.0f,0.0f);
int i;
for ( i = 0; i < 1; i++ )
{
alSource3f( m_Source[i],AL_POSITION,0.0f,0.0f,0.0f );
alSourcef( m_Source[i], AL_PITCH, 1.0f );
alSourcef( m_Source[i], AL_GAIN, 1.0f );
}
}
}
m_SndBufSize = NUM_BUFFERS_SOURCES*BUFFER_SIZE;
m_deviceSampleCount = m_SndBufSize / DeviceSampleBytes();
if ( !m_sndBuffers )
{
m_sndBuffers = malloc( m_SndBufSize );
memset( m_sndBuffers, 0x0, m_SndBufSize );
}
}
//-----------------------------------------------------------------------------
// Closes the windows wave out device
//-----------------------------------------------------------------------------
void CAudioDeviceOpenAL::CloseWaveOut( void )
{
if ( ValidWaveOut() )
{
ALCcontext *context = NULL;
ALCdevice *device = NULL;
m_bSoundsShutdown = true;
alSourceStop( m_Source[0] );
// Delete the Sources
alDeleteSources(1, m_Source);
// Delete the Buffers
alDeleteBuffers(NUM_BUFFERS_SOURCES, m_Buffer);
//Get active context
context = alcGetCurrentContext();
//Get device for active context
device = alcGetContextsDevice(context);
alcMakeContextCurrent( NULL );
alcSuspendContext(context);
//Release context
alcDestroyContext(context);
//Close device
alcCloseDevice(device);
}
if ( m_sndBuffers )
{
free( m_sndBuffers );
m_sndBuffers = NULL;
}
}
//-----------------------------------------------------------------------------
// Mixing setup
//-----------------------------------------------------------------------------
int CAudioDeviceOpenAL::PaintBegin( float mixAheadTime, int soundtime, int paintedtime )
{
// soundtime - total samples that have been played out to hardware at dmaspeed
// paintedtime - total samples that have been mixed at speed
// endtime - target for samples in mixahead buffer at speed
unsigned int endtime = soundtime + mixAheadTime * DeviceDmaSpeed();
int samps = DeviceSampleCount() >> (DeviceChannels()-1);
if ((int)(endtime - soundtime) > samps)
endtime = soundtime + samps;
if ((endtime - paintedtime) & 0x3)
{
// The difference between endtime and painted time should align on
// boundaries of 4 samples. This is important when upsampling from 11khz -> 44khz.
endtime -= (endtime - paintedtime) & 0x3;
}
return endtime;
}
#ifdef OSX
ALvoid alBufferDataStaticProc(const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq)
{
static alBufferDataStaticProcPtr proc = NULL;
if (proc == NULL) {
proc = (alBufferDataStaticProcPtr) alGetProcAddress((const ALCchar*) "alBufferDataStatic");
}
if (proc)
proc(bid, format, data, size, freq);
}
#endif
//-----------------------------------------------------------------------------
// Actually performs the mixing
//-----------------------------------------------------------------------------
void CAudioDeviceOpenAL::PaintEnd( void )
{
if ( !m_sndBuffers /*|| m_bSoundsShutdown*/ )
return;
ALint state;
ALenum error;
int iloop;
int cblocks = 4 << 1;
ALint processed = 1;
ALuint lastUnqueuedBuffer = 0;
ALuint unqueuedBuffer = -1;
int nProcessedLoop = 200; // spin for a max of 200 times de-queing buffers, fixes a hang on exit
while ( processed > 0 && --nProcessedLoop > 0 )
{
alGetSourcei( m_Source[ 0 ], AL_BUFFERS_PROCESSED, &processed);
error = alGetError();
if (error != AL_NO_ERROR)
break;
if ( processed > 0 )
{
lastUnqueuedBuffer = unqueuedBuffer;
alSourceUnqueueBuffers( m_Source[ 0 ], 1, &unqueuedBuffer );
error = alGetError();
if ( error != AL_NO_ERROR && error != AL_INVALID_NAME )
{
DevMsg( "Error alSourceUnqueueBuffers %d\n", error );
break;
}
else
{
m_buffersCompleted++; // this buffer has been played
}
}
}
//
// submit a few new sound blocks
//
// 44K sound support
while (((m_buffersSent - m_buffersCompleted) >> SAMPLE_16BIT_SHIFT) < cblocks)
{
int iBuf = m_buffersSent&BUFF_MASK;
#ifdef OSX
alBufferDataStaticProc( m_Buffer[iBuf], AL_FORMAT_STEREO16, (char *)m_sndBuffers + iBuf*BUFFER_SIZE, BUFFER_SIZE, DeviceDmaSpeed() );
#else
alBufferData( m_Buffer[iBuf], AL_FORMAT_STEREO16, (char *)m_sndBuffers + iBuf*BUFFER_SIZE, BUFFER_SIZE, DeviceDmaSpeed() );
#endif
if ( (error = alGetError()) != AL_NO_ERROR )
{
DevMsg( "Error alBufferData %d %d\n", iBuf, error );
}
alSourceQueueBuffers( m_Source[0], 1, &m_Buffer[iBuf] );
if ( (error = alGetError() ) != AL_NO_ERROR )
{
DevMsg( "Error alSourceQueueBuffers %d %d\n", iBuf, error );
}
m_buffersSent++;
}
// make sure the stream is playing
alGetSourcei( m_Source[ 0 ], AL_SOURCE_STATE, &state);
if ( state != AL_PLAYING )
{
DevMsg( "Restarting sound playback\n" );
alSourcePlay( m_Source[0] );
if((error = alGetError()) != AL_NO_ERROR)
{
DevMsg( "Error alSourcePlay %d\n", error );
}
}
}
int CAudioDeviceOpenAL::GetOutputPosition( void )
{
int s = m_buffersSent * BUFFER_SIZE;
s >>= SAMPLE_16BIT_SHIFT;
s &= (DeviceSampleCount()-1);
return s / DeviceChannels();
}
//-----------------------------------------------------------------------------
// Pausing
//-----------------------------------------------------------------------------
void CAudioDeviceOpenAL::Pause( void )
{
m_pauseCount++;
if (m_pauseCount == 1)
{
alSourceStop( m_Source[0] );
}
}
void CAudioDeviceOpenAL::UnPause( void )
{
if ( m_pauseCount > 0 )
{
m_pauseCount--;
}
if ( m_pauseCount == 0 )
{
alSourcePlay( m_Source[0] );
}
}
bool CAudioDeviceOpenAL::IsActive( void )
{
return ( m_pauseCount == 0 );
}
float CAudioDeviceOpenAL::MixDryVolume( void )
{
return 0;
}
bool CAudioDeviceOpenAL::Should3DMix( void )
{
return false;
}
void CAudioDeviceOpenAL::ClearBuffer( void )
{
if ( !m_sndBuffers )
return;
Q_memset( m_sndBuffers, 0x0, DeviceSampleCount() * DeviceSampleBytes() );
}
void CAudioDeviceOpenAL::UpdateListener( const Vector& position, const Vector& forward, const Vector& right, const Vector& up )
{
}
void CAudioDeviceOpenAL::MixBegin( int sampleCount )
{
MIX_ClearAllPaintBuffers( sampleCount, false );
}
void CAudioDeviceOpenAL::MixUpsample( int sampleCount, int filtertype )
{
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
int ifilter = ppaint->ifilter;
Assert (ifilter < CPAINTFILTERS);
S_MixBufferUpsample2x( sampleCount, ppaint->pbuf, &(ppaint->fltmem[ifilter][0]), CPAINTFILTERMEM, filtertype );
ppaint->ifilter++;
}
void CAudioDeviceOpenAL::Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
{
int volume[CCHANVOLUMES];
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 1))
return;
Mix8MonoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, (byte *)pData, inputOffset, rateScaleFix, outCount );
}
void CAudioDeviceOpenAL::Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
{
int volume[CCHANVOLUMES];
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 2 ))
return;
Mix8StereoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, (byte *)pData, inputOffset, rateScaleFix, outCount );
}
void CAudioDeviceOpenAL::Mix16Mono( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
{
int volume[CCHANVOLUMES];
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 1 ))
return;
Mix16MonoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, pData, inputOffset, rateScaleFix, outCount );
}
void CAudioDeviceOpenAL::Mix16Stereo( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
{
int volume[CCHANVOLUMES];
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 2 ))
return;
Mix16StereoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, pData, inputOffset, rateScaleFix, outCount );
}
void CAudioDeviceOpenAL::ChannelReset( int entnum, int channelIndex, float distanceMod )
{
}
void CAudioDeviceOpenAL::TransferSamples( int end )
{
int lpaintedtime = g_paintedtime;
int endtime = end;
// resumes playback...
if ( m_sndBuffers )
{
S_TransferStereo16( m_sndBuffers, PAINTBUFFER, lpaintedtime, endtime );
}
}
void CAudioDeviceOpenAL::SpatializeChannel( int volume[CCHANVOLUMES/2], int master_vol, const Vector& sourceDir, float gain, float mono )
{
VPROF("CAudioDeviceOpenAL::SpatializeChannel");
S_SpatializeChannel( volume, master_vol, &sourceDir, gain, mono );
}
void CAudioDeviceOpenAL::StopAllSounds( void )
{
m_bSoundsShutdown = true;
alSourceStop( m_Source[0] );
}
void CAudioDeviceOpenAL::ApplyDSPEffects( int idsp, portable_samplepair_t *pbuffront, portable_samplepair_t *pbufrear, portable_samplepair_t *pbufcenter, int samplecount )
{
//SX_RoomFX( endtime, filter, timefx );
DSP_Process( idsp, pbuffront, pbufrear, pbufcenter, samplecount );
}
static uint32 GetOSXSpeakerConfig()
{
return 2;
}
static uint32 GetSpeakerConfigForSurroundMode( int surroundMode, const char **pConfigDesc )
{
uint32 newSpeakerConfig = 2;
*pConfigDesc = "stereo speaker";
return newSpeakerConfig;
}
void OnSndSurroundCvarChanged( IConVar *pVar, const char *pOldString, float flOldValue )
{
// if the old value is -1, we're setting this from the detect routine for the first time
// no need to reset the device
if ( flOldValue == -1 )
return;
// get the user's previous speaker config
uint32 speaker_config = GetOSXSpeakerConfig();
// get the new config
uint32 newSpeakerConfig = 0;
const char *speakerConfigDesc = "";
ConVarRef var( pVar );
newSpeakerConfig = GetSpeakerConfigForSurroundMode( var.GetInt(), &speakerConfigDesc );
// make sure the config has changed
if (newSpeakerConfig == speaker_config)
return;
// set new configuration
//SetWindowsSpeakerConfig(newSpeakerConfig);
Msg("Speaker configuration has been changed to %s.\n", speakerConfigDesc);
// restart sound system so it takes effect
//g_pSoundServices->RestartSoundSystem();
}
void OnSndSurroundLegacyChanged( IConVar *pVar, const char *pOldString, float flOldValue )
{
}
#endif // !DEDICATED

View File

@ -0,0 +1,14 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#ifndef SND_DEV_OPENAL_H
#define SND_DEV_OPENAL_H
#pragma once
class IAudioDevice;
IAudioDevice *Audio_CreateOpenALDevice( void );
#endif // SND_DEV_OPENAL_H

View File

@ -0,0 +1,574 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//===========================================================================//
#include "audio_pch.h"
#if !DEDICATED
#include "tier0/dynfunction.h"
#include "video//ivideoservices.h"
#include "../../sys_dll.h"
// prevent some conflicts in SDL headers...
#undef M_PI
#include <stdint.h>
#ifndef _STDINT_H_
#define _STDINT_H_ 1
#endif
#include "SDL.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern bool snd_firsttime;
extern bool MIX_ScaleChannelVolume( paintbuffer_t *ppaint, channel_t *pChannel, int volume[CCHANVOLUMES], int mixchans );
extern void S_SpatializeChannel( /*int nSlot,*/ int volume[6], int master_vol, const Vector *psourceDir, float gain, float mono );
// 64K is about 1/3 second at 16-bit, stereo, 44100 Hz
// 44k: UNDONE - need to double buffers now that we're playing back at 44100?
#define WAV_BUFFERS 64
#define WAV_MASK (WAV_BUFFERS - 1)
#define WAV_BUFFER_SIZE 0x0400
#if 0
#define debugsdl printf
#else
static inline void debugsdl(const char *fmt, ...) {}
#endif
//-----------------------------------------------------------------------------
//
// NOTE: This only allows 16-bit, stereo wave out (!!! FIXME: but SDL supports 7.1, etc, too!)
//
//-----------------------------------------------------------------------------
class CAudioDeviceSDLAudio : public CAudioDeviceBase
{
public:
CAudioDeviceSDLAudio();
virtual ~CAudioDeviceSDLAudio();
bool IsActive( void );
bool Init( void );
void Shutdown( void );
void PaintEnd( void );
int GetOutputPosition( void );
void ChannelReset( int entnum, int channelIndex, float distanceMod );
void Pause( void );
void UnPause( void );
float MixDryVolume( void );
bool Should3DMix( void );
void StopAllSounds( void );
int PaintBegin( float mixAheadTime, int soundtime, int paintedtime );
void ClearBuffer( void );
void MixBegin( int sampleCount );
void MixUpsample( int sampleCount, int filtertype );
void Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress );
void Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress );
void Mix16Mono( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress );
void Mix16Stereo( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress );
void TransferSamples( int end );
void SpatializeChannel( int nSlot, int volume[CCHANVOLUMES/2], int master_vol, const Vector& sourceDir, float gain, float mono);
void ApplyDSPEffects( int idsp, portable_samplepair_t *pbuffront, portable_samplepair_t *pbufrear, portable_samplepair_t *pbufcenter, int samplecount );
const char *DeviceName( void ) { return "SDL"; }
int DeviceChannels( void ) { return 2; }
int DeviceSampleBits( void ) { return 16; }
int DeviceSampleBytes( void ) { return 2; }
int DeviceDmaSpeed( void ) { return SOUND_DMA_SPEED; }
int DeviceSampleCount( void ) { return m_deviceSampleCount; }
private:
SDL_AudioDeviceID m_devId;
static void SDLCALL AudioCallbackEntry(void *userdata, Uint8 * stream, int len);
void AudioCallback(Uint8 *stream, int len);
void OpenWaveOut( void );
void CloseWaveOut( void );
void AllocateOutputBuffers();
void FreeOutputBuffers();
bool ValidWaveOut( void ) const;
int m_deviceSampleCount;
int m_buffersSent;
int m_pauseCount;
int m_readPos;
int m_partialWrite;
// Memory for the wave data
uint8_t *m_pBuffer;
};
static CAudioDeviceSDLAudio *g_wave = NULL;
//-----------------------------------------------------------------------------
// Constructor (just lookup SDL entry points, real work happens in this->Init())
//-----------------------------------------------------------------------------
CAudioDeviceSDLAudio::CAudioDeviceSDLAudio()
{
m_devId = 0;
}
//-----------------------------------------------------------------------------
// Destructor. Make sure our global pointer gets set to NULL.
//-----------------------------------------------------------------------------
CAudioDeviceSDLAudio::~CAudioDeviceSDLAudio()
{
g_wave = NULL;
}
//-----------------------------------------------------------------------------
// Class factory
//-----------------------------------------------------------------------------
IAudioDevice *Audio_CreateSDLAudioDevice( void )
{
if ( !g_wave )
{
g_wave = new CAudioDeviceSDLAudio;
Assert( g_wave );
}
if ( g_wave && !g_wave->Init() )
{
delete g_wave;
g_wave = NULL;
}
return g_wave;
}
//-----------------------------------------------------------------------------
// Init, shutdown
//-----------------------------------------------------------------------------
bool CAudioDeviceSDLAudio::Init( void )
{
// If we've already got a device open, then return. This allows folks to call
// Audio_CreateSDLAudioDevice() multiple times. CloseWaveOut() will free the
// device, and set m_devId to 0.
if( m_devId )
return true;
m_bSurround = false;
m_bSurroundCenter = false;
m_bHeadphone = false;
m_buffersSent = 0;
m_pauseCount = 0;
m_pBuffer = NULL;
m_readPos = 0;
m_partialWrite = 0;
m_devId = 0;
OpenWaveOut();
if ( snd_firsttime )
{
DevMsg( "Wave sound initialized\n" );
}
return ValidWaveOut();
}
void CAudioDeviceSDLAudio::Shutdown( void )
{
CloseWaveOut();
}
//-----------------------------------------------------------------------------
// WAV out device
//-----------------------------------------------------------------------------
inline bool CAudioDeviceSDLAudio::ValidWaveOut( void ) const
{
return m_devId != 0;
}
//-----------------------------------------------------------------------------
// Opens the windows wave out device
//-----------------------------------------------------------------------------
void CAudioDeviceSDLAudio::OpenWaveOut( void )
{
debugsdl("SDLAUDIO: OpenWaveOut...\n");
#ifndef WIN32
char appname[ 256 ];
KeyValues *modinfo = new KeyValues( "ModInfo" );
if ( modinfo->LoadFromFile( g_pFileSystem, "gameinfo.txt" ) )
Q_strncpy( appname, modinfo->GetString( "game" ), sizeof( appname ) );
else
Q_strncpy( appname, "Source1 Game", sizeof( appname ) );
modinfo->deleteThis();
modinfo = NULL;
// Set these environment variables, in case we're using PulseAudio.
setenv("PULSE_PROP_application.name", appname, 1);
setenv("PULSE_PROP_media.role", "game", 1);
#endif
// !!! FIXME: specify channel map, etc
// !!! FIXME: set properties (role, icon, etc).
//#define SDLAUDIO_FAIL(fnstr) do { DevWarning(fnstr " failed"); CloseWaveOut(); return; } while (false)
//#define SDLAUDIO_FAIL(fnstr) do { printf("SDLAUDIO: " fnstr " failed: %s\n", SDL_GetError ? SDL_GetError() : "???"); CloseWaveOut(); return; } while (false)
#define SDLAUDIO_FAIL(fnstr) do { const char *err = SDL_GetError(); printf("SDLAUDIO: " fnstr " failed: %s\n", err ? err : "???"); CloseWaveOut(); return; } while (false)
if (!SDL_WasInit(SDL_INIT_AUDIO))
{
if (SDL_InitSubSystem(SDL_INIT_AUDIO))
SDLAUDIO_FAIL("SDL_InitSubSystem(SDL_INIT_AUDIO)");
}
debugsdl("SDLAUDIO: Using SDL audio target '%s'\n", SDL_GetCurrentAudioDriver());
// Open an audio device...
// !!! FIXME: let user specify a device?
// !!! FIXME: we can handle quad, 5.1, 7.1, etc here.
SDL_AudioSpec desired, obtained;
memset(&desired, '\0', sizeof (desired));
desired.freq = SOUND_DMA_SPEED;
desired.format = AUDIO_S16SYS;
desired.channels = 2;
desired.samples = 2048;
desired.callback = &CAudioDeviceSDLAudio::AudioCallbackEntry;
desired.userdata = this;
m_devId = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, SDL_AUDIO_ALLOW_ANY_CHANGE);
if (!m_devId)
SDLAUDIO_FAIL("SDL_OpenAudioDevice()");
#undef SDLAUDIO_FAIL
// We're now ready to feed audio data to SDL!
AllocateOutputBuffers();
SDL_PauseAudioDevice(m_devId, 0);
#if defined( BINK_VIDEO ) && defined( LINUX )
// Tells Bink to use SDL for its audio decoding
if ( g_pVideo != NULL)
{
g_pVideo->SoundDeviceCommand( VideoSoundDeviceOperation::SET_SDL_PARAMS, NULL, (void *)&obtained );
}
#endif
}
//-----------------------------------------------------------------------------
// Closes the windows wave out device
//-----------------------------------------------------------------------------
void CAudioDeviceSDLAudio::CloseWaveOut( void )
{
// none of these SDL_* functions are available to call if this is false.
if (m_devId)
{
SDL_CloseAudioDevice(m_devId);
m_devId = 0;
}
SDL_QuitSubSystem(SDL_INIT_AUDIO);
FreeOutputBuffers();
}
//-----------------------------------------------------------------------------
// Allocate output buffers
//-----------------------------------------------------------------------------
void CAudioDeviceSDLAudio::AllocateOutputBuffers()
{
// Allocate and lock memory for the waveform data.
const int nBufferSize = WAV_BUFFER_SIZE * WAV_BUFFERS;
m_pBuffer = new uint8_t[nBufferSize];
memset(m_pBuffer, '\0', nBufferSize);
m_readPos = 0;
m_partialWrite = 0;
m_deviceSampleCount = nBufferSize / DeviceSampleBytes();
}
//-----------------------------------------------------------------------------
// Free output buffers
//-----------------------------------------------------------------------------
void CAudioDeviceSDLAudio::FreeOutputBuffers()
{
delete[] m_pBuffer;
m_pBuffer = NULL;
}
//-----------------------------------------------------------------------------
// Mixing setup
//-----------------------------------------------------------------------------
int CAudioDeviceSDLAudio::PaintBegin( float mixAheadTime, int soundtime, int paintedtime )
{
// soundtime - total samples that have been played out to hardware at dmaspeed
// paintedtime - total samples that have been mixed at speed
// endtime - target for samples in mixahead buffer at speed
unsigned int endtime = soundtime + mixAheadTime * DeviceDmaSpeed();
int samps = DeviceSampleCount() >> (DeviceChannels()-1);
if ((int)(endtime - soundtime) > samps)
endtime = soundtime + samps;
if ((endtime - paintedtime) & 0x3)
{
// The difference between endtime and painted time should align on
// boundaries of 4 samples. This is important when upsampling from 11khz -> 44khz.
endtime -= (endtime - paintedtime) & 0x3;
}
return endtime;
}
void CAudioDeviceSDLAudio::AudioCallbackEntry(void *userdata, Uint8 *stream, int len)
{
((CAudioDeviceSDLAudio *) userdata)->AudioCallback(stream, len);
}
void CAudioDeviceSDLAudio::AudioCallback(Uint8 *stream, int len)
{
if (!m_devId)
{
debugsdl("SDLAUDIO: uhoh, no audio device!\n");
return; // can this even happen?
}
const int totalWriteable = len;
#if defined( BINK_VIDEO ) && defined( LINUX )
Uint8 *stream_orig = stream;
#endif
debugsdl("SDLAUDIO: writable size is %d.\n", totalWriteable);
Assert(len <= (WAV_BUFFERS * WAV_BUFFER_SIZE));
while (len > 0)
{
// spaceAvailable == bytes before we overrun the end of the ring buffer.
const int spaceAvailable = ((WAV_BUFFERS * WAV_BUFFER_SIZE) - m_readPos);
const int writeLen = (len < spaceAvailable) ? len : spaceAvailable;
if (writeLen > 0)
{
const uint8_t *buf = m_pBuffer + m_readPos;
debugsdl("SDLAUDIO: Writing %d bytes...\n", writeLen);
#if 0
static FILE *io = NULL;
if (io == NULL) io = fopen("dumpplayback.raw", "wb");
if (io != NULL) { fwrite(buf, writeLen, 1, io); fflush(io); }
#endif
memcpy(stream, buf, writeLen);
stream += writeLen;
len -= writeLen;
Assert(len >= 0);
}
m_readPos = len ? 0 : (m_readPos + writeLen); // if still bytes to write to stream, we're rolling around the ring buffer.
}
#if defined( BINK_VIDEO ) && defined( LINUX )
// Mix in Bink movie audio if that stuff is playing.
if ( g_pVideo != NULL)
{
g_pVideo->SoundDeviceCommand( VideoSoundDeviceOperation::SDLMIXER_CALLBACK, (void *)stream_orig, (void *)&totalWriteable );
}
#endif
// Translate between bytes written and buffers written.
m_partialWrite += totalWriteable;
m_buffersSent += m_partialWrite / WAV_BUFFER_SIZE;
m_partialWrite %= WAV_BUFFER_SIZE;
}
//-----------------------------------------------------------------------------
// Actually performs the mixing
//-----------------------------------------------------------------------------
void CAudioDeviceSDLAudio::PaintEnd( void )
{
debugsdl("SDLAUDIO: PaintEnd...\n");
#if 0 // !!! FIXME: this is the 1.3 headers, but not implemented yet in SDL.
if (SDL_AudioDeviceConnected(m_devId) != 1)
{
debugsdl("SDLAUDIO: Audio device was disconnected!\n");
Shutdown();
}
#endif
}
int CAudioDeviceSDLAudio::GetOutputPosition( void )
{
return (m_readPos >> SAMPLE_16BIT_SHIFT)/DeviceChannels();
}
//-----------------------------------------------------------------------------
// Pausing
//-----------------------------------------------------------------------------
void CAudioDeviceSDLAudio::Pause( void )
{
m_pauseCount++;
if (m_pauseCount == 1)
{
debugsdl("SDLAUDIO: PAUSE\n");
SDL_PauseAudioDevice(m_devId, 1);
}
}
void CAudioDeviceSDLAudio::UnPause( void )
{
if ( m_pauseCount > 0 )
{
m_pauseCount--;
if (m_pauseCount == 0)
{
debugsdl("SDLAUDIO: UNPAUSE\n");
SDL_PauseAudioDevice(m_devId, 0);
}
}
}
bool CAudioDeviceSDLAudio::IsActive( void )
{
return ( m_pauseCount == 0 );
}
float CAudioDeviceSDLAudio::MixDryVolume( void )
{
return 0;
}
bool CAudioDeviceSDLAudio::Should3DMix( void )
{
return false;
}
void CAudioDeviceSDLAudio::ClearBuffer( void )
{
int clear;
if ( !m_pBuffer )
return;
clear = 0;
Q_memset(m_pBuffer, clear, DeviceSampleCount() * DeviceSampleBytes() );
}
void CAudioDeviceSDLAudio::MixBegin( int sampleCount )
{
MIX_ClearAllPaintBuffers( sampleCount, false );
}
void CAudioDeviceSDLAudio::MixUpsample( int sampleCount, int filtertype )
{
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
int ifilter = ppaint->ifilter;
Assert (ifilter < CPAINTFILTERS);
S_MixBufferUpsample2x( sampleCount, ppaint->pbuf, &(ppaint->fltmem[ifilter][0]), CPAINTFILTERMEM, filtertype );
ppaint->ifilter++;
}
void CAudioDeviceSDLAudio::Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
{
int volume[CCHANVOLUMES];
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 1))
return;
Mix8MonoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, (byte *)pData, inputOffset, rateScaleFix, outCount );
}
void CAudioDeviceSDLAudio::Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
{
int volume[CCHANVOLUMES];
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 2 ))
return;
Mix8StereoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, (byte *)pData, inputOffset, rateScaleFix, outCount );
}
void CAudioDeviceSDLAudio::Mix16Mono( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
{
int volume[CCHANVOLUMES];
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 1 ))
return;
Mix16MonoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, pData, inputOffset, rateScaleFix, outCount );
}
void CAudioDeviceSDLAudio::Mix16Stereo( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
{
int volume[CCHANVOLUMES];
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 2 ))
return;
Mix16StereoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, pData, inputOffset, rateScaleFix, outCount );
}
void CAudioDeviceSDLAudio::ChannelReset( int entnum, int channelIndex, float distanceMod )
{
}
void CAudioDeviceSDLAudio::TransferSamples( int end )
{
int lpaintedtime = g_paintedtime;
int endtime = end;
// resumes playback...
if ( m_pBuffer )
{
S_TransferStereo16( m_pBuffer, PAINTBUFFER, lpaintedtime, endtime );
}
}
void CAudioDeviceSDLAudio::SpatializeChannel( int nSlot, int volume[CCHANVOLUMES/2], int master_vol, const Vector& sourceDir, float gain, float mono )
{
VPROF("CAudioDeviceSDLAudio::SpatializeChannel");
S_SpatializeChannel( /*nSlot,*/ volume, master_vol, &sourceDir, gain, mono );
}
void CAudioDeviceSDLAudio::StopAllSounds( void )
{
}
void CAudioDeviceSDLAudio::ApplyDSPEffects( int idsp, portable_samplepair_t *pbuffront, portable_samplepair_t *pbufrear, portable_samplepair_t *pbufcenter, int samplecount )
{
//SX_RoomFX( endtime, filter, timefx );
DSP_Process( idsp, pbuffront, pbufrear, pbufcenter, samplecount );
}
#endif // !DEDICATED

View File

@ -0,0 +1,16 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#ifndef SND_DEV_SDL_H
#define SND_DEV_SDL_H
#pragma once
class IAudioDevice;
IAudioDevice *Audio_CreateSDLAudioDevice( void );
#endif // SND_DEV_SDL_H

View File

@ -0,0 +1,570 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//===========================================================================//
#include "audio_pch.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern bool snd_firsttime;
extern bool MIX_ScaleChannelVolume( paintbuffer_t *ppaint, channel_t *pChannel, int volume[CCHANVOLUMES], int mixchans );
extern void S_SpatializeChannel( int volume[6], int master_vol, const Vector *psourceDir, float gain, float mono );
// 64K is > 1 second at 16-bit, 22050 Hz
// 44k: UNDONE - need to double buffers now that we're playing back at 44100?
#define WAV_BUFFERS 64
#define WAV_MASK 0x3F
#define WAV_BUFFER_SIZE 0x0400
//-----------------------------------------------------------------------------
//
// NOTE: This only allows 16-bit, stereo wave out
//
//-----------------------------------------------------------------------------
class CAudioDeviceWave : public CAudioDeviceBase
{
public:
bool IsActive( void );
bool Init( void );
void Shutdown( void );
void PaintEnd( void );
int GetOutputPosition( void );
void ChannelReset( int entnum, int channelIndex, float distanceMod );
void Pause( void );
void UnPause( void );
float MixDryVolume( void );
bool Should3DMix( void );
void StopAllSounds( void );
int PaintBegin( float mixAheadTime, int soundtime, int paintedtime );
void ClearBuffer( void );
void UpdateListener( const Vector& position, const Vector& forward, const Vector& right, const Vector& up );
void MixBegin( int sampleCount );
void MixUpsample( int sampleCount, int filtertype );
void Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress );
void Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress );
void Mix16Mono( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress );
void Mix16Stereo( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress );
void TransferSamples( int end );
void SpatializeChannel( int volume[CCHANVOLUMES/2], int master_vol, const Vector& sourceDir, float gain, float mono);
void ApplyDSPEffects( int idsp, portable_samplepair_t *pbuffront, portable_samplepair_t *pbufrear, portable_samplepair_t *pbufcenter, int samplecount );
const char *DeviceName( void ) { return "Windows WAVE"; }
int DeviceChannels( void ) { return 2; }
int DeviceSampleBits( void ) { return 16; }
int DeviceSampleBytes( void ) { return 2; }
int DeviceDmaSpeed( void ) { return SOUND_DMA_SPEED; }
int DeviceSampleCount( void ) { return m_deviceSampleCount; }
private:
void OpenWaveOut( void );
void CloseWaveOut( void );
void AllocateOutputBuffers();
void FreeOutputBuffers();
void* AllocOutputMemory( int nSize, HGLOBAL &hMemory );
void FreeOutputMemory( HGLOBAL &hMemory );
bool ValidWaveOut( void ) const;
int m_deviceSampleCount;
int m_buffersSent;
int m_buffersCompleted;
int m_pauseCount;
// This is a single allocation for all wave headers (there are OUTPUT_BUFFER_COUNT of them)
HGLOBAL m_hWaveHdr;
// This is a single allocation for all wave data (there are OUTPUT_BUFFER_COUNT of them)
HANDLE m_hWaveData;
HWAVEOUT m_waveOutHandle;
// Memory for the wave data + wave headers
void *m_pBuffer;
LPWAVEHDR m_pWaveHdr;
};
//-----------------------------------------------------------------------------
// Class factory
//-----------------------------------------------------------------------------
IAudioDevice *Audio_CreateWaveDevice( void )
{
CAudioDeviceWave *wave = NULL;
if ( !wave )
{
wave = new CAudioDeviceWave;
}
if ( wave->Init() )
return wave;
delete wave;
wave = NULL;
return NULL;
}
//-----------------------------------------------------------------------------
// Init, shutdown
//-----------------------------------------------------------------------------
bool CAudioDeviceWave::Init( void )
{
m_bSurround = false;
m_bSurroundCenter = false;
m_bHeadphone = false;
m_buffersSent = 0;
m_buffersCompleted = 0;
m_pauseCount = 0;
m_waveOutHandle = 0;
m_pBuffer = NULL;
m_pWaveHdr = NULL;
m_hWaveHdr = NULL;
m_hWaveData = NULL;
OpenWaveOut();
if ( snd_firsttime )
{
DevMsg( "Wave sound initialized\n" );
}
return ValidWaveOut();
}
void CAudioDeviceWave::Shutdown( void )
{
CloseWaveOut();
}
//-----------------------------------------------------------------------------
// WAV out device
//-----------------------------------------------------------------------------
inline bool CAudioDeviceWave::ValidWaveOut( void ) const
{
return m_waveOutHandle != 0;
}
//-----------------------------------------------------------------------------
// Opens the windows wave out device
//-----------------------------------------------------------------------------
void CAudioDeviceWave::OpenWaveOut( void )
{
WAVEFORMATEX waveFormat;
memset( &waveFormat, 0, sizeof(waveFormat) );
// Select a PCM, 16-bit stereo playback device
waveFormat.cbSize = sizeof(waveFormat);
waveFormat.wFormatTag = WAVE_FORMAT_PCM;
waveFormat.nChannels = DeviceChannels();
waveFormat.wBitsPerSample = DeviceSampleBits();
waveFormat.nSamplesPerSec = DeviceDmaSpeed(); // DeviceSampleRate
waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8;
waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
MMRESULT errorCode = waveOutOpen( &m_waveOutHandle, WAVE_MAPPER, &waveFormat, 0, 0L, CALLBACK_NULL );
while ( errorCode != MMSYSERR_NOERROR )
{
if ( errorCode != MMSYSERR_ALLOCATED )
{
DevWarning( "waveOutOpen failed\n" );
m_waveOutHandle = 0;
return;
}
int nRetVal = MessageBox( NULL,
"The sound hardware is in use by another app.\n\n"
"Select Retry to try to start sound again or Cancel to run with no sound.",
"Sound not available",
MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION);
if ( nRetVal != IDRETRY )
{
DevWarning( "waveOutOpen failure--hardware already in use\n" );
m_waveOutHandle = 0;
return;
}
errorCode = waveOutOpen( &m_waveOutHandle, WAVE_MAPPER, &waveFormat, 0, 0L, CALLBACK_NULL );
}
AllocateOutputBuffers();
}
//-----------------------------------------------------------------------------
// Closes the windows wave out device
//-----------------------------------------------------------------------------
void CAudioDeviceWave::CloseWaveOut( void )
{
if ( ValidWaveOut() )
{
waveOutReset( m_waveOutHandle );
FreeOutputBuffers();
waveOutClose( m_waveOutHandle );
m_waveOutHandle = NULL;
}
}
//-----------------------------------------------------------------------------
// Alloc output memory
//-----------------------------------------------------------------------------
void* CAudioDeviceWave::AllocOutputMemory( int nSize, HGLOBAL &hMemory )
{
// Output memory for waveform data+hdrs must be
// globally allocated with GMEM_MOVEABLE and GMEM_SHARE flags.
hMemory = GlobalAlloc( GMEM_MOVEABLE | GMEM_SHARE, nSize );
if ( !hMemory )
{
DevWarning( "Sound: Out of memory.\n");
CloseWaveOut();
return NULL;
}
HPSTR lpData = (char *)GlobalLock( hMemory );
if ( !lpData )
{
DevWarning( "Sound: Failed to lock.\n");
GlobalFree( hMemory );
hMemory = NULL;
CloseWaveOut();
return NULL;
}
memset( lpData, 0, nSize );
return lpData;
}
//-----------------------------------------------------------------------------
// Free output memory
//-----------------------------------------------------------------------------
void CAudioDeviceWave::FreeOutputMemory( HGLOBAL &hMemory )
{
if ( hMemory )
{
GlobalUnlock( hMemory );
GlobalFree( hMemory );
hMemory = NULL;
}
}
//-----------------------------------------------------------------------------
// Allocate output buffers
//-----------------------------------------------------------------------------
void CAudioDeviceWave::AllocateOutputBuffers()
{
// Allocate and lock memory for the waveform data.
int nBufferSize = WAV_BUFFER_SIZE * WAV_BUFFERS;
HPSTR lpData = (char *)AllocOutputMemory( nBufferSize, m_hWaveData );
if ( !lpData )
return;
// Allocate and lock memory for the waveform header
int nHdrSize = sizeof( WAVEHDR ) * WAV_BUFFERS;
LPWAVEHDR lpWaveHdr = (LPWAVEHDR)AllocOutputMemory( nHdrSize, m_hWaveHdr );
if ( !lpWaveHdr )
return;
// After allocation, set up and prepare headers.
for ( int i=0 ; i < WAV_BUFFERS; i++ )
{
LPWAVEHDR lpHdr = lpWaveHdr + i;
lpHdr->dwBufferLength = WAV_BUFFER_SIZE;
lpHdr->lpData = lpData + (i * WAV_BUFFER_SIZE);
MMRESULT nResult = waveOutPrepareHeader( m_waveOutHandle, lpHdr, sizeof(WAVEHDR) );
if ( nResult != MMSYSERR_NOERROR )
{
DevWarning( "Sound: failed to prepare wave headers\n" );
CloseWaveOut();
return;
}
}
m_deviceSampleCount = nBufferSize / DeviceSampleBytes();
m_pBuffer = (void *)lpData;
m_pWaveHdr = lpWaveHdr;
}
//-----------------------------------------------------------------------------
// Free output buffers
//-----------------------------------------------------------------------------
void CAudioDeviceWave::FreeOutputBuffers()
{
// Unprepare headers.
if ( m_pWaveHdr )
{
for ( int i=0 ; i < WAV_BUFFERS; i++ )
{
waveOutUnprepareHeader( m_waveOutHandle, m_pWaveHdr+i, sizeof(WAVEHDR) );
}
}
m_pWaveHdr = NULL;
m_pBuffer = NULL;
FreeOutputMemory( m_hWaveData );
FreeOutputMemory( m_hWaveHdr );
}
//-----------------------------------------------------------------------------
// Mixing setup
//-----------------------------------------------------------------------------
int CAudioDeviceWave::PaintBegin( float mixAheadTime, int soundtime, int paintedtime )
{
// soundtime - total samples that have been played out to hardware at dmaspeed
// paintedtime - total samples that have been mixed at speed
// endtime - target for samples in mixahead buffer at speed
unsigned int endtime = soundtime + mixAheadTime * DeviceDmaSpeed();
int samps = DeviceSampleCount() >> (DeviceChannels()-1);
if ((int)(endtime - soundtime) > samps)
endtime = soundtime + samps;
if ((endtime - paintedtime) & 0x3)
{
// The difference between endtime and painted time should align on
// boundaries of 4 samples. This is important when upsampling from 11khz -> 44khz.
endtime -= (endtime - paintedtime) & 0x3;
}
return endtime;
}
//-----------------------------------------------------------------------------
// Actually performs the mixing
//-----------------------------------------------------------------------------
void CAudioDeviceWave::PaintEnd( void )
{
LPWAVEHDR h;
int wResult;
int cblocks;
//
// find which sound blocks have completed
//
while (1)
{
if ( m_buffersCompleted == m_buffersSent )
{
//DevMsg ("Sound overrun\n");
break;
}
if ( ! (m_pWaveHdr[ m_buffersCompleted & WAV_MASK].dwFlags & WHDR_DONE) )
{
break;
}
m_buffersCompleted++; // this buffer has been played
}
//
// submit a few new sound blocks
//
// 22K sound support
// 44k: UNDONE - double blocks out now that we're at 44k playback?
cblocks = 4 << 1;
while (((m_buffersSent - m_buffersCompleted) >> SAMPLE_16BIT_SHIFT) < cblocks)
{
h = m_pWaveHdr + ( m_buffersSent&WAV_MASK );
m_buffersSent++;
/*
* Now the data block can be sent to the output device. The
* waveOutWrite function returns immediately and waveform
* data is sent to the output device in the background.
*/
wResult = waveOutWrite( m_waveOutHandle, h, sizeof(WAVEHDR) );
if (wResult != MMSYSERR_NOERROR)
{
Warning( "Failed to write block to device\n");
Shutdown();
return;
}
}
}
int CAudioDeviceWave::GetOutputPosition( void )
{
int s = m_buffersSent * WAV_BUFFER_SIZE;
s >>= SAMPLE_16BIT_SHIFT;
s &= (DeviceSampleCount()-1);
return s / DeviceChannels();
}
//-----------------------------------------------------------------------------
// Pausing
//-----------------------------------------------------------------------------
void CAudioDeviceWave::Pause( void )
{
m_pauseCount++;
if (m_pauseCount == 1)
{
waveOutReset( m_waveOutHandle );
}
}
void CAudioDeviceWave::UnPause( void )
{
if ( m_pauseCount > 0 )
{
m_pauseCount--;
}
}
bool CAudioDeviceWave::IsActive( void )
{
return ( m_pauseCount == 0 );
}
float CAudioDeviceWave::MixDryVolume( void )
{
return 0;
}
bool CAudioDeviceWave::Should3DMix( void )
{
return false;
}
void CAudioDeviceWave::ClearBuffer( void )
{
int clear;
if ( !m_pBuffer )
return;
clear = 0;
Q_memset(m_pBuffer, clear, DeviceSampleCount() * DeviceSampleBytes() );
}
void CAudioDeviceWave::UpdateListener( const Vector& position, const Vector& forward, const Vector& right, const Vector& up )
{
}
void CAudioDeviceWave::MixBegin( int sampleCount )
{
MIX_ClearAllPaintBuffers( sampleCount, false );
}
void CAudioDeviceWave::MixUpsample( int sampleCount, int filtertype )
{
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
int ifilter = ppaint->ifilter;
Assert (ifilter < CPAINTFILTERS);
S_MixBufferUpsample2x( sampleCount, ppaint->pbuf, &(ppaint->fltmem[ifilter][0]), CPAINTFILTERMEM, filtertype );
ppaint->ifilter++;
}
void CAudioDeviceWave::Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
{
int volume[CCHANVOLUMES];
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 1))
return;
Mix8MonoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, (byte *)pData, inputOffset, rateScaleFix, outCount );
}
void CAudioDeviceWave::Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
{
int volume[CCHANVOLUMES];
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 2 ))
return;
Mix8StereoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, (byte *)pData, inputOffset, rateScaleFix, outCount );
}
void CAudioDeviceWave::Mix16Mono( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
{
int volume[CCHANVOLUMES];
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 1 ))
return;
Mix16MonoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, pData, inputOffset, rateScaleFix, outCount );
}
void CAudioDeviceWave::Mix16Stereo( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
{
int volume[CCHANVOLUMES];
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 2 ))
return;
Mix16StereoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, pData, inputOffset, rateScaleFix, outCount );
}
void CAudioDeviceWave::ChannelReset( int entnum, int channelIndex, float distanceMod )
{
}
void CAudioDeviceWave::TransferSamples( int end )
{
int lpaintedtime = g_paintedtime;
int endtime = end;
// resumes playback...
if ( m_pBuffer )
{
S_TransferStereo16( m_pBuffer, PAINTBUFFER, lpaintedtime, endtime );
}
}
void CAudioDeviceWave::SpatializeChannel( int volume[CCHANVOLUMES/2], int master_vol, const Vector& sourceDir, float gain, float mono )
{
VPROF("CAudioDeviceWave::SpatializeChannel");
S_SpatializeChannel( volume, master_vol, &sourceDir, gain, mono );
}
void CAudioDeviceWave::StopAllSounds( void )
{
}
void CAudioDeviceWave::ApplyDSPEffects( int idsp, portable_samplepair_t *pbuffront, portable_samplepair_t *pbufrear, portable_samplepair_t *pbufcenter, int samplecount )
{
//SX_RoomFX( endtime, filter, timefx );
DSP_Process( idsp, pbuffront, pbufrear, pbufcenter, samplecount );
}

View File

@ -0,0 +1,14 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#ifndef SND_DEV_WAVE_H
#define SND_DEV_WAVE_H
#pragma once
class IAudioDevice;
IAudioDevice *Audio_CreateWaveDevice( void );
#endif // SND_DEV_WAVE_H

View File

@ -0,0 +1,872 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: X360 XAudio Version
//
//=====================================================================================//
#include "audio_pch.h"
#include "snd_dev_xaudio.h"
#include "UtlLinkedList.h"
#include "session.h"
#include "server.h"
#include "client.h"
#include "matchmaking.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// The outer code mixes in PAINTBUFFER_SIZE (# of samples) chunks (see MIX_PaintChannels), we will never need more than
// that many samples in a buffer. This ends up being about 20ms per buffer
#define XAUDIO2_BUFFER_SAMPLES 8192
// buffer return has a latency, so need a decent pool
#define MAX_XAUDIO2_BUFFERS 32
#define SURROUND_HEADPHONES 0
#define SURROUND_STEREO 2
#define SURROUND_DIGITAL5DOT1 5
// 5.1 means there are a max of 6 channels
#define MAX_DEVICE_CHANNELS 6
ConVar snd_xaudio_spew_packets( "snd_xaudio_spew_packets", "0", 0, "Spew XAudio packet delivery" );
//-----------------------------------------------------------------------------
// Implementation of XAudio
//-----------------------------------------------------------------------------
class CAudioXAudio : public CAudioDeviceBase
{
public:
~CAudioXAudio( void );
bool IsActive( void ) { return true; }
bool Init( void );
void Shutdown( void );
void Pause( void );
void UnPause( void );
int PaintBegin( float mixAheadTime, int soundtime, int paintedtime );
int GetOutputPosition( void );
void ClearBuffer( void );
void TransferSamples( int end );
const char *DeviceName( void );
int DeviceChannels( void ) { return m_deviceChannels; }
int DeviceSampleBits( void ) { return m_deviceSampleBits; }
int DeviceSampleBytes( void ) { return m_deviceSampleBits/8; }
int DeviceDmaSpeed( void ) { return m_deviceDmaSpeed; }
int DeviceSampleCount( void ) { return m_deviceSampleCount; }
void XAudioPacketCallback( int hCompletedBuffer );
static CAudioXAudio *m_pSingleton;
CXboxVoice *GetVoiceData( void ) { return &m_VoiceData; }
IXAudio2 *GetXAudio2( void ) { return m_pXAudio2; }
private:
int TransferStereo( const portable_samplepair_t *pFront, int paintedTime, int endTime, char *pOutptuBuffer );
int TransferSurroundInterleaved( const portable_samplepair_t *pFront, const portable_samplepair_t *pRear, const portable_samplepair_t *pCenter, int paintedTime, int endTime, char *pOutputBuffer );
int m_deviceChannels; // channels per hardware output buffer (1 for quad/5.1, 2 for stereo)
int m_deviceSampleBits; // bits per sample (16)
int m_deviceSampleCount; // count of mono samples in output buffer
int m_deviceDmaSpeed; // samples per second per output buffer
int m_clockDivider;
IXAudio2 *m_pXAudio2;
IXAudio2MasteringVoice *m_pMasteringVoice;
IXAudio2SourceVoice *m_pSourceVoice;
XAUDIO2_BUFFER m_Buffers[MAX_XAUDIO2_BUFFERS];
BYTE *m_pOutputBuffer;
int m_bufferSizeBytes; // size of a single hardware output buffer, in bytes
CInterlockedUInt m_BufferTail;
CInterlockedUInt m_BufferHead;
CXboxVoice m_VoiceData;
};
CAudioXAudio *CAudioXAudio::m_pSingleton = NULL;
//-----------------------------------------------------------------------------
// XAudio packet completion callback
//-----------------------------------------------------------------------------
class XAudio2VoiceCallback : public IXAudio2VoiceCallback
{
public:
XAudio2VoiceCallback() {}
~XAudio2VoiceCallback() {}
void OnStreamEnd() {}
void OnVoiceProcessingPassEnd() {}
void OnVoiceProcessingPassStart( UINT32 SamplesRequired ) {}
void OnBufferEnd( void *pBufferContext )
{
CAudioXAudio::m_pSingleton->XAudioPacketCallback( (int)pBufferContext );
}
void OnBufferStart( void *pBufferContext ) {}
void OnLoopEnd( void *pBufferContext ) {}
void OnVoiceError( void *pBufferContext, HRESULT Error ) {}
};
XAudio2VoiceCallback s_XAudio2VoiceCallback;
//-----------------------------------------------------------------------------
// Create XAudio Device
//-----------------------------------------------------------------------------
IAudioDevice *Audio_CreateXAudioDevice( void )
{
MEM_ALLOC_CREDIT();
if ( !CAudioXAudio::m_pSingleton )
{
CAudioXAudio::m_pSingleton = new CAudioXAudio;
}
if ( !CAudioXAudio::m_pSingleton->Init() )
{
delete CAudioXAudio::m_pSingleton;
CAudioXAudio::m_pSingleton = NULL;
}
return CAudioXAudio::m_pSingleton;
}
CXboxVoice *Audio_GetXVoice( void )
{
if ( CAudioXAudio::m_pSingleton )
{
return CAudioXAudio::m_pSingleton->GetVoiceData();
}
return NULL;
}
IXAudio2 *Audio_GetXAudio2( void )
{
if ( CAudioXAudio::m_pSingleton )
{
return CAudioXAudio::m_pSingleton->GetXAudio2();
}
return NULL;
}
//-----------------------------------------------------------------------------
// Destructor
//-----------------------------------------------------------------------------
CAudioXAudio::~CAudioXAudio( void )
{
m_pSingleton = NULL;
}
//-----------------------------------------------------------------------------
// Initialize XAudio
//-----------------------------------------------------------------------------
bool CAudioXAudio::Init( void )
{
XAUDIOSPEAKERCONFIG xAudioConfig = 0;
XAudioGetSpeakerConfig( &xAudioConfig );
snd_surround.SetValue( ( xAudioConfig & XAUDIOSPEAKERCONFIG_DIGITAL_DOLBYDIGITAL ) ? SURROUND_DIGITAL5DOT1 : SURROUND_STEREO );
m_bHeadphone = false;
m_bSurround = false;
m_bSurroundCenter = false;
switch ( snd_surround.GetInt() )
{
case SURROUND_HEADPHONES:
m_bHeadphone = true;
m_deviceChannels = 2;
break;
default:
case SURROUND_STEREO:
m_deviceChannels = 2;
break;
case SURROUND_DIGITAL5DOT1:
m_bSurround = true;
m_bSurroundCenter = true;
m_deviceChannels = 6;
break;
}
m_deviceSampleBits = 16;
m_deviceDmaSpeed = SOUND_DMA_SPEED;
// initialize the XAudio Engine
// Both threads on core 2
m_pXAudio2 = NULL;
HRESULT hr = XAudio2Create( &m_pXAudio2, 0, XboxThread5 );
if ( FAILED( hr ) )
return false;
// create the mastering voice, this will upsample to the devices target hw output rate
m_pMasteringVoice = NULL;
hr = m_pXAudio2->CreateMasteringVoice( &m_pMasteringVoice );
if ( FAILED( hr ) )
return false;
// 16 bit PCM
WAVEFORMATEX waveFormatEx = { 0 };
waveFormatEx.wFormatTag = WAVE_FORMAT_PCM;
waveFormatEx.nChannels = m_deviceChannels;
waveFormatEx.nSamplesPerSec = m_deviceDmaSpeed;
waveFormatEx.wBitsPerSample = 16;
waveFormatEx.nBlockAlign = ( waveFormatEx.nChannels * waveFormatEx.wBitsPerSample ) / 8;
waveFormatEx.nAvgBytesPerSec = waveFormatEx.nSamplesPerSec * waveFormatEx.nBlockAlign;
waveFormatEx.cbSize = 0;
m_pSourceVoice = NULL;
hr = m_pXAudio2->CreateSourceVoice(
&m_pSourceVoice,
&waveFormatEx,
0,
XAUDIO2_DEFAULT_FREQ_RATIO,
&s_XAudio2VoiceCallback,
NULL,
NULL );
if ( FAILED( hr ) )
return false;
float volumes[MAX_DEVICE_CHANNELS];
for ( int i = 0; i < MAX_DEVICE_CHANNELS; i++ )
{
if ( !m_bSurround && i >= 2 )
{
volumes[i] = 0;
}
else
{
volumes[i] = 1.0;
}
}
m_pSourceVoice->SetChannelVolumes( m_deviceChannels, volumes );
m_bufferSizeBytes = XAUDIO2_BUFFER_SAMPLES * (m_deviceSampleBits/8) * m_deviceChannels;
m_pOutputBuffer = new BYTE[MAX_XAUDIO2_BUFFERS * m_bufferSizeBytes];
ClearBuffer();
V_memset( m_Buffers, 0, MAX_XAUDIO2_BUFFERS * sizeof( XAUDIO2_BUFFER ) );
for ( int i = 0; i < MAX_XAUDIO2_BUFFERS; i++ )
{
m_Buffers[i].pAudioData = m_pOutputBuffer + i*m_bufferSizeBytes;
m_Buffers[i].pContext = (LPVOID)i;
}
m_BufferHead = 0;
m_BufferTail = 0;
// number of mono samples output buffer may hold
m_deviceSampleCount = MAX_XAUDIO2_BUFFERS * (m_bufferSizeBytes/(DeviceSampleBytes()));
// NOTE: This really shouldn't be tied to the # of bufferable samples.
// This just needs to be large enough so that it doesn't fake out the sampling in
// GetSoundTime(). Basically GetSoundTime() assumes a cyclical time stamp and finds wraparound cases
// but that means it needs to get called much more often than once per cycle. So this number should be
// much larger than the framerate in terms of output time
m_clockDivider = m_deviceSampleCount / DeviceChannels();
// not really part of XAudio2, but mixer xma lacks one-time init, so doing it here
XMAPlaybackInitialize();
hr = m_pSourceVoice->Start( 0 );
if ( FAILED( hr ) )
return false;
DevMsg( "XAudio Device Initialized:\n" );
DevMsg( " %s\n"
" %d channel(s)\n"
" %d bits/sample\n"
" %d samples/sec\n", DeviceName(), DeviceChannels(), DeviceSampleBits(), DeviceDmaSpeed() );
m_VoiceData.VoiceInit();
// success
return true;
}
//-----------------------------------------------------------------------------
// Shutdown XAudio
//-----------------------------------------------------------------------------
void CAudioXAudio::Shutdown( void )
{
if ( m_pSourceVoice )
{
m_pSourceVoice->Stop( 0 );
m_pSourceVoice->DestroyVoice();
m_pSourceVoice = NULL;
delete[] m_pOutputBuffer;
}
if ( m_pMasteringVoice )
{
m_pMasteringVoice->DestroyVoice();
m_pMasteringVoice = NULL;
}
// need to release ref to XAudio2
m_VoiceData.VoiceShutdown();
if ( m_pXAudio2 )
{
m_pXAudio2->Release();
m_pXAudio2 = NULL;
}
if ( this == CAudioXAudio::m_pSingleton )
{
CAudioXAudio::m_pSingleton = NULL;
}
}
//-----------------------------------------------------------------------------
// XAudio has completed a packet. Assuming these are sequential
//-----------------------------------------------------------------------------
void CAudioXAudio::XAudioPacketCallback( int hCompletedBuffer )
{
// packet completion expected to be sequential
Assert( hCompletedBuffer == (int)( m_PacketTail % MAX_XAUDIO2_BUFFERS ) );
m_BufferTail++;
if ( snd_xaudio_spew_packets.GetBool() )
{
if ( m_BufferTail == m_BufferHead )
{
Warning( "XAudio: Starved\n" );
}
else
{
Msg( "XAudio: Packet Callback, Submit: %2d, Free: %2d\n", m_BufferHead - m_BufferTail, MAX_XAUDIO2_BUFFERS - ( m_BufferHead - m_BufferTail ) );
}
}
if ( m_BufferTail == m_BufferHead )
{
// very bad, out of packets, xaudio is starving
// mix thread didn't keep up with audio clock and submit packets
// submit a silent buffer to keep stream playing and audio clock running
int head = m_BufferHead++;
XAUDIO2_BUFFER *pBuffer = &m_Buffers[head % MAX_XAUDIO2_BUFFERS];
V_memset( pBuffer->pAudioData, 0, m_bufferSizeBytes );
pBuffer->AudioBytes = m_bufferSizeBytes;
m_pSourceVoice->SubmitSourceBuffer( pBuffer );
}
}
//-----------------------------------------------------------------------------
// Return the "write" cursor. Used to clock the audio mixing.
// The actual hw write cursor and the number of samples it fetches is unknown.
//-----------------------------------------------------------------------------
int CAudioXAudio::GetOutputPosition( void )
{
XAUDIO2_VOICE_STATE state;
state.SamplesPlayed = 0;
m_pSourceVoice->GetState( &state );
return ( state.SamplesPlayed % m_clockDivider );
}
//-----------------------------------------------------------------------------
// Pause playback
//-----------------------------------------------------------------------------
void CAudioXAudio::Pause( void )
{
if ( m_pSourceVoice )
{
m_pSourceVoice->Stop( 0 );
}
}
//-----------------------------------------------------------------------------
// Resume playback
//-----------------------------------------------------------------------------
void CAudioXAudio::UnPause( void )
{
if ( m_pSourceVoice )
{
m_pSourceVoice->Start( 0 );
}
}
//-----------------------------------------------------------------------------
// Calc the paint position
//-----------------------------------------------------------------------------
int CAudioXAudio::PaintBegin( float mixAheadTime, int soundtime, int paintedtime )
{
// soundtime = total full samples that have been played out to hardware at dmaspeed
// paintedtime = total full samples that have been mixed at speed
// endtime = target for full samples in mixahead buffer at speed
int mixaheadtime = mixAheadTime * DeviceDmaSpeed();
int endtime = soundtime + mixaheadtime;
if ( endtime <= paintedtime )
{
return endtime;
}
int fullsamps = DeviceSampleCount() / DeviceChannels();
if ( ( endtime - soundtime ) > fullsamps )
{
endtime = soundtime + fullsamps;
}
if ( ( endtime - paintedtime ) & 0x03 )
{
// The difference between endtime and painted time should align on
// boundaries of 4 samples. This is important when upsampling from 11khz -> 44khz.
endtime -= ( endtime - paintedtime ) & 0x03;
}
return endtime;
}
//-----------------------------------------------------------------------------
// Fill the output buffers with silence
//-----------------------------------------------------------------------------
void CAudioXAudio::ClearBuffer( void )
{
V_memset( m_pOutputBuffer, 0, MAX_XAUDIO2_BUFFERS * m_bufferSizeBytes );
}
//-----------------------------------------------------------------------------
// Fill the output buffer with L/R samples
//-----------------------------------------------------------------------------
int CAudioXAudio::TransferStereo( const portable_samplepair_t *pFrontBuffer, int paintedTime, int endTime, char *pOutputBuffer )
{
int linearCount;
int i;
int val;
int volumeFactor = S_GetMasterVolume() * 256;
int *pFront = (int *)pFrontBuffer;
short *pOutput = (short *)pOutputBuffer;
// get size of output buffer in full samples (LR pairs)
// number of sequential sample pairs that can be wrriten
linearCount = g_AudioDevice->DeviceSampleCount() >> 1;
// clamp output count to requested number of samples
if ( linearCount > endTime - paintedTime )
{
linearCount = endTime - paintedTime;
}
// linearCount is now number of mono 16 bit samples (L and R) to xfer.
linearCount <<= 1;
// transfer mono 16bit samples multiplying each sample by volume.
for ( i=0; i<linearCount; i+=2 )
{
// L Channel
val = ( pFront[i] * volumeFactor ) >> 8;
*pOutput++ = CLIP( val );
// R Channel
val = ( pFront[i+1] * volumeFactor ) >> 8;
*pOutput++ = CLIP( val );
}
return linearCount * DeviceSampleBytes();
}
//-----------------------------------------------------------------------------
// Fill the output buffer with interleaved surround samples
//-----------------------------------------------------------------------------
int CAudioXAudio::TransferSurroundInterleaved( const portable_samplepair_t *pFrontBuffer, const portable_samplepair_t *pRearBuffer, const portable_samplepair_t *pCenterBuffer, int paintedTime, int endTime, char *pOutputBuffer )
{
int linearCount;
int i, j;
int val;
int volumeFactor = S_GetMasterVolume() * 256;
int *pFront = (int *)pFrontBuffer;
int *pRear = (int *)pRearBuffer;
int *pCenter = (int *)pCenterBuffer;
short *pOutput = (short *)pOutputBuffer;
// number of mono samples per channel
// number of sequential samples that can be wrriten
linearCount = m_bufferSizeBytes/( DeviceSampleBytes() * DeviceChannels() );
// clamp output count to requested number of samples
if ( linearCount > endTime - paintedTime )
{
linearCount = endTime - paintedTime;
}
for ( i = 0, j = 0; i < linearCount; i++, j += 2 )
{
// FL
val = ( pFront[j] * volumeFactor ) >> 8;
*pOutput++ = CLIP( val );
// FR
val = ( pFront[j+1] * volumeFactor ) >> 8;
*pOutput++ = CLIP( val );
// Center
val = ( pCenter[j] * volumeFactor) >> 8;
*pOutput++ = CLIP( val );
// Let the hardware mix the sub from the main channels since
// we don't have any sub-specific sounds, or direct sub-addressing
*pOutput++ = 0;
// RL
val = ( pRear[j] * volumeFactor ) >> 8;
*pOutput++ = CLIP( val );
// RR
val = ( pRear[j+1] * volumeFactor ) >> 8;
*pOutput++ = CLIP( val );
}
return linearCount * DeviceSampleBytes() * DeviceChannels();
}
//-----------------------------------------------------------------------------
// Transfer up to a full paintbuffer (PAINTBUFFER_SIZE) of samples out to the xaudio buffer(s).
//-----------------------------------------------------------------------------
void CAudioXAudio::TransferSamples( int endTime )
{
XAUDIO2_BUFFER *pBuffer;
if ( m_BufferHead - m_BufferTail >= MAX_XAUDIO2_BUFFERS )
{
DevWarning( "XAudio: No Free Buffers!\n" );
return;
}
int sampleCount = endTime - g_paintedtime;
if ( sampleCount > XAUDIO2_BUFFER_SAMPLES )
{
DevWarning( "XAudio: Overflowed mix buffer!\n" );
endTime = g_paintedtime + XAUDIO2_BUFFER_SAMPLES;
}
unsigned int nBuffer = m_BufferHead++;
pBuffer = &m_Buffers[nBuffer % MAX_XAUDIO2_BUFFERS];
if ( !m_bSurround )
{
pBuffer->AudioBytes = TransferStereo( PAINTBUFFER, g_paintedtime, endTime, (char *)pBuffer->pAudioData );
}
else
{
pBuffer->AudioBytes = TransferSurroundInterleaved( PAINTBUFFER, REARPAINTBUFFER, CENTERPAINTBUFFER, g_paintedtime, endTime, (char *)pBuffer->pAudioData );
}
// submit buffer
m_pSourceVoice->SubmitSourceBuffer( pBuffer );
}
//-----------------------------------------------------------------------------
// Get our device name
//-----------------------------------------------------------------------------
const char *CAudioXAudio::DeviceName( void )
{
if ( m_bSurround )
{
return "XAudio: 5.1 Channel Surround";
}
return "XAudio: Stereo";
}
CXboxVoice::CXboxVoice()
{
m_pXHVEngine = NULL;
}
//-----------------------------------------------------------------------------
// Initialize Voice
//-----------------------------------------------------------------------------
void CXboxVoice::VoiceInit( void )
{
if ( !m_pXHVEngine )
{
// Set the processing modes
XHV_PROCESSING_MODE rgMode = XHV_VOICECHAT_MODE;
// Set up parameters for the voice chat engine
XHV_INIT_PARAMS xhvParams = {0};
xhvParams.dwMaxRemoteTalkers = MAX_PLAYERS;
xhvParams.dwMaxLocalTalkers = XUSER_MAX_COUNT;
xhvParams.localTalkerEnabledModes = &rgMode;
xhvParams.remoteTalkerEnabledModes = &rgMode;
xhvParams.dwNumLocalTalkerEnabledModes = 1;
xhvParams.dwNumRemoteTalkerEnabledModes = 1;
xhvParams.pXAudio2 = CAudioXAudio::m_pSingleton->GetXAudio2();
// Create the engine
HRESULT hr = XHV2CreateEngine( &xhvParams, NULL, &m_pXHVEngine );
if ( hr != S_OK )
{
Warning( "Couldn't load XHV engine!\n" );
}
}
VoiceResetLocalData( );
}
void CXboxVoice::VoiceShutdown( void )
{
if ( !m_pXHVEngine )
return;
m_pXHVEngine->Release();
m_pXHVEngine = NULL;
}
void CXboxVoice::AddPlayerToVoiceList( CClientInfo *pClient, bool bLocal )
{
XHV_PROCESSING_MODE local_proc_mode = XHV_VOICECHAT_MODE;
for ( int i = 0; i < pClient->m_cPlayers; ++i )
{
if ( pClient->m_xuids[i] == 0 )
continue;
if ( bLocal == true )
{
if ( m_pXHVEngine->RegisterLocalTalker( pClient->m_iControllers[i] ) == S_OK )
{
m_pXHVEngine->StartLocalProcessingModes( pClient->m_iControllers[i], &local_proc_mode, 1 );
}
}
else
{
if ( m_pXHVEngine->RegisterRemoteTalker( pClient->m_xuids[i], NULL, NULL, NULL ) == S_OK )
{
m_pXHVEngine->StartRemoteProcessingModes( pClient->m_xuids[i], &local_proc_mode, 1 );
}
}
}
}
void CXboxVoice::RemovePlayerFromVoiceList( CClientInfo *pClient, bool bLocal )
{
for ( int i = 0; i < pClient->m_cPlayers; ++i )
{
if ( pClient->m_xuids[i] == 0 )
continue;
if ( bLocal == true )
{
m_pXHVEngine->UnregisterLocalTalker( pClient->m_iControllers[i] );
}
else
{
m_pXHVEngine->UnregisterRemoteTalker( pClient->m_xuids[i] );
}
}
}
void CXboxVoice::PlayIncomingVoiceData( XUID xuid, const byte *pbData, DWORD pdwSize )
{
XUID localXUID;
XUserGetXUID( XBX_GetPrimaryUserId(), &localXUID );
//Hack: Don't play stuff that comes from ourselves.
if ( localXUID == xuid )
return;
m_pXHVEngine->SubmitIncomingChatData( xuid, pbData, &pdwSize );
}
void CXboxVoice::UpdateHUDVoiceStatus( void )
{
for ( int iClient = 0; iClient < cl.m_nMaxClients; iClient++ )
{
bool bSelf = (cl.m_nPlayerSlot == iClient);
int iIndex = iClient + 1;
XUID id = g_pMatchmaking->PlayerIdToXuid( iIndex );
if ( id != 0 )
{
bool bTalking = false;
if ( bSelf == true )
{
//Make sure the player's own label is not on.
g_pSoundServices->OnChangeVoiceStatus( iIndex, false );
iIndex = -1;
if ( IsPlayerTalking( XBX_GetPrimaryUserId(), true ) )
{
bTalking = true;
}
}
else
{
if ( IsPlayerTalking( id, false ) )
{
bTalking = true;
}
}
g_pSoundServices->OnChangeVoiceStatus( iIndex, bTalking );
}
else
{
g_pSoundServices->OnChangeVoiceStatus( iIndex, false );
}
}
}
bool CXboxVoice::VoiceUpdateData( void )
{
DWORD dwNumPackets = 0;
DWORD dwBytes = 0;
WORD wVoiceBytes = 0;
bool bShouldSend = false;
DWORD dwVoiceFlags = m_pXHVEngine->GetDataReadyFlags();
//Update UI stuff.
UpdateHUDVoiceStatus();
for ( uint i = 0; i < XUSER_MAX_COUNT; ++i )
{
// We currently only allow one player per console
if ( i != XBX_GetPrimaryUserId() )
{
continue;
}
if ( IsHeadsetPresent( i ) == false )
continue;
if ( !(dwVoiceFlags & ( 1 << i )) )
continue;
dwBytes = m_ChatBufferSize - m_wLocalDataSize;
if( dwBytes < XHV_VOICECHAT_MODE_PACKET_SIZE )
{
bShouldSend = true;
}
else
{
m_pXHVEngine->GetLocalChatData( i, m_ChatBuffer + m_wLocalDataSize, &dwBytes, &dwNumPackets );
m_wLocalDataSize += ((WORD)dwBytes) & MAXWORD;
if( m_wLocalDataSize > ( ( m_ChatBufferSize * 7 ) / 10 ) )
{
bShouldSend = true;
}
}
wVoiceBytes += m_wLocalDataSize & MAXWORD;
break;
}
return bShouldSend ||
( wVoiceBytes &&
( GetTickCount() - m_dwLastVoiceSend ) > MAX_VOICE_BUFFER_TIME );
}
void CXboxVoice::SetPlaybackPriority( XUID remoteTalker, DWORD dwUserIndex, XHV_PLAYBACK_PRIORITY playbackPriority )
{
m_pXHVEngine->SetPlaybackPriority( remoteTalker, dwUserIndex, playbackPriority );
}
void CXboxVoice::GetRemoteTalkers( int *pNumTalkers, XUID *pRemoteTalkers )
{
m_pXHVEngine->GetRemoteTalkers( (DWORD*)pNumTalkers, pRemoteTalkers );
}
void CXboxVoice::GetVoiceData( CLC_VoiceData *pMessage )
{
byte *puchVoiceData = NULL;
pMessage->m_nLength = m_wLocalDataSize;
XUserGetXUID( XBX_GetPrimaryUserId(), &pMessage->m_xuid );
puchVoiceData = m_ChatBuffer;
pMessage->m_DataOut.StartWriting( puchVoiceData, pMessage->m_nLength );
pMessage->m_nLength *= 8;
pMessage->m_DataOut.SeekToBit( pMessage->m_nLength ); // set correct writing position
}
void CXboxVoice::VoiceSendData( INetChannel *pChannel )
{
CLC_VoiceData voiceMsg;
GetVoiceData( &voiceMsg );
if ( pChannel )
{
pChannel->SendNetMsg( voiceMsg, false, true );
VoiceResetLocalData();
}
}
void CXboxVoice::VoiceResetLocalData( void )
{
m_wLocalDataSize = 0;
m_dwLastVoiceSend = GetTickCount();
Q_memset( m_ChatBuffer, 0, m_ChatBufferSize );
}
bool CXboxVoice::IsPlayerTalking( XUID uid, bool bLocal )
{
if ( bLocal == true )
{
return m_pXHVEngine->IsLocalTalking( XBX_GetPrimaryUserId() );
}
else
{
return !g_pMatchmaking->IsPlayerMuted( XBX_GetPrimaryUserId(), uid ) && m_pXHVEngine->IsRemoteTalking( uid );
}
return false;
}
bool CXboxVoice::IsHeadsetPresent( int id )
{
return m_pXHVEngine->IsHeadsetPresent( id );
}
void CXboxVoice::RemoveAllTalkers( CClientInfo *pLocal )
{
int numRemoteTalkers;
XUID remoteTalkers[MAX_PLAYERS];
GetRemoteTalkers( &numRemoteTalkers, remoteTalkers );
for ( int iRemote = 0; iRemote < numRemoteTalkers; iRemote++ )
{
m_pXHVEngine->UnregisterRemoteTalker( remoteTalkers[iRemote] );
}
if ( pLocal )
{
for ( int i = 0; i < pLocal->m_cPlayers; ++i )
{
if ( pLocal->m_xuids[i] == 0 )
continue;
m_pXHVEngine->UnregisterLocalTalker( pLocal->m_iControllers[i] );
}
}
}

View File

@ -0,0 +1,64 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#ifndef SND_DEV_XAUDIO_H
#define SND_DEV_XAUDIO_H
#pragma once
#include "audio_pch.h"
#include "inetmessage.h"
#include "netmessages.h"
class IAudioDevice;
IAudioDevice *Audio_CreateXAudioDevice( void );
#if defined ( _X360 )
class CClientInfo;
void VOICE_AddPlayerToVoiceList( CClientInfo *pClient, bool bLocal );
class CXboxVoice
{
public:
static const DWORD MAX_VOICE_BUFFER_TIME = 200; // 200ms
CXboxVoice( void );
void VoiceInit( void );
void VoiceShutdown( void );
void AddPlayerToVoiceList( CClientInfo *pClient, bool bLocal );
void RemovePlayerFromVoiceList( CClientInfo *pClient, bool bLocal );
bool VoiceUpdateData( void );
void GetVoiceData( CLC_VoiceData *pData );
void VoiceSendData( INetChannel *pChannel );
void VoiceResetLocalData( void );
void PlayIncomingVoiceData( XUID xuid, const byte *pbData, DWORD pdwSize );
void UpdateHUDVoiceStatus( void );
void GetRemoteTalkers( int *pNumTalkers, XUID *pRemoteTalkers );
void SetPlaybackPriority( XUID remoteTalker, DWORD dwUserIndex, XHV_PLAYBACK_PRIORITY playbackPriority );
bool IsPlayerTalking( XUID uid, bool bLocal );
bool IsHeadsetPresent( int id );
void RemoveAllTalkers( CClientInfo *pLocal );
private:
PIXHV2ENGINE m_pXHVEngine;
// Local chat data
static const WORD m_ChatBufferSize = XHV_VOICECHAT_MODE_PACKET_SIZE * XHV_MAX_VOICECHAT_PACKETS;
BYTE m_ChatBuffer[ m_ChatBufferSize ];
WORD m_wLocalDataSize;
// Last voice data sent
DWORD m_dwLastVoiceSend;
};
CXboxVoice *Audio_GetXVoice( void );
IXAudio2 *Audio_GetXAudio2( void );
#endif
#endif // SND_DEV_XAUDIO_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef SND_DMA_H
#define SND_DMA_H
#ifdef _WIN32
#pragma once
#endif
extern bool snd_initialized;
bool SND_IsInGame( void );
#endif // SND_DMA_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,61 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef SND_ENV_FX_H
#define SND_ENV_FX_H
#if defined( _WIN32 )
#pragma once
#endif
//=====================================================================
// FX presets
//=====================================================================
#define SXROOM_OFF 0
#define SXROOM_GENERIC 1 // general, low reflective, diffuse room
#define SXROOM_METALIC_S 2 // highly reflective, parallel surfaces
#define SXROOM_METALIC_M 3
#define SXROOM_METALIC_L 4
#define SXROOM_TUNNEL_S 5 // resonant reflective, long surfaces
#define SXROOM_TUNNEL_M 6
#define SXROOM_TUNNEL_L 7
#define SXROOM_CHAMBER_S 8 // diffuse, moderately reflective surfaces
#define SXROOM_CHAMBER_M 9
#define SXROOM_CHAMBER_L 10
#define SXROOM_BRITE_S 11 // diffuse, highly reflective
#define SXROOM_BRITE_M 12
#define SXROOM_BRITE_L 13
#define SXROOM_WATER1 14 // underwater fx
#define SXROOM_WATER2 15
#define SXROOM_WATER3 16
#define SXROOM_CONCRETE_S 17 // bare, reflective, parallel surfaces
#define SXROOM_CONCRETE_M 18
#define SXROOM_CONCRETE_L 19
#define SXROOM_OUTSIDE1 20 // echoing, moderately reflective
#define SXROOM_OUTSIDE2 21 // echoing, dull
#define SXROOM_OUTSIDE3 22 // echoing, very dull
#define SXROOM_CAVERN_S 23 // large, echoing area
#define SXROOM_CAVERN_M 24
#define SXROOM_CAVERN_L 25
#define SXROOM_WEIRDO1 26
#define SXROOM_WEIRDO2 27
#define SXROOM_WEIRDO3 28
#define CSXROOM 29
#endif // SND_ENV_FX_H

View File

@ -0,0 +1,40 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef SND_FIXEDINT_H
#define SND_FIXEDINT_H
#if defined( _WIN32 )
#pragma once
#endif
// fixed point stuff for real-time resampling
#define FIX_BITS 28
#define FIX_SCALE (1 << FIX_BITS)
#define FIX_MASK ((1 << FIX_BITS)-1)
#define FIX_FLOAT(a) ((int)((a) * FIX_SCALE))
#define FIX(a) (((int)(a)) << FIX_BITS)
#define FIX_INTPART(a) (((int)(a)) >> FIX_BITS)
#define FIX_FRACTION(a,b) (FIX(a)/(b))
#define FIX_FRACPART(a) ((a) & FIX_MASK)
#define FIX_TODOUBLE(a) ((double)(a) / (double)FIX_SCALE)
typedef unsigned int fixedint;
#define FIX_BITS14 14
#define FIX_SCALE14 (1 << FIX_BITS14)
#define FIX_MASK14 ((1 << FIX_BITS14)-1)
#define FIX_FLOAT14(a) ((int)((a) * FIX_SCALE14))
#define FIX14(a) (((int)(a)) << FIX_BITS14)
#define FIX_INTPART14(a) (((int)(a)) >> FIX_BITS14)
#define FIX_FRACTION14(a,b) (FIX14(a)/(b))
#define FIX_FRACPART14(a) ((a) & FIX_MASK14)
#define FIX_14TODOUBLE(a) ((double)(a) / (double)FIX_SCALE14)
#define FIX_28TO14(a) ( (int)( ((unsigned int)(a)) >> (FIX_BITS - 14) ) )
#endif // SND_FIXEDINT_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,107 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef SND_MIX_BUF_H
#define SND_MIX_BUF_H
#if defined( _WIN32 )
#pragma once
#endif
// OPTIMIZE: note that making this larger will not increase performance (12/27/03)
#define PAINTBUFFER_SIZE 1020 // 44k: was 512
#define PAINTBUFFER (g_curpaintbuffer)
#define REARPAINTBUFFER (g_currearpaintbuffer)
#define CENTERPAINTBUFFER (g_curcenterpaintbuffer)
enum SoundBufferType_t
{
SOUND_BUFFER_PAINT = 0,
SOUND_BUFFER_ROOM,
SOUND_BUFFER_FACING,
SOUND_BUFFER_FACINGAWAY,
SOUND_BUFFER_DRY,
SOUND_BUFFER_SPEAKER,
SOUND_BUFFER_BASETOTAL,
SOUND_BUFFER_SPECIAL_START = SOUND_BUFFER_BASETOTAL
};
// !!! if this is changed, it much be changed in native assembly too !!!
struct portable_samplepair_t
{
int left;
int right;
};
// sound mixing buffer
#define CPAINTFILTERMEM 3
#define CPAINTFILTERS 4 // maximum number of consecutive upsample passes per paintbuffer
struct paintbuffer_t
{
bool factive; // if true, mix to this paintbuffer using flags
bool fsurround; // if true, mix to front and rear paintbuffers using flags
bool fsurround_center; // if true, mix to front, rear and center paintbuffers using flags
int idsp_specialdsp;
int nPrevSpecialDSP;
int nSpecialDSP;
int flags; // SOUND_BUSS_ROOM, SOUND_BUSS_FACING, SOUND_BUSS_FACINGAWAY, SOUND_BUSS_SPEAKER, SOUND_BUSS_SPECIAL_DSP, SOUND_BUSS_DRY
portable_samplepair_t *pbuf; // front stereo mix buffer, for 2 or 4 channel mixing
portable_samplepair_t *pbufrear; // rear mix buffer, for 4 channel mixing
portable_samplepair_t *pbufcenter; // center mix buffer, for 5 channel mixing
int ifilter; // current filter memory buffer to use for upsampling pass
portable_samplepair_t fltmem[CPAINTFILTERS][CPAINTFILTERMEM]; // filter memory, for upsampling with linear or cubic interpolation
portable_samplepair_t fltmemrear[CPAINTFILTERS][CPAINTFILTERMEM]; // filter memory, for upsampling with linear or cubic interpolation
portable_samplepair_t fltmemcenter[CPAINTFILTERS][CPAINTFILTERMEM]; // filter memory, for upsampling with linear or cubic interpolation
};
extern "C"
{
extern portable_samplepair_t *g_paintbuffer;
// temp paintbuffer - not included in main list of paintbuffers
extern portable_samplepair_t *g_temppaintbuffer;
extern CUtlVector< paintbuffer_t > g_paintBuffers;
extern void MIX_SetCurrentPaintbuffer( int ipaintbuffer );
extern int MIX_GetCurrentPaintbufferIndex( void );
extern paintbuffer_t *MIX_GetCurrentPaintbufferPtr( void );
extern paintbuffer_t *MIX_GetPPaintFromIPaint( int ipaintbuffer );
extern void MIX_ClearAllPaintBuffers( int SampleCount, bool clearFilters );
extern bool MIX_InitAllPaintbuffers(void);
extern void MIX_FreeAllPaintbuffers(void);
extern portable_samplepair_t *g_curpaintbuffer;
extern portable_samplepair_t *g_currearpaintbuffer;
extern portable_samplepair_t *g_curcenterpaintbuffer;
};
// must be at least PAINTBUFFER_SIZE+1 for upsampling
#define PAINTBUFFER_MEM_SIZE (PAINTBUFFER_SIZE+4)
// size in samples of copy buffer used by pitch shifters in mixing
#if defined(_GAMECONSOLE)
#define TEMP_COPY_BUFFER_SIZE (PAINTBUFFER_MEM_SIZE * 2)
#else
// allow more memory for this on PC for developers to pitch-shift their way through dialog
#define TEMP_COPY_BUFFER_SIZE (PAINTBUFFER_MEM_SIZE * 4)
#endif
// hard clip input value to -32767 <= y <= 32767
#define CLIP(x) ((x) > 32767 ? 32767 : ((x) < -32767 ? -32767 : (x)))
#endif // SND_MIX_BUF_H

View File

@ -0,0 +1,602 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "audio_pch.h"
#include "snd_mp3_source.h"
#include "snd_dma.h"
#include "snd_wave_mixer_mp3.h"
#include "filesystem_engine.h"
#include "utldict.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#ifndef DEDICATED // have to test this because VPC is forcing us to compile this file.
// How many bytes initial data bytes of the mp3 should be saved in the soundcache, in addition to the small amount of
// metadata (playbackrate, etc). This will increase memory usage by
// ( N * number-of-precached-mp3-sounds-in-the-whole-game ) at all times, as the soundcache is held in memory.
//
// Right now we're setting this to zero. The IsReadyToMix() logic at the data layer will delay mixing of the sound until
// it arrives. Setting this to anything above zero, however, will allow the sound to start, so it needs to either be
// enough to cover SND_ASYNC_LOOKAHEAD_SECONDS or none at all.
#define MP3_STARTUP_DATA_SIZE_BYTES 0
CUtlDict< CSentence *, int> g_PhonemeFileSentences;
bool g_bAllPhonemesLoaded;
void PhonemeMP3Shutdown( void )
{
g_PhonemeFileSentences.PurgeAndDeleteElements();
g_bAllPhonemesLoaded = false;
}
void AddPhonemesFromFile( const char *pszFileName )
{
// If all Phonemes are loaded, do not load anymore
if ( g_bAllPhonemesLoaded && g_PhonemeFileSentences.Count() != 0 )
return;
// Empty file name implies stop loading more phonemes
if ( pszFileName == NULL )
{
g_bAllPhonemesLoaded = true;
return;
}
// Load this file
g_bAllPhonemesLoaded = false;
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
if ( g_pFileSystem->ReadFile( pszFileName, "MOD", buf ) )
{
while ( 1 )
{
char token[4096];
buf.GetString( token );
V_FixSlashes( token );
int iIndex = g_PhonemeFileSentences.Find( token );
if ( iIndex != g_PhonemeFileSentences.InvalidIndex() )
{
delete g_PhonemeFileSentences.Element( iIndex );
g_PhonemeFileSentences.Remove( token );
}
CSentence *pSentence = new CSentence;
g_PhonemeFileSentences.Insert( token, pSentence );
buf.GetString( token );
if ( strlen( token ) <= 0 )
break;
if ( !stricmp( token, "{" ) )
{
pSentence->InitFromBuffer( buf );
}
}
}
}
CAudioSourceMP3::CAudioSourceMP3( CSfxTable *pSfx )
{
m_sampleRate = 0;
m_pSfx = pSfx;
m_refCount = 0;
m_dataStart = 0;
int file = g_pSndIO->open( pSfx->GetFileName() );
if ( file != -1 )
{
m_dataSize = g_pSndIO->size( file );
g_pSndIO->close( file );
}
else
{
// No sound cache, the file isn't here, print this so that the relatively deep failure points that are about to
// spew make a little more sense
Warning( "MP3 is completely missing, sound system will be upset to learn of this [ %s ]\n", pSfx->GetFileName() );
m_dataSize = 0;
}
m_nCachedDataSize = 0;
m_bIsPlayOnce = false;
m_bIsSentenceWord = false;
m_bCheckedForPendingSentence = false;
}
CAudioSourceMP3::CAudioSourceMP3( CSfxTable *pSfx, CAudioSourceCachedInfo *info )
{
m_pSfx = pSfx;
m_refCount = 0;
m_sampleRate = info->SampleRate();
m_dataSize = info->DataSize();
m_dataStart = info->DataStart();
m_nCachedDataSize = 0;
m_bIsPlayOnce = false;
m_bCheckedForPendingSentence = false;
CheckAudioSourceCache();
}
CAudioSourceMP3::~CAudioSourceMP3()
{
}
// mixer's references
void CAudioSourceMP3::ReferenceAdd( CAudioMixer * )
{
m_refCount++;
}
void CAudioSourceMP3::ReferenceRemove( CAudioMixer * )
{
m_refCount--;
if ( m_refCount == 0 && IsPlayOnce() )
{
SetPlayOnce( false ); // in case it gets used again
CacheUnload();
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAudioSourceMP3::IsAsyncLoad()
{
// If there's a bit of "cached data" then we don't have to lazy/async load (we still async load the remaining data,
// but we run from the cache initially)
return ( m_nCachedDataSize > 0 ) ? false : true;
}
// check reference count, return true if nothing is referencing this
bool CAudioSourceMP3::CanDelete( void )
{
return m_refCount > 0 ? false : true;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : int
//-----------------------------------------------------------------------------
int CAudioSourceMP3::GetType()
{
return AUDIO_SOURCE_MP3;
}
//-----------------------------------------------------------------------------
void CAudioSourceMP3::SetSentence( CSentence *pSentence )
{
CAudioSourceCachedInfo *info = m_AudioCacheHandle.FastGet();
if ( !info )
return;
if ( info && info->Sentence() )
return;
CSentence *pNewSentence = new CSentence;
pNewSentence->Append( 0.0f, *pSentence );
pNewSentence->MakeRuntimeOnly();
info->SetSentence( pNewSentence );
}
int CAudioSourceMP3::SampleRate()
{
if ( !m_sampleRate )
{
// This should've come from the sound cache. We can avoid sync I/O jank if and only if we've started streaming
// data already for some other reason. (Despite the name, CreateWaveDataMemory is just creating a wrapper class
// that manages access to the wave data cache)
IWaveData *pData = CreateWaveDataMemory( *this );
if ( !pData->IsReadyToMix() && SND_IsInGame() )
{
// If you hit this, you're creating a sound source that isn't in the sound cache, and asking for its sample
// rate before it has streamed enough data in to read it from the underlying file. Your options are:
// - Rebuild sound cache or figure out why this sound wasn't included.
// - Precache this sound at level load so this doesn't happen during gameplay.
// - Somehow call CacheLoad() on this source earlier so it has time to get data into memory so the data
// shows up as IsReadyToMix here, and this crutch won't jank.
Warning( "MP3 initialized with no sound cache, this may cause janking. [ %s ]\n", GetFileName() );
// The code below will still go fine, but the mixer will emit a jank warning that the data wasn't ready and
// do sync I/O
}
CAudioMixerWaveMP3 *pMixer = new CAudioMixerWaveMP3( pData );
m_sampleRate = pMixer->GetStreamOutputRate();
// pData ownership is passed to, and free'd by, pMixer
delete pMixer;
}
return m_sampleRate;
}
void CAudioSourceMP3::GetCacheData( CAudioSourceCachedInfo *info )
{
// Don't want to replicate our cached sample rate back into the new cache, ensure we recompute it.
CAudioMixerWaveMP3 *pTempMixer = new CAudioMixerWaveMP3( CreateWaveDataMemory(*this) );
m_sampleRate = pTempMixer->GetStreamOutputRate();
delete pTempMixer;
AssertMsg( m_sampleRate, "Creating cache with invalid sample rate data" );
if ( !m_sampleRate )
{
Warning( "Failed to find sample rate creating cache data for MP3, cache will be invalid [ %s ]\n", GetFileName() );
}
info->SetSampleRate( m_sampleRate );
info->SetDataStart( 0 );
int file = g_pSndIO->open( m_pSfx->GetFileName() );
if ( !file )
{
Warning( "Failed to find file for building soundcache [ %s ]\n", m_pSfx->GetFileName() );
// Don't re-use old cached value
m_dataSize = 0;
}
else
{
m_dataSize = (int)g_pSndIO->size( file );
}
Assert( m_dataSize > 0 );
// Do we need to actually load any audio data?
#if MP3_STARTUP_DATA_SIZE_BYTES > 0 // We may have defined the startup data to nothingness
if ( info->s_bIsPrecacheSound && m_dataSize > 0 )
{
// Ideally this would mimic the wave startup data code and figure out this calculation:
// int bytesNeeded = m_channels * ( m_bits >> 3 ) * m_rate * SND_ASYNC_LOOKAHEAD_SECONDS;
// (plus header)
int dataSize = min( MP3_STARTUP_DATA_SIZE_BYTES, m_dataSize );
byte *data = new byte[ dataSize ]();
int readSize = g_pSndIO->read( data, dataSize, file );
if ( readSize != dataSize )
{
Warning( "Building soundcache, expected %i bytes of data but got %i [ %s ]\n", dataSize, readSize, m_pSfx->GetFileName() );
dataSize = readSize;
}
info->SetCachedDataSize( dataSize );
info->SetCachedData( data );
}
#endif // MP3_STARTUP_DATA_SIZE_BYTES > 0
g_pSndIO->close( file );
// Data size gets computed in GetStartupData!!!
info->SetDataSize( m_dataSize );
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : char const
//-----------------------------------------------------------------------------
char const *CAudioSourceMP3::GetFileName()
{
return m_pSfx ? m_pSfx->GetFileName() : "NULL m_pSfx";
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAudioSourceMP3::CheckAudioSourceCache()
{
Assert( m_pSfx );
if ( !m_pSfx->IsPrecachedSound() )
{
return;
}
// This will "re-cache" this if it's not in this level's cache already
m_AudioCacheHandle.Get( GetType(), true, m_pSfx, &m_nCachedDataSize );
}
//-----------------------------------------------------------------------------
// Purpose: NULL the wave data pointer (we haven't loaded yet)
//-----------------------------------------------------------------------------
CAudioSourceMP3Cache::CAudioSourceMP3Cache( CSfxTable *pSfx ) :
CAudioSourceMP3( pSfx )
{
m_hCache = 0;
}
CAudioSourceMP3Cache::CAudioSourceMP3Cache( CSfxTable *pSfx, CAudioSourceCachedInfo *info ) :
CAudioSourceMP3( pSfx, info )
{
m_hCache = 0;
m_dataSize = info->DataSize();
m_dataStart = info->DataStart();
m_bNoSentence = false;
}
//-----------------------------------------------------------------------------
// Purpose: Free any wave data we've allocated
//-----------------------------------------------------------------------------
CAudioSourceMP3Cache::~CAudioSourceMP3Cache( void )
{
CacheUnload();
}
int CAudioSourceMP3Cache::GetCacheStatus( void )
{
bool bCacheValid;
int loaded = wavedatacache->IsDataLoadCompleted( m_hCache, &bCacheValid ) ? AUDIO_IS_LOADED : AUDIO_NOT_LOADED;
if ( !bCacheValid )
{
wavedatacache->RestartDataLoad( &m_hCache, m_pSfx->GetFileName(), m_dataSize, m_dataStart );
}
return loaded;
}
void CAudioSourceMP3Cache::CacheLoad( void )
{
// Commence lazy load?
if ( m_hCache != 0 )
{
GetCacheStatus();
return;
}
m_hCache = wavedatacache->AsyncLoadCache( m_pSfx->GetFileName(), m_dataSize, m_dataStart );
}
void CAudioSourceMP3Cache::CacheUnload( void )
{
if ( m_hCache != 0 )
{
wavedatacache->Unload( m_hCache );
}
}
char *CAudioSourceMP3Cache::GetDataPointer( void )
{
char *pMP3Data = NULL;
bool dummy = false;
if ( m_hCache == 0 )
{
CacheLoad();
}
wavedatacache->GetDataPointer(
m_hCache,
m_pSfx->GetFileName(),
m_dataSize,
m_dataStart,
(void **)&pMP3Data,
0,
&dummy );
return pMP3Data;
}
int CAudioSourceMP3Cache::GetOutputData( void **pData, int samplePosition, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
{
// how many bytes are available ?
int totalSampleCount = m_dataSize - samplePosition;
// may be asking for a sample out of range, clip at zero
if ( totalSampleCount < 0 )
totalSampleCount = 0;
// clip max output samples to max available
if ( sampleCount > totalSampleCount )
sampleCount = totalSampleCount;
// if we are returning some samples, store the pointer
if ( sampleCount )
{
// Starting past end of "preloaded" data, just use regular cache
if ( samplePosition >= m_nCachedDataSize )
{
*pData = GetDataPointer();
}
else
{
// Start async loader if we haven't already done so
CacheLoad();
// Return less data if we are about to run out of uncached data
if ( samplePosition + sampleCount >= m_nCachedDataSize )
{
sampleCount = m_nCachedDataSize - samplePosition;
}
// Point at preloaded/cached data from .cache file for now
*pData = GetCachedDataPointer();
}
if ( *pData )
{
*pData = (char *)*pData + samplePosition;
}
else
{
// Out of data or file i/o problem
sampleCount = 0;
}
}
return sampleCount;
}
CAudioMixer *CAudioSourceMP3Cache::CreateMixer( int initialStreamPosition )
{
CAudioMixer *pMixer = new CAudioMixerWaveMP3( CreateWaveDataMemory(*this) );
return pMixer;
}
CSentence *CAudioSourceMP3Cache::GetSentence( void )
{
// Already checked and this wav doesn't have sentence data...
if ( m_bNoSentence == true )
{
return NULL;
}
// Look up sentence from cache
CAudioSourceCachedInfo *info = m_AudioCacheHandle.FastGet();
if ( !info )
{
info = m_AudioCacheHandle.Get( CAudioSource::AUDIO_SOURCE_WAV, m_pSfx->IsPrecachedSound(), m_pSfx, &m_nCachedDataSize );
}
Assert( info );
if ( !info )
{
m_bNoSentence = true;
return NULL;
}
CSentence *sentence = info->Sentence();
if ( !sentence )
{
if ( !m_bCheckedForPendingSentence )
{
int iSentence = g_PhonemeFileSentences.Find( m_pSfx->GetFileName() );
if ( iSentence != g_PhonemeFileSentences.InvalidIndex() )
{
sentence = g_PhonemeFileSentences.Element( iSentence );
SetSentence( sentence );
}
m_bCheckedForPendingSentence = true;
}
}
if ( !sentence )
{
m_bNoSentence = true;
return NULL;
}
if ( sentence->m_bIsValid )
{
return sentence;
}
m_bNoSentence = true;
return NULL;
}
//-----------------------------------------------------------------------------
// CAudioSourceStreamMP3
//-----------------------------------------------------------------------------
CAudioSourceStreamMP3::CAudioSourceStreamMP3( CSfxTable *pSfx ) :
CAudioSourceMP3( pSfx )
{
}
CAudioSourceStreamMP3::CAudioSourceStreamMP3( CSfxTable *pSfx, CAudioSourceCachedInfo *info ) :
CAudioSourceMP3( pSfx, info )
{
m_dataSize = info->DataSize();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAudioSourceStreamMP3::Prefetch()
{
PrefetchDataStream( m_pSfx->GetFileName(), 0, m_dataSize );
}
CAudioMixer *CAudioSourceStreamMP3::CreateMixer( int intialStreamPosition )
{
// BUGBUG: Source constructs the IWaveData, mixer frees it, fix this?
IWaveData *pWaveData = CreateWaveDataStream( *this, static_cast<IWaveStreamSource *>( this ), m_pSfx->GetFileName(), 0, m_dataSize, m_pSfx, 0 );
if ( pWaveData )
{
CAudioMixer *pMixer = new CAudioMixerWaveMP3( pWaveData );
if ( pMixer )
{
if ( !m_bCheckedForPendingSentence )
{
int iSentence = g_PhonemeFileSentences.Find( m_pSfx->GetFileName() );
if ( iSentence != g_PhonemeFileSentences.InvalidIndex() )
{
SetSentence( g_PhonemeFileSentences.Element( iSentence ) );
}
m_bCheckedForPendingSentence = true;
}
return pMixer;
}
// no mixer but pWaveData was deleted in mixer's destructor
// so no need to delete
}
return NULL;
}
int CAudioSourceStreamMP3::GetOutputData( void **pData, int samplePosition, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
{
return 0;
}
bool Audio_IsMP3( const char *pName )
{
int len = strlen(pName);
if ( len > 4 )
{
if ( !Q_strnicmp( &pName[len - 4], ".mp3", 4 ) )
{
return true;
}
}
return false;
}
CAudioSource *Audio_CreateStreamedMP3( CSfxTable *pSfx )
{
CAudioSourceStreamMP3 *pMP3 = NULL;
CAudioSourceCachedInfo *info = audiosourcecache->GetInfo( CAudioSource::AUDIO_SOURCE_MP3, pSfx->IsPrecachedSound(), pSfx );
if ( info )
{
pMP3 = new CAudioSourceStreamMP3( pSfx, info );
}
else
{
pMP3 = new CAudioSourceStreamMP3( pSfx );
}
return pMP3;
}
CAudioSource *Audio_CreateMemoryMP3( CSfxTable *pSfx )
{
CAudioSourceMP3Cache *pMP3 = NULL;
CAudioSourceCachedInfo *info = audiosourcecache->GetInfo( CAudioSource::AUDIO_SOURCE_MP3, pSfx->IsPrecachedSound(), pSfx );
if ( info )
{
pMP3 = new CAudioSourceMP3Cache( pSfx, info );
}
else
{
pMP3 = new CAudioSourceMP3Cache( pSfx );
}
return pMP3;
}
#endif

View File

@ -0,0 +1,186 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef SND_MP3_SOURCE_H
#define SND_MP3_SOURCE_H
#ifdef _WIN32
#pragma once
#endif
#include "snd_audio_source.h"
#include "snd_wave_data.h"
#include "snd_sfx.h"
class IWaveData;
class CAudioMixer;
abstract_class CAudioSourceMP3 : public CAudioSource
{
public:
CAudioSourceMP3( CSfxTable *pSfx );
CAudioSourceMP3( CSfxTable *pSfx, CAudioSourceCachedInfo *info );
virtual ~CAudioSourceMP3();
// Create an instance (mixer) of this audio source
virtual CAudioMixer *CreateMixer( int initialStreamPosition = 0 ) = 0;
virtual int GetType( void );
virtual void GetCacheData( CAudioSourceCachedInfo *info );
// Provide samples for the mixer. You can point pData at your own data, or if you prefer to copy the data,
// you can copy it into copyBuf and set pData to copyBuf.
virtual int GetOutputData( void **pData, int samplePosition, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] ) = 0;
virtual int SampleRate( void );
// Returns true if the source is a voice source.
// This affects the voice_overdrive behavior (all sounds get quieter when
// someone is speaking).
virtual bool IsVoiceSource() { return false; }
virtual int SampleSize( void ) { return 1; }
// Total number of samples in this source. NOTE: Some sources are infinite (mic input), they should return
// a count equal to one second of audio at their current rate.
virtual int SampleCount( void ) { return m_dataSize; }
virtual int Format() { return 0; }
virtual int DataSize( void ) { return 0; }
virtual bool IsLooped( void ) { return false; }
virtual bool IsStereoWav( void ) { return false; }
virtual bool IsStreaming( void ) { return false; }
virtual int GetCacheStatus( void ) { return AUDIO_IS_LOADED; }
virtual void CacheLoad( void ) {}
virtual void CacheUnload( void ) {}
virtual CSentence *GetSentence( void ) { return NULL; }
virtual int ZeroCrossingBefore( int sample ) { return sample; }
virtual int ZeroCrossingAfter( int sample ) { return sample; }
// mixer's references
virtual void ReferenceAdd( CAudioMixer *pMixer );
virtual void ReferenceRemove( CAudioMixer *pMixer );
// check reference count, return true if nothing is referencing this
virtual bool CanDelete( void );
virtual bool IsAsyncLoad();
virtual void CheckAudioSourceCache();
virtual char const *GetFileName();
virtual void SetPlayOnce( bool isPlayOnce ) { m_bIsPlayOnce = isPlayOnce; }
virtual bool IsPlayOnce() { return m_bIsPlayOnce; }
virtual void SetSentenceWord( bool bIsWord ) { m_bIsSentenceWord = bIsWord; }
virtual bool IsSentenceWord() { return m_bIsSentenceWord; }
virtual int SampleToStreamPosition( int samplePosition ) { return 0; }
virtual int StreamToSamplePosition( int streamPosition ) { return 0; }
virtual void SetSentence( CSentence *pSentence );
protected:
//-----------------------------------------------------------------------------
// Purpose:
// Output : byte
//-----------------------------------------------------------------------------
inline byte *GetCachedDataPointer()
{
VPROF("CAudioSourceMP3::GetCachedDataPointer");
CAudioSourceCachedInfo *info = m_AudioCacheHandle.Get( CAudioSource::AUDIO_SOURCE_MP3, m_pSfx->IsPrecachedSound(), m_pSfx, &m_nCachedDataSize );
if ( !info )
{
Assert( !"CAudioSourceMP3::GetCachedDataPointer info == NULL" );
return NULL;
}
return (byte *)info->CachedData();
}
CAudioSourceCachedInfoHandle_t m_AudioCacheHandle;
int m_nCachedDataSize;
protected:
CSfxTable *m_pSfx;
int m_sampleRate;
int m_dataSize;
int m_dataStart;
int m_refCount;
bool m_bIsPlayOnce : 1;
bool m_bIsSentenceWord : 1;
bool m_bCheckedForPendingSentence;
};
//-----------------------------------------------------------------------------
// Purpose: Streaming MP3 file
//-----------------------------------------------------------------------------
class CAudioSourceStreamMP3 : public CAudioSourceMP3, public IWaveStreamSource
{
public:
CAudioSourceStreamMP3( CSfxTable *pSfx );
CAudioSourceStreamMP3( CSfxTable *pSfx, CAudioSourceCachedInfo *info );
~CAudioSourceStreamMP3() {}
bool IsStreaming( void ) OVERRIDE { return true; }
bool IsStereoWav( void ) OVERRIDE { return false; }
CAudioMixer *CreateMixer( int initialStreamPosition = 0 ) OVERRIDE;
int GetOutputData( void **pData, int samplePosition, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] ) OVERRIDE;
// IWaveStreamSource
int UpdateLoopingSamplePosition( int samplePosition ) OVERRIDE
{
return samplePosition;
}
void UpdateSamples( char *pData, int sampleCount ) OVERRIDE {}
int GetLoopingInfo( int *pLoopBlock, int *pNumLeadingSamples, int *pNumTrailingSamples ) OVERRIDE
{
return 0;
}
void Prefetch() OVERRIDE;
private:
CAudioSourceStreamMP3( const CAudioSourceStreamMP3 & ); // not implemented, not accessible
};
class CAudioSourceMP3Cache : public CAudioSourceMP3
{
public:
CAudioSourceMP3Cache( CSfxTable *pSfx );
CAudioSourceMP3Cache( CSfxTable *pSfx, CAudioSourceCachedInfo *info );
~CAudioSourceMP3Cache( void );
int GetCacheStatus( void ) OVERRIDE;
void CacheLoad( void ) OVERRIDE;
void CacheUnload( void ) OVERRIDE;
// NOTE: "samples" are bytes for MP3
int GetOutputData( void **pData, int samplePosition, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] ) OVERRIDE;
CAudioMixer *CreateMixer( int initialStreamPosition = 0 ) OVERRIDE;
CSentence *GetSentence( void ) OVERRIDE;
void Prefetch() OVERRIDE {}
protected:
virtual char *GetDataPointer( void );
memhandle_t m_hCache;
private:
CAudioSourceMP3Cache( const CAudioSourceMP3Cache & );
unsigned int m_bNoSentence : 1;
};
bool Audio_IsMP3( const char *pName );
CAudioSource *Audio_CreateStreamedMP3( CSfxTable *pSfx );
CAudioSource *Audio_CreateMemoryMP3( CSfxTable *pSfx );
#endif // SND_MP3_SOURCE_H

View File

@ -0,0 +1,15 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//===========================================================================//
#include "audio_pch.h"
#include "ivoicerecord.h"
#include "voice_mixer_controls.h"
#include "snd_dev_openal.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

View File

@ -0,0 +1,369 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Sentence Mixing
//
//=============================================================================//
#include "audio_pch.h"
#include "vox_private.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
// Purpose: This replaces the old sentence logic that was integrated with the
// sound code. Now it is a hierarchical mixer.
//-----------------------------------------------------------------------------
class CSentenceMixer : public CAudioMixer
{
public:
CSentenceMixer( voxword_t *pWords );
~CSentenceMixer( void );
// return number of samples mixed
virtual int MixDataToDevice( IAudioDevice *pDevice, channel_t *pChannel, int sampleCount, int outputRate, int outputOffset );
virtual int SkipSamples( channel_t *pChannel, int sampleCount, int outputRate, int outputOffset );
virtual bool ShouldContinueMixing( void );
virtual CAudioSource* GetSource( void );
// get the current position (next sample to be mixed)
virtual int GetSamplePosition( void );
virtual float ModifyPitch( float pitch );
virtual float GetVolumeScale( void );
// BUGBUG: These are only applied to the current word, not the whole sentence!!!!
virtual void SetSampleStart( int newPosition );
virtual void SetSampleEnd( int newEndPosition );
virtual void SetStartupDelaySamples( int delaySamples );
virtual int GetMixSampleSize() { return m_pCurrentWordMixer ? m_pCurrentWordMixer->GetMixSampleSize() : 0; }
virtual bool IsReadyToMix();
virtual int GetPositionForSave() { return GetSamplePosition(); }
virtual void SetPositionFromSaved( int savedPosition ) { SetSampleStart( savedPosition ); }
private:
CAudioMixer *LoadWord( int nWordIndex );
void FreeWord( int nWordIndex );
// identifies the active word
int m_currentWordIndex;
CAudioMixer *m_pCurrentWordMixer;
// set when a transition to a new word occurs
bool m_bNewWord;
voxword_t m_VoxWords[CVOXWORDMAX];
CAudioMixer *m_pWordMixers[CVOXWORDMAX];
int m_nNumWords;
};
CAudioMixer *CreateSentenceMixer( voxword_t *pWords )
{
if ( pWords )
{
return new CSentenceMixer( pWords );
}
return NULL;
}
CSentenceMixer::CSentenceMixer( voxword_t *pWords )
{
// count the expected number of words
m_nNumWords = 0;
while ( pWords[m_nNumWords].sfx != NULL )
{
// get a private copy of the words
m_VoxWords[m_nNumWords] = pWords[m_nNumWords];
m_nNumWords++;
if ( m_nNumWords >= ARRAYSIZE( m_VoxWords ) )
{
// very long sentence, prevent overflow
break;
}
}
// startup all the mixers now, this serves as a hint to the audio streamer
// actual mixing will commence when they are ALL ready
for ( int nWord = 0; nWord < m_nNumWords; nWord++ )
{
// it is possible to get a null mixer (due to wav error, etc)
// the sentence will skip these words
m_pWordMixers[nWord] = LoadWord( nWord );
}
Assert( m_nNumWords < ARRAYSIZE( m_pWordMixers ) );
// find first valid word mixer
m_currentWordIndex = 0;
m_pCurrentWordMixer = NULL;
for ( int nWord = 0; nWord < m_nNumWords; nWord++ )
{
if ( m_pWordMixers[nWord] )
{
m_currentWordIndex = nWord;
m_pCurrentWordMixer = m_pWordMixers[nWord];
break;
}
}
m_bNewWord = ( m_pCurrentWordMixer != NULL );
}
CSentenceMixer::~CSentenceMixer( void )
{
// free all words
for ( int nWord = 0; nWord < m_nNumWords; nWord++ )
{
FreeWord( nWord );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true if mixing can commence, false otherwise
//-----------------------------------------------------------------------------
bool CSentenceMixer::IsReadyToMix()
{
if ( !m_pCurrentWordMixer )
{
// no word, but mixing has to commence in order to shutdown
return true;
}
// all the words should be available before mixing the sentence
for ( int nWord = m_currentWordIndex; nWord < m_nNumWords; nWord++ )
{
if ( m_pWordMixers[nWord] && !m_pWordMixers[nWord]->IsReadyToMix() )
{
// Still waiting for async data to arrive
return false;
}
}
if ( m_bNewWord )
{
m_bNewWord = false;
int start = m_VoxWords[m_currentWordIndex].start;
int end = m_VoxWords[m_currentWordIndex].end;
// don't allow overlapped ranges
if ( end <= start )
{
end = 0;
}
if ( start || end )
{
int sampleCount = m_pCurrentWordMixer->GetSource()->SampleCount();
if ( start > 0 && start < 100 )
{
m_pCurrentWordMixer->SetSampleStart( (int)(sampleCount * 0.01f * start) );
}
if ( end > 0 && end < 100 )
{
m_pCurrentWordMixer->SetSampleEnd( (int)(sampleCount * 0.01f * end) );
}
}
}
return true;
}
bool CSentenceMixer::ShouldContinueMixing( void )
{
if ( m_pCurrentWordMixer )
{
// keep mixing until the words run out
return true;
}
return false;
}
CAudioSource *CSentenceMixer::GetSource( void )
{
if ( m_pCurrentWordMixer )
{
return m_pCurrentWordMixer->GetSource();
}
return NULL;
}
// get the current position (next sample to be mixed)
int CSentenceMixer::GetSamplePosition( void )
{
if ( m_pCurrentWordMixer )
{
return m_pCurrentWordMixer->GetSamplePosition();
}
return 0;
}
void CSentenceMixer::SetSampleStart( int newPosition )
{
if ( m_pCurrentWordMixer )
{
m_pCurrentWordMixer->SetSampleStart( newPosition );
}
}
// End playback at newEndPosition
void CSentenceMixer::SetSampleEnd( int newEndPosition )
{
if ( m_pCurrentWordMixer )
{
m_pCurrentWordMixer->SetSampleEnd( newEndPosition );
}
}
void CSentenceMixer::SetStartupDelaySamples( int delaySamples )
{
if ( m_pCurrentWordMixer )
{
m_pCurrentWordMixer->SetStartupDelaySamples( delaySamples );
}
}
//-----------------------------------------------------------------------------
// Purpose: Free a word
//-----------------------------------------------------------------------------
void CSentenceMixer::FreeWord( int nWord )
{
if ( m_pWordMixers[nWord] )
{
delete m_pWordMixers[nWord];
m_pWordMixers[nWord] = NULL;
}
if ( m_VoxWords[nWord].sfx )
{
// If this wave wasn't precached by the game code
if ( !m_VoxWords[nWord].fKeepCached )
{
// If this was the last mixer that had a reference
if ( m_VoxWords[nWord].sfx->pSource->CanDelete() )
{
// free the source
delete m_VoxWords[nWord].sfx->pSource;
m_VoxWords[nWord].sfx->pSource = NULL;
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Load a word
//-----------------------------------------------------------------------------
CAudioMixer *CSentenceMixer::LoadWord( int nWord )
{
CAudioMixer *pMixer = NULL;
if ( m_VoxWords[nWord].sfx )
{
CAudioSource *pSource = S_LoadSound( m_VoxWords[nWord].sfx, NULL );
if ( pSource )
{
pSource->SetSentenceWord( true );
pMixer = pSource->CreateMixer();
}
}
return pMixer;
}
float CSentenceMixer::ModifyPitch( float pitch )
{
if ( m_pCurrentWordMixer )
{
if ( m_VoxWords[m_currentWordIndex].pitch > 0 )
{
pitch += (m_VoxWords[m_currentWordIndex].pitch - 100) * 0.01f;
}
}
return pitch;
}
float CSentenceMixer::GetVolumeScale( void )
{
if ( m_pCurrentWordMixer )
{
if ( m_VoxWords[m_currentWordIndex].volume )
{
float volume = m_VoxWords[m_currentWordIndex].volume * 0.01;
if ( volume < 1.0f )
return volume;
}
}
return 1.0f;
}
int CSentenceMixer::SkipSamples( channel_t *pChannel, int sampleCount, int outputRate, int outputOffset )
{
Assert( 0 );
return 0;
}
// return number of samples mixed
int CSentenceMixer::MixDataToDevice( IAudioDevice *pDevice, channel_t *pChannel, int sampleCount, int outputRate, int outputOffset )
{
if ( !m_pCurrentWordMixer )
{
return 0;
}
// save this to compute total output
int startingOffset = outputOffset;
while ( sampleCount > 0 && m_pCurrentWordMixer )
{
int outputCount = m_pCurrentWordMixer->MixDataToDevice( pDevice, pChannel, sampleCount, outputRate, outputOffset );
outputOffset += outputCount;
sampleCount -= outputCount;
if ( !m_pCurrentWordMixer->ShouldContinueMixing() )
{
bool bMouth = SND_IsMouth( pChannel );
if ( bMouth )
{
SND_ClearMouth( pChannel );
}
// advance to next valid word mixer
do
{
m_currentWordIndex++;
if ( m_currentWordIndex >= m_nNumWords )
{
// end of sentence
m_pCurrentWordMixer = NULL;
break;
}
m_pCurrentWordMixer = m_pWordMixers[m_currentWordIndex];
}
while ( m_pCurrentWordMixer == NULL );
if ( m_pCurrentWordMixer )
{
m_bNewWord = true;
pChannel->sfx = m_VoxWords[m_currentWordIndex].sfx;
if ( bMouth )
{
SND_UpdateMouth( pChannel );
}
if ( !IsReadyToMix() )
{
// current word isn't ready, stop mixing
break;
}
}
}
}
return outputOffset - startingOffset;
}

View File

@ -0,0 +1,48 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef SND_SFX_H
#define SND_SFX_H
#if defined( _WIN32 )
#pragma once
#endif
class CAudioSource;
class CSfxTable
{
public:
CSfxTable();
// gets sound name, possible decoracted with prefixes
virtual const char *getname();
// gets the filename, the part after the optional prefixes
const char *GetFileName();
FileNameHandle_t GetFileNameHandle();
void SetNamePoolIndex( int index );
bool IsPrecachedSound();
void OnNameChanged( const char *pName );
int m_namePoolIndex;
CAudioSource *pSource;
bool m_bUseErrorFilename : 1;
bool m_bIsUISound : 1;
bool m_bIsLateLoad : 1;
bool m_bMixGroupsCached : 1;
byte m_mixGroupCount;
// UNDONE: Use a fixed bit vec here?
byte m_mixGroupList[8];
private:
// Only set in debug mode so you can see the name.
const char *m_pDebugName;
};
#endif // SND_SFX_H

View File

@ -0,0 +1,279 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
#include "audio_pch.h"
//#include "matchmaking/IMatchFramework.h"
#include "tier2/tier2.h"
#include "audio/public/voice.h"
#if !defined( DEDICATED ) && ( defined( OSX ) || defined( _WIN32 ) ) && !defined( NO_STEAM )
#include "cl_steamauth.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
CEngineVoiceStub *Audio_GetEngineVoiceStub()
{
static CEngineVoiceStub s_EngineVoiceStub;
return &s_EngineVoiceStub;
}
#if !defined( DEDICATED ) && ( defined( OSX ) || defined( _WIN32 ) ) && !defined( NO_STEAM )
class CEngineVoiceSteam : public IEngineVoice
{
public:
CEngineVoiceSteam();
public:
virtual bool IsHeadsetPresent( int iController );
virtual bool IsLocalPlayerTalking( int iController );
virtual void AddPlayerToVoiceList( XUID xPlayer, int iController );
virtual void RemovePlayerFromVoiceList( XUID xPlayer, int iController );
virtual void GetRemoteTalkers( int *pNumTalkers, XUID *pRemoteTalkers );
virtual bool VoiceUpdateData( int iController );
virtual void GetVoiceData( int iController, const byte **ppvVoiceDataBuffer, unsigned int *pnumVoiceDataBytes );
virtual void VoiceResetLocalData( int iController );
virtual void SetPlaybackPriority( XUID remoteTalker, int iController, int iAllowPlayback );
virtual void PlayIncomingVoiceData( XUID xuid, const byte *pbData, unsigned int dwDataSize, const bool *bAudiblePlayers = NULL );
virtual void RemoveAllTalkers();
protected:
void AudioInitializationUpdate();
public:
bool m_bLocalVoice[ XUSER_MAX_COUNT ];
CUtlVector< XUID > m_arrRemoteVoice;
bool m_bInitializedAudio;
byte m_pbVoiceData[ 1024 * XUSER_MAX_COUNT ];
};
CEngineVoiceSteam::CEngineVoiceSteam()
{
memset( m_bLocalVoice, 0, sizeof( m_bLocalVoice ) );
memset( m_pbVoiceData, 0, sizeof( m_pbVoiceData ) );
m_bInitializedAudio = false;
}
bool CEngineVoiceSteam::IsHeadsetPresent( int iController )
{
return false;
}
bool CEngineVoiceSteam::IsLocalPlayerTalking( int iController )
{
uint32 nBytes = 0;
EVoiceResult res = Steam3Client().SteamUser()->GetAvailableVoice( &nBytes, NULL, 0 );
switch ( res )
{
case k_EVoiceResultOK:
case k_EVoiceResultNoData:
case k_EVoiceResultBufferTooSmall:
return true;
default:
return false;
}
}
void CEngineVoiceSteam::AddPlayerToVoiceList( XUID xPlayer, int iController )
{
if ( !xPlayer && iController >= 0 && iController < XUSER_MAX_COUNT )
{
// Add local player
m_bLocalVoice[ iController ] = true;
AudioInitializationUpdate();
}
if ( xPlayer )
{
if ( m_arrRemoteVoice.Find( xPlayer ) == m_arrRemoteVoice.InvalidIndex() )
{
m_arrRemoteVoice.AddToTail( xPlayer );
AudioInitializationUpdate();
}
}
}
void CEngineVoiceSteam::RemovePlayerFromVoiceList( XUID xPlayer, int iController )
{
if ( !xPlayer && iController >= 0 && iController < XUSER_MAX_COUNT )
{
// Remove local player
m_bLocalVoice[ iController ] = false;
AudioInitializationUpdate();
}
if ( xPlayer )
{
int idx = m_arrRemoteVoice.Find( xPlayer );
if ( idx != m_arrRemoteVoice.InvalidIndex() )
{
m_arrRemoteVoice.FastRemove( idx );
AudioInitializationUpdate();
}
}
}
void CEngineVoiceSteam::GetRemoteTalkers( int *pNumTalkers, XUID *pRemoteTalkers )
{
if ( pNumTalkers )
*pNumTalkers = m_arrRemoteVoice.Count();
if ( pRemoteTalkers )
{
for ( int k = 0; k < m_arrRemoteVoice.Count(); ++ k )
pRemoteTalkers[k] = m_arrRemoteVoice[k];
}
}
bool CEngineVoiceSteam::VoiceUpdateData( int iController )
{
uint32 nBytes = 0;
EVoiceResult res = Steam3Client().SteamUser()->GetAvailableVoice( &nBytes, NULL, 0 );
switch ( res )
{
case k_EVoiceResultOK:
// case k_EVoiceResultNoData: - no data means false
case k_EVoiceResultBufferTooSmall:
return true;
default:
return false;
}
}
void CEngineVoiceSteam::GetVoiceData( int iController, const byte **ppvVoiceDataBuffer, unsigned int *pnumVoiceDataBytes )
{
const int size = ARRAYSIZE( m_pbVoiceData ) / XUSER_MAX_COUNT;
byte *pbVoiceData = m_pbVoiceData + iController * ARRAYSIZE( m_pbVoiceData ) / XUSER_MAX_COUNT;
*ppvVoiceDataBuffer = pbVoiceData;
EVoiceResult res = Steam3Client().SteamUser()->GetVoice( true, pbVoiceData, size, pnumVoiceDataBytes, false, NULL, 0, NULL, 0 );
switch ( res )
{
case k_EVoiceResultNoData:
case k_EVoiceResultOK:
return;
default:
*pnumVoiceDataBytes = 0;
*ppvVoiceDataBuffer = NULL;
return;
}
}
void CEngineVoiceSteam::VoiceResetLocalData( int iController )
{
const int size = ARRAYSIZE( m_pbVoiceData ) / XUSER_MAX_COUNT;
byte *pbVoiceData = m_pbVoiceData + iController * ARRAYSIZE( m_pbVoiceData ) / XUSER_MAX_COUNT;
memset( pbVoiceData, 0, size );
}
void CEngineVoiceSteam::SetPlaybackPriority( XUID remoteTalker, int iController, int iAllowPlayback )
{
;
}
void CEngineVoiceSteam::PlayIncomingVoiceData( XUID xuid, const byte *pbData, unsigned int dwDataSize, const bool *bAudiblePlayers /* = NULL */ )
{
for ( DWORD dwSlot = 0; dwSlot < XBX_GetNumGameUsers(); ++ dwSlot )
{
IPlayerLocal *pPlayer = g_pMatchFramework->GetMatchSystem()->GetPlayerManager()->GetLocalPlayer( dwSlot );
if ( pPlayer && pPlayer->GetXUID() == xuid )
//Hack: Don't play stuff that comes from ourselves.
return;
}
// Make sure voice playback is allowed for the specified user
if ( !g_pMatchFramework->GetMatchSystem()->GetMatchVoice()->CanPlaybackTalker( xuid ) )
return;
// Uncompress the voice data
char pbUncompressedVoice[ 11025 * 2 ];
uint32 numUncompressedBytes;
EVoiceResult res = Steam3Client().SteamUser()->DecompressVoice( const_cast< byte * >( pbData ), dwDataSize,
pbUncompressedVoice, sizeof( pbUncompressedVoice ), &numUncompressedBytes, 0 );
if ( res != k_EVoiceResultOK )
return;
// Voice channel index
int idxVoiceChan = 0;
int idxRemoteTalker = m_arrRemoteVoice.Find( xuid );
if ( idxRemoteTalker != m_arrRemoteVoice.InvalidIndex() )
idxVoiceChan = idxRemoteTalker;
int nChannel = Voice_GetChannel( idxVoiceChan );
if ( nChannel == VOICE_CHANNEL_ERROR )
{
// Create a channel in the voice engine and a channel in the sound engine for this guy.
nChannel = Voice_AssignChannel( idxVoiceChan, false, 0.0f );
}
// Give the voice engine the data (it in turn gives it to the mixer for the sound engine).
if ( nChannel != VOICE_CHANNEL_ERROR )
{
Voice_AddIncomingData( nChannel, pbUncompressedVoice, numUncompressedBytes, 0, false );
}
}
void CEngineVoiceSteam::RemoveAllTalkers()
{
memset( m_bLocalVoice, 0, sizeof( m_bLocalVoice ) );
m_arrRemoteVoice.RemoveAll();
AudioInitializationUpdate();
}
void CEngineVoiceSteam::AudioInitializationUpdate()
{
bool bHasTalkers = ( m_arrRemoteVoice.Count() > 0 );
for ( int k = 0; k < ARRAYSIZE( m_bLocalVoice ); ++ k )
{
if ( m_bLocalVoice[k] )
{
bHasTalkers = true;
break;
}
}
// Initialized already
if ( bHasTalkers == m_bInitializedAudio )
return;
// Clear out voice buffers
memset( m_pbVoiceData, 0, sizeof( m_pbVoiceData ) );
// Init or deinit voice system
if ( bHasTalkers )
{
Voice_ForceInit();
}
else
{
Voice_Deinit();
}
m_bInitializedAudio = bHasTalkers;
}
IEngineVoice *Audio_GetEngineVoiceSteam()
{
static CEngineVoiceSteam s_EngineVoiceSteam;
return &s_EngineVoiceSteam;
}
#else
IEngineVoice *Audio_GetEngineVoiceSteam()
{
return Audio_GetEngineVoiceStub();
}
#endif

View File

@ -0,0 +1,45 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef SND_STUBS_H
#define SND_STUBS_H
#include "engine/ienginevoice.h"
class CEngineVoiceStub : public IEngineVoice
{
public:
virtual bool IsHeadsetPresent( int iController ) { return false; }
virtual bool IsLocalPlayerTalking( int iController ) { return false; }
virtual void AddPlayerToVoiceList( XUID xPlayer, int iController ) {}
virtual void RemovePlayerFromVoiceList( XUID xPlayer, int iController ) {}
virtual void GetRemoteTalkers( int *pNumTalkers, XUID *pRemoteTalkers )
{
if ( pNumTalkers )
*pNumTalkers = 0;
}
virtual bool VoiceUpdateData( int iController ) { return false; }
virtual void GetVoiceData( int iController, const byte **ppvVoiceDataBuffer, unsigned int *pnumVoiceDataBytes )
{
if ( ppvVoiceDataBuffer )
*ppvVoiceDataBuffer = NULL;
if ( pnumVoiceDataBytes )
*pnumVoiceDataBytes = NULL;
}
virtual void VoiceResetLocalData( int iController ) {}
virtual void SetPlaybackPriority( XUID remoteTalker, int iController, int iAllowPlayback ) {}
virtual void PlayIncomingVoiceData( XUID xuid, const byte *pbData, unsigned int dwDataSize, const bool *bAudiblePlayers = NULL ) {}
virtual void RemoveAllTalkers() {}
};
CEngineVoiceStub *Audio_GetEngineVoiceStub();
IEngineVoice *Audio_GetEngineVoiceSteam();
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef SND_WAVE_DATA_H
#define SND_WAVE_DATA_H
#ifdef _WIN32
#pragma once
#endif
#include "snd_audio_source.h"
//-----------------------------------------------------------------------------
// Purpose: Linear iterator over source data.
// Keeps track of position in source, and maintains necessary buffers
//-----------------------------------------------------------------------------
abstract_class IWaveData
{
public:
virtual ~IWaveData( void ) {}
virtual CAudioSource &Source( void ) = 0;
virtual int ReadSourceData( void **pData, int sampleIndex, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] ) = 0;
virtual bool IsReadyToMix() = 0;
};
abstract_class IWaveStreamSource
{
public:
virtual int UpdateLoopingSamplePosition( int samplePosition ) = 0;
virtual void UpdateSamples( char *pData, int sampleCount ) = 0;
virtual int GetLoopingInfo( int *pLoopBlock, int *pNumLeadingSamples, int *pNumTrailingSamples ) = 0;
};
class IFileReadBinary;
class CSfxTable;
extern IWaveData *CreateWaveDataStream( CAudioSource &source, IWaveStreamSource *pStreamSource, const char *pFileName, int dataStart, int dataSize, CSfxTable *pSfx, int startOffset );
extern IWaveData *CreateWaveDataMemory( CAudioSource &source );
void PrefetchDataStream( const char *pFileName, int dataOffset, int dataSize );
#endif // SND_WAVE_DATA_H

View File

@ -0,0 +1,788 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#include "audio_pch.h"
#include "fmtstr.h"
#include "sysexternal.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern bool FUseHighQualityPitch( channel_t *pChannel );
//-----------------------------------------------------------------------------
// These mixers provide an abstraction layer between the audio device and
// mixing/decoding code. They allow data to be decoded and mixed using
// optimized, format sensitive code by calling back into the device that
// controls them.
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose: maps mixing to 8-bit mono mixer
//-----------------------------------------------------------------------------
class CAudioMixerWave8Mono : public CAudioMixerWave
{
public:
CAudioMixerWave8Mono( IWaveData *data ) : CAudioMixerWave( data ) {}
virtual int GetMixSampleSize() { return CalcSampleSize(8, 1); }
virtual void Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress )
{
pDevice->Mix8Mono( pChannel, (char *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress );
}
};
//-----------------------------------------------------------------------------
// Purpose: maps mixing to 8-bit stereo mixer
//-----------------------------------------------------------------------------
class CAudioMixerWave8Stereo : public CAudioMixerWave
{
public:
CAudioMixerWave8Stereo( IWaveData *data ) : CAudioMixerWave( data ) {}
virtual int GetMixSampleSize( ) { return CalcSampleSize(8, 2); }
virtual void Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress )
{
pDevice->Mix8Stereo( pChannel, (char *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress );
}
};
//-----------------------------------------------------------------------------
// Purpose: maps mixing to 16-bit mono mixer
//-----------------------------------------------------------------------------
class CAudioMixerWave16Mono : public CAudioMixerWave
{
public:
CAudioMixerWave16Mono( IWaveData *data ) : CAudioMixerWave( data ) {}
virtual int GetMixSampleSize() { return CalcSampleSize(16, 1); }
virtual void Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress )
{
pDevice->Mix16Mono( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress );
}
};
//-----------------------------------------------------------------------------
// Purpose: maps mixing to 16-bit stereo mixer
//-----------------------------------------------------------------------------
class CAudioMixerWave16Stereo : public CAudioMixerWave
{
public:
CAudioMixerWave16Stereo( IWaveData *data ) : CAudioMixerWave( data ) {}
virtual int GetMixSampleSize() { return CalcSampleSize(16, 2); }
virtual void Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress )
{
pDevice->Mix16Stereo( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress );
}
};
//-----------------------------------------------------------------------------
// Purpose: Create an appropriate mixer type given the data format
// Input : *data - data access abstraction
// format - pcm or adpcm (1 or 2 -- RIFF format)
// channels - number of audio channels (1 = mono, 2 = stereo)
// bits - bits per sample
// Output : CAudioMixer * abstract mixer type that maps mixing to appropriate code
//-----------------------------------------------------------------------------
CAudioMixer *CreateWaveMixer( IWaveData *data, int format, int nChannels, int bits, int initialStreamPosition )
{
CAudioMixer *pMixer = NULL;
if ( format == WAVE_FORMAT_PCM )
{
if ( nChannels > 1 )
{
if ( bits == 8 )
pMixer = new CAudioMixerWave8Stereo( data );
else
pMixer = new CAudioMixerWave16Stereo( data );
}
else
{
if ( bits == 8 )
pMixer = new CAudioMixerWave8Mono( data );
else
pMixer = new CAudioMixerWave16Mono( data );
}
}
else if ( format == WAVE_FORMAT_ADPCM )
{
return CreateADPCMMixer( data );
}
#if defined( _X360 )
else if ( format == WAVE_FORMAT_XMA )
{
return CreateXMAMixer( data, initialStreamPosition );
}
#endif
else
{
// unsupported format or wav file missing!!!
return NULL;
}
if ( pMixer )
{
Assert( CalcSampleSize(bits, nChannels ) == pMixer->GetMixSampleSize() );
}
else
{
Assert( 0 );
}
return pMixer;
}
//-----------------------------------------------------------------------------
// Purpose: Init the base WAVE mixer.
// Input : *data - data access object
//-----------------------------------------------------------------------------
CAudioMixerWave::CAudioMixerWave( IWaveData *data ) : m_pData(data)
{
CAudioSource *pSource = GetSource();
if ( pSource )
{
pSource->ReferenceAdd( this );
}
m_fsample_index = 0;
m_sample_max_loaded = 0;
m_sample_loaded_index = -1;
m_finished = false;
m_forcedEndSample = 0;
m_delaySamples = 0;
}
//-----------------------------------------------------------------------------
// Purpose: Frees the data access object (we own it after construction)
//-----------------------------------------------------------------------------
CAudioMixerWave::~CAudioMixerWave( void )
{
CAudioSource *pSource = GetSource();
if ( pSource )
{
pSource->ReferenceRemove( this );
}
delete m_pData;
}
bool CAudioMixerWave::IsReadyToMix()
{
return m_pData->IsReadyToMix();
}
//-----------------------------------------------------------------------------
// Purpose: Decode and read the data
// by default we just pass the request on to the data access object
// other mixers may need to buffer or decode the data for some reason
//
// Input : **pData - dest pointer
// sampleCount - number of samples needed
// Output : number of samples available in this batch
//-----------------------------------------------------------------------------
int CAudioMixerWave::GetOutputData( void **pData, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
{
int samples_loaded;
// clear this out in case the underlying code leaves it unmodified
*pData = NULL;
samples_loaded = m_pData->ReadSourceData( pData, m_sample_max_loaded, sampleCount, copyBuf );
// keep track of total samples loaded
m_sample_max_loaded += samples_loaded;
// keep track of index of last sample loaded
m_sample_loaded_index += samples_loaded;
return samples_loaded;
}
//-----------------------------------------------------------------------------
// Purpose: calls through the wavedata to get the audio source
// Output : CAudioSource
//-----------------------------------------------------------------------------
CAudioSource *CAudioMixerWave::GetSource( void )
{
if ( m_pData )
return &m_pData->Source();
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Gets the current sample location in playback (index of next sample
// to be loaded).
// Output : int (samples from start of wave)
//-----------------------------------------------------------------------------
int CAudioMixerWave::GetSamplePosition( void )
{
return m_sample_max_loaded;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : delaySamples -
//-----------------------------------------------------------------------------
void CAudioMixerWave::SetStartupDelaySamples( int delaySamples )
{
m_delaySamples = delaySamples;
}
// Move the current position to newPosition
void CAudioMixerWave::SetSampleStart( int newPosition )
{
CAudioSource *pSource = GetSource();
if ( pSource )
newPosition = pSource->ZeroCrossingAfter( newPosition );
m_fsample_index = newPosition;
// index of last sample loaded - set to sample at new position
m_sample_loaded_index = newPosition;
m_sample_max_loaded = m_sample_loaded_index + 1;
}
// End playback at newEndPosition
void CAudioMixerWave::SetSampleEnd( int newEndPosition )
{
// forced end of zero means play the whole sample
if ( !newEndPosition )
newEndPosition = 1;
CAudioSource *pSource = GetSource();
if ( pSource )
newEndPosition = pSource->ZeroCrossingBefore( newEndPosition );
// past current position? limit.
if ( newEndPosition < m_fsample_index )
newEndPosition = m_fsample_index;
m_forcedEndSample = newEndPosition;
}
//-----------------------------------------------------------------------------
// Purpose: Skip source data (read but don't mix). The mixer must provide the
// full amount of samples or have silence in its output stream.
//-----------------------------------------------------------------------------
int CAudioMixerWave::SkipSamples( channel_t *pChannel, int sampleCount, int outputRate, int outputOffset )
{
float flTempPitch = pChannel->pitch;
pChannel->pitch = 1.0f;
int nRetVal = MixDataToDevice_( NULL, pChannel, sampleCount, outputRate, outputOffset, true );
pChannel->pitch = flTempPitch;
return nRetVal;
}
// wrapper routine to append without overflowing the temp buffer
static uint AppendToBuffer( char *pBuffer, const char *pSampleData, size_t nBytes, const char *pBufferEnd )
{
#if defined(_WIN32) && !defined(_X360)
// FIXME: Some clients are crashing here. Let's try to detect why.
if ( nBytes > 0 && ( (size_t)pBuffer <= 0xFFF || (size_t)pSampleData <= 0xFFF ) )
{
Warning( "AppendToBuffer received potentially bad values (%p, %p, %u, %p)\n", pBuffer, pSampleData, (int)nBytes, pBufferEnd );
}
#endif
if ( pBufferEnd > pBuffer )
{
size_t nAvail = pBufferEnd - pBuffer;
size_t nCopy = MIN( nBytes, nAvail );
Q_memcpy( pBuffer, pSampleData, nCopy );
return nCopy;
}
else
{
return 0;
}
}
// Load a static copy buffer (g_temppaintbuffer) with the requested number of samples,
// with the first sample(s) in the buffer always set up as the last sample(s) of the previous load.
// Return a pointer to the head of the copy buffer.
// This ensures that interpolating pitch shifters always have the previous sample to reference.
// pChannel: sound's channel data
// sample_load_request: number of samples to load from source data
// pSamplesLoaded: returns the actual number of samples loaded (should always = sample_load_request)
// copyBuf: req'd by GetOutputData, used by some Mixers
// Returns: NULL ptr to data if no samples available, otherwise always fills remainder of copy buffer with
// 0 to pad remainder.
// NOTE: DO NOT MODIFY THIS ROUTINE (KELLYB)
char *CAudioMixerWave::LoadMixBuffer( channel_t *pChannel, int sample_load_request, int *pSamplesLoaded, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
{
int samples_loaded;
char *pSample = NULL;
char *pData = NULL;
int cCopySamps = 0;
// save index of last sample loaded (updated in GetOutputData)
int sample_loaded_index = m_sample_loaded_index;
// get data from source (copyBuf is expected to be available for use)
samples_loaded = GetOutputData( (void **)&pData, sample_load_request, copyBuf );
if ( !samples_loaded && sample_load_request )
{
// none available, bail out
// 360 might not be able to get samples due to latency of loop seek
// could also be the valid EOF for non-loops (caller keeps polling for data, until no more)
AssertOnce( IsX360() || !m_pData->Source().IsLooped() );
*pSamplesLoaded = 0;
return NULL;
}
int samplesize = GetMixSampleSize();
const int nTempCopyBufferSize = ( TEMP_COPY_BUFFER_SIZE * sizeof( portable_samplepair_t ) );
char *pCopy = (char *)g_temppaintbuffer;
const char *pCopyBufferEnd = pCopy + nTempCopyBufferSize;
if ( IsX360() || IsDebug() )
{
// for safety, 360 always validates sample request, due to new xma audio code and possible logic flaws
// PC can expect number of requested samples to be within tolerances due to exisiting aged code
// otherwise buffer overruns cause hard to track random crashes
if ( ( ( sample_load_request + 1 ) * samplesize ) > nTempCopyBufferSize )
{
// make sure requested samples will fit in temp buffer.
// if this assert fails, then pitch is too high (ie: > 2.0) or the sample counters have diverged.
// NOTE: to prevent this, pitch should always be capped in MixDataToDevice (but isn't nor are the sample counters).
DevWarning( "LoadMixBuffer: sample load request %d exceeds buffer sizes\n", sample_load_request );
Assert( 0 );
*pSamplesLoaded = 0;
return NULL;
}
}
// copy all samples from pData to copy buffer, set 0th sample to saved previous sample - this ensures
// interpolation pitch shift routines always have a previous sample to reference.
// copy previous sample(s) to head of copy buffer pCopy
// In some cases, we'll need the previous 2 samples. This occurs when
// Rate < 1.0 - in example below, sample 4.86 - 6.48 requires samples 4-7 (previous samples saved are 4 & 5)
/*
Example:
rate = 0.81, sampleCount = 3 (ie: # of samples to return )
_____load 3______ ____load 3_______ __load 2__
0 1 2 3 4 5 6 7 sample_index (whole samples)
^ ^ ^ ^ ^ ^ ^ ^ ^
| | | | | | | | |
0 0.81 1.68 2.43 3.24 4.05 4.86 5.67 6.48 m_fsample_index (rate*sample)
_______________ ________________ ________________
^ ^ ^ ^
| | | |
m_sample_loaded_index | | m_sample_loaded_index
| |
m_fsample_index---- ----m_fsample_index
[return 3 samp] [return 3 samp] [return 3 samp]
*/
pSample = &(pChannel->sample_prev[0]);
// determine how many saved samples we need to copy to head of copy buffer (0,1 or 2)
// so that pitch interpolation will correctly reference samples.
// NOTE: pitch interpolators always reference the sample before and after the indexed sample.
// cCopySamps = sample_max_loaded - floor(m_fsample_index);
if ( sample_loaded_index < 0 || (floor(m_fsample_index) > sample_loaded_index))
{
// no samples previously loaded, or
// next sample index is entirely within the next block of samples to be loaded,
// so we won't need any samples from the previous block. (can occur when rate > 2.0)
cCopySamps = 0;
}
else if ( m_fsample_index < sample_loaded_index )
{
// next sample index is entirely within the previous block of samples loaded,
// so we'll need the last 2 samples loaded. (can occur when rate < 1.0)
Assert ( ceil(m_fsample_index + 0.00000001) == sample_loaded_index );
cCopySamps = 2;
}
else
{
// next sample index is between the next block and the previously loaded block,
// so we'll need the last sample loaded. (can occur when 1.0 < rate < 2.0)
Assert( floor(m_fsample_index) == sample_loaded_index );
cCopySamps = 1;
}
Assert( cCopySamps >= 0 && cCopySamps <= 2 );
// point to the sample(s) we are to copy
if ( cCopySamps )
{
pSample = cCopySamps == 1 ? pSample + samplesize : pSample;
pCopy += AppendToBuffer( pCopy, pSample, samplesize * cCopySamps, pCopyBufferEnd );
}
// copy loaded samples from pData into pCopy
// and update pointer to free space in copy buffer
if ( ( samples_loaded * samplesize ) != 0 && !pData )
{
char const *pWavName = "";
CSfxTable *source = pChannel->sfx;
if ( source )
{
pWavName = source->getname();
}
Warning( "CAudioMixerWave::LoadMixBuffer: '%s' samples_loaded * samplesize = %i but pData == NULL\n", pWavName, ( samples_loaded * samplesize ) );
*pSamplesLoaded = 0;
return NULL;
}
pCopy += AppendToBuffer( pCopy, pData, samples_loaded * samplesize, pCopyBufferEnd );
// if we loaded fewer samples than we wanted to, and we're not
// delaying, load more samples or, if we run out of samples from non-looping source,
// pad copy buffer.
if ( samples_loaded < sample_load_request )
{
// retry loading source data until 0 bytes returned, or we've loaded enough data.
// if we hit 0 bytes, fill remaining space in copy buffer with 0 and exit
int samples_load_extra;
int samples_loaded_retry = -1;
for ( int k = 0; (k < 10000 && samples_loaded_retry && samples_loaded < sample_load_request); k++ )
{
// how many more samples do we need to satisfy load request
samples_load_extra = sample_load_request - samples_loaded;
samples_loaded_retry = GetOutputData( (void**)&pData, samples_load_extra, copyBuf );
// copy loaded samples from pData into pCopy
if ( samples_loaded_retry )
{
if ( ( samples_loaded_retry * samplesize ) != 0 && !pData )
{
Warning( "CAudioMixerWave::LoadMixBuffer: samples_loaded_retry * samplesize = %i but pData == NULL\n", ( samples_loaded_retry * samplesize ) );
*pSamplesLoaded = 0;
return NULL;
}
pCopy += AppendToBuffer( pCopy, pData, samples_loaded_retry * samplesize, pCopyBufferEnd );
samples_loaded += samples_loaded_retry;
}
}
}
// if we still couldn't load the requested samples, fill rest of copy buffer with 0
if ( samples_loaded < sample_load_request )
{
// should always be able to get as many samples as we request from looping sound sources
AssertOnce ( IsX360() || !m_pData->Source().IsLooped() );
// these samples are filled with 0, not loaded.
// non-looping source hit end of data, fill rest of g_temppaintbuffer with 0
int samples_zero_fill = sample_load_request - samples_loaded;
int nAvail = pCopyBufferEnd - pCopy;
int nFill = samples_zero_fill * samplesize;
nFill = MIN( nAvail, nFill );
Q_memset( pCopy, 0, nFill );
pCopy += nFill;
samples_loaded += samples_zero_fill;
}
if ( samples_loaded >= 2 )
{
// always save last 2 samples from copy buffer to channel
// (we'll need 0,1 or 2 samples as start of next buffer for interpolation)
Assert( sizeof( pChannel->sample_prev ) >= samplesize*2 );
pSample = pCopy - samplesize*2;
Q_memcpy( &(pChannel->sample_prev[0]), pSample, samplesize*2 );
}
// this routine must always return as many samples loaded (or zeros) as requested.
Assert( samples_loaded == sample_load_request );
*pSamplesLoaded = samples_loaded;
return (char *)g_temppaintbuffer;
}
// Helper routine to round (rate * samples) down to fixed point precision
double RoundToFixedPoint( double rate, int samples, bool bInterpolated_pitch )
{
fixedint fixp_rate;
int64 d64_newSamps; // need to use double precision int to avoid overflow
double newSamps;
// get rate, in fixed point, determine new samples at rate
if ( bInterpolated_pitch )
fixp_rate = FIX_FLOAT14(rate); // 14 bit iterator
else
fixp_rate = FIX_FLOAT(rate); // 28 bit iterator
// get number of new samples, convert back to float
d64_newSamps = (int64)fixp_rate * (int64)samples;
if ( bInterpolated_pitch )
newSamps = FIX_14TODOUBLE(d64_newSamps);
else
newSamps = FIX_TODOUBLE(d64_newSamps);
return newSamps;
}
extern double MIX_GetMaxRate( double rate, int sampleCount );
// Helper routine for MixDataToDevice:
// Compute number of new samples to load at 'rate' so we can
// output 'sampleCount' samples, from m_fsample_index to fsample_index_end (inclusive)
// rate: sample rate
// sampleCountOut: number of samples calling routine needs to output
// bInterpolated_pitch: true if mixers use interpolating pitch shifters
int CAudioMixerWave::GetSampleLoadRequest( double rate, int sampleCountOut, bool bInterpolated_pitch )
{
double fsample_index_end; // index of last sample we'll need
int sample_index_high; // rounded up last sample index
int sample_load_request; // number of samples to load
// NOTE: we must use fixed point math here, identical to math in mixers, to make sure
// we predict iteration results exactly.
// get floating point sample index of last sample we'll need
fsample_index_end = m_fsample_index + RoundToFixedPoint( rate, sampleCountOut-1, bInterpolated_pitch );
// always round up to ensure we'll have that n+1 sample for interpolation
sample_index_high = (int)( ceil( fsample_index_end ) );
// make sure we always round the floating point index up by at least 1 sample,
// ie: make sure integer sample_index_high is greater than floating point sample index
if ( (double)sample_index_high <= fsample_index_end )
{
sample_index_high++;
}
Assert ( sample_index_high > fsample_index_end );
// attempt to load enough samples so we can reach sample_index_high sample.
sample_load_request = sample_index_high - m_sample_loaded_index;
Assert( sample_index_high >= m_sample_loaded_index );
// NOTE: we can actually return 0 samples to load if rate < 1.0
// and sampleCountOut == 1. In this case, the output sample
// is computed from the previously saved buffer data.
return sample_load_request;
}
int CAudioMixerWave::MixDataToDevice( IAudioDevice *pDevice, channel_t *pChannel, int sampleCount, int outputRate, int outputOffset )
{
return MixDataToDevice_(pDevice, pChannel, sampleCount, outputRate, outputOffset, false );
}
//-----------------------------------------------------------------------------
// Purpose: The device calls this to request data. The mixer must provide the
// full amount of samples or have silence in its output stream.
// Mix channel to all active paintbuffers.
// NOTE: cannot be called consecutively to mix into multiple paintbuffers!
// Input : *pDevice - requesting device
// sampleCount - number of samples at the output rate - should never be more than size of paintbuffer.
// outputRate - sampling rate of the request
// outputOffset - starting offset to mix to in paintbuffer
// bskipallmixing - true if we just want to skip ahead in source data
// Output : Returns true to keep mixing, false to delete this mixer
// NOTE: DO NOT MODIFY THIS ROUTINE (KELLYB)
//-----------------------------------------------------------------------------
int CAudioMixerWave::MixDataToDevice_( IAudioDevice *pDevice, channel_t *pChannel, int sampleCount, int outputRate, int outputOffset, bool bSkipAllMixing )
{
// shouldn't be playing this if finished, but return if we are
if ( m_finished )
return 0;
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
// save this to compute total output
int startingOffset = outputOffset;
double inputRate = (pChannel->pitch * m_pData->Source().SampleRate());
double rate_max = inputRate / outputRate;
// If we are terminating this wave prematurely, then make sure we detect the limit
if ( m_forcedEndSample )
{
// How many total input samples will we need?
int samplesRequired = (int)(sampleCount * rate_max);
// will this hit the end?
if ( m_fsample_index + samplesRequired >= m_forcedEndSample )
{
// yes, mark finished and truncate the sample request
m_finished = true;
sampleCount = (int)( (m_forcedEndSample - m_fsample_index) / rate_max );
}
}
/*
Example:
rate = 1.2, sampleCount = 3 (ie: # of samples to return )
______load 4 samples_____ ________load 4 samples____ ___load 3 samples__
0 1 2 3 4 5 6 7 8 9 10 sample_index (whole samples)
^ ^ ^ ^ ^ ^ ^ ^ ^
| | | | | | | | |
0 1.2 2.4 3.6 4.8 6.0 7.2 8.4 9.6 m_fsample_index (rate*sample)
_______return 3_______ _______return 3_______ _______return 3__________
^ ^
| |
m_sample_loaded_index----- | (after first load 4 samples, this is where pointers are)
m_fsample_index---------
*/
while ( sampleCount > 0 )
{
bool advanceSample = true;
int samples_loaded, outputSampleCount;
char *pData = NULL;
double fsample_index_prev = m_fsample_index; // save so we can modify in LoadMixBuffer
bool bInterpolated_pitch = FUseHighQualityPitch( pChannel );
double rate;
VPROF_( bInterpolated_pitch ? "CAudioMixerWave::MixData innerloop interpolated" : "CAudioMixerWave::MixData innerloop not interpolated", 2, VPROF_BUDGETGROUP_OTHER_SOUND, false, BUDGETFLAG_OTHER );
// process samples in paintbuffer-sized batches
int sampleCountOut = min( sampleCount, PAINTBUFFER_SIZE );
// cap rate so that we never overflow the input copy buffer.
rate = MIX_GetMaxRate( rate_max, sampleCountOut );
if ( m_delaySamples > 0 )
{
// If we are preceding sample playback with a delay,
// just fill data buffer with 0 value samples.
// Because there is no pitch shift applied, outputSampleCount == sampleCountOut.
int num_zero_samples = min( m_delaySamples, sampleCountOut );
// Decrement delay counter
m_delaySamples -= num_zero_samples;
int sampleSize = GetMixSampleSize();
int readBytes = sampleSize * num_zero_samples;
// make sure we don't overflow temp copy buffer (g_temppaintbuffer)
Assert ( (TEMP_COPY_BUFFER_SIZE * sizeof(portable_samplepair_t)) > readBytes );
pData = (char *)g_temppaintbuffer;
// Now copy in some zeroes
memset( pData, 0, readBytes );
// we don't pitch shift these samples, so outputSampleCount == samples_loaded
samples_loaded = num_zero_samples;
outputSampleCount = num_zero_samples;
advanceSample = false;
// the zero samples are at the output rate, so set the input/output ratio to 1.0
rate = 1.0f;
}
else
{
// ask the source for the data...
// temp buffer req'd by some data loaders
char copyBuf[AUDIOSOURCE_COPYBUF_SIZE];
// compute number of new samples to load at 'rate' so we can
// output 'sampleCount' samples, from m_fsample_index to fsample_index_end (inclusive)
int sample_load_request = GetSampleLoadRequest( rate, sampleCountOut, bInterpolated_pitch );
// return pointer to a new copy buffer (g_temppaintbuffer) loaded with sample_load_request samples +
// first sample(s), which are always the last sample(s) from the previous load.
// Always returns sample_load_request samples. Updates m_sample_max_loaded, m_sample_loaded_index.
pData = LoadMixBuffer( pChannel, sample_load_request, &samples_loaded, copyBuf );
// LoadMixBuffer should always return requested samples.
Assert ( !pData || ( samples_loaded == sample_load_request ) );
outputSampleCount = sampleCountOut;
}
// no samples available
if ( !pData )
{
break;
}
// get sample fraction from 0th sample in copy buffer
double sampleFraction = m_fsample_index - floor( m_fsample_index );
// if just skipping samples in source, don't mix, just keep reading
if ( !bSkipAllMixing )
{
// mix this data to all active paintbuffers
// Verify that we won't get a buffer overrun.
Assert( floor( sampleFraction + RoundToFixedPoint(rate, (outputSampleCount-1), bInterpolated_pitch) ) <= samples_loaded );
int saveIndex = MIX_GetCurrentPaintbufferIndex();
for ( int i = 0 ; i < g_paintBuffers.Count(); i++ )
{
if ( g_paintBuffers[i].factive )
{
// mix channel into all active paintbuffers
MIX_SetCurrentPaintbuffer( i );
Mix(
pDevice, // Device.
pChannel, // Channel.
pData, // Input buffer.
outputOffset, // Output position.
FIX_FLOAT( sampleFraction ), // Iterators.
FIX_FLOAT( rate ),
outputSampleCount,
0 );
}
}
MIX_SetCurrentPaintbuffer( saveIndex );
}
if ( advanceSample )
{
// update sample index to point to the next sample to output
// if we're not delaying
// Use fixed point math to make sure we exactly match results of mix
// iterators.
m_fsample_index = fsample_index_prev + RoundToFixedPoint( rate, outputSampleCount, bInterpolated_pitch );
}
outputOffset += outputSampleCount;
sampleCount -= outputSampleCount;
}
// Did we run out of samples? if so, mark finished
if ( sampleCount > 0 )
{
m_finished = true;
}
// total number of samples mixed !!! at the output clock rate !!!
return outputOffset - startingOffset;
}
bool CAudioMixerWave::ShouldContinueMixing( void )
{
return !m_finished;
}
float CAudioMixerWave::ModifyPitch( float pitch )
{
return pitch;
}
float CAudioMixerWave::GetVolumeScale( void )
{
return 1.0f;
}

View File

@ -0,0 +1,24 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $Workfile: $
// $Date: $
//
//-----------------------------------------------------------------------------
// $Log: $
//
// $NoKeywords: $
//=============================================================================//
#ifndef SND_WAVE_MIXER_H
#define SND_WAVE_MIXER_H
#pragma once
class IWaveData;
class CAudioMixer;
CAudioMixer *CreateWaveMixer( IWaveData *data, int format, int channels, int bits, int initialStreamPosition );
#endif // SND_WAVE_MIXER_H

View File

@ -0,0 +1,469 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#include "audio_pch.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// max size of ADPCM block in bytes
#define MAX_BLOCK_SIZE 4096
//-----------------------------------------------------------------------------
// Purpose: Mixer for ADPCM encoded audio
//-----------------------------------------------------------------------------
class CAudioMixerWaveADPCM : public CAudioMixerWave
{
public:
CAudioMixerWaveADPCM( IWaveData *data );
~CAudioMixerWaveADPCM( void );
virtual void Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress );
virtual int GetOutputData( void **pData, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] );
// need to override this to fixup blocks
void SetSampleStart( int newPosition );
virtual int GetMixSampleSize() { return CalcSampleSize( 16, NumChannels() ); }
private:
bool DecodeBlock( void );
int NumChannels( void );
void DecompressBlockMono( short *pOut, const char *pIn, int count );
void DecompressBlockStereo( short *pOut, const char *pIn, int count );
const ADPCMWAVEFORMAT *m_pFormat;
const ADPCMCOEFSET *m_pCoefficients;
short *m_pSamples;
int m_sampleCount;
int m_samplePosition;
int m_blockSize;
int m_offset;
int m_totalBytes;
};
CAudioMixerWaveADPCM::CAudioMixerWaveADPCM( IWaveData *data ) : CAudioMixerWave( data )
{
m_pSamples = NULL;
m_sampleCount = 0;
m_samplePosition = 0;
m_offset = 0;
CAudioSourceWave &source = reinterpret_cast<CAudioSourceWave &>(m_pData->Source());
#ifdef _DEBUG
CAudioSource *pSource = NULL;
pSource = &m_pData->Source();
Assert( dynamic_cast<CAudioSourceWave *>(pSource) != NULL );
#endif
m_pFormat = (const ADPCMWAVEFORMAT *)source.GetHeader();
if ( m_pFormat )
{
m_pCoefficients = (ADPCMCOEFSET *)((char *)m_pFormat + sizeof(WAVEFORMATEX) + 4);
// create the decode buffer
m_pSamples = new short[m_pFormat->wSamplesPerBlock * m_pFormat->wfx.nChannels];
// number of bytes for samples
m_blockSize = ((m_pFormat->wSamplesPerBlock - 2) * m_pFormat->wfx.nChannels ) / 2;
// size of channel header
m_blockSize += 7 * m_pFormat->wfx.nChannels;
Assert( m_blockSize < MAX_BLOCK_SIZE );
m_totalBytes = source.DataSize();
}
}
CAudioMixerWaveADPCM::~CAudioMixerWaveADPCM( void )
{
delete[] m_pSamples;
}
int CAudioMixerWaveADPCM::NumChannels( void )
{
if ( m_pFormat )
{
return m_pFormat->wfx.nChannels;
}
return 0;
}
void CAudioMixerWaveADPCM::Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress )
{
if ( NumChannels() == 1 )
pDevice->Mix16Mono( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress );
else
pDevice->Mix16Stereo( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress );
}
static int error_sign_lut[] = { 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1 };
static int error_coefficients_lut[] = { 230, 230, 230, 230, 307, 409, 512, 614,
768, 614, 512, 409, 307, 230, 230, 230 };
//-----------------------------------------------------------------------------
// Purpose: ADPCM decompress a single block of 1-channel audio
// Input : *pOut - output buffer 16-bit
// *pIn - input block
// count - number of samples to decode (to support partial blocks)
//-----------------------------------------------------------------------------
void CAudioMixerWaveADPCM::DecompressBlockMono( short *pOut, const char *pIn, int count )
{
int pred = *pIn++;
int co1 = m_pCoefficients[pred].iCoef1;
int co2 = m_pCoefficients[pred].iCoef2;
// read initial delta
int delta = *((short *)pIn);
pIn += 2;
// read initial samples for prediction
int samp1 = *((short *)pIn);
pIn += 2;
int samp2 = *((short *)pIn);
pIn += 2;
// write out the initial samples (stored in reverse order)
*pOut++ = (short)samp2;
*pOut++ = (short)samp1;
// subtract the 2 samples in the header
count -= 2;
// this is a toggle to read nibbles, first nibble is high
int high = 1;
int error, sample=0;
// now process the block
while ( count )
{
// read the error nibble from the input stream
if ( high )
{
sample = (unsigned char) (*pIn++);
// high nibble
error = sample >> 4;
// cache low nibble for next read
sample = sample & 0xf;
// Next read is from cache, not stream
high = 0;
}
else
{
// stored in previous read (low nibble)
error = sample;
// next read is from stream
high = 1;
}
// convert to signed with LUT
int errorSign = error_sign_lut[error];
// interpolate the new sample
int predSample = (samp1 * co1) + (samp2 * co2);
// coefficients are fixed point 8-bit, so shift back to 16-bit integer
predSample >>= 8;
// Add in current error estimate
predSample += (errorSign * delta);
// Correct error estimate
delta = (delta * error_coefficients_lut[error]) >> 8;
// Clamp error estimate
if ( delta < 16 )
delta = 16;
// clamp
if ( predSample > 32767L )
predSample = 32767L;
else if ( predSample < -32768L )
predSample = -32768L;
// output
*pOut++ = (short)predSample;
// move samples over
samp2 = samp1;
samp1 = predSample;
count--;
}
}
//-----------------------------------------------------------------------------
// Purpose: Decode a single block of stereo ADPCM audio
// Input : *pOut - 16-bit output buffer
// *pIn - ADPCM encoded block data
// count - number of sample pairs to decode
//-----------------------------------------------------------------------------
void CAudioMixerWaveADPCM::DecompressBlockStereo( short *pOut, const char *pIn, int count )
{
int pred[2], co1[2], co2[2];
int i;
for ( i = 0; i < 2; i++ )
{
pred[i] = *pIn++;
co1[i] = m_pCoefficients[pred[i]].iCoef1;
co2[i] = m_pCoefficients[pred[i]].iCoef2;
}
int delta[2], samp1[2], samp2[2];
for ( i = 0; i < 2; i++, pIn += 2 )
{
// read initial delta
delta[i] = *((short *)pIn);
}
// read initial samples for prediction
for ( i = 0; i < 2; i++, pIn += 2 )
{
samp1[i] = *((short *)pIn);
}
for ( i = 0; i < 2; i++, pIn += 2 )
{
samp2[i] = *((short *)pIn);
}
// write out the initial samples (stored in reverse order)
*pOut++ = (short)samp2[0]; // left
*pOut++ = (short)samp2[1]; // right
*pOut++ = (short)samp1[0]; // left
*pOut++ = (short)samp1[1]; // right
// subtract the 2 samples in the header
count -= 2;
// this is a toggle to read nibbles, first nibble is high
int high = 1;
int error, sample=0;
// now process the block
while ( count )
{
for ( i = 0; i < 2; i++ )
{
// read the error nibble from the input stream
if ( high )
{
sample = (unsigned char) (*pIn++);
// high nibble
error = sample >> 4;
// cache low nibble for next read
sample = sample & 0xf;
// Next read is from cache, not stream
high = 0;
}
else
{
// stored in previous read (low nibble)
error = sample;
// next read is from stream
high = 1;
}
// convert to signed with LUT
int errorSign = error_sign_lut[error];
// interpolate the new sample
int predSample = (samp1[i] * co1[i]) + (samp2[i] * co2[i]);
// coefficients are fixed point 8-bit, so shift back to 16-bit integer
predSample >>= 8;
// Add in current error estimate
predSample += (errorSign * delta[i]);
// Correct error estimate
delta[i] = (delta[i] * error_coefficients_lut[error]) >> 8;
// Clamp error estimate
if ( delta[i] < 16 )
delta[i] = 16;
// clamp
if ( predSample > 32767L )
predSample = 32767L;
else if ( predSample < -32768L )
predSample = -32768L;
// output
*pOut++ = (short)predSample;
// move samples over
samp2[i] = samp1[i];
samp1[i] = predSample;
}
count--;
}
}
//-----------------------------------------------------------------------------
// Purpose: Read data from the source and pass it to the appropriate decompress
// routine.
// Output : Returns true if data was decoded, false if none.
//-----------------------------------------------------------------------------
bool CAudioMixerWaveADPCM::DecodeBlock( void )
{
char tmpBlock[MAX_BLOCK_SIZE];
char *pData;
int blockSize;
int firstSample;
// fixup position with possible loop
CAudioSourceWave &source = reinterpret_cast<CAudioSourceWave &>(m_pData->Source());
m_offset = source.ConvertLoopedPosition( m_offset );
if ( m_offset >= m_totalBytes )
{
// no more data
return false;
}
// can only decode in block sized chunks
firstSample = m_offset % m_blockSize;
m_offset = m_offset - firstSample;
// adpcm must calculate and request correct block size for proper decoding
// last block size may be truncated
blockSize = m_totalBytes - m_offset;
if ( blockSize > m_blockSize )
{
blockSize = m_blockSize;
}
// get requested data
int available = m_pData->ReadSourceData( (void **)(&pData), m_offset, blockSize, NULL );
if ( available < blockSize )
{
// pump to get all of requested data
int total = 0;
while ( available && total < blockSize )
{
memcpy( tmpBlock + total, pData, available );
total += available;
available = m_pData->ReadSourceData( (void **)(&pData), m_offset + total, blockSize - total, NULL );
}
pData = tmpBlock;
available = total;
}
if ( !available )
{
// no more data
return false;
}
// advance the file pointer
m_offset += available;
int channelCount = NumChannels();
// this is sample pairs for stereo, samples for mono
m_sampleCount = m_pFormat->wSamplesPerBlock;
// short block?, fixup sample count (2 samples per byte, divided by number of channels per sample set)
m_sampleCount -= ((m_blockSize - available) * 2) / channelCount;
// new block, start at the first sample
m_samplePosition = firstSample;
// no need to subclass for different channel counts...
if ( channelCount == 1 )
{
DecompressBlockMono( m_pSamples, pData, m_sampleCount );
}
else
{
DecompressBlockStereo( m_pSamples, pData, m_sampleCount );
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Read existing buffer or decompress a new block when necessary
// Input : **pData - output data pointer
// sampleCount - number of samples (or pairs)
// Output : int - available samples (zero to stop decoding)
//-----------------------------------------------------------------------------
int CAudioMixerWaveADPCM::GetOutputData( void **pData, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
{
if ( m_samplePosition >= m_sampleCount )
{
if ( !DecodeBlock() )
return 0;
}
if ( m_pSamples && m_samplePosition < m_sampleCount )
{
*pData = (void *)(m_pSamples + m_samplePosition * NumChannels());
int available = m_sampleCount - m_samplePosition;
if ( available > sampleCount )
available = sampleCount;
m_samplePosition += available;
// update count of max samples loaded in CAudioMixerWave
CAudioMixerWave::m_sample_max_loaded += available;
// update index of last sample loaded
CAudioMixerWave::m_sample_loaded_index += available;
return available;
}
return 0;
}
//-----------------------------------------------------------------------------
// Purpose: Seek to a new position in the file
// NOTE: In most cases, only call this once, and call it before playing
// any data.
// Input : newPosition - new position in the sample clocks of this sample
//-----------------------------------------------------------------------------
void CAudioMixerWaveADPCM::SetSampleStart( int newPosition )
{
// cascade to base wave to update sample counter
CAudioMixerWave::SetSampleStart( newPosition );
// which block is the desired starting sample in?
int blockStart = newPosition / m_pFormat->wSamplesPerBlock;
// how far into the block is the sample
int blockOffset = newPosition % m_pFormat->wSamplesPerBlock;
// set the file position
m_offset = blockStart * m_blockSize;
// NOTE: Must decode a block here to properly position the sample Index
// THIS MEANS YOU DON'T WANT TO CALL THIS ROUTINE OFTEN FOR ADPCM SOUNDS
DecodeBlock();
// limit to the samples decoded
if ( blockOffset < m_sampleCount )
blockOffset = m_sampleCount;
// set the new current position
m_samplePosition = blockOffset;
}
//-----------------------------------------------------------------------------
// Purpose: Abstract factory function for ADPCM mixers
// Input : *data - wave data access object
// channels -
// Output : CAudioMixer
//-----------------------------------------------------------------------------
CAudioMixer *CreateADPCMMixer( IWaveData *data )
{
return new CAudioMixerWaveADPCM( data );
}

View File

@ -0,0 +1,24 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $Workfile: $
// $Date: $
//
//-----------------------------------------------------------------------------
// $Log: $
//
// $NoKeywords: $
//=============================================================================//
#ifndef SND_WAVE_MIXER_ADPCM_H
#define SND_WAVE_MIXER_ADPCM_H
#pragma once
class CAudioMixer;
class IWaveData;
CAudioMixer *CreateADPCMMixer( IWaveData *data );
#endif // SND_WAVE_MIXER_ADPCM_H

View File

@ -0,0 +1,238 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "audio_pch.h"
#include "snd_mp3_source.h"
#include "snd_wave_mixer_mp3.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#ifndef DEDICATED // have to test this because VPC is forcing us to compile this file.
extern IVAudio *vaudio;
CAudioMixerWaveMP3::CAudioMixerWaveMP3( IWaveData *data ) : CAudioMixerWave( data )
{
m_sampleCount = 0;
m_samplePosition = 0;
m_offset = 0;
m_delaySamples = 0;
m_headerOffset = 0;
m_pStream = NULL;
m_bStreamInit = false;
m_channelCount = 0;
}
CAudioMixerWaveMP3::~CAudioMixerWaveMP3( void )
{
if ( m_pStream )
delete m_pStream;
}
void CAudioMixerWaveMP3::Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress )
{
Assert( IsReadyToMix() );
if ( m_channelCount == 1 )
pDevice->Mix16Mono( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress );
else
pDevice->Mix16Stereo( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress );
}
// Some MP3 files are wrapped in ID3
void CAudioMixerWaveMP3::GetID3HeaderOffset()
{
char copyBuf[AUDIOSOURCE_COPYBUF_SIZE];
byte *pData;
int bytesRead = m_pData->ReadSourceData( (void **)&pData, 0, 10, copyBuf );
if ( bytesRead < 10 )
return;
m_headerOffset = 0;
if (( pData[ 0 ] == 0x49 ) &&
( pData[ 1 ] == 0x44 ) &&
( pData[ 2 ] == 0x33 ) &&
( pData[ 3 ] < 0xff ) &&
( pData[ 4 ] < 0xff ) &&
( pData[ 6 ] < 0x80 ) &&
( pData[ 7 ] < 0x80 ) &&
( pData[ 8 ] < 0x80 ) &&
( pData[ 9 ] < 0x80 ) )
{
// this is in id3 file
// compute the size of the wrapper and skip it
m_headerOffset = 10 + ( pData[9] | (pData[8]<<7) | (pData[7]<<14) | (pData[6]<<21) );
}
}
int CAudioMixerWaveMP3::StreamRequestData( void *pBuffer, int bytesRequested, int offset )
{
if ( offset < 0 )
{
offset = m_offset;
}
else
{
m_offset = offset;
}
// read the data out of the source
int totalBytesRead = 0;
if ( offset == 0 )
{
// top of file, check for ID3 wrapper
GetID3HeaderOffset();
}
offset += m_headerOffset; // skip any id3 header/wrapper
while ( bytesRequested > 0 )
{
char *pOutputBuffer = (char *)pBuffer;
pOutputBuffer += totalBytesRead;
void *pData = NULL;
int bytesRead = m_pData->ReadSourceData( &pData, offset + totalBytesRead, bytesRequested, pOutputBuffer );
if ( !bytesRead )
break;
if ( bytesRead > bytesRequested )
{
bytesRead = bytesRequested;
}
// if the source is buffering it, copy it to the MP3 decomp buffer
if ( pData != pOutputBuffer )
{
memcpy( pOutputBuffer, pData, bytesRead );
}
totalBytesRead += bytesRead;
bytesRequested -= bytesRead;
}
m_offset += totalBytesRead;
return totalBytesRead;
}
bool CAudioMixerWaveMP3::DecodeBlock()
{
IAudioStream *pStream = GetStream();
if ( !pStream )
{
return false;
}
m_sampleCount = pStream->Decode( m_samples, sizeof(m_samples) );
m_samplePosition = 0;
return m_sampleCount > 0;
}
IAudioStream *CAudioMixerWaveMP3::GetStream()
{
if ( !m_bStreamInit )
{
m_bStreamInit = true;
if ( vaudio )
{
m_pStream = vaudio->CreateMP3StreamDecoder( static_cast<IAudioStreamEvent *>(this) );
}
else
{
Warning( "Attempting to play MP3 with no vaudio [ %s ]\n", m_pData->Source().GetFileName() );
}
if ( m_pStream )
{
m_channelCount = m_pStream->GetOutputChannels();
//Assert( m_pStream->GetOutputRate() == m_pData->Source().SampleRate() );
}
if ( !m_pStream )
{
Warning( "Failed to create decoder for MP3 [ %s ]\n", m_pData->Source().GetFileName() );
}
}
return m_pStream;
}
//-----------------------------------------------------------------------------
// Purpose: Read existing buffer or decompress a new block when necessary
// Input : **pData - output data pointer
// sampleCount - number of samples (or pairs)
// Output : int - available samples (zero to stop decoding)
//-----------------------------------------------------------------------------
int CAudioMixerWaveMP3::GetOutputData( void **pData, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
{
if ( m_samplePosition >= m_sampleCount )
{
if ( !DecodeBlock() )
return 0;
}
IAudioStream *pStream = GetStream();
if ( !pStream )
{
// Needed for channel count, and with a failed stream init we probably should fail to return data anyway.
return 0;
}
if ( m_samplePosition < m_sampleCount )
{
int sampleSize = pStream->GetOutputChannels() * 2;
*pData = (void *)(m_samples + m_samplePosition);
int available = m_sampleCount - m_samplePosition;
int bytesRequired = sampleCount * sampleSize;
if ( available > bytesRequired )
available = bytesRequired;
m_samplePosition += available;
int samples_loaded = available / sampleSize;
// update count of max samples loaded in CAudioMixerWave
CAudioMixerWave::m_sample_max_loaded += samples_loaded;
// update index of last sample loaded
CAudioMixerWave::m_sample_loaded_index += samples_loaded;
return samples_loaded;
}
return 0;
}
//-----------------------------------------------------------------------------
// Purpose: Seek to a new position in the file
// NOTE: In most cases, only call this once, and call it before playing
// any data.
// Input : newPosition - new position in the sample clocks of this sample
//-----------------------------------------------------------------------------
void CAudioMixerWaveMP3::SetSampleStart( int newPosition )
{
// UNDONE: Implement this?
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : delaySamples -
//-----------------------------------------------------------------------------
void CAudioMixerWaveMP3::SetStartupDelaySamples( int delaySamples )
{
m_delaySamples = delaySamples;
}
#endif

View File

@ -0,0 +1,59 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Mixer for ADPCM encoded audio
//
//=============================================================================//
#ifndef SND_WAVE_MIXER_MP3_H
#define SND_WAVE_MIXER_MP3_H
#pragma once
#include "vaudio/ivaudio.h"
static const int MP3_BUFFER_SIZE = 16384;
class CAudioMixerWaveMP3 : public CAudioMixerWave, public IAudioStreamEvent
{
public:
CAudioMixerWaveMP3( IWaveData *data );
~CAudioMixerWaveMP3( void );
virtual void Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress );
virtual int GetOutputData( void **pData, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] );
// need to override this to fixup blocks
// UNDONE: This doesn't quite work with MP3 - we need a MP3 position, not a sample position
void SetSampleStart( int newPosition );
int GetPositionForSave() { return GetStream() ? GetStream()->GetPosition() : 0; }
void SetPositionFromSaved(int position) { if ( GetStream() ) GetStream()->SetPosition(position); }
// IAudioStreamEvent
virtual int StreamRequestData( void *pBuffer, int bytesRequested, int offset );
virtual void SetStartupDelaySamples( int delaySamples );
virtual int GetMixSampleSize() { return CalcSampleSize( 16, m_channelCount ); }
virtual int GetStreamOutputRate() { return GetStream() ? GetStream()->GetOutputRate() : 0; }
private:
IAudioStream *GetStream();
bool DecodeBlock( void );
void GetID3HeaderOffset();
// Lazily initialized, use GetStream
IAudioStream *m_pStream;
bool m_bStreamInit;
char m_samples[MP3_BUFFER_SIZE];
int m_sampleCount;
int m_samplePosition;
int m_channelCount;
int m_offset;
int m_delaySamples;
int m_headerOffset;
};
CAudioMixerWaveMP3 *CreateMP3Mixer( IWaveData *data );
#endif // SND_WAVE_MIXER_MP3_H

View File

@ -0,0 +1,74 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $Workfile: $
// $Date: $
//
//-----------------------------------------------------------------------------
// $Log: $
//
// $NoKeywords: $
//=============================================================================//
#ifndef SND_WAVE_MIXER_PRIVATE_H
#define SND_WAVE_MIXER_PRIVATE_H
#pragma once
#include "snd_audio_source.h"
#include "snd_wave_mixer.h"
#include "sound_private.h"
#include "snd_wave_source.h"
class IWaveData;
abstract_class CAudioMixerWave : public CAudioMixer
{
public:
CAudioMixerWave( IWaveData *data );
virtual ~CAudioMixerWave( void );
int MixDataToDevice( IAudioDevice *pDevice, channel_t *pChannel, int sampleCount, int outputRate, int outputOffset );
int SkipSamples( channel_t *pChannel, int sampleCount, int outputRate, int outputOffset );
bool ShouldContinueMixing( void );
virtual void Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress ) = 0;
virtual int GetOutputData( void **pData, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] );
virtual CAudioSource* GetSource( void );
virtual int GetSamplePosition( void );
virtual float ModifyPitch( float pitch );
virtual float GetVolumeScale( void );
// Move the current position to newPosition
virtual void SetSampleStart( int newPosition );
// End playback at newEndPosition
virtual void SetSampleEnd( int newEndPosition );
virtual void SetStartupDelaySamples( int delaySamples );
// private helper routines
char * LoadMixBuffer( channel_t *pChannel, int sample_load_request, int *psamples_loaded, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] );
int MixDataToDevice_( IAudioDevice *pDevice, channel_t *pChannel, int sampleCount, int outputRate, int outputOffset, bool bSkipAllSamples );
int GetSampleLoadRequest( double rate, int sampleCount, bool bInterpolated_pitch );
virtual bool IsReadyToMix();
virtual int GetPositionForSave() { return GetSamplePosition(); }
virtual void SetPositionFromSaved( int savedPosition ) { SetSampleStart(savedPosition); }
protected:
double m_fsample_index; // index of next sample to output
int m_sample_max_loaded; // count of total samples loaded - ie: the index of
// the next sample to be loaded.
int m_sample_loaded_index; // index of last sample loaded
IWaveData *m_pData;
double m_forcedEndSample;
bool m_finished;
int m_delaySamples;
};
#endif // SND_WAVE_MIXER_PRIVATE_H

View File

@ -0,0 +1,959 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: XMA Decoding
//
//=====================================================================================//
#include "audio_pch.h"
#include "tier1/mempool.h"
#include "circularbuffer.h"
#include "tier1/utllinkedlist.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//#define DEBUG_XMA
// Failed attempt to allow mixer to request data that is immediately discarded
// to support < 0 delay samples
//#define ALLOW_SKIP_SAMPLES
// XMA is supposed to decode at an ideal max of 512 mono samples every 4msec.
// XMA can only peel a max of 1984 stereo samples per poll request (if available).
// Max is not achievable and degrades based on quality settings, stereo, etc, but using these numbers for for calcs.
// 1984 stereo samples should be decoded by xma in 31 msec.
// 1984 stereo samples at 44.1Khz dictates a request every 45 msec.
// GetOutputData() must be clocked faster than 45 msec or samples will not be available.
// However, the XMA decoder must be serviced much faster. It was designed for 5 msec.
// 15 msec seems to be fast enough for XMA to decode enough to keep the smaller buffer sizes satisfied, and have slop for +/- 5 msec swings.
// Need at least this amount of decoded pcm samples before mixing can commence.
// This needs to be able to cover the initial mix request, while a new decode cycle is in flight.
#define MIN_READYTOMIX ( ( 2 * XMA_POLL_RATE ) * 0.001f )
// number of samples that xma decodes
// must be 128 aligned for mono (1984 is hw max for stereo)
#define XMA_MONO_OUTPUT_BUFFER_SAMPLES 2048
#define XMA_STEREO_OUTPUT_BUFFER_SAMPLES 1920
// for decoder input
// xma blocks are fetched from the datacache into one of these hw buffers for decoding
// must be in quantum units of XMA_BLOCK_SIZE
#define XMA_INPUT_BUFFER_SIZE ( 8 * XMA_BLOCK_SIZE )
// circular staging buffer to drain xma decoder and stage until mixer requests
// must be large enough to hold the slowest expected mixing frame worth of samples
#define PCM_STAGING_BUFFER_TIME 200
// xma physical heap, supplies xma input buffers for hw decoder
// each potential channel must be able to peel 2 buffers for driving xma decoder
#define XMA_PHYSICAL_HEAP_SIZE ( 2 * MAX_CHANNELS * XMA_INPUT_BUFFER_SIZE )
// in millseconds
#define MIX_IO_DATA_TIMEOUT 2000 // async i/o from dvd could be very late
#define MIX_DECODER_TIMEOUT 3000 // decoder might be very busy
#define MIX_DECODER_POLLING_LATENCY 5 // not faster than 5ms, or decoder will sputter
// diagnostic errors
#define ERROR_IO_DATA_TIMEOUT -1 // async i/o taking too long to deliver xma blocks
#define ERROR_IO_TRUNCATED_BLOCK -2 // async i/o failed to deliver complete blocks
#define ERROR_IO_NO_XMA_DATA -3 // async i/o failed to deliver any block
#define ERROR_DECODER_TIMEOUT -4 // decoder taking too long to decode xma blocks
#define ERROR_OUT_OF_MEMORY -5 // not enough physical memory for xma blocks
#define ERROR_XMA_PARSE -6 // decoder barfed on xma blocks
#define ERROR_XMA_CANTLOCK -7 // hw not acting as expected
#define ERROR_XMA_CANTSUBMIT -8 // hw not acting as expected
#define ERROR_XMA_CANTRESUME -9 // hw not acting as expected
#define ERROR_XMA_NO_PCM_DATA -10 // no xma decoded pcm data ready
#define ERROR_NULL_BUFFER -11 // logic flaw, expected buffer is null
const char *g_XMAErrorStrings[] =
{
"Unknown Error Code",
"Async I/O Data Timeout", // ERROR_IO_DATA_TIMEOUT
"Async I/O Truncated Block", // ERROR_IO_TRUNCATED_BLOCK
"Async I/O Data Not Ready", // ERROR_IO_NO_XMA_DATA
"Decoder Timeout", // ERROR_DECODER_TIMEOUT
"Out Of Memory", // ERROR_OUT_OF_MEMORY
"XMA Parse", // ERROR_XMA_PARSE
"XMA Cannot Lock", // ERROR_XMA_CANTLOCK
"XMA Cannot Submit", // ERROR_XMA_CANTSUBMIT
"XMA Cannot Resume", // ERROR_XMA_CANTRESUME
"XMA No PCM Data Ready", // ERROR_XMA_NO_PCM_DATA
"NULL Buffer", // ERROR_NULL_BUFFER
};
class CXMAAllocator
{
public:
static void *Alloc( int bytes )
{
MEM_ALLOC_CREDIT();
return XMemAlloc( bytes,
MAKE_XALLOC_ATTRIBUTES(
0,
false,
TRUE,
FALSE,
eXALLOCAllocatorId_XAUDIO,
XALLOC_PHYSICAL_ALIGNMENT_4K,
XALLOC_MEMPROTECT_WRITECOMBINE_LARGE_PAGES,
FALSE,
XALLOC_MEMTYPE_PHYSICAL ) );
}
static void Free( void *p )
{
XMemFree( p,
MAKE_XALLOC_ATTRIBUTES(
0,
false,
TRUE,
FALSE,
eXALLOCAllocatorId_XAUDIO,
XALLOC_PHYSICAL_ALIGNMENT_4K,
XALLOC_MEMPROTECT_WRITECOMBINE_LARGE_PAGES,
FALSE,
XALLOC_MEMTYPE_PHYSICAL ) );
}
};
// for XMA decoding, fixed size allocations aligned to 4K from a single physical heap
CAlignedMemPool< XMA_INPUT_BUFFER_SIZE, 4096, XMA_PHYSICAL_HEAP_SIZE, CXMAAllocator > g_XMAMemoryPool;
ConVar snd_xma_spew_warnings( "snd_xma_spew_warnings", "0" );
ConVar snd_xma_spew_startup( "snd_xma_spew_startup", "0" );
ConVar snd_xma_spew_mixers( "snd_xma_spew_mixers", "0" );
ConVar snd_xma_spew_decode( "snd_xma_spew_decode", "0" );
ConVar snd_xma_spew_drain( "snd_xma_spew_drain", "0" );
#ifdef DEBUG_XMA
ConVar snd_xma_record( "snd_xma_record", "0" );
ConVar snd_xma_spew_errors( "snd_xma_spew_errors", "0" );
#endif
//-----------------------------------------------------------------------------
// Purpose: Mixer for ADPCM encoded audio
//-----------------------------------------------------------------------------
class CAudioMixerWaveXMA : public CAudioMixerWave
{
public:
typedef CAudioMixerWave BaseClass;
CAudioMixerWaveXMA( IWaveData *data, int initialStreamPosition );
~CAudioMixerWaveXMA( void );
virtual void Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress );
virtual int GetOutputData( void **pData, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] );
virtual void SetSampleStart( int newPosition );
virtual int GetPositionForSave();
virtual void SetPositionFromSaved( int savedPosition );
virtual int GetMixSampleSize() { return CalcSampleSize( 16, m_NumChannels ); }
virtual bool IsReadyToMix();
virtual bool ShouldContinueMixing();
private:
int GetXMABlocksAndSubmitToDecoder( bool bDecoderLocked );
int UpdatePositionForLooping( int *pNumRequestedSamples );
int ServiceXMADecoder( bool bForceUpdate );
int GetPCMSamples( int numRequested, char *pData );
XMAPLAYBACK *m_pXMAPlayback;
// input buffers, encoded xma
byte *m_pXMABuffers[2];
int m_XMABufferIndex;
// output buffer, decoded pcm samples, a staging circular buffer, waiting for mixer requests
// due to staging nature, contains decoded samples from multiple input buffers
CCircularBuffer *m_pPCMSamples;
int m_SampleRate;
int m_NumChannels;
// maximum possible decoded samples
int m_SampleCount;
// decoded sample position
int m_SamplePosition;
// current data marker
int m_LastDataOffset;
int m_DataOffset;
// total bytes of data
int m_TotalBytes;
#if defined( ALLOW_SKIP_SAMPLES )
// number of samples to throwaway
int m_SkipSamples;
#endif
// timers
unsigned int m_StartTime;
unsigned int m_LastDrainTime;
unsigned int m_LastPollTime;
int m_hMixerList;
int m_Error;
unsigned int m_bStartedMixing : 1;
unsigned int m_bFinished : 1;
unsigned int m_bLooped : 1;
};
CUtlFixedLinkedList< CAudioMixerWaveXMA * > g_XMAMixerList;
CON_COMMAND( snd_xma_info, "Spew XMA Info" )
{
Msg( "XMA Memory:\n" );
Msg( " Blocks Allocated: %d\n", g_XMAMemoryPool.NumAllocated() );
Msg( " Blocks Free: %d\n", g_XMAMemoryPool.NumFree() );
Msg( " Total Bytes: %d\n", g_XMAMemoryPool.BytesTotal() );
Msg( "Active XMA Mixers: %d\n", g_XMAMixerList.Count() );
for ( int hMixer = g_XMAMixerList.Head(); hMixer != g_XMAMixerList.InvalidIndex(); hMixer = g_XMAMixerList.Next( hMixer ) )
{
CAudioMixerWaveXMA *pXMAMixer = g_XMAMixerList[hMixer];
Msg( " rate:%5d ch:%1d '%s'\n", pXMAMixer->GetSource()->SampleRate(), pXMAMixer->GetSource()->IsStereoWav() ? 2 : 1, pXMAMixer->GetSource()->GetFileName() );
}
}
CAudioMixerWaveXMA::CAudioMixerWaveXMA( IWaveData *data, int initialStreamPosition ) : CAudioMixerWave( data )
{
Assert( dynamic_cast<CAudioSourceWave *>(&m_pData->Source()) != NULL );
m_Error = 0;
m_NumChannels = m_pData->Source().IsStereoWav() ? 2 : 1;
m_SampleRate = m_pData->Source().SampleRate();
m_bLooped = m_pData->Source().IsLooped();
m_SampleCount = m_pData->Source().SampleCount();
m_TotalBytes = m_pData->Source().DataSize();
#if defined( ALLOW_SKIP_SAMPLES )
m_SkipSamples = 0;
#endif
m_LastDataOffset = initialStreamPosition;
m_DataOffset = initialStreamPosition;
m_SamplePosition = 0;
if ( initialStreamPosition )
{
m_SamplePosition = m_pData->Source().StreamToSamplePosition( initialStreamPosition );
CAudioMixerWave::m_sample_loaded_index = m_SamplePosition;
CAudioMixerWave::m_sample_max_loaded = m_SamplePosition + 1;
}
m_bStartedMixing = false;
m_bFinished = false;
m_StartTime = 0;
m_LastPollTime = 0;
m_LastDrainTime = 0;
m_pXMAPlayback = NULL;
m_pPCMSamples = NULL;
m_pXMABuffers[0] = NULL;
m_pXMABuffers[1] = NULL;
m_XMABufferIndex = 0;
m_hMixerList = g_XMAMixerList.AddToTail( this );
#ifdef DEBUG_XMA
if ( snd_xma_record.GetBool() )
{
WaveCreateTmpFile( "debug.wav", m_SampleRate, 16, m_NumChannels );
}
#endif
if ( snd_xma_spew_mixers.GetBool() )
{
Msg( "XMA: 0x%8.8x (%2d), Mixer Alloc, '%s'\n", (unsigned int)this, g_XMAMixerList.Count(), m_pData->Source().GetFileName() );
}
}
CAudioMixerWaveXMA::~CAudioMixerWaveXMA( void )
{
if ( m_pXMAPlayback )
{
XMAPlaybackDestroy( m_pXMAPlayback );
g_XMAMemoryPool.Free( m_pXMABuffers[0] );
if ( m_pXMABuffers[1] )
{
g_XMAMemoryPool.Free( m_pXMABuffers[1] );
}
}
if ( m_pPCMSamples )
{
FreeCircularBuffer( m_pPCMSamples );
}
g_XMAMixerList.Remove( m_hMixerList );
if ( snd_xma_spew_mixers.GetBool() )
{
Msg( "XMA: 0x%8.8x (%2d), Mixer Freed, '%s'\n", (unsigned int)this, g_XMAMixerList.Count(), m_pData->Source().GetFileName() );
}
}
void CAudioMixerWaveXMA::Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress )
{
if ( m_NumChannels == 1 )
{
pDevice->Mix16Mono( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress );
}
else
{
pDevice->Mix16Stereo( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress );
}
}
//-----------------------------------------------------------------------------
// Looping is achieved in two passes to provide a circular view of the linear data.
// Pass1: Clamps a sample request to the end of data.
// Pass2: Snaps to the loop start, and returns the number of samples to discard, could be 0,
// up to the expected loop sample position.
// Returns the number of samples to discard, or 0.
//-----------------------------------------------------------------------------
int CAudioMixerWaveXMA::UpdatePositionForLooping( int *pNumRequestedSamples )
{
if ( !m_bLooped )
{
// not looping, no fixups
return 0;
}
int numLeadingSamples;
int numTrailingSamples;
CAudioSourceWave &source = reinterpret_cast<CAudioSourceWave &>(m_pData->Source());
int loopSampleStart = source.GetLoopingInfo( NULL, &numLeadingSamples, &numTrailingSamples );
int numRemainingSamples = ( m_SampleCount - numTrailingSamples ) - m_SamplePosition;
// possibly straddling the end of data (and thus about to loop)
// want to split the straddle into two regions, due to loops possibly requiring a trailer and leader of discarded samples
if ( numRemainingSamples > 0 )
{
// first region, all the remaining samples, clamped until end of desired data
*pNumRequestedSamples = min( *pNumRequestedSamples, numRemainingSamples );
// nothing to discard
return 0;
}
else if ( numRemainingSamples == 0 )
{
// at exact end of desired data, snap the sample position back
// the position will be correct AFTER discarding decoded trailing and leading samples
m_SamplePosition = loopSampleStart;
// clamp the request
numRemainingSamples = ( m_SampleCount - numTrailingSamples ) - m_SamplePosition;
*pNumRequestedSamples = min( *pNumRequestedSamples, numRemainingSamples );
// flush these samples so the sample position is the real loop sample starting position
return numTrailingSamples + numLeadingSamples;
}
return 0;
}
//-----------------------------------------------------------------------------
// Get and submit XMA block(s). The decoder must stay blocks ahead of mixer
// so the decoded samples are available for peeling.
// An XMA file is thus treated as a series of fixed size large buffers (multiple xma blocks),
// which are streamed in sequentially. The XMA buffers may be delayed from the
// audio data cache due to async i/o latency.
// Returns < 0 if error, 0 if no decode started, 1 if decode submitted.
//-----------------------------------------------------------------------------
int CAudioMixerWaveXMA::GetXMABlocksAndSubmitToDecoder( bool bDecoderIsLocked )
{
int status = 0;
if ( m_DataOffset >= m_TotalBytes )
{
if ( !m_bLooped )
{
// end of file, no more data to decode
// not an error, because decoder finishes long before samples drained
return 0;
}
// start from beginning of loop
CAudioSourceWave &source = reinterpret_cast<CAudioSourceWave &>(m_pData->Source());
source.GetLoopingInfo( &m_DataOffset, NULL, NULL );
m_DataOffset *= XMA_BLOCK_SIZE;
}
HRESULT hr;
bool bLocked = false;
if ( !bDecoderIsLocked )
{
// decoder must be locked before any access
hr = XMAPlaybackRequestModifyLock( m_pXMAPlayback );
if ( FAILED( hr ) )
{
status = ERROR_XMA_CANTLOCK;
goto cleanUp;
}
hr = XMAPlaybackWaitUntilModifyLockObtained( m_pXMAPlayback );
if ( FAILED( hr ) )
{
status = ERROR_XMA_CANTLOCK;
goto cleanUp;
}
bLocked = true;
}
// the input buffer can never be less than a single xma block (buffer size is multiple blocks)
int bufferSize = min( m_TotalBytes - m_DataOffset, XMA_INPUT_BUFFER_SIZE );
if ( !bufferSize )
{
// EOF
goto cleanUp;
}
Assert( !( bufferSize % XMA_BLOCK_SIZE ) );
byte *pXMABuffer = m_pXMABuffers[m_XMABufferIndex & 0x01];
if ( !pXMABuffer )
{
// shouldn't happen, buffer should have been allocated
Assert( 0 );
status = ERROR_NULL_BUFFER;
goto cleanUp;
}
if ( !XMAPlaybackQueryReadyForMoreData( m_pXMAPlayback, 0 ) || XMAPlaybackQueryInputDataPending( m_pXMAPlayback, 0, pXMABuffer ) )
{
// decoder too saturated for more data or
// decoder still decoding from input hw buffer
goto cleanUp;
}
// get xma block(s)
// pump to get all of requested data
char *pData;
int total = 0;
while ( total < bufferSize )
{
int available = m_pData->ReadSourceData( (void **)&pData, m_DataOffset, bufferSize - total, NULL );
if ( !available )
break;
// aggregate into hw buffer
V_memcpy( pXMABuffer + total, pData, available );
m_DataOffset += available;
total += available;
}
if ( total != bufferSize )
{
if ( !total )
{
// failed to get any data, could be async latency or file error
status = ERROR_IO_NO_XMA_DATA;
}
else
{
// failed to get complete xma block(s)
status = ERROR_IO_TRUNCATED_BLOCK;
}
goto cleanUp;
}
// track the currently submitted offset
// this is used as a cheap method for save/restore because an XMA seek table is not available
m_LastDataOffset = m_DataOffset - total;
// start decoding the block(s) in the hw buffer
hr = XMAPlaybackSubmitData( m_pXMAPlayback, 0, pXMABuffer, bufferSize );
if ( FAILED( hr ) )
{
// failed to start decoder
status = ERROR_XMA_CANTSUBMIT;
goto cleanUp;
}
// decode submitted
status = 1;
// advance to next buffer
m_XMABufferIndex++;
if ( snd_xma_spew_decode.GetBool() )
{
Msg( "XMA: 0x%8.8x, XMABuffer: 0x%8.8x, BufferSize: %d, NextDataOffset: %d, %s\n", (unsigned int)this, pXMABuffer, bufferSize, m_DataOffset, m_pData->Source().GetFileName() );
}
cleanUp:
if ( bLocked )
{
// release the lock and let the decoder run
hr = XMAPlaybackResumePlayback( m_pXMAPlayback );
if ( FAILED( hr ) )
{
status = ERROR_XMA_CANTRESUME;
}
}
return status;
}
//-----------------------------------------------------------------------------
// Drain the XMA Decoder into the staging circular buffer of PCM for mixer.
// Fetch new XMA samples for the decoder.
//-----------------------------------------------------------------------------
int CAudioMixerWaveXMA::ServiceXMADecoder( bool bForceUpdate )
{
// allow decoder to work without being polled (lock causes a decoding stall)
// decoder must be allowed minimum operating latency
// the buffers are sized to compensate for the operating latency
if ( !bForceUpdate && ( Plat_MSTime() - m_LastPollTime <= MIX_DECODER_POLLING_LATENCY ) )
{
return 0;
}
m_LastPollTime = Plat_MSTime();
// lock and pause the decoder to gain access
HRESULT hr = XMAPlaybackRequestModifyLock( m_pXMAPlayback );
if ( FAILED( hr ) )
{
m_Error = ERROR_XMA_CANTLOCK;
return -1;
}
hr = XMAPlaybackWaitUntilModifyLockObtained( m_pXMAPlayback );
if ( FAILED( hr ) )
{
m_Error = ERROR_XMA_CANTLOCK;
return -1;
}
DWORD dwParseError = XMAPlaybackGetParseError( m_pXMAPlayback, 0 );
if ( dwParseError )
{
if ( snd_xma_spew_warnings.GetBool() )
{
Warning( "XMA: 0x%8.8x, Decoder Error, Parse: %d, '%s'\n", (unsigned int)this, dwParseError, m_pData->Source().GetFileName() );
}
m_Error = ERROR_XMA_PARSE;
return -1;
}
#ifdef DEBUG_XMA
if ( snd_xma_spew_errors.GetBool() )
{
DWORD dwError = XMAPlaybackGetErrorBits( m_pXMAPlayback, 0 );
if ( dwError )
{
Warning( "XMA: 0x%8.8x, Playback Error: %d, '%s'\n", (unsigned int)this, dwError, m_pData->Source().GetFileName() );
}
}
#endif
int numNewSamples = XMAPlaybackQueryAvailableData( m_pXMAPlayback, 0 );
int numMaxSamples = m_pPCMSamples->GetWriteAvailable()/( m_NumChannels*sizeof( short ) );
int numSamples = min( numNewSamples, numMaxSamples );
while ( numSamples )
{
char *pPCMData = NULL;
int numSamplesDecoded = XMAPlaybackConsumeDecodedData( m_pXMAPlayback, 0, numSamples, (void**)&pPCMData );
// put into staging buffer, ready for mixer to drain
m_pPCMSamples->Write( pPCMData, numSamplesDecoded*m_NumChannels*sizeof( short ) );
numSamples -= numSamplesDecoded;
numNewSamples -= numSamplesDecoded;
}
// queue up more blocks for the decoder
// the decoder will always finish ahead of the mixer, submit nothing, and the mixer will still be draining
int decodeStatus = GetXMABlocksAndSubmitToDecoder( true );
if ( decodeStatus < 0 )
{
m_Error = decodeStatus;
return -1;
}
m_bFinished = ( numNewSamples == 0 ) && ( decodeStatus == 0 ) && XMAPlaybackIsIdle( m_pXMAPlayback, 0 );
// decoder was paused for access, let the decoder run
hr = XMAPlaybackResumePlayback( m_pXMAPlayback );
if ( FAILED( hr ) )
{
m_Error = ERROR_XMA_CANTRESUME;
return -1;
}
return 1;
}
//-----------------------------------------------------------------------------
// Drain the PCM staging buffer.
// Copy samples (numSamplesToCopy && pData). Return actual copied.
// Flush Samples (numSamplesToCopy && !pData). Return actual flushed.
// Query available number of samples (!numSamplesToCopy && !pData). Returns available.
//-----------------------------------------------------------------------------
int CAudioMixerWaveXMA::GetPCMSamples( int numSamplesToCopy, char *pData )
{
int numReadySamples = m_pPCMSamples->GetReadAvailable()/( m_NumChannels*sizeof( short ) );
// peel sequential samples from the stream's staging buffer
int numCopiedSamples = 0;
int numRequestedSamples = min( numSamplesToCopy, numReadySamples );
if ( numRequestedSamples )
{
if ( pData )
{
// copy to caller
m_pPCMSamples->Read( pData, numRequestedSamples*m_NumChannels*sizeof( short ) );
pData += numRequestedSamples*m_NumChannels*sizeof( short );
}
else
{
// flush
m_pPCMSamples->Advance( numRequestedSamples*m_NumChannels*sizeof( short ) );
}
numCopiedSamples += numRequestedSamples;
}
if ( snd_xma_spew_drain.GetBool() )
{
char *pOperation = ( numSamplesToCopy && !pData ) ? "Flushed" : "Copied";
Msg( "XMA: 0x%8.8x, SamplePosition: %d, Ready: %d, Requested: %d, %s: %d, Elapsed: %d ms '%s'\n",
(unsigned int)this, m_SamplePosition, numReadySamples, numSamplesToCopy, pOperation, numCopiedSamples, Plat_MSTime() - m_LastDrainTime, m_pData->Source().GetFileName() );
}
m_LastDrainTime = Plat_MSTime();
if ( numSamplesToCopy )
{
// could be actual flushed or actual copied
return numCopiedSamples;
}
if ( !pData )
{
// satify query for available
return numReadySamples;
}
return 0;
}
//-----------------------------------------------------------------------------
// Stall mixing until initial buffer of decoded samples are available.
//-----------------------------------------------------------------------------
bool CAudioMixerWaveXMA::IsReadyToMix()
{
// XMA mixing cannot be driven from the main thread
Assert( ThreadInMainThread() == false );
if ( m_Error )
{
// error has been set
// let mixer try to get unavailable samples, which casues the real abort
return true;
}
if ( m_bStartedMixing )
{
// decoding process has started
return true;
}
if ( !m_pXMAPlayback )
{
// first time, finish setup
int numBuffers;
if ( m_bLooped || m_TotalBytes > XMA_INPUT_BUFFER_SIZE )
{
// data will cascade through multiple buffers
numBuffers = 2;
}
else
{
// data can fit into a single buffer
numBuffers = 1;
}
// xma data must be decoded from a hw friendly buffer
// pool should have buffers available
if ( g_XMAMemoryPool.BytesAllocated() != numBuffers * g_XMAMemoryPool.ChunkSize() )
{
for ( int i = 0; i < numBuffers; i++ )
{
m_pXMABuffers[i] = (byte*)g_XMAMemoryPool.Alloc();
}
XMA_PLAYBACK_INIT xmaPlaybackInit = { 0 };
xmaPlaybackInit.sampleRate = m_SampleRate;
xmaPlaybackInit.channelCount = m_NumChannels;
xmaPlaybackInit.subframesToDecode = 4;
xmaPlaybackInit.outputBufferSizeInSamples = ( m_NumChannels == 2 ) ? XMA_STEREO_OUTPUT_BUFFER_SAMPLES : XMA_MONO_OUTPUT_BUFFER_SAMPLES;
XMAPlaybackCreate( 1, &xmaPlaybackInit, 0, &m_pXMAPlayback );
int stagingSize = PCM_STAGING_BUFFER_TIME * m_SampleRate * m_NumChannels * sizeof( short ) * 0.001f;
m_pPCMSamples = AllocateCircularBuffer( AlignValue( stagingSize, 4 ) );
}
else
{
// too many sounds playing, no xma buffers free
m_Error = ERROR_OUT_OF_MEMORY;
return true;
}
m_StartTime = Plat_MSTime();
}
// waiting for samples
// allow decoder to work without being polled (lock causes a decoding stall)
if ( Plat_MSTime() - m_LastPollTime <= MIX_DECODER_POLLING_LATENCY )
{
return false;
}
m_LastPollTime = Plat_MSTime();
// must have buffers in flight before mixing can begin
if ( m_DataOffset == m_LastDataOffset )
{
// keep trying to get data, async i/o has some allowable latency
int decodeStatus = GetXMABlocksAndSubmitToDecoder( false );
if ( decodeStatus < 0 && decodeStatus != ERROR_IO_NO_XMA_DATA )
{
m_Error = decodeStatus;
return true;
}
else if ( !decodeStatus || decodeStatus == ERROR_IO_NO_XMA_DATA )
{
// async streaming latency could be to blame, check watchdog
if ( Plat_MSTime() - m_StartTime >= MIX_IO_DATA_TIMEOUT )
{
m_Error = ERROR_IO_DATA_TIMEOUT;
}
return false;
}
}
// get the available samples ready for immediate mixing
if ( ServiceXMADecoder( true ) < 0 )
{
return true;
}
// can't mix until we have a minimum threshold of data or the decoder is finished
int minSamplesNeeded = m_bFinished ? 0 : MIN_READYTOMIX * m_SampleRate;
#if defined( ALLOW_SKIP_SAMPLES )
minSamplesNeeded += m_bFinished ? 0 : m_SkipSamples;
#endif
int numReadySamples = GetPCMSamples( 0, NULL );
if ( numReadySamples > minSamplesNeeded )
{
// decoder has samples ready for draining
m_bStartedMixing = true;
if ( snd_xma_spew_startup.GetBool() )
{
Msg( "XMA: 0x%8.8x, Startup Latency: %d ms, Samples Ready: %d, '%s'\n", (unsigned int)this, Plat_MSTime() - m_StartTime, numReadySamples, m_pData->Source().GetFileName() );
}
return true;
}
if ( Plat_MSTime() - m_StartTime >= MIX_DECODER_TIMEOUT )
{
m_Error = ERROR_DECODER_TIMEOUT;
}
// on startup error, let mixer start and get unavailable samples, and abort
// otherwise hold off mixing until samples arrive
return ( m_Error != 0 );
}
//-----------------------------------------------------------------------------
// Returns true to mix, false to stop mixer completely. Called after
// mixer requests samples.
//-----------------------------------------------------------------------------
bool CAudioMixerWaveXMA::ShouldContinueMixing()
{
if ( !IsRetail() && m_Error && snd_xma_spew_warnings.GetBool() )
{
const char *pErrorString;
if ( m_Error < 0 && -m_Error < ARRAYSIZE( g_XMAErrorStrings ) )
{
pErrorString = g_XMAErrorStrings[-m_Error];
}
else
{
pErrorString = g_XMAErrorStrings[0];
}
Warning( "XMA: 0x%8.8x, Mixer Aborted: %s, SamplePosition: %d/%d, DataOffset: %d/%d, '%s'\n",
(unsigned int)this, pErrorString, m_SamplePosition, m_SampleCount, m_DataOffset, m_TotalBytes, m_pData->Source().GetFileName() );
}
// an error condition is fatal to mixer
return ( m_Error == 0 && BaseClass::ShouldContinueMixing() );
}
//-----------------------------------------------------------------------------
// Read existing buffer or decompress a new block when necessary.
// If no samples can be fetched, returns 0, which hints the mixer to a pending shutdown state.
// This routines operates in large buffer quantums, and nothing smaller.
// XMA decode performance severly degrades if the lock is too frequent.
//-----------------------------------------------------------------------------
int CAudioMixerWaveXMA::GetOutputData( void **pData, int numSamplesToCopy, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
{
if ( m_Error )
{
// mixer will eventually shutdown
return 0;
}
if ( !m_bStartedMixing )
{
#if defined( ALLOW_SKIP_SAMPLES )
int numMaxSamples = AUDIOSOURCE_COPYBUF_SIZE/( m_NumChannels * sizeof( short ) );
numSamplesToCopy = min( numSamplesToCopy, numMaxSamples );
m_SkipSamples += numSamplesToCopy;
// caller requesting data before mixing has commenced
V_memset( copyBuf, 0, numSamplesToCopy );
*pData = (void*)copyBuf;
return numSamplesToCopy;
#else
// not allowed, GetOutputData() should only be called by the mixing loop
Assert( 0 );
return 0;
#endif
}
// XMA mixing cannot be driven from the main thread
Assert( ThreadInMainThread() == false );
// needs to be clocked at regular intervals
if ( ServiceXMADecoder( false ) < 0 )
{
return 0;
}
#if defined( ALLOW_SKIP_SAMPLES )
if ( m_SkipSamples > 0 )
{
// flush whatever is available
// ignore
m_SkipSamples -= GetPCMSamples( m_SkipSamples, NULL );
if ( m_SkipSamples != 0 )
{
// not enough decoded data ready to flush
// must flush these samples to maintain proper position
m_Error = ERROR_XMA_NO_PCM_DATA;
return 0;
}
}
#endif
// loopback may require flushing some decoded samples
int numRequestedSamples = numSamplesToCopy;
int numDiscardSamples = UpdatePositionForLooping( &numRequestedSamples );
if ( numDiscardSamples > 0 )
{
// loopback requires discarding samples to converge to expected looppoint
numDiscardSamples -= GetPCMSamples( numDiscardSamples, NULL );
if ( numDiscardSamples != 0 )
{
// not enough decoded data ready to flush
// must flush these samples to achieve looping
m_Error = ERROR_XMA_NO_PCM_DATA;
return 0;
}
}
// can only drain as much as can be copied to caller
int numMaxSamples = AUDIOSOURCE_COPYBUF_SIZE/( m_NumChannels * sizeof( short ) );
numRequestedSamples = min( numRequestedSamples, numMaxSamples );
int numCopiedSamples = GetPCMSamples( numRequestedSamples, copyBuf );
if ( numCopiedSamples )
{
CAudioMixerWave::m_sample_max_loaded += numCopiedSamples;
CAudioMixerWave::m_sample_loaded_index += numCopiedSamples;
// advance position by valid samples
m_SamplePosition += numCopiedSamples;
*pData = (void*)copyBuf;
#ifdef DEBUG_XMA
if ( snd_xma_record.GetBool() )
{
WaveAppendTmpFile( "debug.wav", copyBuf, 16, numCopiedSamples * m_NumChannels );
WaveFixupTmpFile( "debug.wav" );
}
#endif
}
else
{
// no samples copied
if ( !m_bFinished && numRequestedSamples )
{
// XMA latency error occurs when decoder not finished (not at EOF) and caller wanted samples but can't get any
if ( snd_xma_spew_warnings.GetInt() )
{
Warning( "XMA: 0x%8.8x, No Decoded Data Ready: %d samples needed, '%s'\n", (unsigned int)this, numSamplesToCopy, m_pData->Source().GetFileName() );
}
m_Error = ERROR_XMA_NO_PCM_DATA;
}
}
return numCopiedSamples;
}
//-----------------------------------------------------------------------------
// Purpose: Seek to a new position in the file
// NOTE: In most cases, only call this once, and call it before playing
// any data.
// Input : newPosition - new position in the sample clocks of this sample
//-----------------------------------------------------------------------------
void CAudioMixerWaveXMA::SetSampleStart( int newPosition )
{
// cannot support this
// this should be unused and thus not supporting
Assert( 0 );
}
int CAudioMixerWaveXMA::GetPositionForSave()
{
if ( m_bLooped )
{
// A looped sample cannot be saved/restored because the decoded sample position,
// which is needed for loop calc, cannot ever be correctly restored without
// the XMA seek table.
return 0;
}
// This is silly and totally wrong, but doing it anyways.
// The correct thing was to have the XMA seek table and use
// that to determine the correct packet. This is just a hopeful
// nearby approximation. Music did not have the seek table at
// the time of this code. The Seek table was added for vo
// restoration later.
return m_LastDataOffset;
}
void CAudioMixerWaveXMA::SetPositionFromSaved( int savedPosition )
{
// Not used here. The Mixer creation will be given the initial startup offset.
}
//-----------------------------------------------------------------------------
// Purpose: Abstract factory function for XMA mixers
//-----------------------------------------------------------------------------
CAudioMixer *CreateXMAMixer( IWaveData *data, int initialStreamPosition )
{
return new CAudioMixerWaveXMA( data, initialStreamPosition );
}

View File

@ -0,0 +1,24 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#ifndef SND_WAVE_MIXER_XMA_H
#define SND_WAVE_MIXER_XMA_H
#pragma once
class CAudioMixer;
class IWaveData;
// xma must be decoded as atomic blocks
#define XMA_BLOCK_SIZE ( 2 * 1024 )
// cannot be made slower than 15ms
// cannot be made faster than 5ms
// xma hardware needs be have stable clocking
#define XMA_POLL_RATE 15
CAudioMixer *CreateXMAMixer( IWaveData *data, int initialStreamPosition );
#endif // SND_WAVE_MIXER_XMA_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,160 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $Workfile: $
// $Date: $
//
//-----------------------------------------------------------------------------
// $Log: $
//
// $NoKeywords: $
//=============================================================================//
#ifndef SND_WAVE_SOURCE_H
#define SND_WAVE_SOURCE_H
#pragma once
#include "snd_audio_source.h"
class IterateRIFF;
#include "sentence.h"
#include "snd_sfx.h"
//=============================================================================
// Functions to create audio sources from wave files or from wave data.
//=============================================================================
extern CAudioSource* Audio_CreateMemoryWave( CSfxTable *pSfx );
extern CAudioSource* Audio_CreateStreamedWave( CSfxTable *pSfx );
class CAudioSourceWave : public CAudioSource
{
public:
CAudioSourceWave( CSfxTable *pSfx );
CAudioSourceWave( CSfxTable *pSfx, CAudioSourceCachedInfo *info );
~CAudioSourceWave( void );
virtual int GetType( void );
virtual void GetCacheData( CAudioSourceCachedInfo *info );
void Setup( const char *pFormat, int formatSize, IterateRIFF &walk );
virtual int SampleRate( void );
virtual int SampleSize( void );
virtual int SampleCount( void );
virtual int Format( void );
virtual int DataSize( void );
void *GetHeader( void );
virtual bool IsVoiceSource();
virtual void ParseChunk( IterateRIFF &walk, int chunkName );
virtual void ParseSentence( IterateRIFF &walk );
void ConvertSamples( char *pData, int sampleCount );
bool IsLooped( void );
bool IsStereoWav( void );
bool IsStreaming( void );
int GetCacheStatus( void );
int ConvertLoopedPosition( int samplePosition );
void CacheLoad( void );
void CacheUnload( void );
virtual int ZeroCrossingBefore( int sample );
virtual int ZeroCrossingAfter( int sample );
virtual void ReferenceAdd( CAudioMixer *pMixer );
virtual void ReferenceRemove( CAudioMixer *pMixer );
virtual bool CanDelete( void );
virtual CSentence *GetSentence( void );
const char *GetName();
virtual bool IsAsyncLoad();
virtual void CheckAudioSourceCache();
virtual char const *GetFileName();
// 360 uses alternate play once semantics
virtual void SetPlayOnce( bool bIsPlayOnce ) { m_bIsPlayOnce = IsPC() ? bIsPlayOnce : false; }
virtual bool IsPlayOnce() { return IsPC() ? m_bIsPlayOnce : false; }
virtual void SetSentenceWord( bool bIsWord ) { m_bIsSentenceWord = bIsWord; }
virtual bool IsSentenceWord() { return m_bIsSentenceWord; }
int GetLoopingInfo( int *pLoopBlock, int *pNumLeadingSamples, int *pNumTrailingSamples );
virtual int SampleToStreamPosition( int samplePosition ) { return 0; }
virtual int StreamToSamplePosition( int streamPosition ) { return 0; }
protected:
void ParseCueChunk( IterateRIFF &walk );
void ParseSamplerChunk( IterateRIFF &walk );
void Init( const char *pHeaderBuffer, int headerSize );
bool GetStartupData( void *dest, int destsize, int& bytesCopied );
bool GetXboxAudioStartupData();
//-----------------------------------------------------------------------------
// Purpose:
// Output : byte
//-----------------------------------------------------------------------------
inline byte *GetCachedDataPointer()
{
VPROF("CAudioSourceWave::GetCachedDataPointer");
CAudioSourceCachedInfo *info = m_AudioCacheHandle.Get( CAudioSource::AUDIO_SOURCE_WAV, m_pSfx->IsPrecachedSound(), m_pSfx, &m_nCachedDataSize );
if ( !info )
{
Assert( !"CAudioSourceWave::GetCachedDataPointer info == NULL" );
return NULL;
}
return (byte *)info->CachedData();
}
int m_bits;
int m_rate;
int m_channels;
int m_format;
int m_sampleSize;
int m_loopStart;
int m_sampleCount; // can be "samples" or "bytes", depends on format
CSfxTable *m_pSfx;
CSentence *m_pTempSentence;
int m_dataStart; // offset of sample data
int m_dataSize; // size of sample data
char *m_pHeader;
int m_nHeaderSize;
CAudioSourceCachedInfoHandle_t m_AudioCacheHandle;
int m_nCachedDataSize;
// number of actual samples (regardless of format)
// compressed formats alter definition of m_sampleCount
// used to spare expensive calcs by decoders
int m_numDecodedSamples;
// additional data needed by xma decoder to for looping
unsigned short m_loopBlock; // the block the loop occurs in
unsigned short m_numLeadingSamples; // number of leader samples in the loop block to discard
unsigned short m_numTrailingSamples; // number of trailing samples in the final block to discard
unsigned short unused;
unsigned int m_bNoSentence : 1;
unsigned int m_bIsPlayOnce : 1;
unsigned int m_bIsSentenceWord : 1;
private:
CAudioSourceWave( const CAudioSourceWave & ); // not implemented, not allowed
int m_refCount;
#ifdef _DEBUG
// Only set in debug mode so you can see the name.
const char *m_pDebugName;
#endif
};
#endif // SND_WAVE_SOURCE_H

View File

@ -0,0 +1,149 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Create an output wave stream. Used to record audio for in-engine movies or
// mixer debugging.
//
//=====================================================================================//
#include "audio_pch.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern IFileSystem *g_pFileSystem;
// FIXME: shouldn't this API be part of IFileSystem?
extern bool COM_CopyFile( const char *netpath, const char *cachepath );
// Create a wave file
void WaveCreateTmpFile( const char *filename, int rate, int bits, int nChannels )
{
char tmpfilename[MAX_PATH];
Q_StripExtension( filename, tmpfilename, sizeof( tmpfilename ) );
Q_DefaultExtension( tmpfilename, ".WAV", sizeof( tmpfilename ) );
FileHandle_t file;
file = g_pFileSystem->Open( tmpfilename, "wb" );
if ( file == FILESYSTEM_INVALID_HANDLE )
return;
int chunkid = LittleLong( RIFF_ID );
int chunksize = LittleLong( 0 );
g_pFileSystem->Write( &chunkid, sizeof(int), file );
g_pFileSystem->Write( &chunksize, sizeof(int), file );
chunkid = LittleLong( RIFF_WAVE );
g_pFileSystem->Write( &chunkid, sizeof(int), file );
// create a 16-bit PCM stereo output file
PCMWAVEFORMAT fmt = { { 0 } };
fmt.wf.wFormatTag = LittleWord( (short)WAVE_FORMAT_PCM );
fmt.wf.nChannels = LittleWord( (short)nChannels );
fmt.wf.nSamplesPerSec = LittleDWord( rate );
fmt.wf.nAvgBytesPerSec = LittleDWord( rate * bits * nChannels / 8 );
fmt.wf.nBlockAlign = LittleWord( (short)( 2 * nChannels) );
fmt.wBitsPerSample = LittleWord( (short)bits );
chunkid = LittleLong( WAVE_FMT );
chunksize = LittleLong( sizeof(fmt) );
g_pFileSystem->Write( &chunkid, sizeof(int), file );
g_pFileSystem->Write( &chunksize, sizeof(int), file );
g_pFileSystem->Write( &fmt, sizeof( PCMWAVEFORMAT ), file );
chunkid = LittleLong( WAVE_DATA );
chunksize = LittleLong( 0 );
g_pFileSystem->Write( &chunkid, sizeof(int), file );
g_pFileSystem->Write( &chunksize, sizeof(int), file );
g_pFileSystem->Close( file );
}
void WaveAppendTmpFile( const char *filename, void *pBuffer, int sampleBits, int numSamples )
{
char tmpfilename[MAX_PATH];
Q_StripExtension( filename, tmpfilename, sizeof( tmpfilename ) );
Q_DefaultExtension( tmpfilename, ".WAV", sizeof( tmpfilename ) );
FileHandle_t file;
file = g_pFileSystem->Open( tmpfilename, "r+b" );
if ( file == FILESYSTEM_INVALID_HANDLE )
return;
g_pFileSystem->Seek( file, 0, FILESYSTEM_SEEK_TAIL );
if ( IsX360() && sampleBits == 16 )
{
short *pSwapped = (short * )_alloca( numSamples * sampleBits/8 );
for ( int i=0; i<numSamples; i++ )
{
pSwapped[i] = LittleShort( ((short*)pBuffer)[i] );
}
g_pFileSystem->Write( pSwapped, numSamples * sizeof( short ), file );
}
else
{
g_pFileSystem->Write( pBuffer, numSamples * sampleBits/8, file );
}
g_pFileSystem->Close( file );
}
void WaveFixupTmpFile( const char *filename )
{
char tmpfilename[MAX_PATH];
Q_StripExtension( filename, tmpfilename, sizeof( tmpfilename ) );
Q_DefaultExtension( tmpfilename, ".WAV", sizeof( tmpfilename ) );
FileHandle_t file;
file = g_pFileSystem->Open( tmpfilename, "r+b" );
if ( FILESYSTEM_INVALID_HANDLE == file )
{
Warning( "WaveFixupTmpFile( '%s' ) failed to open file for editing\n", tmpfilename );
return;
}
// file size goes in RIFF chunk
int size = g_pFileSystem->Size( file ) - 2*sizeof( int );
// offset to data chunk
int headerSize = (sizeof(int)*5 + sizeof(PCMWAVEFORMAT));
// size of data chunk
int dataSize = size - headerSize;
size = LittleLong( size );
g_pFileSystem->Seek( file, sizeof( int ), FILESYSTEM_SEEK_HEAD );
g_pFileSystem->Write( &size, sizeof( int ), file );
// skip the header and the 4-byte chunk tag and write the size
dataSize = LittleLong( dataSize );
g_pFileSystem->Seek( file, headerSize+sizeof( int ), FILESYSTEM_SEEK_HEAD );
g_pFileSystem->Write( &dataSize, sizeof( int ), file );
g_pFileSystem->Close( file );
}
CON_COMMAND( movie_fixwave, "Fixup corrupted .wav file if engine crashed during startmovie/endmovie, etc." )
{
if ( args.ArgC() != 2 )
{
Msg ("Usage: movie_fixwave wavname\n");
return;
}
char const *wavname = args.Arg( 1 );
if ( !g_pFileSystem->FileExists( wavname ) )
{
Warning( "movie_fixwave: File '%s' does not exist\n", wavname );
return;
}
char tmpfilename[256];
Q_StripExtension( wavname, tmpfilename, sizeof( tmpfilename ) );
Q_strncat( tmpfilename, "_fixed", sizeof( tmpfilename ), COPY_ALL_CHARACTERS );
Q_DefaultExtension( tmpfilename, ".wav", sizeof( tmpfilename ) );
// Now copy the file
Msg( "Copying '%s' to '%s'\n", wavname, tmpfilename );
COM_CopyFile( wavname, tmpfilename );
Msg( "Performing fixup on '%s'\n", tmpfilename );
WaveFixupTmpFile( tmpfilename );
}

View File

@ -0,0 +1,18 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Create an output wave stream. Used to record audio for in-engine movies or
// mixer debugging.
//
//=====================================================================================//
#ifndef SND_WAVE_TEMP_H
#define SND_WAVE_TEMP_H
#ifdef _WIN32
#pragma once
#endif
extern void WaveCreateTmpFile( const char *filename, int rate, int bits, int channels );
extern void WaveAppendTmpFile( const char *filename, void *buffer, int sampleBits, int numSamples );
extern void WaveFixupTmpFile( const char *filename );
#endif // SND_WAVE_TEMP_H

View File

@ -0,0 +1,185 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#include "audio_pch.h"
#if defined( USE_SDL )
#include "snd_dev_sdl.h"
#endif
#ifdef OSX
#include "snd_dev_openal.h"
#include "snd_dev_mac_audioqueue.h"
ConVar snd_audioqueue( "snd_audioqueue", "1" );
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
bool snd_firsttime = true;
/*
* Global variables. Must be visible to window-procedure function
* so it can unlock and free the data block after it has been played.
*/
IAudioDevice *g_AudioDevice = NULL;
/*
==================
S_BlockSound
==================
*/
void S_BlockSound( void )
{
if ( !g_AudioDevice )
return;
g_AudioDevice->Pause();
}
/*
==================
S_UnblockSound
==================
*/
void S_UnblockSound( void )
{
if ( !g_AudioDevice )
return;
g_AudioDevice->UnPause();
}
/*
==================
AutoDetectInit
Try to find a sound device to mix for.
Returns a CAudioNULLDevice if nothing is found.
==================
*/
IAudioDevice *IAudioDevice::AutoDetectInit( bool waveOnly )
{
IAudioDevice *pDevice = NULL;
if ( IsPC() )
{
#if defined( WIN32 ) && !defined( USE_SDL )
if ( waveOnly )
{
pDevice = Audio_CreateWaveDevice();
if ( !pDevice )
goto NULLDEVICE;
}
if ( !pDevice )
{
if ( snd_firsttime )
{
pDevice = Audio_CreateDirectSoundDevice();
}
}
// if DirectSound didn't succeed in initializing, try to initialize
// waveOut sound, unless DirectSound failed because the hardware is
// already allocated (in which case the user has already chosen not
// to have sound)
// UNDONE: JAY: This doesn't test for the hardware being in use anymore, REVISIT
if ( !pDevice )
{
pDevice = Audio_CreateWaveDevice();
}
#elif defined(OSX)
if ( !CommandLine()->CheckParm( "-snd_openal" ) )
{
DevMsg( "Using AudioQueue Interface\n" );
pDevice = Audio_CreateMacAudioQueueDevice();
}
if ( !pDevice )
{
DevMsg( "Using OpenAL Interface\n" );
pDevice = Audio_CreateOpenALDevice(); // fall back to openAL if the audio queue fails
}
#elif defined( USE_SDL )
DevMsg( "Trying SDL Audio Interface\n" );
pDevice = Audio_CreateSDLAudioDevice();
#ifdef NEVER
// Jul 2012. mikesart. E-mail exchange with Ryan Gordon after figuring out that
// Audio_CreatePulseAudioDevice() wasn't working on Ubuntu 12.04 (lots of stuttering).
//
// > I installed libpulse-dev, rebuilt SDL, and now SDL is using pulse
// > audio and everything is working great. However I'm wondering if we
// > need to fall back to PulseAudio in our codebase if SDL is doing that
// > for us. I mean, is it worth me going through and debugging our Pulse
// > Audio path or should I just remove it?
//
// Remove it...it never worked well, and only remained in case there were
// concerns about relying on SDL. The SDL codepath is way easier to read,
// simpler to maintain, and handles all sorts of strange audio backends,
// including Pulse.
if ( !pDevice )
{
DevMsg( "Trying PulseAudio Interface\n" );
pDevice = Audio_CreatePulseAudioDevice(); // fall back to PulseAudio if SDL fails
}
#endif // NEVER
#else
#error
#endif
}
#if defined( _X360 )
else
{
pDevice = Audio_CreateXAudioDevice( true );
if ( pDevice )
{
// xaudio requires threaded mixing
S_EnableThreadedMixing( true );
}
}
#endif
#if defined( WIN32 ) && !defined( USE_SDL )
NULLDEVICE:
#endif
snd_firsttime = false;
if ( !pDevice )
{
if ( snd_firsttime )
DevMsg( "No sound device initialized\n" );
return Audio_GetNullDevice();
}
return pDevice;
}
/*
==============
SNDDMA_Shutdown
Reset the sound device for exiting
===============
*/
void SNDDMA_Shutdown( void )
{
if ( g_AudioDevice != Audio_GetNullDevice() )
{
if ( g_AudioDevice )
{
g_AudioDevice->Shutdown();
delete g_AudioDevice;
}
// the NULL device is always valid
g_AudioDevice = Audio_GetNullDevice();
}
}

View File

@ -0,0 +1,128 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "basetypes.h"
#include "snd_fixedint.h"
#ifndef SOUND_PRIVATE_H
#define SOUND_PRIVATE_H
#pragma once
// Forward declarations
struct portable_samplepair_t;
struct channel_t;
typedef int SoundSource;
class CAudioSource;
struct channel_t;
class CSfxTable;
class IAudioDevice;
// ====================================================================
#define SAMPLE_16BIT_SHIFT 1
void S_Startup (void);
void S_FlushSoundData(int rate);
CAudioSource *S_LoadSound( CSfxTable *s, channel_t *ch );
void S_TouchSound (char *sample);
CSfxTable *S_FindName (const char *name, int *pfInCache);
// spatializes a channel
void SND_Spatialize(channel_t *ch);
void SND_ActivateChannel( channel_t *ch );
// shutdown the DMA xfer.
void SNDDMA_Shutdown(void);
// ====================================================================
// User-setable variables
// ====================================================================
extern int g_paintedtime;
extern bool snd_initialized;
extern class Vector listener_origin;
void S_LocalSound (char *s);
void SND_InitScaletable (void);
void S_AmbientOff (void);
void S_AmbientOn (void);
void S_FreeChannel(channel_t *ch);
// resync the sample-timing adjustment clock (for scheduling a group of waves with precise timing - e.g. machine gun sounds)
extern void S_SyncClockAdjust( clocksync_index_t );
//=============================================================================
// UNDONE: Move this global?
extern IAudioDevice *g_AudioDevice;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
void S_TransferStereo16 (void *pOutput, const portable_samplepair_t *pfront, int lpaintedtime, int endtime);
void S_TransferPaintBuffer(void *pOutput, const portable_samplepair_t *pfront, int lpaintedtime, int endtime);
void S_MixBufferUpsample2x( int count, portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int filtertype );
extern void Mix8MonoWavtype( channel_t *pChannel, portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount );
extern void Mix8StereoWavtype( channel_t *pChannel, portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount );
extern void Mix16MonoWavtype( channel_t *pChannel, portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount );
extern void Mix16StereoWavtype( channel_t *pChannel, portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount );
extern void SND_MoveMouth8(channel_t *pChannel, CAudioSource *pSource, int count);
extern void SND_CloseMouth(channel_t *pChannel);
extern void SND_InitMouth( channel_t *pChannel );
extern void SND_UpdateMouth( channel_t *pChannel );
extern void SND_ClearMouth( channel_t *pChannel );
extern bool SND_IsMouth( channel_t *pChannel );
extern bool SND_ShouldPause( channel_t *pChannel );
extern bool SND_IsRecording();
void MIX_PaintChannels( int endtime, bool bIsUnderwater );
// Play a big of zeroed out sound
void MIX_PaintNullChannels( int endtime );
bool AllocDsps( bool bLoadPresetFile );
void FreeDsps( bool bReleaseTemplateMemory );
void ForceCleanDspPresets( void );
void CheckNewDspPresets( void );
void DSP_Process( int idsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, portable_samplepair_t *pbcenter, int sampleCount );
void DSP_ClearState();
extern int idsp_room;
extern int idsp_water;
extern int idsp_player;
extern int idsp_facingaway;
extern int idsp_speaker;
extern int idsp_spatial;
extern float g_DuckScale;
// Legacy DSP Routines
void SX_Init (void);
void SX_Free (void);
void SX_ReloadRoomFX();
void SX_RoomFX(int endtime, int fFilter, int fTimefx);
// DSP Routines
void DSP_InitAll(bool bLoadPresetFile);
void DSP_FreeAll(void);
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // SOUND_PRIVATE_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,92 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "audio_pch.h"
#include "minmax.h"
#include "voice_gain.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
CAutoGain::CAutoGain()
{
Reset(128, 5.0f, 0.5f, 1);
}
void CAutoGain::Reset(int blockSize, float maxGain, float avgToMaxVal, float scale)
{
m_BlockSize = blockSize;
m_MaxGain = maxGain;
m_AvgToMaxVal = avgToMaxVal;
m_CurBlockOffset = 0;
m_CurTotal = 0;
m_CurMax = 0;
m_CurrentGain = 1;
m_NextGain = 1;
m_Scale = scale;
m_GainMultiplier = 0;
m_FixedCurrentGain = 1 << AG_FIX_SHIFT;
}
void CAutoGain::ProcessSamples(
short *pSamples,
int nSamples)
{
short *pCurPos = pSamples;
int nSamplesLeft = nSamples;
// Continue until we hit the end of this block.
while(nSamplesLeft)
{
int nToProcess = min(nSamplesLeft, (m_BlockSize - m_CurBlockOffset));
for(int iSample=0; iSample < nToProcess; iSample++)
{
// Update running totals..
m_CurTotal += abs( pCurPos[iSample] );
m_CurMax = max( m_CurMax, (int)abs( pCurPos[iSample] ) );
// Apply gain on this sample.
AGFixed gain = m_FixedCurrentGain + m_CurBlockOffset * m_GainMultiplier;
m_CurBlockOffset++;
int newval = ((int)pCurPos[iSample] * gain) >> AG_FIX_SHIFT;
newval = min(32767, max(newval, -32768));
pCurPos[iSample] = (short)newval;
}
pCurPos += nToProcess;
nSamplesLeft -= nToProcess;
// Did we just end a block? Update our next gain.
if((m_CurBlockOffset % m_BlockSize) == 0)
{
// Now we've interpolated to our next gain, make it our current gain.
m_CurrentGain = m_NextGain * m_Scale;
m_FixedCurrentGain = (int)((double)m_CurrentGain * (1 << AG_FIX_SHIFT));
// Figure out the next gain (the gain we'll interpolate to).
int avg = m_CurTotal / m_BlockSize;
float modifiedMax = avg + (m_CurMax - avg) * m_AvgToMaxVal;
m_NextGain = min(32767.0f / modifiedMax, m_MaxGain) * m_Scale;
// Setup the interpolation multiplier.
float fGainMultiplier = (m_NextGain - m_CurrentGain) / (m_BlockSize - 1);
m_GainMultiplier = (AGFixed)((double)fGainMultiplier * (1 << AG_FIX_SHIFT));
// Reset counters.
m_CurTotal = 0;
m_CurMax = 0;
m_CurBlockOffset = 0;
}
}
}

View File

@ -0,0 +1,62 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef VOICE_GAIN_H
#define VOICE_GAIN_H
#pragma once
// ----------------------------------------------------------------------- //
// CAutoGain is fed samples and figures out a gain to apply to blocks of samples.
// Right now, this class applies gain one block behind. The assumption is that the blocks are
// small enough that gain settings for one block will usually be right for the next block.
// The ideal way to implement this class would be to have a delay the size of a block
// so it can apply the right gain to the actual block it was calculated for.
// ----------------------------------------------------------------------- //
class CAutoGain
{
public:
CAutoGain();
// maxGain and avgToMaxVal are used to derive the gain amount for each block of samples.
// All samples are scaled by scale.
void Reset(int blockSize, float maxGain, float avgToMaxVal, float scale);
// Process the specified samples and apply gain to them.
void ProcessSamples(
short *pSamples,
int nSamples);
private:
enum {AG_FIX_SHIFT=7};
typedef long AGFixed;
// Parameters affecting the algorithm.
int m_BlockSize; // Derive gain from blocks of this size.
float m_MaxGain;
float m_AvgToMaxVal;
// These are calculated as samples are passed in.
int m_CurBlockOffset;
int m_CurTotal; // Total of sample values in current block.
int m_CurMax; // Highest (absolute) sample value.
float m_Scale; // All samples are scaled by this amount.
float m_CurrentGain; // Gain at sample 0 in this block.
float m_NextGain; // Gain at the last sample in this block.
AGFixed m_FixedCurrentGain; // Fixed-point m_CurrentGain.
AGFixed m_GainMultiplier; // (m_NextGain - m_CurrentGain) / (m_BlockSize - 1).
};
#endif // VOICE_GAIN_H

View File

@ -0,0 +1,503 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "audio_pch.h"
#include "voice_mixer_controls.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// NOTE: Vista deprecated these APIs
// Under vista these settings are per-session (not persistent)
// The correct method is to use the AudioEndpoint COM objects to manage controls like this
// The interface is not 1:1 so for now we'll just back the state with convars and reapply it
// on init of the mixer controls. In the future when XP is no longer the majority of our user base
// we should revisit this and move to the new API.
// http://msdn.microsoft.com/en-us/library/aa964574(VS.85).aspx
class CMixerControls : public IMixerControls
{
public:
CMixerControls();
virtual ~CMixerControls();
virtual bool GetValue_Float(Control iControl, float &value);
virtual bool SetValue_Float(Control iControl, float value);
virtual bool SelectMicrophoneForWaveInput();
private:
bool Init();
void Term();
void Clear();
bool GetControlOption_Bool(DWORD dwControlID, DWORD cMultipleItems, bool &bValue);
bool SetControlOption_Bool(DWORD dwControlID, DWORD cMultipleItems, const bool bValue);
bool GetControlOption_Unsigned(DWORD dwControlID, DWORD cMultipleItems, DWORD &value);
bool SetControlOption_Unsigned(DWORD dwControlID, DWORD cMultipleItems, const DWORD value);
bool GetLineControls( DWORD dwLineID, MIXERCONTROL *controls, DWORD nControls );
void FindMicSelectControl( DWORD dwLineID, DWORD nControls );
private:
HMIXER m_hMixer;
class ControlInfo
{
public:
DWORD m_dwControlID;
DWORD m_cMultipleItems;
bool m_bFound;
};
DWORD m_dwMicSelectControlID;
DWORD m_dwMicSelectMultipleItems;
DWORD m_dwMicSelectControlType;
DWORD m_dwMicSelectIndex;
// Info about the controls we found.
ControlInfo m_ControlInfos[NumControls];
};
CMixerControls::CMixerControls()
{
m_dwMicSelectControlID = 0xFFFFFFFF;
Clear();
Init();
}
CMixerControls::~CMixerControls()
{
Term();
}
bool CMixerControls::Init()
{
Term();
MMRESULT mmr;
bool bFoundMixer = false;
bool bFoundConnectionWithMicVolume = false;
CUtlVectorFixedGrowable<MIXERCONTROL, 64> controls;
// Iterate over all the devices
// This is done in reverse so the 0th device is our fallback if none of them had the correct MicVolume control
for ( int iDevice = static_cast<int>( mixerGetNumDevs() ) - 1; iDevice >= 0 && !bFoundConnectionWithMicVolume; --iDevice )
{
// Open the mixer.
mmr = mixerOpen(&m_hMixer, (DWORD)iDevice, 0, 0, 0 );
if(mmr != MMSYSERR_NOERROR)
{
continue;
}
// Iterate over each destination line, looking for Play Controls.
MIXERCAPS mxcaps;
mmr = mixerGetDevCaps((UINT)m_hMixer, &mxcaps, sizeof(mxcaps));
if(mmr != MMSYSERR_NOERROR)
{
continue;
}
bFoundMixer = true;
for(UINT u = 0; u < mxcaps.cDestinations; u++)
{
MIXERLINE recordLine;
recordLine.cbStruct = sizeof(recordLine);
recordLine.dwDestination = u;
mmr = mixerGetLineInfo((HMIXEROBJ)m_hMixer, &recordLine, MIXER_GETLINEINFOF_DESTINATION);
if(mmr != MMSYSERR_NOERROR)
continue;
// Go through the controls that aren't attached to a specific src connection.
// We're looking for the checkbox that enables the user's microphone for waveIn.
if( recordLine.dwComponentType == MIXERLINE_COMPONENTTYPE_DST_WAVEIN )
{
FindMicSelectControl( recordLine.dwLineID, recordLine.cControls );
}
// Now iterate over each connection (things like wave out, microphone, speaker, CD audio), looking for Microphone.
UINT cConnections = (UINT)recordLine.cConnections;
for (UINT v = 0; v < cConnections; v++)
{
MIXERLINE micLine;
micLine.cbStruct = sizeof(micLine);
micLine.dwDestination = u;
micLine.dwSource = v;
mmr = mixerGetLineInfo((HMIXEROBJ)m_hMixer, &micLine, MIXER_GETLINEINFOF_SOURCE);
if(mmr != MMSYSERR_NOERROR)
continue;
// Now look at all the controls (volume, mute, boost, etc).
controls.RemoveAll();
controls.SetCount(micLine.cControls);
if( !GetLineControls( micLine.dwLineID, controls.Base(), micLine.cControls ) )
continue;
for(UINT i=0; i < micLine.cControls; i++)
{
MIXERCONTROL *pControl = &controls[i];
if(micLine.dwComponentType == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE)
{
if( pControl->dwControlType == MIXERCONTROL_CONTROLTYPE_ONOFF &&
(
strstr(pControl->szShortName, "Gain") ||
strstr(pControl->szShortName, "Boos") ||
strstr(pControl->szShortName, "+20d")
)
)
{
// This is the (record) boost option.
m_ControlInfos[MicBoost].m_bFound = true;
m_ControlInfos[MicBoost].m_dwControlID = pControl->dwControlID;
m_ControlInfos[MicBoost].m_cMultipleItems = pControl->cMultipleItems;
}
if(recordLine.dwComponentType == MIXERLINE_COMPONENTTYPE_DST_SPEAKERS &&
pControl->dwControlType == MIXERCONTROL_CONTROLTYPE_MUTE)
{
// This is the mute button.
m_ControlInfos[MicMute].m_bFound = true;
m_ControlInfos[MicMute].m_dwControlID = pControl->dwControlID;
m_ControlInfos[MicMute].m_cMultipleItems = pControl->cMultipleItems;
}
if(recordLine.dwComponentType == MIXERLINE_COMPONENTTYPE_DST_WAVEIN &&
pControl->dwControlType == MIXERCONTROL_CONTROLTYPE_VOLUME)
{
// This is the mic input level.
m_ControlInfos[MicVolume].m_bFound = true;
m_ControlInfos[MicVolume].m_dwControlID = pControl->dwControlID;
m_ControlInfos[MicVolume].m_cMultipleItems = pControl->cMultipleItems;
// We found a good recording device and can stop looking throught the available devices
bFoundConnectionWithMicVolume = true;
}
}
}
}
}
}
if ( !bFoundMixer )
{
// Failed to find any mixer (MixVolume or not)
Term();
return false;
}
return true;
}
void CMixerControls::Term()
{
if(m_hMixer)
{
mixerClose(m_hMixer);
m_hMixer = 0;
}
Clear();
}
bool CMixerControls::GetValue_Float( Control iControl, float &flValue )
{
if( iControl < 0 || iControl >= NumControls || !m_ControlInfos[iControl].m_bFound )
return false;
if(iControl == MicBoost || iControl == MicMute)
{
bool bValue = false;
bool ret = GetControlOption_Bool(m_ControlInfos[iControl].m_dwControlID, m_ControlInfos[iControl].m_cMultipleItems, bValue);
flValue = (float)bValue;
return ret;
}
else if(iControl == MicVolume)
{
DWORD dwValue = (DWORD)0;
if(GetControlOption_Unsigned(m_ControlInfos[iControl].m_dwControlID, m_ControlInfos[iControl].m_cMultipleItems, dwValue))
{
flValue = dwValue / 65535.0f;
return true;
}
}
return false;
}
bool CMixerControls::SetValue_Float(Control iControl, float flValue )
{
if(iControl < 0 || iControl >= NumControls || !m_ControlInfos[iControl].m_bFound)
return false;
if(iControl == MicBoost || iControl == MicMute)
{
bool bValue = !!flValue;
return SetControlOption_Bool(m_ControlInfos[iControl].m_dwControlID, m_ControlInfos[iControl].m_cMultipleItems, bValue);
}
else if(iControl == MicVolume)
{
DWORD dwValue = (DWORD)(flValue * 65535.0f);
return SetControlOption_Unsigned(m_ControlInfos[iControl].m_dwControlID, m_ControlInfos[iControl].m_cMultipleItems, dwValue);
}
return false;
}
bool CMixerControls::SelectMicrophoneForWaveInput()
{
if( m_dwMicSelectControlID == 0xFFFFFFFF )
return false;
MIXERCONTROLDETAILS_BOOLEAN *pmxcdSelectValue =
(MIXERCONTROLDETAILS_BOOLEAN*)_alloca( sizeof(MIXERCONTROLDETAILS_BOOLEAN) * m_dwMicSelectMultipleItems );
MIXERCONTROLDETAILS mxcd;
mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
mxcd.dwControlID = m_dwMicSelectControlID;
mxcd.cChannels = 1;
mxcd.cMultipleItems = m_dwMicSelectMultipleItems;
mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
mxcd.paDetails = pmxcdSelectValue;
if (mixerGetControlDetails(reinterpret_cast<HMIXEROBJ>(m_hMixer),
&mxcd,
MIXER_OBJECTF_HMIXER |
MIXER_GETCONTROLDETAILSF_VALUE)
== MMSYSERR_NOERROR)
{
// MUX restricts the line selection to one source line at a time.
if( m_dwMicSelectControlType == MIXERCONTROL_CONTROLTYPE_MUX )
{
ZeroMemory(pmxcdSelectValue,
m_dwMicSelectMultipleItems * sizeof(MIXERCONTROLDETAILS_BOOLEAN));
}
// set the Microphone value
pmxcdSelectValue[m_dwMicSelectIndex].fValue = 1;
mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
mxcd.dwControlID = m_dwMicSelectControlID;
mxcd.cChannels = 1;
mxcd.cMultipleItems = m_dwMicSelectMultipleItems;
mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
mxcd.paDetails = pmxcdSelectValue;
if (mixerSetControlDetails(reinterpret_cast<HMIXEROBJ>(m_hMixer),
&mxcd,
MIXER_OBJECTF_HMIXER |
MIXER_SETCONTROLDETAILSF_VALUE) == MMSYSERR_NOERROR)
{
return true;
}
}
return false;
}
void CMixerControls::Clear()
{
m_hMixer = 0;
memset(m_ControlInfos, 0, sizeof(m_ControlInfos));
}
bool CMixerControls::GetControlOption_Bool(DWORD dwControlID, DWORD cMultipleItems, bool &bValue)
{
MIXERCONTROLDETAILS details;
MIXERCONTROLDETAILS_BOOLEAN controlValue;
details.cbStruct = sizeof(details);
details.dwControlID = dwControlID;
details.cChannels = 1; // uniform..
details.cMultipleItems = cMultipleItems;
details.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
details.paDetails = &controlValue;
MMRESULT mmr = mixerGetControlDetails((HMIXEROBJ)m_hMixer, &details, 0L);
if(mmr == MMSYSERR_NOERROR)
{
bValue = !!controlValue.fValue;
return true;
}
else
{
return false;
}
}
bool CMixerControls::SetControlOption_Bool(DWORD dwControlID, DWORD cMultipleItems, const bool bValue)
{
MIXERCONTROLDETAILS details;
MIXERCONTROLDETAILS_BOOLEAN controlValue;
details.cbStruct = sizeof(details);
details.dwControlID = dwControlID;
details.cChannels = 1; // uniform..
details.cMultipleItems = cMultipleItems;
details.cbDetails = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
details.paDetails = &controlValue;
controlValue.fValue = bValue;
MMRESULT mmr = mixerSetControlDetails((HMIXEROBJ)m_hMixer, &details, 0L);
return mmr == MMSYSERR_NOERROR;
}
bool CMixerControls::GetControlOption_Unsigned(DWORD dwControlID, DWORD cMultipleItems, DWORD &value)
{
MIXERCONTROLDETAILS details;
MIXERCONTROLDETAILS_UNSIGNED controlValue;
details.cbStruct = sizeof(details);
details.dwControlID = dwControlID;
details.cChannels = 1; // uniform..
details.cMultipleItems = cMultipleItems;
details.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
details.paDetails = &controlValue;
MMRESULT mmr = mixerGetControlDetails((HMIXEROBJ)m_hMixer, &details, 0L);
if(mmr == MMSYSERR_NOERROR)
{
value = controlValue.dwValue;
return true;
}
else
{
return false;
}
}
bool CMixerControls::SetControlOption_Unsigned(DWORD dwControlID, DWORD cMultipleItems, const DWORD value)
{
MIXERCONTROLDETAILS details;
MIXERCONTROLDETAILS_UNSIGNED controlValue;
details.cbStruct = sizeof(details);
details.dwControlID = dwControlID;
details.cChannels = 1; // uniform..
details.cMultipleItems = cMultipleItems;
details.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
details.paDetails = &controlValue;
controlValue.dwValue = value;
MMRESULT mmr = mixerSetControlDetails((HMIXEROBJ)m_hMixer, &details, 0L);
return mmr == MMSYSERR_NOERROR;
}
bool CMixerControls::GetLineControls( DWORD dwLineID, MIXERCONTROL *controls, DWORD nControls )
{
MIXERLINECONTROLS mxlc;
mxlc.cbStruct = sizeof(mxlc);
mxlc.dwLineID = dwLineID;
mxlc.cControls = nControls;
mxlc.cbmxctrl = sizeof(MIXERCONTROL);
mxlc.pamxctrl = controls;
MMRESULT mmr = mixerGetLineControls((HMIXEROBJ)m_hMixer, &mxlc, MIXER_GETLINECONTROLSF_ALL);
return mmr == MMSYSERR_NOERROR;
}
void CMixerControls::FindMicSelectControl( DWORD dwLineID, DWORD nControls )
{
m_dwMicSelectControlID = 0xFFFFFFFF;
MIXERCONTROL *recControls = (MIXERCONTROL*)_alloca( sizeof(MIXERCONTROL) * nControls );
if( !GetLineControls( dwLineID, recControls, nControls ) )
return;
for( UINT iRecControl=0; iRecControl < nControls; iRecControl++ )
{
if( recControls[iRecControl].dwControlType == MIXERCONTROL_CONTROLTYPE_MIXER ||
recControls[iRecControl].dwControlType == MIXERCONTROL_CONTROLTYPE_MUX )
{
m_dwMicSelectControlID = recControls[iRecControl].dwControlID;
m_dwMicSelectControlType = recControls[iRecControl].dwControlType;
m_dwMicSelectMultipleItems = recControls[iRecControl].cMultipleItems;
m_dwMicSelectIndex = iRecControl;
// Get the index of the one that selects the mic.
MIXERCONTROLDETAILS_LISTTEXT *pmxcdSelectText =
(MIXERCONTROLDETAILS_LISTTEXT*)_alloca( sizeof(MIXERCONTROLDETAILS_LISTTEXT) * m_dwMicSelectMultipleItems );
MIXERCONTROLDETAILS mxcd;
mxcd.cbStruct = sizeof(MIXERCONTROLDETAILS);
mxcd.dwControlID = m_dwMicSelectControlID;
mxcd.cChannels = 1;
mxcd.cMultipleItems = m_dwMicSelectMultipleItems;
mxcd.cbDetails = sizeof(MIXERCONTROLDETAILS_LISTTEXT);
mxcd.paDetails = pmxcdSelectText;
if (mixerGetControlDetails((HMIXEROBJ)m_hMixer,
&mxcd,
MIXER_OBJECTF_HMIXER |
MIXER_GETCONTROLDETAILSF_LISTTEXT) == MMSYSERR_NOERROR)
{
// determine which controls the Microphone source line
for (DWORD dwi = 0; dwi < m_dwMicSelectMultipleItems; dwi++)
{
// get the line information
MIXERLINE mxl;
mxl.cbStruct = sizeof(MIXERLINE);
mxl.dwLineID = pmxcdSelectText[dwi].dwParam1;
if (mixerGetLineInfo((HMIXEROBJ)m_hMixer,
&mxl,
MIXER_OBJECTF_HMIXER |
MIXER_GETLINEINFOF_LINEID) == MMSYSERR_NOERROR &&
mxl.dwComponentType == MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE)
{
// found, dwi is the index.
m_dwMicSelectIndex = dwi;
break;
}
}
}
break;
}
}
}
IMixerControls* g_pMixerControls = NULL;
void InitMixerControls()
{
if ( !g_pMixerControls )
{
g_pMixerControls = new CMixerControls;
}
}
void ShutdownMixerControls()
{
delete g_pMixerControls;
g_pMixerControls = NULL;
}

View File

@ -0,0 +1,47 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef MIXER_CONTROLS_H
#define MIXER_CONTROLS_H
#pragma once
abstract_class IMixerControls
{
public:
virtual ~IMixerControls() {}
enum Control
{
// Microphone boost is a boolean switch that sound cards support which boosts the input signal by about +20dB.
// If this isn't on, the mic is usually way too quiet.
MicBoost=0,
// Volume values are 0-1.
MicVolume,
// Mic playback muting. You usually want this set to false, otherwise the sound card echoes whatever you say into the mic.
MicMute,
NumControls
};
virtual bool GetValue_Float(Control iControl, float &value) = 0;
virtual bool SetValue_Float(Control iControl, float value) = 0;
// Apps like RealJukebox will switch the waveIn input to use CD audio
// rather than the microphone. This should be called at startup to set it back.
virtual bool SelectMicrophoneForWaveInput() = 0;
};
extern IMixerControls *g_pMixerControls;
// Allocates a set of mixer controls.
void InitMixerControls();
void ShutdownMixerControls();
#endif // MIXER_CONTROLS_H

View File

@ -0,0 +1,286 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifdef OSX
#include <Carbon/Carbon.h>
#include <CoreAudio/CoreAudio.h>
#endif
#include "tier0/platform.h"
#include "ivoicerecord.h"
#include "voice_mixer_controls.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#ifndef OSX
class CMixerControls : public IMixerControls
{
public:
CMixerControls() {}
virtual ~CMixerControls() {}
virtual void Release() {}
virtual bool GetValue_Float(Control iControl, float &value ) {return false;}
virtual bool SetValue_Float(Control iControl, float value) {return false;}
virtual bool SelectMicrophoneForWaveInput() {return false;}
virtual const char *GetMixerName() {return "Linux"; }
private:
};
IMixerControls* g_pMixerControls = NULL;
void InitMixerControls()
{
if ( !g_pMixerControls )
{
g_pMixerControls = new CMixerControls;
}
}
void ShutdownMixerControls()
{
delete g_pMixerControls;
g_pMixerControls = NULL;
}
#elif defined(OSX)
class CMixerControls : public IMixerControls
{
public:
CMixerControls();
virtual ~CMixerControls();
virtual void Release();
virtual bool GetValue_Float(Control iControl, float &value);
virtual bool SetValue_Float(Control iControl, float value);
virtual bool SelectMicrophoneForWaveInput();
virtual const char *GetMixerName();
private:
AudioObjectID GetDefaultInputDevice();
char *m_szMixerName;
AudioObjectID m_theDefaultDeviceID;
};
CMixerControls::CMixerControls()
{
m_szMixerName = NULL;
m_theDefaultDeviceID = GetDefaultInputDevice();
OSStatus theStatus;
UInt32 outSize = sizeof(UInt32);
theStatus = AudioDeviceGetPropertyInfo( m_theDefaultDeviceID,
0,
TRUE,
kAudioDevicePropertyDeviceName,
&outSize,
NULL);
if ( theStatus == noErr )
{
m_szMixerName = (char *)malloc( outSize*sizeof(char));
theStatus = AudioDeviceGetProperty( m_theDefaultDeviceID,
0,
TRUE,
kAudioDevicePropertyDeviceName,
&outSize,
m_szMixerName);
if ( theStatus != noErr )
{
free( m_szMixerName );
m_szMixerName = NULL;
}
}
}
CMixerControls::~CMixerControls()
{
if ( m_szMixerName )
free( m_szMixerName );
}
void CMixerControls::Release()
{
}
bool CMixerControls::SelectMicrophoneForWaveInput()
{
return true; // not needed
}
const char *CMixerControls::GetMixerName()
{
return m_szMixerName;
}
bool CMixerControls::GetValue_Float(Control iControl, float &value)
{
switch( iControl)
{
case MicBoost:
{
value = 0.0f;
return true;
}
case MicVolume:
{
OSStatus theError = noErr;
for ( int iChannel = 0; iChannel < 3; iChannel++ )
{
// scan the channel list until you find a channel set to non-zero, then use that
Float32 theVolume = 0;
UInt32 theSize = sizeof(Float32);
AudioObjectPropertyAddress theAddress = { kAudioDevicePropertyVolumeScalar, kAudioDevicePropertyScopeInput, iChannel };
theError = AudioObjectGetPropertyData(m_theDefaultDeviceID,
&theAddress,
0,
NULL,
&theSize,
&theVolume);
value = theVolume;
if ( theError == noErr && theVolume != 0.0f )
break;
}
return theError == noErr;
}
case MicMute:
// Mic playback muting. You usually want this set to false, otherwise the sound card echoes whatever you say into the mic.
{
Float32 theMute = 0;
UInt32 theSize = sizeof(Float32);
AudioObjectPropertyAddress theAddress = { kAudioDevicePropertyMute, kAudioDevicePropertyScopeInput, 1 };
OSStatus theError = AudioObjectGetPropertyData(m_theDefaultDeviceID,
&theAddress,
0,
NULL,
&theSize,
&theMute);
value = theMute;
return theError == noErr;
}
default:
assert( !"Invalid Control type" );
value = 0.0f;
return false;
};
}
bool CMixerControls::SetValue_Float(Control iControl, float value)
{
switch( iControl)
{
case MicBoost:
{
return false;
}
case MicVolume:
{
if ( value <= 0.0 )
return false; // don't let the volume be set to zero
Float32 theVolume = value;
UInt32 size = sizeof(Float32);
Boolean canset = false;
AudioObjectID defaultInputDevice = m_theDefaultDeviceID;
size = sizeof(canset);
OSStatus err = AudioDeviceGetPropertyInfo( defaultInputDevice, 0, true, kAudioDevicePropertyVolumeScalar, &size, &canset);
if(err==noErr && canset==true)
{
size = sizeof(theVolume);
err = AudioDeviceSetProperty( defaultInputDevice, NULL, 0, true, kAudioDevicePropertyVolumeScalar, size, &theVolume);
return err==noErr;
}
// try seperate channels
// get channels
UInt32 channels[2];
size = sizeof(channels);
err = AudioDeviceGetProperty(defaultInputDevice, 0, true, kAudioDevicePropertyPreferredChannelsForStereo, &size,&channels);
if(err!=noErr)
return false;
// set volume
size = sizeof(float);
err = AudioDeviceSetProperty(defaultInputDevice, 0, channels[0], true, kAudioDevicePropertyVolumeScalar, size, &theVolume);
//AssertMsg1( noErr==err, "error setting volume of channel %d\n",(int)channels[0]);
err = AudioDeviceSetProperty(defaultInputDevice, 0, channels[1], true, kAudioDevicePropertyVolumeScalar, size, &theVolume);
//AssertMsg1( noErr==err, "error setting volume of channel %d\n",(int)channels[1]);
return err == noErr;
}
case MicMute:
// Mic playback muting. You usually want this set to false, otherwise the sound card echoes whatever you say into the mic.
{
Float32 theMute = value;
UInt32 theMuteSize = sizeof(Float32);
OSStatus theError = paramErr;
theError = AudioDeviceSetProperty( m_theDefaultDeviceID,
NULL,
0,
TRUE,
kAudioDevicePropertyMute,
theMuteSize,
&theMute);
return theError == noErr;
}
default:
assert( !"Invalid Control type" );
return false;
};
}
AudioObjectID CMixerControls::GetDefaultInputDevice()
{
AudioObjectID theDefaultDeviceID = kAudioObjectUnknown;
AudioObjectPropertyAddress theDefaultDeviceAddress = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };
UInt32 theDefaultDeviceSize = sizeof(AudioObjectID);
OSStatus theError = AudioObjectGetPropertyData (kAudioObjectSystemObject, &theDefaultDeviceAddress, 0, NULL, &theDefaultDeviceSize, &theDefaultDeviceID);
return theDefaultDeviceID;
}
IMixerControls* g_pMixerControls = NULL;
void InitMixerControls()
{
if ( !g_pMixerControls )
{
g_pMixerControls = new CMixerControls;
}
}
void ShutdownMixerControls()
{
delete g_pMixerControls;
g_pMixerControls = NULL;
}
#else
#error
#endif

View File

@ -0,0 +1,400 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// This module implements the voice record and compression functions
#include "audio_pch.h"
#if !defined( _X360 )
#include "dsound.h"
#endif
#include <assert.h>
#include "voice.h"
#include "tier0/vcrmode.h"
#include "ivoicerecord.h"
#if defined( _X360 )
#include "xbox/xbox_win32stubs.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// ------------------------------------------------------------------------------
// Globals.
// ------------------------------------------------------------------------------
typedef HRESULT (WINAPI *DirectSoundCaptureCreateFn)(const GUID FAR *lpGUID, LPDIRECTSOUNDCAPTURE *pCapture, LPUNKNOWN pUnkOuter);
// ------------------------------------------------------------------------------
// Static helpers
// ------------------------------------------------------------------------------
// ------------------------------------------------------------------------------
// VoiceRecord_DSound
// ------------------------------------------------------------------------------
class VoiceRecord_DSound : public IVoiceRecord
{
protected:
virtual ~VoiceRecord_DSound();
// IVoiceRecord.
public:
VoiceRecord_DSound();
virtual void Release();
virtual bool RecordStart();
virtual void RecordStop();
// Initialize. The format of the data we expect from the provider is
// 8-bit signed mono at the specified sample rate.
virtual bool Init(int sampleRate);
virtual void Idle();
// Get the most recent N samples.
virtual int GetRecordedData(short *pOut, int nSamplesWanted);
private:
void Term(); // Delete members.
void Clear(); // Clear members.
void UpdateWrapping();
inline DWORD NumCaptureBufferBytes() {return m_nCaptureBufferBytes;}
private:
HINSTANCE m_hInstDS;
LPDIRECTSOUNDCAPTURE m_pCapture;
LPDIRECTSOUNDCAPTUREBUFFER m_pCaptureBuffer;
// How many bytes our capture buffer has.
DWORD m_nCaptureBufferBytes;
// We need to know when the capture buffer loops, so we install an event and
// update this in the event.
DWORD m_WrapOffset;
HANDLE m_hWrapEvent;
// This is our (unwrapped) position that tells how much data we've given to the app.
DWORD m_LastReadPos;
};
VoiceRecord_DSound::VoiceRecord_DSound()
{
Clear();
}
VoiceRecord_DSound::~VoiceRecord_DSound()
{
Term();
}
void VoiceRecord_DSound::Release()
{
delete this;
}
bool VoiceRecord_DSound::RecordStart()
{
//When we start recording we want to make sure we don't provide any audio
//that occurred before now. So set m_LastReadPos to the current
//read position of the audio device
if (m_pCaptureBuffer == NULL)
{
return false;
}
Idle();
DWORD dwStatus;
HRESULT hr = m_pCaptureBuffer->GetStatus(&dwStatus);
if (FAILED(hr) || !(dwStatus & DSCBSTATUS_CAPTURING))
return false;
DWORD dwReadPos;
hr = m_pCaptureBuffer->GetCurrentPosition(NULL, &dwReadPos);
if (!FAILED(hr))
{
m_LastReadPos = dwReadPos + m_WrapOffset;
}
return true;
}
void VoiceRecord_DSound::RecordStop()
{
}
static bool IsRunningWindows7()
{
if ( IsPC() )
{
OSVERSIONINFOEX osvi;
ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
if ( GetVersionEx ((OSVERSIONINFO *)&osvi) )
{
if ( osvi.dwMajorVersion > 6 || (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion >= 1) )
return true;
}
}
return false;
}
bool VoiceRecord_DSound::Init(int sampleRate)
{
HRESULT hr;
DSCBUFFERDESC dscDesc;
DirectSoundCaptureCreateFn createFn;
Term();
WAVEFORMATEX recordFormat =
{
WAVE_FORMAT_PCM, // wFormatTag
1, // nChannels
(uint32)sampleRate, // nSamplesPerSec
(uint32)sampleRate*2, // nAvgBytesPerSec
2, // nBlockAlign
16, // wBitsPerSample
sizeof(WAVEFORMATEX) // cbSize
};
// Load the DSound DLL.
m_hInstDS = LoadLibrary("dsound.dll");
if(!m_hInstDS)
goto HandleError;
createFn = (DirectSoundCaptureCreateFn)GetProcAddress(m_hInstDS, "DirectSoundCaptureCreate");
if(!createFn)
goto HandleError;
const GUID FAR *pGuid = &DSDEVID_DefaultVoiceCapture;
if ( IsRunningWindows7() )
{
pGuid = NULL;
}
hr = createFn(pGuid, &m_pCapture, NULL);
if(FAILED(hr))
goto HandleError;
// Create the capture buffer.
memset(&dscDesc, 0, sizeof(dscDesc));
dscDesc.dwSize = sizeof(dscDesc);
dscDesc.dwFlags = 0;
dscDesc.dwBufferBytes = recordFormat.nAvgBytesPerSec;
dscDesc.lpwfxFormat = &recordFormat;
hr = m_pCapture->CreateCaptureBuffer(&dscDesc, &m_pCaptureBuffer, NULL);
if(FAILED(hr))
goto HandleError;
// Figure out how many bytes we got in our capture buffer.
DSCBCAPS caps;
memset(&caps, 0, sizeof(caps));
caps.dwSize = sizeof(caps);
hr = m_pCaptureBuffer->GetCaps(&caps);
if(FAILED(hr))
goto HandleError;
m_nCaptureBufferBytes = caps.dwBufferBytes;
// Set it up so we get notification when the buffer wraps.
m_hWrapEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if(!m_hWrapEvent)
goto HandleError;
DSBPOSITIONNOTIFY dsbNotify;
dsbNotify.dwOffset = dscDesc.dwBufferBytes - 1;
dsbNotify.hEventNotify = m_hWrapEvent;
// Get the IDirectSoundNotify interface.
LPDIRECTSOUNDNOTIFY pNotify;
hr = m_pCaptureBuffer->QueryInterface(IID_IDirectSoundNotify, (void**)&pNotify);
if(FAILED(hr))
goto HandleError;
hr = pNotify->SetNotificationPositions(1, &dsbNotify);
pNotify->Release();
if(FAILED(hr))
goto HandleError;
// Start capturing.
hr = m_pCaptureBuffer->Start(DSCBSTART_LOOPING);
if(FAILED(hr))
return false;
return true;
HandleError:;
Term();
return false;
}
void VoiceRecord_DSound::Term()
{
if(m_pCaptureBuffer)
m_pCaptureBuffer->Release();
if(m_pCapture)
m_pCapture->Release();
if(m_hWrapEvent)
DeleteObject(m_hWrapEvent);
if(m_hInstDS)
{
FreeLibrary(m_hInstDS);
m_hInstDS = NULL;
}
Clear();
}
void VoiceRecord_DSound::Clear()
{
m_pCapture = NULL;
m_pCaptureBuffer = NULL;
m_WrapOffset = 0;
m_LastReadPos = 0;
m_hWrapEvent = NULL;
m_hInstDS = NULL;
}
void VoiceRecord_DSound::Idle()
{
UpdateWrapping();
}
int VoiceRecord_DSound::GetRecordedData( short *pOut, int nSamples )
{
if(!m_pCaptureBuffer)
{
assert(false);
return 0;
}
DWORD dwStatus;
HRESULT hr = m_pCaptureBuffer->GetStatus(&dwStatus);
if(FAILED(hr) || !(dwStatus & DSCBSTATUS_CAPTURING))
return 0;
Idle(); // Update wrapping..
DWORD nBytesWanted = (DWORD)( nSamples << 1 );
DWORD dwReadPos;
hr = m_pCaptureBuffer->GetCurrentPosition( NULL, &dwReadPos);
if(FAILED(hr))
return 0;
dwReadPos += m_WrapOffset;
// Read the range (dwReadPos-nSamplesWanted, dwReadPos), but don't re-read data we've already read.
DWORD readStart = Max( dwReadPos - nBytesWanted, (DWORD)0u );
if ( readStart < m_LastReadPos )
{
readStart = m_LastReadPos;
}
// Lock the buffer.
LPVOID pData[2];
DWORD dataLen[2];
hr = m_pCaptureBuffer->Lock(
readStart % NumCaptureBufferBytes(), // Offset.
dwReadPos - readStart, // Number of bytes to lock.
&pData[0], // Buffer 1.
&dataLen[0], // Buffer 1 length.
&pData[1], // Buffer 2.
&dataLen[1], // Buffer 2 length.
0 // Flags.
);
if(FAILED(hr))
return 0;
// Hopefully we didn't get too much data back!
if((dataLen[0]+dataLen[1]) > nBytesWanted )
{
assert(false);
m_pCaptureBuffer->Unlock(pData[0], dataLen[0], pData[1], dataLen[1]);
return 0;
}
// Copy the data to the output.
memcpy(pOut, pData[0], dataLen[0]);
memcpy(&pOut[dataLen[0]/2], pData[1], dataLen[1]);
m_pCaptureBuffer->Unlock(pData[0], dataLen[0], pData[1], dataLen[1]);
// Last Read Position
m_LastReadPos = dwReadPos;
// Return sample count (not bytes)
return (dataLen[0] + dataLen[1]) >> 1;
}
void VoiceRecord_DSound::UpdateWrapping()
{
if(!m_pCaptureBuffer)
return;
// Has the buffer wrapped?
if ( VCRHook_WaitForSingleObject(m_hWrapEvent, 0) == WAIT_OBJECT_0 )
{
m_WrapOffset += m_nCaptureBufferBytes;
}
}
IVoiceRecord* CreateVoiceRecord_DSound(int sampleRate)
{
VoiceRecord_DSound *pRecord = new VoiceRecord_DSound;
if(pRecord && pRecord->Init(sampleRate))
{
return pRecord;
}
else
{
if(pRecord)
pRecord->Release();
return NULL;
}
}

View File

@ -0,0 +1,528 @@
//========= Copyright 1996-2009, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// This module implements the voice record and compression functions
#include <Carbon/Carbon.h>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
#include "tier0/platform.h"
#include "tier0/threadtools.h"
//#include "tier0/vcrmode.h"
#include "ivoicerecord.h"
#define kNumSecAudioBuffer 1.0f
// ------------------------------------------------------------------------------
// VoiceRecord_AudioQueue
// ------------------------------------------------------------------------------
class VoiceRecord_AudioQueue : public IVoiceRecord
{
public:
VoiceRecord_AudioQueue();
virtual ~VoiceRecord_AudioQueue();
// IVoiceRecord.
virtual void Release();
virtual bool RecordStart();
virtual void RecordStop();
// Initialize. The format of the data we expect from the provider is
// 8-bit signed mono at the specified sample rate.
virtual bool Init( int nSampleRate );
virtual void Idle();
// Get the most recent N samples.
virtual int GetRecordedData(short *pOut, int nSamplesWanted );
AudioUnit GetAudioUnit() { return m_AudioUnit; }
AudioConverterRef GetConverter() { return m_Converter; }
void RenderBuffer( const short *pszBuf, int nSamples );
bool BRecording() { return m_bRecordingAudio; }
void ClearThreadHandle() { m_hThread = NULL; m_bFirstInit = false; }
AudioBufferList m_MicInputBuffer;
AudioBufferList m_ConverterBuffer;
void *m_pMicInputBuffer;
int m_nMicInputSamplesAvaialble;
float m_flSampleRateConversion;
int m_nBufferFrameSize;
int m_ConverterBufferSize;
int m_MicInputBufferSize;
int m_InputBytesPerPacket;
private:
bool InitalizeInterfaces(); // Initialize the openal capture buffers and other interfaces
void ReleaseInterfaces(); // Release openal buffers and other interfaces
void ClearInterfaces(); // Clear members.
private:
AudioUnit m_AudioUnit;
char *m_SampleBuffer;
int m_SampleBufferSize;
int m_nSampleRate;
bool m_bRecordingAudio;
bool m_bFirstInit;
ThreadHandle_t m_hThread;
AudioConverterRef m_Converter;
CInterlockedUInt m_SampleBufferReadPos;
CInterlockedUInt m_SampleBufferWritePos;
//UInt32 nPackets = 0;
//bool bHaveListData = false;
};
VoiceRecord_AudioQueue::VoiceRecord_AudioQueue() :
m_nSampleRate( 0 ), m_AudioUnit( NULL ), m_SampleBufferSize(0), m_SampleBuffer(NULL),
m_SampleBufferReadPos(0), m_SampleBufferWritePos(0), m_bRecordingAudio(false), m_hThread( NULL ), m_bFirstInit( true )
{
ClearInterfaces();
}
VoiceRecord_AudioQueue::~VoiceRecord_AudioQueue()
{
ReleaseInterfaces();
if ( m_hThread )
ReleaseThreadHandle( m_hThread );
m_hThread = NULL;
}
void VoiceRecord_AudioQueue::Release()
{
ReleaseInterfaces();
}
uintp StartAudio( void *pRecorder )
{
VoiceRecord_AudioQueue *vr = (VoiceRecord_AudioQueue *)pRecorder;
if ( vr )
{
//printf( "AudioOutputUnitStart\n" );
AudioOutputUnitStart( vr->GetAudioUnit() );
vr->ClearThreadHandle();
}
//printf( "StartAudio thread done\n" );
return 0;
}
bool VoiceRecord_AudioQueue::RecordStart()
{
if ( !m_AudioUnit )
return false;
if ( m_bFirstInit )
m_hThread = CreateSimpleThread( StartAudio, this );
else
AudioOutputUnitStart( m_AudioUnit );
m_SampleBufferReadPos = m_SampleBufferWritePos = 0;
m_bRecordingAudio = true;
//printf( "VoiceRecord_AudioQueue::RecordStart\n" );
return ( !m_bFirstInit || m_hThread != NULL );
}
void VoiceRecord_AudioQueue::RecordStop()
{
// Stop capturing.
if ( m_AudioUnit && m_bRecordingAudio )
{
AudioOutputUnitStop( m_AudioUnit );
//printf( "AudioOutputUnitStop\n" );
}
m_SampleBufferReadPos = m_SampleBufferWritePos = 0;
m_bRecordingAudio = false;
if ( m_hThread )
ReleaseThreadHandle( m_hThread );
m_hThread = NULL;
}
OSStatus ComplexBufferFillPlayback( AudioConverterRef inAudioConverter,
UInt32 *ioNumberDataPackets,
AudioBufferList *ioData,
AudioStreamPacketDescription **outDataPacketDesc,
void *inUserData)
{
VoiceRecord_AudioQueue *vr = (VoiceRecord_AudioQueue *)inUserData;
if ( !vr->BRecording() )
return noErr;
if ( vr->m_nMicInputSamplesAvaialble )
{
int nBytesRequired = *ioNumberDataPackets * vr->m_InputBytesPerPacket;
int nBytesAvailable = vr->m_nMicInputSamplesAvaialble*vr->m_InputBytesPerPacket;
if ( nBytesRequired < nBytesAvailable )
{
ioData->mBuffers[0].mData = vr->m_MicInputBuffer.mBuffers[0].mData;
ioData->mBuffers[0].mDataByteSize = nBytesRequired;
vr->m_MicInputBuffer.mBuffers[0].mData = (char *)vr->m_MicInputBuffer.mBuffers[0].mData+nBytesRequired;
vr->m_MicInputBuffer.mBuffers[0].mDataByteSize = nBytesAvailable - nBytesRequired;
}
else
{
ioData->mBuffers[0].mData = vr->m_MicInputBuffer.mBuffers[0].mData;
ioData->mBuffers[0].mDataByteSize = nBytesAvailable;
vr->m_MicInputBuffer.mBuffers[0].mData = vr->m_pMicInputBuffer;
vr->m_MicInputBuffer.mBuffers[0].mDataByteSize = vr->m_MicInputBufferSize;
}
*ioNumberDataPackets = ioData->mBuffers[0].mDataByteSize / vr->m_InputBytesPerPacket;
vr->m_nMicInputSamplesAvaialble = nBytesAvailable / vr->m_InputBytesPerPacket - *ioNumberDataPackets;
}
else
{
*ioNumberDataPackets = 0;
return -1;
}
return noErr;
}
static OSStatus recordingCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData)
{
VoiceRecord_AudioQueue *vr = (VoiceRecord_AudioQueue *)inRefCon;
if ( !vr->BRecording() )
return noErr;
OSStatus err = noErr;
if ( vr->m_nMicInputSamplesAvaialble == 0 )
{
err = AudioUnitRender( vr->GetAudioUnit(), ioActionFlags, inTimeStamp, 1, inNumberFrames, &vr->m_MicInputBuffer );
if ( err == noErr )
vr->m_nMicInputSamplesAvaialble = vr->m_MicInputBuffer.mBuffers[0].mDataByteSize / vr->m_InputBytesPerPacket;
}
if ( vr->m_nMicInputSamplesAvaialble > 0 )
{
UInt32 nConverterSamples = ceil(vr->m_nMicInputSamplesAvaialble/vr->m_flSampleRateConversion);
vr->m_ConverterBuffer.mBuffers[0].mDataByteSize = vr->m_ConverterBufferSize;
OSStatus err = AudioConverterFillComplexBuffer( vr->GetConverter(),
ComplexBufferFillPlayback,
vr,
&nConverterSamples,
&vr->m_ConverterBuffer,
NULL );
if ( err == noErr || err == -1 )
vr->RenderBuffer( (short *)vr->m_ConverterBuffer.mBuffers[0].mData, vr->m_ConverterBuffer.mBuffers[0].mDataByteSize/sizeof(short) );
}
return err;
}
void VoiceRecord_AudioQueue::RenderBuffer( const short *pszBuf, int nSamples )
{
int samplePos = m_SampleBufferWritePos;
int samplePosBefore = samplePos;
int readPos = m_SampleBufferReadPos;
bool bBeforeRead = false;
if ( samplePos < readPos )
bBeforeRead = true;
char *pOut = (char *)(m_SampleBuffer + samplePos);
int nFirstCopy = MIN( nSamples*sizeof(short), m_SampleBufferSize - samplePos );
memcpy( pOut, pszBuf, nFirstCopy );
samplePos += nFirstCopy;
if ( nSamples*sizeof(short) > nFirstCopy )
{
nSamples -= ( nFirstCopy / sizeof(short) );
samplePos = 0;
memcpy( m_SampleBuffer, pszBuf + nFirstCopy, nSamples * sizeof(short) );
samplePos += nSamples * sizeof(short);
}
m_SampleBufferWritePos = samplePos%m_SampleBufferSize;
if ( (bBeforeRead && samplePos > readPos) )
{
m_SampleBufferReadPos = (readPos+m_SampleBufferSize/2)%m_SampleBufferSize; // if we crossed the read pointer then bump it forward
//printf( "Crossed %d %d (%d)\n", (int)samplePosBefore, (int)samplePos, readPos );
}
}
bool VoiceRecord_AudioQueue::InitalizeInterfaces()
{
//printf( "Initializing audio queue recorder\n" );
// Describe audio component
ComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_HALOutput;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
Component comp = FindNextComponent(NULL, &desc);
if (comp == NULL)
return false;
OSStatus status = OpenAComponent(comp, &m_AudioUnit);
if ( status != noErr )
return false;
// Enable IO for recording
UInt32 flag = 1;
status = AudioUnitSetProperty( m_AudioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input,
1, &flag, sizeof(flag));
if ( status != noErr )
return false;
// disable output on the device
flag = 0;
status = AudioUnitSetProperty( m_AudioUnit,kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output,
0, &flag,sizeof(flag));
if ( status != noErr )
return false;
UInt32 size = sizeof(AudioDeviceID);
AudioDeviceID inputDevice;
status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice,&size, &inputDevice);
if ( status != noErr )
return false;
status =AudioUnitSetProperty( m_AudioUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global,
0, &inputDevice, sizeof(inputDevice));
if ( status != noErr )
return false;
// Describe format
AudioStreamBasicDescription audioDeviceFormat;
size = sizeof(AudioStreamBasicDescription);
status = AudioUnitGetProperty( m_AudioUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
1, // input bus
&audioDeviceFormat,
&size);
if ( status != noErr )
return false;
// we only want mono audio, so if they have a stero input ask for mono
if ( audioDeviceFormat.mChannelsPerFrame == 2 )
{
audioDeviceFormat.mChannelsPerFrame = 1;
audioDeviceFormat.mBytesPerPacket /= 2;
audioDeviceFormat.mBytesPerFrame /= 2;
}
// Apply format
status = AudioUnitSetProperty( m_AudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output,
1, &audioDeviceFormat, sizeof(audioDeviceFormat) );
if ( status != noErr )
return false;
AudioStreamBasicDescription audioOutputFormat;
audioOutputFormat = audioDeviceFormat;
audioOutputFormat.mFormatID = kAudioFormatLinearPCM;
audioOutputFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
audioOutputFormat.mBytesPerPacket = 2; // 16-bit samples * 1 channels
audioOutputFormat.mFramesPerPacket = 1;
audioOutputFormat.mBytesPerFrame = 2; // 16-bit samples * 1 channels
audioOutputFormat.mChannelsPerFrame = 1;
audioOutputFormat.mBitsPerChannel = 16;
audioOutputFormat.mReserved = 0;
audioOutputFormat.mSampleRate = m_nSampleRate;
m_flSampleRateConversion = audioDeviceFormat.mSampleRate / audioOutputFormat.mSampleRate;
// setup sample rate conversion
status = AudioConverterNew( &audioDeviceFormat, &audioOutputFormat, &m_Converter );
if ( status != noErr )
return false;
UInt32 primeMethod = kConverterPrimeMethod_None;
status = AudioConverterSetProperty( m_Converter, kAudioConverterPrimeMethod, sizeof(UInt32), &primeMethod);
if ( status != noErr )
return false;
UInt32 quality = kAudioConverterQuality_Medium;
status = AudioConverterSetProperty( m_Converter, kAudioConverterSampleRateConverterQuality, sizeof(UInt32), &quality);
if ( status != noErr )
return false;
// Set input callback
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = recordingCallback;
callbackStruct.inputProcRefCon = this;
status = AudioUnitSetProperty( m_AudioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global,
0, &callbackStruct, sizeof(callbackStruct) );
if ( status != noErr )
return false;
UInt32 bufferFrameSize;
size = sizeof(bufferFrameSize);
status = AudioDeviceGetProperty( inputDevice, 1, 1, kAudioDevicePropertyBufferFrameSize, &size, &bufferFrameSize );
if ( status != noErr )
return false;
m_nBufferFrameSize = bufferFrameSize;
// allocate the input and conversion sound storage buffers
m_MicInputBuffer.mNumberBuffers = 1;
m_MicInputBuffer.mBuffers[0].mDataByteSize = m_nBufferFrameSize*audioDeviceFormat.mBitsPerChannel/8*audioDeviceFormat.mChannelsPerFrame;
m_MicInputBuffer.mBuffers[0].mData = malloc( m_MicInputBuffer.mBuffers[0].mDataByteSize );
m_MicInputBuffer.mBuffers[0].mNumberChannels = audioDeviceFormat.mChannelsPerFrame;
m_pMicInputBuffer = m_MicInputBuffer.mBuffers[0].mData;
m_MicInputBufferSize = m_MicInputBuffer.mBuffers[0].mDataByteSize;
m_InputBytesPerPacket = audioDeviceFormat.mBytesPerPacket;
m_ConverterBuffer.mNumberBuffers = 1;
m_ConverterBuffer.mBuffers[0].mDataByteSize = m_nBufferFrameSize*audioOutputFormat.mBitsPerChannel/8*audioOutputFormat.mChannelsPerFrame;
m_ConverterBuffer.mBuffers[0].mData = malloc( m_MicInputBuffer.mBuffers[0].mDataByteSize );
m_ConverterBuffer.mBuffers[0].mNumberChannels = 1;
m_ConverterBufferSize = m_ConverterBuffer.mBuffers[0].mDataByteSize;
m_nMicInputSamplesAvaialble = 0;
m_SampleBufferReadPos = m_SampleBufferWritePos = 0;
m_SampleBufferSize = ceil( kNumSecAudioBuffer * m_nSampleRate * audioOutputFormat.mBytesPerPacket );
m_SampleBuffer = (char *)malloc( m_SampleBufferSize );
memset( m_SampleBuffer, 0x0, m_SampleBufferSize );
DevMsg( "Initialized AudioQueue record interface\n" );
return true;
}
bool VoiceRecord_AudioQueue::Init( int nSampleRate )
{
if ( m_AudioUnit && m_nSampleRate != nSampleRate )
{
// Need to recreate interfaces with different sample rate
ReleaseInterfaces();
ClearInterfaces();
}
m_nSampleRate = nSampleRate;
// Re-initialize the capture buffer if neccesary
if ( !m_AudioUnit )
{
InitalizeInterfaces();
}
m_SampleBufferReadPos = m_SampleBufferWritePos = 0;
//printf( "VoiceRecord_AudioQueue::Init()\n" );
// Initialise
OSStatus status = AudioUnitInitialize( m_AudioUnit );
if ( status != noErr )
return false;
return true;
}
void VoiceRecord_AudioQueue::ReleaseInterfaces()
{
AudioOutputUnitStop( m_AudioUnit );
AudioConverterDispose( m_Converter );
AudioUnitUninitialize( m_AudioUnit );
m_AudioUnit = NULL;
m_Converter = NULL;
}
void VoiceRecord_AudioQueue::ClearInterfaces()
{
m_AudioUnit = NULL;
m_Converter = NULL;
m_SampleBufferReadPos = m_SampleBufferWritePos = 0;
if ( m_SampleBuffer )
free( m_SampleBuffer );
m_SampleBuffer = NULL;
if ( m_MicInputBuffer.mBuffers[0].mData )
free( m_MicInputBuffer.mBuffers[0].mData );
if ( m_ConverterBuffer.mBuffers[0].mData )
free( m_ConverterBuffer.mBuffers[0].mData );
m_MicInputBuffer.mBuffers[0].mData = NULL;
m_ConverterBuffer.mBuffers[0].mData = NULL;
}
void VoiceRecord_AudioQueue::Idle()
{
}
int VoiceRecord_AudioQueue::GetRecordedData(short *pOut, int nSamples )
{
if ( !m_SampleBuffer )
return 0;
int cbSamples = nSamples*2; // convert to bytes
int writePos = m_SampleBufferWritePos;
int readPos = m_SampleBufferReadPos;
int nOutstandingSamples = ( writePos - readPos );
if ( readPos > writePos ) // writing has wrapped around
{
nOutstandingSamples = writePos + ( m_SampleBufferSize - readPos );
}
if ( !nOutstandingSamples )
return 0;
if ( nOutstandingSamples < cbSamples )
cbSamples = nOutstandingSamples; // clamp to the number of samples we have available
memcpy( (char *)pOut, m_SampleBuffer + readPos, MIN( cbSamples, m_SampleBufferSize - readPos ) );
if ( cbSamples > ( m_SampleBufferSize - readPos ) )
{
int offset = m_SampleBufferSize - readPos;
cbSamples -= offset;
readPos = 0;
memcpy( (char *)pOut + offset, m_SampleBuffer, cbSamples );
}
readPos+=cbSamples;
m_SampleBufferReadPos = readPos%m_SampleBufferSize;
//printf( "Returning %d samples, %d %d (%d)\n", cbSamples/2, (int)m_SampleBufferReadPos, (int)m_SampleBufferWritePos, m_SampleBufferSize );
return cbSamples/2;
}
VoiceRecord_AudioQueue g_AudioQueueVoiceRecord;
IVoiceRecord* CreateVoiceRecord_AudioQueue( int sampleRate )
{
if ( g_AudioQueueVoiceRecord.Init( sampleRate ) )
{
return &g_AudioQueueVoiceRecord;
}
else
{
g_AudioQueueVoiceRecord.Release();
return NULL;
}
}

View File

@ -0,0 +1,209 @@
//========= Copyright 1996-2009, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// This module implements the voice record and compression functions
//#include "audio_pch.h"
//#include "voice.h"
#include "tier0/platform.h"
#include "ivoicerecord.h"
#include <assert.h>
#ifndef POSIX
class VoiceRecord_OpenAL : public IVoiceRecord
{
public:
VoiceRecord_OpenAL() {}
virtual ~VoiceRecord_OpenAL() {}
virtual void Release() {}
virtual bool RecordStart() { return true; }
virtual void RecordStop() {}
virtual bool Init(int sampleRate) { return true; }
virtual void Idle() {}
virtual int GetRecordedData(short *pOut, int nSamplesWanted ) { return 0; }
};
IVoiceRecord* CreateVoiceRecord_DSound(int sampleRate) { return new VoiceRecord_OpenAL; }
#else
#define min(a,b) (((a) < (b)) ? (a) : (b))
#ifdef OSX
#include <Carbon/Carbon.h>
#include <OpenAL/al.h>
#else
#include <AL/al.h>
#endif
#include "openal/alc.h"
// ------------------------------------------------------------------------------
// VoiceRecord_OpenAL
// ------------------------------------------------------------------------------
class VoiceRecord_OpenAL : public IVoiceRecord
{
protected:
virtual ~VoiceRecord_OpenAL();
// IVoiceRecord.
public:
VoiceRecord_OpenAL();
virtual void Release();
virtual bool RecordStart();
virtual void RecordStop();
// Initialize. The format of the data we expect from the provider is
// 8-bit signed mono at the specified sample rate.
virtual bool Init(int sampleRate);
virtual void Idle();
// Get the most recent N samples.
virtual int GetRecordedData(short *pOut, int nSamplesWanted );
private:
bool InitalizeInterfaces(); // Initialize the openal capture buffers and other interfaces
void ReleaseInterfaces(); // Release openal buffers and other interfaces
void ClearInterfaces(); // Clear members.
private:
ALCdevice *m_Device;
int m_nSampleRate;
};
VoiceRecord_OpenAL::VoiceRecord_OpenAL() :
m_nSampleRate( 0 ), m_Device( NULL )
{
ClearInterfaces();
}
VoiceRecord_OpenAL::~VoiceRecord_OpenAL()
{
ReleaseInterfaces();
}
void VoiceRecord_OpenAL::Release()
{
delete this;
}
bool VoiceRecord_OpenAL::RecordStart()
{
// Re-initialize the capture buffer if neccesary (should always be)
if ( !m_Device )
{
InitalizeInterfaces();
}
if ( !m_Device )
return false;
alcGetError(m_Device);
alcCaptureStart(m_Device);
const ALenum error = alcGetError(m_Device);
return error == AL_NO_ERROR;
}
void VoiceRecord_OpenAL::RecordStop()
{
// Stop capturing.
if ( m_Device )
{
alcCaptureStop( m_Device );
}
// Release the capture buffer interface and any other resources that are no
// longer needed
ReleaseInterfaces();
}
bool VoiceRecord_OpenAL::InitalizeInterfaces()
{
m_Device = alcCaptureOpenDevice( NULL, m_nSampleRate, AL_FORMAT_MONO16, m_nSampleRate * 10 * 2);
const ALenum error = alcGetError(m_Device);
const bool result = error == AL_NO_ERROR;
return m_Device != NULL && result;
}
bool VoiceRecord_OpenAL::Init(int sampleRate)
{
m_nSampleRate = sampleRate;
ReleaseInterfaces();
return true;
}
void VoiceRecord_OpenAL::ReleaseInterfaces()
{
alcCaptureCloseDevice(m_Device);
ClearInterfaces();
}
void VoiceRecord_OpenAL::ClearInterfaces()
{
m_Device = NULL;
}
void VoiceRecord_OpenAL::Idle()
{
}
int VoiceRecord_OpenAL::GetRecordedData(short *pOut, int nSamples )
{
int frameCount = 0;
alcGetIntegerv( m_Device,ALC_CAPTURE_SAMPLES,1,&frameCount );
if ( frameCount > 0 )
{
frameCount = min( nSamples, frameCount );
alcCaptureSamples( m_Device, pOut, frameCount );
if ( alcGetError(m_Device) != ALC_NO_ERROR )
{
return 0;
}
return frameCount;
}
return 0;
}
IVoiceRecord* CreateVoiceRecord_OpenAL(int sampleRate)
{
VoiceRecord_OpenAL *pRecord = new VoiceRecord_OpenAL;
if ( pRecord && pRecord->Init(sampleRate) )
{
return pRecord;
}
else
{
if ( pRecord )
{
pRecord->Release();
}
return NULL;
}
}
#endif

View File

@ -0,0 +1,377 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "audio_pch.h"
#include <assert.h>
#include "voice.h"
#include "ivoicecodec.h"
#if defined( _X360 )
#include "xauddefs.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// ------------------------------------------------------------------------- //
// CAudioSourceVoice.
// This feeds the data from an incoming voice channel (a guy on the server
// who is speaking) into the sound engine.
// ------------------------------------------------------------------------- //
class CAudioSourceVoice : public CAudioSourceWave
{
public:
CAudioSourceVoice(CSfxTable *pSfx, int iEntity);
virtual ~CAudioSourceVoice();
virtual int GetType( void )
{
return AUDIO_SOURCE_VOICE;
}
virtual void GetCacheData( CAudioSourceCachedInfo *info )
{
Assert( 0 );
}
virtual CAudioMixer *CreateMixer( int initialStreamPosition = 0 );
virtual int GetOutputData( void **pData, int samplePosition, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] );
virtual int SampleRate( void );
// Sample size is in bytes. It will not be accurate for compressed audio. This is a best estimate.
// The compressed audio mixers understand this, but in general do not assume that SampleSize() * SampleCount() = filesize
// or even that SampleSize() is 100% accurate due to compression.
virtual int SampleSize( void );
// Total number of samples in this source. NOTE: Some sources are infinite (mic input), they should return
// a count equal to one second of audio at their current rate.
virtual int SampleCount( void );
virtual bool IsVoiceSource() {return true;}
virtual bool IsLooped() {return false;}
virtual bool IsStreaming() {return true;}
virtual bool IsStereoWav() {return false;}
virtual int GetCacheStatus() {return AUDIO_IS_LOADED;}
virtual void CacheLoad() {}
virtual void CacheUnload() {}
virtual CSentence *GetSentence() {return NULL;}
virtual int ZeroCrossingBefore( int sample ) {return sample;}
virtual int ZeroCrossingAfter( int sample ) {return sample;}
// mixer's references
virtual void ReferenceAdd( CAudioMixer *pMixer );
virtual void ReferenceRemove( CAudioMixer *pMixer );
// check reference count, return true if nothing is referencing this
virtual bool CanDelete();
virtual void Prefetch() {}
// Nothing, not a cache object...
virtual void CheckAudioSourceCache() {}
private:
class CWaveDataVoice : public IWaveData
{
public:
CWaveDataVoice( CAudioSourceWave &source ) : m_source(source) {}
~CWaveDataVoice( void ) {}
virtual CAudioSource &Source( void )
{
return m_source;
}
// this file is in memory, simply pass along the data request to the source
virtual int ReadSourceData( void **pData, int sampleIndex, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
{
return m_source.GetOutputData( pData, sampleIndex, sampleCount, copyBuf );
}
virtual bool IsReadyToMix()
{
return true;
}
private:
CAudioSourceWave &m_source; // pointer to source
};
private:
CAudioSourceVoice( const CAudioSourceVoice & );
// Which entity's voice this is for.
int m_iChannel;
// How many mixers are referencing us.
int m_refCount;
};
// ----------------------------------------------------------------------------- //
// Globals.
// ----------------------------------------------------------------------------- //
// The format we sample voice in.
extern WAVEFORMATEX g_VoiceSampleFormat;
class CVoiceSfx : public CSfxTable
{
public:
virtual const char *getname()
{
return "?VoiceSfx";
}
};
static CVoiceSfx g_CVoiceSfx[VOICE_NUM_CHANNELS];
static float g_VoiceOverdriveDuration = 0;
static bool g_bVoiceOverdriveOn = false;
// When voice is on, all other sounds are decreased by this factor.
static ConVar voice_overdrive( "voice_overdrive", "2" );
static ConVar voice_overdrivefadetime( "voice_overdrivefadetime", "0.4" ); // How long it takes to fade in and out of the voice overdrive.
// The sound engine uses this to lower all sound volumes.
// All non-voice sounds are multiplied by this and divided by 256.
int g_SND_VoiceOverdriveInt = 256;
extern int Voice_SamplesPerSec();
extern int Voice_AvgBytesPerSec();
// ----------------------------------------------------------------------------- //
// CAudioSourceVoice implementation.
// ----------------------------------------------------------------------------- //
CAudioSourceVoice::CAudioSourceVoice( CSfxTable *pSfx, int iChannel )
: CAudioSourceWave( pSfx )
{
m_iChannel = iChannel;
m_refCount = 0;
WAVEFORMATEX tmp = g_VoiceSampleFormat;
tmp.nSamplesPerSec = Voice_SamplesPerSec();
tmp.nAvgBytesPerSec = Voice_AvgBytesPerSec();
Init((char*)&tmp, sizeof(tmp));
m_sampleCount = tmp.nSamplesPerSec;
}
CAudioSourceVoice::~CAudioSourceVoice()
{
Voice_OnAudioSourceShutdown( m_iChannel );
}
CAudioMixer *CAudioSourceVoice::CreateMixer( int initialStreamPosition )
{
CWaveDataVoice *pVoice = new CWaveDataVoice(*this);
if(!pVoice)
return NULL;
CAudioMixer *pMixer = CreateWaveMixer( pVoice, WAVE_FORMAT_PCM, 1, BYTES_PER_SAMPLE*8, 0 );
if(!pMixer)
{
delete pVoice;
return NULL;
}
return pMixer;
}
int CAudioSourceVoice::GetOutputData( void **pData, int samplePosition, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
{
int nSamplesGotten = Voice_GetOutputData(
m_iChannel,
copyBuf,
AUDIOSOURCE_COPYBUF_SIZE,
samplePosition,
sampleCount );
// If there weren't enough bytes in the received data channel, pad it with zeros.
if( nSamplesGotten < sampleCount )
{
memset( &copyBuf[nSamplesGotten], 0, (sampleCount - nSamplesGotten) * BYTES_PER_SAMPLE );
nSamplesGotten = sampleCount;
}
*pData = copyBuf;
return nSamplesGotten;
}
int CAudioSourceVoice::SampleRate()
{
return Voice_SamplesPerSec();
}
int CAudioSourceVoice::SampleSize()
{
return BYTES_PER_SAMPLE;
}
int CAudioSourceVoice::SampleCount()
{
return Voice_SamplesPerSec();
}
void CAudioSourceVoice::ReferenceAdd(CAudioMixer *pMixer)
{
m_refCount++;
}
void CAudioSourceVoice::ReferenceRemove(CAudioMixer *pMixer)
{
m_refCount--;
if ( m_refCount <= 0 )
delete this;
}
bool CAudioSourceVoice::CanDelete()
{
return m_refCount == 0;
}
// ----------------------------------------------------------------------------- //
// Interface implementation.
// ----------------------------------------------------------------------------- //
bool VoiceSE_Init()
{
if( !snd_initialized )
return false;
g_SND_VoiceOverdriveInt = 256;
return true;
}
void VoiceSE_Term()
{
// Disable voice ducking.
g_SND_VoiceOverdriveInt = 256;
}
void VoiceSE_Idle(float frametime)
{
g_SND_VoiceOverdriveInt = 256;
if( g_bVoiceOverdriveOn )
{
g_VoiceOverdriveDuration = min( g_VoiceOverdriveDuration+frametime, voice_overdrivefadetime.GetFloat() );
}
else
{
if(g_VoiceOverdriveDuration == 0)
return;
g_VoiceOverdriveDuration = max(g_VoiceOverdriveDuration-frametime, 0.f);
}
float percent = g_VoiceOverdriveDuration / voice_overdrivefadetime.GetFloat();
percent = (float)(-cos(percent * 3.1415926535) * 0.5 + 0.5); // Smooth it out..
float voiceOverdrive = 1 + (voice_overdrive.GetFloat() - 1) * percent;
g_SND_VoiceOverdriveInt = (int)(256 / voiceOverdrive);
}
int VoiceSE_StartChannel(
int iChannel, //! Which channel to start.
int iEntity,
bool bProximity,
int nViewEntityIndex )
{
Assert( iChannel >= 0 && iChannel < VOICE_NUM_CHANNELS );
// Start the sound.
CSfxTable *sfx = &g_CVoiceSfx[iChannel];
sfx->pSource = NULL;
Vector vOrigin(0,0,0);
StartSoundParams_t params;
params.staticsound = false;
params.entchannel = (CHAN_VOICE_BASE+iChannel);
params.pSfx = sfx;
params.origin = vOrigin;
params.fvol = 1.0f;
params.flags = 0;
params.pitch = PITCH_NORM;
if ( bProximity == true )
{
params.bUpdatePositions = true;
params.soundlevel = SNDLVL_TALKING;
params.soundsource = iEntity;
}
else
{
params.soundlevel = SNDLVL_IDLE;
params.soundsource = nViewEntityIndex;
}
return S_StartSound( params );
}
void VoiceSE_EndChannel(
int iChannel, //! Which channel to stop.
int iEntity
)
{
Assert( iChannel >= 0 && iChannel < VOICE_NUM_CHANNELS );
S_StopSound( iEntity, CHAN_VOICE_BASE+iChannel );
// Start the sound.
CSfxTable *sfx = &g_CVoiceSfx[iChannel];
sfx->pSource = NULL;
}
void VoiceSE_StartOverdrive()
{
g_bVoiceOverdriveOn = true;
}
void VoiceSE_EndOverdrive()
{
g_bVoiceOverdriveOn = false;
}
void VoiceSE_InitMouth(int entnum)
{
}
void VoiceSE_CloseMouth(int entnum)
{
}
void VoiceSE_MoveMouth(int entnum, short *pSamples, int nSamples)
{
}
CAudioSource* Voice_SetupAudioSource( int soundsource, int entchannel )
{
int iChannel = entchannel - CHAN_VOICE_BASE;
if( iChannel >= 0 && iChannel < VOICE_NUM_CHANNELS )
{
CSfxTable *sfx = &g_CVoiceSfx[iChannel];
return new CAudioSourceVoice( sfx, iChannel );
}
else
return NULL;
}

View File

@ -0,0 +1,102 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//
// $NoKeywords: $
//=============================================================================//
#ifndef VOICE_SOUND_ENGINE_INTERFACE_H
#define VOICE_SOUND_ENGINE_INTERFACE_H
#pragma once
/*! @defgroup VoiceSoundEngineInterface VoiceSoundEngineInterface
Abstracts out the sound engine for the voice code.
GoldSrc and Src each have a different implementation of this.
@{
*/
//! Max number of receiving voice channels.
#define VOICE_NUM_CHANNELS 5
// ----------------------------------------------------------------------------- //
// Functions for voice.cpp.
// ----------------------------------------------------------------------------- //
//! Initialize the sound engine interface.
bool VoiceSE_Init();
//! Shutdown the sound engine interface.
void VoiceSE_Term();
//! Called each frame.
void VoiceSE_Idle(float frametime);
//! Start audio playback on the specified voice channel.
//! Voice_GetChannelAudio is called by the mixer for each active channel.
int VoiceSE_StartChannel(
//! Which channel to start.
int iChannel,
int iEntity,
bool bProximity,
int nViewEntityIndex
);
//! Stop audio playback on the specified voice channel.
void VoiceSE_EndChannel(
//! Which channel to stop.
int iChannel,
int iEntity
);
//! Starts the voice overdrive (lowers volume of all sounds other than voice).
void VoiceSE_StartOverdrive();
void VoiceSE_EndOverdrive();
//! Control mouth movement for an entity.
void VoiceSE_InitMouth(int entnum);
void VoiceSE_CloseMouth(int entnum);
void VoiceSE_MoveMouth(int entnum, short *pSamples, int nSamples);
// ----------------------------------------------------------------------------- //
// Functions for voice.cpp to implement.
// ----------------------------------------------------------------------------- //
//! This function is implemented in voice.cpp. Gives 16-bit signed mono samples to the mixer.
//! \return Number of samples actually gotten.
int Voice_GetOutputData(
//! The voice channel it wants samples from.
const int iChannel,
//! The buffer to copy the samples into.
char *copyBuf,
//! Maximum size of copyBuf.
const int copyBufSize,
//! Which sample to start at.
const int samplePosition,
//! How many samples to get.
const int sampleCount
);
// This is called when an audio source is deleted by the sound engine. The voice could
// should detach whatever it needs to in order to free up the specified channel.
void Voice_OnAudioSourceShutdown( int iChannel );
// ----------------------------------------------------------------------------- //
// Functions for the sound engine.
// ----------------------------------------------------------------------------- //
class CAudioSource;
CAudioSource* Voice_SetupAudioSource( int soundsource, int entchannel );
/*! @} End VoiceSoundEngineInterface group */
#endif // VOICE_SOUND_ENGINE_INTERFACE_H

View File

@ -0,0 +1,119 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#undef fopen
#include <stdio.h>
#include "voice_wavefile.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
static unsigned long ReadDWord(FILE * fp)
{
unsigned long ret;
fread( &ret, 4, 1, fp );
return ret;
}
static unsigned short ReadWord(FILE * fp)
{
unsigned short ret;
fread( &ret, 2, 1, fp );
return ret;
}
static void WriteDWord(FILE * fp, unsigned long val)
{
fwrite( &val, 4, 1, fp );
}
static void WriteWord(FILE * fp, unsigned short val)
{
fwrite( &val, 2, 1, fp );
}
bool ReadWaveFile(
const char *pFilename,
char *&pData,
int &nDataBytes,
int &wBitsPerSample,
int &nChannels,
int &nSamplesPerSec)
{
FILE * fp = fopen(pFilename, "rb");
if(!fp)
return false;
fseek( fp, 22, SEEK_SET );
nChannels = ReadWord(fp);
nSamplesPerSec = ReadDWord(fp);
fseek(fp, 34, SEEK_SET);
wBitsPerSample = ReadWord(fp);
fseek(fp, 40, SEEK_SET);
nDataBytes = ReadDWord(fp);
ReadDWord(fp);
pData = new char[nDataBytes];
if(!pData)
{
fclose(fp);
return false;
}
fread(pData, nDataBytes, 1, fp);
fclose( fp );
return true;
}
bool WriteWaveFile(
const char *pFilename,
const char *pData,
int nBytes,
int wBitsPerSample,
int nChannels,
int nSamplesPerSec)
{
FILE * fp = fopen(pFilename, "wb");
if(!fp)
return false;
// Write the RIFF chunk.
fwrite("RIFF", 4, 1, fp);
WriteDWord(fp, 0);
fwrite("WAVE", 4, 1, fp);
// Write the FORMAT chunk.
fwrite("fmt ", 4, 1, fp);
WriteDWord(fp, 0x10);
WriteWord(fp, 1); // WAVE_FORMAT_PCM
WriteWord(fp, (unsigned short)nChannels);
WriteDWord(fp, (unsigned long)nSamplesPerSec);
WriteDWord(fp, (unsigned long)((wBitsPerSample / 8) * nChannels * nSamplesPerSec));
WriteWord(fp, (unsigned short)((wBitsPerSample / 8) * nChannels));
WriteWord(fp, (unsigned short)wBitsPerSample);
// Write the DATA chunk.
fwrite("data", 4, 1, fp);
WriteDWord(fp, (unsigned long)nBytes);
fwrite(pData, nBytes, 1, fp);
// Go back and write the length of the riff file.
unsigned long dwVal = ftell(fp) - 8;
fseek( fp, 4, SEEK_SET );
WriteDWord(fp, dwVal);
fclose(fp);
return true;
}

View File

@ -0,0 +1,34 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef VOICE_WAVEFILE_H
#define VOICE_WAVEFILE_H
#pragma once
// Load in a wave file. This isn't very flexible and is only guaranteed to work with files
// saved with WriteWaveFile.
bool ReadWaveFile(
const char *pFilename,
char *&pData,
int &nDataBytes,
int &wBitsPerSample,
int &nChannels,
int &nSamplesPerSec);
// Write out a wave file.
bool WriteWaveFile(
const char *pFilename,
const char *pData,
int nBytes,
int wBitsPerSample,
int nChannels,
int nSamplesPerSec);
#endif // VOICE_WAVEFILE_H

2862
engine/audio/private/vox.cpp Normal file

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More