@omote/core 0.9.3 → 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
@@ -2541,6 +2541,9 @@ declare class A2EProcessor {
2541
2541
  private lastPulledFrame;
2542
2542
  private lastDequeuedTime;
2543
2543
  private decayBuffer;
2544
+ private smoother;
2545
+ private gapDecayStarted;
2546
+ private lastSmootherUpdate;
2544
2547
  private inferenceRunning;
2545
2548
  private pendingChunks;
2546
2549
  private getFrameCallCount;
@@ -2660,6 +2663,15 @@ declare class BlendshapeSmoother {
2660
2663
  * Springs will converge toward these values on subsequent update() calls.
2661
2664
  */
2662
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;
2663
2675
  /**
2664
2676
  * Advance all 52 springs by `dt` seconds and return the smoothed frame.
2665
2677
  *
package/dist/index.d.ts CHANGED
@@ -2541,6 +2541,9 @@ declare class A2EProcessor {
2541
2541
  private lastPulledFrame;
2542
2542
  private lastDequeuedTime;
2543
2543
  private decayBuffer;
2544
+ private smoother;
2545
+ private gapDecayStarted;
2546
+ private lastSmootherUpdate;
2544
2547
  private inferenceRunning;
2545
2548
  private pendingChunks;
2546
2549
  private getFrameCallCount;
@@ -2660,6 +2663,15 @@ declare class BlendshapeSmoother {
2660
2663
  * Springs will converge toward these values on subsequent update() calls.
2661
2664
  */
2662
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;
2663
2675
  /**
2664
2676
  * Advance all 52 springs by `dt` seconds and return the smoothed frame.
2665
2677
  *
package/dist/index.js CHANGED
@@ -1687,12 +1687,99 @@ 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
+ * 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
+ }
1726
+ /**
1727
+ * Advance all 52 springs by `dt` seconds and return the smoothed frame.
1728
+ *
1729
+ * Call this every render frame (e.g., inside requestAnimationFrame).
1730
+ * Returns the internal values buffer — do NOT mutate the returned array.
1731
+ *
1732
+ * @param dt - Time step in seconds (e.g., 1/60 for 60fps)
1733
+ * @returns Smoothed blendshape values (Float32Array of 52)
1734
+ */
1735
+ update(dt) {
1736
+ if (!this._hasTarget) {
1737
+ return this.values;
1738
+ }
1739
+ if (this.halflife <= 0) {
1740
+ this.values.set(this.targets);
1741
+ this.velocities.fill(0);
1742
+ return this.values;
1743
+ }
1744
+ const damping = Math.LN2 / this.halflife;
1745
+ const eydt = Math.exp(-damping * dt);
1746
+ for (let i = 0; i < NUM_BLENDSHAPES; i++) {
1747
+ const j0 = this.values[i] - this.targets[i];
1748
+ const j1 = this.velocities[i] + j0 * damping;
1749
+ this.values[i] = eydt * (j0 + j1 * dt) + this.targets[i];
1750
+ this.velocities[i] = eydt * (this.velocities[i] - j1 * damping * dt);
1751
+ this.values[i] = Math.max(0, Math.min(1, this.values[i]));
1752
+ }
1753
+ return this.values;
1754
+ }
1755
+ /**
1756
+ * Decay all spring targets to neutral (0).
1757
+ *
1758
+ * Call when inference stalls (no new frames for threshold duration).
1759
+ * The springs will smoothly close the mouth / relax the face over
1760
+ * the halflife period rather than freezing.
1761
+ */
1762
+ decayToNeutral() {
1763
+ this.targets.fill(0);
1764
+ }
1765
+ /**
1766
+ * Reset all state (values, velocities, targets).
1767
+ * Call when starting a new playback session.
1768
+ */
1769
+ reset() {
1770
+ this.values.fill(0);
1771
+ this.velocities.fill(0);
1772
+ this.targets.fill(0);
1773
+ this._hasTarget = false;
1774
+ }
1775
+ };
1776
+
1690
1777
  // src/inference/A2EProcessor.ts
1691
1778
  var logger4 = createLogger("A2EProcessor");
1692
1779
  var FRAME_RATE = 30;
1693
1780
  var DRIP_INTERVAL_MS = 33;
1694
1781
  var HOLD_DURATION_MS = 400;
1695
- var DECAY_DURATION_MS = 300;
1782
+ var GAP_DECAY_HALFLIFE_S = 0.08;
1696
1783
  var _A2EProcessor = class _A2EProcessor {
1697
1784
  constructor(config) {
1698
1785
  this.writeOffset = 0;
@@ -1707,6 +1794,8 @@ var _A2EProcessor = class _A2EProcessor {
1707
1794
  this.lastPulledFrame = null;
1708
1795
  this.lastDequeuedTime = 0;
1709
1796
  this.decayBuffer = null;
1797
+ this.gapDecayStarted = false;
1798
+ this.lastSmootherUpdate = 0;
1710
1799
  // Inference serialization
1711
1800
  this.inferenceRunning = false;
1712
1801
  this.pendingChunks = [];
@@ -1721,6 +1810,7 @@ var _A2EProcessor = class _A2EProcessor {
1721
1810
  this.onError = config.onError;
1722
1811
  this.bufferCapacity = this.chunkSize * 2;
1723
1812
  this.buffer = new Float32Array(this.bufferCapacity);
1813
+ this.smoother = new BlendshapeSmoother({ halflife: GAP_DECAY_HALFLIFE_S });
1724
1814
  }
1725
1815
  // ═══════════════════════════════════════════════════════════════════════
1726
1816
  // Audio Input
@@ -1823,6 +1913,9 @@ var _A2EProcessor = class _A2EProcessor {
1823
1913
  this.pendingChunks = [];
1824
1914
  this.inferenceRunning = false;
1825
1915
  this.getFrameCallCount = 0;
1916
+ this.smoother.reset();
1917
+ this.gapDecayStarted = false;
1918
+ this.lastSmootherUpdate = 0;
1826
1919
  }
1827
1920
  // ═══════════════════════════════════════════════════════════════════════
1828
1921
  // Frame Output — Pull Mode (TTS playback)
@@ -1857,6 +1950,7 @@ var _A2EProcessor = class _A2EProcessor {
1857
1950
  const { frame } = this.timestampedQueue.shift();
1858
1951
  this.lastPulledFrame = frame;
1859
1952
  this.lastDequeuedTime = getClock().now();
1953
+ this.gapDecayStarted = false;
1860
1954
  return frame;
1861
1955
  }
1862
1956
  if (this.timestampedQueue.length > 0 && this.getFrameCallCount % 60 === 0) {
@@ -1868,21 +1962,30 @@ var _A2EProcessor = class _A2EProcessor {
1868
1962
  });
1869
1963
  }
1870
1964
  if (this.lastPulledFrame) {
1871
- const elapsed = getClock().now() - this.lastDequeuedTime;
1965
+ const now = getClock().now();
1966
+ const elapsed = now - this.lastDequeuedTime;
1872
1967
  if (elapsed < HOLD_DURATION_MS) {
1873
1968
  return this.lastPulledFrame;
1874
1969
  }
1875
- const decayElapsed = elapsed - HOLD_DURATION_MS;
1876
- if (decayElapsed >= DECAY_DURATION_MS) {
1970
+ if (!this.gapDecayStarted) {
1971
+ this.smoother.setPosition(this.lastPulledFrame);
1972
+ this.smoother.decayToNeutral();
1973
+ this.gapDecayStarted = true;
1974
+ this.lastSmootherUpdate = now;
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) {
1877
1984
  this.lastPulledFrame = null;
1878
1985
  return null;
1879
1986
  }
1880
- const t = decayElapsed / DECAY_DURATION_MS;
1881
- const factor = (1 - t) * (1 - t);
1882
1987
  if (!this.decayBuffer) this.decayBuffer = new Float32Array(52);
1883
- for (let i = 0; i < 52; i++) {
1884
- this.decayBuffer[i] = this.lastPulledFrame[i] * factor;
1885
- }
1988
+ this.decayBuffer.set(smoothed);
1886
1989
  return this.decayBuffer;
1887
1990
  }
1888
1991
  return null;
@@ -11216,80 +11319,6 @@ var InterruptionHandler = class extends EventEmitter {
11216
11319
  }
11217
11320
  };
11218
11321
 
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
11322
  // src/inference/SafariSpeechRecognition.ts
11294
11323
  var logger33 = createLogger("SafariSpeech");
11295
11324
  var SafariSpeechRecognition = class _SafariSpeechRecognition {