This repository has been archived on 2025-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
CnC_Renegade/Code/Combat/viseme.cpp

640 lines
15 KiB
C++

/*
** Command & Conquer Renegade(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <stdlib.h>
#include <wwdebug.h>
#include "Viseme.h"
#define IS_VOWEL(x) ( x && (x=='a' || x=='e' || x=='i' || x=='o' || x=='u') )
#define IS_CONSONANT(x) ( x && !IS_VOWEL(x) )
struct VisemeTableItem
{
char *LetterCombination;
char Visemes[2];
};
static VisemeTableItem gsVisemeTable[] =
{
{"air", VISEME_IF, -1},
{"ar", VISEME_OX, -1},
{"ch", VISEME_CHURCH, -1},
{"ea", VISEME_EAT, -1},
{"ee", VISEME_EAT, -1},
{"er", VISEME_EARTH, -1},
{"oa", VISEME_OX, -1},
{"oo", VISEME_WET, -1},
{"ou", VISEME_OX, VISEME_WET},
{"ow", VISEME_WET},
{"qu", VISEME_WET, -1},
{"sch", VISEME_SIZE, VISEME_CAGE},
{"sh", VISEME_CHURCH, -1},
{"tch", VISEME_CHURCH, -1},
{"th", VISEME_THOUGH, -1},
{"gn", VISEME_NEW, -1},
{"kn", VISEME_NEW, -1},
{"eye", VISEME_IF, -1},
{"uy", VISEME_IF, -1},
{"ar", VISEME_OX, -1},
{"kw", VISEME_WET, -1},
{"and", VISEME_NEW, VISEME_TOLD},
{"ze", VISEME_ROAR, VISEME_EAT}, // numbers: zero to nine
{"ro", VISEME_WET, VISEME_OX},
{"one", VISEME_WET, VISEME_CAT},
{"two", VISEME_EAT, VISEME_WET},
{"thr", VISEME_THOUGH, VISEME_ROAR},
{"fou", VISEME_FAVE, VISEME_OX},
{"ive", VISEME_IF, VISEME_FAVE},
{"six", VISEME_UP, VISEME_IF},
{"se", VISEME_UP, VISEME_EAT},
{"ven", VISEME_FAVE, VISEME_EAT},
{"eight", VISEME_CAT, VISEME_EAT},
{"ni", VISEME_THOUGH, VISEME_IF},
{"ne", VISEME_THOUGH, -1}, // end numbers
};
struct VisemeTableReferenceItem
{
int StartIndex;
int Count;
};
#define NUM_VISEME_REFERENCES 26 // 'a' - 'z'
static VisemeTableReferenceItem VisemeReferenceTable[NUM_VISEME_REFERENCES];
//======================================================================================
static int CompareVisemeTableItems( const void *arg1, const void *arg2 )
{
VisemeTableItem *p1 = (VisemeTableItem *)arg1;
VisemeTableItem *p2 = (VisemeTableItem *)arg2;
return strcmp(p1->LetterCombination, p2->LetterCombination);
}
//======================================================================================
VisemeManager::VisemeManager(void)
{
// sort viseme lookup table
int numVisemeTableItems = sizeof(gsVisemeTable) / sizeof(VisemeTableItem);
qsort(gsVisemeTable, numVisemeTableItems, sizeof(VisemeTableItem), CompareVisemeTableItems);
// build viseme lookup reference table
memset(VisemeReferenceTable, 0, sizeof(VisemeReferenceTable));
VisemeTableItem *pItem = gsVisemeTable;
for (int i=0; i<numVisemeTableItems; i++,pItem++) {
int index = (int)tolower(pItem->LetterCombination[0]) - 'a';
if ( index >= 0 && index < NUM_VISEME_REFERENCES ) {
VisemeTableReferenceItem *pR = &VisemeReferenceTable[index];
if ( pR->Count == 0 ) {
pR->StartIndex = i;
}
pR->Count++;
}
}
}
//======================================================================================
// GetVisemes(const char *word, int *visemelist, int maxvisemes)
// Input: word - word to parse
// maxvisemes - max. number of visemes on the output list visemelist
// Output: visemelist - contains viseme ID's
// Remark: this routine examines word and identifies visemes and place them
// on the given output list(visemelist)
// Return: number of visemes on the output list
//
int VisemeManager::Get_Visemes(const char *word, int *visemelist, int maxvisemes) const
{
char prevchar = 0;
int last_viseme = -1;
int viseme[2];
int i;
int offset;
int num_visemes = 0;
char local_buf[128];
// make a local copy of the word in lower case
strncpy(local_buf, word, sizeof(local_buf)-1);
local_buf[sizeof(local_buf)-1] = 0;
_strlwr(local_buf);
const char *pchar = local_buf;
while ( *pchar ) {
// check for tabled viseme combinations
offset = Lookup(pchar, word, viseme);
if ( offset == 0 ) {
// analyse the current letter
// init default viseme ID
viseme[0] = VISEME_TOLD;
viseme[1] = -1;
offset = 1;
switch (*pchar) {
case 'a':
offset = Do_Letter_a(pchar, prevchar, viseme);
break;
case 'b':
viseme[0] = VISEME_BUMP;
break;
case 'c':
viseme[0] = VISEME_CAGE;
break;
case 'd':
viseme[0] = VISEME_TOLD;
break;
case 'e':
offset = Do_Letter_e(pchar, prevchar, viseme);
break;
case 'f':
viseme[0] = VISEME_FAVE;
break;
case 'g':
viseme[0] = VISEME_CAGE;
break;
case 'h':
// no sound
break;
case 'i':
offset = Do_Letter_i(pchar, prevchar, viseme);
break;
case 'j':
viseme[0] = VISEME_CHURCH;
break;
case 'k':
viseme[0] = VISEME_CAGE;
break;
case 'l':
viseme[0] = VISEME_THOUGH;
break;
case 'm':
viseme[0] = VISEME_BUMP;
break;
case 'n':
viseme[0] = VISEME_NEW;
break;
case 'o':
offset = Do_Letter_o(pchar, prevchar, viseme);
break;
case 'p':
viseme[0] = VISEME_BUMP;
break;
case 'q':
viseme[0] = VISEME_CAGE;
break;
case 'r':
viseme[0] = VISEME_ROAR;
break;
case 's':
offset = Do_Letter_s(pchar, prevchar, viseme);
break;
case 't':
offset = Do_Letter_t(pchar, prevchar, viseme);
break;
case 'u':
offset = Do_Letter_u(pchar, prevchar, viseme);
break;
case 'v':
viseme[0] = VISEME_FAVE;
break;
case 'w':
viseme[0] = VISEME_WET;
break;
case 'x':
viseme[0] = VISEME_CAGE;
viseme[1] = VISEME_SIZE;
break;
case 'y':
// geo-aug22/00 no viseme reference
//viseme[0] = VISEME_WET;
break;
case 'z':
viseme[0] = VISEME_SIZE;
break;
}
}
// add the newly found visemes to the list
for (i=0; i<2; i++) {
if ( viseme[i] >= 0 && viseme[i] != last_viseme ) {
*visemelist++ = viseme[i];
last_viseme = viseme[i];
// check for list limitation
if ( ++num_visemes >= maxvisemes ) {
// limit reached
return(num_visemes);
}
}
}
// next letter
pchar += offset;
prevchar = *(pchar - 1);
}
return(num_visemes);
}
//======================================================================================
// return: number of bytes to jump over when done
//
int VisemeManager::Lookup(const char *pchar, const char * /*word*/, int viseme[]) const
{
int length = 0;
char ch = (char)tolower(*pchar);
int index = ch - 'a';
if ( index < 0 || index >= NUM_VISEME_REFERENCES ) {
// out of range
return 0;
}
VisemeTableReferenceItem *pR = &VisemeReferenceTable[index];
// we search backwards so that we can find the max. match first
VisemeTableItem *pI = &gsVisemeTable[pR->StartIndex + pR->Count - 1];
for (int i=0; i<pR->Count; i++,pI--) {
length = strlen(pI->LetterCombination);
if ( strnicmp(pchar, pI->LetterCombination, length) == 0 )
{
// found!
viseme[0] = pI->Visemes[0];
viseme[1] = pI->Visemes[1];
return length;
}
}
return 0;
}
//======================================================================================
// return: number of bytes to jump over when done
//
int VisemeManager::Do_Letter_a(const char *pchar, char prevchar, int *viseme) const
{
int offset = 1; // default
// default as in "a" or "about"
viseme[0] = VISEME_TOLD;
viseme[1] = -1;
if ( IS_VOWEL(pchar[1]) ) {
offset++;
switch (pchar[1]) {
case 'i': // ai
viseme[1] = VISEME_EAT; // default: e.g. plaid, aim, maize, train
if ( pchar[2] == 's' ) {
// as in "aisle"
viseme[0] = VISEME_CAT;
}
break;
case 'o': // ao
// as in "gaol"
viseme[1] = VISEME_EAT;
break;
case 'u': // au
switch (prevchar) {
case 'g': // as in "gauge"
viseme[1] = VISEME_EAT;
break;
case 'l': // as in "laugh"
viseme[0] = VISEME_CAT;
break;
case 's': // as in "sauerkraut"
viseme[0] = VISEME_CAT;
viseme[1] = VISEME_WET;
break;
default: // as in "daughter"
viseme[0] = VISEME_CAT;
}
break;
}
}
else {
if ( IS_VOWEL(pchar[2]) ) {
// e.g. "ate", 12+2
viseme[1] = VISEME_EAT;
}
else {
// e.g. "add", 1
viseme[0] = VISEME_CAT;
}
}
return(offset);
}
//======================================================================================
// return: number of bytes to jump over when done
//
int VisemeManager::Do_Letter_e(const char *pchar, char prevchar, int *viseme) const
{
int offset = 1;
// default as in "e" or "be"
viseme[0] = VISEME_EAT; // default, as in "beach"
viseme[1] = -1;
if ( IS_VOWEL(pchar[1]) ) {
offset++;
//viseme[0] = VISEME_EAT; // default, as in "beach"
switch ( pchar[1] ) {
case 'a': // ea
if ( pchar[2] == 'u' ) {
// as in "beauty"
viseme[0] = VISEME_EAT;
viseme[1] = VISEME_WET;
offset++;
}
break;
case 'i': // ei
if ( prevchar == 'h' ) {
// as in "height"
viseme[0] = VISEME_CAT;
viseme[1] = VISEME_EAT;
}
else if ( pchar[2] == 'g' ) {
// as in "eight", "reign"
viseme[0] = VISEME_TOLD;
viseme[1] = VISEME_EAT;
}
else if ( pchar[2] == 'z' ) {
// as in "seize"
viseme[0] = VISEME_EAT;
}
else {// plain old "ei"
// default:
viseme[0] = VISEME_IF;
}
break;
case 'o': // eo
if ( prevchar == 'p' ) {
// as in "people"
viseme[0] = VISEME_EAT;
}
else {
// default:
viseme[0] = VISEME_TOLD;
}
break;
case 'u': // eu
// as in "maneuver", "eulogy", "queue"
viseme[0] = VISEME_WET;
break;
}
}
else {
if ( prevchar == 'b' || pchar[1] == 0 ) {
// as in "be"
viseme[0] = VISEME_EAT;
}
else {
// default as in "pet",
viseme[0] = VISEME_WET;
}
}
return(offset);
}
//======================================================================================
// return: number of bytes to jump over when done
//
int VisemeManager::Do_Letter_i(const char *pchar, char prevchar, int *viseme) const
{
int offset = 1;
// default as in "I"
viseme[0] = VISEME_IF;
viseme[1] = -1;
if ( IS_VOWEL(pchar[1]) ) {
offset++;
viseme[0] = VISEME_TOLD; // default,
viseme[1] = -1;
switch ( pchar[1] ) {
case 'e': // ie
if ( pchar[2] == 0 && prevchar == 'l' ) {
// as in "lie"
viseme[0] = VISEME_CAT;
viseme[1] = VISEME_EAT;
}
else if ( pchar[2] == 'u' || pchar[2] == 'w' ) {
viseme[0] = VISEME_WET; // as in lieutenant or view
offset++;
}
else {
viseme[0] = VISEME_EAT; // as in grief
}
break;
case 'o': // io
viseme[0] = VISEME_EAT; // as in onion
break;
}
}
else {
viseme[0] = VISEME_IF; // as in "pig"
viseme[1] = -1;
}
return(offset);
}
//======================================================================================
// return: number of bytes to jump over when done
//
int VisemeManager::Do_Letter_o(const char *pchar, char /*prevchar*/, int *viseme) const
{
int offset = 1;
// default as in "no"
viseme[0] = VISEME_OX;
viseme[1] = -1;
// geo-modified aug21/00
//viseme[1] = VISEME_WET;
if ( IS_VOWEL(pchar[1]) ) {
offset++;
viseme[0] = VISEME_UP; // default, as in "took"
viseme[1] = -1;
switch ( pchar[1] ) {
case 'a': // oa
if ( pchar[-2] == 'b' && pchar[-1] == 'r' ) {
viseme[0] = VISEME_UP; // as in "broad"
}
else {
viseme[0] = VISEME_OX; // as in "loan"
viseme[1] = VISEME_WET;
}
break;
case 'e': // oe
viseme[0] = VISEME_WET; // default as in "canoe", "foe"
if ( pchar[-1] == 'd' && pchar[2] == 's' ) {
viseme[0] = VISEME_UP; // as in "does"
}
else if ( pchar[-2] == 'p' && pchar[-1] == 'h' ) {
viseme[0] = VISEME_EAT; // as in "phoenix"
}
break;
case 'i': // oi
viseme[0] = VISEME_UP; // as in "noise"
viseme[1] = VISEME_EAT;
break;
case 'o': // oo
// GEO-modified aug21/00
viseme[0] = VISEME_WET; // default as in "took", "look", "book"
break;
case 'u': // ou
viseme[0] = VISEME_WET; // default as in "through", "though","croup"
if ( pchar[-2] == 't' && pchar[-1] == 'r' ) {
// as in "trouble", "trough"
viseme[0] = VISEME_UP;
}
else if ( pchar[-2] == 't' && pchar[-1] == 'h' ) {
if ( strncmp(&pchar[2], "ght", 3) == 0 ) {
viseme[0] = VISEME_UP; // as in "thought"
offset += 3;
}
}
else if ( pchar[2] == 'r' ) {
viseme[0] = VISEME_TOLD; // as in "journey"
offset++;
}
break;
}
}
else if ( pchar[1] ) {
if ( IS_VOWEL(pchar[2]) ) {
viseme[0] = VISEME_WET; // as in "move"
viseme[1] = -1;
}
else {
viseme[0] = VISEME_OX; // as in "comment"
viseme[1] = -1;
}
}
return(offset);
}
//======================================================================================
// return: number of bytes to jump over when done
//
int VisemeManager::Do_Letter_s(const char *pchar, char /*prevchar*/, int *viseme) const
{
int offset = 1;
// default as in "Say", "yourS"
//viseme[0] = VISEME_TOLD;
viseme[0] = VISEME_SIZE;
viseme[1] = -1;
// special cases
if ( pchar[1] == 'h' || pchar[1] == 'u' ) {
viseme[0] = VISEME_CHURCH; // e.g. "ship", "sure", "shoot"
}
else if ( pchar[1] == 'c' ) {
if ( pchar[2] == 'h' ) {
viseme[0] = VISEME_CHURCH; // e.g. "school"
viseme[1] = VISEME_TOLD;
offset = 3;
}
else {
viseme[0] = VISEME_TOLD; // e.g. "scent"
offset = 2;
}
}
else if ( strncmp(&pchar[1], "eou", 3) == 0 ) {
viseme[0] = VISEME_CHURCH; // e.g. "nauseous"
offset = 4;
}
return(offset);
}
//======================================================================================
// return: number of bytes to jump over when done
//
int VisemeManager::Do_Letter_t(const char *pchar, char /*prevchar*/, int *viseme) const
{
int offset = 1;
// default as in "t"
viseme[0] = VISEME_TOLD;
// special cases
if ( pchar[1] == 'c' ) {
if ( pchar[2] == 'h' ) {
viseme[0] = VISEME_CHURCH; // e.g. "catch"
offset = 3;
}
}
else if ( pchar[1] == 'h' ) {
viseme[0] = VISEME_THOUGH; // e.g. "this","thin", "through", "then"
offset = 2; // except for "thomas"
}
return(offset);
}
//======================================================================================
// return: number of bytes to jump over when done
//
int VisemeManager::Do_Letter_u(const char *pchar, char /*prevchar*/, int *viseme) const
{
int offset = 1;
// default as in "u" or "use"
//viseme[0] = VISEME_EAT;
//viseme[1] = VISEME_WET;
// geo-modified aug21/00
viseme[0] = VISEME_UP;
if ( IS_VOWEL(pchar[1]) ) {
offset++;
switch ( pchar[1] ) {
case 'e': // ue
case 'i': // ui
viseme[0] = VISEME_WET; // as in "blue" or "fruit"
viseme[1] = -1;
break;
}
}
else {
if ( IS_CONSONANT(pchar[2]) ) {
viseme[0] = VISEME_UP; // as in "utter"
viseme[1] = -1;
}
}
return(offset);
}
//======================================================================================
//======================================================================================
//======================================================================================