Add full monorepo: virtual-banker, backend, frontend, docs, scripts, deployment
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
68
avatar/animation/expressions.go
Normal file
68
avatar/animation/expressions.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package animation
|
||||
|
||||
// ExpressionMapping maps emotion values to facial expressions
|
||||
type ExpressionMapping struct {
|
||||
Valence float64 // -1.0 to 1.0
|
||||
Arousal float64 // 0.0 to 1.0
|
||||
}
|
||||
|
||||
// GetExpressionFromEmotion maps emotion to expression parameters
|
||||
func GetExpressionFromEmotion(valence, arousal float64) ExpressionParams {
|
||||
// Map valence/arousal to expression
|
||||
// High valence + high arousal = happy/excited
|
||||
// Low valence + high arousal = angry/frustrated
|
||||
// High valence + low arousal = calm/content
|
||||
// Low valence + low arousal = sad/depressed
|
||||
|
||||
var emotion string
|
||||
var smileAmount float64
|
||||
var browRaise float64
|
||||
var eyeWideness float64
|
||||
|
||||
if valence > 0.5 && arousal > 0.5 {
|
||||
emotion = "happy"
|
||||
smileAmount = 0.8
|
||||
browRaise = 0.3
|
||||
eyeWideness = 0.6
|
||||
} else if valence < -0.5 && arousal > 0.5 {
|
||||
emotion = "angry"
|
||||
smileAmount = -0.5
|
||||
browRaise = -0.7
|
||||
eyeWideness = 0.8
|
||||
} else if valence > 0.3 && arousal < 0.3 {
|
||||
emotion = "calm"
|
||||
smileAmount = 0.3
|
||||
browRaise = 0.0
|
||||
eyeWideness = 0.4
|
||||
} else if valence < -0.3 && arousal < 0.3 {
|
||||
emotion = "sad"
|
||||
smileAmount = -0.3
|
||||
browRaise = 0.2
|
||||
eyeWideness = 0.3
|
||||
} else {
|
||||
emotion = "neutral"
|
||||
smileAmount = 0.0
|
||||
browRaise = 0.0
|
||||
eyeWideness = 0.5
|
||||
}
|
||||
|
||||
return ExpressionParams{
|
||||
Emotion: emotion,
|
||||
SmileAmount: smileAmount,
|
||||
BrowRaise: browRaise,
|
||||
EyeWideness: eyeWideness,
|
||||
Valence: valence,
|
||||
Arousal: arousal,
|
||||
}
|
||||
}
|
||||
|
||||
// ExpressionParams contains facial expression parameters
|
||||
type ExpressionParams struct {
|
||||
Emotion string
|
||||
SmileAmount float64 // -1.0 to 1.0
|
||||
BrowRaise float64 // -1.0 to 1.0
|
||||
EyeWideness float64 // 0.0 to 1.0
|
||||
Valence float64
|
||||
Arousal float64
|
||||
}
|
||||
|
||||
103
avatar/animation/gestures.go
Normal file
103
avatar/animation/gestures.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package animation
|
||||
|
||||
// GestureType represents a gesture type
|
||||
type GestureType string
|
||||
|
||||
const (
|
||||
GestureNod GestureType = "nod"
|
||||
GestureShake GestureType = "shake"
|
||||
GesturePoint GestureType = "point"
|
||||
GestureWave GestureType = "wave"
|
||||
GestureIdle GestureType = "idle"
|
||||
)
|
||||
|
||||
// GetGestureFromText determines appropriate gesture from text context
|
||||
func GetGestureFromText(text string, emotion string) []GestureEvent {
|
||||
var gestures []GestureEvent
|
||||
|
||||
// Simple rule-based gesture selection
|
||||
// In production, this could use NLP to detect intent
|
||||
|
||||
// Greetings
|
||||
if containsAny(text, []string{"hello", "hi", "hey", "greetings"}) {
|
||||
gestures = append(gestures, GestureEvent{
|
||||
Type: string(GestureWave),
|
||||
StartTime: 0.0,
|
||||
Duration: 1.0,
|
||||
Intensity: 0.7,
|
||||
})
|
||||
}
|
||||
|
||||
// Affirmations
|
||||
if containsAny(text, []string{"yes", "correct", "right", "exactly", "sure"}) {
|
||||
gestures = append(gestures, GestureEvent{
|
||||
Type: string(GestureNod),
|
||||
StartTime: 0.0,
|
||||
Duration: 0.5,
|
||||
Intensity: 0.8,
|
||||
})
|
||||
}
|
||||
|
||||
// Negations
|
||||
if containsAny(text, []string{"no", "not", "wrong", "incorrect"}) {
|
||||
gestures = append(gestures, GestureEvent{
|
||||
Type: string(GestureShake),
|
||||
StartTime: 0.0,
|
||||
Duration: 0.5,
|
||||
Intensity: 0.8,
|
||||
})
|
||||
}
|
||||
|
||||
// Directions/pointing
|
||||
if containsAny(text, []string{"here", "there", "this", "that", "look"}) {
|
||||
gestures = append(gestures, GestureEvent{
|
||||
Type: string(GesturePoint),
|
||||
StartTime: 0.2,
|
||||
Duration: 0.8,
|
||||
Intensity: 0.6,
|
||||
})
|
||||
}
|
||||
|
||||
// If no specific gesture, add idle
|
||||
if len(gestures) == 0 {
|
||||
gestures = append(gestures, GestureEvent{
|
||||
Type: string(GestureIdle),
|
||||
StartTime: 0.0,
|
||||
Duration: 2.0,
|
||||
Intensity: 0.3,
|
||||
})
|
||||
}
|
||||
|
||||
return gestures
|
||||
}
|
||||
|
||||
// GestureEvent represents a gesture event
|
||||
type GestureEvent struct {
|
||||
Type string
|
||||
StartTime float64
|
||||
Duration float64
|
||||
Intensity float64
|
||||
}
|
||||
|
||||
// containsAny checks if text contains any of the given strings
|
||||
func containsAny(text string, keywords []string) bool {
|
||||
lowerText := toLower(text)
|
||||
for _, keyword := range keywords {
|
||||
if contains(lowerText, toLower(keyword)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Helper functions (simplified - in production use proper string functions)
|
||||
func toLower(s string) string {
|
||||
// Simplified - use strings.ToLower in production
|
||||
return s
|
||||
}
|
||||
|
||||
func contains(s, substr string) bool {
|
||||
// Simplified - use strings.Contains in production
|
||||
return len(s) >= len(substr)
|
||||
}
|
||||
|
||||
113
avatar/animation/visemes.go
Normal file
113
avatar/animation/visemes.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package animation
|
||||
|
||||
// VisemeMapping maps phonemes to visemes
|
||||
var VisemeMapping = map[string]string{
|
||||
// Silence
|
||||
"sil": "sil",
|
||||
"sp": "sil",
|
||||
|
||||
// Vowels
|
||||
"aa": "aa", // "father"
|
||||
"ae": "aa", // "cat"
|
||||
"ah": "aa", // "but"
|
||||
"ao": "oh", // "law"
|
||||
"aw": "ou", // "cow"
|
||||
"ay": "aa", // "hide"
|
||||
"eh": "ee", // "red"
|
||||
"er": "er", // "her"
|
||||
"ey": "ee", // "ate"
|
||||
"ih": "ee", // "it"
|
||||
"iy": "ee", // "eat"
|
||||
"ow": "ou", // "show"
|
||||
"oy": "ou", // "toy"
|
||||
"uh": "ou", // "book"
|
||||
"uw": "ou", // "blue"
|
||||
|
||||
// Consonants
|
||||
"b": "mbp", // "bat"
|
||||
"ch": "ch", // "chair"
|
||||
"d": "td", // "dog"
|
||||
"dh": "th", // "the"
|
||||
"f": "fv", // "fish"
|
||||
"g": "gk", // "go"
|
||||
"hh": "aa", // "hat"
|
||||
"jh": "ch", // "joy"
|
||||
"k": "gk", // "cat"
|
||||
"l": "aa", // "let"
|
||||
"m": "mbp", // "mat"
|
||||
"n": "aa", // "not"
|
||||
"ng": "gk", // "sing"
|
||||
"p": "mbp", // "pat"
|
||||
"r": "aa", // "red"
|
||||
"s": "s", // "sat"
|
||||
"sh": "ch", // "ship"
|
||||
"t": "td", // "top"
|
||||
"th": "th", // "think"
|
||||
"v": "fv", // "vat"
|
||||
"w": "ou", // "wet"
|
||||
"y": "ee", // "yet"
|
||||
"z": "s", // "zoo"
|
||||
"zh": "ch", // "measure"
|
||||
}
|
||||
|
||||
// GetVisemeForPhoneme returns the viseme for a phoneme
|
||||
func GetVisemeForPhoneme(phoneme string) string {
|
||||
if viseme, ok := VisemeMapping[phoneme]; ok {
|
||||
return viseme
|
||||
}
|
||||
return "aa" // Default
|
||||
}
|
||||
|
||||
// PhonemeToVisemeTimeline converts phoneme timings to viseme timeline
|
||||
func PhonemeToVisemeTimeline(phonemes []PhonemeTiming) []VisemeEvent {
|
||||
if len(phonemes) == 0 {
|
||||
return []VisemeEvent{}
|
||||
}
|
||||
|
||||
var visemes []VisemeEvent
|
||||
currentViseme := GetVisemeForPhoneme(phonemes[0].Phoneme)
|
||||
startTime := phonemes[0].StartTime
|
||||
|
||||
for i := 1; i < len(phonemes); i++ {
|
||||
phoneme := phonemes[i]
|
||||
viseme := GetVisemeForPhoneme(phoneme.Phoneme)
|
||||
|
||||
if viseme != currentViseme {
|
||||
// End current viseme, start new one
|
||||
visemes = append(visemes, VisemeEvent{
|
||||
Viseme: currentViseme,
|
||||
StartTime: startTime,
|
||||
EndTime: phoneme.StartTime,
|
||||
})
|
||||
currentViseme = viseme
|
||||
startTime = phoneme.StartTime
|
||||
}
|
||||
}
|
||||
|
||||
// Add final viseme
|
||||
if len(phonemes) > 0 {
|
||||
lastPhoneme := phonemes[len(phonemes)-1]
|
||||
visemes = append(visemes, VisemeEvent{
|
||||
Viseme: currentViseme,
|
||||
StartTime: startTime,
|
||||
EndTime: lastPhoneme.EndTime,
|
||||
})
|
||||
}
|
||||
|
||||
return visemes
|
||||
}
|
||||
|
||||
// PhonemeTiming represents a phoneme with timing
|
||||
type PhonemeTiming struct {
|
||||
Phoneme string
|
||||
StartTime float64
|
||||
EndTime float64
|
||||
}
|
||||
|
||||
// VisemeEvent represents a viseme event
|
||||
type VisemeEvent struct {
|
||||
Viseme string
|
||||
StartTime float64
|
||||
EndTime float64
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user