1183 lines
25 KiB
C++
1183 lines
25 KiB
C++
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||
|
//
|
||
|
// Purpose:
|
||
|
//
|
||
|
//=============================================================================//
|
||
|
|
||
|
#include "cbase.h"
|
||
|
#define WIN32_LEAN_AND_MEAN
|
||
|
#include <windows.h>
|
||
|
#include <mmsystem.h>
|
||
|
#include <stdio.h>
|
||
|
#include <math.h>
|
||
|
#include "snd_audio_source.h"
|
||
|
#include "AudioWaveOutput.h"
|
||
|
#include "ISceneManagerSound.h"
|
||
|
#include "utlvector.h"
|
||
|
#include "filesystem.h"
|
||
|
#include "sentence.h"
|
||
|
|
||
|
typedef struct channel_s
|
||
|
{
|
||
|
int leftvol;
|
||
|
int rightvol;
|
||
|
int rleftvol;
|
||
|
int rrightvol;
|
||
|
float pitch;
|
||
|
} channel_t;
|
||
|
|
||
|
#define INPUT_BUFFER_COUNT 32
|
||
|
|
||
|
class CAudioWaveInput : public CAudioInput
|
||
|
{
|
||
|
public:
|
||
|
CAudioWaveInput( void );
|
||
|
~CAudioWaveInput( void );
|
||
|
|
||
|
// Returns the current count of available samples
|
||
|
int SampleCount( void );
|
||
|
|
||
|
// returns the size of each sample in bytes
|
||
|
int SampleSize( void ) { return m_sampleSize; }
|
||
|
|
||
|
// returns the sampling rate of the data
|
||
|
int SampleRate( void ) { return m_sampleRate; }
|
||
|
|
||
|
// returns a pointer to the actual data
|
||
|
void *SampleData( void );
|
||
|
|
||
|
// release the available data (mark as done)
|
||
|
void SampleRelease( void );
|
||
|
|
||
|
// returns the mono/stereo status of this device (true if stereo)
|
||
|
bool IsStereo( void ) { return m_isStereo; }
|
||
|
|
||
|
// begin sampling
|
||
|
void Start( void );
|
||
|
|
||
|
// stop sampling
|
||
|
void Stop( void );
|
||
|
|
||
|
void WaveMessage( HWAVEIN hdevice, UINT uMsg, DWORD dwParam1, DWORD dwParam2 );
|
||
|
|
||
|
private:
|
||
|
void OpenDevice( void );
|
||
|
bool ValidDevice( void ) { return m_deviceId >= 0; }
|
||
|
void ClearDevice( void ) { m_deviceId = (UINT)-1; }
|
||
|
|
||
|
// returns true if the new format is better
|
||
|
bool BetterFormat( DWORD dwNewFormat, DWORD dwOldFormat );
|
||
|
|
||
|
void InitReadyList( void );
|
||
|
void AddToReadyList( WAVEHDR *pBuffer );
|
||
|
void PopReadyList( void );
|
||
|
|
||
|
WAVEHDR *m_pReadyList;
|
||
|
|
||
|
int m_sampleSize;
|
||
|
int m_sampleRate;
|
||
|
bool m_isStereo;
|
||
|
|
||
|
UINT m_deviceId;
|
||
|
HWAVEIN m_deviceHandle;
|
||
|
|
||
|
WAVEHDR *m_buffers[ INPUT_BUFFER_COUNT ];
|
||
|
};
|
||
|
|
||
|
extern "C" void CALLBACK WaveData( HWAVEIN hwi, UINT uMsg, CAudioWaveInput *pAudio, DWORD dwParam1, DWORD dwParam2 );
|
||
|
|
||
|
CAudioWaveInput::CAudioWaveInput( void )
|
||
|
{
|
||
|
memset( m_buffers, 0, sizeof( m_buffers ) );
|
||
|
int deviceCount = (int)waveInGetNumDevs();
|
||
|
UINT deviceId = (UINT)-1;
|
||
|
DWORD deviceFormat = 0;
|
||
|
|
||
|
int i;
|
||
|
for ( i = 0; i < deviceCount; i++ )
|
||
|
{
|
||
|
WAVEINCAPS waveCaps;
|
||
|
MMRESULT errorCode = waveInGetDevCaps( (UINT)i, &waveCaps, sizeof(waveCaps) );
|
||
|
if ( errorCode == MMSYSERR_NOERROR )
|
||
|
{
|
||
|
// valid device
|
||
|
if ( BetterFormat( waveCaps.dwFormats, deviceFormat ) )
|
||
|
{
|
||
|
deviceId = i;
|
||
|
deviceFormat = waveCaps.dwFormats;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( !deviceFormat )
|
||
|
{
|
||
|
m_deviceId = (UINT)-1;
|
||
|
m_sampleSize = 0;
|
||
|
m_sampleRate = 0;
|
||
|
m_isStereo = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_deviceId = deviceId;
|
||
|
m_sampleRate = 44100;
|
||
|
m_isStereo = false;
|
||
|
if ( deviceFormat & WAVE_FORMAT_4M16 )
|
||
|
{
|
||
|
m_sampleSize = 2;
|
||
|
}
|
||
|
else if ( deviceFormat & WAVE_FORMAT_4M08 )
|
||
|
{
|
||
|
m_sampleSize = 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// ERROR!
|
||
|
}
|
||
|
|
||
|
OpenDevice();
|
||
|
}
|
||
|
|
||
|
InitReadyList();
|
||
|
}
|
||
|
|
||
|
CAudioWaveInput::~CAudioWaveInput( void )
|
||
|
{
|
||
|
if ( ValidDevice() )
|
||
|
{
|
||
|
Stop();
|
||
|
waveInReset( m_deviceHandle );
|
||
|
waveInClose( m_deviceHandle );
|
||
|
for ( int i = 0; i < INPUT_BUFFER_COUNT; i++ )
|
||
|
{
|
||
|
if ( m_buffers[i] )
|
||
|
{
|
||
|
waveInUnprepareHeader( m_deviceHandle, m_buffers[i], sizeof( *m_buffers[i] ) );
|
||
|
delete[] m_buffers[i]->lpData;
|
||
|
delete m_buffers[i];
|
||
|
}
|
||
|
m_buffers[i] = NULL;
|
||
|
}
|
||
|
ClearDevice();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CALLBACK WaveData( HWAVEIN hwi, UINT uMsg, CAudioWaveInput *pAudio, DWORD dwParam1, DWORD dwParam2 )
|
||
|
{
|
||
|
if ( pAudio )
|
||
|
{
|
||
|
pAudio->WaveMessage( hwi, uMsg, dwParam1, dwParam2 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CAudioWaveInput::WaveMessage( HWAVEIN hdevice, UINT uMsg, DWORD dwParam1, DWORD dwParam2 )
|
||
|
{
|
||
|
if ( hdevice != m_deviceHandle )
|
||
|
return;
|
||
|
switch( uMsg )
|
||
|
{
|
||
|
case WIM_DATA:
|
||
|
WAVEHDR *pHeader = (WAVEHDR *)dwParam1;
|
||
|
AddToReadyList( pHeader );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CAudioWaveInput::OpenDevice( void )
|
||
|
{
|
||
|
if ( !ValidDevice() )
|
||
|
return;
|
||
|
|
||
|
WAVEFORMATEX format;
|
||
|
|
||
|
memset( &format, 0, sizeof(format) );
|
||
|
format.nAvgBytesPerSec = m_sampleRate * m_sampleSize;
|
||
|
format.nChannels = 1;
|
||
|
format.wBitsPerSample = m_sampleSize * 8;
|
||
|
format.nSamplesPerSec = m_sampleRate;
|
||
|
format.wFormatTag = WAVE_FORMAT_PCM;
|
||
|
format.nBlockAlign = m_sampleSize;
|
||
|
|
||
|
MMRESULT errorCode = waveInOpen( &m_deviceHandle, m_deviceId, &format, (DWORD)WaveData, (DWORD)this, CALLBACK_FUNCTION );
|
||
|
if ( errorCode == MMSYSERR_NOERROR )
|
||
|
{
|
||
|
// valid device opened
|
||
|
int bufferSize = m_sampleSize * m_sampleRate / INPUT_BUFFER_COUNT; // total of one second of data
|
||
|
|
||
|
// allocate buffers
|
||
|
for ( int i = 0; i < INPUT_BUFFER_COUNT; i++ )
|
||
|
{
|
||
|
m_buffers[i] = new WAVEHDR;
|
||
|
m_buffers[i]->lpData = new char[ bufferSize ];
|
||
|
m_buffers[i]->dwBufferLength = bufferSize;
|
||
|
m_buffers[i]->dwUser = 0;
|
||
|
m_buffers[i]->dwFlags = 0;
|
||
|
|
||
|
waveInPrepareHeader( m_deviceHandle, m_buffers[i], sizeof( *m_buffers[i] ) );
|
||
|
waveInAddBuffer( m_deviceHandle, m_buffers[i], sizeof( *m_buffers[i] ) );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ClearDevice();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CAudioWaveInput::Start( void )
|
||
|
{
|
||
|
if ( !ValidDevice() )
|
||
|
return;
|
||
|
|
||
|
waveInStart( m_deviceHandle );
|
||
|
}
|
||
|
|
||
|
void CAudioWaveInput::Stop( void )
|
||
|
{
|
||
|
if ( !ValidDevice() )
|
||
|
return;
|
||
|
|
||
|
waveInStop( m_deviceHandle );
|
||
|
}
|
||
|
|
||
|
void CAudioWaveInput::InitReadyList( void )
|
||
|
{
|
||
|
m_pReadyList = NULL;
|
||
|
}
|
||
|
|
||
|
void CAudioWaveInput::AddToReadyList( WAVEHDR *pBuffer )
|
||
|
{
|
||
|
WAVEHDR **pList = &m_pReadyList;
|
||
|
|
||
|
waveInUnprepareHeader( m_deviceHandle, pBuffer, sizeof(*pBuffer) );
|
||
|
// insert at the tail of the list
|
||
|
while ( *pList )
|
||
|
{
|
||
|
pList = reinterpret_cast<WAVEHDR **>(&((*pList)->dwUser));
|
||
|
}
|
||
|
pBuffer->dwUser = NULL;
|
||
|
*pList = pBuffer;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CAudioWaveInput::PopReadyList( void )
|
||
|
{
|
||
|
if ( m_pReadyList )
|
||
|
{
|
||
|
WAVEHDR *pBuffer = m_pReadyList;
|
||
|
m_pReadyList = reinterpret_cast<WAVEHDR *>(m_pReadyList->dwUser);
|
||
|
waveInPrepareHeader( m_deviceHandle, pBuffer, sizeof(*pBuffer) );
|
||
|
waveInAddBuffer( m_deviceHandle, pBuffer, sizeof(*pBuffer) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
#define WAVE_FORMAT_STEREO (WAVE_FORMAT_1S08|WAVE_FORMAT_1S16|WAVE_FORMAT_2S08|WAVE_FORMAT_2S16|WAVE_FORMAT_4S08|WAVE_FORMAT_4S16)
|
||
|
#define WAVE_FORMATS_UNDERSTOOD (0xFFF)
|
||
|
#define WAVE_FORMAT_11K (WAVE_FORMAT_1M08|WAVE_FORMAT_1M16)
|
||
|
#define WAVE_FORMAT_22K (WAVE_FORMAT_2M08|WAVE_FORMAT_2M16)
|
||
|
#define WAVE_FORMAT_44K (WAVE_FORMAT_4M08|WAVE_FORMAT_4M16)
|
||
|
|
||
|
static int HighestBit( DWORD dwFlags )
|
||
|
{
|
||
|
int i = 31;
|
||
|
while ( i )
|
||
|
{
|
||
|
if ( dwFlags & (1<<i) )
|
||
|
return i;
|
||
|
i--;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
bool CAudioWaveInput::BetterFormat( DWORD dwNewFormat, DWORD dwOldFormat )
|
||
|
{
|
||
|
dwNewFormat &= WAVE_FORMATS_UNDERSTOOD & (~WAVE_FORMAT_STEREO);
|
||
|
dwOldFormat &= WAVE_FORMATS_UNDERSTOOD & (~WAVE_FORMAT_STEREO);
|
||
|
|
||
|
// our target format is 44.1KHz, mono, 16-bit
|
||
|
if ( HighestBit(dwOldFormat) >= HighestBit(dwNewFormat) )
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
int CAudioWaveInput::SampleCount( void )
|
||
|
{
|
||
|
if ( !ValidDevice() )
|
||
|
return 0;
|
||
|
|
||
|
if ( m_pReadyList )
|
||
|
{
|
||
|
switch( SampleSize() )
|
||
|
{
|
||
|
case 2:
|
||
|
return m_pReadyList->dwBytesRecorded >> 1;
|
||
|
case 1:
|
||
|
return m_pReadyList->dwBytesRecorded;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void *CAudioWaveInput::SampleData( void )
|
||
|
{
|
||
|
if ( !ValidDevice() )
|
||
|
return NULL;
|
||
|
|
||
|
if ( m_pReadyList )
|
||
|
{
|
||
|
return m_pReadyList->lpData;
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
// release the available data (mark as done)
|
||
|
void CAudioWaveInput::SampleRelease( void )
|
||
|
{
|
||
|
PopReadyList();
|
||
|
}
|
||
|
|
||
|
|
||
|
// factory to create a suitable audio input for this system
|
||
|
CAudioInput *CAudioInput::Create( void )
|
||
|
{
|
||
|
// sound source is a singleton for now
|
||
|
static CAudioInput *pSource = NULL;
|
||
|
|
||
|
if ( !pSource )
|
||
|
{
|
||
|
pSource = new CAudioWaveInput;
|
||
|
}
|
||
|
|
||
|
return pSource;
|
||
|
}
|
||
|
|
||
|
void CAudioDeviceSWMix::Mix8Mono( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, int rateScaleFix, int outCount, int timecompress, bool forward )
|
||
|
{
|
||
|
int sampleIndex = 0;
|
||
|
fixedint sampleFrac = inputOffset;
|
||
|
|
||
|
int fixup = 0;
|
||
|
int fixupstep = 1;
|
||
|
|
||
|
if ( !forward )
|
||
|
{
|
||
|
fixup = outCount - 1;
|
||
|
fixupstep = -1;
|
||
|
}
|
||
|
|
||
|
for ( int i = 0; i < outCount; i++, fixup += fixupstep )
|
||
|
{
|
||
|
int dest = max( outputOffset + fixup, 0 );
|
||
|
|
||
|
m_paintbuffer[ dest ].left += pChannel->leftvol * pData[sampleIndex];
|
||
|
m_paintbuffer[ dest ].right += pChannel->rightvol * pData[sampleIndex];
|
||
|
sampleFrac += rateScaleFix;
|
||
|
sampleIndex += FIX_INTPART(sampleFrac);
|
||
|
sampleFrac = FIX_FRACPART(sampleFrac);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CAudioDeviceSWMix::Mix8Stereo( channel_t *pChannel, char *pData, int outputOffset, int inputOffset, int rateScaleFix, int outCount, int timecompress, bool forward )
|
||
|
{
|
||
|
int sampleIndex = 0;
|
||
|
fixedint sampleFrac = inputOffset;
|
||
|
|
||
|
int fixup = 0;
|
||
|
int fixupstep = 1;
|
||
|
|
||
|
if ( !forward )
|
||
|
{
|
||
|
fixup = outCount - 1;
|
||
|
fixupstep = -1;
|
||
|
}
|
||
|
|
||
|
for ( int i = 0; i < outCount; i++, fixup += fixupstep )
|
||
|
{
|
||
|
int dest = max( outputOffset + fixup, 0 );
|
||
|
|
||
|
m_paintbuffer[ dest ].left += pChannel->leftvol * pData[sampleIndex];
|
||
|
m_paintbuffer[ dest ].right += pChannel->rightvol * pData[sampleIndex+1];
|
||
|
sampleFrac += rateScaleFix;
|
||
|
sampleIndex += FIX_INTPART(sampleFrac)<<1;
|
||
|
sampleFrac = FIX_FRACPART(sampleFrac);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CAudioDeviceSWMix::Mix16Mono( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, int rateScaleFix, int outCount, int timecompress, bool forward )
|
||
|
{
|
||
|
int sampleIndex = 0;
|
||
|
fixedint sampleFrac = inputOffset;
|
||
|
|
||
|
int fixup = 0;
|
||
|
int fixupstep = 1;
|
||
|
|
||
|
if ( !forward )
|
||
|
{
|
||
|
fixup = outCount - 1;
|
||
|
fixupstep = -1;
|
||
|
}
|
||
|
|
||
|
for ( int i = 0; i < outCount; i++, fixup += fixupstep )
|
||
|
{
|
||
|
int dest = max( outputOffset + fixup, 0 );
|
||
|
|
||
|
m_paintbuffer[ dest ].left += (pChannel->leftvol * pData[sampleIndex])>>8;
|
||
|
m_paintbuffer[ dest ].right += (pChannel->rightvol * pData[sampleIndex])>>8;
|
||
|
sampleFrac += rateScaleFix;
|
||
|
sampleIndex += FIX_INTPART(sampleFrac);
|
||
|
sampleFrac = FIX_FRACPART(sampleFrac);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CAudioDeviceSWMix::Mix16Stereo( channel_t *pChannel, short *pData, int outputOffset, int inputOffset, int rateScaleFix, int outCount, int timecompress, bool forward )
|
||
|
{
|
||
|
int sampleIndex = 0;
|
||
|
fixedint sampleFrac = inputOffset;
|
||
|
|
||
|
int fixup = 0;
|
||
|
int fixupstep = 1;
|
||
|
|
||
|
if ( !forward )
|
||
|
{
|
||
|
fixup = outCount - 1;
|
||
|
fixupstep = -1;
|
||
|
}
|
||
|
|
||
|
for ( int i = 0; i < outCount; i++, fixup += fixupstep )
|
||
|
{
|
||
|
int dest = max( outputOffset + fixup, 0 );
|
||
|
|
||
|
m_paintbuffer[ dest ].left += (pChannel->leftvol * pData[sampleIndex])>>8;
|
||
|
m_paintbuffer[ dest ].right += (pChannel->rightvol * pData[sampleIndex+1])>>8;
|
||
|
|
||
|
sampleFrac += rateScaleFix;
|
||
|
sampleIndex += FIX_INTPART(sampleFrac)<<1;
|
||
|
sampleFrac = FIX_FRACPART(sampleFrac);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
int CAudioDeviceSWMix::MaxSampleCount( void )
|
||
|
{
|
||
|
return PAINTBUFFER_SIZE;
|
||
|
}
|
||
|
|
||
|
void CAudioDeviceSWMix::MixBegin( void )
|
||
|
{
|
||
|
memset( m_paintbuffer, 0, sizeof(m_paintbuffer) );
|
||
|
}
|
||
|
|
||
|
void CAudioDeviceSWMix::TransferBufferStereo16( short *pOutput, int sampleCount )
|
||
|
{
|
||
|
for ( int i = 0; i < sampleCount; i++ )
|
||
|
{
|
||
|
if ( m_paintbuffer[i].left > 32767 )
|
||
|
m_paintbuffer[i].left = 32767;
|
||
|
else if ( m_paintbuffer[i].left < -32768 )
|
||
|
m_paintbuffer[i].left = -32768;
|
||
|
|
||
|
if ( m_paintbuffer[i].right > 32767 )
|
||
|
m_paintbuffer[i].right = 32767;
|
||
|
else if ( m_paintbuffer[i].right < -32768 )
|
||
|
m_paintbuffer[i].right = -32768;
|
||
|
|
||
|
*pOutput++ = (short)m_paintbuffer[i].left;
|
||
|
*pOutput++ = (short)m_paintbuffer[i].right;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CAudioWaveOutput::CAudioWaveOutput( void )
|
||
|
{
|
||
|
for ( int i = 0; i < OUTPUT_BUFFER_COUNT; i++ )
|
||
|
{
|
||
|
CAudioBuffer *buffer = &m_buffers[ i ];
|
||
|
Assert( buffer );
|
||
|
buffer->hdr = NULL;
|
||
|
buffer->submitted = false;
|
||
|
buffer->submit_sample_count = false;
|
||
|
}
|
||
|
|
||
|
ClearDevice();
|
||
|
OpenDevice();
|
||
|
|
||
|
m_mixTime = -1;
|
||
|
m_sampleIndex = 0;
|
||
|
memset( m_sourceList, 0, sizeof(m_sourceList) );
|
||
|
|
||
|
m_nEstimatedSamplesAhead = (int)( ( float ) OUTPUT_SAMPLE_RATE / 10.0f );
|
||
|
}
|
||
|
|
||
|
void CAudioWaveOutput::RemoveMixerChannelReferences( CAudioMixer *mixer )
|
||
|
{
|
||
|
for ( int i = 0; i < OUTPUT_BUFFER_COUNT; i++ )
|
||
|
{
|
||
|
RemoveFromReferencedList( mixer, &m_buffers[ i ] );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CAudioWaveOutput::AddToReferencedList( CAudioMixer *mixer, CAudioBuffer *buffer )
|
||
|
{
|
||
|
// Already in list
|
||
|
for ( int i = 0; i < buffer->m_Referenced.Size(); i++ )
|
||
|
{
|
||
|
if ( buffer->m_Referenced[ i ].mixer == mixer )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Just remove it
|
||
|
int idx = buffer->m_Referenced.AddToTail();
|
||
|
|
||
|
CAudioMixerState *state = &buffer->m_Referenced[ idx ];
|
||
|
state->mixer = mixer;
|
||
|
state->submit_mixer_sample = mixer->GetSamplePosition();
|
||
|
|
||
|
}
|
||
|
|
||
|
void CAudioWaveOutput::RemoveFromReferencedList( CAudioMixer *mixer, CAudioBuffer *buffer )
|
||
|
{
|
||
|
for ( int i = 0; i < buffer->m_Referenced.Size(); i++ )
|
||
|
{
|
||
|
if ( buffer->m_Referenced[ i ].mixer == mixer )
|
||
|
{
|
||
|
buffer->m_Referenced.Remove( i );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool CAudioWaveOutput::IsSoundInReferencedList( CAudioMixer *mixer, CAudioBuffer *buffer )
|
||
|
{
|
||
|
for ( int i = 0; i < buffer->m_Referenced.Size(); i++ )
|
||
|
{
|
||
|
if ( buffer->m_Referenced[ i ].mixer == mixer )
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool CAudioWaveOutput::IsSourceReferencedByActiveBuffer( CAudioMixer *mixer )
|
||
|
{
|
||
|
if ( !ValidDevice() )
|
||
|
return false;
|
||
|
|
||
|
CAudioBuffer *buffer;
|
||
|
for ( int i = 0; i < OUTPUT_BUFFER_COUNT; i++ )
|
||
|
{
|
||
|
buffer = &m_buffers[ i ];
|
||
|
if ( !buffer->submitted )
|
||
|
continue;
|
||
|
|
||
|
if ( buffer->hdr->dwFlags & WHDR_DONE )
|
||
|
continue;
|
||
|
|
||
|
// See if it's referenced
|
||
|
if ( IsSoundInReferencedList( mixer, buffer ) )
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
CAudioWaveOutput::~CAudioWaveOutput( void )
|
||
|
{
|
||
|
if ( ValidDevice() )
|
||
|
{
|
||
|
waveOutReset( m_deviceHandle );
|
||
|
for ( int i = 0; i < OUTPUT_BUFFER_COUNT; i++ )
|
||
|
{
|
||
|
if ( m_buffers[i].hdr )
|
||
|
{
|
||
|
waveOutUnprepareHeader( m_deviceHandle, m_buffers[i].hdr, sizeof(*m_buffers[i].hdr) );
|
||
|
delete[] m_buffers[i].hdr->lpData;
|
||
|
delete m_buffers[i].hdr;
|
||
|
}
|
||
|
m_buffers[i].hdr = NULL;
|
||
|
m_buffers[i].submitted = false;
|
||
|
m_buffers[i].submit_sample_count = 0;
|
||
|
m_buffers[i].m_Referenced.Purge();
|
||
|
}
|
||
|
waveOutClose( m_deviceHandle );
|
||
|
ClearDevice();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
CAudioBuffer *CAudioWaveOutput::GetEmptyBuffer( void )
|
||
|
{
|
||
|
CAudioBuffer *pOutput = NULL;
|
||
|
if ( ValidDevice() )
|
||
|
{
|
||
|
for ( int i = 0; i < OUTPUT_BUFFER_COUNT; i++ )
|
||
|
{
|
||
|
if ( !(m_buffers[ i ].submitted ) ||
|
||
|
m_buffers[i].hdr->dwFlags & WHDR_DONE )
|
||
|
{
|
||
|
pOutput = &m_buffers[i];
|
||
|
pOutput->submitted = true;
|
||
|
pOutput->m_Referenced.Purge();
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return pOutput;
|
||
|
}
|
||
|
|
||
|
void CAudioWaveOutput::SilenceBuffer( short *pSamples, int sampleCount )
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for ( i = 0; i < sampleCount; i++ )
|
||
|
{
|
||
|
// left
|
||
|
*pSamples++ = 0;
|
||
|
// right
|
||
|
*pSamples++ = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CAudioWaveOutput::Flush( void )
|
||
|
{
|
||
|
waveOutReset( m_deviceHandle );
|
||
|
}
|
||
|
|
||
|
// mix a buffer up to time (time is absolute)
|
||
|
void CAudioWaveOutput::Update( float time )
|
||
|
{
|
||
|
channel_t channel;
|
||
|
|
||
|
channel.leftvol = 200;
|
||
|
channel.rightvol = 200;
|
||
|
channel.pitch = 1.0;
|
||
|
|
||
|
if ( !ValidDevice() )
|
||
|
return;
|
||
|
|
||
|
// reset the system
|
||
|
if ( m_mixTime < 0 || time < m_baseTime )
|
||
|
{
|
||
|
m_baseTime = time;
|
||
|
m_mixTime = 0;
|
||
|
}
|
||
|
|
||
|
// put time in our coordinate frame
|
||
|
time -= m_baseTime;
|
||
|
|
||
|
if ( time > m_mixTime )
|
||
|
{
|
||
|
CAudioBuffer *pBuffer = GetEmptyBuffer();
|
||
|
|
||
|
// no free buffers, mixing is ahead of the playback!
|
||
|
if ( !pBuffer || !pBuffer->hdr )
|
||
|
{
|
||
|
//Con_Printf( "out of buffers\n" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// UNDONE: These numbers are constants
|
||
|
// calc number of samples (2 channels * 2 bytes per sample)
|
||
|
int sampleCount = pBuffer->hdr->dwBufferLength >> 2;
|
||
|
//float oldTime = m_mixTime;
|
||
|
|
||
|
m_mixTime += sampleCount * (1.0f / OUTPUT_SAMPLE_RATE);
|
||
|
|
||
|
short *pSamples = reinterpret_cast<short *>(pBuffer->hdr->lpData);
|
||
|
|
||
|
SilenceBuffer( pSamples, sampleCount );
|
||
|
|
||
|
int tempCount = sampleCount;
|
||
|
|
||
|
while ( tempCount > 0 )
|
||
|
{
|
||
|
if ( tempCount > m_audioDevice.MaxSampleCount() )
|
||
|
sampleCount = m_audioDevice.MaxSampleCount();
|
||
|
else
|
||
|
sampleCount = tempCount;
|
||
|
|
||
|
m_audioDevice.MixBegin();
|
||
|
for ( int i = 0; i < MAX_CHANNELS; i++ )
|
||
|
{
|
||
|
CAudioMixer *pSource = m_sourceList[i];
|
||
|
if ( !pSource )
|
||
|
continue;
|
||
|
|
||
|
int currentsample = pSource->GetSamplePosition();
|
||
|
bool forward = pSource->GetDirection();
|
||
|
|
||
|
if ( pSource->GetActive() )
|
||
|
{
|
||
|
if ( !pSource->MixDataToDevice( &m_audioDevice, &channel, currentsample, sampleCount, SampleRate(), forward ) )
|
||
|
{
|
||
|
// Source becomes inactive when last submitted sample is finally
|
||
|
// submitted. But it lingers until it's no longer referenced
|
||
|
pSource->SetActive( false );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
AddToReferencedList( pSource, pBuffer );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( !IsSourceReferencedByActiveBuffer( pSource ) )
|
||
|
{
|
||
|
if ( !pSource->GetAutoDelete() )
|
||
|
{
|
||
|
FreeChannel( i );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pSource->IncrementSamples( &channel, currentsample, sampleCount, SampleRate(), forward );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
m_audioDevice.TransferBufferStereo16( pSamples, sampleCount );
|
||
|
|
||
|
m_sampleIndex += sampleCount;
|
||
|
tempCount -= sampleCount;
|
||
|
pSamples += sampleCount * 2;
|
||
|
}
|
||
|
// if the buffers aren't aligned on sample boundaries, this will hard-lock the machine!
|
||
|
|
||
|
pBuffer->submit_sample_count = GetOutputPosition();
|
||
|
|
||
|
waveOutWrite( m_deviceHandle, pBuffer->hdr, sizeof(*(pBuffer->hdr)) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int CAudioWaveOutput::GetNumberofSamplesAhead( void )
|
||
|
{
|
||
|
ComputeSampleAheadAmount();
|
||
|
return m_nEstimatedSamplesAhead;
|
||
|
}
|
||
|
|
||
|
float CAudioWaveOutput::GetAmountofTimeAhead( void )
|
||
|
{
|
||
|
ComputeSampleAheadAmount();
|
||
|
return ( (float)m_nEstimatedSamplesAhead / (float)OUTPUT_SAMPLE_RATE );
|
||
|
}
|
||
|
|
||
|
// Find the most recent submitted sample that isn't flagged as whdr_done
|
||
|
void CAudioWaveOutput::ComputeSampleAheadAmount( void )
|
||
|
{
|
||
|
m_nEstimatedSamplesAhead = 0;
|
||
|
|
||
|
int newest_sample_index = -1;
|
||
|
int newest_sample_count = 0;
|
||
|
|
||
|
CAudioBuffer *buffer;
|
||
|
|
||
|
if ( ValidDevice() )
|
||
|
{
|
||
|
|
||
|
for ( int i = 0; i < OUTPUT_BUFFER_COUNT; i++ )
|
||
|
{
|
||
|
buffer = &m_buffers[ i ];
|
||
|
if ( !buffer->submitted )
|
||
|
continue;
|
||
|
|
||
|
if ( buffer->hdr->dwFlags & WHDR_DONE )
|
||
|
continue;
|
||
|
|
||
|
if ( buffer->submit_sample_count > newest_sample_count )
|
||
|
{
|
||
|
newest_sample_index = i;
|
||
|
newest_sample_count = buffer->submit_sample_count;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( newest_sample_index == -1 )
|
||
|
return;
|
||
|
|
||
|
|
||
|
buffer = &m_buffers[ newest_sample_index ];
|
||
|
int currentPos = GetOutputPosition() ;
|
||
|
m_nEstimatedSamplesAhead = currentPos - buffer->submit_sample_count;
|
||
|
}
|
||
|
|
||
|
int CAudioWaveOutput::FindSourceIndex( CAudioMixer *pSource )
|
||
|
{
|
||
|
for ( int i = 0; i < MAX_CHANNELS; i++ )
|
||
|
{
|
||
|
if ( pSource == m_sourceList[i] )
|
||
|
{
|
||
|
return i;
|
||
|
}
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
CAudioMixer *CAudioWaveOutput::GetMixerForSource( CAudioSource *source )
|
||
|
{
|
||
|
for ( int i = 0; i < MAX_CHANNELS; i++ )
|
||
|
{
|
||
|
if ( !m_sourceList[i] )
|
||
|
continue;
|
||
|
|
||
|
if ( source == m_sourceList[i]->GetSource() )
|
||
|
{
|
||
|
return m_sourceList[i];
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
void CAudioWaveOutput::AddSource( CAudioMixer *pSource )
|
||
|
{
|
||
|
int slot = 0;
|
||
|
for ( int i = 0; i < MAX_CHANNELS; i++ )
|
||
|
{
|
||
|
if ( !m_sourceList[i] )
|
||
|
{
|
||
|
slot = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( m_sourceList[slot] )
|
||
|
{
|
||
|
FreeChannel( slot );
|
||
|
}
|
||
|
SetChannel( slot, pSource );
|
||
|
|
||
|
pSource->SetActive( true );
|
||
|
}
|
||
|
|
||
|
|
||
|
void CAudioWaveOutput::StopSounds( void )
|
||
|
{
|
||
|
for ( int i = 0; i < MAX_CHANNELS; i++ )
|
||
|
{
|
||
|
if ( m_sourceList[i] )
|
||
|
{
|
||
|
FreeChannel( i );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CAudioWaveOutput::SetChannel( int channelIndex, CAudioMixer *pSource )
|
||
|
{
|
||
|
if ( channelIndex < 0 || channelIndex >= MAX_CHANNELS )
|
||
|
return;
|
||
|
|
||
|
m_sourceList[channelIndex] = pSource;
|
||
|
}
|
||
|
|
||
|
void CAudioWaveOutput::FreeChannel( int channelIndex )
|
||
|
{
|
||
|
if ( channelIndex < 0 || channelIndex >= MAX_CHANNELS )
|
||
|
return;
|
||
|
|
||
|
if ( m_sourceList[channelIndex] )
|
||
|
{
|
||
|
RemoveMixerChannelReferences( m_sourceList[channelIndex] );
|
||
|
|
||
|
delete m_sourceList[channelIndex];
|
||
|
m_sourceList[channelIndex] = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int CAudioWaveOutput::GetOutputPosition( void )
|
||
|
{
|
||
|
if ( !m_deviceHandle )
|
||
|
return 0;
|
||
|
|
||
|
MMTIME mmtime;
|
||
|
mmtime.wType = TIME_SAMPLES;
|
||
|
waveOutGetPosition( m_deviceHandle, &mmtime, sizeof( MMTIME ) );
|
||
|
|
||
|
// Convert time to sample count
|
||
|
return ( mmtime.u.sample );
|
||
|
}
|
||
|
|
||
|
void CAudioWaveOutput::OpenDevice( void )
|
||
|
{
|
||
|
WAVEFORMATEX waveFormat;
|
||
|
|
||
|
memset( &waveFormat, 0, sizeof(waveFormat) );
|
||
|
// Select a PCM, 16-bit stereo playback device
|
||
|
waveFormat.cbSize = sizeof(waveFormat);
|
||
|
waveFormat.nAvgBytesPerSec = OUTPUT_SAMPLE_RATE * 2 * 2;
|
||
|
waveFormat.nBlockAlign = 2 * 2; // channels * sample size
|
||
|
waveFormat.nChannels = 2; // stereo
|
||
|
waveFormat.nSamplesPerSec = OUTPUT_SAMPLE_RATE;
|
||
|
waveFormat.wBitsPerSample = 16;
|
||
|
waveFormat.wFormatTag = WAVE_FORMAT_PCM;
|
||
|
|
||
|
MMRESULT errorCode = waveOutOpen( &m_deviceHandle, WAVE_MAPPER, &waveFormat, 0, 0, CALLBACK_NULL );
|
||
|
if ( errorCode == MMSYSERR_NOERROR )
|
||
|
{
|
||
|
int bufferSize = 4 * ( OUTPUT_SAMPLE_RATE / OUTPUT_BUFFER_COUNT ); // total of 1 second of data
|
||
|
|
||
|
// Got one!
|
||
|
for ( int i = 0; i < OUTPUT_BUFFER_COUNT; i++ )
|
||
|
{
|
||
|
m_buffers[i].hdr = new WAVEHDR;
|
||
|
m_buffers[i].hdr->lpData = new char[ bufferSize ];
|
||
|
long align = (long)m_buffers[i].hdr->lpData;
|
||
|
if ( align & 3 )
|
||
|
{
|
||
|
m_buffers[i].hdr->lpData = (char *) ( (align+3) &~3 );
|
||
|
}
|
||
|
m_buffers[i].hdr->dwBufferLength = bufferSize - (align&3);
|
||
|
m_buffers[i].hdr->dwFlags = 0;
|
||
|
|
||
|
if ( waveOutPrepareHeader( m_deviceHandle, m_buffers[i].hdr, sizeof(*m_buffers[i].hdr) ) != MMSYSERR_NOERROR )
|
||
|
{
|
||
|
ClearDevice();
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ClearDevice();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// factory to create a suitable audio output for this system
|
||
|
CAudioOutput *CAudioOutput::Create( void )
|
||
|
{
|
||
|
// sound device is a singleton for now
|
||
|
static CAudioOutput *pWaveOut = NULL;
|
||
|
|
||
|
if ( !pWaveOut )
|
||
|
{
|
||
|
pWaveOut = new CAudioWaveOutput;
|
||
|
}
|
||
|
|
||
|
return pWaveOut;
|
||
|
}
|
||
|
|
||
|
struct CSoundFile
|
||
|
{
|
||
|
char filename[ 512 ];
|
||
|
CAudioSource *source;
|
||
|
long filetime;
|
||
|
};
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
class CSceneManagerSound : public ISceneManagerSound
|
||
|
{
|
||
|
public:
|
||
|
~CSceneManagerSound( void );
|
||
|
|
||
|
void Init( void );
|
||
|
void Shutdown( void );
|
||
|
void Update( float dt );
|
||
|
void Flush( void );
|
||
|
|
||
|
CAudioSource *LoadSound( const char *wavfile );
|
||
|
void PlaySound( const char *wavfile, CAudioMixer **ppMixer );
|
||
|
|
||
|
void PlaySound( CAudioSource *source, CAudioMixer **ppMixer );
|
||
|
bool IsSoundPlaying( CAudioMixer *pMixer );
|
||
|
CAudioMixer *FindMixer( CAudioSource *source );
|
||
|
|
||
|
void StopAll( void );
|
||
|
void StopSound( CAudioMixer *mixer );
|
||
|
|
||
|
CAudioOuput *GetAudioOutput( void );
|
||
|
|
||
|
virtual CAudioSource *FindOrAddSound( const char *filename );
|
||
|
|
||
|
private:
|
||
|
|
||
|
CAudioOutput *m_pAudio;
|
||
|
|
||
|
float m_flElapsedTime;
|
||
|
|
||
|
CUtlVector < CSoundFile > m_ActiveSounds;
|
||
|
};
|
||
|
|
||
|
static CSceneManagerSound g_FacePoserSound;
|
||
|
|
||
|
ISceneManagerSound *sound = ( ISceneManagerSound * )&g_FacePoserSound;
|
||
|
|
||
|
CSceneManagerSound::~CSceneManagerSound( void )
|
||
|
{
|
||
|
OutputDebugString( va( "Removing %i sounds\n", m_ActiveSounds.Size() ) );
|
||
|
for ( int i = 0 ; i < m_ActiveSounds.Size(); i++ )
|
||
|
{
|
||
|
CSoundFile *p = &m_ActiveSounds[ i ];
|
||
|
OutputDebugString( va( "Removing sound: %s\n", p->filename ) );
|
||
|
delete p->source;
|
||
|
}
|
||
|
|
||
|
m_ActiveSounds.RemoveAll();
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
CAudioOuput *CSceneManagerSound::GetAudioOutput( void )
|
||
|
{
|
||
|
return (CAudioOuput *)m_pAudio;
|
||
|
}
|
||
|
|
||
|
CAudioSource *CSceneManagerSound::FindOrAddSound( const char *filename )
|
||
|
{
|
||
|
CSoundFile *s;
|
||
|
|
||
|
int i;
|
||
|
for ( i = 0; i < m_ActiveSounds.Size(); i++ )
|
||
|
{
|
||
|
s = &m_ActiveSounds[ i ];
|
||
|
Assert( s );
|
||
|
if ( !stricmp( s->filename, filename ) )
|
||
|
{
|
||
|
long filetime = filesystem->GetFileTime( filename );
|
||
|
if ( filetime != s->filetime )
|
||
|
{
|
||
|
Con_Printf( "Reloading sound %s\n", filename );
|
||
|
delete s->source;
|
||
|
s->source = LoadSound( filename );
|
||
|
s->filetime = filetime;
|
||
|
}
|
||
|
return s->source;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
i = m_ActiveSounds.AddToTail();
|
||
|
s = &m_ActiveSounds[ i ];
|
||
|
strcpy( s->filename, filename );
|
||
|
s->source = LoadSound( filename );
|
||
|
s->filetime = filesystem->GetFileTime( filename );
|
||
|
|
||
|
return s->source;
|
||
|
}
|
||
|
|
||
|
void CSceneManagerSound::Init( void )
|
||
|
{
|
||
|
m_flElapsedTime = 0.0f;
|
||
|
m_pAudio = CAudioOutput::Create();
|
||
|
}
|
||
|
|
||
|
void CSceneManagerSound::Shutdown( void )
|
||
|
{
|
||
|
}
|
||
|
|
||
|
CAudioSource *CSceneManagerSound::LoadSound( const char *wavfile )
|
||
|
{
|
||
|
if ( !m_pAudio )
|
||
|
return NULL;
|
||
|
|
||
|
CAudioSource *wave = AudioSource_Create( wavfile );
|
||
|
return wave;
|
||
|
}
|
||
|
|
||
|
void CSceneManagerSound::PlaySound( const char *wavfile, CAudioMixer **ppMixer )
|
||
|
{
|
||
|
if ( m_pAudio )
|
||
|
{
|
||
|
CAudioSource *wave = FindOrAddSound( wavfile );
|
||
|
if ( !wave )
|
||
|
return;
|
||
|
|
||
|
CAudioMixer *pMixer = wave->CreateMixer();
|
||
|
if ( ppMixer )
|
||
|
{
|
||
|
*ppMixer = pMixer;
|
||
|
}
|
||
|
m_pAudio->AddSource( pMixer );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CSceneManagerSound::PlaySound( CAudioSource *source, CAudioMixer **ppMixer )
|
||
|
{
|
||
|
if ( ppMixer )
|
||
|
{
|
||
|
*ppMixer = NULL;
|
||
|
}
|
||
|
|
||
|
if ( m_pAudio )
|
||
|
{
|
||
|
CAudioMixer *mixer = source->CreateMixer();
|
||
|
if ( ppMixer )
|
||
|
{
|
||
|
*ppMixer = mixer;
|
||
|
}
|
||
|
m_pAudio->AddSource( mixer );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CSceneManagerSound::Update( float dt )
|
||
|
{
|
||
|
if ( m_pAudio )
|
||
|
{
|
||
|
m_pAudio->Update( m_flElapsedTime );
|
||
|
}
|
||
|
|
||
|
m_flElapsedTime += dt;
|
||
|
}
|
||
|
|
||
|
void CSceneManagerSound::Flush( void )
|
||
|
{
|
||
|
if ( m_pAudio )
|
||
|
{
|
||
|
m_pAudio->Flush();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CSceneManagerSound::StopAll( void )
|
||
|
{
|
||
|
if ( m_pAudio )
|
||
|
{
|
||
|
m_pAudio->StopSounds();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CSceneManagerSound::StopSound( CAudioMixer *mixer )
|
||
|
{
|
||
|
int idx = m_pAudio->FindSourceIndex( mixer );
|
||
|
if ( idx != -1 )
|
||
|
{
|
||
|
m_pAudio->FreeChannel( idx );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool CSceneManagerSound::IsSoundPlaying( CAudioMixer *pMixer )
|
||
|
{
|
||
|
if ( !m_pAudio || !pMixer )
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
int index = m_pAudio->FindSourceIndex( pMixer );
|
||
|
if ( index != -1 )
|
||
|
return true;
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
CAudioMixer *CSceneManagerSound::FindMixer( CAudioSource *source )
|
||
|
{
|
||
|
if ( !m_pAudio )
|
||
|
return NULL;
|
||
|
|
||
|
return m_pAudio->GetMixerForSource( source );
|
||
|
}
|