📄 sound.cpp
字号:
}
}
void CFacePoserSound::PlayPartialSound( StudioModel *model, const char *wavfile, CAudioMixer **ppMixer, int startSample, int endSample )
{
if ( !m_pAudio )
return;
StopAll();
CAudioSource *wave = FindOrAddSound( wavfile );
if ( !wave )
return;
CAudioMixer *mixer = wave->CreateMixer();
if ( ppMixer )
{
*ppMixer = mixer;
}
m_pAudio->AddSource( mixer );
mixer->SetSamplePosition( startSample );
mixer->SetLoopPosition( endSample );
}
void CFacePoserSound::PlaySound( CAudioSource *source, CAudioMixer **ppMixer )
{
if ( ppMixer )
{
*ppMixer = NULL;
}
if ( m_pAudio )
{
CAudioMixer *mixer = source->CreateMixer();
if ( ppMixer )
{
*ppMixer = mixer;
}
m_pAudio->AddSource( mixer );
}
}
enum
{
PHONEME_CLASS_WEAK = 0,
PHONEME_CLASS_NORMAL,
PHONEME_CLASS_STRONG,
NUM_PHONEME_CLASSES
};
struct Emphasized_Phoneme
{
char *classname;
bool required;
bool valid;
CExpClass *cl;
CExpression *exp;
float *settings;
float amount;
};
static Emphasized_Phoneme g_PhonemeClasses[ NUM_PHONEME_CLASSES ] =
{
{ "phonemes_weak", false },
{ "phonemes", true },
{ "phonemes_strong", false },
};
#define STRONG_CROSSFADE_START 0.60f
#define WEAK_CROSSFADE_START 0.40f
void ComputeBlendedSetting( Emphasized_Phoneme *classes, float emphasis_intensity )
{
// Here's the formula
// 0.5 is neutral 100 % of the default setting
// Crossfade starts at STRONG_CROSSFADE_START and is full at STRONG_CROSSFADE_END
// If there isn't a strong then the intensity of the underlying phoneme is fixed at 2 x STRONG_CROSSFADE_START
// so we don't get huge numbers
bool has_weak = classes[ PHONEME_CLASS_WEAK ].valid;
bool has_strong = classes[ PHONEME_CLASS_STRONG ].valid;
Assert( classes[ PHONEME_CLASS_NORMAL ].valid );
if ( emphasis_intensity > STRONG_CROSSFADE_START )
{
if ( has_strong )
{
// Blend in some of strong
float dist_remaining = 1.0f - emphasis_intensity;
float frac = dist_remaining / ( 1.0f - STRONG_CROSSFADE_START );
classes[ PHONEME_CLASS_NORMAL ].amount = (frac) * 2.0f * STRONG_CROSSFADE_START;
classes[ PHONEME_CLASS_STRONG ].amount = 1.0f - frac;
}
else
{
emphasis_intensity = min( emphasis_intensity, STRONG_CROSSFADE_START );
classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity;
}
}
else if ( emphasis_intensity < WEAK_CROSSFADE_START )
{
if ( has_weak )
{
// Blend in some weak
float dist_remaining = WEAK_CROSSFADE_START - emphasis_intensity;
float frac = dist_remaining / ( WEAK_CROSSFADE_START );
classes[ PHONEME_CLASS_NORMAL ].amount = (1.0f - frac) * 2.0f * WEAK_CROSSFADE_START;
classes[ PHONEME_CLASS_WEAK ].amount = frac;
}
else
{
emphasis_intensity = max( emphasis_intensity, WEAK_CROSSFADE_START );
classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity;
}
}
else
{
classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity;
}
}
void CFacePoserSound::AddViseme( float intensity, StudioModel *model, int phoneme, float scale )
{
int i;
Assert( model );
studiohdr_t *hdr = model->getStudioHeader ();
Assert( hdr );
if ( !hdr )
return;
for ( i = 0; i < NUM_PHONEME_CLASSES; i++ )
{
Emphasized_Phoneme *info = &g_PhonemeClasses[ i ];
info->valid = false;
info->exp = NULL;
info->settings = NULL;
info->amount = 0.0f;
info->cl = expressions->FindClass( info->classname );
if ( info->cl )
{
info->exp = info->cl->FindExpression( ConvertPhoneme( phoneme ) );
}
if ( info->required && ( !info->cl || !info->exp ) )
{
return;
}
if ( info->exp )
{
info->valid = true;
// Resolve markov chains
info->exp = info->exp->GetExpression();
// Allow overrides
if ( FacePoser_GetOverridesShowing() )
{
if ( info->exp->HasOverride() && info->exp->GetOverride() )
{
info->exp = info->exp->GetOverride();
}
}
info->settings = info->exp->GetSettings();
Assert( info->settings );
}
}
ComputeBlendedSetting( g_PhonemeClasses, intensity );
// Look up the phoneme
for ( i = 0; i < hdr->numflexcontrollers; i++)
{
int j = hdr->pFlexcontroller( i )->link;
float add = 0.0f;
for ( int k = 0 ; k < NUM_PHONEME_CLASSES; k++ )
{
Emphasized_Phoneme *info = &g_PhonemeClasses[ k ];
if ( !info->valid || !info->amount )
continue;
add += info->amount * info->settings[ j ];
}
if ( add == 0.0f )
continue;
float curvalue = model->GetFlexController( i );
curvalue += add * scale;
model->SetFlexController( i, curvalue );
}
}
#define PHONEME_FILTER 0.08f
#define PHONEME_DELAY 0.0f
void CFacePoserSound::SetupWeights( void )
{
StudioModel *model;
int c = models->Count();
for ( int i = 0; i < c; i++ )
{
model = models->GetStudioModel( i );
if ( !model )
continue;
// Reset flexes
studiohdr_t *hdr = model->getStudioHeader();
if ( !hdr )
continue;
for ( int s = 0; s < model->m_mouth.GetNumVoiceSources(); s++ )
{
CVoiceData *vd = model->m_mouth.GetVoiceSource( s );
if ( !vd )
continue;
CAudioSource *source = vd->m_pAudioSource;
// check for phoneme flexes
if ( !source )
continue;
CAudioMixer *mixer = FindMixer( source );
if ( !mixer )
continue;
CSentence *sentence = source->GetSentence();
if ( !sentence )
continue;
// Zero faces if needed
models->CheckResetFlexes();
float pos = (float)mixer->GetSamplePosition();
// Con_Printf( "pos %f for mixer %p\n", pos, mixer );
float soundtime = pos / source->SampleRate();
float t = soundtime - PHONEME_DELAY;
float dt = PHONEME_FILTER;
float sentence_duration = source->GetRunningLength();
float emphasis_intensity = sentence->GetIntensity( t, sentence_duration );
if ( t > 0.0f )
{
for ( int w = 0 ; w < sentence->m_Words.Size(); w++ )
{
CWordTag *word = sentence->m_Words[ w ];
if ( !word )
continue;
for ( int k = 0; k < word->m_Phonemes.Size(); k++)
{
CPhonemeTag *phoneme = word->m_Phonemes[ k ];
if ( !phoneme )
continue;
// if the filter starts within this phoneme, make sure the filter size is
// at least least as long as the current phoneme, or until the end of the next phoneme,
// whichever is smaller
if (t > phoneme->m_flStartTime && t < phoneme->m_flEndTime)
{
if (k < word->m_Phonemes.Size()-1)
{
dt = max( dt, min( word->m_Phonemes[ k+1 ]->m_flEndTime - t, phoneme->m_flEndTime - phoneme->m_flStartTime ) );
}
}
float t1 = ( phoneme->m_flStartTime - t) / dt;
float t2 = ( phoneme->m_flEndTime - t) / dt;
if (t1 < 1.0 && t2 > 0)
{
float scale;
// clamp
if (t2 > 1)
t2 = 1;
if (t1 < 0)
t1 = 0;
// FIXME: simple box filter. Should use something fancier
scale = (t2 - t1);
AddViseme( emphasis_intensity, model, phoneme->m_nPhonemeCode, scale );
}
}
}
ProcessCloseCaptionData( model, t, sentence );
}
}
}
}
static int g_nSoundFrameCount = 0;
void CFacePoserSound::ProcessCloseCaptionData( StudioModel *model, float curtime, CSentence* sentence )
{
closecaptionmanager->Process( g_nSoundFrameCount, model, curtime, sentence );
}
void CFacePoserSound::Update( float dt )
{
closecaptionmanager->PreProcess( g_nSoundFrameCount );
if ( m_pAudio )
{
SetupWeights();
m_pAudio->Update( m_flElapsedTime );
}
closecaptionmanager->PostProcess( g_nSoundFrameCount, dt );
m_flElapsedTime += dt;
g_nSoundFrameCount++;
}
void CFacePoserSound::Flush( void )
{
if ( m_pAudio )
{
m_pAudio->Flush();
}
}
void CFacePoserSound::StopAll( void )
{
int c = models->Count();
for ( int i = 0; i < c; i++ )
{
StudioModel *model = models->GetStudioModel( i );
if ( model )
{
model->m_mouth.ClearVoiceSources();
}
}
if ( m_pAudio )
{
m_pAudio->StopSounds();
}
}
void CFacePoserSound::StopSound( CAudioMixer *mixer )
{
int idx = m_pAudio->FindSourceIndex( mixer );
if ( idx != -1 )
{
m_pAudio->FreeChannel( idx );
}
}
void CFacePoserSound::RenderWavToDC( HDC dc, RECT& outrect, COLORREF clr,
float starttime, float endtime, CAudioSource *pWave,
bool selected /*= false*/, int selectionstart /*= 0*/, int selectionend /*= 0*/ )
{
channel_t channel;
channel.leftvol = 200;
channel.rightvol = 200;
channel.pitch = 1.0;
if ( !pWave )
return;
CAudioWaveOutput *pWaveOutput = ( CAudioWaveOutput * )m_pAudio;
CAudioMixer *pMixer = pWave->CreateMixer();
float timeperpixel = ( endtime - starttime ) / (float)( outrect.right - outrect.left );
float samplesperpixel = timeperpixel * pWave->SampleRate();
samplesperpixel = min( samplesperpixel, (float)PAINTBUFFER_SIZE );
int intsamplesperpixel = (int)samplesperpixel;
// Determine start/stop positions
int totalsamples = (int)( pWave->GetRunningLength() * pWave->SampleRate() );
if ( totalsamples <= 0 )
return;
float selectionstarttime = pWave->GetRunningLength() * ( float )selectionstart / ( float )totalsamples;
float selectionendtime = pWave->GetRunningLength() * ( float )selectionend / ( float )totalsamples;
HPEN oldPen, pen, pen2, pen3, pen4;
pen = CreatePen( PS_SOLID, 1, RGB( 175, 175, 250 ) );
pen2 = CreatePen( PS_SOLID, 1, clr );
pen3 = CreatePen( PS_SOLID, 1, RGB( 127, 200, 249 ) );
pen4 = CreatePen( PS_SOLID, 2, RGB( 0, 0, 200 ) );
oldPen = (HPEN)SelectObject( dc, pen );
MoveToEx( dc, outrect.left, ( outrect.bottom + outrect.top ) / 2, NULL );
LineTo( dc, outrect.right, ( outrect.bottom + outrect.top ) / 2 );
SelectObject( dc, pen2 );
// Now iterate the samples
float currenttime = 0.0f;
int pixel = 0;
int height = ( outrect.bottom - outrect.top ) / 2;
int midy = ( outrect.bottom + outrect.top ) / 2;
int bufferlen = ( intsamplesperpixel + 3 ) & ~3;
short *samples = new short[ 2 * bufferlen ];
bool drawingselection = false;
int maxsamples = max( 32, intsamplesperpixel / 16 );
int currentsample = 0;
while ( currenttime < endtime )
{
pWaveOutput->m_audioDevice.MixBegin();
int samplecount = min( maxsamples, intsamplesperpixel );
if ( !pMixer->MixDataToDevice( &pWaveOutput->m_audioDevice, &channel, currentsample, samplecount, pWave->SampleRate(), true ) )
break;
currentsample = pMixer->GetSamplePosition();
// Jump ahead by diff
int diff = intsamplesperpixel - samplecount;
if ( diff > 0 )
{
if ( !pMixer->SkipSamples( &channel, currentsample, diff, pWave->SampleRate(), true ) )
break;
}
currentsample = pMixer->GetSamplePosition();
pWaveOutput->m_audioDevice.TransferBufferStereo16( samples, samplecount );
if ( currenttime >= starttime )
{
if ( selected )
{
bool boundary = false;
bool inselection = ( currenttime >= selectionstarttime &&
currenttime <= selectionendtime );
if ( inselection )
{
if ( !drawingselection )
{
drawingselection = true;
boundary = true;
}
}
else if ( drawingselection )
{
boundary = true;
drawingselection = false;
}
if ( inselection || boundary )
{
int top, bottom;
bottom = outrect.bottom;
HPEN *usePen;
if ( boundary )
{
usePen = &pen4;
top = outrect.top;
}
else
{
usePen = &pen3;
top = outrect.bottom - 19;
}
HPEN old = (HPEN)SelectObject( dc, *usePen );
MoveToEx( dc, outrect.left + pixel, top, NULL );
LineTo( dc, outrect.left + pixel, bottom-1 );
SelectObject( dc, old );
}
}
int maxvalue = -65536;
int minvalue = 65536;
short *pData = samples;
// only take fix samples
int step = 2;
int count = 2 * samplecount;
for ( int i = 0; i < count; i+=step )
{
int val = (int)( pData[i] + pData[i+1] ) / 2;
if ( val > maxvalue )
{
maxvalue = val;
}
if ( val < minvalue )
{
minvalue = val;
}
}
float maxv = (float)( maxvalue ) / 32768.0f;
float minv = (float)( minvalue ) / 32768.0f;
MoveToEx( dc, outrect.left + pixel, midy + ( int ) ( maxv * height ), NULL );
LineTo( dc, outrect.left + pixel, midy + ( int ) ( minv * height ) );
pixel++;
}
currenttime += timeperpixel;
}
delete[] samples;
SelectObject( dc, oldPen );
DeleteObject( pen );
DeleteObject( pen2 );
DeleteObject( pen3 );
delete pMixer;
}
bool CFacePoserSound::IsSoundPlaying( CAudioMixer *pMixer )
{
if ( !m_pAudio || !pMixer )
{
return false;
}
//
int index = m_pAudio->FindSourceIndex( pMixer );
if ( index != -1 )
return true;
return false;
}
CAudioMixer *CFacePoserSound::FindMixer( CAudioSource *source )
{
if ( !m_pAudio )
return NULL;
return m_pAudio->GetMixerForSource( source );
}
void CFacePoserSound::EnsureNoModelReferences( CAudioSource *source )
{
int c = models->Count();
for ( int i = 0; i < c; i++ )
{
StudioModel *model = models->GetStudioModel( i );
if ( model->m_mouth.IsSourceReferenced( source ) )
{
model->m_mouth.RemoveSource( source );
}
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -