@omote/core 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +111 -101
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +111 -101
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -2517,6 +2517,12 @@ interface A2EProcessorConfig {
|
|
|
2517
2517
|
* different expression intensity across face regions (brows, eyes, cheeks).
|
|
2518
2518
|
*/
|
|
2519
2519
|
identityIndex?: number;
|
|
2520
|
+
/**
|
|
2521
|
+
* Spring halflife for blendshape smoothing (seconds).
|
|
2522
|
+
* - 0.06 (default): Sweet spot for lip sync
|
|
2523
|
+
* - 0: Bypass (raw frames, no smoothing)
|
|
2524
|
+
*/
|
|
2525
|
+
smoothingHalflife?: number;
|
|
2520
2526
|
/** Callback fired with each blendshape frame (push mode) */
|
|
2521
2527
|
onFrame?: (frame: Float32Array) => void;
|
|
2522
2528
|
/** Error callback */
|
|
@@ -2538,9 +2544,11 @@ declare class A2EProcessor {
|
|
|
2538
2544
|
private plainQueue;
|
|
2539
2545
|
private _latestFrame;
|
|
2540
2546
|
private dripInterval;
|
|
2541
|
-
private
|
|
2547
|
+
private smoother;
|
|
2542
2548
|
private lastDequeuedTime;
|
|
2543
|
-
private
|
|
2549
|
+
private lastUpdateTime;
|
|
2550
|
+
private neutralTriggered;
|
|
2551
|
+
private outputBuffer;
|
|
2544
2552
|
private inferenceRunning;
|
|
2545
2553
|
private pendingChunks;
|
|
2546
2554
|
private getFrameCallCount;
|
package/dist/index.d.ts
CHANGED
|
@@ -2517,6 +2517,12 @@ interface A2EProcessorConfig {
|
|
|
2517
2517
|
* different expression intensity across face regions (brows, eyes, cheeks).
|
|
2518
2518
|
*/
|
|
2519
2519
|
identityIndex?: number;
|
|
2520
|
+
/**
|
|
2521
|
+
* Spring halflife for blendshape smoothing (seconds).
|
|
2522
|
+
* - 0.06 (default): Sweet spot for lip sync
|
|
2523
|
+
* - 0: Bypass (raw frames, no smoothing)
|
|
2524
|
+
*/
|
|
2525
|
+
smoothingHalflife?: number;
|
|
2520
2526
|
/** Callback fired with each blendshape frame (push mode) */
|
|
2521
2527
|
onFrame?: (frame: Float32Array) => void;
|
|
2522
2528
|
/** Error callback */
|
|
@@ -2538,9 +2544,11 @@ declare class A2EProcessor {
|
|
|
2538
2544
|
private plainQueue;
|
|
2539
2545
|
private _latestFrame;
|
|
2540
2546
|
private dripInterval;
|
|
2541
|
-
private
|
|
2547
|
+
private smoother;
|
|
2542
2548
|
private lastDequeuedTime;
|
|
2543
|
-
private
|
|
2549
|
+
private lastUpdateTime;
|
|
2550
|
+
private neutralTriggered;
|
|
2551
|
+
private outputBuffer;
|
|
2544
2552
|
private inferenceRunning;
|
|
2545
2553
|
private pendingChunks;
|
|
2546
2554
|
private getFrameCallCount;
|
package/dist/index.js
CHANGED
|
@@ -1687,12 +1687,85 @@ var AudioChunkCoalescer = class {
|
|
|
1687
1687
|
}
|
|
1688
1688
|
};
|
|
1689
1689
|
|
|
1690
|
+
// src/inference/BlendshapeSmoother.ts
|
|
1691
|
+
var NUM_BLENDSHAPES = 52;
|
|
1692
|
+
var BlendshapeSmoother = class {
|
|
1693
|
+
constructor(config) {
|
|
1694
|
+
/** Whether any target has been set */
|
|
1695
|
+
this._hasTarget = false;
|
|
1696
|
+
this.halflife = config?.halflife ?? 0.06;
|
|
1697
|
+
this.values = new Float32Array(NUM_BLENDSHAPES);
|
|
1698
|
+
this.velocities = new Float32Array(NUM_BLENDSHAPES);
|
|
1699
|
+
this.targets = new Float32Array(NUM_BLENDSHAPES);
|
|
1700
|
+
}
|
|
1701
|
+
/** Whether a target frame has been set (false until first setTarget call) */
|
|
1702
|
+
get hasTarget() {
|
|
1703
|
+
return this._hasTarget;
|
|
1704
|
+
}
|
|
1705
|
+
/**
|
|
1706
|
+
* Set new target frame from inference output.
|
|
1707
|
+
* Springs will converge toward these values on subsequent update() calls.
|
|
1708
|
+
*/
|
|
1709
|
+
setTarget(frame) {
|
|
1710
|
+
this.targets.set(frame);
|
|
1711
|
+
this._hasTarget = true;
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Advance all 52 springs by `dt` seconds and return the smoothed frame.
|
|
1715
|
+
*
|
|
1716
|
+
* Call this every render frame (e.g., inside requestAnimationFrame).
|
|
1717
|
+
* Returns the internal values buffer — do NOT mutate the returned array.
|
|
1718
|
+
*
|
|
1719
|
+
* @param dt - Time step in seconds (e.g., 1/60 for 60fps)
|
|
1720
|
+
* @returns Smoothed blendshape values (Float32Array of 52)
|
|
1721
|
+
*/
|
|
1722
|
+
update(dt) {
|
|
1723
|
+
if (!this._hasTarget) {
|
|
1724
|
+
return this.values;
|
|
1725
|
+
}
|
|
1726
|
+
if (this.halflife <= 0) {
|
|
1727
|
+
this.values.set(this.targets);
|
|
1728
|
+
this.velocities.fill(0);
|
|
1729
|
+
return this.values;
|
|
1730
|
+
}
|
|
1731
|
+
const damping = Math.LN2 / this.halflife;
|
|
1732
|
+
const eydt = Math.exp(-damping * dt);
|
|
1733
|
+
for (let i = 0; i < NUM_BLENDSHAPES; i++) {
|
|
1734
|
+
const j0 = this.values[i] - this.targets[i];
|
|
1735
|
+
const j1 = this.velocities[i] + j0 * damping;
|
|
1736
|
+
this.values[i] = eydt * (j0 + j1 * dt) + this.targets[i];
|
|
1737
|
+
this.velocities[i] = eydt * (this.velocities[i] - j1 * damping * dt);
|
|
1738
|
+
this.values[i] = Math.max(0, Math.min(1, this.values[i]));
|
|
1739
|
+
}
|
|
1740
|
+
return this.values;
|
|
1741
|
+
}
|
|
1742
|
+
/**
|
|
1743
|
+
* Decay all spring targets to neutral (0).
|
|
1744
|
+
*
|
|
1745
|
+
* Call when inference stalls (no new frames for threshold duration).
|
|
1746
|
+
* The springs will smoothly close the mouth / relax the face over
|
|
1747
|
+
* the halflife period rather than freezing.
|
|
1748
|
+
*/
|
|
1749
|
+
decayToNeutral() {
|
|
1750
|
+
this.targets.fill(0);
|
|
1751
|
+
}
|
|
1752
|
+
/**
|
|
1753
|
+
* Reset all state (values, velocities, targets).
|
|
1754
|
+
* Call when starting a new playback session.
|
|
1755
|
+
*/
|
|
1756
|
+
reset() {
|
|
1757
|
+
this.values.fill(0);
|
|
1758
|
+
this.velocities.fill(0);
|
|
1759
|
+
this.targets.fill(0);
|
|
1760
|
+
this._hasTarget = false;
|
|
1761
|
+
}
|
|
1762
|
+
};
|
|
1763
|
+
|
|
1690
1764
|
// src/inference/A2EProcessor.ts
|
|
1691
1765
|
var logger4 = createLogger("A2EProcessor");
|
|
1692
1766
|
var FRAME_RATE = 30;
|
|
1693
1767
|
var DRIP_INTERVAL_MS = 33;
|
|
1694
|
-
var
|
|
1695
|
-
var DECAY_DURATION_MS = 200;
|
|
1768
|
+
var NEUTRAL_THRESHOLD_MS = 1500;
|
|
1696
1769
|
var _A2EProcessor = class _A2EProcessor {
|
|
1697
1770
|
constructor(config) {
|
|
1698
1771
|
this.writeOffset = 0;
|
|
@@ -1703,10 +1776,10 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
1703
1776
|
// Push mode state
|
|
1704
1777
|
this._latestFrame = null;
|
|
1705
1778
|
this.dripInterval = null;
|
|
1706
|
-
// Last-frame-hold for pull mode (prevents avatar freezing between frames)
|
|
1707
|
-
this.lastPulledFrame = null;
|
|
1708
1779
|
this.lastDequeuedTime = 0;
|
|
1709
|
-
this.
|
|
1780
|
+
this.lastUpdateTime = 0;
|
|
1781
|
+
this.neutralTriggered = false;
|
|
1782
|
+
this.outputBuffer = null;
|
|
1710
1783
|
// Inference serialization
|
|
1711
1784
|
this.inferenceRunning = false;
|
|
1712
1785
|
this.pendingChunks = [];
|
|
@@ -1719,6 +1792,7 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
1719
1792
|
this.identityIndex = config.identityIndex ?? 0;
|
|
1720
1793
|
this.onFrame = config.onFrame;
|
|
1721
1794
|
this.onError = config.onError;
|
|
1795
|
+
this.smoother = new BlendshapeSmoother({ halflife: config.smoothingHalflife ?? 0.06 });
|
|
1722
1796
|
this.bufferCapacity = this.chunkSize * 2;
|
|
1723
1797
|
this.buffer = new Float32Array(this.bufferCapacity);
|
|
1724
1798
|
}
|
|
@@ -1818,8 +1892,11 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
1818
1892
|
this.timestampedQueue = [];
|
|
1819
1893
|
this.plainQueue = [];
|
|
1820
1894
|
this._latestFrame = null;
|
|
1821
|
-
this.
|
|
1895
|
+
this.smoother.reset();
|
|
1822
1896
|
this.lastDequeuedTime = 0;
|
|
1897
|
+
this.lastUpdateTime = 0;
|
|
1898
|
+
this.neutralTriggered = false;
|
|
1899
|
+
this.outputBuffer = null;
|
|
1823
1900
|
this.pendingChunks = [];
|
|
1824
1901
|
this.inferenceRunning = false;
|
|
1825
1902
|
this.getFrameCallCount = 0;
|
|
@@ -1838,6 +1915,7 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
1838
1915
|
*/
|
|
1839
1916
|
getFrameForTime(currentTime) {
|
|
1840
1917
|
this.getFrameCallCount++;
|
|
1918
|
+
const now = getClock().now();
|
|
1841
1919
|
const discardWindow = this.backend.backend === "wasm" ? 1.5 : 0.5;
|
|
1842
1920
|
let discardCount = 0;
|
|
1843
1921
|
while (this.timestampedQueue.length > 0 && this.timestampedQueue[0].timestamp < currentTime - discardWindow) {
|
|
@@ -1853,13 +1931,20 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
1853
1931
|
nextFrameTs: this.timestampedQueue.length > 0 ? this.timestampedQueue[0].timestamp.toFixed(3) : "none"
|
|
1854
1932
|
});
|
|
1855
1933
|
}
|
|
1934
|
+
let newDequeue = false;
|
|
1856
1935
|
if (this.timestampedQueue.length > 0 && this.timestampedQueue[0].timestamp <= currentTime) {
|
|
1857
1936
|
const { frame } = this.timestampedQueue.shift();
|
|
1858
|
-
|
|
1859
|
-
this.
|
|
1860
|
-
|
|
1937
|
+
const firstTarget = !this.smoother.hasTarget;
|
|
1938
|
+
this.smoother.setTarget(frame);
|
|
1939
|
+
if (firstTarget) {
|
|
1940
|
+
this.smoother.update(1);
|
|
1941
|
+
this.lastUpdateTime = now;
|
|
1942
|
+
}
|
|
1943
|
+
newDequeue = true;
|
|
1944
|
+
this.neutralTriggered = false;
|
|
1945
|
+
this.lastDequeuedTime = now;
|
|
1861
1946
|
}
|
|
1862
|
-
if (this.timestampedQueue.length > 0 && this.getFrameCallCount % 60 === 0) {
|
|
1947
|
+
if (!newDequeue && this.timestampedQueue.length > 0 && this.getFrameCallCount % 60 === 0) {
|
|
1863
1948
|
logger4.debug("getFrameForTime: waiting for playback time to reach queued frames", {
|
|
1864
1949
|
queueLen: this.timestampedQueue.length,
|
|
1865
1950
|
frontTimestamp: this.timestampedQueue[0].timestamp.toFixed(4),
|
|
@@ -1867,25 +1952,24 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
1867
1952
|
delta: (this.timestampedQueue[0].timestamp - currentTime).toFixed(4)
|
|
1868
1953
|
});
|
|
1869
1954
|
}
|
|
1870
|
-
if (this.
|
|
1871
|
-
const elapsed =
|
|
1872
|
-
if (elapsed
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
const decayElapsed = elapsed - HOLD_DURATION_MS;
|
|
1876
|
-
if (decayElapsed >= DECAY_DURATION_MS) {
|
|
1877
|
-
this.lastPulledFrame = null;
|
|
1878
|
-
return null;
|
|
1879
|
-
}
|
|
1880
|
-
const t = decayElapsed / DECAY_DURATION_MS;
|
|
1881
|
-
const factor = (1 - t) * (1 - t);
|
|
1882
|
-
if (!this.decayBuffer) this.decayBuffer = new Float32Array(52);
|
|
1883
|
-
for (let i = 0; i < 52; i++) {
|
|
1884
|
-
this.decayBuffer[i] = this.lastPulledFrame[i] * factor;
|
|
1955
|
+
if (!newDequeue && this.smoother.hasTarget && !this.neutralTriggered) {
|
|
1956
|
+
const elapsed = now - this.lastDequeuedTime;
|
|
1957
|
+
if (elapsed >= NEUTRAL_THRESHOLD_MS) {
|
|
1958
|
+
this.smoother.decayToNeutral();
|
|
1959
|
+
this.neutralTriggered = true;
|
|
1885
1960
|
}
|
|
1886
|
-
return this.decayBuffer;
|
|
1887
1961
|
}
|
|
1888
|
-
|
|
1962
|
+
if (!this.smoother.hasTarget) {
|
|
1963
|
+
return null;
|
|
1964
|
+
}
|
|
1965
|
+
const dt = Math.min((now - this.lastUpdateTime) / 1e3, 0.1);
|
|
1966
|
+
const smoothed = this.smoother.update(dt);
|
|
1967
|
+
this.lastUpdateTime = now;
|
|
1968
|
+
if (newDequeue || !this.outputBuffer) {
|
|
1969
|
+
this.outputBuffer = new Float32Array(52);
|
|
1970
|
+
}
|
|
1971
|
+
this.outputBuffer.set(smoothed);
|
|
1972
|
+
return this.outputBuffer;
|
|
1889
1973
|
}
|
|
1890
1974
|
// ═══════════════════════════════════════════════════════════════════════
|
|
1891
1975
|
// Frame Output — Push Mode (live mic, game loop)
|
|
@@ -11216,80 +11300,6 @@ var InterruptionHandler = class extends EventEmitter {
|
|
|
11216
11300
|
}
|
|
11217
11301
|
};
|
|
11218
11302
|
|
|
11219
|
-
// src/inference/BlendshapeSmoother.ts
|
|
11220
|
-
var NUM_BLENDSHAPES = 52;
|
|
11221
|
-
var BlendshapeSmoother = class {
|
|
11222
|
-
constructor(config) {
|
|
11223
|
-
/** Whether any target has been set */
|
|
11224
|
-
this._hasTarget = false;
|
|
11225
|
-
this.halflife = config?.halflife ?? 0.06;
|
|
11226
|
-
this.values = new Float32Array(NUM_BLENDSHAPES);
|
|
11227
|
-
this.velocities = new Float32Array(NUM_BLENDSHAPES);
|
|
11228
|
-
this.targets = new Float32Array(NUM_BLENDSHAPES);
|
|
11229
|
-
}
|
|
11230
|
-
/** Whether a target frame has been set (false until first setTarget call) */
|
|
11231
|
-
get hasTarget() {
|
|
11232
|
-
return this._hasTarget;
|
|
11233
|
-
}
|
|
11234
|
-
/**
|
|
11235
|
-
* Set new target frame from inference output.
|
|
11236
|
-
* Springs will converge toward these values on subsequent update() calls.
|
|
11237
|
-
*/
|
|
11238
|
-
setTarget(frame) {
|
|
11239
|
-
this.targets.set(frame);
|
|
11240
|
-
this._hasTarget = true;
|
|
11241
|
-
}
|
|
11242
|
-
/**
|
|
11243
|
-
* Advance all 52 springs by `dt` seconds and return the smoothed frame.
|
|
11244
|
-
*
|
|
11245
|
-
* Call this every render frame (e.g., inside requestAnimationFrame).
|
|
11246
|
-
* Returns the internal values buffer — do NOT mutate the returned array.
|
|
11247
|
-
*
|
|
11248
|
-
* @param dt - Time step in seconds (e.g., 1/60 for 60fps)
|
|
11249
|
-
* @returns Smoothed blendshape values (Float32Array of 52)
|
|
11250
|
-
*/
|
|
11251
|
-
update(dt) {
|
|
11252
|
-
if (!this._hasTarget) {
|
|
11253
|
-
return this.values;
|
|
11254
|
-
}
|
|
11255
|
-
if (this.halflife <= 0) {
|
|
11256
|
-
this.values.set(this.targets);
|
|
11257
|
-
this.velocities.fill(0);
|
|
11258
|
-
return this.values;
|
|
11259
|
-
}
|
|
11260
|
-
const damping = Math.LN2 / this.halflife;
|
|
11261
|
-
const eydt = Math.exp(-damping * dt);
|
|
11262
|
-
for (let i = 0; i < NUM_BLENDSHAPES; i++) {
|
|
11263
|
-
const j0 = this.values[i] - this.targets[i];
|
|
11264
|
-
const j1 = this.velocities[i] + j0 * damping;
|
|
11265
|
-
this.values[i] = eydt * (j0 + j1 * dt) + this.targets[i];
|
|
11266
|
-
this.velocities[i] = eydt * (this.velocities[i] - j1 * damping * dt);
|
|
11267
|
-
this.values[i] = Math.max(0, Math.min(1, this.values[i]));
|
|
11268
|
-
}
|
|
11269
|
-
return this.values;
|
|
11270
|
-
}
|
|
11271
|
-
/**
|
|
11272
|
-
* Decay all spring targets to neutral (0).
|
|
11273
|
-
*
|
|
11274
|
-
* Call when inference stalls (no new frames for threshold duration).
|
|
11275
|
-
* The springs will smoothly close the mouth / relax the face over
|
|
11276
|
-
* the halflife period rather than freezing.
|
|
11277
|
-
*/
|
|
11278
|
-
decayToNeutral() {
|
|
11279
|
-
this.targets.fill(0);
|
|
11280
|
-
}
|
|
11281
|
-
/**
|
|
11282
|
-
* Reset all state (values, velocities, targets).
|
|
11283
|
-
* Call when starting a new playback session.
|
|
11284
|
-
*/
|
|
11285
|
-
reset() {
|
|
11286
|
-
this.values.fill(0);
|
|
11287
|
-
this.velocities.fill(0);
|
|
11288
|
-
this.targets.fill(0);
|
|
11289
|
-
this._hasTarget = false;
|
|
11290
|
-
}
|
|
11291
|
-
};
|
|
11292
|
-
|
|
11293
11303
|
// src/inference/SafariSpeechRecognition.ts
|
|
11294
11304
|
var logger33 = createLogger("SafariSpeech");
|
|
11295
11305
|
var SafariSpeechRecognition = class _SafariSpeechRecognition {
|