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