diff options
Diffstat (limited to 'engine/audio/private/snd_sentence_mixer.cpp')
| -rw-r--r-- | engine/audio/private/snd_sentence_mixer.cpp | 369 |
1 files changed, 369 insertions, 0 deletions
diff --git a/engine/audio/private/snd_sentence_mixer.cpp b/engine/audio/private/snd_sentence_mixer.cpp new file mode 100644 index 0000000..7a5fd13 --- /dev/null +++ b/engine/audio/private/snd_sentence_mixer.cpp @@ -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; +} |