mirror of
https://github.com/celisej567/source-engine.git
synced 2026-01-04 18:09:53 +03:00
1
This commit is contained in:
10
engine/audio/audio_pch.cpp
Normal file
10
engine/audio/audio_pch.cpp
Normal 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
66
engine/audio/audio_pch.h
Normal 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
|
||||
414
engine/audio/private/MPAFile.cpp
Normal file
414
engine/audio/private/MPAFile.cpp
Normal 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;
|
||||
}
|
||||
125
engine/audio/private/MPAFile.h
Normal file
125
engine/audio/private/MPAFile.h
Normal 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
|
||||
332
engine/audio/private/MPAHeader.cpp
Normal file
332
engine/audio/private/MPAHeader.cpp
Normal 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
|
||||
};
|
||||
116
engine/audio/private/MPAHeader.h
Normal file
116
engine/audio/private/MPAHeader.h
Normal 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
|
||||
304
engine/audio/private/VBRHeader.cpp
Normal file
304
engine/audio/private/VBRHeader.cpp
Normal 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;
|
||||
}
|
||||
|
||||
72
engine/audio/private/VBRHeader.h
Normal file
72
engine/audio/private/VBRHeader.h
Normal 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
|
||||
271
engine/audio/private/circularbuffer.cpp
Normal file
271
engine/audio/private/circularbuffer.cpp
Normal 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 );
|
||||
}
|
||||
|
||||
99
engine/audio/private/circularbuffer.h
Normal file
99
engine/audio/private/circularbuffer.h
Normal 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
124
engine/audio/private/eax.h
Normal 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
|
||||
|
||||
234
engine/audio/private/posix_stubs.h
Normal file
234
engine/audio/private/posix_stubs.h
Normal 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
|
||||
|
||||
213
engine/audio/private/snd_channels.h
Normal file
213
engine/audio/private/snd_channels.h
Normal 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
|
||||
21
engine/audio/private/snd_convars.h
Normal file
21
engine/audio/private/snd_convars.h
Normal 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
|
||||
623
engine/audio/private/snd_dev_common.cpp
Normal file
623
engine/audio/private/snd_dev_common.cpp
Normal 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;
|
||||
}
|
||||
59
engine/audio/private/snd_dev_common.h
Normal file
59
engine/audio/private/snd_dev_common.h
Normal 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
|
||||
2094
engine/audio/private/snd_dev_direct.cpp
Normal file
2094
engine/audio/private/snd_dev_direct.cpp
Normal file
File diff suppressed because it is too large
Load Diff
14
engine/audio/private/snd_dev_direct.h
Normal file
14
engine/audio/private/snd_dev_direct.h
Normal 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
|
||||
599
engine/audio/private/snd_dev_mac_audioqueue.cpp
Normal file
599
engine/audio/private/snd_dev_mac_audioqueue.cpp
Normal 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 )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
14
engine/audio/private/snd_dev_mac_audioqueue.h
Normal file
14
engine/audio/private/snd_dev_mac_audioqueue.h
Normal 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
|
||||
611
engine/audio/private/snd_dev_openal.cpp
Normal file
611
engine/audio/private/snd_dev_openal.cpp
Normal 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 system‚use default output device
|
||||
const ALCchar *initStr = (const ALCchar *)"\'( (sampling-rate 44100 ))";
|
||||
|
||||
newDevice = alcOpenDevice(initStr);
|
||||
if (newDevice != NULL)
|
||||
{
|
||||
// Create a new OpenAL Context
|
||||
// The new context will render to the OpenAL Device just created
|
||||
ALCint attr[] = { ALC_FREQUENCY, DeviceDmaSpeed(), ALC_SYNC, AL_FALSE, 0 };
|
||||
|
||||
newContext = alcCreateContext(newDevice, attr );
|
||||
if (newContext != NULL)
|
||||
{
|
||||
// Make the new context the Current OpenAL Context
|
||||
alcMakeContextCurrent(newContext);
|
||||
|
||||
// Create some OpenAL Buffer Objects
|
||||
alGenBuffers( NUM_BUFFERS_SOURCES, m_Buffer);
|
||||
if((error = alGetError()) != AL_NO_ERROR)
|
||||
{
|
||||
DevMsg("Error Generating Buffers: ");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create some OpenAL Source Objects
|
||||
alGenSources(1, m_Source);
|
||||
if(alGetError() != AL_NO_ERROR)
|
||||
{
|
||||
DevMsg("Error generating sources! \n");
|
||||
return;
|
||||
}
|
||||
|
||||
alListener3f( AL_POSITION,0.0f,0.0f,0.0f);
|
||||
int i;
|
||||
for ( i = 0; i < 1; i++ )
|
||||
{
|
||||
alSource3f( m_Source[i],AL_POSITION,0.0f,0.0f,0.0f );
|
||||
alSourcef( m_Source[i], AL_PITCH, 1.0f );
|
||||
alSourcef( m_Source[i], AL_GAIN, 1.0f );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
m_SndBufSize = NUM_BUFFERS_SOURCES*BUFFER_SIZE;
|
||||
m_deviceSampleCount = m_SndBufSize / DeviceSampleBytes();
|
||||
|
||||
if ( !m_sndBuffers )
|
||||
{
|
||||
m_sndBuffers = malloc( m_SndBufSize );
|
||||
memset( m_sndBuffers, 0x0, m_SndBufSize );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Closes the windows wave out device
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAudioDeviceOpenAL::CloseWaveOut( void )
|
||||
{
|
||||
if ( ValidWaveOut() )
|
||||
{
|
||||
ALCcontext *context = NULL;
|
||||
ALCdevice *device = NULL;
|
||||
|
||||
m_bSoundsShutdown = true;
|
||||
alSourceStop( m_Source[0] );
|
||||
|
||||
// Delete the Sources
|
||||
alDeleteSources(1, m_Source);
|
||||
// Delete the Buffers
|
||||
alDeleteBuffers(NUM_BUFFERS_SOURCES, m_Buffer);
|
||||
|
||||
//Get active context
|
||||
context = alcGetCurrentContext();
|
||||
//Get device for active context
|
||||
device = alcGetContextsDevice(context);
|
||||
alcMakeContextCurrent( NULL );
|
||||
alcSuspendContext(context);
|
||||
//Release context
|
||||
alcDestroyContext(context);
|
||||
//Close device
|
||||
alcCloseDevice(device);
|
||||
}
|
||||
|
||||
if ( m_sndBuffers )
|
||||
{
|
||||
free( m_sndBuffers );
|
||||
m_sndBuffers = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Mixing setup
|
||||
//-----------------------------------------------------------------------------
|
||||
int CAudioDeviceOpenAL::PaintBegin( float mixAheadTime, int soundtime, int paintedtime )
|
||||
{
|
||||
// soundtime - total samples that have been played out to hardware at dmaspeed
|
||||
// paintedtime - total samples that have been mixed at speed
|
||||
// endtime - target for samples in mixahead buffer at speed
|
||||
|
||||
unsigned int endtime = soundtime + mixAheadTime * DeviceDmaSpeed();
|
||||
|
||||
int samps = DeviceSampleCount() >> (DeviceChannels()-1);
|
||||
|
||||
if ((int)(endtime - soundtime) > samps)
|
||||
endtime = soundtime + samps;
|
||||
|
||||
if ((endtime - paintedtime) & 0x3)
|
||||
{
|
||||
// The difference between endtime and painted time should align on
|
||||
// boundaries of 4 samples. This is important when upsampling from 11khz -> 44khz.
|
||||
endtime -= (endtime - paintedtime) & 0x3;
|
||||
}
|
||||
|
||||
return endtime;
|
||||
}
|
||||
|
||||
|
||||
#ifdef OSX
|
||||
ALvoid alBufferDataStaticProc(const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq)
|
||||
{
|
||||
static alBufferDataStaticProcPtr proc = NULL;
|
||||
|
||||
if (proc == NULL) {
|
||||
proc = (alBufferDataStaticProcPtr) alGetProcAddress((const ALCchar*) "alBufferDataStatic");
|
||||
}
|
||||
|
||||
if (proc)
|
||||
proc(bid, format, data, size, freq);
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Actually performs the mixing
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAudioDeviceOpenAL::PaintEnd( void )
|
||||
{
|
||||
if ( !m_sndBuffers /*|| m_bSoundsShutdown*/ )
|
||||
return;
|
||||
|
||||
ALint state;
|
||||
ALenum error;
|
||||
int iloop;
|
||||
|
||||
int cblocks = 4 << 1;
|
||||
ALint processed = 1;
|
||||
ALuint lastUnqueuedBuffer = 0;
|
||||
ALuint unqueuedBuffer = -1;
|
||||
int nProcessedLoop = 200; // spin for a max of 200 times de-queing buffers, fixes a hang on exit
|
||||
while ( processed > 0 && --nProcessedLoop > 0 )
|
||||
{
|
||||
alGetSourcei( m_Source[ 0 ], AL_BUFFERS_PROCESSED, &processed);
|
||||
error = alGetError();
|
||||
if (error != AL_NO_ERROR)
|
||||
break;
|
||||
|
||||
if ( processed > 0 )
|
||||
{
|
||||
lastUnqueuedBuffer = unqueuedBuffer;
|
||||
alSourceUnqueueBuffers( m_Source[ 0 ], 1, &unqueuedBuffer );
|
||||
error = alGetError();
|
||||
if ( error != AL_NO_ERROR && error != AL_INVALID_NAME )
|
||||
{
|
||||
DevMsg( "Error alSourceUnqueueBuffers %d\n", error );
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_buffersCompleted++; // this buffer has been played
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// submit a few new sound blocks
|
||||
//
|
||||
// 44K sound support
|
||||
while (((m_buffersSent - m_buffersCompleted) >> SAMPLE_16BIT_SHIFT) < cblocks)
|
||||
{
|
||||
int iBuf = m_buffersSent&BUFF_MASK;
|
||||
#ifdef OSX
|
||||
alBufferDataStaticProc( m_Buffer[iBuf], AL_FORMAT_STEREO16, (char *)m_sndBuffers + iBuf*BUFFER_SIZE, BUFFER_SIZE, DeviceDmaSpeed() );
|
||||
#else
|
||||
alBufferData( m_Buffer[iBuf], AL_FORMAT_STEREO16, (char *)m_sndBuffers + iBuf*BUFFER_SIZE, BUFFER_SIZE, DeviceDmaSpeed() );
|
||||
#endif
|
||||
if ( (error = alGetError()) != AL_NO_ERROR )
|
||||
{
|
||||
DevMsg( "Error alBufferData %d %d\n", iBuf, error );
|
||||
}
|
||||
|
||||
alSourceQueueBuffers( m_Source[0], 1, &m_Buffer[iBuf] );
|
||||
if ( (error = alGetError() ) != AL_NO_ERROR )
|
||||
{
|
||||
DevMsg( "Error alSourceQueueBuffers %d %d\n", iBuf, error );
|
||||
}
|
||||
m_buffersSent++;
|
||||
}
|
||||
|
||||
// make sure the stream is playing
|
||||
alGetSourcei( m_Source[ 0 ], AL_SOURCE_STATE, &state);
|
||||
if ( state != AL_PLAYING )
|
||||
{
|
||||
DevMsg( "Restarting sound playback\n" );
|
||||
alSourcePlay( m_Source[0] );
|
||||
if((error = alGetError()) != AL_NO_ERROR)
|
||||
{
|
||||
DevMsg( "Error alSourcePlay %d\n", error );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int CAudioDeviceOpenAL::GetOutputPosition( void )
|
||||
{
|
||||
int s = m_buffersSent * BUFFER_SIZE;
|
||||
|
||||
s >>= SAMPLE_16BIT_SHIFT;
|
||||
|
||||
s &= (DeviceSampleCount()-1);
|
||||
|
||||
return s / DeviceChannels();
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Pausing
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAudioDeviceOpenAL::Pause( void )
|
||||
{
|
||||
m_pauseCount++;
|
||||
if (m_pauseCount == 1)
|
||||
{
|
||||
alSourceStop( m_Source[0] );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CAudioDeviceOpenAL::UnPause( void )
|
||||
{
|
||||
if ( m_pauseCount > 0 )
|
||||
{
|
||||
m_pauseCount--;
|
||||
}
|
||||
|
||||
if ( m_pauseCount == 0 )
|
||||
{
|
||||
alSourcePlay( m_Source[0] );
|
||||
}
|
||||
}
|
||||
|
||||
bool CAudioDeviceOpenAL::IsActive( void )
|
||||
{
|
||||
return ( m_pauseCount == 0 );
|
||||
}
|
||||
|
||||
float CAudioDeviceOpenAL::MixDryVolume( void )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
bool CAudioDeviceOpenAL::Should3DMix( void )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void CAudioDeviceOpenAL::ClearBuffer( void )
|
||||
{
|
||||
if ( !m_sndBuffers )
|
||||
return;
|
||||
|
||||
Q_memset( m_sndBuffers, 0x0, DeviceSampleCount() * DeviceSampleBytes() );
|
||||
}
|
||||
|
||||
void CAudioDeviceOpenAL::UpdateListener( const Vector& position, const Vector& forward, const Vector& right, const Vector& up )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void CAudioDeviceOpenAL::MixBegin( int sampleCount )
|
||||
{
|
||||
MIX_ClearAllPaintBuffers( sampleCount, false );
|
||||
}
|
||||
|
||||
|
||||
void CAudioDeviceOpenAL::MixUpsample( int sampleCount, int filtertype )
|
||||
{
|
||||
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
|
||||
int ifilter = ppaint->ifilter;
|
||||
|
||||
Assert (ifilter < CPAINTFILTERS);
|
||||
|
||||
S_MixBufferUpsample2x( sampleCount, ppaint->pbuf, &(ppaint->fltmem[ifilter][0]), CPAINTFILTERMEM, filtertype );
|
||||
|
||||
ppaint->ifilter++;
|
||||
}
|
||||
|
||||
void CAudioDeviceOpenAL::Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
|
||||
{
|
||||
int volume[CCHANVOLUMES];
|
||||
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
|
||||
|
||||
if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 1))
|
||||
return;
|
||||
|
||||
Mix8MonoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, (byte *)pData, inputOffset, rateScaleFix, outCount );
|
||||
}
|
||||
|
||||
|
||||
void CAudioDeviceOpenAL::Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
|
||||
{
|
||||
int volume[CCHANVOLUMES];
|
||||
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
|
||||
|
||||
if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 2 ))
|
||||
return;
|
||||
|
||||
Mix8StereoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, (byte *)pData, inputOffset, rateScaleFix, outCount );
|
||||
}
|
||||
|
||||
|
||||
void CAudioDeviceOpenAL::Mix16Mono( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
|
||||
{
|
||||
int volume[CCHANVOLUMES];
|
||||
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
|
||||
|
||||
if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 1 ))
|
||||
return;
|
||||
|
||||
Mix16MonoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, pData, inputOffset, rateScaleFix, outCount );
|
||||
}
|
||||
|
||||
|
||||
void CAudioDeviceOpenAL::Mix16Stereo( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, fixedint rateScaleFix, int outCount, int timecompress )
|
||||
{
|
||||
int volume[CCHANVOLUMES];
|
||||
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
|
||||
|
||||
if (!MIX_ScaleChannelVolume( ppaint, pChannel, volume, 2 ))
|
||||
return;
|
||||
|
||||
Mix16StereoWavtype( pChannel, ppaint->pbuf + outputOffset, volume, pData, inputOffset, rateScaleFix, outCount );
|
||||
}
|
||||
|
||||
|
||||
void CAudioDeviceOpenAL::ChannelReset( int entnum, int channelIndex, float distanceMod )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void CAudioDeviceOpenAL::TransferSamples( int end )
|
||||
{
|
||||
int lpaintedtime = g_paintedtime;
|
||||
int endtime = end;
|
||||
|
||||
// resumes playback...
|
||||
|
||||
if ( m_sndBuffers )
|
||||
{
|
||||
S_TransferStereo16( m_sndBuffers, PAINTBUFFER, lpaintedtime, endtime );
|
||||
}
|
||||
}
|
||||
|
||||
void CAudioDeviceOpenAL::SpatializeChannel( int volume[CCHANVOLUMES/2], int master_vol, const Vector& sourceDir, float gain, float mono )
|
||||
{
|
||||
VPROF("CAudioDeviceOpenAL::SpatializeChannel");
|
||||
S_SpatializeChannel( volume, master_vol, &sourceDir, gain, mono );
|
||||
}
|
||||
|
||||
void CAudioDeviceOpenAL::StopAllSounds( void )
|
||||
{
|
||||
m_bSoundsShutdown = true;
|
||||
alSourceStop( m_Source[0] );
|
||||
}
|
||||
|
||||
|
||||
|
||||
void CAudioDeviceOpenAL::ApplyDSPEffects( int idsp, portable_samplepair_t *pbuffront, portable_samplepair_t *pbufrear, portable_samplepair_t *pbufcenter, int samplecount )
|
||||
{
|
||||
//SX_RoomFX( endtime, filter, timefx );
|
||||
DSP_Process( idsp, pbuffront, pbufrear, pbufcenter, samplecount );
|
||||
}
|
||||
|
||||
|
||||
static uint32 GetOSXSpeakerConfig()
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
static uint32 GetSpeakerConfigForSurroundMode( int surroundMode, const char **pConfigDesc )
|
||||
{
|
||||
uint32 newSpeakerConfig = 2;
|
||||
*pConfigDesc = "stereo speaker";
|
||||
return newSpeakerConfig;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void OnSndSurroundCvarChanged( IConVar *pVar, const char *pOldString, float flOldValue )
|
||||
{
|
||||
// if the old value is -1, we're setting this from the detect routine for the first time
|
||||
// no need to reset the device
|
||||
if ( flOldValue == -1 )
|
||||
return;
|
||||
|
||||
// get the user's previous speaker config
|
||||
uint32 speaker_config = GetOSXSpeakerConfig();
|
||||
|
||||
// get the new config
|
||||
uint32 newSpeakerConfig = 0;
|
||||
const char *speakerConfigDesc = "";
|
||||
|
||||
ConVarRef var( pVar );
|
||||
newSpeakerConfig = GetSpeakerConfigForSurroundMode( var.GetInt(), &speakerConfigDesc );
|
||||
// make sure the config has changed
|
||||
if (newSpeakerConfig == speaker_config)
|
||||
return;
|
||||
|
||||
// set new configuration
|
||||
//SetWindowsSpeakerConfig(newSpeakerConfig);
|
||||
|
||||
Msg("Speaker configuration has been changed to %s.\n", speakerConfigDesc);
|
||||
|
||||
// restart sound system so it takes effect
|
||||
//g_pSoundServices->RestartSoundSystem();
|
||||
}
|
||||
|
||||
void OnSndSurroundLegacyChanged( IConVar *pVar, const char *pOldString, float flOldValue )
|
||||
{
|
||||
}
|
||||
|
||||
#endif // !DEDICATED
|
||||
|
||||
14
engine/audio/private/snd_dev_openal.h
Normal file
14
engine/audio/private/snd_dev_openal.h
Normal 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
|
||||
574
engine/audio/private/snd_dev_sdl.cpp
Normal file
574
engine/audio/private/snd_dev_sdl.cpp
Normal 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
|
||||
|
||||
16
engine/audio/private/snd_dev_sdl.h
Normal file
16
engine/audio/private/snd_dev_sdl.h
Normal 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
|
||||
|
||||
|
||||
570
engine/audio/private/snd_dev_wave.cpp
Normal file
570
engine/audio/private/snd_dev_wave.cpp
Normal 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 );
|
||||
}
|
||||
14
engine/audio/private/snd_dev_wave.h
Normal file
14
engine/audio/private/snd_dev_wave.h
Normal 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
|
||||
872
engine/audio/private/snd_dev_xaudio.cpp
Normal file
872
engine/audio/private/snd_dev_xaudio.cpp
Normal 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] );
|
||||
}
|
||||
}
|
||||
}
|
||||
64
engine/audio/private/snd_dev_xaudio.h
Normal file
64
engine/audio/private/snd_dev_xaudio.h
Normal 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
|
||||
8401
engine/audio/private/snd_dma.cpp
Normal file
8401
engine/audio/private/snd_dma.cpp
Normal file
File diff suppressed because it is too large
Load Diff
19
engine/audio/private/snd_dma.h
Normal file
19
engine/audio/private/snd_dma.h
Normal 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
|
||||
9679
engine/audio/private/snd_dsp.cpp
Normal file
9679
engine/audio/private/snd_dsp.cpp
Normal file
File diff suppressed because it is too large
Load Diff
61
engine/audio/private/snd_env_fx.h
Normal file
61
engine/audio/private/snd_env_fx.h
Normal 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
|
||||
40
engine/audio/private/snd_fixedint.h
Normal file
40
engine/audio/private/snd_fixedint.h
Normal 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
|
||||
4293
engine/audio/private/snd_mix.cpp
Normal file
4293
engine/audio/private/snd_mix.cpp
Normal file
File diff suppressed because it is too large
Load Diff
107
engine/audio/private/snd_mix_buf.h
Normal file
107
engine/audio/private/snd_mix_buf.h
Normal 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
|
||||
602
engine/audio/private/snd_mp3_source.cpp
Normal file
602
engine/audio/private/snd_mp3_source.cpp
Normal 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
|
||||
|
||||
186
engine/audio/private/snd_mp3_source.h
Normal file
186
engine/audio/private/snd_mp3_source.h
Normal 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
|
||||
15
engine/audio/private/snd_posix.cpp
Normal file
15
engine/audio/private/snd_posix.cpp
Normal 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"
|
||||
|
||||
|
||||
369
engine/audio/private/snd_sentence_mixer.cpp
Normal file
369
engine/audio/private/snd_sentence_mixer.cpp
Normal 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;
|
||||
}
|
||||
48
engine/audio/private/snd_sfx.h
Normal file
48
engine/audio/private/snd_sfx.h
Normal 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
|
||||
279
engine/audio/private/snd_stubs.cpp
Normal file
279
engine/audio/private/snd_stubs.cpp
Normal 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
|
||||
45
engine/audio/private/snd_stubs.h
Normal file
45
engine/audio/private/snd_stubs.h
Normal 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
|
||||
2394
engine/audio/private/snd_wave_data.cpp
Normal file
2394
engine/audio/private/snd_wave_data.cpp
Normal file
File diff suppressed because it is too large
Load Diff
45
engine/audio/private/snd_wave_data.h
Normal file
45
engine/audio/private/snd_wave_data.h
Normal 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
|
||||
788
engine/audio/private/snd_wave_mixer.cpp
Normal file
788
engine/audio/private/snd_wave_mixer.cpp
Normal 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;
|
||||
}
|
||||
|
||||
24
engine/audio/private/snd_wave_mixer.h
Normal file
24
engine/audio/private/snd_wave_mixer.h
Normal 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
|
||||
469
engine/audio/private/snd_wave_mixer_adpcm.cpp
Normal file
469
engine/audio/private/snd_wave_mixer_adpcm.cpp
Normal 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 );
|
||||
}
|
||||
24
engine/audio/private/snd_wave_mixer_adpcm.h
Normal file
24
engine/audio/private/snd_wave_mixer_adpcm.h
Normal 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
|
||||
238
engine/audio/private/snd_wave_mixer_mp3.cpp
Normal file
238
engine/audio/private/snd_wave_mixer_mp3.cpp
Normal 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
|
||||
59
engine/audio/private/snd_wave_mixer_mp3.h
Normal file
59
engine/audio/private/snd_wave_mixer_mp3.h
Normal 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
|
||||
74
engine/audio/private/snd_wave_mixer_private.h
Normal file
74
engine/audio/private/snd_wave_mixer_private.h
Normal 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
|
||||
959
engine/audio/private/snd_wave_mixer_xma.cpp
Normal file
959
engine/audio/private/snd_wave_mixer_xma.cpp
Normal 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 );
|
||||
}
|
||||
24
engine/audio/private/snd_wave_mixer_xma.h
Normal file
24
engine/audio/private/snd_wave_mixer_xma.h
Normal 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
|
||||
2816
engine/audio/private/snd_wave_source.cpp
Normal file
2816
engine/audio/private/snd_wave_source.cpp
Normal file
File diff suppressed because it is too large
Load Diff
160
engine/audio/private/snd_wave_source.h
Normal file
160
engine/audio/private/snd_wave_source.h
Normal 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
|
||||
149
engine/audio/private/snd_wave_temp.cpp
Normal file
149
engine/audio/private/snd_wave_temp.cpp
Normal 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 );
|
||||
}
|
||||
18
engine/audio/private/snd_wave_temp.h
Normal file
18
engine/audio/private/snd_wave_temp.h
Normal 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
|
||||
185
engine/audio/private/snd_win.cpp
Normal file
185
engine/audio/private/snd_win.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
128
engine/audio/private/sound_private.h
Normal file
128
engine/audio/private/sound_private.h
Normal 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
|
||||
1566
engine/audio/private/voice.cpp
Normal file
1566
engine/audio/private/voice.cpp
Normal file
File diff suppressed because it is too large
Load Diff
92
engine/audio/private/voice_gain.cpp
Normal file
92
engine/audio/private/voice_gain.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
62
engine/audio/private/voice_gain.h
Normal file
62
engine/audio/private/voice_gain.h
Normal 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
|
||||
503
engine/audio/private/voice_mixer_controls.cpp
Normal file
503
engine/audio/private/voice_mixer_controls.cpp
Normal 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;
|
||||
}
|
||||
|
||||
47
engine/audio/private/voice_mixer_controls.h
Normal file
47
engine/audio/private/voice_mixer_controls.h
Normal 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
|
||||
286
engine/audio/private/voice_mixer_controls_openal.cpp
Normal file
286
engine/audio/private/voice_mixer_controls_openal.cpp
Normal 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
|
||||
|
||||
400
engine/audio/private/voice_record_dsound.cpp
Normal file
400
engine/audio/private/voice_record_dsound.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
528
engine/audio/private/voice_record_mac_audioqueue.cpp
Normal file
528
engine/audio/private/voice_record_mac_audioqueue.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
209
engine/audio/private/voice_record_openal.cpp
Normal file
209
engine/audio/private/voice_record_openal.cpp
Normal 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
|
||||
377
engine/audio/private/voice_sound_engine_interface.cpp
Normal file
377
engine/audio/private/voice_sound_engine_interface.cpp
Normal 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( ©Buf[nSamplesGotten], 0, (sampleCount - nSamplesGotten) * BYTES_PER_SAMPLE );
|
||||
nSamplesGotten = sampleCount;
|
||||
}
|
||||
|
||||
*pData = copyBuf;
|
||||
return nSamplesGotten;
|
||||
}
|
||||
|
||||
int CAudioSourceVoice::SampleRate()
|
||||
{
|
||||
return Voice_SamplesPerSec();
|
||||
}
|
||||
|
||||
int CAudioSourceVoice::SampleSize()
|
||||
{
|
||||
return BYTES_PER_SAMPLE;
|
||||
}
|
||||
|
||||
int CAudioSourceVoice::SampleCount()
|
||||
{
|
||||
return Voice_SamplesPerSec();
|
||||
}
|
||||
|
||||
void CAudioSourceVoice::ReferenceAdd(CAudioMixer *pMixer)
|
||||
{
|
||||
m_refCount++;
|
||||
}
|
||||
|
||||
void CAudioSourceVoice::ReferenceRemove(CAudioMixer *pMixer)
|
||||
{
|
||||
m_refCount--;
|
||||
if ( m_refCount <= 0 )
|
||||
delete this;
|
||||
}
|
||||
|
||||
bool CAudioSourceVoice::CanDelete()
|
||||
{
|
||||
return m_refCount == 0;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- //
|
||||
// Interface implementation.
|
||||
// ----------------------------------------------------------------------------- //
|
||||
|
||||
bool VoiceSE_Init()
|
||||
{
|
||||
if( !snd_initialized )
|
||||
return false;
|
||||
|
||||
g_SND_VoiceOverdriveInt = 256;
|
||||
return true;
|
||||
}
|
||||
|
||||
void VoiceSE_Term()
|
||||
{
|
||||
// Disable voice ducking.
|
||||
g_SND_VoiceOverdriveInt = 256;
|
||||
}
|
||||
|
||||
|
||||
void VoiceSE_Idle(float frametime)
|
||||
{
|
||||
g_SND_VoiceOverdriveInt = 256;
|
||||
|
||||
if( g_bVoiceOverdriveOn )
|
||||
{
|
||||
g_VoiceOverdriveDuration = min( g_VoiceOverdriveDuration+frametime, voice_overdrivefadetime.GetFloat() );
|
||||
}
|
||||
else
|
||||
{
|
||||
if(g_VoiceOverdriveDuration == 0)
|
||||
return;
|
||||
|
||||
g_VoiceOverdriveDuration = max(g_VoiceOverdriveDuration-frametime, 0.f);
|
||||
}
|
||||
|
||||
float percent = g_VoiceOverdriveDuration / voice_overdrivefadetime.GetFloat();
|
||||
percent = (float)(-cos(percent * 3.1415926535) * 0.5 + 0.5); // Smooth it out..
|
||||
float voiceOverdrive = 1 + (voice_overdrive.GetFloat() - 1) * percent;
|
||||
g_SND_VoiceOverdriveInt = (int)(256 / voiceOverdrive);
|
||||
}
|
||||
|
||||
|
||||
int VoiceSE_StartChannel(
|
||||
int iChannel, //! Which channel to start.
|
||||
int iEntity,
|
||||
bool bProximity,
|
||||
int nViewEntityIndex )
|
||||
{
|
||||
Assert( iChannel >= 0 && iChannel < VOICE_NUM_CHANNELS );
|
||||
|
||||
// Start the sound.
|
||||
CSfxTable *sfx = &g_CVoiceSfx[iChannel];
|
||||
sfx->pSource = NULL;
|
||||
Vector vOrigin(0,0,0);
|
||||
|
||||
StartSoundParams_t params;
|
||||
params.staticsound = false;
|
||||
params.entchannel = (CHAN_VOICE_BASE+iChannel);
|
||||
params.pSfx = sfx;
|
||||
params.origin = vOrigin;
|
||||
params.fvol = 1.0f;
|
||||
params.flags = 0;
|
||||
params.pitch = PITCH_NORM;
|
||||
|
||||
|
||||
if ( bProximity == true )
|
||||
{
|
||||
params.bUpdatePositions = true;
|
||||
params.soundlevel = SNDLVL_TALKING;
|
||||
params.soundsource = iEntity;
|
||||
}
|
||||
else
|
||||
{
|
||||
params.soundlevel = SNDLVL_IDLE;
|
||||
params.soundsource = nViewEntityIndex;
|
||||
}
|
||||
|
||||
|
||||
return S_StartSound( params );
|
||||
}
|
||||
|
||||
void VoiceSE_EndChannel(
|
||||
int iChannel, //! Which channel to stop.
|
||||
int iEntity
|
||||
)
|
||||
{
|
||||
Assert( iChannel >= 0 && iChannel < VOICE_NUM_CHANNELS );
|
||||
|
||||
S_StopSound( iEntity, CHAN_VOICE_BASE+iChannel );
|
||||
|
||||
// Start the sound.
|
||||
CSfxTable *sfx = &g_CVoiceSfx[iChannel];
|
||||
sfx->pSource = NULL;
|
||||
}
|
||||
|
||||
void VoiceSE_StartOverdrive()
|
||||
{
|
||||
g_bVoiceOverdriveOn = true;
|
||||
}
|
||||
|
||||
void VoiceSE_EndOverdrive()
|
||||
{
|
||||
g_bVoiceOverdriveOn = false;
|
||||
}
|
||||
|
||||
|
||||
void VoiceSE_InitMouth(int entnum)
|
||||
{
|
||||
}
|
||||
|
||||
void VoiceSE_CloseMouth(int entnum)
|
||||
{
|
||||
}
|
||||
|
||||
void VoiceSE_MoveMouth(int entnum, short *pSamples, int nSamples)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
CAudioSource* Voice_SetupAudioSource( int soundsource, int entchannel )
|
||||
{
|
||||
int iChannel = entchannel - CHAN_VOICE_BASE;
|
||||
if( iChannel >= 0 && iChannel < VOICE_NUM_CHANNELS )
|
||||
{
|
||||
CSfxTable *sfx = &g_CVoiceSfx[iChannel];
|
||||
return new CAudioSourceVoice( sfx, iChannel );
|
||||
}
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
102
engine/audio/private/voice_sound_engine_interface.h
Normal file
102
engine/audio/private/voice_sound_engine_interface.h
Normal 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
|
||||
119
engine/audio/private/voice_wavefile.cpp
Normal file
119
engine/audio/private/voice_wavefile.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
34
engine/audio/private/voice_wavefile.h
Normal file
34
engine/audio/private/voice_wavefile.h
Normal 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
2862
engine/audio/private/vox.cpp
Normal file
File diff suppressed because it is too large
Load Diff
74
engine/audio/private/vox_private.h
Normal file
74
engine/audio/private/vox_private.h
Normal 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
|
||||
61
engine/audio/public/ivoicecodec.h
Normal file
61
engine/audio/public/ivoicecodec.h
Normal 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
|
||||
40
engine/audio/public/ivoicerecord.h
Normal file
40
engine/audio/public/ivoicerecord.h
Normal 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
|
||||
526
engine/audio/public/snd_audio_source.h
Normal file
526
engine/audio/public/snd_audio_source.h
Normal 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
|
||||
127
engine/audio/public/snd_device.h
Normal file
127
engine/audio/public/snd_device.h
Normal 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
|
||||
16
engine/audio/public/snd_io.h
Normal file
16
engine/audio/public/snd_io.h
Normal 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
150
engine/audio/public/sound.h
Normal 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
|
||||
135
engine/audio/public/soundservice.h
Normal file
135
engine/audio/public/soundservice.h
Normal 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
138
engine/audio/public/voice.h
Normal 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
46
engine/audio/public/vox.h
Normal 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
|
||||
Reference in New Issue
Block a user