summaryrefslogtreecommitdiff
path: root/engine/audio/private/snd_sentence_mixer.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/audio/private/snd_sentence_mixer.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'engine/audio/private/snd_sentence_mixer.cpp')
-rw-r--r--engine/audio/private/snd_sentence_mixer.cpp369
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;
+}