@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.mjs CHANGED
@@ -806,18 +806,19 @@ var _A2EProcessor = class _A2EProcessor {
806
806
  */
807
807
  async flush() {
808
808
  if (this.disposed || this.writeOffset === 0) return;
809
+ const actualSamples = this.writeOffset;
809
810
  const padded = new Float32Array(this.chunkSize);
810
- padded.set(this.buffer.subarray(0, this.writeOffset), 0);
811
+ padded.set(this.buffer.subarray(0, actualSamples), 0);
811
812
  const chunkTimestamp = this.bufferStartTime > 0 ? this.bufferStartTime : void 0;
812
813
  logger4.info("flush: routing through drain pipeline", {
813
- actualSamples: this.writeOffset,
814
+ actualSamples,
814
815
  chunkTimestamp: chunkTimestamp?.toFixed(3),
815
816
  pendingChunks: this.pendingChunks.length,
816
817
  inferenceRunning: this.inferenceRunning
817
818
  });
818
819
  this.writeOffset = 0;
819
820
  this.bufferStartTime = 0;
820
- this.pendingChunks.push({ chunk: padded, timestamp: chunkTimestamp });
821
+ this.pendingChunks.push({ chunk: padded, timestamp: chunkTimestamp, actualSamples });
821
822
  this.drainPendingChunks();
822
823
  }
823
824
  /**
@@ -975,14 +976,15 @@ var _A2EProcessor = class _A2EProcessor {
975
976
  logger4.info("drainPendingChunks starting", { pendingChunks: this.pendingChunks.length });
976
977
  const processNext = async () => {
977
978
  while (this.pendingChunks.length > 0 && !this.disposed) {
978
- const { chunk, timestamp } = this.pendingChunks.shift();
979
+ const { chunk, timestamp, actualSamples } = this.pendingChunks.shift();
979
980
  try {
980
981
  const t0 = getClock().now();
981
982
  const result = await this.backend.infer(chunk, this.identityIndex);
982
983
  const inferMs = Math.round(getClock().now() - t0);
983
- const actualDuration = chunk.length / this.sampleRate;
984
+ const effectiveSamples = actualSamples ?? chunk.length;
985
+ const actualDuration = effectiveSamples / this.sampleRate;
984
986
  const actualFrameCount = Math.ceil(actualDuration * FRAME_RATE);
985
- const framesToQueue = Math.min(actualFrameCount, result.blendshapes.length);
987
+ const framesToQueue = Math.min(Math.max(1, actualFrameCount), result.blendshapes.length);
986
988
  logger4.info("Inference complete", {
987
989
  inferMs,
988
990
  modelFrames: result.blendshapes.length,
@@ -1235,7 +1237,7 @@ function applyProfile(raw, profile, out) {
1235
1237
 
1236
1238
  // src/audio/PlaybackPipeline.ts
1237
1239
  var logger5 = createLogger("PlaybackPipeline");
1238
- var PlaybackPipeline = class extends EventEmitter {
1240
+ var _PlaybackPipeline = class _PlaybackPipeline extends EventEmitter {
1239
1241
  constructor(config) {
1240
1242
  super();
1241
1243
  this.config = config;
@@ -1254,6 +1256,10 @@ var PlaybackPipeline = class extends EventEmitter {
1254
1256
  this.neutralTransitionFrame = null;
1255
1257
  this.neutralTransitionStart = 0;
1256
1258
  this.neutralAnimationId = null;
1259
+ this.rampInActive = false;
1260
+ this.rampInLastTime = 0;
1261
+ this.rampInStartTime = 0;
1262
+ this._rampInBuffer = new Float32Array(52);
1257
1263
  // Current frame refs
1258
1264
  this._currentFrame = null;
1259
1265
  this._currentRawFrame = null;
@@ -1280,6 +1286,7 @@ var PlaybackPipeline = class extends EventEmitter {
1280
1286
  modelId: config.lam.modelId,
1281
1287
  neutralTransitionEnabled: this.neutralTransitionEnabled
1282
1288
  });
1289
+ this.rampInSmoother = new BlendshapeSmoother({ halflife: _PlaybackPipeline.RAMP_IN_HALFLIFE });
1283
1290
  this.scheduler = new AudioScheduler({
1284
1291
  sampleRate: this.sampleRate,
1285
1292
  initialLookaheadSec: audioDelayMs / 1e3
@@ -1347,6 +1354,10 @@ var PlaybackPipeline = class extends EventEmitter {
1347
1354
  this.frameLoopCount = 0;
1348
1355
  this._currentFrame = null;
1349
1356
  this._currentRawFrame = null;
1357
+ this.rampInSmoother.reset();
1358
+ this.rampInActive = true;
1359
+ this.rampInLastTime = 0;
1360
+ this.rampInStartTime = 0;
1350
1361
  this.cancelNeutralTransition();
1351
1362
  this.scheduler.warmup();
1352
1363
  this.sessionStartTime = getClock().now();
@@ -1483,17 +1494,36 @@ var PlaybackPipeline = class extends EventEmitter {
1483
1494
  }
1484
1495
  }
1485
1496
  if (lamFrame) {
1486
- const scaled = applyProfile(lamFrame, this.profile, this._profileBuffer);
1497
+ let effectiveFrame = lamFrame;
1498
+ if (this.rampInActive) {
1499
+ const now = getClock().now();
1500
+ if (this.rampInLastTime === 0) {
1501
+ this.rampInStartTime = now;
1502
+ this.rampInLastTime = now;
1503
+ }
1504
+ this.rampInSmoother.setTarget(lamFrame);
1505
+ const dt = (now - this.rampInLastTime) / 1e3;
1506
+ this.rampInLastTime = now;
1507
+ if (dt > 0) {
1508
+ const smoothed = this.rampInSmoother.update(dt);
1509
+ this._rampInBuffer.set(smoothed);
1510
+ effectiveFrame = this._rampInBuffer;
1511
+ }
1512
+ if (now - this.rampInStartTime > _PlaybackPipeline.RAMP_IN_DURATION_MS) {
1513
+ this.rampInActive = false;
1514
+ }
1515
+ }
1516
+ const scaled = applyProfile(effectiveFrame, this.profile, this._profileBuffer);
1487
1517
  this._currentFrame = scaled;
1488
- this._currentRawFrame = lamFrame;
1518
+ this._currentRawFrame = effectiveFrame;
1489
1519
  const fullFrame = {
1490
1520
  blendshapes: scaled,
1491
- rawBlendshapes: lamFrame,
1521
+ rawBlendshapes: effectiveFrame,
1492
1522
  timestamp: currentTime,
1493
1523
  emotion: this._emotion ?? void 0
1494
1524
  };
1495
1525
  this.emit("frame", fullFrame);
1496
- this.emit("frame:raw", lamFrame);
1526
+ this.emit("frame:raw", effectiveFrame);
1497
1527
  }
1498
1528
  this.frameAnimationId = requestAnimationFrame(updateFrame);
1499
1529
  };
@@ -1593,6 +1623,11 @@ var PlaybackPipeline = class extends EventEmitter {
1593
1623
  this.emit("state", state);
1594
1624
  }
1595
1625
  };
1626
+ // Ramp-in smoother: smooths neutral → first inference frame at start-of-speech
1627
+ _PlaybackPipeline.RAMP_IN_HALFLIFE = 0.05;
1628
+ // 50ms — snappy but smooth
1629
+ _PlaybackPipeline.RAMP_IN_DURATION_MS = 150;
1630
+ var PlaybackPipeline = _PlaybackPipeline;
1596
1631
 
1597
1632
  // src/audio/TTSPlayback.ts
1598
1633
  var logger6 = createLogger("TTSPlayback");