@omote/core 0.5.2 → 0.5.3

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.mjs CHANGED
@@ -761,80 +761,6 @@ var A2EProcessor = class {
761
761
  }
762
762
  };
763
763
 
764
- // src/inference/BlendshapeSmoother.ts
765
- var NUM_BLENDSHAPES = 52;
766
- var BlendshapeSmoother = class {
767
- constructor(config) {
768
- /** Whether any target has been set */
769
- this._hasTarget = false;
770
- this.halflife = config?.halflife ?? 0.06;
771
- this.values = new Float32Array(NUM_BLENDSHAPES);
772
- this.velocities = new Float32Array(NUM_BLENDSHAPES);
773
- this.targets = new Float32Array(NUM_BLENDSHAPES);
774
- }
775
- /** Whether a target frame has been set (false until first setTarget call) */
776
- get hasTarget() {
777
- return this._hasTarget;
778
- }
779
- /**
780
- * Set new target frame from inference output.
781
- * Springs will converge toward these values on subsequent update() calls.
782
- */
783
- setTarget(frame) {
784
- this.targets.set(frame);
785
- this._hasTarget = true;
786
- }
787
- /**
788
- * Advance all 52 springs by `dt` seconds and return the smoothed frame.
789
- *
790
- * Call this every render frame (e.g., inside requestAnimationFrame).
791
- * Returns the internal values buffer — do NOT mutate the returned array.
792
- *
793
- * @param dt - Time step in seconds (e.g., 1/60 for 60fps)
794
- * @returns Smoothed blendshape values (Float32Array of 52)
795
- */
796
- update(dt) {
797
- if (!this._hasTarget) {
798
- return this.values;
799
- }
800
- if (this.halflife <= 0) {
801
- this.values.set(this.targets);
802
- this.velocities.fill(0);
803
- return this.values;
804
- }
805
- const damping = Math.LN2 / this.halflife;
806
- const eydt = Math.exp(-damping * dt);
807
- for (let i = 0; i < NUM_BLENDSHAPES; i++) {
808
- const j0 = this.values[i] - this.targets[i];
809
- const j1 = this.velocities[i] + j0 * damping;
810
- this.values[i] = eydt * (j0 + j1 * dt) + this.targets[i];
811
- this.velocities[i] = eydt * (this.velocities[i] - j1 * damping * dt);
812
- this.values[i] = Math.max(0, Math.min(1, this.values[i]));
813
- }
814
- return this.values;
815
- }
816
- /**
817
- * Decay all spring targets to neutral (0).
818
- *
819
- * Call when inference stalls (no new frames for threshold duration).
820
- * The springs will smoothly close the mouth / relax the face over
821
- * the halflife period rather than freezing.
822
- */
823
- decayToNeutral() {
824
- this.targets.fill(0);
825
- }
826
- /**
827
- * Reset all state (values, velocities, targets).
828
- * Call when starting a new playback session.
829
- */
830
- reset() {
831
- this.values.fill(0);
832
- this.velocities.fill(0);
833
- this.targets.fill(0);
834
- this._hasTarget = false;
835
- }
836
- };
837
-
838
764
  // src/telemetry/exporters/console.ts
839
765
  var ConsoleExporter = class {
840
766
  constructor(options = {}) {
@@ -2891,16 +2817,11 @@ var FullFacePipeline = class extends EventEmitter {
2891
2817
  this.lastNewFrameTime = 0;
2892
2818
  this.lastKnownLamFrame = null;
2893
2819
  this.staleWarningEmitted = false;
2894
- // Frame loop timing (for dt calculation)
2895
- this.lastFrameLoopTime = 0;
2896
2820
  // Diagnostic logging counter
2897
2821
  this.frameLoopCount = 0;
2898
2822
  const sampleRate = options.sampleRate ?? 16e3;
2899
2823
  this.profile = options.profile ?? {};
2900
2824
  this.staleThresholdMs = options.staleThresholdMs ?? 2e3;
2901
- this.smoother = new BlendshapeSmoother({
2902
- halflife: options.smoothingHalflife ?? 0.06
2903
- });
2904
2825
  const isCpuModel = options.lam.modelId === "wav2arkit_cpu";
2905
2826
  const chunkSize = options.chunkSize ?? options.lam.chunkSize ?? 16e3;
2906
2827
  const chunkAccumulationMs = chunkSize / sampleRate * 1e3;
@@ -2983,9 +2904,7 @@ var FullFacePipeline = class extends EventEmitter {
2983
2904
  this.lastNewFrameTime = 0;
2984
2905
  this.lastKnownLamFrame = null;
2985
2906
  this.staleWarningEmitted = false;
2986
- this.lastFrameLoopTime = 0;
2987
2907
  this.frameLoopCount = 0;
2988
- this.smoother.reset();
2989
2908
  this.scheduler.warmup();
2990
2909
  this.startFrameLoop();
2991
2910
  this.startMonitoring();
@@ -3020,22 +2939,16 @@ var FullFacePipeline = class extends EventEmitter {
3020
2939
  /**
3021
2940
  * Start frame animation loop
3022
2941
  *
3023
- * Uses critically damped spring smoother to produce continuous output
3024
- * at render rate (60fps), even between inference batches (~30fps bursts).
3025
- * Springs interpolate toward the latest inference target, and decay
3026
- * to neutral when inference stalls.
2942
+ * Polls A2EProcessor at render rate (60fps) for the latest inference frame
2943
+ * matching the current AudioContext time. Between inference batches (~30fps
2944
+ * bursts), getFrameForTime() holds the last frame.
3027
2945
  */
3028
2946
  startFrameLoop() {
3029
- this.lastFrameLoopTime = 0;
3030
2947
  const updateFrame = () => {
3031
- const now = performance.now() / 1e3;
3032
- const dt = this.lastFrameLoopTime > 0 ? now - this.lastFrameLoopTime : 1 / 60;
3033
- this.lastFrameLoopTime = now;
3034
2948
  this.frameLoopCount++;
3035
2949
  const currentTime = this.scheduler.getCurrentTime();
3036
2950
  const lamFrame = this.processor.getFrameForTime(currentTime);
3037
2951
  if (lamFrame && lamFrame !== this.lastKnownLamFrame) {
3038
- this.smoother.setTarget(lamFrame);
3039
2952
  this.lastNewFrameTime = performance.now();
3040
2953
  this.lastKnownLamFrame = lamFrame;
3041
2954
  this.staleWarningEmitted = false;
@@ -3055,17 +2968,15 @@ var FullFacePipeline = class extends EventEmitter {
3055
2968
  currentTime: currentTime.toFixed(3),
3056
2969
  playbackEndTime: this.scheduler.getPlaybackEndTime().toFixed(3),
3057
2970
  queuedFrames: this.processor.queuedFrameCount,
3058
- hasTarget: this.smoother.hasTarget,
3059
2971
  playbackStarted: this.playbackStarted,
3060
2972
  msSinceNewFrame: this.lastNewFrameTime > 0 ? Math.round(performance.now() - this.lastNewFrameTime) : -1,
3061
2973
  processorFill: this.processor.fillLevel.toFixed(2)
3062
2974
  });
3063
2975
  }
3064
2976
  if (this.playbackStarted && this.lastNewFrameTime > 0 && performance.now() - this.lastNewFrameTime > this.staleThresholdMs) {
3065
- this.smoother.decayToNeutral();
3066
2977
  if (!this.staleWarningEmitted) {
3067
2978
  this.staleWarningEmitted = true;
3068
- logger4.warn("A2E stalled \u2014 decaying to neutral", {
2979
+ logger4.warn("A2E stalled \u2014 no new inference frames", {
3069
2980
  staleDurationMs: Math.round(performance.now() - this.lastNewFrameTime),
3070
2981
  queuedFrames: this.processor.queuedFrameCount
3071
2982
  });
@@ -3104,12 +3015,10 @@ var FullFacePipeline = class extends EventEmitter {
3104
3015
  await this.scheduler.cancelAll(fadeOutMs);
3105
3016
  this.coalescer.reset();
3106
3017
  this.processor.reset();
3107
- this.smoother.reset();
3108
3018
  this.playbackStarted = false;
3109
3019
  this.lastNewFrameTime = 0;
3110
3020
  this.lastKnownLamFrame = null;
3111
3021
  this.staleWarningEmitted = false;
3112
- this.lastFrameLoopTime = 0;
3113
3022
  this.emit("playback_complete", void 0);
3114
3023
  }
3115
3024
  /**
@@ -6995,6 +6904,80 @@ var A2EWithFallback = class {
6995
6904
  }
6996
6905
  };
6997
6906
 
6907
+ // src/inference/BlendshapeSmoother.ts
6908
+ var NUM_BLENDSHAPES = 52;
6909
+ var BlendshapeSmoother = class {
6910
+ constructor(config) {
6911
+ /** Whether any target has been set */
6912
+ this._hasTarget = false;
6913
+ this.halflife = config?.halflife ?? 0.06;
6914
+ this.values = new Float32Array(NUM_BLENDSHAPES);
6915
+ this.velocities = new Float32Array(NUM_BLENDSHAPES);
6916
+ this.targets = new Float32Array(NUM_BLENDSHAPES);
6917
+ }
6918
+ /** Whether a target frame has been set (false until first setTarget call) */
6919
+ get hasTarget() {
6920
+ return this._hasTarget;
6921
+ }
6922
+ /**
6923
+ * Set new target frame from inference output.
6924
+ * Springs will converge toward these values on subsequent update() calls.
6925
+ */
6926
+ setTarget(frame) {
6927
+ this.targets.set(frame);
6928
+ this._hasTarget = true;
6929
+ }
6930
+ /**
6931
+ * Advance all 52 springs by `dt` seconds and return the smoothed frame.
6932
+ *
6933
+ * Call this every render frame (e.g., inside requestAnimationFrame).
6934
+ * Returns the internal values buffer — do NOT mutate the returned array.
6935
+ *
6936
+ * @param dt - Time step in seconds (e.g., 1/60 for 60fps)
6937
+ * @returns Smoothed blendshape values (Float32Array of 52)
6938
+ */
6939
+ update(dt) {
6940
+ if (!this._hasTarget) {
6941
+ return this.values;
6942
+ }
6943
+ if (this.halflife <= 0) {
6944
+ this.values.set(this.targets);
6945
+ this.velocities.fill(0);
6946
+ return this.values;
6947
+ }
6948
+ const damping = Math.LN2 / this.halflife;
6949
+ const eydt = Math.exp(-damping * dt);
6950
+ for (let i = 0; i < NUM_BLENDSHAPES; i++) {
6951
+ const j0 = this.values[i] - this.targets[i];
6952
+ const j1 = this.velocities[i] + j0 * damping;
6953
+ this.values[i] = eydt * (j0 + j1 * dt) + this.targets[i];
6954
+ this.velocities[i] = eydt * (this.velocities[i] - j1 * damping * dt);
6955
+ this.values[i] = Math.max(0, Math.min(1, this.values[i]));
6956
+ }
6957
+ return this.values;
6958
+ }
6959
+ /**
6960
+ * Decay all spring targets to neutral (0).
6961
+ *
6962
+ * Call when inference stalls (no new frames for threshold duration).
6963
+ * The springs will smoothly close the mouth / relax the face over
6964
+ * the halflife period rather than freezing.
6965
+ */
6966
+ decayToNeutral() {
6967
+ this.targets.fill(0);
6968
+ }
6969
+ /**
6970
+ * Reset all state (values, velocities, targets).
6971
+ * Call when starting a new playback session.
6972
+ */
6973
+ reset() {
6974
+ this.values.fill(0);
6975
+ this.velocities.fill(0);
6976
+ this.targets.fill(0);
6977
+ this._hasTarget = false;
6978
+ }
6979
+ };
6980
+
6998
6981
  // src/animation/audioEnergy.ts
6999
6982
  function calculateRMS(samples) {
7000
6983
  if (samples.length === 0) return 0;