//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// // // Purpose: // // $NoKeywords: $ // //===========================================================================// #define WIN32_LEAN_AND_MEAN #include #pragma warning( disable : 4201 ) #include #include #include #include "color.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" 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(&((*pList)->dwUser)); } pBuffer->dwUser = NULL; *pList = pBuffer; } void CAudioWaveInput::PopReadyList( void ) { if ( m_pReadyList ) { WAVEHDR *pBuffer = m_pReadyList; m_pReadyList = reinterpret_cast(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<= 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.Count(); 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.Count(); 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.Count(); 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(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, const Color& 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.Count() ) ); for ( int i = 0 ; i < m_ActiveSounds.Count(); 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.Count(); 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(); } 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.Count(); w++ ) { CWordTag *word = sentence->m_Words[ w ]; if ( !word ) continue; for ( int k = 0; k < word->m_Phonemes.Count(); 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.Count()-1) { next = word->m_Phonemes[ k+1 ]; } else if ( w < sentence->m_Words.Count() - 1 && sentence->m_Words[ w+1 ]->m_Phonemes.Count() ) { 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, const Color& 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, ColorToRGB( Color( 175, 175, 250 ) ) ); pen2 = CreatePen( PS_SOLID, 1, ColorToRGB( clr ) ); pen3 = CreatePen( PS_SOLID, 1, ColorToRGB( Color( 127, 200, 249 ) ) ); pen4 = CreatePen( PS_SOLID, 2, ColorToRGB( Color( 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 ); } } }