📄 vox.cpp
字号:
//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. ===========
//
// The copyright to the contents herein is the property of Valve, L.L.C.
// The contents may be used and/or copied only with the written permission of
// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
// the agreement/contract under which the contents have been supplied.
//
// Purpose: Voice / Sentence streaming & parsing code
//
// $Workfile: $
// $Date: $
// $NoKeywords: $
//=============================================================================
//===============================================================================
// VOX. Algorithms to load and play spoken text sentences from a file:
//
// In ambient sounds or entity sounds, precache the
// name of the sentence instead of the wave name, ie: !C1A2S4
//
// During sound system init, the 'sentences.txt' is read.
// This file has the format:
//
// C1A2S4 agrunt/vox/You will be exterminated, surrender NOW.
// C1A2s5 hgrunt/vox/Radio check, over.
// ...
//
// There must be at least one space between the sentence name and the sentence.
// Sentences may be separated by one or more lines
// There may be tabs or spaces preceding the sentence name
// The sentence must end in a /n or /r
// Lines beginning with // are ignored as comments
//
// Period or comma will insert a pause in the wave unless
// the period or comma is the last character in the string.
//
// If first 2 chars of a word are upper case, word volume increased by 25%
//
// If last char of a word is a number from 0 to 9
// then word will be pitch-shifted up by 0 to 9, where 0 is a small shift
// and 9 is a very high pitch shift.
//
// We alloc heap space to contain this data, and track total
// sentences read. A pointer to each sentence is maintained in g_Sentences.
//
// When sound is played back in S_StartDynamicSound or s_startstaticsound, we detect the !name
// format and lookup the actual sentence in the sentences array
//
// To play, we parse each word in the sentence, chain the words, and play the sentence
// each word's data is loaded directy from disk and freed right after playback.
//===============================================================================
#include <stdio.h>
#include <ctype.h>
#include "tier0/dbg.h"
#include "convar.h"
#include "basetypes.h"
#include "commonmacros.h"
#include "const.h"
#include "sound_private.h"
#include "vox_private.h"
#include "snd_channels.h"
#include "characterset.h"
#include "snd_device.h"
#include "snd_audio_source.h"
#include "vstdlib/random.h"
#include "soundservice.h"
#include "common.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// In other C files.
// Globals
// This is the initial capacity for sentences, the array will grow if necessary
#define MAX_EXPECTED_SENTENCES 2048
CUtlVector<sentence_t> g_Sentences;
// Module Locals
static char *rgpparseword[CVOXWORDMAX]; // array of pointers to parsed words
static char voxperiod[] = "_period"; // vocal pause
static char voxcomma[] = "_comma"; // vocal pause
// Sentence file list management
static void VOX_ListClear( void );
static int VOX_ListFileIsLoaded( const char *psentenceFileName );
static void VOX_ListMarkFileLoaded( const char *psentenceFileName, byte *pFileData );
// This module depends on these engine calls:
// DevMsg
// S_FreeChannel
// S_LoadSound
// S_FindName
// COM_LoadFileForMe()
// COM_FreeFile
// It also depends on vstdlib/RandomInt (all other random calls go through g_pSoundServices)
void VOX_Init( void )
{
g_Sentences.RemoveAll();
g_Sentences.EnsureCapacity( MAX_EXPECTED_SENTENCES );
VOX_ListClear();
VOX_ReadSentenceFile( "scripts/sentences.txt" );
}
void VOX_Shutdown( void )
{
g_Sentences.RemoveAll();
VOX_ListClear();
}
//-----------------------------------------------------------------------------
// Purpose: This is kind of like strchr(), but we get the actual pointer to the
// end of the string when it fails rather than NULL. This is useful
// for parsing buffers containing multiple strings
// Input : *string -
// scan -
// Output : char
//-----------------------------------------------------------------------------
char *ScanForwardUntil( char *string, char scan )
{
while( string[0] )
{
if ( string[0] == scan )
return string;
string++;
}
return string;
}
// parse a null terminated string of text into component words, with
// pointers to each word stored in rgpparseword
// note: this code actually alters the passed in string!
char **VOX_ParseString(char *psz)
{
int i;
int fdone = 0;
char *pszscan = psz;
char c;
characterset_t nextWord, skip;
memset(rgpparseword, 0, sizeof(char *) * CVOXWORDMAX);
if (!psz)
return NULL;
i = 0;
rgpparseword[i++] = psz;
CharacterSetBuild( &nextWord, " ,.({" );
CharacterSetBuild( &skip, "., " );
while (!fdone && i < CVOXWORDMAX)
{
// scan up to next word
c = *pszscan;
while (c && !IN_CHARACTERSET(nextWord,c) )
c = *(++pszscan);
// if '(' then scan for matching ')'
if ( c == '(' || c=='{' )
{
if ( c == '(' )
pszscan = ScanForwardUntil( pszscan, ')' );
else if ( c == '{' )
pszscan = ScanForwardUntil( pszscan, '}' );
c = *(++pszscan);
if (!c)
fdone = 1;
}
if (fdone || !c)
fdone = 1;
else
{
// if . or , insert pause into rgpparseword,
// unless this is the last character
if ((c == '.' || c == ',') && *(pszscan+1) != '\n' && *(pszscan+1) != '\r'
&& *(pszscan+1) != 0)
{
if (c == '.')
rgpparseword[i++] = voxperiod;
else
rgpparseword[i++] = voxcomma;
if (i >= CVOXWORDMAX)
break;
}
// null terminate substring
*pszscan++ = 0;
// skip whitespace
c = *pszscan;
while (c && IN_CHARACTERSET(skip, c))
c = *(++pszscan);
if (!c)
fdone = 1;
else
rgpparseword[i++] = pszscan;
}
}
return rgpparseword;
}
// backwards scan psz for last '/'
// return substring in szpath null terminated
// if '/' not found, return 'vox/'
char *VOX_GetDirectory(char *szpath, char *psz)
{
char c;
int cb = 0;
char *pszscan = psz + strlen(psz) - 1;
// scan backwards until first '/' or start of string
c = *pszscan;
while (pszscan > psz && c != '/')
{
c = *(--pszscan);
cb++;
}
if (c != '/')
{
// didn't find '/', return default directory
strcpy(szpath, "vox/");
return psz;
}
cb = strlen(psz) - cb;
memcpy(szpath, psz, cb);
szpath[cb] = 0;
return pszscan + 1;
}
// set channel volume based on volume of current word
#ifndef SWDS
void VOX_SetChanVol(channel_t *ch)
{
if ( !ch->pMixer )
return;
float scale = ch->pMixer->GetVolumeScale();
if ( scale == 1.0 )
return;
ch->rightvol = (int) (ch->rightvol * scale);
ch->leftvol = (int) (ch->leftvol * scale);
if ( g_AudioDevice->Should3DMix() )
{
ch->rrightvol = (int) (ch->rrightvol * scale);
ch->rleftvol = (int) (ch->rleftvol * scale);
}
else
{
ch->rrightvol = 0;
ch->rleftvol = 0;
}
}
#endif
//===============================================================================
// Get any pitch, volume, start, end params into voxword
// and null out trailing format characters
// Format:
// someword(v100 p110 s10 e20)
//
// v is volume, 0% to n%
// p is pitch shift up 0% to n%
// s is start wave offset %
// e is end wave offset %
// t is timecompression %
//
// pass fFirst == 1 if this is the first string in sentence
// returns 1 if valid string, 0 if parameter block only.
//
// If a ( xxx ) parameter block does not directly follow a word,
// then that 'default' parameter block will be used as the default value
// for all following words. Default parameter values are reset
// by another 'default' parameter block. Default parameter values
// for a single word are overridden for that word if it has a parameter block.
//
//===============================================================================
int VOX_ParseWordParams(char *psz, voxword_t *pvoxword, int fFirst)
{
char *pszsave = psz;
char c;
char ct;
char sznum[8];
int i;
static voxword_t voxwordDefault;
characterset_t commandSet, delimitSet;
// List of valid commands
CharacterSetBuild( &commandSet, "vpset)" );
// init to defaults if this is the first word in string.
if (fFirst)
{
voxwordDefault.pitch = -1;
voxwordDefault.volume = 100;
voxwordDefault.start = 0;
voxwordDefault.end = 100;
voxwordDefault.fKeepCached = 0;
voxwordDefault.timecompress = 0;
}
*pvoxword = voxwordDefault;
// look at next to last char to see if we have a
// valid format:
c = *(psz + strlen(psz) - 1);
if (c != ')')
return 1; // no formatting, return
// scan forward to first '('
CharacterSetBuild( &delimitSet, "()" );
c = *psz;
while ( !IN_CHARACTERSET(delimitSet, c) )
c = *(++psz);
if ( c == ')' )
return 0; // bogus formatting
// null terminate
*psz = 0;
ct = *(++psz);
while (1)
{
// scan until we hit a character in the commandSet
while (ct && !IN_CHARACTERSET(commandSet, ct) )
ct = *(++psz);
if (ct == ')')
break;
memset(sznum, 0, sizeof(sznum));
i = 0;
c = *(++psz);
if (!isdigit(c))
break;
// read number
while (isdigit(c) && i < sizeof(sznum) - 1)
{
sznum[i++] = c;
c = *(++psz);
}
// get value of number
i = atoi(sznum);
switch (ct)
{
case 'v': pvoxword->volume = i; break;
case 'p': pvoxword->pitch = i; break;
case 's': pvoxword->start = i; break;
case 'e': pvoxword->end = i; break;
case 't': pvoxword->timecompress = i; break;
}
ct = c;
}
// if the string has zero length, this was an isolated
// parameter block. Set default voxword to these
// values
if (strlen(pszsave) == 0)
{
voxwordDefault = *pvoxword;
return 0;
}
else
return 1;
}
// link all sounds in sentence, start playing first word.
void VOX_LoadSound( channel_t *pchan, const char *pszin )
{
#ifndef SWDS
char buffer[512];
int i, cword;
char pathbuffer[64];
char szpath[32];
voxword_t rgvoxword[CVOXWORDMAX];
char *psz;
if (!pszin)
return;
memset(rgvoxword, 0, sizeof (voxword_t) * CVOXWORDMAX);
memset(buffer, 0, sizeof(buffer));
// lookup actual string in g_Sentences,
// set pointer to string data
psz = VOX_LookupString(pszin, NULL);
if (!psz)
{
DevMsg ("VOX_LoadSound: no sentence named %s\n",pszin);
return;
}
// get directory from string, advance psz
psz = VOX_GetDirectory(szpath, psz);
if (strlen(psz) > sizeof(buffer) - 1)
{
DevMsg ("VOX_LoadSound: sentence is too long %s\n",psz);
return;
}
// copy into buffer
strcpy(buffer, psz);
psz = buffer;
// parse sentence (also inserts null terminators between words)
VOX_ParseString(psz);
// for each word in the sentence, construct the filename,
// lookup the sfx and save each pointer in a temp array
i = 0;
cword = 0;
while (rgpparseword[i])
{
// Get any pitch, volume, start, end params into voxword
if (VOX_ParseWordParams(rgpparseword[i], &rgvoxword[cword], i == 0))
{
// this is a valid word (as opposed to a parameter block)
strcpy(pathbuffer, szpath);
Q_strncat(pathbuffer, rgpparseword[i], sizeof( pathbuffer ) );
Q_strncat(pathbuffer, ".wav", sizeof( pathbuffer ));
// find name, if already in cache, mark voxword
// so we don't discard when word is done playing
rgvoxword[cword].sfx = S_FindName(pathbuffer,
&(rgvoxword[cword].fKeepCached));
cword++;
}
i++;
}
pchan->pMixer = CreateSentenceMixer( rgvoxword );
if ( !pchan->pMixer )
return;
pchan->isSentence = true;
pchan->sfx = rgvoxword[0].sfx;
#endif
}
//-----------------------------------------------------------------------------
// Purpose: Take a NULL terminated sentence, and parse any commands contained in
// {}. The string is rewritten in place with those commands removed.
//
// Input : *pSentenceData - sentence data to be modified in place
// sentenceIndex - global sentence table index for any data that is
// parsed out
//-----------------------------------------------------------------------------
void VOX_ParseLineCommands( char *pSentenceData, int sentenceIndex )
{
char tempBuffer[512];
char *pNext, *pStart;
int length, tempBufferPos = 0;
if ( !pSentenceData )
return;
pStart = pSentenceData;
while ( *pSentenceData )
{
pNext = ScanForwardUntil( pSentenceData, '{' );
// Find length of "good" portion of the string (not a {} command)
length = pNext - pSentenceData;
if ( tempBufferPos + length > sizeof(tempBuffer) )
{
DevMsg("Error! sentence too long!\n" );
return;
}
// Copy good string to temp buffer
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -