mirror of
https://github.com/nillerusr/source-engine.git
synced 2026-08-08 17:59:37 +00:00
1
This commit is contained in:
@@ -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()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
} */
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 */
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+2358
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,10 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $Workfile: $
|
||||
// $Date: $
|
||||
// $NoKeywords: $
|
||||
//=============================================================================//
|
||||
|
||||
#include "audio_pch.h"
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
@@ -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 )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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 system‚use 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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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
|
||||
@@ -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] );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,400 @@
|
||||
//========= Copyright © 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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( ©Buf[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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
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
Reference in New Issue
Block a user