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.
369 lines
8.8 KiB
369 lines
8.8 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Sentence Mixing |
|
// |
|
//=============================================================================// |
|
|
|
#include "audio_pch.h" |
|
#include "vox_private.h" |
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: This replaces the old sentence logic that was integrated with the |
|
// sound code. Now it is a hierarchical mixer. |
|
//----------------------------------------------------------------------------- |
|
class CSentenceMixer : public CAudioMixer |
|
{ |
|
public: |
|
CSentenceMixer( voxword_t *pWords ); |
|
~CSentenceMixer( void ); |
|
|
|
// return number of samples mixed |
|
virtual int MixDataToDevice( IAudioDevice *pDevice, channel_t *pChannel, int sampleCount, int outputRate, int outputOffset ); |
|
virtual int SkipSamples( channel_t *pChannel, int sampleCount, int outputRate, int outputOffset ); |
|
virtual bool ShouldContinueMixing( void ); |
|
|
|
virtual CAudioSource* GetSource( void ); |
|
|
|
// get the current position (next sample to be mixed) |
|
virtual int GetSamplePosition( void ); |
|
virtual float ModifyPitch( float pitch ); |
|
virtual float GetVolumeScale( void ); |
|
|
|
// BUGBUG: These are only applied to the current word, not the whole sentence!!!! |
|
virtual void SetSampleStart( int newPosition ); |
|
virtual void SetSampleEnd( int newEndPosition ); |
|
|
|
virtual void SetStartupDelaySamples( int delaySamples ); |
|
virtual int GetMixSampleSize() { return m_pCurrentWordMixer ? m_pCurrentWordMixer->GetMixSampleSize() : 0; } |
|
|
|
virtual bool IsReadyToMix(); |
|
|
|
virtual int GetPositionForSave() { return GetSamplePosition(); } |
|
virtual void SetPositionFromSaved( int savedPosition ) { SetSampleStart( savedPosition ); } |
|
|
|
private: |
|
CAudioMixer *LoadWord( int nWordIndex ); |
|
void FreeWord( int nWordIndex ); |
|
|
|
// identifies the active word |
|
int m_currentWordIndex; |
|
CAudioMixer *m_pCurrentWordMixer; |
|
|
|
// set when a transition to a new word occurs |
|
bool m_bNewWord; |
|
|
|
voxword_t m_VoxWords[CVOXWORDMAX]; |
|
CAudioMixer *m_pWordMixers[CVOXWORDMAX]; |
|
int m_nNumWords; |
|
}; |
|
|
|
CAudioMixer *CreateSentenceMixer( voxword_t *pWords ) |
|
{ |
|
if ( pWords ) |
|
{ |
|
return new CSentenceMixer( pWords ); |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
CSentenceMixer::CSentenceMixer( voxword_t *pWords ) |
|
{ |
|
// count the expected number of words |
|
m_nNumWords = 0; |
|
while ( pWords[m_nNumWords].sfx != NULL ) |
|
{ |
|
// get a private copy of the words |
|
m_VoxWords[m_nNumWords] = pWords[m_nNumWords]; |
|
m_nNumWords++; |
|
if ( m_nNumWords >= ARRAYSIZE( m_VoxWords ) ) |
|
{ |
|
// very long sentence, prevent overflow |
|
break; |
|
} |
|
} |
|
|
|
// startup all the mixers now, this serves as a hint to the audio streamer |
|
// actual mixing will commence when they are ALL ready |
|
for ( int nWord = 0; nWord < m_nNumWords; nWord++ ) |
|
{ |
|
// it is possible to get a null mixer (due to wav error, etc) |
|
// the sentence will skip these words |
|
m_pWordMixers[nWord] = LoadWord( nWord ); |
|
} |
|
Assert( m_nNumWords < ARRAYSIZE( m_pWordMixers ) ); |
|
|
|
// find first valid word mixer |
|
m_currentWordIndex = 0; |
|
m_pCurrentWordMixer = NULL; |
|
for ( int nWord = 0; nWord < m_nNumWords; nWord++ ) |
|
{ |
|
if ( m_pWordMixers[nWord] ) |
|
{ |
|
m_currentWordIndex = nWord; |
|
m_pCurrentWordMixer = m_pWordMixers[nWord]; |
|
break; |
|
} |
|
} |
|
|
|
m_bNewWord = ( m_pCurrentWordMixer != NULL ); |
|
} |
|
|
|
CSentenceMixer::~CSentenceMixer( void ) |
|
{ |
|
// free all words |
|
for ( int nWord = 0; nWord < m_nNumWords; nWord++ ) |
|
{ |
|
FreeWord( nWord ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true if mixing can commence, false otherwise |
|
//----------------------------------------------------------------------------- |
|
bool CSentenceMixer::IsReadyToMix() |
|
{ |
|
if ( !m_pCurrentWordMixer ) |
|
{ |
|
// no word, but mixing has to commence in order to shutdown |
|
return true; |
|
} |
|
|
|
// all the words should be available before mixing the sentence |
|
for ( int nWord = m_currentWordIndex; nWord < m_nNumWords; nWord++ ) |
|
{ |
|
if ( m_pWordMixers[nWord] && !m_pWordMixers[nWord]->IsReadyToMix() ) |
|
{ |
|
// Still waiting for async data to arrive |
|
return false; |
|
} |
|
} |
|
|
|
if ( m_bNewWord ) |
|
{ |
|
m_bNewWord = false; |
|
|
|
int start = m_VoxWords[m_currentWordIndex].start; |
|
int end = m_VoxWords[m_currentWordIndex].end; |
|
|
|
// don't allow overlapped ranges |
|
if ( end <= start ) |
|
{ |
|
end = 0; |
|
} |
|
|
|
if ( start || end ) |
|
{ |
|
int sampleCount = m_pCurrentWordMixer->GetSource()->SampleCount(); |
|
if ( start > 0 && start < 100 ) |
|
{ |
|
m_pCurrentWordMixer->SetSampleStart( (int)(sampleCount * 0.01f * start) ); |
|
} |
|
if ( end > 0 && end < 100 ) |
|
{ |
|
m_pCurrentWordMixer->SetSampleEnd( (int)(sampleCount * 0.01f * end) ); |
|
} |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool CSentenceMixer::ShouldContinueMixing( void ) |
|
{ |
|
if ( m_pCurrentWordMixer ) |
|
{ |
|
// keep mixing until the words run out |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
CAudioSource *CSentenceMixer::GetSource( void ) |
|
{ |
|
if ( m_pCurrentWordMixer ) |
|
{ |
|
return m_pCurrentWordMixer->GetSource(); |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
// get the current position (next sample to be mixed) |
|
int CSentenceMixer::GetSamplePosition( void ) |
|
{ |
|
if ( m_pCurrentWordMixer ) |
|
{ |
|
return m_pCurrentWordMixer->GetSamplePosition(); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
void CSentenceMixer::SetSampleStart( int newPosition ) |
|
{ |
|
if ( m_pCurrentWordMixer ) |
|
{ |
|
m_pCurrentWordMixer->SetSampleStart( newPosition ); |
|
} |
|
} |
|
|
|
// End playback at newEndPosition |
|
void CSentenceMixer::SetSampleEnd( int newEndPosition ) |
|
{ |
|
if ( m_pCurrentWordMixer ) |
|
{ |
|
m_pCurrentWordMixer->SetSampleEnd( newEndPosition ); |
|
} |
|
} |
|
|
|
void CSentenceMixer::SetStartupDelaySamples( int delaySamples ) |
|
{ |
|
if ( m_pCurrentWordMixer ) |
|
{ |
|
m_pCurrentWordMixer->SetStartupDelaySamples( delaySamples ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Free a word |
|
//----------------------------------------------------------------------------- |
|
void CSentenceMixer::FreeWord( int nWord ) |
|
{ |
|
if ( m_pWordMixers[nWord] ) |
|
{ |
|
delete m_pWordMixers[nWord]; |
|
m_pWordMixers[nWord] = NULL; |
|
} |
|
|
|
if ( m_VoxWords[nWord].sfx ) |
|
{ |
|
// If this wave wasn't precached by the game code |
|
if ( !m_VoxWords[nWord].fKeepCached ) |
|
{ |
|
// If this was the last mixer that had a reference |
|
if ( m_VoxWords[nWord].sfx->pSource->CanDelete() ) |
|
{ |
|
// free the source |
|
delete m_VoxWords[nWord].sfx->pSource; |
|
m_VoxWords[nWord].sfx->pSource = NULL; |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Load a word |
|
//----------------------------------------------------------------------------- |
|
CAudioMixer *CSentenceMixer::LoadWord( int nWord ) |
|
{ |
|
CAudioMixer *pMixer = NULL; |
|
if ( m_VoxWords[nWord].sfx ) |
|
{ |
|
CAudioSource *pSource = S_LoadSound( m_VoxWords[nWord].sfx, NULL ); |
|
if ( pSource ) |
|
{ |
|
pSource->SetSentenceWord( true ); |
|
pMixer = pSource->CreateMixer(); |
|
} |
|
} |
|
|
|
return pMixer; |
|
} |
|
|
|
float CSentenceMixer::ModifyPitch( float pitch ) |
|
{ |
|
if ( m_pCurrentWordMixer ) |
|
{ |
|
if ( m_VoxWords[m_currentWordIndex].pitch > 0 ) |
|
{ |
|
pitch += (m_VoxWords[m_currentWordIndex].pitch - 100) * 0.01f; |
|
} |
|
} |
|
return pitch; |
|
} |
|
|
|
float CSentenceMixer::GetVolumeScale( void ) |
|
{ |
|
if ( m_pCurrentWordMixer ) |
|
{ |
|
if ( m_VoxWords[m_currentWordIndex].volume ) |
|
{ |
|
float volume = m_VoxWords[m_currentWordIndex].volume * 0.01; |
|
if ( volume < 1.0f ) |
|
return volume; |
|
} |
|
} |
|
return 1.0f; |
|
} |
|
|
|
int CSentenceMixer::SkipSamples( channel_t *pChannel, int sampleCount, int outputRate, int outputOffset ) |
|
{ |
|
Assert( 0 ); |
|
return 0; |
|
} |
|
|
|
// return number of samples mixed |
|
int CSentenceMixer::MixDataToDevice( IAudioDevice *pDevice, channel_t *pChannel, int sampleCount, int outputRate, int outputOffset ) |
|
{ |
|
if ( !m_pCurrentWordMixer ) |
|
{ |
|
return 0; |
|
} |
|
|
|
// save this to compute total output |
|
int startingOffset = outputOffset; |
|
|
|
while ( sampleCount > 0 && m_pCurrentWordMixer ) |
|
{ |
|
int outputCount = m_pCurrentWordMixer->MixDataToDevice( pDevice, pChannel, sampleCount, outputRate, outputOffset ); |
|
|
|
outputOffset += outputCount; |
|
sampleCount -= outputCount; |
|
|
|
if ( !m_pCurrentWordMixer->ShouldContinueMixing() ) |
|
{ |
|
bool bMouth = SND_IsMouth( pChannel ); |
|
if ( bMouth ) |
|
{ |
|
SND_ClearMouth( pChannel ); |
|
} |
|
|
|
// advance to next valid word mixer |
|
do |
|
{ |
|
m_currentWordIndex++; |
|
if ( m_currentWordIndex >= m_nNumWords ) |
|
{ |
|
// end of sentence |
|
m_pCurrentWordMixer = NULL; |
|
break; |
|
} |
|
m_pCurrentWordMixer = m_pWordMixers[m_currentWordIndex]; |
|
} |
|
while ( m_pCurrentWordMixer == NULL ); |
|
|
|
if ( m_pCurrentWordMixer ) |
|
{ |
|
m_bNewWord = true; |
|
|
|
pChannel->sfx = m_VoxWords[m_currentWordIndex].sfx; |
|
if ( bMouth ) |
|
{ |
|
SND_UpdateMouth( pChannel ); |
|
} |
|
if ( !IsReadyToMix() ) |
|
{ |
|
// current word isn't ready, stop mixing |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return outputOffset - startingOffset; |
|
}
|
|
|