@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.d.mts CHANGED
@@ -587,18 +587,7 @@ interface FullFacePipelineOptions {
587
587
  /** Per-character expression weight scaling */
588
588
  profile?: ExpressionProfile;
589
589
  /**
590
- * Spring smoothing halflife in seconds.
591
- * Controls how quickly blendshapes converge to inference targets.
592
- * Lower = snappier but more jittery. Higher = smoother but laggy.
593
- * Set to 0 to disable smoothing (raw frame pass-through).
594
- *
595
- * Default: 0.06 (60ms)
596
- */
597
- smoothingHalflife?: number;
598
- /**
599
- * Time in ms with no new inference frames before decaying to neutral.
600
- * When exceeded, spring targets are set to 0 and the face smoothly
601
- * relaxes rather than freezing on the last frame.
590
+ * Time in ms with no new inference frames before logging a stale warning.
602
591
  *
603
592
  * Must be larger than the inter-batch gap (chunkSize/sampleRate + inference time).
604
593
  * Default: 2000
@@ -646,7 +635,6 @@ declare class FullFacePipeline extends EventEmitter<FullFacePipelineEvents> {
646
635
  private scheduler;
647
636
  private coalescer;
648
637
  private processor;
649
- private smoother;
650
638
  private playbackStarted;
651
639
  private monitorInterval;
652
640
  private frameAnimationId;
@@ -654,7 +642,6 @@ declare class FullFacePipeline extends EventEmitter<FullFacePipelineEvents> {
654
642
  private lastKnownLamFrame;
655
643
  private staleWarningEmitted;
656
644
  private readonly staleThresholdMs;
657
- private lastFrameLoopTime;
658
645
  private frameLoopCount;
659
646
  private profile;
660
647
  constructor(options: FullFacePipelineOptions);
@@ -694,10 +681,9 @@ declare class FullFacePipeline extends EventEmitter<FullFacePipelineEvents> {
694
681
  /**
695
682
  * Start frame animation loop
696
683
  *
697
- * Uses critically damped spring smoother to produce continuous output
698
- * at render rate (60fps), even between inference batches (~30fps bursts).
699
- * Springs interpolate toward the latest inference target, and decay
700
- * to neutral when inference stalls.
684
+ * Polls A2EProcessor at render rate (60fps) for the latest inference frame
685
+ * matching the current AudioContext time. Between inference batches (~30fps
686
+ * bursts), getFrameForTime() holds the last frame.
701
687
  */
702
688
  private startFrameLoop;
703
689
  /**
package/dist/index.d.ts CHANGED
@@ -587,18 +587,7 @@ interface FullFacePipelineOptions {
587
587
  /** Per-character expression weight scaling */
588
588
  profile?: ExpressionProfile;
589
589
  /**
590
- * Spring smoothing halflife in seconds.
591
- * Controls how quickly blendshapes converge to inference targets.
592
- * Lower = snappier but more jittery. Higher = smoother but laggy.
593
- * Set to 0 to disable smoothing (raw frame pass-through).
594
- *
595
- * Default: 0.06 (60ms)
596
- */
597
- smoothingHalflife?: number;
598
- /**
599
- * Time in ms with no new inference frames before decaying to neutral.
600
- * When exceeded, spring targets are set to 0 and the face smoothly
601
- * relaxes rather than freezing on the last frame.
590
+ * Time in ms with no new inference frames before logging a stale warning.
602
591
  *
603
592
  * Must be larger than the inter-batch gap (chunkSize/sampleRate + inference time).
604
593
  * Default: 2000
@@ -646,7 +635,6 @@ declare class FullFacePipeline extends EventEmitter<FullFacePipelineEvents> {
646
635
  private scheduler;
647
636
  private coalescer;
648
637
  private processor;
649
- private smoother;
650
638
  private playbackStarted;
651
639
  private monitorInterval;
652
640
  private frameAnimationId;
@@ -654,7 +642,6 @@ declare class FullFacePipeline extends EventEmitter<FullFacePipelineEvents> {
654
642
  private lastKnownLamFrame;
655
643
  private staleWarningEmitted;
656
644
  private readonly staleThresholdMs;
657
- private lastFrameLoopTime;
658
645
  private frameLoopCount;
659
646
  private profile;
660
647
  constructor(options: FullFacePipelineOptions);
@@ -694,10 +681,9 @@ declare class FullFacePipeline extends EventEmitter<FullFacePipelineEvents> {
694
681
  /**
695
682
  * Start frame animation loop
696
683
  *
697
- * Uses critically damped spring smoother to produce continuous output
698
- * at render rate (60fps), even between inference batches (~30fps bursts).
699
- * Springs interpolate toward the latest inference target, and decay
700
- * to neutral when inference stalls.
684
+ * Polls A2EProcessor at render rate (60fps) for the latest inference frame
685
+ * matching the current AudioContext time. Between inference batches (~30fps
686
+ * bursts), getFrameForTime() holds the last frame.
701
687
  */
702
688
  private startFrameLoop;
703
689
  /**
package/dist/index.js CHANGED
@@ -1171,80 +1171,6 @@ var A2EProcessor = class {
1171
1171
  }
1172
1172
  };
1173
1173
 
1174
- // src/inference/BlendshapeSmoother.ts
1175
- var NUM_BLENDSHAPES = 52;
1176
- var BlendshapeSmoother = class {
1177
- constructor(config) {
1178
- /** Whether any target has been set */
1179
- this._hasTarget = false;
1180
- this.halflife = config?.halflife ?? 0.06;
1181
- this.values = new Float32Array(NUM_BLENDSHAPES);
1182
- this.velocities = new Float32Array(NUM_BLENDSHAPES);
1183
- this.targets = new Float32Array(NUM_BLENDSHAPES);
1184
- }
1185
- /** Whether a target frame has been set (false until first setTarget call) */
1186
- get hasTarget() {
1187
- return this._hasTarget;
1188
- }
1189
- /**
1190
- * Set new target frame from inference output.
1191
- * Springs will converge toward these values on subsequent update() calls.
1192
- */
1193
- setTarget(frame) {
1194
- this.targets.set(frame);
1195
- this._hasTarget = true;
1196
- }
1197
- /**
1198
- * Advance all 52 springs by `dt` seconds and return the smoothed frame.
1199
- *
1200
- * Call this every render frame (e.g., inside requestAnimationFrame).
1201
- * Returns the internal values buffer — do NOT mutate the returned array.
1202
- *
1203
- * @param dt - Time step in seconds (e.g., 1/60 for 60fps)
1204
- * @returns Smoothed blendshape values (Float32Array of 52)
1205
- */
1206
- update(dt) {
1207
- if (!this._hasTarget) {
1208
- return this.values;
1209
- }
1210
- if (this.halflife <= 0) {
1211
- this.values.set(this.targets);
1212
- this.velocities.fill(0);
1213
- return this.values;
1214
- }
1215
- const damping = Math.LN2 / this.halflife;
1216
- const eydt = Math.exp(-damping * dt);
1217
- for (let i = 0; i < NUM_BLENDSHAPES; i++) {
1218
- const j0 = this.values[i] - this.targets[i];
1219
- const j1 = this.velocities[i] + j0 * damping;
1220
- this.values[i] = eydt * (j0 + j1 * dt) + this.targets[i];
1221
- this.velocities[i] = eydt * (this.velocities[i] - j1 * damping * dt);
1222
- this.values[i] = Math.max(0, Math.min(1, this.values[i]));
1223
- }
1224
- return this.values;
1225
- }
1226
- /**
1227
- * Decay all spring targets to neutral (0).
1228
- *
1229
- * Call when inference stalls (no new frames for threshold duration).
1230
- * The springs will smoothly close the mouth / relax the face over
1231
- * the halflife period rather than freezing.
1232
- */
1233
- decayToNeutral() {
1234
- this.targets.fill(0);
1235
- }
1236
- /**
1237
- * Reset all state (values, velocities, targets).
1238
- * Call when starting a new playback session.
1239
- */
1240
- reset() {
1241
- this.values.fill(0);
1242
- this.velocities.fill(0);
1243
- this.targets.fill(0);
1244
- this._hasTarget = false;
1245
- }
1246
- };
1247
-
1248
1174
  // src/telemetry/exporters/console.ts
1249
1175
  var ConsoleExporter = class {
1250
1176
  constructor(options = {}) {
@@ -3301,16 +3227,11 @@ var FullFacePipeline = class extends EventEmitter {
3301
3227
  this.lastNewFrameTime = 0;
3302
3228
  this.lastKnownLamFrame = null;
3303
3229
  this.staleWarningEmitted = false;
3304
- // Frame loop timing (for dt calculation)
3305
- this.lastFrameLoopTime = 0;
3306
3230
  // Diagnostic logging counter
3307
3231
  this.frameLoopCount = 0;
3308
3232
  const sampleRate = options.sampleRate ?? 16e3;
3309
3233
  this.profile = options.profile ?? {};
3310
3234
  this.staleThresholdMs = options.staleThresholdMs ?? 2e3;
3311
- this.smoother = new BlendshapeSmoother({
3312
- halflife: options.smoothingHalflife ?? 0.06
3313
- });
3314
3235
  const isCpuModel = options.lam.modelId === "wav2arkit_cpu";
3315
3236
  const chunkSize = options.chunkSize ?? options.lam.chunkSize ?? 16e3;
3316
3237
  const chunkAccumulationMs = chunkSize / sampleRate * 1e3;
@@ -3393,9 +3314,7 @@ var FullFacePipeline = class extends EventEmitter {
3393
3314
  this.lastNewFrameTime = 0;
3394
3315
  this.lastKnownLamFrame = null;
3395
3316
  this.staleWarningEmitted = false;
3396
- this.lastFrameLoopTime = 0;
3397
3317
  this.frameLoopCount = 0;
3398
- this.smoother.reset();
3399
3318
  this.scheduler.warmup();
3400
3319
  this.startFrameLoop();
3401
3320
  this.startMonitoring();
@@ -3430,22 +3349,16 @@ var FullFacePipeline = class extends EventEmitter {
3430
3349
  /**
3431
3350
  * Start frame animation loop
3432
3351
  *
3433
- * Uses critically damped spring smoother to produce continuous output
3434
- * at render rate (60fps), even between inference batches (~30fps bursts).
3435
- * Springs interpolate toward the latest inference target, and decay
3436
- * to neutral when inference stalls.
3352
+ * Polls A2EProcessor at render rate (60fps) for the latest inference frame
3353
+ * matching the current AudioContext time. Between inference batches (~30fps
3354
+ * bursts), getFrameForTime() holds the last frame.
3437
3355
  */
3438
3356
  startFrameLoop() {
3439
- this.lastFrameLoopTime = 0;
3440
3357
  const updateFrame = () => {
3441
- const now = performance.now() / 1e3;
3442
- const dt = this.lastFrameLoopTime > 0 ? now - this.lastFrameLoopTime : 1 / 60;
3443
- this.lastFrameLoopTime = now;
3444
3358
  this.frameLoopCount++;
3445
3359
  const currentTime = this.scheduler.getCurrentTime();
3446
3360
  const lamFrame = this.processor.getFrameForTime(currentTime);
3447
3361
  if (lamFrame && lamFrame !== this.lastKnownLamFrame) {
3448
- this.smoother.setTarget(lamFrame);
3449
3362
  this.lastNewFrameTime = performance.now();
3450
3363
  this.lastKnownLamFrame = lamFrame;
3451
3364
  this.staleWarningEmitted = false;
@@ -3465,17 +3378,15 @@ var FullFacePipeline = class extends EventEmitter {
3465
3378
  currentTime: currentTime.toFixed(3),
3466
3379
  playbackEndTime: this.scheduler.getPlaybackEndTime().toFixed(3),
3467
3380
  queuedFrames: this.processor.queuedFrameCount,
3468
- hasTarget: this.smoother.hasTarget,
3469
3381
  playbackStarted: this.playbackStarted,
3470
3382
  msSinceNewFrame: this.lastNewFrameTime > 0 ? Math.round(performance.now() - this.lastNewFrameTime) : -1,
3471
3383
  processorFill: this.processor.fillLevel.toFixed(2)
3472
3384
  });
3473
3385
  }
3474
3386
  if (this.playbackStarted && this.lastNewFrameTime > 0 && performance.now() - this.lastNewFrameTime > this.staleThresholdMs) {
3475
- this.smoother.decayToNeutral();
3476
3387
  if (!this.staleWarningEmitted) {
3477
3388
  this.staleWarningEmitted = true;
3478
- logger4.warn("A2E stalled \u2014 decaying to neutral", {
3389
+ logger4.warn("A2E stalled \u2014 no new inference frames", {
3479
3390
  staleDurationMs: Math.round(performance.now() - this.lastNewFrameTime),
3480
3391
  queuedFrames: this.processor.queuedFrameCount
3481
3392
  });
@@ -3514,12 +3425,10 @@ var FullFacePipeline = class extends EventEmitter {
3514
3425
  await this.scheduler.cancelAll(fadeOutMs);
3515
3426
  this.coalescer.reset();
3516
3427
  this.processor.reset();
3517
- this.smoother.reset();
3518
3428
  this.playbackStarted = false;
3519
3429
  this.lastNewFrameTime = 0;
3520
3430
  this.lastKnownLamFrame = null;
3521
3431
  this.staleWarningEmitted = false;
3522
- this.lastFrameLoopTime = 0;
3523
3432
  this.emit("playback_complete", void 0);
3524
3433
  }
3525
3434
  /**
@@ -7405,6 +7314,80 @@ var A2EWithFallback = class {
7405
7314
  }
7406
7315
  };
7407
7316
 
7317
+ // src/inference/BlendshapeSmoother.ts
7318
+ var NUM_BLENDSHAPES = 52;
7319
+ var BlendshapeSmoother = class {
7320
+ constructor(config) {
7321
+ /** Whether any target has been set */
7322
+ this._hasTarget = false;
7323
+ this.halflife = config?.halflife ?? 0.06;
7324
+ this.values = new Float32Array(NUM_BLENDSHAPES);
7325
+ this.velocities = new Float32Array(NUM_BLENDSHAPES);
7326
+ this.targets = new Float32Array(NUM_BLENDSHAPES);
7327
+ }
7328
+ /** Whether a target frame has been set (false until first setTarget call) */
7329
+ get hasTarget() {
7330
+ return this._hasTarget;
7331
+ }
7332
+ /**
7333
+ * Set new target frame from inference output.
7334
+ * Springs will converge toward these values on subsequent update() calls.
7335
+ */
7336
+ setTarget(frame) {
7337
+ this.targets.set(frame);
7338
+ this._hasTarget = true;
7339
+ }
7340
+ /**
7341
+ * Advance all 52 springs by `dt` seconds and return the smoothed frame.
7342
+ *
7343
+ * Call this every render frame (e.g., inside requestAnimationFrame).
7344
+ * Returns the internal values buffer — do NOT mutate the returned array.
7345
+ *
7346
+ * @param dt - Time step in seconds (e.g., 1/60 for 60fps)
7347
+ * @returns Smoothed blendshape values (Float32Array of 52)
7348
+ */
7349
+ update(dt) {
7350
+ if (!this._hasTarget) {
7351
+ return this.values;
7352
+ }
7353
+ if (this.halflife <= 0) {
7354
+ this.values.set(this.targets);
7355
+ this.velocities.fill(0);
7356
+ return this.values;
7357
+ }
7358
+ const damping = Math.LN2 / this.halflife;
7359
+ const eydt = Math.exp(-damping * dt);
7360
+ for (let i = 0; i < NUM_BLENDSHAPES; i++) {
7361
+ const j0 = this.values[i] - this.targets[i];
7362
+ const j1 = this.velocities[i] + j0 * damping;
7363
+ this.values[i] = eydt * (j0 + j1 * dt) + this.targets[i];
7364
+ this.velocities[i] = eydt * (this.velocities[i] - j1 * damping * dt);
7365
+ this.values[i] = Math.max(0, Math.min(1, this.values[i]));
7366
+ }
7367
+ return this.values;
7368
+ }
7369
+ /**
7370
+ * Decay all spring targets to neutral (0).
7371
+ *
7372
+ * Call when inference stalls (no new frames for threshold duration).
7373
+ * The springs will smoothly close the mouth / relax the face over
7374
+ * the halflife period rather than freezing.
7375
+ */
7376
+ decayToNeutral() {
7377
+ this.targets.fill(0);
7378
+ }
7379
+ /**
7380
+ * Reset all state (values, velocities, targets).
7381
+ * Call when starting a new playback session.
7382
+ */
7383
+ reset() {
7384
+ this.values.fill(0);
7385
+ this.velocities.fill(0);
7386
+ this.targets.fill(0);
7387
+ this._hasTarget = false;
7388
+ }
7389
+ };
7390
+
7408
7391
  // src/animation/audioEnergy.ts
7409
7392
  function calculateRMS(samples) {
7410
7393
  if (samples.length === 0) return 0;