summaryrefslogtreecommitdiff
path: root/engine/audio
diff options
context:
space:
mode:
Diffstat (limited to 'engine/audio')
-rw-r--r--engine/audio/audio_pch.cpp10
-rw-r--r--engine/audio/audio_pch.h66
-rw-r--r--engine/audio/private/MPAFile.cpp414
-rw-r--r--engine/audio/private/MPAFile.h125
-rw-r--r--engine/audio/private/MPAHeader.cpp332
-rw-r--r--engine/audio/private/MPAHeader.h116
-rw-r--r--engine/audio/private/VBRHeader.cpp304
-rw-r--r--engine/audio/private/VBRHeader.h72
-rw-r--r--engine/audio/private/circularbuffer.cpp271
-rw-r--r--engine/audio/private/circularbuffer.h99
-rw-r--r--engine/audio/private/eax.h124
-rw-r--r--engine/audio/private/posix_stubs.h234
-rw-r--r--engine/audio/private/snd_channels.h213
-rw-r--r--engine/audio/private/snd_convars.h21
-rw-r--r--engine/audio/private/snd_dev_common.cpp623
-rw-r--r--engine/audio/private/snd_dev_common.h59
-rw-r--r--engine/audio/private/snd_dev_direct.cpp2094
-rw-r--r--engine/audio/private/snd_dev_direct.h14
-rw-r--r--engine/audio/private/snd_dev_mac_audioqueue.cpp599
-rw-r--r--engine/audio/private/snd_dev_mac_audioqueue.h14
-rw-r--r--engine/audio/private/snd_dev_openal.cpp611
-rw-r--r--engine/audio/private/snd_dev_openal.h14
-rw-r--r--engine/audio/private/snd_dev_sdl.cpp574
-rw-r--r--engine/audio/private/snd_dev_sdl.h16
-rw-r--r--engine/audio/private/snd_dev_wave.cpp570
-rw-r--r--engine/audio/private/snd_dev_wave.h14
-rw-r--r--engine/audio/private/snd_dev_xaudio.cpp872
-rw-r--r--engine/audio/private/snd_dev_xaudio.h64
-rw-r--r--engine/audio/private/snd_dma.cpp8401
-rw-r--r--engine/audio/private/snd_dma.h19
-rw-r--r--engine/audio/private/snd_dsp.cpp9679
-rw-r--r--engine/audio/private/snd_env_fx.h61
-rw-r--r--engine/audio/private/snd_fixedint.h40
-rw-r--r--engine/audio/private/snd_mix.cpp4293
-rw-r--r--engine/audio/private/snd_mix_buf.h107
-rw-r--r--engine/audio/private/snd_mp3_source.cpp602
-rw-r--r--engine/audio/private/snd_mp3_source.h186
-rw-r--r--engine/audio/private/snd_posix.cpp15
-rw-r--r--engine/audio/private/snd_sentence_mixer.cpp369
-rw-r--r--engine/audio/private/snd_sfx.h48
-rw-r--r--engine/audio/private/snd_stubs.cpp279
-rw-r--r--engine/audio/private/snd_stubs.h45
-rw-r--r--engine/audio/private/snd_wave_data.cpp2394
-rw-r--r--engine/audio/private/snd_wave_data.h45
-rw-r--r--engine/audio/private/snd_wave_mixer.cpp788
-rw-r--r--engine/audio/private/snd_wave_mixer.h24
-rw-r--r--engine/audio/private/snd_wave_mixer_adpcm.cpp469
-rw-r--r--engine/audio/private/snd_wave_mixer_adpcm.h24
-rw-r--r--engine/audio/private/snd_wave_mixer_mp3.cpp238
-rw-r--r--engine/audio/private/snd_wave_mixer_mp3.h59
-rw-r--r--engine/audio/private/snd_wave_mixer_private.h74
-rw-r--r--engine/audio/private/snd_wave_mixer_xma.cpp959
-rw-r--r--engine/audio/private/snd_wave_mixer_xma.h24
-rw-r--r--engine/audio/private/snd_wave_source.cpp2816
-rw-r--r--engine/audio/private/snd_wave_source.h160
-rw-r--r--engine/audio/private/snd_wave_temp.cpp149
-rw-r--r--engine/audio/private/snd_wave_temp.h18
-rw-r--r--engine/audio/private/snd_win.cpp185
-rw-r--r--engine/audio/private/sound_private.h128
-rw-r--r--engine/audio/private/voice.cpp1566
-rw-r--r--engine/audio/private/voice_gain.cpp92
-rw-r--r--engine/audio/private/voice_gain.h62
-rw-r--r--engine/audio/private/voice_mixer_controls.cpp503
-rw-r--r--engine/audio/private/voice_mixer_controls.h47
-rw-r--r--engine/audio/private/voice_mixer_controls_openal.cpp286
-rw-r--r--engine/audio/private/voice_record_dsound.cpp400
-rw-r--r--engine/audio/private/voice_record_mac_audioqueue.cpp528
-rw-r--r--engine/audio/private/voice_record_openal.cpp209
-rw-r--r--engine/audio/private/voice_sound_engine_interface.cpp377
-rw-r--r--engine/audio/private/voice_sound_engine_interface.h102
-rw-r--r--engine/audio/private/voice_wavefile.cpp119
-rw-r--r--engine/audio/private/voice_wavefile.h34
-rw-r--r--engine/audio/private/vox.cpp2862
-rw-r--r--engine/audio/private/vox_private.h74
-rw-r--r--engine/audio/public/ivoicecodec.h61
-rw-r--r--engine/audio/public/ivoicerecord.h40
-rw-r--r--engine/audio/public/snd_audio_source.h526
-rw-r--r--engine/audio/public/snd_device.h127
-rw-r--r--engine/audio/public/snd_io.h16
-rw-r--r--engine/audio/public/sound.h150
-rw-r--r--engine/audio/public/soundservice.h135
-rw-r--r--engine/audio/public/voice.h138
-rw-r--r--engine/audio/public/vox.h46
83 files changed, 49737 insertions, 0 deletions
diff --git a/engine/audio/audio_pch.cpp b/engine/audio/audio_pch.cpp
new file mode 100644
index 0000000..ca79d1f
--- /dev/null
+++ b/engine/audio/audio_pch.cpp
@@ -0,0 +1,10 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $Workfile: $
+// $Date: $
+// $NoKeywords: $
+//=============================================================================//
+
+#include "audio_pch.h"
diff --git a/engine/audio/audio_pch.h b/engine/audio/audio_pch.h
new file mode 100644
index 0000000..177e36a
--- /dev/null
+++ b/engine/audio/audio_pch.h
@@ -0,0 +1,66 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $Workfile: $
+// $Date: $
+// $NoKeywords: $
+//===========================================================================//
+
+#include "platform.h"
+
+#if !defined( _X360 ) && defined( WIN32 )
+#define WIN32_LEAN_AND_MEAN
+#pragma warning(push, 1)
+#pragma warning(disable: 4005)
+#include <windows.h>
+#include <mmsystem.h>
+#pragma warning(pop)
+#include <mmreg.h>
+#endif
+
+#include "basetypes.h"
+#include "commonmacros.h"
+#include "mathlib/mathlib.h"
+#include "tier0/dbg.h"
+#include "tier0/vprof.h"
+#include "tier0/icommandline.h"
+#include "tier1/strtools.h"
+
+#include "tier2/riff.h"
+#include "sound.h"
+#include "Color.h"
+#include "convar.h"
+#include "soundservice.h"
+#include "voice_sound_engine_interface.h"
+#include "soundflags.h"
+#include "filesystem.h"
+#include "../filesystem_engine.h"
+
+#include "snd_device.h"
+#include "sound_private.h"
+#include "snd_mix_buf.h"
+#include "snd_env_fx.h"
+#include "snd_channels.h"
+#include "snd_audio_source.h"
+#include "snd_convars.h"
+#include "snd_dev_common.h"
+#include "snd_dev_direct.h"
+#include "snd_dev_wave.h"
+#include "snd_dev_xaudio.h"
+#include "snd_sfx.h"
+#include "snd_audio_source.h"
+#include "snd_wave_source.h"
+#include "snd_wave_temp.h"
+#include "snd_wave_data.h"
+#include "snd_wave_mixer_private.h"
+#include "snd_wave_mixer_adpcm.h"
+#include "snd_io.h"
+
+#include "snd_wave_mixer_xma.h"
+#if defined( _X360 )
+#include "xbox/xbox_win32stubs.h"
+#include <xhv2.h>
+#elif POSIX
+#include "audio/private/posix_stubs.h"
+#endif
diff --git a/engine/audio/private/MPAFile.cpp b/engine/audio/private/MPAFile.cpp
new file mode 100644
index 0000000..8f9a1b8
--- /dev/null
+++ b/engine/audio/private/MPAFile.cpp
@@ -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;
+}
diff --git a/engine/audio/private/MPAFile.h b/engine/audio/private/MPAFile.h
new file mode 100644
index 0000000..92d15ff
--- /dev/null
+++ b/engine/audio/private/MPAFile.h
@@ -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
diff --git a/engine/audio/private/MPAHeader.cpp b/engine/audio/private/MPAHeader.cpp
new file mode 100644
index 0000000..6a8357c
--- /dev/null
+++ b/engine/audio/private/MPAHeader.cpp
@@ -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
+};
diff --git a/engine/audio/private/MPAHeader.h b/engine/audio/private/MPAHeader.h
new file mode 100644
index 0000000..3810124
--- /dev/null
+++ b/engine/audio/private/MPAHeader.h
@@ -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
diff --git a/engine/audio/private/VBRHeader.cpp b/engine/audio/private/VBRHeader.cpp
new file mode 100644
index 0000000..ce554d4
--- /dev/null
+++ b/engine/audio/private/VBRHeader.cpp
@@ -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;
+}
+
diff --git a/engine/audio/private/VBRHeader.h b/engine/audio/private/VBRHeader.h
new file mode 100644
index 0000000..42395cf
--- /dev/null
+++ b/engine/audio/private/VBRHeader.h
@@ -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
diff --git a/engine/audio/private/circularbuffer.cpp b/engine/audio/private/circularbuffer.cpp
new file mode 100644
index 0000000..8c188e3
--- /dev/null
+++ b/engine/audio/private/circularbuffer.cpp
@@ -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 );
+}
+
diff --git a/engine/audio/private/circularbuffer.h b/engine/audio/private/circularbuffer.h
new file mode 100644
index 0000000..d24f04d
--- /dev/null
+++ b/engine/audio/private/circularbuffer.h
@@ -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 \ No newline at end of file
diff --git a/engine/audio/private/eax.h b/engine/audio/private/eax.h
new file mode 100644
index 0000000..82051e9
--- /dev/null
+++ b/engine/audio/private/eax.h
@@ -0,0 +1,124 @@
+// EAX.H -- DirectSound Environmental Audio Extensions
+
+#ifndef EAX_H
+#define EAX_H
+#pragma once
+
+
+// EAX (listener) reverb property set {4a4e6fc1-c341-11d1-b73a-444553540000}
+DEFINE_GUID(DSPROPSETID_EAX_ReverbProperties,
+ 0x4a4e6fc1,
+ 0xc341,
+ 0x11d1,
+ 0xb7, 0x3a, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00);
+
+typedef enum
+{
+ DSPROPERTY_EAX_ALL, // all reverb properties
+ DSPROPERTY_EAX_ENVIRONMENT, // standard environment no.
+ DSPROPERTY_EAX_VOLUME, // loudness of the reverb
+ DSPROPERTY_EAX_DECAYTIME, // how long the reverb lasts
+ DSPROPERTY_EAX_DAMPING // the high frequencies decay faster
+} DSPROPERTY_EAX_REVERBPROPERTY;
+
+#define EAX_NUM_STANDARD_PROPERTIES (DSPROPERTY_EAX_DAMPING + 1)
+
+// use this structure for get/set all properties...
+typedef struct
+{
+ unsigned long environment; // 0 to EAX_ENVIRONMENT_COUNT-1
+ float fVolume; // 0 to 1
+ float fDecayTime_sec; // seconds, 0.1 to 100
+ float fDamping; // 0 to 1
+} EAX_REVERBPROPERTIES;
+
+
+enum
+{
+ EAX_ENVIRONMENT_GENERIC, // factory default
+ EAX_ENVIRONMENT_PADDEDCELL,
+ EAX_ENVIRONMENT_ROOM, // standard environments
+ EAX_ENVIRONMENT_BATHROOM,
+ EAX_ENVIRONMENT_LIVINGROOM,
+ EAX_ENVIRONMENT_STONEROOM,
+ EAX_ENVIRONMENT_AUDITORIUM,
+ EAX_ENVIRONMENT_CONCERTHALL,
+ EAX_ENVIRONMENT_CAVE,
+ EAX_ENVIRONMENT_ARENA,
+ EAX_ENVIRONMENT_HANGAR,
+ EAX_ENVIRONMENT_CARPETEDHALLWAY,
+ EAX_ENVIRONMENT_HALLWAY,
+ EAX_ENVIRONMENT_STONECORRIDOR,
+ EAX_ENVIRONMENT_ALLEY,
+ EAX_ENVIRONMENT_FOREST,
+ EAX_ENVIRONMENT_CITY,
+ EAX_ENVIRONMENT_MOUNTAINS,
+ EAX_ENVIRONMENT_QUARRY,
+ EAX_ENVIRONMENT_PLAIN,
+ EAX_ENVIRONMENT_PARKINGLOT,
+ EAX_ENVIRONMENT_SEWERPIPE,
+ EAX_ENVIRONMENT_UNDERWATER,
+ EAX_ENVIRONMENT_DRUGGED,
+ EAX_ENVIRONMENT_DIZZY,
+ EAX_ENVIRONMENT_PSYCHOTIC,
+
+ EAX_ENVIRONMENT_COUNT // total number of environments
+};
+
+#define EAX_MAX_ENVIRONMENT (EAX_ENVIRONMENT_COUNT - 1)
+
+// presets
+#define EAX_PRESET_GENERIC EAX_ENVIRONMENT_GENERIC,0.5F,1.493F,0.5F
+#define EAX_PRESET_PADDEDCELL EAX_ENVIRONMENT_PADDEDCELL,0.25F,0.1F,0.0F
+#define EAX_PRESET_ROOM EAX_ENVIRONMENT_ROOM,0.417F,0.4F,0.666F
+#define EAX_PRESET_BATHROOM EAX_ENVIRONMENT_BATHROOM,0.653F,1.499F,0.166F
+#define EAX_PRESET_LIVINGROOM EAX_ENVIRONMENT_LIVINGROOM,0.208F,0.478F,0.0F
+#define EAX_PRESET_STONEROOM EAX_ENVIRONMENT_STONEROOM,0.5F,2.309F,0.888F
+#define EAX_PRESET_AUDITORIUM EAX_ENVIRONMENT_AUDITORIUM,0.403F,4.279F,0.5F
+#define EAX_PRESET_CONCERTHALL EAX_ENVIRONMENT_CONCERTHALL,0.5F,3.961F,0.5F
+#define EAX_PRESET_CAVE EAX_ENVIRONMENT_CAVE,0.5F,2.886F,1.304F
+#define EAX_PRESET_ARENA EAX_ENVIRONMENT_ARENA,0.361F,7.284F,0.332F
+#define EAX_PRESET_HANGAR EAX_ENVIRONMENT_HANGAR,0.5F,10.0F,0.3F
+#define EAX_PRESET_CARPETEDHALLWAY EAX_ENVIRONMENT_CARPETEDHALLWAY,0.153F,0.259F,2.0F
+#define EAX_PRESET_HALLWAY EAX_ENVIRONMENT_HALLWAY,0.361F,1.493F,0.0F
+#define EAX_PRESET_STONECORRIDOR EAX_ENVIRONMENT_STONECORRIDOR,0.444F,2.697F,0.638F
+#define EAX_PRESET_ALLEY EAX_ENVIRONMENT_ALLEY,0.25F,1.752F,0.776F
+#define EAX_PRESET_FOREST EAX_ENVIRONMENT_FOREST,0.111F,3.145F,0.472F
+#define EAX_PRESET_CITY EAX_ENVIRONMENT_CITY,0.111F,2.767F,0.224F
+#define EAX_PRESET_MOUNTAINS EAX_ENVIRONMENT_MOUNTAINS,0.194F,7.841F,0.472F
+#define EAX_PRESET_QUARRY EAX_ENVIRONMENT_QUARRY,1.0F,1.499F,0.5F
+#define EAX_PRESET_PLAIN EAX_ENVIRONMENT_PLAIN,0.097F,2.767F,0.224F
+#define EAX_PRESET_PARKINGLOT EAX_ENVIRONMENT_PARKINGLOT,0.208F,1.652F,1.5F
+#define EAX_PRESET_SEWERPIPE EAX_ENVIRONMENT_SEWERPIPE,0.652F,2.886F,0.25F
+#define EAX_PRESET_UNDERWATER EAX_ENVIRONMENT_UNDERWATER,1.0F,1.499F,0.0F
+#define EAX_PRESET_DRUGGED EAX_ENVIRONMENT_DRUGGED,0.875F,8.392F,1.388F
+#define EAX_PRESET_DIZZY EAX_ENVIRONMENT_DIZZY,0.139F,17.234F,0.666F
+#define EAX_PRESET_PSYCHOTIC EAX_ENVIRONMENT_PSYCHOTIC,0.486F,7.563F,0.806F
+
+
+// EAX buffer reverb property set {4a4e6fc0-c341-11d1-b73a-444553540000}
+DEFINE_GUID(DSPROPSETID_EAXBUFFER_ReverbProperties,
+ 0x4a4e6fc0,
+ 0xc341,
+ 0x11d1,
+ 0xb7, 0x3a, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00);
+
+typedef enum
+{
+ DSPROPERTY_EAXBUFFER_ALL, // all reverb buffer properties
+ DSPROPERTY_EAXBUFFER_REVERBMIX // the wet source amount
+} DSPROPERTY_EAXBUFFER_REVERBPROPERTY;
+
+// use this structure for get/set all properties...
+typedef struct
+{
+ float fMix; // linear factor, 0.0F to 1.0F
+} EAXBUFFER_REVERBPROPERTIES;
+
+#define EAX_REVERBMIX_USEDISTANCE -1.0F // out of normal range
+ // signifies the reverb engine should
+ // calculate it's own reverb mix value
+ // based on distance
+
+#endif // EAX_H
+
diff --git a/engine/audio/private/posix_stubs.h b/engine/audio/private/posix_stubs.h
new file mode 100644
index 0000000..69ba41f
--- /dev/null
+++ b/engine/audio/private/posix_stubs.h
@@ -0,0 +1,234 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Posix win32 replacements - Mocks trivial windows flow
+//
+//=============================================================================
+#ifndef POSIX_AUDIO_STUBS_H
+#define POSIX_AUDIO_STUBS_H
+
+#define DSBCAPS_LOCSOFTWARE 0
+
+#define DSERR_BUFFERLOST 0
+
+#define DSBSTATUS_BUFFERLOST 0x02
+
+#define DSSPEAKER_GEOMETRY(x) (((x)>>16) & 0xFFFF)
+#define DSSPEAKER_CONFIG(x) ((x) & 0xFFFF)
+
+#define DSSPEAKER_HEADPHONE -1
+#define DSSPEAKER_QUAD -2
+#define DSSPEAKER_5POINT1 -3
+#define DSSPEAKER_7POINT1 -4
+
+#define DISP_CHANGE_SUCCESSFUL 0
+
+#define HKEY_CURRENT_USER NULL
+#define HKEY_LOCAL_MACHINE NULL
+#define KEY_QUERY_VALUE 0
+
+#define KEY_READ 0
+#define KEY_WRITE 1
+#define KEY_ALL_ACCESS ((ULONG)-1)
+
+#define SMTO_ABORTIFHUNG 0
+
+#define JOY_RETURNX 0x01
+#define JOY_RETURNY 0x02
+#define JOY_RETURNZ 0x04
+#define JOY_RETURNR 0x08
+#define JOY_RETURNU 0x10
+#define JOY_RETURNV 0x20
+
+#define JOYCAPS_HASPOV 0x01
+#define JOYCAPS_HASU 0x01
+#define JOYCAPS_HASV 0x01
+#define JOYCAPS_HASR 0x01
+#define JOYCAPS_HASZ 0x01
+
+#define MMSYSERR_NODRIVER 1
+#define JOYERR_NOERROR 0
+#define JOY_RETURNCENTERED 0
+#define JOY_RETURNBUTTONS 0
+#define JOY_RETURNPOV 0
+#define JOY_POVCENTERED 0
+#define JOY_POVFORWARD 0
+#define JOY_POVRIGHT 0
+#define JOY_POVBACKWARD 0
+#define JOY_POVLEFT 0
+
+#define CCHDEVICENAME 32
+#define CCHFORMNAME 32
+
+typedef wchar_t BCHAR;
+
+typedef uint MMRESULT;
+typedef uint32 *DWORD_PTR;
+typedef char *LPCSTR;
+typedef uint POINTL;
+
+#define IDLE_PRIORITY_CLASS 1
+#define HIGH_PRIORITY_CLASS 2
+
+typedef struct _devicemode {
+ BCHAR dmDeviceName[CCHDEVICENAME];
+ WORD dmSpecVersion;
+ WORD dmDriverVersion;
+ WORD dmSize;
+ WORD dmDriverExtra;
+ DWORD dmFields;
+ union u1 {
+ struct s {
+ short dmOrientation;
+ short dmPaperSize;
+ short dmPaperLength;
+ short dmPaperWidth;
+ short dmScale;
+ short dmCopies;
+ short dmDefaultSource;
+ short dmPrintQuality;
+ };
+ POINTL dmPosition;
+ DWORD dmDisplayOrientation;
+ DWORD dmDisplayFixedOutput;
+ };
+ short dmColor;
+ short dmDuplex;
+ short dmYResolution;
+ short dmTTOption;
+ short dmCollate;
+ BYTE dmFormName[CCHFORMNAME];
+ WORD dmLogPixels;
+ DWORD dmBitsPerPel;
+ DWORD dmPelsWidth;
+ DWORD dmPelsHeight;
+ union u2 {
+ DWORD dmDisplayFlags;
+ DWORD dmNup;
+ };
+ DWORD dmDisplayFrequency;
+ DWORD dmICMMethod;
+ DWORD dmICMIntent;
+ DWORD dmMediaType;
+ DWORD dmDitherType;
+ DWORD dmReserved1;
+ DWORD dmReserved2;
+ DWORD dmPanningWidth;
+ DWORD dmPanningHeight;
+} DEVMODE, *LPDEVMODE;
+
+typedef uint32 MCIERROR;
+typedef uint MCIDEVICEID;
+
+typedef struct {
+ DWORD_PTR dwCallback;
+} MCI_GENERIC_PARMS;
+
+typedef struct {
+ DWORD_PTR dwCallback;
+ DWORD dwReturn;
+ DWORD dwItem;
+ DWORD dwTrack;
+} MCI_STATUS_PARMS;
+
+typedef struct {
+ DWORD_PTR dwCallback;
+ DWORD dwFrom;
+ DWORD dwTo;
+} MCI_PLAY_PARMS;
+
+typedef struct {
+ DWORD_PTR dwCallback;
+ MCIDEVICEID wDeviceID;
+ LPCSTR lpstrDeviceType;
+ LPCSTR lpstrElementName;
+ LPCSTR lpstrAlias;
+} MCI_OPEN_PARMS;
+
+typedef struct {
+ DWORD_PTR dwCallback;
+ DWORD dwTimeFormat;
+ DWORD dwAudio;
+} MCI_SET_PARMS;
+
+#define MCI_MAKE_TMSF(t, m, s, f) ((DWORD)(((BYTE)(t) | ((WORD)(m) << 8)) | ((DWORD)(BYTE)(s) | ((WORD)(f)<<8)) << 16))
+#define MCI_MSF_MINUTE(msf) ((BYTE)(msf))
+#define MCI_MSF_SECOND(msf) ((BYTE)(((WORD)(msf)) >> 8))
+
+#define MCI_OPEN 0
+#define MCI_OPEN_TYPE 0
+#define MCI_OPEN_SHAREABLE 0
+#define MCI_FORMAT_TMSF 0
+#define MCI_SET_TIME_FORMAT 0
+#define MCI_CLOSE 0
+#define MCI_STOP 0
+#define MCI_PAUSE 0
+#define MCI_PLAY 0
+#define MCI_SET 0
+#define MCI_SET_DOOR_OPEN 0
+#define MCI_SET_DOOR_CLOSED 0
+#define MCI_STATUS_READY 0
+#define MCI_STATUS 0
+#define MCI_STATUS_ITEM 0
+#define MCI_STATUS_WAIT 0
+#define MCI_STATUS_NUMBER_OF_TRACKS 0
+#define MCI_CDA_STATUS_TYPE_TRACK 0
+#define MCI_TRACK 0
+#define MCI_WAIT 0
+#define MCI_CDA_TRACK_AUDIO 0
+#define MCI_STATUS_LENGTH 0
+#define MCI_NOTIFY 0
+#define MCI_FROM 0
+#define MCI_TO 0
+#define MCIERR_DRIVER -1
+
+#define DSERR_ALLOCATED 0
+
+#pragma pack(push, 1)
+typedef struct tWAVEFORMATEX
+{
+ WORD wFormatTag;
+ WORD nChannels;
+ DWORD nSamplesPerSec;
+ DWORD nAvgBytesPerSec;
+ WORD nBlockAlign;
+ WORD wBitsPerSample;
+ WORD cbSize;
+} WAVEFORMATEX, *PWAVEFORMATEX, *NPWAVEFORMATEX, *LPWAVEFORMATEX;
+
+typedef const WAVEFORMATEX *LPCWAVEFORMATEX;
+
+
+typedef struct waveformat_tag
+{
+ WORD wFormatTag;
+ WORD nChannels;
+ DWORD nSamplesPerSec;
+ DWORD nAvgBytesPerSec;
+ WORD nBlockAlign;
+} WAVEFORMAT, *PWAVEFORMAT, *NPWAVEFORMAT, *LPWAVEFORMAT;
+
+typedef const WAVEFORMAT *LPCWAVEFORMAT;
+
+typedef struct pcmwaveformat_tag
+{
+ WAVEFORMAT wf;
+ WORD wBitsPerSample;
+} PCMWAVEFORMAT, *PPCMWAVEFORMAT, *NPPCMWAVEFORMAT, *LPPCMWAVEFORMAT;
+
+typedef const PCMWAVEFORMAT *LPCPCMWAVEFORMAT;
+
+typedef struct adpcmcoef_tag {
+ short iCoef1;
+ short iCoef2;
+} ADPCMCOEFSET;
+
+typedef struct adpcmwaveformat_tag {
+ WAVEFORMATEX wfx;
+ WORD wSamplesPerBlock;
+ WORD wNumCoef;
+ ADPCMCOEFSET aCoef[1];
+} ADPCMWAVEFORMAT;
+
+#pragma pack(pop)
+#endif
+
diff --git a/engine/audio/private/snd_channels.h b/engine/audio/private/snd_channels.h
new file mode 100644
index 0000000..06fd065
--- /dev/null
+++ b/engine/audio/private/snd_channels.h
@@ -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
diff --git a/engine/audio/private/snd_convars.h b/engine/audio/private/snd_convars.h
new file mode 100644
index 0000000..9e7c7b6
--- /dev/null
+++ b/engine/audio/private/snd_convars.h
@@ -0,0 +1,21 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef SND_CONVARS_H
+#define SND_CONVARS_H
+
+#if defined( _WIN32 )
+#pragma once
+#endif
+
+#include "convar.h"
+
+extern ConVar snd_legacy_surround;
+extern ConVar snd_surround;
+extern ConVar snd_mix_async;
+
+#endif // SND_CONVARS_H
diff --git a/engine/audio/private/snd_dev_common.cpp b/engine/audio/private/snd_dev_common.cpp
new file mode 100644
index 0000000..bbfe2f1
--- /dev/null
+++ b/engine/audio/private/snd_dev_common.cpp
@@ -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;
+} \ No newline at end of file
diff --git a/engine/audio/private/snd_dev_common.h b/engine/audio/private/snd_dev_common.h
new file mode 100644
index 0000000..45c2ee3
--- /dev/null
+++ b/engine/audio/private/snd_dev_common.h
@@ -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
diff --git a/engine/audio/private/snd_dev_direct.cpp b/engine/audio/private/snd_dev_direct.cpp
new file mode 100644
index 0000000..9149964
--- /dev/null
+++ b/engine/audio/private/snd_dev_direct.cpp
@@ -0,0 +1,2094 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=====================================================================================//
+
+#include "audio_pch.h"
+#include <dsound.h>
+#pragma warning(disable : 4201) // nameless struct/union
+#include <ks.h>
+// Fix for VS 2010 build errors copied from Dota
+#if !defined( NEW_DXSDK ) && ( _MSC_VER >= 1600 )
+#undef KSDATAFORMAT_SUBTYPE_WAVEFORMATEX
+#undef KSDATAFORMAT_SUBTYPE_PCM
+#undef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
+#endif
+#include <ksmedia.h>
+#include "iprediction.h"
+#include "eax.h"
+#include "tier0/icommandline.h"
+#include "video//ivideoservices.h"
+#include "../../sys_dll.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+extern bool snd_firsttime;
+
+extern void DEBUG_StartSoundMeasure(int type, int samplecount );
+extern void DEBUG_StopSoundMeasure(int type, int samplecount );
+
+// legacy support
+extern ConVar sxroom_off;
+extern ConVar sxroom_type;
+extern ConVar sxroomwater_type;
+extern float sxroom_typeprev;
+
+extern HWND* pmainwindow;
+
+typedef enum {SIS_SUCCESS, SIS_FAILURE, SIS_NOTAVAIL} sndinitstat;
+
+#define SECONDARY_BUFFER_SIZE 0x10000 // output buffer size in bytes
+#define SECONDARY_BUFFER_SIZE_SURROUND 0x04000 // output buffer size in bytes, one per channel
+
+#if !defined( NEW_DXSDK )
+// hack - need to include latest dsound.h
+#undef DSSPEAKER_5POINT1
+#undef DSSPEAKER_7POINT1
+#define DSSPEAKER_5POINT1 6
+#define DSSPEAKER_7POINT1 7
+#define DSSPEAKER_7POINT1_SURROUND 8
+#define DSSPEAKER_5POINT1_SURROUND 9
+#endif
+
+HRESULT (WINAPI *pDirectSoundCreate)(GUID FAR *lpGUID, LPDIRECTSOUND FAR *lplpDS, IUnknown FAR *pUnkOuter);
+
+extern void ReleaseSurround(void);
+extern bool MIX_ScaleChannelVolume( paintbuffer_t *ppaint, channel_t *pChannel, int volume[CCHANVOLUMES], int mixchans );
+void OnSndSurroundCvarChanged( IConVar *var, const char *pOldString, float flOldValue );
+void OnSndSurroundLegacyChanged( IConVar *var, const char *pOldString, float flOldValue );
+void OnSndVarChanged( IConVar *var, const char *pOldString, float flOldValue );
+
+static LPDIRECTSOUND pDS = NULL;
+static LPDIRECTSOUNDBUFFER pDSBuf = NULL, pDSPBuf = NULL;
+
+static GUID IID_IDirectSound3DBufferDef = {0x279AFA86, 0x4981, 0x11CE, {0xA5, 0x21, 0x00, 0x20, 0xAF, 0x0B, 0xE5, 0x60}};
+static ConVar windows_speaker_config("windows_speaker_config", "-1", FCVAR_ARCHIVE);
+static DWORD g_ForcedSpeakerConfig = 0;
+
+extern ConVar snd_mute_losefocus;
+
+//-----------------------------------------------------------------------------
+// Purpose: Implementation of direct sound
+//-----------------------------------------------------------------------------
+class CAudioDirectSound : public CAudioDeviceBase
+{
+public:
+ ~CAudioDirectSound( void );
+ bool IsActive( void ) { return true; }
+ bool Init( void );
+ void Shutdown( void );
+ void Pause( void );
+ void UnPause( void );
+ float MixDryVolume( void );
+ bool Should3DMix( void );
+ void StopAllSounds( void );
+
+ int PaintBegin( float mixAheadTime, int soundtime, int paintedtime );
+ void PaintEnd( void );
+
+ int GetOutputPosition( void );
+ void ClearBuffer( void );
+ void UpdateListener( const Vector& position, const Vector& forward, const Vector& right, const Vector& up );
+
+ void ChannelReset( int entnum, int channelIndex, float distanceMod );
+ 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; }
+
+ bool IsInterleaved() { return m_isInterleaved; }
+
+ // Singleton object
+ static CAudioDirectSound *m_pSingleton;
+
+private:
+ void DetectWindowsSpeakerSetup();
+ bool LockDSBuffer( LPDIRECTSOUNDBUFFER pBuffer, DWORD **pdwWriteBuffer, DWORD *pdwSizeBuffer, const char *pBufferName, int lockFlags = 0 );
+ bool IsUsingBufferPerSpeaker();
+
+ sndinitstat SNDDMA_InitDirect( void );
+ bool SNDDMA_InitInterleaved( LPDIRECTSOUND lpDS, WAVEFORMATEX* lpFormat, int channelCount );
+ bool SNDDMA_InitSurround(LPDIRECTSOUND lpDS, WAVEFORMATEX* lpFormat, DSBCAPS* lpdsbc, int cchan);
+ void S_TransferSurround16( portable_samplepair_t *pfront, portable_samplepair_t *prear, portable_samplepair_t *pcenter, int lpaintedtime, int endtime, int cchan);
+ void S_TransferSurround16Interleaved( const portable_samplepair_t *pfront, const portable_samplepair_t *prear, const portable_samplepair_t *pcenter, int lpaintedtime, int endtime);
+ void S_TransferSurround16Interleaved_FullLock( const portable_samplepair_t *pfront, const portable_samplepair_t *prear, const portable_samplepair_t *pcenter, int lpaintedtime, int endtime);
+
+ 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_bufferSizeBytes; // size of a single hardware output buffer, in bytes
+
+ DWORD m_outputBufferStartOffset; // output buffer playback starting byte offset
+ HINSTANCE m_hInstDS;
+ bool m_isInterleaved;
+};
+
+CAudioDirectSound *CAudioDirectSound::m_pSingleton = NULL;
+
+LPDIRECTSOUNDBUFFER pDSBufFL = NULL;
+LPDIRECTSOUNDBUFFER pDSBufFR = NULL;
+LPDIRECTSOUNDBUFFER pDSBufRL = NULL;
+LPDIRECTSOUNDBUFFER pDSBufRR = NULL;
+LPDIRECTSOUNDBUFFER pDSBufFC = NULL;
+LPDIRECTSOUND3DBUFFER pDSBuf3DFL = NULL;
+LPDIRECTSOUND3DBUFFER pDSBuf3DFR = NULL;
+LPDIRECTSOUND3DBUFFER pDSBuf3DRL = NULL;
+LPDIRECTSOUND3DBUFFER pDSBuf3DRR = NULL;
+LPDIRECTSOUND3DBUFFER pDSBuf3DFC = NULL;
+
+// ----------------------------------------------------------------------------- //
+// Helpers.
+// ----------------------------------------------------------------------------- //
+
+
+CAudioDirectSound::~CAudioDirectSound( void )
+{
+ m_pSingleton = NULL;
+}
+
+bool CAudioDirectSound::Init( void )
+{
+ m_hInstDS = NULL;
+
+ static bool first = true;
+ if ( first )
+ {
+ snd_surround.InstallChangeCallback( &OnSndSurroundCvarChanged );
+ snd_legacy_surround.InstallChangeCallback( &OnSndSurroundLegacyChanged );
+ snd_mute_losefocus.InstallChangeCallback( &OnSndVarChanged );
+ first = false;
+ }
+
+ if ( SNDDMA_InitDirect() == SIS_SUCCESS)
+ {
+ if ( g_pVideo != NULL )
+ {
+ g_pVideo->SoundDeviceCommand( VideoSoundDeviceOperation::SET_DIRECT_SOUND_DEVICE, pDS );
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+void CAudioDirectSound::Shutdown( void )
+{
+ ReleaseSurround();
+
+ if (pDSBuf)
+ {
+ pDSBuf->Stop();
+ pDSBuf->Release();
+ }
+
+ // only release primary buffer if it's not also the mixing buffer we just released
+ if (pDSPBuf && (pDSBuf != pDSPBuf))
+ {
+ pDSPBuf->Release();
+ }
+
+ if (pDS)
+ {
+ pDS->SetCooperativeLevel(*pmainwindow, DSSCL_NORMAL);
+ pDS->Release();
+ }
+
+ pDS = NULL;
+ pDSBuf = NULL;
+ pDSPBuf = NULL;
+
+ if ( m_hInstDS )
+ {
+ FreeLibrary( m_hInstDS );
+ m_hInstDS = NULL;
+ }
+
+ if ( this == CAudioDirectSound::m_pSingleton )
+ {
+ CAudioDirectSound::m_pSingleton = NULL;
+ }
+}
+
+// Total number of samples that have played out to hardware
+// for current output buffer (ie: from buffer offset start).
+// return playback position within output playback buffer:
+// the output units are dependant on the device channels
+// so the ouput units for a 2 channel device are as 16 bit LR pairs
+// and the output unit for a 1 channel device are as 16 bit mono samples.
+// take into account the original start position within the buffer, and
+// calculate difference between current position (with buffer wrap) and
+// start position.
+int CAudioDirectSound::GetOutputPosition( void )
+{
+ int samp16;
+ int start, current;
+ DWORD dwCurrent;
+
+ // get size in bytes of output buffer
+ const int size_bytes = m_bufferSizeBytes;
+ if ( IsUsingBufferPerSpeaker() )
+ {
+ // mono output buffers
+ // get byte offset of playback cursor in Front Left output buffer
+ pDSBufFL->GetCurrentPosition(&dwCurrent, NULL);
+
+ start = (int) m_outputBufferStartOffset;
+ current = (int) dwCurrent;
+ }
+ else
+ {
+ // multi-channel interleavd output buffer
+ // get byte offset of playback cursor in output buffer
+ pDSBuf->GetCurrentPosition(&dwCurrent, NULL);
+
+ start = (int) m_outputBufferStartOffset;
+ current = (int) dwCurrent;
+ }
+
+ // get 16 bit samples played, relative to buffer starting offset
+ if (current > start)
+ {
+ // get difference & convert to 16 bit mono samples
+ samp16 = (current - start) >> SAMPLE_16BIT_SHIFT;
+ }
+ else
+ {
+ // get difference (with buffer wrap) convert to 16 bit mono samples
+ samp16 = ((size_bytes - start) + current) >> SAMPLE_16BIT_SHIFT;
+ }
+
+ int outputPosition = samp16 / DeviceChannels();
+
+ return outputPosition;
+}
+
+void CAudioDirectSound::Pause( void )
+{
+ if (pDSBuf)
+ {
+ pDSBuf->Stop();
+ }
+
+ if ( pDSBufFL ) pDSBufFL->Stop();
+ if ( pDSBufFR ) pDSBufFR->Stop();
+ if ( pDSBufRL ) pDSBufRL->Stop();
+ if ( pDSBufRR ) pDSBufRR->Stop();
+ if ( pDSBufFC ) pDSBufFC->Stop();
+}
+
+
+void CAudioDirectSound::UnPause( void )
+{
+ if (pDSBuf)
+ pDSBuf->Play(0, 0, DSBPLAY_LOOPING);
+
+ if (pDSBufFL) pDSBufFL->Play(0, 0, DSBPLAY_LOOPING);
+ if (pDSBufFR) pDSBufFR->Play(0, 0, DSBPLAY_LOOPING);
+ if (pDSBufRL) pDSBufRL->Play(0, 0, DSBPLAY_LOOPING);
+ if (pDSBufRR) pDSBufRR->Play( 0, 0, DSBPLAY_LOOPING);
+ if (pDSBufFC) pDSBufFC->Play( 0, 0, DSBPLAY_LOOPING);
+}
+
+
+float CAudioDirectSound::MixDryVolume( void )
+{
+ return 0;
+}
+
+
+bool CAudioDirectSound::Should3DMix( void )
+{
+ if ( m_bSurround )
+ return true;
+ return false;
+}
+
+
+IAudioDevice *Audio_CreateDirectSoundDevice( void )
+{
+ if ( !CAudioDirectSound::m_pSingleton )
+ CAudioDirectSound::m_pSingleton = new CAudioDirectSound;
+
+ if ( CAudioDirectSound::m_pSingleton->Init() )
+ {
+ if (snd_firsttime)
+ DevMsg ("DirectSound initialized\n");
+
+ return CAudioDirectSound::m_pSingleton;
+ }
+
+ DevMsg ("DirectSound failed to init\n");
+
+ delete CAudioDirectSound::m_pSingleton;
+ CAudioDirectSound::m_pSingleton = NULL;
+
+ return NULL;
+}
+
+int CAudioDirectSound::PaintBegin( float mixAheadTime, int soundtime, int lpaintedtime )
+{
+ // 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
+ // samps - size of output buffer in full samples
+
+ int mixaheadtime = mixAheadTime * DeviceDmaSpeed();
+ int endtime = soundtime + mixaheadtime;
+
+ if ( endtime <= lpaintedtime )
+ return endtime;
+
+ uint nSamples = endtime - lpaintedtime;
+ if ( nSamples & 0x3 )
+ {
+ // The difference between endtime and painted time should align on
+ // boundaries of 4 samples. This is important when upsampling from 11khz -> 44khz.
+ nSamples += (4 - (nSamples & 3));
+ }
+ // clamp to min 512 samples per mix
+ if ( nSamples > 0 && nSamples < 512 )
+ {
+ nSamples = 512;
+ }
+ endtime = lpaintedtime + nSamples;
+
+ int fullsamps = DeviceSampleCount() / DeviceChannels();
+ if ( (endtime - soundtime) > fullsamps)
+ {
+ endtime = soundtime + fullsamps;
+ endtime += (4 - (endtime & 3));
+ }
+
+ DWORD dwStatus;
+
+ // If using surround, there are 4 or 5 different buffers being used and the pDSBuf is NULL.
+ if ( IsUsingBufferPerSpeaker() )
+ {
+ if (pDSBufFL->GetStatus(&dwStatus) != DS_OK)
+ Msg ("Couldn't get SURROUND FL sound buffer status\n");
+
+ if (dwStatus & DSBSTATUS_BUFFERLOST)
+ pDSBufFL->Restore();
+
+ if (!(dwStatus & DSBSTATUS_PLAYING))
+ pDSBufFL->Play(0, 0, DSBPLAY_LOOPING);
+
+ if (pDSBufFR->GetStatus(&dwStatus) != DS_OK)
+ Msg ("Couldn't get SURROUND FR sound buffer status\n");
+
+ if (dwStatus & DSBSTATUS_BUFFERLOST)
+ pDSBufFR->Restore();
+
+ if (!(dwStatus & DSBSTATUS_PLAYING))
+ pDSBufFR->Play(0, 0, DSBPLAY_LOOPING);
+
+ if (pDSBufRL->GetStatus(&dwStatus) != DS_OK)
+ Msg ("Couldn't get SURROUND RL sound buffer status\n");
+
+ if (dwStatus & DSBSTATUS_BUFFERLOST)
+ pDSBufRL->Restore();
+
+ if (!(dwStatus & DSBSTATUS_PLAYING))
+ pDSBufRL->Play(0, 0, DSBPLAY_LOOPING);
+
+ if (pDSBufRR->GetStatus(&dwStatus) != DS_OK)
+ Msg ("Couldn't get SURROUND RR sound buffer status\n");
+
+ if (dwStatus & DSBSTATUS_BUFFERLOST)
+ pDSBufRR->Restore();
+
+ if (!(dwStatus & DSBSTATUS_PLAYING))
+ pDSBufRR->Play(0, 0, DSBPLAY_LOOPING);
+
+ if ( m_bSurroundCenter )
+ {
+ if (pDSBufFC->GetStatus(&dwStatus) != DS_OK)
+ Msg ("Couldn't get SURROUND FC sound buffer status\n");
+
+ if (dwStatus & DSBSTATUS_BUFFERLOST)
+ pDSBufFC->Restore();
+
+ if (!(dwStatus & DSBSTATUS_PLAYING))
+ pDSBufFC->Play(0, 0, DSBPLAY_LOOPING);
+ }
+ }
+ else if (pDSBuf)
+ {
+ if ( pDSBuf->GetStatus (&dwStatus) != DS_OK )
+ Msg("Couldn't get sound buffer status\n");
+
+ if ( dwStatus & DSBSTATUS_BUFFERLOST )
+ pDSBuf->Restore();
+
+ if ( !(dwStatus & DSBSTATUS_PLAYING) )
+ pDSBuf->Play(0, 0, DSBPLAY_LOOPING);
+ }
+
+ return endtime;
+}
+
+
+void CAudioDirectSound::PaintEnd( void )
+{
+}
+
+
+void CAudioDirectSound::ClearBuffer( void )
+{
+ int clear;
+
+ DWORD dwSizeFL, dwSizeFR, dwSizeRL, dwSizeRR, dwSizeFC;
+ char *pDataFL, *pDataFR, *pDataRL, *pDataRR, *pDataFC;
+
+ dwSizeFC = 0; // compiler warning
+ pDataFC = NULL;
+
+ if ( IsUsingBufferPerSpeaker() )
+ {
+ int SURROUNDreps;
+ HRESULT SURROUNDhresult;
+ SURROUNDreps = 0;
+
+ if ( !pDSBufFL && !pDSBufFR && !pDSBufRL && !pDSBufRR && !pDSBufFC )
+ return;
+
+ while ((SURROUNDhresult = pDSBufFL->Lock(0, m_bufferSizeBytes, (void**)&pDataFL, &dwSizeFL, NULL, NULL, 0)) != DS_OK)
+ {
+ if (SURROUNDhresult != DSERR_BUFFERLOST)
+ {
+ Msg ("S_ClearBuffer: DS::Lock FL Sound Buffer Failed\n");
+ S_Shutdown ();
+ return;
+ }
+
+ if (++SURROUNDreps > 10000)
+ {
+ Msg ("S_ClearBuffer: DS: couldn't restore FL buffer\n");
+ S_Shutdown ();
+ return;
+ }
+ }
+
+ SURROUNDreps = 0;
+ while ((SURROUNDhresult = pDSBufFR->Lock(0, m_bufferSizeBytes, (void**)&pDataFR, &dwSizeFR, NULL, NULL, 0)) != DS_OK)
+ {
+ if (SURROUNDhresult != DSERR_BUFFERLOST)
+ {
+ Msg ("S_ClearBuffer: DS::Lock FR Sound Buffer Failed\n");
+ S_Shutdown ();
+ return;
+ }
+
+ if (++SURROUNDreps > 10000)
+ {
+ Msg ("S_ClearBuffer: DS: couldn't restore FR buffer\n");
+ S_Shutdown ();
+ return;
+ }
+ }
+
+ SURROUNDreps = 0;
+ while ((SURROUNDhresult = pDSBufRL->Lock(0, m_bufferSizeBytes, (void**)&pDataRL, &dwSizeRL, NULL, NULL, 0)) != DS_OK)
+ {
+ if (SURROUNDhresult != DSERR_BUFFERLOST)
+ {
+ Msg ("S_ClearBuffer: DS::Lock RL Sound Buffer Failed\n");
+ S_Shutdown ();
+ return;
+ }
+
+ if (++SURROUNDreps > 10000)
+ {
+ Msg ("S_ClearBuffer: DS: couldn't restore RL buffer\n");
+ S_Shutdown ();
+ return;
+ }
+ }
+
+ SURROUNDreps = 0;
+ while ((SURROUNDhresult = pDSBufRR->Lock(0, m_bufferSizeBytes, (void**)&pDataRR, &dwSizeRR, NULL, NULL, 0)) != DS_OK)
+ {
+ if (SURROUNDhresult != DSERR_BUFFERLOST)
+ {
+ Msg ("S_ClearBuffer: DS::Lock RR Sound Buffer Failed\n");
+ S_Shutdown ();
+ return;
+ }
+
+ if (++SURROUNDreps > 10000)
+ {
+ Msg ("S_ClearBuffer: DS: couldn't restore RR buffer\n");
+ S_Shutdown ();
+ return;
+ }
+ }
+
+ if (m_bSurroundCenter)
+ {
+ SURROUNDreps = 0;
+ while ((SURROUNDhresult = pDSBufFC->Lock(0, m_bufferSizeBytes, (void**)&pDataFC, &dwSizeFC, NULL, NULL, 0)) != DS_OK)
+ {
+ if (SURROUNDhresult != DSERR_BUFFERLOST)
+ {
+ Msg ("S_ClearBuffer: DS::Lock FC Sound Buffer Failed\n");
+ S_Shutdown ();
+ return;
+ }
+
+ if (++SURROUNDreps > 10000)
+ {
+ Msg ("S_ClearBuffer: DS: couldn't restore FC buffer\n");
+ S_Shutdown ();
+ return;
+ }
+ }
+ }
+
+ Q_memset(pDataFL, 0, m_bufferSizeBytes);
+ Q_memset(pDataFR, 0, m_bufferSizeBytes);
+ Q_memset(pDataRL, 0, m_bufferSizeBytes);
+ Q_memset(pDataRR, 0, m_bufferSizeBytes);
+
+ if (m_bSurroundCenter)
+ Q_memset(pDataFC, 0, m_bufferSizeBytes);
+
+ pDSBufFL->Unlock(pDataFL, dwSizeFL, NULL, 0);
+ pDSBufFR->Unlock(pDataFR, dwSizeFR, NULL, 0);
+ pDSBufRL->Unlock(pDataRL, dwSizeRL, NULL, 0);
+ pDSBufRR->Unlock(pDataRR, dwSizeRR, NULL, 0);
+
+ if (m_bSurroundCenter)
+ pDSBufFC->Unlock(pDataFC, dwSizeFC, NULL, 0);
+
+ return;
+ }
+
+ if ( !pDSBuf )
+ return;
+
+ if ( DeviceSampleBits() == 8 )
+ clear = 0x80;
+ else
+ clear = 0;
+
+ if (pDSBuf)
+ {
+ DWORD dwSize;
+ DWORD *pData;
+ int reps;
+ HRESULT hresult;
+
+ reps = 0;
+ while ((hresult = pDSBuf->Lock(0, m_bufferSizeBytes, (void**)&pData, &dwSize, NULL, NULL, 0)) != DS_OK)
+ {
+ if (hresult != DSERR_BUFFERLOST)
+ {
+ Msg("S_ClearBuffer: DS::Lock Sound Buffer Failed\n");
+ S_Shutdown();
+ return;
+ }
+
+ if (++reps > 10000)
+ {
+ Msg("S_ClearBuffer: DS: couldn't restore buffer\n");
+ S_Shutdown();
+ return;
+ }
+ }
+
+ Q_memset(pData, clear, dwSize);
+
+ pDSBuf->Unlock(pData, dwSize, NULL, 0);
+ }
+}
+
+void CAudioDirectSound::StopAllSounds( void )
+{
+}
+
+bool CAudioDirectSound::SNDDMA_InitInterleaved( LPDIRECTSOUND lpDS, WAVEFORMATEX* lpFormat, int channelCount )
+{
+ WAVEFORMATEXTENSIBLE wfx = { 0 } ; // DirectSoundBuffer wave format (extensible)
+
+ // set the channel mask and number of channels based on the command line parameter
+ if(channelCount == 2)
+ {
+ wfx.Format.nChannels = 2;
+ wfx.dwChannelMask = KSAUDIO_SPEAKER_STEREO; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
+ }
+ else if(channelCount == 4)
+ {
+ wfx.Format.nChannels = 4;
+ wfx.dwChannelMask = KSAUDIO_SPEAKER_QUAD; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
+ }
+ else if(channelCount == 6)
+ {
+ wfx.Format.nChannels = 6;
+ wfx.dwChannelMask = KSAUDIO_SPEAKER_5POINT1; // SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
+ }
+ else
+ {
+ return false;
+ }
+
+ // setup the extensible structure
+ wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ //wfx.Format.nChannels = SET ABOVE
+ wfx.Format.nSamplesPerSec = lpFormat->nSamplesPerSec;
+ wfx.Format.wBitsPerSample = lpFormat->wBitsPerSample;
+ wfx.Format.nBlockAlign = wfx.Format.wBitsPerSample / 8 * wfx.Format.nChannels;
+ wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
+ wfx.Format.cbSize = 22; // size from after this to end of extensible struct. sizeof(WORD + DWORD + GUID)
+ wfx.Samples.wValidBitsPerSample = lpFormat->wBitsPerSample;
+ //wfx.dwChannelMask = SET ABOVE BASED ON COMMAND LINE PARAMETERS
+ wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+
+ // setup the DirectSound
+ DSBUFFERDESC dsbdesc = { 0 }; // DirectSoundBuffer descriptor
+ dsbdesc.dwSize = sizeof(DSBUFFERDESC);
+ dsbdesc.dwFlags = 0;
+
+ dsbdesc.dwBufferBytes = SECONDARY_BUFFER_SIZE_SURROUND * channelCount;
+
+ dsbdesc.lpwfxFormat = (WAVEFORMATEX*)&wfx;
+ bool bSuccess = false;
+ for ( int i = 0; i < 3; i++ )
+ {
+ switch(i)
+ {
+ case 0:
+ dsbdesc.dwFlags = DSBCAPS_LOCHARDWARE;
+ break;
+ case 1:
+ dsbdesc.dwFlags = DSBCAPS_LOCSOFTWARE;
+ break;
+ case 2:
+ dsbdesc.dwFlags = 0;
+ break;
+ }
+ if ( !snd_mute_losefocus.GetBool() )
+ {
+ dsbdesc.dwFlags |= DSBCAPS_GLOBALFOCUS;
+ }
+
+ if(!FAILED(lpDS->CreateSoundBuffer(&dsbdesc, &pDSBuf, NULL)))
+ {
+ bSuccess = true;
+ break;
+ }
+ }
+ if ( !bSuccess )
+ return false;
+
+ DWORD dwSize = 0, dwWrite;
+ DWORD *pBuffer = 0;
+ if ( !LockDSBuffer( pDSBuf, &pBuffer, &dwSize, "DS_INTERLEAVED", DSBLOCK_ENTIREBUFFER ) )
+ return false;
+
+ m_deviceChannels = wfx.Format.nChannels;
+ m_deviceSampleBits = wfx.Format.wBitsPerSample;
+ m_deviceDmaSpeed = wfx.Format.nSamplesPerSec;
+ m_bufferSizeBytes = dsbdesc.dwBufferBytes;
+ m_isInterleaved = true;
+
+ Q_memset( pBuffer, 0, dwSize );
+
+ pDSBuf->Unlock(pBuffer, dwSize, NULL, 0);
+
+ // Make sure mixer is active (this was moved after the zeroing to avoid popping on startup -- at least when using the dx9.0b debug .dlls)
+ pDSBuf->Play(0, 0, DSBPLAY_LOOPING);
+
+ pDSBuf->Stop();
+ pDSBuf->GetCurrentPosition(&m_outputBufferStartOffset, &dwWrite);
+
+ pDSBuf->Play(0, 0, DSBPLAY_LOOPING);
+
+ return true;
+}
+
+/*
+==================
+SNDDMA_InitDirect
+
+Direct-Sound support
+==================
+*/
+sndinitstat CAudioDirectSound::SNDDMA_InitDirect( void )
+{
+ DSBUFFERDESC dsbuf;
+ DSBCAPS dsbcaps;
+ DWORD dwSize, dwWrite;
+ WAVEFORMATEX format;
+ WAVEFORMATEX pformat;
+ HRESULT hresult;
+ void *lpData = NULL;
+ bool primary_format_set = false;
+ int pri_channels = 2;
+
+ if (!m_hInstDS)
+ {
+ m_hInstDS = LoadLibrary("dsound.dll");
+ if (m_hInstDS == NULL)
+ {
+ Warning( "Couldn't load dsound.dll\n");
+ return SIS_FAILURE;
+ }
+
+ pDirectSoundCreate = (long (__stdcall *)(struct _GUID *,struct IDirectSound ** ,struct IUnknown *))GetProcAddress(m_hInstDS,"DirectSoundCreate");
+ if (!pDirectSoundCreate)
+ {
+ Warning( "Couldn't get DS proc addr\n");
+ return SIS_FAILURE;
+ }
+ }
+
+ while ((hresult = pDirectSoundCreate(NULL, &pDS, NULL)) != DS_OK)
+ {
+ if (hresult != DSERR_ALLOCATED)
+ {
+ DevMsg ("DirectSound create failed\n");
+ return SIS_FAILURE;
+ }
+
+ return SIS_NOTAVAIL;
+ }
+
+ // get snd_surround value from window settings
+ DetectWindowsSpeakerSetup();
+
+ m_bSurround = false;
+ m_bSurroundCenter = false;
+ m_bHeadphone = false;
+ m_isInterleaved = false;
+
+ switch ( snd_surround.GetInt() )
+ {
+ case 0:
+ m_bHeadphone = true; // stereo headphone
+ pri_channels = 2; // primary buffer mixes stereo input data
+ break;
+ default:
+ case 2:
+ pri_channels = 2; // primary buffer mixes stereo input data
+ break; // no surround
+ case 4:
+ m_bSurround = true; // quad surround
+ pri_channels = 1; // primary buffer mixes 3d mono input data
+ break;
+ case 5:
+ case 7:
+ m_bSurround = true; // 5.1 surround
+ m_bSurroundCenter = true;
+ pri_channels = 1; // primary buffer mixes 3d mono input data
+ break;
+ }
+
+ m_deviceChannels = pri_channels; // secondary buffers should have same # channels as primary
+ m_deviceSampleBits = 16; // hardware bits per sample
+ m_deviceDmaSpeed = SOUND_DMA_SPEED; // hardware playback rate
+
+ Q_memset( &format, 0, sizeof(format) );
+ format.wFormatTag = WAVE_FORMAT_PCM;
+ format.nChannels = pri_channels;
+ format.wBitsPerSample = m_deviceSampleBits;
+ format.nSamplesPerSec = m_deviceDmaSpeed;
+ format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8;
+ format.cbSize = 0;
+ format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
+
+ DSCAPS dscaps;
+ Q_memset( &dscaps, 0, sizeof(dscaps) );
+ dscaps.dwSize = sizeof(dscaps);
+ if (DS_OK != pDS->GetCaps(&dscaps))
+ {
+ Warning( "Couldn't get DS caps\n");
+ }
+
+ if (dscaps.dwFlags & DSCAPS_EMULDRIVER)
+ {
+ Warning( "No DirectSound driver installed\n");
+ Shutdown();
+ return SIS_FAILURE;
+ }
+
+ if (DS_OK != pDS->SetCooperativeLevel(*pmainwindow, DSSCL_EXCLUSIVE))
+ {
+ Warning( "Set coop level failed\n");
+ Shutdown();
+ return SIS_FAILURE;
+ }
+
+ // get access to the primary buffer, if possible, so we can set the
+ // sound hardware format
+ Q_memset( &dsbuf, 0, sizeof(dsbuf) );
+ dsbuf.dwSize = sizeof(DSBUFFERDESC);
+ dsbuf.dwFlags = DSBCAPS_PRIMARYBUFFER;
+ if ( snd_legacy_surround.GetBool() || m_bSurround )
+ {
+ dsbuf.dwFlags |= DSBCAPS_CTRL3D;
+ }
+ dsbuf.dwBufferBytes = 0;
+ dsbuf.lpwfxFormat = NULL;
+
+ Q_memset( &dsbcaps, 0, sizeof(dsbcaps) );
+ dsbcaps.dwSize = sizeof(dsbcaps);
+
+ if ( !CommandLine()->CheckParm("-snoforceformat"))
+ {
+ if (DS_OK == pDS->CreateSoundBuffer(&dsbuf, &pDSPBuf, NULL))
+ {
+ pformat = format;
+
+ if (DS_OK != pDSPBuf->SetFormat(&pformat))
+ {
+ if (snd_firsttime)
+ DevMsg ("Set primary sound buffer format: no\n");
+ }
+ else
+ {
+ if (snd_firsttime)
+ DevMsg ("Set primary sound buffer format: yes\n");
+
+ primary_format_set = true;
+ }
+ }
+ }
+
+ if ( m_bSurround )
+ {
+ // try to init surround
+ m_bSurround = false;
+ if ( snd_legacy_surround.GetBool() )
+ {
+ if (snd_surround.GetInt() == 4)
+ {
+ // attempt to init 4 channel surround
+ m_bSurround = SNDDMA_InitSurround(pDS, &format, &dsbcaps, 4);
+ }
+ else if (snd_surround.GetInt() == 5 || snd_surround.GetInt() == 7)
+ {
+ // attempt to init 5 channel surround
+ m_bSurroundCenter = SNDDMA_InitSurround(pDS, &format, &dsbcaps, 5);
+ m_bSurround = m_bSurroundCenter;
+ }
+ }
+ if ( !m_bSurround )
+ {
+ pri_channels = 6;
+ if ( snd_surround.GetInt() < 5 )
+ {
+ pri_channels = 4;
+ }
+
+ m_bSurround = SNDDMA_InitInterleaved( pDS, &format, pri_channels );
+ }
+ }
+
+ if ( !m_bSurround )
+ {
+ // snd_surround.SetValue( 0 );
+ if ( !primary_format_set || !CommandLine()->CheckParm ("-primarysound") )
+ {
+ // create the secondary buffer we'll actually work with
+ Q_memset( &dsbuf, 0, sizeof(dsbuf) );
+ dsbuf.dwSize = sizeof(DSBUFFERDESC);
+ dsbuf.dwFlags = DSBCAPS_LOCSOFTWARE; // NOTE: don't use CTRLFREQUENCY (slow)
+ dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE;
+ dsbuf.lpwfxFormat = &format;
+ if ( !snd_mute_losefocus.GetBool() )
+ {
+ dsbuf.dwFlags |= DSBCAPS_GLOBALFOCUS;
+ }
+
+ if (DS_OK != pDS->CreateSoundBuffer(&dsbuf, &pDSBuf, NULL))
+ {
+ Warning( "DS:CreateSoundBuffer Failed");
+ Shutdown();
+ return SIS_FAILURE;
+ }
+
+ m_deviceChannels = format.nChannels;
+ m_deviceSampleBits = format.wBitsPerSample;
+ m_deviceDmaSpeed = format.nSamplesPerSec;
+
+ Q_memset(&dsbcaps, 0, sizeof(dsbcaps));
+ dsbcaps.dwSize = sizeof(dsbcaps);
+
+ if (DS_OK != pDSBuf->GetCaps( &dsbcaps ))
+ {
+ Warning( "DS:GetCaps failed\n");
+ Shutdown();
+ return SIS_FAILURE;
+ }
+
+ if ( snd_firsttime )
+ DevMsg ("Using secondary sound buffer\n");
+ }
+ else
+ {
+ if (DS_OK != pDS->SetCooperativeLevel(*pmainwindow, DSSCL_WRITEPRIMARY))
+ {
+ Warning( "Set coop level failed\n");
+ Shutdown();
+ return SIS_FAILURE;
+ }
+
+ Q_memset(&dsbcaps, 0, sizeof(dsbcaps));
+ dsbcaps.dwSize = sizeof(dsbcaps);
+ if (DS_OK != pDSPBuf->GetCaps(&dsbcaps))
+ {
+ Msg ("DS:GetCaps failed\n");
+ return SIS_FAILURE;
+ }
+
+ pDSBuf = pDSPBuf;
+ DevMsg ("Using primary sound buffer\n");
+ }
+
+ if ( snd_firsttime )
+ {
+ DevMsg(" %d channel(s)\n"
+ " %d bits/sample\n"
+ " %d samples/sec\n",
+ DeviceChannels(), DeviceSampleBits(), DeviceDmaSpeed());
+ }
+
+ // initialize the buffer
+ m_bufferSizeBytes = dsbcaps.dwBufferBytes;
+ int reps = 0;
+ while ((hresult = pDSBuf->Lock(0, m_bufferSizeBytes, (void**)&lpData, &dwSize, NULL, NULL, 0)) != DS_OK)
+ {
+ if (hresult != DSERR_BUFFERLOST)
+ {
+ Warning( "SNDDMA_InitDirect: DS::Lock Sound Buffer Failed\n");
+ Shutdown();
+ return SIS_FAILURE;
+ }
+
+ if (++reps > 10000)
+ {
+ Warning( "SNDDMA_InitDirect: DS: couldn't restore buffer\n");
+ Shutdown();
+ return SIS_FAILURE;
+ }
+ }
+
+ Q_memset( lpData, 0, dwSize );
+ pDSBuf->Unlock(lpData, dwSize, NULL, 0);
+
+ // Make sure mixer is active (this was moved after the zeroing to avoid popping on startup -- at least when using the dx9.0b debug .dlls)
+ pDSBuf->Play(0, 0, DSBPLAY_LOOPING);
+
+ // we don't want anyone to access the buffer directly w/o locking it first.
+ lpData = NULL;
+
+ pDSBuf->Stop();
+
+ pDSBuf->GetCurrentPosition(&m_outputBufferStartOffset, &dwWrite);
+
+ pDSBuf->Play(0, 0, DSBPLAY_LOOPING);
+ }
+
+ // number of mono samples output buffer may hold
+ m_deviceSampleCount = m_bufferSizeBytes/(DeviceSampleBytes());
+
+ return SIS_SUCCESS;
+
+}
+
+static DWORD GetSpeakerConfigForSurroundMode( int surroundMode, const char **pConfigDesc )
+{
+ DWORD newSpeakerConfig = DSSPEAKER_STEREO;
+ const char *speakerConfigDesc = "";
+
+ switch ( surroundMode )
+ {
+ case 0:
+ newSpeakerConfig = DSSPEAKER_HEADPHONE;
+ speakerConfigDesc = "headphone";
+ break;
+
+ case 2:
+ default:
+ newSpeakerConfig = DSSPEAKER_STEREO;
+ speakerConfigDesc = "stereo speaker";
+ break;
+
+ case 4:
+ newSpeakerConfig = DSSPEAKER_QUAD;
+ speakerConfigDesc = "quad speaker";
+ break;
+
+ case 5:
+ newSpeakerConfig = DSSPEAKER_5POINT1;
+ speakerConfigDesc = "5.1 speaker";
+ break;
+
+ case 7:
+ newSpeakerConfig = DSSPEAKER_7POINT1;
+ speakerConfigDesc = "7.1 speaker";
+ break;
+ }
+ if ( pConfigDesc )
+ {
+ *pConfigDesc = speakerConfigDesc;
+ }
+ return newSpeakerConfig;
+}
+
+// Read the speaker config from windows
+static DWORD GetWindowsSpeakerConfig()
+{
+ DWORD speaker_config = windows_speaker_config.GetInt();
+ if ( windows_speaker_config.GetInt() < 0 )
+ {
+ speaker_config = DSSPEAKER_STEREO;
+ if (DS_OK == pDS->GetSpeakerConfig( &speaker_config ))
+ {
+ // split out settings
+ speaker_config = DSSPEAKER_CONFIG(speaker_config);
+ if ( speaker_config == DSSPEAKER_7POINT1_SURROUND )
+ speaker_config = DSSPEAKER_7POINT1;
+ if ( speaker_config == DSSPEAKER_5POINT1_SURROUND)
+ speaker_config = DSSPEAKER_5POINT1;
+ }
+ windows_speaker_config.SetValue((int)speaker_config);
+ }
+
+ return speaker_config;
+}
+
+// Writes snd_surround convar given a directsound speaker config
+static void SetSurroundModeFromSpeakerConfig( DWORD speakerConfig )
+{
+ // set the cvar to be the windows setting
+ switch (speakerConfig)
+ {
+ case DSSPEAKER_HEADPHONE:
+ snd_surround.SetValue(0);
+ break;
+
+ case DSSPEAKER_MONO:
+ case DSSPEAKER_STEREO:
+ default:
+ snd_surround.SetValue( 2 );
+ break;
+
+ case DSSPEAKER_QUAD:
+ snd_surround.SetValue(4);
+ break;
+
+ case DSSPEAKER_5POINT1:
+ snd_surround.SetValue(5);
+ break;
+
+ case DSSPEAKER_7POINT1:
+ snd_surround.SetValue(7);
+ break;
+ }
+}
+/*
+ Sets the snd_surround_speakers cvar based on the windows setting
+*/
+
+void CAudioDirectSound::DetectWindowsSpeakerSetup()
+{
+ // detect speaker settings from windows
+ DWORD speaker_config = GetWindowsSpeakerConfig();
+ SetSurroundModeFromSpeakerConfig(speaker_config);
+
+ // DEBUG
+ if (speaker_config == DSSPEAKER_MONO)
+ DevMsg( "DS:mono configuration detected\n");
+
+ if (speaker_config == DSSPEAKER_HEADPHONE)
+ DevMsg( "DS:headphone configuration detected\n");
+
+ if (speaker_config == DSSPEAKER_STEREO)
+ DevMsg( "DS:stereo speaker configuration detected\n");
+
+ if (speaker_config == DSSPEAKER_QUAD)
+ DevMsg( "DS:quad speaker configuration detected\n");
+
+ if (speaker_config == DSSPEAKER_SURROUND)
+ DevMsg( "DS:surround speaker configuration detected\n");
+
+ if (speaker_config == DSSPEAKER_5POINT1)
+ DevMsg( "DS:5.1 speaker configuration detected\n");
+
+ if (speaker_config == DSSPEAKER_7POINT1)
+ DevMsg( "DS:7.1 speaker configuration detected\n");
+}
+
+/*
+ Updates windows settings based on snd_surround_speakers cvar changing
+ This should only happen if the user has changed it via the console or the UI
+ Changes won't take effect until the engine has restarted
+*/
+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 (!pDS || flOldValue == -1 )
+ return;
+
+ // get the user's previous speaker config
+ DWORD speaker_config = GetWindowsSpeakerConfig();
+
+ // get the new config
+ DWORD 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
+ windows_speaker_config.SetValue( (int)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 )
+{
+ if ( pDS && CAudioDirectSound::m_pSingleton )
+ {
+ ConVarRef var( pVar );
+ // should either be interleaved or have legacy surround set, not both
+ if ( CAudioDirectSound::m_pSingleton->IsInterleaved() == var.GetBool() )
+ {
+ Msg( "Legacy Surround %s.\n", var.GetBool() ? "enabled" : "disabled" );
+ // restart sound system so it takes effect
+ g_pSoundServices->RestartSoundSystem();
+ }
+ }
+}
+
+void OnSndVarChanged( IConVar *pVar, const char *pOldString, float flOldValue )
+{
+ ConVarRef var(pVar);
+ // restart sound system so the change takes effect
+ if ( var.GetInt() != int(flOldValue) )
+ {
+ g_pSoundServices->RestartSoundSystem();
+ }
+}
+
+/*
+ Release all Surround buffer pointers
+*/
+void ReleaseSurround(void)
+{
+ if ( pDSBuf3DFL != NULL )
+ {
+ pDSBuf3DFL->Release();
+ pDSBuf3DFL = NULL;
+ }
+
+ if ( pDSBuf3DFR != NULL)
+ {
+ pDSBuf3DFR->Release();
+ pDSBuf3DFR = NULL;
+ }
+
+ if ( pDSBuf3DRL != NULL )
+ {
+ pDSBuf3DRL->Release();
+ pDSBuf3DRL = NULL;
+ }
+
+ if ( pDSBuf3DRR != NULL )
+ {
+ pDSBuf3DRR->Release();
+ pDSBuf3DRR = NULL;
+ }
+
+ if ( pDSBufFL != NULL )
+ {
+ pDSBufFL->Release();
+ pDSBufFL = NULL;
+ }
+
+ if ( pDSBufFR != NULL )
+ {
+ pDSBufFR->Release();
+ pDSBufFR = NULL;
+ }
+
+ if ( pDSBufRL != NULL )
+ {
+ pDSBufRL->Release();
+ pDSBufRL = NULL;
+ }
+
+ if ( pDSBufRR != NULL )
+ {
+ pDSBufRR->Release();
+ pDSBufRR = NULL;
+ }
+
+ if ( pDSBufFC != NULL )
+ {
+ pDSBufFC->Release();
+ pDSBufFC = NULL;
+ }
+}
+
+void DEBUG_DS_FillSquare( void *lpData, DWORD dwSize )
+{
+ short *lpshort = (short *)lpData;
+ DWORD j = min((DWORD)10000, dwSize/2);
+
+ for (DWORD i = 0; i < j; i++)
+ lpshort[i] = 8000;
+}
+
+void DEBUG_DS_FillSquare2( void *lpData, DWORD dwSize )
+{
+ short *lpshort = (short *)lpData;
+ DWORD j = min((DWORD)1000, dwSize/2);
+
+ for (DWORD i = 0; i < j; i++)
+ lpshort[i] = 16000;
+}
+
+// helper to set default buffer params
+void DS3D_SetBufferParams( LPDIRECTSOUND3DBUFFER pDSBuf3D, D3DVECTOR *pbpos, D3DVECTOR *pbdir )
+{
+ DS3DBUFFER bparm;
+ D3DVECTOR bvel;
+ D3DVECTOR bpos, bdir;
+ HRESULT hr;
+
+ bvel.x = 0.0f; bvel.y = 0.0f; bvel.z = 0.0f;
+ bpos = *pbpos;
+ bdir = *pbdir;
+
+ bparm.dwSize = sizeof(DS3DBUFFER);
+
+ hr = pDSBuf3D->GetAllParameters( &bparm );
+
+ bparm.vPosition = bpos;
+ bparm.vVelocity = bvel;
+ bparm.dwInsideConeAngle = 5.0; // narrow cones for each speaker
+ bparm.dwOutsideConeAngle = 10.0;
+ bparm.vConeOrientation = bdir;
+ bparm.lConeOutsideVolume = DSBVOLUME_MIN;
+ bparm.flMinDistance = 100.0; // no rolloff (until > 2.0 meter distance)
+ bparm.flMaxDistance = DS3D_DEFAULTMAXDISTANCE;
+ bparm.dwMode = DS3DMODE_NORMAL;
+
+ hr = pDSBuf3D->SetAllParameters( &bparm, DS3D_DEFERRED );
+}
+
+// Initialization for Surround sound support (4 channel or 5 channel).
+// Creates 4 or 5 mono 3D buffers to be used as Front Left, (Front Center), Front Right, Rear Left, Rear Right
+bool CAudioDirectSound::SNDDMA_InitSurround(LPDIRECTSOUND lpDS, WAVEFORMATEX* lpFormat, DSBCAPS* lpdsbc, int cchan)
+{
+ DSBUFFERDESC dsbuf;
+ WAVEFORMATEX wvex;
+ DWORD dwSize, dwWrite;
+ int reps;
+ HRESULT hresult;
+ void *lpData = NULL;
+
+ if ( lpDS == NULL ) return FALSE;
+
+ // Force format to mono channel
+
+ memcpy(&wvex, lpFormat, sizeof(WAVEFORMATEX));
+ wvex.nChannels = 1;
+ wvex.nBlockAlign = wvex.nChannels * wvex.wBitsPerSample / 8;
+ wvex.nAvgBytesPerSec = wvex.nSamplesPerSec * wvex.nBlockAlign;
+
+ memset (&dsbuf, 0, sizeof(dsbuf));
+ dsbuf.dwSize = sizeof(DSBUFFERDESC);
+ // NOTE: LOCHARDWARE causes SB AWE64 to crash in it's DSOUND driver
+ dsbuf.dwFlags = DSBCAPS_CTRL3D; // don't use CTRLFREQUENCY (slow)
+ if ( !snd_mute_losefocus.GetBool() )
+ {
+ dsbuf.dwFlags |= DSBCAPS_GLOBALFOCUS;
+ }
+
+ // reserve space for each buffer
+
+ dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE_SURROUND;
+
+ dsbuf.lpwfxFormat = &wvex;
+
+ // create 4 mono buffers FL, FR, RL, RR
+
+ if (DS_OK != lpDS->CreateSoundBuffer(&dsbuf, &pDSBufFL, NULL))
+ {
+ Warning( "DS:CreateSoundBuffer for 3d front left failed");
+ ReleaseSurround();
+ return FALSE;
+ }
+
+ if (DS_OK != lpDS->CreateSoundBuffer(&dsbuf, &pDSBufFR, NULL))
+ {
+ Warning( "DS:CreateSoundBuffer for 3d front right failed");
+ ReleaseSurround();
+ return FALSE;
+ }
+
+ if (DS_OK != lpDS->CreateSoundBuffer(&dsbuf, &pDSBufRL, NULL))
+ {
+ Warning( "DS:CreateSoundBuffer for 3d rear left failed");
+ ReleaseSurround();
+ return FALSE;
+ }
+
+ if (DS_OK != lpDS->CreateSoundBuffer(&dsbuf, &pDSBufRR, NULL))
+ {
+ Warning( "DS:CreateSoundBuffer for 3d rear right failed");
+ ReleaseSurround();
+ return FALSE;
+ }
+
+ // create center channel
+
+ if (cchan == 5)
+ {
+ if (DS_OK != lpDS->CreateSoundBuffer(&dsbuf, &pDSBufFC, NULL))
+ {
+ Warning( "DS:CreateSoundBuffer for 3d front center failed");
+ ReleaseSurround();
+ return FALSE;
+ }
+ }
+
+ // Try to get 4 or 5 3D buffers from the mono DS buffers
+
+ if (DS_OK != pDSBufFL->QueryInterface(IID_IDirectSound3DBufferDef, (void**)&pDSBuf3DFL))
+ {
+ Warning( "DS:Query 3DBuffer for 3d front left failed");
+ ReleaseSurround();
+ return FALSE;
+ }
+
+ if (DS_OK != pDSBufFR->QueryInterface(IID_IDirectSound3DBufferDef, (void**)&pDSBuf3DFR))
+ {
+ Warning( "DS:Query 3DBuffer for 3d front right failed");
+ ReleaseSurround();
+ return FALSE;
+ }
+
+ if (DS_OK != pDSBufRL->QueryInterface(IID_IDirectSound3DBufferDef, (void**)&pDSBuf3DRL))
+ {
+ Warning( "DS:Query 3DBuffer for 3d rear left failed");
+ ReleaseSurround();
+ return FALSE;
+ }
+
+ if (DS_OK != pDSBufRR->QueryInterface(IID_IDirectSound3DBufferDef, (void**)&pDSBuf3DRR))
+ {
+ Warning( "DS:Query 3DBuffer for 3d rear right failed");
+ ReleaseSurround();
+ return FALSE;
+ }
+
+ if (cchan == 5)
+ {
+ if (DS_OK != pDSBufFC->QueryInterface(IID_IDirectSound3DBufferDef, (void**)&pDSBuf3DFC))
+ {
+ Warning( "DS:Query 3DBuffer for 3d front center failed");
+ ReleaseSurround();
+ return FALSE;
+ }
+ }
+
+ // set listener position & orientation.
+ // DS uses left handed coord system: +x is right, +y is up, +z is forward
+
+ HRESULT hr;
+
+ IDirectSound3DListener *plistener = NULL;
+
+ hr = pDSPBuf->QueryInterface(IID_IDirectSound3DListener, (void**)&plistener);
+ if (plistener)
+ {
+ DS3DLISTENER lparm;
+ lparm.dwSize = sizeof(DS3DLISTENER);
+
+ hr = plistener->GetAllParameters( &lparm );
+
+ hr = plistener->SetOrientation( 0.0f,0.0f,1.0f, 0.0f,1.0f,0.0f, DS3D_IMMEDIATE); // frontx,y,z topx,y,z
+ hr = plistener->SetPosition(0.0f, 0.0f, 0.0f, DS3D_IMMEDIATE);
+ }
+ else
+ {
+ Warning( "DS: failed to get 3D listener interface.");
+ ReleaseSurround();
+ return FALSE;
+ }
+
+ // set 3d buffer position and orientation params
+
+ D3DVECTOR bpos, bdir;
+
+ bpos.x = -1.0; bpos.y = 0.0; bpos.z = 1.0; // FL
+ bdir.x = 1.0; bdir.y = 0.0; bdir.z = -1.0;
+
+ DS3D_SetBufferParams( pDSBuf3DFL, &bpos, &bdir );
+
+ bpos.x = 1.0; bpos.y = 0.0; bpos.z = 1.0; // FR
+ bdir.x = -1.0; bdir.y = 0.0; bdir.z = -1.0;
+
+ DS3D_SetBufferParams( pDSBuf3DFR, &bpos, &bdir );
+
+ bpos.x = -1.0; bpos.y = 0.0; bpos.z = -1.0; // RL
+ bdir.x = 1.0; bdir.y = 0.0; bdir.z = 1.0;
+
+ DS3D_SetBufferParams( pDSBuf3DRL, &bpos, &bdir );
+
+ bpos.x = 1.0; bpos.y = 0.0; bpos.z = -1.0; // RR
+ bdir.x = -1.0; bdir.y = 0.0; bdir.z = 1.0;
+
+ DS3D_SetBufferParams( pDSBuf3DRR, &bpos, &bdir );
+
+ if (cchan == 5)
+ {
+ bpos.x = 0.0; bpos.y = 0.0; bpos.z = 1.0; // FC
+ bdir.x = 0.0; bdir.y = 0.0; bdir.z = -1.0;
+
+ DS3D_SetBufferParams( pDSBuf3DFC, &bpos, &bdir );
+ }
+
+ // commit all buffer param settings
+
+ hr = plistener->CommitDeferredSettings();
+
+ m_deviceChannels = 1; // 1 mono 3d output buffer
+ m_deviceSampleBits = lpFormat->wBitsPerSample;
+ m_deviceDmaSpeed = lpFormat->nSamplesPerSec;
+
+ memset(lpdsbc, 0, sizeof(DSBCAPS));
+ lpdsbc->dwSize = sizeof(DSBCAPS);
+
+ if (DS_OK != pDSBufFL->GetCaps (lpdsbc))
+ {
+ Warning( "DS:GetCaps failed for 3d sound buffer\n");
+ ReleaseSurround();
+ return FALSE;
+ }
+
+ pDSBufFL->Play(0, 0, DSBPLAY_LOOPING);
+ pDSBufFR->Play(0, 0, DSBPLAY_LOOPING);
+ pDSBufRL->Play(0, 0, DSBPLAY_LOOPING);
+ pDSBufRR->Play(0, 0, DSBPLAY_LOOPING);
+
+ if (cchan == 5)
+ pDSBufFC->Play(0, 0, DSBPLAY_LOOPING);
+
+ if (snd_firsttime)
+ DevMsg(" %d channel(s)\n"
+ " %d bits/sample\n"
+ " %d samples/sec\n",
+ cchan, DeviceSampleBits(), DeviceDmaSpeed());
+
+ m_bufferSizeBytes = lpdsbc->dwBufferBytes;
+
+ // Test everything just like in the normal initialization.
+ if (cchan == 5)
+ {
+ reps = 0;
+ while ((hresult = pDSBufFC->Lock(0, lpdsbc->dwBufferBytes, (void**)&lpData, &dwSize, NULL, NULL, 0)) != DS_OK)
+ {
+ if (hresult != DSERR_BUFFERLOST)
+ {
+ Warning( "SNDDMA_InitDirect: DS::Lock Sound Buffer Failed for FC\n");
+ ReleaseSurround();
+ return FALSE;
+ }
+
+ if (++reps > 10000)
+ {
+ Warning( "SNDDMA_InitDirect: DS: couldn't restore buffer for FC\n");
+ ReleaseSurround();
+ return FALSE;
+ }
+ }
+ memset(lpData, 0, dwSize);
+// DEBUG_DS_FillSquare( lpData, dwSize );
+ pDSBufFC->Unlock(lpData, dwSize, NULL, 0);
+ }
+
+ reps = 0;
+ while ((hresult = pDSBufFL->Lock(0, lpdsbc->dwBufferBytes, (void**)&lpData, &dwSize, NULL, NULL, 0)) != DS_OK)
+ {
+ if (hresult != DSERR_BUFFERLOST)
+ {
+ Warning( "SNDDMA_InitSurround: DS::Lock Sound Buffer Failed for 3d FL\n");
+ ReleaseSurround();
+ return FALSE;
+ }
+
+ if (++reps > 10000)
+ {
+ Warning( "SNDDMA_InitSurround: DS: couldn't restore buffer for 3d FL\n");
+ ReleaseSurround();
+ return FALSE;
+ }
+ }
+ memset(lpData, 0, dwSize);
+// DEBUG_DS_FillSquare( lpData, dwSize );
+ pDSBufFL->Unlock(lpData, dwSize, NULL, 0);
+
+ reps = 0;
+ while ((hresult = pDSBufFR->Lock(0, lpdsbc->dwBufferBytes, (void**)&lpData, &dwSize, NULL, NULL, 0)) != DS_OK)
+ {
+ if (hresult != DSERR_BUFFERLOST)
+ {
+ Warning( "SNDDMA_InitSurround: DS::Lock Sound Buffer Failed for 3d FR\n");
+ ReleaseSurround();
+ return FALSE;
+ }
+
+ if (++reps > 10000)
+ {
+ Warning( "SNDDMA_InitSurround: DS: couldn't restore buffer for FR\n");
+ ReleaseSurround();
+ return FALSE;
+ }
+ }
+ memset(lpData, 0, dwSize);
+// DEBUG_DS_FillSquare( lpData, dwSize );
+ pDSBufFR->Unlock(lpData, dwSize, NULL, 0);
+
+ reps = 0;
+ while ((hresult = pDSBufRL->Lock(0, lpdsbc->dwBufferBytes, (void**)&lpData, &dwSize, NULL, NULL, 0)) != DS_OK)
+ {
+ if (hresult != DSERR_BUFFERLOST)
+ {
+ Warning( "SNDDMA_InitDirect: DS::Lock Sound Buffer Failed for RL\n");
+ ReleaseSurround();
+ return FALSE;
+ }
+
+ if (++reps > 10000)
+ {
+ Warning( "SNDDMA_InitDirect: DS: couldn't restore buffer for RL\n");
+ ReleaseSurround();
+ return FALSE;
+ }
+ }
+ memset(lpData, 0, dwSize);
+// DEBUG_DS_FillSquare( lpData, dwSize );
+ pDSBufRL->Unlock(lpData, dwSize, NULL, 0);
+
+ reps = 0;
+ while ((hresult = pDSBufRR->Lock(0, lpdsbc->dwBufferBytes, (void**)&lpData, &dwSize, NULL, NULL, 0)) != DS_OK)
+ {
+ if (hresult != DSERR_BUFFERLOST)
+ {
+ Warning( "SNDDMA_InitDirect: DS::Lock Sound Buffer Failed for RR\n");
+ ReleaseSurround();
+ return FALSE;
+ }
+
+ if (++reps > 10000)
+ {
+ Warning( "SNDDMA_InitDirect: DS: couldn't restore buffer for RR\n");
+ ReleaseSurround();
+ return FALSE;
+ }
+ }
+ memset(lpData, 0, dwSize);
+// DEBUG_DS_FillSquare( lpData, dwSize );
+ pDSBufRR->Unlock(lpData, dwSize, NULL, 0);
+
+ lpData = NULL; // this is invalid now
+
+ // OK Stop and get our positions and were good to go.
+ pDSBufFL->Stop();
+ pDSBufFR->Stop();
+ pDSBufRL->Stop();
+ pDSBufRR->Stop();
+ if (cchan == 5)
+ pDSBufFC->Stop();
+
+ // get hardware playback position, store it, syncronize all buffers to FL
+
+ pDSBufFL->GetCurrentPosition(&m_outputBufferStartOffset, &dwWrite);
+ pDSBufFR->SetCurrentPosition(m_outputBufferStartOffset);
+ pDSBufRL->SetCurrentPosition(m_outputBufferStartOffset);
+ pDSBufRR->SetCurrentPosition(m_outputBufferStartOffset);
+ if (cchan == 5)
+ pDSBufFC->SetCurrentPosition(m_outputBufferStartOffset);
+
+ pDSBufFL->Play(0, 0, DSBPLAY_LOOPING);
+ pDSBufFR->Play(0, 0, DSBPLAY_LOOPING);
+ pDSBufRL->Play(0, 0, DSBPLAY_LOOPING);
+ pDSBufRR->Play(0, 0, DSBPLAY_LOOPING);
+ if (cchan == 5)
+ pDSBufFC->Play(0, 0, DSBPLAY_LOOPING);
+
+ if (snd_firsttime)
+ Warning( "3d surround sound initialization successful\n");
+
+ return TRUE;
+}
+
+void CAudioDirectSound::UpdateListener( const Vector& position, const Vector& forward, const Vector& right, const Vector& up )
+{
+}
+
+void CAudioDirectSound::ChannelReset( int entnum, int channelIndex, float distanceMod )
+{
+}
+
+const char *CAudioDirectSound::DeviceName( void )
+{
+ if ( m_bSurroundCenter )
+ return "5 Channel Surround";
+
+ if ( m_bSurround )
+ return "4 Channel Surround";
+
+ return "Direct Sound";
+}
+
+// use the partial buffer locking code in stereo as well - not available when recording a movie
+ConVar snd_lockpartial("snd_lockpartial","1");
+
+// Transfer up to a full paintbuffer (PAINTBUFFER_SIZE) of stereo samples
+// out to the directsound secondary buffer(s).
+// For 4 or 5 ch surround, there are 4 or 5 mono 16 bit secondary DS streaming buffers.
+// For stereo speakers, there is one stereo 16 bit secondary DS streaming buffer.
+
+void CAudioDirectSound::TransferSamples( int end )
+{
+ int lpaintedtime = g_paintedtime;
+ int endtime = end;
+
+ // When Surround is enabled, divert to 4 or 5 chan xfer scheme.
+ if ( m_bSurround )
+ {
+ if ( m_isInterleaved )
+ {
+ S_TransferSurround16Interleaved( PAINTBUFFER, REARPAINTBUFFER, CENTERPAINTBUFFER, lpaintedtime, endtime);
+ }
+ else
+ {
+ int cchan = ( m_bSurroundCenter ? 5 : 4);
+
+ S_TransferSurround16( PAINTBUFFER, REARPAINTBUFFER, CENTERPAINTBUFFER, lpaintedtime, endtime, cchan);
+ }
+ return;
+ }
+ else if ( snd_lockpartial.GetBool() && DeviceChannels() == 2 && DeviceSampleBits() == 16 && !SND_IsRecording() )
+ {
+ S_TransferSurround16Interleaved( PAINTBUFFER, NULL, NULL, lpaintedtime, endtime );
+ }
+ else
+ {
+ DWORD *pBuffer = NULL;
+ DWORD dwSize = 0;
+ if ( !LockDSBuffer( pDSBuf, &pBuffer, &dwSize, "DS_STEREO" ) )
+ {
+ S_Shutdown();
+ S_Startup();
+ return;
+ }
+ if ( pBuffer )
+ {
+ if ( DeviceChannels() == 2 && DeviceSampleBits() == 16 )
+ {
+ S_TransferStereo16( pBuffer, PAINTBUFFER, lpaintedtime, endtime );
+ }
+ else
+ {
+ // UNDONE: obsolete - no 8 bit mono output supported
+ S_TransferPaintBuffer( pBuffer, PAINTBUFFER, lpaintedtime, endtime );
+ }
+ pDSBuf->Unlock( pBuffer, dwSize, NULL, 0 );
+ }
+ }
+}
+
+bool CAudioDirectSound::IsUsingBufferPerSpeaker()
+{
+ return m_bSurround && !m_isInterleaved;
+}
+
+bool CAudioDirectSound::LockDSBuffer( LPDIRECTSOUNDBUFFER pBuffer, DWORD **pdwWriteBuffer, DWORD *pdwSizeBuffer, const char *pBufferName, int lockFlags )
+{
+ if ( !pBuffer )
+ return false;
+ HRESULT hr;
+ int reps = 0;
+ while ((hr = pBuffer->Lock(0, m_bufferSizeBytes, (void**)pdwWriteBuffer, pdwSizeBuffer,
+ NULL, NULL, lockFlags)) != DS_OK)
+ {
+ if (hr != DSERR_BUFFERLOST)
+ {
+ Msg ("DS::Lock Sound Buffer Failed %s\n", pBufferName);
+ return false;
+ }
+
+ if (++reps > 10000)
+ {
+ Msg ("DS:: couldn't restore buffer %s\n", pBufferName);
+ return false;
+ }
+ }
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+// Given front, rear and center stereo paintbuffers, split samples into 4 or 5 mono directsound buffers (FL, FC, FR, RL, RR)
+void CAudioDirectSound::S_TransferSurround16( portable_samplepair_t *pfront, portable_samplepair_t *prear, portable_samplepair_t *pcenter, int lpaintedtime, int endtime, int cchan)
+{
+ int lpos;
+ DWORD *pdwWriteFL=NULL, *pdwWriteFR=NULL, *pdwWriteRL=NULL, *pdwWriteRR=NULL, *pdwWriteFC=NULL;
+ DWORD dwSizeFL=0, dwSizeFR=0, dwSizeRL=0, dwSizeRR=0, dwSizeFC=0;
+ int i, j, *snd_p, *snd_rp, *snd_cp, volumeFactor;
+ short *snd_out_fleft, *snd_out_fright, *snd_out_rleft, *snd_out_rright, *snd_out_fcenter;
+
+ pdwWriteFC = NULL; // compiler warning
+ dwSizeFC = 0;
+ snd_out_fcenter = NULL;
+
+ volumeFactor = S_GetMasterVolume() * 256;
+
+ // lock all 4 or 5 mono directsound buffers FL, FR, RL, RR, FC
+ if ( !LockDSBuffer( pDSBufFL, &pdwWriteFL, &dwSizeFL, "FL" ) ||
+ !LockDSBuffer( pDSBufFR, &pdwWriteFR, &dwSizeFR, "FR" ) ||
+ !LockDSBuffer( pDSBufRL, &pdwWriteRL, &dwSizeRL, "RL" ) ||
+ !LockDSBuffer( pDSBufRR, &pdwWriteRR, &dwSizeRR, "RR" ) )
+ {
+ S_Shutdown();
+ S_Startup();
+ return;
+ }
+
+ if (cchan == 5 && !LockDSBuffer( pDSBufFC, &pdwWriteFC, &dwSizeFC, "FC" ))
+ {
+ S_Shutdown ();
+ S_Startup ();
+ return;
+ }
+
+ // take stereo front and rear paintbuffers, and center paintbuffer if provided,
+ // and copy samples into the 4 or 5 mono directsound buffers
+
+ snd_rp = (int *)prear;
+ snd_cp = (int *)pcenter;
+ snd_p = (int *)pfront;
+
+ int linearCount; // space in output buffer for linearCount mono samples
+ int sampleMonoCount = DeviceSampleCount(); // number of mono samples per output buffer (was;(DeviceSampleCount()>>1))
+ int sampleMask = sampleMonoCount - 1;
+
+ // paintedtime - number of full samples that have played since start
+ // endtime - number of full samples to play to - endtime is g_soundtime + mixahead samples
+
+ while (lpaintedtime < endtime)
+ {
+ lpos = lpaintedtime & sampleMask; // lpos is next output position in output buffer
+
+ linearCount = sampleMonoCount - lpos;
+
+ // limit output count to requested number of samples
+
+ if (linearCount > endtime - lpaintedtime)
+ linearCount = endtime - lpaintedtime;
+
+ snd_out_fleft = (short *)pdwWriteFL + lpos;
+ snd_out_fright = (short *)pdwWriteFR + lpos;
+ snd_out_rleft = (short *)pdwWriteRL + lpos;
+ snd_out_rright = (short *)pdwWriteRR + lpos;
+
+ if (cchan == 5)
+ snd_out_fcenter = (short *)pdwWriteFC + lpos;
+
+ // for 16 bit sample in the front and rear stereo paintbuffers, copy
+ // into the 4 or 5 FR, FL, RL, RR, FC directsound paintbuffers
+
+ for (i=0, j= 0 ; i<linearCount ; i++, j+=2)
+ {
+ snd_out_fleft[i] = (snd_p[j]*volumeFactor)>>8;
+ snd_out_fright[i] = (snd_p[j + 1]*volumeFactor)>>8;
+ snd_out_rleft[i] = (snd_rp[j]*volumeFactor)>>8;
+ snd_out_rright[i] = (snd_rp[j + 1]*volumeFactor)>>8;
+ }
+
+ // copy front center buffer (mono) data to center chan directsound paintbuffer
+
+ if (cchan == 5)
+ {
+ for (i=0, j=0 ; i<linearCount ; i++, j+=2)
+ {
+ snd_out_fcenter[i] = (snd_cp[j]*volumeFactor)>>8;
+
+ }
+ }
+
+ snd_p += linearCount << 1;
+ snd_rp += linearCount << 1;
+ snd_cp += linearCount << 1;
+
+ lpaintedtime += linearCount;
+ }
+
+ pDSBufFL->Unlock(pdwWriteFL, dwSizeFL, NULL, 0);
+ pDSBufFR->Unlock(pdwWriteFR, dwSizeFR, NULL, 0);
+ pDSBufRL->Unlock(pdwWriteRL, dwSizeRL, NULL, 0);
+ pDSBufRR->Unlock(pdwWriteRR, dwSizeRR, NULL, 0);
+
+ if (cchan == 5)
+ pDSBufFC->Unlock(pdwWriteFC, dwSizeFC, NULL, 0);
+}
+
+struct surround_transfer_t
+{
+ int paintedtime;
+ int linearCount;
+ int sampleMask;
+ int channelCount;
+ int *snd_p;
+ int *snd_rp;
+ int *snd_cp;
+ short *pOutput;
+};
+
+static void TransferSamplesToSurroundBuffer( int outputCount, surround_transfer_t &transfer )
+{
+ int i, j;
+ int volumeFactor = S_GetMasterVolume() * 256;
+
+ if ( transfer.channelCount == 2 )
+ {
+ for (i=0, j=0; i<outputCount ; i++, j+=2)
+ {
+ transfer.pOutput[0] = (transfer.snd_p[j]*volumeFactor)>>8; // FL
+ transfer.pOutput[1] = (transfer.snd_p[j + 1]*volumeFactor)>>8; // FR
+ transfer.pOutput += 2;
+ }
+ }
+ // no center channel, 4 channel surround
+ else if ( transfer.channelCount == 4 )
+ {
+ for (i=0, j=0; i<outputCount ; i++, j+=2)
+ {
+ transfer.pOutput[0] = (transfer.snd_p[j]*volumeFactor)>>8; // FL
+ transfer.pOutput[1] = (transfer.snd_p[j + 1]*volumeFactor)>>8; // FR
+ transfer.pOutput[2] = (transfer.snd_rp[j]*volumeFactor)>>8; // RL
+ transfer.pOutput[3] = (transfer.snd_rp[j + 1]*volumeFactor)>>8; // RR
+ transfer.pOutput += 4;
+ //Assert( baseOffset <= (DeviceSampleCount()) );
+ }
+ }
+ else
+ {
+ Assert(transfer.snd_cp);
+ // 6 channel / 5.1
+ for (i=0, j=0 ; i<outputCount ; i++, j+=2)
+ {
+ transfer.pOutput[0] = (transfer.snd_p[j]*volumeFactor)>>8; // FL
+ transfer.pOutput[1] = (transfer.snd_p[j + 1]*volumeFactor)>>8; // FR
+
+ transfer.pOutput[2] = (transfer.snd_cp[j]*volumeFactor)>>8; // Center
+
+ transfer.pOutput[3] = 0;
+
+ transfer.pOutput[4] = (transfer.snd_rp[j]*volumeFactor)>>8; // RL
+ transfer.pOutput[5] = (transfer.snd_rp[j + 1]*volumeFactor)>>8; // RR
+
+#if 0
+ // average channels into the subwoofer, let the sub filter the output
+ // NOTE: avg l/r rear to do 2 shifts instead of divide by 5
+ int sumFront = (int)transfer.pOutput[0] + (int)transfer.pOutput[1] + (int)transfer.pOutput[2];
+ int sumRear = (int)transfer.pOutput[4] + (int)transfer.pOutput[5];
+ transfer.pOutput[3] = (sumFront + (sumRear>>1)) >> 2;
+#endif
+
+ transfer.pOutput += 6;
+ //Assert( baseOffset <= (DeviceSampleCount()) );
+ }
+ }
+
+ transfer.snd_p += outputCount << 1;
+ if ( transfer.snd_rp )
+ {
+ transfer.snd_rp += outputCount << 1;
+ }
+ if ( transfer.snd_cp )
+ {
+ transfer.snd_cp += outputCount << 1;
+ }
+
+ transfer.paintedtime += outputCount;
+ transfer.linearCount -= outputCount;
+
+}
+
+void CAudioDirectSound::S_TransferSurround16Interleaved_FullLock( const portable_samplepair_t *pfront, const portable_samplepair_t *prear, const portable_samplepair_t *pcenter, int lpaintedtime, int endtime )
+{
+ int lpos;
+ DWORD *pdwWrite = NULL;
+ DWORD dwSize = 0;
+ int i, j, *snd_p, *snd_rp, *snd_cp, volumeFactor;
+
+ volumeFactor = S_GetMasterVolume() * 256;
+ int channelCount = m_bSurroundCenter ? 5 : 4;
+ if ( DeviceChannels() == 2 )
+ {
+ channelCount = 2;
+ }
+
+ // lock single interleaved buffer
+ if ( !LockDSBuffer( pDSBuf, &pdwWrite, &dwSize, "DS_INTERLEAVED" ) )
+ {
+ S_Shutdown ();
+ S_Startup ();
+ return;
+ }
+
+ // take stereo front and rear paintbuffers, and center paintbuffer if provided,
+ // and copy samples into the 4 or 5 mono directsound buffers
+
+ snd_rp = (int *)prear;
+ snd_cp = (int *)pcenter;
+ snd_p = (int *)pfront;
+
+ int linearCount; // space in output buffer for linearCount mono samples
+ int sampleMonoCount = m_bufferSizeBytes/(DeviceSampleBytes()*DeviceChannels()); // number of mono samples per output buffer (was;(DeviceSampleCount()>>1))
+ int sampleMask = sampleMonoCount - 1;
+
+ // paintedtime - number of full samples that have played since start
+ // endtime - number of full samples to play to - endtime is g_soundtime + mixahead samples
+
+ short *pOutput = (short *)pdwWrite;
+ while (lpaintedtime < endtime)
+ {
+ lpos = lpaintedtime & sampleMask; // lpos is next output position in output buffer
+
+ linearCount = sampleMonoCount - lpos;
+
+ // limit output count to requested number of samples
+
+ if (linearCount > endtime - lpaintedtime)
+ linearCount = endtime - lpaintedtime;
+
+ if ( channelCount == 4 )
+ {
+ int baseOffset = lpos * channelCount;
+ for (i=0, j= 0 ; i<linearCount ; i++, j+=2)
+ {
+ pOutput[baseOffset+0] = (snd_p[j]*volumeFactor)>>8; // FL
+ pOutput[baseOffset+1] = (snd_p[j + 1]*volumeFactor)>>8; // FR
+ pOutput[baseOffset+2] = (snd_rp[j]*volumeFactor)>>8; // RL
+ pOutput[baseOffset+3] = (snd_rp[j + 1]*volumeFactor)>>8; // RR
+ baseOffset += 4;
+ }
+ }
+ else
+ {
+ Assert(channelCount==5); // 6 channel / 5.1
+ int baseOffset = lpos * 6;
+ for (i=0, j= 0 ; i<linearCount ; i++, j+=2)
+ {
+ pOutput[baseOffset+0] = (snd_p[j]*volumeFactor)>>8; // FL
+ pOutput[baseOffset+1] = (snd_p[j + 1]*volumeFactor)>>8; // FR
+
+ pOutput[baseOffset+2] = (snd_cp[j]*volumeFactor)>>8; // Center
+ // NOTE: Let the hardware mix the sub from the main channels since
+ // we don't have any sub-specific sounds, or direct sub-addressing
+ pOutput[baseOffset+3] = 0;
+
+ pOutput[baseOffset+4] = (snd_rp[j]*volumeFactor)>>8; // RL
+ pOutput[baseOffset+5] = (snd_rp[j + 1]*volumeFactor)>>8; // RR
+
+
+ baseOffset += 6;
+ }
+ }
+
+ snd_p += linearCount << 1;
+ snd_rp += linearCount << 1;
+ snd_cp += linearCount << 1;
+
+ lpaintedtime += linearCount;
+ }
+
+ pDSBuf->Unlock(pdwWrite, dwSize, NULL, 0);
+}
+
+void CAudioDirectSound::S_TransferSurround16Interleaved( const portable_samplepair_t *pfront, const portable_samplepair_t *prear, const portable_samplepair_t *pcenter, int lpaintedtime, int endtime )
+{
+ if ( !pDSBuf )
+ return;
+ if ( !snd_lockpartial.GetBool() )
+ {
+ S_TransferSurround16Interleaved_FullLock( pfront, prear, pcenter, lpaintedtime, endtime );
+ return;
+ }
+ // take stereo front and rear paintbuffers, and center paintbuffer if provided,
+ // and copy samples into the 4 or 5 mono directsound buffers
+
+ surround_transfer_t transfer;
+ transfer.snd_rp = (int *)prear;
+ transfer.snd_cp = (int *)pcenter;
+ transfer.snd_p = (int *)pfront;
+
+ int sampleMonoCount = DeviceSampleCount()/DeviceChannels(); // number of full samples per output buffer
+ Assert(IsPowerOfTwo(sampleMonoCount));
+ transfer.sampleMask = sampleMonoCount - 1;
+ transfer.paintedtime = lpaintedtime;
+ transfer.linearCount = endtime - lpaintedtime;
+ // paintedtime - number of full samples that have played since start
+ // endtime - number of full samples to play to - endtime is g_soundtime + mixahead samples
+ int channelCount = m_bSurroundCenter ? 6 : 4;
+ if ( DeviceChannels() == 2 )
+ {
+ channelCount = 2;
+ }
+ transfer.channelCount = channelCount;
+ void *pBuffer0=NULL;
+ void *pBuffer1=NULL;
+ DWORD size0, size1;
+ int lpos = transfer.paintedtime & transfer.sampleMask; // lpos is next output position in output buffer
+
+ int offset = lpos*2*channelCount;
+ int lockSize = transfer.linearCount*2*channelCount;
+ int reps = 0;
+ HRESULT hr;
+ while ( (hr = pDSBuf->Lock( offset, lockSize, &pBuffer0, &size0, &pBuffer1, &size1, 0 )) != DS_OK )
+ {
+ if ( hr == DSERR_BUFFERLOST )
+ {
+ if ( ++reps < 10000 )
+ continue;
+ }
+ Msg ("DS::Lock Sound Buffer Failed\n");
+ return;
+ }
+
+ if ( pBuffer0 )
+ {
+ transfer.pOutput = (short *)pBuffer0;
+ TransferSamplesToSurroundBuffer( size0 / (channelCount*2), transfer );
+ }
+ if ( pBuffer1 )
+ {
+ transfer.pOutput = (short *)pBuffer1;
+ TransferSamplesToSurroundBuffer( size1 / (channelCount*2), transfer );
+ }
+ pDSBuf->Unlock(pBuffer0, size0, pBuffer1, size1);
+}
+
diff --git a/engine/audio/private/snd_dev_direct.h b/engine/audio/private/snd_dev_direct.h
new file mode 100644
index 0000000..428ff4d
--- /dev/null
+++ b/engine/audio/private/snd_dev_direct.h
@@ -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
diff --git a/engine/audio/private/snd_dev_mac_audioqueue.cpp b/engine/audio/private/snd_dev_mac_audioqueue.cpp
new file mode 100644
index 0000000..e58a703
--- /dev/null
+++ b/engine/audio/private/snd_dev_mac_audioqueue.cpp
@@ -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 )
+{
+}
+
+
diff --git a/engine/audio/private/snd_dev_mac_audioqueue.h b/engine/audio/private/snd_dev_mac_audioqueue.h
new file mode 100644
index 0000000..7de1260
--- /dev/null
+++ b/engine/audio/private/snd_dev_mac_audioqueue.h
@@ -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
diff --git a/engine/audio/private/snd_dev_openal.cpp b/engine/audio/private/snd_dev_openal.cpp
new file mode 100644
index 0000000..82c6136
--- /dev/null
+++ b/engine/audio/private/snd_dev_openal.cpp
@@ -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
+
diff --git a/engine/audio/private/snd_dev_openal.h b/engine/audio/private/snd_dev_openal.h
new file mode 100644
index 0000000..95dc2e6
--- /dev/null
+++ b/engine/audio/private/snd_dev_openal.h
@@ -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
diff --git a/engine/audio/private/snd_dev_sdl.cpp b/engine/audio/private/snd_dev_sdl.cpp
new file mode 100644
index 0000000..642090f
--- /dev/null
+++ b/engine/audio/private/snd_dev_sdl.cpp
@@ -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
+
diff --git a/engine/audio/private/snd_dev_sdl.h b/engine/audio/private/snd_dev_sdl.h
new file mode 100644
index 0000000..ce0af10
--- /dev/null
+++ b/engine/audio/private/snd_dev_sdl.h
@@ -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
+
+
diff --git a/engine/audio/private/snd_dev_wave.cpp b/engine/audio/private/snd_dev_wave.cpp
new file mode 100644
index 0000000..92cb439
--- /dev/null
+++ b/engine/audio/private/snd_dev_wave.cpp
@@ -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 );
+}
diff --git a/engine/audio/private/snd_dev_wave.h b/engine/audio/private/snd_dev_wave.h
new file mode 100644
index 0000000..582fc0b
--- /dev/null
+++ b/engine/audio/private/snd_dev_wave.h
@@ -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
diff --git a/engine/audio/private/snd_dev_xaudio.cpp b/engine/audio/private/snd_dev_xaudio.cpp
new file mode 100644
index 0000000..1601701
--- /dev/null
+++ b/engine/audio/private/snd_dev_xaudio.cpp
@@ -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] );
+ }
+ }
+} \ No newline at end of file
diff --git a/engine/audio/private/snd_dev_xaudio.h b/engine/audio/private/snd_dev_xaudio.h
new file mode 100644
index 0000000..83ec00b
--- /dev/null
+++ b/engine/audio/private/snd_dev_xaudio.h
@@ -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
diff --git a/engine/audio/private/snd_dma.cpp b/engine/audio/private/snd_dma.cpp
new file mode 100644
index 0000000..137bce7
--- /dev/null
+++ b/engine/audio/private/snd_dma.cpp
@@ -0,0 +1,8401 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Main control for any streaming sound output device.
+//
+//===========================================================================//
+
+#include "audio_pch.h"
+#include "const.h"
+#include "cdll_int.h"
+#include "client_class.h"
+#include "icliententitylist.h"
+#include "tier0/vcrmode.h"
+#include "con_nprint.h"
+#include "tier0/icommandline.h"
+#include "vox_private.h"
+#include "../../traceinit.h"
+#include "../../cmd.h"
+#include "toolframework/itoolframework.h"
+#include "vstdlib/random.h"
+#include "vstdlib/jobthread.h"
+#include "vaudio/ivaudio.h"
+#include "../../client.h"
+#include "../../cl_main.h"
+#include "utldict.h"
+#include "mempool.h"
+#include "../../enginetrace.h" // for traceline
+#include "../../public/bspflags.h" // for traceline
+#include "../../public/gametrace.h" // for traceline
+#include "vphysics_interface.h" // for surface props
+#include "../../ispatialpartitioninternal.h" // for entity enumerator
+#include "../../debugoverlay.h"
+#include "icliententity.h"
+#include "../../cmodel_engine.h"
+#include "../../staticpropmgr.h"
+#include "../../server.h"
+#include "edict.h"
+#include "../../pure_server.h"
+#include "filesystem/IQueuedLoader.h"
+#include "voice.h"
+#if defined( _X360 )
+#include "xbox/xbox_console.h"
+#include "xmp.h"
+#endif
+
+#include "replay/iclientreplaycontext.h"
+#include "replay/ireplaymovierenderer.h"
+
+#include "video/ivideoservices.h"
+extern IVideoServices *g_pVideo;
+
+/*
+#include "gl_model_private.h"
+#include "world.h"
+#include "vphysics_interface.h"
+#include "client_class.h"
+#include "server_class.h"
+*/
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+///////////////////////////////////
+// DEBUGGING
+//
+// Turn this on to print channel output msgs.
+//
+//#define DEBUG_CHANNELS
+
+#define SNDLVL_TO_DIST_MULT( sndlvl ) ( sndlvl ? ((pow( 10.0f, snd_refdb.GetFloat() / 20 ) / pow( 10.0f, (float)sndlvl / 20 )) / snd_refdist.GetFloat()) : 0 )
+#define DIST_MULT_TO_SNDLVL( dist_mult ) (soundlevel_t)(int)( dist_mult ? ( 20 * log10( pow( 10.0f, snd_refdb.GetFloat() / 20 ) / (dist_mult * snd_refdist.GetFloat()) ) ) : 0 )
+
+extern ConVar dsp_spatial;
+extern IPhysicsSurfaceProps *physprop;
+
+extern bool IsReplayRendering();
+
+static void S_Play( const CCommand &args );
+static void S_PlayVol( const CCommand &args );
+void S_SoundList(void);
+static void S_Say ( const CCommand &args );
+void S_Update_(float);
+void S_StopAllSounds(bool clear);
+void S_StopAllSoundsC(void);
+void S_ShutdownMixThread();
+const char *GetClientClassname( SoundSource soundsource );
+
+float SND_GetGainObscured( channel_t *ch, bool fplayersound, bool flooping, bool bAttenuated );
+void DSP_ChangePresetValue( int idsp, int channel, int iproc, float value );
+bool DSP_CheckDspAutoEnabled( void );
+void DSP_SetDspAuto( int dsp_preset );
+float dB_To_Radius ( float db );
+int dsp_room_GetInt ( void );
+
+bool MXR_LoadAllSoundMixers( void );
+void MXR_ReleaseMemory( void );
+int MXR_GetMixGroupListFromDirName( const char *pDirname, byte *pList, int listMax );
+void MXR_GetMixGroupFromSoundsource( channel_t *pchan, SoundSource soundsource, soundlevel_t soundlevel);
+float MXR_GetVolFromMixGroup( int rgmixgroupid[8], int *plast_mixgroupid );
+char *MXR_GetGroupnameFromId( int mixgroupid );
+int MXR_GetMixgroupFromName( const char *pszgroupname );
+void MXR_DebugShowMixVolumes( void );
+#ifdef _DEBUG
+static void MXR_DebugSetMixGroupVolume( const CCommand &args );
+#endif //_DEBUG
+void MXR_UpdateAllDuckerVolumes( void );
+
+void ChannelSetVolTargets( channel_t *pch, int *pvolumes, int ivol_offset, int cvol );
+void ChannelUpdateVolXfade( channel_t *pch );
+void ChannelClearVolumes( channel_t *pch );
+float VOX_GetChanVol(channel_t *ch);
+void ConvertListenerVectorTo2D( Vector *pvforward, Vector *pvright );
+int ChannelGetMaxVol( channel_t *pch );
+
+// Forceably ends voice tweak mode (only occurs during snd_restart
+void VoiceTweak_EndVoiceTweakMode();
+bool VoiceTweak_IsStillTweaking();
+// Only does anything for voice tweak channel so if view entity changes it doesn't fade out to zero volume
+void Voice_Spatialize( channel_t *channel );
+
+// =======================================================================
+// Internal sound data & structures
+// =======================================================================
+
+channel_t channels[MAX_CHANNELS];
+
+int total_channels;
+CActiveChannels g_ActiveChannels;
+static double g_LastSoundFrame = 0.0f; // last full frame of sound
+static double g_LastMixTime = 0.0f; // last time we did mixing
+static float g_EstFrameTime = 0.1f; // estimated frame time running average
+
+// x360 override to fade out game music when the user is playing music through the dashboard
+static float g_DashboardMusicMixValue = 1.0f;
+static float g_DashboardMusicMixTarget = 1.0f;
+const float g_DashboardMusicFadeRate = 0.5f; // Fades one half full-scale volume per second (two seconds for complete fadeout)
+
+// sound mixers
+int g_csoundmixers = 0; // total number of soundmixers found
+int g_cgrouprules = 0; // total number of group rules found
+int g_cgroupclass = 0;
+
+// this is used to enable/disable music playback on x360 when the user selects his own soundtrack to play
+void S_EnableMusic( bool bEnable )
+{
+ if ( bEnable )
+ {
+ g_DashboardMusicMixTarget = 1.0f;
+ }
+ else
+ {
+ g_DashboardMusicMixTarget = 0.0f;
+ }
+}
+
+bool IsSoundSourceLocalPlayer( int soundsource )
+{
+ if ( soundsource == SOUND_FROM_UI_PANEL )
+ return true;
+
+ return ( soundsource == g_pSoundServices->GetViewEntity() );
+}
+
+CThreadMutex g_SndMutex;
+
+#define THREAD_LOCK_SOUND() AUTO_LOCK( g_SndMutex )
+
+const int MASK_BLOCK_AUDIO = CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW;
+
+void CActiveChannels::Add( channel_t *pChannel )
+{
+ Assert( pChannel->activeIndex == 0 );
+ m_list[m_count] = pChannel - channels;
+ m_count++;
+ pChannel->activeIndex = m_count;
+}
+
+void CActiveChannels::Remove( channel_t *pChannel )
+{
+ if ( pChannel->activeIndex == 0 )
+ return;
+ int activeIndex = pChannel->activeIndex - 1;
+ Assert( activeIndex >= 0 && activeIndex < m_count );
+ Assert( pChannel == &channels[m_list[activeIndex]] );
+ m_count--;
+ // Not the last one? Swap the last one with this one and fix its index
+ if ( activeIndex < m_count )
+ {
+ m_list[activeIndex] = m_list[m_count];
+ channels[m_list[activeIndex]].activeIndex = activeIndex+1;
+ }
+ pChannel->activeIndex = 0;
+}
+
+
+void CActiveChannels::GetActiveChannels( CChannelList &list )
+{
+ list.m_count = m_count;
+ if ( m_count )
+ {
+ Q_memcpy( list.m_list, m_list, sizeof(m_list[0])*m_count );
+ }
+
+ for ( int i = SOUND_BUFFER_SPECIAL_START; i < g_paintBuffers.Count(); ++i )
+ {
+ paintbuffer_t *pSpecialBuffer = MIX_GetPPaintFromIPaint( i );
+ if ( pSpecialBuffer->nSpecialDSP != 0 )
+ {
+ list.m_nSpecialDSPs.AddToTail( pSpecialBuffer->nSpecialDSP );
+ }
+ }
+
+ list.m_hasSpeakerChannels = true;
+ list.m_has11kChannels = true;
+ list.m_has22kChannels = true;
+ list.m_has44kChannels = true;
+ list.m_hasDryChannels = true;
+}
+
+void CActiveChannels::Init()
+{
+ m_count = 0;
+}
+
+bool snd_initialized = false;
+
+Vector listener_origin;
+static Vector listener_forward;
+Vector listener_right;
+static Vector listener_up;
+static bool s_bIsListenerUnderwater;
+static vec_t sound_nominal_clip_dist=SOUND_NORMAL_CLIP_DIST;
+
+// @TODO (toml 05-08-02): put this somewhere more reasonable
+vec_t S_GetNominalClipDist()
+{
+ return sound_nominal_clip_dist;
+}
+
+int g_soundtime = 0; // sample PAIRS output since start
+int g_paintedtime = 0; // sample PAIRS mixed since start
+
+float g_ReplaySoundTimeFracAccumulator = 0.0f; // Used by replay
+
+float g_ClockSyncArray[NUM_CLOCK_SYNCS] = {0};
+int g_SoundClockPaintTime[NUM_CLOCK_SYNCS] = {0};
+
+// default 10ms
+ConVar snd_delay_sound_shift("snd_delay_sound_shift","0.01");
+// this forces the clock to resync on the next delayed/sync sound
+void S_SyncClockAdjust( clocksync_index_t syncIndex )
+{
+ g_ClockSyncArray[syncIndex] = 0;
+ g_SoundClockPaintTime[syncIndex] = 0;
+}
+
+float S_ComputeDelayForSoundtime( float soundtime, clocksync_index_t syncIndex )
+{
+ // reset clock and return 0
+ if ( g_ClockSyncArray[syncIndex] == 0 )
+ {
+ // Put the current time marker one tick back to impose a minimum delay on the first sample
+ // this shifts the drift over so the sounds are more likely to delay (rather than skip)
+ // over the burst
+ // NOTE: The first sound after a sync MUST have a non-zero delay for the delay channel
+ // detection logic to work (otherwise we keep resetting the clock)
+ g_ClockSyncArray[syncIndex] = soundtime - host_state.interval_per_tick;
+ g_SoundClockPaintTime[syncIndex] = g_paintedtime;
+ }
+
+ // how much time has passed in the game since we did a clock sync?
+ float gameDeltaTime = soundtime - g_ClockSyncArray[syncIndex];
+
+ // how many samples have been mixed since we did a clock sync?
+ int paintedSamples = g_paintedtime - g_SoundClockPaintTime[syncIndex];
+ int dmaSpeed = g_AudioDevice->DeviceDmaSpeed();
+ int gameSamples = (gameDeltaTime * dmaSpeed);
+ int delaySamples = gameSamples - paintedSamples;
+ float delay = delaySamples / float(dmaSpeed);
+
+ if ( gameDeltaTime < 0 || fabs(delay) > 0.500f )
+ {
+ // Note that the equations assume a correlation between game time and real time
+ // some kind of clock error. This can happen with large host_timescale or when the
+ // framerate hitches drastically (game time is a smaller clamped value wrt real time).
+ // The current sync estimate has probably drifted due to this or some other problem, recompute.
+ //Msg("Clock ERROR!: %.2f %.2f\n", gameDeltaTime, delay);
+ S_SyncClockAdjust(syncIndex);
+ return 0;
+ }
+ return delay + snd_delay_sound_shift.GetFloat();
+}
+
+static int s_buffers = 0;
+static int s_oldsampleOutCount = 0;
+static float s_lastsoundtime = 0.0f;
+
+bool s_bOnLoadScreen = false;
+
+static CClassMemoryPool< CSfxTable > s_SoundPool( MAX_SFX );
+struct SfxDictEntry
+{
+ CSfxTable *pSfx;
+};
+
+static CUtlMap< FileNameHandle_t, SfxDictEntry > s_Sounds( 0, 0, DefLessFunc( FileNameHandle_t ) );
+
+class CDummySfx : public CSfxTable
+{
+public:
+ virtual const char *getname()
+ {
+ return name;
+ }
+
+ void setname( const char *pName )
+ {
+ Q_strncpy( name, pName, sizeof( name ) );
+ OnNameChanged(name);
+ }
+
+private:
+ char name[MAX_PATH];
+};
+
+static CDummySfx dummySfx;
+
+// returns true if ok to procede with TraceRay calls
+bool SND_IsInGame( void )
+{
+ return cl.IsActive();
+}
+
+
+CSfxTable::CSfxTable()
+{
+ m_namePoolIndex = s_Sounds.InvalidIndex();
+ pSource = NULL;
+ m_bUseErrorFilename = false;
+ m_bIsUISound = false;
+ m_bIsLateLoad = false;
+ m_bMixGroupsCached = false;
+ m_pDebugName = NULL;
+}
+
+
+void CSfxTable::SetNamePoolIndex( int index )
+{
+ m_namePoolIndex = index;
+ if ( m_namePoolIndex != s_Sounds.InvalidIndex() )
+ {
+ OnNameChanged(getname());
+ }
+#ifdef _DEBUG
+ m_pDebugName = strdup( getname() );
+#endif
+}
+
+void CSfxTable::OnNameChanged( const char *pName )
+{
+ if ( pName && g_cgrouprules )
+ {
+ char szString[MAX_PATH];
+ Q_strncpy( szString, pName, sizeof(szString) );
+ Q_FixSlashes( szString, '/' );
+ m_mixGroupCount = MXR_GetMixGroupListFromDirName( szString, m_mixGroupList, ARRAYSIZE(m_mixGroupList) );
+ m_bMixGroupsCached = true;
+ }
+}
+//-----------------------------------------------------------------------------
+// Purpose: Wrapper for sfxtable->getname()
+// Output : char const
+//-----------------------------------------------------------------------------
+const char *CSfxTable::getname()
+{
+ if ( s_Sounds.InvalidIndex() != m_namePoolIndex )
+ {
+ char* pString = tmpstr512();
+ if ( g_pFileSystem )
+ g_pFileSystem->String( s_Sounds.Key( m_namePoolIndex ), pString, 512 );
+ else
+ {
+ pString[0] = 0;
+ }
+ return pString;
+ }
+
+ return NULL;
+}
+
+FileNameHandle_t CSfxTable::GetFileNameHandle()
+{
+ if ( s_Sounds.InvalidIndex() != m_namePoolIndex )
+ {
+ return s_Sounds.Key( m_namePoolIndex );
+ }
+ return NULL;
+}
+
+const char *CSfxTable::GetFileName()
+{
+ if ( IsX360() && m_bUseErrorFilename )
+ {
+ // Redirecting error sounds to a valid empty wave, prevents a bad loading retry pattern during gameplay
+ // which may event sounds skipped by preload, because they don't exist.
+ return "common/null.wav";
+ }
+
+ const char *pName = getname();
+ return pName ? PSkipSoundChars( pName ) : NULL;
+}
+
+bool CSfxTable::IsPrecachedSound()
+{
+ const char *pName = getname();
+
+ if ( sv.IsActive() )
+ {
+ // Server uses zero to mark invalid sounds
+ return sv.LookupSoundIndex( pName ) != 0 ? true : false;
+ }
+
+ // Client uses -1
+ // WE SHOULD FIX THIS!!!
+ return ( cl.LookupSoundIndex( pName ) != -1 ) ? true : false;
+}
+
+float g_DuckScale = 1.0f;
+
+// Structure used for fading in and out client sound volume.
+typedef struct
+{
+ float initial_percent;
+
+ // How far to adjust client's volume down by.
+ float percent;
+
+ // GetHostTime() when we started adjusting volume
+ float starttime;
+
+ // # of seconds to get to faded out state
+ float fadeouttime;
+ // # of seconds to hold
+ float holdtime;
+ // # of seconds to restore
+ float fadeintime;
+} soundfade_t;
+
+static soundfade_t soundfade; // Client sound fading singleton object
+
+// 0)headphones 2)stereo speakers 4)quad 5)5point1
+// autodetected from windows settings
+ConVar snd_surround( "snd_surround_speakers", "-1", FCVAR_INTERNAL_USE );
+ConVar snd_legacy_surround( "snd_legacy_surround", "0", FCVAR_ARCHIVE );
+ConVar snd_noextraupdate( "snd_noextraupdate", "0" );
+ConVar snd_show( "snd_show", "0", FCVAR_CHEAT, "Show sounds info" );
+ConVar snd_visualize ("snd_visualize", "0", FCVAR_CHEAT, "Show sounds location in world" );
+ConVar snd_pitchquality( "snd_pitchquality", "1", FCVAR_ARCHIVE ); // 1) use high quality pitch shifters
+
+// master volume
+static ConVar volume( "volume", "1.0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "Sound volume", true, 0.0f, true, 1.0f );
+// user configurable music volume
+ConVar snd_musicvolume( "snd_musicvolume", "1.0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "Music volume", true, 0.0f, true, 1.0f );
+
+ConVar snd_mixahead( "snd_mixahead", "0.1", FCVAR_ARCHIVE );
+ConVar snd_mix_async( "snd_mix_async", "0" );
+#ifdef _DEBUG
+static ConCommand snd_mixvol("snd_mixvol", MXR_DebugSetMixGroupVolume, "Set named Mixgroup to mix volume.");
+#endif
+
+// vaudio DLL
+IVAudio *vaudio = NULL;
+CSysModule *g_pVAudioModule = NULL;
+
+//-----------------------------------------------------------------------------
+// Resource loading for sound
+//-----------------------------------------------------------------------------
+class CResourcePreloadSound : public CResourcePreload
+{
+public:
+ CResourcePreloadSound() : m_SoundNames( 0, 0, true )
+ {
+ }
+
+ virtual bool CreateResource( const char *pName )
+ {
+ CSfxTable *pSfx = S_PrecacheSound( pName );
+ if ( !pSfx )
+ {
+ return false;
+ }
+
+ m_SoundNames.AddString( pSfx->GetFileName() );
+ return true;
+ }
+
+ virtual void PurgeUnreferencedResources()
+ {
+ bool bSpew = ( g_pQueuedLoader->GetSpewDetail() & LOADER_DETAIL_PURGES ) != 0;
+
+ for ( int i = s_Sounds.FirstInorder(); i != s_Sounds.InvalidIndex(); i = s_Sounds.NextInorder( i ) )
+ {
+ // the master sound table grows forever
+ // remove sound sources from the master sound table that were not in the preload list
+ CSfxTable *pSfx = s_Sounds[i].pSfx;
+ if ( pSfx && pSfx->pSource )
+ {
+ if ( pSfx->m_bIsUISound )
+ {
+ // never purge ui
+ continue;
+ }
+
+ UtlSymId_t symbol = m_SoundNames.Find( pSfx->GetFileName() );
+ if ( symbol == UTL_INVAL_SYMBOL )
+ {
+ // sound was not part of preload, purge it
+ if ( bSpew )
+ {
+ Msg( "CResourcePreloadSound: Purging: %s\n", pSfx->GetFileName() );
+ }
+
+ pSfx->pSource->CacheUnload();
+ delete pSfx->pSource;
+ pSfx->pSource = NULL;
+ }
+ }
+ }
+
+ m_SoundNames.RemoveAll();
+
+ if ( !g_pQueuedLoader->IsSameMapLoading() )
+ {
+ wavedatacache->Flush();
+ }
+ }
+
+ virtual void PurgeAll()
+ {
+ bool bSpew = ( g_pQueuedLoader->GetSpewDetail() & LOADER_DETAIL_PURGES ) != 0;
+
+ for ( int i = s_Sounds.FirstInorder(); i != s_Sounds.InvalidIndex(); i = s_Sounds.NextInorder( i ) )
+ {
+ // the master sound table grows forever
+ // remove sound sources from the master sound table that were not in the preload list
+ CSfxTable *pSfx = s_Sounds[i].pSfx;
+ if ( pSfx && pSfx->pSource )
+ {
+ if ( pSfx->m_bIsUISound )
+ {
+ // never purge ui
+ if ( bSpew )
+ {
+ Msg( "CResourcePreloadSound: Skipping: %s\n", pSfx->GetFileName() );
+ }
+ continue;
+ }
+
+ // sound was not part of preload, purge it
+ if ( bSpew )
+ {
+ Msg( "CResourcePreloadSound: Purging: %s\n", pSfx->GetFileName() );
+ }
+
+ pSfx->pSource->CacheUnload();
+ delete pSfx->pSource;
+ pSfx->pSource = NULL;
+ }
+ }
+
+ m_SoundNames.RemoveAll();
+
+ wavedatacache->Flush();
+ }
+
+private:
+ CUtlSymbolTable m_SoundNames;
+};
+static CResourcePreloadSound s_ResourcePreloadSound;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : float
+//-----------------------------------------------------------------------------
+float S_GetMasterVolume( void )
+{
+ float scale = 1.0f;
+ if ( soundfade.percent != 0 )
+ {
+ scale = clamp( (float)soundfade.percent / 100.0f, 0.0f, 1.0f );
+ scale = 1.0f - scale;
+ }
+ return volume.GetFloat() * scale;
+}
+
+
+void S_SoundInfo_f(void)
+{
+ if ( !g_AudioDevice->IsActive() )
+ {
+ Msg( "Sound system not started\n" );
+ return;
+ }
+
+ Msg( "Sound Device: %s\n", g_AudioDevice->DeviceName() );
+ Msg( " Channels: %d\n", g_AudioDevice->DeviceChannels() );
+ Msg( " Samples: %d\n", g_AudioDevice->DeviceSampleCount() );
+ Msg( " Bits/Sample: %d\n", g_AudioDevice->DeviceSampleBits() );
+ Msg( " Rate: %d\n", g_AudioDevice->DeviceDmaSpeed() );
+ Msg( "total_channels: %d\n", total_channels);
+
+ if ( IsX360() )
+ {
+ // dump a glimpse of the mixing state
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+
+ Msg( "\nActive Channels: (%d)\n", list.Count() );
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ channel_t *pChannel = list.GetChannel(i);
+ Msg( "%s (Mixer: 0x%p)\n", pChannel->sfx->GetFileName(), pChannel->pMixer );
+ }
+ }
+}
+
+
+/*
+================
+S_Startup
+================
+*/
+
+void S_Startup( void )
+{
+ if ( !snd_initialized )
+ return;
+
+ if ( !g_AudioDevice || g_AudioDevice == Audio_GetNullDevice() )
+ {
+ g_AudioDevice = IAudioDevice::AutoDetectInit( CommandLine()->CheckParm( "-wavonly" ) != 0 );
+ if ( !g_AudioDevice )
+ {
+ Error( "Unable to init audio" );
+ }
+ }
+}
+
+static ConCommand play("play", S_Play, "Play a sound.", FCVAR_SERVER_CAN_EXECUTE );
+static ConCommand playflush( "playflush", S_Play, "Play a sound, reloading from disk in case of changes." );
+static ConCommand playvol( "playvol", S_PlayVol, "Play a sound at a specified volume." );
+static ConCommand speak( "speak", S_Say, "Play a constructed sentence." );
+static ConCommand stopsound( "stopsound", S_StopAllSoundsC, 0, FCVAR_CHEAT); // Marked cheat because it gives an advantage to players minimising ambient noise.
+static ConCommand soundlist( "soundlist", S_SoundList, "List all known sounds." );
+static ConCommand soundinfo( "soundinfo", S_SoundInfo_f, "Describe the current sound device." );
+
+bool IsValidSampleRate( int rate )
+{
+ return rate == SOUND_11k || rate == SOUND_22k || rate == SOUND_44k;
+}
+
+void VAudioInit()
+{
+ if ( IsPC() )
+ {
+ if ( !IsPosix() )
+ {
+ // vaudio_miles.dll will load this...
+ g_pFileSystem->GetLocalCopy( "mss32.dll" );
+ }
+
+ g_pVAudioModule = FileSystem_LoadModule( "vaudio_miles" );
+ if ( g_pVAudioModule )
+ {
+ CreateInterfaceFn vaudioFactory = Sys_GetFactory( g_pVAudioModule );
+ vaudio = (IVAudio *)vaudioFactory( VAUDIO_INTERFACE_VERSION, NULL );
+ }
+ }
+}
+
+/*
+================
+S_Init
+================
+*/
+void S_Init( void )
+{
+ if ( sv.IsDedicated() && !CommandLine()->CheckParm( "-forcesound" ) )
+ return;
+
+ DevMsg( "Sound Initialization: Start\n" );
+
+ // KDB: init sentence array
+ TRACEINIT( VOX_Init(), VOX_Shutdown() );
+
+ VAudioInit();
+
+ if ( CommandLine()->CheckParm( "-nosound" ) )
+ {
+ g_AudioDevice = Audio_GetNullDevice();
+ TRACEINIT( audiosourcecache->Init( host_parms.memsize >> 2 ), audiosourcecache->Shutdown() );
+ return;
+ }
+
+ snd_initialized = true;
+
+ g_ActiveChannels.Init();
+ S_Startup();
+
+ MIX_InitAllPaintbuffers();
+
+ SND_InitScaletable();
+
+ MXR_LoadAllSoundMixers();
+
+ S_StopAllSounds( true );
+
+ TRACEINIT( audiosourcecache->Init( host_parms.memsize >> 2 ), audiosourcecache->Shutdown() );
+
+ AllocDsps( true );
+
+ if ( IsX360() )
+ {
+ g_pQueuedLoader->InstallLoader( RESOURCEPRELOAD_SOUND, &s_ResourcePreloadSound );
+ }
+
+ DevMsg( "Sound Initialization: Finish, Sampling Rate: %i\n", g_AudioDevice->DeviceDmaSpeed() );
+
+#ifdef _X360
+ BOOL bPlaybackControl;
+ // get initial state of the x360 media player
+ if ( XMPTitleHasPlaybackControl( &bPlaybackControl ) == ERROR_SUCCESS )
+ {
+ S_EnableMusic(bPlaybackControl!=0);
+ }
+
+ Assert( g_pVideo != NULL );
+
+ if ( g_pVideo != NULL )
+ {
+ if ( g_pVideo->SoundDeviceCommand( VideoSoundDeviceOperation::HOOK_X_AUDIO, NULL ) != VideoResult::SUCCESS )
+ {
+ Assert( 0 );
+ }
+ }
+#endif
+}
+
+
+// =======================================================================
+// Shutdown sound engine
+// =======================================================================
+void S_Shutdown(void)
+{
+#if !defined( _X360 )
+ if ( VoiceTweak_IsStillTweaking() )
+ {
+ VoiceTweak_EndVoiceTweakMode();
+ }
+#endif
+
+ S_StopAllSounds( true );
+ S_ShutdownMixThread();
+
+ TRACESHUTDOWN( audiosourcecache->Shutdown() );
+
+ SNDDMA_Shutdown();
+
+ for ( int i = s_Sounds.FirstInorder(); i != s_Sounds.InvalidIndex(); i = s_Sounds.NextInorder( i ) )
+ {
+ if ( s_Sounds[i].pSfx )
+ {
+ delete s_Sounds[i].pSfx->pSource;
+ s_Sounds[i].pSfx->pSource = NULL;
+ }
+ }
+ s_Sounds.RemoveAll();
+ s_SoundPool.Clear();
+
+ // release DSP resources
+ FreeDsps( true );
+
+ MXR_ReleaseMemory();
+
+ // release sentences resources
+ TRACESHUTDOWN( VOX_Shutdown() );
+
+ if ( IsPC() )
+ {
+ // shutdown vaudio
+ if ( vaudio )
+ delete vaudio;
+ FileSystem_UnloadModule( g_pVAudioModule );
+ g_pVAudioModule = NULL;
+ vaudio = NULL;
+ }
+
+ MIX_FreeAllPaintbuffers();
+ snd_initialized = false;
+ g_paintedtime = 0;
+ g_soundtime = 0;
+ g_ReplaySoundTimeFracAccumulator = 0.0f;
+ s_buffers = 0;
+ s_oldsampleOutCount = 0;
+ s_lastsoundtime = 0.0f;
+#if !defined( _X360 )
+ Voice_Deinit();
+#endif
+}
+
+bool S_IsInitted()
+{
+ return snd_initialized;
+}
+
+// =======================================================================
+// Load a sound
+// =======================================================================
+
+//-----------------------------------------------------------------------------
+// Return sfx and set pfInCache to 1 if
+// name is in name cache. Otherwise, alloc
+// a new spot in name cache and return 0
+// in pfInCache.
+//-----------------------------------------------------------------------------
+CSfxTable *S_FindName( const char *szName, int *pfInCache )
+{
+ int i;
+ CSfxTable *sfx = NULL;
+ char szBuff[MAX_PATH];
+ const char *pName;
+
+ if ( !szName )
+ {
+ Error( "S_FindName: NULL\n" );
+ }
+
+ pName = szName;
+ if ( IsX360() )
+ {
+ Q_strncpy( szBuff, pName, sizeof( szBuff ) );
+ int len = Q_strlen( szBuff )-4;
+ if ( len > 0 && !Q_strnicmp( szBuff+len, ".mp3", 4 ) )
+ {
+ // convert unsupported .mp3 to .wav
+ Q_strcpy( szBuff+len, ".wav" );
+ }
+ pName = szBuff;
+
+ if ( pName[0] == CHAR_STREAM )
+ {
+ // streaming (or not) is hardcoded to alternate criteria
+ // prevent the same sound from creating disparate instances
+ pName++;
+ }
+ }
+
+ // see if already loaded
+ FileNameHandle_t fnHandle = g_pFileSystem->FindOrAddFileName( pName );
+ i = s_Sounds.Find( fnHandle );
+ if ( i != s_Sounds.InvalidIndex() )
+ {
+ sfx = s_Sounds[i].pSfx;
+ Assert( sfx );
+ if ( pfInCache )
+ {
+ // indicate whether or not sound is currently in the cache.
+ *pfInCache = ( sfx->pSource && sfx->pSource->IsCached() ) ? 1 : 0;
+ }
+ return sfx;
+ }
+ else
+ {
+ SfxDictEntry entry;
+ entry.pSfx = ( CSfxTable * )s_SoundPool.Alloc();
+
+ Assert( entry.pSfx );
+
+ i = s_Sounds.Insert( fnHandle, entry );
+ sfx = s_Sounds[i].pSfx;
+
+ sfx->SetNamePoolIndex( i );
+ sfx->pSource = NULL;
+
+ if ( pfInCache )
+ {
+ *pfInCache = 0;
+ }
+ }
+ return sfx;
+}
+
+//-----------------------------------------------------------------------------
+// S_LoadSound
+//
+// Check to see if wave data is in the cache. If so, return pointer to data.
+// If not, allocate cache space for wave data, load wave file into temporary heap
+// space, and dump/convert file data into cache.
+//-----------------------------------------------------------------------------
+double g_flAccumulatedSoundLoadTime = 0.0f;
+CAudioSource *S_LoadSound( CSfxTable *pSfx, channel_t *ch )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ VPROF("S_LoadSound");
+ if ( !pSfx->pSource )
+ {
+ if ( IsX360() )
+ {
+ if ( SND_IsInGame() && !g_pQueuedLoader->IsMapLoading() )
+ {
+ // sound should be present (due to reslists), but NOT allowing a load hitch during gameplay
+ // loading a sound during gameplay is a bad experience, causes a very expensive sync i/o to fetch the header
+ // and in the case of a memory wave, the actual audio data
+ bool bFound = false;
+ if ( !pSfx->m_bIsLateLoad )
+ {
+ if ( pSfx->getname() != PSkipSoundChars( pSfx->getname() ) )
+ {
+ // the sound might already exist as an undecorated audio source
+ FileNameHandle_t fnHandle = g_pFileSystem->FindOrAddFileName( pSfx->GetFileName() );
+ int i = s_Sounds.Find( fnHandle );
+ if ( i != s_Sounds.InvalidIndex() )
+ {
+ CSfxTable *pOtherSfx = s_Sounds[i].pSfx;
+ Assert( pOtherSfx );
+ CAudioSource *pOtherSource = pOtherSfx->pSource;
+ if ( pOtherSource && pOtherSource->IsCached() )
+ {
+ // Can safely let the "load" continue because the headers are expected to be in the preload
+ // that are now persisted and the wave data cache will find an existing audio buffer match,
+ // so no sync i/o should occur from either.
+ bFound = true;
+ }
+ }
+ }
+
+ if ( !bFound )
+ {
+ // warn once
+ DevWarning( "S_LoadSound: Late load '%s', skipping.\n", pSfx->getname() );
+ pSfx->m_bIsLateLoad = true;
+ }
+ }
+
+ if ( !bFound )
+ {
+ return NULL;
+ }
+ }
+ else if ( pSfx->m_bIsLateLoad )
+ {
+ // outside of gameplay, let the load happen
+ pSfx->m_bIsLateLoad = false;
+ }
+ }
+
+ double st = Plat_FloatTime();
+
+ bool bStream = false;
+ bool bUserVox = false;
+
+ // sound chars can explicitly categorize usage
+ bStream = TestSoundChar( pSfx->getname(), CHAR_STREAM );
+ if ( !bStream )
+ {
+ bUserVox = TestSoundChar( pSfx->getname(), CHAR_USERVOX );
+ }
+
+ // override streaming
+ if ( IsX360() )
+ {
+ const char *s_CriticalSounds[] =
+ {
+ "common",
+ "items",
+ "physics",
+ "player",
+ "ui",
+ "weapons",
+ };
+
+ // stream everything but critical sounds
+ bStream = true;
+ const char *pFileName = pSfx->GetFileName();
+ for ( int i = 0; i<ARRAYSIZE( s_CriticalSounds ); i++ )
+ {
+ size_t len = strlen( s_CriticalSounds[i] );
+ if ( !Q_strnicmp( pFileName, s_CriticalSounds[i], len ) && ( pFileName[len] == '\\' || pFileName[len] == '/' ) )
+ {
+ // never stream these, regardless of sound chars
+ bStream = false;
+ break;
+ }
+ }
+ }
+
+ if ( bStream )
+ {
+ // setup as a streaming resource
+ pSfx->pSource = Audio_CreateStreamedWave( pSfx );
+ }
+ else
+ {
+ if ( bUserVox )
+ {
+ if ( !IsX360() )
+ {
+ pSfx->pSource = Voice_SetupAudioSource( ch->soundsource, ch->entchannel );
+ }
+ else
+ {
+ // not supporting
+ Assert( 0 );
+ }
+ }
+ else
+ {
+ // load all into memory directly
+ pSfx->pSource = Audio_CreateMemoryWave( pSfx );
+ }
+ }
+
+ double ed = Plat_FloatTime();
+ g_flAccumulatedSoundLoadTime += ( ed - st );
+ }
+ else
+ {
+ pSfx->pSource->CheckAudioSourceCache();
+ }
+
+ if ( !pSfx->pSource )
+ {
+ return NULL;
+ }
+
+ // first time to load? Create the mixer
+ if ( ch && !ch->pMixer )
+ {
+ ch->pMixer = pSfx->pSource->CreateMixer( ch->initialStreamPosition );
+ if ( !ch->pMixer )
+ {
+ return NULL;
+ }
+ }
+
+ return pSfx->pSource;
+}
+
+//-----------------------------------------------------------------------------
+// S_PrecacheSound
+//
+// Reserve space for the name of the sound in a global array.
+// Load the data for the non-streaming sound. Streaming sounds
+// defer loading of data until just before playback.
+//-----------------------------------------------------------------------------
+CSfxTable *S_PrecacheSound( const char *name )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ if ( !g_AudioDevice )
+ return NULL;
+
+ if ( !g_AudioDevice->IsActive() )
+ return NULL;
+
+ CSfxTable *sfx = S_FindName( name, NULL );
+ if ( sfx )
+ {
+ // cache sound
+ S_LoadSound( sfx, NULL );
+ }
+ else
+ {
+ Assert( !"S_PrecacheSound: Failed to create sfx" );
+ }
+
+ return sfx;
+}
+
+
+void S_InternalReloadSound( CSfxTable *sfx )
+{
+ if ( !sfx || !sfx->pSource )
+ return;
+
+ sfx->pSource->CacheUnload();
+
+ delete sfx->pSource;
+ sfx->pSource = NULL;
+
+ char pExt[10];
+ Q_ExtractFileExtension( sfx->getname(), pExt, sizeof(pExt) );
+ int nSource = !Q_stricmp( pExt, "mp3" ) ? CAudioSource::AUDIO_SOURCE_MP3 : CAudioSource::AUDIO_SOURCE_WAV;
+// audiosourcecache->RebuildCacheEntry( nSource, sfx->IsPrecachedSound(), sfx );
+ audiosourcecache->GetInfo( nSource, sfx->IsPrecachedSound(), sfx ); // Do a size/date check and rebuild the cache entry if necessary.
+}
+
+
+//-----------------------------------------------------------------------------
+// Refresh a sound in the cache
+//-----------------------------------------------------------------------------
+void S_ReloadSound( const char *name )
+{
+ if ( IsX360() )
+ {
+ // not supporting
+ Assert( 0 );
+ return;
+ }
+
+ if ( !g_AudioDevice )
+ return;
+
+ if ( !g_AudioDevice->IsActive() )
+ return;
+
+ CSfxTable *sfx = S_FindName( name, NULL );
+#ifdef _DEBUG
+ if ( sfx )
+ {
+ Assert( Q_stricmp( sfx->getname(), name ) == 0 );
+ }
+#endif
+
+ S_InternalReloadSound( sfx );
+}
+
+
+// See comments on CL_HandlePureServerWhitelist for details of what we're doing here.
+void S_ReloadFilesInList( IFileList *pFilesToReload )
+{
+ if ( !IsPC() )
+ return;
+
+ S_StopAllSounds( true );
+ wavedatacache->Flush();
+ audiosourcecache->ForceRecheckDiskInfo(); // Force all cached audio data to recheck size/date info next time it's accessed.
+
+
+ CUtlVector< CSfxTable * > processed;
+
+ int iLast = s_Sounds.LastInorder();
+ for ( int i = s_Sounds.FirstInorder(); i != iLast; i = s_Sounds.NextInorder( i ) )
+ {
+ FileNameHandle_t fnHandle = s_Sounds.Key( i );
+ char filename[MAX_PATH * 3];
+ if ( !g_pFileSystem->String( fnHandle, filename, sizeof( filename ) ) )
+ {
+ Assert( !"S_HandlePureServerWhitelist - can't get a filename." );
+ continue;
+ }
+
+ // If the file isn't cached in yet, then the filesystem hasn't touched its file, so don't bother.
+ CSfxTable *sfx = s_Sounds[i].pSfx;
+ if ( sfx && ( processed.Find( sfx ) == processed.InvalidIndex() ) )
+ {
+ char fullFilename[MAX_PATH*2];
+ if ( IsSoundChar( filename[0] ) )
+ Q_snprintf( fullFilename, sizeof( fullFilename ), "sound/%s", &filename[1] );
+ else
+ Q_snprintf( fullFilename, sizeof( fullFilename ), "sound/%s", filename );
+
+
+ if ( !pFilesToReload->IsFileInList( fullFilename ) )
+ continue;
+
+ processed.AddToTail( sfx );
+
+ S_InternalReloadSound( sfx );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Unfortunate confusing terminology.
+// Here prefetching means hinting to the audio source (which may be a stream)
+// to get its async data in flight.
+//-----------------------------------------------------------------------------
+void S_PrefetchSound( char const *name, bool bPlayOnce )
+{
+ CSfxTable *sfx;
+
+ if ( !g_AudioDevice )
+ return;
+
+ if ( !g_AudioDevice->IsActive() )
+ return;
+
+ sfx = S_FindName( name, NULL );
+ if ( sfx )
+ {
+ // cache sound
+ S_LoadSound( sfx, NULL );
+ }
+
+ if ( !sfx || !sfx->pSource )
+ {
+ return;
+ }
+
+ // hint the sound to start loading
+ sfx->pSource->Prefetch();
+
+ if ( bPlayOnce )
+ {
+ sfx->pSource->SetPlayOnce( true );
+ }
+}
+
+void S_MarkUISound( CSfxTable *pSfx )
+{
+ pSfx->m_bIsUISound = true;
+}
+
+unsigned int RemainingSamples( channel_t *pChannel )
+{
+ if ( !pChannel || !pChannel->sfx || !pChannel->sfx->pSource )
+ return 0;
+
+ unsigned int timeleft = pChannel->sfx->pSource->SampleCount();
+
+ if ( pChannel->sfx->pSource->IsLooped() )
+ {
+ return pChannel->sfx->pSource->SampleRate();
+ }
+
+ if ( pChannel->pMixer )
+ {
+ timeleft -= pChannel->pMixer->GetSamplePosition();
+ }
+
+ return timeleft;
+}
+
+// chooses the voice stealing algorithm
+ConVar voice_steal("voice_steal", "2");
+
+/*
+=================
+SND_StealDynamicChannel
+Select a channel from the dynamic channel allocation area. For the given entity,
+override any other sound playing on the same channel (see code comments below for
+exceptions).
+=================
+*/
+channel_t *SND_StealDynamicChannel(SoundSource soundsource, int entchannel, const Vector &origin, CSfxTable *sfx, float flDelay, bool bDoNotOverwriteExisting)
+{
+ int canSteal[MAX_DYNAMIC_CHANNELS];
+ int canStealCount = 0;
+
+ int sameSoundCount = 0;
+ unsigned int sameSoundRemaining = 0xFFFFFFFF;
+ int sameSoundIndex = -1;
+ int sameVol = 0xFFFF;
+ int availableChannel = -1;
+ bool bDelaySame = false;
+
+ int nExactMatch[MAX_DYNAMIC_CHANNELS];
+ int nExactCount = 0;
+ // first pass to replace sounds on same ent/channel, and search for free or stealable channels otherwise
+ for ( int ch_idx = 0; ch_idx < MAX_DYNAMIC_CHANNELS ; ch_idx++)
+ {
+ channel_t *ch = &channels[ch_idx];
+
+ if ( ch->activeIndex )
+ {
+ // channel CHAN_AUTO never overrides sounds on same channel
+ if ( entchannel != CHAN_AUTO )
+ {
+ int checkChannel = entchannel;
+ if ( checkChannel == -1 )
+ {
+ if ( ch->entchannel != CHAN_STREAM && ch->entchannel != CHAN_VOICE && ch->entchannel != CHAN_VOICE2 )
+ {
+ checkChannel = ch->entchannel;
+ }
+ }
+ if ( ch->soundsource == soundsource && (soundsource != -1) && ch->entchannel == checkChannel )
+ {
+ // we found an exact match for this entity and this channel, but the sound we want to play is considered
+ // low priority so instead of stomping this entry pretend we couldn't find a free slot to play and let
+ // the existing sound keep going
+ if ( bDoNotOverwriteExisting )
+ return NULL;
+
+ if ( ch->flags.delayed_start )
+ {
+ nExactMatch[nExactCount] = ch_idx;
+ nExactCount++;
+ continue;
+ }
+ return ch; // always override sound from same entity
+ }
+ }
+
+ // Never steal the channel of a streaming sound that is currently playing or
+ // voice over IP data that is playing or any sound on CHAN_VOICE( acting )
+ if ( ch->entchannel == CHAN_STREAM || ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_VOICE2 )
+ continue;
+
+ // don't let monster sounds override player sounds
+ if ( g_pSoundServices->IsPlayer( ch->soundsource ) && !g_pSoundServices->IsPlayer(soundsource) )
+ continue;
+
+ if ( ch->sfx == sfx )
+ {
+ bDelaySame = ch->flags.delayed_start ? true : bDelaySame;
+ sameSoundCount++;
+ int maxVolume = ChannelGetMaxVol( ch );
+ unsigned int remaining = RemainingSamples(ch);
+ if ( maxVolume < sameVol || (maxVolume == sameVol && remaining < sameSoundRemaining) )
+ {
+ sameSoundIndex = ch_idx;
+ sameVol = maxVolume;
+ sameSoundRemaining = remaining;
+ }
+ }
+ canSteal[canStealCount++] = ch_idx;
+ }
+ else
+ {
+ if ( availableChannel < 0 )
+ {
+ availableChannel = ch_idx;
+ }
+ }
+ }
+
+
+ // coalesce the timeline for this channel
+ if ( nExactCount > 0 )
+ {
+ uint nFreeSampleTime = g_paintedtime + (flDelay * SOUND_DMA_SPEED);
+ channel_t *pReturn = &channels[nExactMatch[0]];
+ uint nMinRemaining = RemainingSamples( pReturn );
+ if ( pReturn->nFreeChannelAtSampleTime == 0 || pReturn->nFreeChannelAtSampleTime > nFreeSampleTime )
+ {
+ pReturn->nFreeChannelAtSampleTime = nFreeSampleTime;
+ }
+ for ( int i = 1; i < nExactCount; i++ )
+ {
+ channel_t *pChannel = &channels[nExactMatch[i]];
+ if ( pChannel->nFreeChannelAtSampleTime == 0 || pChannel->nFreeChannelAtSampleTime > nFreeSampleTime )
+ {
+ pChannel->nFreeChannelAtSampleTime = nFreeSampleTime;
+ }
+ uint nRemain = RemainingSamples( pChannel );
+ if ( nRemain < nMinRemaining )
+ {
+ pReturn = pChannel;
+ nMinRemaining = nRemain;
+ }
+ }
+ // if there's only one, mark it to be freed but don't reuse it.
+ // otherwise mark all others to be freed and use the closest one to being done
+ if ( nExactCount > 1 )
+ {
+ return pReturn;
+ }
+ }
+
+ // Limit the number of times a given sfx/wave can play simultaneously
+ if ( voice_steal.GetInt() > 1 && sameSoundIndex >= 0 )
+ {
+ // if sounds of this type are normally delayed, then add an extra slot for stealing
+ // NOTE: In HL2 these are usually NPC gunshot sounds - and stealing too soon will cut
+ // them off early. This is a safe heuristic to avoid that problem. There's probably a better
+ // long-term solution involving only counting channels that are actually going to play (delay included)
+ // at the same time as this one.
+ int maxSameSounds = bDelaySame ? 5 : 4;
+ float distSqr = 0.0f;
+ if ( sfx->pSource )
+ {
+ distSqr = origin.DistToSqr(listener_origin);
+ if ( sfx->pSource->IsLooped() )
+ {
+ maxSameSounds = 3;
+ }
+ }
+
+ // don't play more than N copies of the same sound, steal the quietest & closest one otherwise
+ if ( sameSoundCount >= maxSameSounds )
+ {
+ channel_t *ch = &channels[sameSoundIndex];
+ // you're already playing a closer version of this sound, don't steal
+ if ( distSqr > 0.0f && ch->origin.DistToSqr(listener_origin) < distSqr && entchannel != CHAN_WEAPON )
+ return NULL;
+
+ //Msg("Sound playing %d copies, stole %s (%d)\n", sameSoundCount, ch->sfx->getname(), sameVol );
+ return ch;
+ }
+ }
+
+ // if there's a free channel, just take that one - don't steal
+ if ( availableChannel >= 0 )
+ return &channels[availableChannel];
+
+ // Still haven't found a suitable channel, so choose the one with the least amount of time left to play
+ float life_left = FLT_MAX;
+ int first_to_die = -1;
+ bool bAllowVoiceSteal = voice_steal.GetBool();
+
+ for ( int i = 0; i < canStealCount; i++ )
+ {
+ int ch_idx = canSteal[i];
+ channel_t *ch = &channels[ch_idx];
+ float timeleft = 0;
+ if ( bAllowVoiceSteal )
+ {
+ int maxVolume = ChannelGetMaxVol( ch );
+ if ( maxVolume < 5 )
+ {
+ //Msg("Sound quiet, stole %s for %s\n", ch->sfx->getname(), sfx->getname() );
+ return ch;
+ }
+
+ if ( ch->sfx && ch->sfx->pSource )
+ {
+ unsigned int sampleCount = RemainingSamples( ch );
+ timeleft = (float)sampleCount / (float)ch->sfx->pSource->SampleRate();
+ }
+ }
+ else
+ {
+ // UNDONE: Kill this when voice_steal 0,1,2 has been tested
+ // UNDONE: This is the old buggy code that we're trying to replace
+ if ( ch->sfx )
+ {
+ // basically steals the first one you come to
+ timeleft = 1; //ch->end - paintedtime
+ }
+ }
+
+ if ( timeleft < life_left )
+ {
+ life_left = timeleft;
+ first_to_die = ch_idx;
+ }
+ }
+ if ( first_to_die >= 0 )
+ {
+ //Msg("Stole %s, timeleft %d\n", channels[first_to_die].sfx->getname(), life_left );
+ return &channels[first_to_die];
+ }
+
+ return NULL;
+}
+
+channel_t *SND_PickDynamicChannel(SoundSource soundsource, int entchannel, const Vector &origin, CSfxTable *sfx, float flDelay, bool bDoNotOverwriteExisting)
+{
+ channel_t *pChannel = SND_StealDynamicChannel( soundsource, entchannel, origin, sfx, flDelay, bDoNotOverwriteExisting );
+ if ( !pChannel )
+ return NULL;
+
+ if (pChannel->sfx)
+ {
+ // Don't restart looping sounds for the same entity
+ CAudioSource *pSource = pChannel->sfx->pSource;
+ if ( pSource )
+ {
+ if ( pSource->IsLooped() )
+ {
+ if ( pChannel->soundsource == soundsource && pChannel->entchannel == entchannel && pChannel->sfx == sfx )
+ {
+ // same looping sound, same ent, same channel, don't restart the sound
+ return NULL;
+ }
+ }
+ }
+ // be sure and release previous channel
+ // if sentence.
+ // ("Stealing channel from %s\n", channels[first_to_die].sfx->getname() );
+ S_FreeChannel(pChannel);
+ }
+
+ return pChannel;
+}
+
+
+
+/*
+=====================
+SND_PickStaticChannel
+=====================
+Pick an empty channel from the static sound area, or allocate a new
+channel. Only fails if we're at max_channels (128!!!) or if
+we're trying to allocate a channel for a stream sound that is
+already playing.
+
+*/
+channel_t *SND_PickStaticChannel(int soundsource, CSfxTable *pSfx)
+{
+ int i;
+ channel_t *ch = NULL;
+
+ // Check for replacement sound, or find the best one to replace
+ for (i = MAX_DYNAMIC_CHANNELS; i<total_channels; i++)
+ if (channels[i].sfx == NULL)
+ break;
+
+ if (i < total_channels)
+ {
+ // reuse an empty static sound channel
+ ch = &channels[i];
+ }
+ else
+ {
+ // no empty slots, alloc a new static sound channel
+ if (total_channels == MAX_CHANNELS)
+ {
+ DevMsg ("total_channels == MAX_CHANNELS\n");
+ return NULL;
+ }
+
+ // get a channel for the static sound
+ ch = &channels[total_channels];
+ total_channels++;
+ }
+
+ return ch;
+}
+
+
+void S_SpatializeChannel( int pVolume[CCHANVOLUMES/2], int master_vol, const Vector *psourceDir, float gain, float mono )
+{
+ float lscale, rscale, scale;
+ vec_t dotRight;
+ Vector sourceDir = *psourceDir;
+
+ dotRight = DotProduct(listener_right, sourceDir);
+
+ // clear volumes
+ for (int i = 0; i < CCHANVOLUMES/2; i++)
+ pVolume[i] = 0;
+
+ if (mono > 0.0)
+ {
+ // sound has radius, within which spatialization becomes mono:
+
+ // mono is 0.0 -> 1.0, from radius 100% to radius 50%
+
+ // at radius * 0.5, dotRight is 0 (ie: sound centered left/right)
+ // at radius * 1.0, dotRight == dotRight
+
+ dotRight *= (1.0 - mono);
+ }
+
+ rscale = 1.0 + dotRight;
+ lscale = 1.0 - dotRight;
+
+ // add in distance effect
+ scale = gain * rscale / 2;
+ pVolume[IFRONT_RIGHT] = (int) (master_vol * scale);
+
+ scale = gain * lscale / 2;
+ pVolume[IFRONT_LEFT] = (int) (master_vol * scale);
+
+ pVolume[IFRONT_RIGHT] = clamp( pVolume[IFRONT_RIGHT], 0, 255 );
+ pVolume[IFRONT_LEFT] = clamp( pVolume[IFRONT_LEFT], 0, 255 );
+
+}
+
+bool S_IsMusic( channel_t *pChannel )
+{
+ if ( !pChannel->flags.bdry )
+ return false;
+
+ CSfxTable *sfx = pChannel->sfx;
+ if ( !sfx )
+ return false;
+
+ CAudioSource *source = sfx->pSource;
+ if ( !source )
+ return false;
+
+ // Don't save restore looping sounds as you can end up with an entity restarting them again and have
+ // them accumulate, etc.
+ if ( source->IsLooped() )
+ return false;
+
+ CAudioMixer *pMixer = pChannel->pMixer;
+ if ( !pMixer )
+ return false;
+
+ for ( int i = 0; i < 8; i++ )
+ {
+ if ( pChannel->mixgroups[i] != -1 )
+ {
+ char *pGroupName = MXR_GetGroupnameFromId( pChannel->mixgroups[i] );
+ if ( !Q_strcmp( pGroupName, "Music" ) )
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: For save/restore of currently playing music
+// Input : list -
+//-----------------------------------------------------------------------------
+void S_GetCurrentlyPlayingMusic( CUtlVector< musicsave_t >& musiclist )
+{
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ channel_t *pChannel = &channels[list.GetChannelIndex(i)];
+ if ( !S_IsMusic( pChannel ) )
+ continue;
+
+ musicsave_t song;
+ Q_strncpy( song.songname, pChannel->sfx->getname(), sizeof( song.songname ) );
+ song.sampleposition = pChannel->pMixer->GetPositionForSave();
+ song.master_volume = pChannel->master_vol;
+
+ musiclist.AddToTail( song );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *song -
+//-----------------------------------------------------------------------------
+void S_RestartSong( const musicsave_t *song )
+{
+ Assert( song );
+
+ // Start the song
+ CSfxTable *pSound = S_PrecacheSound( song->songname );
+ if ( pSound )
+ {
+ StartSoundParams_t params;
+ params.staticsound = true;
+ params.soundsource = SOUND_FROM_WORLD;
+ params.entchannel = CHAN_STATIC;
+ params.pSfx = pSound;
+ params.origin = vec3_origin;
+ params.fvol = ( (float)song->master_volume / 255.0f );
+ params.soundlevel = SNDLVL_NONE;
+ params.flags = SND_NOFLAGS;
+ params.pitch = PITCH_NORM;
+ params.initialStreamPosition = song->sampleposition;
+
+ S_StartSound( params );
+
+ if ( IsPC() )
+ {
+ // Now find the channel this went on and skip ahead in the mixer
+ for (int i = 0; i < total_channels; i++)
+ {
+ channel_t *ch = &channels[i];
+
+ if ( !ch->pMixer ||
+ !ch->pMixer->GetSource() )
+ {
+ continue;
+ }
+
+ if ( ch->pMixer->GetSource() != pSound->pSource )
+ {
+ continue;
+ }
+
+ ch->pMixer->SetPositionFromSaved( song->sampleposition );
+ break;
+ }
+ }
+ }
+}
+
+soundlevel_t SND_GetSndlvl ( channel_t *pchannel );
+
+// calculate ammount of sound to be mixed to dsp, based on distance from listener
+
+
+ConVar dsp_dist_min("dsp_dist_min", "0.0", FCVAR_DEMO|FCVAR_CHEAT); // range at which sounds are mixed at dsp_mix_min
+ConVar dsp_dist_max("dsp_dist_max", "1440.0", FCVAR_DEMO|FCVAR_CHEAT); // range at which sounds are mixed at dsp_mix_max
+
+ConVar dsp_mix_min("dsp_mix_min", "0.2", FCVAR_DEMO ); // dsp mix at dsp_dist_min distance "near"
+ConVar dsp_mix_max("dsp_mix_max", "0.8", FCVAR_DEMO ); // dsp mix at dsp_dist_max distance "far"
+ConVar dsp_db_min("dsp_db_min", "80", FCVAR_DEMO ); // sounds with sndlvl below this get dsp_db_mixdrop % less dsp mix
+ConVar dsp_db_mixdrop("dsp_db_mixdrop", "0.5", FCVAR_DEMO ); // sounds with sndlvl below dsp_db_min get dsp_db_mixdrop % less mix
+
+float DSP_ROOM_MIX = 1.0; // mix volume of dsp_room sounds when added back to 'dry' sounds
+float DSP_NOROOM_MIX = 1.0; // mix volume of facing + facing away sounds. added to dsp_room_mix sounds
+
+extern ConVar dsp_off;
+
+// returns 0-1.0 dsp mix value. If sound source is at a range >= DSP_DIST_MAX, return a mix value of
+// DSP_MIX_MAX. This mix value is used later to determine wet/dry mix ratio of sounds.
+
+// This ramp changes with db level of sound source, and is set in the dsp room presets by room size
+// empirical data: 0.78 is nominal mix for sound 100% at far end of room, 0.24 is mix for sound 25% into room
+
+float SND_GetDspMix( channel_t *pchannel, int idist)
+{
+ float mix;
+ float dist = (float)idist;
+ float dist_min = dsp_dist_min.GetFloat();
+ float dist_max = dsp_dist_max.GetFloat();
+ float mix_min;
+ float mix_max;
+
+ // only set dsp mix_min & mix_max when sound is first started
+
+ if ( pchannel->dsp_mix_min < 0 && pchannel->dsp_mix_max < 0 )
+ {
+ mix_min = dsp_mix_min.GetFloat(); // set via dsp_room preset
+ mix_max = dsp_mix_max.GetFloat(); // set via dsp_room preset
+
+ // set mix_min & mix_max based on db level of sound:
+ // sounds below dsp_db_min decrease dsp_mix_min & dsp_mix_max by N%
+ // ie: quiet sounds get less dsp mix than loud sounds
+
+ soundlevel_t sndlvl = SND_GetSndlvl( pchannel );
+ soundlevel_t sndlvl_min = (soundlevel_t)(dsp_db_min.GetInt());
+
+ if (sndlvl <= sndlvl_min)
+ {
+ mix_min *= dsp_db_mixdrop.GetFloat();
+ mix_max *= dsp_db_mixdrop.GetFloat();
+ }
+
+ pchannel->dsp_mix_min = mix_min;
+ pchannel->dsp_mix_max = mix_max;
+ }
+ else
+ {
+ mix_min = pchannel->dsp_mix_min;
+ mix_max = pchannel->dsp_mix_max;
+ }
+
+ // dspmix is 0 (100% mix to facing buffer) if dsp_off
+
+ if ( dsp_off.GetInt() )
+ return 0.0;
+
+ // doppler wavs are mixed dry
+
+ if ( pchannel->wavtype == CHAR_DOPPLER )
+ return 0.0;
+
+ // linear ramp - get dry mix %
+
+ // dist: 0->(max - min)
+
+ dist = clamp( dist, dist_min, dist_max ) - dist_min;
+
+ // dist: 0->1.0
+
+ dist = dist / (dist_max - dist_min);
+
+ // mix: min->max
+
+ mix = ((mix_max - mix_min) * dist) + mix_min;
+
+ return mix;
+}
+
+// calculate crossfade between wav left (close sound) and wav right (far sound) based on
+// distance fron listener
+
+#define DVAR_DIST_MIN (20.0 * 12.0) // play full 'near' sound at 20' or less
+#define DVAR_DIST_MAX (110.0 * 12.0) // play full 'far' sound at 110' or more
+#define DVAR_MIX_MIN 0.0
+#define DVAR_MIX_MAX 1.0
+
+// calculate mixing parameter for CHAR_DISTVAR wavs
+// returns 0 - 1.0, 1.0 is 100% far sound (wav right)
+
+float SND_GetDistanceMix( channel_t *pchannel, int idist)
+{
+ float mix;
+ float dist = (float)idist;
+
+ // doppler wavs are 100% near - their spatialization is calculated later.
+
+ if ( pchannel->wavtype == CHAR_DOPPLER )
+ return 0.0;
+
+ // linear ramp - get dry mix %
+
+ // dist 0->(max - min)
+
+ dist = clamp( dist, (float) DVAR_DIST_MIN, (float) DVAR_DIST_MAX ) - (float) DVAR_DIST_MIN;
+
+ // dist 0->1.0
+
+ dist = dist / (DVAR_DIST_MAX - DVAR_DIST_MIN);
+
+ // mix min->max
+
+ mix = ((DVAR_MIX_MAX - DVAR_MIX_MIN) * dist) + DVAR_MIX_MIN;
+
+ return mix;
+}
+
+// given facing direction of source, and channel,
+// return -1.0 - 1.0, where -1.0 is source facing away from listener
+// and 1.0 is source facing listener
+
+
+float SND_GetFacingDirection( channel_t *pChannel, const QAngle &source_angles )
+{
+ Vector SF; // sound source forward direction unit vector
+ Vector SL; // sound -> listener unit vector
+ float dotSFSL;
+
+ // no facing direction unless wavtyp CHAR_DIRECTIONAL
+
+ if ( pChannel->wavtype != CHAR_DIRECTIONAL )
+ return 1.0;
+
+ VectorSubtract(listener_origin, pChannel->origin, SL);
+ VectorNormalize(SL);
+
+ // compute forward vector for sound entity
+
+ AngleVectors( source_angles, &SF, NULL, NULL );
+
+ // dot source forward unit vector with source to listener unit vector to get -1.0 - 1.0 facing.
+ // ie: projection of SF onto SL
+
+ dotSFSL = DotProduct( SF, SL );
+
+ return dotSFSL;
+}
+
+// calculate point of closest approach - caller must ensure that the
+// forward facing vector of the entity playing this sound points in exactly the direction of
+// travel of the sound. ie: for bullets or tracers, forward vector must point in traceline direction.
+// return true if sound is to be played, false if sound cannot be heard (shot away from player)
+
+bool SND_GetClosestPoint( channel_t *pChannel, QAngle &source_angles, Vector &vnearpoint )
+{
+ // S - sound source origin
+ // L - listener origin
+
+ Vector SF; // sound source forward direction unit vector
+ Vector SL; // sound -> listener vector
+ Vector SD; // sound->closest point vector
+ vec_t dSLSF; // magnitude of project of SL onto SF
+
+ // P = SF (SF . SL) + S
+
+ // only perform this calculation for doppler wavs
+
+ if ( pChannel->wavtype != CHAR_DOPPLER )
+ return false;
+
+ // get vector 'SL' from sound source to listener
+
+ VectorSubtract(listener_origin, pChannel->origin, SL);
+
+ // compute sound->forward vector 'SF' for sound entity
+
+ AngleVectors( source_angles, &SF );
+ VectorNormalize( SF );
+
+ dSLSF = DotProduct( SL, SF );
+
+
+ if ( dSLSF <= 0 && !toolframework->IsToolRecording() )
+ {
+ // source is pointing away from listener, don't play anything
+ // unless we're recording in the tool, since we may play back from in front of the source
+ return false;
+ }
+
+ // project dSLSF along forward unit vector from sound source
+
+ VectorMultiply( SF, dSLSF, SD );
+
+ // output vector - add SD to sound source origin
+
+ VectorAdd( SD, pChannel->origin, vnearpoint );
+
+ return true;
+}
+
+
+// given point of nearest approach and sound source facing angles,
+// return vector pointing into quadrant in which to play
+// doppler left wav (incomming) and doppler right wav (outgoing).
+
+// doppler left is point in space to play left doppler wav
+// doppler right is point in space to play right doppler wav
+
+// Also modifies channel pitch based on distance to nearest approach point
+
+#define DOPPLER_DIST_LEFT_TO_RIGHT (4*12) // separate left/right sounds by 4'
+
+#define DOPPLER_DIST_MAX (20*12) // max distance - causes min pitch
+#define DOPPLER_DIST_MIN (1*12) // min distance - causes max pitch
+#define DOPPLER_PITCH_MAX 1.5 // max pitch change due to distance
+#define DOPPLER_PITCH_MIN 0.25 // min pitch change due to distance
+
+#define DOPPLER_RANGE_MAX (10*12) // don't play doppler wav unless within this range
+ // UNDONE: should be set by caller!
+
+void SND_GetDopplerPoints( channel_t *pChannel, QAngle &source_angles, Vector &vnearpoint, Vector &source_doppler_left, Vector &source_doppler_right)
+{
+ Vector SF; // direction sound source is facing (forward)
+ Vector LN; // vector from listener to closest approach point
+ Vector DL;
+ Vector DR;
+
+ // nearpoint is closest point of approach, when playing CHAR_DOPPLER sounds
+
+ // SF is normalized vector in direction sound source is facing
+
+ AngleVectors( source_angles, &SF );
+ VectorNormalize( SF );
+
+ // source_doppler_left - location in space to play doppler left wav (incomming)
+ // source_doppler_right - location in space to play doppler right wav (outgoing)
+
+ VectorMultiply( SF, -1*DOPPLER_DIST_LEFT_TO_RIGHT, DL );
+ VectorMultiply( SF, DOPPLER_DIST_LEFT_TO_RIGHT, DR );
+
+ VectorAdd( vnearpoint, DL, source_doppler_left );
+ VectorAdd( vnearpoint, DR, source_doppler_right );
+
+ // set pitch of channel based on nearest distance to listener
+
+ // LN is vector from listener to closest approach point
+
+ VectorSubtract(vnearpoint, listener_origin, LN);
+
+ float pitch;
+ float dist = VectorLength( LN );
+
+ // dist varies 0->1
+
+ dist = clamp(dist, (float)DOPPLER_DIST_MIN, (float)DOPPLER_DIST_MAX);
+ dist = (dist - DOPPLER_DIST_MIN) / (DOPPLER_DIST_MAX - DOPPLER_DIST_MIN);
+
+ // pitch varies from max to min
+
+ pitch = DOPPLER_PITCH_MAX - dist * (DOPPLER_PITCH_MAX - DOPPLER_PITCH_MIN);
+
+ pChannel->basePitch = (int)(pitch * 100.0);
+}
+
+// console variables used to construct gain curve - don't change these!
+
+extern ConVar snd_foliage_db_loss;
+extern ConVar snd_gain;
+extern ConVar snd_refdb;
+extern ConVar snd_refdist;
+extern ConVar snd_gain_max;
+extern ConVar snd_gain_min;
+
+ConVar snd_showstart( "snd_showstart", "0", FCVAR_CHEAT ); // showstart always skips info on player footsteps!
+ // 1 - show sound name, channel, volume, time
+ // 2 - show dspmix, distmix, dspface, l/r/f/r vols
+ // 3 - show sound origin coords
+ // 4 - show gain of dsp_room
+ // 5 - show dB loss due to obscured sound
+ // 6 - reserved
+ // 7 - show 2 and total gain & dist in ft. to sound source
+
+#define SND_DB_MAX 140.0 // max db of any sound source
+#define SND_DB_MED 90.0 // db at which compression curve changes
+#define SND_DB_MIN 60.0 // min db of any sound source
+
+#define SND_GAIN_PLAYER_WEAPON_DB 2.0 // increase player weapon gain by N dB
+
+// dB = 20 log (amplitude/32768) 0 to -90.3dB
+// amplitude = 32768 * 10 ^ (dB/20) 0 to +/- 32768
+// gain = amplitude/32768 0 to 1.0
+
+float Gain_To_dB ( float gain )
+{
+ float dB = 20 * log ( gain );
+ return dB;
+}
+
+float dB_To_Gain ( float dB )
+{
+ float gain = powf (10, dB / 20.0);
+ return gain;
+}
+
+float Gain_To_Amplitude ( float gain )
+{
+ return gain * 32768;
+}
+
+float Amplitude_To_Gain ( float amplitude )
+{
+ return amplitude / 32768;
+}
+
+soundlevel_t SND_GetSndlvl ( channel_t *pchannel )
+{
+ return DIST_MULT_TO_SNDLVL( pchannel->dist_mult );
+}
+
+
+// The complete gain calculation, with SNDLVL given in dB is:
+//
+// GAIN = 1/dist * snd_refdist * 10 ^ ( ( SNDLVL - snd_refdb - (dist * snd_foliage_db_loss / 1200)) / 20 )
+//
+// for gain > SND_GAIN_THRESH, start curve smoothing with
+//
+// GAIN = 1 - 1 / (Y * GAIN ^ SND_GAIN_POWER)
+//
+// where Y = -1 / ( (SND_GAIN_THRESH ^ SND_GAIN_POWER) * (SND_GAIN_THRESH - 1) )
+//
+
+float SND_GetGainFromMult( float gain, float dist_mult, vec_t dist );
+
+// gain curve construction
+
+float SND_GetGain( channel_t *ch, bool fplayersound, bool fmusicsound, bool flooping, vec_t dist, bool bAttenuated )
+{
+ VPROF_("SND_GetGain",2,VPROF_BUDGETGROUP_OTHER_SOUND,false,BUDGETFLAG_OTHER);
+ if ( ch->flags.m_bCompatibilityAttenuation )
+ {
+ // Convert to the original attenuation value.
+ soundlevel_t soundlevel = DIST_MULT_TO_SNDLVL( ch->dist_mult );
+ float flAttenuation = SNDLVL_TO_ATTN( soundlevel );
+
+ // Now get the goldsrc dist_mult and use the same calculation it uses in SND_Spatialize.
+ // Straight outta Goldsrc!!!
+ vec_t nominal_clip_dist = 1000.0;
+ float flGoldsrcDistMult = flAttenuation / nominal_clip_dist;
+ dist *= flGoldsrcDistMult;
+ float flReturnValue = 1.0f - dist;
+ flReturnValue = clamp( flReturnValue, 0.f, 1.f );
+ return flReturnValue;
+ }
+ else
+ {
+ float gain = snd_gain.GetFloat();
+
+ if ( fmusicsound )
+ {
+ gain = gain * snd_musicvolume.GetFloat();
+ gain = gain * g_DashboardMusicMixValue;
+ }
+
+ if ( ch->dist_mult )
+ {
+ gain = SND_GetGainFromMult( gain, ch->dist_mult, dist );
+ }
+
+ if ( fplayersound )
+ {
+
+ // player weapon sounds get extra gain - this compensates
+ // for npc distance effect weapons which mix louder as L+R into L,R
+ // Hack.
+
+ if ( ch->entchannel == CHAN_WEAPON )
+ gain = gain * dB_To_Gain( SND_GAIN_PLAYER_WEAPON_DB );
+ }
+
+ // modify gain if sound source not visible to player
+
+ gain = gain * SND_GetGainObscured( ch, fplayersound, flooping, bAttenuated );
+
+ if (snd_showstart.GetInt() == 6)
+ {
+ DevMsg( "(gain %1.3f : dist ft %1.1f) ", gain, (float)dist/12.0 );
+ snd_showstart.SetValue(5); // display once
+ }
+
+ return gain;
+ }
+}
+
+// always ramp channel gain changes over time
+// returns ramped gain, given new target gain
+
+#define SND_GAIN_FADE_TIME 0.25 // xfade seconds between obscuring gain changes
+
+float SND_FadeToNewGain( channel_t *ch, float gain_new )
+{
+
+ if ( gain_new == -1.0 )
+ {
+ // if -1 passed in, just keep fading to existing target
+
+ gain_new = ch->ob_gain_target;
+ }
+
+ // if first time updating, store new gain into gain & target, return
+ // if gain_new is close to existing gain, store new gain into gain & target, return
+
+ if ( ch->flags.bfirstpass || (fabs (gain_new - ch->ob_gain) < 0.01))
+ {
+ ch->ob_gain = gain_new;
+ ch->ob_gain_target = gain_new;
+ ch->ob_gain_inc = 0.0;
+ return gain_new;
+ }
+
+ // set up new increment to new target
+
+ float frametime = g_pSoundServices->GetHostFrametime();
+ float speed;
+ speed = ( frametime / SND_GAIN_FADE_TIME ) * (gain_new - ch->ob_gain);
+
+ ch->ob_gain_inc = fabs(speed);
+
+ // ch->ob_gain_inc = fabs(gain_new - ch->ob_gain) / 10.0;
+
+ ch->ob_gain_target = gain_new;
+
+ // if not hit target, keep approaching
+
+ if ( fabs( ch->ob_gain - ch->ob_gain_target ) > 0.01 )
+ {
+ ch->ob_gain = Approach( ch->ob_gain_target, ch->ob_gain, ch->ob_gain_inc );
+ }
+ else
+ {
+ // close enough, set gain = target
+ ch->ob_gain = ch->ob_gain_target;
+ }
+
+ return ch->ob_gain;
+}
+
+#define SND_TRACE_UPDATE_MAX 2 // max of N channels may be checked for obscured source per frame
+
+static int g_snd_trace_count = 0; // total tracelines for gain obscuring made this frame
+
+// All new sounds must traceline once,
+// but cap the max number of tracelines performed per frame
+// for longer or looping sounds to SND_TRACE_UPDATE_MAX.
+
+bool SND_ChannelOkToTrace( channel_t *ch )
+{
+ // always trace first time sound is spatialized (doesn't update counter)
+
+ if ( ch->flags.bfirstpass )
+ {
+ ch->flags.bTraced = true;
+ return true;
+ }
+
+ // if already traced max channels this frame, return
+
+ if ( g_snd_trace_count >= SND_TRACE_UPDATE_MAX )
+ return false;
+
+ // ok to trace if this sound hasn't yet been traced in this round
+
+ if ( ch->flags.bTraced )
+ return false;
+
+ // set flag - don't traceline this sound again until all others have
+ // been traced
+
+ ch->flags.bTraced = true;
+
+ g_snd_trace_count++; // total traces this frame
+
+ return true;
+}
+
+// determine if we need to reset all flags for traceline limiting -
+// this happens if we hit a frame whein no tracelines occur ie: all currently
+// playing sounds are blocked.
+
+void SND_ChannelTraceReset( void )
+{
+ if ( g_snd_trace_count )
+ return;
+
+ // if no tracelines performed this frame, then reset all
+ // trace flags
+
+ for (int i = 0; i < total_channels; i++)
+ channels[i].flags.bTraced = false;
+}
+
+bool SND_IsLongWave( channel_t *pChannel )
+{
+ CAudioSource *pSource = pChannel->sfx ? pChannel->sfx->pSource : NULL;
+ if ( pSource )
+ {
+ if ( pSource->IsStreaming() )
+ return true;
+
+ // UNDONE: Do this on long wave files too?
+#if 0
+ float length = (float)pSource->SampleCount() / (float)pSource->SampleRate();
+ if ( length > 0.75f )
+ return true;
+#endif
+ }
+
+ return false;
+}
+
+
+ConVar snd_obscured_gain_db( "snd_obscured_gain_dB", "-2.70", FCVAR_CHEAT ); // dB loss due to obscured sound source
+
+// drop gain on channel if sound emitter obscured by
+// world, unbroken windows, closed doors, large solid entities etc.
+
+float SND_GetGainObscured( channel_t *ch, bool fplayersound, bool flooping, bool bAttenuated )
+{
+ float gain = 1.0;
+ int count = 1;
+ float snd_gain_db; // dB loss due to obscured sound source
+
+ // Unattenuated sounds don't get obscured.
+ if ( !bAttenuated )
+ return 1.0f;
+
+ if ( fplayersound )
+ return gain;
+
+ // During signon just apply regular state machine since world hasn't been
+ // created or settled yet...
+
+ if ( !SND_IsInGame() )
+ {
+ if ( !toolframework->InToolMode() )
+ {
+ gain = SND_FadeToNewGain( ch, -1.0 );
+ }
+
+ return gain;
+ }
+
+ // don't do gain obscuring more than once on short one-shot sounds
+
+ if ( !ch->flags.bfirstpass && !ch->flags.isSentence && !flooping && !SND_IsLongWave(ch) )
+ {
+ gain = SND_FadeToNewGain( ch, -1.0 );
+ return gain;
+ }
+
+ snd_gain_db = snd_obscured_gain_db.GetFloat();
+
+ // if long or looping sound, process N channels per frame - set 'processed' flag, clear by
+ // cycling through all channels - this maintains a cap on traces per frame
+
+ if ( !SND_ChannelOkToTrace( ch ) )
+ {
+ // just keep updating fade to existing target gain - no new trace checking
+
+ gain = SND_FadeToNewGain( ch, -1.0 );
+ return gain;
+ }
+ // set up traceline from player eyes to sound emitting entity origin
+
+ Vector endpoint = ch->origin;
+
+ trace_t tr;
+ CTraceFilterWorldOnly filter; // UNDONE: also test for static props?
+ Ray_t ray;
+ ray.Init( MainViewOrigin(), endpoint );
+ g_pEngineTraceClient->TraceRay( ray, MASK_BLOCK_AUDIO, &filter, &tr );
+
+ if (tr.DidHit() && tr.fraction < 0.99)
+ {
+ // can't see center of sound source:
+ // build extents based on dB sndlvl of source,
+ // test to see how many extents are visible,
+ // drop gain by snd_gain_db per extent hidden
+
+ Vector endpoints[4];
+ soundlevel_t sndlvl = DIST_MULT_TO_SNDLVL( ch->dist_mult );
+ float radius;
+ Vector vsrc_forward;
+ Vector vsrc_right;
+ Vector vsrc_up;
+ Vector vecl;
+ Vector vecr;
+ Vector vecl2;
+ Vector vecr2;
+ int i;
+
+ // get radius
+
+ if ( ch->radius > 0 )
+ radius = ch->radius;
+ else
+ radius = dB_To_Radius( sndlvl); // approximate radius from soundlevel
+
+ // set up extent endpoints - on upward or downward diagonals, facing player
+
+ for (i = 0; i < 4; i++)
+ endpoints[i] = endpoint;
+
+ // vsrc_forward is normalized vector from sound source to listener
+
+ VectorSubtract( listener_origin, endpoint, vsrc_forward );
+ VectorNormalize( vsrc_forward );
+ VectorVectors( vsrc_forward, vsrc_right, vsrc_up );
+
+ VectorAdd( vsrc_up, vsrc_right, vecl );
+
+ // if src above listener, force 'up' vector to point down - create diagonals up & down
+
+ if ( endpoint.z > listener_origin.z + (10 * 12) )
+ vsrc_up.z = -vsrc_up.z;
+
+ VectorSubtract( vsrc_up, vsrc_right, vecr );
+ VectorNormalize( vecl );
+ VectorNormalize( vecr );
+
+ // get diagonal vectors from sound source
+
+ vecl2 = radius * vecl;
+ vecr2 = radius * vecr;
+ vecl = (radius / 2.0) * vecl;
+ vecr = (radius / 2.0) * vecr;
+
+ // endpoints from diagonal vectors
+
+ endpoints[0] += vecl;
+ endpoints[1] += vecr;
+ endpoints[2] += vecl2;
+ endpoints[3] += vecr2;
+
+ // drop gain for each point on radius diagonal that is obscured
+
+ for (count = 0, i = 0; i < 4; i++)
+ {
+ // UNDONE: some endpoints are in walls - in this case, trace from the wall hit location
+
+ ray.Init( MainViewOrigin(), endpoints[i] );
+ g_pEngineTraceClient->TraceRay( ray, MASK_BLOCK_AUDIO, &filter, &tr );
+
+ if (tr.DidHit() && tr.fraction < 0.99 && !tr.startsolid )
+ {
+ count++; // skip first obscured point: at least 2 points + center should be obscured to hear db loss
+ if (count > 1)
+ gain = gain * dB_To_Gain( snd_gain_db );
+ }
+ }
+ }
+
+
+ if ( flooping && snd_showstart.GetInt() == 7)
+ {
+ static float g_drop_prev = 0;
+ float drop = (count-1) * snd_gain_db;
+
+ if (drop != g_drop_prev)
+ {
+ DevMsg( "dB drop: %1.4f \n", drop);
+ g_drop_prev = drop;
+ }
+ }
+
+ // crossfade to new gain
+
+ gain = SND_FadeToNewGain( ch, gain );
+
+ return gain;
+}
+
+// convert sound db level to approximate sound source radius,
+// used only for determining how much of sound is obscured by world
+
+#define SND_RADIUS_MAX (20.0 * 12.0) // max sound source radius
+#define SND_RADIUS_MIN (2.0 * 12.0) // min sound source radius
+
+inline float dB_To_Radius ( float db )
+{
+ float radius = SND_RADIUS_MIN + (SND_RADIUS_MAX - SND_RADIUS_MIN) * (db - SND_DB_MIN) / (SND_DB_MAX - SND_DB_MIN);
+
+ return radius;
+}
+
+struct snd_spatial_t
+{
+ int chan; // 0..4 cycles through up to 5 channels
+ int cycle; // 0..2 cycles through 3 vectors per channel
+ int dist[5][3]; // stores last 3 channel distance values [channel][cycle]
+
+ float value_prev[5]; // previous value per channel
+
+ double last_change;
+};
+
+bool g_ssp_init = false;
+snd_spatial_t g_ssp;
+
+// return 0..1 percent difference between a & b
+
+float PercentDifference( float a, float b )
+{
+ float vp;
+
+ if (!(int)a && !(int)b)
+ return 0.0;
+
+ if (!(int)a || !(int)b)
+ return 1.0;
+
+ if (a > b)
+ vp = b / a;
+ else
+ vp = a / b;
+
+ return (1.0 - vp);
+}
+
+// NOTE: Do not change SND_WALL_TRACE_LEN without also changing PRC_MDY6 delay value in snd_dsp.cpp!
+
+#define SND_WALL_TRACE_LEN (100.0*12.0) // trace max of 100' = max of 100 milliseconds of linear delay
+#define SND_SPATIAL_WAIT (0.25) // seconds to wait between traces
+
+// change mod delay value on chan 0..3 to v (inches)
+
+void DSP_SetSpatialDelay( int chan, float v )
+{
+ // remap delay value 0..1200 to 1.0 to -1.0 for modulation
+
+ float value = ( v / SND_WALL_TRACE_LEN) - 1.0; // -1.0...0
+ value = value * 2.0; // -2.0...0
+ value += 1.0; // -1.0...1.0 (0...1200)
+ value *= -1.0; // 1.0...-1.0 (0...1200)
+
+ // assume first processor in dsp_spatial is the modulating delay unit for DSP_ChangePresetValue
+
+ int iproc = 0;
+
+ DSP_ChangePresetValue( idsp_spatial, chan, iproc, value );
+/*
+
+ if (chan & 0x01)
+ DevMsg("RDly: %3.0f \n", v/12 );
+ else
+ DevMsg("LDly: %3.0f \n", v/12 );
+*/
+}
+
+// use non-feedback delay to stereoize (or make quad, or quad + center) the mono dsp_room fx,
+// This simulates the average sum of delays caused by reflections
+// from the left and right walls relative to the player. The average delay
+// difference between left & right wall is (l + r)/2. This becomes the average
+// delay difference between left & right ear.
+// call at most once per frame to update player->wall spatial delays
+
+void SND_SetSpatialDelays()
+{
+ VPROF("SoundSpatialDelays");
+ float dist, v, vp;
+ Vector v_dir, v_dir2;
+ int chan_max = (g_AudioDevice->IsSurround() ? 4 : 2) + (g_AudioDevice->IsSurroundCenter() ? 1 : 0); // 2, 4, 5 channels
+
+ // use listener_forward2d, which doesn't change when player looks up/down.
+
+ Vector listener_forward2d;
+
+ ConvertListenerVectorTo2D( &listener_forward2d, &listener_right );
+
+ // init struct if 1st time through
+
+ if ( !g_ssp_init )
+ {
+ Q_memset(&g_ssp, 0, sizeof(snd_spatial_t));
+ g_ssp_init = true;
+ }
+
+ // return if dsp_spatial is 0
+
+ if ( !dsp_spatial.GetInt() )
+ return;
+
+ // if listener has not been updated, do nothing
+
+ if ((listener_origin == vec3_origin) &&
+ (listener_forward == vec3_origin) &&
+ (listener_right == vec3_origin) &&
+ (listener_up == vec3_origin) )
+ return;
+
+ if ( !SND_IsInGame() )
+ return;
+
+ // get time
+
+ double dtime = g_pSoundServices->GetHostTime();
+
+ // compare to previous time - if starting new check - don't check for new room until timer expires
+
+ if (!g_ssp.chan && !g_ssp.cycle)
+ {
+ if (fabs(dtime - g_ssp.last_change) < SND_SPATIAL_WAIT)
+ return;
+ }
+
+ // cycle through forward, left, rearward vectors, averaging to get left/right delay
+ // count[chan][cycle] 0,1 0,2 0,3 1,1 1,2 1,3 2,1 2,2 2,3 ...
+
+ g_ssp.cycle++;
+
+ if (g_ssp.cycle == 3)
+ {
+ g_ssp.cycle = 0;
+
+ // cycle through front left, front right, rear left, rear right, front center delays
+
+ g_ssp.chan++;
+
+ if (g_ssp.chan >= chan_max )
+ g_ssp.chan = 0;
+ }
+
+ // set up traceline from player eyes to surrounding walls
+
+ switch( g_ssp.chan )
+ {
+ default:
+ case 0: // front left: trace max 100' 'cone' to player's left
+ if ( g_AudioDevice->IsSurround() )
+ {
+ // 4-5 speaker case - front left
+ v_dir = (-listener_right + listener_forward2d) / 2.0;
+ v_dir = g_ssp.cycle ? (g_ssp.cycle == 1 ? -listener_right * 0.5: listener_forward2d * 0.5) : v_dir;
+ }
+ else
+ {
+ // 2 speaker case - left
+ v_dir = listener_right * -1.0;
+ v_dir2 = g_ssp.cycle ? (g_ssp.cycle == 1 ? listener_forward2d * 0.5 : -listener_forward2d * 0.5) : v_dir;
+ v_dir = (v_dir + v_dir2) / 2.0;
+ }
+ break;
+
+ case 1: // front right: trace max 100' 'cone' to player's right
+ if ( g_AudioDevice->IsSurround() )
+ {
+ // 4-5 speaker case - front right
+ v_dir = (listener_right + listener_forward2d) / 2.0;
+ v_dir = g_ssp.cycle ? (g_ssp.cycle == 1 ? listener_right * 0.5: listener_forward2d * 0.5) : v_dir;
+ }
+ else
+ {
+ // 2 speaker case - right
+ v_dir = listener_right;
+ v_dir2 = g_ssp.cycle ? (g_ssp.cycle == 1 ? listener_forward2d * 0.5 : -listener_forward2d * 0.5) : v_dir;
+ v_dir = (v_dir + v_dir2) / 2.0;
+ }
+ break;
+
+ case 2: // rear left: trace max 100' 'cone' to player's rear left
+ v_dir = (listener_right + listener_forward2d) / -2.0;
+ v_dir = g_ssp.cycle ? (g_ssp.cycle == 1 ? -listener_right * 0.5 : -listener_forward2d * 0.5) : v_dir;
+ break;
+
+ case 3: // rear right: trace max 100' 'cone' to player's rear right
+ v_dir = (listener_right - listener_forward2d) / 2.0;
+ v_dir = g_ssp.cycle ? (g_ssp.cycle == 1 ? listener_right * 0.5: -listener_forward2d * 0.5) : v_dir;
+ break;
+
+ case 4: // front center: trace max 100' 'cone' to player's front
+ v_dir = listener_forward2d;
+ v_dir2 = g_ssp.cycle ? (g_ssp.cycle == 1 ? listener_right * 0.15 : -listener_right * 0.15) : v_dir;
+ v_dir = (v_dir + v_dir2);
+ break;
+ }
+
+ Vector endpoint;
+ trace_t tr;
+ CTraceFilterWorldOnly filter;
+
+ endpoint = MainViewOrigin() + v_dir * SND_WALL_TRACE_LEN;
+ Ray_t ray;
+ ray.Init( MainViewOrigin(), endpoint );
+ g_pEngineTraceClient->TraceRay( ray, MASK_BLOCK_AUDIO, &filter, &tr );
+
+ dist = SND_WALL_TRACE_LEN;
+
+ if ( tr.DidHit() )
+ {
+ dist = VectorLength( tr.endpos - MainViewOrigin() );
+ }
+
+ g_ssp.dist[g_ssp.chan][g_ssp.cycle] = dist;
+
+ // set new result in dsp_spatial delay params when all delay values have been filled in
+
+ if (!g_ssp.cycle && !g_ssp.chan)
+ {
+ // update delay for each channel
+
+ for (int chan = 0; chan < chan_max; chan++)
+ {
+ // compute average of 3 traces per channel
+
+ v = (g_ssp.dist[chan][0] + g_ssp.dist[chan][1] + g_ssp.dist[chan][2]) / 3.0;
+ vp = g_ssp.value_prev[chan];
+
+ // only change if 10% difference from previous
+
+ if ((vp != v) && int(v) && (PercentDifference( v, vp ) >= 0.1))
+ {
+ // update when we have data for all L/R && RL/RR channels...
+
+ if (chan & 0x1)
+ {
+ float vr = fpmin( v, (50*12.0f) );
+ float vl = fpmin(g_ssp.value_prev[chan-1], (50*12.0f));
+
+/* UNDONE: not needed, now that this applies only to dsp 'room' buffer
+
+ // ensure minimum separation = average distance to walls
+
+ float dmin = (vl + vr) / 2.0; // average distance to walls
+ float d = vl - vr; // l/r separation
+
+ // if separation is less than average, increase min
+
+ if (abs(d) < dmin/2)
+ {
+ if (vl > vr)
+ vl += dmin/2 - d;
+ else
+ vr += dmin/2 - d;
+ }
+*/
+ DSP_SetSpatialDelay(chan-1, vl);
+ DSP_SetSpatialDelay(chan, vr);
+ }
+
+ // update center chan
+
+ if (chan == 4)
+ {
+ float vl = fpmin( v, (50*12.0f) );
+ DSP_SetSpatialDelay(chan, vl);
+ }
+ }
+
+ g_ssp.value_prev[chan] = v;
+
+ }
+
+ // update wait timer now that all values have been checked
+
+ g_ssp.last_change = dtime;
+ }
+}
+
+// Dsp Automatic Selection:
+
+// a) enabled by setting dsp_room to DSP_AUTOMATIC. Subsequently, dsp_automatic is the actual dsp value for dsp_room.
+// b) disabled by setting dsp_room to anything else
+
+// c) while enabled, detection nodes are placed as player moves into a new space
+// i. at each node, a new dsp setting is calculated and dsp_automatic is set to an appropriate preset
+// ii. new nodes are set when player moves out of sight of previous node
+// iii. moving into line of sight of a detection node causes closest node to player to set dsp_automatic
+
+// see void DAS_CheckNewRoomDSP( ) for main entrypoint
+
+ConVar das_debug( "adsp_debug", "0", FCVAR_ARCHIVE );
+ // >0: draw blue dsp detection node location
+ // >1: draw green room trace height detection bars
+ // 3: draw yellow horizontal trace bars for room width/depth detection
+ // 4: draw yellow upward traces for height detection
+ // 5: draw teal box around all props around player
+ // 6: draw teal box around room as detected
+
+#define DAS_CWALLS 20 // # of wall traces to save for calculating room dimensions
+#define DAS_ROOM_TRACE_LEN (400.0*12.0) // max size of trace to check for room dimensions
+
+#define DAS_AUTO_WAIT 0.25 // wait min of n seconds between dsp_room changes and update checks
+
+#define DAS_WIDTH_MIN 0.4 // min % change in avg width of any wall pair to cause new dsp
+#define DAS_REFL_MIN 0.5 // min % change in avg refl of any wall to cause new dsp
+#define DAS_SKYHIT_MIN 0.8 // min % change in # of sky hits per wall
+
+#define DAS_DIST_MIN (4.0 * 12.0) // min distance between room dsp changes
+#define DAS_DIST_MAX (40.0 * 12.0) // max distance to preserve room dsp changes
+
+#define DAS_DIST_MIN_OUTSIDE (6.0 * 12.0) // min distance between room dsp changes outside
+#define DAS_DIST_MAX_OUTSIDE (100.0 * 12.0) // max distance to preserve room dsp changes outside
+
+#define IVEC_DIAG_UP 8 // start of diagonal up vectors
+#define IVEC_UP 18 // up vector
+#define IVEC_DOWN 19 // down vector
+
+#define DAS_REFLECTIVITY_NORM 0.5
+#define DAS_REFLECTIVITY_SKY 0.0
+
+// auto dsp room struct
+
+struct das_room_t
+{
+ int dist[DAS_CWALLS]; // distance in units from player to axis aligned and diagonal walls
+ float reflect[DAS_CWALLS]; // acoustic reflectivity per wall
+ float skyhits[DAS_CWALLS]; // every sky hit adds 0.1
+ Vector hit[DAS_CWALLS]; // location of trace hit on wall - used for calculating average centers
+ Vector norm[DAS_CWALLS]; // wall normal at hit location
+
+ Vector vplayer; // 'frozen' location above player's head
+
+ Vector vplayer_eyes; // 'frozen' location player's eyes
+
+ int width_max; // max width
+ int length_max; // max length
+ int height_max; // max height
+
+ float refl_avg; // running average of reflectivity of all walls
+ float refl_walls[6]; // left,right,front,back,ceiling,floor reflectivities
+
+ float sky_pct; // percent of sky hits
+
+ Vector room_mins; // room bounds
+ Vector room_maxs;
+
+ double last_dsp_change; // time since last dsp change
+
+ float diffusion; // 0..1.0 check radius (avg of width_avg) for # of props - scale diffusion based on # found
+ short iwall; // cycles through walls 0..5, ensuring only one trace per frame
+ short ent_count; // count of entities found in radius
+ bool bskyabove; // true if sky found above player (ie: outside)
+ bool broomready; // true if all distances are filled in and room is ready to check
+ short lowceiling; // if non-zero, ceiling directly above player if < 112 units
+};
+
+// dsp detection node
+
+struct das_node_t
+{
+ Vector vplayer; // position
+
+ bool fused; // true if valid node
+ bool fseesplayer; // true if node sees player on last check
+ short dsp_preset; // preset
+
+ int range_min; // min,max detection ranges
+ int range_max;
+
+ int dist; // last distance to player
+
+ // room parameters when node was created:
+
+ das_room_t room;
+};
+
+#define DAS_CNODES 40 // keep around last n nodes - must be same as DSP_CAUTO_PRESETS!!!
+
+das_node_t g_das_nodes[DAS_CNODES]; // all dsp detection nodes
+das_node_t *g_pdas_last_node = NULL; // last node that saw player
+
+int g_das_check_next; // next node to check
+int g_das_store_next; // next place to store node
+bool g_das_all_checked; // true if all nodes checked
+int g_das_checked_count; // count of nodes checked in latest pass
+
+das_room_t g_das_room; // room detector
+
+bool g_bdas_room_init = 0;
+bool g_bdas_init_nodes = 0;
+bool g_bdas_create_new_node = 0;
+
+bool DAS_TraceNodeToPlayer( das_room_t *proom, das_node_t *pnode );
+void DAS_InitAutoRoom( das_room_t *proom);
+void DAS_DebugDrawTrace ( trace_t *ptr, int r, int g, int b, float duration, int imax );
+
+Vector g_das_vec3[DAS_CWALLS]; // trace vectors to walls, ceiling, floor
+
+void DAS_InitNodes( void )
+{
+ Q_memset(g_das_nodes, 0, sizeof(das_node_t) * DAS_CNODES);
+ g_das_check_next = 0;
+ g_das_store_next = 0;
+ g_das_all_checked = 0;
+ g_das_checked_count = 0;
+
+ // init all rooms
+
+ for (int i = 0; i < DAS_CNODES; i++)
+ DAS_InitAutoRoom( &(g_das_nodes[i].room) );
+
+ // init trace vectors
+ // set up trace vectors for max, min width
+ float vl = DAS_ROOM_TRACE_LEN;
+ float vlu = DAS_ROOM_TRACE_LEN * 0.52;
+ float vlu2 = DAS_ROOM_TRACE_LEN * 0.48; // don't use 'perfect' diagonals
+
+ g_das_vec3[0].Init(vl, 0.0, 0.0); // x left
+ g_das_vec3[1].Init(-vl, 0.0, 0.0); // x right
+
+ g_das_vec3[2].Init(0.0, vl, 0.0); // y front
+ g_das_vec3[3].Init(0.0, -vl, 0.0); // y back
+
+ g_das_vec3[4].Init(-vlu, vlu2, 0.0); // diagonal front left
+ g_das_vec3[5].Init(vlu, -vlu2, 0.0); // diagonal rear right
+
+ g_das_vec3[6].Init(vlu, vlu2, 0.0); // diagonal front right
+ g_das_vec3[7].Init(-vlu, -vlu2, 0.0); // diagonal rear left
+
+ // set up trace vectors for max height - on x=y diagonal
+
+ g_das_vec3[8].Init(vlu, vlu2, vlu/2.0); // front right up A x,y,z/2 (IVEC_DIAG_UP)
+ g_das_vec3[9].Init(vlu, vlu2, vlu); // front right up B x,y,z
+ g_das_vec3[10].Init(vlu/2.0, vlu2/2.0, vlu); // front right up C x/2,y/2,z
+
+ g_das_vec3[11].Init(-vlu, -vlu2, vlu/2.0); // rear left up A -x,-y,z/2
+ g_das_vec3[12].Init(-vlu, -vlu2, vlu); // rear left up B -x,-y,z
+ g_das_vec3[13].Init(-vlu/2.0, -vlu2/2.0, vlu); // rear left up C -x/2,-y/2,z
+
+ // set up trace vectors for max height - on x axis & y axis
+
+ g_das_vec3[14].Init(-vlu, 0, vlu); // left up B -x,0,z
+ g_das_vec3[15].Init(0, vlu/2.0, vlu); // front up C -x/2,0,z
+
+ g_das_vec3[16].Init(0, -vlu, vlu); // rear up B x,0,z
+ g_das_vec3[17].Init(vlu/2.0, 0, vlu); // right up C x/2,0,z
+
+ g_das_vec3[18].Init(0.0, 0.0, vl); // up (IVEC_UP)
+ g_das_vec3[19].Init(0.0, 0.0, -vl); // down (IVEC_DOWN)
+}
+
+void DAS_InitAutoRoom( das_room_t *proom)
+{
+ Q_memset(proom, 0, sizeof (das_room_t));
+}
+
+// reset all nodes for next round of visibility checks between player & nodes
+
+void DAS_ResetNodes( void )
+{
+ for (int i = 0; i < DAS_CNODES; i++)
+ {
+ g_das_nodes[i].fseesplayer = false;
+ g_das_nodes[i].dist = 0;
+ }
+
+ g_das_all_checked = false;
+ g_das_checked_count = 0;
+ g_bdas_create_new_node = false;
+}
+
+// utility function - return next index, wrap at max
+
+int DAS_GetNextIndex( int *pindex, int max )
+{
+ int i = *pindex;
+ int j;
+
+ j = i+1;
+ if ( j >= max )
+ j = 0;
+
+ *pindex = j;
+
+ return i;
+}
+
+// returns true if dsp node is within range of player
+
+bool DAS_NodeInRange( das_room_t *proom, das_node_t *pnode )
+{
+ float dist;
+
+ dist = VectorLength( proom->vplayer - pnode->vplayer );
+
+ // player can still see previous room selection point, and it's less than n feet away,
+ // then flag this node as visible
+
+ pnode->dist = dist;
+
+ return ( dist <= pnode->range_max );
+}
+
+// update next valid node - set up internal node state if it can see player
+// called once per frame
+// returns true if all nodes have been checked
+
+bool DAS_CheckNextNode( das_room_t *proom )
+{
+ int i, j;
+
+ if ( g_das_all_checked )
+ return true;
+
+ // find next valid node
+
+ for (j = 0; j < DAS_CNODES; j++)
+ {
+ // track number of nodes checked
+
+ g_das_checked_count++;
+
+ // get next node in range to check
+
+ i = DAS_GetNextIndex( &g_das_check_next, DAS_CNODES );
+
+ if ( g_das_nodes[i].fused && DAS_NodeInRange( proom, &(g_das_nodes[i]) ) )
+ {
+ // trace to see if player can still see node,
+ // if so stop checking
+
+ if ( DAS_TraceNodeToPlayer( proom, &(g_das_nodes[i]) ))
+ goto checknode_exit;
+ }
+ }
+
+checknode_exit:
+
+ // flag that all nodes have been checked
+
+ if ( g_das_checked_count >= DAS_CNODES )
+ g_das_all_checked = true;
+
+ return g_das_all_checked;
+}
+
+
+int DAS_GetNextNodeIndex()
+{
+ return g_das_store_next;
+}
+// store new node for room
+
+void DAS_StoreNode( das_room_t *proom, int dsp_preset)
+{
+ // overwrite node in cyclic list
+
+ int i = DAS_GetNextIndex( &g_das_store_next, DAS_CNODES );
+
+ g_das_nodes[i].dsp_preset = dsp_preset;
+ g_das_nodes[i].fused = true;
+ g_das_nodes[i].vplayer = proom->vplayer;
+
+ // calculate node scanning range_max based on room size
+
+ if ( !proom->bskyabove )
+ {
+ // inside range - halls & tunnels have nodes every 5*width
+ g_das_nodes[i].range_max = fpmin((int)DAS_DIST_MAX, min(proom->width_max * 5, proom->length_max) );
+ g_das_nodes[i].range_min = DAS_DIST_MIN;
+ }
+ else
+ {
+ // outside range
+ g_das_nodes[i].range_max = DAS_DIST_MAX_OUTSIDE;
+ g_das_nodes[i].range_min = DAS_DIST_MIN_OUTSIDE;
+ }
+
+ g_das_nodes[i].fseesplayer = false;
+ g_das_nodes[i].dist = 0;
+
+ g_das_nodes[i].room = *proom;
+
+ // update last node visible as this node
+
+ g_pdas_last_node = &(g_das_nodes[i]);
+}
+
+// check all updated nodes,
+// return dsp_preset of largest node (by area) that can see player
+// return -1 if no preset found
+
+// NOTE: outside nodes can't see player if player is inside and vice versa
+// foutside is true if player is outside
+
+int DAS_GetDspPreset( bool foutside )
+{
+ int dsp_preset = -1;
+
+ int i;
+ // int dist_min = 100000;
+ int area_max = 0;
+ int area;
+
+ // find node that represents room with greatest floor area, return its preset.
+
+ for (i = 0; i < DAS_CNODES; i++)
+ {
+ if (g_das_nodes[i].fused && g_das_nodes[i].fseesplayer)
+ {
+ area = (g_das_nodes[i].room.width_max * g_das_nodes[i].room.length_max);
+
+ if ( g_das_nodes[i].room.bskyabove == foutside )
+ {
+ if (area > area_max)
+ {
+ area_max = area;
+ dsp_preset = g_das_nodes[i].dsp_preset;
+
+ // save pointer to last node that saw player
+
+ g_pdas_last_node = &(g_das_nodes[i]);
+ }
+ }
+/*
+
+ // find nearest node, return its preset
+
+ if (g_das_nodes[i].dist < dist_min)
+ {
+ if ( g_das_nodes[i].room.bskyabove == foutside )
+ {
+ dist_min = g_das_nodes[i].dist;
+ dsp_preset = g_das_nodes[i].dsp_preset;
+
+ // save pointer to last node that saw player
+
+ g_pdas_last_node = &(g_das_nodes[i]);
+
+ }
+ }
+*/
+ }
+ }
+
+ return dsp_preset;
+}
+
+// custom trace filter:
+// a) never hit player or monsters or entities
+// b) always hit world, or moveables or static props
+
+class CTraceFilterDAS : public ITraceFilter
+{
+public:
+ bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
+ {
+ IClientUnknown *pUnk = static_cast<IClientUnknown*>(pHandleEntity);
+ IClientEntity *pEntity;
+
+ if ( !pUnk )
+ return false;
+
+ // don't hit non-collideable props
+
+ if ( StaticPropMgr()->IsStaticProp( pHandleEntity ) )
+ {
+
+ ICollideable *pCollide = StaticPropMgr()->GetStaticProp( pHandleEntity);
+ if (!pCollide)
+ return false;
+ }
+
+ // don't hit any ents
+
+ pEntity = pUnk->GetIClientEntity();
+
+ if ( pEntity )
+ return false;
+
+ return true;
+ }
+
+ virtual TraceType_t GetTraceType() const
+ {
+ return TRACE_EVERYTHING_FILTER_PROPS;
+ }
+};
+
+#define DAS_TRACE_MASK (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW)
+
+// returns true if clear line exists between node and player
+// if node can see player, sets up node distance and flag fseesplayer
+
+bool DAS_TraceNodeToPlayer( das_room_t *proom, das_node_t *pnode )
+{
+ trace_t trP;
+ CTraceFilterDAS filterP;
+ bool fseesplayer = false;
+ float dist;
+ Ray_t ray;
+ ray.Init( proom->vplayer, pnode->vplayer );
+
+ g_pEngineTraceClient->TraceRay( ray, DAS_TRACE_MASK, &filterP, &trP );
+ dist = VectorLength( proom->vplayer - pnode->vplayer );
+
+ // player can still see previous room selection point, and it's less than n feet away,
+ // then flag this node as visible
+
+ if ( !trP.DidHit() && (dist <= DAS_DIST_MAX) )
+ {
+ fseesplayer = true;
+ pnode->dist = dist;
+ }
+
+ pnode->fseesplayer = fseesplayer;
+
+ return fseesplayer;
+}
+
+// update room boundary maxs, mins
+
+void DAS_SetRoomBounds( das_room_t *proom, Vector &hit, bool bheight )
+{
+ Vector maxs, mins;
+
+ maxs = proom->room_maxs;
+ mins = proom->room_mins;
+
+ if (!bheight)
+ {
+ if (hit.x > maxs.x)
+ maxs.x = hit.x;
+
+ if (hit.x < mins.x)
+ mins.x = hit.x;
+
+ if (hit.z > maxs.z)
+ maxs.z = hit.z;
+
+ if (hit.z < mins.z)
+ mins.z = hit.z;
+ }
+
+ if (bheight)
+ {
+ if (hit.y > maxs.y)
+ maxs.y = hit.y;
+
+ if (hit.y < mins.y)
+ mins.y = hit.y;
+ }
+
+ proom->room_maxs = maxs;
+ proom->room_mins = mins;
+}
+
+// when all walls are updated, calculate max length, width, height, reflectivity, sky hit%, room center
+// returns true if room parameters are in good location to place a node
+// returns false if room parameters are not in good location to place a node
+// note: false occurs if up vector doesn't hit sky, but one or more up diagonal vectors do hit sky
+
+bool DAS_CalcRoomProps( das_room_t *proom )
+{
+ int length_max = 0;
+ int width_max = 0;
+ int height_max = 0;
+ int dist[4];
+ float area1, area2;
+ int height;
+ int i;
+ int j;
+ int k;
+ bool b_diaghitsky = false;
+
+ // reject this location if up vector doesn't hit sky, but
+ // one or more up diagonals do hit sky -
+ // in this case, player is under a slight overhang, narrow bridge, or
+ // standing just inside a window or doorway. keep looking for better node location
+
+ for (i = IVEC_DIAG_UP; i < IVEC_UP; i++)
+ {
+ if (proom->skyhits[i] > 0.0)
+ b_diaghitsky = true;
+ }
+
+ if (b_diaghitsky && !(proom->skyhits[IVEC_UP] > 0.0))
+ return false;
+
+ // get all distance pairs
+
+ for (i = 0; i < IVEC_DIAG_UP; i+=2)
+ dist[i/2] = proom->dist[i] + proom->dist[i+1]; // 1st pair is width
+
+ // if areas differ by more than 25%
+ // select the pair with the greater area
+
+ // if areas do not differ by more than 25%, select the pair with the
+ // longer measured distance. Filters incorrect selection due to diagonals.
+
+ area1 = (float)(dist[0] * dist[1]);
+ area2 = (float)(dist[2] * dist[3]);
+
+ area1 = (int)area1 == 0 ? 1.0 : area1;
+ area2 = (int)area2 == 0 ? 1.0 : area2;
+
+ if ( PercentDifference(area1, area2) > 0.25 )
+ {
+ // areas are more than 25% different - select pair with greater area
+
+ j = area1 > area2 ? 0 : 2;
+ }
+ else
+ {
+ // select pair with longer measured distance
+
+ int iMaxDist = 0; // index to max dist
+ int dmax = 0;
+
+ for (i = 0; i < 4; i++)
+ {
+ if (dist[i] > dmax)
+ {
+ dmax = dist[i];
+ iMaxDist = i;
+ }
+ }
+
+ j = iMaxDist > 1 ? 2 : 0;
+ }
+
+
+ // width is always the smaller of the dimensions
+
+ width_max = min (dist[j], dist[j+1]);
+ length_max = max (dist[j], dist[j+1]);
+
+ // get max height
+
+ for (i = IVEC_DIAG_UP; i < IVEC_DOWN; i++)
+ {
+ height = proom->dist[i];
+
+ if (height > height_max)
+ height_max = height;
+ }
+
+ proom->length_max = length_max;
+ proom->width_max = width_max;
+ proom->height_max = height_max;
+
+ // get room max,min from chosen width, depth
+ // 0..3 or 4..7
+
+ for ( i = j*2; i < 4+(j*2); i++)
+ DAS_SetRoomBounds( proom, proom->hit[i], false );
+
+ // get room height min from down trace
+
+ proom->room_mins.z = proom->hit[IVEC_DOWN].z;
+
+ // reset room height max to player trace height
+
+ proom->room_maxs.z = proom->vplayer.z;
+
+ // draw box around room max,min
+
+ if (das_debug.GetInt() == 6)
+ {
+ // draw box around all objects detected
+ Vector maxs = proom->room_maxs;
+ Vector mins = proom->room_mins;
+ Vector orig = (maxs + mins) / 2.0;
+ Vector absMax = maxs - orig;
+ Vector absMin = mins - orig;
+
+ CDebugOverlay::AddBoxOverlay( orig, absMax, absMin, vec3_angle, 255, 0, 255, 0, 60.0f );
+ }
+ // calculate average reflectivity
+
+ float refl = 0.0;
+
+ // average reflectivity for walls
+
+ // 0..3 or 4..7
+
+ for ( k = 0, i = j*2; i < 4+(j*2); i++, k++)
+ {
+ refl += proom->reflect[i];
+ proom->refl_walls[k] = proom->reflect[i];
+ }
+
+ // assume ceiling is open
+
+ proom->refl_walls[4] = 0.0;
+
+ // get ceiling reflectivity, if any non zero
+
+ for ( i = IVEC_DIAG_UP; i < IVEC_DOWN; i++)
+ {
+ if (proom->reflect[i] == 0.0)
+ {
+ // if any upward trace hit sky, exit;
+ // ceiling reflectivity is 0.0
+
+ proom->refl_walls[4] = 0.0;
+
+ i = IVEC_DOWN; // exit loop
+ }
+ else
+ {
+
+ // upward trace didn't hit sky, keep checking
+
+ proom->refl_walls[4] = proom->reflect[i];
+ }
+ }
+
+ // add in ceiling reflectivity, if any
+
+ refl += proom->refl_walls[4];
+
+ // get floor reflectivity
+
+ refl += proom->reflect[IVEC_DOWN];
+ proom->refl_walls[5] = proom->reflect[IVEC_DOWN];
+
+ proom->refl_avg = refl / 6.0;
+
+ // calculate sky hit percent for this wall
+
+ float sky_pct = 0.0;
+
+ // 0..3 or 4..7
+
+ for ( i = j*2; i < 4+(j*2); i++)
+ sky_pct += proom->skyhits[i];
+
+ for ( i = IVEC_DIAG_UP; i < IVEC_DOWN; i++)
+ {
+ if (proom->skyhits[i] > 0.0)
+ {
+ // if any upward trace hit sky, exit loop
+ sky_pct += proom->skyhits[i];
+ i = IVEC_DOWN;
+ }
+ }
+
+ // get floor skyhit
+
+ sky_pct += proom->skyhits[IVEC_DOWN];
+
+ proom->sky_pct = sky_pct;
+
+ // check for sky above
+ proom->bskyabove = false;
+
+ for (i = IVEC_DIAG_UP; i < IVEC_DOWN; i++)
+ {
+ if (proom->skyhits[i] > 0.0)
+ proom->bskyabove = true;
+ }
+
+ return true;
+}
+
+// return true if trace hit solid
+// return false if trace hit sky or didn't hit anything
+
+bool DAS_HitSolid( trace_t *ptr )
+{
+ // if hit nothing return false
+
+ if (!ptr->DidHit())
+ return false;
+
+ // if hit sky, return false (not solid)
+ if (ptr->surface.flags & SURF_SKY)
+ return false;
+
+ return true;
+}
+
+// returns true if trace hit sky
+
+bool DAS_HitSky( trace_t *ptr )
+{
+ if (ptr->DidHit() && (ptr->surface.flags & SURF_SKY))
+ return true;
+ if (!ptr->DidHit() )
+ {
+ float dz = ptr->endpos.z - ptr->startpos.z;
+ if ( dz > 200*12.0f )
+ return true;
+ }
+ return false;
+}
+
+
+bool DAS_ScanningForHeight( das_room_t *proom )
+{
+ return (proom->iwall >= IVEC_DIAG_UP);
+}
+
+bool DAS_ScanningForWidth( das_room_t *proom )
+{
+ return (proom->iwall < IVEC_DIAG_UP);
+}
+
+bool DAS_ScanningForFloor( das_room_t *proom )
+{
+ return (proom->iwall == IVEC_DOWN);
+}
+
+ConVar das_door_height("adsp_door_height", "112"); // standard door height hl2
+ConVar das_wall_height("adsp_wall_height", "128"); // standard wall height hl2
+ConVar das_low_ceiling("adsp_low_ceiling", "108"); // low ceiling height hl2
+
+
+// set origin for tracing out to walls to point above player's head
+// allows calculations over walls and floor obstacles, and above door openings
+
+// WARNING: the current settings are optimal for skipping floor and ceiling clutter,
+// and for detecting rooms without 'looking' through doors or windows. Don't change these cvars for hl2!
+
+void DAS_SetTraceHeight( das_room_t *proom, trace_t *ptrU, trace_t *ptrD )
+{
+ // NOTE: when tracing down through player's box, endpos and startpos are reversed and
+ // startsolid and allsolid are true.
+
+ int zup = abs(ptrU->endpos.z - ptrU->startpos.z); // height above player's head
+ int zdown = abs(ptrD->endpos.z - ptrD->startpos.z); // distance to floor from player's head
+ int h;
+ h = zup + zdown;
+
+ int door_height = das_door_height.GetInt();
+ int wall_height = das_wall_height.GetInt();
+ int low_ceiling = das_low_ceiling.GetInt();
+
+ if (h > low_ceiling && h <= wall_height)
+ {
+ // low ceiling - trace out just above standard door height @ 112
+ if (h > door_height)
+ proom->vplayer.z = fpmin(ptrD->endpos.z, ptrD->startpos.z) + door_height + 1;
+ else
+ proom->vplayer.z = fpmin(ptrD->endpos.z, ptrD->startpos.z) + h - 1;
+ }
+ else if ( h > wall_height )
+ {
+ // tall ceiling - trace out over standard walls @ 128
+
+ proom->vplayer.z = fpmin(ptrD->endpos.z, ptrD->startpos.z) + wall_height + 1;
+ }
+ else
+ {
+ // very low ceiling, trace out from just below ceiling
+ proom->vplayer.z = fpmin(ptrD->endpos.z, ptrD->startpos.z) + h - 1;
+ proom->lowceiling = h;
+ }
+
+ Assert (proom->vplayer.z <= ptrU->endpos.z);
+
+ if (das_debug.GetInt() > 1)
+ {
+ // draw line to height, and between floor and ceiling
+
+ CDebugOverlay::AddLineOverlay( ptrD->endpos, ptrU->endpos, 0, 255, 0, 255, false, 20 );
+
+ Vector mins;
+ Vector maxs;
+ mins.Init(-1,-1,-2.0);
+ maxs.Init(1,1,0);
+
+ CDebugOverlay::AddBoxOverlay( proom->vplayer, mins, maxs, vec3_angle, 255, 0, 0, 0, 20 );
+
+ CDebugOverlay::AddBoxOverlay( ptrU->endpos, mins, maxs, vec3_angle, 0, 255, 0, 0, 20 );
+ CDebugOverlay::AddBoxOverlay( ptrD->endpos, mins, maxs, vec3_angle, 0, 255, 0, 0, 20 );
+
+ }
+}
+
+// prepare room struct for new round of checks:
+// clear out struct,
+// init trace height origin by finding space above player's head
+// returns true if player is in valid position to begin checks from
+
+bool DAS_StartTraceChecks( das_room_t *proom )
+{
+ // starting new check: store player position, init maxs, mins
+
+ proom->vplayer_eyes = MainViewOrigin();
+ proom->vplayer = MainViewOrigin();
+
+ proom->height_max = 0;
+ proom->width_max = 0;
+ proom->length_max = 0;
+ proom->room_maxs.Init (0.0, 0.0, 0.0);
+ proom->room_mins.Init (10000.0, 10000.0, 10000.0);
+
+ proom->lowceiling = 0;
+
+ // find point between player's head and ceiling - trace out to walls from here
+
+ trace_t trU, trD;
+ CTraceFilterDAS filterU, filterD;
+
+ Vector v_dir = g_das_vec3[IVEC_DOWN]; // down - find floor
+
+ Vector endpoint = proom->vplayer + v_dir;
+
+ Ray_t ray;
+ ray.Init( proom->vplayer, endpoint );
+
+ g_pEngineTraceClient->TraceRay( ray, DAS_TRACE_MASK, &filterD, &trD );
+
+ // if player jumping or in air, don't continue
+
+ if (trD.DidHit() && abs(trD.endpos.z - trD.startpos.z) > 72)
+ return false;
+
+ v_dir = g_das_vec3[IVEC_UP]; // up - find ceiling
+
+ endpoint = proom->vplayer + v_dir;
+
+ ray.Init( proom->vplayer, endpoint );
+
+ g_pEngineTraceClient->TraceRay( ray, DAS_TRACE_MASK, &filterU, &trU );
+
+ // if down trace hits floor, set trace height, otherwise default is player eye location
+
+ if ( DAS_HitSolid( &trD) )
+ DAS_SetTraceHeight( proom, &trU, &trD );
+
+ return true;
+}
+
+void DAS_DebugDrawTrace ( trace_t *ptr, int r, int g, int b, float duration, int imax)
+{
+
+ // das_debug == 3: draw horizontal trace bars for room width/depth detection
+ // das_debug == 4: draw upward traces for height detection
+
+ if (das_debug.GetInt() != imax)
+ return;
+
+ CDebugOverlay::AddLineOverlay( ptr->startpos, ptr->endpos, r, g, b, 255, false, duration );
+
+ Vector mins;
+ Vector maxs;
+ mins.Init(-1,-1,-2.0);
+ maxs.Init(1,1,0);
+
+ CDebugOverlay::AddBoxOverlay( ptr->endpos, mins, maxs, vec3_angle, r, g, b, 0, duration );
+
+}
+
+// wall surface data
+
+struct das_surfdata_t
+{
+ float dist; // distance to player
+ float reflectivity; // acoustic reflectivity of material on surface
+ Vector hit; // trace hit location
+ Vector norm; // wall normal at hit location
+};
+
+// trace hit wall surface, get info about surface and store in surfdata struct
+// if scanning for height, bounce a second trace off of ceiling and get dist to floor
+
+void DAS_GetSurfaceData( das_room_t *proom, trace_t *ptr, das_surfdata_t *psurfdata )
+{
+
+ float dist; // distance to player
+ float reflectivity; // acoustic reflectivity of material on surface
+ Vector hit; // trace hit location
+ Vector norm; // wall normal at hit location
+ surfacedata_t *psurf;
+
+ psurf = physprop->GetSurfaceData( ptr->surface.surfaceProps );
+
+ reflectivity = psurf ? psurf->audio.reflectivity : DAS_REFLECTIVITY_NORM;
+
+ // keep wall hit location and normal, to calc room bounds and center
+
+ norm = ptr->plane.normal;
+
+ // get length to hit location
+
+ dist = VectorLength(ptr->endpos - ptr->startpos);
+
+ // if started tracing from within player box, startpos & endpos may be flipped
+
+ if (ptr->endpos.z >= ptr->startpos.z)
+ hit = ptr->endpos;
+ else
+ hit = ptr->startpos;
+
+ // if checking for max height by bouncing several vectors off of ceiling:
+ // ignore returned normal from 1st bounce, just search straight down from trace hit location
+
+ if ( DAS_ScanningForHeight( proom ) && !DAS_ScanningForFloor( proom ) )
+ {
+ trace_t tr2;
+ CTraceFilterDAS filter2;
+
+ norm.Init(0.0, 0.0, -1.0);
+
+ Vector endpoint = hit + ( norm * DAS_ROOM_TRACE_LEN );
+
+ Ray_t ray;
+ ray.Init( hit, endpoint );
+
+ g_pEngineTraceClient->TraceRay( ray, DAS_TRACE_MASK, &filter2, &tr2 );
+
+ //DAS_DebugDrawTrace( &tr2, 255, 255, 0, 10, 1);
+
+ if (tr2.DidHit())
+ {
+ // get distance between surfaces
+
+ dist = VectorLength(tr2.endpos - tr2.startpos);
+ }
+ }
+
+ // set up surface struct and return
+
+ psurfdata->dist = dist;
+ psurfdata->hit = hit;
+ psurfdata->norm = norm;
+ psurfdata->reflectivity = reflectivity;
+
+}
+
+
+// algorithm for detecting approximate size of space around player. Handles player in corner & non-axis aligned rooms.
+// also handles player on catwalk or player under small bridge/overhang.
+// The goal is to only change the dsp room description if the the player moves into
+// a space which is SIGNIFICANTLY different from the previously set dsp space.
+
+// save player position. find a point above player's head and trace out from here.
+
+// from player position, get max width and max length:
+
+// from player position,
+// a) trace x,-x, y,-y axes
+// b) trace xy, -xy, x-y, -x-y diagonals
+// c) select largest room size detected from max width, max length
+
+
+// from player position, get height
+// a) trace out along front-up (or left-up, back-up, right-up), save hit locations
+// b) trace down -z from hit locations
+// c) save max height
+
+// when max width, max length, max height all updated, get new player position
+
+// get average room size & wall materials:
+// update averages with one traceline per frame only
+// returns true if room is fully updated and ready to check
+
+bool DAS_UpdateRoomSize( das_room_t *proom )
+{
+ Vector endpoint;
+ Vector startpoint;
+ Vector v_dir;
+ int iwall;
+ bool bskyhit = false;
+ das_surfdata_t surfdata;
+
+ // do nothing if room already fully checked
+
+ if ( proom->broomready )
+ return true;
+
+ // cycle through all walls, floor, ceiling
+ // get wall index
+
+ iwall = proom->iwall;
+
+ // get height above player and init proom for new round of checks
+
+ if (iwall == 0)
+ {
+ if (!DAS_StartTraceChecks( proom ))
+ return false; // bad location to check room - player is jumping etc.
+ }
+
+ // get trace vector
+
+ v_dir = g_das_vec3[iwall];
+
+ // trace out from trace origin, in axis-aligned direction or along diagonals
+
+ // if looking for max height, trace from top of player's eyes
+
+ if ( DAS_ScanningForHeight( proom ) )
+ {
+ startpoint = proom->vplayer_eyes;
+ endpoint = proom->vplayer_eyes + v_dir;
+ }
+ else
+ {
+ startpoint = proom->vplayer;
+ endpoint = proom->vplayer + v_dir;
+ }
+
+ // try less expensive world-only trace first (no props, no ents - just try to hit walls)
+
+ trace_t tr;
+ CTraceFilterWorldOnly filter;
+
+ Ray_t ray;
+ ray.Init( startpoint, endpoint );
+
+ g_pEngineTraceClient->TraceRay( ray, CONTENTS_SOLID, &filter, &tr );
+
+ // if didn't hit world, or we hit sky when looking horizontally,
+ // retrace, this time including props
+
+ if ( !DAS_HitSolid( &tr ) && DAS_ScanningForWidth( proom ) )
+ {
+ CTraceFilterDAS filterDas;
+
+ ray.Init( startpoint, endpoint );
+ g_pEngineTraceClient->TraceRay( ray, DAS_TRACE_MASK, &filterDas, &tr );
+ }
+
+ if (das_debug.GetInt() > 2)
+ {
+ // draw trace lines
+
+ if ( DAS_HitSolid( &tr ) )
+ DAS_DebugDrawTrace( &tr, 0, 255, 255, 10, DAS_ScanningForHeight( proom ) + 3);
+ else
+ DAS_DebugDrawTrace( &tr, 255, 0, 0, 10, DAS_ScanningForHeight( proom ) + 3); // red lines if sky hit or no hit
+ }
+
+ // init surface data with defaults, in case we didn't hit world
+
+ surfdata.dist = DAS_ROOM_TRACE_LEN;
+ surfdata.reflectivity = DAS_REFLECTIVITY_SKY; // assume sky or open area
+ surfdata.hit = endpoint; // trace hit location
+ surfdata.norm = -v_dir;
+
+ // check for sky hits
+
+ if ( DAS_HitSky( &tr ) )
+ {
+ bskyhit = true;
+
+ if ( DAS_ScanningForWidth( proom ) )
+ // ignore horizontal sky hits for distance calculations
+ surfdata.dist = 1.0;
+ else
+ surfdata.dist = surfdata.dist; // debug
+ }
+
+ // get length of trace if it hit world
+
+ // if hit solid and not sky (tr.DidHit() && !bskyhit)
+ // get surface information
+
+ if ( DAS_HitSolid( &tr) )
+ DAS_GetSurfaceData( proom, &tr, &surfdata );
+
+ // store surface data
+
+ proom->dist[iwall] = surfdata.dist;
+ proom->reflect[iwall] = clamp(surfdata.reflectivity, 0.0f, 1.0f);
+ proom->skyhits[iwall] = bskyhit ? 0.1 : 0.0;
+ proom->hit[iwall] = surfdata.hit;
+ proom->norm[iwall] = surfdata.norm;
+
+ // update wall counter
+
+ proom->iwall++;
+
+ if (proom->iwall == DAS_CWALLS)
+ {
+ bool b_good_node_location;
+
+ // calculate room mins, maxs, reflectivity etc
+
+ b_good_node_location = DAS_CalcRoomProps( proom );
+
+ // reset wall counter
+
+ proom->iwall = 0;
+ proom->broomready = b_good_node_location; // room ready to check if good node location
+
+ return b_good_node_location;
+ }
+
+ return false; // room not yet fully updated
+}
+
+// create entity enumerator for counting ents & summing volume of ents in room
+
+class CDasEntEnum : public IPartitionEnumerator
+{
+ public:
+ int m_count; // # of ents in space
+ float m_volume; // space occupied by ents
+
+ public:
+
+ void Reset()
+ {
+ m_count = 0;
+ m_volume = 0.0;
+ }
+
+ // called with each handle...
+
+ IterationRetval_t EnumElement( IHandleEntity *pHandleEntity )
+ {
+ float vol;
+
+ // get bounding box of entity
+ // Generate a collideable
+
+ ICollideable *pCollideable = g_pEngineTraceClient->GetCollideable( pHandleEntity );
+
+ if ( !pCollideable )
+ return ITERATION_CONTINUE;
+
+ // Check for solid
+
+ if ( !IsSolid( pCollideable->GetSolid(), pCollideable->GetSolidFlags() ) )
+ return ITERATION_CONTINUE;
+
+ m_count++;
+
+ // compute volume of space occupied by entity
+ Vector mins = pCollideable->OBBMins();
+ Vector maxs = pCollideable->OBBMaxs();
+
+ vol = fabs((maxs.x - mins.x) * (maxs.y - mins.y) * (maxs.z - mins.z));
+
+ m_volume += vol; // add to total vol
+
+ if (das_debug.GetInt() == 5)
+ {
+ // draw box around all objects detected
+
+ Vector orig = pCollideable->GetCollisionOrigin();
+ CDebugOverlay::AddBoxOverlay( orig, mins, maxs, pCollideable->GetCollisionAngles(), 255, 0, 255, 0, 60.0f );
+ }
+
+ return ITERATION_CONTINUE;
+ }
+};
+
+// determine # of solid ents/props within detected room boundaries
+// and set diffusion based on count of ents and spatial volume of ents
+
+void DAS_SetDiffusion( das_room_t *proom )
+{
+ // BRJ 7/12/05
+ // This was commented out because the y component of proom->room_mins, proom->room_maxs was never
+ // being computed, causing a bogus box to be sent to the partition system. The results of
+ // this computation (namely the diffusion + ent_count fields of das_room_t) were never being used.
+ // Therefore, we'll avoid the enumeration altogether
+
+ proom->diffusion = 0.0f;
+ proom->ent_count = 0;
+
+ /*
+ CDasEntEnum enumerator;
+ SpatialPartitionListMask_t mask = PARTITION_CLIENT_SOLID_EDICTS; // count only solid ents in room
+ int count;
+ float vol;
+ float volroom;
+ float dfn;
+
+ enumerator.Reset();
+
+ SpatialPartition()->EnumerateElementsInBox(mask, proom->room_mins, proom->room_maxs, true, &enumerator );
+
+ count = enumerator.m_count;
+ vol = enumerator.m_volume;
+
+ // compute diffusion from volume
+
+ // how much space around player is filled with props?
+
+ volroom = (proom->room_maxs.x - proom->room_mins.x) * (proom->room_maxs.y - proom->room_mins.y) * (proom->room_maxs.z - proom->room_mins.z);
+ volroom = fabs(volroom);
+
+ if ( !(int)volroom )
+ volroom = 1.0;
+
+ dfn = vol / volroom; // % of total volume occupied by props
+
+ dfn = clamp (dfn, 0.0, 1.0);
+
+ proom->diffusion = dfn;
+ proom->ent_count = count;
+ */
+}
+
+// debug routine to display current room params
+
+void DAS_DisplayRoomDEBUG( das_room_t *proom, bool fnew, float preset )
+{
+ float dx,dy,dz;
+ Vector ctr;
+ float count;
+
+ if (das_debug.GetInt() == 0)
+ return;
+
+ dx = proom->length_max / 12.0;
+ dy = proom->width_max / 12.0;
+ dz = proom->height_max / 12.0;
+
+ float refl = proom->refl_avg;
+
+ count = (float)(proom->ent_count);
+ float fsky = (proom->bskyabove ? 1.0 : 0.0);
+
+ if (fnew)
+ DevMsg( "NEW DSP NODE: size:(%.0f,%.0f) height:(%.0f) dif %.4f : refl %.4f : cobj: %.0f : sky %.0f \n", dx, dy, dz, proom->diffusion, refl, count, fsky);
+
+ if (!fnew && preset < 0.0)
+ return;
+
+ if (preset >= 0.0)
+ {
+ if (proom == NULL)
+ return;
+
+ DevMsg( "DSP PRESET: %.0f size:(%.0f,%.0f) height:(%.0f) dif %.4f : refl %.4f : cobj: %.0f : sky %.0f \n", preset, dx, dy, dz, proom->diffusion, refl, count, fsky);
+ return;
+ }
+
+ // draw box around new node location
+
+ Vector mins;
+ Vector maxs;
+ mins.Init(-8,-8,-16);
+ maxs.Init(8,8,0);
+
+ CDebugOverlay::AddBoxOverlay( proom->vplayer, mins, maxs, vec3_angle, 0, 0, 255, 0, 1000.0f );
+
+ // draw red box around node origin
+
+ mins.Init(-0.5,-0.5,-1.0);
+ maxs.Init(0.5,0.5,0);
+
+ CDebugOverlay::AddBoxOverlay( proom->vplayer, mins, maxs, vec3_angle, 255, 0, 0, 0, 1000.0f );
+
+ CDebugOverlay::AddTextOverlay( proom->vplayer, 0, 10, 1.0, "DSP NODE" );
+}
+
+// check newly calculated room parameters against current stored params.
+// if different, return true.
+// NOTE: only call when all proom params have been calculated.
+// return false if this is not a good location for creating a new node
+
+bool DAS_CheckNewRoom( das_room_t *proom )
+{
+ bool bnewroom;
+ float dw,dw2,dr,ds,dh;
+ int cchanged = 0;
+ das_room_t *proom_prev = NULL;
+ Vector2D v2d;
+ Vector v3d;
+ float dist;
+
+ // player can't see previous node, determine if this is a good place to lay down
+ // a new node. Get room at last seen node for comparison
+
+ if (g_pdas_last_node)
+ proom_prev = &(g_pdas_last_node->room);
+
+ // no previous room node saw player, go create new room node
+
+ if (!proom_prev)
+ {
+ bnewroom = true;
+ goto check_ret;
+ }
+
+ // if player not at least n feet from last node, return false
+
+ v3d = proom->vplayer - proom_prev->vplayer;
+ v2d.Init(v3d.x, v3d.y);
+
+ dist = Vector2DLength(v2d);
+
+ if (dist <= DAS_DIST_MIN)
+ return false;
+
+ // see if room size has changed significantly since last node
+
+ bnewroom = true;
+
+ dw = 0.0;
+ dw2 = 0.0;
+ dh = 0.0;
+ dr = 0.0;
+
+ if ( proom_prev->width_max != 0 )
+ dw = (float)proom->width_max / (float)proom_prev->width_max; // max width delta
+
+ if ( proom_prev->length_max != 0 )
+ dw2 = (float)proom->length_max / (float)proom_prev->length_max; // max length delta
+
+ if ( proom_prev->height_max != 0 )
+ dh = (float)proom->height_max / (float)proom_prev->height_max; // max height delta
+
+ if ( proom_prev->refl_avg != 0.0 )
+ dr = proom->refl_avg / proom_prev->refl_avg; // reflectivity delta
+
+ ds = fabs( proom->sky_pct - proom_prev->sky_pct); // sky hits delta
+
+ if (dw > 1.0) dw = 1.0 / dw;
+ if (dw2 > 1.0) dw = 1.0 / dw2;
+ if (dh > 1.0) dh = 1.0 / dh;
+ if (dr > 1.0) dr = 1.0 / dr;
+
+ if ( (1.0 - dw) >= DAS_WIDTH_MIN )
+ cchanged++;
+
+ if ( (1.0 - dw2) >= DAS_WIDTH_MIN )
+ cchanged++;
+
+// if ( (1.0 - dh) >= DAS_WIDTH_MIN ) // don't change room based on height change
+// cchanged++;
+
+ // new room only if at least 1 changed
+
+ if (cchanged >= 1)
+ goto check_ret;
+
+// if ( (1.0 - dr) >= DAS_REFL_MIN ) // don't change room based on reflectivity change
+// goto check_ret;
+
+// if (ds >= DAS_SKYHIT_MIN )
+// goto check_ret;
+
+ // new room if sky above changes state
+
+ if (proom->bskyabove != proom_prev->bskyabove)
+ goto check_ret;
+
+ // room didn't change significantly, return false
+
+ bnewroom = false;
+
+check_ret:
+
+ if ( bnewroom )
+ {
+ // if low ceiling detected < 112 units, and max height is > low ceiling height by 20%, discard - no change
+ // this detects player in doorway, under pipe or narrow bridge
+
+ if ( proom->lowceiling && (proom->lowceiling < proom->height_max))
+ {
+ float h = (float)(proom->lowceiling) / (float)proom->height_max;
+
+ if (h < 0.8)
+ return false;
+ }
+
+ DAS_SetDiffusion( proom );
+ }
+
+ DAS_DisplayRoomDEBUG( proom, bnewroom, -1.0 );
+
+ return bnewroom;
+}
+
+
+extern int DSP_ConstructPreset( bool bskyabove, int width, int length, int height, float fdiffusion, float freflectivity, float *psurf_refl, int inode, int cnodes );
+
+// select new dsp_room based on size, wall materials
+// (or modulate params for current dsp)
+// returns new preset # for dsp_automatic
+
+int DAS_GetRoomDSP( das_room_t *proom, int inode )
+{
+
+ // preset constructor
+ // call dsp module with params, get dsp preset back
+
+ bool bskyabove = proom->bskyabove;
+ int width = proom->width_max;
+ int length = proom->length_max;
+ int height = proom->height_max;
+ float fdiffusion = proom->diffusion;
+ float freflectivity = proom->refl_avg;
+ float surf_refl[6];
+
+ // fill array of surface reflectivities - for left,right,front,back,ceiling,floor
+
+ for (int i = 0; i < 6; i++)
+ surf_refl[i] = proom->refl_walls[i];
+
+ return DSP_ConstructPreset( bskyabove, width, length, height, fdiffusion, freflectivity, surf_refl, inode, DAS_CNODES );
+
+}
+
+
+// main entry point: call once per frame to update dsp_automatic
+// for automatic room detection. dsp_room must be set to DSP_AUTOMATIC to enable.
+// NOTE: this routine accumulates traceline information over several frames - it
+// never traces more than 3 times per call, and normally just once per call.
+
+void DAS_CheckNewRoomDSP( )
+{
+ VPROF("DAS_CheckNewRoomDSP");
+ das_room_t *proom = &g_das_room;
+ int dsp_preset;
+ bool bRoom_ready = false;
+
+ // if listener has not been updated, do nothing
+
+ if ((listener_origin == vec3_origin) &&
+ (listener_forward == vec3_origin) &&
+ (listener_right == vec3_origin) &&
+ (listener_up == vec3_origin) )
+ return;
+
+ if ( !SND_IsInGame() )
+ return;
+
+ // make sure we init nodes & vectors first time this is called
+
+ if ( !g_bdas_init_nodes )
+ {
+ g_bdas_init_nodes = 1;
+ DAS_InitNodes();
+ }
+
+ if ( !DSP_CheckDspAutoEnabled())
+ {
+ // make sure room params are reinitialized each time autoroom is selected
+
+ g_bdas_room_init = 0;
+ return;
+ }
+
+ if ( !g_bdas_room_init )
+ {
+ g_bdas_room_init = 1;
+
+ DAS_InitAutoRoom( proom );
+ }
+
+ // get time
+
+ double dtime = g_pSoundServices->GetHostTime();
+
+ // compare to previous time - don't check for new room until timer expires
+ // ie: wait at least DAS_AUTO_WAIT seconds between preset changes
+
+ if ( fabs(dtime - proom->last_dsp_change) < DAS_AUTO_WAIT )
+ return;
+
+ // first, update room size parameters, see if room is ready to check - if room is updated, return true right away
+
+ // 3 traces per frame while accumulating room size info
+
+ for (int i = 0 ; i < 3; i++)
+ bRoom_ready = DAS_UpdateRoomSize( proom );
+
+ if (!bRoom_ready)
+ return;
+
+
+ if ( !g_bdas_create_new_node )
+ {
+ // next, check all nodes for line of sight to player - if all checked, return true right away
+
+ if ( !DAS_CheckNextNode( proom ) )
+ {
+ // check all nodes first
+
+ return;
+ }
+
+ // find out if any previously stored nodes can see player,
+ // if so, get closest node's dsp preset
+
+ dsp_preset = DAS_GetDspPreset( proom->bskyabove );
+
+ if (dsp_preset != -1)
+ {
+ // an existing node can see player - just set preset and return
+
+ if (dsp_preset != dsp_room_GetInt())
+ {
+ // changed preset, so update timestamp
+
+ proom->last_dsp_change = g_pSoundServices->GetHostTime();
+
+ if (g_pdas_last_node)
+ DAS_DisplayRoomDEBUG( &(g_pdas_last_node->room), false, (float)dsp_preset );
+ }
+
+ DSP_SetDspAuto( dsp_preset );
+
+ goto check_new_room_exit;
+ }
+ }
+
+ g_bdas_create_new_node = true;
+
+ // no nodes can see player, need to try to create a new one
+
+ // check for 'new' room around player
+
+ if ( DAS_CheckNewRoom( proom ) )
+ {
+ // new room found - update dsp_automatic
+
+ dsp_preset = DAS_GetRoomDSP( proom, DAS_GetNextNodeIndex() );
+
+ DSP_SetDspAuto( dsp_preset );
+
+ // changed preset, so update timestamp
+
+ proom->last_dsp_change = g_pSoundServices->GetHostTime();
+
+ // save room as new node
+
+ DAS_StoreNode( proom, dsp_preset );
+
+ goto check_new_room_exit;
+ }
+
+check_new_room_exit:
+
+ // reset new node creation flag - start checking for visible nodes again
+
+ g_bdas_create_new_node = false;
+
+ // reset room checking flag - start checking room around player again
+
+ proom->broomready = false;
+
+ // reset node checking flag - start checking nodes around player again
+
+ DAS_ResetNodes();
+
+ return;
+}
+
+// remap contents of volumes[] arrary if sound originates from player, or is music, and is 100% 'mono'
+// ie: same volume in all channels
+
+void RemapPlayerOrMusicVols( channel_t *ch, int volumes[CCHANVOLUMES/2], bool fplayersound, bool fmusicsound, float mono )
+{
+ VPROF_("RemapPlayerOrMusicVols", 2, VPROF_BUDGETGROUP_OTHER_SOUND, false, BUDGETFLAG_OTHER );
+
+ if ( !fplayersound && !fmusicsound )
+ return; // no remapping
+
+ if ( ch->flags.bSpeaker )
+ return; // don't remap speaker sounds rebroadcast on player
+
+ // get total volume
+
+ float vol_total = 0.0;
+ int k;
+
+ for (k = 0; k < CCHANVOLUMES/2; k++)
+ vol_total += (float)volumes[k];
+
+ if ( !g_AudioDevice->IsSurround() )
+ {
+ if (mono < 1.0)
+ return;
+
+ // remap 2 chan non-spatialized versions of player and music sounds
+ // note: this is required to keep volumes same as 4 & 5 ch cases!
+
+ float vol_dist_music[] = {1.0, 1.0}; // FL, FR music volumes
+ float vol_dist_player[] = {1.0, 1.0}; // FL, FR player volumes
+ float *pvol_dist;
+
+ pvol_dist = (fplayersound ? vol_dist_player : vol_dist_music);
+
+ for (k = 0; k < 2; k++)
+ volumes[k] = clamp((int)(vol_total * pvol_dist[k]), 0, 255);
+
+ return;
+ }
+
+ // surround sound configuration...
+
+ if ( fplayersound ) // && (ch->bstereowav && ch->wavtype != CHAR_DIRECTIONAL && ch->wavtype != CHAR_DISTVARIANT) )
+ {
+ // NOTE: player sounds also get n% overall volume boost.
+
+ //float vol_dist5[] = {0.29, 0.29, 0.09, 0.09, 0.63}; // FL, FR, RL, RR, FC - 5 channel (mono source) volume distribution
+ //float vol_dist5st[] = {0.29, 0.29, 0.09, 0.09, 0.63}; // FL, FR, RL, RR, FC - 5 channel (stereo source) volume distribution
+
+ float vol_dist5[] = {0.30, 0.30, 0.09, 0.09, 0.59}; // FL, FR, RL, RR, FC - 5 channel (mono source) volume distribution
+ float vol_dist5st[] = {0.30, 0.30, 0.09, 0.09, 0.59}; // FL, FR, RL, RR, FC - 5 channel (stereo source) volume distribution
+
+ float vol_dist4[] = {0.50, 0.50, 0.15, 0.15, 0.00}; // FL, FR, RL, RR, 0 - 4 channel (mono source) volume distribution
+ float vol_dist4st[] = {0.50, 0.50, 0.15, 0.15, 0.00}; // FL, FR, RL, RR, 0 - 4 channel (stereo source)volume distribution
+
+ float *pvol_dist;
+
+ if ( ch->flags.bstereowav && (ch->wavtype == CHAR_OMNI || ch->wavtype == CHAR_SPATIALSTEREO || ch->wavtype == 0))
+ {
+ pvol_dist = (g_AudioDevice->IsSurroundCenter() ? vol_dist5st : vol_dist4st);
+ }
+ else
+ {
+ pvol_dist = (g_AudioDevice->IsSurroundCenter() ? vol_dist5 : vol_dist4);
+ }
+
+ for (k = 0; k < 5; k++)
+ volumes[k] = clamp((int)(vol_total * pvol_dist[k]), 0, 255);
+
+ return;
+ }
+
+ // Special case for music in surround mode
+
+ if ( fmusicsound )
+ {
+ float vol_dist5[] = {0.5, 0.5, 0.25, 0.25, 0.0}; // FL, FR, RL, RR, FC - 5 channel distribution
+ float vol_dist4[] = {0.5, 0.5, 0.25, 0.25, 0.0}; // FL, FR, RL, RR, 0 - 4 channel distribution
+ float *pvol_dist;
+
+ pvol_dist = (g_AudioDevice->IsSurroundCenter() ? vol_dist5 : vol_dist4);
+
+ for (k = 0; k < 5; k++)
+ volumes[k] = clamp((int)(vol_total * pvol_dist[k]), 0, 255);
+
+ return;
+ }
+
+ return;
+}
+
+static int s_nSoundGuid = 0;
+
+void SND_ActivateChannel( channel_t *pChannel )
+{
+ Q_memset( pChannel, 0, sizeof(*pChannel) );
+ g_ActiveChannels.Add( pChannel );
+ pChannel->guid = ++s_nSoundGuid;
+}
+
+/*
+=================
+SND_Spatialize
+=================
+*/
+void SND_Spatialize(channel_t *ch)
+{
+ VPROF("SND_Spatialize");
+
+ vec_t dist;
+ Vector source_vec;
+ Vector source_vec_DL;
+ Vector source_vec_DR;
+ Vector source_doppler_left;
+ Vector source_doppler_right;
+
+ bool fdopplerwav = false;
+ bool fplaydopplerwav = false;
+ bool fvalidentity;
+ float gain;
+ float scale = 1.0;
+ bool fplayersound = false;
+ bool fmusicsound = false;
+ float mono = 0.0;
+ bool bAttenuated = true;
+
+ ch->dspface = 1.0; // default facing direction: always facing player
+ ch->dspmix = 0; // default mix 0% dsp_room fx
+ ch->distmix = 0; // default 100% left (near) wav
+
+#if !defined( _X360 )
+ if ( ch->sfx &&
+ ch->sfx->pSource &&
+ ch->sfx->pSource->GetType() == CAudioSource::AUDIO_SOURCE_VOICE )
+ {
+ Voice_Spatialize( ch );
+ }
+#endif
+
+ if ( IsSoundSourceLocalPlayer( ch->soundsource ) && !toolframework->InToolMode() )
+ {
+ // sounds coming from listener actually come from a short distance directly in front of listener
+ // in tool mode however, the view entity is meaningless, since we're viewing from arbitrary locations in space
+ fplayersound = true;
+ }
+
+ // assume 'dry', playeverwhere sounds are 'music' or 'voiceover'
+
+ if ( ch->flags.bdry && ch->dist_mult <= 0 )
+ {
+ fmusicsound = true;
+ fplayersound = false;
+ }
+
+ // update channel's position in case ent that made the sound is moving.
+ QAngle source_angles;
+ source_angles.Init(0.0, 0.0, 0.0);
+ Vector entOrigin = ch->origin;
+
+ bool looping = false;
+
+ CAudioSource *pSource = ch->sfx ? ch->sfx->pSource : NULL;
+ if ( pSource )
+ {
+ looping = pSource->IsLooped();
+ }
+
+ SpatializationInfo_t si;
+ si.info.Set(
+ ch->soundsource,
+ ch->entchannel,
+ ch->sfx ? ch->sfx->getname() : "",
+ ch->origin,
+ ch->direction,
+ ch->master_vol,
+ DIST_MULT_TO_SNDLVL( ch->dist_mult ),
+ looping,
+ ch->pitch,
+ listener_origin,
+ ch->speakerentity );
+
+ si.type = SpatializationInfo_t::SI_INSPATIALIZATION;
+ si.pOrigin = &entOrigin;
+ si.pAngles = &source_angles;
+ si.pflRadius = NULL;
+ if ( ch->soundsource != 0 && ch->radius == 0 )
+ {
+ si.pflRadius = &ch->radius;
+ }
+
+ {
+ VPROF_("SoundServices->GetSoundSpatializtion", 2, VPROF_BUDGETGROUP_OTHER_SOUND, false, BUDGETFLAG_OTHER );
+ fvalidentity = g_pSoundServices->GetSoundSpatialization( ch->soundsource, si );
+ }
+
+ if ( ch->flags.bUpdatePositions )
+ {
+ AngleVectors( source_angles, &ch->direction );
+ ch->origin = entOrigin;
+ }
+ else
+ {
+ VectorAngles( ch->direction, source_angles );
+ }
+
+ if ( ch->userdata != 0 )
+ {
+ g_pSoundServices->GetToolSpatialization( ch->userdata, ch->guid, si );
+ if ( ch->flags.bUpdatePositions )
+ {
+ AngleVectors( source_angles, &ch->direction );
+ ch->origin = entOrigin;
+ }
+ }
+
+#if 0
+ // !!!UNDONE - above code assumes the ENT hasn't been removed or respawned as another ent!
+ // !!!UNDONE - fix this by flagging some entities (ie: glass) as immobile. Don't spatialize them.
+ if ( !fvalidendity)
+ {
+ // Turn off the sound while the entity doesn't exist or is not in the PVS.
+ goto ClearAllVolumes;
+ }
+#endif // 0
+
+
+ fdopplerwav = ((ch->wavtype == CHAR_DOPPLER) && !fplayersound);
+ if ( fdopplerwav )
+ {
+ VPROF_("SND_Spatialize doppler", 2, VPROF_BUDGETGROUP_OTHER_SOUND, false, BUDGETFLAG_OTHER );
+ Vector vnearpoint; // point of closest approach to listener,
+ // along sound source forward direction (doppler wavs)
+
+ vnearpoint = ch->origin; // default nearest sound approach point
+
+ // calculate point of closest approach for CHAR_DOPPLER wavs, replace source_vec
+
+ fplaydopplerwav = SND_GetClosestPoint( ch, source_angles, vnearpoint );
+
+ // if doppler sound was 'shot' away from listener, don't play it
+
+ if ( !fplaydopplerwav )
+ goto ClearAllVolumes;
+
+ // find location of doppler left & doppler right points
+
+ SND_GetDopplerPoints( ch, source_angles, vnearpoint, source_doppler_left, source_doppler_right);
+
+ // source_vec_DL is vector from listener to doppler left point
+ // source_vec_DR is vector from listener to doppler right point
+
+ VectorSubtract(source_doppler_left, listener_origin, source_vec_DL );
+ VectorSubtract(source_doppler_right, listener_origin, source_vec_DR );
+
+ // normalized vectors to left and right doppler locations
+
+ dist = VectorNormalize( source_vec_DL );
+ VectorNormalize( source_vec_DR );
+
+ // don't play doppler if out of range
+ // unless recording in the tool, since we may play back in range
+ if ( dist > DOPPLER_RANGE_MAX && !toolframework->IsToolRecording() )
+ goto ClearAllVolumes;
+ }
+ else
+ {
+ // source_vec is vector from listener to sound source
+
+ if ( fplayersound )
+ {
+ // get 2d forward direction vector, ignoring pitch angle
+ Vector listener_forward2d;
+
+ ConvertListenerVectorTo2D( &listener_forward2d, &listener_right );
+
+ // player sounds originate from 1' in front of player, 2d
+
+ VectorMultiply(listener_forward2d, 12.0, source_vec );
+ }
+ else
+ {
+ VectorSubtract(ch->origin, listener_origin, source_vec);
+ }
+
+ // normalize source_vec and get distance from listener to source
+
+ dist = VectorNormalize( source_vec );
+ }
+
+ // calculate dsp mix based on distance to listener & sound level (linear approximation)
+
+ ch->dspmix = SND_GetDspMix( ch, dist );
+
+ // calculate sound source facing direction for CHAR_DIRECTIONAL wavs
+
+ if ( !fplayersound )
+ {
+ ch->dspface = SND_GetFacingDirection( ch, source_angles );
+
+ // calculate mixing parameter for CHAR_DISTVAR wavs
+
+ ch->distmix = SND_GetDistanceMix( ch, dist );
+ }
+
+ // for sounds with a radius, spatialize left/right/front/rear evenly within the radius
+
+ if ( ch->radius > 0 && dist < ch->radius && !fdopplerwav )
+ {
+ float interval = ch->radius * 0.5;
+ mono = dist - interval;
+ if ( mono < 0.0 )
+ mono = 0.0;
+ mono /= interval;
+
+ mono = 1.0 - mono;
+
+ // mono is 0.0 -> 1.0 from radius 100% to radius 50%
+ }
+
+ // don't pan sounds with no attenuation
+ if ( ch->dist_mult <= 0 && !fdopplerwav )
+ {
+ // sound is centered left/right/front/back
+
+ mono = 1.0;
+ bAttenuated = false;
+ }
+
+ if ( ch->wavtype == CHAR_OMNI )
+ {
+ // omni directional sound sources are mono mix, all speakers
+ // ie: they only attenuate by distance, not by source direction.
+
+ mono = 1.0;
+ bAttenuated = false;
+ }
+
+ // calculate gain based on distance, atmospheric attenuation, interposed objects
+ // perform compression as gain approaches 1.0
+
+ gain = SND_GetGain( ch, fplayersound, fmusicsound, looping, dist, bAttenuated );
+
+ // map gain through global mixer by soundtype
+
+ // gain *= SND_GetVolFromSoundtype( ch->soundtype );
+ int last_mixgroupid;
+
+ gain *= MXR_GetVolFromMixGroup( ch->mixgroups, &last_mixgroupid );
+
+ // if playing a word, get volume scale of word - scale gain
+
+ scale = VOX_GetChanVol(ch);
+
+ gain *= scale;
+
+ // save spatialized volume and mixgroupid for display later
+
+ ch->last_mixgroupid = last_mixgroupid;
+
+ if ( fdopplerwav )
+ {
+ VPROF_("SND_Spatialize doppler", 2, VPROF_BUDGETGROUP_OTHER_SOUND, false, BUDGETFLAG_OTHER );
+ // fill out channel volumes for both doppler sound source locations
+ int volumes[CCHANVOLUMES/2];
+
+ // left doppler location
+
+ g_AudioDevice->SpatializeChannel( volumes, ch->master_vol, source_vec_DL, gain, mono );
+
+ // load volumes into channel as crossfade targets
+
+ ChannelSetVolTargets( ch, volumes, IFRONT_LEFT, CCHANVOLUMES/2 );
+
+ // right doppler location
+
+ g_AudioDevice->SpatializeChannel( volumes, ch->master_vol, source_vec_DR, gain, mono );
+
+ // load volumes into channel as crossfade targets
+
+ ChannelSetVolTargets( ch, volumes, IFRONT_LEFTD, CCHANVOLUMES/2 );
+ }
+ else
+ {
+ // fill out channel volumes for single sound source location
+ int volumes[CCHANVOLUMES/2];
+
+ g_AudioDevice->SpatializeChannel( volumes, ch->master_vol, source_vec, gain, mono );
+
+ // Special case for stereo sounds originating from player in surround mode
+ // and special case for musci: remap volumes directly to channels.
+
+ RemapPlayerOrMusicVols( ch, volumes, fplayersound, fmusicsound, mono );
+
+ // load volumes into channel as crossfade volume targets
+
+ ChannelSetVolTargets( ch, volumes, IFRONT_LEFT, CCHANVOLUMES/2 );
+ }
+
+
+ // prevent left/right/front/rear/center volumes from changing too quickly & producing pops
+
+ ChannelUpdateVolXfade( ch );
+
+ // end of first time spatializing sound
+
+ if ( SND_IsInGame() || toolframework->InToolMode() )
+ {
+ ch->flags.bfirstpass = false;
+ }
+
+ // calculate total volume for display later
+ ch->last_vol = gain * (ch->master_vol/255.0);
+
+ return;
+
+ClearAllVolumes:
+
+ // Clear all volumes and return.
+ // This shuts the sound off permanently.
+
+ ChannelClearVolumes( ch );
+
+ // end of first time spatializing sound
+
+ ch->flags.bfirstpass = false;
+}
+
+ConVar snd_defer_trace("snd_defer_trace","1");
+void SND_SpatializeFirstFrameNoTrace( channel_t *pChannel)
+{
+ if ( snd_defer_trace.GetBool() )
+ {
+ // set up tracing state to be non-obstructed
+ pChannel->flags.bfirstpass = false;
+ pChannel->flags.bTraced = true;
+ pChannel->ob_gain = 1.0;
+ pChannel->ob_gain_inc = 1.0;
+ pChannel->ob_gain_target = 1.0;
+ // now spatialize without tracing
+ SND_Spatialize(pChannel);
+ // now reset tracing state to firstpass so the trace gets done on next spatialize
+ pChannel->ob_gain = 0.0;
+ pChannel->ob_gain_inc = 0.0;
+ pChannel->ob_gain_target = 0.0;
+ pChannel->flags.bfirstpass = true;
+ pChannel->flags.bTraced = false;
+ }
+ else
+ {
+ pChannel->ob_gain = 0.0;
+ pChannel->ob_gain_inc = 0.0;
+ pChannel->ob_gain_target = 0.0;
+ pChannel->flags.bfirstpass = true;
+ pChannel->flags.bTraced = false;
+ SND_Spatialize(pChannel);
+ }
+}
+
+
+// search through all channels for a channel that matches this
+// soundsource, entchannel and sfx, and perform alteration on channel
+// as indicated by 'flags' parameter. If shut down request and
+// sfx contains a sentence name, shut off the sentence.
+// returns TRUE if sound was altered,
+// returns FALSE if sound was not found (sound is not playing)
+
+int S_AlterChannel( int soundsource, int entchannel, CSfxTable *sfx, int vol, int pitch, int flags )
+{
+ THREAD_LOCK_SOUND();
+ int ch_idx;
+
+ const char *name = sfx->getname();
+ if ( name && TestSoundChar( name, CHAR_SENTENCE ) )
+ {
+ // This is a sentence name.
+ // For sentences: assume that the entity is only playing one sentence
+ // at a time, so we can just shut off
+ // any channel that has ch->isentence >= 0 and matches the
+ // soundsource.
+
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ ch_idx = list.GetChannelIndex(i);
+ if (channels[ch_idx].soundsource == soundsource
+ && channels[ch_idx].entchannel == entchannel
+ && channels[ch_idx].sfx != NULL )
+ {
+
+ if (flags & SND_CHANGE_PITCH)
+ channels[ch_idx].basePitch = pitch;
+
+ if (flags & SND_CHANGE_VOL)
+ channels[ch_idx].master_vol = vol;
+
+ if (flags & SND_STOP)
+ {
+ S_FreeChannel(&channels[ch_idx]);
+ }
+
+ return TRUE;
+ }
+ }
+ // channel not found
+ return FALSE;
+
+ }
+
+ // regular sound or streaming sound
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+
+ bool bSuccess = false;
+
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ ch_idx = list.GetChannelIndex(i);
+ if ( channels[ch_idx].soundsource == soundsource &&
+ ( ( flags & SND_IGNORE_NAME ) ||
+ ( channels[ch_idx].entchannel == entchannel && channels[ch_idx].sfx == sfx ) ) )
+ {
+ if (flags & SND_CHANGE_PITCH)
+ channels[ch_idx].basePitch = pitch;
+
+ if (flags & SND_CHANGE_VOL)
+ channels[ch_idx].master_vol = vol;
+
+ if (flags & SND_STOP)
+ {
+ S_FreeChannel(&channels[ch_idx]);
+ }
+
+ if ( ( flags & SND_IGNORE_NAME ) == 0 )
+ return TRUE;
+ else
+ bSuccess = true;
+ }
+ }
+
+ return ( bSuccess ) ? ( TRUE ) : ( FALSE );
+}
+
+// set channel flags during initialization based on
+// source name
+
+void S_SetChannelWavtype( channel_t *target_chan, CSfxTable *pSfx )
+{
+ // if 1st or 2nd character of name is CHAR_DRYMIX, sound should be mixed dry with no dsp (ie: music)
+
+ if ( TestSoundChar(pSfx->getname(), CHAR_DRYMIX) )
+ target_chan->flags.bdry = true;
+ else
+ target_chan->flags.bdry = false;
+
+ if ( TestSoundChar(pSfx->getname(), CHAR_FAST_PITCH) )
+ target_chan->flags.bfast_pitch = true;
+ else
+ target_chan->flags.bfast_pitch = false;
+
+ // get sound spatialization encoding
+
+ target_chan->wavtype = 0;
+
+ if ( TestSoundChar( pSfx->getname(), CHAR_DOPPLER ))
+ target_chan->wavtype = CHAR_DOPPLER;
+
+ if ( TestSoundChar( pSfx->getname(), CHAR_DIRECTIONAL ))
+ target_chan->wavtype = CHAR_DIRECTIONAL;
+
+ if ( TestSoundChar( pSfx->getname(), CHAR_DISTVARIANT ))
+ target_chan->wavtype = CHAR_DISTVARIANT;
+
+ if ( TestSoundChar( pSfx->getname(), CHAR_OMNI ))
+ target_chan->wavtype = CHAR_OMNI;
+
+ if ( TestSoundChar( pSfx->getname(), CHAR_SPATIALSTEREO ))
+ target_chan->wavtype = CHAR_SPATIALSTEREO;
+}
+
+
+// Sets bstereowav flag in channel if source is true stere wav
+// sets default wavtype for stereo wavs to CHAR_DISTVARIANT -
+// ie: sound varies with distance (left is close, right is far)
+// Must be called after S_SetChannelWavtype
+
+void S_SetChannelStereo( channel_t *target_chan, CAudioSource *pSource )
+{
+ if ( !pSource )
+ {
+ target_chan->flags.bstereowav = false;
+ return;
+ }
+
+ // returns true only if source data is a stereo wav file.
+ // ie: mp3, voice, sentence are all excluded.
+
+ target_chan->flags.bstereowav = pSource->IsStereoWav();
+
+ // Default stereo wavtype:
+
+ // just player standard stereo wavs on player entity - no override.
+
+ if ( IsSoundSourceLocalPlayer( target_chan->soundsource ) )
+ return;
+
+ // default wavtype for stereo wavs is OMNI - except for drymix or sounds with 0 attenuation
+
+ if ( target_chan->flags.bstereowav && !target_chan->wavtype && !target_chan->flags.bdry && target_chan->dist_mult )
+ // target_chan->wavtype = CHAR_DISTVARIANT;
+ target_chan->wavtype = CHAR_OMNI;
+}
+
+// =======================================================================
+// Channel volume management routines:
+
+// channel volumes crossfade between values over time
+// to prevent pops due to rapid spatialization changes
+// =======================================================================
+
+// return true if all volumes and target volumes for channel are less/equal to 'vol'
+
+bool BChannelLowVolume( channel_t *pch, int vol_min )
+{
+ int max = -1;
+ int max_target = -1;
+ int vol;
+ int vol_target;
+
+ for (int i = 0; i < CCHANVOLUMES; i++)
+ {
+ vol = (int)(pch->fvolume[i]);
+ vol_target = (int)(pch->fvolume_target[i]);
+
+ if (vol > max)
+ max = vol;
+
+ if (vol_target > max_target)
+ max_target = vol_target;
+ }
+
+ return (max <= vol_min && max_target <= vol_min);
+}
+
+// Get the loudest actual volume for a channel (not counting targets).
+float ChannelLoudestCurVolume( const channel_t * RESTRICT pch )
+{
+ float loudest = pch->fvolume[0];
+ for (int i = 1; i < CCHANVOLUMES; i++)
+ {
+ loudest = fpmax(loudest, pch->fvolume[i]);
+ }
+ return loudest;
+}
+
+// clear all volumes, targets, crossfade increments
+
+void ChannelClearVolumes( channel_t *pch )
+{
+ for (int i = 0; i < CCHANVOLUMES; i++)
+ {
+ pch->fvolume[i] = 0.0;
+ pch->fvolume_target[i] = 0.0;
+ pch->fvolume_inc[i] = 0.0;
+ }
+}
+
+// return current volume as integer
+
+int ChannelGetVol( channel_t *pch, int ivol )
+{
+ Assert(ivol < CCHANVOLUMES);
+ return (int)(pch->fvolume[ivol]);
+}
+
+// return maximum current output volume
+
+int ChannelGetMaxVol( channel_t *pch )
+{
+ float max = 0.0;
+
+ for (int i = 0; i < CCHANVOLUMES; i++)
+ {
+ if (pch->fvolume[i] > max)
+ max = pch->fvolume[i];
+ }
+
+ return (int)max;
+}
+
+// set current volume (clears crossfading - instantaneous value change)
+
+void ChannelSetVol( channel_t *pch, int ivol, int vol )
+{
+ Assert(ivol < CCHANVOLUMES);
+
+ pch->fvolume[ivol] = (float)(clamp(vol, 0, 255));
+
+ pch->fvolume_target[ivol] = pch->fvolume[ivol];
+ pch->fvolume_inc[ivol] = 0.0;
+}
+
+// copy current channel volumes into target array, starting at ivol, copying cvol entries
+
+void ChannelCopyVolumes( channel_t *pch, int *pvolume_dest, int ivol_start, int cvol )
+{
+ Assert (ivol_start < CCHANVOLUMES);
+ Assert (ivol_start + cvol <= CCHANVOLUMES);
+
+ for (int i = 0; i < cvol; i++)
+ pvolume_dest[i] = (int)(pch->fvolume[i + ivol_start]);
+}
+
+// volume has hit target, shut off crossfading increment
+
+inline void ChannelStopVolXfade( channel_t *pch, int ivol )
+{
+ pch->fvolume[ivol] = pch->fvolume_target[ivol];
+ pch->fvolume_inc[ivol] = 0.0;
+}
+
+#define VOL_XFADE_TIME 0.070 // channel volume crossfade time in seconds
+
+#define VOL_INCR_MAX 20.0 // never change volume by more than +/-N units per frame
+
+// set volume target and volume increment (for crossfade) for channel & speaker
+
+void ChannelSetVolTarget( channel_t *pch, int ivol, int volume_target )
+{
+ float frametime = g_pSoundServices->GetHostFrametime();
+ float speed;
+ float vol_target = (float)(clamp(volume_target, 0, 255));
+ float vol_current;
+
+ Assert(ivol < CCHANVOLUMES);
+
+ // set volume target
+
+ pch->fvolume_target[ivol] = vol_target;
+
+ // current volume
+
+ vol_current = pch->fvolume[ivol];
+
+ // if first time spatializing, set target = volume with no crossfade
+ // if current & target volumes are close - don't bother crossfading
+
+ if ( pch->flags.bfirstpass || (fabs(vol_target - vol_current) < 5.0))
+ {
+ // set current volume = target, no increment
+
+ ChannelStopVolXfade( pch, ivol);
+ return;
+ }
+
+ // get crossfade increment 'speed' (volume change per frame)
+
+ speed = ( frametime / VOL_XFADE_TIME ) * (vol_target - vol_current);
+
+ // make sure we never increment by more than +/- VOL_INCR_MAX volume units per frame
+
+ speed = clamp(speed, (float) -VOL_INCR_MAX, (float) VOL_INCR_MAX);
+
+ pch->fvolume_inc[ivol] = speed;
+}
+
+// set volume targets, using array pvolume as source volumes.
+// set into channel volumes starting at ivol_offset index
+// set cvol volumes
+
+void ChannelSetVolTargets( channel_t *pch, int *pvolumes, int ivol_offset, int cvol )
+{
+ int volume_target;
+
+ Assert(ivol_offset + cvol <= CCHANVOLUMES);
+
+ for (int i = 0; i < cvol; i++)
+ {
+ volume_target = pvolumes[i];
+
+ ChannelSetVolTarget( pch, ivol_offset + i, volume_target );
+ }
+}
+
+
+// Call once per frame, per channel:
+// update all volume crossfades, from fvolume -> fvolume_target
+// if current volume reaches target, set increment to 0
+
+void ChannelUpdateVolXfade( channel_t *pch )
+{
+ float fincr;
+
+ for (int i = 0; i < CCHANVOLUMES; i++)
+ {
+ fincr = pch->fvolume_inc[i];
+
+ if (fincr != 0.0)
+ {
+ pch->fvolume[i] += fincr;
+
+ // test for hit target
+
+ if (fincr > 0.0)
+ {
+ if (pch->fvolume[i] >= pch->fvolume_target[i])
+ ChannelStopVolXfade( pch, i );
+ }
+ else
+ {
+ if (pch->fvolume[i] <= pch->fvolume_target[i])
+ ChannelStopVolXfade( pch, i );
+ }
+ }
+ }
+}
+
+// =======================================================================
+// S_StartDynamicSound
+// =======================================================================
+// Start a sound effect for the given entity on the given channel (ie; voice, weapon etc).
+// Try to grab a channel out of the 8 dynamic spots available.
+// Currently used for looping sounds, streaming sounds, sentences, and regular entity sounds.
+// NOTE: volume is 0.0 - 1.0 and attenuation is 0.0 - 1.0 when passed in.
+// Pitch changes playback pitch of wave by % above or below 100. Ignored if pitch == 100
+
+// NOTE: it's not a good idea to play looping sounds through StartDynamicSound, because
+// if the looping sound starts out of range, or is bumped from the buffer by another sound
+// it will never be restarted. Use StartStaticSound (pass CHAN_STATIC to EMIT_SOUND or
+// SV_StartSound.
+
+int S_StartDynamicSound( StartSoundParams_t& params )
+{
+ Assert( params.staticsound == false );
+
+ channel_t *target_chan;
+ int vol;
+
+ if ( !g_AudioDevice || !g_AudioDevice->IsActive())
+ return 0;
+
+ if (!params.pSfx)
+ return 0;
+
+ // For debugging to see the actual name of the sound...
+ char sndname[ MAX_OSPATH ];
+ Q_strncpy( sndname, params.pSfx->getname(), sizeof( sndname ) );
+
+ // Msg("Start sound %s\n", pSfx->getname() );
+
+ // override the entchannel to CHAN_STREAM if this is a
+ // non-voice stream sound.
+ if ( TestSoundChar(sndname, CHAR_STREAM ) && params.entchannel != CHAN_VOICE && params.entchannel != CHAN_VOICE2 )
+ params.entchannel = CHAN_STREAM;
+
+ vol = params.fvol*255;
+
+ if (vol > 255)
+ {
+ DevMsg("S_StartDynamicSound: %s volume > 255", sndname );
+ vol = 255;
+ }
+
+ THREAD_LOCK_SOUND();
+
+ if ( params.flags & (SND_STOP|SND_CHANGE_VOL|SND_CHANGE_PITCH) )
+ {
+ if ( S_AlterChannel( params.soundsource, params.entchannel, params.pSfx, vol, params.pitch, params.flags) )
+ return 0;
+ if ( params.flags & SND_STOP )
+ return 0;
+ // fall through - if we're not trying to stop the sound,
+ // and we didn't find it (it's not playing), go ahead and start it up
+ }
+
+ if (params.pitch == 0)
+ {
+ DevMsg ("Warning: S_StartDynamicSound (%s) Ignored, called with pitch 0\n", sndname );
+ return 0;
+ }
+
+ // pick a channel to play on
+ target_chan = SND_PickDynamicChannel(params.soundsource, params.entchannel, params.origin, params.pSfx, params.delay, (params.flags & SND_DO_NOT_OVERWRITE_EXISTING_ON_CHANNEL) != 0 );
+ if ( !target_chan )
+ return 0;
+
+ int channelIndex = (int)( target_chan - channels );
+ g_AudioDevice->ChannelReset( params.soundsource, channelIndex, target_chan->dist_mult );
+
+#ifdef DEBUG_CHANNELS
+ {
+ char szTmp[128];
+ Q_snprintf(szTmp, sizeof( szTmp ), "Sound %s playing on Dynamic game channel %d\n", sndname, IWavstreamOfCh(target_chan));
+ Plat_DebugString(szTmp);
+ }
+#endif
+
+ bool bIsSentence = TestSoundChar( sndname, CHAR_SENTENCE );
+
+ SND_ActivateChannel( target_chan );
+ ChannelClearVolumes( target_chan );
+
+ target_chan->userdata = params.userdata;
+ target_chan->initialStreamPosition = params.initialStreamPosition;
+
+ VectorCopy(params.origin, target_chan->origin);
+ VectorCopy(params.direction, target_chan->direction);
+
+ // never update positions if source entity is 0
+ target_chan->flags.bUpdatePositions = params.bUpdatePositions && (params.soundsource == 0 ? 0 : 1);
+
+ // reference_dist / (reference_power_level / actual_power_level)
+ target_chan->flags.m_bCompatibilityAttenuation = SNDLEVEL_IS_COMPATIBILITY_MODE( params.soundlevel );
+ if ( target_chan->flags.m_bCompatibilityAttenuation )
+ {
+ // Translate soundlevel from its 'encoded' value to a real soundlevel that we can use in the sound system.
+ params.soundlevel = SNDLEVEL_FROM_COMPATIBILITY_MODE( params.soundlevel );
+ }
+
+ target_chan->dist_mult = SNDLVL_TO_DIST_MULT( params.soundlevel );
+
+ S_SetChannelWavtype( target_chan, params.pSfx );
+
+ target_chan->master_vol = vol;
+ target_chan->soundsource = params.soundsource;
+ target_chan->entchannel = params.entchannel;
+ target_chan->basePitch = params.pitch;
+ target_chan->flags.isSentence = false;
+ target_chan->radius = 0;
+ target_chan->sfx = params.pSfx;
+ target_chan->special_dsp = params.specialdsp;
+ target_chan->flags.fromserver = params.fromserver;
+ target_chan->flags.bSpeaker = (params.flags & SND_SPEAKER) ? 1 : 0;
+ target_chan->speakerentity = params.speakerentity;
+
+ target_chan->flags.m_bShouldPause = (params.flags & SND_SHOULDPAUSE) ? 1 : 0;
+
+ // initialize dsp room mixing params
+ target_chan->dsp_mix_min = -1;
+ target_chan->dsp_mix_max = -1;
+
+ CAudioSource *pSource = NULL;
+
+ if ( bIsSentence )
+ {
+ // this is a sentence
+ // link all words and load the first word
+
+ // NOTE: sentence names stored in the cache lookup are
+ // prepended with a '!'. Sentence names stored in the
+ // sentence file do not have a leading '!'.
+ VOX_LoadSound( target_chan, PSkipSoundChars( sndname ) );
+ }
+ else
+ {
+ // regular or streamed sound fx
+ pSource = S_LoadSound( params.pSfx, target_chan );
+ if ( pSource && !IsValidSampleRate( pSource->SampleRate() ) )
+ {
+ Warning( "*** Invalid sample rate (%d) for sound '%s'.\n", pSource->SampleRate(), sndname );
+ }
+
+ if ( !pSource && !params.pSfx->m_bIsLateLoad )
+ {
+ Warning( "Failed to load sound \"%s\", file probably missing from disk/repository\n", sndname );
+ }
+
+ }
+
+ if (!target_chan->pMixer)
+ {
+ // couldn't load the sound's data, or sentence has 0 words (this is not an error)
+ S_FreeChannel( target_chan );
+ return 0;
+ }
+
+ int nSndShowStart = snd_showstart.GetInt();
+
+ // TODO: Support looping sounds through speakers.
+ // If the sound is from a speaker, and it's looping, ignore it.
+ if ( target_chan->flags.bSpeaker )
+ {
+ if ( params.pSfx->pSource && params.pSfx->pSource->IsLooped() )
+ {
+ if (nSndShowStart > 0 && nSndShowStart < 7 && nSndShowStart != 4)
+ {
+ DevMsg("DynamicSound : Speaker ignored looping sound: %s\n", sndname );
+ }
+
+ S_FreeChannel( target_chan );
+ return 0;
+ }
+ }
+
+ S_SetChannelStereo( target_chan, pSource );
+
+ if (nSndShowStart == 5)
+ {
+ snd_showstart.SetValue(6); // debug: show gain for next spatialize only
+ nSndShowStart = 6;
+ }
+
+ // get sound type before we spatialize
+ MXR_GetMixGroupFromSoundsource( target_chan, params.soundsource, params.soundlevel );
+
+ // skip the trace on the first spatialization. This channel may be stolen
+ // by another sound played this frame. Defer the trace to the mix loop
+ SND_SpatializeFirstFrameNoTrace(target_chan);
+
+ if (nSndShowStart > 0 && nSndShowStart < 7 && nSndShowStart != 4)
+ {
+ channel_t *pTargetChan = target_chan;
+
+ DevMsg( "DynamicSound %s : src %d : channel %d : %d dB : vol %.2f : time %.3f\n", sndname, params.soundsource, params.entchannel, params.soundlevel, params.fvol, g_pSoundServices->GetHostTime() );
+ if (nSndShowStart == 2 || nSndShowStart == 5)
+ DevMsg( "\t dspmix %1.2f : distmix %1.2f : dspface %1.2f : lvol %1.2f : cvol %1.2f : rvol %1.2f : rlvol %1.2f : rrvol %1.2f\n",
+ pTargetChan->dspmix, pTargetChan->distmix, pTargetChan->dspface,
+ pTargetChan->fvolume[IFRONT_LEFT], pTargetChan->fvolume[IFRONT_CENTER], pTargetChan->fvolume[IFRONT_RIGHT], pTargetChan->fvolume[IREAR_LEFT], pTargetChan->fvolume[IREAR_RIGHT] );
+ if (nSndShowStart == 3)
+ DevMsg( "\t x: %4f y: %4f z: %4f\n", pTargetChan->origin.x, pTargetChan->origin.y, pTargetChan->origin.z );
+
+ if ( snd_visualize.GetInt() )
+ {
+ CDebugOverlay::AddTextOverlay( pTargetChan->origin, 2.0f, sndname );
+ }
+ }
+
+ // If a client can't hear a sound when they FIRST receive the StartSound message,
+ // the client will never be able to hear that sound. This is so that out of
+ // range sounds don't fill the playback buffer. For streaming sounds, we bypass this optimization.
+
+ if ( BChannelLowVolume( target_chan, 0 ) && !toolframework->IsToolRecording() )
+ {
+ // Looping sounds don't use this optimization because they should stick around until they're killed.
+ // Also bypass for speech (GetSentence)
+ if ( !params.pSfx->pSource || (!params.pSfx->pSource->IsLooped() && !params.pSfx->pSource->GetSentence()) )
+ {
+ // if this is long sound, play the whole thing.
+ if (!SND_IsLongWave( target_chan ))
+ {
+ // DevMsg("S_StartDynamicSound: spatialized to 0 vol & ignored %s", sndname);
+ S_FreeChannel( target_chan );
+ return 0; // not audible at all
+ }
+ }
+ }
+
+ // Init client entity mouth movement vars
+ target_chan->flags.m_bIgnorePhonemes = ( params.flags & SND_IGNORE_PHONEMES ) != 0;
+ SND_InitMouth(target_chan);
+
+ if ( IsX360() && params.delay < 0 )
+ {
+ params.delay = 0;
+ target_chan->flags.delayed_start = true;
+ }
+
+ // Pre-startup delay. Compute # of samples over which to mix in zeros from data source before
+ // actually reading first set of samples
+ if ( params.delay != 0.0f )
+ {
+ Assert( target_chan->sfx );
+ Assert( target_chan->sfx->pSource );
+
+ // delay count is computed at the sampling rate of the source because the output rate will
+ // match the source rate when the sound is mixed
+ float rate = target_chan->sfx->pSource->SampleRate();
+ int delaySamples = (int)( params.delay * rate );
+
+ if ( params.delay > 0 )
+ {
+ target_chan->pMixer->SetStartupDelaySamples( delaySamples );
+ target_chan->flags.delayed_start = true;
+ }
+ else
+ {
+ int skipSamples = -delaySamples;
+ int totalSamples = target_chan->sfx->pSource->SampleCount();
+ if ( target_chan->sfx->pSource->IsLooped() )
+ {
+ skipSamples = skipSamples % totalSamples;
+ }
+ if ( skipSamples >= totalSamples )
+ {
+ S_FreeChannel( target_chan );
+ return 0;
+ }
+ target_chan->pitch = target_chan->basePitch * 0.01f;
+ target_chan->pMixer->SkipSamples( target_chan, skipSamples, rate, 0 );
+ target_chan->ob_gain_target = 1.0f;
+ target_chan->ob_gain = 1.0f;
+ target_chan->ob_gain_inc = 0.0;
+ target_chan->flags.bfirstpass = false;
+ target_chan->flags.delayed_start = true;
+ }
+ }
+
+ g_pSoundServices->OnSoundStarted( target_chan->guid, params, sndname );
+ return target_chan->guid;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *name -
+// Output : CSfxTable
+//-----------------------------------------------------------------------------
+CSfxTable *S_DummySfx( const char *name )
+{
+ dummySfx.setname( name );
+ return &dummySfx;
+}
+
+/*
+=================
+S_StartStaticSound
+=================
+Start playback of a sound, loaded into the static portion of the channel array.
+Currently, this should be used for looping ambient sounds, looping sounds
+that should not be interrupted until complete, non-creature sentences,
+and one-shot ambient streaming sounds. Can also play 'regular' sounds one-shot,
+in case designers want to trigger regular game sounds.
+Pitch changes playback pitch of wave by % above or below 100. Ignored if pitch == 100
+
+ NOTE: volume is 0.0 - 1.0 and attenuation is 0.0 - 1.0 when passed in.
+*/
+
+int S_StartStaticSound( StartSoundParams_t& params )
+{
+ Assert( params.staticsound == true );
+
+ channel_t *ch;
+ CAudioSource *pSource = NULL;
+
+ if ( !g_AudioDevice->IsActive() )
+ return 0;
+
+ if ( !params.pSfx )
+ return 0;
+
+ // For debugging to see the actual name of the sound...
+ char sndname[ MAX_OSPATH ];
+ Q_strncpy( sndname, params.pSfx->getname(), sizeof( sndname ) );
+// Msg("Start static sound %s\n", pSfx->getname() );
+
+ int vol = params.fvol * 255;
+ if ( vol > 255 )
+ {
+ DevMsg( "S_StartStaticSound: %s volume > 255", sndname );
+ vol = 255;
+ }
+
+ int nSndShowStart = snd_showstart.GetInt();
+
+ if ((params.flags & SND_STOP) && nSndShowStart > 0)
+ DevMsg("S_StartStaticSound: %s Stopped.\n", sndname);
+
+ if ((params.flags & SND_STOP) || (params.flags & SND_CHANGE_VOL) || (params.flags & SND_CHANGE_PITCH))
+ {
+ if (S_AlterChannel(params.soundsource, params.entchannel, params.pSfx, vol, params.pitch, params.flags) || (params.flags & SND_STOP))
+ return 0;
+ }
+
+ if ( params.pitch == 0 )
+ {
+ DevMsg( "Warning: S_StartStaticSound Ignored, called with pitch 0\n");
+ return 0;
+ }
+
+ // First, make sure the sound source entity is even in the PVS.
+ float flSoundRadius = 0.0f;
+
+ bool looping = false;
+
+ /*
+ CAudioSource *pSource = pSfx ? pSfx->pSource : NULL;
+ if ( pSource )
+ {
+ looping = pSource->IsLooped();
+ }
+ */
+
+ SpatializationInfo_t si;
+ si.info.Set(
+ params.soundsource,
+ params.entchannel,
+ params.pSfx ? sndname : "",
+ params.origin,
+ params.direction,
+ vol,
+ params.soundlevel,
+ looping,
+ params.pitch,
+ listener_origin,
+ params.speakerentity );
+
+ si.type = SpatializationInfo_t::SI_INCREATION;
+
+ si.pOrigin = NULL;
+ si.pAngles = NULL;
+ si.pflRadius = &flSoundRadius;
+
+ g_pSoundServices->GetSoundSpatialization( params.soundsource, si );
+
+ // pick a channel to play on from the static area
+ THREAD_LOCK_SOUND();
+
+ ch = SND_PickStaticChannel(params.soundsource, params.pSfx); // Autolooping sounds are always fixed origin(?)
+ if ( !ch )
+ return 0;
+
+ SND_ActivateChannel( ch );
+ ChannelClearVolumes( ch );
+
+ ch->userdata = params.userdata;
+ ch->initialStreamPosition = params.initialStreamPosition;
+
+ if ( ch->userdata != 0 )
+ {
+ g_pSoundServices->GetToolSpatialization( ch->userdata, ch->guid, si );
+ }
+
+ int channelIndex = ch - channels;
+ g_AudioDevice->ChannelReset( params.soundsource, channelIndex, ch->dist_mult );
+
+#ifdef DEBUG_CHANNELS
+ {
+ char szTmp[128];
+ Q_snprintf(szTmp, sizeof( szTmp ), "Sound %s playing on Static game channel %d\n", sfxin->name, IWavstreamOfCh(ch));
+ Plat_DebugString(szTmp);
+ }
+#endif
+
+ if ( TestSoundChar(sndname, CHAR_SENTENCE) )
+ {
+ // this is a sentence. link words to play in sequence.
+
+ // NOTE: sentence names stored in the cache lookup are
+ // prepended with a '!'. Sentence names stored in the
+ // sentence file do not have a leading '!'.
+
+ // link all words and load the first word
+ VOX_LoadSound( ch, PSkipSoundChars(sndname) );
+ }
+ else
+ {
+ // load regular or stream sound
+ pSource = S_LoadSound( params.pSfx, ch );
+ if ( pSource && !IsValidSampleRate( pSource->SampleRate() ) )
+ {
+ Warning( "*** Invalid sample rate (%d) for sound '%s'.\n", pSource->SampleRate(), sndname );
+ }
+
+ if ( !pSource && !params.pSfx->m_bIsLateLoad )
+ {
+ Warning( "Failed to load sound \"%s\", file probably missing from disk/repository\n", sndname );
+ }
+
+ ch->sfx = params.pSfx;
+ ch->flags.isSentence = false;
+ }
+
+ if ( !ch->pMixer )
+ {
+ // couldn't load sounds' data, or sentence has 0 words (not an error)
+ S_FreeChannel( ch );
+ return 0;
+ }
+
+ VectorCopy (params.origin, ch->origin);
+ VectorCopy (params.direction, ch->direction);
+
+ // never update positions if source entity is 0
+ ch->flags.bUpdatePositions = params.bUpdatePositions && (params.soundsource == 0 ? 0 : 1);
+
+ ch->master_vol = vol;
+
+ ch->flags.m_bCompatibilityAttenuation = SNDLEVEL_IS_COMPATIBILITY_MODE( params.soundlevel );
+ if ( ch->flags.m_bCompatibilityAttenuation )
+ {
+ // Translate soundlevel from its 'encoded' value to a real soundlevel that we can use in the sound system.
+ params.soundlevel = SNDLEVEL_FROM_COMPATIBILITY_MODE( params.soundlevel );
+ }
+
+ ch->dist_mult = SNDLVL_TO_DIST_MULT( params.soundlevel );
+
+ S_SetChannelWavtype( ch, params.pSfx );
+
+ ch->basePitch = params.pitch;
+ ch->soundsource = params.soundsource;
+ ch->entchannel = params.entchannel;
+ ch->special_dsp = params.specialdsp;
+ ch->flags.fromserver = params.fromserver;
+ ch->flags.bSpeaker = (params.flags & SND_SPEAKER) ? 1 : 0;
+ ch->speakerentity = params.speakerentity;
+
+ ch->flags.m_bShouldPause = (params.flags & SND_SHOULDPAUSE) ? 1 : 0;
+
+ // TODO: Support looping sounds through speakers.
+ // If the sound is from a speaker, and it's looping, ignore it.
+ if ( ch->flags.bSpeaker )
+ {
+ if ( params.pSfx->pSource && params.pSfx->pSource->IsLooped() )
+ {
+ if (nSndShowStart > 0 && nSndShowStart < 7 && nSndShowStart != 4)
+ {
+ DevMsg("StaticSound : Speaker ignored looping sound: %s\n", sndname);
+ }
+
+ S_FreeChannel( ch );
+ return 0;
+ }
+ }
+
+ // set the default radius
+ ch->radius = flSoundRadius;
+
+ S_SetChannelStereo( ch, pSource );
+
+ // initialize dsp room mixing params
+ ch->dsp_mix_min = -1;
+ ch->dsp_mix_max = -1;
+
+ if (nSndShowStart == 5)
+ {
+ snd_showstart.SetValue(6); // display gain once only
+ nSndShowStart = 6;
+ }
+
+ // get sound type before we spatialize
+
+ MXR_GetMixGroupFromSoundsource( ch, params.soundsource, params.soundlevel );
+
+ // skip the trace on the first spatialization. This channel may be stolen
+ // by another sound played this frame. Defer the trace to the mix loop
+ SND_SpatializeFirstFrameNoTrace(ch);
+
+ // Init client entity mouth movement vars
+ ch->flags.m_bIgnorePhonemes = ( params.flags & SND_IGNORE_PHONEMES ) != 0;
+ SND_InitMouth( ch );
+
+ if ( IsX360() && params.delay < 0 )
+ {
+ // X360TEMP: Can't support yet, but going to.
+ params.delay = 0;
+ }
+
+ // Pre-startup delay. Compute # of samples over which to mix in zeros from data source before
+ // actually reading first set of samples
+ if ( params.delay != 0.0f )
+ {
+ Assert( ch->sfx );
+ Assert( ch->sfx->pSource );
+
+ float rate = ch->sfx->pSource->SampleRate();
+
+ int delaySamples = (int)( params.delay * rate * params.pitch * 0.01f );
+
+ ch->pMixer->SetStartupDelaySamples( delaySamples );
+
+ if ( params.delay > 0 )
+ {
+ ch->pMixer->SetStartupDelaySamples( delaySamples );
+ ch->flags.delayed_start = true;
+ }
+ else
+ {
+ int skipSamples = -delaySamples;
+ int totalSamples = ch->sfx->pSource->SampleCount();
+
+ if ( ch->sfx->pSource->IsLooped() )
+ {
+ skipSamples = skipSamples % totalSamples;
+ }
+
+ if ( skipSamples >= totalSamples )
+ {
+ S_FreeChannel( ch );
+ return 0;
+ }
+
+ ch->pitch = ch->basePitch * 0.01f;
+ ch->pMixer->SkipSamples( ch, skipSamples, rate, 0 );
+ ch->ob_gain_target = 1.0f;
+ ch->ob_gain = 1.0f;
+ ch->ob_gain_inc = 0.0f;
+ ch->flags.bfirstpass = false;
+ }
+ }
+
+ if ( S_IsMusic( ch ) )
+ {
+ // See if we have "music" of same name playing from "world" which means we save/restored this sound already. If so,
+ // kill the new version and update the soundsource
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ channel_t *pChannel = list.GetChannel(i);
+ // Don't mess with the channel we just created, of course
+ if ( ch == pChannel )
+ continue;
+ if ( ch->sfx != pChannel->sfx )
+ continue;
+ if ( pChannel->soundsource != SOUND_FROM_WORLD )
+ continue;
+ if ( !S_IsMusic( pChannel ) )
+ continue;
+
+ DevMsg( 1, "Hooking duplicate restored song track %s\n", sndname );
+
+ // the new channel will have an updated soundsource and probably
+ // has an updated pitch or volume since we are receiving this sound message
+ // after the sound has started playing (usually a volume change)
+ // copy that data out of the source
+ pChannel->soundsource = ch->soundsource;
+ pChannel->master_vol = ch->master_vol;
+ pChannel->basePitch = ch->basePitch;
+ pChannel->pitch = ch->pitch;
+ S_FreeChannel( ch );
+
+ return 0;
+ }
+ }
+
+ g_pSoundServices->OnSoundStarted( ch->guid, params, sndname );
+
+ if (nSndShowStart > 0 && nSndShowStart < 7 && nSndShowStart != 4)
+ {
+ DevMsg( "StaticSound %s : src %d : channel %d : %d dB : vol %.2f : radius %.0f : time %.3f\n", sndname, params.soundsource, params.entchannel, params.soundlevel, params.fvol, flSoundRadius, g_pSoundServices->GetHostTime() );
+ if (nSndShowStart == 2 || nSndShowStart == 5)
+ DevMsg( "\t dspmix %1.2f : distmix %1.2f : dspface %1.2f : lvol %1.2f : cvol %1.2f : rvol %1.2f : rlvol %1.2f : rrvol %1.2f\n",
+ ch->dspmix, ch->distmix, ch->dspface,
+ ch->fvolume[IFRONT_LEFT], ch->fvolume[IFRONT_CENTER], ch->fvolume[IFRONT_RIGHT], ch->fvolume[IREAR_LEFT], ch->fvolume[IREAR_RIGHT] );
+ if (nSndShowStart == 3)
+ DevMsg( "\t x: %4f y: %4f z: %4f\n", ch->origin.x, ch->origin.y, ch->origin.z );
+ }
+
+ return ch->guid;
+}
+
+#ifdef STAGING_ONLY
+static ConVar snd_filter( "snd_filter", "", FCVAR_CHEAT );
+#endif // STAGING_ONLY
+
+int S_StartSound( StartSoundParams_t& params )
+{
+
+ if( ! params.pSfx )
+ {
+ return 0;
+ }
+
+#ifdef STAGING_ONLY
+ if ( snd_filter.GetString()[ 0 ] && !Q_stristr( params.pSfx->getname(), snd_filter.GetString() ) )
+ {
+ return 0;
+ }
+#endif // STAGING_ONLY
+
+ if ( IsX360() && params.delay < 0 && !params.initialStreamPosition && params.pSfx )
+ {
+ // calculate an initial stream position from the expected sample position
+ float rate = params.pSfx->pSource->SampleRate();
+ int samplePosition = (int)( -params.delay * rate * params.pitch * 0.01f );
+ params.initialStreamPosition = params.pSfx->pSource->SampleToStreamPosition( samplePosition );
+ }
+
+ if ( params.staticsound )
+ {
+ VPROF_( "StartStaticSound", 0, VPROF_BUDGETGROUP_OTHER_SOUND, false, BUDGETFLAG_OTHER );
+ return S_StartStaticSound( params );
+ }
+ else
+ {
+ VPROF_( "StartDynamicSound", 0, VPROF_BUDGETGROUP_OTHER_SOUND, false, BUDGETFLAG_OTHER );
+ return S_StartDynamicSound( params );
+ }
+}
+
+// Restart all the sounds on the specified channel
+inline bool IsChannelLooped( int iChannel )
+{
+ return (channels[iChannel].sfx &&
+ channels[iChannel].sfx->pSource &&
+ channels[iChannel].sfx->pSource->IsLooped() );
+}
+
+int S_GetCurrentStaticSounds( SoundInfo_t *pResult, int nSizeResult, int entchannel )
+{
+ int nSpaceRemaining = nSizeResult;
+ for (int i = MAX_DYNAMIC_CHANNELS; i < total_channels && nSpaceRemaining; i++)
+ {
+ if ( channels[i].entchannel == entchannel && channels[i].sfx )
+ {
+ pResult->Set( channels[i].soundsource,
+ channels[i].entchannel,
+ channels[i].sfx->getname(),
+ channels[i].origin,
+ channels[i].direction,
+ ( (float)channels[i].master_vol / 255.0 ),
+ DIST_MULT_TO_SNDLVL( channels[i].dist_mult ),
+ IsChannelLooped( i ),
+ channels[i].basePitch,
+ listener_origin,
+ channels[i].speakerentity );
+ pResult++;
+ nSpaceRemaining--;
+ }
+ }
+ return (nSizeResult - nSpaceRemaining);
+}
+
+
+// Stop all sounds for entity on a channel.
+void S_StopSound(int soundsource, int entchannel)
+{
+ THREAD_LOCK_SOUND();
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ channel_t *pChannel = list.GetChannel(i);
+ if (pChannel->soundsource == soundsource
+ && pChannel->entchannel == entchannel)
+ {
+ S_FreeChannel( pChannel );
+ }
+ }
+}
+
+channel_t *S_FindChannelByGuid( int guid )
+{
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ channel_t *pChannel = list.GetChannel(i);
+ if ( pChannel->guid == guid )
+ {
+ return pChannel;
+ }
+ }
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : guid -
+//-----------------------------------------------------------------------------
+void S_StopSoundByGuid( int guid )
+{
+ THREAD_LOCK_SOUND();
+ channel_t *pChannel = S_FindChannelByGuid( guid );
+ if ( pChannel )
+ {
+ S_FreeChannel( pChannel );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : guid -
+//-----------------------------------------------------------------------------
+float S_SoundDurationByGuid( int guid )
+{
+ channel_t *pChannel = S_FindChannelByGuid( guid );
+ if ( !pChannel || !pChannel->sfx )
+ return 0.0f;
+
+ // NOTE: Looping sounds will return the length of a single loop
+ // Use S_IsLoopingSoundByGuid to see if they are looped
+ float flRate = pChannel->sfx->pSource->SampleRate() * pChannel->basePitch * 0.01f;
+ int nTotalSamples = pChannel->sfx->pSource->SampleCount();
+ return (flRate != 0.0f) ? nTotalSamples / flRate : 0.0f;
+}
+
+
+//-----------------------------------------------------------------------------
+// Is this sound a looping sound?
+//-----------------------------------------------------------------------------
+bool S_IsLoopingSoundByGuid( int guid )
+{
+ channel_t *pChannel = S_FindChannelByGuid( guid );
+ if ( !pChannel || !pChannel->sfx )
+ return false;
+
+ return( pChannel->sfx->pSource->IsLooped() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Note that the guid is preincremented, so we can just return the current value as the "last sound" indicator
+// Input : -
+// Output : int
+//-----------------------------------------------------------------------------
+int S_GetGuidForLastSoundEmitted()
+{
+ return s_nSoundGuid;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : guid -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool S_IsSoundStillPlaying( int guid )
+{
+ channel_t *pChannel = S_FindChannelByGuid( guid );
+ return pChannel != NULL ? true : false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : guid -
+// fvol -
+//-----------------------------------------------------------------------------
+void S_SetVolumeByGuid( int guid, float fvol )
+{
+ channel_t *pChannel = S_FindChannelByGuid( guid );
+ pChannel->master_vol = 255.0f * clamp( fvol, 0.0f, 1.0f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : guid -
+// Output : float
+//-----------------------------------------------------------------------------
+float S_GetElapsedTimeByGuid( int guid )
+{
+ channel_t *pChannel = S_FindChannelByGuid( guid );
+ if ( !pChannel )
+ return 0.0f;
+
+ CAudioMixer *mixer = pChannel->pMixer;
+ if ( !mixer )
+ return 0.0f;
+
+ float elapsed = mixer->GetSamplePosition() / ( mixer->GetSource()->SampleRate() * pChannel->pitch * 0.01f );
+ return elapsed;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : sndlist -
+//-----------------------------------------------------------------------------
+void S_GetActiveSounds( CUtlVector< SndInfo_t >& sndlist )
+{
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ channel_t *ch = list.GetChannel(i);
+
+ SndInfo_t info;
+
+ info.m_nGuid = ch->guid;
+ info.m_filenameHandle = ch->sfx ? ch->sfx->GetFileNameHandle() : NULL;
+ info.m_nSoundSource = ch->soundsource;
+ info.m_nChannel = ch->entchannel;
+ // 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
+ info.m_nSpeakerEntity = ch->speakerentity;
+ info.m_flVolume = (float)ch->master_vol / 255.0f;
+ info.m_flLastSpatializedVolume = ch->last_vol;
+ // Radius of this sound effect (spatialization is different within the radius)
+ info.m_flRadius = ch->radius;
+ info.m_nPitch = ch->basePitch;
+ info.m_pOrigin = &ch->origin;
+ info.m_pDirection = &ch->direction;
+
+ // if true, assume sound source can move and update according to entity
+ info.m_bUpdatePositions = ch->flags.bUpdatePositions;
+ // true if playing linked sentence
+ info.m_bIsSentence = ch->flags.isSentence;
+ // if true, bypass all dsp processing for this sound (ie: music)
+ info.m_bDryMix = ch->flags.bdry;
+ // true if sound is playing through in-game speaker entity.
+ info.m_bSpeaker = ch->flags.bSpeaker;
+ // true if sound is using special DSP effect
+ info.m_bSpecialDSP = ( ch->special_dsp != 0 );
+ // for snd_show, networked sounds get colored differently than local sounds
+ info.m_bFromServer = ch->flags.fromserver;
+
+ sndlist.AddToTail( info );
+ }
+}
+
+void S_StopAllSounds( bool bClear )
+{
+ THREAD_LOCK_SOUND();
+ int i;
+
+ if ( !g_AudioDevice )
+ return;
+
+ if ( !g_AudioDevice->IsActive() )
+ return;
+
+ total_channels = MAX_DYNAMIC_CHANNELS; // no statics
+
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+ for ( i = 0; i < list.Count(); i++ )
+ {
+ channel_t *pChannel = list.GetChannel(i);
+ if ( channels[i].sfx )
+ {
+ DevMsg( 1, "%2d:Stopped sound %s\n", i, channels[i].sfx->getname() );
+ }
+ S_FreeChannel( pChannel );
+ }
+
+ Q_memset( channels, 0, MAX_CHANNELS * sizeof(channel_t) );
+
+ if ( bClear )
+ {
+ S_ClearBuffer();
+ }
+
+ // Clear any remaining soundfade
+ memset( &soundfade, 0, sizeof( soundfade ) );
+
+ g_AudioDevice->StopAllSounds();
+ Assert( g_ActiveChannels.GetActiveCount() == 0 );
+}
+
+void S_StopAllSoundsC( void )
+{
+ S_StopAllSounds( true );
+}
+
+void S_OnLoadScreen( bool value )
+{
+ s_bOnLoadScreen = value;
+}
+
+void S_ClearBuffer( void )
+{
+ if ( !g_AudioDevice )
+ return;
+
+ g_AudioDevice->ClearBuffer();
+ DSP_ClearState();
+ MIX_ClearAllPaintBuffers( PAINTBUFFER_SIZE, true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : percent -
+// holdtime -
+// intime -
+// outtime -
+//-----------------------------------------------------------------------------
+void S_SoundFade( float percent, float holdtime, float intime, float outtime )
+{
+ soundfade.starttime = g_pSoundServices->GetHostTime();
+
+ soundfade.initial_percent = percent;
+ soundfade.fadeouttime = outtime;
+ soundfade.holdtime = holdtime;
+ soundfade.fadeintime = intime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Modulates sound volume on the client.
+//-----------------------------------------------------------------------------
+void S_UpdateSoundFade(void)
+{
+ float totaltime;
+ float f;
+ // Determine current fade value.
+
+ // Assume no fading remains
+ soundfade.percent = 0;
+
+ totaltime = soundfade.fadeouttime + soundfade.fadeintime + soundfade.holdtime;
+
+ float elapsed = g_pSoundServices->GetHostTime() - soundfade.starttime;
+
+ // Clock wrapped or reset (BUG) or we've gone far enough
+ if ( elapsed < 0.0f || elapsed >= totaltime || totaltime <= 0.0f )
+ {
+ return;
+ }
+
+ // We are in the fade time, so determine amount of fade.
+ if ( soundfade.fadeouttime > 0.0f && ( elapsed < soundfade.fadeouttime ) )
+ {
+ // Ramp up
+ f = elapsed / soundfade.fadeouttime;
+ }
+ // Inside the hold time
+ else if ( elapsed <= ( soundfade.fadeouttime + soundfade.holdtime ) )
+ {
+ // Stay
+ f = 1.0f;
+ }
+ else
+ {
+ // Ramp down
+ f = ( elapsed - ( soundfade.fadeouttime + soundfade.holdtime ) ) / soundfade.fadeintime;
+ // backward interpolated...
+ f = 1.0f - f;
+ }
+
+ // Spline it.
+ f = SimpleSpline( f );
+ f = clamp( f, 0.0f, 1.0f );
+
+ soundfade.percent = soundfade.initial_percent * f;
+}
+
+
+//=============================================================================
+
+// Global Voice Ducker - enabled in vcd scripts, when characters deliver important dialog. Overrides all
+// other mixer ducking, and ducks all other sounds except dialog.
+
+ConVar snd_ducktovolume( "snd_ducktovolume", "0.55", FCVAR_ARCHIVE );
+ConVar snd_duckerattacktime( "snd_duckerattacktime", "0.5", FCVAR_ARCHIVE );
+ConVar snd_duckerreleasetime( "snd_duckerreleasetime", "2.5", FCVAR_ARCHIVE );
+ConVar snd_duckerthreshold("snd_duckerthreshold", "0.15", FCVAR_ARCHIVE );
+
+static void S_UpdateVoiceDuck( int voiceChannelCount, int voiceChannelMaxVolume, float frametime )
+{
+ float volume_when_ducked = snd_ducktovolume.GetFloat();
+ int volume_threshold = (int)(snd_duckerthreshold.GetFloat() * 255.0);
+
+ float duckTarget = 1.0;
+ if ( voiceChannelCount > 0 )
+ {
+ voiceChannelMaxVolume = clamp(voiceChannelMaxVolume, 0, 255);
+
+ // duckTarget = RemapVal( voiceChannelMaxVolume, 0, 255, 1.0, volume_when_ducked );
+
+ // KB: Change: ducker now active if any character is speaking above threshold volume.
+ // KB: Active ducker drops all volumes to volumes * snd_duckvolume
+
+ if ( voiceChannelMaxVolume > volume_threshold )
+ duckTarget = volume_when_ducked;
+ }
+ float rate = ( duckTarget < g_DuckScale ) ? snd_duckerattacktime.GetFloat() : snd_duckerreleasetime.GetFloat();
+ g_DuckScale = Approach( duckTarget, g_DuckScale, frametime * ((1-volume_when_ducked) / rate) );
+}
+
+// set 2d forward vector, given 3d right vector.
+// NOTE: this should only be used for a listener forward
+// vector from a listener right vector. It is not a general use routine.
+
+void ConvertListenerVectorTo2D( Vector *pvforward, Vector *pvright )
+{
+ // get 2d forward direction vector, ignoring pitch angle
+ QAngle angles2d;
+ Vector source2d;
+ Vector listener_forward2d;
+
+ source2d = *pvright;
+ source2d.z = 0.0;
+
+ VectorNormalize(source2d);
+
+ // convert right vector to euler angles (yaw & pitch)
+
+ VectorAngles(source2d, angles2d);
+
+ // get forward angle of listener
+
+ angles2d[PITCH] = 0;
+ angles2d[YAW] += 90; // rotate 90 ccw
+ angles2d[ROLL] = 0;
+
+ if (angles2d[YAW] >= 360)
+ angles2d[YAW] -= 360;
+
+ AngleVectors(angles2d, &listener_forward2d);
+
+ VectorNormalize(listener_forward2d);
+
+ *pvforward = listener_forward2d;
+}
+
+// If this is nonzero, we will only spatialize some of the static
+// channels each frame. The round robin will spatialize 1 / (2 ^ x)
+// of the spatial channels each frame.
+ConVar snd_spatialize_roundrobin( "snd_spatialize_roundrobin", "0", FCVAR_ALLOWED_IN_COMPETITIVE, "Lowend optimization: if nonzero, spatialize only a fraction of sound channels each frame. 1/2^x of channels will be spatialized per frame." );
+/*
+============
+S_Update
+
+Called once each time through the main loop
+============
+*/
+void S_Update( const AudioState_t *pAudioState )
+{
+ VPROF("S_Update");
+ channel_t *ch;
+ channel_t *combine;
+ static unsigned int s_roundrobin = 0 ; ///< number of times this function is called.
+ ///< used instead of host_frame because that number
+ ///< isn't necessarily available here (sez Yahn).
+
+ if ( !g_AudioDevice->IsActive() )
+ return;
+
+ g_SndMutex.Lock();
+
+ // Update any client side sound fade
+ S_UpdateSoundFade();
+
+ if ( pAudioState )
+ {
+ VectorCopy( pAudioState->m_Origin, listener_origin );
+ AngleVectors( pAudioState->m_Angles, &listener_forward, &listener_right, &listener_up );
+ s_bIsListenerUnderwater = pAudioState->m_bIsUnderwater;
+ }
+ else
+ {
+ VectorCopy( vec3_origin, listener_origin );
+ VectorCopy( vec3_origin, listener_forward );
+ VectorCopy( vec3_origin, listener_right );
+ VectorCopy( vec3_origin, listener_up );
+ s_bIsListenerUnderwater = false;
+ }
+
+ g_AudioDevice->UpdateListener( listener_origin, listener_forward, listener_right, listener_up );
+
+ combine = NULL;
+
+ int voiceChannelCount = 0;
+ int voiceChannelMaxVolume = 0;
+
+ // reset traceline counter for this frame
+ g_snd_trace_count = 0;
+
+ // calculate distance to nearest walls, update dsp_spatial
+ // updates one wall only per frame (one trace per frame)
+ SND_SetSpatialDelays();
+
+ // updates dsp_room if automatic room detection enabled
+ DAS_CheckNewRoomDSP();
+
+ // update spatialization for static and dynamic sounds
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+
+ if (snd_spatialize_roundrobin.GetInt() == 0)
+ {
+ // spatialize each channel each time
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ ch = list.GetChannel(i);
+ Assert(ch->sfx);
+ Assert(ch->activeIndex > 0);
+
+ SND_Spatialize(ch); // respatialize channel
+
+ if ( ch->sfx->pSource && ch->sfx->pSource->IsVoiceSource() )
+ {
+ voiceChannelCount++;
+ voiceChannelMaxVolume = max(voiceChannelMaxVolume, ChannelGetMaxVol( ch) );
+ }
+ }
+ }
+ else // lowend performance improvement: spatialize only some channels each frame.
+ {
+ unsigned int robinmask = (1 << snd_spatialize_roundrobin.GetInt()) - 1;
+
+ // now do static channels
+ for ( int i = 0 ; i < list.Count() ; ++i )
+ {
+ ch = list.GetChannel(i);
+ Assert(ch->sfx);
+ Assert(ch->activeIndex > 0);
+
+ // need to check bfirstpass because sound tracing may have been deferred
+ if ( ch->flags.bfirstpass || (robinmask & s_roundrobin) == ( i & robinmask ) )
+ {
+ SND_Spatialize(ch); // respatialize channel
+ }
+
+ if ( ch->sfx->pSource && ch->sfx->pSource->IsVoiceSource() )
+ {
+ voiceChannelCount++;
+ voiceChannelMaxVolume = max( voiceChannelMaxVolume, ChannelGetMaxVol( ch) );
+ }
+ }
+
+ ++s_roundrobin;
+ }
+
+
+
+ SND_ChannelTraceReset();
+
+ // set new target for voice ducking
+ float frametime = g_pSoundServices->GetHostFrametime();
+ S_UpdateVoiceDuck( voiceChannelCount, voiceChannelMaxVolume, frametime );
+
+ // update x360 music volume
+ g_DashboardMusicMixValue = Approach( g_DashboardMusicMixTarget, g_DashboardMusicMixValue, g_DashboardMusicFadeRate * frametime );
+
+ //
+ // debugging output
+ //
+ if (snd_show.GetInt())
+ {
+ con_nprint_t np;
+ np.time_to_live = 2.0f;
+ np.fixed_width_font = true;
+
+ int total = 0;
+
+ CChannelList activeChannels;
+ g_ActiveChannels.GetActiveChannels( activeChannels );
+ for ( int i = 0; i < activeChannels.Count(); i++ )
+ {
+ channel_t *channel = activeChannels.GetChannel(i);
+ if ( !channel->sfx )
+ continue;
+
+ np.index = total + 2;
+ if ( channel->flags.fromserver )
+ {
+ np.color[0] = 1.0;
+ np.color[1] = 0.8;
+ np.color[2] = 0.1;
+ }
+ else
+ {
+ np.color[0] = 0.1;
+ np.color[1] = 0.9;
+ np.color[2] = 1.0;
+ }
+
+ unsigned int sampleCount = RemainingSamples( channel );
+ float timeleft = (float)sampleCount / (float)channel->sfx->pSource->SampleRate();
+ bool bLooping = channel->sfx->pSource->IsLooped();
+
+ if (snd_surround.GetInt() < 4)
+ {
+ Con_NXPrintf ( &np, "%02i l(%03d) r(%03d) vol(%03d) ent(%03d) pos(%6d %6d %6d) timeleft(%f) looped(%d) %50s",
+ total+ 1,
+ (int)channel->fvolume[IFRONT_LEFT],
+ (int)channel->fvolume[IFRONT_RIGHT],
+ channel->master_vol,
+ channel->soundsource,
+ (int)channel->origin[0],
+ (int)channel->origin[1],
+ (int)channel->origin[2],
+ timeleft,
+ bLooping,
+ channel->sfx->getname());
+ }
+ else
+ {
+ Con_NXPrintf ( &np, "%02i l(%03d) c(%03d) r(%03d) rl(%03d) rr(%03d) vol(%03d) ent(%03d) pos(%6d %6d %6d) timeleft(%f) looped(%d) %50s",
+ total+ 1,
+ (int)channel->fvolume[IFRONT_LEFT],
+ (int)channel->fvolume[IFRONT_CENTER],
+ (int)channel->fvolume[IFRONT_RIGHT],
+ (int)channel->fvolume[IREAR_LEFT],
+ (int)channel->fvolume[IREAR_RIGHT],
+ channel->master_vol,
+ channel->soundsource,
+ (int)channel->origin[0],
+ (int)channel->origin[1],
+ (int)channel->origin[2],
+ timeleft,
+ bLooping,
+ channel->sfx->getname());
+ }
+
+ if ( snd_visualize.GetInt() )
+ {
+ CDebugOverlay::AddTextOverlay( channel->origin, 0.05f, channel->sfx->getname() );
+ }
+
+ total++;
+ }
+
+ while ( total <= 128 )
+ {
+ Con_NPrintf( total + 2, "" );
+ total++;
+ }
+ }
+
+ g_SndMutex.Unlock();
+
+ if ( s_bOnLoadScreen )
+ return;
+
+ // not time to update yet?
+ double tNow = Plat_FloatTime();
+ // this is the last time we ran a sound frame
+ g_LastSoundFrame = tNow;
+ // this is the last time we did mixing (extraupdate also advances this if it mixes)
+ g_LastMixTime = tNow;
+ // mix some sound
+ // try to stay at least one frame + mixahead ahead in the mix.
+ g_EstFrameTime = (g_EstFrameTime * 0.9f) + (g_pSoundServices->GetHostFrametime() * 0.1f);
+ S_Update_( g_EstFrameTime + snd_mixahead.GetFloat() );
+}
+
+CON_COMMAND( snd_dumpclientsounds, "Dump sounds to VXConsole" )
+{
+ con_nprint_t np;
+ np.time_to_live = 2.0f;
+ np.fixed_width_font = true;
+
+ int total = 0;
+
+ CChannelList list;
+ g_ActiveChannels.GetActiveChannels( list );
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ channel_t *ch = list.GetChannel(i);
+ if ( !ch->sfx )
+ continue;
+
+ unsigned int sampleCount = RemainingSamples( ch );
+ float timeleft = (float)sampleCount / (float)ch->sfx->pSource->SampleRate();
+ bool bLooping = ch->sfx->pSource->IsLooped();
+ const char *pszclassname = GetClientClassname(ch->soundsource);
+
+ Msg( "%02i %s l(%03d) c(%03d) r(%03d) rl(%03d) rr(%03d) vol(%03d) pos(%6d %6d %6d) timeleft(%f) looped(%d) %50s chan:%d ent(%03d):%s\n",
+ total+ 1,
+ ch->flags.fromserver ? "SERVER" : "CLIENT",
+ (int)ch->fvolume[IFRONT_LEFT],
+ (int)ch->fvolume[IFRONT_CENTER],
+ (int)ch->fvolume[IFRONT_RIGHT],
+ (int)ch->fvolume[IREAR_LEFT],
+ (int)ch->fvolume[IREAR_RIGHT],
+ ch->master_vol,
+ (int)ch->origin[0],
+ (int)ch->origin[1],
+ (int)ch->origin[2],
+ timeleft,
+ bLooping,
+ ch->sfx->getname(),
+ ch->entchannel,
+ ch->soundsource,
+ pszclassname ? pszclassname : "NULL" );
+
+ total++;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Set g_soundtime to number of full samples that have been transfered out to hardware
+// since start.
+//-----------------------------------------------------------------------------
+void GetSoundTime(void)
+{
+ int fullsamples;
+ int sampleOutCount;
+
+ // size of output buffer in *full* 16 bit samples
+ // A 2 channel device has a *full* sample consisting of a 16 bit LR pair.
+ // A 1 channel device has a *full* sample consiting of a 16 bit single sample.
+ fullsamples = g_AudioDevice->DeviceSampleCount() / g_AudioDevice->DeviceChannels();
+
+ // NOTE: it is possible to miscount buffers if it has wrapped twice between
+ // calls to S_Update. However, since the output buffer size is > 1 second of sound,
+ // this should only occur for framerates lower than 1hz
+
+ // sampleOutCount is counted in 16 bit *full* samples, of number of samples output to hardware
+ // for current output buffer
+ sampleOutCount = g_AudioDevice->GetOutputPosition();
+ if ( sampleOutCount < s_oldsampleOutCount )
+ {
+ // buffer wrapped
+ s_buffers++;
+ if ( g_paintedtime > 0x70000000 )
+ {
+ // time to chop things off to avoid 32 bit limits
+ s_buffers = 0;
+ g_paintedtime = fullsamples;
+ S_StopAllSounds( true );
+ }
+ }
+
+ s_oldsampleOutCount = sampleOutCount;
+
+ if ( cl_movieinfo.IsRecording() || IsReplayRendering() )
+ {
+ // when recording a replay, we look at the record frame rate, not the engine frame rate
+
+#if defined( REPLAY_ENABLED )
+ extern IClientReplayContext *g_pClientReplayContext;
+ if ( IsReplayRendering() )
+ {
+ IReplayMovieRenderer *pMovieRenderer = (g_pClientReplayContext != NULL) ? g_pClientReplayContext->GetMovieRenderer() : NULL;
+
+ if ( pMovieRenderer && pMovieRenderer->IsAudioSyncFrame() )
+ {
+ float t = g_pSoundServices->GetHostTime();
+ if ( s_lastsoundtime != t )
+ {
+ float frameTime = pMovieRenderer->GetRecordingFrameDuration();
+ float fSamples = frameTime * (float) g_AudioDevice->DeviceDmaSpeed() + g_ReplaySoundTimeFracAccumulator;
+
+ float intPart = (float) floor( fSamples );
+ g_ReplaySoundTimeFracAccumulator = fSamples - intPart;
+
+ g_soundtime += (int) intPart;
+ s_lastsoundtime = t;
+ }
+ }
+
+ }
+ else // cl_movieinfo.IsRecording()
+ // in movie, just mix one frame worth of sound
+#endif
+ {
+
+ float t = g_pSoundServices->GetHostTime();
+ if ( s_lastsoundtime != t )
+ {
+ g_soundtime += g_pSoundServices->GetHostFrametime() * g_AudioDevice->DeviceDmaSpeed();
+
+ s_lastsoundtime = t;
+ }
+ }
+ }
+ else
+ {
+ // g_soundtime indicates how many *full* samples have actually been
+ // played out to dma
+ g_soundtime = s_buffers*fullsamples + sampleOutCount;
+ }
+}
+
+void S_ExtraUpdate( void )
+{
+ if ( !g_AudioDevice || !g_pSoundServices )
+ return;
+
+ if ( !g_AudioDevice->IsActive() )
+ return;
+
+ if ( s_bOnLoadScreen )
+ return;
+
+ if ( snd_noextraupdate.GetInt() || cl_movieinfo.IsRecording() || IsReplayRendering() )
+ return; // don't pollute timings
+
+ // If listener position and orientation has not yet been updated (ie: no call to S_Update since level load)
+ // then don't mix. Important - mixing with listener at 'false' origin causes
+ // some sounds to incorrectly spatialize to 0 volume, killing them before they can play.
+
+ if ((listener_origin == vec3_origin) &&
+ (listener_forward == vec3_origin) &&
+ (listener_right == vec3_origin) &&
+ (listener_up == vec3_origin) )
+ return;
+
+ // Only mix if you have used up 90% of the mixahead buffer
+ double tNow = Plat_FloatTime();
+ float delta = (tNow - g_LastMixTime);
+ // we know we were at least snd_mixahead seconds ahead of the output the last time we did mixing
+ // if we're not close to running out just exit to avoid small mix batches
+ if ( delta > 0 && delta < (snd_mixahead.GetFloat() * 0.9f) )
+ return;
+ g_LastMixTime = tNow;
+
+ g_pSoundServices->OnExtraUpdate();
+ // Shouldn't have to do any work here if your framerate hasn't dropped
+ S_Update_( snd_mixahead.GetFloat() );
+}
+
+extern void DEBUG_StartSoundMeasure(int type, int samplecount );
+extern void DEBUG_StopSoundMeasure(int type, int samplecount );
+
+void S_Update_Guts( float mixAheadTime )
+{
+ VPROF( "S_Update_Guts" );
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ DEBUG_StartSoundMeasure(4, 0);
+
+ // Update our perception of audio time.
+ // 'g_soundtime' tells how many samples have
+ // been played out of the dma buffer since sound system startup.
+ // 'g_paintedtime' indicates how many samples we've actually mixed
+ // and sent to the dma buffer since sound system startup.
+ GetSoundTime();
+
+// if ( g_soundtime > g_paintedtime )
+// {
+// // if soundtime > paintedtime, then the dma buffer
+// // has played out more sound than we've actually
+// // mixed. We need to call S_Update_ more often.
+//
+// DevMsg ("S_Update_ : Underflow\n");
+// paintedtime = g_soundtime;
+// }
+// (kdb) above code doesn't handle underflow correctly
+// should actually zero out the paintbuffer to advance to the new
+// time.
+
+ // mix ahead of current position
+ unsigned endtime = g_AudioDevice->PaintBegin( mixAheadTime, g_soundtime, g_paintedtime );
+
+ int samples = endtime - g_paintedtime;
+ samples = samples < 0 ? 0 : samples;
+ if ( samples )
+ {
+ THREAD_LOCK_SOUND();
+
+ DEBUG_StartSoundMeasure( 2, samples );
+
+ MIX_PaintChannels( endtime, s_bIsListenerUnderwater );
+
+ MXR_DebugShowMixVolumes();
+
+ MXR_UpdateAllDuckerVolumes();
+
+ DEBUG_StopSoundMeasure( 2, 0 );
+ }
+
+ g_AudioDevice->PaintEnd();
+ DEBUG_StopSoundMeasure( 4, samples );
+}
+
+#if !defined( _X360 )
+#define THREADED_MIX_TIME 33
+#else
+#define THREADED_MIX_TIME XMA_POLL_RATE
+#endif
+
+ConVar snd_ShowThreadFrameTime( "snd_ShowThreadFrameTime", "0" );
+
+bool g_bMixThreadExit;
+ThreadHandle_t g_hMixThread;
+void S_Update_Thread()
+{
+ float frameTime = THREADED_MIX_TIME * 0.001f;
+ double lastFrameTime = Plat_FloatTime();
+
+ while ( !g_bMixThreadExit )
+ {
+ // mixing (for 360) needs to be updated at a steady rate
+ // large update times causes the mixer to demand more audio data
+ // the 360 decoder has finite latency and cannot fulfill spike requests
+ float t0 = Plat_FloatTime();
+ S_Update_Guts( frameTime + snd_mixahead.GetFloat() );
+ int updateTime = ( Plat_FloatTime() - t0 ) * 1000.0f;
+
+ // try to maintain a steadier rate by compensating for fluctuating mix times
+ int sleepTime = THREADED_MIX_TIME - updateTime;
+ if ( sleepTime > 0 )
+ {
+ ThreadSleep( sleepTime );
+ }
+
+ // mimic a frametime needed for sound update
+ double t1 = Plat_FloatTime();
+ frameTime = t1 - lastFrameTime;
+ lastFrameTime = t1;
+
+ if ( snd_ShowThreadFrameTime.GetBool() )
+ {
+ Msg( "S_Update_Thread: frameTime: %d ms\n", (int)( frameTime * 1000.0f ) );
+ }
+ }
+}
+
+void S_ShutdownMixThread()
+{
+ if ( g_hMixThread )
+ {
+ g_bMixThreadExit = true;
+ ThreadJoin( g_hMixThread );
+ ReleaseThreadHandle( g_hMixThread );
+ g_hMixThread = NULL;
+ }
+}
+
+void S_Update_( float mixAheadTime )
+{
+ if ( !IsConsole() || !snd_mix_async.GetBool() )
+ {
+ S_ShutdownMixThread();
+ S_Update_Guts( mixAheadTime );
+ }
+ else
+ {
+ if ( !g_hMixThread )
+ {
+ g_bMixThreadExit = false;
+ g_hMixThread = ThreadExecuteSolo( "SndMix", S_Update_Thread );
+ if ( IsX360() )
+ {
+ ThreadSetAffinity( g_hMixThread, XBOX_PROCESSOR_5 );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Threaded mixing enable. Purposely hiding enable/disable details.
+//-----------------------------------------------------------------------------
+void S_EnableThreadedMixing( bool bEnable )
+{
+ if ( snd_mix_async.GetBool() != bEnable )
+ {
+ snd_mix_async.SetValue( bEnable );
+ }
+}
+
+/*
+===============================================================================
+
+console functions
+
+===============================================================================
+*/
+extern void DSP_DEBUGSetParams(int ipreset, int iproc, float *pvalues, int cparams);
+extern void DSP_DEBUGReloadPresetFile( void );
+
+void S_DspParms( const CCommand &args )
+{
+ if ( args.ArgC() == 1)
+ {
+ // if dsp_parms with no arguments, reload entire preset file
+
+ DSP_DEBUGReloadPresetFile();
+
+ return;
+ }
+
+ if ( args.ArgC() < 4 )
+ {
+ Msg( "Usage: dsp_parms PRESET# PROC# param0 param1 ...up to param15 \n" );
+ return;
+ }
+
+ int cparam = min( args.ArgC() - 4, 16);
+
+ float params[16];
+ Q_memset( params, 0, sizeof(float) * 16 );
+
+ // get preset & proc
+ int idsp, iproc;
+ idsp = Q_atof( args[1] );
+ iproc = Q_atof( args[2] );
+
+ // get params
+ for (int i = 0; i < cparam; i++)
+ {
+ params[i] = Q_atof( args[i+4] );
+ }
+
+ // set up params & switch preset
+ DSP_DEBUGSetParams(idsp, iproc, params, cparam);
+}
+
+static ConCommand dsp_parm("dsp_reload", S_DspParms );
+
+void S_Play( const char *pszName, bool flush = false )
+{
+ int inCache;
+ char szName[256];
+ CSfxTable *pSfx;
+
+ Q_strncpy( szName, pszName, sizeof( szName ) );
+ if ( !Q_strrchr( pszName, '.' ) )
+ {
+ Q_strncat( szName, ".wav", sizeof( szName ), COPY_ALL_CHARACTERS );
+ }
+
+ pSfx = S_FindName( szName, &inCache );
+ if ( inCache && flush )
+ {
+ pSfx->pSource->CacheUnload();
+ }
+
+ StartSoundParams_t params;
+ params.staticsound = false;
+ params.soundsource = g_pSoundServices->GetViewEntity();
+ params.entchannel = CHAN_REPLACE;
+ params.pSfx = pSfx;
+ params.origin = listener_origin;
+ params.fvol = 1.0f;
+ params.soundlevel = SNDLVL_NONE;
+ params.flags = 0;
+ params.pitch = PITCH_NORM;
+
+ S_StartSound( params );
+}
+
+static void S_Play( const CCommand &args )
+{
+ bool bFlush = !Q_stricmp( args[0], "playflush" );
+ for ( int i = 1; i < args.ArgC(); ++i )
+ {
+ S_Play( args[i], bFlush );
+ }
+}
+
+static void S_PlayVol( const CCommand &args )
+{
+ static int hash=543;
+ float vol;
+ char name[256];
+ CSfxTable *pSfx;
+
+ for ( int i = 1; i<args.ArgC(); i += 2 )
+ {
+ if ( !Q_strrchr( args[i], '.') )
+ {
+ Q_strncpy( name, args[i], sizeof( name ) );
+ Q_strncat( name, ".wav", sizeof( name ), COPY_ALL_CHARACTERS );
+ }
+ else
+ {
+ Q_strncpy( name, args[i], sizeof( name ) );
+ }
+
+ pSfx = S_PrecacheSound( name );
+ vol = Q_atof( args[i+1] );
+
+ StartSoundParams_t params;
+ params.staticsound = false;
+ params.soundsource = hash++;
+ params.entchannel = CHAN_AUTO;
+ params.pSfx = pSfx;
+ params.origin = listener_origin;
+ params.fvol = vol;
+ params.soundlevel = SNDLVL_NONE;
+ params.flags = 0;
+ params.pitch = PITCH_NORM;
+
+ S_StartDynamicSound( params );
+ }
+}
+
+static void S_PlayDelay( const CCommand &args )
+{
+ if ( args.ArgC() != 3 )
+ {
+ Msg( "Usage: sndplaydelay delay_in_sec (negative to skip ahead) soundname\n" );
+ return;
+ }
+
+ char szName[256];
+ CSfxTable *pSfx;
+
+ float delay = Q_atof( args[ 1 ] );
+
+ Q_strncpy(szName, args[ 2 ], sizeof( szName ) );
+ if ( !Q_strrchr( args[ 2 ], '.' ) )
+ {
+ Q_strncat( szName, ".wav", sizeof( szName ), COPY_ALL_CHARACTERS );
+ }
+
+ pSfx = S_FindName( szName, NULL );
+
+ StartSoundParams_t params;
+ params.staticsound = false;
+ params.soundsource = g_pSoundServices->GetViewEntity();
+ params.entchannel = CHAN_REPLACE;
+ params.pSfx = pSfx;
+ params.origin = listener_origin;
+ params.fvol = 1.0f;
+ params.soundlevel = SNDLVL_NONE;
+ params.flags = 0;
+ params.pitch = PITCH_NORM;
+ params.delay = delay;
+
+ S_StartSound( params );
+
+}
+static ConCommand sndplaydelay( "sndplaydelay", S_PlayDelay, "Usage: sndplaydelay delay_in_sec (negative to skip ahead) soundname", FCVAR_SERVER_CAN_EXECUTE );
+
+static bool SortByNameLessFunc( const int &lhs, const int &rhs )
+{
+ CSfxTable *pSfx1 = s_Sounds[lhs].pSfx;
+ CSfxTable *pSfx2 = s_Sounds[rhs].pSfx;
+
+ return CaselessStringLessThan( pSfx1->getname(), pSfx2->getname() );
+}
+
+void S_SoundList(void)
+{
+ CSfxTable *sfx;
+ CAudioSource *pSource;
+ int size, total;
+
+ total = 0;
+ for ( int i = s_Sounds.FirstInorder(); i != s_Sounds.InvalidIndex(); i = s_Sounds.NextInorder( i ) )
+ {
+ sfx = s_Sounds[i].pSfx;
+
+ pSource = sfx->pSource;
+ if ( !pSource || !pSource->IsCached() )
+ continue;
+
+ size = pSource->SampleSize() * pSource->SampleCount();
+ total += size;
+
+ if ( pSource->IsLooped() )
+ Msg ("L");
+ else
+ Msg (" ");
+ Msg("(%2db) %6i : %s\n", pSource->SampleSize(), size, sfx->getname());
+ }
+ Msg( "Total resident: %i\n", total );
+}
+
+#if defined( _X360 )
+CON_COMMAND( vx_soundlist, "Dump sounds to VXConsole" )
+{
+ CSfxTable *sfx;
+ CAudioSource *pSource;
+ int dataSize;
+ char *pFormatStr;
+ int sampleRate;
+ int sampleBits;
+ int streamed;
+ int looped;
+ int channels;
+ int numSamples;
+
+ int numSounds = s_Sounds.Count();
+ xSoundList_t* pSoundList = new xSoundList_t[numSounds];
+
+ int i = 0;
+ for ( int iSrcSound=s_Sounds.FirstInorder(); iSrcSound != s_Sounds.InvalidIndex(); iSrcSound = s_Sounds.NextInorder( iSrcSound ) )
+ {
+ dataSize = -1;
+ sampleRate = -1;
+ sampleBits = -1;
+ pFormatStr = "???";
+ streamed = -1;
+ looped = -1;
+ channels = -1;
+ numSamples = -1;
+
+ sfx = s_Sounds[iSrcSound].pSfx;
+ pSource = sfx->pSource;
+ if ( pSource && pSource->IsCached() )
+ {
+ numSamples = pSource->SampleCount();
+ dataSize = pSource->DataSize();
+ sampleRate = pSource->SampleRate();
+ streamed = pSource->IsStreaming();
+ looped = pSource->IsLooped();
+ channels = pSource->IsStereoWav() ? 2 : 1;
+
+ if ( pSource->Format() == WAVE_FORMAT_ADPCM )
+ {
+ pFormatStr = "ADPCM";
+ sampleBits = 16;
+ }
+ else if ( pSource->Format() == WAVE_FORMAT_PCM )
+ {
+ pFormatStr = "PCM";
+ sampleBits = (pSource->SampleSize() * 8)/channels;
+ }
+ else if ( pSource->Format() == WAVE_FORMAT_XMA )
+ {
+ pFormatStr = "XMA";
+ sampleBits = 16;
+ }
+ }
+
+ V_strncpy( pSoundList[i].name, sfx->getname(), sizeof( pSoundList[i].name ) );
+ V_strncpy( pSoundList[i].formatName, pFormatStr, sizeof( pSoundList[i].formatName ) );
+ pSoundList[i].rate = sampleRate;
+ pSoundList[i].bits = sampleBits;
+ pSoundList[i].channels = channels;
+ pSoundList[i].looped = looped;
+ pSoundList[i].dataSize = dataSize;
+ pSoundList[i].numSamples = numSamples;
+ pSoundList[i].streamed = streamed;
+ ++i;
+ }
+
+ XBX_rSoundList( numSounds, pSoundList );
+ delete [] pSoundList;
+}
+#endif
+
+extern unsigned g_snd_time_debug;
+extern unsigned g_snd_call_time_debug;
+extern unsigned g_snd_count_debug;
+extern unsigned g_snd_samplecount;
+extern unsigned g_snd_frametime;
+extern unsigned g_snd_frametime_total;
+extern int g_snd_profile_type;
+
+// start measuring sound perf, 100 reps
+// type 1 - dsp, 2 - mix, 3 - load sound, 4 - all sound
+// set type via ConVar snd_profile
+
+void DEBUG_StartSoundMeasure(int type, int samplecount )
+{
+ if (type != g_snd_profile_type)
+ return;
+
+ if (samplecount)
+ g_snd_samplecount += samplecount;
+
+ g_snd_call_time_debug = Plat_MSTime();
+}
+
+// show sound measurement after 25 reps - show as % of total frame
+// type 1 - dsp, 2 - mix, 3 - load sound, 4 - all sound
+
+// BUGBUG: snd_profile 4 reports a lower average because it's average cost
+// PER CALL and most calls (via SoundExtraUpdate()) don't do any work and
+// bring the average down. If you want an average PER FRAME instead, it's generally higher.
+void DEBUG_StopSoundMeasure(int type, int samplecount )
+{
+ if (type != g_snd_profile_type)
+ return;
+
+ if (samplecount)
+ g_snd_samplecount += samplecount;
+
+ // add total time since last frame
+
+ g_snd_frametime_total += Plat_MSTime() - g_snd_frametime;
+
+ // performance timing
+
+ g_snd_time_debug += Plat_MSTime() - g_snd_call_time_debug;
+
+ if (++g_snd_count_debug >= 100)
+ {
+ switch (g_snd_profile_type)
+ {
+ case 1:
+ Msg("dsp: (%2.2f) millisec ", ((float)g_snd_time_debug) / 100.0);
+ Msg("(%2.2f) pct of frame \n", 100.0 * ((float)g_snd_time_debug) / ((float)g_snd_frametime_total));
+ break;
+ case 2:
+ Msg("mix+dsp:(%2.2f) millisec ", ((float)g_snd_time_debug) / 100.0);
+ Msg("(%2.2f) pct of frame \n", 100.0 * ((float)g_snd_time_debug) / ((float)g_snd_frametime_total));
+ break;
+ case 3:
+ //if ( (((float)g_snd_time_debug) / 100.0) < 0.01 )
+ // break;
+ Msg("snd load: (%2.2f) millisec ", ((float)g_snd_time_debug) / 100.0);
+ Msg("(%2.2f) pct of frame \n", 100.0 * ((float)g_snd_time_debug) / ((float)g_snd_frametime_total));
+ break;
+ case 4:
+ Msg("sound: (%2.2f) millisec ", ((float)g_snd_time_debug) / 100.0);
+ Msg("(%2.2f) pct of frame (%d samples) \n", 100.0 * ((float)g_snd_time_debug) / ((float)g_snd_frametime_total), g_snd_samplecount);
+ break;
+ }
+
+ g_snd_count_debug = 0;
+ g_snd_time_debug = 0;
+ g_snd_samplecount = 0;
+ g_snd_frametime_total = 0;
+ }
+
+ g_snd_frametime = Plat_MSTime();
+}
+
+// speak a sentence from console; works by passing in "!sentencename"
+// or "sentence"
+
+extern ConVar dsp_room;
+
+static void S_Say( const CCommand &args )
+{
+ CSfxTable *pSfx;
+
+ if ( !g_AudioDevice->IsActive() )
+ return;
+
+ char sound[256];
+ Q_strncpy( sound, args[1], sizeof( sound ) );
+
+ // DEBUG - test performance of dsp code
+ if ( !Q_stricmp( sound, "dsp" ) )
+ {
+ unsigned time;
+ int i;
+ int count = 10000;
+ int idsp;
+
+ for (i = 0; i < PAINTBUFFER_SIZE; i++)
+ {
+ g_paintbuffer[i].left = RandomInt(0,2999);
+ g_paintbuffer[i].right = RandomInt(0,2999);
+ }
+
+ Msg ("Start profiling 10,000 calls to DSP\n");
+
+ idsp = dsp_room.GetInt();
+
+ // get system time
+
+ time = Plat_MSTime();
+
+ for (i = 0; i < count; i++)
+ {
+ // SX_RoomFX(PAINTBUFFER_SIZE, TRUE, TRUE);
+
+ DSP_Process(idsp, g_paintbuffer, NULL, NULL, PAINTBUFFER_SIZE);
+
+ }
+ // display system time delta
+ Msg("%d milliseconds \n", Plat_MSTime() - time);
+ return;
+ }
+
+ if ( !Q_stricmp(sound, "paint") )
+ {
+ unsigned time;
+ int count = 10000;
+ static int hash=543;
+ int psav = g_paintedtime;
+
+ Msg ("Start profiling MIX_PaintChannels\n");
+
+ pSfx = S_PrecacheSound("ambience/labdrone1.wav");
+
+ StartSoundParams_t params;
+ params.staticsound = false;
+ params.soundsource = hash++;
+ params.entchannel = CHAN_AUTO;
+ params.pSfx = pSfx;
+ params.origin = listener_origin;
+ params.fvol = 1.0f;
+ params.soundlevel = SNDLVL_NONE;
+ params.flags = 0;
+ params.pitch = PITCH_NORM;
+
+ S_StartDynamicSound( params );
+
+ // get system time
+ time = Plat_MSTime();
+
+ // paint a boatload of sound
+
+ MIX_PaintChannels( g_paintedtime + 512*count, s_bIsListenerUnderwater );
+
+ // display system time delta
+ Msg("%d milliseconds \n", Plat_MSTime() - time);
+ g_paintedtime = psav;
+ return;
+ }
+
+ // DEBUG
+ if ( !TestSoundChar( sound, CHAR_SENTENCE ) )
+ {
+ // build a fake sentence name, then play the sentence text
+
+ Q_strncpy(sound, "xxtestxx ", sizeof( sound ) );
+ Q_strncat(sound, args[1], sizeof( sound ), COPY_ALL_CHARACTERS );
+
+ int addIndex = g_Sentences.AddToTail();
+ sentence_t *pSentence = &g_Sentences[addIndex];
+ pSentence->pName = sound;
+ pSentence->length = 0;
+
+ // insert null terminator after sentence name
+ sound[8] = 0;
+
+ pSfx = S_PrecacheSound ("!xxtestxx");
+ if (!pSfx)
+ {
+ Msg ("S_Say: can't cache %s\n", sound);
+ return;
+ }
+
+ StartSoundParams_t params;
+ params.staticsound = false;
+ params.soundsource = g_pSoundServices->GetViewEntity();
+ params.entchannel = CHAN_REPLACE;
+ params.pSfx = pSfx;
+ params.origin = vec3_origin;
+ params.fvol = 1.0f;
+ params.soundlevel = SNDLVL_NONE;
+ params.flags = 0;
+ params.pitch = PITCH_NORM;
+
+ S_StartDynamicSound ( params );
+
+ // remove last
+ g_Sentences.Remove( g_Sentences.Size() - 1 );
+ }
+ else
+ {
+ pSfx = S_FindName(sound, NULL);
+ if (!pSfx)
+ {
+ Msg ("S_Say: can't find sentence name %s\n", sound);
+ return;
+ }
+
+ StartSoundParams_t params;
+ params.staticsound = false;
+ params.soundsource = g_pSoundServices->GetViewEntity();
+ params.entchannel = CHAN_REPLACE;
+ params.pSfx = pSfx;
+ params.origin = vec3_origin;
+ params.fvol = 1.0f;
+ params.soundlevel = SNDLVL_NONE;
+ params.flags = 0;
+ params.pitch = PITCH_NORM;
+
+ S_StartDynamicSound( params );
+ }
+}
+
+
+//------------------------------------------------------------------------------
+//
+// Sound Mixers
+//
+// Sound mixers are referenced by name from Soundscapes, and are used to provide
+// custom volume control over various sound categories, called 'mix groups'
+//
+// see scripts/soundmixers.txt for data format
+//------------------------------------------------------------------------------
+
+#define CMXRGROUPMAX 64 // up to n mixgroups
+#define CMXRGROUPRULESMAX (CMXRGROUPMAX + 16) // max number of group rules
+#define CMXRSOUNDMIXERSMAX 32 // up to n sound mixers per project
+
+// mix groups - these equivalent to submixes on an audio mixer
+
+// list of rules for determining sound membership in mix groups.
+// All conditions which are not null are ANDed together
+#define CMXRCLASSMAX 16
+#define CMXRNAMEMAX 32
+
+struct classlistelem_t
+{
+ char szclassname[CMXRNAMEMAX]; // name of entities' class, such as CAI_BaseNPC or CHL2_Player
+};
+
+
+struct grouprule_t
+{
+ char szmixgroup[CMXRNAMEMAX]; // mix group name
+ int mixgroupid; // mix group unique id
+ char szdir[CMXRNAMEMAX]; // substring to search for in ch->sfx
+ int classId; // index of classname
+ int chantype; // channel type (CHAN_WEAPON, etc)
+ int soundlevel_min; // min soundlevel
+ int soundlevel_max; // max soundlevel
+
+ int priority; // 0..100 higher priority sound groups duck all lower pri groups if enabled
+ int is_ducked; // if 1, sound group is ducked by all higher priority 'causes_duck" sounds
+ int causes_ducking; // if 1, sound group ducks other 'is_ducked' sounds of lower priority
+ float duck_target_pct; // if sound group is ducked, target percent of original volume
+
+ float total_vol; // total volume of all sounds in this group, if group can cause ducking
+ float ducker_threshold; // ducking is caused by this group if total_vol > ducker_threshold
+ // and causes_ducking is enabled.
+ float duck_target_vol; // target volume while ducking
+ float duck_ramp_val; // current value of ramp - moves towards duck_target_vol
+};
+
+// sound mixer
+
+struct soundmixer_t
+{
+ char szsoundmixer[CMXRNAMEMAX]; // name of this soundmixer
+ float mapMixgroupidToValue[CMXRGROUPMAX]; // sparse array of mix group values for this soundmixer
+};
+
+int g_mapMixgroupidToGrouprulesid[CMXRGROUPMAX]; // map mixgroupid (one per unique group name)
+ // back to 1st entry of this name in g_grouprules
+
+// sound mixer globals
+
+classlistelem_t g_groupclasslist[CMXRCLASSMAX];
+soundmixer_t g_soundmixers[CMXRSOUNDMIXERSMAX]; // all sound mixers
+grouprule_t g_grouprules[CMXRGROUPRULESMAX]; // all rules for determining mix group membership
+
+
+// set current soundmixer index g_isoundmixer, search for match in soundmixers
+// Only change current soundmixer if new name is different from current name.
+
+int g_isoundmixer = -1; // index of current sound mixer
+char g_szsoundmixer_cur[64]; // current soundmixer name
+
+ConVar snd_soundmixer("snd_soundmixer", "Default_Mix"); // current soundmixer name
+
+
+void MXR_SetCurrentSoundMixer( const char *szsoundmixer )
+{
+ // if soundmixer name is not different from current name, return
+
+ if ( !Q_stricmp(szsoundmixer, g_szsoundmixer_cur) )
+ {
+ return;
+ }
+
+ for (int i = 0; i < g_csoundmixers; i++)
+ {
+ if ( !Q_stricmp(g_soundmixers[i].szsoundmixer, szsoundmixer) )
+ {
+ g_isoundmixer = i;
+
+ // save new current sound mixer name
+ V_strcpy_safe(g_szsoundmixer_cur, szsoundmixer);
+
+ return;
+ }
+ }
+}
+
+ConVar snd_showclassname("snd_showclassname", "0"); // if 1, show classname of ent making sound
+ // if 2, show all mixgroup matches
+ // if 3, show all mixgroup matches with current soundmixer for ent
+// get the client class name if an entity was specified
+const char *GetClientClassname( SoundSource soundsource )
+{
+ IClientEntity *pClientEntity = NULL;
+ if ( entitylist )
+ {
+ pClientEntity = entitylist->GetClientEntity( soundsource );
+ if ( pClientEntity )
+ {
+ ClientClass *pClientClass = pClientEntity->GetClientClass();
+ // check npc sounds
+ if ( pClientClass )
+ {
+ return pClientClass->GetName();
+ }
+ }
+ }
+
+ return NULL;
+}
+
+// builds a cached list of rules that match the directory name on the sound
+int MXR_GetMixGroupListFromDirName( const char *pDirname, byte *pList, int listMax )
+{
+ // if we call this before the groups are parsed we'll get bad data
+ Assert(g_cgrouprules>0);
+ int count = 0;
+ for ( int i = 0; i < listMax; i++ )
+ {
+ pList[i] = 255;
+ }
+
+ for ( int i = 0; i < g_cgrouprules; i++ )
+ {
+ grouprule_t *prule = &g_grouprules[i];
+ if ( prule->szdir[ 0 ] && Q_stristr( pDirname, prule->szdir ) )
+ {
+ pList[count] = i;
+ count++;
+ if ( count >= listMax )
+ return count;
+ }
+ }
+ return count;
+}
+
+
+// determine which mixgroups sound is in, and save those mixgroupids in sound.
+// use current soundmixer indicated with g_isoundmixer, and contents of g_rgpgrouprules.
+// Algorithm:
+// 1. all conditions in a row are AND conditions,
+// 2. all rows sharing the same groupname are OR conditions.
+// so - if a sound matches all conditions of a row, it is given that row's mixgroup id
+// if a sound doesn't match all conditions of a row, the next row is checked.
+
+// returns 0, default mixgroup if no match
+void MXR_GetMixGroupFromSoundsource( channel_t *pchan, SoundSource soundsource, soundlevel_t soundlevel)
+{
+ int i;
+ grouprule_t *prule;
+ bool fmatch;
+ bool classMatch[CMXRCLASSMAX];
+
+ // init all mixgroups for channel
+ for ( i = 0; i < 8; i++ )
+ {
+ pchan->mixgroups[i] = -1;
+ }
+
+ char sndname[MAX_OSPATH];
+ Q_strncpy( sndname, pchan->sfx->getname(), sizeof( sndname ) );
+ // Use forward slashes here
+ Q_FixSlashes( sndname, '/' );
+ const char *pszclassname = GetClientClassname(soundsource);
+
+ for ( i = 0; i < g_cgroupclass; i++ )
+ {
+ classMatch[i] = false;
+ if ( pszclassname && Q_stristr(pszclassname, g_groupclasslist[i].szclassname ) )
+ {
+ classMatch[i] = true;
+ }
+ }
+
+ if ( snd_showclassname.GetInt() == 1)
+ {
+ // utility: show classname of ent making sound
+
+ if (pszclassname)
+ {
+ DevMsg("(%s:%s) \n", pszclassname, sndname);
+ }
+ }
+
+ // check all group rules for a match, save
+ // up to 8 matches in channel mixgroup.
+
+ int cmixgroups = 0;
+ if (!pchan->sfx->m_bMixGroupsCached)
+ {
+ pchan->sfx->OnNameChanged( pchan->sfx->getname() );
+ }
+
+ // since this is a sorted list (in group rule order) we only need to test against the next matching rule
+ // this avoids a search inside the loop
+ int currentDirRuleIndex = 0;
+ int currentDirRule = pchan->sfx->m_mixGroupList[0];
+
+ for (i = 0; i < g_cgrouprules; i++)
+ {
+ prule = &g_grouprules[i];
+ fmatch = true;
+
+ // check directory or name substring
+#if _DEBUG
+ // check dir table is correct in CSfxTable cache
+ if ( prule->szdir[ 0 ] && Q_stristr( sndname, prule->szdir ) )
+ {
+ Assert(currentDirRule == i);
+ }
+ else
+ {
+ Assert(currentDirRule != i);
+ }
+ if ( prule->classId >= 0 )
+ {
+ // rule has a valid class id and table is correct
+ Assert(prule->classId < g_cgroupclass);
+ if ( pszclassname && Q_stristr(pszclassname, g_groupclasslist[prule->classId].szclassname) )
+ {
+ Assert(classMatch[prule->classId] == true);
+ }
+ else
+ {
+ Assert(classMatch[prule->classId] == false);
+ }
+ }
+#endif
+ // this is the next matching dir for this sound, no need to search
+ // becuse the list is sorted and we visit all elements
+ if ( currentDirRule == i )
+ {
+ Assert(prule->szdir[0]);
+ currentDirRuleIndex++;
+ currentDirRule = 255;
+ if ( currentDirRuleIndex < pchan->sfx->m_mixGroupCount )
+ {
+ currentDirRule = pchan->sfx->m_mixGroupList[currentDirRuleIndex];
+ }
+ }
+ else if ( prule->szdir[ 0 ] )
+ {
+ fmatch = false; // substring doesn't match, keep looking
+ }
+
+ // check class name
+
+ if ( fmatch && prule->classId >= 0 )
+ {
+ fmatch = classMatch[prule->classId];
+ }
+
+ // check channel type
+
+ if ( fmatch && prule->chantype >= 0)
+ {
+ if ( pchan->entchannel != prule->chantype )
+ fmatch = false; // channel type doesn't match, keep looking
+ }
+
+ // check sndlvlmin/max
+
+ if ( fmatch && prule->soundlevel_min >= 0)
+ {
+ if ( soundlevel < prule->soundlevel_min )
+ fmatch = false; // soundlevel is less than min, keep looking
+ }
+
+ if ( fmatch && prule->soundlevel_max >= 0)
+ {
+ if ( soundlevel > prule->soundlevel_max )
+ fmatch = false; // soundlevel is greater than max, keep looking
+ }
+
+ if ( fmatch )
+ {
+ pchan->mixgroups[cmixgroups] = prule->mixgroupid;
+ cmixgroups++;
+ if (cmixgroups >= 8)
+ return; // too many matches, stop looking
+ }
+
+ if (fmatch && snd_showclassname.GetInt() >= 2)
+ {
+ // show all mixgroups for this sound
+ if (cmixgroups == 1)
+ {
+ DevMsg("\n%s:%s: ", g_szsoundmixer_cur, sndname);
+ }
+ if (prule->szmixgroup[0])
+ {
+ // int rgmixgroupid[8];
+ // for (int i = 0; i < 8; i++)
+ // rgmixgroupid[i] = -1;
+ // rgmixgroupid[0] = prule->mixgroupid;
+ // float vol = MXR_GetVolFromMixGroup( rgmixgroupid );
+ // DevMsg("%s(%1.2f) ", prule->szmixgroup, vol);
+ DevMsg("%s ", prule->szmixgroup);
+ }
+ }
+ }
+}
+
+struct debug_showvols_t
+{
+ char *psz; // group name
+ int mixgroupid; // groupid
+ float vol; // group volume
+ float totalvol; // total volume of all sounds playing in this group
+};
+
+
+// display routine for MXR_DebugShowMixVolumes
+
+#define MXR_DEBUG_INCY (1.0/40.0) // vertical text spacing
+#define MXR_DEBUG_GREENSTART 0.3 // start position on screen of bar
+
+#define MXR_DEBUG_MAXVOL 1.0 // max volume scale
+#define MXR_DEBUG_REDLIMIT 1.0 // volume limit into yellow
+#define MXR_DEBUG_YELLOWLIMIT 0.7 // volume limit into red
+
+#define MXR_DEBUG_VOLSCALE 48 // length of graph in characters
+#define MXR_DEBUG_CHAR '-' // bar character
+
+extern ConVar dsp_volume;
+int g_debug_mxr_displaycount = 0;
+
+void MXR_DebugGraphMixVolumes( debug_showvols_t *groupvols, int cgroups)
+{
+ float flXpos, flYpos, flXposBar, duration;
+ int r,g,b,a;
+ int rb, gb, bb, ab;
+ flXpos = 0;
+ flYpos = 0;
+ char text[128];
+ char bartext[MXR_DEBUG_VOLSCALE*3];
+
+ duration = 0.01;
+
+ g_debug_mxr_displaycount++;
+
+ if (!(g_debug_mxr_displaycount % 10))
+ return; // only display every 10 frames
+
+
+ r = 96; g = 86; b = 226; a = 255; ab = 255;
+
+ // show volume, dsp_volume
+
+ Q_snprintf( text, 128, "Game Volume: %1.2f", volume.GetFloat());
+ CDebugOverlay::AddScreenTextOverlay(flXpos, flYpos, duration, r, g, b,a, text);
+ flYpos += MXR_DEBUG_INCY;
+
+ Q_snprintf( text, 128, "DSP Volume: %1.2f", dsp_volume.GetFloat());
+ CDebugOverlay::AddScreenTextOverlay(flXpos, flYpos, duration, r, g, b,a, text);
+ flYpos += MXR_DEBUG_INCY;
+
+ for (int i = 0; i < cgroups; i++)
+ {
+ // r += 64; g += 64; b += 16;
+
+ r = r % 255; g = g % 255; b = b % 255;
+
+ Q_snprintf( text, 128, "%s: %1.2f (%1.2f)", groupvols[i].psz,
+ groupvols[i].vol * g_DuckScale, groupvols[i].totalvol * g_DuckScale);
+
+ CDebugOverlay::AddScreenTextOverlay(flXpos, flYpos, duration, r, g, b,a, text);
+
+ // draw volume bar graph
+
+ float vol = (groupvols[i].totalvol * g_DuckScale) / MXR_DEBUG_MAXVOL;
+
+ // draw first 70% green
+ float vol1 = 0.0;
+ float vol2 = 0.0;
+ float vol3 = 0.0;
+ int cbars;
+
+ vol1 = clamp(vol, 0.0f, 0.7f);
+ vol2 = clamp(vol, 0.0f, 0.95f);
+ vol3 = vol;
+
+ flXposBar = flXpos + MXR_DEBUG_GREENSTART;
+
+ if (vol1 > 0.0)
+ {
+ //flXposBar = flXpos + MXR_DEBUG_GREENSTART;
+
+ rb = 0; gb= 255; bb = 0; // green bar
+ Q_memset(bartext, 0, sizeof(bartext));
+
+ cbars = (int)((float)vol1 * (float)MXR_DEBUG_VOLSCALE);
+ cbars = clamp(cbars, 0, MXR_DEBUG_VOLSCALE*3-1);
+ Q_memset(bartext, MXR_DEBUG_CHAR, cbars);
+
+ CDebugOverlay::AddScreenTextOverlay(flXposBar, flYpos, duration, rb, gb, bb,ab, bartext);
+ }
+
+
+ // yellow bar
+ if (vol2 > MXR_DEBUG_YELLOWLIMIT)
+ {
+ rb = 255; gb = 255; bb = 0;
+ Q_memset(bartext, 0, sizeof(bartext));
+
+ cbars = (int)((float)vol2 * (float)MXR_DEBUG_VOLSCALE);
+ cbars = clamp(cbars, 0, MXR_DEBUG_VOLSCALE*3-1);
+ Q_memset(bartext, MXR_DEBUG_CHAR, cbars);
+
+ CDebugOverlay::AddScreenTextOverlay(flXposBar, flYpos, duration, rb, gb, bb,ab, bartext);
+ }
+
+ // red bar
+ if (vol3 > MXR_DEBUG_REDLIMIT)
+ {
+ //flXposBar = flXpos + MXR_DEBUG_REDSTART;
+ rb = 255; gb = 0; bb = 0;
+ Q_memset(bartext, 0, sizeof(bartext));
+
+ cbars = (int)((float)vol3 * (float)MXR_DEBUG_VOLSCALE);
+ cbars = clamp(cbars, 0, MXR_DEBUG_VOLSCALE*3-1);
+ Q_memset(bartext, MXR_DEBUG_CHAR, cbars);
+
+ CDebugOverlay::AddScreenTextOverlay(flXposBar, flYpos, duration, rb, gb, bb,ab, bartext);
+ }
+
+ flYpos += MXR_DEBUG_INCY;
+ }
+}
+
+ConVar snd_disable_mixer_duck("snd_disable_mixer_duck", "0"); // if 1, soundmixer ducking is disabled
+
+// given mix group id, return current duck volume
+
+float MXR_GetDuckVolume( int mixgroupid )
+{
+
+ if ( snd_disable_mixer_duck.GetInt() )
+ return 1.0;
+
+ Assert ( mixgroupid < g_cgrouprules );
+
+ int grouprulesid = g_mapMixgroupidToGrouprulesid[mixgroupid];
+
+ // if this mixgroup is not ducked, return 1.0
+
+ if ( !g_grouprules[grouprulesid].is_ducked )
+ return 1.0;
+
+ // return current duck value for this group, scaled by current fade in/out ramp
+
+ return g_grouprules[grouprulesid].duck_ramp_val;
+
+}
+
+#define SND_DUCKER_UPDATETIME 0.1 // seconds to wait between ducker updates
+
+double g_mxr_ducktime = 0.0; // time of last update to ducker
+
+// Get total volume currently playing in all groups,
+// process duck volumes for all groups
+// Call once per frame - updates occur at 10hz
+
+void MXR_UpdateAllDuckerVolumes( void )
+{
+ if ( snd_disable_mixer_duck.GetInt() )
+ return;
+
+ // check timer since last update, only update at 10hz
+
+ int i;
+ double dtime = g_pSoundServices->GetHostTime();
+
+ // don't update until timer expires
+
+ if (fabs(dtime - g_mxr_ducktime) < SND_DUCKER_UPDATETIME)
+ return;
+
+ g_mxr_ducktime = dtime;
+
+ // clear out all total volume values for groups
+
+ for ( i = 0; i < g_cgrouprules; i++)
+ g_grouprules[i].total_vol = 0.0;
+
+ // for every channel in a mix group which can cause ducking:
+ // get total volume, store total in grouprule:
+
+ CChannelList list;
+ int ch_idx;
+
+ channel_t *pchan;
+ bool b_found_ducked_channel = false;
+
+ g_ActiveChannels.GetActiveChannels( list );
+
+ for ( i = 0; i < list.Count(); i++ )
+ {
+ ch_idx = list.GetChannelIndex(i);
+ pchan = &channels[ch_idx];
+
+ if (pchan->last_vol > 0.0)
+ {
+ // account for all mix groups this channel belongs to...
+
+ for (int j = 0; j < 8; j++)
+ {
+ int imixgroup = pchan->mixgroups[j];
+
+ if (imixgroup < 0)
+ continue;
+
+ int grouprulesid = g_mapMixgroupidToGrouprulesid[imixgroup];
+
+ if (g_grouprules[grouprulesid].causes_ducking)
+ g_grouprules[grouprulesid].total_vol += pchan->last_vol;
+
+ if (g_grouprules[grouprulesid].is_ducked)
+ b_found_ducked_channel = true;
+ }
+ }
+ }
+
+ // if no channels playing which may be ducked, do nothing
+
+ if ( !b_found_ducked_channel )
+ return;
+
+ // for all groups that can be ducked:
+ // see if a higher priority sound group has a volume > threshold,
+ // if so, then duck this group by setting duck_target_vol to duck_target_pct.
+ // if no sound group is causing ducking in this group, reset duck_target_vol to 1.0
+
+ for (i = 0; i < g_cgrouprules; i++)
+ {
+ if (g_grouprules[i].is_ducked)
+ {
+ int priority = g_grouprules[i].priority;
+
+ float duck_volume = 1.0; // clear to 1.0 if no channel causing ducking
+
+ // make sure we interact appropriately with global voice ducking...
+ // if global voice ducking is active, skip sound group ducking and just set duck_volume target to 1.0
+
+ if ( g_DuckScale >= 1.0 )
+ {
+ // check all sound groups for higher priority duck trigger
+
+ for (int j = 0; j < g_cgrouprules; j++)
+ {
+ if (g_grouprules[j].priority > priority &&
+ g_grouprules[j].causes_ducking &&
+ g_grouprules[j].total_vol > g_grouprules[j].ducker_threshold)
+ {
+ // a higher priority group is causing this group to be ducked
+ // set duck volume target to the ducked group's duck target percent
+ // and break
+
+ duck_volume = g_grouprules[i].duck_target_pct;
+
+ // UNDONE: to prevent edge condition caused by crossing threshold, may need to have secondary
+ // UNDONE: timer which allows ducking at 0.2 hz
+
+ break;
+ }
+ }
+ }
+
+ g_grouprules[i].duck_target_vol = duck_volume;
+ }
+ }
+
+ // update all ducker ramps if current duck value is not target
+ // if ramp is greater than duck_volume, approach at 'attack rate'
+ // if ramp is less than duck_volume, approach at 'decay rate'
+
+ for (i = 0; i < g_cgrouprules; i++)
+ {
+ float target = g_grouprules[i].duck_target_vol;
+ float current = g_grouprules[i].duck_ramp_val;
+
+ if (g_grouprules[i].is_ducked && (current != target))
+ {
+
+ float ramptime = target < current ? snd_duckerattacktime.GetFloat() : snd_duckerreleasetime.GetFloat();
+
+ // delta is volume change per update (we can do this
+ // since we run at an approximate fixed update rate of 10hz)
+
+ float delta = (1.0 - g_grouprules[i].duck_target_pct);
+
+ delta *= ( SND_DUCKER_UPDATETIME / ramptime );
+
+ if (current > target)
+ delta = -delta;
+
+ // update ramps
+
+ current += delta;
+
+ if (current < target && delta < 0)
+ current = target;
+ if (current > target && delta > 0)
+ current = target;
+
+ g_grouprules[i].duck_ramp_val = current;
+ }
+ }
+
+}
+
+ConVar snd_showmixer("snd_showmixer", "0"); // set to 1 to show mixer every frame
+
+// show the current soundmixer output
+
+void MXR_DebugShowMixVolumes( void )
+{
+ if (snd_showmixer.GetInt() == 0)
+ return;
+
+ // for the current soundmixer:
+ // make a totalvolume bucket for each mixgroup type in the soundmixer.
+ // for every active channel, add its spatialized volume to
+ // totalvolume bucket for that channel's selected mixgroup
+
+ // display all mixgroup/volume/totalvolume values as horizontal bars
+
+ debug_showvols_t groupvols[CMXRGROUPMAX];
+
+ int i;
+ int cgroups = 0;
+
+ if (g_isoundmixer < 0)
+ {
+ DevMsg("No sound mixer selected!");
+ return;
+ }
+
+ soundmixer_t *pmixer = &g_soundmixers[g_isoundmixer];
+
+ // for every entry in mapMixgroupidToValue which is not -1,
+ // set up groupvols
+
+ for (i = 0; i < CMXRGROUPMAX; i++)
+ {
+ if (pmixer->mapMixgroupidToValue[i] >= 0)
+ {
+ groupvols[cgroups].mixgroupid = i;
+ groupvols[cgroups].psz = MXR_GetGroupnameFromId( i );
+ groupvols[cgroups].totalvol = 0.0;
+ groupvols[cgroups].vol = pmixer->mapMixgroupidToValue[i];
+ cgroups++;
+ }
+ }
+
+ // for every active channel, get its volume and
+ // the selected mixgroupid, add to groupvols totalvol
+
+ CChannelList list;
+ int ch_idx;
+ channel_t *pchan;
+
+ g_ActiveChannels.GetActiveChannels( list );
+
+ for ( i = 0; i < list.Count(); i++ )
+ {
+ ch_idx = list.GetChannelIndex(i);
+ pchan = &channels[ch_idx];
+ if (pchan->last_vol > 0.0)
+ {
+ // find entry in groupvols
+ for (int j = 0; j < CMXRGROUPMAX; j++)
+ {
+ if (pchan->last_mixgroupid == groupvols[j].mixgroupid)
+ {
+ groupvols[j].totalvol += pchan->last_vol;
+ break;
+ }
+ }
+ }
+ }
+
+ // groupvols is now fully initialized - just display it
+
+ MXR_DebugGraphMixVolumes( groupvols, cgroups);
+}
+
+#ifdef _DEBUG
+
+// set the named mixgroup volume to vol for the current soundmixer
+static void MXR_DebugSetMixGroupVolume( const CCommand &args )
+{
+ if ( args.ArgC() != 3 )
+ {
+ DevMsg("Parameters: mix group name, volume");
+ return;
+ }
+
+ const char *szgroupname = args[1];
+ float vol = atof( args[2] );
+
+ int imixgroup = MXR_GetMixgroupFromName( szgroupname );
+
+ if ( g_isoundmixer < 0 )
+ return;
+
+ soundmixer_t *pmixer = &g_soundmixers[g_isoundmixer];
+
+ pmixer->mapMixgroupidToValue[imixgroup] = vol;
+}
+
+#endif //_DEBUG
+
+// given array of groupids (ie: the sound is in these groups),
+// return a mix volume.
+
+// return first mixgroup id in the provided array
+// which maps to a non -1 volume value for this
+// sound mixer
+
+float MXR_GetVolFromMixGroup( int rgmixgroupid[8], int *plast_mixgroupid )
+{
+
+ // if no soundmixer currently set, return 1.0 volume
+
+ if (g_isoundmixer < 0)
+ {
+ *plast_mixgroupid = 0;
+ return 1.0;
+ }
+
+ float duckgain = 1.0;
+
+ if (g_csoundmixers)
+ {
+ soundmixer_t *pmixer = &g_soundmixers[g_isoundmixer];
+
+ if (pmixer)
+ {
+ // search mixgroupid array, return first match (non -1)
+
+ for (int i = 0; i < 8; i++)
+ {
+ int imixgroup = rgmixgroupid[i];
+
+ if (imixgroup < 0)
+ continue;
+
+ // save lowest duck gain value for any of the mix groups this sound is in
+
+ float duckgain_new = MXR_GetDuckVolume( imixgroup );
+
+ if ( duckgain_new < duckgain)
+ duckgain = duckgain_new;
+
+ Assert(imixgroup < CMXRGROUPMAX);
+
+ // return first mixgroup id in the passed in array
+ // that maps to a non -1 volume value for this
+ // sound mixer
+
+ if ( pmixer->mapMixgroupidToValue[imixgroup] >= 0)
+ {
+ *plast_mixgroupid = imixgroup;
+
+ // get gain due to mixer settings
+
+ float gain = pmixer->mapMixgroupidToValue[imixgroup];
+
+ // modify gain with ducker settings for this group
+
+ return gain * duckgain;
+ }
+ }
+ }
+ }
+
+ *plast_mixgroupid = 0;
+ return duckgain;
+}
+
+// get id of mixgroup name
+
+int MXR_GetMixgroupFromName( const char *pszgroupname )
+{
+ // scan group rules for mapping from name to id
+ if ( !pszgroupname )
+ return -1;
+
+ if ( Q_strlen(pszgroupname) == 0 )
+ return -1;
+
+ for (int i = 0; i < g_cgrouprules; i++)
+ {
+ if ( !Q_stricmp(g_grouprules[i].szmixgroup, pszgroupname ) )
+ return g_grouprules[i].mixgroupid;
+ }
+
+ return -1;
+}
+
+// get mixgroup name from id
+char *MXR_GetGroupnameFromId( int mixgroupid)
+{
+ // scan group rules for mapping from name to id
+ if (mixgroupid < 0)
+ return NULL;
+
+ for (int i = 0; i < g_cgrouprules; i++)
+ {
+ if ( g_grouprules[i].mixgroupid == mixgroupid)
+ return g_grouprules[i].szmixgroup;
+ }
+
+ return NULL;
+}
+
+// assign a unique mixgroup id to each unique named mix group
+// within grouprules. Note: all mixgroupids in grouprules must be -1
+// when this routine starts.
+
+void MXR_AssignGroupIds( void )
+{
+ int cmixgroupid = 0;
+
+ for (int i = 0; i < g_cgrouprules; i++)
+ {
+ int mixgroupid = MXR_GetMixgroupFromName( g_grouprules[i].szmixgroup );
+
+ if (mixgroupid == -1)
+ {
+ // groupname is not yet assigned, provide a unique mixgroupid.
+
+ g_grouprules[i].mixgroupid = cmixgroupid;
+
+ // save reverse mapping, from mixgroupid to the first grouprules entry for this name
+
+ g_mapMixgroupidToGrouprulesid[cmixgroupid] = i;
+
+ cmixgroupid++;
+ }
+ }
+}
+
+int MXR_AddClassname( const char *pName )
+{
+ char szclassname[CMXRNAMEMAX];
+ Q_strncpy( szclassname, pName, CMXRNAMEMAX );
+ for ( int i = 0; i < g_cgroupclass; i++ )
+ {
+ if ( !Q_stricmp( szclassname, g_groupclasslist[i].szclassname ) )
+ return i;
+ }
+ if ( g_cgroupclass >= CMXRCLASSMAX )
+ {
+ Assert(g_cgroupclass < CMXRCLASSMAX);
+ return -1;
+ }
+ Q_memcpy(g_groupclasslist[g_cgroupclass].szclassname, pName, min((size_t)CMXRNAMEMAX-1, strlen(pName)));
+ g_cgroupclass++;
+ return g_cgroupclass-1;
+}
+
+#define CHAR_LEFT_PAREN '{'
+#define CHAR_RIGHT_PAREN '}'
+
+// load group rules and sound mixers from file
+
+bool MXR_LoadAllSoundMixers( void )
+{
+ // init soundmixer globals
+
+ g_isoundmixer = -1;
+ g_szsoundmixer_cur[0] = 0;
+
+ g_csoundmixers = 0; // total number of soundmixers found
+ g_cgrouprules = 0; // total number of group rules found
+
+ Q_memset(g_soundmixers, 0, sizeof(g_soundmixers));
+ Q_memset(g_grouprules, 0, sizeof(g_grouprules));
+
+ // load file
+
+ // build rules
+
+ // build array of sound mixers
+
+ char szFile[MAX_OSPATH];
+ const char *pstart;
+ bool bResult = false;
+ char *pbuffer;
+
+ Q_snprintf( szFile, sizeof( szFile ), "scripts/soundmixers.txt" );
+
+ pbuffer = (char *)COM_LoadFile( szFile, 5, NULL ); // Use malloc - free at end of this routine
+ if ( !pbuffer )
+ {
+ Error( "MXR_LoadAllSoundMixers: unable to open '%s'\n", szFile );
+ return bResult;
+ }
+
+ pstart = pbuffer;
+
+ // first pass: load g_grouprules[]
+
+ // starting at top of file,
+ // scan for first '{', skipping all comment lines
+ // get strings for: groupname, directory, classname, chan, sndlvl_min, sndlvl_max
+ // convert chan to CHAN_ lookup
+ // convert sndlvl_min, sndl_max to ints
+ // store all in g_grouprules, update g_cgrouprules;
+ // get next line
+ // when hit '}' we're done with grouprules
+
+ // check for first CHAR_LEFT_PAREN
+
+ while (1)
+ {
+ pstart = COM_Parse( pstart );
+
+ if ( strlen(com_token) <= 0)
+ break; // eof
+
+ if ( com_token[0] != CHAR_LEFT_PAREN )
+ continue;
+
+ break;
+ }
+
+ while (1)
+ {
+ pstart = COM_Parse( pstart );
+ if (com_token[0] == CHAR_RIGHT_PAREN)
+ break;
+
+ grouprule_t *pgroup = &g_grouprules[g_cgrouprules];
+
+ // copy mixgroup name, directory, classname
+ // if no value specified, set to 0 length string
+
+ if (com_token[0])
+ Q_memcpy(pgroup->szmixgroup, com_token, min((size_t)CMXRNAMEMAX-1, strlen(com_token)));
+
+ pstart = COM_Parse( pstart );
+ if (com_token[0])
+ Q_memcpy(pgroup->szdir, com_token, min((size_t)CMXRNAMEMAX-1, strlen(com_token)));
+
+ pgroup->classId = -1;
+ pstart = COM_Parse( pstart );
+ if (com_token[0])
+ {
+ pgroup->classId = MXR_AddClassname( com_token );
+ }
+
+ // make sure all copied strings are null terminated
+ pgroup->szmixgroup[CMXRNAMEMAX-1] = 0;
+ pgroup->szdir[CMXRNAMEMAX-1] = 0;
+
+ // lookup chan
+ pstart = COM_Parse( pstart );
+ if (com_token[0])
+ {
+ if (!Q_stricmp(com_token, "CHAN_STATIC"))
+ pgroup->chantype = CHAN_STATIC;
+ else if (!Q_stricmp(com_token, "CHAN_WEAPON"))
+ pgroup->chantype = CHAN_WEAPON;
+ else if (!Q_stricmp(com_token, "CHAN_VOICE"))
+ pgroup->chantype = CHAN_VOICE;
+ else if (!Q_stricmp(com_token, "CHAN_VOICE2"))
+ pgroup->chantype = CHAN_VOICE2;
+ else if (!Q_stricmp(com_token, "CHAN_BODY"))
+ pgroup->chantype = CHAN_BODY;
+ else if (!Q_stricmp(com_token, "CHAN_ITEM"))
+ pgroup->chantype = CHAN_ITEM;
+ }
+ else
+ pgroup->chantype = -1;
+
+ // get sndlvls
+
+ pstart = COM_Parse( pstart );
+ if (com_token[0])
+ pgroup->soundlevel_min = atoi(com_token);
+ else
+ pgroup->soundlevel_min = -1;
+
+ pstart = COM_Parse( pstart );
+ if (com_token[0])
+ pgroup->soundlevel_max = atoi(com_token);
+ else
+ pgroup->soundlevel_max = -1;
+
+ // get duck priority, IsDucked, Causes_ducking, duck_target_pct
+
+ pstart = COM_Parse( pstart );
+ if (com_token[0])
+ pgroup->priority = atoi(com_token);
+ else
+ pgroup->priority = 50;
+
+ pstart = COM_Parse( pstart );
+ if (com_token[0])
+ pgroup->is_ducked = atoi(com_token);
+ else
+ pgroup->is_ducked = 0;
+
+ pstart = COM_Parse( pstart );
+ if (com_token[0])
+ pgroup->causes_ducking = atoi(com_token);
+ else
+ pgroup->causes_ducking = 0;
+
+ pstart = COM_Parse( pstart );
+ if (com_token[0])
+ pgroup->duck_target_pct = ((float)(atoi(com_token))) / 100.0f;
+ else
+ pgroup->duck_target_pct = 0.5f;
+
+ pstart = COM_Parse( pstart );
+ if (com_token[0])
+ pgroup->ducker_threshold = ((float)(atoi(com_token))) / 100.0f;
+ else
+ pgroup->ducker_threshold = 0.5f;
+
+ pgroup->duck_ramp_val = 1.0;
+ pgroup->duck_target_vol = 1.0;
+ pgroup->total_vol = 0.0;
+
+ // set mixgroup id to -1
+ pgroup->mixgroupid = -1;
+
+ // update rule count
+
+ g_cgrouprules++;
+
+ if (g_cgrouprules >= CMXRGROUPRULESMAX)
+ {
+ // UNDONE: error! too many rules
+ break;
+ }
+ }
+
+ // now process all groupids in groups, such that
+ // each mixgroup gets a unique id.
+
+ MXR_AssignGroupIds();
+
+ // now load g_soundmixers
+
+ // while not at end of file...
+ // scan for "<name>", if found save as new soundmixer name
+ // while not '}'
+ // scan for "<name>", save as groupname
+ // scan for "<num>", save as mix value
+
+ while(1)
+ {
+ pstart = COM_Parse( pstart );
+
+ if ( strlen(com_token) <= 0)
+ break; // eof
+
+ // save name in soundmixer
+
+ soundmixer_t *pmixer = &g_soundmixers[g_csoundmixers];
+
+ Q_memcpy(pmixer->szsoundmixer, com_token, min((size_t)CMXRNAMEMAX-1, strlen(com_token)));
+
+ // init all mixer values to -1.
+
+ for (int j = 0; j < CMXRGROUPMAX; j++)
+ {
+ pmixer->mapMixgroupidToValue[j] = -1.0;
+ }
+
+ // load all groupnames for this soundmixer
+
+ while (1)
+ {
+ pstart = COM_Parse( pstart );
+
+ if (com_token[0] == CHAR_LEFT_PAREN)
+ continue; // skip {
+
+ if (com_token[0] == CHAR_RIGHT_PAREN)
+ break; // finished with this sounmixer
+
+ // lookup mixgroupid for groupname
+ int mixgroupid = MXR_GetMixgroupFromName( com_token );
+ float value;
+
+ // get mix value
+ pstart = COM_Parse( pstart );
+ value = atof( com_token );
+
+ // store value for mixgroupid
+ Assert(mixgroupid <= CMXRGROUPMAX);
+
+ pmixer->mapMixgroupidToValue[mixgroupid] = value;
+ }
+
+ g_csoundmixers++;
+ if (g_csoundmixers >= CMXRSOUNDMIXERSMAX)
+ {
+ // UNDONE: error! to many sound mixers
+ break;
+ }
+ }
+
+ bResult = true;
+
+// loadmxr_exit:
+ free( pbuffer );
+ return bResult;
+}
+
+void MXR_ReleaseMemory( void )
+{
+ // free all resources
+}
+
+float S_GetMono16Samples( const char *pszName, CUtlVector< short >& sampleList )
+{
+ CSfxTable *pSfx = S_PrecacheSound( PSkipSoundChars( pszName ) );
+ if ( !pSfx )
+ return 0.0f;
+
+ CAudioSource *pWave = pSfx->pSource;
+ if ( !pWave )
+ return 0.0f;
+
+ int nType = pWave->GetType();
+ if ( nType != CAudioSource::AUDIO_SOURCE_WAV )
+ return 0.0f;
+
+ CAudioMixer *pMixer = pWave->CreateMixer();
+ if ( !pMixer )
+ return 0.0f;
+
+ float duration = AudioSource_GetSoundDuration( pSfx );
+
+ // Determine start/stop positions
+ int totalsamples = (int)( duration * pWave->SampleRate() );
+ if ( totalsamples <= 0 )
+ return 0;
+
+ bool bStereo = pWave->IsStereoWav();
+ int mix_sample_size = pMixer->GetMixSampleSize();
+ int nNumChannels = bStereo ? 2 : 1;
+
+ char *pData = NULL;
+
+ int pos = 0;
+ int remaining = totalsamples;
+ while ( remaining > 0 )
+ {
+ int blockSize = min( remaining, 1000 );
+
+ char copyBuf[AUDIOSOURCE_COPYBUF_SIZE];
+ int copied = pWave->GetOutputData( (void **)&pData, pos, blockSize, copyBuf );
+ if ( !copied )
+ {
+ break;
+ }
+
+ remaining -= copied;
+ pos += copied;
+
+ // Now get samples out of output data
+ switch ( nNumChannels )
+ {
+ default:
+ case 1:
+ {
+ for ( int i = 0; i < copied; ++i )
+ {
+ int offset = i * mix_sample_size;
+
+ short sample = 0;
+ if ( mix_sample_size == 1 )
+ {
+ char s = *( char * )( pData + offset );
+ // Upscale it to fit into a short
+ sample = s << 8;
+ }
+ else if ( mix_sample_size == 2 )
+ {
+ sample = *( short * )( pData + offset );
+ }
+ else if ( mix_sample_size == 4 )
+ {
+ // Not likely to have 4 bytes mono!!!
+ Assert( 0 );
+
+ int s = *( int * )( pData + offset );
+ sample = s >> 16;
+ }
+ else
+ {
+ Assert( 0 );
+ }
+
+ sampleList.AddToTail( sample );
+ }
+ }
+ break;
+
+ case 2:
+ {
+ for ( int i = 0; i < copied; ++i )
+ {
+ int offset = i * mix_sample_size;
+
+ short left = 0;
+ short right = 0;
+
+ if ( mix_sample_size == 1 )
+ {
+ // Not possible!!!, must be at least 2 bytes!!!
+ Assert( 0 );
+
+ char v = *( char * )( pData + offset );
+ left = right = ( v << 8 );
+ }
+ else if ( mix_sample_size == 2 )
+ {
+ // One byte per channel
+ left = (short)( ( *(char *)( pData + offset ) ) << 8 );
+ right = (short)( ( *(char *)( pData + offset + 1 ) ) << 8 );
+ }
+ else if ( mix_sample_size == 4 )
+ {
+ // 2 bytes per channel
+ left = *( short * )( pData + offset );
+ right = *( short * )( pData + offset + 2 );
+ }
+ else
+ {
+ Assert( 0 );
+ }
+
+ short sample = ( left + right ) >> 1;
+ sampleList.AddToTail( sample );
+ }
+ }
+ break;
+ }
+ }
+
+ delete pMixer;
+
+ return duration;
+}
diff --git a/engine/audio/private/snd_dma.h b/engine/audio/private/snd_dma.h
new file mode 100644
index 0000000..30d3552
--- /dev/null
+++ b/engine/audio/private/snd_dma.h
@@ -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
diff --git a/engine/audio/private/snd_dsp.cpp b/engine/audio/private/snd_dsp.cpp
new file mode 100644
index 0000000..4e73fdb
--- /dev/null
+++ b/engine/audio/private/snd_dsp.cpp
@@ -0,0 +1,9679 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+// snd_dsp.c -- audio processing routines
+
+
+#include "audio_pch.h"
+#include "snd_mix_buf.h"
+
+#include "iprediction.h"
+#include "../../common.h" // for parsing routines
+#include "vstdlib/random.h"
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#define SIGN(d) ((d)<0?-1:1)
+
+#define ABS(a) abs(a)
+
+#define MSEC_TO_SAMPS(a) (((a)*SOUND_DMA_SPEED) / 1000) // convert milliseconds to # samples in equivalent time
+#define SEC_TO_SAMPS(a) ((a)*SOUND_DMA_SPEED) // convert seconds to # samples in equivalent time
+
+// Suppress the noisy warnings caused by CLIP_DSP
+#if defined(__clang__)
+ #pragma GCC diagnostic ignored "-Wself-assign"
+#endif
+#define CLIP_DSP(x) (x)
+
+extern ConVar das_debug;
+
+#define SOUND_MS_PER_FT 1 // sound travels approx 1 foot per millisecond
+#define ROOM_MAX_SIZE 1000 // max size in feet of room simulation for dsp
+
+void DSP_ReleaseMemory( void );
+bool DSP_LoadPresetFile( void );
+
+extern float Gain_To_dB ( float gain );
+extern float dB_To_Gain ( float dB );
+extern float Gain_To_Amplitude ( float gain );
+extern float Amplitude_To_Gain ( float amplitude );
+
+extern bool g_bdas_room_init;
+extern bool g_bdas_init_nodes;
+
+//===============================================================================
+//
+// Digital Signal Processing algorithms for audio FX.
+//
+// KellyB 2/18/03
+//===============================================================================
+
+// Performance notes:
+
+// DSP processing should take no more than 3ms total time per frame to remain on par with hl1
+// Assume a min frame rate of 24fps = 42ms per frame
+// at 24fps, to maintain 44.1khz output rate, we must process about 1840 mono samples per frame.
+// So we must process 1840 samples in 3ms.
+
+// on a 1Ghz CPU (mid-low end CPU) 3ms provides roughly 3,000,000 cycles.
+// Thus we have 3e6 / 1840 = 1630 cycles per sample.
+
+#define PBITS 12 // parameter bits
+#define PMAX ((1 << PBITS)) // parameter max
+
+// crossfade from y2 to y1 at point r (0 < r < PMAX)
+
+#define XFADE(y1,y2,r) ((y2) + ( ( ((y1) - (y2)) * (r) ) >> PBITS) )
+
+// exponential crossfade from y2 to y1 at point r (0 < r < PMAX)
+
+#define XFADE_EXP(y1, y2, r) ((y2) + ((((((y1) - (y2)) * (r) ) >> PBITS) * (r)) >> PBITS) )
+
+/////////////////////
+// dsp helpers
+/////////////////////
+
+// reverse delay pointer
+
+inline void DlyPtrReverse (int dlysize, int *psamps, int **ppsamp)
+{
+ // when *ppsamp = psamps - 1, it wraps around to *ppsamp = psamps + dlysize
+
+ if ( *ppsamp < psamps )
+ *ppsamp += dlysize + 1;
+}
+
+// advance delay pointer
+
+inline void DlyPtrForward (int dlysize, int *psamps, int **ppsamp)
+{
+ // when *ppsamp = psamps + dlysize + 1, it wraps around to *ppsamp = psamps
+
+ if ( *ppsamp > psamps + dlysize )
+ *ppsamp -= dlysize + 1;
+}
+
+// Infinite Impulse Response (feedback) filter, cannonical form
+
+// returns single sample 'out' for current input value 'in'
+// in: input sample
+// psamp: internal state array, dimension max(cdenom,cnumer) + 1
+// cnumer,cdenom: numerator and denominator filter orders
+// denom,numer: cdenom+1 dimensional arrays of filter params
+//
+// for cdenom = 4:
+//
+// 1 psamp0(n) numer0
+// in(n)--->(+)--(*)---.------(*)---->(+)---> out(n)
+// ^ | ^
+// | [Delay d] |
+// | | |
+// | -denom1 |psamp1 numer1 |
+// ----(*)---.------(*)-------
+// ^ | ^
+// | [Delay d] |
+// | | |
+// | -denom2 |psamp2 numer2 |
+// ----(*)---.------(*)-------
+// ^ | ^
+// | [Delay d] |
+// | | |
+// | -denom3 |psamp3 numer3 |
+// ----(*)---.------(*)-------
+// ^ | ^
+// | [Delay d] |
+// | | |
+// | -denom4 |psamp4 numer4 |
+// ----(*)---.------(*)-------
+//
+// for each input sample in:
+// psamp0 = in - denom1*psamp1 - denom2*psamp2 - ...
+// out = numer0*psamp0 + numer1*psamp1 + ...
+// psampi = psampi-1, i = cmax, cmax-1, ..., 1
+
+inline int IIRFilter_Update_OrderN ( int cdenom, int *denom, int cnumer, int *numer, int *psamp, int in )
+{
+ int cmax, i;
+ int out;
+ int in0;
+
+ out = 0;
+ in0 = in;
+
+ cmax = max ( cdenom, cnumer );
+
+ // add input values
+
+ // for (i = 1; i <= cdenom; i++)
+ // psamp[0] -= ( denom[i] * psamp[i] ) >> PBITS;
+
+ switch (cdenom)
+ {
+ case 12: in0 -= ( denom[12] * psamp[12] ) >> PBITS;
+ case 11: in0 -= ( denom[11] * psamp[11] ) >> PBITS;
+ case 10: in0 -= ( denom[10] * psamp[10] ) >> PBITS;
+ case 9: in0 -= ( denom[9] * psamp[9] ) >> PBITS;
+ case 8: in0 -= ( denom[8] * psamp[8] ) >> PBITS;
+ case 7: in0 -= ( denom[7] * psamp[7] ) >> PBITS;
+ case 6: in0 -= ( denom[6] * psamp[6] ) >> PBITS;
+ case 5: in0 -= ( denom[5] * psamp[5] ) >> PBITS;
+ case 4: in0 -= ( denom[4] * psamp[4] ) >> PBITS;
+ case 3: in0 -= ( denom[3] * psamp[3] ) >> PBITS;
+ case 2: in0 -= ( denom[2] * psamp[2] ) >> PBITS;
+ default:
+ case 1: in0 -= ( denom[1] * psamp[1] ) >> PBITS;
+ }
+
+ psamp[0] = in0;
+
+ // add output values
+
+ //for (i = 0; i <= cnumer; i++)
+ // out += ( numer[i] * psamp[i] ) >> PBITS;
+
+ switch (cnumer)
+ {
+ case 12: out += ( numer[12] * psamp[12] ) >> PBITS;
+ case 11: out += ( numer[11] * psamp[11] ) >> PBITS;
+ case 10: out += ( numer[10] * psamp[10] ) >> PBITS;
+ case 9: out += ( numer[9] * psamp[9] ) >> PBITS;
+ case 8: out += ( numer[8] * psamp[8] ) >> PBITS;
+ case 7: out += ( numer[7] * psamp[7] ) >> PBITS;
+ case 6: out += ( numer[6] * psamp[6] ) >> PBITS;
+ case 5: out += ( numer[5] * psamp[5] ) >> PBITS;
+ case 4: out += ( numer[4] * psamp[4] ) >> PBITS;
+ case 3: out += ( numer[3] * psamp[3] ) >> PBITS;
+ case 2: out += ( numer[2] * psamp[2] ) >> PBITS;
+ default:
+ case 1: out += ( numer[1] * psamp[1] ) >> PBITS;
+ case 0: out += ( numer[0] * psamp[0] ) >> PBITS;
+ }
+
+ // update internal state (reverse order)
+
+ for (i = cmax; i >= 1; i--)
+ psamp[i] = psamp[i-1];
+
+ // return current output sample
+
+ return out;
+}
+
+// 1st order filter - faster version
+
+inline int IIRFilter_Update_Order1 ( int *denom, int cnumer, int *numer, int *psamp, int in )
+{
+ int out;
+
+ if (!psamp[0] && !psamp[1] && !in)
+ return 0;
+
+ psamp[0] = in - (( denom[1] * psamp[1] ) >> PBITS);
+
+ out = ( ( numer[1] * psamp[1] ) + ( numer[0] * psamp[0] ) ) >> PBITS;
+
+ psamp[1] = psamp[0];
+
+ return out;
+}
+
+// return 'tdelay' delayed sample from delay buffer
+// dlysize: delay samples
+// psamps: head of delay buffer psamps[0...dlysize]
+// psamp: current data pointer
+// sdly: 0...dlysize
+
+inline int GetDly ( int dlysize, int *psamps, int *psamp, int tdelay )
+{
+ int *pout;
+
+ pout = psamp + tdelay;
+
+ if ( pout <= (psamps + dlysize))
+ return *pout;
+ else
+ return *(pout - dlysize - 1);
+}
+
+// update the delay buffer pointer
+// dlysize: delay samples
+// psamps: head of delay buffer psamps[0...dlysize]
+// ppsamp: data pointer
+
+inline void DlyUpdate ( int dlysize, int *psamps, int **ppsamp )
+{
+ // decrement pointer and fix up on buffer boundary
+
+ // when *ppsamp = psamps-1, it wraps around to *ppsamp = psamps+dlysize
+
+ (*ppsamp)--;
+ DlyPtrReverse ( dlysize, psamps, ppsamp );
+}
+
+// simple delay with feedback, no filter in feedback line.
+// delaysize: delay line size in samples
+// tdelay: tap from this location - <= delaysize
+// psamps: delay line buffer pointer of dimension delaysize+1
+// ppsamp: circular pointer, must be init to &psamps[0] before first call
+// fbgain: feedback value, 0-PMAX (normalized to 0.0-1.0)
+// outgain: gain
+// in: input sample
+
+// psamps0(n) outgain
+// in(n)--->(+)--------.-----(*)-> out(n)
+// ^ |
+// | [Delay d]
+// | |
+// | fbgain |Wd(n)
+// ----(*)---.
+
+inline int ReverbSimple ( int delaysize, int tdelay, int *psamps, int **ppsamp, int fbgain, int outgain, int in )
+{
+ int out, sD;
+
+ // get current delay output
+
+ sD = GetDly ( delaysize, psamps, *ppsamp, tdelay );
+
+ // calculate output + delay * gain
+
+ out = in + (( fbgain * sD ) >> PBITS);
+
+ // write to delay
+
+ **ppsamp = out;
+
+ // advance internal delay pointers
+
+ DlyUpdate ( delaysize, psamps, ppsamp );
+
+ return ( (out * outgain) >> PBITS );
+}
+
+inline int ReverbSimple_xfade ( int delaysize, int tdelay, int tdelaynew, int xf, int *psamps, int **ppsamp, int fbgain, int outgain, int in )
+{
+ int out, sD;
+ int sDnew;
+
+ // crossfade from tdelay to tdelaynew samples. xfade is 0..PMAX
+
+ sD = GetDly ( delaysize, psamps, *ppsamp, tdelay );
+ sDnew = GetDly ( delaysize, psamps, *ppsamp, tdelaynew );
+ sD = sD + (((sDnew - sD) * xf) >> PBITS);
+
+ out = in + (( fbgain * sD ) >> PBITS);
+ **ppsamp = out;
+ DlyUpdate ( delaysize, psamps, ppsamp );
+
+ return ( (out * outgain) >> PBITS );
+}
+
+// multitap simple reverb
+
+// NOTE: tdelay3 > tdelay2 > tdelay1 > t0
+// NOTE: fbgain * 4 < 1!
+
+inline int ReverbSimple_multitap ( int delaysize, int tdelay0, int tdelay1, int tdelay2, int tdelay3, int *psamps, int **ppsamp, int fbgain, int outgain, int in )
+{
+ int s1, s2, s3, s4, sum;
+
+ s1 = GetDly ( delaysize, psamps, *ppsamp, tdelay0 );
+ s2 = GetDly ( delaysize, psamps, *ppsamp, tdelay1 );
+ s3 = GetDly ( delaysize, psamps, *ppsamp, tdelay2 );
+ s4 = GetDly ( delaysize, psamps, *ppsamp, tdelay3 );
+
+ sum = s1 + s2 + s3 + s4;
+
+ // write to delay
+
+ **ppsamp = in + ((s4 * fbgain) >> PBITS);
+
+ // update delay pointers
+
+ DlyUpdate ( delaysize, psamps, ppsamp );
+
+ return ( ((sum + in) * outgain ) >> PBITS );
+}
+
+// modulate smallest tap delay only
+
+inline int ReverbSimple_multitap_xfade ( int delaysize, int tdelay0, int tdelaynew, int xf, int tdelay1, int tdelay2, int tdelay3, int *psamps, int **ppsamp, int fbgain, int outgain, int in )
+{
+ int s1, s2, s3, s4, sum;
+ int sD, sDnew;
+
+ // crossfade from tdelay to tdelaynew tap. xfade is 0..PMAX
+
+ sD = GetDly ( delaysize, psamps, *ppsamp, tdelay3 );
+ sDnew = GetDly ( delaysize, psamps, *ppsamp, tdelaynew );
+
+ s4 = sD + (((sDnew - sD) * xf) >> PBITS);
+
+ s1 = GetDly ( delaysize, psamps, *ppsamp, tdelay0 );
+ s2 = GetDly ( delaysize, psamps, *ppsamp, tdelay1 );
+ s3 = GetDly ( delaysize, psamps, *ppsamp, tdelay2 );
+
+ sum = s1 + s2 + s3 + s4;
+
+ // write to delay
+
+ **ppsamp = in + ((s4 * fbgain) >> PBITS);
+
+ // update delay pointers
+
+ DlyUpdate ( delaysize, psamps, ppsamp );
+
+ return ( ((sum + in) * outgain ) >> PBITS );
+}
+
+// straight delay, no feedback
+//
+// delaysize: delay line size in samples
+// tdelay: tap from this location - <= delaysize
+// psamps: delay line buffer pointer of dimension delaysize+1
+// ppsamp: circular pointer, must be init to &psamps[0] before first call
+// in: input sample
+//
+// in(n)--->[Delay d]---> out(n)
+//
+
+inline int DelayLinear ( int delaysize, int tdelay, int *psamps, int **ppsamp, int in )
+{
+ int out;
+
+ out = GetDly ( delaysize, psamps, *ppsamp, tdelay );
+
+ **ppsamp = in;
+
+ DlyUpdate ( delaysize, psamps, ppsamp );
+
+ return ( out );
+}
+
+// crossfade delay values from tdelay to tdelaynew, with xfade1 for tdelay and xfade2 for tdelaynew. xfade = 0...PMAX
+
+inline int DelayLinear_xfade ( int delaysize, int tdelay, int tdelaynew, int xf, int *psamps, int **ppsamp, int in )
+{
+ int out;
+ int outnew;
+
+ out = GetDly ( delaysize, psamps, *ppsamp, tdelay );
+
+ outnew = GetDly ( delaysize, psamps, *ppsamp, tdelaynew );
+
+ out = out + (((outnew - out) * xf) >> PBITS);
+
+ **ppsamp = in;
+
+ DlyUpdate ( delaysize, psamps, ppsamp );
+
+ return ( out );
+}
+
+// lowpass reverberator, replace feedback multiplier 'fbgain' in
+// reverberator with a low pass filter
+
+// delaysize: delay line size in samples
+// tdelay: tap from this location - <= delaysize
+// psamps: delay line buffer pointer of dimension delaysize+1
+// ppsamp: circular pointer, must be init to &w[0] before first call
+// fbgain: feedback gain (built into filter gain)
+// outgain: output gain
+// cnumer: filter order
+// numer: filter numerator, 0-PMAX (normalized to 0.0-1.0), cnumer+1 dimensional
+// denom: filter denominator, 0-PMAX (normalized to 0.0-1.0), cnumer+1 dimensional
+// pfsamps: filter state, cnumer+1 dimensional
+// in: input sample
+
+// psamps0(n) outgain
+// in(n)--->(+)--------------.----(*)--> out(n)
+// ^ |
+// | [Delay d]
+// | |
+// | fbgain |Wd(n)
+// --(*)--[Filter])-
+
+inline int DelayLowpass ( int delaysize, int tdelay, int *psamps, int **ppsamp, int fbgain, int outgain, int *denom, int Ll, int *numer, int *pfsamps, int in )
+{
+ int out, sD;
+
+ // delay output is filter input
+
+ sD = GetDly ( delaysize, psamps, *ppsamp, tdelay );
+
+ // filter output, with feedback 'fbgain' baked into filter params
+
+ out = in + IIRFilter_Update_Order1 ( denom, Ll, numer, pfsamps, sD );
+
+ // write to delay
+
+ **ppsamp = out;
+
+ // update delay pointers
+
+ DlyUpdate ( delaysize, psamps, ppsamp );
+
+ // output with gain
+
+ return ( (out * outgain) >> PBITS );
+}
+
+inline int DelayLowpass_xfade ( int delaysize, int tdelay, int tdelaynew, int xf, int *psamps, int **ppsamp, int fbgain, int outgain, int *denom, int Ll, int *numer, int *pfsamps, int in )
+{
+ int out, sD;
+ int sDnew;
+
+ // crossfade from tdelay to tdelaynew tap. xfade is 0..PMAX
+
+ sD = GetDly ( delaysize, psamps, *ppsamp, tdelay );
+ sDnew = GetDly ( delaysize, psamps, *ppsamp, tdelaynew );
+ sD = sD + (((sDnew - sD) * xf) >> PBITS);
+
+ // filter output with feedback 'fbgain' baked into filter params
+
+ out = in + IIRFilter_Update_Order1 ( denom, Ll, numer, pfsamps, sD );
+
+ // write to delay
+
+ **ppsamp = out;
+
+ // update delay ptrs
+
+ DlyUpdate ( delaysize, psamps, ppsamp );
+
+ // output with gain
+
+ return ( (out * outgain) >> PBITS );
+}
+
+// delay is multitap tdelay0,tdelay1,tdelay2,tdelay3
+
+// NOTE: tdelay3 > tdelay2 > tdelay1 > tdelay0
+// NOTE: fbgain * 4 < 1!
+
+inline int DelayLowpass_multitap ( int delaysize, int tdelay0, int tdelay1, int tdelay2, int tdelay3, int *psamps, int **ppsamp, int fbgain, int outgain, int *denom, int Ll, int *numer, int *pfsamps, int in )
+{
+ int s0, s1, s2, s3, s4, sum;
+
+ s1 = GetDly ( delaysize, psamps, *ppsamp, tdelay0 );
+ s2 = GetDly ( delaysize, psamps, *ppsamp, tdelay1 );
+ s3 = GetDly ( delaysize, psamps, *ppsamp, tdelay2 );
+ s4 = GetDly ( delaysize, psamps, *ppsamp, tdelay3 );
+
+ sum = s1 + s2 + s3 + s4;
+
+ s0 = in + IIRFilter_Update_Order1 ( denom, Ll, numer, pfsamps, s4 );
+
+ // write to delay
+
+ **ppsamp = s0;
+
+ // update delay ptrs
+
+ DlyUpdate ( delaysize, psamps, ppsamp );
+
+ return ( ((sum + in) * outgain ) >> PBITS );
+}
+
+inline int DelayLowpass_multitap_xfade ( int delaysize, int tdelay0, int tdelaynew, int xf, int tdelay1, int tdelay2, int tdelay3, int *psamps, int **ppsamp, int fbgain, int outgain, int *denom, int Ll, int *numer, int *pfsamps, int in )
+{
+ int s0, s1, s2, s3, s4, sum;
+
+ int sD, sDnew;
+
+ // crossfade from tdelay to tdelaynew tap. xfade is 0..PMAX
+
+ sD = GetDly ( delaysize, psamps, *ppsamp, tdelay3 );
+ sDnew = GetDly ( delaysize, psamps, *ppsamp, tdelaynew );
+
+ s4 = sD + (((sDnew - sD) * xf) >> PBITS);
+
+ s1 = GetDly ( delaysize, psamps, *ppsamp, tdelay0 );
+ s2 = GetDly ( delaysize, psamps, *ppsamp, tdelay1 );
+ s3 = GetDly ( delaysize, psamps, *ppsamp, tdelay2 );
+
+ sum = s1 + s2 + s3 + s4;
+
+ s0 = in + IIRFilter_Update_Order1 ( denom, Ll, numer, pfsamps, s4 );
+
+ **ppsamp = s0;
+ DlyUpdate ( delaysize, psamps, ppsamp );
+
+ return ( ((sum + in) * outgain ) >> PBITS );
+}
+
+// linear delay with lowpass filter on delay output and gain stage
+// delaysize: delay line size in samples
+// tdelay: delay tap from this location - <= delaysize
+// psamps: delay line buffer pointer of dimension delaysize+1
+// ppsamp: circular pointer, must init &psamps[0] before first call
+// fbgain: feedback gain (ignored)
+// outgain: output gain
+// cnumer: filter order
+// numer: filter numerator, 0-PMAX (normalized to 0.0-1.0), cnumer+1 dimensional
+// denom: filter denominator, 0-PMAX (normalized to 0.0-1.0), cnumer+1 dimensional
+// pfsamps: filter state, cnumer+1 dimensional
+// in: input sample
+
+// in(n)--->[Delay d]--->[Filter]-->(*outgain)---> out(n)
+
+inline int DelayLinear_lowpass ( int delaysize, int tdelay, int *psamps, int **ppsamp, int fbgain, int outgain, int *denom, int cnumer, int *numer, int *pfsamps, int in )
+{
+ int out, sD;
+
+ // delay output is filter input
+
+ sD = GetDly ( delaysize, psamps, *ppsamp, tdelay );
+
+ // calc filter output
+
+ out = IIRFilter_Update_Order1 ( denom, cnumer, numer, pfsamps, sD );
+
+ // input sample to delay input
+
+ **ppsamp = in;
+
+ // update delay pointers
+
+ DlyUpdate ( delaysize, psamps, ppsamp );
+
+ // output with gain
+
+ return ( (out * outgain) >> PBITS );
+}
+
+inline int DelayLinear_lowpass_xfade ( int delaysize, int tdelay, int tdelaynew, int xf, int *psamps, int **ppsamp, int fbgain, int outgain, int *denom, int cnumer, int *numer, int *pfsamps, int in )
+{
+ int out, sD;
+ int sDnew;
+
+ // crossfade from tdelay to tdelaynew tap. xfade is 0..PMAX
+
+ sD = GetDly ( delaysize, psamps, *ppsamp, tdelay );
+ sDnew = GetDly ( delaysize, psamps, *ppsamp, tdelaynew );
+ sD = sD + (((sDnew - sD) * xf) >> PBITS);
+
+ out = IIRFilter_Update_Order1 ( denom, cnumer, numer, pfsamps, sD );
+
+ **ppsamp = in;
+
+ DlyUpdate ( delaysize, psamps, ppsamp );
+
+ return ( (out * outgain) >> PBITS );
+}
+
+
+// classic allpass reverb
+// delaysize: delay line size in samples
+// tdelay: tap from this location - <= D
+// psamps: delay line buffer pointer of dimension delaysize+1
+// ppsamp: circular pointer, must be init to &psamps[0] before first call
+// fbgain: feedback value, 0-PMAX (normalized to 0.0-1.0)
+// outgain: gain
+
+// psamps0(n) -fbgain outgain
+// in(n)--->(+)--------.-----(*)-->(+)--(*)-> out(n)
+// ^ | ^
+// | [Delay d] |
+// | | |
+// | fbgain |psampsd(n) |
+// ----(*)---.-------------
+//
+// for each input sample 'in':
+// psamps0 = in + fbgain * psampsd
+// y = -fbgain * psamps0 + psampsd
+// delay (d, psamps) - psamps is the delay buffer array
+//
+// or, using circular delay, for each input sample 'in':
+//
+// Sd = GetDly (delaysize,psamps,ppsamp,delaysize)
+// S0 = in + fbgain*Sd
+// y = -fbgain*S0 + Sd
+// *ppsamp = S0
+// DlyUpdate(delaysize, psamps, &ppsamp)
+
+inline int DelayAllpass ( int delaysize, int tdelay, int *psamps, int **ppsamp, int fbgain, int outgain, int in )
+{
+ int out, s0, sD;
+
+ sD = GetDly ( delaysize, psamps, *ppsamp, tdelay );
+ s0 = in + (( fbgain * sD ) >> PBITS);
+
+ out = ( ( -fbgain * s0 ) >> PBITS ) + sD;
+ **ppsamp = s0;
+ DlyUpdate ( delaysize, psamps, ppsamp );
+
+ return ( (out * outgain) >> PBITS );
+}
+
+
+inline int DelayAllpass_xfade ( int delaysize, int tdelay, int tdelaynew, int xf, int *psamps, int **ppsamp, int fbgain, int outgain, int in )
+{
+ int out, s0, sD;
+ int sDnew;
+
+ // crossfade from t to tnew tap. xfade is 0..PMAX
+
+ sD = GetDly ( delaysize, psamps, *ppsamp, tdelay );
+ sDnew = GetDly ( delaysize, psamps, *ppsamp, tdelaynew );
+ sD = sD + (((sDnew - sD) * xf) >> PBITS);
+
+ s0 = in + (( fbgain * sD ) >> PBITS);
+
+ out = ( ( -fbgain * s0 ) >> PBITS ) + sD;
+ **ppsamp = s0;
+ DlyUpdate ( delaysize, psamps, ppsamp );
+
+ return ( (out * outgain) >> PBITS );
+}
+
+///////////////////////////////////////////////////////////////////////////////////
+// fixed point math for real-time wave table traversing, pitch shifting, resampling
+///////////////////////////////////////////////////////////////////////////////////
+
+#define FIX20_BITS 20 // 20 bits of fractional part
+#define FIX20_SCALE (1 << FIX20_BITS)
+
+#define FIX20_INTMAX ((1 << (32 - FIX20_BITS))-1) // maximum step integer
+
+#define FLOAT_TO_FIX20(a) ((int)((a) * (float)FIX20_SCALE)) // convert float to fixed point
+#define INT_TO_FIX20(a) (((int)(a)) << FIX20_BITS) // convert int to fixed point
+#define FIX20_TO_FLOAT(a) ((float)(a) / (float)FIX20_SCALE) // convert fix20 to float
+#define FIX20_INTPART(a) (((int)(a)) >> FIX20_BITS) // get integer part of fixed point
+#define FIX20_FRACPART(a) ((a) - (((a) >> FIX20_BITS) << FIX20_BITS)) // get fractional part of fixed point
+
+#define FIX20_FRACTION(a,b) (FIX(a)/(b)) // convert int a to fixed point, divide by b
+
+typedef int fix20int;
+
+/////////////////////////////////
+// DSP processor parameter block
+/////////////////////////////////
+
+// NOTE: these prototypes must match the XXX_Params ( prc_t *pprc ) and XXX_GetNext ( XXX_t *p, int x ) functions
+
+typedef void * (*prc_Param_t)( void *pprc ); // individual processor allocation functions
+typedef int (*prc_GetNext_t) ( void *pdata, int x ); // get next function for processor
+typedef int (*prc_GetNextN_t) ( void *pdata, portable_samplepair_t *pbuffer, int SampleCount, int op); // batch version of getnext
+typedef void (*prc_Free_t) ( void *pdata ); // free function for processor
+typedef void (*prc_Mod_t) (void *pdata, float v); // modulation function for processor
+
+#define OP_LEFT 0 // batch process left channel in place
+#define OP_RIGHT 1 // batch process right channel in place
+#define OP_LEFT_DUPLICATE 2 // batch process left channel in place, duplicate to right channel
+
+#define PRC_NULL 0 // pass through - must be 0
+#define PRC_DLY 1 // simple feedback reverb
+#define PRC_RVA 2 // parallel reverbs
+#define PRC_FLT 3 // lowpass or highpass filter
+#define PRC_CRS 4 // chorus
+#define PRC_PTC 5 // pitch shifter
+#define PRC_ENV 6 // adsr envelope
+#define PRC_LFO 7 // lfo
+#define PRC_EFO 8 // envelope follower
+#define PRC_MDY 9 // mod delay
+#define PRC_DFR 10 // diffusor - n series allpass delays
+#define PRC_AMP 11 // amplifier with distortion
+
+#define QUA_LO 0 // quality of filter or reverb. Must be 0,1,2,3.
+#define QUA_MED 1
+#define QUA_HI 2
+#define QUA_VHI 3
+#define QUA_MAX QUA_VHI
+
+#define CPRCPARAMS 16 // up to 16 floating point params for each processor type
+
+// processor definition - one for each running instance of a dsp processor
+
+struct prc_t
+{
+ int type; // PRC type
+
+ float prm[CPRCPARAMS]; // dsp processor parameters - array of floats
+
+ prc_Param_t pfnParam; // allocation function - takes ptr to prc, returns ptr to specialized data struct for proc type
+ prc_GetNext_t pfnGetNext; // get next function
+ prc_GetNextN_t pfnGetNextN; // batch version of get next
+ prc_Free_t pfnFree; // free function
+ prc_Mod_t pfnMod; // modulation function
+
+ void *pdata; // processor state data - ie: pdly, pflt etc.
+};
+
+// processor parameter ranges - for validating parameters during allocation of new processor
+
+typedef struct prm_rng_t
+{
+ int iprm; // parameter index
+ float lo; // min value of parameter
+ float hi; // max value of parameter
+} prm_rng_s;
+
+void PRC_CheckParams ( prc_t *pprc, prm_rng_t *prng );
+
+///////////
+// Filters
+///////////
+
+#define CFLTS 64 // max number of filters simultaneously active
+#define FLT_M 12 // max order of any filter
+
+#define FLT_LP 0 // lowpass filter
+#define FLT_HP 1 // highpass filter
+#define FLT_BP 2 // bandpass filter
+#define FTR_MAX FLT_BP
+
+// flt parameters
+
+struct flt_t
+{
+ bool fused; // true if slot in use
+
+ int b[FLT_M+1]; // filter numerator parameters (convert 0.0-1.0 to 0-PMAX representation)
+ int a[FLT_M+1]; // filter denominator parameters (convert 0.0-1.0 to 0-PMAX representation)
+ int w[FLT_M+1]; // filter state - samples (dimension of max (M, L))
+ int L; // filter order numerator (dimension of a[M+1])
+ int M; // filter order denominator (dimension of b[L+1])
+ int N; // # of series sections - 1 (0 = 1 section, 1 = 2 sections etc)
+
+ flt_t *pf1; // series cascaded versions of filter
+ flt_t *pf2;
+ flt_t *pf3;
+};
+
+// flt flts
+
+flt_t flts[CFLTS];
+
+void FLT_Init ( flt_t *pf ) { if ( pf ) Q_memset ( pf, 0, sizeof (flt_t) ); }
+void FLT_InitAll ( void ) { for ( int i = 0 ; i < CFLTS; i++ ) FLT_Init ( &flts[i] ); }
+
+void FLT_Free ( flt_t *pf )
+{
+ if ( pf )
+ {
+ if (pf->pf1)
+ Q_memset ( pf->pf1, 0, sizeof (flt_t) );
+
+ if (pf->pf2)
+ Q_memset ( pf->pf2, 0, sizeof (flt_t) );
+
+ if (pf->pf3)
+ Q_memset ( pf->pf3, 0, sizeof (flt_t) );
+
+ Q_memset ( pf, 0, sizeof (flt_t) );
+ }
+}
+
+void FLT_FreeAll ( void ) { for (int i = 0 ; i < CFLTS; i++) FLT_Free ( &flts[i] ); }
+
+
+// find a free filter from the filter pool
+// initialize filter numerator, denominator b[0..M], a[0..L]
+// gain scales filter numerator
+// N is # of series sections - 1
+
+flt_t * FLT_Alloc ( int N, int M, int L, int *a, int *b, float gain )
+{
+ int i, j;
+ flt_t *pf = NULL;
+
+ for (i = 0; i < CFLTS; i++)
+ {
+ if ( !flts[i].fused )
+ {
+ pf = &flts[i];
+
+ // transfer filter params into filter struct
+ pf->M = M;
+ pf->L = L;
+ pf->N = N;
+
+ for (j = 0; j <= M; j++)
+ pf->a[j] = a[j];
+
+ for (j = 0; j <= L; j++)
+ pf->b[j] = (int)((float)(b[j]) * gain);
+
+ pf->pf1 = NULL;
+ pf->pf2 = NULL;
+ pf->pf3 = NULL;
+
+ pf->fused = true;
+ break;
+ }
+ }
+
+ Assert(pf); // make sure we're not trying to alloc more than CFLTS flts
+
+ return pf;
+}
+
+// convert filter params cutoff and type into
+// iir transfer function params M, L, a[], b[]
+
+// iir filter, 1st order, transfer function is H(z) = b0 + b1 Z^-1 / a0 + a1 Z^-1
+// or H(z) = b0 - b1 Z^-1 / a0 + a1 Z^-1 for lowpass
+
+// design cutoff filter at 3db (.5 gain) p579
+
+void FLT_Design_3db_IIR ( float cutoff, float ftype, int *pM, int *pL, int *a, int *b )
+{
+ // ftype: FLT_LP, FLT_HP, FLT_BP
+
+ double Wc = 2.0 * M_PI * cutoff / SOUND_DMA_SPEED; // radians per sample
+ double Oc;
+ double fa;
+ double fb;
+
+ // calculations:
+ // Wc = 2pi * fc/44100 convert to radians
+ // Oc = tan (Wc/2) * Gc / sqt ( 1 - Gc^2) get analog version, low pass
+ // Oc = tan (Wc/2) * (sqt (1 - Gc^2)) / Gc analog version, high pass
+ // Gc = 10 ^ (-Ac/20) gain at cutoff. Ac = 3db, so Gc^2 = 0.5
+ // a = ( 1 - Oc ) / ( 1 + Oc )
+ // b = ( 1 - a ) / 2
+
+ Oc = tan ( Wc / 2.0 );
+
+ fa = ( 1.0 - Oc ) / ( 1.0 + Oc );
+
+ fb = ( 1.0 - fa ) / 2.0;
+
+ if ( ftype == FLT_HP )
+ fb = ( 1.0 + fa ) / 2.0;
+
+ a[0] = 0; // a0 always ignored
+ a[1] = (int)( -fa * PMAX ); // quantize params down to 0-PMAX >> PBITS
+ b[0] = (int)( fb * PMAX );
+ b[1] = b[0];
+
+ if ( ftype == FLT_HP )
+ b[1] = -b[1];
+
+ *pM = *pL = 1;
+
+ return;
+}
+
+// filter parameter order
+
+typedef enum
+{
+ flt_iftype,
+ flt_icutoff,
+ flt_iqwidth,
+ flt_iquality,
+ flt_igain,
+
+ flt_cparam // # of params
+} flt_e;
+
+// filter parameter ranges
+
+prm_rng_t flt_rng[] = {
+
+ {flt_cparam, 0, 0}, // first entry is # of parameters
+
+ {flt_iftype, 0, FTR_MAX}, // filter type FLT_LP, FLT_HP, FLT_BP
+ {flt_icutoff, 10, 22050}, // cutoff frequency in hz at -3db gain
+ {flt_iqwidth, 0, 11025}, // width of BP (cut in starts at cutoff)
+ {flt_iquality, 0, QUA_MAX}, // QUA_LO, _MED, _HI, _VHI = # of series sections
+ {flt_igain, 0.0, 10.0}, // output gain 0-10.0
+};
+
+
+// convert prc float params to iir filter params, alloc filter and return ptr to it
+// filter quality set by prc quality - 0,1,2
+
+flt_t * FLT_Params ( prc_t *pprc )
+{
+ float qual = pprc->prm[flt_iquality];
+ float cutoff = pprc->prm[flt_icutoff];
+ float ftype = pprc->prm[flt_iftype];
+ float qwidth = pprc->prm[flt_iqwidth];
+ float gain = pprc->prm[flt_igain];
+
+ int L = 0; // numerator order
+ int M = 0; // denominator order
+ int b[FLT_M+1]; // numerator params 0..PMAX
+ int b_scaled[FLT_M+1]; // gain scaled numerator
+ int a[FLT_M+1]; // denominator params 0..PMAX
+
+ int L_bp = 0; // bandpass numerator order
+ int M_bp = 0; // bandpass denominator order
+ int b_bp[FLT_M+1]; // bandpass numerator params 0..PMAX
+ int b_bp_scaled[FLT_M+1]; // gain scaled numerator
+ int a_bp[FLT_M+1]; // bandpass denominator params 0..PMAX
+
+ int N; // # of series sections
+ bool bpass = false;
+
+ // if qwidth > 0 then alloc bandpass filter (pf is lowpass)
+
+ if ( qwidth > 0.0 )
+ bpass = true;
+
+ if (bpass)
+ {
+ ftype = FLT_LP;
+ }
+
+ // low pass and highpass filter design
+
+ // 1st order IIR filter, 3db cutoff at fc
+
+ if ( bpass )
+ {
+ // highpass section
+
+ FLT_Design_3db_IIR ( cutoff, FLT_HP, &M_bp, &L_bp, a_bp, b_bp );
+ M_bp = clamp (M_bp, 1, FLT_M);
+ L_bp = clamp (L_bp, 1, FLT_M);
+ cutoff += qwidth;
+ }
+
+ // lowpass section
+
+ FLT_Design_3db_IIR ( cutoff, (int)ftype, &M, &L, a, b );
+
+ M = clamp (M, 1, FLT_M);
+ L = clamp (L, 1, FLT_M);
+
+ // quality = # of series sections - 1
+
+ N = clamp ((int)qual, 0, 3);
+
+ // make sure we alloc at least 2 filters
+
+ if (bpass)
+ N = max(N, 1);
+
+ flt_t *pf0 = NULL;
+ flt_t *pf1 = NULL;
+ flt_t *pf2 = NULL;
+ flt_t *pf3 = NULL;
+
+ // scale b numerators with gain - only scale for first filter if series filters
+
+ for (int i = 0; i < FLT_M; i++)
+ {
+ b_bp_scaled[i] = (int)((float)(b_bp[i]) * gain );
+ b_scaled[i] = (int)((float)(b[i]) * gain );
+ }
+
+ if (bpass)
+ {
+ // 1st filter is lowpass
+
+ pf0 = FLT_Alloc ( N, M_bp, L_bp, a_bp, b_bp_scaled, 1.0 );
+ }
+ else
+ {
+ pf0 = FLT_Alloc ( N, M, L, a, b_scaled, 1.0 );
+ }
+
+ // allocate series filters
+
+ if (pf0)
+ {
+ switch (N)
+ {
+ case 3:
+ // alloc last filter as lowpass also if FLT_BP
+ if (bpass)
+ pf3 = FLT_Alloc ( 0, M_bp, L_bp, a_bp, b_bp, 1.0 );
+ else
+ pf3 = FLT_Alloc ( 0, M, L, a, b, 1.0 );
+ case 2:
+ pf2 = FLT_Alloc ( 0, M, L, a, b, 1.0 );
+ case 1:
+ pf1 = FLT_Alloc ( 0, M, L, a, b, 1.0 );
+ case 0:
+ break;
+ }
+
+ pf0->pf1 = pf1;
+ pf0->pf2 = pf2;
+ pf0->pf3 = pf3;
+ }
+
+ return pf0;
+}
+
+inline void * FLT_VParams ( void *p )
+{
+ PRC_CheckParams( (prc_t *)p, flt_rng);
+ return (void *) FLT_Params ((prc_t *)p);
+}
+
+inline void FLT_Mod ( void *p, float v ) { return; }
+
+// get next filter value for filter pf and input x
+
+inline int FLT_GetNext ( flt_t *pf, int x )
+{
+ flt_t *pf1;
+ flt_t *pf2;
+ flt_t *pf3;
+ int y;
+
+ switch( pf->N )
+ {
+ default:
+ case 0:
+ return IIRFilter_Update_Order1(pf->a, pf->L, pf->b, pf->w, x);
+ case 1:
+ pf1 = pf->pf1;
+
+ y = IIRFilter_Update_Order1(pf->a, pf->L, pf->b, pf->w, x);
+ return IIRFilter_Update_Order1(pf1->a, pf1->L, pf1->b, pf1->w, y);
+ case 2:
+ pf1 = pf->pf1;
+ pf2 = pf->pf2;
+
+ y = IIRFilter_Update_Order1(pf->a, pf->L, pf->b, pf->w, x);
+ y = IIRFilter_Update_Order1(pf1->a, pf1->L, pf1->b, pf1->w, y);
+ return IIRFilter_Update_Order1(pf2->a, pf2->L, pf2->b, pf2->w, y);
+ case 3:
+ pf1 = pf->pf1;
+ pf2 = pf->pf2;
+ pf3 = pf->pf3;
+
+ y = IIRFilter_Update_Order1(pf->a, pf->L, pf->b, pf->w, x);
+ y = IIRFilter_Update_Order1(pf1->a, pf1->L, pf1->b, pf1->w, y);
+ y = IIRFilter_Update_Order1(pf2->a, pf2->L, pf2->b, pf2->w, y);
+ return IIRFilter_Update_Order1(pf3->a, pf3->L, pf3->b, pf3->w, y);
+ }
+}
+
+// batch version for performance
+
+inline void FLT_GetNextN( flt_t *pflt, portable_samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ portable_samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = FLT_GetNext( pflt, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = FLT_GetNext( pflt, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = FLT_GetNext( pflt, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Positional updaters for pitch shift etc
+///////////////////////////////////////////////////////////////////////////
+
+// looping position within a wav, with integer and fractional parts
+// used for pitch shifting, upsampling/downsampling
+// 20 bits of fraction, 8+ bits of integer
+
+struct pos_t
+{
+
+ fix20int step; // wave table whole and fractional step value
+ fix20int cstep; // current cummulative step value
+ int pos; // current position within wav table
+
+ int D; // max dimension of array w[0...D] ie: # of samples = D+1
+};
+
+// circular wrap of pointer p, relative to array w
+// D max buffer index w[0...D] (count of samples in buffer is D+1)
+// i circular index
+
+inline void POS_Wrap ( int D, int *i )
+{
+ if ( *i > D )
+ *i -= D + 1; // when *pi = D + 1, it wraps around to *pi = 0
+
+ if ( *i < 0 )
+ *i += D + 1; // when *pi = - 1, it wraps around to *pi = D
+}
+
+// set initial update value - fstep can have no more than 8 bits of integer and 20 bits of fract
+// D is array max dimension w[0...D] (ie: size D+1)
+// w is ptr to array
+// p is ptr to pos_t to initialize
+
+inline void POS_Init( pos_t *p, int D, float fstep )
+{
+ float step = fstep;
+
+ // make sure int part of step is capped at fix20_intmax
+
+ if ((int)step > FIX20_INTMAX)
+ step = (step - (int)step) + FIX20_INTMAX;
+
+ p->step = FLOAT_TO_FIX20(step); // convert fstep to fixed point
+ p->cstep = 0;
+ p->pos = 0; // current update value
+
+ p->D = D; // always init to end value, in case we're stepping backwards
+}
+
+// change step value - this is an instantaneous change, not smoothed.
+
+inline void POS_ChangeVal( pos_t *p, float fstepnew )
+{
+ p->step = FLOAT_TO_FIX20( fstepnew ); // convert fstep to fixed point
+}
+
+// return current integer position, then update internal position value
+
+inline int POS_GetNext ( pos_t *p )
+{
+
+ //float f = FIX20_TO_FLOAT(p->cstep);
+ //int i1 = FIX20_INTPART(p->cstep);
+ //float f1 = FIX20_TO_FLOAT(FIX20_FRACPART(p->cstep));
+ //float f2 = FIX20_TO_FLOAT(p->step);
+
+ p->cstep += p->step; // update accumulated fraction step value (fixed point)
+ p->pos += FIX20_INTPART( p->cstep ); // update pos with integer part of accumulated step
+ p->cstep = FIX20_FRACPART( p->cstep ); // throw away the integer part of accumulated step
+
+ // wrap pos around either end of buffer if needed
+
+ POS_Wrap(p->D, &(p->pos));
+
+ // make sure returned position is within array bounds
+
+ Assert (p->pos <= p->D);
+
+ return p->pos;
+}
+
+// oneshot position within wav
+struct pos_one_t
+{
+ pos_t p; // pos_t
+
+ bool fhitend; // flag indicating we hit end of oneshot wav
+};
+
+// set initial update value - fstep can have no more than 8 bits of integer and 20 bits of fract
+// one shot position - play only once, don't wrap, when hit end of buffer, return last position
+
+inline void POS_ONE_Init( pos_one_t *p1, int D, float fstep )
+{
+ POS_Init( &p1->p, D, fstep ) ;
+
+ p1->fhitend = false;
+}
+
+// return current integer position, then update internal position value
+
+inline int POS_ONE_GetNext ( pos_one_t *p1 )
+{
+ int pos;
+ pos_t *p0;
+
+ pos = p1->p.pos; // return current position
+
+ if (p1->fhitend)
+ return pos;
+
+ p0 = &(p1->p);
+ p0->cstep += p0->step; // update accumulated fraction step value (fixed point)
+ p0->pos += FIX20_INTPART( p0->cstep ); // update pos with integer part of accumulated step
+ //p0->cstep = SIGN(p0->cstep) * FIX20_FRACPART( p0->cstep );
+ p0->cstep = FIX20_FRACPART( p0->cstep ); // throw away the integer part of accumulated step
+
+ // if we wrapped, stop updating, always return last position
+ // if step value is 0, return hit end
+
+ if (!p0->step || p0->pos < 0 || p0->pos >= p0->D )
+ p1->fhitend = true;
+ else
+ pos = p0->pos;
+
+ // make sure returned value is within array bounds
+
+ Assert ( pos <= p0->D );
+
+ return pos;
+}
+
+
+/////////////////////
+// Reverbs and delays
+/////////////////////
+
+#define CDLYS 128 // max delay lines active. Also used for lfos.
+
+#define DLY_PLAIN 0 // single feedback loop
+#define DLY_ALLPASS 1 // feedback and feedforward loop - flat frequency response (diffusor)
+#define DLY_LOWPASS 2 // lowpass filter in feedback loop
+#define DLY_LINEAR 3 // linear delay, no feedback, unity gain
+#define DLY_FLINEAR 4 // linear delay with lowpass filter and output gain
+#define DLY_LOWPASS_4TAP 5 // lowpass filter in feedback loop, 4 delay taps
+#define DLY_PLAIN_4TAP 6 // single feedback loop, 4 delay taps
+
+#define DLY_MAX DLY_PLAIN_4TAP
+
+#define DLY_HAS_MULTITAP(a) ((a) == DLY_LOWPASS_4TAP || (a) == DLY_PLAIN_4TAP)
+#define DLY_HAS_FILTER(a) ((a) == DLY_FLINEAR || (a) == DLY_LOWPASS || (a) == DLY_LOWPASS_4TAP)
+
+#define DLY_TAP_FEEDBACK_GAIN 0.25 // drop multitap feedback to compensate for sum of taps in dly_*multitap()
+
+#define DLY_NORMALIZING_REDUCTION_MAX 0.25 // don't reduce gain (due to feedback) below N% of original gain
+
+// delay line
+
+struct dly_t
+{
+
+ bool fused; // true if dly is in use
+ int type; // delay type
+
+ int D; // delay size, in samples
+ int t; // current tap, <= D
+ int tnew; // crossfading to tnew
+ int xf; // crossfade value of t (0..PMAX)
+ int t1,t2,t3; // additional taps for multi-tap delays
+ int a1,a2,a3; // feedback values for taps
+ int D0; // original delay size (only relevant if calling DLY_ChangeVal)
+ int *p; // circular buffer pointer
+ int *w; // array of samples
+
+ int a; // feedback value 0..PMAX,normalized to 0-1.0
+ int b; // gain value 0..PMAX, normalized to 0-1.0
+
+ flt_t *pflt; // pointer to filter, if type DLY_LOWPASS
+};
+
+dly_t dlys[CDLYS]; // delay lines
+
+void DLY_Init ( dly_t *pdly ) { if ( pdly ) Q_memset( pdly, 0, sizeof (dly_t)); }
+void DLY_InitAll ( void ) { for (int i = 0 ; i < CDLYS; i++) DLY_Init ( &dlys[i] ); }
+void DLY_Free ( dly_t *pdly )
+{
+ // free memory buffer
+
+ if ( pdly )
+ {
+ FLT_Free ( pdly->pflt );
+
+ if ( pdly->w )
+ {
+ delete[] pdly->w;
+ }
+
+ // free dly slot
+
+ Q_memset ( pdly, 0, sizeof (dly_t) );
+ }
+}
+
+
+void DLY_FreeAll ( void ) { for (int i = 0; i < CDLYS; i++ ) DLY_Free ( &dlys[i] ); }
+
+// return adjusted feedback value for given dly
+// such that decay time is same as that for dmin and fbmin
+
+// dmin - minimum delay
+// fbmin - minimum feedback
+// dly - delay to match decay to dmin, fbmin
+
+float DLY_NormalizeFeedback ( int dmin, float fbmin, int dly )
+{
+ // minimum decay time T to -60db for a simple reverb is:
+
+ // Tmin = (ln 10^-3 / Ln fbmin) * (Dmin / fs)
+
+ // where fs = sample frequency
+
+ // similarly,
+
+ // Tdly = (ln 10^-3 / Ln fb) * (D / fs)
+
+ // setting Tdly = Tmin and solving for fb gives:
+
+ // D / Dmin = ln fb / ln fbmin
+
+ // since y^x = z gives x = ln z / ln y
+
+ // fb = fbmin ^ (D/Dmin)
+
+ float fb = powf (fbmin, (float)dly / (float) dmin);
+
+ return fb;
+}
+
+// set up 'b' gain parameter of feedback delay to
+// compensate for gain caused by feedback 'fb'.
+
+void DLY_SetNormalizingGain ( dly_t *pdly, int feedback )
+{
+ // compute normalized gain, set as output gain
+
+ // calculate gain of delay line with feedback, and use it to
+ // reduce output. ie: force delay line with feedback to unity gain
+
+ // for constant input x with feedback fb:
+
+ // out = x + x*fb + x * fb^2 + x * fb^3...
+ // gain = out/x
+ // so gain = 1 + fb + fb^2 + fb^3...
+ // which, by the miracle of geometric series, equates to 1/1-fb
+ // thus, gain = 1/(1-fb)
+
+ float fgain = 0;
+ float gain;
+ int b;
+ float fb = (float)feedback;
+
+ fb = fb / (float)PMAX;
+ fb = fpmin(fb, 0.999f);
+
+ // if b is 0, set b to PMAX (1)
+
+ b = pdly->b ? pdly->b : PMAX;
+
+ fgain = 1.0 / (1.0 - fb);
+
+ // compensating gain - multiply rva output by gain then >> PBITS
+
+ gain = (int)((1.0 / fgain) * PMAX);
+
+ gain = gain * 4; // compensate for fact that gain calculation is for +/- 32767 amplitude wavs
+ // ie: ok to allow a bit more gain because most wavs are not at theoretical peak amplitude at all times
+
+ // limit gain reduction to N% PMAX
+
+ gain = clamp (gain, (float)(PMAX * DLY_NORMALIZING_REDUCTION_MAX), (float)PMAX);
+
+ gain = ((float)b/(float)PMAX) * gain; // scale final gain by pdly->b.
+
+ pdly->b = (int)gain;
+}
+
+void DLY_ChangeTaps ( dly_t *pdly, int t0, int t1, int t2, int t3 );
+
+// allocate a new delay line
+// D number of samples to delay
+// a feedback value (0-PMAX normalized to 0.0-1.0)
+// b gain value (0-PMAX normalized to 0.0-1.0) - this is folded into the filter fb params
+// if DLY_LOWPASS or DLY_FLINEAR:
+// L - numerator order of filter
+// M - denominator order of filter
+// fb - numerator params, M+1
+// fa - denominator params, L+1
+
+dly_t * DLY_AllocLP ( int D, int a, int b, int type, int M, int L, int *fa, int *fb )
+{
+ int *w;
+ int i;
+ dly_t *pdly = NULL;
+ int feedback;
+
+ // find open slot
+
+ for (i = 0; i < CDLYS; i++)
+ {
+ if (!dlys[i].fused)
+ {
+ pdly = &dlys[i];
+ DLY_Init( pdly );
+ break;
+ }
+ }
+
+ if ( i == CDLYS )
+ {
+ DevMsg ("DSP: Warning, failed to allocate delay line.\n" );
+ return NULL; // all delay lines in use
+ }
+
+ // save original feedback value
+
+ feedback = a;
+
+ // adjust feedback a, gain b if delay is multitap unit
+
+ if ( DLY_HAS_MULTITAP(type) )
+ {
+ // split output gain over 4 taps
+
+ b = (int)((float)(b) * DLY_TAP_FEEDBACK_GAIN);
+ }
+
+ if ( DLY_HAS_FILTER(type) )
+ {
+ // alloc lowpass iir_filter
+ // delay feedback gain is built into filter gain
+
+ float gain = (float)a / (float)(PMAX);
+
+ pdly->pflt = FLT_Alloc( 0, M, L, fa, fb, gain );
+ if ( !pdly->pflt )
+ {
+ DevMsg ("DSP: Warning, failed to allocate filter for delay line.\n" );
+ return NULL;
+ }
+ }
+
+ // alloc delay memory
+ w = new int[D+1];
+ if ( !w )
+ {
+ Warning( "Sound DSP: Failed to lock.\n");
+ FLT_Free ( pdly->pflt );
+ return NULL;
+ }
+
+ // clear delay array
+
+ Q_memset (w, 0, sizeof(int) * (D+1));
+
+ // init values
+
+ pdly->type = type;
+ pdly->D = D;
+ pdly->t = D; // set delay tap to full delay
+ pdly->tnew = D;
+ pdly->xf = 0;
+ pdly->D0 = D;
+ pdly->p = w; // init circular pointer to head of buffer
+ pdly->w = w;
+ pdly->a = min( a, PMAX - 1 ); // do not allow 100% feedback
+ pdly->b = b;
+ pdly->fused = true;
+
+ if ( type == DLY_LINEAR || type == DLY_FLINEAR )
+ {
+ // linear delay has no feedback and unity gain
+
+ pdly->a = 0;
+ pdly->b = PMAX;
+ }
+ else
+ {
+ // adjust b to compensate for feedback gain of steady state max input
+
+ DLY_SetNormalizingGain( pdly, feedback );
+ }
+
+ if ( DLY_HAS_MULTITAP(type) )
+ {
+ // initially set up all taps to same value - caller uses DLY_ChangeTaps to change values
+
+ DLY_ChangeTaps( pdly, D, D, D, D );
+ }
+
+ return (pdly);
+}
+
+// allocate lowpass or allpass delay
+
+dly_t * DLY_Alloc( int D, int a, int b, int type )
+{
+ return DLY_AllocLP( D, a, b, type, 0, 0, 0, 0 );
+}
+
+
+// Allocate new delay, convert from float params in prc preset to internal parameters
+// Uses filter params in prc if delay is type lowpass
+
+// delay parameter order
+
+typedef enum {
+
+ dly_idtype, // NOTE: first 8 params must match those in mdy_e
+ dly_idelay,
+ dly_ifeedback,
+ dly_igain,
+
+ dly_iftype,
+ dly_icutoff,
+ dly_iqwidth,
+ dly_iquality,
+
+ dly_itap1,
+ dly_itap2,
+ dly_itap3,
+
+ dly_cparam
+
+} dly_e;
+
+
+// delay parameter ranges
+
+prm_rng_t dly_rng[] = {
+
+ {dly_cparam, 0, 0}, // first entry is # of parameters
+
+ // delay params
+
+ {dly_idtype, 0, DLY_MAX}, // delay type DLY_PLAIN, DLY_LOWPASS, DLY_ALLPASS etc
+ {dly_idelay, -1.0, 1000.0}, // delay in milliseconds (-1 forces auto dsp to set delay value from room size)
+ {dly_ifeedback, 0.0, 0.99}, // feedback 0-1.0
+ {dly_igain, 0.0, 10.0}, // final gain of output stage, 0-10.0
+
+ // filter params if dly type DLY_LOWPASS or DLY_FLINEAR
+
+ {dly_iftype, 0, FTR_MAX},
+ {dly_icutoff, 10.0, 22050.0},
+ {dly_iqwidth, 100.0, 11025.0},
+ {dly_iquality, 0, QUA_MAX},
+ // note: -1 flag tells auto dsp to get value directly from room size
+ {dly_itap1, -1.0, 1000.0}, // delay in milliseconds NOTE: delay > tap3 > tap2 > tap1
+ {dly_itap2, -1.0, 1000.0}, // delay in milliseconds
+ {dly_itap3, -1.0, 1000.0}, // delay in milliseconds
+};
+
+dly_t * DLY_Params ( prc_t *pprc )
+{
+ dly_t *pdly = NULL;
+ int D, a, b;
+
+ float delay = fabs(pprc->prm[dly_idelay]);
+ float feedback = pprc->prm[dly_ifeedback];
+ float gain = pprc->prm[dly_igain];
+ int type = pprc->prm[dly_idtype];
+
+ float ftype = pprc->prm[dly_iftype];
+ float cutoff = pprc->prm[dly_icutoff];
+ float qwidth = pprc->prm[dly_iqwidth];
+ float qual = pprc->prm[dly_iquality];
+
+ float t1 = fabs(pprc->prm[dly_itap1]);
+ float t2 = fabs(pprc->prm[dly_itap2]);
+ float t3 = fabs(pprc->prm[dly_itap3]);
+
+ D = MSEC_TO_SAMPS(delay); // delay samples
+ a = feedback * PMAX; // feedback
+ b = gain * PMAX; // gain
+
+ switch ( (int) type )
+ {
+ case DLY_PLAIN:
+ case DLY_PLAIN_4TAP:
+ case DLY_ALLPASS:
+ case DLY_LINEAR:
+ pdly = DLY_Alloc( D, a, b, type );
+ break;
+
+ case DLY_FLINEAR:
+ case DLY_LOWPASS:
+ case DLY_LOWPASS_4TAP:
+ {
+ // set up dummy lowpass filter to convert params
+
+ prc_t prcf;
+
+ prcf.prm[flt_iquality] = qual; // 0,1,2 - (0 or 1 low quality implies faster execution time)
+ prcf.prm[flt_icutoff] = cutoff;
+ prcf.prm[flt_iftype] = ftype;
+ prcf.prm[flt_iqwidth] = qwidth;
+ prcf.prm[flt_igain] = 1.0;
+
+ flt_t *pflt = (flt_t *)FLT_Params ( &prcf );
+
+ if ( !pflt )
+ {
+ DevMsg ("DSP: Warning, failed to allocate filter.\n" );
+ return NULL;
+ }
+
+ pdly = DLY_AllocLP ( D, a, b, type, pflt->M, pflt->L, pflt->a, pflt->b );
+
+ FLT_Free ( pflt );
+ break;
+ }
+ }
+
+ // set up multi-tap delays
+
+ if ( pdly && DLY_HAS_MULTITAP((int)type) )
+ DLY_ChangeTaps( pdly, D, MSEC_TO_SAMPS(t1), MSEC_TO_SAMPS(t2), MSEC_TO_SAMPS(t3) );
+
+ return pdly;
+}
+
+inline void * DLY_VParams ( void *p )
+{
+ PRC_CheckParams( (prc_t *)p, dly_rng );
+ return (void *) DLY_Params ((prc_t *)p);
+}
+
+// get next value from delay line, move x into delay line
+
+inline int DLY_GetNext ( dly_t *pdly, int x )
+{
+ switch (pdly->type)
+ {
+ default:
+ case DLY_PLAIN:
+ return ReverbSimple( pdly->D, pdly->t, pdly->w, &pdly->p, pdly->a, pdly->b, x );
+ case DLY_ALLPASS:
+ return DelayAllpass( pdly->D, pdly->t, pdly->w, &pdly->p, pdly->a, pdly->b, x );
+ case DLY_LOWPASS:
+ return DelayLowpass( pdly->D, pdly->t, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, x );
+ case DLY_LINEAR:
+ return DelayLinear( pdly->D, pdly->t, pdly->w, &pdly->p, x );
+ case DLY_FLINEAR:
+ return DelayLinear_lowpass( pdly->D, pdly->t, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, x );
+ case DLY_PLAIN_4TAP:
+ return ReverbSimple_multitap( pdly->D, pdly->t, pdly->t1, pdly->t2, pdly->t3, pdly->w, &pdly->p, pdly->a, pdly->b, x );
+ case DLY_LOWPASS_4TAP:
+ return DelayLowpass_multitap( pdly->D, pdly->t, pdly->t1, pdly->t2,pdly->t3, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, x );
+ }
+}
+
+inline int DLY_GetNextXfade ( dly_t *pdly, int x )
+{
+
+ switch (pdly->type)
+ {
+ default:
+ case DLY_PLAIN:
+ return ReverbSimple_xfade( pdly->D, pdly->t, pdly->tnew, pdly->xf, pdly->w, &pdly->p, pdly->a, pdly->b, x );
+ case DLY_ALLPASS:
+ return DelayAllpass_xfade( pdly->D, pdly->t, pdly->tnew, pdly->xf, pdly->w, &pdly->p, pdly->a, pdly->b, x );
+ case DLY_LOWPASS:
+ return DelayLowpass_xfade( pdly->D, pdly->t, pdly->tnew, pdly->xf, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, x );
+ case DLY_LINEAR:
+ return DelayLinear_xfade( pdly->D, pdly->t, pdly->tnew, pdly->xf, pdly->w, &pdly->p, x );
+ case DLY_FLINEAR:
+ return DelayLinear_lowpass_xfade( pdly->D, pdly->t, pdly->tnew, pdly->xf, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, x );
+ case DLY_PLAIN_4TAP:
+ return ReverbSimple_multitap_xfade( pdly->D, pdly->t, pdly->tnew, pdly->xf, pdly->t1, pdly->t2, pdly->t3, pdly->w, &pdly->p, pdly->a, pdly->b, x );
+ case DLY_LOWPASS_4TAP:
+ return DelayLowpass_multitap_xfade( pdly->D, pdly->t, pdly->tnew, pdly->xf, pdly->t1, pdly->t2, pdly->t3, pdly->w, &(pdly->p), pdly->a, pdly->b, pdly->pflt->a, pdly->pflt->L, pdly->pflt->b, pdly->pflt->w, x );
+ }
+}
+
+// batch version for performance
+// UNDONE: a) unwind this more - pb increments by 2 to avoid pb->left or pb->right deref.
+// UNDONE: b) all filter and delay params are dereferenced outside of DLY_GetNext and passed as register values
+// UNDONE: c) pull case statement in dly_getnext out, so loop directly calls the inline dly_*() routine.
+
+inline void DLY_GetNextN( dly_t *pdly, portable_samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ portable_samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = DLY_GetNext( pdly, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = DLY_GetNext( pdly, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = DLY_GetNext( pdly, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+
+// get tap on t'th sample in delay - don't update buffer pointers, this is done via DLY_GetNext
+// Only valid for DLY_LINEAR.
+
+inline int DLY_GetTap ( dly_t *pdly, int t )
+{
+ return GetDly (pdly->D, pdly->w, pdly->p, t );
+}
+
+#define SWAP(a,b,t) {(t) = (a); (a) = (b); (b) = (t);}
+
+// make instantaneous change to tap values t0..t3
+// all values of t must be less than original delay D
+// only processed for DLY_LOWPASS_4TAP & DLY_PLAIN_4TAP
+// NOTE: pdly->a feedback must have been set before this call!
+void DLY_ChangeTaps ( dly_t *pdly, int t0, int t1, int t2, int t3 )
+{
+ if (!pdly)
+ return;
+
+ int temp;
+
+ // sort taps to make sure t3 > t2 > t1 > t0 !
+
+ for (int i = 0; i < 4; i++)
+ {
+ if (t0 > t1) SWAP(t0, t1, temp);
+ if (t1 > t2) SWAP(t1, t2, temp);
+ if (t2 > t3) SWAP(t2, t3, temp);
+ }
+
+ pdly->t = min ( t0, pdly->D0 );
+ pdly->t1 = min ( t1, pdly->D0 );
+ pdly->t2 = min ( t2, pdly->D0 );
+ pdly->t3 = min ( t3, pdly->D0 );
+
+}
+
+// make instantaneous change for first delay tap 't' to new delay value.
+// t tap value must be <= original D (ie: we don't do any reallocation here)
+
+void DLY_ChangeVal ( dly_t *pdly, int t )
+{
+ // never set delay > original delay
+
+ pdly->t = min ( t, pdly->D0 );
+}
+
+// ignored - use MDY_ for modulatable delay
+
+inline void DLY_Mod ( void *p, float v ) { return; }
+
+
+/////////////////////////////////////////////////////////////////////////////
+// Ramp - used for varying smoothly between int parameters ie: modulation delays
+/////////////////////////////////////////////////////////////////////////////
+
+
+struct rmp_t
+{
+ int initval; // initial ramp value
+ int target; // final ramp value
+ int sign; // increasing (1) or decreasing (-1) ramp
+
+ int yprev; // previous output value
+ bool fhitend; // true if hit end of ramp
+ bool bEndAtTime; // if true, fhitend is true when ramp time is hit (even if target not hit)
+ // if false, then fhitend is true only when target is hit
+ pos_one_t ps; // current ramp output
+};
+
+// ramp smoothly between initial value and target value in approx 'ramptime' seconds.
+// (initial value may be greater or less than target value)
+// never changes output by more than +1 or -1 (which can cause the ramp to take longer to complete than ramptime - see bEndAtTime)
+// called once per sample while ramping
+// ramptime - duration of ramp in seconds
+// initval - initial ramp value
+// targetval - target ramp value
+// if bEndAtTime is true, then RMP_HitEnd returns true when ramp time is reached, EVEN IF TARGETVAL IS NOT REACHED
+// if bEndAtTime is false, then RMP_HitEnd returns true when targetval is reached, EVEN IF DELTA IN RAMP VALUES IS > +/- 1
+
+void RMP_Init( rmp_t *prmp, float ramptime, int initval, int targetval, bool bEndAtTime )
+{
+ int rise;
+ int run;
+
+ if (prmp)
+ Q_memset( prmp, 0, sizeof (rmp_t) );
+ else
+ return;
+
+ run = (int) (ramptime * SOUND_DMA_SPEED); // 'samples' in ramp
+ rise = (targetval - initval); // height of ramp
+
+ // init fixed point iterator to iterate along the height of the ramp 'rise'
+ // always iterates from 0..'rise', increasing in value
+
+ POS_ONE_Init( &prmp->ps, ABS( rise ), ABS((float) rise) / ((float) run) );
+
+ prmp->yprev = initval;
+ prmp->initval = initval;
+ prmp->target = targetval;
+ prmp->sign = SIGN( rise );
+ prmp->bEndAtTime = bEndAtTime;
+
+}
+
+// continues from current position to new target position
+
+void RMP_SetNext( rmp_t *prmp, float ramptime, int targetval )
+{
+ RMP_Init ( prmp, ramptime, prmp->yprev, targetval, prmp->bEndAtTime );
+}
+
+inline bool RMP_HitEnd ( rmp_t *prmp )
+{
+ return prmp->fhitend;
+}
+
+inline void RMP_SetEnd ( rmp_t *prmp )
+{
+ prmp->fhitend = true;
+}
+
+// get next ramp value & update ramp, if bEndAtTime is true, never varies by more than +1 or -1 between calls
+// when ramp hits target value, it thereafter always returns last value
+
+inline int RMP_GetNext( rmp_t *prmp )
+{
+ int y;
+ int d;
+
+ // if we hit ramp end, return last value
+
+ if (prmp->fhitend)
+ return prmp->yprev;
+
+ // get next integer position in ramp height.
+
+ d = POS_ONE_GetNext( &prmp->ps );
+
+ if ( prmp->ps.fhitend )
+ prmp->fhitend = true;
+
+ // increase or decrease from initval, depending on ramp sign
+
+ if ( prmp->sign > 0 )
+ y = prmp->initval + d;
+ else
+ y = prmp->initval - d;
+
+ // if bEndAtTime is true, only update current height by a max of +1 or -1
+ // this also means that for short ramp times, we may not hit target
+
+ if (prmp->bEndAtTime)
+ {
+ if ( ABS( y - prmp->yprev ) >= 1 )
+ prmp->yprev += prmp->sign;
+ }
+ else
+ {
+ // always hits target - but varies by more than +/- 1
+
+ prmp->yprev = y;
+ }
+
+ return prmp->yprev;
+}
+
+// get current ramp value, don't update ramp
+
+inline int RMP_GetCurrent( rmp_t *prmp )
+{
+ return prmp->yprev;
+}
+
+
+//////////////
+// mod delay
+//////////////
+
+// modulate delay time anywhere from 0..D using MDY_ChangeVal. no output glitches (uses RMP)
+
+#define CMDYS 64 // max # of mod delays active (steals from delays)
+
+struct mdy_t
+{
+ bool fused;
+
+ bool fchanging; // true if modulating to new delay value
+
+ dly_t *pdly; // delay
+
+ float ramptime; // ramp 'glide' time - time in seconds to change between values
+
+ int mtime; // time in samples between delay changes. 0 implies no self-modulating
+ int mtimecur; // current time in samples until next delay change
+ float depth; // modulate delay from D to D - (D*depth) depth 0-1.0
+
+ int mix; // PMAX as % processed fx signal mix
+
+ rmp_t rmp_interp; // interpolation ramp 0...PMAX
+
+ bool bPhaseInvert; // if true, invert phase of output
+
+};
+
+mdy_t mdys[CMDYS];
+
+void MDY_Init( mdy_t *pmdy ) { if (pmdy) Q_memset( pmdy, 0, sizeof (mdy_t) ); };
+void MDY_Free( mdy_t *pmdy ) { if (pmdy) { DLY_Free (pmdy->pdly); Q_memset( pmdy, 0, sizeof (mdy_t) ); } };
+void MDY_InitAll() { for (int i = 0; i < CMDYS; i++) MDY_Init( &mdys[i] ); };
+void MDY_FreeAll() { for (int i = 0; i < CMDYS; i++) MDY_Free( &mdys[i] ); };
+
+
+// allocate mod delay, given previously allocated dly (NOTE: mod delay only sweeps tap 0, not t1,t2 or t3)
+// ramptime is time in seconds for delay to change from dcur to dnew
+// modtime is time in seconds between modulations. 0 if no self-modulation
+// depth is 0-1.0 multiplier, new delay values when modulating are Dnew = randomlong (D - D*depth, D)
+// mix - 0-1.0, default 1.0 for 100% fx mix - pans between input signal and fx signal
+
+mdy_t *MDY_Alloc ( dly_t *pdly, float ramptime, float modtime, float depth, float mix )
+{
+ int i;
+ mdy_t *pmdy;
+
+ if ( !pdly )
+ return NULL;
+
+ for (i = 0; i < CMDYS; i++)
+ {
+ if ( !mdys[i].fused )
+ {
+ pmdy = &mdys[i];
+
+ MDY_Init ( pmdy );
+
+ pmdy->pdly = pdly;
+
+ if ( !pmdy->pdly )
+ {
+ DevMsg ("DSP: Warning, failed to allocate delay for mod delay.\n" );
+ return NULL;
+ }
+
+ pmdy->fused = true;
+ pmdy->ramptime = ramptime;
+ pmdy->mtime = SEC_TO_SAMPS(modtime);
+ pmdy->mtimecur = pmdy->mtime;
+ pmdy->depth = depth;
+ pmdy->mix = int ( PMAX * mix );
+ pmdy->bPhaseInvert = false;
+
+ return pmdy;
+ }
+ }
+
+ DevMsg ("DSP: Warning, failed to allocate mod delay.\n" );
+ return NULL;
+}
+
+// change to new delay tap value t samples, ramp linearly over ramptime seconds
+
+void MDY_ChangeVal ( mdy_t *pmdy, int t )
+{
+ // if D > original delay value, cap at original value
+
+ t = min (pmdy->pdly->D0, t);
+
+ pmdy->fchanging = true;
+
+ // init interpolation ramp - always hit target
+
+ RMP_Init ( &pmdy->rmp_interp, pmdy->ramptime, 0, PMAX, false );
+
+ // init delay xfade values
+
+ pmdy->pdly->tnew = t;
+ pmdy->pdly->xf = 0;
+}
+
+// interpolate between current and target delay values
+
+inline int MDY_GetNext( mdy_t *pmdy, int x )
+{
+ int xout;
+
+ if ( !pmdy->fchanging )
+ {
+ // not modulating...
+
+ xout = DLY_GetNext( pmdy->pdly, x );
+
+ if ( !pmdy->mtime )
+ {
+ // return right away if not modulating (not changing and not self modulating)
+
+ goto mdy_return;
+ }
+ }
+ else
+ {
+ // modulating...
+
+ xout = DLY_GetNextXfade( pmdy->pdly, x );
+
+ // get xfade ramp & set up delay xfade value for next call to DLY_GetNextXfade()
+
+ pmdy->pdly->xf = RMP_GetNext( &pmdy->rmp_interp ); // 0...PMAX
+
+ if ( RMP_HitEnd( &pmdy->rmp_interp ) )
+ {
+ // done. set delay tap & value = target
+
+ DLY_ChangeVal( pmdy->pdly, pmdy->pdly->tnew );
+
+ pmdy->pdly->t = pmdy->pdly->tnew;
+
+ pmdy->fchanging = false;
+ }
+ }
+
+ // if self-modulating and timer has expired, get next change
+
+ if ( pmdy->mtime && !pmdy->mtimecur-- )
+ {
+ pmdy->mtimecur = pmdy->mtime;
+
+ int D0 = pmdy->pdly->D0;
+ int Dnew;
+ float D1;
+
+ // modulate between 0 and 100% of d0
+
+ D1 = (float)D0 * (1.0 - pmdy->depth);
+
+ Dnew = RandomInt( (int)D1, D0 );
+
+ // set up modulation to new value
+
+ MDY_ChangeVal ( pmdy, Dnew );
+ }
+
+mdy_return:
+
+ // reverse phase of output
+
+ if ( pmdy->bPhaseInvert )
+ xout = -xout;
+
+ // 100% fx mix
+
+ if ( pmdy->mix == PMAX)
+ return xout;
+
+ // special case 50/50 mix
+
+ if ( pmdy->mix == PMAX / 2)
+ return ( (xout + x) >> 1 );
+
+ // return mix of input and processed signal
+
+ return ( x + (((xout - x) * pmdy->mix) >> PBITS) );
+}
+
+
+// batch version for performance
+// UNDONE: unwind MDY_GetNext so that it directly calls DLY_GetNextN:
+// UNDONE: a) if not currently modulating and never self-modulating, then just unwind like DLY_GetNext
+// UNDONE: b) if not currently modulating, figure out how many samples N until self-modulation timer kicks in again
+// and stream out N samples just like DLY_GetNext
+
+inline void MDY_GetNextN( mdy_t *pmdy, portable_samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ portable_samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = MDY_GetNext( pmdy, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = MDY_GetNext( pmdy, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = MDY_GetNext( pmdy, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+
+// parameter order
+
+typedef enum {
+
+ mdy_idtype, // NOTE: first 8 params must match params in dly_e
+ mdy_idelay,
+ mdy_ifeedback,
+ mdy_igain,
+
+ mdy_iftype,
+ mdy_icutoff,
+ mdy_iqwidth,
+ mdy_iquality,
+
+ mdy_imodrate,
+ mdy_imoddepth,
+ mdy_imodglide,
+
+ mdy_imix,
+ mdy_ibxfade,
+
+ mdy_cparam
+
+} mdy_e;
+
+
+// parameter ranges
+
+prm_rng_t mdy_rng[] = {
+
+ {mdy_cparam, 0, 0}, // first entry is # of parameters
+
+ // delay params
+
+ {mdy_idtype, 0, DLY_MAX}, // delay type DLY_PLAIN, DLY_LOWPASS, DLY_ALLPASS
+ {mdy_idelay, 0.0, 1000.0}, // delay in milliseconds
+ {mdy_ifeedback, 0.0, 0.99}, // feedback 0-1.0
+ {mdy_igain, 0.0, 1.0}, // final gain of output stage, 0-1.0
+
+ // filter params if mdy type DLY_LOWPASS
+
+ {mdy_iftype, 0, FTR_MAX},
+ {mdy_icutoff, 10.0, 22050.0},
+ {mdy_iqwidth, 100.0, 11025.0},
+ {mdy_iquality, 0, QUA_MAX},
+
+ {mdy_imodrate, 0.01, 200.0}, // frequency at which delay values change to new random value. 0 is no self-modulation
+ {mdy_imoddepth, 0.0, 1.0}, // how much delay changes (decreases) from current value (0-1.0)
+ {mdy_imodglide, 0.01, 100.0}, // glide time between dcur and dnew in milliseconds
+ {mdy_imix, 0.0, 1.0} // 1.0 = full fx mix, 0.5 = 50% fx, 50% dry
+};
+
+
+// convert user parameters to internal parameters, allocate and return
+
+mdy_t * MDY_Params ( prc_t *pprc )
+{
+ mdy_t *pmdy;
+ dly_t *pdly;
+
+ float ramptime = pprc->prm[mdy_imodglide] / 1000.0; // get ramp time in seconds
+ float modtime = 0.0f;
+ if ( pprc->prm[mdy_imodrate] != 0.0f )
+ {
+ modtime = 1.0 / pprc->prm[mdy_imodrate]; // time between modulations in seconds
+ }
+ float depth = pprc->prm[mdy_imoddepth]; // depth of modulations 0-1.0
+ float mix = pprc->prm[mdy_imix];
+
+ // alloc plain, allpass or lowpass delay
+
+ pdly = DLY_Params( pprc );
+
+ if ( !pdly )
+ return NULL;
+
+ pmdy = MDY_Alloc ( pdly, ramptime, modtime, depth, mix );
+
+ return pmdy;
+}
+
+inline void * MDY_VParams ( void *p )
+{
+ PRC_CheckParams ( (prc_t *)p, mdy_rng );
+ return (void *) MDY_Params ((prc_t *)p);
+}
+
+// v is +/- 0-1.0
+// change current delay value 0..D
+
+void MDY_Mod ( mdy_t *pmdy, float v )
+{
+
+ int D0 = pmdy->pdly->D0; // base delay value
+ float v2;
+
+ // if v is < -2.0 then delay is v + 10.0
+ // invert phase of output. hack.
+
+ if ( v < -2.0 )
+ {
+ v = v + 10.0;
+ pmdy->bPhaseInvert = true;
+ }
+ else
+ {
+ pmdy->bPhaseInvert = false;
+ }
+
+ v2 = -(v + 1.0)/2.0; // v2 varies -1.0-0.0
+
+ // D0 varies 0..D0
+
+ D0 = D0 + (int)((float)D0 * v2);
+
+ // change delay
+
+ MDY_ChangeVal( pmdy, D0 );
+
+ return;
+}
+
+
+///////////////////
+// Parallel reverbs
+///////////////////
+
+// Reverb A
+// M parallel reverbs, mixed to mono output
+
+#define CRVAS 64 // max number of parallel series reverbs active
+
+#define CRVA_DLYS 12 // max number of delays making up reverb_a
+
+struct rva_t
+{
+ bool fused;
+ int m; // number of parallel plain or lowpass delays
+ int fparallel; // true if filters in parallel with delays, otherwise single output filter
+ flt_t *pflt; // series filters
+
+ dly_t *pdlys[CRVA_DLYS]; // array of pointers to delays
+ mdy_t *pmdlys[CRVA_DLYS]; // array of pointers to mod delays
+
+ bool fmoddly; // true if using mod delays
+};
+
+rva_t rvas[CRVAS];
+
+void RVA_Init ( rva_t *prva ) { if ( prva ) Q_memset (prva, 0, sizeof (rva_t)); }
+void RVA_InitAll( void ) { for (int i = 0; i < CRVAS; i++) RVA_Init ( &rvas[i] ); }
+
+// free parallel series reverb
+
+void RVA_Free( rva_t *prva )
+{
+ int i;
+
+ if ( prva )
+ {
+ // free all delays
+ for (i = 0; i < CRVA_DLYS; i++)
+ DLY_Free ( prva->pdlys[i] );
+
+ // zero all ptrs to delays in mdy array
+ for (i = 0; i < CRVA_DLYS; i++)
+ {
+ if ( prva->pmdlys[i] )
+ prva->pmdlys[i]->pdly = NULL;
+ }
+
+ // free all mod delays
+ for (i = 0; i < CRVA_DLYS; i++)
+ MDY_Free ( prva->pmdlys[i] );
+
+ FLT_Free( prva->pflt );
+
+ Q_memset( prva, 0, sizeof (rva_t) );
+ }
+}
+
+
+void RVA_FreeAll( void ) { for (int i = 0; i < CRVAS; i++) RVA_Free( &rvas[i] ); }
+
+// create parallel reverb - m parallel reverbs summed
+
+// D array of CRVB_DLYS reverb delay sizes max sample index w[0...D] (ie: D+1 samples)
+// a array of reverb feedback parms for parallel reverbs (CRVB_P_DLYS)
+// if a[i] < 0 then this is a predelay - use DLY_FLINEAR instead of DLY_LOWPASS
+// b array of CRVB_P_DLYS - mix params for parallel reverbs
+// m - number of parallel delays
+// pflt - filter template, to be used by all parallel delays
+// fparallel - true if filter operates in parallel with delays, otherwise filter output only
+// fmoddly - > 0 if delays are all mod delays (milliseconds of delay modulation)
+// fmodrate - # of delay repetitions between changes to mod delay
+// ftaps - if > 0, use 4 taps per reverb delay unit (increases density) tap = D - n*ftaps n = 0,1,2,3
+
+rva_t * RVA_Alloc ( int *D, int *a, int *b, int m, flt_t *pflt, int fparallel, float fmoddly, float fmodrate, float ftaps )
+{
+
+ int i;
+ int dtype;
+ rva_t *prva;
+ flt_t *pflt2 = NULL;
+
+ bool btaps = ftaps > 0.0;
+
+ // find open slot
+
+ for ( i = 0; i < CRVAS; i++ )
+ {
+ if ( !rvas[i].fused )
+ break;
+ }
+
+ // return null if no free slots
+
+ if (i == CRVAS)
+ {
+ DevMsg ("DSP: Warning, failed to allocate reverb.\n" );
+ return NULL;
+ }
+
+ prva = &rvas[i];
+
+ // if series filter specified, alloc two series filters
+
+ if ( pflt && !fparallel)
+ {
+ // use filter data as template for a filter on output (2 cascaded filters)
+
+ pflt2 = FLT_Alloc (0, pflt->M, pflt->L, pflt->a, pflt->b, 1.0);
+
+ if (!pflt2)
+ {
+ DevMsg ("DSP: Warning, failed to allocate flt for reverb.\n" );
+ return NULL;
+ }
+
+ pflt2->pf1 = FLT_Alloc (0, pflt->M, pflt->L, pflt->a, pflt->b, 1.0);
+ pflt2->N = 1;
+ }
+
+ // allocate parallel delays
+
+ for (i = 0; i < m; i++)
+ {
+ // set delay type
+
+ if ( pflt && fparallel )
+ // if a[i] param is < 0, allocate delay as predelay instead of feedback delay
+ dtype = a[i] < 0 ? DLY_FLINEAR : DLY_LOWPASS;
+ else
+ // if no filter specified, alloc as plain or multitap plain delay
+ dtype = btaps ? DLY_PLAIN_4TAP : DLY_PLAIN;
+
+ if ( dtype == DLY_LOWPASS && btaps )
+ dtype = DLY_LOWPASS_4TAP;
+
+ // if filter specified and parallel specified, alloc 1 filter per delay
+
+ if ( DLY_HAS_FILTER(dtype) )
+ prva->pdlys[i] = DLY_AllocLP( D[i], abs(a[i]), b[i], dtype, pflt->M, pflt->L, pflt->a, pflt->b );
+ else
+ prva->pdlys[i] = DLY_Alloc( D[i], abs(a[i]), b[i], dtype );
+
+ if ( DLY_HAS_MULTITAP(dtype) )
+ {
+ // set up delay taps to increase density around delay value.
+
+ // value of ftaps is the seed for all tap values
+
+ float t1 = max((double)MSEC_TO_SAMPS(5), D[i] * (1.0 - ftaps * 3.141592) );
+ float t2 = max((double)MSEC_TO_SAMPS(7), D[i] * (1.0 - ftaps * 1.697043) );
+ float t3 = max((double)MSEC_TO_SAMPS(10), D[i] * (1.0 - ftaps * 0.96325) );
+
+ DLY_ChangeTaps( prva->pdlys[i], (int)t1, (int)t2, (int)t3, D[i] );
+ }
+ }
+
+
+ if ( fmoddly > 0.0 )
+ {
+ // alloc mod delays, using previously alloc'd delays
+
+ // ramptime is time in seconds for delay to change from dcur to dnew
+ // modtime is time in seconds between modulations. 0 if no self-modulation
+ // depth is 0-1.0 multiplier, new delay values when modulating are Dnew = randomlong (D - D*depth, D)
+
+ float ramptime;
+ float modtime;
+ float depth;
+
+ for (i = 0; i < m; i++)
+ {
+ int Do = prva->pdlys[i]->D;
+
+ modtime = (float)Do / (float)(SOUND_DMA_SPEED); // seconds per delay
+ depth = (fmoddly * 0.001f) / modtime; // convert milliseconds to 'depth' %
+ depth = clamp (depth, 0.01f, 0.99f);
+ modtime = modtime * fmodrate; // modulate every N delay passes
+
+ ramptime = fpmin(20.0f/1000.0f, modtime / 2); // ramp between delay values in N ms
+
+ prva->pmdlys[i] = MDY_Alloc( prva->pdlys[i], ramptime, modtime, depth, 1.0 );
+ }
+
+ prva->fmoddly = true;
+ }
+
+ // if we failed to alloc any reverb, free all, return NULL
+
+ for (i = 0; i < m; i++)
+ {
+ if ( !prva->pdlys[i] )
+ {
+ FLT_Free( pflt2 );
+ RVA_Free( prva );
+ DevMsg ("DSP: Warning, failed to allocate delay for reverb.\n" );
+ return NULL;
+ }
+ }
+
+ prva->fused = true;
+ prva->m = m;
+ prva->fparallel = fparallel;
+ prva->pflt = pflt2;
+ return prva;
+}
+
+
+// parallel reverberator
+//
+// for each input sample x do:
+// x0 = plain(D0,w0,&p0,a0,x)
+// x1 = plain(D1,w1,&p1,a1,x)
+// x2 = plain(D2,w2,&p2,a2,x)
+// x3 = plain(D3,w3,&p3,a3,x)
+// y = b0*x0 + b1*x1 + b2*x2 + b3*x3
+//
+// rgdly - array of M delays:
+// D - Delay values (typical - 29, 37, 44, 50, 27, 31)
+// w - array of delayed values
+// p - array of pointers to circular delay line pointers
+// a - array of M feedback values (typical - all equal, like 0.75 * PMAX)
+// b - array of M gain values for plain reverb outputs (1, .9, .8, .7)
+// xin - input value
+// if fparallel, filters are built into delays,
+// otherwise, filter is in feedback loop
+
+
+int g_MapIntoPBITSDivInt[] =
+{
+ 0, PMAX/1, PMAX/2, PMAX/3, PMAX/4, PMAX/5, PMAX/6, PMAX/7, PMAX/8,
+ PMAX/9, PMAX/10, PMAX/11,PMAX/12,PMAX/13,PMAX/14,PMAX/15,PMAX/16,
+};
+
+inline int RVA_GetNext( rva_t *prva, int x )
+{
+ int m = prva->m;
+ int y = 0;
+
+ if ( prva->fmoddly )
+ {
+ // get output of parallel mod delays
+
+ for (int i = 0; i < m; i++ )
+ y += MDY_GetNext( prva->pmdlys[i], x );
+ }
+ else
+ {
+ // get output of parallel delays
+
+ for (int i = 0; i < m; i++ )
+ y += DLY_GetNext( prva->pdlys[i], x );
+ }
+
+ // PERFORMANCE: y/m is now baked into the 'b' gain params for each delay ( b = b/m )
+ // y = (y * g_MapIntoPBITSDivInt[m]) >> PBITS;
+
+ if ( prva->fparallel )
+ return y;
+
+ // run series filters if present
+
+ if ( prva->pflt )
+ {
+ y = FLT_GetNext( prva->pflt, y);
+ }
+
+ return y;
+}
+
+
+// batch version for performance
+// UNDONE: unwind RVA_GetNextN so that it directly calls DLY_GetNextN or MDY_GetNextN
+
+inline void RVA_GetNextN( rva_t *prva, portable_samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ portable_samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = RVA_GetNext( prva, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = RVA_GetNext( prva, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = RVA_GetNext( prva, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+
+// reverb parameter order
+
+typedef enum
+{
+
+// parameter order
+
+ rva_size_max,
+ rva_size_min,
+
+ rva_inumdelays,
+ rva_ifeedback,
+ rva_igain,
+
+ rva_icutoff,
+
+ rva_ifparallel,
+ rva_imoddly,
+ rva_imodrate,
+
+ rva_width,
+ rva_depth,
+ rva_height,
+
+ rva_fbwidth,
+ rva_fbdepth,
+ rva_fbheight,
+
+ rva_iftaps,
+
+ rva_cparam // # of params
+} rva_e;
+
+// filter parameter ranges
+
+prm_rng_t rva_rng[] = {
+
+ {rva_cparam, 0, 0}, // first entry is # of parameters
+
+ // reverb params
+ {rva_size_max, 0.0, 1000.0}, // max room delay in milliseconds
+ {rva_size_min, 0.0, 1000.0}, // min room delay in milliseconds
+ {rva_inumdelays,1.0, 12.0}, // controls # of parallel or series delays
+ {rva_ifeedback, 0.0, 1.0}, // feedback of delays
+ {rva_igain, 0.0, 10.0}, // output gain
+
+ // filter params for each parallel reverb (quality set to 0 for max execution speed)
+
+ {rva_icutoff, 10, 22050},
+
+ {rva_ifparallel, 0, 1}, // if 1, then all filters operate in parallel with delays. otherwise filter output only
+ {rva_imoddly, 0.0, 50.0}, // if > 0 then all delays are modulating delays, mod param controls milliseconds of mod depth
+ {rva_imodrate, 0.0, 10.0}, // how many delay repetitions pass between mod changes to delayl
+
+ // override params - for more detailed description of room
+ // note: width/depth/height < 0 only for some automatic dsp presets
+ {rva_width, -1000.0, 1000.0}, // 0-1000.0 millisec (room width in feet) - used instead of size if non-zero
+ {rva_depth, -1000.0, 1000.0}, // 0-1000.0 room depth in feet - used instead of size if non-zero
+ {rva_height, -1000.0, 1000.0}, // 0-1000.0 room height in feet - used instead of size if non-zero
+
+ {rva_fbwidth, -1.0, 1.0}, // 0-1.0 material reflectivity - used as feedback param instead of decay if non-zero
+ {rva_fbdepth, -1.0, 1.0}, // 0-1.0 material reflectivity - used as feedback param instead of decay if non-zero
+ {rva_fbheight, -1.0, 1.0}, // 0-1.0 material reflectivity - used as feedback param instead of decay if non-zero
+ // if < 0, a predelay is allocated, then feedback is -1*param given
+
+ {rva_iftaps, 0.0, 0.333} // if > 0, use 3 extra taps with delay values = d * (1 - faps*n) n = 0,1,2,3
+};
+
+#define RVA_BASEM 1 // base number of parallel delays
+
+// nominal delay and feedback values. More delays = more density.
+
+#define RVADLYSMAX 49
+float rvadlys[] = {18, 23, 28, 33, 42, 21, 26, 36, 39, 45, 47, 30};
+float rvafbs[] = {0.9, 0.9, 0.9, 0.85, 0.8, 0.9, 0.9, 0.85, 0.8, 0.8, 0.8, 0.85};
+
+#define SWAP(a,b,t) {(t) = (a); (a) = (b); (b) = (t);}
+
+#define RVA_MIN_SEPARATION 7 // minimum separation between reverbs, in ms.
+
+// Construct D,a,b delay arrays given array of length,width,height sizes and feedback values
+// rgd[] array of delay values in milliseconds (feet)
+// rgf[] array of feedback values 0..1
+// m # of parallel reverbs to construct
+// D[] array of output delay values for parallel reverbs
+// a[] array of output feedback values
+// b[] array of output gain values = 1/m
+// gain - output gain
+// feedback - default feedback if rgf members are 0
+
+void RVA_ConstructDelays( float *rgd, float *rgf, int m, int *D, int *a, int *b, float gain, float feedback )
+{
+
+ int i;
+ float r;
+ int d;
+ float t, d1, d2, dm;
+ bool bpredelay;
+
+ // sort descending, so rgd[0] is largest delay & rgd[2] is smallest
+
+ if (rgd[2] > rgd[1]) { SWAP(rgd[2], rgd[1], t); SWAP(rgf[2], rgf[1], t); }
+ if (rgd[1] > rgd[0]) { SWAP(rgd[0], rgd[1], t); SWAP(rgf[0], rgf[1], t); }
+ if (rgd[2] > rgd[1]) { SWAP(rgd[2], rgd[1], t); SWAP(rgf[2], rgf[1], t); }
+
+ // if all feedback values 0, use default feedback
+
+ if (rgf[0] == 0.0 && rgf[1] == 0.0 && rgf[2] == 0.0 )
+ {
+ // use feedback param for all
+
+ rgf[0] = rgf[1] = rgf[2] = feedback;
+
+ // adjust feedback down for larger delays so that decay is constant for all delays
+
+ rgf[0] = DLY_NormalizeFeedback( rgd[2], rgf[2], rgd[0] );
+ rgf[1] = DLY_NormalizeFeedback( rgd[2], rgf[2], rgd[1] );
+
+ }
+
+ // make sure all reverbs are different by at least RVA_MIN_SEPARATION * m/3 m is 3,6,9 or 12
+
+ int dmin = (m/3) * RVA_MIN_SEPARATION;
+
+ d1 = rgd[1] - rgd[2];
+
+ if (d1 <= dmin)
+ rgd[1] += (dmin-d1); // make difference = dmin
+
+ d2 = rgd[0] - rgd[1];
+
+ if (d2 <= dmin)
+ rgd[0] += (dmin-d1); // make difference = dmin
+
+ for ( i = 0; i < m; i++ )
+ {
+ // reverberations due to room width, depth, height
+ // assume sound moves at approx 1ft/ms
+
+ int j = (int)(fmod ((float)i, 3.0f)); // j counts 0,1,2 0,1,2 0,1..
+
+ d = (int)rgd[j];
+ r = fabs(rgf[j]);
+
+ bpredelay = ((rgf[j] < 0) && i < 3);
+
+ // re-use predelay values as reverb values:
+
+ if (rgf[j] < 0 && !bpredelay)
+ d = max((int)(rgd[j] / 4.0), RVA_MIN_SEPARATION);
+
+ if (i < 3)
+ dm = 0.0;
+ else
+ dm = max( (double)(RVA_MIN_SEPARATION * (i/3)), ((i/3) * ((float)d * 0.18)) );
+
+ d += (int)dm;
+ D[i] = MSEC_TO_SAMPS(d);
+
+ // D[i] = MSEC_TO_SAMPS(d + ((i/3) * RVA_MIN_SEPARATION)); // (i/3) counts 0,0,0 1,1,1 2,2,2 ... separate all reverbs by 5ms
+
+ // feedback - due to wall/floor/ceiling reflectivity
+ a[i] = (int) min (0.999 * PMAX, (double)PMAX * r);
+
+ if (bpredelay)
+ a[i] = -a[i]; // flag delay as predelay
+
+ b[i] = (int)((float)(gain * PMAX) / (float)m);
+ }
+}
+
+void RVA_PerfTest()
+{
+ double time1, time2;
+
+ int i;
+ int k;
+ int j;
+ int m;
+ int a[100];
+
+ time1 = Plat_FloatTime();
+
+ for (m = 0; m < 1000; m++)
+ {
+ for (i = 0, j = 10000; i < 10000; i++, j--)
+ {
+ // j = j % 6;
+ // k = (i * j) >> PBITS;
+
+ k = i / ((j % 6) + 1);
+ }
+ }
+
+ time2 = Plat_FloatTime();
+
+ DevMsg("divide = %2.5f \n", (time2-time1));
+
+
+ for (i=1;i<10;i++)
+ a[i] = PMAX / i;
+
+ time1 = Plat_FloatTime();
+
+ for (m = 0; m < 1000; m++)
+ {
+ for (i = 0, j = 10000; i < 10000; i++, j--)
+ {
+ k = (i * a[(j % 6) + 1] ) >> PBITS;
+ }
+ }
+
+ time2 = Plat_FloatTime();
+
+ DevMsg("shift & multiply = %2.5f \n", (time2-time1));
+}
+
+rva_t * RVA_Params ( prc_t *pprc )
+{
+ rva_t *prva;
+
+ float size_max = pprc->prm[rva_size_max]; // max delay size
+ float size_min = pprc->prm[rva_size_min]; // min delay size
+
+ float numdelays = pprc->prm[rva_inumdelays]; // controls # of parallel delays
+ float feedback = pprc->prm[rva_ifeedback]; // 0-1.0 controls feedback parameters
+ float gain = pprc->prm[rva_igain]; // 0-10.0 controls output gain
+
+ float cutoff = pprc->prm[rva_icutoff]; // filter cutoff
+
+ float fparallel = pprc->prm[rva_ifparallel]; // if true, all filters are in delay feedback paths - otherwise single flt on output
+
+ float fmoddly = pprc->prm[rva_imoddly]; // if > 0, milliseconds of delay mod depth
+ float fmodrate = pprc->prm[rva_imodrate]; // if fmoddly > 0, # of delay repetitions between modulations
+
+ float width = fabs(pprc->prm[rva_width]); // 0-1000 controls size of 1/3 of delays - used instead of size if non-zero
+ float depth = fabs(pprc->prm[rva_depth]); // 0-1000 controls size of 1/3 of delays - used instead of size if non-zero
+ float height = fabs(pprc->prm[rva_height]); // 0-1000 controls size of 1/3 of delays - used instead of size if non-zero
+
+ float fbwidth = pprc->prm[rva_fbwidth]; // feedback parameter for walls 0..2
+ float fbdepth = pprc->prm[rva_fbdepth]; // feedback parameter for floor
+ float fbheight = pprc->prm[rva_fbheight]; // feedback parameter for ceiling
+
+ float ftaps = pprc->prm[rva_iftaps]; // if > 0 increase reverb density using 3 extra taps d = (1.0 - ftaps * n) n = 0,1,2,3
+
+
+
+// RVA_PerfTest();
+
+ // D array of CRVB_DLYS reverb delay sizes max sample index w[0...D] (ie: D+1 samples)
+ // a array of reverb feedback parms for parallel delays
+ // b array of CRVB_P_DLYS - mix params for parallel reverbs
+ // m - number of parallel delays
+
+ int D[CRVA_DLYS];
+ int a[CRVA_DLYS];
+ int b[CRVA_DLYS];
+ int m;
+
+ // limit # delays 1-12
+
+ m = clamp (numdelays, (float)RVA_BASEM, (float)CRVA_DLYS);
+
+ // set up D (delay) a (feedback) b (gain) arrays
+
+ if ( int(width) || int(height) || int(depth) )
+ {
+ // if width, height, depth given, use values as simple delays
+
+ float rgd[3];
+ float rgfb[3];
+
+ // force m to 3, 6, 9 or 12
+
+ if (m < 3) m = 3;
+ if (m > 3 && m < 6) m = 6;
+ if (m > 6 && m < 9) m = 9;
+ if (m > 9) m = 12;
+
+ rgd[0] = width; rgfb[0] = fbwidth;
+ rgd[1] = depth; rgfb[1] = fbdepth;
+ rgd[2] = height; rgfb[2] = fbheight;
+
+ RVA_ConstructDelays( rgd, rgfb, m, D, a, b, gain, feedback );
+ }
+ else
+ {
+ // use size parameter instead of width/depth/height
+
+ for ( int i = 0; i < m; i++ )
+ {
+ // delays of parallel reverb. D[0] = size_min.
+
+ D[i] = MSEC_TO_SAMPS( size_min + (int)( ((float)(size_max - size_min) / (float)m) * (float)i) );
+
+ // feedback and gain of parallel reverb
+
+ if (i == 0)
+ {
+ // set feedback for smallest delay
+
+ a[i] = (int) min (0.999 * PMAX, (double)PMAX * feedback );
+ }
+ else
+ {
+ // adjust feedback down for larger delays so that decay time is constant
+
+ a[i] = (int) min (0.999 * PMAX, (double)PMAX * DLY_NormalizeFeedback( D[0], feedback, D[i] ) );
+ }
+
+ b[i] = (int) ((float)(gain * PMAX) / (float)m);
+ }
+ }
+
+ // add filter
+
+ flt_t *pflt = NULL;
+
+ if ( cutoff )
+ {
+
+ // set up dummy lowpass filter to convert params
+
+ prc_t prcf;
+
+ prcf.prm[flt_iquality] = QUA_LO; // force filter to low quality for faster execution time
+ prcf.prm[flt_icutoff] = cutoff;
+ prcf.prm[flt_iftype] = FLT_LP;
+ prcf.prm[flt_iqwidth] = 0;
+ prcf.prm[flt_igain] = 1.0;
+
+ pflt = (flt_t *)FLT_Params ( &prcf );
+ }
+
+ prva = RVA_Alloc ( D, a, b, m, pflt, fparallel, fmoddly, fmodrate, ftaps );
+
+ FLT_Free( pflt );
+
+ return prva;
+}
+
+
+inline void * RVA_VParams ( void *p )
+{
+ PRC_CheckParams ( (prc_t *)p, rva_rng );
+ return (void *) RVA_Params ((prc_t *)p);
+}
+
+inline void RVA_Mod ( void *p, float v ) { return; }
+
+
+
+////////////
+// Diffusor
+///////////
+
+// (N series allpass reverbs)
+
+#define CDFRS 64 // max number of series reverbs active
+
+#define CDFR_DLYS 16 // max number of delays making up diffusor
+
+struct dfr_t
+{
+ bool fused;
+ int n; // series allpass delays
+ int w[CDFR_DLYS]; // internal state array for series allpass filters
+
+ dly_t *pdlys[CDFR_DLYS]; // array of pointers to delays
+};
+
+dfr_t dfrs[CDFRS];
+
+void DFR_Init ( dfr_t *pdfr ) { if ( pdfr ) Q_memset (pdfr, 0, sizeof (dfr_t)); }
+void DFR_InitAll( void ) { for (int i = 0; i < CDFRS; i++) DFR_Init ( &dfrs[i] ); }
+
+// free parallel series reverb
+
+void DFR_Free( dfr_t *pdfr )
+{
+ if ( pdfr )
+ {
+ // free all delays
+
+ for (int i = 0; i < CDFR_DLYS; i++)
+ DLY_Free ( pdfr->pdlys[i] );
+
+ Q_memset( pdfr, 0, sizeof (dfr_t) );
+ }
+}
+
+
+void DFR_FreeAll( void ) { for (int i = 0; i < CDFRS; i++) DFR_Free( &dfrs[i] ); }
+
+// create n series allpass reverbs
+
+// D array of CRVB_DLYS reverb delay sizes max sample index w[0...D] (ie: D+1 samples)
+// a array of reverb feedback parms for series delays
+// b array of gain params for parallel reverbs
+// n - number of series delays
+
+dfr_t * DFR_Alloc ( int *D, int *a, int *b, int n )
+{
+
+ int i;
+ dfr_t *pdfr;
+
+ // find open slot
+
+ for (i = 0; i < CDFRS; i++)
+ {
+ if (!dfrs[i].fused)
+ break;
+ }
+
+ // return null if no free slots
+
+ if (i == CDFRS)
+ {
+ DevMsg ("DSP: Warning, failed to allocate diffusor.\n" );
+ return NULL;
+ }
+
+ pdfr = &dfrs[i];
+
+ DFR_Init( pdfr );
+
+ // alloc reverbs
+
+ for (i = 0; i < n; i++)
+ pdfr->pdlys[i] = DLY_Alloc( D[i], a[i], b[i], DLY_ALLPASS );
+
+ // if we failed to alloc any reverb, free all, return NULL
+
+ for (i = 0; i < n; i++)
+ {
+ if ( !pdfr->pdlys[i])
+ {
+ DFR_Free( pdfr );
+ DevMsg ("DSP: Warning, failed to allocate delay for diffusor.\n" );
+ return NULL;
+ }
+ }
+
+ pdfr->fused = true;
+ pdfr->n = n;
+
+ return pdfr;
+}
+
+
+// series reverberator
+
+inline int DFR_GetNext( dfr_t *pdfr, int x )
+{
+ int i;
+ int y;
+ dly_t *pdly;
+
+ y = x;
+
+ for (i = 0; i < pdfr->n; i++)
+ {
+ pdly = pdfr->pdlys[i];
+ y = DelayAllpass( pdly->D, pdly->t, pdly->w, &pdly->p, pdly->a, pdly->b, y );
+ }
+
+ return y;
+}
+
+// batch version for performance
+
+inline void DFR_GetNextN( dfr_t *pdfr, portable_samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ portable_samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = DFR_GetNext( pdfr, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = DFR_GetNext( pdfr, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = DFR_GetNext( pdfr, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+
+#define DFR_BASEN 1 // base number of series allpass delays
+
+// nominal diffusor delay and feedback values
+
+float dfrdlys[] = {13, 19, 26, 21, 32, 36, 38, 16, 24, 28, 41, 35, 10, 46, 50, 27};
+float dfrfbs[] = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0};
+
+
+// diffusor parameter order
+
+typedef enum
+{
+
+// parameter order
+
+ dfr_isize,
+ dfr_inumdelays,
+ dfr_ifeedback,
+ dfr_igain,
+
+ dfr_cparam // # of params
+
+} dfr_e;
+
+// diffusor parameter ranges
+
+prm_rng_t dfr_rng[] = {
+
+ {dfr_cparam, 0, 0}, // first entry is # of parameters
+
+ {dfr_isize, 0.0, 1.0}, // 0-1.0 scales all delays
+ {dfr_inumdelays,0.0, 4.0}, // 0-4.0 controls # of series delays
+ {dfr_ifeedback, 0.0, 1.0}, // 0-1.0 scales all feedback parameters
+ {dfr_igain, 0.0, 10.0}, // 0-1.0 scales all feedback parameters
+};
+
+
+dfr_t * DFR_Params ( prc_t *pprc )
+{
+ dfr_t *pdfr;
+ int i;
+ int s;
+ float size = pprc->prm[dfr_isize]; // 0-1.0 scales all delays
+ float numdelays = pprc->prm[dfr_inumdelays]; // 0-4.0 controls # of series delays
+ float feedback = pprc->prm[dfr_ifeedback]; // 0-1.0 scales all feedback parameters
+ float gain = pprc->prm[dfr_igain]; // 0-10.0 controls output gain
+
+ // D array of CRVB_DLYS reverb delay sizes max sample index w[0...D] (ie: D+1 samples)
+ // a array of reverb feedback parms for series delays (CRVB_S_DLYS)
+ // b gain of each reverb section
+ // n - number of series delays
+
+ int D[CDFR_DLYS];
+ int a[CDFR_DLYS];
+ int b[CDFR_DLYS];
+ int n;
+
+ if (gain == 0.0)
+ gain = 1.0;
+
+ // get # series diffusors
+
+ // limit m, n to half max number of delays
+
+ n = clamp (Float2Int(numdelays), DFR_BASEN, CDFR_DLYS/2);
+
+ // compute delays for diffusors
+
+ for (i = 0; i < n; i++)
+ {
+ s = (int)( dfrdlys[i] * size );
+
+ // delay of diffusor
+
+ D[i] = MSEC_TO_SAMPS(s);
+
+ // feedback and gain of diffusor
+
+ a[i] = min (0.999 * PMAX, (double)(dfrfbs[i] * PMAX * feedback));
+ b[i] = (int) ( (float)(gain * (float)PMAX) );
+ }
+
+
+ pdfr = DFR_Alloc ( D, a, b, n );
+
+ return pdfr;
+}
+
+inline void * DFR_VParams ( void *p )
+{
+ PRC_CheckParams ((prc_t *)p, dfr_rng);
+ return (void *) DFR_Params ((prc_t *)p);
+}
+
+inline void DFR_Mod ( void *p, float v ) { return; }
+
+
+//////////////////////
+// LFO wav definitions
+//////////////////////
+
+#define CLFOSAMPS 512 // samples per wav table - single cycle only
+#define LFOBITS 14 // bits of peak amplitude of lfo wav
+#define LFOAMP ((1<<LFOBITS)-1) // peak amplitude of lfo wav
+
+//types of lfo wavs
+
+#define LFO_SIN 0 // sine wav
+#define LFO_TRI 1 // triangle wav
+#define LFO_SQR 2 // square wave, 50% duty cycle
+#define LFO_SAW 3 // forward saw wav
+#define LFO_RND 4 // random wav
+#define LFO_LOG_IN 5 // logarithmic fade in
+#define LFO_LOG_OUT 6 // logarithmic fade out
+#define LFO_LIN_IN 7 // linear fade in
+#define LFO_LIN_OUT 8 // linear fade out
+#define LFO_MAX LFO_LIN_OUT
+
+#define CLFOWAV 9 // number of LFO wav tables
+
+struct lfowav_t // lfo or envelope wave table
+{
+ int type; // lfo type
+ dly_t *pdly; // delay holds wav values and step pointers
+};
+
+lfowav_t lfowavs[CLFOWAV];
+
+// deallocate lfo wave table. Called only when sound engine exits.
+
+void LFOWAV_Free( lfowav_t *plw )
+{
+ // free delay
+
+ if ( plw )
+ DLY_Free( plw->pdly );
+
+ Q_memset( plw, 0, sizeof (lfowav_t) );
+}
+
+// deallocate all lfo wave tables. Called only when sound engine exits.
+
+void LFOWAV_FreeAll( void )
+{
+ for ( int i = 0; i < CLFOWAV; i++ )
+ LFOWAV_Free( &lfowavs[i] );
+}
+
+// fill lfo array w with count samples of lfo type 'type'
+// all lfo wavs except fade out, rnd, and log_out should start with 0 output
+
+void LFOWAV_Fill( int *w, int count, int type )
+{
+ int i,x;
+ switch (type)
+ {
+ default:
+ case LFO_SIN: // sine wav, all values 0 <= x <= LFOAMP, initial value = 0
+ for (i = 0; i < count; i++ )
+ {
+ x = ( int )( (float)(LFOAMP) * sinf( (2.0 * M_PI_F * (float)i / (float)count ) + (M_PI_F * 1.5) ) );
+ w[i] = (x + LFOAMP)/2;
+ }
+ break;
+ case LFO_TRI: // triangle wav, all values 0 <= x <= LFOAMP, initial value = 0
+ for (i = 0; i < count; i++)
+ {
+ w[i] = ( int ) ( (float)(2 * LFOAMP * i ) / (float)(count) );
+
+ if ( i > count / 2 )
+ w[i] = ( int ) ( (float) (2 * LFOAMP) - (float)( 2 * LFOAMP * i ) / (float)(count) );
+ }
+ break;
+ case LFO_SQR: // square wave, 50% duty cycle, all values 0 <= x <= LFOAMP, initial value = 0
+ for (i = 0; i < count; i++)
+ w[i] = i > count / 2 ? 0 : LFOAMP;
+ break;
+ case LFO_SAW: // forward saw wav, aall values 0 <= x <= LFOAMP, initial value = 0
+ for (i = 0; i < count; i++)
+ w[i] = ( int ) ( (float)(LFOAMP) * (float)i / (float)(count) );
+ break;
+ case LFO_RND: // random wav, all values 0 <= x <= LFOAMP
+ for (i = 0; i < count; i++)
+ w[i] = ( int ) ( RandomInt(0, LFOAMP) );
+ break;
+ case LFO_LOG_IN: // logarithmic fade in, all values 0 <= x <= LFOAMP, initial value = 0
+ for (i = 0; i < count; i++)
+ w[i] = ( int ) ( (float)(LFOAMP) * powf( (float)i / (float)count, 2));
+ break;
+ case LFO_LOG_OUT: // logarithmic fade out, all values 0 <= x <= LFOAMP, initial value = LFOAMP
+ for (i = 0; i < count; i++)
+ w[i] = ( int ) ( (float)(LFOAMP) * powf( 1.0 - ((float)i / (float)count), 2 ));
+ break;
+ case LFO_LIN_IN: // linear fade in, all values 0 <= x <= LFOAMP, initial value = 0
+ for (i = 0; i < count; i++)
+ w[i] = ( int ) ( (float)(LFOAMP) * (float)i / (float)(count) );
+ break;
+ case LFO_LIN_OUT: // linear fade out, all values 0 <= x <= LFOAMP, initial value = LFOAMP
+ for (i = 0; i < count; i++)
+ w[i] = LFOAMP - ( int ) ( (float)(LFOAMP) * (float)i / (float)(count) );
+ break;
+ }
+}
+
+// allocate all lfo wave tables. Called only when sound engine loads.
+
+void LFOWAV_InitAll()
+{
+ int i;
+ dly_t *pdly;
+
+ Q_memset( lfowavs, 0, sizeof( lfowavs ) );
+
+ // alloc space for each lfo wav type
+
+ for (i = 0; i < CLFOWAV; i++)
+ {
+ pdly = DLY_Alloc( CLFOSAMPS, 0, 0 , DLY_PLAIN);
+
+ lfowavs[i].pdly = pdly;
+ lfowavs[i].type = i;
+
+ LFOWAV_Fill( pdly->w, CLFOSAMPS, i );
+ }
+
+ // if any dlys fail to alloc, free all
+
+ for (i = 0; i < CLFOWAV; i++)
+ {
+ if ( !lfowavs[i].pdly )
+ LFOWAV_FreeAll();
+ }
+}
+
+
+////////////////////////////////////////
+// LFO iterators - one shot and looping
+////////////////////////////////////////
+
+#define CLFO 16 // max active lfos (this steals from active delays)
+
+struct lfo_t
+{
+ bool fused; // true if slot take
+
+ dly_t *pdly; // delay points to lfo wav within lfowav_t (don't free this)
+
+ int gain;
+
+ float f; // playback frequency in hz
+
+ pos_t pos; // current position within wav table, looping
+ pos_one_t pos1; // current position within wav table, one shot
+
+ int foneshot; // true - one shot only, don't repeat
+};
+
+lfo_t lfos[CLFO];
+
+void LFO_Init( lfo_t *plfo ) { if ( plfo ) Q_memset( plfo, 0, sizeof (lfo_t) ); }
+void LFO_InitAll( void ) { for (int i = 0; i < CLFO; i++) LFO_Init(&lfos[i]); }
+void LFO_Free( lfo_t *plfo ) { if ( plfo ) Q_memset( plfo, 0, sizeof (lfo_t) ); }
+void LFO_FreeAll( void ) { for (int i = 0; i < CLFO; i++) LFO_Free(&lfos[i]); }
+
+
+// get step value given desired playback frequency
+
+inline float LFO_HzToStep ( float freqHz )
+{
+ float lfoHz;
+
+ // calculate integer and fractional step values,
+ // assume an update rate of SOUND_DMA_SPEED samples/sec
+
+ // 1 cycle/CLFOSAMPS * SOUND_DMA_SPEED samps/sec = cycles/sec = current lfo rate
+ //
+ // lforate * X = freqHz so X = freqHz/lforate = update rate
+
+ lfoHz = (float)(SOUND_DMA_SPEED) / (float)(CLFOSAMPS);
+
+ return freqHz / lfoHz;
+}
+
+// return pointer to new lfo
+
+lfo_t * LFO_Alloc( int wtype, float freqHz, bool foneshot, float gain )
+{
+ int i;
+ int type = min ( CLFOWAV - 1, wtype );
+ float lfostep;
+
+ for (i = 0; i < CLFO; i++)
+ if (!lfos[i].fused)
+ {
+ lfo_t *plfo = &lfos[i];
+
+ LFO_Init( plfo );
+
+ plfo->fused = true;
+ plfo->pdly = lfowavs[type].pdly; // pdly in lfo points to wav table data in lfowavs
+ plfo->f = freqHz;
+ plfo->foneshot = foneshot;
+ plfo->gain = gain * PMAX;
+
+ lfostep = LFO_HzToStep( freqHz );
+
+ // init positional pointer (ie: fixed point updater for controlling pitch of lfo)
+
+ if ( !foneshot )
+ POS_Init(&(plfo->pos), plfo->pdly->D, lfostep );
+ else
+ POS_ONE_Init(&(plfo->pos1), plfo->pdly->D,lfostep );
+
+ return plfo;
+ }
+ DevMsg ("DSP: Warning, failed to allocate LFO.\n" );
+ return NULL;
+}
+
+// get next lfo value
+// Value returned is 0..LFOAMP. can be normalized by shifting right by LFOBITS
+// To play back at correct passed in frequency, routien should be
+// called once for every output sample (ie: at SOUND_DMA_SPEED)
+// x is dummy param
+
+inline int LFO_GetNext( lfo_t *plfo, int x )
+{
+ int i;
+
+ // get current position
+
+ if ( !plfo->foneshot )
+ i = POS_GetNext( &plfo->pos );
+ else
+ i = POS_ONE_GetNext( &plfo->pos1 );
+
+ // return current sample
+
+ if (plfo->gain == PMAX)
+ return plfo->pdly->w[i];
+ else
+ return (plfo->pdly->w[i] * plfo->gain ) >> PBITS;
+}
+
+// batch version for performance
+
+inline void LFO_GetNextN( lfo_t *plfo, portable_samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ portable_samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = LFO_GetNext( plfo, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = LFO_GetNext( plfo, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = LFO_GetNext( plfo, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+
+// uses lfowav, rate, foneshot
+
+typedef enum
+{
+
+// parameter order
+
+ lfo_iwav,
+ lfo_irate,
+ lfo_ifoneshot,
+ lfo_igain,
+
+ lfo_cparam // # of params
+
+} lfo_e;
+
+// parameter ranges
+
+prm_rng_t lfo_rng[] = {
+
+ {lfo_cparam, 0, 0}, // first entry is # of parameters
+
+ {lfo_iwav, 0.0, LFO_MAX}, // lfo type to use (LFO_SIN, LFO_RND...)
+ {lfo_irate, 0.0, 16000.0}, // modulation rate in hz. for MDY, 1/rate = 'glide' time in seconds
+ {lfo_ifoneshot, 0.0, 1.0}, // 1.0 if lfo is oneshot
+ {lfo_igain, 0.0, 10.0}, // output gain
+};
+
+
+lfo_t * LFO_Params ( prc_t *pprc )
+{
+ lfo_t *plfo;
+ bool foneshot = pprc->prm[lfo_ifoneshot] > 0 ? true : false;
+ float gain = pprc->prm[lfo_igain];
+
+ plfo = LFO_Alloc ( pprc->prm[lfo_iwav], pprc->prm[lfo_irate], foneshot, gain );
+
+ return plfo;
+}
+
+void LFO_ChangeVal ( lfo_t *plfo, float fhz )
+{
+ float fstep = LFO_HzToStep( fhz );
+
+ // change lfo playback rate to new frequency fhz
+
+ if ( plfo->foneshot )
+ POS_ChangeVal( &plfo->pos, fstep );
+ else
+ POS_ChangeVal( &plfo->pos1.p, fstep );
+}
+
+inline void * LFO_VParams ( void *p )
+{
+ PRC_CheckParams ( (prc_t *)p, lfo_rng );
+ return (void *) LFO_Params ((prc_t *)p);
+}
+
+// v is +/- 0-1.0
+// v changes current lfo frequency up/down by +/- v%
+
+inline void LFO_Mod ( lfo_t *plfo, float v )
+{
+ float fhz;
+ float fhznew;
+
+ fhz = plfo->f;
+ fhznew = fhz * (1.0 + v);
+
+ LFO_ChangeVal ( plfo, fhznew );
+
+ return;
+}
+
+
+////////////////////////////////////////
+// Time Compress/expand with pitch shift
+////////////////////////////////////////
+
+// realtime pitch shift - ie: pitch shift without change to playback rate
+
+#define CPTCS 64
+
+struct ptc_t
+{
+ bool fused;
+
+ dly_t *pdly_in; // input buffer space
+ dly_t *pdly_out; // output buffer space
+
+ int *pin; // input buffer (pdly_in->w)
+ int *pout; // output buffer (pdly_out->w)
+
+ int cin; // # samples in input buffer
+ int cout; // # samples in output buffer
+
+ int cxfade; // # samples in crossfade segment
+ int ccut; // # samples to cut
+ int cduplicate; // # samples to duplicate (redundant - same as ccut)
+
+ int iin; // current index into input buffer (reading)
+
+ pos_one_t psn; // stepping index through output buffer
+
+ bool fdup; // true if duplicating, false if cutting
+
+ float fstep; // pitch shift & time compress/expand
+};
+
+ptc_t ptcs[CPTCS];
+
+void PTC_Init( ptc_t *pptc ) { if (pptc) Q_memset( pptc, 0, sizeof (ptc_t) ); };
+void PTC_Free( ptc_t *pptc )
+{
+ if (pptc)
+ {
+ DLY_Free (pptc->pdly_in);
+ DLY_Free (pptc->pdly_out);
+
+ Q_memset( pptc, 0, sizeof (ptc_t) );
+ }
+};
+void PTC_InitAll() { for (int i = 0; i < CPTCS; i++) PTC_Init( &ptcs[i] ); };
+void PTC_FreeAll() { for (int i = 0; i < CPTCS; i++) PTC_Free( &ptcs[i] ); };
+
+
+
+// Time compressor/expander with pitch shift (ie: pitch changes, playback rate does not)
+//
+// Algorithm:
+
+// 1) Duplicate or discard chunks of sound to provide tslice * fstep seconds of sound.
+// (The user-selectable size of the buffer to process is tslice milliseconds in length)
+// 2) Resample this compressed/expanded buffer at fstep to produce a pitch shifted
+// output with the same duration as the input (ie: #samples out = # samples in, an
+// obvious requirement for realtime inline processing).
+
+// timeslice is size in milliseconds of full buffer to process.
+// timeslice * fstep is the size of the expanded/compressed buffer
+// timexfade is length in milliseconds of crossfade region between duplicated or cut sections
+// fstep is % expanded/compressed sound normalized to 0.01-2.0 (1% - 200%)
+
+// input buffer:
+
+// iin-->
+
+// [0... tslice ...D] input samples 0...D (D is NEWEST sample)
+// [0... ...n][m... tseg ...D] region to be cut or duplicated m...D
+
+// [0... [p..txf1..n][m... tseg ...D] fade in region 1 txf1 p...n
+// [0... ...n][m..[q..txf2..D] fade out region 2 txf2 q...D
+
+
+// pitch up: duplicate into output buffer: tdup = tseg
+
+// [0... ...n][m... tdup ...D][m... tdup ...D] output buffer size with duplicate region
+// [0... ...n][m..[p...xf1..n][m... tdup ...D] fade in p...n while fading out q...D
+// [0... ...n][m..[q...xf2..D][m... tdup ...D]
+// [0... ...n][m..[.XFADE...n][m... tdup ...D] final duplicated output buffer - resample at fstep
+
+// pitch down: cut into output buffer: tcut = tseg
+
+// [0... ...n][m... tcut ...D] input samples with cut region delineated m...D
+// [0... ...n] output buffer size after cut
+// [0... [q..txf2...D] fade in txf1 q...D while fade out txf2 p...n
+// [0... [.XFADE ...D] final cut output buffer - resample at fstep
+
+
+ptc_t * PTC_Alloc( float timeslice, float timexfade, float fstep )
+{
+
+ int i;
+ ptc_t *pptc;
+ float tout;
+ int cin, cout;
+ float tslice = timeslice;
+ float txfade = timexfade;
+ float tcutdup;
+
+ // find time compressor slot
+
+ for ( i = 0; i < CPTCS; i++ )
+ {
+ if ( !ptcs[i].fused )
+ break;
+ }
+
+ if ( i == CPTCS )
+ {
+ DevMsg ("DSP: Warning, failed to allocate pitch shifter.\n" );
+ return NULL;
+ }
+
+ pptc = &ptcs[i];
+
+ PTC_Init ( pptc );
+
+ // get size of region to cut or duplicate
+
+ tcutdup = abs((fstep - 1.0) * timeslice);
+
+ // to prevent buffer overruns:
+
+ // make sure timeslice is greater than cut/dup time
+
+ tslice = max ( (double)tslice, 1.1 * tcutdup);
+
+ // make sure xfade time smaller than cut/dup time, and smaller than (timeslice-cutdup) time
+
+ txfade = min ( (double)txfade, 0.9 * tcutdup );
+ txfade = min ( (double)txfade, 0.9 * (tslice - tcutdup));
+
+ pptc->cxfade = MSEC_TO_SAMPS( txfade );
+ pptc->ccut = MSEC_TO_SAMPS( tcutdup );
+ pptc->cduplicate = MSEC_TO_SAMPS( tcutdup );
+
+ // alloc delay lines (buffers)
+
+ tout = tslice * fstep;
+
+ cin = MSEC_TO_SAMPS( tslice );
+ cout = MSEC_TO_SAMPS( tout );
+
+ pptc->pdly_in = DLY_Alloc( cin, 0, 1, DLY_LINEAR ); // alloc input buffer
+ pptc->pdly_out = DLY_Alloc( cout, 0, 1, DLY_LINEAR); // alloc output buffer
+
+ if ( !pptc->pdly_in || !pptc->pdly_out )
+ {
+ PTC_Free( pptc );
+ DevMsg ("DSP: Warning, failed to allocate delay for pitch shifter.\n" );
+ return NULL;
+ }
+
+ // buffer pointers
+
+ pptc->pin = pptc->pdly_in->w;
+ pptc->pout = pptc->pdly_out->w;
+
+ // input buffer index
+
+ pptc->iin = 0;
+
+ // output buffer index
+
+ POS_ONE_Init ( &pptc->psn, cout, fstep );
+
+ // if fstep > 1.0 we're pitching shifting up, so fdup = true
+
+ pptc->fdup = fstep > 1.0 ? true : false;
+
+ pptc->cin = cin;
+ pptc->cout = cout;
+
+ pptc->fstep = fstep;
+ pptc->fused = true;
+
+ return pptc;
+}
+
+// linear crossfader
+// yfadein - instantaneous value fading in
+// ydafeout -instantaneous value fading out
+// nsamples - duration in #samples of fade
+// isample - index in to fade 0...nsamples-1
+
+inline int xfade ( int yfadein, int yfadeout, int nsamples, int isample )
+{
+ int yout;
+ int m = (isample << PBITS ) / nsamples;
+
+// yout = ((yfadein * m) >> PBITS) + ((yfadeout * (PMAX - m)) >> PBITS);
+ yout = (yfadeout + (yfadein - yfadeout) * m ) >> PBITS;
+
+ return yout;
+}
+
+// w - pointer to start of input buffer samples
+// v - pointer to start of output buffer samples
+// cin - # of input buffer samples
+// cout = # of output buffer samples
+// cxfade = # of crossfade samples
+// cduplicate = # of samples in duplicate/cut segment
+
+void TimeExpand( int *w, int *v, int cin, int cout, int cxfade, int cduplicate )
+{
+ int i,j;
+ int m;
+ int p;
+ int q;
+ int D;
+
+ // input buffer
+ // xfade source duplicate
+ // [0...........][p.......n][m...........D]
+
+ // output buffer
+ // xfade region duplicate
+ // [0.....................n][m..[q.......D][m...........D]
+
+ // D - index of last sample in input buffer
+ // m - index of 1st sample in duplication region
+ // p - index of 1st sample of crossfade source
+ // q - index of 1st sample in crossfade region
+
+ D = cin - 1;
+ m = cin - cduplicate;
+ p = m - cxfade;
+ q = cin - cxfade;
+
+ // copy up to crossfade region
+
+ for (i = 0; i < q; i++)
+ v[i] = w[i];
+
+ // crossfade region
+
+ j = p;
+
+ for (i = q; i <= D; i++)
+ v[i] = xfade (w[j++], w[i], cxfade, i-q); // fade out p..n, fade in q..D
+
+ // duplicate region
+
+ j = D+1;
+
+ for (i = m; i <= D; i++)
+ v[j++] = w[i];
+
+}
+
+// cut ccut samples from end of input buffer, crossfade end of cut section
+// with end of remaining section
+
+// w - pointer to start of input buffer samples
+// v - pointer to start of output buffer samples
+// cin - # of input buffer samples
+// cout = # of output buffer samples
+// cxfade = # of crossfade samples
+// ccut = # of samples in cut segment
+
+void TimeCompress( int *w, int *v, int cin, int cout, int cxfade, int ccut )
+{
+ int i,j;
+ int m;
+ int p;
+ int q;
+ int D;
+
+ // input buffer
+ // xfade source
+ // [0.....................n][m..[p.......D]
+
+ // xfade region cut
+ // [0...........][q.......n][m...........D]
+
+ // output buffer
+ // xfade to source
+ // [0...........][p.......D]
+
+ // D - index of last sample in input buffer
+ // m - index of 1st sample in cut region
+ // p - index of 1st sample of crossfade source
+ // q - index of 1st sample in crossfade region
+
+ D = cin - 1;
+ m = cin - ccut;
+ p = cin - cxfade;
+ q = m - cxfade;
+
+ // copy up to crossfade region
+
+ for (i = 0; i < q; i++)
+ v[i] = w[i];
+
+ // crossfade region
+
+ j = p;
+
+ for (i = q; i < m; i++)
+ v[i] = xfade (w[j++], w[i], cxfade, i-q); // fade out p..n, fade in q..D
+
+ // skip rest of input buffer
+}
+
+// get next sample
+
+// put input sample into input (delay) buffer
+// get output sample from output buffer, step by fstep %
+// output buffer is time expanded or compressed version of previous input buffer
+
+inline int PTC_GetNext( ptc_t *pptc, int x )
+{
+ int iout, xout;
+ bool fhitend = false;
+
+ // write x into input buffer
+ Assert (pptc->iin < pptc->cin);
+
+ pptc->pin[pptc->iin] = x;
+
+ pptc->iin++;
+
+ // check for end of input buffer
+
+ if ( pptc->iin >= pptc->cin )
+ fhitend = true;
+
+ // read sample from output buffer, resampling at fstep
+
+ iout = POS_ONE_GetNext( &pptc->psn );
+ Assert (iout < pptc->cout);
+ xout = pptc->pout[iout];
+
+ if ( fhitend )
+ {
+ // if hit end of input buffer (ie: input buffer is full)
+ // reset input buffer pointer
+ // reset output buffer pointer
+ // rebuild entire output buffer (TimeCompress/TimeExpand)
+
+ pptc->iin = 0;
+
+ POS_ONE_Init( &pptc->psn, pptc->cout, pptc->fstep );
+
+ if ( pptc->fdup )
+ TimeExpand ( pptc->pin, pptc->pout, pptc->cin, pptc->cout, pptc->cxfade, pptc->cduplicate );
+ else
+ TimeCompress ( pptc->pin, pptc->pout, pptc->cin, pptc->cout, pptc->cxfade, pptc->ccut );
+ }
+
+ return xout;
+}
+
+// batch version for performance
+
+inline void PTC_GetNextN( ptc_t *pptc, portable_samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ portable_samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = PTC_GetNext( pptc, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = PTC_GetNext( pptc, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = PTC_GetNext( pptc, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+
+// change time compression to new value
+// fstep is new value
+// ramptime is how long change takes in seconds (ramps smoothly), 0 for no ramp
+
+void PTC_ChangeVal( ptc_t *pptc, float fstep, float ramptime )
+{
+// UNDONE: ignored
+// UNDONE: just realloc time compressor with new fstep
+}
+
+// uses pitch:
+// 1.0 = playback normal rate
+// 0.5 = cut 50% of sound (2x playback)
+// 1.5 = add 50% sound (0.5x playback)
+
+typedef enum
+{
+
+// parameter order
+
+ ptc_ipitch,
+ ptc_itimeslice,
+ ptc_ixfade,
+
+ ptc_cparam // # of params
+
+} ptc_e;
+
+// diffusor parameter ranges
+
+prm_rng_t ptc_rng[] = {
+
+ {ptc_cparam, 0, 0}, // first entry is # of parameters
+
+ {ptc_ipitch, 0.1, 4.0}, // 0-n.0 where 1.0 = 1 octave up and 0.5 is one octave down
+ {ptc_itimeslice, 20.0, 300.0}, // in milliseconds - size of sound chunk to analyze and cut/duplicate - 100ms nominal
+ {ptc_ixfade, 1.0, 200.0}, // in milliseconds - size of crossfade region between spliced chunks - 20ms nominal
+};
+
+
+ptc_t * PTC_Params ( prc_t *pprc )
+{
+ ptc_t *pptc;
+
+ float pitch = pprc->prm[ptc_ipitch];
+ float timeslice = pprc->prm[ptc_itimeslice];
+ float txfade = pprc->prm[ptc_ixfade];
+
+ pptc = PTC_Alloc( timeslice, txfade, pitch );
+
+ return pptc;
+}
+
+inline void * PTC_VParams ( void *p )
+{
+ PRC_CheckParams ( (prc_t *)p, ptc_rng );
+ return (void *) PTC_Params ((prc_t *)p);
+}
+
+// change to new pitch value
+// v is +/- 0-1.0
+// v changes current pitch up/down by +/- v%
+
+void PTC_Mod ( ptc_t *pptc, float v )
+{
+ float fstep;
+ float fstepnew;
+
+ fstep = pptc->fstep;
+ fstepnew = fstep * (1.0 + v);
+
+ PTC_ChangeVal( pptc, fstepnew, 0.01 );
+}
+
+
+////////////////////
+// ADSR envelope
+////////////////////
+
+#define CENVS 64 // max # of envelopes active
+#define CENVRMPS 4 // A, D, S, R
+
+#define ENV_LIN 0 // linear a,d,s,r
+#define ENV_EXP 1 // exponential a,d,s,r
+#define ENV_MAX ENV_EXP
+
+#define ENV_BITS 14 // bits of resolution of ramp
+
+struct env_t
+{
+ bool fused;
+
+ bool fhitend; // true if done
+ bool fexp; // true if exponential ramps
+
+ int ienv; // current ramp
+ rmp_t rmps[CENVRMPS]; // ramps
+};
+
+env_t envs[CENVS];
+
+void ENV_Init( env_t *penv ) { if (penv) Q_memset( penv, 0, sizeof (env_t) ); };
+void ENV_Free( env_t *penv ) { if (penv) Q_memset( penv, 0, sizeof (env_t) ); };
+void ENV_InitAll() { for (int i = 0; i < CENVS; i++) ENV_Init( &envs[i] ); };
+void ENV_FreeAll() { for (int i = 0; i < CENVS; i++) ENV_Free( &envs[i] ); };
+
+
+// allocate ADSR envelope
+// all times are in seconds
+// amp1 - attack amplitude multiplier 0-1.0
+// amp2 - sustain amplitude multiplier 0-1.0
+// amp3 - end of sustain amplitude multiplier 0-1.0
+
+env_t *ENV_Alloc ( int type, float famp1, float famp2, float famp3, float attack, float decay, float sustain, float release, bool fexp)
+{
+ int i;
+ env_t *penv;
+
+ for (i = 0; i < CENVS; i++)
+ {
+ if ( !envs[i].fused )
+ {
+
+ int amp1 = famp1 * (1 << ENV_BITS); // ramp resolution
+ int amp2 = famp2 * (1 << ENV_BITS);
+ int amp3 = famp3 * (1 << ENV_BITS);
+
+ penv = &envs[i];
+
+ ENV_Init (penv);
+
+ // UNDONE: ignoring type = ENV_EXP - use oneshot LFOS instead with sawtooth/exponential
+
+ // set up ramps
+
+ RMP_Init( &penv->rmps[0], attack, 0, amp1, true );
+ RMP_Init( &penv->rmps[1], decay, amp1, amp2, true );
+ RMP_Init( &penv->rmps[2], sustain, amp2, amp3, true );
+ RMP_Init( &penv->rmps[3], release, amp3, 0, true );
+
+ penv->ienv = 0;
+ penv->fused = true;
+ penv->fhitend = false;
+ penv->fexp = fexp;
+ return penv;
+ }
+ }
+ DevMsg ("DSP: Warning, failed to allocate envelope.\n" );
+ return NULL;
+}
+
+
+inline int ENV_GetNext( env_t *penv, int x )
+{
+ if ( !penv->fhitend )
+ {
+ int i;
+ int y;
+
+ i = penv->ienv;
+ y = RMP_GetNext ( &penv->rmps[i] );
+
+ // check for next ramp
+
+ if ( penv->rmps[i].fhitend )
+ i++;
+
+ penv->ienv = i;
+
+ // check for end of all ramps
+
+ if ( i > 3)
+ penv->fhitend = true;
+
+ // multiply input signal by ramp
+
+ if (penv->fexp)
+ return (((x * y) >> ENV_BITS) * y) >> ENV_BITS;
+ else
+ return (x * y) >> ENV_BITS;
+ }
+
+ return 0;
+}
+
+// batch version for performance
+
+inline void ENV_GetNextN( env_t *penv, portable_samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ portable_samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = ENV_GetNext( penv, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = ENV_GetNext( penv, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = ENV_GetNext( penv, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+
+// uses lfowav, amp1, amp2, amp3, attack, decay, sustain, release
+// lfowav is type, currently ignored - ie: LFO_LIN_IN, LFO_LOG_IN
+
+// parameter order
+
+typedef enum
+{
+ env_itype,
+ env_iamp1,
+ env_iamp2,
+ env_iamp3,
+ env_iattack,
+ env_idecay,
+ env_isustain,
+ env_irelease,
+ env_ifexp,
+ env_cparam // # of params
+
+} env_e;
+
+// parameter ranges
+
+prm_rng_t env_rng[] = {
+
+ {env_cparam, 0, 0}, // first entry is # of parameters
+
+ {env_itype, 0.0,ENV_MAX}, // ENV_LINEAR, ENV_LOG - currently ignored
+ {env_iamp1, 0.0, 1.0}, // attack peak amplitude 0-1.0
+ {env_iamp2, 0.0, 1.0}, // decay target amplitued 0-1.0
+ {env_iamp3, 0.0, 1.0}, // sustain target amplitude 0-1.0
+ {env_iattack, 0.0, 20000.0}, // attack time in milliseconds
+ {env_idecay, 0.0, 20000.0}, // envelope decay time in milliseconds
+ {env_isustain, 0.0, 20000.0}, // sustain time in milliseconds
+ {env_irelease, 0.0, 20000.0}, // release time in milliseconds
+ {env_ifexp, 0.0, 1.0}, // 1.0 if exponential ramps
+};
+
+env_t * ENV_Params ( prc_t *pprc )
+{
+ env_t *penv;
+
+ float type = pprc->prm[env_itype];
+ float amp1 = pprc->prm[env_iamp1];
+ float amp2 = pprc->prm[env_iamp2];
+ float amp3 = pprc->prm[env_iamp3];
+ float attack = pprc->prm[env_iattack]/1000.0;
+ float decay = pprc->prm[env_idecay]/1000.0;
+ float sustain = pprc->prm[env_isustain]/1000.0;
+ float release = pprc->prm[env_irelease]/1000.0;
+ float fexp = pprc->prm[env_ifexp];
+ bool bexp;
+
+ bexp = fexp > 0.0 ? 1 : 0;
+ penv = ENV_Alloc ( type, amp1, amp2, amp3, attack, decay, sustain, release, bexp );
+ return penv;
+}
+
+inline void * ENV_VParams ( void *p )
+{
+ PRC_CheckParams( (prc_t *)p, env_rng );
+ return (void *) ENV_Params ((prc_t *)p);
+}
+
+inline void ENV_Mod ( void *p, float v ) { return; }
+
+//////////////////////////
+// Gate & envelope follower
+//////////////////////////
+
+#define CEFOS 64 // max # of envelope followers active
+
+struct efo_t
+{
+ bool fused;
+
+ int xout; // current output value
+
+ // gate params
+
+ bool bgate; // if true, gate function is on
+
+ bool bgateon; // if true, gate is on
+ bool bexp; // if true, use exponential fade out
+
+ int thresh; // amplitude threshold for gate on
+ int thresh_off; // amplitidue threshold for gate off
+
+ float attack_time; // gate attack time in seconds
+ float decay_time; // gate decay time in seconds
+
+ rmp_t rmp_attack; // gate on ramp - attack
+ rmp_t rmp_decay; // gate off ramp - decay
+};
+
+efo_t efos[CEFOS];
+
+void EFO_Init( efo_t *pefo ) { if (pefo) Q_memset( pefo, 0, sizeof (efo_t) ); };
+void EFO_Free( efo_t *pefo ) { if (pefo) Q_memset( pefo, 0, sizeof (efo_t) ); };
+void EFO_InitAll() { for (int i = 0; i < CEFOS; i++) EFO_Init( &efos[i] ); };
+void EFO_FreeAll() { for (int i = 0; i < CEFOS; i++) EFO_Free( &efos[i] ); };
+
+// return true when gate is off AND decay ramp has hit end
+
+inline bool EFO_GateOff( efo_t *pefo )
+{
+ return ( !pefo->bgateon && RMP_HitEnd( &pefo->rmp_decay ) );
+}
+
+
+// allocate enveloper follower
+
+#define EFO_HYST_AMP 1000 // hysteresis amplitude
+
+efo_t *EFO_Alloc ( float threshold, float attack_sec, float decay_sec, bool bexp )
+{
+ int i;
+ efo_t *pefo;
+
+ for (i = 0; i < CEFOS; i++)
+ {
+ if ( !efos[i].fused )
+ {
+ pefo = &efos[i];
+
+ EFO_Init ( pefo );
+
+ pefo->xout = 0;
+ pefo->fused = true;
+
+ // init gate params
+
+ pefo->bgate = threshold > 0.0;
+
+ if (pefo->bgate)
+ {
+ pefo->attack_time = attack_sec;
+ pefo->decay_time = decay_sec;
+
+ RMP_Init( &pefo->rmp_attack, attack_sec, 0, PMAX, false);
+ RMP_Init( &pefo->rmp_decay, decay_sec, PMAX, 0, false);
+ RMP_SetEnd( &pefo->rmp_attack );
+ RMP_SetEnd( &pefo->rmp_decay );
+
+ pefo->thresh = threshold;
+ pefo->thresh_off = max(1.f, threshold - EFO_HYST_AMP);
+ pefo->bgateon = false;
+ pefo->bexp = bexp;
+ }
+
+ return pefo;
+ }
+ }
+
+ DevMsg ("DSP: Warning, failed to allocate envelope follower.\n" );
+ return NULL;
+}
+
+// values of L for CEFO_BITS_DIVIDE: L = (1 - 1/(1 << CEFO_BITS_DIVIDE))
+// 1 L = 0.5
+// 2 L = 0.75
+// 3 L = 0.875
+// 4 L = 0.9375
+// 5 L = 0.96875
+// 6 L = 0.984375
+// 7 L = 0.9921875
+// 8 L = 0.99609375
+// 9 L = 0.998046875
+// 10 L = 0.9990234375
+// 11 L = 0.99951171875
+// 12 L = 0.999755859375
+
+
+// decay time constant for values of L, for E = 10^-3 = 60dB of attenuation
+//
+// Neff = Ln E / Ln L = -6.9077552 / Ln L
+//
+// 1 L = 0.5 Neff = 10 samples
+// 2 L = 0.75 Neff = 24
+// 3 L = 0.875 Neff = 51
+// 4 L = 0.9375 Neff = 107
+// 5 L = 0.96875 Neff = 217
+// 6 L = 0.984375 Neff = 438
+// 7 L = 0.9921875 Neff = 880
+// 8 L = 0.99609375 Neff = 1764
+// 9 L = 0.998046875 Neff = 3533
+// 10 L = 0.9990234375 Neff = 7070
+// 11 L = 0.99951171875 Neff = 14143
+// 12 L = 0.999755859375 Neff = 28290
+
+#define CEFO_BITS 11 // 14143 samples in gate window (3hz)
+
+inline int EFO_GetNext( efo_t *pefo, int x )
+{
+ int r;
+ int xa = abs(x);
+ int xdif;
+
+
+ // get envelope:
+ // Cn = L * Cn-1 + ( 1 - L ) * |x|
+
+ // which simplifies to:
+ // Cn = |x| + (Cn-1 - |x|) * L
+
+ // for 0 < L < 1
+
+ // increasing L increases time to rise or fall to a new input level
+
+ // so: increasing CEFO_BITS_DIVIDE increases rise/fall time
+
+ // where: L = (1 - 1/(1 << CEFO_BITS))
+ // xdif = Cn-1 - |x|
+ // so: xdif * L = xdif - xdif / (1 << CEFO_BITS) = ((xdif << CEFO_BITS) - xdif ) >> CEFO_BITS
+
+ xdif = pefo->xout - xa;
+
+ pefo->xout = xa + (((xdif << CEFO_BITS) - xdif) >> CEFO_BITS);
+
+ if ( pefo->bgate )
+ {
+ // gate
+
+ bool bgateon_prev = pefo->bgateon;
+
+ // gate hysteresis
+
+ if (bgateon_prev)
+ // gate was on - it's off only if amp drops below thresh_off
+ pefo->bgateon = ( pefo->xout >= pefo->thresh_off );
+ else
+ // gate was off - it's on only if amp > thresh
+ pefo->bgateon = ( pefo->xout >= pefo->thresh );
+
+ if ( pefo->bgateon )
+ {
+ // gate is on
+
+ if ( bgateon_prev && RMP_HitEnd( &pefo->rmp_attack ))
+ return x; // gate is fully on
+
+ if ( !bgateon_prev )
+ {
+ // gate just turned on, start ramp attack
+
+ // start attack from previous decay ramp if active
+
+ r = RMP_HitEnd( &pefo->rmp_decay ) ? 0 : RMP_GetNext( &pefo->rmp_decay );
+ RMP_SetEnd( &pefo->rmp_decay);
+
+ // DevMsg ("GATE ON \n");
+
+ RMP_Init( &pefo->rmp_attack, pefo->attack_time, r, PMAX, false);
+
+ return (x * r) >> PBITS;
+ }
+
+ if ( !RMP_HitEnd( &pefo->rmp_attack ) )
+ {
+ r = RMP_GetNext( &pefo->rmp_attack );
+
+ // gate is on and ramping up
+
+ return (x * r) >> PBITS;
+ }
+
+ }
+ else
+ {
+ // gate is fully off
+
+ if ( !bgateon_prev && RMP_HitEnd( &pefo->rmp_decay))
+ return 0;
+
+ if ( bgateon_prev )
+ {
+ // gate just turned off, start ramp decay
+
+ // start decay from previous attack ramp if active
+
+ r = RMP_HitEnd( &pefo->rmp_attack ) ? PMAX : RMP_GetNext( &pefo->rmp_attack );
+ RMP_SetEnd( &pefo->rmp_attack);
+
+ RMP_Init( &pefo->rmp_decay, pefo->decay_time, r, 0, false);
+
+ // DevMsg ("GATE OFF \n");
+
+ // if exponential set, gate has exponential ramp down. Otherwise linear ramp down.
+
+ if ( pefo->bexp )
+ return ( (((x * r) >> PBITS) * r ) >> PBITS );
+ else
+ return (x * r) >> PBITS;
+
+ }
+ else if ( !RMP_HitEnd( &pefo->rmp_decay ) )
+ {
+ // gate is off and ramping down
+
+ r = RMP_GetNext( &pefo->rmp_decay );
+
+
+ // if exponential set, gate has exponential ramp down. Otherwise linear ramp down.
+
+ if ( pefo->bexp )
+ return ( (((x * r) >> PBITS) * r ) >> PBITS );
+ else
+ return (x * r) >> PBITS;
+ }
+ }
+
+ return x;
+ }
+
+ return pefo->xout;
+}
+
+// batch version for performance
+
+inline void EFO_GetNextN( efo_t *pefo, portable_samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ portable_samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = EFO_GetNext( pefo, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = EFO_GetNext( pefo, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = EFO_GetNext( pefo, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+// parameter order
+
+typedef enum
+{
+ efo_ithreshold,
+ efo_iattack,
+ efo_idecay,
+ efo_iexp,
+
+ efo_cparam // # of params
+
+} efo_e;
+
+// parameter ranges
+
+prm_rng_t efo_rng[] = {
+
+ {efo_cparam, 0, 0}, // first entry is # of parameters
+
+ {efo_ithreshold, -140.0, 0.0}, // gate threshold in db. if 0.0 then no gate.
+ {efo_iattack, 0.0, 20000.0}, // attack time in milliseconds
+ {efo_idecay, 0.0, 20000.0}, // envelope decay time in milliseconds
+ {efo_iexp, 0.0, 1.0}, // if 1, use exponential decay ramp (for more realistic reverb tail)
+
+};
+
+efo_t * EFO_Params ( prc_t *pprc )
+{
+ efo_t *penv;
+
+ float threshold = Gain_To_Amplitude( dB_To_Gain(pprc->prm[efo_ithreshold]) );
+ float attack = pprc->prm[efo_iattack]/1000.0;
+ float decay = pprc->prm[efo_idecay]/1000.0;
+ float fexp = pprc->prm[efo_iexp];
+ bool bexp;
+
+ // check for no gate
+
+ if ( pprc->prm[efo_ithreshold] == 0.0 )
+ threshold = 0.0;
+
+ bexp = fexp > 0.0 ? 1 : 0;
+
+ penv = EFO_Alloc ( threshold, attack, decay, bexp );
+ return penv;
+}
+
+inline void * EFO_VParams ( void *p )
+{
+ PRC_CheckParams( (prc_t *)p, efo_rng );
+ return (void *) EFO_Params ((prc_t *)p);
+}
+
+inline void EFO_Mod ( void *p, float v ) { return; }
+
+
+///////////////////////////////////////////
+// Chorus - lfo modulated delay
+///////////////////////////////////////////
+
+
+#define CCRSS 64 // max number chorus' active
+
+struct crs_t
+{
+ bool fused;
+
+ mdy_t *pmdy; // modulatable delay
+ lfo_t *plfo; // modulating lfo
+
+ int lfoprev; // previous modulator value from lfo
+
+};
+
+crs_t crss[CCRSS];
+
+void CRS_Init( crs_t *pcrs ) { if (pcrs) Q_memset( pcrs, 0, sizeof (crs_t) ); };
+void CRS_Free( crs_t *pcrs )
+{
+ if (pcrs)
+ {
+ MDY_Free ( pcrs->pmdy );
+ LFO_Free ( pcrs->plfo );
+ Q_memset( pcrs, 0, sizeof (crs_t) );
+ }
+}
+
+
+void CRS_InitAll() { for (int i = 0; i < CCRSS; i++) CRS_Init( &crss[i] ); }
+void CRS_FreeAll() { for (int i = 0; i < CCRSS; i++) CRS_Free( &crss[i] ); }
+
+// fstep is base pitch shift, ie: floating point step value, where 1.0 = +1 octave, 0.5 = -1 octave
+// lfotype is LFO_SIN, LFO_RND, LFO_TRI etc (LFO_RND for chorus, LFO_SIN for flange)
+// fHz is modulation frequency in Hz
+// depth is modulation depth, 0-1.0
+// mix is mix of chorus and clean signal
+
+#define CRS_DELAYMAX 100 // max milliseconds of sweepable delay
+#define CRS_RAMPTIME 5 // milliseconds to ramp between new delay values
+
+crs_t * CRS_Alloc( int lfotype, float fHz, float fdepth, float mix )
+{
+
+ int i;
+ crs_t *pcrs;
+ dly_t *pdly;
+ mdy_t *pmdy;
+ lfo_t *plfo;
+ float ramptime;
+ int D;
+
+ // find free chorus slot
+
+ for ( i = 0; i < CCRSS; i++ )
+ {
+ if ( !crss[i].fused )
+ break;
+ }
+
+ if ( i == CCRSS )
+ {
+ DevMsg ("DSP: Warning, failed to allocate chorus.\n" );
+ return NULL;
+ }
+
+ pcrs = &crss[i];
+
+ CRS_Init ( pcrs );
+
+ D = fdepth * MSEC_TO_SAMPS(CRS_DELAYMAX); // sweep from 0 - n milliseconds
+
+ ramptime = (float) CRS_RAMPTIME / 1000.0; // # milliseconds to ramp between new values
+
+ pdly = DLY_Alloc ( D, 0, 1, DLY_LINEAR );
+
+ pmdy = MDY_Alloc ( pdly, ramptime, 0.0, 0.0, mix );
+
+ plfo = LFO_Alloc ( lfotype, fHz, false, 1.0 );
+
+ if ( !plfo || !pmdy )
+ {
+ LFO_Free ( plfo );
+ MDY_Free ( pmdy );
+ DevMsg ("DSP: Warning, failed to allocate lfo or mdy for chorus.\n" );
+ return NULL;
+ }
+
+ pcrs->pmdy = pmdy;
+ pcrs->plfo = plfo;
+ pcrs->fused = true;
+
+ return pcrs;
+}
+
+// return next chorused sample (modulated delay) mixed with input sample
+
+inline int CRS_GetNext( crs_t *pcrs, int x )
+{
+ int l;
+ int y;
+
+ // get current mod delay value
+
+ y = MDY_GetNext ( pcrs->pmdy, x );
+
+ // get next lfo value for modulation
+ // note: lfo must return 0 as first value
+
+ l = LFO_GetNext ( pcrs->plfo, x );
+
+ // if modulator has changed, change mdy
+
+ if ( l != pcrs->lfoprev )
+ {
+ // calculate new tap starts at D)
+
+ int D = pcrs->pmdy->pdly->D0;
+ int tap;
+
+ // lfo should always output values 0 <= l <= LFOMAX
+
+ if (l < 0)
+ l = 0;
+
+ tap = D - ((l * D) >> LFOBITS);
+
+ MDY_ChangeVal ( pcrs->pmdy, tap );
+
+ pcrs->lfoprev = l;
+ }
+
+ return y;
+}
+
+// batch version for performance
+
+inline void CRS_GetNextN( crs_t *pcrs, portable_samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ portable_samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = CRS_GetNext( pcrs, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = CRS_GetNext( pcrs, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = CRS_GetNext( pcrs, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+
+// parameter order
+
+typedef enum {
+
+ crs_ilfotype,
+ crs_irate,
+ crs_idepth,
+ crs_imix,
+
+ crs_cparam
+
+} crs_e;
+
+
+// parameter ranges
+
+prm_rng_t crs_rng[] = {
+
+ {crs_cparam, 0, 0}, // first entry is # of parameters
+
+ {crs_ilfotype, 0, LFO_MAX}, // lfotype is LFO_SIN, LFO_RND, LFO_TRI etc (LFO_RND for chorus, LFO_SIN for flange)
+ {crs_irate, 0.0, 1000.0}, // rate is modulation frequency in Hz
+ {crs_idepth, 0.0, 1.0}, // depth is modulation depth, 0-1.0
+ {crs_imix, 0.0, 1.0}, // mix is mix of chorus and clean signal
+
+};
+
+// uses pitch, lfowav, rate, depth
+
+crs_t * CRS_Params ( prc_t *pprc )
+{
+ crs_t *pcrs;
+
+ pcrs = CRS_Alloc ( pprc->prm[crs_ilfotype], pprc->prm[crs_irate], pprc->prm[crs_idepth], pprc->prm[crs_imix] );
+
+ return pcrs;
+}
+
+inline void * CRS_VParams ( void *p )
+{
+ PRC_CheckParams ( (prc_t *)p, crs_rng );
+ return (void *) CRS_Params ((prc_t *)p);
+}
+
+inline void CRS_Mod ( void *p, float v ) { return; }
+
+
+////////////////////////////////////////////////////
+// amplifier - modulatable gain, distortion
+////////////////////////////////////////////////////
+
+#define CAMPS 64 // max number amps active
+
+#define AMPSLEW 10 // milliseconds of slew time between gain changes
+
+struct amp_t
+{
+ bool fused;
+
+ int gain; // amplification 0-6.0 * PMAX
+ int gain_max; // original gain setting
+ int distmix; // 0-1.0 mix of distortion with clean * PMAX
+ int vfeed; // 0-1.0 feedback with distortion * PMAX
+ int vthresh; // amplitude of clipping threshold 0..32768
+
+
+ bool fchanging; // true if modulating to new amp value
+ float ramptime; // ramp 'glide' time - time in seconds to change between values
+ int mtime; // time in samples between amp changes. 0 implies no self-modulating
+ int mtimecur; // current time in samples until next amp change
+ int depth; // modulate amp from A to A - (A*depth) depth 0-1.0
+ bool brand; // if true, use random modulation otherwise alternate btwn max/min
+ rmp_t rmp_interp; // interpolation ramp 0...PMAX
+
+};
+
+amp_t amps[CAMPS];
+
+void AMP_Init( amp_t *pamp ) { if (pamp) Q_memset( pamp, 0, sizeof (amp_t) ); };
+void AMP_Free( amp_t *pamp )
+{
+ if (pamp)
+ {
+ Q_memset( pamp, 0, sizeof (amp_t) );
+ }
+}
+
+
+void AMP_InitAll() { for (int i = 0; i < CAMPS; i++) AMP_Init( &amps[i] ); }
+void AMP_FreeAll() { for (int i = 0; i < CAMPS; i++) AMP_Free( &amps[i] ); }
+
+
+amp_t * AMP_Alloc( float gain, float vthresh, float distmix, float vfeed, float ramptime, float modtime, float depth, bool brand )
+{
+ int i;
+ amp_t *pamp;
+
+ // find free amp slot
+
+ for ( i = 0; i < CAMPS; i++ )
+ {
+ if ( !amps[i].fused )
+ break;
+ }
+
+ if ( i == CAMPS )
+ {
+ DevMsg ("DSP: Warning, failed to allocate amp.\n" );
+ return NULL;
+ }
+
+ pamp = &amps[i];
+
+ AMP_Init ( pamp );
+
+ pamp->fused = true;
+
+ pamp->gain = gain * PMAX;
+ pamp->gain_max = gain * PMAX;
+ pamp->distmix = distmix * PMAX;
+ pamp->vfeed = vfeed * PMAX;
+ pamp->vthresh = vthresh * 32767.0;
+
+ // modrate, 0.01, 200.0}, // frequency at which amplitude values change to new random value. 0 is no self-modulation
+ // moddepth, 0.0, 1.0}, // how much amplitude changes (decreases) from current value (0-1.0)
+ // modglide, 0.01, 100.0}, // glide time between mapcur and ampnew in milliseconds
+
+ pamp->ramptime = ramptime;
+ pamp->mtime = SEC_TO_SAMPS(modtime);
+ pamp->mtimecur = pamp->mtime;
+ pamp->depth = depth * PMAX;
+ pamp->brand = brand;
+
+ return pamp;
+}
+
+// return next amplified sample
+
+inline int AMP_GetNext( amp_t *pamp, int x )
+{
+ int y = x;
+ int d;
+
+ // if distortion is on, add distortion, feedback
+
+ if ( pamp->vthresh < PMAX && pamp->distmix )
+ {
+ int vthresh = pamp->vthresh;
+
+/* if ( pamp->vfeed > 0.0 )
+ {
+ // UNDONE: feedback
+ }
+*/
+ // clip distort
+
+ d = ( y > vthresh ? vthresh : ( y < -vthresh ? -vthresh : y));
+
+ // mix distorted with clean (1.0 = full distortion)
+
+ if ( pamp->distmix < PMAX )
+ y = y + (((d - y) * pamp->distmix ) >> PBITS);
+ else
+ y = d;
+ }
+
+ // get output for current gain value
+
+ int xout = (y * pamp->gain) >> PBITS;
+
+ if ( !pamp->fchanging && !pamp->mtime )
+ {
+ // if not modulating and not self modulating, return right away
+
+ return xout;
+ }
+
+ if (pamp->fchanging)
+ {
+ // modulating...
+
+ // get next gain value
+
+ pamp->gain = RMP_GetNext( &pamp->rmp_interp ); // 0...next gain
+
+ if ( RMP_HitEnd( &pamp->rmp_interp ) )
+ {
+ // done.
+
+ pamp->fchanging = false;
+ }
+ }
+
+ // if self-modulating and timer has expired, get next change
+
+ if ( pamp->mtime && !pamp->mtimecur-- )
+ {
+ pamp->mtimecur = pamp->mtime;
+
+ int gain_new;
+ int G1;
+ int G2 = pamp->gain_max;
+
+ // modulate between 0 and 100% of gain_max
+
+ G1 = pamp->gain_max - ((pamp->gain_max * pamp->depth) >> PBITS);
+
+ if (pamp->brand)
+ {
+ gain_new = RandomInt( min(G1,G2), max(G1,G2) );
+ }
+ else
+ {
+ // alternate between min & max
+
+ gain_new = (pamp->gain == G1 ? G2 : G1);
+ }
+
+ // set up modulation to new value
+
+ pamp->fchanging = true;
+
+ // init gain ramp - always hit target
+
+ RMP_Init ( &pamp->rmp_interp, pamp->ramptime, pamp->gain, gain_new, false );
+ }
+
+ return xout;
+
+}
+
+// batch version for performance
+
+inline void AMP_GetNextN( amp_t *pamp, portable_samplepair_t *pbuffer, int SampleCount, int op )
+{
+ int count = SampleCount;
+ portable_samplepair_t *pb = pbuffer;
+
+ switch (op)
+ {
+ default:
+ case OP_LEFT:
+ while (count--)
+ {
+ pb->left = AMP_GetNext( pamp, pb->left );
+ pb++;
+ }
+ return;
+ case OP_RIGHT:
+ while (count--)
+ {
+ pb->right = AMP_GetNext( pamp, pb->right );
+ pb++;
+ }
+ return;
+ case OP_LEFT_DUPLICATE:
+ while (count--)
+ {
+ pb->left = pb->right = AMP_GetNext( pamp, pb->left );
+ pb++;
+ }
+ return;
+ }
+}
+
+inline void AMP_Mod( amp_t *pamp, float v )
+{
+}
+
+
+// parameter order
+
+typedef enum {
+
+ amp_gain,
+ amp_vthresh,
+ amp_distmix,
+ amp_vfeed,
+ amp_imodrate,
+ amp_imoddepth,
+ amp_imodglide,
+ amp_irand,
+ amp_cparam
+
+} amp_e;
+
+
+// parameter ranges
+
+prm_rng_t amp_rng[] = {
+
+ {amp_cparam, 0, 0}, // first entry is # of parameters
+
+ {amp_gain, 0.0, 1000.0}, // amplification
+ {amp_vthresh, 0.0, 1.0}, // threshold for distortion (1.0 = no distortion)
+ {amp_distmix, 0.0, 1.0}, // mix of clean and distortion (1.0 = full distortion, 0.0 = full clean)
+ {amp_vfeed, 0.0, 1.0}, // distortion feedback
+
+ {amp_imodrate, 0.0, 200.0}, // frequency at which amplitude values change to new random value. 0 is no self-modulation
+ {amp_imoddepth, 0.0, 1.0}, // how much amplitude changes (decreases) from current value (0-1.0)
+ {amp_imodglide, 0.01, 100.0}, // glide time between mapcur and ampnew in milliseconds
+ {amp_irand, 0.0, 1.0}, // if 1, use random modulation otherwise alternate from max-min-max
+};
+
+amp_t * AMP_Params ( prc_t *pprc )
+{
+ amp_t *pamp;
+
+ float ramptime = 0.0;
+ float modtime = 0.0;
+ float depth = 0.0;
+ float rand = pprc->prm[amp_irand];
+ bool brand;
+
+ if (pprc->prm[amp_imodrate] > 0.0)
+ {
+ ramptime = pprc->prm[amp_imodglide] / 1000.0; // get ramp time in seconds
+ modtime = 1.0 / max((double)pprc->prm[amp_imodrate], 0.01); // time between modulations in seconds
+ depth = pprc->prm[amp_imoddepth]; // depth of modulations 0-1.0
+ }
+
+ brand = rand > 0.0 ? 1 : 0;
+
+ pamp = AMP_Alloc ( pprc->prm[amp_gain], pprc->prm[amp_vthresh], pprc->prm[amp_distmix], pprc->prm[amp_vfeed],
+ ramptime, modtime, depth, brand );
+
+ return pamp;
+}
+
+inline void * AMP_VParams ( void *p )
+{
+ PRC_CheckParams ( (prc_t *)p, amp_rng );
+ return (void *) AMP_Params ((prc_t *)p);
+}
+
+
+/////////////////
+// NULL processor
+/////////////////
+
+struct nul_t
+{
+ int type;
+};
+
+nul_t nuls[] = {{0}};
+
+void NULL_Init ( nul_t *pnul ) { }
+void NULL_InitAll( ) { }
+void NULL_Free ( nul_t *pnul ) { }
+void NULL_FreeAll ( ) { }
+nul_t *NULL_Alloc ( ) { return &nuls[0]; }
+
+inline int NULL_GetNext ( void *p, int x) { return x; }
+
+inline void NULL_GetNextN( nul_t *pnul, portable_samplepair_t *pbuffer, int SampleCount, int op ) { return; }
+
+inline void NULL_Mod ( void *p, float v ) { return; }
+
+inline void * NULL_VParams ( void *p ) { return (void *) (&nuls[0]); }
+
+//////////////////////////
+// DSP processors presets - see dsp_presets.txt
+//////////////////////////
+
+
+
+
+// init array of processors - first store pfnParam, pfnGetNext and pfnFree functions for type,
+// then call the pfnParam function to initialize each processor
+
+// prcs - an array of prc structures, all with initialized params
+// count - number of elements in the array
+
+// returns false if failed to init one or more processors
+
+bool PRC_InitAll( prc_t *prcs, int count )
+{
+ int i;
+ prc_Param_t pfnParam; // allocation function - takes ptr to prc, returns ptr to specialized data struct for proc type
+ prc_GetNext_t pfnGetNext; // get next function
+ prc_GetNextN_t pfnGetNextN; // get next function, batch version
+ prc_Free_t pfnFree;
+ prc_Mod_t pfnMod;
+
+ bool fok = true;;
+
+ if ( count == 0 )
+ count = 1;
+
+ // set up pointers to XXX_Free, XXX_GetNext and XXX_Params functions
+
+ for (i = 0; i < count; i++)
+ {
+ switch (prcs[i].type)
+ {
+ default:
+ case PRC_NULL:
+ pfnFree = (prc_Free_t)NULL_Free;
+ pfnGetNext = (prc_GetNext_t)NULL_GetNext;
+ pfnGetNextN = (prc_GetNextN_t)NULL_GetNextN;
+ pfnParam = NULL_VParams;
+ pfnMod = (prc_Mod_t)NULL_Mod;
+ break;
+ case PRC_DLY:
+ pfnFree = (prc_Free_t)DLY_Free;
+ pfnGetNext = (prc_GetNext_t)DLY_GetNext;
+ pfnGetNextN = (prc_GetNextN_t)DLY_GetNextN;
+ pfnParam = DLY_VParams;
+ pfnMod = (prc_Mod_t)DLY_Mod;
+ break;
+ case PRC_RVA:
+ pfnFree = (prc_Free_t)RVA_Free;
+ pfnGetNext = (prc_GetNext_t)RVA_GetNext;
+ pfnGetNextN = (prc_GetNextN_t)RVA_GetNextN;
+ pfnParam = RVA_VParams;
+ pfnMod = (prc_Mod_t)RVA_Mod;
+ break;
+ case PRC_FLT:
+ pfnFree = (prc_Free_t)FLT_Free;
+ pfnGetNext = (prc_GetNext_t)FLT_GetNext;
+ pfnGetNextN = (prc_GetNextN_t)FLT_GetNextN;
+ pfnParam = FLT_VParams;
+ pfnMod = (prc_Mod_t)FLT_Mod;
+ break;
+ case PRC_CRS:
+ pfnFree = (prc_Free_t)CRS_Free;
+ pfnGetNext = (prc_GetNext_t)CRS_GetNext;
+ pfnGetNextN = (prc_GetNextN_t)CRS_GetNextN;
+ pfnParam = CRS_VParams;
+ pfnMod = (prc_Mod_t)CRS_Mod;
+ break;
+ case PRC_PTC:
+ pfnFree = (prc_Free_t)PTC_Free;
+ pfnGetNext = (prc_GetNext_t)PTC_GetNext;
+ pfnGetNextN = (prc_GetNextN_t)PTC_GetNextN;
+ pfnParam = PTC_VParams;
+ pfnMod = (prc_Mod_t)PTC_Mod;
+ break;
+ case PRC_ENV:
+ pfnFree = (prc_Free_t)ENV_Free;
+ pfnGetNext = (prc_GetNext_t)ENV_GetNext;
+ pfnGetNextN = (prc_GetNextN_t)ENV_GetNextN;
+ pfnParam = ENV_VParams;
+ pfnMod = (prc_Mod_t)ENV_Mod;
+ break;
+ case PRC_LFO:
+ pfnFree = (prc_Free_t)LFO_Free;
+ pfnGetNext = (prc_GetNext_t)LFO_GetNext;
+ pfnGetNextN = (prc_GetNextN_t)LFO_GetNextN;
+ pfnParam = LFO_VParams;
+ pfnMod = (prc_Mod_t)LFO_Mod;
+ break;
+ case PRC_EFO:
+ pfnFree = (prc_Free_t)EFO_Free;
+ pfnGetNext = (prc_GetNext_t)EFO_GetNext;
+ pfnGetNextN = (prc_GetNextN_t)EFO_GetNextN;
+ pfnParam = EFO_VParams;
+ pfnMod = (prc_Mod_t)EFO_Mod;
+ break;
+ case PRC_MDY:
+ pfnFree = (prc_Free_t)MDY_Free;
+ pfnGetNext = (prc_GetNext_t)MDY_GetNext;
+ pfnGetNextN = (prc_GetNextN_t)MDY_GetNextN;
+ pfnParam = MDY_VParams;
+ pfnMod = (prc_Mod_t)MDY_Mod;
+ break;
+ case PRC_DFR:
+ pfnFree = (prc_Free_t)DFR_Free;
+ pfnGetNext = (prc_GetNext_t)DFR_GetNext;
+ pfnGetNextN = (prc_GetNextN_t)DFR_GetNextN;
+ pfnParam = DFR_VParams;
+ pfnMod = (prc_Mod_t)DFR_Mod;
+ break;
+ case PRC_AMP:
+ pfnFree = (prc_Free_t)AMP_Free;
+ pfnGetNext = (prc_GetNext_t)AMP_GetNext;
+ pfnGetNextN = (prc_GetNextN_t)AMP_GetNextN;
+ pfnParam = AMP_VParams;
+ pfnMod = (prc_Mod_t)AMP_Mod;
+ break;
+ }
+
+ // set up function pointers
+
+ prcs[i].pfnParam = pfnParam;
+ prcs[i].pfnGetNext = pfnGetNext;
+ prcs[i].pfnGetNextN = pfnGetNextN;
+ prcs[i].pfnFree = pfnFree;
+ prcs[i].pfnMod = pfnMod;
+
+ // call param function, store pdata for the processor type
+
+ prcs[i].pdata = pfnParam ( (void *) (&prcs[i]) );
+
+ if ( !prcs[i].pdata )
+ fok = false;
+ }
+
+ return fok;
+}
+
+// free individual processor's data
+
+void PRC_Free ( prc_t *pprc )
+{
+ if ( pprc->pfnFree && pprc->pdata )
+ pprc->pfnFree ( pprc->pdata );
+}
+
+// free all processors for supplied array
+// prcs - array of processors
+// count - elements in array
+
+void PRC_FreeAll ( prc_t *prcs, int count )
+{
+ for (int i = 0; i < count; i++)
+ PRC_Free( &prcs[i] );
+}
+
+// get next value for processor - (usually called directly by PSET_GetNext)
+
+inline int PRC_GetNext ( prc_t *pprc, int x )
+{
+ return pprc->pfnGetNext ( pprc->pdata, x );
+}
+
+// automatic parameter range limiting
+// force parameters between specified min/max in param_rng
+
+void PRC_CheckParams ( prc_t *pprc, prm_rng_t *prng )
+{
+ // first entry in param_rng is # of parameters
+
+ int cprm = prng[0].iprm;
+
+ for (int i = 0; i < cprm; i++)
+ {
+ // if parameter is 0.0, always allow it (this is 'off' for most params)
+
+ if ( pprc->prm[i] != 0.0 && (pprc->prm[i] > prng[i+1].hi || pprc->prm[i] < prng[i+1].lo) )
+ {
+ DevMsg ("DSP: Warning, clamping out of range parameter.\n" );
+ pprc->prm[i] = clamp (pprc->prm[i], prng[i+1].lo, prng[i+1].hi);
+ }
+ }
+}
+
+
+// DSP presets
+
+// A dsp preset comprises one or more dsp processors in linear, parallel or feedback configuration
+
+// preset configurations
+//
+#define PSET_SIMPLE 0
+
+// x(n)--->P(0)--->y(n)
+
+#define PSET_LINEAR 1
+
+// x(n)--->P(0)-->P(1)-->...P(m)--->y(n)
+
+
+#define PSET_PARALLEL2 5
+
+// x(n)--->P(0)-->(+)-->y(n)
+// ^
+// |
+// x(n)--->P(1)-----
+
+#define PSET_PARALLEL4 6
+
+// x(n)--->P(0)-->P(1)-->(+)-->y(n)
+// ^
+// |
+// x(n)--->P(2)-->P(3)-----
+
+#define PSET_PARALLEL5 7
+
+// x(n)--->P(0)-->P(1)-->(+)-->P(4)-->y(n)
+// ^
+// |
+// x(n)--->P(2)-->P(3)-----
+
+#define PSET_FEEDBACK 8
+
+// x(n)-P(0)--(+)-->P(1)-->P(2)---->y(n)
+// ^ |
+// | v
+// -----P(4)<--P(3)--
+
+#define PSET_FEEDBACK3 9
+
+// x(n)---(+)-->P(0)--------->y(n)
+// ^ |
+// | v
+// -----P(2)<--P(1)--
+
+#define PSET_FEEDBACK4 10
+
+// x(n)---(+)-->P(0)-------->P(3)--->y(n)
+// ^ |
+// | v
+// ---P(2)<--P(1)--
+
+#define PSET_MOD 11
+
+//
+// x(n)------>P(1)--P(2)--P(3)--->y(n)
+// ^
+// x(n)------>P(0)....:
+
+#define PSET_MOD2 12
+
+//
+// x(n)-------P(1)-->y(n)
+// ^
+// x(n)-->P(0)..:
+
+
+#define PSET_MOD3 13
+
+//
+// x(n)-------P(1)-->P(2)-->y(n)
+// ^
+// x(n)-->P(0)..:
+
+
+#define CPSETS 64 // max number of presets simultaneously active
+
+#define CPSET_PRCS 5 // max # of processors per dsp preset
+#define CPSET_STATES (CPSET_PRCS+3) // # of internal states
+
+// NOTE: do not reorder members of pset_t - g_psettemplates relies on it!!!
+
+struct pset_t
+{
+ int type; // preset configuration type
+ int cprcs; // number of processors for this preset
+
+ prc_t prcs[CPSET_PRCS]; // processor preset data
+
+ float mix_min; // min dsp mix at close range
+ float mix_max; // max dsp mix at long range
+ float db_min; // if sndlvl of a new sound is < db_min, reduce mix_min/max by db_mixdrop
+ float db_mixdrop; // reduce mix_min/max by n% if sndlvl of new sound less than db_min
+ float duration; // if > 0, duration of preset in seconds (duration 0 = infinite)
+ float fade; // fade out time, exponential fade
+
+ int csamp_duration; // duration counter # samples
+
+ int w[CPSET_STATES]; // internal states
+ int fused;
+};
+
+pset_t psets[CPSETS];
+
+pset_t *g_psettemplates = NULL;
+int g_cpsettemplates = 0;
+
+// returns true if preset will expire after duration
+
+bool PSET_IsOneShot( pset_t *ppset )
+{
+ return ppset->duration > 0.0;
+}
+
+// return true if preset is no longer active - duration has expired
+
+bool PSET_HasExpired( pset_t *ppset )
+{
+ if (!PSET_IsOneShot( ppset ))
+ return false;
+
+ return ppset->csamp_duration <= 0;
+}
+
+// if preset is oneshot, update duration counter by SampleCount samples
+
+void PSET_UpdateDuration( pset_t *ppset, int SampleCount )
+{
+ if ( PSET_IsOneShot( ppset ) )
+ {
+ // if oneshot preset and not expired, decrement sample count
+
+ if (ppset->csamp_duration > 0)
+ ppset->csamp_duration -= SampleCount;
+ }
+}
+
+// A dsp processor (prc) performs a single-sample function, such as pitch shift, delay, reverb, filter
+
+
+// init a preset - just clear state array
+
+void PSET_Init( pset_t *ppset )
+{
+ // clear state array
+
+ if (ppset)
+ Q_memset( ppset->w, 0, sizeof (int) * (CPSET_STATES) );
+}
+
+// clear runtime slots
+
+void PSET_InitAll( void )
+{
+ for (int i = 0; i < CPSETS; i++)
+ Q_memset( &psets[i], 0, sizeof(pset_t));
+}
+
+// free the preset - free all processors
+
+void PSET_Free( pset_t *ppset )
+{
+ if (ppset)
+ {
+ // free processors
+
+ PRC_FreeAll ( ppset->prcs, ppset->cprcs );
+
+ // clear
+
+ Q_memset( ppset, 0, sizeof (pset_t));
+ }
+}
+
+void PSET_FreeAll() { for (int i = 0; i < CPSETS; i++) PSET_Free( &psets[i] ); };
+
+// return preset struct, given index into preset template array
+// NOTE: should not ever be more than 2 or 3 of these active simultaneously
+
+pset_t * PSET_Alloc ( int ipsettemplate )
+{
+ pset_t *ppset;
+ bool fok;
+
+ // don't excede array bounds
+
+ if ( ipsettemplate >= g_cpsettemplates)
+ ipsettemplate = 0;
+
+ // find free slot
+ int i = 0;
+ for (i = 0; i < CPSETS; i++)
+ {
+ if ( !psets[i].fused )
+ break;
+ }
+
+ if ( i == CPSETS )
+ return NULL;
+
+ if (das_debug.GetInt())
+ {
+ int nSlots = 0;
+ for ( int j = 0; j < CPSETS; j++)
+ {
+ if ( psets[j].fused )
+ nSlots++;
+ }
+ DevMsg("total preset slots used: %d \n", nSlots );
+ }
+
+
+ ppset = &psets[i];
+
+ // clear preset
+
+ Q_memset(ppset, 0, sizeof(pset_t));
+
+ // copy template into preset
+
+ *ppset = g_psettemplates[ipsettemplate];
+
+ ppset->fused = true;
+
+ // clear state array
+
+ PSET_Init ( ppset );
+
+ // init all processors, set up processor function pointers
+
+ fok = PRC_InitAll( ppset->prcs, ppset->cprcs );
+
+ if ( !fok )
+ {
+ // failed to init one or more processors
+ Warning( "Sound DSP: preset failed to init.\n");
+ PRC_FreeAll ( ppset->prcs, ppset->cprcs );
+ return NULL;
+ }
+
+ // if preset has duration, setup duration sample counter
+
+ if ( PSET_IsOneShot( ppset ) )
+ {
+ ppset->csamp_duration = SEC_TO_SAMPS( ppset->duration );
+ }
+
+ return ppset;
+}
+
+// batch version of PSET_GetNext for linear array of processors. For performance.
+
+// ppset - preset array
+// pbuffer - input sample data
+// SampleCount - size of input buffer
+// OP: OP_LEFT - process left channel in place
+// OP_RIGHT - process right channel in place
+// OP_LEFT_DUPLICATe - process left channel, duplicate into right
+
+inline void PSET_GetNextN( pset_t *ppset, portable_samplepair_t *pbuffer, int SampleCount, int op )
+{
+ portable_samplepair_t *pbf = pbuffer;
+ prc_t *pprc;
+ int count = ppset->cprcs;
+
+ switch ( ppset->type )
+ {
+ default:
+ case PSET_SIMPLE:
+ {
+ // x(n)--->P(0)--->y(n)
+
+ ppset->prcs[0].pfnGetNextN (ppset->prcs[0].pdata, pbf, SampleCount, op);
+ return;
+ }
+ case PSET_LINEAR:
+ {
+
+ // w0 w1 w2
+ // x(n)--->P(0)-->P(1)-->...P(count-1)--->y(n)
+
+ // w0 w1 w2 w3 w4 w5
+ // x(n)--->P(0)-->P(1)-->P(2)-->P(3)-->P(4)-->y(n)
+
+ // call batch processors in sequence - no internal state for batch processing
+
+ // point to first processor
+
+ pprc = &ppset->prcs[0];
+
+ for (int i = 0; i < count; i++)
+ {
+ pprc->pfnGetNextN (pprc->pdata, pbf, SampleCount, op);
+ pprc++;
+ }
+
+ return;
+ }
+ }
+}
+
+
+// Get next sample from this preset. called once for every sample in buffer
+// ppset is pointer to preset
+// x is input sample
+
+inline int PSET_GetNext ( pset_t *ppset, int x )
+{
+
+ // pset_simple and pset_linear have no internal state:
+ // this is REQUIRED for all presets that have a batch getnextN equivalent!
+
+ if ( ppset->type == PSET_SIMPLE )
+ {
+ // x(n)--->P(0)--->y(n)
+
+ return ppset->prcs[0].pfnGetNext (ppset->prcs[0].pdata, x);
+ }
+
+ prc_t *pprc;
+ int count = ppset->cprcs;
+
+ if ( ppset->type == PSET_LINEAR )
+ {
+ int y = x;
+
+ // w0 w1 w2
+ // x(n)--->P(0)-->P(1)-->...P(count-1)--->y(n)
+
+ // w0 w1 w2 w3 w4 w5
+ // x(n)--->P(0)-->P(1)-->P(2)-->P(3)-->P(4)-->y(n)
+
+ // call processors in reverse order, from count to 1
+
+ //for (int i = count; i > 0; i--, pprc--)
+ // w[i] = pprc->pfnGetNext (pprc->pdata, w[i-1]);
+
+ // return w[count];
+
+
+ // point to first processor, update sequentially, no state preserved
+
+ pprc = &ppset->prcs[0];
+
+ switch (count)
+ {
+ default:
+ case 5:
+ y = pprc->pfnGetNext (pprc->pdata, y);
+ pprc++;
+ case 4:
+ y = pprc->pfnGetNext (pprc->pdata, y);
+ pprc++;
+ case 3:
+ y = pprc->pfnGetNext (pprc->pdata, y);
+ pprc++;
+ case 2:
+ y = pprc->pfnGetNext (pprc->pdata, y);
+ pprc++;
+ case 1:
+ case 0:
+ y = pprc->pfnGetNext (pprc->pdata, y);
+ }
+
+ return y;
+ }
+
+ // all other preset types have internal state:
+
+ // initialize 0'th element of state array
+
+ int *w = ppset->w;
+ w[0] = x;
+
+ switch ( ppset->type )
+ {
+ default:
+
+ case PSET_PARALLEL2:
+ { // w0 w1 w3
+ // x(n)--->P(0)-->(+)-->y(n)
+ // ^
+ // w0 w2 |
+ // x(n)--->P(1)-----
+
+ pprc = &ppset->prcs[0];
+
+ w[3] = w[1] + w[2];
+
+ w[1] = pprc->pfnGetNext( pprc->pdata, w[0] );
+ pprc++;
+ w[2] = pprc->pfnGetNext( pprc->pdata, w[0] );
+
+ return w[3];
+ }
+
+ case PSET_PARALLEL4:
+ { // w0 w1 w2 w5
+ // x(n)--->P(0)-->P(1)-->(+)-->y(n)
+ // ^
+ // w0 w3 w4 |
+ // x(n)--->P(2)-->P(3)-----
+
+
+ pprc = &ppset->prcs[0];
+
+ w[5] = w[2] + w[4];
+
+ w[2] = pprc[1].pfnGetNext( pprc[1].pdata, w[1] );
+ w[4] = pprc[3].pfnGetNext( pprc[3].pdata, w[3] );
+
+ w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] );
+ w[3] = pprc[2].pfnGetNext( pprc[2].pdata, w[0] );
+
+ return w[5];
+ }
+
+ case PSET_PARALLEL5:
+ { // w0 w1 w2 w5 w6
+ // x(n)--->P(0)-->P(1)-->(+)--P(4)-->y(n)
+ // ^
+ // w0 w3 w4 |
+ // x(n)--->P(2)-->P(3)-----
+
+ pprc = &ppset->prcs[0];
+
+ w[5] = w[2] + w[4];
+
+ w[2] = pprc[1].pfnGetNext( pprc[1].pdata, w[1] );
+ w[4] = pprc[3].pfnGetNext( pprc[3].pdata, w[3] );
+
+ w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] );
+ w[3] = pprc[2].pfnGetNext( pprc[2].pdata, w[0] );
+
+ return pprc[4].pfnGetNext( pprc[4].pdata, w[5] );
+ }
+
+ case PSET_FEEDBACK:
+ {
+ // w0 w1 w2 w3 w4 w7
+ // x(n)-P(0)--(+)-->P(1)-->P(2)-->---->y(n)
+ // ^ |
+ // | w6 w5 v
+ // -----P(4)<--P(3)--
+
+ pprc = &ppset->prcs[0];
+
+ // start with adders
+
+ w[2] = w[1] + w[6];
+
+ // evaluate in reverse order
+
+ w[6] = pprc[4].pfnGetNext( pprc[4].pdata, w[5] );
+ w[5] = pprc[3].pfnGetNext( pprc[3].pdata, w[4] );
+
+ w[4] = pprc[2].pfnGetNext( pprc[2].pdata, w[3] );
+ w[3] = pprc[1].pfnGetNext( pprc[1].pdata, w[2] );
+ w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] );
+
+ return w[4];
+ }
+ case PSET_FEEDBACK3:
+ {
+ // w0 w1 w2
+ // x(n)---(+)-->P(0)--------->y(n)
+ // ^ |
+ // | w4 w3 v
+ // -----P(2)<--P(1)--
+
+ pprc = &ppset->prcs[0];
+
+ // start with adders
+
+ w[1] = w[0] + w[4];
+
+ // evaluate in reverse order
+
+ w[4] = pprc[2].pfnGetNext( pprc[2].pdata, w[3] );
+ w[3] = pprc[1].pfnGetNext( pprc[1].pdata, w[2] );
+ w[2] = pprc[0].pfnGetNext( pprc[0].pdata, w[1] );
+
+ return w[2];
+ }
+ case PSET_FEEDBACK4:
+ {
+ // w0 w1 w2 w5
+ // x(n)---(+)-->P(0)-------->P(3)--->y(n)
+ // ^ |
+ // | w4 w3 v
+ // ---P(2)<--P(1)--
+
+ pprc = &ppset->prcs[0];
+
+ // start with adders
+
+ w[1] = w[0] + w[4];
+
+ // evaluate in reverse order
+
+ w[5] = pprc[3].pfnGetNext( pprc[3].pdata, w[2] );
+ w[4] = pprc[2].pfnGetNext( pprc[2].pdata, w[3] );
+ w[3] = pprc[1].pfnGetNext( pprc[1].pdata, w[2] );
+ w[2] = pprc[0].pfnGetNext( pprc[0].pdata, w[1] );
+
+ return w[2];
+ }
+ case PSET_MOD:
+ {
+ // w0 w1 w3 w4
+ // x(n)------>P(1)--P(2)--P(3)--->y(n)
+ // w0 w2 ^
+ // x(n)------>P(0)....:
+
+ pprc = &ppset->prcs[0];
+
+ w[4] = pprc[3].pfnGetNext( pprc[3].pdata, w[3] );
+
+ w[3] = pprc[2].pfnGetNext( pprc[2].pdata, w[1] );
+
+ // modulate processor 2
+
+ pprc[2].pfnMod( pprc[2].pdata, ((float)w[2] / (float)PMAX));
+
+ // get modulator output
+
+ w[2] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] );
+
+ w[1] = pprc[1].pfnGetNext( pprc[1].pdata, w[0] );
+
+ return w[4];
+ }
+ case PSET_MOD2:
+ {
+ // w0 w2
+ // x(n)---------P(1)-->y(n)
+ // w0 w1 ^
+ // x(n)-->P(0)....:
+
+ pprc = &ppset->prcs[0];
+
+ // modulate processor 1
+
+ pprc[1].pfnMod( pprc[1].pdata, ((float)w[1] / (float)PMAX));
+
+ // get modulator output
+
+ w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] );
+
+ w[2] = pprc[1].pfnGetNext( pprc[1].pdata, w[0] );
+
+ return w[2];
+
+ }
+ case PSET_MOD3:
+ {
+ // w0 w2 w3
+ // x(n)----------P(1)-->P(2)-->y(n)
+ // w0 w1 ^
+ // x(n)-->P(0).....:
+
+ pprc = &ppset->prcs[0];
+
+ w[3] = pprc[2].pfnGetNext( pprc[2].pdata, w[2] );
+
+ // modulate processor 1
+
+ pprc[1].pfnMod( pprc[1].pdata, ((float)w[1] / (float)PMAX));
+
+ // get modulator output
+
+ w[1] = pprc[0].pfnGetNext( pprc[0].pdata, w[0] );
+
+ w[2] = pprc[1].pfnGetNext( pprc[1].pdata, w[0] );
+
+ return w[2];
+ }
+ }
+}
+
+
+/////////////
+// DSP system
+/////////////
+
+// Main interface
+
+// Whenever the preset # changes on any of these processors, the old processor is faded out, new is faded in.
+// dsp_chan is optionally set when a sound is played - a preset is sent with the start_static/dynamic sound.
+//
+// sound1---->dsp_chan--> -------------(+)---->dsp_water--->dsp_player--->out
+// sound2---->dsp_chan--> | |
+// sound3---------------> ----dsp_room---
+// | |
+// --dsp_indirect-
+
+// dsp_room - set this cvar to a preset # to change the room dsp. room fx are more prevalent farther from player.
+// use: when player moves into a new room, all sounds played in room take on its reverberant character
+// dsp_water - set this cvar (once) to a preset # for serial underwater sound.
+// use: when player goes under water, all sounds pass through this dsp (such as low pass filter)
+// dsp_player - set this cvar to a preset # to cause all sounds to run through the effect (serial, in-line).
+// use: player is deafened, player fires special weapon, player is hit by special weapon.
+// dsp_facingaway- set this cvar to a preset # appropriate for sounds which are played facing away from player (weapon,voice)
+//
+// dsp_spatial - set by system to create modulated spatial delays for left/right/front/back ears - delay value
+// modulates by distance to nearest l/r surface in world
+
+// Dsp presets
+
+
+ConVar dsp_room ("dsp_room", "0", FCVAR_DEMO ); // room dsp preset - sounds more distant from player (1ch)
+ConVar dsp_water ("dsp_water", "14", FCVAR_DEMO ); // "14" underwater dsp preset - sound when underwater (1-2ch)
+ConVar dsp_player ("dsp_player", "0", FCVAR_DEMO | FCVAR_SERVER_CAN_EXECUTE ); // dsp on player - sound when player hit by special device (1-2ch)
+ConVar dsp_facingaway ("dsp_facingaway", "0", FCVAR_DEMO ); // "30" sounds that face away from player (weapons, voice) (1-4ch)
+ConVar dsp_speaker ("dsp_speaker", "50", FCVAR_DEMO ); // "50" small distorted speaker sound (1ch)
+ConVar dsp_spatial ("dsp_spatial", "40", FCVAR_DEMO ); // spatial delays for l/r front/rear ears
+ConVar dsp_automatic ("dsp_automatic", "0", FCVAR_DEMO ); // automatic room type detection. if non zero, replaces dsp_room
+
+int ipset_room_prev;
+int ipset_water_prev;
+int ipset_player_prev;
+int ipset_facingaway_prev;
+int ipset_speaker_prev;
+int ipset_spatial_prev;
+int ipset_automatic_prev;
+
+// legacy room_type support
+
+ConVar dsp_room_type ( "room_type", "0", FCVAR_DEMO );
+int ipset_room_typeprev;
+
+
+// DSP processors
+
+int idsp_room;
+int idsp_water;
+int idsp_player;
+int idsp_facingaway;
+int idsp_speaker;
+int idsp_spatial;
+int idsp_automatic;
+
+ConVar dsp_off ("dsp_off", "0", FCVAR_CHEAT | FCVAR_ALLOWED_IN_COMPETITIVE ); // set to 1 to disable all dsp processing
+ConVar dsp_slow_cpu ("dsp_slow_cpu", "0", FCVAR_ARCHIVE|FCVAR_DEMO ); // set to 1 if cpu bound - ie: does not process dsp_room fx
+ConVar snd_profile ("snd_profile", "0", FCVAR_DEMO ); // 1 - profile dsp, 2 - mix, 3 - load sound, 4 - all sound
+ConVar dsp_volume ("dsp_volume", "1.0", FCVAR_ARCHIVE|FCVAR_DEMO ); // 0.0 - 2.0; master dsp volume control
+ConVar dsp_vol_5ch ("dsp_vol_5ch", "0.5", FCVAR_DEMO ); // 0.0 - 1.0; attenuate master dsp volume for 5ch surround
+ConVar dsp_vol_4ch ("dsp_vol_4ch", "0.5", FCVAR_DEMO ); // 0.0 - 1.0; attenuate master dsp volume for 4ch surround
+ConVar dsp_vol_2ch ("dsp_vol_2ch", "1.0", FCVAR_DEMO ); // 0.0 - 1.0; attenuate master dsp volume for 2ch surround
+
+ConVar dsp_enhance_stereo("dsp_enhance_stereo", "0", FCVAR_ARCHIVE ); // 1) use dsp_spatial delays on all reverb channels
+
+// DSP preset executor
+
+#define CDSPS 32 // max number dsp executors active
+#define DSPCHANMAX 5 // max number of channels dsp can process (allocs a separte processor for each chan)
+
+struct dsp_t
+{
+ bool fused;
+ int cchan; // 1-5 channels, ie: mono, FrontLeft, FrontRight, RearLeft, RearRight, FrontCenter
+
+ pset_t *ppset[DSPCHANMAX]; // current preset (1-5 channels)
+ int ipset; // current ipreset
+
+ pset_t *ppsetprev[DSPCHANMAX]; // previous preset (1-5 channels)
+ int ipsetprev; // previous ipreset
+
+ float xfade; // crossfade time between previous preset and new
+ float xfade_default; // default xfade value, set in DSP_Alloc
+ bool bexpfade; // true if exponential crossfade
+
+ int ipsetsav_oneshot; // previous preset before one-shot preset was set
+
+ rmp_t xramp; // crossfade ramp
+};
+
+dsp_t dsps[CDSPS];
+
+void DSP_Init( int idsp )
+{
+ dsp_t *pdsp;
+
+ Assert( idsp < CDSPS );
+
+ if (idsp < 0 || idsp >= CDSPS)
+ return;
+
+ pdsp = &dsps[idsp];
+
+ Q_memset( pdsp, 0, sizeof (dsp_t) );
+}
+
+void DSP_Free( int idsp )
+{
+ dsp_t *pdsp;
+
+ Assert( idsp < CDSPS );
+
+ if (idsp < 0 || idsp >= CDSPS)
+ return;
+
+ pdsp = &dsps[idsp];
+
+ for (int i = 0; i < pdsp->cchan; i++)
+ {
+ if ( pdsp->ppset[i] )
+ PSET_Free( pdsp->ppset[i] );
+
+ if ( pdsp->ppsetprev[i] )
+ PSET_Free( pdsp->ppsetprev[i] );
+ }
+
+ Q_memset( pdsp, 0, sizeof (dsp_t) );
+}
+
+// Init all dsp processors - called once, during engine startup
+
+void DSP_InitAll ( bool bLoadPresetFile )
+{
+ // only load template file on engine startup
+
+ if ( bLoadPresetFile )
+ DSP_LoadPresetFile();
+
+ // order is important, don't rearange.
+
+ FLT_InitAll();
+ DLY_InitAll();
+ RVA_InitAll();
+ LFOWAV_InitAll();
+ LFO_InitAll();
+
+ CRS_InitAll();
+ PTC_InitAll();
+ ENV_InitAll();
+ EFO_InitAll();
+ MDY_InitAll();
+ AMP_InitAll();
+
+ PSET_InitAll();
+
+ for (int idsp = 0; idsp < CDSPS; idsp++)
+ DSP_Init( idsp );
+}
+
+// free all resources associated with dsp - called once, during engine shutdown
+
+void DSP_FreeAll (void)
+{
+ // order is important, don't rearange.
+
+ for (int idsp = 0; idsp < CDSPS; idsp++)
+ DSP_Free( idsp );
+
+ AMP_FreeAll();
+ MDY_FreeAll();
+ EFO_FreeAll();
+ ENV_FreeAll();
+ PTC_FreeAll();
+ CRS_FreeAll();
+
+ LFO_FreeAll();
+ LFOWAV_FreeAll();
+ RVA_FreeAll();
+ DLY_FreeAll();
+ FLT_FreeAll();
+}
+
+
+// allocate a new dsp processor chain, kill the old processor. Called during dsp init only.
+// ipset is new preset
+// xfade is crossfade time when switching between presets (milliseconds)
+// cchan is how many simultaneous preset channels to allocate (1-4)
+// return index to new dsp
+
+int DSP_Alloc( int ipset, float xfade, int cchan )
+{
+ dsp_t *pdsp;
+ int i;
+ int idsp;
+ int cchans = clamp( cchan, 1, DSPCHANMAX);
+
+ // find free slot
+
+ for ( idsp = 0; idsp < CDSPS; idsp++ )
+ {
+ if ( !dsps[idsp].fused )
+ break;
+ }
+
+ if ( idsp >= CDSPS )
+ return -1;
+
+ pdsp = &dsps[idsp];
+
+ DSP_Init ( idsp );
+
+ pdsp->fused = true;
+
+ pdsp->cchan = cchans;
+
+ // allocate a preset processor for each channel
+
+ pdsp->ipset = ipset;
+ pdsp->ipsetprev = 0;
+ pdsp->ipsetsav_oneshot = 0;
+
+ for (i = 0; i < pdsp->cchan; i++)
+ {
+ pdsp->ppset[i] = PSET_Alloc ( ipset );
+ pdsp->ppsetprev[i] = NULL;
+ }
+
+ // set up crossfade time in seconds
+
+ pdsp->xfade = xfade / 1000.0;
+ pdsp->xfade_default = pdsp->xfade;
+
+ RMP_SetEnd(&pdsp->xramp);
+
+ return idsp;
+}
+
+// call modulation function of specified processor within dsp preset
+
+// idsp - dsp preset
+// channel - channel 1-5 (l,r,rl,rr,fc)
+// iproc - which processor to change (normally 0)
+// value - new parameter value for processor
+
+// NOTE: routine returns with no result or error if any parameter is invalid.
+
+void DSP_ChangePresetValue( int idsp, int channel, int iproc, float value )
+{
+
+ dsp_t *pdsp;
+ pset_t *ppset; // preset
+ prc_Mod_t pfnMod; // modulation function
+
+ if (idsp < 0 || idsp >= CDSPS)
+ return;
+
+ if (channel >= DSPCHANMAX)
+ return;
+
+ if (iproc >= CPSET_PRCS)
+ return;
+
+ // get ptr to processor preset
+
+ pdsp = &dsps[idsp];
+
+ // assert that this dsp processor has enough separate channels
+
+ Assert(channel <= pdsp->cchan);
+
+ ppset = pdsp->ppset[channel];
+
+ if (!ppset)
+ return;
+
+ // get ptr to modulation function
+
+ pfnMod = ppset->prcs[iproc].pfnMod;
+
+ if (!pfnMod)
+ return;
+
+ // call modulation function with new value
+
+ pfnMod (ppset->prcs[iproc].pdata, value);
+}
+
+
+#define DSP_AUTOMATIC 1 // corresponds to Generic preset
+
+// if dsp_room == DSP_AUTOMATIC, then use dsp_automatic value for dsp
+// any subsequent reset of dsp_room will disable automatic room detection.
+
+// return true if automatic room detection is enabled
+
+bool DSP_CheckDspAutoEnabled( void )
+{
+ return (dsp_room.GetInt() == DSP_AUTOMATIC);
+}
+
+// set dsp_automatic preset, used in place of dsp_room when automatic room detection enabled
+
+void DSP_SetDspAuto( int dsp_preset )
+{
+ // set dsp_preset into dsp_automatic
+
+ dsp_automatic.SetValue( dsp_preset );
+}
+
+// wrapper on dsp_room GetInt so that dsp_automatic can override
+
+int dsp_room_GetInt ( void )
+{
+ // if dsp_automatic is not enabled, get room
+
+ if (! DSP_CheckDspAutoEnabled())
+ return dsp_room.GetInt();
+
+ // automatic room detection is on, get dsp_automatic instead of dsp_room
+
+ return dsp_automatic.GetInt();
+}
+
+// wrapper on idsp_room preset so that idsp_automatic can override
+
+int Get_idsp_room ( void )
+{
+
+ // if dsp_automatic is not enabled, get room
+
+ if ( !DSP_CheckDspAutoEnabled())
+ return idsp_room;
+
+ // automatic room detection is on, return dsp_automatic preset instead of dsp_room preset
+
+ return idsp_automatic;
+}
+
+
+// free previous preset if not 0
+
+inline void DSP_FreePrevPreset( dsp_t *pdsp )
+{
+ // free previous presets if non-null - ie: rapid change of preset just kills old without xfade
+
+ if ( pdsp->ipsetprev )
+ {
+ for (int i = 0; i < pdsp->cchan; i++)
+ {
+ if ( pdsp->ppsetprev[i] )
+ {
+ PSET_Free( pdsp->ppsetprev[i] );
+ pdsp->ppsetprev[i] = NULL;
+ }
+ }
+
+ pdsp->ipsetprev = 0;
+ }
+
+}
+
+extern ConVar dsp_mix_min;
+extern ConVar dsp_mix_max;
+extern ConVar dsp_db_min;
+extern ConVar dsp_db_mixdrop;
+
+// alloc new preset if different from current
+// xfade from prev to new preset
+// free previous preset, copy current into previous, set up xfade from previous to new
+
+void DSP_SetPreset( int idsp, int ipsetnew)
+{
+ dsp_t *pdsp;
+ pset_t *ppsetnew[DSPCHANMAX];
+
+ Assert (idsp >= 0 && idsp < CDSPS);
+
+ pdsp = &dsps[idsp];
+
+ // validate new preset range
+
+ if ( ipsetnew >= g_cpsettemplates || ipsetnew < 0 )
+ return;
+
+ // ignore if new preset is same as current preset
+
+ if ( ipsetnew == pdsp->ipset )
+ return;
+
+ // alloc new presets (each channel is a duplicate preset)
+
+ Assert (pdsp->cchan <= DSPCHANMAX);
+
+ for (int i = 0; i < pdsp->cchan; i++)
+ {
+ ppsetnew[i] = PSET_Alloc ( ipsetnew );
+ if ( !ppsetnew[i] )
+ {
+ DevMsg("WARNING: DSP preset failed to allocate.\n");
+ return;
+ }
+ }
+
+ Assert (pdsp);
+
+ // free PREVIOUS previous preset if not 0
+
+ DSP_FreePrevPreset( pdsp );
+
+ for (int i = 0; i < pdsp->cchan; i++)
+ {
+ // current becomes previous
+
+ pdsp->ppsetprev[i] = pdsp->ppset[i];
+
+ // new becomes current
+
+ pdsp->ppset[i] = ppsetnew[i];
+ }
+
+ pdsp->ipsetprev = pdsp->ipset;
+ pdsp->ipset = ipsetnew;
+
+ if ( idsp == idsp_room || idsp == idsp_automatic )
+ {
+ // set up new dsp mix min & max, db_min & db_drop params so that new channels get new mix values
+
+ // NOTE: only new sounds will get the new mix min/max values set in their dspmix param
+ // NOTE: so - no crossfade is needed between dspmix and dspmix prev, but this also means
+ // NOTE: that currently playing ambients will not see changes to dspmix at all.
+
+ float mix_min = pdsp->ppset[0]->mix_min;
+ float mix_max = pdsp->ppset[0]->mix_max;
+ float db_min = pdsp->ppset[0]->db_min;
+ float db_mixdrop = pdsp->ppset[0]->db_mixdrop;
+
+ dsp_mix_min.SetValue( mix_min );
+ dsp_mix_max.SetValue( mix_max );
+ dsp_db_min.SetValue( db_min );
+ dsp_db_mixdrop.SetValue( db_mixdrop );
+ }
+
+ RMP_SetEnd( &pdsp->xramp );
+
+ // make sure previous dsp preset has data
+
+ Assert (pdsp->ppsetprev[0]);
+
+ // shouldn't be crossfading if current dsp preset == previous dsp preset
+
+ Assert (pdsp->ipset != pdsp->ipsetprev);
+
+ // if new preset is one-shot, keep previous preset to restore when one-shot times out
+ // but: don't restore previous one-shots!
+
+ pdsp->ipsetsav_oneshot = 0;
+
+ if ( PSET_IsOneShot( pdsp->ppset[0] ) && !PSET_IsOneShot( pdsp->ppsetprev[0] ) )
+ pdsp->ipsetsav_oneshot = pdsp->ipsetprev;
+
+ // get new xfade time from previous preset (ie: fade out time). if 0 use default. if < 0, use exponential xfade
+
+ if ( fabs(pdsp->ppsetprev[0]->fade) > 0.0 )
+ {
+ pdsp->xfade = fabs(pdsp->ppsetprev[0]->fade);
+ pdsp->bexpfade = pdsp->ppsetprev[0]->fade < 0 ? 1 : 0;
+ }
+ else
+ {
+ // no previous preset - use defauts, set in DSP_Alloc
+
+ pdsp->xfade = pdsp->xfade_default;
+ pdsp->bexpfade = false;
+ }
+
+ RMP_Init( &(pdsp->xramp), pdsp->xfade, 0, PMAX, false );
+}
+
+
+#define DSP_AUTO_BASE 60 // presets 60-100 in g_psettemplates are reserved as autocreated presets
+#define DSP_CAUTO_PRESETS 40 // must be same as DAS_CNODES!!!
+
+// construct a dsp preset based on provided parameters,
+// preset is constructed within g_psettemplates[] array.
+// return preset #
+
+// parameter batch
+
+struct auto_params_t
+{
+ // passed in params
+
+ bool bskyabove; // true if sky is mostly above player
+ int width; // max width of room in inches
+ int length; // max length of room in inches (length always > width)
+ int height; // max height of room in inches
+ float fdiffusion; // diffusion of room 0..1.0
+ float freflectivity; // average reflectivity of all surfaces in room 0..1.0
+ float surface_refl[6]; // reflectivity for left,right,front,back,ceiling,floor surfaces 0.0 for open surface (sky or no hit)
+
+ // derived params
+
+ int shape; // ADSP_ROOM, etc 0...4
+ int size; // ADSP_SIZE_SMALL, etc 0...3
+ int len; // ADSP_LENGTH_SHORT, etc 0...3
+ int wid; // ADSP_WIDTH_NARROW, etc 0...3
+ int ht; // ADSP_HEIGHT_LOW, etc 0...3
+ int reflectivity; // ADSP_DULL, etc 0..3
+ int diffusion; // ADSP_EMPTY, etc 0...3
+};
+
+
+// select type 1..5 based on params
+ // 1:simple reverb
+ // 2:diffusor + reverb
+ // 3:diffusor + delay + reverb
+ // 4:simple delay
+ // 5:diffusor + delay
+
+#define AROOM_SMALL (10.0 * 12.0) // small room
+#define AROOM_MEDIUM (20.0 * 12.0) // medium room
+#define AROOM_LARGE (40.0 * 12.0) // large room
+#define AROOM_HUGE (100.0 * 12.0) // huge room
+#define AROOM_GIGANTIC (200.0 * 12.0) // gigantic room
+
+#define AROOM_DUCT_WIDTH (4.0 * 12.0) // max width for duct
+#define AROOM_DUCT_HEIGHT (6.0 * 12.0)
+
+#define AROOM_HALL_WIDTH (8.0 * 12.0) // max width for hall
+#define AROOM_HALL_HEIGHT (16.0 * 12.0) // max height for hall
+
+#define AROOM_TUNNEL_WIDTH (20.0 * 12.0) // max width for tunnel
+#define AROOM_TUNNEL_HEIGHT (30.0 * 12.0) // max height for tunnel
+
+#define AROOM_STREET_WIDTH (12.0 * 12.0) // min width for street
+
+#define AROOM_SHORT_LENGTH (12.0 * 12.0) // max length for short hall
+#define AROOM_MEDIUM_LENGTH (24.0 * 12.0) // min length for medium hall
+#define AROOM_LONG_LENGTH (48.0 * 12.0) // min length for long hall
+#define AROOM_VLONG_LENGTH (96.0 * 12.0) // min length for very long hall
+#define AROOM_XLONG_LENGTH (192.0 * 12.0) // min length for huge hall
+
+#define AROOM_LOW_HEIGHT (4.0 * 12.0) // short ceiling
+#define AROOM_MEDIUM_HEIGHT (128) // medium ceiling
+#define AROOM_TALL_HEIGHT (18.0 * 12.0) // tall ceiling
+#define AROOM_VTALL_HEIGHT (32.0 * 12.0) // very tall ceiling
+#define AROOM_XTALL_HEIGHT (64.0 * 12.0) // huge tall ceiling
+
+#define AROOM_NARROW_WIDTH (6.0 * 12.0) // narrow width
+#define AROOM_MEDIUM_WIDTH (12.0 * 12.0) // medium width
+#define AROOM_WIDE_WIDTH (24.0 * 12.0) // wide width
+#define AROOM_VWIDE_WIDTH (48.0 * 12.0) // very wide
+#define AROOM_XWIDE_WIDTH (96.0 * 12.0) // huge width
+
+#define BETWEEN(a,b,c) ( ((a) > (b)) && ((a) <= (c)) )
+
+#define ADSP_IsShaft(pa) (pa->height > (3.0 * pa->length))
+#define ADSP_IsRoom(pa) (pa->length <= (2.5 * pa->width))
+#define ADSP_IsHall(pa) ((pa->length > (2.5 * pa->width)) && (BETWEEN(pa->width, AROOM_DUCT_WIDTH, AROOM_HALL_WIDTH)))
+#define ADSP_IsTunnel(pa) ((pa->length > (4.0 * pa->width)) && (pa->width > AROOM_HALL_WIDTH))
+#define ADSP_IsDuct(pa) ((pa->length > (4.0 * pa->width)) && (pa->width <= AROOM_DUCT_WIDTH))
+
+#define ADSP_IsCourtyard(pa) (pa->length <= (2.5 * pa->width))
+#define ADSP_IsAlley(pa) ((pa->length > (2.5 * pa->width)) && (pa->width <= AROOM_STREET_WIDTH))
+#define ADSP_IsStreet(pa) ((pa->length > (2.5 * pa->width)) && (pa->width > AROOM_STREET_WIDTH))
+
+#define ADSP_IsSmallRoom(pa) (pa->length <= AROOM_SMALL)
+#define ADSP_IsMediumRoom(pa) ((BETWEEN(pa->length, AROOM_SMALL, AROOM_MEDIUM)) ) // && (BETWEEN(pa->width, AROOM_SMALL, AROOM_MEDIUM)))
+#define ADSP_IsLargeRoom(pa) (BETWEEN(pa->length, AROOM_MEDIUM, AROOM_LARGE) ) // && BETWEEN(pa->width, AROOM_MEDIUM, AROOM_LARGE))
+#define ADSP_IsHugeRoom(pa) (BETWEEN(pa->length, AROOM_LARGE, AROOM_HUGE) ) // && BETWEEN(pa->width, AROOM_LARGE, AROOM_HUGE))
+#define ADSP_IsGiganticRoom(pa) ((pa->length > AROOM_HUGE) ) // && (pa->width > AROOM_HUGE))
+
+#define ADSP_IsShortLength(pa) (pa->length <= AROOM_SHORT_LENGTH)
+#define ADSP_IsMediumLength(pa) (BETWEEN(pa->length, AROOM_SHORT_LENGTH, AROOM_MEDIUM_LENGTH))
+#define ADSP_IsLongLength(pa) (BETWEEN(pa->length, AROOM_MEDIUM_LENGTH, AROOM_LONG_LENGTH))
+#define ADSP_IsVLongLength(pa) (BETWEEN(pa->length, AROOM_LONG_LENGTH, AROOM_VLONG_LENGTH))
+#define ADSP_IsXLongLength(pa) (pa->length > AROOM_VLONG_LENGTH)
+
+#define ADSP_IsLowHeight(pa) (pa->height <= AROOM_LOW_HEIGHT)
+#define ADSP_IsMediumHeight(pa) (BETWEEN(pa->height, AROOM_LOW_HEIGHT, AROOM_MEDIUM_HEIGHT))
+#define ADSP_IsTallHeight(pa) (BETWEEN(pa->height, AROOM_MEDIUM_HEIGHT, AROOM_TALL_HEIGHT))
+#define ADSP_IsVTallHeight(pa) (BETWEEN(pa->height, AROOM_TALL_HEIGHT, AROOM_VTALL_HEIGHT))
+#define ADSP_IsXTallHeight(pa) (pa->height > AROOM_VTALL_HEIGHT)
+
+#define ADSP_IsNarrowWidth(pa) (pa->width <= AROOM_NARROW_WIDTH)
+#define ADSP_IsMediumWidth(pa) (BETWEEN(pa->width, AROOM_NARROW_WIDTH, AROOM_MEDIUM_WIDTH))
+#define ADSP_IsWideWidth(pa) (BETWEEN(pa->width, AROOM_MEDIUM_WIDTH, AROOM_WIDE_WIDTH))
+#define ADSP_IsVWideWidth(pa) (BETWEEN(pa->width, AROOM_WIDE_WIDTH, AROOM_VWIDE_WIDTH))
+#define ADSP_IsXWideWidth(pa) (pa->width > AROOM_VWIDE_WIDTH)
+
+#define ADSP_IsInside(pa) (!(pa->bskyabove))
+
+// room diffusion
+
+#define ADSP_EMPTY 0
+#define ADSP_SPARSE 1
+#define ADSP_CLUTTERED 2
+#define ADSP_FULL 3
+#define ADSP_DIFFUSION_MAX 4
+
+#define AROOM_DIF_EMPTY 0.01 // 1% of space by volume is other objects
+#define AROOM_DIF_SPARSE 0.1 // 10% "
+#define AROOM_DIF_CLUTTERED 0.3 // 30% "
+#define AROOM_DIF_FULL 0.5 // 50% "
+
+#define ADSP_IsEmpty(pa) (pa->fdiffusion <= AROOM_DIF_EMPTY)
+#define ADSP_IsSparse(pa) (BETWEEN(pa->fdiffusion, AROOM_DIF_EMPTY, AROOM_DIF_SPARSE))
+#define ADSP_IsCluttered(pa) (BETWEEN(pa->fdiffusion, AROOM_DIF_SPARSE, AROOM_DIF_CLUTTERED))
+#define ADSP_IsFull(pa) (pa->fdiffusion > AROOM_DIF_CLUTTERED)
+
+#define ADSP_IsDiffuse(pa) (pa->diffusion > ADSP_SPARSE)
+
+// room acoustic reflectivity
+
+ // tile 0.3 * 3.3 = 0.99
+ // metal 0.25 * 3.3 = 0.83
+ // concrete,rock,brick,glass,gravel 0.2 * 3.3 = 0.66
+ // metal panel/vent, wood, water 0.1 * 3.3 = 0.33
+ // carpet,sand,snow,dirt 0.01 * 3.3 = 0.03
+
+#define ADSP_DULL 0
+#define ADSP_FLAT 1
+#define ADSP_REFLECTIVE 2
+#define ADSP_BRIGHT 3
+#define ADSP_REFLECTIVITY_MAX 4
+
+#define AROOM_REF_DULL 0.04
+#define AROOM_REF_FLAT 0.50
+#define AROOM_REF_REFLECTIVE 0.80
+#define AROOM_REF_BRIGHT 0.99
+
+#define ADSP_IsDull(pa) (pa->freflectivity <= AROOM_REF_DULL)
+#define ADSP_IsFlat(pa) (BETWEEN(pa->freflectivity, AROOM_REF_DULL, AROOM_REF_FLAT))
+#define ADSP_IsReflective(pa) (BETWEEN(pa->freflectivity, AROOM_REF_FLAT, AROOM_REF_REFLECTIVE))
+#define ADSP_IsBright(pa) (pa->freflectivity > AROOM_REF_REFLECTIVE)
+
+#define ADSP_IsRefl(pa) (pa->reflectivity > ADSP_FLAT)
+
+// room shapes
+
+#define ADSP_ROOM 0
+#define ADSP_DUCT 1
+#define ADSP_HALL 2
+#define ADSP_TUNNEL 3
+#define ADSP_STREET 4
+#define ADSP_ALLEY 5
+#define ADSP_COURTYARD 6
+#define ADSP_OPEN_SPACE 7 // NOTE: 7..10 must remain in order !!!
+#define ADSP_OPEN_WALL 8
+#define ADSP_OPEN_STREET 9
+#define ADSP_OPEN_COURTYARD 10
+
+// room sizes
+
+#define ADSP_SIZE_SMALL 0 // NOTE: must remain 0..4!!!
+#define ADSP_SIZE_MEDIUM 1
+#define ADSP_SIZE_LARGE 2
+#define ADSP_SIZE_HUGE 3
+#define ADSP_SIZE_GIGANTIC 4
+#define ADSP_SIZE_MAX 5
+
+#define ADSP_LENGTH_SHORT 0
+#define ADSP_LENGTH_MEDIUM 1
+#define ADSP_LENGTH_LONG 2
+#define ADSP_LENGTH_VLONG 3
+#define ADSP_LENGTH_XLONG 4
+#define ADSP_LENGTH_MAX 5
+
+#define ADSP_WIDTH_NARROW 0
+#define ADSP_WIDTH_MEDIUM 1
+#define ADSP_WIDTH_WIDE 2
+#define ADSP_WIDTH_VWIDE 3
+#define ADSP_WIDTH_XWIDE 4
+#define ADSP_WIDTH_MAX 5
+
+#define ADSP_HEIGHT_LOW 0
+#define ADSP_HEIGTH_MEDIUM 1
+#define ADSP_HEIGHT_TALL 2
+#define ADSP_HEIGHT_VTALL 3
+#define ADSP_HEIGHT_XTALL 4
+#define ADSP_HEIGHT_MAX 5
+
+
+// convert numeric size params to #defined size params
+
+void ADSP_GetSize( auto_params_t *pa )
+{
+ pa->size = ((ADSP_IsSmallRoom(pa) ? 1 : 0) * ADSP_SIZE_SMALL) +
+ ((ADSP_IsMediumRoom(pa) ? 1 : 0) * ADSP_SIZE_MEDIUM) +
+ ((ADSP_IsLargeRoom(pa) ? 1 : 0) * ADSP_SIZE_LARGE) +
+ ((ADSP_IsHugeRoom(pa) ? 1 : 0) * ADSP_SIZE_HUGE) +
+ ((ADSP_IsGiganticRoom(pa) ? 1 : 0) * ADSP_SIZE_GIGANTIC);
+
+ pa->len = ((ADSP_IsShortLength(pa) ? 1 : 0) * ADSP_LENGTH_SHORT) +
+ ((ADSP_IsMediumLength(pa) ? 1 : 0) * ADSP_LENGTH_MEDIUM) +
+ ((ADSP_IsLongLength(pa) ? 1 : 0) * ADSP_LENGTH_LONG) +
+ ((ADSP_IsVLongLength(pa) ? 1 : 0) * ADSP_LENGTH_VLONG) +
+ ((ADSP_IsXLongLength(pa) ? 1 : 0) * ADSP_LENGTH_XLONG);
+
+ pa->wid = ((ADSP_IsNarrowWidth(pa) ? 1 : 0) * ADSP_WIDTH_NARROW) +
+ ((ADSP_IsMediumWidth(pa) ? 1 : 0) * ADSP_WIDTH_MEDIUM) +
+ ((ADSP_IsWideWidth(pa) ? 1 : 0) * ADSP_WIDTH_WIDE) +
+ ((ADSP_IsVWideWidth(pa) ? 1 : 0) * ADSP_WIDTH_VWIDE) +
+ ((ADSP_IsXWideWidth(pa) ? 1 : 0) * ADSP_WIDTH_XWIDE);
+
+ pa->ht = ((ADSP_IsLowHeight(pa) ? 1 : 0) * ADSP_HEIGHT_LOW) +
+ ((ADSP_IsMediumHeight(pa) ? 1 : 0) * ADSP_HEIGTH_MEDIUM) +
+ ((ADSP_IsTallHeight(pa) ? 1 : 0) * ADSP_HEIGHT_TALL) +
+ ((ADSP_IsVTallHeight(pa) ? 1 : 0) * ADSP_HEIGHT_VTALL) +
+ ((ADSP_IsXTallHeight(pa) ? 1 : 0) * ADSP_HEIGHT_XTALL);
+
+ pa->reflectivity =
+ ((ADSP_IsDull(pa) ? 1 : 0) * ADSP_DULL) +
+ ((ADSP_IsFlat(pa) ? 1 : 0) * ADSP_FLAT) +
+ ((ADSP_IsReflective(pa) ? 1 : 0) * ADSP_REFLECTIVE) +
+ ((ADSP_IsBright(pa) ? 1 : 0) * ADSP_BRIGHT);
+
+ pa->diffusion =
+ ((ADSP_IsEmpty(pa) ? 1 : 0) * ADSP_EMPTY) +
+ ((ADSP_IsSparse(pa) ? 1 : 0) * ADSP_SPARSE) +
+ ((ADSP_IsCluttered(pa) ? 1 : 0) * ADSP_CLUTTERED) +
+ ((ADSP_IsFull(pa) ? 1 : 0) * ADSP_FULL);
+
+ Assert(pa->size < ADSP_SIZE_MAX);
+ Assert(pa->len < ADSP_LENGTH_MAX);
+ Assert(pa->wid < ADSP_WIDTH_MAX);
+ Assert(pa->ht < ADSP_HEIGHT_MAX);
+ Assert(pa->reflectivity < ADSP_REFLECTIVITY_MAX);
+ Assert(pa->diffusion < ADSP_DIFFUSION_MAX);
+
+ if ( pa->shape != ADSP_COURTYARD && pa->shape != ADSP_OPEN_COURTYARD )
+ {
+ // fix up size for streets, alleys, halls, ducts, tunnelsy
+
+ if (pa->shape == ADSP_STREET || pa->shape == ADSP_ALLEY )
+ pa->size = pa->wid;
+ else
+ pa->size = (pa->len + pa->wid) / 2;
+
+ }
+
+}
+
+void ADSP_GetOutsideSize( auto_params_t *pa )
+{
+ ADSP_GetSize( pa );
+}
+
+// return # of sides that had max length or sky hits (out of 6 sides).
+
+int ADSP_COpenSides( auto_params_t *pa )
+{
+ int count = 0;
+
+ // only look at left,right,front,back walls - ignore floor, ceiling
+
+ for (int i = 0; i < 4; i++)
+ {
+ if (pa->surface_refl[i] == 0.0)
+ count++;
+ }
+
+ return count;
+}
+
+// given auto params, return shape and size of room
+
+void ADSP_GetAutoShape( auto_params_t *pa )
+{
+
+ // INSIDE:
+ // shapes: duct, hall, tunnel, shaft (vertical duct, hall or tunnel)
+ // sizes: short->long, narrow->wide, low->tall
+ // shapes: room
+ // sizes: small->large, low->tall
+
+ // OUTSIDE:
+ // shapes: street, alley
+ // sizes: short->long, narrow->wide
+ // shapes: courtyard
+ // sizes: small->large
+
+ // shapes: open_space, wall, open_street, open_corner, open_courtyard
+ // sizes: open, narrow->wide
+
+ bool bshaft = false;
+ int t;
+
+ if (ADSP_IsInside(pa))
+ {
+ if (ADSP_IsShaft(pa))
+ {
+ // temp swap height and length
+
+ bshaft = true;
+ t = pa->height;
+ pa->height = pa->length;
+ pa->length = t;
+ if (das_debug.GetInt() > 1)
+ DevMsg("VERTICAL SHAFT Detected \n");
+ }
+
+ // get shape
+
+ if (ADSP_IsDuct(pa))
+ {
+ pa->shape = ADSP_DUCT;
+ ADSP_GetSize( pa );
+ if (das_debug.GetInt() > 1)
+ DevMsg("DUCT Detected \n");
+ goto autoshape_exit;
+ }
+
+ if (ADSP_IsHall(pa))
+ {
+ // get size
+ pa->shape = ADSP_HALL;
+ ADSP_GetSize( pa );
+
+ if (das_debug.GetInt() > 1)
+ DevMsg("HALL Detected \n");
+
+ goto autoshape_exit;
+ }
+
+ if (ADSP_IsTunnel(pa))
+ {
+ // get size
+ pa->shape = ADSP_TUNNEL;
+ ADSP_GetSize( pa );
+
+ if (das_debug.GetInt() > 1)
+ DevMsg("TUNNEL Detected \n");
+
+ goto autoshape_exit;
+ }
+
+ // default
+ // (ADSP_IsRoom(pa))
+ {
+ // get size
+ pa->shape = ADSP_ROOM;
+ ADSP_GetSize( pa );
+
+ if (das_debug.GetInt() > 1)
+ DevMsg("ROOM Detected \n");
+
+ goto autoshape_exit;
+ }
+ }
+
+ // outside:
+
+ if (ADSP_COpenSides(pa) > 0) // side hit sky, or side has max length
+ {
+ // get shape - courtyard, street, wall or open space
+ // 10..7
+ pa->shape = ADSP_OPEN_COURTYARD - (ADSP_COpenSides(pa) - 1);
+ ADSP_GetOutsideSize( pa );
+
+ if (das_debug.GetInt() > 1)
+ DevMsg("OPEN SIDED OUTDOOR AREA Detected \n");
+
+ goto autoshape_exit;
+ }
+
+ // all sides closed:
+
+ // get shape - closed street or alley or courtyard
+
+ if (ADSP_IsCourtyard(pa))
+ {
+ pa->shape = ADSP_COURTYARD;
+ ADSP_GetOutsideSize( pa );
+
+ if (das_debug.GetInt() > 1)
+ DevMsg("OUTSIDE COURTYARD Detected \n");
+
+ goto autoshape_exit;
+ }
+
+ if (ADSP_IsAlley(pa))
+ {
+ pa->shape = ADSP_ALLEY;
+ ADSP_GetOutsideSize( pa );
+
+ if (das_debug.GetInt() > 1)
+ DevMsg("OUTSIDE ALLEY Detected \n");
+ goto autoshape_exit;
+ }
+
+ // default to 'street' if sides are closed
+
+ // if (ADSP_IsStreet(pa))
+ {
+ pa->shape = ADSP_STREET;
+ ADSP_GetOutsideSize( pa );
+ if (das_debug.GetInt() > 1)
+ DevMsg("OUTSIDE STREET Detected \n");
+ goto autoshape_exit;
+ }
+
+autoshape_exit:
+
+ // swap height & length if needed
+
+ if (bshaft)
+ {
+ t = pa->height;
+ pa->height = pa->length;
+ pa->length = t;
+ }
+}
+
+int MapReflectivityToDLYCutoff[] =
+{
+ 1000, // DULL
+ 2000, // FLAT
+ 4000, // REFLECTIVE
+ 6000 // BRIGHT
+};
+
+float MapSizeToDLYFeedback[] =
+{
+ 0.9, // 0.6, // SMALL
+ 0.8, // 0.5, // MEDIUM
+ 0.7, // 0.4, // LARGE
+ 0.6, // 0.3, // HUGE
+ 0.5, // 0.2, // GIGANTIC
+};
+
+void ADSP_SetupAutoDelay( prc_t *pprc_dly, auto_params_t *pa )
+{
+ // shapes:
+ // inside: duct, long hall, long tunnel, large room
+ // outside: open courtyard, street wall, space
+ // outside: closed courtyard, alley, street
+
+ // size 0..4
+ // len 0..3
+ // wid 0..3
+ // reflectivity: 0..3
+ // diffusion 0..3
+
+ // dtype: delay type DLY_PLAIN, DLY_LOWPASS, DLY_ALLPASS
+ // delay: delay in milliseconds (room max size in feet)
+ // feedback: feedback 0-1.0
+ // gain: final gain of output stage, 0-1.0
+
+ int size = pa->length * 2.0;
+
+ if (pa->shape == ADSP_ALLEY || pa->shape == ADSP_STREET || pa->shape == ADSP_OPEN_STREET)
+ size = pa->width * 2.0;
+
+ pprc_dly->type = PRC_DLY;
+
+ pprc_dly->prm[dly_idtype] = DLY_LOWPASS; // delay with feedback
+
+ pprc_dly->prm[dly_idelay] = clamp((size / 12.0), 5.0, 500.0);
+
+ pprc_dly->prm[dly_ifeedback] = MapSizeToDLYFeedback[pa->len];
+
+ // reduce gain based on distance reflection travels
+// float g = 1.0 - ( clamp(pprc_dly->prm[dly_idelay], 10.0, 1000.0) / (1000.0 - 10.0) );
+// pprc_dly->prm[dly_igain] = g;
+
+ pprc_dly->prm[dly_iftype] = FLT_LP;
+ if (ADSP_IsInside(pa))
+ pprc_dly->prm[dly_icutoff] = MapReflectivityToDLYCutoff[pa->reflectivity];
+ else
+ pprc_dly->prm[dly_icutoff] = (int)((float)(MapReflectivityToDLYCutoff[pa->reflectivity]) * 0.75);
+
+ pprc_dly->prm[dly_iqwidth] = 0;
+
+ pprc_dly->prm[dly_iquality] = QUA_LO;
+
+ float l = clamp((pa->length * 2.0 / 12.0), 14.0, 500.0);
+ float w = clamp((pa->width * 2.0 / 12.0), 14.0, 500.0);
+
+ // convert to multitap delay
+
+ pprc_dly->prm[dly_idtype] = DLY_LOWPASS_4TAP;
+
+ pprc_dly->prm[dly_idelay] = l;
+ pprc_dly->prm[dly_itap1] = w;
+ pprc_dly->prm[dly_itap2] = l; // max(7, l * 0.7 );
+ pprc_dly->prm[dly_itap3] = l; // max(7, w * 0.7 );
+
+ pprc_dly->prm[dly_igain] = 1.0;
+}
+
+int MapReflectivityToRVACutoff[] =
+{
+ 1000, // DULL
+ 2000, // FLAT
+ 4000, // REFLECTIVE
+ 6000 // BRIGHT
+};
+
+float MapSizeToRVANumDelays[] =
+{
+ 3, // SMALL 3 reverbs
+ 6, // MEDIUM 6 reverbs
+ 6, // LARGE 6 reverbs
+ 9, // HUGE 9 reverbs
+ 12, // GIGANTIC 12 reverbs
+};
+
+float MapSizeToRVAFeedback[] =
+{
+ 0.75, // SMALL
+ 0.8, // MEDIUM
+ 0.9, // LARGE
+ 0.95, // HUGE
+ 0.98, // GIGANTIC
+};
+
+void ADSP_SetupAutoReverb( prc_t *pprc_rva, auto_params_t *pa )
+{
+ // shape: hall, tunnel or room
+ // size 0..4
+ // reflectivity: 0..3
+ // diffusion 0..3
+
+ // size: 0-2.0 scales nominal delay parameters (18 to 47 ms * scale = delay)
+ // numdelays: 0-12 controls # of parallel or series delays
+ // decay: 0-2.0 scales feedback parameters (.7 to .9 * scale/2.0 = feedback)
+ // fparallel: if true, filters are built into delays, otherwise filter output only
+ // fmoddly: if true, all delays are modulating delays
+ float gain = 1.0;
+
+ pprc_rva->type = PRC_RVA;
+
+ pprc_rva->prm[rva_size_max] = 50.0;
+ pprc_rva->prm[rva_size_min] = 30.0;
+
+ if (ADSP_IsRoom(pa))
+ pprc_rva->prm[rva_inumdelays] = MapSizeToRVANumDelays[pa->size];
+ else
+ pprc_rva->prm[rva_inumdelays] = MapSizeToRVANumDelays[pa->len];
+
+ pprc_rva->prm[rva_ifeedback] = 0.9;
+
+ pprc_rva->prm[rva_icutoff] = MapReflectivityToRVACutoff[pa->reflectivity];
+
+ pprc_rva->prm[rva_ifparallel] = 1;
+ pprc_rva->prm[rva_imoddly] = ADSP_IsEmpty(pa) ? 0 : 4;
+ pprc_rva->prm[rva_imodrate] = 3.48;
+
+ pprc_rva->prm[rva_iftaps] = 0; // 0.1 // use extra delay taps to increase density
+
+ pprc_rva->prm[rva_width] = clamp( ((float)(pa->width) / 12.0), 6.0, 500.0); // in feet
+ pprc_rva->prm[rva_depth] = clamp( ((float)(pa->length) / 12.0), 6.0, 500.0);
+ pprc_rva->prm[rva_height] = clamp( ((float)(pa->height) / 12.0), 6.0, 500.0);
+
+ // room
+ pprc_rva->prm[rva_fbwidth] = 0.9; // MapSizeToRVAFeedback[pa->size]; // larger size = more feedback
+ pprc_rva->prm[rva_fbdepth] = 0.9; // MapSizeToRVAFeedback[pa->size];
+ pprc_rva->prm[rva_fbheight] = 0.5; // MapSizeToRVAFeedback[pa->size];
+
+ // feedback is based on size of room:
+
+ if (ADSP_IsInside(pa))
+ {
+ if (pa->shape == ADSP_HALL)
+ {
+ pprc_rva->prm[rva_fbwidth] = 0.7; //MapSizeToRVAFeedback[pa->wid];
+ pprc_rva->prm[rva_fbdepth] = -0.5; //MapSizeToRVAFeedback[pa->len];
+ pprc_rva->prm[rva_fbheight] = 0.3; //MapSizeToRVAFeedback[pa->ht];
+ }
+
+ if (pa->shape == ADSP_TUNNEL)
+ {
+ pprc_rva->prm[rva_fbwidth] = 0.9;
+ pprc_rva->prm[rva_fbdepth] = -0.8; // fixed pre-delay, no feedback
+ pprc_rva->prm[rva_fbheight] = 0.3;
+ }
+ }
+ else
+ {
+ if (pa->shape == ADSP_ALLEY)
+ {
+ pprc_rva->prm[rva_fbwidth] = 0.9;
+ pprc_rva->prm[rva_fbdepth] = -0.8; // fixed pre-delay, no feedback
+ pprc_rva->prm[rva_fbheight] = 0.0;
+ }
+ }
+
+ if (!ADSP_IsInside(pa))
+ pprc_rva->prm[rva_fbheight] = 0.0;
+
+ pprc_rva->prm[rva_igain] = gain;
+}
+
+// diffusor templates for auto create
+
+ // size: 0-1.0 scales all delays (13ms to 41ms * scale = delay)
+ // numdelays: 0-4.0 controls # of series delays
+ // decay: 0-1.0 scales all feedback parameters
+
+// prctype size #dly feedback
+
+#if 0
+#define PRC_DFRA_S {PRC_DFR, {0.5, 2, 0.10}, NULL,NULL,NULL,NULL,NULL} // S room
+#define PRC_DFRA_M {PRC_DFR, {0.75, 2, 0.12}, NULL,NULL,NULL,NULL,NULL} // M room
+#define PRC_DFRA_L {PRC_DFR, {1.0, 3, 0.13}, NULL,NULL,NULL,NULL,NULL} // L room
+#define PRC_DFRA_VL {PRC_DFR, {1.0, 3, 0.15}, NULL,NULL,NULL,NULL,NULL} // VL room
+
+prc_t g_prc_dfr_auto[] = {PRC_DFRA_S, PRC_DFRA_M, PRC_DFRA_L, PRC_DFRA_VL, PRC_DFRA_VL};
+
+//$BUGBUGBUG: I think this should be sizeof(prc_t), not sizeof(pset_t)...
+#define CDFRTEMPLATES (sizeof(g_prc_dfr_auto)/sizeof(pset_t)) // number of diffusor templates
+
+// copy diffusor template from preset list, based on room size
+
+void ADSP_SetupAutoDiffusor( prc_t *pprc_dfr, auto_params_t *pa )
+{
+ int i = clamp(pa->size, 0, (int)CDFRTEMPLATES - 1);
+
+ // copy diffusor preset based on size
+
+ *pprc_dfr = g_prc_dfr_auto[i];
+}
+#endif
+
+// return index to processor given processor type and preset
+// skips N processors of similar type
+// returns -1 if type not found
+
+int ADSP_FindProc( pset_t *ppset, int proc_type, int skip )
+{
+ int skipcount = skip;
+
+ for (int i = 0; i < ppset->cprcs; i++)
+ {
+ // look for match on processor type
+
+ if ( ppset->prcs[i].type == proc_type )
+ {
+ // skip first N procs of similar type,
+
+ // return index to processor
+
+ if (!skipcount)
+ return i;
+
+ skipcount--;
+ }
+
+ }
+
+ return -1;
+}
+
+// interpolate parameter:
+// pnew - target preset
+// pmin - preset with parameter with min value
+// pmax - preset with parameter with max value
+// proc_type - type of processor to look for ie: PRC_RVA or PRC_DLY
+// skipprocs - skip n processors of type
+// iparam - which parameter within processor to interpolate
+// index -
+// index_max: use index/index_max as interpolater between pmin param and pmax param
+// if bexp is true, interpolate exponentially as (index/index_max)^2
+
+// NOTE: returns with no result if processor type is not found in all presets.
+
+void ADSP_InterpParam( pset_t *pnew, pset_t *pmin, pset_t *pmax, int proc_type, int skipprocs, int iparam, int index, int index_max, bool bexp )
+{
+ // find processor index in pnew
+ int iproc_new = ADSP_FindProc( pnew, proc_type, skipprocs);
+ int iproc_min = ADSP_FindProc( pmin, proc_type, skipprocs);
+ int iproc_max = ADSP_FindProc( pmax, proc_type, skipprocs);
+
+ // make sure processor type found in all presets
+
+ if ( iproc_new < 0 || iproc_min < 0 || iproc_max < 0 )
+ return;
+
+ float findex = (float)index/(float)index_max;
+ float vmin = pmin->prcs[iproc_min].prm[iparam];
+ float vmax = pmax->prcs[iproc_max].prm[iparam];
+ float vinterp;
+
+ // interpolate
+
+ if (!bexp)
+ vinterp = vmin + (vmax - vmin) * findex;
+ else
+ vinterp = vmin + (vmax - vmin) * findex * findex;
+
+ pnew->prcs[iproc_new].prm[iparam] = vinterp;
+
+ return;
+}
+
+// directly set parameter
+
+void ADSP_SetParam( pset_t *pnew, int proc_type, int skipprocs, int iparam, float value )
+{
+ int iproc_new = ADSP_FindProc( pnew, proc_type, skipprocs);
+
+ if (iproc_new >= 0)
+ pnew->prcs[iproc_new].prm[iparam] = value;
+}
+
+// directly set parameter if min or max is negative
+
+void ADSP_SetParamIfNegative( pset_t *pnew, pset_t *pmin, pset_t *pmax, int proc_type, int skipprocs, int iparam, int index, int index_max, bool bexp, float value )
+{
+ // find processor index in pnew
+ int iproc_new = ADSP_FindProc( pnew, proc_type, skipprocs);
+ int iproc_min = ADSP_FindProc( pmin, proc_type, skipprocs);
+ int iproc_max = ADSP_FindProc( pmax, proc_type, skipprocs);
+
+ // make sure processor type found in all presets
+
+ if ( iproc_new < 0 || iproc_min < 0 || iproc_max < 0 )
+ return;
+
+ float vmin = pmin->prcs[iproc_min].prm[iparam];
+ float vmax = pmax->prcs[iproc_max].prm[iparam];
+
+ if ( vmin < 0.0 || vmax < 0.0 )
+ ADSP_SetParam( pnew, proc_type, skipprocs, iparam, value );
+ else
+ ADSP_InterpParam( pnew, pmin, pmax, proc_type, skipprocs, iparam, index, index_max, bexp);
+
+ return;
+}
+
+// given min and max preset and auto parameters, create new preset
+// NOTE: the # and type of processors making up pmin and pmax presets must be identical!
+
+void ADSP_InterpolatePreset( pset_t *pnew, pset_t *pmin, pset_t *pmax, auto_params_t *pa, int iskip )
+{
+ int i;
+
+ // if size > mid size, then copy basic processors from MAX preset,
+ // otherwise, copy from MIN preset
+
+ if ( !iskip )
+ {
+ // only copy on 1st call
+
+ if ( pa->size > ADSP_SIZE_MEDIUM )
+ {
+ *pnew = *pmax;
+ }
+ else
+ {
+ *pnew = *pmin;
+ }
+ }
+
+ // DFR
+
+ // interpolate all DFR params on size
+
+ for (i = 0; i < dfr_cparam; i++)
+ ADSP_InterpParam( pnew, pmin, pmax, PRC_DFR, iskip, i, pa->size, ADSP_SIZE_MAX , 0);
+
+ // RVA
+
+ // interpolate size_max, size_min, feedback, #delays, moddly, imodrate, based on ap size
+
+ ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_ifeedback, pa->size, ADSP_SIZE_MAX, 0);
+ ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_size_min, pa->size, ADSP_SIZE_MAX, 1);
+ ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_size_max, pa->size, ADSP_SIZE_MAX, 1);
+ ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_igain, pa->size, ADSP_SIZE_MAX, 0);
+ ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_inumdelays, pa->size, ADSP_SIZE_MAX , 0);
+ ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_imoddly, pa->size, ADSP_SIZE_MAX , 0);
+ ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_imodrate, pa->size, ADSP_SIZE_MAX , 0);
+
+ // interpolate width,depth,height based on ap width length & height - exponential interpolation
+ // if pmin or pmax parameters are < 0, directly set value from w/l/h
+
+ float w = clamp( ((float)(pa->width) / 12.0), 6.0, 500.0); // in feet
+ float l = clamp( ((float)(pa->length) / 12.0), 6.0, 500.0);
+ float h = clamp( ((float)(pa->height) / 12.0), 6.0, 500.0);
+
+ ADSP_SetParamIfNegative( pnew, pmin, pmax, PRC_RVA, iskip, rva_width, pa->wid, ADSP_WIDTH_MAX, 1, w);
+ ADSP_SetParamIfNegative( pnew, pmin, pmax, PRC_RVA, iskip, rva_depth, pa->len, ADSP_LENGTH_MAX, 1, l);
+ ADSP_SetParamIfNegative( pnew, pmin, pmax, PRC_RVA, iskip, rva_height, pa->ht, ADSP_HEIGHT_MAX, 1, h);
+
+ // interpolate w/d/h feedback based on ap w/d/f
+
+ ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_fbwidth, pa->wid, ADSP_WIDTH_MAX , 0);
+ ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_fbdepth, pa->len, ADSP_LENGTH_MAX , 0);
+ ADSP_InterpParam( pnew, pmin, pmax, PRC_RVA, iskip, rva_fbheight, pa->ht, ADSP_HEIGHT_MAX , 0);
+
+ // interpolate cutoff based on ap reflectivity
+ // NOTE: cutoff goes from max to min! ie: small bright - large dull
+
+ ADSP_InterpParam( pnew, pmax, pmin, PRC_RVA, iskip, rva_icutoff, pa->reflectivity, ADSP_REFLECTIVITY_MAX , 0);
+
+ // don't interpolate: fparallel, ftaps
+
+ // DLY
+
+ // directly set delay value from pa->length if pmin or pmax value is < 0
+
+ l = clamp((pa->length * 2.0 / 12.0), 14.0, 500.0);
+ w = clamp((pa->width * 2.0 / 12.0), 14.0, 500.0);
+
+ ADSP_SetParamIfNegative( pnew, pmin, pmax, PRC_DLY, iskip, dly_idelay, pa->len, ADSP_LENGTH_MAX, 1, l);
+
+ // interpolate feedback, gain, based on max size (length)
+
+ ADSP_InterpParam( pnew, pmin, pmax, PRC_DLY, iskip, dly_ifeedback, pa->len, ADSP_LENGTH_MAX , 0);
+ ADSP_InterpParam( pnew, pmin, pmax, PRC_DLY, iskip, dly_igain, pa->len, ADSP_LENGTH_MAX , 0);
+
+ // directly set tap value from pa->width if pmin or pmax value is < 0
+
+ ADSP_SetParamIfNegative( pnew, pmin, pmax, PRC_DLY, iskip, dly_itap1, pa->len, ADSP_LENGTH_MAX, 1, w);
+ ADSP_SetParamIfNegative( pnew, pmin, pmax, PRC_DLY, iskip, dly_itap2, pa->len, ADSP_LENGTH_MAX, 1, l);
+ ADSP_SetParamIfNegative( pnew, pmin, pmax, PRC_DLY, iskip, dly_itap3, pa->len, ADSP_LENGTH_MAX, 1, l);
+
+ // interpolate cutoff and qwidth based on reflectivity NOTE: this can affect gain!
+ // NOTE: cutoff goes from max to min! ie: small bright - large dull
+
+ ADSP_InterpParam( pnew, pmax, pmin, PRC_DLY, iskip, dly_icutoff, pa->len, ADSP_LENGTH_MAX , 0);
+ ADSP_InterpParam( pnew, pmax, pmin, PRC_DLY, iskip, dly_iqwidth, pa->len, ADSP_LENGTH_MAX , 0);
+
+ // interpolate all other parameters for all other processor types based on size
+
+ // PRC_MDY, PRC_AMP, PRC_FLT, PTC, CRS, ENV, EFO, LFO
+
+ for (i = 0; i < mdy_cparam; i++)
+ ADSP_InterpParam( pnew, pmin, pmax, PRC_MDY, iskip, i, pa->len, ADSP_LENGTH_MAX , 0);
+
+ for (i = 0; i < amp_cparam; i++)
+ ADSP_InterpParam( pnew, pmin, pmax, PRC_AMP, iskip, i, pa->size, ADSP_SIZE_MAX , 0);
+
+ for (i = 0; i < flt_cparam; i++)
+ ADSP_InterpParam( pnew, pmin, pmax, PRC_FLT, iskip, i, pa->size, ADSP_SIZE_MAX , 0);
+
+ for (i = 0; i < ptc_cparam; i++)
+ ADSP_InterpParam( pnew, pmin, pmax, PRC_PTC, iskip, i, pa->size, ADSP_SIZE_MAX , 0);
+
+ for (i = 0; i < crs_cparam; i++)
+ ADSP_InterpParam( pnew, pmin, pmax, PRC_CRS, iskip, i, pa->size, ADSP_SIZE_MAX , 0);
+
+ for (i = 0; i < env_cparam; i++)
+ ADSP_InterpParam( pnew, pmin, pmax, PRC_ENV, iskip, i, pa->size, ADSP_SIZE_MAX , 0);
+
+ for (i = 0; i < efo_cparam; i++)
+ ADSP_InterpParam( pnew, pmin, pmax, PRC_EFO, iskip, i, pa->size, ADSP_SIZE_MAX , 0);
+
+ for (i = 0; i < lfo_cparam; i++)
+ ADSP_InterpParam( pnew, pmin, pmax, PRC_LFO, iskip, i, pa->size, ADSP_SIZE_MAX , 0);
+
+}
+
+// these convars store the index to the first preset for each shape type in dsp_presets.txt
+
+ConVar adsp_room_min ("adsp_room_min", "102");
+ConVar adsp_duct_min ("adsp_duct_min", "106");
+ConVar adsp_hall_min ("adsp_hall_min", "110");
+ConVar adsp_tunnel_min ("adsp_tunnel_min", "114");
+ConVar adsp_street_min ("adsp_street_min", "118");
+ConVar adsp_alley_min ("adsp_alley_min", "122");
+ConVar adsp_courtyard_min ("adsp_courtyard_min", "126");
+ConVar adsp_openspace_min ("adsp_openspace_min", "130");
+ConVar adsp_openwall_min ("adsp_openwall_min", "130");
+ConVar adsp_openstreet_min ("adsp_openstreet_min", "118");
+ConVar adsp_opencourtyard_min ("adsp_opencourtyard_min", "126");
+
+// given room parameters, construct and return a dsp preset representing the room.
+// bskyabove, width, length, height, fdiffusion, freflectivity are all passed-in room parameters
+// psurf_refl is a passed-in array of reflectivity values for 6 surfaces
+// inode is the location within g_psettemplates[] that the dsp preset will be constructed (inode = dsp preset#)
+// cnode should always = DSP_CAUTO_PRESETS
+// returns idsp preset.
+
+int DSP_ConstructPreset( bool bskyabove, int width, int length, int height, float fdiffusion, float freflectivity, float *psurf_refl, int inode, int cnodes )
+{
+ auto_params_t ap;
+ auto_params_t *pa;
+
+ pset_t new_pset; // preset
+ pset_t pset_min;
+ pset_t pset_max;
+
+ int ipreset;
+ int ipset_min;
+ int ipset_max;
+
+ if (inode >= DSP_CAUTO_PRESETS)
+ {
+ Assert(false); // check DAS_CNODES == DSP_CAUTO_PRESETS!!!
+ return 0;
+ }
+
+ // fill parameter struct
+
+ ap.bskyabove = bskyabove;
+ ap.width = width;
+ ap.length = length;
+ ap.height = height;
+ ap.fdiffusion = fdiffusion;
+ ap.freflectivity = freflectivity;
+
+ for (int i = 0; i < 6; i++)
+ ap.surface_refl[i] = psurf_refl[i];
+
+ if (ap.bskyabove)
+ ap.surface_refl[4] = 0.0;
+
+ // select shape, size based on params
+
+ ADSP_GetAutoShape( &ap );
+
+ // set up min/max presets based on shape
+
+ switch ( ap.shape )
+ {
+ default:
+ case ADSP_ROOM: ipset_min = adsp_room_min.GetInt(); break;
+ case ADSP_DUCT: ipset_min = adsp_duct_min.GetInt(); break;
+ case ADSP_HALL: ipset_min = adsp_hall_min.GetInt(); break;
+ case ADSP_TUNNEL: ipset_min = adsp_tunnel_min.GetInt(); break;
+ case ADSP_STREET: ipset_min = adsp_street_min.GetInt(); break;
+ case ADSP_ALLEY: ipset_min = adsp_alley_min.GetInt(); break;
+ case ADSP_COURTYARD: ipset_min = adsp_courtyard_min.GetInt(); break;
+ case ADSP_OPEN_SPACE: ipset_min = adsp_openspace_min.GetInt(); break;
+ case ADSP_OPEN_WALL: ipset_min = adsp_openwall_min.GetInt(); break;
+ case ADSP_OPEN_STREET: ipset_min = adsp_openstreet_min.GetInt(); break;
+ case ADSP_OPEN_COURTYARD: ipset_min = adsp_opencourtyard_min.GetInt(); break;
+ }
+
+ // presets in dsp_presets.txt are ordered as:
+
+ // <shape><empty><min>
+ // <shape><empty><max>
+ // <shape><diffuse><min>
+ // <shape><diffuse><max>
+ pa = &ap;
+ if ( ADSP_IsDiffuse(pa) )
+ ipset_min += 2;
+
+ ipset_max = ipset_min + 1;
+
+ pset_min = g_psettemplates[ipset_min];
+ pset_max = g_psettemplates[ipset_max];
+
+ // given min and max preset and auto parameters, create new preset
+
+ // interpolate between 1st instances of each processor type (ie: PRC_DLY) appearing in preset
+
+ ADSP_InterpolatePreset( &new_pset, &pset_min, &pset_max, &ap, 0 );
+
+ // interpolate between 2nd instances of each processor type (ie: PRC_DLY) appearing in preset
+
+ ADSP_InterpolatePreset( &new_pset, &pset_min, &pset_max, &ap, 1 );
+
+ // copy constructed preset back into node's template location
+
+ ipreset = DSP_AUTO_BASE + inode;
+
+ g_psettemplates[ipreset] = new_pset;
+
+ return ipreset;
+}
+
+///////////////////////////////////////
+// Helpers: called only from DSP_Process
+///////////////////////////////////////
+
+
+// return true if batch processing version of preset exists
+
+inline bool FBatchPreset( pset_t *ppset )
+{
+
+ switch (ppset->type)
+ {
+ case PSET_LINEAR:
+ return true;
+ case PSET_SIMPLE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+// Helper: called only from DSP_Process
+// mix front stereo buffer to mono buffer, apply dsp fx
+
+inline void DSP_ProcessStereoToMono(dsp_t *pdsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, int sampleCount, bool bcrossfading )
+{
+ portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process
+ int count = sampleCount;
+ int av;
+ int x;
+
+ if ( !bcrossfading )
+ {
+ if ( !pdsp->ipset )
+ return;
+
+ if ( FBatchPreset(pdsp->ppset[0]))
+ {
+ // convert Stereo to Mono in place, then batch process fx: perf KDB
+
+ // front->left + front->right / 2 into front->left, front->right duplicated.
+
+ while ( count-- )
+ {
+ pbf->left = (pbf->left + pbf->right) >> 1;
+ pbf++;
+ }
+
+ // process left (mono), duplicate output into right
+
+ PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT_DUPLICATE);
+ }
+ else
+ {
+ // avg left and right -> mono fx -> duplcate out left and right
+ while ( count-- )
+ {
+ av = ( ( pbf->left + pbf->right ) >> 1 );
+ x = PSET_GetNext( pdsp->ppset[0], av );
+ x = CLIP_DSP( x );
+ pbf->left = pbf->right = x;
+ pbf++;
+ }
+ }
+ return;
+ }
+
+ // crossfading to current preset from previous preset
+
+ if ( bcrossfading )
+ {
+ int r;
+ int fl;
+ int fr;
+ int flp;
+ int frp;
+ int xf_fl;
+ int xf_fr;
+ bool bexp = pdsp->bexpfade;
+ bool bfadetostereo = (pdsp->ipset == 0);
+ bool bfadefromstereo = (pdsp->ipsetprev == 0);
+
+ Assert ( !(bfadetostereo && bfadefromstereo) ); // don't call if ipset & ipsetprev both 0!
+
+ if ( bfadetostereo || bfadefromstereo )
+ {
+ // special case if fading to or from preset 0, stereo passthrough
+
+ while ( count-- )
+ {
+ av = ( ( pbf->left + pbf->right ) >> 1 );
+
+ // get current preset values
+
+ if ( pdsp->ipset )
+ {
+ fl = fr = PSET_GetNext( pdsp->ppset[0], av );
+ }
+ else
+ {
+ fl = pbf->left;
+ fr = pbf->right;
+ }
+
+ // get previous preset values
+
+ if ( pdsp->ipsetprev )
+ {
+ frp = flp = PSET_GetNext( pdsp->ppsetprev[0], av );
+ }
+ else
+ {
+ flp = pbf->left;
+ frp = pbf->right;
+ }
+
+ fl = CLIP_DSP(fl);
+ fr = CLIP_DSP(fr);
+ flp = CLIP_DSP(flp);
+ frp = CLIP_DSP(frp);
+
+ // get current ramp value
+
+ r = RMP_GetNext( &pdsp->xramp );
+
+ // crossfade from previous to current preset
+
+ if (!bexp)
+ {
+ xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
+ xf_fr = XFADE(fr, frp, r); // crossfade front left previous to front left
+ }
+ else
+ {
+ xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
+ xf_fr = XFADE_EXP(fr, frp, r); // crossfade front left previous to front left
+ }
+
+ pbf->left = xf_fl; // crossfaded front left, duplicate in right channel
+ pbf->right = xf_fr;
+
+ pbf++;
+ }
+
+ return;
+ }
+
+ // crossfade mono to mono preset
+
+ while ( count-- )
+ {
+ av = ( ( pbf->left + pbf->right ) >> 1 );
+
+ // get current preset values
+
+ fl = PSET_GetNext( pdsp->ppset[0], av );
+
+ // get previous preset values
+
+ flp = PSET_GetNext( pdsp->ppsetprev[0], av );
+
+ fl = CLIP_DSP(fl);
+ flp = CLIP_DSP(flp);
+
+ // get current ramp value
+
+ r = RMP_GetNext( &pdsp->xramp );
+
+ // crossfade from previous to current preset
+
+ if (!bexp)
+ xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
+ else
+ xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
+
+ pbf->left = xf_fl; // crossfaded front left, duplicate in right channel
+ pbf->right = xf_fl;
+
+ pbf++;
+ }
+ }
+}
+
+// Helper: called only from DSP_Process
+// DSP_Process stereo in to stereo out (if more than 2 procs, ignore them)
+
+inline void DSP_ProcessStereoToStereo(dsp_t *pdsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, int sampleCount, bool bcrossfading )
+{
+ portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process
+ int count = sampleCount;
+ int fl, fr;
+
+ if ( !bcrossfading )
+ {
+
+ if ( !pdsp->ipset )
+ return;
+
+ if ( FBatchPreset(pdsp->ppset[0]) && FBatchPreset(pdsp->ppset[1]) )
+ {
+
+ // process left & right
+
+ PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT );
+ PSET_GetNextN( pdsp->ppset[1], pbfront, sampleCount, OP_RIGHT );
+ }
+ else
+ {
+ // left -> left fx, right -> right fx
+ while ( count-- )
+ {
+ fl = PSET_GetNext( pdsp->ppset[0], pbf->left );
+ fr = PSET_GetNext( pdsp->ppset[1], pbf->right );
+
+ fl = CLIP_DSP( fl );
+ fr = CLIP_DSP( fr );
+
+ pbf->left = fl;
+ pbf->right = fr;
+ pbf++;
+ }
+ }
+ return;
+ }
+
+ // crossfading to current preset from previous preset
+
+ if ( bcrossfading )
+ {
+ int r;
+ int flp, frp;
+ int xf_fl, xf_fr;
+ bool bexp = pdsp->bexpfade;
+
+ while ( count-- )
+ {
+ // get current preset values
+
+ fl = PSET_GetNext( pdsp->ppset[0], pbf->left );
+ fr = PSET_GetNext( pdsp->ppset[1], pbf->right );
+
+ // get previous preset values
+
+ flp = PSET_GetNext( pdsp->ppsetprev[0], pbf->left );
+ frp = PSET_GetNext( pdsp->ppsetprev[1], pbf->right );
+
+ // get current ramp value
+
+ r = RMP_GetNext( &pdsp->xramp );
+
+ fl = CLIP_DSP( fl );
+ fr = CLIP_DSP( fr );
+ flp = CLIP_DSP( flp );
+ frp = CLIP_DSP( frp );
+
+ // crossfade from previous to current preset
+ if (!bexp)
+ {
+ xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
+ xf_fr = XFADE(fr, frp, r);
+ }
+ else
+ {
+ xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
+ xf_fr = XFADE_EXP(fr, frp, r);
+ }
+
+ pbf->left = xf_fl; // crossfaded front left
+ pbf->right = xf_fr;
+
+ pbf++;
+ }
+ }
+}
+
+// Helper: called only from DSP_Process
+// DSP_Process quad in to mono out (front left = front right)
+
+inline void DSP_ProcessQuadToMono(dsp_t *pdsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, int sampleCount, bool bcrossfading )
+{
+ portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process
+ portable_samplepair_t *pbr = pbrear; // pointer to buffer of rear stereo samples to process
+ int count = sampleCount;
+ int x;
+ int av;
+
+ if ( !bcrossfading )
+ {
+ if ( !pdsp->ipset )
+ return;
+
+ if ( FBatchPreset(pdsp->ppset[0]) )
+ {
+
+ // convert Quad to Mono in place, then batch process fx: perf KDB
+
+ // left front + rear -> left, right front + rear -> right
+ while ( count-- )
+ {
+ pbf->left = ((pbf->left + pbf->right + pbr->left + pbr->right) >> 2);
+ pbf++;
+ pbr++;
+ }
+
+ // process left (mono), duplicate into right
+
+ PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT_DUPLICATE);
+
+ // copy processed front to rear
+
+ count = sampleCount;
+
+ pbf = pbfront;
+ pbr = pbrear;
+
+ while ( count-- )
+ {
+ pbr->left = pbf->left;
+ pbr->right = pbf->right;
+ pbf++;
+ pbr++;
+ }
+
+ }
+ else
+ {
+ // avg fl,fr,rl,rr into mono fx, duplicate on all channels
+ while ( count-- )
+ {
+ av = ((pbf->left + pbf->right + pbr->left + pbr->right) >> 2);
+ x = PSET_GetNext( pdsp->ppset[0], av );
+ x = CLIP_DSP( x );
+ pbr->left = pbr->right = pbf->left = pbf->right = x;
+ pbf++;
+ pbr++;
+ }
+ }
+ return;
+ }
+
+ if ( bcrossfading )
+ {
+ int r;
+ int fl, fr, rl, rr;
+ int flp, frp, rlp, rrp;
+ int xf_fl, xf_fr, xf_rl, xf_rr;
+ bool bexp = pdsp->bexpfade;
+ bool bfadetoquad = (pdsp->ipset == 0);
+ bool bfadefromquad = (pdsp->ipsetprev == 0);
+
+ if ( bfadetoquad || bfadefromquad )
+ {
+ // special case if previous or current preset is 0 (quad passthrough)
+
+ while ( count-- )
+ {
+ av = ((pbf->left + pbf->right + pbr->left + pbr->right) >> 2);
+
+ // get current preset values
+
+ // current preset is 0, which implies fading to passthrough quad output
+ // need to fade from mono to quad
+
+ if ( pdsp->ipset )
+ {
+ rl = rr = fl = fr = PSET_GetNext( pdsp->ppset[0], av );
+ }
+ else
+ {
+ fl = pbf->left;
+ fr = pbf->right;
+ rl = pbr->left;
+ rr = pbr->right;
+ }
+
+ // get previous preset values
+
+ if ( pdsp->ipsetprev )
+ {
+ rrp = rlp = frp = flp = PSET_GetNext( pdsp->ppsetprev[0], av );
+ }
+ else
+ {
+ flp = pbf->left;
+ frp = pbf->right;
+ rlp = pbr->left;
+ rrp = pbr->right;
+ }
+
+ fl = CLIP_DSP(fl);
+ fr = CLIP_DSP(fr);
+ flp = CLIP_DSP(flp);
+ frp = CLIP_DSP(frp);
+ rl = CLIP_DSP(rl);
+ rr = CLIP_DSP(rr);
+ rlp = CLIP_DSP(rlp);
+ rrp = CLIP_DSP(rrp);
+
+ // get current ramp value
+
+ r = RMP_GetNext( &pdsp->xramp );
+
+ // crossfade from previous to current preset
+
+ if (!bexp)
+ {
+ xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
+ xf_fr = XFADE(fr, frp, r); // crossfade front left previous to front left
+ xf_rl = XFADE(rl, rlp, r); // crossfade front left previous to front left
+ xf_rr = XFADE(rr, rrp, r); // crossfade front left previous to front left
+ }
+ else
+ {
+ xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
+ xf_fr = XFADE_EXP(fr, frp, r); // crossfade front left previous to front left
+ xf_rl = XFADE_EXP(rl, rlp, r); // crossfade front left previous to front left
+ xf_rr = XFADE_EXP(rr, rrp, r); // crossfade front left previous to front left
+ }
+
+ pbf->left = xf_fl;
+ pbf->right = xf_fr;
+ pbr->left = xf_rl;
+ pbr->right = xf_rr;
+
+ pbf++;
+ pbr++;
+ }
+
+ return;
+ }
+
+ while ( count-- )
+ {
+
+ av = ((pbf->left + pbf->right + pbr->left + pbr->right) >> 2);
+
+ // get current preset values
+
+ fl = PSET_GetNext( pdsp->ppset[0], av );
+
+ // get previous preset values
+
+ flp = PSET_GetNext( pdsp->ppsetprev[0], av );
+
+ // get current ramp value
+
+ r = RMP_GetNext( &pdsp->xramp );
+
+ fl = CLIP_DSP( fl );
+ flp = CLIP_DSP( flp );
+
+ // crossfade from previous to current preset
+ if (!bexp)
+ xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
+ else
+ xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
+
+ pbf->left = xf_fl; // crossfaded front left, duplicated to all channels
+ pbf->right = xf_fl;
+ pbr->left = xf_fl;
+ pbr->right = xf_fl;
+
+ pbf++;
+ pbr++;
+ }
+ }
+}
+
+// Helper: called only from DSP_Process
+// DSP_Process quad in to stereo out (preserve stereo spatialization, throw away front/rear)
+
+inline void DSP_ProcessQuadToStereo(dsp_t *pdsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, int sampleCount, bool bcrossfading )
+{
+ portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process
+ portable_samplepair_t *pbr = pbrear; // pointer to buffer of rear stereo samples to process
+ int count = sampleCount;
+ int fl, fr;
+
+ if ( !bcrossfading )
+ {
+ if ( !pdsp->ipset )
+ return;
+
+ if ( FBatchPreset(pdsp->ppset[0]) && FBatchPreset(pdsp->ppset[1]) )
+ {
+
+ // convert Quad to Stereo in place, then batch process fx: perf KDB
+
+ // left front + rear -> left, right front + rear -> right
+
+ while ( count-- )
+ {
+ pbf->left = (pbf->left + pbr->left) >> 1;
+ pbf->right = (pbf->right + pbr->right) >> 1;
+ pbf++;
+ pbr++;
+ }
+
+ // process left & right
+
+ PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT);
+ PSET_GetNextN( pdsp->ppset[1], pbfront, sampleCount, OP_RIGHT );
+
+ // copy processed front to rear
+
+ count = sampleCount;
+
+ pbf = pbfront;
+ pbr = pbrear;
+
+ while ( count-- )
+ {
+ pbr->left = pbf->left;
+ pbr->right = pbf->right;
+ pbf++;
+ pbr++;
+ }
+
+ }
+ else
+ {
+ // left front + rear -> left fx, right front + rear -> right fx
+ while ( count-- )
+ {
+ fl = PSET_GetNext( pdsp->ppset[0], (pbf->left + pbr->left) >> 1);
+ fr = PSET_GetNext( pdsp->ppset[1], (pbf->right + pbr->right) >> 1);
+ fl = CLIP_DSP( fl );
+ fr = CLIP_DSP( fr );
+
+ pbr->left = pbf->left = fl;
+ pbr->right = pbf->right = fr;
+ pbf++;
+ pbr++;
+ }
+ }
+ return;
+ }
+
+ // crossfading to current preset from previous preset
+
+ if ( bcrossfading )
+ {
+ int r;
+ int rl, rr;
+ int flp, frp, rlp, rrp;
+ int xf_fl, xf_fr, xf_rl, xf_rr;
+ int avl, avr;
+ bool bexp = pdsp->bexpfade;
+ bool bfadetoquad = (pdsp->ipset == 0);
+ bool bfadefromquad = (pdsp->ipsetprev == 0);
+
+ if ( bfadetoquad || bfadefromquad )
+ {
+ // special case if previous or current preset is 0 (quad passthrough)
+
+ while ( count-- )
+ {
+ avl = (pbf->left + pbr->left) >> 1;
+ avr = (pbf->right + pbr->right) >> 1;
+
+ // get current preset values
+
+ // current preset is 0, which implies fading to passthrough quad output
+ // need to fade from stereo to quad
+
+ if ( pdsp->ipset )
+ {
+ rl = fl = PSET_GetNext( pdsp->ppset[0], avl );
+ rr = fr = PSET_GetNext( pdsp->ppset[0], avr );
+ }
+ else
+ {
+ fl = pbf->left;
+ fr = pbf->right;
+ rl = pbr->left;
+ rr = pbr->right;
+ }
+
+ // get previous preset values
+
+ if ( pdsp->ipsetprev )
+ {
+ rlp = flp = PSET_GetNext( pdsp->ppsetprev[0], avl );
+ rrp = frp = PSET_GetNext( pdsp->ppsetprev[0], avr );
+ }
+ else
+ {
+ flp = pbf->left;
+ frp = pbf->right;
+ rlp = pbr->left;
+ rrp = pbr->right;
+ }
+
+ fl = CLIP_DSP(fl);
+ fr = CLIP_DSP(fr);
+ flp = CLIP_DSP(flp);
+ frp = CLIP_DSP(frp);
+ rl = CLIP_DSP(rl);
+ rr = CLIP_DSP(rr);
+ rlp = CLIP_DSP(rlp);
+ rrp = CLIP_DSP(rrp);
+
+ // get current ramp value
+
+ r = RMP_GetNext( &pdsp->xramp );
+
+ // crossfade from previous to current preset
+
+ if (!bexp)
+ {
+ xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
+ xf_fr = XFADE(fr, frp, r); // crossfade front left previous to front left
+ xf_rl = XFADE(rl, rlp, r); // crossfade front left previous to front left
+ xf_rr = XFADE(rr, rrp, r); // crossfade front left previous to front left
+ }
+ else
+ {
+ xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
+ xf_fr = XFADE_EXP(fr, frp, r); // crossfade front left previous to front left
+ xf_rl = XFADE_EXP(rl, rlp, r); // crossfade front left previous to front left
+ xf_rr = XFADE_EXP(rr, rrp, r); // crossfade front left previous to front left
+ }
+
+ pbf->left = xf_fl;
+ pbf->right = xf_fr;
+ pbr->left = xf_rl;
+ pbr->right = xf_rr;
+
+ pbf++;
+ pbr++;
+ }
+
+ return;
+ }
+
+ while ( count-- )
+ {
+ avl = (pbf->left + pbr->left) >> 1;
+ avr = (pbf->right + pbr->right) >> 1;
+
+ // get current preset values
+
+ fl = PSET_GetNext( pdsp->ppset[0], avl );
+ fr = PSET_GetNext( pdsp->ppset[1], avr );
+
+ // get previous preset values
+
+ flp = PSET_GetNext( pdsp->ppsetprev[0], avl );
+ frp = PSET_GetNext( pdsp->ppsetprev[1], avr );
+
+
+ fl = CLIP_DSP( fl );
+ fr = CLIP_DSP( fr );
+
+ // get previous preset values
+
+ flp = CLIP_DSP( flp );
+ frp = CLIP_DSP( frp );
+
+ // get current ramp value
+
+ r = RMP_GetNext( &pdsp->xramp );
+
+ // crossfade from previous to current preset
+ if (!bexp)
+ {
+ xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
+ xf_fr = XFADE(fr, frp, r);
+ }
+ else
+ {
+ xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
+ xf_fr = XFADE_EXP(fr, frp, r);
+ }
+
+ pbf->left = xf_fl; // crossfaded front left
+ pbf->right = xf_fr;
+
+ pbr->left = xf_fl; // duplicate front channel to rear channel
+ pbr->right = xf_fr;
+
+ pbf++;
+ pbr++;
+ }
+ }
+}
+
+// Helper: called only from DSP_Process
+// DSP_Process quad in to quad out
+
+inline void DSP_ProcessQuadToQuad(dsp_t *pdsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, int sampleCount, bool bcrossfading )
+{
+ portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process
+ portable_samplepair_t *pbr = pbrear; // pointer to buffer of rear stereo samples to process
+ int count = sampleCount;
+ int fl, fr, rl, rr;
+
+ if ( !bcrossfading )
+ {
+ if ( !pdsp->ipset )
+ return;
+
+ // each channel gets its own processor
+
+ if ( FBatchPreset(pdsp->ppset[0]) && FBatchPreset(pdsp->ppset[1]) && FBatchPreset(pdsp->ppset[2]) && FBatchPreset(pdsp->ppset[3]))
+ {
+ // batch process fx front & rear, left & right: perf KDB
+
+ PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT);
+ PSET_GetNextN( pdsp->ppset[1], pbfront, sampleCount, OP_RIGHT );
+ PSET_GetNextN( pdsp->ppset[2], pbrear, sampleCount, OP_LEFT );
+ PSET_GetNextN( pdsp->ppset[3], pbrear, sampleCount, OP_RIGHT );
+ }
+ else
+ {
+ while ( count-- )
+ {
+ fl = PSET_GetNext( pdsp->ppset[0], pbf->left );
+ fr = PSET_GetNext( pdsp->ppset[1], pbf->right );
+ rl = PSET_GetNext( pdsp->ppset[2], pbr->left );
+ rr = PSET_GetNext( pdsp->ppset[3], pbr->right );
+
+ pbf->left = CLIP_DSP( fl );
+ pbf->right = CLIP_DSP( fr );
+ pbr->left = CLIP_DSP( rl );
+ pbr->right = CLIP_DSP( rr );
+
+ pbf++;
+ pbr++;
+ }
+ }
+ return;
+ }
+
+ // crossfading to current preset from previous preset
+
+ if ( bcrossfading )
+ {
+ int r;
+ int flp, frp, rlp, rrp;
+ int xf_fl, xf_fr, xf_rl, xf_rr;
+ bool bexp = pdsp->bexpfade;
+
+ while ( count-- )
+ {
+ // get current preset values
+
+ fl = PSET_GetNext( pdsp->ppset[0], pbf->left );
+ fr = PSET_GetNext( pdsp->ppset[1], pbf->right );
+ rl = PSET_GetNext( pdsp->ppset[2], pbr->left );
+ rr = PSET_GetNext( pdsp->ppset[3], pbr->right );
+
+ // get previous preset values
+
+ flp = PSET_GetNext( pdsp->ppsetprev[0], pbf->left );
+ frp = PSET_GetNext( pdsp->ppsetprev[1], pbf->right );
+ rlp = PSET_GetNext( pdsp->ppsetprev[2], pbr->left );
+ rrp = PSET_GetNext( pdsp->ppsetprev[3], pbr->right );
+
+ // get current ramp value
+
+ r = RMP_GetNext( &pdsp->xramp );
+
+ // crossfade from previous to current preset
+ if (!bexp)
+ {
+ xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
+ xf_fr = XFADE(fr, frp, r);
+ xf_rl = XFADE(rl, rlp, r);
+ xf_rr = XFADE(rr, rrp, r);
+ }
+ else
+ {
+ xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
+ xf_fr = XFADE_EXP(fr, frp, r);
+ xf_rl = XFADE_EXP(rl, rlp, r);
+ xf_rr = XFADE_EXP(rr, rrp, r);
+ }
+
+ pbf->left = CLIP_DSP(xf_fl); // crossfaded front left
+ pbf->right = CLIP_DSP(xf_fr);
+ pbr->left = CLIP_DSP(xf_rl);
+ pbr->right = CLIP_DSP(xf_rr);
+
+ pbf++;
+ pbr++;
+ }
+ }
+}
+
+
+// Helper: called only from DSP_Process
+// DSP_Process quad + center in to mono out (front left = front right)
+
+inline void DSP_Process5To1(dsp_t *pdsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, portable_samplepair_t *pbcenter, int sampleCount, bool bcrossfading )
+{
+ portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process
+ portable_samplepair_t *pbr = pbrear; // pointer to buffer of rear stereo samples to process
+ portable_samplepair_t *pbc = pbcenter; // pointer to buffer of center mono samples to process
+ int count = sampleCount;
+ int x;
+ int av;
+
+ if ( !bcrossfading )
+ {
+ if ( !pdsp->ipset )
+ return;
+
+ if ( FBatchPreset(pdsp->ppset[0]) )
+ {
+
+ // convert Quad + Center to Mono in place, then batch process fx: perf KDB
+
+ // left front + rear -> left, right front + rear -> right
+ while ( count-- )
+ {
+ // pbf->left = ((pbf->left + pbf->right + pbr->left + pbr->right + pbc->left) / 5);
+
+ av = (pbf->left + pbf->right + pbr->left + pbr->right + pbc->left) * 51; // 51/255 = 1/5
+ av >>= 8;
+ pbf->left = av;
+ pbf++;
+ pbr++;
+ pbc++;
+ }
+
+ // process left (mono), duplicate into right
+
+ PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT_DUPLICATE);
+
+ // copy processed front to rear & center
+
+ count = sampleCount;
+
+ pbf = pbfront;
+ pbr = pbrear;
+ pbc = pbcenter;
+
+ while ( count-- )
+ {
+ pbr->left = pbf->left;
+ pbr->right = pbf->right;
+ pbc->left = pbf->left;
+ pbf++;
+ pbr++;
+ pbc++;
+ }
+
+ }
+ else
+ {
+ // avg fl,fr,rl,rr,fc into mono fx, duplicate on all channels
+ while ( count-- )
+ {
+ // av = ((pbf->left + pbf->right + pbr->left + pbr->right + pbc->left) / 5);
+ av = (pbf->left + pbf->right + pbr->left + pbr->right + pbc->left) * 51; // 51/255 = 1/5
+ av >>= 8;
+ x = PSET_GetNext( pdsp->ppset[0], av );
+ x = CLIP_DSP( x );
+ pbr->left = pbr->right = pbf->left = pbf->right = pbc->left = x;
+ pbf++;
+ pbr++;
+ pbc++;
+ }
+ }
+ return;
+ }
+
+ if ( bcrossfading )
+ {
+ int r;
+ int fl, fr, rl, rr, fc;
+ int flp, frp, rlp, rrp, fcp;
+ int xf_fl, xf_fr, xf_rl, xf_rr, xf_fc;
+ bool bexp = pdsp->bexpfade;
+ bool bfadetoquad = (pdsp->ipset == 0);
+ bool bfadefromquad = (pdsp->ipsetprev == 0);
+
+ if ( bfadetoquad || bfadefromquad )
+ {
+ // special case if previous or current preset is 0 (quad passthrough)
+
+ while ( count-- )
+ {
+ // av = ((pbf->left + pbf->right + pbr->left + pbr->right) >> 2);
+
+ av = (pbf->left + pbf->right + pbr->left + pbr->right + pbc->left) * 51; // 51/255 = 1/5
+ av >>= 8;
+
+ // get current preset values
+
+ // current preset is 0, which implies fading to passthrough quad output
+ // need to fade from mono to quad
+
+ if ( pdsp->ipset )
+ {
+ fc = rl = rr = fl = fr = PSET_GetNext( pdsp->ppset[0], av );
+ }
+ else
+ {
+ fl = pbf->left;
+ fr = pbf->right;
+ rl = pbr->left;
+ rr = pbr->right;
+ fc = pbc->left;
+ }
+
+ // get previous preset values
+
+ if ( pdsp->ipsetprev )
+ {
+ fcp = rrp = rlp = frp = flp = PSET_GetNext( pdsp->ppsetprev[0], av );
+ }
+ else
+ {
+ flp = pbf->left;
+ frp = pbf->right;
+ rlp = pbr->left;
+ rrp = pbr->right;
+ fcp = pbc->left;
+ }
+
+ fl = CLIP_DSP(fl);
+ fr = CLIP_DSP(fr);
+ flp = CLIP_DSP(flp);
+ frp = CLIP_DSP(frp);
+ rl = CLIP_DSP(rl);
+ rr = CLIP_DSP(rr);
+ rlp = CLIP_DSP(rlp);
+ rrp = CLIP_DSP(rrp);
+ fc = CLIP_DSP(fc);
+ fcp = CLIP_DSP(fcp);
+
+ // get current ramp value
+
+ r = RMP_GetNext( &pdsp->xramp );
+
+ // crossfade from previous to current preset
+
+ if (!bexp)
+ {
+ xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
+ xf_fr = XFADE(fr, frp, r); // crossfade front left previous to front left
+ xf_rl = XFADE(rl, rlp, r); // crossfade front left previous to front left
+ xf_rr = XFADE(rr, rrp, r); // crossfade front left previous to front left
+ xf_fc = XFADE(fc, fcp, r); // crossfade front left previous to front left
+ }
+ else
+ {
+ xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
+ xf_fr = XFADE_EXP(fr, frp, r); // crossfade front left previous to front left
+ xf_rl = XFADE_EXP(rl, rlp, r); // crossfade front left previous to front left
+ xf_rr = XFADE_EXP(rr, rrp, r); // crossfade front left previous to front left
+ xf_fc = XFADE_EXP(fc, fcp, r); // crossfade front left previous to front left
+ }
+
+ pbf->left = xf_fl;
+ pbf->right = xf_fr;
+ pbr->left = xf_rl;
+ pbr->right = xf_rr;
+ pbc->left = xf_fc;
+
+ pbf++;
+ pbr++;
+ pbc++;
+ }
+
+ return;
+ }
+
+ while ( count-- )
+ {
+
+ // av = ((pbf->left + pbf->right + pbr->left + pbr->right) >> 2);
+ av = (pbf->left + pbf->right + pbr->left + pbr->right + pbc->left) * 51; // 51/255 = 1/5
+ av >>= 8;
+
+ // get current preset values
+
+ fl = PSET_GetNext( pdsp->ppset[0], av );
+
+ // get previous preset values
+
+ flp = PSET_GetNext( pdsp->ppsetprev[0], av );
+
+ // get current ramp value
+
+ r = RMP_GetNext( &pdsp->xramp );
+
+ fl = CLIP_DSP( fl );
+ flp = CLIP_DSP( flp );
+
+ // crossfade from previous to current preset
+ if (!bexp)
+ xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
+ else
+ xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
+
+ pbf->left = xf_fl; // crossfaded front left, duplicated to all channels
+ pbf->right = xf_fl;
+ pbr->left = xf_fl;
+ pbr->right = xf_fl;
+ pbc->left = xf_fl;
+
+ pbf++;
+ pbr++;
+ pbc++;
+ }
+ }
+}
+
+// Helper: called only from DSP_Process
+// DSP_Process quad + center in to quad + center out
+
+inline void DSP_Process5To5(dsp_t *pdsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, portable_samplepair_t *pbcenter, int sampleCount, bool bcrossfading )
+{
+ portable_samplepair_t *pbf = pbfront; // pointer to buffer of front stereo samples to process
+ portable_samplepair_t *pbr = pbrear; // pointer to buffer of rear stereo samples to process
+ portable_samplepair_t *pbc = pbcenter; // pointer to buffer of center mono samples to process
+
+ int count = sampleCount;
+ int fl, fr, rl, rr, fc;
+
+ if ( !bcrossfading )
+ {
+ if ( !pdsp->ipset )
+ return;
+
+ // each channel gets its own processor
+
+ if ( FBatchPreset(pdsp->ppset[0]) && FBatchPreset(pdsp->ppset[1]) && FBatchPreset(pdsp->ppset[2]) && FBatchPreset(pdsp->ppset[3]))
+ {
+ // batch process fx front & rear, left & right: perf KDB
+
+ PSET_GetNextN( pdsp->ppset[0], pbfront, sampleCount, OP_LEFT);
+ PSET_GetNextN( pdsp->ppset[1], pbfront, sampleCount, OP_RIGHT );
+ PSET_GetNextN( pdsp->ppset[2], pbrear, sampleCount, OP_LEFT );
+ PSET_GetNextN( pdsp->ppset[3], pbrear, sampleCount, OP_RIGHT );
+ PSET_GetNextN( pdsp->ppset[4], pbcenter, sampleCount, OP_LEFT );
+ }
+ else
+ {
+ while ( count-- )
+ {
+ fl = PSET_GetNext( pdsp->ppset[0], pbf->left );
+ fr = PSET_GetNext( pdsp->ppset[1], pbf->right );
+ rl = PSET_GetNext( pdsp->ppset[2], pbr->left );
+ rr = PSET_GetNext( pdsp->ppset[3], pbr->right );
+ fc = PSET_GetNext( pdsp->ppset[4], pbc->left );
+
+ pbf->left = CLIP_DSP( fl );
+ pbf->right = CLIP_DSP( fr );
+ pbr->left = CLIP_DSP( rl );
+ pbr->right = CLIP_DSP( rr );
+ pbc->left = CLIP_DSP( fc );
+
+ pbf++;
+ pbr++;
+ pbc++;
+ }
+ }
+ return;
+ }
+
+ // crossfading to current preset from previous preset
+
+ if ( bcrossfading )
+ {
+ int r;
+ int flp, frp, rlp, rrp, fcp;
+ int xf_fl, xf_fr, xf_rl, xf_rr, xf_fc;
+ bool bexp = pdsp->bexpfade;
+
+ while ( count-- )
+ {
+ // get current preset values
+
+ fl = PSET_GetNext( pdsp->ppset[0], pbf->left );
+ fr = PSET_GetNext( pdsp->ppset[1], pbf->right );
+ rl = PSET_GetNext( pdsp->ppset[2], pbr->left );
+ rr = PSET_GetNext( pdsp->ppset[3], pbr->right );
+ fc = PSET_GetNext( pdsp->ppset[4], pbc->left );
+
+ // get previous preset values
+
+ flp = PSET_GetNext( pdsp->ppsetprev[0], pbf->left );
+ frp = PSET_GetNext( pdsp->ppsetprev[1], pbf->right );
+ rlp = PSET_GetNext( pdsp->ppsetprev[2], pbr->left );
+ rrp = PSET_GetNext( pdsp->ppsetprev[3], pbr->right );
+ fcp = PSET_GetNext( pdsp->ppsetprev[4], pbc->left );
+
+ // get current ramp value
+
+ r = RMP_GetNext( &pdsp->xramp );
+
+ // crossfade from previous to current preset
+ if (!bexp)
+ {
+ xf_fl = XFADE(fl, flp, r); // crossfade front left previous to front left
+ xf_fr = XFADE(fr, frp, r);
+ xf_rl = XFADE(rl, rlp, r);
+ xf_rr = XFADE(rr, rrp, r);
+ xf_fc = XFADE(fc, fcp, r);
+ }
+ else
+ {
+ xf_fl = XFADE_EXP(fl, flp, r); // crossfade front left previous to front left
+ xf_fr = XFADE_EXP(fr, frp, r);
+ xf_rl = XFADE_EXP(rl, rlp, r);
+ xf_rr = XFADE_EXP(rr, rrp, r);
+ xf_fc = XFADE_EXP(fc, fcp, r);
+ }
+
+ pbf->left = CLIP_DSP(xf_fl); // crossfaded front left
+ pbf->right = CLIP_DSP(xf_fr);
+ pbr->left = CLIP_DSP(xf_rl);
+ pbr->right = CLIP_DSP(xf_rr);
+ pbc->left = CLIP_DSP(xf_fc);
+
+ pbf++;
+ pbr++;
+ pbc++;
+ }
+ }
+}
+
+// This is an evil hack, but we need to restore the old presets after letting the sound system update for a few frames, so we just
+// "defer" the restore until the top of the next call to CheckNewDspPresets. I put in a bit of warning in case we ever have code
+// outside of this time period modifying any of the dsp convars. It doesn't seem to be an issue just save/loading between levels
+static bool g_bNeedPresetRestore = false;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+struct PreserveDSP_t
+{
+ ConVar *cvar;
+ float oldvalue;
+};
+
+static PreserveDSP_t g_PreserveDSP[] =
+{
+ { &dsp_room },
+ { &dsp_water },
+ { &dsp_player },
+ { &dsp_facingaway },
+ { &dsp_speaker },
+ { &dsp_spatial },
+ { &dsp_automatic }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Called at the top of CheckNewDspPresets to restore ConVars to real values
+//-----------------------------------------------------------------------------
+void DSP_CheckRestorePresets()
+{
+ if ( !g_bNeedPresetRestore )
+ return;
+
+ g_bNeedPresetRestore = false;
+
+ int i;
+ int c = ARRAYSIZE( g_PreserveDSP );
+
+ // Restore
+ for ( i = 0 ; i < c; ++i )
+ {
+ PreserveDSP_t& slot = g_PreserveDSP[ i ];
+
+ ConVar *cv = slot.cvar;
+ Assert( cv );
+ if ( cv->GetFloat() != 0.0f )
+ {
+ // NOTE: dsp_speaker is being (correctly) save/restored by maps, which would trigger this warning
+ //Warning( "DSP_CheckRestorePresets: Value of %s was changed between DSP_ClearState and CheckNewDspPresets, not restoring to old value\n", cv->GetName() );
+ continue;
+ }
+ cv->SetValue( slot.oldvalue );
+ }
+
+ // reinit all dsp processors (only load preset file on engine init, however)
+
+ AllocDsps( false );
+
+ // flush dsp automatic nodes
+
+ g_bdas_init_nodes = 0;
+ g_bdas_room_init = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void DSP_ClearState()
+{
+ // if we already cleared dsp state, and a restore is pending,
+ // don't clear again
+
+ if ( g_bNeedPresetRestore )
+ return;
+
+ // always save a cleared dsp automatic value to force reset of all adsp code
+
+ dsp_automatic.SetValue(0);
+
+ // Tracker 7155: YWB: This is a pretty ugly hack to zero out all of the dsp convars and bootstrap the dsp system into using them for a few frames
+
+ int i;
+ int c = ARRAYSIZE( g_PreserveDSP );
+
+ for ( i = 0 ; i < c; ++i )
+ {
+ PreserveDSP_t& slot = g_PreserveDSP[ i ];
+
+ ConVar *cv = slot.cvar;
+ Assert( cv );
+ slot.oldvalue = cv->GetFloat();
+ cv->SetValue( 0 );
+ }
+
+ // force all dsp presets to end crossfades, end one-shot presets, & release and reset all resources
+ // immediately.
+
+ FreeDsps( false ); // free all dsp states, but don't discard preset templates
+
+ // This forces the ConVars which we set to zero above to be reloaded to their old values at the time we issue the CheckNewDspPresets
+ // command. This seems to happen early enough in level changes were we don't appear to be trying to stomp real settings...
+
+ g_bNeedPresetRestore = true;
+}
+
+// return true if dsp's preset is one-shot and it has expired
+
+bool DSP_HasExpired( int idsp )
+{
+ dsp_t *pdsp;
+
+ Assert( idsp < CDSPS );
+
+ if (idsp < 0 || idsp >= CDSPS)
+ return false;
+
+ pdsp = &dsps[idsp];
+
+ // if first preset has expired, dsp has expired
+
+ if ( PSET_IsOneShot( pdsp->ppset[0] ) )
+ return PSET_HasExpired( pdsp->ppset[0] );
+ else
+ return false;
+}
+
+// returns true if dsp is crossfading from previous dsp preset
+
+bool DSP_IsCrossfading( int idsp )
+{
+ dsp_t *pdsp;
+
+ Assert( idsp < CDSPS );
+
+ if (idsp < 0 || idsp >= CDSPS)
+ return false;
+
+ pdsp = &dsps[idsp];
+
+ return !RMP_HitEnd( &pdsp->xramp );
+
+}
+
+// returns previous preset # before oneshot preset was set
+
+int DSP_OneShotPrevious( int idsp )
+{
+ dsp_t *pdsp;
+ int idsp_prev;
+
+ Assert( idsp < CDSPS );
+
+ if (idsp < 0 || idsp >= CDSPS)
+ return 0;
+
+ pdsp = &dsps[idsp];
+
+ idsp_prev = pdsp->ipsetsav_oneshot;
+
+ return idsp_prev;
+}
+
+// given idsp (processor index), return true if
+// both current and previous presets are 0 for this processor
+
+bool DSP_PresetIsOff( int idsp )
+{
+ dsp_t *pdsp;
+
+ if (idsp < 0 || idsp >= CDSPS)
+ return true;
+
+ Assert ( idsp < CDSPS ); // make sure idsp is valid
+
+ pdsp = &dsps[idsp];
+
+ // if current and previous preset 0, return - preset 0 is 'off'
+
+ return ( !pdsp->ipset && !pdsp->ipsetprev );
+}
+
+// returns true if dsp is off for room effects
+
+bool DSP_RoomDSPIsOff()
+{
+ return DSP_PresetIsOff( Get_idsp_room() );
+}
+
+// Main DSP processing routine:
+// process samples in buffers using pdsp processor
+// continue crossfade between 2 dsp processors if crossfading on switch
+// pfront - front stereo buffer to process
+// prear - rear stereo buffer to process (may be NULL)
+// pcenter - front center mono buffer (may be NULL)
+// sampleCount - number of samples in pbuf to process
+// This routine also maps the # processing channels in the pdsp to the number of channels
+// supplied. ie: if the pdsp has 4 channels and pbfront and pbrear are both non-null, the channels
+// map 1:1 through the processors.
+
+void DSP_Process( int idsp, portable_samplepair_t *pbfront, portable_samplepair_t *pbrear, portable_samplepair_t *pbcenter, int sampleCount )
+{
+ bool bcrossfading;
+ int cchan_in; // input channels (2,4 or 5)
+ int cprocs; // output cannels (1, 2 or 4)
+ dsp_t *pdsp;
+
+ if (idsp < 0 || idsp >= CDSPS)
+ return;
+
+ // Don't pull dsp data in if player is not connected (during load/level change)
+ if ( !g_pSoundServices->IsConnected() )
+ return;
+
+ Assert ( idsp < CDSPS ); // make sure idsp is valid
+
+ pdsp = &dsps[idsp];
+
+ Assert (pbfront);
+
+ // return right away if fx processing is turned off
+
+ if ( dsp_off.GetInt() )
+ return;
+
+ // if current and previous preset 0, return - preset 0 is 'off'
+
+ if ( !pdsp->ipset && !pdsp->ipsetprev )
+ return;
+
+ if ( sampleCount < 0 )
+ return;
+
+ bcrossfading = !RMP_HitEnd( &pdsp->xramp );
+
+ // if not crossfading, and previous channel is not null, free previous
+
+ if ( !bcrossfading )
+ DSP_FreePrevPreset( pdsp );
+
+ // if current and previous preset 0 (ie: just freed previous), return - preset 0 is 'off'
+
+ if ( !pdsp->ipset && !pdsp->ipsetprev )
+ return;
+
+ cchan_in = (pbrear ? 4 : 2) + (pbcenter ? 1 : 0);
+ cprocs = pdsp->cchan;
+
+ Assert(cchan_in == 2 || cchan_in == 4 || cchan_in == 5 );
+
+ // if oneshot preset, update the duration counter (only update front left counter)
+
+ PSET_UpdateDuration( pdsp->ppset[0], sampleCount );
+
+ // NOTE: when mixing between different channel sizes,
+ // always AVERAGE down to fewer channels and DUPLICATE up more channels.
+ // The following routines always process cchan_in channels.
+ // ie: QuadToMono still updates 4 values in buffer
+
+ // DSP_Process stereo in to mono out (ie: left and right are averaged)
+
+ if ( cchan_in == 2 && cprocs == 1)
+ {
+ DSP_ProcessStereoToMono( pdsp, pbfront, pbrear, sampleCount, bcrossfading );
+ return;
+ }
+
+ // DSP_Process stereo in to stereo out (if more than 2 procs, ignore them)
+
+ if ( cchan_in == 2 && cprocs >= 2)
+ {
+ DSP_ProcessStereoToStereo( pdsp, pbfront, pbrear, sampleCount, bcrossfading );
+ return;
+ }
+
+
+ // DSP_Process quad in to mono out
+
+ if ( cchan_in == 4 && cprocs == 1)
+ {
+ DSP_ProcessQuadToMono( pdsp, pbfront, pbrear, sampleCount, bcrossfading );
+ return;
+ }
+
+
+ // DSP_Process quad in to stereo out (preserve stereo spatialization, loose front/rear)
+
+ if ( cchan_in == 4 && cprocs == 2)
+ {
+ DSP_ProcessQuadToStereo( pdsp, pbfront, pbrear, sampleCount, bcrossfading );
+ return;
+ }
+
+
+ // DSP_Process quad in to quad out
+
+ if ( cchan_in == 4 && cprocs == 4)
+ {
+ DSP_ProcessQuadToQuad( pdsp, pbfront, pbrear, sampleCount, bcrossfading );
+ return;
+ }
+
+ // DSP_Process quad + center in to mono out
+
+ if ( cchan_in == 5 && cprocs == 1)
+ {
+ DSP_Process5To1( pdsp, pbfront, pbrear, pbcenter, sampleCount, bcrossfading );
+ return;
+ }
+
+ if ( cchan_in == 5 && cprocs == 2)
+ {
+ // undone: not used in AllocDsps
+ Assert(false);
+ //DSP_Process5to2( pdsp, pbfront, pbrear, pbcenter, sampleCount, bcrossfading );
+ return;
+ }
+
+ if ( cchan_in == 5 && cprocs == 4)
+ {
+ // undone: not used in AllocDsps
+ Assert(false);
+ //DSP_Process5to4( pdsp, pbfront, pbrear, pbcenter, sampleCount, bcrossfading );
+ return;
+ }
+
+ // DSP_Process quad + center in to quad + center out
+
+ if ( cchan_in == 5 && cprocs == 5)
+ {
+ DSP_Process5To5( pdsp, pbfront, pbrear, pbcenter, sampleCount, bcrossfading );
+ return;
+ }
+
+}
+
+// DSP helpers
+
+// free all dsp processors
+
+void FreeDsps( bool bReleaseTemplateMemory )
+{
+
+ DSP_Free(idsp_room);
+ DSP_Free(idsp_water);
+ DSP_Free(idsp_player);
+ DSP_Free(idsp_facingaway);
+ DSP_Free(idsp_speaker);
+ DSP_Free(idsp_spatial);
+ DSP_Free(idsp_automatic);
+
+ idsp_room = 0;
+ idsp_water = 0;
+ idsp_player = 0;
+ idsp_facingaway = 0;
+ idsp_speaker = 0;
+ idsp_spatial = 0;
+ idsp_automatic = 0;
+
+ for ( int i = SOUND_BUFFER_SPECIAL_START; i < g_paintBuffers.Count(); ++i )
+ {
+ paintbuffer_t *pSpecialBuffer = MIX_GetPPaintFromIPaint( i );
+ if ( pSpecialBuffer->nSpecialDSP != 0 )
+ {
+ DSP_Free( pSpecialBuffer->idsp_specialdsp );
+ pSpecialBuffer->idsp_specialdsp = 0;
+ pSpecialBuffer->nPrevSpecialDSP = 0;
+ pSpecialBuffer->nSpecialDSP = 0;
+ }
+ }
+
+ DSP_FreeAll();
+
+ // only unlock and free psettemplate memory on engine shutdown
+
+ if ( bReleaseTemplateMemory )
+ DSP_ReleaseMemory();
+}
+
+// alloc dsp processors, load dsp preset array from file on engine init only
+
+bool AllocDsps( bool bLoadPresetFile )
+{
+ int csurround = (g_AudioDevice->IsSurround() ? 2: 0); // surround channels to allocate
+ int ccenter = (g_AudioDevice->IsSurroundCenter() ? 1 : 0); // center channels to allocate
+
+ DSP_InitAll( bLoadPresetFile );
+
+ idsp_room = -1;
+ idsp_water = -1;
+ idsp_player = -1;
+ idsp_facingaway = -1;
+ idsp_speaker = -1;
+ idsp_spatial = -1;
+ idsp_automatic = -1;
+
+ // alloc dsp room channel (mono, stereo if dsp_stereo is 1)
+
+ // dsp room is mono, 300ms default fade time
+
+ idsp_room = DSP_Alloc( dsp_room.GetInt(), 200, 1 );
+
+ // dsp automatic overrides dsp_room, if dsp_room set to DSP_AUTOMATIC (1)
+
+ idsp_automatic = DSP_Alloc( dsp_automatic.GetInt(), 200, 1 ) ;
+
+ // alloc stereo or quad series processors for player or water
+
+ // water and player presets are mono
+
+ idsp_water = DSP_Alloc( dsp_water.GetInt(), 100, 1 );
+ idsp_player = DSP_Alloc( dsp_player.GetInt(), 100, 1 );
+
+ // alloc facing away filters (stereo, quad or 5ch)
+
+ idsp_facingaway = DSP_Alloc( dsp_facingaway.GetInt(), 100, 2 + csurround + ccenter );
+
+ // alloc speaker preset (mono)
+
+ idsp_speaker = DSP_Alloc( dsp_speaker.GetInt(), 300, 1 );
+
+ // alloc spatial preset (2-5 chan)
+
+ idsp_spatial = DSP_Alloc( dsp_spatial.GetInt(), 300, 2 + csurround + ccenter );
+
+ // init prev values
+
+ ipset_room_prev = dsp_room.GetInt();
+ ipset_water_prev = dsp_water.GetInt();
+ ipset_player_prev = dsp_player.GetInt();
+ ipset_facingaway_prev = dsp_facingaway.GetInt();
+ ipset_room_typeprev = dsp_room_type.GetInt();
+ ipset_speaker_prev = dsp_speaker.GetInt();
+ ipset_spatial_prev = dsp_spatial.GetInt();
+ ipset_automatic_prev = dsp_automatic.GetInt();
+
+ if (idsp_room < 0 || idsp_water < 0 || idsp_player < 0 || idsp_facingaway < 0 || idsp_speaker < 0 || idsp_spatial < 0 || idsp_automatic < 0)
+ {
+ DevMsg ("WARNING: DSP processor failed to initialize! \n" );
+
+ FreeDsps( true );
+ return false;
+ }
+
+ return true;
+}
+
+// count number of dsp presets specified in preset file
+// counts outer {} pairs, ignoring inner {} pairs.
+
+int DSP_CountFilePresets( const char *pstart )
+{
+ int cpresets = 0;
+ bool binpreset = false;
+ bool blookleft = false;
+
+ while ( 1 )
+ {
+ pstart = COM_Parse( pstart );
+
+ if ( strlen(com_token) <= 0)
+ break;
+
+ if ( com_token[0] == '{' ) // left paren
+ {
+ if (!binpreset)
+ {
+ cpresets++; // found preset:
+ blookleft = true; // look for another left
+ binpreset = true;
+ }
+ else
+ {
+ blookleft = false; // inside preset: next, look for matching right paren
+ }
+
+ continue;
+ }
+
+ if ( com_token[0] == '}' ) // right paren
+ {
+ if (binpreset)
+ {
+ if (!blookleft) // looking for right paren
+ {
+ blookleft = true; // found it, now look for another left
+ }
+ else
+ {
+ // expected inner left paren, found outer right - end of preset definition
+ binpreset = false;
+ blookleft = true;
+ }
+ }
+ else
+ {
+ // error - unexpected } paren
+ DevMsg("PARSE ERROR!!! dsp_presets.txt: unexpected '}' \n");
+ continue;
+ }
+ }
+
+ }
+
+ return cpresets;
+}
+
+struct dsp_stringmap_t
+{
+ char sz[33];
+ int i;
+};
+
+// token map for dsp_preset.txt
+
+dsp_stringmap_t gdsp_stringmap[] =
+{
+ // PROCESSOR TYPE:
+ {"NULL", PRC_NULL},
+ {"DLY", PRC_DLY},
+ {"RVA", PRC_RVA},
+ {"FLT", PRC_FLT},
+ {"CRS", PRC_CRS},
+ {"PTC", PRC_PTC},
+ {"ENV", PRC_ENV},
+ {"LFO", PRC_LFO},
+ {"EFO", PRC_EFO},
+ {"MDY", PRC_MDY},
+ {"DFR", PRC_DFR},
+ {"AMP", PRC_AMP},
+
+ // FILTER TYPE:
+ {"LP", FLT_LP},
+ {"HP", FLT_HP},
+ {"BP", FLT_BP},
+
+ // FILTER QUALITY:
+ {"LO", QUA_LO},
+ {"MED", QUA_MED},
+ {"HI", QUA_HI},
+ {"VHI", QUA_VHI},
+
+ // DELAY TYPE:
+ {"PLAIN", DLY_PLAIN},
+ {"ALLPASS", DLY_ALLPASS},
+ {"LOWPASS", DLY_LOWPASS},
+ {"DLINEAR", DLY_LINEAR},
+ {"FLINEAR", DLY_FLINEAR},
+ {"LOWPASS_4TAP",DLY_LOWPASS_4TAP},
+ {"PLAIN_4TAP", DLY_PLAIN_4TAP},
+
+ // LFO TYPE:
+ {"SIN", LFO_SIN},
+ {"TRI", LFO_TRI},
+ {"SQR", LFO_SQR},
+ {"SAW", LFO_SAW},
+ {"RND", LFO_RND},
+ {"LOG_IN", LFO_LOG_IN},
+ {"LOG_OUT", LFO_LOG_OUT},
+ {"LIN_IN", LFO_LIN_IN},
+ {"LIN_OUT", LFO_LIN_OUT},
+
+ // ENVELOPE TYPE:
+ {"LIN", ENV_LIN},
+ {"EXP", ENV_EXP},
+
+ // PRESET CONFIGURATION TYPE:
+ {"SIMPLE", PSET_SIMPLE},
+ {"LINEAR", PSET_LINEAR},
+ {"PARALLEL2", PSET_PARALLEL2},
+ {"PARALLEL4", PSET_PARALLEL4},
+ {"PARALLEL5", PSET_PARALLEL5},
+ {"FEEDBACK", PSET_FEEDBACK},
+ {"FEEDBACK3", PSET_FEEDBACK3},
+ {"FEEDBACK4", PSET_FEEDBACK4},
+ {"MOD1", PSET_MOD},
+ {"MOD2", PSET_MOD2},
+ {"MOD3", PSET_MOD3}
+};
+
+int gcdsp_stringmap = sizeof(gdsp_stringmap) / sizeof (dsp_stringmap_t);
+
+#define isnumber(c) (c == '+' || c == '-' || c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || c == '6' || c == '7'|| c == '8' || c == '9')\
+
+// given ptr to null term. string, return integer or float value from g_dsp_stringmap
+
+float DSP_LookupStringToken( char *psz, int ipset )
+{
+ int i;
+ float fipset = (float)ipset;
+
+ if (isnumber(psz[0]))
+ return atof(psz);
+
+ for (i = 0; i < gcdsp_stringmap; i++)
+ {
+ if (!strcmpi(gdsp_stringmap[i].sz, psz))
+ return gdsp_stringmap[i].i;
+ }
+
+ // not found
+
+ DevMsg("DSP PARSE ERROR! token not found in dsp_presets.txt. Preset: %3.0f \n", fipset );
+ return 0;
+}
+
+// load dsp preset file, parse presets into g_psettemplate array
+// format for each preset:
+// { <preset #> <preset type> <#processors> <gain> { <processor type> <param0>...<param15> } {...} {...} }
+
+#define CHAR_LEFT_PAREN '{'
+#define CHAR_RIGHT_PAREN '}'
+
+// free preset template memory
+
+void DSP_ReleaseMemory( void )
+{
+ if (g_psettemplates)
+ {
+ delete[] g_psettemplates;
+ g_psettemplates = NULL;
+ }
+}
+
+bool DSP_LoadPresetFile( void )
+{
+ char szFile[ MAX_OSPATH ];
+ char *pbuffer;
+ const char *pstart;
+ bool bResult = false;
+ int cpresets;
+ int ipreset;
+ int itype;
+ int cproc;
+ float mix_min;
+ float mix_max;
+ float db_min;
+ float db_mixdrop;
+ int j;
+ bool fdone;
+ float duration;
+ float fadeout;
+
+ Q_snprintf( szFile, sizeof( szFile ), "scripts/dsp_presets.txt" );
+
+ MEM_ALLOC_CREDIT();
+
+ CUtlBuffer buf;
+
+ if ( !g_pFullFileSystem->ReadFile( szFile, "GAME", buf ) )
+ {
+ Error( "DSP_LoadPresetFile: unable to open '%s'\n", szFile );
+ return bResult;
+ }
+ pbuffer = (char *)buf.PeekGet(); // Use malloc - free at end of this routine
+
+ pstart = pbuffer;
+
+ // figure out how many presets we're loading - count outer parens.
+
+ cpresets = DSP_CountFilePresets( pstart );
+
+ g_cpsettemplates = cpresets;
+
+ g_psettemplates = new pset_t[cpresets];
+ if (!g_psettemplates)
+ {
+ Warning( "DSP Preset Loader: Out of memory.\n");
+ goto load_exit;
+ }
+ memset (g_psettemplates, 0, cpresets * sizeof(pset_t));
+
+
+ // parse presets into g_psettemplates array
+
+ pstart = pbuffer;
+
+ // for each preset...
+
+ for ( j = 0; j < cpresets; j++)
+ {
+ // check for end of file or next CHAR_LEFT_PAREN
+
+ while (1)
+ {
+ pstart = COM_Parse( pstart );
+
+ if ( strlen(com_token) <= 0)
+ break;
+
+ if ( com_token[0] != CHAR_LEFT_PAREN )
+ continue;
+
+ break;
+ }
+
+ // found start of a new preset definition
+
+ // get preset #, type, cprocessors, gain
+
+ pstart = COM_Parse( pstart );
+ ipreset = atoi( com_token );
+
+ pstart = COM_Parse( pstart );
+ itype = (int)DSP_LookupStringToken( com_token , ipreset);
+
+ pstart = COM_Parse( pstart );
+ mix_min = atof( com_token );
+
+ pstart = COM_Parse( pstart );
+ mix_max = atof( com_token );
+
+ pstart = COM_Parse( pstart );
+ duration = atof( com_token );
+
+ pstart = COM_Parse( pstart );
+ fadeout = atof( com_token );
+
+ pstart = COM_Parse( pstart );
+ db_min = atof( com_token );
+
+ pstart = COM_Parse( pstart );
+ db_mixdrop = atof( com_token );
+
+
+ g_psettemplates[ipreset].fused = true;
+ g_psettemplates[ipreset].mix_min = mix_min;
+ g_psettemplates[ipreset].mix_max = mix_max;
+ g_psettemplates[ipreset].duration = duration;
+ g_psettemplates[ipreset].fade = fadeout;
+ g_psettemplates[ipreset].db_min = db_min;
+ g_psettemplates[ipreset].db_mixdrop = db_mixdrop;
+
+ // parse each processor for this preset
+
+ fdone = false;
+ cproc = 0;
+
+ while (1)
+ {
+ // find CHAR_LEFT_PAREN - start of new processor
+
+ while (1)
+ {
+ pstart = COM_Parse( pstart );
+
+ if ( strlen(com_token) <= 0)
+ break;
+
+ if (com_token[0] == CHAR_LEFT_PAREN)
+ break;
+
+ if (com_token[0] == CHAR_RIGHT_PAREN)
+ {
+ // if found right paren, no more processors: done with this preset
+ fdone = true;
+ break;
+ }
+ }
+
+ if ( fdone )
+ break;
+
+ // get processor type
+
+ pstart = COM_Parse( pstart );
+ g_psettemplates[ipreset].prcs[cproc].type = (int)DSP_LookupStringToken( com_token, ipreset );
+
+ // get param 0..n or stop when hit closing CHAR_RIGHT_PAREN
+
+ int ip = 0;
+
+ while (1)
+ {
+ pstart = COM_Parse( pstart );
+
+ if ( strlen(com_token) <= 0)
+ break;
+
+ if ( com_token[0] == CHAR_RIGHT_PAREN )
+ break;
+
+ g_psettemplates[ipreset].prcs[cproc].prm[ip++] = DSP_LookupStringToken( com_token, ipreset );
+
+ // cap at max params
+
+ ip = min(ip, CPRCPARAMS);
+ }
+
+ cproc++;
+ if (cproc > CPSET_PRCS)
+ DevMsg("DSP PARSE ERROR!!! dsp_presets.txt: missing } or too many processors in preset #: %d \n", ipreset);
+ cproc = min(cproc, CPSET_PRCS); // don't overflow # procs
+ }
+
+ // if cproc == 1, type is always SIMPLE
+
+ if ( cproc == 1)
+ itype = PSET_SIMPLE;
+
+ g_psettemplates[ipreset].type = itype;
+ g_psettemplates[ipreset].cprcs = cproc;
+
+ }
+
+ bResult = true;
+
+load_exit:
+ return bResult;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called by client on level shutdown to clear ear ringing dsp effects
+// could be extended to other stuff
+//-----------------------------------------------------------------------------
+void DSP_FastReset( int dspType )
+{
+ int c = ARRAYSIZE( g_PreserveDSP );
+
+ // Restore
+ for ( int i = 0 ; i < c; ++i )
+ {
+ PreserveDSP_t& slot = g_PreserveDSP[ i ];
+
+ if ( slot.cvar == &dsp_player )
+ {
+ slot.oldvalue = dspType;
+ return;
+ }
+ }
+}
+
+// Helper to check for change in preset of any of 4 processors
+// if switching to a new preset, alloc new preset, simulate both presets in DSP_Process & xfade,
+// called a few times per frame.
+
+void CheckNewDspPresets( void )
+{
+ bool b_slow_cpu = dsp_slow_cpu.GetInt() == 0 ? false : true;
+
+ DSP_CheckRestorePresets();
+
+ // room fx are on only if cpu is not slow
+
+ int iroom = b_slow_cpu ? 0 : dsp_room.GetInt() ;
+ int ifacingaway = b_slow_cpu ? 0 : dsp_facingaway.GetInt();
+ int iroomtype = b_slow_cpu ? 0 : dsp_room_type.GetInt();
+ int ispatial = b_slow_cpu ? 0 : dsp_spatial.GetInt();
+ int iautomatic = b_slow_cpu ? 0 : dsp_automatic.GetInt();
+
+ // always use dsp to process these
+
+ int iwater = dsp_water.GetInt();
+ int iplayer = dsp_player.GetInt();
+ int ispeaker = dsp_speaker.GetInt();
+
+ // check for expired one-shot presets on player and room.
+ // Only check if a) no new preset has been set and b) not crossfading from previous preset (ie; previous is null)
+
+ if ( iplayer == ipset_player_prev && !DSP_IsCrossfading( idsp_player ) )
+ {
+ if ( DSP_HasExpired ( idsp_player ) )
+ {
+ iplayer = DSP_OneShotPrevious( idsp_player); // preset has expired - revert to previous preset before one-shot
+ dsp_player.SetValue(iplayer);
+ }
+ }
+
+ if ( iroom == ipset_room_prev && !DSP_IsCrossfading( idsp_room ) )
+ {
+ if ( DSP_HasExpired ( idsp_room ) )
+ {
+ iroom = DSP_OneShotPrevious( idsp_room ); // preset has expired - revert to previous preset before one-shot
+ dsp_room.SetValue(iroom);
+ }
+ }
+
+
+ // legacy code support for "room_type" Cvar
+
+ if ( iroomtype != ipset_room_typeprev )
+ {
+ // force dsp_room = room_type
+
+ ipset_room_typeprev = iroomtype;
+ dsp_room.SetValue(iroomtype);
+ }
+
+ // NOTE: don't change presets if currently crossfading from a previous preset
+
+ if ( iroom != ipset_room_prev && !DSP_IsCrossfading( idsp_room) )
+ {
+ DSP_SetPreset( idsp_room, iroom );
+ ipset_room_prev = iroom;
+
+ // force room_type = dsp_room
+
+ dsp_room_type.SetValue(iroom);
+ ipset_room_typeprev = iroom;
+ }
+
+ if ( iwater != ipset_water_prev && !DSP_IsCrossfading( idsp_water) )
+ {
+ DSP_SetPreset( idsp_water, iwater );
+ ipset_water_prev = iwater;
+ }
+
+ if ( iplayer != ipset_player_prev && !DSP_IsCrossfading( idsp_player))
+ {
+ DSP_SetPreset( idsp_player, iplayer );
+ ipset_player_prev = iplayer;
+ }
+
+ if ( ifacingaway != ipset_facingaway_prev && !DSP_IsCrossfading( idsp_facingaway) )
+ {
+ DSP_SetPreset( idsp_facingaway, ifacingaway );
+ ipset_facingaway_prev = ifacingaway;
+ }
+
+ if ( ispeaker != ipset_speaker_prev && !DSP_IsCrossfading( idsp_speaker) )
+ {
+ DSP_SetPreset( idsp_speaker, ispeaker );
+ ipset_speaker_prev = ispeaker;
+ }
+
+ if ( ispatial != ipset_spatial_prev && !DSP_IsCrossfading( idsp_spatial) )
+ {
+ DSP_SetPreset( idsp_spatial, ispatial );
+ ipset_spatial_prev = ispatial;
+ }
+
+ if ( iautomatic != ipset_automatic_prev && !DSP_IsCrossfading( idsp_automatic) )
+ {
+ DSP_SetPreset( idsp_automatic, iautomatic );
+ ipset_automatic_prev = iautomatic;
+ }
+
+ for ( int i = SOUND_BUFFER_SPECIAL_START; i < g_paintBuffers.Count(); ++i )
+ {
+ paintbuffer_t *pSpecialBuffer = MIX_GetPPaintFromIPaint( i );
+ if ( pSpecialBuffer->nSpecialDSP != pSpecialBuffer->nPrevSpecialDSP && !DSP_IsCrossfading( pSpecialBuffer->idsp_specialdsp ) )
+ {
+ DSP_SetPreset( pSpecialBuffer->idsp_specialdsp, pSpecialBuffer->nSpecialDSP );
+ pSpecialBuffer->nPrevSpecialDSP = pSpecialBuffer->nSpecialDSP;
+ }
+ }
+}
+
+// create idsp_room preset from set of values, reload the preset.
+// modifies psettemplates in place.
+
+// ipreset is the preset # ie: 40
+// iproc is the processor to modify within the preset (typically 0)
+// pvalues is an array of floating point parameters
+// cparams is the # of elements in pvalues
+
+// USED FOR DEBUG ONLY.
+
+void DSP_DEBUGSetParams(int ipreset, int iproc, float *pvalues, int cparams)
+{
+ pset_t new_pset; // preset
+ int cparam = clamp (cparams, 0, CPRCPARAMS);
+ prc_t *pprct;
+
+ // copy template preset from template array
+
+ new_pset = g_psettemplates[ipreset];
+
+ // get iproc processor
+
+ pprct = &(new_pset.prcs[iproc]);
+
+ // copy parameters in to processor
+
+ for (int i = 0; i < cparam; i++)
+ {
+ pprct->prm[i] = pvalues[i];
+ }
+
+ // copy constructed preset back into template location
+
+ g_psettemplates[ipreset] = new_pset;
+
+ // setup new preset
+
+ dsp_room.SetValue( 0 );
+
+ CheckNewDspPresets();
+
+ dsp_room.SetValue( ipreset );
+
+ CheckNewDspPresets();
+}
+
+// reload entire preset file, reset all current dsp presets
+// NOTE: this is debug code only. It doesn't do all mem free work correctly!
+
+void DSP_DEBUGReloadPresetFile( void )
+{
+ int iroom = dsp_room.GetInt();
+ int iwater = dsp_water.GetInt();
+ int iplayer = dsp_player.GetInt();
+// int ifacingaway = dsp_facingaway.GetInt();
+// int iroomtype = dsp_room_type.GetInt();
+ int ispeaker = dsp_speaker.GetInt();
+ int ispatial = dsp_spatial.GetInt();
+// int iautomatic = dsp_automatic.GetInt();
+
+ // reload template array
+
+ DSP_ReleaseMemory();
+
+ DSP_LoadPresetFile();
+
+ // force presets to reload
+
+ dsp_room.SetValue( 0 );
+ dsp_water.SetValue( 0 );
+ dsp_player.SetValue( 0 );
+ //dsp_facingaway.SetValue( 0 );
+ //dsp_room_type.SetValue( 0 );
+ dsp_speaker.SetValue( 0 );
+ dsp_spatial.SetValue( 0 );
+ //dsp_automatic.SetValue( 0 );
+
+ CUtlVector< int > specialDSPs;
+ for ( int i = SOUND_BUFFER_SPECIAL_START; i < g_paintBuffers.Count(); ++i )
+ {
+ paintbuffer_t *pSpecialBuffer = MIX_GetPPaintFromIPaint( i );
+
+ specialDSPs.AddToTail( pSpecialBuffer->nSpecialDSP );
+ pSpecialBuffer->nSpecialDSP = 0;
+ }
+
+ CheckNewDspPresets();
+
+ dsp_room.SetValue( iroom );
+ dsp_water.SetValue( iwater );
+ dsp_player.SetValue( iplayer );
+ //dsp_facingaway.SetValue( ifacingaway );
+ //dsp_room_type.SetValue( iroomtype );
+ dsp_speaker.SetValue( ispeaker );
+ dsp_spatial.SetValue( ispatial );
+ //dsp_automatic.SetValue( iautomatic );
+
+ int nSpecialDSPNum = 0;
+ for ( int i = SOUND_BUFFER_SPECIAL_START; i < g_paintBuffers.Count(); ++i )
+ {
+ paintbuffer_t *pSpecialBuffer = MIX_GetPPaintFromIPaint( i );
+
+ pSpecialBuffer->nSpecialDSP = specialDSPs[ nSpecialDSPNum ];
+ nSpecialDSPNum++;
+ }
+
+ CheckNewDspPresets();
+
+ // flush dsp automatic nodes
+
+ g_bdas_init_nodes = 0;
+ g_bdas_room_init = 0;
+}
+
+// UNDONE: stock reverb presets:
+
+// carpet hallway
+// tile hallway
+// wood hallway
+// metal hallway
+
+// train tunnel
+// sewer main tunnel
+// concrete access tunnel
+// cave tunnel
+// sand floor cave tunnel
+
+// metal duct shaft
+// elevator shaft
+// large elevator shaft
+
+// parking garage
+// aircraft hangar
+// cathedral
+// train station
+
+// small cavern
+// large cavern
+// huge cavern
+// watery cavern
+// long, low cavern
+
+// wood warehouse
+// metal warehouse
+// concrete warehouse
+
+// small closet room
+// medium drywall room
+// medium wood room
+// medium metal room
+
+// elevator
+// small metal room
+// medium metal room
+// large metal room
+// huge metal room
+
+// small metal room dense
+// medium metal room dense
+// large metal room dense
+// huge metal room dense
+
+// small concrete room
+// medium concrete room
+// large concrete room
+// huge concrete room
+
+// small concrete room dense
+// medium concrete room dense
+// large concrete room dense
+// huge concrete room dense
+
+// soundproof room
+// carpet lobby
+// swimming pool
+// open park
+// open courtyard
+// wide parkinglot
+// narrow street
+// wide street, short buildings
+// wide street, tall buildings
+// narrow canyon
+// wide canyon
+// huge canyon
+// small valley
+// wide valley
+// wreckage & rubble
+// small building cluster
+// wide open plain
+// high vista
+
+// alien interior small
+// alien interior medium
+// alien interior large
+// alien interior huge
+
+// special fx presets:
+
+// alien citadel
+
+// teleport aftershock (these presets all ADSR timeout and reset the dsp_* to 0)
+// on target teleport
+// off target teleport
+// death fade
+// beam stasis
+// scatterbrain
+// pulse only
+// slomo
+// hypersensitive
+// supershocker
+// physwhacked
+// forcefieldfry
+// juiced
+// zoomed in
+// crabbed
+// barnacle gut
+// bad transmission
+
+////////////////////////
+// Dynamics processing
+////////////////////////
+
+// compressor defines
+#define COMP_MAX_AMP 32767 // abs max amplitude
+#define COMP_THRESH 20000 // start compressing at this threshold
+
+// compress input value - smoothly limit output y to -32767 <= y <= 32767
+// UNDONE: not tested or used
+
+inline int S_Compress( int xin )
+{
+
+ return CLIP( xin >> 2 ); // DEBUG - disabled
+
+
+ float Yn, Xn, Cn, Fn;
+ float C0 = 20000; // threshold
+ float p = .3; // compression ratio
+ float g = 1; // gain after compression
+
+ Xn = (float)xin;
+
+ // Compressor formula:
+ // Cn = l*Cn-1 + (1-l)*|Xn| // peak detector with memory
+ // f(Cn) = (Cn/C0)^(p-1) for Cn > C0 // gain function above threshold
+ // f(Cn) = 1 for C <= C0 // unity gain below threshold
+ // Yn = f(Cn) * Xn // compressor output
+
+ // UNDONE: curves discontinuous at threshold, causes distortion, try catmul-rom
+
+ //float l = .5; // compressor memory
+ //Cn = l * (*pCnPrev) + (1 - l) * fabs((float)xin);
+ //*pCnPrev = Cn;
+
+ Cn = fabs((float)xin);
+
+ if (Cn < C0)
+ Fn = 1;
+ else
+ Fn = powf((Cn / C0),(p - 1));
+
+ Yn = Fn * Xn * g;
+
+ //if (Cn > 0)
+ // Msg("%d -> %d\n", xin, (int)Yn); // DEBUG
+
+ //if (fabs(Yn) > 32767)
+ // Yn = Yn; // DEBUG
+
+ return (CLIP((int)Yn));
+}
+
diff --git a/engine/audio/private/snd_env_fx.h b/engine/audio/private/snd_env_fx.h
new file mode 100644
index 0000000..cad7d72
--- /dev/null
+++ b/engine/audio/private/snd_env_fx.h
@@ -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
diff --git a/engine/audio/private/snd_fixedint.h b/engine/audio/private/snd_fixedint.h
new file mode 100644
index 0000000..b1e1aac
--- /dev/null
+++ b/engine/audio/private/snd_fixedint.h
@@ -0,0 +1,40 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef SND_FIXEDINT_H
+#define SND_FIXEDINT_H
+
+#if defined( _WIN32 )
+#pragma once
+#endif
+
+// fixed point stuff for real-time resampling
+#define FIX_BITS 28
+#define FIX_SCALE (1 << FIX_BITS)
+#define FIX_MASK ((1 << FIX_BITS)-1)
+#define FIX_FLOAT(a) ((int)((a) * FIX_SCALE))
+#define FIX(a) (((int)(a)) << FIX_BITS)
+#define FIX_INTPART(a) (((int)(a)) >> FIX_BITS)
+#define FIX_FRACTION(a,b) (FIX(a)/(b))
+#define FIX_FRACPART(a) ((a) & FIX_MASK)
+#define FIX_TODOUBLE(a) ((double)(a) / (double)FIX_SCALE)
+
+typedef unsigned int fixedint;
+
+#define FIX_BITS14 14
+#define FIX_SCALE14 (1 << FIX_BITS14)
+#define FIX_MASK14 ((1 << FIX_BITS14)-1)
+#define FIX_FLOAT14(a) ((int)((a) * FIX_SCALE14))
+#define FIX14(a) (((int)(a)) << FIX_BITS14)
+#define FIX_INTPART14(a) (((int)(a)) >> FIX_BITS14)
+#define FIX_FRACTION14(a,b) (FIX14(a)/(b))
+#define FIX_FRACPART14(a) ((a) & FIX_MASK14)
+#define FIX_14TODOUBLE(a) ((double)(a) / (double)FIX_SCALE14)
+
+#define FIX_28TO14(a) ( (int)( ((unsigned int)(a)) >> (FIX_BITS - 14) ) )
+
+#endif // SND_FIXEDINT_H
diff --git a/engine/audio/private/snd_mix.cpp b/engine/audio/private/snd_mix.cpp
new file mode 100644
index 0000000..ca44cbf
--- /dev/null
+++ b/engine/audio/private/snd_mix.cpp
@@ -0,0 +1,4293 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Portable code to mix sounds for snd_dma.cpp.
+//
+//=============================================================================//
+
+#include "audio_pch.h"
+
+#include "mouthinfo.h"
+#include "../../cl_main.h"
+#include "icliententitylist.h"
+#include "icliententity.h"
+#include "../../sys_dll.h"
+#include "video/ivideoservices.h"
+#include "engine/IEngineSound.h"
+
+#if defined( REPLAY_ENABLED )
+#include "demo.h"
+#include "replay_internal.h"
+#endif
+#ifdef GNUC
+// we don't suport the ASM in this file right now under GCC, fallback to C libs
+#undef id386
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#if defined( REPLAY_ENABLED )
+extern IReplayMovieManager *g_pReplayMovieManager;
+#endif
+
+#if defined(_WIN32) && id386
+// warning C4731: frame pointer register 'ebp' modified by inline assembly code
+#pragma warning(disable : 4731)
+#endif
+
+// NOTE: !!!!!! YOU MUST UPDATE SND_MIXA.S IF THIS VALUE IS CHANGED !!!!!
+#define SND_SCALE_BITS 7
+#define SND_SCALE_SHIFT (8-SND_SCALE_BITS)
+#define SND_SCALE_LEVELS (1<<SND_SCALE_BITS)
+
+#define SND_SCALE_BITS16 8
+#define SND_SCALE_SHIFT16 (8-SND_SCALE_BITS16)
+#define SND_SCALE_LEVELS16 (1<<SND_SCALE_BITS16)
+
+void Snd_WriteLinearBlastStereo16(void);
+void SND_PaintChannelFrom8( portable_samplepair_t *pOutput, int *volume, byte *pData8, int count );
+bool Con_IsVisible( void );
+void SND_RecordBuffer( void );
+bool DSP_RoomDSPIsOff( void );
+bool BChannelLowVolume( channel_t *pch, int vol_min );
+void ChannelCopyVolumes( channel_t *pch, int *pvolume_dest, int ivol_start, int cvol );
+float ChannelLoudestCurVolume( const channel_t * RESTRICT pch );
+
+extern int g_soundtime;
+extern float host_frametime;
+extern float host_frametime_unbounded;
+
+
+#if !defined( NO_VOICE )
+extern int g_SND_VoiceOverdriveInt;
+#endif
+
+extern ConVar dsp_room;
+extern ConVar dsp_water;
+extern ConVar dsp_player;
+extern ConVar dsp_facingaway;
+extern ConVar snd_showstart;
+extern ConVar dsp_automatic;
+extern ConVar snd_pitchquality;
+
+extern float DSP_ROOM_MIX;
+extern float DSP_NOROOM_MIX;
+
+portable_samplepair_t *g_paintbuffer;
+
+// temp paintbuffer - not included in main list of paintbuffers
+// NOTE: this paintbuffer is also used as a copy buffer by interpolating pitch
+// shift routines. Decreasing TEMP_COPY_BUFFER_SIZE (or PAINTBUFFER_MEM_SIZE)
+// will decrease the maximum pitch level (current 4.0)!
+portable_samplepair_t *g_temppaintbuffer = NULL;
+
+CUtlVector< paintbuffer_t > g_paintBuffers;
+
+
+// pointer to current paintbuffer (front and reare), used by all mixing, upsampling and dsp routines
+portable_samplepair_t *g_curpaintbuffer = NULL;
+portable_samplepair_t *g_currearpaintbuffer = NULL;
+portable_samplepair_t *g_curcenterpaintbuffer = NULL;
+
+bool g_bdirectionalfx;
+bool g_bDspOff;
+float g_dsp_volume;
+
+// dsp performance timing
+unsigned g_snd_call_time_debug = 0;
+unsigned g_snd_time_debug = 0;
+unsigned g_snd_count_debug = 0;
+unsigned g_snd_samplecount = 0;
+unsigned g_snd_frametime = 0;
+unsigned g_snd_frametime_total = 0;
+int g_snd_profile_type = 0; // type 1 dsp, type 2 mixer, type 3 load sound, type 4 all sound
+
+#define FILTERTYPE_NONE 0
+#define FILTERTYPE_LINEAR 1
+#define FILTERTYPE_CUBIC 2
+
+// filter memory for upsampling
+portable_samplepair_t cubicfilter1[3] = {{0,0},{0,0},{0,0}};
+portable_samplepair_t cubicfilter2[3] = {{0,0},{0,0},{0,0}};
+
+portable_samplepair_t linearfilter1[1] = {{0,0}};
+portable_samplepair_t linearfilter2[1] = {{0,0}};
+portable_samplepair_t linearfilter3[1] = {{0,0}};
+portable_samplepair_t linearfilter4[1] = {{0,0}};
+portable_samplepair_t linearfilter5[1] = {{0,0}};
+portable_samplepair_t linearfilter6[1] = {{0,0}};
+portable_samplepair_t linearfilter7[1] = {{0,0}};
+portable_samplepair_t linearfilter8[1] = {{0,0}};
+
+int snd_scaletable[SND_SCALE_LEVELS][256]; // 32k*4 = 128K
+
+int *snd_p, snd_linear_count, snd_vol;
+short *snd_out;
+
+extern int DSP_Alloc( int ipset, float xfade, int cchan );
+
+bool DSP_CheckDspAutoEnabled( void );
+int Get_idsp_room ( void );
+int dsp_room_GetInt ( void );
+void DSP_SetDspAuto( int dsp_preset );
+bool DSP_CheckDspAutoEnabled( void );
+
+void MIX_ScalePaintBuffer( int bufferIndex, int count, float fgain );
+
+bool IsReplayRendering()
+{
+#if defined( REPLAY_ENABLED )
+ return g_pReplayMovieManager && g_pReplayMovieManager->IsRendering();
+#else
+ return false;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Free allocated memory buffers
+//-----------------------------------------------------------------------------
+void MIX_FreeAllPaintbuffers(void)
+{
+ if ( g_paintBuffers.Count() )
+ {
+ if ( g_temppaintbuffer )
+ {
+ _aligned_free( g_temppaintbuffer );
+ g_temppaintbuffer = NULL;
+ }
+
+ for ( int i = 0; i < g_paintBuffers.Count(); i++ )
+ {
+ if ( g_paintBuffers[i].pbuf )
+ {
+ _aligned_free( g_paintBuffers[i].pbuf );
+ }
+ if ( g_paintBuffers[i].pbufrear )
+ {
+ _aligned_free( g_paintBuffers[i].pbufrear );
+ }
+ if ( g_paintBuffers[i].pbufcenter )
+ {
+ _aligned_free( g_paintBuffers[i].pbufcenter );
+ }
+ }
+
+ g_paintBuffers.RemoveAll();
+ }
+}
+
+void MIX_InitializePaintbuffer( paintbuffer_t *pPaintBuffer, bool bSurround, bool bSurroundCenter )
+{
+ V_memset( pPaintBuffer, 0, sizeof( *pPaintBuffer ) );
+
+ pPaintBuffer->pbuf = (portable_samplepair_t *)_aligned_malloc( PAINTBUFFER_MEM_SIZE*sizeof(portable_samplepair_t), 16 );
+ V_memset( pPaintBuffer->pbuf, 0, PAINTBUFFER_MEM_SIZE*sizeof(portable_samplepair_t) );
+
+ if ( bSurround )
+ {
+ pPaintBuffer->pbufrear = (portable_samplepair_t *)_aligned_malloc( PAINTBUFFER_MEM_SIZE*sizeof(portable_samplepair_t), 16 );
+ V_memset( pPaintBuffer->pbufrear, 0, PAINTBUFFER_MEM_SIZE*sizeof(portable_samplepair_t) );
+ }
+ if ( bSurroundCenter )
+ {
+ pPaintBuffer->pbufcenter = (portable_samplepair_t *)_aligned_malloc( PAINTBUFFER_MEM_SIZE*sizeof(portable_samplepair_t), 16 );
+ V_memset( pPaintBuffer->pbufcenter, 0, PAINTBUFFER_MEM_SIZE*sizeof(portable_samplepair_t) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Allocate memory buffers
+// Initialize paintbuffers array, set current paint buffer to main output buffer SOUND_BUFFER_PAINT
+//-----------------------------------------------------------------------------
+bool MIX_InitAllPaintbuffers(void)
+{
+ bool bSurround;
+ bool bSurroundCenter;
+
+ bSurroundCenter = g_AudioDevice->IsSurroundCenter();
+ bSurround = g_AudioDevice->IsSurround() || bSurroundCenter;
+
+ g_temppaintbuffer = (portable_samplepair_t*)_aligned_malloc( TEMP_COPY_BUFFER_SIZE*sizeof(portable_samplepair_t), 16 );
+ V_memset( g_temppaintbuffer, 0, TEMP_COPY_BUFFER_SIZE*sizeof(portable_samplepair_t) );
+
+ while ( g_paintBuffers.Count() < SOUND_BUFFER_BASETOTAL )
+ {
+ int nIndex = g_paintBuffers.AddToTail();
+ MIX_InitializePaintbuffer( &(g_paintBuffers[ nIndex ]), bSurround, bSurroundCenter );
+ }
+
+ g_paintbuffer = g_paintBuffers[SOUND_BUFFER_PAINT].pbuf;
+
+ // buffer flags
+ g_paintBuffers[SOUND_BUFFER_ROOM].flags = SOUND_BUSS_ROOM;
+ g_paintBuffers[SOUND_BUFFER_FACING].flags = SOUND_BUSS_FACING;
+ g_paintBuffers[SOUND_BUFFER_FACINGAWAY].flags = SOUND_BUSS_FACINGAWAY;
+ g_paintBuffers[SOUND_BUFFER_SPEAKER].flags = SOUND_BUSS_SPEAKER;
+ g_paintBuffers[SOUND_BUFFER_DRY].flags = SOUND_BUSS_DRY;
+
+ // buffer surround sound flag
+ g_paintBuffers[SOUND_BUFFER_PAINT].fsurround = bSurround;
+ g_paintBuffers[SOUND_BUFFER_FACING].fsurround = bSurround;
+ g_paintBuffers[SOUND_BUFFER_FACINGAWAY].fsurround = bSurround;
+ g_paintBuffers[SOUND_BUFFER_DRY].fsurround = bSurround;
+
+ // buffer 5 channel surround sound flag
+ g_paintBuffers[SOUND_BUFFER_PAINT].fsurround_center = bSurroundCenter;
+ g_paintBuffers[SOUND_BUFFER_FACING].fsurround_center = bSurroundCenter;
+ g_paintBuffers[SOUND_BUFFER_FACINGAWAY].fsurround_center = bSurroundCenter;
+ g_paintBuffers[SOUND_BUFFER_DRY].fsurround_center = bSurroundCenter;
+
+ // room buffer mixes down to mono or stereo, never to 4 or 5 ch
+ g_paintBuffers[SOUND_BUFFER_ROOM].fsurround = false;
+ g_paintBuffers[SOUND_BUFFER_ROOM].fsurround_center = false;
+
+ // speaker buffer mixes to mono
+ g_paintBuffers[SOUND_BUFFER_SPEAKER].fsurround = false;
+ g_paintBuffers[SOUND_BUFFER_SPEAKER].fsurround_center = false;
+
+ MIX_SetCurrentPaintbuffer( SOUND_BUFFER_PAINT );
+
+ return true;
+}
+
+// called before loading samples to mix - cap the mix rate (ie: pitch) so that
+// we never overflow the mix copy buffer.
+
+double MIX_GetMaxRate( double rate, int sampleCount )
+{
+ if (rate <= 2.0)
+ return rate;
+
+ // copybuf_bytes = rate_max * samples_max * samplesize_max
+ // so:
+ // rate_max = copybuf_bytes / (samples_max * samplesize_max )
+
+ double samplesize_max = 4.0; // stereo 16bit samples
+ double copybuf_bytes = (double)(TEMP_COPY_BUFFER_SIZE * sizeof(portable_samplepair_t));
+ double samples_max = (double)(PAINTBUFFER_SIZE);
+
+ double rate_max = copybuf_bytes / (samples_max * samplesize_max);
+
+ // make sure sampleCount is never greater than paintbuffer samples
+ // (this should have been set up in MIX_PaintChannels)
+
+ Assert (sampleCount <= PAINTBUFFER_SIZE);
+
+ return fpmin( rate, rate_max );
+}
+
+
+// Transfer (endtime - lpaintedtime) stereo samples in pfront out to hardware
+// pfront - pointer to stereo paintbuffer - 32 bit samples, interleaved stereo
+// lpaintedtime - total number of 32 bit stereo samples previously output to hardware
+// endtime - total number of 32 bit stereo samples currently mixed in paintbuffer
+void S_TransferStereo16( void *pOutput, const portable_samplepair_t *pfront, int lpaintedtime, int endtime )
+{
+ int lpos;
+
+ if ( IsX360() )
+ {
+ // not the right path for 360
+ Assert( 0 );
+ return;
+ }
+
+ Assert( pOutput );
+
+ snd_vol = S_GetMasterVolume()*256;
+ snd_p = (int *)pfront;
+
+ // get size of output buffer in full samples (LR pairs)
+ int samplePairCount = g_AudioDevice->DeviceSampleCount() >> 1;
+ int sampleMask = samplePairCount - 1;
+
+ bool bShouldPlaySound = !cl_movieinfo.IsRecording() && !IsReplayRendering();
+
+ while ( lpaintedtime < endtime )
+ {
+ // pbuf can hold 16384, 16 bit L/R samplepairs.
+ // lpaintedtime - where to start painting into dma buffer.
+ // (modulo size of dma buffer for current position).
+ // handle recirculating buffer issues
+ // lpos - samplepair index into dma buffer. First samplepair from paintbuffer to be xfered here.
+ lpos = lpaintedtime & sampleMask;
+
+ // snd_out is L/R sample index into dma buffer. First L sample from paintbuffer goes here.
+ snd_out = (short *)pOutput + (lpos<<1);
+
+ // snd_linear_count is number of samplepairs between end of dma buffer and xfer start index.
+ snd_linear_count = samplePairCount - lpos;
+
+ // clamp snd_linear_count to be only as many samplepairs premixed
+ if ( snd_linear_count > endtime - lpaintedtime )
+ {
+ // endtime - lpaintedtime = number of premixed sample pairs ready for xfer.
+ snd_linear_count = endtime - lpaintedtime;
+ }
+
+ // snd_linear_count is now number of mono 16 bit samples (L and R) to xfer.
+ snd_linear_count <<= 1;
+
+ // write a linear blast of samples
+ SND_RecordBuffer();
+ if ( bShouldPlaySound )
+ {
+ // transfer 16bit samples from snd_p into snd_out, multiplying each sample by volume.
+ Snd_WriteLinearBlastStereo16();
+ }
+
+ // advance paintbuffer pointer
+ snd_p += snd_linear_count;
+
+ // advance lpaintedtime by number of samplepairs just xfered.
+ lpaintedtime += (snd_linear_count>>1);
+ }
+}
+
+// Transfer contents of main paintbuffer pfront out to
+// device. Perform volume multiply on each sample.
+void S_TransferPaintBuffer(void *pOutput, const portable_samplepair_t *pfront, int lpaintedtime, int endtime)
+{
+ int out_idx; // mono sample index
+ int count; // number of mono samples to output
+ int out_mask;
+ int step;
+ int val;
+ int nSoundVol;
+ const int *p;
+
+ if ( IsX360() )
+ {
+ // not the right path for 360
+ Assert( 0 );
+ return;
+ }
+
+ Assert( pOutput );
+
+ p = (const int *) pfront;
+
+ count = ((endtime - lpaintedtime) * g_AudioDevice->DeviceChannels());
+
+ out_mask = g_AudioDevice->DeviceSampleCount() - 1;
+
+ // 44k: remove old 22k sound support << HISPEED_DMA
+ // out_idx = ((paintedtime << HISPEED_DMA) * g_AudioDevice->DeviceChannels()) & out_mask;
+
+ out_idx = (lpaintedtime * g_AudioDevice->DeviceChannels()) & out_mask;
+
+ step = 3 - g_AudioDevice->DeviceChannels(); // mono output buffer - step 2, stereo - step 1
+ nSoundVol = S_GetMasterVolume()*256;
+
+ if (g_AudioDevice->DeviceSampleBits() == 16)
+ {
+ short *out = (short *) pOutput;
+ while (count--)
+ {
+ val = (*p * nSoundVol) >> 8;
+ p+= step;
+ val = CLIP(val);
+
+ out[out_idx] = val;
+ out_idx = (out_idx + 1) & out_mask;
+ }
+ }
+ else if (g_AudioDevice->DeviceSampleBits() == 8)
+ {
+ unsigned char *out = (unsigned char *) pOutput;
+ while (count--)
+ {
+ val = (*p * nSoundVol) >> 8;
+ p+= step;
+ val = CLIP(val);
+
+ out[out_idx] = (val>>8) + 128;
+ out_idx = (out_idx + 1) & out_mask;
+ }
+ }
+}
+
+/*
+===============================================================================
+
+CHANNEL MIXING
+
+===============================================================================
+*/
+
+
+// free channel so that it may be allocated by the
+// next request to play a sound. If sound is a
+// word in a sentence, release the sentence.
+// Works for static, dynamic, sentence and stream sounds
+
+void S_FreeChannel(channel_t *ch)
+{
+ // Don't reenter in here (can happen inside voice code).
+ if ( ch->flags.m_bIsFreeingChannel )
+ return;
+ ch->flags.m_bIsFreeingChannel = true;
+
+ SND_CloseMouth(ch);
+
+ g_pSoundServices->OnSoundStopped( ch->guid, ch->soundsource, ch->entchannel, ch->sfx->getname() );
+
+ ch->flags.isSentence = false;
+// Msg("End sound %s\n", ch->sfx->getname() );
+
+ delete ch->pMixer;
+ ch->pMixer = NULL;
+ ch->sfx = NULL;
+
+ // zero all data in channel
+ g_ActiveChannels.Remove( ch );
+ Q_memset(ch, 0, sizeof(channel_t));
+}
+
+
+// Mix all channels into active paintbuffers until paintbuffer is full or 'endtime' is reached.
+// endtime: time in 44khz samples to mix
+// rate: ignore samples which are not natively at this rate (for multipass mixing/filtering)
+// if rate == SOUND_ALL_RATES then mix all samples this pass
+// flags: if SOUND_MIX_DRY, then mix only samples with channel flagged as 'dry'
+// outputRate: target mix rate for all samples. Note, if outputRate = SOUND_DMA_SPEED, then
+// this routine will fill the paintbuffer to endtime. Otherwise, fewer samples are mixed.
+// if (endtime - paintedtime) is not aligned on boundaries of 4,
+// we'll miss data if outputRate < SOUND_DMA_SPEED!
+void MIX_MixChannelsToPaintbuffer( CChannelList &list, int endtime, int flags, int rate, int outputRate )
+{
+ VPROF( "MixChannelsToPaintbuffer" );
+ int i;
+ int sampleCount;
+
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s c:%d %d/%d", __FUNCTION__, list.Count(), rate, outputRate );
+
+ // mix each channel into paintbuffer
+ // validate parameters
+ Assert( outputRate <= SOUND_DMA_SPEED );
+ Assert( !((endtime - g_paintedtime) & 0x3) || (outputRate == SOUND_DMA_SPEED) ); // make sure we're not discarding data
+
+ // 44k: try to mix this many samples at outputRate
+ sampleCount = ( endtime - g_paintedtime ) / ( SOUND_DMA_SPEED / outputRate );
+ if ( sampleCount <= 0 )
+ return;
+
+ // Apply a global pitch shift if we're playing back a time-scaled replay
+ float flGlobalPitchScale = 1.0f;
+
+#if defined( REPLAY_ENABLED )
+ extern IDemoPlayer *g_pReplayDemoPlayer;
+ if ( demoplayer->IsPlayingBack() && demoplayer == g_pReplayDemoPlayer )
+ {
+ // adjust time scale if playing back demo
+ flGlobalPitchScale = demoplayer->GetPlaybackTimeScale();
+ }
+#endif
+
+ for ( i = list.Count(); --i >= 0; )
+ {
+ channel_t *ch = list.GetChannel( i );
+ Assert( ch->sfx );
+ // must never have a 'dry' and 'speaker' set - causes double mixing & double data reading
+ Assert ( !( ( ch->flags.bdry && ch->flags.bSpeaker ) || ( ch->flags.bdry && ch->special_dsp != 0 ) ) );
+
+ // if mixing with SOUND_MIX_DRY flag, ignore (don't even load) all channels not flagged as 'dry'
+ if ( flags == SOUND_MIX_DRY )
+ {
+ if ( !ch->flags.bdry )
+ continue;
+ }
+
+ // if mixing with SOUND_MIX_WET flag, ignore (don't even load) all channels flagged as 'dry' or 'speaker'
+ if ( flags == SOUND_MIX_WET )
+ {
+ if ( ch->flags.bdry || ch->flags.bSpeaker || ch->special_dsp != 0 )
+ continue;
+ }
+
+ // if mixing with SOUND_MIX_SPEAKER flag, ignore (don't even load) all channels not flagged as 'speaker'
+ if ( flags == SOUND_MIX_SPEAKER )
+ {
+ if ( !ch->flags.bSpeaker )
+ continue;
+ }
+
+ // if mixing with SOUND_MIX_SPEAKER flag, ignore (don't even load) all channels not flagged as 'speaker'
+ if ( flags == SOUND_MIX_SPECIAL_DSP )
+ {
+ if ( ch->special_dsp == 0 )
+ continue;
+ }
+
+ // multipass mixing - only mix samples of specified sample rate
+ switch ( rate )
+ {
+ case SOUND_11k:
+ case SOUND_22k:
+ case SOUND_44k:
+ if ( rate != ch->sfx->pSource->SampleRate() )
+ continue;
+ break;
+ default:
+ case SOUND_ALL_RATES:
+ break;
+ }
+
+ // Tracker 20771, if breen is speaking through the monitor, the client doesn't have an entity
+ // for the "soundsource" but we still need the lipsync to pause if the game is paused. Therefore
+ // I changed SND_IsMouth to look for any .wav on any channels which has sentence data
+ bool bIsMouth = SND_IsMouth(ch);
+ bool bShouldPause = IsX360() ? !ch->sfx->m_bIsUISound : bIsMouth;
+
+ // Tracker 14637: Pausing the game pauses voice sounds, but not other sounds...
+ if ( bShouldPause && g_pSoundServices->IsGamePaused() )
+ {
+ continue;
+ }
+
+ if ( bIsMouth )
+ {
+ if ( ( ch->soundsource == SOUND_FROM_UI_PANEL ) || entitylist->GetClientEntity(ch->soundsource) ||
+ ( ch->flags.bSpeaker && entitylist->GetClientEntity( ch->speakerentity ) ) )
+ {
+ // UNDONE: recode this as a member function of CAudioMixer
+ SND_MoveMouth8(ch, ch->sfx->pSource, sampleCount);
+ }
+ }
+
+ // mix channel to all active paintbuffers:
+ // mix 'dry' sounds only to dry paintbuffer.
+ // mix 'speaker' sounds only to speaker paintbuffer.
+ // mix all other sounds between room, facing & facingaway paintbuffers
+ // NOTE: must be called once per channel only - consecutive calls retrieve additional data.
+ float flPitch = ch->pitch;
+ ch->pitch *= flGlobalPitchScale;
+
+ if (list.IsQuashed(i))
+ {
+ // If the sound has been silenced as a performance heuristic, quash it.
+ ch->pMixer->SkipSamples( ch, sampleCount, outputRate, 0 );
+ // DevMsg("Quashed channel %d (%s)\n", i, ch->sfx->GetFileName());
+ }
+ else
+ {
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "MixDataToDevice" );
+ ch->pMixer->MixDataToDevice( g_AudioDevice, ch, sampleCount, outputRate, 0 );
+ }
+
+ // restore to original pitch settings
+ ch->pitch = flPitch;
+
+ if ( !ch->pMixer->ShouldContinueMixing() )
+ {
+ S_FreeChannel( ch );
+ list.RemoveChannelFromList(i);
+ }
+ if ( (ch->nFreeChannelAtSampleTime > 0 && (int)ch->nFreeChannelAtSampleTime <= endtime) )
+ {
+ S_FreeChannel( ch );
+ list.RemoveChannelFromList(i);
+ }
+ }
+}
+
+// pass in index -1...count+2, return pointer to source sample in either paintbuffer or delay buffer
+inline portable_samplepair_t * S_GetNextpFilter(int i, portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem)
+{
+ // The delay buffer is assumed to precede the paintbuffer by 6 duplicated samples
+ if (i == -1)
+ return (&(pfiltermem[0]));
+ if (i == 0)
+ return (&(pfiltermem[1]));
+ if (i == 1)
+ return (&(pfiltermem[2]));
+
+ // return from paintbuffer, where samples are doubled.
+ // even samples are to be replaced with interpolated value.
+
+ return (&(pbuffer[(i-2)*2 + 1]));
+}
+
+// pass forward over passed in buffer and cubic interpolate all odd samples
+// pbuffer: buffer to filter (in place)
+// prevfilter: filter memory. NOTE: this must match the filtertype ie: filtercubic[] for FILTERTYPE_CUBIC
+// if NULL then perform no filtering. UNDONE: should have a filter memory array type
+// count: how many samples to upsample. will become count*2 samples in buffer, in place.
+
+void S_Interpolate2xCubic( portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int count )
+{
+
+// implement cubic interpolation on 2x upsampled buffer. Effectively delays buffer contents by 2 samples.
+// pbuffer: contains samples at 0, 2, 4, 6...
+// temppaintbuffer is temp buffer, of same or larger size than a paintbuffer, used to store processed values
+// count: number of samples to process in buffer ie: how many samples at 0, 2, 4, 6...
+
+// finpos is the fractional, inpos the integer part.
+// finpos = 0.5 for upsampling by 2x
+// inpos is the position of the sample
+
+// xm1 = x [inpos - 1];
+// x0 = x [inpos + 0];
+// x1 = x [inpos + 1];
+// x2 = x [inpos + 2];
+// a = (3 * (x0-x1) - xm1 + x2) / 2;
+// b = 2*x1 + xm1 - (5*x0 + x2) / 2;
+// c = (x1 - xm1) / 2;
+// y [outpos] = (((a * finpos) + b) * finpos + c) * finpos + x0;
+
+ int i, upCount = count << 1;
+ int a, b, c;
+ int xm1, x0, x1, x2;
+ portable_samplepair_t *psamp0;
+ portable_samplepair_t *psamp1;
+ portable_samplepair_t *psamp2;
+ portable_samplepair_t *psamp3;
+ int outpos = 0;
+
+ Assert (upCount <= PAINTBUFFER_SIZE);
+
+ // pfiltermem holds 6 samples from previous buffer pass
+
+ // process 'count' samples
+
+ for ( i = 0; i < count; i++)
+ {
+
+ // get source sample pointer
+
+ psamp0 = S_GetNextpFilter(i-1, pbuffer, pfiltermem);
+ psamp1 = S_GetNextpFilter(i, pbuffer, pfiltermem);
+ psamp2 = S_GetNextpFilter(i+1, pbuffer, pfiltermem);
+ psamp3 = S_GetNextpFilter(i+2, pbuffer, pfiltermem);
+
+ // write out original sample to interpolation buffer
+
+ g_temppaintbuffer[outpos++] = *psamp1;
+
+ // get all left samples for interpolation window
+
+ xm1 = psamp0->left;
+ x0 = psamp1->left;
+ x1 = psamp2->left;
+ x2 = psamp3->left;
+
+ // interpolate
+
+ a = (3 * (x0-x1) - xm1 + x2) / 2;
+ b = 2*x1 + xm1 - (5*x0 + x2) / 2;
+ c = (x1 - xm1) / 2;
+
+ // write out interpolated sample
+
+ g_temppaintbuffer[outpos].left = a/8 + b/4 + c/2 + x0;
+
+ // get all right samples for window
+
+ xm1 = psamp0->right;
+ x0 = psamp1->right;
+ x1 = psamp2->right;
+ x2 = psamp3->right;
+
+ // interpolate
+
+ a = (3 * (x0-x1) - xm1 + x2) / 2;
+ b = 2*x1 + xm1 - (5*x0 + x2) / 2;
+ c = (x1 - xm1) / 2;
+
+ // write out interpolated sample, increment output counter
+ g_temppaintbuffer[outpos++].right = a/8 + b/4 + c/2 + x0;
+
+ Assert( outpos <= TEMP_COPY_BUFFER_SIZE );
+ }
+
+ Assert(cfltmem >= 3);
+
+ // save last 3 samples from paintbuffer
+
+ pfiltermem[0] = pbuffer[upCount - 5];
+ pfiltermem[1] = pbuffer[upCount - 3];
+ pfiltermem[2] = pbuffer[upCount - 1];
+
+ // copy temppaintbuffer back into paintbuffer
+
+ for (i = 0; i < upCount; i++)
+ pbuffer[i] = g_temppaintbuffer[i];
+}
+
+// pass forward over passed in buffer and linearly interpolate all odd samples
+// pbuffer: buffer to filter (in place)
+// prevfilter: filter memory. NOTE: this must match the filtertype ie: filterlinear[] for FILTERTYPE_LINEAR
+// if NULL then perform no filtering.
+// count: how many samples to upsample. will become count*2 samples in buffer, in place.
+
+void S_Interpolate2xLinear( portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int count )
+{
+ int i, upCount = count<<1;
+
+ Assert (upCount <= PAINTBUFFER_SIZE);
+ Assert (cfltmem >= 1);
+
+ // use interpolation value from previous mix
+
+ pbuffer[0].left = (pfiltermem->left + pbuffer[0].left) >> 1;
+ pbuffer[0].right = (pfiltermem->right + pbuffer[0].right) >> 1;
+
+ for ( i = 2; i < upCount; i+=2)
+ {
+ // use linear interpolation for upsampling
+
+ pbuffer[i].left = (pbuffer[i].left + pbuffer[i-1].left) >> 1;
+ pbuffer[i].right = (pbuffer[i].right + pbuffer[i-1].right) >> 1;
+ }
+
+ // save last value to be played out in buffer
+
+ *pfiltermem = pbuffer[upCount - 1];
+}
+
+
+// Optimized routine. 2.27X faster than the above routine
+void S_Interpolate2xLinear_2( int count, portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem )
+{
+ Assert (cfltmem >= 1);
+
+ int sample = count-1;
+ int end = (count*2)-1;
+ portable_samplepair_t *pwrite = &pbuffer[end];
+ portable_samplepair_t *pread = &pbuffer[sample];
+ portable_samplepair_t last = pread[0];
+ pread--;
+
+ // PERFORMANCE: Unroll the loop 8 times. This improves speed quite a bit
+ for ( ;sample >= 8; sample -= 8 )
+ {
+ pwrite[0] = last;
+ pwrite[-1].left = (pread[0].left + last.left)>>1;
+ pwrite[-1].right = (pread[0].right + last.right)>>1;
+ last = pread[0];
+
+ pwrite[-2] = last;
+ pwrite[-3].left = (pread[-1].left + last.left)>>1;
+ pwrite[-3].right = (pread[-1].right + last.right)>>1;
+ last = pread[-1];
+
+ pwrite[-4] = last;
+ pwrite[-5].left = (pread[-2].left + last.left)>>1;
+ pwrite[-5].right = (pread[-2].right + last.right)>>1;
+ last = pread[-2];
+
+ pwrite[-6] = last;
+ pwrite[-7].left = (pread[-3].left + last.left)>>1;
+ pwrite[-7].right = (pread[-3].right + last.right)>>1;
+ last = pread[-3];
+
+ pwrite[-8] = last;
+ pwrite[-9].left = (pread[-4].left + last.left)>>1;
+ pwrite[-9].right = (pread[-4].right + last.right)>>1;
+ last = pread[-4];
+
+ pwrite[-10] = last;
+ pwrite[-11].left = (pread[-5].left + last.left)>>1;
+ pwrite[-11].right = (pread[-5].right + last.right)>>1;
+ last = pread[-5];
+
+ pwrite[-12] = last;
+ pwrite[-13].left = (pread[-6].left + last.left)>>1;
+ pwrite[-13].right = (pread[-6].right + last.right)>>1;
+ last = pread[-6];
+
+ pwrite[-14] = last;
+ pwrite[-15].left = (pread[-7].left + last.left)>>1;
+ pwrite[-15].right = (pread[-7].right + last.right)>>1;
+ last = pread[-7];
+
+ pread -= 8;
+ pwrite -= 16;
+ }
+ while ( pread >= pbuffer )
+ {
+ pwrite[0] = last;
+ pwrite[-1].left = (pread[0].left + last.left)>>1;
+ pwrite[-1].right = (pread[0].right + last.right)>>1;
+ last = pread[0];
+ pread--;
+ pwrite-=2;
+ }
+ pbuffer[1] = last;
+ pbuffer[0].left = (pfiltermem->left + last.left) >> 1;
+ pbuffer[0].right = (pfiltermem->right + last.right) >> 1;
+ *pfiltermem = pbuffer[end];
+}
+
+
+// upsample by 2x, optionally using interpolation
+// count: how many samples to upsample. will become count*2 samples in buffer, in place.
+// pbuffer: buffer to upsample into (in place)
+// pfiltermem: filter memory. NOTE: this must match the filtertype ie: filterlinear[] for FILTERTYPE_LINEAR
+// if NULL then perform no filtering.
+// cfltmem: max number of sample pairs filter can use
+// filtertype: FILTERTYPE_NONE, _LINEAR, _CUBIC etc. Must match prevfilter.
+void S_MixBufferUpsample2x( int count, portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int filtertype )
+{
+ // JAY: Optimized this routine. Test then remove old routine.
+ // NOTE: Has been proven equivalent by comparing output.
+ if ( filtertype == FILTERTYPE_LINEAR )
+ {
+ S_Interpolate2xLinear_2( count, pbuffer, pfiltermem, cfltmem );
+ return;
+ }
+ int i, j, upCount = count<<1;
+
+ // reverse through buffer, duplicating contents for 'count' samples
+
+ for (i = upCount - 1, j = count - 1; j >= 0; i-=2, j--)
+ {
+ pbuffer[i] = pbuffer[j];
+ pbuffer[i-1] = pbuffer[j];
+ }
+
+ // pass forward through buffer, interpolate all even slots
+
+ switch (filtertype)
+ {
+ default:
+ break;
+ case FILTERTYPE_LINEAR:
+ S_Interpolate2xLinear(pbuffer, pfiltermem, cfltmem, count);
+ break;
+ case FILTERTYPE_CUBIC:
+ S_Interpolate2xCubic(pbuffer, pfiltermem, cfltmem, count);
+ break;
+ }
+}
+
+//===============================================================================
+// PAINTBUFFER ROUTINES
+//===============================================================================
+
+
+// Set current paintbuffer to pbuf.
+// The set paintbuffer is used by all subsequent mixing, upsampling and dsp routines.
+// Also sets the rear paintbuffer if paintbuffer has fsurround true.
+// (otherwise, rearpaintbuffer is NULL)
+
+void MIX_SetCurrentPaintbuffer(int ipaintbuffer)
+{
+ // set front and rear paintbuffer
+
+ Assert(ipaintbuffer < g_paintBuffers.Count());
+
+ g_curpaintbuffer = g_paintBuffers[ipaintbuffer].pbuf;
+
+ if ( g_paintBuffers[ipaintbuffer].fsurround )
+ {
+ g_currearpaintbuffer = g_paintBuffers[ipaintbuffer].pbufrear;
+
+ g_curcenterpaintbuffer = NULL;
+
+ if ( g_paintBuffers[ipaintbuffer].fsurround_center )
+ g_curcenterpaintbuffer = g_paintBuffers[ipaintbuffer].pbufcenter;
+ }
+ else
+ {
+ g_currearpaintbuffer = NULL;
+ g_curcenterpaintbuffer = NULL;
+ }
+
+ Assert(g_curpaintbuffer != NULL);
+}
+
+// return index to current paintbuffer
+
+int MIX_GetCurrentPaintbufferIndex( void )
+{
+ int i;
+
+ for ( i = 0; i < g_paintBuffers.Count(); i++ )
+ {
+ if (g_curpaintbuffer == g_paintBuffers[i].pbuf)
+ return i;
+ }
+
+ return 0;
+}
+
+
+// return pointer to current paintbuffer struct
+
+paintbuffer_t *MIX_GetCurrentPaintbufferPtr( void )
+{
+ int ipaint = MIX_GetCurrentPaintbufferIndex();
+
+ Assert( ipaint < g_paintBuffers.Count() );
+
+ return &g_paintBuffers[ipaint];
+}
+
+
+// return pointer to front paintbuffer pbuf, given index
+
+inline portable_samplepair_t *MIX_GetPFrontFromIPaint(int ipaintbuffer)
+{
+ return g_paintBuffers[ipaintbuffer].pbuf;
+}
+
+paintbuffer_t *MIX_GetPPaintFromIPaint( int ipaintbuffer )
+{
+ Assert( ipaintbuffer < g_paintBuffers.Count() );
+
+ return &g_paintBuffers[ipaintbuffer];
+}
+
+
+// return pointer to rear buffer, given index.
+// returns null if fsurround is false;
+
+inline portable_samplepair_t *MIX_GetPRearFromIPaint(int ipaintbuffer)
+{
+ if ( g_paintBuffers[ipaintbuffer].fsurround )
+ return g_paintBuffers[ipaintbuffer].pbufrear;
+
+ return NULL;
+}
+
+// return pointer to center buffer, given index.
+// returns null if fsurround_center is false;
+
+inline portable_samplepair_t *MIX_GetPCenterFromIPaint(int ipaintbuffer)
+{
+ if ( g_paintBuffers[ipaintbuffer].fsurround_center )
+ return g_paintBuffers[ipaintbuffer].pbufcenter;
+
+ return NULL;
+}
+
+// return index to paintbuffer, given buffer pointer
+
+inline int MIX_GetIPaintFromPFront( portable_samplepair_t *pbuf )
+{
+ int i;
+
+ for ( i = 0; i < g_paintBuffers.Count(); i++ )
+ {
+ if ( pbuf == g_paintBuffers[i].pbuf )
+ return i;
+ }
+
+ return 0;
+}
+
+// return pointer to paintbuffer struct, given ptr to buffer data
+
+inline paintbuffer_t *MIX_GetPPaintFromPFront( portable_samplepair_t *pbuf )
+{
+ int i;
+ i = MIX_GetIPaintFromPFront( pbuf );
+
+ return &g_paintBuffers[i];
+}
+
+
+// up convert mono buffer to full surround
+
+inline void MIX_ConvertBufferToSurround( int ipaintbuffer )
+{
+ paintbuffer_t *ppaint = &g_paintBuffers[ipaintbuffer];
+
+ // duplicate channel data as needed
+
+ if ( g_AudioDevice->IsSurround() )
+ {
+ // set buffer flags
+
+ ppaint->fsurround = g_AudioDevice->IsSurround();
+ ppaint->fsurround_center = g_AudioDevice->IsSurroundCenter();
+
+ portable_samplepair_t *pfront = MIX_GetPFrontFromIPaint( ipaintbuffer );
+ portable_samplepair_t *prear = MIX_GetPRearFromIPaint( ipaintbuffer );
+ portable_samplepair_t *pcenter = MIX_GetPCenterFromIPaint( ipaintbuffer );
+
+ // copy front to rear
+ Q_memcpy(prear, pfront, sizeof(portable_samplepair_t) * PAINTBUFFER_SIZE);
+
+ // copy front to center
+ if ( g_AudioDevice->IsSurroundCenter() )
+ Q_memcpy(pcenter, pfront, sizeof(portable_samplepair_t) * PAINTBUFFER_SIZE);
+ }
+}
+
+// Activate a paintbuffer. All active paintbuffers are mixed in parallel within
+// MIX_MixChannelsToPaintbuffer, according to flags
+
+inline void MIX_ActivatePaintbuffer(int ipaintbuffer)
+{
+ Assert( ipaintbuffer < g_paintBuffers.Count() );
+ g_paintBuffers[ipaintbuffer].factive = true;
+}
+
+// Don't mix into this paintbuffer
+
+inline void MIX_DeactivatePaintbuffer(int ipaintbuffer)
+{
+ Assert( ipaintbuffer < g_paintBuffers.Count() );
+ g_paintBuffers[ipaintbuffer].factive = false;
+}
+
+// Don't mix into any paintbuffers
+
+inline void MIX_DeactivateAllPaintbuffers(void)
+{
+ int i;
+ for ( i = 0; i < g_paintBuffers.Count(); i++ )
+ g_paintBuffers[i].factive = false;
+}
+
+// set upsampling filter indexes back to 0
+
+inline void MIX_ResetPaintbufferFilterCounters( void )
+
+{
+ int i;
+ for ( i = 0; i < g_paintBuffers.Count(); i++ )
+ g_paintBuffers[i].ifilter = 0;
+}
+
+inline void MIX_ResetPaintbufferFilterCounter( int ipaintbuffer )
+{
+ Assert ( ipaintbuffer < g_paintBuffers.Count() );
+ g_paintBuffers[ipaintbuffer].ifilter = 0;
+}
+
+// Change paintbuffer's flags
+
+inline void MIX_SetPaintbufferFlags(int ipaintbuffer, int flags)
+{
+ Assert( ipaintbuffer < g_paintBuffers.Count() );
+ g_paintBuffers[ipaintbuffer].flags = flags;
+}
+
+
+// zero out all paintbuffers
+
+void MIX_ClearAllPaintBuffers( int SampleCount, bool clearFilters )
+{
+ // g_paintBuffers can be NULL with -nosound
+ if ( g_paintBuffers.Count() <= 0 )
+ {
+ return;
+ }
+
+ int i;
+ int count = min(SampleCount, PAINTBUFFER_SIZE);
+
+ // zero out all paintbuffer data (ignore sampleCount)
+
+ for ( i = 0; i < g_paintBuffers.Count(); i++ )
+ {
+ if (g_paintBuffers[i].pbuf != NULL)
+ Q_memset(g_paintBuffers[i].pbuf, 0, (count+1) * sizeof(portable_samplepair_t));
+
+ if (g_paintBuffers[i].pbufrear != NULL)
+ Q_memset(g_paintBuffers[i].pbufrear, 0, (count+1) * sizeof(portable_samplepair_t));
+
+ if (g_paintBuffers[i].pbufcenter != NULL)
+ Q_memset(g_paintBuffers[i].pbufcenter, 0, (count+1) * sizeof(portable_samplepair_t));
+
+ if ( clearFilters )
+ {
+ Q_memset( g_paintBuffers[i].fltmem, 0, sizeof(g_paintBuffers[i].fltmem) );
+ Q_memset( g_paintBuffers[i].fltmemrear, 0, sizeof(g_paintBuffers[i].fltmemrear) );
+ Q_memset( g_paintBuffers[i].fltmemcenter, 0, sizeof(g_paintBuffers[i].fltmemcenter) );
+ }
+ }
+
+ if ( clearFilters )
+ {
+ MIX_ResetPaintbufferFilterCounters();
+ }
+}
+
+#define SWAP(a,b,t) {(t) = (a); (a) = (b); (b) = (t);}
+#define AVG(a,b) (((a) + (b)) >> 1 )
+#define AVG4(a,b,c,d) (((a) + (b) + (c) + (d)) >> 2 )
+
+// Synthesize center channel from left/right values (average).
+// Currently just averages, but could actually remove
+// the center signal from the l/r channels...
+
+inline void MIX_CenterFromLeftRight( int *pl, int *pr, int *pc )
+{
+ int l = *pl;
+ int r = *pr;
+ int c = 0;
+
+
+ c = (l + r) / 2;
+
+/*
+ l = l - c/2;
+ r = r - c/2;
+
+ if (l < 0)
+ {
+ l = 0;
+ r += (-l);
+ c += (-l);
+ }
+ else if (r < 0)
+ {
+ r = 0;
+ l += (-r);
+ c += (-r);
+ }
+*/
+ *pc = c;
+// *pl = l;
+// *pr = r;
+}
+
+// mixes pbuf1 + pbuf2 into pbuf3, count samples
+// fgain is output gain 0-1.0
+// NOTE: pbuf3 may equal pbuf1 or pbuf2!
+
+// mixing algorithms:
+
+// destination 2ch:
+// pb1 2ch + pb2 2ch -> pb3 2ch
+// pb1 (4ch->2ch) + pb2 2ch -> pb3 2ch
+// pb1 2ch + pb2 (4ch->2ch) -> pb3 2ch
+// pb1 (4ch->2ch) + pb2 (4ch->2ch) -> pb3 2ch
+
+// destination 4ch:
+// pb1 4ch + pb2 4ch -> pb3 4ch
+// pb1 (2ch->4ch) + pb2 4ch -> pb3 4ch
+// pb1 4ch + pb2 (2ch->4ch) -> pb3 4ch
+// pb1 (2ch->4ch) + pb2 (2ch->4ch) -> pb3 4ch
+
+// if all buffers are 4 or 5 ch surround, mix rear & center channels into ibuf3 as well.
+
+// NOTE: for performance, conversion and mixing are done in a single pass instead of
+// a two pass channel convert + mix scheme.
+
+void MIX_MixPaintbuffers(int ibuf1, int ibuf2, int ibuf3, int count, float fgain_out)
+{
+ VPROF("Mixpaintbuffers");
+ int i;
+ portable_samplepair_t *pbuf1, *pbuf2, *pbuf3, *pbuft;
+ portable_samplepair_t *pbufrear1, *pbufrear2, *pbufrear3, *pbufreart;
+ portable_samplepair_t *pbufcenter1, *pbufcenter2, *pbufcenter3, *pbufcentert;
+ int cchan1, cchan2, cchan3, cchant;
+ int xl,xr;
+ int l,r,l2,r2,c, c2;
+ int gain_out;
+
+ gain_out = 256 * fgain_out;
+
+ Assert (count <= PAINTBUFFER_SIZE);
+ Assert (ibuf1 < g_paintBuffers.Count());
+ Assert (ibuf2 < g_paintBuffers.Count());
+ Assert (ibuf3 < g_paintBuffers.Count());
+
+ pbuf1 = g_paintBuffers[ibuf1].pbuf;
+ pbuf2 = g_paintBuffers[ibuf2].pbuf;
+ pbuf3 = g_paintBuffers[ibuf3].pbuf;
+
+ pbufrear1 = g_paintBuffers[ibuf1].pbufrear;
+ pbufrear2 = g_paintBuffers[ibuf2].pbufrear;
+ pbufrear3 = g_paintBuffers[ibuf3].pbufrear;
+
+ pbufcenter1 = g_paintBuffers[ibuf1].pbufcenter;
+ pbufcenter2 = g_paintBuffers[ibuf2].pbufcenter;
+ pbufcenter3 = g_paintBuffers[ibuf3].pbufcenter;
+
+ cchan1 = 2 + (g_paintBuffers[ibuf1].fsurround ? 2 : 0) + (g_paintBuffers[ibuf1].fsurround_center ? 1 : 0);
+ cchan2 = 2 + (g_paintBuffers[ibuf2].fsurround ? 2 : 0) + (g_paintBuffers[ibuf2].fsurround_center ? 1 : 0);
+ cchan3 = 2 + (g_paintBuffers[ibuf3].fsurround ? 2 : 0) + (g_paintBuffers[ibuf3].fsurround_center ? 1 : 0);
+
+ // make sure pbuf1 always has fewer or equal channels than pbuf2
+ // NOTE: pbuf3 may equal pbuf1 or pbuf2!
+
+ if ( cchan2 < cchan1 )
+ {
+ SWAP( cchan1, cchan2, cchant );
+ SWAP( pbuf1, pbuf2, pbuft );
+ SWAP( pbufrear1, pbufrear2, pbufreart );
+ SWAP( pbufcenter1, pbufcenter2, pbufcentert);
+ }
+
+
+ // UNDONE: implement fast mixing routines for each of the following sections
+
+ // destination buffer stereo - average n chans down to stereo
+
+ if ( cchan3 == 2 )
+ {
+ // destination 2ch:
+ // pb1 2ch + pb2 2ch -> pb3 2ch
+ // pb1 2ch + pb2 (4ch->2ch) -> pb3 2ch
+ // pb1 (4ch->2ch) + pb2 (4ch->2ch) -> pb3 2ch
+
+ if ( cchan1 == 2 && cchan2 == 2 )
+ {
+ // mix front channels
+
+ for (i = 0; i < count; i++)
+ {
+ pbuf3[i].left = pbuf1[i].left + pbuf2[i].left;
+ pbuf3[i].right = pbuf1[i].right + pbuf2[i].right;
+ }
+ goto gain2ch;
+ }
+
+ if ( cchan1 == 2 && cchan2 == 4 )
+ {
+ // avg rear chan l/r
+
+ for (i = 0; i < count; i++)
+ {
+ pbuf3[i].left = pbuf1[i].left + AVG( pbuf2[i].left, pbufrear2[i].left );
+ pbuf3[i].right = pbuf1[i].right + AVG( pbuf2[i].right, pbufrear2[i].right );
+ }
+ goto gain2ch;
+ }
+
+ if ( cchan1 == 4 && cchan2 == 4 )
+ {
+ // avg rear chan l/r
+
+ for (i = 0; i < count; i++)
+ {
+ pbuf3[i].left = AVG( pbuf1[i].left, pbufrear1[i].left) + AVG( pbuf2[i].left, pbufrear2[i].left );
+ pbuf3[i].right = AVG( pbuf1[i].right, pbufrear1[i].right) + AVG( pbuf2[i].right, pbufrear2[i].right );
+ }
+ goto gain2ch;
+ }
+
+ if ( cchan1 == 2 && cchan2 == 5 )
+ {
+ // avg rear chan l/r + center split into left/right
+
+ for (i = 0; i < count; i++)
+ {
+ l = pbuf2[i].left + ((pbufcenter2[i].left) >> 1);
+ r = pbuf2[i].right + ((pbufcenter2[i].left) >> 1);
+
+ pbuf3[i].left = pbuf1[i].left + AVG( l, pbufrear2[i].left );
+ pbuf3[i].right = pbuf1[i].right + AVG( r, pbufrear2[i].right );
+ }
+ goto gain2ch;
+ }
+
+ if ( cchan1 == 4 && cchan2 == 5)
+ {
+ for (i = 0; i < count; i++)
+ {
+ l = pbuf2[i].left + ((pbufcenter2[i].left) >> 1);
+ r = pbuf2[i].right + ((pbufcenter2[i].left) >> 1);
+
+ pbuf3[i].left = AVG( pbuf1[i].left, pbufrear1[i].left) + AVG( l, pbufrear2[i].left );
+ pbuf3[i].right = AVG( pbuf1[i].right, pbufrear1[i].right) + AVG( r, pbufrear2[i].right );
+ }
+ goto gain2ch;
+ }
+
+ if ( cchan1 == 5 && cchan2 == 5)
+ {
+ for (i = 0; i < count; i++)
+ {
+ l = pbuf1[i].left + ((pbufcenter1[i].left) >> 1);
+ r = pbuf1[i].right + ((pbufcenter1[i].left) >> 1);
+
+ l2 = pbuf2[i].left + ((pbufcenter2[i].left) >> 1);
+ r2 = pbuf2[i].right + ((pbufcenter2[i].left) >> 1);
+
+ pbuf3[i].left = AVG( l, pbufrear1[i].left) + AVG( l2, pbufrear2[i].left );
+ pbuf3[i].right = AVG( r, pbufrear1[i].right) + AVG( r2, pbufrear2[i].right );
+ } goto gain2ch;
+ }
+
+ }
+
+ // destination buffer quad - duplicate n chans up to quad
+
+ if ( cchan3 == 4 )
+ {
+
+ // pb1 4ch + pb2 4ch -> pb3 4ch
+ // pb1 (2ch->4ch) + pb2 4ch -> pb3 4ch
+ // pb1 (2ch->4ch) + pb2 (2ch->4ch) -> pb3 4ch
+
+ if ( cchan1 == 4 && cchan2 == 4)
+ {
+ // mix front -> front, rear -> rear
+
+ for (i = 0; i < count; i++)
+ {
+ pbuf3[i].left = pbuf1[i].left + pbuf2[i].left;
+ pbuf3[i].right = pbuf1[i].right + pbuf2[i].right;
+
+ pbufrear3[i].left = pbufrear1[i].left + pbufrear2[i].left;
+ pbufrear3[i].right = pbufrear1[i].right + pbufrear2[i].right;
+ }
+ goto gain4ch;
+ }
+
+ if ( cchan1 == 2 && cchan2 == 4)
+ {
+
+ for (i = 0; i < count; i++)
+ {
+ // split 2 ch left -> front left, rear left
+ // split 2 ch right -> front right, rear right
+
+ xl = pbuf1[i].left;
+ xr = pbuf1[i].right;
+
+ pbuf3[i].left = xl + pbuf2[i].left;
+ pbuf3[i].right = xr + pbuf2[i].right;
+
+ pbufrear3[i].left = xl + pbufrear2[i].left;
+ pbufrear3[i].right = xr + pbufrear2[i].right;
+ }
+ goto gain4ch;
+ }
+
+ if ( cchan1 == 2 && cchan2 == 2)
+ {
+ // mix l,r, split into front l, front r
+
+ for (i = 0; i < count; i++)
+ {
+ xl = pbuf1[i].left + pbuf2[i].left;
+ xr = pbuf1[i].right + pbuf2[i].right;
+
+ pbufrear3[i].left = pbuf3[i].left = xl;
+ pbufrear3[i].right = pbuf3[i].right = xr;
+ }
+ goto gain4ch;
+ }
+
+
+ if ( cchan1 == 2 && cchan2 == 5 )
+ {
+ for (i = 0; i < count; i++)
+ {
+ // split center of chan2 into left/right
+
+ l2 = pbuf2[i].left + ((pbufcenter2[i].left) >> 1);
+ r2 = pbuf2[i].right + ((pbufcenter2[i].left) >> 1);
+
+ xl = pbuf1[i].left;
+ xr = pbuf1[i].right;
+
+ pbuf3[i].left = xl + l2;
+ pbuf3[i].right = xr + r2;
+
+ pbufrear3[i].left = xl + pbufrear2[i].left;
+ pbufrear3[i].right = xr + pbufrear2[i].right;
+ }
+ goto gain4ch;
+ }
+
+ if ( cchan1 == 4 && cchan2 == 5)
+ {
+
+ for (i = 0; i < count; i++)
+ {
+ l2 = pbuf2[i].left + ((pbufcenter2[i].left) >> 1);
+ r2 = pbuf2[i].right + ((pbufcenter2[i].left) >> 1);
+
+ pbuf3[i].left = pbuf1[i].left + l2;
+ pbuf3[i].right = pbuf1[i].right + r2;
+
+ pbufrear3[i].left = pbufrear1[i].left + pbufrear2[i].left;
+ pbufrear3[i].right = pbufrear1[i].right + pbufrear2[i].right;
+ }
+ goto gain4ch;
+ }
+
+ if ( cchan1 == 5 && cchan2 == 5 )
+ {
+ for (i = 0; i < count; i++)
+ {
+ l = pbuf1[i].left + ((pbufcenter1[i].left) >> 1);
+ r = pbuf1[i].right + ((pbufcenter1[i].left) >> 1);
+
+ l2 = pbuf2[i].left + ((pbufcenter2[i].left) >> 1);
+ r2 = pbuf2[i].right + ((pbufcenter2[i].left) >> 1);
+
+ pbuf3[i].left = l + l2;
+ pbuf3[i].right = r + r2;
+
+ pbufrear3[i].left = pbufrear1[i].left + pbufrear2[i].left;
+ pbufrear3[i].right = pbufrear1[i].right + pbufrear2[i].right;
+ }
+ goto gain4ch;
+ }
+ }
+
+ // 5 channel destination
+
+ if (cchan3 == 5)
+ {
+ // up convert from 2 or 4 ch buffer to 5 ch buffer:
+ // center channel is synthesized from front left, front right
+
+ if (cchan1 == 2 && cchan2 == 2)
+ {
+ for (i = 0; i < count; i++)
+ {
+ // split 2 ch left -> front left, center, rear left
+ // split 2 ch right -> front right, center, rear right
+
+ l = pbuf1[i].left;
+ r = pbuf1[i].right;
+
+ MIX_CenterFromLeftRight(&l, &r, &c);
+
+ l2 = pbuf2[i].left;
+ r2 = pbuf2[i].right;
+
+ MIX_CenterFromLeftRight(&l2, &r2, &c2);
+
+ pbuf3[i].left = l + l2;
+ pbuf3[i].right = r + r2;
+
+ pbufrear3[i].left = pbuf1[i].left + pbuf2[i].left;
+ pbufrear3[i].right = pbuf1[i].right + pbuf2[i].right;
+
+ pbufcenter3[i].left = c + c2;
+ }
+ goto gain5ch;
+ }
+
+ if (cchan1 == 2 && cchan2 == 4)
+ {
+ for (i = 0; i < count; i++)
+ {
+ l = pbuf1[i].left;
+ r = pbuf1[i].right;
+
+ MIX_CenterFromLeftRight(&l, &r, &c);
+
+ l2 = pbuf2[i].left;
+ r2 = pbuf2[i].right;
+
+ MIX_CenterFromLeftRight(&l2, &r2, &c2);
+
+ pbuf3[i].left = l + l2;
+ pbuf3[i].right = r + r2;
+
+ pbufrear3[i].left = pbuf1[i].left + pbufrear2[i].left;
+ pbufrear3[i].right = pbuf1[i].right + pbufrear2[i].right;
+
+ pbufcenter3[i].left = c + c2;
+ }
+ goto gain5ch;
+ }
+
+ if (cchan1 == 2 && cchan2 == 5)
+ {
+ for (i = 0; i < count; i++)
+ {
+ l = pbuf1[i].left;
+ r = pbuf1[i].right;
+
+ MIX_CenterFromLeftRight(&l, &r, &c);
+
+ pbuf3[i].left = l + pbuf2[i].left;
+ pbuf3[i].right = r + pbuf2[i].right;
+
+ pbufrear3[i].left = pbuf1[i].left + pbufrear2[i].left;
+ pbufrear3[i].right = pbuf1[i].right + pbufrear2[i].right;
+
+ pbufcenter3[i].left = c + pbufcenter2[i].left;
+ }
+ goto gain5ch;
+ }
+
+ if (cchan1 == 4 && cchan2 == 4)
+ {
+ for (i = 0; i < count; i++)
+ {
+ l = pbuf1[i].left;
+ r = pbuf1[i].right;
+
+ MIX_CenterFromLeftRight(&l, &r, &c);
+
+ l2 = pbuf2[i].left;
+ r2 = pbuf2[i].right;
+
+ MIX_CenterFromLeftRight(&l2, &r2, &c2);
+
+ pbuf3[i].left = l + l2;
+ pbuf3[i].right = r + r2;
+
+ pbufrear3[i].left = pbufrear1[i].left + pbufrear2[i].left;
+ pbufrear3[i].right = pbufrear1[i].right + pbufrear2[i].right;
+
+ pbufcenter3[i].left = c + c2;
+ }
+ goto gain5ch;
+ }
+
+
+ if (cchan1 == 4 && cchan2 == 5)
+ {
+ for (i = 0; i < count; i++)
+ {
+ l = pbuf1[i].left;
+ r = pbuf1[i].right;
+
+ MIX_CenterFromLeftRight(&l, &r, &c);
+
+ pbuf3[i].left = l + pbuf2[i].left;
+ pbuf3[i].right = r + pbuf2[i].right;
+
+ pbufrear3[i].left = pbufrear1[i].left + pbufrear2[i].left;
+ pbufrear3[i].right = pbufrear1[i].right + pbufrear2[i].right;
+
+ pbufcenter3[i].left = c + pbufcenter2[i].left;
+ }
+ goto gain5ch;
+ }
+
+ if ( cchan2 == 5 && cchan1 == 5 )
+ {
+ for (i = 0; i < count; i++)
+ {
+ pbuf3[i].left = pbuf1[i].left + pbuf2[i].left;
+ pbuf3[i].right = pbuf1[i].right + pbuf2[i].right;
+ pbufrear3[i].left = pbufrear1[i].left + pbufrear2[i].left;
+ pbufrear3[i].right = pbufrear1[i].right + pbufrear2[i].right;
+ pbufcenter3[i].left = pbufcenter1[i].left + pbufcenter2[i].left;
+ }
+ goto gain5ch;
+ }
+ }
+
+gain2ch:
+ if ( gain_out == 256) // KDB: perf
+ return;
+
+ for (i = 0; i < count; i++)
+ {
+ pbuf3[i].left = (pbuf3[i].left * gain_out) >> 8;
+ pbuf3[i].right = (pbuf3[i].right * gain_out) >> 8;
+ }
+ return;
+
+gain4ch:
+ if ( gain_out == 256) // KDB: perf
+ return;
+
+ for (i = 0; i < count; i++)
+ {
+ pbuf3[i].left = (pbuf3[i].left * gain_out) >> 8;
+ pbuf3[i].right = (pbuf3[i].right * gain_out) >> 8;
+ pbufrear3[i].left = (pbufrear3[i].left * gain_out) >> 8;
+ pbufrear3[i].right = (pbufrear3[i].right * gain_out) >> 8;
+ }
+ return;
+
+gain5ch:
+ if ( gain_out == 256) // KDB: perf
+ return;
+
+ for (i = 0; i < count; i++)
+ {
+ pbuf3[i].left = (pbuf3[i].left * gain_out) >> 8;
+ pbuf3[i].right = (pbuf3[i].right * gain_out) >> 8;
+ pbufrear3[i].left = (pbufrear3[i].left * gain_out) >> 8;
+ pbufrear3[i].right = (pbufrear3[i].right * gain_out) >> 8;
+ pbufcenter3[i].left = (pbufcenter3[i].left * gain_out) >> 8;
+ }
+ return;
+}
+
+// multiply all values in paintbuffer by fgain
+
+void MIX_ScalePaintBuffer( int bufferIndex, int count, float fgain )
+{
+ portable_samplepair_t *pbuf = g_paintBuffers[bufferIndex].pbuf;
+ portable_samplepair_t *pbufrear = g_paintBuffers[bufferIndex].pbufrear;
+ portable_samplepair_t *pbufcenter = g_paintBuffers[bufferIndex].pbufcenter;
+
+ int gain = 256 * fgain;
+ int i;
+
+ if (gain == 256)
+ return;
+
+ if ( !g_paintBuffers[bufferIndex].fsurround )
+ {
+ for (i = 0; i < count; i++)
+ {
+ pbuf[i].left = (pbuf[i].left * gain) >> 8;
+ pbuf[i].right = (pbuf[i].right * gain) >> 8;
+ }
+ }
+ else
+ {
+ for (i = 0; i < count; i++)
+ {
+ pbuf[i].left = (pbuf[i].left * gain) >> 8;
+ pbuf[i].right = (pbuf[i].right * gain) >> 8;
+ pbufrear[i].left = (pbufrear[i].left * gain) >> 8;
+ pbufrear[i].right = (pbufrear[i].right * gain) >> 8;
+ }
+
+ if (g_paintBuffers[bufferIndex].fsurround_center)
+ {
+ for (i = 0; i < count; i++)
+ {
+ pbufcenter[i].left = (pbufcenter[i].left * gain) >> 8;
+ // pbufcenter[i].right = (pbufcenter[i].right * gain) >> 8; mono center channel
+ }
+ }
+ }
+}
+
+// DEBUG peak detection values
+#define _SDEBUG 1
+
+#ifdef _SDEBUG
+float sdebug_avg_in = 0.0;
+float sdebug_in_count = 0.0;
+float sdebug_avg_out = 0.0;
+float sdebug_out_count = 0.0;
+#define SDEBUG_TOTAL_COUNT (3*44100)
+#endif // DEBUG
+
+// DEBUG code - get and show peak value of specified paintbuffer
+// DEBUG code - ibuf is buffer index, count is # samples to test, pppeakprev stores peak
+
+
+void SDEBUG_GetAvgValue( int ibuf, int count, float *pav )
+{
+#ifdef _SDEBUG
+ if (snd_showstart.GetInt() != 4 )
+ return;
+
+ float av = 0.0;
+
+ for (int i = 0; i < count; i++)
+ av += (float)(abs(g_paintBuffers[ibuf].pbuf->left) + abs(g_paintBuffers[ibuf].pbuf->right))/2.0;
+
+ *pav = av / count;
+#endif // DEBUG
+}
+
+
+void SDEBUG_GetAvgIn( int ibuf, int count)
+{
+ float av = 0.0;
+ SDEBUG_GetAvgValue( ibuf, count, &av );
+
+ sdebug_avg_in = ((av * count ) + (sdebug_avg_in * sdebug_in_count)) / (count + sdebug_in_count);
+ sdebug_in_count += count;
+}
+
+void SDEBUG_GetAvgOut( int ibuf, int count)
+{
+ float av = 0.0;
+ SDEBUG_GetAvgValue( ibuf, count, &av );
+
+ sdebug_avg_out = ((av * count ) + (sdebug_avg_out * sdebug_out_count)) / (count + sdebug_out_count);
+ sdebug_out_count += count;
+}
+
+
+void SDEBUG_ShowAvgValue()
+{
+#ifdef _SDEBUG
+ if (sdebug_in_count > SDEBUG_TOTAL_COUNT)
+ {
+ if ((int)sdebug_avg_in > 20.0 && (int)sdebug_avg_out > 20.0)
+ DevMsg("dsp avg gain:%1.2f in:%1.2f out:%1.2f 1/gain:%1.2f\n", sdebug_avg_out/sdebug_avg_in, sdebug_avg_in, sdebug_avg_out, sdebug_avg_in/sdebug_avg_out);
+
+ sdebug_avg_in = 0.0;
+ sdebug_avg_out = 0.0;
+ sdebug_in_count = 0.0;
+ sdebug_out_count = 0.0;
+ }
+#endif // DEBUG
+}
+
+// clip all values in paintbuffer to 16bit.
+// if fsurround is set for paintbuffer, also process rear buffer samples
+
+void MIX_CompressPaintbuffer(int ipaint, int count)
+{
+ VPROF("CompressPaintbuffer");
+ int i;
+ paintbuffer_t *ppaint = MIX_GetPPaintFromIPaint(ipaint);
+ portable_samplepair_t *pbf;
+ portable_samplepair_t *pbr;
+ portable_samplepair_t *pbc;
+
+ pbf = ppaint->pbuf;
+ pbr = ppaint->pbufrear;
+ pbc = ppaint->pbufcenter;
+
+ for (i = 0; i < count; i++)
+ {
+ pbf->left = CLIP(pbf->left);
+ pbf->right = CLIP(pbf->right);
+ pbf++;
+ }
+
+ if ( ppaint->fsurround )
+ {
+ Assert (pbr);
+
+ for (i = 0; i < count; i++)
+ {
+ pbr->left = CLIP(pbr->left);
+ pbr->right = CLIP(pbr->right);
+ pbr++;
+ }
+ }
+
+ if ( ppaint->fsurround_center )
+ {
+ Assert (pbc);
+
+ for (i = 0; i < count; i++)
+ {
+ pbc->left = CLIP(pbc->left);
+ //pbc->right = CLIP(pbc->right); mono center channel
+ pbc++;
+ }
+ }
+}
+
+
+// mix and upsample channels to 44khz 'ipaintbuffer'
+// mix channels matching 'flags' (SOUND_MIX_DRY, SOUND_MIX_WET, SOUND_MIX_SPEAKER) into specified paintbuffer
+// upsamples 11khz, 22khz channels to 44khz.
+
+// NOTE: only call this on channels that will be mixed into only 1 paintbuffer
+// and that will not be mixed until the next mix pass! otherwise, MIX_MixChannelsToPaintbuffer
+// will advance any internal pointers on mixed channels; subsequent calls will be at
+// incorrect offset.
+
+void MIX_MixUpsampleBuffer( CChannelList &list, int ipaintbuffer, int end, int count, int flags )
+{
+ VPROF("MixUpsampleBuffer");
+ int ipaintcur = MIX_GetCurrentPaintbufferIndex(); // save current paintbuffer
+
+ // reset paintbuffer upsampling filter index
+ MIX_ResetPaintbufferFilterCounter( ipaintbuffer );
+
+ // prevent other paintbuffers from being mixed
+ MIX_DeactivateAllPaintbuffers();
+
+ MIX_ActivatePaintbuffer( ipaintbuffer ); // operates on MIX_MixChannelsToPaintbuffer
+ MIX_SetCurrentPaintbuffer( ipaintbuffer ); // operates on MixUpSample
+
+ // mix 11khz channels to buffer
+ if ( list.m_has11kChannels )
+ {
+ MIX_MixChannelsToPaintbuffer( list, end, flags, SOUND_11k, SOUND_11k );
+
+ // upsample 11khz buffer by 2x
+ g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_11k), FILTERTYPE_LINEAR );
+ }
+
+ if ( list.m_has22kChannels || list.m_has11kChannels )
+ {
+ // mix 22khz channels to buffer
+ MIX_MixChannelsToPaintbuffer( list, end, flags, SOUND_22k, SOUND_22k );
+
+#if (SOUND_DMA_SPEED > SOUND_22k)
+ // upsample 22khz buffer by 2x
+ g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_22k), FILTERTYPE_LINEAR );
+#endif
+ }
+
+ // mix 44khz channels to buffer
+ MIX_MixChannelsToPaintbuffer( list, end, flags, SOUND_44k, SOUND_DMA_SPEED);
+
+ MIX_DeactivateAllPaintbuffers();
+
+ // restore previous paintbuffer
+ MIX_SetCurrentPaintbuffer( ipaintcur );
+}
+
+// upsample and mix sounds into final 44khz versions of the following paintbuffers:
+// SOUND_BUFFER_ROOM, SOUND_BUFFER_FACING, IFACINGAWAY, SOUND_BUFFER_DRY, SOUND_BUFFER_SPEAKER, SOUND_BUFFER_SPECIALs
+// dsp fx are then applied to these buffers by the caller.
+// caller also remixes all into final SOUND_BUFFER_PAINT output.
+
+void MIX_UpsampleAllPaintbuffers( CChannelList &list, int end, int count )
+{
+ VPROF( "MixUpsampleAll" );
+
+ // 'dry' and 'speaker' channel sounds mix 100% into their corresponding buffers
+
+ // mix and upsample all 'dry' sounds (channels) to 44khz SOUND_BUFFER_DRY paintbuffer
+
+ if ( list.m_hasDryChannels )
+ MIX_MixUpsampleBuffer( list, SOUND_BUFFER_DRY, end, count, SOUND_MIX_DRY );
+
+ // mix and upsample all 'speaker' sounds (channels) to 44khz SOUND_BUFFER_SPEAKER paintbuffer
+
+ if ( list.m_hasSpeakerChannels )
+ MIX_MixUpsampleBuffer( list, SOUND_BUFFER_SPEAKER, end, count, SOUND_MIX_SPEAKER );
+
+ // mix and upsample all 'special dsp' sounds (channels) to 44khz SOUND_BUFFER_SPECIALs paintbuffer
+
+ for ( int iDSP = 0; iDSP < list.m_nSpecialDSPs.Count(); ++iDSP )
+ {
+ for ( int i = SOUND_BUFFER_SPECIAL_START; i < g_paintBuffers.Count(); ++i )
+ {
+ paintbuffer_t *pSpecialBuffer = MIX_GetPPaintFromIPaint( i );
+ if ( pSpecialBuffer->nSpecialDSP == list.m_nSpecialDSPs[ iDSP ] && pSpecialBuffer->idsp_specialdsp != -1 )
+ {
+ MIX_MixUpsampleBuffer( list, i, end, count, SOUND_MIX_SPECIAL_DSP );
+ break;
+ }
+ }
+ }
+
+ // 'room', 'facing' 'facingaway' sounds are mixed into up to 3 buffers:
+
+ // 11khz sounds are mixed into 3 buffers based on distance from listener, and facing direction
+ // These buffers are room, facing, facingaway
+ // These 3 mixed buffers are then each upsampled to 22khz.
+
+ // 22khz sounds are mixed into the 3 buffers based on distance from listener, and facing direction
+ // These 3 mixed buffers are then each upsampled to 44khz.
+
+ // 44khz sounds are mixed into the 3 buffers based on distance from listener, and facing direction
+ MIX_DeactivateAllPaintbuffers();
+
+ // set paintbuffer upsample filter indices to 0
+ MIX_ResetPaintbufferFilterCounters();
+
+ if ( !g_bDspOff )
+ {
+ // only mix to roombuffer if dsp fx are on KDB: perf
+ MIX_ActivatePaintbuffer(SOUND_BUFFER_ROOM); // operates on MIX_MixChannelsToPaintbuffer
+ }
+
+ MIX_ActivatePaintbuffer(SOUND_BUFFER_FACING);
+
+ if ( g_bdirectionalfx )
+ {
+ // mix to facing away buffer only if directional presets are set
+
+ MIX_ActivatePaintbuffer(SOUND_BUFFER_FACINGAWAY);
+ }
+
+ // mix 11khz sounds:
+ // pan sounds between 3 busses: facing, facingaway and room buffers
+
+ MIX_MixChannelsToPaintbuffer( list, end, SOUND_MIX_WET, SOUND_11k, SOUND_11k);
+
+ // upsample all 11khz buffers by 2x
+ if ( !g_bDspOff )
+ {
+ // only upsample roombuffer if dsp fx are on KDB: perf
+ MIX_SetCurrentPaintbuffer(SOUND_BUFFER_ROOM); // operates on MixUpSample
+ g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_11k), FILTERTYPE_LINEAR );
+ }
+
+ MIX_SetCurrentPaintbuffer(SOUND_BUFFER_FACING);
+ g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_11k), FILTERTYPE_LINEAR );
+
+ if ( g_bdirectionalfx )
+ {
+ MIX_SetCurrentPaintbuffer(SOUND_BUFFER_FACINGAWAY);
+ g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_11k), FILTERTYPE_LINEAR );
+ }
+
+ // mix 22khz sounds:
+ // pan sounds between 3 busses: facing, facingaway and room buffers
+ MIX_MixChannelsToPaintbuffer( list, end, SOUND_MIX_WET, SOUND_22k, SOUND_22k);
+
+ // upsample all 22khz buffers by 2x
+#if ( SOUND_DMA_SPEED > SOUND_22k )
+ if ( !g_bDspOff )
+ {
+ // only upsample roombuffer if dsp fx are on KDB: perf
+
+ MIX_SetCurrentPaintbuffer(SOUND_BUFFER_ROOM);
+ g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_22k), FILTERTYPE_LINEAR );
+ }
+
+ MIX_SetCurrentPaintbuffer(SOUND_BUFFER_FACING);
+ g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_22k), FILTERTYPE_LINEAR );
+
+ if ( g_bdirectionalfx )
+ {
+ MIX_SetCurrentPaintbuffer(SOUND_BUFFER_FACINGAWAY);
+ g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_22k), FILTERTYPE_LINEAR );
+ }
+#endif
+
+ // mix all 44khz sounds to all active paintbuffers
+ MIX_MixChannelsToPaintbuffer( list, end, SOUND_MIX_WET, SOUND_44k, SOUND_DMA_SPEED);
+
+ MIX_DeactivateAllPaintbuffers();
+
+ MIX_SetCurrentPaintbuffer(SOUND_BUFFER_PAINT);
+}
+
+ConVar snd_cull_duplicates("snd_cull_duplicates","0",FCVAR_ALLOWED_IN_COMPETITIVE,"If nonzero, aggressively cull duplicate sounds during mixing. The number specifies the number of duplicates allowed to be played.");
+
+
+// Helper class for determining whether a given channel number should be culled from
+// mixing, if snd_cull_duplicates is enabled (psychoacoustic quashing).
+class CChannelCullList
+{
+public:
+ // default constructor
+ CChannelCullList() : m_numChans(0) {};
+
+ // call if you plan on culling channels - and not otherwise, it's a little expensive
+ // (that's why it's not in the constructor)
+ void Initialize( CChannelList &list );
+
+ // returns true if a given channel number has been marked for culling
+ inline bool ShouldCull( int channelNum )
+ {
+ return (m_numChans > channelNum) ? m_bShouldCull[channelNum] : false;
+ }
+
+ // an array of sound names and their volumes
+ // TODO: there may be a way to do this faster on 360 (eg, pad to 128bit, use SIMD)
+ struct sChannelVolData
+ {
+ int m_channelNum;
+ int m_vol; // max volume of sound. -1 means "do not cull, ever, do not even do the math"
+ unsigned int m_nameHash; // a unique id for a sound file
+ };
+protected:
+ sChannelVolData m_channelInfo[MAX_CHANNELS];
+
+ bool m_bShouldCull[MAX_CHANNELS]; // in ChannelList order, not sorted order
+ int m_numChans;
+};
+
+// comparator for qsort as used below (eg a lambda)
+// returns < 0 if a should come before b, > 0 if a should come after, 0 otherwise
+static int __cdecl ChannelVolComparator ( const void * a, const void * b )
+{
+ // greater numbers come first.
+ return static_cast<const CChannelCullList::sChannelVolData *>(b)->m_vol - static_cast<const CChannelCullList::sChannelVolData *>(a)->m_vol;
+}
+
+
+void CChannelCullList::Initialize( CChannelList &list )
+{
+ VPROF("CChannelCullList::Initialize");
+ // First, build a sorted list of channels by decreasing volume, and by a hash of their wavname.
+ m_numChans = list.Count();
+
+ for ( int i = m_numChans - 1 ; i >= 0 ; --i )
+ {
+ channel_t *ch = list.GetChannel(i);
+ m_channelInfo[i].m_channelNum = i;
+ if ( ch && ch->pMixer->IsReadyToMix() )
+ {
+ m_channelInfo[i].m_vol = ChannelLoudestCurVolume(ch);
+ AssertMsg(m_channelInfo[i].m_vol >= 0, "Sound channel has a negative volume?");
+ m_channelInfo[i].m_nameHash = (unsigned int) ch->sfx;
+ }
+ else
+ {
+ m_channelInfo[i].m_vol = -1;
+ m_channelInfo[i].m_nameHash = NULL; // doesn't matter
+ }
+ }
+
+ // set the unused channels to invalid data
+ for ( int i = m_numChans ; i < MAX_CHANNELS ; ++i )
+ {
+ m_channelInfo[i].m_channelNum = -1;
+ m_channelInfo[i].m_vol = -1;
+ }
+
+ // Sort the list.
+ qsort( m_channelInfo, MAX_CHANNELS, sizeof(sChannelVolData), ChannelVolComparator );
+
+ // Then, determine if the given sound is less than the nth loudest of its hash. If so, mark its flag
+ // for removal.
+ // TODO: use an actual algorithm rather than this bogus quadratic technique.
+ // (I'm using it for now because we don't have convenient/fast hash table
+ // classes, which would be the linear-time way to deal with this).
+ const int cutoff = snd_cull_duplicates.GetInt();
+ for ( int i = 0 ; i < m_numChans ; ++i ) // i is index in original channel list
+ {
+ channel_t *ch = list.GetChannel(i);
+ // for each sound, determine where it ranks in loudness
+ int howManyLouder = 0;
+ for ( int j = 0 ;
+ m_channelInfo[j].m_channelNum != i && m_channelInfo[j].m_vol >= 0 && j < MAX_CHANNELS ;
+ ++j )
+ {
+ // j steps through the sorted list until we find ourselves:
+ if (m_channelInfo[j].m_nameHash == (unsigned int)(ch->sfx))
+ {
+ // that's another channel playing this sound but louder than me
+ ++howManyLouder;
+ }
+ }
+ if (howManyLouder >= cutoff)
+ {
+ // this sound should be culled
+ m_bShouldCull[i] = true;
+ }
+ else
+ {
+ // this sound should not be culled
+ m_bShouldCull[i] = false;
+ }
+ }
+}
+
+ConVar snd_mute_losefocus("snd_mute_losefocus", "1", FCVAR_ARCHIVE);
+
+// build a list of channels that will actually do mixing in this update
+// remove all active channels that won't mix for some reason
+void MIX_BuildChannelList( CChannelList &list )
+{
+ VPROF("MIX_BuildChannelList");
+ g_ActiveChannels.GetActiveChannels( list );
+ list.m_nSpecialDSPs.RemoveAll();
+ list.m_hasDryChannels = false;
+ list.m_hasSpeakerChannels = false;
+ list.m_has11kChannels = false;
+ list.m_has22kChannels = false;
+ list.m_has44kChannels = false;
+ bool delayStartServer = false;
+ bool delayStartClient = false;
+ bool bPaused = g_pSoundServices->IsGamePaused();
+#ifdef POSIX
+ bool bActive = g_pSoundServices->IsGameActive();
+ bool bStopOnFocusLoss = !bActive && snd_mute_losefocus.GetBool();
+#endif
+
+ CChannelCullList cullList;
+ if (snd_cull_duplicates.GetInt() > 0)
+ {
+ cullList.Initialize(list);
+ }
+
+ // int numQuashed = 0;
+ for ( int i = list.Count(); --i >= 0; )
+ {
+ channel_t *ch = list.GetChannel(i);
+ bool bRemove = false;
+ // Certain async loaded sounds lazily load into memory in the background, use this to determine
+ // if the sound is ready for mixing
+ CAudioSource *pSource = NULL;
+ if ( ch->pMixer->IsReadyToMix() )
+ {
+ pSource = S_LoadSound( ch->sfx, ch );
+
+ // Don't mix sound data for sounds with 'zero' volume. If it's a non-looping sound,
+ // just remove the sound when its volume goes to zero. If it's a 'dry' channel sound (ie: music)
+ // then assume bZeroVolume is fade in - don't restart
+
+ // To be 'zero' volume, all target volume and current volume values must all be less than 5
+
+ bool bZeroVolume = BChannelLowVolume( ch, 1 );
+
+ if ( !pSource || ( bZeroVolume && !pSource->IsLooped() && !ch->flags.bdry ) )
+ {
+ // NOTE: Since we've loaded the sound, check to see if it's a sentence. Play them at zero anyway
+ // to keep the character's lips moving and the captions happening.
+ if ( !pSource || pSource->GetSentence() == NULL )
+ {
+ S_FreeChannel( ch );
+ bRemove = true;
+ }
+ }
+ else if ( bZeroVolume )
+ {
+ bRemove = true;
+ }
+ // If the sound wants to stop when the game pauses, do so
+ if ( bPaused && SND_ShouldPause(ch) )
+ {
+ bRemove = true;
+ }
+#ifdef POSIX
+ // If we aren't the active app and the option for background audio isn't on, mute the audio
+ // Windows has it's own system for background muting
+ if ( !bRemove && bStopOnFocusLoss )
+ {
+ bRemove = true;
+
+ // Free up the sound channels otherwise they start filling up
+ if ( pSource && ( !pSource->IsLooped() && !pSource->IsStreaming() ) )
+ {
+ S_FreeChannel( ch );
+ }
+
+ }
+#endif
+ // On lowend, aggressively cull duplicate sounds.
+ if ( !bRemove && snd_cull_duplicates.GetInt() > 0 )
+ {
+ // We can't simply remove them, because then sounds will pile up waiting to finish later.
+ // We need to flag them for not mixing.
+ list.m_quashed[i] = cullList.ShouldCull(i);
+ /*
+ if (list.m_quashed[i])
+ {
+ numQuashed++;
+ // Msg("removed %i\n", i);
+ }
+ */
+ }
+ else
+ {
+ list.m_quashed[i] = false;
+ }
+ }
+ else
+ {
+ bRemove = true;
+ }
+
+ if ( bRemove )
+ {
+ list.RemoveChannelFromList(i);
+ continue;
+ }
+ if ( ch->flags.bSpeaker )
+ {
+ list.m_hasSpeakerChannels = true;
+ }
+ if ( ch->special_dsp != 0 )
+ {
+ if ( list.m_nSpecialDSPs.Find( ch->special_dsp ) == -1 )
+ {
+ list.m_nSpecialDSPs.AddToTail( ch->special_dsp );
+ }
+ }
+ if ( ch->flags.bdry )
+ {
+ list.m_hasDryChannels = true;
+ }
+ int rate = pSource->SampleRate();
+ if ( rate == SOUND_11k )
+ {
+ list.m_has11kChannels = true;
+ }
+ else if ( rate == SOUND_22k )
+ {
+ list.m_has22kChannels = true;
+ }
+ else if ( rate == SOUND_44k )
+ {
+ list.m_has44kChannels = true;
+ }
+ if ( ch->flags.delayed_start && !SND_IsMouth(ch) )
+ {
+ if ( ch->flags.fromserver )
+ {
+ delayStartServer = true;
+ }
+ else
+ {
+ delayStartClient = true;
+ }
+ }
+
+ // get playback pitch
+ ch->pitch = ch->pMixer->ModifyPitch( ch->basePitch * 0.01f );
+ }
+ // DevMsg( "%d channels quashed.\n", numQuashed );
+
+ // This code will resync the delay calculation clock really often
+ // any time there are no scheduled waves or the game is paused
+ // we go ahead and reset the clock
+ // That way the clock is only used for short periods of time
+ // and we need no solution for drift
+ if ( bPaused || (host_frametime_unbounded > host_frametime) )
+ {
+ delayStartClient = false;
+ delayStartServer = false;
+ }
+ if (!delayStartServer)
+ {
+ S_SyncClockAdjust(CLOCK_SYNC_SERVER);
+ }
+ if (!delayStartClient)
+ {
+ S_SyncClockAdjust(CLOCK_SYNC_CLIENT);
+ }
+}
+
+// main mixing rountine - mix up to 'endtime' samples.
+// All channels are mixed in a paintbuffer and then sent to
+// hardware.
+
+// A mix pass is performed, resulting in mixed sounds in SOUND_BUFFER_ROOM, SOUND_BUFFER_FACING, SOUND_BUFFER_FACINGAWAY, SOUND_BUFFER_DRY, SOUND_BUFFER_SPEAKER, SOUND_BUFFER_SPECIALs
+
+ // directional sounds are panned and mixed between SOUND_BUFFER_FACING and SOUND_BUFFER_FACINGAWAY
+ // omnidirectional sounds are panned 100% into SOUND_BUFFER_FACING
+ // sound sources far from player (ie: near back of room ) are mixed in proportion to this distance
+ // into SOUND_BUFFER_ROOM
+ // sounds with ch->bSpeaker set are mixed in mono into SOUND_BUFFER_SPEAKER
+ // sounds with ch->bSpecialDSP set are mixed in mono into SOUND_BUFFER_SPECIALs
+
+// dsp_facingaway fx (2 or 4ch filtering) are then applied to the SOUND_BUFFER_FACINGAWAY
+// dsp_speaker fx (1ch) are then applied to the SOUND_BUFFER_SPEAKER
+// dsp_specialdsp fx (1ch) are then applied to the SOUND_BUFFER_SPECIALs
+// dsp_room fx (1ch reverb) are then applied to the SOUND_BUFFER_ROOM
+
+// All buffers are recombined into the SOUND_BUFFER_PAINT
+
+// The dsp_water and dsp_player fx are applied in series to the SOUND_BUFFER_PAINT
+
+// Finally, the SOUND_BUFFER_DRY buffer is mixed into the SOUND_BUFFER_PAINT
+
+extern ConVar dsp_off;
+extern ConVar snd_profile;
+extern void DEBUG_StartSoundMeasure(int type, int samplecount );
+extern void DEBUG_StopSoundMeasure(int type, int samplecount );
+extern ConVar dsp_enhance_stereo;
+
+extern ConVar dsp_volume;
+extern ConVar dsp_vol_5ch;
+extern ConVar dsp_vol_4ch;
+extern ConVar dsp_vol_2ch;
+
+extern void MXR_SetCurrentSoundMixer( const char *szsoundmixer );
+extern ConVar snd_soundmixer;
+
+void MIX_PaintChannels( int endtime, bool bIsUnderwater )
+{
+ VPROF("MIX_PaintChannels");
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ int end;
+ int count;
+ bool b_spatial_delays = dsp_enhance_stereo.GetInt() != 0 ? true : false;
+ bool room_fsurround_sav;
+ bool room_fsurround_center_sav;
+ paintbuffer_t *proom = MIX_GetPPaintFromIPaint(SOUND_BUFFER_ROOM);
+
+ CheckNewDspPresets();
+
+ MXR_SetCurrentSoundMixer( snd_soundmixer.GetString() );
+
+ // dsp performance tuning
+
+ g_snd_profile_type = snd_profile.GetInt();
+
+ // dsp_off is true if no dsp processing is to run
+ // directional dsp processing is enabled if dsp_facingaway is non-zero
+
+ g_bDspOff = dsp_off.GetInt() ? 1 : 0;
+ CChannelList list;
+
+ MIX_BuildChannelList(list);
+
+ // get master dsp volume
+ g_dsp_volume = dsp_volume.GetFloat();
+
+ // attenuate master dsp volume by 2,4 or 5 ch settings
+ if ( g_AudioDevice->IsSurround() )
+ {
+ g_dsp_volume *= ( g_AudioDevice->IsSurroundCenter() ? dsp_vol_5ch.GetFloat() : dsp_vol_4ch.GetFloat() );
+ }
+ else
+ {
+ g_dsp_volume *= dsp_vol_2ch.GetFloat();
+ }
+
+ if ( !g_bDspOff )
+ {
+ g_bdirectionalfx = dsp_facingaway.GetInt() ? 1 : 0;
+ }
+ else
+ {
+ g_bdirectionalfx = 0;
+ }
+
+ // get dsp preset gain values, update gain crossfaders, used when mixing dsp processed buffers into paintbuffer
+ SDEBUG_ShowAvgValue();
+
+ // the cache needs to hold the audio in memory during mixing, so tell it that mixing is starting
+ wavedatacache->OnMixBegin();
+
+ while ( g_paintedtime < endtime )
+ {
+ VPROF("MIX_PaintChannels inner loop");
+ // mix a full 'paintbuffer' of sound
+
+ // clamp at paintbuffer size
+ end = endtime;
+ if (endtime - g_paintedtime > PAINTBUFFER_SIZE)
+ {
+ end = g_paintedtime + PAINTBUFFER_SIZE;
+ }
+
+ // number of 44khz samples to mix into paintbuffer, up to paintbuffer size
+ count = end - g_paintedtime;
+
+ // clear all mix buffers
+ g_AudioDevice->MixBegin( count );
+
+ // upsample all mix buffers.
+ // results in 44khz versions of:
+ // SOUND_BUFFER_ROOM, SOUND_BUFFER_FACING, SOUND_BUFFER_FACINGAWAY, SOUND_BUFFER_DRY, SOUND_BUFFER_SPEAKER, SOUND_BUFFER_SPECIALs
+ MIX_UpsampleAllPaintbuffers( list, end, count );
+
+ // apply appropriate dsp fx to each buffer, remix buffers into single quad output buffer
+ // apply 2 or 4ch filtering to IFACINGAWAY buffer
+ if ( g_bdirectionalfx )
+ {
+ g_AudioDevice->ApplyDSPEffects( idsp_facingaway, MIX_GetPFrontFromIPaint(SOUND_BUFFER_FACINGAWAY), MIX_GetPRearFromIPaint(SOUND_BUFFER_FACINGAWAY), MIX_GetPCenterFromIPaint(SOUND_BUFFER_FACINGAWAY), count );
+ }
+
+ if ( !g_bDspOff && list.m_hasSpeakerChannels )
+ {
+ // apply 1ch filtering to SOUND_BUFFER_SPEAKER
+ g_AudioDevice->ApplyDSPEffects( idsp_speaker, MIX_GetPFrontFromIPaint(SOUND_BUFFER_SPEAKER), MIX_GetPRearFromIPaint(SOUND_BUFFER_SPEAKER), MIX_GetPCenterFromIPaint(SOUND_BUFFER_SPEAKER), count );
+
+ // mix SOUND_BUFFER_SPEAKER with SOUND_BUFFER_ROOM and SOUND_BUFFER_FACING
+ MIX_ScalePaintBuffer( SOUND_BUFFER_SPEAKER, count, 0.7 );
+
+ MIX_MixPaintbuffers( SOUND_BUFFER_SPEAKER, SOUND_BUFFER_FACING, SOUND_BUFFER_FACING, count, 1.0 ); // +70% dry speaker
+
+ MIX_ScalePaintBuffer( SOUND_BUFFER_SPEAKER, count, 0.43 );
+
+ MIX_MixPaintbuffers( SOUND_BUFFER_SPEAKER, SOUND_BUFFER_ROOM, SOUND_BUFFER_ROOM, count, 1.0 ); // +30% wet speaker
+ }
+
+ if ( !g_bDspOff )
+ {
+ // apply 1ch filtering to SOUND_BUFFER_SPECIALs
+ for ( int iDSP = 0; iDSP < list.m_nSpecialDSPs.Count(); ++iDSP )
+ {
+ bool bFoundMixer = false;
+
+ for ( int i = SOUND_BUFFER_SPECIAL_START; i < g_paintBuffers.Count(); ++i )
+ {
+ paintbuffer_t *pSpecialBuffer = MIX_GetPPaintFromIPaint( i );
+ if ( pSpecialBuffer->nSpecialDSP == list.m_nSpecialDSPs[ iDSP ] && pSpecialBuffer->idsp_specialdsp != -1 )
+ {
+ g_AudioDevice->ApplyDSPEffects( pSpecialBuffer->idsp_specialdsp, MIX_GetPFrontFromIPaint( i ), MIX_GetPRearFromIPaint( i ), MIX_GetPCenterFromIPaint( i ), count );
+
+ // mix SOUND_BUFFER_SPECIALs with SOUND_BUFFER_ROOM and SOUND_BUFFER_FACING
+ MIX_ScalePaintBuffer( i, count, 0.7 );
+
+ MIX_MixPaintbuffers( i, SOUND_BUFFER_FACING, SOUND_BUFFER_FACING, count, 1.0 ); // +70% dry speaker
+
+ MIX_ScalePaintBuffer( i, count, 0.43 );
+
+ MIX_MixPaintbuffers( i, SOUND_BUFFER_ROOM, SOUND_BUFFER_ROOM, count, 1.0 ); // +30% wet speaker
+
+ bFoundMixer = true;
+
+ break;
+ }
+ }
+
+ // Couldn't find a mixer with the correct DSP, so make a new one!
+ if ( !bFoundMixer )
+ {
+ bool bSurroundCenter = g_AudioDevice->IsSurroundCenter();
+ bool bSurround = g_AudioDevice->IsSurround() || bSurroundCenter;
+
+ int nIndex = g_paintBuffers.AddToTail();
+ MIX_InitializePaintbuffer( &(g_paintBuffers[ nIndex ]), bSurround, bSurroundCenter );
+
+ g_paintBuffers[ nIndex ].flags = SOUND_BUSS_SPECIAL_DSP;
+
+ // special dsp buffer mixes to mono
+ g_paintBuffers[ nIndex ].fsurround = false;
+ g_paintBuffers[ nIndex ].fsurround_center = false;
+
+ g_paintBuffers[ nIndex ].idsp_specialdsp = -1;
+ g_paintBuffers[ nIndex ].nSpecialDSP = list.m_nSpecialDSPs[ iDSP ];
+
+ g_paintBuffers[ nIndex ].nPrevSpecialDSP = g_paintBuffers[ nIndex ].nSpecialDSP;
+ g_paintBuffers[ nIndex ].idsp_specialdsp = DSP_Alloc( g_paintBuffers[ nIndex ].nSpecialDSP, 300, 1 );
+ }
+ }
+ }
+
+ // apply dsp_room effects to room buffer
+ g_AudioDevice->ApplyDSPEffects( Get_idsp_room(), MIX_GetPFrontFromIPaint(SOUND_BUFFER_ROOM), MIX_GetPRearFromIPaint(SOUND_BUFFER_ROOM), MIX_GetPCenterFromIPaint(SOUND_BUFFER_ROOM), count );
+
+ // save room buffer surround status, in case we upconvert it
+ room_fsurround_sav = proom->fsurround;
+ room_fsurround_center_sav = proom->fsurround_center;
+
+ // apply left/center/right/lrear/rrear spatial delays to room buffer
+ if ( b_spatial_delays && !g_bDspOff && !DSP_RoomDSPIsOff() )
+ {
+ // upgrade mono room buffer to surround status so we can apply spatial delays to all channels
+ MIX_ConvertBufferToSurround( SOUND_BUFFER_ROOM );
+ g_AudioDevice->ApplyDSPEffects( idsp_spatial, MIX_GetPFrontFromIPaint(SOUND_BUFFER_ROOM), MIX_GetPRearFromIPaint(SOUND_BUFFER_ROOM), MIX_GetPCenterFromIPaint(SOUND_BUFFER_ROOM), count );
+ }
+
+ if ( g_bdirectionalfx ) // KDB: perf
+ {
+ // Recombine IFACING and IFACINGAWAY buffers into SOUND_BUFFER_PAINT
+ MIX_MixPaintbuffers( SOUND_BUFFER_FACING, SOUND_BUFFER_FACINGAWAY, SOUND_BUFFER_PAINT, count, DSP_NOROOM_MIX );
+
+ // Add in dsp room fx to paintbuffer, mix at 75%
+ MIX_MixPaintbuffers( SOUND_BUFFER_ROOM, SOUND_BUFFER_PAINT, SOUND_BUFFER_PAINT, count, DSP_ROOM_MIX );
+ }
+ else
+ {
+ // Mix IFACING buffer with SOUND_BUFFER_ROOM
+ // (SOUND_BUFFER_FACINGAWAY contains no data, IFACINGBBUFFER has full dry mix based on distance from listener)
+ // if dsp disabled, mix 100% facingbuffer, otherwise, mix 75% facingbuffer + roombuffer
+ float mix = g_bDspOff ? 1.0 : DSP_ROOM_MIX;
+ MIX_MixPaintbuffers( SOUND_BUFFER_ROOM, SOUND_BUFFER_FACING, SOUND_BUFFER_PAINT, count, mix );
+ }
+
+ // restore room buffer surround status, in case we upconverted it
+ proom->fsurround = room_fsurround_sav;
+ proom->fsurround_center = room_fsurround_center_sav;
+
+ // Apply underwater fx dsp_water (serial in-line)
+ if ( bIsUnderwater )
+ {
+ // BUG: if out of water, previous delays will be heard. must clear dly buffers.
+ g_AudioDevice->ApplyDSPEffects( idsp_water, MIX_GetPFrontFromIPaint(SOUND_BUFFER_PAINT), MIX_GetPRearFromIPaint(SOUND_BUFFER_PAINT), MIX_GetPCenterFromIPaint(SOUND_BUFFER_PAINT), count );
+ }
+
+ // find dsp gain
+ SDEBUG_GetAvgIn(SOUND_BUFFER_PAINT, count);
+
+ // Apply player fx dsp_player (serial in-line) - does nothing if dsp fx are disabled
+ g_AudioDevice->ApplyDSPEffects( idsp_player, MIX_GetPFrontFromIPaint(SOUND_BUFFER_PAINT), MIX_GetPRearFromIPaint(SOUND_BUFFER_PAINT), MIX_GetPCenterFromIPaint(SOUND_BUFFER_PAINT), count );
+
+ // display dsp gain
+ SDEBUG_GetAvgOut(SOUND_BUFFER_PAINT, count);
+
+/*
+ // apply left/center/right/lrear/rrear spatial delays to paint buffer
+
+ if ( b_spatial_delays )
+ g_AudioDevice->ApplyDSPEffects( idsp_spatial, MIX_GetPFrontFromIPaint(SOUND_BUFFER_PAINT), MIX_GetPRearFromIPaint(SOUND_BUFFER_PAINT), MIX_GetPCenterFromIPaint(SOUND_BUFFER_PAINT), count );
+*/
+ // Add dry buffer, set output gain to water * player dsp gain (both 1.0 if not active)
+
+ MIX_MixPaintbuffers( SOUND_BUFFER_PAINT, SOUND_BUFFER_DRY, SOUND_BUFFER_PAINT, count, 1.0);
+
+ // clip all values > 16 bit down to 16 bit
+ // NOTE: This is required - the hardware buffer transfer routines no longer perform clipping.
+ MIX_CompressPaintbuffer( SOUND_BUFFER_PAINT, count );
+
+ // transfer SOUND_BUFFER_PAINT paintbuffer out to DMA buffer
+ MIX_SetCurrentPaintbuffer( SOUND_BUFFER_PAINT );
+
+ g_AudioDevice->TransferSamples( end );
+
+ g_paintedtime = end;
+ }
+
+ // the cache needs to hold the audio in memory during mixing, so tell it that mixing is complete
+ wavedatacache->OnMixEnd();
+}
+
+// Applies volume scaling (evenly) to all fl,fr,rl,rr volumes
+// used for voice ducking and panning between various mix busses
+// Ensures if mixing to speaker buffer, only speaker sounds pass through
+
+// Called just before mixing wav data to current paintbuffer.
+// a) if another player in a multiplayer game is speaking, scale all volumes down.
+// b) if mixing to SOUND_BUFFER_ROOM, scale all volumes by ch.dspmix and dsp_room gain
+// c) if mixing to SOUND_BUFFER_FACINGAWAY, scale all volumes by ch.dspface and dsp_facingaway gain
+// d) If SURROUND_ON, but buffer is not surround, recombined front/rear volumes
+
+// returns false if channel is to be entirely skipped.
+
+bool MIX_ScaleChannelVolume( paintbuffer_t *ppaint, channel_t *pChannel, int volume[CCHANVOLUMES], int mixchans )
+{
+ int i;
+ int mixflag = ppaint->flags;
+ float scale;
+ char wavtype = pChannel->wavtype;
+ float dspmix;
+
+ // copy current channel volumes into output array
+
+ ChannelCopyVolumes( pChannel, volume, 0, CCHANVOLUMES );
+
+ dspmix = pChannel->dspmix;
+
+ // if dsp is off, or room dsp is off, mix 0% to mono room buffer, 100% to facing buffer
+
+ if ( g_bDspOff || DSP_RoomDSPIsOff() )
+ dspmix = 0.0;
+
+ // duck all sound volumes except speaker's voice
+#if !defined( NO_VOICE )
+ int duckScale = min((int)(g_DuckScale * 256), g_SND_VoiceOverdriveInt);
+#else
+ int duckScale = (int)(g_DuckScale * 256);
+#endif
+ if( duckScale < 256 )
+ {
+ if( pChannel->pMixer )
+ {
+ CAudioSource *pSource = pChannel->pMixer->GetSource();
+ if( !pSource->IsVoiceSource() )
+ {
+ // Apply voice overdrive..
+ for (i = 0; i < CCHANVOLUMES; i++)
+ volume[i] = (volume[i] * duckScale) >> 8;
+ }
+ }
+ }
+
+ // If mixing to the room buss, adjust volume based on channel's dspmix setting.
+ // dspmix is DSP_MIX_MAX (~0.78) if sound is far from player, DSP_MIX_MIN (~0.24) if sound is near player
+
+ if ( mixflag & SOUND_BUSS_ROOM )
+ {
+ // set dsp mix volume, scaled by global dsp_volume
+
+ float dspmixvol = fpmin(dspmix * g_dsp_volume, 1.0f);
+
+ // if dspmix is 1.0, 100% of sound goes to SOUND_BUFFER_ROOM and 0% to SOUND_BUFFER_FACING
+
+ for (i = 0; i < CCHANVOLUMES; i++)
+ volume[i] = (int)((float)(volume[i]) * dspmixvol);
+ }
+
+ // If global dsp volume is less than 1, reduce dspmix (ie: increase dry volume)
+ // If gloabl dsp volume is greater than 1, do not reduce dspmix
+
+ if (g_dsp_volume < 1.0)
+ dspmix *= g_dsp_volume;
+
+ // If mixing to facing/facingaway buss, adjust volume based on sound entity's facing direction.
+
+ // If sound directly faces player, ch->dspface = 1.0. If facing directly away, ch->dspface = -1.0.
+ // mix to lowpass buffer if facing away, to allpass if facing
+
+ // scale 1.0 - facing player, scale 0, facing away
+
+ scale = (pChannel->dspface + 1.0) / 2.0;
+
+ // UNDONE: get front cone % from channel to set this.
+
+ // bias scale such that 1.0 to 'cone' is considered facing. Facing cone narrows as cone -> 1.0
+ // and 'cone' -> 0.0 becomes 1.0 -> 0.0
+
+ float cone = 0.6f;
+
+ scale = scale * (1/cone);
+
+ scale = clamp( scale, 0.0f, 1.0f );
+
+ // pan between facing and facing away buffers
+
+ // if ( !g_bdirectionalfx || wavtype == CHAR_DOPPLER || wavtype == CHAR_OMNI || (wavtype == CHAR_DIRECTIONAL && mixchans == 2) )
+ if ( !g_bdirectionalfx || wavtype != CHAR_DIRECTIONAL )
+ {
+ // if no directional fx mix 0% to facingaway buffer
+ // if wavtype is DOPPLER, mix 0% to facingaway buffer - DOPPLER wavs have a custom mixer
+ // if wavtype is OMNI, mix 0% to facingaway buffer - OMNI wavs have no directionality
+ // if wavtype is DIRECTIONAL and stereo encoded, mix 0% to facingaway buffer - DIRECTIONAL STEREO wavs have a custom mixer
+
+ scale = 1.0;
+ }
+
+ if ( mixflag & SOUND_BUSS_FACING )
+ {
+ // facing player
+ // if dspface is 1.0, 100% of sound goes to SOUND_BUFFER_FACING
+
+ for (i = 0; i < CCHANVOLUMES; i++)
+ volume[i] = (int)((float)(volume[i]) * scale * (1.0 - dspmix));
+ }
+ else if ( mixflag & SOUND_BUSS_FACINGAWAY )
+ {
+ // facing away from player
+ // if dspface is 0.0, 100% of sound goes to SOUND_BUFFER_FACINGAWAY
+
+ for (i = 0; i < CCHANVOLUMES; i++)
+ volume[i] = (int)((float)(volume[i]) * (1.0 - scale) * (1.0 - dspmix));
+ }
+
+ // NOTE: this must occur last in this routine:
+
+ if ( g_AudioDevice->IsSurround() && !ppaint->fsurround )
+ {
+ // if 4ch or 5ch spatialization on, but current mix buffer is 2ch,
+ // recombine front + rear volumes (revert to 2ch spatialization)
+
+ volume[IFRONT_RIGHT] += volume[IREAR_RIGHT];
+ volume[IFRONT_LEFT] += volume[IREAR_LEFT];
+
+ volume[IFRONT_RIGHTD] += volume[IREAR_RIGHTD];
+ volume[IFRONT_LEFTD] += volume[IREAR_LEFTD];
+
+ // if 5 ch, recombine center channel vol
+
+ if ( g_AudioDevice->IsSurroundCenter() )
+ {
+ volume[IFRONT_RIGHT] += volume[IFRONT_CENTER] / 2;
+ volume[IFRONT_LEFT] += volume[IFRONT_CENTER] / 2;
+
+ volume[IFRONT_RIGHTD] += volume[IFRONT_CENTERD] / 2;
+ volume[IFRONT_LEFTD] += volume[IFRONT_CENTERD] / 2;
+ }
+
+ // clear rear & center volumes
+
+ volume[IREAR_RIGHT] = 0;
+ volume[IREAR_LEFT] = 0;
+ volume[IFRONT_CENTER] = 0;
+
+ volume[IREAR_RIGHTD] = 0;
+ volume[IREAR_LEFTD] = 0;
+ volume[IFRONT_CENTERD] = 0;
+
+ }
+
+ bool fzerovolume = true;
+
+ for (i = 0; i < CCHANVOLUMES; i++)
+ {
+ volume[i] = clamp(volume[i], 0, 255);
+
+ if (volume[i])
+ fzerovolume = false;
+ }
+
+
+ if ( fzerovolume )
+ {
+ // DevMsg ("Skipping mix of 0 volume sound! \n");
+ return false;
+ }
+
+ return true;
+}
+
+
+//===============================================================================
+// Low level mixing routines
+//===============================================================================
+void Snd_WriteLinearBlastStereo16( void )
+{
+#if !id386
+ int i;
+ int val;
+
+ for ( i=0; i<snd_linear_count; i+=2 )
+ {
+ // scale and clamp left 16bit signed: [0x8000, 0x7FFF]
+ val = ( snd_p[i] * snd_vol )>>8;
+ if ( val > 32767 )
+ snd_out[i] = 32767;
+ else if ( val < -32768 )
+ snd_out[i] = -32768;
+ else
+ snd_out[i] = val;
+
+ // scale and clamp right 16bit signed: [0x8000, 0x7FFF]
+ val = ( snd_p[i+1] * snd_vol )>>8;
+ if ( val > 32767 )
+ snd_out[i+1] = 32767;
+ else if ( val < -32768 )
+ snd_out[i+1] = -32768;
+ else
+ snd_out[i+1] = val;
+ }
+#else
+ __asm
+ {
+ // input data
+ mov ebx,snd_p
+
+ // output data
+ mov edi,snd_out
+
+ // iterate from end to beginning
+ mov ecx,snd_linear_count
+
+ // scale table
+ mov esi,snd_vol
+
+ // scale and clamp 16bit signed lsw: [0x8000, 0x7FFF]
+WLBS16_LoopTop:
+ mov eax,[ebx+ecx*4-8]
+ imul eax,esi
+ sar eax,0x08
+ cmp eax,0x7FFF
+ jg WLBS16_ClampHigh
+ cmp eax,0xFFFF8000
+ jnl WLBS16_ClampDone
+ mov eax,0xFFFF8000
+ jmp WLBS16_ClampDone
+WLBS16_ClampHigh:
+ mov eax,0x7FFF
+WLBS16_ClampDone:
+
+ // scale and clamp 16bit signed msw: [0x8000, 0x7FFF]
+ mov edx,[ebx+ecx*4-4]
+ imul edx,esi
+ sar edx,0x08
+ cmp edx,0x7FFF
+ jg WLBS16_ClampHigh2
+ cmp edx,0xFFFF8000
+ jnl WLBS16_ClampDone2
+ mov edx,0xFFFF8000
+ jmp WLBS16_ClampDone2
+WLBS16_ClampHigh2:
+ mov edx,0x7FFF
+WLBS16_ClampDone2:
+ shl edx,0x10
+ and eax,0xFFFF
+ or edx,eax
+ mov [edi+ecx*2-4],edx
+
+ // two shorts per iteration
+ sub ecx,0x02
+ jnz WLBS16_LoopTop
+ }
+#endif
+}
+
+void SND_InitScaletable (void)
+{
+ int i, j;
+
+ for (i=0 ; i<SND_SCALE_LEVELS; i++)
+ for (j=0 ; j<256 ; j++)
+ snd_scaletable[i][j] = ((signed char)j) * i * (1<<SND_SCALE_SHIFT);
+}
+
+void SND_PaintChannelFrom8(portable_samplepair_t *pOutput, int *volume, byte *pData8, int count)
+{
+#if !id386
+ int data;
+ int *lscale, *rscale;
+ int i;
+
+ lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];
+ rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];
+
+ for (i=0 ; i<count ; i++)
+ {
+ data = pData8[i];
+
+ pOutput[i].left += lscale[data];
+ pOutput[i].right += rscale[data];
+ }
+#else
+ // portable_samplepair_t structure
+#define psp_left 0
+#define psp_right 4
+#define psp_size 8
+ static int tempStore;
+
+ __asm
+ {
+ // prologue
+ push ebp
+
+ // esp = pOutput
+ mov eax, pOutput
+ mov tempStore, eax
+ xchg esp,tempStore
+ // ebx = volume
+ mov ebx,volume
+ // esi = pData8
+ mov esi,pData8
+ // ecx = count
+ mov ecx,count
+
+ // These values depend on the setting of SND_SCALE_BITS
+ // The mask must mask off all the lower bits you aren't using in the multiply
+ // so for 7 bits, the mask is 0xFE, 6 bits 0xFC, etc.
+ // The shift must multiply by the table size. There are 256 4-byte values in the table at each level.
+ // So each index must be shifted left by 10, but since the bits we use are in the MSB rather than LSB
+ // they must be shifted right by 8 - SND_SCALE_BITS. e.g., for a 7 bit number the left shift is:
+ // 10 - (8-7) = 9. For a 5 bit number it's 10 - (8-5) = 7.
+ mov eax,[ebx]
+ mov edx,[ebx + 4]
+ and eax,0xFE
+ and edx,0xFE
+
+ // shift up by 10 to index table, down by 1 to make the 7 MSB of the bytes an index
+ // eax = lscale
+ // edx = rscale
+ shl eax,0x09
+ shl edx,0x09
+ add eax,OFFSET snd_scaletable
+ add edx,OFFSET snd_scaletable
+
+ // ebx = data byte
+ sub ebx,ebx
+ mov bl,[esi+ecx-1]
+
+ // odd or even number of L/R samples
+ test ecx,0x01
+ jz PCF8_Loop
+
+ // process odd L/R sample
+ mov edi,[eax+ebx*4]
+ mov ebp,[edx+ebx*4]
+ add edi,[esp+ecx*psp_size-psp_size+psp_left]
+ add ebp,[esp+ecx*psp_size-psp_size+psp_right]
+ mov [esp+ecx*psp_size-psp_size+psp_left],edi
+ mov [esp+ecx*psp_size-psp_size+psp_right],ebp
+ mov bl,[esi+ecx-1-1]
+
+ dec ecx
+ jz PCF8_Done
+
+PCF8_Loop:
+ // process L/R sample N
+ mov edi,[eax+ebx*4]
+ mov ebp,[edx+ebx*4]
+ add edi,[esp+ecx*psp_size-psp_size+psp_left]
+ add ebp,[esp+ecx*psp_size-psp_size+psp_right]
+ mov [esp+ecx*psp_size-psp_size+psp_left],edi
+ mov [esp+ecx*psp_size-psp_size+psp_right],ebp
+ mov bl,[esi+ecx-1-1]
+
+ // process L/R sample N-1
+ mov edi,[eax+ebx*4]
+ mov ebp,[edx+ebx*4]
+ add edi,[esp+ecx*psp_size-psp_size*2+psp_left]
+ add ebp,[esp+ecx*psp_size-psp_size*2+psp_right]
+ mov [esp+ecx*psp_size-psp_size*2+psp_left],edi
+ mov [esp+ecx*psp_size-psp_size*2+psp_right],ebp
+ mov bl,[esi+ecx-1-2]
+
+ // two L/R samples per iteration
+ sub ecx,0x02
+ jnz PCF8_Loop
+
+PCF8_Done:
+ // epilogue
+ xchg esp,tempStore
+ pop ebp
+ }
+#endif
+}
+
+//===============================================================================
+// SOFTWARE MIXING ROUTINES
+//===============================================================================
+
+// UNDONE: optimize these
+
+// grab samples from left source channel only and mix as if mono.
+// volume array contains appropriate spatialization volumes for doppler left (incoming sound)
+void SW_Mix8StereoDopplerLeft( portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ int sampleIndex = 0;
+ fixedint sampleFrac = inputOffset;
+ int *lscale, *rscale;
+
+ lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];
+ rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];
+
+ for ( int i = 0; i < outCount; i++ )
+ {
+ pOutput[i].left += lscale[pData[sampleIndex]];
+ pOutput[i].right += rscale[pData[sampleIndex]];
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+}
+
+// grab samples from right source channel only and mix as if mono.
+// volume array contains appropriate spatialization volumes for doppler right (outgoing sound)
+void SW_Mix8StereoDopplerRight( portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ int sampleIndex = 0;
+ fixedint sampleFrac = inputOffset;
+ int *lscale, *rscale;
+
+ lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];
+ rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];
+
+ for ( int i = 0; i < outCount; i++ )
+ {
+ pOutput[i].left += lscale[pData[sampleIndex+1]];
+ pOutput[i].right += rscale[pData[sampleIndex+1]];
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+
+}
+
+
+// grab samples from left source channel only and mix as if mono.
+// volume array contains appropriate spatialization volumes for doppler left (incoming sound)
+
+void SW_Mix16StereoDopplerLeft( portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ int sampleIndex = 0;
+ fixedint sampleFrac = inputOffset;
+
+ for ( int i = 0; i < outCount; i++ )
+ {
+ pOutput[i].left += (volume[0] * (int)(pData[sampleIndex]))>>8;
+ pOutput[i].right += (volume[1] * (int)(pData[sampleIndex]))>>8;
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+}
+
+
+// grab samples from right source channel only and mix as if mono.
+// volume array contains appropriate spatialization volumes for doppler right (outgoing sound)
+
+void SW_Mix16StereoDopplerRight( portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ int sampleIndex = 0;
+ fixedint sampleFrac = inputOffset;
+
+ for ( int i = 0; i < outCount; i++ )
+ {
+ pOutput[i].left += (volume[0] * (int)(pData[sampleIndex+1]))>>8;
+ pOutput[i].right += (volume[1] * (int)(pData[sampleIndex+1]))>>8;
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+}
+
+// mix left wav (front facing) with right wav (rear facing) based on soundfacing direction
+void SW_Mix8StereoDirectional( float soundfacing, portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ int sampleIndex = 0;
+ fixedint sampleFrac = inputOffset;
+ int x;
+ int l,r;
+ signed char lb,rb;
+ int *lscale, *rscale;
+
+ lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];
+ rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];
+
+ // if soundfacing -1.0, sound source is facing away from player
+ // if soundfacing 0.0, sound source is perpendicular to player
+ // if soundfacing 1.0, sound source is facing player
+
+ int frontmix = (int)(256.0f * ((1.f + soundfacing) / 2.f)); // 0 -> 256
+
+ for ( int i = 0; i < outCount; i++ )
+ {
+ lb = (pData[sampleIndex]); // get left byte
+ rb = (pData[sampleIndex+1]); // get right byte
+
+ l = ((int)lb);
+ r = ((int)rb);
+
+ x = ( r + ((( l - r ) * frontmix) >> 8) );
+
+ pOutput[i].left += lscale[x & 0xFF]; // multiply by volume and convert to 16 bit
+ pOutput[i].right += rscale[x & 0xFF];
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+}
+
+
+// mix left wav (front facing) with right wav (rear facing) based on soundfacing direction
+// interpolating pitch shifter - sample(s) from preceding buffer are preloaded in
+// pData buffer, ensuring we can always provide 'outCount' samples.
+void SW_Mix8StereoDirectional_Interp( float soundfacing, portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ fixedint sampleIndex = 0;
+ fixedint rateScaleFix14 = FIX_28TO14(rateScaleFix); // convert 28 bit fixed point to 14 bit fixed point
+ fixedint sampleFrac14 = FIX_28TO14(inputOffset);
+
+ int first, second, interpl, interpr;
+ int *lscale, *rscale;
+
+ lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];
+ rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];
+
+ int x;
+
+ // if soundfacing -1.0, sound source is facing away from player
+ // if soundfacing 0.0, sound source is perpendicular to player
+ // if soundfacing 1.0, sound source is facing player
+
+ int frontmix = (int)(256.0f * ((1.f + soundfacing) / 2.f)); // 0 -> 256
+
+ for ( int i = 0; i < outCount; i++ )
+ {
+ // interpolate between first & second sample (the samples bordering sampleFrac12 fraction)
+
+ first = (int)((signed char)(pData[sampleIndex])); // left byte
+ second = (int)((signed char)(pData[sampleIndex+2]));
+
+ interpl = first + ( ((second - first) * (int)sampleFrac14) >> 14 );
+
+ first = (int)((signed char)(pData[sampleIndex+1])); // right byte
+ second = (int)((signed char)(pData[sampleIndex+3]));
+
+ interpr = first + ( ((second - first) * (int)sampleFrac14) >> 14 );
+
+ // crossfade between right/left based on directional mix
+
+ x = ( interpr + ((( interpl - interpr ) * frontmix) >> 8) );
+
+ pOutput[i].left += lscale[x & 0xFF]; // scale and convert to 16 bit
+ pOutput[i].right += rscale[x & 0xFF];
+
+ sampleFrac14 += rateScaleFix14;
+ sampleIndex += FIX_INTPART14(sampleFrac14)<<1;
+ sampleFrac14 = FIX_FRACPART14(sampleFrac14);
+ }
+}
+
+
+// mix left wav (front facing) with right wav (rear facing) based on soundfacing direction
+
+void SW_Mix16StereoDirectional( float soundfacing, portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ fixedint sampleIndex = 0;
+ fixedint sampleFrac = inputOffset;
+
+ int x;
+ int l, r;
+
+ // if soundfacing -1.0, sound source is facing away from player
+ // if soundfacing 0.0, sound source is perpendicular to player
+ // if soundfacing 1.0, sound source is facing player
+
+ int frontmix = (int)(256.0f * ((1.f + soundfacing) / 2.f)); // 0 -> 256
+
+ for ( int i = 0; i < outCount; i++ )
+ {
+ // get left, right samples
+
+ l = (int)(pData[sampleIndex]);
+ r = (int)(pData[sampleIndex+1]);
+
+ // crossfade between left & right based on front/rear facing
+
+ x = ( r + ((( l - r ) * frontmix) >> 8) );
+
+ pOutput[i].left += (volume[0] * x) >> 8;
+ pOutput[i].right += (volume[1] * x) >> 8;
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+}
+
+// mix left wav (front facing) with right wav (rear facing) based on soundfacing direction
+// interpolating pitch shifter - sample(s) from preceding buffer are preloaded in
+// pData buffer, ensuring we can always provide 'outCount' samples.
+
+void SW_Mix16StereoDirectional_Interp( float soundfacing, portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ fixedint sampleIndex = 0;
+ fixedint rateScaleFix14 = FIX_28TO14(rateScaleFix); // convert 28 bit fixed point to 14 bit fixed point
+ fixedint sampleFrac14 = FIX_28TO14(inputOffset);
+
+ int x;
+ int first, second, interpl, interpr;
+
+ // if soundfacing -1.0, sound source is facing away from player
+ // if soundfacing 0.0, sound source is perpendicular to player
+ // if soundfacing 1.0, sound source is facing player
+
+ int frontmix = (int)(256.0f * ((1.f + soundfacing) / 2.f)); // 0 -> 256
+
+ for ( int i = 0; i < outCount; i++ )
+ {
+ // get interpolated left, right samples
+
+ first = (int)(pData[sampleIndex]);
+ second = (int)(pData[sampleIndex+2]);
+
+ interpl = first + (((second - first) * (int)sampleFrac14) >> 14);
+
+ first = (int)(pData[sampleIndex+1]);
+ second = (int)(pData[sampleIndex+3]);
+
+ interpr = first + (((second - first) * (int)sampleFrac14) >> 14);
+
+ // crossfade between left & right based on front/rear facing
+
+ x = ( interpr + ((( interpl - interpr ) * frontmix) >> 8) );
+
+ pOutput[i].left += (volume[0] * x) >> 8;
+ pOutput[i].right += (volume[1] * x) >> 8;
+
+ sampleFrac14 += rateScaleFix14;
+ sampleIndex += FIX_INTPART14(sampleFrac14)<<1;
+ sampleFrac14 = FIX_FRACPART14(sampleFrac14);
+ }
+}
+
+
+// distance variant wav (left is close, right is far)
+void SW_Mix8StereoDistVar( float distmix, portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ int sampleIndex = 0;
+ fixedint sampleFrac = inputOffset;
+ int x;
+ int l,r;
+ signed char lb, rb;
+ int *lscale, *rscale;
+
+ lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];
+ rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];
+
+ // distmix 0 - sound is near player (100% wav left)
+ // distmix 1.0 - sound is far from player (100% wav right)
+
+ int nearmix = (int)(256.0f * (1.0f - distmix));
+ int farmix = (int)(256.0f * distmix);
+
+ // if mixing at max or min range, skip crossfade (KDB: perf)
+
+ if (!nearmix)
+ {
+ for ( int i = 0; i < outCount; i++ )
+ {
+ rb = (pData[sampleIndex+1]); // get right byte
+ x = (int) rb;
+
+ pOutput[i].left += lscale[x & 0xFF]; // multiply by volume and convert to 16 bit
+ pOutput[i].right += rscale[x & 0xFF];
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+ return;
+ }
+
+ if (!farmix)
+ {
+ for ( int i = 0; i < outCount; i++ )
+ {
+
+ lb = (pData[sampleIndex]); // get left byte
+ x = (int) lb;
+
+ pOutput[i].left += lscale[x & 0xFF]; // multiply by volume and convert to 16 bit
+ pOutput[i].right += rscale[x & 0xFF];
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+ return;
+ }
+
+ // crossfade left/right
+
+ for ( int i = 0; i < outCount; i++ )
+ {
+
+ lb = (pData[sampleIndex]); // get left byte
+ rb = (pData[sampleIndex+1]); // get right byte
+
+ l = (int)lb;
+ r = (int)rb;
+
+ x = ( l + (((r - l) * farmix ) >> 8) );
+
+ pOutput[i].left += lscale[x & 0xFF]; // multiply by volume and convert to 16 bit
+ pOutput[i].right += rscale[x & 0xFF];
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+}
+
+
+// distance variant wav (left is close, right is far)
+// interpolating pitch shifter - sample(s) from preceding buffer are preloaded in
+// pData buffer, ensuring we can always provide 'outCount' samples.
+void SW_Mix8StereoDistVar_Interp( float distmix, portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ int x;
+
+ // distmix 0 - sound is near player (100% wav left)
+ // distmix 1.0 - sound is far from player (100% wav right)
+
+ int nearmix = (int)(256.0f * (1.0f - distmix));
+ int farmix = (int)(256.0f * distmix);
+
+ fixedint sampleIndex = 0;
+ fixedint rateScaleFix14 = FIX_28TO14(rateScaleFix); // convert 28 bit fixed point to 14 bit fixed point
+ fixedint sampleFrac14 = FIX_28TO14(inputOffset);
+
+ int first, second, interpl, interpr;
+ int *lscale, *rscale;
+
+ lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];
+ rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];
+
+ // if mixing at max or min range, skip crossfade (KDB: perf)
+
+ if (!nearmix)
+ {
+ for ( int i = 0; i < outCount; i++ )
+ {
+ first = (int)((signed char)(pData[sampleIndex+1])); // right sample
+ second = (int)((signed char)(pData[sampleIndex+3]));
+
+ interpr = first + ( ((second - first) * (int)sampleFrac14) >> 14 );
+
+ pOutput[i].left += lscale[interpr & 0xFF]; // scale and convert to 16 bit
+ pOutput[i].right += rscale[interpr & 0xFF];
+
+ sampleFrac14 += rateScaleFix14;
+ sampleIndex += FIX_INTPART14(sampleFrac14)<<1;
+ sampleFrac14 = FIX_FRACPART14(sampleFrac14);
+
+ }
+ return;
+ }
+
+ if (!farmix)
+ {
+ for ( int i = 0; i < outCount; i++ )
+ {
+ first = (int)((signed char)(pData[sampleIndex])); // left sample
+ second = (int)((signed char)(pData[sampleIndex+2]));
+
+ interpl = first + ( ((second - first) * (int)sampleFrac14) >> 14 );
+
+ pOutput[i].left += lscale[interpl & 0xFF]; // scale and convert to 16 bit
+ pOutput[i].right += rscale[interpl & 0xFF];
+
+ sampleFrac14 += rateScaleFix14;
+ sampleIndex += FIX_INTPART14(sampleFrac14)<<1;
+ sampleFrac14 = FIX_FRACPART14(sampleFrac14);
+ }
+ return;
+ }
+
+ // crossfade left/right
+
+ for ( int i = 0; i < outCount; i++ )
+ {
+ // interpolate between first & second sample (the samples bordering sampleFrac14 fraction)
+
+ first = (int)((signed char)(pData[sampleIndex]));
+ second = (int)((signed char)(pData[sampleIndex+2]));
+
+ interpl = first + ( ((second - first) * (int)sampleFrac14) >> 14 );
+
+ first = (int)((signed char)(pData[sampleIndex+1]));
+ second = (int)((signed char)(pData[sampleIndex+3]));
+
+ interpr = first + ( ((second - first) * (int)sampleFrac14) >> 14 );
+
+ // crossfade between left and right based on distance mix
+
+ x = ( interpl + (((interpr - interpl) * farmix ) >> 8) );
+
+ pOutput[i].left += lscale[x & 0xFF]; // scale and convert to 16 bit
+ pOutput[i].right += rscale[x & 0xFF];
+
+ sampleFrac14 += rateScaleFix14;
+ sampleIndex += FIX_INTPART14(sampleFrac14)<<1;
+ sampleFrac14 = FIX_FRACPART14(sampleFrac14);
+ }
+}
+
+
+// distance variant wav (left is close, right is far)
+
+void SW_Mix16StereoDistVar( float distmix, portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ int sampleIndex = 0;
+ fixedint sampleFrac = inputOffset;
+ int x;
+ int l,r;
+
+ // distmix 0 - sound is near player (100% wav left)
+ // distmix 1.0 - sound is far from player (100% wav right)
+
+ int nearmix = Float2Int(256.0f * (1.f - distmix));
+ int farmix = Float2Int(256.0f * distmix);
+
+ // if mixing at max or min range, skip crossfade (KDB: perf)
+
+ if (!nearmix)
+ {
+ for ( int i = 0; i < outCount; i++ )
+ {
+ x = pData[sampleIndex+1]; // right sample
+
+ pOutput[i].left += (volume[0] * x)>>8;
+ pOutput[i].right += (volume[1] * x)>>8;
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+ return;
+ }
+
+ if (!farmix)
+ {
+ for ( int i = 0; i < outCount; i++ )
+ {
+ x = pData[sampleIndex]; // left sample
+
+ pOutput[i].left += (volume[0] * x)>>8;
+ pOutput[i].right += (volume[1] * x)>>8;
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+ return;
+ }
+
+ // crossfade left/right
+
+ for ( int i = 0; i < outCount; i++ )
+ {
+ l = pData[sampleIndex];
+ r = pData[sampleIndex+1];
+
+ x = ( l + (((r - l) * farmix) >> 8) );
+
+ pOutput[i].left += (volume[0] * x)>>8;
+ pOutput[i].right += (volume[1] * x)>>8;
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+}
+
+// distance variant wav (left is close, right is far)
+// interpolating pitch shifter - sample(s) from preceding buffer are preloaded in
+// pData buffer, ensuring we can always provide 'outCount' samples.
+
+void SW_Mix16StereoDistVar_Interp( float distmix, portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ int x;
+
+ fixedint sampleIndex = 0;
+ fixedint rateScaleFix14 = FIX_28TO14(rateScaleFix); // convert 28 bit fixed point to 14 bit fixed point
+ fixedint sampleFrac14 = FIX_28TO14(inputOffset);
+
+ int first, second, interpl, interpr;
+
+
+ // distmix 0 - sound is near player (100% wav left)
+ // distmix 1.0 - sound is far from player (100% wav right)
+
+ int nearmix = Float2Int(256.0f * (1.f - distmix));
+ int farmix = Float2Int(256.0f * distmix);
+
+ // if mixing at max or min range, skip crossfade (KDB: perf)
+
+ if (!nearmix)
+ {
+ for ( int i = 0; i < outCount; i++ )
+ {
+ first = (int)(pData[sampleIndex+1]); // right sample
+ second = (int)(pData[sampleIndex+3]);
+ interpr = first + (((second - first) * (int)sampleFrac14) >> 14);
+
+ pOutput[i].left += (volume[0] * interpr)>>8;
+ pOutput[i].right += (volume[1] * interpr)>>8;
+
+ sampleFrac14 += rateScaleFix14;
+ sampleIndex += FIX_INTPART14(sampleFrac14)<<1;
+ sampleFrac14 = FIX_FRACPART14(sampleFrac14);
+ }
+ return;
+ }
+
+ if (!farmix)
+ {
+ for ( int i = 0; i < outCount; i++ )
+ {
+ first = (int)(pData[sampleIndex]); // left sample
+ second = (int)(pData[sampleIndex+2]);
+ interpl = first + (((second - first) * (int)sampleFrac14) >> 14);
+
+ pOutput[i].left += (volume[0] * interpl)>>8;
+ pOutput[i].right += (volume[1] * interpl)>>8;
+
+ sampleFrac14 += rateScaleFix14;
+ sampleIndex += FIX_INTPART14(sampleFrac14)<<1;
+ sampleFrac14 = FIX_FRACPART14(sampleFrac14);
+ }
+ return;
+ }
+
+ // crossfade left/right
+
+ for ( int i = 0; i < outCount; i++ )
+ {
+ first = (int)(pData[sampleIndex]);
+ second = (int)(pData[sampleIndex+2]);
+ interpl = first + (((second - first) * (int)sampleFrac14) >> 14);
+
+ first = (int)(pData[sampleIndex+1]);
+ second = (int)(pData[sampleIndex+3]);
+ interpr = first + (((second - first) * (int)sampleFrac14) >> 14);
+
+ // crossfade between left & right samples
+
+ x = ( interpl + (((interpr - interpl) * farmix) >> 8) );
+
+ pOutput[i].left += (volume[0] * x) >> 8;
+ pOutput[i].right += (volume[1] * x) >> 8;
+
+ sampleFrac14 += rateScaleFix14;
+ sampleIndex += FIX_INTPART14(sampleFrac14)<<1;
+ sampleFrac14 = FIX_FRACPART14(sampleFrac14);
+ }
+}
+
+void SW_Mix8Mono( portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ // Not using pitch shift?
+ if ( rateScaleFix == FIX(1) )
+ {
+ // native code
+ SND_PaintChannelFrom8( pOutput, volume, (byte *)pData, outCount );
+ return;
+ }
+
+ int sampleIndex = 0;
+ fixedint sampleFrac = inputOffset;
+ int *lscale, *rscale;
+
+ lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];
+ rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];
+
+ for ( int i = 0; i < outCount; i++ )
+ {
+ pOutput[i].left += lscale[pData[sampleIndex]];
+ pOutput[i].right += rscale[pData[sampleIndex]];
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac);
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+}
+
+
+// interpolating pitch shifter - sample(s) from preceding buffer are preloaded in
+// pData buffer, ensuring we can always provide 'outCount' samples.
+void SW_Mix8Mono_Interp( portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount)
+{
+ fixedint sampleIndex = 0;
+ fixedint rateScaleFix14 = FIX_28TO14(rateScaleFix); // convert 28 bit fixed point to 14 bit fixed point
+ fixedint sampleFrac14 = FIX_28TO14(inputOffset);
+
+ int first, second, interp;
+ int *lscale, *rscale;
+
+ lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];
+ rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];
+
+ // iterate 0th sample to outCount-1 sample
+
+ for (int i = 0; i < outCount; i++ )
+ {
+ // interpolate between first & second sample (the samples bordering sampleFrac12 fraction)
+
+ first = (int)((signed char)(pData[sampleIndex]));
+ second = (int)((signed char)(pData[sampleIndex+1]));
+
+ interp = first + ( ((second - first) * (int)sampleFrac14) >> 14 );
+
+ pOutput[i].left += lscale[interp & 0xFF]; // multiply by volume and convert to 16 bit
+ pOutput[i].right += rscale[interp & 0xFF];
+
+ sampleFrac14 += rateScaleFix14;
+ sampleIndex += FIX_INTPART14(sampleFrac14);
+ sampleFrac14 = FIX_FRACPART14(sampleFrac14);
+ }
+}
+
+void SW_Mix8Stereo( portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ int sampleIndex = 0;
+ fixedint sampleFrac = inputOffset;
+ int *lscale, *rscale;
+
+ lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];
+ rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];
+
+ for ( int i = 0; i < outCount; i++ )
+ {
+ pOutput[i].left += lscale[pData[sampleIndex]];
+ pOutput[i].right += rscale[pData[sampleIndex+1]];
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+}
+
+
+// interpolating pitch shifter - sample(s) from preceding buffer are preloaded in
+// pData buffer, ensuring we can always provide 'outCount' samples.
+void SW_Mix8Stereo_Interp( portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount)
+{
+ fixedint sampleIndex = 0;
+ fixedint rateScaleFix14 = FIX_28TO14(rateScaleFix); // convert 28 bit fixed point to 14 bit fixed point
+ fixedint sampleFrac14 = FIX_28TO14(inputOffset);
+
+ int first, second, interpl, interpr;
+ int *lscale, *rscale;
+
+ lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];
+ rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];
+
+ // iterate 0th sample to outCount-1 sample
+
+ for (int i = 0; i < outCount; i++ )
+ {
+ // interpolate between first & second sample (the samples bordering sampleFrac12 fraction)
+
+ first = (int)((signed char)(pData[sampleIndex])); // left
+ second = (int)((signed char)(pData[sampleIndex+2]));
+
+ interpl = first + ( ((second - first) * (int)sampleFrac14) >> 14 );
+
+ first = (int)((signed char)(pData[sampleIndex+1])); // right
+ second = (int)((signed char)(pData[sampleIndex+3]));
+
+ interpr = first + ( ((second - first) * (int)sampleFrac14) >> 14 );
+
+ pOutput[i].left += lscale[interpl & 0xFF]; // multiply by volume and convert to 16 bit
+ pOutput[i].right += rscale[interpr & 0xFF];
+
+ sampleFrac14 += rateScaleFix14;
+ sampleIndex += FIX_INTPART14(sampleFrac14)<<1;
+ sampleFrac14 = FIX_FRACPART14(sampleFrac14);
+ }
+}
+
+void SW_Mix16Mono_Shift( portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ int vol0 = volume[0];
+ int vol1 = volume[1];
+
+#if !id386
+ int sampleIndex = 0;
+ fixedint sampleFrac = inputOffset;
+
+ for ( int i = 0; i < outCount; i++ )
+ {
+ pOutput[i].left += (vol0 * (int)(pData[sampleIndex]))>>8;
+ pOutput[i].right += (vol1 * (int)(pData[sampleIndex]))>>8;
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac);
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+#else
+ // in assembly, you can make this 32.32 instead of 4.28 and use the carry flag instead of masking
+ int rateScaleInt = FIX_INTPART(rateScaleFix);
+ unsigned int rateScaleFrac = FIX_FRACPART(rateScaleFix) << (32-FIX_BITS);
+
+ __asm
+ {
+ mov eax, volume ;
+ movq mm0, DWORD PTR [eax] ; vol1, vol0 (32-bits each)
+ packssdw mm0, mm0 ; pack and replicate... vol1, vol0, vol1, vol0 (16-bits each)
+ //pxor mm7, mm7 ; mm7 is my zero register...
+
+ xor esi, esi
+ mov eax, DWORD PTR [pOutput] ; store initial output ptr
+ mov edx, DWORD PTR [pData] ; store initial input ptr
+ mov ebx, inputOffset;
+ mov ecx, outCount;
+
+BEGINLOAD:
+ movd mm2, WORD PTR [edx+2*esi] ; load first piece of data from pData
+ punpcklwd mm2, mm2 ; 0, 0, pData_1st, pData_1st
+
+ add ebx, rateScaleFrac ; do the crazy fixed integer math
+ adc esi, rateScaleInt
+
+ movd mm3, WORD PTR [edx+2*esi] ; load second piece of data from pData
+ punpcklwd mm3, mm3 ; 0, 0, pData_2nd, pData_2nd
+ punpckldq mm2, mm3 ; pData_2nd, pData_2nd, pData_2nd, pData_2nd
+
+ add ebx, rateScaleFrac ; do the crazy fixed integer math
+ adc esi, rateScaleInt
+
+ movq mm3, mm2 ; copy the goods
+ pmullw mm2, mm0 ; pData_2nd*vol1, pData_2nd*vol0, pData_1st*vol1, pData_1st*vol0 (bits 0-15)
+ pmulhw mm3, mm0 ; pData_2nd*vol1, pData_2nd*vol0, pData_1st*vol1, pData_1st*vol0 (bits 16-31)
+
+ movq mm4, mm2 ; copy
+ movq mm5, mm3 ; copy
+
+ punpcklwd mm2, mm3 ; pData_1st*vol1, pData_1st*vol0 (bits 0-31)
+ punpckhwd mm4, mm5 ; pData_2nd*vol1, pData_2nd*vol0 (bits 0-31)
+ psrad mm2, 8 ; shift right by 8
+ psrad mm4, 8 ; shift right by 8
+
+ add ecx, -2 ; decrement i-value
+ paddd mm2, QWORD PTR [eax] ; add to existing vals
+ paddd mm4, QWORD PTR [eax+8] ;
+
+ movq QWORD PTR [eax], mm2 ; store back
+ movq QWORD PTR [eax+8], mm4 ;
+
+ add eax, 10h ;
+ cmp ecx, 01h ; see if we can quit
+ jg BEGINLOAD ; Kipp Owens is a doof...
+ jl END ; Nick Shaffner is killing me...
+
+ movsx edi, WORD PTR [edx+2*esi] ; load first 16 bit val and zero-extend
+ imul edi, vol0 ; multiply pData[sampleIndex] by volume[0]
+ sar edi, 08h ; divide by 256
+ add DWORD PTR [eax], edi ; add to pOutput[i].left
+
+ movsx edi, WORD PTR [edx+2*esi] ; load same 16 bit val and zero-extend (cuz I thrashed the reg)
+ imul edi, vol1 ; multiply pData[sampleIndex] by volume[1]
+ sar edi, 08h ; divide by 256
+ add DWORD PTR [eax+04h], edi ; add to pOutput[i].right
+END:
+ emms;
+ }
+#endif
+}
+
+void SW_Mix16Mono_NoShift( portable_samplepair_t *pOutput, int *volume, short *pData, int outCount )
+{
+ int vol0 = volume[0];
+ int vol1 = volume[1];
+#if !id386
+ for ( int i = 0; i < outCount; i++ )
+ {
+ int x = *pData++;
+ pOutput[i].left += (x * vol0) >> 8;
+ pOutput[i].right += (x * vol1) >> 8;
+ }
+#else
+ __asm
+ {
+ mov eax, volume ;
+ movq mm0, DWORD PTR [eax] ; vol1, vol0 (32-bits each)
+ packssdw mm0, mm0 ; pack and replicate... vol1, vol0, vol1, vol0 (16-bits each)
+ //pxor mm7, mm7 ; mm7 is my zero register...
+
+ mov eax, DWORD PTR [pOutput] ; store initial output ptr
+ mov edx, DWORD PTR [pData] ; store initial input ptr
+ mov ecx, outCount;
+
+BEGINLOAD:
+ movd mm2, WORD PTR [edx] ; load first piece o data from pData
+ punpcklwd mm2, mm2 ; 0, 0, pData_1st, pData_1st
+ add edx,2 ; move to the next sample
+
+ movd mm3, WORD PTR [edx] ; load second piece o data from pData
+ punpcklwd mm3, mm3 ; 0, 0, pData_2nd, pData_2nd
+ punpckldq mm2, mm3 ; pData_2nd, pData_2nd, pData_2nd, pData_2nd
+
+ add edx,2 ; move to the next sample
+
+ movq mm3, mm2 ; copy the goods
+ pmullw mm2, mm0 ; pData_2nd*vol1, pData_2nd*vol0, pData_1st*vol1, pData_1st*vol0 (bits 0-15)
+ pmulhw mm3, mm0 ; pData_2nd*vol1, pData_2nd*vol0, pData_1st*vol1, pData_1st*vol0 (bits 16-31)
+
+ movq mm4, mm2 ; copy
+ movq mm5, mm3 ; copy
+
+ punpcklwd mm2, mm3 ; pData_1st*vol1, pData_1st*vol0 (bits 0-31)
+ punpckhwd mm4, mm5 ; pData_2nd*vol1, pData_2nd*vol0 (bits 0-31)
+ psrad mm2, 8 ; shift right by 8
+ psrad mm4, 8 ; shift right by 8
+
+ add ecx, -2 ; decrement i-value
+ paddd mm2, QWORD PTR [eax] ; add to existing vals
+ paddd mm4, QWORD PTR [eax+8] ;
+
+ movq QWORD PTR [eax], mm2 ; store back
+ movq QWORD PTR [eax+8], mm4 ;
+
+ add eax, 10h ;
+ cmp ecx, 01h ; see if we can quit
+ jg BEGINLOAD ; I can cut and paste code!
+ jl END ;
+
+ movsx edi, WORD PTR [edx] ; load first 16 bit val and zero-extend
+ mov esi,edi ; save a copy for the other channel
+ imul edi, vol0 ; multiply pData[sampleIndex] by volume[0]
+ sar edi, 08h ; divide by 256
+ add DWORD PTR [eax], edi ; add to pOutput[i].left
+
+ ; esi has a copy, use it now
+ imul esi, vol1 ; multiply pData[sampleIndex] by volume[1]
+ sar esi, 08h ; divide by 256
+ add DWORD PTR [eax+04h], esi ; add to pOutput[i].right
+END:
+ emms;
+ }
+#endif
+}
+
+void SW_Mix16Mono( portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ if ( rateScaleFix == FIX(1) )
+ {
+ SW_Mix16Mono_NoShift( pOutput, volume, pData, outCount );
+ }
+ else
+ {
+ SW_Mix16Mono_Shift( pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ }
+}
+
+// interpolating pitch shifter - sample(s) from preceding buffer are preloaded in
+// pData buffer, ensuring we can always provide 'outCount' samples.
+
+void SW_Mix16Mono_Interp( portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ fixedint sampleIndex = 0;
+ fixedint rateScaleFix14 = FIX_28TO14(rateScaleFix); // convert 28 bit fixed point to 14 bit fixed point
+ fixedint sampleFrac14 = FIX_28TO14(inputOffset);
+
+ int first, second, interp;
+
+ for ( int i = 0; i < outCount; i++ )
+ {
+ first = (int)(pData[sampleIndex]);
+ second = (int)(pData[sampleIndex+1]);
+
+ interp = first + (((second - first) * (int)sampleFrac14) >> 14);
+
+ pOutput[i].left += (volume[0] * interp) >> 8;
+ pOutput[i].right += (volume[1] * interp) >> 8;
+
+ sampleFrac14 += rateScaleFix14;
+ sampleIndex += FIX_INTPART14(sampleFrac14);
+ sampleFrac14 = FIX_FRACPART14(sampleFrac14);
+ }
+}
+
+void SW_Mix16Stereo( portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ int sampleIndex = 0;
+ fixedint sampleFrac = inputOffset;
+
+ for ( int i = 0; i < outCount; i++ )
+ {
+ pOutput[i].left += (volume[0] * (int)(pData[sampleIndex]))>>8;
+ pOutput[i].right += (volume[1] * (int)(pData[sampleIndex+1]))>>8;
+
+ sampleFrac += rateScaleFix;
+ sampleIndex += FIX_INTPART(sampleFrac)<<1;
+ sampleFrac = FIX_FRACPART(sampleFrac);
+ }
+}
+
+// interpolating pitch shifter - sample(s) from preceding buffer are preloaded in
+// pData buffer, ensuring we can always provide 'outCount' samples.
+
+void SW_Mix16Stereo_Interp( portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ fixedint sampleIndex = 0;
+ fixedint rateScaleFix14 = FIX_28TO14(rateScaleFix); // convert 28 bit fixed point to 14 bit fixed point
+ fixedint sampleFrac14 = FIX_28TO14(inputOffset);
+
+ int first, second, interpl, interpr;
+
+ for ( int i = 0; i < outCount; i++ )
+ {
+ first = (int)(pData[sampleIndex]);
+ second = (int)(pData[sampleIndex+2]);
+
+ interpl = first + (((second - first) * (int)sampleFrac14) >> 14);
+
+ first = (int)(pData[sampleIndex+1]);
+ second = (int)(pData[sampleIndex+3]);
+
+ interpr = first + (((second - first) * (int)sampleFrac14) >> 14);
+
+ pOutput[i].left += (volume[0] * interpl) >> 8;
+ pOutput[i].right += (volume[1] * interpr) >> 8;
+
+ sampleFrac14 += rateScaleFix14;
+ sampleIndex += FIX_INTPART14(sampleFrac14)<<1;
+ sampleFrac14 = FIX_FRACPART14(sampleFrac14);
+ }
+}
+// return true if mixer should use high quality pitch interpolation for this sound
+
+bool FUseHighQualityPitch( channel_t *pChannel )
+{
+ // do not use interpolating pitch shifter if:
+ // low quality flag set on sound (ie: wave name is prepended with CHAR_FAST_PITCH)
+ // or pitch has no fractional part
+ // or snd_pitchquality is 0
+ if ( !snd_pitchquality.GetInt() || pChannel->flags.bfast_pitch )
+ return false;
+
+ return ( (pChannel->pitch != floor(pChannel->pitch)) );
+}
+
+//===============================================================================
+// DISPATCHERS FOR MIXING ROUTINES
+//===============================================================================
+void Mix8MonoWavtype( channel_t *pChannel, portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ if ( FUseHighQualityPitch( pChannel ) )
+ SW_Mix8Mono_Interp( pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ else
+ SW_Mix8Mono( pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+}
+
+void Mix16MonoWavtype( channel_t *pChannel, portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ if ( FUseHighQualityPitch( pChannel ) )
+ SW_Mix16Mono_Interp( pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ else
+ // fast native coded mixers with lower quality pitch shift
+ SW_Mix16Mono( pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+}
+
+void Mix8StereoWavtype( channel_t *pChannel, portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ switch ( pChannel->wavtype )
+ {
+ case CHAR_DOPPLER:
+ SW_Mix8StereoDopplerLeft( pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ SW_Mix8StereoDopplerRight( pOutput, &volume[IFRONT_LEFTD], pData, inputOffset, rateScaleFix, outCount );
+ break;
+
+ case CHAR_DIRECTIONAL:
+ if ( FUseHighQualityPitch( pChannel ) )
+ SW_Mix8StereoDirectional_Interp( pChannel->dspface, pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ else
+ SW_Mix8StereoDirectional( pChannel->dspface, pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ break;
+
+ case CHAR_DISTVARIANT:
+ if ( FUseHighQualityPitch( pChannel ) )
+ SW_Mix8StereoDistVar_Interp( pChannel->distmix, pOutput, volume, pData, inputOffset, rateScaleFix, outCount);
+ else
+ SW_Mix8StereoDistVar( pChannel->distmix, pOutput, volume, pData, inputOffset, rateScaleFix, outCount);
+ break;
+
+ case CHAR_OMNI:
+ // non directional stereo - all channel volumes are the same
+ if ( FUseHighQualityPitch( pChannel ) )
+ SW_Mix8Stereo_Interp( pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ else
+ SW_Mix8Stereo( pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ break;
+
+ default:
+ case CHAR_SPATIALSTEREO:
+ if ( FUseHighQualityPitch( pChannel ) )
+ SW_Mix8Stereo_Interp( pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ else
+ SW_Mix8Stereo( pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ break;
+ }
+}
+
+
+void Mix16StereoWavtype( channel_t *pChannel, portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount )
+{
+ switch ( pChannel->wavtype )
+ {
+ case CHAR_DOPPLER:
+ SW_Mix16StereoDopplerLeft( pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ SW_Mix16StereoDopplerRight( pOutput, &volume[IFRONT_LEFTD], pData, inputOffset, rateScaleFix, outCount );
+ break;
+
+ case CHAR_DIRECTIONAL:
+ if ( FUseHighQualityPitch( pChannel ) )
+ SW_Mix16StereoDirectional_Interp( pChannel->dspface, pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ else
+ SW_Mix16StereoDirectional( pChannel->dspface, pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ break;
+
+ case CHAR_DISTVARIANT:
+ if ( FUseHighQualityPitch( pChannel ) )
+ SW_Mix16StereoDistVar_Interp( pChannel->distmix, pOutput, volume, pData, inputOffset, rateScaleFix, outCount);
+ else
+ SW_Mix16StereoDistVar( pChannel->distmix, pOutput, volume, pData, inputOffset, rateScaleFix, outCount);
+ break;
+
+ case CHAR_OMNI:
+ // non directional stereo - all channel volumes are same
+ if ( FUseHighQualityPitch( pChannel ) )
+ SW_Mix16Stereo_Interp( pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ else
+ SW_Mix16Stereo( pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ break;
+
+ default:
+ case CHAR_SPATIALSTEREO:
+ if ( FUseHighQualityPitch( pChannel ) )
+ SW_Mix16Stereo_Interp( pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ else
+ SW_Mix16Stereo( pOutput, volume, pData, inputOffset, rateScaleFix, outCount );
+ break;
+ }
+}
+
+
+//===============================================================================
+// Client entity mouth movement code. Set entity mouthopen variable, based
+// on the sound envelope of the voice channel playing.
+// KellyB 10/22/97
+//===============================================================================
+
+
+extern IBaseClientDLL *g_ClientDLL;
+
+// called when voice channel is first opened on this entity
+static CMouthInfo *GetMouthInfoForChannel( channel_t *pChannel )
+{
+#ifndef DEDICATED
+ // If it's a sound inside the client UI, ask the client for the mouthinfo
+ if ( pChannel->soundsource == SOUND_FROM_UI_PANEL )
+ return g_ClientDLL ? g_ClientDLL->GetClientUIMouthInfo() : NULL;
+#endif
+
+ int mouthentity = pChannel->speakerentity == -1 ? pChannel->soundsource : pChannel->speakerentity;
+
+ IClientEntity *pClientEntity = entitylist->GetClientEntity( mouthentity );
+
+ if( !pClientEntity )
+ return NULL;
+
+ return pClientEntity->GetMouth();
+}
+
+void SND_InitMouth( channel_t *pChannel )
+{
+ if ( SND_IsMouth( pChannel ) )
+ {
+ CMouthInfo *pMouth = GetMouthInfoForChannel(pChannel);
+ // init mouth movement vars
+ if ( pMouth )
+ {
+ pMouth->mouthopen = 0;
+ pMouth->sndavg = 0;
+ pMouth->sndcount = 0;
+ if ( pChannel->sfx->pSource && pChannel->sfx->pSource->GetSentence() )
+ {
+ pMouth->AddSource( pChannel->sfx->pSource, pChannel->flags.m_bIgnorePhonemes );
+ }
+ }
+ }
+}
+
+// called when channel stops
+
+void SND_CloseMouth(channel_t *pChannel)
+{
+ if ( SND_IsMouth( pChannel ) )
+ {
+ CMouthInfo *pMouth = GetMouthInfoForChannel(pChannel);
+ if ( pMouth )
+ {
+ // shut mouth
+ int idx = pMouth->GetIndexForSource( pChannel->sfx->pSource );
+
+ if ( idx != UNKNOWN_VOICE_SOURCE )
+ {
+ pMouth->RemoveSourceByIndex(idx);
+ }
+ else
+ {
+ pMouth->ClearVoiceSources();
+ }
+ pMouth->mouthopen = 0;
+ }
+ }
+}
+
+#define CAVGSAMPLES 10
+// need this to make the debug code below work.
+//#include "snd_wave_source.h"
+void SND_MoveMouth8( channel_t *ch, CAudioSource *pSource, int count )
+{
+ int data;
+ char *pdata = NULL;
+ int i;
+ int savg;
+ int scount;
+
+ CMouthInfo *pMouth = GetMouthInfoForChannel( ch );
+
+ if ( !pMouth )
+ return;
+
+ if ( pSource->GetSentence() )
+ {
+ int idx = pMouth->GetIndexForSource( pSource );
+
+ if ( idx == UNKNOWN_VOICE_SOURCE )
+ {
+ if ( pMouth->AddSource( pSource, ch->flags.m_bIgnorePhonemes ) == NULL )
+ {
+ DevMsg( 1, "out of voice sources, won't lipsync %s\n", ch->sfx->getname() );
+#if 0
+ for ( int i = 0; i < pMouth->GetNumVoiceSources(); i++ )
+ {
+ CVoiceData *pVoice = pMouth->GetVoiceSource(i);
+ CAudioSourceWave *pWave = dynamic_cast<CAudioSourceWave *>(pVoice->GetSource());
+ const char *pName = "unknown";
+ if ( pWave && pWave->GetName() )
+ pName = pWave->GetName();
+ Msg("Playing %s...\n", pName );
+ }
+#endif
+ }
+ }
+ else
+ {
+ // Update elapsed time from mixer
+ CVoiceData *vd = pMouth->GetVoiceSource( idx );
+ Assert( vd );
+ if ( vd )
+ {
+ Assert( pSource->SampleRate() > 0 );
+
+ float elapsed = ( float )ch->pMixer->GetSamplePosition() / ( float )pSource->SampleRate();
+
+ vd->SetElapsedTime( elapsed );
+ }
+ }
+ }
+
+ if ( IsX360() )
+ {
+ // not supporting because data is assumed to be 8 bit and bypasses mixer (decoding)
+ return;
+ }
+
+ if ( pMouth->NeedsEnvelope() )
+ {
+ int availableSamples = pSource->GetOutputData((void**)&pdata, ch->pMixer->GetSamplePosition(), count, NULL );
+
+ if( pdata == NULL )
+ return;
+
+ i = 0;
+ scount = pMouth->sndcount;
+ savg = 0;
+
+ while ( i < availableSamples && scount < CAVGSAMPLES )
+ {
+ data = pdata[i];
+ savg += abs(data);
+
+ i += 80 + ((byte)data & 0x1F);
+ scount++;
+ }
+
+ pMouth->sndavg += savg;
+ pMouth->sndcount = (byte) scount;
+
+ if ( pMouth->sndcount >= CAVGSAMPLES )
+ {
+ pMouth->mouthopen = pMouth->sndavg / CAVGSAMPLES;
+ pMouth->sndavg = 0;
+ pMouth->sndcount = 0;
+ }
+ }
+ else
+ {
+ pMouth->mouthopen = 0;
+ }
+}
+
+
+void SND_UpdateMouth( channel_t *pChannel )
+{
+ CMouthInfo *m = GetMouthInfoForChannel( pChannel );
+ if ( !m )
+ return;
+
+ if ( pChannel->sfx )
+ {
+ m->AddSource( pChannel->sfx->pSource, pChannel->flags.m_bIgnorePhonemes );
+ }
+}
+
+
+void SND_ClearMouth( channel_t *pChannel )
+{
+ CMouthInfo *m = GetMouthInfoForChannel( pChannel );
+ if ( !m )
+ return;
+
+ if ( pChannel->sfx )
+ {
+ m->RemoveSource( pChannel->sfx->pSource );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pChannel -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool SND_IsMouth( channel_t *pChannel )
+{
+#ifndef DEDICATED
+ if ( pChannel->soundsource == SOUND_FROM_UI_PANEL )
+ return true;
+#endif
+
+ if ( !entitylist )
+ {
+ return false;
+ }
+
+ if ( pChannel->entchannel == CHAN_VOICE || pChannel->entchannel == CHAN_VOICE2 )
+ {
+ return true;
+ }
+
+ if ( pChannel->sfx &&
+ pChannel->sfx->pSource &&
+ pChannel->sfx->pSource->GetSentence() )
+ {
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pChannel -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool SND_ShouldPause( channel_t *pChannel )
+{
+ return pChannel->flags.m_bShouldPause;
+}
+
+//===============================================================================
+// Movie recording support
+//===============================================================================
+
+void SND_RecordInit()
+{
+ g_paintedtime = 0;
+ g_soundtime = 0;
+
+ // TMP Wave file supports stereo only, so force stereo
+ if ( snd_surround.GetInt() != 2 )
+ {
+ snd_surround.SetValue( 2 );
+ }
+}
+
+void SND_MovieStart( void )
+{
+ if ( IsX360() )
+ return;
+
+ if ( !cl_movieinfo.IsRecording() )
+ return;
+
+ SND_RecordInit();
+
+ // 44k: engine playback rate is now 44100...changed from 22050
+ if ( cl_movieinfo.DoWav() )
+ {
+ WaveCreateTmpFile( cl_movieinfo.moviename, SOUND_DMA_SPEED, 16, 2 );
+ }
+}
+
+void SND_MovieEnd( void )
+{
+ if ( IsX360() )
+ return;
+
+ if ( !cl_movieinfo.IsRecording() )
+ {
+ return;
+ }
+
+ if ( cl_movieinfo.DoWav() )
+ {
+ WaveFixupTmpFile( cl_movieinfo.moviename );
+ }
+}
+
+bool SND_IsRecording()
+{
+ return ( ( IsReplayRendering() || cl_movieinfo.IsRecording() ) && !Con_IsVisible() );
+}
+
+
+
+extern IVideoRecorder *g_pVideoRecorder;
+void SND_RecordBuffer( void )
+{
+ if ( IsX360() )
+ return;
+
+ if ( !SND_IsRecording() )
+ return;
+
+ int i;
+ int val;
+ int bufferSize = snd_linear_count * sizeof(short);
+ short *tmp = (short *)_alloca( bufferSize );
+
+ for (i=0 ; i<snd_linear_count ; i+=2)
+ {
+ val = (snd_p[i]*snd_vol)>>8;
+ tmp[i] = CLIP(val);
+
+ val = (snd_p[i+1]*snd_vol)>>8;
+ tmp[i+1] = CLIP(val);
+ }
+
+ if ( IsReplayRendering() )
+ {
+#if defined( REPLAY_ENABLED )
+ extern IClientReplayContext *g_pClientReplayContext;
+ IReplayMovieRenderer *pMovieRenderer = g_pClientReplayContext->GetMovieRenderer();
+ if ( IsReplayRendering() && pMovieRenderer && pMovieRenderer->IsAudioSyncFrame() )
+ {
+ pMovieRenderer->RenderAudio( (unsigned char *)tmp, bufferSize, snd_linear_count );
+ }
+#endif
+ }
+ else
+ {
+ if ( cl_movieinfo.DoWav() )
+ {
+ WaveAppendTmpFile( cl_movieinfo.moviename, tmp, 16, snd_linear_count );
+ }
+
+ if ( cl_movieinfo.DoVideoSound() )
+ {
+ g_pVideoRecorder->AppendAudioSamples( tmp, bufferSize );
+ }
+ }
+}
diff --git a/engine/audio/private/snd_mix_buf.h b/engine/audio/private/snd_mix_buf.h
new file mode 100644
index 0000000..b51da6d
--- /dev/null
+++ b/engine/audio/private/snd_mix_buf.h
@@ -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
diff --git a/engine/audio/private/snd_mp3_source.cpp b/engine/audio/private/snd_mp3_source.cpp
new file mode 100644
index 0000000..d4b012e
--- /dev/null
+++ b/engine/audio/private/snd_mp3_source.cpp
@@ -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
+
diff --git a/engine/audio/private/snd_mp3_source.h b/engine/audio/private/snd_mp3_source.h
new file mode 100644
index 0000000..38d6291
--- /dev/null
+++ b/engine/audio/private/snd_mp3_source.h
@@ -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
diff --git a/engine/audio/private/snd_posix.cpp b/engine/audio/private/snd_posix.cpp
new file mode 100644
index 0000000..629105b
--- /dev/null
+++ b/engine/audio/private/snd_posix.cpp
@@ -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"
+
+
diff --git a/engine/audio/private/snd_sentence_mixer.cpp b/engine/audio/private/snd_sentence_mixer.cpp
new file mode 100644
index 0000000..7a5fd13
--- /dev/null
+++ b/engine/audio/private/snd_sentence_mixer.cpp
@@ -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;
+}
diff --git a/engine/audio/private/snd_sfx.h b/engine/audio/private/snd_sfx.h
new file mode 100644
index 0000000..a0de0d3
--- /dev/null
+++ b/engine/audio/private/snd_sfx.h
@@ -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
diff --git a/engine/audio/private/snd_stubs.cpp b/engine/audio/private/snd_stubs.cpp
new file mode 100644
index 0000000..9adb60a
--- /dev/null
+++ b/engine/audio/private/snd_stubs.cpp
@@ -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 \ No newline at end of file
diff --git a/engine/audio/private/snd_stubs.h b/engine/audio/private/snd_stubs.h
new file mode 100644
index 0000000..9435813
--- /dev/null
+++ b/engine/audio/private/snd_stubs.h
@@ -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
diff --git a/engine/audio/private/snd_wave_data.cpp b/engine/audio/private/snd_wave_data.cpp
new file mode 100644
index 0000000..c03b524
--- /dev/null
+++ b/engine/audio/private/snd_wave_data.cpp
@@ -0,0 +1,2394 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+
+#include "audio_pch.h"
+#include "datacache/idatacache.h"
+#include "utllinkedlist.h"
+#include "utldict.h"
+#include "filesystem/IQueuedLoader.h"
+#include "cdll_int.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+extern IVEngineClient *engineClient;
+extern IFileSystem *g_pFileSystem;
+extern IDataCache *g_pDataCache;
+extern double realtime;
+
+// console streaming buffer implementation, appropriate for high latency and low memory
+// shift this many buffers through the wave
+#define STREAM_BUFFER_COUNT 2
+// duration of audio samples per buffer, 200ms is 2x the worst frame rate (10Hz)
+// the engine then has at least 400ms to deliver a new buffer or pop (assuming 2 buffers)
+#define STREAM_BUFFER_TIME 0.200f
+// force a single buffer when streaming waves smaller than this
+#define STREAM_BUFFER_DATASIZE XBOX_DVD_ECC_SIZE
+
+// PC single buffering implementation
+// UNDONE: Allocate this in cache instead?
+#define SINGLE_BUFFER_SIZE 16384
+
+// Force a small cache for debugging cache issues.
+// #define FORCE_SMALL_MEMORY_CACHE_SIZE ( 6 * 1024 * 1024 )
+
+#define DEFAULT_WAV_MEMORY_CACHE ( 16 * 1024 * 1024 )
+#define DEFAULT_XBOX_WAV_MEMORY_CACHE ( 16 * 1024 * 1024 )
+#define TF_XBOX_WAV_MEMORY_CACHE ( 24 * 1024 * 1024 ) // Team Fortress uses a larger cache
+
+// Dev builds will be missing soundcaches and hitch sometimes, we only care if its being properly launched from steam where sound caches should be complete.
+ConVar snd_async_spew_blocking( "snd_async_spew_blocking", "1", 0, "Spew message to console any time async sound loading blocks on file i/o. ( 0=Off, 1=With -steam only, 2=Always" );
+ConVar snd_async_spew( "snd_async_spew", "0", 0, "Spew all async sound reads, including success" );
+ConVar snd_async_fullyasync( "snd_async_fullyasync", "0", 0, "All playback is fully async (sound doesn't play until data arrives)." );
+ConVar snd_async_stream_spew( "snd_async_stream_spew", "0", 0, "Spew streaming info ( 0=Off, 1=streams, 2=buffers" );
+
+static bool SndAsyncSpewBlocking()
+{
+ int pref = snd_async_spew_blocking.GetInt();
+ return ( pref >= 2 ) || ( pref == 1 && CommandLine()->FindParm( "-steam" ) != 0 );
+}
+
+#define SndAlignReads() 1
+
+void MaybeReportMissingWav( char const *wav );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+struct asyncwaveparams_t
+{
+ asyncwaveparams_t() : bPrefetch( false ), bCanBeQueued( false ) {}
+
+ FileNameHandle_t hFilename; // handle to sound item name (i.e. not with sound\ prefix)
+ int datasize;
+ int seekpos;
+ int alignment;
+ bool bPrefetch;
+ bool bCanBeQueued;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Builds a cache of the data bytes for a specific .wav file
+//-----------------------------------------------------------------------------
+class CAsyncWaveData
+{
+public:
+ explicit CAsyncWaveData();
+
+ // APIS required by CManagedDataCacheClient
+ void DestroyResource();
+ CAsyncWaveData *GetData();
+ unsigned int Size();
+
+ static void AsyncCallback( const FileAsyncRequest_t &asyncRequest, int numReadBytes, FSAsyncStatus_t err );
+ static void QueuedLoaderCallback( void *pContext, void *pContext2, const void *pData, int nSize, LoaderError_t loaderError );
+ static CAsyncWaveData *CreateResource( const asyncwaveparams_t &params );
+ static unsigned int EstimatedSize( const asyncwaveparams_t &params );
+
+ void OnAsyncCompleted( const FileAsyncRequest_t* asyncFilePtr, int numReadBytes, FSAsyncStatus_t err );
+ bool BlockingCopyData( void *destbuffer, int destbufsize, int startoffset, int count );
+ bool BlockingGetDataPointer( void **ppData );
+ void SetAsyncPriority( int priority );
+ void StartAsyncLoading( const asyncwaveparams_t& params );
+ bool GetPostProcessed();
+ void SetPostProcessed( bool proc );
+
+ bool IsCurrentlyLoading();
+ char const *GetFileName();
+
+ // Data
+public:
+ int m_nDataSize; // bytes requested
+ int m_nReadSize; // bytes actually read
+ void *m_pvData; // target buffer
+ byte *m_pAlloc; // memory of buffer (base may not match)
+ FileAsyncRequest_t m_async;
+ FSAsyncControl_t m_hAsyncControl;
+ float m_start; // time at request invocation
+ float m_arrival; // time at data arrival
+ FileNameHandle_t m_hFileNameHandle;
+ int m_nBufferBytes; // size of any pre-allocated target buffer
+ BufferHandle_t m_hBuffer; // used to dequeue the buffer after lru
+ unsigned int m_bLoaded : 1;
+ unsigned int m_bMissing : 1;
+ unsigned int m_bPostProcessed : 1;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: C'tor
+//-----------------------------------------------------------------------------
+CAsyncWaveData::CAsyncWaveData() :
+ m_nDataSize( 0 ),
+ m_nReadSize( 0 ),
+ m_pvData( 0 ),
+ m_pAlloc( 0 ),
+ m_hBuffer( INVALID_BUFFER_HANDLE ),
+ m_nBufferBytes( 0 ),
+ m_hAsyncControl( NULL ),
+ m_bLoaded( false ),
+ m_bMissing( false ),
+ m_start( 0.0 ),
+ m_arrival( 0.0 ),
+ m_bPostProcessed( false ),
+ m_hFileNameHandle( 0 )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: // APIS required by CDataLRU
+//-----------------------------------------------------------------------------
+void CAsyncWaveData::DestroyResource()
+{
+ if ( IsPC() )
+ {
+ if ( m_hAsyncControl )
+ {
+ if ( !m_bLoaded && !m_bMissing )
+ {
+ // NOTE: We CANNOT call AsyncAbort since if the file is actually being read we'll end
+ // up still getting a callback, but our this ptr (deleted below) will be feeefeee and we'll trash the heap
+ // pretty bad. So we call AsyncFinish, which will do a blocking read and will definitely succeed
+ // Block until we are finished
+ g_pFileSystem->AsyncFinish( m_hAsyncControl, true );
+ }
+
+ g_pFileSystem->AsyncRelease( m_hAsyncControl );
+ m_hAsyncControl = NULL;
+ }
+ }
+
+ if ( IsX360() )
+ {
+ if ( m_hAsyncControl )
+ {
+ if ( !m_bLoaded && !m_bMissing )
+ {
+ // force an abort
+ int errStatus = g_pFileSystem->AsyncAbort( m_hAsyncControl );
+ if ( errStatus != FSASYNC_ERR_UNKNOWNID )
+ {
+ // must wait for abort to finish before deallocating data
+ g_pFileSystem->AsyncFinish( m_hAsyncControl, true );
+ }
+ }
+ g_pFileSystem->AsyncRelease( m_hAsyncControl );
+ m_hAsyncControl = NULL;
+ }
+ if ( m_hBuffer != INVALID_BUFFER_HANDLE )
+ {
+ // hint the manager that this tracked buffer is invalid
+ wavedatacache->MarkBufferDiscarded( m_hBuffer );
+ }
+ }
+
+ // delete buffers
+ if ( IsPC() || !IsX360() )
+ {
+ g_pFileSystem->FreeOptimalReadBuffer( m_pAlloc );
+ }
+ else
+ {
+ delete [] m_pAlloc;
+ }
+
+ delete this;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : char const
+//-----------------------------------------------------------------------------
+char const *CAsyncWaveData::GetFileName()
+{
+ static char sz[MAX_PATH];
+
+ if ( m_hFileNameHandle )
+ {
+ if ( g_pFileSystem->String( m_hFileNameHandle, sz, sizeof( sz ) ) )
+ {
+ return sz;
+ }
+ }
+
+ Assert( 0 );
+ return "";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : CAsyncWaveData
+//-----------------------------------------------------------------------------
+CAsyncWaveData *CAsyncWaveData::GetData()
+{
+ return this;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : unsigned int
+//-----------------------------------------------------------------------------
+unsigned int CAsyncWaveData::Size()
+{
+ int size = sizeof( *this );
+
+ if ( IsPC() )
+ {
+ size += m_nDataSize;
+ }
+
+ if ( IsX360() )
+ {
+ // the data size can shrink during streaming near end of file
+ // need the real contant size of this object's allocations
+ size += m_nBufferBytes;
+ }
+
+ return size;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Static method for CDataLRU
+// Input : &params -
+// Output : CAsyncWaveData
+//-----------------------------------------------------------------------------
+CAsyncWaveData *CAsyncWaveData::CreateResource( const asyncwaveparams_t &params )
+{
+ CAsyncWaveData *pData = new CAsyncWaveData;
+ Assert( pData );
+ if ( pData )
+ {
+ if ( IsX360() )
+ {
+ // create buffer now for re-use during streaming process
+ pData->m_nBufferBytes = AlignValue( params.datasize, params.alignment );
+ pData->m_pAlloc = new byte[pData->m_nBufferBytes];
+ pData->m_pvData = pData->m_pAlloc;
+ }
+ pData->StartAsyncLoading( params );
+ }
+
+ return pData;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Static method
+// Input : &params -
+// Output : static unsigned int
+//-----------------------------------------------------------------------------
+unsigned int CAsyncWaveData::EstimatedSize( const asyncwaveparams_t &params )
+{
+ int size = sizeof( CAsyncWaveData );
+
+ if ( IsPC() )
+ {
+ size += params.datasize;
+ }
+ if ( IsX360() )
+ {
+ // the expected size of this object's allocations
+ size += AlignValue( params.datasize, params.alignment );
+ }
+
+ return size;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Static method, called by thread, don't call anything non-threadsafe from handler!!!
+// Input : asyncFilePtr -
+// numReadBytes -
+// err -
+//-----------------------------------------------------------------------------
+void CAsyncWaveData::AsyncCallback(const FileAsyncRequest_t &asyncRequest, int numReadBytes, FSAsyncStatus_t err )
+{
+ CAsyncWaveData *pObject = reinterpret_cast< CAsyncWaveData * >( asyncRequest.pContext );
+ Assert( pObject );
+ if ( pObject )
+ {
+ pObject->OnAsyncCompleted( &asyncRequest, numReadBytes, err );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Static method, called by thread, don't call anything non-threadsafe from handler!!!
+//-----------------------------------------------------------------------------
+void CAsyncWaveData::QueuedLoaderCallback( void *pContext, void *pContext2, const void *pData, int nSize, LoaderError_t loaderError )
+{
+ CAsyncWaveData *pObject = reinterpret_cast< CAsyncWaveData * >( pContext );
+ Assert( pObject );
+
+ pObject->OnAsyncCompleted( NULL, nSize, loaderError == LOADERERROR_NONE ? FSASYNC_OK : FSASYNC_ERR_FILEOPEN );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: NOTE: THIS IS CALLED FROM A THREAD SO YOU CAN'T CALL INTO ANYTHING NON-THREADSAFE
+// such as CUtlSymbolTable/CUtlDict (many of the CUtl* are non-thread safe)!!!
+// Input : asyncFilePtr -
+// numReadBytes -
+// err -
+//-----------------------------------------------------------------------------
+void CAsyncWaveData::OnAsyncCompleted( const FileAsyncRequest_t *asyncFilePtr, int numReadBytes, FSAsyncStatus_t err )
+{
+ if ( IsPC() )
+ {
+ // Take hold of pointer (we can just use delete[] across .dlls because we are using a shared memory allocator...)
+ if ( err == FSASYNC_OK || err == FSASYNC_ERR_READING )
+ {
+ m_arrival = ( float )Plat_FloatTime();
+
+ // Take over ptr
+ m_pAlloc = ( byte * )asyncFilePtr->pData;
+ if ( SndAlignReads() )
+ {
+ m_async.nOffset = ( m_async.nBytes - m_nDataSize );
+ m_async.nBytes -= m_async.nOffset;
+ m_pvData = ((byte *)m_pAlloc) + m_async.nOffset;
+ m_nReadSize = numReadBytes - m_async.nOffset;
+ }
+ else
+ {
+ m_pvData = m_pAlloc;
+ m_nReadSize = numReadBytes;
+ }
+
+ // Needs to be post-processed
+ m_bPostProcessed = false;
+
+ // Finished loading
+ m_bLoaded = true;
+ }
+ else if ( err == FSASYNC_ERR_FILEOPEN )
+ {
+ // SEE NOTE IN FUNCTION COMMENT ABOVE!!!
+ // Tracker 22905, et al.
+ // Because this api gets called from the other thread, don't spew warning here as it can
+ // cause a crash in searching CUtlSymbolTables since they use a global var for a LessFunc context!!!
+ m_bMissing = true;
+ }
+ }
+
+ if ( IsX360() )
+ {
+ m_arrival = (float)Plat_FloatTime();
+
+ // possibly reading more than intended due to alignment restriction
+ m_nReadSize = numReadBytes;
+ if ( m_nReadSize > m_nDataSize )
+ {
+ // clamp to expected, extra data is unreliable
+ m_nReadSize = m_nDataSize;
+ }
+
+ if ( err != FSASYNC_OK )
+ {
+ // track as any error
+ m_bMissing = true;
+ }
+
+ if ( err != FSASYNC_ERR_FILEOPEN )
+ {
+ // some data got loaded
+ m_bLoaded = true;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *destbuffer -
+// destbufsize -
+// startoffset -
+// count -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAsyncWaveData::BlockingCopyData( void *destbuffer, int destbufsize, int startoffset, int count )
+{
+ if ( !m_bLoaded )
+ {
+ Assert( m_hAsyncControl );
+ // Force it to finish
+ // It could finish between the above line and here, but the AsyncFinish call will just have a bogus id, not a big deal
+ if ( SndAsyncSpewBlocking() )
+ {
+ // Force it to finish
+ float st = ( float )Plat_FloatTime();
+ g_pFileSystem->AsyncFinish( m_hAsyncControl, true );
+ float ed = ( float )Plat_FloatTime();
+ Warning( "%f BCD: Async I/O Force %s (%8.2f msec / %8.2f msec total)\n", realtime, GetFileName(), 1000.0f * (float)( ed - st ), 1000.0f * (float)( m_arrival - m_start ) );
+ }
+ else
+ {
+ g_pFileSystem->AsyncFinish( m_hAsyncControl, true );
+ }
+ }
+
+ // notify on any error
+ if ( m_bMissing )
+ {
+ // Only warn once
+ m_bMissing = false;
+
+ char fn[MAX_PATH];
+ if ( g_pFileSystem->String( m_hFileNameHandle, fn, sizeof( fn ) ) )
+ {
+ MaybeReportMissingWav( fn );
+ }
+ }
+
+ if ( !m_bLoaded )
+ {
+ return false;
+ }
+ else if ( m_arrival != 0 && snd_async_spew.GetBool() )
+ {
+ DevMsg( "%f Async I/O Read successful %s (%8.2f msec)\n", realtime, GetFileName(), 1000.0f * (float)( m_arrival - m_start ) );
+ m_arrival = 0;
+ }
+
+ // clamp requested to available
+ if ( count > m_nReadSize )
+ {
+ count = m_nReadSize - startoffset;
+ }
+
+ if ( count < 0 )
+ {
+ return false;
+ }
+
+ // Copy data from stream buffer
+ Q_memcpy( destbuffer, (char *)m_pvData + ( startoffset - m_async.nOffset ), count );
+
+ g_pFileSystem->AsyncRelease( m_hAsyncControl );
+ m_hAsyncControl = NULL;
+ return true;
+}
+
+
+bool CAsyncWaveData::IsCurrentlyLoading()
+{
+ if ( m_bLoaded )
+ return true;
+ FSAsyncStatus_t status = g_pFileSystem->AsyncStatus( m_hAsyncControl );
+ if ( status == FSASYNC_STATUS_INPROGRESS || status == FSASYNC_OK )
+ return true;
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : **ppData -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAsyncWaveData::BlockingGetDataPointer( void **ppData )
+{
+ Assert( ppData );
+ if ( !m_bLoaded )
+ {
+ // Force it to finish
+ // It could finish between the above line and here, but the AsyncFinish call will just have a bogus id, not a big deal
+ if ( SndAsyncSpewBlocking() )
+ {
+ float st = ( float )Plat_FloatTime();
+ g_pFileSystem->AsyncFinish( m_hAsyncControl, true );
+ float ed = ( float )Plat_FloatTime();
+ Warning( "%f BlockingGetDataPointer: Async I/O Force %s (%8.2f msec / %8.2f msec total )\n", realtime, GetFileName(), 1000.0f * (float)( ed - st ), 1000.0f * (float)( m_arrival - m_start ) );
+ }
+ else
+ {
+ g_pFileSystem->AsyncFinish( m_hAsyncControl, true );
+ }
+ }
+
+ // notify on any error
+ if ( m_bMissing )
+ {
+ // Only warn once
+ m_bMissing = false;
+
+ char fn[MAX_PATH];
+ if ( g_pFileSystem->String( m_hFileNameHandle, fn, sizeof( fn ) ) )
+ {
+ MaybeReportMissingWav( fn );
+ }
+ }
+
+ if ( !m_bLoaded )
+ {
+ return false;
+ }
+ else if ( m_arrival != 0 && snd_async_spew.GetBool() )
+ {
+ DevMsg( "%f Async I/O Read successful %s (%8.2f msec)\n", realtime, GetFileName(), 1000.0f * (float)( m_arrival - m_start ) );
+ m_arrival = 0;
+ }
+
+ *ppData = m_pvData;
+
+ g_pFileSystem->AsyncRelease( m_hAsyncControl );
+ m_hAsyncControl = NULL;
+
+ return true;
+}
+
+void CAsyncWaveData::SetAsyncPriority( int priority )
+{
+ if ( m_async.priority != priority )
+ {
+ m_async.priority = priority;
+ g_pFileSystem->AsyncSetPriority( m_hAsyncControl, m_async.priority );
+ if ( snd_async_spew.GetBool() )
+ {
+ DevMsg( "%f Async I/O Bumped priority for %s (%8.2f msec)\n", realtime, GetFileName(), 1000.0f * (float)( Plat_FloatTime() - m_start ) );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : params -
+//-----------------------------------------------------------------------------
+void CAsyncWaveData::StartAsyncLoading( const asyncwaveparams_t& params )
+{
+ Assert( IsX360() || ( IsPC() && !m_bLoaded ) );
+
+ // expected to be relative to the sound\ dir
+ m_hFileNameHandle = params.hFilename;
+
+ // build the real filename
+ char szFilename[MAX_PATH];
+ Q_snprintf( szFilename, sizeof( szFilename ), "sound\\%s", GetFileName() );
+
+ int nPriority = 1;
+ if ( params.bPrefetch )
+ {
+ // lower the priority of prefetched sounds, so they don't block immediate sounds from being loaded
+ nPriority = 0;
+ }
+
+ if ( !IsX360() )
+ {
+ m_async.pData = NULL;
+ if ( SndAlignReads() )
+ {
+ m_async.nOffset = 0;
+ m_async.nBytes = params.seekpos + params.datasize;
+ }
+ else
+ {
+ m_async.nOffset = params.seekpos;
+ m_async.nBytes = params.datasize;
+ }
+ }
+ else
+ {
+ Assert( params.datasize > 0 );
+
+ // using explicit allocated buffer on xbox
+ m_async.pData = m_pvData;
+ m_async.nOffset = params.seekpos;
+ m_async.nBytes = AlignValue( params.datasize, params.alignment );
+ }
+
+ m_async.pfnCallback = AsyncCallback; // optional completion callback
+ m_async.pContext = (void *)this; // caller's unique context
+ m_async.priority = nPriority; // inter list priority, 0=lowest
+ m_async.flags = IsX360() ? 0 : FSASYNC_FLAGS_ALLOCNOFREE;
+ m_async.pszPathID = "GAME";
+
+ m_bLoaded = false;
+ m_bMissing = false;
+ m_nDataSize = params.datasize;
+ m_start = (float)Plat_FloatTime();
+ m_arrival = 0;
+ m_nReadSize = 0;
+ m_bPostProcessed = false;
+
+ // The async layer creates a copy of this string, ok to send a local reference
+ m_async.pszFilename = szFilename;
+
+ char szFullName[MAX_PATH];
+ if ( IsX360() && ( g_pFileSystem->GetDVDMode() == DVDMODE_STRICT ) )
+ {
+ // all audio is expected be in zips
+ // resolve to absolute name now, where path can be filtered to just the zips (fast find, no real i/o)
+ // otherwise the dvd will do a costly seek for each zip miss due to search path fall through
+ PathTypeQuery_t pathType;
+ if ( !g_pFileSystem->RelativePathToFullPath( m_async.pszFilename, m_async.pszPathID, szFullName, sizeof( szFullName ), FILTER_CULLNONPACK, &pathType ) )
+ {
+ // not found, do callback now to handle error
+ m_async.pfnCallback( m_async, 0, FSASYNC_ERR_FILEOPEN );
+ return;
+ }
+ m_async.pszFilename = szFullName;
+ }
+
+ if ( IsX360() && params.bCanBeQueued )
+ {
+ // queued loader takes over
+ LoaderJob_t loaderJob;
+ loaderJob.m_pFilename = m_async.pszFilename;
+ loaderJob.m_pPathID = m_async.pszPathID;
+ loaderJob.m_pCallback = QueuedLoaderCallback;
+ loaderJob.m_pContext = (void *)this;
+ loaderJob.m_Priority = LOADERPRIORITY_DURINGPRELOAD;
+ loaderJob.m_pTargetData = m_async.pData;
+ loaderJob.m_nBytesToRead = m_async.nBytes;
+ loaderJob.m_nStartOffset = m_async.nOffset;
+ g_pQueuedLoader->AddJob( &loaderJob );
+ return;
+ }
+
+ MEM_ALLOC_CREDIT();
+
+ // Commence async I/O
+ Assert( !m_hAsyncControl );
+ g_pFileSystem->AsyncRead( m_async, &m_hAsyncControl );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAsyncWaveData::GetPostProcessed()
+{
+ return m_bPostProcessed;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : proc -
+//-----------------------------------------------------------------------------
+void CAsyncWaveData::SetPostProcessed( bool proc )
+{
+ m_bPostProcessed = proc;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Implements a cache of .wav / .mp3 data based on filename
+//-----------------------------------------------------------------------------
+class CAsyncWavDataCache : public IAsyncWavDataCache,
+ public CManagedDataCacheClient<CAsyncWaveData, asyncwaveparams_t>
+{
+public:
+ CAsyncWavDataCache();
+ ~CAsyncWavDataCache() {}
+
+ virtual bool Init( unsigned int memSize );
+ virtual void Shutdown();
+
+ // implementation that treats file as monolithic
+ virtual memhandle_t AsyncLoadCache( char const *filename, int datasize, int startpos, bool bIsPrefetch = false );
+ virtual void PrefetchCache( char const *filename, int datasize, int startpos );
+ virtual bool CopyDataIntoMemory( char const *filename, int datasize, int startpos, void *buffer, int bufsize, int copystartpos, int bytestocopy, bool *pbPostProcessed );
+ virtual bool CopyDataIntoMemory( memhandle_t& handle, char const *filename, int datasize, int startpos, void *buffer, int bufsize, int copystartpos, int bytestocopy, bool *pbPostProcessed );
+ virtual void SetPostProcessed( memhandle_t handle, bool proc );
+ virtual void Unload( memhandle_t handle );
+ virtual bool GetDataPointer( memhandle_t& handle, char const *filename, int datasize, int startpos, void **pData, int copystartpos, bool *pbPostProcessed );
+ virtual bool IsDataLoadCompleted( memhandle_t handle, bool *pIsValid );
+ virtual void RestartDataLoad( memhandle_t* handle, char const *filename, int datasize, int startpos );
+ virtual bool IsDataLoadInProgress( memhandle_t handle );
+
+ // Xbox: 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 );
+ virtual void CloseStreamedLoad( StreamHandle_t hStream );
+ virtual int CopyStreamedDataIntoMemory( StreamHandle_t hStream, void *pBuffer, int bufferSize, int copyStartPos, int bytesToCopy );
+ virtual bool IsStreamedDataReady( StreamHandle_t hStream );
+ virtual void MarkBufferDiscarded( BufferHandle_t hBuffer );
+ virtual void *GetStreamedDataPointer( StreamHandle_t hStream, bool bSync );
+
+ virtual void Flush();
+ virtual void OnMixBegin();
+ virtual void OnMixEnd();
+
+ void QueueUnlock( const memhandle_t &handle );
+ void SpewMemoryUsage( int level );
+
+ // Cache helpers
+ bool GetItemName( DataCacheClientID_t clientId, const void *pItem, char *pDest, unsigned nMaxLen );
+
+private:
+ void Clear();
+
+ struct CacheEntry_t
+ {
+ CacheEntry_t() :
+ name( 0 ),
+ handle( 0 )
+ {
+ }
+ FileNameHandle_t name;
+ memhandle_t handle;
+ };
+
+ // tags the signature of a buffer inside a rb tree for faster than linear find
+ struct BufferEntry_t
+ {
+ FileNameHandle_t m_hName;
+ memhandle_t m_hWaveData;
+ int m_StartPos;
+ bool m_bCanBeShared;
+ };
+
+ static bool BufferHandleLessFunc( const BufferEntry_t& lhs, const BufferEntry_t& rhs )
+ {
+ if ( lhs.m_hName != rhs.m_hName )
+ {
+ return lhs.m_hName < rhs.m_hName;
+ }
+
+ if ( lhs.m_StartPos != rhs.m_StartPos )
+ {
+ return lhs.m_StartPos < rhs.m_StartPos;
+ }
+
+ return lhs.m_bCanBeShared < rhs.m_bCanBeShared;
+ }
+
+ CUtlRBTree< BufferEntry_t, BufferHandle_t > m_BufferList;
+
+ // encapsulates (n) buffers for a streamed wave object
+ struct StreamedEntry_t
+ {
+ FileNameHandle_t m_hName;
+ memhandle_t m_hWaveData[STREAM_BUFFER_COUNT];
+ int m_Front; // buffer index, forever incrementing
+ int m_NextStartPos; // predicted offset if mixing linearly
+ int m_DataSize; // length of the data set in bytes
+ int m_DataStart; // file offset where data set starts
+ int m_LoopStart; // offset in data set where loop starts
+ int m_BufferSize; // size of the buffer in bytes
+ int m_numBuffers; // number of buffers (1 or 2) to march through
+ int m_SectorSize; // size of sector on stream device
+ bool m_bSinglePlay; // hint to keep same buffers
+ };
+ CUtlLinkedList< StreamedEntry_t, StreamHandle_t > m_StreamedHandles;
+
+ static bool CacheHandleLessFunc( const CacheEntry_t& lhs, const CacheEntry_t& rhs )
+ {
+ return lhs.name < rhs.name;
+ }
+ CUtlRBTree< CacheEntry_t, int > m_CacheHandles;
+
+ memhandle_t FindOrCreateBuffer( asyncwaveparams_t &params, bool bFind );
+ bool m_bInitialized;
+ bool m_bQueueCacheUnlocks;
+ CUtlVector<memhandle_t> m_unlockQueue;
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CAsyncWavDataCache::CAsyncWavDataCache() :
+ m_CacheHandles( 0, 0, CacheHandleLessFunc ),
+ m_BufferList( 0, 0, BufferHandleLessFunc ),
+ m_bInitialized( false ),
+ m_bQueueCacheUnlocks( false )
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAsyncWavDataCache::Init( unsigned int memSize )
+{
+ if ( m_bInitialized )
+ return true;
+
+ if ( IsX360() )
+ {
+ const char *pGame = engineClient->GetGameDirectory();
+ if ( !Q_stricmp( Q_UnqualifiedFileName( pGame ), "tf" ) )
+ {
+ memSize = TF_XBOX_WAV_MEMORY_CACHE;
+ }
+ else
+ {
+ memSize = DEFAULT_XBOX_WAV_MEMORY_CACHE;
+ }
+ }
+ else
+ {
+ if ( memSize < DEFAULT_WAV_MEMORY_CACHE )
+ {
+ memSize = DEFAULT_WAV_MEMORY_CACHE;
+ }
+ }
+
+#if FORCE_SMALL_MEMORY_CACHE_SIZE
+ memSize = FORCE_SMALL_MEMORY_CACHE_SIZE;
+ Msg( "WARNING CAsyncWavDataCache::Init() forcing small memory cache size: %u\n", memSize );
+#endif
+
+ CCacheClientBaseClass::Init( g_pDataCache, "WaveData", memSize );
+
+ m_bInitialized = true;
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAsyncWavDataCache::Shutdown()
+{
+ if ( !m_bInitialized )
+ {
+ return;
+ }
+
+ Clear();
+
+ CCacheClientBaseClass::Shutdown();
+
+ m_bInitialized = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates initial cache object if it doesn't already exist, starts async loading the actual data
+// in any case.
+// Input : *filename -
+// datasize -
+// startpos -
+// Output : memhandle_t
+//-----------------------------------------------------------------------------
+memhandle_t CAsyncWavDataCache::AsyncLoadCache( char const *filename, int datasize, int startpos, bool bIsPrefetch )
+{
+ VPROF( "CAsyncWavDataCache::AsyncLoadCache" );
+
+ FileNameHandle_t fnh = g_pFileSystem->FindOrAddFileName( filename );
+
+ CacheEntry_t search;
+ search.name = fnh;
+ search.handle = 0;
+
+ // find or create the handle
+ int idx = m_CacheHandles.Find( search );
+ if ( idx == m_CacheHandles.InvalidIndex() )
+ {
+ idx = m_CacheHandles.Insert( search );
+ Assert( idx != m_CacheHandles.InvalidIndex() );
+ }
+
+ CacheEntry_t &entry = m_CacheHandles[idx];
+
+ // Try and pull it into cache
+ CAsyncWaveData *data = CacheGet( entry.handle );
+ if ( !data )
+ {
+ // Try and reload it
+ asyncwaveparams_t params;
+ params.hFilename = fnh;
+ params.datasize = datasize;
+ params.seekpos = startpos;
+ params.bPrefetch = bIsPrefetch;
+ entry.handle = CacheCreate( params );
+ }
+
+ return entry.handle;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Reclaim a buffer. A reclaimed resident buffer is ready for play.
+//-----------------------------------------------------------------------------
+memhandle_t CAsyncWavDataCache::FindOrCreateBuffer( asyncwaveparams_t &params, bool bFind )
+{
+ CAsyncWaveData *pWaveData;
+ BufferEntry_t search;
+ BufferHandle_t hBuffer;
+
+ search.m_hName = params.hFilename;
+ search.m_StartPos = params.seekpos;
+ search.m_bCanBeShared = bFind;
+ search.m_hWaveData = 0;
+
+ if ( bFind )
+ {
+ // look for an existing buffer that matches exactly (same file, offset, and share)
+ int iBuffer = m_BufferList.Find( search );
+ if ( iBuffer != m_BufferList.InvalidIndex() )
+ {
+ // found
+ search.m_hWaveData = m_BufferList[iBuffer].m_hWaveData;
+ if ( snd_async_stream_spew.GetInt() >= 2 )
+ {
+ char tempBuff[MAX_PATH];
+ g_pFileSystem->String( params.hFilename, tempBuff, sizeof( tempBuff ) );
+ Msg( "Found Buffer: %s, offset: %d\n", tempBuff, params.seekpos );
+ }
+ }
+ }
+
+ // each resource buffer stays locked (valid) while in use
+ // a buffering stream is not subject to lru and can rely on it's buffers
+ // a buffering stream may obsolete it's buffers by reducing the lock count, allowing for lru
+ pWaveData = CacheLock( search.m_hWaveData );
+ if ( !pWaveData )
+ {
+ // not in cache, create and lock
+ // not found, create buffer and fill with data
+ search.m_hWaveData = CacheCreate( params, DCAF_LOCK );
+
+ // add the buffer to our managed list
+ hBuffer = m_BufferList.Insert( search );
+ Assert( hBuffer != m_BufferList.InvalidIndex() );
+
+ // store the handle into our managed list
+ // used during a lru discard as a means to keep the list in-sync
+ pWaveData = CacheGet( search.m_hWaveData );
+ pWaveData->m_hBuffer = hBuffer;
+ }
+ else
+ {
+ // still in cache
+ // same as requesting it and having it arrive instantly
+ pWaveData->m_start = pWaveData->m_arrival = (float)Plat_FloatTime();
+ }
+
+ return search.m_hWaveData;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Load data asynchronously via multi-buffers, returns specialized handle
+//-----------------------------------------------------------------------------
+StreamHandle_t CAsyncWavDataCache::OpenStreamedLoad( char const *pFileName, int dataSize, int dataStart, int startPos, int loopPos, int bufferSize, int numBuffers, streamFlags_t flags )
+{
+ VPROF( "CAsyncWavDataCache::OpenStreamedLoad" );
+
+ StreamedEntry_t streamedEntry;
+ StreamHandle_t hStream;
+ asyncwaveparams_t params;
+ int i;
+
+ Assert( numBuffers > 0 && numBuffers <= STREAM_BUFFER_COUNT );
+
+ // queued load mandates one buffer
+ Assert( !( flags & STREAMED_QUEUEDLOAD ) || numBuffers == 1 );
+
+ streamedEntry.m_hName = g_pFileSystem->FindOrAddFileName( pFileName );
+ streamedEntry.m_Front = 0;
+ streamedEntry.m_DataSize = dataSize;
+ streamedEntry.m_DataStart = dataStart;
+ streamedEntry.m_NextStartPos = startPos + numBuffers * bufferSize;
+ streamedEntry.m_LoopStart = loopPos;
+ streamedEntry.m_BufferSize = bufferSize;
+ streamedEntry.m_numBuffers = numBuffers;
+ streamedEntry.m_bSinglePlay = ( flags & STREAMED_SINGLEPLAY ) != 0;
+ streamedEntry.m_SectorSize = ( IsX360() && ( flags & STREAMED_FROMDVD ) ) ? XBOX_DVD_SECTORSIZE : 1;
+
+ // single play streams expect to uniquely own and thus recycle their buffers though the data
+ // single play streams are guaranteed that their buffers are private and cannot be shared
+ // a non-single play stream wants persisting buffers and attempts to reclaim a matching buffer
+ bool bFindBuffer = ( streamedEntry.m_bSinglePlay == false );
+
+ // initial load populates buffers
+ // mixing starts after front buffer viable
+ // buffer rotation occurs after front buffer consumed
+ // there should be no blocking
+ params.hFilename = streamedEntry.m_hName;
+ params.datasize = bufferSize;
+ params.alignment = streamedEntry.m_SectorSize;
+ params.bCanBeQueued = ( flags & STREAMED_QUEUEDLOAD ) != 0;
+ for ( i=0; i<numBuffers; ++i )
+ {
+ params.seekpos = dataStart + startPos + i * bufferSize;
+ streamedEntry.m_hWaveData[i] = FindOrCreateBuffer( params, bFindBuffer );
+ }
+
+ // get a unique handle for each stream request
+ hStream = m_StreamedHandles.AddToTail( streamedEntry );
+ Assert( hStream != m_StreamedHandles.InvalidIndex() );
+
+ return hStream;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Cleanup a streamed load's resources.
+//-----------------------------------------------------------------------------
+void CAsyncWavDataCache::CloseStreamedLoad( StreamHandle_t hStream )
+{
+ VPROF( "CAsyncWavDataCache::CloseStreamedLoad" );
+
+ if ( hStream == INVALID_STREAM_HANDLE )
+ {
+ return;
+ }
+
+ int lockCount;
+ StreamedEntry_t &streamedEntry = m_StreamedHandles[hStream];
+ for ( int i=0; i<streamedEntry.m_numBuffers; ++i )
+ {
+ // multiple streams could be using the same buffer, keeping the lock count nonzero
+ lockCount = GetCacheSection()->GetLockCount( streamedEntry.m_hWaveData[i] );
+ Assert( lockCount >= 1 );
+ if ( lockCount > 0 )
+ {
+ lockCount = CacheUnlock( streamedEntry.m_hWaveData[i] );
+ }
+
+ if ( streamedEntry.m_bSinglePlay )
+ {
+ // a buffering single play stream has no reason to reuse its own buffers and destroys them
+ Assert( lockCount == 0 );
+ CacheRemove( streamedEntry.m_hWaveData[i] );
+ }
+ }
+
+ m_StreamedHandles.Remove( hStream );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *filename -
+// datasize -
+// startpos -
+//-----------------------------------------------------------------------------
+void CAsyncWavDataCache::PrefetchCache( char const *filename, int datasize, int startpos )
+{
+ // Just do an async load, but don't get cache handle
+ AsyncLoadCache( filename, datasize, startpos, true );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *filename -
+// datasize -
+// startpos -
+// *buffer -
+// bufsize -
+// copystartpos -
+// bytestocopy -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAsyncWavDataCache::CopyDataIntoMemory( char const *filename, int datasize, int startpos, void *buffer, int bufsize, int copystartpos, int bytestocopy, bool *pbPostProcessed )
+{
+ VPROF( "CAsyncWavDataCache::CopyDataIntoMemory" );
+
+ bool bret = false;
+
+ // Add to caching system
+ AsyncLoadCache( filename, datasize, startpos );
+
+ FileNameHandle_t fnh = g_pFileSystem->FindOrAddFileName( filename );
+
+ CacheEntry_t search;
+ search.name = fnh;
+ search.handle = 0;
+
+ // Now look it up, it should be in the system
+ int idx = m_CacheHandles.Find( search );
+ if ( idx == m_CacheHandles.InvalidIndex() )
+ {
+ Assert( 0 );
+ return bret;
+ }
+
+ // Now see if the handle has been paged out...
+ return CopyDataIntoMemory( m_CacheHandles[ idx ].handle, filename, datasize, startpos, buffer, bufsize, copystartpos, bytestocopy, pbPostProcessed );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : handle -
+// *filename -
+// datasize -
+// startpos -
+// *buffer -
+// bufsize -
+// copystartpos -
+// bytestocopy -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAsyncWavDataCache::CopyDataIntoMemory( memhandle_t& handle, char const *filename, int datasize, int startpos, void *buffer, int bufsize, int copystartpos, int bytestocopy, bool *pbPostProcessed )
+{
+ VPROF( "CAsyncWavDataCache::CopyDataIntoMemory" );
+
+ *pbPostProcessed = false;
+
+ bool bret = false;
+
+ CAsyncWaveData *data = CacheLock( handle );
+ if ( !data )
+ {
+ FileNameHandle_t fnh = g_pFileSystem->FindOrAddFileName( filename );
+
+ CacheEntry_t search;
+ search.name = fnh;
+ search.handle = 0;
+
+ // Now look it up, it should be in the system
+ int idx = m_CacheHandles.Find( search );
+ if ( idx == m_CacheHandles.InvalidIndex() )
+ {
+ Assert( 0 );
+ return false;
+ }
+
+ // Try and reload it
+ asyncwaveparams_t params;
+ params.hFilename = fnh;
+ params.datasize = datasize;
+ params.seekpos = startpos;
+
+ handle = m_CacheHandles[ idx ].handle = CacheCreate( params );
+ data = CacheLock( handle );
+ if ( !data )
+ {
+ return bret;
+ }
+ }
+
+ // Cache entry exists, but if filesize == 0 then the file itself wasn't on disk...
+ if ( data->m_nDataSize != 0 )
+ {
+ bret = data->BlockingCopyData( buffer, bufsize, copystartpos, bytestocopy );
+ }
+
+ *pbPostProcessed = data->GetPostProcessed();
+
+ // Release lock
+ CacheUnlock( handle );
+ return bret;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Copy from streaming buffers into target memory, never blocks.
+//-----------------------------------------------------------------------------
+int CAsyncWavDataCache::CopyStreamedDataIntoMemory( int hStream, void *pBuffer, int bufferSize, int copyStartPos, int bytesToCopy )
+{
+ VPROF( "CAsyncWavDataCache::CopyStreamedDataIntoMemory" );
+
+ int actualCopied;
+ int count;
+ int i;
+ int which;
+ CAsyncWaveData *pWaveData[STREAM_BUFFER_COUNT];
+ CAsyncWaveData *pFront;
+ asyncwaveparams_t params;
+ int nextStartPos;
+ int bufferPos;
+ bool bEndOfFile;
+ int index;
+ bool bWaiting;
+ bool bCompleted;
+ StreamedEntry_t &streamedEntry = m_StreamedHandles[hStream];
+
+ if ( copyStartPos >= streamedEntry.m_DataStart + streamedEntry.m_DataSize )
+ {
+ // at or past end of file
+ return 0;
+ }
+
+ for ( i=0; i<streamedEntry.m_numBuffers; ++i )
+ {
+ pWaveData[i] = CacheGetNoTouch( streamedEntry.m_hWaveData[i] );
+ Assert( pWaveData[i] );
+ }
+
+ // drive the buffering
+ index = streamedEntry.m_Front;
+ bEndOfFile = 0;
+ actualCopied = 0;
+ bWaiting = false;
+ while ( 1 )
+ {
+ // try to satisfy from the front
+ pFront = pWaveData[index % streamedEntry.m_numBuffers];
+ bufferPos = copyStartPos - pFront->m_async.nOffset;
+
+ // cache atomic async completion signal off to avoid coherency issues
+ bCompleted = pFront->m_bLoaded || pFront->m_bMissing;
+
+ if ( snd_async_stream_spew.GetInt() >= 1 )
+ {
+ // interval is the audio block clock rate, the block must be available within this interval
+ // a faster audio rate or smaller block size implies a smaller interval
+ // latency is the actual block delivery time
+ // latency must not exceed the delivery interval or stariving occurs and audio pops
+ float nowTime = Plat_FloatTime();
+ int interval = (int)(1000.0f*(nowTime-pFront->m_start));
+ int latency;
+ if ( bCompleted && pFront->m_bLoaded )
+ {
+ latency = (int)(1000.0f*(pFront->m_arrival-pFront->m_start));
+ }
+ else
+ {
+ // buffer has not arrived yet
+ latency = -1;
+ }
+ DevMsg( "Stream:%2d interval:%5dms latency:%5dms offset:%d length:%d (%s)\n", hStream, interval, latency, pFront->m_async.nOffset, pFront->m_nReadSize, pFront->GetFileName() );
+ }
+
+ if ( bCompleted && pFront->m_hAsyncControl && ( pFront->m_bLoaded || pFront->m_bMissing) )
+ {
+ g_pFileSystem->AsyncRelease( pFront->m_hAsyncControl );
+ pFront->m_hAsyncControl = NULL;
+ }
+
+ if ( bCompleted && pFront->m_bLoaded )
+ {
+ if ( bufferPos >= 0 && bufferPos < pFront->m_nReadSize )
+ {
+ count = bytesToCopy;
+ if ( bufferPos + bytesToCopy > pFront->m_nReadSize )
+ {
+ // clamp requested to actual available
+ count = pFront->m_nReadSize - bufferPos;
+ }
+ if ( bufferPos + count > bufferSize )
+ {
+ // clamp requested to caller's buffer dimension
+ count = bufferSize - bufferPos;
+ }
+
+ Q_memcpy( pBuffer, (char *)pFront->m_pvData + bufferPos, count );
+
+ // advance past consumed bytes
+ actualCopied += count;
+ copyStartPos += count;
+ bufferPos += count;
+ }
+ }
+ else if ( bCompleted && pFront->m_bMissing )
+ {
+ // notify on any error
+ MaybeReportMissingWav( pFront->GetFileName() );
+ break;
+ }
+ else
+ {
+ // data not available
+ bWaiting = true;
+ break;
+ }
+
+ // cycle past obsolete or consumed buffers
+ if ( bufferPos < 0 || bufferPos >= pFront->m_nReadSize )
+ {
+ // move to next buffer
+ index++;
+ if ( index - streamedEntry.m_Front >= streamedEntry.m_numBuffers )
+ {
+ // out of buffers
+ break;
+ }
+ }
+
+ if ( actualCopied == bytesToCopy )
+ {
+ // satisfied request
+ break;
+ }
+ }
+
+ if ( streamedEntry.m_numBuffers > 1 )
+ {
+ // restart consumed buffers
+ while ( streamedEntry.m_Front < index )
+ {
+ if ( !actualCopied && !bWaiting )
+ {
+ // couldn't return any data because the buffers aren't in the right location
+ // oh no! caller must be skipping
+ // due to latency the next buffer position has to start one full buffer ahead of the caller's desired read location
+ // hopefully only 1 buffer will stutter
+ nextStartPos = copyStartPos - streamedEntry.m_DataStart + streamedEntry.m_BufferSize;
+
+ // advance past, ready for next possible iteration
+ copyStartPos += streamedEntry.m_BufferSize;
+ }
+ else
+ {
+ // get the next forecasted read location
+ nextStartPos = streamedEntry.m_NextStartPos;
+ }
+
+ if ( nextStartPos >= streamedEntry.m_DataSize )
+ {
+ // next buffer is at or past end of file
+ if ( streamedEntry.m_LoopStart >= 0 )
+ {
+ // wrap back around to loop position
+ nextStartPos = streamedEntry.m_LoopStart;
+ }
+ else
+ {
+ // advance past consumed buffer
+ streamedEntry.m_Front++;
+
+ // start no further buffers
+ break;
+ }
+ }
+
+ // still valid data left to read
+ // snap the buffer position to required alignment
+ nextStartPos = streamedEntry.m_SectorSize * (nextStartPos/streamedEntry.m_SectorSize);
+
+ // start loading back buffer at future location
+ params.hFilename = streamedEntry.m_hName;
+ params.seekpos = streamedEntry.m_DataStart + nextStartPos;
+ params.datasize = streamedEntry.m_DataSize - nextStartPos;
+ params.alignment = streamedEntry.m_SectorSize;
+ if ( params.datasize > streamedEntry.m_BufferSize )
+ {
+ // clamp to buffer size
+ params.datasize = streamedEntry.m_BufferSize;
+ }
+
+ // save next start position
+ streamedEntry.m_NextStartPos = nextStartPos + params.datasize;
+
+ which = streamedEntry.m_Front % streamedEntry.m_numBuffers;
+ if ( streamedEntry.m_bSinglePlay )
+ {
+ // a single play wave has no reason to persist its buffers into the lru
+ // reuse buffer and restart until finished
+ pWaveData[which]->StartAsyncLoading( params );
+ }
+ else
+ {
+ // release obsolete buffer to lru management
+ CacheUnlock( streamedEntry.m_hWaveData[which] );
+ // reclaim or create/load the desired buffer
+ streamedEntry.m_hWaveData[which] = FindOrCreateBuffer( params, true );
+ }
+
+ streamedEntry.m_Front++;
+ }
+
+ if ( bWaiting )
+ {
+ // oh no! data needed is not yet available in front buffer
+ // caller requesting data faster than can be provided or caller skipped
+ // can only return what has been copied thus far (could be 0)
+ return actualCopied;
+ }
+ }
+
+ return actualCopied;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the front buffer, optionally block.
+// Intended for user of a single buffer stream.
+//-----------------------------------------------------------------------------
+void *CAsyncWavDataCache::GetStreamedDataPointer( StreamHandle_t hStream, bool bSync )
+{
+ void *pData;
+ CAsyncWaveData *pFront;
+ int index;
+ StreamedEntry_t &streamedEntry = m_StreamedHandles[hStream];
+
+ index = streamedEntry.m_Front % streamedEntry.m_numBuffers;
+ pFront = CacheGetNoTouch( streamedEntry.m_hWaveData[index] );
+ Assert( pFront );
+ if ( !pFront )
+ {
+ // shouldn't happen
+ return NULL;
+ }
+
+ if ( !pFront->m_bMissing && pFront->m_bLoaded )
+ {
+ return pFront->m_pvData;
+ }
+
+ if ( bSync && pFront->BlockingGetDataPointer( &pData ) )
+ {
+ return pData;
+ }
+
+ return NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: The front buffer must be valid
+//-----------------------------------------------------------------------------
+bool CAsyncWavDataCache::IsStreamedDataReady( int hStream )
+{
+ VPROF( "CAsyncWavDataCache::IsStreamedDataReady" );
+
+ if ( hStream == INVALID_STREAM_HANDLE )
+ {
+ return false;
+ }
+
+ StreamedEntry_t &streamedEntry = m_StreamedHandles[hStream];
+
+ if ( streamedEntry.m_Front )
+ {
+ // already streaming, the buffers better be arriving as expected
+ return true;
+ }
+
+ // only the first front buffer must be present
+ CAsyncWaveData *pFront = CacheGetNoTouch( streamedEntry.m_hWaveData[0] );
+ Assert( pFront );
+ if ( !pFront )
+ {
+ // shouldn't happen
+ // let the caller think data is ready, so stream can shutdown
+ return true;
+ }
+
+ // regardless of any errors
+ // errors handled during data fetch
+ return pFront->m_bLoaded || pFront->m_bMissing;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Dequeue the buffer entry (backdoor for list management)
+//-----------------------------------------------------------------------------
+void CAsyncWavDataCache::MarkBufferDiscarded( BufferHandle_t hBuffer )
+{
+ m_BufferList.RemoveAt( hBuffer );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : handle -
+// proc -
+//-----------------------------------------------------------------------------
+void CAsyncWavDataCache::SetPostProcessed( memhandle_t handle, bool proc )
+{
+ CAsyncWaveData *data = CacheGet( handle );
+ if ( data )
+ {
+ data->SetPostProcessed( proc );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : handle -
+//-----------------------------------------------------------------------------
+void CAsyncWavDataCache::Unload( memhandle_t handle )
+{
+ // Don't actually unload, just mark it as stale
+ if ( GetCacheSection() )
+ {
+ GetCacheSection()->Age( handle );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : handle -
+// *filename -
+// datasize -
+// startpos -
+// **pData -
+// copystartpos -
+// *pbPostProcessed -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAsyncWavDataCache::GetDataPointer( memhandle_t& handle, char const *filename, int datasize, int startpos, void **pData, int copystartpos, bool *pbPostProcessed )
+{
+ VPROF( "CAsyncWavDataCache::GetDataPointer" );
+
+ Assert( pbPostProcessed );
+ Assert( pData );
+
+ *pbPostProcessed = false;
+
+ bool bret = false;
+ *pData = NULL;
+
+ CAsyncWaveData *data = CacheLock( handle );
+ if ( !data )
+ {
+ FileNameHandle_t fnh = g_pFileSystem->FindOrAddFileName( filename );
+
+ CacheEntry_t search;
+ search.name = fnh;
+ search.handle = 0;
+
+ int idx = m_CacheHandles.Find( search );
+ if ( idx == m_CacheHandles.InvalidIndex() )
+ {
+ Assert( 0 );
+ return bret;
+ }
+
+ // Try and reload it
+ asyncwaveparams_t params;
+ params.hFilename = fnh;
+ params.datasize = datasize;
+ params.seekpos = startpos;
+
+ handle = m_CacheHandles[ idx ].handle = CacheCreate( params );
+ data = CacheLock( handle );
+ if ( !data )
+ {
+ return bret;
+ }
+ }
+
+ // Cache entry exists, but if filesize == 0 then the file itself wasn't on disk...
+ if ( data->m_nDataSize != 0 )
+ {
+ if ( datasize != data->m_nDataSize )
+ {
+ // We've had issues where we are called with datasize larger than what we read on disk.
+ // Ie: datasize is 277,180, data->m_nDataSize is 263,168
+ // This can happen due to a corrupted audio cache, but it's more likely that somehow
+ // we wound up reading the cache data from one language and the file from another.
+ DevMsg( "Cached datasize != sound datasize %d - %d.\n", datasize, data->m_nDataSize );
+#ifdef STAGING_ONLY
+ // Adding a STAGING_ONLY debugger break to try and help track this down. Hopefully we'll
+ // get this crash internally with full debug information instead of just minidump files.
+ DebuggerBreak();
+#endif
+ }
+ else if ( copystartpos < data->m_nDataSize )
+ {
+ if ( data->BlockingGetDataPointer( pData ) )
+ {
+ *pData = (char *)*pData + copystartpos;
+ bret = true;
+ }
+ }
+ }
+
+ *pbPostProcessed = data->GetPostProcessed();
+
+ // Release lock at the end of mixing
+ QueueUnlock( handle );
+ return bret;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : handle -
+// *filename -
+// datasize -
+// startpos -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAsyncWavDataCache::IsDataLoadCompleted( memhandle_t handle, bool *pIsValid )
+{
+ VPROF( "CAsyncWavDataCache::IsDataLoadCompleted" );
+
+ CAsyncWaveData *data = CacheGet( handle );
+ if ( !data )
+ {
+ *pIsValid = false;
+ return false;
+ }
+ *pIsValid = true;
+ // bump the priority
+ data->SetAsyncPriority( 1 );
+
+ return data->m_bLoaded;
+}
+
+
+void CAsyncWavDataCache::RestartDataLoad( memhandle_t *pHandle, const char *pFilename, int dataSize, int startpos )
+{
+ CAsyncWaveData *data = CacheGet( *pHandle );
+ if ( !data )
+ {
+ *pHandle = AsyncLoadCache( pFilename, dataSize, startpos );
+ }
+}
+
+bool CAsyncWavDataCache::IsDataLoadInProgress( memhandle_t handle )
+{
+ CAsyncWaveData *data = CacheGet( handle );
+ if ( data )
+ {
+ return data->IsCurrentlyLoading();
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAsyncWavDataCache::Flush()
+{
+ GetCacheSection()->Flush();
+ SpewMemoryUsage( 0 );
+}
+
+void CAsyncWavDataCache::QueueUnlock( const memhandle_t &handle )
+{
+ // not queuing right now, just unlock
+ if ( !m_bQueueCacheUnlocks )
+ {
+ CacheUnlock( handle );
+ return;
+ }
+ // queue to unlock at the end of mixing
+ m_unlockQueue.AddToTail( handle );
+}
+
+void CAsyncWavDataCache::OnMixBegin()
+{
+ Assert( !m_bQueueCacheUnlocks );
+ m_bQueueCacheUnlocks = true;
+ Assert( m_unlockQueue.Count() == 0 );
+}
+
+void CAsyncWavDataCache::OnMixEnd()
+{
+ m_bQueueCacheUnlocks = false;
+ // flush the unlock queue
+ for ( int i = 0; i < m_unlockQueue.Count(); i++ )
+ {
+ CacheUnlock( m_unlockQueue[i] );
+ }
+ m_unlockQueue.RemoveAll();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CAsyncWavDataCache::GetItemName( DataCacheClientID_t clientId, const void *pItem, char *pDest, unsigned nMaxLen )
+{
+ CAsyncWaveData *pWaveData = (CAsyncWaveData *)pItem;
+ Q_strncpy( pDest, pWaveData->GetFileName(), nMaxLen );
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Spew a cache summary to the console
+//-----------------------------------------------------------------------------
+void CAsyncWavDataCache::SpewMemoryUsage( int level )
+{
+ DataCacheStatus_t status;
+ DataCacheLimits_t limits;
+ GetCacheSection()->GetStatus( &status, &limits );
+ int bytesUsed = status.nBytes;
+ int bytesTotal = limits.nMaxBytes;
+
+ if ( IsPC() )
+ {
+ float percent = 100.0f * (float)bytesUsed / (float)bytesTotal;
+
+ Msg( "CAsyncWavDataCache: %i .wavs total %s, %.2f %% of capacity\n", m_CacheHandles.Count(), Q_pretifymem( bytesUsed, 2 ), percent );
+
+ if ( level >= 1 )
+ {
+ for ( int i = m_CacheHandles.FirstInorder(); m_CacheHandles.IsValidIndex(i); i = m_CacheHandles.NextInorder(i) )
+ {
+ char name[MAX_PATH];
+ if ( !g_pFileSystem->String( m_CacheHandles[ i ].name, name, sizeof( name ) ) )
+ {
+ Assert( 0 );
+ continue;
+ }
+ memhandle_t &handle = m_CacheHandles[ i ].handle;
+ CAsyncWaveData *data = CacheGetNoTouch( handle );
+ if ( data )
+ {
+ Msg( "\t%16.16s : %s\n", Q_pretifymem(data->Size()),name);
+ }
+ else
+ {
+ Msg( "\t%16.16s : %s\n", "not resident",name);
+ }
+ }
+ Msg( "CAsyncWavDataCache: %i .wavs total %s, %.2f %% of capacity\n", m_CacheHandles.Count(), Q_pretifymem( bytesUsed, 2 ), percent );
+ }
+ }
+
+ if ( IsX360() )
+ {
+ CAsyncWaveData *pData;
+ BufferEntry_t *pBuffer;
+ BufferHandle_t h;
+ float percent;
+ int lockCount;
+
+ if ( bytesTotal <= 0 )
+ {
+ // unbounded, indeterminate
+ percent = 0;
+ bytesTotal = 0;
+ }
+ else
+ {
+ percent = 100.0f*(float)bytesUsed/(float)bytesTotal;
+ }
+
+ if ( level >= 1 )
+ {
+ // detail buffers
+ ConMsg( "Streaming Buffer List:\n" );
+ for ( h = m_BufferList.FirstInorder(); h != m_BufferList.InvalidIndex(); h = m_BufferList.NextInorder( h ) )
+ {
+ pBuffer = &m_BufferList[h];
+ pData = CacheGetNoTouch( pBuffer->m_hWaveData );
+ lockCount = GetCacheSection()->GetLockCount( pBuffer->m_hWaveData );
+
+ CacheLockMutex();
+ if ( pData )
+ {
+ ConMsg( "Start:%7d Length:%7d Lock:%3d %s\n", pData->m_async.nOffset, pData->m_nDataSize, lockCount, pData->GetFileName() );
+ }
+ CacheUnlockMutex();
+ }
+ }
+
+ ConMsg( "CAsyncWavDataCache: %.2f MB used of %.2f MB, %.2f%% of capacity", (float)bytesUsed/(1024.0f*1024.0f), (float)bytesTotal/(1024.0f*1024.0f), percent );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAsyncWavDataCache::Clear()
+{
+ for ( int i = m_CacheHandles.FirstInorder(); m_CacheHandles.IsValidIndex(i); i = m_CacheHandles.NextInorder(i) )
+ {
+ CacheEntry_t& dat = m_CacheHandles[i];
+ CacheRemove( dat.handle );
+ }
+ m_CacheHandles.RemoveAll();
+
+ FOR_EACH_LL( m_StreamedHandles, i )
+ {
+ StreamedEntry_t &dat = m_StreamedHandles[i];
+ for ( int j=0; j<dat.m_numBuffers; ++j )
+ {
+ GetCacheSection()->BreakLock( dat.m_hWaveData[j] );
+ CacheRemove( dat.m_hWaveData[j] );
+ }
+ }
+ m_StreamedHandles.RemoveAll();
+ m_BufferList.RemoveAll();
+}
+
+
+static CAsyncWavDataCache g_AsyncWaveDataCache;
+IAsyncWavDataCache *wavedatacache = &g_AsyncWaveDataCache;
+
+CON_COMMAND( snd_async_flush, "Flush all unlocked async audio data" )
+{
+ g_AsyncWaveDataCache.Flush();
+}
+
+CON_COMMAND( snd_async_showmem, "Show async memory stats" )
+{
+ g_AsyncWaveDataCache.SpewMemoryUsage( 1 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pFileName -
+// dataOffset -
+// dataSize -
+//-----------------------------------------------------------------------------
+void PrefetchDataStream( const char *pFileName, int dataOffset, int dataSize )
+{
+ if ( IsX360() )
+ {
+ // Xbox streaming buffer implementation does not support this "hinting"
+ return;
+ }
+
+ wavedatacache->PrefetchCache( pFileName, dataSize, dataOffset );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: This is an instance of a stream.
+// This contains the file handle and streaming buffer
+// The mixer doesn't know the file is streaming. The IWaveData
+// abstracts the data access. The mixer abstracts data encoding/format
+//-----------------------------------------------------------------------------
+class CWaveDataStreamAsync : public IWaveData
+{
+public:
+ CWaveDataStreamAsync( CAudioSource &source, IWaveStreamSource *pStreamSource, const char *pFileName, int fileStart, int fileSize, CSfxTable *sfx, int startOffset );
+ ~CWaveDataStreamAsync( void );
+
+ // return the source pointer (mixer needs this to determine some things like sampling rate)
+ CAudioSource &Source( void ) { return m_source; }
+
+ // Read data from the source - this is the primary function of a IWaveData subclass
+ // Get the data from the buffer (or reload from disk)
+ virtual int ReadSourceData( void **pData, int sampleIndex, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] );
+ bool IsValid() { return m_bValid; }
+ virtual bool IsReadyToMix();
+
+private:
+ CWaveDataStreamAsync( const CWaveDataStreamAsync & );
+
+ //-----------------------------------------------------------------------------
+ // Purpose:
+ // Output : byte
+ //-----------------------------------------------------------------------------
+ inline byte *GetCachedDataPointer()
+ {
+ VPROF( "CWaveDataStreamAsync::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();
+ }
+
+ char const *GetFileName();
+ CAudioSource &m_source; // wave source
+ IWaveStreamSource *m_pStreamSource; // streaming
+ int m_sampleSize; // size of a sample in bytes
+ int m_waveSize; // total number of samples in the file
+
+ int m_bufferSize; // size of buffer in samples
+ char *m_buffer;
+ int m_sampleIndex;
+ int m_bufferCount;
+ int m_dataStart;
+ int m_dataSize;
+
+ memhandle_t m_hCache;
+ StreamHandle_t m_hStream;
+ FileNameHandle_t m_hFileName;
+
+ bool m_bValid;
+ CAudioSourceCachedInfoHandle_t m_AudioCacheHandle;
+ int m_nCachedDataSize;
+ CSfxTable *m_pSfx;
+};
+
+CWaveDataStreamAsync::CWaveDataStreamAsync
+ (
+ CAudioSource &source,
+ IWaveStreamSource *pStreamSource,
+ const char *pFileName,
+ int fileStart,
+ int fileSize,
+ CSfxTable *sfx,
+ int startOffset
+ ) :
+ m_source( source ),
+ m_dataStart( fileStart ),
+ m_dataSize( fileSize ),
+ m_pStreamSource( pStreamSource ),
+ m_bValid( false ),
+ m_hCache( 0 ),
+ m_hStream( INVALID_STREAM_HANDLE ),
+ m_hFileName( 0 ),
+ m_pSfx( sfx )
+{
+ m_hFileName = g_pFileSystem->FindOrAddFileName( pFileName );
+
+ // nothing in the buffer yet
+ m_sampleIndex = 0;
+ m_bufferCount = 0;
+
+ if ( IsPC() )
+ {
+ m_buffer = new char[SINGLE_BUFFER_SIZE];
+ Q_memset( m_buffer, 0, SINGLE_BUFFER_SIZE );
+ }
+
+ m_nCachedDataSize = 0;
+
+ if ( m_dataSize <= 0 )
+ {
+ DevMsg(1, "Can't find streaming wav file: sound\\%s\n", GetFileName() );
+ return;
+ }
+
+ if ( IsPC() )
+ {
+ m_hCache = wavedatacache->AsyncLoadCache( GetFileName(), m_dataSize, m_dataStart );
+
+ // size of a sample
+ m_sampleSize = source.SampleSize();
+ // size in samples of the buffer
+ m_bufferSize = SINGLE_BUFFER_SIZE / m_sampleSize;
+ // size in samples (not bytes) of the wave itself
+ m_waveSize = fileSize / m_sampleSize;
+
+ m_AudioCacheHandle.Get( CAudioSource::AUDIO_SOURCE_WAV, m_pSfx->IsPrecachedSound(), m_pSfx, &m_nCachedDataSize );
+ }
+
+ if ( IsX360() )
+ {
+ // size of a sample
+ m_sampleSize = source.SampleSize();
+ // size in samples (not bytes) of the wave itself
+ m_waveSize = fileSize / m_sampleSize;
+
+ streamFlags_t flags = STREAMED_FROMDVD;
+
+ if ( !Q_strnicmp( pFileName, "music", 5 ) && ( pFileName[5] == '\\' || pFileName[5] == '/') )
+ {
+ // music discards and cycles its buffers
+ flags |= STREAMED_SINGLEPLAY;
+ }
+ else if ( !Q_strnicmp( pFileName, "vo", 2 ) && ( pFileName[2] == '\\' || pFileName[2] == '/' ) && !source.IsSentenceWord() )
+ {
+ // vo discards and cycles its buffers, except for sentence sources, which do recur
+ flags |= STREAMED_SINGLEPLAY;
+ }
+
+ int bufferSize;
+ if ( source.Format() == WAVE_FORMAT_XMA )
+ {
+ // each xma block has its own compression rate
+ // the buffer must be large enough to cover worst case delivery i/o latency
+ // the xma mixer expects quantum xma blocks
+ COMPILE_TIME_ASSERT( ( STREAM_BUFFER_DATASIZE % XMA_BLOCK_SIZE ) == 0 );
+ bufferSize = STREAM_BUFFER_DATASIZE;
+ }
+ else
+ {
+ // calculate a worst case buffer size based on rate
+ bufferSize = STREAM_BUFFER_TIME*source.SampleRate()*m_sampleSize;
+ if ( source.Format() == WAVE_FORMAT_ADPCM )
+ {
+ // consider adpcm as 4 bit samples
+ bufferSize /= 2;
+ }
+
+ if ( source.IsLooped() )
+ {
+ // lighten the streaming load for looping samples
+ // doubling the buffer halves the buffer search/load requests
+ bufferSize *= 2;
+ }
+ }
+
+ // streaming buffers obey alignments
+ bufferSize = AlignValue( bufferSize, XBOX_DVD_SECTORSIZE );
+
+ // use double buffering
+ int numBuffers = 2;
+
+ if ( m_dataSize <= STREAM_BUFFER_DATASIZE || m_dataSize <= numBuffers*bufferSize )
+ {
+ // no gain for buffering a small file or multiple buffering
+ // match the expected transfer with a single buffer
+ bufferSize = m_dataSize;
+ numBuffers = 1;
+ }
+
+ // size in samples of the transfer buffer
+ m_bufferSize = bufferSize / m_sampleSize;
+
+ // allocate a transfer buffer
+ // matches the size of the streaming buffer exactly
+ // ensures that buffers can be filled and then consumed/requeued at the same time
+ m_buffer = new char[bufferSize];
+
+ int loopStart;
+ if ( source.IsLooped() )
+ {
+ int loopBlock;
+ loopStart = m_pStreamSource->GetLoopingInfo( &loopBlock, NULL, NULL ) * m_sampleSize;
+ if ( source.Format() == WAVE_FORMAT_XMA )
+ {
+ // xma works in blocks, mixer handles inter-block accurate loop positioning
+ // block streaming will cycle from the block where the loop occurs
+ loopStart = loopBlock * XMA_BLOCK_SIZE;
+ }
+ }
+ else
+ {
+ // sample not looped
+ loopStart = -1;
+ }
+
+ // load the file piecewise through a buffering implementation
+ m_hStream = wavedatacache->OpenStreamedLoad( pFileName, m_dataSize, m_dataStart, startOffset, loopStart, bufferSize, numBuffers, flags );
+ }
+
+ m_bValid = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CWaveDataStreamAsync::~CWaveDataStreamAsync( void )
+{
+ if ( IsPC() && m_source.IsPlayOnce() && m_source.CanDelete() )
+ {
+ m_source.SetPlayOnce( false ); // in case it gets used again
+ wavedatacache->Unload( m_hCache );
+ }
+
+ if ( IsX360() )
+ {
+ wavedatacache->CloseStreamedLoad( m_hStream );
+ }
+
+ delete [] m_buffer;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : char const
+//-----------------------------------------------------------------------------
+char const *CWaveDataStreamAsync::GetFileName()
+{
+ static char fn[MAX_PATH];
+
+ if ( m_hFileName )
+ {
+ if ( g_pFileSystem->String( m_hFileName, fn, sizeof( fn ) ) )
+ {
+ return fn;
+ }
+ }
+
+ Assert( 0 );
+ return "";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CWaveDataStreamAsync::IsReadyToMix()
+{
+ if ( IsPC() )
+ {
+ // If not async loaded, start mixing right away
+ if ( !m_source.IsAsyncLoad() && !snd_async_fullyasync.GetBool() )
+ {
+ return true;
+ }
+
+ bool bCacheValid;
+ bool bLoaded = wavedatacache->IsDataLoadCompleted( m_hCache, &bCacheValid );
+ if ( !bCacheValid )
+ {
+ wavedatacache->RestartDataLoad( &m_hCache, GetFileName(), m_dataSize, m_dataStart );
+ }
+ return bLoaded;
+ }
+
+ if ( IsX360() )
+ {
+ return wavedatacache->IsStreamedDataReady( m_hStream );
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Read data from the source - this is the primary function of a IWaveData subclass
+// Get the data from the buffer (or reload from disk)
+// Input : **pData -
+// sampleIndex -
+// sampleCount -
+// copyBuf[AUDIOSOURCE_COPYBUF_SIZE] -
+// Output : int
+//-----------------------------------------------------------------------------
+int CWaveDataStreamAsync::ReadSourceData( void **pData, int sampleIndex, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
+{
+ // Current file position
+ int seekpos = m_dataStart + m_sampleIndex * m_sampleSize;
+
+ // wrap position if looping
+ if ( m_source.IsLooped() )
+ {
+ sampleIndex = m_pStreamSource->UpdateLoopingSamplePosition( sampleIndex );
+ if ( sampleIndex < m_sampleIndex )
+ {
+ // looped back, buffer has no samples yet
+ m_sampleIndex = sampleIndex;
+ m_bufferCount = 0;
+
+ // update file position
+ seekpos = m_dataStart + sampleIndex * m_sampleSize;
+ }
+ }
+
+ // UNDONE: This is an error!!
+ // The mixer playing back the stream tried to go backwards!?!?!
+ // BUGBUG: Just play the beginning of the buffer until we get to a valid linear position
+ if ( sampleIndex < m_sampleIndex )
+ sampleIndex = m_sampleIndex;
+
+ // calc sample position relative to the current buffer
+ // m_sampleIndex is the sample position of the first byte of the buffer
+ sampleIndex -= m_sampleIndex;
+
+ // out of range? refresh buffer
+ if ( sampleIndex >= m_bufferCount )
+ {
+ // advance one buffer (the file is positioned here)
+ m_sampleIndex += m_bufferCount;
+ // next sample to load
+ sampleIndex -= m_bufferCount;
+
+ // if the remainder is greated than one buffer size, seek over it. Otherwise, read the next chunk
+ // and leave the remainder as an offset.
+
+ // number of buffers to "skip" (as in the case where we are starting a streaming sound not at the beginning)
+ int skips = sampleIndex / m_bufferSize;
+
+ // If we are skipping over a buffer, do it with a seek instead of a read.
+ if ( skips )
+ {
+ // skip directly to next position
+ m_sampleIndex += sampleIndex;
+ sampleIndex = 0;
+ }
+
+ // move the file to the new position
+ seekpos = m_dataStart + (m_sampleIndex * m_sampleSize);
+
+ // This is the maximum number of samples we could read from the file
+ m_bufferCount = m_waveSize - m_sampleIndex;
+
+ // past the end of the file? stop the wave.
+ if ( m_bufferCount <= 0 )
+ return 0;
+
+ // clamp available samples to buffer size
+ if ( m_bufferCount > m_bufferSize )
+ m_bufferCount = m_bufferSize;
+
+ if ( IsPC() )
+ {
+ // See if we can load in the intial data right out of the cached data lump instead.
+ int cacheddatastartpos = ( seekpos - m_dataStart );
+
+ // FastGet doesn't call into IsPrecachedSound if the handle appears valid...
+ CAudioSourceCachedInfo *info = m_AudioCacheHandle.FastGet();
+ if ( !info )
+ {
+ // Full recache
+ info = m_AudioCacheHandle.Get( CAudioSource::AUDIO_SOURCE_WAV, m_pSfx->IsPrecachedSound(), m_pSfx, &m_nCachedDataSize );
+ }
+
+ bool startupCacheUsed = false;
+
+ if ( info &&
+ ( m_nCachedDataSize > 0 ) &&
+ ( cacheddatastartpos < m_nCachedDataSize ) )
+ {
+ // Get a ptr to the cached data
+ const byte *cacheddata = info->CachedData();
+ if ( cacheddata )
+ {
+ // See how many samples of cached data are available (cacheddatastartpos is zero on the first read)
+ int availSamples = ( m_nCachedDataSize - cacheddatastartpos ) / m_sampleSize;
+
+ // Clamp to size of our internal buffer
+ if ( availSamples > m_bufferSize )
+ {
+ availSamples = m_bufferSize;
+ }
+
+ // Mark how many we are returning
+ m_bufferCount = availSamples;
+ // Copy raw sample data directly out of cache
+ Q_memcpy( m_buffer, ( char * )cacheddata + cacheddatastartpos, availSamples * m_sampleSize );
+
+ startupCacheUsed = true;
+ }
+ }
+
+ // Not in startup cache, grab data from async cache loader (will block if data hasn't arrived yet)
+ if ( !startupCacheUsed )
+ {
+ bool postprocessed = false;
+
+ // read in the max bufferable, available samples
+ if ( !wavedatacache->CopyDataIntoMemory(
+ m_hCache,
+ GetFileName(),
+ m_dataSize,
+ m_dataStart,
+ m_buffer,
+ sizeof( m_buffer ),
+ seekpos,
+ m_bufferCount * m_sampleSize,
+ &postprocessed ) )
+ {
+ return 0;
+ }
+
+ // do any conversion the source needs (mixer will decode/decompress)
+ if ( !postprocessed )
+ {
+ // Note that we don't set the postprocessed flag on the underlying data, since for streaming we're copying the
+ // original data into this buffer instead.
+ m_pStreamSource->UpdateSamples( m_buffer, m_bufferCount );
+ }
+ }
+ }
+
+ if ( IsX360() )
+ {
+ if ( m_hStream != INVALID_STREAM_HANDLE )
+ {
+ // request available data, may get less
+ // drives the buffering
+ m_bufferCount = wavedatacache->CopyStreamedDataIntoMemory(
+ m_hStream,
+ m_buffer,
+ m_bufferSize * m_sampleSize,
+ seekpos,
+ m_bufferCount * m_sampleSize );
+ // convert to number of samples in the buffer
+ m_bufferCount /= m_sampleSize;
+ }
+ else
+ {
+ return 0;
+ }
+
+ // do any conversion now the source needs (mixer will decode/decompress) on this buffer
+ m_pStreamSource->UpdateSamples( m_buffer, m_bufferCount );
+ }
+ }
+
+ // If we have some samples in the buffer that are within range of the request
+ // Use unsigned comparisons so that if sampleIndex is somehow negative that
+ // will be treated as out of range.
+ if ( (unsigned)sampleIndex < (unsigned)m_bufferCount )
+ {
+ // Get the desired starting sample
+ *pData = (void *)&m_buffer[sampleIndex * m_sampleSize];
+
+ // max available
+ int available = m_bufferCount - sampleIndex;
+ // clamp available to max requested
+ if ( available > sampleCount )
+ available = sampleCount;
+
+ return available;
+ }
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Iterator for wave data (this is to abstract streaming/buffering)
+//-----------------------------------------------------------------------------
+class CWaveDataMemoryAsync : public IWaveData
+{
+public:
+ CWaveDataMemoryAsync( CAudioSource &source );
+ ~CWaveDataMemoryAsync( void ) {}
+ CAudioSource &Source( void ) { return m_source; }
+
+ virtual int ReadSourceData( void **pData, int sampleIndex, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] );
+ virtual bool IsReadyToMix();
+
+private:
+ CAudioSource &m_source; // pointer to source
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &source -
+//-----------------------------------------------------------------------------
+CWaveDataMemoryAsync::CWaveDataMemoryAsync( CAudioSource &source ) :
+ m_source(source)
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : **pData -
+// sampleIndex -
+// sampleCount -
+// copyBuf[AUDIOSOURCE_COPYBUF_SIZE] -
+// Output : int
+//-----------------------------------------------------------------------------
+int CWaveDataMemoryAsync::ReadSourceData( void **pData, int sampleIndex, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
+{
+ return m_source.GetOutputData( pData, sampleIndex, sampleCount, copyBuf );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CWaveDataMemoryAsync::IsReadyToMix()
+{
+ if ( !m_source.IsAsyncLoad() && !snd_async_fullyasync.GetBool() )
+ {
+ // Wait until we're pending at least
+ if ( m_source.GetCacheStatus() == CAudioSource::AUDIO_NOT_LOADED )
+ {
+ return false;
+ }
+ return true;
+ }
+
+ if ( m_source.IsCached() )
+ {
+ return true;
+ }
+
+ if ( IsPC() )
+ {
+ // Msg( "Waiting for data '%s'\n", m_source.GetFileName() );
+ m_source.CacheLoad();
+ }
+
+ if ( IsX360() )
+ {
+ // expected to be resident and valid, otherwise being called prior to load
+ Assert( 0 );
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &source -
+// *pStreamSource -
+// &io -
+// *pFileName -
+// dataOffset -
+// dataSize -
+// Output : IWaveData
+//-----------------------------------------------------------------------------
+IWaveData *CreateWaveDataStream( CAudioSource &source, IWaveStreamSource *pStreamSource, const char *pFileName, int dataStart, int dataSize, CSfxTable *pSfx, int startOffset )
+{
+ CWaveDataStreamAsync *pStream = new CWaveDataStreamAsync( source, pStreamSource, pFileName, dataStart, dataSize, pSfx, startOffset );
+ if ( !pStream || !pStream->IsValid() )
+ {
+ delete pStream;
+ pStream = NULL;
+ }
+ return pStream;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &source -
+// Output : IWaveData
+//-----------------------------------------------------------------------------
+IWaveData *CreateWaveDataMemory( CAudioSource &source )
+{
+ CWaveDataMemoryAsync *mem = new CWaveDataMemoryAsync( source );
+ return mem;
+}
diff --git a/engine/audio/private/snd_wave_data.h b/engine/audio/private/snd_wave_data.h
new file mode 100644
index 0000000..54a8285
--- /dev/null
+++ b/engine/audio/private/snd_wave_data.h
@@ -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
diff --git a/engine/audio/private/snd_wave_mixer.cpp b/engine/audio/private/snd_wave_mixer.cpp
new file mode 100644
index 0000000..484b431
--- /dev/null
+++ b/engine/audio/private/snd_wave_mixer.cpp
@@ -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;
+}
+
diff --git a/engine/audio/private/snd_wave_mixer.h b/engine/audio/private/snd_wave_mixer.h
new file mode 100644
index 0000000..595fc7b
--- /dev/null
+++ b/engine/audio/private/snd_wave_mixer.h
@@ -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
diff --git a/engine/audio/private/snd_wave_mixer_adpcm.cpp b/engine/audio/private/snd_wave_mixer_adpcm.cpp
new file mode 100644
index 0000000..ffa71f6
--- /dev/null
+++ b/engine/audio/private/snd_wave_mixer_adpcm.cpp
@@ -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 );
+}
diff --git a/engine/audio/private/snd_wave_mixer_adpcm.h b/engine/audio/private/snd_wave_mixer_adpcm.h
new file mode 100644
index 0000000..298fe66
--- /dev/null
+++ b/engine/audio/private/snd_wave_mixer_adpcm.h
@@ -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
diff --git a/engine/audio/private/snd_wave_mixer_mp3.cpp b/engine/audio/private/snd_wave_mixer_mp3.cpp
new file mode 100644
index 0000000..8eaa16f
--- /dev/null
+++ b/engine/audio/private/snd_wave_mixer_mp3.cpp
@@ -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
diff --git a/engine/audio/private/snd_wave_mixer_mp3.h b/engine/audio/private/snd_wave_mixer_mp3.h
new file mode 100644
index 0000000..1138c25
--- /dev/null
+++ b/engine/audio/private/snd_wave_mixer_mp3.h
@@ -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
diff --git a/engine/audio/private/snd_wave_mixer_private.h b/engine/audio/private/snd_wave_mixer_private.h
new file mode 100644
index 0000000..3aae40c
--- /dev/null
+++ b/engine/audio/private/snd_wave_mixer_private.h
@@ -0,0 +1,74 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $Workfile: $
+// $Date: $
+//
+//-----------------------------------------------------------------------------
+// $Log: $
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef SND_WAVE_MIXER_PRIVATE_H
+#define SND_WAVE_MIXER_PRIVATE_H
+#pragma once
+
+#include "snd_audio_source.h"
+#include "snd_wave_mixer.h"
+#include "sound_private.h"
+#include "snd_wave_source.h"
+
+class IWaveData;
+
+abstract_class CAudioMixerWave : public CAudioMixer
+{
+public:
+ CAudioMixerWave( IWaveData *data );
+ virtual ~CAudioMixerWave( void );
+
+ int MixDataToDevice( IAudioDevice *pDevice, channel_t *pChannel, int sampleCount, int outputRate, int outputOffset );
+ int SkipSamples( channel_t *pChannel, int sampleCount, int outputRate, int outputOffset );
+ bool ShouldContinueMixing( void );
+
+ virtual void Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress ) = 0;
+ virtual int GetOutputData( void **pData, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] );
+
+ virtual CAudioSource* GetSource( void );
+ virtual int GetSamplePosition( void );
+ virtual float ModifyPitch( float pitch );
+ virtual float GetVolumeScale( void );
+
+ // Move the current position to newPosition
+ virtual void SetSampleStart( int newPosition );
+
+ // End playback at newEndPosition
+ virtual void SetSampleEnd( int newEndPosition );
+
+ virtual void SetStartupDelaySamples( int delaySamples );
+
+ // private helper routines
+
+ char * LoadMixBuffer( channel_t *pChannel, int sample_load_request, int *psamples_loaded, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] );
+ int MixDataToDevice_( IAudioDevice *pDevice, channel_t *pChannel, int sampleCount, int outputRate, int outputOffset, bool bSkipAllSamples );
+ int GetSampleLoadRequest( double rate, int sampleCount, bool bInterpolated_pitch );
+
+ virtual bool IsReadyToMix();
+ virtual int GetPositionForSave() { return GetSamplePosition(); }
+ virtual void SetPositionFromSaved( int savedPosition ) { SetSampleStart(savedPosition); }
+
+protected:
+ double m_fsample_index; // index of next sample to output
+ int m_sample_max_loaded; // count of total samples loaded - ie: the index of
+ // the next sample to be loaded.
+ int m_sample_loaded_index; // index of last sample loaded
+
+ IWaveData *m_pData;
+ double m_forcedEndSample;
+ bool m_finished;
+ int m_delaySamples;
+};
+
+
+#endif // SND_WAVE_MIXER_PRIVATE_H
diff --git a/engine/audio/private/snd_wave_mixer_xma.cpp b/engine/audio/private/snd_wave_mixer_xma.cpp
new file mode 100644
index 0000000..13f183c
--- /dev/null
+++ b/engine/audio/private/snd_wave_mixer_xma.cpp
@@ -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 );
+}
diff --git a/engine/audio/private/snd_wave_mixer_xma.h b/engine/audio/private/snd_wave_mixer_xma.h
new file mode 100644
index 0000000..7ec7663
--- /dev/null
+++ b/engine/audio/private/snd_wave_mixer_xma.h
@@ -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
diff --git a/engine/audio/private/snd_wave_source.cpp b/engine/audio/private/snd_wave_source.cpp
new file mode 100644
index 0000000..38c70a0
--- /dev/null
+++ b/engine/audio/private/snd_wave_source.cpp
@@ -0,0 +1,2816 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+
+#include "audio_pch.h"
+#include "snd_mp3_source.h"
+#include "utlsymbol.h"
+#include "checksum_crc.h"
+#include "../../host.h"
+#include "xwvfile.h"
+#include "filesystem/IQueuedLoader.h"
+#include "tier1/lzmaDecoder.h"
+#include "tier2/fileutils.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+// This determines how much data to pre-cache (will invalidate per-map caches if changed).
+#define SND_ASYNC_LOOKAHEAD_SECONDS ( 0.125f )
+
+extern ConVar snd_async_spew_blocking;
+ConVar snd_async_minsize("snd_async_minsize", "262144");
+
+// #define DEBUG_CHUNKS
+
+//-----------------------------------------------------------------------------
+// Purpose: Report chunk error
+// Input : id - chunk FOURCC
+//-----------------------------------------------------------------------------
+void ChunkError( unsigned int id )
+{
+#if defined( DEBUG_CHUNKS ) && defined( _DEBUG )
+ if ( id == WAVE_LIST || id == WAVE_FACT )
+ {
+ // unused chunks, not an error
+ return;
+ }
+
+ char tmp[256];
+ char idname[5];
+ idname[4] = 0;
+ memcpy( idname, &id, 4 );
+
+ Q_snprintf( tmp, sizeof( tmp ), "Unhandled chunk %s\n", idname );
+ Plat_DebugString( tmp );
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Determine a true sample count for an ADPCM blob
+//-----------------------------------------------------------------------------
+int ADPCMSampleCount( ADPCMWAVEFORMAT *pFormat, int length )
+{
+ // determine a true sample count
+ int nChannels = LittleWord( pFormat->wfx.nChannels );
+ int wSamplesPerBlock = LittleWord( pFormat->wSamplesPerBlock );
+
+ int blockSize = (( wSamplesPerBlock - 2) * nChannels ) / 2;
+ blockSize += 7 * nChannels;
+
+ int blockCount = length / blockSize;
+ int blockRem = length % blockSize;
+
+ // total samples in complete blocks
+ int sampleCount = blockCount * wSamplesPerBlock;
+
+ // add remaining in a short block
+ if ( blockRem )
+ {
+ sampleCount += wSamplesPerBlock - (((blockSize - blockRem) * 2) / nChannels );
+ }
+
+ return sampleCount;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Init to empty wave
+//-----------------------------------------------------------------------------
+CAudioSourceWave::CAudioSourceWave( CSfxTable *pSfx )
+{
+ m_format = 0;
+ m_pHeader = NULL;
+ m_nHeaderSize = 0;
+
+ // no looping
+ m_loopStart = -1;
+
+ m_sampleSize = 1;
+ m_sampleCount = 0;
+ m_bits = 0;
+ m_channels = 0;
+ m_dataStart = 0;
+ m_dataSize = 0;
+ m_rate = 0;
+
+ m_refCount = 0;
+
+ m_pSfx = pSfx;
+#ifdef _DEBUG
+ if ( m_pSfx )
+ m_pDebugName = strdup( m_pSfx->getname() );
+#endif
+
+ m_bNoSentence = false;
+ m_pTempSentence = NULL;
+ m_nCachedDataSize = 0;
+ m_bIsPlayOnce = false;
+ m_bIsSentenceWord = false;
+
+ m_numDecodedSamples = 0;
+}
+
+CAudioSourceWave::CAudioSourceWave( CSfxTable *pSfx, CAudioSourceCachedInfo *info )
+{
+ m_pSfx = pSfx;
+#ifdef _DEBUG
+ if ( m_pSfx )
+ m_pDebugName = strdup( m_pSfx->getname() );
+#endif
+
+ m_refCount = 0;
+
+ m_pHeader = NULL;
+ m_nHeaderSize = 0;
+
+ if ( info->HeaderData() )
+ {
+ m_pHeader = new char[ info->HeaderSize() ];
+ Assert( m_pHeader );
+ Q_memcpy( m_pHeader, info->HeaderData(), info->HeaderSize() );
+ m_nHeaderSize = info->HeaderSize();
+ }
+
+ m_bits = info->Bits();
+ m_channels = info->Channels();
+ m_sampleSize = info->SampleSize();
+ m_format = info->Format();
+ m_dataStart = info->DataStart();
+ m_dataSize = info->DataSize();
+ m_rate = info->SampleRate();
+ m_loopStart = info->LoopStart();
+ m_sampleCount = info->SampleCount();
+ m_numDecodedSamples = m_sampleCount;
+
+ if ( m_format == WAVE_FORMAT_ADPCM && m_pHeader )
+ {
+ m_numDecodedSamples = ADPCMSampleCount( (ADPCMWAVEFORMAT *)m_pHeader, m_sampleCount );
+ }
+
+ m_bNoSentence = false;
+ m_pTempSentence = NULL;
+ m_nCachedDataSize = 0;
+ m_bIsPlayOnce = false;
+ m_bIsSentenceWord = false;
+}
+
+
+CAudioSourceWave::~CAudioSourceWave( void )
+{
+#if _DEBUG
+ if ( !CanDelete() )
+ Assert(0);
+#endif
+
+ // for non-standard waves, we store a copy of the header in RAM
+ delete[] m_pHeader;
+ delete m_pTempSentence;
+}
+
+int CAudioSourceWave::GetType( void )
+{
+ return AUDIO_SOURCE_WAV;
+}
+
+void CAudioSourceWave::GetCacheData( CAudioSourceCachedInfo *info )
+{
+ Assert( info->Type() == CAudioSource::AUDIO_SOURCE_WAV );
+
+ byte tempbuf[ 32768 ];
+ int datalen = 0;
+ // NOTE GetStartupData has side-effects (...) hence the unconditional call
+ if ( GetStartupData( tempbuf, sizeof( tempbuf ), datalen ) &&
+ info->s_bIsPrecacheSound &&
+ datalen > 0 )
+ {
+ byte *data = new byte[ datalen ];
+ Q_memcpy( data, tempbuf, datalen );
+ info->SetCachedDataSize( datalen );
+ info->SetCachedData( data );
+ }
+
+ info->SetBits( m_bits );
+ info->SetChannels( m_channels );
+ info->SetSampleSize( m_sampleSize );
+ info->SetFormat( m_format );
+ info->SetDataStart( m_dataStart ); // offset of wave data chunk
+ info->SetDataSize( m_dataSize ); // size of wave data chunk
+ info->SetSampleRate( m_rate );
+ info->SetLoopStart( m_loopStart );
+ info->SetSampleCount( m_sampleCount );
+
+ if ( m_pTempSentence )
+ {
+ CSentence *scopy = new CSentence;
+ *scopy = *m_pTempSentence;
+ info->SetSentence( scopy );
+
+ // Wipe it down to basically nothing
+ delete m_pTempSentence;
+ m_pTempSentence = NULL;
+ }
+
+ if ( m_pHeader && m_nHeaderSize > 0 )
+ {
+ byte *data = new byte[ m_nHeaderSize ];
+ Q_memcpy( data, m_pHeader, m_nHeaderSize );
+ info->SetHeaderSize( m_nHeaderSize );
+ info->SetHeaderData( data );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : char const
+//-----------------------------------------------------------------------------
+char const *CAudioSourceWave::GetFileName()
+{
+ return m_pSfx ? m_pSfx->GetFileName() : "NULL m_pSfx";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAudioSourceWave::IsAsyncLoad()
+{
+ VPROF("CAudioSourceWave::IsAsyncLoad");
+
+ if ( ( IsPC() || !IsX360() ) && !m_AudioCacheHandle.IsValid() )
+ {
+ m_AudioCacheHandle.Get( GetType(), m_pSfx->IsPrecachedSound(), m_pSfx, &m_nCachedDataSize );
+ }
+
+ // 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)
+ if ( m_dataSize > snd_async_minsize.GetInt() )
+ return true;
+ return ( m_nCachedDataSize > 0 ) ? false : true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAudioSourceWave::CheckAudioSourceCache()
+{
+ if ( IsX360() )
+ {
+ // 360 does not use audio cache files
+ return;
+ }
+
+ Assert( m_pSfx );
+
+ if ( !m_pSfx || !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: Init the wave data.
+// Input : *pHeaderBuffer - the RIFF fmt chunk
+// headerSize - size of that chunk
+//-----------------------------------------------------------------------------
+void CAudioSourceWave::Init( const char *pHeaderBuffer, int headerSize )
+{
+ const WAVEFORMATEX *pHeader = (const WAVEFORMATEX *)pHeaderBuffer;
+
+ // copy the relevant header data
+ m_format = LittleWord( pHeader->wFormatTag );
+ m_bits = LittleWord( pHeader->wBitsPerSample );
+ m_rate = LittleDWord( pHeader->nSamplesPerSec );
+ m_channels = LittleWord( pHeader->nChannels );
+ m_sampleSize = (m_bits * m_channels)/8;
+
+ // this can never be zero -- other functions divide by this.
+ // this should never happen, but avoid crashing
+ if ( m_sampleSize <= 0 )
+ {
+ m_sampleSize = 1;
+ }
+
+ if ( m_format == WAVE_FORMAT_ADPCM )
+ {
+ // For non-standard waves (like ADPCM) store the header, it has the decoding coefficients
+ m_pHeader = new char[headerSize];
+ memcpy( m_pHeader, pHeader, headerSize );
+ m_nHeaderSize = headerSize;
+
+ // treat ADPCM sources as a file of bytes. They are decoded by the mixer
+ m_sampleSize = 1;
+ }
+}
+
+
+int CAudioSourceWave::SampleRate( void )
+{
+ return m_rate;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Size of each sample
+// Output :
+//-----------------------------------------------------------------------------
+int CAudioSourceWave::SampleSize( void )
+{
+ return m_sampleSize;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Total number of samples in this source
+// Output : int
+//-----------------------------------------------------------------------------
+int CAudioSourceWave::SampleCount( void )
+{
+ // caller wants real samples
+ return m_numDecodedSamples;
+}
+
+int CAudioSourceWave::Format( void )
+{
+ return m_format;
+}
+
+int CAudioSourceWave::DataSize( void )
+{
+ return m_dataSize;
+}
+
+bool CAudioSourceWave::IsVoiceSource()
+{
+ if ( GetSentence() )
+ {
+ if ( GetSentence()->GetVoiceDuck() )
+ return true;
+ }
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Do any sample conversion
+// For 8 bit PCM, convert to signed because the mixing routine assumes this
+// Input : *pData - pointer to sample data
+// sampleCount - number of samples
+//-----------------------------------------------------------------------------
+void CAudioSourceWave::ConvertSamples( char *pData, int sampleCount )
+{
+ if ( m_format == WAVE_FORMAT_PCM )
+ {
+ if ( m_bits == 8 )
+ {
+ for ( int i = 0; i < sampleCount*m_channels; i++ )
+ {
+ *pData = (unsigned char)((int)((unsigned)*pData) - 128);
+ pData++;
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Parse base chunks
+// Input : &walk - riff file to parse
+// : chunkName - name of the chunk to parse
+//-----------------------------------------------------------------------------
+// UNDONE: Move parsing loop here and drop each chunk into a virtual function
+// instead of this being virtual.
+void CAudioSourceWave::ParseChunk( IterateRIFF &walk, int chunkName )
+{
+ switch( chunkName )
+ {
+ case WAVE_CUE:
+ ParseCueChunk( walk );
+ break;
+ case WAVE_SAMPLER:
+ ParseSamplerChunk( walk );
+ break;
+ case WAVE_VALVEDATA:
+ ParseSentence( walk );
+ break;
+ default:
+ // unknown and don't care
+ ChunkError( walk.ChunkName() );
+ break;
+ }
+}
+
+bool CAudioSourceWave::IsLooped( void )
+{
+ return (m_loopStart >= 0) ? true : false;
+}
+
+bool CAudioSourceWave::IsStereoWav( void )
+{
+ return (m_channels == 2) ? true : false;
+}
+
+bool CAudioSourceWave::IsStreaming( void )
+{
+ return false;
+}
+
+
+int CAudioSourceWave::GetCacheStatus( void )
+{
+ return AUDIO_IS_LOADED;
+}
+
+void CAudioSourceWave::CacheLoad( void )
+{
+}
+
+void CAudioSourceWave::CacheUnload( void )
+{
+}
+
+int CAudioSourceWave::ZeroCrossingBefore( int sample )
+{
+ return sample;
+}
+
+
+int CAudioSourceWave::ZeroCrossingAfter( int sample )
+{
+ return sample;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &walk -
+//-----------------------------------------------------------------------------
+void CAudioSourceWave::ParseSentence( IterateRIFF &walk )
+{
+ CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
+
+ buf.EnsureCapacity( walk.ChunkSize() );
+ walk.ChunkRead( buf.Base() );
+ buf.SeekPut( CUtlBuffer::SEEK_HEAD, walk.ChunkSize() );
+
+ m_pTempSentence = new CSentence();
+ Assert( m_pTempSentence );
+
+ m_pTempSentence->InitFromDataChunk( buf.Base(), buf.TellPut() );
+
+ // Throws all phonemes into one word, discards sentence memory, etc.
+ m_pTempSentence->MakeRuntimeOnly();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : CSentence
+//-----------------------------------------------------------------------------
+CSentence *CAudioSourceWave::GetSentence( void )
+{
+ if ( IsX360() )
+ {
+ return m_pTempSentence;
+ }
+
+ // 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 )
+ {
+ m_bNoSentence = true;
+ return NULL;
+ }
+
+ if ( sentence->m_bIsValid )
+ {
+ return sentence;
+ }
+
+ m_bNoSentence = true;
+
+ return NULL;
+}
+
+const char *CAudioSourceWave::GetName()
+{
+ return m_pSfx ? m_pSfx->getname() : NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Load a native xaudio or legacy wav
+//-----------------------------------------------------------------------------
+bool CAudioSourceWave::GetXboxAudioStartupData()
+{
+ CUtlBuffer buf;
+ char fileName[MAX_PATH];
+ char tempFileName[MAX_PATH];
+
+ MEM_ALLOC_CREDIT();
+
+ // try native optimal xma wav file first
+ Q_StripExtension( m_pSfx->GetFileName(), tempFileName, sizeof( tempFileName ) );
+ Q_snprintf( fileName, sizeof( fileName ), "sound\\%s.360.wav", tempFileName );
+ if ( !g_pFullFileSystem->ReadFile( fileName, "GAME", buf, sizeof( xwvHeader_t ) ) )
+ {
+ // not found, not supported
+ return false;
+ }
+ else
+ {
+ xwvHeader_t* pHeader = (xwvHeader_t *)buf.Base();
+ if ( pHeader->id != XWV_ID || pHeader->version != XWV_VERSION )
+ {
+ return false;
+ }
+
+ if ( pHeader->format == XWV_FORMAT_XMA )
+ {
+ m_format = WAVE_FORMAT_XMA;
+ }
+ else if ( pHeader->format == XWV_FORMAT_PCM )
+ {
+ m_format = WAVE_FORMAT_PCM;
+ }
+ else
+ {
+ // unknown
+ return false;
+ }
+
+ m_rate = pHeader->GetSampleRate();
+ m_channels = pHeader->channels;
+ m_dataStart = pHeader->dataOffset;
+ m_dataSize = pHeader->dataSize;
+
+ m_loopStart = pHeader->loopStart;
+ m_loopBlock = pHeader->loopBlock;
+ m_numLeadingSamples = pHeader->numLeadingSamples;
+ m_numTrailingSamples = pHeader->numTrailingSamples;
+
+ if ( m_format == WAVE_FORMAT_XMA )
+ {
+ // xma is compressed blocks, trick to fool system to treat data as bytes, not samples
+ // unfortunate, but callers must know xma context and provide offsets in samples or bytes
+ m_bits = 16;
+ m_sampleSize = 1;
+ m_sampleCount = m_dataSize;
+ }
+ else
+ {
+ m_bits = 16;
+ m_sampleSize = sizeof( short ) * m_channels;
+ m_sampleCount = m_dataSize / m_sampleSize;
+ }
+
+ // keep true decoded samples because cannot be easily determined
+ m_numDecodedSamples = pHeader->numDecodedSamples;
+
+ m_bNoSentence = true;
+
+ CUtlBuffer fileBuffer;
+ if ( pHeader->staticDataSize )
+ {
+ // get optional data
+ if ( !g_pFullFileSystem->ReadFile( fileName, "GAME", fileBuffer, pHeader->staticDataSize, sizeof( xwvHeader_t ) ) )
+ {
+ return false;
+ }
+
+ unsigned char *pData = (unsigned char *)fileBuffer.Base() + sizeof( xwvHeader_t );
+ if ( pHeader->GetSeekTableSize() )
+ {
+ // store off the seek table
+ m_nHeaderSize = pHeader->GetSeekTableSize();
+ m_pHeader = new char[m_nHeaderSize];
+ V_memcpy( m_pHeader, pData, m_nHeaderSize );
+
+ // advance past optional seek table
+ pData += m_nHeaderSize;
+ }
+
+ if ( pHeader->vdatSize )
+ {
+ m_pTempSentence = new CSentence();
+ Assert( m_pTempSentence );
+ m_bNoSentence = false;
+
+ // vdat is precompiled into minimal binary format and possibly compressed
+ if ( CLZMA::IsCompressed( pData ) )
+ {
+ // uncompress binary vdat and restore
+ CUtlBuffer targetBuffer;
+ int originalSize = CLZMA::GetActualSize( pData );
+ targetBuffer.EnsureCapacity( originalSize );
+ CLZMA::Uncompress( pData, (unsigned char *)targetBuffer.Base() );
+ targetBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, originalSize );
+ m_pTempSentence->CacheRestoreFromBuffer( targetBuffer );
+ }
+ else
+ {
+ m_pTempSentence->CacheRestoreFromBuffer( fileBuffer );
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Bastardized construction routine. This is just to avoid complex
+// constructor functions so code can be shared more easily by sub-classes
+// Input : *pFormatBuffer - RIFF header
+// formatSize - header size
+// &walk - RIFF file
+//-----------------------------------------------------------------------------
+void CAudioSourceWave::Setup( const char *pFormatBuffer, int formatSize, IterateRIFF &walk )
+{
+ Init( pFormatBuffer, formatSize );
+
+ while ( walk.ChunkAvailable() )
+ {
+ ParseChunk( walk, walk.ChunkName() );
+ walk.ChunkNext();
+ }
+}
+
+
+bool CAudioSourceWave::GetStartupData( void *dest, int destsize, int& bytesCopied )
+{
+ bytesCopied = 0;
+
+ char formatBuffer[1024];
+ const char *pName = m_pSfx->GetFileName();
+ InFileRIFF riff( pName, *g_pSndIO );
+
+ if ( riff.RIFFName() != RIFF_WAVE )
+ {
+ return false;
+ }
+
+ // set up the iterator for the whole file (root RIFF is a chunk)
+ IterateRIFF walk( riff, riff.RIFFSize() );
+
+ int format = 0;
+ int formatSize = 0;
+
+ // This chunk must be first as it contains the wave's format
+ // break out when we've parsed it
+ while ( walk.ChunkAvailable() && format == 0 )
+ {
+ switch( walk.ChunkName() )
+ {
+ case WAVE_FMT:
+ {
+ if ( walk.ChunkSize() <= sizeof( formatBuffer ) )
+ {
+ walk.ChunkRead( formatBuffer );
+ formatSize = walk.ChunkSize();
+ format = ((WAVEFORMATEX *)formatBuffer)->wFormatTag;
+ if( ((WAVEFORMATEX *)formatBuffer)->wBitsPerSample > 16)
+ {
+ Warning("Unsupported %d-bit wave file %s\n", (int)((WAVEFORMATEX *)formatBuffer)->wBitsPerSample, pName);
+ }
+ }
+ }
+ break;
+ default:
+ {
+ ChunkError( walk.ChunkName() );
+ }
+ break;
+ }
+ walk.ChunkNext();
+ }
+
+ // Not really a WAVE file or no format chunk, bail
+ if ( !format )
+ {
+ return false;
+ }
+
+ Setup( formatBuffer, formatSize, walk );
+
+ if ( !m_dataStart || !m_dataSize )
+ {
+ // failed during setup
+ return false;
+ }
+
+ // requesting precache snippet as leader for streaming startup latency
+ if ( destsize )
+ {
+ int file = g_pSndIO->open( m_pSfx->GetFileName() );
+ if ( !file )
+ {
+ return false;
+ }
+
+ int bytesNeeded = m_channels * ( m_bits >> 3 ) * m_rate * SND_ASYNC_LOOKAHEAD_SECONDS;
+
+ // Round to multiple of 4
+ bytesNeeded = ( bytesNeeded + 3 ) & ~3;
+
+ bytesCopied = min( destsize, m_dataSize );
+ bytesCopied = min( bytesNeeded, bytesCopied );
+
+ g_pSndIO->seek( file, m_dataStart );
+ g_pSndIO->read( dest, bytesCopied, file );
+ g_pSndIO->close( file );
+
+ // some samples need to be converted
+ ConvertSamples( (char *)dest, ( bytesCopied / m_sampleSize ) );
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: parses loop information from a cue chunk
+// Input : &walk - RIFF iterator
+// Output : int loop start position
+//-----------------------------------------------------------------------------
+void CAudioSourceWave::ParseCueChunk( IterateRIFF &walk )
+{
+ // Cue chunk as specified by RIFF format
+ // see $/research/jay/sound/riffnew.htm
+ struct
+ {
+ unsigned int dwName;
+ unsigned int dwPosition;
+ unsigned int fccChunk;
+ unsigned int dwChunkStart;
+ unsigned int dwBlockStart;
+ unsigned int dwSampleOffset;
+ } cue_chunk;
+
+ int cueCount;
+
+ // assume that the cue chunk stored in the wave is the start of the loop
+ // assume only one cue chunk, UNDONE: Test this assumption here?
+ cueCount = walk.ChunkReadInt();
+ if ( cueCount > 0 )
+ {
+ walk.ChunkReadPartial( &cue_chunk, sizeof(cue_chunk) );
+ m_loopStart = LittleLong( cue_chunk.dwSampleOffset );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: parses loop information from a 'smpl' chunk
+// Input : &walk - RIFF iterator
+// Output : int loop start position
+//-----------------------------------------------------------------------------
+void CAudioSourceWave::ParseSamplerChunk( IterateRIFF &walk )
+{
+ // Sampler chunk for MIDI instruments
+ // Parse loop info from this chunk too
+ struct SampleLoop
+ {
+ unsigned int dwIdentifier;
+ unsigned int dwType;
+ unsigned int dwStart;
+ unsigned int dwEnd;
+ unsigned int dwFraction;
+ unsigned int dwPlayCount;
+ };
+
+ struct
+ {
+ unsigned int dwManufacturer;
+ unsigned int dwProduct;
+ unsigned int dwSamplePeriod;
+ unsigned int dwMIDIUnityNote;
+ unsigned int dwMIDIPitchFraction;
+ unsigned int dwSMPTEFormat;
+ unsigned int dwSMPTEOffset;
+ unsigned int cSampleLoops;
+ unsigned int cbSamplerData;
+ struct SampleLoop Loops[1];
+ } samplerChunk;
+
+ // assume that the loop end is the sample end
+ // assume that only the first loop is relevant
+
+ walk.ChunkReadPartial( &samplerChunk, sizeof(samplerChunk) );
+ if ( LittleLong( samplerChunk.cSampleLoops ) > 0 )
+ {
+ // only support normal forward loops
+ if ( LittleLong( samplerChunk.Loops[0].dwType ) == 0 )
+ {
+ m_loopStart = LittleLong( samplerChunk.Loops[0].dwStart );
+ }
+#ifdef _DEBUG
+ else
+ {
+ Msg("Unknown sampler chunk type %d on %s\n", LittleLong( samplerChunk.Loops[0].dwType ), m_pSfx->GetFileName() );
+ }
+#endif
+ }
+ // else discard - this is some other non-loop sampler data we don't support
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: get the wave header
+//-----------------------------------------------------------------------------
+void *CAudioSourceWave::GetHeader( void )
+{
+ return m_pHeader;
+}
+
+//-----------------------------------------------------------------------------
+// Gets the looping information. Some parameters are interpreted based on format
+//-----------------------------------------------------------------------------
+int CAudioSourceWave::GetLoopingInfo( int *pLoopBlock, int *pNumLeadingSamples, int *pNumTrailingSamples )
+{
+ if ( pLoopBlock )
+ {
+ // for xma, the block that contains the loop point
+ *pLoopBlock = m_loopBlock;
+ }
+
+ if ( pNumLeadingSamples )
+ {
+ // for xma, the number of leading samples at the loop block to discard
+ *pNumLeadingSamples = m_numLeadingSamples;
+ }
+
+ if ( pNumTrailingSamples )
+ {
+ // for xma, the number of trailing samples at the final block to discard
+ *pNumTrailingSamples = m_numTrailingSamples;
+ }
+
+ // the loop point in samples
+ return m_loopStart;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: wrap the position wrt looping
+// Input : samplePosition - absolute position
+// Output : int - looped position
+//-----------------------------------------------------------------------------
+int CAudioSourceWave::ConvertLoopedPosition( int samplePosition )
+{
+ if ( m_format == WAVE_FORMAT_XMA )
+ {
+ // xma mixer interprets loops and *always* sends a corrected position
+ return samplePosition;
+ }
+
+ // if the wave is looping and we're past the end of the sample
+ // convert to a position within the loop
+ // At the end of the loop, we return a short buffer, and subsequent call
+ // will loop back and get the rest of the buffer
+ if ( m_loopStart >= 0 && samplePosition >= m_sampleCount )
+ {
+ // size of loop
+ int loopSize = m_sampleCount - m_loopStart;
+ // subtract off starting bit of the wave
+ samplePosition -= m_loopStart;
+
+ if ( loopSize )
+ {
+ // "real" position in memory (mod off extra loops)
+ samplePosition = m_loopStart + (samplePosition % loopSize);
+ }
+ // ERROR? if no loopSize
+ }
+
+ return samplePosition;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: remove the reference for the mixer getting deleted
+// Input : *pMixer -
+//-----------------------------------------------------------------------------
+void CAudioSourceWave::ReferenceRemove( CAudioMixer *pMixer )
+{
+ m_refCount--;
+
+ if ( m_refCount == 0 && ( ( IsPC() && IsPlayOnce() ) || ( IsX360() && IsStreaming() ) ) )
+ {
+ SetPlayOnce( false ); // in case it gets used again
+ CacheUnload();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Add a mixer reference
+// Input : *pMixer -
+//-----------------------------------------------------------------------------
+void CAudioSourceWave::ReferenceAdd( CAudioMixer *pMixer )
+{
+ m_refCount++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: return true if no mixers reference this source
+//-----------------------------------------------------------------------------
+bool CAudioSourceWave::CanDelete( void )
+{
+ if ( m_refCount > 0 )
+ return false;
+
+ return true;
+}
+
+
+// CAudioSourceMemWave is a bunch of wave data that is all in memory.
+// To use it:
+// - derive from CAudioSourceMemWave
+// - call CAudioSourceWave::Init with a WAVEFORMATEX
+// - set m_sampleCount.
+// - implement GetDataPointer
+class CAudioSourceMemWave : public CAudioSourceWave
+{
+public:
+ CAudioSourceMemWave();
+ CAudioSourceMemWave( CSfxTable *pSfx );
+ CAudioSourceMemWave( CSfxTable *pSfx, CAudioSourceCachedInfo *info );
+ virtual ~CAudioSourceMemWave();
+
+ // These are all implemented by CAudioSourceMemWave.
+ virtual CAudioMixer* CreateMixer( int initialStreamPosition = 0 );
+ virtual int GetOutputData( void **pData, int samplePosition, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] );
+ virtual int ZeroCrossingBefore( int sample );
+ virtual int ZeroCrossingAfter( int sample );
+
+ virtual int GetCacheStatus( void );
+ virtual void CacheLoad( void );
+ virtual void CacheUnload( void );
+
+ // by definition, should already be in memory
+ virtual void Prefetch() {}
+
+ virtual void ParseChunk( IterateRIFF &walk, int chunkName );
+ void ParseDataChunk( IterateRIFF &walk );
+
+protected:
+
+ // Whoeover derives must implement this.
+ virtual char *GetDataPointer( void );
+
+ memhandle_t m_hCache;
+ StreamHandle_t m_hStream;
+
+private:
+ CAudioSourceMemWave( const CAudioSourceMemWave & ); // not implemented, not accessible
+};
+
+
+CAudioSourceMemWave::CAudioSourceMemWave() :
+ CAudioSourceWave( NULL )
+{
+ m_hCache = 0;
+ m_hStream = INVALID_STREAM_HANDLE;
+}
+
+CAudioSourceMemWave::CAudioSourceMemWave( CSfxTable *pSfx ) :
+ CAudioSourceWave( pSfx )
+{
+ m_hCache = 0;
+ m_hStream = INVALID_STREAM_HANDLE;
+
+ if ( IsX360() )
+ {
+ bool bValid = GetXboxAudioStartupData();
+ if ( !bValid )
+ {
+ // failed, substitute placeholder
+ pSfx->m_bUseErrorFilename = true;
+ bValid = GetXboxAudioStartupData();
+ if ( bValid )
+ {
+ DevWarning( "Failed to load sound \"%s\", substituting \"%s\"\n", pSfx->getname(), pSfx->GetFileName() );
+ }
+ }
+
+ if ( bValid )
+ {
+ // a 360 memory wave is a critical resource kept locked in memory, load its data now
+ CacheLoad();
+ }
+ }
+}
+
+CAudioSourceMemWave::CAudioSourceMemWave( CSfxTable *pSfx, CAudioSourceCachedInfo *info ) :
+ CAudioSourceWave( pSfx, info )
+{
+ m_hCache = 0;
+ m_hStream = INVALID_STREAM_HANDLE;
+}
+
+CAudioSourceMemWave::~CAudioSourceMemWave()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates a mixer and initializes it with an appropriate mixer
+//-----------------------------------------------------------------------------
+CAudioMixer *CAudioSourceMemWave::CreateMixer( int initialStreamPosition )
+{
+ CAudioMixer *pMixer = CreateWaveMixer( CreateWaveDataMemory(*this), m_format, m_channels, m_bits, initialStreamPosition );
+
+ return pMixer;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : **pData - output pointer to samples
+// samplePosition - position (in samples not bytes)
+// sampleCount - number of samples (not bytes)
+// Output : int - number of samples available
+//-----------------------------------------------------------------------------
+int CAudioSourceMemWave::GetOutputData( void **pData, int samplePosition, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
+{
+ // handle position looping
+ samplePosition = ConvertLoopedPosition( samplePosition );
+
+ // how many samples are available (linearly not counting looping)
+ int totalSampleCount = m_sampleCount - 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;
+ }
+
+ // byte offset in sample database
+ samplePosition *= m_sampleSize;
+
+ // 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
+ {
+ if ( IsPC() || !IsX360() )
+ {
+ // 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_sampleSize ) >= m_nCachedDataSize )
+ {
+ sampleCount = ( m_nCachedDataSize - samplePosition ) / m_sampleSize;
+ }
+
+ // Point at preloaded/cached data from .cache file for now
+ *pData = GetCachedDataPointer();
+ }
+ else
+ {
+ // for 360, memory wave data should have already been loaded and locked in cache
+ Assert( 0 );
+ }
+ }
+
+ if ( *pData )
+ {
+ *pData = (char *)*pData + samplePosition;
+ }
+ else
+ {
+ // End of data or some other problem
+ sampleCount = 0;
+ }
+ }
+
+ return sampleCount;
+}
+
+
+// Hardcoded macros to test for zero crossing
+#define ZERO_X_8(b) ((b)<2 && (b)>-2)
+#define ZERO_X_16(b) ((b)<512 && (b)>-512)
+
+//-----------------------------------------------------------------------------
+// Purpose: Search backward for a zero crossing starting at sample
+// Input : sample - starting point
+// Output : position of zero crossing
+//-----------------------------------------------------------------------------
+int CAudioSourceMemWave::ZeroCrossingBefore( int sample )
+{
+ char *pWaveData = GetDataPointer();
+
+ if ( m_format == WAVE_FORMAT_PCM )
+ {
+ if ( m_bits == 8 )
+ {
+ char *pData = pWaveData + sample * m_sampleSize;
+ bool zero = false;
+
+ if ( m_channels == 1 )
+ {
+ while ( sample > 0 && !zero )
+ {
+ if ( ZERO_X_8(*pData) )
+ zero = true;
+ else
+ {
+ sample--;
+ pData--;
+ }
+ }
+ }
+ else
+ {
+ while ( sample > 0 && !zero )
+ {
+ if ( ZERO_X_8(*pData) && ZERO_X_8(pData[1]) )
+ zero = true;
+ else
+ {
+ sample--;
+ pData--;
+ }
+ }
+ }
+ }
+ else
+ {
+ short *pData = (short *)(pWaveData + sample * m_sampleSize);
+ bool zero = false;
+
+ if ( m_channels == 1 )
+ {
+ while ( sample > 0 && !zero )
+ {
+ if ( ZERO_X_16(*pData) )
+ zero = true;
+ else
+ {
+ pData--;
+ sample--;
+ }
+ }
+ }
+ else
+ {
+ while ( sample > 0 && !zero )
+ {
+ if ( ZERO_X_16(*pData) && ZERO_X_16(pData[1]) )
+ zero = true;
+ else
+ {
+ sample--;
+ pData--;
+ }
+ }
+ }
+ }
+ }
+ return sample;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Search forward for a zero crossing
+// Input : sample - starting point
+// Output : position of found zero crossing
+//-----------------------------------------------------------------------------
+int CAudioSourceMemWave::ZeroCrossingAfter( int sample )
+{
+ char *pWaveData = GetDataPointer();
+
+ if ( m_format == WAVE_FORMAT_PCM )
+ {
+ if ( m_bits == 8 )
+ {
+ char *pData = pWaveData + sample * m_sampleSize;
+ bool zero = false;
+
+ if ( m_channels == 1 )
+ {
+ while ( sample < SampleCount() && !zero )
+ {
+ if ( ZERO_X_8(*pData) )
+ zero = true;
+ else
+ {
+ sample++;
+ pData++;
+ }
+ }
+ }
+ else
+ {
+ while ( sample < SampleCount() && !zero )
+ {
+ if ( ZERO_X_8(*pData) && ZERO_X_8(pData[1]) )
+ zero = true;
+ else
+ {
+ sample++;
+ pData++;
+ }
+ }
+ }
+ }
+ else
+ {
+ short *pData = (short *)(pWaveData + sample * m_sampleSize);
+ bool zero = false;
+
+ if ( m_channels == 1 )
+ {
+ while ( sample > 0 && !zero )
+ {
+ if ( ZERO_X_16(*pData) )
+ zero = true;
+ else
+ {
+ pData++;
+ sample++;
+ }
+ }
+ }
+ else
+ {
+ while ( sample > 0 && !zero )
+ {
+ if ( ZERO_X_16(*pData) && ZERO_X_16(pData[1]) )
+ zero = true;
+ else
+ {
+ sample++;
+ pData++;
+ }
+ }
+ }
+ }
+ }
+ return sample;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: parse chunks with unique processing to in-memory waves
+// Input : &walk - RIFF file
+//-----------------------------------------------------------------------------
+void CAudioSourceMemWave::ParseChunk( IterateRIFF &walk, int chunkName )
+{
+ switch( chunkName )
+ {
+ // this is the audio data
+ case WAVE_DATA:
+ ParseDataChunk( walk );
+ return;
+ }
+
+ CAudioSourceWave::ParseChunk( walk, chunkName );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: reads the actual sample data and parses it
+// Input : &walk - RIFF file
+//-----------------------------------------------------------------------------
+void CAudioSourceMemWave::ParseDataChunk( IterateRIFF &walk )
+{
+ m_dataStart = walk.ChunkFilePosition() + 8;
+ m_dataSize = walk.ChunkSize();
+
+ // 360 streaming model loads data later, but still needs critical member setup
+ char *pData = NULL;
+ if ( IsPC() || !IsX360() )
+ {
+ pData = GetDataPointer();
+ if ( !pData )
+ {
+ Error( "CAudioSourceMemWave (%s): GetDataPointer() failed.", m_pSfx ? m_pSfx->GetFileName() : "m_pSfx = NULL" );
+ }
+
+ // load them into memory (bad!!, this is a duplicate read of the data chunk)
+ walk.ChunkRead( pData );
+ }
+
+ if ( m_format == WAVE_FORMAT_PCM )
+ {
+ // number of samples loaded
+ m_sampleCount = m_dataSize / m_sampleSize;
+ m_numDecodedSamples = m_sampleCount;
+ }
+ else if ( m_format == WAVE_FORMAT_ADPCM )
+ {
+ // The ADPCM mixers treat the wave source as a flat file of bytes.
+ // Since each "sample" is a byte (this is a flat file), the number of samples is the file size
+ m_sampleCount = m_dataSize;
+ m_sampleSize = 1;
+
+ // file says 4, output is 16
+ m_bits = 16;
+
+ m_numDecodedSamples = ADPCMSampleCount( (ADPCMWAVEFORMAT *)m_pHeader, m_dataSize );
+ }
+
+ // some samples need to be converted
+ if ( pData )
+ {
+ ConvertSamples( pData, m_sampleCount );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+int CAudioSourceMemWave::GetCacheStatus( void )
+{
+ VPROF("CAudioSourceMemWave::GetCacheStatus");
+
+ if ( IsPC() || !IsX360() )
+ {
+ // NOTE: This will start the load if it isn't started
+ bool bCacheValid;
+ bool bCompleted = wavedatacache->IsDataLoadCompleted( m_hCache, &bCacheValid );
+ if ( !bCacheValid )
+ {
+ wavedatacache->RestartDataLoad( &m_hCache, m_pSfx->GetFileName(), m_dataSize, m_dataStart );
+ }
+ if ( bCompleted )
+ return AUDIO_IS_LOADED;
+ if ( wavedatacache->IsDataLoadInProgress( m_hCache ) )
+ return AUDIO_LOADING;
+ }
+ else
+ {
+ return wavedatacache->IsStreamedDataReady( m_hStream ) ? AUDIO_IS_LOADED : AUDIO_NOT_LOADED;
+ }
+
+ return AUDIO_NOT_LOADED;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAudioSourceMemWave::CacheLoad( void )
+{
+ if ( IsPC() || !IsX360() )
+ {
+ // Commence lazy load?
+ if ( m_hCache != 0 )
+ {
+ bool bCacheValid;
+ wavedatacache->IsDataLoadCompleted( m_hCache, &bCacheValid );
+ if ( !bCacheValid )
+ {
+ wavedatacache->RestartDataLoad( &m_hCache, m_pSfx->GetFileName(), m_dataSize, m_dataStart );
+ }
+ return;
+ }
+
+ m_hCache = wavedatacache->AsyncLoadCache( m_pSfx->GetFileName(), m_dataSize, m_dataStart );
+ }
+ else
+ {
+ if ( m_hStream == INVALID_STREAM_HANDLE )
+ {
+ // memory wave is resident
+ const char *pFilename = m_pSfx->GetFileName();
+ streamFlags_t streamFlags = STREAMED_FROMDVD;
+ char szFilename[MAX_PATH];
+ if ( m_format == WAVE_FORMAT_XMA || m_format == WAVE_FORMAT_PCM )
+ {
+ V_strcpy_safe( szFilename, pFilename );
+ V_SetExtension( szFilename, ".360.wav", sizeof( szFilename ) );
+ pFilename = szFilename;
+
+ // memory resident xma waves use the queued loader
+ // restricting to XMA due to not correctly running a post ConvertSamples, which is not an issue for XMA
+ if ( g_pQueuedLoader->IsMapLoading() )
+ {
+ // hint the wave data cache
+ streamFlags |= STREAMED_QUEUEDLOAD;
+ }
+ }
+
+ // open stream to load as a single monolithic buffer
+ m_hStream = wavedatacache->OpenStreamedLoad( pFilename, m_dataSize, m_dataStart, 0, -1, m_dataSize, 1, streamFlags );
+ if ( m_hStream != INVALID_STREAM_HANDLE && !( streamFlags & STREAMED_QUEUEDLOAD ) )
+ {
+ // block and finish load, convert data once right now
+ char *pWaveData = (char *)wavedatacache->GetStreamedDataPointer( m_hStream, true );
+ if ( pWaveData )
+ {
+ ConvertSamples( pWaveData, m_dataSize/m_sampleSize );
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAudioSourceMemWave::CacheUnload( void )
+{
+ if ( IsPC() || !IsX360() )
+ {
+ if ( m_hCache != 0 )
+ {
+ wavedatacache->Unload( m_hCache );
+ }
+ }
+ else
+ {
+ if ( m_hStream != INVALID_STREAM_HANDLE )
+ {
+ wavedatacache->CloseStreamedLoad( m_hStream );
+ m_hStream = INVALID_STREAM_HANDLE;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : char
+//-----------------------------------------------------------------------------
+char *CAudioSourceMemWave::GetDataPointer( void )
+{
+ char *pWaveData = NULL;
+
+ if ( IsPC() || !IsX360() )
+ {
+ bool bSamplesConverted = false;
+
+ if ( m_hCache == 0 )
+ {
+ // not in cache, start loading
+ CacheLoad();
+ }
+
+ // mount the requested data, blocks if necessary
+ wavedatacache->GetDataPointer(
+ m_hCache,
+ m_pSfx->GetFileName(),
+ m_dataSize,
+ m_dataStart,
+ (void **)&pWaveData,
+ 0,
+ &bSamplesConverted );
+
+ // If we have reloaded data from disk (async) and we haven't converted the samples yet, do it now
+ // FIXME: Is this correct for stereo wavs?
+ if ( pWaveData && !bSamplesConverted )
+ {
+ ConvertSamples( pWaveData, m_dataSize/m_sampleSize );
+ wavedatacache->SetPostProcessed( m_hCache, true );
+ }
+ }
+ else
+ {
+ if ( m_hStream != INVALID_STREAM_HANDLE )
+ {
+ // expected to be valid, unless failure during setup
+ pWaveData = (char *)wavedatacache->GetStreamedDataPointer( m_hStream, true );
+ }
+ }
+
+ return pWaveData;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Wave source for streaming wave files
+// UNDONE: Handle looping
+//-----------------------------------------------------------------------------
+class CAudioSourceStreamWave : public CAudioSourceWave, public IWaveStreamSource
+{
+public:
+ CAudioSourceStreamWave( CSfxTable *pSfx );
+ CAudioSourceStreamWave( CSfxTable *pSfx, CAudioSourceCachedInfo *info );
+ ~CAudioSourceStreamWave();
+
+ CAudioMixer *CreateMixer( int initialStreamPosition = 0 );
+ int GetOutputData( void **pData, int samplePosition, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] );
+ void ParseChunk( IterateRIFF &walk, int chunkName );
+ bool IsStreaming( void ) { return true; }
+
+ virtual int GetCacheStatus( void );
+
+ // IWaveStreamSource
+ virtual int UpdateLoopingSamplePosition( int samplePosition )
+ {
+ return ConvertLoopedPosition( samplePosition );
+ }
+ virtual void UpdateSamples( char *pData, int sampleCount )
+ {
+ ConvertSamples( pData, sampleCount );
+ }
+ virtual int GetLoopingInfo( int *pLoopBlock, int *pNumLeadingSamples, int *pNumTrailingSamples )
+ {
+ return CAudioSourceWave::GetLoopingInfo( pLoopBlock, pNumLeadingSamples, pNumTrailingSamples );
+ }
+
+ virtual void Prefetch();
+
+ virtual int SampleToStreamPosition( int samplePosition );
+ virtual int StreamToSamplePosition( int streamPosition );
+
+private:
+ CAudioSourceStreamWave( const CAudioSourceStreamWave & ); // not implemented, not accessible
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Save a copy of the file name for instances to open later
+// Input : *pFileName - filename
+//-----------------------------------------------------------------------------
+CAudioSourceStreamWave::CAudioSourceStreamWave( CSfxTable *pSfx ) : CAudioSourceWave( pSfx )
+{
+ m_pSfx = pSfx;
+ m_dataStart = -1;
+ m_dataSize = 0;
+ m_sampleCount = 0;
+
+ if ( IsX360() )
+ {
+ bool bValid = GetXboxAudioStartupData();
+ if ( !bValid )
+ {
+ // failed, substitute placeholder
+ pSfx->m_bUseErrorFilename = true;
+ bValid = GetXboxAudioStartupData();
+ if ( bValid )
+ {
+ DevWarning( "Failed to load sound \"%s\", substituting \"%s\"\n", pSfx->getname(), pSfx->GetFileName() );
+ }
+ }
+ }
+}
+
+CAudioSourceStreamWave::CAudioSourceStreamWave( CSfxTable *pSfx, CAudioSourceCachedInfo *info ) :
+ CAudioSourceWave( pSfx, info )
+{
+ m_pSfx = pSfx;
+ m_dataStart = info->DataStart();
+ m_dataSize = info->DataSize();
+
+ m_sampleCount = info->SampleCount();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: free the filename buffer
+//-----------------------------------------------------------------------------
+CAudioSourceStreamWave::~CAudioSourceStreamWave( void )
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Create an instance (mixer & wavedata) of this sound
+// Output : CAudioMixer * - pointer to the mixer
+//-----------------------------------------------------------------------------
+CAudioMixer *CAudioSourceStreamWave::CreateMixer( int initialStreamPosition )
+{
+ char fileName[MAX_PATH];
+ const char *pFileName = m_pSfx->GetFileName();
+ if ( IsX360() && ( m_format == WAVE_FORMAT_XMA || m_format == WAVE_FORMAT_PCM ) )
+ {
+ V_strcpy_safe( fileName, pFileName );
+ V_SetExtension( fileName, ".360.wav", sizeof( fileName ) );
+ pFileName = fileName;
+
+ // for safety, validate the initial stream position
+ // not trusting save/load
+ if ( m_format == WAVE_FORMAT_XMA )
+ {
+ if ( ( initialStreamPosition % XBOX_DVD_SECTORSIZE ) ||
+ ( initialStreamPosition % XMA_BLOCK_SIZE ) ||
+ ( initialStreamPosition >= m_dataSize ) )
+ {
+ initialStreamPosition = 0;
+ }
+ }
+ }
+
+ // BUGBUG: Source constructs the IWaveData, mixer frees it, fix this?
+ IWaveData *pWaveData = CreateWaveDataStream( *this, static_cast<IWaveStreamSource *>(this), pFileName, m_dataStart, m_dataSize, m_pSfx, initialStreamPosition );
+ if ( pWaveData )
+ {
+ CAudioMixer *pMixer = CreateWaveMixer( pWaveData, m_format, m_channels, m_bits, initialStreamPosition );
+ if ( pMixer )
+ {
+ return pMixer;
+ }
+
+ // no mixer, delete the stream buffer/instance
+ delete pWaveData;
+ }
+
+ return NULL;
+}
+
+void CAudioSourceStreamWave::Prefetch()
+{
+ PrefetchDataStream( m_pSfx->GetFileName(), m_dataStart, m_dataSize );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CAudioSourceStreamWave::SampleToStreamPosition( int samplePosition )
+{
+ if ( IsPC() )
+ {
+ // not for PC
+ Assert( 0 );
+ return 0;
+ }
+
+ if ( m_format != WAVE_FORMAT_XMA || !m_nHeaderSize )
+ {
+ // not in the expected format or lacking the seek table
+ return 0;
+ }
+
+ // Run through the seek table to find the block closest to the desired sample.
+ // Each seek table entry is the index (counting from the beginning of the file)
+ // of the first sample in the corresponding block, but there's no entry for the
+ // first block (since the index would always be zero).
+ int *pSeekTable = (int*)m_pHeader;
+ int packet = 0;
+ for ( int i = 0; i < m_nHeaderSize/(int)sizeof( int ); ++i )
+ {
+ if ( samplePosition < pSeekTable[i] )
+ {
+ packet = i;
+ break;
+ }
+ }
+
+ int streamPosition = ( packet == 0 ) ? 0 : ( packet - 1 ) * 2048;
+ return streamPosition;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CAudioSourceStreamWave::StreamToSamplePosition( int streamPosition )
+{
+ if ( IsPC() )
+ {
+ // not for PC
+ Assert( 0 );
+ return 0;
+ }
+
+ if ( m_format != WAVE_FORMAT_XMA || !m_nHeaderSize )
+ {
+ // not in the expected format or lacking the seek table
+ return 0;
+ }
+
+ int packet = streamPosition/2048;
+ if ( packet <= 0 )
+ {
+ return 0;
+ }
+ if ( packet > m_nHeaderSize/(int)sizeof( int ) )
+ {
+ return m_numDecodedSamples;
+ }
+
+ return ((int*)m_pHeader)[packet - 1];
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Parse a stream wave file chunk
+// unlike the in-memory file, don't load the data, just get a reference to it.
+// Input : &walk - RIFF file
+//-----------------------------------------------------------------------------
+void CAudioSourceStreamWave::ParseChunk( IterateRIFF &walk, int chunkName )
+{
+ // NOTE: It would be nice to break out of parsing once we have the data start and
+ // save seeking over the whole file. But to do so, the other needed chunks must occur
+ // before the DATA chunk. But, that is not standard and breaks most other wav parsers.
+
+ switch( chunkName )
+ {
+ case WAVE_DATA:
+ // data starts at chunk + 8 (chunk name, chunk size = 2*4=8 bytes)
+ // don't load the data, just know where it is so each instance
+ // can load it later
+ m_dataStart = walk.ChunkFilePosition() + 8;
+ m_dataSize = walk.ChunkSize();
+ m_sampleCount = m_dataSize / m_sampleSize;
+ return;
+ }
+ CAudioSourceWave::ParseChunk( walk, chunkName );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: This is not implemented here. This source has no data. It is the
+// WaveData's responsibility to load/serve the data
+//-----------------------------------------------------------------------------
+int CAudioSourceStreamWave::GetOutputData( void **pData, int samplePosition, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
+{
+ return 0;
+}
+
+int CAudioSourceStreamWave::GetCacheStatus( void )
+{
+ if ( !m_dataSize || !m_dataStart )
+ {
+ // didn't get precached properly
+ return AUDIO_NOT_LOADED;
+ }
+
+ return AUDIO_IS_LOADED;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Create a wave audio source (streaming or in memory)
+// Input : *pName - file name (NOTE: CAUDIOSOURCE KEEPS A POINTER TO pSfx)
+// streaming - if true, don't load, stream each instance
+// Output : CAudioSource * - a new source
+//-----------------------------------------------------------------------------
+CAudioSource *CreateWave( CSfxTable *pSfx, bool bStreaming )
+{
+ Assert( pSfx );
+
+#if defined( _DEBUG )
+ // For some reason you can't usually do pSfx->getname() in the dev studio debugger, so for convenience we'll grab the name
+ // here in debug builds at least...
+ char const *pName = pSfx->getname();
+ NOTE_UNUSED( pName );
+#endif
+
+ CAudioSourceWave *pWave = NULL;
+
+ if ( IsPC() || !IsX360() )
+ {
+ // Caching should always work, so if we failed to cache, it's a problem reading the file data, etc.
+ bool bIsMapSound = pSfx->IsPrecachedSound();
+ CAudioSourceCachedInfo *pInfo = audiosourcecache->GetInfo( CAudioSource::AUDIO_SOURCE_WAV, bIsMapSound, pSfx );
+
+ if ( pInfo && pInfo->Type() != CAudioSource::AUDIO_SOURCE_UNK )
+ {
+ // create the source from this file
+ if ( bStreaming )
+ {
+ pWave = new CAudioSourceStreamWave( pSfx, pInfo );
+ }
+ else
+ {
+ pWave = new CAudioSourceMemWave( pSfx, pInfo );
+ }
+ }
+ }
+ else
+ {
+ // 360 does not use audio cache system
+ // create the desired type
+ if ( bStreaming )
+ {
+ pWave = new CAudioSourceStreamWave( pSfx );
+ }
+ else
+ {
+ pWave = new CAudioSourceMemWave( pSfx );
+ }
+ }
+
+ if ( pWave && !pWave->Format() )
+ {
+ // lack of format indicates failure
+ delete pWave;
+ pWave = NULL;
+ }
+
+ return pWave;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Wrapper for CreateWave()
+//-----------------------------------------------------------------------------
+CAudioSource *Audio_CreateStreamedWave( CSfxTable *pSfx )
+{
+#if defined( MP3_SUPPORT )
+ if ( Audio_IsMP3( pSfx->GetFileName() ) )
+ {
+ return Audio_CreateStreamedMP3( pSfx );
+ }
+#endif
+
+ return CreateWave( pSfx, true );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Wrapper for CreateWave()
+//-----------------------------------------------------------------------------
+CAudioSource *Audio_CreateMemoryWave( CSfxTable *pSfx )
+{
+#if defined( MP3_SUPPORT )
+ if ( Audio_IsMP3( pSfx->GetFileName() ) )
+ {
+ return Audio_CreateMemoryMP3( pSfx );
+ }
+#endif
+
+ return CreateWave( pSfx, false );
+}
+
+float GetMP3Duration_Helper( char const *filename );
+static float Audio_GetMP3Duration( char const *pName )
+{
+ // Deduce from file
+ return GetMP3Duration_Helper( pName );
+}
+
+
+void MaybeReportMissingWav( char const *wav )
+{
+ static CUtlSymbolTable wavErrors;
+
+ CUtlSymbol sym;
+ sym = wavErrors.Find( wav );
+ if ( UTL_INVAL_SYMBOL == sym )
+ {
+ // See if file exists
+ if ( g_pFullFileSystem->FileExists( wav ) )
+ {
+ DevWarning( "Bad Audio file '%s'\n", wav );
+ }
+ else
+ {
+ DevWarning( "Missing wav file '%s'\n", wav );
+ }
+ wavErrors.AddString( wav );
+ }
+}
+
+static float Audio_GetWaveDuration( char const *pName )
+{
+ if ( IsX360() )
+ {
+ // should have precached
+ return 0;
+ }
+
+ char formatBuffer[1024];
+ WAVEFORMATEX *pfmt = (WAVEFORMATEX *)formatBuffer;
+
+ InFileRIFF riff( pName, *g_pSndIO );
+
+ if ( riff.RIFFName() != RIFF_WAVE )
+ {
+ MaybeReportMissingWav( pName );
+ return 0.0f;
+ }
+
+ // set up the iterator for the whole file (root RIFF is a chunk)
+ IterateRIFF walk( riff, riff.RIFFSize() );
+
+ int format = 0;
+ int formatSize = 0;
+ int sampleCount = 0;
+
+ // This chunk must be first as it contains the wave's format
+ // break out when we've parsed it
+ while ( walk.ChunkAvailable() && ( format == 0 || sampleCount == 0 ) )
+ {
+ switch( walk.ChunkName() )
+ {
+ case WAVE_FMT:
+ if ( walk.ChunkSize() <= sizeof( formatBuffer ) )
+ {
+ walk.ChunkRead( formatBuffer );
+ formatSize = walk.ChunkSize();
+ format = LittleWord( pfmt->wFormatTag );
+ }
+ break;
+ case WAVE_DATA:
+ if ( format != 0 )
+ {
+ int dataSize = walk.ChunkSize();
+ if ( format == WAVE_FORMAT_ADPCM )
+ {
+ // Dummy size for now
+ sampleCount = dataSize;
+ }
+ else
+ {
+ sampleCount = dataSize / ( LittleWord( pfmt->wBitsPerSample ) >> 3 );
+ }
+ }
+ break;
+ default:
+ ChunkError( walk.ChunkName() );
+ break;
+ }
+ walk.ChunkNext();
+ }
+
+ // Not really a WAVE file or no format chunk, bail
+ if ( !format || !sampleCount )
+ return 0.0f;
+
+ float sampleRate = LittleDWord( pfmt->nSamplesPerSec );
+
+ if ( format == WAVE_FORMAT_ADPCM )
+ {
+ // Determine actual duration
+ sampleCount = ADPCMSampleCount( (ADPCMWAVEFORMAT *)formatBuffer, sampleCount );
+ }
+
+ return (float)sampleCount / sampleRate;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Fast method for determining duration of .wav/.mp3, exposed to server as well
+// Input : *pName -
+// Output : float
+//-----------------------------------------------------------------------------
+float AudioSource_GetSoundDuration( char const *pName )
+{
+#if defined( MP3_SUPPORT )
+ if ( Audio_IsMP3( pName ) )
+ {
+ return Audio_GetMP3Duration( pName );
+ }
+#endif
+
+ CSfxTable *pSound = S_PrecacheSound( pName );
+ if ( pSound )
+ {
+ return AudioSource_GetSoundDuration( pSound );
+ }
+
+ return Audio_GetWaveDuration( pName );
+}
+
+float AudioSource_GetSoundDuration( CSfxTable *pSfx )
+{
+ if ( pSfx && pSfx->pSource )
+ {
+ return (float)pSfx->pSource->SampleCount() / (float)pSfx->pSource->SampleRate();
+ }
+
+ return 0;
+}
+
+CAudioSourceCachedInfo::CAudioSourceCachedInfo() :
+ infolong( 0 ),
+ flagsbyte( 0 ),
+ m_dataStart( 0 ),
+ m_dataSize( 0 ),
+ m_loopStart( 0 ),
+ m_sampleCount( 0 ),
+ m_usCachedDataSize( 0 ),
+ m_pCachedData( 0 ),
+ m_usHeaderSize( 0 ),
+ m_pHeader( 0 ),
+ m_pSentence( 0 )
+{
+}
+
+CAudioSourceCachedInfo& CAudioSourceCachedInfo::operator =( const CAudioSourceCachedInfo& src )
+{
+ if ( this == &src )
+ return *this;
+
+ infolong = src.infolong;
+ flagsbyte = src.flagsbyte;
+ SetDataStart( src.DataStart() );
+ SetDataSize( src.DataSize() );
+ SetLoopStart( src.LoopStart() );
+ SetSampleCount( src.SampleCount() );
+
+ CSentence *scopy = NULL;
+ if ( src.Sentence() )
+ {
+ scopy = new CSentence();
+ *scopy = *src.Sentence();
+ }
+ SetSentence( scopy );
+
+ byte *data = NULL;
+
+ Assert( src.CachedDataSize() == 0 || src.CachedData() );
+
+ m_usCachedDataSize = 0;
+
+ if ( src.CachedData() && src.CachedDataSize() > 0 )
+ {
+ SetCachedDataSize( src.CachedDataSize() );
+ data = new byte[ src.CachedDataSize() ];
+ Assert( data );
+ Q_memcpy( data, src.CachedData(), src.CachedDataSize() );
+ }
+
+ SetCachedData( data );
+
+ data = NULL;
+
+ Assert( src.HeaderSize() == 0 || src.HeaderData() );
+
+ m_usHeaderSize = 0;
+
+ if ( src.HeaderData() && src.HeaderSize() > 0 )
+ {
+ SetHeaderSize( src.HeaderSize() );
+ data = new byte[ src.HeaderSize() ];
+ Assert( data );
+ Q_memcpy( data, src.HeaderData(), src.HeaderSize() );
+ }
+
+ SetHeaderData( data );
+
+ return *this;
+}
+
+CAudioSourceCachedInfo::CAudioSourceCachedInfo( const CAudioSourceCachedInfo& src )
+{
+ if ( this == &src )
+ {
+ Assert( 0 );
+ return;
+ }
+
+ infolong = src.infolong;
+ flagsbyte = src.flagsbyte;
+ SetDataStart( src.DataStart() );
+ SetDataSize( src.DataSize() );
+ SetLoopStart( src.LoopStart() );
+ SetSampleCount( src.SampleCount() );
+
+ CSentence *scopy = NULL;
+ if ( src.Sentence() )
+ {
+ scopy = new CSentence();
+ *scopy = *src.Sentence();
+ }
+ SetSentence( scopy );
+
+ byte *data = NULL;
+
+ Assert( src.CachedDataSize() == 0 || src.CachedData() );
+
+ m_usCachedDataSize = 0;
+
+ if ( src.CachedData() && src.CachedDataSize() > 0 )
+ {
+ SetCachedDataSize( src.CachedDataSize() );
+ data = new byte[ src.CachedDataSize() ];
+ Assert( data );
+ Q_memcpy( data, src.CachedData(), src.CachedDataSize() );
+ }
+
+ SetCachedData( data );
+
+ data = NULL;
+
+ Assert( src.HeaderSize() == 0 || src.HeaderData() );
+
+ m_usHeaderSize = 0;
+
+ if ( src.HeaderData() && src.HeaderSize() > 0 )
+ {
+ SetHeaderSize( src.HeaderSize() );
+ data = new byte[ src.HeaderSize() ];
+ Assert( data );
+ Q_memcpy( data, src.HeaderData(), src.HeaderSize() );
+ }
+
+ SetHeaderData( data );
+}
+
+CAudioSourceCachedInfo::~CAudioSourceCachedInfo()
+{
+ Clear();
+}
+
+void CAudioSourceCachedInfo::Clear()
+{
+ infolong = 0;
+ flagsbyte = 0;
+ m_dataStart = 0;
+ m_dataSize = 0;
+ m_loopStart = 0;
+ m_sampleCount = 0;
+
+ delete m_pSentence;
+ m_pSentence = NULL;
+
+ delete[] m_pCachedData;
+ m_pCachedData = NULL;
+ m_usCachedDataSize = 0;
+
+ delete[] m_pHeader;
+ m_pHeader = NULL;
+ m_usHeaderSize = 0;
+}
+
+void CAudioSourceCachedInfo::RemoveData()
+{
+ delete[] m_pCachedData;
+ m_pCachedData = NULL;
+ m_usCachedDataSize = 0;
+ flags.m_bCachedData = false;
+}
+
+void CAudioSourceCachedInfo::Save( CUtlBuffer& buf )
+{
+ buf.PutInt( infolong );
+ buf.PutChar( flagsbyte );
+ buf.PutInt( m_dataStart );
+ buf.PutInt( m_dataSize );
+ buf.PutInt( m_loopStart );
+ buf.PutInt( m_sampleCount );
+
+ if ( flags.m_bSentence )
+ {
+ m_pSentence->CacheSaveToBuffer( buf, CACHED_SENTENCE_VERSION );
+ }
+
+ Assert( m_usCachedDataSize < 65535 );
+
+ if ( flags.m_bCachedData && m_pCachedData )
+ {
+ buf.PutInt( m_usCachedDataSize );
+ buf.Put( m_pCachedData, m_usCachedDataSize );
+ }
+
+ Assert( m_usHeaderSize <= 32767 );
+
+ if ( flags.m_bHeader )
+ {
+ buf.PutShort( m_usHeaderSize );
+ buf.Put( m_pHeader, m_usHeaderSize );
+ }
+}
+
+void CAudioSourceCachedInfo::Restore( CUtlBuffer& buf )
+{
+ // Wipe any old data!!!
+ Clear();
+
+ infolong = buf.GetInt();
+ flagsbyte = buf.GetChar();
+ m_dataStart = buf.GetInt();
+ m_dataSize = buf.GetInt();
+ m_loopStart = buf.GetInt();
+ m_sampleCount = buf.GetInt();
+ if ( flags.m_bSentence )
+ {
+ m_pSentence = new CSentence();
+ Assert( m_pSentence );
+ m_pSentence->CacheRestoreFromBuffer( buf );
+ }
+
+ if ( flags.m_bCachedData )
+ {
+ m_usCachedDataSize = buf.GetInt();
+ Assert( m_usCachedDataSize > 0 && m_usCachedDataSize < 65535 );
+ if ( m_usCachedDataSize > 0 )
+ {
+ byte *data = new byte[ m_usCachedDataSize ];
+ buf.Get( data, m_usCachedDataSize );
+ SetCachedData( data );
+ }
+ }
+
+ if ( flags.m_bHeader )
+ {
+ m_usHeaderSize = buf.GetShort();
+ Assert( m_usHeaderSize > 0 && m_usHeaderSize <= 32767 );
+ if ( m_usHeaderSize > 0 )
+ {
+ byte *data = new byte[ m_usHeaderSize ];
+ buf.Get( data, m_usHeaderSize );
+ SetHeaderData( data );
+ }
+ }
+}
+
+int CAudioSourceCachedInfo::s_CurrentType = CAudioSource::AUDIO_SOURCE_MAXTYPE;
+CSfxTable *CAudioSourceCachedInfo::s_pSfx = NULL;
+bool CAudioSourceCachedInfo::s_bIsPrecacheSound = false;
+
+void CAudioSourceCachedInfo::Rebuild( char const *filename )
+{
+ // Wipe any old data
+ Clear();
+
+ Assert( s_pSfx );
+ Assert( s_CurrentType != CAudioSource::AUDIO_SOURCE_MAXTYPE );
+
+#if 0
+ // Never cachify something which is not in the client precache list
+ if ( s_bIsPrecacheSound != s_pSfx->IsPrecachedSound() )
+ {
+ Msg( "Logic bug, precaching entry for '%s' which is not in precache list\n",
+ filename );
+ }
+#endif
+
+ SetType( s_CurrentType );
+
+ CAudioSource *as = NULL;
+
+ // Note though these instantiate a specific AudioSource subclass, it doesn't matter, we just need one for .wav and one for .mp3
+ switch ( s_CurrentType )
+ {
+ default:
+ case CAudioSource::AUDIO_SOURCE_VOICE:
+ break;
+ case CAudioSource::AUDIO_SOURCE_WAV:
+ as = new CAudioSourceMemWave( s_pSfx );
+ break;
+ case CAudioSource::AUDIO_SOURCE_MP3:
+#if defined( MP3_SUPPORT )
+ as = new CAudioSourceMP3Cache( s_pSfx );
+#endif
+ break;
+ }
+
+ if ( as )
+ {
+ as->GetCacheData( this );
+ delete as;
+ }
+}
+
+// Versions
+// 3: The before time
+// 4: Changed MP3 caching to ensure we store proper sample rate, removed hack to not cache vo/
+// 5: Fixed bug that could result in incorrect mp3 datasizes in the sound cache
+#define AUDIOSOURCE_CACHE_VERSION 5
+class CAudioSourceCache : public IAudioSourceCache
+{
+public:
+
+ struct SearchPathCache : CUtlCachedFileData< CAudioSourceCachedInfo >
+ {
+ SearchPathCache( const char *pszRepositoryFilename, const char *pszSearchPath, UtlCachedFileDataType_t eOutOfDateMethod )
+ : CUtlCachedFileData( pszRepositoryFilename, AUDIOSOURCE_CACHE_VERSION, AsyncLookaheadMetaChecksum, eOutOfDateMethod )
+ {
+ V_strcpy_safe( m_szSearchPath, pszSearchPath );
+
+ // Delete any existing cache if it's out of date
+ IsUpToDate();
+
+ // Load up existing cache file
+ Init();
+ }
+
+ char m_szSearchPath[ MAX_PATH ];
+
+ virtual ~SearchPathCache()
+ {
+ Shutdown();
+ }
+ };
+
+
+ CAudioSourceCache()
+ {
+ m_nServerCount = -1;
+ m_bSndCacheDebug = false;
+ }
+
+ bool Init( unsigned int memSize );
+ void Shutdown();
+
+ void CheckSaveDirtyCaches();
+ void CheckCacheBuild();
+ void BuildCache( char const *pszSearchPath );
+
+ static unsigned int AsyncLookaheadMetaChecksum( void );
+
+ void LevelInit( char const *mapname );
+ void LevelShutdown();
+
+ virtual CAudioSourceCachedInfo *GetInfo( int audiosourcetype, bool soundisprecached, CSfxTable *sfx );
+ virtual void RebuildCacheEntry( int audiosourcetype, bool soundisprecached, CSfxTable *sfx );
+ virtual void ForceRecheckDiskInfo();
+
+private:
+ SearchPathCache *LookUpCacheEntry( const char *szCleanedFilename, int audiosourcetype, bool soundisprecached, CSfxTable *sfx );
+
+ SearchPathCache *FindCacheForSearchPath( const char *pszSearchPath );
+ SearchPathCache *CreateCacheForSearchPath( const char *pszSearchPath );
+
+ static void GetSoundFilename( char *szResult, int nResultSize, const char *pszInputFilename );
+
+ void RemoveCache( char const *cachename );
+
+ // List of all loaded caches
+ CUtlVector<SearchPathCache*> m_vecCaches;
+
+ int m_nServerCount;
+ bool m_bSndCacheDebug;
+};
+
+static CAudioSourceCache g_ASCache;
+IAudioSourceCache *audiosourcecache = &g_ASCache;
+
+unsigned int CAudioSourceCachedInfoHandle_t::s_nCurrentFlushCount = 1;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAudioSourceCachedInfoHandle_t::InvalidateCache()
+{
+ ++s_nCurrentFlushCount;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAudioSourceCache::Init( unsigned int memSize )
+{
+#if defined( _DEBUG )
+ Msg( "CAudioSourceCache: Init\n" );
+#endif
+
+ m_bSndCacheDebug = CommandLine()->FindParm( "-sndcachedebug" ) ? true : false;
+
+ if ( !wavedatacache->Init( memSize ) )
+ {
+ Error( "Unable to init wavedatacache system\n" );
+ return false;
+ }
+
+ if ( IsX360() )
+ {
+ // 360 doesn't use audio source caches
+ return true;
+ }
+
+ // Gather up list of search paths
+ CUtlVector< CUtlString > vecSearchPaths;
+ GetSearchPath( vecSearchPaths, "game" );
+
+ // Create corresponding caches
+ FOR_EACH_VEC( vecSearchPaths, idxSearchPath )
+ {
+
+ // Standardize the name
+ char szSearchPath[ MAX_PATH ];
+ V_strcpy_safe( szSearchPath, vecSearchPaths[idxSearchPath] );
+ V_FixSlashes( szSearchPath );
+ V_AppendSlash( szSearchPath, sizeof(szSearchPath ) );
+
+ // See if we already have a cache for this search path.
+ bool bFound = false;
+ FOR_EACH_VEC( m_vecCaches, idxCache )
+ {
+ if ( V_stricmp( szSearchPath, m_vecCaches[idxCache]->m_szSearchPath ) == 0 )
+ {
+ Assert( V_strcmp( szSearchPath, m_vecCaches[idxCache]->m_szSearchPath ) == 0 ); // case *should* match exactly
+ bFound = true;
+ break;
+ }
+ }
+ if ( bFound )
+ continue;
+
+ // Add a ceche
+ SearchPathCache *pCache = CreateCacheForSearchPath( szSearchPath );
+ m_vecCaches.AddToTail( pCache );
+ }
+
+ return true;
+}
+
+CAudioSourceCache::SearchPathCache *CAudioSourceCache::FindCacheForSearchPath( const char *pszSearchPath )
+{
+ FOR_EACH_VEC( m_vecCaches, idx )
+ {
+ SearchPathCache *pCache = m_vecCaches[idx];
+ if ( V_stricmp( pCache->m_szSearchPath, pszSearchPath ) == 0 )
+ {
+ return pCache;
+ }
+ }
+
+ return NULL;
+}
+
+CAudioSourceCache::SearchPathCache *CAudioSourceCache::CreateCacheForSearchPath( const char *pszSearchPath )
+{
+
+ // Make sure search path ends in a slash
+ char szSearchPath[ MAX_PATH ];
+ V_strcpy_safe( szSearchPath, pszSearchPath );
+ V_AppendSlash( szSearchPath, sizeof(szSearchPath) );
+
+ // Set the filename for the cache.
+ UtlCachedFileDataType_t eOutOfDateMethod = UTL_CACHED_FILE_USE_FILESIZE;
+ char szCacheName[ MAX_PATH + 32 ];
+ V_strcpy_safe( szCacheName, szSearchPath );
+ char *dotVpkSlash = V_stristr( szCacheName, ".vpk" CORRECT_PATH_SEPARATOR_S );
+ if ( dotVpkSlash )
+ {
+ Assert( dotVpkSlash[5] == '\0' );
+ char *d = dotVpkSlash+4; // backup to where the slash is
+ Assert( *d == CORRECT_PATH_SEPARATOR );
+ V_strcpy( d, ".sound.cache" );
+ }
+ else
+ {
+ V_strcat_safe( szCacheName, "sound" CORRECT_PATH_SEPARATOR_S "sound.cache" );
+ eOutOfDateMethod = UTL_CACHED_FILE_USE_TIMESTAMP;
+ }
+
+ return new SearchPathCache( szCacheName, szSearchPath, eOutOfDateMethod );
+}
+
+//-----------------------------------------------------------------------------
+void CAudioSourceCache::Shutdown()
+{
+#if defined( _DEBUG )
+ Msg( "CAudioSourceCache: Shutdown\n" );
+#endif
+
+ CheckSaveDirtyCaches();
+ m_vecCaches.PurgeAndDeleteElements();
+
+ wavedatacache->Shutdown();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called by Host_Init on engine startup to rebuild everything if needed
+//-----------------------------------------------------------------------------
+void CAudioSourceCache::CheckCacheBuild()
+{
+ if ( IsX360() )
+ {
+ return;
+ }
+
+ // !FIXME! We'll just do everything lazily for now!
+ FOR_EACH_VEC( m_vecCaches, idx )
+ {
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CAudioSourceCache::CheckSaveDirtyCaches()
+{
+ FOR_EACH_VEC( m_vecCaches, idx )
+ {
+ SearchPathCache *pCache = m_vecCaches[idx];
+ if ( pCache->IsDirty() && pCache->GetNumElements() > 0 )
+ {
+ Msg( "Saving %s\n", pCache->GetRepositoryFileName() );
+ pCache->Save();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Static method
+// Output : unsigned int
+//-----------------------------------------------------------------------------
+unsigned int CAudioSourceCache::AsyncLookaheadMetaChecksum( void )
+{
+ if ( IsX360() )
+ {
+ return 0;
+ }
+
+ CRC32_t crc;
+ CRC32_Init( &crc );
+
+ float f = SND_ASYNC_LOOKAHEAD_SECONDS;
+ CRC32_ProcessBuffer( &crc, &f, sizeof( f ) );
+ // Finish
+ CRC32_Final( &crc );
+
+ return (unsigned int)crc;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *mapname -
+//-----------------------------------------------------------------------------
+void CAudioSourceCache::LevelInit( char const *mapname )
+{
+ CheckSaveDirtyCaches();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAudioSourceCache::LevelShutdown()
+{
+ CheckSaveDirtyCaches();
+}
+
+//-----------------------------------------------------------------------------
+void CAudioSourceCache::GetSoundFilename( char *szResult, int nResultSize, const char *pszInputFilename )
+{
+ V_snprintf( szResult, nResultSize, "sound/%s", pszInputFilename );
+ V_FixSlashes( szResult );
+ V_RemoveDotSlashes( szResult );
+ V_strlower( szResult );
+}
+
+//-----------------------------------------------------------------------------
+CAudioSourceCache::SearchPathCache *CAudioSourceCache::LookUpCacheEntry( const char *fn, int audiosourcetype, bool soundisprecached, CSfxTable *sfx )
+{
+ if ( IsX360() )
+ {
+ return NULL;
+ }
+
+ // Hack to remember the type of audiosource to create if we need to recreate it
+ CAudioSourceCachedInfo::s_CurrentType = audiosourcetype;
+ CAudioSourceCachedInfo::s_pSfx = sfx;
+ CAudioSourceCachedInfo::s_bIsPrecacheSound = soundisprecached;
+
+ // Get cleaned up filename
+ char szRelFilename[ 256 ];
+ GetSoundFilename( szRelFilename, sizeof( szRelFilename ), sfx->GetFileName() );
+
+ // Get absolute filename. This thing had better exist in the filesystem somewhere
+ char szAbsFilename[ 1024 ];
+ if ( !g_pFullFileSystem->RelativePathToFullPath( szRelFilename, "game", szAbsFilename, sizeof(szAbsFilename) ) )
+ {
+ return NULL;
+ }
+
+ // now try to figure out which search path this corresponds to
+ FOR_EACH_VEC( m_vecCaches, idx )
+ {
+ SearchPathCache *pCache = m_vecCaches[idx];
+ if ( V_strncmp( pCache->m_szSearchPath, szAbsFilename, V_strlen( pCache->m_szSearchPath ) ) == 0 )
+ {
+ return pCache;
+ }
+ }
+ Warning( "Cannot figure out which search path %s came from. Absolute path is %s\n", szRelFilename, szAbsFilename );
+
+ SearchPathCache *pCache = NULL;
+
+ return pCache;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CAudioSourceCachedInfo *CAudioSourceCache::GetInfo( int audiosourcetype, bool soundisprecached, CSfxTable *sfx )
+{
+ VPROF("CAudioSourceCache::GetInfo");
+
+ if ( IsX360() )
+ {
+ // 360 not using
+ return NULL;
+ }
+
+ Assert( sfx );
+
+ char fn[ 512 ];
+ GetSoundFilename( fn, sizeof( fn ), sfx->GetFileName() );
+
+ CAudioSourceCachedInfo *info = NULL;
+ SearchPathCache *pCache = LookUpCacheEntry( fn, audiosourcetype, soundisprecached, sfx );
+ if ( !pCache )
+ return NULL;
+
+ info = pCache->Get( fn );
+
+// Is this applicable anymore now that we have a cache per search path?
+// if ( info && info->Format() == 0 )
+// {
+// if ( g_pFullFileSystem->FileExists( fn, "BSP" ) )
+// {
+// DevMsg( 1, "Forced rebuild of bsp cache sound '%s'\n", fn );
+// info = pCache->RebuildItem( fn );
+// Assert( info->Format() != 0 );
+// }
+// }
+
+ return info;
+}
+
+
+void CAudioSourceCache::RebuildCacheEntry( int audiosourcetype, bool soundisprecached, CSfxTable *sfx )
+{
+ VPROF("CAudioSourceCache::RebuildCacheEntry");
+
+ if ( IsX360() )
+ {
+ // 360 not using
+ return;
+ }
+
+ Assert( sfx );
+
+ char fn[ 512 ];
+ GetSoundFilename( fn, sizeof( fn ), sfx->GetFileName() );
+ SearchPathCache *pCache = LookUpCacheEntry( fn, audiosourcetype, soundisprecached, sfx );
+ if ( !pCache )
+ return;
+
+ pCache->RebuildItem( fn );
+}
+
+
+//-----------------------------------------------------------------------------
+void CAudioSourceCache::ForceRecheckDiskInfo()
+{
+ FOR_EACH_VEC( m_vecCaches, idx )
+ {
+ m_vecCaches[ idx ]->ForceRecheckDiskInfo();
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CAudioSourceCache::RemoveCache( char const *cachename )
+{
+ if ( IsX360() )
+ {
+ return;
+ }
+
+ if ( g_pFullFileSystem->FileExists( cachename, "MOD" ) )
+ {
+ if ( !g_pFullFileSystem->IsFileWritable( cachename, "MOD" ) )
+ {
+ g_pFullFileSystem->SetFileWritable( cachename, true, "MOD" );
+ }
+ g_pFullFileSystem->RemoveFile( cachename, "MOD" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CAudioSourceCache::BuildCache( char const *pszSearchPath )
+{
+
+ // Get absolute path
+ char szAbsPath[ MAX_PATH ];
+ V_MakeAbsolutePath( szAbsPath, sizeof(szAbsPath), pszSearchPath );
+ V_FixSlashes( szAbsPath );
+
+ // Add a search path to the filesystem. We'll add one search path as a kludge so we
+ // can use the existing file finder system easily
+ g_pFullFileSystem->AddSearchPath( szAbsPath, "soundcache_kludge", PATH_ADD_TO_HEAD );
+
+ Msg( "Finding .wav files...\n");
+ CUtlVector< CUtlString > vecFilenames;
+ AddFilesToList( vecFilenames, "sound", "soundcache_kludge", "wav" );
+
+ Msg( "Finding .mp3 files...\n");
+ AddFilesToList( vecFilenames, "sound", "soundcache_kludge", "mp3" );
+
+ Msg( "Found %d audio files.\n", vecFilenames.Count() );
+
+ g_pFullFileSystem->RemoveSearchPaths( "soundcache_kludge" );
+
+ if ( vecFilenames.Count() < 1 )
+ {
+ Warning(" No audio files found. Not building cache\n" );
+ return;
+ }
+
+ // FindCacheForSearchPath expects an absolute search path, but if we're working with a VPK we'll have the path to
+ // the file, wherein the proper path to the file is /foo/bar.vpk, but the *search path* should be /foo/bar.vpk/
+ char szAsSearchPath[MAX_PATH] = { 0 };
+ V_strncpy( szAsSearchPath, szAbsPath, sizeof( szAsSearchPath ) );
+ V_AppendSlash( szAsSearchPath, sizeof( szAsSearchPath ) );
+
+ SearchPathCache *pCache = FindCacheForSearchPath( szAsSearchPath );
+ if ( !pCache )
+ {
+ // This cache might not have existed on startup
+ pCache = CreateCacheForSearchPath( szAbsPath );
+ m_vecCaches.AddToTail( pCache );
+ }
+
+ g_pFullFileSystem->AddSearchPath( szAbsPath, "game", PATH_ADD_TO_HEAD );
+ int nLenAbsPath = V_strlen( szAbsPath );
+ int iLastShownPct = -1;
+ FOR_EACH_VEC( vecFilenames, idxFilename )
+ {
+ const char *pszFilename = vecFilenames[ idxFilename ];
+ if ( V_strnicmp( pszFilename, szAbsPath, nLenAbsPath ) != 0 )
+ {
+ Warning( "Sound %s doesn't begin with search path %s\n", pszFilename, szAbsPath );
+ Assert( false );
+ continue;
+ }
+ const char *pszRelName = pszFilename + nLenAbsPath;
+ if ( *pszRelName == '/' || *pszRelName == '\\' )
+ ++pszRelName;
+ if ( V_strnicmp( pszRelName, "sound" CORRECT_PATH_SEPARATOR_S, 6 ) != 0 )
+ {
+ Warning( "Relative name %s doesn't begin with leading 'sound' directory?\n", pszRelName );
+ Assert( false );
+ continue;
+ }
+ const char *pszName = pszRelName + 6;
+
+ // Show progress
+ int iPct = idxFilename * 100 / vecFilenames.Count();
+ if ( iPct != iLastShownPct )
+ {
+ Msg( " %3d%% %s\n", iPct, pszName );
+ iLastShownPct = iPct;
+ }
+
+ CAudioSourceCachedInfo::s_bIsPrecacheSound = true;
+ CAudioSourceCachedInfo::s_CurrentType = CAudioSource::AUDIO_SOURCE_WAV;
+ char szExt[ 10 ] = { 0 };
+ V_ExtractFileExtension( pszFilename, szExt, sizeof( szExt ) );
+ if ( V_stricmp( szExt, "mp3" ) == 0 )
+ {
+ CAudioSourceCachedInfo::s_CurrentType = CAudioSource::AUDIO_SOURCE_MP3;
+ }
+ CAudioSourceCachedInfo::s_pSfx = S_DummySfx( pszName );
+
+ const CAudioSourceCachedInfo *pInfo = pCache->Get( pszRelName );
+ if ( !pInfo )
+ {
+ Warning( "Failed to cache info for %s\n", pszFilename );
+ }
+ }
+ g_pFullFileSystem->RemoveSearchPath( szAbsPath, "game" );
+
+ if ( pCache->IsDirty() )
+ {
+ Msg( "Saving %s\n", pCache->GetRepositoryFileName() );
+ pCache->Save();
+ }
+ else
+ {
+ Msg( "No changes detected; not saving %s\n", pCache->GetRepositoryFileName() );
+ }
+}
+
+void CheckCacheBuild()
+{
+ g_ASCache.CheckCacheBuild();
+}
+
+CON_COMMAND( snd_buildcache, "<directory or VPK filename> Rebulds sound cache for a given search path.\n" )
+{
+ if ( args.ArgC() < 2 )
+ {
+ ConMsg( "Usage: snd_buildcache <directory or VPK filename>\n" );
+ return;
+ }
+
+ // Allow them to eitehr specify multiple args, or comma-seperated list.
+ // You cannot easily pas multiple args on the (OS) command line.
+ for ( int idxArg = 1 ; idxArg < args.ArgC() ; ++idxArg )
+ {
+ CUtlStringList vecPaths;
+ V_SplitString( args[idxArg], ",", vecPaths );
+ FOR_EACH_VEC( vecPaths, idxPath )
+ {
+ g_ASCache.BuildCache( vecPaths[idxPath] );
+ }
+ }
+
+ // And now quit the game, because we mucked with search paths and the game is almost certainly not
+ // going to work anymore
+ Msg( "Quitting the game because we probably screwed up the search paths...\n" );
+ extern void HostState_Shutdown();
+ HostState_Shutdown();
+}
+
diff --git a/engine/audio/private/snd_wave_source.h b/engine/audio/private/snd_wave_source.h
new file mode 100644
index 0000000..09fe4c1
--- /dev/null
+++ b/engine/audio/private/snd_wave_source.h
@@ -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
diff --git a/engine/audio/private/snd_wave_temp.cpp b/engine/audio/private/snd_wave_temp.cpp
new file mode 100644
index 0000000..b1c6702
--- /dev/null
+++ b/engine/audio/private/snd_wave_temp.cpp
@@ -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 );
+}
diff --git a/engine/audio/private/snd_wave_temp.h b/engine/audio/private/snd_wave_temp.h
new file mode 100644
index 0000000..6d7f74f
--- /dev/null
+++ b/engine/audio/private/snd_wave_temp.h
@@ -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
diff --git a/engine/audio/private/snd_win.cpp b/engine/audio/private/snd_win.cpp
new file mode 100644
index 0000000..742f559
--- /dev/null
+++ b/engine/audio/private/snd_win.cpp
@@ -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();
+ }
+}
+
diff --git a/engine/audio/private/sound_private.h b/engine/audio/private/sound_private.h
new file mode 100644
index 0000000..f054c7c
--- /dev/null
+++ b/engine/audio/private/sound_private.h
@@ -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
diff --git a/engine/audio/private/voice.cpp b/engine/audio/private/voice.cpp
new file mode 100644
index 0000000..ce506d1
--- /dev/null
+++ b/engine/audio/private/voice.cpp
@@ -0,0 +1,1566 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+
+#include "audio_pch.h"
+#include "circularbuffer.h"
+#include "voice.h"
+#include "voice_wavefile.h"
+#include "r_efx.h"
+#include "cdll_int.h"
+#include "voice_gain.h"
+#include "voice_mixer_controls.h"
+
+#include "ivoicerecord.h"
+#include "ivoicecodec.h"
+#include "filesystem.h"
+#include "../../filesystem_engine.h"
+#include "tier1/utlbuffer.h"
+#if defined( _X360 )
+#include "xauddefs.h"
+#endif
+
+#include "steam/steam_api.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+static CSteamAPIContext g_SteamAPIContext;
+static CSteamAPIContext *steamapicontext = NULL;
+
+void Voice_EndChannel( int iChannel );
+void VoiceTweak_EndVoiceTweakMode();
+void EngineTool_OverrideSampleRate( int& rate );
+
+// A fallback codec that should be the most likely to work for local/offline use
+#define VOICE_FALLBACK_CODEC "vaudio_celt"
+
+// Special entity index used for tweak mode.
+#define TWEAKMODE_ENTITYINDEX -500
+
+// Special channel index passed to Voice_AddIncomingData when in tweak mode.
+#define TWEAKMODE_CHANNELINDEX -100
+
+
+// How long does the sign stay above someone's head when they talk?
+#define SPARK_TIME 0.2
+
+// How long a voice channel has to be inactive before we free it.
+#define DIE_COUNTDOWN 0.5
+
+#define VOICE_RECEIVE_BUFFER_SIZE (VOICE_OUTPUT_SAMPLE_RATE_MAX * BYTES_PER_SAMPLE)
+
+#define LOCALPLAYERTALKING_TIMEOUT 0.2f // How long it takes for the client to decide the server isn't sending acks
+ // of voice data back.
+
+// If this is defined, then the data is converted to 8-bit and sent otherwise uncompressed.
+// #define VOICE_SEND_RAW_TEST
+
+// The format we sample voice in.
+WAVEFORMATEX g_VoiceSampleFormat =
+{
+ WAVE_FORMAT_PCM, // wFormatTag
+ 1, // nChannels
+ // These two can be dynamically changed by voice_init
+ VOICE_OUTPUT_SAMPLE_RATE_LOW, // nSamplesPerSec
+ VOICE_OUTPUT_SAMPLE_RATE_LOW*2, // nAvgBytesPerSec
+ 2, // nBlockAlign
+ 16, // wBitsPerSample
+ sizeof(WAVEFORMATEX) // cbSize
+};
+
+static bool Voice_SetSampleRate( DWORD rate )
+{
+ if ( g_VoiceSampleFormat.nSamplesPerSec != rate ||
+ g_VoiceSampleFormat.nAvgBytesPerSec != rate * 2 )
+ {
+ g_VoiceSampleFormat.nSamplesPerSec = rate;
+ g_VoiceSampleFormat.nAvgBytesPerSec = rate * 2;
+ return true;
+ }
+
+ return false;
+}
+
+int Voice_SamplesPerSec()
+{
+ int rate = g_VoiceSampleFormat.nSamplesPerSec;
+ EngineTool_OverrideSampleRate( rate );
+ return rate;
+}
+
+int Voice_AvgBytesPerSec()
+{
+ int rate = g_VoiceSampleFormat.nSamplesPerSec;
+ EngineTool_OverrideSampleRate( rate );
+ return ( rate * g_VoiceSampleFormat.wBitsPerSample ) >> 3;
+}
+
+ConVar voice_avggain( "voice_avggain", "0.5" );
+ConVar voice_maxgain( "voice_maxgain", "10" );
+ConVar voice_scale( "voice_scale", "1", FCVAR_ARCHIVE );
+
+ConVar voice_loopback( "voice_loopback", "0", FCVAR_USERINFO );
+ConVar voice_fadeouttime( "voice_fadeouttime", "0.1" ); // It fades to no sound at the tail end of your voice data when you release the key.
+
+// Debugging cvars.
+ConVar voice_profile( "voice_profile", "0" );
+ConVar voice_showchannels( "voice_showchannels", "0" ); // 1 = list channels
+ // 2 = show timing info, etc
+ConVar voice_showincoming( "voice_showincoming", "0" ); // show incoming voice data
+
+ConVar voice_enable( "voice_enable", "1", FCVAR_ARCHIVE ); // Globally enable or disable voice.
+#ifdef VOICE_VOX_ENABLE
+ConVar voice_threshold( "voice_threshold", "2000", FCVAR_ARCHIVE );
+#endif // VOICE_VOX_ENABLE
+
+// Have it force your mixer control settings so waveIn comes from the microphone.
+// CD rippers change your waveIn to come from the CD drive
+ConVar voice_forcemicrecord( "voice_forcemicrecord", "1", FCVAR_ARCHIVE );
+
+// This should not be lower than the maximum difference between clients' frame durations (due to cmdrate/updaterate),
+// plus some jitter allowance.
+ConVar voice_buffer_ms( "voice_buffer_ms", "100", FCVAR_INTERNAL_USE,
+ "How many milliseconds of voice to buffer to avoid dropouts due to jitter and frame time differences." );
+
+int g_nVoiceFadeSamples = 1; // Calculated each frame from the cvar.
+float g_VoiceFadeMul = 1; // 1 / (g_nVoiceFadeSamples - 1).
+
+// While in tweak mode, you can't hear anything anyone else is saying, and your own voice data
+// goes directly to the speakers.
+bool g_bInTweakMode = false;
+int g_VoiceTweakSpeakingVolume = 0;
+
+bool g_bVoiceAtLeastPartiallyInitted = false;
+
+// The codec and sample rate passed to Voice_Init. "" and -1 if voice is not initialized
+char g_szVoiceCodec[_MAX_PATH] = { 0 };
+int g_nVoiceRequestedSampleRate = -1;
+
+const char *Voice_ConfiguredCodec() { return g_szVoiceCodec; }
+int Voice_ConfiguredSampleRate() { return g_nVoiceRequestedSampleRate; }
+
+// Timing info for each frame.
+static double g_CompressTime = 0;
+static double g_DecompressTime = 0;
+static double g_GainTime = 0;
+static double g_UpsampleTime = 0;
+
+class CVoiceTimer
+{
+public:
+ inline void Start()
+ {
+ if( voice_profile.GetInt() )
+ {
+ m_StartTime = Plat_FloatTime();
+ }
+ }
+
+ inline void End(double *out)
+ {
+ if( voice_profile.GetInt() )
+ {
+ *out += Plat_FloatTime() - m_StartTime;
+ }
+ }
+
+ double m_StartTime;
+};
+
+
+static bool g_bLocalPlayerTalkingAck = false;
+static float g_LocalPlayerTalkingTimeout = 0;
+
+
+CSysModule *g_hVoiceCodecDLL = 0;
+
+// Voice recorder. Can be waveIn, DSound, or whatever.
+static IVoiceRecord *g_pVoiceRecord = NULL;
+static IVoiceCodec *g_pEncodeCodec = NULL;
+
+static bool g_bVoiceRecording = false; // Are we recording at the moment?
+static bool g_bVoiceRecordStopping = false; // Are we waiting to stop?
+bool g_bUsingSteamVoice = false;
+
+#ifdef WIN32
+extern IVoiceRecord* CreateVoiceRecord_DSound(int nSamplesPerSec);
+#elif defined( OSX )
+extern IVoiceRecord* CreateVoiceRecord_AudioQueue(int sampleRate);
+#endif
+
+#ifdef POSIX
+extern IVoiceRecord* CreateVoiceRecord_OpenAL(int sampleRate);
+#endif
+
+
+static bool VoiceRecord_Start()
+{
+ if ( g_bUsingSteamVoice )
+ {
+ if ( steamapicontext && steamapicontext->SteamUser() )
+ {
+ steamapicontext->SteamUser()->StartVoiceRecording();
+ return true;
+ }
+ }
+ else if ( g_pVoiceRecord )
+ {
+ return g_pVoiceRecord->RecordStart();
+ }
+ return false;
+}
+
+static void VoiceRecord_Stop()
+{
+ if ( g_bUsingSteamVoice )
+ {
+ if ( steamapicontext && steamapicontext->SteamUser() )
+ {
+ steamapicontext->SteamUser()->StopVoiceRecording();
+ }
+ }
+ else if ( g_pVoiceRecord )
+ {
+ return g_pVoiceRecord->RecordStop();
+ }
+}
+
+//
+// Used for storing incoming voice data from an entity.
+//
+class CVoiceChannel
+{
+public:
+ CVoiceChannel();
+
+ // Called when someone speaks and a new voice channel is setup to hold the data.
+ void Init(int nEntity);
+
+public:
+ int m_iEntity; // Number of the entity speaking on this channel (index into cl_entities).
+ // This is -1 when the channel is unused.
+
+
+ CSizedCircularBuffer
+ <VOICE_RECEIVE_BUFFER_SIZE> m_Buffer; // Circular buffer containing the voice data.
+
+ // Used for upsampling..
+ double m_LastFraction; // Fraction between m_LastSample and the next sample.
+ short m_LastSample;
+
+ bool m_bStarved; // Set to true when the channel runs out of data for the mixer.
+ // The channel is killed at that point.
+
+ float m_TimePad; // Set to TIME_PADDING when the first voice packet comes in from a sender.
+ // We add time padding (for frametime differences)
+ // by waiting a certain amount of time before starting to output the sound.
+
+ IVoiceCodec *m_pVoiceCodec; // Each channel gets is own IVoiceCodec instance so the codec can maintain state.
+
+ CAutoGain m_AutoGain; // Gain we're applying to this channel
+
+ CVoiceChannel *m_pNext;
+
+ bool m_bProximity;
+ int m_nViewEntityIndex;
+ int m_nSoundGuid;
+};
+
+
+CVoiceChannel::CVoiceChannel()
+{
+ m_iEntity = -1;
+ m_pVoiceCodec = NULL;
+ m_nViewEntityIndex = -1;
+ m_nSoundGuid = -1;
+}
+
+void CVoiceChannel::Init(int nEntity)
+{
+ m_iEntity = nEntity;
+ m_bStarved = false;
+ m_Buffer.Flush();
+ m_TimePad = Clamp( voice_buffer_ms.GetFloat(), 1.f, 5000.f ) / 1000.f;
+ m_LastSample = 0;
+ m_LastFraction = 0.999;
+
+ m_AutoGain.Reset( 128, voice_maxgain.GetFloat(), voice_avggain.GetFloat(), voice_scale.GetFloat() );
+}
+
+
+
+// Incoming voice channels.
+CVoiceChannel g_VoiceChannels[VOICE_NUM_CHANNELS];
+
+
+// These are used for recording the wave data into files for debugging.
+#define MAX_WAVEFILEDATA_LEN 1024*1024
+char *g_pUncompressedFileData = NULL;
+int g_nUncompressedDataBytes = 0;
+const char *g_pUncompressedDataFilename = NULL;
+
+char *g_pDecompressedFileData = NULL;
+int g_nDecompressedDataBytes = 0;
+const char *g_pDecompressedDataFilename = NULL;
+
+char *g_pMicInputFileData = NULL;
+int g_nMicInputFileBytes = 0;
+int g_CurMicInputFileByte = 0;
+double g_MicStartTime;
+
+static ConVar voice_writevoices( "voice_writevoices", "0", 0, "Saves each speaker's voice data into separate .wav files\n" );
+class CVoiceWriterData
+{
+public:
+ CVoiceWriterData() :
+ m_pChannel( NULL ),
+ m_nCount( 0 ),
+ m_Buffer()
+ {
+ }
+
+ // Copy ctor is needed to insert into CVoiceWriter's CUtlRBTree.
+ CVoiceWriterData(const CVoiceWriterData& other) :
+ m_pChannel( other.m_pChannel ),
+ m_nCount( other.m_nCount ),
+ m_Buffer( )
+ {
+ m_Buffer.CopyBuffer( other.m_Buffer );
+ }
+
+ static bool Less( const CVoiceWriterData &lhs, const CVoiceWriterData &rhs )
+ {
+ return lhs.m_pChannel < rhs.m_pChannel;
+ }
+
+ CVoiceChannel *m_pChannel;
+ int m_nCount;
+ CUtlBuffer m_Buffer;
+
+private:
+ CVoiceWriterData& operator=(const CVoiceWriterData&);
+};
+
+class CVoiceWriter
+{
+public:
+ CVoiceWriter() :
+ m_VoiceWriter( 0, 0, CVoiceWriterData::Less )
+ {
+ }
+
+ void Flush()
+ {
+ for ( int i = m_VoiceWriter.FirstInorder(); i != m_VoiceWriter.InvalidIndex(); i = m_VoiceWriter.NextInorder( i ) )
+ {
+ CVoiceWriterData *data = &m_VoiceWriter[ i ];
+
+ if ( data->m_Buffer.TellPut() <= 0 )
+ continue;
+ data->m_Buffer.Purge();
+ }
+ }
+
+ void Finish()
+ {
+ if ( !g_pSoundServices->IsConnected() )
+ {
+ Flush();
+ return;
+ }
+
+ for ( int i = m_VoiceWriter.FirstInorder(); i != m_VoiceWriter.InvalidIndex(); i = m_VoiceWriter.NextInorder( i ) )
+ {
+ CVoiceWriterData *data = &m_VoiceWriter[ i ];
+
+ if ( data->m_Buffer.TellPut() <= 0 )
+ continue;
+
+ int index = data->m_pChannel - g_VoiceChannels;
+ Assert( index >= 0 && index < (int)ARRAYSIZE( g_VoiceChannels ) );
+
+ char path[ MAX_PATH ];
+ Q_snprintf( path, sizeof( path ), "%s/voice", g_pSoundServices->GetGameDir() );
+ g_pFileSystem->CreateDirHierarchy( path );
+
+ char fn[ MAX_PATH ];
+ Q_snprintf( fn, sizeof( fn ), "%s/pl%02d_slot%d-time%d.wav", path, index, data->m_nCount, (int)g_pSoundServices->GetClientTime() );
+
+ WriteWaveFile( fn, (const char *)data->m_Buffer.Base(), data->m_Buffer.TellPut(), g_VoiceSampleFormat.wBitsPerSample, g_VoiceSampleFormat.nChannels, Voice_SamplesPerSec() );
+
+ Msg( "Writing file %s\n", fn );
+
+ ++data->m_nCount;
+ data->m_Buffer.Purge();
+ }
+ }
+
+
+ void AddDecompressedData( CVoiceChannel *ch, const byte *data, size_t datalen )
+ {
+ if ( !voice_writevoices.GetBool() )
+ return;
+
+ CVoiceWriterData search;
+ search.m_pChannel = ch;
+ int idx = m_VoiceWriter.Find( search );
+ if ( idx == m_VoiceWriter.InvalidIndex() )
+ {
+ idx = m_VoiceWriter.Insert( search );
+ }
+
+ CVoiceWriterData *slot = &m_VoiceWriter[ idx ];
+ slot->m_Buffer.Put( data, datalen );
+ }
+private:
+
+ CUtlRBTree< CVoiceWriterData > m_VoiceWriter;
+};
+
+static CVoiceWriter g_VoiceWriter;
+
+inline void ApplyFadeToSamples(short *pSamples, int nSamples, int fadeOffset, float fadeMul)
+{
+ for(int i=0; i < nSamples; i++)
+ {
+ float percent = (i+fadeOffset) * fadeMul;
+ pSamples[i] = (short)(pSamples[i] * (1 - percent));
+ }
+}
+
+
+bool Voice_Enabled( void )
+{
+ return voice_enable.GetBool();
+}
+
+
+int Voice_GetOutputData(
+ const int iChannel, //! The voice channel it wants samples from.
+ char *copyBufBytes, //! The buffer to copy the samples into.
+ const int copyBufSize, //! Maximum size of copyBuf.
+ const int samplePosition, //! Which sample to start at.
+ const int sampleCount //! How many samples to get.
+)
+{
+ CVoiceChannel *pChannel = &g_VoiceChannels[iChannel];
+ short *pCopyBuf = (short*)copyBufBytes;
+
+
+ int maxOutSamples = copyBufSize / BYTES_PER_SAMPLE;
+
+ // Find out how much we want and get it from the received data channel.
+ CCircularBuffer *pBuffer = &pChannel->m_Buffer;
+ int nBytesToRead = pBuffer->GetReadAvailable();
+ nBytesToRead = min(min(nBytesToRead, (int)maxOutSamples), sampleCount * BYTES_PER_SAMPLE);
+ int nSamplesGotten = pBuffer->Read(pCopyBuf, nBytesToRead) / BYTES_PER_SAMPLE;
+
+ // Are we at the end of the buffer's data? If so, fade data to silence so it doesn't clip.
+ int readSamplesAvail = pBuffer->GetReadAvailable() / BYTES_PER_SAMPLE;
+ if(readSamplesAvail < g_nVoiceFadeSamples)
+ {
+ int bufferFadeOffset = max((readSamplesAvail + nSamplesGotten) - g_nVoiceFadeSamples, 0);
+ int globalFadeOffset = max(g_nVoiceFadeSamples - (readSamplesAvail + nSamplesGotten), 0);
+
+ ApplyFadeToSamples(
+ &pCopyBuf[bufferFadeOffset],
+ nSamplesGotten - bufferFadeOffset,
+ globalFadeOffset,
+ g_VoiceFadeMul);
+ }
+
+ // If there weren't enough samples in the received data channel,
+ // pad it with a copy of the most recent data, and if there
+ // isn't any, then use zeros.
+ if ( nSamplesGotten < sampleCount )
+ {
+ int wantedSampleCount = min( sampleCount, maxOutSamples );
+ int nAdditionalNeeded = (wantedSampleCount - nSamplesGotten);
+ if ( nSamplesGotten > 0 )
+ {
+ short *dest = (short *)&pCopyBuf[ nSamplesGotten ];
+ int nSamplesToDuplicate = min( nSamplesGotten, nAdditionalNeeded );
+ const short *src = (short *)&pCopyBuf[ nSamplesGotten - nSamplesToDuplicate ];
+
+ Q_memcpy( dest, src, nSamplesToDuplicate * BYTES_PER_SAMPLE );
+
+ //Msg( "duplicating %d samples\n", nSamplesToDuplicate );
+
+ nAdditionalNeeded -= nSamplesToDuplicate;
+ if ( nAdditionalNeeded > 0 )
+ {
+ dest = (short *)&pCopyBuf[ nSamplesGotten + nSamplesToDuplicate ];
+ Q_memset(dest, 0, nAdditionalNeeded * BYTES_PER_SAMPLE);
+
+ // Msg( "zeroing %d samples\n", nAdditionalNeeded );
+
+ Assert( ( nAdditionalNeeded + nSamplesGotten + nSamplesToDuplicate ) == wantedSampleCount );
+ }
+ }
+ else
+ {
+ Q_memset( &pCopyBuf[ nSamplesGotten ], 0, nAdditionalNeeded * BYTES_PER_SAMPLE );
+ }
+ nSamplesGotten = wantedSampleCount;
+ }
+
+ // If the buffer is out of data, mark this channel to go away.
+ if(pBuffer->GetReadAvailable() == 0)
+ {
+ pChannel->m_bStarved = true;
+ }
+
+ if(voice_showchannels.GetInt() >= 2)
+ {
+ Msg("Voice - mixed %d samples from channel %d\n", nSamplesGotten, iChannel);
+ }
+
+ VoiceSE_MoveMouth(pChannel->m_iEntity, (short*)copyBufBytes, nSamplesGotten);
+ return nSamplesGotten;
+}
+
+
+void Voice_OnAudioSourceShutdown( int iChannel )
+{
+ Voice_EndChannel( iChannel );
+}
+
+
+// ------------------------------------------------------------------------ //
+// Internal stuff.
+// ------------------------------------------------------------------------ //
+
+CVoiceChannel* GetVoiceChannel(int iChannel, bool bAssert=true)
+{
+ if(iChannel < 0 || iChannel >= VOICE_NUM_CHANNELS)
+ {
+ if(bAssert)
+ {
+ Assert(false);
+ }
+ return NULL;
+ }
+ else
+ {
+ return &g_VoiceChannels[iChannel];
+ }
+}
+
+// Helper for doing a default-init with some codec if we weren't passed specific parameters
+bool Voice_InitWithDefault( const char *pCodecName )
+{
+ if ( !pCodecName || !*pCodecName )
+ {
+ return false;
+ }
+
+ int nRate = Voice_GetDefaultSampleRate( pCodecName );
+ if ( nRate < 0 )
+ {
+ Msg( "Voice_InitWithDefault: Unable to determine defaults for codec \"%s\"\n", pCodecName );
+ return false;
+ }
+
+ return Voice_Init( pCodecName, Voice_GetDefaultSampleRate( pCodecName ) );
+}
+
+bool Voice_Init( const char *pCodecName, int nSampleRate )
+{
+ if ( voice_enable.GetInt() == 0 )
+ {
+ return false;
+ }
+
+ if ( !pCodecName || !pCodecName[0] )
+ {
+ return false;
+ }
+
+ bool bSpeex = Q_stricmp( pCodecName, "vaudio_speex" ) == 0;
+ bool bCelt = Q_stricmp( pCodecName, "vaudio_celt" ) == 0;
+ bool bSteam = Q_stricmp( pCodecName, "steam" ) == 0;
+ // Miles has not been in use for voice in a long long time. Not worth the surface to support ancient demos that may
+ // use it (and probably do not work for other reasons)
+ // "vaudio_miles"
+
+ if ( !( bSpeex || bCelt || bSteam ) )
+ {
+ Msg( "Voice_Init Failed: invalid voice codec %s.\n", pCodecName );
+ return false;
+ }
+
+ Voice_Deinit();
+
+ g_bVoiceAtLeastPartiallyInitted = true;
+ V_strncpy( g_szVoiceCodec, pCodecName, sizeof(g_szVoiceCodec) );
+ g_nVoiceRequestedSampleRate = nSampleRate;
+
+ g_bUsingSteamVoice = bSteam;
+
+ if ( !steamapicontext )
+ {
+ steamapicontext = &g_SteamAPIContext;
+ steamapicontext->Init();
+ }
+
+ if ( g_bUsingSteamVoice )
+ {
+ if ( !steamapicontext->SteamFriends() || !steamapicontext->SteamUser() )
+ {
+ Msg( "Voice_Init: Requested Steam voice, but cannot access API. Voice will not function\n" );
+ return false;
+ }
+ }
+
+ // For steam, nSampleRate 0 means "use optimal steam sample rate".
+ if ( bSteam && nSampleRate == 0 )
+ {
+ Msg( "Voice_Init: Using Steam voice optimal sample rate %d\n",
+ steamapicontext->SteamUser()->GetVoiceOptimalSampleRate() );
+ // Steam's sample rate may change and not be supported by our rather unflexible sound engine. However, steam
+ // will resample as necessary in DecompressVoice, so we can pretend we're outputting at native rates.
+ //
+ // Behind the scenes, we'll request steam give us the encoded stream at its "optimal" rate, then we'll try to
+ // decompress the output at this rate, making it transparent to us that the encoded stream is not at our output
+ // rate.
+ Voice_SetSampleRate( SOUND_DMA_SPEED );
+ }
+ else
+ {
+ Voice_SetSampleRate( nSampleRate );
+ }
+
+ if(!VoiceSE_Init())
+ return false;
+
+ // Get the voice input device.
+#ifdef OSX
+ g_pVoiceRecord = CreateVoiceRecord_AudioQueue( Voice_SamplesPerSec() );
+ if ( !g_pVoiceRecord )
+ {
+ // Fall back to OpenAL
+ g_pVoiceRecord = CreateVoiceRecord_OpenAL( Voice_SamplesPerSec() );
+ }
+#elif defined( WIN32 )
+ g_pVoiceRecord = CreateVoiceRecord_DSound( Voice_SamplesPerSec() );
+#else
+ g_pVoiceRecord = CreateVoiceRecord_OpenAL( Voice_SamplesPerSec() );
+#endif
+
+ if( !g_pVoiceRecord )
+ {
+ Msg( "Unable to initialize sound capture. You won't be able to speak to other players." );
+ }
+
+ // Init codec DLL for non-steam
+ if ( !bSteam )
+ {
+ // CELT's qualities are 0-3, we historically just passed 4 to the other two even though they don't really map to the
+ // same thing.
+ //
+ // Changing the quality level we use here will require either extending SVC_VoiceInit to pass down which quality is
+ // in use or using a different codec name (vaudio_celtHD!) for backwards compatibility
+ int quality = bCelt ? 3 : 4;
+
+ // Get the codec.
+ CreateInterfaceFn createCodecFn = NULL;
+ g_hVoiceCodecDLL = FileSystem_LoadModule(pCodecName);
+
+ if ( !g_hVoiceCodecDLL || (createCodecFn = Sys_GetFactory(g_hVoiceCodecDLL)) == NULL ||
+ (g_pEncodeCodec = (IVoiceCodec*)createCodecFn(pCodecName, NULL)) == NULL || !g_pEncodeCodec->Init( quality ) )
+ {
+ Msg("Unable to load voice codec '%s'. Voice disabled. (module %i, iface %i, codec %i)\n",
+ pCodecName, !!g_hVoiceCodecDLL, !!createCodecFn, !!g_pEncodeCodec);
+ Voice_Deinit();
+ return false;
+ }
+
+ for (int i=0; i < VOICE_NUM_CHANNELS; i++)
+ {
+ CVoiceChannel *pChannel = &g_VoiceChannels[i];
+
+ if ((pChannel->m_pVoiceCodec = (IVoiceCodec*)createCodecFn(pCodecName, NULL)) == NULL || !pChannel->m_pVoiceCodec->Init( quality ))
+ {
+ Voice_Deinit();
+ return false;
+ }
+ }
+ }
+
+ // XXX(JohnS): These don't do much in Steam codec mode, but code below uses their presence to mean 'voice fully
+ // initialized' and other things assume they will succeed.
+ InitMixerControls();
+
+ // Steam mode uses steam for raw input so this isn't meaningful and could have side-effects
+ if( voice_forcemicrecord.GetInt() && !bSteam )
+ {
+ if( g_pMixerControls )
+ g_pMixerControls->SelectMicrophoneForWaveInput();
+ }
+
+ return true;
+}
+
+
+void Voice_EndChannel(int iChannel)
+{
+ Assert(iChannel >= 0 && iChannel < VOICE_NUM_CHANNELS);
+
+ CVoiceChannel *pChannel = &g_VoiceChannels[iChannel];
+
+ if ( pChannel->m_iEntity != -1 )
+ {
+ int iEnt = pChannel->m_iEntity;
+ pChannel->m_iEntity = -1;
+
+ if ( pChannel->m_bProximity == true )
+ {
+ VoiceSE_EndChannel( iChannel, iEnt );
+ }
+ else
+ {
+ VoiceSE_EndChannel( iChannel, pChannel->m_nViewEntityIndex );
+ }
+
+ g_pSoundServices->OnChangeVoiceStatus( iEnt, false );
+ VoiceSE_CloseMouth( iEnt );
+
+ pChannel->m_nViewEntityIndex = -1;
+ pChannel->m_nSoundGuid = -1;
+
+ // If the tweak mode channel is ending
+ if ( iChannel == 0 &&
+ g_bInTweakMode )
+ {
+ VoiceTweak_EndVoiceTweakMode();
+ }
+ }
+}
+
+
+void Voice_EndAllChannels()
+{
+ for(int i=0; i < VOICE_NUM_CHANNELS; i++)
+ {
+ Voice_EndChannel(i);
+ }
+}
+
+bool EngineTool_SuppressDeInit();
+
+void Voice_Deinit()
+{
+ // This call tends to be expensive and when voice is not enabled it will continually
+ // call in here, so avoid the work if possible.
+ if( !g_bVoiceAtLeastPartiallyInitted )
+ return;
+
+ if ( EngineTool_SuppressDeInit() )
+ return;
+
+ Voice_EndAllChannels();
+
+ Voice_RecordStop();
+
+ for(int i=0; i < VOICE_NUM_CHANNELS; i++)
+ {
+ CVoiceChannel *pChannel = &g_VoiceChannels[i];
+
+ if ( pChannel->m_pVoiceCodec )
+ {
+ pChannel->m_pVoiceCodec->Release();
+ pChannel->m_pVoiceCodec = NULL;
+ }
+ }
+
+ if( g_pEncodeCodec )
+ {
+ g_pEncodeCodec->Release();
+ g_pEncodeCodec = NULL;
+ }
+
+ if(g_hVoiceCodecDLL)
+ {
+ FileSystem_UnloadModule(g_hVoiceCodecDLL);
+ g_hVoiceCodecDLL = NULL;
+ }
+
+ if(g_pVoiceRecord)
+ {
+ g_pVoiceRecord->Release();
+ g_pVoiceRecord = NULL;
+ }
+
+ VoiceSE_Term();
+
+ g_bVoiceAtLeastPartiallyInitted = false;
+ g_szVoiceCodec[0] = '\0';
+ g_nVoiceRequestedSampleRate = -1;
+ g_bUsingSteamVoice = false;
+}
+
+bool Voice_GetLoopback()
+{
+ return !!voice_loopback.GetInt();
+}
+
+
+void Voice_LocalPlayerTalkingAck()
+{
+ if(!g_bLocalPlayerTalkingAck)
+ {
+ // Tell the client DLL when this changes.
+ g_pSoundServices->OnChangeVoiceStatus(-2, TRUE);
+ }
+
+ g_bLocalPlayerTalkingAck = true;
+ g_LocalPlayerTalkingTimeout = 0;
+}
+
+
+void Voice_UpdateVoiceTweakMode()
+{
+ if(!g_bInTweakMode || !g_pVoiceRecord)
+ return;
+
+ CVoiceChannel *pTweakChannel = GetVoiceChannel( 0 );
+
+ if ( pTweakChannel->m_nSoundGuid != -1 &&
+ !S_IsSoundStillPlaying( pTweakChannel->m_nSoundGuid ) )
+ {
+ VoiceTweak_EndVoiceTweakMode();
+ return;
+ }
+
+ char uchVoiceData[4096];
+ bool bFinal = false;
+ int nDataLength = Voice_GetCompressedData(uchVoiceData, sizeof(uchVoiceData), bFinal);
+
+ Voice_AddIncomingData(TWEAKMODE_CHANNELINDEX, uchVoiceData, nDataLength, 0);
+}
+
+
+void Voice_Idle(float frametime)
+{
+ if( voice_enable.GetInt() == 0 )
+ {
+ Voice_Deinit();
+ return;
+ }
+
+ if( g_bLocalPlayerTalkingAck )
+ {
+ g_LocalPlayerTalkingTimeout += frametime;
+ if(g_LocalPlayerTalkingTimeout > LOCALPLAYERTALKING_TIMEOUT)
+ {
+ g_bLocalPlayerTalkingAck = false;
+
+ // Tell the client DLL.
+ g_pSoundServices->OnChangeVoiceStatus(-2, FALSE);
+ }
+ }
+
+ // Precalculate these to speedup the voice fadeout.
+ g_nVoiceFadeSamples = max((int)(voice_fadeouttime.GetFloat() * g_VoiceSampleFormat.nSamplesPerSec ), 2);
+ g_VoiceFadeMul = 1.0f / (g_nVoiceFadeSamples - 1);
+
+ if(g_pVoiceRecord)
+ g_pVoiceRecord->Idle();
+
+ // If we're in voice tweak mode, feed our own data back to us.
+ Voice_UpdateVoiceTweakMode();
+
+ // Age the channels.
+ int nActive = 0;
+ for(int i=0; i < VOICE_NUM_CHANNELS; i++)
+ {
+ CVoiceChannel *pChannel = &g_VoiceChannels[i];
+
+ if(pChannel->m_iEntity != -1)
+ {
+ if(pChannel->m_bStarved)
+ {
+ // Kill the channel. It's done playing.
+ Voice_EndChannel(i);
+ pChannel->m_nSoundGuid = -1;
+ }
+ else
+ {
+ float oldpad = pChannel->m_TimePad;
+ pChannel->m_TimePad -= frametime;
+ if(oldpad > 0 && pChannel->m_TimePad <= 0)
+ {
+ // Start its audio.
+ pChannel->m_nViewEntityIndex = g_pSoundServices->GetViewEntity();
+ pChannel->m_nSoundGuid = VoiceSE_StartChannel( i, pChannel->m_iEntity, pChannel->m_bProximity, pChannel->m_nViewEntityIndex );
+ g_pSoundServices->OnChangeVoiceStatus(pChannel->m_iEntity, TRUE);
+
+ VoiceSE_InitMouth(pChannel->m_iEntity);
+ }
+
+ ++nActive;
+ }
+ }
+ }
+
+ if(nActive == 0)
+ VoiceSE_EndOverdrive();
+
+ VoiceSE_Idle(frametime);
+
+ // voice_showchannels.
+ if( voice_showchannels.GetInt() >= 1 )
+ {
+ for(int i=0; i < VOICE_NUM_CHANNELS; i++)
+ {
+ CVoiceChannel *pChannel = &g_VoiceChannels[i];
+
+ if(pChannel->m_iEntity == -1)
+ continue;
+
+ Msg("Voice - chan %d, ent %d, bufsize: %d\n", i, pChannel->m_iEntity, pChannel->m_Buffer.GetReadAvailable());
+ }
+ }
+
+ // Show profiling data?
+ if( voice_profile.GetInt() )
+ {
+ Msg("Voice - compress: %7.2fu, decompress: %7.2fu, gain: %7.2fu, upsample: %7.2fu, total: %7.2fu\n",
+ g_CompressTime*1000000.0,
+ g_DecompressTime*1000000.0,
+ g_GainTime*1000000.0,
+ g_UpsampleTime*1000000.0,
+ (g_CompressTime+g_DecompressTime+g_GainTime+g_UpsampleTime)*1000000.0
+ );
+
+ g_CompressTime = g_DecompressTime = g_GainTime = g_UpsampleTime = 0;
+ }
+}
+
+
+bool Voice_IsRecording()
+{
+ return g_bVoiceRecording && !g_bInTweakMode;
+}
+
+
+bool Voice_RecordStart(
+ const char *pUncompressedFile,
+ const char *pDecompressedFile,
+ const char *pMicInputFile)
+{
+ if( !g_pEncodeCodec && !g_bUsingSteamVoice )
+ return false;
+
+ g_VoiceWriter.Flush();
+
+ Voice_RecordStop();
+
+ if ( !g_bUsingSteamVoice )
+ {
+ g_pEncodeCodec->ResetState();
+ }
+
+ if(pMicInputFile)
+ {
+ int a, b, c;
+ ReadWaveFile(pMicInputFile, g_pMicInputFileData, g_nMicInputFileBytes, a, b, c);
+ g_CurMicInputFileByte = 0;
+ g_MicStartTime = Plat_FloatTime();
+ }
+
+ if(pUncompressedFile)
+ {
+ g_pUncompressedFileData = new char[MAX_WAVEFILEDATA_LEN];
+ g_nUncompressedDataBytes = 0;
+ g_pUncompressedDataFilename = pUncompressedFile;
+ }
+
+ if(pDecompressedFile)
+ {
+ g_pDecompressedFileData = new char[MAX_WAVEFILEDATA_LEN];
+ g_nDecompressedDataBytes = 0;
+ g_pDecompressedDataFilename = pDecompressedFile;
+ }
+
+ g_bVoiceRecording = false;
+ if ( g_pVoiceRecord )
+ {
+ g_bVoiceRecording = VoiceRecord_Start();
+ if ( g_bVoiceRecording )
+ {
+ if ( steamapicontext && steamapicontext->SteamFriends() && steamapicontext->SteamUser() )
+ {
+ // Tell Friends' Voice chat that the local user has started speaking
+ steamapicontext->SteamFriends()->SetInGameVoiceSpeaking( steamapicontext->SteamUser()->GetSteamID(), true );
+ }
+
+ g_pSoundServices->OnChangeVoiceStatus( -1, true ); // Tell the client DLL.
+ }
+ }
+
+ return g_bVoiceRecording;
+}
+
+
+void Voice_UserDesiresStop()
+{
+ if ( g_bVoiceRecordStopping )
+ return;
+
+ g_bVoiceRecordStopping = true;
+ g_pSoundServices->OnChangeVoiceStatus( -1, false ); // Tell the client DLL.
+
+ // If we're using Steam voice, we'll keep recording until Steam tells us we
+ // received all the data.
+ if ( g_bUsingSteamVoice )
+ {
+ steamapicontext->SteamUser()->StopVoiceRecording();
+ }
+ else
+ {
+ VoiceRecord_Stop();
+ }
+}
+
+
+bool Voice_RecordStop()
+{
+ // Write the files out for debugging.
+ if(g_pMicInputFileData)
+ {
+ delete [] g_pMicInputFileData;
+ g_pMicInputFileData = NULL;
+ }
+
+ if(g_pUncompressedFileData)
+ {
+ WriteWaveFile(g_pUncompressedDataFilename, g_pUncompressedFileData, g_nUncompressedDataBytes, g_VoiceSampleFormat.wBitsPerSample, g_VoiceSampleFormat.nChannels, Voice_SamplesPerSec() );
+ delete [] g_pUncompressedFileData;
+ g_pUncompressedFileData = NULL;
+ }
+
+ if(g_pDecompressedFileData)
+ {
+ WriteWaveFile(g_pDecompressedDataFilename, g_pDecompressedFileData, g_nDecompressedDataBytes, g_VoiceSampleFormat.wBitsPerSample, g_VoiceSampleFormat.nChannels, Voice_SamplesPerSec() );
+ delete [] g_pDecompressedFileData;
+ g_pDecompressedFileData = NULL;
+ }
+
+ g_VoiceWriter.Finish();
+
+ VoiceRecord_Stop();
+
+ if ( g_bVoiceRecording )
+ {
+ if ( steamapicontext->SteamFriends() && steamapicontext->SteamUser() )
+ {
+ // Tell Friends' Voice chat that the local user has stopped speaking
+ steamapicontext->SteamFriends()->SetInGameVoiceSpeaking( steamapicontext->SteamUser()->GetSteamID(), false );
+ }
+ }
+
+ g_bVoiceRecording = false;
+ g_bVoiceRecordStopping = false;
+ return(true);
+}
+
+
+int Voice_GetCompressedData(char *pchDest, int nCount, bool bFinal)
+{
+ // Check g_bVoiceRecordStopping in case g_bUsingSteamVoice changes on us
+ // while waiting for the end of voice data.
+ if ( g_bUsingSteamVoice || g_bVoiceRecordStopping )
+ {
+ uint32 cbCompressedWritten = 0;
+ uint32 cbUncompressedWritten = 0;
+ uint32 cbCompressed = 0;
+ uint32 cbUncompressed = 0;
+ // We're going to always request steam give us the encoded stream at the optimal rate, unless our final output
+ // rate is lower than it. We'll pass our output rate when we actually extract the data, which Steam will
+ // happily upsample from its optimal rate for us.
+ int nEncodeRate = min( (int)steamapicontext->SteamUser()->GetVoiceOptimalSampleRate(), Voice_SamplesPerSec() );
+ EVoiceResult result = steamapicontext->SteamUser()->GetAvailableVoice( &cbCompressed, &cbUncompressed, nEncodeRate );
+ if ( result == k_EVoiceResultOK )
+ {
+ result = steamapicontext->SteamUser()->GetVoice( true, pchDest, nCount, &cbCompressedWritten,
+ g_pUncompressedFileData != NULL, g_pUncompressedFileData,
+ MAX_WAVEFILEDATA_LEN - g_nUncompressedDataBytes,
+ &cbUncompressedWritten, nEncodeRate );
+
+ if ( g_pUncompressedFileData )
+ {
+ g_nUncompressedDataBytes += cbUncompressedWritten;
+ }
+ g_pSoundServices->OnChangeVoiceStatus( -3, true );
+ }
+ else
+ {
+ if ( result == k_EVoiceResultNotRecording && g_bVoiceRecording )
+ {
+ Voice_RecordStop();
+ }
+
+ g_pSoundServices->OnChangeVoiceStatus( -3, false );
+ }
+ return cbCompressedWritten;
+ }
+
+ IVoiceCodec *pCodec = g_pEncodeCodec;
+ if( g_pVoiceRecord && pCodec )
+ {
+#ifdef VOICE_VOX_ENABLE
+ static ConVarRef voice_vox( "voice_vox" );
+#endif // VOICE_VOX_ENABLE
+
+ short tempData[8192];
+ int samplesWanted = min(nCount/BYTES_PER_SAMPLE, (int)sizeof(tempData)/BYTES_PER_SAMPLE);
+ int gotten = g_pVoiceRecord->GetRecordedData(tempData, samplesWanted);
+
+ // If they want to get the data from a file instead of the mic, use that.
+ if(g_pMicInputFileData)
+ {
+ double curtime = Plat_FloatTime();
+ int nShouldGet = (curtime - g_MicStartTime) * Voice_SamplesPerSec();
+ gotten = min(sizeof(tempData)/BYTES_PER_SAMPLE,
+ (size_t)min(nShouldGet, (g_nMicInputFileBytes - g_CurMicInputFileByte) / BYTES_PER_SAMPLE));
+ memcpy(tempData, &g_pMicInputFileData[g_CurMicInputFileByte], gotten*BYTES_PER_SAMPLE);
+ g_CurMicInputFileByte += gotten * BYTES_PER_SAMPLE;
+ g_MicStartTime = curtime;
+ }
+#ifdef VOICE_VOX_ENABLE
+ else if ( gotten && voice_vox.GetBool() == true )
+ {
+ // If the voice data is essentially silent, don't transmit
+ short *pData = tempData;
+ int averageData = 0;
+ int minData = 16384;
+ int maxData = -16384;
+ for ( int i=0; i<gotten; ++i )
+ {
+ short val = *pData;
+ averageData += val;
+ minData = min( val, minData );
+ maxData = max( val, maxData );
+ ++pData;
+ }
+ averageData /= gotten;
+ int deltaData = maxData - minData;
+
+ if ( deltaData < voice_threshold.GetFloat() && maxData < voice_threshold.GetFloat() )
+ {
+ // -3 signals that we're silent
+ g_pSoundServices->OnChangeVoiceStatus( -3, false );
+ return 0;
+ }
+ }
+#endif // VOICE_VOX_ENABLE
+
+#ifdef VOICE_SEND_RAW_TEST
+ int nCompressedBytes = min( gotten, nCount );
+ for ( int i=0; i < nCompressedBytes; i++ )
+ {
+ pchDest[i] = (char)(tempData[i] >> 8);
+ }
+#else
+ int nCompressedBytes = pCodec->Compress((char*)tempData, gotten, pchDest, nCount, !!bFinal);
+#endif
+
+ // Write to our file buffers..
+ if(g_pUncompressedFileData)
+ {
+ int nToWrite = min(gotten*BYTES_PER_SAMPLE, MAX_WAVEFILEDATA_LEN - g_nUncompressedDataBytes);
+ memcpy(&g_pUncompressedFileData[g_nUncompressedDataBytes], tempData, nToWrite);
+ g_nUncompressedDataBytes += nToWrite;
+ }
+#ifdef VOICE_VOX_ENABLE
+ // -3 signals that we're talking
+ g_pSoundServices->OnChangeVoiceStatus( -3, (nCompressedBytes > 0) );
+#endif // VOICE_VOX_ENABLE
+ return nCompressedBytes;
+ }
+ else
+ {
+#ifdef VOICE_VOX_ENABLE
+ // -3 signals that we're silent
+ g_pSoundServices->OnChangeVoiceStatus( -3, false );
+#endif // VOICE_VOX_ENABLE
+ return 0;
+ }
+}
+
+
+//------------------ Copyright (c) 1999 Valve, LLC. ----------------------------
+// Purpose: Assigns a channel to an entity by searching for either a channel
+// already assigned to that entity or picking the least recently used
+// channel. If the LRU channel is picked, it is flushed and all other
+// channels are aged.
+// Input : nEntity - entity number to assign to a channel.
+// Output : A channel index to which the entity has been assigned.
+//------------------------------------------------------------------------------
+int Voice_AssignChannel(int nEntity, bool bProximity)
+{
+ if(g_bInTweakMode)
+ return VOICE_CHANNEL_IN_TWEAK_MODE;
+
+ // See if a channel already exists for this entity and if so, just return it.
+ int iFree = -1;
+ for(int i=0; i < VOICE_NUM_CHANNELS; i++)
+ {
+ CVoiceChannel *pChannel = &g_VoiceChannels[i];
+
+ if(pChannel->m_iEntity == nEntity)
+ {
+ return i;
+ }
+ else if(pChannel->m_iEntity == -1 && ( pChannel->m_pVoiceCodec || g_bUsingSteamVoice ) )
+ {
+ // Won't exist in steam voice mode
+ if ( pChannel->m_pVoiceCodec )
+ {
+ pChannel->m_pVoiceCodec->ResetState();
+ }
+ iFree = i;
+ break;
+ }
+ }
+
+ // If they're all used, then don't allow them to make a new channel.
+ if(iFree == -1)
+ {
+ return VOICE_CHANNEL_ERROR;
+ }
+
+ CVoiceChannel *pChannel = &g_VoiceChannels[iFree];
+ pChannel->Init(nEntity);
+ pChannel->m_bProximity = bProximity;
+ VoiceSE_StartOverdrive();
+
+ return iFree;
+}
+
+
+//------------------ Copyright (c) 1999 Valve, LLC. ----------------------------
+// Purpose: Determines which channel has been assigened to a given entity.
+// Input : nEntity - entity number.
+// Output : The index of the channel assigned to the entity, VOICE_CHANNEL_ERROR
+// if no channel is currently assigned to the given entity.
+//------------------------------------------------------------------------------
+int Voice_GetChannel(int nEntity)
+{
+ for(int i=0; i < VOICE_NUM_CHANNELS; i++)
+ if(g_VoiceChannels[i].m_iEntity == nEntity)
+ return i;
+
+ return VOICE_CHANNEL_ERROR;
+}
+
+
+double UpsampleIntoBuffer(
+ const short *pSrc,
+ int nSrcSamples,
+ CCircularBuffer *pBuffer,
+ double startFraction,
+ double rate)
+{
+ double maxFraction = nSrcSamples - 1;
+
+ while(1)
+ {
+ if(startFraction >= maxFraction)
+ break;
+
+ int iSample = (int)startFraction;
+ double frac = startFraction - floor(startFraction);
+
+ double val1 = pSrc[iSample];
+ double val2 = pSrc[iSample+1];
+ short newSample = (short)(val1 + (val2 - val1) * frac);
+ pBuffer->Write(&newSample, sizeof(newSample));
+
+ startFraction += rate;
+ }
+
+ return startFraction - floor(startFraction);
+}
+
+
+//------------------ Copyright (c) 1999 Valve, LLC. ----------------------------
+// Purpose: Adds received voice data to
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+int Voice_AddIncomingData(int nChannel, const char *pchData, int nCount, int iSequenceNumber)
+{
+ CVoiceChannel *pChannel;
+
+ // If in tweak mode, we call this during Idle with -1 as the channel, so any channel data from the network
+ // gets rejected.
+ if(g_bInTweakMode)
+ {
+ if(nChannel == TWEAKMODE_CHANNELINDEX)
+ nChannel = 0;
+ else
+ return 0;
+ }
+
+ if ( ( pChannel = GetVoiceChannel(nChannel)) == NULL || ( !g_bUsingSteamVoice && !pChannel->m_pVoiceCodec ) )
+ {
+ return(0);
+ }
+
+ pChannel->m_bStarved = false; // This only really matters if you call Voice_AddIncomingData between the time the mixer
+ // asks for data and Voice_Idle is called.
+
+ // Decompress.
+ // @note Tom Bui: suggested destination buffer for Steam voice is 22kb
+ char decompressed[22528];
+
+#ifdef VOICE_SEND_RAW_TEST
+
+ int nDecompressed = nCount;
+ for ( int i=0; i < nDecompressed; i++ )
+ ((short*)decompressed)[i] = pchData[i] << 8;
+
+#else
+
+ int nDecompressed = 0;
+ if ( g_bUsingSteamVoice )
+ {
+ uint32 nBytesWritten = 0;
+ EVoiceResult result = steamapicontext->SteamUser()->DecompressVoice( pchData, nCount,
+ decompressed, sizeof( decompressed ),
+ &nBytesWritten, Voice_SamplesPerSec() );
+ if ( result == k_EVoiceResultOK )
+ {
+ nDecompressed = nBytesWritten / BYTES_PER_SAMPLE;
+ }
+ }
+ else
+ {
+ nDecompressed = pChannel->m_pVoiceCodec->Decompress(pchData, nCount, decompressed, sizeof(decompressed));
+ }
+
+#endif
+
+ if ( g_bInTweakMode )
+ {
+ short *data = (short *)decompressed;
+ g_VoiceTweakSpeakingVolume = 0;
+
+ // Find the highest value
+ for ( int i=0; i<nDecompressed; ++i )
+ {
+ g_VoiceTweakSpeakingVolume = max((int)abs(data[i]), g_VoiceTweakSpeakingVolume);
+ }
+
+ // Smooth it out
+ g_VoiceTweakSpeakingVolume &= 0xFE00;
+ }
+
+ pChannel->m_AutoGain.ProcessSamples((short*)decompressed, nDecompressed);
+
+ // Upsample into the dest buffer. We could do this in a mixer but it complicates the mixer.
+ pChannel->m_LastFraction = UpsampleIntoBuffer( (short*)decompressed,
+ nDecompressed,
+ &pChannel->m_Buffer,
+ pChannel->m_LastFraction,
+ (double)Voice_SamplesPerSec()/g_VoiceSampleFormat.nSamplesPerSec );
+ pChannel->m_LastSample = decompressed[nDecompressed];
+
+ // Write to our file buffer..
+ if(g_pDecompressedFileData)
+ {
+ int nToWrite = min(nDecompressed*2, MAX_WAVEFILEDATA_LEN - g_nDecompressedDataBytes);
+ memcpy(&g_pDecompressedFileData[g_nDecompressedDataBytes], decompressed, nToWrite);
+ g_nDecompressedDataBytes += nToWrite;
+ }
+
+ g_VoiceWriter.AddDecompressedData( pChannel, (const byte *)decompressed, nDecompressed * 2 );
+
+ if( voice_showincoming.GetInt() != 0 )
+ {
+ Msg("Voice - %d incoming samples added to channel %d\n", nDecompressed, nChannel);
+ }
+
+ return(nChannel);
+}
+
+
+#if DEAD
+//------------------ Copyright (c) 1999 Valve, LLC. ----------------------------
+// Purpose: Flushes a given receive channel.
+// Input : nChannel - index of channel to flush.
+//------------------------------------------------------------------------------
+void Voice_FlushChannel(int nChannel)
+{
+ if ((nChannel < 0) || (nChannel >= VOICE_NUM_CHANNELS))
+ {
+ Assert(false);
+ return;
+ }
+
+ g_VoiceChannels[nChannel].m_Buffer.Flush();
+}
+#endif
+
+
+//------------------------------------------------------------------------------
+// IVoiceTweak implementation.
+//------------------------------------------------------------------------------
+
+int VoiceTweak_StartVoiceTweakMode()
+{
+ // If we're already in voice tweak mode, return an error.
+ if(g_bInTweakMode)
+ {
+ Assert(!"VoiceTweak_StartVoiceTweakMode called while already in tweak mode.");
+ return 0;
+ }
+
+ if ( !g_pMixerControls && voice_enable.GetBool() )
+ {
+ Voice_ForceInit();
+ }
+
+ if( !g_pMixerControls )
+ return 0;
+
+ Voice_EndAllChannels();
+ Voice_RecordStart(NULL, NULL, NULL);
+ Voice_AssignChannel(TWEAKMODE_ENTITYINDEX, false );
+ g_bInTweakMode = true;
+ InitMixerControls();
+
+ return 1;
+}
+
+void VoiceTweak_EndVoiceTweakMode()
+{
+ if(!g_bInTweakMode)
+ {
+ Assert(!"VoiceTweak_EndVoiceTweakMode called when not in tweak mode.");
+ return;
+ }
+
+ g_bInTweakMode = false;
+ Voice_RecordStop();
+}
+
+void VoiceTweak_SetControlFloat(VoiceTweakControl iControl, float flValue)
+{
+ if(!g_pMixerControls)
+ return;
+
+ if(iControl == MicrophoneVolume)
+ {
+ g_pMixerControls->SetValue_Float(IMixerControls::MicVolume, flValue);
+ }
+ else if ( iControl == MicBoost )
+ {
+ g_pMixerControls->SetValue_Float( IMixerControls::MicBoost, flValue );
+ }
+ else if(iControl == OtherSpeakerScale)
+ {
+ voice_scale.SetValue( flValue );
+ }
+}
+
+void Voice_ForceInit()
+{
+ if ( g_pMixerControls || !voice_enable.GetBool() )
+ {
+ // Nothing to do
+ return;
+ }
+
+ // Lacking a better default, just peak at what the server's sv_voicecodec is set to
+ static ConVarRef sv_voicecodec( "sv_voicecodec" );
+ if ( !Voice_InitWithDefault( sv_voicecodec.GetString() ) )
+ {
+ // Try ultimate fallback
+ Voice_InitWithDefault( VOICE_FALLBACK_CODEC );
+ }
+}
+
+float VoiceTweak_GetControlFloat(VoiceTweakControl iControl)
+{
+ Voice_ForceInit();
+
+ if(!g_pMixerControls)
+ return 0;
+
+ if(iControl == MicrophoneVolume)
+ {
+ float value = 1;
+ g_pMixerControls->GetValue_Float(IMixerControls::MicVolume, value);
+ return value;
+ }
+ else if(iControl == OtherSpeakerScale)
+ {
+ return voice_scale.GetFloat();
+ }
+ else if(iControl == SpeakingVolume)
+ {
+ return g_VoiceTweakSpeakingVolume * 1.0f / 32768;
+ }
+ else if ( iControl == MicBoost )
+ {
+ float flValue = 1;
+ g_pMixerControls->GetValue_Float( IMixerControls::MicBoost, flValue );
+ return flValue;
+ }
+ else
+ {
+ return 1;
+ }
+}
+
+bool VoiceTweak_IsStillTweaking()
+{
+ return g_bInTweakMode;
+}
+
+void Voice_Spatialize( channel_t *channel )
+{
+ if ( !g_bInTweakMode )
+ return;
+
+ Assert( channel->sfx );
+ Assert( channel->sfx->pSource );
+ Assert( channel->sfx->pSource->GetType() == CAudioSource::AUDIO_SOURCE_VOICE );
+
+ // Place the tweak mode sound back at the view entity
+ CVoiceChannel *pVoiceChannel = GetVoiceChannel( 0 );
+ Assert( pVoiceChannel );
+ if ( !pVoiceChannel )
+ return;
+
+ if ( pVoiceChannel->m_nSoundGuid != channel->guid )
+ return;
+
+ // No change
+ if ( g_pSoundServices->GetViewEntity() == pVoiceChannel->m_nViewEntityIndex )
+ return;
+
+ DevMsg( 1, "Voice_Spatialize changing voice tweak entity from %d to %d\n", pVoiceChannel->m_nViewEntityIndex, g_pSoundServices->GetViewEntity() );
+
+ pVoiceChannel->m_nViewEntityIndex = g_pSoundServices->GetViewEntity();
+ channel->soundsource = pVoiceChannel->m_nViewEntityIndex;
+}
+
+IVoiceTweak g_VoiceTweakAPI =
+{
+ VoiceTweak_StartVoiceTweakMode,
+ VoiceTweak_EndVoiceTweakMode,
+ VoiceTweak_SetControlFloat,
+ VoiceTweak_GetControlFloat,
+ VoiceTweak_IsStillTweaking,
+};
+
+
diff --git a/engine/audio/private/voice_gain.cpp b/engine/audio/private/voice_gain.cpp
new file mode 100644
index 0000000..bf14671
--- /dev/null
+++ b/engine/audio/private/voice_gain.cpp
@@ -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;
+ }
+ }
+}
diff --git a/engine/audio/private/voice_gain.h b/engine/audio/private/voice_gain.h
new file mode 100644
index 0000000..cd614e0
--- /dev/null
+++ b/engine/audio/private/voice_gain.h
@@ -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
diff --git a/engine/audio/private/voice_mixer_controls.cpp b/engine/audio/private/voice_mixer_controls.cpp
new file mode 100644
index 0000000..44c6a94
--- /dev/null
+++ b/engine/audio/private/voice_mixer_controls.cpp
@@ -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;
+}
+
diff --git a/engine/audio/private/voice_mixer_controls.h b/engine/audio/private/voice_mixer_controls.h
new file mode 100644
index 0000000..295ac88
--- /dev/null
+++ b/engine/audio/private/voice_mixer_controls.h
@@ -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
diff --git a/engine/audio/private/voice_mixer_controls_openal.cpp b/engine/audio/private/voice_mixer_controls_openal.cpp
new file mode 100644
index 0000000..832815c
--- /dev/null
+++ b/engine/audio/private/voice_mixer_controls_openal.cpp
@@ -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
+
diff --git a/engine/audio/private/voice_record_dsound.cpp b/engine/audio/private/voice_record_dsound.cpp
new file mode 100644
index 0000000..70fc2b6
--- /dev/null
+++ b/engine/audio/private/voice_record_dsound.cpp
@@ -0,0 +1,400 @@
+//========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+
+// This module implements the voice record and compression functions
+
+#include "audio_pch.h"
+#if !defined( _X360 )
+#include "dsound.h"
+#endif
+#include <assert.h>
+#include "voice.h"
+#include "tier0/vcrmode.h"
+#include "ivoicerecord.h"
+
+#if defined( _X360 )
+#include "xbox/xbox_win32stubs.h"
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+// ------------------------------------------------------------------------------
+// Globals.
+// ------------------------------------------------------------------------------
+
+typedef HRESULT (WINAPI *DirectSoundCaptureCreateFn)(const GUID FAR *lpGUID, LPDIRECTSOUNDCAPTURE *pCapture, LPUNKNOWN pUnkOuter);
+
+
+
+// ------------------------------------------------------------------------------
+// Static helpers
+// ------------------------------------------------------------------------------
+
+
+
+// ------------------------------------------------------------------------------
+// VoiceRecord_DSound
+// ------------------------------------------------------------------------------
+
+class VoiceRecord_DSound : public IVoiceRecord
+{
+protected:
+
+ virtual ~VoiceRecord_DSound();
+
+
+// IVoiceRecord.
+public:
+
+ VoiceRecord_DSound();
+ virtual void Release();
+
+ virtual bool RecordStart();
+ virtual void RecordStop();
+
+ // Initialize. The format of the data we expect from the provider is
+ // 8-bit signed mono at the specified sample rate.
+ virtual bool Init(int sampleRate);
+
+ virtual void Idle();
+
+ // Get the most recent N samples.
+ virtual int GetRecordedData(short *pOut, int nSamplesWanted);
+
+private:
+ void Term(); // Delete members.
+ void Clear(); // Clear members.
+ void UpdateWrapping();
+
+ inline DWORD NumCaptureBufferBytes() {return m_nCaptureBufferBytes;}
+
+
+private:
+ HINSTANCE m_hInstDS;
+
+ LPDIRECTSOUNDCAPTURE m_pCapture;
+ LPDIRECTSOUNDCAPTUREBUFFER m_pCaptureBuffer;
+
+ // How many bytes our capture buffer has.
+ DWORD m_nCaptureBufferBytes;
+
+ // We need to know when the capture buffer loops, so we install an event and
+ // update this in the event.
+ DWORD m_WrapOffset;
+ HANDLE m_hWrapEvent;
+
+ // This is our (unwrapped) position that tells how much data we've given to the app.
+ DWORD m_LastReadPos;
+};
+
+
+
+VoiceRecord_DSound::VoiceRecord_DSound()
+{
+ Clear();
+}
+
+
+VoiceRecord_DSound::~VoiceRecord_DSound()
+{
+ Term();
+}
+
+
+void VoiceRecord_DSound::Release()
+{
+ delete this;
+}
+
+
+bool VoiceRecord_DSound::RecordStart()
+{
+ //When we start recording we want to make sure we don't provide any audio
+ //that occurred before now. So set m_LastReadPos to the current
+ //read position of the audio device
+ if (m_pCaptureBuffer == NULL)
+ {
+ return false;
+ }
+
+ Idle();
+
+ DWORD dwStatus;
+ HRESULT hr = m_pCaptureBuffer->GetStatus(&dwStatus);
+ if (FAILED(hr) || !(dwStatus & DSCBSTATUS_CAPTURING))
+ return false;
+
+ DWORD dwReadPos;
+ hr = m_pCaptureBuffer->GetCurrentPosition(NULL, &dwReadPos);
+ if (!FAILED(hr))
+ {
+ m_LastReadPos = dwReadPos + m_WrapOffset;
+ }
+
+ return true;
+}
+
+
+void VoiceRecord_DSound::RecordStop()
+{
+}
+
+static bool IsRunningWindows7()
+{
+ if ( IsPC() )
+ {
+ OSVERSIONINFOEX osvi;
+ ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
+ osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+
+ if ( GetVersionEx ((OSVERSIONINFO *)&osvi) )
+ {
+ if ( osvi.dwMajorVersion > 6 || (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion >= 1) )
+ return true;
+ }
+ }
+ return false;
+}
+
+bool VoiceRecord_DSound::Init(int sampleRate)
+{
+ HRESULT hr;
+ DSCBUFFERDESC dscDesc;
+ DirectSoundCaptureCreateFn createFn;
+
+
+ Term();
+
+
+ WAVEFORMATEX recordFormat =
+ {
+ WAVE_FORMAT_PCM, // wFormatTag
+ 1, // nChannels
+ (uint32)sampleRate, // nSamplesPerSec
+ (uint32)sampleRate*2, // nAvgBytesPerSec
+ 2, // nBlockAlign
+ 16, // wBitsPerSample
+ sizeof(WAVEFORMATEX) // cbSize
+ };
+
+
+
+ // Load the DSound DLL.
+ m_hInstDS = LoadLibrary("dsound.dll");
+ if(!m_hInstDS)
+ goto HandleError;
+
+ createFn = (DirectSoundCaptureCreateFn)GetProcAddress(m_hInstDS, "DirectSoundCaptureCreate");
+ if(!createFn)
+ goto HandleError;
+
+ const GUID FAR *pGuid = &DSDEVID_DefaultVoiceCapture;
+ if ( IsRunningWindows7() )
+ {
+ pGuid = NULL;
+ }
+ hr = createFn(pGuid, &m_pCapture, NULL);
+ if(FAILED(hr))
+ goto HandleError;
+
+ // Create the capture buffer.
+ memset(&dscDesc, 0, sizeof(dscDesc));
+ dscDesc.dwSize = sizeof(dscDesc);
+ dscDesc.dwFlags = 0;
+ dscDesc.dwBufferBytes = recordFormat.nAvgBytesPerSec;
+ dscDesc.lpwfxFormat = &recordFormat;
+
+ hr = m_pCapture->CreateCaptureBuffer(&dscDesc, &m_pCaptureBuffer, NULL);
+ if(FAILED(hr))
+ goto HandleError;
+
+
+ // Figure out how many bytes we got in our capture buffer.
+ DSCBCAPS caps;
+ memset(&caps, 0, sizeof(caps));
+ caps.dwSize = sizeof(caps);
+
+ hr = m_pCaptureBuffer->GetCaps(&caps);
+ if(FAILED(hr))
+ goto HandleError;
+
+ m_nCaptureBufferBytes = caps.dwBufferBytes;
+
+
+ // Set it up so we get notification when the buffer wraps.
+ m_hWrapEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if(!m_hWrapEvent)
+ goto HandleError;
+
+ DSBPOSITIONNOTIFY dsbNotify;
+ dsbNotify.dwOffset = dscDesc.dwBufferBytes - 1;
+ dsbNotify.hEventNotify = m_hWrapEvent;
+
+ // Get the IDirectSoundNotify interface.
+ LPDIRECTSOUNDNOTIFY pNotify;
+ hr = m_pCaptureBuffer->QueryInterface(IID_IDirectSoundNotify, (void**)&pNotify);
+ if(FAILED(hr))
+ goto HandleError;
+
+ hr = pNotify->SetNotificationPositions(1, &dsbNotify);
+ pNotify->Release();
+ if(FAILED(hr))
+ goto HandleError;
+
+ // Start capturing.
+ hr = m_pCaptureBuffer->Start(DSCBSTART_LOOPING);
+ if(FAILED(hr))
+ return false;
+
+ return true;
+
+
+HandleError:;
+ Term();
+ return false;
+}
+
+
+void VoiceRecord_DSound::Term()
+{
+ if(m_pCaptureBuffer)
+ m_pCaptureBuffer->Release();
+
+ if(m_pCapture)
+ m_pCapture->Release();
+
+ if(m_hWrapEvent)
+ DeleteObject(m_hWrapEvent);
+
+ if(m_hInstDS)
+ {
+ FreeLibrary(m_hInstDS);
+ m_hInstDS = NULL;
+ }
+
+ Clear();
+}
+
+
+void VoiceRecord_DSound::Clear()
+{
+ m_pCapture = NULL;
+ m_pCaptureBuffer = NULL;
+ m_WrapOffset = 0;
+ m_LastReadPos = 0;
+ m_hWrapEvent = NULL;
+ m_hInstDS = NULL;
+}
+
+
+void VoiceRecord_DSound::Idle()
+{
+ UpdateWrapping();
+}
+
+int VoiceRecord_DSound::GetRecordedData( short *pOut, int nSamples )
+{
+ if(!m_pCaptureBuffer)
+ {
+ assert(false);
+ return 0;
+ }
+
+ DWORD dwStatus;
+ HRESULT hr = m_pCaptureBuffer->GetStatus(&dwStatus);
+ if(FAILED(hr) || !(dwStatus & DSCBSTATUS_CAPTURING))
+ return 0;
+
+ Idle(); // Update wrapping..
+
+ DWORD nBytesWanted = (DWORD)( nSamples << 1 );
+
+ DWORD dwReadPos;
+ hr = m_pCaptureBuffer->GetCurrentPosition( NULL, &dwReadPos);
+ if(FAILED(hr))
+ return 0;
+
+ dwReadPos += m_WrapOffset;
+
+ // Read the range (dwReadPos-nSamplesWanted, dwReadPos), but don't re-read data we've already read.
+ DWORD readStart = Max( dwReadPos - nBytesWanted, (DWORD)0u );
+ if ( readStart < m_LastReadPos )
+ {
+ readStart = m_LastReadPos;
+ }
+
+ // Lock the buffer.
+ LPVOID pData[2];
+ DWORD dataLen[2];
+
+ hr = m_pCaptureBuffer->Lock(
+ readStart % NumCaptureBufferBytes(), // Offset.
+ dwReadPos - readStart, // Number of bytes to lock.
+ &pData[0], // Buffer 1.
+ &dataLen[0], // Buffer 1 length.
+ &pData[1], // Buffer 2.
+ &dataLen[1], // Buffer 2 length.
+ 0 // Flags.
+ );
+
+ if(FAILED(hr))
+ return 0;
+
+ // Hopefully we didn't get too much data back!
+ if((dataLen[0]+dataLen[1]) > nBytesWanted )
+ {
+ assert(false);
+ m_pCaptureBuffer->Unlock(pData[0], dataLen[0], pData[1], dataLen[1]);
+ return 0;
+ }
+
+ // Copy the data to the output.
+ memcpy(pOut, pData[0], dataLen[0]);
+ memcpy(&pOut[dataLen[0]/2], pData[1], dataLen[1]);
+
+ m_pCaptureBuffer->Unlock(pData[0], dataLen[0], pData[1], dataLen[1]);
+
+ // Last Read Position
+ m_LastReadPos = dwReadPos;
+ // Return sample count (not bytes)
+ return (dataLen[0] + dataLen[1]) >> 1;
+}
+
+
+void VoiceRecord_DSound::UpdateWrapping()
+{
+ if(!m_pCaptureBuffer)
+ return;
+
+ // Has the buffer wrapped?
+ if ( VCRHook_WaitForSingleObject(m_hWrapEvent, 0) == WAIT_OBJECT_0 )
+ {
+ m_WrapOffset += m_nCaptureBufferBytes;
+ }
+}
+
+
+
+IVoiceRecord* CreateVoiceRecord_DSound(int sampleRate)
+{
+ VoiceRecord_DSound *pRecord = new VoiceRecord_DSound;
+ if(pRecord && pRecord->Init(sampleRate))
+ {
+ return pRecord;
+ }
+ else
+ {
+ if(pRecord)
+ pRecord->Release();
+
+ return NULL;
+ }
+}
+
diff --git a/engine/audio/private/voice_record_mac_audioqueue.cpp b/engine/audio/private/voice_record_mac_audioqueue.cpp
new file mode 100644
index 0000000..e26ba4e
--- /dev/null
+++ b/engine/audio/private/voice_record_mac_audioqueue.cpp
@@ -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;
+ }
+}
diff --git a/engine/audio/private/voice_record_openal.cpp b/engine/audio/private/voice_record_openal.cpp
new file mode 100644
index 0000000..679bf7d
--- /dev/null
+++ b/engine/audio/private/voice_record_openal.cpp
@@ -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
diff --git a/engine/audio/private/voice_sound_engine_interface.cpp b/engine/audio/private/voice_sound_engine_interface.cpp
new file mode 100644
index 0000000..3ee5e59
--- /dev/null
+++ b/engine/audio/private/voice_sound_engine_interface.cpp
@@ -0,0 +1,377 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "audio_pch.h"
+#include <assert.h>
+#include "voice.h"
+#include "ivoicecodec.h"
+
+#if defined( _X360 )
+#include "xauddefs.h"
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+// ------------------------------------------------------------------------- //
+// CAudioSourceVoice.
+// This feeds the data from an incoming voice channel (a guy on the server
+// who is speaking) into the sound engine.
+// ------------------------------------------------------------------------- //
+
+class CAudioSourceVoice : public CAudioSourceWave
+{
+public:
+ CAudioSourceVoice(CSfxTable *pSfx, int iEntity);
+ virtual ~CAudioSourceVoice();
+
+ virtual int GetType( void )
+ {
+ return AUDIO_SOURCE_VOICE;
+ }
+ virtual void GetCacheData( CAudioSourceCachedInfo *info )
+ {
+ Assert( 0 );
+ }
+
+
+ virtual CAudioMixer *CreateMixer( int initialStreamPosition = 0 );
+ virtual int GetOutputData( void **pData, int samplePosition, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] );
+ virtual int SampleRate( void );
+
+ // Sample size is in bytes. It will not be accurate for compressed audio. This is a best estimate.
+ // The compressed audio mixers understand this, but in general do not assume that SampleSize() * SampleCount() = filesize
+ // or even that SampleSize() is 100% accurate due to compression.
+ virtual int SampleSize( void );
+
+ // Total number of samples in this source. NOTE: Some sources are infinite (mic input), they should return
+ // a count equal to one second of audio at their current rate.
+ virtual int SampleCount( void );
+
+ virtual bool IsVoiceSource() {return true;}
+
+ virtual bool IsLooped() {return false;}
+ virtual bool IsStreaming() {return true;}
+ virtual bool IsStereoWav() {return false;}
+ virtual int GetCacheStatus() {return AUDIO_IS_LOADED;}
+ virtual void CacheLoad() {}
+ virtual void CacheUnload() {}
+ virtual CSentence *GetSentence() {return NULL;}
+
+ virtual int ZeroCrossingBefore( int sample ) {return sample;}
+ virtual int ZeroCrossingAfter( int sample ) {return sample;}
+
+ // mixer's references
+ virtual void ReferenceAdd( CAudioMixer *pMixer );
+ virtual void ReferenceRemove( CAudioMixer *pMixer );
+
+ // check reference count, return true if nothing is referencing this
+ virtual bool CanDelete();
+
+ virtual void Prefetch() {}
+
+ // Nothing, not a cache object...
+ virtual void CheckAudioSourceCache() {}
+
+private:
+
+ class CWaveDataVoice : public IWaveData
+ {
+ public:
+ CWaveDataVoice( CAudioSourceWave &source ) : m_source(source) {}
+ ~CWaveDataVoice( void ) {}
+
+ virtual CAudioSource &Source( void )
+ {
+ return m_source;
+ }
+
+ // this file is in memory, simply pass along the data request to the source
+ virtual int ReadSourceData( void **pData, int sampleIndex, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
+ {
+ return m_source.GetOutputData( pData, sampleIndex, sampleCount, copyBuf );
+ }
+
+ virtual bool IsReadyToMix()
+ {
+ return true;
+ }
+
+ private:
+ CAudioSourceWave &m_source; // pointer to source
+ };
+
+
+private:
+ CAudioSourceVoice( const CAudioSourceVoice & );
+
+ // Which entity's voice this is for.
+ int m_iChannel;
+
+ // How many mixers are referencing us.
+ int m_refCount;
+};
+
+
+
+// ----------------------------------------------------------------------------- //
+// Globals.
+// ----------------------------------------------------------------------------- //
+
+// The format we sample voice in.
+extern WAVEFORMATEX g_VoiceSampleFormat;
+
+class CVoiceSfx : public CSfxTable
+{
+public:
+ virtual const char *getname()
+ {
+ return "?VoiceSfx";
+ }
+};
+
+static CVoiceSfx g_CVoiceSfx[VOICE_NUM_CHANNELS];
+
+static float g_VoiceOverdriveDuration = 0;
+static bool g_bVoiceOverdriveOn = false;
+
+// When voice is on, all other sounds are decreased by this factor.
+static ConVar voice_overdrive( "voice_overdrive", "2" );
+static ConVar voice_overdrivefadetime( "voice_overdrivefadetime", "0.4" ); // How long it takes to fade in and out of the voice overdrive.
+
+// The sound engine uses this to lower all sound volumes.
+// All non-voice sounds are multiplied by this and divided by 256.
+int g_SND_VoiceOverdriveInt = 256;
+
+
+extern int Voice_SamplesPerSec();
+extern int Voice_AvgBytesPerSec();
+
+// ----------------------------------------------------------------------------- //
+// CAudioSourceVoice implementation.
+// ----------------------------------------------------------------------------- //
+
+CAudioSourceVoice::CAudioSourceVoice( CSfxTable *pSfx, int iChannel )
+ : CAudioSourceWave( pSfx )
+{
+ m_iChannel = iChannel;
+ m_refCount = 0;
+
+ WAVEFORMATEX tmp = g_VoiceSampleFormat;
+ tmp.nSamplesPerSec = Voice_SamplesPerSec();
+ tmp.nAvgBytesPerSec = Voice_AvgBytesPerSec();
+ Init((char*)&tmp, sizeof(tmp));
+ m_sampleCount = tmp.nSamplesPerSec;
+}
+
+CAudioSourceVoice::~CAudioSourceVoice()
+{
+ Voice_OnAudioSourceShutdown( m_iChannel );
+}
+
+CAudioMixer *CAudioSourceVoice::CreateMixer( int initialStreamPosition )
+{
+ CWaveDataVoice *pVoice = new CWaveDataVoice(*this);
+ if(!pVoice)
+ return NULL;
+
+ CAudioMixer *pMixer = CreateWaveMixer( pVoice, WAVE_FORMAT_PCM, 1, BYTES_PER_SAMPLE*8, 0 );
+ if(!pMixer)
+ {
+ delete pVoice;
+ return NULL;
+ }
+
+ return pMixer;
+}
+
+int CAudioSourceVoice::GetOutputData( void **pData, int samplePosition, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
+{
+ int nSamplesGotten = Voice_GetOutputData(
+ m_iChannel,
+ copyBuf,
+ AUDIOSOURCE_COPYBUF_SIZE,
+ samplePosition,
+ sampleCount );
+
+ // If there weren't enough bytes in the received data channel, pad it with zeros.
+ if( nSamplesGotten < sampleCount )
+ {
+ memset( &copyBuf[nSamplesGotten], 0, (sampleCount - nSamplesGotten) * BYTES_PER_SAMPLE );
+ nSamplesGotten = sampleCount;
+ }
+
+ *pData = copyBuf;
+ return nSamplesGotten;
+}
+
+int CAudioSourceVoice::SampleRate()
+{
+ return Voice_SamplesPerSec();
+}
+
+int CAudioSourceVoice::SampleSize()
+{
+ return BYTES_PER_SAMPLE;
+}
+
+int CAudioSourceVoice::SampleCount()
+{
+ return Voice_SamplesPerSec();
+}
+
+void CAudioSourceVoice::ReferenceAdd(CAudioMixer *pMixer)
+{
+ m_refCount++;
+}
+
+void CAudioSourceVoice::ReferenceRemove(CAudioMixer *pMixer)
+{
+ m_refCount--;
+ if ( m_refCount <= 0 )
+ delete this;
+}
+
+bool CAudioSourceVoice::CanDelete()
+{
+ return m_refCount == 0;
+}
+
+
+// ----------------------------------------------------------------------------- //
+// Interface implementation.
+// ----------------------------------------------------------------------------- //
+
+bool VoiceSE_Init()
+{
+ if( !snd_initialized )
+ return false;
+
+ g_SND_VoiceOverdriveInt = 256;
+ return true;
+}
+
+void VoiceSE_Term()
+{
+ // Disable voice ducking.
+ g_SND_VoiceOverdriveInt = 256;
+}
+
+
+void VoiceSE_Idle(float frametime)
+{
+ g_SND_VoiceOverdriveInt = 256;
+
+ if( g_bVoiceOverdriveOn )
+ {
+ g_VoiceOverdriveDuration = min( g_VoiceOverdriveDuration+frametime, voice_overdrivefadetime.GetFloat() );
+ }
+ else
+ {
+ if(g_VoiceOverdriveDuration == 0)
+ return;
+
+ g_VoiceOverdriveDuration = max(g_VoiceOverdriveDuration-frametime, 0.f);
+ }
+
+ float percent = g_VoiceOverdriveDuration / voice_overdrivefadetime.GetFloat();
+ percent = (float)(-cos(percent * 3.1415926535) * 0.5 + 0.5); // Smooth it out..
+ float voiceOverdrive = 1 + (voice_overdrive.GetFloat() - 1) * percent;
+ g_SND_VoiceOverdriveInt = (int)(256 / voiceOverdrive);
+}
+
+
+int VoiceSE_StartChannel(
+ int iChannel, //! Which channel to start.
+ int iEntity,
+ bool bProximity,
+ int nViewEntityIndex )
+{
+ Assert( iChannel >= 0 && iChannel < VOICE_NUM_CHANNELS );
+
+ // Start the sound.
+ CSfxTable *sfx = &g_CVoiceSfx[iChannel];
+ sfx->pSource = NULL;
+ Vector vOrigin(0,0,0);
+
+ StartSoundParams_t params;
+ params.staticsound = false;
+ params.entchannel = (CHAN_VOICE_BASE+iChannel);
+ params.pSfx = sfx;
+ params.origin = vOrigin;
+ params.fvol = 1.0f;
+ params.flags = 0;
+ params.pitch = PITCH_NORM;
+
+
+ if ( bProximity == true )
+ {
+ params.bUpdatePositions = true;
+ params.soundlevel = SNDLVL_TALKING;
+ params.soundsource = iEntity;
+ }
+ else
+ {
+ params.soundlevel = SNDLVL_IDLE;
+ params.soundsource = nViewEntityIndex;
+ }
+
+
+ return S_StartSound( params );
+}
+
+void VoiceSE_EndChannel(
+ int iChannel, //! Which channel to stop.
+ int iEntity
+ )
+{
+ Assert( iChannel >= 0 && iChannel < VOICE_NUM_CHANNELS );
+
+ S_StopSound( iEntity, CHAN_VOICE_BASE+iChannel );
+
+ // Start the sound.
+ CSfxTable *sfx = &g_CVoiceSfx[iChannel];
+ sfx->pSource = NULL;
+}
+
+void VoiceSE_StartOverdrive()
+{
+ g_bVoiceOverdriveOn = true;
+}
+
+void VoiceSE_EndOverdrive()
+{
+ g_bVoiceOverdriveOn = false;
+}
+
+
+void VoiceSE_InitMouth(int entnum)
+{
+}
+
+void VoiceSE_CloseMouth(int entnum)
+{
+}
+
+void VoiceSE_MoveMouth(int entnum, short *pSamples, int nSamples)
+{
+}
+
+
+CAudioSource* Voice_SetupAudioSource( int soundsource, int entchannel )
+{
+ int iChannel = entchannel - CHAN_VOICE_BASE;
+ if( iChannel >= 0 && iChannel < VOICE_NUM_CHANNELS )
+ {
+ CSfxTable *sfx = &g_CVoiceSfx[iChannel];
+ return new CAudioSourceVoice( sfx, iChannel );
+ }
+ else
+ return NULL;
+}
+
+
diff --git a/engine/audio/private/voice_sound_engine_interface.h b/engine/audio/private/voice_sound_engine_interface.h
new file mode 100644
index 0000000..10fcb1a
--- /dev/null
+++ b/engine/audio/private/voice_sound_engine_interface.h
@@ -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
diff --git a/engine/audio/private/voice_wavefile.cpp b/engine/audio/private/voice_wavefile.cpp
new file mode 100644
index 0000000..f4e554c
--- /dev/null
+++ b/engine/audio/private/voice_wavefile.cpp
@@ -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;
+}
+
+
diff --git a/engine/audio/private/voice_wavefile.h b/engine/audio/private/voice_wavefile.h
new file mode 100644
index 0000000..08afe86
--- /dev/null
+++ b/engine/audio/private/voice_wavefile.h
@@ -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
diff --git a/engine/audio/private/vox.cpp b/engine/audio/private/vox.cpp
new file mode 100644
index 0000000..80a3a2b
--- /dev/null
+++ b/engine/audio/private/vox.cpp
@@ -0,0 +1,2862 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Voice / Sentence streaming & parsing code
+//
+// $Workfile: $
+// $Date: $
+// $NoKeywords: $
+//=============================================================================//
+
+//===============================================================================
+// VOX. Algorithms to load and play spoken text sentences from a file:
+//
+// In ambient sounds or entity sounds, precache the
+// name of the sentence instead of the wave name, ie: !C1A2S4
+//
+// During sound system init, the 'sentences.txt' is read.
+// This file has the format:
+//
+// C1A2S4 agrunt/vox/You will be exterminated, surrender NOW.
+// C1A2s5 hgrunt/vox/Radio check, over.
+// ...
+//
+// There must be at least one space between the sentence name and the sentence.
+// Sentences may be separated by one or more lines
+// There may be tabs or spaces preceding the sentence name
+// The sentence must end in a /n or /r
+// Lines beginning with // are ignored as comments
+//
+// Period or comma will insert a pause in the wave unless
+// the period or comma is the last character in the string.
+//
+// If first 2 chars of a word are upper case, word volume increased by 25%
+//
+// If last char of a word is a number from 0 to 9
+// then word will be pitch-shifted up by 0 to 9, where 0 is a small shift
+// and 9 is a very high pitch shift.
+//
+// We alloc heap space to contain this data, and track total
+// sentences read. A pointer to each sentence is maintained in g_Sentences.
+//
+// When sound is played back in S_StartDynamicSound or s_startstaticsound, we detect the !name
+// format and lookup the actual sentence in the sentences array
+//
+// To play, we parse each word in the sentence, chain the words, and play the sentence
+// each word's data is loaded directy from disk and freed right after playback.
+//===============================================================================
+
+#include "audio_pch.h"
+#include "vox_private.h"
+#include "characterset.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "utlsymbol.h"
+#include "utldict.h"
+#include "../../MapReslistGenerator.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+// In other C files.
+// Globals
+extern IFileSystem *g_pFileSystem;
+
+// This is the initial capacity for sentences, the array will grow if necessary
+#define MAX_EXPECTED_SENTENCES 900
+
+CUtlVector<sentence_t> g_Sentences;
+// FIXME: could get this through common includes
+const char *COM_Parse (const char *data);
+extern char com_token[1024];
+
+// Module Locals
+static char *rgpparseword[CVOXWORDMAX]; // array of pointers to parsed words
+static char voxperiod[] = "_period"; // vocal pause
+static char voxcomma[] = "_comma"; // vocal pause
+
+#define CVOXMAPNAMESMAX 24
+static char *g_rgmapnames[CVOXMAPNAMESMAX];
+static int g_cmapnames = 0;
+
+// Sentence file list management
+static void VOX_ListClear( void );
+static int VOX_ListFileIsLoaded( const char *psentenceFileName );
+static void VOX_ListMarkFileLoaded( const char *psentenceFileName );
+static void VOX_InitAllEntnames( void );
+
+void VOX_LookupMapnames( void );
+
+static void VOX_Reload()
+{
+ VOX_Shutdown();
+ VOX_Init();
+}
+static ConCommand vox_reload( "vox_reload", VOX_Reload, "Reload sentences.txt file", FCVAR_CHEAT );
+
+static CUtlVector<unsigned char> g_GroupLRU;
+static CUtlVector<char> g_SentenceFile;
+
+struct sentencegroup_t
+{
+ short count;
+
+public:
+ short lru;
+ const char *GroupName() const;
+ CUtlSymbol GroupNameSymbol() const;
+ void SetGroupName( const char *pName );
+ static CUtlSymbol GetSymbol( const char *pName );
+
+private:
+ CUtlSymbol groupname;
+ static CUtlSymbolTable s_SymbolTable;
+};
+
+const char *sentencegroup_t::GroupName() const
+{
+ return s_SymbolTable.String( groupname );
+}
+
+void sentencegroup_t::SetGroupName( const char *pName )
+{
+ groupname = s_SymbolTable.AddString( pName );
+}
+
+CUtlSymbol sentencegroup_t::GroupNameSymbol() const
+{
+ return groupname;
+}
+
+CUtlSymbol sentencegroup_t::GetSymbol( const char *pName )
+{
+ return s_SymbolTable.AddString( pName );
+}
+
+CUtlVector<sentencegroup_t> g_SentenceGroups;
+CUtlSymbolTable sentencegroup_t::s_SymbolTable( 0, 256, true );
+
+struct WordBuf
+{
+ WordBuf()
+ {
+ word[ 0 ] = 0;
+ }
+
+ WordBuf( const WordBuf& src )
+ {
+ Q_strncpy( word, src.word, sizeof( word ) );
+ }
+
+ void Set( char const *w )
+ {
+ if ( !w )
+ {
+ word[ 0 ] = 0;
+ return;
+ }
+ Q_strncpy( word, w, sizeof( word ) );
+ while ( Q_strlen( word ) >= 1 && word[ Q_strlen( word ) - 1 ] == ' ' )
+ {
+ word[ Q_strlen( word ) - 1 ] = 0;
+ }
+ }
+
+ char word[ 256 ];
+};
+
+struct ccpair
+{
+ WordBuf token;
+ WordBuf value;
+
+ WordBuf fullpath;
+};
+
+static void VOX_BuildVirtualNameList( char *word, CUtlVector< WordBuf >& list );
+
+// This module depends on these engine calls:
+// DevMsg
+// S_FreeChannel
+// S_LoadSound
+// S_FindName
+// It also depends on vstdlib/RandomInt (all other random calls go through g_pSoundServices)
+
+void VOX_Init( void )
+{
+ VOX_InitAllEntnames();
+
+ g_SentenceFile.Purge();
+ g_GroupLRU.Purge();
+ g_Sentences.RemoveAll();
+ g_Sentences.EnsureCapacity( MAX_EXPECTED_SENTENCES );
+
+ VOX_ListClear();
+
+ VOX_ReadSentenceFile( "scripts/sentences.txt" );
+ VOX_LookupMapnames();
+}
+
+
+void VOX_Shutdown( void )
+{
+ g_Sentences.RemoveAll();
+ VOX_ListClear();
+ g_SentenceGroups.RemoveAll();
+ g_cmapnames = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: This is kind of like strchr(), but we get the actual pointer to the
+// end of the string when it fails rather than NULL. This is useful
+// for parsing buffers containing multiple strings
+// Input : *string -
+// scan -
+// Output : char
+//-----------------------------------------------------------------------------
+char *ScanForwardUntil( char *string, char scan )
+{
+ while( string[0] )
+ {
+ if ( string[0] == scan )
+ return string;
+
+ string++;
+ }
+ return string;
+}
+
+// parse a null terminated string of text into component words, with
+// pointers to each word stored in rgpparseword
+// note: this code actually alters the passed in string!
+
+char **VOX_ParseString(char *psz)
+{
+ int i;
+ int fdone = 0;
+ char *pszscan = psz;
+ char c;
+ characterset_t nextWord, skip;
+
+ memset(rgpparseword, 0, sizeof(char *) * CVOXWORDMAX);
+
+ if (!psz)
+ return NULL;
+
+ i = 0;
+ rgpparseword[i++] = psz;
+
+ CharacterSetBuild( &nextWord, " ,.({" );
+ CharacterSetBuild( &skip, "., " );
+ while (!fdone && i < CVOXWORDMAX)
+ {
+ // scan up to next word
+ c = *pszscan;
+ while (c && !IN_CHARACTERSET(nextWord,c) )
+ c = *(++pszscan);
+
+ // if '(' then scan for matching ')'
+ if ( c == '(' || c=='{' )
+ {
+ if ( c == '(' )
+ pszscan = ScanForwardUntil( pszscan, ')' );
+ else if ( c == '{' )
+ pszscan = ScanForwardUntil( pszscan, '}' );
+
+ c = *(++pszscan);
+ if (!c)
+ fdone = 1;
+ }
+
+ if (fdone || !c)
+ fdone = 1;
+ else
+ {
+ // if . or , insert pause into rgpparseword,
+ // unless this is the last character
+ if ((c == '.' || c == ',') && *(pszscan+1) != '\n' && *(pszscan+1) != '\r'
+ && *(pszscan+1) != 0)
+ {
+ if (c == '.')
+ rgpparseword[i++] = voxperiod;
+ else
+ rgpparseword[i++] = voxcomma;
+
+ if (i >= CVOXWORDMAX)
+ break;
+ }
+
+ // null terminate substring
+ *pszscan++ = 0;
+
+ // skip whitespace
+ c = *pszscan;
+ while (c && IN_CHARACTERSET(skip, c))
+ c = *(++pszscan);
+
+ if (!c)
+ fdone = 1;
+ else
+ rgpparseword[i++] = pszscan;
+ }
+ }
+ return rgpparseword;
+}
+
+// backwards scan psz for last '/'
+// return substring in szpath null terminated
+// if '/' not found, return 'vox/'
+
+char *VOX_GetDirectory(char *szpath, int maxpath, char *psz)
+{
+ char c;
+ int cb = 0;
+ char *pszscan = psz + Q_strlen( psz ) - 1;
+
+ // scan backwards until first '/' or start of string
+ c = *pszscan;
+ while (pszscan > psz && c != '/')
+ {
+ c = *(--pszscan);
+ cb++;
+ }
+
+ if (c != '/')
+ {
+ // didn't find '/', return default directory
+ Q_strncpy(szpath, "vox/", maxpath );
+ return psz;
+ }
+
+ cb = Q_strlen(psz) - cb;
+
+ cb = clamp( cb, 0, maxpath - 1 );
+
+ // FIXME: Is this safe?
+ Q_memcpy(szpath, psz, cb);
+ szpath[cb] = 0;
+ return pszscan + 1;
+}
+
+// get channel volume scale if word
+
+#ifndef SWDS
+float VOX_GetChanVol(channel_t *ch)
+{
+ if ( !ch->pMixer )
+ return 1.0;
+
+ return ch->pMixer->GetVolumeScale();
+/*
+
+ if ( scale == 1.0 )
+ return;
+
+ ch->rightvol = (int) (ch->rightvol * scale);
+ ch->leftvol = (int) (ch->leftvol * scale);
+
+ if ( g_AudioDevice->Should3DMix() )
+ {
+ ch->rrightvol = (int) (ch->rrightvol * scale);
+ ch->rleftvol = (int) (ch->rleftvol * scale);
+ ch->centervol = (int) (ch->centervol * scale);
+ }
+ else
+ {
+ ch->rrightvol = 0;
+ ch->rleftvol = 0;
+ ch->centervol = 0;
+ }
+*/
+}
+#endif
+
+//===============================================================================
+// Get any pitch, volume, start, end params into voxword
+// and null out trailing format characters
+// Format:
+// someword(v100 p110 s10 e20)
+//
+// v is volume, 0% to n%
+// p is pitch shift up 0% to n%
+// s is start wave offset %
+// e is end wave offset %
+// t is timecompression %
+//
+// pass fFirst == 1 if this is the first string in sentence
+// returns 1 if valid string, 0 if parameter block only.
+//
+// If a ( xxx ) parameter block does not directly follow a word,
+// then that 'default' parameter block will be used as the default value
+// for all following words. Default parameter values are reset
+// by another 'default' parameter block. Default parameter values
+// for a single word are overridden for that word if it has a parameter block.
+//
+//===============================================================================
+
+int VOX_ParseWordParams(char *psz, voxword_t *pvoxword, int fFirst)
+{
+ char *pszsave = psz;
+ char c;
+ char ct;
+ char sznum[8];
+ int i;
+ static voxword_t voxwordDefault;
+ characterset_t commandSet, delimitSet;
+
+ // List of valid commands
+ CharacterSetBuild( &commandSet, "vpset)" );
+
+ // init to defaults if this is the first word in string.
+ if (fFirst)
+ {
+ voxwordDefault.pitch = -1;
+ voxwordDefault.volume = 100;
+ voxwordDefault.start = 0;
+ voxwordDefault.end = 100;
+ voxwordDefault.fKeepCached = 0;
+ voxwordDefault.timecompress = 0;
+ }
+
+ *pvoxword = voxwordDefault;
+
+ // look at next to last char to see if we have a
+ // valid format:
+
+ c = *(psz + strlen(psz) - 1);
+
+ if (c != ')')
+ return 1; // no formatting, return
+
+ // scan forward to first '('
+ CharacterSetBuild( &delimitSet, "()" );
+ c = *psz;
+ while ( !IN_CHARACTERSET(delimitSet, c) )
+ c = *(++psz);
+
+ if ( c == ')' )
+ return 0; // bogus formatting
+
+ // null terminate
+
+ *psz = 0;
+ ct = *(++psz);
+
+ while (1)
+ {
+ // scan until we hit a character in the commandSet
+
+ while (ct && !IN_CHARACTERSET(commandSet, ct) )
+ ct = *(++psz);
+
+ if (ct == ')')
+ break;
+
+ memset(sznum, 0, sizeof(sznum));
+ i = 0;
+
+ c = *(++psz);
+
+ if (!V_isdigit(c))
+ break;
+
+ // read number
+ while (V_isdigit(c) && i < sizeof(sznum) - 1)
+ {
+ sznum[i++] = c;
+ c = *(++psz);
+ }
+
+ // get value of number
+ i = atoi(sznum);
+
+ switch (ct)
+ {
+ case 'v': pvoxword->volume = i; break;
+ case 'p': pvoxword->pitch = i; break;
+ case 's': pvoxword->start = i; break;
+ case 'e': pvoxword->end = i; break;
+ case 't': pvoxword->timecompress = i; break;
+ }
+
+ ct = c;
+ }
+
+ // if the string has zero length, this was an isolated
+ // parameter block. Set default voxword to these
+ // values
+
+ if (strlen(pszsave) == 0)
+ {
+ voxwordDefault = *pvoxword;
+ return 0;
+ }
+ else
+ return 1;
+}
+
+#define CVOXSAVEDWORDSIZE 32
+
+// saved entity name/number based on type of entity & id
+
+#define CVOXGLOBMAX 4 // max number of rnd and seqential globals
+
+typedef struct _vox_entname
+{
+ // type is defined by last character of group name.
+ // for instance, V_MYNAME_S has type 'S', which is used for soldiers
+ // V_MYNUM_M has type 'P' which is used for metrocops
+
+ int type;
+
+ SoundSource soundsource; // the enity emitting the sentence
+ char *pszname; // a custom name for the entity (this is a word name)
+ char *psznum; // a custom number for the entity (this is a word name)
+ char *pszglobal[CVOXGLOBMAX]; // 1 global word, shared by this type of entity, picked randomly, expires after 5min
+ char *pszglobalseq[CVOXGLOBMAX]; // 1 global word, shared by this type of entity, picked in sequence, expires after 5 min
+ bool fdied; // true if ent died (don't clear, we need its name)
+ int iseq[CVOXGLOBMAX]; // sequence index, for global sequential lookups
+ float timestamp[CVOXGLOBMAX]; // latest update to this ent global timestamp
+ float timestampseq[CVOXGLOBMAX]; // latest update to this ent global sequential timestamp
+ float timedied; // timestamp of death
+
+} vox_entname;
+
+#define CENTNAMESMAX 64
+
+vox_entname g_entnames[CENTNAMESMAX];
+
+int g_entnamelastsaved = 0;
+
+// init all
+
+void VOX_InitAllEntnames( void )
+{
+ g_entnamelastsaved = 0;
+ Q_memset(g_entnames, 0, sizeof(g_entnames));
+ Q_memset(g_rgmapnames, 0, sizeof(g_rgmapnames));
+ g_cmapnames = 0;
+}
+
+// get new index
+
+int VOX_GetNextEntnameIndex( void )
+{
+ g_entnamelastsaved++;
+
+ if (g_entnamelastsaved >= CENTNAMESMAX)
+ {
+ g_entnamelastsaved = 0;
+ }
+
+ return g_entnamelastsaved;
+}
+
+// get index of this ent, or get a new index. if fallocnew is true,
+// get a new slot if none found.
+// NOTE: this routine always sets fdied to false - fdied is later
+// set to true by the caller if in IDIED routine. This
+// ensures that if an ent is reused, it won't be marked as fdied.
+
+int VOX_LookupEntIndex( int type, SoundSource soundsource, bool fallocnew)
+{
+ int i;
+
+ for (i = 0; i < CENTNAMESMAX; i++)
+ {
+ if ((g_entnames[i].type == type) && (g_entnames[i].soundsource == soundsource))
+ {
+ g_entnames[i].fdied = false;
+ return i;
+ }
+ }
+
+ if ( !fallocnew )
+ return -1;
+
+ // new index slot - init
+
+ int inew = VOX_GetNextEntnameIndex();
+
+ g_entnames[inew].type = type;
+ g_entnames[inew].soundsource = soundsource;
+ g_entnames[inew].timedied = 0;
+ g_entnames[inew].fdied = 0;
+ g_entnames[inew].pszname = NULL;
+ g_entnames[inew].psznum = NULL;
+
+ for (i = 0; i < CVOXGLOBMAX; i++)
+ {
+ g_entnames[inew].pszglobal[i] = NULL;
+ g_entnames[inew].timestamp[i] = 0;
+ g_entnames[inew].iseq[i] = 0;
+ g_entnames[inew].timestampseq[i] = 0;
+ g_entnames[inew].pszglobalseq[i] = NULL;
+ }
+
+ return inew;
+}
+
+// lookup random first word from this named group,
+// return static, null terminated string
+
+char * VOX_LookupRndVirtual( char *pGroupName )
+{
+ // get group index
+
+ int isentenceg = VOX_GroupIndexFromName( pGroupName );
+
+ if ( isentenceg < 0)
+ return NULL;
+
+ char szsentencename[32];
+
+ // get pointer to sentence name within group, using lru
+
+ int isentence = VOX_GroupPick( isentenceg, szsentencename, sizeof(szsentencename)-1 );
+
+ if (isentence < 0)
+ return NULL;
+
+ // get pointer to sentence data
+
+ char *psz = VOX_LookupString( szsentencename[0] == '!' ? szsentencename+1 : szsentencename, NULL);
+
+ // strip trailing whitespace
+
+ if (!psz)
+ return NULL;
+
+ char *pend = Q_strstr(psz, " ");
+ if (pend)
+ *pend = 0;
+
+ // return pointer to first (and only) word
+
+ return psz;
+}
+
+// given groupname, get pointer to first word of n'th sentence in group
+
+char *VOX_LookupSentenceByIndex( char *pGroupname, int ipick, int *pipicknext )
+{
+ // get group index
+
+ int isentenceg = VOX_GroupIndexFromName( pGroupname );
+
+ if ( isentenceg < 0)
+ return NULL;
+
+ char szsentencename[32];
+
+ // get pointer to sentence name within group, using lru
+
+ int isentence = VOX_GroupPickSequential( isentenceg, szsentencename, sizeof(szsentencename)-1, ipick, true );
+
+ if (isentence < 0)
+ return NULL;
+
+ // get pointer to sentence data
+
+ char *psz = VOX_LookupString( szsentencename[0] == '!' ? szsentencename+1 : szsentencename, NULL);
+
+ // strip trailing whitespace
+
+ char *pend = Q_strstr(psz, " ");
+ if (pend)
+ *pend = 0;
+
+ if (pipicknext)
+ *pipicknext = isentence;
+
+ // return pointer to first (and only) word
+ return psz;
+}
+
+// lookup first word from this named group, group entry 'ipick',
+// return static, null terminated string
+
+char * VOX_LookupNumber( char *pGroupName, int ipick )
+{
+ // construct group name from V_NUMBERS + TYPE
+
+ char sznumbers[16];
+ int glen = Q_strlen(pGroupName);
+ int slen = Q_strlen("V_NUMBERS");
+
+ V_strcpy_safe(sznumbers, "V_NUMBERS");
+
+ // insert type character
+ sznumbers[slen] = pGroupName[glen-1];
+ sznumbers[slen+1] = 0;
+
+ return VOX_LookupSentenceByIndex( sznumbers, ipick, NULL );
+}
+
+// lookup ent & type, return static, null terminated string
+// if no saved string, create one.
+// UNDONE: init ent/type/string array, wrap when saving
+
+char * VOX_LookupMyVirtual( int iname, char *pGroupName, char chtype, SoundSource soundsource)
+{
+ char *psz = NULL;
+ char **ppsz = NULL;
+
+ // get existing ent index, or index to new slot
+
+ int ient = VOX_LookupEntIndex( (int)chtype, soundsource, true );
+
+ if (iname == 1)
+ {
+ // lookup saved name
+
+ psz = g_entnames[ient].pszname;
+ ppsz = &(g_entnames[ient].pszname);
+ }
+ else
+ {
+ // lookup saved number
+
+ psz = g_entnames[ient].psznum;
+ ppsz = &(g_entnames[ient].psznum);
+ }
+
+ // if none found for this ent - pick one and save it
+
+ if (psz == NULL)
+ {
+ // get new string
+ psz = VOX_LookupRndVirtual( pGroupName );
+
+ // save pointer to new string in g_entnames
+ *ppsz = psz;
+ }
+
+ return psz;
+}
+
+// get range or heading from ent to player,
+// store range in from 1 to 3 words as ppszNew...ppszNew2
+// store count of words in pcnew
+// if fsimple is true, return numeric sequence based on ten digit max
+
+void VOX_LookupRangeHeadingOrGrid( int irhg, char *pGroupName, channel_t *pChannel, SoundSource soundsource, char **ppszNew, char **ppszNew1, char **ppszNew2, int *pcnew, bool fsimple )
+{
+ Vector SL; // sound -> listener vector
+ char *phundreds = NULL;
+ char *ptens = NULL;
+ char *pones = NULL;
+ int cnew = 0;
+ float dist;
+ int dmeters = 0;
+ int hundreds, tens, ones;
+
+ VectorSubtract(listener_origin, pChannel->origin, SL);
+
+ if (irhg == 0)
+ {
+ // get range
+ dist = VectorLength(SL);
+
+ dmeters = (int)((dist * 2.54 / 100.0)); // convert inches to meters
+
+ dmeters = clamp(dmeters, 0, 900);
+ }
+ else if (irhg == 1)
+ {
+ // get heading
+ QAngle source_angles;
+
+ source_angles.Init(0.0, 0.0, 0.0);
+
+ VectorAngles( SL, source_angles );
+
+ dmeters = source_angles[YAW];
+ } else if (irhg == 2)
+ {
+ // get gridx
+ dmeters = (int)(((16384 + listener_origin.x) * 2.54 / 100.0) / 10) % 20;
+ }
+ else if (irhg == 3)
+ {
+ // get gridy
+ dmeters = (int)(((16384 + listener_origin.y) * 2.54 / 100.0) / 10) % 20;
+ }
+
+ dmeters = clamp(dmeters, 0, 999);
+
+ // get hundreds, tens, ones
+
+ hundreds = dmeters / 100;
+ tens = (dmeters - hundreds * 100) / 10;
+ ones = (dmeters - hundreds * 100 - tens * 10);
+
+
+ if (fsimple)
+ {
+ // just return simple ten digit lookups for ones, tens, hundreds
+
+ pones = VOX_LookupNumber( pGroupName, ones);
+ cnew++;
+
+ if (tens || hundreds)
+ {
+ ptens = VOX_LookupNumber( pGroupName, tens);
+ cnew++;
+ }
+
+ if (hundreds)
+ {
+ phundreds = VOX_LookupNumber( pGroupName, hundreds );
+ cnew++;
+ }
+
+ goto LookupNumExit;
+ }
+
+ // get pointer to string from groupname and number
+
+ // 100,200,300,400,500,600,700,800,900
+ if (hundreds && !tens && !ones)
+ {
+ if (hundreds <= 3)
+ {
+ phundreds = VOX_LookupNumber( pGroupName, 27 + hundreds);
+ cnew++;
+ }
+ else
+ {
+ phundreds = VOX_LookupNumber( pGroupName, hundreds );
+ ptens = VOX_LookupNumber( pGroupName, 0);
+ pones = VOX_LookupNumber( pGroupName, 0);
+ cnew++;
+ cnew++;
+
+ }
+ goto LookupNumExit;
+ }
+
+
+ if ( hundreds )
+ {
+ // 101..999
+ if (hundreds <= 3 && !tens && ones)
+ phundreds = VOX_LookupNumber( pGroupName, 27 + hundreds);
+ else
+ phundreds = VOX_LookupNumber( pGroupName, hundreds );
+
+ cnew++;
+
+ // 101..109 to 901..909
+ if (!tens && ones)
+ {
+ pones = VOX_LookupNumber( pGroupName, ones);
+ cnew++;
+ if (hundreds > 3)
+ {
+ ptens = VOX_LookupNumber( pGroupName, 0);
+ cnew++;
+ }
+ goto LookupNumExit;
+ }
+ }
+
+ // 1..19
+ if (tens <= 1 && (tens || ones))
+ {
+ pones = VOX_LookupNumber( pGroupName, ones + tens * 10 );
+ cnew++;
+ tens = 0;
+ goto LookupNumExit;
+ }
+
+ // 20..99
+ if (tens > 1)
+ {
+ if (ones)
+ {
+ pones = VOX_LookupNumber( pGroupName, ones );
+ cnew++;
+ }
+
+ ptens = VOX_LookupNumber( pGroupName, 18 + tens);
+ cnew++;
+ }
+
+
+LookupNumExit:
+ // return values
+
+ *pcnew = cnew;
+
+ // return
+ switch (cnew)
+ {
+ default:
+ *ppszNew = NULL;
+ return;
+ case 1: // 1..19,20,30,40,50,60,70,80,90,100,200,300
+ *ppszNew = pones ? pones : (ptens ? ptens : (phundreds ? phundreds : NULL));
+ return;
+ case 2:
+ if (ptens && pones)
+ {
+ *ppszNew = ptens;
+ *ppszNew1 = pones;
+ }
+ else if (phundreds && pones)
+ {
+ *ppszNew = phundreds;
+ *ppszNew1 = pones;
+ }
+ else if (phundreds && ptens)
+ {
+ *ppszNew = phundreds;
+ *ppszNew1 = ptens;
+ }
+ return;
+ case 3:
+ *ppszNew = phundreds;
+ *ppszNew1 = ptens;
+ *ppszNew2 = pones;
+ return;
+ }
+}
+
+// find most recent ent of this type marked as dead
+
+int VOX_LookupLastDeadIndex( int type )
+{
+ float timemax = -1;
+ int ifound = -1;
+ int i;
+
+ for (i = 0; i < CENTNAMESMAX; i++)
+ {
+ if (g_entnames[i].type == type && g_entnames[i].fdied)
+ {
+ if (g_entnames[i].timedied >= timemax)
+ {
+ timemax = g_entnames[i].timedied;
+ ifound = i;
+ }
+ }
+ }
+
+ return ifound;
+}
+
+ConVar snd_vox_globaltimeout("snd_vox_globaltimeout", "300"); // n second timeout to reset global vox words
+ConVar snd_vox_seqtimeout("snd_vox_seqtimetout", "300"); // n second timeout to reset global sequential vox words
+ConVar snd_vox_sectimeout("snd_vox_sectimetout", "300"); // n second timeout to reset global sector id
+ConVar snd_vox_captiontrace( "snd_vox_captiontrace", "0", 0, "Shows sentence name for sentences which are set not to show captions." );
+
+// return index to ent which knows the current sector.
+// if no ent found, alloc a new one and establish shector.
+// sectors expire after approx 5 minutes.
+
+#define VOXSECTORMAX 20
+
+static float g_vox_lastsectorupdate = 0;
+static int g_vox_isector = -1;
+
+char *VOX_LookupSectorVirtual( char *pGroupname )
+{
+ float curtime = g_pSoundServices->GetClientTime();
+
+ if (g_vox_isector == -1)
+ {
+ g_vox_isector = RandomInt(0, VOXSECTORMAX-1);
+ }
+
+// update sector every 5 min
+
+ if (curtime - g_vox_lastsectorupdate > snd_vox_sectimeout.GetInt())
+ {
+ g_vox_isector++;
+ if (g_vox_isector > VOXSECTORMAX)
+ g_vox_isector = 1;
+ g_vox_lastsectorupdate = curtime;
+ }
+
+ return VOX_LookupNumber( pGroupname, g_vox_isector );
+}
+
+
+
+char *VOX_LookupGlobalVirtual( int type, SoundSource soundsource, char *pGroupName, int iglobal )
+{
+ int i;
+ float curtime = g_pSoundServices->GetClientTime();
+
+ // look for ent of this type with un-expired global
+
+ for (i = 0; i < CENTNAMESMAX; i++)
+ {
+ if (g_entnames[i].type == type)
+ {
+ if (curtime - g_entnames[i].timestamp[iglobal] <= snd_vox_globaltimeout.GetInt())
+ {
+ // if this ent has an un-expired global, return it, otherwise break
+
+ if (g_entnames[i].pszglobal[iglobal])
+ return g_entnames[i].pszglobal[iglobal];
+ else
+ break;
+ }
+ }
+ }
+
+ // if not found, construct a new global for this ent
+
+ // pick random word from groupname
+
+ char *psz = VOX_LookupRndVirtual( pGroupName );
+
+ // get existing ent index, or index to new slot
+
+ int ient = VOX_LookupEntIndex( type, soundsource, true );
+
+ g_entnames[ient].timestamp[iglobal] = curtime;
+ g_entnames[ient].pszglobal[iglobal] = psz;
+
+ return psz;
+}
+
+// lookup global values in group in sequence - get next value
+// in sequence. sequence counter expires every 2.5 minutes.
+
+char *VOX_LookupGlobalSeqVirtual( int type, SoundSource soundsource, char *pGroupName, int iglobal )
+{
+
+ int i;
+ int ient;
+ float curtime = g_pSoundServices->GetClientTime();
+
+ // look for ent of this type with un-expired global
+
+ for (i = 0; i < CENTNAMESMAX; i++)
+ {
+ if (g_entnames[i].type == type)
+ {
+ if (curtime - g_entnames[i].timestampseq[iglobal] <= (snd_vox_seqtimeout.GetInt()/2))
+ {
+ // if first ent found has an un-expired global sequence set,
+ // get next value in sequence, otherwise break
+
+ ient = i;
+ goto Pick_next;
+ }
+ else
+ {
+ // global has expired - reset sequence
+
+ ient = i;
+ g_entnames[ient].iseq[iglobal] = 0;
+ goto Pick_next;
+ }
+ }
+ }
+
+ // if not found, construct a new sequential global for this ent
+
+ ient = VOX_LookupEntIndex( type, soundsource, true );
+
+ // pick next word from groupname
+Pick_next:
+ int ipick = g_entnames[ient].iseq[iglobal];
+ int ipicknext = 0;
+
+ char *psz = VOX_LookupSentenceByIndex( pGroupName, ipick, &ipicknext );
+ g_entnames[ient].iseq[iglobal] = ipicknext;
+
+ // get existing ent index, or index to new slot
+
+ g_entnames[ient].timestampseq[iglobal] = curtime;
+ g_entnames[ient].pszglobalseq[iglobal] = psz;
+
+ return psz;
+}
+
+// insert new words into rgpparseword at 'ireplace' slot
+
+void VOX_InsertWords( int ireplace, int cnew, char *pszNew, char *pszNew1, char *pszNew2 )
+{
+ if ( cnew )
+ {
+ // make space in rgpparseword for 'cnew - 1' new words
+ int ccopy = cnew - 1; // number of new slots we need
+ int j;
+
+ if (ccopy)
+ {
+ for (j = CVOXWORDMAX-1; j > ireplace + ccopy; j--)
+ rgpparseword[j] = rgpparseword[j - ccopy ];
+ }
+
+ // replace rgpparseword entry(s) with the substitued name(s)
+
+ rgpparseword[ireplace] = pszNew;
+
+ if ( cnew == 2 || cnew == 3)
+ rgpparseword[ireplace+1] = pszNew1;
+
+ if ( cnew == 3 )
+ rgpparseword[ireplace+2] = pszNew2;
+ }
+}
+
+// remove 'silent' word from rgpparseword
+
+void VOX_DeleteWord( int iword )
+{
+ if (iword < 0 || iword >= CVOXWORDMAX)
+ return;
+
+ rgpparseword[iword] = 0;
+
+ // slide all words > iword up into vacated slot
+
+ for (int j = iword; j < CVOXWORDMAX-1; j++)
+ rgpparseword[j] = rgpparseword[j+1];
+}
+
+
+// get global list of map names from sentences.txt
+// map names are stored in order in V_MAPNAMES group
+
+void VOX_LookupMapnames( void )
+{
+ // get group V_MAPNAMES
+
+ int i;
+ char *psz;
+ int inext = 0;
+
+ for (i = 0; i < CVOXMAPNAMESMAX; i++)
+ {
+ // step sequentially through group - return ptr to 1st word in each group (map name)
+
+ psz = VOX_LookupSentenceByIndex( "V_MAPNAME", i, &inext );
+
+ if (!psz)
+ return;
+
+ g_rgmapnames[i] = psz;
+ g_cmapnames++;
+ }
+}
+
+// get index of current map name
+// return 0 as default index if not found
+
+int VOX_GetMapNameIndex( const char *pszmapname )
+{
+ for (int i = 0; i < g_cmapnames; i++)
+ {
+ if ( Q_strstr( pszmapname, g_rgmapnames[i] ) )
+ return i;
+ }
+ return 0;
+}
+
+// look for virtual 'V_' values in rgpparseword.
+ // V_MYNAME - replace with saved name value (based on type + entity)
+ // - if no saved name, create one and save
+ // V_MYNUM - replace with saved number value (based on type + entity)
+ // - if no saved num, create on and save
+ // V_RNDNUM - grab a random number string from V_RNDNUM_<type>
+ // V_RNDNAME - grab a random name string from V_RNDNAME_<type>
+
+ // replace any 'V_' values with actual string names in rgpparseword
+
+extern ConVar host_map;
+inline bool IsVirtualName( const char *pName )
+{
+ return (pName[0] == 'V' && pName[1] == '_');
+}
+
+void VOX_ReplaceVirtualNames( channel_t *pchan )
+{
+ // for each word in the sentence, check for V_, if found
+ // replace virtual word with saved word or rnd word
+
+ int i = 0;
+ char *pszNew = NULL;
+ char *pszNew1 = NULL;
+ char *pszNew2 = NULL;
+ int iname = -1;
+ int cnew = 0;
+ bool fbymap;
+ char *pszmaptoken;
+ SoundSource soundsource = pchan ? pchan->soundsource : 0;
+
+ const char *pszmap = host_map.GetString();
+
+ // get global list of map names from sentences.txt
+
+ while (rgpparseword[i])
+ {
+
+ if ( IsVirtualName( rgpparseword[i] ) )
+ {
+ iname = -1;
+ cnew = 0;
+ pszNew = NULL;
+ pszNew1 = NULL;
+ pszNew2 = NULL;
+ char szparseword[256];
+
+ int slen = Q_strlen(rgpparseword[i]);
+ char chtype = rgpparseword[i][slen-1];
+
+ // copy word to temp location so we can perform in-place substitutions
+
+ V_strcpy_safe(szparseword, rgpparseword[i]);
+
+ // fbymap is true if lookup is performed via mapname instead of via ordinal
+
+ pszmaptoken = ( Q_strstr(szparseword, "_MAP__") );
+
+ fbymap = (pszmaptoken == NULL ? false : true);
+
+ if (fbymap)
+ {
+ int imap = VOX_GetMapNameIndex( pszmap );
+ imap = clamp (imap, 0, 99);
+
+ // replace last 2 characters in _MAP__ substring
+ // with imap - this effectively makes all
+ // '_map_' lookups relative to the mapname
+ if ( imap >= 10 )
+ {
+ pszmaptoken[4] = (imap/10) + '0';
+ pszmaptoken[5] = (imap%10) + '0';
+ }
+ else
+ {
+ pszmaptoken[4] = '0';
+ pszmaptoken[5] = imap + '0';
+ }
+ }
+
+ if ( Q_strstr(szparseword, "V_MYNAME") )
+ {
+ iname = 1;
+ }
+ else if ( Q_strstr(szparseword, "V_MYNUM") )
+ {
+ iname = 0;
+ }
+
+ if ( iname >= 0 )
+ {
+
+ // lookup ent & type, return static, null terminated string
+ // if no saved string, create one
+
+ pszNew = VOX_LookupMyVirtual( iname, szparseword, chtype, soundsource);
+ cnew = 1;
+ }
+ else
+ {
+ if ( Q_strstr(szparseword, "V_RND") )
+ {
+ // lookup random first word from this named group,
+ // return static, null terminated string
+
+ pszNew = VOX_LookupRndVirtual( szparseword );
+ cnew = 1;
+ }
+ else if ( Q_strstr(szparseword, "V_DIST") )
+ {
+ // get range from ent to player, return pointers to new words
+ VOX_LookupRangeHeadingOrGrid( 0, szparseword, pchan, soundsource, &pszNew, &pszNew1, &pszNew2, &cnew, true );
+ }
+ else if ( Q_strstr(szparseword, "V_DIR") )
+ {
+ // get heading from ent to player, return pointers to new words
+ VOX_LookupRangeHeadingOrGrid( 1, szparseword, pchan, soundsource, &pszNew, &pszNew1, &pszNew2, &cnew, false);
+ }
+ else if ( Q_strstr(szparseword, "V_IDIED") )
+ {
+ // SILENT MARKER - this ent died - mark as dead and timestamp
+
+ int ient = VOX_LookupEntIndex( chtype, soundsource, false);
+ if (ient < 0)
+ {
+ // if not found, allocate new ent, give him a name & number, mark as dead
+ char szgroup1[32];
+ char szgroup2[32];
+ V_strcpy_safe(szgroup1, "V_MYNAME");
+ szgroup1[8] = chtype;
+ szgroup1[9] = 0;
+
+ V_strcpy_safe(szgroup2, "V_MYNUM");
+ szgroup2[7] = chtype;
+ szgroup2[8] = 0;
+
+ ient = VOX_LookupEntIndex( chtype, soundsource, true);
+ g_entnames[ient].pszname = VOX_LookupRndVirtual( szgroup1 );
+ g_entnames[ient].psznum = VOX_LookupRndVirtual( szgroup2 );
+ }
+
+ g_entnames[ient].fdied = true;
+ g_entnames[ient].timedied = g_pSoundServices->GetClientTime();
+
+ // clear this 'silent' word from rgpparseword
+
+ VOX_DeleteWord(i);
+
+ }
+ else if ( Q_strstr(szparseword, "V_WHODIED") )
+ {
+ // get last dead unit of this type
+
+ int ient = VOX_LookupLastDeadIndex( chtype );
+
+ // get name and number
+
+ if (ient >= 0)
+ {
+ cnew = 1;
+ pszNew = g_entnames[ient].pszname;
+ pszNew1 = g_entnames[ient].psznum;
+ if (pszNew1)
+ cnew++;
+ }
+ else
+ {
+ // no dead units, just clear V_WHODIED
+
+ VOX_DeleteWord(i);
+ }
+
+ }
+ else if ( Q_strstr(szparseword, "V_SECTOR") )
+ {
+ // sectors are fictional - they simply
+ // increase sequentially and expire every 5 minutes
+
+ pszNew = VOX_LookupSectorVirtual( szparseword );
+ if (pszNew)
+ cnew = 1;
+ }
+ else if ( Q_strstr(szparseword, "V_GRIDX") )
+ {
+ // player x position in 10 meter increments
+ VOX_LookupRangeHeadingOrGrid( 2, szparseword, pchan, soundsource, &pszNew, &pszNew1, &pszNew2, &cnew, true );
+ }
+ else if ( Q_strstr(szparseword, "V_GRIDY") )
+ {
+ // player y position in 10 meter increments
+ VOX_LookupRangeHeadingOrGrid( 3, szparseword, pchan, soundsource, &pszNew, &pszNew1, &pszNew2, &cnew, true );
+
+ }
+ else if ( Q_strstr(szparseword, "V_G0_") )
+ {
+ // 4 rnd globals per type, globals expire after 5 minutes
+ // used for target designation, master sector code name etc.
+
+ pszNew = VOX_LookupGlobalVirtual( chtype, soundsource, szparseword, 0 );
+ if (pszNew)
+ cnew = 1;
+ }
+ else if ( Q_strstr(szparseword, "V_G1_") )
+ {
+ // 4 rnd globals per type, globals expire after 5 minutes
+ // used for target designation, master sector code name etc.
+
+ pszNew = VOX_LookupGlobalVirtual( chtype, soundsource, szparseword, 1 );
+ if (pszNew)
+ cnew = 1;
+ }
+ else if ( Q_strstr(szparseword, "V_G2_") )
+ {
+ // 4 rnd globals per type, globals expire after 5 minutes
+ // used for target designation, master sector code name etc.
+
+ pszNew = VOX_LookupGlobalVirtual( chtype, soundsource, szparseword, 2 );
+ if (pszNew)
+ cnew = 1;
+ }
+ else if ( Q_strstr(szparseword, "V_G3_") )
+ {
+ // 4 rnd globals per type, globals expire after 5 minutes
+ // used for target designation, master sector code name etc.
+
+ pszNew = VOX_LookupGlobalVirtual( chtype, soundsource, szparseword, 3 );
+ if (pszNew)
+ cnew = 1;
+ }
+ else if ( Q_strstr(szparseword, "V_SEQG0_") )
+ {
+ // 4 sequential globals per type, selected sequentially in list
+ // used for total target hit count etc.
+
+ pszNew = VOX_LookupGlobalSeqVirtual( chtype, soundsource, szparseword, 0 );
+ if (pszNew)
+ cnew = 1;
+ }
+ else if ( Q_strstr(szparseword, "V_SEQG1_") )
+ {
+ // 4 sequential globals per type, selected sequentially in list
+ // used for total target hit count etc.
+
+ pszNew = VOX_LookupGlobalSeqVirtual( chtype, soundsource, szparseword, 1 );
+ if (pszNew)
+ cnew = 1;
+ }
+ else if ( Q_strstr(szparseword, "V_SEQG2_") )
+ {
+ // 4 sequential globals per type, selected sequentially in list
+ // used for total target hit count etc.
+
+ pszNew = VOX_LookupGlobalSeqVirtual( chtype, soundsource, szparseword, 2 );
+ if (pszNew)
+ cnew = 1;
+ }
+ else if ( Q_strstr(szparseword, "V_SEQG3_") )
+ {
+ // 4 sequential globals per type, selected sequentially in list
+ // used for total target hit count etc.
+
+ pszNew = VOX_LookupGlobalSeqVirtual( chtype, soundsource, szparseword, 3 );
+ if (pszNew)
+ cnew = 1;
+ }
+
+ }
+
+ // insert up to 3 new words into rgpparseword at 'i' location
+
+ VOX_InsertWords( i, cnew, pszNew, pszNew1, pszNew2 );
+ }
+ i++;
+ }
+}
+
+void VOX_Precache( IEngineSound *pSoundSystem, int sentenceIndex, const char *pPathOverride = NULL )
+{
+ voxword_t rgvoxword[CVOXWORDMAX];
+ char buffer[512];
+ char szpath[MAX_PATH];
+ char pathbuffer[MAX_PATH];
+ char *pWords[CVOXWORDMAX]; // array of pointers to parsed words
+
+ if ( !IsVirtualName(g_Sentences[sentenceIndex].pName))
+ {
+ g_Sentences[sentenceIndex].isPrecached = true;
+ }
+
+ memset(rgvoxword, 0, sizeof (voxword_t) * CVOXWORDMAX);
+ char *psz = (char *)(g_Sentences[sentenceIndex].pName + Q_strlen(g_Sentences[sentenceIndex].pName) + 1);
+ // get directory from string, advance psz
+ psz = VOX_GetDirectory(szpath, sizeof( szpath ), psz );
+ Q_strncpy(buffer, psz, sizeof( buffer ) );
+ psz = buffer;
+ if ( pPathOverride )
+ {
+ Q_strncpy(szpath, pPathOverride, sizeof(szpath));
+ }
+
+ // parse sentence (also inserts null terminators between words)
+
+ VOX_ParseString(psz);
+ int i = 0, count = 0;
+ // copy the parsed words out of the globals
+ for ( i = 0; rgpparseword[i]; i++ )
+ {
+ pWords[i] = rgpparseword[i];
+ count++;
+ }
+ int cword = 0;
+ for ( i = 0; i < count; i++ )
+ {
+ if ( IsVirtualName(pWords[i]) )
+ {
+ CUtlVector< WordBuf > list;
+
+ VOX_BuildVirtualNameList( pWords[i], list );
+
+ int c = list.Count();
+ for ( int j = 0 ; j < c; ++j )
+ {
+ Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s%s.wav", szpath, list[j].word );
+ pSoundSystem->PrecacheSound( pathbuffer, false );
+ }
+ }
+ else
+ {
+ // Get any pitch, volume, start, end params into voxword
+ if (VOX_ParseWordParams(pWords[i], &rgvoxword[cword], i == 0))
+ {
+ // this is a valid word (as opposed to a parameter block)
+ Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s%s.wav", szpath, pWords[i] );
+ // find name, if already in cache, mark voxword
+ // so we don't discard when word is done playing
+ pSoundSystem->PrecacheSound( pathbuffer, false );
+ cword++;
+ }
+ }
+ }
+}
+
+void VOX_PrecacheSentenceGroup( IEngineSound *pSoundSystem, const char *pGroupName, const char *pPathOverride )
+{
+ int i;
+
+ int len = Q_strlen( pGroupName );
+ for ( i = 0; i < g_Sentences.Count(); i++ )
+ {
+ if ( !g_Sentences[i].isPrecached && !Q_strncasecmp( g_Sentences[i].pName, pGroupName, len ) )
+ {
+ VOX_Precache( pSoundSystem, i, pPathOverride );
+ }
+ }
+}
+
+
+// link all sounds in sentence, start playing first word.
+// return number of words loaded
+void VOX_LoadSound( channel_t *pchan, const char *pszin )
+{
+#ifndef SWDS
+ char buffer[512];
+ int i, cword;
+ char pathbuffer[MAX_PATH];
+ char szpath[MAX_PATH];
+ voxword_t rgvoxword[CVOXWORDMAX];
+ char *psz;
+ bool emitcaption = false;
+ CUtlSymbol captionSymbol = UTL_INVAL_SYMBOL;
+ float duration = 0.0f;
+
+ if (!pszin)
+ return;
+
+ memset(rgvoxword, 0, sizeof (voxword_t) * CVOXWORDMAX);
+ memset(buffer, 0, sizeof(buffer));
+
+ // lookup actual string in g_Sentences,
+ // set pointer to string data
+
+ psz = VOX_LookupString(pszin, NULL, &emitcaption, &captionSymbol, &duration );
+
+ if (!psz)
+ {
+ DevMsg ("VOX_LoadSound: no sentence named %s\n",pszin);
+ return;
+ }
+
+ // get directory from string, advance psz
+ psz = VOX_GetDirectory(szpath, sizeof( szpath ), psz );
+
+ if ( Q_strlen(psz) > sizeof(buffer) - 1 )
+ {
+ DevMsg ("VOX_LoadSound: sentence is too long %s\n",psz);
+ return;
+ }
+
+ // copy into buffer
+ Q_strncpy(buffer, psz, sizeof( buffer ) );
+ psz = buffer;
+
+ // parse sentence (also inserts null terminators between words)
+
+ VOX_ParseString(psz);
+
+ // replace any 'V_' values with actual string names in rgpparseword
+
+ VOX_ReplaceVirtualNames( pchan );
+
+ // for each word in the sentence, construct the filename,
+ // lookup the sfx and save each pointer in a temp array
+
+ i = 0;
+ cword = 0;
+
+ char captionstream[ 1024 ];
+
+ char groupname[ 512 ];
+ Q_strncpy( groupname, pszin, sizeof( groupname ) );
+
+ int len = Q_strlen( groupname );
+
+ while ( len > 0 && V_isdigit( groupname[ len - 1 ] ) )
+ {
+ groupname[ len - 1 ] = 0;
+ --len;
+ }
+
+ Q_snprintf( captionstream, sizeof( captionstream ), "%s ", groupname );
+
+ while (rgpparseword[i])
+ {
+ // Get any pitch, volume, start, end params into voxword
+
+ if (VOX_ParseWordParams(rgpparseword[i], &rgvoxword[cword], i == 0))
+ {
+ // this is a valid word (as opposed to a parameter block)
+ Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s%s.wav", szpath, rgpparseword[i] );
+
+ // find name, if already in cache, mark voxword
+ // so we don't discard when word is done playing
+ rgvoxword[cword].sfx = S_FindName(pathbuffer,
+ &(rgvoxword[cword].fKeepCached));
+ // JAY: HACKHACK: Keep all sentences cached for now
+ rgvoxword[cword].fKeepCached = 1;
+
+ char captiontoken[ 128 ];
+ Q_snprintf( captiontoken, sizeof( captiontoken ), "S(%s%s) ", szpath, rgpparseword[i] );
+
+ Q_strncat( captionstream, captiontoken, sizeof( captionstream ), COPY_ALL_CHARACTERS );
+
+ cword++;
+ }
+ i++;
+ }
+
+ pchan->pMixer = NULL;
+
+ if (cword)
+ {
+ // some 'virtual' sentences can end up with 0 words
+ // if no words, then pchan->pMixer is null; chan will be released right away.
+
+ pchan->pMixer = CreateSentenceMixer( rgvoxword );
+ if ( !pchan->pMixer )
+ return;
+
+ pchan->flags.isSentence = true;
+ pchan->sfx = rgvoxword[0].sfx;
+ Assert(pchan->sfx);
+
+ if ( g_pSoundServices )
+ {
+ if ( emitcaption )
+ {
+ if ( captionSymbol != UTL_INVAL_SYMBOL )
+ {
+ g_pSoundServices->EmitCloseCaption( captionSymbol.String(), duration );
+
+ if ( snd_vox_captiontrace.GetBool() )
+ {
+ Msg( "Vox: caption '%s'\n", captionSymbol.String() );
+ }
+ }
+ else
+ {
+ g_pSoundServices->EmitSentenceCloseCaption( captionstream );
+
+ if ( snd_vox_captiontrace.GetBool() )
+ {
+ Msg( "Vox: captionstream '%s'\n", captionstream );
+ }
+ }
+ }
+ else
+ {
+ if ( snd_vox_captiontrace.GetBool() )
+ {
+ Msg( "Vox: No caption for '%s'\n", pszin ? pszin : "NULL" );
+ }
+ }
+ }
+ }
+
+#endif
+}
+
+static bool CCPairLessFunc( const ccpair& lhs, const ccpair& rhs )
+{
+ return Q_stricmp( lhs.token.word, rhs.token.word ) < 0;
+}
+
+void VOX_AddNumbers( char *pGroupName, CUtlVector< WordBuf >& list )
+{
+ // construct group name from V_NUMBERS + TYPE
+ for ( int i = 0; i <= 30; ++i )
+ {
+ char sznumbers[16];
+ int glen = Q_strlen(pGroupName);
+ int slen = Q_strlen("V_NUMBERS");
+
+ V_strcpy_safe(sznumbers, "V_NUMBERS");
+
+ // insert type character
+ sznumbers[slen] = pGroupName[glen-1];
+ sznumbers[slen+1] = 0;
+
+ WordBuf w;
+ // w.Set( VOX_LookupString( VOX_LookupSentenceByIndex( sznumbers, i, NULL ), NULL ) );
+ w.Set( VOX_LookupSentenceByIndex( sznumbers, i, NULL ) );
+ list.AddToTail( w );
+ }
+}
+
+void VOX_AddRndVirtual( char *pGroupName, CUtlVector< WordBuf >& list )
+{
+ // get group index
+
+ int isentenceg = VOX_GroupIndexFromName( pGroupName );
+
+ if ( isentenceg < 0)
+ return;
+
+ char szsentencename[32];
+
+ char const *szgroupname = g_SentenceGroups[ isentenceg ].GroupName();
+
+ // get pointer to sentence name within group, using lru
+ for ( int snum = 0; snum < g_SentenceGroups[ isentenceg ].count; ++snum )
+ {
+ Q_snprintf( szsentencename, sizeof( szsentencename ), "%s%d", szgroupname, snum );
+
+ char *psz = VOX_LookupString( szsentencename[0] == '!' ? szsentencename+1 : szsentencename, NULL);
+
+ if ( psz )
+ {
+ WordBuf w;
+ w.Set( psz );
+ list.AddToTail( w );
+ }
+ }
+}
+
+void VOX_AddMyVirtualWords( int iname, char *pGroupName, char chtype, CUtlVector< WordBuf >& list )
+{
+ VOX_AddRndVirtual( pGroupName, list );
+}
+
+void VOX_BuildVirtualNameList( char *word, CUtlVector< WordBuf >& list )
+{
+ // for each word in the sentence, check for V_, if found
+ // replace virtual word with saved word or rnd word
+
+ int iname = -1;
+ bool fbymap;
+ char *pszmaptoken;
+
+
+ char szparseword[256];
+
+ int slen = Q_strlen(word);
+ char chtype = word[slen-1];
+
+ // copy word to temp location so we can perform in-place substitutions
+
+ Q_strncpy( szparseword, word, sizeof( szparseword ) );
+
+ // fbymap is true if lookup is performed via mapname instead of via ordinal
+
+ pszmaptoken = ( Q_strstr(szparseword, "_MAP__") );
+
+ fbymap = (pszmaptoken == NULL ? false : true);
+
+ if (fbymap)
+ {
+ for ( int imap = 0; imap < g_cmapnames; ++imap )
+ {
+ // replace last 2 characters in _MAP__ substring
+ // with imap - this effectively makes all
+ // '_map_' lookups relative to the mapname
+ pszmaptoken[4] = '0';
+ if (imap < 10)
+ Q_snprintf( &(pszmaptoken[5]), 1, "%1d", imap );
+ else
+ Q_snprintf( &(pszmaptoken[4]), 2, "%d", imap );
+
+ // Recurse...
+ VOX_BuildVirtualNameList( szparseword, list );
+ }
+ return;
+ }
+
+ if ( Q_strstr(szparseword, "V_MYNAME") )
+ {
+ iname = 1;
+ }
+ else if ( Q_strstr(szparseword, "V_MYNUM") )
+ {
+ iname = 0;
+ }
+
+ if ( iname >= 0 )
+ {
+
+ // lookup ent & type, return static, null terminated string
+ // if no saved string, create one
+
+ VOX_AddMyVirtualWords( iname, szparseword, chtype, list );
+ }
+ else
+ {
+ if ( Q_strstr(szparseword, "V_RND") )
+ {
+ // lookup random first word from this named group,
+ // return static, null terminated string
+ VOX_AddRndVirtual( szparseword, list );
+ }
+ else if ( Q_strstr(szparseword, "V_DIST") )
+ {
+ VOX_AddNumbers( szparseword, list );
+ }
+ else if ( Q_strstr(szparseword, "V_DIR") )
+ {
+ VOX_AddNumbers( szparseword, list );
+ }
+ else if ( Q_strstr(szparseword, "V_IDIED") )
+ {
+ // SILENT MARKER - this ent died - mark as dead and timestamp
+
+ // if not found, allocate new ent, give him a name & number, mark as dead
+ char szgroup1[32];
+ char szgroup2[32];
+ V_strcpy_safe(szgroup1, "V_MYNAME");
+ szgroup1[8] = chtype;
+ szgroup1[9] = 0;
+
+ V_strcpy_safe(szgroup2, "V_MYNUM");
+ szgroup2[7] = chtype;
+ szgroup2[8] = 0;
+
+ VOX_BuildVirtualNameList( szgroup1, list );
+ VOX_BuildVirtualNameList( szgroup2, list );
+ return;
+
+ }
+ else if ( Q_strstr(szparseword, "V_WHODIED") )
+ {
+ // get last dead unit of this type
+ /*
+
+ int ient = VOX_LookupLastDeadIndex( chtype );
+
+ // get name and number
+
+ if (ient >= 0)
+ {
+ cnew = 1;
+ pszNew = g_entnames[ient].pszname;
+ pszNew1 = g_entnames[ient].psznum;
+ if (pszNew1)
+ cnew++;
+ }
+ else
+ {
+ // no dead units, just clear V_WHODIED
+
+ VOX_DeleteWord(i);
+ }
+ */
+
+ }
+ else if ( Q_strstr(szparseword, "V_SECTOR") )
+ {
+ VOX_AddNumbers( szparseword, list );
+ }
+ else if ( Q_strstr(szparseword, "V_GRIDX") )
+ {
+ VOX_AddNumbers( szparseword, list );
+ }
+ else if ( Q_strstr(szparseword, "V_GRIDY") )
+ {
+ VOX_AddNumbers( szparseword, list );
+ }
+ else if ( Q_strstr(szparseword, "V_G0_") )
+ {
+ VOX_AddRndVirtual( szparseword, list );
+ }
+ else if ( Q_strstr(szparseword, "V_G1_") )
+ {
+ VOX_AddRndVirtual( szparseword, list );
+ }
+ else if ( Q_strstr(szparseword, "V_G2_") )
+ {
+ VOX_AddRndVirtual( szparseword, list );
+ }
+ else if ( Q_strstr(szparseword, "V_G3_") )
+ {
+ VOX_AddRndVirtual( szparseword, list );
+ }
+ else if ( Q_strstr(szparseword, "V_SEQG0_") )
+ {
+ VOX_AddRndVirtual( szparseword, list );
+ }
+ else if ( Q_strstr(szparseword, "V_SEQG1_") )
+ {
+ VOX_AddRndVirtual( szparseword, list );
+ }
+ else if ( Q_strstr(szparseword, "V_SEQG2_") )
+ {
+ VOX_AddRndVirtual( szparseword, list );
+ }
+ else if ( Q_strstr(szparseword, "V_SEQG3_") )
+ {
+ VOX_AddRndVirtual( szparseword, list );
+ }
+
+ }
+
+ if ( Q_strnicmp( szparseword, "V_", 2 ) )
+ {
+ WordBuf w;
+ w.Set( szparseword );
+ list.AddToTail( w );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: For generating reslists, adds the wavefile to the dictionary
+// Input : *fn -
+//-----------------------------------------------------------------------------
+void VOX_Touch( char const *fn, CUtlDict< int, int >& list )
+{
+ if ( list.Find( fn ) == list.InvalidIndex() )
+ {
+ list.Insert( fn );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Iterates the touch list and touches all referenced .wav files.
+// Input : int -
+// list -
+//-----------------------------------------------------------------------------
+void VOX_TouchSounds( CUtlDict< int, int >& list, CUtlRBTree< ccpair, int >& ccpairs, bool spewsentences )
+{
+ int i;
+ for ( i = list.First(); i != list.InvalidIndex(); i = list.Next( i ) )
+ {
+ char const *fn = list.GetElementName( i );
+
+ // Msg( "touch %s\n", fn );
+ char expanded[ 512 ];
+ Q_snprintf( expanded, sizeof( expanded ), "sound/%s", fn );
+
+ FileHandle_t fh = g_pFileSystem->Open( expanded, "rb" );
+ if ( FILESYSTEM_INVALID_HANDLE != fh )
+ {
+ g_pFileSystem->Close( fh );
+ }
+ }
+
+ if ( spewsentences )
+ {
+ for ( i = ccpairs.FirstInorder() ; i != ccpairs.InvalidIndex(); i = ccpairs.NextInorder( i ) )
+ {
+ ccpair& pair = ccpairs[ i ];
+
+ Msg( "\"%s\"\t\"%s\"\n",
+ pair.token.word,
+ pair.value.word );
+ }
+
+ FileHandle_t fh = g_pFileSystem->Open( "sentences.m3u", "wt", "GAME" );
+ if ( FILESYSTEM_INVALID_HANDLE != fh )
+ {
+ for ( i = ccpairs.FirstInorder() ; i != ccpairs.InvalidIndex(); i = ccpairs.NextInorder( i ) )
+ {
+ ccpair& pair = ccpairs[ i ];
+
+ char outline[ 512 ];
+ Q_snprintf( outline, sizeof( outline ), "%s\n", pair.fullpath.word );
+
+ g_pFileSystem->Write( outline, Q_strlen(outline), fh );
+ }
+
+ g_pFileSystem->Close( fh );
+ }
+ }
+}
+
+// link all sounds in sentence, start playing first word.
+// return number of words loaded
+void VOX_TouchSound( const char *pszin, CUtlDict< int, int >& filelist, CUtlRBTree< ccpair, int >& ccpairs, bool spewsentences )
+{
+#ifndef SWDS
+ char buffer[512];
+ int i, cword;
+ char pathbuffer[MAX_PATH];
+ char szpath[MAX_PATH];
+ voxword_t rgvoxword[CVOXWORDMAX];
+ char *psz;
+
+ if (!pszin)
+ return;
+
+ memset(rgvoxword, 0, sizeof (voxword_t) * CVOXWORDMAX);
+ memset(buffer, 0, sizeof(buffer));
+
+ // lookup actual string in g_Sentences,
+ // set pointer to string data
+
+ psz = VOX_LookupString(pszin, NULL);
+
+ if (!psz)
+ {
+ DevMsg ("VOX_TouchSound: no sentence named %s\n",pszin);
+ return;
+ }
+
+ // get directory from string, advance psz
+ psz = VOX_GetDirectory(szpath, sizeof( szpath ), psz );
+
+ if ( Q_strlen(psz) > sizeof(buffer) - 1 )
+ {
+ DevMsg ("VOX_TouchSound: sentence is too long %s\n",psz);
+ return;
+ }
+
+ // copy into buffer
+ Q_strncpy(buffer, psz, sizeof( buffer ) );
+ psz = buffer;
+
+ // parse sentence (also inserts null terminators between words)
+
+ VOX_ParseString(psz);
+
+ // for each word in the sentence, construct the filename,
+ // lookup the sfx and save each pointer in a temp array
+
+ i = 0;
+ cword = 0;
+
+ CUtlVector< WordBuf > rep;
+
+ while (rgpparseword[i])
+ {
+ // Get any pitch, volume, start, end params into voxword
+
+ if ( VOX_ParseWordParams(rgpparseword[i], &rgvoxword[cword], i == 0 ) )
+ {
+ // Iterate all virtuals here...
+ if ( !Q_strnicmp( rgpparseword[i], "V_", 2 ) )
+ {
+ CUtlVector< WordBuf > list;
+
+ VOX_BuildVirtualNameList( rgpparseword[i], list );
+
+ int c = list.Count();
+ for ( int j = 0 ; j < c; ++j )
+ {
+ char name[ 256 ];
+ Q_snprintf( name, sizeof( name ), "%s", list[ j ].word );
+
+ if ( !Q_strnicmp( name, "V_", 2 ) )
+ {
+ Warning( "VOX_TouchSound didn't resolve virtual token %s!\n", name );
+ }
+
+ Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s%s.wav", szpath, name );
+ VOX_Touch( pathbuffer, filelist );
+
+ WordBuf w;
+ if ( j == 0 )
+ {
+ w.Set( name );
+ rep.AddToTail( w );
+ }
+ ccpair pair;
+ Q_snprintf( pair.token.word, sizeof( pair.token.word ), "S(%s%s)", szpath, name );
+ pair.value.Set( name );
+
+ Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s/sound/%s%s.wav", g_pSoundServices->GetGameDir(), szpath, name );
+ Q_FixSlashes( pathbuffer, '\\' );
+ pair.fullpath.Set( pathbuffer );
+
+ if ( ccpairs.Find( pair ) == ccpairs.InvalidIndex() )
+ {
+ ccpairs.Insert( pair );
+ }
+ }
+ }
+ else
+ {
+ // this is a valid word (as opposed to a parameter block)
+ Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s%s.wav", szpath, rgpparseword[i] );
+ VOX_Touch( pathbuffer, filelist );
+
+ WordBuf w;
+ w.Set( rgpparseword[ i ] );
+ rep.AddToTail( w );
+
+ ccpair pair;
+ Q_snprintf( pair.token.word, sizeof( pair.token.word ), "S(%s%s)", szpath, rgpparseword[i] );
+ pair.value.Set( rgpparseword[i] );
+
+ Q_snprintf( pathbuffer, sizeof( pathbuffer ), "%s/sound/%s%s.wav", g_pSoundServices->GetGameDir(), szpath, rgpparseword[ i ] );
+ Q_FixSlashes( pathbuffer, CORRECT_PATH_SEPARATOR );
+ pair.fullpath.Set( pathbuffer );
+
+ if ( ccpairs.Find( pair ) == ccpairs.InvalidIndex() )
+ {
+ ccpairs.Insert( pair );
+ }
+ }
+ }
+ i++;
+ }
+
+ if ( spewsentences )
+ {
+ char outbuf[ 1024 ];
+ // Build representative text
+ outbuf[ 0 ] = 0;
+ for ( i = 0; i < rep.Count(); ++i )
+ {
+ /*
+ if ( !Q_stricmp( rep[ i ].word, "_comma" ) )
+ {
+ if ( i != 0 && Q_strlen( outbuf ) >= 1 )
+ {
+ outbuf[ Q_strlen( outbuf ) - 1 ] =0;
+ }
+
+ // Don't end sentence with comma..
+ if ( i != rep.Count() - 1 )
+ {
+ Q_strncat( outbuf, ", ", sizeof( outbuf ), COPY_ALL_CHARACTERS );
+ }
+ continue;
+ }
+ */
+
+ Q_strncat( outbuf, rep[ i ].word, sizeof( outbuf ), COPY_ALL_CHARACTERS );
+ if ( i != rep.Count() - 1 )
+ {
+ Q_strncat( outbuf, " ", sizeof( outbuf ), COPY_ALL_CHARACTERS );
+ }
+ }
+
+ Msg( " %s\n", outbuf );
+ }
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Take a NULL terminated sentence, and parse any commands contained in
+// {}. The string is rewritten in place with those commands removed.
+//
+// Input : *pSentenceData - sentence data to be modified in place
+// sentenceIndex - global sentence table index for any data that is
+// parsed out
+//-----------------------------------------------------------------------------
+void VOX_ParseLineCommands( char *pSentenceData, int sentenceIndex )
+{
+ char tempBuffer[512];
+ char *pNext, *pStart;
+ int length, tempBufferPos = 0;
+
+ if ( !pSentenceData )
+ return;
+
+ pStart = pSentenceData;
+
+ while ( *pSentenceData )
+ {
+ pNext = ScanForwardUntil( pSentenceData, '{' );
+
+ // Find length of "good" portion of the string (not a {} command)
+ length = pNext - pSentenceData;
+ if ( tempBufferPos + length > sizeof(tempBuffer) )
+ {
+ DevMsg("Error! sentence too long!\n" );
+ return;
+ }
+
+ // Copy good string to temp buffer
+ memcpy( tempBuffer + tempBufferPos, pSentenceData, length );
+
+ // Move the copy position
+ tempBufferPos += length;
+
+ pSentenceData = pNext;
+
+ // Skip ahead of the opening brace
+ if ( *pSentenceData )
+ {
+ pSentenceData++;
+ }
+
+ while ( 1 )
+ {
+ // Skip whitespace
+ while ( *pSentenceData && *pSentenceData <= 32 )
+ {
+ pSentenceData++;
+ }
+
+ // Simple comparison of string commands:
+ switch( tolower( *pSentenceData ) )
+ {
+ case 'l':
+ // All commands starting with the letter 'l' here
+ if ( !Q_strnicmp( pSentenceData, "len", 3 ) )
+ {
+ g_Sentences[sentenceIndex].length = atof( pSentenceData + 3 ) ;
+
+ // "len " len + space
+ pSentenceData += 4;
+
+ // Skip until next } or whitespace character
+ while ( *pSentenceData && ( *pSentenceData != '}' && !( *pSentenceData <= 32 ) ) )
+ pSentenceData++;
+ }
+ break;
+ case 'c':
+ // This sentence should emit a close caption
+ if ( !Q_strnicmp( pSentenceData, "closecaption", 12 ) )
+ {
+ g_Sentences[sentenceIndex].closecaption = true;
+
+ pSentenceData += 12;
+
+ pSentenceData = (char *)COM_Parse( pSentenceData );
+
+ // Skip until next } or whitespace character
+ while ( *pSentenceData && ( *pSentenceData != '}' && !( *pSentenceData <= 32 ) ) )
+ pSentenceData++;
+
+ if ( Q_strlen( com_token ) > 0 )
+ {
+ g_Sentences[sentenceIndex].caption = com_token;
+ }
+ else
+ {
+ g_Sentences[sentenceIndex].caption = UTL_INVAL_SYMBOL;
+ }
+ }
+ break;
+ case 0:
+ default:
+ {
+ // Skip until next } or whitespace character
+ while ( *pSentenceData && ( *pSentenceData != '}' && !( *pSentenceData <= 32 ) ) )
+ pSentenceData++;
+ }
+ break;
+ }
+
+ // Done?
+ if ( !*pSentenceData || *pSentenceData == '}' )
+ {
+ break;
+ }
+ }
+
+ // pSentenceData = ScanForwardUntil( pSentenceData, '}' );
+
+ // Skip the closing brace
+ if ( *pSentenceData )
+ pSentenceData++;
+
+ // Skip trailing whitespace
+ while ( *pSentenceData && *pSentenceData <= 32 )
+ pSentenceData++;
+ }
+
+ if ( tempBufferPos < sizeof(tempBuffer) )
+ {
+ // terminate cleaned up copy
+ tempBuffer[ tempBufferPos ] = 0;
+
+ // Copy it over the original data
+ Q_strcpy( pStart, tempBuffer );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add a new group or increment count of the existing one
+// Input : *pSentenceName - text of the sentence name
+//-----------------------------------------------------------------------------
+int VOX_GroupAdd( const char *pSentenceName )
+{
+ int len = strlen( pSentenceName ) - 1;
+
+ // group members end in a number
+ if ( len <= 0 || !V_isdigit(pSentenceName[len]) )
+ return -1;
+
+ // truncate away the index
+ while ( len > 0 && V_isdigit(pSentenceName[len]) )
+ {
+ len--;
+ }
+
+ // make a copy of the actual group name
+ char *groupName = (char *)stackalloc( len + 2 );
+ Q_strncpy( groupName, pSentenceName, len+2 );
+
+ // check for it in the list
+ int i;
+ sentencegroup_t *pGroup;
+
+ CUtlSymbol symGroupName = sentencegroup_t::GetSymbol( groupName );
+ int groupCount = g_SentenceGroups.Size();
+ for ( i = 0; i < groupCount; i++ )
+ {
+ int groupIndex = (i + groupCount-1) % groupCount;
+
+ // Start at the last group a loop around
+ pGroup = &g_SentenceGroups[groupIndex];
+ if ( symGroupName == pGroup->GroupNameSymbol() )
+ {
+ // Matches previous group, bump count
+ pGroup->count++;
+ return i;
+ }
+ }
+
+ // new group
+ int addIndex = g_SentenceGroups.AddToTail();
+ sentencegroup_t *group = &g_SentenceGroups[addIndex];
+ group->SetGroupName( groupName );
+ group->count = 1;
+ return addIndex;
+}
+
+#if DEAD
+//-----------------------------------------------------------------------------
+// Purpose: clear the sentence groups
+//-----------------------------------------------------------------------------
+void VOX_GroupClear( void )
+{
+ g_SentenceGroups.RemoveAll();
+}
+#endif
+
+
+void VOX_LRUInit( sentencegroup_t *pGroup )
+{
+ int i, n1, n2, temp;
+
+ if ( pGroup->count )
+ {
+ unsigned char *pLRU = &g_GroupLRU[pGroup->lru];
+ for (i = 0; i < pGroup->count; i++)
+ pLRU[i] = (unsigned char) i;
+
+ // randomize array by swapping random elements
+ for (i = 0; i < (pGroup->count * 4); i++)
+ {
+ // FIXME: This should probably call through g_pSoundServices
+ // or some other such call?
+ n1 = RandomInt(0,pGroup->count-1);
+ n2 = RandomInt(0,pGroup->count-1);
+ temp = pLRU[n1];
+ pLRU[n1] = pLRU[n2];
+ pLRU[n2] = temp;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Init the LRU for each sentence group
+//-----------------------------------------------------------------------------
+void VOX_GroupInitAllLRUs( void )
+{
+ int i;
+
+ int totalCount = 0;
+ for ( i = 0; i < g_SentenceGroups.Size(); i++ )
+ {
+ g_SentenceGroups[i].lru = totalCount;
+ totalCount += g_SentenceGroups[i].count;
+ }
+ g_GroupLRU.Purge();
+ g_GroupLRU.EnsureCount( totalCount );
+ for ( i = 0; i < g_SentenceGroups.Size(); i++ )
+ {
+ VOX_LRUInit( &g_SentenceGroups[i] );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Only during reslist generation
+//-----------------------------------------------------------------------------
+void VOX_AddSentenceWavesToResList( void )
+{
+ if ( !CommandLine()->FindParm( "-makereslists" ) &&
+ !CommandLine()->FindParm( "-spewsentences" ) )
+ {
+ return;
+ }
+
+ bool spewsentences = CommandLine()->FindParm( "-spewsentences" ) != 0 ? true : false;
+
+ CUtlDict< int, int > list;
+ CUtlRBTree< ccpair, int > ccpairs( 0, 0, CCPairLessFunc );
+
+ int i;
+ int sentencecount = g_Sentences.Count();
+
+ for ( i = 0; i < sentencecount; i++ )
+ {
+ // Walk through all nonvirtual sentences and touch the referenced sounds...
+ sentence_t *pSentence = &g_Sentences[i];
+
+ if ( !Q_strnicmp( pSentence->pName, "V_", 2 ) )
+ {
+ continue;
+ }
+
+ if ( spewsentences )
+ {
+ const char *psz = VOX_LookupString(pSentence->pName, NULL);
+ if ( psz )
+ {
+ Msg( "%s : %s\n", pSentence->pName, psz );
+ }
+ }
+
+ VOX_TouchSound( pSentence->pName, list, ccpairs, spewsentences );
+
+ }
+
+ VOX_TouchSounds( list, ccpairs, spewsentences );
+
+ list.RemoveAll();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Given a group name, return that group's index
+// Input : *pGroupName - name of the group
+// Output : int - index in group table, returns -1 if no matching group is found
+//-----------------------------------------------------------------------------
+int VOX_GroupIndexFromName( const char *pGroupName )
+{
+ int i;
+
+ if ( pGroupName )
+ {
+ // search rgsentenceg for match on szgroupname
+ CUtlSymbol symGroupName = sentencegroup_t::GetSymbol( pGroupName );
+ for ( i = 0; i < g_SentenceGroups.Size(); i++ )
+ {
+ if ( symGroupName == g_SentenceGroups[i].GroupNameSymbol() )
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: return the group's name
+// Input : groupIndex - index of the group
+// Output : const char * - name pointer
+//-----------------------------------------------------------------------------
+const char *VOX_GroupNameFromIndex( int groupIndex )
+{
+ if ( groupIndex >= 0 && groupIndex < g_SentenceGroups.Size() )
+ return g_SentenceGroups[groupIndex].GroupName();
+
+ return NULL;
+}
+
+// ignore lru. pick next sentence from sentence group. Go in order until we hit the last sentence,
+// then repeat list if freset is true. If freset is false, then repeat last sentence.
+// ipick is passed in as the requested sentence ordinal.
+// ipick 'next' is returned.
+// return of -1 indicates an error.
+
+int VOX_GroupPickSequential( int isentenceg, char *szfound, int szfoundLen, int ipick, int freset )
+{
+ const char *szgroupname;
+ unsigned char count;
+
+ if (isentenceg < 0 || isentenceg > g_SentenceGroups.Size())
+ return -1;
+
+ szgroupname = g_SentenceGroups[isentenceg].GroupName();
+ count = g_SentenceGroups[isentenceg].count;
+
+ if (count == 0)
+ return -1;
+
+ if (ipick >= count)
+ ipick = count-1;
+
+ Q_snprintf( szfound, szfoundLen, "!%s%d", szgroupname, ipick );
+
+ if (ipick >= count)
+ {
+ if (freset)
+ // reset at end of list
+ return 0;
+ else
+ return count;
+ }
+
+ return ipick + 1;
+}
+
+
+
+// pick a random sentence from rootname0 to rootnameX.
+// picks from the rgsentenceg[isentenceg] least
+// recently used, modifies lru array. returns the sentencename.
+// note, lru must be seeded with 0-n randomized sentence numbers, with the
+// rest of the lru filled with -1. The first integer in the lru is
+// actually the size of the list. Returns ipick, the ordinal
+// of the picked sentence within the group.
+
+int VOX_GroupPick( int isentenceg, char *szfound, int strLen )
+{
+ const char *szgroupname;
+ unsigned char *plru;
+ unsigned char i;
+ unsigned char count;
+ unsigned char ipick=0;
+ int ffound = FALSE;
+
+ if (isentenceg < 0 || isentenceg > g_SentenceGroups.Size())
+ return -1;
+
+ szgroupname = g_SentenceGroups[isentenceg].GroupName();
+ count = g_SentenceGroups[isentenceg].count;
+ plru = &g_GroupLRU[g_SentenceGroups[isentenceg].lru];
+
+ while (!ffound)
+ {
+ for (i = 0; i < count; i++)
+ if (plru[i] != 0xFF)
+ {
+ ipick = plru[i];
+ plru[i] = 0xFF;
+ ffound = TRUE;
+ break;
+ }
+
+ if (!ffound)
+ {
+ VOX_LRUInit( &g_SentenceGroups[isentenceg] );
+ }
+ else
+ {
+ Q_snprintf( szfound, strLen, "!%s%d", szgroupname, ipick );
+ return ipick;
+ }
+ }
+ return -1;
+}
+
+
+struct filelist_t
+{
+ const char *pFileName;
+ filelist_t *pNext;
+};
+
+static filelist_t *g_pSentenceFileList = NULL;
+
+//-----------------------------------------------------------------------------
+// Purpose: clear / reinitialize the vox list
+//-----------------------------------------------------------------------------
+void VOX_ListClear( void )
+{
+ filelist_t *pList, *pNext;
+
+ pList = g_pSentenceFileList;
+
+ while ( pList )
+ {
+ pNext = pList->pNext;
+ free( pList );
+
+ pList = pNext;
+ }
+
+ g_pSentenceFileList = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Check to see if this file is in the list
+// Input : *psentenceFileName -
+// Output : int, true if the file is in the list, false if not
+//-----------------------------------------------------------------------------
+int VOX_ListFileIsLoaded( const char *psentenceFileName )
+{
+ filelist_t *pList = g_pSentenceFileList;
+ while ( pList )
+ {
+ if ( !strcmp( psentenceFileName, pList->pFileName ) )
+ return true;
+
+ pList = pList->pNext;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add this file name to the sentence list
+// Input : *psentenceFileName -
+//-----------------------------------------------------------------------------
+void VOX_ListMarkFileLoaded( const char *psentenceFileName )
+{
+ filelist_t *pEntry;
+ char *pName;
+
+ pEntry = (filelist_t *)malloc( sizeof(filelist_t) + strlen( psentenceFileName ) + 1);
+
+ if ( pEntry )
+ {
+ pName = (char *)(pEntry+1);
+ Q_strcpy( pName, psentenceFileName );
+
+ pEntry->pFileName = pName;
+ pEntry->pNext = g_pSentenceFileList;
+
+ g_pSentenceFileList = pEntry;
+ }
+}
+
+// This creates a compact copy of the sentence file in memory with only the necessary data
+void VOX_CompactSentenceFile()
+{
+ int totalMem = 0;
+ int i;
+ for ( i = 0; i < g_Sentences.Count(); i++ )
+ {
+ int len = Q_strlen( g_Sentences[i].pName ) + 1;
+ const char *pData = g_Sentences[i].pName + len;
+ int dataLen = Q_strlen( pData ) + 1;
+ totalMem += len + dataLen;
+ }
+ g_SentenceFile.EnsureCount( totalMem );
+ totalMem = 0;
+ for ( i = 0; i < g_Sentences.Count(); i++ )
+ {
+ int len = Q_strlen( g_Sentences[i].pName ) + 1;
+ const char *pData = g_Sentences[i].pName + len;
+ int dataLen = Q_strlen( pData ) + 1;
+ char *pDest = &g_SentenceFile[totalMem];
+ memcpy( pDest, g_Sentences[i].pName, len + dataLen );
+ g_Sentences[i].pName = pDest;
+ totalMem += len + dataLen;
+ }
+}
+
+// Load sentence file into memory, insert null terminators to
+// delimit sentence name/sentence pairs. Keep pointer to each
+// sentence name so we can search later.
+
+void VOX_ReadSentenceFile( const char *psentenceFileName )
+{
+ char *pch;
+ byte *pFileData;
+ int fileSize;
+ char c;
+ char *pchlast, *pSentenceData;
+ characterset_t whitespace;
+
+ // Have we already loaded this file?
+ if ( VOX_ListFileIsLoaded( psentenceFileName ) )
+ {
+ // must touch any sentence wavs again to ensure the map's init path gets the results
+ if ( MapReslistGenerator().IsLoggingToMap() )
+ {
+ VOX_AddSentenceWavesToResList();
+ }
+ return;
+ }
+
+ // load file
+
+ FileHandle_t file;
+ file = g_pFileSystem->Open( psentenceFileName, "rb" );
+ if ( FILESYSTEM_INVALID_HANDLE == file )
+ {
+ DevMsg ("Couldn't load %s\n", psentenceFileName);
+ return;
+ }
+
+ fileSize = g_pFileSystem->Size( file );
+ if ( fileSize <= 0 )
+ {
+ DevMsg ("VOX_ReadSentenceFile: %s has invalid size %i\n", psentenceFileName, fileSize );
+ g_pFileSystem->Close( file );
+ return;
+ }
+
+ pFileData = (byte *)g_pFileSystem->AllocOptimalReadBuffer( file, fileSize + 1 );
+ if ( !pFileData )
+ {
+ DevMsg ("VOX_ReadSentenceFile: %s couldn't allocate %i bytes for data\n", psentenceFileName, fileSize );
+ g_pFileSystem->Close( file );
+ return;
+ }
+
+ // Read the data and close the file
+ g_pFileSystem->ReadEx( pFileData, g_pFileSystem->GetOptimalReadSize( file, fileSize ), fileSize, file );
+ g_pFileSystem->Close( file );
+
+ // Make sure we end with a null terminator
+ pFileData[ fileSize ] = 0;
+
+ pch = (char *)pFileData;
+ pchlast = pch + fileSize;
+ CharacterSetBuild( &whitespace, "\n\r\t " );
+ const char *pName = 0;
+ while (pch < pchlast)
+ {
+ // Only process this pass on sentences
+ pSentenceData = NULL;
+
+ // skip newline, cr, tab, space
+
+ c = *pch;
+ while (pch < pchlast && IN_CHARACTERSET( whitespace, c ))
+ c = *(++pch);
+
+ // YWB: Fix possible crashes reading past end of file if the last line has only whitespace on it...
+ if ( !*pch )
+ break;
+
+ // skip entire line if first char is /
+ if (*pch != '/')
+ {
+ int addIndex = g_Sentences.AddToTail();
+ sentence_t *pSentence = &g_Sentences[addIndex];
+ pName = pch;
+ pSentence->pName = pch;
+ pSentence->length = 0;
+ pSentence->closecaption = false;
+ pSentence->isPrecached = false;
+ pSentence->caption = UTL_INVAL_SYMBOL;
+
+ // scan forward to first space, insert null terminator
+ // after sentence name
+
+ c = *pch;
+ while (pch < pchlast && c != ' ')
+ c = *(++pch);
+
+ if (pch < pchlast)
+ *pch++ = 0;
+
+ // A sentence may have some line commands, make an extra pass
+ pSentenceData = pch;
+ }
+ // scan forward to end of sentence or eof
+ while (pch < pchlast && pch[0] != '\n' && pch[0] != '\r')
+ pch++;
+
+ // insert null terminator
+ if (pch < pchlast)
+ *pch++ = 0;
+
+ // If we have some sentence data, parse out any line commands
+ if ( pSentenceData && pSentenceData < pchlast )
+ {
+ // Add a new group or increment count of the existing one
+ VOX_GroupAdd( pName );
+ int index = g_Sentences.Size()-1;
+ // The current sentence has an index of count-1
+ VOX_ParseLineCommands( pSentenceData, index );
+
+ }
+ }
+ // now compact the file data in memory
+ VOX_CompactSentenceFile();
+ g_pFileSystem->FreeOptimalReadBuffer( pFileData );
+
+ VOX_GroupInitAllLRUs();
+
+ // This only does stuff during reslist generation...
+ VOX_AddSentenceWavesToResList();
+
+ VOX_ListMarkFileLoaded( psentenceFileName );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the current number of sentences in the database
+// Output : int
+//-----------------------------------------------------------------------------
+int VOX_SentenceCount( void )
+{
+ return g_Sentences.Size();
+}
+
+
+float VOX_SentenceLength( int sentence_num )
+{
+ if ( sentence_num < 0 || sentence_num > g_Sentences.Size()-1 )
+ return 0.0f;
+
+ return g_Sentences[ sentence_num ].length;
+}
+
+// scan g_Sentences, looking for pszin sentence name
+// return pointer to sentence data if found, null if not
+// CONSIDER: if we have a large number of sentences, should
+// CONSIDER: sort strings in g_Sentences and do binary search.
+char *VOX_LookupString(const char *pSentenceName, int *psentencenum, bool *pbEmitCaption /*=NULL*/, CUtlSymbol *pCaptionSymbol /*=NULL*/, float *pflDuration /*= NULL*/ )
+{
+ if ( pbEmitCaption )
+ {
+ *pbEmitCaption = false;
+ }
+
+ if ( pCaptionSymbol )
+ {
+ *pCaptionSymbol = UTL_INVAL_SYMBOL;
+ }
+
+ if ( pflDuration )
+ {
+ *pflDuration = 0.0f;
+ }
+
+ int i;
+ int c = g_Sentences.Size();
+ for (i = 0; i < c; i++)
+ {
+ char const *name = g_Sentences[i].pName;
+
+ if (!stricmp(pSentenceName, name))
+ {
+ if (psentencenum)
+ {
+ *psentencenum = i;
+ }
+
+ if ( pbEmitCaption )
+ {
+ *pbEmitCaption = g_Sentences[ i ].closecaption;
+ }
+
+ if ( pCaptionSymbol )
+ {
+ *pCaptionSymbol = g_Sentences[ i ].caption;
+ }
+
+ if ( pflDuration )
+ {
+ *pflDuration = g_Sentences[ i ].length;
+ }
+
+ return (char *)(name + Q_strlen(name) + 1);
+ }
+ }
+ return NULL;
+}
+
+
+// Abstraction for sentence name array
+const char *VOX_SentenceNameFromIndex( int sentencenum )
+{
+ if ( sentencenum < g_Sentences.Size() )
+ return g_Sentences[sentencenum].pName;
+ return NULL;
+}
+
+
+
diff --git a/engine/audio/private/vox_private.h b/engine/audio/private/vox_private.h
new file mode 100644
index 0000000..cd29c05
--- /dev/null
+++ b/engine/audio/private/vox_private.h
@@ -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
diff --git a/engine/audio/public/ivoicecodec.h b/engine/audio/public/ivoicecodec.h
new file mode 100644
index 0000000..f7c43b3
--- /dev/null
+++ b/engine/audio/public/ivoicecodec.h
@@ -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
diff --git a/engine/audio/public/ivoicerecord.h b/engine/audio/public/ivoicerecord.h
new file mode 100644
index 0000000..9f830ff
--- /dev/null
+++ b/engine/audio/public/ivoicerecord.h
@@ -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
diff --git a/engine/audio/public/snd_audio_source.h b/engine/audio/public/snd_audio_source.h
new file mode 100644
index 0000000..b8ac6b9
--- /dev/null
+++ b/engine/audio/public/snd_audio_source.h
@@ -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
diff --git a/engine/audio/public/snd_device.h b/engine/audio/public/snd_device.h
new file mode 100644
index 0000000..9c844b6
--- /dev/null
+++ b/engine/audio/public/snd_device.h
@@ -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
diff --git a/engine/audio/public/snd_io.h b/engine/audio/public/snd_io.h
new file mode 100644
index 0000000..dd74798
--- /dev/null
+++ b/engine/audio/public/snd_io.h
@@ -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
diff --git a/engine/audio/public/sound.h b/engine/audio/public/sound.h
new file mode 100644
index 0000000..64b4f9d
--- /dev/null
+++ b/engine/audio/public/sound.h
@@ -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
diff --git a/engine/audio/public/soundservice.h b/engine/audio/public/soundservice.h
new file mode 100644
index 0000000..b1fb4df
--- /dev/null
+++ b/engine/audio/public/soundservice.h
@@ -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
diff --git a/engine/audio/public/voice.h b/engine/audio/public/voice.h
new file mode 100644
index 0000000..2bfea81
--- /dev/null
+++ b/engine/audio/public/voice.h
@@ -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
diff --git a/engine/audio/public/vox.h b/engine/audio/public/vox.h
new file mode 100644
index 0000000..6b50008
--- /dev/null
+++ b/engine/audio/public/vox.h
@@ -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