SP Tutorial Animation Speaking character with Lip-Sync, blink, moods

Users who are viewing this thread

DtheHun

Sergeant Knight
Object
Mimic the phonation of the sounds and facial expressions of a recorded speech.

Demonstration


Skill requirements (topics not covered in this tutorial)
- loading brf file
- add new race
- spawn troop
- set up dialog or trigger event in scene

Talkress
A "race" with only 8 face keys based on a slightly changed vanilla head mesh, completed with teeth and tongue. The usual face trait frames can be added later.
You can download and use this head model. talkress.brf Set "talkress_head" mesh to be used by the race of the speaking troop.

Under the hood
The first eight shape keys (variations of the base head model) are scalable separately by code. Refreshing the head display in scenes can be achieved by helm equip&unequip, so it's done once per frame. The eight scalable shape IDs and their values are both in the [0-7] range, so I load them frame by frame into two arrays containing nuber lines where the values are separated by their decimal places.

lZ9IF1c.jpg

M, B, P sounds have no keys, they are formed with closed mouth

Code samples
Code:
  [anyone,"talkress_talk2", [], 
	"...From Me? Of course from Me! Ha-ha-hah!", "talkress_talk3", [
	#Lip-Sync
		(assign, "$lipsync_agent", "$g_talk_agent"),
	  #clear temp troop arrays - use A for ID, B for value
		(assign, "$talk_length", 250),
		(store_add, ":last_frame_plus_one", "$talk_length", 1),	
		(try_for_range, ":slot_no", 0, ":last_frame_plus_one"),
			(troop_set_slot, "trp_temp_array_a", ":slot_no", -1),
			(troop_set_slot, "trp_temp_array_b", ":slot_no", -1),
		(try_end),	
		(assign, "$talk_length", 0),
		
		(call_script, "script_set_keys_and_values", 1, 76543210, 3300000),	#XX
		
		(call_script, "script_set_keys_and_values", 2, 5, 5),		#H Sad/Happy5 (closed mouth move ~ WUT???)
		(call_script, "script_set_keys_and_values", 2, 5, 6),		#M Sad/Happy6
		(call_script, "script_set_keys_and_values", 2, 5, 7),		#M Sad/Happy7
		(call_script, "script_set_keys_and_values", 2, 5, 3),		#M??? Sad/Happy3
		
		(call_script, "script_set_keys_and_values", 2, 4, 7),		#F
		(call_script, "script_set_keys_and_values", 2, 24, 50), 	#R
		(call_script, "script_set_keys_and_values", 2, 32, 70), 	#O
		(call_script, "script_set_keys_and_values", 2, 3, 0), 		#M
		
		(call_script, "script_set_keys_and_values", 2, 63, 70), 	#M	Angry/Wonder7
		(call_script, "script_set_keys_and_values", 2, 2, 7), 		#I'?
		
		(call_script, "script_set_keys_and_values", 2, 7, 7), 		#- 	Blink7
		(call_script, "script_set_keys_and_values", 2, 7, 5), 		#- 	Blink5
		(call_script, "script_set_keys_and_values", 2, 72, 0), 		#-	Blink0
		
		(call_script, "script_set_keys_and_values", 18, 63, 45),	#O	Angry/Wonder4
		(call_script, "script_set_keys_and_values", 2, 543, 770), 	#F	Sad/Happy7
		
		(call_script, "script_set_keys_and_values", 2, 13, 53), 	#K
		(call_script, "script_set_keys_and_values", 2, 31, 50),		#O
		(call_script, "script_set_keys_and_values", 2, 23, 70),		#RZ
		
		(call_script, "script_set_keys_and_values", 4, 42, 70),		#F
		(call_script, "script_set_keys_and_values", 2, 24, 50), 	#R
		(call_script, "script_set_keys_and_values", 2, 32, 70), 	#O
		(call_script, "script_set_keys_and_values", 2, 3, 0), 		#M
		
		(call_script, "script_set_keys_and_values", 2, 3, 0), 		#M
		(call_script, "script_set_keys_and_values", 2, 2, 7), 		#I'
		
		(call_script, "script_set_keys_and_values", 6, 475, 774), 	#V	Blink7	Sad/Happy4 (start to smile)
		(call_script, "script_set_keys_and_values", 2, 4075, 725), 	#A'	Blink5	Sad/Happy5 (lower eyelids)
		(call_script, "script_set_keys_and_values", 2, 05, 66), 	#H			Sad/Happy6
		(call_script, "script_set_keys_and_values", 1, 50, 77), 	#A'			Sad/Happy7
		(call_script, "script_set_keys_and_values", 2, 0, 6), 		#H
		(call_script, "script_set_keys_and_values", 1, 0, 7), 		#A'
		(call_script, "script_set_keys_and_values", 2, 0, 6), 		#H
		(call_script, "script_set_keys_and_values", 2, 0, 7), 		#A'
		(call_script, "script_set_keys_and_values", 2, 0, 6), 		#H
		(call_script, "script_set_keys_and_values", 2, 70, 70), 	#A'	Blink0 				(open eyelids)
		
		(call_script, "script_set_keys_and_values", 2, 5, 6), 		#-			Sad/Happy6
		
		(call_script, "script_set_keys_and_values", 2, 76543210, 3500000), 	#XX	Sad/Happy5 - smile remains
	
		(assign, "$talk_frame", 1), #start lips
	#Sound
		(stop_all_sounds, 1),
		(play_sound,"snd_evl_from_me"),
	]],
Code:
 ("set_keys_and_values", [
	(store_script_param_1, ":step"),
	(store_script_param_2, ":keys"),
	(store_script_param, ":values", 3),
	 (assign, ":prev_frame", "$talk_length"), #for blending
	(val_add, "$talk_length", ":step"),
	(troop_set_slot, "trp_temp_array_a", "$talk_length",  ":keys"),
	(troop_set_slot, "trp_temp_array_b", "$talk_length",  ":values"),
	(try_begin), #BLEND MIDDLE FRAME
		(ge, "$talk_length", 3),
		(eq, ":step", 2),
		(store_add, ":blend_frame", ":prev_frame", 1),
		(troop_get_slot, ":prev_keys", "trp_temp_array_a", ":prev_frame"),
		(neq, ":prev_keys", -1),	
		(troop_get_slot, ":prev_values", "trp_temp_array_b", ":prev_frame"),
		(assign, ":mid_keys", 0),
		(assign, ":mid_values", 0),
		(assign, ":end", 8),
		(try_for_range, ":i", 0, ":end"),
			(store_mod, ":prev_key", ":prev_keys", 10),	#1305 ->   5, 3, 0, 1 (!do not start with 0 morph key)
			(val_div, ":prev_keys", 10),				#1305 -> 130,13, 1, 0<- end loop 
			(store_mod, ":prev_value", ":prev_values", 10),
			(val_div, ":prev_values", 10),
			(assign, ":endd", 8),
			(try_for_range, ":j", 0, ":endd"),
				(store_mod, ":key", ":keys", 10),	
				(val_div, ":keys", 10),
				(store_mod, ":value", ":values", 10),
				(val_div, ":values", 10),
				(try_begin),
					(eq, ":key", ":prev_key"), #->Blend them
					(val_add, ":value", ":prev_value"),
					(val_div, ":value", 2),
					(try_begin), #EXCEPTION (0,X) -/-> (0B,XY), BUT (B0,YX)
						(ge, ":mid_values", 0),
						(eq, ":mid_keys", 0),
						(val_mul, ":keys", 10),
						(val_add, ":mid_keys", ":key"),
						(val_mul, ":values", 10),
						(val_add, ":mid_values", ":value"),						
					(else_try),
						(val_mul, ":mid_keys", 10),
						(val_add, ":mid_keys", ":key"),
						(val_mul, ":mid_values", 10),
						(val_add, ":mid_values", ":value"),
					(try_end),
					(assign, ":endd", 0),
				(try_end),	
				(try_begin),
					(eq, ":keys", 0),
					(assign, ":endd", 0),
				(try_end),	
			(try_end),			
			(try_begin),
				(eq, ":prev_keys", 0),
				(assign, ":end", 0),
			(try_end),	
		(try_end),
		(neq, ":mid_values", 0), #":midkeys" can be 0 if just the 0. morph blends
		(troop_set_slot, "trp_temp_array_a", ":blend_frame",  ":mid_keys"),
		(troop_set_slot, "trp_temp_array_b", ":blend_frame",  ":mid_values"),		
	(try_end),
   ] 
  ),
Code:
face_morph = (
  0, 0, 0.04, # reactivate delay 25 frame per sec
  [
	(ge, "$lipsync_agent", 0),
	(try_begin),
		(ge, "$talk_frame", 1),
		(le, "$talk_frame", "$talk_length"),
		(troop_get_slot, ":key_code", "trp_temp_array_a", "$talk_frame"), #example: 135
		(try_begin),
			(ge, ":key_code", 0), # NOT -1 (0 -> 777777`)
			(troop_get_slot, ":value_code", "trp_temp_array_b", "$talk_frame"),
			#(assign, reg0, ":key_code"),
			#(display_message, "@K:{reg0}"),
			#(assign, reg0, ":value_code"),
			#(display_message, "@V:{reg0}"),
			(agent_get_troop_id, ":lipsync_troop", "$lipsync_agent"), #<- from dialogue
			(assign, ":end", 8),
			(try_for_range, ":i", 0, ":end"),
				(try_begin),
					(store_mod, ":key", ":key_code", 10),	#1305 ->   5, 3, 0, 1 (!do not start with 0 morph key)
					(val_div, ":key_code", 10),				#1305 -> 130,13, 1, 0<- end loop 
					(store_mod, ":value", ":value_code", 10),
					(val_div, ":value_code", 10),
					(str_clear, s0),
					(str_store_troop_face_keys, s0, ":lipsync_troop"),
					(face_keys_set_morph_key, s0, ":key", ":value"),		
					(troop_set_face_keys, ":lipsync_troop", s0),
					#(assign, reg0, "$talk_frame"),
					#(assign, reg1, ":key"),
					#(assign, reg2, ":value"),
					#(display_message, "@{reg0} : {reg1} = {reg2}"),
					(try_begin),
						(eq, ":key_code", 0),
						(assign, ":end", 0),
					(try_end),
				(try_end),	
			(try_end),	
			(try_begin),	#EXCEPTION : has helm on
                (troop_get_inventory_slot, ":item_no", ":lipsync_troop", ek_head),
				(ge, ":item_no", 0),
				(agent_has_item_equipped, "$lipsync_agent", ":item_no"),
				(agent_unequip_item, "$lipsync_agent", ":item_no"),
				(agent_equip_item, "$lipsync_agent", ":item_no"),
			(else_try),
				(agent_equip_item, "$lipsync_agent", "itm_full_helm"),
				(agent_unequip_item, "$lipsync_agent", "itm_full_helm"),
			(try_end),
		(try_end),
		(val_add, "$talk_frame", 1), #increment
	(else_try),
		(ge, "$talk_frame", 1),
		(assign, "$talk_frame", 0), # Reset
		(assign, "$talk_length", 0),
		(assign, "$lipsync_agent", -1),
	(try_end),
  ],
  []
)
Code:
talkress_face_keys = [
(10,0,0.0,0.7, "lip 0"),
(20,0,0.0,0.7, "lip 1"), 
(30,0,0.0,0.7, "lip 2"),
(40,0,0.0,0.7, "lip 3"), 
(50,0,0.1,0.7, "lip 4"),
(60,0,-1.0,1.0, "Happy/Sad"), 
(70,0,-0.3,0.7, "Angry/Wonder"),
(80,0,0.0,1.0, "Blink"),
(90,0,0.0,1.0, "N/A"),
(100,0,0.0,1.0, "N/A"), 
(110,0,0.0,1.0, "N/A"),
(120,0,0.0,1.0, "N/A"), 
(130,0,0.0,1.0, "N/A"),
(140,0,0.0,1.0, "N/A"), 
(150,0,0.0,1.0, "N/A"),
(160,0,0.0,1.0, "N/A"), 
(170,0,0.0,1.0, "N/A"),
(180,0,0.0,1.0, "N/A"),
(190,0,0.0,1.0, "N/A"),
(200,0,0.0,1.0, "N/A"), 
(210,0,0.0,1.0, "N/A"),
(220,0,0.0,1.0, "N/A"), 
(230,0,0.0,1.0, "N/A"),
(240,0,0.0,1.0, "N/A"), 
(250,0,0.0,1.0, "N/A"),
(260,0,0.0,1.0, "N/A"), 
(270,0,0.0,1.0, "N/A"),
(280,0,0.0,1.0, "Post-Edit"),
]
.
.
.
skins = [
	.
	.
	.
  (
    "talkress", skf_use_morph_key_10,
    "woman_body",  "woman_calf_l", "f_handL",
    "talkress_head", talkress_face_keys,
    ["woman_hair_p","woman_hair_n"
		.
		.
		.
]

Loading speech sounds
These two lines (module_dialogues.py) add "F" and "R" sound to the playlist.

(call_script, "script_set_keys_and_values", 2, 4, 7), #F
(call_script, "script_set_keys_and_values", 2, 24, 50), #R

2: DELAY - 2 frame steps from the previous sound +(2*40ms or 2*25[1/s])
2: ID#2 (F,V)
4: ID#1 (E,R,K,G,H)
5: value#2 - 5 in [0-7] range: R sound is less "open" than E, a little gap between the teeth is enough (use a mirror)
0: value#1 - 0 in [0-7] range: switches off the previously used shape for F

The second line contains a pair of IDs and values. It zeroes out the previous "F" sound (ID=4, value=0) to shape the mouth to an "R" sound (ID=2, value=5) in 2 frame later.
I use 2 frame steps frequently. That's usually a good tempo, and my code calculates average shapes for the in-between frames to smooth out the animation. When the tempo must be changed (other than 2 frame steps needed) to be in synchron with the audio, you have to make the changes gradually to keep the animation smooth.

IDs and values are construed from right to left (<-), if there are less values than IDs, then the value of the undefined ID will be 0.
IMPORTANT to do NOT start these number lines with 0: You can change the order of IDs (don't forget to change the values too), and skip the starting zeros of values.
For example, the first call - which resets the default morph values -
Code:
(call_script, "script_set_keys_and_values", 1, 76543210, 3300000),	#XX
specifies all the 8 IDs, but gives them only 7 values.
Therefore the value of the leftmost id (7) will be set to 0. (Blink=0 -> start dialogue with eyes opened)

You can find more working code samples in WarDrobe ModuleSystem.

Corrections and suggestions are welcome.

Edit:
module_skins.py code sample added
list of sounds order corrected
note inserted about M,B,P sounds
 
I love it! Looks really good =)

About how long does it take to set up each additional line of dialogue? (once all of the prep work has been done)
 
Mandible said:
I love it! Looks really good =)

About how long does it take to set up each additional line of dialogue? (once all of the prep work has been done)

Thanks! It's good to have some free hours for start, then it takes lesser and lesser with practice. It's not a five minit process because of regular ingame check of timing.
 
Thank you very much
It must be reserved only for the very important dialogues with key characters of a mod.
(I love to see the reaction of the player when NPCs start winking  :shifty: )
 
Interesting setup you've got here. I would be very interested in seeing what people could do with this tutorial.

Unfortunately for me, I'm still very new to using the module system and actually making mods so I won't be able to do anything with this tutorial (yet) as I can't really understand some parts of it.
Total noob question: How are voices handled? I'm sorry if you already explained it in your tutorial if you did I missed it or didn't understand it.
 
Leonion said:
Mother of god. :shock:
This has to be some sort of sorcery.

Absolutely brilliant. :eek:
And know it's framed.  :grin:
Thanks! It's really nothing compared to your PoP-art.
Lord Engineer said:
How are voices handled?
In the last section of module_dialogs.py "play_sound" starts to play your sound file.
Code:
                
                ...
		(assign, "$talk_frame", 1), #start lips
	#Sound
		(stop_all_sounds, 1),
		(play_sound,"snd_evl_from_me"),
	]],
"snd_evl_from_me" is a reference of a sound file in module_sounds.py
Code:
 ("evl_from_me",sf_2d|sf_vol_8, ["evl_from_me.ogg"]),
If there is a real demand for it, I can prepare a ModuleSystem with a pre-set test NPC, maybe it could push on voice acting for Warband.
 
Oh sorry. I remember an impressive merchant inventory expansion from you, but not which mod it was for. Maybe my memory tricks me again.
 
Terco_Viejo said:
Okay lol, so tell me what kind of witchcraft is this?
Thanks! Just exploiting the face operations came with v1.61. Looks like it's a plan&forget feature for a DLC. Anyway, they left that door open for me.  :smile:
 
A really cool magic you made here, my friend.

A question though: can this be used to portray a conversation-like between npcs in taverns, shops, town guards.....etc. where you enter a tavern for example you hear voices of chatters and when you look around you see the npcs as if talking with expressions?

Thanks my friend.
 
Algerian.Sultan said:
A really cool magic you made here, my friend.

A question though: can this be used to portray a conversation-like between npcs in taverns, shops, town guards.....etc. where you enter a tavern for example you hear voices of chatters and when you look around you see the npcs as if talking with expressions?

Thanks my friend.

Thanks. Actually you can set multiple charaters in a scene to have a custom head model, even the player - with the animation keys required for speaking - and change the actively speaking character, setting one at a time. So, the code in this form is not ready for paralell talking, but it can be improved that way to make something like a bazaar scene.
 
DtheHun said:
Thanks. Actually you can set multiple charaters in a scene to have a custom head model, even the player - with the animation keys required for speaking - and change the actively speaking character, setting one at a time. So, the code in this form is not ready for paralell talking, but it can be improved that way to make something like a bazaar scene.

I see, glad to hear that.
a question, will it effect performance if i tried this in town walk scene, with tens of npcs?

To explain better, what i'm trying to do is remove different scenes and only leave 2 or maybe 3 scenes, so while you walk without leaving the scene you can enter the tavern or the shop....etc.
 
Back
Top Bottom