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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,74 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $Workfile: $
// $Date: $
//
//-----------------------------------------------------------------------------
// $Log: $
//
// $NoKeywords: $
//=============================================================================//
#ifndef VOX_PRIVATE_H
#define VOX_PRIVATE_H
#pragma once
#ifndef VOX_H
#include "vox.h"
#endif
#ifndef UTLVECTOR_H
#include "utlvector.h"
#endif
#include "utlsymbol.h"
struct channel_t;
class CSfxTable;
class CAudioMixer;
struct voxword_t
{
int volume; // increase percent, ie: 125 = 125% increase
int pitch; // pitch shift up percent
int start; // offset start of wave percent
int end; // offset end of wave percent
int cbtrim; // end of wave after being trimmed to 'end'
int fKeepCached; // 1 if this word was already in cache before sentence referenced it
int samplefrac; // if pitch shifting, this is position into wav * 256
int timecompress; // % of wave to skip during playback (causes no pitch shift)
CSfxTable *sfx; // name and cache pointer
};
#define CVOXWORDMAX 32
#define CVOXZEROSCANMAX 255 // scan up to this many samples for next zero crossing
struct sentence_t
{
sentence_t() :
pName( 0 ),
length( 0.0f ),
closecaption( false )
{
}
char *pName;
float length;
bool closecaption : 7;
bool isPrecached : 1;
CUtlSymbol caption;
};
extern CUtlVector<sentence_t> g_Sentences;
extern int VOX_FPaintPitchChannelFrom8( channel_t *ch, sfxcache_t *sc, int count, int pitch, int timecompress );
extern void VOX_TrimStartEndTimes( channel_t *ch, sfxcache_t *sc );
extern int VOX_ParseWordParams( char *psz, voxword_t *pvoxword, int fFirst );
extern void VOX_SetChanVol( channel_t *ch );
extern char **VOX_ParseString( char *psz );
extern CAudioMixer *CreateSentenceMixer( voxword_t *pwords );
#endif // VOX_PRIVATE_H

View File

@@ -0,0 +1,61 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Define the IVoiceCodec interface.
//
// $NoKeywords: $
//=============================================================================//
#ifndef IVOICECODEC_H
#define IVOICECODEC_H
#pragma once
#include "interface.h"
#define BYTES_PER_SAMPLE 2
// This interface is for voice codecs to implement.
// Codecs are guaranteed to be called with the exact output from Compress into Decompress (ie:
// data won't be stuck together and sent to Decompress).
// Decompress is not guaranteed to be called in any specific order relative to Compress, but
// Codecs maintain state between calls, so it is best to call Compress with consecutive voice data
// and decompress likewise. If you call it out of order, it will sound wierd.
// In the same vein, calling Decompress twice with the same data is a bad idea since the state will be
// expecting the next block of data, not the same block.
class IVoiceCodec
{
protected:
virtual ~IVoiceCodec() {}
public:
// Initialize the object. The uncompressed format is always 8-bit signed mono.
virtual bool Init( int quality )=0;
// Use this to delete the object.
virtual void Release()=0;
// Compress the voice data.
// pUncompressed - 16-bit signed mono voice data.
// maxCompressedBytes - The length of the pCompressed buffer. Don't exceed this.
// bFinal - Set to true on the last call to Compress (the user stopped talking).
// Some codecs like big block sizes and will hang onto data you give them in Compress calls.
// When you call with bFinal, the codec will give you compressed data no matter what.
// Return the number of bytes you filled into pCompressed.
virtual int Compress(const char *pUncompressed, int nSamples, char *pCompressed, int maxCompressedBytes, bool bFinal)=0;
// Decompress voice data. pUncompressed is 16-bit signed mono.
virtual int Decompress(const char *pCompressed, int compressedBytes, char *pUncompressed, int maxUncompressedBytes)=0;
// Some codecs maintain state between Compress and Decompress calls. This should clear that state.
virtual bool ResetState()=0;
};
#endif // IVOICECODEC_H

View File

@@ -0,0 +1,40 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef IVOICERECORD_H
#define IVOICERECORD_H
#pragma once
// This is the voice recording interface. It provides 16-bit signed mono data from
// a mic at some sample rate.
abstract_class IVoiceRecord
{
protected:
virtual ~IVoiceRecord() {}
public:
// Use this to delete the object.
virtual void Release()=0;
// Start/stop capturing.
virtual bool RecordStart() = 0;
virtual void RecordStop() = 0;
// Idle processing.
virtual void Idle()=0;
// Get the most recent N samples. If nSamplesWanted is less than the number of
// available samples, it discards the first samples and gives you the last ones.
virtual int GetRecordedData(short *pOut, int nSamplesWanted)=0;
};
#endif // IVOICERECORD_H

View File

@@ -0,0 +1,526 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $Workfile: $
// $Date: $
//
//-----------------------------------------------------------------------------
// $Log: $
//
// $NoKeywords: $
//=============================================================================//
#ifndef SND_AUDIO_SOURCE_H
#define SND_AUDIO_SOURCE_H
#pragma once
#if !defined( _X360 )
#define MP3_SUPPORT 1
#endif
#define AUDIOSOURCE_COPYBUF_SIZE 4096
struct channel_t;
class CSentence;
class CSfxTable;
class CAudioSource;
class IAudioDevice;
class CUtlBuffer;
#include "tier0/vprof.h"
//-----------------------------------------------------------------------------
// Purpose: This is an instance of an audio source.
// Mixers are attached to channels and reference an audio source.
// Mixers are specific to the sample format and source format.
// Mixers are never re-used, so they can track instance data like
// sample position, fractional sample, stream cache, faders, etc.
//-----------------------------------------------------------------------------
abstract_class CAudioMixer
{
public:
virtual ~CAudioMixer( void ) {}
// return number of samples mixed
virtual int MixDataToDevice( IAudioDevice *pDevice, channel_t *pChannel, int sampleCount, int outputRate, int outputOffset ) = 0;
virtual int SkipSamples( channel_t *pChannel, int sampleCount, int outputRate, int outputOffset ) = 0;
virtual bool ShouldContinueMixing( void ) = 0;
virtual CAudioSource *GetSource( void ) = 0;
// get the current position (next sample to be mixed)
virtual int GetSamplePosition( void ) = 0;
// Allow the mixer to modulate pitch and volume.
// returns a floating point modulator
virtual float ModifyPitch( float pitch ) = 0;
virtual float GetVolumeScale( void ) = 0;
// NOTE: Playback is optimized for linear streaming. These calls will usually cost performance
// It is currently optimal to call them before any playback starts, but some audio sources may not
// guarantee this. Also, some mixers may choose to ignore these calls for internal reasons (none do currently).
// Move the current position to newPosition
// BUGBUG: THIS CALL DOES NOT SUPPORT MOVING BACKWARD, ONLY FORWARD!!!
virtual void SetSampleStart( int newPosition ) = 0;
// End playback at newEndPosition
virtual void SetSampleEnd( int newEndPosition ) = 0;
// How many samples to skip before commencing actual data reading ( to allow sub-frametime sound
// offsets and avoid synchronizing sounds to various 100 msec clock intervals throughout the
// engine and game code)
virtual void SetStartupDelaySamples( int delaySamples ) = 0;
virtual int GetMixSampleSize() = 0;
// Certain async loaded sounds lazilly load into memory in the background, use this to determine
// if the sound is ready for mixing
virtual bool IsReadyToMix() = 0;
// NOTE: The "saved" position can be different than the "sample" position
// NOTE: Allows mixer to save file offsets, loop info, etc
virtual int GetPositionForSave() = 0;
virtual void SetPositionFromSaved( int savedPosition ) = 0;
};
inline int CalcSampleSize( int bitsPerSample, int _channels )
{
return (bitsPerSample >> 3) * _channels;
}
#include "UtlCachedFileData.h"
class CSentence;
class CSfxTable;
class CAudioSourceCachedInfo : public IBaseCacheInfo
{
public:
CAudioSourceCachedInfo();
CAudioSourceCachedInfo( const CAudioSourceCachedInfo& src );
virtual ~CAudioSourceCachedInfo();
CAudioSourceCachedInfo& operator =( const CAudioSourceCachedInfo& src );
void Clear();
void RemoveData();
virtual void Save( CUtlBuffer& buf );
virtual void Restore( CUtlBuffer& buf );
virtual void Rebuild( char const *filename );
// A hack, but will work okay
static int s_CurrentType;
static CSfxTable *s_pSfx;
static bool s_bIsPrecacheSound;
inline int Type() const
{
return info.m_Type;
}
void SetType( int type )
{
info.m_Type = type;
}
inline int Bits() const
{
return info.m_bits;
}
void SetBits( int bits )
{
info.m_bits = bits;
}
inline int Channels() const
{
return info.m_channels;
}
void SetChannels( int _channels )
{
info.m_channels = _channels;
}
inline int SampleSize() const
{
return info.m_sampleSize;
}
void SetSampleSize( int size )
{
info.m_sampleSize = size;
}
inline int Format() const
{
return info.m_format;
}
void SetFormat( int format )
{
info.m_format = format;
}
inline int SampleRate() const
{
return info.m_rate;
}
void SetSampleRate( int rate )
{
info.m_rate = rate;
}
inline int CachedDataSize() const
{
return (int)m_usCachedDataSize;
}
void SetCachedDataSize( int size )
{
m_usCachedDataSize = (unsigned short)size;
}
inline const byte *CachedData() const
{
return m_pCachedData;
}
void SetCachedData( const byte *data )
{
m_pCachedData = ( byte * )data;
flags.m_bCachedData = ( data != NULL ) ? true : false;
}
inline int HeaderSize() const
{
return (int)m_usHeaderSize;
}
void SetHeaderSize( int size )
{
m_usHeaderSize = (unsigned short)size;
}
inline const byte *HeaderData() const
{
return m_pHeader;
}
void SetHeaderData( const byte *data )
{
m_pHeader = ( byte * )data;
flags.m_bHeader = ( data != NULL ) ? true : false;
}
inline int LoopStart() const
{
return m_loopStart;
}
void SetLoopStart( int start )
{
m_loopStart = start;
}
inline int SampleCount() const
{
return m_sampleCount;
}
void SetSampleCount( int count )
{
m_sampleCount = count;
}
inline int DataStart() const
{
return m_dataStart;
}
void SetDataStart( int start )
{
m_dataStart = start;
}
inline int DataSize() const
{
return m_dataSize;
}
void SetDataSize( int size )
{
m_dataSize = size;
}
inline CSentence *Sentence() const
{
return m_pSentence;
}
void SetSentence( CSentence *sentence )
{
m_pSentence = sentence;
flags.m_bSentence = ( sentence != NULL ) ? true : false;
}
private:
union
{
unsigned int infolong;
struct
{
unsigned int m_Type : 2; // 0 1 2 or 3
unsigned int m_bits : 5; // 0 to 31
unsigned int m_channels : 2; // 1 or 2
unsigned int m_sampleSize : 3; // 1 2 or 4
unsigned int m_format : 2; // 1 == PCM, 2 == ADPCM
unsigned int m_rate : 17; // 0 to 64 K
} info;
};
union
{
byte flagsbyte;
struct
{
bool m_bSentence : 1;
bool m_bCachedData : 1;
bool m_bHeader : 1;
} flags;
};
int m_loopStart;
int m_sampleCount;
int m_dataStart; // offset of wave data chunk
int m_dataSize; // size of wave data chunk
unsigned short m_usCachedDataSize;
unsigned short m_usHeaderSize;
CSentence *m_pSentence;
byte *m_pCachedData;
byte *m_pHeader;
};
class IAudioSourceCache
{
public:
virtual bool Init( unsigned int memSize ) = 0;
virtual void Shutdown() = 0;
virtual void LevelInit( char const *mapname ) = 0;
virtual void LevelShutdown() = 0;
// This invalidates the cached size/date info for sounds so it'll regenerate that next time it's accessed.
// Used when you connect to a pure server.
virtual void ForceRecheckDiskInfo() = 0;
virtual CAudioSourceCachedInfo *GetInfo( int audiosourcetype, bool soundisprecached, CSfxTable *sfx ) = 0;
virtual void RebuildCacheEntry( int audiosourcetype, bool soundisprecached, CSfxTable *sfx ) = 0;
};
extern IAudioSourceCache *audiosourcecache;
FORWARD_DECLARE_HANDLE( memhandle_t );
typedef int StreamHandle_t;
enum
{
INVALID_STREAM_HANDLE = (StreamHandle_t)~0
};
typedef int BufferHandle_t;
enum
{
INVALID_BUFFER_HANDLE = (BufferHandle_t)~0
};
typedef unsigned int streamFlags_t;
enum
{
STREAMED_FROMDVD = 0x00000001, // stream buffers are compliant to dvd sectors
STREAMED_SINGLEPLAY = 0x00000002, // non recurring data, buffers don't need to persist and can be recycled
STREAMED_QUEUEDLOAD = 0x00000004, // hint the streamer to load using the queued loader system
};
abstract_class IAsyncWavDataCache
{
public:
virtual bool Init( unsigned int memSize ) = 0;
virtual void Shutdown() = 0;
// implementation that treats file as monolithic
virtual memhandle_t AsyncLoadCache( char const *filename, int datasize, int startpos, bool bIsPrefetch = false ) = 0;
virtual void PrefetchCache( char const *filename, int datasize, int startpos ) = 0;
virtual bool CopyDataIntoMemory( char const *filename, int datasize, int startpos, void *buffer, int bufsize, int copystartpos, int bytestocopy, bool *pbPostProcessed ) = 0;
virtual bool CopyDataIntoMemory( memhandle_t& handle, char const *filename, int datasize, int startpos, void *buffer, int bufsize, int copystartpos, int bytestocopy, bool *pbPostProcessed ) = 0;
virtual bool IsDataLoadCompleted( memhandle_t handle, bool *pIsValid ) = 0;
virtual void RestartDataLoad( memhandle_t *pHandle, const char *pFilename, int dataSize, int startpos ) = 0;
virtual bool GetDataPointer( memhandle_t& handle, char const *filename, int datasize, int startpos, void **pData, int copystartpos, bool *pbPostProcessed ) = 0;
virtual void SetPostProcessed( memhandle_t handle, bool proc ) = 0;
virtual void Unload( memhandle_t handle ) = 0;
// alternate multi-buffer streaming implementation
virtual StreamHandle_t OpenStreamedLoad( char const *pFileName, int dataSize, int dataStart, int startPos, int loopPos, int bufferSize, int numBuffers, streamFlags_t flags ) = 0;
virtual void CloseStreamedLoad( StreamHandle_t hStream ) = 0;
virtual int CopyStreamedDataIntoMemory( StreamHandle_t hStream, void *pBuffer, int buffSize, int copyStartPos, int bytesToCopy ) = 0;
virtual bool IsStreamedDataReady( StreamHandle_t hStream ) = 0;
virtual void MarkBufferDiscarded( BufferHandle_t hBuffer ) = 0;
virtual void *GetStreamedDataPointer( StreamHandle_t hStream, bool bSync ) = 0;
virtual bool IsDataLoadInProgress( memhandle_t handle ) = 0;
virtual void Flush() = 0;
virtual void OnMixBegin() = 0;
virtual void OnMixEnd() = 0;
};
extern IAsyncWavDataCache *wavedatacache;
struct CAudioSourceCachedInfoHandle_t
{
CAudioSourceCachedInfoHandle_t() :
info( NULL ),
m_FlushCount( 0 )
{
}
CAudioSourceCachedInfo *info;
unsigned int m_FlushCount;
inline CAudioSourceCachedInfo *Get( int audiosourcetype, bool soundisprecached, CSfxTable *sfx, int *pcacheddatasize )
{
VPROF("CAudioSourceCachedInfoHandle_t::Get");
if ( m_FlushCount != s_nCurrentFlushCount )
{
// Reacquire
info = audiosourcecache->GetInfo( audiosourcetype, soundisprecached, sfx );
if ( pcacheddatasize )
{
*pcacheddatasize = info ? info->CachedDataSize() : 0;
}
// Tag as current
m_FlushCount = s_nCurrentFlushCount;
}
return info;
}
inline bool IsValid()
{
return !!( m_FlushCount == s_nCurrentFlushCount );
}
inline CAudioSourceCachedInfo *FastGet()
{
VPROF("CAudioSourceCachedInfoHandle_t::FastGet");
if ( m_FlushCount != s_nCurrentFlushCount )
{
return NULL;
}
return info;
}
static void InvalidateCache();
static unsigned int s_nCurrentFlushCount;
};
//-----------------------------------------------------------------------------
// Purpose: A source is an abstraction for a stream, cached file, or procedural
// source of audio.
//-----------------------------------------------------------------------------
abstract_class CAudioSource
{
public:
enum
{
AUDIO_SOURCE_UNK = 0,
AUDIO_SOURCE_WAV,
AUDIO_SOURCE_MP3,
AUDIO_SOURCE_VOICE,
AUDIO_SOURCE_MAXTYPE,
};
enum
{
AUDIO_NOT_LOADED = 0,
AUDIO_IS_LOADED = 1,
AUDIO_LOADING = 2,
};
virtual ~CAudioSource( void ) {}
// Create an instance (mixer) of this audio source
virtual CAudioMixer *CreateMixer( int initialStreamPosition = 0 ) = 0;
// Serialization for caching
virtual int GetType( void ) = 0;
virtual void GetCacheData( CAudioSourceCachedInfo *info ) = 0;
// 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 ) = 0;
// 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() = 0;
// 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 ) = 0;
// 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 ) = 0;
virtual int Format( void ) = 0;
virtual int DataSize( void ) = 0;
virtual bool IsLooped( void ) = 0;
virtual bool IsStereoWav( void ) = 0;
virtual bool IsStreaming( void ) = 0;
virtual int GetCacheStatus( void ) = 0;
int IsCached( void ) { return GetCacheStatus() == AUDIO_IS_LOADED ? true : false; }
virtual void CacheLoad( void ) = 0;
virtual void CacheUnload( void ) = 0;
virtual CSentence *GetSentence( void ) = 0;
// these are used to find good splice/loop points.
// If not implementing these, simply return sample
virtual int ZeroCrossingBefore( int sample ) = 0;
virtual int ZeroCrossingAfter( int sample ) = 0;
// mixer's references
virtual void ReferenceAdd( CAudioMixer *pMixer ) = 0;
virtual void ReferenceRemove( CAudioMixer *pMixer ) = 0;
// check reference count, return true if nothing is referencing this
virtual bool CanDelete( void ) = 0;
virtual void Prefetch() = 0;
virtual bool IsAsyncLoad() = 0;
// Make sure our data is rebuilt into the per-level cache
virtual void CheckAudioSourceCache() = 0;
virtual char const *GetFileName() = 0;
virtual void SetPlayOnce( bool ) = 0;
virtual bool IsPlayOnce() = 0;
// Used to identify a word that is part of a sentence mixing operation
virtual void SetSentenceWord( bool bIsWord ) = 0;
virtual bool IsSentenceWord() = 0;
virtual int SampleToStreamPosition( int samplePosition ) = 0;
virtual int StreamToSamplePosition( int streamPosition ) = 0;
};
// Fast method for determining duration of .wav/.mp3, exposed to server as well
extern float AudioSource_GetSoundDuration( char const *pName );
// uses wave file cached in memory already
extern float AudioSource_GetSoundDuration( CSfxTable *pSfx );
#endif // SND_AUDIO_SOURCE_H

View File

@@ -0,0 +1,127 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: This abstracts the various hardware dependent implementations of sound
// At the time of this writing there are Windows WAVEOUT, Direct Sound,
// and Null implementations.
//
//=====================================================================================//
#ifndef SND_DEVICE_H
#define SND_DEVICE_H
#pragma once
#include "snd_fixedint.h"
#include "snd_mix_buf.h"
// sound engine rate defines
#define SOUND_DMA_SPEED 44100 // hardware playback rate
#define SOUND_11k 11025 // 11khz sample rate
#define SOUND_22k 22050 // 22khz sample rate
#define SOUND_44k 44100 // 44khz sample rate
#define SOUND_ALL_RATES 1 // mix all sample rates
#define SOUND_MIX_WET 0 // mix only samples that don't have channel set to 'dry' or 'speaker' (default)
#define SOUND_MIX_DRY 1 // mix only samples with channel set to 'dry' (ie: music)
#define SOUND_MIX_SPEAKER 2 // mix only samples with channel set to 'speaker'
#define SOUND_MIX_SPECIAL_DSP 3 // mix only samples with channel set to 'special dsp'
#define SOUND_BUSS_ROOM (1<<0) // mix samples using channel dspmix value (based on distance from player)
#define SOUND_BUSS_FACING (1<<1) // mix samples using channel dspface value (source facing)
#define SOUND_BUSS_FACINGAWAY (1<<2) // mix samples using 1-dspface
#define SOUND_BUSS_SPEAKER (1<<3) // mix ch->bspeaker samples in mono to speaker buffer
#define SOUND_BUSS_DRY (1<<4) // mix ch->bdry samples into dry buffer
#define SOUND_BUSS_SPECIAL_DSP (1<<5) // mix ch->bspecialdsp samples into special dsp buffer
class Vector;
struct channel_t;
// UNDONE: Create a simulated audio device to replace the old -simsound functionality?
// General interface to an audio device
abstract_class IAudioDevice
{
public:
// Add a virtual destructor to silence the clang warning.
// This is harmless but not important since the only derived class
// doesn't have a destructor.
virtual ~IAudioDevice() {}
// Detect the sound hardware and create a compatible device
// NOTE: This should NEVER fail. There is a function called Audio_GetNullDevice
// which will create a "null" device that makes no sound. If we can't create a real
// sound device, this will return a device of that type. All of the interface
// functions can be called on the null device, but it will not, of course, make sound.
static IAudioDevice *AutoDetectInit( bool waveOnly );
// This is needed by some of the routines to avoid doing work when you've got a null device
virtual bool IsActive( void ) = 0;
// This initializes the sound hardware. true on success, false on failure
virtual bool Init( void ) = 0;
// This releases all sound hardware
virtual void Shutdown( void ) = 0;
// stop outputting sound, but be ready to resume on UnPause
virtual void Pause( void ) = 0;
// return to normal operation after a Pause()
virtual void UnPause( void ) = 0;
// The volume of the "dry" mix (no effects).
// This should return 0 on all implementations that don't need a separate dry mix
virtual float MixDryVolume( void ) = 0;
// Should we mix sounds to a 3D (quadraphonic) sound buffer (front/rear both stereo)
virtual bool Should3DMix( void ) = 0;
// This is called when the application stops all sounds
// NOTE: Stopping each channel and clearing the sound buffer are done separately
virtual void StopAllSounds( void ) = 0;
// Called before painting channels, must calculated the endtime and return it (once per frame)
virtual int PaintBegin( float, int soundtime, int paintedtime ) = 0;
// Called when all channels are painted (once per frame)
virtual void PaintEnd( void ) = 0;
// Called to set the volumes on a channel with the given gain & dot parameters
virtual void SpatializeChannel( int volume[6], int master_vol, const Vector& sourceDir, float gain, float mono ) = 0;
// The device should apply DSP up to endtime in the current paint buffer
// this is called during painting
virtual void ApplyDSPEffects( int idsp, portable_samplepair_t *pbuffront, portable_samplepair_t *pbufrear, portable_samplepair_t *pbufcenter, int samplecount ) = 0;
// replaces SNDDMA_GetDMAPos, gets the output sample position for tracking
virtual int GetOutputPosition( void ) = 0;
// Fill the output buffer with silence (e.g. during pause)
virtual void ClearBuffer( void ) = 0;
// Called each frame with the listener's coordinate system
virtual void UpdateListener( const Vector& position, const Vector& forward, const Vector& right, const Vector& up ) = 0;
// Called each time a new paint buffer is mixed (may be multiple times per frame)
virtual void MixBegin( int sampleCount ) = 0;
virtual void MixUpsample( int sampleCount, int filtertype ) = 0;
// sink sound data
virtual void Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress ) = 0;
virtual void Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress ) = 0;
virtual void Mix16Mono( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress ) = 0;
virtual void Mix16Stereo( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress ) = 0;
// Reset a channel
virtual void ChannelReset( int entnum, int channelIndex, float distanceMod ) = 0;
virtual void TransferSamples( int end ) = 0;
// device parameters
virtual const char *DeviceName( void ) = 0;
virtual int DeviceChannels( void ) = 0; // 1 = mono, 2 = stereo
virtual int DeviceSampleBits( void ) = 0; // bits per sample (8 or 16)
virtual int DeviceSampleBytes( void ) = 0; // above / 8
virtual int DeviceDmaSpeed( void ) = 0; // Actual DMA speed
virtual int DeviceSampleCount( void ) = 0; // Total samples in buffer
virtual bool IsSurround( void ) = 0; // surround enabled, could be quad or 5.1
virtual bool IsSurroundCenter( void ) = 0; // surround enabled as 5.1
virtual bool IsHeadphone( void ) = 0;
};
extern IAudioDevice *Audio_GetNullDevice( void );
#endif // SND_DEVICE_H

View File

@@ -0,0 +1,16 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef SND_IO_H
#define SND_IO_H
#pragma once
class IFileReadBinary;
extern IFileReadBinary *g_pSndIO;
#endif // SND_IO_H

150
engine/audio/public/sound.h Normal file
View File

@@ -0,0 +1,150 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: client sound i/o functions
//
//===========================================================================//
#ifndef SOUND_H
#define SOUND_H
#ifdef _WIN32
#pragma once
#endif
#include "basetypes.h"
#include "datamap.h"
#include "mathlib/vector.h"
#include "mathlib/mathlib.h"
#include "tier1/strtools.h"
#include "soundflags.h"
#include "utlvector.h"
#include "engine/SndInfo.h"
#define MAX_SFX 2048
#define AUDIOSOURCE_CACHE_ROOTDIR "maps/soundcache"
class CSfxTable;
enum soundlevel_t;
struct SoundInfo_t;
struct AudioState_t;
class IFileList;
void S_Init (void);
void S_Shutdown (void);
bool S_IsInitted();
void S_StopAllSounds(bool clear);
void S_Update( const AudioState_t *pAudioState );
void S_ExtraUpdate (void);
void S_ClearBuffer (void);
void S_BlockSound (void);
void S_UnblockSound (void);
float S_GetMasterVolume( void );
void S_SoundFade( float percent, float holdtime, float intime, float outtime );
void S_OnLoadScreen(bool value);
void S_EnableThreadedMixing( bool bEnable );
void S_EnableMusic( bool bEnable );
struct StartSoundParams_t
{
StartSoundParams_t() :
staticsound( false ),
userdata( 0 ),
soundsource( 0 ),
entchannel( CHAN_AUTO ),
pSfx( 0 ),
bUpdatePositions( true ),
fvol( 1.0f ),
soundlevel( SNDLVL_NORM ),
flags( SND_NOFLAGS ),
pitch( PITCH_NORM ),
specialdsp( 0 ),
fromserver( false ),
delay( 0.0f ),
speakerentity( -1 ),
suppressrecording( false ),
initialStreamPosition( 0 )
{
origin.Init();
direction.Init();
}
bool staticsound;
int userdata;
int soundsource;
int entchannel;
CSfxTable *pSfx;
Vector origin;
Vector direction;
bool bUpdatePositions;
float fvol;
soundlevel_t soundlevel;
int flags;
int pitch;
int specialdsp;
bool fromserver;
float delay;
int speakerentity;
bool suppressrecording;
int initialStreamPosition;
};
int S_StartSound( StartSoundParams_t& params );
void S_StopSound ( int entnum, int entchannel );
enum clocksync_index_t
{
CLOCK_SYNC_CLIENT = 0,
CLOCK_SYNC_SERVER,
NUM_CLOCK_SYNCS
};
extern float S_ComputeDelayForSoundtime( float soundtime, clocksync_index_t syncIndex );
void S_StopSoundByGuid( int guid );
float S_SoundDurationByGuid( int guid );
int S_GetGuidForLastSoundEmitted();
bool S_IsSoundStillPlaying( int guid );
void S_GetActiveSounds( CUtlVector< SndInfo_t >& sndlist );
void S_SetVolumeByGuid( int guid, float fvol );
float S_GetElapsedTimeByGuid( int guid );
bool S_IsLoopingSoundByGuid( int guid );
void S_ReloadSound( const char *pSample );
float S_GetMono16Samples( const char *pszName, CUtlVector< short >& sampleList );
CSfxTable *S_DummySfx( const char *name );
CSfxTable *S_PrecacheSound (const char *sample );
void S_PrefetchSound( char const *name, bool bPlayOnce );
void S_MarkUISound( CSfxTable *pSfx );
void S_ReloadFilesInList( IFileList *pFilesToReload );
vec_t S_GetNominalClipDist();
extern bool TestSoundChar(const char *pch, char c);
extern char *PSkipSoundChars(const char *pch);
#include "soundchars.h"
// for recording movies
void SND_MovieStart( void );
void SND_MovieEnd( void );
//-------------------------------------
int S_GetCurrentStaticSounds( SoundInfo_t *pResult, int nSizeResult, int entchannel );
//-----------------------------------------------------------------------------
float S_GetGainFromSoundLevel( soundlevel_t soundlevel, vec_t dist );
struct musicsave_t
{
DECLARE_SIMPLE_DATADESC();
char songname[ 128 ];
int sampleposition;
short master_volume;
};
void S_GetCurrentlyPlayingMusic( CUtlVector< musicsave_t >& list );
void S_RestartSong( const musicsave_t *song );
#endif // SOUND_H

View File

@@ -0,0 +1,135 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Applicaton-level hooks for clients of the audio subsystem
//
// $NoKeywords: $
//=============================================================================//
#ifndef SOUNDSERVICE_H
#define SOUNDSERVICE_H
#if defined( _WIN32 )
#pragma once
#endif
class Vector;
class QAngle;
class CAudioSource;
typedef int SoundSource;
struct SpatializationInfo_t;
typedef void *FileNameHandle_t;
struct StartSoundParams_t;
#include "utlrbtree.h"
//-----------------------------------------------------------------------------
// Purpose: Services required by the audio system to function, this facade
// defines the bridge between the audio code and higher level
// systems.
//
// Note that some of these currently suggest that certain
// functionality would like to exist at a deeper layer so
// systems like audio can take advantage of them
// diectly (toml 05-02-02)
//-----------------------------------------------------------------------------
abstract_class ISoundServices
{
public:
//---------------------------------
// Allocate a block of memory that will be automatically
// cleaned up on level change
//---------------------------------
virtual void *LevelAlloc( int nBytes, const char *pszTag ) = 0;
//---------------------------------
// Notification that someone called S_ExtraUpdate()
//---------------------------------
virtual void OnExtraUpdate() = 0;
//---------------------------------
// Return false if the entity doesn't exist or is out of the PVS, in which case the sound shouldn't be heard.
//---------------------------------
virtual bool GetSoundSpatialization( int entIndex, SpatializationInfo_t& info ) = 0;
//---------------------------------
// This is the client's clock, which follows the servers and thus isn't 100% smooth all the time (it is in single player)
//---------------------------------
virtual float GetClientTime() = 0;
//---------------------------------
// This is the engine's filtered timer, it's pretty smooth all the time
//---------------------------------
virtual float GetHostTime() = 0;
//---------------------------------
//---------------------------------
virtual int GetViewEntity() = 0;
//---------------------------------
//---------------------------------
virtual float GetHostFrametime() = 0;
virtual void SetSoundFrametime( float realDt, float hostDt ) = 0;
//---------------------------------
//---------------------------------
virtual int GetServerCount() = 0;
//---------------------------------
//---------------------------------
virtual bool IsPlayer( SoundSource source ) = 0;
//---------------------------------
//---------------------------------
virtual void OnChangeVoiceStatus( int entity, bool status) = 0;
// Is the player fully connected (don't do DSP processing if not)
virtual bool IsConnected() = 0;
// Calls into client .dll with list of close caption tokens to construct a caption out of
virtual void EmitSentenceCloseCaption( char const *tokenstream ) = 0;
// Calls into client .dll with list of close caption tokens to construct a caption out of
virtual void EmitCloseCaption( char const *captionname, float duration ) = 0;
virtual char const *GetGameDir() = 0;
// If the game is paused, certain audio will pause, too (anything with phoneme/sentence data for now)
virtual bool IsGamePaused() = 0;
// If the game is not active, certain audio will pause
virtual bool IsGameActive() = 0;
// restarts the sound system externally
virtual void RestartSoundSystem() = 0;
virtual void GetAllSoundFilesReferencedInReslists( CUtlRBTree< FileNameHandle_t, int >& list ) = 0;
virtual void GetAllManifestFiles( CUtlRBTree< FileNameHandle_t, int >& list ) = 0;
virtual void GetAllSoundFilesInManifest( CUtlRBTree< FileNameHandle_t, int >& list, char const *manifestfile ) = 0;
virtual void CacheBuildingStart() = 0;
virtual void CacheBuildingUpdateProgress( float percent, char const *cachefile ) = 0;
virtual void CacheBuildingFinish() = 0;
// For building sound cache manifests
virtual int GetPrecachedSoundCount() = 0;
virtual char const *GetPrecachedSound( int index ) = 0;
virtual void OnSoundStarted( int guid, StartSoundParams_t& params, char const *soundname ) = 0;
virtual void OnSoundStopped( int guid, int soundsource, int channel, char const *soundname ) = 0;
virtual bool GetToolSpatialization( int iUserData, int guid, SpatializationInfo_t& info ) = 0;
#if defined( _XBOX )
virtual bool ShouldSuppressNonUISounds() = 0;
#endif
virtual char const *GetUILanguage() = 0;
};
//-------------------------------------
extern ISoundServices *g_pSoundServices;
//=============================================================================
#endif // SOUNDSERVICE_H

138
engine/audio/public/voice.h Normal file
View File

@@ -0,0 +1,138 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef VOICE_H
#define VOICE_H
#pragma once
#include "ivoicetweak.h"
/*! @defgroup Voice Voice
Defines the engine's interface to the voice code.
@{
*/
// Voice_Init will pick a sample rate, it must be within RATE_MAX
#define VOICE_OUTPUT_SAMPLE_RATE_LOW 11025 // Sample rate that we feed to the mixer.
#define VOICE_OUTPUT_SAMPLE_RATE_HIGH 22050 // Sample rate that we feed to the mixer.
#define VOICE_OUTPUT_SAMPLE_RATE_MAX 22050 // Sample rate that we feed to the mixer.
//! Returned on error from certain voice functions.
#define VOICE_CHANNEL_ERROR -1
#define VOICE_CHANNEL_IN_TWEAK_MODE -2 // Returned by AssignChannel if currently in tweak mode (not an error).
//! Initialize the voice code.
bool Voice_Init( const char *pCodec, int nSampleRate );
//! Inits voice with defaults if it is not initialized normally, e.g. for local mixer use.
void Voice_ForceInit();
//! Get the default sample rate to use for this codec
inline int Voice_GetDefaultSampleRate( const char *pCodec ) // Inline for DEDICATED builds
{
// Use legacy lower rate for speex
if ( Q_stricmp( pCodec, "vaudio_speex" ) == 0 )
{
return VOICE_OUTPUT_SAMPLE_RATE_LOW;
}
else if ( Q_stricmp( pCodec, "steam" ) == 0 )
{
return 0; // For the steam codec, 0 passed to voice_init means "use optimal steam voice rate"
}
// Use high sample rate by default for other codecs.
return VOICE_OUTPUT_SAMPLE_RATE_HIGH;
}
//! Shutdown the voice code.
void Voice_Deinit();
//! Returns true if the client has voice enabled
bool Voice_Enabled( void );
//! The codec voice was initialized with. Empty string if voice is not initialized.
const char *Voice_ConfiguredCodec();
//! The sample rate voice was initialized with. -1 if voice is not initialized.
int Voice_ConfiguredSampleRate();
//! Returns true if the user can hear themself speak.
bool Voice_GetLoopback();
//! This is called periodically by the engine when the server acks the local player talking.
//! This tells the client DLL that the local player is talking and fades after about 200ms.
void Voice_LocalPlayerTalkingAck();
//! Call every frame to update the voice stuff.
void Voice_Idle(float frametime);
//! Returns true if mic input is currently being recorded.
bool Voice_IsRecording();
//! Begin recording input from the mic.
bool Voice_RecordStart(
//! Filename to store incoming mic data, or NULL if none.
const char *pUncompressedFile,
//! Filename to store the output of compression and decompressiong with the codec, or NULL if none.
const char *pDecompressedFile,
//! If this is non-null, the voice manager will use this file for input instead of the mic.
const char *pMicInputFile
);
// User wants to stop recording
void Voice_UserDesiresStop();
//! Stop recording from the mic.
bool Voice_RecordStop();
//! Get the most recent N bytes of compressed data. If nCount is less than the number of
//! available bytes, it discards the first bytes and gives you the last ones.
//! Set bFinal to true on the last call to this (it will flush out any stored voice data).
int Voice_GetCompressedData(char *pchData, int nCount, bool bFinal);
//! Pass incoming data from the server into here.
//! The data should have been compressed and gotten through a Voice_GetCompressedData call.
int Voice_AddIncomingData(
//! Channel index.
int nChannel,
//! Compressed data to add to the channel.
const char *pchData,
//! Number of bytes in pchData.
int nCount,
//! Sequence number. If a packet is missed, it adds padding so the time isn't squashed.
int iSequenceNumber
);
//! Call this to reserve a voice channel for the specified entity to talk into.
//! \return A channel index for use with Voice_AddIncomingData or VOICE_CHANNEL_ERROR on error.
int Voice_AssignChannel(int nEntity, bool bProximity );
//! Call this to get the channel index that the specified entity is talking into.
//! \return A channel index for use with Voice_AddIncomingData or VOICE_CHANNEL_ERROR if the entity isn't talking.
int Voice_GetChannel(int nEntity);
#if !defined( NO_VOICE )
extern IVoiceTweak g_VoiceTweakAPI;
extern bool g_bUsingSteamVoice;
#endif
/*! @} */
#endif // VOICE_H

46
engine/audio/public/vox.h Normal file
View File

@@ -0,0 +1,46 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $Workfile: $
// $Date: $
//
//-----------------------------------------------------------------------------
// $Log: $
//
// $NoKeywords: $
//=============================================================================//
#ifndef VOX_H
#define VOX_H
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
struct sfxcache_t;
struct channel_t;
class CUtlSymbol;
extern void VOX_Init( void );
extern void VOX_Shutdown( void );
extern void VOX_ReadSentenceFile( const char *psentenceFileName );
extern int VOX_SentenceCount( void );
extern void VOX_LoadSound( channel_t *pchan, const char *psz );
// UNDONE: Improve the interface of this call, it returns sentence data AND the sentence index
extern char *VOX_LookupString( const char *pSentenceName, int *psentencenum, bool *pbEmitCaption = NULL, CUtlSymbol *pCaptionSymbol = NULL, float * pflDuration = NULL );
extern void VOX_PrecacheSentenceGroup( class IEngineSound *pSoundSystem, const char *pGroupName, const char *pPathOverride = NULL );
extern const char *VOX_SentenceNameFromIndex( int sentencenum );
extern float VOX_SentenceLength( int sentence_num );
extern const char *VOX_GroupNameFromIndex( int groupIndex );
extern int VOX_GroupIndexFromName( const char *pGroupName );
extern int VOX_GroupPick( int isentenceg, char *szfound, int strLen );
extern int VOX_GroupPickSequential( int isentenceg, char *szfound, int szfoundLen, int ipick, int freset );
#ifdef __cplusplus
}
#endif
#endif // VOX_H