@omote/core 0.9.4 → 0.9.6
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 +14 -10
- package/dist/index.d.ts +14 -10
- package/dist/index.js +62 -41
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +62 -41
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -2517,12 +2517,6 @@ 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;
|
|
2526
2520
|
/** Callback fired with each blendshape frame (push mode) */
|
|
2527
2521
|
onFrame?: (frame: Float32Array) => void;
|
|
2528
2522
|
/** Error callback */
|
|
@@ -2544,11 +2538,12 @@ declare class A2EProcessor {
|
|
|
2544
2538
|
private plainQueue;
|
|
2545
2539
|
private _latestFrame;
|
|
2546
2540
|
private dripInterval;
|
|
2547
|
-
private
|
|
2541
|
+
private lastPulledFrame;
|
|
2548
2542
|
private lastDequeuedTime;
|
|
2549
|
-
private
|
|
2550
|
-
private
|
|
2551
|
-
private
|
|
2543
|
+
private decayBuffer;
|
|
2544
|
+
private smoother;
|
|
2545
|
+
private gapDecayStarted;
|
|
2546
|
+
private lastSmootherUpdate;
|
|
2552
2547
|
private inferenceRunning;
|
|
2553
2548
|
private pendingChunks;
|
|
2554
2549
|
private getFrameCallCount;
|
|
@@ -2668,6 +2663,15 @@ declare class BlendshapeSmoother {
|
|
|
2668
2663
|
* Springs will converge toward these values on subsequent update() calls.
|
|
2669
2664
|
*/
|
|
2670
2665
|
setTarget(frame: Float32Array): void;
|
|
2666
|
+
/**
|
|
2667
|
+
* Snap current position to a frame without triggering spring physics.
|
|
2668
|
+
* Zeroes velocities so the spring starts from rest at this position.
|
|
2669
|
+
*
|
|
2670
|
+
* Used by A2EProcessor to seed the smoother when entering gap decay —
|
|
2671
|
+
* positions the spring at the last real inference frame before decaying
|
|
2672
|
+
* toward neutral.
|
|
2673
|
+
*/
|
|
2674
|
+
setPosition(frame: Float32Array): void;
|
|
2671
2675
|
/**
|
|
2672
2676
|
* Advance all 52 springs by `dt` seconds and return the smoothed frame.
|
|
2673
2677
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -2517,12 +2517,6 @@ 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;
|
|
2526
2520
|
/** Callback fired with each blendshape frame (push mode) */
|
|
2527
2521
|
onFrame?: (frame: Float32Array) => void;
|
|
2528
2522
|
/** Error callback */
|
|
@@ -2544,11 +2538,12 @@ declare class A2EProcessor {
|
|
|
2544
2538
|
private plainQueue;
|
|
2545
2539
|
private _latestFrame;
|
|
2546
2540
|
private dripInterval;
|
|
2547
|
-
private
|
|
2541
|
+
private lastPulledFrame;
|
|
2548
2542
|
private lastDequeuedTime;
|
|
2549
|
-
private
|
|
2550
|
-
private
|
|
2551
|
-
private
|
|
2543
|
+
private decayBuffer;
|
|
2544
|
+
private smoother;
|
|
2545
|
+
private gapDecayStarted;
|
|
2546
|
+
private lastSmootherUpdate;
|
|
2552
2547
|
private inferenceRunning;
|
|
2553
2548
|
private pendingChunks;
|
|
2554
2549
|
private getFrameCallCount;
|
|
@@ -2668,6 +2663,15 @@ declare class BlendshapeSmoother {
|
|
|
2668
2663
|
* Springs will converge toward these values on subsequent update() calls.
|
|
2669
2664
|
*/
|
|
2670
2665
|
setTarget(frame: Float32Array): void;
|
|
2666
|
+
/**
|
|
2667
|
+
* Snap current position to a frame without triggering spring physics.
|
|
2668
|
+
* Zeroes velocities so the spring starts from rest at this position.
|
|
2669
|
+
*
|
|
2670
|
+
* Used by A2EProcessor to seed the smoother when entering gap decay —
|
|
2671
|
+
* positions the spring at the last real inference frame before decaying
|
|
2672
|
+
* toward neutral.
|
|
2673
|
+
*/
|
|
2674
|
+
setPosition(frame: Float32Array): void;
|
|
2671
2675
|
/**
|
|
2672
2676
|
* Advance all 52 springs by `dt` seconds and return the smoothed frame.
|
|
2673
2677
|
*
|
package/dist/index.js
CHANGED
|
@@ -1710,6 +1710,19 @@ var BlendshapeSmoother = class {
|
|
|
1710
1710
|
this.targets.set(frame);
|
|
1711
1711
|
this._hasTarget = true;
|
|
1712
1712
|
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Snap current position to a frame without triggering spring physics.
|
|
1715
|
+
* Zeroes velocities so the spring starts from rest at this position.
|
|
1716
|
+
*
|
|
1717
|
+
* Used by A2EProcessor to seed the smoother when entering gap decay —
|
|
1718
|
+
* positions the spring at the last real inference frame before decaying
|
|
1719
|
+
* toward neutral.
|
|
1720
|
+
*/
|
|
1721
|
+
setPosition(frame) {
|
|
1722
|
+
this.values.set(frame);
|
|
1723
|
+
this.velocities.fill(0);
|
|
1724
|
+
this._hasTarget = true;
|
|
1725
|
+
}
|
|
1713
1726
|
/**
|
|
1714
1727
|
* Advance all 52 springs by `dt` seconds and return the smoothed frame.
|
|
1715
1728
|
*
|
|
@@ -1765,7 +1778,8 @@ var BlendshapeSmoother = class {
|
|
|
1765
1778
|
var logger4 = createLogger("A2EProcessor");
|
|
1766
1779
|
var FRAME_RATE = 30;
|
|
1767
1780
|
var DRIP_INTERVAL_MS = 33;
|
|
1768
|
-
var
|
|
1781
|
+
var HOLD_DURATION_MS = 400;
|
|
1782
|
+
var GAP_DECAY_HALFLIFE_S = 0.08;
|
|
1769
1783
|
var _A2EProcessor = class _A2EProcessor {
|
|
1770
1784
|
constructor(config) {
|
|
1771
1785
|
this.writeOffset = 0;
|
|
@@ -1776,10 +1790,12 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
1776
1790
|
// Push mode state
|
|
1777
1791
|
this._latestFrame = null;
|
|
1778
1792
|
this.dripInterval = null;
|
|
1793
|
+
// Last-frame-hold for pull mode (prevents avatar freezing between frames)
|
|
1794
|
+
this.lastPulledFrame = null;
|
|
1779
1795
|
this.lastDequeuedTime = 0;
|
|
1780
|
-
this.
|
|
1781
|
-
this.
|
|
1782
|
-
this.
|
|
1796
|
+
this.decayBuffer = null;
|
|
1797
|
+
this.gapDecayStarted = false;
|
|
1798
|
+
this.lastSmootherUpdate = 0;
|
|
1783
1799
|
// Inference serialization
|
|
1784
1800
|
this.inferenceRunning = false;
|
|
1785
1801
|
this.pendingChunks = [];
|
|
@@ -1792,9 +1808,9 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
1792
1808
|
this.identityIndex = config.identityIndex ?? 0;
|
|
1793
1809
|
this.onFrame = config.onFrame;
|
|
1794
1810
|
this.onError = config.onError;
|
|
1795
|
-
this.smoother = new BlendshapeSmoother({ halflife: config.smoothingHalflife ?? 0.06 });
|
|
1796
1811
|
this.bufferCapacity = this.chunkSize * 2;
|
|
1797
1812
|
this.buffer = new Float32Array(this.bufferCapacity);
|
|
1813
|
+
this.smoother = new BlendshapeSmoother({ halflife: GAP_DECAY_HALFLIFE_S });
|
|
1798
1814
|
}
|
|
1799
1815
|
// ═══════════════════════════════════════════════════════════════════════
|
|
1800
1816
|
// Audio Input
|
|
@@ -1869,18 +1885,19 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
1869
1885
|
*/
|
|
1870
1886
|
async flush() {
|
|
1871
1887
|
if (this.disposed || this.writeOffset === 0) return;
|
|
1888
|
+
const actualSamples = this.writeOffset;
|
|
1872
1889
|
const padded = new Float32Array(this.chunkSize);
|
|
1873
|
-
padded.set(this.buffer.subarray(0,
|
|
1890
|
+
padded.set(this.buffer.subarray(0, actualSamples), 0);
|
|
1874
1891
|
const chunkTimestamp = this.bufferStartTime > 0 ? this.bufferStartTime : void 0;
|
|
1875
1892
|
logger4.info("flush: routing through drain pipeline", {
|
|
1876
|
-
actualSamples
|
|
1893
|
+
actualSamples,
|
|
1877
1894
|
chunkTimestamp: chunkTimestamp?.toFixed(3),
|
|
1878
1895
|
pendingChunks: this.pendingChunks.length,
|
|
1879
1896
|
inferenceRunning: this.inferenceRunning
|
|
1880
1897
|
});
|
|
1881
1898
|
this.writeOffset = 0;
|
|
1882
1899
|
this.bufferStartTime = 0;
|
|
1883
|
-
this.pendingChunks.push({ chunk: padded, timestamp: chunkTimestamp });
|
|
1900
|
+
this.pendingChunks.push({ chunk: padded, timestamp: chunkTimestamp, actualSamples });
|
|
1884
1901
|
this.drainPendingChunks();
|
|
1885
1902
|
}
|
|
1886
1903
|
/**
|
|
@@ -1892,14 +1909,14 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
1892
1909
|
this.timestampedQueue = [];
|
|
1893
1910
|
this.plainQueue = [];
|
|
1894
1911
|
this._latestFrame = null;
|
|
1895
|
-
this.
|
|
1912
|
+
this.lastPulledFrame = null;
|
|
1896
1913
|
this.lastDequeuedTime = 0;
|
|
1897
|
-
this.lastUpdateTime = 0;
|
|
1898
|
-
this.neutralTriggered = false;
|
|
1899
|
-
this.outputBuffer = null;
|
|
1900
1914
|
this.pendingChunks = [];
|
|
1901
1915
|
this.inferenceRunning = false;
|
|
1902
1916
|
this.getFrameCallCount = 0;
|
|
1917
|
+
this.smoother.reset();
|
|
1918
|
+
this.gapDecayStarted = false;
|
|
1919
|
+
this.lastSmootherUpdate = 0;
|
|
1903
1920
|
}
|
|
1904
1921
|
// ═══════════════════════════════════════════════════════════════════════
|
|
1905
1922
|
// Frame Output — Pull Mode (TTS playback)
|
|
@@ -1915,7 +1932,6 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
1915
1932
|
*/
|
|
1916
1933
|
getFrameForTime(currentTime) {
|
|
1917
1934
|
this.getFrameCallCount++;
|
|
1918
|
-
const now = getClock().now();
|
|
1919
1935
|
const discardWindow = this.backend.backend === "wasm" ? 1.5 : 0.5;
|
|
1920
1936
|
let discardCount = 0;
|
|
1921
1937
|
while (this.timestampedQueue.length > 0 && this.timestampedQueue[0].timestamp < currentTime - discardWindow) {
|
|
@@ -1931,20 +1947,14 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
1931
1947
|
nextFrameTs: this.timestampedQueue.length > 0 ? this.timestampedQueue[0].timestamp.toFixed(3) : "none"
|
|
1932
1948
|
});
|
|
1933
1949
|
}
|
|
1934
|
-
let newDequeue = false;
|
|
1935
1950
|
if (this.timestampedQueue.length > 0 && this.timestampedQueue[0].timestamp <= currentTime) {
|
|
1936
1951
|
const { frame } = this.timestampedQueue.shift();
|
|
1937
|
-
|
|
1938
|
-
this.
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
this.lastUpdateTime = now;
|
|
1942
|
-
}
|
|
1943
|
-
newDequeue = true;
|
|
1944
|
-
this.neutralTriggered = false;
|
|
1945
|
-
this.lastDequeuedTime = now;
|
|
1952
|
+
this.lastPulledFrame = frame;
|
|
1953
|
+
this.lastDequeuedTime = getClock().now();
|
|
1954
|
+
this.gapDecayStarted = false;
|
|
1955
|
+
return frame;
|
|
1946
1956
|
}
|
|
1947
|
-
if (
|
|
1957
|
+
if (this.timestampedQueue.length > 0 && this.getFrameCallCount % 60 === 0) {
|
|
1948
1958
|
logger4.debug("getFrameForTime: waiting for playback time to reach queued frames", {
|
|
1949
1959
|
queueLen: this.timestampedQueue.length,
|
|
1950
1960
|
frontTimestamp: this.timestampedQueue[0].timestamp.toFixed(4),
|
|
@@ -1952,24 +1962,34 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
1952
1962
|
delta: (this.timestampedQueue[0].timestamp - currentTime).toFixed(4)
|
|
1953
1963
|
});
|
|
1954
1964
|
}
|
|
1955
|
-
if (
|
|
1965
|
+
if (this.lastPulledFrame) {
|
|
1966
|
+
const now = getClock().now();
|
|
1956
1967
|
const elapsed = now - this.lastDequeuedTime;
|
|
1957
|
-
if (elapsed
|
|
1968
|
+
if (elapsed < HOLD_DURATION_MS) {
|
|
1969
|
+
return this.lastPulledFrame;
|
|
1970
|
+
}
|
|
1971
|
+
if (!this.gapDecayStarted) {
|
|
1972
|
+
this.smoother.setPosition(this.lastPulledFrame);
|
|
1958
1973
|
this.smoother.decayToNeutral();
|
|
1959
|
-
this.
|
|
1974
|
+
this.gapDecayStarted = true;
|
|
1975
|
+
this.lastSmootherUpdate = now;
|
|
1960
1976
|
}
|
|
1977
|
+
const dt = Math.min((now - this.lastSmootherUpdate) / 1e3, 0.1);
|
|
1978
|
+
this.lastSmootherUpdate = now;
|
|
1979
|
+
const smoothed = this.smoother.update(dt);
|
|
1980
|
+
let maxVal = 0;
|
|
1981
|
+
for (let i = 0; i < 52; i++) {
|
|
1982
|
+
if (smoothed[i] > maxVal) maxVal = smoothed[i];
|
|
1983
|
+
}
|
|
1984
|
+
if (maxVal < 1e-3) {
|
|
1985
|
+
this.lastPulledFrame = null;
|
|
1986
|
+
return null;
|
|
1987
|
+
}
|
|
1988
|
+
if (!this.decayBuffer) this.decayBuffer = new Float32Array(52);
|
|
1989
|
+
this.decayBuffer.set(smoothed);
|
|
1990
|
+
return this.decayBuffer;
|
|
1961
1991
|
}
|
|
1962
|
-
|
|
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;
|
|
1992
|
+
return null;
|
|
1973
1993
|
}
|
|
1974
1994
|
// ═══════════════════════════════════════════════════════════════════════
|
|
1975
1995
|
// Frame Output — Push Mode (live mic, game loop)
|
|
@@ -2035,14 +2055,15 @@ var _A2EProcessor = class _A2EProcessor {
|
|
|
2035
2055
|
logger4.info("drainPendingChunks starting", { pendingChunks: this.pendingChunks.length });
|
|
2036
2056
|
const processNext = async () => {
|
|
2037
2057
|
while (this.pendingChunks.length > 0 && !this.disposed) {
|
|
2038
|
-
const { chunk, timestamp } = this.pendingChunks.shift();
|
|
2058
|
+
const { chunk, timestamp, actualSamples } = this.pendingChunks.shift();
|
|
2039
2059
|
try {
|
|
2040
2060
|
const t0 = getClock().now();
|
|
2041
2061
|
const result = await this.backend.infer(chunk, this.identityIndex);
|
|
2042
2062
|
const inferMs = Math.round(getClock().now() - t0);
|
|
2043
|
-
const
|
|
2063
|
+
const effectiveSamples = actualSamples ?? chunk.length;
|
|
2064
|
+
const actualDuration = effectiveSamples / this.sampleRate;
|
|
2044
2065
|
const actualFrameCount = Math.ceil(actualDuration * FRAME_RATE);
|
|
2045
|
-
const framesToQueue = Math.min(actualFrameCount, result.blendshapes.length);
|
|
2066
|
+
const framesToQueue = Math.min(Math.max(1, actualFrameCount), result.blendshapes.length);
|
|
2046
2067
|
logger4.info("Inference complete", {
|
|
2047
2068
|
inferMs,
|
|
2048
2069
|
modelFrames: result.blendshapes.length,
|