Skip to content

TryllLipSync

Type: MonoBehaviour (sealed)
Namespace: Tryll.Client
Source: Runtime/TryllLipSync.cs
Menu: Add Component → Tryll → Tryll Lip Sync

Plays an agent's viseme track on a face with ARKit blendshapes, in step with the audio played by a TryllSpeaker. The agent's voice profile must have Emit Visemes on — see Add Lip Sync to a Character.


Requirements

  • [RequireComponent(typeof(TryllSpeaker))] — the speaker on the same GameObject is the playback clock. Assign it to the TryllAgentComponent's Speaker field so the agent's audio plays through it.
  • A TryllAgentComponent — the source of the viseme track. A warning is logged in Awake if none is assigned or found on the GameObject.
  • A TryllVoiceProfile on that component with Emit Visemes on. Without it no viseme track arrives: the Inspector shows a warning, and one is logged when the agent is bound.
  • Face meshes whose blendshapes use ARKit names.

Inspector fields

Source

Field Type Default Description
_agent TryllAgentComponent same GameObject Agent whose viseme track is played. Re-binds automatically when the agent is re-created.
_speaker TryllSpeaker same GameObject Speaker whose playback position the face follows.

Face

Field Type Default Description
_renderers List<SkinnedMeshRenderer> empty Meshes to drive. Bound at Start when set.
_faceRoot Transform none Parent of the face meshes. When Renderers is empty, every SkinnedMeshRenderer under it is bound at Start.

Set one of the two, or call BindFace at runtime; with neither, no mesh is driven.

Only blendshapes named by the Map are bound and written; every other blendshape on those meshes is left untouched for other animation to drive. MappedShapes counts the bound ones.

Clock

Field Type Default Description
_smoothPlaybackClock bool true Smooths the speaker's playback position into a steady clock.
_clockWindowSeconds float 1.5 Window of the clock smoother.
_outputLeadTrimMs float 342 How far the streaming clip's buffer runs ahead of what is audible. Device-dependent; adjust when a large sync offset is needed.
_compensateOutputLatency bool true Subtracts Unity's DSP buffer latency.
_syncOffsetMs float 0 Manual sync correction. Positive moves the face earlier.

Envelope

Field Type Default Description
_attackDecayMs float 50 Cross-fade between consecutive shapes.
_intensity float (0–1.5) 0.4 Scale on every pose. 0 = no movement, 1 = the full pose table.
_smoothingPerSecond float (0–120) 16 Rate at which the face moves toward each shape. 0 disables smoothing.
_closureSmoothingPerSecond float (0–200) 60 Rate used while a lip closure (PP, FF) is active.
_maxHoldMs float 400 Returns to rest when the last shape of a turn has been held this long (cancelled turns). 0 disables.
_compensateSmoothingLag bool true Samples the track ahead by the delay the smoothing adds (1000 / smoothing + attack / 2 ms), so tuning smoothing does not shift sync.

Mapping

Field Type Default Description
_map List<TryllVisemePose> built-in table Pose per viseme, as ARKit channel weights. Left empty, it is filled with the built-in table at Awake.

Diagnostics and apply

Field Type Default Description
_logFrameArrival bool false Logs how far ahead of playback each viseme frame arrived.
_applyToMesh bool true When off, the pose is evaluated but not written to the meshes.

Public members

Member Type Description
Intensity float (get/set) Same as the Intensity field.
SmoothingPerSecond float (get/set) Same as the Smoothing Per Second field.
AttackDecayMs float (get/set) Same as the Attack Decay Ms field.
ApplyToMesh bool (get/set) Same as the Apply To Mesh field.
BindFace(Transform faceRoot) void Re-binds to the meshes under faceRoot — call it after spawning or swapping the face at runtime.
FaceBound bool True once at least one blendshape is bound.
MappedShapes int Number of bound blendshapes.
ActiveViseme WireViseme Viseme being shown this frame.
AppliedJawOpen float jawOpen value written this frame (0–1), after Intensity and smoothing; 0 if the mesh has no jawOpen. With Apply To Mesh off, the raw pose value before Intensity and smoothing.
SmoothingLagMs float Look-ahead currently applied for the smoothing delay.
OutputLatencyMs double DSP buffer latency being subtracted.
StatusLine string One-line state for debug UI.
BuildDefaultOculusArkitMap() static List<TryllVisemePose> The built-in pose table.
VisemeCount const int 15 — the number of visemes in the table.
VisemeSetupProblem(TryllAgentComponent) static string Why the agent will send no viseme track (no voice profile, or Emit Visemes off), or null when it is set up.

Built-in pose table

The 15 visemes and the ARKit channels each one drives (before Intensity and the event weight are applied):

Viseme Sounds (as in) Channels
sil rest, pauses —
PP pen, bat, me mouthClose .55, mouthPressLeft/Right .45, mouthPucker .15
FF fan, van jawOpen .12, mouthLowerDownLeft/Right .55, mouthFunnel .15
TH thin, that jawOpen .18, mouthStretchLeft/Right .25, mouthUpperUpLeft/Right .20
DD top, dog jawOpen .22, mouthClose .15
kk key, go jawOpen .32
CH chin, jam, she, measure jawOpen .18, mouthFunnel .45, mouthPucker .20
SS sun, zoo jawOpen .08, mouthStretchLeft/Right .55
nn no, low, sing jawOpen .14, mouthClose .20
RR red jawOpen .16, mouthPucker .35, mouthFunnel .15
aa father, cat, cup, about, my jawOpen .72
E bed, say, her jawOpen .38, mouthStretchLeft/Right .45
ih sit, see, yes jawOpen .28, mouthStretchLeft/Right .28
oh law, go, boy jawOpen .48, mouthFunnel .55, mouthPucker .25
ou you, book, how, we jawOpen .28, mouthPucker .72, mouthFunnel .35

Type Description
TryllVisemeEvent readonly struct: SampleOffset (uint, samples from the start of the turn's audio — its format announcement — across every speaking node of the turn), Viseme (WireViseme), Weight (float, 0–1).
TryllAgent.OnTtsVisemes event Action<string, TryllVisemeEvent[]> — fired on the main thread for each viseme frame (one per synthesized sentence). The array belongs to the invocation; copy it to keep it.
WireViseme enum : byte in namespace Tryll (add using Tryll;): sil, PP, FF, TH, DD, kk, CH, SS, nn, RR, aa, E, ih, oh, ou.
TryllVisemePose Serializable: Viseme + Channels (TryllVisemeChannelWeight[]).
TryllVisemeChannelWeight Serializable: Channel (ARKit blendshape name) + Weight.

Lifecycle and threading

  • Awake fills the default map and builds the pose table; Start binds the meshes; OnEnable subscribes to the agent's viseme events.
  • Each LateUpdate evaluates the pose at the current playback position and writes it to the meshes. Everything runs on the main thread.
  • Each audio format announcement (one per agent turn) clears the previous track.
  • OnDisable unsubscribes and resets the clock and smoothing.

See also