@omote/core 0.9.5 → 0.9.7

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
@@ -678,6 +678,13 @@ declare class PlaybackPipeline extends EventEmitter<PlaybackPipelineEvents> {
678
678
  private neutralTransitionFrame;
679
679
  private neutralTransitionStart;
680
680
  private neutralAnimationId;
681
+ private static readonly RAMP_IN_HALFLIFE;
682
+ private static readonly RAMP_IN_DURATION_MS;
683
+ private rampInSmoother;
684
+ private rampInActive;
685
+ private rampInLastTime;
686
+ private rampInStartTime;
687
+ private readonly _rampInBuffer;
681
688
  private _currentFrame;
682
689
  private _currentRawFrame;
683
690
  private _emotion;
package/dist/index.d.ts CHANGED
@@ -678,6 +678,13 @@ declare class PlaybackPipeline extends EventEmitter<PlaybackPipelineEvents> {
678
678
  private neutralTransitionFrame;
679
679
  private neutralTransitionStart;
680
680
  private neutralAnimationId;
681
+ private static readonly RAMP_IN_HALFLIFE;
682
+ private static readonly RAMP_IN_DURATION_MS;
683
+ private rampInSmoother;
684
+ private rampInActive;
685
+ private rampInLastTime;
686
+ private rampInStartTime;
687
+ private readonly _rampInBuffer;
681
688
  private _currentFrame;
682
689
  private _currentRawFrame;
683
690
  private _emotion;
package/dist/index.js CHANGED
@@ -1885,18 +1885,19 @@ var _A2EProcessor = class _A2EProcessor {
1885
1885
  */
1886
1886
  async flush() {
1887
1887
  if (this.disposed || this.writeOffset === 0) return;
1888
+ const actualSamples = this.writeOffset;
1888
1889
  const padded = new Float32Array(this.chunkSize);
1889
- padded.set(this.buffer.subarray(0, this.writeOffset), 0);
1890
+ padded.set(this.buffer.subarray(0, actualSamples), 0);
1890
1891
  const chunkTimestamp = this.bufferStartTime > 0 ? this.bufferStartTime : void 0;
1891
1892
  logger4.info("flush: routing through drain pipeline", {
1892
- actualSamples: this.writeOffset,
1893
+ actualSamples,
1893
1894
  chunkTimestamp: chunkTimestamp?.toFixed(3),
1894
1895
  pendingChunks: this.pendingChunks.length,
1895
1896
  inferenceRunning: this.inferenceRunning
1896
1897
  });
1897
1898
  this.writeOffset = 0;
1898
1899
  this.bufferStartTime = 0;
1899
- this.pendingChunks.push({ chunk: padded, timestamp: chunkTimestamp });
1900
+ this.pendingChunks.push({ chunk: padded, timestamp: chunkTimestamp, actualSamples });
1900
1901
  this.drainPendingChunks();
1901
1902
  }
1902
1903
  /**
@@ -2054,14 +2055,15 @@ var _A2EProcessor = class _A2EProcessor {
2054
2055
  logger4.info("drainPendingChunks starting", { pendingChunks: this.pendingChunks.length });
2055
2056
  const processNext = async () => {
2056
2057
  while (this.pendingChunks.length > 0 && !this.disposed) {
2057
- const { chunk, timestamp } = this.pendingChunks.shift();
2058
+ const { chunk, timestamp, actualSamples } = this.pendingChunks.shift();
2058
2059
  try {
2059
2060
  const t0 = getClock().now();
2060
2061
  const result = await this.backend.infer(chunk, this.identityIndex);
2061
2062
  const inferMs = Math.round(getClock().now() - t0);
2062
- const actualDuration = chunk.length / this.sampleRate;
2063
+ const effectiveSamples = actualSamples ?? chunk.length;
2064
+ const actualDuration = effectiveSamples / this.sampleRate;
2063
2065
  const actualFrameCount = Math.ceil(actualDuration * FRAME_RATE);
2064
- const framesToQueue = Math.min(actualFrameCount, result.blendshapes.length);
2066
+ const framesToQueue = Math.min(Math.max(1, actualFrameCount), result.blendshapes.length);
2065
2067
  logger4.info("Inference complete", {
2066
2068
  inferMs,
2067
2069
  modelFrames: result.blendshapes.length,
@@ -2314,7 +2316,7 @@ function applyProfile(raw, profile, out) {
2314
2316
 
2315
2317
  // src/audio/PlaybackPipeline.ts
2316
2318
  var logger5 = createLogger("PlaybackPipeline");
2317
- var PlaybackPipeline = class extends EventEmitter {
2319
+ var _PlaybackPipeline = class _PlaybackPipeline extends EventEmitter {
2318
2320
  constructor(config) {
2319
2321
  super();
2320
2322
  this.config = config;
@@ -2333,6 +2335,10 @@ var PlaybackPipeline = class extends EventEmitter {
2333
2335
  this.neutralTransitionFrame = null;
2334
2336
  this.neutralTransitionStart = 0;
2335
2337
  this.neutralAnimationId = null;
2338
+ this.rampInActive = false;
2339
+ this.rampInLastTime = 0;
2340
+ this.rampInStartTime = 0;
2341
+ this._rampInBuffer = new Float32Array(52);
2336
2342
  // Current frame refs
2337
2343
  this._currentFrame = null;
2338
2344
  this._currentRawFrame = null;
@@ -2359,6 +2365,7 @@ var PlaybackPipeline = class extends EventEmitter {
2359
2365
  modelId: config.lam.modelId,
2360
2366
  neutralTransitionEnabled: this.neutralTransitionEnabled
2361
2367
  });
2368
+ this.rampInSmoother = new BlendshapeSmoother({ halflife: _PlaybackPipeline.RAMP_IN_HALFLIFE });
2362
2369
  this.scheduler = new AudioScheduler({
2363
2370
  sampleRate: this.sampleRate,
2364
2371
  initialLookaheadSec: audioDelayMs / 1e3
@@ -2426,6 +2433,10 @@ var PlaybackPipeline = class extends EventEmitter {
2426
2433
  this.frameLoopCount = 0;
2427
2434
  this._currentFrame = null;
2428
2435
  this._currentRawFrame = null;
2436
+ this.rampInSmoother.reset();
2437
+ this.rampInActive = true;
2438
+ this.rampInLastTime = 0;
2439
+ this.rampInStartTime = 0;
2429
2440
  this.cancelNeutralTransition();
2430
2441
  this.scheduler.warmup();
2431
2442
  this.sessionStartTime = getClock().now();
@@ -2562,17 +2573,36 @@ var PlaybackPipeline = class extends EventEmitter {
2562
2573
  }
2563
2574
  }
2564
2575
  if (lamFrame) {
2565
- const scaled = applyProfile(lamFrame, this.profile, this._profileBuffer);
2576
+ let effectiveFrame = lamFrame;
2577
+ if (this.rampInActive) {
2578
+ const now = getClock().now();
2579
+ if (this.rampInLastTime === 0) {
2580
+ this.rampInStartTime = now;
2581
+ this.rampInLastTime = now;
2582
+ }
2583
+ this.rampInSmoother.setTarget(lamFrame);
2584
+ const dt = (now - this.rampInLastTime) / 1e3;
2585
+ this.rampInLastTime = now;
2586
+ if (dt > 0) {
2587
+ const smoothed = this.rampInSmoother.update(dt);
2588
+ this._rampInBuffer.set(smoothed);
2589
+ effectiveFrame = this._rampInBuffer;
2590
+ }
2591
+ if (now - this.rampInStartTime > _PlaybackPipeline.RAMP_IN_DURATION_MS) {
2592
+ this.rampInActive = false;
2593
+ }
2594
+ }
2595
+ const scaled = applyProfile(effectiveFrame, this.profile, this._profileBuffer);
2566
2596
  this._currentFrame = scaled;
2567
- this._currentRawFrame = lamFrame;
2597
+ this._currentRawFrame = effectiveFrame;
2568
2598
  const fullFrame = {
2569
2599
  blendshapes: scaled,
2570
- rawBlendshapes: lamFrame,
2600
+ rawBlendshapes: effectiveFrame,
2571
2601
  timestamp: currentTime,
2572
2602
  emotion: this._emotion ?? void 0
2573
2603
  };
2574
2604
  this.emit("frame", fullFrame);
2575
- this.emit("frame:raw", lamFrame);
2605
+ this.emit("frame:raw", effectiveFrame);
2576
2606
  }
2577
2607
  this.frameAnimationId = requestAnimationFrame(updateFrame);
2578
2608
  };
@@ -2672,6 +2702,11 @@ var PlaybackPipeline = class extends EventEmitter {
2672
2702
  this.emit("state", state);
2673
2703
  }
2674
2704
  };
2705
+ // Ramp-in smoother: smooths neutral → first inference frame at start-of-speech
2706
+ _PlaybackPipeline.RAMP_IN_HALFLIFE = 0.05;
2707
+ // 50ms — snappy but smooth
2708
+ _PlaybackPipeline.RAMP_IN_DURATION_MS = 150;
2709
+ var PlaybackPipeline = _PlaybackPipeline;
2675
2710
 
2676
2711
  // src/audio/TTSPlayback.ts
2677
2712
  var logger6 = createLogger("TTSPlayback");