Skip to content
This repository was archived by the owner on May 30, 2024. It is now read-only.

Commit 2efe01d

Browse files
authored
Merge pull request #88 from you-win/feature/real-time-lip-sync-gap
Feature/real time lip sync Former-commit-id: c235cbc
2 parents 7fd633c + 58426b9 commit 2efe01d

File tree

13 files changed

+275
-18
lines changed

13 files changed

+275
-18
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[gd_resource type="NativeScript" load_steps=2 format=2]
2+
3+
[ext_resource path="res://addons/real-time-lip-sync-gd/real_time_lip_sync.gdnlib" type="GDNativeLibrary" id=1]
4+
5+
[resource]
6+
resource_name = "LipSync"
7+
class_name = "LipSync"
8+
library = ExtResource( 1 )
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[general]
2+
3+
singleton=false
4+
load_once=true
5+
symbol_prefix="godot_"
6+
reloadable=true
7+
8+
[entry]
9+
10+
Windows.64="res://addons/real-time-lip-sync-gd/real_time_lip_sync_gd.dll"
11+
12+
[dependencies]
13+
14+
Windows.64=[ ]
Binary file not shown.

entities/vrm/VRMModel.gd

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ var u: ExpressionData
5757
# TODO stopgap
5858
var last_expression: ExpressionData
5959

60+
var current_mouth_shape: ExpressionData
61+
const VOWEL_HISTORY: int = 5 # TODO move to config
62+
const MIN_VOWEL_CHANGE: int = 3 # TODO move to config
63+
var last_vowels: Array = []
64+
6065
var all_expressions: Dictionary = {} # String: ExpressionData
6166

6267
###############################################################################
@@ -79,6 +84,8 @@ func _ready() -> void:
7984
# TODO stopgap
8085
AppManager.sb.connect("blend_shapes", self, "_on_blend_shapes")
8186

87+
AppManager.sb.connect("lip_sync_updated", self, "_on_lip_sync_updated")
88+
8289
# Map expressions
8390
var anim_player: AnimationPlayer = find_node("anim")
8491

@@ -113,6 +120,8 @@ func _ready() -> void:
113120

114121
for key in all_expressions.keys():
115122
set(key, all_expressions[key])
123+
124+
current_mouth_shape = a
116125

117126
_map_eye_expressions(all_expressions)
118127

@@ -154,6 +163,53 @@ func _on_blend_shapes(value: String) -> void:
154163

155164
last_expression = ed
156165

166+
func _on_lip_sync_updated(data: Dictionary) -> void:
167+
for x in current_mouth_shape.morphs:
168+
_modify_blend_shape(x.mesh, x.morph, 1)
169+
170+
last_vowels.push_back(data["vowel"])
171+
if last_vowels.size() > VOWEL_HISTORY:
172+
last_vowels.pop_front()
173+
174+
var vowel_count: Dictionary = {
175+
"a": 0,
176+
"i": 0,
177+
"u": 0,
178+
"e": 0,
179+
"o": 0
180+
}
181+
for x in last_vowels:
182+
match x:
183+
0: # A
184+
vowel_count.a += 1
185+
1: # I
186+
vowel_count.i += 1
187+
2: # U
188+
vowel_count.u += 1
189+
3: # E
190+
vowel_count.e += 1
191+
4: # O
192+
vowel_count.o += 1
193+
194+
var last_shape = current_mouth_shape
195+
196+
if vowel_count.a >= MIN_VOWEL_CHANGE:
197+
current_mouth_shape = a
198+
elif vowel_count.i >= MIN_VOWEL_CHANGE:
199+
current_mouth_shape = i
200+
elif vowel_count.u >= MIN_VOWEL_CHANGE:
201+
current_mouth_shape = u
202+
elif vowel_count.e >= MIN_VOWEL_CHANGE:
203+
current_mouth_shape = e
204+
elif vowel_count.o >= MIN_VOWEL_CHANGE:
205+
current_mouth_shape = o
206+
207+
if current_mouth_shape != last_shape:
208+
for x in current_mouth_shape.morphs:
209+
_modify_blend_shape(x.mesh, x.morph, 1)
210+
for x in last_shape.morphs:
211+
_modify_blend_shape(x.mesh, x.morph, 0)
212+
157213
###############################################################################
158214
# Private functions #
159215
###############################################################################
@@ -276,25 +332,25 @@ func custom_update(data, interpolation_data) -> void:
276332
if (last_expression != joy and last_expression != sorrow):
277333
# Left eye blinking
278334
if data.left_eye_open >= blink_threshold:
279-
for i in blink_r.morphs:
280-
_modify_blend_shape(i.mesh, i.morph, i.values[1] - interpolation_data.interpolate(InterpolationData.InterpolationDataType.LEFT_EYE_BLINK, 1.0))
335+
for x in blink_r.morphs:
336+
_modify_blend_shape(x.mesh, x.morph, x.values[1] - interpolation_data.interpolate(InterpolationData.InterpolationDataType.LEFT_EYE_BLINK, 1.0))
281337
else:
282-
for i in blink_r.morphs:
283-
_modify_blend_shape(i.mesh, i.morph, i.values[1])
338+
for x in blink_r.morphs:
339+
_modify_blend_shape(x.mesh, x.morph, x.values[1])
284340

285341
# Right eye blinking
286342
if data.right_eye_open >= blink_threshold:
287-
for i in blink_l.morphs:
288-
_modify_blend_shape(i.mesh, i.morph, i.values[1] - interpolation_data.interpolate(InterpolationData.InterpolationDataType.RIGHT_EYE_BLINK, 1.0))
343+
for x in blink_l.morphs:
344+
_modify_blend_shape(x.mesh, x.morph, x.values[1] - interpolation_data.interpolate(InterpolationData.InterpolationDataType.RIGHT_EYE_BLINK, 1.0))
289345
else:
290-
for i in blink_l.morphs:
291-
_modify_blend_shape(i.mesh, i.morph, i.values[1])
346+
for x in blink_l.morphs:
347+
_modify_blend_shape(x.mesh, x.morph, x.values[1])
292348
else:
293349
# Unblink if the facial expression doesn't allow blinking
294-
for i in blink_r.morphs:
295-
_modify_blend_shape(i.mesh, i.morph, i.values[0])
296-
for i in blink_l.morphs:
297-
_modify_blend_shape(i.mesh, i.morph, i.values[0])
350+
for x in blink_r.morphs:
351+
_modify_blend_shape(x.mesh, x.morph, x.values[0])
352+
for x in blink_l.morphs:
353+
_modify_blend_shape(x.mesh, x.morph, x.values[0])
298354

299355
# TODO eyes show weird behaviour when blinking
300356
# TODO make sure angle between eyes' x values are at least parallel
@@ -340,10 +396,10 @@ func custom_update(data, interpolation_data) -> void:
340396
skeleton.set_bone_pose(left_eye_id, right_eye_transform)
341397

342398
# Mouth tracking
343-
for i in a.morphs:
344-
_modify_blend_shape(i.mesh, i.morph,
345-
min(max(i.values[0], interpolation_data.interpolate(InterpolationData.InterpolationDataType.MOUTH_MOVEMENT, 2.0)),
346-
i.values[1]))
399+
for x in current_mouth_shape.morphs:
400+
_modify_blend_shape(x.mesh, x.morph,
401+
min(max(x.values[0], interpolation_data.interpolate(InterpolationData.InterpolationDataType.MOUTH_MOVEMENT, 2.0)),
402+
x.values[1]))
347403
else:
348404
# TODO implement eco mode, should be more efficient than standard mode
349405
# Eco-mode blinking

project.godot

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ _global_script_classes=[ {
6969
"language": "GDScript",
7070
"path": "res://utils/JSONUtil.gd"
7171
}, {
72+
"base": "Node",
73+
"class": "LipSyncManager",
74+
"language": "GDScript",
75+
"path": "res://utils/LipSyncManager.gd"
76+
}, {
7277
"base": "Reference",
7378
"class": "Logger",
7479
"language": "GDScript",
@@ -122,6 +127,7 @@ _global_script_class_icons={
122127
"GOTHGui": "",
123128
"InterpolationData": "",
124129
"JSONUtil": "",
130+
"LipSyncManager": "",
125131
"Logger": "",
126132
"MainScreen": "",
127133
"ModelDisplayScreen": "",
@@ -138,6 +144,10 @@ config/name="OpenSeeFace GD"
138144
run/main_scene="res://screens/MainScreen.tscn"
139145
config/icon="res://assets/osfgd_icon.png"
140146

147+
[audio]
148+
149+
enable_audio_input=true
150+
141151
[autoload]
142152

143153
AppManager="*res://utils/AppManager.gd"

resources/gui/tracking.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
<input name="Tracker Address" data="tracker_address" event="tracker_address" type="string"/>
3838
<input name="Tracker Port" data="tracker_port" event="tracker_port" type="integer"/>
3939
<button name="Start Tracker" event="toggle_tracker" label_updatable="true"/>
40+
<label name="Lip Sync"/>
41+
<toggle name="Use lip sync" data="use_lip_sync" event="use_lip_sync"/>
4042
<!-- TODO this is bad, should refactor into a list -->
4143
<label name="Blend Shapes"/>
4244
<drop_down name="Blend Shapes" event="blend_shapes" setup="setup_blend_shapes"/>

screens/MainScreen.gd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func _ready() -> void:
3939

4040
AppManager.cm.metadata_config.apply_rendering_changes(get_viewport())
4141

42-
AppManager.logger.notify("Welcome to openseeface-gd!")
42+
AppManager.logger.notify("Press TAB to hide the UI")
4343

4444
func _unhandled_input(event: InputEvent) -> void:
4545
if(event.is_action_pressed("ui_cancel") and OS.is_debug_build()):

screens/ModelDisplayScreen.gd

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ func _ready() -> void:
7878
AppManager.sb.connect(i, self, "_on_%s" % i)
7979
set(i, AppManager.cm.current_model_config.get(i))
8080

81+
AppManager.sb.connect("lip_sync_updated", self, "_on_lip_sync_updated")
82+
8183
if model_resource_path:
8284
_try_load_model(model_resource_path)
8385

@@ -218,6 +220,10 @@ func _on_apply_rotation(value: bool) -> void:
218220
func _on_should_track_eye(value: bool) -> void:
219221
should_track_eye = value
220222

223+
func _on_lip_sync_updated(_data: Dictionary) -> void:
224+
interpolation_data.target_mouth_movement = 1
225+
interpolation_data.interpolate(InterpolationData.InterpolationDataType.MOUTH_MOVEMENT, 0.8)
226+
221227
###############################################################################
222228
# Private functions #
223229
###############################################################################
@@ -226,6 +232,7 @@ func _try_load_model(file_path):
226232
var dir := Directory.new()
227233
if not dir.file_exists(file_path):
228234
AppManager.logger.error("File path not found: %s" % file_path)
235+
AppManager.logger.notify("File path not found: %s" % file_path)
229236
return
230237

231238
match file_path.get_extension():
@@ -268,7 +275,7 @@ func _try_load_model(file_path):
268275
rotation_adjustment = Vector3(-1, -1, 1)
269276
AppManager.logger.info("TSCN file loaded successfully.")
270277
_:
271-
AppManager.logger.info("File extension not recognized. %s" % file_path)
278+
AppManager.logger.notify("File extension not recognized. %s" % file_path)
272279
printerr("File extension not recognized. %s" % file_path)
273280

274281
# TODO probably incorrect?

screens/gui/Toast.gd

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ func _process(delta: float) -> void:
3737
_on_close()
3838
is_closing = true
3939

40+
func _unhandled_input(event: InputEvent) -> void:
41+
if event.is_action_pressed("toggle_gui"):
42+
toast.visible = not toast.visible
43+
4044
###############################################################################
4145
# Connections #
4246
###############################################################################

utils/AppManager.gd

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ const DYNAMIC_PHYSICS_BONES: bool = false
77
onready var sb: SignalBroadcaster = load("res://utils/SignalBroadcaster.gd").new()
88
onready var cm: ConfigManager = load("res://utils/ConfigManager.gd").new()
99
var nm: NotificationManager = load("res://utils/NotificationManager.gd").new()
10+
onready var lsm: LipSyncManager = load("res://utils/LipSyncManager.gd").new()
11+
# TODO clean this up with a stripped down implementation
12+
#onready var estimate_vowel = load("res://addons/godot-audio-processing/EstimateVowel.gd").new()
13+
#onready var rtls = load("res://addons/real-time-lip-sync-gd/lip_sync.gdns").new()
14+
#var effect
15+
#var buffer = 5
16+
1017
onready var logger: Logger = load("res://utils/Logger.gd").new()
1118

1219
# Debounce
@@ -36,8 +43,10 @@ func _ready() -> void:
3643

3744
cm.setup()
3845
add_child(nm)
46+
add_child(lsm)
3947

4048
func _process(delta: float) -> void:
49+
# rtls.poll()
4150
if should_save:
4251
debounce_counter += delta
4352
if debounce_counter > DEBOUNCE_TIME:

0 commit comments

Comments
 (0)