@@ -1623,7 +1623,11 @@ <h2 class="step-title">Add Real-time Microphone</h2>
16231623 '</div>' ,
16241624 '' ,
16251625 '<div class="controls">' ,
1626- ' <button class="btn btn-primary" id="btn-init">Initialize V1 Engine</button>' ,
1626+ ' <select id="engine-select">' ,
1627+ ' <option value="v1" selected>V1 — Phoneme (111-dim)</option>' ,
1628+ ' <option value="v2">V2 — Emotion (52-dim)</option>' ,
1629+ ' </select>' ,
1630+ ' <button class="btn btn-primary" id="btn-init">Initialize Engine</button>' ,
16271631 ' <button class="btn btn-secondary" id="btn-vrm">Upload VRM</button>' ,
16281632 ' <input type="file" id="vrm-input" accept=".vrm" style="display:none">' ,
16291633 ' <button class="btn btn-secondary" id="btn-audio" disabled>Upload Audio</button>' ,
@@ -1632,7 +1636,7 @@ <h2 class="step-title">Add Real-time Microphone</h2>
16321636 ' <button class="btn btn-secondary" id="btn-mic" disabled>Start Mic</button>' ,
16331637 '</div>' ,
16341638 '' ,
1635- '<div class="status-bar" id="status">Click "Initialize V1 Engine " to start.</div>' ,
1639+ '<div class="status-bar" id="status">Select engine and click "Initialize " to start.</div>' ,
16361640 '' ,
16371641 '<script type="module">' ,
16381642 'import * as THREE from ' + "'three'" + ';' ,
@@ -1642,8 +1646,11 @@ <h2 class="step-title">Add Real-time Microphone</h2>
16421646 'import { VRMAnimationLoaderPlugin, createVRMAnimationClip } from ' + "'@pixiv/three-vrm-animation'" + ';' ,
16431647 '' ,
16441648 '// \u2500\u2500 Config \u2500\u2500' ,
1645- "const VERSION = '" + VERSION + "';" ,
1646- 'const CDN = ' + BT + 'https://cdn.jsdelivr.net/npm/@goodganglabs/lipsync-wasm-v1@' + DL + '{VERSION}' + BT + ';' ,
1649+ "const VERSION_V1 = '" + VERSION_V1 + "';" ,
1650+ "const VERSION_V2 = '" + VERSION_V2 + "';" ,
1651+ 'const CDN_V1 = ' + BT + 'https://cdn.jsdelivr.net/npm/@goodganglabs/lipsync-wasm-v1@' + DL + '{VERSION_V1}' + BT + ';' ,
1652+ 'const CDN_V2 = ' + BT + 'https://cdn.jsdelivr.net/npm/@goodganglabs/lipsync-wasm-v2@' + DL + '{VERSION_V2}' + BT + ';' ,
1653+ "let selectedEngine = 'v1';" ,
16471654 '' ,
16481655 '// ARKit 52-dim blendshape names' ,
16491656 "const ARKIT_52 = [" ,
@@ -1785,30 +1792,45 @@ <h2 class="step-title">Add Real-time Microphone</h2>
17851792 ' updateControls();' ,
17861793 '}' ,
17871794 '' ,
1788- '// \u2500\u2500 Initialize AnimaSync V1 \u2500\u2500' ,
1795+ '// \u2500\u2500 Initialize AnimaSync (V1 or V2) \u2500\u2500' ,
1796+ "const engineSelect = el('engine-select');" ,
17891797 "btnInit.addEventListener('click', async () => {" ,
1790- ' if (lipsync?.ready) return;' ,
1798+ ' if (lipsync?.ready && engineSelect.value === selectedEngine) return;' ,
1799+ ' selectedEngine = engineSelect.value;' ,
1800+ " const cdn = selectedEngine === 'v1' ? CDN_V1 : CDN_V2;" ,
1801+ " const wasmFile = selectedEngine === 'v1' ? 'lipsync_wasm_v1.js' : 'lipsync_wasm_v2.js';" ,
1802+ ' const label = selectedEngine.toUpperCase();' ,
1803+ '' ,
1804+ ' if (lipsync) { try { lipsync.reset?.(); } catch {} lipsync = null; }' ,
1805+ ' idleGenerator = null; idleClock = 0;' ,
17911806 ' btnInit.disabled = true;' ,
1792- " btnInit.textContent = 'Initializing...';" ,
1793- " setStatus('Loading WASM + ONNX model...');" ,
1807+ ' setStatus(' + BT + 'Loading ' + DL + '{label} WASM + ONNX model...' + BT + ');' ,
17941808 '' ,
17951809 ' try {' ,
1796- ' const { LipSyncWasmWrapper } = await import(' + BT + DL + '{CDN }/lipsync-wasm-wrapper.js' + BT + ');' ,
1797- ' lipsync = new LipSyncWasmWrapper({ wasmPath: ' + BT + DL + '{CDN}/lipsync_wasm_v1.js ' + BT + ' });' ,
1810+ ' const { LipSyncWasmWrapper } = await import(' + BT + DL + '{cdn }/lipsync-wasm-wrapper.js' + BT + ');' ,
1811+ ' lipsync = new LipSyncWasmWrapper({ wasmPath: ' + BT + DL + '{cdn}/' + DL + '{wasmFile} ' + BT + ' });' ,
17981812 ' await lipsync.init({' ,
1799- ' onProgress: (stage, pct) => setStatus(' + BT + DL + '{stage} \\u2014 ' + DL + '{pct}%' + BT + '),' ,
1813+ ' onProgress: (stage, pct) => setStatus(' + BT + DL + '{label} ' + DL + '{ stage} \\u2014 ' + DL + '{pct}%' + BT + '),' ,
18001814 ' });' ,
18011815 '' ,
1802- ' // Eye blink + micro expressions' ,
1816+ ' // Eye blink + micro expressions (V1 only) ' ,
18031817 ' try {' ,
1804- ' idleGenerator = new lipsync.wasmModule.IdleExpressionGenerator();' ,
1805- ' idleClock = 0;' ,
1818+ ' if (lipsync.wasmModule?.IdleExpressionGenerator) {' ,
1819+ ' idleGenerator = new lipsync.wasmModule.IdleExpressionGenerator();' ,
1820+ ' idleClock = 0;' ,
1821+ ' }' ,
18061822 " } catch (e) { console.warn('Idle generator init failed:', e); }" ,
18071823 '' ,
1808- " initBadge.textContent = 'V1 Ready';" ,
1824+ ' initBadge.textContent = label + ' + "' Ready'" + ';' ,
18091825 " initBadge.className = 'status-badge badge-ready';" ,
1810- " btnInit.textContent = 'Initialized';" ,
1811- " setStatus('Engine ready. Upload a VRM to continue.');" ,
1826+ ' btnInit.textContent = label + ' + "' Initialized'" + ';' ,
1827+ ' btnInit.disabled = false;' ,
1828+ ' setStatus(label + ' + "' engine ready. Upload a VRM to continue.'" + ');' ,
1829+ '' ,
1830+ " // Emotion control: setEmotion() available on V2" ,
1831+ " // Mic streaming: emotion applies per-chunk in real time" ,
1832+ " // File playback: emotion applies on next processFile()" ,
1833+ '' ,
18121834 ' updateControls();' ,
18131835 ' } catch (err) {' ,
18141836 ' lipsync = null;' ,
@@ -1921,6 +1943,13 @@ <h2 class="step-title">Add Real-time Microphone</h2>
19211943 " setStatus('Microphone stopped.');" ,
19221944 '}' ,
19231945 '' ,
1946+ '// \u2500\u2500 Emotion Control (V2 only) \u2500\u2500' ,
1947+ '// Call lipsync.setEmotion([neutral, joy, anger, sadness, surprise]) with values 0-1.' ,
1948+ '// Mic streaming: emotion applies per-chunk in real time.' ,
1949+ '// File playback: set emotion before uploading audio.' ,
1950+ '// Flush queue on change so new emotion frames appear immediately:' ,
1951+ '// if (micActive) frameQueue.length = 0;' ,
1952+ '' ,
19241953 '// \u2500\u2500 Blendshape Helpers \u2500\u2500' ,
19251954 'function applyArkitBlendshapes(frame) {' ,
19261955 ' if (!vrm?.expressionManager) return;' ,
0 commit comments