|
|
//========= Copyright � 1996-2005, 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( 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 bool IsSetSampleStartSupported() const; 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++]; 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; }
bool CSentenceMixer::IsSetSampleStartSupported() const { return true; }
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 ) { SoundError soundError; CAudioSource *pSource = S_LoadSound( m_VoxWords[nWord].sfx, NULL, soundError ); if ( pSource ) { pSource->SetSentenceWord( true ); SoundError soundError; pMixer = pSource->CreateMixer( 0, 0, false, soundError, nullptr ); } }
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( 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( pChannel, sampleCount, outputRate, outputOffset );
outputOffset += outputCount; sampleCount -= outputCount;
if ( !m_pCurrentWordMixer->ShouldContinueMixing() ) { if ( pChannel->flags.m_bHasMouth ) { 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 ( !IsReadyToMix() ) { // current word isn't ready, stop mixing
break; } } } }
return outputOffset - startingOffset; }
|