### Install Speech SDK Source: https://developer.openhuman.ai/tts-lipsync-integration Install the Microsoft Cognitive Services Speech SDK for Node.js. ```bash npm install microsoft-cognitiveservices-speech-sdk ``` -------------------------------- ### Example Animation Clip Structure Source: https://developer.openhuman.ai/loading-custom-characters Illustrates the expected directory structure for supplying animation clips. Each .glb file should contain a single animation track. ```bash animations/ ├── idle.glb ├── talk_neutral.glb ├── talk_happy.glb ├── gesture_wave.glb └── blink.glb ``` -------------------------------- ### Install OpenHuman SDK Source: https://developer.openhuman.ai/ Install the OpenHuman SDK package using npm. This package includes all necessary components for the engine. ```bash npm install @openhuman/sdk ``` -------------------------------- ### Full Example: Azure TTS End-to-End Lipsync Source: https://developer.openhuman.ai/tts-lipsync-integration This snippet shows the complete setup for Azure TTS and OpenHuman SDK integration. It initializes the character, configures Azure TTS, and provides a 'speak' function that handles audio playback and viseme scheduling for lip-sync. ```typescript import * as sdk from "microsoft-cognitiveservices-speech-sdk" import { OpenHuman } from "@openhuman/sdk" async function setup() { // 1. Init character const human = new OpenHuman({ canvas: document.getElementById("canvas") }) await human.loadCharacter("./character.ohb") human.setParam("isTalking", false) human.setBlink({ enabled: true }) // 2. Azure TTS setup const speechConfig = sdk.SpeechConfig.fromSubscription(KEY, REGION) speechConfig.speechSynthesisVoiceName = "en-US-JennyNeural" const synthesizer = new sdk.SpeechSynthesizer(speechConfig, null) // 3. Speak function async function speak(text) { return new Promise((resolve, reject) => { const visemes = [] const audioCtx = new AudioContext() synthesizer.visemeReceived = (s, e) => { visemes.push({ audioOffsetMs: e.audioOffset / 10000, visemeId: e.visemeId, }) } synthesizer.speakTextAsync( text, async (result) => { human.setParam("isTalking", true) const buffer = await audioCtx.decodeAudioData(result.audioData) const source = audioCtx.createBufferSource() source.buffer = buffer source.connect(audioCtx.destination) const startTime = audioCtx.currentTime + 0.1 // 100ms lead time visemes.forEach(({ audioOffsetMs, visemeId }) => { human.scheduleViseme({ visemeId, triggerAt: startTime + audioOffsetMs / 1000, audioContext: audioCtx, }) }) source.start(startTime) source.onended = () => { human.setParam("isTalking", false) resolve() } }, reject ) }) } return { human, speak } } // Usage const { human, speak } = await setup() await speak("Hello! I am Sarah, your AI assistant.") await speak("How can I help you today?") ``` -------------------------------- ### Create and Play a Simple Animation Graph Source: https://developer.openhuman.ai/animation-graph This snippet demonstrates the basic setup of an Animation Graph with two states and parameter-driven transitions. It shows how to add states, define transitions, attach the graph to a character, and trigger transitions at runtime. ```javascript import { OpenHuman, AnimationGraph } from "@openhuman/sdk" const human = await OpenHuman.load("character.ohb", canvas) // 1. Create the graph const graph = new AnimationGraph() // 2. Add states graph.addState("idle", human.animation.getClip("idle")) graph.addState("talk", human.animation.getClip("talk_neutral")) // 3. Add transitions graph.addTransition("idle", "talk", { bool: "isTalking", value: true, duration: 0.3 }) graph.addTransition("talk", "idle", { bool: "isTalking", value: false, duration: 0.5 }) // 4. Attach to the character and start human.animation.setGraph(graph) human.animation.play("idle") // 5. Trigger transitions at runtime document.getElementById("btn-talk").addEventListener("click", () => { graph.setBool("isTalking", true) }) ``` -------------------------------- ### Complete Expressive Conversational Avatar Example Source: https://developer.openhuman.ai/facial-blendshapes A comprehensive example demonstrating autonomous blinking via an Animation Graph, emotion state management using morph presets, phoneme-driven lip sync, and reactive micro-expressions. This snippet shows the integration of various facial animation techniques. ```javascript import { OpenHuman, VisemeMapper } from "@openhuman/sdk" const human = await OpenHuman.load("character.ohb", canvas) const mapper = new VisemeMapper() // ── Autonomous blink (via Animation Graph layer) ────────────── human.animation.setLayer("blink", human.animation.getClip("blink_idle"), { additive: true, mask: "facial", loop: true, }) // ── Emotion state ───────────────────────────────────────────── let currentEmotion = "neutral" async function setEmotion(name, intensity = 1.0) { currentEmotion = name await human.morph.applyPreset(name, intensity, { fadeDuration: 0.35 }) } // ── Lip sync (phoneme-driven) ───────────────────────────────── let lipSyncActive = false function startSpeech(emotion = "happy", intensity = 0.6) { lipSyncActive = true setEmotion(emotion, intensity) } function onPhoneme(phoneme, intensity) { if (!lipSyncActive) return const weights = mapper.toFACS(phoneme, intensity) human.morph.setMany(weights) } function stopSpeech() { lipSyncActive = false // Fade mouth back to emotion preset, keep brows / cheeks human.morph.animateTo({ jawOpen: 0, mouthFunnel: 0, mouthPucker: 0, tongueOut: 0 }, { duration: 0.15 }) } // ── Reactive micro-expressions ──────────────────────────────── function raiseBrow(side = "both", amount = 0.4) { if (side === "left" || side === "both") human.morph.set("browOuterUpLeft", amount) if (side === "right" || side === "both") human.morph.set("browOuterUpRight", amount) setTimeout(() => { human.morph.animateTo({ browOuterUpLeft: 0, browOuterUpRight: 0 }, { duration: 0.4 }) }, 600) } // ── Usage ───────────────────────────────────────────────────── await setEmotion("neutral") // Simulate a TTS speech event startSpeech("happy", 0.7) onPhoneme("HH", 0.5) onPhoneme("EH", 0.9) onPhoneme("L", 0.8) onPhoneme("OW", 0.7) stopSpeech() // React to a user statement raiseBrow("left", 0.5) ``` -------------------------------- ### HTML Canvas Setup Source: https://developer.openhuman.ai/ Add a canvas element to your HTML file where the OpenHuman engine will render. This example includes basic styling for the canvas. ```html OpenHuman Demo ``` -------------------------------- ### Complete Conversational Avatar Animation Graph Source: https://developer.openhuman.ai/animation-graph This example demonstrates setting up a full animation graph for a conversational AI avatar, including idle, talking, gestures, and additive layers for blinking and gazing. It shows how to define states, transitions, and control them at runtime. ```javascript import { OpenHuman, AnimationGraph, BlendTree1D, BlendTree2D } from "@openhuman/sdk" const human = await OpenHuman.load("character.ohb", canvas) const graph = new AnimationGraph() // ── Base layer ──────────────────────────────────────────────── // Idle state graph.addState("idle", human.animation.getClip("idle"), { loop: true }) // Talk state - 1D blend across emotion const talkTree = new BlendTree1D("emotionHappy") talkTree.addClip(0.0, human.animation.getClip("talk_neutral")) talkTree.addClip(1.0, human.animation.getClip("talk_happy")) graph.addState("talk", talkTree, { loop: true }) // Gesture states (one-shot) graph.addState("wave", human.animation.getClip("gesture_wave"), { loop: false, exitTime: 2.1 }) graph.addState("nod", human.animation.getClip("gesture_nod"), { loop: false, exitTime: 1.2 }) // ── Transitions ──────────────────────────────────────────────── graph.addTransition("idle", "talk", { bool: "isTalking", value: true, duration: 0.3 }) graph.addTransition("talk", "idle", { bool: "isTalking", value: false, duration: 0.5 }) graph.addTransition("idle", "wave", { trigger: "doWave", duration: 0.2 }) graph.addTransition("talk", "nod", { trigger: "doNod", duration: 0.15 }) graph.addTransition("wave", "idle", { onEnd: true, duration: 0.3 }) graph.addTransition("nod", "talk", { onEnd: true, duration: 0.2 }) // ── Additive layers ──────────────────────────────────────────── // Blink - runs continuously, independent of body state human.animation.setLayer("blink", human.animation.getClip("blink_idle"), { additive: true, mask: "facial", loop: true, }) // Gaze - 2D blend tree driven by lookX / lookY const gazeTree = new BlendTree2D("lookX", "lookY") gazeTree.addClip([0, 0], human.animation.getClip("look_center")) gazeTree.addClip([-1, 0], human.animation.getClip("look_left")) gazeTree.addClip([1, 0], human.animation.getClip("look_right")) gazeTree.addClip([0, 1], human.animation.getClip("look_up")) gazeTree.addClip([0, -1], human.animation.getClip("look_down")) human.animation.setLayer("gaze", gazeTree, { additive: true, mask: "facial", weight: 0.6, }) // ── Start ────────────────────────────────────────────────────── human.animation.setGraph(graph) human.animation.play("idle") // ── Runtime control ──────────────────────────────────────────── // When AI starts speaking function onSpeechStart(emotion = 0.5) { graph.setBool("isTalking", true) graph.setFloat("emotionHappy", emotion) } // When AI stops speaking function onSpeechEnd() { graph.setBool("isTalking", false) } // Trigger a gesture mid-conversation function triggerNod() { graph.trigger("doNod") } function triggerWave() { graph.trigger("doWave") } // Animate gaze toward mouse position (normalised -1..1) canvas.addEventListener("mousemove", (e) => { const x = (e.clientX / canvas.clientWidth - 0.5) * 2 const y = -(e.clientY / canvas.clientHeight - 0.5) * 2 graph.setFloat("lookX", x * 0.6) graph.setFloat("lookY", y * 0.4) }) ``` -------------------------------- ### Additive Blending Example Source: https://developer.openhuman.ai/facial-blendshapes Demonstrates how to use additive blending for animations driven by the Animation Graph, allowing independent control of morph targets via the `human.morph` API. ```APIDOC ## Blending with the Animation Graph Morph weights set via `human.morph` compose **additively** with weights driven by the Animation Graph's facial layer. The final weight for each target is calculated as: ``` finalWeight = clamp(graphWeight + morphAPIWeight, 0.0, 1.0) ``` This allows for simultaneous control, such as procedural blinks from the graph and lip sync from the morph API. ### Example Usage ```javascript // Graph drives blink loop autonomously human.animation.setLayer("blink", blinkClip, { additive: true, mask: "facial", loop: true, }) // Morph API drives lip sync independently function onFacsFrame(weights) { human.morph.setFromArray(weights) // blink weights from graph still apply on top } // To exclude specific targets from the facial layer mask: // graph.excludeFromMask('facial', ['jawOpen']) ``` ``` -------------------------------- ### Basic iframe Setup Source: https://developer.openhuman.ai/embed-api-reference Embed the OpenHuman character component in an iframe. Configure the source URL with the desired character asset and quality settings. ```html ``` -------------------------------- ### Get a Reference to the Open Human Element Source: https://developer.openhuman.ai/embed-api-reference Select the element and wait for its engine to be fully initialized before interacting with it. ```javascript const el = document.querySelector("open-human") // Wait for the engine to be fully initialised await el.ready // Promise - resolves when character is loaded and first frame is rendered ``` -------------------------------- ### Additive Animation Layer Example Source: https://developer.openhuman.ai/facial-blendshapes Demonstrates how to run a procedural animation (like a blink) via an Animation Graph layer while simultaneously driving lip sync using the morph API. The graph drives the blink autonomously, and the morph API drives lip sync independently. ```javascript human.animation.setLayer("blink", blinkClip, { additive: true, mask: "facial", loop: true, }) function onFacsFrame(weights) { human.morph.setFromArray(weights) // blink weights from graph still apply on top } ``` -------------------------------- ### AI TTS Avatar Integration Example Source: https://developer.openhuman.ai/streaming-protocol Connects an AI speech backend to a live avatar for real-time lip-sync and animation. Requires loading a character, initializing a StreamingClient, and wiring UI events to trigger speech synthesis. ```javascript import { OpenHuman, StreamingClient } from "@openhuman/sdk" // 1. Load character const human = await OpenHuman.load("character.ohb", canvas) human.animation.play("idle") // 2. Create lip sync streaming client const lipsync = new StreamingClient({ transport: "websocket", url: "wss://tts-backend.example.com/lipsync", mode: "facs", jitterBuffer: 80, smoothing: 0.7, reconnect: true, }) lipsync.attach(human) // 3. Wire UI → TTS → stream const input = document.getElementById("user-input") const btn = document.getElementById("send-btn") btn.addEventListener("click", async () => { const text = input.value.trim() if (!text) return // Transition to talking state human.animation.crossFadeTo("talk", 0.3) human.morph.applyPreset("neutral") // POST to TTS backend - it begins streaming FACS over WebSocket await fetch("https://tts-backend.example.com/speak", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text }), }) }) // 4. Return to idle when speech ends lipsync.on("speechEnd", () => { human.animation.crossFadeTo("idle", 0.5) human.morph.animateTo({}, { duration: 0.3 }) }) // 5. Diagnostics lipsync.on("bufferUnderrun", () => { console.warn("Lip sync buffer underrun - consider increasing jitterBuffer") }) setInterval(() => { const s = lipsync.getStats() console.log(`Buffer: ${s.bufferDepth}ms | Latency: ${s.estimatedLatency}ms | FPS: ${s.fps}`) }, 2000) await lipsync.connect() ``` -------------------------------- ### Initialize OpenHuman Engine Source: https://developer.openhuman.ai/ Initialize the OpenHuman engine in your main JavaScript file, providing the canvas element and desired quality settings. Load a character bundle and start the render loop. ```javascript import { OpenHuman } from "@openhuman/sdk" const canvas = document.getElementById("oh-canvas") const human = new OpenHuman({ canvas, quality: "high", // 'high' | 'medium' | 'low' fps: 60, }) // Load a digital human character bundle (.ohb) await human.loadCharacter("./characters/default.ohb") // Start the render loop human.play("idle") ``` -------------------------------- ### Handle GPU Out of Memory Errors Source: https://developer.openhuman.ai/error-code-reference This example shows how to handle `ASSET_GPU_OOM` errors by destroying a lower-priority character instance and retrying the load. It utilizes the `human.on('error', ...)` event listener. ```typescript human.on("error", async (err) => { if (err.code === "ASSET_GPU_OOM") { // Destroy lowest-priority character and retry await lowPriorityHuman.destroy() await human.loadCharacter("./character.ohb") } }) ``` -------------------------------- ### NDJSON Animation Frame Example Source: https://developer.openhuman.ai/streaming-protocol Example of newline-delimited JSON (NDJSON) format for animation frames. Each chunk is a UTF-8 JSON object terminated by a newline. ```json {"t":1.033,"facs":[0,0,0,0,0,0,0,0,0.41,0,0,0,0,0,0,0,0,0,0,0,0,0,0.61,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]} {"t":1.066,"facs":[0,0,0,0,0,0,0,0,0.55,0,0,0,0,0,0,0,0,0,0,0,0,0,0.71,0,0.3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]} ``` -------------------------------- ### Configure 3-Point Lighting Source: https://developer.openhuman.ai/render-pipeline Sets up a fixed 3-point lighting model (key, fill, rim) using SDK configuration. Each light contributes diffuse and specular components. ```javascript human.setLighting({ key: { direction: [-1, -1, -1], color: "#fff5e6", intensity: 1.0 }, fill: { direction: [1, -0.5, -1], color: "#e6f0ff", intensity: 0.4 }, rim: { direction: [0, 0, 1], color: "#ffffff", intensity: 0.6 }, }) ``` -------------------------------- ### Configure One-Shot Gestures with Auto-Return Source: https://developer.openhuman.ai/animation-graph Shows how to set up one-shot gestures like 'wave' that automatically transition back to a previous state upon completion. This is achieved by setting `loop: false` and `exitTime` to the clip's duration. ```javascript // One-shot gesture that auto-returns to idle graph.addState("wave", human.animation.getClip("gesture_wave"), { loop: false, exitTime: 2.1, // clip is 2.1s long }) graph.addTransition("idle", "wave", { trigger: "doWave", duration: 0.2 }) graph.addTransition("wave", "idle", { onEnd: true, duration: 0.3 }) ``` -------------------------------- ### Configure Loading and Environment Attributes Source: https://developer.openhuman.ai/embed-api-reference Set the poster image, loading behavior, background color, and environment preset for the component. ```html ``` ```html ``` -------------------------------- ### Configure Real-time Streaming Source: https://developer.openhuman.ai/embed-api-reference Set up the WebSocket or HTTP stream URL for real-time lip-sync or FACS data. Adjust smoothing and buffer settings for optimal performance. ```html ``` -------------------------------- ### Set Eyes for Looking Left Source: https://developer.openhuman.ai/facial-blendshapes Adjusts eye look blendshapes to simulate the character looking to the left. This example requires the 'human' object to be loaded. ```javascript // Look left human.morph.setMany({ eyeLookOutLeft: 0.6, eyeLookInRight: 0.6, }) ``` -------------------------------- ### Basic Streaming Client Configuration Source: https://developer.openhuman.ai/streaming-protocol Configure the StreamingClient with transport, URL, and buffer settings. Adjust jitterBuffer for network latency and maxBuffer to prevent memory growth. ```javascript const client = new StreamingClient({ transport: "websocket", url: "wss://...", jitterBuffer: 80, // target buffer depth in ms (default: 80) maxBuffer: 300, // drop frames older than this ms (default: 300) extrapolate: true, // predict pose on buffer underrun (default: true) }) ``` -------------------------------- ### Initialize OpenHuman SDK Source: https://developer.openhuman.ai/ Instantiate the OpenHuman SDK with various configuration options. Ensure a valid HTMLCanvasElement is provided. ```javascript new OpenHuman({ canvas, quality, fps, shadows, postProcess, sss, }) ``` -------------------------------- ### Initialize OpenHuman and Speech Synthesizer Source: https://developer.openhuman.ai/tts-lipsync-integration Initialize the OpenHuman SDK and configure the Azure Speech Synthesizer with your subscription key and region. ```typescript import * as sdk from "microsoft-cognitiveservices-speech-sdk" import { OpenHuman } from "@openhuman/sdk" const human = new OpenHuman({ canvas }) await human.loadCharacter("./character.ohb") const speechConfig = sdk.SpeechConfig.fromSubscription("YOUR_AZURE_KEY", "YOUR_AZURE_REGION") speechConfig.speechSynthesisVoiceName = "en-US-JennyNeural" const synthesizer = new sdk.SpeechSynthesizer(speechConfig) ``` -------------------------------- ### Embed OpenHuman as a Web Component Source: https://developer.openhuman.ai/ Embed the OpenHuman engine into your HTML using the `` custom element for a zero-configuration setup. This component supports various attributes for customization. ```html ``` -------------------------------- ### Listen to AnimationGraph Events Source: https://developer.openhuman.ai/animation-graph Attach event listeners to the `human` instance to react to animation graph lifecycle events such as state changes, transition starts, and animation clip endings. ```javascript human.on("stateEnter", ({ state }) => console.log("Entered state:", state)) human.on("stateExit", ({ state }) => console.log("Exited state:", state)) human.on("animationEnd", ({ clip }) => console.log("Clip ended:", clip)) human.on("transitionStart", ({ from, to, duration }) => { console.log(`Crossfading ${from} → ${to} over ${duration}s`) }) ``` -------------------------------- ### Configure Playback and Animation Source: https://developer.openhuman.ai/embed-api-reference Set the default animation, enable/disable autoplay and looping, and adjust playback speed. ```html ``` -------------------------------- ### ParrotSpeech IPA Phoneme Timestamps Integration Source: https://developer.openhuman.ai/tts-lipsync-integration Integrate ParrotSpeech to obtain native IPA phoneme timestamps for high-accuracy lip sync. Requires ParrotSpeech API key and client setup. ```typescript import { ParrotSpeechClient } from "@parrotspeech/client" const parrot = new ParrotSpeechClient({ apiKey: "YOUR_KEY" }) const result = await parrot.synthesize({ text: "Hello, how can I help you today?", voice: "nova_en_f", format: "wav", phonemes: true, // request IPA phoneme timestamps }) // result.phonemes: [{ ipa: 'h', startMs: 0, endMs: 60 }, ...] // result.audioBuffer: ArrayBuffer result.phonemes.forEach(({ ipa, startMs, endMs }) => { human.scheduleVisemeByIPA({ ipa, startMs, endMs, audioContext: ctx, audioStartTime: ctx.currentTime, }) }) playAudio(ctx, result.audioBuffer) ``` -------------------------------- ### Add Animation States with Options Source: https://developer.openhuman.ai/animation-graph Demonstrates how to register animation clips as states, including options for looping, playback speed, and start/exit times. Useful for configuring different animation behaviors. ```javascript graph.addState("idle", human.animation.getClip("idle")) graph.addState("talk", human.animation.getClip("talk_neutral")) graph.addState("wave", human.animation.getClip("gesture_wave")) graph.addState("blink", human.animation.getClip("blink"), { loop: false }) graph.addState("lookLeft", human.animation.getClip("look_left"), { loop: true, speed: 0.8 }) ``` -------------------------------- ### VisemeScheduler API: scheduleVisemeByIPA Source: https://developer.openhuman.ai/tts-lipsync-integration Schedule a viseme event using an IPA phoneme string. This is the most accurate method when working with IPA data from TTS services. Requires an AudioContext and audio start time. ```javascript human.scheduleVisemeByIPA({ ipa: string, // IPA phoneme string startMs: number, // ms from audio start endMs: number, // ms from audio start audioContext: AudioContext, audioStartTime: number, // AudioContext time when audio started }) ``` -------------------------------- ### Verify WebGL 2.0 Support and Handle Errors Source: https://developer.openhuman.ai/ Listen for the 'ready' event to confirm WebGL version and extensions. Implement error handling for unsupported browsers, such as falling back to a lower quality setting. ```javascript human.on("ready", ({ webglVersion, extensions }) => { console.log(`Running on WebGL ${webglVersion}`) console.log("Loaded extensions:", extensions) }) human.on("error", (err) => { if (err.code === "WEBGL2_NOT_SUPPORTED") { // Show fallback UI or downgrade to WebGL 1.0 reduced-quality mode console.warn("WebGL 2.0 not available, falling back to reduced quality.") human.setQuality("low") } }) ``` -------------------------------- ### StreamingClient Options Source: https://developer.openhuman.ai/streaming-protocol Configuration options for initializing the StreamingClient to control transport, buffering, smoothing, and reconnection. ```APIDOC ## `StreamingClient` options ### Description Configuration options for initializing the `StreamingClient` to control transport, buffering, smoothing, and reconnection. ### Parameters #### `transport` * Type: `'websocket' | 'http'` * Default: `'websocket'` * Description: Network transport protocol. #### `url` * Type: `string` * Description: The `wss://` or `https://` endpoint for the streaming service. This is a required parameter. #### `format` * Type: `'binary' | 'ndjson'` * Default: `'binary'` for WebSocket, `'ndjson'` for HTTP. * Description: Specifies the frame encoding format. #### `mode` * Type: `'full' | 'facs'` * Default: `'full'` * Description: Sets the processing mode. `'facs'` can be used for lip-sync-only streams, skipping joint processing. #### `jitterBuffer` * Type: `number` * Default: `80` * Description: The depth of the jitter buffer in milliseconds. Controls how much latency is introduced to smooth out network jitter. #### `maxBuffer` * Type: `number` * Default: `300` * Description: The maximum age of a frame in milliseconds before it is discarded. #### `extrapolate` * Type: `boolean` * Default: `true` * Description: Enables pose prediction when the buffer underruns. #### `smoothing` * Type: `number` * Default: `0.7` * Description: Exponential Moving Average (EMA) alpha for smoothing FACS weights. `0` means no smoothing, `1` means frozen. #### `reconnect` * Type: `boolean` * Default: `true` * Description: Automatically attempts to reconnect if the connection is lost. #### `reconnectDelay` * Type: `number` * Default: `1000` * Description: The delay in milliseconds between reconnection attempts. ``` -------------------------------- ### Asset Loading Data Flow Source: https://developer.openhuman.ai/architecture-overview Illustrates the process of parsing .ohb files, uploading assets to the GPU, and preparing a character instance for rendering. ```mermaid flowchart TD A[".ohb file on disk / CDN"] --> B["BundleParser"] B --> B1["Header
version, chunk count, flags"] B --> B2["Chunk[0]
glTF mesh (binary)"] B --> B3["Chunk[1]
KTX2 textures
(albedo, normal, ORM, emissive)"] B --> B4["Chunk[2]
Skeleton
(joint hierarchy + bind pose)"] B --> B5["Chunk[3]
Morph targets
(52 FACS delta buffers)"] B --> B6["Chunk[4]
Animation clips
(idle, talk, blink, ...)"] B --> C["GPU Upload
(async, chunked to avoid frame drops)"] C --> C1["WebGLBuffer
vertex / index data"] C --> C2["WebGLTexture
KTX2 compressed textures"] C --> C3["Float32Array
morph target deltas
(kept in JS heap)"] C --> D["CharacterInstance
(ready to render)"] ``` -------------------------------- ### Create and Configure 1D Blend Tree Source: https://developer.openhuman.ai/animation-graph Create a 1D blend tree using `BlendTree1D` to smoothly blend between multiple clips based on a single float parameter. Add clips with their corresponding parameter values. ```javascript import { BlendTree1D } from "@openhuman/sdk" const emotionTree = new BlendTree1D("emotionHappy") emotionTree.addClip(0.0, human.animation.getClip("talk_neutral")) emotionTree.addClip(0.5, human.animation.getClip("talk_happy_light")) emotionTree.addClip(1.0, human.animation.getClip("talk_happy_full")) graph.addState("talk", emotionTree) // At runtime: smoothly blend across emotion intensity graph.setFloat("emotionHappy", 0.75) ``` -------------------------------- ### Streaming Client Event Handlers Source: https://developer.openhuman.ai/streaming-protocol Set up event handlers for connection status, frame reception, and buffer events. These callbacks allow you to react to stream lifecycle changes and data availability. ```javascript client.on("connected", () => console.log("Stream connected")) client.on("disconnected", ({ code, reason }) => console.warn("Stream dropped:", reason)) client.on("reconnecting", ({ attempt }) => console.log("Reconnect attempt", attempt)) client.on("frame", (pose) => { // pose: { timestamp, joints?: Float32Array, facs?: Float32Array } // Fired after jitter buffer output - before GPU upload }) client.on("bufferUnderrun", () => console.warn("Jitter buffer ran dry")) client.on("bufferOverflow", () => console.warn("Buffer full - frames dropped")) ``` -------------------------------- ### Manual Pose Application with Pre-processing Source: https://developer.openhuman.ai/streaming-protocol Handle pose data manually by skipping client.attach() and processing frames within the 'frame' event. This allows for pre-processing like retargeting or blending before applying to the human model. ```javascript // Do NOT call client.attach(human) client.on("frame", ({ joints, facs }) => { if (joints) { // Retarget from a different skeleton before applying const retargeted = myRetargeter.apply(joints) human.skeleton.setPose(retargeted) } if (facs) { // Blend 50/50 with a local procedural expression const blended = facs.map((w, i) => w * 0.5 + localFacs[i] * 0.5) human.morph.setFromArray(blended) } }) await client.connect() ``` -------------------------------- ### Listening for SDK Errors Source: https://developer.openhuman.ai/error-code-reference Demonstrates how to globally handle errors using `human.on('error')` and how to catch asynchronous errors from SDK methods like `loadCharacter`. ```typescript // Global error handler human.on("error", (err: OpenHumanError) => { console.error(`[${err.category}] ${err.code}: ${err.message}`) if (!err.recoverable) { // show fallback UI } }) // Async errors from loadCharacter, connectStream, etc. try { await human.loadCharacter("./character.ohb") } catch (err) { if (err instanceof OpenHumanError) { handleError(err) } } ``` -------------------------------- ### Create and Configure 2D Blend Tree Source: https://developer.openhuman.ai/animation-graph Create a 2D blend tree using `BlendTree2D` to blend between clips based on two float parameters. Add clips with their corresponding 2D coordinate values. ```javascript import { BlendTree2D } from "@openhuman/sdk" const gazeTree = new BlendTree2D("lookX", "lookY") gazeTree.addClip([0, 0], human.animation.getClip("look_center")) gazeTree.addClip([-1, 0], human.animation.getClip("look_left")) gazeTree.addClip([1, 0], human.animation.getClip("look_right")) gazeTree.addClip([0, 1], human.animation.getClip("look_up")) gazeTree.addClip([0, -1], human.animation.getClip("look_down")) human.animation.setLayer("gaze", gazeTree, { additive: true, mask: "facial" }) // Animate gaze toward a target graph.setFloat("lookX", 0.4) graph.setFloat("lookY", -0.2) ``` -------------------------------- ### Check for WebGL 2.0 Support Before Initialization Source: https://developer.openhuman.ai/error-code-reference This code snippet checks if the browser supports WebGL 2.0 by attempting to create a context. It's crucial for preventing `WEBGL2_NOT_SUPPORTED` errors. ```javascript // Check before initializing if (!document.createElement("canvas").getContext("webgl2")) { showFallbackUI("Your browser does not support WebGL 2.0.") return } ``` -------------------------------- ### Connect to HTTP Chunked Stream with OpenHuman SDK Source: https://developer.openhuman.ai/streaming-protocol Connects to an HTTP chunked stream using the OpenHuman SDK. Ensure the 'character.ohb' file is loaded and the server URL is correct. ```javascript import { OpenHuman, StreamingClient } from "@openhuman/sdk" const human = await OpenHuman.load("character.ohb", canvas) const client = new StreamingClient({ transport: "http", url: "https://your-server.example.com/animation-stream", format: "ndjson", // 'ndjson' (default) or 'binary' }) client.attach(human) await client.connect() ``` -------------------------------- ### Set Rendering Quality and Features Source: https://developer.openhuman.ai/embed-api-reference Adjust rendering quality presets, target frame rate, and toggle specific graphical features like shadows and post-processing. ```html ``` -------------------------------- ### Enable Streaming Animation Source: https://developer.openhuman.ai/ Connect to a streaming animation server via WebSocket to apply real-time animation data, such as lip sync or mocap. The `StreamingClient` handles the connection and frame processing. ```javascript import { OpenHuman, StreamingClient } from "@openhuman/sdk" const human = new OpenHuman({ canvas }) await human.loadCharacter("./characters/default.ohb") // Connect to a streaming animation server const stream = new StreamingClient({ url: "wss://your-server.example.com/animation-stream", jitterBuffer: 80, // ms - smooths latency spikes }) stream.on("frame", (pose) => { human.applyPose(pose) }) stream.connect() ``` -------------------------------- ### Load Character and Set Morph Weights Source: https://developer.openhuman.ai/facial-blendshapes Loads a character and demonstrates setting individual and multiple morph target weights. Use this for basic facial animation control. ```javascript import { OpenHuman } from "@openhuman/sdk" const human = await OpenHuman.load("character.ohb", canvas) // Set a single morph target weight (0.0 – 1.0) human.morph.set("mouthSmileLeft", 0.7) human.morph.set("mouthSmileRight", 0.7) // Set multiple targets in one call human.morph.setMany({ jawOpen: 0.4, mouthFunnel: 0.3, browInnerUp: 0.5, }) // Read current weight const w = human.morph.get("jawOpen") // → 0.4 // Reset all targets to 0 human.morph.reset() ``` -------------------------------- ### Control Camera Settings Source: https://developer.openhuman.ai/embed-api-reference Adjust the camera's position, target, field of view, and enable runtime orbit control. ```javascript // Set camera position and target el.setCameraPosition([0, 1.65, 0.8]) el.setCameraTarget([0, 1.55, 0]) el.setFOV(35) // Enable orbit control at runtime el.enableOrbit(true) ``` -------------------------------- ### Enable Procedural Head Motion Source: https://developer.openhuman.ai/tts-lipsync-integration Enables a built-in system for subtle head motion synchronized with speech audio envelope to improve realism. ```javascript human.setHeadMotion({ enabled: true, intensity: 0.4, // 0.0–1.0 - scale of motion noddingRate: 0.3, // nods per second during speech swayAmplitude: 0.02, // meters - lateral sway tiltAmplitude: 1.5, // degrees - head tilt range }) ``` -------------------------------- ### Node.js WebSocket Server for Pose Streaming Source: https://developer.openhuman.ai/streaming-protocol A minimal WebSocket server that streams pose data at 30 fps. Replace placeholder joint and FACS data with actual pose source. ```javascript import { WebSocketServer } from "ws" const wss = new WebSocketServer({ port: 8080 }) const JOINT_COUNT = 256 const FACS_COUNT = 52 const FRAME_BYTES = 8 + JOINT_COUNT * 32 + FACS_COUNT * 2 wss.on("connection", (ws) => { console.log("Client connected") const interval = setInterval(() => { const buf = new ArrayBuffer(FRAME_BYTES) const view = new DataView(buf) let offset = 0 // Header view.setFloat32(offset, performance.now() / 1000, true) offset += 4 view.setUint16(offset, JOINT_COUNT, true) offset += 2 view.setUint16(offset, FACS_COUNT, true) offset += 2 // Joint data - replace with your actual pose source for (let i = 0; i < JOINT_COUNT; i++) { view.setFloat32(offset, 0, true) // position.x view.setFloat32(offset + 4, 0, true) // position.y view.setFloat32(offset + 8, 0, true) // position.z view.setFloat32(offset + 12, 0, true) // rotation.x view.setFloat32(offset + 16, 0, true) // rotation.y view.setFloat32(offset + 20, 0, true) // rotation.z view.setFloat32(offset + 24, 1, true) // rotation.w (identity) view.setFloat32(offset + 28, 1, true) // scale offset += 32 } // FACS weights - replace with your TTS / phoneme output for (let i = 0; i < FACS_COUNT; i++) { const weight = 0.0 // float in range 0.0–1.0 view.setInt16(offset, Math.round(weight * 32767), true) offset += 2 } ws.send(buf) }, 1000 / 30) // 30 fps ws.on("close", () => clearInterval(interval)) }) console.log("Streaming server on ws://localhost:8080") ``` -------------------------------- ### Connect to WebSocket Stream Source: https://developer.openhuman.ai/streaming-protocol Establishes a connection to the WebSocket streaming server and attaches it to a loaded OpenHuman character. The client automatically applies incoming poses. ```javascript import { OpenHuman, StreamingClient } from "@openhuman/sdk" const human = await OpenHuman.load("character.ohb", canvas) const client = new StreamingClient({ transport: "websocket", url: "wss://your-server.example.com/animation-stream", jitterBuffer: 80, // ms - smooths latency spikes (default: 80) reconnect: true, // auto-reconnect on drop (default: true) reconnectDelay: 1000, // ms between reconnect attempts (default: 1000) }) // Attach to the character - poses are applied automatically each frame client.attach(human) // Open the connection await client.connect() ``` -------------------------------- ### Informational Warning: WASM_SIMD_UNSUPPORTED Source: https://developer.openhuman.ai/error-code-reference This warning is emitted when the WASM SIMD build is loaded on an unsupported browser. The SDK automatically falls back to the scalar build, resulting in a performance degradation. ```javascript WASM_SIMD_UNSUPPORTED ``` -------------------------------- ### Creating a 1D Blend Tree Source: https://developer.openhuman.ai/animation-graph Creates a 1D blend tree that smoothly blends between animation clips based on a single float parameter. ```APIDOC ## 1D Blend Tree - emotion intensity ### Description Creates and configures a 1D blend tree, driven by a float parameter, to blend between multiple animation clips. ### Method `BlendTree1D` constructor, `addClip`, `graph.addState` ### Parameters #### `BlendTree1D(parameterName)` - **parameterName** (string) - Required - The name of the float parameter that drives this blend tree. #### `addClip(parameterValue, clip)` - **parameterValue** (number) - Required - The value of the parameter at which this clip should be fully active. - **clip** (AnimationClip) - Required - The animation clip to add. #### `graph.addState(stateName, blendTree)` - **stateName** (string) - Required - The name of the state to add the blend tree to. - **blendTree** (BlendTree1D) - Required - The configured 1D blend tree instance. ### Request Example ```javascript import { BlendTree1D } from "@openhuman/sdk" const emotionTree = new BlendTree1D("emotionHappy") emotionTree.addClip(0.0, human.animation.getClip("talk_neutral")) emotionTree.addClip(0.5, human.animation.getClip("talk_happy_light")) emotionTree.addClip(1.0, human.animation.getClip("talk_happy_full")) graph.addState("talk", emotionTree) // At runtime: smoothly blend across emotion intensity graph.setFloat("emotionHappy", 0.75) ``` ``` -------------------------------- ### Listen for Open Human Events Source: https://developer.openhuman.ai/embed-api-reference Add event listeners to the element to react to various lifecycle, animation, streaming, and user interaction events. ```javascript const el = document.querySelector("open-human") // Engine lifecycle el.addEventListener("oh:ready", (e) => console.log("Character ready")) el.addEventListener("oh:error", (e) => console.error("Error:", e.detail.message)) el.addEventListener("oh:loadprogress", (e) => console.log(`${e.detail.stage}: ${e.detail.percent}%`)) // Animation el.addEventListener("oh:animationstart", (e) => console.log("Started:", e.detail.state)) el.addEventListener("oh:animationend", (e) => console.log("Ended:", e.detail.clip)) el.addEventListener("oh:transition", (e) => console.log(`${e.detail.from} → ${e.detail.to}`)) // Streaming el.addEventListener("oh:streamconnected", () => console.log("Stream connected")) el.addEventListener("oh:streamdisconnected", (e) => console.warn("Stream dropped:", e.detail.reason)) el.addEventListener("oh:streamframe", (e) => { // e.detail: { timestamp, joints?, facs? } }) // User interaction (requires orbit="true") el.addEventListener("oh:orbitstart", () => console.log("User started rotating")) el.addEventListener("oh:orbitend", () => console.log("User stopped rotating")) ``` -------------------------------- ### Quick Embed with Script and Component Source: https://developer.openhuman.ai/embed-api-reference The minimum required HTML to load the embed script and render a character. The component handles asset loading, rendering, and animation automatically. ```html ``` -------------------------------- ### Manage Streaming Connections Source: https://developer.openhuman.ai/embed-api-reference Connect to a streaming endpoint for real-time data, disconnect the stream, and retrieve stream statistics. ```javascript // Connect a stream await el.connectStream("wss://tts-backend.example.com/lipsync", { mode: "facs" }) // Disconnect el.disconnectStream() // Stream stats const stats = el.getStreamStats() console.log(stats.bufferDepth, stats.estimatedLatency, stats.fps) ```