@omote/core 0.9.4 → 0.9.5

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 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 smoother;
2541
+ private lastPulledFrame;
2548
2542
  private lastDequeuedTime;
2549
- private lastUpdateTime;
2550
- private neutralTriggered;
2551
- private outputBuffer;
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 smoother;
2541
+ private lastPulledFrame;
2548
2542
  private lastDequeuedTime;
2549
- private lastUpdateTime;
2550
- private neutralTriggered;
2551
- private outputBuffer;
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 NEUTRAL_THRESHOLD_MS = 1500;
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.lastUpdateTime = 0;
1781
- this.neutralTriggered = false;
1782
- this.outputBuffer = null;
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
@@ -1892,14 +1908,14 @@ var _A2EProcessor = class _A2EProcessor {
1892
1908
  this.timestampedQueue = [];
1893
1909
  this.plainQueue = [];
1894
1910
  this._latestFrame = null;
1895
- this.smoother.reset();
1911
+ this.lastPulledFrame = null;
1896
1912
  this.lastDequeuedTime = 0;
1897
- this.lastUpdateTime = 0;
1898
- this.neutralTriggered = false;
1899
- this.outputBuffer = null;
1900
1913
  this.pendingChunks = [];
1901
1914
  this.inferenceRunning = false;
1902
1915
  this.getFrameCallCount = 0;
1916
+ this.smoother.reset();
1917
+ this.gapDecayStarted = false;
1918
+ this.lastSmootherUpdate = 0;
1903
1919
  }
1904
1920
  // ═══════════════════════════════════════════════════════════════════════
1905
1921
  // Frame Output — Pull Mode (TTS playback)
@@ -1915,7 +1931,6 @@ var _A2EProcessor = class _A2EProcessor {
1915
1931
  */
1916
1932
  getFrameForTime(currentTime) {
1917
1933
  this.getFrameCallCount++;
1918
- const now = getClock().now();
1919
1934
  const discardWindow = this.backend.backend === "wasm" ? 1.5 : 0.5;
1920
1935
  let discardCount = 0;
1921
1936
  while (this.timestampedQueue.length > 0 && this.timestampedQueue[0].timestamp < currentTime - discardWindow) {
@@ -1931,20 +1946,14 @@ var _A2EProcessor = class _A2EProcessor {
1931
1946
  nextFrameTs: this.timestampedQueue.length > 0 ? this.timestampedQueue[0].timestamp.toFixed(3) : "none"
1932
1947
  });
1933
1948
  }
1934
- let newDequeue = false;
1935
1949
  if (this.timestampedQueue.length > 0 && this.timestampedQueue[0].timestamp <= currentTime) {
1936
1950
  const { frame } = this.timestampedQueue.shift();
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;
1951
+ this.lastPulledFrame = frame;
1952
+ this.lastDequeuedTime = getClock().now();
1953
+ this.gapDecayStarted = false;
1954
+ return frame;
1946
1955
  }
1947
- if (!newDequeue && this.timestampedQueue.length > 0 && this.getFrameCallCount % 60 === 0) {
1956
+ if (this.timestampedQueue.length > 0 && this.getFrameCallCount % 60 === 0) {
1948
1957
  logger4.debug("getFrameForTime: waiting for playback time to reach queued frames", {
1949
1958
  queueLen: this.timestampedQueue.length,
1950
1959
  frontTimestamp: this.timestampedQueue[0].timestamp.toFixed(4),
@@ -1952,24 +1961,34 @@ var _A2EProcessor = class _A2EProcessor {
1952
1961
  delta: (this.timestampedQueue[0].timestamp - currentTime).toFixed(4)
1953
1962
  });
1954
1963
  }
1955
- if (!newDequeue && this.smoother.hasTarget && !this.neutralTriggered) {
1964
+ if (this.lastPulledFrame) {
1965
+ const now = getClock().now();
1956
1966
  const elapsed = now - this.lastDequeuedTime;
1957
- if (elapsed >= NEUTRAL_THRESHOLD_MS) {
1967
+ if (elapsed < HOLD_DURATION_MS) {
1968
+ return this.lastPulledFrame;
1969
+ }
1970
+ if (!this.gapDecayStarted) {
1971
+ this.smoother.setPosition(this.lastPulledFrame);
1958
1972
  this.smoother.decayToNeutral();
1959
- this.neutralTriggered = true;
1973
+ this.gapDecayStarted = true;
1974
+ this.lastSmootherUpdate = now;
1960
1975
  }
1976
+ const dt = Math.min((now - this.lastSmootherUpdate) / 1e3, 0.1);
1977
+ this.lastSmootherUpdate = now;
1978
+ const smoothed = this.smoother.update(dt);
1979
+ let maxVal = 0;
1980
+ for (let i = 0; i < 52; i++) {
1981
+ if (smoothed[i] > maxVal) maxVal = smoothed[i];
1982
+ }
1983
+ if (maxVal < 1e-3) {
1984
+ this.lastPulledFrame = null;
1985
+ return null;
1986
+ }
1987
+ if (!this.decayBuffer) this.decayBuffer = new Float32Array(52);
1988
+ this.decayBuffer.set(smoothed);
1989
+ return this.decayBuffer;
1961
1990
  }
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;
1991
+ return null;
1973
1992
  }
1974
1993
  // ═══════════════════════════════════════════════════════════════════════
1975
1994
  // Frame Output — Push Mode (live mic, game loop)