You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1800 lines
40 KiB
1800 lines
40 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//===========================================================================// |
|
#define WIN32_LEAN_AND_MEAN |
|
#include <windows.h> |
|
#pragma warning( disable : 4201 ) |
|
#include <mmsystem.h> |
|
#include <stdio.h> |
|
#include <math.h> |
|
#include "snd_audio_source.h" |
|
#include "AudioWaveOutput.h" |
|
#include "ifaceposersound.h" |
|
#include "StudioModel.h" |
|
#include "hlfaceposer.h" |
|
#include "expressions.h" |
|
#include "expclass.h" |
|
#include "PhonemeConverter.h" |
|
#include "utlvector.h" |
|
#include "filesystem.h" |
|
#include "sentence.h" |
|
#include "faceposer_models.h" |
|
#include "iclosecaptionmanager.h" |
|
#include "phonemeeditor.h" |
|
#include "wavebrowser.h" |
|
#include "choreoscene.h" |
|
#include "choreoview.h" |
|
#include "KeyValues.h" |
|
|
|
extern ISoundEmitterSystemBase *soundemitter; |
|
|
|
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 != 0xFFFFFFFF; } |
|
void ClearDevice( void ) { m_deviceId = 0xFFFFFFFF; } |
|
|
|
// 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 = 0; |
|
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 = 0xFFFFFFFF; |
|
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 ) |
|
{ |
|
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; |
|
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; |
|
|
|
StudioModel *model = NULL; |
|
|
|
int modelindex = pSource->GetModelIndex(); |
|
if ( modelindex >= 0 ) |
|
{ |
|
model = models->GetStudioModel( modelindex ); |
|
} |
|
else |
|
{ |
|
if ( g_pPhonemeEditor->IsActiveTool() || g_pWaveBrowser->IsActiveTool() ) |
|
{ |
|
model = models->GetActiveStudioModel(); |
|
|
|
} |
|
} |
|
|
|
if ( model && !model->m_mouth.IsSourceReferenced( pSource->GetSource() ) ) |
|
{ |
|
CChoreoScene *pScene = g_pChoreoView->GetScene(); |
|
bool bIgnorePhonemes = pScene ? pScene->ShouldIgnorePhonemes() : false; |
|
model->m_mouth.AddSource( pSource->GetSource(), bIgnorePhonemes ); |
|
if ( modelindex < 0 ) |
|
{ |
|
pSource->SetModelIndex( models->GetIndexForStudioModel( model ) ); |
|
} |
|
} |
|
|
|
int currentsample = pSource->GetSamplePosition(); |
|
bool forward = pSource->GetDirection(); |
|
|
|
if ( pSource->GetActive() ) |
|
{ |
|
if ( !pSource->MixDataToDevice( &m_audioDevice, pSource->GetChannel(), 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( pSource->GetChannel(), 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] ) |
|
{ |
|
StudioModel *model = NULL; |
|
int modelindex = m_sourceList[channelIndex]->GetModelIndex(); |
|
if ( modelindex >= 0) |
|
{ |
|
model = models->GetStudioModel( modelindex ); |
|
} |
|
|
|
if ( model ) |
|
{ |
|
model->m_mouth.RemoveSource( m_sourceList[channelIndex]->GetSource() ); |
|
} |
|
|
|
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 CFacePoserSound : public IFacePoserSound |
|
{ |
|
public: |
|
~CFacePoserSound( void ); |
|
|
|
void Init( void ); |
|
void Shutdown( void ); |
|
void Update( float dt ); |
|
void Flush( void ); |
|
|
|
CAudioSource *LoadSound( const char *wavfile ); |
|
void PlaySound( StudioModel *source, float volume, const char *wavfile, CAudioMixer **ppMixer ); |
|
void PlaySound( CAudioSource *source, float volume, CAudioMixer **ppMixer ); |
|
void PlayPartialSound( StudioModel *model, float volume, const char *wavfile, CAudioMixer **ppMixer, int startSample, int endSample ); |
|
|
|
bool IsSoundPlaying( CAudioMixer *pMixer ); |
|
CAudioMixer *FindMixer( CAudioSource *source ); |
|
|
|
void StopAll( void ); |
|
void StopSound( CAudioMixer *mixer ); |
|
|
|
void RenderWavToDC( HDC dc, RECT& outrect, COLORREF clr, float starttime, float endtime, |
|
CAudioSource *pWave, bool selected = false, int selectionstart = 0, int selectionend = 0 ); |
|
|
|
// void InstallPhonemecallback( IPhonemeTag *pTagInterface ); |
|
float GetAmountofTimeAhead( void ); |
|
|
|
int GetNumberofSamplesAhead( void ); |
|
|
|
CAudioOuput *GetAudioOutput( void ); |
|
|
|
virtual void EnsureNoModelReferences( CAudioSource *source ); |
|
|
|
private: |
|
void AddViseme( float intensity, StudioModel *model, int phoneme, float scale ); |
|
void ProcessCloseCaptionData( StudioModel *model, float curtime, CSentence* sentence ); |
|
void SetupWeights( void ); |
|
|
|
CAudioSource *FindOrAddSound( const char *filename ); |
|
|
|
CAudioOutput *m_pAudio; |
|
|
|
float m_flElapsedTime; |
|
|
|
CUtlVector < CSoundFile > m_ActiveSounds; |
|
}; |
|
|
|
static CFacePoserSound g_FacePoserSound; |
|
|
|
IFacePoserSound *sound = ( IFacePoserSound * )&g_FacePoserSound; |
|
|
|
CFacePoserSound::~CFacePoserSound( 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 *CFacePoserSound::GetAudioOutput( void ) |
|
{ |
|
return (CAudioOuput *)m_pAudio; |
|
} |
|
|
|
CAudioSource *CFacePoserSound::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 CFacePoserSound::Init( void ) |
|
{ |
|
m_flElapsedTime = 0.0f; |
|
m_pAudio = CAudioOutput::Create(); |
|
|
|
// Load SoundOverrides for Faceposer |
|
|
|
KeyValues *manifest = new KeyValues( "scripts/game_sounds_manifest.txt" ); |
|
if ( filesystem->LoadKeyValues( *manifest, IFileSystem::TYPE_SOUNDEMITTER, "scripts/game_sounds_manifest.txt", "GAME" ) ) |
|
{ |
|
for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() ) |
|
{ |
|
if ( !Q_stricmp( sub->GetName(), "faceposer_file" ) ) |
|
{ |
|
soundemitter->AddSoundOverrides( sub->GetString() ); |
|
continue; |
|
} |
|
} |
|
} |
|
manifest->deleteThis(); |
|
} |
|
|
|
void CFacePoserSound::Shutdown( void ) |
|
{ |
|
} |
|
|
|
float CFacePoserSound::GetAmountofTimeAhead( void ) |
|
{ |
|
if ( !m_pAudio ) |
|
return 0.0f; |
|
|
|
return m_pAudio->GetAmountofTimeAhead(); |
|
} |
|
|
|
int CFacePoserSound::GetNumberofSamplesAhead( void ) |
|
{ |
|
if ( !m_pAudio ) |
|
return 0; |
|
|
|
return m_pAudio->GetNumberofSamplesAhead(); |
|
} |
|
|
|
|
|
CAudioSource *CFacePoserSound::LoadSound( const char *wavfile ) |
|
{ |
|
if ( !m_pAudio ) |
|
return NULL; |
|
|
|
CAudioSource *wave = AudioSource_Create( wavfile ); |
|
return wave; |
|
} |
|
|
|
void CFacePoserSound::PlaySound( StudioModel *model, float volume, const char *wavfile, CAudioMixer **ppMixer ) |
|
{ |
|
if ( m_pAudio ) |
|
{ |
|
CAudioSource *wave = FindOrAddSound( wavfile ); |
|
if ( !wave ) |
|
return; |
|
|
|
CAudioMixer *pMixer = wave->CreateMixer(); |
|
if ( ppMixer ) |
|
{ |
|
*ppMixer = pMixer; |
|
} |
|
pMixer->SetVolume( volume ); |
|
m_pAudio->AddSource( pMixer ); |
|
if ( model ) |
|
{ |
|
pMixer->SetModelIndex( models->GetIndexForStudioModel( model ) ); |
|
} |
|
} |
|
} |
|
|
|
void CFacePoserSound::PlayPartialSound( StudioModel *model, float volume, const char *wavfile, CAudioMixer **ppMixer, int startSample, int endSample ) |
|
{ |
|
if ( !m_pAudio ) |
|
return; |
|
|
|
StopAll(); |
|
CAudioSource *wave = FindOrAddSound( wavfile ); |
|
if ( !wave ) |
|
return; |
|
|
|
CAudioMixer *mixer = wave->CreateMixer(); |
|
if ( ppMixer ) |
|
{ |
|
*ppMixer = mixer; |
|
} |
|
|
|
mixer->SetSamplePosition( startSample ); |
|
mixer->SetLoopPosition( endSample ); |
|
mixer->SetVolume( volume ); |
|
m_pAudio->AddSource( mixer ); |
|
} |
|
|
|
void CFacePoserSound::PlaySound( CAudioSource *source, float volume, CAudioMixer **ppMixer ) |
|
{ |
|
if ( ppMixer ) |
|
{ |
|
*ppMixer = NULL; |
|
} |
|
|
|
if ( m_pAudio ) |
|
{ |
|
CAudioMixer *mixer = source->CreateMixer(); |
|
if ( ppMixer ) |
|
{ |
|
*ppMixer = mixer; |
|
} |
|
mixer->SetVolume( volume ); |
|
m_pAudio->AddSource( mixer ); |
|
} |
|
} |
|
|
|
enum |
|
{ |
|
PHONEME_CLASS_WEAK = 0, |
|
PHONEME_CLASS_NORMAL, |
|
PHONEME_CLASS_STRONG, |
|
|
|
NUM_PHONEME_CLASSES |
|
}; |
|
|
|
struct Emphasized_Phoneme |
|
{ |
|
char *classname; |
|
bool required; |
|
bool valid; |
|
CExpClass *cl; |
|
CExpression *exp; |
|
float *settings; |
|
float amount; |
|
}; |
|
|
|
static Emphasized_Phoneme g_PhonemeClasses[ NUM_PHONEME_CLASSES ] = |
|
{ |
|
{ "phonemes_weak", false }, |
|
{ "phonemes", true }, |
|
{ "phonemes_strong", false }, |
|
}; |
|
|
|
#define STRONG_CROSSFADE_START 0.60f |
|
#define WEAK_CROSSFADE_START 0.40f |
|
|
|
void ComputeBlendedSetting( Emphasized_Phoneme *classes, float emphasis_intensity ) |
|
{ |
|
// Here's the formula |
|
// 0.5 is neutral 100 % of the default setting |
|
|
|
// Crossfade starts at STRONG_CROSSFADE_START and is full at STRONG_CROSSFADE_END |
|
// If there isn't a strong then the intensity of the underlying phoneme is fixed at 2 x STRONG_CROSSFADE_START |
|
// so we don't get huge numbers |
|
|
|
bool has_weak = classes[ PHONEME_CLASS_WEAK ].valid; |
|
bool has_strong = classes[ PHONEME_CLASS_STRONG ].valid; |
|
|
|
Assert( classes[ PHONEME_CLASS_NORMAL ].valid ); |
|
|
|
if ( emphasis_intensity > STRONG_CROSSFADE_START ) |
|
{ |
|
if ( has_strong ) |
|
{ |
|
// Blend in some of strong |
|
float dist_remaining = 1.0f - emphasis_intensity; |
|
float frac = dist_remaining / ( 1.0f - STRONG_CROSSFADE_START ); |
|
|
|
classes[ PHONEME_CLASS_NORMAL ].amount = (frac) * 2.0f * STRONG_CROSSFADE_START; |
|
classes[ PHONEME_CLASS_STRONG ].amount = 1.0f - frac; |
|
} |
|
else |
|
{ |
|
emphasis_intensity = min( emphasis_intensity, STRONG_CROSSFADE_START ); |
|
classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity; |
|
} |
|
} |
|
else if ( emphasis_intensity < WEAK_CROSSFADE_START ) |
|
{ |
|
if ( has_weak ) |
|
{ |
|
// Blend in some weak |
|
float dist_remaining = WEAK_CROSSFADE_START - emphasis_intensity; |
|
float frac = dist_remaining / ( WEAK_CROSSFADE_START ); |
|
|
|
classes[ PHONEME_CLASS_NORMAL ].amount = (1.0f - frac) * 2.0f * WEAK_CROSSFADE_START; |
|
classes[ PHONEME_CLASS_WEAK ].amount = frac; |
|
} |
|
else |
|
{ |
|
emphasis_intensity = max( emphasis_intensity, WEAK_CROSSFADE_START ); |
|
classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity; |
|
} |
|
} |
|
else |
|
{ |
|
classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity; |
|
} |
|
} |
|
|
|
void CFacePoserSound::AddViseme( float intensity, StudioModel *model, int phoneme, float scale ) |
|
{ |
|
int i; |
|
|
|
Assert( model ); |
|
CStudioHdr *hdr = model->GetStudioHdr(); |
|
Assert( hdr ); |
|
if ( !hdr ) |
|
return; |
|
|
|
for ( i = 0; i < NUM_PHONEME_CLASSES; i++ ) |
|
{ |
|
Emphasized_Phoneme *info = &g_PhonemeClasses[ i ]; |
|
|
|
info->valid = false; |
|
info->exp = NULL; |
|
info->settings = NULL; |
|
info->amount = 0.0f; |
|
|
|
info->cl = expressions->FindClass( info->classname, true ); |
|
if ( info->cl ) |
|
{ |
|
info->exp = info->cl->FindExpression( ConvertPhoneme( phoneme ) ); |
|
} |
|
|
|
if ( info->required && ( !info->cl || !info->exp ) ) |
|
{ |
|
return; |
|
} |
|
|
|
if ( info->exp ) |
|
{ |
|
info->valid = true; |
|
|
|
info->settings = info->exp->GetSettings(); |
|
Assert( info->settings ); |
|
} |
|
} |
|
|
|
ComputeBlendedSetting( g_PhonemeClasses, intensity ); |
|
|
|
// Look up the phoneme |
|
for ( LocalFlexController_t i = LocalFlexController_t(0); i < hdr->numflexcontrollers(); i++) |
|
{ |
|
int j = hdr->pFlexcontroller( i )->localToGlobal; |
|
|
|
float add = 0.0f; |
|
|
|
for ( int k = 0 ; k < NUM_PHONEME_CLASSES; k++ ) |
|
{ |
|
Emphasized_Phoneme *info = &g_PhonemeClasses[ k ]; |
|
if ( !info->valid || !info->amount ) |
|
continue; |
|
|
|
add += info->amount * info->settings[ j ]; |
|
} |
|
|
|
if ( add == 0.0f ) |
|
continue; |
|
|
|
float curvalue = model->GetFlexController( i ); |
|
curvalue += add * scale; |
|
model->SetFlexController( i, curvalue ); |
|
} |
|
} |
|
|
|
|
|
#define PHONEME_FILTER 0.08f |
|
#define PHONEME_DELAY 0.0f |
|
|
|
void CFacePoserSound::SetupWeights( void ) |
|
{ |
|
StudioModel *model; |
|
int c = models->Count(); |
|
for ( int i = 0; i < c; i++ ) |
|
{ |
|
model = models->GetStudioModel( i ); |
|
if ( !model ) |
|
continue; |
|
|
|
// Reset flexes |
|
CStudioHdr *hdr = model->GetStudioHdr(); |
|
if ( !hdr ) |
|
continue; |
|
|
|
for ( int s = 0; s < model->m_mouth.GetNumVoiceSources(); s++ ) |
|
{ |
|
CVoiceData *vd = model->m_mouth.GetVoiceSource( s ); |
|
if ( !vd || vd->ShouldIgnorePhonemes() ) |
|
continue; |
|
|
|
CAudioSource *source = vd->GetSource(); |
|
// check for phoneme flexes |
|
if ( !source ) |
|
continue; |
|
|
|
CAudioMixer *mixer = FindMixer( source ); |
|
if ( !mixer ) |
|
continue; |
|
|
|
CSentence *sentence = source->GetSentence(); |
|
if ( !sentence ) |
|
continue; |
|
|
|
// Zero faces if needed |
|
models->CheckResetFlexes(); |
|
|
|
float pos = (float)mixer->GetScrubPosition(); |
|
|
|
// Con_Printf( "pos %f for mixer %p\n", pos, mixer ); |
|
|
|
float soundtime = pos / source->SampleRate(); |
|
|
|
float t = soundtime - PHONEME_DELAY; |
|
float dt = PHONEME_FILTER; |
|
|
|
float sentence_duration = source->GetRunningLength(); |
|
float emphasis_intensity = sentence->GetIntensity( t, sentence_duration ); |
|
|
|
if ( t > 0.0f ) |
|
{ |
|
for ( int w = 0 ; w < sentence->m_Words.Size(); w++ ) |
|
{ |
|
CWordTag *word = sentence->m_Words[ w ]; |
|
if ( !word ) |
|
continue; |
|
|
|
for ( int k = 0; k < word->m_Phonemes.Size(); k++) |
|
{ |
|
CPhonemeTag *phoneme = word->m_Phonemes[ k ]; |
|
if ( !phoneme ) |
|
continue; |
|
|
|
// if the filter starts within this phoneme, make sure the filter size is |
|
// at least least as long as the current phoneme, or until the end of the next phoneme, |
|
// whichever is smaller |
|
if (t > phoneme->GetStartTime() && t < phoneme->GetEndTime()) |
|
{ |
|
CPhonemeTag *next = NULL; |
|
// try next phoneme, or first phoneme of next word |
|
if (k < word->m_Phonemes.Size()-1) |
|
{ |
|
next = word->m_Phonemes[ k+1 ]; |
|
} |
|
else if ( w < sentence->m_Words.Size() - 1 && sentence->m_Words[ w+1 ]->m_Phonemes.Size() ) |
|
{ |
|
next = sentence->m_Words[ w+1 ]->m_Phonemes[ 0 ]; |
|
} |
|
|
|
// if I have a neighbor |
|
if (next) |
|
{ |
|
// and they're touching |
|
if (next->GetStartTime() == phoneme->GetEndTime()) |
|
{ |
|
// no gap, so increase the blend length to the end of the next phoneme, as long as it's not longer than the current phoneme |
|
dt = max( dt, min( next->GetEndTime() - t, phoneme->GetEndTime() - phoneme->GetStartTime() ) ); |
|
} |
|
else |
|
{ |
|
// dead space, so increase the blend length to the start of the next phoneme, as long as it's not longer than the current phoneme |
|
dt = max( dt, min( next->GetStartTime() - t, phoneme->GetEndTime() - phoneme->GetStartTime() ) ); |
|
} |
|
} |
|
else |
|
{ |
|
// last phoneme in list, increase the blend length to the length of the current phoneme |
|
dt = max( dt, phoneme->GetEndTime() - phoneme->GetStartTime() ); |
|
} |
|
} |
|
|
|
float t1 = ( phoneme->GetStartTime() - t) / dt; |
|
float t2 = ( phoneme->GetEndTime() - t) / dt; |
|
|
|
if (t1 < 1.0 && t2 > 0) |
|
{ |
|
float scale; |
|
|
|
// clamp |
|
if (t2 > 1) |
|
t2 = 1; |
|
if (t1 < 0) |
|
t1 = 0; |
|
|
|
// FIXME: simple box filter. Should use something fancier |
|
scale = (t2 - t1); |
|
|
|
AddViseme( emphasis_intensity, model, phoneme->GetPhonemeCode(), scale ); |
|
} |
|
} |
|
} |
|
ProcessCloseCaptionData( model, t, sentence ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
static int g_nSoundFrameCount = 0; |
|
|
|
void CFacePoserSound::ProcessCloseCaptionData( StudioModel *model, float curtime, CSentence* sentence ) |
|
{ |
|
// closecaptionmanager->Process( g_nSoundFrameCount, model, curtime, sentence, GetCloseCaptionLanguageId() ); |
|
} |
|
|
|
void CFacePoserSound::Update( float dt ) |
|
{ |
|
// closecaptionmanager->PreProcess( g_nSoundFrameCount ); |
|
|
|
if ( m_pAudio ) |
|
{ |
|
SetupWeights(); |
|
m_pAudio->Update( m_flElapsedTime ); |
|
} |
|
|
|
// closecaptionmanager->PostProcess( g_nSoundFrameCount, dt ); |
|
|
|
m_flElapsedTime += dt; |
|
g_nSoundFrameCount++; |
|
} |
|
|
|
void CFacePoserSound::Flush( void ) |
|
{ |
|
if ( m_pAudio ) |
|
{ |
|
m_pAudio->Flush(); |
|
} |
|
} |
|
|
|
void CFacePoserSound::StopAll( void ) |
|
{ |
|
int c = models->Count(); |
|
for ( int i = 0; i < c; i++ ) |
|
{ |
|
StudioModel *model = models->GetStudioModel( i ); |
|
if ( model ) |
|
{ |
|
model->m_mouth.ClearVoiceSources(); |
|
} |
|
} |
|
|
|
if ( m_pAudio ) |
|
{ |
|
m_pAudio->StopSounds(); |
|
} |
|
} |
|
|
|
void CFacePoserSound::StopSound( CAudioMixer *mixer ) |
|
{ |
|
int idx = m_pAudio->FindSourceIndex( mixer ); |
|
if ( idx != -1 ) |
|
{ |
|
m_pAudio->FreeChannel( idx ); |
|
} |
|
} |
|
|
|
void CFacePoserSound::RenderWavToDC( HDC dc, RECT& outrect, COLORREF clr, |
|
float starttime, float endtime, CAudioSource *pWave, |
|
bool selected /*= false*/, int selectionstart /*= 0*/, int selectionend /*= 0*/ ) |
|
{ |
|
channel_t channel; |
|
|
|
channel.leftvol = 127; |
|
channel.rightvol = 127; |
|
channel.pitch = 1.0; |
|
|
|
if ( !pWave ) |
|
return; |
|
|
|
CAudioWaveOutput *pWaveOutput = ( CAudioWaveOutput * )m_pAudio; |
|
|
|
CAudioMixer *pMixer = pWave->CreateMixer(); |
|
|
|
float timeperpixel = ( endtime - starttime ) / (float)( outrect.right - outrect.left ); |
|
|
|
float samplesperpixel = timeperpixel * pWave->SampleRate(); |
|
|
|
samplesperpixel = min( samplesperpixel, (float)PAINTBUFFER_SIZE ); |
|
|
|
int intsamplesperpixel = (int)samplesperpixel; |
|
|
|
// Determine start/stop positions |
|
int totalsamples = (int)( pWave->GetRunningLength() * pWave->SampleRate() ); |
|
|
|
if ( totalsamples <= 0 ) |
|
return; |
|
|
|
float selectionstarttime = pWave->GetRunningLength() * ( float )selectionstart / ( float )totalsamples; |
|
float selectionendtime = pWave->GetRunningLength() * ( float )selectionend / ( float )totalsamples; |
|
|
|
|
|
HPEN oldPen, pen, pen2, pen3, pen4; |
|
|
|
pen = CreatePen( PS_SOLID, 1, RGB( 175, 175, 250 ) ); |
|
pen2 = CreatePen( PS_SOLID, 1, clr ); |
|
pen3 = CreatePen( PS_SOLID, 1, RGB( 127, 200, 249 ) ); |
|
pen4 = CreatePen( PS_SOLID, 2, RGB( 0, 0, 200 ) ); |
|
|
|
oldPen = (HPEN)SelectObject( dc, pen ); |
|
|
|
MoveToEx( dc, outrect.left, ( outrect.bottom + outrect.top ) / 2, NULL ); |
|
LineTo( dc, outrect.right, ( outrect.bottom + outrect.top ) / 2 ); |
|
|
|
SelectObject( dc, pen2 ); |
|
|
|
// Now iterate the samples |
|
float currenttime = 0.0f; |
|
int pixel = 0; |
|
int height = ( outrect.bottom - outrect.top ) / 2; |
|
int midy = ( outrect.bottom + outrect.top ) / 2; |
|
int bufferlen = ( intsamplesperpixel + 3 ) & ~3; |
|
short *samples = new short[ 2 * bufferlen ]; |
|
bool drawingselection = false; |
|
int maxsamples = max( 32, intsamplesperpixel / 16 ); |
|
int currentsample = 0; |
|
|
|
while ( currenttime < endtime ) |
|
{ |
|
|
|
pWaveOutput->m_audioDevice.MixBegin(); |
|
|
|
int samplecount = min( maxsamples, intsamplesperpixel ); |
|
|
|
if ( !pMixer->MixDataToDevice( &pWaveOutput->m_audioDevice, &channel, currentsample, samplecount, pWave->SampleRate(), true ) ) |
|
break; |
|
|
|
currentsample = pMixer->GetSamplePosition(); |
|
|
|
// Jump ahead by diff |
|
int diff = intsamplesperpixel - samplecount; |
|
if ( diff > 0 ) |
|
{ |
|
if ( !pMixer->SkipSamples( &channel, currentsample, diff, pWave->SampleRate(), true ) ) |
|
break; |
|
} |
|
|
|
currentsample = pMixer->GetSamplePosition(); |
|
|
|
pWaveOutput->m_audioDevice.TransferBufferStereo16( samples, samplecount ); |
|
|
|
if ( currenttime >= starttime ) |
|
{ |
|
if ( selected ) |
|
{ |
|
bool boundary = false; |
|
bool inselection = ( currenttime >= selectionstarttime && |
|
currenttime <= selectionendtime ); |
|
|
|
if ( inselection ) |
|
{ |
|
if ( !drawingselection ) |
|
{ |
|
drawingselection = true; |
|
boundary = true; |
|
} |
|
} |
|
else if ( drawingselection ) |
|
{ |
|
boundary = true; |
|
drawingselection = false; |
|
} |
|
|
|
if ( inselection || boundary ) |
|
{ |
|
int top, bottom; |
|
|
|
bottom = outrect.bottom; |
|
|
|
HPEN *usePen; |
|
if ( boundary ) |
|
{ |
|
usePen = &pen4; |
|
top = outrect.top; |
|
} |
|
else |
|
{ |
|
usePen = &pen3; |
|
top = outrect.bottom - 19; |
|
} |
|
|
|
HPEN old = (HPEN)SelectObject( dc, *usePen ); |
|
|
|
MoveToEx( dc, outrect.left + pixel, top, NULL ); |
|
LineTo( dc, outrect.left + pixel, bottom-1 ); |
|
|
|
SelectObject( dc, old ); |
|
} |
|
} |
|
|
|
|
|
int maxvalue = -65536; |
|
int minvalue = 65536; |
|
|
|
short *pData = samples; |
|
|
|
// only take fix samples |
|
int step = 2; |
|
int count = 2 * samplecount; |
|
|
|
for ( int i = 0; i < count; i+=step ) |
|
{ |
|
int val = (int)( pData[i] + pData[i+1] ) / 2; |
|
|
|
if ( val > maxvalue ) |
|
{ |
|
maxvalue = val; |
|
} |
|
|
|
if ( val < minvalue ) |
|
{ |
|
minvalue = val; |
|
} |
|
} |
|
|
|
float maxv = (float)( maxvalue ) / 32768.0f; |
|
float minv = (float)( minvalue ) / 32768.0f; |
|
|
|
MoveToEx( dc, outrect.left + pixel, midy + ( int ) ( maxv * height ), NULL ); |
|
LineTo( dc, outrect.left + pixel, midy + ( int ) ( minv * height ) ); |
|
|
|
pixel++; |
|
} |
|
currenttime += timeperpixel; |
|
} |
|
|
|
delete[] samples; |
|
|
|
SelectObject( dc, oldPen ); |
|
DeleteObject( pen ); |
|
DeleteObject( pen2 ); |
|
DeleteObject( pen3 ); |
|
|
|
delete pMixer; |
|
} |
|
|
|
bool CFacePoserSound::IsSoundPlaying( CAudioMixer *pMixer ) |
|
{ |
|
if ( !m_pAudio || !pMixer ) |
|
{ |
|
return false; |
|
} |
|
|
|
// |
|
int index = m_pAudio->FindSourceIndex( pMixer ); |
|
if ( index != -1 ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
CAudioMixer *CFacePoserSound::FindMixer( CAudioSource *source ) |
|
{ |
|
if ( !m_pAudio ) |
|
return NULL; |
|
|
|
return m_pAudio->GetMixerForSource( source ); |
|
} |
|
|
|
|
|
void CFacePoserSound::EnsureNoModelReferences( CAudioSource *source ) |
|
{ |
|
int c = models->Count(); |
|
for ( int i = 0; i < c; i++ ) |
|
{ |
|
StudioModel *model = models->GetStudioModel( i ); |
|
if ( model->m_mouth.IsSourceReferenced( source ) ) |
|
{ |
|
model->m_mouth.RemoveSource( source ); |
|
} |
|
} |
|
} |